Compare commits

..

11 commits

8 changed files with 153 additions and 53 deletions

View file

@ -29,3 +29,7 @@ background_path = "~/.local/share/backgrounds/adventurer/t.jpg"
match = "Pioneer Electronic Corporation AV Receiver Unknown" match = "Pioneer Electronic Corporation AV Receiver Unknown"
mode = "3840x2160@60Hz" mode = "3840x2160@60Hz"
scale = 2 scale = 2
[niri]
# 1/3 proportion is better for the ultrawide
default_column_width = 0.33333

View file

@ -1,11 +1,12 @@
wireless = ["wlp170s0"] wireless = ["wlp170s0"]
#ethernet = ["enp2s0"] auto_ethernet = true
temperature_path = "/sys/class/hwmon/hwmon4/temp1_input" temperature_path = "/sys/class/hwmon/hwmon4/temp1_input"
has_battery = true
[[outputs]] [[outputs]]
match = "eDP-1" match = "eDP-1"
background_path = "~/.local/share/backgrounds/sway-dark-1920x1080.png" background_path = "~/.local/share/backgrounds/liberation.jpg"
scale = 1.25 scale = 1
[[inputs]] [[inputs]]
match = "2362:628:PIXA3854:00_093A:0274_Touchpad" match = "2362:628:PIXA3854:00_093A:0274_Touchpad"

View file

