├── LICENSE ├── README.md ├── old ├── LICENSE ├── README.md ├── detect_accelerometers ├── detect_tablet_mode ├── is_not_moving ├── watch_tablet └── watch_tablet.yml ├── watch_tablet └── watch_tablet.yml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ales Huzik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tablet mode detection and setup scripts for linux 2 | 3 | ## What it does 4 | 5 | It uses `libinput debug-events` to detect switches to normal and tablet mode, 6 | and executes commands for switching into that mode, which are specified in 7 | a config file. Generally you would put there commands to disable/enable a 8 | keyboard/touchpad/trackpoint, show/hide an on-screen keyboard, toggle some desktop 9 | environment panels, and the like. 10 | 11 | ## Supported devices 12 | 13 | All devices that have a tablet mode switch supported by libinput. As far as I understand 14 | this is a standard mechanism for this functionality nowadays. Tested devices: 15 | 16 | - ThinkPad X1 Yoga Gen2 (it was developed for it) 17 | - Thinkpad X1 Yoga Gen3 18 | - Thinkpad X1 Yoga Gen4 19 | - Thinkpad X1 Yoga Gen6 20 | - Thinkpad Yoga 11e Gen6 21 | - Thinkpad X370 Yoga 22 | - Lenovo IdeaPad Flex 5i Gen 7 23 | - Samsung Galaxy Book Flex2 5G 24 | - Dell XPS 13 9310 2-in-1 25 | - ASUS ZenBook 15 Flip OLED UP6502ZD 26 | - ASUS TP200SA 27 | 28 | If it works on your device, please tell me and I'll add it to the list (or just submit a pull request yourself). 29 | To check if your device is supported, run `stdbuf -oL libinput debug-events|grep switch`, flip your laptop between 30 | normal and tablet mode, and see if it printed anything. If you don't see any switch events, your device will 31 | not work with these scripts. 32 | 33 | ## Installation 34 | 35 | 1. Add your user to the `input` group (`sudo gpasswd --add username input`) and relogin to apply group membership. 36 | 2. Install ruby and stdbuf (most likely you already have them preinstalled) 37 | 3. Clone this repo somewhere, and optionally symlink `watch_tablet` into any directory in your $PATH 38 | 4. Copy a config file into `~/.config/watch_tablet.yml` 39 | 5. Adjust the config (see below) 40 | 6. Test it by running `watch_tablet` in a terminal and flipping your laptop to tablet and back. You should see commands from the config being executed. Press Ctrl+C to terminate it. 41 | 7. After you confirmed that everything works, add `watch_tablet &` to your `~/.xinitrc` 42 | 8. Restart your desktop session and enjoy 43 | 44 | ### Arch Linux 45 | 46 | If you have an Arch-based distribution, you can install it using [this AUR package](https://aur.archlinux.org/packages/detect-tablet-mode-git/) 47 | 48 | 49 | ## Configuration 50 | 51 | `input_device` is a path to the device that provides the tablet mode switch. To find it you 52 | may run `stdbuf -oL libinput debug-events|grep switch` and notice something like `event4` in 53 | the leftmost column. That would correspond to /dev/input/event4. Device numbers may be unstable 54 | across reboots, so you may consider doing `ls -lh /dev/input/by-path` and finding a symlink to 55 | that device. For X1 Yoga Gen2 it's `/dev/input/by-path/platform-thinkpad_acpi-event`. 56 | 57 | `modes.laptop`, `modes.tablet` - this contain commands that will be executed when mode changes. 58 | Most likely this will contain `xinput enable` and `xinput disable` commands to enable/disable 59 | kb/touchpad/trackpoint (just run `xinput` to look them up). You may use any other commands 60 | to adjust your desktop environment (e.g. hide or show additional panels, increase button size, 61 | hide/show onscreen keyboard etc.) 62 | 63 | Example: 64 | 65 | ```yaml 66 | input_device: /dev/input/by-path/platform-thinkpad_acpi-event 67 | modes: 68 | laptop: 69 | # - xinput enable "Wacom Pen and multitouch sensor Finger" 70 | - xinput enable "AT Translated Set 2 keyboard" 71 | - xinput enable "SynPS/2 Synaptics TouchPad" 72 | - xinput enable "TPPS/2 IBM TrackPoint" 73 | tablet: 74 | # - xinput disable "Wacom Pen and multitouch sensor Finger" 75 | - xinput disable "AT Translated Set 2 keyboard" 76 | - xinput disable "SynPS/2 Synaptics TouchPad" 77 | - xinput disable "TPPS/2 IBM TrackPoint" 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /old/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ales Huzik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /old/README.md: -------------------------------------------------------------------------------- 1 | # Tablet mode detection and setup scripts for linux 2 | 3 | ## What this is 4 | 5 | It's an older version of tablet mode detection scripts. Unlike the main version, it uses 6 | a pair of accelerometers instead of mode switch events. Mode switch events are the right 7 | way to handle tablet mode on linux, but it might be that some laptops don't support it (yet?). 8 | This set of scripts is more complicated, less reliable, and requires the device to be in 9 | laptop mode when the script is started. On the other hand, due its low-level nature, it 10 | allows you to customize the angle between the screen and a keyboard that is treated as a 11 | tablet mode. 12 | 13 | ## What it does 14 | 15 | It uses two accelerometers to detect an angle between the screen and the keyboard, 16 | decides if that angle corresponds to laptop or tablet mode, and if mode have changed, 17 | it executes commands for switching into that mode, which are specified in 18 | a config file. Generally you would put there commands to disable/enable a 19 | keyboard/touchpad/trackpoint, show/hide an on-screen keyboard, toggle some desktop 20 | environment panels, and the like. 21 | 22 | ## Supported devices 23 | 24 | Supposedly any 2-in-1s that have 2 accelerometers. Tested devices: 25 | 26 | - ThinkPad X1 Yoga Gen2 (it was developed for it, but it doesn't work anymore, because its accelerometers aren't exposed anymore) 27 | 28 | If it works on your device, please tell me and I'll add it to the list (or just submit a pull request yourself). 29 | 30 | ## Installation 31 | 32 | 1. Install ruby (most likely you already have it preinstalled) 33 | 2. Clone it somewhere, and optionally symlink `watch_tablet` into any directory in your $PATH 34 | 3. Copy a config file into `~/.config/watch_tablet.yml` 35 | 4. Adjust the config (see below) 36 | 5. Add `watch_tablet &` to your `~/.xinitrc` 37 | 6. Restart your desktop session and enjoy 38 | 39 | ## Configuration 40 | 41 | `modes.laptop`, `modes.tablet` - this contain commands that will be executed when mode changes. 42 | Most likely this will contain `xinput enable` and `xinput disable` commands to enable/disable 43 | kb/touchpad/trackpoint (just run `xinput` to look them up). You may use any other commands 44 | to adjust your desktop environment (e.g. hide or show additional panels, increase button size, 45 | hide/show onscreen keyboard etc.) 46 | 47 | Example: 48 | 49 | ```yaml 50 | modes: 51 | laptop: 52 | - xinput enable 9 # touch 53 | - xinput enable 12 # keyboard 54 | - xinput enable 13 # touchpad 55 | - xinput enable 14 # trackpoint 56 | tablet: 57 | - xinput disable 12 # keyboard 58 | - xinput disable 13 # touchpad 59 | - xinput disable 14 # trackpoint 60 | ``` 61 | 62 | ## Bugs 63 | 64 | Because IIO device ids are not stable across reboots, we can't put them in a config, meaning we have 65 | to detect them. Current autodetection logic is very simplistic and just grabs first 2 accelerometers, 66 | and arranges them assuming that the laptop is currently in a laptop mode. For proper autodetection 67 | we need PLD (Physical Location of Device) information to be available in the userspace, but at this 68 | point patches for that are not in a mainline kernel (https://patchwork.kernel.org/patch/4464341/). 69 | -------------------------------------------------------------------------------- /old/detect_accelerometers: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'yaml' 3 | 4 | scripts_dir = File.dirname File.realpath $0 5 | DETECT_TABLET_MODE_SCRIPT_PATH="#{scripts_dir}/detect_tablet_mode" 6 | 7 | def accel_ids 8 | $accel_ids.join(' ') 9 | end 10 | 11 | def get_mode 12 | `#{DETECT_TABLET_MODE_SCRIPT_PATH} #{accel_ids}`.chomp 13 | end 14 | 15 | def detect_accels! 16 | prefix='/sys/bus/iio/devices/iio:device' 17 | $accel_ids = Dir[prefix+'*'].find_all{|x| File.read(x+"/name").chomp == 'accel_3d' }.map{|x| x.sub(prefix,'')} 18 | if $accel_ids.size == 2 19 | if get_mode == 'tablet' 20 | $accel_ids.reverse! 21 | end 22 | end 23 | puts accel_ids 24 | end 25 | 26 | detect_accels! 27 | -------------------------------------------------------------------------------- /old/detect_tablet_mode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | trap('INT'){} 4 | 5 | def magnitude(v) 6 | Math.sqrt v.map{|x| x*x}.reduce(:+) 7 | end 8 | 9 | def vector_product(v1,v2) 10 | v1.zip(v2).map{|i1,i2| i1*i2}.reduce(:+) 11 | end 12 | 13 | def vector_angle(v1, v2) 14 | Math.acos(vector_product(v1,v2) / (magnitude(v1)*magnitude(v2))) 15 | end 16 | 17 | def scale_vector(v, c) 18 | v.map{|i| i*c} 19 | end 20 | 21 | def sign((y1,z1),(y2,z2)) 22 | val = z1*y2 - y1*z2 23 | if val == 0 then 1 else val / val.abs end 24 | end 25 | 26 | def read_in(dev, in_name) 27 | File.read("#{dev}/in_#{in_name}") 28 | end 29 | 30 | def read_float_in(dev, property_name, in_name) 31 | read_in(dev, "#{property_name}_#{in_name}").to_f 32 | end 33 | 34 | def device(device_id) 35 | "/sys/bus/iio/devices/iio:device#{device_id}" 36 | end 37 | 38 | def read_3d_sensor(device_id, property_name) 39 | dev = device device_id 40 | offset = read_float_in(dev, property_name, "offset") 41 | scale = read_float_in(dev, property_name, "scale") 42 | %w(x y z).map{|axis| (read_float_in(dev, property_name, "#{axis}_raw") + offset) * scale} 43 | end 44 | 45 | def read_angle(display_accel_id, keyboard_accel_id) 46 | accel_3d_display = read_3d_sensor(display_accel_id, "accel") 47 | accel_3d_keyboard = read_3d_sensor(keyboard_accel_id, "accel") 48 | accel_2d_display = accel_3d_display[1..2] 49 | accel_2d_keyboard = accel_3d_keyboard[1..2] 50 | 51 | return sign(accel_2d_keyboard, accel_2d_display) * 52 | vector_angle(accel_2d_keyboard, accel_2d_display) 53 | end 54 | 55 | def read_mode(display_accel_id, keyboard_accel_id) 56 | angle = read_angle(display_accel_id, keyboard_accel_id) 57 | if angle.abs > 2.8 or angle < -0.5 58 | :tablet 59 | else 60 | :laptop 61 | end 62 | end 63 | 64 | if ARGV.count != 2 65 | puts "Usage: #{File.basename($0)} " 66 | puts "Example: #{File.basename($0)} 1 6" 67 | else 68 | puts read_mode(ARGV[0],ARGV[1]) 69 | end 70 | -------------------------------------------------------------------------------- /old/is_not_moving: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | trap('INT'){} 4 | 5 | def magnitude(v) 6 | Math.sqrt v.map{|x| x*x}.reduce(:+) 7 | end 8 | 9 | def read_in(dev, in_name) 10 | File.read("#{dev}/in_#{in_name}") 11 | end 12 | 13 | def read_float_in(dev, property_name, in_name) 14 | read_in(dev, "#{property_name}_#{in_name}").to_f 15 | end 16 | 17 | def first_device_by_name(name) 18 | Dir["/sys/bus/iio/devices/iio:device*"].find do |x| 19 | File.read(x+'/name').chomp == name 20 | end 21 | end 22 | 23 | def read_3d_sensor(dev, property_name) 24 | offset = read_float_in(dev, property_name, "offset") 25 | scale = read_float_in(dev, property_name, "scale") 26 | %w(x y z).map{|axis| (read_float_in(dev, property_name, "#{axis}_raw") + offset) * scale} 27 | end 28 | 29 | exit 0 unless gyro = first_device_by_name('gyro_3d') 30 | m = magnitude read_3d_sensor(gyro, 'anglvel') 31 | exit (m < 0.5 ? 0 : 1) 32 | -------------------------------------------------------------------------------- /old/watch_tablet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'yaml' 3 | 4 | def script(name) 5 | scripts_dir = File.dirname File.realpath $0 6 | "#{scripts_dir}/#{name}" 7 | end 8 | 9 | def load_config! 10 | @config = YAML.load(File.read("#{ENV['HOME']}/.config/watch_tablet.yml")) 11 | end 12 | 13 | def config 14 | load_config! unless @config 15 | @config 16 | end 17 | 18 | def detect_accels! 19 | $accels = `#{script('detect_accelerometers')}`.chomp.split(' ') 20 | case 21 | when $accels.size < 2 22 | puts "This set of scripts requires 2 accelerometers to detect angle between 23 | a keyboard and a display. Your system has #{$accels.size}, sorry." 24 | exit 1 25 | when $accels.size > 2 26 | puts "Your system has #{$accels.size} accelerometers. This set of scripts requires 27 | two (one in a keyboard and one in a display). For it to work you need to 28 | modify detect_accelerometers script to find correct accelerometers." 29 | exit 1 30 | end 31 | return $accels 32 | end 33 | 34 | def get_mode 35 | `#{script('detect_tablet_mode')} #{$accels[0]} #{$accels[1]}`.chomp 36 | end 37 | 38 | def activate_mode(mode) 39 | puts "Switching to #{mode} mode" 40 | if cmds = config['modes'][mode] 41 | cmds.each{|cmd| system cmd } 42 | end 43 | end 44 | 45 | def in_motion? 46 | ! system script 'is_not_moving' 47 | end 48 | 49 | def run_watcher 50 | detect_accels! 51 | old_mode = nil 52 | loop do 53 | new_mode = get_mode 54 | if old_mode != new_mode 55 | if in_motion? 56 | puts "not changing mode until motion is over" 57 | else 58 | activate_mode(new_mode) 59 | old_mode = new_mode 60 | end 61 | end 62 | sleep 0.5 63 | end 64 | rescue Interrupt 65 | end 66 | 67 | run_watcher 68 | -------------------------------------------------------------------------------- /old/watch_tablet.yml: -------------------------------------------------------------------------------- 1 | modes: 2 | laptop: 3 | # - xinput enable "Wacom Pen and multitouch sensor Finger" 4 | - xinput enable "AT Translated Set 2 keyboard" 5 | - xinput enable "SynPS/2 Synaptics TouchPad" 6 | - xinput enable "TPPS/2 IBM TrackPoint" 7 | tablet: 8 | # - xinput disable "Wacom Pen and multitouch sensor Finger" 9 | - xinput disable "AT Translated Set 2 keyboard" 10 | - xinput disable "SynPS/2 Synaptics TouchPad" 11 | - xinput disable "TPPS/2 IBM TrackPoint" 12 | -------------------------------------------------------------------------------- /watch_tablet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'yaml' 3 | 4 | def die(msg); puts msg; exit 1; end 5 | 6 | def load_config! 7 | @config = YAML.load(File.read("#{ENV['HOME']}/.config/watch_tablet.yml")) 8 | unless @config['input_device'] 9 | die "Please specify input_device in the config file" 10 | end 11 | end 12 | 13 | def config 14 | load_config! unless @config 15 | @config 16 | end 17 | 18 | def activate_mode(mode) 19 | puts "Switching to #{mode} mode" 20 | if cmds = config['modes'][mode] 21 | cmds.each{|cmd| system cmd } 22 | end 23 | end 24 | 25 | def run_watcher 26 | in_tablet_mode = 0 27 | cmd = "stdbuf -oL -eL libinput debug-events --device #{config['input_device']}" 28 | io = IO.popen(cmd,"r") 29 | while s=io.gets 30 | if m=s.match(/switch tablet-mode state (\d+)/) 31 | case d=m[1].to_i 32 | when 0 then activate_mode "laptop" 33 | when 1 then activate_mode "tablet" 34 | end 35 | else 36 | if m=s.match(/pressed/) 37 | case in_tablet_mode 38 | when 1 39 | activate_mode "laptop" 40 | in_tablet_mode = 0 41 | when 0 42 | activate_mode "tablet" 43 | in_tablet_mode = 1 44 | end 45 | end 46 | end 47 | end 48 | rescue Interrupt 49 | io.close 50 | end 51 | 52 | 53 | run_watcher 54 | -------------------------------------------------------------------------------- /watch_tablet.yml: -------------------------------------------------------------------------------- 1 | input_device: /dev/input/by-path/platform-thinkpad_acpi-event 2 | # input_device: /dev/input/by-path/pci-0000:00:1f.0-platform-VPC2004:00-event # Lenovo IdeaPad Flex 5i Gen 7 3 | # input_device: /dev/input/by-path/pci-0000:00:1f.0-platform-INT33D6:00-event # Dell XPS 9365 4 | # input_device: /dev/input/by-path/platform-INTC1051:00-event # Samsung Galaxy Book Flex2 5G 5 | 6 | modes: 7 | laptop: 8 | # - xinput enable "Wacom Pen and multitouch sensor Finger" 9 | - xinput enable "AT Translated Set 2 keyboard" 10 | # - xinput enable "ELAN0B00:00 04F3:3136 Touchpad" # Samsung Galaxy Book Flex2 5G 11 | - xinput enable "SynPS/2 Synaptics TouchPad" 12 | - xinput enable "TPPS/2 IBM TrackPoint" 13 | # - xinput enable "Elan Touchpad" 14 | tablet: 15 | # - xinput disable "Wacom Pen and multitouch sensor Finger" 16 | - xinput disable "AT Translated Set 2 keyboard" 17 | # - xinput disable "ELAN0B00:00 04F3:3136 Touchpad" # Samsung Galaxy Book Flex2 5G 18 | - xinput disable "SynPS/2 Synaptics TouchPad" 19 | - xinput disable "TPPS/2 IBM TrackPoint" 20 | # - xinput disable "Elan Touchpad" 21 | --------------------------------------------------------------------------------