Desktop setup
Introduction
I am a long-term KDE user. How long term? Well, since 1997 or so with intermittencies. A couple of weeks ago, I switched my personal desktops to QTile.
Mandatory screenshot:
Why?
Well, I like to switch things around every few years. I have used Unity, and MATE, and even LxQt at some point, and I have noticed over the years that I don't tend to overlap windows, so tiling window managers are the natural progression of my usage.
For a few years I have used Krohnkite an awesome KWin extension that has really been a pleasure to use and proved me that a tiling WM, with a few reasonable keyboard shortcuts, was a workable solution.
Lately I have been building a homebrew laptop which has fairly limited hardware, but I want to use for real. So I also wanted to explore ways to lower resource usage.
Other related concerns:
- I have very dynamic monitor configurations. My computers gain and lose monitor all the time, and this needs to be handled automatically.
- I have multiple computers (work, home, homebrew) and they all have very different setups and configurations, but I want them all to work the same.
- I use chezmoi to manage my configurations so I want config to be simple text files I can store with a minimum of templating.
- I want comfort. I want disk automouning, volume keys, bluetooth menus, wifi configuration, a clock, battery information, notifications and so on.
But why QTile specifically:
- It's tiling but with a nice attitude. The whole "hardcore" thing in both tiling WM users and projects is a bit tiresome.
- It's written, configurable and extendable in Python, which I like.
- It works reasonably out of the box. No, a black screen where you can do nothing without looking up shortcuts in your phone at first is too much.
- It's "dynamic". This is one of those minute subcategories you can see in WM fandom, but basically: windows get placed more or less correctly by default, while other WMs will make you explicitly handle each window as it appears, which sounds sort of like torture to me.
So what?
So I will now describe painstakingly every decision I made as of today, and describe the important configuration, and bore anyone who tries to read this to tears, that's what.
In an attempt to make this a bit less annoying I will setup a problem, then describe the solution I found.
Uniform UI for utilities
The Problem
There are a number of ways you interact with your "desktop". You may want to logout. Or start an app. Or run a command. Or configure a bluetooth speaker. Or see the clipboard history. Or configure wifi.
In the desktop world, this all comes included in the package deal. In the Window Manager universe, you have to roll your own, and most setups end with a disparate collection of utilities, like blueman and network-manager and clipit and so on plus whatever the WM itself provides.
Well, but what if they could be the same thing?
What if in addition they could all share a lovely, customizable UI?
The Solution
Enter rofi
the most common usage for rofi is as an app launcher, and it's great at that, but it's just scratching the surface:
Because of its flexible architecture, it can be used to configure your network using networkmanager-dmenu:
As your power/logout menu via rofi-power-menu
And much more. I also use it as a fronted to a pasword manager, a frontend to ssh, a simple Todo list, an emoji picker, as a fancy window switcher, and as a clipboard history manager using rofi-greenclip
Rofi starts so fast that it's often imposible to notice the difference between having a tool always running and with an icon in the systray and just having a button that launches rofi.
So, how do I use it in my ~/.config/qtile/config.py
?
Bound to some key shortcuts:
keys = [
# Launch things
Key([mod], 'p', lazy.spawn('rofi -show drun'), desc="Run app"),
Key([mod, "shift"], 'p', lazy.spawn('rofi -show run'), desc="Run Command"),
Key([mod], 's', lazy.spawn('rofi -show ssh'), desc="Ssh"),
Key([mod], 'e', lazy.spawn('rofi -show emoji'), desc="Emoji"),
Key([mod], 'v', lazy.spawn('rofi -modi "clipboard:greenclip print" -show'), desc="Clipboard")
Key([mod, alt], "Tab", lazy.spawn('rofi -show window'), desc="Move focus to next window"),
I also added a LaunchBar widget to my QTile bar where it replaces something like clipit, the blueman applet, a password manager and a todo app:
widget.LaunchBar(padding=0, text_only=True, font="Font Awesome 6 Free", fontsize=16, foreground="2e3440", progs=[
("", "rofi-bluetooth", "Bluetooth"),
("", "rofi -modi 'clipboard:greenclip print' -show", "Clipboard History"),
("", "rofi-pass", "Passwords"),
("", "rofi-todo -f /home/ralsina/todo.txt", "Todo"),
("", "flameshot gui", "Screenshot"),
]),
The first field that probably looks like a square in there are unicode characters displayed on the desktop using font awesome:
Also for mouse interactions with the Wlan widget:
widget.Wlan(format=" {percent:2.0%}",width=54, mouse_callbacks={'Button1': lazy.spawn('networkmanager_dmenu')}),
I even use it to show me the shortcut list in case I forgot any!
I could write some minor code and use it for other things too. Rofi is a magnificent example of a tool with a nice API that you can leverage to make it do many different things.
Display Configuration
The Problem
My display configurations are many. I use notebooks, so they all have one "default" configuration where they use their own screen.
In one case, it's all it can use.
In another, it's permanently docked to a monitor above its screen, but sometimes I close the notebook and the only display is the monitor.
In another it's usually docked to a monitor besides its screen, but it's shared with my work computer, so it loses and recovers that monitor dozens of times via a HDMI switch through the day as I switch tasks.
While QTile handles gracefully the addition and removal of displays in general, there are some issues.
- Using the HDMI switch didn't make the monitor "go away" which caused some issues with barrier
- The config file describes the "bars" for each screen. So I need to put the main bar that has the systray on a screen that is always on. But the screens are one per-output and in a machine-dependent order, and things got complicated really quick. Here Plasma does the right thing effortlessly.
The Solution
Enter autorandr an awesome tool that will react to changes in your situation and do the right thing.
How?
- Set your machine as you normally use it say, with two monitors using arandr or something. Then
autorandr -s two-monitors
- Set your machine in its alternate config, say, with the lid closed. Then
autorandr -s closed-lid
- Put
autorandr -c
in your~.xprofile
This is enough for most things, except the bar situation in QTile.
For that, I wrote two things. First a shell script that goes in ~/.config/autorandr/postswitch
This is a "hook" that gets executed after the display configuration changes. Here's mine:
#!/bin/bash -x
config="$AUTORANDR_CURRENT_PROFILE"
notify-send "Switched to config $AUTORANDR_CURRENT_PROFILE"
killall -USR1 qtile # Make qtile reread configuration
And why do I want qtile to reread its configuration? Because I want the systray to be on the "last" screen, which is always larger :-)
Here's the relevant fragment from ~/.config/qtile/conf.py
screen_count = len(subprocess.check_output(shlex.split("xrandr --listmonitors")).splitlines()) -1
screens = [
Screen(
bottom=bar.Bar(
[ ... # List of widgets for this bar
# Add systray in the LAST screen, otherwise invisible widget
widget.Systray(padding=10) if i==screen_count-1 else widget.Sep(linewidth=0),),
) for i in range(screen_count)]
This solution requires a minimum of setup for my standards (just configure your screens once and a bit of code) and a maximum of benefit:
- The same config works on all my machines, except for autorandr profiles, which have to be done on each computer
- It works in a totally automated manner, I never need to move screens around
- It will work on any future computers thank to chezmoi
Chrome hates WMs
The Problem
I have seen it a few times in the past. I change WMs or desktops and suddenly chrome has no idea where my passwords went. They are there in passwords.google.com but chrome never autofills, and they are not listed in my local profile.
The Solution
Make sure you are running gnome-keyring-daemon
and add this in ~/.config/chrome-flags.conf
--password-store=gnome
The Other Solution
You want a password store that is not chrome. I have one in addition of chrome, using pass
This keeps my passwords encrypted in Git (you can use GitHub, I use my personal gitea) and you can access them from the command line, from a Chrome extension, or from a button in my desktop bar using rofi-pass
This ensures I never will lose access to my passwords, and maximizes the ways I can get to them while keeping things nice and secure.
Automounting Devices
The Problem
While I know perfectly well how to mount devices in Linux, I'd rather they mount themselves.
The Solution
Use udiskie it's lightweight, nicely integrated, and it just works well by default.
Notifications
The Problem
I want desktop notifications.
The solution
Use dunst it's lightweight, nicely integrated, and it just works well by default.
Screen Locking
The Problem
I want my screen to lock, but don't want anything fancy.
The Solution
Use xss-lock
and slock
While slock in itself works great, xss-lock
integrates it with loginctl
so it works better. Just add this to your ~/.xprofile
:
xss-lock slock &
Pretty Icons and Colors
The Problem
I want simple icons that are not very colorful, so they integrate with whatever color scheme I am using.
The Solution
Since I am following more or less a nord color scheme I am using a combination of Nordzy icons and just using font-awesome
Here are some snippets from the qtile config. Where I am pointing icons to a specific folder, that folder contains copies of nordzy icons converted to PNG with transparent backgrounds:
bottom=bar.Bar(
[ ...
widget.GroupBox(highlight_method="block", foreground="2e3440", active="d08770", inactive="4c566a", this_current_screen_border="242831",this_screen_border="88c0d0"),
widget.BatteryIcon(theme_path="/home/ralsina/.config/qtile/battery_icons", background="d8dee9", mouse_callbacks=power_callbacks),
widget.Volume(fmt="",padding=0, theme_path="/home/ralsina/.config/qtile/battery_icons"),
widget.LaunchBar(padding=0, text_only=True, font="Font Awesome 6 Free", fontsize=16, foreground="2e3440", progs=[
("", "rofi-bluetooth", "Bluetooth"),
("", "rofi -modi 'clipboard:greenclip print' -show", "Clipboard History"),
("", "rofi-pass", "Passwords"),
("", "rofi-todo -f /home/ralsina/todo.txt", "Todo"),
("", "flameshot gui", "Screenshot"),
]),
],
background=["d8dee9"],
border_width=[1, 0, 0, 0], # Draw top and bottom borders
border_color=["b48ead","b48ead","b48ead","b48ead" ], # Borders are magenta
Then I configured styles and icons for gtk apps (using lxappearance
) and Qt apps (using qt5ct
) and things now look decent.
Color Schemes
The Problem
I want a coordinated color scheme in the disparate tools I am using.
The Solution
Update: I wrote a more tutorial-like thing about this called Color Coordination Using Base16
Enter flavours, a tool to generate configurations for different things out of simple templates and predefined color schemes.
It already supported my terminal (alacritty) and rofi, just needed to create templates for my qtile config:
colors = {
"background": "#{{base00-hex}}",
"background-lighter": "#{{base01-hex}}",
"selection-background": "#{{base02-hex}}",
"comments": "#{{base03-hex}}",
"foreground-dark": "#{{base04-hex}}",
"foreground": "#{{base05-hex}}",
"foreground-light": "#{{base06-hex}}",
"background-light": "#{{base07-hex}}",
"variables": "#{{base08-hex}}",
"integers": "#{{base09-hex}}",
"bold": "#{{base0A-hex}}",
"strings": "#{{base0B-hex}}",
"quotes": "#{{base0C-hex}}",
"headings": "#{{base0D-hex}}",
"italic": "#{{base0E-hex}}",
"tags": "#{{base0F-hex}}",
}
And my tmuxer (zellij):
themes {
default {
fg "#{{base05-hex}}"
bg "#{{base00-hex}}"
black "#{{base00-hex}}"
red "#{{base08-hex}}"
green "#{{base0B-hex}}"
yellow "#{{base0A-hex}}"
blue "#{{base0D-hex}}"
magenta "#{{base0E-hex}}"
cyan "#{{base0C-hex}}"
white "#{{base05-hex}}"
orange "#{{base09-hex}}"
}
}
And put the right incantations in the flavours config (partial):
[[items]]
file = "~/.config/qtile/colors.py"
template = "ralsina"
subtemplate = "qtile"
rewrite = true
hook = "killall -USR1 qtile"
[[items]]
file = "~/.config/zellij/themes/default.kdl"
template = "ralsina"
subtemplate = "zellij"
rewrite = true
I will have to configure everything I want to follow my colour scheme but I can apply it to everything with a simple command like flavours apply monokai
As an extra, here's a script that lets you choose the color scheme using rofi:
#!/bin/sh
LAST_SCHEME=$(cat ~/.local/share/flavours/lastscheme)
SELECTED=$(flavours list | sed 's/\s/\n/g' | grep -n $LAST_SCHEME | cut -d: -f1)
flavours apply $(flavours list| sed 's/\s/\n/g' | rofi -dmenu -selected-row $SELECTED)
Conclusion
What can I say, I like it, it's not ugly, has all the functionality I wanted and uses around 400MB of RAM before I start chrome. I am quite enjoying it.
While this site (intentionally) has no comments, feel free to discuss this in Reddit