chore: remove all unsused configs

This commit is contained in:
2025-06-14 22:26:33 +09:00
parent 56fd0796cb
commit 524364fceb
11 changed files with 29 additions and 2001 deletions

View File

@@ -12,41 +12,34 @@ Use ./stow.sh --unstow <folder_name> to remove configuration
- Install based on Fedora Workstation with GNOME
- Font: [JetBrains Mono](https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/JetBrainsMono.zip)
- 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)
- Lenovo Conservation Mode without root
```bash
%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 \
kitty git vim tmux ripgrep fd-find fzf stow \
brave \
sway waybar \
brightnessctl wireplumber pasystray blueman \
network-manager network-manager-applet \
fcitx5 fcitx5-configtool fcitx5-anthy fcitx5-* \
nextcloud keepassxc
kitty tmux \
git ripgrep fd-find fzf stow \
firefox \
nextcloud keepassxc tailscale
```
### 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)
- [application launcher - tofi](https://github.com/philj56/tofi)
- [screenshot tool](https://github.com/moverest/sway-interactive-screenshot) (checked in [here](files/sway/.config/sway/screenshot))
- [screen mirror for live presentations](https://github.com/Ferdi265/wl-mirror)
### Extensions
- appindicatorsupport@rgcjonas.gmail.com (bring back tray icons)
- blur-my-shell@aunetx (blur panel and background in overview, rest disabled)
- 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)

View File

@@ -1,23 +1,11 @@
PATH=$HOME/.dots/scripts:$HOME/.cargo/bin:$HOME/.ghcup/bin:$HOME/.local/bin:$HOME/.cabal/bin:$PATH
EDITOR=hx
VISUAL=hx
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
_JAVA_AWT_WM_NONREPARENTING=1
# XXX: render shadow on wayland
# KITTY_DISABLE_WAYLAND=1
# KITTY_ENABLE_WAYLAND=1
# support jp input in gnome
GTK_IM_MODULE=ibus
QT_IM_MODULE=ibus
XMODIFIERS=@im=ibus
SDL_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

View File

@@ -1,10 +1,7 @@
### System Utility
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 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 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'
### fzf
# basics
alias f='fzf --reverse'
alias c='cd $(fd --type d | fzf --reverse)'
# 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'";
}
alias blame='git show $(git ls-files | fzf -e --reverse --bind "enter:become(git blame {1} | fzf -e --ansi --reverse | cut -f 1 -d \" \")")'
# nix
alias nd="nix develop ."
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
alias d='docker'
alias dc='docker compose'
alias dcr='dc down && dc up -d && dc logs -f'
alias dcr='docker compose down && docker compose up -d && docker compose logs -f'
alias dh1='du . -h -d1'
alias dhs='du . -hs'
alias diff_dir='diff -qr'
alias g='git'
alias l='ls --color --hyperlink'
alias ls='ls --color --hyperlink'
alias nssh='SSH_AUTH_SOCK= ssh'
alias s='kitten ssh'
alias o='xdg-open' # to change a mime use: `xdg-mime default APPLICATION HANDLE`
t() {
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)
pwhash() {
name=$1
@@ -88,6 +54,13 @@ ocr() {
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
conservation() {

View File

@@ -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/*

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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": "|",
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 */
}