summaryrefslogtreecommitdiff
path: root/features
diff options
context:
space:
mode:
Diffstat (limited to 'features')
-rw-r--r--features/system/cli/shells/nushell.nix10
-rw-r--r--features/system/cli/utils/nh.nix17
-rw-r--r--features/system/global/agenix.nix7
-rw-r--r--features/system/global/nix.nix13
-rw-r--r--features/system/global/noxterm.nix3
-rw-r--r--features/system/gui/apps/steam.nix27
-rw-r--r--features/system/gui/apps/vmware.nix8
-rw-r--r--features/system/gui/desktops/niri.nix9
-rw-r--r--features/system/server/cloud/syncthing.nix1
-rw-r--r--features/system/server/core/ssh.nix15
-rw-r--r--features/system/server/development/git-server.nix1
-rw-r--r--features/system/server/gaming/minecraft.nix1
-rw-r--r--features/system/server/media/calibre.nix1
-rw-r--r--features/system/server/media/invidious.nix1
-rw-r--r--features/system/services/bundles/printing.nix13
-rw-r--r--features/system/services/core/systemd-boot.nix11
-rw-r--r--features/system/services/extra/plymouth.nix36
-rw-r--r--features/system/services/extra/sddm.nix23
-rw-r--r--features/system/services/extra/ssh.nix14
-rw-r--r--features/system/services/extra/syncthing-client.nix35
-rw-r--r--features/system/services/extra/udisks2.nix12
-rw-r--r--features/system/services/hardware/bluetooth.nix23
-rw-r--r--features/system/services/hardware/iwd.nix19
-rw-r--r--features/system/services/hardware/networkmanager.nix11
-rw-r--r--features/system/services/hardware/pipewire.nix15
-rw-r--r--features/user/cli/apps/btop.nix15
-rw-r--r--features/user/cli/apps/helix.nix71
-rw-r--r--features/user/cli/apps/zellij.nix22
-rw-r--r--features/user/cli/bundles/go-env.nix14
-rw-r--r--features/user/cli/scripts/spiral/default.nix10
-rw-r--r--features/user/cli/scripts/spiral/plan.md47
-rwxr-xr-xfeatures/user/cli/scripts/spiral/spiral.nu329
-rw-r--r--features/user/cli/shells/nushell/completion.nu17
-rw-r--r--features/user/cli/shells/nushell/default.nix93
-rw-r--r--features/user/cli/shells/nushell/direnv-project-template/.envrc1
-rw-r--r--features/user/cli/shells/nushell/direnv-project-template/flake.nix29
-rw-r--r--features/user/cli/shells/nushell/prompt.nu95
-rw-r--r--features/user/cli/utils/git.nix12
-rw-r--r--features/user/cli/utils/pandoc.nix13
-rw-r--r--features/user/cli/utils/ssh.nix20
-rw-r--r--features/user/global/home-manager.nix3
-rw-r--r--features/user/global/nixpkgs.nix3
-rw-r--r--features/user/gui/apps/foot.nix14
-rw-r--r--features/user/gui/apps/librewolf.nix19
-rw-r--r--features/user/gui/apps/mpv.nix28
-rw-r--r--features/user/gui/apps/obs.nix10
-rw-r--r--features/user/gui/apps/qutebrowser/default.nix81
-rw-r--r--features/user/gui/apps/qutebrowser/scripts/yt-ad-skip.js29
-rw-r--r--features/user/gui/apps/qutebrowser/scripts/yt-dislike-viewer.js704
-rw-r--r--features/user/gui/apps/qutebrowser/scripts/yt-shorts-blocker.js23
-rw-r--r--features/user/gui/apps/qutebrowser/scripts/yt-sponsor-skip.js51
-rw-r--r--features/user/gui/apps/vesktop.nix8
-rw-r--r--features/user/gui/bundles/video.nix11
-rwxr-xr-xfeatures/user/gui/desktops/niri/default.nix126
-rw-r--r--features/user/gui/desktops/niri/keybinds.nix165
-rwxr-xr-xfeatures/user/gui/desktops/niri/parts/fuzzel.nix30
-rwxr-xr-xfeatures/user/gui/desktops/niri/parts/hyprlock.nix46
-rwxr-xr-xfeatures/user/gui/desktops/niri/parts/ignis/default.nix21
-rwxr-xr-xfeatures/user/gui/desktops/niri/parts/mako.nix26
-rw-r--r--features/user/gui/desktops/niri/parts/selectors.nix117
-rwxr-xr-xfeatures/user/gui/desktops/niri/parts/swww.nix8
-rwxr-xr-xfeatures/user/gui/desktops/niri/parts/waybar.nix136
-rw-r--r--features/user/gui/desktops/niri/parts/wl-kbptr.nix14
-rw-r--r--features/user/gui/desktops/niri/parts/wluma.nix11
-rw-r--r--features/user/gui/desktops/niri/readme.md9
-rw-r--r--features/user/services/flatpak.nix27
-rw-r--r--features/user/services/udiskie.nix17
-rw-r--r--features/user/services/wluma.nix11
68 files changed, 2862 insertions, 0 deletions
diff --git a/features/system/cli/shells/nushell.nix b/features/system/cli/shells/nushell.nix
new file mode 100644
index 0000000..0974e9a
--- /dev/null
+++ b/features/system/cli/shells/nushell.nix
@@ -0,0 +1,10 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.cli.shells.nushell;
+in {
+ options.features.cli.shells.nushell.enable = lib.mkEnableOption "nushell";
+ config = lib.mkIf cfg.enable {
+ environment.shells = [ pkgs.nushell ];
+ users.defaultUserShell = pkgs.nushell;
+ environment.systemPackages = [ pkgs.nushell ];
+ };
+}
diff --git a/features/system/cli/utils/nh.nix b/features/system/cli/utils/nh.nix
new file mode 100644
index 0000000..e34157e
--- /dev/null
+++ b/features/system/cli/utils/nh.nix
@@ -0,0 +1,17 @@
+{ config, lib, ... }: let
+ cfg = config.features.cli.utils.nh;
+in {
+ options.features.cli.utils.nh = {
+ enable = lib.mkEnableOption "nh";
+ flake = lib.mkOption {
+ type = lib.types.str;
+ description = "Absolute path to the flake.";
+ };
+ };
+ config = lib.mkIf cfg.enable {
+ programs.nh = {
+ enable = true;
+ flake = cfg.flake;
+ };
+ };
+}
diff --git a/features/system/global/agenix.nix b/features/system/global/agenix.nix
new file mode 100644
index 0000000..f232886
--- /dev/null
+++ b/features/system/global/agenix.nix
@@ -0,0 +1,7 @@
+{ pkgs, inputs, ...}: {
+ imports = [ inputs.agenix.nixosModules.default ];
+ environment.systemPackages = [
+ inputs.agenix.packages.${pkgs.system}.default
+ ];
+ age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
+}
diff --git a/features/system/global/nix.nix b/features/system/global/nix.nix
new file mode 100644
index 0000000..a0ece02
--- /dev/null
+++ b/features/system/global/nix.nix
@@ -0,0 +1,13 @@
+{ ... }: {
+ nix = {
+ settings = {
+ experimental-features = [ "nix-command" "flakes" ];
+ auto-optimise-store = true;
+ };
+ gc = {
+ automatic = true;
+ dates = "daily";
+ options = "-d 5";
+ };
+ };
+}
diff --git a/features/system/global/noxterm.nix b/features/system/global/noxterm.nix
new file mode 100644
index 0000000..44c0917
--- /dev/null
+++ b/features/system/global/noxterm.nix
@@ -0,0 +1,3 @@
+{ pkgs, ... }: {
+ services.xserver.excludePackages = [ pkgs.xterm ];
+}
diff --git a/features/system/gui/apps/steam.nix b/features/system/gui/apps/steam.nix
new file mode 100644
index 0000000..de17f43
--- /dev/null
+++ b/features/system/gui/apps/steam.nix
@@ -0,0 +1,27 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ cfg = config.features.gui.apps.steam;
+in {
+ options.features.gui.apps.steam.enable = lib.mkEnableOption "steam";
+ config = lib.mkIf cfg.enable {
+ programs.steam = {
+ enable = true;
+ remotePlay.openFirewall = true;
+ dedicatedServer.openFirewall = true;
+ gamescopeSession.enable = true;
+ };
+ hardware.steam-hardware.enable = true;
+ programs.gamemode.enable = true;
+ environment.systemPackages = [pkgs.mangohud];
+
+ # Steam Launch Options
+ # Add this to 3D games:
+ # LD_PRELOAD="" gamescope --force-grab-cursor --backend sdl -bf -W 1600 -H 900 -- %command%
+ # Add this to 2D games:
+ # LD_PRELOAD="" gamescope -bf -W 1600 -H 900 -- %command%
+ };
+}
diff --git a/features/system/gui/apps/vmware.nix b/features/system/gui/apps/vmware.nix
new file mode 100644
index 0000000..4da3f09
--- /dev/null
+++ b/features/system/gui/apps/vmware.nix
@@ -0,0 +1,8 @@
+{ config, lib, ... }: let
+ cfg = config.features.gui.apps.vmware;
+in {
+ options.features.gui.apps.vmware.enable = lib.mkEnableOption "vmware";
+ config = lib.mkIf cfg.enable {
+ virtualisation.vmware.host.enable = true;
+ };
+}
diff --git a/features/system/gui/desktops/niri.nix b/features/system/gui/desktops/niri.nix
new file mode 100644
index 0000000..c68a4b6
--- /dev/null
+++ b/features/system/gui/desktops/niri.nix
@@ -0,0 +1,9 @@
+{ config, lib, inputs, ... }: let
+ cfg = config.features.gui.desktops.niri;
+in {
+ imports = [ inputs.niri.nixosModules.niri ];
+ options.features.gui.desktops.niri.enable = lib.mkEnableOption "niri";
+ config = lib.mkIf cfg.enable {
+ programs.niri.enable = true;
+ };
+}
diff --git a/features/system/server/cloud/syncthing.nix b/features/system/server/cloud/syncthing.nix
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/features/system/server/cloud/syncthing.nix
@@ -0,0 +1 @@
+{}
diff --git a/features/system/server/core/ssh.nix b/features/system/server/core/ssh.nix
new file mode 100644
index 0000000..16fec48
--- /dev/null
+++ b/features/system/server/core/ssh.nix
@@ -0,0 +1,15 @@
+{ config, lib, ... }: let
+ cfg = config.features.server.ssh;
+in {
+ options.features.server.ssh.enable = lib.mkEnableOption "sshd";
+ config = lib.mkIf cfg.enable {
+ services.openssh = {
+ enable = true;
+ ports = [ 2200 ];
+ settings = {
+ PermitRootLogin = "no";
+ PasswordAuthentication = false;
+ };
+ };
+ };
+}
diff --git a/features/system/server/development/git-server.nix b/features/system/server/development/git-server.nix
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/features/system/server/development/git-server.nix
@@ -0,0 +1 @@
+{}
diff --git a/features/system/server/gaming/minecraft.nix b/features/system/server/gaming/minecraft.nix
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/features/system/server/gaming/minecraft.nix
@@ -0,0 +1 @@
+{}
diff --git a/features/system/server/media/calibre.nix b/features/system/server/media/calibre.nix
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/features/system/server/media/calibre.nix
@@ -0,0 +1 @@
+{}
diff --git a/features/system/server/media/invidious.nix b/features/system/server/media/invidious.nix
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/features/system/server/media/invidious.nix
@@ -0,0 +1 @@
+{}
diff --git a/features/system/services/bundles/printing.nix b/features/system/services/bundles/printing.nix
new file mode 100644
index 0000000..ea255be
--- /dev/null
+++ b/features/system/services/bundles/printing.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }: let
+ cfg = config.features.services.bundles.printing;
+in {
+ options.features.services.bundles.printing.enable = lib.mkEnableOption "printing";
+ config = lib.mkIf cfg.enable {
+ services.printing.enable = true;
+ services.avahi = {
+ enable = true;
+ nssmdns4 = true;
+ openFirewall = true;
+ };
+ };
+}
diff --git a/features/system/services/core/systemd-boot.nix b/features/system/services/core/systemd-boot.nix
new file mode 100644
index 0000000..2826f1b
--- /dev/null
+++ b/features/system/services/core/systemd-boot.nix
@@ -0,0 +1,11 @@
+{ config, lib, ... }: let
+ cfg = config.features.services.core.systemd-boot;
+in {
+ options.features.services.core.systemd-boot.enable = lib.mkEnableOption "systemd-boot";
+ config = lib.mkIf cfg.enable {
+ boot.loader = {
+ systemd-boot.enable = true;
+ efi.canTouchEfiVariables = true;
+ };
+ };
+}
diff --git a/features/system/services/extra/plymouth.nix b/features/system/services/extra/plymouth.nix
new file mode 100644
index 0000000..07f00cd
--- /dev/null
+++ b/features/system/services/extra/plymouth.nix
@@ -0,0 +1,36 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.extra.plymouth;
+in {
+ options.features.services.extra.plymouth.enable = lib.mkEnableOption "plymouth";
+ config = lib.mkIf cfg.enable {
+ boot = {
+ plymouth = {
+ enable = true;
+ theme = "dark_planet";
+ themePackages = with pkgs; [
+ # By default we would install all themes
+ (adi1090x-plymouth-themes.override {
+ selected_themes = [ "dark_planet" ];
+ })
+ ];
+ };
+
+ # Enable "Silent Boot"
+ consoleLogLevel = 0;
+ initrd.verbose = false;
+ kernelParams = [
+ "quiet"
+ "splash"
+ "boot.shell_on_fail"
+ "loglevel=3"
+ "rd.systemd.show_status=false"
+ "rd.udev.log_level=3"
+ "udev.log_priority=3"
+ ];
+ # Hide the OS choice for bootloaders.
+ # It's still possible to open the bootloader list by pressing any key
+ # It will just not appear on screen unless a key is pressed
+ loader.timeout = 0;
+ };
+ };
+}
diff --git a/features/system/services/extra/sddm.nix b/features/system/services/extra/sddm.nix
new file mode 100644
index 0000000..f266575
--- /dev/null
+++ b/features/system/services/extra/sddm.nix
@@ -0,0 +1,23 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.extra.sddm;
+ sddm-astronaut = pkgs.sddm-astronaut.override {
+ themeConfig = {
+ # Background = ./background.png;
+ };
+ };
+in {
+ options.features.services.extra.sddm.enable = lib.mkEnableOption "sddm";
+ config = lib.mkIf cfg.enable {
+ services = {
+ displayManager.sddm = {
+ enable = true;
+ wayland.enable = true;
+ package = pkgs.kdePackages.sddm;
+ theme = "sddm-astronaut-theme";
+ extraPackages = [ sddm-astronaut ];
+ };
+ # xserver.enable = true;
+ };
+ environment.systemPackages = [ sddm-astronaut ];
+ };
+}
diff --git a/features/system/services/extra/ssh.nix b/features/system/services/extra/ssh.nix
new file mode 100644
index 0000000..d1cee26
--- /dev/null
+++ b/features/system/services/extra/ssh.nix
@@ -0,0 +1,14 @@
+{ config, lib, ... }: let
+ cfg = config.features.services.extra.ssh;
+in {
+ options.features.services.extra.ssh.enable = lib.mkEnableOption "ssh";
+ config = lib.mkIf cfg.enable {
+ services.openssh = {
+ enable = true;
+ settings = {
+ PermitRootLogin = "no";
+ PasswordAuthentication = false;
+ };
+ };
+ };
+}
diff --git a/features/system/services/extra/syncthing-client.nix b/features/system/services/extra/syncthing-client.nix
new file mode 100644
index 0000000..6d3d3d3
--- /dev/null
+++ b/features/system/services/extra/syncthing-client.nix
@@ -0,0 +1,35 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.extra.syncthing-client;
+in {
+ options.features.services.extra.syncthing-client = {
+ enable = lib.mkEnableOption "syncthing";
+ username = lib.mkOption {
+ type = lib.types.str;
+ description = "The username of syncthing's user";
+ };
+ };
+ config = lib.mkIf cfg.enable {
+ services.syncthing = {
+ enable = true;
+ user = cfg.username;
+ dataDir = "/home/${cfg.username}/Sync";
+ configDir = "/home/${cfg.username}/.config/syncthing";
+ overrideDevices = true;
+ overrideFolders = true;
+ settings = {
+ devices = {
+ "prodesk-server" = {
+ id = "SBH4S2T-B7KVAAI-BKBOQKZ-YSNQDSM-TKXPV6O-OSZUD3O-N6USL6L-DHL3BAK";
+ };
+ };
+ folders = {
+ "Main" = {
+ path = "/home/${cfg.username}/Sync";
+ devices = [ "prodesk-server" ];
+ };
+ };
+ };
+ };
+ environment.systemPackages = [ pkgs.syncthing ];
+ };
+}
diff --git a/features/system/services/extra/udisks2.nix b/features/system/services/extra/udisks2.nix
new file mode 100644
index 0000000..2ec2fa4
--- /dev/null
+++ b/features/system/services/extra/udisks2.nix
@@ -0,0 +1,12 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.features.services.extra.udisks2;
+in {
+ options.features.services.extra.udisks2.enable = lib.mkEnableOption "udisks2";
+ config = lib.mkIf cfg.enable {
+ services.udisks2.enable = true;
+ };
+}
diff --git a/features/system/services/hardware/bluetooth.nix b/features/system/services/hardware/bluetooth.nix
new file mode 100644
index 0000000..a9829ac
--- /dev/null
+++ b/features/system/services/hardware/bluetooth.nix
@@ -0,0 +1,23 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.hardware.bluetooth;
+in {
+ options.features.services.hardware.bluetooth.enable = lib.mkEnableOption "bluetooth";
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.bluetui ];
+ services.blueman = {
+ enable = true;
+ };
+ hardware.bluetooth = {
+ enable = true;
+ powerOnBoot = true;
+ };
+ services.pulseaudio = {
+ package = pkgs.pulseaudioFull;
+ };
+ hardware.bluetooth.settings = {
+ General = {
+ Enable = "Source,Sink,Media,Socket";
+ };
+ };
+ };
+}
diff --git a/features/system/services/hardware/iwd.nix b/features/system/services/hardware/iwd.nix
new file mode 100644
index 0000000..c2394bc
--- /dev/null
+++ b/features/system/services/hardware/iwd.nix
@@ -0,0 +1,19 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.hardware.iwd;
+in {
+ options.features.services.hardware.iwd.enable = lib.mkEnableOption "iwd";
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.impala ];
+ networking.wireless.iwd = {
+ enable = true;
+ settings = {
+ IPv6 = {
+ Enabled = true;
+ };
+ Settings = {
+ AutoConnect = true;
+ };
+ };
+ };
+ };
+}
diff --git a/features/system/services/hardware/networkmanager.nix b/features/system/services/hardware/networkmanager.nix
new file mode 100644
index 0000000..49cc7bc
--- /dev/null
+++ b/features/system/services/hardware/networkmanager.nix
@@ -0,0 +1,11 @@
+{ config, lib, ... }: let
+ cfg = config.features.services.hardware.networkmanager;
+in {
+ options.features.services.hardware.networkmanager.enable = lib.mkEnableOption "networkmanager";
+ config = lib.mkIf cfg.enable {
+ networking.networkmanager = {
+ enable = true;
+ };
+ systemd.services.NetworkManager-wait-online.enable = false;
+ };
+}
diff --git a/features/system/services/hardware/pipewire.nix b/features/system/services/hardware/pipewire.nix
new file mode 100644
index 0000000..f36a0e9
--- /dev/null
+++ b/features/system/services/hardware/pipewire.nix
@@ -0,0 +1,15 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.hardware.pipewire;
+in {
+ options.features.services.hardware.pipewire.enable = lib.mkEnableOption "pipewire";
+ config = lib.mkIf cfg.enable {
+ security.rtkit.enable = true;
+ environment.systemPackages = [ pkgs.git ]; # Fixes a weird error
+ services.pipewire = {
+ enable = true;
+ alsa.enable = true;
+ alsa.support32Bit = true;
+ pulse.enable = true;
+ };
+ };
+}
diff --git a/features/user/cli/apps/btop.nix b/features/user/cli/apps/btop.nix
new file mode 100644
index 0000000..5bb2499
--- /dev/null
+++ b/features/user/cli/apps/btop.nix
@@ -0,0 +1,15 @@
+{ config, lib, ... }: let
+ cfg = config.features.cli.apps.btop;
+in {
+ options.features.cli.apps.btop.enable = lib.mkEnableOption "btop";
+ config = lib.mkIf cfg.enable {
+ programs.btop = {
+ enable = true;
+ settings = {
+ color_theme = "TTY";
+ theme_background = false;
+ update_ms = 500;
+ };
+ };
+ };
+}
diff --git a/features/user/cli/apps/helix.nix b/features/user/cli/apps/helix.nix
new file mode 100644
index 0000000..a267b23
--- /dev/null
+++ b/features/user/cli/apps/helix.nix
@@ -0,0 +1,71 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ cfg = config.features.cli.apps.helix;
+in {
+ options.features.cli.apps.helix.enable = lib.mkEnableOption "helix";
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ wl-clipboard
+ ];
+ programs.helix = {
+ enable = true;
+ extraPackages = with pkgs; [
+ markdown-oxide
+ python312Packages.black
+ vscode-langservers-extracted
+ nil
+ ];
+ settings = {
+ editor = {
+ color-modes = true;
+ true-color = true;
+ line-number = "relative";
+ mouse = false;
+ lsp.display-messages = true;
+ statusline = {
+ left = ["file-name" "file-modification-indicator"];
+ center = [];
+ right = [];
+ mode = {
+ normal = "NORMAL";
+ insert = "INSERT";
+ select = "SELECT";
+ };
+ };
+ cursor-shape = {
+ normal = "block";
+ insert = "bar";
+ select = "underline";
+ };
+ soft-wrap = {
+ enable = true;
+ wrap-indicator = "";
+ };
+ };
+ keys = {
+ normal = {
+ esc = ["collapse_selection" "keep_primary_selection"];
+ space = {
+ v = "file_picker_in_current_buffer_directory";
+ h = ":lsp-workspace-command";
+ };
+ };
+ };
+ };
+ languages = {
+ language = [
+ {
+ name = "nix";
+ auto-format = true;
+ formatter.command = "${pkgs.alejandra}/bin/alejandra";
+ language-servers = ["nil"];
+ }
+ ];
+ };
+ };
+ };
+}
diff --git a/features/user/cli/apps/zellij.nix b/features/user/cli/apps/zellij.nix
new file mode 100644
index 0000000..8942771
--- /dev/null
+++ b/features/user/cli/apps/zellij.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }: let
+ cfg = config.features.cli.apps.zellij;
+in {
+ options.features.cli.apps.zellij.enable = lib.mkEnableOption "zellij";
+ config = lib.mkIf cfg.enable {
+ programs.zellij = {
+ enable = true;
+ settings = {
+ pane_frames = true;
+ ui.pane_frames.rounded_corners = true;
+ simplified_ui = true;
+ default_layout = "compact";
+ hide_session_name = true;
+
+ mouse_mode = false;
+
+ on_force_close = "detach";
+ session_serialization = true;
+ };
+ };
+ };
+}
diff --git a/features/user/cli/bundles/go-env.nix b/features/user/cli/bundles/go-env.nix
new file mode 100644
index 0000000..6e103a3
--- /dev/null
+++ b/features/user/cli/bundles/go-env.nix
@@ -0,0 +1,14 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.cli.bundles.go-env;
+in {
+ options.features.cli.bundles.go-env.enable = lib.mkEnableOption "go-env";
+ config = lib.mkIf cfg.enable {
+ home.sessionVariables = {
+ GOPATH = "~/.go";
+ };
+ home.packages = with pkgs; [
+ go
+ gopls
+ ];
+ };
+}
diff --git a/features/user/cli/scripts/spiral/default.nix b/features/user/cli/scripts/spiral/default.nix
new file mode 100644
index 0000000..780c207
--- /dev/null
+++ b/features/user/cli/scripts/spiral/default.nix
@@ -0,0 +1,10 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.cli.scripts.spiral;
+in {
+ options.features.cli.scripts.spiral.enable = lib.mkEnableOption "spiral";
+ config = lib.mkIf cfg.enable {
+ home.packages = [
+ (pkgs.writers.writeNuBin "sp" (builtins.readFile ./spiral.nu))
+ ];
+ };
+}
diff --git a/features/user/cli/scripts/spiral/plan.md b/features/user/cli/scripts/spiral/plan.md
new file mode 100644
index 0000000..adbcba0
--- /dev/null
+++ b/features/user/cli/scripts/spiral/plan.md
@@ -0,0 +1,47 @@
+# Spiral Plan
+
+## Main Functions
+
+Are ran by the user.
+
+- `main new (--no-frontmatter (-n)) (title: <string>)`
+- `main open (--by-title (-t)) (--by-contents (-c)) (title: <string>)`
+- `main tags (tag: <string>)`
+- `main remove (--by-title (-t)) (--by-contents (-c)) (title: <string>)`
+- `main list`
+- `main restore (--by-title (-t)) (--by-contents (-c)) (title: <string>)`
+- `main journal (date: <string>)`
+
+## Action Functions
+
+Accept parameters and perform an action.
+
+- `create_note (--no-frontmatter) --title <string>`
+- `create_journal_entry --title <string>`
+- `open_file_in_editor --dir <dir>`
+
+## Interface Functions
+
+Accept a prompt and other data and return a value from the user.
+
+- `get_confirmation --prompt <string> -> bool`
+- `get_string_input --prompt <string> -> string`
+- `get_choice_from_list --list <list> -> string`
+- `get_choice_from_dir --dir <dir> -> path`
+- `get_choice_from_dir_file_contents --dir <dir> -> path: line number`
+- `get_choice_from_note_titles --dir <dir> -> path`
+
+## String Manipulation Functions
+
+Accept, modify, and return strings.
+
+- `title_to_path title: <string> -> path`
+- `strip_line_number string: <string> -> string`
+- `generate_note_text (--no-frontmatter) --title <string> -> string`
+
+## File Manipulation Functions
+
+Read, write, or otherwise manipulate files.
+
+- `parse_note_frontmatter path: <path> -> record`
+- `list_notes -> table`
diff --git a/features/user/cli/scripts/spiral/spiral.nu b/features/user/cli/scripts/spiral/spiral.nu
new file mode 100755
index 0000000..399dace
--- /dev/null
+++ b/features/user/cli/scripts/spiral/spiral.nu
@@ -0,0 +1,329 @@
+#!/usr/bin/env nu
+
+# +---------------+
+# | Configuration |
+# +---------------+
+
+# Default config options.
+mut config = {
+ notebook_dir: "~/Sync/notebook"
+ journal_dir: "~/Sync/notebook/journal"
+ trash_dir: "~/Sync/notebook/.trash"
+}
+
+# Read config options from environment variables.
+if ((try {$env.spiral_notebook_dir}) != null) {
+ $config.notebook_dir = $env.spiral_notebook_dir
+}
+if ((try {$env.spiral_journal_dir}) != null) {
+ $config.journal_dir = $env.spiral_journal_dir
+}
+if ((try {$env.spiral_trash_dir}) != null) {
+ $config.trash_dir = $env.spiral_trash_dir
+}
+
+# Make config immutable.
+let config = $config
+
+# +-------------+
+# | Subcommands |
+# +-------------+
+
+# A commandline utility written in nushell to manage your markdown notebook!
+def "main" [] {}
+
+# Create a new note.
+def "main new" [
+ title?: string # The title of the new note.
+ --no-frontmatter (-n) # Don't add a frontmatter to the note.
+] {
+ mut title = $title
+ if ($title == null) {
+ $title = get_string_input "Enter a title: "
+ }
+ if $no_frontmatter {
+ create_note --no-frontmatter $title
+ } else {
+ create_note $title
+ }
+}
+
+# Open a pre-existing note.
+def "main open" [
+ --by-title (-t) # Filter notes by their title (disables preview)
+ --by-contents (-c) # Search through the contents of the notebook.
+ title?: string # The title of the note to open.
+] {
+ if ($by_title and $by_contents) {
+ error make {msg: "Only one flag can be used at a time."}
+ }
+ if ($title != null) {
+ if ((title_to_path $title) | path exists) {
+ return (title_to_path $title)
+ } else {
+ if (confirm $"The note ($title) does not exist. Would you like to create it? ") {
+ create_note $title
+ }
+ }
+ } else if $by_title {
+ edit $"(get_choice_from_note_titles --dir $config.notebook_dir)"
+ } else if $by_contents {
+ edit (get_choice_from_dir_file_contents --dir $config.notebook_dir)
+ } else {
+ edit $"(get_choice_from_dir --dir $config.notebook_dir)"
+ }
+}
+
+# Open a note based on the tag.
+def "main tags" [
+ tag?: string # The tag to select a note from.
+] {
+ # This section of code creates nested records with tags and notes and stores them in $tag_record.
+ let note_table = list_notes --dir $config.notebook_dir
+
+ let tag_list = $note_table
+ | get tags
+ | flatten
+ | where (($it != null) and ($it != ""))
+
+ mut tag_record = {}
+
+ print $tag_list
+ for tag in $tag_list {
+ mut notes = {}
+ for note_path in ($note_table | get path) {
+ let note_tags = ($note_table | where path == $note_path | get tags | flatten)
+ let note_title = ($note_table | where path == $note_path | get title | to text | str trim)
+ if ($note_tags != []) {
+ for note_tag in $note_tags {
+ if ($note_tag == $tag) {
+ $notes = $notes | insert $note_title { $note_path }
+ }
+ }
+ }
+ }
+ let notes = $notes
+ # $tag_record = $tag_record | insert $tag { $notes }
+ }
+
+ # Actual logic of the subcommand
+ if ($tag == null) {
+ let user_tag_choice = get_choice_from_list --list $tag_list
+ if ($user_tag_choice != "") {
+ let note_record = $tag_record | get $user_tag_choice
+ let user_note_title_choice = get_choice_from_list --list ($note_record | columns)
+ if ($user_note_title_choice != "") {
+ edit ($note_record | get $user_note_title_choice)
+ }
+ }
+ } else {
+ if ($tag_list | any {|item| $item == $tag}) {
+ let note_record = $tag_record | get $tag
+ let user_note_title_choice = get_choice_from_list --list ($note_record | columns)
+ if ($user_note_title_choice != "") {
+ edit ($note_record | get $user_note_title_choice)
+ }
+ } else {
+ print $"The tag '($tag)' does not exist."
+ }
+ }
+}
+
+# Get a table of notes and their properties.
+def "main list" [] {
+ return (list_notes --dir $config.notebook_dir)
+}
+
+# Send a note to the trash.
+def "main remove" [
+ --by-title (-t) # Filter notes by their title (disables preview)
+ --by-contents (-c) # Search through the contents of the notebook.
+ title?: string # The title of the note to send to the trash.
+] {
+ if ($by_title and $by_contents) {
+ error make {msg: "Only one flag can be used at a time."}
+ }
+ if ($title != null) {
+ if ((title_to_path $title) | path exists) {
+ return (title_to_path $title)
+ } else {
+ if (confirm $"The note ($title) does not exist. Would you like to create it? ") {
+ create_note $title
+ }
+ }
+ } else if $by_title {
+ mv (select_note --by-title --dir $config.notebook_dir)
+ } else if $by_contents {
+ ^$env.editor (select_note --by-contents --dir $config.notebook_dir)
+ } else {
+ ^$env.editor (select_note --dir $config.notebook_dir)
+ }
+}
+
+# Explains how to configural Spiral.
+def "main config" [] {
+ print "The default options:"
+ print $config
+ print "These can be modified by editing the environment variables corresponding to each option. For example, to change the notebook directory, set $env.spiral_notebook_dir='~/custom-dir/notebook'."
+}
+
+# +------------------+
+# | Action Functions |
+# +------------------+
+
+def create_note [--no-frontmatter, title: string] {
+ let note_path = title_to_path $title
+ if ($note_path | path exists) {
+ if (confirm "This note already exists. Would you like to open it?") {
+ edit $note_path
+ } else {exit}
+ } else {
+ if $no_frontmatter {
+ generate_note_text --no-frontmatter --title $title | save $note_path
+ } else {
+ generate_note_text --title $title | save $note_path
+ }
+ edit $"($note_path):100:100"
+ }
+}
+
+def create_journal_entry [
+
+] {
+
+}
+
+def edit [
+ file_path: string
+] {
+ if (($file_path | path type) == file) {
+ ^$env.editor ($file_path | path expand)
+ }
+}
+
+# +---------------------+
+# | Interface Functions |
+# +---------------------+
+
+def get_string_input [prompt: string] {
+ print $prompt
+ return (input)
+}
+
+def get_confirmation [prompt: string] {
+ print $"($prompt) \(Y/n)"
+ let confirmation = (input | str downcase)
+ if ($confirmation == "n" ) {
+ return false
+ } else {return true}
+}
+
+def get_choice_from_list [ --list: list ] {
+ return (($list | to text) | tv)
+} # -> string
+
+def get_choice_from_dir [ --dir: string ] {
+ return $"($config.notebook_dir | path expand)/(tv files ($dir | path expand))"
+} # -> full path
+
+def get_choice_from_dir_file_contents [ --dir: string ] {
+ return $"($config.notebook_dir | path expand)/(tv text ($dir | path expand))"
+} # -> full path: line number
+
+def get_choice_from_note_titles [ --dir: string ] {
+ let note_table = list_notes --dir $config.notebook_dir
+ let user_choice = get_choice_from_list --list ($note_table | get title)
+ let user_choice_path = (
+ $note_table
+ | where title == $user_choice
+ | get path
+ | to text
+ | str trim
+ )
+ if (($user_choice_path | path type) == file) {
+ return $user_choice_path
+ }
+} # -> full path
+
+# +-------------------------------+
+# | String Manipulation Functions |
+# +-------------------------------+
+
+def title_to_path [title: string] {
+ return (
+ $"($config.notebook_dir)/($title | str kebab-case).md"
+ | path expand
+ )
+} # -> full path
+
+def strip_line_number [string: string] {
+ return (
+ $string
+ | split row ":"
+ | select 0
+ | to text
+ | str trim
+ )
+} # -> string
+
+def generate_note_text [
+ --no-frontmatter
+ --title: string
+] {
+ if $no_frontmatter {
+ return $"# ($title)\n"
+ } else {
+ let frontmatter_info = {
+ title: $title
+ date: (date now)
+ lang: "en-US"
+ tags: ['note']
+ }
+ return $"---\n($frontmatter_info | to yaml)---\n\n# ($title)"
+ }
+} # -> string
+
+# +-----------------------------+
+# | File Manipulation Functions |
+# +-----------------------------+
+
+def list_notes [--dir: string] {
+ return ((
+ ls ($dir | path expand)
+ | where type == file
+ | get name
+ ) | par-each {
+ |note_path|
+ let frontmatter = parse_frontmatter $note_path
+ if ($frontmatter != null) {
+ return {
+ title: (try {$frontmatter.title})
+ path: $note_path
+ tags: (try {$frontmatter.tags})
+ date: (try {$frontmatter.date})
+ lang: (try {$frontmatter.lang})
+ }
+ }
+ })
+} # -> table
+
+def parse_frontmatter [path: string] {
+ let frontmatter = try {
+ open ($path | path expand)
+ | split row "---"
+ | get 1
+ | from yaml
+ }
+ if ($frontmatter == null) {
+ let title = try {
+ open ($path | path expand)
+ | split row "#"
+ | get 1
+ | split row "\n"
+ | get 0
+ | str trim
+ }
+ return {title: $title}
+ }
+ return $frontmatter
+} # -> record
diff --git a/features/user/cli/shells/nushell/completion.nu b/features/user/cli/shells/nushell/completion.nu
new file mode 100644
index 0000000..b25d387
--- /dev/null
+++ b/features/user/cli/shells/nushell/completion.nu
@@ -0,0 +1,17 @@
+# Carapace Autocomplete
+let carapace_completer = {|spans|
+ carapace $spans.0 nushell ...$spans | from json
+}
+$env.config = {
+ completions: {
+ case_sensitive: false
+ quick: true
+ partial: true
+ algorithm: fuzzy
+ external: {
+ enable: true
+ max_results: 100
+ completer: $carapace_completer
+ }
+ }
+}
diff --git a/features/user/cli/shells/nushell/default.nix b/features/user/cli/shells/nushell/default.nix
new file mode 100644
index 0000000..7df34e8
--- /dev/null
+++ b/features/user/cli/shells/nushell/default.nix
@@ -0,0 +1,93 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ cfg = config.features.cli.shells.nushell;
+in {
+ options.features.cli.shells.nushell.enable = lib.mkEnableOption "nushell";
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ television
+ gitprompt-rs
+ ];
+ programs.nushell = {
+ enable = true;
+ plugins = with pkgs.nushellPlugins; [
+ gstat
+ ];
+ shellAliases = {
+ "nix-shell" = "nix-shell --command 'SHELL=nu nu'";
+ "cd" = "z";
+ "ze" = "zellij";
+ "bk" = "cd $env.OLDPWD";
+ "fg" = "job unfreeze";
+ };
+ configFile.text =
+ /*
+ nu
+ */
+ ''
+ # Source modularized configs.
+ source ${./prompt.nu}
+ source ${./completion.nu}
+
+ # General Config
+ $env.config = {
+ show_banner: false
+ edit_mode: 'vi'
+
+ history: {
+ isolation: true # Isolate the history of each nushell session
+ file_format: sqlite # Required for isolation
+ }
+ }
+ $env.editor = "hx"
+
+ # go should use a hidden directory
+ $env.gopath = "${config.home.homeDirectory}/.go"
+
+ # Use direnv if present.
+ { ||
+ if (which direnv | is-empty) {
+ return
+ }
+ direnv export json | from json | default {} | load-env
+
+ }
+
+ # define a function to initialize a direnv project
+ def projinit [path?: string] {
+ mut path = $path
+ if ($path == null) {
+ $path = "./"
+ }
+ let path = $path | path expand
+ cp -r ${./direnv-project-template}/* ./
+ direnv allow
+ }
+
+ # Quicker nix shells.
+ def qs [...pkgs] {
+ if $pkgs == [] {
+ print "Please use a package name"
+ return
+ }
+ let pkgs_string = $pkgs
+ | each {|pkg| return $"nixpkgs#($pkg) "}
+ | str join
+ | str trim
+ nu -c $"nix shell ($pkgs_string)"
+ }
+ '';
+ };
+ programs.zoxide.enable = true;
+ programs.carapace.enable = true;
+ programs.direnv = {
+ enable = true;
+ nix-direnv.enable = true;
+ silent = true;
+ };
+ };
+}
diff --git a/features/user/cli/shells/nushell/direnv-project-template/.envrc b/features/user/cli/shells/nushell/direnv-project-template/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/features/user/cli/shells/nushell/direnv-project-template/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/features/user/cli/shells/nushell/direnv-project-template/flake.nix b/features/user/cli/shells/nushell/direnv-project-template/flake.nix
new file mode 100644
index 0000000..4bab14a
--- /dev/null
+++ b/features/user/cli/shells/nushell/direnv-project-template/flake.nix
@@ -0,0 +1,29 @@
+{
+ description = "Project flake";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+ systems.url = "github:nix-systems/default";
+ flake-utils = {
+ url = "github:numtide/flake-utils";
+ inputs.systems.follows = "systems";
+ };
+ };
+
+ outputs = {
+ nixpkgs,
+ flake-utils,
+ ...
+ }:
+ flake-utils.lib.eachDefaultSystem (
+ system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ hello
+ ];
+ };
+ }
+ );
+}
diff --git a/features/user/cli/shells/nushell/prompt.nu b/features/user/cli/shells/nushell/prompt.nu
new file mode 100644
index 0000000..9e04ed5
--- /dev/null
+++ b/features/user/cli/shells/nushell/prompt.nu
@@ -0,0 +1,95 @@
+# +---------------+
+# | Prompt Config |
+# +---------------+
+
+# define segmants
+def path_segment [] {
+ let current_dir = pwd
+ let truncated_current_dir = $current_dir
+ return (pwd | str replace $env.home '~')
+}
+def git_segment [] {
+ return (gitprompt-rs)
+}
+let do_newline = false # whether to do the newline thing
+$env.PROMPT_COMMAND = {||
+ # add a newline at the beginning if it isn't the first prompt
+ mut newline = ""
+ if ($env.CMD_DURATION_MS != '0823') and $do_newline {
+ $newline = "\n"
+ }
+ # define the prompt
+ return $"($newline)(ansi white) ╭ (ansi green_bold)(path_segment)(ansi reset) (git_segment)\n "
+}
+
+# remove right prompt section
+$env.PROMPT_COMMAND_RIGHT = { "" }
+
+# set default prompt indicator
+$env.PROMPT_INDICATOR = $'(ansi white)╰ (ansi red_bold)> '
+# $env.TRANSIENT_PROMPT_INDICATOR = $' (ansi red_bold)> '
+$env.PROMPT_MULTILINE_INDICATOR = " : "
+
+# change prompt to a lambda if in a nix shell
+let in_nix_shell = $env.path | str contains "/nix/store" | any {|el| $el}
+if $in_nix_shell {
+ $env.PROMPT_INDICATOR = $'(ansi white)╰ (ansi red_bold)λ '
+}
+
+# replace vi insert and normal mode prompt indicators with cursor changes
+$env.PROMPT_INDICATOR_VI_NORMAL = $env.PROMPT_INDICATOR
+$env.PROMPT_INDICATOR_VI_INSERT = $env.PROMPT_INDICATOR
+$env.config.cursor_shape.vi_insert = "line"
+$env.config.cursor_shape.vi_normal = "block"
+
+# customize menus to match
+$env.config.menus = [
+ {
+ name: help_menu
+ only_buffer_difference: true # Search is done on the text written after activating the menu
+ marker: $env.PROMPT_INDICATOR # Indicator that appears with the menu is active
+ type: {
+ layout: description # Type of menu
+ columns: 4 # Number of columns where the options are displayed
+ col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
+ col_padding: 2 # Padding between columns
+ selection_rows: 4 # Number of rows allowed to display found options
+ description_rows: 10 # Number of rows allowed to display command description
+ }
+ style: {
+ text: green # Text style
+ selected_text: green_reverse # Text style for selected option
+ description_text: yellow # Text style for description
+ }
+ }
+ {
+ name: completion_menu
+ only_buffer_difference: false # Search is done on the text written after activating the menu
+ marker: $env.PROMPT_INDICATOR # Indicator that appears with the menu is active
+ type: {
+ layout: columnar # Type of menu
+ columns: 4 # Number of columns where the options are displayed
+ col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
+ col_padding: 2 # Padding between columns
+ }
+ style: {
+ text: green # Text style
+ selected_text: green_reverse # Text style for selected option
+ description_text: yellow # Text style for description
+ }
+ }
+ {
+ name: history_menu
+ only_buffer_difference: true # Search is done on the text written after activating the menu
+ marker: $env.PROMPT_INDICATOR # Indicator that appears with the menu is active
+ type: {
+ layout: list # Type of menu
+ page_size: 10 # Number of entries that will presented when activating the menu
+ }
+ style: {
+ text: green # Text style
+ selected_text: green_reverse # Text style for selected option
+ description_text: yellow # Text style for description
+ }
+ }
+]
diff --git a/features/user/cli/utils/git.nix b/features/user/cli/utils/git.nix
new file mode 100644
index 0000000..8d202d1
--- /dev/null
+++ b/features/user/cli/utils/git.nix
@@ -0,0 +1,12 @@
+{ config, lib, ... }: let
+ cfg = config.features.cli.utils.git;
+in {
+ options.features.cli.utils.git.enable = lib.mkEnableOption "git";
+ config = lib.mkIf cfg.enable {
+ programs.git = {
+ enable = true;
+ userEmail = "culsans@vivaldi.net";
+ userName = "culsans";
+ };
+ };
+}
diff --git a/features/user/cli/utils/pandoc.nix b/features/user/cli/utils/pandoc.nix
new file mode 100644
index 0000000..899bd4b
--- /dev/null
+++ b/features/user/cli/utils/pandoc.nix
@@ -0,0 +1,13 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.cli.utils.pandoc;
+in {
+ options.features.cli.utils.pandoc.enable = lib.mkEnableOption "pandoc";
+ config = lib.mkIf cfg.enable {
+ programs.pandoc = {
+ enable = true;
+ };
+ home.packages = with pkgs; [
+ texliveSmall
+ ];
+ };
+}
diff --git a/features/user/cli/utils/ssh.nix b/features/user/cli/utils/ssh.nix
new file mode 100644
index 0000000..f10d0b2
--- /dev/null
+++ b/features/user/cli/utils/ssh.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }: let
+ cfg = config.features.cli.utils.ssh;
+in {
+ options.features.cli.utils.ssh.enable = lib.mkEnableOption "ssh";
+ config = lib.mkIf cfg.enable {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ "culsans.site" = {
+ hostname = "culsans.site";
+ user = "server";
+ port = 2200;
+ };
+ "git.culsans.site" = {
+ hostname = "culsans.site";
+ };
+ };
+ };
+ };
+}
diff --git a/features/user/global/home-manager.nix b/features/user/global/home-manager.nix
new file mode 100644
index 0000000..4be0d07
--- /dev/null
+++ b/features/user/global/home-manager.nix
@@ -0,0 +1,3 @@
+{ ... }: {
+ programs.home-manager.enable = true;
+}
diff --git a/features/user/global/nixpkgs.nix b/features/user/global/nixpkgs.nix
new file mode 100644
index 0000000..b19d5e0
--- /dev/null
+++ b/features/user/global/nixpkgs.nix
@@ -0,0 +1,3 @@
+{...}: {
+ nixpkgs.config.allowUnfree = true;
+}
diff --git a/features/user/gui/apps/foot.nix b/features/user/gui/apps/foot.nix
new file mode 100644
index 0000000..d099e16
--- /dev/null
+++ b/features/user/gui/apps/foot.nix
@@ -0,0 +1,14 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.gui.apps.foot;
+in {
+ options.features.gui.apps.foot.enable = lib.mkEnableOption "foot";
+ config = lib.mkIf cfg.enable {
+ programs.foot = {
+ enable = true;
+ server.enable = true;
+ settings = {
+ main.pad = "0x4";
+ };
+ };
+ };
+}
diff --git a/features/user/gui/apps/librewolf.nix b/features/user/gui/apps/librewolf.nix
new file mode 100644
index 0000000..fc6c2c4
--- /dev/null
+++ b/features/user/gui/apps/librewolf.nix
@@ -0,0 +1,19 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ cfg = config.features.gui.apps.librewolf;
+in {
+ options.features.gui.apps.librewolf.enable = lib.mkEnableOption "librewolf";
+ config = lib.mkIf cfg.enable {
+ # programs.librewolf = {
+ # enable = true;
+ # settings = {
+ # "browser.tabs.inTitlebar" = 0;
+ # };
+ # };
+ home.packages = [ pkgs.librewolf-bin ];
+ };
+}
diff --git a/features/user/gui/apps/mpv.nix b/features/user/gui/apps/mpv.nix
new file mode 100644
index 0000000..562d151
--- /dev/null
+++ b/features/user/gui/apps/mpv.nix
@@ -0,0 +1,28 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.gui.apps.mpv;
+in {
+ options.features.gui.apps.mpv.enable = lib.mkEnableOption "mpv";
+ config = lib.mkIf cfg.enable {
+ programs.mpv = {
+ enable = true;
+ config = {
+ # Change youtube downloader to yt-dlp for faster downloads.
+ script-opts = "ytdl_hook-ytdl_path=${lib.getExe pkgs.yt-dlp}";
+ # Download videos at 720p or lower.
+ ytdl-format = "bestvideo[height<=720]+bestaudio/best[height<=720]";
+
+ save-position-on-quit = false; # makes mpv audio only when true?
+
+ osd-bar = false;
+ };
+ bindings = {
+ "tab" = "script-binding uosc/toggle-ui";
+ };
+ scripts = with pkgs.mpvScripts; [
+ uosc
+ thumbfast
+ sponsorblock
+ ];
+ };
+ };
+}
diff --git a/features/user/gui/apps/obs.nix b/features/user/gui/apps/obs.nix
new file mode 100644
index 0000000..916f11e
--- /dev/null
+++ b/features/user/gui/apps/obs.nix
@@ -0,0 +1,10 @@
+{ config, lib, ... }: let
+ cfg = config.features.gui.apps.obs;
+in {
+ options.features.gui.apps.obs.enable = lib.mkEnableOption "obs";
+ config = lib.mkIf cfg.enable {
+ programs.obs-studio = {
+ enable = true;
+ };
+ };
+}
diff --git a/features/user/gui/apps/qutebrowser/default.nix b/features/user/gui/apps/qutebrowser/default.nix
new file mode 100644
index 0000000..e12c9f3
--- /dev/null
+++ b/features/user/gui/apps/qutebrowser/default.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.gui.apps.qutebrowser;
+in {
+ options.features.gui.apps.qutebrowser.enable = lib.mkEnableOption "qutebrowser";
+ config = lib.mkIf cfg.enable {
+ programs.qutebrowser = {
+ enable = true;
+ settings = {
+ content = {
+ autoplay = false;
+ tls.certificate_errors = "ask-block-thirdparty";
+ };
+ scrolling.bar = "never";
+ window.transparent = true;
+ keyhint.delay = 0;
+ tabs = {
+ position = "left";
+ width = "10%";
+ };
+ url = {
+ default_page = "https://web.tabliss.io";
+ start_pages = "https://web.tabliss.io";
+ };
+ input.insert_mode.auto_load = true;
+ };
+ keyBindings = {
+ normal = {
+ # Tab Movement Keys
+ "<Ctrl-j>" = "tab-move +";
+ "<Ctrl-k>" = "tab-move -";
+
+ # Universal Scrolling Keys
+ "<Alt-j>" = "scroll-px 0 50";
+ "<Alt-k>" = "scroll-px 0 -50";
+
+ # Open Tab Relatively
+ "<Ctrl-o>" = "cmd-set-text -s :open -tr";
+
+ # Move Tab to Another Window
+ "gc" = "cmd-set-text -s :tab-give";
+
+ # Hide UI
+ "z" = lib.mkMerge [
+ "config-cycle tabs.show never always"
+ "config-cycle statusbar.show in-mode always"
+ "config-cycle scrolling.bar never always"
+ ];
+ };
+ };
+ searchEngines = {
+ # Default Search Engine
+ DEFAULT = "https://search.brave.com/search?q={}";
+
+ # General Search Engines
+ g = "https://www.google.com/search?q={}";
+ b = "https://www.bing.com/search?q={}";
+
+ # Other Search Engines
+ w = "https://en.wikipedia.org/wiki/Special:Search?search={}&amp;go=Go&amp;ns0=1";
+ y = "https://youtube.com/results?search_query={}";
+ t = "https://www.wordreference.com/es/translation.asp?tranword={}";
+ p = "https://thangs.com/search/{}?scope=all";
+
+ # Nix Search Engines
+ n = "https://mynixos.com/search?q={}";
+ nw = "https://wiki.nixos.org/index.php?search={}";
+ np = "https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query={}";
+ no = "https://search.nixos.org/options?channel=unstable&size=50&sort=relevance&type=packages&query={}";
+ nh = "https://home-manager-options.extranix.com/?query={}&release=master";
+ };
+ greasemonkey = let
+ mkGmScript = name: ( pkgs.writeText "${name}.js" (builtins.readFile ./scripts/${name}.js) );
+ in [
+ (mkGmScript "yt-ad-skip")
+ (mkGmScript "yt-sponsor-skip")
+ (mkGmScript "yt-shorts-blocker")
+ (mkGmScript "yt-dislike-viewer")
+ ];
+ };
+ };
+}
diff --git a/features/user/gui/apps/qutebrowser/scripts/yt-ad-skip.js b/features/user/gui/apps/qutebrowser/scripts/yt-ad-skip.js
new file mode 100644
index 0000000..56336df
--- /dev/null
+++ b/features/user/gui/apps/qutebrowser/scripts/yt-ad-skip.js
@@ -0,0 +1,29 @@
+// ==UserScript==
+// @name Youtube Ad Skip
+// @version 0.0.7
+// @description Make Youtube more tolerable by automatically skipping ads
+// @author Adcott
+// @match *://*.youtube.com/*
+// ==/UserScript==
+
+GM_addStyle(`
+#player-ads,
+.adDisplay,
+.ad-container,
+.ytd-display-ad-renderer,
+.video-ads,
+ytd-rich-item-renderer:has(ytd-ad-slot-renderer),
+ytd-ad-slot-renderer,
+#masthead-ad,
+*[class^="ytd-ad-"],
+#panels.ytd-watch-flexy {
+ display: none !important;
+}`);
+
+document.addEventListener('load', () => {
+ let ad = document.querySelector('.ad-showing:has(.ytp-ad-persistent-progress-bar-container) video');
+ let skipButton = document.querySelector('.ytp-ad-skip-button');
+
+ if (ad) ad.currentTime = 99999;
+ if (skipButton) skipButton.click();
+}, true);
diff --git a/features/user/gui/apps/qutebrowser/scripts/yt-dislike-viewer.js b/features/user/gui/apps/qutebrowser/scripts/yt-dislike-viewer.js
new file mode 100644
index 0000000..50f3eff
--- /dev/null
+++ b/features/user/gui/apps/qutebrowser/scripts/yt-dislike-viewer.js
@@ -0,0 +1,704 @@
+// ==UserScript==
+// @name Return YouTube Dislike
+// @namespace https://www.returnyoutubedislike.com/
+// @homepage https://www.returnyoutubedislike.com/
+// @version 3.1.5
+// @encoding utf-8
+// @description Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/
+// @icon https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png
+// @author Anarios & JRWR
+// @match *://*.youtube.com/*
+// @exclude *://music.youtube.com/*
+// @exclude *://*.music.youtube.com/*
+// @compatible chrome
+// @compatible firefox
+// @compatible opera
+// @compatible safari
+// @compatible edge
+// @grant GM.xmlHttpRequest
+// @connect youtube.com
+// @grant GM_addStyle
+// @run-at document-end
+// @downloadURL https://update.greasyfork.org/scripts/436115/Return%20YouTube%20Dislike.user.js
+// @updateURL https://update.greasyfork.org/scripts/436115/Return%20YouTube%20Dislike.meta.js
+// ==/UserScript==
+
+const extConfig = {
+ // BEGIN USER OPTIONS
+ // You may change the following variables to allowed values listed in the corresponding brackets (* means default). Keep the style and keywords intact.
+ showUpdatePopup: false, // [true, false*] Show a popup tab after extension update (See what's new)
+ disableVoteSubmission: false, // [true, false*] Disable like/dislike submission (Stops counting your likes and dislikes)
+ disableLogging: true, // [true*, false] Disable Logging API Response in JavaScript Console.
+ coloredThumbs: false, // [true, false*] Colorize thumbs (Use custom colors for thumb icons)
+ coloredBar: false, // [true, false*] Colorize ratio bar (Use custom colors for ratio bar)
+ colorTheme: "classic", // [classic*, accessible, neon] Color theme (red/green, blue/yellow, pink/cyan)
+ numberDisplayFormat: "compactShort", // [compactShort*, compactLong, standard] Number format (For non-English locale users, you may be able to improve appearance with a different option. Please file a feature request if your locale is not covered)
+ numberDisplayRoundDown: true, // [true*, false] Round down numbers (Show rounded down numbers)
+ tooltipPercentageMode: "none", // [none*, dash_like, dash_dislike, both, only_like, only_dislike] Mode of showing percentage in like/dislike bar tooltip.
+ numberDisplayReformatLikes: false, // [true, false*] Re-format like numbers (Make likes and dislikes format consistent)
+ rateBarEnabled: false, // [true, false*] Enables ratio bar under like/dislike buttons
+ // END USER OPTIONS
+};
+
+const LIKED_STATE = "LIKED_STATE";
+const DISLIKED_STATE = "DISLIKED_STATE";
+const NEUTRAL_STATE = "NEUTRAL_STATE";
+let previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL
+let likesvalue = 0;
+let dislikesvalue = 0;
+let preNavigateLikeButton = null;
+
+let isMobile = location.hostname == "m.youtube.com";
+let isShorts = () => location.pathname.startsWith("/shorts");
+let mobileDislikes = 0;
+function cLog(text, subtext = "") {
+ if (!extConfig.disableLogging) {
+ subtext = subtext.trim() === "" ? "" : `(${subtext})`;
+ console.log(`[Return YouTube Dislikes] ${text} ${subtext}`);
+ }
+}
+
+function isInViewport(element) {
+ const rect = element.getBoundingClientRect();
+ const height = innerHeight || document.documentElement.clientHeight;
+ const width = innerWidth || document.documentElement.clientWidth;
+ return (
+ // When short (channel) is ignored, the element (like/dislike AND short itself) is
+ // hidden with a 0 DOMRect. In this case, consider it outside of Viewport
+ !(rect.top == 0 && rect.left == 0 && rect.bottom == 0 && rect.right == 0) &&
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= height &&
+ rect.right <= width
+ );
+}
+
+function getButtons() {
+ if (isShorts()) {
+ let elements = document.querySelectorAll(
+ isMobile ? "ytm-like-button-renderer" : "#like-button > ytd-like-button-renderer",
+ );
+ for (let element of elements) {
+ if (isInViewport(element)) {
+ return element;
+ }
+ }
+ }
+ if (isMobile) {
+ return (
+ document.querySelector(".slim-video-action-bar-actions .segmented-buttons") ??
+ document.querySelector(".slim-video-action-bar-actions")
+ );
+ }
+ if (document.getElementById("menu-container")?.offsetParent === null) {
+ return (
+ document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div") ??
+ document.querySelector("ytd-menu-renderer.ytd-video-primary-info-renderer > div")
+ );
+ } else {
+ return document.getElementById("menu-container")?.querySelector("#top-level-buttons-computed");
+ }
+}
+
+function getDislikeButton() {
+ if (getButtons().children[0].tagName === "YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER") {
+ if (getButtons().children[0].children[1] === undefined) {
+ return document.querySelector("#segmented-dislike-button");
+ } else {
+ return getButtons().children[0].children[1];
+ }
+ } else {
+ if (getButtons().querySelector("segmented-like-dislike-button-view-model")) {
+ const dislikeViewModel = getButtons().querySelector("dislike-button-view-model");
+ if (!dislikeViewModel) cLog("Dislike button wasn't added to DOM yet...");
+ return dislikeViewModel;
+ } else {
+ return getButtons().children[1];
+ }
+ }
+}
+
+function getLikeButton() {
+ return getButtons().children[0].tagName === "YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER"
+ ? document.querySelector("#segmented-like-button") !== null
+ ? document.querySelector("#segmented-like-button")
+ : getButtons().children[0].children[0]
+ : getButtons().querySelector("like-button-view-model") ?? getButtons().children[0];
+}
+
+function getLikeTextContainer() {
+ return (
+ getLikeButton().querySelector("#text") ??
+ getLikeButton().getElementsByTagName("yt-formatted-string")[0] ??
+ getLikeButton().querySelector("span[role='text']")
+ );
+}
+
+function getDislikeTextContainer() {
+ const dislikeButton = getDislikeButton();
+ let result =
+ dislikeButton?.querySelector("#text") ??
+ dislikeButton?.getElementsByTagName("yt-formatted-string")[0] ??
+ dislikeButton?.querySelector("span[role='text']");
+ if (result === null) {
+ let textSpan = document.createElement("span");
+ textSpan.id = "text";
+ textSpan.style.marginLeft = "6px";
+ dislikeButton?.querySelector("button").appendChild(textSpan);
+ if (dislikeButton) dislikeButton.querySelector("button").style.width = "auto";
+ result = textSpan;
+ }
+ return result;
+}
+
+function createObserver(options, callback) {
+ const observerWrapper = new Object();
+ observerWrapper.options = options;
+ observerWrapper.observer = new MutationObserver(callback);
+ observerWrapper.observe = function (element) {
+ this.observer.observe(element, this.options);
+ };
+ observerWrapper.disconnect = function () {
+ this.observer.disconnect();
+ };
+ return observerWrapper;
+}
+
+let shortsObserver = null;
+
+if (isShorts() && !shortsObserver) {
+ cLog("Initializing shorts mutation observer");
+ shortsObserver = createObserver(
+ {
+ attributes: true,
+ },
+ (mutationList) => {
+ mutationList.forEach((mutation) => {
+ if (
+ mutation.type === "attributes" &&
+ mutation.target.nodeName === "TP-YT-PAPER-BUTTON" &&
+ mutation.target.id === "button"
+ ) {
+ cLog("Short thumb button status changed");
+ if (mutation.target.getAttribute("aria-pressed") === "true") {
+ mutation.target.style.color =
+ mutation.target.parentElement.parentElement.id === "like-button"
+ ? getColorFromTheme(true)
+ : getColorFromTheme(false);
+ } else {
+ mutation.target.style.color = "unset";
+ }
+ return;
+ }
+ cLog("Unexpected mutation observer event: " + mutation.target + mutation.type);
+ });
+ },
+ );
+}
+
+function isVideoLiked() {
+ if (isMobile) {
+ return getLikeButton().querySelector("button").getAttribute("aria-label") == "true";
+ }
+ return getLikeButton().classList.contains("style-default-active");
+}
+
+function isVideoDisliked() {
+ if (isMobile) {
+ return getDislikeButton()?.querySelector("button").getAttribute("aria-label") == "true";
+ }
+ return getDislikeButton()?.classList.contains("style-default-active");
+}
+
+function isVideoNotLiked() {
+ if (isMobile) {
+ return !isVideoLiked();
+ }
+ return getLikeButton().classList.contains("style-text");
+}
+
+function isVideoNotDisliked() {
+ if (isMobile) {
+ return !isVideoDisliked();
+ }
+ return getDislikeButton()?.classList.contains("style-text");
+}
+
+function checkForUserAvatarButton() {
+ if (isMobile) {
+ return;
+ }
+ if (document.querySelector("#avatar-btn")) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function getState() {
+ if (isVideoLiked()) {
+ return LIKED_STATE;
+ }
+ if (isVideoDisliked()) {
+ return DISLIKED_STATE;
+ }
+ return NEUTRAL_STATE;
+}
+
+function setLikes(likesCount) {
+ if (isMobile) {
+ getButtons().children[0].querySelector(".button-renderer-text").innerText = likesCount;
+ return;
+ }
+ getLikeTextContainer().innerText = likesCount;
+}
+
+function setDislikes(dislikesCount) {
+ if (isMobile) {
+ mobileDislikes = dislikesCount;
+ return;
+ }
+
+ const _container = getDislikeTextContainer();
+ _container?.removeAttribute("is-empty");
+ if (_container?.innerText !== dislikesCount) {
+ _container.innerText = dislikesCount;
+ }
+}
+
+function getLikeCountFromButton() {
+ try {
+ if (isShorts()) {
+ //Youtube Shorts don't work with this query. It's not necessary; we can skip it and still see the results.
+ //It should be possible to fix this function, but it's not critical to showing the dislike count.
+ return false;
+ }
+ let likeButton =
+ getLikeButton().querySelector("yt-formatted-string#text") ?? getLikeButton().querySelector("button");
+
+ let likesStr = likeButton.getAttribute("aria-label").replace(/\D/g, "");
+ return likesStr.length > 0 ? parseInt(likesStr) : false;
+ } catch {
+ return false;
+ }
+}
+
+(typeof GM_addStyle != "undefined"
+ ? GM_addStyle
+ : (styles) => {
+ let styleNode = document.createElement("style");
+ styleNode.type = "text/css";
+ styleNode.innerText = styles;
+ document.head.appendChild(styleNode);
+ })(`
+ #return-youtube-dislike-bar-container {
+ background: var(--yt-spec-icon-disabled);
+ border-radius: 2px;
+ }
+
+ #return-youtube-dislike-bar {
+ background: var(--yt-spec-text-primary);
+ border-radius: 2px;
+ transition: all 0.15s ease-in-out;
+ }
+
+ .ryd-tooltip {
+ position: absolute;
+ display: block;
+ height: 2px;
+ bottom: -10px;
+ }
+
+ .ryd-tooltip-bar-container {
+ width: 100%;
+ height: 2px;
+ position: absolute;
+ padding-top: 6px;
+ padding-bottom: 12px;
+ top: -6px;
+ }
+
+ ytd-menu-renderer.ytd-watch-metadata {
+ overflow-y: visible !important;
+ }
+
+ #top-level-buttons-computed {
+ position: relative !important;
+ }
+ `);
+
+function createRateBar(likes, dislikes) {
+ if (isMobile || !extConfig.rateBarEnabled) {
+ return;
+ }
+ let rateBar = document.getElementById("return-youtube-dislike-bar-container");
+
+ const widthPx = getLikeButton().clientWidth + (getDislikeButton()?.clientWidth ?? 52);
+
+ const widthPercent = likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
+
+ var likePercentage = parseFloat(widthPercent.toFixed(1));
+ const dislikePercentage = (100 - likePercentage).toLocaleString();
+ likePercentage = likePercentage.toLocaleString();
+
+ var tooltipInnerHTML;
+ switch (extConfig.tooltipPercentageMode) {
+ case "dash_like":
+ tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}&nbsp;&nbsp;-&nbsp;&nbsp;${likePercentage}%`;
+ break;
+ case "dash_dislike":
+ tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}&nbsp;&nbsp;-&nbsp;&nbsp;${dislikePercentage}%`;
+ break;
+ case "both":
+ tooltipInnerHTML = `${likePercentage}%&nbsp;/&nbsp;${dislikePercentage}%`;
+ break;
+ case "only_like":
+ tooltipInnerHTML = `${likePercentage}%`;
+ break;
+ case "only_dislike":
+ tooltipInnerHTML = `${dislikePercentage}%`;
+ break;
+ default:
+ tooltipInnerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
+ }
+
+ if (!rateBar && !isMobile) {
+ let colorLikeStyle = "";
+ let colorDislikeStyle = "";
+ if (extConfig.coloredBar) {
+ colorLikeStyle = "; background-color: " + getColorFromTheme(true);
+ colorDislikeStyle = "; background-color: " + getColorFromTheme(false);
+ }
+
+ getButtons().insertAdjacentHTML(
+ "beforeend",
+ `
+ <div class="ryd-tooltip" style="width: ${widthPx}px">
+ <div class="ryd-tooltip-bar-container">
+ <div
+ id="return-youtube-dislike-bar-container"
+ style="width: 100%; height: 2px;${colorDislikeStyle}"
+ >
+ <div
+ id="return-youtube-dislike-bar"
+ style="width: ${widthPercent}%; height: 100%${colorDislikeStyle}"
+ ></div>
+ </div>
+ </div>
+ <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
+ <!--css-build:shady-->${tooltipInnerHTML}
+ </tp-yt-paper-tooltip>
+ </div>
+`,
+ );
+ let descriptionAndActionsElement = document.getElementById("top-row");
+ descriptionAndActionsElement.style.borderBottom = "1px solid var(--yt-spec-10-percent-layer)";
+ descriptionAndActionsElement.style.paddingBottom = "10px";
+ } else {
+ document.querySelector(".ryd-tooltip").style.width = widthPx + "px";
+ document.getElementById("return-youtube-dislike-bar").style.width = widthPercent + "%";
+
+ if (extConfig.coloredBar) {
+ document.getElementById("return-youtube-dislike-bar-container").style.backgroundColor = getColorFromTheme(false);
+ document.getElementById("return-youtube-dislike-bar").style.backgroundColor = getColorFromTheme(true);
+ }
+ }
+}
+
+function setState() {
+ cLog("Fetching votes...");
+ let statsSet = false;
+
+ fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`).then((response) => {
+ response.json().then((json) => {
+ if (json && !("traceId" in response) && !statsSet) {
+ const { dislikes, likes } = json;
+ cLog(`Received count: ${dislikes}`);
+ likesvalue = likes;
+ dislikesvalue = dislikes;
+ setDislikes(numberFormat(dislikes));
+ if (extConfig.numberDisplayReformatLikes === true) {
+ const nativeLikes = getLikeCountFromButton();
+ if (nativeLikes !== false) {
+ setLikes(numberFormat(nativeLikes));
+ }
+ }
+ createRateBar(likes, dislikes);
+ if (extConfig.coloredThumbs === true) {
+ const dislikeButton = getDislikeButton();
+ if (isShorts()) {
+ // for shorts, leave deactived buttons in default color
+ const shortLikeButton = getLikeButton().querySelector("tp-yt-paper-button#button");
+ const shortDislikeButton = dislikeButton?.querySelector("tp-yt-paper-button#button");
+ if (shortLikeButton.getAttribute("aria-pressed") === "true") {
+ shortLikeButton.style.color = getColorFromTheme(true);
+ }
+ if (shortDislikeButton && shortDislikeButton.getAttribute("aria-pressed") === "true") {
+ shortDislikeButton.style.color = getColorFromTheme(false);
+ }
+ shortsObserver.observe(shortLikeButton);
+ shortsObserver.observe(shortDislikeButton);
+ } else {
+ getLikeButton().style.color = getColorFromTheme(true);
+ if (dislikeButton) dislikeButton.style.color = getColorFromTheme(false);
+ }
+ }
+ }
+ });
+ });
+}
+
+function updateDOMDislikes() {
+ setDislikes(numberFormat(dislikesvalue));
+ createRateBar(likesvalue, dislikesvalue);
+}
+
+function likeClicked() {
+ if (checkForUserAvatarButton() == true) {
+ if (previousState == 1) {
+ likesvalue--;
+ updateDOMDislikes();
+ previousState = 3;
+ } else if (previousState == 2) {
+ likesvalue++;
+ dislikesvalue--;
+ updateDOMDislikes();
+ previousState = 1;
+ } else if (previousState == 3) {
+ likesvalue++;
+ updateDOMDislikes();
+ previousState = 1;
+ }
+ if (extConfig.numberDisplayReformatLikes === true) {
+ const nativeLikes = getLikeCountFromButton();
+ if (nativeLikes !== false) {
+ setLikes(numberFormat(nativeLikes));
+ }
+ }
+ }
+}
+
+function dislikeClicked() {
+ if (checkForUserAvatarButton() == true) {
+ if (previousState == 3) {
+ dislikesvalue++;
+ updateDOMDislikes();
+ previousState = 2;
+ } else if (previousState == 2) {
+ dislikesvalue--;
+ updateDOMDislikes();
+ previousState = 3;
+ } else if (previousState == 1) {
+ likesvalue--;
+ dislikesvalue++;
+ updateDOMDislikes();
+ previousState = 2;
+ if (extConfig.numberDisplayReformatLikes === true) {
+ const nativeLikes = getLikeCountFromButton();
+ if (nativeLikes !== false) {
+ setLikes(numberFormat(nativeLikes));
+ }
+ }
+ }
+ }
+}
+
+function setInitialState() {
+ setState();
+}
+
+function getVideoId() {
+ const urlObject = new URL(window.location.href);
+ const pathname = urlObject.pathname;
+ if (pathname.startsWith("/clip")) {
+ return (document.querySelector("meta[itemprop='videoId']") || document.querySelector("meta[itemprop='identifier']")).content;
+ } else {
+ if (pathname.startsWith("/shorts")) {
+ return pathname.slice(8);
+ }
+ return urlObject.searchParams.get("v");
+ }
+}
+
+function isVideoLoaded() {
+ if (isMobile) {
+ return document.getElementById("player").getAttribute("loading") == "false";
+ }
+ const videoId = getVideoId();
+
+ return (
+ // desktop: spring 2024 UI
+ document.querySelector(`ytd-watch-grid[video-id='${videoId}']`) !== null ||
+ // desktop: older UI
+ document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null ||
+ // mobile: no video-id attribute
+ document.querySelector('#player[loading="false"]:not([hidden])') !== null
+ );
+}
+
+function roundDown(num) {
+ if (num < 1000) return num;
+ const int = Math.floor(Math.log10(num) - 2);
+ const decimal = int + (int % 3 ? 1 : 0);
+ const value = Math.floor(num / 10 ** decimal);
+ return value * 10 ** decimal;
+}
+
+function numberFormat(numberState) {
+ let numberDisplay;
+ if (extConfig.numberDisplayRoundDown === false) {
+ numberDisplay = numberState;
+ } else {
+ numberDisplay = roundDown(numberState);
+ }
+ return getNumberFormatter(extConfig.numberDisplayFormat).format(numberDisplay);
+}
+
+function getNumberFormatter(optionSelect) {
+ let userLocales;
+ if (document.documentElement.lang) {
+ userLocales = document.documentElement.lang;
+ } else if (navigator.language) {
+ userLocales = navigator.language;
+ } else {
+ try {
+ userLocales = new URL(
+ Array.from(document.querySelectorAll("head > link[rel='search']"))
+ ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
+ ?.getAttribute("href"),
+ )?.searchParams?.get("locale");
+ } catch {
+ cLog("Cannot find browser locale. Use en as default for number formatting.");
+ userLocales = "en";
+ }
+ }
+
+ let formatterNotation;
+ let formatterCompactDisplay;
+ switch (optionSelect) {
+ case "compactLong":
+ formatterNotation = "compact";
+ formatterCompactDisplay = "long";
+ break;
+ case "standard":
+ formatterNotation = "standard";
+ formatterCompactDisplay = "short";
+ break;
+ case "compactShort":
+ default:
+ formatterNotation = "compact";
+ formatterCompactDisplay = "short";
+ }
+
+ const formatter = Intl.NumberFormat(userLocales, {
+ notation: formatterNotation,
+ compactDisplay: formatterCompactDisplay,
+ });
+ return formatter;
+}
+
+function getColorFromTheme(voteIsLike) {
+ let colorString;
+ switch (extConfig.colorTheme) {
+ case "accessible":
+ if (voteIsLike === true) {
+ colorString = "dodgerblue";
+ } else {
+ colorString = "gold";
+ }
+ break;
+ case "neon":
+ if (voteIsLike === true) {
+ colorString = "aqua";
+ } else {
+ colorString = "magenta";
+ }
+ break;
+ case "classic":
+ default:
+ if (voteIsLike === true) {
+ colorString = "lime";
+ } else {
+ colorString = "red";
+ }
+ }
+ return colorString;
+}
+
+let smartimationObserver = null;
+
+function setEventListeners(evt) {
+ let jsInitChecktimer;
+
+ function checkForJS_Finish() {
+ //console.log();
+ if (isShorts() || (getButtons()?.offsetParent && isVideoLoaded())) {
+ const buttons = getButtons();
+ const dislikeButton = getDislikeButton();
+
+ if (preNavigateLikeButton !== getLikeButton() && dislikeButton) {
+ cLog("Registering button listeners...");
+ try {
+ getLikeButton().addEventListener("click", likeClicked);
+ dislikeButton?.addEventListener("click", dislikeClicked);
+ getLikeButton().addEventListener("touchstart", likeClicked);
+ dislikeButton?.addEventListener("touchstart", dislikeClicked);
+ dislikeButton?.addEventListener("focusin", updateDOMDislikes);
+ dislikeButton?.addEventListener("focusout", updateDOMDislikes);
+ preNavigateLikeButton = getLikeButton();
+
+ if (!smartimationObserver) {
+ smartimationObserver = createObserver(
+ {
+ attributes: true,
+ subtree: true,
+ childList: true,
+ },
+ updateDOMDislikes,
+ );
+ smartimationObserver.container = null;
+ }
+
+ const smartimationContainer = buttons.querySelector("yt-smartimation");
+ if (smartimationContainer && smartimationObserver.container != smartimationContainer) {
+ cLog("Initializing smartimation mutation observer");
+ smartimationObserver.disconnect();
+ smartimationObserver.observe(smartimationContainer);
+ smartimationObserver.container = smartimationContainer;
+ }
+ } catch {
+ return;
+ } //Don't spam errors into the console
+ }
+ if (dislikeButton) {
+ setInitialState();
+ clearInterval(jsInitChecktimer);
+ }
+ }
+ }
+
+ cLog("Setting up...");
+ jsInitChecktimer = setInterval(checkForJS_Finish, 111);
+}
+
+(function () {
+ "use strict";
+ window.addEventListener("yt-navigate-finish", setEventListeners, true);
+ setEventListeners();
+})();
+if (isMobile) {
+ let originalPush = history.pushState;
+ history.pushState = function (...args) {
+ window.returnDislikeButtonlistenersSet = false;
+ setEventListeners(args[2]);
+ return originalPush.apply(history, args);
+ };
+ setInterval(() => {
+ const dislikeButton = getDislikeButton();
+ if (dislikeButton?.querySelector(".button-renderer-text") === null) {
+ getDislikeTextContainer().innerText = mobileDislikes;
+ } else {
+ if (dislikeButton) dislikeButton.querySelector(".button-renderer-text").innerText = mobileDislikes;
+ }
+ }, 1000);
+}
diff --git a/features/user/gui/apps/qutebrowser/scripts/yt-shorts-blocker.js b/features/user/gui/apps/qutebrowser/scripts/yt-shorts-blocker.js
new file mode 100644
index 0000000..383fec3
--- /dev/null
+++ b/features/user/gui/apps/qutebrowser/scripts/yt-shorts-blocker.js
@@ -0,0 +1,23 @@
+// ==UserScript==
+// @name YouTube Shorts Blocker
+// @namespace http://tampermonkey.net/
+// @version 0.1.2
+// @description Blocks the YouTube shorts from appearing.
+// @author Aiden Charles
+// @license MIT
+// @match https://www.youtube.com/*
+// @require https://code.jquery.com/jquery-3.4.1.slim.min.js
+// @grant none
+// ==/UserScript==
+
+(function() {
+ console.log("YouTube Shorts blocker script is running!");
+
+ setInterval(function() {
+ $("ytd-reel-shelf-renderer").hide();
+ $("a[title='Shorts']").hide();
+ $('a[href^="/shorts/"]').closest('ytd-video-renderer').hide();
+ $('span:contains("Shorts")').closest('#content.ytd-rich-section-renderer').hide();
+ }, 1000);
+})();
+
diff --git a/features/user/gui/apps/qutebrowser/scripts/yt-sponsor-skip.js b/features/user/gui/apps/qutebrowser/scripts/yt-sponsor-skip.js
new file mode 100644
index 0000000..3779cbf
--- /dev/null
+++ b/features/user/gui/apps/qutebrowser/scripts/yt-sponsor-skip.js
@@ -0,0 +1,51 @@
+// ==UserScript==
+// @name Sponsorblock
+// @version 1.1.0
+// @description Skip sponsor segments automatically
+// @author afreakk
+// @author vongaisberg
+// @match *://*.youtube.com/*
+// @exclude *://*.youtube.com/subscribe_embed?*
+// ==/UserScript==
+const delay = 1000;
+
+const tryFetchSkipSegments = (videoID) =>
+
+ fetch(`https://sponsor.ajay.app/api/skipSegments?videoID=${videoID}`)
+ .then((r) => r.json())
+ .then((rJson) =>
+ rJson.filter((a) => a.actionType === 'skip').map((a) => a.segment)
+ )
+ .catch(
+ (e) =>
+ console.log(
+ `Sponsorblock: failed fetching skipSegments for ${videoID}, reason: ${e}`
+ ) || []
+ );
+
+const skipSegments = async () => {
+ const videoID = new URL(document.location).searchParams.get('v');
+ if (!videoID) {
+ return;
+ }
+ const key = `segmentsToSkip-${videoID}`;
+ window[key] = window[key] || (await tryFetchSkipSegments(videoID));
+ for (const v of document.querySelectorAll('video')) {
+ if (Number.isNaN(v.duration)) continue;
+ for (const [start, end] of window[key]) {
+ if (v.currentTime < end && v.currentTime >= start) {
+ console.log(`Sponsorblock: skipped video @${v.currentTime} from ${start} to ${end}`);
+ v.currentTime = end;
+ return
+ }
+ const timeToSponsor = (start - v.currentTime) / v.playbackRate;
+ if (v.currentTime < start && timeToSponsor < (delay / 1000)) {
+ console.log(`Sponsorblock: Almost at sponsor segment, sleep for ${timeToSponsor * 1000}ms`);
+ setTimeout(skipSegments, timeToSponsor * 1000);
+ }
+ }
+ }
+};
+if (!window.skipSegmentsIntervalID) {
+ window.skipSegmentsIntervalID = setInterval(skipSegments, delay);
+}
diff --git a/features/user/gui/apps/vesktop.nix b/features/user/gui/apps/vesktop.nix
new file mode 100644
index 0000000..f28d719
--- /dev/null
+++ b/features/user/gui/apps/vesktop.nix
@@ -0,0 +1,8 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.gui.apps.vesktop;
+in {
+ options.features.gui.apps.vesktop.enable = lib.mkEnableOption "vesktop";
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [(vesktop.override { withSystemVencord = false; })];
+ };
+}
diff --git a/features/user/gui/bundles/video.nix b/features/user/gui/bundles/video.nix
new file mode 100644
index 0000000..491bf21
--- /dev/null
+++ b/features/user/gui/bundles/video.nix
@@ -0,0 +1,11 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.gui.bundles.video;
+in {
+ options.features.gui.bundles.video.enable = lib.mkEnableOption "video";
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ kdePackages.kdenlive
+ ];
+ features.gui.apps.obs.enable = true;
+ };
+}
diff --git a/features/user/gui/desktops/niri/default.nix b/features/user/gui/desktops/niri/default.nix
new file mode 100755
index 0000000..cd904de
--- /dev/null
+++ b/features/user/gui/desktops/niri/default.nix
@@ -0,0 +1,126 @@
+{
+ config,
+ pkgs,
+ lib,
+ inputs,
+ ...
+}: let
+ cfg = config.features.gui.desktops.niri;
+ aes = config.aesthetics;
+in {
+ imports = [
+ inputs.niri.homeModules.niri
+ ./keybinds.nix
+ ];
+ options.features.gui.desktops.niri.enable = lib.mkEnableOption "niri";
+ config = lib.mkIf cfg.enable {
+ features.gui.desktops.niri.parts = {
+ waybar.enable = true;
+ fuzzel.enable = true;
+ selectors.enable = true;
+ swww.enable = true;
+ mako.enable = true;
+ ignis.enable = true;
+ hyprlock.enable = true;
+ };
+ programs.niri = {
+ enable = true;
+ package = inputs.niri.packages.${pkgs.system}.niri-unstable;
+ settings = {
+ outputs = {
+ "Samsung Electric Company SAMSUNG 0x00000001" = {
+ enable = true;
+ scale = 1.5;
+ };
+ };
+ spawn-at-startup = [
+ # Status Bar
+ {command = ["waybar"];}
+
+ # Wallpaper Daemon
+ {command = ["swww-daemon"];}
+
+ # Allows x apps to be used in wayland.
+ {command = ["${lib.getExe pkgs.xwayland-satellite}"];}
+
+ # Logs the clipboard for use in utilities.
+ {command = ["${pkgs.wl-clipboard}/bin/wl-paste" "--watch" "${pkgs.cliphist}/bin/cliphist" "store"];}
+ ];
+ environment = {
+ DISPLAY = ":0"; # Important for Xwayland.
+ };
+ window-rules = [
+ {
+ geometry-corner-radius = let
+ radius = 4.0;
+ in {
+ top-left = radius;
+ top-right = radius;
+ bottom-left = radius;
+ bottom-right = radius;
+ };
+ clip-to-geometry = true;
+ default-column-width.proportion = 1. / 3.;
+ }
+ {
+ # Prevent Tor from being screen captured.
+ matches = [{app-id = "Tor Browser";}];
+ block-out-from = "screen-capture";
+ }
+ ];
+ switch-events = {
+ lid-close.action.spawn = ["hyprlock"];
+ };
+ prefer-no-csd = true;
+ overview = {
+ backdrop-color = "#${aes.scheme.base01}";
+ };
+ layout = {
+ gaps = 14;
+ insert-hint.enable = false;
+ shadow = {
+ enable = true;
+ softness = 10;
+ spread = 5;
+ offset = {
+ x = 0;
+ y = 0;
+ };
+ };
+ focus-ring = {
+ enable = true;
+ width = 3;
+ active.color = "#${aes.scheme.base09}";
+ };
+ border = {
+ enable = false;
+ width = 3;
+ inactive.color = "#${aes.scheme.base03}";
+ active.color = "#${aes.scheme.base08}";
+ };
+ struts = {
+ # left = -1;
+ # right = -1;
+
+ left = 20;
+ right = 20;
+ top = 20;
+ bottom = 20;
+ };
+ always-center-single-column = false;
+ empty-workspace-above-first = true;
+ };
+ input.keyboard.xkb.options = ''
+ caps:escape,
+ compose:ins
+ '';
+ hotkey-overlay.skip-at-startup = true;
+ input = {
+ touchpad = {
+ click-method = "clickfinger";
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/keybinds.nix b/features/user/gui/desktops/niri/keybinds.nix
new file mode 100644
index 0000000..5426ee6
--- /dev/null
+++ b/features/user/gui/desktops/niri/keybinds.nix
@@ -0,0 +1,165 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: {
+ config = lib.mkIf config.features.gui.desktops.niri.enable {
+ programs.niri.settings.binds = let
+ left = "h";
+ down = "j";
+ up = "k";
+ right = "l";
+ in {
+ # App Launching Keys
+ "Mod+Q".action.spawn = ["footclient"];
+ "Mod+W".action.spawn = ["qutebrowser"];
+ "Mod+Shift+W".action.spawn = ["librewolf"];
+ "Mod+E".action.spawn = ["neovide"];
+
+ # Clear Notifications
+ "Mod+B".action.spawn = ["makoctl" "dismiss" "-a"];
+
+ # Selectors
+ "Mod+R".action.spawn = ["fuzzel"];
+ "Mod+T".action.spawn = ["tool-selector"];
+ "Mod+Y".action.spawn = ["clipboard-selector"];
+ "Mod+U".action.spawn = ["wallpaper-selector" "--all-outputs"];
+ "Mod+Shift+U".action.spawn = ["wallpaper-selector"];
+ "Mod+Control+U".action.spawn = ["wallpaper-selector" "--randomize" "--all-outputs"];
+ "Mod+Control+Shift+U".action.spawn = ["wallpaper-selector" "--randomize"];
+
+ # Screenshot Keys
+ "Mod+P".action.screenshot = [];
+ "Shift+Mod+P".action.screenshot-screen = [];
+ "Control+Mod+P".action.screenshot-window = [];
+
+ # Power Keys
+ "Mod+comma".action.spawn = ["${lib.getExe (pkgs.writers.writeNuBin "nirilock" "systemctl suspend ; hyprlock")}"];
+
+ # Horizontal Tiling Keys
+ "Mod+A".action.maximize-column = [];
+ "Mod+S".action.switch-preset-column-width = [];
+
+ # Vertical Tiling Keys
+ "Mod+Shift+A".action.reset-window-height = [];
+ "Mod+Shift+S".action.switch-preset-window-height = [];
+ "Mod+D".action.consume-or-expel-window-right = [];
+
+ # Floating Window Management Keys
+ "Mod+Z".action.switch-focus-between-floating-and-tiling = [];
+ "Mod+X".action.toggle-window-floating = [];
+
+ # Other Window Management Keys
+ "Mod+C".action.close-window = [];
+ "Mod+V".action.fullscreen-window = [];
+
+ # Overlay Keys
+ "Mod+F".action.toggle-overview = [];
+
+ # +---------------------+
+ # | Arrow Movement Keys |
+ # +---------------------+
+
+ # Window Focus Keys
+ "Mod+Left".action.focus-column-left = [];
+ "Mod+Right".action.focus-column-right = [];
+ "Mod+Up".action.focus-window-up = [];
+ "Mod+Down".action.focus-window-down = [];
+
+ # Monitor Focus Keys
+ "Mod+Shift+Left".action.focus-monitor-left = [];
+ "Mod+Shift+Right".action.focus-monitor-right = [];
+
+ # Workspace Focus Keys
+ "Mod+Shift+Up".action.focus-workspace-up = [];
+ "Mod+Shift+Down".action.focus-workspace-down = [];
+
+ # Window Motion Keys
+ "Mod+Control+Left".action.move-column-left = [];
+ "Mod+Control+Right".action.move-column-right = [];
+ "Mod+Control+Up".action.move-window-up = [];
+ "Mod+Control+Down".action.move-window-down = [];
+
+ # Window - Monitor Motion Keys
+ "Mod+Control+Shift+Left".action.move-column-to-monitor-left = [];
+ "Mod+Control+Shift+Right".action.move-column-to-monitor-right = [];
+
+ # Window - Workspace Motion Keys
+ "Mod+Control+Shift+Up".action.move-window-to-workspace-up = [];
+ "Mod+Control+Shift+Down".action.move-window-to-workspace-down = [];
+
+ # Workspace Motion Keys
+ "Mod+Alt+Shift+Up".action.move-workspace-up = [];
+ "Mod+Alt+Shift+Down".action.move-workspace-down = [];
+
+ # Workspace - Monitor Motion Keys
+ "Mod+Alt+Shift+Left".action.move-workspace-to-monitor-left = [];
+ "Mod+Alt+Shift+Right".action.move-workspace-to-monitor-right = [];
+
+ # +-------------------+
+ # | Vim Movement Keys |
+ # +-------------------+
+
+ # Window Focus Keys
+ "Mod+${left}".action.focus-column-left = [];
+ "Mod+${right}".action.focus-column-right = [];
+ "Mod+${up}".action.focus-window-up = [];
+ "Mod+${down}".action.focus-window-down = [];
+
+ # Monitor Focus Keys
+ "Mod+Shift+${left}".action.focus-monitor-left = [];
+ "Mod+Shift+${right}".action.focus-monitor-right = [];
+
+ # Workspace Focus Keys
+ "Mod+Shift+${up}".action.focus-workspace-up = [];
+ "Mod+Shift+${down}".action.focus-workspace-down = [];
+
+ # Monitor Motion Keys
+ "Mod+Control+Shift+${left}".action.move-column-to-monitor-left = [];
+ "Mod+Control+Shift+${right}".action.move-column-to-monitor-right = [];
+
+ # Workspace Motion Keys
+ "Mod+Control+Shift+${up}".action.move-window-to-workspace-up = [];
+ "Mod+Control+Shift+${down}".action.move-window-to-workspace-down = [];
+
+ # Window Motion Keys
+ "Mod+Control+${left}".action.move-column-left = [];
+ "Mod+Control+${right}".action.move-column-right = [];
+ "Mod+Control+${up}".action.move-window-up = [];
+ "Mod+Control+${down}".action.move-window-down = [];
+
+ # Workspace Motion Keys
+ "Mod+Alt+Shift+${up}".action.move-workspace-up = [];
+ "Mod+Alt+Shift+${down}".action.move-workspace-down = [];
+
+ # Workspace - Monitor Motion Keys
+ "Mod+Alt+Shift+${left}".action.move-workspace-to-monitor-left = [];
+ "Mod+Alt+Shift+${right}".action.move-workspace-to-monitor-right = [];
+
+ # +-------------------+
+
+ # Numbered Workspace Movement Keys
+ "Mod+1".action.focus-workspace = 1;
+ "Mod+2".action.focus-workspace = 2;
+ "Mod+3".action.focus-workspace = 3;
+ "Mod+4".action.focus-workspace = 4;
+ "Mod+5".action.focus-workspace = 5;
+ "Mod+6".action.focus-workspace = 6;
+ "Mod+7".action.focus-workspace = 7;
+ "Mod+8".action.focus-workspace = 8;
+ "Mod+9".action.focus-workspace = 9;
+ "Mod+0".action.focus-workspace = 0;
+
+ # XF86 Keys
+ "XF86AudioRaiseVolume".action.spawn = ["wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%+"];
+ "XF86AudioLowerVolume".action.spawn = ["wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%-"];
+ "XF86AudioMute".action.spawn = ["wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"];
+
+ "XF86MonBrightnessUp".action.spawn = ["${lib.getExe pkgs.brightnessctl}" "s" "+5%"];
+ "XF86MonBrightnessDown".action.spawn = ["${lib.getExe pkgs.brightnessctl}" "s" "5%-"];
+
+ "XF86LaunchB".action.spawn = ["fuzzel"];
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/fuzzel.nix b/features/user/gui/desktops/niri/parts/fuzzel.nix
new file mode 100755
index 0000000..e939f88
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/fuzzel.nix
@@ -0,0 +1,30 @@
+{ config, lib, ... }: let
+ cfg = config.features.gui.desktops.niri.parts.fuzzel;
+ aes = config.aesthetics;
+in {
+ options.features.gui.desktops.niri.parts.fuzzel.enable = lib.mkEnableOption "fuzzel";
+ config = lib.mkIf cfg.enable {
+ programs.fuzzel = {
+ enable = true;
+ settings = {
+ main = {
+ width = 20;
+ #terminal = config.custom.libraries.default-applications.terminal-emulator.command;
+ };
+ border = {
+ width = 3;
+ radius = 4;
+ };
+ colors = with aes.scheme; {
+ background = "${base00}ff";
+ selection = "${base00}ff";
+ selection-text = "${base0C}ff";
+ selection-match = "${base0E}ff";
+ match = "${base0E}ff";
+ border = "${base0C}ff";
+ text = "${base05}ff";
+ };
+ };
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/hyprlock.nix b/features/user/gui/desktops/niri/parts/hyprlock.nix
new file mode 100755
index 0000000..f8a50e3
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/hyprlock.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }: let
+ cfg = config.features.gui.desktops.niri.parts.hyprlock;
+in {
+ options.features.gui.desktops.niri.parts.hyprlock.enable = lib.mkEnableOption "hyprlock";
+ config = lib.mkIf cfg.enable {
+ home.packages = [(
+ pkgs.writers.writeNuBin "nirilock" /*nu*/ ''
+ niri msg action do-screen-transition --delay-ms 500
+ systemctl suspend
+ hyprlock
+ ''
+ )];
+ programs.niri.settings.window-rules = [
+ {
+ matches = [{ title = "hyprlock"; }];
+ draw-border-with-background = false;
+ }
+ ];
+ programs.hyprlock = {
+ enable = true;
+ settings = {
+ background = {
+ monitor = "";
+ path = "${config.aesthetics.wallpaper}";
+ blur_passes = 0;
+ blur_size = 5;
+ };
+ label = {
+ text = "$TIME";
+ font_size = 65;
+ font_family = "Cantarell Bold";
+
+ position = "0, 0";
+ halign = "center";
+ valign = "center";
+ };
+ input-field = {
+ size = "250, 50";
+ position = "0, -80";
+ outline_thickness = 0;
+ placeholder_text = "";
+ };
+ };
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/ignis/default.nix b/features/user/gui/desktops/niri/parts/ignis/default.nix
new file mode 100755
index 0000000..c609ca9
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/ignis/default.nix
@@ -0,0 +1,21 @@
+{ config, pkgs, lib, inputs, ... }: let
+ cfg = config.features.gui.desktops.niri.parts.ignis;
+in {
+ options.features.gui.desktops.niri.parts.ignis.enable = lib.mkEnableOption "ignis";
+ config = lib.mkIf cfg.enable {
+ home.packages = [
+ inputs.ignis.packages.${pkgs.system}.ignis
+ pkgs.python3
+ ];
+ # home.file."ignis-config" = {
+ # target = ".config/ignis/config.py";
+ # src = ./config.py;
+ # };
+ # home.file."ignis-style" = {
+ # target = ".config/ignis/style.scss";
+ # text = /*scss*/ ''
+
+ # '';
+ # };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/mako.nix b/features/user/gui/desktops/niri/parts/mako.nix
new file mode 100755
index 0000000..e5bc3b8
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/mako.nix
@@ -0,0 +1,26 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.features.gui.desktops.niri.parts.mako;
+in {
+ options.features.gui.desktops.niri.parts.mako.enable = lib.mkEnableOption "mako";
+ config = lib.mkIf cfg.enable {
+ services.mako = {
+ enable = true;
+ settings = {
+ border-radius = 4;
+ border-size = 3;
+ # margin = "11";
+ margin = "31";
+ padding = "5";
+
+ anchor = "top-center";
+
+ ignore-timeout = true;
+ default-timeout = 10000;
+ };
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/selectors.nix b/features/user/gui/desktops/niri/parts/selectors.nix
new file mode 100644
index 0000000..f0f9ce0
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/selectors.nix
@@ -0,0 +1,117 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}: let
+ cfg = config.features.gui.desktops.niri.parts.selectors;
+ aes = config.aesthetics;
+in {
+ options.features.gui.desktops.niri.parts.selectors.enable = lib.mkEnableOption "selectors";
+ config = lib.mkIf cfg.enable {
+ home.packages = with pkgs; [
+ # Tool Selector
+ (
+ pkgs.writers.writeNuBin "tool-selector"
+ /*
+ nu
+ */
+ ''
+
+ # Tools
+ let tools = {
+ "rebuild nixos": {
+ footclient -H sudo nixos-rebuild switch --flake ($"~/Sync/setup#(hostname)" | path expand)
+ }
+ "rebuild home": {
+ footclient -H home-manager switch --flake ($"~/Sync/setup#(whoami)@(hostname)" | path expand)
+ }
+ "update flake": {
+ footclient -H nix flake update --flake ($"~/Sync/setup/" | path expand)
+ }
+ "manage wifi": {
+ footclient ${pkgs.impala}/bin/impala
+ }
+ "manage bluetooth": {
+ footclient ${lib.getExe pkgs.bluetui}
+ }
+ "create qr-code": {
+ let temp_file = mktemp
+ let qr_code_bin = ${lib.getExe pkgs.qrtool} encode (${pkgs.wl-clipboard}/bin/wl-paste)
+ $qr_code_bin | ${pkgs.wl-clipboard}/bin/wl-copy
+ $qr_code_bin | save -f $temp_file
+ ${lib.getExe pkgs.imv} $temp_file
+ }
+ }
+
+ # Logic
+ let user_tool_choice = $tools
+ | columns
+ | to text
+ | fuzzel -d --placeholder "Tools"
+ if ($user_tool_choice != "") {
+ do ($tools | get $user_tool_choice)
+ }
+
+ ''
+ )
+
+ # Wallpaper Selector
+ (
+ writers.writeNuBin "wallpaper-selector"
+ /*
+ nu
+ */
+ ''
+ def main [
+ --all-outputs # Change wallpaper for all outputs
+ --randomize
+ ] {
+ mut wallpapers = {}
+ for path in (ls ${aes.wallpapersDir}/**/* | where {|item| $item.type != dir} | get name) {
+ $wallpapers = $wallpapers | insert ($path | path basename | split row "." | get 0) $path
+ }
+ mut prompt = "Wallpaper (current)"
+ if $all_outputs {
+ $prompt = "Wallpaper (all)"
+ }
+ mut wallpaper_path = ""
+ if $randomize {
+ $wallpaper_path = $wallpapers | get (
+ $wallpapers
+ | columns
+ | shuffle
+ | get 0
+ )
+ } else {
+ $wallpaper_path = $wallpapers | get (
+ $wallpapers
+ | columns
+ | to text
+ | ${lib.getExe pkgs.fuzzel} -d --placeholder $prompt
+ )
+ }
+ if $all_outputs {
+ ${lib.getExe pkgs.swww} img $wallpaper_path -t wipe --transition-fps 60 --transition-angle 45
+ } else {
+ let focused_display = niri msg -j focused-output
+ | from json
+ | get name
+ ${lib.getExe pkgs.swww} img $wallpaper_path -t wipe --transition-fps 60 --transition-angle 45 --outputs $focused_display
+ }
+ }
+ ''
+ )
+
+ # Clipboard Selector
+ (
+ writers.writeNuBin "clipboard-selector" ''
+ ${lib.getExe pkgs.cliphist} list
+ | cut -f 2-
+ | ${lib.getExe pkgs.fuzzel} --dmenu
+ | ${pkgs.wl-clipboard}/bin/wl-copy
+ ''
+ )
+ ];
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/swww.nix b/features/user/gui/desktops/niri/parts/swww.nix
new file mode 100755
index 0000000..dca163b
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/swww.nix
@@ -0,0 +1,8 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.gui.desktops.niri.parts.swww;
+in {
+ options.features.gui.desktops.niri.parts.swww.enable = lib.mkEnableOption "swww";
+ config = lib.mkIf cfg.enable {
+ home.packages = [pkgs.swww];
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/waybar.nix b/features/user/gui/desktops/niri/parts/waybar.nix
new file mode 100755
index 0000000..7535b0c
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/waybar.nix
@@ -0,0 +1,136 @@
+{ config, pkgs, lib, ... }:
+let
+ cfg = config.features.gui.desktops.niri.parts.waybar;
+ aes = config.aesthetics;
+in {
+ options.features.gui.desktops.niri.parts.waybar.enable = lib.mkEnableOption "waybar";
+ config = lib.mkIf cfg.enable {
+ programs.waybar = {
+ enable = true;
+ settings = {
+ bar = {
+ layer = "top";
+ position = "bottom";
+ height = 32;
+ modules-left = [ "battery" "network" "backlight" "pulseaudio" ];
+ modules-center = [ "niri/workspaces" ];
+ modules-right = [ "clock#date" "clock#time" ];
+ "clock#date" = {
+ format = " {:%A, %B %d}";
+ };
+ "clock#time" = {
+ format = " {:%I:%M}";
+ };
+ pulseaudio = {
+ format = " {volume}%";
+ format-muted = " {volume}%";
+ };
+ network = {
+ format = "{essid}";
+ format-wifi = "{icon} {essid}";
+ format-ethernet = "󰈀 Ethernet";
+ format-disconnected = "󰤭 Disconnected";
+ format-icons = [ "󰤯" "󰤟" "󰤢" "󰤥" "󰤨" ];
+ };
+ battery = {
+ format = "{icon} {capacity}%";
+ format-charging = "󰂄 {capacity}%";
+ format-icons = [ "󰂎" "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ];
+ };
+ backlight = {
+ format = " {percent}%";
+ };
+ "niri/workspaces" = {
+ format = "{icon}";
+ format-icons = {
+ default = "";
+ active = "";
+ /*
+ "1" = "1";
+ "2" = "2";
+ "3" = "3";
+ "4" = "4";
+ "5" = "5";
+ "6" = "6";
+ "7" = "7";
+ "8" = "8";
+ "9" = "9";
+ "10" = "10";
+ "11" = "11";
+ "12" = "12";
+ */
+ };
+ };
+ };
+ };
+ style = let
+ border-radius = "4";
+ padding = "12";
+ in /*css*/ ''
+ @define-color background-color #${aes.scheme.base00};
+ @define-color border-color #${aes.scheme.base0C};
+ @define-color text-color #${aes.scheme.base05};
+ * {
+ font-family: ${aes.font.name};
+ font-weight: 600;
+ font-size: ${aes.font.size.medium}px;
+ }
+ window#waybar {
+ background-color: transparent;
+ }
+ #clock.time, #clock.date, #backlight, #pulseaudio, #battery, #network {
+ background-color: @background-color;
+ color: @text-color;
+ border-radius: ${border-radius}px;
+ border-width: 0px;
+ border-color: @border-color;
+ padding: 0px ${padding}px;
+ }
+ #backlight, #pulseaudio, #battery, #network {
+ margin: 0px 0px ${padding} ${padding};
+ }
+ #workspaces {
+ background-color: @background-color;
+ color: @background-color;
+ border-radius: ${border-radius}px;
+ border-width: 0px;
+ border-color: @border-color;
+ padding: 0px 0px;
+ margin-bottom: ${padding}px;
+ }
+ #workspaces button {
+ font-weight: bold;
+ padding: 0px 4px;
+ margin: 4px 4px;
+ border-radius: ${border-radius}px;
+ color: @background-color;
+ background: @text-color;
+ opacity: 0.5;
+ transition: all 0.3s cubic-bezier(.25,.1,.25,1);
+ }
+ #workspaces button.active {
+ font-weight: bold;
+ padding: 0px 4px;
+ margin: 4px 4px;
+ border-radius: ${border-radius}px;
+ color: @background-color;
+ background: @text-color;
+ transition: all 0.3s cubic-bezier(.25,.1,.25,1);
+ opacity: 1.0;
+ min-width: 40px;
+ }
+ #workspaces button:hover {
+ font-weight: bold;
+ border-radius: ${border-radius}px;
+ color: @background-color;
+ background: @text-color;
+ opacity: 0.8;
+ transition: all 0.3s cubic-bezier(.25,.1,.25,1);
+ }
+ #clock.date, #clock.time {
+ margin: 0px ${padding} ${padding} 0px
+ }
+ '';
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/wl-kbptr.nix b/features/user/gui/desktops/niri/parts/wl-kbptr.nix
new file mode 100644
index 0000000..6f6ed56
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/wl-kbptr.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, inputs, ... }: let
+ cfg = config.features.gui.desktops.niri.parts.wl-kbptr;
+in {
+ options.features.gui.desktops.niri.parts.wl-kbptr.enable = lib.mkEnableOption "wl-kbptr";
+ config = lib.mkIf cfg.enable {
+ home.packages = [ pkgs.wl-kbptr ];
+ home.file."wl-kbptr-config" = {
+ target = ".config/wl-kbptr/config";
+ text = ''
+
+ '';
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/parts/wluma.nix b/features/user/gui/desktops/niri/parts/wluma.nix
new file mode 100644
index 0000000..21b9edc
--- /dev/null
+++ b/features/user/gui/desktops/niri/parts/wluma.nix
@@ -0,0 +1,11 @@
+{ config, lib, ... }: let
+ cfg = config.features.gui.desktops.niri.parts.wluma;
+in {
+ options.features.gui.desktops.niri.parts.wluma.enable = lib.mkEnableOption "wluma";
+ config = lib.mkIf cfg.enable {
+ services.wluma = {
+ enable = true;
+ systemd.enable = true;
+ };
+ };
+}
diff --git a/features/user/gui/desktops/niri/readme.md b/features/user/gui/desktops/niri/readme.md
new file mode 100644
index 0000000..a70f77d
--- /dev/null
+++ b/features/user/gui/desktops/niri/readme.md
@@ -0,0 +1,9 @@
+# Keybinds Scheme
+
+OS: The OS + Arrows combination is for moving between windows within a workspace.
+
+OS + Shift: The OS + Shift + Arrows combination is for moving between workspaces.
+
+OS + Alt: The OS + Alt + Arrows combination is for moving between monitors.
+
+The control key can be used with any of these combos to bring the current window along with you.
diff --git a/features/user/services/flatpak.nix b/features/user/services/flatpak.nix
new file mode 100644
index 0000000..37225aa
--- /dev/null
+++ b/features/user/services/flatpak.nix
@@ -0,0 +1,27 @@
+{
+ config,
+ pkgs,
+ lib,
+ inputs,
+ ...
+}: let
+ cfg = config.features.services.flatpak;
+in {
+ imports = [inputs.nix-flatpak.homeManagerModules.nix-flatpak];
+ options.features.services.flatpak = {
+ enable = lib.mkEnableOption "flatpak";
+ packages = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [];
+ description = "A list of flatpaks to install.";
+ };
+ };
+ config = lib.mkIf cfg.enable {
+ home.packages = [pkgs.flatpak];
+ services.flatpak = {
+ enable = true;
+ packages = cfg.packages;
+ overrides.global.Context.sockets = ["wayland" "!x11" "!fallback-x11"];
+ };
+ };
+}
diff --git a/features/user/services/udiskie.nix b/features/user/services/udiskie.nix
new file mode 100644
index 0000000..9b79d11
--- /dev/null
+++ b/features/user/services/udiskie.nix
@@ -0,0 +1,17 @@
+{ config, lib, ... }: let
+ cfg = config.features.services.udiskie;
+in {
+ options.features.services.udiskie.enable = lib.mkEnableOption "udiskie";
+ config = lib.mkIf cfg.enable {
+ services.udiskie = {
+ enable = true;
+ automount = true;
+ };
+ systemd.user.targets.tray = {
+ Unit = {
+ Description = "Home Manager System Tray";
+ Requires = [ "graphical-session-pre.target" ];
+ };
+ };
+ };
+}
diff --git a/features/user/services/wluma.nix b/features/user/services/wluma.nix
new file mode 100644
index 0000000..7c9369e
--- /dev/null
+++ b/features/user/services/wluma.nix
@@ -0,0 +1,11 @@
+{ config, pkgs, lib, ... }: let
+ cfg = config.features.services.wluma;
+in {
+ options.features.services.wluma.enable = lib.mkEnableOption "wluma";
+ config = lib.mkIf cfg.enable {
+ services.wluma = {
+ enable = true;
+ systemd.enable = true;
+ };
+ };
+}