chore: remove all unsused configs
This commit is contained in:
45
README.md
45
README.md
@@ -12,41 +12,34 @@ Use ./stow.sh --unstow <folder_name> to remove configuration
|
|||||||
- Install based on Fedora Workstation with GNOME
|
- Install based on Fedora Workstation with GNOME
|
||||||
- Font: [JetBrains Mono](https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/JetBrainsMono.zip)
|
- Font: [JetBrains Mono](https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/JetBrainsMono.zip)
|
||||||
- Keyboard Layout: EurKey
|
- Keyboard Layout: EurKey
|
||||||
- Keepass should use `/run/user/1000/keyring/ssh` as SSH Auth Socket
|
|
||||||
- Keyring provided by GNOME and started with sway
|
|
||||||
- Environment Vairables exported using systemd in [envvars.conf](files/environment/.config/environment.d/envvars.conf)
|
- Environment Vairables exported using systemd in [envvars.conf](files/environment/.config/environment.d/envvars.conf)
|
||||||
- Lenovo Conservation Mode without root
|
- Lenovo Conservation Mode without root
|
||||||
```bash
|
```bash
|
||||||
%wheel ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/bus/platform/drivers/ideapad_acpi/VPC????\:??/conservation_mode
|
%wheel ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/bus/platform/drivers/ideapad_acpi/VPC????\:??/conservation_mode
|
||||||
```
|
```
|
||||||
- Brave Flags for Wayland
|
|
||||||
```bash
|
|
||||||
/usr/bin/brave-browser-stable --enable-features=UseOzonePlatform --ozone-platform=wayland --gtk-version=4 --enable-wayland-ime --enable-features=TouchpadOverscrollHistoryNavigation
|
|
||||||
```
|
|
||||||
- Remove Client Side Decorations
|
|
||||||
```bash
|
|
||||||
$ gsettings get org.gnome.desktop.wm.preferences button-layout
|
|
||||||
'icon:close'
|
|
||||||
$ gsettings set org.gnome.desktop.wm.preferences button-layout ''
|
|
||||||
```
|
|
||||||
|
|
||||||
### Software via dnf
|
### Software
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo dnf install \
|
sudo dnf install \
|
||||||
kitty git vim tmux ripgrep fd-find fzf stow \
|
kitty tmux \
|
||||||
brave \
|
git ripgrep fd-find fzf stow \
|
||||||
sway waybar \
|
firefox \
|
||||||
brightnessctl wireplumber pasystray blueman \
|
nextcloud keepassxc tailscale
|
||||||
network-manager network-manager-applet \
|
|
||||||
fcitx5 fcitx5-configtool fcitx5-anthy fcitx5-* \
|
|
||||||
nextcloud keepassxc
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Software via GitHub
|
[Helix](https://github.com/helix-editor/helix/releases)
|
||||||
|
|
||||||
- [sway fork - swayfx](https://github.com/WillPower3309/swayfx)
|
|
||||||
- [notification daemon + popup - swaync](https://github.com/ErikReider/SwayNotificationCenter)
|
### Extensions
|
||||||
- [application launcher - tofi](https://github.com/philj56/tofi)
|
- appindicatorsupport@rgcjonas.gmail.com (bring back tray icons)
|
||||||
- [screenshot tool](https://github.com/moverest/sway-interactive-screenshot) (checked in [here](files/sway/.config/sway/screenshot))
|
- blur-my-shell@aunetx (blur panel and background in overview, rest disabled)
|
||||||
- [screen mirror for live presentations](https://github.com/Ferdi265/wl-mirror)
|
- caffeine@patapon.info (don't sleep on fullscreen)
|
||||||
|
- compiz-alike-magic-lamp-effect@hermes83.github.com (minimize animation)
|
||||||
|
- gnome-ui-tune@itstime.tech (desktop thumbnails scale 300%, show search)
|
||||||
|
- gsconnect@andyholmes.github.io (connect android for file share)
|
||||||
|
- ideapad@laurento.frittella (lenovo conservation mode)
|
||||||
|
- light-style@gnome-shell-extensions.gcampax.github.com (everything light mode)
|
||||||
|
- rounded-window-corners@fxgn (rounded corners of all windows)
|
||||||
|
- tiling-assistant@leleat-on-github (2 gaps, tiling state, super+wasd)
|
||||||
|
- user-theme@gnome-shell-extensions.gcampax.github.com (legacy apps adw-gtk3, icons papirus)
|
||||||
@@ -1,23 +1,11 @@
|
|||||||
PATH=$HOME/.dots/scripts:$HOME/.cargo/bin:$HOME/.ghcup/bin:$HOME/.local/bin:$HOME/.cabal/bin:$PATH
|
PATH=$HOME/.dots/scripts:$HOME/.cargo/bin:$HOME/.ghcup/bin:$HOME/.local/bin:$HOME/.cabal/bin:$PATH
|
||||||
EDITOR=hx
|
EDITOR=hx
|
||||||
VISUAL=hx
|
VISUAL=hx
|
||||||
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
|
|
||||||
_JAVA_AWT_WM_NONREPARENTING=1
|
_JAVA_AWT_WM_NONREPARENTING=1
|
||||||
|
|
||||||
# XXX: render shadow on wayland
|
|
||||||
# KITTY_DISABLE_WAYLAND=1
|
|
||||||
# KITTY_ENABLE_WAYLAND=1
|
|
||||||
|
|
||||||
# support jp input in gnome
|
# support jp input in gnome
|
||||||
GTK_IM_MODULE=ibus
|
GTK_IM_MODULE=ibus
|
||||||
QT_IM_MODULE=ibus
|
QT_IM_MODULE=ibus
|
||||||
XMODIFIERS=@im=ibus
|
XMODIFIERS=@im=ibus
|
||||||
SDL_IM_MODULE=ibus
|
SDL_IM_MODULE=ibus
|
||||||
GLFW_IM_MODULE=ibus
|
GLFW_IM_MODULE=ibus
|
||||||
|
|
||||||
# sway - fcitx5 fcitx5-configtool-gtk fcitx5-anthy
|
|
||||||
# GTK_IM_MODULE=fcitx
|
|
||||||
# QT_IM_MODULE=fcitx
|
|
||||||
# XMODIFIERS=@im=fcitx
|
|
||||||
# SDL_IM_MODULE=fcitx
|
|
||||||
# GLFW_IM_MODULE=fcitx
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
### System Utility
|
### System Utility
|
||||||
alias cdt='mkdir /tmp/$(cat /proc/sys/kernel/random/uuid); cd $_'
|
alias cdt='mkdir /tmp/$(cat /proc/sys/kernel/random/uuid); cd $_'
|
||||||
alias cpu='watch -n.1 "grep \"^[c]pu MHz\" /proc/cpuinfo"'
|
|
||||||
alias fixagent='eval $(tmux show-env -s | grep "^SSH_")'
|
alias fixagent='eval $(tmux show-env -s | grep "^SSH_")'
|
||||||
alias truecolor='curl -s https://raw.githubusercontent.com/JohnMorales/dotfiles/master/colors/24-bit-color.sh | bash'
|
alias truecolor='curl -s https://raw.githubusercontent.com/JohnMorales/dotfiles/master/colors/24-bit-color.sh | bash'
|
||||||
alias whatsmyip='curl https://ipinfo.io/ip; echo'
|
|
||||||
alias mirror='wl-mirror eDP-1'
|
|
||||||
|
|
||||||
### zfs (just saved commands)
|
### zfs (just saved commands)
|
||||||
# zfs send pool/path/to/zvol@snapshot| gzip -c >/mnt/some/location/zvol@20230302.gz
|
# zfs send pool/path/to/zvol@snapshot| gzip -c >/mnt/some/location/zvol@20230302.gz
|
||||||
@@ -16,57 +13,26 @@ alias mv='mv -i'
|
|||||||
alias rm='rm -i'
|
alias rm='rm -i'
|
||||||
|
|
||||||
### fzf
|
### fzf
|
||||||
# basics
|
|
||||||
alias f='fzf --reverse'
|
alias f='fzf --reverse'
|
||||||
alias c='cd $(fd --type d | fzf --reverse)'
|
alias c='cd $(fd --type d | fzf --reverse)'
|
||||||
|
alias blame='git show $(git ls-files | fzf -e --reverse --bind "enter:become(git blame {1} | fzf -e --ansi --reverse | cut -f 1 -d \" \")")'
|
||||||
# git
|
|
||||||
alias gbl='git show $(git ls-files | fzf -e --reverse --bind "enter:become(git blame {1} | fzf -e --ansi --reverse | cut -f 1 -d \" \")")'
|
|
||||||
gg() {
|
|
||||||
git grep --color=always $1 | fzf --reverse --ansi --bind "enter:become($EDITOR {1} +{2})" --delimiter :
|
|
||||||
}
|
|
||||||
glg() {
|
|
||||||
git log --graph --color=always --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" | fzf --ansi --no-sort --reverse --tiebreak=index --toggle-sort=\` --bind "ctrl-m:execute: echo {} | grep -o '[a-f0-9]\{7\}' | head -1 | xargs -I % sh -c 'git show --color=always % | less -R'";
|
|
||||||
}
|
|
||||||
|
|
||||||
# nix
|
# nix
|
||||||
alias nd="nix develop ."
|
alias nd="nix develop ."
|
||||||
alias ns="nix-shell"
|
alias ns="nix-shell"
|
||||||
alias nu="nix-channel --update"
|
|
||||||
alias nt="cp $HOME/.dots/templates/typst.nix . && echo 'Copied typst.nix'"
|
|
||||||
|
|
||||||
tw() {
|
|
||||||
typst watch $1 --open
|
|
||||||
}
|
|
||||||
|
|
||||||
# ripgrep
|
|
||||||
rf() {
|
|
||||||
rg $1 --line-number --color=always | fzf --reverse --ansi --bind "enter:become($EDITOR {1} +{2})" --delimiter :
|
|
||||||
}
|
|
||||||
|
|
||||||
### Abbreviations
|
### Abbreviations
|
||||||
alias d='docker'
|
alias dcr='docker compose down && docker compose up -d && docker compose logs -f'
|
||||||
alias dc='docker compose'
|
|
||||||
alias dcr='dc down && dc up -d && dc logs -f'
|
|
||||||
alias dh1='du . -h -d1'
|
alias dh1='du . -h -d1'
|
||||||
alias dhs='du . -hs'
|
alias dhs='du . -hs'
|
||||||
alias diff_dir='diff -qr'
|
|
||||||
alias g='git'
|
alias g='git'
|
||||||
alias l='ls --color --hyperlink'
|
alias ls='ls --color --hyperlink'
|
||||||
alias nssh='SSH_AUTH_SOCK= ssh'
|
alias nssh='SSH_AUTH_SOCK= ssh'
|
||||||
alias s='kitten ssh'
|
|
||||||
alias o='xdg-open' # to change a mime use: `xdg-mime default APPLICATION HANDLE`
|
alias o='xdg-open' # to change a mime use: `xdg-mime default APPLICATION HANDLE`
|
||||||
t() {
|
t() {
|
||||||
tmux new-session -A -s ${1:-dev}
|
tmux new-session -A -s ${1:-dev}
|
||||||
}
|
}
|
||||||
|
|
||||||
### nmcli(1)
|
|
||||||
alias con='nmcli con'
|
|
||||||
alias conup='nmcli con up id'
|
|
||||||
alias condown='nmcli con down id'
|
|
||||||
alias conscan='nmcli dev wifi'
|
|
||||||
alias conedit='nm-connection-editor'
|
|
||||||
|
|
||||||
# password hash (sed needed when using in docker-compose)
|
# password hash (sed needed when using in docker-compose)
|
||||||
pwhash() {
|
pwhash() {
|
||||||
name=$1
|
name=$1
|
||||||
@@ -88,6 +54,13 @@ ocr() {
|
|||||||
ocrmypdf -l deu+eng --output-type pdf $file ${name}.ocr.pdf
|
ocrmypdf -l deu+eng --output-type pdf $file ${name}.ocr.pdf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# scale down images and remove exif
|
||||||
|
downimage() {
|
||||||
|
filename=$1
|
||||||
|
convert -resize 720 $filename $filename
|
||||||
|
exiftool -all= $filename
|
||||||
|
}
|
||||||
|
|
||||||
### laptop acpi magic
|
### laptop acpi magic
|
||||||
|
|
||||||
conservation() {
|
conservation() {
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
### Variables
|
|
||||||
set $mod Mod4
|
|
||||||
floating_modifier $mod normal
|
|
||||||
|
|
||||||
### Output configuration (swaymsg -t get_output)
|
|
||||||
# output * bg ~/cloud/images/wallpaper/wallpaper.png fit
|
|
||||||
output * bg ~/downloads/wallpaper.png fill
|
|
||||||
# output eDP-1 scale 2
|
|
||||||
|
|
||||||
# monitor setup at home
|
|
||||||
output 'AOC U34G2G1 0x00000254' mode 3440x1440@99.982Hz position 0,0
|
|
||||||
output 'Acer Technologies XF270HU T78EE0048521' mode 2560x1440@143.856Hz position 440,1440
|
|
||||||
bindsym $mod+p exec wdisplays
|
|
||||||
|
|
||||||
# disable laptop when closed
|
|
||||||
set $laptop eDP-1
|
|
||||||
bindswitch --reload --locked lid:on output $laptop disable
|
|
||||||
bindswitch --reload --locked lid:off output $laptop enable
|
|
||||||
|
|
||||||
workspace_layout default
|
|
||||||
|
|
||||||
# exec_always --no-startup-id convert -scale 10% -blur 0x2.5 -resize 1000% ~/cloud/images/wallpaper/wallpaper.png ~/cloud/images/wallpaper/lock.png
|
|
||||||
# set $lock swaylock -f -i ~/cloud/images/wallpaper/lock.png
|
|
||||||
set $lock swaylock -f -i ~/cloud/images/wallpaper/win_lock.png -u -s stretch
|
|
||||||
exec swayidle -w \
|
|
||||||
timeout 600 '$lock' \
|
|
||||||
timeout 900 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \
|
|
||||||
timeout 1200 'systemctl suspend' \
|
|
||||||
before-sleep '$lock'
|
|
||||||
|
|
||||||
### Input configuration
|
|
||||||
|
|
||||||
input "type:keyboard" {
|
|
||||||
xkb_layout eu
|
|
||||||
xkb_options caps:escape
|
|
||||||
repeat_delay 250
|
|
||||||
repeat_rate 100
|
|
||||||
}
|
|
||||||
|
|
||||||
input "type:pointer" {
|
|
||||||
accel_profile flat
|
|
||||||
}
|
|
||||||
|
|
||||||
bindgesture swipe:3:right workspace next
|
|
||||||
bindgesture swipe:3:left workspace prev
|
|
||||||
|
|
||||||
bindgesture swipe:4:right focus right
|
|
||||||
bindgesture swipe:4:left focus left
|
|
||||||
|
|
||||||
### Key bindings
|
|
||||||
|
|
||||||
# System
|
|
||||||
bindsym --locked XF86MonBrightnessDown exec brightnessctl set 5%-
|
|
||||||
bindsym --locked XF86MonBrightnessUp exec brightnessctl set 5%+
|
|
||||||
|
|
||||||
bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
|
||||||
bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- -l 1.2
|
|
||||||
bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ -l 1.2
|
|
||||||
|
|
||||||
bindsym $mod+BackSpace exec $lock
|
|
||||||
bindsym $mod+Control+BackSpace exec systemctl suspend
|
|
||||||
|
|
||||||
bindsym $mod+Shift+s exec ~/.config/sway/screenshot -s region
|
|
||||||
bindsym $mod+Ctrl+Shift+s exec ~/.config/sway/screenshot -s focused-window
|
|
||||||
|
|
||||||
bindsym $mod+Ctrl+c reload
|
|
||||||
bindsym $mod+Ctrl+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit'
|
|
||||||
|
|
||||||
bindsym $mod+Shift+q kill
|
|
||||||
|
|
||||||
# Run applications
|
|
||||||
bindsym $mod+Return exec kitty
|
|
||||||
bindsym $mod+d exec tofi-drun | xargs swaymsg exec --
|
|
||||||
bindsym $mod+Shift+d exec tofi-run | xargs swaymsg exec --
|
|
||||||
bindsym $mod+e exec nautilus
|
|
||||||
|
|
||||||
# Moving around
|
|
||||||
set $left h
|
|
||||||
set $down j
|
|
||||||
set $up k
|
|
||||||
set $right l
|
|
||||||
# Move your focus around
|
|
||||||
bindsym $mod+$left focus left
|
|
||||||
bindsym $mod+$down focus down
|
|
||||||
bindsym $mod+$up focus up
|
|
||||||
bindsym $mod+$right focus right
|
|
||||||
|
|
||||||
bindsym $mod+Tab focus right
|
|
||||||
bindsym $mod+Shift+Tab focus left
|
|
||||||
|
|
||||||
# Move the focused window with the same, but add Shift
|
|
||||||
bindsym $mod+Shift+$left move left
|
|
||||||
bindsym $mod+Shift+$down move down
|
|
||||||
bindsym $mod+Shift+$up move up
|
|
||||||
bindsym $mod+Shift+$right move right
|
|
||||||
|
|
||||||
bindsym $mod+greater move workspace to output right
|
|
||||||
bindsym $mod+less move workspace to output left
|
|
||||||
|
|
||||||
# Workspaces
|
|
||||||
# Switch to workspace
|
|
||||||
bindsym $mod+1 workspace number 1
|
|
||||||
bindsym $mod+2 workspace number 2
|
|
||||||
bindsym $mod+3 workspace number 3
|
|
||||||
bindsym $mod+4 workspace number 4
|
|
||||||
bindsym $mod+5 workspace number 5
|
|
||||||
bindsym $mod+6 workspace number 6
|
|
||||||
bindsym $mod+7 workspace number 7
|
|
||||||
bindsym $mod+8 workspace number 8
|
|
||||||
bindsym $mod+9 workspace number 9
|
|
||||||
bindsym $mod+0 workspace number 10
|
|
||||||
# Move focused container to workspace
|
|
||||||
bindsym $mod+Shift+1 move container to workspace number 1
|
|
||||||
bindsym $mod+Shift+2 move container to workspace number 2
|
|
||||||
bindsym $mod+Shift+3 move container to workspace number 3
|
|
||||||
bindsym $mod+Shift+4 move container to workspace number 4
|
|
||||||
bindsym $mod+Shift+5 move container to workspace number 5
|
|
||||||
bindsym $mod+Shift+6 move container to workspace number 6
|
|
||||||
bindsym $mod+Shift+7 move container to workspace number 7
|
|
||||||
bindsym $mod+Shift+8 move container to workspace number 8
|
|
||||||
bindsym $mod+Shift+9 move container to workspace number 9
|
|
||||||
bindsym $mod+Shift+0 move container to workspace number 10
|
|
||||||
|
|
||||||
# Scratchpad
|
|
||||||
bindsym $mod+minus move scratchpad
|
|
||||||
bindsym $mod+equal scratchpad show, border pixel none
|
|
||||||
|
|
||||||
# Layout stuff
|
|
||||||
bindsym $mod+f fullscreen
|
|
||||||
bindsym $mod+Shift+space floating toggle; \
|
|
||||||
[tiling con_id=__focused__] border pixel none; \
|
|
||||||
[floating con_id=__focused__] border pixel none
|
|
||||||
|
|
||||||
### Theming
|
|
||||||
|
|
||||||
# title bar
|
|
||||||
font pango: JetBrainsMono Nerd Font Mono 10
|
|
||||||
for_window [title="."] title_format "%app_id"
|
|
||||||
|
|
||||||
# border
|
|
||||||
hide_edge_borders --i3 smart
|
|
||||||
default_border pixel none
|
|
||||||
default_floating_border pixel none
|
|
||||||
|
|
||||||
# inactive
|
|
||||||
default_dim_inactive 0.1
|
|
||||||
|
|
||||||
# gaps
|
|
||||||
gaps inner 6
|
|
||||||
smart_gaps off
|
|
||||||
|
|
||||||
# swayfx start
|
|
||||||
blur enable
|
|
||||||
shadows enable
|
|
||||||
corner_radius 6
|
|
||||||
layer_effects "waybar" blur enable; shadows enable; corner_radius 6
|
|
||||||
layer_effects "launcher" blur enable; shadows enable
|
|
||||||
# swayfx end
|
|
||||||
|
|
||||||
bar {
|
|
||||||
swaybar_command waybar
|
|
||||||
}
|
|
||||||
|
|
||||||
client.focused #A7C080 #A7C080 #2D353B #A7C080 #A7C080
|
|
||||||
client.focused_inactive #3D484D #2D353B #859289 #3D484D #3D484D
|
|
||||||
client.unfocused #3D484D #2D353B #859289 #3D484D #3D484D
|
|
||||||
client.urgent #E67E80 #E67E80 #2D353B #E67E80 #E67E80
|
|
||||||
client.placeholder #2D353B #2D353B #859289 #2D353B #2D353B
|
|
||||||
client.background #2D353B
|
|
||||||
|
|
||||||
### Autostart
|
|
||||||
|
|
||||||
# SSH - SSH_AUTH_SOCK must be exported as env
|
|
||||||
exec gnome-keyring-daemon --start
|
|
||||||
|
|
||||||
# Programs
|
|
||||||
exec trayscale --hide-window
|
|
||||||
exec nm-applet
|
|
||||||
exec swaync
|
|
||||||
exec nextcloud --background
|
|
||||||
exec pasystray
|
|
||||||
exec fcitx5
|
|
||||||
exec gammastep-indicator -l 48:11.5
|
|
||||||
|
|
||||||
### Sway defaults
|
|
||||||
include /etc/sway/config.d/*
|
|
||||||
@@ -1,925 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# https://github.com/moverest/sway-interactive-screenshot
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
|
||||||
from functools import partial
|
|
||||||
from typing import (
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Iterable,
|
|
||||||
Iterator,
|
|
||||||
List,
|
|
||||||
NamedTuple,
|
|
||||||
Union,
|
|
||||||
Optional,
|
|
||||||
Set,
|
|
||||||
Tuple,
|
|
||||||
Type,
|
|
||||||
)
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
try:
|
|
||||||
import tomllib
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
import tomli as tomllib
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(NamedTuple):
|
|
||||||
path: str
|
|
||||||
error: str
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_VALIDATOR = Callable[[str, Any], Iterator[ValidationError]]
|
|
||||||
|
|
||||||
|
|
||||||
def config_dict_validator(
|
|
||||||
field_validators: Dict[str, CONFIG_VALIDATOR]
|
|
||||||
) -> CONFIG_VALIDATOR:
|
|
||||||
def validate(path: str, value: Any) -> Iterator[ValidationError]:
|
|
||||||
if not isinstance(value, Dict):
|
|
||||||
yield ValidationError(path, "Should be a dictionary.")
|
|
||||||
return
|
|
||||||
|
|
||||||
unknown_fields = set(value).difference(field_validators)
|
|
||||||
for unknown_field in unknown_fields:
|
|
||||||
yield ValidationError(f"{path}.{unknown_field}", "Field does not exist.")
|
|
||||||
|
|
||||||
for field_name, field_value in value.items():
|
|
||||||
if field_name not in field_validators:
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield from field_validators[field_name](f"{path}.{field_name}", field_value)
|
|
||||||
|
|
||||||
return validate
|
|
||||||
|
|
||||||
|
|
||||||
def config_type_validator(*types: Type) -> CONFIG_VALIDATOR:
|
|
||||||
def validate(path: str, value: Any) -> Iterator[ValidationError]:
|
|
||||||
if not isinstance(value, types):
|
|
||||||
yield ValidationError(
|
|
||||||
path, f"Type {type(value)} is not one of {', '.join(types)}."
|
|
||||||
)
|
|
||||||
|
|
||||||
return validate
|
|
||||||
|
|
||||||
|
|
||||||
def config_enum_validator(values: Set[Any]) -> CONFIG_VALIDATOR:
|
|
||||||
def validate(path: str, value: Any) -> Iterator[ValidationError]:
|
|
||||||
if value not in values:
|
|
||||||
yield ValidationError(path, f"Value '{value}' not one of {values}.")
|
|
||||||
|
|
||||||
return validate
|
|
||||||
|
|
||||||
def config_list_validator(*types: Type) -> CONFIG_VALIDATOR:
|
|
||||||
def validate(path: str, value: Any) -> Iterator[ValidationError]:
|
|
||||||
if not isinstance(value, list):
|
|
||||||
yield ValidationError(path, f"Expected a list")
|
|
||||||
return
|
|
||||||
|
|
||||||
for i, element in enumerate(value):
|
|
||||||
if not isinstance(element, types):
|
|
||||||
yield ValidationError(
|
|
||||||
f"{path}[{i}]", f"Type {type(value)} is not one of {', '.join(types)}."
|
|
||||||
)
|
|
||||||
|
|
||||||
return validate
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigError(Exception):
|
|
||||||
def __init__(self, errors: List[ValidationError]) -> None:
|
|
||||||
super().__init__(
|
|
||||||
"Config errors: \n"
|
|
||||||
+ "\n".join(f" {error.path}: {error.error}" for error in errors)
|
|
||||||
)
|
|
||||||
self.errors = errors
|
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
CONFIG_DEFAULT = {
|
|
||||||
"screenshot": {
|
|
||||||
"prompt": "📷> ",
|
|
||||||
"file_name": "screenshots/screenshot_%Y-%m-%dT%H:%M:%S.png",
|
|
||||||
},
|
|
||||||
"screencast": {
|
|
||||||
"prompt": "📹> ",
|
|
||||||
"pid_file": "${XDG_RUNTIME_DIR}/sway-interactive-screenshot.${WAYLAND_DISPLAY}.video.pid",
|
|
||||||
"file_name": "screencast_%Y-%m-%dT%H:%M:%S.mkv",
|
|
||||||
"audio": "ask",
|
|
||||||
},
|
|
||||||
"notification_actions": {
|
|
||||||
"dragon": {
|
|
||||||
"command": "dragon-drop",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ask" : {
|
|
||||||
"command" : "rofi",
|
|
||||||
"args": [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_validate = staticmethod(
|
|
||||||
config_dict_validator(
|
|
||||||
{
|
|
||||||
"screenshot": config_dict_validator(
|
|
||||||
{
|
|
||||||
"prompt": config_type_validator(str),
|
|
||||||
"save_dir": config_type_validator(str),
|
|
||||||
"file_name": config_type_validator(str),
|
|
||||||
"type": config_enum_validator({"png", "jpeg", "ppm"}),
|
|
||||||
"jpeg_quality": config_type_validator(int),
|
|
||||||
"png_level": config_type_validator(int),
|
|
||||||
"cursor": config_type_validator(bool),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"screencast": config_dict_validator(
|
|
||||||
{
|
|
||||||
"prompt": config_type_validator(str),
|
|
||||||
"save_dir": config_type_validator(str),
|
|
||||||
"pid_file": config_type_validator(str),
|
|
||||||
"file_name": config_type_validator(str),
|
|
||||||
"audio": config_enum_validator({"yes", "no", "ask"}),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"notification_actions": config_dict_validator(
|
|
||||||
{
|
|
||||||
"dragon": config_dict_validator(
|
|
||||||
{
|
|
||||||
"command": config_type_validator(str),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"ask" : config_dict_validator(
|
|
||||||
{
|
|
||||||
"command" : config_type_validator(str),
|
|
||||||
"args" : config_list_validator(str),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
config: Dict[str, Any]
|
|
||||||
|
|
||||||
def __init__(self, filepath: Union[Path, str, None]) -> None:
|
|
||||||
self._load(filepath)
|
|
||||||
errors = list(self._validate("", self.config))
|
|
||||||
if errors:
|
|
||||||
raise ConfigError(errors)
|
|
||||||
|
|
||||||
def get(self, *path: str) -> Any:
|
|
||||||
value: Any = self.config
|
|
||||||
for item in path:
|
|
||||||
if item not in value:
|
|
||||||
default: Any = self.CONFIG_DEFAULT
|
|
||||||
for item in path:
|
|
||||||
default = default.get(item)
|
|
||||||
if default is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return default
|
|
||||||
|
|
||||||
value = value.get(item)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def _load(self, filepath: Union[Path, str, None]) -> None:
|
|
||||||
if filepath is None:
|
|
||||||
xdg_config_home = os.getenv("XDG_CONFIG_HOME", "~/.config")
|
|
||||||
filepath = Path(xdg_config_home) / "sway-interactive-screenshot/config.toml"
|
|
||||||
|
|
||||||
filepath = Path(filepath).expanduser()
|
|
||||||
|
|
||||||
if filepath.exists():
|
|
||||||
with open(filepath, "rb") as f:
|
|
||||||
self.config = tomllib.load(f)
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.config = {}
|
|
||||||
|
|
||||||
|
|
||||||
class CanceledError(Exception):
|
|
||||||
def __init__(self, msg: Optional[str] = None) -> None:
|
|
||||||
super().__init__(msg)
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
|
|
||||||
class Area(NamedTuple):
|
|
||||||
output: Optional[str] = None
|
|
||||||
geometry: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Window(NamedTuple):
|
|
||||||
name: str
|
|
||||||
x: int
|
|
||||||
y: int
|
|
||||||
width: int
|
|
||||||
height: int
|
|
||||||
focused: bool
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"window: {self.name}"
|
|
||||||
|
|
||||||
def get_geometry_str(self) -> str:
|
|
||||||
return f"{self.x},{self.y} {self.width}x{self.height}"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
return Area(geometry=self.get_geometry_str())
|
|
||||||
|
|
||||||
|
|
||||||
class AllOutputs:
|
|
||||||
selection_name = "all-outputs"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "all outputs"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
return Area()
|
|
||||||
|
|
||||||
|
|
||||||
class Region:
|
|
||||||
selection_name = "region"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "region"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
geometry = ask_geometry()
|
|
||||||
if geometry is None:
|
|
||||||
raise CanceledError("No region selected.")
|
|
||||||
|
|
||||||
return Area(geometry=geometry)
|
|
||||||
|
|
||||||
|
|
||||||
class FocusedWindow:
|
|
||||||
selection_name = "focused-window"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "focused window"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
window = next(
|
|
||||||
(window for window in get_windows() if window.focused),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if window is None:
|
|
||||||
raise CanceledError("Could not find any focused window.")
|
|
||||||
|
|
||||||
return window.get_area()
|
|
||||||
|
|
||||||
|
|
||||||
class SelectWindow:
|
|
||||||
selection_name = "select-window"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "select window"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
windows = list(get_windows())
|
|
||||||
if not windows:
|
|
||||||
raise CanceledError("No visible window found.")
|
|
||||||
|
|
||||||
geometry = ask_geometry(window.get_geometry_str() for window in windows)
|
|
||||||
if geometry is None:
|
|
||||||
raise CanceledError("No window selected.")
|
|
||||||
|
|
||||||
return Area(geometry=geometry)
|
|
||||||
|
|
||||||
|
|
||||||
class FocusedOutput:
|
|
||||||
selection_name = "focused-output"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "focused output"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
output = next(
|
|
||||||
(output for output in get_outputs() if output.focused),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if output is None:
|
|
||||||
raise CanceledError("Could not find any focused output.")
|
|
||||||
|
|
||||||
return output.get_area()
|
|
||||||
|
|
||||||
|
|
||||||
class Output(NamedTuple):
|
|
||||||
name: str
|
|
||||||
model: Optional[str]
|
|
||||||
focused: bool
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
model = f" ({self.model})" if self.model else ""
|
|
||||||
focused = " (focused)" if self.focused else ""
|
|
||||||
return f"output: {self.name}{model}{focused}"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
return Area(output=self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class SelectOutput:
|
|
||||||
selection_name = "select-output"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "select output"
|
|
||||||
|
|
||||||
def get_area(self) -> Area:
|
|
||||||
output = ask_output()
|
|
||||||
if output is None:
|
|
||||||
raise CanceledError("No output selected.")
|
|
||||||
|
|
||||||
return Area(output=output)
|
|
||||||
|
|
||||||
|
|
||||||
def get_windows() -> Iterator[Window]:
|
|
||||||
def walk(node: Dict[str, Any]) -> Iterator[Window]:
|
|
||||||
sub_nodes = node.get("floating_nodes")
|
|
||||||
if sub_nodes:
|
|
||||||
for sub_node in sub_nodes:
|
|
||||||
yield from walk(sub_node)
|
|
||||||
sub_nodes = node.get("nodes")
|
|
||||||
if sub_nodes:
|
|
||||||
for sub_node in sub_nodes:
|
|
||||||
yield from walk(sub_node)
|
|
||||||
elif node.get("visible") and node.get("pid"):
|
|
||||||
rect = node["rect"]
|
|
||||||
yield Window(
|
|
||||||
name=node["name"],
|
|
||||||
x=rect["x"],
|
|
||||||
y=rect["y"],
|
|
||||||
width=rect["width"],
|
|
||||||
height=rect["height"],
|
|
||||||
focused=node["focused"],
|
|
||||||
)
|
|
||||||
|
|
||||||
process = subprocess.run(
|
|
||||||
["swaymsg", "-t", "get_tree"],
|
|
||||||
capture_output=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
tree = json.loads(process.stdout.decode())
|
|
||||||
return walk(tree)
|
|
||||||
|
|
||||||
|
|
||||||
def get_outputs() -> Iterator[Output]:
|
|
||||||
process = subprocess.run(
|
|
||||||
["swaymsg", "-t", "get_outputs"],
|
|
||||||
capture_output=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
for output in json.loads(process.stdout.decode()):
|
|
||||||
if output["active"]:
|
|
||||||
yield Output(
|
|
||||||
name=output["name"],
|
|
||||||
model=output["model"],
|
|
||||||
focused=output["focused"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ask_rofi(
|
|
||||||
choices: List[Any],
|
|
||||||
*,
|
|
||||||
prompt: Optional[str] = None,
|
|
||||||
lines: Optional[int] = None,
|
|
||||||
index: bool = False,
|
|
||||||
additional_args: Optional[List[str]] = None
|
|
||||||
) -> Union[int, str, None]:
|
|
||||||
args = ["rofi", "-dmenu"]
|
|
||||||
|
|
||||||
if index:
|
|
||||||
args.extend(("-format","i"))
|
|
||||||
|
|
||||||
if prompt is not None:
|
|
||||||
args.extend(("-p", prompt))
|
|
||||||
|
|
||||||
if lines is not None:
|
|
||||||
args.extend(("-l", str(lines)))
|
|
||||||
|
|
||||||
if additional_args:
|
|
||||||
args.extend(additional_args)
|
|
||||||
|
|
||||||
process = subprocess.run(
|
|
||||||
args,
|
|
||||||
input=b"\n".join(str(choice).encode() for choice in choices),
|
|
||||||
capture_output=True,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if process.returncode != 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
stdout = process.stdout.decode()
|
|
||||||
|
|
||||||
breakpoint()
|
|
||||||
|
|
||||||
return int(stdout) if index else stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def ask_fuzzel(
|
|
||||||
choices: List[Any],
|
|
||||||
*,
|
|
||||||
prompt: Optional[str] = None,
|
|
||||||
lines: Optional[int] = None,
|
|
||||||
index: bool = False,
|
|
||||||
additional_args: Optional[List[str]] = None
|
|
||||||
) -> Union[int, str, None]:
|
|
||||||
args = ["fuzzel", "--dmenu"]
|
|
||||||
|
|
||||||
if index:
|
|
||||||
args.append("--index")
|
|
||||||
|
|
||||||
if prompt is not None:
|
|
||||||
args.extend(("--prompt", prompt))
|
|
||||||
|
|
||||||
if lines is not None:
|
|
||||||
args.extend(("--lines", str(lines)))
|
|
||||||
|
|
||||||
if additional_args:
|
|
||||||
args.extend(additional_args)
|
|
||||||
|
|
||||||
process = subprocess.run(
|
|
||||||
args,
|
|
||||||
input=b"\n".join(str(choice).encode() for choice in choices),
|
|
||||||
capture_output=True,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if process.returncode != 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
stdout = process.stdout.decode()
|
|
||||||
|
|
||||||
return int(stdout) if index else stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
ASK_FNS = {
|
|
||||||
"rofi" : ask_rofi,
|
|
||||||
"fuzzel" : ask_fuzzel
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ask = ask_fuzzel
|
|
||||||
|
|
||||||
def notify(
|
|
||||||
title: str,
|
|
||||||
summary: Optional[str] = None,
|
|
||||||
*,
|
|
||||||
icon: Optional[str] = None,
|
|
||||||
actions: Optional[List[Tuple[str, str]]] = None,
|
|
||||||
) -> Optional[str]:
|
|
||||||
args = ["notify-send"]
|
|
||||||
|
|
||||||
if icon is not None:
|
|
||||||
args.extend(("--icon", icon))
|
|
||||||
|
|
||||||
for action_name, action_desc in actions or ():
|
|
||||||
args.extend(("-A", f"{action_name}={action_desc}"))
|
|
||||||
|
|
||||||
args.extend(("--", title))
|
|
||||||
if summary:
|
|
||||||
args.append(summary)
|
|
||||||
|
|
||||||
process = subprocess.run(args, capture_output=True)
|
|
||||||
if process.returncode != 0:
|
|
||||||
print("Error while sending notification.", file=sys.stderr)
|
|
||||||
print(process.stderr.decode(), file=sys.stderr)
|
|
||||||
|
|
||||||
return process.stdout.decode().strip() or None
|
|
||||||
|
|
||||||
|
|
||||||
def take_screenshot(
|
|
||||||
*,
|
|
||||||
filepath: str,
|
|
||||||
geometry: Optional[str] = None,
|
|
||||||
output: Optional[str] = None,
|
|
||||||
cursor: Optional[bool] = None,
|
|
||||||
type_: Optional[str] = None,
|
|
||||||
jpeg_quality: Optional[int] = None,
|
|
||||||
png_level: Optional[int] = None,
|
|
||||||
) -> None:
|
|
||||||
args = ["grim"]
|
|
||||||
|
|
||||||
if geometry is not None:
|
|
||||||
args.extend(("-g", geometry))
|
|
||||||
|
|
||||||
if output is not None:
|
|
||||||
args.extend(("-o", output))
|
|
||||||
|
|
||||||
if cursor:
|
|
||||||
args.append("-c")
|
|
||||||
|
|
||||||
if type_ is not None:
|
|
||||||
args.extend(("-t", type_))
|
|
||||||
|
|
||||||
if png_level is not None and (type == "png" or filepath.endswith(".png")):
|
|
||||||
args.extend(("-l", str(png_level)))
|
|
||||||
|
|
||||||
if jpeg_quality is not None and (
|
|
||||||
type == "jpeg" or filepath.endswith(".jpg") or filepath.endswith(".jpeg")
|
|
||||||
):
|
|
||||||
args.extend(("-q", str(jpeg_quality)))
|
|
||||||
|
|
||||||
args.append(filepath)
|
|
||||||
subprocess.run(args, check=True)
|
|
||||||
|
|
||||||
|
|
||||||
def take_screencast(
|
|
||||||
*,
|
|
||||||
filepath: str,
|
|
||||||
geometry: Optional[str] = None,
|
|
||||||
output: Optional[str] = None,
|
|
||||||
audio: Union[bool, str, None] = None,
|
|
||||||
pid_file: Optional[Path] = None,
|
|
||||||
) -> None:
|
|
||||||
args = ["wf-recorder", "-f", filepath]
|
|
||||||
|
|
||||||
if geometry is not None:
|
|
||||||
args.extend(("-g", geometry))
|
|
||||||
|
|
||||||
if output is not None:
|
|
||||||
args.extend(("-o", output))
|
|
||||||
|
|
||||||
if audio is not None:
|
|
||||||
if audio is True:
|
|
||||||
args.append("-a")
|
|
||||||
else:
|
|
||||||
args.extend(("-a", str(audio)))
|
|
||||||
|
|
||||||
with subprocess.Popen(args) as process:
|
|
||||||
try:
|
|
||||||
if pid_file:
|
|
||||||
pid_file.write_text(str(process.pid))
|
|
||||||
process.wait()
|
|
||||||
if process.returncode != 0:
|
|
||||||
raise Exception(
|
|
||||||
f"wf-recorder exited with status code {process.returncode}"
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
if pid_file:
|
|
||||||
pid_file.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def ask_geometry(geometries: Optional[Iterable[str]] = None) -> Optional[str]:
|
|
||||||
if geometries is not None:
|
|
||||||
geomerties_input = b"\n".join(geometry.encode() for geometry in geometries)
|
|
||||||
else:
|
|
||||||
geomerties_input = None
|
|
||||||
|
|
||||||
process = subprocess.run(
|
|
||||||
["slurp"],
|
|
||||||
capture_output=True,
|
|
||||||
input=geomerties_input,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
if process.returncode != 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return process.stdout.decode().strip()
|
|
||||||
|
|
||||||
|
|
||||||
def ask_output() -> Optional[str]:
|
|
||||||
process = subprocess.run(
|
|
||||||
["slurp", "-o", "-f", "%o"], capture_output=True, check=False
|
|
||||||
)
|
|
||||||
if process.returncode != 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return process.stdout.decode().strip()
|
|
||||||
|
|
||||||
|
|
||||||
def edit_capture(filepath: str) -> None:
|
|
||||||
subprocess.run(["swappy", "-f", filepath, "-o", filepath], check=False)
|
|
||||||
|
|
||||||
|
|
||||||
def copy_file_to_clipboard(filepath: str) -> None:
|
|
||||||
with open(filepath, "rb") as file:
|
|
||||||
with subprocess.Popen("wl-copy", stdin=file) as process:
|
|
||||||
process.wait()
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationAction(abc.ABC):
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abc.abstractmethod
|
|
||||||
def run(cls, *, filepath: Path, config_getter: Callable[[str], Any]) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EditNotificationAction(NotificationAction):
|
|
||||||
name = "default"
|
|
||||||
description = "Edit"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def run(cls, *, filepath: Path, config_getter: Callable[[str], Any]) -> None:
|
|
||||||
filepath_str = str(filepath.expanduser())
|
|
||||||
edit_capture(filepath_str)
|
|
||||||
copy_file_to_clipboard(filepath_str)
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteNotificationAction(NotificationAction):
|
|
||||||
name = "delete"
|
|
||||||
description = "Delete"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def run(cls, *, filepath: Path, config_getter: Callable[[str], Any]) -> None:
|
|
||||||
filepath.expanduser().unlink()
|
|
||||||
notify("Deleted")
|
|
||||||
|
|
||||||
|
|
||||||
class DragonNotificationAction(NotificationAction):
|
|
||||||
name = "dragon"
|
|
||||||
description = "Drag and drop"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def run(cls, *, filepath: Path, config_getter: Callable[[str], Any]) -> None:
|
|
||||||
subprocess.run([config_getter("command"), filepath.expanduser()], check=False)
|
|
||||||
|
|
||||||
|
|
||||||
class OpenNotificationAction(NotificationAction):
|
|
||||||
name = "open"
|
|
||||||
description = "Open"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def run(cls, *, filepath: Path, config_getter: Callable[[str], Any]) -> None:
|
|
||||||
subprocess.run(["xdg-open", filepath.expanduser()], check=False)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
|
||||||
argparser = argparse.ArgumentParser(
|
|
||||||
prog="sway-interactive-screenshot",
|
|
||||||
description="Interactively take screenshots with Sway.",
|
|
||||||
)
|
|
||||||
selections = (
|
|
||||||
Region,
|
|
||||||
FocusedOutput,
|
|
||||||
AllOutputs,
|
|
||||||
SelectOutput,
|
|
||||||
FocusedWindow,
|
|
||||||
SelectWindow,
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
"-s",
|
|
||||||
"--selection",
|
|
||||||
choices=[selection.selection_name for selection in selections],
|
|
||||||
help="Selection mode.",
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
"--save-dir",
|
|
||||||
help="Directory where screenshots are saved.",
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--output",
|
|
||||||
help="Output file name. If set, --save-dir is ignored.",
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
"--video",
|
|
||||||
help="Make a screen video recording.",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
"-c",
|
|
||||||
"--config",
|
|
||||||
help="Config file.",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = argparser.parse_args()
|
|
||||||
if args.selection is not None:
|
|
||||||
args.selection = next(
|
|
||||||
(
|
|
||||||
selection
|
|
||||||
for selection in selections
|
|
||||||
if selection.selection_name == args.selection
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
class CaptureMode(abc.ABC):
|
|
||||||
def __init__(self, config_getter: Callable[[str], Any]) -> None:
|
|
||||||
self.config_getter = config_getter
|
|
||||||
|
|
||||||
def get_prompt(self) -> str:
|
|
||||||
return self.config_getter("prompt")
|
|
||||||
|
|
||||||
def get_filename(self) -> str:
|
|
||||||
return datetime.now().strftime(self.config_getter("file_name"))
|
|
||||||
|
|
||||||
def get_save_dir(self) -> str:
|
|
||||||
return self.config_getter("save_dir")
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_display_name(self) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def early_exit(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def capture(self, *, area: Area, filepath: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_notification_actions(self) -> Iterable[Type[NotificationAction]]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_notification_icon(self, filepath: str) -> Optional[str]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Screenshot(CaptureMode):
|
|
||||||
def get_display_name(self) -> str:
|
|
||||||
return "Screenshot"
|
|
||||||
|
|
||||||
def capture(self, *, area: Area, filepath: str) -> None:
|
|
||||||
take_screenshot(
|
|
||||||
filepath=filepath,
|
|
||||||
geometry=area.geometry,
|
|
||||||
output=area.output,
|
|
||||||
cursor=self.config_getter("cursor"),
|
|
||||||
type_=self.config_getter("type"),
|
|
||||||
jpeg_quality=self.config_getter("jpeg_quality"),
|
|
||||||
png_level=self.config_getter("png_level"),
|
|
||||||
)
|
|
||||||
copy_file_to_clipboard(filepath)
|
|
||||||
|
|
||||||
def get_notification_actions(self) -> Iterable[Type[NotificationAction]]:
|
|
||||||
return (
|
|
||||||
EditNotificationAction,
|
|
||||||
DeleteNotificationAction,
|
|
||||||
DragonNotificationAction,
|
|
||||||
OpenNotificationAction,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_notification_icon(self, filepath: str) -> Optional[str]:
|
|
||||||
return filepath
|
|
||||||
|
|
||||||
|
|
||||||
class Screencast(CaptureMode):
|
|
||||||
def get_display_name(self) -> str:
|
|
||||||
return "Screencast"
|
|
||||||
|
|
||||||
def get_pid_filepath(self) -> Path:
|
|
||||||
uid = os.getuid()
|
|
||||||
xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR", f"/run/user/{uid}")
|
|
||||||
wayland_display = os.getenv("WAYLAND_DISPLAY", "wayland-1")
|
|
||||||
|
|
||||||
return Path(
|
|
||||||
self.config_getter("pid_file")
|
|
||||||
.replace("${XDG_RUNTIME_DIR}", xdg_runtime_dir)
|
|
||||||
.replace("${WAYLAND_DISPLAY}", wayland_display)
|
|
||||||
.replace("${UID}", str(uid))
|
|
||||||
)
|
|
||||||
|
|
||||||
def early_exit(self) -> bool:
|
|
||||||
pid_filepath = self.get_pid_filepath()
|
|
||||||
if pid_filepath.exists():
|
|
||||||
pid = int(pid_filepath.read_text())
|
|
||||||
print(
|
|
||||||
f"Sending kill signal to {pid} to stop video recording.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
os.kill(pid, signal.SIGINT)
|
|
||||||
notify(self.get_display_name(), "Stopping recording.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def capture(self, *, area: Area, filepath: str) -> None:
|
|
||||||
audio_raw_input = self.config_getter("audio")
|
|
||||||
if audio_raw_input == "ask":
|
|
||||||
audio_raw_input = ask(["yes", "no"], prompt=self.get_prompt() + "Audio? ")
|
|
||||||
if audio_raw_input is None:
|
|
||||||
raise CanceledError("No audio preference selected.")
|
|
||||||
|
|
||||||
start_recording = ask(
|
|
||||||
["yes", "cancel"], prompt=self.get_prompt() + "Start recording? "
|
|
||||||
)
|
|
||||||
if start_recording != "yes":
|
|
||||||
raise CanceledError()
|
|
||||||
|
|
||||||
take_screencast(
|
|
||||||
filepath=filepath,
|
|
||||||
geometry=area.geometry,
|
|
||||||
output=area.output,
|
|
||||||
audio=audio_raw_input == "yes",
|
|
||||||
pid_file=self.get_pid_filepath(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_notification_actions(self) -> Iterable[Type[NotificationAction]]:
|
|
||||||
return (
|
|
||||||
DeleteNotificationAction,
|
|
||||||
DragonNotificationAction,
|
|
||||||
OpenNotificationAction,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
mode: Optional[CaptureMode] = None
|
|
||||||
try:
|
|
||||||
args = parse_arguments()
|
|
||||||
config = Config(args.config)
|
|
||||||
mode: CaptureMode = (
|
|
||||||
Screenshot(partial(config.get, "screenshot"))
|
|
||||||
if not args.video
|
|
||||||
else Screencast(partial(config.get, "screencast"))
|
|
||||||
)
|
|
||||||
if mode.early_exit():
|
|
||||||
return
|
|
||||||
|
|
||||||
global ask
|
|
||||||
ask = partial(
|
|
||||||
ASK_FNS[config.get("ask","command")],
|
|
||||||
additional_args=config.get("ask","args")
|
|
||||||
)
|
|
||||||
|
|
||||||
save_dir = args.save_dir
|
|
||||||
if save_dir is None:
|
|
||||||
save_dir = mode.get_save_dir()
|
|
||||||
if save_dir is None:
|
|
||||||
save_dir = os.getenv("SWAY_INTERACTIVE_SCREENSHOT_SAVEDIR", "~")
|
|
||||||
|
|
||||||
save_dir = Path(save_dir)
|
|
||||||
save_dir.expanduser().mkdir(parents=True, exist_ok=True)
|
|
||||||
filepath = Path(args.output) if args.output else None
|
|
||||||
|
|
||||||
if args.selection is None:
|
|
||||||
choices = [
|
|
||||||
Region(),
|
|
||||||
FocusedOutput(),
|
|
||||||
*((AllOutputs(),) if isinstance(mode, Screenshot) else ()),
|
|
||||||
SelectOutput(),
|
|
||||||
*get_outputs(),
|
|
||||||
FocusedWindow(),
|
|
||||||
SelectWindow(),
|
|
||||||
*get_windows(),
|
|
||||||
]
|
|
||||||
|
|
||||||
choice_idx = ask(choices, prompt=mode.get_prompt(), index=True)
|
|
||||||
if choice_idx is None:
|
|
||||||
return
|
|
||||||
if choice_idx == -1:
|
|
||||||
raise CanceledError("No option selected.")
|
|
||||||
|
|
||||||
choice = choices[choice_idx]
|
|
||||||
else:
|
|
||||||
choice = args.selection()
|
|
||||||
|
|
||||||
area = choice.get_area()
|
|
||||||
if not filepath:
|
|
||||||
filepath = save_dir / mode.get_filename()
|
|
||||||
|
|
||||||
mode.capture(filepath=str(filepath.expanduser()), area=area)
|
|
||||||
|
|
||||||
action_name = notify(
|
|
||||||
mode.get_display_name(),
|
|
||||||
summary=f"File saved as <i>{filepath}</i>.",
|
|
||||||
icon=mode.get_notification_icon(filepath.expanduser()),
|
|
||||||
actions=[
|
|
||||||
(action.name, action.description)
|
|
||||||
for action in mode.get_notification_actions()
|
|
||||||
],
|
|
||||||
)
|
|
||||||
action = next(
|
|
||||||
(
|
|
||||||
action
|
|
||||||
for action in mode.get_notification_actions()
|
|
||||||
if action.name == action_name
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
|
|
||||||
if action is not None:
|
|
||||||
action.run(filepath=filepath,
|
|
||||||
config_getter=partial(config.get,
|
|
||||||
"notification_actions",
|
|
||||||
action.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as err: # pylint: disable=broad-except
|
|
||||||
display_name = (
|
|
||||||
mode.get_display_name()
|
|
||||||
if mode is not None
|
|
||||||
else "sway-interactive-screenshot"
|
|
||||||
)
|
|
||||||
if isinstance(err, CanceledError):
|
|
||||||
notify(f"{display_name} canceled", summary=err.msg)
|
|
||||||
else:
|
|
||||||
notify(f"{display_name} error", str(err))
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
* {
|
|
||||||
all: unset;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: "JetBrainsMono Nerd Font Mono";
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
trough highlight {
|
|
||||||
background: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale trough {
|
|
||||||
margin: 0rem 1rem;
|
|
||||||
background-color: #A7C080;
|
|
||||||
min-height: 8px;
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
slider {
|
|
||||||
background-color: #89b4fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background {
|
|
||||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #A7C080;
|
|
||||||
border-radius: 12.6px;
|
|
||||||
margin: 18px;
|
|
||||||
background-color: #1e1e2e;
|
|
||||||
color: #cdd6f4;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification {
|
|
||||||
padding: 7px;
|
|
||||||
border-radius: 12.6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification.critical {
|
|
||||||
box-shadow: inset 0 0 7px 0 #A7C080;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification .notification-content {
|
|
||||||
margin: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification .notification-content .summary {
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification .notification-content .time {
|
|
||||||
color: #a6adc8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification .notification-content .body {
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification > *:last-child > * {
|
|
||||||
min-height: 3.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification > *:last-child > * .notification-action {
|
|
||||||
border-radius: 7px;
|
|
||||||
color: #1e1e2e;
|
|
||||||
background-color: #A7C080;
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
margin: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification > *:last-child > * .notification-action:hover {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #A7C080;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .notification > *:last-child > * .notification-action:active {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #74c7ec;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .close-button {
|
|
||||||
margin: 7px;
|
|
||||||
padding: 2px;
|
|
||||||
border-radius: 6.3px;
|
|
||||||
color: #1e1e2e;
|
|
||||||
background-color: #A7C080;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .close-button:hover {
|
|
||||||
background-color: #eba0ac;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-notifications.background .notification-row .notification-background .close-button:active {
|
|
||||||
background-color: #f38ba8;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center {
|
|
||||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #A7C080;
|
|
||||||
border-radius: 12.6px;
|
|
||||||
margin: 18px;
|
|
||||||
background-color: #1e1e2e;
|
|
||||||
color: #cdd6f4;
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .widget-title > label {
|
|
||||||
color: #cdd6f4;
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .widget-title button {
|
|
||||||
border-radius: 7px;
|
|
||||||
color: #cdd6f4;
|
|
||||||
background-color: #A7C080;
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .widget-title button:hover {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #585b70;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .widget-title button:active {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #74c7ec;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background {
|
|
||||||
border-radius: 7px;
|
|
||||||
color: #cdd6f4;
|
|
||||||
background-color: #A7C080;
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
margin-top: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification {
|
|
||||||
padding: 7px;
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification.critical {
|
|
||||||
box-shadow: inset 0 0 7px 0 #f38ba8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification .notification-content {
|
|
||||||
margin: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification .notification-content .summary {
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification .notification-content .time {
|
|
||||||
color: #a6adc8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification .notification-content .body {
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification > *:last-child > * {
|
|
||||||
min-height: 3.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification > *:last-child > * .notification-action {
|
|
||||||
border-radius: 7px;
|
|
||||||
color: #cdd6f4;
|
|
||||||
background-color: #11111b;
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
margin: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification > *:last-child > * .notification-action:hover {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #A7C080;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .notification > *:last-child > * .notification-action:active {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #74c7ec;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .close-button {
|
|
||||||
margin: 7px;
|
|
||||||
padding: 2px;
|
|
||||||
border-radius: 6.3px;
|
|
||||||
color: #1e1e2e;
|
|
||||||
background-color: #eba0ac;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
border-radius: 6.3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .close-button:hover {
|
|
||||||
background-color: #f38ba8;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background .close-button:active {
|
|
||||||
background-color: #f38ba8;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background:hover {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #7f849c;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .notification-row .notification-background:active {
|
|
||||||
box-shadow: inset 0 0 0 1px #45475a;
|
|
||||||
background-color: #74c7ec;
|
|
||||||
color: #cdd6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.critical progress {
|
|
||||||
background-color: #f38ba8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.low progress,
|
|
||||||
.notification.normal progress {
|
|
||||||
background-color: #89b4fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center-dnd {
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #A7C080;
|
|
||||||
border: 1px solid #45475a;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center-dnd:checked {
|
|
||||||
background: #A7C080;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center-dnd slider {
|
|
||||||
background: #45475a;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-dnd {
|
|
||||||
margin: 0px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-dnd > switch {
|
|
||||||
font-size: initial;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #A7C080;
|
|
||||||
border: 1px solid #45475a;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-dnd > switch:checked {
|
|
||||||
background: #A7C080;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-dnd > switch slider {
|
|
||||||
background: #45475a;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #6c7086;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-mpris .widget-mpris-player {
|
|
||||||
background: #A7C080;
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-mpris .widget-mpris-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-mpris .widget-mpris-subtitle {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-menubar > box > .menu-button-bar > button > label {
|
|
||||||
font-size: 3rem;
|
|
||||||
padding: 0.5rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-menubar > box > .menu-button-bar > :last-child {
|
|
||||||
color: #f38ba8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-buttons button:hover,
|
|
||||||
.powermode-buttons button:hover,
|
|
||||||
.screenshot-buttons button:hover {
|
|
||||||
background: #A7C080;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-center .widget-label > label {
|
|
||||||
color: #cdd6f4;
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-buttons-grid {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-buttons-grid > flowbox > flowboxchild > button label {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-volume {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-volume label {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #74c7ec;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-volume trough highlight {
|
|
||||||
background: #74c7ec;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-backlight trough highlight {
|
|
||||||
background: #f9e2af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-backlight label {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #f9e2af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-backlight .KB {
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
font-size = 12
|
|
||||||
font = JetBrainsMono NFM
|
|
||||||
anchor = top
|
|
||||||
width = 100%
|
|
||||||
height = 31
|
|
||||||
horizontal = true
|
|
||||||
prompt-text = " run: "
|
|
||||||
outline-width = 0
|
|
||||||
border-width = 0
|
|
||||||
background-color = #11111133
|
|
||||||
selection-color = #A7C080
|
|
||||||
min-input-width = 120
|
|
||||||
result-spacing = 15
|
|
||||||
padding-top = 6
|
|
||||||
padding-bottom = 0
|
|
||||||
padding-left = 0
|
|
||||||
padding-right = 0
|
|
||||||
auto-accept-single = false
|
|
||||||
physical-keybindings = false
|
|
||||||
text-cursor = false
|
|
||||||
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
//
|
|
||||||
// Waybar configuration
|
|
||||||
//
|
|
||||||
// Configuration reference: https://github.com/Alexays/Waybar/wiki/Configuration
|
|
||||||
//
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
{
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Global configuration
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
"layer": "top",
|
|
||||||
|
|
||||||
"position": "top",
|
|
||||||
|
|
||||||
// If height property would be not present, it'd be calculated dynamically
|
|
||||||
"height": 25,
|
|
||||||
"margin-left": 6,
|
|
||||||
"margin-right": 6,
|
|
||||||
"margin-top": 6,
|
|
||||||
"margin-bottom": 0,
|
|
||||||
|
|
||||||
"modules-left": [
|
|
||||||
"sway/workspaces",
|
|
||||||
// "sway/mode",
|
|
||||||
"sway/window"
|
|
||||||
],
|
|
||||||
"modules-center": [
|
|
||||||
],
|
|
||||||
"modules-right": [
|
|
||||||
// "network",
|
|
||||||
// "pulseaudio",
|
|
||||||
// "memory",
|
|
||||||
// "cpu",
|
|
||||||
// "temperature",
|
|
||||||
"backlight",
|
|
||||||
// "custom/keyboard-layout",
|
|
||||||
"battery",
|
|
||||||
// "custom/conservation",
|
|
||||||
// "custom/power",
|
|
||||||
"clock#date",
|
|
||||||
"clock#time",
|
|
||||||
"custom/sep",
|
|
||||||
"tray"
|
|
||||||
],
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Modules
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
"battery": {
|
|
||||||
"interval": 1,
|
|
||||||
"states": {
|
|
||||||
"warning": 30,
|
|
||||||
"critical": 15
|
|
||||||
},
|
|
||||||
// Connected to AC
|
|
||||||
"format": "{capacity}% ", // Icon: bolt
|
|
||||||
// "format": "⚡ {capacity}%", // Icon: bolt
|
|
||||||
// "format": "C {capacity}%", // Icon: bolt
|
|
||||||
// Not connected to AC
|
|
||||||
"format-discharging": "{capacity}% {icon}",
|
|
||||||
// "format-discharging": "🔋 {capacity}%",
|
|
||||||
// "format-discharging": "{capacity}%",
|
|
||||||
"format-icons": [
|
|
||||||
"", // Icon: battery-empty
|
|
||||||
"", // Icon: battery-quarter
|
|
||||||
"", // Icon: battery-half
|
|
||||||
"", // Icon: battery-three-quarters
|
|
||||||
"" // Icon: battery-full
|
|
||||||
],
|
|
||||||
"tooltip": true,
|
|
||||||
"on-click": "~/.config/waybar/conservation-toggle.sh"
|
|
||||||
},
|
|
||||||
|
|
||||||
"backlight": {
|
|
||||||
"device": "intel_backligt",
|
|
||||||
"format": "{percent} {icon}",
|
|
||||||
// "format": "{percent}%",
|
|
||||||
// "format-icons": ["🔅", "🔆"]
|
|
||||||
"format-icons": ["", "", "", "", ""]
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
"clock#time": {
|
|
||||||
"interval": 1,
|
|
||||||
// "format": "🕒 {:%H:%M}",
|
|
||||||
"format": "{:%H:%M}",
|
|
||||||
"tooltip": false,
|
|
||||||
"on-click": "gnome-clocks"
|
|
||||||
},
|
|
||||||
|
|
||||||
"clock#date": {
|
|
||||||
"interval": 10,
|
|
||||||
// "format": " {:%e %b %Y}", // Icon: calendar-alt
|
|
||||||
// "format": "🗓️ {:%e. %b %Y (%a)}", // Icon: calendar-alt
|
|
||||||
"format": "{:%e. %b (%a)}", // Icon: calendar-alt
|
|
||||||
"tooltip-format": "{:%e %B %Y}",
|
|
||||||
"on-click": "gnome-calendar"
|
|
||||||
},
|
|
||||||
|
|
||||||
"cpu": {
|
|
||||||
"interval": 30,
|
|
||||||
"format": "🧮 {usage}% ({load})", // Icon: microcip
|
|
||||||
"states": {
|
|
||||||
"warning": 70,
|
|
||||||
"critical": 90
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"custom/keyboard-layout": {
|
|
||||||
"exec": "swaymsg -t get_inputs | grep -m1 'xkb_active_layout_name' | cut -d '\"' -f4",
|
|
||||||
// Interval set only as a fallback, as the value is updated by signal
|
|
||||||
"interval": 30,
|
|
||||||
"format": " {}", // Icon: keyboard
|
|
||||||
// Signal sent by Sway key binding (~/.config/sway/key-bindings)
|
|
||||||
"signal": 1, // SIGHUP
|
|
||||||
"tooltip": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"memory": {
|
|
||||||
"interval": 30,
|
|
||||||
"format": "💾 {used} GiB", // Icon: memory
|
|
||||||
"states": {
|
|
||||||
"warning": 70,
|
|
||||||
"critical": 90
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"network": {
|
|
||||||
"interval": 5,
|
|
||||||
"format-wifi": " {essid} ({signalStrength}%)", // Icon: wifi
|
|
||||||
"format-ethernet": " {ifname}: {ipaddr}/{cidr}", // Icon: ethernet
|
|
||||||
"format-disconnected": "⚠ Disconnected",
|
|
||||||
"tooltip-format": "{ifname}: {ipaddr}"
|
|
||||||
},
|
|
||||||
|
|
||||||
"sway/mode": {
|
|
||||||
"format": "<span style=\"italic\"> {}</span>", // Icon: expand-arrows-alt
|
|
||||||
"tooltip": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"sway/window": {
|
|
||||||
"format": "{}",
|
|
||||||
"max-length": 100,
|
|
||||||
"icon": true,
|
|
||||||
"icon-size": 18
|
|
||||||
},
|
|
||||||
|
|
||||||
"sway/workspaces": {
|
|
||||||
"all-outputs": false,
|
|
||||||
"disable-scroll": false,
|
|
||||||
"format": "{name}",
|
|
||||||
"format-icons": {
|
|
||||||
"1": "一",
|
|
||||||
"2": "二",
|
|
||||||
"3": "三",
|
|
||||||
"4": "四",
|
|
||||||
"5": "五",
|
|
||||||
"6": "六",
|
|
||||||
"7": "七",
|
|
||||||
"8": "八",
|
|
||||||
"9": "九",
|
|
||||||
"10": "十",
|
|
||||||
// "urgent": "",
|
|
||||||
// "focused": "",
|
|
||||||
"default": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"pulseaudio": {
|
|
||||||
//"scroll-step": 1,
|
|
||||||
"format": "{volume}%",
|
|
||||||
"format-bluetooth": "{volume}%",
|
|
||||||
"format-muted": "Mute",
|
|
||||||
"format-icons": {
|
|
||||||
"headphones": "",
|
|
||||||
"handsfree": "",
|
|
||||||
"headset": "",
|
|
||||||
"phone": "",
|
|
||||||
"portable": "",
|
|
||||||
"car": "",
|
|
||||||
"default": ["", ""]
|
|
||||||
},
|
|
||||||
"on-click": "pavucontrol"
|
|
||||||
},
|
|
||||||
|
|
||||||
"temperature": {
|
|
||||||
"critical-threshold": 80,
|
|
||||||
"interval": 5,
|
|
||||||
"format": "{icon} {temperatureC}°C",
|
|
||||||
"format-icons": [
|
|
||||||
"", // Icon: temperature-empty
|
|
||||||
"", // Icon: temperature-quarter
|
|
||||||
"", // Icon: temperature-half
|
|
||||||
"", // Icon: temperature-three-quarters
|
|
||||||
"" // Icon: temperature-full
|
|
||||||
],
|
|
||||||
"tooltip": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"tray": {
|
|
||||||
"icon-size": 13,
|
|
||||||
"spacing": 15
|
|
||||||
},
|
|
||||||
|
|
||||||
"custom/conservation": {
|
|
||||||
"exec": "~/.config/waybar/conservation.sh",
|
|
||||||
"interval": 10,
|
|
||||||
"on-click": "~/.config/waybar/conservation-toggle.sh"
|
|
||||||
},
|
|
||||||
|
|
||||||
"custom/power": {
|
|
||||||
"exec": "echo 💻 $(cat /sys/firmware/acpi/platform_profile)",
|
|
||||||
"interval": 10,
|
|
||||||
"on-click": "kitty -e 'htop'"
|
|
||||||
},
|
|
||||||
|
|
||||||
"custom/sep": {
|
|
||||||
"format": "|",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
location='/sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/conservation_mode'
|
|
||||||
val=$(cat $location)
|
|
||||||
|
|
||||||
if [ "X$val" == "X1" ]; then
|
|
||||||
echo 0 | sudo tee $location
|
|
||||||
notify-send "Battery" "Disabled conservation mode" -i battery
|
|
||||||
else
|
|
||||||
echo 1 | sudo tee $location
|
|
||||||
notify-send "Battery" "Enabled conservation mode" -i battery
|
|
||||||
fi
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
location='/sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/conservation_mode'
|
|
||||||
val=$(cat $location)
|
|
||||||
|
|
||||||
if [ "X$val" == "X1" ]; then
|
|
||||||
echo "(On)"
|
|
||||||
else
|
|
||||||
echo "(Off)"
|
|
||||||
fi
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
/* =============================================================================
|
|
||||||
*
|
|
||||||
* Waybar configuration
|
|
||||||
*
|
|
||||||
* Configuration reference: https://github.com/Alexays/Waybar/wiki/Configuration
|
|
||||||
*
|
|
||||||
* =========================================================================== */
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
|
||||||
* Keyframes
|
|
||||||
* -------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
@keyframes blink-warning {
|
|
||||||
70% {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes blink-critical {
|
|
||||||
70% {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
|
||||||
* Base styles
|
|
||||||
* -------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/* Reset all styles */
|
|
||||||
* {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
min-height: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The whole bar */
|
|
||||||
#waybar {
|
|
||||||
background: rgba(100, 100, 100, 0.2);
|
|
||||||
color: white;
|
|
||||||
/* font-family: Cantarell, Noto Sans, sans-serif; */
|
|
||||||
font-family: JetBrainsMono Nerd Font;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Each module */
|
|
||||||
#battery,
|
|
||||||
#backlight,
|
|
||||||
#clock,
|
|
||||||
#cpu,
|
|
||||||
#custom-keyboard-layout,
|
|
||||||
#custom-weather,
|
|
||||||
#custom-power,
|
|
||||||
#memory,
|
|
||||||
#mode,
|
|
||||||
#network,
|
|
||||||
#pulseaudio,
|
|
||||||
#temperature,
|
|
||||||
#tray {
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#custom-conservation {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
|
||||||
* Module styles
|
|
||||||
* -------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#battery {
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
#battery.warning {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
#battery.critical {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
#battery.warning.discharging {
|
|
||||||
animation-name: blink-warning;
|
|
||||||
animation-duration: 3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#battery.critical.discharging {
|
|
||||||
animation-name: blink-critical;
|
|
||||||
animation-duration: 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#clock {
|
|
||||||
/* font-weight: bold; */
|
|
||||||
}
|
|
||||||
|
|
||||||
#cpu {
|
|
||||||
/* No styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
#cpu.warning {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cpu.critical {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
#memory {
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
#memory.warning {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
#memory.critical {
|
|
||||||
color: red;
|
|
||||||
animation-name: blink-critical;
|
|
||||||
animation-duration: 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mode {
|
|
||||||
background: #64727D;
|
|
||||||
border-top: 2px solid white;
|
|
||||||
/* To compensate for the top border and still have vertical centering */
|
|
||||||
padding-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#network {
|
|
||||||
/* No styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
#network.disconnected {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pulseaudio {
|
|
||||||
/* No styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
#pulseaudio.muted {
|
|
||||||
/* No styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
#custom-spotify {
|
|
||||||
color: rgb(102, 220, 105);
|
|
||||||
}
|
|
||||||
|
|
||||||
#temperature {
|
|
||||||
/* No styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
#temperature.critical {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tray {
|
|
||||||
/* No styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
#window {
|
|
||||||
/* font-weight: bold; */
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button {
|
|
||||||
border-top: 4px solid transparent;
|
|
||||||
/* To compensate for the top border and still have vertical centering */
|
|
||||||
padding-bottom: 2px;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 5px;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button.focused {
|
|
||||||
border-color: #A7C080;
|
|
||||||
/* color: white; */
|
|
||||||
color: #A7C080;
|
|
||||||
text-shadow: 0 0 2px #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button.urgent {
|
|
||||||
border-color: #c9545d;
|
|
||||||
color: #c9545d;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button:hover {
|
|
||||||
box-shadow: none; /* Remove predefined box-shadow */
|
|
||||||
text-shadow: none; /* Remove predefined text-shadow */
|
|
||||||
background: none; /* Remove predefined background color (white) */
|
|
||||||
transition: none; /* Disable predefined animations */
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user