@ -90,7 +90,7 @@ class OutputConfig:
scale: float | None = None scale: float | None = None
background_path: str | None = None background_path: str | None = None
background_mode: BackgroundMode = BackgroundMode.Fill background_mode: BackgroundMode = BackgroundMode.Fill
device: str | None = None port: str | None = None
@property @property
def sway_lines(self) -> str: def sway_lines(self) -> str:
@ -108,12 +108,12 @@ class OutputConfig:
@property @property
def swaylock_image_line(self) -> str | None: def swaylock_image_line(self) -> str | None:
if (self.match != "*" and self.device is None) or self.background_path is None: if (self.match != "*" and self.port is None) or self.background_path is None:
return None return None
if self.match == "*": if self.match == "*":
return f"image={self.background_path}" return f"image={self.background_path}"
return f"image={self.device}:{self.background_path}" return f"image={self.port}:{self.background_path}"
@property @property
def niri_lines(self) -> str: def niri_lines(self) -> str:
@ -132,8 +132,9 @@ class OutputConfig:
if self.background_path is None: if self.background_path is None:
return {} return {}
key = self.match # TODO: match by serial number instead of by port
if key == "*": key = self.port
if self.match == "*":
key = "any" key = "any"
return { return {
@ -144,6 +145,11 @@ class OutputConfig:
} }
@define
class NiriConfig:
default_column_width: float = 0.5
@define @define
class HostConfig: class HostConfig:
name: str name: str
@ -151,7 +157,9 @@ class HostConfig:
base16_scheme: str = "seti" base16_scheme: str = "seti"
wireless: list[str] = field(factory=list) wireless: list[str] = field(factory=list)
ethernet: list[str] = field(factory=list) ethernet: list[str] = field(factory=list)
auto_ethernet: bool = True
disks: list[str] = field(factory=lambda: ["/"]) disks: list[str] = field(factory=lambda: ["/"])
has_battery: bool = False
system_font: str = "Fira Sans" system_font: str = "Fira Sans"
system_mono_font: str = "Fira Mono" system_mono_font: str = "Fira Mono"
temperature_path: str | None = None temperature_path: str | None = None
@ -162,6 +170,7 @@ class HostConfig:
use_jump_host: bool = False use_jump_host: bool = False
inputs: list[InputConfig] = field(factory=list) inputs: list[InputConfig] = field(factory=list)
outputs: list[OutputConfig] = field(factory=list) outputs: list[OutputConfig] = field(factory=list)
niri: NiriConfig = field(factory=NiriConfig)
@property @property
def swaylock_images(self) -> str: def swaylock_images(self) -> str:
@ -213,10 +222,11 @@ def main():
) )
args = parser.parse_args() args = parser.parse_args()
raw_dir = args.dotfiles / "raw" dotfiles_dir: Path = args.dotfiles
templates_dir = args.dotfiles / "templates" raw_dir = dotfiles_dir / "raw"
include_dir = args.dotfiles / "include" templates_dir = dotfiles_dir / "templates"
host_filename = args.dotfiles / "hosts" / "{}.toml".format(args.hostname) include_dir = dotfiles_dir / "include"
host_filename = dotfiles_dir / "hosts" / "{}.toml".format(args.hostname)
host_toml = { host_toml = {
"name": args.hostname, "name": args.hostname,
@ -228,7 +238,7 @@ def main():
host_config = cattrs.structure(host_toml, HostConfig) host_config = cattrs.structure(host_toml, HostConfig)
for output in host_config.outputs: for output in host_config.outputs:
# Attempt to resolve device names for swaylock template # Attempt to resolve port names for swaylock template
# (Workaround https://github.com/swaywm/swaylock/issues/114) # (Workaround https://github.com/swaywm/swaylock/issues/114)
# #
# This will only work if this is run on the target host # This will only work if this is run on the target host
@ -242,9 +252,9 @@ def main():
["swaymsg", "-t", "get_outputs", "-p"], ["swaymsg", "-t", "get_outputs", "-p"],
).decode("utf-8") ).decode("utf-8")
for line in get_outputs.splitlines(): for line in get_outputs.splitlines():
# Line format: Output <device> '<match identifier>' # Line format: Output <port> '<match identifier>'
if line.startswith("Output") and output.match in line: if line.startswith("Output") and output.match in line:
output.device = line.split()[1] output.port = line.split()[1]
break break
elif "NIRI_SOCKET" in os.environ: elif "NIRI_SOCKET" in os.environ:
@ -252,9 +262,9 @@ def main():
["niri", "msg", "outputs"], ["niri", "msg", "outputs"],
).decode("utf-8") ).decode("utf-8")
for line in get_outputs.splitlines(): for line in get_outputs.splitlines():
# Line format: Output "<match identifier>" (<device>) # Line format: Output "<match identifier>" (<port>)
if line.startswith("Output") and output.match in line: if line.startswith("Output") and output.match in line:
output.device = ( output.port = (
line.split()[-1].removeprefix("(").removesuffix(")") line.split()[-1].removeprefix("(").removesuffix(")")
) )
break break
@ -262,9 +272,7 @@ def main():
print( print(
"Could not find SWAYSOCK or NIRI_SOCKET, cannot retrieve output names." "Could not find SWAYSOCK or NIRI_SOCKET, cannot retrieve output names."
) )
print( print("Please re-run in sway or niri to finish configuring swaylock.")
"Please re-run in sway or niri to finish configuring swaylock."
)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
print("Could not contact sway or niri to retrieve output names.") print("Could not contact sway or niri to retrieve output names.")
@ -277,6 +285,8 @@ def main():
], ],
) )
changed_paths = set()
for raw_path in raw_dir.glob("**/*"): for raw_path in raw_dir.glob("**/*"):
if not raw_path.is_file(): if not raw_path.is_file():
continue continue
@ -287,6 +297,7 @@ def main():
print(rel_path) print(rel_path)
output_path.parent.mkdir(parents=True, exist_ok=True) output_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(raw_path, output_path) shutil.copy(raw_path, output_path)
changed_paths.update(map(str, rel_path.parents))
for template_path in templates_dir.glob("**/*"): for template_path in templates_dir.glob("**/*"):
if not template_path.is_file(): if not template_path.is_file():
@ -312,6 +323,11 @@ def main():
# Copy permissions from original file # Copy permissions from original file
output_path.chmod(template_path.stat().st_mode & 0o777) output_path.chmod(template_path.stat().st_mode & 0o777)
changed_paths.update(map(str, rel_path.parents))
# Post-install hooks
if ".config/waybar" in changed_paths:
subprocess.call(["killall", "-USR2", "waybar"])
if __name__ == "__main__": if __name__ == "__main__":

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -0,0 +1 @@
chase-baker-NPiv36bUc4I-unsplash.jpg

View file

