diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64a3045 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +pkg-debian/* +!pkg-debian/DEBIAN +*.deb + +pkg-debian/DEBIAN/md5sums diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..04fee6a --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,46 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}", + "/usr/include", + "/usr/src/linux-headers-5.8.0-7642-generic/include/", + "/usr/src/linux-headers-5.8.0-7642-generic/include/uapi", + "/usr/src/linux-headers-5.8.0-7642-generic/include/generated/uapi", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/generated", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/uapi", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/generated/uapi", + "/usr/lib/gcc/x86_64-linux-gnu/10/include/" + ], + "defines": [ + "KBUILD_MODNAME=\"hello_module\"", + "__GNUC__", + "__KERNEL__", + "MODULE" + ], + "intelliSenseMode": "gcc-x64", + "browse": { + "path": [ + "${workspaceFolder}", + "/usr/include", + "/usr/src/linux-headers-5.8.0-7642-generic/include/", + "/usr/src/linux-headers-5.8.0-7642-generic/include/uapi", + "/usr/src/linux-headers-5.8.0-7642-generic/include/generated/uapi", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/generated", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/uapi", + "/usr/src/linux-headers-5.8.0-7642-generic/arch/x86/include/generated/uapi", + "/usr/lib/gcc/x86_64-linux-gnu/10/include/" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1a83278 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,70 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build and Clean the Magicmouse 2 Driver", + "type": "shell", + "command": "cd linux/drivers/hid && make KERNEL_SRC=LINUX_PATH && make KERNEL_SRC=LINUX_PATH clean", + "args": [], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Build the Magicmouse 2 Driver", + "type": "shell", + "command": "cd linux/drivers/hid && make KERNEL_SRC=LINUX_PATH", + "args": [], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Clean the Magicmouse 2 Driver", + "type": "shell", + "command": "cd linux/drivers/hid && make KERNEL_SRC=LINUX_PATH clean", + "args": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Install the Magicmouse 2 Driver (requires sudo password input)", + "type": "shell", + "command": "sudo ./install.sh", + "args": [], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Reinstall the Magicmouse 2 Driver (requires sudo password input)", + "type": "shell", + "command": "sudo ./remove.sh && sudo ./install.sh", + "args": [], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Uninstall the Magicmouse 2 Driver (requires sudo password input)", + "type": "shell", + "command": "sudo ./remove.sh", + "args": [], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + } + ] +} diff --git a/README.md b/README.md index c9c2ba9..42af91a 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,71 @@ # Linux Apple Magic Mouse 2 and Magic Trackpad 2 Driver -This repository contains the linux hid-magicmouse driver with Magic Trackpad 2 and Magic Mouse 2 support for Linux 4.18. For older kernels you might have to diff and backport. +> [!WARNING] +> **In kernel version 5.15 support was added for the magic mouse 2**. Meaning that this driver is only necessary for prior versions of the kernel (`<= 5.14`). If you want the mouse to "just work" either install a more recent kernel or proceed with this driver installation. +> +> Furthermore, this **repository is in Maintenance mode**, meaning that no new features are planned. -This driver is based off of the work of @robotrovsky, @svartalf and probably others. +This repository contains the Linux hid-magicmouse driver with Magic Trackpad 2 and Magic Mouse 2 support for Linux 4.18 onwards. For older kernels, you might have to diff and backport. It also contains 2 fixes to the Magic Mouse 2 regarding Bluetooth random disconnections and no scroll after a Bluetooth reconnection. -The driver is tested in combination with the xf86-libinput and xf86-mtrack driver. +This driver is based on the work of @robotrovsky, @svartalf, @0xABAD, and probably others. Thank you! -Please help to test this driver and report issues. +## Ubuntu (and derivatives) -## Apple Magic Trackpad 2 -The driver supports bluetooth and USB for the trackpad. To connect the Trackpad via bluetooth, it must be clicked once after it is turned on, then the Trackpad tries to reconnect to the last paired (and trusted) connection. +A `.deb` file is now available in Releases. Get the [latest version here](https://github.com/RicardoEPRodrigues/magicmouse-hid/releases/latest). -## Apple Magic Mouse 2 -The drivers supports regular mouse motion and additionally scrolling and mouse middle click. Middle click is a single finger click near the middle portion of the touch surface OR a 3 finger click anywhere on the touch surface if you put the mouse in 3 finger middle click mode (instructions on how to do this are in the installation section. If you like this, please let me know so I can make it the default). Scrolling is a single finger up or down motion anywhere on the touch surface. -## libinput -You can just use the standard xf86-libinput driver and configure it through your Window-Manager-Settings. This driver works very well, but does not support three-finger-drag, but tap-to-drag. +## Install Driver with DKMS and the two fixes. + +Setup/install with: -## mTrack -An example configuration for mtrack can be found in: ``` -usr/share/X11/xorg.conf.d/90-magictrackpad.conf +sudo apt-get install dkms +git clone https://github.com/RicardoEPRodrigues/Linux-Magic-Trackpad-2-Driver.git +cd Linux-Magic-Trackpad-2-Driver +chmod u+x install.sh +sudo ./install.sh ``` -This configuration supports tap-to-click, two-finger-scroll and three-finger-drag. Though scrolling is not as smooth as with xf86-libinput. It can be used as starting point for your own configuration. Make sure, that you have xf86-input-mtrack-git installed and it gets loaded. You find more information about the options here: https://github.com/p2rkw/xf86-input-mtrack -## Installation with DKMS - -@adam-h made a DKMS which can be used for testing: - -Setup/install with: +## Uninstall Driver. -You will need a 4.18 or above kernel. ``` - sudo apt-get install dkms - git clone https://github.com/rohitpid/Linux-Magic-Trackpad-2-Driver.git - cd Linux-Magic-Trackpad-2-Driver/scripts - chmod u+x install.sh - sudo ./install.sh +sudo ./remove.sh ``` -If you want test out 3 finger middle click feature (please do) +## Apple Magic Trackpad 2 +The driver supports Bluetooth and USB for the trackpad. To connect the Trackpad via Bluetooth, it must be clicked once after it is turned on, then the Trackpad tries to reconnect to the last paired (and trusted) connection. -``` - cd Linux-Magic-Trackpad-2-Driver/linux/drivers/hid - make clean - make - sudo rmmod hid-magicmouse - sudo insmod ./hid-magicmouse.ko middle_click_3finger=1 -``` +## Apple Magic Mouse 2 +The driver supports regular mouse motion and, additionally, scrolling and mouse middle click. Middle click is a single finger click near the middle portion of the touch surface OR a 3 finger click anywhere on the touch surface if you put the mouse in 3 finger middle click mode. Scrolling is a single finger up or down motion anywhere on the touch surface. + +### Changing Parameters + +Several parameters are available for you to modify to personalize the driver to your taste. These can be found in `/etc/modprobe.d/hid-magicmouse.conf` after install. Modify them and the next time the driver is loaded it will have the new values. + +### Reloading the driver + +After changing the parameters the driver can be reloaded using the following commands: -Remove with: ``` - sudo ./remove.sh +sudo rmmod hid_magicmouse +sudo modprobe hid_magicmouse ``` -Or just use regular `dkms` commands once you've added `./linux/drivers/hid`. -## Troubleshooting +## Troubleshooting (outdated) If the driver is not working, please make sure that the correct hid-magicmouse driver gets loaded and try the following steps: - cd linux/drivers/hid - make - sudo rmmod hid_magicmouse - sudo insmod ./hid-magicmouse.ko - tail -f ~/.local/share/xorg/Xorg.0.log +``` +cd linux/drivers/hid +make +sudo rmmod hid_magicmouse +sudo insmod ./hid-magicmouse.ko +tail -f ~/.local/share/xorg/Xorg.0.log +``` Now unplug the trackpad and plug it back in, to see which driver gets loaded. -## Data Layout of bluetooth packets. +## Data Layout of Bluetooth packets. ``` /* The data layout for magic mouse 2 is: @@ -118,6 +116,89 @@ Now unplug the trackpad and plug it back in, to see which driver gets loaded. */ ``` + + +## Fixes + +Below is the explanation of the 2 fixes performed when running the `install.sh` shown above. The first relates to the disconnection of the mouse over Bluetooth and will restart the Bluetooth service. The second regards the driver not being loaded when the mouse reconnects with the computer. + +### Bluetooth fix + +There have been many complaints of repeated and random disconnections of the Magic Mouse 2. One solution to this is to disable `eSCO mode` on the Bluetooth service as shown [in this answer](https://askubuntu.com/a/629495/297110). You can disable it like this: + +``` +echo 1 | sudo tee /sys/module/bluetooth/parameters/disable_esco +sudo /etc/init.d/bluetooth restart +# persist setting +echo "options bluetooth disable_esco=1" | sudo tee /etc/modprobe.d/bluetooth-tweaks.conf +``` + +### Driver not loading when connecting Magic Mouse 2 + +[0xABAD](https://github.com/0xABAD/magic-mouse-2) created a fix that loads the driver when it detects the mouse. Here we'll show an updated version that was changed a bit to use the idProduct of the device to identify any Magic Mouse 2. + +To begin we need to build the driver and register it as a kernel module, please take a look at `scripts/install.sh`. + +With that, we'll create a shell script that will load the driver. Let's create a folder in `/opt/` to place it into. + +```bash +sudo mkdir -p /opt/magic-mouse-fix/ +``` + +Let's create a script named `magic-mouse-2-add.sh` (to create and edit it use something like `sudo nano /opt/magic-mouse-fix/magic-mouse-2-add.sh`). This should be the contents: + +```bash +#!/bin/sh + +FILE=/tmp/magicmouse-driveload + +reload() { + if [ ! -f "$FILE" ]; then + touch $FILE + + modprobe -r hid_magicmouse + sleep 2 + modprobe hid-generic + modprobe hid_magicmouse \ + scroll_acceleration=1 \ + scroll_speed=25 \ + middle_click_3finger=1 + + sleep 2 + rm -f "$FILE" + + fi +} + +reload & +``` + +You can also adjust the scroll_speed to a value of your liking (somewhere between 0 to 63). If you wish to disable scroll acceleration or middle-clicking with 3 fingers then set those values to zero. Give the script permission to run with `sudo chmod +x /opt/magic-mouse-fix/magic-mouse-2-add.sh`. When this script is run it will unload the default Magic Mouse driver and then load the new one built earlier. + +We now need to create a `udev` rule that runs the script and loads the driver when the Mouse connects. In `/etc/udev/rules.d` directory create a `10-magicmouse.rules` file and add the following: + +``` +SUBSYSTEM=="input", \ + KERNEL=="mouse*", \ + DRIVER=="", \ + SUBSYSTEMS=="hid", \ + KERNELS=="0005:004C:0269*", \ + DRIVERS=="hid-generic|magicmouse", \ + ACTION=="add", \ + SYMLINK+="input/magicmouse-%k", \ + RUN+="/opt/magic-mouse-fix/magic-mouse-2-add.sh" +``` + +The `10-` prefix was picked arbitrarily and could be any number as it is used to determine the lexical ordering of rules in the kernel. The earlier the file is loaded guarantees that the rule will be applied before any others. + +Now we need to reload the `udev` database with: + +``` +sudo udevadm control -R +``` + +With that in place, the Magic Mouse 2 will now be properly loaded with scrolling when connected via Bluetooth. Note that isn't perfect and bugs may be around. + ## Thanks * https://github.com/ponyfleisch/hid-magictrackpad2 * https://github.com/adam-h/Linux-Magic-Trackpad-2-Driver diff --git a/build-deb.sh b/build-deb.sh new file mode 100755 index 0000000..dd3d9c2 --- /dev/null +++ b/build-deb.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e +set -x + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ETC_DIR="/etc" +PKG_DIR="/pkg-debian" +OPT_DIR="/opt/magicmouse-hid" + +DEB="magicmouse-hid_2.1.0-0.deb" + +cp -rf ${DIR}${ETC_DIR} ${DIR}${PKG_DIR} + +mkdir -p ${DIR}${PKG_DIR}${OPT_DIR}/scripts +cp -f ${DIR}/scripts/install.sh ${DIR}${PKG_DIR}${OPT_DIR}/scripts/install.sh +cp -f ${DIR}/scripts/remove.sh ${DIR}${PKG_DIR}${OPT_DIR}/scripts/remove.sh + +# Copy source to /opt +cp -rf ${DIR}/linux ${DIR}${PKG_DIR}${OPT_DIR}/linux + +# Generate MD5sums +cd ${DIR}${PKG_DIR} +find ${DIR}${PKG_DIR} -type f ! -regex '.*?debian-binary.*' ! -regex '.*?DEBIAN.*' -printf '%P ' | xargs md5sum > ${DIR}${PKG_DIR}/DEBIAN/md5sums +cd ${DIR} + +# Set correct permissions +chmod 0775 ${DIR}${PKG_DIR}/DEBIAN/postinst +chmod 0775 ${DIR}${PKG_DIR}/DEBIAN/prerm + +dpkg -b ${DIR}${PKG_DIR} ${DIR}/${DEB} + +dpkg -I ${DIR}/${DEB} \ No newline at end of file diff --git a/etc/modprobe.d/hid-magicmouse.conf b/etc/modprobe.d/hid-magicmouse.conf new file mode 100644 index 0000000..38b2edc --- /dev/null +++ b/etc/modprobe.d/hid-magicmouse.conf @@ -0,0 +1,7 @@ +options hid-magicmouse \ + scroll_acceleration=1 \ + stop_scroll_while_moving=1 \ + scroll_speed=10 \ + middle_click_3finger=1 \ + scroll_delay_pos_x=300 \ + scroll_delay_pos_y=250 \ No newline at end of file diff --git a/hid-magicmouse.code-workspace b/hid-magicmouse.code-workspace new file mode 100644 index 0000000..0700333 --- /dev/null +++ b/hid-magicmouse.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "files.associations": { + } + } +} \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..6500bb0 --- /dev/null +++ b/install.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +if [ "$EUID" -ne 0 ] + then echo "Please run as root" + exit +fi + +set -e +set -x + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +MODPROBE_DIR="/etc/modprobe.d" + + +# Copy Modprobe config file +cp -f ${DIR}${MODPROBE_DIR}/hid-magicmouse.conf ${MODPROBE_DIR}/hid-magicmouse.conf + +# Install drive through DKMS +chmod u+x ${DIR}/scripts/install.sh +${DIR}/scripts/install.sh + +# Disable eSCO mode in Bluetooth to fix disconnection problems with the mouse +echo 1 | tee /sys/module/bluetooth/parameters/disable_esco +systemctl restart bluetooth +# persist eSCO mode in Bluetooth setting +echo "options bluetooth disable_esco=1" | tee /etc/modprobe.d/bluetooth-tweaks.conf + +# Load driver +sudo modprobe -a hid_magicmouse \ No newline at end of file diff --git a/linux/drivers/hid/dkms.conf b/linux/drivers/hid/dkms.conf index a601f98..b39fbf7 100644 --- a/linux/drivers/hid/dkms.conf +++ b/linux/drivers/hid/dkms.conf @@ -3,6 +3,6 @@ CLEAN="make KERNEL_VERSION=${kernelver} clean" BUILT_MODULE_NAME[0]=hid-magicmouse DEST_MODULE_LOCATION[0]='/kernel/drivers/hid' PACKAGE_NAME=hid-magicmouse-dkms -PACKAGE_VERSION=4.18+magictrackpad2 +PACKAGE_VERSION=4.18+magicmouse2 REMAKE_INITRD=yes AUTOINSTALL=yes diff --git a/linux/drivers/hid/hid-magicmouse.c b/linux/drivers/hid/hid-magicmouse.c index 1745a64..485f148 100644 --- a/linux/drivers/hid/hid-magicmouse.c +++ b/linux/drivers/hid/hid-magicmouse.c @@ -4,6 +4,7 @@ * Copyright (c) 2010 Michael Poole * Copyright (c) 2010 Chase Douglas * Copyright (c) 2018 Rohit Pidaparthi + * Copyright (c) 2021 Ricardo Rodrigues */ /* @@ -20,7 +21,7 @@ #include #include #include -#include +#include #include "hid-ids.h" @@ -39,6 +40,10 @@ static bool emulate_scroll_wheel = true; module_param(emulate_scroll_wheel, bool, 0644); MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel"); +static bool stop_scroll_while_moving = true; +module_param(stop_scroll_while_moving, bool, 0644); +MODULE_PARM_DESC(stop_scroll_while_moving, "Stop scrolling whenever the mouse moves"); + static unsigned int scroll_speed = 32; static int param_set_scroll_speed(const char *val, const struct kernel_param *kp) { @@ -51,6 +56,30 @@ static int param_set_scroll_speed(const char *val, module_param_call(scroll_speed, param_set_scroll_speed, param_get_uint, &scroll_speed, 0644); MODULE_PARM_DESC(scroll_speed, "Scroll speed, value from 0 (slow) to 63 (fast)"); +static unsigned int scroll_delay_pos_x = 200; +static int param_set_scroll_delay_pos_x(const char *val, + const struct kernel_param *kp) { + unsigned long delay; + if (!val || kstrtoul(val, 0, &delay)) + return -EINVAL; + scroll_delay_pos_x = delay; + return 0; +} +module_param_call(scroll_delay_pos_x, param_set_scroll_delay_pos_x, param_get_uint, &scroll_delay_pos_x, 0644); +MODULE_PARM_DESC(scroll_delay_pos_x, "Scroll X position delay before start scrolling"); + +static unsigned int scroll_delay_pos_y = 200; +static int param_set_scroll_delay_pos_y(const char *val, + const struct kernel_param *kp) { + unsigned long delay; + if (!val || kstrtoul(val, 0, &delay)) + return -EINVAL; + scroll_delay_pos_y = delay; + return 0; +} +module_param_call(scroll_delay_pos_y, param_set_scroll_delay_pos_y, param_get_uint, &scroll_delay_pos_y, 0644); +MODULE_PARM_DESC(scroll_delay_pos_y, "Scroll Y position delay before start scrolling"); + static bool scroll_acceleration = false; module_param(scroll_acceleration, bool, 0644); MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events"); @@ -77,6 +106,10 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define TOUCH_STATE_START 0x30 #define TOUCH_STATE_DRAG 0x40 +/* Number of high-resolution events for each low-resolution detent. */ +#define SCROLL_HR_STEPS 10 +#define SCROLL_HR_MULT (120 / SCROLL_HR_STEPS) +#define SCROLL_HR_THRESHOLD 90 /* units */ #define SCROLL_ACCEL_DEFAULT 3 /* Touch surface information. Dimension is in hundredths of a mm, min and max @@ -121,6 +154,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie * @ntouches: Number of touches in most recent touch report. * @scroll_accel: Number of consecutive scroll motions. * @scroll_jiffies: Time of last scroll motion. + * @drag_start: Time of drag start. * @touches: Most recent data for a touch, indexed by tracking ID. * @tracking_ids: Mapping of current touch input data to @touches. */ @@ -131,15 +165,24 @@ struct magicmouse_sc { int ntouches; int scroll_accel; unsigned long scroll_jiffies; + int x; + int y; struct { short x; short y; short scroll_x; short scroll_y; + short scroll_x_hr; + short scroll_y_hr; u8 size; + bool scroll_x_active; + bool scroll_y_active; } touches[MAX_TOUCHES]; int tracking_ids[MAX_TOUCHES]; + + struct hid_device *hdev; + struct delayed_work work; }; static int magicmouse_firm_touch(struct magicmouse_sc *msc) @@ -154,7 +197,10 @@ static int magicmouse_firm_touch(struct magicmouse_sc *msc) int idx = msc->tracking_ids[ii]; if (msc->touches[idx].size < 8) { /* Ignore this touch. */ - } else if (touch >= 0) { + continue; + } + + if (touch >= 0) { touch = -1; break; } else { @@ -192,6 +238,8 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) if (emulate_3button) { int id; + id = magicmouse_firm_touch(msc); + /* If some button was pressed before, keep it held * down. Otherwise, if there's exactly one firm * touch, use that to override the mouse's guess. @@ -200,9 +248,8 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) /* The button was released. */ } else if (last_state != 0) { state = last_state; - } else if (middle_click_3finger){ + } else if (id >= 0 && middle_click_3finger){ int x; - id = magicmouse_firm_touch(msc); x = msc->touches[id].x; if (magicmouse_detect_3finger_click(msc) > 2) state = 4; @@ -210,7 +257,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) state = 1; else if (x > 0) state = 2; - } else if ((id = magicmouse_firm_touch(msc)) >= 0) { + } else if (id >= 0) { int x = msc->touches[id].x; if (x < middle_button_start) state = 1; @@ -218,7 +265,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) state = 2; else state = 4; - }/* else: we keep the mouse's guess */ + }/* else: we keep the mouse's guess */ input_report_key(msc->input, BTN_MIDDLE, state & 4); } @@ -230,7 +277,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) } static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, - u8 *tdata, int npoints) + u8 *tdata, int npoints, int mouse_loc_x, int mouse_loc_y) { struct input_dev *input = msc->input; int id, x, y, size, orientation, touch_major, touch_minor, state, down; @@ -307,39 +354,104 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, unsigned long now = jiffies; int step_x = msc->touches[id].scroll_x - x; int step_y = msc->touches[id].scroll_y - y; + int step_hr = ((64 - (int)scroll_speed) * msc->scroll_accel) / + SCROLL_HR_STEPS; + int step_x_hr = msc->touches[id].scroll_x_hr - x; + int step_y_hr = msc->touches[id].scroll_y_hr - y; + + /* Determine if the mouse has moved, if so then disable scrolling. */ + bool continue_scroll = true; + if (stop_scroll_while_moving) + { + continue_scroll = msc->x == mouse_loc_x && msc->y == mouse_loc_y; + } - /* Calculate and apply the scroll motion. */ - switch (state) { - case TOUCH_STATE_START: - msc->touches[id].scroll_x = x; - msc->touches[id].scroll_y = y; - - /* Reset acceleration after half a second. */ - if (scroll_acceleration && time_before(now, - msc->scroll_jiffies + HZ / 2)) - msc->scroll_accel = max_t(int, - msc->scroll_accel - 1, 1); - else - msc->scroll_accel = SCROLL_ACCEL_DEFAULT; - - break; - case TOUCH_STATE_DRAG: - step_x /= (64 - (int)scroll_speed) * msc->scroll_accel; - if (step_x != 0) { - msc->touches[id].scroll_x -= step_x * - (64 - scroll_speed) * msc->scroll_accel; - msc->scroll_jiffies = now; - input_report_rel(input, REL_HWHEEL, -step_x); - } - - step_y /= (64 - (int)scroll_speed) * msc->scroll_accel; - if (step_y != 0) { - msc->touches[id].scroll_y -= step_y * - (64 - scroll_speed) * msc->scroll_accel; - msc->scroll_jiffies = now; - input_report_rel(input, REL_WHEEL, step_y); + if (continue_scroll) { + /* Calculate and apply the scroll motion. */ + switch (state) { + case TOUCH_STATE_START: + msc->touches[id].scroll_x = x; + msc->touches[id].scroll_y = y; + msc->touches[id].scroll_x_hr = x; + msc->touches[id].scroll_y_hr = y; + msc->touches[id].scroll_x_active = false; + msc->touches[id].scroll_y_active = false; + + /* Reset acceleration after half a second. */ + if (scroll_acceleration && time_before(now, + msc->scroll_jiffies + HZ / 2)) + msc->scroll_accel = max_t(int, + msc->scroll_accel - 1, 1); + else + msc->scroll_accel = SCROLL_ACCEL_DEFAULT; + + break; + case TOUCH_STATE_DRAG: + /* Add a position delay since the drag start in which + * drag events are not registered. This decreases the + * sensitivity of dragging on Magic Mouse devices. + */ + if (abs(step_x) < scroll_delay_pos_x) { + step_x = 0; + } else { + step_x /= (64 - (int)scroll_speed) * msc->scroll_accel; + } + + if (abs(step_y) < scroll_delay_pos_y) { + step_y = 0; + } else { + step_y /= (64 - (int)scroll_speed) * msc->scroll_accel; + } + + if (step_x != 0) { + msc->touches[id].scroll_x -= step_x * + (64 - scroll_speed) * msc->scroll_accel; + msc->scroll_jiffies = now; + input_report_rel(input, REL_HWHEEL, -step_x); + } + + if (step_y != 0) { + msc->touches[id].scroll_y -= step_y * + (64 - scroll_speed) * msc->scroll_accel; + msc->scroll_jiffies = now; + input_report_rel(input, REL_WHEEL, step_y); + } + + if (!msc->touches[id].scroll_x_active && + abs(step_x_hr) > SCROLL_HR_THRESHOLD) { + msc->touches[id].scroll_x_active = true; + msc->touches[id].scroll_x_hr = x; + step_x_hr = 0; + } + + step_x_hr /= step_hr; + if (step_x_hr != 0 && + msc->touches[id].scroll_x_active) { + msc->touches[id].scroll_x_hr -= step_x_hr * + step_hr; + input_report_rel(input, + REL_HWHEEL_HI_RES, + -step_x_hr * SCROLL_HR_MULT); + } + + if (!msc->touches[id].scroll_y_active && + abs(step_y_hr) > SCROLL_HR_THRESHOLD) { + msc->touches[id].scroll_y_active = true; + msc->touches[id].scroll_y_hr = y; + step_y_hr = 0; + } + + step_y_hr /= step_hr; + if (step_y_hr != 0 && + msc->touches[id].scroll_y_active) { + msc->touches[id].scroll_y_hr -= step_y_hr * + step_hr; + input_report_rel(input, + REL_WHEEL_HI_RES, + step_y_hr * SCROLL_HR_MULT); + } + break; } - break; } } @@ -393,7 +505,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, } msc->ntouches = 0; for (ii = 0; ii < npoints; ii++) - magicmouse_emit_touch(msc, ii, data + ii * 9 + 4, npoints); + magicmouse_emit_touch(msc, ii, data + ii * 9 + 4, npoints, 0, 0); clicks = data[1]; break; @@ -409,7 +521,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, } msc->ntouches = 0; for (ii = 0; ii < npoints; ii++) - magicmouse_emit_touch(msc, ii, data + ii * 9 + 12, npoints); + magicmouse_emit_touch(msc, ii, data + ii * 9 + 12, npoints, 0, 0); clicks = data[1]; break; @@ -424,8 +536,6 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 0; } msc->ntouches = 0; - for (ii = 0; ii < npoints; ii++) - magicmouse_emit_touch(msc, ii, data + ii * 8 + 6, npoints); /* When emulating three-button mode, it is important * to have the current touch information before @@ -433,8 +543,10 @@ static int magicmouse_raw_event(struct hid_device *hdev, */ x = (int)(((data[3] & 0x0c) << 28) | (data[1] << 22)) >> 22; y = (int)(((data[3] & 0x30) << 26) | (data[2] << 22)) >> 22; - clicks = data[3]; + for (ii = 0; ii < npoints; ii++) + magicmouse_emit_touch(msc, ii, data + ii * 8 + 6, npoints, x, y); + clicks = data[3]; /* The following bits provide a device specific timestamp. They * are unused here. * @@ -473,6 +585,14 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 0; } msc->ntouches = 0; + + /* When emulating three-button mode, it is important + * to have the current touch information before + * generating a click event. + */ + x = (int)((data[3] << 24) | (data[2] << 16)) >> 16; + y = (int)((data[5] << 24) | (data[4] << 16)) >> 16; + // print the values of the first 14 bytes of data and number of points and size. // printk("The contents of npoints are: %i\n", npoints); // printk("Size is: %i\n", size); @@ -482,14 +602,8 @@ static int magicmouse_raw_event(struct hid_device *hdev, // printk("data %i is: %i\n", jj, d); // } for (ii = 0; ii < npoints; ii++) - magicmouse_emit_touch(msc, ii, data + ii * 8 + 14, npoints); - - /* When emulating three-button mode, it is important - * to have the current touch information before - * generating a click event. - */ - x = (int)((data[3] << 24) | (data[2] << 16)) >> 16; - y = (int)((data[5] << 24) | (data[4] << 16)) >> 16; + magicmouse_emit_touch(msc, ii, data + ii * 8 + 14, npoints, x, y); + clicks = data[1]; break; case DOUBLE_REPORT_ID: @@ -506,6 +620,8 @@ static int magicmouse_raw_event(struct hid_device *hdev, if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE || input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { + msc->x = x; + msc->y = y; magicmouse_emit_buttons(msc, clicks & 3); input_report_rel(input, REL_X, x); input_report_rel(input, REL_Y, y); @@ -522,6 +638,21 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 1; } +static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + if (msc->input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 && + field->report->id == MOUSE2_REPORT_ID) { + // magic_mouse_raw_event has done all the work. Skip hidinput. + // + // Specifically, hidinput may modify BTN_LEFT and BTN_RIGHT, + // breaking emulate_3button. + return 1; + } + return 0; +} + static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) { int error; @@ -542,6 +673,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd if (emulate_scroll_wheel) { __set_bit(REL_WHEEL, input->relbit); __set_bit(REL_HWHEEL, input->relbit); + __set_bit(REL_WHEEL_HI_RES, input->relbit); + __set_bit(REL_HWHEEL_HI_RES, input->relbit); } } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD){ /* input->keybit is initialized with incorrect button info @@ -695,26 +828,66 @@ static int magicmouse_input_configured(struct hid_device *hdev, } + +static int magicmouse_enable_multitouch(struct hid_device *hdev) +{ + const u8 *feature; + const u8 feature_mt[] = { 0xD7, 0x01 }; + const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 }; + const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 }; + const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 }; + u8 *buf; + int ret; + int feature_size; + + if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { + if (hdev->vendor == BT_VENDOR_ID_APPLE) { + feature_size = sizeof(feature_mt_trackpad2_bt); + feature = feature_mt_trackpad2_bt; + } else { /* USB_VENDOR_ID_APPLE */ + feature_size = sizeof(feature_mt_trackpad2_usb); + feature = feature_mt_trackpad2_usb; + } + } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { + feature_size = sizeof(feature_mt_mouse2); + feature = feature_mt_mouse2; + } else { + feature_size = sizeof(feature_mt); + feature = feature_mt; + } + + buf = kmemdup(feature, feature_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + kfree(buf); + return ret; +} + +static void magicmouse_enable_mt_work(struct work_struct *work) +{ + struct magicmouse_sc *msc = + container_of(work, struct magicmouse_sc, work.work); + int ret; + + ret = magicmouse_enable_multitouch(msc->hdev); + if (ret < 0) + hid_err(msc->hdev, "unable to request touch data (%d)\n", ret); +} + static int magicmouse_probe(struct hid_device *hdev, const struct hid_device_id *id) { - __u8 feature_mt_mouse2_bt[] = { 0xF1, 0x02, 0x01 }; - __u8 feature_mt[] = { 0xD7, 0x01 }; - __u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 }; - __u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 }; - __u8 *feature; struct magicmouse_sc *msc; struct hid_report *report; int ret; - int feature_size; - struct usb_interface *intf; if (id->vendor == USB_VENDOR_ID_APPLE && - id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) { - intf = to_usb_interface(hdev->dev.parent); - if (intf->cur_altsetting->desc.bInterfaceNumber != 1) - return 0; - } + id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 && + hdev->type != HID_TYPE_USBMOUSE) + return -ENODEV; msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL); if (msc == NULL) { @@ -723,6 +896,8 @@ static int magicmouse_probe(struct hid_device *hdev, } msc->scroll_accel = SCROLL_ACCEL_DEFAULT; + msc->hdev = hdev; + INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work); msc->quirks = id->driver_data; hid_set_drvdata(hdev, msc); @@ -781,36 +956,14 @@ static int magicmouse_probe(struct hid_device *hdev, * but there seems to be no other way of switching the mode. * Thus the super-ugly hacky success check below. */ - if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE || - id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD) { - feature_size = sizeof(feature_mt); - feature = kmemdup(feature_mt, feature_size, GFP_KERNEL); - } - else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2){ - feature_size = sizeof(feature_mt_mouse2_bt); - feature = kmemdup(feature_mt_mouse2_bt, feature_size, GFP_KERNEL); - } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 */ - if (id->vendor == BT_VENDOR_ID_APPLE) { - feature_size = sizeof(feature_mt_trackpad2_bt); - feature = kmemdup(feature_mt_trackpad2_bt, feature_size, - GFP_KERNEL); - } else { /* USB_VENDOR_ID_APPLE */ - feature_size = sizeof(feature_mt_trackpad2_usb); - feature = kmemdup(feature_mt_trackpad2_usb, feature_size, - GFP_KERNEL); - } - } - if (!feature) { - ret = -ENOMEM; - goto err_stop_hw; - } - ret = hid_hw_raw_request(hdev, feature[0], feature, feature_size, - HID_FEATURE_REPORT, HID_REQ_SET_REPORT); - kfree(feature); - if (ret != -EIO && ret != feature_size) { + ret = magicmouse_enable_multitouch(hdev); + if (ret != -EIO && ret < 0) { hid_err(hdev, "unable to request touch data (%d)\n", ret); goto err_stop_hw; } + if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) { + schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + } return 0; err_stop_hw: @@ -818,6 +971,14 @@ static int magicmouse_probe(struct hid_device *hdev, return ret; } +static void magicmouse_remove(struct hid_device *hdev) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + if (msc) + cancel_delayed_work_sync(&msc->work); + hid_hw_stop(hdev); +} + static const struct hid_device_id magic_mice[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 }, @@ -837,10 +998,20 @@ static struct hid_driver magicmouse_driver = { .name = "magicmouse", .id_table = magic_mice, .probe = magicmouse_probe, + .remove = magicmouse_remove, .raw_event = magicmouse_raw_event, + .event = magicmouse_event, .input_mapping = magicmouse_input_mapping, .input_configured = magicmouse_input_configured, }; module_hid_driver(magicmouse_driver); +MODULE_AUTHOR("Ricardo Rodrigues"); +MODULE_AUTHOR("John Chen"); +MODULE_AUTHOR("Rohit Pidaparthi"); +MODULE_AUTHOR("Chase Douglas"); +MODULE_AUTHOR("Michael Poole"); + +MODULE_DESCRIPTION("Magic Mouse 2 driver for Linux"); + MODULE_LICENSE("GPL"); diff --git a/pkg-debian/DEBIAN/conffiles b/pkg-debian/DEBIAN/conffiles new file mode 100644 index 0000000..c8c51ba --- /dev/null +++ b/pkg-debian/DEBIAN/conffiles @@ -0,0 +1 @@ +/etc/modprobe.d/hid-magicmouse.conf diff --git a/pkg-debian/DEBIAN/control b/pkg-debian/DEBIAN/control new file mode 100644 index 0000000..8f27992 --- /dev/null +++ b/pkg-debian/DEBIAN/control @@ -0,0 +1,13 @@ +Package: magicmouse-hid +Version: 2.1.0-0 +Maintainer: Ricardo Rodrigues +Description: Magic Mouse 2 driver for Linux + This driver makes use of DKMS to load the driver in the kernel, while + making use of udev rules to load the driver when a mouse is connected. +Homepage: https://github.com/RicardoEPRodrigues/Linux-Magic-Trackpad-2-Driver +Architecture: all +Depends: dkms, bash +Section: utils +Priority: optional +Essential: no +Installed-Size: 354 diff --git a/pkg-debian/DEBIAN/postinst b/pkg-debian/DEBIAN/postinst new file mode 100755 index 0000000..7fd1707 --- /dev/null +++ b/pkg-debian/DEBIAN/postinst @@ -0,0 +1,17 @@ +#!/bin/bash + +DIR="/opt/magicmouse-hid" +cd ${DIR} + +# Install drive through DKMS +chmod u+x ${DIR}/scripts/install.sh +${DIR}/scripts/install.sh + +# Load driver +sudo modprobe -a hid_magicmouse + +# Disable eSCO mode in Bluetooth to fix disconnection problems with the mouse +echo 1 | tee /sys/module/bluetooth/parameters/disable_esco +systemctl restart bluetooth +# persist eSCO mode in Bluetooth setting +echo "options bluetooth disable_esco=1" | tee /etc/modprobe.d/bluetooth-tweaks.conf \ No newline at end of file diff --git a/pkg-debian/DEBIAN/prerm b/pkg-debian/DEBIAN/prerm new file mode 100755 index 0000000..4b34b82 --- /dev/null +++ b/pkg-debian/DEBIAN/prerm @@ -0,0 +1,14 @@ +#!/bin/bash + +DIR="/opt/magicmouse-hid" +cd ${DIR} + +# Remove drive through DKMS +chmod u+x ${DIR}/scripts/remove.sh +${DIR}/scripts/remove.sh + +# Remove loaded driver +modprobe -r hid_magicmouse + +# Restart Bluetooth +systemctl restart bluetooth diff --git a/remove.sh b/remove.sh new file mode 100755 index 0000000..97a3b6f --- /dev/null +++ b/remove.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [ "$EUID" -ne 0 ] + then echo "Please run as root" + exit +fi + +set -e +set -x + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +MODPROBE_DIR="/etc/modprobe.d" + +# Remove drive through DKMS +chmod u+x ${DIR}/scripts/remove.sh +${DIR}/scripts/remove.sh + +# Remove loaded driver +modprobe -r hid_magicmouse + +# Remove Modprobe configuration file +rm -f ${MODPROBE_DIR}/hid-magicmouse.conf + +# Restart Bluetooth +systemctl restart bluetooth diff --git a/scripts/install.sh b/scripts/install.sh index 678e6cd..1b6b4a6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -4,7 +4,7 @@ set -e set -x dkms_name="hid-magicmouse-dkms" -dkms_version="4.18+magictrackpad2" +dkms_version="4.18+magicmouse2" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # add diff --git a/scripts/remove.sh b/scripts/remove.sh index 046adf8..b33e4b9 100755 --- a/scripts/remove.sh +++ b/scripts/remove.sh @@ -3,7 +3,7 @@ set -e dkms_name="hid-magicmouse-dkms" -dkms_version="4.18+magictrackpad2" +dkms_version="4.18+magicmouse2" if dkms status -m $dkms_name -v $dkms_version | egrep '(added|built|installed)' >/dev/null ; then # if dkms bindings exist, remove them diff --git a/usr/share/X11/xorg.conf.d/90-magictrackpad.conf b/usr/share/X11/xorg.conf.d/90-magictrackpad.conf deleted file mode 100644 index afd1b22..0000000 --- a/usr/share/X11/xorg.conf.d/90-magictrackpad.conf +++ /dev/null @@ -1,35 +0,0 @@ -Section "InputClass" - Identifier "Touchpads" - Driver "mtrack" - MatchProduct "Trackpad" - MatchDevicePath "/dev/input/event*" - # options... - Option "Sensitivity" "0.55" - Option "FingerHigh" "10" - Option "FingerLow" "10" - Option "TapButton1" "1" - Option "TapButton2" "3" - Option "TapButton3" "2" - Option "TapButton4" "0" - Option "ButtonIntegrated" "true" - Option "ClickTime" "25" - Option "ScrollDistance" "75" - Option "ScrollSmooth" "1" - Option "TapDragEnable" "false" - -# natural scroll - Option "ScrollDownButton" "4" - Option "ScrollUpButton" "5" - Option "ScrollLeftButton" "7" - Option "ScrollRightButton" "6" - - -# three finger drag: - Option "SwipeDistance" "1" - Option "SwipeLeftButton" "1" - Option "SwipeRightButton" "1" - Option "SwipeUpButton" "1" - Option "SwipeDownButton" "1" - Option "SwipeClickTime" "0" - Option "SwipeSensitivity" "1000" -EndSection