swaywm: add dotfiles/scripts
This commit is contained in:
parent
49f94bb424
commit
9d26abbb3a
22 changed files with 1168 additions and 0 deletions
14
outerheaven.init3.home/.config/sway/sway/10-variables.conf
Normal file
14
outerheaven.init3.home/.config/sway/sway/10-variables.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
### Variables
|
||||||
|
# key bindings, preferred applications
|
||||||
|
# NOTE: Mod1 = alt, Mod4 = Super key
|
||||||
|
set $mod Mod4
|
||||||
|
#set $mod Mod1
|
||||||
|
set $left Left
|
||||||
|
set $down Down
|
||||||
|
set $up Up
|
||||||
|
set $right Right
|
||||||
|
|
||||||
|
set $menu exec fuzzel --log-level=error --log-no-syslog
|
||||||
|
set $term kitty --directory $HOME
|
||||||
|
#set $term tilix -w $HOME
|
||||||
|
#set $term alacritty --working-directory ${HOME}
|
80
outerheaven.init3.home/.config/sway/sway/11-colors.conf
Normal file
80
outerheaven.init3.home/.config/sway/sway/11-colors.conf
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# Constants
|
||||||
|
# note: black was claimed as 'transparent' in the source where this was found
|
||||||
|
#
|
||||||
|
# nord colors from here: https://www.nordtheme.com/docs/colors-and-palettes
|
||||||
|
set {
|
||||||
|
$white #ffffff
|
||||||
|
$cyan #00afd7
|
||||||
|
$acqua #00d787
|
||||||
|
$wine #72003e
|
||||||
|
$magenta #af005f
|
||||||
|
$orange #ff8700
|
||||||
|
$silver #e4e4e4
|
||||||
|
$elegant #1b1d1e
|
||||||
|
$black #000000
|
||||||
|
$nord0 #2e3440
|
||||||
|
$nord1 #3b4252
|
||||||
|
$nord2 #434c5e
|
||||||
|
$nord3 #4c566a
|
||||||
|
$nord4 #d8dee9
|
||||||
|
$nord5 #e5e9f0
|
||||||
|
$nord6 #eceff4
|
||||||
|
$nord7 #8fbcbb
|
||||||
|
$nord8 #88c0d0
|
||||||
|
$nord9 #81a1c1
|
||||||
|
$nord10 #5e81ac
|
||||||
|
$nord11 #bf616a
|
||||||
|
$nord12 #d08770
|
||||||
|
$nord13 #ebcb8b
|
||||||
|
$nord14 #a3be8c
|
||||||
|
$nord15 #b48ead
|
||||||
|
}
|
||||||
|
|
||||||
|
# format:
|
||||||
|
# client.<class> <border> <background> <text> [<indicator> [<child_border>]]
|
||||||
|
#
|
||||||
|
# The default colors are:
|
||||||
|
# ┌──────────────────┬─────────┬────────────┬─────────┬───────────┬──────────────┐
|
||||||
|
# │ class │ border │ background │ text │ indicator │ child_border │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │background │ n/a │ #ffffff │ n/a │ n/a │ n/a │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │focused │ #4c7899 │ #285577 │ #ffffff │ #2e9ef4 │ #285577 │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │focused_inactive │ #333333 │ #5f676a │ #ffffff │ #484e50 │ #5f676a │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │focused_tab_title │ #333333 │ #5f676a │ #ffffff │ n/a │ n/a │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │unfocused │ #333333 │ #222222 │ #888888 │ #292d2e │ #222222 │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │urgent │ #2f343a │ #900000 │ #ffffff │ #900000 │ #900000 │
|
||||||
|
# ├──────────────────┼─────────┼────────────┼─────────┼───────────┼──────────────┤
|
||||||
|
# │placeholder │ #000000 │ #0c0c0c │ #ffffff │ #000000 │ #0c0c0c │
|
||||||
|
# └──────────────────┴─────────┴────────────┴─────────┴───────────┴──────────────┘
|
||||||
|
|
||||||
|
# Dracula colors:
|
||||||
|
# client.focused #6272A4 #6272A4 #F8F8F2 #6272A4 #6272A4
|
||||||
|
# client.focused_inactive #44475A #44475A #F8F8F2 #44475A #44475A
|
||||||
|
# client.unfocused #282A36 #282A36 #BFBFBF #282A36 #282A36
|
||||||
|
#
|
||||||
|
# Garuda (distribution) colors:
|
||||||
|
# client.focused #608080 $nord2 $nord6 $nord7 $nord8
|
||||||
|
# client.focused_inactive #608080 $nord0 $nord4 $nord3 $nord3
|
||||||
|
# client.unfocused #608080 $nord0 $nord4 $nord3 $nord3
|
||||||
|
# client.urgent #608080 $nord13 $nord0 $nord7 $nord13
|
||||||
|
|
||||||
|
|
||||||
|
# last-used:
|
||||||
|
# Garuda but tweaked: diff borders, nord alert/yellow
|
||||||
|
#client.background $black
|
||||||
|
#client.focused $nord2 $nord2 $nord6 $nord7 $nord8
|
||||||
|
#client.focused_inactive $nord3 $nord0 $nord4 $nord3 $nord3
|
||||||
|
#client.unfocused $nord3 $nord0 $nord4 $nord3 $nord3
|
||||||
|
#client.urgent $nord12 $nord13 $nord0 $nord7 $nord13
|
||||||
|
|
||||||
|
# xfce kind of look, from https://github.com/ayamir/nord-and-light/blob/master/.config/sway/config
|
||||||
|
# client.focused $nord10 $nord10 $nord1 $nord1 $nord10
|
||||||
|
# client.focused_inactive $nord5 $nord5 $nord1 $nord5 $nord2
|
||||||
|
# client.unfocused $nord5 $nord5 $nord1 $nord5 $nord2
|
||||||
|
# client.urgent #7c818c #bf616a $nord1 #900000 #bf616a
|
||||||
|
# client.placeholder $black #0c0c0c $nord1 $black #0c0c0c
|
55
outerheaven.init3.home/.config/sway/sway/12-displays.conf
Normal file
55
outerheaven.init3.home/.config/sway/sway/12-displays.conf
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
## workspace setup
|
||||||
|
workspace 1 output $MON_CENTER
|
||||||
|
workspace 2 output $MON_RIGHT
|
||||||
|
workspace 3 output $MON_LEFT
|
||||||
|
workspace 4 output $MON_RIGHT
|
||||||
|
|
||||||
|
## configure displays
|
||||||
|
# use the following page to identify the correct subpixel rendering methods
|
||||||
|
# http://www.lagom.nl/lcd-test/subpixel.php
|
||||||
|
# query display info: 'swaymsg -t get_outputs'
|
||||||
|
# important notes:
|
||||||
|
# adaptive sync will cause some displays to flicker
|
||||||
|
# 10-bit color breaks sharing of *that* display with 'xdg-desktop-portal-wlr'
|
||||||
|
# negative coords will make menus and some XWayland things behave poorly: base left-most display on 0,0
|
||||||
|
|
||||||
|
# layout with 4k/center display at native res/scaling:
|
||||||
|
output $MON_CENTER {
|
||||||
|
position 1440 400
|
||||||
|
resolution 3840x2160@159.975Hz
|
||||||
|
subpixel rgb
|
||||||
|
# render_bit_depth 10
|
||||||
|
# adaptive_sync on
|
||||||
|
# max_render_time 6
|
||||||
|
}
|
||||||
|
|
||||||
|
# trying left monitor with lower refresh rate, it's a little flaky going back to gdm
|
||||||
|
output $MON_LEFT {
|
||||||
|
position 0 0
|
||||||
|
resolution 2560x1440@165.000Hz
|
||||||
|
subpixel vrgb
|
||||||
|
transform 270
|
||||||
|
# adaptive_sync on
|
||||||
|
# max_render_time 7
|
||||||
|
}
|
||||||
|
|
||||||
|
output $MON_RIGHT {
|
||||||
|
position 5280 400
|
||||||
|
resolution 3840x2160@159.975Hz
|
||||||
|
subpixel rgb
|
||||||
|
# max_render_time 6
|
||||||
|
}
|
||||||
|
|
||||||
|
# for mouse tracking in XWayland/games, ensure the center display is marked primary
|
||||||
|
exec_always xrandr --output $MON_CENTER --primary
|
||||||
|
|
||||||
|
# enable adaptive sync on capable displays - incapable are gracefully handled
|
||||||
|
# output * adaptive_sync on
|
||||||
|
# enabling here warrants enabling in '/etc/sway/sddm-greeter.config' as well; remember ~/git/workstation/ copy (if still relevant)
|
||||||
|
|
||||||
|
# random-wallpaper script; using 'exec_always' allows for a new wallpaper on each config load
|
||||||
|
exec_always ~/.config/sway/scripts/wallpaper.py --select unique ~/Pictures/wallpapers/mac
|
||||||
|
# change '--mode single' to '--mode multiple' for a unique image per display
|
||||||
|
|
||||||
|
# don't let floating windows get ridiculously large
|
||||||
|
floating_maximum_size 1920 x 1080
|
67
outerheaven.init3.home/.config/sway/sway/13-assignments.conf
Normal file
67
outerheaven.init3.home/.config/sway/sway/13-assignments.conf
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
## config for assigning applications to specific workspaces/displays (see 'display.cfg')
|
||||||
|
# see 'man 5 sway' CRITERIA for qualifying criteria
|
||||||
|
|
||||||
|
# ensure unigine benchmarks run on main display
|
||||||
|
assign [title="Unigine*" app_id=""] workspace 1
|
||||||
|
assign [title="Steam Sign In" class="^(?i)Steam"] workspace 1
|
||||||
|
|
||||||
|
#assign [app_id="work"] workspace work
|
||||||
|
#assign [class="Virt-manager"] workspace control
|
||||||
|
#assign [app_id="control"] workspace control
|
||||||
|
assign [app_id="control"] workspace 3
|
||||||
|
assign [app_id="scratch"] workspace 3
|
||||||
|
|
||||||
|
# workspace 1/center/main monitor assignment
|
||||||
|
# assign the pcsx2 game window here - main window goes to ws3 below
|
||||||
|
assign [class="PCSX2" title="^.*(nterlaced|rogressive).*$"] workspace 1
|
||||||
|
|
||||||
|
# workspace 2/right monitor assignment
|
||||||
|
assign [app_id="kitty" title="stats"] workspace 2
|
||||||
|
assign [app_id="nethogs"] workspace 2
|
||||||
|
assign [app_id="btm"] workspace 2
|
||||||
|
assign [app_id="ticker"] workspace 2
|
||||||
|
assign [app_id="deluge"] workspace 2
|
||||||
|
assign [app_id="Trezor Suite"] workspace 2
|
||||||
|
assign [title="Cryptowatch"] workspace 2
|
||||||
|
assign [app_id="mpv"] workspace 2
|
||||||
|
assign [app_id="CiscoCollabHost"] workspace 2
|
||||||
|
assign [app_id="audacious"] workspace 2
|
||||||
|
assign [app_id="com.belmoussaoui.Authenticator"] workspace 2
|
||||||
|
assign [app_id="gnome-system-monitor"] workspace 2
|
||||||
|
assign [app_id="imv"] workspace 2
|
||||||
|
assign [app_id="org.strawberrymusicplayer.strawberry"] workspace 2
|
||||||
|
assign [app_id="rhythmbox"] workspace 2
|
||||||
|
assign [app_id="python" title="^(?i)vorta.*$"] workspace 2
|
||||||
|
# vscodium, weird name - commented out, like it to go wherever when not (fractional) scaling 4k display on wayland
|
||||||
|
#assign [app_id="codium-url-handler"] workspace 2
|
||||||
|
|
||||||
|
# workspace 3/left (vertical) monitor assignment
|
||||||
|
assign [app_id="pavucontrol"] workspace 3
|
||||||
|
assign [app_id="thunderbird"] workspace 3
|
||||||
|
assign [app_id="evolution"] workspace 3
|
||||||
|
assign [app_id="org.kde.quassel"] workspace 3
|
||||||
|
assign [app_id="[Dd]iscord"] workspace 3
|
||||||
|
assign [class="[Dd]iscord"] workspace 3
|
||||||
|
# two ways for Slack, xwayland and native wayland - also covers non-native app (eg: browser)
|
||||||
|
assign [app_id="Slack"] workspace 3
|
||||||
|
assign [title="Slack.*"] workspace 3
|
||||||
|
assign [app_id="Element"] workspace 3
|
||||||
|
assign [title="Element.*" app_id=""] workspace 3
|
||||||
|
assign [class="[Ss]ignal"] workspace 3
|
||||||
|
assign [app_id="[Ss]ignal"] workspace 3
|
||||||
|
assign [title="Wine System Tray" class="steam_app_.*$"] workspace 3
|
||||||
|
# move main pcsx2 window and the log to the third/left-most workspace
|
||||||
|
assign [class="PCSX2" title="PCSX2 .*$"] workspace 3
|
||||||
|
assign [class="PCSX2" title="PCSX2 Program Log"] workspace 3
|
||||||
|
assign [app_id="transmission-gtk"] workspace 3
|
||||||
|
|
||||||
|
# move work-related firefox windows to 'work' workspace, #4
|
||||||
|
assign [app_id="work-firefox"] workspace 4
|
||||||
|
|
||||||
|
# move steam windows around
|
||||||
|
# want it in the default workspace of another display
|
||||||
|
# on xwayland Steam windows render at 1 FPS if *all* windows aren't visible
|
||||||
|
# fixed in 2023 beta update
|
||||||
|
# assign [title="Steam Big Picture Mode" class="[Ss]team"] workspace 1
|
||||||
|
assign [title="Friends List.*" class="[Ss]team"] workspace 3
|
||||||
|
# assign [title="[Ss]team" class="[Ss]team"] workspace 2
|
33
outerheaven.init3.home/.config/sway/sway/14-input.conf
Normal file
33
outerheaven.init3.home/.config/sway/sway/14-input.conf
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
## input cfg; eg: mouse, keyboard, touchpad
|
||||||
|
# see `man 5 sway-input` for more information
|
||||||
|
# query: swaymsg -t get_inputs
|
||||||
|
|
||||||
|
# specific devices are used in an attempt to fix Firefox crashing on config reloads
|
||||||
|
# presumably due to it capturing a bunch of devices that aren't related
|
||||||
|
# alts:
|
||||||
|
# set $mouse type:pointer
|
||||||
|
# set $keyboard type:keyboard
|
||||||
|
|
||||||
|
# make keyboard go faaast, turn on numlock, and set US layout
|
||||||
|
# input "9456:8346:Metadot_-_Das_Keyboard_DK5QS" {
|
||||||
|
input "9456:8353:Das_Keyboard_Das_Keyboard_6_Pro" {
|
||||||
|
repeat_delay 250
|
||||||
|
repeat_rate 40
|
||||||
|
xkb_numlock enabled
|
||||||
|
xkb_layout us
|
||||||
|
}
|
||||||
|
|
||||||
|
input "1133:16519:Logitech_G903_LS" {
|
||||||
|
# disable mouse acceleration (enabled by default; to set it manually, use "adaptive" instead of "flat")
|
||||||
|
accel_profile "flat"
|
||||||
|
# set mouse sensitivity (between -1 and 1)
|
||||||
|
pointer_accel 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
# try to cater to laptops with touchpads
|
||||||
|
input "type:touchpad" {
|
||||||
|
left_handed enabled
|
||||||
|
tap enabled
|
||||||
|
natural_scroll disabled
|
||||||
|
dwt enabled
|
||||||
|
}
|
147
outerheaven.init3.home/.config/sway/sway/15-keybinds.conf
Normal file
147
outerheaven.init3.home/.config/sway/sway/15-keybinds.conf
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
### Key bindings
|
||||||
|
#
|
||||||
|
# Basics:
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Start a terminal
|
||||||
|
bindsym $mod+Return exec $term
|
||||||
|
bindsym $mod+KP_Enter exec $term
|
||||||
|
|
||||||
|
# Kill focused window
|
||||||
|
bindsym $mod+Shift+q kill
|
||||||
|
|
||||||
|
# Start your launcher
|
||||||
|
bindsym $mod+d exec $menu
|
||||||
|
|
||||||
|
# Reload the configuration file
|
||||||
|
bindsym $mod+Shift+c reload
|
||||||
|
|
||||||
|
# logout
|
||||||
|
bindsym $mod+Shift+e exec swaynag -t warning -m 'Do you really want to log out?' -b 'Yes, exit sway' 'swaymsg exit'
|
||||||
|
|
||||||
|
# lock screen - alt+l and super+l
|
||||||
|
bindsym --no-repeat Mod1+l exec ${HOME}/.config/sway/scripts/locker.py
|
||||||
|
bindsym --no-repeat Mod4+l exec ${HOME}/.config/sway/scripts/locker.py
|
||||||
|
#
|
||||||
|
# Moving around:
|
||||||
|
#
|
||||||
|
# Move focus around
|
||||||
|
bindsym $mod+$left focus left
|
||||||
|
bindsym $mod+$down focus down
|
||||||
|
bindsym $mod+$up focus up
|
||||||
|
bindsym $mod+$right focus right
|
||||||
|
# Move windows around
|
||||||
|
bindsym $mod+Shift+$left move left
|
||||||
|
bindsym $mod+Shift+$down move down
|
||||||
|
bindsym $mod+Shift+$up move up
|
||||||
|
bindsym $mod+Shift+$right move right
|
||||||
|
# Cycle between workspaces
|
||||||
|
bindsym $mod+control+Right workspace next
|
||||||
|
bindsym $mod+control+Left workspace prev
|
||||||
|
|
||||||
|
#
|
||||||
|
# Workspaces:
|
||||||
|
#
|
||||||
|
# Switch to workspace
|
||||||
|
bindsym $mod+1 workspace 1
|
||||||
|
bindsym $mod+2 workspace 2
|
||||||
|
bindsym $mod+3 workspace 3
|
||||||
|
bindsym $mod+4 workspace 4
|
||||||
|
bindsym $mod+5 workspace 5
|
||||||
|
bindsym $mod+6 workspace 6
|
||||||
|
bindsym $mod+7 workspace 7
|
||||||
|
bindsym $mod+8 workspace 8
|
||||||
|
bindsym $mod+9 workspace 9
|
||||||
|
bindsym $mod+0 workspace 10
|
||||||
|
# Move focused container to workspace
|
||||||
|
bindsym $mod+Shift+1 move container to workspace 1
|
||||||
|
bindsym $mod+Shift+2 move container to workspace 2
|
||||||
|
bindsym $mod+Shift+3 move container to workspace 3
|
||||||
|
bindsym $mod+Shift+4 move container to workspace 4
|
||||||
|
bindsym $mod+Shift+5 move container to workspace 5
|
||||||
|
bindsym $mod+Shift+6 move container to workspace 6
|
||||||
|
bindsym $mod+Shift+7 move container to workspace 7
|
||||||
|
bindsym $mod+Shift+8 move container to workspace 8
|
||||||
|
bindsym $mod+Shift+9 move container to workspace 9
|
||||||
|
bindsym $mod+Shift+0 move container to workspace 10
|
||||||
|
# Note: workspaces can have any name you want, not just numbers.
|
||||||
|
# We just use 1-10 as the default.
|
||||||
|
#
|
||||||
|
# Layout stuff:
|
||||||
|
#
|
||||||
|
# You can "split" the current object of your focus with
|
||||||
|
# $mod+b or $mod+v, for horizontal and vertical splits
|
||||||
|
# respectively.
|
||||||
|
bindsym $mod+b splith
|
||||||
|
bindsym $mod+v splitv
|
||||||
|
|
||||||
|
# Switch the current container between different layout styles
|
||||||
|
bindsym $mod+s layout stacking
|
||||||
|
bindsym $mod+w layout tabbed
|
||||||
|
bindsym $mod+e layout toggle split
|
||||||
|
|
||||||
|
# Make the current focus fullscreen
|
||||||
|
bindsym $mod+f fullscreen
|
||||||
|
|
||||||
|
# Toggle the current focus between tiling and floating mode
|
||||||
|
bindsym $mod+Shift+space floating toggle
|
||||||
|
|
||||||
|
# Swap focus between the tiling area and the floating area
|
||||||
|
bindsym $mod+space focus mode_toggle
|
||||||
|
|
||||||
|
# Move focus to the parent container
|
||||||
|
bindsym $mod+a focus parent
|
||||||
|
#
|
||||||
|
# Scratchpad:
|
||||||
|
#
|
||||||
|
# Sway has a "scratchpad", which is a bag of holding for windows.
|
||||||
|
# You can send windows there and get them back later.
|
||||||
|
|
||||||
|
# Move the currently focused window to the scratchpad
|
||||||
|
bindsym $mod+Shift+minus move scratchpad
|
||||||
|
|
||||||
|
# Show the next scratchpad window or hide the focused scratchpad window.
|
||||||
|
# If there are multiple scratchpad windows, this command cycles through them.
|
||||||
|
bindsym $mod+minus scratchpad show
|
||||||
|
#
|
||||||
|
# Resizing containers:
|
||||||
|
#
|
||||||
|
mode "resize" {
|
||||||
|
# left will shrink the containers width
|
||||||
|
# right will grow the containers width
|
||||||
|
# up will shrink the containers height
|
||||||
|
# down will grow the containers height
|
||||||
|
bindsym $left resize shrink width 20px
|
||||||
|
bindsym $down resize grow height 20px
|
||||||
|
bindsym $up resize shrink height 20px
|
||||||
|
bindsym $right resize grow width 20px
|
||||||
|
|
||||||
|
# Return to default mode
|
||||||
|
bindsym Return mode "default"
|
||||||
|
bindsym Escape mode "default"
|
||||||
|
}
|
||||||
|
bindsym $mod+r mode "resize"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Screenshots:
|
||||||
|
#
|
||||||
|
bindsym --no-repeat Print exec "~/.config/sway/scripts/screenshot.py region"
|
||||||
|
bindsym --no-repeat Shift+Print exec "~/.config/sway/scripts/screenshot.py window"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Media Control:
|
||||||
|
# note: only coverage for the two media keys and knob on the Das 5QS keyboard
|
||||||
|
# for the rest, see:
|
||||||
|
# https://wiki.archlinux.org/title/Sway#Custom_keybindings
|
||||||
|
# first, set up some aliases for brevity
|
||||||
|
# ... expect title/body to follow as two more strings
|
||||||
|
set $media-notify exec notify-send --urgency normal --expire-time 1000 --transient
|
||||||
|
bindsym --locked XF86AudioPlay $media-notify --icon /usr/share/icons/breeze-dark/status/22/media-playback-playing.svg 'Media Playback' 'Play state toggled'; exec playerctl play-pause
|
||||||
|
bindsym --locked XF86AudioNext $media-notify --icon /usr/share/icons/breeze-dark/actions/32/go-next.svg 'Media Playback' 'Skipped to the next track'; exec playerctl next
|
||||||
|
# bindsym --locked XF86AudioRaiseVolume exec ~/.config/sway/scripts/volume.py raise 1
|
||||||
|
# bindsym --locked XF86AudioLowerVolume exec ~/.config/sway/scripts/volume.py lower 1
|
||||||
|
# weird problem with knobs on the DAC and keyboard competing for this keybind, even though the DAC should only manage itself...
|
||||||
|
# instead, bind them to exec echo to prevent '438u' spam
|
||||||
|
bindsym XF86AudioRaiseVolume nop
|
||||||
|
bindsym XF86AudioLowerVolume nop
|
||||||
|
bindsym --locked XF86AudioMute $media-notify --icon /usr/share/icons/breeze-dark/status/22/audio-volume-muted.svg 'Volume adjustment' 'Mute toggled'; exec pactl set-sink-mute @DEFAULT_SINK@ toggle
|
84
outerheaven.init3.home/.config/sway/sway/16-for_windows.conf
Normal file
84
outerheaven.init3.home/.config/sway/sway/16-for_windows.conf
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
## per-window preferences
|
||||||
|
# eg: title format, floating, opacity
|
||||||
|
#
|
||||||
|
# add app_id to window title
|
||||||
|
# styling requires pango be enabled in the font of the main config
|
||||||
|
# for_window [title="."] title_format "<b>%title</b> (%app_id)"
|
||||||
|
#
|
||||||
|
# very-generic titles will benefit from anchors (^...$) - it's effectively a wildcard otherwise
|
||||||
|
#
|
||||||
|
# for_window [shell="xdg_shell"] title_format "<b>%title</b> <i>(%app_id)</i>"
|
||||||
|
# commented out the above to only expose xwayland things and reduce repetition in titles
|
||||||
|
for_window [shell="xwayland"] title_format "<span> %title <i>[X11]</i> </span>"
|
||||||
|
|
||||||
|
# added 3/17/2023: enable CSDs for floating windows - client side decorations
|
||||||
|
for_window [floating] border csd; sticky enable
|
||||||
|
|
||||||
|
# make polkit, file managers, and other dialog prompts float
|
||||||
|
# - no longer a fullscreen-ish window
|
||||||
|
# - 'sticky'; follows across workspaces
|
||||||
|
# - has csd for potential shadows/etc
|
||||||
|
# gnome polkit segfaults sometimes when needed (eg: stopping a service as regular user), don't use it
|
||||||
|
for_window [app_id="xfce-polkit"] floating enable; sticky enable
|
||||||
|
for_window [app_id="lxqt-policykit-agent"] floating enable; sticky enable
|
||||||
|
for_window [app_id="thunar"] floating enable; sticky enable; border normal
|
||||||
|
for_window [app_id="solaar"] floating enable; sticky enable; border normal
|
||||||
|
for_window [app_id="nm-connection-editor"] floating enable; sticky enable; border csd
|
||||||
|
|
||||||
|
# common things that should float, also made sticky
|
||||||
|
# snippet from gentoo wiki for Sway: https://wiki.gentoo.org/wiki/Sway#Automatic_floating_windows
|
||||||
|
for_window [window_role="pop-up"] floating enable; sticky enable
|
||||||
|
for_window [window_role="bubble"] floating enable; sticky enable
|
||||||
|
for_window [window_role="dialog"] floating enable; sticky enable
|
||||||
|
for_window [window_role="task_dialog"] floating enable; sticky enable
|
||||||
|
for_window [window_role="About"] floating enable; sticky enable
|
||||||
|
for_window [window_type="menu"] floating enable; sticky enable
|
||||||
|
for_window [window_type="dialog"] floating enable; sticky enable
|
||||||
|
for_window [app_id="org.kde.elisa" title="Configure.*$"] sticky enable; floating enable
|
||||||
|
for_window [app_id="albert"] floating enable; border none
|
||||||
|
for_window [app_id="com.github.gi_lom.dialect"] floating enable
|
||||||
|
# make Discord file open windows only float - specifically cased app_id and no title
|
||||||
|
for_window [app_id="Discord" title=""] floating enable
|
||||||
|
for_window [app_id="org.gnome.Calculator"] floating enable
|
||||||
|
# for_window [app_id="org.gnome.Calendar"] floating enable
|
||||||
|
for_window [app_id="org.kde.dolphin"] floating enable; border normal
|
||||||
|
for_window [app_id="org.mozilla.firefox" title="^About( Mozilla)? Firefox$"] floating enable; sticky enable; border normal
|
||||||
|
for_window [app_id="org.mozilla.firefox" title="^Library$"] floating enable; sticky enable; border normal
|
||||||
|
for_window [app_id="thunderbird" title="^About Mozilla Thunderbird$"] floating enable; sticky enable
|
||||||
|
for_window [class="XEyes" title="xeyes"] floating enable; border none
|
||||||
|
|
||||||
|
# make the main pcsx2 window float -- the double spacing is significant
|
||||||
|
for_window [class="PCSX2" title="PCSX2 .*$"] floating enable
|
||||||
|
|
||||||
|
# make mpv / other windows sticky, appear on any workspace on that output
|
||||||
|
for_window [app_id=mpv] sticky enable; border csd
|
||||||
|
|
||||||
|
# get rid of annoying webex popup
|
||||||
|
for_window [app_id="org.mozilla.firefox" title="Firefox — Sharing Indicator"] kill
|
||||||
|
|
||||||
|
# mark wayland/xwayland browser windows, to inhibit idle when fullscreen
|
||||||
|
#for_window [class="Chromium-browser"] mark Browser
|
||||||
|
#for_window [class="Brave-browser"] mark Browser
|
||||||
|
#for_window [class="firefox"] mark Browser
|
||||||
|
#for_window [class="work-firefox"] mark Browser
|
||||||
|
#for_window [app_id="Chromium-browser"] mark Browser
|
||||||
|
#for_window [app_id="brave-browser"] mark Browser
|
||||||
|
#for_window [app_id="firefox"] mark Browser
|
||||||
|
#for_window [app_id="org.mozilla.firefox"] mark Browser
|
||||||
|
#for_window [app_id="work-firefox"] mark Browser
|
||||||
|
|
||||||
|
# CS2 is floating for some reason, stop it
|
||||||
|
for_window [class="cs2" title="Counter-Strike 2"] floating disable; fullscreen enable; border none; max_render_time off
|
||||||
|
|
||||||
|
# inhibit scrensaver for fullscreen browser windows
|
||||||
|
#for_window [con_mark="Browser"] {
|
||||||
|
# inhibit_idle fullscreen
|
||||||
|
# max_render_time off
|
||||||
|
#}
|
||||||
|
|
||||||
|
# misc Steam fixes, also from Gentoo wiki (link above)
|
||||||
|
for_window [class="^Steam$" title="^Settings$"] floating enable; sticky enable
|
||||||
|
for_window [class="steam" title="Steam Settings"] floating enable; sticky enable
|
||||||
|
for_window [class="^Steam$" title="^Steam - Self Updater$"] floating enable; sticky enable
|
||||||
|
for_window [class="^Steam$" title="^Screenshot Uploader$"] floating enable; sticky enable
|
||||||
|
for_window [class="^Steam$" title="^Steam Guard - Computer Authorization Required$"] floating enable; sticky enable
|
6
outerheaven.init3.home/.config/sway/sway/90-bar.conf
Normal file
6
outerheaven.init3.home/.config/sway/sway/90-bar.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#
|
||||||
|
## Status Bar:
|
||||||
|
#
|
||||||
|
bar {
|
||||||
|
swaybar_command waybar
|
||||||
|
}
|
36
outerheaven.init3.home/.config/sway/sway/config
Normal file
36
outerheaven.init3.home/.config/sway/sway/config
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Config for sway
|
||||||
|
#
|
||||||
|
# See `man 5 sway` for a complete reference.
|
||||||
|
font pango:SFProDisplay Regular 12
|
||||||
|
smart_borders on
|
||||||
|
default_border pixel 1
|
||||||
|
default_floating_border pixel 1
|
||||||
|
# gaps
|
||||||
|
#smart_gaps on
|
||||||
|
#gaps inner 1
|
||||||
|
# default_floating_border normal
|
||||||
|
# default_floating_border none
|
||||||
|
hide_edge_borders --i3 smart
|
||||||
|
tiling_drag disable
|
||||||
|
floating_modifier none
|
||||||
|
workspace_layout tabbed
|
||||||
|
titlebar_border_thickness 1
|
||||||
|
titlebar_padding 4 2
|
||||||
|
title_align center
|
||||||
|
workspace_auto_back_and_forth yes
|
||||||
|
# cursor stuff - default size is 24
|
||||||
|
#seat seat0 xcursor_theme Bibata-Modern-Classic 32
|
||||||
|
#seat seat0 xcursor_theme Bibata-Modern-Ice 24
|
||||||
|
#seat seat0 xcursor_theme capitaine-cursors-light 24
|
||||||
|
|
||||||
|
# map human friendly names for displays to outputs, may be used by included configs
|
||||||
|
set $MON_LEFT DP-2
|
||||||
|
set $MON_CENTER DP-1
|
||||||
|
set $MON_RIGHT DP-3
|
||||||
|
|
||||||
|
# include distribution + user things
|
||||||
|
include '$(/usr/libexec/sway/layered-include "/usr/share/sway/config.d/*.conf" "/etc/sway/config.d/*.conf" "${XDG_CONFIG_HOME:-$HOME/.config}/sway/*.conf")'
|
||||||
|
|
||||||
|
# run script which handles conditional/timely autostarts. uses dict w/ this structure:
|
||||||
|
# {'autostarts': { 'pre': [], 'weekend': [], 'common': [], 'work': []}}
|
||||||
|
exec 'python3 ~/.config/sway/scripts/startup.py'
|
48
outerheaven.init3.home/.config/sway/sway/environment
Normal file
48
outerheaven.init3.home/.config/sway/sway/environment
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# vim: set ft=sh:
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
#
|
||||||
|
# Set environment variables for Sway session
|
||||||
|
#
|
||||||
|
# Useful variables for wlroots:
|
||||||
|
# https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/docs/env_vars.md
|
||||||
|
#
|
||||||
|
#WLR_NO_HARDWARE_CURSORS=1
|
||||||
|
PATH="${HOME}/.cargo/bin:${HOME}/.local/bin:${PATH}"
|
||||||
|
#SWAY_EXTRA_ARGS="$SWAY_EXTRA_ARGS --debug" ## Pass extra arguments to the /usr/bin/sway executable
|
||||||
|
GTK2_RC_FILES="${HOME}/.gtkrc-2.0"
|
||||||
|
QT_STYLE_OVERRIDE=kvantum # use kvantum to try to mirror gtk w/ QT
|
||||||
|
# XDG_CURRENT_DESKTOP=sway
|
||||||
|
XDG_DATA_DIRS="${HOME}/.local/share/:${XDG_DATA_DIRS}"
|
||||||
|
EDITOR=/usr/bin/nvim # change default CLI editor to 'nvim'; Fedora defaults to nano
|
||||||
|
GNUPGHOME=${HOME}/.gnupg/trezor # use Trezor hardware wallet for GPG signing
|
||||||
|
# ref: https://github.com/romanz/trezor-agent/blob/master/doc/README-GPG.md
|
||||||
|
LIBVIRT_DEFAULT_URI="qemu:///system" # default to the system context with qemu/libvirt
|
||||||
|
SSH_AUTH_SOCK=${XDG_RUNTIME_DIR}/keyring/ssh # use the SSH keyring spawned by the DE (if Sway, ~/.config/sway/startup.py)
|
||||||
|
ANSIBLE_NOCOWS=1 # disable ansible cowsay tomfoolery, I control my ranch [poorly]
|
||||||
|
MOZ_CRASHREPORTER_DISABLE=1 # disable crash reports for mozilla things -- they eat space while submission is disabled
|
||||||
|
MOZ_ENABLE_WAYLAND=1
|
||||||
|
# GDK_BACKEND=wayland # superfluous?
|
||||||
|
KITTY_ENABLE_WAYLAND=1
|
||||||
|
QT_QPA_PLATFORM=wayland # encourage wayland for QT/KDE apps
|
||||||
|
PROTON_LOG_DIR=$(mktemp --tmpdir -d proton_logs.XXXX)
|
||||||
|
SYSTEMD_PAGER='' # disable the auto pager
|
||||||
|
_JAVA_AWT_WM_NONREPARENTING=1
|
||||||
|
NO_AT_BRIDGE=1
|
||||||
|
BEMENU_BACKEND=wayland
|
||||||
|
VAAPI_MPEG4_ENABLED=true
|
||||||
|
# increase on-disk cache from 1G to 4, NOTE: may further multiply across architectures (32/64)
|
||||||
|
# MESA_SHADER_CACHE_MAX_SIZE=4G
|
||||||
|
|
||||||
|
# electron vars - wanted features, common args, or wayland specific args (for Sway)
|
||||||
|
# *not* used by Electron applications directly, used elsewhere in session (ie: autostarts)
|
||||||
|
W_ELECTRON_FEATURES="VaapiVideoDecoder,VaapiVideoEncoder,WebRTCPipeWireCapturer,UseOzonePlatform,WaylandWindowDecorations,VaapiVideoDecodeLinuxGL"
|
||||||
|
# egl causes at least mattermost (potentially other things) to have 'GPU' crashes - while others are fine (ie: Discord)
|
||||||
|
#W_ELECTRON_ARGS="--silent --enable-gpu --use-gl=egl --enable-features='${W_ELECTRON_FEATURES}' --ozone-platform-hint=auto"
|
||||||
|
W_ELECTRON_ARGS="--silent --enable-gpu-rasterization --enable-sync --disable-features='AudioServiceSandbox,Vulkan' --enable-features='${W_ELECTRON_FEATURES}' --ozone-platform-hint=auto"
|
||||||
|
|
||||||
|
# maximum 'eFfIciEncY', avoid a ton of syscalls - set TZ to use localtime for glibc benefit, ref:
|
||||||
|
# https://blog.packagecloud.io/set-environment-variable-save-thousands-of-system-calls/
|
||||||
|
# flatpak things get weird with this, commenting out
|
||||||
|
#TZ=:/etc/localtime
|
||||||
|
# untested theory, may improve: TZ=:../usr/share/zoneinfo/America/Chicago
|
42
outerheaven.init3.home/.config/sway/sway/scripts/locker.py
Executable file
42
outerheaven.init3.home/.config/sway/sway/scripts/locker.py
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
'''simple screen locker for Sway, run by $mod+l'''
|
||||||
|
from os import environ
|
||||||
|
from i3ipc import Connection
|
||||||
|
|
||||||
|
|
||||||
|
def lock_session(manager):
|
||||||
|
'''function for the lock sequence
|
||||||
|
pauses any playing media and runs swaylock to lock the session'''
|
||||||
|
|
||||||
|
lock_commands = ['playerctl pause',
|
||||||
|
'swaylock -f']
|
||||||
|
|
||||||
|
for command in lock_commands:
|
||||||
|
print(f'Executing: {command}')
|
||||||
|
manager.command(f'exec {command}')
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
# explicitly tied to sway/swaysock
|
||||||
|
# don't get the impression swaylock works w/ i3
|
||||||
|
# ...which this module also supports/would use naively
|
||||||
|
SWAYSOCK = environ['SWAYSOCK']
|
||||||
|
|
||||||
|
# use subprocess/xrandr to get the 4k display to (later) make it primary
|
||||||
|
# otherwise games seem to get confused on monitor/resolution
|
||||||
|
|
||||||
|
|
||||||
|
# with the socket, connect and lock
|
||||||
|
_wm = Connection(socket_path=SWAYSOCK, auto_reconnect=True)
|
||||||
|
lock_session(_wm)
|
||||||
|
|
||||||
|
# clean up, disconnect from WM
|
||||||
|
_wm.main_quit()
|
||||||
|
|
||||||
|
except IOError as e:
|
||||||
|
# Handle exceptions related to the connection
|
||||||
|
print("There was a problem establishing the connection, socket:", SWAYSOCK)
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
print('The "SWAYSOCK" var is not defined')
|
164
outerheaven.init3.home/.config/sway/sway/scripts/screenshot.py
Executable file
164
outerheaven.init3.home/.config/sway/sway/scripts/screenshot.py
Executable file
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
This script provides screenshot handling for Sway
|
||||||
|
|
||||||
|
Determines the mode (region or window), and then takes a capture
|
||||||
|
|
||||||
|
Expects window manager keybinds like below:
|
||||||
|
bindsym --no-repeat Print exec "~/.config/sway/scripts/screenshot.py region"
|
||||||
|
bindsym --no-repeat Shift+Print exec "~/.config/sway/scripts/screenshot.py window"
|
||||||
|
|
||||||
|
If mode is 'region', the tool 'slurp' is used to select the region to capture.
|
||||||
|
|
||||||
|
If mode is 'window', the WM is asked which display is active (to capture).
|
||||||
|
|
||||||
|
In both cases, the 'grim' utility does the image capture. Captures go here:
|
||||||
|
~/Pictures/screenshots/Screenshot_*.png
|
||||||
|
|
||||||
|
Fedora dependencies:
|
||||||
|
- sudo dnf in python-{i3ipc,pillow}
|
||||||
|
|
||||||
|
Note: while i3ipc aspects of this will work with i3...
|
||||||
|
slurp/grim likely will not
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from time import strftime
|
||||||
|
from i3ipc import Connection, Event
|
||||||
|
from PIL import Image
|
||||||
|
from PIL.PngImagePlugin import PngInfo
|
||||||
|
|
||||||
|
# setup argparse
|
||||||
|
# take one arg, m/mode: selection/window
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Take some screenshots for Sway using IPC and slurp/grim')
|
||||||
|
|
||||||
|
# add main arg, screenshot mode -- region (selection area) or [focused] window
|
||||||
|
parser.add_argument('mode', type=str, choices=['region', 'window', 'w', 'r'],
|
||||||
|
help='''Screenshot "mode", either:
|
||||||
|
A selected area ('region') or
|
||||||
|
the focused display ('window')"''')
|
||||||
|
|
||||||
|
# instantiate args
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# env prep
|
||||||
|
# get the current time
|
||||||
|
now = strftime("%Y-%m-%dT-%H%M%z")
|
||||||
|
# use strftime - similar iso format as 'datetime', with 1 minor fix
|
||||||
|
# no ':' - in the off chance they are uploaded to Jira
|
||||||
|
# ex: 2022-11-21T-2337-0600
|
||||||
|
|
||||||
|
# set a var for the homedir using os.environ - arguably not portable? /shrug
|
||||||
|
homedir = os.environ['HOME']
|
||||||
|
screenshot_dir = f'{homedir}/Pictures/screenshots'
|
||||||
|
screenshot_path = f'{screenshot_dir}/Screenshot_{now}.png'
|
||||||
|
preview_command = f"imv -d -s none '{screenshot_path}'"
|
||||||
|
# preview_command = f"xdg-open '{screenshot_path}'"
|
||||||
|
|
||||||
|
if not os.path.isdir(screenshot_dir):
|
||||||
|
print(f"Screenshot dir doesn't exist, creating {screenshot_dir}")
|
||||||
|
os.mkdir(screenshot_dir)
|
||||||
|
else:
|
||||||
|
print(f'Screenshot dir ({screenshot_dir}) exists, continuing')
|
||||||
|
|
||||||
|
# misc functions
|
||||||
|
def determine_ss_cmd():
|
||||||
|
'''based on mode, determine the screenshot command'''
|
||||||
|
# screenshot command handling (based on mode)
|
||||||
|
# grim uses compression level 1 in both cases
|
||||||
|
# neglible quality difference while saving space
|
||||||
|
if args.mode in ['window', 'w']:
|
||||||
|
# use wm connection to determine the active output
|
||||||
|
outputs = _wm.get_outputs()
|
||||||
|
for output in outputs:
|
||||||
|
if output.focused:
|
||||||
|
active_output = output.name
|
||||||
|
print(f'determined active output: {active_output}')
|
||||||
|
command = f"grim -l 1 -c -o {active_output} '{screenshot_path}'"
|
||||||
|
elif args.mode in ['region', 'r']:
|
||||||
|
# omits -c to leave out cursors
|
||||||
|
command = f"slurp -d | grim -l 1 -g - '{screenshot_path}'"
|
||||||
|
return command
|
||||||
|
|
||||||
|
def preview_focus(_wm, _event):
|
||||||
|
'''function called by window manager new_window events
|
||||||
|
checks if new window is preview, if so: give it focus'''
|
||||||
|
if _event.container.app_id == 'imv':
|
||||||
|
# give the preview focus
|
||||||
|
# for Sway we use 'app_id', for i3 this is probably 'class'
|
||||||
|
_wm.command('[app_id=imv] focus')
|
||||||
|
# once the preview window is focused, close our connection to the wm
|
||||||
|
_wm.main_quit()
|
||||||
|
|
||||||
|
def wm_connect():
|
||||||
|
'''get the party started, create a connection to the window manager'''
|
||||||
|
conn = Connection(auto_reconnect=True)
|
||||||
|
# on new window events check if screenshot preview window gets focus
|
||||||
|
conn.on(Event.WINDOW_NEW, preview_focus)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def _run_command(command):
|
||||||
|
print(f'Command: {command}')
|
||||||
|
_r = subprocess.run(command, shell=True, capture_output=True, check=False)
|
||||||
|
if _r.stderr:
|
||||||
|
raise subprocess.CalledProcessError(
|
||||||
|
returncode = _r.returncode,
|
||||||
|
cmd = _r.args,
|
||||||
|
stderr = _r.stderr
|
||||||
|
)
|
||||||
|
if _r.stdout:
|
||||||
|
print(f"Command Result: {_r.stdout.decode('utf-8')}")
|
||||||
|
return _r
|
||||||
|
|
||||||
|
# begin screenshot/preview process
|
||||||
|
# connect to the window manager -- Sway
|
||||||
|
# (i3 could work, may need grim/slurp replacements)
|
||||||
|
_wm = wm_connect()
|
||||||
|
# determine screenshot command - differs if window or region mode
|
||||||
|
screenshot_command = determine_ss_cmd()
|
||||||
|
|
||||||
|
# run the screenshot/preview commands
|
||||||
|
# previewing/sending focus only if screenshot is taken
|
||||||
|
SS_RC = -1
|
||||||
|
try:
|
||||||
|
ss_result = _run_command(screenshot_command)
|
||||||
|
SS_RC = ss_result.returncode
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
print('screenshot failed/aborted')
|
||||||
|
# clean up after ourselves, close the wm loop
|
||||||
|
_wm.main_quit()
|
||||||
|
|
||||||
|
# if the screenshot succeeded, place a comment on the image
|
||||||
|
# and then preview/focus the window
|
||||||
|
if SS_RC == 0:
|
||||||
|
#
|
||||||
|
# construct the comment for the screenshot
|
||||||
|
# immediately after it's taken, find the focused window details
|
||||||
|
wm_tree = _wm.get_tree()
|
||||||
|
wm_focused = wm_tree.find_focused()
|
||||||
|
app_name = wm_focused.name
|
||||||
|
app_id = wm_focused.app_id
|
||||||
|
COMMENT = f"Screenshot of '{app_name} (app_id: {app_id}) at {now}'"
|
||||||
|
print(f'storing comment: {COMMENT}')
|
||||||
|
#
|
||||||
|
# open the screenshot for (metadata) modification
|
||||||
|
# adding the application title/window class/date as a comment
|
||||||
|
# visible using 'exiftool', easier sorting through command line
|
||||||
|
ss_obj = Image.open(screenshot_path)
|
||||||
|
ss_metadata = PngInfo()
|
||||||
|
ss_metadata.add_text("Comment", COMMENT)
|
||||||
|
#
|
||||||
|
# write the (commented) image back out
|
||||||
|
ss_obj.save(screenshot_path, pnginfo=ss_metadata)
|
||||||
|
#
|
||||||
|
# open the preview with 'imv'
|
||||||
|
print(f"exec preview: {preview_command}")
|
||||||
|
_wm.command(f'exec {preview_command}')
|
||||||
|
#
|
||||||
|
# start the main loop for the window manager
|
||||||
|
# basically wait for the preview listener to fire
|
||||||
|
# when the preview window opens, a message is sent to give it focus
|
||||||
|
# afterwards we exit
|
||||||
|
_wm.main()
|
203
outerheaven.init3.home/.config/sway/sway/scripts/startup.py
Executable file
203
outerheaven.init3.home/.config/sway/sway/scripts/startup.py
Executable file
|
@ -0,0 +1,203 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
"""
|
||||||
|
A smart but also lazy login autostart manager for i3/Sway.
|
||||||
|
|
||||||
|
Will conditionally exec other things defined in a YML dict. ie: every day, work days, or weekends
|
||||||
|
|
||||||
|
Required i3/Sway config line:
|
||||||
|
exec /home/jlay/.config/sway/scripts/startup.py
|
||||||
|
|
||||||
|
Config sample:
|
||||||
|
---
|
||||||
|
autostarts:
|
||||||
|
pre: [] # blocking tasks that run every day, before any other section. intended for backups/updates
|
||||||
|
common: [] # non-blocking tasks that run every day
|
||||||
|
weekend: [] # blocking tasks for weekends, after 'pre' but before 'common'
|
||||||
|
work: [] # non-blocking tasks run if Monday through Friday between 8AM - 4PM
|
||||||
|
|
||||||
|
Dependencies: python3-i3ipc
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
from time import sleep
|
||||||
|
import argparse
|
||||||
|
from textwrap import dedent
|
||||||
|
from systemd import journal
|
||||||
|
import yaml.loader
|
||||||
|
from i3ipc import Connection
|
||||||
|
from xdg import BaseDirectory
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(
|
||||||
|
message: str, level: str, syslog_identifier: str = "sway-autostart"
|
||||||
|
) -> None:
|
||||||
|
"""Given `message`, send it to the journal and print
|
||||||
|
|
||||||
|
Accepts 'journal' levels. ie: `journal.LOG_{ERR,INFO,CRIT,EMERG}'
|
||||||
|
"""
|
||||||
|
valid_levels = {
|
||||||
|
journal.LOG_EMERG,
|
||||||
|
journal.LOG_ALERT,
|
||||||
|
journal.LOG_CRIT,
|
||||||
|
journal.LOG_ERR,
|
||||||
|
journal.LOG_WARNING,
|
||||||
|
journal.LOG_NOTICE,
|
||||||
|
journal.LOG_INFO,
|
||||||
|
journal.LOG_DEBUG,
|
||||||
|
}
|
||||||
|
if level not in valid_levels:
|
||||||
|
raise ValueError(f"Invalid log level: {level}")
|
||||||
|
print(message)
|
||||||
|
journal.send(message, PRIORITY=level, SYSLOG_IDENTIFIER=syslog_identifier)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
"""If run interactively, this provides arg function to the user"""
|
||||||
|
description_text = dedent(
|
||||||
|
f"""\
|
||||||
|
A smart but also lazy login autostart manager for i3/Sway.
|
||||||
|
|
||||||
|
Will conditionally exec other things defined in a YML dict. ie: every day, work days, or weekends
|
||||||
|
|
||||||
|
Required i3/Sway config line:
|
||||||
|
exec {os.path.abspath(__file__)}
|
||||||
|
|
||||||
|
Config sample:
|
||||||
|
---
|
||||||
|
autostarts:
|
||||||
|
pre: [] # blocking tasks that run every day, before any other section. intended for backups/updates
|
||||||
|
common: [] # non-blocking tasks that run every day
|
||||||
|
weekend: [] # blocking tasks for weekends, after 'pre' but before 'common'
|
||||||
|
work: [] # non-blocking tasks run if Monday through Friday between 8AM - 4PM
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
class PlainDefaultFormatter(
|
||||||
|
argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter
|
||||||
|
):
|
||||||
|
"""Combines two ArgParse formatter classes:
|
||||||
|
- argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
- argparse.RawDescriptionHelpFormatter"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=description_text, formatter_class=PlainDefaultFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default path for the config
|
||||||
|
default_config = os.path.join(BaseDirectory.xdg_config_home, "autostart-i3ipc.yml")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--config",
|
||||||
|
default=default_config,
|
||||||
|
help="Path to the YML configuration file.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
# OOPy way to determine if it's a work day -- mon<->friday, 3AM<->5PM
|
||||||
|
class WorkTime(datetime):
|
||||||
|
"""datetime but with work on the mind"""
|
||||||
|
|
||||||
|
def is_workday(self):
|
||||||
|
"""determine if it's a work day: monday-friday between 3AM and 5PM.
|
||||||
|
Use .vacation file to go on vacation"""
|
||||||
|
|
||||||
|
# first check if ~/.vacation exists - if so, not a work day
|
||||||
|
if os.path.isfile(os.path.expanduser("~/.vacation")):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# note: last number in range isn't included
|
||||||
|
if not self.is_weekend() and self.hour in range(8, 16):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_weekend(self):
|
||||||
|
"""determine if it's the weekend or not, ISO week day outside 1-5"""
|
||||||
|
if self.isoweekday() not in range(1, 6):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parse_args()
|
||||||
|
config_path = args.config
|
||||||
|
|
||||||
|
# get the current time
|
||||||
|
now = WorkTime.now()
|
||||||
|
# determine if it's a work day using WorkTime above
|
||||||
|
workday = now.is_workday()
|
||||||
|
weekend = now.is_weekend()
|
||||||
|
|
||||||
|
# initialize empty lists for the different categories
|
||||||
|
wants = [] # non-blocking tasks from 'common' and 'workday' sections in config
|
||||||
|
pre_list = [] # blocking tasks before the rest
|
||||||
|
weekend_list = [] # non-blocking tasks for weekend days/logins
|
||||||
|
|
||||||
|
# check the config file for existence/structure. if found, extend the lists
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
print(f"found/loading config: '{config_path}'")
|
||||||
|
with open(config_path, "r", encoding="utf-8") as _config:
|
||||||
|
config_file = yaml.load(_config, Loader=yaml.FullLoader)
|
||||||
|
try:
|
||||||
|
loaded_starts = config_file["autostarts"]
|
||||||
|
wants.extend(loaded_starts["common"])
|
||||||
|
if loaded_starts["pre"]:
|
||||||
|
pre_list.extend(loaded_starts["pre"])
|
||||||
|
if workday:
|
||||||
|
wants.extend(loaded_starts["work"])
|
||||||
|
if weekend:
|
||||||
|
weekend_list.extend(loaded_starts["weekend"])
|
||||||
|
except KeyError as key_err:
|
||||||
|
log_message(
|
||||||
|
f"Key not found in {config_path}: {key_err.args[0]}",
|
||||||
|
journal.LOG_ERR,
|
||||||
|
)
|
||||||
|
except NameError as name_err:
|
||||||
|
log_message(f"name error: {name_err}", journal.LOG_ERR)
|
||||||
|
|
||||||
|
# get the party started, create a connection to the window manager
|
||||||
|
_wm = Connection(auto_reconnect=True)
|
||||||
|
|
||||||
|
# start the blocking tasks - 'pre' and 'weekend'
|
||||||
|
# avoid sending them to the WM, would become backgrounded/async
|
||||||
|
for pre_item in pre_list:
|
||||||
|
try:
|
||||||
|
log_message(
|
||||||
|
f'running (blocking) "pre" task: "{pre_item}"', journal.LOG_INFO
|
||||||
|
)
|
||||||
|
subprocess.run(pre_item, shell=True, check=False)
|
||||||
|
except subprocess.CalledProcessError as pre_ex:
|
||||||
|
log_message(f'failed "{pre_item}": {pre_ex.output}', journal.LOG_ERR)
|
||||||
|
|
||||||
|
if weekend:
|
||||||
|
for weekend_item in weekend_list:
|
||||||
|
try:
|
||||||
|
log_message(
|
||||||
|
f'running (blocking) "weekend" task: "{weekend_item}"',
|
||||||
|
journal.LOG_INFO,
|
||||||
|
)
|
||||||
|
subprocess.check_output(weekend_item, shell=True)
|
||||||
|
except subprocess.CalledProcessError as weekend_except:
|
||||||
|
log_message(
|
||||||
|
f'Exception during "{weekend_item}": {weekend_except.output}',
|
||||||
|
journal.LOG_ERR,
|
||||||
|
)
|
||||||
|
|
||||||
|
# launch 'common' and 'work' tasks; not expected to block, sent to window manager
|
||||||
|
for wanteditem in wants:
|
||||||
|
command = "exec " + wanteditem
|
||||||
|
log_message(f'sending to WM: "{command}"', journal.LOG_INFO)
|
||||||
|
reply = _wm.command(command)
|
||||||
|
sleep(0.1)
|
||||||
|
if reply[0].error:
|
||||||
|
# note: this doesn't check return codes
|
||||||
|
# serves to check if there was a parsing/comm. error with the WM
|
||||||
|
log_message(
|
||||||
|
f'autostart "{command}" failed, couldn\'t reach WM', journal.LOG_ERR
|
||||||
|
)
|
||||||
|
|
||||||
|
_wm.main_quit()
|
105
outerheaven.init3.home/.config/sway/sway/scripts/volume.py
Executable file
105
outerheaven.init3.home/.config/sway/sway/scripts/volume.py
Executable file
|
@ -0,0 +1,105 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
'''Manages PulseAudio volume for a sink (output) by percentage
|
||||||
|
|
||||||
|
Run by Sway on XF86Audio{Raise,Lower}Volume'''
|
||||||
|
import argparse
|
||||||
|
import pulsectl
|
||||||
|
from pydbus import SessionBus
|
||||||
|
|
||||||
|
def get_pulse():
|
||||||
|
'''Returns a Pulse object for inspection/modification'''
|
||||||
|
return pulsectl.Pulse('volume-changer')
|
||||||
|
|
||||||
|
def get_sink(pulse, sink_name=None):
|
||||||
|
'''Get / return a PulseAudio sink (output) object by name.
|
||||||
|
|
||||||
|
Used to query and set the volume. If `sink_name` is omitted, return *default*
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pulse (Pulse): The Pulse object to query, see `get_pulse()`
|
||||||
|
sink_name (str, optional): The name of the PulseAudio sink to look for.
|
||||||
|
If omitted the default sink is assumed
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
pulsectl.PulseSinkInfo'''
|
||||||
|
if sink_name is None:
|
||||||
|
return pulse.get_sink_by_name(pulse.server_info().default_sink_name)
|
||||||
|
return pulse.get_sink_by_name(sink_name)
|
||||||
|
|
||||||
|
def get_volume_rounded(pulse, sink):
|
||||||
|
'''Return the volume of the provided sink
|
||||||
|
|
||||||
|
Returned as a rounded int averaged across channels, assumed linked'''
|
||||||
|
return round(pulse.volume_get_all_chans(sink) * 100)
|
||||||
|
|
||||||
|
def set_volume(pulse, sink, adjustment):
|
||||||
|
'''Changes the PulseAudio `sink` volume by the given `adjustment`
|
||||||
|
|
||||||
|
`sink` should be a pactl object; see `get_sink`
|
||||||
|
|
||||||
|
`adjustment` should be a float, ie 0.01 for *raising* 1%
|
||||||
|
Invert (* -1) to lower'''
|
||||||
|
pulse.volume_change_all_chans(sink, adjustment)
|
||||||
|
|
||||||
|
# Create argument parser
|
||||||
|
parser = argparse.ArgumentParser(description='Change audio volume.')
|
||||||
|
parser.add_argument('direction', choices=['raise', 'lower'], help='The direction to change the volume.')
|
||||||
|
parser.add_argument('percentage', nargs='?', default=1, type=int, help='The percentage to change the volume.')
|
||||||
|
parser.add_argument('--sink', default=None, help='The PulseAudio sink (name) to manage.')
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
args = parser.parse_args()
|
||||||
|
# Calculate the volume change as a float, inverse it if *lowering*
|
||||||
|
# used as a multiplier
|
||||||
|
change = args.percentage / 100
|
||||||
|
if args.direction == 'lower':
|
||||||
|
change = change * -1
|
||||||
|
|
||||||
|
# construct empty dict for JSON output/data
|
||||||
|
# interesting info is appended later
|
||||||
|
data = {"sink": "",
|
||||||
|
"change": "",
|
||||||
|
"start_vol": "",
|
||||||
|
"new_vol": ""}
|
||||||
|
|
||||||
|
# connect to the notification bus
|
||||||
|
notifications = SessionBus().get('.Notifications')
|
||||||
|
|
||||||
|
# get pulse / connect
|
||||||
|
try:
|
||||||
|
with get_pulse() as _p:
|
||||||
|
# query the default sink
|
||||||
|
sink_def = get_sink(pulse=_p)
|
||||||
|
|
||||||
|
# get the starting vol
|
||||||
|
start_vol = get_volume_rounded(pulse=_p, sink=sink_def)
|
||||||
|
|
||||||
|
# change the volume
|
||||||
|
set_volume(pulse=_p, sink=sink_def, adjustment=change)
|
||||||
|
|
||||||
|
# query the volume again
|
||||||
|
new_volume = get_volume_rounded(pulse=_p, sink=sink_def)
|
||||||
|
|
||||||
|
# construct data dict for CLI output/reference
|
||||||
|
data['sink'] = sink_def.name
|
||||||
|
data['change'] = change
|
||||||
|
data['start_vol'] = start_vol
|
||||||
|
data['new_vol'] = new_volume
|
||||||
|
|
||||||
|
# Create a desktop notification
|
||||||
|
notification_id = notifications.Notify(
|
||||||
|
'volume-changer', 0, 'dialog-information',
|
||||||
|
'Volume Change',
|
||||||
|
f"Now {data['new_vol']}%, was {data['start_vol']}%",
|
||||||
|
[], {}, 1000)
|
||||||
|
except pulsectl.PulseError as e:
|
||||||
|
data['sink'] = None
|
||||||
|
data['change'] = 'Impossible, exception: {e}'
|
||||||
|
# notify that we couldn't work with pulseaudio/compatible daemon
|
||||||
|
notification_id = notifications.Notify(
|
||||||
|
'volume-changer', 0, 'dialog-error',
|
||||||
|
'Volume Change',
|
||||||
|
f"Exception: {e}",
|
||||||
|
[], {}, 1000)
|
||||||
|
|
||||||
|
print(data, flush=True)
|
84
outerheaven.init3.home/.config/sway/sway/scripts/wallpaper.py
Executable file
84
outerheaven.init3.home/.config/sway/sway/scripts/wallpaper.py
Executable file
|
@ -0,0 +1,84 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
wallpaper.py - random wallpaper/output utility for i3/Sway
|
||||||
|
|
||||||
|
usage: wallpaper.py [-h] [--select {common,unique}] directory
|
||||||
|
|
||||||
|
Selection modes:
|
||||||
|
Common: One wallpaper selected and shared for *all* displays
|
||||||
|
Unique: One wallpaper selected for *each* display
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import random
|
||||||
|
import asyncio
|
||||||
|
from typing import List
|
||||||
|
from i3ipc.aio import Connection
|
||||||
|
|
||||||
|
EXTS = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
'''Handles argparse on startup'''
|
||||||
|
parser = argparse.ArgumentParser(description='Random Wallpaper Setter for Sway')
|
||||||
|
parser.add_argument('directory', type=str, help='Directory containing wallpapers')
|
||||||
|
parser.add_argument('--select',
|
||||||
|
type=str,
|
||||||
|
choices=['common', 'unique'],
|
||||||
|
default='common',
|
||||||
|
help='Wallpaper selection mode: all displays, or each?')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
'''you know what it is'''
|
||||||
|
args = parse_args()
|
||||||
|
sway = await Connection(auto_reconnect=True).connect()
|
||||||
|
def list_image_files(d: str) -> List[str]:
|
||||||
|
'''
|
||||||
|
Given the path to a *'directory'*, returns a list of image files to consider for wallpapers.
|
||||||
|
'''
|
||||||
|
return [os.path.join(d, f) for f in os.listdir(d) if os.path.splitext(f)[1].lower() in EXTS]
|
||||||
|
|
||||||
|
async def set_wallpaper(file_path: str, output=None):
|
||||||
|
'''
|
||||||
|
Given an image path, sets the wallpaper for (optional) outputs in i3/Sway.
|
||||||
|
|
||||||
|
If no output is specified then *all* will receive the wallpaper.
|
||||||
|
'''
|
||||||
|
print(f"{output if output else 'all'}: wallpaper='{file_path}'")
|
||||||
|
if output:
|
||||||
|
await sway.command(f'output "{output}" bg "{file_path}" fill')
|
||||||
|
else:
|
||||||
|
for _output in await sway.get_outputs():
|
||||||
|
await sway.command(f'output "{_output.name}" bg "{file_path}" fill')
|
||||||
|
|
||||||
|
if os.path.isdir(args.directory):
|
||||||
|
image_files = list_image_files(args.directory)
|
||||||
|
else:
|
||||||
|
sys.exit(f'ERR: not a directory: {args.directory}')
|
||||||
|
if not image_files:
|
||||||
|
print("No image files found in the specified directory.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f'Found {len(image_files)} candidate image files')
|
||||||
|
outputs = await sway.get_outputs()
|
||||||
|
|
||||||
|
if args.select == 'common':
|
||||||
|
wallpaper = random.choice(image_files)
|
||||||
|
await set_wallpaper(wallpaper)
|
||||||
|
else: # args.select == 'unique', we need to determine a wallpaper for each display
|
||||||
|
for output in outputs:
|
||||||
|
if len(image_files) == 0:
|
||||||
|
print(f"Not enough images in '{args.directory}' for each display.")
|
||||||
|
break
|
||||||
|
wallpaper = random.choice(image_files)
|
||||||
|
image_files.remove(wallpaper)
|
||||||
|
await set_wallpaper(wallpaper, output.name)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
Loading…
Reference in a new issue