Apple Magic Trackpad on Linux: How I Made It Work on Ubuntu XFCE
Get the Apple Magic Trackpad working on Linux: a guide for Ubuntu XFCE with BlueZ fixes, libinput tuning, autostart, battery indicator, and troubleshooting.

(and it might be saving my shoulder)
There are those problems that don’t really block your work,
but they sit there with you every single day.
Quiet.
Subtle.
Persistent.
In my case: right shoulder pain.
Hours at the computer, mouse on the right, constantly reaching for it.
A tiny movement.
Repeated thousands of times a day.
The kind of thing your body starts complaining about long before your brain takes it seriously.
So I decided to run a slightly weird experiment:
use Apple’s Magic Trackpad as my main pointing device on Ubuntu XFCE.
Spoiler: it works.
But of course… it’s not plug & play.
If you are trying to make Apple Magic Trackpad work on Ubuntu XFCE, the fix comes down to three things: BlueZ configuration, libinput tuning, and making those settings stick across reboots.
Updated May 2026 — Added a new section on the kernel 6.17 hid_magicmouse battery regression (panel showing ◎ 0% forever on Ubuntu kernels 6.17.0-29 and later) and refreshed the battery indicator script with the regression-safe fallback.
The problem
On Linux, the Magic Trackpad is one of those devices that:
- connects via Bluetooth
- shows up as paired
- looks trusted
- looks connected
…and then does absolutely nothing.
No cursor movement.
No scrolling.
No clicking.
It might as well not exist.
BlueZ can see it.
The kernel can’t.
Classic.
Why I wanted it to work
Not for nerd reasons.
Not for Apple fanboy reasons.
For ergonomics.
I wanted:
- a more central hand position
- less arm extension to the right
- less shoulder tension
- a softer, more natural movement pattern
From a biomechanical point of view, a trackpad makes a lot of sense.
If it actually works.
BlueZ fix: make Magic Trackpad work on Ubuntu XFCE
The real issue is that BlueZ, by default, doesn’t expose Apple HID devices correctly as input devices.
So the trackpad connects, but never becomes a real touchpad.
You have to tell it explicitly.
sudo nano /etc/bluetooth/input.conf
Add:
[General]
ClassicBondedOnly=false
UserspaceHID=true
Then:
sudo systemctl restart bluetooth
Clean re-pair:
bluetoothctl
remove XX:XX:XX:XX:XX:XX
scan on
Turn the trackpad on while holding the button until it starts blinking.
pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
exit
Reboot.
Yes, reboot. Always reboot.
Verify that Ubuntu detects the Magic Trackpad
sudo libinput list-devices
And there it is:
Apple Magic Trackpad
That’s the moment.
Now XFCE finally sees it as a real touchpad.
Fix Magic Trackpad scrolling speed on Ubuntu
Out of the box, scrolling is set to 15.
Which is borderline unusable. It feels like scrolling with a broken clutch.
xinput list
xinput set-prop ID "libinput Scrolling Pixel Distance" 50
At 50 it finally becomes human.
Right click, Mac-style
The Magic Trackpad is designed for physical area clicks.
xinput set-prop ID "libinput Click Method Enabled" 1 0
Now:
- bottom-left = left click
- bottom-right = right click
Exactly like macOS.
If you also want tapping:
xinput set-prop ID "libinput Tapping Enabled" 1
xinput set-prop ID "libinput Tapping Button Mapping Enabled" 1 0
Two-finger tap = right click.
Make Magic Trackpad settings persist across reboots
Getting it to work once is nice.
Getting it to work every single morning without touching anything is better.
So I created a small startup script that runs after login and applies all the trackpad fixes automatically.
nano ~/.config/autostart/trackpad-fix.sh
Paste this inside:
#!/bin/bash
sleep 3
xinput set-prop "Apple Magic Trackpad" "libinput Scrolling Pixel Distance" 50
xinput set-prop "Apple Magic Trackpad" "libinput Click Method Enabled" 1 0
xinput set-prop "Apple Magic Trackpad" "libinput Tapping Enabled" 1
xinput set-prop "Apple Magic Trackpad" "libinput Tapping Button Mapping Enabled" 1 0
Make it executable:
chmod +x ~/.config/autostart/trackpad-fix.sh
Add it to:
Settings → Session and Startup → Application Autostart
Command:
/home/youruser/.config/autostart/trackpad-fix.sh
From now on:
- boot the machine
- log in
- trackpad connects
- scroll is fixed
- right click works
No manual commands. No ritual. No debugging before coffee.
The result
- Magic Trackpad auto-connecting at boot
- controlled scrolling
- native right click
- Apple-level precision
- a more central hand position
- a much happier shoulder
On Ubuntu.
On XFCE.
Why I actually did this
Not for fun.
Not for tinkering.
Because we spend too many hours in front of a screen to ignore biomechanics.
That tiny repeated reach for the mouse was slowly loading my shoulder every single day.
The trackpad lets me work closer to the keyboard, with less extension and less tension.
It’s ergonomics.
Not a gadget.
If you work long hours at a computer, these small things compound over time.
Your body always keeps score.
Better to listen early.
How to show Apple Magic Trackpad battery on Ubuntu XFCE panel
If you are using a Magic Trackpad on Ubuntu XFCE, battery visibility is the last missing quality-of-life tweak.
This adds a lightweight battery indicator in the XFCE panel using Generic Monitor and a small bash script. The script tries three sources in order: upower, bluetoothctl, and a direct sysfs read — because on some Ubuntu setups the Magic Trackpad battery is only exposed via /sys/class/power_supply/. It also caches the last known value, so the panel always shows something useful even right after a reconnect when the kernel hasn’t received the battery level yet.
Install the plugin:
sudo apt install xfce4-genmon-plugin
Create the script:
nano ~/.local/bin/trackpad-battery
#!/usr/bin/env bash
set -u
cache_file="/tmp/trackpad-battery-cache"
get_bluetooth_battery() {
local mac=""
local info=""
local battery=""
while read -r _ candidate_mac candidate_name; do
case "$candidate_name" in
*Trackpad*)
mac="$candidate_mac"
break
;;
esac
done < <(bluetoothctl devices Connected 2>/dev/null)
if [ -z "$mac" ]; then
return 1
fi
info=$(bluetoothctl info "$mac" 2>/dev/null)
battery=$(printf '%s\n' "$info" | sed -n 's/^[[:space:]]*Battery Percentage:[[:space:]]*//p' | head -n 1)
if [ -n "$battery" ]; then
printf '%s\n' "$battery"
return 0
fi
# Fallback: read from sysfs using MAC address (lowercased).
# Skip a raw "0": on kernels with the hid_magicmouse battery regression
# (Ubuntu 6.17.0-29+) the power_supply entry is created at connect time
# but capacity is never updated, so 0 means "no data", not "empty".
local sysfs_mac
sysfs_mac=$(printf '%s' "$mac" | tr '[:upper:]' '[:lower:]')
local sysfs_path="/sys/class/power_supply/hid-${sysfs_mac}-battery/capacity"
if [ -r "$sysfs_path" ]; then
battery=$(cat "$sysfs_path" 2>/dev/null)
if [ -n "$battery" ] && [ "$battery" != "0" ]; then
printf '%s%%\n' "$battery"
return 0
fi
fi
return 1
}
device=""
for candidate in $(upower -e 2>/dev/null); do
if upower -i "$candidate" 2>/dev/null | grep -Fqi "Magic Trackpad"; then
device="$candidate"
break
fi
done
bt_pct=$(get_bluetooth_battery || true)
# Cache only real values, never "0%" (which is the regression marker).
if [ -n "$bt_pct" ] && [ "$bt_pct" != "0%" ]; then
printf '%s' "$bt_pct" > "$cache_file"
fi
trackpad_connected=$(bluetoothctl devices Connected 2>/dev/null | grep -c "Trackpad" || true)
render_no_upower() {
if [ -n "$bt_pct" ]; then
echo "<txt>◎ $bt_pct</txt>"
printf '<tool>Apple Magic Trackpad\nSource: bluetoothctl</tool>\n'
elif [ -f "$cache_file" ]; then
cached=$(cat "$cache_file")
echo "<txt>◎ ${cached}~</txt>"
printf '<tool>Apple Magic Trackpad\nBattery (cached): %s</tool>\n' "$cached"
elif [ "$trackpad_connected" -gt 0 ]; then
echo "<txt>◎ —</txt>"
printf '<tool>Trackpad connected\nBattery not reported by driver (kernel %s regression)</tool>\n' "$(uname -r)"
else
echo "<txt>◎ ✕</txt>"
echo "<tool>Magic Trackpad not connected</tool>"
fi
}
if [ -z "$device" ]; then
render_no_upower
exit 0
fi
info=$(upower -i "$device" 2>/dev/null)
pct=$(printf '%s\n' "$info" | awk '/percentage/ {print $2}')
state=$(printf '%s\n' "$info" | awk -F: '/state/ {gsub(/^[[:space:]]+/, "", $2); print $2; exit}')
updated=$(printf '%s\n' "$info" | sed -n 's/^[[:space:]]*updated:[[:space:]]*//p' | head -n 1)
icon_name=$(printf '%s\n' "$info" | sed -n "s/^[[:space:]]*icon-name:[[:space:]]*'\([^']*\)'.*/\1/p" | head -n 1)
if [ -n "$bt_pct" ]; then
echo "<txt>◎ $bt_pct</txt>"
printf '<tool>Apple Magic Trackpad\nSource: bluetoothctl</tool>\n'
elif [ -n "$pct" ] && [ "$pct" != "0%" -o "${updated#*1970}" = "$updated" ] && [ "$icon_name" != "battery-missing-symbolic" ]; then
echo "<txt>◎ $pct</txt>"
printf '<tool>Apple Magic Trackpad\nState: %s\nUpdated: %s</tool>\n' "${state:-unknown}" "${updated:-unknown}"
elif [ -f "$cache_file" ]; then
cached=$(cat "$cache_file")
echo "<txt>◎ ${cached}~</txt>"
printf '<tool>Apple Magic Trackpad\nBattery (cached): %s\nDriver is not updating the current value (kernel %s regression)</tool>\n' "$cached" "$(uname -r)"
elif [ "$trackpad_connected" -gt 0 ]; then
echo "<txt>◎ —</txt>"
printf '<tool>Trackpad connected\nBattery not reported by driver (kernel %s regression).\nWaiting for an upstream kernel fix.</tool>\n' "$(uname -r)"
else
echo "<txt>◎ ✕</txt>"
echo "<tool>Magic Trackpad not connected</tool>"
fi
Make it executable:
chmod +x ~/.local/bin/trackpad-battery
Add it to the XFCE panel:
- Panel -> Add New Items -> Generic Monitor
- Command:
~/.local/bin/trackpad-battery - Update interval:
30or60seconds
You will get a compact panel label like ◎ 43%, with status details in the tooltip.
Update — May 2026: kernel 6.17 hid_magicmouse battery regression
If your panel suddenly shows ◎ 0% permanently — but the trackpad still moves the cursor — you are likely hitting a regression in hid_magicmouse shipped with Ubuntu kernels 6.17.0-29-generic and later.
Diagnostic, no root needed:
cat /sys/class/power_supply/hid-XX:XX:XX:XX:XX:XX-battery/capacity
If that returns 0 and upower -i on the same device reports updated: 1970-01-01 with icon-name: 'battery-missing-symbolic', the driver registered the power-supply entry at connect time but is never actually fetching the battery report from the device. That is the bug.
A common red herring: people try to debug it with bluetoothctl info and expect a Battery Percentage: line. Apple’s Magic Trackpad speaks HID-over-Bluetooth (UUID 0x1124), not BLE’s Battery Service (0x180F). BlueZ has never been the source of truth here — the kernel HID driver is. So bluetoothctl not showing battery is not the regression; it’s just how this device works.
The updated script above handles the regression gracefully:
- Treats a raw
0fromsysfsas “no data” (not “empty”), so it doesn’t poison the cache or display. - Falls back to the last known good value with a
~suffix (e.g.◎ 43%~) so the panel still shows something useful. - If there is no cache yet, shows
◎ —with a tooltip explicitly naming the kernel version, so future-you remembers why the value is missing.
When a fixed kernel ships, no script change is needed — as soon as cat /sys/class/power_supply/.../capacity returns a real number again, the panel switches back to live values on the next 30-second tick.
FAQ
Does Apple Magic Trackpad work on Ubuntu XFCE?
Yes, but not out of the box. You usually need the BlueZ UserspaceHID=true fix before Ubuntu exposes it correctly as a touchpad.
Why is the Magic Trackpad connected over Bluetooth but not moving the cursor?
Because Bluetooth can pair the device even when Linux is not exposing it as a usable input device yet. Pairing is not the same thing as a working touchpad.
Can I see Magic Trackpad battery percentage on XFCE?
Yes. Install xfce4-genmon-plugin and use the script above with the XFCE Generic Monitor plugin. The script reads battery data from upower, bluetoothctl, or directly from /sys/class/power_supply/, whichever works on your system.
Why does my Magic Trackpad show 0% battery on Ubuntu kernel 6.17?
It’s a regression in the hid_magicmouse driver on Ubuntu kernels 6.17.0-29 and later: the driver creates /sys/class/power_supply/hid-<mac>-battery/ but never updates capacity, so it stays at 0 with updated: 1970-01-01. Confirm with cat /sys/class/power_supply/hid-XX:XX:XX:XX:XX:XX-battery/capacity. The script in this guide guards against this case by treating 0 as “no data” and falling back to the last cached value.