sway: add sway and waybar
This commit is contained in:
@@ -2,8 +2,13 @@ PATH=$HOME/.dots/scripts:$HOME/.cargo/bin:$HOME/.ghcup/bin:$HOME/.local/bin:$HOM
|
||||
EDITOR=hx
|
||||
VISUAL=hx
|
||||
|
||||
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
|
||||
|
||||
# XXX: render shadow on wayland
|
||||
KITTY_DISABLE_WAYLAND=1
|
||||
# KITTY_DISABLE_WAYLAND=1
|
||||
# KITTY_ENABLE_WAYLAND=1
|
||||
|
||||
_JAVA_AWT_WM_NONREPARENTING=1
|
||||
|
||||
# support jp input in gnome
|
||||
GTK_IM_MODULE=ibus
|
||||
|
||||
146
files/sway/.config/sway/config
Normal file
146
files/sway/.config/sway/config
Normal file
@@ -0,0 +1,146 @@
|
||||
### Variables
|
||||
set $mod Mod4
|
||||
floating_modifier $mod normal
|
||||
|
||||
### Output configuration (swaymsg -t get_output)
|
||||
output * bg ~/cloud/images/wallpaper/wallpaper.png fill
|
||||
# output eDP-1 scale 2
|
||||
|
||||
# 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 tabbed
|
||||
smart_borders on
|
||||
# default_border none
|
||||
|
||||
exec swayidle -w \
|
||||
timeout 300 'swaylock -i ~/cloud/images/wallpaper/lock.png -s center -u' \
|
||||
timeout 600 'systemctl suspend' \
|
||||
before-sleep 'swaylock -i ~/cloud/images/wallpaper/lock.png -s center -u'
|
||||
|
||||
### Input configuration
|
||||
|
||||
input "type:keyboard" {
|
||||
xkb_layout eu
|
||||
xkb_options caps:swapescape
|
||||
repeat_delay 200
|
||||
repeat_rate 100
|
||||
}
|
||||
|
||||
bindgesture swipe:right workspace prev
|
||||
bindgesture swipe:left workspace next
|
||||
|
||||
### 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+Control+BackSpace exec swaylock -i ~/cloud/images/wallpaper/lock.png -s center -u
|
||||
bindsym $mod+Control+Shift+BackSpace exec systemctl suspend
|
||||
|
||||
bindsym $mod+Shift+s exec ~/.config/sway/screenshot -s region
|
||||
|
||||
bindsym $mod+Shift+c reload
|
||||
bindsym $mod+Shift+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 wofi --show drun -I -i -a
|
||||
bindsym $mod+Shift+d exec wofi --show run -i -a
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# Layout stuff
|
||||
bindsym $mod+f fullscreen
|
||||
bindsym $mod+Shift+space floating toggle
|
||||
bindsym $mod+space focus mode_toggle
|
||||
bindsym $mod+a focus parent
|
||||
|
||||
# Scratchpad
|
||||
bindsym $mod+Shift+minus move scratchpad
|
||||
bindsym $mod+minus scratchpad show
|
||||
|
||||
# Theming
|
||||
font pango: JetBrainsMono Nerd Font Mono 10
|
||||
bar {
|
||||
position top
|
||||
mode dock
|
||||
output *
|
||||
|
||||
# status_command while ~/.config/sway/status.sh; do sleep 1; done
|
||||
swaybar_command waybar
|
||||
|
||||
colors {
|
||||
statusline #5c6a72
|
||||
background #fdf6e3
|
||||
inactive_workspace #fdf6e3 #fdf6e3 #5c6a72
|
||||
focused_workspace #8da101 #8da101 #fdf6e3
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
include /etc/sway/config.d/*
|
||||
|
||||
# Autostart
|
||||
exec --no-startup-id netbird-ui
|
||||
exec --no-startup-id nm-applet
|
||||
exec --no-startup-id keepassxc
|
||||
exec --no-startup-id nextcloud
|
||||
|
||||
# SSH
|
||||
exec eval $(gnome-keyring-daemon --start)
|
||||
exec export SSH_AUTH_SOCK
|
||||
923
files/sway/.config/sway/screenshot
Executable file
923
files/sway/.config/sway/screenshot
Executable file
@@ -0,0 +1,923 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
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()
|
||||
64
files/sway/.config/sway/status.sh
Executable file
64
files/sway/.config/sway/status.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
# Change this according to your device
|
||||
################
|
||||
# Variables
|
||||
################
|
||||
|
||||
# Keyboard input name
|
||||
# keyboard_input_name="1:2:AT_Raw_Set_2_keyboard"
|
||||
|
||||
# Date and time
|
||||
date_and_week=$(date "+%a %d.%b (KW%-V)")
|
||||
current_time=$(date "+%H:%M")
|
||||
|
||||
#############
|
||||
# Commands
|
||||
#############
|
||||
|
||||
# Battery or charger
|
||||
battery_charge=$(upower --show-info $(upower --enumerate | grep 'BAT') | egrep "percentage" | awk '{print $2}')
|
||||
battery_status=$(upower --show-info $(upower --enumerate | grep 'BAT') | egrep "state" | awk '{print $2}')
|
||||
|
||||
# Audio and multimedia
|
||||
audio_volume=$(pamixer --sink `pactl list sinks short | grep RUNNING | awk '{print $1}'` --get-volume)
|
||||
audio_is_muted=$(pamixer --sink `pactl list sinks short | grep RUNNING | awk '{print $1}'` --get-mute)
|
||||
loadavg_5min=$(cat /proc/loadavg | awk -F ' ' '{print $2}')
|
||||
|
||||
# Brightness
|
||||
brightness=$(printf %.0f $(light))
|
||||
|
||||
# Removed weather because we are requesting it too many times to have a proper
|
||||
# refresh on the bar
|
||||
#weather=$(curl -Ss 'https://wttr.in/Pontevedra?0&T&Q&format=1')
|
||||
|
||||
if [ $battery_status = "discharging" ];
|
||||
then
|
||||
battery_pluggedin='⚠'
|
||||
else
|
||||
battery_pluggedin='⚡'
|
||||
fi
|
||||
|
||||
if ! [ $network ]
|
||||
then
|
||||
network_active="⛔"
|
||||
else
|
||||
network_active="⇆"
|
||||
fi
|
||||
|
||||
if [ $player_status = "Playing" ]
|
||||
then
|
||||
song_status='▶'
|
||||
elif [ $player_status = "Paused" ]
|
||||
then
|
||||
song_status='⏸'
|
||||
else
|
||||
song_status='⏹'
|
||||
fi
|
||||
|
||||
if [ $audio_is_muted = "true" ]
|
||||
then
|
||||
audio_active='🔇'
|
||||
else
|
||||
audio_active='🔊'
|
||||
fi
|
||||
|
||||
echo "🏋 $loadavg_5min | $audio_active $audio_volume% | ☀️ $brightness | $battery_pluggedin $battery_charge | $date_and_week 🕘 $current_time"
|
||||
184
files/waybar/.config/waybar/config
Executable file
184
files/waybar/.config/waybar/config
Executable file
@@ -0,0 +1,184 @@
|
||||
// =============================================================================
|
||||
//
|
||||
// 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": 30,
|
||||
|
||||
"modules-left": [
|
||||
"sway/workspaces",
|
||||
// "sway/mode",
|
||||
"sway/window"
|
||||
],
|
||||
"modules-center": [
|
||||
],
|
||||
"modules-right": [
|
||||
// "network",
|
||||
"pulseaudio",
|
||||
// "memory",
|
||||
// "cpu",
|
||||
// "temperature",
|
||||
"backlight",
|
||||
// "custom/keyboard-layout",
|
||||
"battery",
|
||||
"clock#date",
|
||||
"clock#time",
|
||||
"tray"
|
||||
],
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Modules
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
"battery": {
|
||||
"interval": 10,
|
||||
"states": {
|
||||
"warning": 30,
|
||||
"critical": 15
|
||||
},
|
||||
// Connected to AC
|
||||
"format": " {icon} {capacity}%", // Icon: bolt
|
||||
// Not connected to AC
|
||||
// "format-discharging": "{icon} {capacity}%",
|
||||
"format-discharging": "{capacity}%",
|
||||
"format-icons": [
|
||||
"", // Icon: battery-full
|
||||
"", // Icon: battery-three-quarters
|
||||
"", // Icon: battery-half
|
||||
"", // Icon: battery-quarter
|
||||
"" // Icon: battery-empty
|
||||
],
|
||||
"tooltip": true
|
||||
},
|
||||
|
||||
"backlight": {
|
||||
"device": "intel_backlight",
|
||||
// "format": "{icon} {percent}%",
|
||||
"format": "{percent}%",
|
||||
"format-icons": ["", ""]
|
||||
},
|
||||
|
||||
|
||||
"clock#time": {
|
||||
"interval": 1,
|
||||
"format": "{:%H:%M}",
|
||||
"tooltip": false,
|
||||
"on-click": "gnome-clocks"
|
||||
},
|
||||
|
||||
"clock#date": {
|
||||
"interval": 10,
|
||||
// "format": " {:%e %b %Y}", // Icon: calendar-alt
|
||||
"format": "{:%e %b %Y}", // Icon: calendar-alt
|
||||
"tooltip-format": "{:%e %B %Y}",
|
||||
"on-click": "gnome-calendar"
|
||||
},
|
||||
|
||||
"cpu": {
|
||||
"interval": 5,
|
||||
"format": " {usage}% ({load})", // Icon: microchip
|
||||
"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": 5,
|
||||
"format": " {}%", // 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": 120
|
||||
},
|
||||
|
||||
"sway/workspaces": {
|
||||
"all-outputs": false,
|
||||
"disable-scroll": true,
|
||||
"format": "{icon} {name}",
|
||||
"format-icons": {
|
||||
"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": 15,
|
||||
"spacing": 10
|
||||
}
|
||||
|
||||
}
|
||||
197
files/waybar/.config/waybar/style.css
Executable file
197
files/waybar/.config/waybar/style.css
Executable file
@@ -0,0 +1,197 @@
|
||||
/* =============================================================================
|
||||
*
|
||||
* Waybar configuration
|
||||
*
|
||||
* Configuration reference: https://github.com/Alexays/Waybar/wiki/Configuration
|
||||
*
|
||||
* =========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Keyframes
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
@keyframes blink-warning {
|
||||
70% {
|
||||
color: white;
|
||||
}
|
||||
|
||||
to {
|
||||
color: white;
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink-critical {
|
||||
70% {
|
||||
color: white;
|
||||
}
|
||||
|
||||
to {
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Base styles
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Reset all styles */
|
||||
* {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* The whole bar */
|
||||
#waybar {
|
||||
background: #2D353B;
|
||||
color: white;
|
||||
/* font-family: Cantarell, Noto Sans, sans-serif; */
|
||||
font-family: JetBrainsMono Nerd Font Mono;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Each module */
|
||||
#battery,
|
||||
#backlight,
|
||||
#clock,
|
||||
#cpu,
|
||||
#custom-keyboard-layout,
|
||||
#memory,
|
||||
#mode,
|
||||
#network,
|
||||
#pulseaudio,
|
||||
#temperature,
|
||||
#tray {
|
||||
padding-left: 10px;
|
||||
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: 2px solid transparent;
|
||||
/* To compensate for the top border and still have vertical centering */
|
||||
padding-bottom: 2px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
#workspaces button.focused {
|
||||
border-color: #A7C080;
|
||||
/* color: white; */
|
||||
color: #A7C080;
|
||||
}
|
||||
|
||||
#workspaces button.urgent {
|
||||
border-color: #c9545d;
|
||||
color: #c9545d;
|
||||
}
|
||||
40
files/wofi/.config/wofi/style.css
Normal file
40
files/wofi/.config/wofi/style.css
Normal file
@@ -0,0 +1,40 @@
|
||||
window {
|
||||
margin: 0px;
|
||||
border: 2px solid #7A8478;
|
||||
background-color: #1E2326;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#input {
|
||||
margin: 5px;
|
||||
border: none;
|
||||
color: #D3C6AA;
|
||||
background-color: #272E33;
|
||||
}
|
||||
|
||||
#inner-box {
|
||||
margin: 5px;
|
||||
border: none;
|
||||
background-color: #1E2326;
|
||||
}
|
||||
|
||||
#outer-box {
|
||||
margin: 5px;
|
||||
border: none;
|
||||
background-color: #1E2326;
|
||||
}
|
||||
|
||||
#scroll {
|
||||
margin: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#text {
|
||||
margin: 5px;
|
||||
border: none;
|
||||
color: #D3C6AA;
|
||||
}
|
||||
|
||||
#entry:selected {
|
||||
background-color: #272E33;
|
||||
}
|
||||
Reference in New Issue
Block a user