TANSOT

czyli dziennik wojen z oprogramowaniem. Więcej »

Linux desktop tweaks

11 lis 2021 725 słów

I try to stick to defaults whenever possible, but I still have a healthy amount of tweaks and customizations in my Linux workstation installs. I’m using Fedora with GNOME, so some of these tweaks are specific to that platform and desktop environment.

Disable suspend when closing laptop lid

In /etc/systemd/logind.conf:

HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore

Then also uncheck “Suspend when laptop lid is closed” in GNOME Tweaks.

Add common bin dirs to PATH

In ~/.config/fish/config.fish:

fish_add_path --path $HOME/bin
fish_add_path --path $HOME/.local/bin
fish_add_path --path $HOME/Projects/bin
fish_add_path --path $HOME/.config/composer/vendor/bin
fish_add_path --path /var/lib/flatpak/exports/bin
fish_add_path --path $HOME/.local/share/flatpak/exports/bin

Workaroundy environment variables

Some programs I worked with required messing with environment variables to work properly. This is most probably out-of-date. In ~/.config/fish/config.fish:

# Workaround: https://github.com/facebook/create-react-app/issues/10110
#set -x DISABLE_NEW_JSX_TRANSFORM true

# Telemetry breaks under Nixpkgs dotnet
#set -x DOTNET_SKIP_FIRST_TIME_EXPERIENCE true
#set -x DOTNET_CLI_TELEMETRY_OPTOUT true
#set -x POWERSHELL_TELEMETRY_OPTOUT 1

# this variable added by Nix profile causes trouble with non-Nix GTK apps
#set -e GDK_PIXBUF_MODULE_FILE

Default to Yes in dnf confirmation prompts

Run:

sudo dnf config-manager --setopt defaultyes=True --save

Set the VISUAL, EDITOR and SUDO_EDITOR environment variables

In ~/.config/fish/config.fish:

set -x VISUAL (which nvim)
set -x EDITOR $VISUAL
set -x SUDO_EDITOR $VISUAL

Setup dircolors

The default colors of ls are horrible. In ~/.config/fish/config.fish:

eval (dircolors -c ~/.dircolors)

~/.dircolors can be found in my dotfiles repo.

Quickly switch sound device

This uses PulseAudio CLI, but works as well under PipeWire. Save this script on your PATH as rofi-pulse:

#!/usr/bin/env python
import subprocess
import os
import sys
import re

if len(sys.argv) < 2:
    print('usage: rofi-pulse input|output', file=sys.stderr)
    sys.exit(1)

target = sys.argv[1]
assert target == 'input' or target == 'output'

list_type = {'input': 'sources', 'output': 'sinks'}[target]
id_re = r'^Sink #(\d+)\s*$'
default_re = {
        'input': r'^\s*Default Source:\s*(.*)',
        'output': r'^\s*Default Sink:\s*(.*)'}[target]

list_out = [line.decode('utf-8')
        for line in subprocess.run(['pactl', 'list', list_type], stdout=subprocess.PIPE).stdout.split(b'\n')]
ids = [int(re.sub(id_re, r'\1', line)) for line in list_out
        if re.match(id_re, line)]
names = [re.sub(r'^\s*Name:\s*(.*)$', r'\1', line) for line in list_out
        if re.match(r'^\s*Name:', line)]
descriptions = [re.sub(r'^\s*Description:\s*(.*)$', r'\1', line) for line in list_out
        if re.match(r'^\s*Description:', line)]

info_out = [line.decode('utf-8') for line in subprocess.run(['pactl', 'info'],
    stdout=subprocess.PIPE).stdout.split(b'\n')]
default_name = next(re.sub(default_re, r'\1', line) for line in info_out
        if re.match(default_re, line))

name_to_description = dict(zip(names, descriptions))
description_to_name = dict(zip(descriptions, names))

rofi_options = ['rofi', '-i', '-dmenu', '-p', target]
try:
    default_index = str(names.index(default_name))
    rofi_options.extend(['-selected-row', default_index, '-a', default_index])
except ValueError:
    pass

choice = subprocess.Popen(rofi_options, stdin=subprocess.PIPE, stdout=subprocess.PIPE) \
    .communicate(input='\n'.join(descriptions).encode('utf-8'))[0].decode().strip()
try:
    if choice == '':
        sys.exit(0)

    choice_name = description_to_name[choice]
    set_default_cmd = {
            'input': 'set-default-source',
            'output': 'set-default-sink',
            }[target]
    subprocess.check_call(['pactl', set_default_cmd, choice_name])
except Exception as e:
    subprocess.check_call(['rofi', '-e', 'Cannot set sound device.'])

Then, bind Super+[ to rofi-pulse input and Super+] to rofi-pulse output. To be honest, I wish GNOME had built-in UI for this.

GNOME keybindings

Go to Settings → Keyboard → Customize Shortcuts and set:

Action Shortcut
Windows → Activate the window menu Super+Menu
Windows → Close window Super+Q
Windows → Move window Disabled
Windows → Resize window Disabled
Navigation → Switch applications Disabled
Navigation → Switch windows Alt+Tab

As well as custom shortcuts:

Command Shortcut
gnome-calculator Super+Enter (keypad)
nautilus --new-window Super+E
firefox -profilemanager Super+W
xkill Ctrl+Alt+Escape
gnome-system-monitor Ctrl+Shift+Escape
gnome-control-center sound Super+S
tilix --maximize Super+Return

I disabled the shortcut because it conflicted with keybindings of other programs, eg. PhpStorm. Also, in my opinion the window manager should only register keybindings using the Super key.

The Super+Q shortcut to close windows is used by default by i3wm. Way more convenient than Alt+F4!

To set workspace shortcuts (Super+1-8 to switch workspaces, Super+F1-F8 to move window to a workspace), I use this script:

#!/usr/bin/env bash
set -euo pipefail
# Maximum is 10 workspaces.
num_workspaces=8
for n in $(seq 1 $num_workspaces); do
    # By default Super+1-8 is "goto the N-th application in the dock" shortcut.
    # Remove it, so it doesn't conflict.
    # Fun fact: `@as` is to indicate that the value is an array of strings.
    dconf write /org/gnome/shell/keybindings/switch-to-application-$n "@as []"
    # The actual keybindings.
    dconf write /org/gnome/desktop/wm/keybindings/switch-to-workspace-$n \
            "['<Super>$((n%10))']"
    dconf write /org/gnome/desktop/wm/keybindings/move-to-workspace-$n \
            "['<Super>F$n']"
done

Install GNOME Shell extensions

I try to avoid extensions, but these two are necessary for my sanity:

I also use GSConnect to sync with my Android phone.