diff --git a/include/ghostty.h b/include/ghostty.h index c82411820..072a8536a 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -556,7 +556,10 @@ bool ghostty_surface_mouse_button(ghostty_surface_t, ghostty_input_mouse_state_e, ghostty_input_mouse_button_e, ghostty_input_mods_e); -void ghostty_surface_mouse_pos(ghostty_surface_t, double, double); +void ghostty_surface_mouse_pos(ghostty_surface_t, + double, + double, + ghostty_input_mods_e); void ghostty_surface_mouse_scroll(ghostty_surface_t, double, double, diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 90d259d22..82bc547de 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -505,7 +505,8 @@ extension Ghostty { // Convert window position to view position. Note (0, 0) is bottom left. let pos = self.convert(event.locationInWindow, from: nil) - ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y) + let mods = Ghostty.ghosttyMods(event.modifierFlags) + ghostty_surface_mouse_pos(surface, pos.x, frame.height - pos.y, mods) // If focus follows mouse is enabled then move focus to this surface. if let window = self.window as? TerminalWindow, diff --git a/src/Surface.zig b/src/Surface.zig index 3700df7d9..a9b2c17d6 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1459,7 +1459,7 @@ pub fn keyCallback( // mod changes can affect link highlighting. self.mouse.link_point = null; const pos = self.rt_surface.getCursorPos() catch break :mouse_mods; - self.cursorPosCallback(pos) catch {}; + self.cursorPosCallback(pos, null) catch {}; if (rehide) self.mouse.hidden = true; } @@ -2421,7 +2421,7 @@ pub fn mouseButtonCallback( // expensive because it could block all our threads. if (self.hasSelection()) { const pos = try self.rt_surface.getCursorPos(); - try self.cursorPosCallback(pos); + try self.cursorPosCallback(pos, null); return true; } } @@ -2887,9 +2887,18 @@ pub fn mousePressureCallback( } } +/// Cursor position callback. +/// +/// 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 +/// will trigger key press events but on some platforms we don't get them. +/// For example, on macOS, unfocused surfaces don't receive key events but +/// do receive mouse events so we have to rely on updated mods. pub fn cursorPosCallback( self: *Surface, pos: apprt.CursorPos, + mods: ?input.Mods, ) !void { // Crash metadata in case we crash in here crash.sentry.thread_state = self.crashThreadState(); @@ -2898,6 +2907,9 @@ pub fn cursorPosCallback( // Always show the mouse again if it is hidden if (self.mouse.hidden) self.showMouse(); + // Update our modifiers if they changed + if (mods) |v| self.modsChanged(v); + // The mouse position in the viewport const pos_vp = self.posToViewport(pos.x, pos.y); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index f57b16272..b59ab1c9d 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -763,7 +763,12 @@ pub const Surface = struct { }; } - pub fn cursorPosCallback(self: *Surface, x: f64, y: f64) void { + pub fn cursorPosCallback( + self: *Surface, + x: f64, + y: f64, + mods: input.Mods, + ) void { // Convert our unscaled x/y to scaled. self.cursor_pos = self.cursorPosToPixels(.{ .x = @floatCast(x), @@ -776,7 +781,7 @@ pub const Surface = struct { return; }; - self.core_surface.cursorPosCallback(self.cursor_pos) catch |err| { + self.core_surface.cursorPosCallback(self.cursor_pos, mods) catch |err| { log.err("error in cursor pos callback err={}", .{err}); return; }; @@ -1716,8 +1721,20 @@ pub const CAPI = struct { } /// Update the mouse position within the view. - export fn ghostty_surface_mouse_pos(surface: *Surface, x: f64, y: f64) void { - surface.cursorPosCallback(x, y); + export fn ghostty_surface_mouse_pos( + surface: *Surface, + x: f64, + y: f64, + mods: c_int, + ) void { + surface.cursorPosCallback( + x, + y, + @bitCast(@as( + input.Mods.Backing, + @truncate(@as(c_uint, @bitCast(mods))), + )), + ); } export fn ghostty_surface_mouse_scroll( diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index f38214e32..57cb257b4 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -1040,7 +1040,7 @@ pub const Surface = struct { core_win.cursorPosCallback(.{ .x = @floatCast(pos.xpos), .y = @floatCast(pos.ypos), - }) catch |err| { + }, null) catch |err| { log.err("error in cursor pos callback err={}", .{err}); return; }; diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 7d337fbe0..c1146d348 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -1386,7 +1386,7 @@ fn gtkMouseUp( } fn gtkMouseMotion( - _: *c.GtkEventControllerMotion, + ec: *c.GtkEventControllerMotion, x: c.gdouble, y: c.gdouble, ud: ?*anyopaque, @@ -1415,7 +1415,12 @@ fn gtkMouseMotion( self.grabFocus(); } - self.core_surface.cursorPosCallback(self.cursor_pos) catch |err| { + // 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(self.cursor_pos, mods) catch |err| { log.err("error in cursor pos callback err={}", .{err}); return; };