@ -56,6 +56,9 @@ input {
// Focus windows and outputs automatically when moving the mouse into them. // Focus windows and outputs automatically when moving the mouse into them.
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen. // Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
// focus-follows-mouse max-scroll-amount="0%" // focus-follows-mouse max-scroll-amount="0%"
// https://github.com/YaLTeR/niri/wiki/Configuration:-Input#disable-power-key-handling
disable-power-key-handling
} }
% for output in host.outputs: % for output in host.outputs:
@ -97,7 +100,7 @@ layout {
// preset-window-heights { } // preset-window-heights { }
// You can change the default width of the new windows. // You can change the default width of the new windows.
default-column-width { proportion 0.33333; } default-column-width { proportion ${host.niri.default_column_width}; }
// If you leave the brackets empty, the windows themselves will decide their initial width. // If you leave the brackets empty, the windows themselves will decide their initial width.
// default-column-width {} // default-column-width {}
@ -190,6 +193,11 @@ environment {
DISPLAY ":12" DISPLAY ":12"
} }
cursor {
xcursor-theme "breeze_cursors"
xcursor-size 24
}
// Uncomment this line to ask the clients to omit their client-side decorations if possible. // Uncomment this line to ask the clients to omit their client-side decorations if possible.
// If the client will specifically ask for CSD, the request will be honored. // If the client will specifically ask for CSD, the request will be honored.
// Additionally, clients will be informed that they are tiled, removing some client-side rounded corners. // Additionally, clients will be informed that they are tiled, removing some client-side rounded corners.
@ -306,14 +314,14 @@ binds {
Mod+K { focus-window-up; } Mod+K { focus-window-up; }
Mod+L { focus-column-right; } Mod+L { focus-column-right; }
Mod+Ctrl+Left { move-column-left; } Mod+Shift+Left { move-column-left; }
Mod+Ctrl+Down { move-window-down; } Mod+Shift+Down { move-window-down; }
Mod+Ctrl+Up { move-window-up; } Mod+Shift+Up { move-window-up; }
Mod+Ctrl+Right { move-column-right; } Mod+Shift+Right { move-column-right; }
Mod+Ctrl+H { move-column-left; } Mod+Shift+H { move-column-left; }
Mod+Ctrl+J { move-window-down; } Mod+Shift+J { move-window-down; }
Mod+Ctrl+K { move-window-up; } Mod+Shift+K { move-window-up; }
Mod+Ctrl+L { move-column-right; } Mod+Shift+L { move-column-right; }
// Alternative commands that move across workspaces when reaching // Alternative commands that move across workspaces when reaching
// the first or last window in a column. // the first or last window in a column.
@ -327,31 +335,23 @@ binds {
Mod+Ctrl+Home { move-column-to-first; } Mod+Ctrl+Home { move-column-to-first; }
Mod+Ctrl+End { move-column-to-last; } Mod+Ctrl+End { move-column-to-last; }
Mod+Shift+Left { focus-monitor-left; } Mod+Ctrl+Left { focus-monitor-left; }
Mod+Shift+Down { focus-monitor-down; } Mod+Ctrl+Down { focus-monitor-down; }
Mod+Shift+Up { focus-monitor-up; } Mod+Ctrl+Up { focus-monitor-up; }
Mod+Shift+Right { focus-monitor-right; } Mod+Ctrl+Right { focus-monitor-right; }
Mod+Shift+H { focus-monitor-left; } Mod+Ctrl+H { focus-monitor-left; }
Mod+Shift+J { focus-monitor-down; } Mod+Ctrl+J { focus-monitor-down; }
Mod+Shift+K { focus-monitor-up; } Mod+Ctrl+K { focus-monitor-up; }
Mod+Shift+L { focus-monitor-right; } Mod+Ctrl+L { focus-monitor-right; }
Mod+Shift+Ctrl+Left { move-column-to-monitor-left; } Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; }
Mod+Shift+Ctrl+Down { move-column-to-monitor-down; } Mod+Shift+Ctrl+Down { move-workspace-to-monitor-down; }
Mod+Shift+Ctrl+Up { move-column-to-monitor-up; } Mod+Shift+Ctrl+Up { move-workspace-to-monitor-up; }
Mod+Shift+Ctrl+Right { move-column-to-monitor-right; } Mod+Shift+Ctrl+Right { move-workspace-to-monitor-right; }
Mod+Shift+Ctrl+H { move-column-to-monitor-left; } Mod+Shift+Ctrl+H { move-workspace-to-monitor-left; }
Mod+Shift+Ctrl+J { move-column-to-monitor-down; } Mod+Shift+Ctrl+J { move-workspace-to-monitor-down; }
Mod+Shift+Ctrl+K { move-column-to-monitor-up; } Mod+Shift+Ctrl+K { move-workspace-to-monitor-up; }
Mod+Shift+Ctrl+L { move-column-to-monitor-right; } Mod+Shift+Ctrl+L { move-workspace-to-monitor-right; }
// Alternatively, there are commands to move just a single window:
// Mod+Shift+Ctrl+Left { move-window-to-monitor-left; }
// ...
// And you can also move a whole workspace to another monitor:
// Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; }
// ...
Mod+Page_Down { focus-workspace-down; } Mod+Page_Down { focus-workspace-down; }
Mod+Page_Up { focus-workspace-up; } Mod+Page_Up { focus-workspace-up; }

View file

@ -16,7 +16,12 @@
"network#${iface}", "network#${iface}",
% endfor % endfor
% if not host.ethernet and host.auto_ethernet:
"network#auto_ethernet",
% endif
"wireplumber", "wireplumber",
"battery",
"group/disks", "group/disks",
"cpu", "cpu",
"memory", "memory",
@ -49,12 +54,39 @@
}, },
% endfor % endfor
% if not host.ethernet and host.auto_ethernet:
"network#auto_ethernet": {
"interface": "en*",
"format": "\uf6ff \uf058",
"format-disconnected": "\uf6ff \uf057",
"tooltip-format": "${iface} {ipaddr}",
"tooltip-format-disconnected": "${iface} down"
},
% endif
"wireplumber": { "wireplumber": {
"format": "{icon} {volume}%", "format": "{icon} {volume}%",
"format-muted": "\uf6a9 {volume}%", "format-muted": "\uf6a9 {volume}%",
"format-icons": ["\uf026", "\uf027", "\uf028"] "format-icons": ["\uf026", "\uf027", "\uf028"]
}, },
% if host.has_battery:
"battery": {
"design-capacity": true,
"format": "{icon} {capacity}% {time}",
"format-time": "{H}:{m}",
"format-icons": [
"\uf244",
"\uf243",
"\uf242",
"\uf241",
"\uf240",
],
"format-charging": "\ue55b {capacity}% {time}",
"format-full": "\ue55c {capacity}%",
},
% endif
"group/disks": { "group/disks": {
// TODO: make drawer that expands orthogonally, outside of the bar // TODO: make drawer that expands orthogonally, outside of the bar
"orientation": "inherit", "orientation": "inherit",

46
templates/bin/wifi-pass Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env python
import argparse
from io import StringIO
import subprocess
import urllib.parse
def main():
parser = argparse.ArgumentParser(
description="""
Fetch the wifi password and generate a QR code.
This program assumes that the network PSK is stored
in the password-store, and will look for it
at the path `wifi/<essid>`.
""",
)
parser.add_argument(
"-H",
"--hidden",
action="store_true",
help="Indicate that this network's SSID is hidden.",
)
parser.add_argument("essid", help="ESSID for this network")
args = parser.parse_args()
psk = subprocess.check_output(["pass", f"wifi/{args.essid}"], text=True).strip()
hidden_data = "H:true;" if args.hidden else ""
qr_data = f"WIFI:T:WPA;S:{quote(args.essid)};{hidden_data}P:{quote(psk)};;"
proc = subprocess.Popen(["qrencode", "-tANSI"], stdin=subprocess.PIPE, text=True)
proc.communicate(qr_data)
PRINTABLE = [*range(0x20, 0x3B), *range(0x3C, 0x7F)]
def quote(s: str) -> str:
return urllib.parse.quote(s, safe=PRINTABLE)
if __name__ == "__main__":
main()