Sway and Arch with Yoga

Last week I received a small bonus from work and immediately put it to the only reasonable purpose I could conceive — buying a silly device and installing Linux on it.

After looking around for a little bit, I settled on the Lenovo Yoga 7i. I’ve always shunned these 2-in-1 devices since they’re usually good at neither of their intended functions. I have some use cases for a tablet though, and I wanted to replace the aged and broken iPad I’ve had kicking about for years as part of my migration away from Apple. Plus, unlike seemingly the vast majority of similar devices and laptops out there, this one came with something better than a 1080p display, sporting a 14-inch 2240x1400 display.

When it arrived, I installed Windows 11 out of curiosity, to see what it’s like after years of being apart from it. After trawling through reddit comments to figure out how to install it without creating a Microsoft account, and then declining several hundred thousand prompts to use my personal data, I was in the same old environment I hated before. I played with the touchscreen a bit, played some videos, made sure everything was working, then set to work on installing Linux.

Arch Linux

Installation with Arch was by the book with nothing extra needed — as is expected nowadays.

For the first time in a long time, I entered my environment with sound not working. PulseAudio appeared to be playing sound fine, but the speakers produced no sound. Some swift searching later and I found the solution — some extra firmware packages were needed for the onboard audio device. One quick reboot and the speakers were unmuted.

With its default configuration, systemd sets the power button to gracefully shut the machine down when pressed. I found that when rotating the device I frequently hit the button accidentally and caused an infuriating delay waiting for it to shutdown and start up again.

Changing that behaviour was a matter of editing /etc/systemd/logind.conf per its documentation:

# HandlePowerKey can be one of "ignore", "poweroff", "reboot", "halt", "kexec",
# "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate", "lock", and "factory-reset"
# Can also use HandlePowerKeyLongPress if desired

# Default is "poweroff"
#HandlePowerKey=poweroff
# Changing this to "suspend" instead
HandlePowerKey=suspend

# We also want to suspend on lid close
HandleLidSwitch=suspend
# Even when charging
HandleLidSwitchExternalPower=suspend
# But not when docked
HandleLidSwitchDocked=ignore

With that, the only remaining thing is a login manager. I generally opt for gdm, mostly out of laziness, but it works well with Wayland and Sway and brings along a ton of the dependencies needed for Gnome and GTK apps anyway. It supports the Yoga perfectly with zero config, even bringing up a touchscreen keyboard when tapping on the login fields.

Sway

I love Sway. Tiling window management seems odd to those not used to it, but when you are used to it, it’s hard to live without it. Windows are positioned predictably and the whole environment can be navigated very rapidly by keyboard only, while every window remains visible at all times (unless shunted off to a different workspace).

Sway implements that along with being Wayland native, having a very simple i3-compatible config format, modern methods for querying the window manager, and the ability to reload configs without restarting. It’s very lightweight though, requiring you bring your own apps for things like launching applications, displaying desktop panels, and even displaying wallpaper.

That’s something I love about it — you build your own environment with it by choosing what apps to use for different things — but that’s something that probably makes it an odd choice for a tablet computer. Most folks recommend Gnome for tablet computing on Linux, as it provides a more coherent out-of-the-box environment supporting every way you might want to use your tablet.

Sway offers some advantages though, perhaps counter-intuitively. The tiling interface on its own might offer a slight advantage, ensuring apps are always visible when spawned, and can’t get lost behind other windows that you may have to awkwardly have to move around to discover.

On top of that though, its alternative stacking and tabbed interfaces provide a surprisingly good tablet UX, in which you’re likely to want your apps to be full-screen when working while retaining a rapid way to switch between them. I might even posit that it provides a better UX for multitasking in a tablet environment than the swipe-and-hold gesture on Android and the pretty awful Stage Manager in iOS.

Sway's Tabbed Interface

Sway's tabbed interface fits well with a touchscreen tablet interface, where apps appear close to full-screen and can be switched between by tapping on Sway's tab bar.

Sway works with the default config without issue. I replaced its config with my standard config I use on several other machines, installed my preferred terminal (kitty), app launcher (sway-launcher-desktop), menu bar (waybar), screenshot tools (grim, slurp, and swappy) and I was good to go.

