mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
macos: add mouse button intent
This commit is contained in:
@@ -95,6 +95,64 @@ struct KeyEventIntent: AppIntent {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: MouseButtonIntent
|
||||
|
||||
/// App intent to trigger a mouse button event.
|
||||
struct MouseButtonIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Send Mouse Button Event to Terminal"
|
||||
|
||||
@Parameter(
|
||||
title: "Button",
|
||||
description: "The mouse button to press or release.",
|
||||
default: .left
|
||||
)
|
||||
var button: Ghostty.Input.MouseButton
|
||||
|
||||
@Parameter(
|
||||
title: "Action",
|
||||
description: "Whether to press or release the button.",
|
||||
default: .press
|
||||
)
|
||||
var action: Ghostty.Input.MouseState
|
||||
|
||||
@Parameter(
|
||||
title: "Modifier(s)",
|
||||
description: "The modifiers to send with the mouse event.",
|
||||
default: []
|
||||
)
|
||||
var mods: [KeyEventMods]
|
||||
|
||||
@Parameter(
|
||||
title: "Terminal",
|
||||
description: "The terminal to scope this action to."
|
||||
)
|
||||
var terminal: TerminalEntity
|
||||
|
||||
@available(macOS 26.0, *)
|
||||
static var supportedModes: IntentModes = [.background, .foreground]
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
guard let surface = terminal.surfaceModel else {
|
||||
throw GhosttyIntentError.surfaceNotFound
|
||||
}
|
||||
|
||||
// Convert KeyEventMods array to Ghostty.Input.Mods
|
||||
let ghosttyMods = mods.reduce(Ghostty.Input.Mods()) { result, mod in
|
||||
result.union(mod.ghosttyMod)
|
||||
}
|
||||
|
||||
let mouseEvent = Ghostty.Input.MouseButtonEvent(
|
||||
action: action,
|
||||
button: button,
|
||||
mods: ghosttyMods
|
||||
)
|
||||
surface.sendMouseButton(mouseEvent)
|
||||
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Mods
|
||||
|
||||
enum KeyEventMods: String, AppEnum, CaseIterable {
|
||||
|
@@ -215,11 +215,126 @@ extension Ghostty.Input.Action: AppEnum {
|
||||
|
||||
static var caseDisplayRepresentations: [Ghostty.Input.Action : DisplayRepresentation] = [
|
||||
.release: "Release",
|
||||
.press: "Press",
|
||||
.press: "Press",
|
||||
.repeat: "Repeat"
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.MouseEvent
|
||||
|
||||
extension Ghostty.Input {
|
||||
/// Represents a mouse input event with button state, button type, and modifier keys.
|
||||
struct MouseButtonEvent {
|
||||
let action: MouseState
|
||||
let button: MouseButton
|
||||
let mods: Mods
|
||||
|
||||
init(
|
||||
action: MouseState,
|
||||
button: MouseButton,
|
||||
mods: Mods = []
|
||||
) {
|
||||
self.action = action
|
||||
self.button = button
|
||||
self.mods = mods
|
||||
}
|
||||
|
||||
/// Creates a MouseEvent from C enum values.
|
||||
///
|
||||
/// This initializer converts C-style mouse input enums to Swift types.
|
||||
/// Returns nil if any of the C enum values are invalid or unsupported.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - state: The mouse button state (press/release)
|
||||
/// - button: The mouse button that was pressed/released
|
||||
/// - mods: The modifier keys held during the mouse event
|
||||
init?(state: ghostty_input_mouse_state_e, button: ghostty_input_mouse_button_e, mods: ghostty_input_mods_e) {
|
||||
// Convert state
|
||||
switch state {
|
||||
case GHOSTTY_MOUSE_RELEASE: self.action = .release
|
||||
case GHOSTTY_MOUSE_PRESS: self.action = .press
|
||||
default: return nil
|
||||
}
|
||||
|
||||
// Convert button
|
||||
switch button {
|
||||
case GHOSTTY_MOUSE_UNKNOWN: self.button = .unknown
|
||||
case GHOSTTY_MOUSE_LEFT: self.button = .left
|
||||
case GHOSTTY_MOUSE_RIGHT: self.button = .right
|
||||
case GHOSTTY_MOUSE_MIDDLE: self.button = .middle
|
||||
default: return nil
|
||||
}
|
||||
|
||||
// Convert modifiers
|
||||
self.mods = Mods(cMods: mods)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.MouseState
|
||||
|
||||
extension Ghostty.Input {
|
||||
/// `ghostty_input_mouse_state_e`
|
||||
enum MouseState: String, CaseIterable {
|
||||
case release
|
||||
case press
|
||||
|
||||
var cMouseState: ghostty_input_mouse_state_e {
|
||||
switch self {
|
||||
case .release: GHOSTTY_MOUSE_RELEASE
|
||||
case .press: GHOSTTY_MOUSE_PRESS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Ghostty.Input.MouseState: AppEnum {
|
||||
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Mouse State")
|
||||
|
||||
static var caseDisplayRepresentations: [Ghostty.Input.MouseState : DisplayRepresentation] = [
|
||||
.release: "Release",
|
||||
.press: "Press"
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.MouseButton
|
||||
|
||||
extension Ghostty.Input {
|
||||
/// `ghostty_input_mouse_button_e`
|
||||
enum MouseButton: String, CaseIterable {
|
||||
case unknown
|
||||
case left
|
||||
case right
|
||||
case middle
|
||||
|
||||
var cMouseButton: ghostty_input_mouse_button_e {
|
||||
switch self {
|
||||
case .unknown: GHOSTTY_MOUSE_UNKNOWN
|
||||
case .left: GHOSTTY_MOUSE_LEFT
|
||||
case .right: GHOSTTY_MOUSE_RIGHT
|
||||
case .middle: GHOSTTY_MOUSE_MIDDLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Ghostty.Input.MouseButton: AppEnum {
|
||||
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Mouse Button")
|
||||
|
||||
static var caseDisplayRepresentations: [Ghostty.Input.MouseButton : DisplayRepresentation] = [
|
||||
.unknown: "Unknown",
|
||||
.left: "Left",
|
||||
.right: "Right",
|
||||
.middle: "Middle"
|
||||
]
|
||||
|
||||
static var allCases: [Ghostty.Input.MouseButton] = [
|
||||
.left,
|
||||
.right,
|
||||
.middle,
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: Ghostty.Input.Mods
|
||||
|
||||
extension Ghostty.Input {
|
||||
|
@@ -62,6 +62,32 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the terminal has captured mouse input.
|
||||
///
|
||||
/// When the mouse is captured, the terminal application is receiving mouse events
|
||||
/// directly rather than the host system handling them. This typically occurs when
|
||||
/// a terminal application enables mouse reporting mode.
|
||||
@MainActor
|
||||
var mouseCaptured: Bool {
|
||||
ghostty_surface_mouse_captured(surface)
|
||||
}
|
||||
|
||||
/// Send a mouse button event to the terminal.
|
||||
///
|
||||
/// This sends a complete mouse button event including the button state (press/release),
|
||||
/// which button was pressed, and any modifier keys that were held during the event.
|
||||
/// The terminal processes this event according to its mouse handling configuration.
|
||||
///
|
||||
/// - Parameter event: The mouse button event to send to the terminal
|
||||
@MainActor
|
||||
func sendMouseButton(_ event: Input.MouseButtonEvent) {
|
||||
ghostty_surface_mouse_button(
|
||||
surface,
|
||||
event.action.cMouseState,
|
||||
event.button.cMouseButton,
|
||||
event.mods.cMods)
|
||||
}
|
||||
|
||||
/// Perform a keybinding action.
|
||||
///
|
||||
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
||||
|
@@ -1312,8 +1312,8 @@ extension Ghostty {
|
||||
// In this case, AppKit calls menu BEFORE calling any mouse events.
|
||||
// If mouse capturing is enabled then we never show the context menu
|
||||
// so that we can handle ctrl+left-click in the terminal app.
|
||||
guard let surface = self.surface else { return nil }
|
||||
if ghostty_surface_mouse_captured(surface) {
|
||||
guard let surfaceModel else { return nil }
|
||||
if surfaceModel.mouseCaptured {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1323,13 +1323,10 @@ extension Ghostty {
|
||||
//
|
||||
// Note this never sounds a right mouse up event but that's the
|
||||
// same as normal right-click with capturing disabled from AppKit.
|
||||
let mods = Ghostty.ghosttyMods(event.modifierFlags)
|
||||
ghostty_surface_mouse_button(
|
||||
surface,
|
||||
GHOSTTY_MOUSE_PRESS,
|
||||
GHOSTTY_MOUSE_RIGHT,
|
||||
mods
|
||||
)
|
||||
surfaceModel.sendMouseButton(.init(
|
||||
action: .press,
|
||||
button: .right,
|
||||
mods: .init(nsFlags: event.modifierFlags)))
|
||||
|
||||
default:
|
||||
return nil
|
||||
|
Reference in New Issue
Block a user