Compare commits

...

56 Commits

Author SHA1 Message Date
cf44651f85 Fixed emacs window auto-split 2026-04-14 13:06:12 -06:00
7161892da7 Added space-misson-clock-style timer to eww 2026-04-14 13:05:37 -06:00
06815e7bc6 Move kdeconnect to session slice and expanded shell memory limits 2026-01-23 10:31:31 -07:00
cfe932bf4a Improved introspection logic in shell config 2026-01-23 10:29:47 -07:00
e44618391b Added custom tz support to eww clocks
~/.config/eww/displaytime, if it exists, should be a symlink to a
timezone file (like /etc/localtime). When set, clocks will use this
timezone instead of system time. The large sidebar clock will show
the timezone name when the timezone used was not read from /etc/localtime.
2026-01-23 10:29:24 -07:00
d55a8a4d0d Improved the limited SYSTEM_INFO parser 2025-12-17 11:18:25 -07:00
79b9ddf5f6 updated shell config 2025-12-17 11:06:06 -07:00
61b96b3f38 Updated some stuff, this ship may be sinking 2025-09-22 11:54:41 -06:00
364e7c94a2 started some real org mode config stuff 2025-09-09 13:18:30 -06:00
00ef3b6217 removed alias and mask symlinks from systemd/user 2025-09-03 11:18:52 -06:00
58c5211d19 systemdify all the things 2025-09-03 11:05:45 -06:00
49d6627ee1 updates 2025-08-07 12:17:20 -06:00
e80d9d797c encrypted updates 2025-08-06 09:44:24 -06:00
817831838d added a telephony launcher 2025-08-06 09:43:59 -06:00
2343a00f2b Recovery from the sinking ship 2025-08-06 09:43:18 -06:00
7217d016ea Updated sway stuff 2025-06-27 16:56:03 -06:00
261400a636 encrypted updates 2025-04-10 10:25:48 -06:00
2f024495b4 Changed base font for eww 2025-04-07 12:12:15 -06:00
1056c63bca Merge branch 'main' of gitea:ezri/dotfiles 2025-04-03 13:41:11 -06:00
2dac598a95 encrypted updates 2025-04-03 13:41:01 -06:00
de82e55696 Added Razorback workspace file 2025-03-31 21:11:30 -06:00
2ca642aef3 encrypted updates 2025-03-31 13:20:19 -06:00
ec47a1ef95 Renamed workspace to be more generic 2025-03-31 11:32:45 -06:00
4d1c411870 Updates prior to system reset 2025-03-28 10:53:56 -06:00
83acf4ce98 Added wifi module to network 2025-03-14 08:38:53 -06:00
4ac47e3300 Re-added battlestation to roci config 2025-03-13 17:25:16 -06:00
47d093f95f Updated laptop sidebar slightly 2025-03-13 10:32:54 -06:00
408440ad18 Merge branch 'main' of gitea:ezri/dotfiles 2025-03-13 09:40:56 -06:00
baacfff7c4 Updated vpn toggle 2025-03-13 09:37:39 -06:00
6a603a5c9b Updated emacs settings for emacs 30 2025-03-13 09:37:20 -06:00
bbc5492534 Removed absolute path from sway context manager service 2025-03-13 09:36:06 -06:00
1127019300 Added color profiles to work displays 2025-03-13 09:35:26 -06:00
65a39a7cd6 Removed absolute paths for eww network script 2025-03-13 09:33:18 -06:00
9320af98a3 Added mini-sidebar for laptop screens 2025-03-13 09:32:37 -06:00
66d886c4e6 Removed some old drop-ins and variants that are no longer used 2025-03-13 09:32:11 -06:00
098e16e000 Add new workspace config files 2025-02-07 16:14:45 -07:00
bf27d4565e add start of outputs file 2025-02-05 18:28:02 -07:00
593b961a51 Cleaned up old, obsolete configs 2025-02-05 16:02:49 -07:00
feaca0be5a Updated roci workspaces 2025-02-05 15:34:49 -07:00
74b1584e0d Merge branch 'main' of gitea:ezri/dotfiles 2025-02-05 15:30:16 -07:00
241e988813 Unified eww configs 2025-02-05 15:26:37 -07:00
4f60bf3a7f Merge branch 'main' of gitea:ezri/dotfiles 2025-02-05 15:25:44 -07:00
c32e875762 Updates 2025-02-05 15:25:23 -07:00
e0686e8adf Unifying eww configs 2025-02-05 10:23:47 -07:00
41007302d4 Removed bar launch dropins for eww service 2025-02-05 09:11:49 -07:00
ab65bc3406 rename gathering storm to zariman 2025-02-05 09:06:45 -07:00
7e09c1d742 Merge branch 'main' of gitea:ezri/dotfiles 2025-02-04 17:01:18 -07:00
20d31ef694 removed symlink 2025-01-10 15:49:12 -07:00
00d4b17125 Updates 2025-01-10 15:48:36 -07:00
a279fc4923 Updates for gathering storm 2024-11-07 13:38:53 -07:00
06b1f4fb40 Merge branch 'main' of https://git.ezri.dev/ezri/dotfiles 2024-11-01 08:14:45 -06:00
792ba347e5 Merge branch 'main' of https://git.ezri.dev/ezri/dotfiles 2024-10-22 15:18:21 -06:00
790c931bcb Merge branch 'main' of https://git.ezri.dev/ezri/dotfiles 2024-10-22 15:17:02 -06:00
78d8ef68f6 Re-added volume keys to sway config 2024-10-22 15:16:55 -06:00
6a62232f0e Merge branch 'main' of gitea:ezri/dotfiles 2024-08-18 17:46:35 -06:00
7ced7d04de Minor updates 2024-08-18 17:46:30 -06:00
122 changed files with 5429 additions and 1719 deletions

View File

@@ -1,4 +1,5 @@
[bell]
animation = "EaseOutSine"
color = "0x9a7c9d"
@@ -43,19 +44,19 @@ render_timer = false
size = 9
[font.bold]
family = "JetBrainsMono Nerd Font"
family = "JetBrains Mono"
style = "Bold"
[font.bold_italic]
family = "JetBrainsMono Nerd Font"
family = "JetBrains Mono"
style = "Semibold Italic"
[font.italic]
family = "JetBrainsMono Nerd Font"
family = "JetBrains Mono"
style = "Italic"
[font.normal]
family = "JetBrainsMono Nerd Font"
family = "JetBrains Mono"
style = "Regular"
[window]
@@ -64,5 +65,5 @@ dynamic_title = true
opacity = 0.8
title = "Terminal"
[general]
working_directory = "/home/ezri"
[terminal]
#shell = { program = "/usr/bin/systemd-run", args = [ "--user", "--scope", "--slice=app-shell.slice", "--property=PartOf=alacritty.service", "--property=After=alacritty.service", "-S", "-q"]}

View File

@@ -1,66 +0,0 @@
working_directory = "/home/ezri"
[bell]
animation = "EaseOutSine"
color = "0x9a7c9d"
duration = 200
[colors]
draw_bold_text_with_bright_colors = false
[colors.bright]
black = "0x3f3242"
blue = "0x7587a6"
cyan = "0xafc4db"
green = "0x8f9d6a"
magenta = "0x9b859d"
red = "0xcf6a4c"
white = "0xffffff"
yellow = "0xf9ee98"
[colors.normal]
black = "0x2d272f"
blue = "0x7587a6"
cyan = "0xafc4db"
green = "0x8f9d6a"
magenta = "0x9b859d"
red = "0xcf6a4c"
white = "0xa7a7a7"
yellow = "0xf9ee98"
[colors.primary]
background = "0x1e1e1e"
foreground = "0x9a7c9d"
[cursor.style]
blinking = "Always"
shape = "underline"
[debug]
highlight_damage = false
render_timer = false
[font]
size = 12
[font.bold]
family = "JetBrainsMono Nerd Font"
style = "Bold"
[font.bold_italic]
family = "JetBrainsMono Nerd Font"
style = "Semibold Italic"
[font.italic]
family = "JetBrainsMono Nerd Font"
style = "Italic"
[font.normal]
family = "JetBrainsMono Nerd Font"
style = "Regular"
[window]
dynamic_padding = true
dynamic_title = true
opacity = 0.98
title = "Terminal"

View File

@@ -1,14 +1,14 @@
{
"BACKGROUND_COLOR": "#202225",
"IS_MAXIMIZED": true,
"BACKGROUND_COLOR": "#121214",
"IS_MAXIMIZED": false,
"IS_MINIMIZED": false,
"MINIMIZE_TO_TRAY": false,
"OPEN_ON_STARTUP": false,
"WINDOW_BOUNDS": {
"x": 0,
"y": 0,
"width": 1688,
"height": 1022
"width": 2328,
"height": 1382
},
"DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING": true,
"SKIP_HOST_UPDATE": true,

2
.config/eww/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__/
/displaytime

View File

@@ -1,13 +1,13 @@
$wallpaper: #1e1e1e;
$foreground: #9a7c9d;
$foreground: #966e9b;
$bg0: #2d272f;
$bg1: #3f3242;
$red: #cf6a4c;
$green: #8f9d6a;
$red: #b36152;
$green: #a1ae50;
$yellow: #f9ee98;
$blue: #7587a6;
$blue: #7d7ca5;
$magenta: #9b859d;
$cyan: #afc4db;
$white: #a7a7a7;

View File

@@ -10,29 +10,54 @@
.root {
color: $foreground;
font-family: "Source Code Pro";
font-family: "Armstrong";
font-size: 9pt;
background-color: rgba(0, 0, 0, 0);
background-color: rgba(30, 30, 30, 0.7);
padding: 10px;
border-width: 1px;
border-style: solid;
&:not(.focused) {
border-color: $bg0;
}
&.focused {
border-color: $foreground;
}
// Probably needs to change
&.bar {
margin: 10px;
margin-bottom: 0px;
}
&.side {
&.bottombar {
margin: 10px;
margin-top: 0px;
}
&.left-side {
margin: 10px;
margin-top: 20px;
margin-top: 39px;
margin-right: 0px;
margin-bottom: 39px;
}
&.right-side {
margin: 10px;
margin-top: 39px;
margin-left: 0px;
margin-bottom: 39px;
}
&.bg {
background-color: $bg0;
padding: 10px;
}
}
&.outline {
border: 1px solid $bg1;
}
.outline {
border: 1px solid $bg1;
}
.nebula {
@@ -65,7 +90,7 @@
}
.gauge-gutter {
color: $bg0;
color: rgba(30, 30, 30, 0.5);
}
.icon {
@@ -73,13 +98,18 @@
}
.invisible {
color: $wallpaper;
color: rgba(0, 0, 0, 0);
}
.highlight {
color: $red;
}
.timezone {
color: $red;
padding-top: 5px;
}
.offline {
color: $bg1;
}
@@ -100,8 +130,19 @@
background-color: $wallpaper;
}
// mpris module
.mpris--miniplayer-album {
border-radius: 10%;
}
// sway module
.sway--root.sway--vertical {
padding-top: 10px;
padding-bottom: 10px;
}
.sway--ws {
color: $bg1;
@@ -140,3 +181,24 @@
.clock--date {
font-size: 9pt;
}
menu {
padding: 3px;
background-color: $bg0;
font-family: "Armstrong";
border: 1px solid $foreground;
menuitem {
padding-top: 2px;
padding-bottom: 2px;
border-bottom: 1px solid $bg1;
}
menuitem:last-child {
border-bottom: none;
}
menuitem:hover {
background-color: $bg1;
}
}

118
.config/eww/eww.yuck Normal file
View File

@@ -0,0 +1,118 @@
;; -*-lisp-*- Include modules
(include "./modules/workspaces.yuck")
(include "./modules/clock.yuck")
(include "./modules/system.yuck")
(include "./modules/network.yuck")
(include "./modules/volume.yuck")
(include "./modules/aggietime.yuck")
(include "./modules/timer.yuck")
(include "./modules/mpris.yuck")
(defvar power--state "normal")
(include "./windows.yuck")
(defwindow desktop-leftbar [group]
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "h"
:class "bar root ${sway--data.visible[group].focused ? 'focused' : ''}"
(hostname-leftalign)
''
(ws-group-rightalign :workspace-group {group})))
(defwindow desktop-mainbar [group]
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "h"
:class "bar root ${sway--data.visible[group].focused ? 'focused' : ''}"
(ws-group-leftalign :workspace-group {group})
(hostname-centeralign)
(desktop-details-rightalign)))
(defwindow desktop-rightbar [group]
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "h"
:class "bar root ${sway--data.visible[group].focused ? 'focused' : ''}"
(ws-group-leftalign :workspace-group {group})
''
(hostname-rightalign)))
(defwindow sidebar [group side]
:geometry (geometry :width "200px"
:height "100%"
:anchor "${side} center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "v"
;; :class "root ${side}-side ${sway--data.visible[group].focused ? 'focused' : ''}"
(system-sidebar :group {group}
:side {side})
""
""
))
(defwindow user-sidebar [group side]
:geometry (geometry :width "200px"
:height "100%"
:anchor "${side} center")
:exclusive true
:focusable false
:stacking "bg"
(user-sidebar :orientation {side}))
(defwindow laptopbar [group battery]
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "h"
:class "laptop bar root ${sway--data.visible[group].focused ? 'focused' : ''}"
(hostname-and-workspace-leftalign :workspace-group {group})
""
(laptop-details-rightalign :battery {battery} )))
(defwindow laptopsidebar [group battery side]
:geometry (geometry :width "35px"
:height "100%"
:anchor "${side} center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "v"
(laptop-sidebar-top :group {group}
:side {side})
""
(laptop-sidebar-bottom :group {group}
:side {side}
:battery {battery})))
(defwindow vertical-bottombar [group]
:geometry (geometry :width "100%"
:height "65px"
:anchor "bottom center")
:exclusive true
:focusable false
:stacking "bg"
(centerbox :orientation "h"
:class "bottombar root ${sway--data.visible[group].focused ? 'focused' : ''}"
(date)
(big-clock)
(horizontal-minigauges-rightalign)))

View File

@@ -1,74 +0,0 @@
;; -*-lisp-*- Include modules
(include "./modules/workspaces.yuck")
(include "./modules/clock.yuck")
(include "./modules/system.yuck")
(include "./modules/network.yuck")
(include "./modules/volume.yuck")
(include "./modules/aggietime.yuck")
(include "./modules/timer.yuck")
(include "./modules/mpris.yuck")
(include "./windows.yuck")
(defwindow leftbar
:monitor 2
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h"
:class "bar root"
(normandy-leftbar--left)
"" ;;(normandy-leftbar--center)
(normandy-leftbar--right)))
(defwindow rightbar
:monitor 1
:geometry (geometry :width "100%"
:height "26px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h"
:class "bar root"
(normandy-rightbar--left)
"" ;;(normandy-rightbar--center)
(normandy-rightbar--right)))
(defwindow centerbar
:monitor 0
:geometry (geometry :width "100%"
:height "26px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h"
:class "bar root"
(normandy-centerbar--left)
(normandy-centerbar--center)
(normandy-centerbar--right)))
(defwindow sidebar
:monitor 2
:geometry (geometry :width "210px"
:height "1044px"
:anchor "left bottom")
:exclusive true
:focusable false
:stacking "fg"
(normandy-sidebar))
(defwindow sidebar2
:monitor 1
:geometry (geometry :width "210px"
:height "1044px"
:anchor "right bottom")
:exclusive true
:focusable false
:stacking "fg"
(normandy-sidebar))

View File

@@ -1,26 +0,0 @@
;; -*-lisp-*- Include modules
(include "./modules/workspaces.yuck")
(include "./modules/clock.yuck")
(include "./modules/system.yuck")
(include "./modules/network.yuck")
(include "./modules/volume.yuck")
(include "./modules/aggietime.yuck")
(include "./modules/timer.yuck")
(include "./modules/mpris.yuck")
(defvar power--state "normal")
(include "./windows.yuck")
(defwindow builtinbar
:monitor 0
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h" :class "bar root ${power--state == 'critical' ? 'reservepower' : ''}"
(rocinante-builtinbar--left)
(rocinante-builtinbar--center)
(rocinante-builtinbar--right)))

View File

@@ -1,81 +0,0 @@
;; -*-lisp-*- Include modules
(include "./modules/workspaces.yuck")
(include "./modules/clock.yuck")
(include "./modules/system.yuck")
(include "./modules/network.yuck")
(include "./modules/volume.yuck")
(include "./modules/aggietime.yuck")
(include "./modules/timer.yuck")
(include "./modules/mpris.yuck")
(include "./windows.yuck")
(defwindow leftbar
:monitor 1
:geometry (geometry :width "100%"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h"
:class "bar root"
(tycho-leftbar--left)
(tycho-leftbar--center)
(tycho-leftbar--right)))
(defwindow centerbar
:monitor 0
:geometry (geometry :width "100%"
:height "26px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h"
:class "bar root"
(tycho-centerbar--left)
(tycho-centerbar--center)
(tycho-centerbar--right)))
(defwindow rightbar
:monitor 2
:geometry (geometry :width "1440px"
:height "36px"
:anchor "top center")
:exclusive true
:focusable false
:stacking "fg"
(centerbox :orientation "h"
:class "bar root"
(tycho-rightbar--left)
(tycho-rightbar--center)
(tycho-rightbar--right)))
(defwindow sidebar
:monitor 1
:geometry (geometry :width "200px"
:height "100%"
:anchor "left center")
:exclusive true
:focusable false
:stacking "fg"
(tycho-sidebar))
(defwindow network-status
:monitor 0
:geometry (geometry :width "200px"
:height "0px"
:x "300px"
:y "0px"
:anchor "top right")
:exclusive false
:focusable false
:stacking "overlay"
(box :orientation "v"
:class "bar root bg outline"
:visible {network--show-details}
(network-detail)))

View File

@@ -8,14 +8,95 @@
(box :class "module text"
:spacing 0
:orientation "v"
(label :class "special"
:text "${clock--data.hour}:${clock--data.minute}:${clock--data.second}")
(label :text "${clock--data.year}-${clock--data.month}-${clock--data.day}")))
(box :orientation "h"
:spacing -5
:space-evenly true
:class "special"
"${formattime(clock--data.stamp, '%H', clock--data.tz)}"
":"
"${formattime(clock--data.stamp, '%M', clock--data.tz)}"
":"
"${formattime(clock--data.stamp, '%S', clock--data.tz)}"
)
(label :text {formattime(clock--data.stamp, "%Y-%m-%d", clock--data.tz)})))
(defwidget mission-clock []
(box :class "module text"
:spacing 0
:orientation "v"
:halign "center"
:valign "center"
:visible {clock--data.timer.active}
(box :orientation "h"
:space-evenly false
:spacing 5
:valign "center"
:halign "center"
(label :text 'T'
:class "special")
(label :text {clock--data.timer.started ? "+" : "-"}
:class {clock--data.timer.started ? "green" : "highlight"})
(label :text {clock--data.timer.timer}
:class "special"))
(label :text '${clock--data.timer.title} ${clock--data.timer.prefix}')))
(defwidget big-clock []
(centerbox :class "bigger nebula"
:space-evenly false
:halign "center"
:valign "center"
:width 100
:orientation "h"
(box :halign "start" {formattime(clock--data.stamp, "%H", clock--data.tz)})
(box :halign "center" ":")
(box :halign "end" {formattime(clock--data.stamp, "%M", clock--data.tz)})))
(defwidget date []
(box :class "nebula module text"
:space-evenly false
:spacing 20
:halign "start"
:valign "center"
:orientation "h"
(label :text {formattime(clock--data.stamp, "%A", clock--data.tz)}
:class "medium"
:valign "center")
(label :text {formattime(clock--data.stamp, "%B %d", clock--data.tz)}
:class "special"
:valign "center")))
(defwidget side-mission-clock []
(box :class "module text"
:space-evenly false
:spacing 5
:halign "start"
:valign "center"
:visible {clock--data.timer.active}
:width 200
:orientation "v"
(label :class "big nebula special"
:text {clock--data.timer.title})
(label :class "nebula"
:text {clock--data.timer.prefix})
(box :class "nebula"
:spacing 5
:halign "center"
:space-evenly false
(label :text "T")
(label :class {clock--data.timer.started ? "green" : "highlight"}
:text {clock--data.timer.started ? "+" : "-"})
(label :class "nebula"
:text {clock--data.timer.timer}))))
(defwidget sideclock []
(button :onclick "echo -n $(date +%Y-%d-%m) | wl-copy"
(button :onclick "echo -n $(date +%Y-%d-%m) | wl-copy && eww update clock--show=date && sleep 2 && eww update clock--show=clock"
:onrightclick "echo -n $(date +%s) | wl-copy && eww update clock--show=unixtime && sleep 2 && eww update clock--show=clock"
(overlay
(box :class "module text nebula"
(box :class "module text"
:spacing 0
:space-evenly false
:orientation "v"
@@ -24,12 +105,17 @@
:width 150
:halign "center"
:orientation "h"
(box :halign "start" "${clock--data.hour}")
(box :halign "start" {formattime(clock--data.stamp, "%H", clock--data.tz)})
(box :halign "center" ":")
(box :halign "end" "${clock--data.minute}"))
(label :text "${clock--data.dow}"
:class "special")
(label :text "${clock--data.month_name} ${clock--data.day}"))
(box :halign "end" {formattime(clock--data.stamp, "%M", clock--data.tz)}))
(label :text {formattime(clock--data.stamp, "%A", clock--data.tz)}
:class "special nebula")
(label :text {formattime(clock--data.stamp, "%B %d", clock--data.tz)}
:class "nebula")
(label :text {clock--data.tz}
:class "highlight timezone"
:visible {clock--data.tz-source != "system"})
)
(box
:height 100
(revealer :transition "crossfade"

View File

@@ -1,29 +1,64 @@
;; -*-lisp-*-
(deflisten mpris--data :initial "{}"
`~/.config/eww/scripts/mpris.py`)
`~/.config/eww/scripts/mpris2.py`)
(defwidget mpris2
[]
[]
(button :onclick "astal-mpris --player '${mpris--data.active_player}' play-pause"
(box :class "module text"
:spacing 0
:orientation "v"
(label :class {mpris--data.playing ? "special" : "offline"}
:visible {mpris--data.running}
:text "${mpris--data.title} by ${mpris--data.artist}")
(box :spacing 5
:space-evenly false
:halign "center"
:orientation "h"
:visible {mpris--data.running}
:class {mpris--data.playing ? "special" : "offline"}
{mpris--data.title}
"--"
(box :space-evenly false
:orientation "h"
{mpris--data.position_minutes}
":"
{mpris--data.position_seconds}
(box :space-evenly false
:orientation "h"
:visible {mpris--data.length > 0}
"/"
{mpris--data.length_minutes}
":"
{mpris--data.length_seconds}))
)
(label :class "offline"
:visible {!mpris--data.running}
:text "player offline")
(label :visible {mpris--data.running}
:text "now playing from ${mpris--data.album}")
(label :visible {!mpris--data.running}
:text "player offline")))
:text "player offline"))))
(defwidget mpris-miniplayer []
(box :class "miniplayer"
:orientation "v"
:space-evenly false
:spacing 10
(image :path {mpris--data.album_art}
:image-width 100
:image-height 100)
(label)))
(label :class "nebula medium special"
:text "Music")
(box :orientation "v"
:space-evenly false
:spacing 20
:visible {mpris--data.running}
(image :path {mpris--data.album_art}
:class "mpris--miniplayer-album"
:image-width 150
:image-height 150)
(box :orientation "v"
:space-evenly false
:spacing 5
(label :text {mpris--data.title}
:class "large special")
(label :text {mpris--data.artist})))
(label :class "nebula offline"
:visible {!mpris--data.running}
:text "Offline")))

