diff --git a/flake.lock b/flake.lock index 93eb9d6..57cf817 100644 --- a/flake.lock +++ b/flake.lock @@ -33,6 +33,28 @@ "type": "github" } }, + "crane_2": { + "inputs": { + "nixpkgs": [ + "schizofox", + "searx-randomizer", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1701386725, + "narHash": "sha256-w4aBlMYh9Y8co1V80m5LzEKMijUJ7CBTq209WbqVwUU=", + "owner": "ipetkov", + "repo": "crane", + "rev": "8b9bad9b30bd7a9ed08782e64846b7485f9d0a38", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -49,6 +71,22 @@ "type": "github" } }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -70,6 +108,66 @@ "type": "github" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "nixpak", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717285511, + "narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": [ + "schizofox", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717285511, + "narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_4": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1698882062, + "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -90,7 +188,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1685518550, @@ -128,6 +226,58 @@ "type": "github" } }, + "hercules-ci-effects": { + "inputs": { + "flake-parts": [ + "nixpak", + "flake-parts" + ], + "nixpkgs": [ + "nixpak", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1713898448, + "narHash": "sha256-6q6ojsp/Z9P2goqnxyfCSzFOD92T3Uobmj8oVAicUOs=", + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "rev": "c0302ec12d569532a6b6bd218f698bc402e93adc", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "type": "github" + } + }, + "hercules-ci-effects_2": { + "inputs": { + "flake-parts": [ + "schizofox", + "nixpak", + "flake-parts" + ], + "nixpkgs": [ + "schizofox", + "nixpak", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1713898448, + "narHash": "sha256-6q6ojsp/Z9P2goqnxyfCSzFOD92T3Uobmj8oVAicUOs=", + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "rev": "c0302ec12d569532a6b6bd218f698bc402e93adc", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -148,6 +298,27 @@ "type": "github" } }, + "home-manager_2": { + "inputs": { + "nixpkgs": [ + "schizofox", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717097707, + "narHash": "sha256-HC5vJ3oYsjwsCaSbkIPv80e4ebJpNvFKQTBOGlHvjLs=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "0eb314b4f0ba337e88123e0b1e57ef58346aafd9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, "impermanence": { "locked": { "lastModified": 1717932370, @@ -190,6 +361,54 @@ "type": "github" } }, + "nixpak": { + "inputs": { + "flake-parts": "flake-parts_2", + "hercules-ci-effects": "hercules-ci-effects", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717865884, + "narHash": "sha256-imcv6tx4lSfffv6o8T0cIxJmgheItGSSGzxyO4cWnKg=", + "owner": "nixpak", + "repo": "nixpak", + "rev": "f938c3c01026c00a305b5fba8edeb8f7efcfb85a", + "type": "github" + }, + "original": { + "owner": "nixpak", + "repo": "nixpak", + "type": "github" + } + }, + "nixpak_2": { + "inputs": { + "flake-parts": [ + "schizofox", + "flake-parts" + ], + "hercules-ci-effects": "hercules-ci-effects_2", + "nixpkgs": [ + "schizofox", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717036060, + "narHash": "sha256-Bm1pBy1goxIz9PD0PRJ9ZgQzl7MQOQ6n5IB4wLNdXrA=", + "owner": "nixpak", + "repo": "nixpak", + "rev": "3a68b5453f75b1c8e5d31859a654e061a57dc059", + "type": "github" + }, + "original": { + "owner": "nixpak", + "repo": "nixpak", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1717786204, @@ -206,6 +425,24 @@ "type": "github" } }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1698611440, + "narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-stable": { "locked": { "lastModified": 1678872516, @@ -258,7 +495,9 @@ "home-manager": "home-manager", "impermanence": "impermanence", "lanzaboote": "lanzaboote", + "nixpak": "nixpak", "nixpkgs": "nixpkgs", + "schizofox": "schizofox", "spicetify": "spicetify" } }, @@ -287,6 +526,55 @@ "type": "github" } }, + "schizofox": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-parts": "flake-parts_3", + "home-manager": "home-manager_2", + "nixpak": "nixpak_2", + "nixpkgs": [ + "nixpkgs" + ], + "searx-randomizer": "searx-randomizer", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1717289290, + "narHash": "sha256-NS2WFE2mEodtPATQIC16N0ylWnog8B0IownZWnM9JPE=", + "owner": "schizofox", + "repo": "schizofox", + "rev": "04e204d8f605a45e2537343a5e467d5af0518eef", + "type": "github" + }, + "original": { + "owner": "schizofox", + "repo": "schizofox", + "type": "github" + } + }, + "searx-randomizer": { + "inputs": { + "crane": "crane_2", + "flake-parts": "flake-parts_4", + "nixpkgs": [ + "schizofox", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1704412376, + "narHash": "sha256-Ap/AudJxCYBDWYy0lyqP0/FZYJCibL7jKkoj6hp1WS0=", + "owner": "schizofox", + "repo": "searx-randomizer", + "rev": "c36a473732ba6b4f6024ac1c181631cf4d542b17", + "type": "github" + }, + "original": { + "owner": "schizofox", + "repo": "searx-randomizer", + "type": "github" + } + }, "spicetify": { "inputs": { "flake-utils": "flake-utils_2", @@ -324,6 +612,21 @@ } }, "systems_2": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + }, + "systems_3": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", diff --git a/flake.nix b/flake.nix index 2b64964..2b03ea5 100644 --- a/flake.nix +++ b/flake.nix @@ -18,10 +18,25 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + # Spotify + themes spicetify = { url = "github:the-argus/spicetify-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + # Sandbox wrappers for programs + nixpak = { + url = "github:nixpak/nixpak"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # Schizophrenic Firefox + schizofox = { + url = "github:schizofox/schizofox"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; }; outputs = {self, nixpkgs, ...} @ inputs: let diff --git a/home/packages/cli/default.nix b/home/packages/cli/default.nix index 6f102c9..befd2c7 100644 --- a/home/packages/cli/default.nix +++ b/home/packages/cli/default.nix @@ -2,5 +2,6 @@ _: { imports = [ ./shared.nix ./desktop.nix + ./wayland.nix ]; } diff --git a/home/packages/cli/wayland.nix b/home/packages/cli/wayland.nix new file mode 100644 index 0000000..7e3c31e --- /dev/null +++ b/home/packages/cli/wayland.nix @@ -0,0 +1,14 @@ +{ + osConfig, + lib, + pkgs, + ... +}: let + inherit (lib) mkIf; +in { + config = mkIf osConfig.myOptions.home-manager.wms.isWayland { + home.packages = with pkgs; [ + wl-clipboard + ]; + }; +} diff --git a/home/programs/graphical/bars/default.nix b/home/programs/graphical/bars/default.nix new file mode 100644 index 0000000..160e671 --- /dev/null +++ b/home/programs/graphical/bars/default.nix @@ -0,0 +1,5 @@ +_: { + imports = [ + ./eww + ]; +} diff --git a/home/programs/graphical/bars/eww/config/css/_colors.scss b/home/programs/graphical/bars/eww/config/css/_colors.scss new file mode 100644 index 0000000..1534b69 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/_colors.scss @@ -0,0 +1,43 @@ +$rosewater: #f5e0dc; +$flamingo: #f2cdcd; +$pink: #f5c2e7; +$mauve: #cba6f7; +$red: #f38ba8; +$maroon: #eba0ac; +$peach: #fab387; +$yellow: #f9e2af; +$gold: #efcb10; +$green: #a6e3a1; +$lime: #78db32; +$teal: #94e2d5; +$sky: #89dceb; +$sapphire: #74c7ec; +$blue: #89b4fa; +$lavender: #b4befe; +$orange: #ffa500; + +$text: #cdd6f4; +$subtext1: #bac2de; +$subtext0: #a6adc8; +$overlay2: #9399b2; +$overlay1: #7f849c; +$overlay0: #6c7086; + +$surface2: #585b70; +$surface1: #45475a; +$surface0: #313244; + +$base-a: rgba(30, 30, 40, 0.65); +$base: rgba(30, 30, 40, 1); +$base1-a: rgba(49, 50, 68, 0.85); +$base1: rgba(49, 50, 68, 1); +$mantle: #181825; +$crust: #11111b; + +$fg: $text; +$bg-a: $base-a; +$bg: $base; +$bg1: $base1; +$bg1-a: $base1-a; +$border: #28283d; +$shadow: $crust; diff --git a/home/programs/graphical/bars/eww/config/css/modules/_battery.scss b/home/programs/graphical/bars/eww/config/css/modules/_battery.scss new file mode 100644 index 0000000..d4894a4 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_battery.scss @@ -0,0 +1,55 @@ +@keyframes blink { + 0%{ + opacity: 0; + } + 50%{ + opacity: 0.7; + } + 100%{ + opacity: 0; + } + } + +// .unplugged.low { +// color: #0000ff; +// } + +.battery { + + .icon, .icon label { + font-family: "Material Symbols Outlined"; + + .extra, .extra label { + font-family: "Font Awesome 6 Free"; + } + } + + .critical { + .unplugged { + color: #f00; + .extra { animation: blink 1.2s linear infinite; } + } + + .plugged { + .icon { color: $orange } + .extra { color: $green; } + } + } + + .normal { + .unplugged { + .icon { color: $orange } + } + .plugged { + .icon { color: $green; } + } + } + + .full { + .icon { color: $green; } + .extra { color: $lime; } + } + + .extra { margin-right: 5px; } + .icon { margin-right: 5px; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss b/home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss new file mode 100644 index 0000000..82d5a61 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_bitcoin.scss @@ -0,0 +1,3 @@ +.bitcoin { + .icon { margin-right: 6px; color: $gold; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_clock.scss b/home/programs/graphical/bars/eww/config/css/modules/_clock.scss new file mode 100644 index 0000000..98d9823 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_clock.scss @@ -0,0 +1,4 @@ +.clock { + // color: $sapphire; + .icon { margin-right: 6px; color: $sapphire; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_cpu.scss b/home/programs/graphical/bars/eww/config/css/modules/_cpu.scss new file mode 100644 index 0000000..a183c58 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_cpu.scss @@ -0,0 +1,3 @@ +.cpu { + .icon { color: $lime; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss b/home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss new file mode 100644 index 0000000..f62dcc8 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_gammarelay.scss @@ -0,0 +1,7 @@ +.gammarelay { + .icon { + color: $peach; + margin-left: 2px; + margin-right: 2px; + } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_kernel.scss b/home/programs/graphical/bars/eww/config/css/modules/_kernel.scss new file mode 100644 index 0000000..652a60d --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_kernel.scss @@ -0,0 +1,3 @@ +.kernel { + .icon { color: $lavender; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_memory.scss b/home/programs/graphical/bars/eww/config/css/modules/_memory.scss new file mode 100644 index 0000000..37d8432 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_memory.scss @@ -0,0 +1,3 @@ +.memory { + .icon { color: $maroon; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_uptime.scss b/home/programs/graphical/bars/eww/config/css/modules/_uptime.scss new file mode 100644 index 0000000..63b1353 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_uptime.scss @@ -0,0 +1,3 @@ +.uptime { + .icon { color: $green; } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_volume.scss b/home/programs/graphical/bars/eww/config/css/modules/_volume.scss new file mode 100644 index 0000000..7a94630 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_volume.scss @@ -0,0 +1,9 @@ +.volume { + .icon { color: $peach; } + .speaker { + .icon { + margin-left: 8px; + margin-right: 5px; + } + } +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_window_name.scss b/home/programs/graphical/bars/eww/config/css/modules/_window_name.scss new file mode 100644 index 0000000..edf0d3e --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_window_name.scss @@ -0,0 +1,4 @@ +.window_name { + font-family: "JetBrains Mono", "Font Awesome 6 Free", sans-serif; + font-size: 1.4rem; +} diff --git a/home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss b/home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss new file mode 100644 index 0000000..e97b513 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/modules/_workspaces.scss @@ -0,0 +1,28 @@ +.workspaces { + background-color: $bg1-a; + border-radius: 25px; + + .icon, .icon label { + font-family: "Material Symbols Outlined"; + font-size: 1.15rem; + } + + .value { + margin: 0 9px; + } + + .focused { + // text-decoration: underline; + // text-decoration-color: red; + // text-decoration-style: double; + color: $fg; + } + + .active { + color: #bbb; + } + + .inactive { + color: #555; + } +} diff --git a/home/programs/graphical/bars/eww/config/css/windows/_calendar.scss b/home/programs/graphical/bars/eww/config/css/windows/_calendar.scss new file mode 100644 index 0000000..2aadfa3 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/windows/_calendar.scss @@ -0,0 +1,32 @@ +.calendar-win { + @include window; + background-color: $bg; + border: 1px solid $border; + color: $fg; + padding: .2em; +} + +calendar { + padding: 5px; + + :selected { + color: $mauve; + } + + .header { + color: $subtext1; + } + + .highlight { + color: $maroon; + font-weight: bold; + } + + .button { + color: $sapphire; + } + + :indeterminate { + color: $overlay0; + } +} diff --git a/home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss b/home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss new file mode 100644 index 0000000..e6d0e48 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/css/windows/_radio_menu.scss @@ -0,0 +1,46 @@ +.radio-menu-box { + @include window; + background-color: $bg; + border: 1px solid $border; + color: $text; + font-family: "Jost *", "JetBrains Mono", "Font Awesome 6 Free", sans-serif; + + .icon, .icon label { + font-family: "Material Symbols Outlined"; + font-size: 1.15rem; + } + + .text-row { + margin: 1rem 1.5rem 0; + + .title { font-size: 1.2rem; } + } + + .element-row { + margin: .5rem .7rem; + + label { + font-size: 1rem; + margin: 0 .1rem; + } + } + + .element { + @include rounding; + background-color: $surface0; + margin: .3rem; + + button { + @include rounding; + padding: .5rem; + + label { + font-size: 1.5rem; + } + + &:hover { + background-color: $overlay0; + } + } + } +} diff --git a/home/programs/graphical/bars/eww/config/eww.scss b/home/programs/graphical/bars/eww/config/eww.scss new file mode 100644 index 0000000..7854a7b --- /dev/null +++ b/home/programs/graphical/bars/eww/config/eww.scss @@ -0,0 +1,114 @@ +@import "css/colors"; + +@mixin rounding { + border-radius: 16px; +} + +@mixin window { + border: 1px solid $border; + box-shadow: 0 2px 3px $shadow; + margin: 5px 5px 10px; + @include rounding; +} + +* { + all: unset; + transition: 200ms ease; +} + +@import "css/windows/calendar"; +@import "css/windows/radio_menu"; +@import "css/modules/clock"; +@import "css/modules/volume"; +@import "css/modules/bitcoin"; +@import "css/modules/cpu"; +@import "css/modules/memory"; +@import "css/modules/uptime"; +@import "css/modules/kernel"; +@import "css/modules/battery"; +@import "css/modules/workspaces"; +@import "css/modules/gammarelay"; +@import "css/modules/window_name"; + +.bar { + background-color: $bg-a; + color: $fg; + + font-family: "JetBrains Mono", "Jost *", sans-serif; + + label { + font-size: 14px; + } + + // TODO: Use percentages (for some reason it fails now) + min-width: 1900px; +} + +tooltip { + background: $bg; + border: 1px solid $border; + border-radius: 8px; + + label { + font-size: 1rem; + } +} + +.icon, +.icon label { + font-family: "Font Awesome 6 Free", "Material Symbols Outlined"; +} + +.module { + margin: 0 5px; +} + +.separ { + color: $surface0; + font-size: 1.5rem; + padding-bottom: 2px; +} + +scale trough { + background-color: $bg1-a; + border-radius: 24px; + margin: 0 1rem; + min-height: 10px; + min-width: 70px; +} + +.tray { + margin-right: 12px; +} + +menu { + background: $bg1; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + border: 2px solid rgba($crust, 0.5); + padding: 0.5rem 0; +} + +menu menu { + border-top-left-radius: 12px; + border-top-right-radius: 12px; +} + +menu > menuitem { + padding: 0.4em 1rem; + background: transparent; + transition: 0.2s ease background; +} + +menu > menuitem:hover { + background: rgba($overlay1, 0.4); +} + +menu > menuitem check:checked ~ label { + color: $bg1-a; + font-weight: 600; +} + +menubar > menuitem { + margin-left: 0.6rem; +} diff --git a/home/programs/graphical/bars/eww/config/eww.yuck b/home/programs/graphical/bars/eww/config/eww.yuck new file mode 100644 index 0000000..ff4aef7 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/eww.yuck @@ -0,0 +1,94 @@ +(defvar terminal "kitty -e") + +(include "./modules/variables.yuck") + +(include "./modules/clock.yuck") +(include "./modules/volume.yuck") +(include "./modules/bitcoin.yuck") +(include "./modules/cpu.yuck") +(include "./modules/memory.yuck") +(include "./modules/uptime.yuck") +; (include "./modules/kernel.yuck") +(include "./modules/battery.yuck") +(include "./modules/window_name.yuck") +(include "./modules/workspaces.yuck") +(include "./modules/gammarelay.yuck") + +(include "./windows/calendar.yuck") +(include "./windows/radio-menu.yuck") + +(defwidget sep [] + (label :class "separ module" :text "|")) + +(defwidget left [] + (box + :space-evenly false + :halign "start" + (gammarelay_module) + (sep) + (window_name_module) + )) + +(defwidget right [] + (box + :space-evenly false + :halign "end" + ; (kernel_module) + ; (sep) + (volume_module) + (sep) + (battery_module) + (sep) + (bitcoin_module) + (sep) + (cpu_module) + (sep) + (memory_module) + (sep) + ; (uptime_module) + ; (sep) + (clock_module) + (sep) + (systray + :pack-direction "left" + :class "module tray" + ) + )) + +(defwidget center [] + (box + :space-evenly false + :halign "center" + (workspaces_module) + )) + +(defwidget bar [] + (centerbox + :class "bar" + :orientation "horizontal" + (left) + (center) + (right))) + +(defwindow bar0 + :monitor 0 + :geometry (geometry :x "0%" + :y "0%" + :width: "100%" + :height "32px" + :anchor "top center") + :stacking "fg" + :exclusive true + (bar)) + + +(defwindow bar1 + :monitor 1 + :geometry (geometry :x "0%" + :y "0%" + :width: "100%" + :height "32px" + :anchor "top center") + :stacking "fg" + :exclusive true + (bar)) diff --git a/home/programs/graphical/bars/eww/config/modules/battery.yuck b/home/programs/graphical/bars/eww/config/modules/battery.yuck new file mode 100644 index 0000000..b0f033e --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/battery.yuck @@ -0,0 +1,24 @@ +(defwidget battery_module [] + (eventbox + :class "module battery" + + (box + :class {battery.critical ? "critical" : battery.full ? "full" : "normal"} + (box + :space-evenly false + :class {battery.plugged ? "plugged" : "unplugged" } + + (box + :class "icon" + :space-evenly false + + (label + :class "extra" + :text {battery.extra_icon}) + (label + :text {battery.capacity_icon})) + (label + :class "value" + :text "${battery.percent}%" + ) + )))) diff --git a/home/programs/graphical/bars/eww/config/modules/bitcoin.yuck b/home/programs/graphical/bars/eww/config/modules/bitcoin.yuck new file mode 100644 index 0000000..963ef6c --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/bitcoin.yuck @@ -0,0 +1,14 @@ +(defwidget bitcoin_module [] + (eventbox + :onclick "~/.local/bin/scripts/cli/bitcoin | xargs -I_ ${EWW_CMD} update bitcoin=_" + :class "module bitcoin" + + (box + :space-evenly false + + (label + :class "icon" + :text "") + (label :text {bitcoin})) + ) +) diff --git a/home/programs/graphical/bars/eww/config/modules/clock.yuck b/home/programs/graphical/bars/eww/config/modules/clock.yuck new file mode 100644 index 0000000..5217101 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/clock.yuck @@ -0,0 +1,18 @@ +(defwidget clock_module [] + + (eventbox + :tooltip {time.day} + :class "module clock" + ;; :onclick "${EWW_CMD} open --toggle calendar" + + (box + :space-evenly false + + (label + :class "icon" + :text "") + (label + :class "value" + :text "${time.date} ${time.hour}:${time.minute}") + ))) + diff --git a/home/programs/graphical/bars/eww/config/modules/cpu.yuck b/home/programs/graphical/bars/eww/config/modules/cpu.yuck new file mode 100644 index 0000000..940ffb3 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/cpu.yuck @@ -0,0 +1,15 @@ +(defwidget cpu_module [] + (eventbox + :class "module cpu" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text "${round(EWW_CPU.avg,2)}%" + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/gammarelay.yuck b/home/programs/graphical/bars/eww/config/modules/gammarelay.yuck new file mode 100644 index 0000000..513d50a --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/gammarelay.yuck @@ -0,0 +1,41 @@ +(defwidget gammarelay_module [] + (box + :class "module gammarelay" + + (eventbox + :onscroll "scripts/gammarelay temperature scroll {}" + :onclick "scripts/gammarelay temperature set toggle" + :onrightclick "scripts/gammarelay temperature set off" + :tooltip "${temperature} K" + :class "temperature" + (box + (label + :class "icon" + :text "") + )) + + (eventbox + :onscroll "scripts/gammarelay brightness scroll {}" + :onclick "scripts/gammarelay brightness set toggle" + :onrightclick "scripts/gammarelay brightness set off" + :tooltip "${brightness}%" + :class "brightness" + (box + (label + :class "icon" + :text "") + )) + + ; (eventbox + ; :onscroll "scripts/gammarelay gamma scroll {}" + ; :onclick "scripts/gammarelay gamma set toggle" + ; :onrightclick "scripts/gammarelay gamma set off" + ; :tooltip "${gamma}%" + ; :class "gamma" + ; :valign "top" + ; (box + ; (label + ; :class "icon" + ; :text "γ") + ; )) + )) diff --git a/home/programs/graphical/bars/eww/config/modules/kernel.yuck b/home/programs/graphical/bars/eww/config/modules/kernel.yuck new file mode 100644 index 0000000..c784049 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/kernel.yuck @@ -0,0 +1,15 @@ +(defwidget kernel_module [] + (eventbox + :class "module kernel" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text {kernel} + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/memory.yuck b/home/programs/graphical/bars/eww/config/modules/memory.yuck new file mode 100644 index 0000000..43bcca4 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/memory.yuck @@ -0,0 +1,15 @@ +(defwidget memory_module [] + (eventbox + :class "module memory" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text "${round(EWW_RAM.used_mem / 1000000000,1)}G: ${round(EWW_RAM.used_mem_perc,0)}%" + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/nightlight.yuck b/home/programs/graphical/bars/eww/config/modules/nightlight.yuck new file mode 100644 index 0000000..fd2f2fe --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/nightlight.yuck @@ -0,0 +1,3 @@ +(deflisten nightlight + :initial `{"running": false,"temperature": 0}` + `scripts/nightlight --state`) diff --git a/home/programs/graphical/bars/eww/config/modules/uptime.yuck b/home/programs/graphical/bars/eww/config/modules/uptime.yuck new file mode 100644 index 0000000..972b3e1 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/uptime.yuck @@ -0,0 +1,15 @@ +(defwidget uptime_module [] + (eventbox + :class "module uptime" + + (box + :space-evenly false + + (label + :class "icon" + :text " ") + (label + :class "value" + :text {uptime} + ) + ))) diff --git a/home/programs/graphical/bars/eww/config/modules/variables.yuck b/home/programs/graphical/bars/eww/config/modules/variables.yuck new file mode 100644 index 0000000..3dbc09e --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/variables.yuck @@ -0,0 +1,60 @@ +(defpoll time + :interval "5s" + :initial '{"date": "01 Jan", "hour": "00", "minute": "00", "day": "Monday"}' + `date +'{"date": "%d %b", "hour": "%H", "minute": "%M", "day": "%A"}'`) + +(deflisten volume + :initial '{ "speaker_vol": "100", "speaker_mute": false, "speaker_icon": "", "microphone_mute": false, "microphone_vol": "100", "microphone_icon": "" }' + `scripts/volume loop`) + +(deflisten window_name + :initial `{"class":"","name":"","formatted_name":""}` + `scripts/window_name`) + +(deflisten workspaces + :initial `[{"id": 1,"name": "N/A","monitor": "N/A","windows": 1,"hasfullscreen": false,"lastwindow": "N/A","lastwindowtitle": "N/A","format_name": "N/A","active": true}]` + `scripts/workspaces --loop`) + +(defpoll battery + :interval "1s" + :initial '{"percent":"0","plugged":"false","status":"N/A","capacity_icon":"","extra_icon":"","manufacturer":"N/A","model_name":"N/A","technology":"N/A","energy_now":"0","enerfy_full":"0","enerfy_full_design":"0","cycle_count":"0","critical":"false","full":"false"}' + `scripts/battery`) + +(defpoll uptime + :interval "1m" + :initial 'N/A' + `uptime -p | sed \\ + -e 's/^up //' \\ + -e 's/ years\\?,\\?/y/' \\ + -e 's/ months\\?,\\?/m/' \\ + -e 's/ weeks\\?,\\?/w/' \\ + -e 's/ days\\?,\\?/d/' \\ + -e 's/ hours\\?,\\?/h/' \\ + -e 's/ minutes\\?,\\?/m/' \\ + -e 's/ seconds\\?,\\?/s/' \\ + | cut -d' ' -f-2`) + +(defpoll bitcoin + :interval "5m" + :initial "$N/A" + `~/.local/bin/scripts/cli/bitcoin`) + +; TODO: Figure out how to store this one-time +(defpoll kernel + :interval "10000h" + :initial 'N/A' + ; `uname -r | sed -r 's/(.+)-arch(.+)/\\1/'` + `uname -r`) + +(deflisten temperature `scripts/gammarelay temperature watch`) +(deflisten brightness `scripts/gammarelay brightness watch`) +(deflisten gamma `scripts/gammarelay gamma watch`) + +(defpoll net + :interval "3s" + :initial '{"essid":"N/A","icon":"󱛇","state":"unknown","signal":"0"}' + `scripts/net status`) +(defpoll bluetooth + :interval "3s" + :initial '{"icon":"󰂲","status":"unknown","name":"","mac":"","battery":""}' + `scripts/bluetooth status`) diff --git a/home/programs/graphical/bars/eww/config/modules/volume.yuck b/home/programs/graphical/bars/eww/config/modules/volume.yuck new file mode 100644 index 0000000..1f689c9 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/volume.yuck @@ -0,0 +1,40 @@ +(defvar mic_rev false) + +(defwidget volume_module [] + (box + :class "module volume" + :space-evenly false + + (eventbox + :onscroll "scripts/volume setvol SOURCE 0.5 {}" + :onclick "scripts/volume togglemute SOURCE" + :onrightclick "${terminal} pulsemixer &" + :onhover "${EWW_CMD} update mic_rev=true" + :onhoverlost "${EWW_CMD} update mic_rev=false" + :class "microphone" + (box + (label + :class "icon" + :text {volume.microphone_icon}) + (label + :visible {mic_rev && !volume.microphone_mute} + :class "value" + :text "${volume.microphone_vol}%") + )) + + (eventbox + :onscroll "scripts/volume setvol SINK 0.5 {}" + :onclick "scripts/volume togglemute SINK" + :onrightclick "${terminal} pulsemixer &" + :class "speaker" + (box + (label + :class "icon" + :text {volume.speaker_icon}) + (label + :visible {!volume.speaker_mute} + :class "value" + :text "${volume.speaker_vol}%") + )) + + )) diff --git a/home/programs/graphical/bars/eww/config/modules/window_name.yuck b/home/programs/graphical/bars/eww/config/modules/window_name.yuck new file mode 100644 index 0000000..16ace74 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/window_name.yuck @@ -0,0 +1,11 @@ +; Consider making the window name clickable, opening up a full window that's showing +; the selected window details (class, unformatted name, and perhaps even more, like +; xwayland status, ...) +(defwidget window_name_module [] + (box + :class "module window_name" + + (label + :class "value" + :text "${window_name.formatted_name}") + )) diff --git a/home/programs/graphical/bars/eww/config/modules/workspaces.yuck b/home/programs/graphical/bars/eww/config/modules/workspaces.yuck new file mode 100644 index 0000000..3f4feb1 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/modules/workspaces.yuck @@ -0,0 +1,21 @@ +; (defwidget sep [] +; (label :class "separ module" :text "|")) + + +; Consider making the window name clickable, opening up a full window that's showing +; the selected window details (class, unformatted name, and perhaps even more, like +; xwayland status, ...) +(defwidget workspaces_module [] + (box + :class "module workspaces" + + (for workspace in workspaces + (eventbox + :class {workspace.active ? 'focused' : workspace.windows > 0 ? 'active' : 'inactive'} + :onclick `scripts/workspaces --switch ${workspace.id}` + + (label + :class "value icon" + :text {workspace.format_name})) + ) + )) diff --git a/home/programs/graphical/bars/eww/config/scripts/.flake8 b/home/programs/graphical/bars/eww/config/scripts/.flake8 new file mode 100644 index 0000000..ce90fbd --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-line-length=119 +extend-ignore=E203 +extend-select=B902,B904 +exclude=.venv,.git,.cache +ignore= + ANN002, # *args annotation + ANN003, # **kwargs annotation + ANN101, # self param annotation + ANN102, # cls param annotation + ANN204, # return type annotation for special methods diff --git a/home/programs/graphical/bars/eww/config/scripts/battery b/home/programs/graphical/bars/eww/config/scripts/battery new file mode 100755 index 0000000..86d1152 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/battery @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +# $BATTERY and $ADAPTER env vars can be set manually, being the names of the +# devices (in /sys/class/power_supply/) i.e. BATTERY=BAT0 ADAPTER=ADP0 +# or, if left unset, they will be automatically picked. + +CAPACITY_ICONS=("󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹") +CHARGING_ICON="" +DISCHARGING_ICON="" +FULL_ICON="" # Plugged in, but no longer charging (fully charged) +CRITICAL_ICON="" +CRITICAL_PERCENTAGE=15 + +if [ -z "$BATTERY" ]; then + # shellcheck disable=SC2010 + BATTERY="$(\ls -t /sys/class/power_supply | grep "BAT" | head -n 1)" +fi + +if [ -z "$ADAPTER" ]; then + # shellcheck disable=SC2010 + ADAPTER="$(\ls -t /sys/class/power_supply | grep -E "ADP|AC" | head -n 1)" +fi + +get_bat_info() { + cat /sys/class/power_supply/"$BATTERY"/"$1" +} + +get_adp_info() { + cat /sys/class/power_supply/"$ADAPTER"/"$1" +} + +manufacturer="$(get_bat_info manufacturer)" +model_name="$(get_bat_info model_name)" +technology="$(get_bat_info technology)" +energy_now="$(get_bat_info energy_now)" +energy_full="$(get_bat_info energy_full)" +energy_full_design="$(get_bat_info energy_full_design)" +cycle_count="$(get_bat_info cycle_count)" + +capacity="$(get_bat_info capacity)" +status="$(get_bat_info status)" +[ "$(get_adp_info online)" -eq 1 ] && adp_connected="true" || adp_connected="false" + +# Quick overrides to showcase how battery works +# capacity=100 +# adp_connected="true" +# status="Charging" +# status="Not charging" +# status="Discharging" + +full="false" +capacity_icon="$(pick_icon "$capacity" 0 100 "${CAPACITY_ICONS[@]}")" + +if [ "$status" = "Not charging" ] || [ "$status" = "Full" ] && [ "$adp_connected" = "true" ]; then + extra_icon="$FULL_ICON" + full="true" +elif [ "$status" = "Discharging" ] && [ "$capacity" -le "$CRITICAL_PERCENTAGE" ]; then + extra_icon="$CRITICAL_ICON" +elif [ "$status" = "Discharging" ]; then + extra_icon="$DISCHARGING_ICON" +elif [ "$status" = "Charging" ]; then + extra_icon="$CHARGING_ICON" +fi + +[ "$capacity" -le "$CRITICAL_PERCENTAGE" ] && critical="true" || critical="false" + +jq -n -c --monochrome-output \ + --arg percent "$capacity" \ + --arg plugged "$adp_connected" \ + --arg status "$status" \ + --arg capacity_icon "$capacity_icon" \ + --arg extra_icon "$extra_icon" \ + --arg manufacturer "$manufacturer" \ + --arg model_name "$model_name" \ + --arg technology "$technology" \ + --arg energy_now "$energy_now" \ + --arg energy_full "$energy_full" \ + --arg energy_full_design "$energy_full_design" \ + --arg cycle_count "$cycle_count" \ + --arg critical "$critical" \ + --arg full "$full" \ + '$ARGS.named' diff --git a/home/programs/graphical/bars/eww/config/scripts/bluetooth b/home/programs/graphical/bars/eww/config/scripts/bluetooth new file mode 100755 index 0000000..240c4ca --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/bluetooth @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +ICON_IDLE="󰂯" +ICON_CONNECTED="󰂱" +ICON_OFF="󰂲" + +toggle() { + status=$(rfkill -J | jq -r '.rfkilldevices[] | select(.type == "bluetooth") | .soft' | head -1) + + if [ "$status" = "unblocked" ]; then + rfkill block bluetooth + else + rfkill unblock bluetooth + if ! systemctl -q is-active bluetooth.service; then + # This will use polkit for privillege elevation + systemctl start bluetooth + fi + fi +} + +get_report() { + status=$(rfkill -J | jq -r '.rfkilldevices[] | select(.type == "bluetooth") | .soft' | head -1) + if [ "$status" = "blocked" ] || ! systemctl -q is-active bluetooth.service || ! command -v bluetoothctl >/dev/null 2>&1; then + jq -n -c --monochrome-output \ + --arg icon "$ICON_OFF" \ + --arg status "unknown" \ + --arg name "" \ + --arg mac "" \ + --arg battery "" \ + '$ARGS.named' + + return + fi + + status="$(bluetoothctl show)" + + powered="$(echo "$status" | grep Powered | cut -d' ' -f 2-)" + if [ "$powered" != "yes" ]; then + jq -n -c --monochrome-output \ + --arg icon "$ICON_OFF" \ + --arg status "unpowered" \ + --arg name "" \ + --arg mac "" \ + --arg battery "" \ + '$ARGS.named' + + return + fi + + status="$(bluetoothctl info)" + if [ "$status" == "Missing device address argument" ]; then + jq -n -c --monochrome-output \ + --arg icon "$ICON_IDLE" \ + --arg status "idle" \ + --arg name "" \ + --arg mac "" \ + --arg battery "" \ + '$ARGS.named' + + return + fi + + name="$(echo "$status" | grep Name | cut -d' ' -f 2-)" + mac="$(echo "$status" | head -1 | awk '{print $2}' | tr ':' '_')" + + if [ "$(echo "$status" | grep Percentage)" != "" ] && command -v upower >/dev/null 2>&1; then + battery="$(upower -i /org/freedesktop/UPower/devices/headset_dev_"$mac" | grep percentage | awk '{print $2}' | cut -f 1 -d '%')%" + else + battery="" + fi + + jq -n -c --monochrome-output \ + --arg icon "$ICON_CONNECTED" \ + --arg status "connected" \ + --arg name "$name" \ + --arg mac "$mac" \ + --arg battery "$battery" \ + '$ARGS.named' +} + +case "$1" in + "status") get_report ;; + "toggle") toggle ;; + *) >&2 echo "Invalid usage, argument '$1' not recognized."; exit 1 ;; +esac diff --git a/home/programs/graphical/bars/eww/config/scripts/gammarelay b/home/programs/graphical/bars/eww/config/scripts/gammarelay new file mode 100755 index 0000000..039dbea --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/gammarelay @@ -0,0 +1,82 @@ +#!/bin/env bash + +if [ "$1" = "temperature" ]; then + watch_cmd="{t}" + update_cmd="UpdateTemperature" + update_signature="n" + set_cmd="Temperature" + set_signature="q" + default_val=6500 + click_val=4500 + scroll_change=100 + cmp_op="<" + +elif [ "$1" = "brightness" ]; then + watch_cmd="{bp}" + update_cmd="UpdateBrightness" + update_signature="d" + set_cmd="Brightness" + set_signature="d" + default_val=1 + click_val=0.8 + scroll_change=0.02 + cmp_op="<" + +elif [ "$1" = "gamma" ]; then + watch_cmd="{g}" + update_cmd="UpdateGamma" + update_signature="d" + set_cmd="Gamma" + set_signature="d" + default_val=1 + click_val=1.1 + scroll_change=0.02 + cmp_op=">" + +else + >&2 echo "Invalid option, first argument must be one of: temperature, brightness, gamma" + exit 1 +fi + +if [ "$2" = "watch" ]; then + exec wl-gammarelay-rs watch "$watch_cmd" + +elif [ "$2" = "get" ]; then + exec busctl --user get-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" | cut -d' ' -f2 + +elif [ "$2" = "scroll" ]; then + if [ "$3" = "up" ]; then + sign="+" + elif [ "$3" = "down" ]; then + sign="-" + else + >&2 echo "Invalid sign, second argument must be one of: up, down" + exit 1 + fi + + exec busctl --user -- call rs.wl-gammarelay / rs.wl.gammarelay "$update_cmd" "$update_signature" ${sign}${scroll_change} + +elif [ "$2" = "set" ]; then + mode="$3" + if [ "$mode" = "toggle" ]; then + cur_val="$(busctl --user get-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" | cut -d' ' -f2)" + if [ "$(echo "$cur_val $cmp_op $default_val" | bc -l)" = "1" ]; then + mode="off" + else + mode="on" + fi + fi + + if [ "$mode" = "on" ]; then + exec busctl --user -- set-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" "$set_signature" "$click_val" + elif [ "$mode" = "off" ]; then + exec busctl --user -- set-property rs.wl-gammarelay / rs.wl.gammarelay "$set_cmd" "$set_signature" "$default_val" + else + >&2 echo "Invalid mode, third argument, must be one of: toggle, on, off" + exit 1 + fi + +else + >&2 echo "Invalid operation, second argument must be one of: watch, scroll, set" + exit 1 +fi diff --git a/home/programs/graphical/bars/eww/config/scripts/include b/home/programs/graphical/bars/eww/config/scripts/include new file mode 100755 index 0000000..49e58e9 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/include @@ -0,0 +1,50 @@ +#!/bin/env bash + +# $1: Current number +# $2: Range minimum +# $3: Range maximum +# $4-: Icons as individual arguments +pick_icon() { + cur="$1" + min="$2" + max="$3" + shift 3 + icons=("$@") + + index="$(echo "($cur-$min)/(($max-$min)/${#icons[@]})" | bc)" + + # Print the picked icon, handling overflows/underflows, i.e. if our index is <0 or >len(icons) + if [ "$index" -ge "${#icons[@]}" ]; then + index=-1 + elif [ "$index" -lt 0 ]; then + index=0 + fi + + echo "${icons[index]}" +} + +# Will block and listen to the hyprland socket messages and output them +# Generally used like: hyprland_ipc | while read line; do handle $line; done +# Read for output format and available events +# Note: requires openbsd version of netcat. +# $1 - Optional event to listen for (no event filtering will be done if not provided) +hyprland_ipc() { + if [ -z "$HYPRLAND_INSTANCE_SIGNATURE" ]; then + >&2 echo "Hyprland is not running, IPC not available" + exit 1 + fi + + SOCKET_PATH="${XDG_RUNTIME_DIR:-/run/user/$UID}/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" + #SOCKET_PATH="/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" + + if [ -z "$1" ]; then + nc -U "$SOCKET_PATH" | while read -r test; do + echo "$test" + done + else + nc -U "$SOCKET_PATH" | while read -r test; do + # shellcheck disable=SC2016 + echo "$test" | grep --line-buffered -E "^$1>>" | stdbuf -oL awk -F '>>' '{print $2}' + done + fi +} diff --git a/home/programs/graphical/bars/eww/config/scripts/net b/home/programs/graphical/bars/eww/config/scripts/net new file mode 100755 index 0000000..7d84697 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/net @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +STRENGTH_ICONS=("󰤫" "󰤯" "󰤟" "󰤢" "󰤥" "󰤨") +DISCONNECTED_ICON="󰤮" +WIFI_OFF="󰖪" + +toggle() { + status=$(rfkill -J | jq -r '.rfkilldevices[] | select(.type == "wlan") | .soft' | head -1) + + if [ "$status" = "unblocked" ]; then + rfkill block wlan + else + rfkill unblock wlan + fi +} + +get_report() { + connection_details="$(nmcli -t -f NAME,TYPE,DEVICE connection show --active | grep wireless | head -n1)" + essid="$(echo $connection_details | cut -d':' -f1)" + device="$(echo $connection_details | cut -d':' -f3)" + if [ -n "$device" ]; then + state="$(nmcli -t -f DEVICE,STATE device status | grep "$device" | head -n1 | cut -d':' -f2)" + signal="$(nmcli -t -f in-use,signal dev wifi | grep "\*" | cut -d':' -f2)" + else + state="unknown" + signal="0" + fi + + if [ "$state" = "disconnected" ] ; then + icon="$DISCONNECTED_ICON" + elif [ "$state" = "unknown" ]; then + icon="$WIFI_OFF" + else + icon="$(pick_icon "$signal" 0 100 "${STRENGTH_ICONS[@]}")" + fi + + jq -n -c --monochrome-output \ + --arg essid "$essid" \ + --arg icon "$icon" \ + --arg state "$state" \ + --arg signal "$signal" \ + '$ARGS.named' +} + +case "$1" in + "toggle") toggle ;; + "status") get_report ;; + *) >&2 echo "Invalid usage, argument '$1' not recognized."; exit 1 ;; +esac diff --git a/home/programs/graphical/bars/eww/config/scripts/nightlight b/home/programs/graphical/bars/eww/config/scripts/nightlight new file mode 100644 index 0000000..71522f0 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/nightlight @@ -0,0 +1,12 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +# Consider usning a file as a flag for whether nightlight is on or off +# as we might be in transition state and just killing the program might +# not be enough. + +if [ "$1" = "toggle" ]; then + gammastep -x +fi diff --git a/home/programs/graphical/bars/eww/config/scripts/pyproject.toml b/home/programs/graphical/bars/eww/config/scripts/pyproject.toml new file mode 100644 index 0000000..e3a2d4f --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/pyproject.toml @@ -0,0 +1,12 @@ +[tool.black] +line-length = 119 +extend-exclude = "^/.cache" + +[tool.isort] +profile = "black" +line_length = 119 +atomic = true +order_by_type = false +case_sensitive = true +combine_as_imports = true +skip = [".venv", ".git", ".cache"] diff --git a/home/programs/graphical/bars/eww/config/scripts/storage b/home/programs/graphical/bars/eww/config/scripts/storage new file mode 100755 index 0000000..f1ff4b4 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/storage @@ -0,0 +1,35 @@ +#!/bin/env bash + +MOUNTPOINTS=("/" "/mnt/ext") + +data="$(df -H)" + +as_json() { + mountpoint="$1" + res="$2" + arr_res=($res) + + jq -n -c --monochrome-output \ + --arg mountpoint "$mountpoint" \ + --arg size "${arr_res[0]}" \ + --arg used "${arr_res[1]}" \ + --arg avail "${arr_res[2]}" \ + --arg percent "${arr_res[3]}" \ + '$ARGS.named' +} + +output_json="[]" +for mountpoint in "${MOUNTPOINTS[@]}"; do + res="$(echo "$data" | awk -v m="$mountpoint" '$6 == m {print $2 " " $3 " " $4 " " $5}')" + out="$(as_json "$mountpoint" "$res")" + + # echo "$output_json $out" | jq -c -s + + jq --argjson arr1 "$output_json" --argjson arr2 "[$out]" -n \ + '$arr1 + $arr2' + + # mount_data+=("$mountpoint" $res) + # echo "${mount_data[@]}" +done + +# echo "${mount_data[@]}" diff --git a/home/programs/graphical/bars/eww/config/scripts/temp b/home/programs/graphical/bars/eww/config/scripts/temp new file mode 100755 index 0000000..3857531 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/temp @@ -0,0 +1,7 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +#hyprland_ipc "workspace|createworkspace|destroyworkspace|activewindow" +hyprland_ipc diff --git a/home/programs/graphical/bars/eww/config/scripts/volume b/home/programs/graphical/bars/eww/config/scripts/volume new file mode 100755 index 0000000..6003f79 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/volume @@ -0,0 +1,112 @@ +#!/bin/env bash + +# Define some icons +SPEAKER_ICONS=("" "" "") +SPEAKER_MUTED_ICON="" +MIC_ICON="" +MIC_MUTED_ICON="" + +# Define some helper functions for getting/setting audio data using wireplumber (wpctl) + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +get_vol() { + wpctl get-volume "@DEFAULT_AUDIO_${1}@" | awk '{print int($2*100)}' +} + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +# #2 is the voulme (as percentage) to set the volume to +# $3 is optional, if set, it can be '+' or '-', which then adds/decreases volume, instead of setting +set_vol() { + wpctl set-volume "@DEFAULT_AUDIO_${1}@" "$(awk -v n="$2" 'BEGIN{print (n / 100)}')$3" +} + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +check_mute() { + wpctl get-volume "@DEFAULT_AUDIO_${1}@" | grep -i muted >/dev/null + echo $? +} + +# $1 can either be "SINK" (speaker) or "SOURCE" (microphone) +toggle_mute() { + wpctl set-mute "@DEFAULT_AUDIO_${1}@" toggle +} + +get_report() { + spkr_vol="$(get_vol "SINK")" + mic_vol="$(get_vol "SOURCE")" + + if [ "$(check_mute "SINK")" == "0" ]; then + spkr_mute="true" + spkr_icon="$SPEAKER_MUTED_ICON" + else + spkr_mute="false" + index="$(awk -v n="$spkr_vol" -v m="${#SPEAKER_ICONS[@]}" 'BEGIN{print int(n/(100/m))}')" + + # We might end up with an higher than the length of icons, if the volume is over 100% + # in this case, set the index to last icon + if [ "$index" -ge "${#SPEAKER_ICONS[@]}" ]; then + spkr_icon="${SPEAKER_ICONS[-1]}" + else + spkr_icon="${SPEAKER_ICONS[$index]}" + fi + fi + + if [ "$(check_mute "SOURCE")" = "0" ]; then + mic_mute="true" + mic_icon="$MIC_MUTED_ICON" + else + mic_mute="false" + mic_icon="$MIC_ICON" + fi + + echo "{ \"speaker_vol\": \"$spkr_vol\", \"speaker_mute\": $spkr_mute, \"speaker_icon\": \"$spkr_icon\", \"microphone_mute\": $mic_mute, \"microphone_vol\": \"$mic_vol\", \"microphone_icon\": \"$mic_icon\" }" +} + +# Continually run and report every volume change (into stdout) +loop() { + get_report + pactl subscribe | grep --line-buffered "change" | while read -r _; do + get_report + done +} + +case "$1" in +"loop") loop ;; + +"once") get_report ;; + +"togglemute") + if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then + >&2 echo "Invalid usage, expected second argument to be 'SINK' or 'SOURCE', got '$2'" + exit 1 + fi + toggle_mute "$2" + ;; + +"setvol") + if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then + >&2 echo "Invalid usage, expected second argument to be 'SINK' or 'SOURCE', got '$2'" + exit 1 + fi + + if [[ "$3" =~ ^[+-]?[0-9]*\.?[0-9]+$ ]]; then + case "$4" in + "") set_vol "$2" "$3" ;; + up | +) set_vol "$2" "$3" "+" ;; + down | -) set_vol "$2" "$3" "-" ;; + *) + >&2 echo "Invalid usage, expected fourth argument to be up/down or +/-, got '$4'" + exit 1 + ;; + esac + else + >&2 echo "Invalid usage, exepcted third argument to be a number, got '$3'" + exit 1 + fi + ;; + +*) + >&2 echo "Invalid usage, argument '$1' not recognized." + exit 1 + ;; +esac diff --git a/home/programs/graphical/bars/eww/config/scripts/window_name b/home/programs/graphical/bars/eww/config/scripts/window_name new file mode 100755 index 0000000..d32bff7 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/window_name @@ -0,0 +1,6 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +hyprland_ipc "activewindow" | ./scripts/window_name.py diff --git a/home/programs/graphical/bars/eww/config/scripts/window_name.py b/home/programs/graphical/bars/eww/config/scripts/window_name.py new file mode 100755 index 0000000..acc5376 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/window_name.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""This is a utility script for regex remaps on window names. + +This is done in python, because of the complex regex logic, which would be pretty hard to +recreate in bash. This python script is expected to be called from the bash script controlling +the window names. Window name and class are obtained from piped stdin, to prevent having to +needlessly keep restarting this program, which takes a while due to the interpreter starting +overhead. +""" +import json +import re +import sys +from typing import Iterator, Optional + + +class RemapRule: + __slots__ = ("name_pattern", "output_pattern", "class_pattern") + + def __init__( + self, + name_pattern: str, + output_pattern: str, + class_pattern: Optional[str] = None, + ): + self.name_pattern = re.compile(name_pattern) + self.output_pattern = output_pattern + self.class_pattern = re.compile(class_pattern) if class_pattern else None + + def apply(self, window_name: str, window_class: str) -> str: + """Returns new name after this remap rule was applied.""" + if self.class_pattern is not None: + if not self.class_pattern.fullmatch(window_class): + # Rule doesn't apply, class mismatch, return original name + return window_name + + res = self.name_pattern.fullmatch(window_name) + if not res: + # Rule doesn't apply, name mismatch, return original name + return window_name + + # NOTE: This is potentially unsafe, since output_pattern might be say {0.__class__}, etc. + # meaning this allows arbitrary attribute access, however it doesn't allow running functions + # here. That said, code could still end up being run if there's some descriptor defined, + # and generally I wouldn't trust this in production. However, this code is for my personal + # use here, and all of the output patterns are hard-coded in this file, so in this case, it's + # fine. But if you see this code and you'd like to use it in your production code, maybe don't + return self.output_pattern.format(*res.groups()) + + +# Rules will be applied in specified order +REMAP_RULES: list[RemapRule] = [ + RemapRule(r"", "", ""), + RemapRule(r"(.*) — Mozilla Firefox", " {}", "firefox"), + RemapRule(r"Mozilla Firefox", " Mozilla Firefox", "firefox"), + RemapRule(r"Alacritty", " Alacritty", "Alacritty"), + RemapRule( + r"zsh;#toggleterm#1 - \(term:\/\/(.+)\/\/(\d+):(.+)\) - N?VIM", + " Terminal: {0}", + ), + RemapRule(r"(.+) \+ \((.+)\) - N?VIM", " {0} ({1}) [MODIFIED]"), + RemapRule(r"(.+) \((.+)\) - N?VIM", " {0} ({1})"), + RemapRule(r"(?:\[\d+\] )?\*?WebCord - (.+)", " {}", "WebCord"), + RemapRule(r"(.+) - Discord", " {}", "discord"), + RemapRule(r"(?:\(\d+\) )?Discord \| (.+)", " {}", "vesktop"), + RemapRule(r"(.+) - mpv", " {}", "mpv"), + RemapRule(r"Stremio - (.+)", " Stremio - {}", r"(Stremio)|(com.stremio.stremio)"), + RemapRule(r"Spotify", " Spotify", "Spotify"), + RemapRule(r"pulsemixer", " Pulsemixer"), + RemapRule(r"(.*)", " {}", "Pcmanfm"), + RemapRule(r"(.*)", " {}", "pcmanfm-qt"), + # Needs to be last + RemapRule(r"(.*)", " {}", "kitty"), +] + +MAX_LENGTH = 65 + + +def iter_window() -> Iterator[tuple[str, str]]: + """Listen for the window parameters from stdin/pipe, yields (window_name, window_class).""" + for line in sys.stdin: + line = line.removesuffix("\n") + els = line.split(",", maxsplit=1) + if len(els) != 2: + raise ValueError(f"Expected 2 arguments from stdin line (name, class), but got {len(els)}") + yield els[1], els[0] + + +def main() -> None: + for window_name, window_class in iter_window(): + formatted_name = window_name + for remap_rule in REMAP_RULES: + new_name = remap_rule.apply(formatted_name, window_class) + if new_name != formatted_name: + formatted_name = new_name + break + + if len(formatted_name) > MAX_LENGTH: + formatted_name = formatted_name[: MAX_LENGTH - 3] + "..." + + ret = json.dumps( + { + "name": window_name, + "class": window_class, + "formatted_name": formatted_name, + } + ) + print(ret) + sys.stdout.flush() + + +if __name__ == "__main__": + main() diff --git a/home/programs/graphical/bars/eww/config/scripts/workspaces b/home/programs/graphical/bars/eww/config/scripts/workspaces new file mode 100755 index 0000000..992b7ab --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/workspaces @@ -0,0 +1,13 @@ +#!/bin/env bash + +# shellcheck source=include +source "./scripts/include" + +if [ "$1" = "--switch" ]; then + $HOME/.local/bin/scripts/gui/hyprland/swap-workspace "$2" >/dev/null + # hyprctl dispatch workspace "$2" >/dev/null +elif [ "$1" = "--loop" ]; then + hyprland_ipc "workspace|createworkspace|destroyworkspace" | ./scripts/workspaces.py "$@" +else + ./scripts/workspaces.py "$@" +fi diff --git a/home/programs/graphical/bars/eww/config/scripts/workspaces.py b/home/programs/graphical/bars/eww/config/scripts/workspaces.py new file mode 100755 index 0000000..4c31f4b --- /dev/null +++ b/home/programs/graphical/bars/eww/config/scripts/workspaces.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +import argparse +import json +import subprocess +import sys +from typing import TypedDict, TYPE_CHECKING + +if TYPE_CHECKING: + from _typeshed import SupportsRichComparison + + +class WorkspaceInfo(TypedDict): + id: int + name: str + monitor: str + windows: int + hasfullscreen: bool + lastwindow: str + lastwindowtitle: str + + +class ActiveWorkspaceInfo(TypedDict): + id: int + name: str + + +class MonitorInfo(TypedDict): + id: int + name: str + description: str + make: str + model: str + width: int + height: int + refreshRate: float + x: int + y: int + activeWorkspace: ActiveWorkspaceInfo + reserved: list[int] + scale: float + transform: int + focused: bool + dpmsStatus: bool + vrr: bool + + +class OutputWorkspaceInfo(WorkspaceInfo): + format_name: str + active: bool + monitor_id: int + + +# workspace id -> remapped name +REMAPS = { + 1: "󰞷", + 2: "󰈹", + 3: "󱕂", + 4: "󰭹", + 5: "󰝚", + 6: "󰋹", + 7: "7", + 8: "8", + 9: "9", +} + +# Skip the special (scratchpad) workspace +SKIP = {-99} + + +def workspace_sort(obj: OutputWorkspaceInfo) -> "SupportsRichComparison": + """Returns a key to sort by, given the current element.""" + return obj["id"] + + +def fill_blank_workspaces(open: list[OutputWorkspaceInfo]) -> list[OutputWorkspaceInfo]: + """Add in the rest of the workspaces which don't have any open windows on them. + + This is needed because hyprland deletes workspaces with nothing in them. + Note that this assumes all available workspaces were listed in REMAPS, and will + only fill those. These blank workspaces will have most string values set to "N/A", + and most int values set to 0. + """ + # Work on a copy, we don't want to alter the original list + lst = open.copy() + + for remap_id, format_name in REMAPS.items(): + # Skip for already present workspaces + if any(ws_info["id"] == remap_id for ws_info in lst): + continue + + blank_ws: OutputWorkspaceInfo = { + "id": remap_id, + "name": str(remap_id), + "monitor": "N/A", + "windows": 0, + "hasfullscreen": False, + "lastwindow": "N/A", + "lastwindowtitle": "N/A", + "active": False, + "format_name": format_name, + "monitor_id": 0, + } + lst.append(blank_ws) + + return lst + + +def get_workspaces() -> list[OutputWorkspaceInfo]: + """Obtain workspaces from hyprctl, sort them and add format_name arg.""" + proc = subprocess.run(["hyprctl", "workspaces", "-j"], stdout=subprocess.PIPE) + proc.check_returncode() + try: + workspaces: list[WorkspaceInfo] = json.loads(proc.stdout) + except json.JSONDecodeError: + sys.stderr.writelines([ + "Error decoding json response from hyprctl, returning empty workspaces", + f"Actual captured output from hyprctl: {proc.stdout!r}" + ]) + sys.stderr.flush() + workspaces = [] + + proc = subprocess.run(["hyprctl", "monitors", "-j"], stdout=subprocess.PIPE) + proc.check_returncode() + monitors: list[MonitorInfo] = json.loads(proc.stdout) + + active_workspaces = {monitor["activeWorkspace"]["id"] for monitor in monitors} + + out: list[OutputWorkspaceInfo] = [] + for workspace in workspaces: + if workspace["id"] in SKIP: + continue + format_name = REMAPS.get(workspace["id"], workspace["name"]) + active = workspace["id"] in active_workspaces + try: + mon_id = [monitor["id"] for monitor in monitors if monitor["name"] == workspace["monitor"]][0] + except IndexError: # Sometimes workspace["monitor"] is "?", which doesn't match any monitor + mon_id = -1 + out.append({**workspace, "format_name": format_name, "active": active, "monitor_id": mon_id}) + + out = fill_blank_workspaces(out) + out.sort(key=workspace_sort) + return out + + +def print_workspaces() -> None: + wks = get_workspaces() + ret = json.dumps(wks) + print(ret) + sys.stdout.flush() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--oneshot", + action="store_true", + help="Don't listen to stdout for updates, only run once and quit", + ) + parser.add_argument( + "--loop", + action="store_true", + help="Listen to stdout input, once something is received, re-print workspaces" + ) + args = parser.parse_args() + + if args.loop and args.oneshot: + print("Can't use both --oneshot and --loop", file=sys.stdout) + sys.exit(1) + + if args.loop is None and args.oneshot is None: + print("No option specified!", file=sys.stdout) + sys.exit(1) + + # Print workspaces here immediately, we don't want to have to wait for the first + # update from stdin as we only receive those on actual workspace change. + print_workspaces() + + if args.oneshot: + # We've already printed the workspaces once, we can exit now + return + + # Reprint workspaces on each stdin update (flush) + for _ in sys.stdin: + print_workspaces() + + +if __name__ == "__main__": + main() diff --git a/home/programs/graphical/bars/eww/config/windows/calendar.yuck b/home/programs/graphical/bars/eww/config/windows/calendar.yuck new file mode 100644 index 0000000..610f00a --- /dev/null +++ b/home/programs/graphical/bars/eww/config/windows/calendar.yuck @@ -0,0 +1,14 @@ +(defwidget calendar-win [] + (box + :class "calendar-win" + (calendar))) + +(defwindow calendar + :monitor 0 + :geometry (geometry + :x "0%" + :y "0%" + :anchor "top right" + :width "0px" + :height "0px") + (calendar-win)) diff --git a/home/programs/graphical/bars/eww/config/windows/radio-menu.yuck b/home/programs/graphical/bars/eww/config/windows/radio-menu.yuck new file mode 100644 index 0000000..c9a51b5 --- /dev/null +++ b/home/programs/graphical/bars/eww/config/windows/radio-menu.yuck @@ -0,0 +1,78 @@ +(defwidget radio-menu-win [] + (box + :class "radio-menu-box" + :space-evenly false + :orientation "v" + + (box + :class "text-row" + :space-evenly false + (label + :class "title" + :text "Radio/Connections Panel")) + + (box + :class "element-row" + + (box + :class "wifi-box" + :space-evenly false + :orientation "v" + (box + :class "element icon" + :space-evenly false + :halign "center" + (button + :class "wifi-button" + :tooltip "${net.state} (strength: ${net.signal}%)" + :onclick "scripts/net toggle" + {net.icon}) + (label + :class "separator" + :text "│") + (button + :class "wifi-arrow-btn" + :onclick "eww close radio-menu && nm-connection-editor &" + "󰅂")) + (label + :text {net.essid} + :xalign 0.5 + :limit-width 15)) + + (box + :class "bluetooth-box" + :space-evenly false + :orientation "v" + (box + :class "element icon" + :space-evenly false + :halign "center" + (button + :class "bluetooth-button" + :onclick "scripts/bluetooth toggle" + :tooltip "${bluetooth.name} (${bluetooth.mac}) ${bluetooth.battery}" + {bluetooth.icon}) + (label + :class "separator" + :text "│") + (button + :class "bluetooth-arrow-btn" + :onclick "eww close radio-menu && blueberry" + "󰅂")) + (label + :text {bluetooth.name} + :xalign 0.5 + :tooltip "${bluetooth.name} (${bluetooth.mac}) ${bluetooth.battery}" + :limit-width 15))) + )) + +(defwindow radio-menu + :stacking "fg" + :monitor 0 + :geometry (geometry + :x "0" + :y "0" + :width "0%" + :height "0%" + :anchor "right top") + (radio-menu-win)) diff --git a/home/programs/graphical/bars/eww/default.nix b/home/programs/graphical/bars/eww/default.nix new file mode 100644 index 0000000..cb06535 --- /dev/null +++ b/home/programs/graphical/bars/eww/default.nix @@ -0,0 +1,62 @@ +{ + lib, + pkgs, + osConfig, + ... +}: let + inherit (lib) mkIf; + + cfg = osConfig.myOptions.home-manager.programs.bars.eww; +in { + config = mkIf cfg.enable { + programs.eww = { + enable = true; + configDir = ./config; + }; + + systemd.user.services = { + "eww" = { + Unit = { + Description = "ElKowar's Wacky Widgets (eww) daemon"; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + Type = "simple"; + Restart = "always"; + ExecStart = pkgs.writeShellScript "eww-daemon" '' + ${pkgs.eww}/bin/eww daemon --no-daemonize + ''; + + # Takes a value between -20 and 19. Higher values (e.g. 19) mean lower priority. + # Lower priority means the process will get less CPU time and therefore will be slower. + # Fortunately, I do not need my status bar to be fast. Also, te difference is almost + # unnoticeable and definitely negligible. + Nice = 19; + }; + + Install.WantedBy = [ "graphical-session.target" ]; + }; + + "eww-window@" = { + Unit = { + Description = "Open %I eww (ElKowar's Wacky Widgets) window"; + After = [ "eww.service" ]; + PartOf = [ "graphical-session-pre.target" ]; + }; + + Service = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStartPre = "${pkgs.eww}/bin/eww ping"; + ExecStart = "${pkgs.eww}/bin/eww open %i"; + ExecStop = "${pkgs.eww}/bin/eww close %i"; + Restart = "on-failure"; + }; + + Install.WantedBy = [ "graphical-session.target" ]; + }; + }; + }; +} diff --git a/home/programs/graphical/browsers/chromium/default.nix b/home/programs/graphical/browsers/chromium/default.nix new file mode 100644 index 0000000..f79495c --- /dev/null +++ b/home/programs/graphical/browsers/chromium/default.nix @@ -0,0 +1,72 @@ +{ + lib, + osConfig, + pkgs, + ... +}: let + inherit (lib) mkIf optionals concatStringsSep; + + cfg = osConfig.myOptions.home-manager.programs.browsers.chromium; + isWayland = osConfig.myOptions.home-manager.wms.isWayland; +in { + config = mkIf cfg.enable { + programs.chromium = { + enable = true; + + package = pkgs.ungoogled-chromium.override { + nss = pkgs.nss_latest; + commandLineArgs = [ + # Ungoogled features + "--disable-search-engine-collection" + "--extension-mime-request-handling=always-prompt-for-install" + "--fingerprinting-canvas-image-data-noise" + "--fingerprinting-canvas-measuretext-noise" + "--fingerprinting-client-rects-noise" + "--popups-to-tabs" + "--show-avatar-button=incognito-and-guest" + + # Experimental features + "--enable-features=${ + concatStringsSep "," [ + "BackForwardCache:enable_same_site/true" + "CopyLinkToText" + "OverlayScrollbar" + "TabHoverCardImages" + "VaapiVideoDecoder" + ] + }" + + # Aesthetics + "--force-dark-mode" + + # Performance + "--enable-gpu-rasterization" + "--enable-oop-rasterization" + "--enable-zero-copy" + "--ignore-gpu-blocklist" + + # Etc + # "--gtk-version=4" + "--disk-cache=$XDG_RUNTIME_DIR/chromium-cache" + "--no-default-browser-check" + "--no-service-autorun" + "--disable-features=PreloadMediaEngagementData,MediaEngagementBypassAutoplayPolicies" + "--disable-reading-from-canvas" + "--no-pings" + "--no-first-run" + "--no-experiments" + "--no-crash-upload" + "--disable-wake-on-wifi" + "--disable-breakpad" + "--disable-sync" + "--disable-speech-api" + "--disable-speech-synthesis-api" + ] ++ optionals isWayland [ + "--ozone-platform=wayland" + "--enable-features=UseOzonePlatform" + ]; + }; + }; + }; +} + diff --git a/home/programs/graphical/browsers/default.nix b/home/programs/graphical/browsers/default.nix new file mode 100644 index 0000000..23b88ae --- /dev/null +++ b/home/programs/graphical/browsers/default.nix @@ -0,0 +1,9 @@ +_: { + imports = [ + ./chromium + ./mullvad + ./schizofox + ./firefox + ]; +} + diff --git a/home/programs/graphical/browsers/firefox/default.nix b/home/programs/graphical/browsers/firefox/default.nix new file mode 100644 index 0000000..c3af4cb --- /dev/null +++ b/home/programs/graphical/browsers/firefox/default.nix @@ -0,0 +1,17 @@ +{ + lib, + osConfig, + pkgs, + ... +}: let + inherit (lib) mkIf; + + cfg = osConfig.myOptions.home-manager.programs.browsers.firefox; +in { + config = mkIf cfg.enable { + programs.firefox = { + enable = true; + # TODO: Finish this + }; + }; +} diff --git a/home/programs/graphical/browsers/mullvad/default.nix b/home/programs/graphical/browsers/mullvad/default.nix new file mode 100644 index 0000000..7b0079f --- /dev/null +++ b/home/programs/graphical/browsers/mullvad/default.nix @@ -0,0 +1,16 @@ +{ + lib, + osConfig, + pkgs, + ... +}: let + inherit (lib) mkIf; + + cfg = osConfig.myOptions.home-manager.programs.browsers.mullvad-browser; +in { + config = mkIf cfg.enable { + home.packages = with pkgs; [ + mullvad-browser + ]; + }; +} diff --git a/home/programs/graphical/browsers/schizofox/default.nix b/home/programs/graphical/browsers/schizofox/default.nix new file mode 100644 index 0000000..82c93a1 --- /dev/null +++ b/home/programs/graphical/browsers/schizofox/default.nix @@ -0,0 +1,151 @@ +{ + lib, + osConfig, + inputs, + pkgs, + ... +}: let + inherit (lib) mkIf; + + cfg = osConfig.myOptions.home-manager.programs.browsers.schizofox; +in { + imports = [inputs.schizofox.homeManagerModule]; + config = mkIf cfg.enable { + programs.schizofox = { + enable = true; + + theme = { + font = "Inter"; + colors = { + background-darker = "181825"; + background = "1e1e2e"; + foreground = "cdd6f4"; + }; + + extraUserChrome = '' + @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* only needed once */ + + /* full screen toolbars */ + #navigator-toolbox[inFullscreen] toolbar:not([collapsed="true"]) { + visibility:visible!important; + } + ''; + }; + + search = { + defaultSearchEngine = "Brave"; + removeEngines = [ "Bing" "Amazon.com" "eBay" "Twitter" "Wikipedia" "LibRedirect" ]; + searxUrl = "https://searx.be"; + searxQuery = "https://searx.be/search?q={searchTerms}&categories=general"; + + addEngines = [ + { + Name = "Brave"; + Description = "Brave search"; + Alias = "!brave"; + Method = "GET"; + URLTemplate = "https://search.brave.com/search?q={searchTerms}"; + } + { + Name = "NixOS Packages"; + Description = "NixOS Unstable package search"; + Alias = "!np"; + Method = "GET"; + URLTemplate = "https://search.nixos.org/packages?channel=unstable&query={searchTerms}"; + } + { + Name = "NixOS Options"; + Description = "NixOS Unstable option search"; + Alias = "!no"; + Method = "GET"; + URLTemplate = "https://search.nixos.org/options?channel=unstable&query={searchTerms}"; + } + { + Name = "NixOS Wiki"; + Description = "NixOS Wiki search"; + Alias = "!nw"; + Method = "GET"; + URLTemplate = "https://nixos.wiki/index.php?search={searchTerms}"; + } + { + Name = "Home Manager Options"; + Description = "Home Manager option search"; + Alias = "!hm"; + Method = "GET"; + URLTemplate = "https://mipmip.github.io/home-manager-option-search?query={searchTerms}"; + } + { + Name = "MyNixOS"; + Description = "All-In-One NixOS Search (options (including home-manager), packages, categories, flakes) "; + Alias = "!nn"; + Method = "GET"; + URLTemplate = "https://mynixos.com/search?q={searchTerms}"; + } + { + Name = "Arch Wiki"; + Description = "Arch Wiki search"; + Alias = "!aw"; + Method = "GET"; + URLTemplate = "https://wiki.archlinux.org/index.php?search={searchTerms}"; + } + { + Name = "Gentoo Wiki"; + Description = "Gentoo Wiki search"; + Alias = "!gw"; + Method = "GET"; + URLTemplate = "https://wiki.gentoo.org/index.php?search={searchTerms}"; + } + { + Name = "Debian Wiki"; + Description = "Debian Wiki search"; + Alias = "!dw"; + Method = "GET"; + URLTemplate = "https://wiki.debian.org/FrontPage?action=fullsearch&value={searchTerms}"; + } + ]; + }; + + security = { + sanitizeOnShutdown = false; # TODO: check + sandbox = true; + noSessionRestore = false; # TODO: check + userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0"; + }; + + misc = { + drm.enable = true; + disableWebgl = false; + }; + + extensions = { + simplefox.enable = false; # simplified UI, keyboard controled + darkreader.enable = true; + extraExtensions = let + mkUrl = name: "https://addons.mozilla.org/firefox/downloads/latest/${name}/latest.xpi"; + + extensions = [ + { + id = "{c2c003ee-bd69-42a2-b0e9-6f34222cb046}"; + name = "auto-tab-discard"; + } + { + id = "sponsorBlocker@ajay.app"; + name = "sponsorblock"; + } + { + id = "{446900e4-71c2-419f-a6a7-df9c091e268b}"; + name = "bitwarden-password-manager"; + } + { + id = "{9f043cd9-bcc0-4ea0-9407-9e6274c2182d}"; + name = "videospeed"; + } + ]; + + extraExtensions = builtins.foldl' (acc: ext: acc // {ext.id = { install_url = mkUrl ext.name;};}) {} extensions; + in extraExtensions; + }; + }; + }; +} + diff --git a/home/programs/graphical/default.nix b/home/programs/graphical/default.nix index 6b2ded2..68f78e7 100644 --- a/home/programs/graphical/default.nix +++ b/home/programs/graphical/default.nix @@ -3,5 +3,7 @@ _: { ./wms ./launchers ./apps + ./bars + ./browsers ]; } diff --git a/home/programs/terminal/shell/zsh/rc/aliases.zsh b/home/programs/terminal/shell/zsh/rc/aliases.zsh index cba64fc..dcdf069 100644 --- a/home/programs/terminal/shell/zsh/rc/aliases.zsh +++ b/home/programs/terminal/shell/zsh/rc/aliases.zsh @@ -122,8 +122,8 @@ alias sv='systemctl' alias pacnew="find / -name '*.pacnew' 2>/dev/null" # Search for all new configurations after pacman update alias backup="rsync -avHAXS --delete --filter='dir-merge /.rsync-filter'" # Make full rsync backup, respecting .rsync-filter files for exclusions #alias upload='curl -F "f:1=<-" ix.io' # ix.io was offline for about 10 months now... -#alias upload='curl -F "file=@-" https://hardfiles.org' # hardfiles.org works, but has 24-hour expiry -alias upload='curl -F "sprunge=<-" http://sprunge.us' +#alias upload='curl -F "sprunge=<-" http://sprunge.us' # sprunge.us was offline for a few weeks now, not sure why +alias upload='curl -F "file=@-" https://hardfiles.org' # hardfiles.org works, but has 24-hour expiry alias upload-journal='sudo journalctl -b -1 | upload' # Upload journalctl from last boot to ix.io alias auth-log='journalctl SYSLOG_FACILITY=10 -r' alias cpu-stress='for i in $(seq $(getconf _NPROCESSORS_ONLN)); do yes > /dev/null & done' # Run `yes > /dev/null` on all cores as stress test diff --git a/hosts/herugrim/default.nix b/hosts/herugrim/default.nix index 820486b..be8acf2 100644 --- a/hosts/herugrim/default.nix +++ b/hosts/herugrim/default.nix @@ -95,6 +95,13 @@ }; programs = { + browsers = { + chromium.enable = true; + firefox.enable = true; + mullvad-browser.enable = true; + schizofox.enable = true; + }; + spotify.enable = true; }; }; diff --git a/hosts/voyager/default.nix b/hosts/voyager/default.nix index 990e772..393f3e7 100644 --- a/hosts/voyager/default.nix +++ b/hosts/voyager/default.nix @@ -59,13 +59,16 @@ "Media" "dots" + ".mozilla" + ".mullvad" + ".config/chromium" ".local/share/gnupg" ".local/share/wakatime" ".local/share/nvim" ".local/state/nvim" ".local/share/zsh" - ".local/cargo" - ".local/go" + ".local/share/cargo" + ".local/share/go" ]; extraFiles = [ ".config/git/git-credentials" @@ -125,6 +128,12 @@ }; programs = { + bars.eww.enable = true; + browsers = { + firefox.enable = true; + chromium.enable = true; + mullvad-browser.enable = true; + }; spotify.enable = true; }; }; diff --git a/options/home/programs/default.nix b/options/home/programs/default.nix index c6bdb95..ebb153c 100644 --- a/options/home/programs/default.nix +++ b/options/home/programs/default.nix @@ -12,6 +12,20 @@ in }; }; + bars = { + eww = { + enable = mkEnableOption "Eww bar"; + autostart.enable = mkEnableOption "auto-starting eww daemon on graphical-session.target"; + }; + }; + + browsers = { + chromium.enable = mkEnableOption "Chromium browser"; + firefox.enable = mkEnableOption "Firefox browser"; + mullvad-browser.enable = mkEnableOption "Mullvad browser"; + schizofox.enable = mkEnableOption "Schizophrenic version of Firefox browser"; + }; + spotify.enable = mkEnableOption "Spotify"; }; }