summaryrefslogtreecommitdiff
path: root/features/user/gui
diff options
context:
space:
mode:
Diffstat (limited to 'features/user/gui')
-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
23 files changed, 1687 insertions, 0 deletions
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.