View File

@@ -1,6 +1,8 @@
;; -*-lisp-*-
(deflisten network--data
`~/.config/eww/scripts/network.py`)
`~/.config/eww/scripts/network.py`)
(defvar nebula-armstrong-alignment "-3px")
(defvar network--show-details false)
@@ -48,27 +50,12 @@
:halign "start"
:space-evenly false
:spacing 0
(label :class "offline"
:visible {network--data
["network"]
[device]
["offline"]
}
:text "offline")
(label :visible {network--data
["network"]
[device]
["connecting"]
}
:class "highlight"
:text "connecting...")
(label :visible {network--data
["network"]
[device]
["online"]
}
:class "special"
:text "${network--data['wifi']['ssid']}")))
(label :class "highlight"
:visible {!device.online}
:text "offline")
(label :class "special"
:visible {device.online}
:text {network--data.wifi.ssid})))
(defwidget network--lan
[device]
@@ -76,11 +63,11 @@
:halign "start"
:space-evenly false
:spacing 0
(label :class "offline"
(label :class "highlight"
:visible {!device.online}
:text "offline")
(label :visible {device.online}
:class {network--data.last_update.unix < clock--data.unix - 30 ? "highlight" : "special"}
:class {network--data.last_update.unix < clock--data.stamp - 30 ? "highlight" : "special"}
:text "${device.addresses[0].address}/${device.addresses[0].prefixlen}")))
(defwidget network--secure
@@ -127,9 +114,28 @@
:halign "center"
:space-evenly false
:spacing 10
(network--lan :device {network--data.default_interface}))
(network--lan :device {network--data.default_interface}
:visible {!network--data.wifi.default}))
"communications")))
(defwidget wifi []
(box :orientation "v"
:halign "start"
:space-evenly false
:spacing 0
:visible {network--data != ''}
(box :orientation "h"
:halign "center"
:space-evenly false
:spacing 10
(label :class "offline"
:text "offline"
:visible {!network--data.wifi.connected})
(label :class "special"
:text {network--data.wifi.ssid}
:visible {network--data.wifi.connected}))
"wireless"))
(defwidget network--public-ip
[]
(centerbox :orientation "h"
@@ -231,7 +237,7 @@
(box :halign "end"
:spacing 0
:space-evenly false
(label :class {network--data.last_update.unix < clock--data.unix - 30 ? "highlight" : "special"}
(label :class {network--data.last_update.unix < clock--data.stamp - 30 ? "highlight" : "special"}
:text "${network--data.last_update.month}-${network--data.last_update.day} ${network--data.last_update.hour}:${network--data.last_update.minute}:${network--data.last_update.second}")))
))
@@ -265,46 +271,56 @@
:spacing 10
:width 200
:space-evenly false
(box :halign "start"
:class "nebula"
"Route On:")
""
(box :halign "end"
(label :text "No Route"
:class "highlight"
:visible {network--data.online && !network--data.have_default_route})
(label :text "${network--data.default_route}"
:class "special"
:visible {network--data.online && network--data.have_default_route})))
(centerbox :orientation "h"
:halign "start"
:spacing 10
:width 200
:space-evenly false
(transform :translate-y {network--data.have_default_route ? nebula-armstrong-alignment : "0px"}
(box :halign "start"
:valign "start"
:class "nebula"
"Address:")
"Route On:"))
""
(box :halign "end"
:orientation "v"
(label :text "${network--data.default_interface.addresses[0].address}"
:class "special")
(label :text "/${network--data.default_interface.addresses[0].prefixlen}"
:halign "end"
:class "special")))
(label :text "No Route"
:class "nebula highlight"
:visible {!network--data.have_default_route})
(label :text "${network--data.default_route}"
:class "special"
:visible {network--data.have_default_route})))
(centerbox :orientation "h"
:halign "start"
:spacing 10
:width 200
:space-evenly false
(transform :translate-y {network--data.online ? nebula-armstrong-alignment : "0px"}
(box :halign "start"
:valign "start"
:class "nebula"
"Public IP:")
"Address:"))
""
(box :halign "end"
:valign "end"
:orientation "v"
(label :text "${network--data.default_interface.addresses[0].address}"
:class "special"
:visible {network--data.online})
(label :text "offline"
:class "nebula highlight"
:visible {!network--data.online})
(label :text "/${network--data.default_interface.addresses[0].prefixlen}"
:halign "end"
:class "special"
:visible {network--data.online})))
(centerbox :orientation "h"
:halign "start"
:spacing 10
:width 200
:space-evenly false
(transform :translate-y {network--data.have_public_ip ? nebula-armstrong-alignment : "0px"}
(box :halign "start"
:class "nebula"
"Public IP:"))
""
(box :halign "end"
(label :text "Offline"
:class "highlight"
:class "nebula highlight"
:visible {!network--data.have_public_ip})
(label :text {network--data.public_ip.ip}
:class "special"
@@ -314,9 +330,10 @@
:spacing 10
:width 200
:space-evenly false
(box :halign "start"
:class "nebula"
"Gateway:")
(transform :translate-y {network--data.have_gateway ? nebula-armstrong-alignment : "0px"}
(box :halign "start"
:class "nebula"
"Gateway:"))
""
(box :halign "end"
(label :text "Error"
@@ -325,6 +342,23 @@
(label :text {network--data.gateway}
:class "special"
:visible {network--data.have_gateway})))
(centerbox :orientation "h"
:halign "start"
:spacing 10
:width 200
:space-evenly false
(transform :translate-y {network--data.wifi.connected ? nebula-armstrong-alignment : "0px"}
(box :halign "start"
:class "nebula"
"Wireless:"))
""
(box :halign "end"
(label :text "offline"
:class "offline nebula"
:visible {!network--data.wifi.connected})
(label :text {network--data.wifi.ssid}
:class "special"
:visible {network--data.wifi.connected})))
(centerbox :orientation "h"
:halign "start"
:class "nebula"

View File

@@ -1,194 +0,0 @@
(deflisten network--data
`~/.config/eww/scripts/network.py`)
(defwidget network--wlan [device]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 0
(label :class "offline"
:visible {network--data["network"][device]["offline"]}
:text "offline")
(label :visible {network--data["network"][device]["connecting"]}
:class "highlight"
:text "connecting...")
(label :visible {network--data["network"][device]["online"]}
:class "special"
:text "${network--data['wifi']['ssid']}")))
(defwidget network--lan [device]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 0
(label :class "offline"
:visible {network--data["network"][device]["offline"]}
:text "offline")
(label :visible {network--data["network"][device]["connecting"]}
:class "highlight"
:text "connecting...")
(label :visible {network--data["network"][device]["online"] && !network--data.network[device].connecting}
:class "special"
:text "${network--data['network'][device]['ip4_addr']}/${network--data['network'][device]['ip4_prefix']}")))
(defwidget network--proxy-vpn [device ?required]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 0
:visible {network--data["network"][device]["exists"] || required}
(label :class "highlight"
:text "- insecure"
:visible {! network--data["network"][device]["exists"]})
(label :class "green"
:text "- secured"
:visible {network--data["network"][device]["exists"]})))
(defwidget proxy-network [device]
(box :orientation "v"
:halign "start"
:space-evenly false
:spacing 0
(label :class "green"
:text "connected"
:visible {network--data.network[device].exists})
(label :class "offline"
:text "offline"
:visible {!network--data.network[device].exists})
"proxy vpn"))
(defwidget vpn-network []
(box :orientation "v"
:halign "start"
:space-evenly false
:spacing 0
(label :class "highlight"
:text "offline"
:visible {! network--data["network"]["ezrinet"]["exists"] || ! network--data["connected"]})
(label :class "green"
:text "connected"
:visible {network--data["network"]["ezrinet"]["exists"] && network--data["connected"]})
"personal network"))
(defwidget network []
(box :orientation "v"
:halign "start"
:space-evenly false
:spacing 0
(box :orientation "h"
:halign "center"
:space-evenly false
:spacing 10
(network--lan :device "insight")
(network--proxy-vpn :device "wg-mullvad" :required {!network--data.trusted}))
"communications"))
(defwidget network-sidebar-details []
(box :orientation "v"
:halign "start"
:class "module"
:space-evenly false
:spacing 5
:width 200
(box :orientation "h"
:halign "center"
:class "nebula"
:spacing 10
:space-evenly false
(label :text "Comms"
:class "medium special"))
(centerbox :orientation "h"
:halign "start"
:class "nebula"
:spacing 10
:width 200
:space-evenly false
(box :halign "start"
"Status:")
""
(box :halign "end"
(label :text "Online"
:class "green"
:visible {network--data.connected})
(label :text "Offline"
:class "highlight"
:visible {!network--data.connected})))
(centerbox :orientation "h"
:halign "start"
:class "nebula"
:spacing 10
:width 200
:space-evenly false
(box :halign "start"
"VPN:")
""
(box :halign "end"
(label :text "Connected"
:class "green"
:visible {network--data.network.ezrinet.online && network--data.connected})
(label :text "Offline"
:class "highlight"
:visible {!network--data.network.ezrinet.exists})))
(centerbox :orientation "h"
:halign "start"
:class "nebula"
:spacing 10
:width 200
:space-evenly false
(box :halign "start"
"Proxy:")
""
(box :halign "end"
(label :text "Connected"
:class "green"
:visible {network--data.network.wg-mullvad.online})
(label :text "Offline"
:class "highlight"
:visible {!network--data.network.wg-mullvad.exists})))
(centerbox :orientation "h"
:halign "start"
:class "nebula"
:spacing 10
:width 200
:space-evenly false
(box :halign "start"
"Wi-Fi:")
""
(box :halign "end"
(label :text {network--data.wifi.ssid}
:limit-width 12
:class "green"
:visible {network--data.wifi.connected})
(label :text "Offline"
:class "offline"
:visible {!network--data.wifi.connected})))
(centerbox :orientation "h"
:halign "start"
:spacing 10
:width 200
:class "nebula"
:space-evenly false
(box :valign "start"
:halign "start"
"Addrs:")
""
(box :valign "start"
:halign "end"
:visible {network--data.connected}
:orientation "v"
(for addr in {network--data.ip4_addrs}
(box :class "special"
:halign "end"
{addr}))
(box :visible {!network--data.connected}
:class "offline"
"none")))
;; (box :orientation "h"
;; :halign "start"
;; :class "nebula"
;; :spacing 10
;; :space-evenly false
;; (label :text "Addr:")
;; (label :text {network--data.network[device].ip4_addr}
;; :class "medium special"))))
))

View File

@@ -1,77 +0,0 @@
;; -*-lisp-*-
(deflisten network--data
`~/.config/eww/scripts/network.py`)
(defwidget network--wlan [device]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 10
(label :class "offline"
:visible {network--data["network"][device]["offline"]}
:text "offline")
(label :visible {network--data["network"][device]["connecting"]}
:class "highlight"
:text "connecting...")
(label :visible {network--data["network"][device]["online"] && !network--data.network[device].connecting}
:class "special"
:text "${network--data['wifi']['ssid']}")
(label :visible {!network--data.connection.internet && network--data.network[device].online}
:class "highlight"
:text "- no internet"
)))
(defwidget network--lan [device]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 0
(label :class "offline"
:visible {network--data["network"][device]["offline"]}
:text "offline")
(label :visible {network--data["network"][device]["connecting"]}
:class "highlight"
:text "connecting...")
(label :visible {network--data["network"][device]["online"] && !network--data.network[device].connecting}
:class "special"
:text "${network--data['network'][device]['ip4_addr']}/${network--data['network'][device]['ip4_prefix']}")))
(defwidget network--proxy-vpn [device ?required]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 0
:visible {network--data.connection.internet && (network--data["network"][device]["exists"] || required)}
(label :class "highlight"
:text "- insecure"
:visible {! network--data["network"][device]["exists"]})
(label :class "green"
:text "- secured"
:visible {network--data["network"][device]["exists"]})))
(defwidget vpn-network []
(box :orientation "v"
:halign "start"
:space-evenly false
:spacing 0
(label :class "highlight"
:text "offline"
:visible {!network--data.connection.ezrinet})
(label :class "green"
:text "connected"
:visible {network--data.connection.ezrinet})
"personal network"))
(defwidget network []
(box :orientation "v"
:halign "start"
:space-evenly false
:spacing 0
(box :orientation "h"
:halign "center"
:space-evenly false
:spacing 10
(network--wlan :device "wlan0")
(network--proxy-vpn :device "wg-mullvad" :required {!network--data.trusted}))
"communications"))

View File

@@ -12,6 +12,22 @@
:text "${EWW_BATTERY[battery].capacity}%")
(label :text "${EWW_BATTERY[battery].status == 'Charging' || EWW_BATTERY[battery].status == 'Full' ? 'external' : 'internal'} power")))
(defwidget system-battery-gauge-small [battery]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "PWR"
:class "special")
(box :halign "center"
(overlay
(system--small-gauge :value {EWW_BATTERY[battery].capacity}
:threshold 25
:invert-threshold true
:green {EWW_BATTERY[battery].status == "Charging" || EWW_BATTERY[battery].capacity == 100})))))
(defwidget system-name []
(label :halign "center"
:valign "center"
@@ -56,7 +72,7 @@
:text "${round(system--data.memory.used / 1024 / 1024 / 1024, 2)} / ${round(system--data.memory.total / 1024 / 1024 / 1024, 2)} GiB")
(label :text "system memory")))
(defwidget system--gauge [value ?threshold]
(defwidget system--gauge [value ?threshold ?green]
(overlay :width 80
:height 80
(circular-progress :value 75
@@ -68,6 +84,18 @@
:start-at 37.5
:thickness 2)))
(defwidget system--small-gauge [value ?threshold ?green ?invert-threshold]
(overlay :width 30
:height 30
(circular-progress :value 75
:class "gauge-gutter"
:start-at 37.5
:thickness 2)
(circular-progress :value {value * 0.75}
:class 'gauge ${(green ?: false) ? "green" : (((invert-threshold ?: false) ? (value < (threshold ?: 20)) : (value > (threshold ?: 80))) ? "highlight" : "")}'
:start-at 37.5
:thickness 2)))
(defwidget cpu-mem-gauges []
(box :orientation "v"
:space-evenly false
@@ -121,7 +149,40 @@
(transform
:translate-y "3px"
(label :text "%"
:class "offline"))))))))
:class "offline"))))))))
(defwidget system--memory-gauge-small [?invert]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "MEM"
:visible {!(invert ?: false)}
:class "special")
(box :halign "center"
(overlay
(system--small-gauge :value {system--data.memory.percent})))
(label :text "MEM"
:visible {invert ?: false}
:class "special")))
(defwidget system--swap-gauge-small [?invert]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "SWAP"
:visible {!(invert ?: false)}
:class "special")
(box :halign "center"
(system--small-gauge :value {system--data.swap.percent}))
(label :text "SWAP"
:visible {invert ?: false}
:class "special")))
(defwidget system--cpu-gauge []
(box :orientation "v"
@@ -151,6 +212,24 @@
:class "offline"))))))
))
(defwidget system--cpu-gauge-small [?invert]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "CPU"
:visible {!(invert ?: false)}
:class "special")
(box :halign "center"
(overlay
(system--small-gauge :value {system--data.cpu.avg}
:threshold 80)))
(label :text "CPU"
:visible {(invert ?: false)}
:class "special")))
(defwidget system--gpu-gauge []
(box :orientation "v"
:halign "start"
@@ -176,7 +255,24 @@
(transform
:translate-y "3px"
(label :text "%"
:class "offline"))))))))
:class "offline"))))))))
(defwidget system--gpu-gauge-small [?invert]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "GPU"
:visible {!(invert ?: false)}
:class "special")
(box :halign "center"
(overlay
(system--small-gauge :value {system--data.gpu.load * 100})))
(label :text "GPU"
:visible {invert ?: false}
:class "special")))
(defwidget system--vram-gauge []
(box :orientation "v"
@@ -203,7 +299,24 @@
(transform
:translate-y "3px"
(label :text "%"
:class "offline"))))))))
:class "offline"))))))))
(defwidget system--vram-gauge-small [?invert]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "VMEM"
:visible {!(invert ?: false)}
:class "special")
(box :halign "center"
(overlay
(system--small-gauge :value {system--data.gpu.memory * 100})))
(label :text "VMEM"
:visible {invert ?: false}
:class "special")))
(defwidget system--gauge-generic [value value-fmt big-text little-text ?subscript ?threshold]
@@ -247,4 +360,5 @@
(label :text {big-text}
:class "big nebula")
(label :text {little-text}
:class "nebula special")))))
:class "nebula special")))))

View File

@@ -1,6 +1,6 @@
;; -*-lisp-*-
(deflisten volume--data
`~/.config/eww/scripts/volume.py`)
`~/.config/eww/scripts/volume2.py`)
(defwidget volume-h []
(box :orientation "h"
@@ -27,3 +27,90 @@
(label :class {volume--data.input.mute ? "offline" : "special"}
:text "${volume--data.input.volume}%"))
"audio system"))
(defwidget volume-small-gauge [?invert]
(box :orientation "v"
:halign "center"
:width 35
:space-evenly false
:spacing 10
:class ""
(label :text "VOL"
:visible {!(invert ?: false)}
:class "special")
(overlay :width 30
:height 30
(circular-progress :value {75 / 2.05}
:class "gauge-gutter"
:start-at 37.5
:thickness 2)
(circular-progress :value {75 / 2.05}
:class "gauge-gutter"
:start-at 12.5
:clockwise false
:thickness 2)
(circular-progress :value {volume--data.output.mute ? 0 : min(volume--data.output.volume * 0.75 / 2.05, 75 / 2.05)}
:class 'gauge ${volume--data.output.mute ? "green" : volume--data.output.volume > 100 ? "highlight" : ""}'
:start-at 37.5
:thickness 2)
(circular-progress :value {volume--data.input.mute ? 0 : min(volume--data.input.volume * 0.75 / 2.05, 75 / 2.05)}
:class 'gauge ${volume--data.input.mute ? "green" : volume--data.input.volume > 100 ? "highlight" : ""}'
:start-at 12.5
:clockwise false
:thickness 2))
(label :text "VOL"
:visible {invert ?: false}
:class "special")))
(defwidget volume--gauge [io]
(box :orientation "v"
:halign {io == "in" ? "start" : "end"}
:width 95
:space-evenly false
:spacing 10
:class "nebula"
(label :text {io})
(box :halign "center"
(overlay
(system--gauge :value {volume--data["${io}put"].volume}
:threshold {volume--data["${io}put"].mute ? 0 : 100})
(transform :translate-y "-2px"
(box
(box :halign "center"
:valign "center"
:orientation "h"
:space-evenly false
:visible {!volume--data["${io}put"].mute}
(label :text "%"
:class "invisible")
(label :text {volume--data["${io}put"].volume}
:class "special")
(label :text "%"
:class "offline"))
(box :halign "center"
:valign "center"
:orientation "h"
:space-evenly false
:visible {volume--data["${io}put"].mute}
(label :text "mute"
:class "offline"))
))))))
(defwidget volume-gauges []
(box :orientation "v"
:space-evenly false
:spacing 10
:width 200
:halign "start"
:class "nebula"
(label :class "medium special"
:text "Audio")
(box :orientation "h"
:space-evenly false
:width 200
:halign "start"
:spacing 10
(volume--gauge :io "out")
(volume--gauge :io "in"))
))

View File