I began by getting screen rotation working. This one was trivial, just a matter of installing iio-sensor-proxy and the brilliant rot8. rot8 can be run directly from the terminal and worked flawlessly out of the box. In either tablet or laptop mode, the display rotates correctly.

Now for my first problem. When flipping the Yoga over into tablet mode, the touchscreen doesn’t remap itself, so touch inputs are effectively reversed. The solution here is provided by Sway with these two lines in /etc/sway/config:

bindswitch tablet:on exec '...'
bindswitch tablet:off exec '...'

These lines trigger actions when flipping to tablet mode and back again. It triggers these actions based on the device’s hardware switch, which can be seen by running swaymsg -t get_inputs, which lists all of the attached input devices Sway can see via libinput.

Input device: Lenovo Yoga Tablet Mode Control switch
Type: Switch
Identifier: 0:0:Lenovo_Yoga_Tablet_Mode_Control_switch
Product ID: 0
Vendor ID: 0
Libinput Send Events: enabled

When this hardware tablet mode switch gets called, it generates an event in libinput (the module Sway uses for all input devices) that Sway then triggers those bindswitch lines on. That’s pretty neat!

The next step is to remap inputs when flipping to tablet mode. First, I grabbed the Identifier field for the Yoga’s touch input devices — one for touching with fingers, one for touching with the supplied pen.

Input device: Wacom HID 52D4 Finger
Type: Touch
Identifier: 1386:21204:Wacom_HID_52D4_Finger
...

Input device: Wacom HID 52D4 Pen
Type: Tablet tool
Identifier: 1386:21204:Wacom_HID_52D4_Pen
...

Next, I told Sway to remap each of them to the new display dimensions each time tablet mode is toggled:

bindswitch tablet:on exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Finger map_to_output eDP-1', \
exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Pen map_to_output eDP-1'
bindswitch tablet:off exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Finger map_to_output eDP-1', \
exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Pen map_to_output eDP-1'

(Note: eDP-1 is the name of my display as interpreted by Sway. Reading around, it looks like almost all laptop displays will be identified as eDP-1. In a circumstance where it isn’t, the proper name can be found with swaymsg -t get_outputs.)

With that, I had tablet mode working correctly!

The Yoga in Tablet Mode

The Yoga in portrait tablet mode is a fantastic reading and web-browsing device.

I couldn’t do much with it though. I needed at least an on-screen keyboard and a way to start and close apps.

Starting with the keyboard, things are tricky. There are lots of different keyboards out there supporting different modes of getting called when tapping on a text input field. I opted for the simplest, which is to use Gnome’s method.

gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true

(This may depend on wider Gnome configuration which I already had in place, but for me that one-shot command was all that was necessary.)

The next step was to find a keyboard to invoke. There are tons available, but (in my view) none are as complete or as nice to use as Gnome’s in-built one. I don’t think there’s a way to invoke Gnome’s keyboard from within Sway though (if anyone reading would like to correct me on this, please reach out to me!).

The best alternative I can find is Squeekboard, a GTK3 Wayland on-screen keyboard written in Rust. When Squeekboard runs, it runs silently until I tap on a text input field. I don’t want it to show up when I’m in laptop mode and have the real keyboard available, but I do want it to be running whenever I’m in tablet mode.

The solution for that is pretty easy, a small adjustment to my earlier bindswitch commands:

bindswitch tablet:on exec 'squeekboard &', \
exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Finger map_to_output eDP-1', \
exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Pen map_to_output eDP-1'
bindswitch tablet:off exec 'killall squeekboard &', \
exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Finger map_to_output eDP-1', \
exec 'swaymsg input 1386:21204:Wacom_HID_52D4_Pen map_to_output eDP-1'

Squeekboard

The Squeekboard on-screen keyboard appears automatically when tapping on a text input field while in tablet mode.

One last thing in the Sway config before I move on is something every laptop config needs: a rebinding of media keys.

