diff --git a/src/App.zig b/src/App.zig index 1b56ae25a..bd7c06b35 100644 --- a/src/App.zig +++ b/src/App.zig @@ -90,6 +90,18 @@ pub fn run(self: App) !void { // posted by the libuv watcher so that we trigger a libuv loop tick. try glfw.waitEvents(); + // If the window wants the event loop to wakeup, then we "kick" the + // embed thread to wake up. I'm not sure why we have to do this in a + // loop, this is surely a lacking in my understanding of libuv. But + // this works. + if (self.window.wakeup) { + self.window.wakeup = false; + while (embed.sleeping.load(.SeqCst) and embed.terminate.load(.SeqCst) == false) { + try async_h.send(); + try embed.loopRun(); + } + } + // Run the libuv loop try embed.loopRun(); } diff --git a/src/Window.zig b/src/Window.zig index 80e5c86a6..cc2e69c9c 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -38,6 +38,10 @@ terminal: Terminal, /// Timer that blinks the cursor. cursor_timer: libuv.Timer, +/// Set this to true whenver an event occurs that we may want to wake up +/// the event loop. Only set this from the main thread. +wakeup: bool = false, + /// Create a new window. This allocates and returns a pointer because we /// need a stable pointer for user data callbacks. Therefore, a stack-only /// initialization is not currently possible. @@ -119,6 +123,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window { window.setSizeCallback(sizeCallback); window.setCharCallback(charCallback); window.setKeyCallback(keyCallback); + window.setFocusCallback(focusCallback); return self; } @@ -213,6 +218,17 @@ fn keyCallback( } } +fn focusCallback(window: glfw.Window, focused: bool) void { + const win = window.getUserPointer(Window) orelse return; + if (focused) { + win.cursor_timer.start(cursorTimerCallback, 800, 800) catch unreachable; + win.wakeup = true; + } else { + win.grid.cursor_visible = false; + win.cursor_timer.stop() catch unreachable; + } +} + fn cursorTimerCallback(t: *libuv.Timer) void { const win = t.getData(Window) orelse return; win.grid.cursor_visible = !win.grid.cursor_visible; diff --git a/src/libuv/Embed.zig b/src/libuv/Embed.zig index 6fd6552e1..00c4a8d94 100644 --- a/src/libuv/Embed.zig +++ b/src/libuv/Embed.zig @@ -13,11 +13,14 @@ const Loop = @import("Loop.zig"); const Sem = @import("Sem.zig"); const Thread = @import("Thread.zig"); -const TerminateAtomic = std.atomic.Atomic(bool); +const log = std.log.scoped(.libuv_embed); + +const BoolAtomic = std.atomic.Atomic(bool); loop: Loop, sem: Sem, -terminate: TerminateAtomic, +terminate: BoolAtomic, +sleeping: BoolAtomic, callback: fn () void, thread: ?Thread, @@ -27,7 +30,8 @@ pub fn init(alloc: Allocator, loop: Loop, callback: fn () void) !Embed { return Embed{ .loop = loop, .sem = try Sem.init(alloc, 0), - .terminate = TerminateAtomic.init(false), + .terminate = BoolAtomic.init(false), + .sleeping = BoolAtomic.init(false), .callback = callback, .thread = null, }; @@ -77,6 +81,19 @@ fn threadMain(self: *Embed) void { while (self.terminate.load(.SeqCst) == false) { const fd = self.loop.backendFd() catch unreachable; const timeout = self.loop.backendTimeout(); + + // If the timeout is negative then we are sleeping (i.e. no + // timers active or anything). In that case, we set the boolean + // to true so that we can wake up the event loop if we have to. + if (timeout < 0) { + log.debug("going to sleep", .{}); + self.sleeping.store(true, .SeqCst); + } + defer if (timeout < 0) { + log.debug("waking from sleep", .{}); + self.sleeping.store(false, .SeqCst); + }; + switch (builtin.os.tag) { // epoll .linux => { diff --git a/src/libuv/Timer.zig b/src/libuv/Timer.zig index b123cc738..ad3a4f29d 100644 --- a/src/libuv/Timer.zig +++ b/src/libuv/Timer.zig @@ -56,6 +56,24 @@ pub fn stop(self: Timer) !void { try errors.convertError(c.uv_timer_stop(self.handle)); } +/// Stop the timer, and if it is repeating restart it using the repeat value +/// as the timeout. If the timer has never been started before it returns UV_EINVAL. +pub fn again(self: Timer) !void { + try errors.convertError(c.uv_timer_again(self.handle)); +} + +/// Get the timer repeat value. +pub fn getRepeat(self: Timer) u64 { + return c.uv_timer_get_repeat(self.handle); +} + +/// Set the repeat interval value in milliseconds. The timer will be scheduled +/// to run on the given interval, regardless of the callback execution duration, +/// and will follow normal timer semantics in the case of a time-slice overrun. +pub fn setRepeat(self: Timer, repeat: u64) void { + c.uv_timer_set_repeat(self.handle, repeat); +} + test "Timer" { var loop = try Loop.init(testing.allocator); defer loop.deinit(testing.allocator);