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.
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)))))
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
# 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>>
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>>
focus_follows_mouse no
popup_during_fullscreen leave_fullscreen
for_window [instance="Float" class="Alacritty"] floating enable
*.*.font: Ubuntu Mono
: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
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
# 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>>
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
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
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 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
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
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
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
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
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
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
(with-selected-frame (make-frame `((name . "EmacsFloat")
(display . ,(getenv "DISPLAY"))))
(find-file "~/wiki/conversations.org"))
~/.config/i3/scripts/open-conversations
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"
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
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
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
env HARIS_BACKGROUND_TASKS_SILENT=true o
: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
.
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
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
export HARIS_GPT_STORED_PROMPT="$(cat ~/.local/share/haris/gpt-last-prompt.txt)"
bash -c "$(cat ~/.local/share/haris/gpt-last-cmd.txt)"
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"
festival pandoc mpv
minigpt gtts
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
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>>
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"
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
}
}
Make a symlink at ~/.local/share/haris/local.cpu-temperature-0, pointing to your machine-specific file that provides the CPU temperature to show.
#!/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())
# 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> "
}
(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."))))
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
:header-args+: :tangle (haris/tangle-home “.config/picom.conf”)I use picom as my compositor with the i3 window manager.
# 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; }
};
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;
}
# 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
sudo pacman -S i3status ttf-font-awesome
paru -S dmenu-height autotiling i3-balance-workspace
sudo pacman -S sway
paru -S swhkd-git
(user-login-name)