feat: add middle-click action configuration (#12478)

This PR addresses
https://github.com/ghostty-org/ghostty/discussions/12108 implemented
similarly to https://github.com/ghostty-org/ghostty/pull/8254 to allow
middle click + TrackPoint scrolling on MacOS. `primary-paste` naming
comes from `gtk_enable_primary_paste`.

The following configuration values for `middle-click-action` are
provided:
- `primary-paste` - Paste from the selection (or system) clipboard per
`copy-on-select`.
- `ignore` - Do nothing, ignore the middle click.

Tested locally on macOS with Zig 0.15.2 using `zig build
-Doptimize=ReleaseFast`.

Thank you!
This commit is contained in:
Mitchell Hashimoto
2026-04-27 09:29:29 -07:00
committed by GitHub
3 changed files with 41 additions and 15 deletions

View File

@@ -311,6 +311,7 @@ const DerivedConfig = struct {
clipboard_codepoint_map: configpkg.Config.RepeatableClipboardCodepointMap,
copy_on_select: configpkg.CopyOnSelect,
right_click_action: configpkg.RightClickAction,
middle_click_action: configpkg.MiddleClickAction,
confirm_close_surface: configpkg.ConfirmCloseSurface,
cursor_click_to_move: bool,
desktop_notifications: bool,
@@ -389,6 +390,7 @@ const DerivedConfig = struct {
.clipboard_codepoint_map = try config.@"clipboard-codepoint-map".clone(alloc),
.copy_on_select = config.@"copy-on-select",
.right_click_action = config.@"right-click-action",
.middle_click_action = config.@"middle-click-action",
.confirm_close_surface = config.@"confirm-close-surface",
.cursor_click_to_move = config.@"cursor-click-to-move",
.desktop_notifications = config.@"desktop-notifications",
@@ -4014,16 +4016,19 @@ pub fn mouseButtonCallback(
// copy-on-select targets the system clipboard, middle-click reads from
// that instead. Falls back to the standard clipboard on platforms that
// do not support the selection clipboard.
if (button == .middle and action == .press) {
const clipboard: apprt.Clipboard = switch (self.config.copy_on_select) {
.clipboard => .standard,
.true, .false => if (self.rt_surface.supportsClipboard(.selection))
.selection
else
.standard,
};
_ = try self.startClipboardRequest(clipboard, .{ .paste = {} });
}
if (button == .middle and action == .press) switch (self.config.middle_click_action) {
.ignore => {},
.@"primary-paste" => {
const clipboard: apprt.Clipboard = switch (self.config.copy_on_select) {
.clipboard => .standard,
.true, .false => if (self.rt_surface.supportsClipboard(.selection))
.selection
else
.standard,
};
_ = try self.startClipboardRequest(clipboard, .{ .paste = {} });
},
};
// Right-click down selects word for context menus. If the apprt
// doesn't implement context menus this can be a bit weird but they

View File

@@ -23,6 +23,7 @@ pub const Command = Config.Command;
pub const ConfirmCloseSurface = Config.ConfirmCloseSurface;
pub const CopyOnSelect = Config.CopyOnSelect;
pub const RightClickAction = Config.RightClickAction;
pub const MiddleClickAction = Config.MiddleClickAction;
pub const CustomShaderAnimation = Config.CustomShaderAnimation;
pub const FontSyntheticStyle = Config.FontSyntheticStyle;
pub const FontShapingBreak = Config.FontShapingBreak;

View File

@@ -2404,11 +2404,12 @@ keybind: Keybinds = .{},
/// The value `clipboard` will always copy text to the selection clipboard
/// as well as the system clipboard.
///
/// Middle-click paste is always enabled even if this is `false`. The
/// clipboard it pastes from follows this setting: with `true` (or `false`)
/// it reads from the selection clipboard (falling back to the system
/// clipboard on platforms without a selection clipboard); with `clipboard`
/// it reads from the system clipboard.
/// Middle-click primary paste (see `middle-click-action`) is enabled by
/// default even if this is `false`. The clipboard it pastes from follows
/// this setting: with `true` (or `false`) it reads from the selection
/// clipboard (falling back to the system clipboard on platforms without a
/// selection clipboard); with `clipboard` it reads from the system
/// clipboard.
///
/// The default value is true on Linux and macOS.
@"copy-on-select": CopyOnSelect = switch (builtin.os.tag) {
@@ -2430,6 +2431,16 @@ keybind: Keybinds = .{},
/// The default value is `context-menu`.
@"right-click-action": RightClickAction = .@"context-menu",
/// The action to take when the user middle-clicks on the terminal surface.
///
/// Valid values:
/// * `primary-paste` - Paste from the selection (or system) clipboard per
/// `copy-on-select`.
/// * `ignore` - Do nothing, ignore the middle click.
///
/// The default value is `primary-paste`.
@"middle-click-action": MiddleClickAction = .@"primary-paste",
/// The time in milliseconds between clicks to consider a click a repeat
/// (double, triple, etc.) or an entirely new single click. A value of zero will
/// use a platform-specific default. The default on macOS is determined by the
@@ -8653,6 +8664,15 @@ pub const RightClickAction = enum {
@"context-menu",
};
/// Options for middle-click actions.
pub const MiddleClickAction = enum {
/// Paste from the selection/standard clipboard per `copy-on-select`.
@"primary-paste",
/// No action is taken on middle click.
ignore,
};
/// Shell integration values
pub const ShellIntegration = enum {
none,