tangled
alpha
login
or
join now
rockorager.dev
/
comlink
2
fork
atom
an experimental irc client
2
fork
atom
overview
issues
pulls
pipelines
wip: initial vxfw compilation
rockorager.dev
1 year ago
01b1b967
fa4e3f40
+1267
-693
6 changed files
expand all
collapse all
unified
split
build.zig
build.zig.zon
src
app.zig
irc.zig
lua.zig
main.zig
-1
build.zig
···
34
34
const vaxis_dep = b.dependency("vaxis", .{
35
35
.target = target,
36
36
.optimize = optimize,
37
37
-
.libxev = false,
38
37
});
39
38
40
39
const zeit_dep = b.dependency("zeit", .{
+2
-2
build.zig.zon
···
7
7
.hash = "1220affeb3fe37ef09411b5a213b5fdf9bb6568e9913bade204694648983a8b2776d",
8
8
},
9
9
.vaxis = .{
10
10
-
.url = "git+https://github.com/rockorager/libvaxis?ref=v0.5.1#2ab3b46e89bced844b60601c7ab9961420d15994",
11
11
-
.hash = "1220de23a3240e503397ea579de4fd85db422f537e10036ef74717c50164475813ce",
10
10
+
.url = "git+https://github.com/rockorager/libvaxis#0eaf6226b2dd58720c5954d3646d6782e0c063f5",
11
11
+
.hash = "12208b6363d1bff963081ee4cba5c8be9f782e89ed7604e5ceab61999b1a7980f791",
12
12
},
13
13
.zeit = .{
14
14
.url = "git+https://github.com/rockorager/zeit?ref=main#d943bc4bfe9e18490460dfdd64f48e997065eba8",
+721
-646
src/app.zig
···
11
11
const irc = comlink.irc;
12
12
const lua = comlink.lua;
13
13
const mem = std.mem;
14
14
+
const vxfw = vaxis.vxfw;
14
15
15
16
const assert = std.debug.assert;
16
17
18
18
+
const Allocator = std.mem.Allocator;
17
19
const Base64Encoder = std.base64.standard.Encoder;
18
20
const Bind = comlink.Bind;
19
21
const Completer = comlink.Completer;
···
65
67
env: std.process.EnvMap,
66
68
/// Local timezone
67
69
tz: zeit.TimeZone,
68
68
-
/// Instance of vaxis
69
69
-
vx: vaxis.Vaxis,
70
70
-
/// The tty we are talking to
71
71
-
tty: vaxis.Tty,
72
70
73
71
state: State = .{},
74
72
···
80
78
81
79
paste_buffer: std.ArrayList(u8),
82
80
81
81
+
lua: *Lua,
82
82
+
83
83
+
write_queue: comlink.WriteQueue,
84
84
+
write_thread: std.Thread,
85
85
+
83
86
/// initialize vaxis, lua state
84
84
-
pub fn init(alloc: std.mem.Allocator) !App {
85
85
-
const vx = try vaxis.init(alloc, .{});
86
86
-
const env = try std.process.getEnvMap(alloc);
87
87
-
var app: App = .{
88
88
-
.alloc = alloc,
89
89
-
.clients = std.ArrayList(*irc.Client).init(alloc),
90
90
-
.env = env,
91
91
-
.vx = vx,
92
92
-
.tty = try vaxis.Tty.init(),
93
93
-
.binds = try std.ArrayList(Bind).initCapacity(alloc, 16),
94
94
-
.paste_buffer = std.ArrayList(u8).init(alloc),
95
95
-
.tz = try zeit.local(alloc, &env),
87
87
+
pub fn init(self: *App, gpa: std.mem.Allocator) !void {
88
88
+
self.* = .{
89
89
+
.alloc = gpa,
90
90
+
.clients = std.ArrayList(*irc.Client).init(gpa),
91
91
+
.env = try std.process.getEnvMap(gpa),
92
92
+
.binds = try std.ArrayList(Bind).initCapacity(gpa, 16),
93
93
+
.paste_buffer = std.ArrayList(u8).init(gpa),
94
94
+
.tz = try zeit.local(gpa, null),
95
95
+
.lua = undefined,
96
96
+
.write_queue = .{},
97
97
+
.write_thread = undefined,
96
98
};
97
99
98
98
-
try app.binds.append(.{
100
100
+
self.lua = try Lua.init(&self.alloc);
101
101
+
self.write_thread = try std.Thread.spawn(.{}, writeLoop, .{ self.alloc, &self.write_queue });
102
102
+
103
103
+
try lua.init(self);
104
104
+
105
105
+
try self.binds.append(.{
99
106
.key = .{
100
107
.codepoint = 'c',
101
108
.mods = .{ .ctrl = true },
102
109
},
103
110
.command = .quit,
104
111
});
105
105
-
try app.binds.append(.{
112
112
+
try self.binds.append(.{
106
113
.key = .{
107
114
.codepoint = vaxis.Key.up,
108
115
.mods = .{ .alt = true },
109
116
},
110
117
.command = .@"prev-channel",
111
118
});
112
112
-
try app.binds.append(.{
119
119
+
try self.binds.append(.{
113
120
.key = .{
114
121
.codepoint = vaxis.Key.down,
115
122
.mods = .{ .alt = true },
116
123
},
117
124
.command = .@"next-channel",
118
125
});
119
119
-
try app.binds.append(.{
126
126
+
try self.binds.append(.{
120
127
.key = .{
121
128
.codepoint = 'l',
122
129
.mods = .{ .ctrl = true },
···
125
132
});
126
133
127
134
// Get our system tls certs
128
128
-
try app.bundle.rescan(alloc);
129
129
-
130
130
-
return app;
135
135
+
try self.bundle.rescan(gpa);
131
136
}
132
137
133
138
/// close the application. This closes the TUI, disconnects clients, and cleans
···
135
140
pub fn deinit(self: *App) void {
136
141
if (self.deinited) return;
137
142
self.deinited = true;
143
143
+
// Push a join command to the write thread
144
144
+
self.write_queue.push(.join);
138
145
139
146
// clean up clients
140
147
{
···
153
160
}
154
161
155
162
self.bundle.deinit(self.alloc);
156
156
-
self.vx.deinit(self.alloc, self.tty.anyWriter());
157
157
-
self.tty.deinit();
158
163
159
164
if (self.completer) |*completer| completer.deinit();
160
165
self.binds.deinit();
161
166
self.paste_buffer.deinit();
162
167
self.tz.deinit();
168
168
+
169
169
+
// Join the write thread
170
170
+
self.write_thread.join();
163
171
self.env.deinit();
172
172
+
self.lua.deinit();
164
173
}
165
174
166
166
-
pub fn run(self: *App, lua_state: *Lua) !void {
167
167
-
const writer = self.tty.anyWriter();
175
175
+
pub fn widget(self: *App) vxfw.Widget {
176
176
+
return .{
177
177
+
.userdata = self,
178
178
+
.captureHandler = App.typeErasedCaptureHandler,
179
179
+
.eventHandler = App.typeErasedEventHandler,
180
180
+
.drawFn = App.typeErasedDrawFn,
181
181
+
};
182
182
+
}
168
183
169
169
-
var loop: comlink.EventLoop = .{ .vaxis = &self.vx, .tty = &self.tty };
170
170
-
try loop.init();
171
171
-
try loop.start();
172
172
-
defer loop.stop();
173
173
-
174
174
-
try self.vx.enterAltScreen(writer);
175
175
-
try self.vx.queryTerminal(writer, 1 * std.time.ns_per_s);
176
176
-
try self.vx.setMouseMode(writer, true);
177
177
-
try self.vx.setBracketedPaste(writer, true);
178
178
-
179
179
-
// start our write thread
180
180
-
var write_queue: comlink.WriteQueue = .{};
181
181
-
const write_thread = try std.Thread.spawn(.{}, writeLoop, .{ self.alloc, &write_queue });
182
182
-
defer {
183
183
-
write_queue.push(.join);
184
184
-
write_thread.join();
184
184
+
fn typeErasedCaptureHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
185
185
+
// const self: *App = @ptrCast(@alignCast(ptr));
186
186
+
_ = ptr;
187
187
+
switch (event) {
188
188
+
.key_press => |key| {
189
189
+
if (key.matches('c', .{ .ctrl = true })) {
190
190
+
ctx.quit = true;
191
191
+
}
192
192
+
},
193
193
+
else => {},
185
194
}
186
186
-
187
187
-
// initialize lua state
188
188
-
try lua.init(self, lua_state, &loop);
195
195
+
}
189
196
190
190
-
var input = TextInput.init(self.alloc, &self.vx.unicode);
191
191
-
defer input.deinit();
192
192
-
193
193
-
var last_frame: i64 = std.time.milliTimestamp();
194
194
-
loop: while (!self.should_quit) {
195
195
-
var redraw: bool = false;
196
196
-
std.time.sleep(8 * std.time.ns_per_ms);
197
197
-
if (self.state.messages.pending_scroll != 0) {
198
198
-
redraw = true;
199
199
-
if (self.state.messages.pending_scroll > 0) {
200
200
-
self.state.messages.pending_scroll -= 1;
201
201
-
self.state.messages.scroll_offset += 1;
202
202
-
} else {
203
203
-
self.state.messages.pending_scroll += 1;
204
204
-
self.state.messages.scroll_offset -|= 1;
197
197
+
fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
198
198
+
const self: *App = @ptrCast(@alignCast(ptr));
199
199
+
switch (event) {
200
200
+
.init => try ctx.tick(8, self.widget()),
201
201
+
.key_press => |key| {
202
202
+
if (key.matches('c', .{ .ctrl = true })) {
203
203
+
ctx.quit = true;
205
204
}
206
206
-
}
207
207
-
while (loop.tryEvent()) |event| {
208
208
-
redraw = true;
209
209
-
switch (event) {
210
210
-
.redraw => {},
211
211
-
.key_press => |key| {
212
212
-
if (self.state.paste.showDialog()) {
213
213
-
if (key.matches(vaxis.Key.escape, .{})) {
214
214
-
self.state.paste.has_newline = false;
215
215
-
self.paste_buffer.clearAndFree();
216
216
-
}
217
217
-
break;
218
218
-
}
219
219
-
if (self.state.paste.pasting) {
220
220
-
if (key.matches(vaxis.Key.enter, .{})) {
221
221
-
self.state.paste.has_newline = true;
222
222
-
try self.paste_buffer.append('\n');
223
223
-
continue :loop;
224
224
-
}
225
225
-
const text = key.text orelse continue :loop;
226
226
-
try self.paste_buffer.appendSlice(text);
227
227
-
continue;
228
228
-
}
229
229
-
for (self.binds.items) |bind| {
230
230
-
if (key.matches(bind.key.codepoint, bind.key.mods)) {
231
231
-
switch (bind.command) {
232
232
-
.quit => self.should_quit = true,
233
233
-
.@"next-channel" => self.nextChannel(),
234
234
-
.@"prev-channel" => self.prevChannel(),
235
235
-
.redraw => self.vx.queueRefresh(),
236
236
-
.lua_function => |ref| try lua.execFn(lua_state, ref),
237
237
-
else => {},
238
238
-
}
239
239
-
break;
240
240
-
}
241
241
-
} else if (key.matches(vaxis.Key.tab, .{})) {
242
242
-
// if we already have a completion word, then we are
243
243
-
// cycling through the options
244
244
-
if (self.completer) |*completer| {
245
245
-
const line = completer.next();
246
246
-
input.clearRetainingCapacity();
247
247
-
try input.insertSliceAtCursor(line);
248
248
-
} else {
249
249
-
var completion_buf: [irc.maximum_message_size]u8 = undefined;
250
250
-
const content = input.sliceToCursor(&completion_buf);
251
251
-
self.completer = try Completer.init(self.alloc, content);
252
252
-
}
253
253
-
} else if (key.matches(vaxis.Key.tab, .{ .shift = true })) {
254
254
-
if (self.completer) |*completer| {
255
255
-
const line = completer.prev();
256
256
-
input.clearRetainingCapacity();
257
257
-
try input.insertSliceAtCursor(line);
258
258
-
}
259
259
-
} else if (key.matches(vaxis.Key.enter, .{})) {
260
260
-
const buffer = self.selectedBuffer() orelse @panic("no buffer");
261
261
-
const content = try input.toOwnedSlice();
262
262
-
if (content.len == 0) continue;
263
263
-
defer self.alloc.free(content);
264
264
-
if (content[0] == '/')
265
265
-
self.handleCommand(lua_state, buffer, content) catch |err| {
266
266
-
log.err("couldn't handle command: {}", .{err});
267
267
-
}
268
268
-
else {
269
269
-
switch (buffer) {
270
270
-
.channel => |channel| {
271
271
-
var buf: [1024]u8 = undefined;
272
272
-
const msg = try std.fmt.bufPrint(
273
273
-
&buf,
274
274
-
"PRIVMSG {s} :{s}\r\n",
275
275
-
.{
276
276
-
channel.name,
277
277
-
content,
278
278
-
},
279
279
-
);
280
280
-
try channel.client.queueWrite(msg);
281
281
-
},
282
282
-
.client => log.err("can't send message to client", .{}),
283
283
-
}
284
284
-
}
285
285
-
if (self.completer != null) {
286
286
-
self.completer.?.deinit();
287
287
-
self.completer = null;
288
288
-
}
289
289
-
} else if (key.matches(vaxis.Key.page_up, .{})) {
290
290
-
self.state.messages.scroll_offset +|= 3;
291
291
-
} else if (key.matches(vaxis.Key.page_down, .{})) {
292
292
-
self.state.messages.scroll_offset -|= 3;
293
293
-
} else if (key.matches(vaxis.Key.home, .{})) {
294
294
-
self.state.messages.scroll_offset = 0;
295
295
-
} else {
296
296
-
if (self.completer != null and !key.isModifier()) {
297
297
-
self.completer.?.deinit();
298
298
-
self.completer = null;
299
299
-
}
300
300
-
log.debug("{}", .{key});
301
301
-
try input.update(.{ .key_press = key });
302
302
-
}
303
303
-
},
304
304
-
.paste_start => self.state.paste.pasting = true,
305
305
-
.paste_end => {
306
306
-
self.state.paste.pasting = false;
307
307
-
if (self.state.paste.has_newline) {
308
308
-
log.warn("NEWLINE", .{});
309
309
-
} else {
310
310
-
try input.insertSliceAtCursor(self.paste_buffer.items);
311
311
-
defer self.paste_buffer.clearAndFree();
312
312
-
}
313
313
-
},
314
314
-
.focus_out => self.state.mouse = null,
315
315
-
.mouse => |mouse| {
316
316
-
self.state.mouse = mouse;
317
317
-
},
318
318
-
.winsize => |ws| try self.vx.resize(self.alloc, writer, ws),
319
319
-
.connect => |cfg| {
320
320
-
const client = try self.alloc.create(irc.Client);
321
321
-
client.* = try irc.Client.init(self.alloc, self, &write_queue, cfg);
322
322
-
client.thread = try std.Thread.spawn(.{}, irc.Client.readLoop, .{ client, &loop });
323
323
-
try self.clients.append(client);
324
324
-
},
325
325
-
.irc => |irc_event| {
326
326
-
const msg: irc.Message = .{ .bytes = irc_event.msg.slice() };
327
327
-
const client = irc_event.client;
328
328
-
defer irc_event.msg.deinit();
329
329
-
switch (msg.command()) {
330
330
-
.unknown => {},
331
331
-
.CAP => {
332
332
-
// syntax: <client> <ACK/NACK> :caps
333
333
-
var iter = msg.paramIterator();
334
334
-
_ = iter.next() orelse continue; // client
335
335
-
const ack_or_nak = iter.next() orelse continue;
336
336
-
const caps = iter.next() orelse continue;
337
337
-
var cap_iter = mem.splitScalar(u8, caps, ' ');
338
338
-
while (cap_iter.next()) |cap| {
339
339
-
if (mem.eql(u8, ack_or_nak, "ACK")) {
340
340
-
client.ack(cap);
341
341
-
if (mem.eql(u8, cap, "sasl"))
342
342
-
try client.queueWrite("AUTHENTICATE PLAIN\r\n");
343
343
-
} else if (mem.eql(u8, ack_or_nak, "NAK")) {
344
344
-
log.debug("CAP not supported {s}", .{cap});
345
345
-
}
346
346
-
}
347
347
-
},
348
348
-
.AUTHENTICATE => {
349
349
-
var iter = msg.paramIterator();
350
350
-
while (iter.next()) |param| {
351
351
-
// A '+' is the continuuation to send our
352
352
-
// AUTHENTICATE info
353
353
-
if (!mem.eql(u8, param, "+")) continue;
354
354
-
var buf: [4096]u8 = undefined;
355
355
-
const config = client.config;
356
356
-
const sasl = try std.fmt.bufPrint(
357
357
-
&buf,
358
358
-
"{s}\x00{s}\x00{s}",
359
359
-
.{ config.user, config.nick, config.password },
360
360
-
);
205
205
+
},
206
206
+
.tick => {
207
207
+
for (self.clients.items) |client| {
208
208
+
client.drainFifo();
209
209
+
}
210
210
+
try ctx.tick(8, self.widget());
211
211
+
},
212
212
+
else => {},
213
213
+
}
214
214
+
}
361
215
362
362
-
// Create a buffer big enough for the base64 encoded string
363
363
-
const b64_buf = try self.alloc.alloc(u8, Base64Encoder.calcSize(sasl.len));
364
364
-
defer self.alloc.free(b64_buf);
365
365
-
const encoded = Base64Encoder.encode(b64_buf, sasl);
366
366
-
// Make our message
367
367
-
const auth = try std.fmt.bufPrint(
368
368
-
&buf,
369
369
-
"AUTHENTICATE {s}\r\n",
370
370
-
.{encoded},
371
371
-
);
372
372
-
try client.queueWrite(auth);
373
373
-
if (config.network_id) |id| {
374
374
-
const bind = try std.fmt.bufPrint(
375
375
-
&buf,
376
376
-
"BOUNCER BIND {s}\r\n",
377
377
-
.{id},
378
378
-
);
379
379
-
try client.queueWrite(bind);
380
380
-
}
381
381
-
try client.queueWrite("CAP END\r\n");
382
382
-
}
383
383
-
},
384
384
-
.RPL_WELCOME => {
385
385
-
const now = try zeit.instant(.{});
386
386
-
var now_buf: [30]u8 = undefined;
387
387
-
const now_fmt = try now.time().bufPrint(&now_buf, .rfc3339);
216
216
+
fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
217
217
+
const self: *App = @ptrCast(@alignCast(ptr));
388
218
389
389
-
const past = try now.subtract(.{ .days = 7 });
390
390
-
var past_buf: [30]u8 = undefined;
391
391
-
const past_fmt = try past.time().bufPrint(&past_buf, .rfc3339);
219
219
+
var children = std.ArrayList(vxfw.SubSurface).init(ctx.arena);
220
220
+
_ = &children;
392
221
393
393
-
var buf: [128]u8 = undefined;
394
394
-
const targets = try std.fmt.bufPrint(
395
395
-
&buf,
396
396
-
"CHATHISTORY TARGETS timestamp={s} timestamp={s} 50\r\n",
397
397
-
.{ now_fmt, past_fmt },
398
398
-
);
399
399
-
try client.queueWrite(targets);
400
400
-
// on_connect callback
401
401
-
try lua.onConnect(lua_state, client);
402
402
-
},
403
403
-
.RPL_YOURHOST => {},
404
404
-
.RPL_CREATED => {},
405
405
-
.RPL_MYINFO => {},
406
406
-
.RPL_ISUPPORT => {
407
407
-
// syntax: <client> <token>[ <token>] :are supported
408
408
-
var iter = msg.paramIterator();
409
409
-
_ = iter.next() orelse continue; // client
410
410
-
while (iter.next()) |token| {
411
411
-
if (mem.eql(u8, token, "WHOX"))
412
412
-
client.supports.whox = true
413
413
-
else if (mem.startsWith(u8, token, "PREFIX")) {
414
414
-
const prefix = blk: {
415
415
-
const idx = mem.indexOfScalar(u8, token, ')') orelse
416
416
-
// default is "@+"
417
417
-
break :blk try self.alloc.dupe(u8, "@+");
418
418
-
break :blk try self.alloc.dupe(u8, token[idx + 1 ..]);
419
419
-
};
420
420
-
client.supports.prefix = prefix;
421
421
-
}
422
422
-
}
423
423
-
},
424
424
-
.RPL_LOGGEDIN => {},
425
425
-
.RPL_TOPIC => {
426
426
-
// syntax: <client> <channel> :<topic>
427
427
-
var iter = msg.paramIterator();
428
428
-
_ = iter.next() orelse continue :loop; // client ("*")
429
429
-
const channel_name = iter.next() orelse continue :loop; // channel
430
430
-
const topic = iter.next() orelse continue :loop; // topic
222
222
+
const text: vxfw.Text = .{ .text = "hey" };
223
223
+
_ = text;
224
224
+
return .{
225
225
+
.size = ctx.max.size(),
226
226
+
.widget = self.widget(),
227
227
+
.buffer = &.{},
228
228
+
.children = children.items,
229
229
+
};
230
230
+
}
431
231
432
432
-
var channel = try client.getOrCreateChannel(channel_name);
433
433
-
if (channel.topic) |old_topic| {
434
434
-
self.alloc.free(old_topic);
435
435
-
}
436
436
-
channel.topic = try self.alloc.dupe(u8, topic);
437
437
-
},
438
438
-
.RPL_SASLSUCCESS => {},
439
439
-
.RPL_WHOREPLY => {
440
440
-
// syntax: <client> <channel> <username> <host> <server> <nick> <flags> :<hopcount> <real name>
441
441
-
var iter = msg.paramIterator();
442
442
-
_ = iter.next() orelse continue :loop; // client
443
443
-
const channel_name = iter.next() orelse continue :loop; // channel
444
444
-
if (mem.eql(u8, channel_name, "*")) continue;
445
445
-
_ = iter.next() orelse continue :loop; // username
446
446
-
_ = iter.next() orelse continue :loop; // host
447
447
-
_ = iter.next() orelse continue :loop; // server
448
448
-
const nick = iter.next() orelse continue :loop; // nick
449
449
-
const flags = iter.next() orelse continue :loop; // flags
232
232
+
// pub fn run(self: *App, lua_state: *Lua) !void {
233
233
+
// const writer = self.tty.anyWriter();
234
234
+
//
235
235
+
// var loop: comlink.EventLoop = .{ .vaxis = &self.vx, .tty = &self.tty };
236
236
+
// try loop.init();
237
237
+
// try loop.start();
238
238
+
// defer loop.stop();
239
239
+
//
240
240
+
// try self.vx.enterAltScreen(writer);
241
241
+
// try self.vx.queryTerminal(writer, 1 * std.time.ns_per_s);
242
242
+
// try self.vx.setMouseMode(writer, true);
243
243
+
// try self.vx.setBracketedPaste(writer, true);
244
244
+
//
245
245
+
// // start our write thread
246
246
+
// var write_queue: comlink.WriteQueue = .{};
247
247
+
// const write_thread = try std.Thread.spawn(.{}, writeLoop, .{ self.alloc, &write_queue });
248
248
+
// defer {
249
249
+
// write_queue.push(.join);
250
250
+
// write_thread.join();
251
251
+
// }
252
252
+
//
253
253
+
// // initialize lua state
254
254
+
// try lua.init(self, lua_state, &loop);
255
255
+
//
256
256
+
// var input = TextInput.init(self.alloc, &self.vx.unicode);
257
257
+
// defer input.deinit();
258
258
+
//
259
259
+
// var last_frame: i64 = std.time.milliTimestamp();
260
260
+
// loop: while (!self.should_quit) {
261
261
+
// var redraw: bool = false;
262
262
+
// std.time.sleep(8 * std.time.ns_per_ms);
263
263
+
// if (self.state.messages.pending_scroll != 0) {
264
264
+
// redraw = true;
265
265
+
// if (self.state.messages.pending_scroll > 0) {
266
266
+
// self.state.messages.pending_scroll -= 1;
267
267
+
// self.state.messages.scroll_offset += 1;
268
268
+
// } else {
269
269
+
// self.state.messages.pending_scroll += 1;
270
270
+
// self.state.messages.scroll_offset -|= 1;
271
271
+
// }
272
272
+
// }
273
273
+
// while (loop.tryEvent()) |event| {
274
274
+
// redraw = true;
275
275
+
// switch (event) {
276
276
+
// .redraw => {},
277
277
+
// .key_press => |key| {
278
278
+
// if (self.state.paste.showDialog()) {
279
279
+
// if (key.matches(vaxis.Key.escape, .{})) {
280
280
+
// self.state.paste.has_newline = false;
281
281
+
// self.paste_buffer.clearAndFree();
282
282
+
// }
283
283
+
// break;
284
284
+
// }
285
285
+
// if (self.state.paste.pasting) {
286
286
+
// if (key.matches(vaxis.Key.enter, .{})) {
287
287
+
// self.state.paste.has_newline = true;
288
288
+
// try self.paste_buffer.append('\n');
289
289
+
// continue :loop;
290
290
+
// }
291
291
+
// const text = key.text orelse continue :loop;
292
292
+
// try self.paste_buffer.appendSlice(text);
293
293
+
// continue;
294
294
+
// }
295
295
+
// for (self.binds.items) |bind| {
296
296
+
// if (key.matches(bind.key.codepoint, bind.key.mods)) {
297
297
+
// switch (bind.command) {
298
298
+
// .quit => self.should_quit = true,
299
299
+
// .@"next-channel" => self.nextChannel(),
300
300
+
// .@"prev-channel" => self.prevChannel(),
301
301
+
// .redraw => self.vx.queueRefresh(),
302
302
+
// .lua_function => |ref| try lua.execFn(lua_state, ref),
303
303
+
// else => {},
304
304
+
// }
305
305
+
// break;
306
306
+
// }
307
307
+
// } else if (key.matches(vaxis.Key.tab, .{})) {
308
308
+
// // if we already have a completion word, then we are
309
309
+
// // cycling through the options
310
310
+
// if (self.completer) |*completer| {
311
311
+
// const line = completer.next();
312
312
+
// input.clearRetainingCapacity();
313
313
+
// try input.insertSliceAtCursor(line);
314
314
+
// } else {
315
315
+
// var completion_buf: [irc.maximum_message_size]u8 = undefined;
316
316
+
// const content = input.sliceToCursor(&completion_buf);
317
317
+
// self.completer = try Completer.init(self.alloc, content);
318
318
+
// }
319
319
+
// } else if (key.matches(vaxis.Key.tab, .{ .shift = true })) {
320
320
+
// if (self.completer) |*completer| {
321
321
+
// const line = completer.prev();
322
322
+
// input.clearRetainingCapacity();
323
323
+
// try input.insertSliceAtCursor(line);
324
324
+
// }
325
325
+
// } else if (key.matches(vaxis.Key.enter, .{})) {
326
326
+
// const buffer = self.selectedBuffer() orelse @panic("no buffer");
327
327
+
// const content = try input.toOwnedSlice();
328
328
+
// if (content.len == 0) continue;
329
329
+
// defer self.alloc.free(content);
330
330
+
// if (content[0] == '/')
331
331
+
// self.handleCommand(lua_state, buffer, content) catch |err| {
332
332
+
// log.err("couldn't handle command: {}", .{err});
333
333
+
// }
334
334
+
// else {
335
335
+
// switch (buffer) {
336
336
+
// .channel => |channel| {
337
337
+
// var buf: [1024]u8 = undefined;
338
338
+
// const msg = try std.fmt.bufPrint(
339
339
+
// &buf,
340
340
+
// "PRIVMSG {s} :{s}\r\n",
341
341
+
// .{
342
342
+
// channel.name,
343
343
+
// content,
344
344
+
// },
345
345
+
// );
346
346
+
// try channel.client.queueWrite(msg);
347
347
+
// },
348
348
+
// .client => log.err("can't send message to client", .{}),
349
349
+
// }
350
350
+
// }
351
351
+
// if (self.completer != null) {
352
352
+
// self.completer.?.deinit();
353
353
+
// self.completer = null;
354
354
+
// }
355
355
+
// } else if (key.matches(vaxis.Key.page_up, .{})) {
356
356
+
// self.state.messages.scroll_offset +|= 3;
357
357
+
// } else if (key.matches(vaxis.Key.page_down, .{})) {
358
358
+
// self.state.messages.scroll_offset -|= 3;
359
359
+
// } else if (key.matches(vaxis.Key.home, .{})) {
360
360
+
// self.state.messages.scroll_offset = 0;
361
361
+
// } else {
362
362
+
// if (self.completer != null and !key.isModifier()) {
363
363
+
// self.completer.?.deinit();
364
364
+
// self.completer = null;
365
365
+
// }
366
366
+
// log.debug("{}", .{key});
367
367
+
// try input.update(.{ .key_press = key });
368
368
+
// }
369
369
+
// },
370
370
+
// .paste_start => self.state.paste.pasting = true,
371
371
+
// .paste_end => {
372
372
+
// self.state.paste.pasting = false;
373
373
+
// if (self.state.paste.has_newline) {
374
374
+
// log.warn("NEWLINE", .{});
375
375
+
// } else {
376
376
+
// try input.insertSliceAtCursor(self.paste_buffer.items);
377
377
+
// defer self.paste_buffer.clearAndFree();
378
378
+
// }
379
379
+
// },
380
380
+
// .focus_out => self.state.mouse = null,
381
381
+
// .mouse => |mouse| {
382
382
+
// self.state.mouse = mouse;
383
383
+
// },
384
384
+
// .winsize => |ws| try self.vx.resize(self.alloc, writer, ws),
385
385
+
// .connect => |cfg| {
386
386
+
// const client = try self.alloc.create(irc.Client);
387
387
+
// client.* = try irc.Client.init(self.alloc, self, &write_queue, cfg);
388
388
+
// client.thread = try std.Thread.spawn(.{}, irc.Client.readLoop, .{ client, &loop });
389
389
+
// try self.clients.append(client);
390
390
+
// },
391
391
+
// .irc => |irc_event| {
392
392
+
// const msg: irc.Message = .{ .bytes = irc_event.msg.slice() };
393
393
+
// const client = irc_event.client;
394
394
+
// defer irc_event.msg.deinit();
395
395
+
// switch (msg.command()) {
396
396
+
// .unknown => {},
397
397
+
// .CAP => {
398
398
+
// // syntax: <client> <ACK/NACK> :caps
399
399
+
// var iter = msg.paramIterator();
400
400
+
// _ = iter.next() orelse continue; // client
401
401
+
// const ack_or_nak = iter.next() orelse continue;
402
402
+
// const caps = iter.next() orelse continue;
403
403
+
// var cap_iter = mem.splitScalar(u8, caps, ' ');
404
404
+
// while (cap_iter.next()) |cap| {
405
405
+
// if (mem.eql(u8, ack_or_nak, "ACK")) {
406
406
+
// client.ack(cap);
407
407
+
// if (mem.eql(u8, cap, "sasl"))
408
408
+
// try client.queueWrite("AUTHENTICATE PLAIN\r\n");
409
409
+
// } else if (mem.eql(u8, ack_or_nak, "NAK")) {
410
410
+
// log.debug("CAP not supported {s}", .{cap});
411
411
+
// }
412
412
+
// }
413
413
+
// },
414
414
+
// .AUTHENTICATE => {
415
415
+
// var iter = msg.paramIterator();
416
416
+
// while (iter.next()) |param| {
417
417
+
// // A '+' is the continuuation to send our
418
418
+
// // AUTHENTICATE info
419
419
+
// if (!mem.eql(u8, param, "+")) continue;
420
420
+
// var buf: [4096]u8 = undefined;
421
421
+
// const config = client.config;
422
422
+
// const sasl = try std.fmt.bufPrint(
423
423
+
// &buf,
424
424
+
// "{s}\x00{s}\x00{s}",
425
425
+
// .{ config.user, config.nick, config.password },
426
426
+
// );
427
427
+
//
428
428
+
// // Create a buffer big enough for the base64 encoded string
429
429
+
// const b64_buf = try self.alloc.alloc(u8, Base64Encoder.calcSize(sasl.len));
430
430
+
// defer self.alloc.free(b64_buf);
431
431
+
// const encoded = Base64Encoder.encode(b64_buf, sasl);
432
432
+
// // Make our message
433
433
+
// const auth = try std.fmt.bufPrint(
434
434
+
// &buf,
435
435
+
// "AUTHENTICATE {s}\r\n",
436
436
+
// .{encoded},
437
437
+
// );
438
438
+
// try client.queueWrite(auth);
439
439
+
// if (config.network_id) |id| {
440
440
+
// const bind = try std.fmt.bufPrint(
441
441
+
// &buf,
442
442
+
// "BOUNCER BIND {s}\r\n",
443
443
+
// .{id},
444
444
+
// );
445
445
+
// try client.queueWrite(bind);
446
446
+
// }
447
447
+
// try client.queueWrite("CAP END\r\n");
448
448
+
// }
449
449
+
// },
450
450
+
// .RPL_WELCOME => {
451
451
+
// const now = try zeit.instant(.{});
452
452
+
// var now_buf: [30]u8 = undefined;
453
453
+
// const now_fmt = try now.time().bufPrint(&now_buf, .rfc3339);
454
454
+
//
455
455
+
// const past = try now.subtract(.{ .days = 7 });
456
456
+
// var past_buf: [30]u8 = undefined;
457
457
+
// const past_fmt = try past.time().bufPrint(&past_buf, .rfc3339);
458
458
+
//
459
459
+
// var buf: [128]u8 = undefined;
460
460
+
// const targets = try std.fmt.bufPrint(
461
461
+
// &buf,
462
462
+
// "CHATHISTORY TARGETS timestamp={s} timestamp={s} 50\r\n",
463
463
+
// .{ now_fmt, past_fmt },
464
464
+
// );
465
465
+
// try client.queueWrite(targets);
466
466
+
// // on_connect callback
467
467
+
// try lua.onConnect(lua_state, client);
468
468
+
// },
469
469
+
// .RPL_YOURHOST => {},
470
470
+
// .RPL_CREATED => {},
471
471
+
// .RPL_MYINFO => {},
472
472
+
// .RPL_ISUPPORT => {
473
473
+
// // syntax: <client> <token>[ <token>] :are supported
474
474
+
// var iter = msg.paramIterator();
475
475
+
// _ = iter.next() orelse continue; // client
476
476
+
// while (iter.next()) |token| {
477
477
+
// if (mem.eql(u8, token, "WHOX"))
478
478
+
// client.supports.whox = true
479
479
+
// else if (mem.startsWith(u8, token, "PREFIX")) {
480
480
+
// const prefix = blk: {
481
481
+
// const idx = mem.indexOfScalar(u8, token, ')') orelse
482
482
+
// // default is "@+"
483
483
+
// break :blk try self.alloc.dupe(u8, "@+");
484
484
+
// break :blk try self.alloc.dupe(u8, token[idx + 1 ..]);
485
485
+
// };
486
486
+
// client.supports.prefix = prefix;
487
487
+
// }
488
488
+
// }
489
489
+
// },
490
490
+
// .RPL_LOGGEDIN => {},
491
491
+
// .RPL_TOPIC => {
492
492
+
// // syntax: <client> <channel> :<topic>
493
493
+
// var iter = msg.paramIterator();
494
494
+
// _ = iter.next() orelse continue :loop; // client ("*")
495
495
+
// const channel_name = iter.next() orelse continue :loop; // channel
496
496
+
// const topic = iter.next() orelse continue :loop; // topic
497
497
+
//
498
498
+
// var channel = try client.getOrCreateChannel(channel_name);
499
499
+
// if (channel.topic) |old_topic| {
500
500
+
// self.alloc.free(old_topic);
501
501
+
// }
502
502
+
// channel.topic = try self.alloc.dupe(u8, topic);
503
503
+
// },
504
504
+
// .RPL_SASLSUCCESS => {},
505
505
+
// .RPL_WHOREPLY => {
506
506
+
// // syntax: <client> <channel> <username> <host> <server> <nick> <flags> :<hopcount> <real name>
507
507
+
// var iter = msg.paramIterator();
508
508
+
// _ = iter.next() orelse continue :loop; // client
509
509
+
// const channel_name = iter.next() orelse continue :loop; // channel
510
510
+
// if (mem.eql(u8, channel_name, "*")) continue;
511
511
+
// _ = iter.next() orelse continue :loop; // username
512
512
+
// _ = iter.next() orelse continue :loop; // host
513
513
+
// _ = iter.next() orelse continue :loop; // server
514
514
+
// const nick = iter.next() orelse continue :loop; // nick
515
515
+
// const flags = iter.next() orelse continue :loop; // flags
516
516
+
//
517
517
+
// const user_ptr = try client.getOrCreateUser(nick);
518
518
+
// if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true;
519
519
+
// var channel = try client.getOrCreateChannel(channel_name);
520
520
+
//
521
521
+
// const prefix = for (flags) |c| {
522
522
+
// if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| {
523
523
+
// break c;
524
524
+
// }
525
525
+
// } else ' ';
526
526
+
//
527
527
+
// try channel.addMember(user_ptr, .{ .prefix = prefix });
528
528
+
// },
529
529
+
// .RPL_WHOSPCRPL => {
530
530
+
// // syntax: <client> <channel> <nick> <flags> :<realname>
531
531
+
// var iter = msg.paramIterator();
532
532
+
// _ = iter.next() orelse continue;
533
533
+
// const channel_name = iter.next() orelse continue; // channel
534
534
+
// const nick = iter.next() orelse continue;
535
535
+
// const flags = iter.next() orelse continue;
536
536
+
//
537
537
+
// const user_ptr = try client.getOrCreateUser(nick);
538
538
+
// if (iter.next()) |real_name| {
539
539
+
// if (user_ptr.real_name) |old_name| {
540
540
+
// self.alloc.free(old_name);
541
541
+
// }
542
542
+
// user_ptr.real_name = try self.alloc.dupe(u8, real_name);
543
543
+
// }
544
544
+
// if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true;
545
545
+
// var channel = try client.getOrCreateChannel(channel_name);
546
546
+
//
547
547
+
// const prefix = for (flags) |c| {
548
548
+
// if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| {
549
549
+
// break c;
550
550
+
// }
551
551
+
// } else ' ';
552
552
+
//
553
553
+
// try channel.addMember(user_ptr, .{ .prefix = prefix });
554
554
+
// },
555
555
+
// .RPL_ENDOFWHO => {
556
556
+
// // syntax: <client> <mask> :End of WHO list
557
557
+
// var iter = msg.paramIterator();
558
558
+
// _ = iter.next() orelse continue :loop; // client
559
559
+
// const channel_name = iter.next() orelse continue :loop; // channel
560
560
+
// if (mem.eql(u8, channel_name, "*")) continue;
561
561
+
// var channel = try client.getOrCreateChannel(channel_name);
562
562
+
// channel.in_flight.who = false;
563
563
+
// },
564
564
+
// .RPL_NAMREPLY => {
565
565
+
// // syntax: <client> <symbol> <channel> :[<prefix>]<nick>{ [<prefix>]<nick>}
566
566
+
// var iter = msg.paramIterator();
567
567
+
// _ = iter.next() orelse continue; // client
568
568
+
// _ = iter.next() orelse continue; // symbol
569
569
+
// const channel_name = iter.next() orelse continue; // channel
570
570
+
// const names = iter.next() orelse continue;
571
571
+
// var channel = try client.getOrCreateChannel(channel_name);
572
572
+
// var name_iter = std.mem.splitScalar(u8, names, ' ');
573
573
+
// while (name_iter.next()) |name| {
574
574
+
// const nick, const prefix = for (client.supports.prefix) |ch| {
575
575
+
// if (name[0] == ch) {
576
576
+
// break .{ name[1..], name[0] };
577
577
+
// }
578
578
+
// } else .{ name, ' ' };
579
579
+
//
580
580
+
// if (prefix != ' ') {
581
581
+
// log.debug("HAS PREFIX {s}", .{name});
582
582
+
// }
583
583
+
//
584
584
+
// const user_ptr = try client.getOrCreateUser(nick);
585
585
+
//
586
586
+
// try channel.addMember(user_ptr, .{ .prefix = prefix, .sort = false });
587
587
+
// }
588
588
+
//
589
589
+
// channel.sortMembers();
590
590
+
// },
591
591
+
// .RPL_ENDOFNAMES => {
592
592
+
// // syntax: <client> <channel> :End of /NAMES list
593
593
+
// var iter = msg.paramIterator();
594
594
+
// _ = iter.next() orelse continue; // client
595
595
+
// const channel_name = iter.next() orelse continue; // channel
596
596
+
// var channel = try client.getOrCreateChannel(channel_name);
597
597
+
// channel.in_flight.names = false;
598
598
+
// },
599
599
+
// .BOUNCER => {
600
600
+
// var iter = msg.paramIterator();
601
601
+
// while (iter.next()) |param| {
602
602
+
// if (mem.eql(u8, param, "NETWORK")) {
603
603
+
// const id = iter.next() orelse continue;
604
604
+
// const attr = iter.next() orelse continue;
605
605
+
// // check if we already have this network
606
606
+
// for (self.clients.items, 0..) |cl, i| {
607
607
+
// if (cl.config.network_id) |net_id| {
608
608
+
// if (mem.eql(u8, net_id, id)) {
609
609
+
// if (mem.eql(u8, attr, "*")) {
610
610
+
// // * means the network was
611
611
+
// // deleted
612
612
+
// cl.deinit();
613
613
+
// _ = self.clients.swapRemove(i);
614
614
+
// }
615
615
+
// continue :loop;
616
616
+
// }
617
617
+
// }
618
618
+
// }
619
619
+
//
620
620
+
// var cfg = client.config;
621
621
+
// cfg.network_id = try self.alloc.dupe(u8, id);
622
622
+
//
623
623
+
// var attr_iter = std.mem.splitScalar(u8, attr, ';');
624
624
+
// while (attr_iter.next()) |kv| {
625
625
+
// const n = std.mem.indexOfScalar(u8, kv, '=') orelse continue;
626
626
+
// const key = kv[0..n];
627
627
+
// if (mem.eql(u8, key, "name"))
628
628
+
// cfg.name = try self.alloc.dupe(u8, kv[n + 1 ..])
629
629
+
// else if (mem.eql(u8, key, "nickname"))
630
630
+
// cfg.network_nick = try self.alloc.dupe(u8, kv[n + 1 ..]);
631
631
+
// }
632
632
+
// loop.postEvent(.{ .connect = cfg });
633
633
+
// }
634
634
+
// }
635
635
+
// },
636
636
+
// .AWAY => {
637
637
+
// const src = msg.source() orelse continue :loop;
638
638
+
// var iter = msg.paramIterator();
639
639
+
// const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
640
640
+
// const user = try client.getOrCreateUser(src[0..n]);
641
641
+
// // If there are any params, the user is away. Otherwise
642
642
+
// // they are back.
643
643
+
// user.away = if (iter.next()) |_| true else false;
644
644
+
// },
645
645
+
// .BATCH => {
646
646
+
// var iter = msg.paramIterator();
647
647
+
// const tag = iter.next() orelse continue;
648
648
+
// switch (tag[0]) {
649
649
+
// '+' => {
650
650
+
// const batch_type = iter.next() orelse continue;
651
651
+
// if (mem.eql(u8, batch_type, "chathistory")) {
652
652
+
// const target = iter.next() orelse continue;
653
653
+
// var channel = try client.getOrCreateChannel(target);
654
654
+
// channel.at_oldest = true;
655
655
+
// const duped_tag = try self.alloc.dupe(u8, tag[1..]);
656
656
+
// try client.batches.put(duped_tag, channel);
657
657
+
// }
658
658
+
// },
659
659
+
// '-' => {
660
660
+
// const key = client.batches.getKey(tag[1..]) orelse continue;
661
661
+
// var chan = client.batches.get(key) orelse @panic("key should exist here");
662
662
+
// chan.history_requested = false;
663
663
+
// _ = client.batches.remove(key);
664
664
+
// self.alloc.free(key);
665
665
+
// },
666
666
+
// else => {},
667
667
+
// }
668
668
+
// },
669
669
+
// .CHATHISTORY => {
670
670
+
// var iter = msg.paramIterator();
671
671
+
// const should_targets = iter.next() orelse continue;
672
672
+
// if (!mem.eql(u8, should_targets, "TARGETS")) continue;
673
673
+
// const target = iter.next() orelse continue;
674
674
+
// // we only add direct messages, not more channels
675
675
+
// assert(target.len > 0);
676
676
+
// if (target[0] == '#') continue;
677
677
+
//
678
678
+
// var channel = try client.getOrCreateChannel(target);
679
679
+
// const user_ptr = try client.getOrCreateUser(target);
680
680
+
// const me_ptr = try client.getOrCreateUser(client.nickname());
681
681
+
// try channel.addMember(user_ptr, .{});
682
682
+
// try channel.addMember(me_ptr, .{});
683
683
+
// // we set who_requested so we don't try to request
684
684
+
// // who on DMs
685
685
+
// channel.who_requested = true;
686
686
+
// var buf: [128]u8 = undefined;
687
687
+
// const mark_read = try std.fmt.bufPrint(
688
688
+
// &buf,
689
689
+
// "MARKREAD {s}\r\n",
690
690
+
// .{channel.name},
691
691
+
// );
692
692
+
// try client.queueWrite(mark_read);
693
693
+
// try client.requestHistory(.after, channel);
694
694
+
// },
695
695
+
// .JOIN => {
696
696
+
// // get the user
697
697
+
// const src = msg.source() orelse continue :loop;
698
698
+
// const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
699
699
+
// const user = try client.getOrCreateUser(src[0..n]);
700
700
+
//
701
701
+
// // get the channel
702
702
+
// var iter = msg.paramIterator();
703
703
+
// const target = iter.next() orelse continue;
704
704
+
// var channel = try client.getOrCreateChannel(target);
705
705
+
//
706
706
+
// // If it's our nick, we request chat history
707
707
+
// if (mem.eql(u8, user.nick, client.nickname())) {
708
708
+
// try client.requestHistory(.after, channel);
709
709
+
// if (self.explicit_join) {
710
710
+
// self.selectChannelName(client, target);
711
711
+
// self.explicit_join = false;
712
712
+
// }
713
713
+
// } else try channel.addMember(user, .{});
714
714
+
// },
715
715
+
// .MARKREAD => {
716
716
+
// var iter = msg.paramIterator();
717
717
+
// const target = iter.next() orelse continue;
718
718
+
// const timestamp = iter.next() orelse continue;
719
719
+
// const equal = std.mem.indexOfScalar(u8, timestamp, '=') orelse continue;
720
720
+
// const last_read = zeit.instant(.{
721
721
+
// .source = .{
722
722
+
// .iso8601 = timestamp[equal + 1 ..],
723
723
+
// },
724
724
+
// }) catch |err| {
725
725
+
// log.err("couldn't convert timestamp: {}", .{err});
726
726
+
// continue;
727
727
+
// };
728
728
+
// var channel = try client.getOrCreateChannel(target);
729
729
+
// channel.last_read = last_read.unixTimestamp();
730
730
+
// const last_msg = channel.messages.getLastOrNull() orelse continue;
731
731
+
// const time = last_msg.time() orelse continue;
732
732
+
// if (time.unixTimestamp() > channel.last_read)
733
733
+
// channel.has_unread = true
734
734
+
// else
735
735
+
// channel.has_unread = false;
736
736
+
// },
737
737
+
// .PART => {
738
738
+
// // get the user
739
739
+
// const src = msg.source() orelse continue :loop;
740
740
+
// const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
741
741
+
// const user = try client.getOrCreateUser(src[0..n]);
742
742
+
//
743
743
+
// // get the channel
744
744
+
// var iter = msg.paramIterator();
745
745
+
// const target = iter.next() orelse continue;
746
746
+
//
747
747
+
// if (mem.eql(u8, user.nick, client.nickname())) {
748
748
+
// for (client.channels.items, 0..) |channel, i| {
749
749
+
// if (!mem.eql(u8, channel.name, target)) continue;
750
750
+
// var chan = client.channels.orderedRemove(i);
751
751
+
// self.state.buffers.selected_idx -|= 1;
752
752
+
// chan.deinit(self.alloc);
753
753
+
// break;
754
754
+
// }
755
755
+
// } else {
756
756
+
// const channel = try client.getOrCreateChannel(target);
757
757
+
// channel.removeMember(user);
758
758
+
// }
759
759
+
// },
760
760
+
// .PRIVMSG, .NOTICE => {
761
761
+
// // syntax: <target> :<message>
762
762
+
// const msg2: irc.Message = .{
763
763
+
// .bytes = try self.alloc.dupe(u8, msg.bytes),
764
764
+
// };
765
765
+
// var iter = msg2.paramIterator();
766
766
+
// const target = blk: {
767
767
+
// const tgt = iter.next() orelse continue;
768
768
+
// if (mem.eql(u8, tgt, client.nickname())) {
769
769
+
// // If the target is us, it likely has our
770
770
+
// // hostname in it.
771
771
+
// const source = msg2.source() orelse continue;
772
772
+
// const n = mem.indexOfScalar(u8, source, '!') orelse source.len;
773
773
+
// break :blk source[0..n];
774
774
+
// } else break :blk tgt;
775
775
+
// };
776
776
+
//
777
777
+
// // We handle batches separately. When we encounter a
778
778
+
// // PRIVMSG from a batch, we use the original target
779
779
+
// // from the batch start. We also never notify from a
780
780
+
// // batched message. Batched messages also require
781
781
+
// // sorting
782
782
+
// var tag_iter = msg2.tagIterator();
783
783
+
// while (tag_iter.next()) |tag| {
784
784
+
// if (mem.eql(u8, tag.key, "batch")) {
785
785
+
// const entry = client.batches.getEntry(tag.value) orelse @panic("TODO");
786
786
+
// var channel = entry.value_ptr.*;
787
787
+
// try channel.messages.append(msg2);
788
788
+
// std.sort.insertion(irc.Message, channel.messages.items, {}, irc.Message.compareTime);
789
789
+
// channel.at_oldest = false;
790
790
+
// const time = msg2.time() orelse continue;
791
791
+
// if (time.unixTimestamp() > channel.last_read) {
792
792
+
// channel.has_unread = true;
793
793
+
// const content = iter.next() orelse continue;
794
794
+
// if (std.mem.indexOf(u8, content, client.nickname())) |_| {
795
795
+
// channel.has_unread_highlight = true;
796
796
+
// }
797
797
+
// }
798
798
+
// break;
799
799
+
// }
800
800
+
// } else {
801
801
+
// // standard handling
802
802
+
// var channel = try client.getOrCreateChannel(target);
803
803
+
// try channel.messages.append(msg2);
804
804
+
// const content = iter.next() orelse continue;
805
805
+
// var has_highlight = false;
806
806
+
// {
807
807
+
// const sender: []const u8 = blk: {
808
808
+
// const src = msg2.source() orelse break :blk "";
809
809
+
// const l = std.mem.indexOfScalar(u8, src, '!') orelse
810
810
+
// std.mem.indexOfScalar(u8, src, '@') orelse
811
811
+
// src.len;
812
812
+
// break :blk src[0..l];
813
813
+
// };
814
814
+
// try lua.onMessage(lua_state, client, channel.name, sender, content);
815
815
+
// }
816
816
+
// if (std.mem.indexOf(u8, content, client.nickname())) |_| {
817
817
+
// var buf: [64]u8 = undefined;
818
818
+
// const title_or_err = if (msg2.source()) |source|
819
819
+
// std.fmt.bufPrint(&buf, "{s} - {s}", .{ channel.name, source })
820
820
+
// else
821
821
+
// std.fmt.bufPrint(&buf, "{s}", .{channel.name});
822
822
+
// const title = title_or_err catch title: {
823
823
+
// const len = @min(buf.len, channel.name.len);
824
824
+
// @memcpy(buf[0..len], channel.name[0..len]);
825
825
+
// break :title buf[0..len];
826
826
+
// };
827
827
+
// try self.vx.notify(writer, title, content);
828
828
+
// has_highlight = true;
829
829
+
// }
830
830
+
// const time = msg2.time() orelse continue;
831
831
+
// if (time.unixTimestamp() > channel.last_read) {
832
832
+
// channel.has_unread_highlight = has_highlight;
833
833
+
// channel.has_unread = true;
834
834
+
// }
835
835
+
// }
836
836
+
//
837
837
+
// // If we get a message from the current user mark the channel as
838
838
+
// // read, since they must have just sent the message.
839
839
+
// const sender: []const u8 = blk: {
840
840
+
// const src = msg2.source() orelse break :blk "";
841
841
+
// const l = std.mem.indexOfScalar(u8, src, '!') orelse
842
842
+
// std.mem.indexOfScalar(u8, src, '@') orelse
843
843
+
// src.len;
844
844
+
// break :blk src[0..l];
845
845
+
// };
846
846
+
// if (std.mem.eql(u8, sender, client.nickname())) {
847
847
+
// self.markSelectedChannelRead();
848
848
+
// }
849
849
+
// },
850
850
+
// }
851
851
+
// },
852
852
+
// }
853
853
+
// }
854
854
+
//
855
855
+
// if (redraw) {
856
856
+
// try self.draw(&input);
857
857
+
// last_frame = std.time.milliTimestamp();
858
858
+
// }
859
859
+
// }
860
860
+
// }
450
861
451
451
-
const user_ptr = try client.getOrCreateUser(nick);
452
452
-
if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true;
453
453
-
var channel = try client.getOrCreateChannel(channel_name);
862
862
+
pub fn connect(self: *App, cfg: irc.Client.Config) !void {
863
863
+
const client = try self.alloc.create(irc.Client);
864
864
+
client.* = try irc.Client.init(self.alloc, self, &self.write_queue, cfg);
865
865
+
client.thread = try std.Thread.spawn(.{}, irc.Client.readLoop, .{client});
866
866
+
try self.clients.append(client);
867
867
+
}
454
868
455
455
-
const prefix = for (flags) |c| {
456
456
-
if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| {
457
457
-
break c;
458
458
-
}
459
459
-
} else ' ';
460
460
-
461
461
-
try channel.addMember(user_ptr, .{ .prefix = prefix });
462
462
-
},
463
463
-
.RPL_WHOSPCRPL => {
464
464
-
// syntax: <client> <channel> <nick> <flags> :<realname>
465
465
-
var iter = msg.paramIterator();
466
466
-
_ = iter.next() orelse continue;
467
467
-
const channel_name = iter.next() orelse continue; // channel
468
468
-
const nick = iter.next() orelse continue;
469
469
-
const flags = iter.next() orelse continue;
470
470
-
471
471
-
const user_ptr = try client.getOrCreateUser(nick);
472
472
-
if (iter.next()) |real_name| {
473
473
-
if (user_ptr.real_name) |old_name| {
474
474
-
self.alloc.free(old_name);
475
475
-
}
476
476
-
user_ptr.real_name = try self.alloc.dupe(u8, real_name);
477
477
-
}
478
478
-
if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true;
479
479
-
var channel = try client.getOrCreateChannel(channel_name);
480
480
-
481
481
-
const prefix = for (flags) |c| {
482
482
-
if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| {
483
483
-
break c;
484
484
-
}
485
485
-
} else ' ';
486
486
-
487
487
-
try channel.addMember(user_ptr, .{ .prefix = prefix });
488
488
-
},
489
489
-
.RPL_ENDOFWHO => {
490
490
-
// syntax: <client> <mask> :End of WHO list
491
491
-
var iter = msg.paramIterator();
492
492
-
_ = iter.next() orelse continue :loop; // client
493
493
-
const channel_name = iter.next() orelse continue :loop; // channel
494
494
-
if (mem.eql(u8, channel_name, "*")) continue;
495
495
-
var channel = try client.getOrCreateChannel(channel_name);
496
496
-
channel.in_flight.who = false;
497
497
-
},
498
498
-
.RPL_NAMREPLY => {
499
499
-
// syntax: <client> <symbol> <channel> :[<prefix>]<nick>{ [<prefix>]<nick>}
500
500
-
var iter = msg.paramIterator();
501
501
-
_ = iter.next() orelse continue; // client
502
502
-
_ = iter.next() orelse continue; // symbol
503
503
-
const channel_name = iter.next() orelse continue; // channel
504
504
-
const names = iter.next() orelse continue;
505
505
-
var channel = try client.getOrCreateChannel(channel_name);
506
506
-
var name_iter = std.mem.splitScalar(u8, names, ' ');
507
507
-
while (name_iter.next()) |name| {
508
508
-
const nick, const prefix = for (client.supports.prefix) |ch| {
509
509
-
if (name[0] == ch) {
510
510
-
break .{ name[1..], name[0] };
511
511
-
}
512
512
-
} else .{ name, ' ' };
513
513
-
514
514
-
if (prefix != ' ') {
515
515
-
log.debug("HAS PREFIX {s}", .{name});
516
516
-
}
517
517
-
518
518
-
const user_ptr = try client.getOrCreateUser(nick);
519
519
-
520
520
-
try channel.addMember(user_ptr, .{ .prefix = prefix, .sort = false });
521
521
-
}
522
522
-
523
523
-
channel.sortMembers();
524
524
-
},
525
525
-
.RPL_ENDOFNAMES => {
526
526
-
// syntax: <client> <channel> :End of /NAMES list
527
527
-
var iter = msg.paramIterator();
528
528
-
_ = iter.next() orelse continue; // client
529
529
-
const channel_name = iter.next() orelse continue; // channel
530
530
-
var channel = try client.getOrCreateChannel(channel_name);
531
531
-
channel.in_flight.names = false;
532
532
-
},
533
533
-
.BOUNCER => {
534
534
-
var iter = msg.paramIterator();
535
535
-
while (iter.next()) |param| {
536
536
-
if (mem.eql(u8, param, "NETWORK")) {
537
537
-
const id = iter.next() orelse continue;
538
538
-
const attr = iter.next() orelse continue;
539
539
-
// check if we already have this network
540
540
-
for (self.clients.items, 0..) |cl, i| {
541
541
-
if (cl.config.network_id) |net_id| {
542
542
-
if (mem.eql(u8, net_id, id)) {
543
543
-
if (mem.eql(u8, attr, "*")) {
544
544
-
// * means the network was
545
545
-
// deleted
546
546
-
cl.deinit();
547
547
-
_ = self.clients.swapRemove(i);
548
548
-
}
549
549
-
continue :loop;
550
550
-
}
551
551
-
}
552
552
-
}
553
553
-
554
554
-
var cfg = client.config;
555
555
-
cfg.network_id = try self.alloc.dupe(u8, id);
556
556
-
557
557
-
var attr_iter = std.mem.splitScalar(u8, attr, ';');
558
558
-
while (attr_iter.next()) |kv| {
559
559
-
const n = std.mem.indexOfScalar(u8, kv, '=') orelse continue;
560
560
-
const key = kv[0..n];
561
561
-
if (mem.eql(u8, key, "name"))
562
562
-
cfg.name = try self.alloc.dupe(u8, kv[n + 1 ..])
563
563
-
else if (mem.eql(u8, key, "nickname"))
564
564
-
cfg.network_nick = try self.alloc.dupe(u8, kv[n + 1 ..]);
565
565
-
}
566
566
-
loop.postEvent(.{ .connect = cfg });
567
567
-
}
568
568
-
}
569
569
-
},
570
570
-
.AWAY => {
571
571
-
const src = msg.source() orelse continue :loop;
572
572
-
var iter = msg.paramIterator();
573
573
-
const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
574
574
-
const user = try client.getOrCreateUser(src[0..n]);
575
575
-
// If there are any params, the user is away. Otherwise
576
576
-
// they are back.
577
577
-
user.away = if (iter.next()) |_| true else false;
578
578
-
},
579
579
-
.BATCH => {
580
580
-
var iter = msg.paramIterator();
581
581
-
const tag = iter.next() orelse continue;
582
582
-
switch (tag[0]) {
583
583
-
'+' => {
584
584
-
const batch_type = iter.next() orelse continue;
585
585
-
if (mem.eql(u8, batch_type, "chathistory")) {
586
586
-
const target = iter.next() orelse continue;
587
587
-
var channel = try client.getOrCreateChannel(target);
588
588
-
channel.at_oldest = true;
589
589
-
const duped_tag = try self.alloc.dupe(u8, tag[1..]);
590
590
-
try client.batches.put(duped_tag, channel);
591
591
-
}
592
592
-
},
593
593
-
'-' => {
594
594
-
const key = client.batches.getKey(tag[1..]) orelse continue;
595
595
-
var chan = client.batches.get(key) orelse @panic("key should exist here");
596
596
-
chan.history_requested = false;
597
597
-
_ = client.batches.remove(key);
598
598
-
self.alloc.free(key);
599
599
-
},
600
600
-
else => {},
601
601
-
}
602
602
-
},
603
603
-
.CHATHISTORY => {
604
604
-
var iter = msg.paramIterator();
605
605
-
const should_targets = iter.next() orelse continue;
606
606
-
if (!mem.eql(u8, should_targets, "TARGETS")) continue;
607
607
-
const target = iter.next() orelse continue;
608
608
-
// we only add direct messages, not more channels
609
609
-
assert(target.len > 0);
610
610
-
if (target[0] == '#') continue;
611
611
-
612
612
-
var channel = try client.getOrCreateChannel(target);
613
613
-
const user_ptr = try client.getOrCreateUser(target);
614
614
-
const me_ptr = try client.getOrCreateUser(client.nickname());
615
615
-
try channel.addMember(user_ptr, .{});
616
616
-
try channel.addMember(me_ptr, .{});
617
617
-
// we set who_requested so we don't try to request
618
618
-
// who on DMs
619
619
-
channel.who_requested = true;
620
620
-
var buf: [128]u8 = undefined;
621
621
-
const mark_read = try std.fmt.bufPrint(
622
622
-
&buf,
623
623
-
"MARKREAD {s}\r\n",
624
624
-
.{channel.name},
625
625
-
);
626
626
-
try client.queueWrite(mark_read);
627
627
-
try client.requestHistory(.after, channel);
628
628
-
},
629
629
-
.JOIN => {
630
630
-
// get the user
631
631
-
const src = msg.source() orelse continue :loop;
632
632
-
const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
633
633
-
const user = try client.getOrCreateUser(src[0..n]);
634
634
-
635
635
-
// get the channel
636
636
-
var iter = msg.paramIterator();
637
637
-
const target = iter.next() orelse continue;
638
638
-
var channel = try client.getOrCreateChannel(target);
639
639
-
640
640
-
// If it's our nick, we request chat history
641
641
-
if (mem.eql(u8, user.nick, client.nickname())) {
642
642
-
try client.requestHistory(.after, channel);
643
643
-
if (self.explicit_join) {
644
644
-
self.selectChannelName(client, target);
645
645
-
self.explicit_join = false;
646
646
-
}
647
647
-
} else try channel.addMember(user, .{});
648
648
-
},
649
649
-
.MARKREAD => {
650
650
-
var iter = msg.paramIterator();
651
651
-
const target = iter.next() orelse continue;
652
652
-
const timestamp = iter.next() orelse continue;
653
653
-
const equal = std.mem.indexOfScalar(u8, timestamp, '=') orelse continue;
654
654
-
const last_read = zeit.instant(.{
655
655
-
.source = .{
656
656
-
.iso8601 = timestamp[equal + 1 ..],
657
657
-
},
658
658
-
}) catch |err| {
659
659
-
log.err("couldn't convert timestamp: {}", .{err});
660
660
-
continue;
661
661
-
};
662
662
-
var channel = try client.getOrCreateChannel(target);
663
663
-
channel.last_read = last_read.unixTimestamp();
664
664
-
const last_msg = channel.messages.getLastOrNull() orelse continue;
665
665
-
const time = last_msg.time() orelse continue;
666
666
-
if (time.unixTimestamp() > channel.last_read)
667
667
-
channel.has_unread = true
668
668
-
else
669
669
-
channel.has_unread = false;
670
670
-
},
671
671
-
.PART => {
672
672
-
// get the user
673
673
-
const src = msg.source() orelse continue :loop;
674
674
-
const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
675
675
-
const user = try client.getOrCreateUser(src[0..n]);
676
676
-
677
677
-
// get the channel
678
678
-
var iter = msg.paramIterator();
679
679
-
const target = iter.next() orelse continue;
680
680
-
681
681
-
if (mem.eql(u8, user.nick, client.nickname())) {
682
682
-
for (client.channels.items, 0..) |channel, i| {
683
683
-
if (!mem.eql(u8, channel.name, target)) continue;
684
684
-
var chan = client.channels.orderedRemove(i);
685
685
-
self.state.buffers.selected_idx -|= 1;
686
686
-
chan.deinit(self.alloc);
687
687
-
break;
688
688
-
}
689
689
-
} else {
690
690
-
const channel = try client.getOrCreateChannel(target);
691
691
-
channel.removeMember(user);
692
692
-
}
693
693
-
},
694
694
-
.PRIVMSG, .NOTICE => {
695
695
-
// syntax: <target> :<message>
696
696
-
const msg2: irc.Message = .{
697
697
-
.bytes = try self.alloc.dupe(u8, msg.bytes),
698
698
-
};
699
699
-
var iter = msg2.paramIterator();
700
700
-
const target = blk: {
701
701
-
const tgt = iter.next() orelse continue;
702
702
-
if (mem.eql(u8, tgt, client.nickname())) {
703
703
-
// If the target is us, it likely has our
704
704
-
// hostname in it.
705
705
-
const source = msg2.source() orelse continue;
706
706
-
const n = mem.indexOfScalar(u8, source, '!') orelse source.len;
707
707
-
break :blk source[0..n];
708
708
-
} else break :blk tgt;
709
709
-
};
710
710
-
711
711
-
// We handle batches separately. When we encounter a
712
712
-
// PRIVMSG from a batch, we use the original target
713
713
-
// from the batch start. We also never notify from a
714
714
-
// batched message. Batched messages also require
715
715
-
// sorting
716
716
-
var tag_iter = msg2.tagIterator();
717
717
-
while (tag_iter.next()) |tag| {
718
718
-
if (mem.eql(u8, tag.key, "batch")) {
719
719
-
const entry = client.batches.getEntry(tag.value) orelse @panic("TODO");
720
720
-
var channel = entry.value_ptr.*;
721
721
-
try channel.messages.append(msg2);
722
722
-
std.sort.insertion(irc.Message, channel.messages.items, {}, irc.Message.compareTime);
723
723
-
channel.at_oldest = false;
724
724
-
const time = msg2.time() orelse continue;
725
725
-
if (time.unixTimestamp() > channel.last_read) {
726
726
-
channel.has_unread = true;
727
727
-
const content = iter.next() orelse continue;
728
728
-
if (std.mem.indexOf(u8, content, client.nickname())) |_| {
729
729
-
channel.has_unread_highlight = true;
730
730
-
}
731
731
-
}
732
732
-
break;
733
733
-
}
734
734
-
} else {
735
735
-
// standard handling
736
736
-
var channel = try client.getOrCreateChannel(target);
737
737
-
try channel.messages.append(msg2);
738
738
-
const content = iter.next() orelse continue;
739
739
-
var has_highlight = false;
740
740
-
{
741
741
-
const sender: []const u8 = blk: {
742
742
-
const src = msg2.source() orelse break :blk "";
743
743
-
const l = std.mem.indexOfScalar(u8, src, '!') orelse
744
744
-
std.mem.indexOfScalar(u8, src, '@') orelse
745
745
-
src.len;
746
746
-
break :blk src[0..l];
747
747
-
};
748
748
-
try lua.onMessage(lua_state, client, channel.name, sender, content);
749
749
-
}
750
750
-
if (std.mem.indexOf(u8, content, client.nickname())) |_| {
751
751
-
var buf: [64]u8 = undefined;
752
752
-
const title_or_err = if (msg2.source()) |source|
753
753
-
std.fmt.bufPrint(&buf, "{s} - {s}", .{ channel.name, source })
754
754
-
else
755
755
-
std.fmt.bufPrint(&buf, "{s}", .{channel.name});
756
756
-
const title = title_or_err catch title: {
757
757
-
const len = @min(buf.len, channel.name.len);
758
758
-
@memcpy(buf[0..len], channel.name[0..len]);
759
759
-
break :title buf[0..len];
760
760
-
};
761
761
-
try self.vx.notify(writer, title, content);
762
762
-
has_highlight = true;
763
763
-
}
764
764
-
const time = msg2.time() orelse continue;
765
765
-
if (time.unixTimestamp() > channel.last_read) {
766
766
-
channel.has_unread_highlight = has_highlight;
767
767
-
channel.has_unread = true;
768
768
-
}
769
769
-
}
770
770
-
771
771
-
// If we get a message from the current user mark the channel as
772
772
-
// read, since they must have just sent the message.
773
773
-
const sender: []const u8 = blk: {
774
774
-
const src = msg2.source() orelse break :blk "";
775
775
-
const l = std.mem.indexOfScalar(u8, src, '!') orelse
776
776
-
std.mem.indexOfScalar(u8, src, '@') orelse
777
777
-
src.len;
778
778
-
break :blk src[0..l];
779
779
-
};
780
780
-
if (std.mem.eql(u8, sender, client.nickname())) {
781
781
-
self.markSelectedChannelRead();
782
782
-
}
783
783
-
},
784
784
-
}
785
785
-
},
786
786
-
}
787
787
-
}
788
788
-
789
789
-
if (redraw) {
790
790
-
try self.draw(&input);
791
791
-
last_frame = std.time.milliTimestamp();
792
792
-
}
793
793
-
}
794
794
-
}
795
869
pub fn nextChannel(self: *App) void {
796
870
// When leaving a channel we mark it as read, so we make sure that's done
797
871
// before we change to the new channel.
···
968
1042
return client.queueWrite(msg);
969
1043
}
970
1044
},
971
971
-
.redraw => self.vx.queueRefresh(),
1045
1045
+
.redraw => {},
1046
1046
+
// .redraw => self.vx.queueRefresh(),
972
1047
.version => {
973
1048
if (channel == null) return error.InvalidCommand;
974
1049
const msg = try std.fmt.bufPrint(
···
1978
2053
}
1979
2054
}
1980
2055
1981
1981
-
fn markSelectedChannelRead(self: *App) void {
2056
2056
+
pub fn markSelectedChannelRead(self: *App) void {
1982
2057
const buffer = self.selectedBuffer() orelse return;
1983
2058
1984
2059
switch (buffer) {
+497
-5
src/irc.zig
···
1
1
const std = @import("std");
2
2
const comlink = @import("comlink.zig");
3
3
+
const lua = @import("lua.zig");
3
4
const tls = @import("tls");
4
5
const vaxis = @import("vaxis");
5
6
const zeit = @import("zeit");
6
7
const bytepool = @import("pool.zig");
7
8
8
9
const testing = std.testing;
10
10
+
const mem = std.mem;
9
11
10
12
const Allocator = std.mem.Allocator;
13
13
+
const Base64Encoder = std.base64.standard.Encoder;
11
14
pub const MessagePool = bytepool.BytePool(max_raw_msg_size * 4);
12
15
pub const Slice = MessagePool.Slice;
13
16
···
25
28
client: *Client,
26
29
channel: *Channel,
27
30
};
31
31
+
32
32
+
pub const Event = comlink.IrcEvent;
28
33
29
34
pub const Command = enum {
30
35
RPL_WELCOME, // 001
···
495
500
496
501
thread: ?std.Thread = null,
497
502
498
498
-
pub fn init(alloc: std.mem.Allocator, app: *comlink.App, wq: *comlink.WriteQueue, cfg: Config) !Client {
503
503
+
redraw: std.atomic.Value(bool),
504
504
+
fifo: std.fifo.LinearFifo(Event, .Dynamic),
505
505
+
fifo_mutex: std.Thread.Mutex,
506
506
+
507
507
+
pub fn init(
508
508
+
alloc: std.mem.Allocator,
509
509
+
app: *comlink.App,
510
510
+
wq: *comlink.WriteQueue,
511
511
+
cfg: Config,
512
512
+
) !Client {
499
513
return .{
500
514
.alloc = alloc,
501
515
.app = app,
···
506
520
.users = std.StringHashMap(*User).init(alloc),
507
521
.batches = std.StringHashMap(*Channel).init(alloc),
508
522
.write_queue = wq,
523
523
+
.redraw = std.atomic.Value(bool).init(false),
524
524
+
.fifo = std.fifo.LinearFifo(Event, .Dynamic).init(alloc),
525
525
+
.fifo_mutex = .{},
509
526
};
510
527
}
511
528
···
544
561
self.alloc.free(key.*);
545
562
}
546
563
batches.deinit();
564
564
+
self.fifo.deinit();
565
565
+
}
566
566
+
567
567
+
pub fn drainFifo(self: *Client) void {
568
568
+
self.fifo_mutex.lock();
569
569
+
defer self.fifo_mutex.unlock();
570
570
+
while (self.fifo.readItem()) |item| {
571
571
+
self.handleEvent(item) catch |err| {
572
572
+
log.err("error: {}", .{err});
573
573
+
};
574
574
+
}
575
575
+
}
576
576
+
577
577
+
pub fn handleEvent(self: *Client, event: Event) !void {
578
578
+
const msg: Message = .{ .bytes = event.msg.slice() };
579
579
+
const client = event.client;
580
580
+
defer event.msg.deinit();
581
581
+
switch (msg.command()) {
582
582
+
.unknown => {},
583
583
+
.CAP => {
584
584
+
// syntax: <client> <ACK/NACK> :caps
585
585
+
var iter = msg.paramIterator();
586
586
+
_ = iter.next() orelse return; // client
587
587
+
const ack_or_nak = iter.next() orelse return;
588
588
+
const caps = iter.next() orelse return;
589
589
+
var cap_iter = mem.splitScalar(u8, caps, ' ');
590
590
+
while (cap_iter.next()) |cap| {
591
591
+
if (mem.eql(u8, ack_or_nak, "ACK")) {
592
592
+
client.ack(cap);
593
593
+
if (mem.eql(u8, cap, "sasl"))
594
594
+
try client.queueWrite("AUTHENTICATE PLAIN\r\n");
595
595
+
} else if (mem.eql(u8, ack_or_nak, "NAK")) {
596
596
+
log.debug("CAP not supported {s}", .{cap});
597
597
+
}
598
598
+
}
599
599
+
},
600
600
+
.AUTHENTICATE => {
601
601
+
var iter = msg.paramIterator();
602
602
+
while (iter.next()) |param| {
603
603
+
// A '+' is the continuuation to send our
604
604
+
// AUTHENTICATE info
605
605
+
if (!mem.eql(u8, param, "+")) continue;
606
606
+
var buf: [4096]u8 = undefined;
607
607
+
const config = client.config;
608
608
+
const sasl = try std.fmt.bufPrint(
609
609
+
&buf,
610
610
+
"{s}\x00{s}\x00{s}",
611
611
+
.{ config.user, config.nick, config.password },
612
612
+
);
613
613
+
614
614
+
// Create a buffer big enough for the base64 encoded string
615
615
+
const b64_buf = try self.alloc.alloc(u8, Base64Encoder.calcSize(sasl.len));
616
616
+
defer self.alloc.free(b64_buf);
617
617
+
const encoded = Base64Encoder.encode(b64_buf, sasl);
618
618
+
// Make our message
619
619
+
const auth = try std.fmt.bufPrint(
620
620
+
&buf,
621
621
+
"AUTHENTICATE {s}\r\n",
622
622
+
.{encoded},
623
623
+
);
624
624
+
try client.queueWrite(auth);
625
625
+
if (config.network_id) |id| {
626
626
+
const bind = try std.fmt.bufPrint(
627
627
+
&buf,
628
628
+
"BOUNCER BIND {s}\r\n",
629
629
+
.{id},
630
630
+
);
631
631
+
try client.queueWrite(bind);
632
632
+
}
633
633
+
try client.queueWrite("CAP END\r\n");
634
634
+
}
635
635
+
},
636
636
+
.RPL_WELCOME => {
637
637
+
const now = try zeit.instant(.{});
638
638
+
var now_buf: [30]u8 = undefined;
639
639
+
const now_fmt = try now.time().bufPrint(&now_buf, .rfc3339);
640
640
+
641
641
+
const past = try now.subtract(.{ .days = 7 });
642
642
+
var past_buf: [30]u8 = undefined;
643
643
+
const past_fmt = try past.time().bufPrint(&past_buf, .rfc3339);
644
644
+
645
645
+
var buf: [128]u8 = undefined;
646
646
+
const targets = try std.fmt.bufPrint(
647
647
+
&buf,
648
648
+
"CHATHISTORY TARGETS timestamp={s} timestamp={s} 50\r\n",
649
649
+
.{ now_fmt, past_fmt },
650
650
+
);
651
651
+
try client.queueWrite(targets);
652
652
+
// on_connect callback
653
653
+
try lua.onConnect(self.app.lua, client);
654
654
+
},
655
655
+
.RPL_YOURHOST => {},
656
656
+
.RPL_CREATED => {},
657
657
+
.RPL_MYINFO => {},
658
658
+
.RPL_ISUPPORT => {
659
659
+
// syntax: <client> <token>[ <token>] :are supported
660
660
+
var iter = msg.paramIterator();
661
661
+
_ = iter.next() orelse return; // client
662
662
+
while (iter.next()) |token| {
663
663
+
if (mem.eql(u8, token, "WHOX"))
664
664
+
client.supports.whox = true
665
665
+
else if (mem.startsWith(u8, token, "PREFIX")) {
666
666
+
const prefix = blk: {
667
667
+
const idx = mem.indexOfScalar(u8, token, ')') orelse
668
668
+
// default is "@+"
669
669
+
break :blk try self.alloc.dupe(u8, "@+");
670
670
+
break :blk try self.alloc.dupe(u8, token[idx + 1 ..]);
671
671
+
};
672
672
+
client.supports.prefix = prefix;
673
673
+
}
674
674
+
}
675
675
+
},
676
676
+
.RPL_LOGGEDIN => {},
677
677
+
.RPL_TOPIC => {
678
678
+
// syntax: <client> <channel> :<topic>
679
679
+
var iter = msg.paramIterator();
680
680
+
_ = iter.next() orelse return; // client ("*")
681
681
+
const channel_name = iter.next() orelse return; // channel
682
682
+
const topic = iter.next() orelse return; // topic
683
683
+
684
684
+
var channel = try client.getOrCreateChannel(channel_name);
685
685
+
if (channel.topic) |old_topic| {
686
686
+
self.alloc.free(old_topic);
687
687
+
}
688
688
+
channel.topic = try self.alloc.dupe(u8, topic);
689
689
+
},
690
690
+
.RPL_SASLSUCCESS => {},
691
691
+
.RPL_WHOREPLY => {
692
692
+
// syntax: <client> <channel> <username> <host> <server> <nick> <flags> :<hopcount> <real name>
693
693
+
var iter = msg.paramIterator();
694
694
+
_ = iter.next() orelse return; // client
695
695
+
const channel_name = iter.next() orelse return; // channel
696
696
+
if (mem.eql(u8, channel_name, "*")) return;
697
697
+
_ = iter.next() orelse return; // username
698
698
+
_ = iter.next() orelse return; // host
699
699
+
_ = iter.next() orelse return; // server
700
700
+
const nick = iter.next() orelse return; // nick
701
701
+
const flags = iter.next() orelse return; // flags
702
702
+
703
703
+
const user_ptr = try client.getOrCreateUser(nick);
704
704
+
if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true;
705
705
+
var channel = try client.getOrCreateChannel(channel_name);
706
706
+
707
707
+
const prefix = for (flags) |c| {
708
708
+
if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| {
709
709
+
break c;
710
710
+
}
711
711
+
} else ' ';
712
712
+
713
713
+
try channel.addMember(user_ptr, .{ .prefix = prefix });
714
714
+
},
715
715
+
.RPL_WHOSPCRPL => {
716
716
+
// syntax: <client> <channel> <nick> <flags> :<realname>
717
717
+
var iter = msg.paramIterator();
718
718
+
_ = iter.next() orelse return;
719
719
+
const channel_name = iter.next() orelse return; // channel
720
720
+
const nick = iter.next() orelse return;
721
721
+
const flags = iter.next() orelse return;
722
722
+
723
723
+
const user_ptr = try client.getOrCreateUser(nick);
724
724
+
if (iter.next()) |real_name| {
725
725
+
if (user_ptr.real_name) |old_name| {
726
726
+
self.alloc.free(old_name);
727
727
+
}
728
728
+
user_ptr.real_name = try self.alloc.dupe(u8, real_name);
729
729
+
}
730
730
+
if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true;
731
731
+
var channel = try client.getOrCreateChannel(channel_name);
732
732
+
733
733
+
const prefix = for (flags) |c| {
734
734
+
if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| {
735
735
+
break c;
736
736
+
}
737
737
+
} else ' ';
738
738
+
739
739
+
try channel.addMember(user_ptr, .{ .prefix = prefix });
740
740
+
},
741
741
+
.RPL_ENDOFWHO => {
742
742
+
// syntax: <client> <mask> :End of WHO list
743
743
+
var iter = msg.paramIterator();
744
744
+
_ = iter.next() orelse return; // client
745
745
+
const channel_name = iter.next() orelse return; // channel
746
746
+
if (mem.eql(u8, channel_name, "*")) return;
747
747
+
var channel = try client.getOrCreateChannel(channel_name);
748
748
+
channel.in_flight.who = false;
749
749
+
},
750
750
+
.RPL_NAMREPLY => {
751
751
+
// syntax: <client> <symbol> <channel> :[<prefix>]<nick>{ [<prefix>]<nick>}
752
752
+
var iter = msg.paramIterator();
753
753
+
_ = iter.next() orelse return; // client
754
754
+
_ = iter.next() orelse return; // symbol
755
755
+
const channel_name = iter.next() orelse return; // channel
756
756
+
const names = iter.next() orelse return;
757
757
+
var channel = try client.getOrCreateChannel(channel_name);
758
758
+
var name_iter = std.mem.splitScalar(u8, names, ' ');
759
759
+
while (name_iter.next()) |name| {
760
760
+
const nick, const prefix = for (client.supports.prefix) |ch| {
761
761
+
if (name[0] == ch) {
762
762
+
break .{ name[1..], name[0] };
763
763
+
}
764
764
+
} else .{ name, ' ' };
765
765
+
766
766
+
if (prefix != ' ') {
767
767
+
log.debug("HAS PREFIX {s}", .{name});
768
768
+
}
769
769
+
770
770
+
const user_ptr = try client.getOrCreateUser(nick);
771
771
+
772
772
+
try channel.addMember(user_ptr, .{ .prefix = prefix, .sort = false });
773
773
+
}
774
774
+
775
775
+
channel.sortMembers();
776
776
+
},
777
777
+
.RPL_ENDOFNAMES => {
778
778
+
// syntax: <client> <channel> :End of /NAMES list
779
779
+
var iter = msg.paramIterator();
780
780
+
_ = iter.next() orelse return; // client
781
781
+
const channel_name = iter.next() orelse return; // channel
782
782
+
var channel = try client.getOrCreateChannel(channel_name);
783
783
+
channel.in_flight.names = false;
784
784
+
},
785
785
+
.BOUNCER => {
786
786
+
var iter = msg.paramIterator();
787
787
+
while (iter.next()) |param| {
788
788
+
if (mem.eql(u8, param, "NETWORK")) {
789
789
+
const id = iter.next() orelse continue;
790
790
+
const attr = iter.next() orelse continue;
791
791
+
// check if we already have this network
792
792
+
for (self.app.clients.items, 0..) |cl, i| {
793
793
+
if (cl.config.network_id) |net_id| {
794
794
+
if (mem.eql(u8, net_id, id)) {
795
795
+
if (mem.eql(u8, attr, "*")) {
796
796
+
// * means the network was
797
797
+
// deleted
798
798
+
cl.deinit();
799
799
+
_ = self.app.clients.swapRemove(i);
800
800
+
}
801
801
+
return;
802
802
+
}
803
803
+
}
804
804
+
}
805
805
+
806
806
+
var cfg = client.config;
807
807
+
cfg.network_id = try self.alloc.dupe(u8, id);
808
808
+
809
809
+
var attr_iter = std.mem.splitScalar(u8, attr, ';');
810
810
+
while (attr_iter.next()) |kv| {
811
811
+
const n = std.mem.indexOfScalar(u8, kv, '=') orelse continue;
812
812
+
const key = kv[0..n];
813
813
+
if (mem.eql(u8, key, "name"))
814
814
+
cfg.name = try self.alloc.dupe(u8, kv[n + 1 ..])
815
815
+
else if (mem.eql(u8, key, "nickname"))
816
816
+
cfg.network_nick = try self.alloc.dupe(u8, kv[n + 1 ..]);
817
817
+
}
818
818
+
try self.app.connect(cfg);
819
819
+
}
820
820
+
}
821
821
+
},
822
822
+
.AWAY => {
823
823
+
const src = msg.source() orelse return;
824
824
+
var iter = msg.paramIterator();
825
825
+
const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
826
826
+
const user = try client.getOrCreateUser(src[0..n]);
827
827
+
// If there are any params, the user is away. Otherwise
828
828
+
// they are back.
829
829
+
user.away = if (iter.next()) |_| true else false;
830
830
+
},
831
831
+
.BATCH => {
832
832
+
var iter = msg.paramIterator();
833
833
+
const tag = iter.next() orelse return;
834
834
+
switch (tag[0]) {
835
835
+
'+' => {
836
836
+
const batch_type = iter.next() orelse return;
837
837
+
if (mem.eql(u8, batch_type, "chathistory")) {
838
838
+
const target = iter.next() orelse return;
839
839
+
var channel = try client.getOrCreateChannel(target);
840
840
+
channel.at_oldest = true;
841
841
+
const duped_tag = try self.alloc.dupe(u8, tag[1..]);
842
842
+
try client.batches.put(duped_tag, channel);
843
843
+
}
844
844
+
},
845
845
+
'-' => {
846
846
+
const key = client.batches.getKey(tag[1..]) orelse return;
847
847
+
var chan = client.batches.get(key) orelse @panic("key should exist here");
848
848
+
chan.history_requested = false;
849
849
+
_ = client.batches.remove(key);
850
850
+
self.alloc.free(key);
851
851
+
},
852
852
+
else => {},
853
853
+
}
854
854
+
},
855
855
+
.CHATHISTORY => {
856
856
+
var iter = msg.paramIterator();
857
857
+
const should_targets = iter.next() orelse return;
858
858
+
if (!mem.eql(u8, should_targets, "TARGETS")) return;
859
859
+
const target = iter.next() orelse return;
860
860
+
// we only add direct messages, not more channels
861
861
+
assert(target.len > 0);
862
862
+
if (target[0] == '#') return;
863
863
+
864
864
+
var channel = try client.getOrCreateChannel(target);
865
865
+
const user_ptr = try client.getOrCreateUser(target);
866
866
+
const me_ptr = try client.getOrCreateUser(client.nickname());
867
867
+
try channel.addMember(user_ptr, .{});
868
868
+
try channel.addMember(me_ptr, .{});
869
869
+
// we set who_requested so we don't try to request
870
870
+
// who on DMs
871
871
+
channel.who_requested = true;
872
872
+
var buf: [128]u8 = undefined;
873
873
+
const mark_read = try std.fmt.bufPrint(
874
874
+
&buf,
875
875
+
"MARKREAD {s}\r\n",
876
876
+
.{channel.name},
877
877
+
);
878
878
+
try client.queueWrite(mark_read);
879
879
+
try client.requestHistory(.after, channel);
880
880
+
},
881
881
+
.JOIN => {
882
882
+
// get the user
883
883
+
const src = msg.source() orelse return;
884
884
+
const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
885
885
+
const user = try client.getOrCreateUser(src[0..n]);
886
886
+
887
887
+
// get the channel
888
888
+
var iter = msg.paramIterator();
889
889
+
const target = iter.next() orelse return;
890
890
+
var channel = try client.getOrCreateChannel(target);
891
891
+
892
892
+
// If it's our nick, we request chat history
893
893
+
if (mem.eql(u8, user.nick, client.nickname())) {
894
894
+
try client.requestHistory(.after, channel);
895
895
+
if (self.app.explicit_join) {
896
896
+
self.app.selectChannelName(client, target);
897
897
+
self.app.explicit_join = false;
898
898
+
}
899
899
+
} else try channel.addMember(user, .{});
900
900
+
},
901
901
+
.MARKREAD => {
902
902
+
var iter = msg.paramIterator();
903
903
+
const target = iter.next() orelse return;
904
904
+
const timestamp = iter.next() orelse return;
905
905
+
const equal = std.mem.indexOfScalar(u8, timestamp, '=') orelse return;
906
906
+
const last_read = zeit.instant(.{
907
907
+
.source = .{
908
908
+
.iso8601 = timestamp[equal + 1 ..],
909
909
+
},
910
910
+
}) catch |err| {
911
911
+
log.err("couldn't convert timestamp: {}", .{err});
912
912
+
return;
913
913
+
};
914
914
+
var channel = try client.getOrCreateChannel(target);
915
915
+
channel.last_read = last_read.unixTimestamp();
916
916
+
const last_msg = channel.messages.getLastOrNull() orelse return;
917
917
+
const time = last_msg.time() orelse return;
918
918
+
if (time.unixTimestamp() > channel.last_read)
919
919
+
channel.has_unread = true
920
920
+
else
921
921
+
channel.has_unread = false;
922
922
+
},
923
923
+
.PART => {
924
924
+
// get the user
925
925
+
const src = msg.source() orelse return;
926
926
+
const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len;
927
927
+
const user = try client.getOrCreateUser(src[0..n]);
928
928
+
929
929
+
// get the channel
930
930
+
var iter = msg.paramIterator();
931
931
+
const target = iter.next() orelse return;
932
932
+
933
933
+
if (mem.eql(u8, user.nick, client.nickname())) {
934
934
+
for (client.channels.items, 0..) |channel, i| {
935
935
+
if (!mem.eql(u8, channel.name, target)) continue;
936
936
+
var chan = client.channels.orderedRemove(i);
937
937
+
self.app.state.buffers.selected_idx -|= 1;
938
938
+
chan.deinit(self.app.alloc);
939
939
+
break;
940
940
+
}
941
941
+
} else {
942
942
+
const channel = try client.getOrCreateChannel(target);
943
943
+
channel.removeMember(user);
944
944
+
}
945
945
+
},
946
946
+
.PRIVMSG, .NOTICE => {
947
947
+
// syntax: <target> :<message>
948
948
+
const msg2: Message = .{
949
949
+
.bytes = try self.app.alloc.dupe(u8, msg.bytes),
950
950
+
};
951
951
+
var iter = msg2.paramIterator();
952
952
+
const target = blk: {
953
953
+
const tgt = iter.next() orelse return;
954
954
+
if (mem.eql(u8, tgt, client.nickname())) {
955
955
+
// If the target is us, it likely has our
956
956
+
// hostname in it.
957
957
+
const source = msg2.source() orelse return;
958
958
+
const n = mem.indexOfScalar(u8, source, '!') orelse source.len;
959
959
+
break :blk source[0..n];
960
960
+
} else break :blk tgt;
961
961
+
};
962
962
+
963
963
+
// We handle batches separately. When we encounter a
964
964
+
// PRIVMSG from a batch, we use the original target
965
965
+
// from the batch start. We also never notify from a
966
966
+
// batched message. Batched messages also require
967
967
+
// sorting
968
968
+
var tag_iter = msg2.tagIterator();
969
969
+
while (tag_iter.next()) |tag| {
970
970
+
if (mem.eql(u8, tag.key, "batch")) {
971
971
+
const entry = client.batches.getEntry(tag.value) orelse @panic("TODO");
972
972
+
var channel = entry.value_ptr.*;
973
973
+
try channel.messages.append(msg2);
974
974
+
std.sort.insertion(Message, channel.messages.items, {}, Message.compareTime);
975
975
+
channel.at_oldest = false;
976
976
+
const time = msg2.time() orelse continue;
977
977
+
if (time.unixTimestamp() > channel.last_read) {
978
978
+
channel.has_unread = true;
979
979
+
const content = iter.next() orelse continue;
980
980
+
if (std.mem.indexOf(u8, content, client.nickname())) |_| {
981
981
+
channel.has_unread_highlight = true;
982
982
+
}
983
983
+
}
984
984
+
break;
985
985
+
}
986
986
+
} else {
987
987
+
// standard handling
988
988
+
var channel = try client.getOrCreateChannel(target);
989
989
+
try channel.messages.append(msg2);
990
990
+
const content = iter.next() orelse return;
991
991
+
var has_highlight = false;
992
992
+
{
993
993
+
const sender: []const u8 = blk: {
994
994
+
const src = msg2.source() orelse break :blk "";
995
995
+
const l = std.mem.indexOfScalar(u8, src, '!') orelse
996
996
+
std.mem.indexOfScalar(u8, src, '@') orelse
997
997
+
src.len;
998
998
+
break :blk src[0..l];
999
999
+
};
1000
1000
+
try lua.onMessage(self.app.lua, client, channel.name, sender, content);
1001
1001
+
}
1002
1002
+
if (std.mem.indexOf(u8, content, client.nickname())) |_| {
1003
1003
+
var buf: [64]u8 = undefined;
1004
1004
+
const title_or_err = if (msg2.source()) |source|
1005
1005
+
std.fmt.bufPrint(&buf, "{s} - {s}", .{ channel.name, source })
1006
1006
+
else
1007
1007
+
std.fmt.bufPrint(&buf, "{s}", .{channel.name});
1008
1008
+
const title = title_or_err catch title: {
1009
1009
+
const len = @min(buf.len, channel.name.len);
1010
1010
+
@memcpy(buf[0..len], channel.name[0..len]);
1011
1011
+
break :title buf[0..len];
1012
1012
+
};
1013
1013
+
_ = title;
1014
1014
+
// TODO: fix this
1015
1015
+
// try self.vx.notify(writer, title, content);
1016
1016
+
has_highlight = true;
1017
1017
+
}
1018
1018
+
const time = msg2.time() orelse return;
1019
1019
+
if (time.unixTimestamp() > channel.last_read) {
1020
1020
+
channel.has_unread_highlight = has_highlight;
1021
1021
+
channel.has_unread = true;
1022
1022
+
}
1023
1023
+
}
1024
1024
+
1025
1025
+
// If we get a message from the current user mark the channel as
1026
1026
+
// read, since they must have just sent the message.
1027
1027
+
const sender: []const u8 = blk: {
1028
1028
+
const src = msg2.source() orelse break :blk "";
1029
1029
+
const l = std.mem.indexOfScalar(u8, src, '!') orelse
1030
1030
+
std.mem.indexOfScalar(u8, src, '@') orelse
1031
1031
+
src.len;
1032
1032
+
break :blk src[0..l];
1033
1033
+
};
1034
1034
+
if (std.mem.eql(u8, sender, client.nickname())) {
1035
1035
+
self.app.markSelectedChannelRead();
1036
1036
+
}
1037
1037
+
},
1038
1038
+
}
547
1039
}
548
1040
549
1041
pub fn nickname(self: *Client) []const u8 {
···
569
1061
}
570
1062
}
571
1063
572
572
-
pub fn readLoop(self: *Client, loop: *comlink.EventLoop) !void {
1064
1064
+
pub fn readLoop(self: *Client) !void {
573
1065
var delay: u64 = 1 * std.time.ns_per_s;
574
1066
575
1067
while (!self.should_close) {
···
624
1116
if (now - last_msg > keep_alive + max_rt) {
625
1117
// reconnect??
626
1118
self.status = .disconnected;
627
627
-
loop.postEvent(.redraw);
1119
1119
+
self.redraw.store(true, .unordered);
628
1120
break;
629
1121
}
630
1122
if (now - last_msg > keep_alive) {
···
637
1129
if (self.should_close) return;
638
1130
if (n == 0) {
639
1131
self.status = .disconnected;
640
640
-
loop.postEvent(.redraw);
1132
1132
+
self.redraw.store(true, .unordered);
641
1133
break;
642
1134
}
643
1135
last_msg = std.time.milliTimestamp();
···
649
1141
@memcpy(buffer.slice(), buf[i..idx]);
650
1142
assert(std.mem.eql(u8, buf[idx .. idx + 2], "\r\n"));
651
1143
log.debug("[<-{s}] {s}", .{ self.config.name orelse self.config.server, buffer.slice() });
652
652
-
loop.postEvent(.{ .irc = .{ .client = self, .msg = buffer } });
1144
1144
+
try self.fifo.writeItem(.{ .client = self, .msg = buffer });
653
1145
}
654
1146
if (i != n) {
655
1147
// we had a part of a line read. Copy it to the beginning of the
+19
-20
src/lua.zig
···
5
5
6
6
const irc = comlink.irc;
7
7
const App = comlink.App;
8
8
-
const EventLoop = comlink.EventLoop;
9
8
const Lua = ziglua.Lua;
10
9
11
10
const assert = std.debug.assert;
···
16
15
/// global key for the app userdata pointer in the registry
17
16
const app_key = "comlink.app";
18
17
19
19
-
/// global key for the loop userdata pointer
20
20
-
const loop_key = "comlink.loop";
21
21
-
22
18
/// active client key. This gets replaced with the client context during callbacks
23
19
const client_key = "comlink.client";
24
20
25
25
-
pub fn init(app: *App, lua: *Lua, loop: *comlink.EventLoop) !void {
21
21
+
pub fn init(app: *App) !void {
22
22
+
const lua = app.lua;
26
23
// load standard libraries
27
24
lua.openLibs();
28
25
···
65
62
// keep a reference to our app in the lua state
66
63
lua.pushLightUserdata(app); // [userdata]
67
64
lua.setField(registry_index, app_key); // []
68
68
-
// keep a reference to our loop in the lua state
69
69
-
lua.pushLightUserdata(loop); // [userdata]
70
70
-
lua.setField(registry_index, loop_key); // []
71
65
72
66
// load config
73
67
var buf: [std.posix.PATH_MAX]u8 = undefined;
···
98
92
return app;
99
93
}
100
94
101
101
-
/// retrieves the *Loop lightuserdata from the registry index
102
102
-
fn getLoop(lua: *Lua) *EventLoop {
103
103
-
const lua_type = lua.getField(registry_index, loop_key); // [userdata]
104
104
-
assert(lua_type == .light_userdata); // set by comlink as a lightuserdata
105
105
-
const loop = lua.toUserdata(comlink.EventLoop, -1) catch unreachable; // already asserted
106
106
-
// as lightuserdata
107
107
-
return loop;
108
108
-
}
95
95
+
// /// retrieves the *Loop lightuserdata from the registry index
96
96
+
// fn getLoop(lua: *Lua) *EventLoop {
97
97
+
// const lua_type = lua.getField(registry_index, loop_key); // [userdata]
98
98
+
// assert(lua_type == .light_userdata); // set by comlink as a lightuserdata
99
99
+
// const loop = lua.toUserdata(comlink.EventLoop, -1) catch unreachable; // already asserted
100
100
+
// // as lightuserdata
101
101
+
// return loop;
102
102
+
// }
109
103
110
104
fn getClient(lua: *Lua) *irc.Client {
111
105
const lua_type = lua.getField(registry_index, client_key); // [userdata]
···
354
348
.port = port,
355
349
};
356
350
357
357
-
const loop = getLoop(lua); // []
358
358
-
loop.postEvent(.{ .connect = cfg });
351
351
+
const app = getApp(lua);
352
352
+
app.connect(cfg) catch {
353
353
+
lua.raiseErrorStr("couldn't connect", .{});
354
354
+
};
359
355
360
356
// put the table back on the stack
361
357
Client.getTable(lua, table_ref); // [table]
···
374
370
lua.argCheck(lua.isString(1), 1, "expected a string"); // [string, string]
375
371
lua.argCheck(lua.isString(2), 2, "expected a string"); // [string, string]
376
372
const app = getApp(lua);
373
373
+
_ = app; // autofix
377
374
const title = lua.toString(1) catch { // [string, string]
378
375
lua.raiseErrorStr("couldn't write notification", .{});
379
376
};
377
377
+
_ = title; // autofix
380
378
const body = lua.toString(2) catch { // [string, string]
381
379
lua.raiseErrorStr("couldn't write notification", .{});
382
380
};
381
381
+
_ = body; // autofix
383
382
lua.pop(2); // []
384
384
-
app.vx.notify(app.tty.anyWriter(), title, body) catch
385
385
-
lua.raiseErrorStr("couldn't write notification", .{});
383
383
+
// app.vx.notify(app.tty.anyWriter(), title, body) catch
384
384
+
// lua.raiseErrorStr("couldn't write notification", .{});
386
385
return 0;
387
386
}
388
387
+28
-19
src/main.zig
···
22
22
23
23
/// Called after receiving a terminating signal
24
24
fn cleanUp(sig: c_int) callconv(.C) void {
25
25
-
if (vaxis.Tty.global_tty) |gty| {
25
25
+
if (vaxis.tty.global_tty) |gty| {
26
26
const reset: []const u8 = vaxis.ctlseqs.csi_u_pop ++
27
27
vaxis.ctlseqs.mouse_reset ++
28
28
vaxis.ctlseqs.bp_reset ++
···
79
79
comlink.Command.user_commands = std.StringHashMap(i32).init(alloc);
80
80
defer comlink.Command.user_commands.deinit();
81
81
82
82
-
const lua = try Lua.init(&alloc);
83
83
-
defer lua.deinit();
82
82
+
var app = try vaxis.vxfw.App.init(gpa.allocator());
83
83
+
defer app.deinit();
84
84
+
85
85
+
// const lua = try Lua.init(&alloc);
86
86
+
// defer lua.deinit();
87
87
+
88
88
+
// var app = try comlink.App.init(alloc);
89
89
+
// defer app.deinit();
90
90
+
91
91
+
var comlink_app: comlink.App = undefined;
92
92
+
try comlink_app.init(gpa.allocator());
93
93
+
defer comlink_app.deinit();
84
94
85
85
-
var app = try comlink.App.init(alloc);
86
86
-
defer app.deinit();
95
95
+
try app.run(comlink_app.widget(), .{});
87
96
88
88
-
app.run(lua) catch |err| {
89
89
-
switch (err) {
90
90
-
// ziglua errors
91
91
-
error.LuaError => {
92
92
-
const msg = lua.toString(-1) catch "";
93
93
-
const duped = alloc.dupe(u8, msg) catch "";
94
94
-
app.deinit();
95
95
-
defer alloc.free(duped);
96
96
-
log.err("{s}", .{duped});
97
97
-
return err;
98
98
-
},
99
99
-
else => return err,
100
100
-
}
101
101
-
};
97
97
+
// app.run(lua) catch |err| {
98
98
+
// switch (err) {
99
99
+
// // ziglua errors
100
100
+
// error.LuaError => {
101
101
+
// const msg = lua.toString(-1) catch "";
102
102
+
// const duped = alloc.dupe(u8, msg) catch "";
103
103
+
// app.deinit();
104
104
+
// defer alloc.free(duped);
105
105
+
// log.err("{s}", .{duped});
106
106
+
// return err;
107
107
+
// },
108
108
+
// else => return err,
109
109
+
// }
110
110
+
// };
102
111
}
103
112
104
113
fn argMatch(maybe_short: ?[]const u8, maybe_long: ?[]const u8, arg: [:0]const u8) bool {