From 65c907ddab7f522e2edb4284f29c9de879b681f6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Oct 2024 15:09:37 -1000 Subject: [PATCH 1/3] core: negative x/y for cursor position indicates mouse exited viewport --- src/Surface.zig | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Surface.zig b/src/Surface.zig index 1f3a9f20b..4e311cd38 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3032,6 +3032,11 @@ pub fn mousePressureCallback( /// Cursor position callback. /// +/// Send negative x or y values to indicate the cursor is outside the +/// viewport. The magnitude of the negative values are meaningless; +/// they are only used to indicate the cursor is outside the viewport. +/// It's important to do this to ensure hover states are cleared. +/// /// The mods parameter is optional because some apprts do not provide /// modifier information on cursor position events. If mods is null then /// we'll use the last known mods. This is usually accurate since mod events @@ -3047,6 +3052,36 @@ pub fn cursorPosCallback( crash.sentry.thread_state = self.crashThreadState(); defer crash.sentry.thread_state = null; + // If the position is negative, it is outside our viewport and + // we need to clear any hover states. + if (pos.x < 0 or pos.y < 0) { + // Reset our hyperlink state + self.mouse.link_point = null; + if (self.mouse.over_link) { + self.mouse.over_link = false; + try self.rt_app.performAction( + .{ .surface = self }, + .mouse_shape, + self.io.terminal.mouse_shape, + ); + try self.rt_app.performAction( + .{ .surface = self }, + .mouse_over_link, + .{ .url = "" }, + ); + try self.queueRender(); + } + + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); + + // No mouse point so we don't highlight links + self.renderer_state.mouse.point = null; + self.renderer_state.terminal.screen.dirty.hyperlink_hover = true; + + return; + } + // Always show the mouse again if it is hidden if (self.mouse.hidden) self.showMouse(); From 227eda1d0998b3b60130edfaed16750135091966 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Oct 2024 15:09:37 -1000 Subject: [PATCH 2/3] macos: notify core of mouse exit --- macos/Sources/Ghostty/SurfaceView_AppKit.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 592611f32..1e9708f0f 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -354,6 +354,7 @@ extension Ghostty { addTrackingArea(NSTrackingArea( rect: frame, options: [ + .mouseEnteredAndExited, .mouseMoved, // Only send mouse events that happen in our visible (not obscured) rect @@ -486,6 +487,14 @@ extension Ghostty { super.rightMouseUp(with: event) } + override func mouseExited(with event: NSEvent) { + guard let surface = self.surface else { return } + + // Negative values indicate cursor has left the viewport + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_surface_mouse_pos(surface, -1, -1, mods) + } + override func mouseMoved(with event: NSEvent) { guard let surface = self.surface else { return } From c7f83fcef8f7b01e3d9a150baa77599b0a830c62 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Oct 2024 18:23:46 -0700 Subject: [PATCH 3/3] gtk: notify core of cursor leave --- src/apprt/gtk/Surface.zig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 73837d11d..87de0e749 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -526,6 +526,7 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void { _ = c.g_signal_connect_data(gesture_click, "pressed", c.G_CALLBACK(>kMouseDown), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(gesture_click, "released", c.G_CALLBACK(>kMouseUp), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(ec_motion, "motion", c.G_CALLBACK(>kMouseMotion), self, null, c.G_CONNECT_DEFAULT); + _ = c.g_signal_connect_data(ec_motion, "leave", c.G_CALLBACK(>kMouseLeave), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(ec_scroll, "scroll", c.G_CALLBACK(>kMouseScroll), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(im_context, "preedit-start", c.G_CALLBACK(>kInputPreeditStart), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(im_context, "preedit-changed", c.G_CALLBACK(>kInputPreeditChanged), self, null, c.G_CONNECT_DEFAULT); @@ -1344,6 +1345,22 @@ fn gtkMouseMotion( }; } +fn gtkMouseLeave( + ec: *c.GtkEventControllerMotion, + ud: ?*anyopaque, +) callconv(.C) void { + const self = userdataSelf(ud.?); + + // Get our modifiers + const event = c.gtk_event_controller_get_current_event(@ptrCast(ec)); + const gtk_mods = c.gdk_event_get_modifier_state(event); + const mods = translateMods(gtk_mods); + self.core_surface.cursorPosCallback(.{ .x = -1, .y = -1 }, mods) catch |err| { + log.err("error in cursor pos callback err={}", .{err}); + return; + }; +} + fn gtkMouseScroll( _: *c.GtkEventControllerScroll, x: c.gdouble,