Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/ghostty/config
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ copy-on-select = clipboard

keybind = ctrl+shift+c=copy_to_clipboard
keybind = ctrl+shift+v=paste_from_clipboard
# Framework+C/V arrive as plain Ctrl+C/V (keyd, see config/keyd/default.conf).
# performable: copy only when a selection exists; otherwise Ctrl+C stays SIGINT.
keybind = performable:ctrl+c=copy_to_clipboard
keybind = ctrl+v=paste_from_clipboard

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Binding ctrl+v globally in Ghostty to paste_from_clipboard will intercept all Ctrl+V keystrokes.

Because keyd maps the Framework key to Ctrl (v = C-v), both physical Ctrl+V and Framework+V will emit Ctrl+V and trigger a paste. This completely disables the terminal's native Ctrl+V functionality, such as blockwise visual mode in Vim/Neovim or quoted-insert in bash/zsh.

Since ctrl+q is also bound to quit (line 52), Vim users won't easily be able to fall back to Ctrl+Q for visual block mode either. Consider keeping the default paste shortcut (ctrl+shift+v) or using a different non-conflicting key combination if you rely on terminal applications that require raw Ctrl+V input.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In-terminal Ctrl-V is now hijacked as paste. Unlike the performable:ctrl+c line above, keybind = ctrl+v=paste_from_clipboard is unconditional, and there is no way to distinguish a keyd-emitted Framework+V from a real user Ctrl-V — both arrive at Ghostty as bare Ctrl+V. So:

  • vim normal-mode Ctrl-V (visual-block) now pastes instead of entering block-select.
  • bash / readline Ctrl-V (quoted-insert, used to type raw ^M, ^[, etc.) now pastes.
  • Any TUI that binds Ctrl+V loses it inside Ghostty.

The old xremap flow avoided this because the Ghostty-only keymap rewrote Framework's Ctrl+V to Ctrl+Shift+V, leaving real Ctrl+V for TUIs. With xremap gone that distinction is lost.

Options:

  • Keep Ctrl+V=paste only in Ghostty on macOS (macos- scoped keybind) and rely on Ctrl+Shift+V on Linux where TUI conflicts matter.
  • Or accept the trade-off and note it in a comment (some users won't mind).
  • performable:ctrl+v=paste_from_clipboard does not help here — performable: for paste keys is based on clipboard non-emptiness, not TUI focus, so it would still swallow Ctrl-V any time there's clipboard content.

keybind = alt+backspace=text:\x1b\x7f
keybind = shift+enter=text:\n
keybind = ctrl+enter=text:\x1b[13;5u
Expand Down
1 change: 0 additions & 1 deletion config/hyprland/hyprland.conf
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ env = NOCTALIA_PAM_SERVICE,noctalia-shell
# =============================================================================
exec-once = dbus-update-activation-environment --systemd --all
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE XDG_RUNTIME_DIR HYPRLAND_INSTANCE_SIGNATURE XDG_SESSION_DESKTOP
exec-once = sleep 1 && systemctl --user restart xremap
exec-once = sleep 1 && systemctl --user restart xdg-desktop-portal-hyprland && sleep 0.5 && systemctl --user restart xdg-desktop-portal-gtk && sleep 0.5 && systemctl --user restart xdg-desktop-portal

# Blue light filter
Expand Down
90 changes: 68 additions & 22 deletions config/keyd/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@
# does not grab I2C HID consumer control devices, so the Framework key
# bypasses keyd unless this id is listed explicitly.
32ac:0006
# xremap creates its own virtual keyboard device via uinput. By default, xremap
# uses vendor/product 0x1234/0x5678 for that output device.
#
# When keyd matches all keyboards via `*`, it can end up grabbing xremap's
# virtual device too, which may cause event feedback/loops and effectively kill
# input. Exclude the default xremap device id to prevent that.
-1234:5678

[main]
# SUPER: CapsLock/RightAlt → Hyprland "$mod" binds (window mgmt, app launch)
# These skip xremap and go directly to Hyprland.
capslock = layer(meta)
rightalt = layer(meta)

# Hyper: Framework key → Ctrl+Alt+Shift+Super (all four modifiers)
# xremap intercepts these and remaps to Ctrl for macOS-style app shortcuts.
# Cmd: Framework key → macOS-style shortcuts (see [cmd_hyper])
# leftmeta is the actual keycode on Framework 13 AI 300
leftmeta = layer(cmd_hyper)
prog1 = layer(cmd_hyper)
Expand All @@ -31,20 +22,75 @@ rightshift = overload(shift, oneshot(rshift))
[rshift]
rightshift = capslock

[cmd_hyper:C-A-S-M]
# Every Framework+key emits Ctrl+Alt+Shift+Super+key (Hyper).
# xremap then converts Hyper+key → Ctrl+key for apps.
# Keys excluded from xremap (e.g. 3/4/5) pass through as Hyper to Hyprland.

# Framework+Tab → Super+Tab for hyprshell window switcher
tab = M-tab

# Clipboard: emit plain Ctrl+C/V directly, bypassing xremap. Electron apps
# (Slack) drop xremap's synthesized Hyper→Ctrl modifier swaps, so clipboard
# keys must not go through that dance. Ghostty converts C-c/C-v to
# C-S-c/C-S-v via its xremap keymap block.
# Cmd layer: Framework key acts like macOS Cmd. Base modifier is Super only
# (M) so holding Framework never asserts Ctrl/Alt/Shift speculatively —
# Slack treats Alt+Click as "mark unread", so a held Alt plus a stray
# trackpad tap kept marking chats unread. Every key below emits its combo
# atomically instead; no xremap middleman.
#
# Unmapped keys fall through as Super+key (inert — no Hyprland binds match).
# Framework+Tab is intentionally unmapped: it becomes a genuinely held
# Super+Tab, which lets hyprshell hold-to-cycle.
[cmd_hyper:M]
# macOS-style app shortcuts: Framework+key = Ctrl+key.
# Ghostty maps ctrl+c/ctrl+v to copy/paste itself (see config/ghostty/config).
a = C-a
b = C-b
c = C-c
d = C-d
e = C-e
g = C-g
h = C-h
i = C-i
j = C-j
k = C-k
m = C-m
n = C-n
o = C-o
p = C-p
q = C-q
r = C-r
s = C-s
t = C-t
u = C-u
v = C-v
w = C-w
x = C-x
y = C-y
z = C-z
0 = C-0
1 = C-1
2 = C-2
6 = C-6
7 = C-7
8 = C-8
9 = C-9
semicolon = C-semicolon
dot = C-dot
comma = C-comma
slash = C-slash
grave = C-grave
backslash = C-backslash
leftbrace = C-leftbrace
rightbrace = C-rightbrace
apostrophe = C-apostrophe
backspace = C-backspace
left = C-left
right = C-right
up = C-up
down = C-down
Comment on lines +78 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In macOS, Cmd+Left and Cmd+Right are used to navigate to the start and end of the line (which map to home and end in Linux), while Cmd+Up and Cmd+Down navigate to the top and bottom of the document (which map to Ctrl+Home and Ctrl+End in Linux).

Currently, you have mapped them to C-left, C-right, C-up, and C-down. However, in config/hyprland/hyprland.conf (lines 343-344), you have bound CTRL, left and CTRL, right globally to switch workspaces:

bind = CTRL, left, workspace, e-1
bind = CTRL, right, workspace, e+1

As a result, pressing Framework+Left or Framework+Right will trigger a workspace switch instead of cursor navigation.

Mapping them to home, end, C-home, and C-end instead will perfectly replicate macOS-style line and document navigation while avoiding the conflict with Hyprland's workspace switching.

left = home
right = end
up = C-home
down = C-end


# Hyprland Hyper binds (Ctrl+Alt+Shift+Super chords, see hyprland.conf):
# f = fullscreen, l = lock, space = launcher, 3/4/5 = screenshot/recording,
# equal/minus = display zoom
f = C-A-S-M-f
l = C-A-S-M-l
space = C-A-S-M-space
3 = C-A-S-M-3
4 = C-A-S-M-4
5 = C-A-S-M-5
equal = C-A-S-M-equal
minus = C-A-S-M-minus

[cmd_hyper+control]
# Framework+Ctrl+5 → terminal split in VS Code
Expand Down
91 changes: 1 addition & 90 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@
nixos-hardware = {
url = "github:NixOS/nixos-hardware/master";
};
xremap = {
url = "github:xremap/nix-flake";
inputs.nixpkgs.follows = "nixpkgs";
};
noctalia-shell = {
# v5 (C++ rewrite): option is programs.noctalia, config is TOML, binary is
# `noctalia`. Not a drop-in from the v4 Quickshell line - v4 settings are not
Expand Down
1 change: 0 additions & 1 deletion home-manager/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ in
++ services
++ [
inputs.agenix.homeManagerModules.default
inputs.xremap.homeManagerModules.default
inputs.noctalia-shell.homeModules.default
]
++ lib.optionals (pkgs.stdenv.isLinux && inputs.host.isDesktop) [
Expand Down
1 change: 0 additions & 1 deletion home-manager/modules/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
./secure-dotenv
./tailscale
./uv-globals
./xremap
]
Loading
Loading