You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixing Brightness Keys on MacBook Pro (Omarchy / Hyprland)
Hardware: MacBook Pro 11,5 (Mid-2015, 15", dGPU Radeon + i915, gmux backlight)
Software: Omarchy (Arch Linux + Hyprland), kernel 7.2.5
This document captures the full investigation and the minimal, correct fix for the
F1/F2 brightness keys behaving the macOS-native way: press F1 / F2 to dim/brighten
the panel, hold Fn and press F1 / F2 to send a plain F1 / F2 to the focused app.
It also covers binding F3 (Mission Control) and F4 (Launchpad), which the Apple
keyboard emits as KEY_SCALE (evdev 120) and KEY_ALL_APPLICATIONS (evdev 204)
under fnmode=1 and which Hyprland cannot bind by keysym.
TL;DR
If you just want it working, do these four things and reboot:
# 1. Make macOS-native fn mode permanent.echo'options hid_apple fnmode=1'| sudo tee /etc/modprobe.d/hid_apple.conf
# 2. Tell Hyprland that the internal panel is "Unknown-1" so it stays bound# across reloads (otherwise it can lose the output on hot-reload).
cat >~/.config/hypr/monitors.lua <<'EOF'local omarchy_gdk_scale = 2local omarchy_monitor_scale = 1.6hl.env("GDK_SCALE", tostring(omarchy_gdk_scale))-- The internal panel on MacBookPro11,x reports as "Unknown-1" because-- gmux-controlled i915 doesn't expose an eDP connector name. The wrapper-- at ~/.local/bin/omarchy-hyprland-monitor-focused translates it to eDP-1-- so omarchy-brightness-display routes through gmux_backlight instead of-- the failing DDC/CI path.hl.monitor({ output = "Unknown-1", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })EOF# 3. Shadow omarchy's monitor-name query so the panel is recognised as# internal (eDP-1) by brightness/audio/etc. scripts.
cat >~/.local/bin/omarchy-hyprland-monitor-focused <<'EOF'#!/bin/bashexec /usr/share/omarchy/bin/omarchy-hyprland-monitor-focused "$@" \ | sed -E 's/^Unknown-1$/eDP-1/'EOF
chmod +x ~/.local/bin/omarchy-hyprland-monitor-focused
# 4. Prepend ~/.local/bin to PATH for keybind-spawned processes.# Append this block at the end of ~/.config/hypr/hyprland.lua:
cat >>~/.config/hypr/hyprland.lua <<'EOF'do local current = os.getenv("PATH") or "" local prepended = os.getenv("HOME") .. "/.local/bin" local seen = {} local out = { prepended } for entry in current:gmatch("[^:]+") do if entry ~= "" and entry ~= prepended and not seen[entry] then seen[entry] = true table.insert(out, entry) end end hl.env("PATH", table.concat(out, ":"))endEOF# Apply (Hyprland reload is enough; kernel module needs a reboot for fnmode).
hyprctl reload
sudo reboot
The Problem
On the MacBook Pro 11,5 the Apple Internal Keyboard (USB device 05ac:0274) sends
HID scancodes that the Linux hid_apple driver is supposed to translate to
KEY_BRIGHTNESSDOWN / KEY_BRIGHTNESSUP when the keys are pressed without Fn.
On this kernel, the translation is happening — but two other things are wrong:
Hyprland names the internal panel Unknown-1 instead of eDP-1, because
the gmux-controlled i915 output has no proper EDID connector name. Omarchy's
omarchy-brightness-display script classifies anything not matching
^(eDP|LVDS|DSI)- as an external display and tries to use DDC/CI, which
silently fails. Pressing the brightness keys then has no visible effect.
The default fnmode shipped with hid_apple (and assumed by most online
guides) is the opposite of the macOS default. Kernel 7.2's
modinfo hid_apple is unambiguous about it:
fnmode: 0 = disabled
1 = fkeyslast <- macOS default: media keys first
2 = fkeysfirst <- inverted: F-keys first
Anything still relying on fnmode=2 to get macOS behaviour is wrong.
The Diagnosis Path (what we ran)
These are kept for reference; the actual fix above needs none of them.
1. Read the keyboard's raw evdev stream
/dev/input/event6 is the Apple Internal Keyboard / Trackpad's keyboard
interface. With libinput debug-events, the F1 press shows up as
KEYBOARD_KEY … (-1) pressed — the kernel hid_apple is sending an event the
userspace libinput cannot decode.
A raw-bytes probe (parsing struct input_event directly) gives us both
MSC_SCAN (the raw HID usage) and the Linux keycode:
MSC_SCAN 0x0007003a
EV_KEY code=59(0x03b) value=1 <- plain F1 -> KEY_F1
So Omarchy routes the brightness action through DDC/CI for this panel, which
fails. Manual brightnessctl -d gmux_backlight works fine — backlight device
exists and is writable — confirming the wiring is correct.
3. Identify the fnmode inversion
Reading modinfo hid_apple fnmode gives:
Mode of fn key on Apple keyboards (0 = disabled, 1 = fkeyslast, 2 = fkeysfirst, ...)
fkeyslast (fnmode=1) is what macOS ships with: media keys take precedence,
F1–F12 need Fn for the F-keys. The default at boot is 2 (fkeysfirst) on
Arch's hid_apple. Setting it to 1 is the macOS-native choice.
4. Verify with a focused raw-bytes probe
After setting fnmode=1, the same probe shows the kernel now does the
translation natively:
Press
HID scancode
Linux keycode
Behavior
F1 alone
0x0007003a
224
KEY_BRIGHTNESSDOWN ✓ macOS default
F2 alone
0x0007003b
225
KEY_BRIGHTNESSUP ✓
Fn + F1
0x0007003a
59
KEY_F1 ✓
Fn + F2
0x0007003b
60
KEY_F2 ✓
No userspace remapper (keyd, python daemon, udev hwdb) is needed once
fnmode=1 is in place.
False Leads (and why they didn't end up in the fix)
udev hwdb mapping 0x7003a/3b → brightnessdown/up
Useful only when fnmode is mis-set (and only as a stopgap): it forces every
press of the scancode to be a brightness key, regardless of whether Fn is held,
so it breaks the "Fn+F1 = plain F1" half of the requirement. Removed once
fnmode=1 was confirmed working.
keyd daemon
Installed and configured as [fn] brightnessdown = f1 to translate when Fn
is down. The [fn] layer was ending up "always on" because keyd's view of
fn differs from the kernel's, and the layer ended up intercepting
brightness-down events with no Fn held. Not worth the complexity once
fnmode=1 alone solves it.
Python daemon via libevdev/uinput
Same idea as keyd but rolled by hand. Same problem as keyd: anything we build
here is patching around the wrong fnmode value, not the real root cause.
With fnmode=1 the remaining top-row keys also work macOS-natively out of the
box except F3 / F4, which have no Omarchy binding:
evdev code
Kernel key
Meaning
120
KEY_SCALE
Compiz Expose
204
KEY_ALL_APPLICATIONS
Launchpad / Show apps
Two gotchas were required to get F3 / F4 bound:
1. Hyprland code:N uses XKB keycodes = evdev + 8
Hyprland's bind will fire for a key pressed with a different number than
the Linux evdev code. The input.keyboard.key Lua event reports XKB
keycodes, which are evdev + 8:
F3: evdev 120 -> XKB 128
F4: evdev 204 -> XKB 212
So the correct bindings are code:128 and code:212, not code:120 /
code:204. Verify with a probe that writes XKB keycodes to a file:
2. No XKB keysym exists for KEY_SCALE / KEY_ALL_APPLICATIONS
On a default XKB keymap, evdev 120 and 204 do not map to any standard XF86*
keysym, so you cannot bind them by keysym name (XF86FullScreen,
XF86Tools, etc. all mismatch). You must use the XKB keycode form.
F4 deliberately repeats SUPER + ALT + SPACE (Apps menu) — that is the
macOS-natural Launchpad action; the keycap duplicates rather than diverges.
Verification Commands
# fnmode survived reboot:
cat /sys/module/hid_apple/parameters/fnmode # -> 1# Wrapper is in effect for spawned keybind commands:
PATH=$HOME/.local/bin:/usr/share/omarchy/bin:/usr/bin \
omarchy-hyprland-monitor-focused # -> eDP-1# Backlight device exists and responds:
brightnessctl -d gmux_backlight get # current value
PATH=$HOME/.local/bin:/usr/share/omarchy/bin:/usr/bin \
omarchy-brightness-display +5% # +5% step
brightnessctl -d gmux_backlight get # value increased# Plugin is loaded:
hyprctl version | head -1 # Hyprland 0.56.x
hyprctl plugins list 2>&1| head # hymission 0.8.x# F3/F4 bindings are registered:
hyprctl binds | grep -A1 "Mission Control\|Launchpad"
Pressing F1 / F2 pops the OSD and adjusts brightness; Fn + F1 / Fn + F2 sends
a real F1 / F2 to the focused window. Pressing F3 opens a Mission-Control
style overview of all windows across workspaces (press F3 again or Escape to
close). Pressing F4 opens the Omarchy Apps menu (Launchpad).
sudo ufw delete allow 22/tcp
sudo ufw limit proto tcp from 10.10.0.0/16 to any port 22 comment 'ssh lan'
sudo ufw allow proto tcp from 10.10.0.0/16 to any port 53317 comment 'localsend lan'
sudo ufw allow proto udp from 10.10.0.0/16 to any port 53317 comment 'localsend lan'