To do this on any device, the best approach is to use the wev tool, a rough equivalent of the xev tool for X.org. Each time you press a media key in the wev window, the terminal will output its function — usually an XF86* input code, but sometimes something surprising (some of the keys on this device are macro key-combinations instead).

Here’s how my config looks for those, with some I haven’t configured yet commented out:

# F-keys
bindsym XF86AudioMute exec 'pactl set-sink-volume @DEFAULT_SINK@ 0%'
bindsym XF86AudioLowerVolume exec 'pactl set-sink-volume @DEFAULT_SINK@ -10%'
bindsym XF86AudioRaiseVolume exec 'pactl set-sink-volume @DEFAULT_SINK@ +10%'
#bindsym XFAudioMicMute exe
bindsym XF86MonBrightnessDown exec sudo light -U 25
bindsym XF86MonBrightnessUp exec sudo light -A 25
#bindsym Mod4+p exec
bindsym XF86RFKill exec 'rfkill unblock all'
#bindsym Mod4+i exec
#bindsym Mod4+l exec
#bindsym Ctrl+Mod1+Tab exec
#bindsym XF86Calculator
#bindsym XF86Favorites
bindsym Mod4+Shift+s exec grim -g "$(slurp)" - | swappy -f - -o - | tee ~/Screenshots/$(date +%Y-%m-%d_%H-%M-%S).png | wl-copy

Gestures

The really tricky part with all this is how to launch and close applications. Reading around, some folks use custom buttons on their desktop panels, or a permanently visible dock.

Some devices have Windows buttons or similar that can be bound to call an app launcher, but this device doesn’t have any. When the device is in tablet mode, the keyboard is completely disabled — in hardware, I think, with no way to enable keys for use, and it’d probably be a bad idea to try anyway given how the device is held in tablet mode.

I think it’d probably be possible to override systemd’s control of that problematic power button and use that to trigger a launcher, but that still only solves half the problem.

The answer then is gestures, and here’s where things get tricky.

Sway itself has support for gestures via libinput, but only with the touch-pad — touch-screen gestures are not supported. The libinput documentation has some rationale behind not implementing support, though I’ve read conflicting views on how reasonable it is.

The best touch gesture implementation I’ve found so far is lisgd — the LIbinput Synthetic Gesture Daemon. It works fantastically, but configuration is tricky. Config is performed via compiling in config options defined in a config header file or by overriding the compiled-in config at execution. Overriding at execution is performed by adding a long series of arguments, one for each gesture.

Here’s an example from when I was trying this approach:

lisgd -d /dev/input/event10 \
-g "2,DRUL,*,*,R,pactl set-sink-volume @DEFAULT_SINK@ +10%" \
-g "2,URDL,*,*,R,pactl set-sink-volume @DEFAULT_SINK@ -10%" \
-g "3,LR,*,*,R,swaymsg layout tabbed" \
-g "3,RL,*,*,R,swaymsg layout stacking" \
-g "3,UD,*,*,R,swaymsg layout toggle split" \
-g "3,DU,*,*,R,nwggrid" \
-g "4,LR,*,*,R,swaymsg workspace next" \
-g "4,RL,*,*,R,swaymsg workspace prev" \
-g "4,DU,*,*,R,pactl set-sink-volume @DEFAULT_SINK@ 0%" \
-g "4,UD,*,*,R,swaymsg kill"

This works — every gesture I define there functions correctly — but there is a flaw that I can scarcely believe is present.

Because of the defaults set in that config file compiled into the binary, the definitions not overridden by my arguments there remain in place — so swiping left and right tries to call xdotool despite it not being present on my system (incidentally, on Wayland, it’s better to use ydotool if you’re looking to perform mouse actions).

Worse though is that those definitions override the default touch behaviour, so single-finger scrolling left and right no longer works, and nor does two-finger scrolling up and down since they’ve been rebound to (I think) raise and kill the author’s on-screen keyboard.

That meant for me that I had to recompile the app with my own config.h to retain normal behaviour and add my own gestures.

My config is as follows:

