diff options
Diffstat (limited to 'features/user')
43 files changed, 2536 insertions, 0 deletions
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={}&go=Go&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()} / ${dislikes.toLocaleString()} - ${likePercentage}%`; + break; + case "dash_dislike": + tooltipInnerHTML = `${likes.toLocaleString()} / ${dislikes.toLocaleString()} - ${dislikePercentage}%`; + break; + case "both": + tooltipInnerHTML = `${likePercentage}% / ${dislikePercentage}%`; + break; + case "only_like": + tooltipInnerHTML = `${likePercentage}%`; + break; + case "only_dislike": + tooltipInnerHTML = `${dislikePercentage}%`; + break; + default: + tooltipInnerHTML = `${likes.toLocaleString()} / ${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; + }; + }; +} |