@@ -12,6 +12,7 @@
:start-at 0
:clockwise true
:width 16
:height 16
:thickness 1
(box :class 'fill' ))))
@@ -68,6 +69,17 @@
(for workspace in {sway--data.ws[sway--data.context ?: "personal"][group]}
(sway--workspace :ws workspace))))
(defwidget sway-workspaces-vertical [group]
(box :orientation "v"
:valign "start"
:halign "center"
:visible {sway--data != ''}
:space-evenly false
:spacing 5
:class "sway--root sway--vertical"
(for workspace in {sway--data.ws[sway--data.context ?: "personal"][group]}
(sway--workspace :ws workspace))))
(defwidget hypr-workspaces [group]
(box :orientation "h"
:halign "start"

View File

@@ -8,17 +8,109 @@ Used instead of the `date` command in order to avoid spawning a new process ever
import datetime
import sys
import time
import asyncio
from asyncinotify import Inotify, Mask
from pathlib import Path
from json import load, dump
def get_date():
"""Return the current date as a JSON object string."""
return datetime.datetime.now().strftime(
'{"hour": "%H", "minute": "%M", "second": "%S", "day": "%d", "month": "%m", "year": "%Y", "dow": "%A","month_name": "%B", "unix": %s}'
)
def refresh_tz():
"""Reload the timezone."""
tz_file = Path("~/.config/eww/displaytime").expanduser()
source = "user"
if not tz_file.exists():
tz_file = Path("/etc/localtime")
source = "system"
if tz_file.exists():
tz = str(tz_file.resolve().relative_to("/usr/share/zoneinfo"))
else:
tz = "Etc/UTC"
source = "static"
return tz, source
def refresh_timer():
"""Reload the timer."""
timer_file = Path("~/.config/eww/timer.json").expanduser()
if timer_file.exists() and timer_file.is_file():
with timer_file.open("r") as f:
return load(f)
return None
timezone, source = refresh_tz()
timer = refresh_timer()
def format_timer():
"""Format the timer delta."""
if timer is None:
return {"active": False, "error": False, "reason": "no timer"}
startstamp = timer.get("start")
if startstamp is None:
return {"active": False, "error": True, "reason": "no start"}
start = datetime.datetime.fromtimestamp(startstamp)
now = datetime.datetime.now()
if (stopstamp := timer.get("stop")) is not None:
stop = datetime.datetime.fromtimestamp(stopstamp)
if stop < now:
return {"active": False, "error": False, "reason": f"stopped at {stop}"}
delta_sec = (now - start).total_seconds()
started = True
if delta_sec < 0:
started = False
delta = datetime.timedelta(seconds=int(abs(delta_sec)))
else:
delta = datetime.timedelta(seconds=int(delta_sec))
days = delta.days
remainder = datetime.timedelta(seconds=delta.total_seconds() - (days * 24 * 3600))
return {
"active": True,
"error": False,
"prefix": timer.get("prefix", "MET"),
"title": timer.get("title", "timer"),
"started": started,
"timer": f"{days}d {str(remainder)}",
}
async def main():
"""Print loop."""
asyncio.ensure_future(asyncio.create_task(monitor()))
while True:
dump(
{
"stamp": int(datetime.datetime.now().timestamp()),
"tz": timezone,
"tz-source": source,
"timer": format_timer(),
},
sys.stdout,
)
sys.stdout.write("\n")
sys.stdout.flush()
await asyncio.sleep(0.5)
async def monitor():
"""Timezone monitor loop."""
global timezone, source, timer
with Inotify() as inotify:
inotify.add_watch(
Path("~/.config/eww").expanduser(),
Mask.CREATE | Mask.DELETE | Mask.MODIFY,
)
inotify.add_watch(Path("/etc"), Mask.CREATE | Mask.DELETE)
async for event in inotify:
timezone, source = refresh_tz()
timer = refresh_timer()
if __name__ == "__main__":
while True:
print(get_date())
sys.stdout.flush()
time.sleep(0.5)
asyncio.run(main())

View File

@@ -13,6 +13,10 @@ except:
sys.exit(1)
from gi.repository import Playerctl, GLib
from gi.events import GLibEventLoopPolicy
import asyncio
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
title_maxlen = 40
title_end_at = ["(", "-"]
@@ -56,8 +60,8 @@ class StatusDisplay:
def show(self):
self._init_player()
main = GLib.MainLoop()
main.run()
loop = asyncio.get_event_loop()
loop.run_forever()
def _get_status(self, playing=None):
if self._player:

150
.config/eww/scripts/mpris2.py Executable file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python3
from gi.repository import AstalMpris as Mpris, Gio
from gi.events import GLibEventLoopPolicy
import json
import asyncio
import sys
PRIORITY_PLAYERS = {"Feishin"}
# Override the reported album if the URL is in this dict
URL_ALBUM_OVERRIDES = {
"https://distantworlds3.space/radio/": "Distant Radio",
"https://radio.distantworlds3.space/listen/distant_radio/distantradio.mp3": "Distant Radio",
}
class MprisMonitor(Gio.Application):
"""MPRIS monitor application."""
def __init__(self):
super().__init__()
self.mpris = Mpris.Mpris.new()
self.players: dict[str, Mpris.Player] = {} # type: ignore[annotation-unchecked]
self.display_player: Mpris.Player | None = None # type: ignore[annotation-unchecked]
def connect_player(self, mpris: Mpris.Mpris, player: Mpris.Player):
"""Connect a new player."""
self.players[player.props.bus_name] = player
if self.display_player is None:
self.display_player = player
elif (
player.props.identity in PRIORITY_PLAYERS
and self.display_player.props.identity not in PRIORITY_PLAYERS
):
self.display_player = player
player.connect("notify", self.on_status_update)
def disconnect_player(self, mpris: Mpris.Mpris, player: Mpris.Player):
"""Disconnect a closed player."""
print(f"player disconnected: {player.props.bus_name}", file=sys.stderr)
existing_player = self.players.get(player.props.bus_name)
self.players = {
key: val for key, val in self.players.items() if val is not existing_player
}
if existing_player is self.display_player:
self.display_player = None
for player in self.players.values():
if not self.display_player:
self.display_player = player
elif (
self.display_player.props.identity not in PRIORITY_PLAYERS
and player.props.identity in PRIORITY_PLAYERS
):
self.display_player = player
if existing_player is not None:
existing_player.disconnect_by_func(self.on_status_update)
del existing_player
self.output_status()
def do_activate(self):
"""Activate the application."""
self.mpris.connect("player-added", self.connect_player)
self.mpris.connect("player-closed", self.disconnect_player)
for player in self.mpris.get_players():
self.connect_player(self.mpris, player)
self.output_status()
self.hold()
def on_status_update(self, player: Mpris.Player, *args):
"""Perform status update tasks."""
if (
player.props.identity in PRIORITY_PLAYERS
and player.props.playback_status == Mpris.PlaybackStatus.PLAYING
):
self.display_player = player
elif (
player.props.playback_status == Mpris.PlaybackStatus.PLAYING
and self.display_player != Mpris.PlaybackStatus.PLAYING
):
self.display_player = player
self.output_status()
def output_status(self):
"""Print the status of the currently active player, or an offline status if no active player exists."""
if self.display_player is not None:
self.print_player_status(self.display_player)
else:
self.print_offline_status()
def print_player_status(self, player: Mpris.Player):
"""Print a player's status."""
print(
json.dumps(
{
"running": player.props.available
and player.props.playback_status != Mpris.PlaybackStatus.STOPPED,
"playing": player.props.playback_status
== Mpris.PlaybackStatus.PLAYING,
"title": player.props.title,
"artist": player.props.artist,
"album": URL_ALBUM_OVERRIDES.get(
player.props.metadata.unpack().get("xesam:url", ""),
player.props.album,
),
"album_artist": player.props.album_artist,
"position": player.props.position,
"position_minutes": int((player.props.position) // 60),
"position_seconds": f"{int((player.props.position + 0) % 60):02}",
"length": player.props.length,
"length_minutes": int((player.props.length + 0) // 60),
"length_seconds": f"{int((player.props.length + 0) % 60):02}",
"active_player": player.props.identity,
}
),
flush=True,
)
def print_offline_status(self):
"""Print an offline status for when we have no players."""
print(
json.dumps(
{
"running": False,
"playing": False,
"title": None,
"artist": None,
"album": None,
"album_artist": None,
"position": None,
"position_minutes": None,
"position_seconds": None,
"length": None,
"length_minutes": None,
"length_seconds": None,
"active_player": "",
}
),
flush=True,
)
if __name__ == "__main__":
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
app = MprisMonitor()
app.run()

View File

@@ -92,7 +92,7 @@ class Interface:
def get_first_hop() -> IPAddress | None:
"""Get the first network hop."""
# Use ping to get the first hop
cmd = ["/usr/bin/ping", "-c1", "-W0.3", "-t1", "1.1.1.1"]
cmd = ["ping", "-c1", "-W0.3", "-t1", "1.1.1.1"]
result = run(cmd, stdout=PIPE, stderr=PIPE)
try:
ip = IPAddress(result.stdout.decode("utf-8").split("\n")[1].split()[1])
@@ -121,6 +121,16 @@ def get_gateways():
return result
def get_ssid():
cmd = ["iwgetid", "-r"]
result = run(cmd, stdout=PIPE, stderr=PIPE)
if result.returncode == 0:
return result.stdout.decode("utf-8").strip()
else:
return None
def interface_status(interface: str, gw):
"""Get the status of an interface."""
try:
@@ -145,14 +155,14 @@ def interface_status(interface: str, gw):
def ping(host: IPAddress) -> bool:
cmd = ["/usr/bin/ping", "-c1", "-w1", str(host)]
cmd = ["ping", "-c1", "-w1", str(host)]
result = run(cmd, stdout=DEVNULL, stderr=DEVNULL)
return result.returncode == 0
def get_public_ip():
"""Get the public IP address."""
cmd = ["/usr/bin/curl", "-s", "https://ipinfo.io"]
cmd = ["curl", "-s", "https://ipinfo.io"]
result = run(cmd, stdout=PIPE, stderr=PIPE)
try:
data = json.loads(result.stdout.decode("utf-8"))
@@ -171,7 +181,7 @@ def get_public_ip():
def get_default_route():
"""Get the default route."""
cmd = ["/usr/bin/ip", "route", "show", "default"]
cmd = ["ip", "route", "get", "1.1.1.1"]
result = run(cmd, stdout=PIPE, stderr=PIPE)
try:
# Get first line (might have multiple gateway routes)
@@ -197,21 +207,6 @@ def format_time(time: datetime) -> dict[str, str | int]:
}
# system_bus = dbus.SystemBus()
# networkd = system_bus.get_object(
# "org.freedesktop.network1", "/org/freedesktop/network1"
# )
# manager = dbus.Interface(networkd, "org.freedesktop.network1.Manager")
# def get_dbus_interfaces():
# for iface in INTERFACES_TO_WATCH:
# dbus_object = system_bus.get_object(
# "org.freedesktop.network1", manager.GetLinkByName(iface)
# )
# dbus_interface = dbus.Interface(dbus_object, "org.freedesktop.network1.Link")
# yield dbus_interface
runtime_dir = os.environ.get("XDG_RUNTIME_DIR", "/tmp")
@@ -284,6 +279,12 @@ while True:
default_route_iface_data = interface_status(default_route, gw)
ssid = get_ssid()
if ssid is None:
wifi_connected = False
else:
wifi_connected = True
print(
json.dumps(
{
@@ -297,6 +298,11 @@ while True:
"have_default_route": default_route is not None,
"gateway": str(hop),
"have_gateway": hop is not None,
"wifi": {
"ssid": ssid,
"connected": wifi_connected,
"default": default_route == "wlan0",
},
"last_update": format_time(datetime.now()),
}
),

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python3
import subprocess
import json
import sys
from time import sleep
TRUSTED_NETWORKS = ['honnouji', 'honnouji_2.4']
def wifi():
ssid_cmd = subprocess.run(['iwgetid', '-r'], capture_output = True)
if ssid_cmd.returncode != 0:
return {"connected": False, "ssid": None}
return {"connected": True, "ssid": ssid_cmd.stdout.decode('utf-8').strip()}
def netdev(device: str):
ip_cmd = subprocess.run(['ip', '-j', 'addr', 'show', device], capture_output = True)
if ip_cmd.returncode != 0:
sys.stderr.write(ip_cmd.stdout.decode('utf-8'))
return {"exists": False, "online": False}
ip_data = json.loads(ip_cmd.stdout.decode('utf-8'))[0]
ip4_addr = ""
ip4_prefix_length = 24
addr4_info = list(filter(lambda addr: addr.get("family") == "inet", ip_data["addr_info"]))
if len(addr4_info) >= 1:
ip4_addr = addr4_info[0]["local"]
ip4_prefix_length = addr4_info[0]["prefixlen"]
online = ip_data["operstate"] == "UP" or ip4_addr != ""
connecting = ip_data["operstate"] != "DOWN" and (ip4_addr == "" or not online)
return {
"exists": True,
"online": online,
"connecting": connecting,
"offline": not online and not connecting,
"ip4_addr": ip4_addr,
"ip4_prefix": ip4_prefix_length
}
def trusted(ssid: str | None):
# Don't throw up an "INSECURE" alert when offline
if not ssid:
return True
return ssid in TRUSTED_NETWORKS
while True:
insight = netdev('insight')
wlan0 = netdev('wlan0')
wifi_data = wifi()
connected = insight['exists'] and insight['online'] or wifi_data['connected']
networks = {
"insight": insight,
"wlan0": netdev("wlan0"),
"ezrinet": netdev("ezrinet"),
"wg-mullvad": netdev("wg-mullvad"),
}
result = {
'connected': connected,
"network": networks,
'wifi': wifi_data,
"trusted": True,
"ip4_addrs": [ network.get('ip4_addr') for network in networks.values() if network.get('ip4_addr', "") != "" ]
}
print(json.dumps(result), flush=True)
sleep(1)

View File

@@ -1,81 +0,0 @@
#!/usr/bin/env python3
import subprocess
import json
from time import sleep
TRUSTED_NETWORKS = ['gayer people']
def wifi():
ssid_cmd = subprocess.run(['iwgetid', '-r'], capture_output = True)
if ssid_cmd.returncode != 0:
return {"connected": False, "ssid": None}
ssid = ssid_cmd.stdout.decode('utf-8').strip()
ssid = ssid if len(ssid) <= 15 else f"{ssid[:14]}…"
return {"connected": True, "ssid": ssid}
def netdev(device: str):
ip_cmd = subprocess.run(['ip', '-j', 'addr', 'show', device], capture_output = True)
if ip_cmd.returncode != 0:
return {"exists": False}
ip_data = json.loads(ip_cmd.stdout.decode('utf-8'))[0]
online = ip_data["operstate"] == "UP"
ip4_addr = ""
ip4_prefix_length = 24
addr4_info = list(filter(lambda addr: addr.get("family") == "inet", ip_data["addr_info"]))
if len(addr4_info) >= 1:
ip4_addr = addr4_info[0]["local"]
ip4_prefix_length = addr4_info[0]["prefixlen"]
connecting = ip_data["operstate"] != "DOWN" and (ip4_addr == "" or not online)
return {
"exists": True,
"online": online,
"connecting": connecting,
"offline": not online and not connecting,
"ip4_addr": ip4_addr,
"ip4_prefix": ip4_prefix_length
}
def trusted(ssid: str | None):
# Don't throw up an "INSECURE" alert when offline
if not ssid:
return True
return ssid in TRUSTED_NETWORKS
def ping(host: str):
ping_cmd = subprocess.Popen(['/usr/bin/ping', '-c1', '-W2', host], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return ping_cmd
counter = 0
ezrinet_last = False
internet_last = False
ezrinet_ping = None
internet_ping = None
while True:
if counter == 0:
ezrinet_ping = ping('10.242.3.1')
internet_ping = ping('1.1.1.1')
if ezrinet_ping.poll() is not None:
ezrinet_last = ezrinet_ping.returncode == 0
if internet_ping.poll() is not None:
internet_last = internet_ping.returncode == 0
wifi_data = wifi()
result = {
"wifi": wifi_data,
"network": {
"wlan0": netdev("wlan0"),
"ezrinet": netdev("ezrinet"),
"wg-mullvad": netdev("mullvad"),
},
"connection": {
"ezrinet": ezrinet_last,
"internet": internet_last
},
"trusted": trusted(wifi_data.get("ssid", None))
}
print(json.dumps(result), flush=True)
counter += 1
# Send pings every 10 seconds
counter %= 3
sleep(1)

397
.config/eww/scripts/network2.py Executable file
View File

@@ -0,0 +1,397 @@
#!/usr/bin/env python3
"""Asynchronous network monitoring script, reports current state every 5 seconds."""
from ipaddress import (
IPv4Address as IPAddress,
IPv4Network as IPNetwork,
IPv4Interface as IPInterface,
AddressValueError,
)
from dbus_fast.aio import MessageBus
from dbus_fast import Variant, BusType
import re
import asyncio
from subprocess import PIPE
import functools
import sys
from typing import Any, Self, Callable, Awaitable
import aiohttp
import datetime
import json
import socket
from vpn_manager.api.manager import VPNManager, VPNConnection
class PingableIPAddress(IPAddress):
"""IPv4 address with a ping function."""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.reachable: bool | None = None
async def ping(self) -> tuple[bool, float | None]:
"""
Ping this IP address.
Returns a tuple of [success, ping time in ms]
"""
proc = await asyncio.create_subprocess_exec(
"ping", "-c1", "-w1", "-n", str(self), stdout=PIPE
)
stdout, stderr = await proc.communicate()
_match = re.search(
r"icmp_seq=1 ttl=[0-9]+ time=([0-9]+\.?[0-9]*) ms", stdout.decode("utf-8")
)
if _match is None:
self.reachable = False
return (False, None)
else:
self.reachable = True
return (True, float(_match.group(1)))
class Netdata:
"""Container for netdata."""
monitor_ips = {
"ezrinet": PingableIPAddress("10.242.3.1"),
"internet": PingableIPAddress("1.1.1.1"),
}
ssids: list[str] | None = None
public_ip: IPAddress | None = None
public_ip_meta: dict[str, str] | None = None
last_ip_pull = datetime.datetime.fromtimestamp(0)
gateway: PingableIPAddress | None = None
route_on: str | None = None
net_state: str | None = None
vpns: dict[str, bool] = {}
interfaces: dict[str, Any] = {}
INTERFACE_IGNORE_PATTERN = re.compile(r"^vb-|^vz-|^virbr[0-9]+|^lo$")
[
re.compile(r"^vb-"),
re.compile(r"^vz-"),
re.compile(r"^virbr[0-9]+"),
re.compile(r"^lo$"),
]
def timer(interval: float, initial_delay: float = 0):
"""Decorate a function or coroutine to run it on the given interval."""
def decorator(func):
async def do_interval():
try:
await asyncio.sleep(initial_delay)
while True:
try:
await func()
except asyncio.CancelledError:
return
except:
pass
await asyncio.sleep(interval)
except asyncio.CancelledError:
return
def start():
loop = asyncio.get_running_loop()
if "_timer_task" in func.__dict__:
raise Exception("Timer is already running.")
func.__dict__["_timer_task"] = loop.create_task(do_interval())
func.__dict__["start"] = start
return func
return decorator
async def ping(address: IPAddress) -> tuple[bool, float | None]:
"""Ping a host and return a tuple of [success, ping time]."""
proc = await asyncio.create_subprocess_exec(
"ping", "-c1", "-w1", "-n", str(address), stdout=PIPE
)
stdout, stderr = await proc.communicate()
_match = re.search(
r"icmp_seq=1 ttl=[0-9]+ time=([0-9]+\.?[0-9]*) ms", stdout.decode("utf-8")
)
if _match is None:
return (False, None)
else:
return (True, float(_match.group(1)))
class Poller:
"""Poller."""
def __init__(self, freq: int, startup_delay: int = 0):
self._freq = freq
self._startup_delay = startup_delay
self._coros = []
self._task: asyncio.Task | None = None
self._after_poll = lambda context: None
self._setup = lambda context: None
self._context: dict[str, Any] = {}
def poll(
self, func: Callable[[asyncio.TaskGroup, dict[str, Any]], Awaitable[None]]
) -> Callable[[asyncio.TaskGroup], Awaitable[None]]:
"""Decorate a coroutine function to be polled."""
# suppress the exceptions, logging them but otherwise ignoring them.
async def suppressor(*args):
try:
await func(*args)
except (KeyboardInterrupt, SystemExit, asyncio.CancelledError):
print(
f"Poller {func.__name__} cancelled or aborted.",
file=sys.stderr,
flush=True,
)
raise
except BaseException as e:
print(
f"Poller {func.__name__} raised an exception:",
e,
file=sys.stderr,
flush=True,
)
self._coros.append(suppressor)
def after_poll(
self, func: Callable[[dict[str, Any]], None]
) -> Callable[[dict[str, Any]], None]:
"""
Decorate a function to run after each poll cycle (after all pollers are done).
Should not be a coroutine function, and should not execute any blocking I/O or long-running
tasks.
"""
self._after_poll = func
return func
def setup(
self, func: Callable[[dict[str, Any]], None]
) -> Callable[[dict[str, Any]], None]:
"""
Decorate a function to run before the poll cycle starts to set up the context.
This can be a coroutine.
"""
self._setup = func
return func
async def start(self):
"""Execute polling in a loop."""
try:
await asyncio.sleep(self._startup_delay)
if asyncio.iscoroutinefunction(self._setup):
await self._setup(self._context)
else:
self._setup(self._context)
while True:
start = datetime.datetime.now()
try:
async with asyncio.timeout(self._freq):
async with asyncio.TaskGroup() as tg:
for coro in self._coros:
tg.create_task(coro(tg, self._context))
except asyncio.TimeoutError:
print(
"Warning: poll took too long! Not all data will be updated!",
file=sys.stderr,
flush=True,
)
self._after_poll(self._context)
end = datetime.datetime.now()
await asyncio.sleep(self._freq - (start - end).total_seconds())
except asyncio.CancelledError:
return
mainloop = Poller(5)
@mainloop.poll
async def ping_checks(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring ping checks for configured IP addresses."""
for ip in Netdata.monitor_ips.values():
tg.create_task(ip.ping())
@mainloop.poll
async def firsthop_check(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring firsthop check."""
# Targeted IP doesn't matter, since TTL is 1
proc = await asyncio.create_subprocess_exec(
"ping", "-c1", "-W1", "-t1", "1.1.1.1", stdout=PIPE
)
stdout, stderr = await proc.communicate()
try:
ip = IPAddress(stdout.decode("utf-8").split("\n")[1].split()[1])
Netdata.gateway = ip
except (IndexError, AddressValueError):
# No firsthop
Netdata.gateway = None
@mainloop.poll
async def route_check(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring route check."""
proc = await asyncio.create_subprocess_exec(
"ip", "route", "show", "default", stdout=PIPE
)
stdout, stderr = await proc.communicate()
try:
# First line
line = stdout.decode("utf-8").splitlines()[0]
# Get the link name
link = re.search(r"dev\s+(\S+)", line).group(1)
Netdata.route_on = link
except:
Netdata.route_on = None
@mainloop.poll
async def public_ip(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring public IP retriever."""
# Only query for public IP if it's been at least 1 hour since the last query or our gateway has changed.
refresh = False
if Netdata.public_ip is None:
refresh = True
print("no public IP data, fetching", file=sys.stderr, flush=True)
if context.get("last_gateway") != Netdata.gateway:
refresh = True
print("gateway changed, refreshing public IP", file=sys.stderr, flush=True)
if Netdata.last_ip_pull < datetime.datetime.now() - datetime.timedelta(hours=1):
refresh = True
print(
f"last public IP pull was at {Netdata.last_ip_pull.isoformat()}, refreshing",
file=sys.stderr,
flush=True,
)
if refresh:
async with aiohttp.ClientSession() as session:
async with session.get("https://ipinfo.io/json") as response:
data: dict[str, str] = await response.json()
Netdata.public_ip_meta = data
Netdata.public_ip = IPAddress(data["ip"])
Netdata.last_ip_pull = datetime.datetime.now()
context["last_gateway"] = Netdata.gateway
@mainloop.setup
async def setup_dbus(context: dict[str, Any]) -> MessageBus:
"""Set up the D-Bus connections."""
bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
context["system_bus"] = bus
intro = await bus.introspect(
"org.freedesktop.network1", "/org/freedesktop/network1"
)
context["network_manager"] = bus.get_proxy_object(
"org.freedesktop.network1", "/org/freedesktop/network1", intro
)
context["network_manager_iface"] = context["network_manager"].get_interface(
"org.freedesktop.network1.Manager"
)
context["network_manager_props"] = context["network_manager"].get_interface(
"org.freedesktop.DBus.Properties"
)
context["vpn_manager"] = VPNManager(allow_interactive_authorization=True, bus=bus)
@mainloop.poll
async def global_netstate(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring global netstate retriever."""
manager = context["network_manager_iface"]
Netdata.net_state = await manager.get_operational_state()
@mainloop.poll
async def interface_status(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring interface stats generator."""
# Set up D-Bus connection if it's not there already
bus: MessageBus = context["system_bus"]
manager = context["network_manager_iface"]
descr = json.loads(await manager.call_describe())
ifaces = [
{
**iface,
"Addresses": [
IPInterface(
f"{'.'.join(str(octet) for octet in addr['Address'])}/{addr['PrefixLength']}"
)
for addr in iface.get("Addresses", [])
if addr["Family"] == socket.AF_INET
],
}
for iface in descr["Interfaces"]
if not INTERFACE_IGNORE_PATTERN.match(iface["Name"])
]
wlans = [iface for iface in ifaces if iface["Type"] == "wlan"]
Netdata.ssids = [wlan["SSID"] for wlan in wlans if "SSID" in wlan]
Netdata.interfaces = {
iface["Name"]: {
"type": iface["Type"],
"state": iface["OnlineState"],
"addresses": [str(addr) for addr in iface["Addresses"]],
}
for iface in ifaces
}
async def get_vpn_conn(conn: VPNConnection):
"""Populate data for a VPN connection."""
Netdata.vpns[conn._name] = await conn.is_connected()
@mainloop.poll
async def vpn_status(tg: asyncio.TaskGroup, context: dict[str, Any]):
"""Recurring VPN stats generator."""
manager: VPNManager = context["vpn_manager"]
connections = await manager.get_connections()
Netdata.vpns = {}
for connection in connections:
tg.create_task(get_vpn_conn(connection))
@mainloop.after_poll
def output_data(context: dict[str, Any]):
"""Print the data to stdout."""
routable = False
for iface in Netdata.interfaces.values():
if iface["type"] in {"wlan", "ether"} and iface["state"] == "online":
routable = True
break
json.dump(
{
"routable": routable,
"ssids": Netdata.ssids,
"ping_status": {
key: value.reachable for key, value in Netdata.monitor_ips.items()
},
"gateway": str(Netdata.gateway) if Netdata.gateway else None,
"route_on": Netdata.route_on,
"public_ip": Netdata.public_ip_meta,
"operational_state": Netdata.net_state,
"vpn_state": Netdata.vpns,
"interfaces": Netdata.interfaces,
},
sys.stdout,
)
sys.stdout.write("\n")
sys.stdout.flush()
asyncio.run(mainloop.start())

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Polkit agent."""
import json
import gi
from gi.repository import Gtk, GLib, Gdk
from gi.events import GLibEventLoopPolicy
import asyncio
from asyncio import subprocess
import sys
policy = GLibEventLoopPolicy()
asyncio.set_event_loop_policy(policy)
loop = asyncio.get_event_loop()
class AuthHandler(Gtk.Application):
def __init__(self):
super().__init__(application_id="dev.ezri.PolkitAuthHandler")
GLib.set_application_name("Polkit Auth Handler")
sasscmd = loop.run_until_complete(
asyncio.create_subprocess_shell(
"sass ~/.config/eww/eww.scss -I ~/.config/eww", stdout=subprocess.PIPE
)
)
css = loop.run_until_complete(sasscmd.stdout.read())
self.style_provider = Gtk.CssProvider()
self.style_provider.load_from_string(css.decode("utf-8"))
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(),
self.style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
async def handle_input(self):
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
while True:
try:
line = await reader.readline()
except:
break
decoded = json.loads(line.decode("utf-8"))
if decoded["action"] == "request password":
self.message_label.set_label(
decoded.get("polkit action", decoded)["message"]
)
self.prompt_label.set_label(decoded.get("prompt", "Password:"))
self.password_field.set_editable(True)
elif decoded["action"] == "authorization response":
if decoded["authorized"]:
break
def send_response(self, password_field):
password_field.set_editable(False)
password = password_field.get_text()
print(json.dumps({"action": "authenticate", "password": password}), flush=True)
def do_activate(self):
window = Gtk.ApplicationWindow(
application=self,
title="Password prompt",
css_classes=["root", "darkbg"],
default_width=600,
default_height=350,
)
# window.get_style_context().add_provider(self.style_provider, 801)
box = Gtk.Box(
spacing=20, homogeneous=False, orientation=Gtk.Orientation.VERTICAL
)
window.set_child(box)
box.append(
Gtk.Label(label="Authentication Required", css_classes=["nebula", "big"])
)
self.message_label = Gtk.Label(css_classes=["paragraph"])
box.append(self.message_label)
pwbox = Gtk.Box(
spacing=5, homogeneous=False, orientation=Gtk.Orientation.HORIZONTAL
)
self.prompt_label = Gtk.Label()
pwbox.append(self.prompt_label)
self.password_field = Gtk.PasswordEntry(hexpand=True)
pwbox.append(self.password_field)
self.password_field.connect("activate", self.send_response)
box.append(pwbox)
window.present()
loop.create_task(self.handle_input())
app = AuthHandler()
exit_status = app.run()
sys.exit(exit_status)

View File

@@ -100,7 +100,7 @@ while True:
"sensors": {chip: sensor(chip) for chip in sensor_list},
"memory": memory(),
"swap": swap(),
"reboot": reboot(),
# "reboot": reboot(),
"hostname": machine_info.get("PRETTY_HOSTNAME"),
}
print(json.dumps(result), flush=True)

View File

@@ -1,56 +1,51 @@
#!/usr/bin/env python3
# This script prints the time remaining for the current timer
# as stored in the file ~/.timer as a binary unix timestamp.
# Produces a mission timer based on the start time stored in the ~/.config/eww/timer.json file.
# This file should have an object with three fields: "start", containing the start time as an integer
# Unix timestamp in second resolution, "title", containing the title to be presented beneath the timer,
# and "prefix", containing a string to render in front of the timer (such as "MET")
import time
import datetime
import json
import sys
import asyncio
from asyncinotify import Inotify, Mask
from pathlib import Path
# Get the time stored in the file ~/.timer
try:
with open("/home/ezri/.timer", "br") as f:
timer = f.read()
except FileNotFoundError:
print("It's Time!", flush=True)
exit()
# Convert the binary unix timestamp to a datetime object
timer = datetime.datetime.fromtimestamp(int.from_bytes(timer, "big"))
def refresh_timer():
"""Reload the timer."""
while True:
# Get the current time
now = datetime.datetime.now()
timer_file = Path("~/.config/eww/timer.json")
if timer_file.exists() and timer_file.is_file():
with timer_file.open("r") as f:
return json.load(f)
return None
# Calculate the time remaining
remaining = timer - now
# If the timer has already expired, delete the file and exit
if remaining.total_seconds() <= 0:
import os
timer = refresh_timer()
os.remove("/home/ezri/.timer")
print("Timer expired.")
# Print the time remaining in the format "D days, HH:MM:SS"
if remaining.days == 0:
print(
"{} hours {:02d}:{:02d}".format(
remaining.seconds // 3600,
remaining.seconds % 3600 // 60,
remaining.seconds % 60,
),
flush=True,
)
else:
print(
"{} days, {:02d}:{:02d}:{:02d}".format(
remaining.days,
remaining.seconds // 3600,
remaining.seconds % 3600 // 60,
remaining.seconds % 60,
),
flush=True,
)
# Wait half a second before printing the time again
time.sleep(1)
async def main():
"""Print loop."""
asyncio.ensure_future(asyncio.create_task(monitor()))
while True:
if timer is None:
json.dump({"active": False}, sys.stdout)
else:
start = timer.get("start", 0)
now = datetime.datetime.now()
if now < start:
started = True
else:
started = False
json.dump(
{
"active": True,
"stamp": timer.get("start", 0),
"title": timer.get("title", "timer"),
"prefix": timer.get("prefix", "MET"),
},
sys.stdout,
)

View File

@@ -8,6 +8,7 @@ import json
import sys
import os
async def get_values(pulse):
sink = None
try:
@@ -18,27 +19,27 @@ async def get_values(pulse):
os.execv(sys.argv[0], sys.argv)
sink_result = {
"mute": sink.mute == 1,
"volume": f"{int(sink.volume.value_flat * 100):2}"
"volume": int(sink.volume.value_flat * 100 + 0.5),
}
source_result = {
"mute": source.mute == 1,
"volume": f"{int(source.volume.value_flat * 100):2}"
}
result = {
"output": sink_result,
"input": source_result
"volume": int(source.volume.value_flat * 100 + 0.5),
}
result = {"output": sink_result, "input": source_result}
print(json.dumps(result), flush=True)
return result
async def listen():
async with pulsectl_asyncio.PulseAsync("volume-monitor") as pulse:
await get_values(pulse)
async for _ in pulse.subscribe_events('all'):
async for _ in pulse.subscribe_events("all"):
await get_values(pulse)
async def main():
listen_task = asyncio.create_task(listen())
loop = asyncio.get_running_loop()
for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
loop.add_signal_handler(sig, listen_task.cancel)
@@ -46,5 +47,5 @@ async def main():
with suppress(asyncio.CancelledError):
await listen_task
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())

139
.config/eww/scripts/volume2.py Executable file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
from gi import require_version
require_version("AstalWp", "0.1")
from gi.repository import AstalWp as Wp, Gio, GObject
from gi.events import GLibEventLoopPolicy
import json
import asyncio
import sys
from typing import Any, Literal
from enum import IntEnum
import signal
import math
class EndpointBinding:
"""Collection of connection IDs for an audio endpoint."""
def __init__(self, endpoint: Wp.Endpoint, callback):
self.callback = callback
self.volume_binding = endpoint.connect_after(
"notify::volume", self._volume_callback
)
self.mute_binding = endpoint.connect_after("notify::mute", callback)
self.id = endpoint.props.id
self.endpoint = endpoint
def hash(self) -> int:
return self.volume_binding + (self.mute_binding << 32)
def _volume_callback(self, endpoint, param):
if round(endpoint.props.volume * 100) > 100:
self.endpoint.set_volume(1)
else:
self.callback(endpoint, param)
def __del__(self):
"""Object cleanup."""
self.endpoint.disconnect_by_func(self.callback)
self.endpoint.disconnect_by_func(self._volume_callback)
class AudioController(Gio.Application):
"""Audio controller application."""
def __init__(self):
super().__init__()
self.wp = Wp.get_default()
self.bindings = dict[int, EndpointBinding]()
def do_activate(self):
self.wp.connect("ready", self._on_ready)
self.hold()
loop = asyncio.get_running_loop()
for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
loop.add_signal_handler(sig, self.release)
def _on_ready(self, wp: Wp.Wp):
self.audio = self.wp.get_audio()
if len(sys.argv) > 1:
# this is a set-default call, so lets do that.
self.set_default(sys.argv[1], " ".join(sys.argv[2:]))
else:
self.audio.connect("microphone-added", self._device_added)
self.audio.connect("speaker-added", self._device_added)
self.audio.connect("microphone-removed", self._device_removed)
self.audio.connect("speaker-removed", self._device_removed)
for mic in self.audio.props.microphones:
self.bindings[mic.props.id] = EndpointBinding(mic, self._dump_info)
for sink in self.audio.props.speakers:
self.bindings[sink.props.id] = EndpointBinding(sink, self._dump_info)
self._dump_info()
def set_default(self, dir: Literal["in"] | Literal["out"], description: str):
"""Set the default source or sink."""
if dir == "in":
arr: list[Wp.Endpoint] = self.audio.get_microphones()
else:
arr = self.audio.get_speakers()
for dev in arr:
if dev.get_description() == description:
dev.set_is_default(True)
break
self.release()
def _device_added(self, audio: Wp.Audio, device: Wp.Endpoint):
self.bindings[device.props.id] = EndpointBinding(device, self._dump_info)
self._dump_info()
def _device_removed(self, audio: Wp.Audio, device: Wp.Endpoint):
try:
del self.bindings[device.props.id]
except KeyError:
pass
self._dump_info()
def _dump_info(self, *args):
asyncio.ensure_future(self._do_dump())
async def _do_dump(self):
await asyncio.sleep(0.1)
json.dump(
{
"inputs": [
mic.get_description() for mic in self.audio.get_microphones()
],
"outputs": [
sink.get_description() for sink in self.audio.get_speakers()
],
"input": {
"volume": round(
self.audio.get_default_microphone().get_volume() * 100
),
"mute": self.audio.get_default_microphone().get_mute(),
},
"output": {
"volume": round(
self.audio.get_default_speaker().get_volume() * 100
),
"mute": self.audio.get_default_speaker().get_mute(),
},
},
sys.stdout,
)
print()
sys.stdout.flush()
if __name__ == "__main__":
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
app = AudioController()
app.run()

5
.config/eww/timer.json Normal file
View File

@@ -0,0 +1,5 @@
{
"title": "DW3",
"prefix": "expedition clock",
"start": 1768759200
}

View File

@@ -2,6 +2,157 @@
;; Top-Level Module Definitions ;;
;;;; ;;;;
;;; ;;;
;; Generic Windows ;;
;;; ;;;
(defwidget system-sidebar [group side]
(box :orientation "v"
:valign "start"
:space-evenly false
:spacing 20
:class "root ${side}-side ${sway--data.visible[group].focused ? 'focused' : ''}"
(sideclock)
(side-mission-clock)
(network-sidebar-details)
(system-gauges)
(volume-gauges)))
(defwidget user-sidebar [orientation]
(box :orientation "v"
:valign "start"
:space-evenly false
:spacing 20
:class "root ${orientation}-side"
(sideclock)
(mpris-miniplayer)
(volume-gauges)))
(defwidget minigauges-bottomalign [battery]
(box :orientation "v"
:valign "end"
:space-evenly false
:spacing 10
(system--cpu-gauge-small)
(system--memory-gauge-small)
(system--swap-gauge-small)
(system--gpu-gauge-small)
(system--vram-gauge-small)
(volume-small-gauge)
(system-battery-gauge-small :battery {battery})))
(defwidget laptop-sidebar-top [group side]
(box :orientation "v"
:valign "start"
:space-evenly false
:spacing 10
:class "root ${side}-side ${sway--data.visible[group].focused ? 'focused' : ''}"
(sway-workspaces-vertical :group {group})))
(defwidget laptop-sidebar-bottom [group side battery]
(box :orientation "v"
:valign "start"
:space-evenly false
:spacing 10
:class "root ${side}-side ${sway--data.visible[group].focused ? 'focused' : ''}"
(minigauges-bottomalign :battery {battery})))
(defwidget ws-group-rightalign [workspace-group]
(box :orientation "h"
:halign "end"
:space-evenly false
:spacing 20
:class "rightbox"
(sway-workspace :group {workspace-group})
(sway-workspaces :group {workspace-group})))
(defwidget ws-group-leftalign [workspace-group]
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 20
:class "leftbox"
(sway-workspaces :group {workspace-group})
(sway-workspace :group {workspace-group})))
(defwidget desktop-details-rightalign []
(box :orientation "h"
:halign "end"
:space-evenly false
:spacing 20
:class "rightbox"
(mpris2)
(mission-clock)
(vpn-network)
(network)
(wifi)
(system-memory)
(system-cpu-avg)
(clock)))
(defwidget hostname-centeralign []
(box :orientation "h"
:halign "center"
:space-evenly false
:spacing 20
:class "centerbox"
(system-name)))
(defwidget hostname-leftalign []
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 20
:class "leftbox"
(system-name)))
(defwidget hostname-rightalign []
(box :orientation "h"
:halign "end"
:space-evenly false
:spacing 20
:class "rightbox"
(system-name)))
(defwidget hostname-and-workspace-leftalign [workspace-group]
(box :orientation "h"
:halign "start"
:valign "center"
:space-evenly false
:spacing 20
:class "leftbox"
(transform :translate-y "-2px"
(system-name))
(sway-workspace :group {workspace-group})))
(defwidget laptop-details-rightalign [?battery]
(box :orientation "h"
:halign "end"
:space-evenly false
:spacing 20
:class "rightbox"
(mpris2)
(vpn-network)
(network)
(wifi)
(clock)))
(defwidget horizontal-minigauges-rightalign []
(box :orientation "h"
:halign "end"
:space-evenly false
:spacing 20
:class "rightbox"
(system--cpu-gauge-small :invert {false})
(system--memory-gauge-small :invert {false})
(system--swap-gauge-small :invert {false})
(system--gpu-gauge-small :invert {false})
(system--vram-gauge-small :invert {false})
(volume-small-gauge :invert {false})))
;;; ;;;
;; Bars for Rocinante ;;
@@ -36,6 +187,32 @@
(audio)
(clock)))
(defwidget rocinante-externalbar--left []
(box :orientation "h"
:halign "start"
:space-evenly false
:spacing 20
:class "leftbox"
(system-name)))
(defwidget rocinante-externalbar--center []
(box :orientation "h"
:halign "center"
:space-evenly false
:spacing 20
:class "centerbox"))
(defwidget rocinante-externalbar--right []
(box :orientation "h"
:halign "end"
:space-evenly false
:spacing 20
:class "rightbox"
(vpn-network)
(network)
(system-battery :battery "BAT1")
(audio)
(clock)))
;;; ;;;
;; Bars for S.S.V. Normandy ;;

3
.config/git/ignore Normal file
View File

@@ -0,0 +1,3 @@
# Never include pycache directories or pyc files
__pycache__
*.pyc

View File

@@ -1,6 +1,6 @@
# Beware! This file is rewritten by htop when settings are changed in the interface.
# The parser is also very primitive, and not human-friendly.
htop_version=3.3.0
htop_version=3.4.1-3.4.1
config_reader_min_version=3
fields=0 48 17 18 38 39 40 2 46 47 49 1
hide_kernel_threads=1
@@ -21,12 +21,13 @@ strip_exe_from_cmdline=1
show_merged_command=0
header_margin=1
screen_tabs=1
detailed_cpu_time=0
detailed_cpu_time=1
cpu_count_from_one=0
show_cpu_usage=1
show_cpu_frequency=0
show_cpu_temperature=1
degree_fahrenheit=0
show_cached_memory=1
update_process_names=0
account_guest_in_cpu_meter=0
color_scheme=0
@@ -38,20 +39,20 @@ column_meters_0=Hostname Blank Tasks LoadAverage Uptime Clock
column_meter_modes_0=2 2 2 2 2 2
column_meters_1=AllCPUs2 Blank MemorySwap
column_meter_modes_1=1 2 1
tree_view=1
sort_key=0
tree_sort_key=0
sort_direction=1
tree_sort_direction=1
tree_view=0
sort_key=46
tree_sort_key=46
sort_direction=-1
tree_sort_direction=-1
tree_view_always_by_pid=0
all_branches_collapsed=0
screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command
.sort_key=PID
.tree_sort_key=PID
.sort_key=PERCENT_CPU
.tree_sort_key=PERCENT_CPU
.tree_view_always_by_pid=0
.tree_view=1
.sort_direction=1
.tree_sort_direction=1
.tree_view=0
.sort_direction=-1
.tree_sort_direction=-1
.all_branches_collapsed=0
screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command
.sort_key=IO_RATE

View File

@@ -4,6 +4,7 @@ certifi
charset-normalizer
dbus-python
decorator
exceptiongroup
executing
i3ipc
idna
@@ -14,10 +15,13 @@ netifaces
parso
pexpect
pickleshare
prompt-toolkit
prompt_toolkit
psutil
ptyprocess
pure-eval
pulsectl
pulsectl-asyncio
pure_eval
pyamdgpuinfo
pycairo
Pygments
PyGObject
@@ -28,5 +32,6 @@ six
stack-data
traitlets
trparse
typing_extensions
urllib3
wcwidth

View File

@@ -5,8 +5,9 @@
Host gitea
Hostname git.ezri.dev
User git
ControlPersist no
Host github
Hostname github.com
User git
ControlPersist no
Hostname github.com
User git
ControlPersist no

View File

@@ -22,17 +22,6 @@ set $colorprimary '#815986'
set $colorbackground '#2d272f'
set $colorwallpaper '#1e1e1e'
## ##
# Configure displays and workspaces #
## ##
## Set Background
output * bg '#1e1e1e' solid_color
## Include Device-Specific Configs
#include display-arrangement.conf
#include workspace-arrangement.conf
### ###
# Window Management Keybinds #
### ###
@@ -75,6 +64,13 @@ bindsym {
$mod+0 exec busctl --user call dev.ezri.sway /ContextManager dev.ezri.sway.ContextManager FocusWorkspace y 10
}
bindgesture {
swipe:3:left exec busctl --user call dev.ezri.sway /ContextManager dev.ezri.sway.ContextManager FocusNextWorkspace
swipe:4:left exec busctl --user call dev.ezri.sway /ContextManager dev.ezri.sway.ContextManager FocusNextWorkspace
swipe:3:right exec busctl --user call dev.ezri.sway /ContextManager dev.ezri.sway.ContextManager FocusPreviousWorkspace
swipe:4:right exec busctl --user call dev.ezri.sway /ContextManager dev.ezri.sway.ContextManager FocusPreviousWorkspace
}
## Window Reassignment Keybinds
bindsym {
$mod+Shift+1 exec busctl --user call dev.ezri.sway /ContextManager dev.ezri.sway.ContextManager MoveContainer y 1
@@ -199,6 +195,12 @@ for_window [floating] border normal 1
## Thunderbird New.* windows should float
for_window [app_id="thunderbird" title="(New|Write)"] floating enable
## Bitwarden extension windows should float by default
for_window [app_id="firefox" title="Extension: \(Bitwarden Password Manager\)"] floating enable
## Kwallet popup prompts should float
for_window [app_id="rg.kde.kwalletd6"] floating enable
### ###
# Application Keybinds #
### ###
@@ -207,19 +209,23 @@ for_window [app_id="thunderbird" title="(New|Write)"] floating enable
bindsym $mod+Shift+r reload
## Default Application Launcher
bindsym $mod+Return exec default-application-launcher
bindsym $mod+Return exec ~/.local/lib/voidshell/default-application-launcher
## Terminal Launcher
bindsym $mod+Shift+Return exec alacritty msg create-window || alacritty
## Program Menu
bindsym $mod+d exec wofi --show drun
bindsym $mod+d exec systemd-run --user --scope --slice=app-sway.slice --unit=app-sway-wofilaunch-$RANDOM.scope -- wofi --show drun
## Media Controls
bindsym --locked {
XF86AudioPlay exec playerctl play-pause
XF86AudioNext exec playerctl next
XF86AudioPrev exec playerctl previous
XF86AudioRaiseVolume exec wpctl set-volume @DEFAULT_SINK@ 5%+
XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_SINK@ 5%-
XF86AudioMute exec wpctl set-mute @DEFAULT_SINK@ toggle
}
## Screen Locking
@@ -247,14 +253,7 @@ include input.conf
# Service Management #
### ###
## Import Environment Variables
exec_always XDG_CURRENT_DESKTOP=sway {
systemctl --user import-environment DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP PATH ASDF_DATA_DIR
dbus-update-activation-environment DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP PATH ASDF_DATA_DIR
}
## Start Session Target
exec_always systemctl --user start sway-session.target
exec exec ~/.local/lib/voidshell/sway-session-manager setup
### ###
# Generic Local Configuration #

View File

@@ -1,13 +0,0 @@
### -*-conf-space-*- ###
# S.S.V. Normandy Display Config #
### ###
set $leftdisplay 'HDMI-A-1'
set $centerdisplay 'DP-1'
set $rightdisplay 'DP-2'
output {
$leftdisplay pos 0 220 mode --custom 1920x1080@75Hz
$centerdisplay pos 1920 0 mode 2560x1440@165Hz
$rightdisplay pos 4480 230 mode --custom 1920x1080@75Hz
}

View File

@@ -1,7 +0,0 @@
### -*-conf-space-*- ###
# Rocinante Display Config #
### ###
set $display eDP-1
output $display scale 1 pos 0 1080

View File

@@ -1,16 +0,0 @@
### -*-conf-space-*- ###
# Tycho Station Display Config #
### ###
set $leftdisplay "HP Inc. HP Z27n G2 6CM0151FHY"
set $centerdisplay "Hewlett Packard HP S340c CN490508SQ"
set $rightdisplay "HP Inc. HP Z27n G2 6CM0151FD4"
output {
$leftdisplay pos 0 450 mode 2560x1440@60Hz
$leftdisplay bg ~/Images/Wallpaper.png center
$centerdisplay pos 2560 450 mode 3440x1440 transform 0
$centerdisplay bg ~/Images/Wallpaper.png center
$rightdisplay pos 6000 0 mode 2560x1440@60Hz transform 90
$rightdisplay bg ~/Images/Sidepaper.png center
}

View File

@@ -1,3 +0,0 @@
#-*-conf-space-*-
exec_always gsettings set org.gnome.desktop.interface text-scaling-factor 1.25

View File

@@ -1 +0,0 @@
font pango:JetBrainsMono Nerd Font 12

42
.config/sway/outputs.json Normal file
View File

@@ -0,0 +1,42 @@
{
"zariman-builtin": {
"names": ["eDP-1"],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
}
},
"options": {
"mode": "2560x1600",
"refresh": 165,
"color_profile": "icc /usr/share/color/icc/colord/BOE_CQ_______NE160QDM_NZ6.icm"
}
},
"work-left": {
"make": "Acer Technologies",
"model": "XV271U M3",
"serial": "1322131231233",
"eww_windows": {},
"options": {
"mode": "2560x1440",
"refresh": 120
}
},
"work-right": {
"make": "Acer Technologies",
"model": "XV271U M3",
"serial": "1431038964205",
"options": {
"mode": "2560x1440",
"refresh": 120
}
},
"work-center": {
"make": "Dell Inc.",
"model": "DELL U3818DW",
"serial": "97F8P9350W0L",
"options": {
"mode": "3840x1600"
}
}
}

View File

@@ -1,8 +1,7 @@
# -*-conf-*-
timeout 10 'pgrep swaylock &> /dev/null && swaymsg output "*" power off' resume 'pgrep swaylock &> /dev/null && swaymsg output "*" power on'
lock ~/.local/bin/screenlock
before-sleep 'loginctl lock-session'
unlock 'pkill -SIGUSR1 swaylock'
idlehint 600
timeout 10 'systemctl --user start screens-off.target &> /dev/null' resume 'systemctl --user stop screens-off.target'
lock 'systemctl --user start screenlock.service'
before-sleep 'systemctl --user start screenlock.service'
unlock 'systemctl --user stop screenlock.service'
timeout 600 'systemctl --user start idle.target' resume 'systemctl --user stop idle.target'

View File

@@ -1,63 +0,0 @@
### -*-conf-space-*- ###
# Dual-Monitor Workspace Settings #
### ###
# This file defines the workspaces and creates the keybinds to switch
# between them and move containers around between them. This version
# is for dual-monitor systems. The host-specific display-arrangement.conf
# file should declare the $leftdisplay and $rightdisplay variables used
# here.
workspace {
# Left monitor workspaces
1 output $leftdisplay
2 output $leftdisplay
3 output $leftdisplay
4 output $leftdisplay
5 output $leftdisplay
6 output $leftdisplay
7 output $leftdisplay
8 output $leftdisplay
9 output $leftdisplay
10 output $leftdisplay
# Right monitor workspaces
11 output $rightdisplay
12 output $rightdisplay
13 output $rightdisplay
14 output $rightdisplay
15 output $rightdisplay
16 output $rightdisplay
17 output $rightdisplay
18 output $rightdisplay
19 output $rightdisplay
20 output $rightdisplay
}
## Workspace Switching Keybinds
bindsym {
$mod+1 exec swaymsg workspace $(sway-find-workspace 1 )
$mod+2 exec swaymsg workspace $(sway-find-workspace 2 )
$mod+3 exec swaymsg workspace $(sway-find-workspace 3 )
$mod+4 exec swaymsg workspace $(sway-find-workspace 4 )
$mod+5 exec swaymsg workspace $(sway-find-workspace 5 )
$mod+6 exec swaymsg workspace $(sway-find-workspace 6 )
$mod+7 exec swaymsg workspace $(sway-find-workspace 7 )
$mod+8 exec swaymsg workspace $(sway-find-workspace 8 )
$mod+9 exec swaymsg workspace $(sway-find-workspace 9 )
$mod+0 exec swaymsg workspace $(sway-find-workspace 10)
}
## Window Reassignment Keybinds
bindsym {
$mod+Shift+1 exec swaymsg move container to workspace $(sway-find-workspace 1 )
$mod+Shift+2 exec swaymsg move container to workspace $(sway-find-workspace 2 )
$mod+Shift+3 exec swaymsg move container to workspace $(sway-find-workspace 3 )
$mod+Shift+4 exec swaymsg move container to workspace $(sway-find-workspace 4 )
$mod+Shift+5 exec swaymsg move container to workspace $(sway-find-workspace 5 )
$mod+Shift+6 exec swaymsg move container to workspace $(sway-find-workspace 6 )
$mod+Shift+7 exec swaymsg move container to workspace $(sway-find-workspace 7 )
$mod+Shift+8 exec swaymsg move container to workspace $(sway-find-workspace 8 )
$mod+Shift+9 exec swaymsg move container to workspace $(sway-find-workspace 9 )
$mod+Shift+0 exec swaymsg move container to workspace $(sway-find-workspace 10)
}

View File

@@ -1,49 +0,0 @@
### -*-conf-space-*- ###
# Single-Monitor Workspace Settings #
### ###
# This file defines the workspaces and creates the keybinds to switch
# between them and move containers around between them. This version
# is for dual-monitor systems. The host-specific display-arrangement.conf
# file should declare the $display variable used here.
workspace {
1 output $display
2 output $display
3 output $display
4 output $display
5 output $display
6 output $display
7 output DP-1 DP-2 DP-3 DP-4 $display
8 output $display
9 output $display
10 output $display
}
## Workspace Switching Keybinds
bindsym {
$mod+1 exec swaymsg workspace 1
$mod+2 exec swaymsg workspace 2
$mod+3 exec swaymsg workspace 3
$mod+4 exec swaymsg workspace 4
$mod+5 exec swaymsg workspace 5
$mod+6 exec swaymsg workspace 6
$mod+7 exec swaymsg workspace 7
$mod+8 exec swaymsg workspace 8
$mod+9 exec swaymsg workspace 9
$mod+0 exec swaymsg workspace 10
}
## Window Reassignment Keybinds
bindsym {
$mod+Shift+1 exec swaymsg move container to workspace 1
$mod+Shift+2 exec swaymsg move container to workspace 2
$mod+Shift+3 exec swaymsg move container to workspace 3
$mod+Shift+4 exec swaymsg move container to workspace 4
$mod+Shift+5 exec swaymsg move container to workspace 5
$mod+Shift+6 exec swaymsg move container to workspace 6
$mod+Shift+7 exec swaymsg move container to workspace 7
$mod+Shift+8 exec swaymsg move container to workspace 8
$mod+Shift+9 exec swaymsg move container to workspace 9
$mod+Shift+0 exec swaymsg move container to workspace 10
}

View File

@@ -1,38 +0,0 @@
### -*-conf-space-*- ###
# Tie Fighter Workspace Settings #
### ###
# This file defines the workspaces and creates the keybinds to switch
# between them and move containers around between them. This version
# is for "tie-fighter" systems (one horizontal central display flanked
# by two vertical displays). The host-specific display-arrangement.conf
# file should declare the $leftdisplay, $centerdisplay, and $rightdisplay
# variables used here.
workspace {
# Left monitor workspaces
1 output $leftdisplay
2 output $leftdisplay
3 output $leftdisplay
4 output $leftdisplay
5 output $leftdisplay
# Center monitor workspaces
6 output $centerdisplay
7 output $centerdisplay
8 output $centerdisplay
9 output $centerdisplay
10 output $centerdisplay
11 output $centerdisplay
12 output $centerdisplay
13 output $centerdisplay
14 output $centerdisplay
15 output $centerdisplay
# Right monitor workspaces
16 output $rightdisplay
17 output $rightdisplay
18 output $rightdisplay
19 output $rightdisplay
20 output $rightdisplay
}

View File

@@ -1,75 +0,0 @@
### -*-conf-space-*- ###
# Dual-Monitor Workspace Settings #
### ###
# This file defines the workspaces and creates the keybinds to switch
# between them and move containers around between them. This version
# is for dual-monitor systems. The host-specific display-arrangement.conf
# file should declare the $leftdisplay and $rightdisplay variables used
# here.
workspace {
# Left monitor workspaces
1 output $leftdisplay
2 output $leftdisplay
3 output $leftdisplay
4 output $leftdisplay
5 output $leftdisplay
6 output $leftdisplay
7 output $leftdisplay
8 output $leftdisplay
9 output $leftdisplay
10 output $leftdisplay
# Center monitor workspaces
11 output $centerdisplay
12 output $centerdisplay
13 output $centerdisplay
14 output $centerdisplay
15 output $centerdisplay
16 output $centerdisplay
17 output $centerdisplay
18 output $centerdisplay
19 output $centerdisplay
20 output $centerdisplay
# Right monitor workspaces
21 output $rightdisplay
22 output $rightdisplay
23 output $rightdisplay
24 output $rightdisplay
25 output $rightdisplay
26 output $rightdisplay
27 output $rightdisplay
28 output $rightdisplay
29 output $rightdisplay
30 output $rightdisplay
}
## Workspace Switching Keybinds
bindsym {
$mod+1 exec swaymsg workspace $(sway-find-workspace 1 )
$mod+2 exec swaymsg workspace $(sway-find-workspace 2 )
$mod+3 exec swaymsg workspace $(sway-find-workspace 3 )
$mod+4 exec swaymsg workspace $(sway-find-workspace 4 )
$mod+5 exec swaymsg workspace $(sway-find-workspace 5 )
$mod+6 exec swaymsg workspace $(sway-find-workspace 6 )
$mod+7 exec swaymsg workspace $(sway-find-workspace 7 )
$mod+8 exec swaymsg workspace $(sway-find-workspace 8 )
$mod+9 exec swaymsg workspace $(sway-find-workspace 9 )
$mod+0 exec swaymsg workspace $(sway-find-workspace 10)
}
## Window Reassignment Keybinds
bindsym {
$mod+Shift+1 exec swaymsg move container to workspace $(sway-find-workspace 1 )
$mod+Shift+2 exec swaymsg move container to workspace $(sway-find-workspace 2 )
$mod+Shift+3 exec swaymsg move container to workspace $(sway-find-workspace 3 )
$mod+Shift+4 exec swaymsg move container to workspace $(sway-find-workspace 4 )
$mod+Shift+5 exec swaymsg move container to workspace $(sway-find-workspace 5 )
$mod+Shift+6 exec swaymsg move container to workspace $(sway-find-workspace 6 )
$mod+Shift+7 exec swaymsg move container to workspace $(sway-find-workspace 7 )
$mod+Shift+8 exec swaymsg move container to workspace $(sway-find-workspace 8 )
$mod+Shift+9 exec swaymsg move container to workspace $(sway-find-workspace 9 )
$mod+Shift+0 exec swaymsg move container to workspace $(sway-find-workspace 10)
}

View File

@@ -0,0 +1,408 @@
{
"default_context": "personal-portable",
"workspaces": [
{
"index": 1,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 2,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 3,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"environ": {},
"program_name": "firefox"
},
{
"index": 4,
"name": "project",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 5,
"name": "discord",
"exec": "vesktop",
"comms": true,
"program_name": "discord"
},
{
"index": 6,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 7,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 8,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 9,
"name": "project",
"exec": "firefox-developer-edition",
"args": ["-start-debugger-server", "--new-window"],
"program_name": "firefox-developer-edition"
},
{
"index": 10,
"name": "server management",
"exec": "virt-manager",
"program_name": "virt-manager"
},
{
"index": 11,
"name": "password management",
"exec": "bitwarden-desktop",
"program_name": "bitwarden"
},
{
"index": 12,
"name": "video",
"exec": "jellyfinmediaplayer",
"program_name": "jellyfinmediaplayer"
},
{
"index": 13,
"name": "comms",
"exec": "firefoxpwa",
"args": ["site", "launch", "01K233XSC3TE6CM7ZQZ1X303JX", "--protocol"],
"forking": true,
"program_name": "zoom"
},
{
"index": 14,
"name": "mail",
"exec": "thunderbird",
"program_name": "thunderbird",
"environ": {
"MOZ_ENABLE_WAYLAND": "1"
}
},
{
"index": 15,
"name": "config",
"exec": "console",
"program_name": "console"
},
{
"index": 16,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 17,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 18,
"name": "music",
"exec": "feishin-electron",
"program_name": "feishin"
},
{
"index": 19,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 20,
"name": "slack",
"exec": "slack",
"comms": true,
"program_name": "slack",
"args": [
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
"--enable-gpu-rasterization"
]
},
{
"index": 21,
"name": "livestream",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 22,
"name": "encrypted comms",
"exec": "signal-desktop",
"comms": true,
"program_name": "signal",
"environ": {
"ELECTRON_OZONE_PLATFORM_HINT": "wayland"
}
},
{
"index": 23,
"name": "steam",
"exec": "steam",
"program_name": "steam"
},
{
"index": 24,
"name": "telephony",
"exec": "telephony-launcher.sh",
"program_name": "telephony"
}
],
"contexts": {
"docked": {
"primary": "center",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 1100],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2560x1600@165Hz scale 1.5 color_profile icc /usr/share/color/icc/colord/BOE_CQ_______NE160QDM_NZ6.icm"
},
{
"make": "Dell Inc.",
"model": "DELL U2715H",
"serial": "H7YCC64Q0WFL",
"group": "left",
"position": [1706, 500],
"mode": "2560x1440 color_profile icc /usr/share/color/icc/colord/sRGB.icc",
"eww_windows": {
"desktop-leftbar": {},
"sidebar": {
"side": "left"
}
}
},
{
"make": "Dell Inc.",
"model": "DELL U3818DW",
"serial": "97F8P9350W0L",
"group": "center",
"position": [4266, 500],
"mode": "3840x1600 color_profile icc /usr/share/color/icc/colord/sRGB.icc",
"eww_windows": ["desktop-mainbar"]
},
{
"make": "Dell Inc.",
"model": "DELL U2722D",
"serial": "5X3MGH3",
"group": "right",
"position": [8106, 0],
"mode": "2560x1440 transform 270 color_profile icc /usr/share/color/icc/colord/sRGB.icc",
"eww_windows": {
"desktop-rightbar": {},
"vertical-bottombar": {}
}
}
],
"groups": {
"builtin": {
"workspaces": [21, 22],
"reverse": false
},
"left": {
"workspaces": [6, 2, 3, 13, 5],
"reverse": true
},
"center": {
"workspaces": [1, 7, 8, 9, 10, 11, 12, 24, 14, 15],
"reverse": false
},
"right": {
"workspaces": [16, 17, 19, 18, 20],
"reverse": false
}
}
},
"battlestation": {
"primary": "center",
"outputs": [
{
"make": "ASUSTek COMPUTER INC",
"model": "VG245",
"serial": "L7LMQS132447",
"group": "left",
"position": [0, 200],
"mode": "1920x1080@75Hz",
"eww_windows": {
"desktop-leftbar": {},
"sidebar": {
"side": "left"
}
}
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG32AQA1A",
"serial": "S5LMQS033656",
"group": "center",
"position": [1920, 0],
"mode": "2560x1440@165Hz",
"eww_windows": ["desktop-mainbar"]
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG245",
"serial": "L6LMQS065439",
"group": "right",
"position": [4480, 200],
"mode": "1920x1080@75Hz",
"eww_windows": {
"desktop-rightbar": {},
"sidebar": {
"side": "right"
}
}
},
{
"names": ["eDP-1"],
"group": "builtin",
"position": [6400, 600],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2560x1600@165Hz scale 1.5 color_profile icc /usr/share/color/icc/colord/BOE_CQ_______NE160QDM_NZ6.icm"
}
],
"groups": {
"builtin": {
"workspaces": [21, 22],
"reverse": false
},
"left": {
"workspaces": [6, 2, 3, 18, 5],
"reverse": true
},
"center": {
"workspaces": [1, 7, 8, 9, 10, 11, 12, 24, 14, 15],
"reverse": false
},
"right": {
"workspaces": [16, 17, 19, 13, 20],
"reverse": false
}
}
},
"work-portable": {
"primary": "builtin",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2560x1600@165Hz scale 1.25 color_profile icc /usr/share/color/icc/colord/BOE_CQ_______NE160QDM_NZ6.icm"
}
],
"groups": {
"builtin": {
"workspaces": [1, 7, 8, 9, 10, 13, 18, 20, 14, 15],
"reverse": false
}
}
},
"personal-portable": {
"primary": "builtin",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2560x1600@165Hz scale 1.25 color_profile icc /usr/share/color/icc/colord/BOE_CQ_______NE160QDM_NZ6.icm"
}
],
"groups": {
"builtin": {
"workspaces": [1, 7, 8, 9, 23, 22, 18, 5, 14, 15],
"reverse": false
}
}
},
"spawnpoint": {
"primary": "external",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [2560, 0],
"eww_windows": ["builtinbar"],
"mode": "2560x1600@165Hz scale 1.5 color_profile icc /usr/share/color/icc/colord/BOE_CQ_______NE160QDM_NZ6.icm"
},
{
"make": "LG Electronics",
"model": "LG UltraFine",
"serial": "111NTRL2Y030",
"group": "external",
"position": [0, 0],
"eww_windows": ["centerbar"],
"mode": "3840x2160@60Hz scale 1.5"
}
],
"groups": {
"builtin": {
"workspaces": [6, 2, 3, 18, 5],
"reverse": false
},
"external": {
"workspaces": [1, 7, 8, 9, 23, 22, 19, 20, 14, 15],
"reverse": false
}
}
}
}
}

View File

@@ -0,0 +1,398 @@
{
"default_context": "personal-portable",
"workspaces": [
{
"index": 1,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 2,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 3,
"name": "documentation",
"exec": "firefox",
"args": ["--new-window"],
"environ": {},
"program_name": "firefox"
},
{
"index": 4,
"name": "project",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 5,
"name": "discord",
"exec": "vesktop",
"program_name": "discord"
},
{
"index": 6,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 7,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 8,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 9,
"name": "project",
"exec": "firefox-developer-edition",
"args": ["-start-debugger-server", "--new-window"],
"program_name": "firefox-developer-edition"
},
{
"index": 10,
"name": "server management",
"exec": "virt-manager",
"program_name": "virt-manager",
"systemd": false
},
{
"index": 11,
"name": "password management",
"exec": "bitwarden-desktop",
"program_name": "bitwarden"
},
{
"index": 12,
"name": "video",
"exec": "jellyfinmediaplayer",
"program_name": "jellyfinmediaplayer"
},
{
"index": 13,
"name": "comms",
"exec": "zoom",
"program_name": "zoom"
},
{
"index": 14,
"name": "mail",
"exec": "thunderbird",
"program_name": "thunderbird",
"environ": {
"MOZ_ENABLE_WAYLAND": "1"
}
},
{
"index": 15,
"name": "config",
"exec": "console",
"program_name": "console"
},
{
"index": 16,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 17,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 18,
"name": "music",
"exec": "feishin",
"program_name": "feishin"
},
{
"index": 19,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 20,
"name": "slack",
"exec": "slack",
"program_name": "slack",
"args": [
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
"--enable-gpu-rasterization"
]
},
{
"index": 21,
"name": "stream",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 22,
"name": "encrypted comms",
"exec": "signal-desktop",
"program_name": "signal"
},
{
"index": 23,
"name": "steam",
"exec": "steam",
"program_name": "steam"
},
{
"index": 30,
"name": "present 1",
"exec": "console",
"program_name": "console"
},
{
"index": 31,
"name": "present 2",
"exec": "console",
"program_name": "console"
},
{
"index": 32,
"name": "present 3",
"exec": "console",
"program_name": "console"
}
],
"contexts": {
"docked": {
"primary": "center",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 850],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1.5"
},
{
"make": "Dell Inc.",
"model": "DELL U2722D",
"serial": "DV3MGH3",
"group": "left",
"position": [1504, 0],
"mode": "2560x1440 color_profile icc /usr/share/color/icc/colord/DCI-P3.icd",
"eww_windows": {
"desktop-leftbar": {},
"sidebar": {
"side": "left"
}
}
},
{
"make": "Dell Inc.",
"model": "DELL U3818DW",
"serial": "97F8P9350W0L",
"group": "center",
"position": [4064, 0],
"mode": "3840x1600",
"eww_windows": ["desktop-mainbar"]
},
{
"make": "Dell Inc.",
"model": "DELL U2722D",
"serial": "5X3MGH3",
"group": "right",
"position": [7904, 0],
"mode": "2560x1440 color_profile icc /usr/share/color/icc/colord/DCI-P3.icd",
"eww_windows": {
"desktop-rightbar": {},
"sidebar": {
"side": "right"
}
}
}
],
"groups": {
"builtin": {
"workspaces": [21, 22],
"reverse": false
},
"left": {
"workspaces": [6, 2, 3, 4, 5],
"reverse": true
},
"center": {
"workspaces": [1, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"reverse": false
},
"right": {
"workspaces": [16, 17, 19, 18, 20, 13],
"reverse": false
}
}
},
"battlestation": {
"primary": "center",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 600],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1.5"
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG245",
"serial": "L7LMQS132447",
"group": "left",
"position": [1504, 200],
"mode": "1920x1080@75Hz",
"eww_windows": {
"desktop-leftbar": {},
"sidebar": {
"side": "left"
}
}
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG32AQA1A",
"serial": "S5LMQS033656",
"group": "center",
"position": [3424, 0],
"mode": "2560x1440@165Hz",
"eww_windows": ["desktop-mainbar"]
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG245",
"serial": "L6LMQS065439",
"group": "right",
"position": [5984, 200],
"mode": "1920x1080@75Hz",
"eww_windows": {
"desktop-rightbar": {},
"sidebar": {
"side": "right"
}
}
}
],
"groups": {
"builtin": {
"workspaces": [21, 20],
"reverse": false
},
"left": {
"workspaces": [6, 2, 3, 18, 5],
"reverse": true
},
"center": {
"workspaces": [1, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"reverse": false
},
"right": {
"workspaces": [16, 17, 19, 4, 22],
"reverse": false
}
}
},
"portable": {
"primary": "builtin",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1.25"
}
],
"groups": {
"builtin": {
"workspaces": [1, 7, 8, 9, 23, 22, 18, 5, 14, 15],
"reverse": false
}
}
},
"presentation": {
"primary": "builtin",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1"
},
{
"names": ["DP-1", "DP-2", "DP-3", "DP-4"],
"group": "presentation",
"position": [2256, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
}
}
}
],
"groups": {
"builtin": {
"workspaces": [1, 7, 8, 9, 23, 22, 18, 5, 14, 15],
"reverse": false
},
"presentation": {
"workspaces": [30, 31, 32],
"reverse": false
}
}
}
}
}

View File

@@ -1,76 +1,398 @@
{
"default_context": "personal",
"display_ordering": ["builtin"],
"display_layout": {
"builtin": "eDP-1"
},
"contexts": {
"personal": {
"builtin": [
{
"index": 1,
"name": "terminal",
"exec": "console",
"program_name": "console"
},
{
"index": 2,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 3,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 4,
"name": "project",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 5,
"name": "servers",
"exec": "virt-manager",
"program_name": "virt-manager"
},
{
"index": 6,
"name": "steam",
"exec": "steam",
"program_name": "steam"
},
{
"index": 7,
"name": "media",
"exec": "jellyfinmediaplayer",
"program_name": "jellyfinmediaplayer"
},
{
"index": 8,
"name": "real-time comms",
"exec": "discord",
"program_name": "discord"
},
{
"index": 9,
"name": "messages",
"exec": "thunderbird",
"program_name": "thunderbird"
},
{
"index": 10,
"name": "config",
"exec": "pavucontrol",
"program_name": "pavucontrol"
}
"default_context": "personal-portable",
"workspaces": [
{
"index": 1,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 2,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 3,
"name": "documentation",
"exec": "firefox",
"args": ["--new-window"],
"environ": {},
"program_name": "firefox"
},
{
"index": 4,
"name": "project",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 5,
"name": "discord",
"exec": "vesktop",
"program_name": "discord"
},
{
"index": 6,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 7,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 8,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 9,
"name": "project",
"exec": "firefox-developer-edition",
"args": ["-start-debugger-server", "--new-window"],
"program_name": "firefox-developer-edition"
},
{
"index": 10,
"name": "server management",
"exec": "virt-manager",
"program_name": "virt-manager",
"systemd": false
},
{
"index": 11,
"name": "password management",
"exec": "bitwarden-desktop",
"program_name": "bitwarden"
},
{
"index": 12,
"name": "video",
"exec": "jellyfinmediaplayer",
"program_name": "jellyfinmediaplayer"
},
{
"index": 13,
"name": "comms",
"exec": "zoom",
"program_name": "zoom"
},
{
"index": 14,
"name": "mail",
"exec": "thunderbird",
"program_name": "thunderbird",
"environ": {
"MOZ_ENABLE_WAYLAND": "1"
}
},
{
"index": 15,
"name": "config",
"exec": "console",
"program_name": "console"
},
{
"index": 16,
"name": "console",
"exec": "console",
"program_name": "console"
},
{
"index": 17,
"name": "code",
"exec": "emacsclient",
"args": ["-nc"],
"program_name": "emacsclient"
},
{
"index": 18,
"name": "music",
"exec": "feishin",
"program_name": "feishin"
},
{
"index": 19,
"name": "internet",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 20,
"name": "slack",
"exec": "slack",
"program_name": "slack",
"args": [
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
"--enable-gpu-rasterization"
]
},
{
"index": 21,
"name": "stream",
"exec": "firefox",
"args": ["--new-window"],
"program_name": "firefox"
},
{
"index": 22,
"name": "encrypted comms",
"exec": "signal-desktop",
"program_name": "signal"
},
{
"index": 23,
"name": "steam",
"exec": "steam",
"program_name": "steam"
},
{
"index": 30,
"name": "present 1",
"exec": "console",
"program_name": "console"
},
{
"index": 31,
"name": "present 2",
"exec": "console",
"program_name": "console"
},
{
"index": 32,
"name": "present 3",
"exec": "console",
"program_name": "console"
}
],
"contexts": {
"docked": {
"primary": "center",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 850],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1.5"
},
{
"make": "Dell Inc.",
"model": "DELL U2722D",
"serial": "DV3MGH3",
"group": "left",
"position": [1504, 0],
"mode": "2560x1440 color_profile icc /usr/share/color/icc/colord/DCI-P3.icd",
"eww_windows": {
"desktop-leftbar": {},
"sidebar": {
"side": "left"
}
}
},
{
"make": "Dell Inc.",
"model": "DELL U3818DW",
"serial": "97F8P9350W0L",
"group": "center",
"position": [4064, 0],
"mode": "3840x1600",
"eww_windows": ["desktop-mainbar"]
},
{
"make": "Dell Inc.",
"model": "DELL U2722D",
"serial": "5X3MGH3",
"group": "right",
"position": [7904, 0],
"mode": "2560x1440 color_profile icc /usr/share/color/icc/colord/DCI-P3.icd",
"eww_windows": {
"desktop-rightbar": {},
"sidebar": {
"side": "right"
}
}
}
],
"groups": {
"builtin": {
"workspaces": [21, 22],
"reverse": false
},
"left": {
"workspaces": [6, 2, 3, 4, 5],
"reverse": true
},
"center": {
"workspaces": [1, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"reverse": false
},
"right": {
"workspaces": [16, 17, 19, 18, 20, 13],
"reverse": false
}
}
},
"battlestation": {
"primary": "center",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 600],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1.5"
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG245",
"serial": "L7LMQS132447",
"group": "left",
"position": [1504, 200],
"mode": "1920x1080@75Hz",
"eww_windows": {
"desktop-leftbar": {},
"sidebar": {
"side": "left"
}
}
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG32AQA1A",
"serial": "S5LMQS033656",
"group": "center",
"position": [3424, 0],
"mode": "2560x1440@165Hz",
"eww_windows": ["desktop-mainbar"]
},
{
"make": "ASUSTek COMPUTER INC",
"model": "VG245",
"serial": "L6LMQS065439",
"group": "right",
"position": [5984, 200],
"mode": "1920x1080@75Hz",
"eww_windows": {
"desktop-rightbar": {},
"sidebar": {
"side": "right"
}
}
}
],
"groups": {
"builtin": {
"workspaces": [21, 20],
"reverse": false
},
"left": {
"workspaces": [6, 2, 3, 18, 5],
"reverse": true
},
"center": {
"workspaces": [1, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"reverse": false
},
"right": {
"workspaces": [16, 17, 19, 4, 22],
"reverse": false
}
}
},
"portable": {
"primary": "builtin",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1.25"
}
],
"groups": {
"builtin": {
"workspaces": [1, 7, 8, 9, 23, 22, 18, 5, 14, 15],
"reverse": false
}
}
},
"presentation": {
"primary": "builtin",
"outputs": [
{
"names": ["eDP-1"],
"group": "builtin",
"position": [0, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
},
"laptopsidebar": {
"battery": "BAT1",
"side": "left"
}
},
"mode": "2256x1504 scale 1"
},
{
"names": ["DP-1", "DP-2", "DP-3", "DP-4"],
"group": "presentation",
"position": [2256, 0],
"eww_windows": {
"laptopbar": {
"battery": "BAT1"
}
}
}
],
"groups": {
"builtin": {
"workspaces": [1, 7, 8, 9, 23, 22, 18, 5, 14, 15],
"reverse": false
},
"presentation": {
"workspaces": [30, 31, 32],
"reverse": false
}
}
}
}
}

28
.config/swaylock/config Normal file
View File

@@ -0,0 +1,28 @@
font="Source Sans Pro"
font-size=15
indicator-radius=80
indicator-thickness=10
inside-color=#1e1e1e
inside-clear-color=#1e1e1e
inside-caps-lock-color=#1e1e1e
inside-ver-color=#1e1e1e
inside-wrong-color=#1e1e1e
key-hl-color=#815986
bs-hl-color=#cf6a4c
caps-lock-key-hl-color=#f9ee98
caps-lock-bs-hl-color=#cf6a4c
ring-color=#815986
ring-clear-color=#f9ee98
ring-caps-lock-color=#f9ee98
ring-ver-color=#815986
ring-wrong-color=#cf6a4c
line-uses-inside
text-color=#815986
text-clear-color=#f9ee98
text-caps-lock-color=#f9ee98
text-ver-color=#815986
text-wrong-color=#cf6a4c
color=#1e1e1e
show-failed-attempts
ignore-empty-password
indicator-idle-visible

View File

@@ -20,7 +20,7 @@
"timeout-critical": 0,
"fit-to-screen": false,
"relative-timestamps": true,
"control-center-width": 500,
"control-center-width": 800,
"control-center-height": 600,
"notification-window-width": 500,
"keyboard-shortcuts": true,

3
.config/systemd/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/user/*.wants
/user/*.requires
/user/*.upholds

View File

@@ -0,0 +1,2 @@
[Manager]
DefaultEnvironment=ELECTRON_OZONE_PLATFORM_HINT=wayland ORACLE_HOME=/usr

View File

@@ -1,13 +0,0 @@
[Unit]
Description=AggieTimeD
[Service]
Type=simple
ExecStartPre=-/usr/bin/rm /run/user/%U/aggietimed.sock
ExecStartPre=/usr/bin/sleep 1
ExecStart=/usr/bin/node /usr/bin/aggietimed -d -s /run/user/%U/aggietimed.sock -p echo
RestartSec=30
Restart=always
[Install]
WantedBy=sway-session.target

View File

@@ -1,9 +1,9 @@
[Unit]
Description=Terminal emulator
PartOf=sway-session.target
PartOf=graphical-session.target
[Service]
Type=simple
ExecStart=alacritty
Slice=gui.slice
ExecStart=alacritty --daemon --socket=%t/alacritty
ExecStopPost=rm %t/alacritty
Slice=app.slice

View File

@@ -0,0 +1,2 @@
[Unit]
Description=Command-line shells

View File

@@ -1,11 +0,0 @@
[Unit]
Description=dunst notification daemon
PartOf=sway-session.target
[Service]
Type=simple
ExecStart=dunst
Slice=session.slice
[Install]
WantedBy=sway-session.target

View File

@@ -1,20 +0,0 @@
[Unit]
Description=Emacs text editor
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
PartOf=graphical-session.target
[Service]
Type=forking
ExecStart=/usr/bin/emacs --daemon
ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
Environment=SSH_AUTH_SOCK=/run/user/1000/gnupg/S.gpg-agent.ssh
Environment=ASDF_DATA_DIR=/home/ezri/.local/share/asdf-vm
Restart=on-failure
Slice=session.slice
MemoryAccounting=yes
# Tide eats memory like crazy, so lets put it on a diet.
MemoryHigh=3G
MemoryMax=5G
[Install]
WantedBy=graphical-session.target

View File

@@ -0,0 +1,6 @@
[Socket]
ListenStream=%t/emacs/server
DirectoryMode=0700
[Install]
WantedBy=sockets.target

View File

@@ -3,8 +3,12 @@ Description=eww status bars
PartOf=sway-session.target
[Service]
Type=forking
ExecStart=eww daemon
Type=simple
ExecSearchPath=%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
Environment=PATH=%h/.pyenv/shims:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
Environment=PYENV_ROOT=%h/.pyenv
Environment=PYENV_VERSION=eww-modules
ExecStart=eww daemon --no-daemonize
ExecReload=eww reload
ExecStop=eww kill
ExecStopPost=sleep 1

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Ensure the service manager has an SSH agent available -- GnuPG Edition
BindsTo=gpg-agent-ssh.socket
After=gpg-agent-ssh.socket
[Service]
Type=oneshot
ExecStart= systemctl --user set-environment SSH_AUTH_SOCK=%t/gnupg/S.gpg-agent.ssh
ExecStop=systemctl --user unset-environment SSH_AUTH_SOCK=%t/gnupg/S.gpg-agent.ssh
RemainAfterExit=yes
[Install]
UpheldBy=gpg-agent-ssh.socket

View File

@@ -1,7 +0,0 @@
[Unit]
Description=Hyprland compositor session
Documentation=man:systemd.special
BindsTo=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target
Wants=sway-session.target

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Graphical session is idle
# Pull in the idle hint sync service
Wants=logind-idlehint.service
Requisite=graphical-session.target
After=screenlock.service
# Ensure that when the screen unlocks, the session is also no longer considered idle.
StopPropagatedFrom=screenlock.service
[Install]
# If "enabled", the graphical session will be considered idle while the screen is locked.
WantedBy=screenlock.service

View File

@@ -3,7 +3,12 @@ Description=KDE Connect Daemon
[Service]
Type=simple
ExecStart=/usr/lib/kdeconnectd
ExecStart=/usr/bin/kdeconnectd
Restart=on-failure
RestartSec=5s
RestartMaxDelaySec=1min
RestartSteps=5
Slice=session.slice
[Install]
WantedBy=default.target
WantedBy=graphical-session.target

View File

@@ -0,0 +1,14 @@
# This unit is pulled in by idle.target, but can be masked if idle hinting is not desired or if another program already provides it.
# This service should be preferred to other mechanisms, however, as it provides synchronization between the idle target and the logind
# state, which is generally what is desired.
[Unit]
Description=systemd-logind idle hint synchronization
PartOf=idle.target
Before=idle.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=busctl call org.freedesktop.login1 /org/freedesktop/login1/session/auto org.freedesktop.login1.Session SetIdleHint b 1
ExecStop=busctl call org.freedesktop.login1 /org/freedesktop/login1/session/auto org.freedesktop.login1.Session SetIdleHint b 0

View File

@@ -0,0 +1,15 @@
# This unit should be pulled in by a provider of screenlock.service if the service doesn't provide lock hinting support natively.
[Unit]
Description=systemd-logind lock hint synchronization
PartOf=screenlock.service
After=screenlock.service
StopWhenUnneeded=yes
RefuseManualStart=yes
RefuseManualStop=yes
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=busctl call org.freedesktop.login1 /org/freedesktop/login1/session/auto org.freedesktop.login1.Session SetLockedHint b 1
ExecStop=busctl call org.freedesktop.login1 /org/freedesktop/login1/session/auto org.freedesktop.login1.Session SetLockedHint b 0

View File

@@ -1,10 +0,0 @@
[Unit]
Description = PolicyKit authentication agent
BindTo=sway-session.target
[Service]
Type=simple
ExecStart=/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
[Install]
WantedBy=sway-session.target

View File

@@ -1,9 +0,0 @@
[Unit]
Description = Forwarding bluetooth audio controls to MPRIS players
[Service]
Type = simple
ExecStart = /usr/bin/mpris-proxy
[Install]
WantedBy = graphical@i3.target

View File

@@ -0,0 +1,7 @@
[Unit]
Description=Screen locked
Requires=screenlock.service
After=screenlock.service
Requisite=graphical-session.target
AllowIsolate=yes
StopPropagatedFrom=screenlock.service

View File

@@ -0,0 +1,2 @@
[Unit]
Description=Displays are turned off

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Suspend comms apps
PartOf=idle.target
Before=idle.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=bash -c 'echo 1 > /sys/fs/cgroup/user.slice/user-%U.slice/user@%U.service/app.slice/app-comms.slice/cgroup.freeze'
ExecStop=bash -c 'echo 0 > /sys/fs/cgroup/user.slice/user-%U.slice/user@%U.service/app.slice/app-comms.slice/cgroup.freeze'
[Install]
WantedBy=idle.target

View File

@@ -1,11 +1,16 @@
[Unit]
Description = Sway workspace context manager for dynamic-monitor setups
Description=Sway workspace context manager for dynamic-monitor setups
PartOf=sway-session.target
[Service]
Type=dbus
BusName=dev.ezri.sway
ExecStart=/usr/bin/sway_context_manager
ExecSearchPath=
ExecSearchPath=%h/.pyenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
Environment=PATH=%h/.pyenv/shims:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
Environment=PYENV_ROOT=%h/.pyenv
Environment=PYENV_VERSION=sway
ExecStart=sway_context_manager
Slice=session.slice
Restart=on-abnormal
RestartSec=5s

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Clean up sway session enviornment variables from the service manager
[Service]
Type=oneshot
ExecStart=%h/.local/bin/sway-session-manager.sh
[Install]
WantedBy=sway-session-shutdown.target

View File

@@ -0,0 +1,6 @@
[Unit]
Description=Sway session services which should run early before the graphical session is brought up
Documentation=man:systemd.special(7)
Requires=basic.target
RefuseManualStart=yes
StopWhenUnneeded=yes

View File

@@ -0,0 +1,4 @@
[Unit]
Description=Cleanup tasks for Sway compositor sessions that run after the compositor shuts down
RefuseManualStart=yes
StopWhenUnneeded=yes

View File

@@ -1,6 +1,9 @@
[Unit]
Description=Sway compositor session
Documentation=man:systemd.special
Documentation=man:systemd.special(7)
BindsTo=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target
Wants=sway-session-pre.target
After=sway-session-pre.target
Requisite=sway.scope
RefuseManualStart=yes
StopWhenUnneeded=yes

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Sway screen locker
Documentation=man:swaylock(1)
Requisite=sway-session.target
Wants=logind-lockhint.service
[Service]
Type=forking
ExecStart=swaylock -f
KillSignal=SIGUSR1
[Install]
Alias=screenlock.service

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Wallpaper daemon
PartOf=sway-session.target
Before=sway-session.target
[Service]
Type=notify-reload
ExecStart=%h/.local/lib/voidshell/background
NotifyAccess=all
Slice=background.slice
[Install]
WantedBy=sway-session.target

View File

@@ -0,0 +1,8 @@
[Scope]
MemoryAccounting=yes
CPUQuota=400%
MemoryHigh=2G
MemoryMax=4G
TasksMax=100

101
.config/voidshell/README.md Normal file
View File

@@ -0,0 +1,101 @@
# VoidShell Configuration
VoidShell is configured using a YAML file consisting of multiple top-level objects:
- `outputs` contains output definitions, which are referenced elsewhere by name
- `workspaces` contains worksapce definitions, which are referenced by ID
- `layouts` contains information about various output layouts that VoidShell will attempt to load when connected outputs change
- `contexts` contains information about a "context", which describes what workspaces should be present on which outputs and in what order, based on the current layout
- `config` contains global configuration variables that can be referenced elsewhere in the config file using Python format string syntax (`{var_name}`). Due to limitations with Python's `format` function, this object must be flat, i.e., contain only primitive values.
Additionally, there is a top-level `import` directive, which takes a list of file paths to other YAML files, allowing composable configuration. Relative paths are interpreted as being from the location of the *main config file*, not the file in which the `import` directive appears. If a listed file does not exist, it is ignored.
Imports are processed in a depth-first manner.
Finally, there is a top-level `default-context` directive, which specifies which context VoidShell should start in. If not specified, the first non-`default` context will be used, unless `default` is the only defined context in which case `default` will be used.
## Output Section
The `output` section defines the display outputs known to VoidShell. These are physical monitors, identified by various criteria, but should form a one-to-one mapping from the definition to the monitor. This should generally be accomplished by including make, model, and serial number, but for a laptop, the name `eDP-1` will nearly always refer exclusively to the built-in display, making it useful for those laptop panels which do not report a serial number in their EDIDs.
It consists of a mapping of internal output name to output definition.
Multiple output sections are concatenated together. If an output is defined twice, the definitions will be merged, with later appearances overwriting duplicate keys, unless stated otherwise.
### Structure
- `criteria`: Match criteria. The following criteria are available, and all must match for the output to be used:
- `name`: The name of the output as reported by the compositor. This changes based on what port the monitor is plugged into, and should therefore not be used for any external displays unless absolutely necessary. Can also be a list of names, in which case will match on any of them (OR filter)
- `hostname`: The hostname of the computer VoidShell is running on. Can also be a list of hostnames, in which case will match on any of them (OR filter)
- `make`: The make of the monitor, reported by EDID
- `model`: The model of the monitor, reported by EDID
- `serial`: The serial "number" of the monitor, reported by EDID
- `modes`: A list of modes which must be available. All modes listed must be available (AND filter). Undefined attributes are ignored (wildcard match).
- `width`: The width in pixels
- `height`: The height in pixels
- `refresh`: The integer refresh rate in millihertz
- `picture_aspect_ratio`: The aspect ratio listed in the EDID (not necessarily equal to `width / height`)
- `eww-windows`: A list of EWW (ElKowar's Wacky Widgets) windows to open when this output is activated, or an object mapping the name of an EWW window to additional variables to pass when opening the window.
- `options`: An object mapping configuration option names to values. When running under Sway, any unrecognized options will simply be added to the configuration command in the form `{option_name} {option_value}`. Duplicate `options` are merged, with later appearances overwriting duplicate keys. All options are optional, with the compositor determining what to do when an option is absent. The following options are understood:
- `resolution`: The resolution to set the output to. Must be a defined mode reported by the monitor EDID. Mutually exclusive with `custom-mode`.
- `refresh`: The refresh rate in millihertz. Added to `resolution` to produce the final mode line. Must be a defined refresh rate for the resolution. Mutually exclusive with `custom-mode`.
- `custom-mode`: The modeline to set the output to. Does not need to be defined by the monitor's EDID. Use with caution. Mutually exclusive with `mode` and `refresh`.
- `scale`: The scaling factor of the output.
## Workspaces Section
The `workspaces` section defines the workspaces that VoidShell manages. These are implemented using the underlying compositor's workspace system.
Workspaces have a concept of a "default application", which is the program that will be executed on this workspace when the default application keybind is pressed. These applications are run in their own cgroups, with logs directed to the user journal unless this is overridden.
It consists of a mapping of workspace IDs to workspace definitions. The IDs are used to relate the definition to a workspace in the compositor.
Multiple workspace sections are concatenated together. If a workspace ID is defined multiple times, the last definition is used.
### Structure
- `name`: The name of the workspace that is presented to the user
- `application`: The default application of the workspace. The name will have `.desktop` added to the end, and a matching file will be searched for in the standard desktop file locations. Mutually exclusive with `exec`, `args`, and `unit`.
- `exec`: The default application of the workspace. This is assumed to be an executable in VoidShell's PATH. It should contain only the executable name or a path to the executable. Mutually exclusive with `application` and `unit`.
- `args`: The arguments to pass to the command run with `exec`, excluding `argv[0]` (the program name itself). Optional. Mutually exclusive with `application` and `unit`.
- `environ`: A key-value mapping of environment variables and their values to set for the process. Mutually exclusive with `unit`.
- `unit`: A SystemD user unit to start as the default application of the workspace. Mutually exclusive with all other default application options.
- `systemd`: Boolean value indicating whether the default application should be run under SystemD for process control and logging. Defaults to true, and disabling has no effect if the `unit` option is used.
- `log-output`: Boolean value indicating whether the default application should be run with its ouptut (stdout/stderr) connected to the user journal (true), or to `/dev/null` (false). Defaults to true, and disabling has no effect if the `unit` option is used.
## Layouts Section
The `layouts` section defines the display layouts that VoidShell will attempt to configure. A scoring system is used to determine the best-fit layout for a given set of connected monitors, and the highest-scoring layout will be activated automatically. However, any compatible layout may be manually selected at any time.
It consists of a mapping of layout names to layout definitions.
Multiple layout sections are concatenated together. If a layout name is defined multiple times, the last definition is used.
### Structure
A layout object is a key-value mapping specifying the outputs used. The keys provide layout-local generic names for the outputs (e.g. "left" or "right"), which are referenced by contexts to define workspace groups. These objects have the following structure:
- `required`: Boolean value indicating whether the output must be present to activate the layout. Optional outputs still contribute to score when present, but do not remove the layout from consideration when absent. Defaults to true.
- `score`: The score that this output contributes to the layout when matched. Default is based on how the output was matched, detailed in the *Scoring* section.
- `position`: The position within the framebuffer to place the output, as a tuple of the form `[x, y]`. Values can be integers, in which case they are used directly, or strings, in which case they are processed as follows:
- They are formatted with both the global configuration (set in the `config` section), and an additional value for each previously-defined output equal to its framebuffer resolution (resolution after scaling) in the current dimension.
- This allows to create dynamic output positioning formulae
- Unmatched optional outputs will have their values here set to 0, so they can be safely used.
- They are then evaluated as mathematical expressions, and the result is used as the position.
- `outputs`: A list of outputs that can be used in this layout. If multiple listed outputs are connected, an earlier-listed output will be preferred if it is available.
- `options`: An options mapping as in an output definition. It is merged the same as other duplicate option sets, and overrides any keys set in the definition. Useful for e.g. layout-dependent scaling.
## Contexts Section
The `contexts` section defines the "contexts" that VoidShell can be used in (e.g. personal, work, school). These contexts define the mappings of workspaces to outputs based on layout.
It consists of a mapping of context names to context definitions.
Multiple context sections are concatenated together. If a context name is defined multiple times, the definitions will be merged, with duplicate groups being overridden in full by later definitions and configs merged.
The special context name `default` allows for the configuration of fallback workspace assignments in case a group is present in the current layout but not configured in the current context. This context cannot be marked active unless it is the only context defined.
### Structure
- `config`: A key-value pairing of configuration values local to this context. Values duplicated from the global config override those in the global config.
- `groups`: A key-value pairing of group names to workspace lists. These are matched against the layout-specific output names of the current layout, with group names not defined in the layout being ignored. The workspaces are identified by their IDs.

View File

@@ -0,0 +1,10 @@
vars:
battery: BAT1
slack:
team: E080NNY25MX
import:
- local.yml
- outputs.yml
- layouts.yml
- contexts.yml
- workspaces.yml

View File

@@ -0,0 +1,115 @@
contexts:
work:
groups:
auxiliary:
- media-streaming
primary:
- console-1
- code-1
- inet-1
- proj-1
- misc-image-editor
- misc-password
- docs-2
- comms-zoom
- comms-mail
- misc-audio
left-wing:
- console-2
- code-2
- docs-1
- comms-signal
- comms-discord
right-wing:
- console-3
- code-3
- inet-3
- media-music
- comms-slack
builtin:
- console-1
- code-1
- inet-1
- proj-1
- docs-1
- comms-zoom
- media-music
- comms-slack
- comms-mail
- misc-audio
entertainment:
groups:
auxiliary:
- media-streaming
primary:
- console-1
- code-1
- inet-1
- gaming-steam
- gaming-minecraft
- gaming-dnd
- gaming-pf
- gaming-other
- media-video
- misc-audio
left-wing:
- console-2
- gaming-notes-1
- inet-2
- media-streaming
- comms-discord
right-wing:
- console-3
- gaming-notes-2
- inet-3
- media-webvideo-1
- comms-signal
builtin:
- console-1
- gaming-notes-1
- gaming-pf
- gaming-steam
- gaming-minecraft
- gaming-dnd
- gaming-other
- comms-discord
- media-video
- misc-audio
personal:
groups:
auxiliary:
- media-streaming
primary:
- console-1
- code-1
- inet-1
- gaming-steam
- misc-image-editor
- misc-password
- media-video
- comms-matrix
- comms-zoom
- misc-audio
left-wing:
- console-2
- code-2
- inet-2
- comms-mail
- comms-discord
right-wing:
- console-3
- code-3
- inet-3
- media-music
- comms-signal
builtin:
- console-1
- code-1
- inet-1
- proj-1
- comms-signal
- comms-matrix
- media-music
- comms-discord
- comms-mail
- misc-audio

View File

@@ -0,0 +1,33 @@
layouts:
battlestation:
auxiliary:
required: false
score: 0
outputs:
- builtin
options:
scale: 1.5
position: [0, 600]
left-wing:
outputs:
- work-left-wing
- home-left-wing
position: ['{auxiliary}', 0]
primary:
outputs:
- work-center
- home-center
position: ['{auxiliary} + {left-wing}', 0]
right-wing:
required: false
outputs:
- work-right-wing
- home-right-wing
position: ['{auxiliary} + {left-wing} + {right-wing}', 0]
laptop:
builtin:
outputs:
- builtin
options:
scale: '{scaling-factor}'
position: [0, 0]

View File

@@ -0,0 +1,69 @@
outputs:
work-left-wing:
criteria:
make: Dell Inc.
model: DELL U2722D
serial: DV3MGH3
eww-windows:
desktop-leftbar:
sidebar:
side: left
options:
resolution: 2560x1440
refresh: 59951
work-right-wing:
criteria:
make: Dell Inc.
model: DELL U2722D
serial: 5X3MGH3
eww-windows:
desktop-rightbar:
sidebar:
side: right
options:
resolution: 2560x1440
refresh: 59951
work-center:
criteria:
make: Dell Inc.
model: DELL U3818DW
serial: 97F8P9350W0L
eww-windows:
- desktop-mainbar
options:
resolution: 3840x1600
refresh: 59994
home-left-wing:
criteria:
make: ASUSTek COMPUTER INC
model: VG245
serial: L7LMQS132447
eww-windows:
desktop-leftbar:
sidebar:
side: left
options:
mode: 1920x1080
refresh: '75'
home-right-wing:
criteria:
make: ASUSTek COMPUTER INC
model: VG245
serial: L6LMQS065439
eww-windows:
desktop-rightbar:
sidebar:
side: right
options:
resolution: 1920x1080
refresh: '75'
home-center:
criteria:
make: ASUSTek COMPUTER INC
model: VG32AQA1A
serial: S5LMQS033656
eww-windows:
- desktop-mainbar
options:
resolution: 2560x1440
refresh: '165'

View File

@@ -0,0 +1,177 @@
workspaces:
### ###
# Console Workspaces #
### ###
console-1:
name: console
exec: console
console-2:
name: console
exec: console
console-3:
name: console
exec: console
### ###
# Code Workspaces #
### ###
code-1:
name: code
exec: emacsclient
args:
- nc
code-2:
name: code
exec: emacsclient
args:
- nc
code-3:
name: code
exec: emacsclient
args:
- nc
### ###
# Internet Workspaces #
### ###
inet-1:
name: internet
exec: firefox
args:
- '--new-window'
inet-2:
name: internet
exec: firefox
args:
- '--new-window'
inet-3:
name: internet
exec: firefox
args:
- '--new-window'
### ###
# Project Workspaces #
### ###
proj-1:
name: project
exec: firefox
args:
- '--new-window'
proj-2:
name: project
exec: firefox
args:
- '--new-window'
proj-3:
name: project
exec: firefox
args:
- '--new-window'
### ###
# Documentation Workspaces #
### ###
doc-1:
name: project
exec: firefox
args:
- '--new-window'
doc-2:
name: project
exec: firefox
args:
- '--new-window'
doc-3:
name: project
exec: firefox
args:
- '--new-window'
### ###
# Comms Workspaces #
### ###
comms-discord:
name: discord comms
exec: discord
comms-slack:
name: slack comms
exec: slack
args:
- '--enable-features=UseOzonePlatform'
- '--ozone-platform=wayland'
- '--enable-gpu-rasterization'
comms-signal:
name: encrypted comms
exec: signal-desktop
comms-matrix:
name: encrypted comms
exec: element-desktop
comms-zoom:
name: video comms
exec: zoom
comms-mail:
name: messages
exec: thunderbird
environ:
MOZ_ENABLE_WAYLAND: 1
### ###
# Media Workspaces #
### ###
media-music:
name: music
exec: feishin
media-video:
name: video
exec: jellyfinmediaplayer
media-streaming:
name: livestream
exec: firefox
args:
- '--new-window'
media-webvideo-1:
name: web video
exec: firefox
args:
- '--new-window'
media-webvideo-2:
name: web video
exec: firefox
args:
- '--new-window'
### ###
# Gaming Workspaces #
### ###
gaming-steam:
name: steam
exec: steam
gaming-minecraft:
name: minecraft
exec: prismlauncher
gaming-notes-1:
name: notes
exec: obsidian
gaming-notes-2:
name: notes
exec: obsidian
gaming-dnd:
name: 'dungeons & dragons'
exec: firefox
args:
- '--new-window'
- 'https://roll20.net'
gaming-pf:
name: pathfinder
exec: firefox
args:
- '--new-window'
- 'https://mossfinder.ezri.dev'
gaming-other:
name: game
### ###
# Miscellaneous Workspaces #
### ###
misc-audio:
name: audio mixer
exec: pavucontrol
misc-password:
name: password management
exec: bitwarden-desktop
misc-image-editor:
name: image editor
exec: gimp

244
.config/zsh/00-systemd.zsh Normal file
View File

@@ -0,0 +1,244 @@
# Move shell into its own systemd scope if possible
if ! hash busctl &>/dev/null; then
return
fi
if [[ $_scope_loaded == 1 ]]; then
# shell reload, ignore
return
fi
_scope_loaded=1
# Load new scope unit
busctl -q --user call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager StartTransientUnit 'ssa(sv)a(sa(sv))' \
"zsh-$$.scope" \
replace \
2 \
PIDs au 1 $$ \
Slice s ${SHELL_SLICE:-app-shell.slice} \
0 \
&>/dev/null # this call fails on a shell re-exec, but we don't really care since the scope will already exist in that case, so forward to dave null
# Environment variable possibly passed by parent, but we don't want it to be inherited, so unset it
unset SHELL_SLICE
function env_merge {
# usage: env_merge <ENVIRON> <VALUE>
# If ENVIRON is not already set, set it to VALUE. Then export ENVIRON.
: "${(P)1=$2}"
export $1
}
# import environment from systemd user daemon.
# this is necessary when running in a context not descended from the user manager (e.g. tty login, ssh login, etc.)
# use the "env_merge" function defined above to not override existing environment (e.g. SSH-forwarded SSH_AUTH_SOCK or DISPLAY)
eval $(systemctl --user show-environment | awk 'match($0, /^([a-zA-Z_]+)=(.*)$/, ary) { print "env_merge " ary[1] " " ary[2] }')
# discard the env_merge function
unset -f env_merge
# Set and unset variables
export INVOCATION_ID=$(systemctl --user show "zsh-$$.scope" | awk -F= '/InvocationID=/{ print $2 }')
export SYSTEMD_UNIT="zsh-$$.scope"
unset SYSTEMD_EXEC_PID
unset JOURNAL_STREAM
unset MEMORY_PRESSURE_WATCH
unset MEMORY_PRESSURE_WRITE
function lift-constraints() {
# Don't lift the PID constraint since there's really no reason a shell should have more than 100 active PIDs. Reap what you sow!
env systemctl --user set-property zsh-$$.scope CPUQuota=
env systemctl --user set-property zsh-$$.scope MemoryHigh=
env systemctl --user set-property zsh-$$.scope MemoryMax=
}
function run() {
local args slice type unit passed_envs
passed_envs=(SUDO_PROMPT EDITOR SUDO_EDITOR VISUAL)
args=(--user --same-dir -q --collect)
slice="app-shell.slice"
type="scope"
while true; do
case $1 in
--help)
cat <<EOF
Usage: $0 [OPTIONS] <COMMAND> [ARGS...]
Invoke a command outside of the shell's execution context, detaching it from any resource
constraints on the shell itself.
It will be run by the user service manager using systemd-run. By default, the command
is invoked in the app-shells slice, so is still subject to any restrictions placed
on the set of all user shells.
This is intended to simplify the act of intentionally launching resource-intensive programs
(such as compilers) while still limiting most programs in terms of how much they are allowed
to consume.
Some environment variables are inherited by default, which can be disabled with --clean-env:
$passed_envs
OPTIONS:
--help - display this help message and exit
--app - launch in the main app slice rather than the app-shell slice. Equivalent to --slice app.slice
--background - launch in the background slice. Equivalent to --slice background.slice
--slice <SLICE> - launch in the specified user slice.
--service - create a service unit, rather than a scope, producing a truly clean environment. This separates
the program from the shell's process tree, preventing the shell's job management from managing it.
--env <VAR>[=VALUE] - inherit the given environment variable from the shell, or set it if a value is given.
--clean-env - disable the default set of inherited environment variables
--unit <UNIT> - set the unit name. Defaults to "invoke-<shell PID>@<random>" for services,
"invoke-<shell PID>-<random>" for scopes.
EOF
return 0
;;
--app)
slice="app.slice"
;;
--background)
slice="background.slice"
;;
--slice)
slice=$2
shift
;;
--service)
type="service"
;;
--env)
args+="-E"
args+=$2
shift
;;
--clean-env)
passed_envs=()
;;
--unit)
unit=$2
shift
;;
*)
break
;;
esac
shift
done
for env in $passed_envs; do
args+="--setenv=$env"
done
case $type in
service)
if [[ $unit == '' ]]; then
unit="invoke-$$@$RANDOM.service"
fi
args+=(--service-type=exec --pty --pipe --wait)
;;
scope)
if [[ $unit == '' ]]; then
unit="invoke-$$-$RANDOM.scope"
fi
args+="--scope"
;;
esac
if [[ $unit != *.$type ]]; then
echo "Unit suffix does not match unit type! Should end in .$type!"
return 1
fi
systemd-run $args --slice=${slice} --unit=${unit} -- "$@"
}
function with-group {
local args slice unit passed_envs user passed_envs_when_user_is_us
passed_envs=(EDITOR VISUAL)
passed_envs_when_user_is_us=(DBUS_SESSION_BUS_ADDRESS DISPLAY WAYLAND_DISPLAY XDG_RUNTIME_DIR)
args=(--same-dir -q --collect)
slice="system.slice"
user=$USER
groups=${1//,/ }
shift
while true; do
case $1 in
--help)
cat <<EOF
Usage: with-group [OPTIONS] <GROUPS> [<COMMAND> [ARGS...]]
Invoke a command as the current user with additional supplementary groups. Provides a convenient
way to temporarily gain permissions afforded to groups the user does not have, while still retaining
regular user permissions (i.e. without switching to another user or fully elevating to root).
Multiple groups may be specified in a comma-separated list (similar to the -G flag of useradd and usermod).
It will be run by the system service manager using systemd-run as a transient service. As it has to be
invoked by the service manager to gain the additional permissions, there is no way to run it as a scope.
If no command is specified, the current shell will be launched instead.
Some environment variables are inherited by default, which can be disabled with --clean-env:
$passed_envs
OPTIONS:
--help - display this help message and exit.
--slice <SLICE> - launch in the specified system slice.
--env <VAR>[=VALUE] - inherit the given environment variable from the shell, or set it if a value is given.
--clean-env - disable the default set of inherited environment variables.
--unit <UNIT> - set the unit name.
--user <USER> - run as the given user rather than the current user.
EOF
return 0
;;
--slice)
slice=$2
shift
;;
--env)
args+="-E"
args+=$2
shift
;;
--clean-env)
passed_envs=()
;;
--unit)
unit=$2
shift
;;
--user)
user=$2
shift
;;
*)
break
;;
esac
shift
done
for env in $passed_envs; do
args+="--setenv=$env"
done
if [[ $user == $USER ]]; then
for env in $passed_envs_when_user_is_us; do
args+="--setenv=$env"
done
fi
args+=(--service-type=exec --pty --pipe --wait -p "User=$user" -p "SupplementaryGroups=$groups" -p "PAMName=login")
if [[ $unit != "" ]]; then
args+=("--unit=$unit")
fi
if [[ $1 == '' ]]; then
systemd-run $args --slice=${slice} -- $SHELL
else
systemd-run $args --slice=${slice} -- "$@"
fi
}
for cmd in $always_detach; do
eval $(echo alias $cmd=\"run $cmd\")
done
for cmd in $always_detach_as_service; do
eval $(echo alias $cmd=\"run --service $cmd\")
done
alias ssh='run --slice app-ssh.slice --unit ssh-client-$RANDOM.scope ssh'

View File

@@ -0,0 +1,90 @@
#
# System introspection script which defines variables for use in later dropins.
#
# Exposes system data as the SYSTEM_INFO associative array. Primarily pulls information via
# systemd-hostnamed if available; falls back to reading /etc/machine-info and /etc/hostname,
# or retrieving the system hostname with `uname`. Keys will always be the names of the D-Bus
# properties, not their corresponding fields in /etc/machine-info.
#
# Exposes OS release data as the OS_RELEASE associative array. Data is read directly from
# /etc/os-release if available, otherwise it is not provided.
#
# Exposes all /etc/machine-info fields, including those not defined by the spec, under MACHINE_INFO.
# Keys are the field names with no transforms applied.
_introspection--generate-file-parser() {
awk 'match($0, /^([A-Za-z_][A-Za-z0-9_]*)=(.*)/, ary) { print "'"$1"'[" ary[1] "]='\''" ary[2] "'\''"}'
}
_introspection--parse-file() {
local varname=$1
while read line; do
key=${${(LC)line%%=*}//_/}
val=${line#*=}
eval "${varname}[${key}]=\"${${val%\"}#\"}\""
done
}
declare -A SYSTEM_INFO
declare -A OS_RELEASE
declare -A MACHINE_INFO
declare -A INTRO_STATE
refresh-system-info() {
INTRO_STATE[SYSTEM_INFO]=none
INTRO_STATE[OS_RELEASE]=none
INTRO_STATE[MACHINE_INFO]=none
INTRO_STATE[hostname]=none
if [ -f /etc/machine-info ]; then
# Populate MACHINE_INFO array
_introspection--parse-file MACHINE_INFO </etc/machine-info
INTRO_STATE[MACHINE_INFO]=present
fi
# Parse OS release info, trying first from the new os-release file, then from the LSB standard. Variables should have more or less the same names between the two files.
if [ -f /etc/os-release ]; then
_introspection--parse-file OS_RELEASE </etc/os-release
INTRO_STATE[OS_RELEASE]=present
elif [ -f /etc/lsb_release ]; then
_introspection--parse-file OS_RELEASE </etc/lsb_release
INTRO_STATE[OS_RELEASE]=legacy_lsb_release
fi
# Parse detailed system info from hostnamectl, or synthesize what we can from /etc/machine-info and static and transient hostnames.
if type hostnamectl &>/dev/null && (( $(hostnamectl --version | head -n 1 | cut -d' ' -f2) >= 252 )); then
# We can get all relevant data from hostnamectl, so lets do that.
eval $(hostnamectl --json=short | jq -r 'to_entries | .[] | select((.value | type) != "array" and (.value | type) != "object" and (.value | type) != "null") | "SYSTEM_INFO[\(.key)]=\(.value | @sh)"')
INTRO_STATE[SYSTEM_INFO]=full
INTRO_STATE[hostname]=full
else
# Parse machine info if available
if [[ ${INTRO_STATE[MACHINE_INFO]} == present ]]; then
# Populate SYSTEM_INFO array as best as possible (this will be missing a _lot_ of fields that hostnamed calculates)
local shared_keys=(PrettyHostname IconName Chassis Deployment Location HardwareVendor HardwareModel HardwareVersion)
for key val in "${(@kv)MACHINE_INFO}"; do
case $key in
HardwareSku)
SYSTEM_INFO[HardwareSKU]=${MACHINE_INFO[HardwareSku]}
;;
*)
if [[ ${${key:*shared_keys}:+x} == 'x' ]]; then
SYSTEM_INFO[$key]=${MACHINE_INFO[$key]}
fi
;;
esac
done
INTRO_STATE[SYSTEM_INFO]=limited
fi
# Read current (transient) hostname
SYSTEM_INFO[Hostname]=$(uname -n)
INTRO_STATE[hostname]=transient
# Read static hostname if available
if [ -f /etc/hostname ]; then
SYSTEM_INFO[StaticHostname]=$(</etc/hostname)
INTRO_STATE[hostname]=full
fi
fi
}
refresh-system-info

65
.config/zsh/02-colors.zsh Normal file
View File

@@ -0,0 +1,65 @@
#
# Color control code variables
#
declare -A fg
declare -A bg
declare -A ctrl
fg[black]=$'\033[30m'
fg[blue]=$'\033[34m'
fg[red]=$'\033[31m'
fg[green]=$'\033[32m'
fg[magenta]=$'\033[35m'
fg[cyan]=$'\033[36m'
fg[yellow]=$'\033[33m'
fg[white]=$'\033[37m'
fg[bright_black]=$'\033[90m'
fg[bright_blue]=$'\033[94m'
fg[bright_red]=$'\033[91m'
fg[bright_green]=$'\033[92m'
fg[bright_magenta]=$'\033[95m'
fg[bright_cyan]=$'\033[96m'
fg[bright_yellow]=$'\033[93m'
fg[bright_white]=$'\033[97m'
fg[reset]=$'\033[39m'
bg[black]=$'\033[40m'
bg[blue]=$'\033[44m'
bg[red]=$'\033[41m'
bg[green]=$'\033[42m'
bg[magenta]=$'\033[45m'
bg[cyan]=$'\033[46m'
bg[yellow]=$'\033[43m'
bg[white]=$'\033[47m'
bg[bright_black]=$'\033[100m'
bg[bright_blue]=$'\033[104m'
bg[bright_red]=$'\033[101m'
bg[bright_green]=$'\033[102m'
bg[bright_magenta]=$'\033[105m'
bg[bright_cyan]=$'\033[106m'
bg[bright_yellow]=$'\033[103m'
bg[bright_white]=$'\033[107m'
bg[reset]=$'\033[49m'
ctrl[reset]=$'\033[0m'
ctrl[bold]=$'\033[1m'
ctrl[faint]=$'\033[2m'
ctrl[italic]=$'\033[3m'
ctrl[underline]=$'\033[4m'
ctrl[slowblink]=$'\033[5m'
ctrl[fastblink]=$'\033[6m'
ctrl[invert]=$'\033[7m'
ctrl[hide]=$'\033[8m'
ctrl[strike]=$'\033[9m'
ctrl[nobold]=$'\033[22m'
ctrl[noitalic]=$'\033[23m'
ctrl[nounderline]=$'\033[24m'
ctrl[noblink]=$'\033[25m'
ctrl[noinvert]=$'\033[27m'
ctrl[nohide]=$'\033[28m'
ctrl[nostrike]=$'\033[29m'

View File

@@ -0,0 +1,178 @@
#
# Initial setup for prompt generation.
#
# Defines the arrays for prompt elements ($prompt_list), rprompt elements ($rprompt_list), and history prompt elements ($history_prompt, the prompt is rewritten using this when a command is submitted).
#
# Prompt elements are strings. Some special prefixes are understood:
# - Strings prefixed with 'func:' will run the function given, using the contents of ${prompt_element_contents} as the element. Argument splitting is performed, but variable interpolation is not (you should be able to do that yourself).
# - Strings prefixed with 'var:' will expand the given variable specifier when the prompt is generated, within curly braces. This allows specifying that the given variable be evaluated at generation time.
# - Strings prefixed with 'literal:' will use the remaining string literally. This allows a static component which starts with "func:" or "var:" if you want to for whatever reason.
#
# We set the prompt_subst option, so variables can be included in prompt elements (e.g. in single quotes), and changes to them will be reflected the next time the prompt renders.
#
# We also set precmd_functions to an empty array in this file, so it should be extended.
declare -a prompt_list
declare -a rprompt_list
declare -a history_prompt
declare -A prompt_elements
setopt prompt_subst
precmd_functions=()
prompt_elements[privilege]='%B%0(#.%F{red} as root.%(!.%F{red} with privileges.))%b'
prompt-element-host() {
# Adds host info to the prompt.
if [[ ${SYSTEM_INFO[Chassis]} == "container" ]] || [[ ${SYSTEM_INFO[Chassis]} == "vm" ]]; then
prompt_element_contents='%F{blue}%B${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}}%b%f'
elif [[ -v SSH_CLIENT ]]; then
prompt_element_contents='%F{blue}%BRemote Console%b%f @ %F{magenta}%B${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}}%b%f'
elif [[ -o login ]]; then
prompt_element_contents='%F{green}%BLogin Console%b%f @ %F{magenta}%B${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}}%b%f'
else
prompt_element_contents='%F{magenta}%B${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}}%f%b'
fi
}
prompt_elements[host]="func:prompt-element-host"
prompt-element-console() {
if [[ ${TTY} =~ "tty" ]]; then
prompt_element_contents=" %F{red}%y%f"
else
prompt_element_contents=""
fi
}
prompt_elements[console]="func:prompt-element-console"
prompt-element-deployment() {
if [[ ${SYSTEM_INFO[Deployment]+yes} == "yes" ]]; then
case ${(L)SYSTEM_INFO[Deployment]} in
*prod|production)
prompt_element_contents='%F{red}%BPROD%b%f '
;;
*devl|dev|devel|development)
prompt_element_contents='%F{green}%BDEV%b%f '
;;
*pprd|preprod|preproduction)
prompt_element_contents='%F{cyan}%BPRE-PROD%b%f '
;;
*)
prompt_element_contents='%F{blue}%B${(U)SYSTEM_INFO[Deployment]}%b%f '
;;
esac
fi
}
prompt_elements[deployment]="func:prompt-element-deployment"
prompt_elements[retcode]="%(?..%F{red}  %?%f)"
prompt_elements[prompt]="%(?..%F{red}%f) "
prompt_elements[pwd]="%B%F{blue}%3~%f%b"
prompt_elements[pyenv]='$pyenv_info_msg_0_'
prompt_elements[vcs]='$vcs_info_msg_0_'
prompt_elements[clock]='%F{yellow}%T%f'
prompt-element-groups() {
# Produces newlines at start and end.
local ambient_groups defined_groups ambient_map defined_map
declare -A ambient_map
declare -A defined_map
ambient_groups=($(id -Gn))
defined_groups=($(id -Gn ${USER}))
prompt_element_contents=$'\n'
# yay 3 loops :/
for group in "${ambient_groups[@]}"; do
ambient_map[$group]=1
done
for group in "${defined_groups[@]}"; do
defined_map[$group]=1
if [ ${ambient_map[$group]:-0} -eq 0 ]; then
prompt_element_contents+=" %F{red}$group%f"$'\n'
fi
done
for group in "${ambient_groups[@]}"; do
if [ ${defined_map[${group}]:-0} -eq 0 ]; then
prompt_element_contents+=" %F{green}$group%f"$'\n'
fi
done
}
prompt_elements[groups]='func:prompt-element-groups'
_build-prompt() {
PROMPT=""
for el in "${prompt_list[@]}"; do
case $el in
func:*)
# Clear the variable
prompt_element_contents=''
# Run the function
${=el/func:/}
# Add the variable to the prompt
PROMPT+="${prompt_element_contents}"
;;
var:*)
# eval the variable.
eval "PROMPT+=\"\${${el/var:/}}\""
;;
literal:*)
# strip the leading 'literal:' and add the string to the prompt
PROMPT+="${el/literal:/}"
;;
*)
# not a known prefix, so just add it as a string
PROMPT+="${el}"
;;
esac
done
RPROMPT=""
for el in "${rprompt_list[@]}"; do
case $el in
func:*)
# Clear the variable
prompt_element_contents=''
# Run the function
${=el/func:/}
# Add the variable to the prompt
RPROMPT+="${prompt_element_contents}"
;;
var:*)
# eval the variable.
eval "RPROMPT+=\"\${${el/var:/}}\""
;;
literal:*)
# strip the leading 'literal:' and add the string to the prompt
RPROMPT+="${el/literal:/}"
;;
*)
# not a known prefix, so just add it as a string
RPROMPT+="${el}"
;;
esac
done
HISTORY_UNPROMPT=""
for el in "${history_prompt[@]}"; do
case $el in
func:*)
# Clear the variable
prompt_element_contents=''
# Run the function
${=el/func:/}
# Add the variable to the prompt
HISTORY_UNPROMPT+="${prompt_element_contents}"
;;
var:*)
# eval the variable.
eval "HISTORY_UNPROMPT+=\"\${${el/var:/}}\""
;;
literal:*)
# strip the leading 'literal:' and add the string to the prompt
HISTORY_UNPROMPT+="${el/literal:/}"
;;
*)
# not a known prefix, so just add it as a string
HISTORY_UNPROMPT+="${el}"
;;
esac
done
}

View File

@@ -1,5 +1,8 @@
export ASDF_DATA_DIR=$HOME/.local/share/asdf-vm
export RUSTUP_HOME=$HOME/.local/lib/rustup
export CARGO_HOME=$HOME/.local/lib/cargo
PATH_pyenv=$HOME/.pyenv/bin:$HOME/.pyenv/shims:/opt/pyenv/pyenv-virtualenv/shims
PATH_asdf=$ASDF_DATA_DIR/shims:/opt/asdf-vm/bin
PATH_node=$HOME/.opt/npm/bin
@@ -8,10 +11,11 @@ PATH_perl=/usr/bin/vendor_perl:/usr/bin/core_perl
PATH_system=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PATH_user=$HOME/.local/bin
PATH_wincmake=/opt/msvcmake/bin:/opt/msvc/bin/x64
PATH_cargo=$CARGO_HOME/bin
cdpath=(~ ~/src)
export PATH=.:$PATH_asdf:$PATH_node:$PATH_pyenv:$PATH_java:$PATH_user:$PATH_system
export PATH=.:$PATH_node:$PATH_cargo:$PATH_pyenv:$PATH_java:$PATH_user:$PATH_system
# Function to run a command with only the "safe" system PATH element
function safeexec {

View File

@@ -12,13 +12,20 @@ function getkernelpkg() {
fi
}
# function docker() {
# # Wrapper for docker command that executes it inside the systemd-nspawn docker container
# # This container has our home directory bound, so we should cd to the current directory before running this command
# ssh -t dockerbox "env ORIGINALWD=\"$(pwd)\" ~/.local/bin/dockerbox.sh $*"
#}
function random-xkcd() {
blocklist=(
"404"
"631" # "Anatomy Text", don't want that coming up in public thanks
)
link="https://xkcd.com/404/"
while (( $blocklist[(Ie)$(basename $link)] )); do
while (( ${blocklist[(Ie)$(basename $link)]} )); do
link=$(curl https://c.xkcd.com/random/comic/ -v |& grep location | cut -d' ' -f3 | sed 's/\r//')
done
html=$(curl -L ${link/http:/https:} 2>/dev/null)
@@ -30,12 +37,12 @@ function random-xkcd() {
curl $(echo $html | (echo "https:"$(xmllint --html --xpath 'string(//html/body/div[@id="middleContainer"]/div[@id="comic"]/img/@src)' - 2>/dev/null))) 2>/dev/null | feh -
}
if [[ $(hostnamectl chassis) == "container" ]]; then
DEFAULT_WINDOW_NAME="Container Connection: [ $os_icon $hostname Console ]"
if [[ ${SYSTEM_INFO[Chassis]} == "container" ]]; then
DEFAULT_WINDOW_NAME="[ Container Connection ] ${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}} Console"
elif [[ -z "${SSH_CONNECTION+x}" ]]; then
DEFAULT_WINDOW_NAME="$os_icon $hostname Console"
DEFAULT_WINDOW_NAME="${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}} Console"
else
DEFAULT_WINDOW_NAME="Remote Connection: [ $os_icon $hostname Console ]"
DEFAULT_WINDOW_NAME="[ Remote Connection ] ${SYSTEM_INFO[PrettyHostname]:-${SYSTEM_INFO[Hostname]}} Console"
fi
function _rename_window() {
@@ -113,16 +120,12 @@ function bwunlock() {
# Attempt to read Bitwarden password from keyring
if ! (( skipkeyring )); then
bwpasswd=$(secret-tool lookup service $secretstore 2>/dev/null)
bwpasswd=$(kwallet-query -r bitwarden kdewallet 2>/dev/null)
fi
if [[ -z "${bwpasswd}" ]]; then
# If password is not found in keyring, prompt for it
read -s -p "Enter Bitwarden password: " bwpasswd
echo
# Store Bitwarden password in keyring
if ! (( skipkeyring )); then
secret-tool store --label="Bitwarden" service $secretstore
fi
fi
# Unlock Bitwarden vault and export session key
export BW_SESSION=$(eval $bwcmd unlock --raw "'$bwpasswd'" 2>/dev/tty)
@@ -134,3 +137,76 @@ function bwunlock() {
fi
}
if type machinectl &>/dev/null; then
function su {
local user
user=$1
if [[ $user == '' ]]; then
user=root
fi
machinectl -q shell "${user}@"
}
fi
alias relog='exec machinectl -q shell "${USER}@"'
function urlencode {
# credit to https://stackoverflow.com/a/34407620 for the jq solution
local multiline=0
if [[ "$1" == "-m" ]] || [[ "$1" == "--multiline" ]]; then
# Multiline mode, read directly from stdin
multiline=1
shift
fi
if [[ -z "$1" ]]; then
# Read from stdin
if (( multiline )); then
jq -sRr '@uri'
else
# Treat each line as a separate argument, and encode them separately
for line in $(cat); do
echo -n "$line" | jq -sRr '@uri'
done
fi
else
# Read from arguments (use echo because arguments may contain spaces and --arg wasn't working)
echo -n $@ | jq -sRr '@uri'
fi
}
function ssh-copy-shell {
# Copy shell config to the given SSH host
local port=22 host
while [ $# -gt 0 ]; do
case $1 in
-p|--port)
port=$2
shift
;;
--port=*)
port=${1/--port=/}
;;
*:*)
host=${1/:*/}
port=${1/*:/}
;;
*)
host=$1
;;
esac
shift
done
if [[ ${host+x} != x ]]; then
echo "Target host is required!"
return 1
fi
ssh -p ${port} ${host} mkdir -p .config .local/lib
# Deploy files
sftp -P ${port} ${host} <<SFTP
lcd ~
put -R .config/zsh .config
put -R .local/lib/zsh .local/lib
put .zshrc
SFTP
}

View File

@@ -2,12 +2,11 @@
alias emacs="TERM=screen-256color emacsclient -t"
alias systemctl="sudo systemctl"
alias userctl="\systemctl --user"
alias userctl="systemctl --user"
alias ujournalctl="\journalctl --user"
alias reconf="source $HOME/.zshrc"
alias reconf="exec zsh"
alias cp="cp -i --reflink=auto"
alias mv="mv -i"
@@ -22,15 +21,19 @@ alias l='ls -lh'
alias nuke='echo "Are you sure?"; read -q && rm -rf'
alias sudo='sudo '
alias pkgfile='pkgfile -D $HOME/.cache/pkgfile'
alias sign='gpg --sign-with ezri@ezri.dev --detach-sign'
alias verify='gpg --verify'
alias dnslookup="resolvectl query"
alias ip="ip -color=auto"
alias bw-personal='BITWARDENCLI_APPDATA_DIR=~/.config/bw-cli-personal bw'
function didifuckingstutter {
echo $fg[blue]Apologies, right away.$fg[default]
echo $fg[blue]Apologies, milady. Right away.$fg[default]
# Run the last command as root
last_cmd=$(fc -ln -1)
# if last command was emacs, use TRAMP rather than running as root
@@ -39,7 +42,7 @@ function didifuckingstutter {
echo "> $fg[white]$cmd$fg[default]"
$=cmd
else
cmd="sudo $(fc -ln -1)"
cmd="sudo zsh -c '$(fc -ln -1)'"
echo "> $fg[white]$cmd$fg[default]"
sudo -p "$fg[red]I'll need your authorization:$fg[default] " $last_cmd
fi
@@ -119,3 +122,5 @@ function mkcd {
[[ -e $1 ]] || mkdir -p $1
cd $1
}
alias ppctl='/usr/bin/python3 /usr/bin/powerprofilesctl'

View File

@@ -0,0 +1,24 @@
# CD to directory when you type it, without having to type cd
setopt auto_cd
# Use directory stack
setopt auto_pushd
# resolve links when CDing
setopt chase_links
# Completion
setopt auto_list
# History
setopt hist_ignore_dups
setopt hist_ignore_space
setopt hist_expire_dups_first
setopt extended_history # adds timestamps and command durations to history file
setopt hist_reduce_blanks
unsetopt hist_save_by_copy # we have compression enabled on history file, we don't want to lose that attribute, so truncate and rewrite rather than making a new file and moving.
setopt hist_save_no_dups # no reason to save old dups
setopt hist_verify # hitting enter on history search will pull it into the current line editor, not execute directly
setopt inc_append_history_time
# I/O
unsetopt clobber # disallow clobbering of files with > (require >! or >| instead)

View File

@@ -0,0 +1,37 @@
#
# Shell-side support for Emacs libvterm terminal
#
# This file returns immediately if we are not running in an Emacs vterm.
if [[ ${INSIDE_EMACS} != "vterm" ]]; then
return
fi
vterm_printf() {
# Function definition pulled from emacs-libvterm README
if [ -n "$TMUX" ] && { [ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ]; }; then
# Tell tmux to pass the escape sequences through
printf "\ePtmux;\e\e]%s\007\e\\" "$1"
elif [ "${TERM%%-*}" = "screen" ]; then
# GNU screen (screen, screen-256color, screen-256color-bce)
printf "\eP\e]%s\007\e\\" "$1"
else
printf "\e]%s\e\\" "$1"
fi
}
vterm_prompt_end() {
vterm_printf "51;A${USER}@${SYSTEM_INFO[Hostname]}:${PWD}"
}
prompt_elements[prompt]="${prompt_elements[prompt]}"'%{$(vterm_prompt_end)%}'
emacsctl() {
local vterm_elisp
vterm_elisp=""
while [ $# -gt 0 ]; do
vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
shift
done
vterm_printf "51;E${vterm_elisp}"
}

105
.config/zsh/90-prompt.zsh Normal file
View File

@@ -0,0 +1,105 @@
autoload -Uz vcs_info
# Show active python virtualenv in prompt
precmd_pyenv_info() {
# use nerd fonts for icons
_pyenv_icon=""
# Only update if we've changed directory, since pyenv can be slow
if [[ "$PWD" == "$_pyenv_old_pwd" ]]; then
return
else
_pyenv_old_pwd="$PWD"
fi
_pyenv_version=$(pyenv version-name 2>/dev/null)
if [[ $? -eq 0 ]] && [[ ${_pyenv_version} != "system" ]] && [[ ${_pyenv_version} != "personal" ]]; then
pyenv_info_msg_0_="%F{yellow} ${_pyenv_icon} ${_pyenv_version}%f"
elif [[ ${_pyenv_version} == "system" ]]; then
pyenv_info_msg_0_="%F{red} ${_pyenv_icon} ${_pyenv_version}%f"
else
pyenv_info_msg_0_=""
fi
}
precmd_kernel_info() {
# check to see if a kernel update has been installed
# since we need to reboot to use it
# if message has been set, don't check again, since a reboot
# is required to clear the message
if [[ ${kernel_info_msg_0_+x} == "x" ]]; then
return
fi
if [[ ${OS_RELEASE[Id]} == "arch" ]] || [[ ${OS_RELEASE[IdLike]} == "arch" ]]; then
# Arch Linux removes the old kernel when updating, so we can
# just check to see if /usr/lib/modules/$(uname -r)/vmlinuz exists
if ! [[ -f /usr/lib/modules/$(uname -r)/vmlinuz ]]; then
# kernel update available
kernel_info_msg_0_="%F{red}%B󱄋%b%f"
fi
fi
}
precmd_vcs_info() {
vcs_info
}
if [[ ${TERM} == "alacritty" ]]; then
precmd_functions+=(precmd_vcs_info precmd_pyenv_info _reset_window_name)
elif ! [[ ${TERM} == "dumb" ]]; then
precmd_functions+=(precmd_vcs_info precmd_pyenv_info)
fi
if [[ ${SYSTEM_INFO[Chassis]} != "container" ]] && [[ ${OS_RELEASE[IdLike]:-${OS_RELEASE[Id]}} == "arch" ]]; then
# Only check if a reboot is necessary if we're not in a container, and we're arch-based (the check we're using only works for arch-based distros)
precmd_functions+=(precmd_kernel_info)
fi
prompt_list=(
$'\n'
"%F{cyan}[%f " ${prompt_elements[deployment]} ${prompt_elements[host]} ${prompt_elements[privilege]} " %F{cyan}]%f" ${prompt_elements[console]} ${prompt_elements[pyenv]} ' ' ${prompt_elements[pwd]} ${prompt_elements[groups]}
'$kernel_info_msg_0_ ' ${prompt_elements[prompt]}
)
rprompt_list=(
${prompt_elements[vcs]} ${prompt_elements[retcode]}
)
history_prompt=(
"%F{cyan}[%f " ${prompt_elements[host]} ${prompt_elements[privilege]} " %F{cyan}]%f" ${prompt_elements[console]} ' ' ${prompt_elements[clock]} ' ' ${prompt_elements[pwd]} ' ' ${prompt_elements[prompt]}
)
set-prompt() {
if [[ ${TERM} == "dumb" ]]; then
# Dumb terminal needs a dumb prompt, otherwise things like
# emacs TRAMP break
PROMPT='[%~] $ '
PROMPT2='> '
else
_build-prompt
PROMPT2=" %F{yellow}%_ %f "
fi
}
set-prompt
rewrite-prompt-and-accept() {
local oldprompt
if [[ ${TERM} == "dumb" ]]; then
return
fi
oldprompt="${PROMPT}"
PROMPT="${HISTORY_UNPROMPT}"
# Clear any suggestions; we've committed to this command
zle autosuggest-clear
zle reset-prompt
PROMPT="${oldprompt}"
zle accept-line
}
zle -N rewrite-prompt-and-accept
bindkey '^M' rewrite-prompt-and-accept
zstyle ':vcs_info:git:*' formats '%c %F{magenta} 󰊢 %B%r%%b %F{cyan} %b%f'
zstyle ':vcs_info:git:*' actionformats '%c%F{magenta} 󰊢 %B%r%%b %F{cyan} %b %F{red} %a%f'
zstyle ':vcs_info:git:*' stagedstr "%F{green}%f"
zstyle ':vcs_info:git:*' check-for-staged-changes true
zstyle ':vcs_info:*' enable git

Some files were not shown because too many files have changed in this diff Show More