Gesture gestures[] = {
/* nfingers, gesturetype, edge, distance, mode, command */
{2, SwipeDRUL, EdgeAny, DistanceAny, ActModeReleased, "pactl set-sink-volume @DEFAULT_SINK@ +10%"},
{2, SwipeURDL, EdgeAny, DistanceAny, ActModeReleased, "pactl set-sink-volume @DEFAULT_SINK@ -10%"},
{3, SwipeLR, EdgeAny, DistanceAny, ActModeReleased, "swaymsg layout tabbed"},
{3, SwipeRL, EdgeAny, DistanceAny, ActModeReleased, "swaymsg layout stacking"},
{3, SwipeUD, EdgeAny, DistanceAny, ActModeReleased, "swaymsg layout toggle split"},
{3, SwipeDU, EdgeAny, DistanceAny, ActModeReleased, "nwggrid"},
{3, SwipeDRUL, EdgeAny, DistanceAny, ActModeReleased, "(grim -g \"$(slurp)\" - | swappy -f - -o - | tee ~/Screenshots/$(date +%Y-%m-%d_%H-%M-%S).png | wl-copy) &"},
{3, SwipeURDL, EdgeAny, DistanceAny, ActModeReleased, "(grim - | swappy -f - -o - | tee ~/Screenshots/$(date +%Y-%m-%d_%H-%M-%S).png | wl-copy) &"},
{4, SwipeLR, EdgeAny, DistanceAny, ActModeReleased, "swaymsg -t get_workspaces | jq -r '.[] | select(.num != -1) | .num' | sort -nu | awk 'END{print $1+1}' | xargs -I{} swaymsg workspace number {}"},
{4, SwipeRL, EdgeAny, DistanceAny, ActModeReleased, "swaymsg workspace prev"},
{4, SwipeDU, EdgeAny, DistanceAny, ActModeReleased, "pactl set-sink-volume @DEFAULT_SINK@ 0%"},
{4, SwipeUD, EdgeAny, DistanceAny, ActModeReleased, "swaymsg kill"},
};

I think these commands are mostly self-explanatory, but it’s worth highlighting nwggrid from the excellent nwg-launchers package.

After this came more trickiness. Adding exec lisgd & at the end of my Sway config alone didn’t work as the directions for each gesture didn’t change to the new geometry when the screen rotated. This couldn’t be fixed using the tablet mode bindswitch directives I used previously, since they only trigger when switching to tablet mode and back, not on screen rotation.

The answer was even simpler. rot8, the tool I’m using to rotate the screen, has a –hooks option to call commands each time it rotates. That fits the bill nicely, and works perfectly placed at the end of my Sway config as so:

exec rot8 --hooks 'killall lisgd; lisgd &'

And that’s it!

The Device Itself

I’ve never thought much of these 2-in-1 devices. I viewed them as not being good at either function, generally targeting the lower end of the market, and them really being more of a gimmick than a serious productivity aid. My primary purpose in getting this one was to have a screen for watching movies in bed, replacing an ancient iPad long past its prime, and my secondary one was to replace a small 12“ Macbook (running Linux) I currently use for writing on the go.

I’ve found myself loving it though. It has a heft and a build quality I didn’t expect, the screen is delightful, and the tablet mode makes for an exceptional long-form reading device. I’ll still always prefer my Kobo for reading books, but for reading news, commentary, blogs, and reddit, I absolutely love this thing.

The Yoga Tent-poled

In tent-pole mode, this is now my preferred way to play chess online.

On top of that, I’m someone who spends a lot of time doing hand-drawings using a graphics tablet for work (and a tiny bit for this blog). I use Obsidian (why do none of the FOSS clones keep Markdown files in the file-system in a folder hierarchy!?) with the Excalidraw plugin as my drawing tool of choice. This device is absolutely perfect for that use case.

And so I’m left thinking about what my next device would be. My plan here was for this thing to be a toy device, fitting my narrow niche use cases instead of being a full-fledged personal laptop. I figured that when I was ready to get a new laptop again, I’d look at the FOSS and FOSS-friendly offerings out there, like Framework and System76.

Now though, I might end up giving serious thought to a high-end Yoga or a similar device. I wonder if there are any FOSS-friendly clones out there?