Skip to content

Latest commit

 

History

History
1375 lines (1207 loc) · 46 KB

wm.org

File metadata and controls

1375 lines (1207 loc) · 46 KB

Window Manager Config

I use i3 as my WM (running under X server) because I’m used to it and it fits my personal needs. So this file mostly contains the config for X and i3. But I also need to use wayland during app development. Under wayland I use sway, because it is mostly compatible with i3.

Most of the config that is used by i3 is also used by sway, with a few exceptions. The default tangle destination file for code blocks is the i3 config file, and whenever I want to tangle an i3 config code block to sway as well, I create a new code block with a different tangle destination and use a noweb reference to replicate the code for sway.

With i3, I also use picom as my compositor.

Quick interaction

Execute the following call to tangle this file and apply changes to the running i3/sway session:

(haris/tangle-dest)
(shell-command (format "%s-msg -t command restart" wm))
(call-process "pkill" nil 0 nil "-u" (user-login-name) "sxhkd")
(run-with-timer
 1 nil
 (lambda ()
   (let ((default-directory "~/"))
     (call-process "sxhkd" nil 0 nil)
     (if (locate-file "sxhkd.private" exec-path)
         (call-process "sxhkd.private" nil 0 nil)))))

Helper scripts and local variables

:header-args+: :tangle-mode (eval #o744)

These scripts are used by the other sections:

pactl set-source-volume "$(pactl get-default-source)" +5%
pactl set-source-volume "$(pactl get-default-source)" -5%
pactl set-source-mute "$(pactl get-default-source)" toggle
default_source="$(pactl get-default-source)"
default_sink="$(pactl get-default-sink)"
mic_muted="$(pactl get-source-mute "$default_source" | grep 'Mute: yes')"
sink_muted="$(pactl get-sink-mute "$default_sink" | grep 'Mute: yes')"

[ -n "$mic_muted" ] && pactl set-source-mute "$default_source" 0
[ -n "$sink_muted" ] && pactl set-sink-mute "$default_sink" 0

file="$(mktemp)"
arecord -f cd -d 1 "$file"
aplay "$file"

[ -n "$mic_muted" ] && pactl set-source-mute "$default_source" 1
[ -n "$sink_muted" ] && pactl set-sink-mute "$default_sink" 1
default_sink="$(pactl get-default-sink)"
sink_muted="$(pactl get-sink-mute "$default_sink" | grep 'Mute: yes')"

[ -n "$sink_muted" ] && pactl set-sink-mute "$default_sink" 0

speaker-test --test wav --speaker 1

[ -n "$sink_muted" ] && pactl set-sink-mute "$default_sink" 1

Variable definitions

# vim: filetype=i3

# Default mod key
set $mod              Mod4

# Common sizes
set $resizeW0         resize set width 20
set $resizeH0         resize set            height 20
set $resize1          resize set width 710  height 600
set $resize2          resize set width 900  height 700
set $resize3          resize set width 1100 height 800
set $resize4          resize set width 1400 height 900
set $resize_devtools  resize set width 1400 height 600
set $resize_fullhd    resize set width 1920 height 1080
set $resize_term      resize set width 710

# Prefix n stands for normal
set $col_n_k #1e1e1e
set $col_n_r #ff5555
set $col_n_g #5ac2a8
set $col_n_y #f2b374
set $col_n_b #6980fa
set $col_n_m #d098ff
set $col_n_c #8cceff
set $col_n_w #92aab7

# Prefix b stands for bright
set $col_b_k #6b746b
set $col_b_r #ff8c8c
set $col_b_g #98eb98
set $col_b_y #e0d97b
set $col_b_b #99a3ff
set $col_b_m #f298c3
set $col_b_c #a6d9ff
set $col_b_w #dddddd

# Extra colors
set $col_x16 #303030
# Black color with transparency
set $col_a_k #1e1e1ecc
# Bright magenta with transparency for bar separator
set $col_a_m #f298c377
<<variables>>

Visuals

Only for i3:

for_window [all]                      title_format " %title"
# This one prevents _MOTIF_WM_HINTS to force a window title
for_window [all]                      border pixel 2

Shared between i3 and sway:

font pango:Source Code Pro, Icons bold 10
hide_edge_borders none
gaps inner 6
gaps outer 0
popup_during_fullscreen leave_fullscreen
for_window [window_type="dialog"]     border normal 2
for_window [window_type="popup_menu"] border normal 2

# Colors
# class                 border    backgr.   text      indicator child_border
client.focused          $col_n_b  $col_b_b  $col_n_k  $col_b_m  $col_b_b
client.unfocused        $col_n_m  $col_n_m  $col_n_k  $col_n_m  $col_b_k
client.focused_inactive $col_n_b  $col_b_b  $col_n_k  $col_n_m  $col_b_k
client.urgent           $col_n_y  $col_n_y  $col_n_k  $col_n_y  $col_n_y
<<visuals>>

Behavior

focus_follows_mouse no
popup_during_fullscreen leave_fullscreen
for_window [instance="Float" class="Alacritty"] floating enable

X config

*.*.font: Ubuntu Mono

Start-up

.xinitrc

:header-args+: :tangle-mode (eval #o744)Since i3 is used under X server, it needs to be launched within ~/.xinitrc. There are three .xinitrc configuration presets, that I switch between using xpreset. The default one is defined here (you can change it if you want):
<<xinitrc-i3>>

Here are the configs you can choose from:

. ~/.xinitrc.common

dunst &                                       # Notification daemon
picom &                                       # Compositor
autotiling &                                  # Autotiling for i3

export TERM=dumb
sxhkd &                                       # Load default bindings
sxhkd.private &                               # Load private bindings

export DESKTOP_SESSION=i3
exec i3                                       # Window manager
. ~/.xinitrc.common

export DESKTOP_SESSION_TYPE=x11
export GDK_BACKEND=x11
exec gnome-session
. ~/.xinitrc.common

export DESKTOP_SESSION=plasma
exec dbus-launch startplasma-x11
. ~/.xinitrc.common

startxfce4

The following is shared by all presets. It also contains configuration specific to the laptop I currently use. Please change to suit your needs.

export DBUS_SESSION_BUS_ADDRESS=unix:path="$XDG_RUNTIME_DIR"/bus

dbus-update-activation-environment --systemd --all  # Fixes dbus problems with i3

«dell-g15-xinitrc»
xset r rate 200 60                          # Increase key press rate
redshift &                                  # Blue light filter

xset s 300                                  # Set lock timeout (seconds)
xss-lock -l -- lock-screen &                # Start lock screen daemon
systemctl start --user clipmenud            # (ref:clipmenud)

xrdb -merge ~/.Xresources $(ls ~/.local.Xresources 2>/dev/null)
feh --bg-fill ~/.wallpaper                  # Set wallpaper
# vim: ft=sh

i3 start-up

Some things are tightly tied to i3 and should be launched as part of its configuration reload procedure:

exec_always --no-startup-id \
/home/haris/.local/lib/i3/i3-cycle-focus.py --history 2
exec_always --no-startup-id "autotiling"
# Use pactl to adjust volume in PulseAudio.
set $refresh_i3status killall -SIGUSR1 i3status

sway start-up

# TODO debug this
# exec swhks
# exec pkexec swhkd --debug | tee /tmp/swhkd.log

# TODO this is temporary:
bindsym $mod+Return exec alacritty
bindsym $mod+Shift+Return exec alacritty-float

Please verify that the correct device-specific config is chosen. Otherwise change it according to your preference:

<<dell-g15-sway>>

System-specific setups

Dell G15 5510

setxkbmap -layout "ba"
xmodmap -e "keycode 10 = 1 exclam asciitilde asciitilde asciitilde"
xmodmap -e "keycode 16 = 7 slash grave grave grave"
xmodmap -e "keycode 12 = 3 numbersign asciicircum asciicircum asciicircum"

touchpad_id=$( xinput list | grep -i touchpad | awk -F'id=' '{print $2 }' | awk '{print $1}' )
touch_option=$( xinput list-props $touchpad_id | grep -i 'Tapping Enabled' | head -1 | awk -F"[()]" '{print $2}' )
accel_option=$( xinput list-props $touchpad_id | grep -i 'Accel Speed' | head -1 | awk -F"[()]" '{print $2}' )

# Enable touch clicking
xinput set-prop $touchpad_id $touch_option 1
# Increase touchpad acceleration
xinput set-prop $touchpad_id $accel_option 0.5

# Show second monitor
xrandr2 --auto
input * {
    xkb_layout "ba"
}
# TODO replicate other commands from i3 part

Key bindings and menus

Only a core set of keybindings whose behavior is coupled to i3 are defined in the i3 config. All the other bindings are provided by an external program - sxhkd on X and swhkd on Wayland. See Start-up. These bindings can be found here.

exec --no-startup-id swhks

Default mode bindings

These bindings are available in the default mode, i.e. when no submenu is active.

# Manipulation using mouse
floating_modifier $mod

# kill focused window
bindsym $mod+w            kill
bindsym $mod+Ctrl+w       exec xdotool getactivewindow windowkill

# Navigation
bindsym $mod+m            scratchpad show
bindsym $mod+Shift+m      move scratchpad

# Change focus
bindsym $mod+h            focus left
bindsym $mod+j            focus down
bindsym $mod+k            focus up
bindsym $mod+l            focus right
bindsym $mod+space        focus mode_toggle
bindsym $mod+a            focus parent
bindsym $mod+d            focus child

# move focused window
bindsym $mod+Shift+h      move left     30
bindsym $mod+Shift+j      move down     30
bindsym $mod+Shift+k      move up       30
bindsym $mod+Shift+l      move right    30

# Layout manipulation
bindsym $mod+f            fullscreen toggle
bindsym $mod+Shift+space  floating toggle
bindsym $mod+b            bar mode toggle

# Resize window
bindsym $mod+plus         resize grow   width   20;
bindsym $mod+minus        resize shrink width   20;
bindsym $mod+Shift+plus   resize grow   height  20;
bindsym $mod+Shift+minus  resize shrink height  20;
<<general-bindings>>

External bindings

External bindings are served by sxhkd.

# Lock screen
mod4 + ctrl + l
    xset s activate

# Dmenu stuff
mod4 + ISO_Level3_Shift
    dmenu_run
mod4 + c
    dmenu_run config
mod4 + o
    dmenu_run open
mod4 + shift + p
    passmenu -l 10
mod4 + ctrl + p
    dmenu_run otp
mod4 + q
    dmenu_run system
mod4 + ctrl + b
    dmenu_run books
mod4 + ctrl + q
    dmenu_run quickmenu

# Applications
mod4 + Return
    alacritty
mod4 + shift + Return
    alacritty --class Alacritty,Float
mod4 + ctrl + Return
    eterm
mod4 + shift + f
    firefox -P «eval-user-name()»
mod4 + shift + v
    myemacs-float -c --eval "(progn (scratch-buffer) (call-interactively 'find-file))"
mod4 + v
    myemacs-float -c --eval '(scratch-buffer)'
mod4 + shift + e
    myemacs -c --eval '(scratch-buffer)'
mod4 + shift + g
    dmenu_run gpg
mod4 + ctrl + s
    dmenu_run services

# Volume and brightness
XF86AudioRaiseVolume
    pactl set-sink-volume "$(pactl get-default-sink)" +5%
XF86AudioLowerVolume
    pactl set-sink-volume "$(pactl get-default-sink)" -5%
XF86AudioMute
    pactl set-sink-mute   "$(pactl get-default-sink)" toggle
XF86AudioMicMute
    pactl set-source-mute "$(pactl get-default-source)" toggle
XF86MonBrightnessUp
    brightnessctl set 5%+
XF86MonBrightnessDown
    brightnessctl set 5%-

# Browser: switch to google search
mod1 + i
    browser-google-search

# Small utilities
mod4 + ctrl + c
    echo | xsel -b

# vim: ft=sxhkd

NOTE: This is a workaround because for some reason the sxhkd launched from .xinitrc cannot run etranslate properly. (The command is run, but no frame is displayed). When I add exec sxhkd instead of launching it from .xinitrc, then dmenu doesn’t work.

bindsym $mod+Shift+t exec etranslate

Applications menu

set $mode_apps "Apps | [p]acman [c]lipmenu [e]lement [v]iber [u]nicode [V]pn [t]eams"

mode $mode_apps {
bindsym p       exec dmenu_run pacman, mode default
bindsym c       exec dmenu_run Clipboard, mode default
bindsym e       exec element-desktop, mode default
bindsym v       exec viber, mode default
bindsym u       exec dmenu_run Unicode, mode default
bindsym Shift+v exec vpn-toggle, mode default
bindsym t       exec teams, mode default

bindsym Escape mode default
}

bindsym $mod+Shift+a mode $mode_apps

Resize menu

set $mode_resize     " resize | [+] [-] (+ Alt variants) | [c]enter | Presets [0/Alt+0] [1] [2] [3] [4] [a] [b] [d]evtools [f]ullHD"

mode $mode_resize {
bindsym plus            resize  grow    width   1; move position center
bindsym mod1+plus       resize  grow    height  1; move position center
bindsym minus           resize  shrink  width   1; move position center
bindsym mod1+minus      resize  shrink  height  1; move position center

bindsym c               move position center

bindsym =               exec --no-startup-id i3_balance_workspace
# On some systems the = keysym is not emitted, so I use Shift+0 as a backup
bindsym Shift+0         exec --no-startup-id i3_balance_workspace

bindsym t               $resize_term; mode default

# Some standard sizes
bindsym 0               $resizeW0
bindsym mod1+0          $resizeH0
bindsym 1               $resize1; move position center
bindsym 2               $resize2; move position center
bindsym 3               $resize3; move position center
bindsym 4               $resize4; move position center
bindsym d               $resize_devtools; move position center
bindsym f               $resize_fullhd; move position center
bindsym a               resize set width 592 height 926;
bindsym b               resize set width 292 height 580;

# Back to normal: Enter or Escape or $mod+r
bindsym Escape          mode default
}

bindsym $mod+r          mode $mode_resize

Controlling the i3 session

set $mode_session    " i3 session [r]eload [Ctrl+r]estart [q]uit [k]bind"

mode $mode_session {
bindsym r               reload
bindsym Ctrl+r          restart
bindsym q               exit
bindsym k               exec --no-startup-id kbind, mode default
bindsym Escape          mode default
}

bindsym $mod+period     mode $mode_session

Window management menu

set $mode_wm         " WM [h]oriz [v]ert [.]split [s]tacking [t]abbed stic[k]y pi[c]om [a]utotiling [x]randr ws-to-monitor[1]/[2]"

mode $mode_wm {
bindsym h               split h
bindsym v               split v
bindsym period          layout toggle split
bindsym s               layout stacking
bindsym t               layout tabbed
bindsym k               sticky toggle
bindsym c               exec --no-startup-id "pgrep picom && pkill picom || picom"
bindsym a               exec --no-startup-id \
"pgrep autotiling && pkill autotiling || autotiling"
bindsym x               exec xrandr-toggle; mode "default"
bindsym 1               move workspace to output eDP-1
bindsym 2               move workspace to output HDMI-1-0

bindsym Escape          mode default
}

bindsym $mod+s            mode $mode_wm

QR code menu

set $mode_qr         " QR [i]n [o]ut [s]creen"

mode $mode_qr {
bindsym i               exec --no-startup-id "qr in"; mode default
bindsym o               exec --no-startup-id "qr out"; mode default
bindsym s               exec --no-startup-id "qr screen"; mode default

bindsym Escape          mode default
}

bindsym $mod+Shift+q      mode $mode_qr

Organization menu (using org-mode)

set $mode_org        " ORG [t]odo [k]knowledge [j]ournal [n]otes [c]omputers [l]ifestyle [T]odos con[v]ersations [a]ll"

mode $mode_org {
bindsym t               exec --no-startup-id "myemacs-float ~/wiki/todo.org", mode default
bindsym k               exec --no-startup-id "myemacs-float ~/wiki/knowledge.org", mode default
bindsym j               exec --no-startup-id "myemacs-float ~/wiki/journal.org", mode default
bindsym n               exec --no-startup-id "myemacs-float ~/wiki/notes.org", mode default
bindsym c               exec --no-startup-id "myemacs-float ~/wiki/computers.org", mode default
bindsym l               exec --no-startup-id "myemacs-float ~/wiki/lifestyle.org", mode default
bindsym Shift+t         exec --no-startup-id "dmenu_run todo", mode default
bindsym v               exec --no-startup-id "~/.config/i3/scripts/open-conversations", mode default
bindsym a               exec --no-startup-id "myemacs-float ~/wiki/index.org", mode default

bindsym Escape          mode default
}

bindsym $mod+Shift+o      mode $mode_org

Open conversations file

(with-selected-frame (make-frame `((name . "EmacsFloat")
                                   (display . ,(getenv "DISPLAY"))))
  (find-file "~/wiki/conversations.org"))
~/.config/i3/scripts/open-conversations

Audio control menu

set $mode_audio      " [+] [-] [m]ute [t]est |   [c]onnect [d]isconnect |  [T]est"

mode $mode_audio {
bindsym plus            exec --no-startup-id ~/.config/i3/scripts/mic-volume-up
bindsym minus           exec --no-startup-id ~/.config/i3/scripts/mic-volume-down
bindsym m               exec --no-startup-id ~/.config/i3/scripts/mic-mute-toggle; mode default
bindsym t               exec --no-startup-id ~/.config/i3/scripts/mic-test
bindsym c               exec --no-startup-id ~/.local/lib/haris/bluetoothctl-wrapper connect
bindsym d               exec --no-startup-id ~/.local/lib/haris/bluetoothctl-wrapper disconnect
bindsym Shift+t         exec --no-startup-id ~/.config/i3/scripts/speaker-test

bindsym Escape          mode default
bindsym F9              mode default
}

bindsym F9 mode $mode_audio

To modify the bluetooth device ID of the headphones, edit this file.

id_file=~/.local/lib/haris/bt-headphones-id

if [ ! -f "$id_file" ]; then
    ACTION="$(
      dunstify --action=default,Edit \
        --urgency=critical \
        "Bluetooth headphones ID not defined" \
        "Click this notification to fix that (wait a bit for Emacs to open)"
    )"
    [ "$ACTION" = "default" ] && myemacs -c "$id_file"
    exit 1
fi

output="$(bluetoothctl "$1" "$(cat "$id_file" | head -1)")"

notify-send 'Status' "$output"

Emacs apps menu

This is a menu that allows me to launch some utility apps I commonly use, but with an emacs interface.

set $mode_emacs      " Emacs Goodies | [e]lisp [p]ython [d]ocker [i]rc [g]it [o]ctave [⏎]term [m]an [b]luetooth [P]roced"

mode $mode_emacs {
bindsym p              exec --no-startup-id epython,    mode default
bindsym o              exec --no-startup-id eoctave,    mode default
bindsym i              exec --no-startup-id erc,        mode default
bindsym d              exec --no-startup-id edocker,    mode default
bindsym e              exec --no-startup-id elisp,      mode default
bindsym g              exec --no-startup-id magit,      mode default
bindsym Return         exec --no-startup-id eterm,      mode default
bindsym m              exec --no-startup-id eman,       mode default
bindsym b              exec --no-startup-id ebluetooth, mode default
bindsym Shift+p        exec --no-startup-id proced,     mode default
bindsym Escape         mode default
bindsym $mod+e         mode default
}

bindsym $mod+e mode $mode_emacs

Notification menu

A menu for managing notifications.

set $mode_notif      " Notifications | [c]lose [C]lose-all [h]istory [a]ction [m]enu [d]aemon [t]est"

mode $mode_notif {
bindsym a       exec dunstctl action, mode default
bindsym c       exec dunstctl close
bindsym Shift+c exec dunstctl close-all, mode default
bindsym m       exec dunstctl context, mode default
bindsym h       exec dunstctl history-pop
bindsym d       exec sh -c 'pkill "^dunst$" || o dunst'
bindsym t       exec notify-send Test
bindsym Escape  mode default
}

bindsym $mod+Shift+n mode $mode_notif

Talk menu

Menu of utilities that allow me to talk to my computer.

set $mode_talk       " Talk | [d]ictate | ChatGPT [s]peak [c]opy [b]rowser [k]ill [r]epeat"

mode $mode_talk {
bindsym d       exec <<o>> ~/.config/i3/scripts/dictate.sh, mode default
bindsym s       exec <<o>> ~/.config/i3/scripts/chatgpt.sh --speak, mode default
bindsym c       exec <<o>> ~/.config/i3/scripts/chatgpt.sh, mode default
bindsym b       exec auto-browser -P «eval-user-name()» "https://chat.openai.com/chat", \
                mode default
bindsym k       exec ~/.config/i3/scripts/kill-gpt.sh, mode default
bindsym r       exec <<o>> ~/.config/i3/scripts/gpt-repeat-action.sh
bindsym Escape  mode default
}

bindsym $mod+Shift+c mode $mode_talk

Helper alias

env HARIS_BACKGROUND_TASKS_SILENT=true o

Scripts

:header-args+: :tangle-mode (eval #o744)If you are using a custom wrapper for minigpt (it has to be called minigpt as well) that automatically resolves the API key, you can create a marker file at ~/.local/share/haris/minigpt-no-api-key-needed. The existence of this file will prevent the scripts from prompting the user for a password for pass.

Dictate

on_error() {
    close_notification
    notify-send "Dictation" "Dictation failed!" --urgency critical
    restore_mute_state
}

«prelude-all»

id="$(notify-send "Dictation" "Started" --print-id)"

text="$(minigpt textify --copy)"
convert_to_org "$text" >>"$dest_file"

close_notification

notify-send "Dictation" "Dictation copied to clipboard"
restore_mute_state

ChatGPT

on_error() {
    close_notification
    notify-send "ChatGPT" "Processing failed!" --urgency critical
    if declare -F restore_mute_state >/dev/null; then
        restore_mute_state
    fi
}

if [ -z "$HARIS_GPT_STORED_PROMPT" ]; then
    «prelude-all»
    id="$(notify-send "ChatGPT" "Speak your mind" --print-id)"

    text="$(minigpt textify)"

    id="$(notify-send "ChatGPT" "$(echo -e "Prompt recorded:\n $text")" --print-id)"
    restore_mute_state
else
    «prelude-basic»

    text="$HARIS_GPT_STORED_PROMPT"
    id="$(notify-send "ChatGPT" "$(echo -e "Prompt:\n $text")" --print-id)"
fi

response="$(echo "$text" | minigpt chat --copy)"

echo "-------"
echo "Prompt:"
echo "$text" | tee ~/.local/share/haris/gpt-last-prompt.txt
echo "-------"

printf "%q " "${BASH_SOURCE[0]}" "$@" > ~/.local/share/haris/gpt-last-cmd.txt

convert_to_org "$text" "$response" >>"$dest_file"

notify-send "ChatGPT" "Response copied to clipboard!"

if [ "$1" = "--speak" ]; then
    echo "Refined response:"
    refined_response="$(
        {
            echo "In the following text, remove all quotes to make it suitable for low quality text-to-speech software:"
            echo "$response"
        } | minigpt chat | tee /dev/stderr
    )"
    set +e
    echo "$refined_response" \
        | gtts-cli -f- \
        | mpv --speed=1.2 -
    sts="$?"
    if [ "$sts" != 0 ] && [ "$(kill -l "$sts")" != "KILL" ]; then
        set -e
        echo "$refined_response" | bash -c festival festival --tts
    fi
    close_notification
fi

Repeat last action

export HARIS_GPT_STORED_PROMPT="$(cat ~/.local/share/haris/gpt-last-prompt.txt)"
bash -c "$(cat ~/.local/share/haris/gpt-last-cmd.txt)"

Shared

:header-args+: :tangle no
set -e
trap on_error ERR
if [ ! -e ~/.local/share/haris/minigpt-no-api-key-needed ]; then
    export MINIGPT_API_KEY="$(pass show @openai/api-secret-key)"
fi
dest_file=~/wiki/conversations.org
close_notification() {
    if [ -n "$id" ]; then
        dunstify --close="$id"
    fi
}
on_term() {
    notify-send "GPT" "Successfully stopped"
}
echo "$$" > ~/.local/share/haris/gpt-pid.txt
«func:convert_to_org»
default_source="$(pactl get-default-source)"
mic_muted="$(pactl get-source-mute "$default_source" | grep 'Mute: yes' || true)"
restore_mute_state() {
    if [ -n "$mic_muted" ]; then pactl set-source-mute "$default_source" 1; fi
}

# Unmute the microphone
if [ -n "$mic_muted" ]; then
    pactl set-source-mute "$default_source" 0
    sleep 2s  # Wait for it to unmute
fi
<<prelude-basic>>
<<prelude-microphone>>
# Usage:
#   record_conversation PROMPT RESPONSE
#   record_conversation DICTATION
convert_to_org() {
    local shell_options dest_file prompt response
    shell_options="$-"
    prompt="$1"
    response="$2"
    {
        # Insert header
        if [ -z "$response" ]; then
            echo "# Dictation from $(date)"
        else
            echo "# Conversation from $(date)"
        fi
        # Insert prompt
        if [ -n "$prompt" ]; then
            echo "## User"
            echo "$prompt"
        fi
        # Insert response
        if [ -n "$response" ]; then
            echo
            echo "## Computer"
            echo "$response"
        fi
    } | pandoc --from markdown --to org |
        grep -vE '^(:PROPERTIES:|:CUSTOM_ID:|:END:)'
}
file=~/.local/share/haris/gpt-pid.txt
pkill -P $(cat "$file")
echo > "$file"

Dependencies

festival pandoc mpv
minigpt gtts

Screenshot menu

set $mode_screenshot " Screenshot | [g]ui [s]creen [f]ull [c]onfig [d]aemon"

mode $mode_screenshot {
bindsym g       exec sleep 0.1 && flameshot gui,    mode default
bindsym s       exec sleep 0.1 && flameshot screen, mode default
bindsym f       exec sleep 0.1 && flameshot full,   mode default
bindsym c       exec flameshot config, mode default
bindsym d       exec sh -c 'pkill "^flameshot$" || o flameshot'
bindsym Escape  mode default
}

bindsym $mod+Shift+s mode $mode_screenshot

Workspaces

set $ws1 "1:wrk"
set $ws2 "2:study"
set $ws3 "3:sys"
set $ws4 "4:org"
set $ws5 "5:media"
set $ws6 "6:extra"
set $ws7 "7:bg"
set $ws8 "8:vm"
set $ws9 "9:wrk1"
set $ws10 "10:wrk2"

workspace_auto_back_and_forth no

# Switch to workspace
bindsym $mod+1            workspace $ws1
bindsym $mod+2            workspace $ws2
bindsym $mod+3            workspace $ws3
bindsym $mod+4            workspace $ws4
bindsym $mod+5            workspace $ws5
bindsym $mod+6            workspace $ws6
bindsym $mod+7            workspace $ws7
bindsym $mod+8            workspace $ws8
bindsym $mod+9            workspace $ws9
bindsym $mod+0            workspace $ws10

# Move focused container to workspace
bindsym $mod+Shift+1      move  container to  workspace $ws1
bindsym $mod+Shift+2      move  container to  workspace $ws2
bindsym $mod+Shift+3      move  container to  workspace $ws3
bindsym $mod+Shift+4      move  container to  workspace $ws4
bindsym $mod+Shift+5      move  container to  workspace $ws5
bindsym $mod+Shift+6      move  container to  workspace $ws6
bindsym $mod+Shift+7      move  container to  workspace $ws7
bindsym $mod+Shift+8      move  container to  workspace $ws8
bindsym $mod+Shift+9      move  container to  workspace $ws9
bindsym $mod+Shift+0      move  container to  workspace $ws10

bindsym $mod+n            workspace next
bindsym $mod+p            workspace prev
bindsym $mod+Tab          workspace back_and_forth
<<workspaces>>

Workaround for Emacs

Sometimes an Emacs frame becomes faulty and I can’t close it. This workspace exists so I can move the frame there. The workspace is not bound to any keys so any window moved there is effectively closed.

Note: The workspace is available using “workspace next/prev” navigation, but I seldom use that so it’s fine.

bindsym $mod+apostrophe   move  container to  workspace "trash"

Status bar

bar {
font pango:Source Code Pro, Icons bold 9.3
position bottom
mode hide
modifier $mod
workspace_buttons yes
strip_workspace_numbers yes

status_command i3status | ~/.config/i3status/custom-script.py
# TODO status_command i3status
# Trays are bloat, but they are sometimes necessary
tray_output primary

i3bar_command i3bar --transparency
colors {
# class             border    backgr.   text
focused_workspace   $col_b_b  $col_b_b  $col_n_k
inactive_workspace  $col_a_k  $col_a_k  $col_n_m
urgent_workspace    $col_n_y  $col_n_y  $col_n_k

background          $col_a_k
statusline          $col_b_c
separator           $col_a_m
}
}

i3status

Local configuration

Make a symlink at ~/.local/share/haris/local.cpu-temperature-0, pointing to your machine-specific file that provides the CPU temperature to show.

Custom script

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# This script is a simple wrapper which prefixes each i3status line with custom
# information. It is a python reimplementation of:
# http://code.stapelberg.de/git/i3status/tree/contrib/wrapper.pl
#
# To use it, ensure your ~/.i3status.conf contains this line:
#     output_format = "i3bar"
# in the 'general' section.
# Then, in your ~/.i3/config, use:
#     status_command i3status | ~/i3status/contrib/wrapper.py
# In the 'bar' section.
#
# In its current version it will display the cpu frequency governor, but you
# are free to change it to display whatever you like, see the comment in the
# source code below.
#
# © 2012 Valentin Haenel <[email protected]>
#
# This program is free software. It comes without any warranty, to the extent
# permitted by applicable law. You can redistribute it and/or modify it under
# the terms of the Do What The Fuck You Want To Public License (WTFPL), Version
# 2, as published by Sam Hocevar. See http://sam.zoy.org/wtfpl/COPYING for more
# details.
#
# This file has been modified by Haris Gušić <[email protected]>

import sys
import json
import subprocess as sp
import re

def print_line(message):
    """ Non-buffered printing to stdout. """
    sys.stdout.write(message + '\n')
    sys.stdout.flush()

def read_line():
    """ Interrupted respecting reader for stdin. """
    # try reading a line, removing any extra whitespace
    try:
        line = sys.stdin.readline().strip()
        # i3status sends EOF, or an empty line
        if not line:
            sys.exit(3)
        return line
    # exit on ctrl-c
    except KeyboardInterrupt:
        sys.exit()

def run(*args, **kwargs):
    return sp.run(*args, shell=True, stdout=sp.PIPE, encoding='utf-8', check=False, **kwargs)

def get_mic_volume_widget():
    source = run('pactl get-default-source').stdout.strip()
    volume_output = run(f'pactl get-source-volume {source}').stdout
    volume = re.search(r'(\d+\.?\d*%)', volume_output)[1]
    is_muted = 'yes' in run(f'pactl get-source-mute {source}').stdout
    if not is_muted:
        return {
            'full_text': f' {volume}',
            'name': 'volume_mic',
            'separator': False,
            'color': '#d098ff',
        }

def get_vpn_widget():
    active = run('systemctl is-active protonvpn').returncode == 0
    if active:
        return {
            'full_text': '',
            'name': 'vpn',
            'separator': False,
            'color': '#99a3ff'
        }

if __name__ == '__main__':
    lines = sp.Popen(['i3status'], stdout=sp.PIPE, universal_newlines=True, encoding='utf-8').stdout
    # Skip the first line which contains the version header.
    print_line(next(lines))

    # The second line contains the start of the infinite array.
    print_line(next(lines))

    for line in lines:
        prefix = ''
        # ignore comma at start of lines
        if line.startswith(','):
            line, prefix = line[1:], ','

        j = json.loads(line)
        # insert information into the start of the json, but could be anywhere
        # CHANGE THIS LINE TO INSERT SOMETHING ELSE
        vpn_widget = get_vpn_widget()
        volume_widget = get_mic_volume_widget()
        if vpn_widget:
            j.insert((3 if volume_widget else 2), vpn_widget)
        if volume_widget:
            j.insert(0, volume_widget)

        # and echo back new encoded json
        print_line(prefix + json.dumps(j))

    i3status.stdout.close()
    sys.exit(i3status.wait())

Configuration

:header-args+: :tangle (haris/tangle-home “.config/i3status/config”)
# It is important that this file is edited as UTF-8.
# The following line should contain a sharp s:
# ß

general {
output_format   = "i3bar"
colors          = true
color_good      = "#98eb98"
color_degraded  = "#f2b374"
color_bad       = "#ff5555"
markup          = "pango"
interval        = 1
}

order += "volume master"
order += "wireless _first_"
order += "ethernet _first_"
order += "battery all"
order += "disk /"
order += "disk /home"
order += "memory"
order += "cpu_usage"
order += "cpu_temperature 0"
order += "tztime localdate"
order += "tztime localtime"

volume master {
format         = "  %volume "
format_muted   = "  %volume "
}

wireless _first_ {
format_up      = "  %quality"
format_down    = "  "
separator      = false
}

ethernet _first_ {
format_up      = "  %speed "
format_down    = ""
}

battery all {
format_down     = ""
status_full     = "<span color='#98eb98'> </span>"
status_bat      = " "
status_chr      = "<span color='#f2b374'></span>"
format          = " %status %percentage %remaining "
threshold_type  = "percentage"
low_threshold   = "30"
}

disk "/" {
format = " <span color='#d098ff'></span> <span color='#f298c3'>/</span> %avail "
prefix_type           = binary
low_threshold         = 50
threshold_type        = "percentage_free"
separator             = false
separator_block_width = -6
}

disk "/home" {
format                = " <span color='#f298c3'></span> %avail "
prefix_type           = binary
low_threshold         = 35
threshold_type        = "percentage_free"
separator             = false
separator_block_width = -6
}

memory {
format                = "  %available"
threshold_degraded    = "2G"
threshold_critical    = "1G"
format_degraded       = " M: %available "
}

cpu_usage {
format                = " %usage "
max_threshold         = "90"
degraded_threshold    = "60"
separator             = false
separator_block_width = -2
}

cpu_temperature 0 {
format                 = "<span color='#6980fa'></span> %degrees °C"
format_above_threshold = " <span color='#ff8c8c'></span> %degrees °C "
path                   = "~/.local/share/haris/local.cpu-temperature-0"
max_threshold          = 70
}

tztime localdate {
format = " <span color='#d098ff'> %Y-%m-%d</span>"
}

tztime localtime {
format = "<span color='#f298c3'>  %H:%M:%S</span> "
}

Helpers

(let ((file (expand-file-name "~/.local/share/haris/local.cpu-temperature-0"))
      (target-file nil))
  ;; Check if the file already exists, and prompt with yes/no
  (when (or (not (file-exists-p file))
            (y-or-n-p "Symbolic link already exists. Overwrite? "))
    ;; Prompt for a target file
    (setq target-file (read-file-name
                       "Choose a file that reports CPU temperature: "
                       "/sys/"))
    (when target-file
      (make-symbolic-link target-file file t)
      (message
       "Symbolic link created.
Verify that i3status is now displaying the correct temperature."))))

Window rules

for_window [instance="Float"      class="Alacritty"]  floating enable
for_window [instance="Float"      class="Alacritty"]  move scratchpad
for_window [instance="Float"      class="Alacritty"]  scratchpad show

for_window [window_role="Float"   class="Gvim"]       floating enable
for_window [floating              class="Gvim"]       $resize1

for_window [instance="Background" class="Alacritty"]  floating enable
for_window [instance="Background" class="Alacritty"]  move scratchpad
for_window [instance="Background" class="Alacritty"]  scratchpad show

for_window [class="Alacritty" floating]               $resize1
for_window [class="Alacritty" floating]               move position center

for_window [class="flameshot"]                        floating enable

# I use feh to display a QR code, so I make it floating
for_window [class="feh"]                              floating enable
for_window [class="feh" floating]                     move position center

# Display command too sometimes
for_window [class="Display"]                          floating enable
for_window [class="Display" floating]                 move position center

# Matplotlib plots
for_window [class="matplotlib"]                       floating enable
for_window [class="matplotlib" floating]              move position center

# Emacs config editor
for_window [title="EmacsFloat"]                       floating enable
for_window [title="EmacsFloat"]                       move scratchpad
for_window [title="EmacsFloat"]                       scratchpad show

# Emacs man page viewer
for_window [title="EmacsMan"]                         floating enable
for_window [title="EmacsMan" floating]                $resize1
for_window [title="EmacsMan"]                         move scratchpad
for_window [title="EmacsMan"]                         scratchpad show

# Emacs defaults
for_window [class="Emacs" \
            title="^(?!EmacsMan$).*" \
            floating]                                 $resize2; move position center

# Octave plots
for_window [class="GNU Octave"]                       floating enable
for_window [class="GNU Octave" floating]              move position center
for_window [class="GNU Octave"]                       move scratchpad
for_window [class="GNU Octave"]                       scratchpad show

# Miscellaneous
for_window [class="SpeedCrunch"]                      floating enable
for_window [class="SpeedCrunch" floating]             $resize1
for_window [class="SpeedCrunch" floating]             move position center
for_window [class="VirtualBox" \
            title=".*(Settings|Preferences).*"]       floating enable

for_window [instance="zbar" class="zbar"]             floating enable

for_window [class="Typora"]                           floating enable
for_window [class="Typora" floating]                  move position center

assign [class="Gimp.*"] number $ws6
assign [class="VirtualBox Manager"] number $ws8

Compositor

:header-args+: :tangle (haris/tangle-home “.config/picom.conf”)I use picom as my compositor with the i3 window manager.

General

# Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`.
# `xrender` is the default one.
backend = "glx";

# Enable/disable VSync.
# Having it on causes lag together with the glx backend
vsync = false

# Enable remote control via D-Bus.
dbus = false

# Try to detect WM windows (a non-override-redirect window with no
# child that has 'WM_STATE') and mark them as active.
mark-wmwin-focused = true;

# Mark override-redirect windows that doesn't have a child window with 'WM_STATE' focused.
mark-ovredir-focused = true;

# Detect '_NET_WM_OPACITY' on client windows, useful for window managers
# not passing '_NET_WM_OPACITY' of client windows to frame windows.
detect-client-opacity = true;

detect-transient = true

# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
# group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if
# detect-transient is enabled, too.
detect-client-leader = true

log-level = "warn";

wintypes:
{
  tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
  dock = { shadow = false; }
  dnd = { shadow = false; }
  popup_menu = { opacity = 0.9; }
  dropdown_menu = { opacity = 0.8; }
};

Transparency rules

frame-opacity = 0.7;
inactive-opacity-override = true;
inactive-opacity = 0.95

opacity-rule = [
  "95:class_g = 'dmenu'",
  "95:class_g = 'Alacritty'",
  "100:class_g = 'Zathura'",
  "95:class_g = 'Gvim'",
  "100:class_g = 'firefox'",
  "94:class_g = 'emacs'",
  "94:class_g = 'Emacs'"
];

blur:
{
  method = "gaussian";
  size = 8;
  deviation = 5;
}

Fading

# Fade windows in/out when opening/closing and when opacity changes,
#  unless no-fading-openclose is used.
fading = true

# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
fade-in-step = 0.005;

# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
fade-out-step = 0.005;

# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
fade-delta = 1

# Specify a list of conditions of windows that should not be faded.
fade-exclude = []

# Do not fade on window open/close.
no-fading-openclose = false

# Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc.
no-fading-destroyed-argb = false

Dependencies

i3

sudo pacman -S i3status ttf-font-awesome
paru -S dmenu-height autotiling i3-balance-workspace

sway

sudo pacman -S sway
paru -S swhkd-git

Local variables

(user-login-name)