diff --git a/flake.lock b/flake.lock
index a19b836..90537ec 100644
--- a/flake.lock
+++ b/flake.lock
@@ -221,7 +221,7 @@
},
"devshell": {
"inputs": {
- "flake-utils": "flake-utils_5",
+ "flake-utils": "flake-utils_6",
"nixpkgs": [
"nixvim",
"nixpkgs"
@@ -260,11 +260,11 @@
"doomemacs": {
"flake": false,
"locked": {
- "lastModified": 1720224658,
- "narHash": "sha256-ugNtDBO92zFbRx7URXdvtzmGJGLPG6tzDC72UOpf9IA=",
+ "lastModified": 1720315114,
+ "narHash": "sha256-YeXi76K7U2U8u+s3B76zDtJYEglOD+JtIq0o/sGYFJI=",
"owner": "doomemacs",
"repo": "doomemacs",
- "rev": "7bb5df4cd4ae3a0916616dd7e50566b3caa9c931",
+ "rev": "21a427c33b57ab66eb7caa2830c0dfe930509318",
"type": "github"
},
"original": {
@@ -280,11 +280,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
- "lastModified": 1720256942,
- "narHash": "sha256-ohfS5d4yR+zdLTICE78rNJmqL/en0BNPGm5zCK6N0QA=",
+ "lastModified": 1720343386,
+ "narHash": "sha256-6OVidxIFSmlK7dWcU8UvTu5erv9yLXeCdRftaDR9wQk=",
"owner": "nix-community",
"repo": "emacs-overlay",
- "rev": "6d9837126e1be779c8f34ed9fdd609e676a1b891",
+ "rev": "907ffaedc98068a23118e7d9d90ac7200095b3cd",
"type": "github"
},
"original": {
@@ -301,11 +301,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
- "lastModified": 1720247208,
- "narHash": "sha256-isVgeEPkkRz2Tw3vx0J+HyIvKmwpyY4SNcw7MbKZa/I=",
+ "lastModified": 1720333712,
+ "narHash": "sha256-qZmpIPUe4mwdBGmKPBJ5KzvXH6GOCXheiusTAgNjAAA=",
"owner": "nix-community",
"repo": "fenix",
- "rev": "27cbad7cc093c5298231b87daa04db9610053651",
+ "rev": "b78c98c53a46e2a2d6479a920f98aa0aeae56281",
"type": "github"
},
"original": {
@@ -660,23 +660,8 @@
}
},
"flake-utils_3": {
- "locked": {
- "lastModified": 1659877975,
- "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "flake-utils_4": {
"inputs": {
- "systems": "systems_7"
+ "systems": "systems_6"
},
"locked": {
"lastModified": 1710146030,
@@ -692,10 +677,43 @@
"type": "github"
}
},
+ "flake-utils_4": {
+ "locked": {
+ "lastModified": 1659877975,
+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
"flake-utils_5": {
"inputs": {
"systems": "systems_8"
},
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_6": {
+ "inputs": {
+ "systems": "systems_9"
+ },
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
@@ -710,9 +728,9 @@
"type": "github"
}
},
- "flake-utils_6": {
+ "flake-utils_7": {
"inputs": {
- "systems": "systems_10"
+ "systems": "systems_11"
},
"locked": {
"lastModified": 1685518550,
@@ -728,6 +746,21 @@
"type": "github"
}
},
+ "flakey-profile": {
+ "locked": {
+ "lastModified": 1712898590,
+ "narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=",
+ "owner": "lf-",
+ "repo": "flakey-profile",
+ "rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d",
+ "type": "github"
+ },
+ "original": {
+ "owner": "lf-",
+ "repo": "flakey-profile",
+ "type": "github"
+ }
+ },
"fromYaml": {
"flake": false,
"locked": {
@@ -934,11 +967,11 @@
]
},
"locked": {
- "lastModified": 1720188602,
- "narHash": "sha256-lC3byBmhVZFzWl/dCic8+cKUEEAXAswWOYjq4paFmbo=",
+ "lastModified": 1720327769,
+ "narHash": "sha256-kAsg3Lg4YKKpGw+f1W2s5hzjP8B0y/juowvjK8utIag=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "e3582e5151498bc4d757e8361431ace8529e7bb7",
+ "rev": "6b7ce96f34b324e4e104abc30d06955d216bac71",
"type": "github"
},
"original": {
@@ -1203,7 +1236,7 @@
},
"lib-aggregate": {
"inputs": {
- "flake-utils": "flake-utils_4",
+ "flake-utils": "flake-utils_5",
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
@@ -1220,6 +1253,41 @@
"type": "github"
}
},
+ "lix": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1718419213,
+ "narHash": "sha256-WY7BGnu5PnbK4O8cKKv9kvxwzZIGbIQUQLGPHFXitI0=",
+ "rev": "253546d5fbf8a5aa60ac8164c1b4f5794dc4e9d1",
+ "type": "tarball",
+ "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/253546d5fbf8a5aa60ac8164c1b4f5794dc4e9d1.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://git.lix.systems/lix-project/lix/archive/2.90.0-rc1.tar.gz"
+ }
+ },
+ "lix-module": {
+ "inputs": {
+ "flake-utils": "flake-utils_2",
+ "flakey-profile": "flakey-profile",
+ "lix": "lix",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1718778548,
+ "narHash": "sha256-64lB/NO6AQ6z6EDCemPSYZWX/Qc6Rt04cPia5T5v01g=",
+ "rev": "29ed1bb67751e5b107d08df35d18dda6d45324e9",
+ "type": "tarball",
+ "url": "https://git.lix.systems/api/v1/repos/lix-project/nixos-module/archive/29ed1bb67751e5b107d08df35d18dda6d45324e9.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://git.lix.systems/lix-project/nixos-module/archive/2.90.0-rc1.tar.gz"
+ }
+ },
"naersk": {
"inputs": {
"nixpkgs": [
@@ -1245,7 +1313,7 @@
"neovim-flake": {
"inputs": {
"flake-parts": "flake-parts_2",
- "flake-utils": "flake-utils_2",
+ "flake-utils": "flake-utils_3",
"nil": "nil",
"nixpkgs": [
"nixpkgs"
@@ -1350,15 +1418,15 @@
"plugin-vim-vsnip": "plugin-vim-vsnip",
"plugin-which-key": "plugin-which-key",
"rnix-lsp": "rnix-lsp",
- "systems": "systems_6",
+ "systems": "systems_7",
"zig": "zig"
},
"locked": {
- "lastModified": 1720032146,
- "narHash": "sha256-m+442OenNQB3SAhVWh2WmKVIauv+OFvl8a7U1weW19s=",
+ "lastModified": 1720293276,
+ "narHash": "sha256-MUhm7MFTQymDgKziGaEC34jFFpFROv3ZXlD6K+IF4Ek=",
"owner": "notashelf",
"repo": "nvf",
- "rev": "3f5ed9e979f3048dc7ad07b0e9f0a6546588ce6c",
+ "rev": "f1849c0ce57452988bf600e354f33d864881b9ac",
"type": "github"
},
"original": {
@@ -1511,6 +1579,22 @@
"type": "github"
}
},
+ "nixos-hardware": {
+ "locked": {
+ "lastModified": 1719895800,
+ "narHash": "sha256-xNbjISJTFailxass4LmdWeV4jNhAlmJPwj46a/GxE6M=",
+ "owner": "NixOS",
+ "repo": "nixos-hardware",
+ "rev": "6e253f12b1009053eff5344be5e835f604bb64cd",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "master",
+ "repo": "nixos-hardware",
+ "type": "github"
+ }
+ },
"nixpak": {
"inputs": {
"flake-parts": "flake-parts_5",
@@ -1596,16 +1680,16 @@
},
"nixpkgs-stable": {
"locked": {
- "lastModified": 1719957072,
- "narHash": "sha256-gvFhEf5nszouwLAkT9nWsDzocUTqLWHuL++dvNjMp9I=",
+ "lastModified": 1720110830,
+ "narHash": "sha256-E5dN9GDV4LwMEduhBLSkyEz51zM17XkWZ3/9luvNOPs=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "7144d6241f02d171d25fba3edeaf15e0f2592105",
+ "rev": "c0d0be00d4ecc4b51d2d6948e37466194c1e6c51",
"type": "github"
},
"original": {
"owner": "NixOS",
- "ref": "nixos-23.11",
+ "ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
@@ -1618,11 +1702,11 @@
"nixpkgs": "nixpkgs_10"
},
"locked": {
- "lastModified": 1720254101,
- "narHash": "sha256-uLdBJWNxJcq/avc9lSOZ9IbURy+fnRTkApPOkQJhGDY=",
+ "lastModified": 1720297047,
+ "narHash": "sha256-RXwn9ABn7cdMcachf8m6iu5nHDfY621movgpIM6M9vw=",
"owner": "nix-community",
"repo": "nixpkgs-wayland",
- "rev": "3077de42e37d6835fc29196efda76991493c96c9",
+ "rev": "6cebaff2235ddd938eac062c48dfc14477e56b82",
"type": "github"
},
"original": {
@@ -1821,11 +1905,11 @@
"treefmt-nix": "treefmt-nix_2"
},
"locked": {
- "lastModified": 1720262861,
- "narHash": "sha256-KMCrvbTAF9mdEv8ZcG6jqMCKmsZH7K5VTRw5Iv57Zl4=",
+ "lastModified": 1720298683,
+ "narHash": "sha256-CNtfHBwlKuTTanwmUI85Z/HkHShnqZs+WYyxQR8zRFY=",
"owner": "nix-community",
"repo": "nixvim",
- "rev": "04a255ed7e24b0d54a158de5252e8471ac4c6d8f",
+ "rev": "6674dea8403747827431d4d8497c34023f93d047",
"type": "github"
},
"original": {
@@ -1852,11 +1936,11 @@
},
"nur": {
"locked": {
- "lastModified": 1720262786,
- "narHash": "sha256-lOw+DePpT6JEzudxzq/yhDqWW9fOga9vrojV2E1DgAs=",
+ "lastModified": 1720340553,
+ "narHash": "sha256-yltkWY9tbcTO1E9lsegEUcqcHdF+F3Yb8O79CjSAHrg=",
"owner": "nix-community",
"repo": "NUR",
- "rev": "7907d743f2a29c574db99f3297da264fe5fe7a6e",
+ "rev": "64eb60afcb54464e61d34fda417e7a5c7a935c21",
"type": "github"
},
"original": {
@@ -3466,8 +3550,10 @@
"home-manager": "home-manager_2",
"hyprland": "hyprland",
"hyprland-plugins": "hyprland-plugins",
+ "lix-module": "lix-module",
"neovim-flake": "neovim-flake",
"neovim-nightly-overlay": "neovim-nightly-overlay",
+ "nixos-hardware": "nixos-hardware",
"nixpak": "nixpak",
"nixpkgs": "nixpkgs_8",
"nixpkgs-wayland": "nixpkgs-wayland",
@@ -3482,11 +3568,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
- "lastModified": 1720175310,
- "narHash": "sha256-PQG46r17hX06Q1akkuVSeMD1hfiNBENWVlgGn1SF/SE=",
+ "lastModified": 1720292183,
+ "narHash": "sha256-CPcdVpsgmNXYVsw4nKPrM23J5mYXtiotYMz2BjPDIKU=",
"owner": "rust-lang",
"repo": "rust-analyzer",
- "rev": "f2afcb874e7410121c366ae601660abe327e320b",
+ "rev": "058c88da66797eb588b47a4aac3e42847d1333d7",
"type": "github"
},
"original": {
@@ -3535,7 +3621,7 @@
"nixpkgs"
],
"searx-randomizer": "searx-randomizer",
- "systems": "systems_9"
+ "systems": "systems_10"
},
"locked": {
"lastModified": 1719058240,
@@ -3576,7 +3662,7 @@
},
"spicetify-nix": {
"inputs": {
- "flake-utils": "flake-utils_6",
+ "flake-utils": "flake-utils_7",
"nixpkgs": "nixpkgs_11"
},
"locked": {
@@ -3658,6 +3744,21 @@
}
},
"systems_10": {
+ "locked": {
+ "lastModified": 1689347949,
+ "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
+ "owner": "nix-systems",
+ "repo": "default-linux",
+ "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default-linux",
+ "type": "github"
+ }
+ },
+ "systems_11": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@@ -3779,16 +3880,16 @@
},
"systems_9": {
"locked": {
- "lastModified": 1689347949,
- "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
- "repo": "default-linux",
- "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
- "repo": "default-linux",
+ "repo": "default",
"type": "github"
}
},
@@ -3883,7 +3984,7 @@
"zig": {
"inputs": {
"flake-compat": "flake-compat",
- "flake-utils": "flake-utils_3",
+ "flake-utils": "flake-utils_4",
"nixpkgs": "nixpkgs_6"
},
"locked": {
diff --git a/flake.nix b/flake.nix
index a7f2938..f31fe9b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,11 +14,17 @@
url = "github:hyprwm/hyprland-plugins";
inputs.hyprland.follows = "hyprland";
};
+ nixos-hardware.url = "github:NixOS/nixos-hardware/master";
# Plugin to get split monitor workspaces
split-monitor-workspaces = {
url = "github:Duckonaut/split-monitor-workspaces";
inputs.hyprland.follows = "hyprland";
};
+ # Lix because fast rebuild times are cool
+ lix-module = {
+ url = "https://git.lix.systems/lix-project/nixos-module/archive/2.90.0-rc1.tar.gz";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
# Sandboxing
nixpak = {
url = "github:nixpak/nixpak";
diff --git a/hosts/default.nix b/hosts/default.nix
index d963b97..f53a67a 100644
--- a/hosts/default.nix
+++ b/hosts/default.nix
@@ -21,7 +21,9 @@ in {
../modules
inputs.home-manager.nixosModules.home-manager
inputs.agenix.nixosModules.default
+ inputs.lix-module.nixosModules.default
inputs.stylix.nixosModules.stylix
+ inputs.nixos-hardware.nixosModules.dell-xps-15-7590-nvidia
];
};
}
diff --git a/hosts/vali/hermit/configuration.nix b/hosts/vali/hermit/configuration.nix
index 636926d..3f5f710 100644
--- a/hosts/vali/hermit/configuration.nix
+++ b/hosts/vali/hermit/configuration.nix
@@ -62,7 +62,6 @@
mpv.enable = true;
kitty.enable = true;
newsboat.enable = true;
- foot.enable = true;
fish.enable = true;
nh.enable = true;
waybar.enable = true;
@@ -70,6 +69,10 @@
schizofox.enable = true;
anyrun.enable = true;
ags.enable = true;
+ foot = {
+ enable = true;
+ server = false;
+ };
};
editors = {
emacs = {
@@ -89,10 +92,11 @@
stylix = {
enable = true;
scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-dark-pale.yaml";
- cursor = {size = 16;};
+ cursor = {size = 28;};
fontsizes = {
- terminal = 12;
- popups = 12;
+ terminal = 14;
+ popups = 14;
+ applications = 14;
};
image = ../../../assets/wallpapers/tiredgod.png;
};
diff --git a/hosts/vali/hermit/profile.nix b/hosts/vali/hermit/profile.nix
index 45e6158..c843f8e 100644
--- a/hosts/vali/hermit/profile.nix
+++ b/hosts/vali/hermit/profile.nix
@@ -4,7 +4,7 @@ _: {
../../../options/common/bluetooth.nix
../../../options/desktop/fonts.nix
../../../options/common/networking.nix
- # ../../../options/common/gpu/nvidia.nix
+ ../../../options/common/gpu/nvidia.nix
../../../options/desktop/monitors.nix
../../../overlay.nix
];
diff --git a/modules/cli/default.nix b/modules/cli/default.nix
index 78901d3..e8000c8 100644
--- a/modules/cli/default.nix
+++ b/modules/cli/default.nix
@@ -1 +1 @@
-_: {imports = [./fish.nix ./nh.nix];}
+_: {imports = [./fish.nix ./nh.nix ./starship.nix];}
diff --git a/modules/cli/fish.nix b/modules/cli/fish.nix
index ec76ec2..fa1615e 100644
--- a/modules/cli/fish.nix
+++ b/modules/cli/fish.nix
@@ -45,10 +45,10 @@ in {
name = "done";
inherit (pkgs.fishPlugins.done) src;
}
- {
- name = "tide";
- inherit (pkgs.fishPlugins.tide) src;
- }
+ # {
+ # name = "tide";
+ # inherit (pkgs.fishPlugins.tide) src;
+ # }
];
shellAbbrs =
{
diff --git a/modules/cli/starship.nix b/modules/cli/starship.nix
index 8b00731..73e8032 100644
--- a/modules/cli/starship.nix
+++ b/modules/cli/starship.nix
@@ -4,15 +4,12 @@
...
}: let
inherit (builtins) map;
+ inherit (config.modules.other.system) username;
+ hmCfg = config.home-manager.users.${username};
+
inherit (lib.strings) concatStrings;
in {
- home = {
- sessionVariables = {
- STARSHIP_CACHE = "${config.xdg.cacheHome}/starship";
- };
- };
-
- programs.starship = let
+ home-manager.users.${username} = let
elemsConcatted = concatStrings (
map (s: "\$${s}") [
"hostname"
@@ -29,75 +26,95 @@ in {
]
);
in {
- enable = true;
+ home.sessionVariables = {
+ STARSHIP_CACHE = "${hmCfg.home.homeDirectory}/.cache/starship";
+ };
+ programs.starship = {
+ enable = true;
+ enableFishIntegration = true;
+ enableTransience = true;
+ settings = {
+ scan_timeout = 2;
+ command_timeout = 2000; # nixpkgs makes starship implode with lower values
+ add_newline = false;
+ line_break.disabled = false;
- settings = {
- scan_timeout = 2;
- command_timeout = 2000; # nixpkgs makes starship implode with lower values
- add_newline = false;
- line_break.disabled = false;
+ format = "${elemsConcatted}\n$character";
+ # format = concatStrings [
+ # "$hostname"
+ # #"$username"
+ # "$directory"
+ # "$shell"
+ # "$nix_shell"
+ # "$git_branch"
+ # "$git_commit"
+ # "$git_state"
+ # "$git_status"
+ # "$jobs"
+ # "$cmd_duration"
+ # "\n$character"
+ # ];
- format = "${elemsConcatted}\n$character";
-
- hostname = {
- ssh_only = true;
- disabled = false;
- format = "@[$hostname](bold blue) "; # the whitespace at the end is actually important
- };
-
- # configure specific elements
- character = {
- error_symbol = "[](bold red)";
- success_symbol = "[](bold green)";
- vicmd_symbol = "[](bold yellow)";
- format = "$symbol [|](bold bright-black) ";
- };
-
- username = {
- format = "[$user]($style) in ";
- };
-
- directory = {
- truncation_length = 2;
-
- # removes the read_only symbol from the format, it doesn't play nicely with my folder icon
- format = "[ ](bold green) [$path]($style) ";
-
- # the following removes tildes from the path, and substitutes some folders with shorter names
- substitutions = {
- "~/Dev" = "Dev";
- "~/Documents" = "Docs";
+ hostname = {
+ ssh_only = true;
+ disabled = false;
+ format = "@[$hostname](bold blue) "; # the whitespace at the end is actually important
};
+
+ # configure specific elements
+ character = {
+ error_symbol = "[](bold red)";
+ success_symbol = "[](bold green)";
+ vicmd_symbol = "[](bold yellow)";
+ format = "$symbol [|](bold bright-black) ";
+ };
+
+ username = {
+ format = "[$user]($style) in ";
+ };
+
+ directory = {
+ truncation_length = 2;
+
+ # removes the read_only symbol from the format, it doesn't play nicely with my folder icon
+ format = "[ ](bold green) [$path]($style) ";
+
+ # the following removes tildes from the path, and substitutes some folders with shorter names
+ substitutions = {
+ "~/Dev" = "Dev";
+ "~/Documents" = "Docs";
+ };
+ };
+
+ # git
+ git_commit.commit_hash_length = 7;
+ git_branch.style = "bold purple";
+ git_status = {
+ style = "red";
+ ahead = "⇡ ";
+ behind = "⇣ ";
+ conflicted = " ";
+ renamed = "»";
+ deleted = "✘ ";
+ diverged = "⇆ ";
+ modified = "!";
+ stashed = "≡";
+ staged = "+";
+ untracked = "?";
+ };
+
+ # language configurations
+ # the whitespaces at the end *are* necessary for proper formatting
+ lua.symbol = "[ ](blue) ";
+ python.symbol = "[ ](blue) ";
+ rust.symbol = "[ ](red) ";
+ nix_shell.symbol = "[ ](blue) ";
+ golang.symbol = "[ ](blue)";
+ c.symbol = "[ ](black)";
+ nodejs.symbol = "[ ](yellow)";
+
+ package.symbol = "📦 ";
};
-
- # git
- git_commit.commit_hash_length = 7;
- git_branch.style = "bold purple";
- git_status = {
- style = "red";
- ahead = "⇡ ";
- behind = "⇣ ";
- conflicted = " ";
- renamed = "»";
- deleted = "✘ ";
- diverged = "⇆ ";
- modified = "!";
- stashed = "≡";
- staged = "+";
- untracked = "?";
- };
-
- # language configurations
- # the whitespaces at the end *are* necessary for proper formatting
- lua.symbol = "[ ](blue) ";
- python.symbol = "[ ](blue) ";
- rust.symbol = "[ ](red) ";
- nix_shell.symbol = "[ ](blue) ";
- golang.symbol = "[ ](blue)";
- c.symbol = "[ ](black)";
- nodejs.symbol = "[ ](yellow)";
-
- package.symbol = "📦 ";
};
};
}
diff --git a/modules/gui/anyrun/anyrun.nix b/modules/gui/anyrun/anyrun.nix
index c06134a..2af1421 100644
--- a/modules/gui/anyrun/anyrun.nix
+++ b/modules/gui/anyrun/anyrun.nix
@@ -6,7 +6,7 @@
...
}:
with lib; let
- username = config.modules.other.system.username;
+ inherit (config.modules.other.system) username;
cfg = config.modules.programs.anyrun;
in {
options.modules.programs.anyrun.enable = mkEnableOption "anyrun";
@@ -20,11 +20,12 @@ in {
plugins = with inputs.anyrun.packages.${pkgs.system}; [
applications
dictionary
- kidex
+ # kidex
rink
shell
- symbols
+ # symbols
translate
+ websearch
];
hideIcons = false;
ignoreExclusiveZones = false;
@@ -42,7 +43,13 @@ in {
Config(
desktop_actions: false,
max_entries: 5,
- terminal: Some("kitty"),
+ terminal: Some("foot"),
+ )
+ '';
+ extraConfigFiles."websearch.ron".text = ''
+ Config(
+ prefix: "?",
+ engines: [DuckDuckGo]
)
'';
};
diff --git a/modules/other/xdg.nix b/modules/other/xdg.nix
index b020786..ae5b3bb 100644
--- a/modules/other/xdg.nix
+++ b/modules/other/xdg.nix
@@ -1,5 +1,5 @@
{config, ...}: let
- username = config.modules.other.system.username;
+ inherit (config.modules.other.system) username;
hmCfg = config.home-manager.users.${username};
primary_browser = "Schizofox.desktop";
@@ -7,7 +7,7 @@
file_manager = "nautilus.desktop";
media_player = "mpv.desktop";
image_viewer = "imv.desktop";
- text_editor = "helix.desktop";
+ text_editor = "nvim.desktop";
terminal = "foot.desktop";
in {
environment.sessionVariables = {TERMINAL = "${terminal}";};
diff --git a/modules/styling/ags.nix b/modules/styling/ags.nix
index c91193b..97e6117 100644
--- a/modules/styling/ags.nix
+++ b/modules/styling/ags.nix
@@ -13,18 +13,29 @@ in {
options.modules.programs.ags.enable = mkEnableOption "ags";
config = mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ ags
+ bun
+ ddcutil
+ brightnessctl
+ gtksourceview
+ webkitgtk
+ gtksourceview4
+ ollama
+ python311Packages.material-color-utilities
+ python311Packages.pywayland
+ pywal
+ sassc
+ webp-pixbuf-loader
+ ydotool
+ accountsservice
+ ];
+
home-manager.users.${username} = {
imports = [inputs.ags.homeManagerModules.default];
programs.ags = {
enable = true;
configDir = ./config;
- extraPackages = with pkgs; [
- ags
- bun
- gtksourceview
- webkitgtk
- accountsservice
- ];
};
};
};
diff --git a/modules/styling/config.bak/config.js b/modules/styling/config.bak/config.js
new file mode 100644
index 0000000..e137af5
--- /dev/null
+++ b/modules/styling/config.bak/config.js
@@ -0,0 +1,21 @@
+const main = "/tmp/ags/main.js";
+
+try {
+ await Utils.execAsync([
+ "bun",
+ "build",
+ `${App.configDir}/main.ts`,
+ "--outfile",
+ main,
+ "--external",
+ "resource://*",
+ "--external",
+ "gi://*",
+ "--external",
+ "file://*",
+ ]);
+ await import(`file://${main}`);
+} catch (error) {
+ console.error(error);
+ App.quit();
+}
diff --git a/modules/styling/config/main.ts b/modules/styling/config.bak/main.ts
similarity index 100%
rename from modules/styling/config/main.ts
rename to modules/styling/config.bak/main.ts
diff --git a/modules/styling/config.bak/style.css b/modules/styling/config.bak/style.css
new file mode 100644
index 0000000..29f8fdd
--- /dev/null
+++ b/modules/styling/config.bak/style.css
@@ -0,0 +1,40 @@
+window.bar {
+ background-color: @theme_bg_color;
+ color: @theme_fg_color;
+}
+
+button {
+ min-width: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ background-color: transparent;
+}
+
+button:active {
+ background-color: @theme_selected_bg_color;
+}
+
+button:hover {
+ border-bottom: 3px solid @theme_fg_color;
+}
+
+label {
+ font-weight: bold;
+}
+
+.workspaces button.focused {
+ border-bottom: 3px solid @theme_selected_bg_color;
+}
+
+.client-title {
+ color: @theme_selected_bg_color;
+}
+
+.notification {
+ color: yellow;
+}
+
+levelbar block,
+highlight {
+ min-height: 10px;
+}
diff --git a/modules/styling/config/assets/arch-symbolic.svg b/modules/styling/config/assets/arch-symbolic.svg
new file mode 100644
index 0000000..7de9094
--- /dev/null
+++ b/modules/styling/config/assets/arch-symbolic.svg
@@ -0,0 +1,113 @@
+
+
+
+
diff --git a/modules/styling/config/assets/debian-symbolic.svg b/modules/styling/config/assets/debian-symbolic.svg
new file mode 100644
index 0000000..252f853
--- /dev/null
+++ b/modules/styling/config/assets/debian-symbolic.svg
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/modules/styling/config/assets/fedora-symbolic.svg b/modules/styling/config/assets/fedora-symbolic.svg
new file mode 100644
index 0000000..1a4e8c8
--- /dev/null
+++ b/modules/styling/config/assets/fedora-symbolic.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/modules/styling/config/assets/flatpak-symbolic.svg b/modules/styling/config/assets/flatpak-symbolic.svg
new file mode 100644
index 0000000..0c2bf62
--- /dev/null
+++ b/modules/styling/config/assets/flatpak-symbolic.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/modules/styling/config/assets/google-gemini-symbolic.svg b/modules/styling/config/assets/google-gemini-symbolic.svg
new file mode 100644
index 0000000..81f6729
--- /dev/null
+++ b/modules/styling/config/assets/google-gemini-symbolic.svg
@@ -0,0 +1,60 @@
+
+
+
+
diff --git a/modules/styling/config/assets/nixos-symbolic.svg b/modules/styling/config/assets/nixos-symbolic.svg
new file mode 100644
index 0000000..b697b0d
--- /dev/null
+++ b/modules/styling/config/assets/nixos-symbolic.svg
@@ -0,0 +1,77 @@
+
+
diff --git a/modules/styling/config/assets/openai-symbolic.svg b/modules/styling/config/assets/openai-symbolic.svg
new file mode 100644
index 0000000..8ffc912
--- /dev/null
+++ b/modules/styling/config/assets/openai-symbolic.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/modules/styling/config/assets/ubuntu-symbolic.svg b/modules/styling/config/assets/ubuntu-symbolic.svg
new file mode 100644
index 0000000..07746c9
--- /dev/null
+++ b/modules/styling/config/assets/ubuntu-symbolic.svg
@@ -0,0 +1,85 @@
+
+
+
+
diff --git a/modules/styling/config/config.js b/modules/styling/config/config.js
index e137af5..e52568f 100644
--- a/modules/styling/config/config.js
+++ b/modules/styling/config/config.js
@@ -1,21 +1,70 @@
-const main = "/tmp/ags/main.js";
+"use strict";
+// Import
+import Gdk from 'gi://Gdk';
+import App from 'resource:///com/github/Aylur/ags/app.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+// Widgets
+import { Bar, BarCornerTopleft, BarCornerTopright } from './widgets/bar/main.js';
+import Cheatsheet from './widgets/cheatsheet/main.js';
+// import DesktopBackground from './widgets/desktopbackground/main.js';
+// import Dock from './widgets/dock/main.js';
+import Corner from './widgets/screencorners/main.js';
+import Indicator from './widgets/indicators/main.js';
+import Osk from './widgets/onscreenkeyboard/main.js';
+import Overview from './widgets/overview/main.js';
+import Session from './widgets/session/main.js';
+import SideLeft from './widgets/sideleft/main.js';
+import SideRight from './widgets/sideright/main.js';
-try {
- await Utils.execAsync([
- "bun",
- "build",
- `${App.configDir}/main.ts`,
- "--outfile",
- main,
- "--external",
- "resource://*",
- "--external",
- "gi://*",
- "--external",
- "file://*",
- ]);
- await import(`file://${main}`);
-} catch (error) {
- console.error(error);
- App.quit();
+const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start);
+function forMonitors(widget) {
+ const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ return range(n, 0).map(widget).flat(1);
}
+
+// SCSS compilation
+Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicwal.scss'`); // reset music styles
+Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicmaterial.scss'`); // reset music styles
+function applyStyle() {
+ Utils.exec(`sassc ${App.configDir}/scss/main.scss ${App.configDir}/style.css`);
+ App.resetCss();
+ App.applyCss(`${App.configDir}/style.css`);
+ console.log('[LOG] Styles loaded')
+}
+applyStyle();
+
+const Windows = () => [
+ // forMonitors(DesktopBackground),
+ // Dock(),
+ Overview(),
+ forMonitors(Indicator),
+ Cheatsheet(),
+ SideLeft(),
+ SideRight(),
+ Osk(),
+ Session(),
+ // forMonitors(Bar),
+ // forMonitors(BarCornerTopleft),
+ // forMonitors(BarCornerTopright),
+ forMonitors((id) => Corner(id, 'top left')),
+ forMonitors((id) => Corner(id, 'top right')),
+ forMonitors((id) => Corner(id, 'bottom left')),
+ forMonitors((id) => Corner(id, 'bottom right')),
+];
+const CLOSE_ANIM_TIME = 210; // Longer than actual anim time to make sure widgets animate fully
+export default {
+ css: `${App.configDir}/style.css`,
+ stackTraceOnError: true,
+ closeWindowDelay: { // For animations
+ 'sideright': CLOSE_ANIM_TIME,
+ 'sideleft': CLOSE_ANIM_TIME,
+ 'osk': CLOSE_ANIM_TIME,
+ },
+ windows: Windows().flat(1),
+};
+
+// Stuff that don't need to be toggled. And they're async so ugh...
+// Bar().catch(print); // Use this to debug the bar. Single monitor only.
+forMonitors(Bar);
+forMonitors(BarCornerTopleft);
+forMonitors(BarCornerTopright);
diff --git a/modules/styling/config/data/keybinds.js b/modules/styling/config/data/keybinds.js
new file mode 100644
index 0000000..8d0eeb0
--- /dev/null
+++ b/modules/styling/config/data/keybinds.js
@@ -0,0 +1,135 @@
+export const keybindList = [[
+ {
+ "icon": "pin_drop",
+ "name": "Workspaces: navigation",
+ "binds": [
+ { "keys": ["", "+", "#"], "action": "Go to workspace #" },
+ { "keys": ["", "+", "S"], "action": "Toggle special workspace" },
+ { "keys": ["", "+", "(Scroll ↑↓)"], "action": "Go to workspace -1/+1" },
+ { "keys": ["Ctrl", "", "+", "←"], "action": "Go to workspace on the left" },
+ { "keys": ["Ctrl", "", "+", "→"], "action": "Go to workspace on the right" },
+ { "keys": ["", "+", "PageUp"], "action": "Go to workspace on the left" },
+ { "keys": ["", "+", "PageDown"], "action": "Go to workspace on the right" }
+ ],
+ "appeartick": 1
+ },
+ {
+ "icon": "overview_key",
+ "name": "Workspaces: management",
+ "binds": [
+ { "keys": ["", "Alt", "+", "#"], "action": "Move window to workspace #" },
+ { "keys": ["", "Alt", "+", "S"], "action": "Move window to special workspace" },
+ { "keys": ["", "Alt", "+", "PageUp"], "action": "Move window to workspace on the left" },
+ { "keys": ["", "Alt", "+", "PageDown"], "action": "Move window to workspace on the right" }
+ ],
+ "appeartick": 1
+ },
+ {
+ "icon": "move_group",
+ "name": "Windows",
+ "binds": [
+ { "keys": ["", "+", "←↑→↓"], "action": "Focus window in direction" },
+ { "keys": ["", "Shift", "+", "←↑→↓"], "action": "Swap window in direction" },
+ { "keys": ["", "+", ";"], "action": "Split ratio -" },
+ { "keys": ["", "+", "'"], "action": "Split ratio +" },
+ { "keys": ["", "+", "Lmb"], "action": "Move window" },
+ { "keys": ["", "+", "Mmb"], "action": "Move window" },
+ { "keys": ["", "+", "Rmb"], "action": "Resize window" },
+ { "keys": ["", "+", "F"], "action": "Fullscreen" },
+ { "keys": ["", "Alt", "+", "F"], "action": "Fake fullscreen" }
+ ],
+ "appeartick": 1
+ }
+],
+[
+ {
+ "icon": "widgets",
+ "name": "Widgets (AGS)",
+ "binds": [
+ { "keys": ["", "OR", "", "+", "Tab"], "action": "Toggle overview/launcher" },
+ { "keys": ["Ctrl", "", "+", "R"], "action": "Restart AGS" },
+ { "keys": ["", "+", "/"], "action": "Toggle this cheatsheet" },
+ { "keys": ["", "+", "N"], "action": "Toggle system sidebar" },
+ { "keys": ["", "+", "B", "OR", "", "+", "O"], "action": "Toggle utilities sidebar" },
+ { "keys": ["", "+", "K"], "action": "Toggle virtual keyboard" },
+ { "keys": ["Ctrl", "Alt", "+", "Del"], "action": "Power/Session menu" },
+
+ { "keys": ["Esc"], "action": "Exit a window" },
+ { "keys": ["rightCtrl"], "action": "Dismiss/close sidebar" },
+
+ { "keys": ["Ctrl", "", "+", "T"], "action": "Change wallpaper+colorscheme" },
+
+ // { "keys": ["", "+", "B"], "action": "Toggle left sidebar" },
+ // { "keys": ["", "+", "N"], "action": "Toggle right sidebar" },
+ // { "keys": ["", "+", "G"], "action": "Toggle volume mixer" },
+ // { "keys": ["", "+", "M"], "action": "Toggle useless audio visualizer" },
+ // { "keys": ["(right)Ctrl"], "action": "Dismiss notification & close menus" }
+ ],
+ "appeartick": 2
+ },
+ {
+ "icon": "construction",
+ "name": "Utilities",
+ "binds": [
+ { "keys": ["PrtSc"], "action": "Screenshot >> clipboard" },
+ { "keys": ["", "Shift", "+", "S"], "action": "Screen snip >> clipboard" },
+ { "keys": ["", "Shift", "+", "T"], "action": "Image to text >> clipboard" },
+ { "keys": ["", "Shift", "+", "C"], "action": "Color picker" },
+ { "keys": ["", "Alt", "+", "R"], "action": "Record region" },
+ { "keys": ["Ctrl", "Alt", "+", "R"], "action": "Record region with sound" },
+ { "keys": ["", "Shift", "Alt", "+", "R"], "action": "Record screen with sound" }
+ ],
+ "appeartick": 2
+ },
+ // {
+ // "icon": "edit",
+ // "name": "Edit mode",
+ // "binds": [
+ // { "keys": ["Esc"], "action": "Exit Edit mode" },
+ // { "keys": ["#"], "action": "Go to to workspace #" },
+ // { "keys": ["Alt", "+", "#"], "action": "Dump windows to workspace #" },
+ // { "keys": ["Shift", "+", "#"], "action": "Swap windows with workspace #" },
+ // { "keys": ["Lmb"], "action": "Move window" },
+ // { "keys": ["Mmb"], "action": "Move window" },
+ // { "keys": ["Rmb"], "action": "Resize window" }
+ // ],
+ // "appeartick": 2
+ // }
+],
+[
+ {
+ "icon": "apps",
+ "name": "Apps",
+ "binds": [
+ { "keys": ["", "+", "T"], "action": "Launch terminal: foot" },
+ { "keys": ["", "+", "W"], "action": "Launch browser: Firefox" },
+ { "keys": ["", "+", "C"], "action": "Launch editor: vscode" },
+ { "keys": ["", "+", "X"], "action": "Launch editor: GNOME Text Editor" },
+ { "keys": ["", "+", "I"], "action": "Launch settings: GNOME Control center" }
+ ],
+ "appeartick": 3
+ },
+ {
+ "icon": "keyboard",
+ "name": "Typing",
+ "binds": [
+ { "keys": ["", "+", "V"], "action": "Clipboard history >> clipboard" },
+ { "keys": ["", "+", "."], "action": "Emoji picker >> clipboard" },
+ ],
+ "appeartick": 3
+ },
+ {
+ "icon": "terminal",
+ "name": "Launcher actions",
+ "binds": [
+ { "keys": [">raw"], "action": "Toggle mouse acceleration" },
+ { "keys": [">img"], "action": "Select wallpaper and generate colorscheme" },
+ { "keys": [">light"], "action": "Switch to light theme" },
+ { "keys": [">dark"], "action": "Switch to dark theme" },
+ { "keys": [">badapple"], "action": "Apply black n' white colorscheme" },
+ { "keys": [">color"], "action": "Pick acccent color" },
+ { "keys": [">todo"], "action": "Type something after that to add a To-do item" },
+ ],
+ "appeartick": 3
+ }
+]];
diff --git a/modules/styling/config/data/keyboardlayouts.js b/modules/styling/config/data/keyboardlayouts.js
new file mode 100755
index 0000000..cb92b8a
--- /dev/null
+++ b/modules/styling/config/data/keyboardlayouts.js
@@ -0,0 +1,218 @@
+// We're going to use ydotool
+// See /usr/include/linux/input-event-codes.h for keycodes
+
+export const defaultOskLayout = "qwerty_full"
+export const oskLayouts = {
+ qwerty_full: {
+ name: "QWERTY - Full",
+ name_short: "US",
+ comment: "Like physical keyboard",
+ // A key looks like this: { k: "a", ks: "A", t: "normal" } (key, key-shift, type)
+ // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
+ // keys: [
+ // [{ k: "Esc", t: "fn" }, { k: "F1", t: "fn" }, { k: "F2", t: "fn" }, { k: "F3", t: "fn" }, { k: "F4", t: "fn" }, { k: "F5", t: "fn" }, { k: "F6", t: "fn" }, { k: "F7", t: "fn" }, { k: "F8", t: "fn" }, { k: "F9", t: "fn" }, { k: "F10", t: "fn" }, { k: "F11", t: "fn" }, { k: "F12", t: "fn" }, { k: "PrtSc", t: "fn" }, { k: "Del", t: "fn" }],
+ // [{ k: "`", ks: "~", t: "normal" }, { k: "1", ks: "!", t: "normal" }, { k: "2", ks: "@", t: "normal" }, { k: "3", ks: "#", t: "normal" }, { k: "4", ks: "$", t: "normal" }, { k: "5", ks: "%", t: "normal" }, { k: "6", ks: "^", t: "normal" }, { k: "7", ks: "&", t: "normal" }, { k: "8", ks: "*", t: "normal" }, { k: "9", ks: "(", t: "normal" }, { k: "0", ks: ")", t: "normal" }, { k: "-", ks: "_", t: "normal" }, { k: "=", ks: "+", t: "normal" }, { k: "Backspace", t: "shift" }],
+ // [{ k: "Tab", t: "tab" }, { k: "q", ks: "Q", t: "normal" }, { k: "w", ks: "W", t: "normal" }, { k: "e", ks: "E", t: "normal" }, { k: "r", ks: "R", t: "normal" }, { k: "t", ks: "T", t: "normal" }, { k: "y", ks: "Y", t: "normal" }, { k: "u", ks: "U", t: "normal" }, { k: "i", ks: "I", t: "normal" }, { k: "o", ks: "O", t: "normal" }, { k: "p", ks: "P", t: "normal" }, { k: "[", ks: "{", t: "normal" }, { k: "]", ks: "}", t: "normal" }, { k: "\\", ks: "|", t: "expand" }],
+ // [{ k: "Caps", t: "caps" }, { k: "a", ks: "A", t: "normal" }, { k: "s", ks: "S", t: "normal" }, { k: "d", ks: "D", t: "normal" }, { k: "f", ks: "F", t: "normal" }, { k: "g", ks: "G", t: "normal" }, { k: "h", ks: "H", t: "normal" }, { k: "j", ks: "J", t: "normal" }, { k: "k", ks: "K", t: "normal" }, { k: "l", ks: "L", t: "normal" }, { k: ";", ks: ":", t: "normal" }, { k: "'", ks: '"', t: "normal" }, { k: "Enter", t: "expand" }],
+ // [{ k: "Shift", t: "shift" }, { k: "z", ks: "Z", t: "normal" }, { k: "x", ks: "X", t: "normal" }, { k: "c", ks: "C", t: "normal" }, { k: "v", ks: "V", t: "normal" }, { k: "b", ks: "B", t: "normal" }, { k: "n", ks: "N", t: "normal" }, { k: "m", ks: "M", t: "normal" }, { k: ",", ks: "<", t: "normal" }, { k: ".", ks: ">", t: "normal" }, { k: "/", ks: "?", t: "normal" }, { k: "Shift", t: "expand" }],
+ // [{ k: "Ctrl", t: "control" }, { k: "Fn", t: "normal" }, { k: "Win", t: "normal" }, { k: "Alt", t: "normal" }, { k: "Space", t: "space" }, { k: "Alt", t: "normal" }, { k: "Menu", t: "normal" }, { k: "Ctrl", t: "control" }]
+ // ]
+ // A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"}
+ // A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"}
+ // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
+ keys: [
+ [
+ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 },
+ { keytype: "normal", label: "F1", shape: "fn", keycode: 59 },
+ { keytype: "normal", label: "F2", shape: "fn", keycode: 60 },
+ { keytype: "normal", label: "F3", shape: "fn", keycode: 61 },
+ { keytype: "normal", label: "F4", shape: "fn", keycode: 62 },
+ { keytype: "normal", label: "F5", shape: "fn", keycode: 63 },
+ { keytype: "normal", label: "F6", shape: "fn", keycode: 64 },
+ { keytype: "normal", label: "F7", shape: "fn", keycode: 65 },
+ { keytype: "normal", label: "F8", shape: "fn", keycode: 66 },
+ { keytype: "normal", label: "F9", shape: "fn", keycode: 67 },
+ { keytype: "normal", label: "F10", shape: "fn", keycode: 68 },
+ { keytype: "normal", label: "F11", shape: "fn", keycode: 87 },
+ { keytype: "normal", label: "F12", shape: "fn", keycode: 88 },
+ { keytype: "normal", label: "PrtSc", shape: "fn", keycode: 99 },
+ { keytype: "normal", label: "Del", shape: "fn", keycode: 111 }
+ ],
+ [
+ { keytype: "normal", label: "`", labelShift: "~", shape: "normal", keycode: 41 },
+ { keytype: "normal", label: "1", labelShift: "!", shape: "normal", keycode: 2 },
+ { keytype: "normal", label: "2", labelShift: "@", shape: "normal", keycode: 3 },
+ { keytype: "normal", label: "3", labelShift: "#", shape: "normal", keycode: 4 },
+ { keytype: "normal", label: "4", labelShift: "$", shape: "normal", keycode: 5 },
+ { keytype: "normal", label: "5", labelShift: "%", shape: "normal", keycode: 6 },
+ { keytype: "normal", label: "6", labelShift: "^", shape: "normal", keycode: 7 },
+ { keytype: "normal", label: "7", labelShift: "&", shape: "normal", keycode: 8 },
+ { keytype: "normal", label: "8", labelShift: "*", shape: "normal", keycode: 9 },
+ { keytype: "normal", label: "9", labelShift: "(", shape: "normal", keycode: 10 },
+ { keytype: "normal", label: "0", labelShift: ")", shape: "normal", keycode: 11 },
+ { keytype: "normal", label: "-", labelShift: "_", shape: "normal", keycode: 12 },
+ { keytype: "normal", label: "=", labelShift: "+", shape: "normal", keycode: 13 },
+ { keytype: "normal", label: "Backspace", shape: "expand", keycode: 14 }
+ ],
+ [
+ { keytype: "normal", label: "Tab", shape: "tab", keycode: 15 },
+ { keytype: "normal", label: "q", labelShift: "Q", shape: "normal", keycode: 16 },
+ { keytype: "normal", label: "w", labelShift: "W", shape: "normal", keycode: 17 },
+ { keytype: "normal", label: "e", labelShift: "E", shape: "normal", keycode: 18 },
+ { keytype: "normal", label: "r", labelShift: "R", shape: "normal", keycode: 19 },
+ { keytype: "normal", label: "t", labelShift: "T", shape: "normal", keycode: 20 },
+ { keytype: "normal", label: "y", labelShift: "Y", shape: "normal", keycode: 21 },
+ { keytype: "normal", label: "u", labelShift: "U", shape: "normal", keycode: 22 },
+ { keytype: "normal", label: "i", labelShift: "I", shape: "normal", keycode: 23 },
+ { keytype: "normal", label: "o", labelShift: "O", shape: "normal", keycode: 24 },
+ { keytype: "normal", label: "p", labelShift: "P", shape: "normal", keycode: 25 },
+ { keytype: "normal", label: "[", labelShift: "{", shape: "normal", keycode: 26 },
+ { keytype: "normal", label: "]", labelShift: "}", shape: "normal", keycode: 27 },
+ { keytype: "normal", label: "\\", labelShift: "|", shape: "expand", keycode: 43 }
+ ],
+ [
+ //{ keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, // not needed as double-pressing shift does that
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "normal", label: "a", labelShift: "A", shape: "normal", keycode: 30 },
+ { keytype: "normal", label: "s", labelShift: "S", shape: "normal", keycode: 31 },
+ { keytype: "normal", label: "d", labelShift: "D", shape: "normal", keycode: 32 },
+ { keytype: "normal", label: "f", labelShift: "F", shape: "normal", keycode: 33 },
+ { keytype: "normal", label: "g", labelShift: "G", shape: "normal", keycode: 34 },
+ { keytype: "normal", label: "h", labelShift: "H", shape: "normal", keycode: 35 },
+ { keytype: "normal", label: "j", labelShift: "J", shape: "normal", keycode: 36 },
+ { keytype: "normal", label: "k", labelShift: "K", shape: "normal", keycode: 37 },
+ { keytype: "normal", label: "l", labelShift: "L", shape: "normal", keycode: 38 },
+ { keytype: "normal", label: ";", labelShift: ":", shape: "normal", keycode: 39 },
+ { keytype: "normal", label: "'", labelShift: '"', shape: "normal", keycode: 40 },
+ { keytype: "normal", label: "Enter", shape: "expand", keycode: 28 }
+ ],
+ [
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 },
+ { keytype: "normal", label: "z", labelShift: "Z", shape: "normal", keycode: 44 },
+ { keytype: "normal", label: "x", labelShift: "X", shape: "normal", keycode: 45 },
+ { keytype: "normal", label: "c", labelShift: "C", shape: "normal", keycode: 46 },
+ { keytype: "normal", label: "v", labelShift: "V", shape: "normal", keycode: 47 },
+ { keytype: "normal", label: "b", labelShift: "B", shape: "normal", keycode: 48 },
+ { keytype: "normal", label: "n", labelShift: "N", shape: "normal", keycode: 49 },
+ { keytype: "normal", label: "m", labelShift: "M", shape: "normal", keycode: 50 },
+ { keytype: "normal", label: ",", labelShift: "<", shape: "normal", keycode: 51 },
+ { keytype: "normal", label: ".", labelShift: ">", shape: "normal", keycode: 52 },
+ { keytype: "normal", label: "/", labelShift: "?", shape: "normal", keycode: 53 },
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 } // optional
+ ],
+ [
+ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 },
+ // { label: "Super", shape: "normal", keycode: 125 }, // dangerous
+ { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 },
+ { keytype: "normal", label: "Space", shape: "space", keycode: 57 },
+ { keytype: "modkey", label: "Alt", shape: "normal", keycode: 100 },
+ // { label: "Super", shape: "normal", keycode: 126 }, // dangerous
+ { keytype: "normal", label: "Menu", shape: "normal", keycode: 139 },
+ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 }
+ ]
+ ]
+ },
+ qwertz_full: {
+ name: "QWERTZ - Full",
+ name_short: "DE",
+ comment: "Keyboard layout commonly used in German-speaking countries",
+ keys: [
+ [
+ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 },
+ { keytype: "normal", label: "F1", shape: "fn", keycode: 59 },
+ { keytype: "normal", label: "F2", shape: "fn", keycode: 60 },
+ { keytype: "normal", label: "F3", shape: "fn", keycode: 61 },
+ { keytype: "normal", label: "F4", shape: "fn", keycode: 62 },
+ { keytype: "normal", label: "F5", shape: "fn", keycode: 63 },
+ { keytype: "normal", label: "F6", shape: "fn", keycode: 64 },
+ { keytype: "normal", label: "F7", shape: "fn", keycode: 65 },
+ { keytype: "normal", label: "F8", shape: "fn", keycode: 66 },
+ { keytype: "normal", label: "F9", shape: "fn", keycode: 67 },
+ { keytype: "normal", label: "F10", shape: "fn", keycode: 68 },
+ { keytype: "normal", label: "F11", shape: "fn", keycode: 87 },
+ { keytype: "normal", label: "F12", shape: "fn", keycode: 88 },
+ { keytype: "normal", label: "Druck", shape: "fn", keycode: 99 },
+ { keytype: "normal", label: "Entf", shape: "fn", keycode: 111 }
+ ],
+ [
+ { keytype: "normal", label: "^", labelShift: "°", labelAlt: "′", shape: "normal", keycode: 41 },
+ { keytype: "normal", label: "1", labelShift: "!", labelAlt: "¹", shape: "normal", keycode: 2 },
+ { keytype: "normal", label: "2", labelShift: "\"", labelAlt: "²", shape: "normal", keycode: 3 },
+ { keytype: "normal", label: "3", labelShift: "§", labelAlt: "³", shape: "normal", keycode: 4 },
+ { keytype: "normal", label: "4", labelShift: "$", labelAlt: "¼", shape: "normal", keycode: 5 },
+ { keytype: "normal", label: "5", labelShift: "%", labelAlt: "½", shape: "normal", keycode: 6 },
+ { keytype: "normal", label: "6", labelShift: "&", labelAlt: "¬", shape: "normal", keycode: 7 },
+ { keytype: "normal", label: "7", labelShift: "/", labelAlt: "{", shape: "normal", keycode: 8 },
+ { keytype: "normal", label: "8", labelShift: "(", labelAlt: "[", shape: "normal", keycode: 9 },
+ { keytype: "normal", label: "9", labelShift: ")", labelAlt: "]", shape: "normal", keycode: 10 },
+ { keytype: "normal", label: "0", labelShift: "=", labelAlt: "}", shape: "normal", keycode: 11 },
+ { keytype: "normal", label: "ß", labelShift: "?", labelAlt: "\\", shape: "normal", keycode: 12 },
+ { keytype: "normal", label: "´", labelShift: "`", labelAlt: "¸", shape: "normal", keycode: 13 },
+ { keytype: "normal", label: "⟵", shape: "expand", keycode: 14 }
+ ],
+ [
+ { keytype: "normal", label: "Tab ⇆", shape: "tab", keycode: 15 },
+ { keytype: "normal", label: "q", labelShift: "Q", labelAlt: "@", shape: "normal", keycode: 16 },
+ { keytype: "normal", label: "w", labelShift: "W", labelAlt: "ſ", shape: "normal", keycode: 17 },
+ { keytype: "normal", label: "e", labelShift: "E", labelAlt: "€", shape: "normal", keycode: 18 },
+ { keytype: "normal", label: "r", labelShift: "R", labelAlt: "¶", shape: "normal", keycode: 19 },
+ { keytype: "normal", label: "t", labelShift: "T", labelAlt: "ŧ", shape: "normal", keycode: 20 },
+ { keytype: "normal", label: "z", labelShift: "Z", labelAlt: "←", shape: "normal", keycode: 21 },
+ { keytype: "normal", label: "u", labelShift: "U", labelAlt: "↓", shape: "normal", keycode: 22 },
+ { keytype: "normal", label: "i", labelShift: "I", labelAlt: "→", shape: "normal", keycode: 23 },
+ { keytype: "normal", label: "o", labelShift: "O", labelAlt: "ø", shape: "normal", keycode: 24 },
+ { keytype: "normal", label: "p", labelShift: "P", labelAlt: "þ", shape: "normal", keycode: 25 },
+ { keytype: "normal", label: "ü", labelShift: "Ü", labelAlt: "¨", shape: "normal", keycode: 26 },
+ { keytype: "normal", label: "+", labelShift: "*", labelAlt: "~", shape: "normal", keycode: 27 },
+ { keytype: "normal", label: "↵", shape: "expand", keycode: 28 }
+ ],
+ [
+ //{ keytype: "normal", label: "Umschalt ⇩", shape: "caps", keycode: 58 },
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "spacer", label: "", shape: "empty" },
+ { keytype: "normal", label: "a", labelShift: "A", labelAlt: "æ", shape: "normal", keycode: 30 },
+ { keytype: "normal", label: "s", labelShift: "S", labelAlt: "ſ", shape: "normal", keycode: 31 },
+ { keytype: "normal", label: "d", labelShift: "D", labelAlt: "ð", shape: "normal", keycode: 32 },
+ { keytype: "normal", label: "f", labelShift: "F", labelAlt: "đ", shape: "normal", keycode: 33 },
+ { keytype: "normal", label: "g", labelShift: "G", labelAlt: "ŋ", shape: "normal", keycode: 34 },
+ { keytype: "normal", label: "h", labelShift: "H", labelAlt: "ħ", shape: "normal", keycode: 35 },
+ { keytype: "normal", label: "j", labelShift: "J", labelAlt: "", shape: "normal", keycode: 36 },
+ { keytype: "normal", label: "k", labelShift: "K", labelAlt: "ĸ", shape: "normal", keycode: 37 },
+ { keytype: "normal", label: "l", labelShift: "L", labelAlt: "ł", shape: "normal", keycode: 38 },
+ { keytype: "normal", label: "ö", labelShift: "Ö", labelAlt: "˝", shape: "normal", keycode: 39 },
+ { keytype: "normal", label: "ä", labelShift: 'Ä', labelAlt: "^", shape: "normal", keycode: 40 },
+ { keytype: "normal", label: "#", labelShift: '\'', labelAlt: "’", shape: "normal", keycode: 43 },
+ { keytype: "spacer", label: "", shape: "empty" },
+ //{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 }
+ ],
+ [
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 },
+ { keytype: "normal", label: "<", labelShift: ">", labelAlt: "|", shape: "normal", keycode: 86 },
+ { keytype: "normal", label: "y", labelShift: "Y", labelAlt: "»", shape: "normal", keycode: 44 },
+ { keytype: "normal", label: "x", labelShift: "X", labelAlt: "«", shape: "normal", keycode: 45 },
+ { keytype: "normal", label: "c", labelShift: "C", labelAlt: "¢", shape: "normal", keycode: 46 },
+ { keytype: "normal", label: "v", labelShift: "V", labelAlt: "„", shape: "normal", keycode: 47 },
+ { keytype: "normal", label: "b", labelShift: "B", labelAlt: "“", shape: "normal", keycode: 48 },
+ { keytype: "normal", label: "n", labelShift: "N", labelAlt: "”", shape: "normal", keycode: 49 },
+ { keytype: "normal", label: "m", labelShift: "M", labelAlt: "µ", shape: "normal", keycode: 50 },
+ { keytype: "normal", label: ",", labelShift: ";", labelAlt: "·", shape: "normal", keycode: 51 },
+ { keytype: "normal", label: ".", labelShift: ":", labelAlt: "…", shape: "normal", keycode: 52 },
+ { keytype: "normal", label: "-", labelShift: "_", labelAlt: "–", shape: "normal", keycode: 53 },
+ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 }, // optional
+ ],
+ [
+ { keytype: "modkey", label: "Strg", shape: "control", keycode: 29 },
+ //{ keytype: "normal", label: "", shape: "normal", keycode: 125 }, // dangerous
+ { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 },
+ { keytype: "normal", label: "Leertaste", shape: "space", keycode: 57 },
+ { keytype: "modkey", label: "Alt Gr", shape: "normal", keycode: 100 },
+ // { label: "Super", shape: "normal", keycode: 126 }, // dangerous
+ //{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, // doesn't work?
+ { keytype: "modkey", label: "Strg", shape: "control", keycode: 97 },
+ { keytype: "normal", label: "⇦", shape: "normal", keycode: 105 },
+ { keytype: "normal", label: "⇨", shape: "normal", keycode: 106 },
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/modules/styling/config/data/languages.js b/modules/styling/config/data/languages.js
new file mode 100644
index 0000000..c61eedd
--- /dev/null
+++ b/modules/styling/config/data/languages.js
@@ -0,0 +1,62 @@
+// For keyboard layout in lib/statusicons.js
+// This list is not exhaustive. It just includes known/possible languages of users of my dotfiles
+// Add your language here if you use multi-lang xkb input. Else, ignore
+// Note that something like "French (Canada)" should go before "French"
+// and "English (US)" should go before "English"
+export const languages = [
+ {
+ layout: 'us',
+ name: 'English (US)',
+ flag: '🇺🇸'
+ },
+ {
+ layout: 'ru',
+ name: 'Russian',
+ flag: '🇷🇺',
+ },
+ {
+ layout: 'pl',
+ name: 'Polish',
+ flag: '🇷🇵🇵🇱',
+ },
+ {
+ layout: 'ro',
+ name: 'Romanian',
+ flag: '🇷🇴',
+ },
+ {
+ layout: 'ca',
+ name: 'French (Canada)',
+ flag: '🇫🇷',
+ },
+ {
+ layout: 'fr',
+ name: 'French',
+ flag: '🇫🇷',
+ },
+ {
+ layout: 'tr',
+ name: 'Turkish',
+ flag: '🇹🇷',
+ },
+ {
+ layout: 'jp',
+ name: 'Japanese',
+ flag: '🇯🇵',
+ },
+ {
+ layout: 'cn',
+ name: 'Chinese',
+ flag: '🇨🇳',
+ },
+ {
+ layout: 'vn',
+ name: 'Vietnamese',
+ flag: '🇻🇳',
+ },
+ {
+ layout: 'undef',
+ name: 'Undefined',
+ flag: '🧐',
+ },
+]
\ No newline at end of file
diff --git a/modules/styling/config/data/quicklaunches.js b/modules/styling/config/data/quicklaunches.js
new file mode 100644
index 0000000..a5a9562
--- /dev/null
+++ b/modules/styling/config/data/quicklaunches.js
@@ -0,0 +1,14 @@
+export const quickLaunchItems = [
+ {
+ "name": "GitHub + Files×2",
+ "command": "github-desktop & nautilus --new-window & nautilus --new-window &"
+ },
+ {
+ "name": "Terminal×2",
+ "command": "foot & foot &"
+ },
+ {
+ "name": "Discord + Youtube + Github",
+ "command": "xdg-open 'https://discord.com/app' && xdg-open 'https://youtube.com/' && xdg-open 'https://github.com/' &"
+ },
+]
\ No newline at end of file
diff --git a/modules/styling/config/data/quotes.js b/modules/styling/config/data/quotes.js
new file mode 100644
index 0000000..7cea990
--- /dev/null
+++ b/modules/styling/config/data/quotes.js
@@ -0,0 +1,14 @@
+export const quotes = [
+ {
+ quote: 'Nvidia, fuck you',
+ author: 'Linus Torvalds',
+ },
+ {
+ quote: 'reproducible system? cock and vagina?',
+ author: 'vaxry',
+ },
+ {
+ quote: "haha pointers hee hee i love pointe-\\\nProcess Vaxry exited with signal SIGSEGV",
+ author: 'vaxry',
+ }
+];
diff --git a/modules/styling/config/data/sourceviewtheme.xml b/modules/styling/config/data/sourceviewtheme.xml
new file mode 100644
index 0000000..61b3324
--- /dev/null
+++ b/modules/styling/config/data/sourceviewtheme.xml
@@ -0,0 +1,106 @@
+
+
+ end_4
+ <_description>Monokai port but very random
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/styling/config/data/weather.js b/modules/styling/config/data/weather.js
new file mode 100644
index 0000000..57c34fb
--- /dev/null
+++ b/modules/styling/config/data/weather.js
@@ -0,0 +1,94 @@
+export const WWO_CODE = {
+ "113": "Sunny",
+ "116": "PartlyCloudy",
+ "119": "Cloudy",
+ "122": "VeryCloudy",
+ "143": "Fog",
+ "176": "LightShowers",
+ "179": "LightSleetShowers",
+ "182": "LightSleet",
+ "185": "LightSleet",
+ "200": "ThunderyShowers",
+ "227": "LightSnow",
+ "230": "HeavySnow",
+ "248": "Fog",
+ "260": "Fog",
+ "263": "LightShowers",
+ "266": "LightRain",
+ "281": "LightSleet",
+ "284": "LightSleet",
+ "293": "LightRain",
+ "296": "LightRain",
+ "299": "HeavyShowers",
+ "302": "HeavyRain",
+ "305": "HeavyShowers",
+ "308": "HeavyRain",
+ "311": "LightSleet",
+ "314": "LightSleet",
+ "317": "LightSleet",
+ "320": "LightSnow",
+ "323": "LightSnowShowers",
+ "326": "LightSnowShowers",
+ "329": "HeavySnow",
+ "332": "HeavySnow",
+ "335": "HeavySnowShowers",
+ "338": "HeavySnow",
+ "350": "LightSleet",
+ "353": "LightShowers",
+ "356": "HeavyShowers",
+ "359": "HeavyRain",
+ "362": "LightSleetShowers",
+ "365": "LightSleetShowers",
+ "368": "LightSnowShowers",
+ "371": "HeavySnowShowers",
+ "374": "LightSleetShowers",
+ "377": "LightSleet",
+ "386": "ThunderyShowers",
+ "389": "ThunderyHeavyRain",
+ "392": "ThunderySnowShowers",
+ "395": "HeavySnowShowers",
+}
+
+export const WEATHER_SYMBOL = {
+ "Unknown": "air",
+ "Cloudy": "cloud",
+ "Fog": "foggy",
+ "HeavyRain": "rainy",
+ "HeavyShowers": "rainy",
+ "HeavySnow": "snowing",
+ "HeavySnowShowers": "snowing",
+ "LightRain": "rainy",
+ "LightShowers": "rainy",
+ "LightSleet": "rainy",
+ "LightSleetShowers": "rainy",
+ "LightSnow": "cloudy_snowing",
+ "LightSnowShowers": "cloudy_snowing",
+ "PartlyCloudy": "partly_cloudy_day",
+ "Sunny": "clear_day",
+ "ThunderyHeavyRain": "thunderstorm",
+ "ThunderyShowers": "thunderstorm",
+ "ThunderySnowShowers": "thunderstorm",
+ "VeryCloudy": "cloud",
+}
+
+export const NIGHT_WEATHER_SYMBOL = {
+ "Unknown": "air",
+ "Cloudy": "cloud",
+ "Fog": "foggy",
+ "HeavyRain": "rainy",
+ "HeavyShowers": "rainy",
+ "HeavySnow": "snowing",
+ "HeavySnowShowers": "snowing",
+ "LightRain": "rainy",
+ "LightShowers": "rainy",
+ "LightSleet": "rainy",
+ "LightSleetShowers": "rainy",
+ "LightSnow": "cloudy_snowing",
+ "LightSnowShowers": "cloudy_snowing",
+ "PartlyCloudy": "partly_cloudy_night",
+ "Sunny": "clear_night",
+ "ThunderyHeavyRain": "thunderstorm",
+ "ThunderyShowers": "thunderstorm",
+ "ThunderySnowShowers": "thunderstorm",
+ "VeryCloudy": "cloud",
+}
\ No newline at end of file
diff --git a/modules/styling/config/imports.js b/modules/styling/config/imports.js
new file mode 100644
index 0000000..e5e1ab8
--- /dev/null
+++ b/modules/styling/config/imports.js
@@ -0,0 +1,38 @@
+const resource = file => `resource:///com/github/Aylur/ags/${file}.js`;
+const require = async file => (await import(resource(file))).default;
+const service = async file => (await require(`service/${file}`));
+
+export const App = await require('app');
+// export const Widget = await require('widget');
+// export const Service = await require('service');
+// export const Variable = await require('variable');
+export const Utils = await import(resource('utils'));
+// export const Applications = await service('applications');
+// export const Audio = await service('audio');
+// export const Battery = await service('battery');
+// export const Bluetooth = await service('bluetooth');
+// export const Hyprland = await service('hyprland');
+export const Mpris = await service('mpris');
+export const Network = await service('network');
+export const Notifications = await service('notifications');
+// export const SystemTray = await service('systemtray');
+
+globalThis['App'] = App; //////////////////////////////
+// globalThis['Widget'] = Widget;
+// globalThis['Service'] = Service;
+// globalThis['Variable'] = Variable;
+globalThis['Utils'] = Utils; ///////////////////////////
+// globalThis['Applications'] = Applications;
+// globalThis['Audio'] = Audio;
+// globalThis['Battery'] = Battery;
+// globalThis['Bluetooth'] = Bluetooth;
+// globalThis['Hyprland'] = Hyprland;
+globalThis['Mpris'] = Mpris;
+globalThis['Network'] = Network;
+globalThis['Notifications'] = Notifications;
+// globalThis['SystemTray'] = SystemTray;
+
+
+const { exec } = Utils;
+export const SCREEN_WIDTH = Number(exec(`bash -c "xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f1 | head -1" | awk '{print $1}'`));
+export const SCREEN_HEIGHT = Number(exec(`bash -c "xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2 | head -1" | awk '{print $1}'`));
diff --git a/modules/styling/config/lib/advancedwidgets.js b/modules/styling/config/lib/advancedwidgets.js
new file mode 100644
index 0000000..e8dc378
--- /dev/null
+++ b/modules/styling/config/lib/advancedwidgets.js
@@ -0,0 +1,78 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+const { Revealer, Scrollable } = Widget;
+
+export const MarginRevealer = ({
+ transition = 'slide_down',
+ child,
+ revealChild,
+ showClass = 'element-show', // These are for animation curve, they don't really hide
+ hideClass = 'element-hide', // Don't put margins in these classes!
+ extraSetup = () => { },
+ ...rest
+}) => {
+ const widget = Scrollable({
+ ...rest,
+ attribute: {
+ 'revealChild': true, // It'll be set to false after init if it's supposed to hide
+ 'transition': transition,
+ 'show': () => {
+ if (widget.attribute.revealChild) return;
+ widget.hscroll = 'never';
+ widget.vscroll = 'never';
+ child.toggleClassName(hideClass, false);
+ child.toggleClassName(showClass, true);
+ widget.attribute.revealChild = true;
+ child.css = 'margin: 0px;';
+ },
+ 'hide': () => {
+ if (!widget.attribute.revealChild) return;
+ child.toggleClassName(hideClass, true);
+ child.toggleClassName(showClass, false);
+ widget.attribute.revealChild = false;
+ if (widget.attribute.transition == 'slide_left')
+ child.css = `margin-right: -${child.get_allocated_width()}px;`;
+ else if (widget.attribute.transition == 'slide_right')
+ child.css = `margin-left: -${child.get_allocated_width()}px;`;
+ else if (widget.attribute.transition == 'slide_up')
+ child.css = `margin-bottom: -${child.get_allocated_height()}px;`;
+ else if (widget.attribute.transition == 'slide_down')
+ child.css = `margin-top: -${child.get_allocated_height()}px;`;
+ },
+ 'toggle': () => {
+ if (widget.attribute.revealChild) widget.attribute.hide();
+ else widget.attribute.show();
+ },
+ },
+ child: child,
+ hscroll: `${revealChild ? 'never' : 'always'}`,
+ vscroll: `${revealChild ? 'never' : 'always'}`,
+ setup: (self) => {
+ extraSetup(self);
+ }
+ });
+ child.toggleClassName(`${revealChild ? showClass : hideClass}`, true);
+ return widget;
+}
+
+// TODO: Allow reveal update. Currently this just helps at declaration
+export const DoubleRevealer = ({
+ transition1 = 'slide_right',
+ transition2 = 'slide_left',
+ duration1 = 150,
+ duration2 = 150,
+ child,
+ revealChild,
+}) => {
+ return Revealer({
+ transition: transition1,
+ transitionDuration: duration1,
+ revealChild: revealChild,
+ child: Revealer({
+ transition: transition2,
+ transitionDuration: duration2,
+ revealChild: revealChild,
+ child: child,
+ })
+ })
+}
diff --git a/modules/styling/config/lib/animatedcircularprogress.js b/modules/styling/config/lib/animatedcircularprogress.js
new file mode 100644
index 0000000..83c1842
--- /dev/null
+++ b/modules/styling/config/lib/animatedcircularprogress.js
@@ -0,0 +1,106 @@
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+
+// -- Styling --
+// min-height for diameter
+// min-width for trough stroke
+// padding for space between trough and progress
+// margin for space between widget and parent
+// background-color for trough color
+// color for progress color
+// -- Usage --
+// font size for progress value (0-100px) (hacky i know, but i want animations)
+export const AnimatedCircProg = ({
+ initFrom = 0,
+ initTo = 0,
+ initAnimTime = 2900,
+ initAnimPoints = 1,
+ extraSetup = () => { },
+ ...rest
+}) => Widget.DrawingArea({
+ ...rest,
+ css: `${initFrom != initTo ? 'font-size: ' + initFrom + 'px; transition: ' + initAnimTime + 'ms linear;' : ''}`,
+ setup: (area) => {
+ const styleContext = area.get_style_context();
+ const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
+ const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
+ const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
+ const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
+ const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
+ area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
+ area.connect('draw', Lang.bind(area, (area, cr) => {
+ const styleContext = area.get_style_context();
+ const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
+ const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
+ const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
+ const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
+ const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
+ area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
+
+ const progressValue = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100.0;
+
+ const bg_stroke = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const fg_stroke = bg_stroke - padding;
+ const radius = Math.min(width, height) / 2.0 - Math.max(bg_stroke, fg_stroke) / 2.0;
+ const center_x = width / 2.0 + marginLeft;
+ const center_y = height / 2.0 + marginTop;
+ const start_angle = -Math.PI / 2.0;
+ const end_angle = start_angle + (2 * Math.PI * progressValue);
+ const start_x = center_x + Math.cos(start_angle) * radius;
+ const start_y = center_y + Math.sin(start_angle) * radius;
+ const end_x = center_x + Math.cos(end_angle) * radius;
+ const end_y = center_y + Math.sin(end_angle) * radius;
+
+ // Draw background
+ const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
+ cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
+ cr.setLineWidth(bg_stroke);
+ cr.stroke();
+
+ if (progressValue == 0) return;
+
+ // Draw progress
+ const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
+ cr.arc(center_x, center_y, radius, start_angle, end_angle);
+ cr.setLineWidth(fg_stroke);
+ cr.stroke();
+
+ // Draw rounded ends for progress arcs
+ cr.setLineWidth(0);
+ cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
+ cr.fill();
+ cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
+ cr.fill();
+ }));
+
+ // Init animation
+ if (initFrom != initTo) {
+ area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
+ Utils.timeout(20, () => {
+ area.css = `font-size: ${initTo}px;`;
+ }, area)
+ const transitionDistance = initTo - initFrom;
+ const oneStep = initAnimTime / initAnimPoints;
+ area.css = `
+ font-size: ${initFrom}px;
+ transition: ${oneStep}ms linear;
+ `;
+ for (let i = 0; i < initAnimPoints; i++) {
+ Utils.timeout(Math.max(10, i * oneStep), () => {
+ if(!area) return;
+ area.css = `${initFrom != initTo ? 'font-size: ' + (initFrom + (transitionDistance / initAnimPoints * (i + 1))) + 'px;' : ''}`;
+ });
+ }
+ }
+ else area.css = 'font-size: 0px;';
+ extraSetup(area);
+ },
+})
\ No newline at end of file
diff --git a/modules/styling/config/lib/calendarlayout.js b/modules/styling/config/lib/calendarlayout.js
new file mode 100644
index 0000000..35b481d
--- /dev/null
+++ b/modules/styling/config/lib/calendarlayout.js
@@ -0,0 +1,85 @@
+function checkLeapYear(year) {
+ return (
+ year % 400 == 0 ||
+ (year % 4 == 0 && year % 100 != 0));
+}
+
+function getMonthDays(month, year) {
+ const leapYear = checkLeapYear(year);
+ if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31;
+ if (month == 2 && leapYear) return 29;
+ if (month == 2 && !leapYear) return 28;
+ return 30;
+}
+
+function getNextMonthDays(month, year) {
+ const leapYear = checkLeapYear(year);
+ if (month == 1 && leapYear) return 29;
+ if (month == 1 && !leapYear) return 28;
+ if (month == 12) return 31;
+ if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
+ return 31;
+}
+
+function getPrevMonthDays(month, year) {
+ const leapYear = checkLeapYear(year);
+ if (month == 3 && leapYear) return 29;
+ if (month == 3 && !leapYear) return 28;
+ if (month == 1) return 31;
+ if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
+ return 31;
+}
+
+export function getCalendarLayout(dateObject, highlight) {
+ if (!dateObject) dateObject = new Date();
+ const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK
+ const day = dateObject.getDate();
+ const month = dateObject.getMonth() + 1;
+ const year = dateObject.getFullYear();
+ const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7;
+ const daysInMonth = getMonthDays(month, year);
+ const daysInNextMonth = getNextMonthDays(month, year);
+ const daysInPrevMonth = getPrevMonthDays(month, year);
+
+ // Fill
+ var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1);
+ var toFill, dim;
+ if(weekdayOfMonthFirst == 0) {
+ toFill = 1;
+ dim = daysInMonth;
+ }
+ else {
+ toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1));
+ dim = daysInPrevMonth;
+ }
+ var calendar = [...Array(6)].map(() => Array(7));
+ var i = 0, j = 0;
+ while (i < 6 && j < 7) {
+ calendar[i][j] = {
+ "day": toFill,
+ "today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : (
+ monthDiff == 0 ? 0 :
+ -1
+ ))
+ };
+ // Increment
+ toFill++;
+ if (toFill > dim) { // Next month?
+ monthDiff++;
+ if (monthDiff == 0)
+ dim = daysInMonth;
+ else if (monthDiff == 1)
+ dim = daysInNextMonth;
+ toFill = 1;
+ }
+ // Next tile
+ j++;
+ if (j == 7) {
+ j = 0;
+ i++;
+ }
+
+ }
+ return calendar;
+}
+
diff --git a/modules/styling/config/lib/configwidgets.js b/modules/styling/config/lib/configwidgets.js
new file mode 100644
index 0000000..fa12934
--- /dev/null
+++ b/modules/styling/config/lib/configwidgets.js
@@ -0,0 +1,126 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import { MaterialIcon } from './materialicon.js';
+import { setupCursorHover } from './cursorhover.js';
+const { Box, Button, Label, Revealer } = Widget;
+
+export const ConfigToggle = ({ icon, name, desc = '', initValue, onChange, ...rest }) => {
+ let value = initValue;
+ const toggleIcon = Label({
+ className: `icon-material txt-bold ${value ? '' : 'txt-poof'}`,
+ label: `${value ? 'check' : ''}`,
+ })
+ const toggleButtonIndicator = Box({
+ className: `switch-fg ${value ? 'switch-fg-true' : ''}`,
+ vpack: 'center',
+ hpack: 'start',
+ homogeneous: true,
+ children: [toggleIcon,],
+ });
+ const toggleButton = Box({
+ hpack: 'end',
+ className: `switch-bg ${value ? 'switch-bg-true' : ''}`,
+ homogeneous: true,
+ children: [toggleButtonIndicator,],
+ });
+ const widgetContent = Box({
+ tooltipText: desc,
+ className: 'txt spacing-h-5 configtoggle-box',
+ children: [
+ MaterialIcon(icon, 'norm'),
+ Label({
+ className: 'txt txt-small',
+ label: name,
+ }),
+ Box({ hexpand: true }),
+ toggleButton,
+ ]
+ });
+ const interactionWrapper = Button({
+ attribute: {
+ toggle: (newValue) => {
+ value = !value;
+ toggleIcon.toggleClassName('switch-fg-toggling-false', false);
+ if (!value) {
+ toggleIcon.label = '';
+ toggleIcon.toggleClassName('txt-poof', true);
+ }
+ toggleButtonIndicator.toggleClassName('switch-fg-true', value);
+ toggleButton.toggleClassName('switch-bg-true', value);
+ if (value) Utils.timeout(1, () => {
+ toggleIcon.label = 'check';
+ toggleIcon.toggleClassName('txt-poof', false);
+ })
+ onChange(interactionWrapper, value);
+ }
+ },
+ child: widgetContent,
+ onClicked: (self) => self.attribute.toggle(self),
+ setup: (button) => {
+ setupCursorHover(button),
+ button.connect('pressed', () => { // mouse down
+ toggleIcon.toggleClassName('txt-poof', true);
+ toggleIcon.toggleClassName('switch-fg-true', false);
+ if (!value) toggleIcon.toggleClassName('switch-fg-toggling-false', true);
+ });
+ },
+ ...rest,
+ });
+ return interactionWrapper;
+}
+
+export const ConfigSegmentedSelection = ({
+ icon, name, desc = '',
+ options = [{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
+ initIndex = 0,
+ onChange,
+ ...rest
+}) => {
+ let lastSelected = initIndex;
+ let value = options[initIndex].value;
+ const widget = Box({
+ tooltipText: desc,
+ className: 'segment-container',
+ // homogeneous: true,
+ children: options.map((option, id) => {
+ const selectedIcon = Revealer({
+ revealChild: id == initIndex,
+ transition: 'slide_right',
+ transitionDuration: 150,
+ child: MaterialIcon('check', 'norm')
+ });
+ return Button({
+ setup: setupCursorHover,
+ className: `segment-btn ${id == initIndex ? 'segment-btn-enabled' : ''}`,
+ child: Box({
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ selectedIcon,
+ Label({
+ label: option.name,
+ })
+ ]
+ }),
+ onClicked: (self) => {
+ value = option.value;
+ const kids = widget.get_children();
+ kids[lastSelected].toggleClassName('segment-btn-enabled', false);
+ kids[lastSelected].get_children()[0].get_children()[0].revealChild = false;
+ lastSelected = id;
+ self.toggleClassName('segment-btn-enabled', true);
+ selectedIcon.revealChild = true;
+ onChange(option.value, option.name);
+ }
+ })
+ }),
+ ...rest,
+ });
+ return widget;
+
+}
+
+export const ConfigGap = ({ vertical = true, size = 5, ...rest }) => Box({
+ className: `gap-${vertical ? 'v' : 'h'}-${size}`,
+ ...rest,
+})
\ No newline at end of file
diff --git a/modules/styling/config/lib/cursorhover.js b/modules/styling/config/lib/cursorhover.js
new file mode 100644
index 0000000..89be913
--- /dev/null
+++ b/modules/styling/config/lib/cursorhover.js
@@ -0,0 +1,57 @@
+const { Gdk } = imports.gi;
+
+export function setupCursorHover(button) { // Hand pointing cursor on hover
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+
+}
+
+export function setupCursorHoverAim(button) { // Crosshair cursor on hover
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'crosshair');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverGrab(button) { // Hand ready to grab on hover
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'grab');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverInfo(button) { // "?" mark cursor on hover
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'help');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
diff --git a/modules/styling/config/lib/materialicon.js b/modules/styling/config/lib/materialicon.js
new file mode 100644
index 0000000..029f0ed
--- /dev/null
+++ b/modules/styling/config/lib/materialicon.js
@@ -0,0 +1,7 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+export const MaterialIcon = (icon, size, props = {}) => Widget.Label({
+ className: `icon-material txt-${size}`,
+ label: icon,
+ ...props,
+})
diff --git a/modules/styling/config/lib/md2pango.js b/modules/styling/config/lib/md2pango.js
new file mode 100644
index 0000000..054752b
--- /dev/null
+++ b/modules/styling/config/lib/md2pango.js
@@ -0,0 +1,86 @@
+// Converts from Markdown to Pango. This does not support code blocks.
+// For illogical-impulse, code blocks are treated separately, in their own GtkSourceView widgets.
+// Partly inherited from https://github.com/ubunatic/md2pango
+
+const monospaceFonts = 'JetBrains Mono NF, JetBrains Mono Nerd Font, JetBrains Mono NL, SpaceMono NF, SpaceMono Nerd Font, monospace';
+
+const replacements = {
+ 'indents': [
+ { name: 'BULLET', re: /^(\s*)([\*\-]\s)(.*)(\s*)$/, sub: ' $1- $3' },
+ { name: 'NUMBERING', re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: ' $1 $2' },
+ ],
+ 'escapes': [
+ { name: 'COMMENT', re: //, sub: '' },
+ { name: 'AMPERSTAND', re: /&/g, sub: '&' },
+ { name: 'LESSTHAN', re: //g, sub: '>' },
+ ],
+ 'sections': [
+ { name: 'H1', re: /^(#\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H2', re: /^(##\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H3', re: /^(###\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H4', re: /^(####\s+)(.*)(\s*)$/, sub: '$2' },
+ { name: 'H5', re: /^(#####\s+)(.*)(\s*)$/, sub: '$2' },
+ ],
+ 'styles': [
+ { name: 'BOLD', re: /(\*\*)(\S[\s\S]*?\S)(\*\*)/g, sub: "$2" },
+ { name: 'UND', re: /(__)(\S[\s\S]*?\S)(__)/g, sub: "$2" },
+ { name: 'EMPH', re: /\*(\S.*?\S)\*/g, sub: "$1" },
+ // { name: 'EMPH', re: /_(\S.*?\S)_/g, sub: "$1" },
+ { name: 'HEXCOLOR', re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: '#$1' },
+ { name: 'INLCODE', re: /(`)([^`]*)(`)/g, sub: '$2' },
+ // { name: 'UND', re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "$2" },
+ ],
+}
+
+const replaceCategory = (text, replaces) => {
+ for (const type of replaces) {
+ text = text.replace(type.re, type.sub);
+ }
+ return text;
+}
+
+// Main function
+
+export default (text) => {
+ let lines = text.split('\n')
+ let output = [];
+ // Replace
+ for (const line of lines) {
+ let result = line;
+ result = replaceCategory(result, replacements.indents);
+ result = replaceCategory(result, replacements.escapes);
+ result = replaceCategory(result, replacements.sections);
+ result = replaceCategory(result, replacements.styles);
+ output.push(result)
+ }
+ // Remove trailing whitespaces
+ output = output.map(line => line.replace(/ +$/, ''))
+ return output.join('\n');
+}
+
+export const markdownTest = `# Heading 1
+## Heading 2
+### Heading 3
+#### Heading 4
+##### Heading 5
+1. yes
+2. no
+127. well
+- Bulletpoint starting with minus
+* Bulletpoint starting with asterisk
+---
+- __Underline__ __ No underline __
+- **Bold** ** No bold **
+- _Italics1_ *Italics2* _ No Italics _
+- A color: #D6BAFF
+- nvidia green: #7ABB08
+ - sub-item
+\`\`\`javascript
+// A code block!
+myArray = [23, 123, 43, 54, '6969'];
+console.log('uwu');
+\`\`\`
+- Random instruction thing
+ - To update arch lincox, run \`sudo pacman -Syu\`
+`;
\ No newline at end of file
diff --git a/modules/styling/config/lib/navigationindicator.js b/modules/styling/config/lib/navigationindicator.js
new file mode 100644
index 0000000..173c13b
--- /dev/null
+++ b/modules/styling/config/lib/navigationindicator.js
@@ -0,0 +1,71 @@
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+// min-height/min-width for height/width
+// background-color/color for background/indicator color
+// padding for pad of indicator
+// font-size for selected index (0-based)
+export const NavigationIndicator = (count, vertical, props) => Widget.DrawingArea({
+ ...props,
+ setup: (area) => {
+ const styleContext = area.get_style_context();
+ const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
+ const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
+ area.set_size_request(width, height);
+
+ area.connect('draw', Lang.bind(area, (area, cr) => {
+ const styleContext = area.get_style_context();
+ const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
+ const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
+ // console.log('allocated width/height:', area.get_allocated_width(), '/', area.get_allocated_height())
+ area.set_size_request(width, height);
+ const paddingLeft = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
+ const paddingRight = styleContext.get_padding(Gtk.StateFlags.NORMAL).right;
+ const paddingTop = styleContext.get_padding(Gtk.StateFlags.NORMAL).top;
+ const paddingBottom = styleContext.get_padding(Gtk.StateFlags.NORMAL).bottom;
+
+ const selectedCell = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ let cellWidth = width;
+ let cellHeight = height;
+ if (vertical) cellHeight /= count;
+ else cellWidth /= count;
+ const indicatorWidth = cellWidth - paddingLeft - paddingRight;
+ const indicatorHeight = cellHeight - paddingTop - paddingBottom;
+
+ const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ cr.setLineWidth(2);
+ // Background
+ cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
+ cr.rectangle(0, 0, width, height);
+ cr.fill();
+
+ // The indicator line
+ cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
+ if (vertical) {
+ cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
+ cr.stroke();
+ cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
+ cr.fill();
+ cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth / 2, Math.PI, 2 * Math.PI);
+ cr.fill();
+ cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorHeight - indicatorWidth / 2, indicatorWidth / 2, 0, Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
+ cr.stroke();
+ cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
+ cr.fill();
+ cr.arc(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ cr.arc(paddingLeft + cellWidth * selectedCell + indicatorWidth - indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ }))
+ },
+})
+
+
diff --git a/modules/styling/config/lib/notification.js b/modules/styling/config/lib/notification.js
new file mode 100644
index 0000000..47f2c20
--- /dev/null
+++ b/modules/styling/config/lib/notification.js
@@ -0,0 +1,445 @@
+// This file is for the actual widget for each single notification
+
+const { GLib, Gdk, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js'
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+const { Box, EventBox, Icon, Overlay, Label, Button, Revealer } = Widget;
+import { MaterialIcon } from "./materialicon.js";
+import { setupCursorHover } from "./cursorhover.js";
+import { AnimatedCircProg } from "./animatedcircularprogress.js";
+
+function guessMessageType(summary) {
+ if (summary.includes('recording')) return 'screen_record';
+ if (summary.includes('battery') || summary.includes('power')) return 'power';
+ if (summary.includes('screenshot')) return 'screenshot_monitor';
+ if (summary.includes('welcome')) return 'waving_hand';
+ if (summary.includes('time')) return 'scheduleb';
+ if (summary.includes('installed')) return 'download';
+ if (summary.includes('update')) return 'update';
+ if (summary.startsWith('file')) return 'folder_copy';
+ return 'chat';
+}
+
+function exists(widget) {
+ return widget !== null;
+}
+
+const NotificationIcon = (notifObject) => {
+ // { appEntry, appIcon, image }, urgency = 'normal'
+ if (notifObject.image) {
+ return Box({
+ valign: Gtk.Align.CENTER,
+ hexpand: false,
+ className: 'notif-icon',
+ css: `
+ background-image: url("${notifObject.image}");
+ background-size: auto 100%;
+ background-repeat: no-repeat;
+ background-position: center;
+ `,
+ });
+ }
+
+ let icon = 'NO_ICON';
+ if (Utils.lookUpIcon(notifObject.appIcon))
+ icon = notifObject.appIcon;
+ if (Utils.lookUpIcon(notifObject.appEntry))
+ icon = notifObject.appEntry;
+
+ return Box({
+ vpack: 'center',
+ hexpand: false,
+ className: `notif-icon notif-icon-material-${notifObject.urgency}`,
+ homogeneous: true,
+ children: [
+ (icon != 'NO_ICON' ?
+ Icon({
+ vpack: 'center',
+ icon: icon,
+ })
+ :
+ MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', {
+ hexpand: true,
+ })
+ )
+ ],
+ });
+};
+
+export default ({
+ notifObject,
+ isPopup = false,
+ props = {},
+} = {}) => {
+ const popupTimeout = notifObject.timeout || (notifObject.urgency == 'critical' ? 8000 : 3000);
+ const command = (isPopup ?
+ () => notifObject.dismiss() :
+ () => notifObject.close()
+ )
+ const destroyWithAnims = () => {
+ widget.sensitive = false;
+ notificationBox.setCss(middleClickClose);
+ Utils.timeout(200, () => {
+ if (wholeThing) wholeThing.revealChild = false;
+ }, wholeThing);
+ Utils.timeout(400, () => {
+ command();
+ if (wholeThing) {
+ wholeThing.destroy();
+ wholeThing = null;
+ }
+ }, wholeThing);
+ }
+ const widget = EventBox({
+ onHover: (self) => {
+ self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
+ if (!wholeThing.attribute.hovered)
+ wholeThing.attribute.hovered = true;
+ },
+ onHoverLost: (self) => {
+ self.window.set_cursor(null);
+ if (wholeThing.attribute.hovered)
+ wholeThing.attribute.hovered = false;
+ if (isPopup) {
+ command();
+ }
+ },
+ onMiddleClick: (self) => {
+ destroyWithAnims();
+ },
+ setup: (self) => {
+ self.on("button-press-event", () => {
+ wholeThing.attribute.held = true;
+ notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, true);
+ Utils.timeout(800, () => {
+ if (wholeThing?.attribute.held) {
+ Utils.execAsync(['wl-copy', `${notifObject.body}`])
+ notifTextSummary.label = notifObject.summary + " (copied)";
+ Utils.timeout(3000, () => notifTextSummary.label = notifObject.summary)
+ }
+ })
+ }).on("button-release-event", () => {
+ wholeThing.attribute.held = false;
+ notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, false);
+ })
+ }
+ });
+ let wholeThing = Revealer({
+ attribute: {
+ 'close': undefined,
+ 'destroyWithAnims': destroyWithAnims,
+ 'dragging': false,
+ 'held': false,
+ 'hovered': false,
+ 'id': notifObject.id,
+ },
+ revealChild: false,
+ transition: 'slide_down',
+ transitionDuration: 200,
+ child: Box({ // Box to make sure css-based spacing works
+ homogeneous: true,
+ }),
+ });
+
+ const display = Gdk.Display.get_default();
+ const notifTextPreview = Revealer({
+ transition: 'slide_down',
+ transitionDuration: 120,
+ revealChild: true,
+ child: Label({
+ xalign: 0,
+ className: `txt-smallie notif-body-${notifObject.urgency}`,
+ useMarkup: true,
+ xalign: 0,
+ justify: Gtk.Justification.LEFT,
+ maxWidthChars: 24,
+ truncate: 'end',
+ label: notifObject.body.split("\n")[0],
+ }),
+ });
+ const notifTextExpanded = Revealer({
+ transition: 'slide_up',
+ transitionDuration: 120,
+ revealChild: false,
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Label({
+ xalign: 0,
+ className: `txt-smallie notif-body-${notifObject.urgency}`,
+ useMarkup: true,
+ xalign: 0,
+ justify: Gtk.Justification.LEFT,
+ maxWidthChars: 24,
+ wrap: true,
+ label: notifObject.body,
+ }),
+ Box({
+ className: 'notif-actions spacing-h-5',
+ children: [
+ Button({
+ hexpand: true,
+ className: `notif-action notif-action-${notifObject.urgency}`,
+ onClicked: () => destroyWithAnims(),
+ child: Label({
+ label: 'Close',
+ })
+ }),
+ ...notifObject.actions.map(action => Widget.Button({
+ hexpand: true,
+ className: `notif-action notif-action-${notifObject.urgency}`,
+ onClicked: () => notifObject.invoke(action.id),
+ child: Label({
+ label: action.label,
+ })
+ }))
+ ],
+ })
+ ]
+ }),
+ });
+ const notifIcon = Box({
+ vpack: 'start',
+ homogeneous: true,
+ children: [
+ Overlay({
+ child: NotificationIcon(notifObject),
+ overlays: isPopup ? [AnimatedCircProg({
+ className: `notif-circprog-${notifObject.urgency}`,
+ vpack: 'center', hpack: 'center',
+ initFrom: (isPopup ? 100 : 0),
+ initTo: 0,
+ initAnimTime: popupTimeout,
+ })] : [],
+ }),
+ ]
+ });
+ let notifTime = '';
+ const messageTime = GLib.DateTime.new_from_unix_local(notifObject.time);
+ if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year())
+ notifTime = messageTime.format('%H:%M');
+ else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1)
+ notifTime = 'Yesterday';
+ else
+ notifTime = messageTime.format('%d/%m');
+ const notifTextSummary = Label({
+ xalign: 0,
+ className: 'txt-small txt-semibold titlefont',
+ justify: Gtk.Justification.LEFT,
+ hexpand: true,
+ maxWidthChars: 24,
+ truncate: 'end',
+ ellipsize: 3,
+ useMarkup: notifObject.summary.startsWith('<'),
+ label: notifObject.summary,
+ });
+ const notifTextBody = Label({
+ vpack: 'center',
+ justification: 'right',
+ className: 'txt-smaller txt-semibold',
+ label: notifTime,
+ });
+ const notifText = Box({
+ valign: Gtk.Align.CENTER,
+ vertical: true,
+ hexpand: true,
+ children: [
+ Box({
+ children: [
+ notifTextSummary,
+ notifTextBody,
+ ]
+ }),
+ notifTextPreview,
+ notifTextExpanded,
+ ]
+ });
+ const notifExpandButton = Button({
+ vpack: 'start',
+ className: 'notif-expand-btn',
+ onClicked: (self) => {
+ if (notifTextPreview.revealChild) { // Expanding...
+ notifTextPreview.revealChild = false;
+ notifTextExpanded.revealChild = true;
+ self.child.label = 'expand_less';
+ expanded = true;
+ }
+ else {
+ notifTextPreview.revealChild = true;
+ notifTextExpanded.revealChild = false;
+ self.child.label = 'expand_more';
+ expanded = false;
+ }
+ },
+ child: MaterialIcon('expand_more', 'norm', {
+ vpack: 'center',
+ }),
+ setup: setupCursorHover,
+ });
+ const notificationContent = Box({
+ ...props,
+ className: `${isPopup ? 'popup-' : ''}notif-${notifObject.urgency} spacing-h-10`,
+ children: [
+ notifIcon,
+ Box({
+ className: 'spacing-h-5',
+ children: [
+ notifText,
+ notifExpandButton,
+ ]
+ })
+ ]
+ })
+
+ // Gesture stuff
+ const gesture = Gtk.GestureDrag.new(widget);
+ var initDirX = 0;
+ var initDirVertical = -1; // -1: unset, 0: horizontal, 1: vertical
+ var expanded = false;
+ // in px
+ const startMargin = 0;
+ const MOVE_THRESHOLD = 10;
+ const DRAG_CONFIRM_THRESHOLD = 100;
+ // in rem
+ const maxOffset = 10.227;
+ const endMargin = 20.455;
+ const disappearHeight = 6.818;
+ const leftAnim1 = `transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-left: -${Number(maxOffset + endMargin)}rem;
+ margin-right: ${Number(maxOffset + endMargin)}rem;
+ opacity: 0;`;
+
+ const rightAnim1 = `transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-left: ${Number(maxOffset + endMargin)}rem;
+ margin-right: -${Number(maxOffset + endMargin)}rem;
+ opacity: 0;`;
+
+ const middleClickClose = `transition: 200ms cubic-bezier(0.85, 0, 0.15, 1);
+ margin-left: ${Number(maxOffset + endMargin)}rem;
+ margin-right: -${Number(maxOffset + endMargin)}rem;
+ opacity: 0;`;
+
+ const notificationBox = Box({
+ attribute: {
+ 'leftAnim1': leftAnim1,
+ 'rightAnim1': rightAnim1,
+ 'middleClickClose': middleClickClose,
+ 'ready': false,
+ },
+ homogeneous: true,
+ children: [notificationContent],
+ setup: (self) => self
+ .hook(gesture, self => {
+ var offset_x = gesture.get_offset()[1];
+ var offset_y = gesture.get_offset()[2];
+ // Which dir?
+ if (initDirVertical == -1) {
+ if (Math.abs(offset_y) > MOVE_THRESHOLD)
+ initDirVertical = 1;
+ if (initDirX == 0 && Math.abs(offset_x) > MOVE_THRESHOLD) {
+ initDirVertical = 0;
+ initDirX = (offset_x > 0 ? 1 : -1);
+ }
+ }
+ // Horizontal drag
+ if (initDirVertical == 0 && offset_x > MOVE_THRESHOLD) {
+ if (initDirX < 0)
+ self.setCss(`margin-left: 0px; margin-right: 0px;`);
+ else
+ self.setCss(`
+ margin-left: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ margin-right: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ `);
+ }
+ else if (initDirVertical == 0 && offset_x < -MOVE_THRESHOLD) {
+ if (initDirX > 0)
+ self.setCss(`margin-left: 0px; margin-right: 0px;`);
+ else {
+ offset_x = Math.abs(offset_x);
+ self.setCss(`
+ margin-right: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ margin-left: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
+ `);
+ }
+ }
+ // Update dragging
+ wholeThing.attribute.dragging = Math.abs(offset_x) > MOVE_THRESHOLD;
+ if (Math.abs(offset_x) > MOVE_THRESHOLD ||
+ Math.abs(offset_y) > MOVE_THRESHOLD) wholeThing.attribute.held = false;
+ widget.window?.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
+ // Vertical drag
+ if (initDirVertical == 1 && offset_y > MOVE_THRESHOLD && !expanded) {
+ notifTextPreview.revealChild = false;
+ notifTextExpanded.revealChild = true;
+ expanded = true;
+ notifExpandButton.child.label = 'expand_less';
+ }
+ else if (initDirVertical == 1 && offset_y < -MOVE_THRESHOLD && expanded) {
+ notifTextPreview.revealChild = true;
+ notifTextExpanded.revealChild = false;
+ expanded = false;
+ notifExpandButton.child.label = 'expand_more';
+ }
+
+ }, 'drag-update')
+ .hook(gesture, self => {
+ if (!self.attribute.ready) {
+ wholeThing.revealChild = true;
+ self.attribute.ready = true;
+ return;
+ }
+ const offset_h = gesture.get_offset()[1];
+
+ if (Math.abs(offset_h) > DRAG_CONFIRM_THRESHOLD && offset_h * initDirX > 0) {
+ if (offset_h > 0) {
+ self.setCss(rightAnim1);
+ widget.sensitive = false;
+ }
+ else {
+ self.setCss(leftAnim1);
+ widget.sensitive = false;
+ }
+ Utils.timeout(200, () => {
+ if (wholeThing) wholeThing.revealChild = false;
+ }, wholeThing);
+ Utils.timeout(400, () => {
+ command();
+ if (wholeThing) {
+ wholeThing.destroy();
+ wholeThing = null;
+ }
+ }, wholeThing);
+ }
+ else {
+ self.setCss(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-left: ${startMargin}px;
+ margin-right: ${startMargin}px;
+ margin-bottom: unset; margin-top: unset;
+ opacity: 1;`);
+ if (widget.window)
+ widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
+
+ wholeThing.attribute.dragging = false;
+ }
+ initDirX = 0;
+ initDirVertical = -1;
+ }, 'drag-end')
+ ,
+ })
+ widget.add(notificationBox);
+ wholeThing.child.children = [widget];
+ if (isPopup) Utils.timeout(popupTimeout, () => {
+ if (wholeThing) {
+ wholeThing.revealChild = false;
+ Utils.timeout(200, () => {
+ if (wholeThing) {
+ wholeThing.destroy();
+ wholeThing = null;
+ }
+ command();
+ }, wholeThing);
+ }
+ })
+ return wholeThing;
+}
diff --git a/modules/styling/config/lib/popupwindow.js b/modules/styling/config/lib/popupwindow.js
new file mode 100644
index 0000000..2b85ca7
--- /dev/null
+++ b/modules/styling/config/lib/popupwindow.js
@@ -0,0 +1,30 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, Window } = Widget;
+
+
+export default ({
+ name,
+ child,
+ showClassName,
+ hideClassName,
+ ...props
+}) => Window({
+ name,
+ popup: true,
+ visible: false,
+ layer: 'overlay',
+ ...props,
+
+ child: Box({
+ className: `${showClassName} ${hideClassName}`,
+ setup: (self) => self
+ .hook(App, (self, currentName, visible) => {
+ if (currentName === name) {
+ self.toggleClassName(hideClassName, !visible);
+ }
+ })
+ ,
+ child: child,
+ }),
+});
\ No newline at end of file
diff --git a/modules/styling/config/lib/roundedcorner.js b/modules/styling/config/lib/roundedcorner.js
new file mode 100644
index 0000000..fd48d93
--- /dev/null
+++ b/modules/styling/config/lib/roundedcorner.js
@@ -0,0 +1,54 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Gtk } = imports.gi;
+const Lang = imports.lang;
+import Cairo from 'gi://cairo?version=1.0';
+
+export const dummyRegion = new Cairo.Region();
+export const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion);
+
+export const RoundedCorner = (place, props) => Widget.DrawingArea({
+ ...props,
+ hpack: place.includes('left') ? 'start' : 'end',
+ vpack: place.includes('top') ? 'start' : 'end',
+ setup: (widget) => Utils.timeout(1, () => {
+ const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
+ const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+ widget.set_size_request(r, r);
+ widget.connect('draw', Lang.bind(widget, (widget, cr) => {
+ const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
+ const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+ // const borderColor = widget.get_style_context().get_property('color', Gtk.StateFlags.NORMAL);
+ // const borderWidth = widget.get_style_context().get_border(Gtk.StateFlags.NORMAL).left; // ur going to write border-width: something anyway
+ widget.set_size_request(r, r);
+
+ switch (place) {
+ case 'topleft':
+ cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
+ cr.lineTo(0, 0);
+ break;
+
+ case 'topright':
+ cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
+ cr.lineTo(r, 0);
+ break;
+
+ case 'bottomleft':
+ cr.arc(r, 0, r, Math.PI / 2, Math.PI);
+ cr.lineTo(0, r);
+ break;
+
+ case 'bottomright':
+ cr.arc(0, 0, r, 0, Math.PI / 2);
+ cr.lineTo(r, r);
+ break;
+ }
+
+ cr.closePath();
+ cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
+ cr.fill();
+ // cr.setLineWidth(borderWidth);
+ // cr.setSourceRGBA(borderColor.red, borderColor.green, borderColor.blue, borderColor.alpha);
+ // cr.stroke();
+ }));
+ }),
+});
\ No newline at end of file
diff --git a/modules/styling/config/lib/separator.js b/modules/styling/config/lib/separator.js
new file mode 100644
index 0000000..3098c7e
--- /dev/null
+++ b/modules/styling/config/lib/separator.js
@@ -0,0 +1,5 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+export const separatorLine = Widget.Box({
+ className: 'separator-line',
+})
\ No newline at end of file
diff --git a/modules/styling/config/lib/statusicons.js b/modules/styling/config/lib/statusicons.js
new file mode 100644
index 0000000..1e94257
--- /dev/null
+++ b/modules/styling/config/lib/statusicons.js
@@ -0,0 +1,253 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import { MaterialIcon } from './materialicon.js';
+import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
+import Network from 'resource:///com/github/Aylur/ags/service/network.js';
+import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
+import { languages } from '../data/languages.js';
+
+// A guessing func to try to support langs not listed in data/languages.js
+function isLanguageMatch(abbreviation, word) {
+ const lowerAbbreviation = abbreviation.toLowerCase();
+ const lowerWord = word.toLowerCase();
+ let j = 0;
+ for (let i = 0; i < lowerWord.length; i++) {
+ if (lowerWord[i] === lowerAbbreviation[j]) {
+ j++;
+ }
+ if (j === lowerAbbreviation.length) {
+ return true;
+ }
+ }
+ return false;
+}
+
+export const NotificationIndicator = (notifCenterName = 'sideright') => {
+ const widget = Widget.Revealer({
+ transition: 150,
+ transition: 'slide_left',
+ revealChild: false,
+ setup: (self) => self
+ .hook(Notifications, (self, id) => {
+ if (!id || Notifications.dnd) return;
+ if (!Notifications.getNotification(id)) return;
+ self.revealChild = true;
+ }, 'notified')
+ .hook(App, (self, currentName, visible) => {
+ if (visible && currentName === notifCenterName) {
+ self.revealChild = false;
+ }
+ })
+ ,
+ child: Widget.Box({
+ children: [
+ MaterialIcon('notifications', 'norm'),
+ Widget.Label({
+ className: 'txt-small titlefont',
+ attribute: {
+ unreadCount: 0,
+ update: (self) => self.label = `${self.attribute.unreadCount}`,
+ },
+ setup: (self) => self
+ .hook(Notifications, (self, id) => {
+ if (!id || Notifications.dnd) return;
+ if (!Notifications.getNotification(id)) return;
+ self.attribute.unreadCount++;
+ self.attribute.update(self);
+ }, 'notified')
+ .hook(App, (self, currentName, visible) => {
+ if (visible && currentName === notifCenterName) {
+ self.attribute.unreadCount = 0;
+ self.attribute.update(self);
+ }
+ })
+ ,
+ })
+ ]
+ })
+ });
+ return widget;
+}
+
+export const BluetoothIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ children: {
+ 'false': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_disabled' }),
+ 'true': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth' }),
+ },
+ setup: (self) => self
+ .hook(Bluetooth, stack => {
+ stack.shown = String(Bluetooth.enabled);
+ })
+ ,
+});
+
+
+const NetworkWiredIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ children: {
+ 'fallback': SimpleNetworkIndicator(),
+ 'unknown': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
+ 'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
+ 'connected': Widget.Label({ className: 'txt-norm icon-material', label: 'lan' }),
+ 'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
+ },
+ setup: (self) => self.hook(Network, stack => {
+ if (!Network.wired)
+ return;
+
+ const { internet } = Network.wired;
+ if (['connecting', 'connected'].includes(internet))
+ stack.shown = internet;
+ else if (Network.connectivity !== 'full')
+ stack.shown = 'disconnected';
+ else
+ stack.shown = 'fallback';
+ }),
+});
+
+const SimpleNetworkIndicator = () => Widget.Icon({
+ setup: (self) => self.hook(Network, self => {
+ const icon = Network[Network.primary || 'wifi']?.iconName;
+ self.icon = icon || '';
+ self.visible = icon;
+ }),
+});
+
+const NetworkWifiIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ children: {
+ 'disabled': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
+ 'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
+ 'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
+ '0': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' }),
+ '1': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_1_bar' }),
+ '2': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_2_bar' }),
+ '3': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_3_bar' }),
+ '4': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_4_bar' }),
+ },
+ setup: (self) => self.hook(Network, (stack) => {
+ if (!Network.wifi) {
+ return;
+ }
+ if (Network.wifi.internet == 'connected') {
+ stack.shown = String(Math.ceil(Network.wifi.strength / 25));
+ }
+ else if (["disconnected", "connecting"].includes(Network.wifi.internet)) {
+ stack.shown = Network.wifi.internet;
+ }
+ }),
+});
+
+export const NetworkIndicator = () => Widget.Stack({
+ transition: 'slide_up_down',
+ children: {
+ 'fallback': SimpleNetworkIndicator(),
+ 'wifi': NetworkWifiIndicator(),
+ 'wired': NetworkWiredIndicator(),
+ },
+ setup: (self) => self.hook(Network, stack => {
+ if (!Network.primary) {
+ stack.shown = 'wifi';
+ return;
+ }
+ const primary = Network.primary || 'fallback';
+ if (['wifi', 'wired'].includes(primary))
+ stack.shown = primary;
+ else
+ stack.shown = 'fallback';
+ }),
+});
+
+const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ var initLangs = [];
+ var languageStackArray = [];
+ var currentKeyboard;
+
+ const updateCurrentKeyboards = () => {
+ currentKeyboard = JSON.parse(Utils.exec('hyprctl -j devices')).keyboards
+ .find(device => device.name === 'at-translated-set-2-keyboard');
+ if (currentKeyboard) {
+ initLangs = currentKeyboard.layout.split(',').map(lang => lang.trim());
+ }
+ languageStackArray = Array.from({ length: initLangs.length }, (_, i) => {
+ const lang = languages.find(lang => lang.layout == initLangs[i]);
+ // if (!lang) return [
+ // initLangs[i],
+ // Widget.Label({ label: initLangs[i] })
+ // ];
+ // return [
+ // lang.layout,
+ // Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
+ // ];
+ // Object
+ if (!lang) return {
+ [initLangs[i]]: Widget.Label({ label: initLangs[i] })
+ };
+ return {
+ [lang.layout]: Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
+ };
+ });
+ };
+ updateCurrentKeyboards();
+ const widgetRevealer = Widget.Revealer({
+ transition: 150,
+ transition: 'slide_left',
+ revealChild: languageStackArray.length > 1,
+ });
+ const widgetKids = {
+ ...languageStackArray.reduce((obj, lang) => {
+ return { ...obj, ...lang };
+ }, {}),
+ 'undef': Widget.Label({ label: '?' }),
+ }
+ const widgetContent = Widget.Stack({
+ transition: 'slide_up_down',
+ children: widgetKids,
+ setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => {
+ if (!kbName) {
+ return;
+ }
+ var lang = languages.find(lang => layoutName.includes(lang.name));
+ if (lang) {
+ widgetContent.shown = lang.layout;
+ }
+ else { // Attempt to support langs not listed
+ lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName));
+ if (!lang) stack.shown = 'undef';
+ else stack.shown = lang[0];
+ }
+ }, 'keyboard-layout'),
+ });
+ widgetRevealer.child = widgetContent;
+ return widgetRevealer;
+ } catch {
+ return null;
+ }
+}
+
+const OptionalKeyboardLayout = async () => {
+ try {
+ return await HyprlandXkbKeyboardLayout({ useFlag: false });
+ } catch {
+ return null;
+ }
+};
+const optionalKeyboardLayoutInstance = await OptionalKeyboardLayout();
+
+export const StatusIcons = (props = {}) => Widget.Box({
+ ...props,
+ child: Widget.Box({
+ className: 'spacing-h-15',
+ children: [
+ optionalKeyboardLayoutInstance,
+ NotificationIndicator(),
+ NetworkIndicator(),
+ BluetoothIndicator(),
+ ]
+ })
+});
diff --git a/modules/styling/config/scripts/README.md b/modules/styling/config/scripts/README.md
new file mode 100644
index 0000000..6d79a51
--- /dev/null
+++ b/modules/styling/config/scripts/README.md
@@ -0,0 +1,3 @@
+# scripts folder
+- For ARM devices, you have to compile C++ files yourself. If there are none, yippee
+- It is advised to use services instead of listening/polling scripts, so everything here are just scripts for actions
\ No newline at end of file
diff --git a/modules/styling/config/scripts/color_generation/applycolor.sh b/modules/styling/config/scripts/color_generation/applycolor.sh
new file mode 100755
index 0000000..28501c4
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/applycolor.sh
@@ -0,0 +1,178 @@
+#!/usr/bin/env bash
+
+# sleep 0 # idk i wanted some delay or colors dont get applied properly
+if [ ! -d "$HOME"/.cache/ags/user/generated ]; then
+ mkdir -p "$HOME"/.cache/ags/user/generated
+fi
+cd "$HOME/.config/ags" || exit
+
+colornames=''
+colorstrings=''
+colorlist=()
+colorvalues=()
+
+if [[ "$1" = "--bad-apple" ]]; then
+ cp scripts/color_generation/specials/_material_badapple.scss scss/_material.scss
+ colornames=$(cat scripts/color_generation/specials/_material_badapple.scss | cut -d: -f1)
+ colorstrings=$(cat scripts/color_generation/specials/_material_badapple.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d ";" -f1)
+ IFS=$'\n'
+ # filearr=( $filelist ) # Get colors
+ colorlist=( $colornames ) # Array of color names
+ colorvalues=( $colorstrings ) # Array of color values
+else
+ colornames=$(cat scss/_material.scss | cut -d: -f1)
+ colorstrings=$(cat scss/_material.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d ";" -f1)
+ IFS=$'\n'
+ # filearr=( $filelist ) # Get colors
+ colorlist=( $colornames ) # Array of color names
+ colorvalues=( $colorstrings ) # Array of color values
+fi
+
+transparentize() {
+ local hex="$1"
+ local alpha="$2"
+ local red green blue
+
+ red=$((16#${hex:1:2}))
+ green=$((16#${hex:3:2}))
+ blue=$((16#${hex:5:2}))
+
+ printf 'rgba(%d, %d, %d, %.2f)\n' "$red" "$green" "$blue" "$alpha"
+}
+
+get_light_dark() {
+ lightdark=""
+ if [ ! -f "$HOME"/.cache/ags/user/colormode.txt ]; then
+ echo "" > "$HOME"/.cache/ags/user/colormode.txt
+ else
+ lightdark=$(cat "$HOME"/.cache/ags/user/colormode.txt) # either "" or "-l"
+ fi
+ echo "$lightdark"
+}
+
+apply_gtklock() {
+ # Check if scripts/templates/gtklock/main.scss exists
+ if [ ! -f "scripts/templates/gtklock/main.scss" ]; then
+ echo "SCSS not found for Gtklock. Skipping that."
+ return
+ fi
+
+ # Copy template
+ mkdir -p "$HOME"/.cache/ags/user/generated/gtklock
+ sassc "scripts/templates/gtklock/main.scss" "$HOME"/.cache/ags/user/generated/gtklock/style.css
+ cp "$HOME"/.cache/ags/user/generated/gtklock/style.css "$HOME"/.config/gtklock/style.css
+}
+
+apply_fuzzel() {
+ # Check if scripts/templates/fuzzel/fuzzel.ini exists
+ if [ ! -f "scripts/templates/fuzzel/fuzzel.ini" ]; then
+ echo "Template file not found for Fuzzel. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$HOME"/.cache/ags/user/generated/fuzzel
+ cp "scripts/templates/fuzzel/fuzzel.ini" "$HOME"/.cache/ags/user/generated/fuzzel/fuzzel.ini
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]#\#}/g" "$HOME"/.cache/ags/user/generated/fuzzel/fuzzel.ini
+ done
+
+ cp "$HOME"/.cache/ags/user/generated/fuzzel/fuzzel.ini "$HOME"/.config/fuzzel/fuzzel.ini
+}
+
+apply_foot() {
+ if [ ! -f "scripts/templates/foot/foot.ini" ]; then
+ echo "Template file not found for Foot. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$HOME"/.cache/ags/user/generated/foot
+ cp "scripts/templates/foot/foot.ini" "$HOME"/.cache/ags/user/generated/foot/foot.ini
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ # sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$HOME"/.cache/ags/user/generated/foot/foot.ini
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]#\#}/g" "$HOME"/.cache/ags/user/generated/foot/foot.ini
+ done
+
+ cp "$HOME"/.cache/ags/user/generated/foot/foot.ini "$HOME/.config/foot/foot.ini"
+}
+
+apply_term() {
+ # Check if scripts/templates/foot/foot.ini exists
+ if [ ! -f "scripts/templates/terminal/sequences.txt" ]; then
+ echo "Template file not found for Terminal. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$HOME"/.cache/ags/user/generated/terminal
+ cp "scripts/templates/terminal/sequences.txt" "$HOME"/.cache/ags/user/generated/terminal/sequences.txt
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$HOME"/.cache/ags/user/generated/terminal/sequences.txt
+ done
+ cp "$HOME"/.cache/ags/user/generated/terminal/sequences.txt "$HOME"/.config/fish/sequences.txt
+
+ for file in /dev/pts/*; do
+ if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then
+ cat "$HOME"/.config/fish/sequences.txt > "$file"
+ fi
+ done
+}
+
+apply_hyprland() {
+ # Check if scripts/templates/hypr/colors.conf exists
+ if [ ! -f "scripts/templates/hypr/colors.conf" ]; then
+ echo "Template file not found for Hyprland colors. Skipping that."
+ return
+ fi
+ # Copy template
+ mkdir -p "$HOME"/.cache/ags/user/generated/hypr
+ cp "scripts/templates/hypr/colors.conf" "$HOME"/.cache/ags/user/generated/hypr/colors.conf
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]#\#}/g" "$HOME"/.cache/ags/user/generated/hypr/colors.conf
+ done
+
+ cp "$HOME"/.cache/ags/user/generated/hypr/colors.conf "$HOME"/.config/hypr/colors.conf
+}
+
+apply_gtk() { # Using gradience-cli
+ lightdark=$(get_light_dark)
+
+ # Copy template
+ mkdir -p "$HOME"/.cache/ags/user/generated/gradience
+ cp "scripts/templates/gradience/preset.json" "$HOME"/.cache/ags/user/generated/gradience/preset.json
+
+ # Apply colors
+ for i in "${!colorlist[@]}"; do
+ sed -i "s/{{ ${colorlist[$i]} }}/${colorvalues[$i]}/g" "$HOME"/.cache/ags/user/generated/gradience/preset.json
+ done
+
+ mkdir -p "$HOME/.config/presets" # create gradience presets folder
+ gradience-cli apply -p "$HOME"/.cache/ags/user/generated/gradience/preset.json --gtk both
+
+ # Set light/dark preference
+ # And set GTK theme manually as Gradience defaults to light adw-gtk3
+ # (which is unreadable when broken when you use dark mode)
+ if [ "$lightdark" = "-l" ]; then
+ gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3'
+ gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
+ else
+ gsettings set org.gnome.desktop.interface gtk-theme adw-gtk3-dark
+ gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
+ fi
+}
+
+apply_ags() {
+ sassc "$HOME"/.config/ags/scss/main.scss "$HOME"/.config/ags/style.css
+ ags run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);'
+ ags run-js "App.resetCss(); App.applyCss('${HOME}/.config/ags/style.css');"
+}
+
+apply_ags &
+apply_hyprland &
+apply_gtk &
+apply_foot &
+apply_gtklock &
+apply_fuzzel &
+apply_term &
diff --git a/modules/styling/config/scripts/color_generation/colorgen.sh b/modules/styling/config/scripts/color_generation/colorgen.sh
new file mode 100755
index 0000000..93a7e75
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/colorgen.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+# check if no arguments
+if [ $# -eq 0 ]; then
+ echo "Usage: colorgen.sh /path/to/image (--apply)"
+ exit 1
+fi
+
+# check if the file ~/.cache/ags/user/colormode.txt exists. if not, create it. else, read it to $lightdark
+lightdark=""
+if [ ! -f "$HOME/.cache/ags/user/colormode.txt" ]; then
+ echo "" > "$HOME/.cache/ags/user/colormode.txt"
+else
+ lightdark=$(cat "$HOME/.cache/ags/user/colormode.txt") # either "" or "-l"
+fi
+backend="material" # color generator backend
+if [ ! -f "$HOME/.cache/ags/user/colorbackend.txt" ]; then
+ echo "material" > "$HOME/.cache/ags/user/colorbackend.txt"
+else
+ backend=$(cat "$HOME/.cache/ags/user/colorbackend.txt") # either "" or "-l"
+fi
+
+cd "$HOME/.config/ags/scripts/" || exit
+if [[ "$1" = "#"* ]]; then # this is a color
+ color_generation/generate_colors_material.py --color "$1" "$lightdark" > "$HOME"/.cache/ags/user/generated/material_colors.scss
+ if [ "$2" = "--apply" ]; then
+ cp "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME/.config/ags/scss/_material.scss"
+ color_generation/applycolor.sh
+ fi
+elif [ "$backend" = "material" ]; then
+ color_generation/generate_colors_material.py --path "$1" "$lightdark" > "$HOME"/.cache/ags/user/generated/material_colors.scss
+ if [ "$2" = "--apply" ]; then
+ cp "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME/.config/ags/scss/_material.scss"
+ color_generation/applycolor.sh
+ fi
+elif [ "$backend" = "pywal" ]; then
+ # clear and generate
+ wal -c
+ wal -i "$1" -n $lightdark -q
+ # copy scss
+ cp "$HOME/.cache/wal/colors.scss" "$HOME"/.cache/ags/user/generated/material_colors.scss
+
+ cat color_generation/pywal_to_material.scss >> "$HOME"/.cache/ags/user/generated/material_colors.scss
+ if [ "$2" = "--apply" ]; then
+ sassc "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME"/.cache/ags/user/generated/colors_classes.scss --style compact
+ sed -i "s/ { color//g" "$HOME"/.cache/ags/user/generated/colors_classes.scss
+ sed -i "s/\./$/g" "$HOME"/.cache/ags/user/generated/colors_classes.scss
+ sed -i "s/}//g" "$HOME"/.cache/ags/user/generated/colors_classes.scss
+ if [ "$lightdark" = "-l" ]; then
+ printf "\n""\$darkmode: false;""\n" >> "$HOME"/.cache/ags/user/generated/colors_classes.scss
+ else
+ printf "\n""\$darkmode: true;""\n" >> "$HOME"/.cache/ags/user/generated/colors_classes.scss
+ fi
+
+ cp "$HOME"/.cache/ags/user/generated/colors_classes.scss "$HOME/.config/ags/scss/_material.scss"
+
+ color_generation/applycolor.sh
+ fi
+fi
diff --git a/modules/styling/config/scripts/color_generation/generate_colors_material.py b/modules/styling/config/scripts/color_generation/generate_colors_material.py
new file mode 100755
index 0000000..deb34bf
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/generate_colors_material.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+from material_color_utilities_python import *
+from pathlib import Path
+import sys
+import subprocess
+
+def darken(hex_color, factor=0.7):
+ if not (hex_color.startswith('#') and len(hex_color) in (4, 7)):
+ raise ValueError("Invalid hex color format")
+ hex_color = hex_color.lstrip('#')
+ rgb = tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
+ darkened_rgb = tuple(int(max(0, val * factor)) for val in rgb)
+ darkened_hex = "#{:02X}{:02X}{:02X}".format(*darkened_rgb)
+ return darkened_hex
+
+img = 0
+newtheme=0
+if len(sys.argv) > 1 and sys.argv[1] == '--path':
+ # try:
+ img = Image.open(sys.argv[2])
+ basewidth = 64
+ wpercent = (basewidth/float(img.size[0]))
+ hsize = int((float(img.size[1])*float(wpercent)))
+ img = img.resize((basewidth,hsize),Image.Resampling.LANCZOS)
+ newtheme = themeFromImage(img)
+ # except FileNotFoundError:
+ # print('[generate_colors_material.py] File not found', file=sys.stderr);
+ # exit()
+ # except:
+ # print('[generate_colors_material.py] Something went wrong', file=sys.stderr);
+ # exit()
+elif len(sys.argv) > 1 and sys.argv[1] == '--color':
+ colorstr = sys.argv[2]
+ newtheme = themeFromSourceColor(argbFromHex(colorstr))
+else:
+ # try:
+ # imagePath = subprocess.check_output("ags run-js 'wallpaper.get(0)'", shell=True)
+ imagePath = subprocess.check_output("swww query | awk -F 'image: ' '{print $2}'", shell=True)
+ imagePath = imagePath[:-1].decode("utf-8")
+ img = Image.open(imagePath)
+ basewidth = 64
+ wpercent = (basewidth/float(img.size[0]))
+ hsize = int((float(img.size[1])*float(wpercent)))
+ img = img.resize((basewidth,hsize),Image.Resampling.LANCZOS)
+ newtheme = themeFromImage(img)
+ # except FileNotFoundError:
+ # print('[generate_colors_material.py] File not found', file=sys.stderr)
+ # exit()
+ # except:
+ # print('[generate_colors_material.py] Something went wrong', file=sys.stderr);
+ # exit()
+
+colorscheme=0
+darkmode = True
+if("-l" in sys.argv):
+ darkmode = False
+ colorscheme = newtheme.get('schemes').get('light')
+ print('$darkmode: false;')
+else:
+ colorscheme = newtheme.get('schemes').get('dark')
+ print('$darkmode: true;')
+
+primary = hexFromArgb(colorscheme.get_primary())
+onPrimary = hexFromArgb(colorscheme.get_onPrimary())
+primaryContainer = hexFromArgb(colorscheme.get_primaryContainer())
+onPrimaryContainer = hexFromArgb(colorscheme.get_onPrimaryContainer())
+secondary = hexFromArgb(colorscheme.get_secondary())
+onSecondary = hexFromArgb(colorscheme.get_onSecondary())
+secondaryContainer = hexFromArgb(colorscheme.get_secondaryContainer())
+onSecondaryContainer = hexFromArgb(colorscheme.get_onSecondaryContainer())
+tertiary = hexFromArgb(colorscheme.get_tertiary())
+onTertiary = hexFromArgb(colorscheme.get_onTertiary())
+tertiaryContainer = hexFromArgb(colorscheme.get_tertiaryContainer())
+onTertiaryContainer = hexFromArgb(colorscheme.get_onTertiaryContainer())
+error = hexFromArgb(colorscheme.get_error())
+onError = hexFromArgb(colorscheme.get_onError())
+errorContainer = hexFromArgb(colorscheme.get_errorContainer())
+onErrorContainer = hexFromArgb(colorscheme.get_onErrorContainer())
+background = hexFromArgb(colorscheme.get_background())
+onBackground = hexFromArgb(colorscheme.get_onBackground())
+surface = hexFromArgb(colorscheme.get_surface())
+onSurface = hexFromArgb(colorscheme.get_onSurface())
+surfaceVariant = hexFromArgb(colorscheme.get_surfaceVariant())
+onSurfaceVariant = hexFromArgb(colorscheme.get_onSurfaceVariant())
+outline = hexFromArgb(colorscheme.get_outline())
+shadow = hexFromArgb(colorscheme.get_shadow())
+inverseSurface = hexFromArgb(colorscheme.get_inverseSurface())
+inverseOnSurface = hexFromArgb(colorscheme.get_inverseOnSurface())
+inversePrimary = hexFromArgb(colorscheme.get_inversePrimary())
+
+# make material less boring
+if darkmode:
+ background = darken(background, 0.6)
+
+print('$primary: ' + primary + ';')
+print('$onPrimary: ' + onPrimary + ';')
+print('$primaryContainer: ' + primaryContainer + ';')
+print('$onPrimaryContainer: ' + onPrimaryContainer + ';')
+print('$secondary: ' + secondary + ';')
+print('$onSecondary: ' + onSecondary + ';')
+print('$secondaryContainer: ' + secondaryContainer + ';')
+print('$onSecondaryContainer: ' + onSecondaryContainer + ';')
+print('$tertiary: ' + tertiary + ';')
+print('$onTertiary: ' + onTertiary + ';')
+print('$tertiaryContainer: ' + tertiaryContainer + ';')
+print('$onTertiaryContainer: ' + onTertiaryContainer + ';')
+print('$error: ' + error + ';')
+print('$onError: ' + onError + ';')
+print('$errorContainer: ' + errorContainer + ';')
+print('$onErrorContainer: ' + onErrorContainer + ';')
+print('$colorbarbg: ' + background + ';')
+print('$background: ' + background + ';')
+print('$onBackground: ' + onBackground + ';')
+print('$surface: ' + surface + ';')
+print('$onSurface: ' + onSurface + ';')
+print('$surfaceVariant: ' + surfaceVariant + ';')
+print('$onSurfaceVariant: ' + onSurfaceVariant + ';')
+print('$outline: ' + outline + ';')
+print('$shadow: ' + shadow + ';')
+print('$inverseSurface: ' + inverseSurface + ';')
+print('$inverseOnSurface: ' + inverseOnSurface + ';')
+print('$inversePrimary: ' + inversePrimary + ';')
\ No newline at end of file
diff --git a/modules/styling/config/scripts/color_generation/pywal_to_material.scss b/modules/styling/config/scripts/color_generation/pywal_to_material.scss
new file mode 100644
index 0000000..ff049b2
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/pywal_to_material.scss
@@ -0,0 +1,57 @@
+$primary: lighten($color4, 20%);
+$onPrimary: darken($color2, 20%);
+$primaryContainer: darken($color2, 10%);
+$onPrimaryContainer: lighten($color4, 10%);
+$secondary: desaturate(lighten($color5, 20%), 20%);
+$onSecondary: desaturate(darken($color3, 20%), 20%);
+$secondaryContainer: desaturate(darken($color3, 20%), 20%);
+$onSecondaryContainer: desaturate(lighten($color5, 20%), 20%);
+$tertiary: adjust-hue(lighten($color4, 20%), 30deg);
+$onTertiary: adjust-hue(darken($color2, 20%), 30deg);
+$tertiaryContainer: adjust-hue(darken($color2, 10%), 30deg);
+$tertiaryContainer: adjust-hue(lighten($color4, 10%), 30deg);
+$error: #ffb4a9;
+$onError: #680003;
+$errorContainer: #930006;
+$onErrorContainer: #ffb4a9;
+$colorbarbg: $color0;
+$background: $color0;
+$onBackground: $color7;
+$surface: $color0;
+$onSurface: $color7;
+$surfaceVariant: $color1;
+$onSurfaceVariant: $color7;
+$outline: $color7;
+$shadow: #000000;
+$inverseSurface: invert($surface);
+$inverseOnSurface: invert($onSurface);
+$inversePrimary: invert($primary);
+
+.primary { color: $primary; }
+.onPrimary { color: $onPrimary; }
+.primaryContainer { color: $primaryContainer; }
+.onPrimaryContainer { color: $onPrimaryContainer; }
+.secondary { color: $secondary; }
+.onSecondary { color: $onSecondary; }
+.secondaryContainer { color: $secondaryContainer; }
+.onSecondaryContainer { color: $onSecondaryContainer; }
+.tertiary { color: $tertiary; }
+.onTertiary { color: $onTertiary; }
+.tertiaryContainer { color: $tertiaryContainer; }
+.onTertiaryContainer { color: $tertiaryContainer; }
+.error { color: $error; }
+.onError { color: $onError; }
+.errorContainer { color: $errorContainer; }
+.onErrorContainer { color: $onErrorContainer; }
+.colorbarbg { color: $colorbarbg; }
+.background { color: $background; }
+.onBackground { color: $onBackground; }
+.surface { color: $surface; }
+.onSurface { color: $onSurface; }
+.surfaceVariant { color: $surfaceVariant; }
+.onSurfaceVariant { color: $onSurfaceVariant; }
+.outline { color: $outline; }
+.shadow { color: $shadow; }
+.inverseSurface { color: $inverseSurface; }
+.inverseOnSurface { color: $inverseOnSurface; }
+.inversePrimary { color: $inversePrimary; }
diff --git a/modules/styling/config/scripts/color_generation/specials/_material_badapple.scss b/modules/styling/config/scripts/color_generation/specials/_material_badapple.scss
new file mode 100644
index 0000000..cfab8e9
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/specials/_material_badapple.scss
@@ -0,0 +1,30 @@
+$darkmode: true;
+$primary: #e2e2e2;
+$onPrimary: #000000;
+$primaryContainer: #6b6b6b;
+$onPrimaryContainer: #e2e2e2;
+$secondary: #e2e2e2;
+$onSecondary: #000000;
+$secondaryContainer: #313131;
+$onSecondaryContainer: #e2e2e2;
+$tertiary: #e2e2e2;
+$onTertiary: #000000;
+$tertiaryContainer: #000000;
+$onTertiaryContainer: #e2e2e2;
+$error: #e2e2e2;
+$onError: #000000;
+$errorContainer: #000000;
+$onErrorContainer: #e2e2e2;
+$colorbarbg: #000000;
+$background: #000000;
+$onBackground: #e2e2e2;
+$surface: #161616;
+$onSurface: #e2e2e2;
+$surfaceVariant: #242424;
+$onSurfaceVariant: #e2e2e2;
+$outline: #a1a1a1;
+$shadow: #000000;
+$inverseSurface: #e2e2e2;
+$inverseOnSurface: #000000;
+$inversePrimary: #e2e2e2;
+
diff --git a/modules/styling/config/scripts/color_generation/switchcolor.sh b/modules/styling/config/scripts/color_generation/switchcolor.sh
new file mode 100755
index 0000000..6e7e6b3
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/switchcolor.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+color=$(hyprpicker --no-fancy)
+
+# Generate colors for ags n stuff
+"$HOME"/.config/ags/scripts/color_generation/colorgen.sh "${color}" --apply
diff --git a/modules/styling/config/scripts/color_generation/switchwall.sh b/modules/styling/config/scripts/color_generation/switchwall.sh
new file mode 100755
index 0000000..1129ae0
--- /dev/null
+++ b/modules/styling/config/scripts/color_generation/switchwall.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+if [ "$1" == "--noswitch" ]; then
+ imgpath=$(swww query | awk -F 'image: ' '{print $2}')
+ # imgpath=$(ags run-js 'wallpaper.get(0)')
+else
+ # Select and set image (hyprland)
+ cd "$HOME/Pictures"
+ imgpath=$(yad --width 1200 --height 800 --file --title='Choose wallpaper')
+ screensizey=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2 | head -1)
+ cursorposx=$(hyprctl cursorpos -j | gojq '.x' 2>/dev/null) || cursorposx=960
+ cursorposy=$(hyprctl cursorpos -j | gojq '.y' 2>/dev/null) || cursorposy=540
+ cursorposy_inverted=$(( screensizey - cursorposy ))
+
+ if [ "$imgpath" == '' ]; then
+ echo 'Aborted'
+ exit 0
+ fi
+
+
+ # ags run-js "wallpaper.set('')"
+ # sleep 0.1 && ags run-js "wallpaper.set('${imgpath}')" &
+ swww img "$imgpath" --transition-step 100 --transition-fps 60 \
+ --transition-type grow --transition-angle 30 --transition-duration 1 \
+ --transition-pos "$cursorposx, $cursorposy_inverted"
+fi
+
+# Generate colors for ags n stuff
+"$HOME"/.config/ags/scripts/color_generation/colorgen.sh "${imgpath}" --apply
diff --git a/modules/styling/config/scripts/hyprland/workspace_action.sh b/modules/styling/config/scripts/hyprland/workspace_action.sh
new file mode 100755
index 0000000..33f1008
--- /dev/null
+++ b/modules/styling/config/scripts/hyprland/workspace_action.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+hyprctl dispatch "$1" $(((($(hyprctl activeworkspace -j | gojq -r .id) - 1) / 10) * 10 + $2))
diff --git a/modules/styling/config/scripts/quickscripts/nixos-trim-generations.sh b/modules/styling/config/scripts/quickscripts/nixos-trim-generations.sh
new file mode 100755
index 0000000..98b59be
--- /dev/null
+++ b/modules/styling/config/scripts/quickscripts/nixos-trim-generations.sh
@@ -0,0 +1,243 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+## Defaults
+keepGensDef=30; keepDaysDef=30
+keepGens=$keepGensDef; keepDays=$keepDaysDef
+
+## Usage
+usage () {
+ printf "Usage:\n\t ./trim-generations.sh \n\n
+(defaults are: Keep-Gens=$keepGensDef Keep-Days=$keepDaysDef Profile=user)\n\n"
+ printf "If you enter any parameters, you must enter all three, or none to use defaults.\n"
+ printf "Example:\n\t trim-generations.sh 15 10 home-manager\n"
+ printf " this will work on the home-manager profile and keep all generations from the\n"
+ printf "last 10 days, and keep at least 15 generations no matter how old.\n"
+ printf "\nProfiles available are:\tuser, home-manager, channels, system (root)\n"
+ printf "\n-h or --help prints this help text."
+}
+
+if [ $# -eq 1 ]; then # if help requested
+ if [ $1 = "-h" ]; then
+ usage
+ exit 1;
+ fi
+ if [ $1 = "--help" ]; then
+ usage
+ exit 2;
+ fi
+ printf "Dont recognise your option exiting..\n\n"
+ usage
+ exit 3;
+
+ elif [ $# -eq 0 ]; then # print the defaults
+ printf "The current defaults are:\n Keep-Gens=$keepGensDef Keep-Days=$keepDaysDef \n\n"
+ read -p "Keep these defaults? (y/n):" answer
+
+ case "$answer" in
+ [yY1] )
+ printf "Using defaults..\n"
+ ;;
+ [nN0] ) printf "ok, doing nothing, exiting..\n"
+ exit 6;
+ ;;
+ * ) printf "%b" "Doing nothing, exiting.."
+ exit 7;
+ ;;
+ esac
+fi
+
+## Handle parameters (and change if root)
+if [[ $EUID -ne 0 ]]; then # if not root
+ profile=$(readlink /home/$USER/.nix-profile)
+else
+ if [ -d /nix/var/nix/profiles/system ]; then # maybe this or the other
+ profile="/nix/var/nix/profiles/system"
+ elif [ -d /nix/var/nix/profiles/default ]; then
+ profile="/nix/var/nix/profiles/default"
+ else
+ echo "Cant find profile for root. Exiting"
+ exit 8
+ fi
+fi
+if (( $# < 1 )); then
+ printf "Keeping default: $keepGensDef generations OR $keepDaysDef days, whichever is more\n"
+elif [[ $# -le 2 ]]; then
+ printf "\nError: Not enough arguments.\n\n" >&2
+ usage
+ exit 1
+elif (( $# > 4)); then
+ printf "\nError: Too many arguments.\n\n" >&2
+ usage
+ exit 2
+else
+ if [ $1 -lt 1 ]; then
+ printf "using Gen numbers less than 1 not recommended. Setting to min=1\n"
+ read -p "is that ok? (y/n): " asnwer
+ #printf "$asnwer"
+ case "$asnwer" in
+ [yY1] )
+ printf "ok, continuing..\n"
+ ;;
+ [nN0] )
+ printf "ok, doing nothing, exiting..\n"
+ exit 6;
+ ;;
+ * )
+ printf "%b" "Doing nothing, exiting.."
+ exit 7;
+ ;;
+ esac
+ fi
+ if [ $2 -lt 0 ]; then
+ printf "using negative days number not recommended. Setting to min=0\n"
+ read -p "is that ok? (y/n): " asnwer
+
+ case "$asnwer" in
+ [yY1] )
+ printf "ok, continuing..\n"
+ ;;
+ [nN0] )
+ printf "ok, doing nothing, exiting..\n"
+ exit 6;
+ ;;
+ * )
+ printf "%b" "Doing nothing, exiting.."
+ exit 7;
+ ;;
+ esac
+ fi
+ keepGens=$1; keepDays=$2;
+ (( keepGens < 1 )) && keepGens=1
+ (( keepDays < 0 )) && keepDays=0
+ if [[ $EUID -ne 0 ]]; then
+ if [[ $3 == "user" ]] || [[ $3 == "default" ]]; then
+ profile=$(readlink /home/$USER/.nix-profile)
+ elif [[ $3 == "home-manager" ]]; then
+ # home-manager defaults to $XDG_STATE_HOME; otherwise, use
+ # `home-manager generations` and `nix-store --query --roots
+ # /nix/store/...` to figure out what reference is keeping the old
+ # generations alive.
+ profile="${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles/home-manager"
+ elif [[ $3 == "channels" ]]; then
+ profile="/nix/var/nix/profiles/per-user/$USER/channels"
+ else
+ printf "\nError: Do not understand your third argument. Should be one of: (user / home-manager/ channels)\n\n"
+ usage
+ exit 3
+ fi
+ else
+ if [[ $3 == "system" ]]; then
+ profile="/nix/var/nix/profiles/system"
+ elif [[ $3 == "user" ]] || [[ $3 == "default" ]]; then
+ profile="/nix/var/nix/profiles/default"
+ else
+ printf "\nError: Do not understand your third argument. Should be one of: (user / system)\n\n"
+ usage
+ exit 3
+ fi
+ fi
+ printf "OK! \t Keep Gens = $keepGens \t Keep Days = $keepDays\n\n"
+fi
+
+printf "Operating on profile: \t $profile\n\n"
+
+## Runs at the end, to decide whether to delete profiles that match chosen parameters.
+choose () {
+ local default="$1"
+ local prompt="$2"
+ local answer
+
+ read -p "$prompt" answer
+ [ -z "$answer" ] && answer="$default"
+
+ case "$answer" in
+ [yY1] ) #printf "answered yes!\n"
+ nix-env --delete-generations -p $profile ${!gens[@]}
+ exit 0
+ ;;
+ [nN0] ) printf "Ok doing nothing exiting..\n"
+ exit 6;
+ ;;
+ * ) printf "%b" "Unexpected answer '$answer'!" >&2
+ exit 7;
+ ;;
+ esac
+} # end of function choose
+
+# printf "profile = $profile\n\n"
+## Query nix-env for generations list
+IFS=$'\n' nixGens=( $(nix-env --list-generations -p $profile | sed 's:^\s*::; s:\s*$::' | tr '\t' ' ' | tr -s ' ') )
+timeNow=$(date +%s)
+
+## Get info on oldest generation
+IFS=' ' read -r -a oldestGenArr <<< "${nixGens[0]}"
+oldestGen=${oldestGenArr[0]}
+oldestDate=${oldestGenArr[1]}
+printf "%-30s %s\n" "oldest generation:" $oldestGen
+#oldestDate=${nixGens[0]:3:19}
+printf "%-30s %s\n" "oldest generation created:" $oldestDate
+oldestTime=$(date -d "$oldestDate" +%s)
+oldestElapsedSecs=$((timeNow-oldestTime))
+oldestElapsedMins=$((oldestElapsedSecs/60))
+oldestElapsedHours=$((oldestElapsedMins/60))
+oldestElapsedDays=$((oldestElapsedHours/24))
+printf "%-30s %s\n" "minutes before now:" $oldestElapsedMins
+printf "%-30s %s\n" "hours before now:" $oldestElapsedHours
+printf "%-30s %s\n\n" "days before now:" $oldestElapsedDays
+
+## Get info on current generation
+for i in "${nixGens[@]}"; do
+ IFS=' ' read -r -a iGenArr <<< "$i"
+ genNumber=${iGenArr[0]}
+ genDate=${iGenArr[1]}
+ if [[ "$i" =~ current ]]; then
+ currentGen=$genNumber
+ printf "%-30s %s\n" "current generation:" $currentGen
+ currentDate=$genDate
+ printf "%-30s %s\n" "current generation created:" $currentDate
+ currentTime=$(date -d "$currentDate" +%s)
+ currentElapsedSecs=$((timeNow-currentTime))
+ currentElapsedMins=$((currentElapsedSecs/60))
+ currentElapsedHours=$((currentElapsedMins/60))
+ currentElapsedDays=$((currentElapsedHours/24))
+ printf "%-30s %s\n" "minutes before now:" $currentElapsedMins
+ printf "%-30s %s\n" "hours before now:" $currentElapsedHours
+ printf "%-30s %s\n\n" "days before now:" $currentElapsedDays
+ fi
+done
+
+## Compare oldest and current generations
+timeBetweenOldestAndCurrent=$((currentTime-oldestTime))
+elapsedDays=$((timeBetweenOldestAndCurrent/60/60/24))
+generationsDiff=$((currentGen-oldestGen))
+
+## Figure out what we should do, based on generations and options
+if [[ elapsedDays -le keepDays ]]; then
+ printf "All generations are no more than $keepDays days older than current generation. \nOldest gen days difference from current gen: $elapsedDays \n\n\tNothing to do!\n"
+ exit 4;
+elif [[ generationsDiff -lt keepGens ]]; then
+ printf "Oldest generation ($oldestGen) is only $generationsDiff generations behind current ($currentGen). \n\n\t Nothing to do!\n"
+ exit 5;
+else
+ printf "\tSomething to do...\n"
+ declare -a gens
+ for i in "${nixGens[@]}"; do
+ IFS=' ' read -r -a iGenArr <<< "$i"
+ genNumber=${iGenArr[0]}
+ genDiff=$((currentGen-genNumber))
+ genDate=${iGenArr[1]}
+ genTime=$(date -d "$genDate" +%s)
+ elapsedSecs=$((timeNow-genTime))
+ genDaysOld=$((elapsedSecs/60/60/24))
+ if [[ genDaysOld -gt keepDays ]] && [[ genDiff -ge keepGens ]]; then
+ gens["$genNumber"]="$genDate, $genDaysOld day(s) old"
+ fi
+ done
+ printf "\nFound the following generation(s) to delete:\n"
+ for K in "${!gens[@]}"; do
+ printf "generation $K \t ${gens[$K]}\n"
+ done
+ printf "\n"
+ choose "y" "Do you want to delete these? [Y/n]: "
+fi
diff --git a/modules/styling/config/scripts/record-script.sh b/modules/styling/config/scripts/record-script.sh
new file mode 100755
index 0000000..bc50132
--- /dev/null
+++ b/modules/styling/config/scripts/record-script.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+getdate() {
+ date '+%Y%m%d_%H-%M-%S'
+}
+
+cd ~/Videos || exit
+if pgrep wf-recorder > /dev/null; then
+ notify-send "Recording Stopped" "Stopped" -a 'record-script.sh' &
+ pkill wf-recorder &
+else
+ notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'record-script.sh'
+ if [[ "$1" == "--sound" ]]; then
+ wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" --audio=alsa_output.pci-0000_08_00.6.analog-stereo.monitor & disown
+ elif [[ "$1" == "--fullscreen-sound" ]]; then
+ wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio=alsa_output.pci-0000_08_00.6.analog-stereo.monitor & disown
+ elif [[ "$1" == "--fullscreen" ]]; then
+ wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & disown
+ else
+ wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" & disown
+ fi
+fi
diff --git a/modules/styling/config/scripts/sway/swayToRelativeWs.sh b/modules/styling/config/scripts/sway/swayToRelativeWs.sh
new file mode 100755
index 0000000..c97ebdb
--- /dev/null
+++ b/modules/styling/config/scripts/sway/swayToRelativeWs.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# Check if sway is running
+if ! pgrep -x sway > /dev/null; then
+ echo "Sway is not running"
+ exit 1
+fi
+
+
+# Get the current workspace number
+current=$(swaymsg -t get_workspaces | gojq '.[] | select(.focused==true) | .num')
+
+# Check if a number was passed as an argument
+if [[ "$1" =~ ^[+-]?[0-9]+$ ]]; then
+ new_workspace=$((current + $1))
+else
+ new_workspace=$((current + 1))
+fi
+
+# Check if the new workspace number is out of bounds
+if [[ $new_workspace -lt 1 ]]; then
+ exit 0
+fi
+
+# Switch to the new workspace
+if [[ $2 == 'move' ]]; then
+ swaymsg move container to workspace $new_workspace
+else
+ swaymsg workspace $new_workspace
+fi
diff --git a/modules/styling/config/scripts/templates/foot/foot.ini b/modules/styling/config/scripts/templates/foot/foot.ini
new file mode 100644
index 0000000..045a3b1
--- /dev/null
+++ b/modules/styling/config/scripts/templates/foot/foot.ini
@@ -0,0 +1,133 @@
+shell=fish
+term=xterm-256color
+
+title=foot
+
+font=SpaceMono Nerd Font:size=11
+letter-spacing=0
+# horizontal-letter-offset=0
+# vertical-letter-offset=0
+# underline-offset=
+# box-drawings-uses-font-glyphs=no
+dpi-aware=no
+
+# initial-window-size-pixels=700x500 # Or,
+# initial-window-size-chars=
+# initial-window-mode=windowed
+pad=25x25 # optionally append 'center'
+# resize-delay-ms=100
+
+# notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body}
+
+bold-text-in-bright=no
+# word-delimiters=,│`|:"'()[]{}<>
+# selection-target=primary
+# workers=
+
+[scrollback]
+lines=10000
+
+[url]
+# launch=xdg-open ${url}
+# label-letters=sadfjklewcmpgh
+# osc8-underline=url-mode
+# protocols=http, https, ftp, ftps, file, gemini, gopher
+# uri-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+="'
+
+[cursor]
+style=beam
+color={{ $background }} {{ $onBackground }}
+# blink=no
+beam-thickness=1.5
+# underline-thickness=
+
+
+[colors]
+alpha=1
+background={{ $background }}
+foreground={{ $onBackground }}
+regular0={{ $background }}
+regular1={{ $error }}
+regular2={{ $inversePrimary }}
+regular3={{ $onPrimaryContainer }}
+regular4={{ $onPrimaryContainer }}
+regular5={{ $onSecondaryContainer }}
+regular6={{ $primary }}
+regular7={{ $onSurfaceVariant }}
+bright0={{ $background }}
+bright1={{ $error }}
+bright2={{ $inversePrimary }}
+bright3={{ $onPrimaryContainer }}
+bright4={{ $onPrimaryContainer }}
+bright5={{ $onSecondaryContainer }}
+bright6={{ $primary }}
+bright7={{ $onSurfaceVariant }}
+
+[csd]
+# preferred=server
+# size=26
+# font=
+# color=
+# button-width=26
+# button-color=
+# button-minimize-color=
+# button-maximize-color=
+# button-close-color=
+
+[key-bindings]
+scrollback-up-page=Page_Up
+# scrollback-up-half-page=none
+# scrollback-up-line=none
+scrollback-down-page=Page_Down
+# scrollback-down-half-page=none
+# scrollback-down-line=none
+clipboard-copy=Control+c
+clipboard-paste=Control+v
+# primary-paste=Shift+Insert
+search-start=Control+f
+# font-increase=Control+plus Control+equal Control+KP_Add
+# font-decrease=Control+minus Control+KP_Subtract
+# font-reset=Control+0 Control+KP_0
+# spawn-terminal=Control+Shift+n
+# minimize=none
+# maximize=none
+# fullscreen=none
+# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
+# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
+# pipe-selected=[xargs -r firefox] none
+# show-urls-launch=Control+Shift+u
+# show-urls-copy=none
+
+[search-bindings]
+cancel=Escape
+find-prev=Shift+F3
+find-next=F3 Control+G
+# commit=Return
+# cursor-left=Left Control+b
+# cursor-left-word=Control+Left Mod1+b
+# cursor-right=Right Control+f
+# cursor-right-word=Control+Right Mod1+f
+# cursor-home=Home Control+a
+# cursor-end=End Control+e
+# delete-prev=BackSpace
+# delete-prev-word=Control+BackSpace
+# delete-next=Delete
+# delete-next-word=Mod1+d Control+Delete
+# extend-to-word-boundary=Control+w
+# extend-to-next-whitespace=Control+Shift+w
+# clipboard-paste=Control+v Control+y
+# primary-paste=Shift+Insert
+
+[url-bindings]
+# cancel=Control+g Control+c Control+d Escape
+# toggle-url-visible=t
+
+[mouse-bindings]
+# primary-paste=BTN_MIDDLE
+# select-begin=BTN_LEFT
+# select-begin-block=Control+BTN_LEFT
+# select-extend=BTN_RIGHT
+# select-extend-character-wise=Control+BTN_RIGHT
+# select-word=BTN_LEFT-2
+# select-word-whitespace=Control+BTN_LEFT-2
+# select-row=BTN_LEFT-3
diff --git a/modules/styling/config/scripts/templates/fuzzel/fuzzel.ini b/modules/styling/config/scripts/templates/fuzzel/fuzzel.ini
new file mode 100644
index 0000000..6bc565d
--- /dev/null
+++ b/modules/styling/config/scripts/templates/fuzzel/fuzzel.ini
@@ -0,0 +1,21 @@
+font=Gabarito
+terminal=foot -e
+prompt=">> "
+layer=overlay
+
+[colors]
+background={{ $background }}ff
+text={{ $onBackground }}ff
+selection={{ $surfaceVariant }}ff
+selection-text={{ $onSurfaceVariant }}ff
+border={{ $surfaceVariant }}ff
+match={{ $primary }}ff
+selection-match={{ $primary }}ff
+
+
+[border]
+radius=17
+width=2
+
+[dmenu]
+exit-immediately-if-empty=yes
diff --git a/modules/styling/config/scripts/templates/gradience/preset.json b/modules/styling/config/scripts/templates/gradience/preset.json
new file mode 100644
index 0000000..aeb5748
--- /dev/null
+++ b/modules/styling/config/scripts/templates/gradience/preset.json
@@ -0,0 +1,144 @@
+{
+ "name": "Material3_Generated",
+ "variables": {
+ "theme_fg_color": "#AEE5FA",
+ "theme_text_color": "#AEE5FA",
+ "theme_bg_color": "#1a1b26",
+ "theme_base_color": "#1a1b26",
+ "theme_selected_bg_color": "#AEE5FA",
+ "theme_selected_fg_color": "rgba(0, 0, 0, 0.87)",
+ "insensitive_bg_color": "#1a1b26",
+ "insensitive_fg_color": "rgba(192, 202, 245, 0.5)",
+ "insensitive_base_color": "#24283b",
+ "theme_unfocused_fg_color": "#AEE5FA",
+ "theme_unfocused_text_color": "#c0caf5",
+ "theme_unfocused_bg_color": "#1a1b26",
+ "theme_unfocused_base_color": "#1a1b26",
+ "theme_unfocused_selected_bg_color": "#a9b1d6",
+ "theme_unfocused_selected_fg_color": "rgba(0, 0, 0, 0.87)",
+ "unfocused_insensitive_color": "rgba(192, 202, 245, 0.5)",
+ "borders": "rgba(192, 202, 245, 0.12)",
+ "unfocused_borders": "rgba(192, 202, 245, 0.12)",
+ "warning_color": "#FDD633",
+ "error_color": "#BA1B1B",
+ "success_color": "#81C995",
+ "wm_title": "#AEE5FA",
+ "wm_unfocused_title": "rgba(192, 202, 245, 0.7)",
+ "wm_highlight": "rgba(192, 202, 245, 0.1)",
+ "wm_bg": "#1a1b26",
+ "wm_unfocused_bg": "#1a1b26",
+ "wm_button_close_icon": "#1a1b26",
+ "wm_button_close_hover_bg": "#a9b1d6",
+ "wm_button_close_active_bg": "#c7c7c7",
+ "content_view_bg": "#1a1b26",
+ "placeholder_text_color": "silver",
+ "text_view_bg": "#1d1d1d",
+ "budgie_tasklist_indicator_color": "#90D1F6",
+ "budgie_tasklist_indicator_color_active": "#90D1F6",
+ "budgie_tasklist_indicator_color_active_window": "#999999",
+ "budgie_tasklist_indicator_color_attention": "#FDD633",
+ "STRAWBERRY_100": "#FF9262",
+ "STRAWBERRY_300": "#FF793E",
+ "STRAWBERRY_500": "#F15D22",
+ "STRAWBERRY_700": "#CF3B00",
+ "STRAWBERRY_900": "#AC1800",
+ "ORANGE_100": "#FFDB91",
+ "ORANGE_300": "#FFCA40",
+ "ORANGE_500": "#FAA41A",
+ "ORANGE_700": "#DE8800",
+ "ORANGE_900": "#C26C00",
+ "BANANA_100": "#FFFFA8",
+ "BANANA_300": "#FFFA7D",
+ "BANANA_500": "#FFCE51",
+ "BANANA_700": "#D1A023",
+ "BANANA_900": "#A27100",
+ "LIME_100": "#A2F3BE",
+ "LIME_300": "#8ADBA6",
+ "LIME_500": "#73C48F",
+ "LIME_700": "#479863",
+ "LIME_900": "#1C6D38",
+ "BLUEBERRY_100": "#94A6FF",
+ "BLUEBERRY_300": "#6A7CE0",
+ "BLUEBERRY_500": "#3F51B5",
+ "BLUEBERRY_700": "#213397",
+ "BLUEBERRY_900": "#031579",
+ "GRAPE_100": "#D25DE6",
+ "GRAPE_300": "#B84ACB",
+ "GRAPE_500": "#9C27B0",
+ "GRAPE_700": "#830E97",
+ "GRAPE_900": "#6A007E",
+ "COCOA_100": "#9F9792",
+ "COCOA_300": "#7B736E",
+ "COCOA_500": "#574F4A",
+ "COCOA_700": "#463E39",
+ "COCOA_900": "#342C27",
+ "SILVER_100": "#EEE",
+ "SILVER_300": "#CCC",
+ "SILVER_500": "#AAA",
+ "SILVER_700": "#888",
+ "SILVER_900": "#666",
+ "SLATE_100": "#888",
+ "SLATE_300": "#666",
+ "SLATE_500": "#444",
+ "SLATE_700": "#222",
+ "SLATE_900": "#111",
+ "BLACK_100": "#474341",
+ "BLACK_300": "#403C3A",
+ "BLACK_500": "#393634",
+ "BLACK_700": "#33302F",
+ "BLACK_900": "#2B2928",
+ "accent_bg_color": "{{ $primary }}",
+ "accent_fg_color": "{{ $onPrimary }}",
+ "accent_color": "{{ $primary }}",
+ "destructive_bg_color": "{{ $error }}",
+ "destructive_fg_color": "{{ $onError }}",
+ "destructive_color": "{{ $error }}",
+ "success_bg_color": "#81C995",
+ "success_fg_color": "rgba(0, 0, 0, 0.87)",
+ "warning_bg_color": "#FDD633",
+ "warning_fg_color": "rgba(0, 0, 0, 0.87)",
+ "error_bg_color": "{{ $error }}",
+ "error_fg_color": "{{ $onError }}",
+ "window_bg_color": "{{ $background }}",
+ "window_fg_color": "{{ $onBackground }}",
+ "view_bg_color": "{{ $surface }}",
+ "view_fg_color": "{{ $onSurface }}",
+ "headerbar_bg_color": "mix(@dialog_bg_color, @window_bg_color, 0.5)",
+ "headerbar_fg_color": "{{ $onSecondaryContainer }}",
+ "headerbar_border_color": "{{ $secondaryContainer }}",
+ "headerbar_backdrop_color": "@headerbar_bg_color",
+ "headerbar_shade_color": "rgba(0, 0, 0, 0.09)",
+ "card_bg_color": "{{ $background }}",
+ "card_fg_color": "{{ $onSecondaryContainer }}",
+ "card_shade_color": "rgba(0, 0, 0, 0.09)",
+ "dialog_bg_color": "{{ $secondaryContainer }}",
+ "dialog_fg_color": "{{ $onSecondaryContainer }}",
+ "popover_bg_color": "{{ $secondaryContainer }}",
+ "popover_fg_color": "{{ $onSecondaryContainer }}",
+ "thumbnail_bg_color": "#1a1b26",
+ "thumbnail_fg_color": "#AEE5FA",
+ "shade_color": "rgba(0, 0, 0, 0.36)",
+ "scrollbar_outline_color": "rgba(0, 0, 0, 0.5)",
+
+ "sidebar_bg_color": "@window_bg_color",
+ "sidebar_fg_color":"@window_fg_color",
+ "sidebar_border_color": "@sidebar_bg_color",
+ "sidebar_backdrop_color": "@sidebar_bg_color"
+ },
+ "palette": {
+ "blue_": {},
+ "green_": {},
+ "yellow_": {},
+ "orange_": {},
+ "red_": {},
+ "purple_": {},
+ "brown_": {},
+ "light_": {},
+ "dark_": {}
+ },
+ "custom_css": {
+ "gtk4": "",
+ "gtk3": ""
+ },
+ "plugins": {}
+}
\ No newline at end of file
diff --git a/modules/styling/config/scripts/templates/gtklock/main.scss b/modules/styling/config/scripts/templates/gtklock/main.scss
new file mode 100644
index 0000000..5440e3a
--- /dev/null
+++ b/modules/styling/config/scripts/templates/gtklock/main.scss
@@ -0,0 +1,89 @@
+// Could just sed but scss is better
+@import '../../../scss/_material.scss'; // Which is ~/.config/ags/scss/_material.scss
+
+* {
+ all: unset;
+ border: 0rem;
+}
+
+window {
+ background-color: transparentize($background, 0.6);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+#window-box {
+ border-radius: 1.5rem;
+ padding: 1.5rem;
+}
+
+#input-label {
+ font-size: 1.5rem;
+ color: transparent;
+ background-color: transparent;
+ margin: -20rem; // bye bye
+}
+
+#input-field {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ caret-color: $onSecondaryContainer;
+ border-radius: 999px;
+ font-size: 1.3rem;
+ padding: 0.341rem 1.364rem;
+ margin: 0.477rem;
+ box-shadow: 2px 2px 4px rgba(22, 22, 22, 0.5);
+ min-height: 2.727rem;
+}
+
+#unlock-button {
+ margin: -20rem; // bye bye
+ color: transparent;
+ background-color: transparent;
+}
+
+#error-label {
+ color: $error;
+}
+
+#clock-label {
+ font-family: 'Lexend';
+ font-size: 6rem;
+ border-radius: 1.2rem;
+ padding: 0.5rem;
+ margin: 0.6rem;
+ margin-top: -35rem; // higher clock position
+ color: $onSecondaryContainer;
+ text-shadow: 1px 1px 2px rgba(22, 22, 30, 0.5);
+}
+
+// #user-image {}
+
+// #powerbar-box {}
+
+#poweroff-button,
+#reboot-button,
+#suspend-button {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ min-width: 3rem;
+ min-height: 3rem;
+ margin: 0.341rem;
+ border-radius: 9999px;
+}
+
+#poweroff-button:hover,
+#reboot-button:hover,
+#suspend-button:hover,
+#poweroff-button:focus,
+#reboot-button:focus,
+#suspend-button:focus {
+ background-color: mix($secondaryContainer, white, 80%);
+}
+
+#poweroff-button:active,
+#reboot-button:active,
+#suspend-button:active {
+ background-color: mix($secondaryContainer, white, 70%);
+}
\ No newline at end of file
diff --git a/modules/styling/config/scripts/templates/hypr/colors.conf b/modules/styling/config/scripts/templates/hypr/colors.conf
new file mode 100644
index 0000000..7588d93
--- /dev/null
+++ b/modules/styling/config/scripts/templates/hypr/colors.conf
@@ -0,0 +1,32 @@
+$SLURP_COMMAND="$(slurp -d -c {{ $onSecondaryContainer }}BB -b {{ $secondaryContainer }}44 -s 00000000)"
+
+general {
+ col.active_border = rgba({{ $onPrimary }}FF)
+ col.inactive_border = rgba({{ $secondaryContainer }}CC)
+}
+
+misc {
+ background_color = rgba({{ $surface }}FF)
+}
+
+plugin {
+ hyprbars {
+ # Honestly idk if it works like css, but well, why not
+ bar_text_font = Rubik, Geist, AR One Sans, Reddit Sans, Inter, Roboto, Ubuntu, Noto Sans, sans-serif
+ bar_height = 30
+ bar_padding = 10
+ bar_button_padding = 5
+ bar_precedence_over_border = true
+ bar_part_of_window = true
+
+ bar_color = rgb({{ $background }})
+ col.text = rgb({{ $onBackground }})
+
+
+ # example buttons (R -> L)
+ # hyprbars-button = color, size, on-click
+ hyprbars-button = rgb({{ $onBackground }}), 13, , hyprctl dispatch killactive
+ hyprbars-button = rgb({{ $onBackground }}), 13, , hyprctl dispatch fullscreen 1
+ hyprbars-button = rgb({{ $onBackground }}), 13, , hyprctl dispatch movetoworkspacesilent special
+ }
+}
diff --git a/modules/styling/config/scripts/templates/terminal/sequences.txt b/modules/styling/config/scripts/templates/terminal/sequences.txt
new file mode 100644
index 0000000..907eb9f
--- /dev/null
+++ b/modules/styling/config/scripts/templates/terminal/sequences.txt
@@ -0,0 +1 @@
+]4;0;#$background #\]4;1;#$error #\]4;2;#$inversePrimary #\]4;3;#$onPrimaryContainer #\]4;4;#$onPrimaryContainer #\]4;5;#$onSecondaryContainer #\]4;6;#$primary #\]4;7;#$onSurfaceVariant #\]4;8;#$background #\]4;9;#$error #\]4;10;#$inversePrimary #\]4;11;#$onPrimaryContainer #\]4;12;#$onPrimaryContainer #\]4;13;#$onSecondaryContainer #\]4;14;#$primary #\]4;15;#$onSurfaceVariant #\]10;#$onSurfaceVariant #\]11;#$background #\]12;#$onSurfaceVariant #\]13;#$onSurfaceVariant #\]17;#$onSurfaceVariant #\]19;#$background #\]4;232;#$background #\]4;256;#$onSurfaceVariant #\]708;#$background #\
\ No newline at end of file
diff --git a/modules/styling/config/scripts/wayland-idle-inhibitor.py b/modules/styling/config/scripts/wayland-idle-inhibitor.py
new file mode 100755
index 0000000..5a6d0e4
--- /dev/null
+++ b/modules/styling/config/scripts/wayland-idle-inhibitor.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+import sys
+from dataclasses import dataclass
+from signal import SIGINT, SIGTERM, signal
+from threading import Event
+
+from pywayland.client.display import Display
+from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import (
+ ZwpIdleInhibitManagerV1,
+)
+from pywayland.protocol.wayland.wl_compositor import WlCompositor
+from pywayland.protocol.wayland.wl_registry import WlRegistryProxy
+from pywayland.protocol.wayland.wl_surface import WlSurface
+
+
+@dataclass
+class GlobalRegistry:
+ surface: WlSurface | None = None
+ inhibit_manager: ZwpIdleInhibitManagerV1 | None = None
+
+
+def handle_registry_global(
+ wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int
+) -> None:
+ global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry()
+
+ if iface_name == "wl_compositor":
+ compositor = wl_registry.bind(id_num, WlCompositor, version)
+ global_registry.surface = compositor.create_surface() # type: ignore
+ elif iface_name == "zwp_idle_inhibit_manager_v1":
+ global_registry.inhibit_manager = wl_registry.bind(
+ id_num, ZwpIdleInhibitManagerV1, version
+ )
+
+
+def main() -> None:
+ done = Event()
+ signal(SIGINT, lambda _, __: done.set())
+ signal(SIGTERM, lambda _, __: done.set())
+
+ global_registry = GlobalRegistry()
+
+ display = Display()
+ display.connect()
+
+ registry = display.get_registry() # type: ignore
+ registry.user_data = global_registry
+ registry.dispatcher["global"] = handle_registry_global
+
+ def shutdown() -> None:
+ display.dispatch()
+ display.roundtrip()
+ display.disconnect()
+
+ display.dispatch()
+ display.roundtrip()
+
+ if global_registry.surface is None or global_registry.inhibit_manager is None:
+ print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.")
+ shutdown()
+ sys.exit(1)
+
+ inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore
+ global_registry.surface
+ )
+
+ display.dispatch()
+ display.roundtrip()
+
+ print("Inhibiting idle...")
+ done.wait()
+ print("Shutting down...")
+
+ inhibitor.destroy()
+
+ shutdown()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/modules/styling/config/scss/_bar.scss b/modules/styling/config/scss/_bar.scss
new file mode 100644
index 0000000..f835660
--- /dev/null
+++ b/modules/styling/config/scss/_bar.scss
@@ -0,0 +1,321 @@
+// Made to be pixel-perfect with 11pt font size
+// 1rem = 11pt = 14.6666666667px
+
+$black: black;
+$white: white;
+$bar_ws_width: 1.774rem;
+$bar_subgroup_bg: $surfaceVariant;
+
+@mixin bar-group-rounding {
+ @include small-rounding;
+}
+
+.bar-height {
+ min-height: 2.727rem;
+}
+
+.bar-bg {
+ background-color: $t_background;
+ min-height: 2.727rem;
+}
+
+.bar-sidespace {
+ min-width: 1.5rem;
+}
+
+.bar-group-margin {
+ padding: 0.273rem 0rem;
+}
+
+.bar-group {
+ background-color: $l_l_t_surfaceVariant;
+}
+
+.bar-group-pad {
+ padding: 0.205rem;
+}
+
+.bar-group-pad-less {
+ padding: 0rem 0.681rem;
+}
+
+.bar-group-pad-system {
+ padding: 0rem 0.341rem;
+}
+
+.bar-group-pad-music {
+ padding-right: 1.023rem;
+ padding-left: 0.341rem;
+}
+
+.bar-group-standalone {
+ @include bar-group-rounding;
+ -gtk-outline-radius: 1.364rem;
+}
+
+.bar-group-round {
+ border-radius: 10rem;
+ -gtk-outline-radius: 10rem;
+}
+
+.bar-group-middle {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+}
+
+.bar-group-left {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+ border-top-left-radius: 1.364rem;
+ border-bottom-left-radius: 1.364rem;
+}
+
+.bar-group-right {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+ border-top-right-radius: 1.364rem;
+ border-bottom-right-radius: 1.364rem;
+}
+
+.bar-ws-width {
+ min-width: 18.341rem;
+}
+
+.bar-ws {
+ min-width: $bar_ws_width;
+ color: mix($onBackground, $background, 40%);
+ @if $darkmode ==true {
+ color: mix($onBackground, $background, 45%);
+ }
+}
+
+.bar-ws-active {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.bar-ws-occupied {
+ background-color: $bar_subgroup_bg;
+ color: $onSurfaceVariant;
+}
+
+.bar-separator {
+ @include full-rounding;
+ min-width: 0.341rem;
+ min-height: 0.341rem;
+ background-color: mix($t_surface, $t_onSurface, 90%);
+ margin: 0rem 0.341rem;
+}
+
+.bar-clock-box {
+ margin: 0rem 0.682rem;
+}
+
+.bar-clock {
+ @include titlefont;
+ font-size: 1.2727rem;
+}
+
+.bar-date {
+ @include titlefont;
+ font-size: 1rem;
+ color: $onBackground;
+}
+
+.bar-batt {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.bar-sidemodule {
+ min-width: 26rem;
+}
+
+.bar-batt-low {
+ background-color: $error;
+ color: $errorContainer;
+}
+
+.bar-batt-full {
+ background-color: $successContainer;
+ color: $onSuccessContainer;
+}
+
+.bar-batt-circprog {
+ @include fluent_decel_long;
+ min-width: 0.068rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.bar-batt-circprog-low {
+ background-color: $error;
+ color: $errorContainer;
+}
+
+.bar-batt-circprog-full {
+ background-color: $successContainer;
+ color: $onSuccessContainer;
+}
+
+.bar-music-playstate {
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.bar-music-circprog {
+ @include fluent_decel_long;
+ min-width: 0.068rem; // line width
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.bar-music-playstate-playing {
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.bar-music-playstate-txt {
+ transition: 100ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ @include icon-material;
+}
+
+.bar-music-cover {
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 100% auto;
+ min-width: 11.932rem;
+}
+
+.bar-music-extended-bg {
+ border-radius: 1.364rem;
+ min-width: 34.091rem;
+}
+
+.bar-music-extended-ctl-bg {
+ border-radius: 1.364rem;
+ background-color: rgba(30, 30, 30, 0.6);
+}
+
+.bar-music-hide-false {
+ @include menu_decel;
+ transition-duration: 100ms;
+ opacity: 1;
+}
+
+.bar-music-hide-true {
+ @include menu_accel;
+ transition-duration: 100ms;
+ opacity: 0;
+}
+
+.bar-corner-spacing {
+ min-width: $rounding_large;
+ min-height: $rounding_large;
+}
+
+.corner {
+ background-color: $t_background;
+ @include large-rounding;
+}
+
+.corner-black {
+ background-color: $black; // Hard code: fake screen corner
+ @include large-rounding;
+}
+
+.bar-topdesc {
+ margin-top: -0.136rem;
+ margin-bottom: -0.341rem;
+ color: $subtext;
+}
+
+.bar-space-button {
+ padding: 0.341rem;
+}
+
+.bar-space-button > box:first-child {
+ @include full-rounding;
+ padding: 0rem 0.682rem;
+}
+
+.bar-space-button:hover > box:first-child,
+.bar-space-button:focus > box:first-child {
+ background-color: $hovercolor;
+}
+
+.bar-space-button:active > box:first-child {
+ background-color: $activecolor;
+}
+
+.bar-space-button-leftmost {
+ box {
+ margin: 0rem 0.682rem;
+ }
+}
+
+.bar-space-area-rightmost > box {
+ padding-right: 2.386rem;
+}
+
+.bar-systray {
+ @include full-rounding;
+ margin: 0.137rem 0rem;
+ padding: 0rem 0.682rem;
+}
+
+.bar-systray-item {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.032rem;
+ min-width: 1.032rem;
+ font-size: 1.032rem;
+}
+
+.bar-statusicons {
+ @include full-rounding;
+ @include element_decel;
+ margin: 0.273rem;
+ padding: 0rem 0.614rem;
+}
+
+.bar-statusicons-hover {
+ background-color: mix($t_background, $t_onBackground, 90%);
+}
+
+.bar-statusicons-active {
+ background-color: mix($t_background, $t_onBackground, 80%);
+}
+
+.bar-util-btn {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ background-color: $bar_subgroup_bg;
+}
+
+.bar-util-btn:hover,
+.bar-util-btn:focus {
+ background-color: mix($bar_subgroup_bg, $onSurfaceVariant, 90%);
+}
+
+.bar-util-btn:active {
+ background-color: mix($bar_subgroup_bg, $onSurfaceVariant, 80%);
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_cheatsheet.scss b/modules/styling/config/scss/_cheatsheet.scss
new file mode 100644
index 0000000..8681e1c
--- /dev/null
+++ b/modules/styling/config/scss/_cheatsheet.scss
@@ -0,0 +1,52 @@
+.cheatsheet-bg {
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ margin-bottom: 0.682rem;
+ background-color: $t_background;
+ padding: 1.364rem;
+}
+
+.cheatsheet-key {
+ @include techfont;
+ min-height: 1.364rem;
+ min-width: 1.364rem;
+ margin: 0.17rem;
+ padding: 0.136rem 0.205rem;
+ border-radius: 0.409rem;
+ -gtk-outline-radius: 0.409rem;
+ color: $primary;
+ border: 0.068rem solid $primary;
+ box-shadow: 0rem 0.136rem 0rem $primary;
+ font-weight: 500;
+}
+
+.cheatsheet-key-notkey {
+ min-height: 1.364rem;
+ padding: 0.136rem 0.205rem;
+ margin: 0.17rem;
+ color: $onPrimaryContainer;
+}
+
+// .cheatsheet-action {}
+
+.cheatsheet-closebtn {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+}
+
+.cheatsheet-closebtn:hover,
+.cheatsheet-closebtn:focus {
+ background-color: $hovercolor;
+}
+
+.cheatsheet-closebtn:active {
+ background-color: $activecolor;
+}
+
+.cheatsheet-category-title {
+ @include titlefont;
+ font-size: 1.705rem;
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_colors.scss b/modules/styling/config/scss/_colors.scss
new file mode 100644
index 0000000..2cc6ef4
--- /dev/null
+++ b/modules/styling/config/scss/_colors.scss
@@ -0,0 +1,132 @@
+///////////// COLOR MODIFICATIONS /////////////
+// Material colors provide excellent readability, but can be uninteresting.
+// This is an attempt to improve that.
+$transparency_enabled: false;
+
+@if $transparency_enabled ==false {
+ @if $darkmode ==true {
+ $primary: mix($primary, white, 70%);
+ $primaryContainer: mix($primaryContainer, white, 90%);
+ $background: mix(mix($background, $primary, 94%), #000000, 50%);
+ $surface: mix($surface, $primaryContainer, 98%);
+ $surfaceVariant: mix($surfaceVariant, #000000, 75%);
+ // $secondaryContainer: mix($secondaryContainer, $primaryContainer, 90%);
+ }
+
+ @if $darkmode ==false {
+ $background: mix($background, $primary, 87%);
+ $surface: mix($surface, $primary, 93%);
+ $surfaceVariant: mix($surfaceVariant, #ffffff, 25%);
+ }
+}
+
+@if $transparency_enabled ==true {
+ @if $darkmode ==true {
+ $background: mix(mix($background, $primary, 94%), #000000, 50%);
+ $surface: mix($surface, $primaryContainer, 98%);
+ $surfaceVariant: mix($surfaceVariant, #000000, 55%);
+ }
+
+ @if $darkmode ==false {
+ $background: mix($background, $primary, 94%);
+ $surface: mix($surface, $primary, 93%);
+ $surfaceVariant: mix($surfaceVariant, #ffffff, 55%);
+ }
+}
+
+// Amounts
+$transparentize_amount: 0.3;
+$transparentize_surface_amount_less: 0.6;
+$transparentize_surface_amount_less_less: 0.55;
+$transparentize_surface_amount: 0.7;
+$transparentize_surface_amount_more: 0.8;
+$transparentize_surface_amount_subtract_surface: $transparentize_surface_amount - $transparentize_amount;
+
+@if $darkmode ==true {
+ // Less transparency
+ $transparentize_amount: 0.15;
+ $transparentize_surface_amount_less: 0.5;
+ $transparentize_surface_amount_less_less: 0.55;
+ $transparentize_surface_amount: 0.69;
+ $transparentize_surface_amount_more: 0.9;
+ $transparentize_surface_amount_subtract_surface: $transparentize_surface_amount - $transparentize_amount;
+}
+
+@if $transparency_enabled ==false {
+ $transparentize_amount: 0;
+}
+
+// Extended material
+$success: #4f6354;
+$onSuccess: #ffffff;
+$successContainer: #d1e8d5;
+$onSuccessContainer: #0c1f13;
+
+@if $darkmode ==true {
+ // Dark variant
+ $success: #b5ccba;
+ $onSuccess: #213528;
+ $successContainer: #374b3e;
+ $onSuccessContainer: #d1e9d6;
+}
+
+// Transparent versions
+$t_primary: transparentize($primary, $transparentize_amount);
+$t_onPrimary: transparentize($onPrimary, $transparentize_amount);
+$t_primaryContainer: transparentize($primaryContainer, $transparentize_amount);
+$t_onPrimaryContainer: transparentize($onPrimaryContainer, $transparentize_amount);
+$t_secondary: transparentize($secondary, $transparentize_amount);
+$t_onSecondary: transparentize($onSecondary, $transparentize_amount);
+$t_secondaryContainer: transparentize($secondaryContainer, $transparentize_amount);
+$l_t_secondaryContainer: transparentize($secondaryContainer, $transparentize_surface_amount_less);
+$t_onSecondaryContainer: transparentize($onSecondaryContainer, $transparentize_amount);
+$t_t_t_onSecondaryContainer: transparentize($onSecondaryContainer, 0.93);
+$t_tertiary: transparentize($tertiary, $transparentize_amount);
+$t_onTertiary: transparentize($onTertiary, $transparentize_amount);
+$t_tertiaryContainer: transparentize($tertiaryContainer, $transparentize_amount);
+$t_onTertiaryContainer: transparentize($onTertiaryContainer, $transparentize_amount);
+$t_error: transparentize($error, $transparentize_amount);
+$t_onError: transparentize($onError, $transparentize_amount);
+$t_errorContainer: transparentize($errorContainer, $transparentize_amount);
+$t_onErrorContainer: transparentize($onErrorContainer, $transparentize_amount);
+$t_colorbarbg: transparentize($colorbarbg, $transparentize_amount);
+$t_background: transparentize($background, $transparentize_amount);
+$t_t_background: transparentize($background, $transparentize_surface_amount_more);
+$t_onBackground: transparentize($onBackground, $transparentize_amount);
+$t_surface: transparentize($surface, $transparentize_surface_amount);
+$t_t_surface: transparentize($surface, $transparentize_surface_amount_more);
+$t_onSurface: transparentize($onSurface, $transparentize_surface_amount);
+$t_surfaceVariant: transparentize($surfaceVariant, $transparentize_surface_amount);
+$t_onSurfaceVariant: transparentize($onSurfaceVariant, $transparentize_surface_amount);
+$t_t_surfaceVariant: transparentize($surfaceVariant, $transparentize_surface_amount_more);
+$l_t_surfaceVariant: transparentize($surfaceVariant, $transparentize_surface_amount_less);
+$l_l_t_surfaceVariant: transparentize($surfaceVariant, $transparentize_surface_amount_less_less);
+$t_outline: transparentize($outline, $transparentize_amount);
+$t_shadow: transparentize($shadow, $transparentize_amount);
+$t_inverseSurface: transparentize($inverseSurface, $transparentize_amount);
+$t_inverseOnSurface: transparentize($inverseOnSurface, $transparentize_amount);
+$t_inversePrimary: transparentize($inversePrimary, $transparentize_amount);
+// Transparent material (extended)
+$t_success: transparentize($error, $transparentize_amount);
+$t_onSuccess: transparentize($onError, $transparentize_amount);
+$t_successContainer: transparentize($errorContainer, $transparentize_amount);
+$t_onSuccessContainer: transparentize($onErrorContainer,
+ $transparentize_amount);
+
+// Others
+$hovercolor: mix($t_surface, $t_onSurface, 50%);
+$activecolor: mix($t_surface, $t_onSurface, 30%);
+$subtext: mix($onBackground, $background, 70%);
+$actiontext: mix($onBackground, $background, 85%);
+
+// Terminal colors
+$termbg: mix($t_surfaceVariant, $t_onSurfaceVariant, 80%);
+$termfg: $onSurfaceVariant;
+$term0: $t_background;
+$term1: $error;
+$term2: $inversePrimary;
+$term3: $onPrimaryContainer;
+$term4: $onPrimaryContainer;
+$term5: $onSecondaryContainer;
+$term6: $primary;
+$term7: $onSurfaceVariant;
\ No newline at end of file
diff --git a/modules/styling/config/scss/_common.scss b/modules/styling/config/scss/_common.scss
new file mode 100644
index 0000000..f7530f5
--- /dev/null
+++ b/modules/styling/config/scss/_common.scss
@@ -0,0 +1,234 @@
+// * {
+// border: 1px solid $onSurfaceVariant; // Debugging
+// }
+
+// *:focus {
+// outline: 1px solid mix($onSurface, $surface, 40%);
+// -gtk-outline-radius: $rounding_small;
+// }
+
+* {
+ selection {
+ background-color: $secondary;
+ color: $onSecondary;
+ }
+
+ caret-color: $primary;
+}
+
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+menu {
+ @include elevation-border-softer;
+ padding: 0.681rem;
+ background: $surfaceVariant;
+ color: $onSurfaceVariant;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+
+ animation-name: appear;
+ animation-duration: 40ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+}
+
+menubar>menuitem {
+ border-radius: 0.545rem;
+ -gtk-outline-radius: 0.545rem;
+ min-width: 13.636rem;
+ min-height: 2.727rem;
+}
+
+menu>menuitem {
+ padding: 0.4em 1.5rem;
+ background: transparent;
+ transition: 0.2s ease background;
+ border-radius: 0.545rem;
+ -gtk-outline-radius: 0.545rem;
+}
+
+menu>menuitem:hover,
+menu>menuitem:focus {
+ background-color: mix($surfaceVariant, $onSurfaceVariant, 90%);
+}
+
+menu>menuitem:active {
+ background-color: mix($surfaceVariant, $onSurfaceVariant, 80%);
+}
+
+radio {
+ @include full-rounding;
+ margin: 0.273rem;
+ min-width: 15px;
+ min-height: 15px;
+ border: 0.068rem solid $outline;
+}
+
+// radio:first-child {
+// background-color: red;
+// }
+
+radio:checked {
+ min-width: 8px;
+ min-height: 8px;
+ background-color: $onPrimary;
+ border: 0.477rem solid $primary;
+}
+
+tooltip {
+ animation-name: appear;
+ animation-duration: 100ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+ @include normal-rounding;
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ border: 1px solid $onSurfaceVariant;
+}
+
+/////////////////////////////////////////
+// Emoji Chooser structure
+// popover
+// ├── box.emoji-searchbar
+// │ ╰── entry.search
+// ╰── box.emoji-toolbar
+// ├── button.image-button.emoji-section
+// ├── ...
+// ╰── button.image-button.emoji-section
+
+popover {
+ @include elevation-border-softer;
+ padding: 0.681rem;
+ background: $surfaceVariant;
+ color: $onSurfaceVariant;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+
+ animation-name: appear;
+ animation-duration: 40ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+}
+
+
+/////////////////////////////////////////
+
+.configtoggle-box {
+ padding: 0.205rem 0.341rem;
+ border: 0.136rem solid transparent;
+}
+
+.configtoggle-box:focus {
+ border: 0.136rem solid mix($onSurface, $surface, 40%);
+}
+
+.switch-bg {
+ @include element_decel;
+ @include full-rounding;
+ background-color: mix($surface, $background, 50%);
+ border: 0.136rem solid $onSurface;
+ min-width: 2.864rem;
+ min-height: 1.637rem;
+}
+
+.switch-bg-true {
+ background-color: $primary;
+ border: 0.136rem solid $primary;
+}
+
+.switch-fg {
+ @include full-rounding;
+ @include menu_decel;
+ background-color: $onSurface;
+ color: $surface;
+ min-width: 0.819rem;
+ min-height: 0.819rem;
+ margin-left: 0.477rem;
+}
+
+.switch-fg-true {
+ background-color: $onPrimary;
+ color: $primary;
+ min-width: 1.431rem;
+ min-height: 1.431rem;
+ margin-left: 1.431rem;
+}
+
+.switch-fg-toggling-false {
+ @include menu_decel;
+ min-width: 1.636rem;
+ min-height: 0.819rem;
+}
+
+.segment-container {
+ @include full-rounding;
+ border: 0.068rem solid $outline;
+}
+
+.segment-container>*:first-child {
+ border-top-left-radius: 9999px;
+ border-bottom-left-radius: 9999px;
+}
+
+.segment-container>* {
+ border-right: 0.068rem solid $outline;
+ padding: 0.341rem 0.682rem;
+}
+
+.segment-container>*:last-child {
+ border-right: 0rem solid transparent;
+ border-top-right-radius: 9999px;
+ border-bottom-right-radius: 9999px;
+}
+
+.segment-btn {
+ color: $onSurface;
+}
+
+.segment-btn:focus,
+.segment-btn:hover {
+ background-color: $hovercolor;
+}
+
+.segment-btn-enabled {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.segment-btn-enabled:hover,
+.segment-btn-enabled:focus {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.gap-v-5 {
+ min-height: 0.341rem;
+}
+
+.gap-h-5 {
+ min-width: 0.341rem;
+}
+
+.gap-v-10 {
+ min-height: 0.682rem;
+}
+
+.gap-h-10 {
+ min-width: 0.682rem;
+}
+
+.gap-v-15 {
+ min-height: 1.023rem;
+}
+
+.gap-h-15 {
+ min-width: 1.023rem;
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_desktopbackground.scss b/modules/styling/config/scss/_desktopbackground.scss
new file mode 100644
index 0000000..48392e4
--- /dev/null
+++ b/modules/styling/config/scss/_desktopbackground.scss
@@ -0,0 +1,94 @@
+.bg-wallpaper-transition {
+ transition: 1000ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ font-size: 1px;
+}
+
+@mixin bg-textshadow {
+ // text-shadow: mix($shadow, $secondaryContainer, 50%) 1px 0px 3px;
+}
+
+.bg-time-box {
+ @include large-rounding;
+ margin: 2.045rem;
+ padding: 0.682rem;
+}
+
+.bg-time-clock {
+ @include bg-textshadow;
+ font-family: 'Gabarito';
+ font-size: 5.795rem;
+ color: $onBackground;
+}
+
+.bg-time-date {
+ @include bg-textshadow;
+ font-family: 'Gabarito';
+ font-size: 2.591rem;
+ color: $onBackground;
+}
+
+.bg-distro-box {
+ @include large-rounding;
+ margin: 2.045rem;
+ padding: 0.682rem;
+}
+
+.bg-distro-txt {
+ @include bg-textshadow;
+ font-family: 'Gabarito';
+ font-size: 1.432rem;
+ color: $onBackground;
+}
+
+.bg-distro-name {
+ @include bg-textshadow;
+ font-family: 'Gabarito';
+ font-size: 1.432rem;
+ color: $onSecondaryContainer;
+}
+
+.bg-graph {
+ color: rgba(255, 255, 255, 0.5);
+ border-radius: 0.614rem;
+ border: 0.682rem solid;
+}
+
+.bg-quicklaunch-title {
+ @include mainfont;
+ color: $onSurfaceVariant;
+}
+
+.bg-quicklaunch-btn {
+ @include mainfont;
+ @include full-rounding;
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ border: 0.068rem solid $subtext;
+ min-width: 4.432rem;
+ min-height: 2.045rem;
+ padding: 0.273rem 0.682rem;
+}
+
+.bg-quicklaunch-btn:hover,
+.bg-quicklaunch-btn:focus {
+ background-color: mix($surfaceVariant, $onSurfaceVariant, 95%);
+}
+
+.bg-quicklaunch-btn:active {
+ background-color: mix($surfaceVariant, $onSurfaceVariant, 90%);
+}
+
+.bg-system-bg {
+ @include normal-rounding;
+ // background-color: $background;
+}
+
+.bg-system-circprog {
+ @include fluent_decel_long;
+ // margin-left: 0.273rem;
+ min-width: 0.205rem; // Trough stroke width
+ min-height: 4.091rem; // Diameter
+ font-size: 0px;
+ padding: 0rem;
+ background-color: $surfaceVariant;
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_dock.scss b/modules/styling/config/scss/_dock.scss
new file mode 100644
index 0000000..2965726
--- /dev/null
+++ b/modules/styling/config/scss/_dock.scss
@@ -0,0 +1,31 @@
+.dock-bg {
+ @include large-rounding;
+ @include elevation2;
+ background-color: $t_background;
+ padding: 0.682rem;
+}
+
+.dock-app-btn {
+ @include normal-rounding;
+ padding: 0.273rem;
+}
+
+.dock-app-btn:hover,
+.dock-app-btn:focus {
+ background-color: mix($t_surface, $t_onSurface, 95%);
+}
+
+.dock-app-btn:active {
+ background-color: mix($t_surface, $t_onSurface, 85%);
+}
+
+.dock-app-icon {
+ min-width: 3.409rem;
+ min-height: 3.409rem;
+ font-size: 3.409rem;
+}
+
+.dock-separator {
+ min-width: 0.068rem;
+ background-color: $surfaceVariant;
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_lib_classes.scss b/modules/styling/config/scss/_lib_classes.scss
new file mode 100644
index 0000000..6cd3f89
--- /dev/null
+++ b/modules/styling/config/scss/_lib_classes.scss
@@ -0,0 +1,524 @@
+.test {
+ background-image: linear-gradient(
+ 45deg, #f4d609 0%, #f4d609 10%, #212121 10%, #212121 20%, #f4d609 20%, #f4d609 30%, #212121 30%,
+ #212121 40%, #f4d609 40%, #f4d609 50%, #212121 50%, #212121 60%, #f4d609 60%,
+ #f4d609 70%, #212121 70%, #212121 80%, #f4d609 80%, #f4d609 90%, #212121 90%, #212121 100%
+ );
+ background-repeat: repeat;
+}
+
+.test-size {
+ min-height: 3rem;
+ min-width: 3rem;
+}
+
+.txt-title {
+ @include titlefont;
+ font-size: 2.045rem;
+}
+
+.txt-title-small {
+ @include titlefont;
+ font-size: 1.364rem;
+}
+
+.techfont {
+ @include techfont;
+}
+
+.txt-reading {
+ @include readingfont;
+}
+
+.no-anim {
+ @include noanim;
+}
+
+.txt {
+ color: $onBackground;
+}
+
+.txt-primary {
+ color: $primary;
+}
+
+.txt-onSecondaryContainer {
+ color: $onSecondaryContainer;
+}
+
+.txt-onSurfaceVariant {
+ color: $onSurfaceVariant;
+}
+
+.txt-shadow {
+ text-shadow: 1px 2px 8px rgba(0, 0, 0, 0.69);
+ margin: 10px;
+}
+
+.txt-gigantic {
+ @include mainfont;
+ font-size: 3rem;
+}
+
+.txt-massive {
+ @include mainfont;
+ font-size: 2.7273rem;
+}
+
+.txt-hugerass {
+ @include mainfont;
+ font-size: 2.045rem;
+}
+
+.txt-hugeass {
+ @include mainfont;
+ font-size: 1.8182rem;
+}
+
+.txt-larger {
+ @include mainfont;
+ font-size: 1.6363rem;
+}
+
+.txt-large {
+ //16pt
+ @include mainfont;
+ font-size: 1.4545rem;
+}
+
+.txt-norm {
+ //14pt
+ @include mainfont;
+ font-size: 1.2727rem;
+}
+
+.txt-small {
+ //12pt
+ @include mainfont;
+ font-size: 1.0909rem;
+}
+
+.txt-smallie {
+ //11pt
+ @include mainfont;
+ font-size: 1rem;
+}
+
+.txt-smaller {
+ //10pt
+ @include mainfont;
+ font-size: 0.9091rem;
+}
+
+.txt-tiny {
+ @include mainfont;
+ font-size: 0.7273rem;
+}
+
+.txt-poof {
+ font-size: 0px;
+}
+
+.txt-subtext {
+ @include subtext;
+}
+
+.txt-action {
+ @include actiontext;
+}
+
+.txt-thin {
+ font-weight: 300;
+}
+
+.txt-semibold {
+ font-weight: 500;
+}
+
+.txt-bold {
+ font-weight: bold;
+}
+
+.txt-italic {
+ font-style: italic;
+}
+
+.btn-primary {
+ @include full-rounding;
+ background-color: $primary;
+ color: $onPrimary;
+ padding: 0.682rem 1.023rem;
+}
+
+.titlefont {
+ @include titlefont;
+}
+
+.mainfont {
+ @include mainfont;
+}
+
+.icon-material {
+ @include icon-material;
+}
+
+.icon-nerd {
+ @include icon-nerd;
+}
+
+.separator-line {
+ background-color: $outline;
+ min-width: 0.068rem;
+ min-height: 0.068rem;
+}
+
+.separator-circle {
+ @include full-rounding;
+ background-color: $outline;
+ margin: 0rem 0.682rem;
+ min-width: 0.273rem;
+ min-height: 0.273rem;
+}
+
+.spacing-h-3 > * {
+ margin-right: 0.205rem;
+}
+
+.spacing-h-3 > *:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v-3 > * {
+ margin-bottom: 0.205rem;
+}
+
+.spacing-v-3 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-15 > * {
+ margin-bottom: 1.023rem;
+}
+
+.spacing-v-15 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-15 > * {
+ margin-right: 1.023rem;
+}
+
+.spacing-h-15 > *:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-15 > revealer > * {
+ margin-right: 1.023rem;
+}
+
+.spacing-h-15 > revealer:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-h-15 > scrolledwindow > * {
+ margin-right: 1.023rem;
+}
+
+.spacing-h-15 > scrolledwindow:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-v-5 > box {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5 > box:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-5 > * {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-5-revealer > revealer > * {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5-revealer > revealer:last-child > * {
+ margin-bottom: 0rem;
+}
+
+.spacing-v-5-revealer > scrolledwindow > * {
+ margin-bottom: 0.341rem;
+}
+
+.spacing-v-5-revealer > scrolledwindow:last-child > * {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-4 > * {
+ margin-right: 0.273rem;
+}
+
+.spacing-h-4 > *:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-4 > overlay > *:first-child {
+ margin-right: 0.273rem;
+}
+
+.spacing-h-4 > overlay:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-h-5 > * {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5 > *:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-5 > widget > * {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5 > widget:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-h-5 > revealer > * {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5 > revealer:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-h-5 > scrolledwindow > * {
+ margin-right: 0.341rem;
+}
+
+.spacing-h-5 > scrolledwindow:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-v-minus5 > * {
+ margin-bottom: -0.341rem;
+}
+
+.spacing-v-minus5 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-10 > * {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10 > *:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-h-10 > revealer > * {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10 > revealer:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-h-10 > scrolledwindow > * {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10 > scrolledwindow:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-h-10 > flowboxchild > * {
+ margin-right: 0.682rem;
+}
+
+.spacing-h-10 > flowboxchild:last-child > * {
+ margin-right: 0rem;
+}
+
+.spacing-v-10 > * {
+ margin-bottom: 0.682rem;
+}
+
+.spacing-v-10 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h-20 > * {
+ margin-right: 1.364rem;
+}
+
+.spacing-h-20 > *:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v-20 > * {
+ margin-bottom: 1.364rem;
+}
+
+.spacing-v-20 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.anim-enter {
+ @include anim-enter;
+}
+
+.anim-exit {
+ @include anim-exit;
+}
+
+.button-minsize {
+ @include button-minsize;
+}
+
+.margin-right-5 {
+ margin-right: 0.341rem;
+}
+
+.margin-left-5 {
+ margin-left: 0.341rem;
+}
+
+.margin-top-5 {
+ margin-top: 0.341rem;
+}
+
+.margin-bottom-5 {
+ margin-bottom: 0.341rem;
+}
+
+.margin-right-10 {
+ margin-right: 0.682rem;
+}
+
+.margin-left-10 {
+ margin-left: 0.682rem;
+}
+
+.margin-top-10 {
+ margin-top: 0.682rem;
+}
+
+.margin-bottom-10 {
+ margin-bottom: 0.682rem;
+}
+
+.margin-right-15 {
+ margin-right: 1.023rem;
+}
+
+.margin-left-15 {
+ margin-left: 1.023rem;
+}
+
+.margin-top-15 {
+ margin-top: 1.023rem;
+}
+
+.margin-bottom-15 {
+ margin-bottom: 1.023rem;
+}
+
+.width-10 {
+ min-width: 0.682rem;
+}
+
+.height-10 {
+ min-width: 0.682rem;
+}
+
+.invisible {
+ opacity: 0;
+ background-color: transparent;
+ color: transparent;
+}
+
+.spacing-h--5 > box {
+ margin-right: -0.341rem;
+}
+
+.spacing-h--5 > box:last-child {
+ margin-right: 0rem;
+}
+
+.spacing-v--5 > * {
+ margin-bottom: -0.341rem;
+}
+
+.spacing-v--5 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h--10 > * {
+ margin-left: -1.364rem;
+}
+
+.spacing-h--10 > *:first-child {
+ margin-left: 0rem;
+}
+
+.spacing-v--10 > * {
+ margin-bottom: -0.682rem;
+}
+
+.spacing-v--10 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-v--10 > * {
+ margin-bottom: -0.682rem;
+}
+
+.spacing-v--10 > *:last-child {
+ margin-bottom: 0rem;
+}
+
+.spacing-h--20 > * {
+ margin-left: -1.364rem;
+}
+
+.spacing-h--20 > *:first-child {
+ margin-left: 0rem;
+}
+
+.instant {
+ transition: 0ms;
+}
+
+.menu-decel {
+ @include menu_decel;
+}
+.element-show {
+ @include element_easeInOut;
+}
+.element-hide {
+ @include element_easeInOut;
+}
+.element-move {
+ @include element_easeInOut;
+}
+.element-decel {
+ @include element_decel;
+}
+.element-bounceout {
+ @include element_bounceOut;
+}
+.element-accel {
+ @include element_accel;
+}
+.page-move {
+ @include page_move;
+}
diff --git a/modules/styling/config/scss/_lib_mixins.scss b/modules/styling/config/scss/_lib_mixins.scss
new file mode 100644
index 0000000..ba5babf
--- /dev/null
+++ b/modules/styling/config/scss/_lib_mixins.scss
@@ -0,0 +1,223 @@
+// Common colors
+$hovercolor: rgba(128, 128, 128, 0.3);
+$activecolor: rgba(128, 128, 128, 0.7);
+$rounding_small: 0.818rem;
+$rounding_mediumsmall: 0.955rem;
+$rounding_medium: 1.159rem;
+$rounding_mediumlarge: 1.364rem;
+$rounding_large: 1.705rem;
+
+// Common rules
+@mixin small-rounding {
+ border-radius: $rounding_small;
+ -gtk-outline-radius: $rounding_small;
+}
+
+@mixin normal-rounding {
+ border-radius: $rounding_medium;
+ -gtk-outline-radius: $rounding_medium;
+}
+
+@mixin large-rounding {
+ border-radius: $rounding_large;
+ -gtk-outline-radius: $rounding_large;
+}
+
+@mixin full-rounding {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+}
+
+@mixin titlefont {
+ // Geometric sans-serif
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+}
+
+@mixin mainfont {
+ // Other clean sans-serif
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter",
+ "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ // font-weight: 500;
+}
+
+@mixin icon-material {
+ // Material Design Icons
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined",
+ "Material Symbols Sharp";
+}
+
+@mixin icon-nerd {
+ // Nerd Fonts
+ font-family: "SpaceMono NF", "SpaceMono Nerd Font", "JetBrains Mono NF",
+ "JetBrains Mono Nerd Font", monospace;
+}
+
+@mixin techfont {
+ // Monospace for sys info n stuff. Doesn't have to be a nerd font, but it's cool.
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font",
+ "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace;
+}
+
+@mixin readingfont {
+ // The most readable fonts, for a comfortable reading experience
+ // in stuff like ChatGPT widget
+ font-family: "Lexend", "Noto Sans", sans-serif;
+ // font-weight: 500;
+}
+
+@mixin subtext {
+ color: $subtext;
+}
+
+@mixin actiontext {
+ color: $actiontext;
+}
+
+$elevation_margin: 0.476rem;
+
+@mixin elevation-safe {
+ background: $surface;
+ color: $onSurface;
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.69);
+ margin: $elevation_margin;
+}
+
+@mixin elevation2 {
+ box-shadow: 0px 2px 3px transparentize($shadow, 0.55);
+ margin: $elevation_margin;
+}
+
+@mixin elevation2-margin {
+ margin: $elevation_margin;
+}
+
+@mixin elevation2-padding {
+ padding: $elevation_margin;
+}
+
+@mixin elevation3 {
+ box-shadow: 0px 2px 5px $shadow;
+ margin: $elevation_margin;
+}
+
+@keyframes flyin-top {
+ from {
+ margin-top: -2.795rem;
+ }
+
+ to {
+ margin-top: 0rem;
+ }
+}
+
+@keyframes flyin-bottom {
+ from {
+ margin-top: 4.841rem;
+ margin-bottom: -4.841rem;
+ }
+
+ to {
+ margin-bottom: 0rem;
+ margin-top: 0rem;
+ }
+}
+
+@mixin menu_decel {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin menu_decel_fast {
+ transition: 170ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin menu_accel {
+ transition: 160ms cubic-bezier(0.38, 0.04, 1, 0.07);
+}
+
+@mixin menu_accel_fast {
+ transition: 100ms cubic-bezier(0.38, 0.04, 1, 0.07);
+}
+
+@mixin fluent_decel {
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin fluent_decel_long {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+@mixin fluent_accel {
+ transition: 150ms cubic-bezier(0.42, 0, 1, 1);
+}
+
+@mixin noanim {
+ transition: 0ms;
+}
+
+@mixin anim-enter {
+ transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
+}
+
+@mixin anim-exit {
+ transition: 150ms cubic-bezier(0.3, 0, 0.8, 0.15);
+}
+
+@mixin element_decel {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+}
+@mixin element_bounceOut {
+ transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+@mixin element_accel {
+ transition: 300ms cubic-bezier(0.55, 0, 1, 0.45);
+}
+@mixin element_easeInOut {
+ transition: 300ms cubic-bezier(0.85, 0, 0.15, 1);
+}
+@mixin page_move {
+ transition: 500ms cubic-bezier(0.85, 0, 0.15, 1);
+}
+
+@function tint($color, $percentage) {
+ @return mix(rgb(245, 250, 255), $color, $percentage);
+}
+
+@function shade($color, $percentage) {
+ @return mix(rgb(0, 0, 0), $color, $percentage);
+}
+
+$overlay1: mix($onSurface, rgba(0, 0, 0, 0), 25%);
+$overlay2: mix($onSurface, rgba(0, 0, 0, 0), 40%);
+
+@mixin elevation-border-softer {
+ border-top: 1px solid mix($t_t_surface, $t_onSurface, 90%);
+ border-left: 1px solid mix($t_t_surface, $t_onSurface, 90%);
+ border-right: 1px solid mix($t_t_surface, $t_onSurface, 95%);
+ border-bottom: 1px solid mix($t_t_surface, $t_onSurface, 95%);
+}
+
+@mixin elevation-border {
+ border-top: 1px solid mix($t_t_surface, $onSurface, 90%);
+ border-left: 1px solid mix($t_t_surface, $onSurface, 90%);
+ border-right: 1px solid mix($t_t_surface, $onSurface, 95%);
+ border-bottom: 1px solid mix($t_t_surface, $onSurface, 95%);
+}
+
+@mixin elevation-border-heavier {
+ border-top: 1px solid mix($t_t_surface, $onSurface, 80%);
+ border-left: 1px solid mix($t_t_surface, $onSurface, 80%);
+ border-right: 1px solid mix($t_t_surface, $onSurface, 85%);
+ border-bottom: 1px solid mix($t_t_surface, $onSurface, 85%);
+}
+
+@mixin elevation-border-transparent {
+ border-top: 1px solid transparent;
+}
+
+@mixin button-minsize {
+ min-width: 2.727rem;
+ min-height: 2.727rem;
+}
+
+$white: white;
+$black: black;
diff --git a/modules/styling/config/scss/_material.scss b/modules/styling/config/scss/_material.scss
new file mode 100644
index 0000000..2bce3c5
--- /dev/null
+++ b/modules/styling/config/scss/_material.scss
@@ -0,0 +1,29 @@
+$darkmode: true;
+$primary: #ffb1c3;
+$onPrimary: #66002a;
+$primaryContainer: #861c40;
+$onPrimaryContainer: #ffd9e0;
+$secondary: #e4bcc3;
+$onSecondary: #42292e;
+$secondaryContainer: #5c3f45;
+$onSecondaryContainer: #ffd9df;
+$tertiary: #ecbe91;
+$onTertiary: #462a08;
+$tertiaryContainer: #60401d;
+$onTertiaryContainer: #ffdcb9;
+$error: #ffb4a9;
+$onError: #680003;
+$errorContainer: #930006;
+$onErrorContainer: #ffb4a9;
+$colorbarbg: #130F10;
+$background: #130F10;
+$onBackground: #ecdfe0;
+$surface: #201a1b;
+$onSurface: #ecdfe0;
+$surfaceVariant: #514345;
+$onSurfaceVariant: #d6c1c4;
+$outline: #9f8c8f;
+$shadow: #000000;
+$inverseSurface: #ecdfe0;
+$inverseOnSurface: #362f30;
+$inversePrimary: #a53557;
diff --git a/modules/styling/config/scss/_music.scss b/modules/styling/config/scss/_music.scss
new file mode 100644
index 0000000..8fcbdfe
--- /dev/null
+++ b/modules/styling/config/scss/_music.scss
@@ -0,0 +1,155 @@
+@import './material';
+@import './musicmaterial';
+@import './wal';
+@import './musicwal';
+@import './colors';
+@import './lib_mixins';
+$music_gradient1: mix($color1, $background, 50%);
+
+// @if $darkmode ==true {
+// $music_gradient1: mix($color1, $background, 30%);
+// }
+
+$music_gradient2: $color3;
+$music_gradient3: $color5;
+$music_colorstart_transparentize: 0.3;
+$music_extra_transparentize: 0.15;
+
+$secondaryContainer: transparentize(mix(mix($background, $color2, 50%), $color6, 80%), 0.5);
+$onSecondaryContainer: mix($color7, $color2, 90%);
+@if $darkmode ==false {
+ $onSecondaryContainer: mix($onSecondaryContainer, black, 50%);
+}
+
+.osd-music {
+ @include menu_decel;
+ @include elevation2;
+ margin-top: 0.313rem;
+ @include normal-rounding;
+ // min-height: 7.159rem;
+ min-width: 29.659rem;
+ background-color: $t_background;
+ padding: 0rem 1.023rem;
+ background: // Inspired by Amberol
+ linear-gradient(127deg, transparentize($music_gradient1, $music_colorstart_transparentize), transparentize($music_gradient1, $music_colorstart_transparentize - $transparentize_amount + $music_extra_transparentize) 70.71%),
+ linear-gradient(217deg, transparentize($music_gradient2, $music_colorstart_transparentize), transparentize($music_gradient2, $music_colorstart_transparentize - $transparentize_amount + $music_extra_transparentize) 70.71%),
+ radial-gradient(circle at 0% 100%, $color4 13%, rgba(0, 0, 0, 0) 100%),
+ linear-gradient(336deg, transparentize($music_gradient3, $music_colorstart_transparentize), transparentize($music_gradient3, $music_colorstart_transparentize - $transparentize_amount + $music_extra_transparentize) 70.71%),
+ linear-gradient($background, $background), // We don't want wallpaper tint, so here's a fully opaque surface
+ ;
+}
+
+
+.osd-music-cover-fallback {
+ @include element_decel;
+ @include small-rounding;
+ // margin: 1.023rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+ background-color: $t_surface;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-cover {
+ @include small-rounding;
+ @include menu_decel;
+ // Must be -top and -bottom or it'll mess up horizontal spacing
+ margin-top: 1.023rem;
+ margin-bottom: 1.023rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+}
+
+.osd-music-cover-art {
+ @include small-rounding;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+ background-size: cover;
+ background-position: center;
+}
+
+.osd-music-info {
+ margin: 1.023rem 0rem;
+}
+
+.osd-music-title {
+ @include element_decel;
+ @include titlefont;
+ font-size: 1.364rem;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-artists {
+ @include element_decel;
+ @include mainfont;
+ font-size: 0.955rem;
+ color: mix($onSecondaryContainer, $secondaryContainer, 80%);
+}
+
+.osd-music-pill {
+ @include element_decel;
+ @include full-rounding;
+ @include titlefont;
+ min-width: 1.833rem;
+ padding: 0.273rem 0.682rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-controls {
+ @include element_decel;
+ @include full-rounding;
+ @include titlefont;
+ min-width: 1.833rem;
+ padding: 0.205rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-controlbtn {
+ @include menu_decel;
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ @include full-rounding;
+}
+
+.osd-music-controlbtn:hover,
+.osd-music-controlbtn:focus {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 90%);
+}
+
+.osd-music-controlbtn:active {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 85%);
+}
+
+.osd-music-controlbtn-txt {
+ @include element_decel;
+ transition: 100ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ @include icon-material;
+ font-size: 1.364rem;
+ margin: -0.1rem 0rem;
+}
+
+.osd-music-circprog {
+ @include fluent_decel_long;
+ min-width: 0.409rem; // width of progress
+ min-height: 3.068rem;
+ padding: 0.273rem;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-playstate {
+ @include menu_decel;
+ min-height: 3.068rem;
+ min-width: 3.068rem;
+ border-radius: 10rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.osd-music-playstate-btn>label {
+ transition: 50ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ @include icon-material;
+ font-size: 1.364rem;
+ margin: -0.1rem 0rem;
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_musicmaterial.scss b/modules/styling/config/scss/_musicmaterial.scss
new file mode 100644
index 0000000..e69de29
diff --git a/modules/styling/config/scss/_musicwal.scss b/modules/styling/config/scss/_musicwal.scss
new file mode 100644
index 0000000..7dcea73
--- /dev/null
+++ b/modules/styling/config/scss/_musicwal.scss
@@ -0,0 +1,26 @@
+// SCSS Variables
+// Generated by 'wal'
+$wallpaper: "/home/end/.cache/ags/media/8100fe8cfe618b3be1ae9c6c5379e90af63bc3fc";
+
+// Special
+$background: #121520;
+$foreground: #e8e1e5;
+$cursor: #e8e1e5;
+
+// Colors
+$color0: #121520;
+$color1: #8F7A9E;
+$color2: #688BB7;
+$color3: #9B93B0;
+$color4: #D0AAB0;
+$color5: #A9A2CB;
+$color6: #C9BAD3;
+$color7: #e8e1e5;
+$color8: #a29da0;
+$color9: #8F7A9E;
+$color10: #688BB7;
+$color11: #9B93B0;
+$color12: #D0AAB0;
+$color13: #A9A2CB;
+$color14: #C9BAD3;
+$color15: #e8e1e5;
diff --git a/modules/styling/config/scss/_notifications.scss b/modules/styling/config/scss/_notifications.scss
new file mode 100644
index 0000000..2eab850
--- /dev/null
+++ b/modules/styling/config/scss/_notifications.scss
@@ -0,0 +1,243 @@
+$notif_surface: $t_background;
+
+@mixin notif-rounding {
+ @include normal-rounding;
+}
+
+.notif-low {
+ @include notif-rounding;
+ background-color: $l_l_t_surfaceVariant;
+ color: $onSurfaceVariant;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.notif-normal {
+ @include notif-rounding;
+ background-color: $l_l_t_surfaceVariant;
+ color: $onSurfaceVariant;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.notif-critical {
+ @include notif-rounding;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.notif-clicked-low {
+ background-color: mix($l_l_t_surfaceVariant, $t_onSurfaceVariant, 85%);
+}
+
+.notif-clicked-normal {
+ background-color: mix($l_l_t_surfaceVariant, $t_onSurfaceVariant, 85%);
+}
+
+.notif-clicked-critical {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 95%);
+}
+
+.popup-notif-low {
+ @include notif-rounding;
+ min-width: 30.682rem;
+ background-color: $notif_surface;
+ color: $onSurfaceVariant;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.popup-notif-normal {
+ @include notif-rounding;
+ min-width: 30.682rem;
+ background-color: $notif_surface;
+ color: $onSurfaceVariant;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.popup-notif-critical {
+ @include notif-rounding;
+ min-width: 30.682rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ padding: $rounding_small;
+ padding-right: $rounding_small + 0.545rem;
+}
+
+.popup-notif-clicked-low {
+ background-color: mix($notif_surface, $onBackground, 94%);
+}
+
+.popup-notif-clicked-normal {
+ background-color: mix($notif_surface, $onBackground, 94%);
+}
+
+.popup-notif-clicked-critical {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 96%);
+}
+
+.notif-body-low {
+ color: mix($onSurfaceVariant, $surfaceVariant, 67%);
+}
+
+.notif-body-normal {
+ color: mix($onSurfaceVariant, $surfaceVariant, 67%);
+}
+
+.notif-body-critical {
+ color: mix($onSecondaryContainer, $secondaryContainer, 67%);
+}
+
+.notif-icon {
+ @include full-rounding;
+ min-width: 3.409rem;
+ min-height: 3.409rem;
+ font-size: 3.409rem;
+}
+
+.notif-icon-material {
+ background-color: $t_secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-icon-material-low {
+ background-color: $t_secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-icon-material-normal {
+ background-color: $t_secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-icon-material-critical {
+ background-color: $t_onSecondaryContainer;
+ color: $secondaryContainer;
+}
+
+.notif-expand-btn {
+ @include notif-rounding;
+ min-width: 1.841rem;
+ min-height: 1.841rem;
+}
+
+.notif-expand-btn:hover,
+.notif-expand-btn:focus {
+ background: $hovercolor;
+}
+
+.notif-expand-btn:active {
+ background: $activecolor;
+}
+
+.notif-listaction-btn {
+ @include notif-rounding;
+ padding: 0.341rem 0.682rem;
+}
+
+.notif-listaction-btn:hover,
+.notif-listaction-btn:focus {
+ background-color: $hovercolor;
+}
+
+.notif-listaction-btn:active {
+ background-color: $activecolor;
+}
+
+.notif-listaction-btn-enabled {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.notif-listaction-btn-enabled:hover,
+.notif-listaction-btn-enabled:focus {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 90%);
+}
+
+.notif-listaction-btn-enabled:active {
+ background-color: mix($secondaryContainer, $onSecondaryContainer, 75%);
+}
+
+.osd-notif {
+ @include notif-rounding;
+ background-color: transparentize($background,
+ $transparentize_surface_amount_subtract_surface );
+ min-width: 30.682rem;
+}
+
+.notif-circprog-low {
+ transition: 0ms linear;
+ min-width: 0.136rem; // line width
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: $onSecondaryContainer;
+}
+
+.notif-circprog-normal {
+ transition: 0ms linear;
+ min-width: 0.136rem; // line width
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: $onSecondaryContainer;
+}
+
+.notif-circprog-critical {
+ transition: 0ms linear;
+ min-width: 0.136rem; // line width
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: $secondaryContainer;
+}
+
+.notif-actions {
+ min-height: 2.045rem;
+}
+
+.notif-action {
+ @include small-rounding;
+}
+
+.notif-action-low {
+ background-color: mix($t_onSurfaceVariant, $t_surface, 10%);
+ color: $onSurfaceVariant;
+}
+
+.notif-action-low:focus,
+.notif-action-low:hover {
+ background-color: $hovercolor;
+}
+
+.notif-action-low:active {
+ background-color: $activecolor;
+}
+
+.notif-action-normal {
+ background-color: mix($t_onSurfaceVariant, $t_surface, 10%);
+ color: $onSurfaceVariant;
+}
+
+.notif-action-normal:focus,
+.notif-action-normal:hover {
+ background-color: $hovercolor;
+}
+
+.notif-action-normal:active {
+ background-color: $activecolor;
+}
+
+.notif-action-critical {
+ background-color: mix($t_onSecondaryContainer, $t_secondaryContainer, 10%);
+ color: $onSurfaceVariant;
+}
+
+.notif-action-critical:focus,
+.notif-action-critical:hover {
+ background-color: mix($t_onSecondaryContainer, $t_secondaryContainer, 18%);
+}
+
+.notif-action-critical:active {
+ background-color: mix($t_onSecondaryContainer, $t_secondaryContainer, 23%);
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_osd.scss b/modules/styling/config/scss/_osd.scss
new file mode 100644
index 0000000..15119a7
--- /dev/null
+++ b/modules/styling/config/scss/_osd.scss
@@ -0,0 +1,128 @@
+// .osd-window {
+ // margin-top: 2.727rem;
+// }
+
+.osd-bg {
+ min-width: 8.864rem;
+ min-height: 3.409rem;
+}
+
+.osd-value {
+ @include elevation-border;
+ @include elevation2;
+ background-color: $t_background;
+ border-radius: 1.023rem;
+ padding: 0.625rem 1.023rem;
+ padding-top: 0.313rem;
+}
+
+.osd-progress {
+ min-height: 0.955rem;
+ min-width: 0.068rem;
+ padding: 0rem;
+ border-radius: 10rem;
+ @include fluent_decel;
+
+ trough {
+ min-height: 0.954rem;
+ min-width: 0.068rem;
+ border-radius: 10rem;
+ background-color: $secondaryContainer;
+ // border: 0.068rem solid $onSecondaryContainer;
+ }
+
+ progress {
+ @include fluent_decel;
+ min-height: 0.680rem;
+ min-width: 0.680rem;
+ margin: 0rem 0.137rem;
+ border-radius: 10rem;
+ background-color: $onSecondaryContainer;
+ }
+}
+
+.osd-icon {
+ color: $onPrimaryContainer;
+}
+
+.osd-label {
+ font-size: 1.023rem;
+ font-weight: 500;
+ color: $onBackground;
+ margin-top: 0.341rem;
+}
+
+.osd-value-txt {
+ @include titlefont;
+ font-size: 1.688rem;
+ font-weight: 500;
+ color: $onBackground;
+}
+
+.osd-notifs {
+ padding-top: 0.313rem;
+}
+
+.osd-colorscheme {
+ border-radius: 1.023rem;
+ background-color: $t_background;
+ padding: 1.023rem;
+ @include elevation2;
+}
+
+.osd-color {
+ @include full-rounding;
+ min-width: 2.727rem;
+ min-height: 1.705rem;
+ padding: 0rem 0.341rem;
+ font-weight: bold;
+
+ box {
+ @include full-rounding;
+ margin: 0.409rem;
+ }
+}
+
+.osd-color-primary {
+ background-color: $primary;
+ color: $onPrimary;
+ box { background-color: $onPrimary; }
+}
+.osd-color-primaryContainer {
+ background-color: $primaryContainer;
+ color: $onPrimaryContainer;
+ box { background-color: $onPrimaryContainer; }
+}
+.osd-color-secondary {
+ background-color: $secondary;
+ color: $onSecondary;
+ box { background-color: $onSecondary; }
+}
+.osd-color-secondaryContainer {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ box { background-color: $onSecondaryContainer; }
+}
+.osd-color-surfaceVariant {
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ box { background-color: $onSurfaceVariant; }
+}
+.osd-color-surface {
+ background-color: $surface;
+ color: $onSurface;
+ box { background-color: $onSurface; }
+}
+.osd-color-background {
+ background-color: $background;
+ color: $onBackground;
+ box { background-color: $onBackground; }
+}
+
+.osd-show {
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+.osd-hide {
+ transition: 190ms cubic-bezier(0.85, 0, 0.15, 1);
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_osk.scss b/modules/styling/config/scss/_osk.scss
new file mode 100644
index 0000000..eca128c
--- /dev/null
+++ b/modules/styling/config/scss/_osk.scss
@@ -0,0 +1,118 @@
+$osk_key_height: 2.5rem;
+$osk_key_width: 2.5rem;
+$osk_key_padding: 0.188rem;
+$osk_key_rounding: 0.682rem;
+$osk_key_fontsize: 1.091rem;
+
+.osk-window {
+ // @include menu_decel_fast;
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ // min-height: 29.591rem;
+ // min-width: 50rem;
+ background-color: $t_background;
+}
+
+.osk-body {
+ padding: 1.023rem;
+ padding-top: 0px;
+}
+
+.osk-show {
+ @include menu_decel_fast;
+}
+
+.osk-hide {
+ margin-top: 30.682rem;
+ margin-bottom: -30.682rem;
+ // opacity: 0;
+ @include menu_accel_fast;
+}
+
+.osk-dragline {
+ @include full-rounding;
+ background-color: $surfaceVariant;
+ min-height: 0.273rem;
+ min-width: 10.227rem;
+ margin-top: 0.545rem;
+ margin-bottom: 0.205rem;
+}
+
+.osk-key {
+ border-radius: $osk_key_rounding;
+ background-color: $t_surfaceVariant;
+ color: $onSurfaceVariant;
+ padding: $osk_key_padding;
+ font-weight: 500;
+ font-size: $osk_key_fontsize;
+}
+
+.osk-key:hover,
+.osk-key:focus {
+ background-color: $hovercolor;
+}
+
+.osk-key:active {
+ background-color: $activecolor;
+ font-size: $osk_key_fontsize;
+}
+
+.osk-key-active {
+ background-color: $activecolor;
+}
+
+.osk-key-normal {
+ min-width: $osk_key_width;
+ min-height: $osk_key_height;
+}
+
+.osk-key-fn {
+ min-width: $osk_key_width * 1.005;
+ min-height: $osk_key_height / 2;
+}
+
+.osk-key-tab {
+ min-width: $osk_key_width * 1.6;
+ min-height: $osk_key_height;
+}
+
+.osk-key-caps {
+ min-width: $osk_key_width * 1.9;
+ min-height: $osk_key_height;
+}
+
+.osk-key-shift {
+ min-width: $osk_key_width * 2.5;
+ min-height: $osk_key_height;
+}
+
+.osk-key-control {
+ min-width: $osk_key_width * 1.3;
+ min-height: $osk_key_height;
+}
+
+.osk-control-button {
+ border-radius: $osk_key_rounding;
+ background-color: $t_surfaceVariant;
+ color: $onSurfaceVariant;
+ font-weight: 500;
+ font-size: $osk_key_fontsize;
+ padding: 0.682rem;
+}
+
+.osk-control-button:hover,
+.osk-control-button:focus {
+ background-color: mix($t_surfaceVariant, $t_onSurfaceVariant, 90%);
+}
+
+.osk-control-button:active {
+ background-color: mix($t_surfaceVariant, $t_onSurfaceVariant, 70%);
+ font-size: $osk_key_fontsize;
+}
+
+.osk-key-empty, .osk-key-empty:hover, .osk-key-empty:focus {
+ min-width: $osk_key_width;
+ min-height: $osk_key_height;
+ background-color: transparent;
+}
diff --git a/modules/styling/config/scss/_overview.scss b/modules/styling/config/scss/_overview.scss
new file mode 100644
index 0000000..8afa43b
--- /dev/null
+++ b/modules/styling/config/scss/_overview.scss
@@ -0,0 +1,139 @@
+.overview-window {
+ margin-top: 2.727rem;
+}
+
+.overview-search-box {
+ @include element_decel;
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ min-width: 13.636rem;
+ min-height: 3.409rem;
+ padding: 0rem 1.364rem;
+ padding-right: 2.864rem;
+ background-color: $t_background;
+ color: $onBackground;
+
+ selection {
+ background-color: $secondary;
+ color: $onSecondary;
+ }
+
+ caret-color: transparent;
+}
+
+.overview-search-box-extended {
+ min-width: 25.909rem;
+ caret-color: $onSecondaryContainer;
+}
+
+.overview-search-prompt {
+ color: $subtext;
+}
+
+.overview-search-icon {
+ margin: 0rem 1.023rem;
+}
+
+.overview-search-prompt-box {
+ margin-left: -18.545rem;
+ margin-right: $elevation_margin + 0.068rem;
+}
+
+.overview-search-icon-box {
+ margin-left: -18.545rem;
+ margin-right: $elevation_margin + 0.068rem;
+}
+
+.overview-search-results {
+ // min-height: 2.813rem;
+ // min-height: 37.5rem;
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ min-width: 28.773rem;
+ padding: 0.682rem;
+ background-color: $t_background;
+ color: $onBackground;
+}
+
+.overview-search-results-icon {
+ margin: 0rem 0.682rem;
+ font-size: 2.386rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+}
+
+.overview-search-results-txt {
+ margin-right: 0.682rem;
+}
+
+.overview-search-results-txt-cmd {
+ margin-right: 0.682rem;
+ @include techfont;
+ font-size: 1.227rem;
+}
+
+.overview-search-result-btn {
+ @include normal-rounding;
+ padding: 0.341rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+
+ caret-color: transparent;
+}
+
+.overview-search-result-btn:hover,
+.overview-search-result-btn:focus {
+ background-color: $hovercolor;
+}
+
+.overview-search-result-btn:active {
+ background-color: $activecolor;
+}
+
+.overview-tasks {
+ @include large-rounding;
+ @include elevation-border;
+ @include elevation2;
+ padding: 0.341rem;
+ background-color: $t_background;
+ color: $onBackground;
+}
+
+.overview-tasks-workspace {
+ @include normal-rounding;
+ // @include elevation-border;
+ margin: 0.341rem;
+ background-color: mix($t_t_surface, $t_onSurface, 93%);
+}
+
+.overview-tasks-workspace-number {
+ @include mainfont;
+ color: mix($t_onSurface, $t_surface, 93%);
+}
+
+.overview-tasks-window {
+ @include normal-rounding;
+ @include menu_decel;
+ background-color: $t_surfaceVariant;
+ color: $onSecondaryContainer;
+ border: 0.068rem solid $t_t_t_onSecondaryContainer;
+}
+
+.overview-tasks-window:hover,
+.overview-tasks-window:focus {
+ background-color: mix($l_t_secondaryContainer, $primary, 95%);
+}
+
+.overview-tasks-window:active {
+ background-color: mix($l_t_secondaryContainer, $primary, 90%);
+}
+
+.overview-tasks-window-selected {
+ background-color: mix($l_t_secondaryContainer, $primary, 90%);
+}
+
+.overview-tasks-window-dragging {
+ opacity: 0.2;
+}
\ No newline at end of file
diff --git a/modules/styling/config/scss/_session.scss b/modules/styling/config/scss/_session.scss
new file mode 100644
index 0000000..35c7b3f
--- /dev/null
+++ b/modules/styling/config/scss/_session.scss
@@ -0,0 +1,36 @@
+.session-bg {
+ margin-top: -2.727rem;
+ background-color: mix($t_t_background, $background, 40%);
+}
+
+.session-button {
+ @include large-rounding;
+ min-width: 8.182rem;
+ min-height: 8.182rem;
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ font-size: 3rem;
+}
+
+.session-button-focused {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.session-button-desc {
+ background-color: mix($surface, $surfaceVariant, 50%);
+ color: mix($onSurface, $onSurfaceVariant, 50%);
+ border-bottom-left-radius: $rounding_large;
+ border-bottom-right-radius: $rounding_large;
+ padding: 0.205rem 0.341rem;
+ font-weight: 700;
+}
+
+.session-button-cancel {
+ @include large-rounding;
+ min-width: 8.182rem;
+ min-height: 5.455rem;
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ font-size: 3rem;
+}
diff --git a/modules/styling/config/scss/_sidebars.scss b/modules/styling/config/scss/_sidebars.scss
new file mode 100644
index 0000000..51a3b5d
--- /dev/null
+++ b/modules/styling/config/scss/_sidebars.scss
@@ -0,0 +1,859 @@
+$sidebar_chat_textboxareaColor: mix($onSurfaceVariant, $surfaceVariant, 40%);
+$textboxColor: mix($surface, $surfaceVariant, 80%);
+$system: $secondary;
+$onSystem: $onSecondary;
+$chatgpt: $primary;
+$onChatgpt: $onPrimary;
+
+@mixin group-padding {
+ padding: 0.341rem;
+}
+
+.sidebar-right {
+ @include menu_decel;
+ @include elevation-border;
+ @include elevation2;
+ border-radius: $rounding_large - $elevation_margin + 0.068rem;
+ min-width: 27.818rem; // COMMENT THIS LATER IF TEXT WRAP IS USED
+ // min-height: 29.591rem;
+ background-color: $t_background;
+ padding: 1.023rem;
+}
+
+.sideright-show {
+ @include menu_decel;
+}
+
+.sideright-hide {
+ @include menu_accel;
+ margin-right: -30.682rem;
+ // opacity: 0;
+}
+
+.sidebar-left {
+ @include menu_decel;
+ @include elevation-border;
+ @include elevation2;
+ border-radius: $rounding_large - $elevation_margin + 0.068rem;
+ min-width: 27.818rem; // COMMENT THIS LATER IF TEXT WRAP IS USED
+ // min-height: 29.591rem;
+ background-color: $t_background;
+ padding: 1.023rem;
+}
+
+.sideleft-show {
+ @include menu_decel;
+}
+
+.sideleft-hide {
+ margin-left: -30.682rem;
+ // opacity: 0;
+ @include menu_accel;
+}
+
+.sidebar-group {
+ @include normal-rounding;
+ @include group-padding;
+ background-color: $t_surface;
+}
+
+.sidebar-group-nopad {
+ @include normal-rounding;
+ background-color: $t_surface;
+}
+
+.sidebar-group-invisible {
+ @include group-padding;
+}
+
+.sidebar-group-invisible-morehorizpad {
+ padding: 0.341rem 0.682rem;
+}
+
+.sidebar-togglesbox {
+ @include full-rounding;
+ @include group-padding;
+ background-color: $t_surface;
+}
+
+.sidebar-iconbutton {
+ @include full-rounding;
+ @include element_decel;
+ color: $onSurface;
+ min-width: 2.727rem;
+ min-height: 2.727rem;
+}
+
+.sidebar-iconbutton:hover,
+.sidebar-iconbutton:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-iconbutton:active {
+ background-color: $activecolor;
+}
+
+.sidebar-button {
+ @include element_decel;
+ padding: 0rem $rounding_small;
+ background-color: $t_secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.sidebar-button:hover,
+.sidebar-button:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-button:active {
+ background-color: $activecolor;
+}
+
+.sidebar-button-active {
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-button-active:hover,
+.sidebar-button-active:focus {
+ background-color: mix($primary, $hovercolor, 70%);
+}
+
+.sidebar-button-active:active {
+ background-color: mix($primary, $hovercolor, 40%);
+}
+
+.sidebar-buttons-separator {
+ min-width: 0.068rem;
+ min-height: 0.068rem;
+ background-color: $onSurfaceVariant;
+}
+
+.sidebar-navrail {
+ // background-color: $t_surface;
+ padding: 0rem $rounding_medium;
+}
+
+.sidebar-navrail-btn>box>label {
+ @include full-rounding;
+ @include menu_decel;
+}
+
+.sidebar-navrail-btn:hover>box>label:first-child,
+.sidebar-navrail-btn:focus>box>label:first-child {
+ background-color: $hovercolor;
+}
+
+.sidebar-navrail-btn:active>box>label:first-child {
+ background-color: $activecolor;
+}
+
+.sidebar-navrail-btn-active>box>label:first-child {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.sidebar-navrail-btn-active:hover>box>label:first-child,
+.sidebar-navrail-btn-active:focus>box>label:first-child {
+ background-color: mix($secondaryContainer, $hovercolor, 90%);
+ color: mix($onSecondaryContainer, $hovercolor, 90%);
+}
+
+.sidebar-sysinfo-grouppad {
+ padding: 1.159rem;
+}
+
+.sidebar-memory-ram-circprog {
+ @include fluent_decel_long;
+ min-width: $rounding_small;
+ min-height: 4.091rem;
+ padding: 0.409rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ font-size: 0px;
+}
+
+.sidebar-memory-swap-circprog {
+ @include fluent_decel_long;
+ min-width: $rounding_small;
+ min-height: 2.255rem;
+ padding: 0.409rem;
+ margin: 0.918rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ font-size: 0px;
+}
+
+.sidebar-cpu-circprog {
+ min-width: $rounding_small;
+ min-height: 3.409rem;
+ padding: 0.409rem;
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+ @include fluent_decel_long;
+ font-size: 0px;
+}
+
+.sidebar-scrollbar {
+ trough {
+ @include full-rounding;
+ min-width: 0.545rem;
+ background-color: transparent;
+ }
+
+ slider {
+ @include full-rounding;
+ @include element_decel;
+ min-width: 0.273rem;
+ min-height: 2.045rem;
+ background-color: $t_onSurfaceVariant;
+ }
+
+ slider:hover,
+ slider:focus {
+ background-color: mix($t_onSurfaceVariant, $onSurfaceVariant, 80%);
+ }
+
+ slider:active {
+ background-color: mix($onSurfaceVariant, $surfaceVariant, 50%);
+ }
+}
+
+.sidebar-calendar-btn {
+ @include full-rounding;
+ @include element_decel;
+ min-height: 2.523rem;
+ min-width: 2.523rem;
+ color: $onSurface;
+}
+
+.sidebar-calendar-btn:hover,
+.sidebar-calendar-btn:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-calendar-btn:active {
+ background-color: $activecolor;
+}
+
+.sidebar-calendar-btn-txt {
+ margin-left: -10.341rem;
+ margin-right: -10.341rem;
+}
+
+.sidebar-calendar-btn-today {
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-calendar-btn-today:hover,
+.sidebar-calendar-btn-today:focus {
+ background-color: mix($primary, $hovercolor, 70%);
+}
+
+.sidebar-calendar-btn-today:active {
+ background-color: mix($primary, $hovercolor, 40%);
+}
+
+.sidebar-calendar-btn-othermonth {
+ color: mix($onSurface, $surface, 50%);
+}
+
+.sidebar-calendar-header {
+ margin: 0.341rem;
+}
+
+.sidebar-calendar-monthyear-btn {
+ @include full-rounding;
+ @include element_decel;
+ padding: 0rem 0.682rem;
+ background-color: $t_surfaceVariant;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-calendar-monthyear-btn:hover,
+.sidebar-calendar-monthyear-btn:focus {
+ background-color: $hovercolor;
+ color: mix($onSurfaceVariant, $surfaceVariant, 95%);
+}
+
+.sidebar-calendar-monthyear-btn:active {
+ background-color: $activecolor;
+ color: mix($onSurfaceVariant, $surfaceVariant, 85%);
+}
+
+.sidebar-calendar-monthshift-btn {
+ @include full-rounding;
+ @include element_decel;
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ background-color: $t_surfaceVariant;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-calendar-monthshift-btn:hover {
+ background-color: $hovercolor;
+ color: mix($onSurfaceVariant, $surfaceVariant, 95%);
+}
+
+.sidebar-calendar-monthshift-btn:active {
+ background-color: $activecolor;
+ color: mix($onSurfaceVariant, $surfaceVariant, 85%);
+}
+
+.sidebar-selector-tab {
+ @include small-rounding;
+ @include element_decel;
+ min-height: 2.5rem;
+ color: $onSurface;
+}
+
+.sidebar-selector-tab:hover,
+.sidebar-selector-tab:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-selector-tab:active {
+ background-color: $activecolor;
+}
+
+.sidebar-selector-tab-active>box>label {
+ color: $primary;
+}
+
+.sidebar-selector-highlight-offset {
+ margin-top: -0.205rem;
+ margin-bottom: 0.205rem;
+}
+
+.sidebar-selector-highlight {
+ transition: 180ms ease-in-out; // Doesn't look that good, but it syncs with the GtkStack
+ color: $primary;
+ // padding: 0rem 2.045rem;
+ min-height: 0.205rem;
+}
+
+.sidebar-todo-item {
+ padding-right: 0.545rem;
+}
+
+.sidebar-todo-item-even {
+ background-color: $t_t_surfaceVariant;
+}
+
+.sidebar-todo-item-action {
+ @include element_decel;
+ border-radius: 9999px;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+}
+
+.sidebar-todo-item-action:hover,
+.sidebar-todo-item-action:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-todo-item-action:active {
+ background-color: $activecolor;
+}
+
+.sidebar-todo-crosser {
+ transition: margin 200ms cubic-bezier(0.1, 1, 0, 1), background-color 0ms;
+ min-width: 0rem;
+}
+
+.sidebar-todo-crosser-crossed {
+ background-color: $onBackground;
+}
+
+.sidebar-todo-crosser-removed {
+ background-color: $error;
+}
+
+.sidebar-todo-new {
+ @include full-rounding;
+ @include element_decel;
+ color: $onSecondaryContainer;
+ margin: 0.341rem;
+ padding: 0.205rem 0.545rem;
+ border: 0.068rem solid $onSurface;
+}
+
+.sidebar-todo-new,
+.sidebar-todo-new:focus {
+ background-color: mix($t_secondaryContainer, $t_onSecondaryContainer, 97%);
+}
+
+.sidebar-todo-new:active {
+ background-color: mix($t_secondaryContainer, $t_onSecondaryContainer, 80%);
+}
+
+.sidebar-todo-add {
+ @include element_decel;
+ @include small-rounding;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ color: $onSecondaryContainer;
+ border: 0.068rem solid $onSurface;
+}
+
+.sidebar-todo-add:hover,
+.sidebar-todo-add:focus {
+ background-color: mix($t_secondaryContainer, $t_onSecondaryContainer, 97%);
+}
+
+.sidebar-todo-add:active {
+ background-color: mix($t_secondaryContainer, $t_onSecondaryContainer, 80%);
+}
+
+.sidebar-todo-add-available {
+ @include element_decel;
+ @include small-rounding;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ background-color: $primary;
+ color: $onPrimary;
+ border: 0.068rem solid $primary;
+}
+
+.sidebar-todo-add-available:hover,
+.sidebar-todo-add-available:focus {
+ background-color: mix($primary, $onPrimary, 97%);
+}
+
+.sidebar-todo-add-available:active {
+ background-color: mix($primary, $onPrimary, 80%);
+}
+
+.sidebar-todo-entry {
+ @include element_decel;
+ @include small-rounding;
+ background-color: $surfaceVariant;
+ color: $onSurfaceVariant;
+ caret-color: $onSurfaceVariant;
+ margin: 0rem 0.341rem;
+ min-height: 1.773rem;
+ min-width: 0rem;
+ padding: 0.205rem 0.682rem;
+ border: 0.068rem solid mix($onSurfaceVariant, $surfaceVariant, 50%);
+}
+
+.sidebar-todo-entry:focus {
+ border: 0.068rem solid mix($onSurfaceVariant, $surfaceVariant, 90%);
+}
+
+.sidebar-module {
+ @include normal-rounding;
+ @include group-padding;
+ background-color: $l_l_t_surfaceVariant;
+ padding: 0.682rem;
+}
+
+.sidebar-module-btn-arrow {
+ @include full-rounding;
+ @include icon-material;
+ background-color: $l_l_t_surfaceVariant;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+
+ &:hover {
+ background-color: $hovercolor;
+ }
+}
+
+.sidebar-module-scripts-button {
+ @include full-rounding;
+ @include icon-material;
+ background-color: $l_l_t_surfaceVariant;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+
+ &:hover {
+ background-color: $hovercolor;
+ }
+
+ &:active {
+ background-color: $activecolor;
+ }
+}
+
+$colorpicker_rounding: 0.341rem;
+
+.sidebar-module-colorpicker-wrapper {
+ padding: 0.341rem;
+}
+
+.sidebar-module-colorpicker-cursorwrapper {
+ padding: 0.341rem 0.136rem;
+}
+
+.sidebar-module-colorpicker-hue {
+ min-height: 13.636rem;
+ min-width: 1.091rem;
+ border-radius: $colorpicker_rounding;
+}
+
+.sidebar-module-colorpicker-hue-cursor {
+ background-color: $onBackground;
+ border: 0.136rem solid $onBackground;
+ min-height: 0.136rem;
+ margin-top: -0.136rem;
+ border-radius: $colorpicker_rounding;
+}
+
+.sidebar-module-colorpicker-saturationandlightness-wrapper {
+ padding: 0.341rem;
+}
+
+.sidebar-module-colorpicker-saturationandlightness {
+ min-height: 13.636rem;
+ min-width: 13.636rem;
+ border-radius: $colorpicker_rounding;
+}
+
+.sidebar-module-colorpicker-saturationandlightness-cursorwrapper {
+ padding: 0.341rem;
+ margin-top: -0.409rem;
+ margin-left: -0.409rem;
+}
+
+.sidebar-module-colorpicker-saturationandlightness-cursor {
+ @include full-rounding;
+ border: 0.136rem solid white;
+ min-width: 0.682rem;
+ min-height: 0.682rem;
+ margin-top: -0.409rem;
+ margin-left: -0.409rem;
+}
+
+.sidebar-module-colorpicker-result-area {
+ padding: 0.341rem;
+}
+
+.sidebar-module-colorpicker-result-box {
+ border-radius: $colorpicker_rounding;
+ min-width: 2.045rem;
+ min-height: 0.682rem;
+ padding: 0.341rem;
+}
+
+.sidebar-chat-apiswitcher {
+ @include full-rounding;
+ @include group-padding;
+ background-color: $t_surface;
+}
+
+.sidebar-chat-apiswitcher-icon {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 2.182rem;
+ min-height: 2.182rem;
+ font-size: 1.406rem;
+ color: $onSurface;
+}
+
+.sidebar-chat-apiswitcher-icon-enabled {
+ background-color: $secondaryContainer;
+ color: $onSecondaryContainer;
+}
+
+.sidebar-chat-viewport {
+ @include element_decel;
+ // margin: 0.682rem 0rem;
+ padding: 0.682rem 0rem;
+}
+
+.sidebar-chat-textarea {
+ @include normal-rounding;
+ background-color: $textboxColor;
+ color: $onSurfaceVariant;
+ padding: 0.682rem;
+}
+
+.sidebar-chat-entry {
+ color: $onSurfaceVariant;
+ caret-color: $onSurfaceVariant;
+ min-height: 1.773rem;
+ min-width: 0rem;
+}
+
+.sidebar-chat-wrapper {
+ transition: 400ms cubic-bezier(0.1, 1, 0, 1);
+}
+
+.sidebar-chat-wrapper-extended {
+ min-height: 7.500rem;
+}
+
+.sidebar-chat-send {
+ @include element_decel;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ border-radius: $rounding_medium - 0.681rem;
+}
+
+.sidebar-chat-send:hover,
+.sidebar-chat-send:focus {
+ background-color: $surfaceVariant;
+}
+
+.sidebar-chat-send:active {
+ background-color: mix($surfaceVariant, $onBackground, 80%);
+}
+
+.sidebar-chat-send-available {
+ background-color: $primary;
+ color: $onPrimary;
+}
+
+.sidebar-chat-send-available:hover,
+.sidebar-chat-send-available:focus {
+ background-color: mix($primary, $onPrimary, 97%);
+}
+
+.sidebar-chat-send-available:active {
+ background-color: mix($primary, $onPrimary, 80%);
+}
+
+.sidebar-chat-message {
+ margin: 0.682rem;
+}
+
+.sidebar-chat-indicator {
+ @include element_decel;
+ @include full-rounding;
+ min-width: 0.136rem;
+ background-color: $onBackground;
+}
+
+.sidebar-chat-indicator-user {
+ background-color: $onBackground;
+}
+
+.sidebar-chat-indicator-bot {
+ background-color: $chatgpt;
+}
+
+.sidebar-chat-indicator-System {
+ background-color: $system;
+}
+
+.sidebar-chat-name {
+ @include titlefont;
+ padding: 0.341rem;
+ margin-left: -0.136rem;
+ padding-left: 0.818rem;
+}
+
+.sidebar-chat-txtblock {
+ margin-left: -0.136rem;
+ padding: 0.341rem;
+ padding-left: 0.818rem;
+}
+
+.sidebar-chat-txt {
+ @include readingfont;
+}
+
+.sidebar-chat-codeblock {
+ @include normal-rounding;
+ // @include elevation2;
+ background-color: $termbg;
+ color: $termfg;
+ margin: 0rem 0.682rem;
+ border: 0.068rem solid $t_t_t_onSecondaryContainer;
+}
+
+.sidebar-chat-codeblock-topbar {
+ @include mainfont;
+ margin: 0.273rem;
+ margin-bottom: 0rem;
+ background-color: mix($t_secondaryContainer, $t_onSurfaceVariant, 30%);
+ color: $onSecondaryContainer;
+ border-radius: $rounding_medium - 0.273rem;
+ border: 0.068rem solid mix($secondaryContainer, $onSecondaryContainer, 90%);
+ border-top-left-radius: $rounding_small + 0.068rem;
+ border-top-right-radius: $rounding_small + 0.068rem;
+ padding: 0.341rem 0.477rem;
+}
+
+.sidebar-chat-codeblock-topbar-txt {
+ @include full-rounding;
+ padding: 0.273rem;
+}
+
+.sidebar-chat-codeblock-topbar-btn {
+ @include full-rounding;
+ @include element_decel;
+ padding: 0.273rem 0.477rem;
+}
+
+.sidebar-chat-codeblock-topbar-btn:hover,
+.sidebar-chat-codeblock-topbar-btn:focus {
+ background-color: mix($t_secondaryContainer, $t_onSecondaryContainer, 80%);
+}
+
+.sidebar-chat-codeblock-topbar-btn:active {
+ background-color: mix($t_secondaryContainer, $t_onSecondaryContainer, 60%);
+}
+
+.sidebar-chat-codeblock-code {
+ @include techfont;
+ padding: 0.682rem;
+}
+
+.sidebar-chat-divider {
+ min-height: 1px;
+ background-color: $sidebar_chat_textboxareaColor;
+ margin: 0rem 0.545rem;
+}
+
+.sidebar-chat-welcome-txt {
+ margin: 0rem 3.409rem;
+}
+
+.sidebar-chat-settings-toggles {
+ margin: 0rem 5.455rem;
+}
+
+.sidebar-chat-welcome-icon {
+ @include full-rounding;
+ font-size: 4rem;
+}
+
+.sidebar-chat-welcome-logo {
+ @include full-rounding;
+ @include element_decel;
+ @include icon-material;
+ min-height: 4.773rem;
+ min-width: 4.773rem;
+ font-size: 3.076rem;
+ background-color: $onBackground;
+ color: $background;
+}
+
+.sidebar-chat-chip {
+ @include element_decel;
+ @include small-rounding;
+ padding: 0.341rem 0.477rem;
+}
+
+.sidebar-chat-chip-action {
+ @include element_decel;
+ background-color: $textboxColor;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-chat-chip-action:hover,
+.sidebar-chat-chip-action:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-chat-chip-action:active {
+ background-color: $activecolor;
+}
+
+.sidebar-chat-chip-action-active {
+ color: $sidebar_chat_textboxareaColor;
+ border: 0.068rem solid $sidebar_chat_textboxareaColor;
+}
+
+.sidebar-chat-chip-toggle {
+ @include element_decel;
+ @include small-rounding;
+ padding: 0.341rem 0.477rem;
+ background-color: $textboxColor;
+ color: $onSurfaceVariant;
+}
+
+.sidebar-chat-chip-toggle:focus,
+.sidebar-chat-chip-toggle:hover {
+ background-color: $hovercolor;
+}
+
+.sidebar-chat-chip-toggle:active {
+ background-color: $activecolor;
+}
+
+.sidebar-pin {
+ @include small-rounding;
+ @include element_decel;
+ min-height: 2.386rem;
+ min-width: 2.386rem;
+ color: $onSurface;
+}
+
+.sidebar-pin:hover,
+.sidebar-pin:focus {
+ background-color: $hovercolor;
+}
+
+.sidebar-pin:active {
+ background-color: $activecolor;
+}
+
+.sidebar-pin-enabled {
+ background-color: $primary;
+
+ label {
+ color: $onPrimary;
+ }
+}
+
+.sidebar-pin-enabled:hover,
+.sidebar-pin-enabled:focus {
+ background-color: mix($primary, $onPrimary, 90%);
+}
+
+.sidebar-pin-enabled:active {
+ background-color: mix($primary, $onPrimary, 80%);
+}
+
+.sidebar-waifu-heading {
+ @include titlefont;
+ padding: 0.341rem;
+ margin-left: -0.136rem;
+ padding-left: 0.818rem;
+}
+
+.sidebar-waifu-content {
+ margin-left: 0.682rem;
+}
+
+.sidebar-waifu-txt {
+ @include readingfont;
+ margin-left: 0.682rem;
+}
+
+.sidebar-waifu-image {
+ margin-left: 0.682rem;
+ @include normal-rounding;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.sidebar-waifu-image-actions {
+ @include element_decel;
+ padding: 0.313rem;
+}
+
+$waifu_image_overlay_transparency: 0.7;
+
+.sidebar-waifu-image-action {
+ @include full-rounding;
+ min-width: 1.875rem;
+ min-height: 1.875rem;
+ background-color: rgba(0, 0, 0,
+ $waifu_image_overlay_transparency ); // Fixed cuz on image
+ color: rgba(255, 255, 255, $waifu_image_overlay_transparency);
+}
+
+.sidebar-waifu-image-action:hover,
+.sidebar-waifu-image-action:focus {
+ background-color: rgba(30, 30, 30, $waifu_image_overlay_transparency);
+}
+
+.sidebar-waifu-image-action:active {
+ background-color: rgba(60, 60, 60, $waifu_image_overlay_transparency);
+}
diff --git a/modules/styling/config/scss/_wal.scss b/modules/styling/config/scss/_wal.scss
new file mode 100644
index 0000000..50ec8e5
--- /dev/null
+++ b/modules/styling/config/scss/_wal.scss
@@ -0,0 +1,8 @@
+// To prevent errors
+$color1: $surfaceVariant;
+$color2: $surfaceVariant;
+$color3: $surfaceVariant;
+$color4: $secondaryContainer;
+$color5: $secondaryContainer;
+$color6: $secondaryContainer;
+$color7: $onBackground;
\ No newline at end of file
diff --git a/modules/styling/config/scss/main.scss b/modules/styling/config/scss/main.scss
new file mode 100644
index 0000000..ea1f378
--- /dev/null
+++ b/modules/styling/config/scss/main.scss
@@ -0,0 +1,41 @@
+// Reset
+// * {
+// all: unset;
+// }
+*:not(popover) { all: unset; }
+
+// Colors
+@import './material'; // Material colors
+@import './colors'; // Global color definitions. Uses material colors as base.
+@import './lib_mixins';
+@import './lib_classes';
+@import './common'; // Context menu n stuff
+
+// Components
+@import './bar';
+@import './cheatsheet';
+@import './desktopbackground';
+@import './dock';
+@import './osd';
+@import './overview';
+@import './osk';
+@import './sidebars';
+@import './session';
+@import './notifications';
+
+// Music is put last as it might mess stuff up with pywal
+@import './music'; // Everything related to music is here
+
+// Classes for interaction
+.growingRadial {
+ transition: 300ms cubic-bezier(0.2, 0.0, 0, 1.0);
+}
+.fadingRadial {
+ transition: 50ms cubic-bezier(0.2, 0.0, 0, 1.0);
+}
+.sidebar-pinned {
+ margin: 0rem;
+ border-radius: 0rem;
+ border-bottom-right-radius: $rounding_large;
+ border: 0rem solid;
+}
\ No newline at end of file
diff --git a/modules/styling/config/services/brightness.js b/modules/styling/config/services/brightness.js
new file mode 100644
index 0000000..8032b14
--- /dev/null
+++ b/modules/styling/config/services/brightness.js
@@ -0,0 +1,59 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+class BrightnessService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'screen-changed': ['float'], },
+ { 'screen-value': ['float', 'rw'], },
+ );
+ }
+
+ _screenValue = 0;
+
+ // the getter has to be in snake_case
+ get screen_value() { return this._screenValue; }
+
+ // the setter has to be in snake_case too
+ set screen_value(percent) {
+ percent = clamp(percent, 0, 1);
+ this._screenValue = percent;
+
+ Utils.execAsync(`brightnessctl s ${percent * 100}% -q`)
+ .then(() => {
+ // signals has to be explicity emitted
+ this.emit('screen-changed', percent);
+ this.notify('screen-value');
+
+ // or use Service.changed(propName: string) which does the above two
+ // this.changed('screen');
+ })
+ .catch(print);
+ }
+
+ constructor() {
+ super();
+ const current = Number(exec('brightnessctl g'));
+ const max = Number(exec('brightnessctl m'));
+ this._screenValue = current / max;
+ }
+
+ // overwriting connectWidget method, lets you
+ // change the default event that widgets connect to
+ connectWidget(widget, callback, event = 'screen-changed') {
+ super.connectWidget(widget, callback, event);
+ }
+}
+
+// the singleton instance
+const service = new BrightnessService();
+
+// make it global for easy use with cli
+globalThis.brightness = service;
+
+// export to use in other modules
+export default service;
\ No newline at end of file
diff --git a/modules/styling/config/services/chatgpt.js b/modules/styling/config/services/chatgpt.js
new file mode 100644
index 0000000..b4264dd
--- /dev/null
+++ b/modules/styling/config/services/chatgpt.js
@@ -0,0 +1,266 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+import Soup from 'gi://Soup?version=3.0';
+import { fileExists } from './messages.js';
+
+// Custom prompt
+const initMessages =
+ [
+ { role: "user", content: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are encouraged to use bullet points and headings. Use casual language and be short and concise. \nThanks!", },
+ { role: "assistant", content: "- Got it!", },
+ { role: "user", content: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"", },
+ { role: "assistant", content: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"", },
+ { role: "user", content: "raise volume by 5%", },
+ { role: "assistant", content: "## Volume +5```bash\nwpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\n```\nThis command uses the `wpctl` utility to adjust the volume of the default sink.", },
+ { role: "user", content: "main advantages of the nixos operating system", },
+ { role: "assistant", content: "## NixOS advantages\n- **Reproducible**: A config working on one device will also work on another\n- **Declarative**: One config language to rule them all. Effortlessly share them with others.\n- **Reliable**: Per-program software versioning. Mitigates the impact of software breakage", },
+ { role: "user", content: "whats skeumorphism", },
+ { role: "assistant", content: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today.", },
+ ];
+
+function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+// We're using many models to not be restricted to 3 messages per minute.
+// The whole chat will be sent every request anyway.
+Utils.exec(`mkdir -p ${GLib.get_user_cache_dir()}/ags/user/ai`);
+const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/openai_key.txt`;
+const APIDOM_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/openai_api_dom.txt`;
+function replaceapidom(URL) {
+ //Utils.writeFile(URL, "/tmp/openai-url-old.log"); // For debugging
+ if (fileExists(expandTilde(APIDOM_FILE_LOCATION))) {
+ var contents = Utils.readFile(expandTilde(APIDOM_FILE_LOCATION)).trim();
+ var URL = URL.toString().replace("api.openai.com", contents);
+ }
+ //Utils.writeFile(URL, "/tmp/openai-url.log"); // For debugging
+ return URL;
+}
+const CHAT_MODELS = ["gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0613"]
+const ONE_CYCLE_COUNT = 3;
+
+class ChatGPTMessage extends Service {
+ static {
+ Service.register(this,
+ {
+ 'delta': ['string'],
+ },
+ {
+ 'content': ['string'],
+ 'thinking': ['boolean'],
+ 'done': ['boolean'],
+ });
+ }
+
+ _role = '';
+ _content = '';
+ _thinking = false;
+ _done = false;
+
+ constructor(role, content, thinking = false, done = false) {
+ super();
+ this._role = role;
+ this._content = content;
+ this._thinking = thinking;
+ this._done = done;
+ }
+
+ get done() { return this._done }
+ set done(isDone) { this._done = isDone; this.notify('done') }
+
+ get role() { return this._role }
+ set role(role) { this._role = role; this.emit('changed') }
+
+ get content() { return this._content }
+ set content(content) {
+ this._content = content;
+ this.notify('content')
+ this.emit('changed')
+ }
+
+ get label() { return this._parserState.parsed + this._parserState.stack.join('') }
+
+ get thinking() { return this._thinking }
+ set thinking(thinking) {
+ this._thinking = thinking;
+ this.notify('thinking')
+ this.emit('changed')
+ }
+
+ addDelta(delta) {
+ if (this.thinking) {
+ this.thinking = false;
+ this.content = delta;
+ }
+ else {
+ this.content += delta;
+ }
+ this.emit('delta', delta);
+ }
+}
+
+class ChatGPTService extends Service {
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newMsg': ['int'],
+ 'hasKey': ['boolean'],
+ });
+ }
+
+ _assistantPrompt = true;
+ _messages = [];
+ _cycleModels = true;
+ _requestCount = 0;
+ _temperature = 0.9;
+ _modelIndex = 0;
+ _key = '';
+ _decoder = new TextDecoder();
+
+ url = GLib.Uri.parse(replaceapidom('https://api.openai.com/v1/chat/completions'), GLib.UriFlags.NONE);
+
+ constructor() {
+ super();
+
+ if (fileExists(expandTilde(KEY_FILE_LOCATION))) this._key = Utils.readFile(expandTilde(KEY_FILE_LOCATION)).trim();
+ else this.emit('hasKey', false);
+
+ if (this._assistantPrompt) this._messages = [...initMessages];
+ else this._messages = [];
+
+ this.emit('initialized');
+ }
+
+ get modelName() { return CHAT_MODELS[this._modelIndex] }
+
+ get keyPath() { return KEY_FILE_LOCATION }
+ get key() { return this._key }
+ set key(keyValue) {
+ this._key = keyValue;
+ Utils.writeFile(this._key, expandTilde(KEY_FILE_LOCATION))
+ .then(this.emit('hasKey', true))
+ .catch(err => print(err));
+ }
+
+ get cycleModels() { return this._cycleModels }
+ set cycleModels(value) {
+ this._cycleModels = value;
+ if (!value) this._modelIndex = 0;
+ else {
+ this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
+ }
+ }
+
+ get temperature() { return this._temperature }
+ set temperature(value) { this._temperature = value; }
+
+ get messages() { return this._messages }
+ get lastMessage() { return this._messages[this._messages.length - 1] }
+
+ clear() {
+ if (this._assistantPrompt)
+ this._messages = [...initMessages];
+ else
+ this._messages = [];
+ this.emit('clear');
+ }
+
+ get assistantPrompt() { return this._assistantPrompt; }
+ set assistantPrompt(value) {
+ this._assistantPrompt = value;
+ if (value) this._messages = [...initMessages];
+ else this._messages = [];
+ }
+
+ readResponse(stream, aiResponse) {
+ stream.read_line_async(
+ 0, null,
+ (stream, res) => {
+ if (!stream) return;
+ const [bytes] = stream.read_line_finish(res);
+ const line = this._decoder.decode(bytes);
+ if (line && line != '') {
+ let data = line.substr(6);
+ if (data == '[DONE]') return;
+ try {
+ const result = JSON.parse(data);
+ if (result.choices[0].finish_reason === 'stop') {
+ aiResponse.done = true;
+ return;
+ }
+ aiResponse.addDelta(result.choices[0].delta.content);
+ }
+ catch {
+ aiResponse.addDelta(line + '\n');
+ }
+ }
+ this.readResponse(stream, aiResponse);
+ });
+ }
+
+ addMessage(role, message) {
+ this._messages.push(new ChatGPTMessage(role, message));
+ this.emit('newMsg', this._messages.length - 1);
+ }
+
+ send(msg) {
+ this._messages.push(new ChatGPTMessage('user', msg));
+ this.emit('newMsg', this._messages.length - 1);
+ const aiResponse = new ChatGPTMessage('assistant', 'thinking...', true, false)
+ this._messages.push(aiResponse);
+ this.emit('newMsg', this._messages.length - 1);
+
+ const body = {
+ model: CHAT_MODELS[this._modelIndex],
+ messages: this._messages.map(msg => { let m = { role: msg.role, content: msg.content }; return m; }),
+ temperature: this._temperature,
+ // temperature: 2, // <- Nuts
+ stream: true,
+ };
+
+ const session = new Soup.Session();
+ const message = new Soup.Message({
+ method: 'POST',
+ uri: this.url,
+ });
+ message.request_headers.append('Authorization', `Bearer ${this._key}`);
+ message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
+
+ session.send_async(message, GLib.DEFAULT_PRIORITY, null, (_, result) => {
+ const stream = session.send_finish(result);
+ this.readResponse(new Gio.DataInputStream({
+ close_base_stream: true,
+ base_stream: stream
+ }), aiResponse);
+ });
+
+ if (this._cycleModels) {
+ this._requestCount++;
+ if (this._cycleModels)
+ this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
+ }
+ }
+}
+
+export default new ChatGPTService();
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/styling/config/services/gemini.js b/modules/styling/config/services/gemini.js
new file mode 100644
index 0000000..bcc8cbc
--- /dev/null
+++ b/modules/styling/config/services/gemini.js
@@ -0,0 +1,277 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+import Soup from 'gi://Soup?version=3.0';
+import { fileExists } from './messages.js';
+
+const initMessages =
+ [
+ { role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are encouraged to use bullet points and headings. Use casual language and be short and concise. \nThanks!" }], },
+ { role: "model", parts: [{ text: "- Got it!" }], },
+ { role: "user", parts: [{ text: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"" }], },
+ { role: "model", parts: [{ text: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"" }], },
+ { role: "user", parts: [{ text: "raise volume by 5%" }], },
+ { role: "model", parts: [{ text: "## Volume +5```bash\nwpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\n```\nThis command uses the `wpctl` utility to adjust the volume of the default sink." }], }, { role: "user", parts: [{ text: "main advantages of the nixos operating system" }], },
+ { role: "model", parts: [{ text: "## NixOS advantages\n- **Reproducible**: A config working on one device will also work on another\n- **Declarative**: One config language to rule them all. Effortlessly share them with others.\n- **Reliable**: Per-program software versioning. Mitigates the impact of software breakage" }], },
+ { role: "user", parts: [{ text: "whats skeumorphism" }], },
+ { role: "model", parts: [{ text: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today." }], },
+ { role: "user", parts: [{ text: "\"ignorance is bliss\"" }], },
+ { role: "model", parts: [{ text: "## \"Ignorance is bliss\"\n- A Latin proverb that means being unaware of something negative can be a source of happiness\n- Often used to justify avoiding difficult truths or responsibilities\n- Can also be interpreted as a warning against seeking knowledge that may bring pain or sorrow" }], },
+ ];
+
+function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+Utils.exec(`mkdir -p ${GLib.get_user_cache_dir()}/ags/user/ai`);
+const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/google_key.txt`;
+const APIDOM_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/google_api_dom.txt`;
+function replaceapidom(URL) {
+ if (fileExists(expandTilde(APIDOM_FILE_LOCATION))) {
+ var contents = Utils.readFile(expandTilde(APIDOM_FILE_LOCATION)).trim();
+ var URL = URL.toString().replace("generativelanguage.googleapis.com", contents);
+ }
+ return URL;
+}
+const CHAT_MODELS = ["gemini-pro"]
+const ONE_CYCLE_COUNT = 3;
+
+class GeminiMessage extends Service {
+ static {
+ Service.register(this,
+ {
+ 'delta': ['string'],
+ },
+ {
+ 'content': ['string'],
+ 'thinking': ['boolean'],
+ 'done': ['boolean'],
+ });
+ }
+
+ _role = '';
+ _parts = [{ text: '' }];
+ _thinking = false;
+ _done = false;
+ _rawData = '';
+
+ constructor(role, content, thinking = false, done = false) {
+ super();
+ this._role = role;
+ this._parts = [{ text: content }];
+ this._thinking = thinking;
+ this._done = done;
+ }
+
+ get rawData() { return this._rawData }
+ set rawData(value) { this._rawData = value }
+
+ get done() { return this._done }
+ set done(isDone) { this._done = isDone; this.notify('done') }
+
+ get role() { return this._role }
+ set role(role) { this._role = role; this.emit('changed') }
+
+ get content() {
+ return this._parts.map(part => part.text).join();
+ }
+ set content(content) {
+ this._parts = [{ text: content }];
+ this.notify('content')
+ this.emit('changed')
+ }
+
+ get parts() { return this._parts }
+
+ get label() { return this._parserState.parsed + this._parserState.stack.join('') }
+
+ get thinking() { return this._thinking }
+ set thinking(thinking) {
+ this._thinking = thinking;
+ this.notify('thinking')
+ this.emit('changed')
+ }
+
+ addDelta(delta) {
+ if (this.thinking) {
+ this.thinking = false;
+ this.content = delta;
+ }
+ else {
+ this.content += delta;
+ }
+ this.emit('delta', delta);
+ }
+
+ parseSection() {
+ if(this._thinking) {
+ this._thinking = false;
+ this._parts[0].text= '';
+ }
+ const parsedData = JSON.parse(this._rawData);
+ const delta = parsedData.candidates[0].content.parts[0].text;
+ this._parts[0].text += delta;
+ // this.emit('delta', delta);
+ this.notify('content');
+ this._rawData = '';
+ }
+}
+
+class GeminiService extends Service {
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newMsg': ['int'],
+ 'hasKey': ['boolean'],
+ });
+ }
+
+ _assistantPrompt = true;
+ _messages = [];
+ _cycleModels = true;
+ _requestCount = 0;
+ _temperature = 0.9;
+ _modelIndex = 0;
+ _key = '';
+ _decoder = new TextDecoder();
+
+ constructor() {
+ super();
+
+ if (fileExists(expandTilde(KEY_FILE_LOCATION))) this._key = Utils.readFile(expandTilde(KEY_FILE_LOCATION)).trim();
+ else this.emit('hasKey', false);
+
+ if (this._assistantPrompt) this._messages = [...initMessages];
+ else this._messages = [];
+
+ this.emit('initialized');
+ }
+
+ get modelName() { return CHAT_MODELS[this._modelIndex] }
+
+ get keyPath() { return KEY_FILE_LOCATION }
+ get key() { return this._key }
+ set key(keyValue) {
+ this._key = keyValue;
+ Utils.writeFile(this._key, expandTilde(KEY_FILE_LOCATION))
+ .then(this.emit('hasKey', true))
+ .catch(err => print(err));
+ }
+
+ get cycleModels() { return this._cycleModels }
+ set cycleModels(value) {
+ this._cycleModels = value;
+ if (!value) this._modelIndex = 0;
+ else {
+ this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
+ }
+ }
+
+ get temperature() { return this._temperature }
+ set temperature(value) { this._temperature = value; }
+
+ get messages() { return this._messages }
+ get lastMessage() { return this._messages[this._messages.length - 1] }
+
+ clear() {
+ if (this._assistantPrompt)
+ this._messages = [...initMessages];
+ else
+ this._messages = [];
+ this.emit('clear');
+ }
+
+ get assistantPrompt() { return this._assistantPrompt; }
+ set assistantPrompt(value) {
+ this._assistantPrompt = value;
+ if (value) this._messages = [...initMessages];
+ else this._messages = [];
+ }
+
+ readResponse(stream, aiResponse) {
+ stream.read_line_async(
+ 0, null,
+ (stream, res) => {
+ try {
+ const [bytes] = stream.read_line_finish(res);
+ const line = this._decoder.decode(bytes);
+ if (line == '[{') { // beginning of response
+ aiResponse._rawData += '{';
+ this.thinking = false;
+ }
+ else if (line == ',\u000d' || line == ']') { // end of stream pulse
+ aiResponse.parseSection();
+ }
+ else // Normal content
+ aiResponse._rawData += line;
+
+ this.readResponse(stream, aiResponse);
+ } catch {
+ aiResponse.done = true;
+ return;
+ }
+ });
+ }
+
+ addMessage(role, message) {
+ this._messages.push(new GeminiMessage(role, message));
+ this.emit('newMsg', this._messages.length - 1);
+ }
+
+ send(msg) {
+ this._messages.push(new GeminiMessage('user', msg));
+ this.emit('newMsg', this._messages.length - 1);
+ const aiResponse = new GeminiMessage('model', 'thinking...', true, false)
+
+ const body =
+ {
+ "contents": this._messages.map(msg => { let m = { role: msg.role, parts: msg.parts }; return m; }),
+ // "safetySettings": [
+ // { category: "HARM_CATEGORY_DEROGATORY", threshold: "BLOCK_NONE", },
+ // { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE", },
+ // { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE", },
+ // { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE", },
+ // { category: "HARM_CATEGORY_UNSPECIFIED", threshold: "BLOCK_NONE", },
+ // ],
+ "generationConfig": {
+ "temperature": this._temperature,
+ },
+ // "key": this._key,
+ // "apiKey": this._key,
+ };
+
+ const session = new Soup.Session();
+ const message = new Soup.Message({
+ method: 'POST',
+ uri: GLib.Uri.parse(replaceapidom(`https://generativelanguage.googleapis.com/v1/models/${this.modelName}:streamGenerateContent?key=${this._key}`), GLib.UriFlags.NONE),
+ });
+ message.request_headers.append('Content-Type', `application/json`);
+ message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
+
+ session.send_async(message, GLib.DEFAULT_PRIORITY, null, (_, result) => {
+ const stream = session.send_finish(result);
+ this.readResponse(new Gio.DataInputStream({
+ close_base_stream: true,
+ base_stream: stream
+ }), aiResponse);
+ });
+ this._messages.push(aiResponse);
+ this.emit('newMsg', this._messages.length - 1);
+
+ if (this._cycleModels) {
+ this._requestCount++;
+ if (this._cycleModels)
+ this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
+ }
+ }
+}
+
+export default new GeminiService();
+
diff --git a/modules/styling/config/services/indicator.js b/modules/styling/config/services/indicator.js
new file mode 100644
index 0000000..f74f2a1
--- /dev/null
+++ b/modules/styling/config/services/indicator.js
@@ -0,0 +1,41 @@
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+class IndicatorService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'popup': ['double'], },
+ );
+ }
+
+ _delay = 1500;
+ _count = 0;
+
+ popup(value) {
+ this.emit('popup', value);
+ this._count++;
+ Utils.timeout(this._delay, () => {
+ this._count--;
+
+ if (this._count === 0)
+ this.emit('popup', -1);
+ });
+ }
+
+ connectWidget(widget, callback) {
+ connect(this, widget, callback, 'popup');
+ }
+}
+
+// the singleton instance
+const service = new IndicatorService();
+
+// make it global for easy use with cli
+globalThis['indicator'] = service;
+
+// export to use in other modules
+export default service;
\ No newline at end of file
diff --git a/modules/styling/config/services/messages.js b/modules/styling/config/services/messages.js
new file mode 100644
index 0000000..7f0db3d
--- /dev/null
+++ b/modules/styling/config/services/messages.js
@@ -0,0 +1,58 @@
+const { Notify, GLib, Gio } = imports.gi;
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
+
+
+export function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+const FIRST_RUN_FILE = "firstrun.txt";
+const FIRST_RUN_PATH = GLib.build_filenamev([GLib.get_user_cache_dir(), "ags", "user", FIRST_RUN_FILE]);
+const FIRST_RUN_FILE_CONTENT = "Just a file to confirm that you have been greeted ;)";
+const APP_NAME = "illogical-impulse";
+const FIRST_RUN_NOTIF_TITLE = "Welcome!";
+const FIRST_RUN_NOTIF_BODY = `Looks like this is your first run. For a list of keybinds, hit Super + /.`;
+
+export async function firstRunWelcome() {
+ if (!fileExists(FIRST_RUN_PATH)) {
+ Utils.writeFile(FIRST_RUN_FILE_CONTENT, FIRST_RUN_PATH)
+ .then(() => {
+ // Note that we add a little delay to make sure the cool circular progress works
+ Utils.execAsync(['hyprctl', 'keyword', 'bind', "Super,Slash,exec,ags -t cheatsheet"]);
+ Utils.execAsync(['bash', '-c',
+ `sleep 0.5; notify-send "Millis since epoch" "$(date +%s%N | cut -b1-13)"; sleep 0.5; notify-send '${FIRST_RUN_NOTIF_TITLE}' '${FIRST_RUN_NOTIF_BODY}' -a '${APP_NAME}' &`
+ ]).catch(print)
+ })
+ .catch(print);
+ }
+}
+
+const BATTERY_WARN_LEVELS = [20, 15, 5];
+const BATTERY_WARN_TITLES = ["Low battery", "Very low battery", 'Critical Battery']
+const BATTERY_WARN_BODIES = ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY']
+var batteryWarned = false;
+async function batteryMessage() {
+ const perc = Battery.percent;
+ const charging = Battery.charging;
+ if(charging) {
+ batteryWarned = false;
+ return;
+ }
+ for (let i = BATTERY_WARN_LEVELS.length - 1; i >= 0; i--) {
+ if (perc <= BATTERY_WARN_LEVELS[i] && !charging && !batteryWarned) {
+ batteryWarned = true;
+ Utils.execAsync(['bash', '-c',
+ `notify-send "${BATTERY_WARN_TITLES[i]}" "${BATTERY_WARN_BODIES[i]}" -u critical -a 'ags' &`
+ ]).catch(print);
+ break;
+ }
+ }
+}
+
+// Run them
+firstRunWelcome();
+Utils.timeout(1, () => {
+ Battery.connect('changed', () => batteryMessage());
+})
\ No newline at end of file
diff --git a/modules/styling/config/services/sway.js b/modules/styling/config/services/sway.js
new file mode 100644
index 0000000..388372e
--- /dev/null
+++ b/modules/styling/config/services/sway.js
@@ -0,0 +1,400 @@
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+import Service from "resource:///com/github/Aylur/ags/service.js";
+
+const SIS = GLib.getenv('SWAYSOCK');
+
+export const PAYLOAD_TYPE = {
+ MESSAGE_RUN_COMMAND: 0,
+ MESSAGE_GET_WORKSPACES: 1,
+ MESSAGE_SUBSCRIBE: 2,
+ MESSAGE_GET_OUTPUTS: 3,
+ MESSAGE_GET_TREE: 4,
+ MESSAGE_GET_MARKS: 5,
+ MESSAGE_GET_BAR_CONFIG: 6,
+ MESSAGE_GET_VERSION: 7,
+ MESSAGE_GET_BINDING_NODES: 8,
+ MESSAGE_GET_CONFIG: 9,
+ MESSAGE_SEND_TICK: 10,
+ MESSAGE_SYNC: 11,
+ MESSAGE_GET_BINDING_STATE: 12,
+ MESSAGE_GET_INPUTS: 100,
+ MESSAGE_GET_SEATS: 101,
+ EVENT_WORKSPACE: 0x80000000,
+ EVENT_MODE: 0x80000002,
+ EVENT_WINDOW: 0x80000003,
+ EVENT_BARCONFIG_UPDATE: 0x80000004,
+ EVENT_BINDING: 0x80000005,
+ EVENT_SHUTDOWN: 0x80000006,
+ EVENT_TICK: 0x80000007,
+ EVENT_BAR_STATE_UPDATE: 0x80000014,
+ EVENT_INPUT: 0x80000015,
+}
+
+const Client_Event = {
+ change: undefined,
+ container: undefined,
+}
+
+const Workspace_Event = {
+ change: undefined,
+ current: undefined,
+ old: undefined,
+}
+
+const Geometry = {
+ x: undefined,
+ y: undefined,
+ width: undefined,
+ height: undefined,
+}
+
+//NOTE: not all properties are listed here
+export const Node = {
+ id: undefined,
+ name: undefined,
+ type: undefined,
+ border: undefined,
+ current_border_width: undefined,
+ layout: undefined,
+ orientation: undefined,
+ percent: undefined,
+ rect: undefined,
+ window_rect: undefined,
+ deco_rect: undefined,
+ geometry: undefined,
+ urgent: undefined,
+ sticky: undefined,
+ marks: undefined,
+ focused: undefined,
+ active: undefined,
+ focus: undefined,
+ nodes: undefined,
+ floating_nodes: undefined,
+ representation: undefined,
+ fullscreen_mode: undefined,
+ app_id: undefined,
+ pid: undefined,
+ visible: undefined,
+ shell: undefined,
+ output: undefined,
+ inhibit_idle: undefined,
+ idle_inhibitors: {
+ application: undefined,
+ user: undefined,
+ },
+ window: undefined,
+ window_properties: {
+ title: undefined,
+ class: undefined,
+ instance: undefined,
+ window_role: undefined,
+ window_type: undefined,
+ transient_for: undefined,
+ }
+}
+
+export class SwayActiveClient extends Service {
+ static {
+ Service.register(this, {}, {
+ 'id': ['int'],
+ 'name': ['string'],
+ 'class': ['string'],
+ });
+ }
+
+ _id = 0;
+ _name = '';
+ _class = '';
+
+ get id() { return this._id; }
+ get name() { return this._name; }
+ get class() { return this._class; }
+
+ updateProperty(prop, value) {
+ if (!['id', 'name', 'class'].includes(prop)) return;
+ super.updateProperty(prop, value);
+ this.emit('changed');
+ }
+}
+
+export class SwayActiveID extends Service {
+ static {
+ Service.register(this, {}, {
+ 'id': ['int'],
+ 'name': ['string'],
+ });
+ }
+
+ _id = 0;
+ _name = '';
+
+ get id() { return this._id; }
+ get name() { return this._name; }
+
+ update(id, name) {
+ super.updateProperty('id', id);
+ super.updateProperty('name', name);
+ this.emit('changed');
+ }
+}
+
+export class SwayActives extends Service {
+ static {
+ Service.register(this, {}, {
+ 'client': ['jsobject'],
+ 'monitor': ['jsobject'],
+ 'workspace': ['jsobject'],
+ });
+ }
+
+ _client = new SwayActiveClient;
+ _monitor = new SwayActiveID;
+ _workspace = new SwayActiveID;
+
+ constructor() {
+ super();
+
+ (['client', 'workspace', 'monitor']).forEach(obj => {
+ this[`_${obj}`].connect('changed', () => {
+ this.notify(obj);
+ this.emit('changed');
+ });
+ });
+ }
+
+ get client() { return this._client; }
+ get monitor() { return this._monitor; }
+ get workspace() { return this._workspace; }
+}
+
+export class Sway extends Service {
+ static {
+ Service.register(this, {}, {
+ 'active': ['jsobject'],
+ 'monitors': ['jsobject'],
+ 'workspaces': ['jsobject'],
+ 'clients': ['jsobject'],
+ });
+ }
+
+ _decoder = new TextDecoder();
+ _encoder = new TextEncoder();
+ _socket;
+
+ _active;
+ _monitors;
+ _workspaces;
+ _clients;
+
+ get active() { return this._active; }
+ get monitors() { return Array.from(this._monitors.values()); }
+ get workspaces() { return Array.from(this._workspaces.values()); }
+ get clients() { return Array.from(this._clients.values()); }
+
+ getMonitor(id) { return this._monitors.get(id); }
+ getWorkspace(name) { return this._workspaces.get(name); }
+ getClient(id) { return this._clients.get(id); }
+
+ msg(payload) { this._send(PAYLOAD_TYPE.MESSAGE_RUN_COMMAND, payload); }
+
+ constructor() {
+ if (!SIS)
+ console.error('Sway is not running');
+ super();
+
+ this._active = new SwayActives();
+ this._monitors = new Map();
+ this._workspaces = new Map();
+ this._clients = new Map();
+
+ this._socket = new Gio.SocketClient().connect(new Gio.UnixSocketAddress({
+ path: `${SIS}`,
+ }), null);
+
+ this._watchSocket(this._socket.get_input_stream());
+ this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, '');
+ this._send(PAYLOAD_TYPE.MESSAGE_SUBSCRIBE, JSON.stringify(['window', 'workspace']));
+
+ this._active.connect('changed', () => this.emit('changed'));
+ ['monitor', 'workspace', 'client'].forEach(active =>
+ this._active.connect(`notify::${active}`, () => this.notify('active')));
+ }
+
+ _send(payloadType, payload) {
+ const pb = this._encoder.encode(payload);
+ const type = new Uint32Array([payloadType]);
+ const pl = new Uint32Array([pb.length]);
+ const magic_string = this._encoder.encode('i3-ipc');
+ const data = new Uint8Array([
+ ...magic_string,
+ ...(new Uint8Array(pl.buffer)),
+ ...(new Uint8Array(type.buffer)),
+ ...pb]);
+ this._socket.get_output_stream().write(data, null);
+ }
+
+ _watchSocket(stream) {
+ stream.read_bytes_async(14, GLib.PRIORITY_DEFAULT, null, (_, resultHeader) => {
+ const data = stream.read_bytes_finish(resultHeader).get_data();
+ if (!data)
+ return;
+ const payloadLength = new Uint32Array(data.slice(6, 10).buffer)[0];
+ const payloadType = new Uint32Array(data.slice(10, 14).buffer)[0];
+ stream.read_bytes_async(
+ payloadLength,
+ GLib.PRIORITY_DEFAULT,
+ null,
+ (_, resultPayload) => {
+ const data = stream.read_bytes_finish(resultPayload).get_data();
+ if (!data)
+ return;
+ this._onEvent(payloadType, JSON.parse(this._decoder.decode(data)));
+ this._watchSocket(stream);
+ });
+ });
+ }
+
+ async _onEvent(event_type, event) {
+ if (!event)
+ return;
+ try {
+ switch (event_type) {
+ case PAYLOAD_TYPE.EVENT_WORKSPACE:
+ this._handleWorkspaceEvent(event);
+ break;
+ case PAYLOAD_TYPE.EVENT_WINDOW:
+ this._handleWindowEvent(event);
+ break;
+ case PAYLOAD_TYPE.MESSAGE_GET_TREE:
+ this._handleTreeMessage(event);
+ break;
+ default:
+ break;
+ }
+ } catch (error) {
+ logError(error);
+ }
+ this.emit('changed');
+ }
+
+ _handleWorkspaceEvent(workspaceEvent) {
+ const workspace = workspaceEvent.current;
+ switch (workspaceEvent.change) {
+ case 'init':
+ this._workspaces.set(workspace.name, workspace);
+ break;
+ case 'empty':
+ this._workspaces.delete(workspace.name);
+ break;
+ case 'focus':
+ this._active.workspace.update(workspace.id, workspace.name);
+ this._active.monitor.update(1, workspace.output);
+
+ this._workspaces.set(workspace.name, workspace);
+ this._workspaces.set(workspaceEvent.old.name, workspaceEvent.old);
+ break;
+ case 'rename':
+ if (this._active.workspace.id === workspace.id)
+ this._active.workspace.updateProperty('name', workspace.name);
+ this._workspaces.set(workspace.name, workspace);
+ break;
+ case 'reload':
+ break;
+ case 'move':
+ case 'urgent':
+ default:
+ this._workspaces.set(workspace.name, workspace);
+ }
+ this.notify('workspaces');
+ }
+
+ _handleWindowEvent(clientEvent) {
+ const client = clientEvent.container;
+ const id = client.id;
+ switch (clientEvent.change) {
+ case 'new':
+ case 'close':
+ case 'floating':
+ case 'move':
+ // Refresh tree since client events don't contain the relevant information
+ // to be able to modify `workspace.nodes` or `workspace.floating_nodes`.
+ // There has to be a better way than this though :/
+ this._send(PAYLOAD_TYPE.MESSAGE_GET_TREE, '');
+ break;
+ case 'focus':
+ if (this._active.client.id === id)
+ return;
+ // eslint-disable-next-line no-case-declarations
+ const current_active = this._clients.get(this._active.client.id);
+ if (current_active)
+ current_active.focused = false;
+ this._active.client.updateProperty('id', id);
+ this._active.client.updateProperty('name', client.name);
+ this._active.client.updateProperty('class', client.shell === 'xwayland'
+ ? client.window_properties?.class || ''
+ : client.app_id,
+ );
+ break;
+ case 'title':
+ if (client.focused)
+ this._active.client.updateProperty('name', client.name);
+ this._clients.set(id, client);
+ this.notify('clients');
+ break;
+ case 'fullscreen_mode':
+ case 'urgent':
+ case 'mark':
+ default:
+ this._clients.set(id, client);
+ this.notify('clients');
+ }
+ }
+
+ _handleTreeMessage(node) {
+ switch (node.type) {
+ case 'root':
+ this._workspaces.clear();
+ this._clients.clear();
+ this._monitors.clear();
+ node.nodes.map(n => this._handleTreeMessage(n));
+ break;
+ case 'output':
+ this._monitors.set(node.id, node);
+ if (node.active)
+ this._active.monitor.update(node.id, node.name);
+ node.nodes.map(n => this._handleTreeMessage(n));
+ this.notify('monitors');
+ break;
+ case 'workspace':
+ this._workspaces.set(node.name, node);
+ // I think I'm missing something. There has to be a better way.
+ // eslint-disable-next-line no-case-declarations
+ const hasFocusedChild =
+ (n) => n.nodes.some(c => c.focused || hasFocusedChild(c));
+ if (node.focused || hasFocusedChild(node))
+ this._active.workspace.update(node.id, node.name);
+
+ node.nodes.map(n => this._handleTreeMessage(n));
+ this.notify('workspaces');
+ break;
+ case 'con':
+ case 'floating_con':
+ this._clients.set(node.id, node);
+ if (node.focused) {
+ this._active.client.updateProperty('id', node.id);
+ this._active.client.updateProperty('name', node.name);
+ this._active.client.updateProperty('class', node.shell === 'xwayland'
+ ? node.window_properties?.class || ''
+ : node.app_id,
+ );
+ }
+ node.nodes.map(n => this._handleTreeMessage(n));
+ this.notify('clients');
+ break;
+ }
+ }
+}
+
+export const sway = new Sway;
+export default sway;
\ No newline at end of file
diff --git a/modules/styling/config/services/todo.js b/modules/styling/config/services/todo.js
new file mode 100644
index 0000000..377fd8c
--- /dev/null
+++ b/modules/styling/config/services/todo.js
@@ -0,0 +1,89 @@
+const { Gio, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+class TodoService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'updated': [], },
+ );
+ }
+
+ _todoPath = '';
+ _todoJson = [];
+
+ refresh(value) {
+ this.emit('updated', value);
+ }
+
+ connectWidget(widget, callback) {
+ this.connect(widget, callback, 'updated');
+ }
+
+ get todo_json() {
+ return this._todoJson;
+ }
+
+ _save() {
+ Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath)
+ .catch(print);
+ }
+
+ add(content) {
+ this._todoJson.push({ content, done: false });
+ this._save();
+ this.emit('updated');
+ }
+
+ check(index) {
+ this._todoJson[index].done = true;
+ this._save();
+ this.emit('updated');
+ }
+
+ uncheck(index) {
+ this._todoJson[index].done = false;
+ this._save();
+ this.emit('updated');
+ }
+
+ remove(index) {
+ this._todoJson.splice(index, 1);
+ Utils.writeFile(JSON.stringify(this._todoJson), this._todoPath)
+ .catch(print);
+ this.emit('updated');
+ }
+
+ constructor() {
+ super();
+ this._todoPath = `${GLib.get_user_cache_dir()}/ags/user/todo.json`;
+ try {
+ const fileContents = Utils.readFile(this._todoPath);
+ this._todoJson = JSON.parse(fileContents);
+ }
+ catch {
+ Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`);
+ Utils.exec(`touch ${this._todoPath}`);
+ Utils.writeFile("[]", this._todoPath).then(() => {
+ this._todoJson = JSON.parse(Utils.readFile(this._todoPath))
+ }).catch(print);
+ }
+ }
+}
+
+// the singleton instance
+const service = new TodoService();
+
+// make it global for easy use with cli
+globalThis.todo = service;
+
+// export to use in other modules
+export default service;
\ No newline at end of file
diff --git a/modules/styling/config/services/waifus.js b/modules/styling/config/services/waifus.js
new file mode 100644
index 0000000..30ba8e7
--- /dev/null
+++ b/modules/styling/config/services/waifus.js
@@ -0,0 +1,161 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+// Usage from my python waifu fetcher, for reference
+// Usage: waifu-get.py [OPTION]... [TAG]...
+// Options:
+// --im\tUse waifu.im API. You can use many tags
+// --pics\tUse waifu.pics API. Use 1 tag only.
+// --nekos\tUse nekos.life (old) API. No tags.
+// --segs\tForce NSFW images
+
+// Tags:
+// waifu.im (type):
+// maid waifu marin-kitagawa mori-calliope raiden-shogun oppai selfies uniform
+// waifu.im (nsfw tags):
+// ecchi hentai ero ass paizuri oral milf
+
+function paramStringFromObj(params) {
+ return Object.entries(params)
+ .map(([key, value]) => {
+ if (Array.isArray(value)) { // If it's an array, repeat
+ if (value.length == 0) return '';
+ let thisKey = `${encodeURIComponent(key)}=${encodeURIComponent(value[0])}`
+ for (let i = 1; i < value.length; i++) {
+ thisKey += `&${encodeURIComponent(key)}=${encodeURIComponent(value[i])}`;
+ }
+ return thisKey;
+ }
+ return `${key}=${value}`;
+ })
+ .join('&');
+}
+
+class WaifuService extends Service {
+ _endpoints = {
+ 'im': 'https://api.waifu.im/search',
+ 'nekos': 'https://nekos.life/api/neko',
+ 'pics': 'https://api.waifu.pics/sfw/',
+ }
+ _headers = {
+ 'im': { 'Accept-Version': 'v5' },
+ 'nekos': {},
+ 'pics': {},
+ }
+ _baseUrl = 'https://api.waifu.im/search';
+ _mode = 'im'; // Allowed: im
+ _responses = [];
+ _queries = [];
+ _nsfw = false;
+ _minHeight = 600;
+ _status = 0;
+
+ static {
+ Service.register(this, {
+ 'initialized': [],
+ 'clear': [],
+ 'newResponse': ['int'],
+ 'updateResponse': ['int'],
+ });
+ }
+
+ constructor() {
+ super();
+ this.emit('initialized');
+ }
+
+ clear() {
+ this._responses = [];
+ this._queries = [];
+ this.emit('clear');
+ }
+
+ get mode() { return this._mode }
+ set mode(value) {
+ this._mode = value;
+ this._baseUrl = this._endpoints[this._mode];
+ }
+ get nsfw() { return this._nsfw }
+ set nsfw(value) { this._nsfw = value }
+ get queries() { return this._queries }
+ get responses() { return this._responses }
+
+ async fetch(msg) {
+ // Init
+ const userArgs = msg.split(/\s+/);
+
+ let taglist = [];
+ this._nsfw = false;
+ // Construct body/headers
+ for (let i = 0; i < userArgs.length; i++) {
+ const thisArg = userArgs[i].trim();
+ if(thisArg.length == 0) continue;
+ if (thisArg == '--im') this._mode = 'im';
+ else if (thisArg == '--nekos') this._mode = 'nekos';
+ else if (thisArg.includes('pics')) this._mode = 'pics';
+ else if (thisArg.includes('segs') || thisArg.includes('sex') || thisArg.includes('lewd')) this._nsfw = true;
+ else {
+ taglist.push(thisArg);
+ if(['ecchi', 'hentai', 'ero', 'ass', 'paizuri', 'oral', 'milf'].includes(thisArg)) this._nsfw = true;
+ }
+ }
+ const newMessageId = this._queries.length;
+ this._queries.push(taglist);
+ this.emit('newResponse', newMessageId);
+ const params = {
+ 'included_tags': taglist,
+ 'height': `>=${this._minHeight}`,
+ 'nsfw': this._nsfw,
+ };
+ const paramString = paramStringFromObj(params);
+ // Fetch
+ // Note: body isn't included since passing directly to url is more reliable
+ const options = {
+ method: 'GET',
+ headers: this._headers[this._mode],
+ };
+ var status = 0;
+ Utils.fetch(`${this._endpoints[this._mode]}?${paramString}`, options)
+ .then(result => {
+ status = result.status;
+ return result.text();
+ })
+ .then((dataString) => { // Store interesting stuff and emit
+ const parsedData = JSON.parse(dataString);
+ if (!parsedData.images) this._responses.push({
+ status: status,
+ signature: -1,
+ url: '',
+ extension: '',
+ source: '',
+ dominant_color: '#383A40',
+ is_nsfw: false,
+ width: 0,
+ height: 0,
+ tags: [],
+ });
+ else {
+ const imageData = parsedData.images[0];
+ this._responses.push({
+ status: status,
+ signature: imageData?.signature || -1,
+ url: imageData?.url || undefined,
+ extension: imageData.extension,
+ source: imageData?.source,
+ dominant_color: imageData?.dominant_color || '#9392A6',
+ is_nsfw: imageData?.is_nsfw || false,
+ width: imageData?.width || 0,
+ height: imageData?.height || 0,
+ tags: imageData?.tags.map(obj => obj["name"]) || [],
+ });
+ }
+ this.emit('updateResponse', newMessageId);
+ })
+ .catch(console.error)
+
+ }
+}
+
+export default new WaifuService();
+
diff --git a/modules/styling/config/services/wallpaper.js b/modules/styling/config/services/wallpaper.js
new file mode 100644
index 0000000..9b501c8
--- /dev/null
+++ b/modules/styling/config/services/wallpaper.js
@@ -0,0 +1,75 @@
+const { Gdk, Gio, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+class WallpaperService extends Service {
+ static {
+ Service.register(
+ this,
+ { 'updated': [], },
+ );
+ }
+
+ _wallPath = '';
+ _wallJson = [];
+ _monitorCount = 1;
+
+ _save() {
+ Utils.writeFile(JSON.stringify(this._wallJson), this._wallPath)
+ .catch(print);
+ }
+
+ add(path) {
+ this._wallJson.push(path);
+ this._save();
+ this.emit('updated');
+ }
+
+ set(path, monitor = -1) {
+ this._monitorCount = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ if (this._wallJson.length < this._monitorCount) this._wallJson[this._monitorCount - 1] = "";
+ if (monitor == -1)
+ this._wallJson.fill(path);
+ else
+ this._wallJson[monitor] = path;
+
+ this._save();
+ this.emit('updated');
+ }
+
+ get(monitor = 0) {
+ return this._wallJson[monitor];
+ }
+
+ constructor() {
+ super();
+ // How many screens?
+ this._monitorCount = Gdk.Display.get_default()?.get_n_monitors() || 1;
+ // Read config
+ this._wallPath = `${GLib.get_user_cache_dir()}/ags/user/wallpaper.json`;
+ try {
+ const fileContents = Utils.readFile(this._wallPath);
+ this._wallJson = JSON.parse(fileContents);
+ }
+ catch {
+ Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`);
+ Utils.exec(`touch ${this._wallPath}`);
+ Utils.writeFile('[]', this._wallPath).then(() => {
+ this._wallJson = JSON.parse(Utils.readFile(this._wallPath))
+ }).catch(print);
+ }
+ }
+}
+
+// instance
+const service = new WallpaperService();
+// make it global for easy use with cli
+globalThis['wallpaper'] = service;
+export default service;
\ No newline at end of file
diff --git a/modules/styling/config/style.css b/modules/styling/config/style.css
index 29f8fdd..8e7efd5 100644
--- a/modules/styling/config/style.css
+++ b/modules/styling/config/style.css
@@ -1,40 +1,2460 @@
-window.bar {
- background-color: @theme_bg_color;
- color: @theme_fg_color;
-}
+*:not(popover) {
+ all: unset; }
-button {
- min-width: 0;
- padding-top: 0;
- padding-bottom: 0;
+@keyframes flyin-top {
+ from {
+ margin-top: -2.795rem; }
+ to {
+ margin-top: 0rem; } }
+
+@keyframes flyin-bottom {
+ from {
+ margin-top: 4.841rem;
+ margin-bottom: -4.841rem; }
+ to {
+ margin-bottom: 0rem;
+ margin-top: 0rem; } }
+
+.test {
+ background-image: linear-gradient(45deg, #f4d609 0%, #f4d609 10%, #212121 10%, #212121 20%, #f4d609 20%, #f4d609 30%, #212121 30%, #212121 40%, #f4d609 40%, #f4d609 50%, #212121 50%, #212121 60%, #f4d609 60%, #f4d609 70%, #212121 70%, #212121 80%, #f4d609 80%, #f4d609 90%, #212121 90%, #212121 100%);
+ background-repeat: repeat; }
+
+.test-size {
+ min-height: 3rem;
+ min-width: 3rem; }
+
+.txt-title {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 2.045rem; }
+
+.txt-title-small {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 1.364rem; }
+
+.techfont {
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font", "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace; }
+
+.txt-reading {
+ font-family: "Lexend", "Noto Sans", sans-serif; }
+
+.no-anim {
+ transition: 0ms; }
+
+.txt {
+ color: #ecdfe0; }
+
+.txt-primary {
+ color: #ffc8d5; }
+
+.txt-onSecondaryContainer {
+ color: #ffd9df; }
+
+.txt-onSurfaceVariant {
+ color: #d6c1c4; }
+
+.txt-shadow {
+ text-shadow: 1px 2px 8px rgba(0, 0, 0, 0.69);
+ margin: 10px; }
+
+.txt-gigantic {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 3rem; }
+
+.txt-massive {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 2.7273rem; }
+
+.txt-hugerass {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 2.045rem; }
+
+.txt-hugeass {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 1.8182rem; }
+
+.txt-larger {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 1.6363rem; }
+
+.txt-large {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 1.4545rem; }
+
+.txt-norm {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 1.2727rem; }
+
+.txt-small {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 1.0909rem; }
+
+.txt-smallie {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 1rem; }
+
+.txt-smaller {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 0.9091rem; }
+
+.txt-tiny {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 0.7273rem; }
+
+.txt-poof {
+ font-size: 0px; }
+
+.txt-subtext {
+ color: #aaa0a1; }
+
+.txt-action {
+ color: #cbc0c1; }
+
+.txt-thin {
+ font-weight: 300; }
+
+.txt-semibold {
+ font-weight: 500; }
+
+.txt-bold {
+ font-weight: bold; }
+
+.txt-italic {
+ font-style: italic; }
+
+.btn-primary {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ background-color: #ffc8d5;
+ color: #66002a;
+ padding: 0.682rem 1.023rem; }
+
+.titlefont {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif; }
+
+.mainfont {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif; }
+
+.icon-material {
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp"; }
+
+.icon-nerd {
+ font-family: "SpaceMono NF", "SpaceMono Nerd Font", "JetBrains Mono NF", "JetBrains Mono Nerd Font", monospace; }
+
+.separator-line {
+ background-color: #9f8c8f;
+ min-width: 0.068rem;
+ min-height: 0.068rem; }
+
+.separator-circle {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ background-color: #9f8c8f;
+ margin: 0rem 0.682rem;
+ min-width: 0.273rem;
+ min-height: 0.273rem; }
+
+.spacing-h-3 > * {
+ margin-right: 0.205rem; }
+
+.spacing-h-3 > *:last-child {
+ margin-right: 0rem; }
+
+.spacing-v-3 > * {
+ margin-bottom: 0.205rem; }
+
+.spacing-v-3 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-v-15 > * {
+ margin-bottom: 1.023rem; }
+
+.spacing-v-15 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-h-15 > * {
+ margin-right: 1.023rem; }
+
+.spacing-h-15 > *:last-child {
+ margin-right: 0rem; }
+
+.spacing-h-15 > revealer > * {
+ margin-right: 1.023rem; }
+
+.spacing-h-15 > revealer:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-h-15 > scrolledwindow > * {
+ margin-right: 1.023rem; }
+
+.spacing-h-15 > scrolledwindow:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-v-5 > box {
+ margin-bottom: 0.341rem; }
+
+.spacing-v-5 > box:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-v-5 > * {
+ margin-bottom: 0.341rem; }
+
+.spacing-v-5 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-v-5-revealer > revealer > * {
+ margin-bottom: 0.341rem; }
+
+.spacing-v-5-revealer > revealer:last-child > * {
+ margin-bottom: 0rem; }
+
+.spacing-v-5-revealer > scrolledwindow > * {
+ margin-bottom: 0.341rem; }
+
+.spacing-v-5-revealer > scrolledwindow:last-child > * {
+ margin-bottom: 0rem; }
+
+.spacing-h-4 > * {
+ margin-right: 0.273rem; }
+
+.spacing-h-4 > *:last-child {
+ margin-right: 0rem; }
+
+.spacing-h-4 > overlay > *:first-child {
+ margin-right: 0.273rem; }
+
+.spacing-h-4 > overlay:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-h-5 > * {
+ margin-right: 0.341rem; }
+
+.spacing-h-5 > *:last-child {
+ margin-right: 0rem; }
+
+.spacing-h-5 > widget > * {
+ margin-right: 0.341rem; }
+
+.spacing-h-5 > widget:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-h-5 > revealer > * {
+ margin-right: 0.341rem; }
+
+.spacing-h-5 > revealer:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-h-5 > scrolledwindow > * {
+ margin-right: 0.341rem; }
+
+.spacing-h-5 > scrolledwindow:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-v-minus5 > * {
+ margin-bottom: -0.341rem; }
+
+.spacing-v-minus5 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-h-10 > * {
+ margin-right: 0.682rem; }
+
+.spacing-h-10 > *:last-child {
+ margin-right: 0rem; }
+
+.spacing-h-10 > revealer > * {
+ margin-right: 0.682rem; }
+
+.spacing-h-10 > revealer:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-h-10 > scrolledwindow > * {
+ margin-right: 0.682rem; }
+
+.spacing-h-10 > scrolledwindow:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-h-10 > flowboxchild > * {
+ margin-right: 0.682rem; }
+
+.spacing-h-10 > flowboxchild:last-child > * {
+ margin-right: 0rem; }
+
+.spacing-v-10 > * {
+ margin-bottom: 0.682rem; }
+
+.spacing-v-10 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-h-20 > * {
+ margin-right: 1.364rem; }
+
+.spacing-h-20 > *:last-child {
+ margin-right: 0rem; }
+
+.spacing-v-20 > * {
+ margin-bottom: 1.364rem; }
+
+.spacing-v-20 > *:last-child {
+ margin-bottom: 0rem; }
+
+.anim-enter {
+ transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1); }
+
+.anim-exit {
+ transition: 150ms cubic-bezier(0.3, 0, 0.8, 0.15); }
+
+.button-minsize {
+ min-width: 2.727rem;
+ min-height: 2.727rem; }
+
+.margin-right-5 {
+ margin-right: 0.341rem; }
+
+.margin-left-5 {
+ margin-left: 0.341rem; }
+
+.margin-top-5 {
+ margin-top: 0.341rem; }
+
+.margin-bottom-5 {
+ margin-bottom: 0.341rem; }
+
+.margin-right-10 {
+ margin-right: 0.682rem; }
+
+.margin-left-10 {
+ margin-left: 0.682rem; }
+
+.margin-top-10 {
+ margin-top: 0.682rem; }
+
+.margin-bottom-10 {
+ margin-bottom: 0.682rem; }
+
+.margin-right-15 {
+ margin-right: 1.023rem; }
+
+.margin-left-15 {
+ margin-left: 1.023rem; }
+
+.margin-top-15 {
+ margin-top: 1.023rem; }
+
+.margin-bottom-15 {
+ margin-bottom: 1.023rem; }
+
+.width-10 {
+ min-width: 0.682rem; }
+
+.height-10 {
+ min-width: 0.682rem; }
+
+.invisible {
+ opacity: 0;
background-color: transparent;
-}
+ color: transparent; }
-button:active {
- background-color: @theme_selected_bg_color;
-}
+.spacing-h--5 > box {
+ margin-right: -0.341rem; }
-button:hover {
- border-bottom: 3px solid @theme_fg_color;
-}
+.spacing-h--5 > box:last-child {
+ margin-right: 0rem; }
-label {
- font-weight: bold;
-}
+.spacing-v--5 > * {
+ margin-bottom: -0.341rem; }
-.workspaces button.focused {
- border-bottom: 3px solid @theme_selected_bg_color;
-}
+.spacing-v--5 > *:last-child {
+ margin-bottom: 0rem; }
-.client-title {
- color: @theme_selected_bg_color;
-}
+.spacing-h--10 > * {
+ margin-left: -1.364rem; }
-.notification {
- color: yellow;
-}
+.spacing-h--10 > *:first-child {
+ margin-left: 0rem; }
-levelbar block,
-highlight {
- min-height: 10px;
-}
+.spacing-v--10 > * {
+ margin-bottom: -0.682rem; }
+
+.spacing-v--10 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-v--10 > * {
+ margin-bottom: -0.682rem; }
+
+.spacing-v--10 > *:last-child {
+ margin-bottom: 0rem; }
+
+.spacing-h--20 > * {
+ margin-left: -1.364rem; }
+
+.spacing-h--20 > *:first-child {
+ margin-left: 0rem; }
+
+.instant {
+ transition: 0ms; }
+
+.menu-decel {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1); }
+
+.element-show {
+ transition: 300ms cubic-bezier(0.85, 0, 0.15, 1); }
+
+.element-hide {
+ transition: 300ms cubic-bezier(0.85, 0, 0.15, 1); }
+
+.element-move {
+ transition: 300ms cubic-bezier(0.85, 0, 0.15, 1); }
+
+.element-decel {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1); }
+
+.element-bounceout {
+ transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1); }
+
+.element-accel {
+ transition: 300ms cubic-bezier(0.55, 0, 1, 0.45); }
+
+.page-move {
+ transition: 500ms cubic-bezier(0.85, 0, 0.15, 1); }
+
+* {
+ caret-color: #ffc8d5; }
+ * selection {
+ background-color: #e4bcc3;
+ color: #42292e; }
+
+@keyframes appear {
+ from {
+ opacity: 0; }
+ to {
+ opacity: 1; } }
+
+menu {
+ border-top: 1px solid rgba(63, 56, 57, 0.121);
+ border-left: 1px solid rgba(63, 56, 57, 0.121);
+ border-right: 1px solid rgba(49, 42, 43, 0.1105);
+ border-bottom: 1px solid rgba(49, 42, 43, 0.1105);
+ padding: 0.681rem;
+ background: #3d3234;
+ color: #d6c1c4;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ animation-name: appear;
+ animation-duration: 40ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1; }
+
+menubar > menuitem {
+ border-radius: 0.545rem;
+ -gtk-outline-radius: 0.545rem;
+ min-width: 13.636rem;
+ min-height: 2.727rem; }
+
+menu > menuitem {
+ padding: 0.4em 1.5rem;
+ background: transparent;
+ transition: 0.2s ease background;
+ border-radius: 0.545rem;
+ -gtk-outline-radius: 0.545rem; }
+
+menu > menuitem:hover,
+menu > menuitem:focus {
+ background-color: #4c4042; }
+
+menu > menuitem:active {
+ background-color: #5c4f51; }
+
+radio {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ margin: 0.273rem;
+ min-width: 15px;
+ min-height: 15px;
+ border: 0.068rem solid #9f8c8f; }
+
+radio:checked {
+ min-width: 8px;
+ min-height: 8px;
+ background-color: #66002a;
+ border: 0.477rem solid #ffc8d5; }
+
+tooltip {
+ animation-name: appear;
+ animation-duration: 100ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: #3d3234;
+ color: #d6c1c4;
+ border: 1px solid #d6c1c4; }
+
+popover {
+ border-top: 1px solid rgba(63, 56, 57, 0.121);
+ border-left: 1px solid rgba(63, 56, 57, 0.121);
+ border-right: 1px solid rgba(49, 42, 43, 0.1105);
+ border-bottom: 1px solid rgba(49, 42, 43, 0.1105);
+ padding: 0.681rem;
+ background: #3d3234;
+ color: #d6c1c4;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ animation-name: appear;
+ animation-duration: 40ms;
+ animation-timing-function: ease-out;
+ animation-iteration-count: 1; }
+
+.configtoggle-box {
+ padding: 0.205rem 0.341rem;
+ border: 0.136rem solid transparent; }
+
+.configtoggle-box:focus {
+ border: 0.136rem solid #73696a; }
+
+.switch-bg {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ background-color: #1a1415;
+ border: 0.136rem solid #ecdfe0;
+ min-width: 2.864rem;
+ min-height: 1.637rem; }
+
+.switch-bg-true {
+ background-color: #ffc8d5;
+ border: 0.136rem solid #ffc8d5; }
+
+.switch-fg {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ background-color: #ecdfe0;
+ color: #221b1c;
+ min-width: 0.819rem;
+ min-height: 0.819rem;
+ margin-left: 0.477rem; }
+
+.switch-fg-true {
+ background-color: #66002a;
+ color: #ffc8d5;
+ min-width: 1.431rem;
+ min-height: 1.431rem;
+ margin-left: 1.431rem; }
+
+.switch-fg-toggling-false {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 1.636rem;
+ min-height: 0.819rem; }
+
+.segment-container {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ border: 0.068rem solid #9f8c8f; }
+
+.segment-container > *:first-child {
+ border-top-left-radius: 9999px;
+ border-bottom-left-radius: 9999px; }
+
+.segment-container > * {
+ border-right: 0.068rem solid #9f8c8f;
+ padding: 0.341rem 0.682rem; }
+
+.segment-container > *:last-child {
+ border-right: 0rem solid transparent;
+ border-top-right-radius: 9999px;
+ border-bottom-right-radius: 9999px; }
+
+.segment-btn {
+ color: #ecdfe0; }
+
+.segment-btn:focus,
+.segment-btn:hover {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.segment-btn-enabled {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.segment-btn-enabled:hover,
+.segment-btn-enabled:focus {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.gap-v-5 {
+ min-height: 0.341rem; }
+
+.gap-h-5 {
+ min-width: 0.341rem; }
+
+.gap-v-10 {
+ min-height: 0.682rem; }
+
+.gap-h-10 {
+ min-width: 0.682rem; }
+
+.gap-v-15 {
+ min-height: 1.023rem; }
+
+.gap-h-15 {
+ min-width: 1.023rem; }
+
+.bar-height {
+ min-height: 2.727rem; }
+
+.bar-bg {
+ background-color: #110d0e;
+ min-height: 2.727rem; }
+
+.bar-sidespace {
+ min-width: 1.5rem; }
+
+.bar-group-margin {
+ padding: 0.273rem 0rem; }
+
+.bar-group {
+ background-color: rgba(61, 50, 52, 0.45); }
+
+.bar-group-pad {
+ padding: 0.205rem; }
+
+.bar-group-pad-less {
+ padding: 0rem 0.681rem; }
+
+.bar-group-pad-system {
+ padding: 0rem 0.341rem; }
+
+.bar-group-pad-music {
+ padding-right: 1.023rem;
+ padding-left: 0.341rem; }
+
+.bar-group-standalone {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ -gtk-outline-radius: 1.364rem; }
+
+.bar-group-round {
+ border-radius: 10rem;
+ -gtk-outline-radius: 10rem; }
+
+.bar-group-middle {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem; }
+
+.bar-group-left {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+ border-top-left-radius: 1.364rem;
+ border-bottom-left-radius: 1.364rem; }
+
+.bar-group-right {
+ border-radius: 0.477rem;
+ -gtk-outline-radius: 0.477rem;
+ border-top-right-radius: 1.364rem;
+ border-bottom-right-radius: 1.364rem; }
+
+.bar-ws-width {
+ min-width: 18.341rem; }
+
+.bar-ws {
+ min-width: 1.774rem;
+ color: #696162;
+ color: #746c6d; }
+
+.bar-ws-active {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.bar-ws-occupied {
+ background-color: #3d3234;
+ color: #d6c1c4; }
+
+.bar-separator {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 0.341rem;
+ min-height: 0.341rem;
+ background-color: rgba(54, 47, 48, 0.31);
+ margin: 0rem 0.341rem; }
+
+.bar-clock-box {
+ margin: 0rem 0.682rem; }
+
+.bar-clock {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 1.2727rem; }
+
+.bar-date {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 1rem;
+ color: #ecdfe0; }
+
+.bar-batt {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.bar-sidemodule {
+ min-width: 26rem; }
+
+.bar-batt-low {
+ background-color: #ffb4a9;
+ color: #930006; }
+
+.bar-batt-full {
+ background-color: #374b3e;
+ color: #d1e9d6; }
+
+.bar-batt-circprog {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 0.068rem;
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.bar-batt-circprog-low {
+ background-color: #ffb4a9;
+ color: #930006; }
+
+.bar-batt-circprog-full {
+ background-color: #374b3e;
+ color: #d1e9d6; }
+
+.bar-music-playstate {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.bar-music-circprog {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 0.068rem;
+ min-height: 1.636rem;
+ padding: 0rem;
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.bar-music-playstate-playing {
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ border-radius: 10rem;
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.bar-music-playstate-txt {
+ transition: 100ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp"; }
+
+.bar-music-cover {
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 100% auto;
+ min-width: 11.932rem; }
+
+.bar-music-extended-bg {
+ border-radius: 1.364rem;
+ min-width: 34.091rem; }
+
+.bar-music-extended-ctl-bg {
+ border-radius: 1.364rem;
+ background-color: rgba(30, 30, 30, 0.6); }
+
+.bar-music-hide-false {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ transition-duration: 100ms;
+ opacity: 1; }
+
+.bar-music-hide-true {
+ transition: 160ms cubic-bezier(0.38, 0.04, 1, 0.07);
+ transition-duration: 100ms;
+ opacity: 0; }
+
+.bar-corner-spacing {
+ min-width: 1.705rem;
+ min-height: 1.705rem; }
+
+.corner {
+ background-color: #110d0e;
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem; }
+
+.corner-black {
+ background-color: black;
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem; }
+
+.bar-topdesc {
+ margin-top: -0.136rem;
+ margin-bottom: -0.341rem;
+ color: #aaa0a1; }
+
+.bar-space-button {
+ padding: 0.341rem; }
+
+.bar-space-button > box:first-child {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ padding: 0rem 0.682rem; }
+
+.bar-space-button:hover > box:first-child,
+.bar-space-button:focus > box:first-child {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.bar-space-button:active > box:first-child {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.bar-space-button-leftmost box {
+ margin: 0rem 0.682rem; }
+
+.bar-space-area-rightmost > box {
+ padding-right: 2.386rem; }
+
+.bar-systray {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ margin: 0.137rem 0rem;
+ padding: 0rem 0.682rem; }
+
+.bar-systray-item {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 1.032rem;
+ min-width: 1.032rem;
+ font-size: 1.032rem; }
+
+.bar-statusicons {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ margin: 0.273rem;
+ padding: 0rem 0.614rem; }
+
+.bar-statusicons-hover {
+ background-color: #272223; }
+
+.bar-statusicons-active {
+ background-color: #3d3738; }
+
+.bar-util-btn {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 1.77rem;
+ min-width: 1.77rem;
+ background-color: #3d3234; }
+
+.bar-util-btn:hover,
+.bar-util-btn:focus {
+ background-color: #4c4042; }
+
+.bar-util-btn:active {
+ background-color: #5c4f51; }
+
+.cheatsheet-bg {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ margin-bottom: 0.682rem;
+ background-color: #110d0e;
+ padding: 1.364rem; }
+
+.cheatsheet-key {
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font", "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace;
+ min-height: 1.364rem;
+ min-width: 1.364rem;
+ margin: 0.17rem;
+ padding: 0.136rem 0.205rem;
+ border-radius: 0.409rem;
+ -gtk-outline-radius: 0.409rem;
+ color: #ffc8d5;
+ border: 0.068rem solid #ffc8d5;
+ box-shadow: 0rem 0.136rem 0rem #ffc8d5;
+ font-weight: 500; }
+
+.cheatsheet-key-notkey {
+ min-height: 1.364rem;
+ padding: 0.136rem 0.205rem;
+ margin: 0.17rem;
+ color: #ffd9e0; }
+
+.cheatsheet-closebtn {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 2.386rem;
+ min-height: 2.386rem; }
+
+.cheatsheet-closebtn:hover,
+.cheatsheet-closebtn:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.cheatsheet-closebtn:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.cheatsheet-category-title {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 1.705rem; }
+
+.bg-wallpaper-transition {
+ transition: 1000ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ font-size: 1px; }
+
+.bg-time-box {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ margin: 2.045rem;
+ padding: 0.682rem; }
+
+.bg-time-clock {
+ font-family: 'Gabarito';
+ font-size: 5.795rem;
+ color: #ecdfe0; }
+
+.bg-time-date {
+ font-family: 'Gabarito';
+ font-size: 2.591rem;
+ color: #ecdfe0; }
+
+.bg-distro-box {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ margin: 2.045rem;
+ padding: 0.682rem; }
+
+.bg-distro-txt {
+ font-family: 'Gabarito';
+ font-size: 1.432rem;
+ color: #ecdfe0; }
+
+.bg-distro-name {
+ font-family: 'Gabarito';
+ font-size: 1.432rem;
+ color: #ffd9df; }
+
+.bg-graph {
+ color: rgba(255, 255, 255, 0.5);
+ border-radius: 0.614rem;
+ border: 0.682rem solid; }
+
+.bg-quicklaunch-title {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ color: #d6c1c4; }
+
+.bg-quicklaunch-btn {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ background-color: #3d3234;
+ color: #d6c1c4;
+ border: 0.068rem solid #aaa0a1;
+ min-width: 4.432rem;
+ min-height: 2.045rem;
+ padding: 0.273rem 0.682rem; }
+
+.bg-quicklaunch-btn:hover,
+.bg-quicklaunch-btn:focus {
+ background-color: #45393b; }
+
+.bg-quicklaunch-btn:active {
+ background-color: #4c4042; }
+
+.bg-system-bg {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem; }
+
+.bg-system-circprog {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 0.205rem;
+ min-height: 4.091rem;
+ font-size: 0px;
+ padding: 0rem;
+ background-color: #3d3234; }
+
+.dock-bg {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ background-color: #110d0e;
+ padding: 0.682rem; }
+
+.dock-app-btn {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ padding: 0.273rem; }
+
+.dock-app-btn:hover,
+.dock-app-btn:focus {
+ background-color: rgba(44, 37, 38, 0.31); }
+
+.dock-app-btn:active {
+ background-color: rgba(64, 56, 57, 0.31); }
+
+.dock-app-icon {
+ min-width: 3.409rem;
+ min-height: 3.409rem;
+ font-size: 3.409rem; }
+
+.dock-separator {
+ min-width: 0.068rem;
+ background-color: #3d3234; }
+
+.osd-bg {
+ min-width: 8.864rem;
+ min-height: 3.409rem; }
+
+.osd-value {
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ background-color: #110d0e;
+ border-radius: 1.023rem;
+ padding: 0.625rem 1.023rem;
+ padding-top: 0.313rem; }
+
+.osd-progress {
+ min-height: 0.955rem;
+ min-width: 0.068rem;
+ padding: 0rem;
+ border-radius: 10rem;
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1); }
+ .osd-progress trough {
+ min-height: 0.954rem;
+ min-width: 0.068rem;
+ border-radius: 10rem;
+ background-color: #5c3f45; }
+ .osd-progress progress {
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1);
+ min-height: 0.680rem;
+ min-width: 0.680rem;
+ margin: 0rem 0.137rem;
+ border-radius: 10rem;
+ background-color: #ffd9df; }
+
+.osd-icon {
+ color: #ffd9e0; }
+
+.osd-label {
+ font-size: 1.023rem;
+ font-weight: 500;
+ color: #ecdfe0;
+ margin-top: 0.341rem; }
+
+.osd-value-txt {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 1.688rem;
+ font-weight: 500;
+ color: #ecdfe0; }
+
+.osd-notifs {
+ padding-top: 0.313rem; }
+
+.osd-colorscheme {
+ border-radius: 1.023rem;
+ background-color: #110d0e;
+ padding: 1.023rem;
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem; }
+
+.osd-color {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 2.727rem;
+ min-height: 1.705rem;
+ padding: 0rem 0.341rem;
+ font-weight: bold; }
+ .osd-color box {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ margin: 0.409rem; }
+
+.osd-color-primary {
+ background-color: #ffc8d5;
+ color: #66002a; }
+ .osd-color-primary box {
+ background-color: #66002a; }
+
+.osd-color-primaryContainer {
+ background-color: #923353;
+ color: #ffd9e0; }
+ .osd-color-primaryContainer box {
+ background-color: #ffd9e0; }
+
+.osd-color-secondary {
+ background-color: #e4bcc3;
+ color: #42292e; }
+ .osd-color-secondary box {
+ background-color: #42292e; }
+
+.osd-color-secondaryContainer {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+ .osd-color-secondaryContainer box {
+ background-color: #ffd9df; }
+
+.osd-color-surfaceVariant {
+ background-color: #3d3234;
+ color: #d6c1c4; }
+ .osd-color-surfaceVariant box {
+ background-color: #d6c1c4; }
+
+.osd-color-surface {
+ background-color: #221b1c;
+ color: #ecdfe0; }
+ .osd-color-surface box {
+ background-color: #ecdfe0; }
+
+.osd-color-background {
+ background-color: #110d0e;
+ color: #ecdfe0; }
+ .osd-color-background box {
+ background-color: #ecdfe0; }
+
+.osd-show {
+ transition: 200ms cubic-bezier(0.1, 1, 0, 1); }
+
+.osd-hide {
+ transition: 190ms cubic-bezier(0.85, 0, 0.15, 1); }
+
+.overview-window {
+ margin-top: 2.727rem; }
+
+.overview-search-box {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ min-width: 13.636rem;
+ min-height: 3.409rem;
+ padding: 0rem 1.364rem;
+ padding-right: 2.864rem;
+ background-color: #110d0e;
+ color: #ecdfe0;
+ caret-color: transparent; }
+ .overview-search-box selection {
+ background-color: #e4bcc3;
+ color: #42292e; }
+
+.overview-search-box-extended {
+ min-width: 25.909rem;
+ caret-color: #ffd9df; }
+
+.overview-search-prompt {
+ color: #aaa0a1; }
+
+.overview-search-icon {
+ margin: 0rem 1.023rem; }
+
+.overview-search-prompt-box {
+ margin-left: -18.545rem;
+ margin-right: 0.544rem; }
+
+.overview-search-icon-box {
+ margin-left: -18.545rem;
+ margin-right: 0.544rem; }
+
+.overview-search-results {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ min-width: 28.773rem;
+ padding: 0.682rem;
+ background-color: #110d0e;
+ color: #ecdfe0; }
+
+.overview-search-results-icon {
+ margin: 0rem 0.682rem;
+ font-size: 2.386rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem; }
+
+.overview-search-results-txt {
+ margin-right: 0.682rem; }
+
+.overview-search-results-txt-cmd {
+ margin-right: 0.682rem;
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font", "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace;
+ font-size: 1.227rem; }
+
+.overview-search-result-btn {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ padding: 0.341rem;
+ min-width: 2.386rem;
+ min-height: 2.386rem;
+ caret-color: transparent; }
+
+.overview-search-result-btn:hover,
+.overview-search-result-btn:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.overview-search-result-btn:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.overview-tasks {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ padding: 0.341rem;
+ background-color: #110d0e;
+ color: #ecdfe0; }
+
+.overview-tasks-workspace {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ margin: 0.341rem;
+ background-color: rgba(55, 47, 48, 0.1147); }
+
+.overview-tasks-workspace-number {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ color: rgba(222, 209, 210, 0.31); }
+
+.overview-tasks-window {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ background-color: rgba(61, 50, 52, 0.31);
+ color: #ffd9df;
+ border: 0.068rem solid rgba(255, 217, 223, 0.07); }
+
+.overview-tasks-window:hover,
+.overview-tasks-window:focus {
+ background-color: rgba(114, 82, 89, 0.525); }
+
+.overview-tasks-window:active {
+ background-color: rgba(133, 97, 105, 0.55); }
+
+.overview-tasks-window-selected {
+ background-color: rgba(133, 97, 105, 0.55); }
+
+.overview-tasks-window-dragging {
+ opacity: 0.2; }
+
+.osk-window {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ background-color: #110d0e; }
+
+.osk-body {
+ padding: 1.023rem;
+ padding-top: 0px; }
+
+.osk-show {
+ transition: 170ms cubic-bezier(0.1, 1, 0, 1); }
+
+.osk-hide {
+ margin-top: 30.682rem;
+ margin-bottom: -30.682rem;
+ transition: 100ms cubic-bezier(0.38, 0.04, 1, 0.07); }
+
+.osk-dragline {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ background-color: #3d3234;
+ min-height: 0.273rem;
+ min-width: 10.227rem;
+ margin-top: 0.545rem;
+ margin-bottom: 0.205rem; }
+
+.osk-key {
+ border-radius: 0.682rem;
+ background-color: rgba(61, 50, 52, 0.31);
+ color: #d6c1c4;
+ padding: 0.188rem;
+ font-weight: 500;
+ font-size: 1.091rem; }
+
+.osk-key:hover,
+.osk-key:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.osk-key:active {
+ background-color: rgba(128, 128, 128, 0.7);
+ font-size: 1.091rem; }
+
+.osk-key-active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.osk-key-normal {
+ min-width: 2.5rem;
+ min-height: 2.5rem; }
+
+.osk-key-fn {
+ min-width: 2.5125rem;
+ min-height: 1.25rem; }
+
+.osk-key-tab {
+ min-width: 4rem;
+ min-height: 2.5rem; }
+
+.osk-key-caps {
+ min-width: 4.75rem;
+ min-height: 2.5rem; }
+
+.osk-key-shift {
+ min-width: 6.25rem;
+ min-height: 2.5rem; }
+
+.osk-key-control {
+ min-width: 3.25rem;
+ min-height: 2.5rem; }
+
+.osk-control-button {
+ border-radius: 0.682rem;
+ background-color: rgba(61, 50, 52, 0.31);
+ color: #d6c1c4;
+ font-weight: 500;
+ font-size: 1.091rem;
+ padding: 0.682rem; }
+
+.osk-control-button:hover,
+.osk-control-button:focus {
+ background-color: rgba(76, 64, 66, 0.31); }
+
+.osk-control-button:active {
+ background-color: rgba(107, 93, 95, 0.31);
+ font-size: 1.091rem; }
+
+.osk-key-empty, .osk-key-empty:hover, .osk-key-empty:focus {
+ min-width: 2.5rem;
+ min-height: 2.5rem;
+ background-color: transparent; }
+
+.sidebar-right {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ border-radius: 1.297rem;
+ min-width: 27.818rem;
+ background-color: #110d0e;
+ padding: 1.023rem; }
+
+.sideright-show {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1); }
+
+.sideright-hide {
+ transition: 160ms cubic-bezier(0.38, 0.04, 1, 0.07);
+ margin-right: -30.682rem; }
+
+.sidebar-left {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ border-top: 1px solid rgba(171, 160, 161, 0.19);
+ border-left: 1px solid rgba(171, 160, 161, 0.19);
+ border-right: 1px solid rgba(135, 125, 126, 0.145);
+ border-bottom: 1px solid rgba(135, 125, 126, 0.145);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ border-radius: 1.297rem;
+ min-width: 27.818rem;
+ background-color: #110d0e;
+ padding: 1.023rem; }
+
+.sideleft-show {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1); }
+
+.sideleft-hide {
+ margin-left: -30.682rem;
+ transition: 160ms cubic-bezier(0.38, 0.04, 1, 0.07); }
+
+.sidebar-group {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ padding: 0.341rem;
+ background-color: rgba(34, 27, 28, 0.31); }
+
+.sidebar-group-nopad {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: rgba(34, 27, 28, 0.31); }
+
+.sidebar-group-invisible {
+ padding: 0.341rem; }
+
+.sidebar-group-invisible-morehorizpad {
+ padding: 0.341rem 0.682rem; }
+
+.sidebar-togglesbox {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ padding: 0.341rem;
+ background-color: rgba(34, 27, 28, 0.31); }
+
+.sidebar-iconbutton {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ color: #ecdfe0;
+ min-width: 2.727rem;
+ min-height: 2.727rem; }
+
+.sidebar-iconbutton:hover,
+.sidebar-iconbutton:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-iconbutton:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-button {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ padding: 0rem 0.818rem;
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.sidebar-button:hover,
+.sidebar-button:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-button:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-button-active {
+ background-color: #ffc8d5;
+ color: #66002a; }
+
+.sidebar-button-active:hover,
+.sidebar-button-active:focus {
+ background-color: rgba(246, 195, 207, 0.79); }
+
+.sidebar-button-active:active {
+ background-color: rgba(228, 185, 195, 0.58); }
+
+.sidebar-buttons-separator {
+ min-width: 0.068rem;
+ min-height: 0.068rem;
+ background-color: #d6c1c4; }
+
+.sidebar-navrail {
+ padding: 0rem 1.159rem; }
+
+.sidebar-navrail-btn > box > label {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1); }
+
+.sidebar-navrail-btn:hover > box > label:first-child,
+.sidebar-navrail-btn:focus > box > label:first-child {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-navrail-btn:active > box > label:first-child {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-navrail-btn-active > box > label:first-child {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.sidebar-navrail-btn-active:hover > box > label:first-child,
+.sidebar-navrail-btn-active:focus > box > label:first-child {
+ background-color: rgba(93, 64, 70, 0.93);
+ color: rgba(253, 215, 221, 0.93); }
+
+.sidebar-sysinfo-grouppad {
+ padding: 1.159rem; }
+
+.sidebar-memory-ram-circprog {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 0.818rem;
+ min-height: 4.091rem;
+ padding: 0.409rem;
+ background-color: #5c3f45;
+ color: #ffd9df;
+ font-size: 0px; }
+
+.sidebar-memory-swap-circprog {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 0.818rem;
+ min-height: 2.255rem;
+ padding: 0.409rem;
+ margin: 0.918rem;
+ background-color: #5c3f45;
+ color: #ffd9df;
+ font-size: 0px; }
+
+.sidebar-cpu-circprog {
+ min-width: 0.818rem;
+ min-height: 3.409rem;
+ padding: 0.409rem;
+ background-color: #5c3f45;
+ color: #ffd9df;
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ font-size: 0px; }
+
+.sidebar-scrollbar trough {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 0.545rem;
+ background-color: transparent; }
+
+.sidebar-scrollbar slider {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-width: 0.273rem;
+ min-height: 2.045rem;
+ background-color: rgba(214, 193, 196, 0.31); }
+
+.sidebar-scrollbar slider:hover,
+.sidebar-scrollbar slider:focus {
+ background-color: rgba(214, 193, 196, 0.448); }
+
+.sidebar-scrollbar slider:active {
+ background-color: #8a7a7c; }
+
+.sidebar-calendar-btn {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 2.523rem;
+ min-width: 2.523rem;
+ color: #ecdfe0; }
+
+.sidebar-calendar-btn:hover,
+.sidebar-calendar-btn:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-calendar-btn:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-calendar-btn-txt {
+ margin-left: -10.341rem;
+ margin-right: -10.341rem; }
+
+.sidebar-calendar-btn-today {
+ background-color: #ffc8d5;
+ color: #66002a; }
+
+.sidebar-calendar-btn-today:hover,
+.sidebar-calendar-btn-today:focus {
+ background-color: rgba(246, 195, 207, 0.79); }
+
+.sidebar-calendar-btn-today:active {
+ background-color: rgba(228, 185, 195, 0.58); }
+
+.sidebar-calendar-btn-othermonth {
+ color: #877d7e; }
+
+.sidebar-calendar-header {
+ margin: 0.341rem; }
+
+.sidebar-calendar-monthyear-btn {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ padding: 0rem 0.682rem;
+ background-color: rgba(61, 50, 52, 0.31);
+ color: #d6c1c4; }
+
+.sidebar-calendar-monthyear-btn:hover,
+.sidebar-calendar-monthyear-btn:focus {
+ background-color: rgba(128, 128, 128, 0.3);
+ color: #cebabd; }
+
+.sidebar-calendar-monthyear-btn:active {
+ background-color: rgba(128, 128, 128, 0.7);
+ color: #bfacae; }
+
+.sidebar-calendar-monthshift-btn {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ background-color: rgba(61, 50, 52, 0.31);
+ color: #d6c1c4; }
+
+.sidebar-calendar-monthshift-btn:hover {
+ background-color: rgba(128, 128, 128, 0.3);
+ color: #cebabd; }
+
+.sidebar-calendar-monthshift-btn:active {
+ background-color: rgba(128, 128, 128, 0.7);
+ color: #bfacae; }
+
+.sidebar-selector-tab {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 2.5rem;
+ color: #ecdfe0; }
+
+.sidebar-selector-tab:hover,
+.sidebar-selector-tab:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-selector-tab:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-selector-tab-active > box > label {
+ color: #ffc8d5; }
+
+.sidebar-selector-highlight-offset {
+ margin-top: -0.205rem;
+ margin-bottom: 0.205rem; }
+
+.sidebar-selector-highlight {
+ transition: 180ms ease-in-out;
+ color: #ffc8d5;
+ min-height: 0.205rem; }
+
+.sidebar-todo-item {
+ padding-right: 0.545rem; }
+
+.sidebar-todo-item-even {
+ background-color: rgba(61, 50, 52, 0.1); }
+
+.sidebar-todo-item-action {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ min-width: 1.705rem;
+ min-height: 1.705rem; }
+
+.sidebar-todo-item-action:hover,
+.sidebar-todo-item-action:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-todo-item-action:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-todo-crosser {
+ transition: margin 200ms cubic-bezier(0.1, 1, 0, 1), background-color 0ms;
+ min-width: 0rem; }
+
+.sidebar-todo-crosser-crossed {
+ background-color: #ecdfe0; }
+
+.sidebar-todo-crosser-removed {
+ background-color: #ffb4a9; }
+
+.sidebar-todo-new {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ color: #ffd9df;
+ margin: 0.341rem;
+ padding: 0.205rem 0.545rem;
+ border: 0.068rem solid #ecdfe0; }
+
+.sidebar-todo-new,
+.sidebar-todo-new:focus {
+ background-color: #61444a; }
+
+.sidebar-todo-new:active {
+ background-color: #7d5e64; }
+
+.sidebar-todo-add {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ color: #ffd9df;
+ border: 0.068rem solid #ecdfe0; }
+
+.sidebar-todo-add:hover,
+.sidebar-todo-add:focus {
+ background-color: #61444a; }
+
+.sidebar-todo-add:active {
+ background-color: #7d5e64; }
+
+.sidebar-todo-add-available {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ background-color: #ffc8d5;
+ color: #66002a;
+ border: 0.068rem solid #ffc8d5; }
+
+.sidebar-todo-add-available:hover,
+.sidebar-todo-add-available:focus {
+ background-color: #fac2d0; }
+
+.sidebar-todo-add-available:active {
+ background-color: #e0a0b3; }
+
+.sidebar-todo-entry {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ background-color: #3d3234;
+ color: #d6c1c4;
+ caret-color: #d6c1c4;
+ margin: 0rem 0.341rem;
+ min-height: 1.773rem;
+ min-width: 0rem;
+ padding: 0.205rem 0.682rem;
+ border: 0.068rem solid #8a7a7c; }
+
+.sidebar-todo-entry:focus {
+ border: 0.068rem solid #c7b3b6; }
+
+.sidebar-module {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ padding: 0.341rem;
+ background-color: rgba(61, 50, 52, 0.45);
+ padding: 0.682rem; }
+
+.sidebar-module-btn-arrow {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp";
+ background-color: rgba(61, 50, 52, 0.45);
+ min-width: 1.705rem;
+ min-height: 1.705rem; }
+ .sidebar-module-btn-arrow:hover {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-module-scripts-button {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp";
+ background-color: rgba(61, 50, 52, 0.45);
+ min-width: 1.705rem;
+ min-height: 1.705rem; }
+ .sidebar-module-scripts-button:hover {
+ background-color: rgba(128, 128, 128, 0.3); }
+ .sidebar-module-scripts-button:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-module-colorpicker-wrapper {
+ padding: 0.341rem; }
+
+.sidebar-module-colorpicker-cursorwrapper {
+ padding: 0.341rem 0.136rem; }
+
+.sidebar-module-colorpicker-hue {
+ min-height: 13.636rem;
+ min-width: 1.091rem;
+ border-radius: 0.341rem; }
+
+.sidebar-module-colorpicker-hue-cursor {
+ background-color: #ecdfe0;
+ border: 0.136rem solid #ecdfe0;
+ min-height: 0.136rem;
+ margin-top: -0.136rem;
+ border-radius: 0.341rem; }
+
+.sidebar-module-colorpicker-saturationandlightness-wrapper {
+ padding: 0.341rem; }
+
+.sidebar-module-colorpicker-saturationandlightness {
+ min-height: 13.636rem;
+ min-width: 13.636rem;
+ border-radius: 0.341rem; }
+
+.sidebar-module-colorpicker-saturationandlightness-cursorwrapper {
+ padding: 0.341rem;
+ margin-top: -0.409rem;
+ margin-left: -0.409rem; }
+
+.sidebar-module-colorpicker-saturationandlightness-cursor {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ border: 0.136rem solid white;
+ min-width: 0.682rem;
+ min-height: 0.682rem;
+ margin-top: -0.409rem;
+ margin-left: -0.409rem; }
+
+.sidebar-module-colorpicker-result-area {
+ padding: 0.341rem; }
+
+.sidebar-module-colorpicker-result-box {
+ border-radius: 0.341rem;
+ min-width: 2.045rem;
+ min-height: 0.682rem;
+ padding: 0.341rem; }
+
+.sidebar-chat-apiswitcher {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ padding: 0.341rem;
+ background-color: rgba(34, 27, 28, 0.31); }
+
+.sidebar-chat-apiswitcher-icon {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 2.182rem;
+ min-height: 2.182rem;
+ font-size: 1.406rem;
+ color: #ecdfe0; }
+
+.sidebar-chat-apiswitcher-icon-enabled {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.sidebar-chat-viewport {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ padding: 0.682rem 0rem; }
+
+.sidebar-chat-textarea {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: #272021;
+ color: #d6c1c4;
+ padding: 0.682rem; }
+
+.sidebar-chat-entry {
+ color: #d6c1c4;
+ caret-color: #d6c1c4;
+ min-height: 1.773rem;
+ min-width: 0rem; }
+
+.sidebar-chat-wrapper {
+ transition: 400ms cubic-bezier(0.1, 1, 0, 1); }
+
+.sidebar-chat-wrapper-extended {
+ min-height: 7.500rem; }
+
+.sidebar-chat-send {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-width: 1.705rem;
+ min-height: 1.705rem;
+ border-radius: 0.478rem; }
+
+.sidebar-chat-send:hover,
+.sidebar-chat-send:focus {
+ background-color: #3d3234; }
+
+.sidebar-chat-send:active {
+ background-color: #605556; }
+
+.sidebar-chat-send-available {
+ background-color: #ffc8d5;
+ color: #66002a; }
+
+.sidebar-chat-send-available:hover,
+.sidebar-chat-send-available:focus {
+ background-color: #fac2d0; }
+
+.sidebar-chat-send-available:active {
+ background-color: #e0a0b3; }
+
+.sidebar-chat-message {
+ margin: 0.682rem; }
+
+.sidebar-chat-indicator {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 0.136rem;
+ background-color: #ecdfe0; }
+
+.sidebar-chat-indicator-user {
+ background-color: #ecdfe0; }
+
+.sidebar-chat-indicator-bot {
+ background-color: #ffc8d5; }
+
+.sidebar-chat-indicator-System {
+ background-color: #e4bcc3; }
+
+.sidebar-chat-name {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ padding: 0.341rem;
+ margin-left: -0.136rem;
+ padding-left: 0.818rem; }
+
+.sidebar-chat-txtblock {
+ margin-left: -0.136rem;
+ padding: 0.341rem;
+ padding-left: 0.818rem; }
+
+.sidebar-chat-txt {
+ font-family: "Lexend", "Noto Sans", sans-serif; }
+
+.sidebar-chat-codeblock {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: rgba(92, 79, 81, 0.31);
+ color: #d6c1c4;
+ margin: 0rem 0.682rem;
+ border: 0.068rem solid rgba(255, 217, 223, 0.07); }
+
+.sidebar-chat-codeblock-topbar {
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ margin: 0.273rem;
+ margin-bottom: 0rem;
+ background-color: rgba(129, 102, 107, 0.517);
+ color: #ffd9df;
+ border-radius: 0.886rem;
+ border: 0.068rem solid #6c4e54;
+ border-top-left-radius: 0.886rem;
+ border-top-right-radius: 0.886rem;
+ padding: 0.341rem 0.477rem; }
+
+.sidebar-chat-codeblock-topbar-txt {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ padding: 0.273rem; }
+
+.sidebar-chat-codeblock-topbar-btn {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ padding: 0.273rem 0.477rem; }
+
+.sidebar-chat-codeblock-topbar-btn:hover,
+.sidebar-chat-codeblock-topbar-btn:focus {
+ background-color: #7d5e64; }
+
+.sidebar-chat-codeblock-topbar-btn:active {
+ background-color: #9d7d83; }
+
+.sidebar-chat-codeblock-code {
+ font-family: "JetBrains Mono NF", "JetBrains Mono Nerd Font", "JetBrains Mono NL", "SpaceMono NF", "SpaceMono Nerd Font", monospace;
+ padding: 0.682rem; }
+
+.sidebar-chat-divider {
+ min-height: 1px;
+ background-color: #7a6b6e;
+ margin: 0rem 0.545rem; }
+
+.sidebar-chat-welcome-txt {
+ margin: 0rem 3.409rem; }
+
+.sidebar-chat-settings-toggles {
+ margin: 0rem 5.455rem; }
+
+.sidebar-chat-welcome-icon {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ font-size: 4rem; }
+
+.sidebar-chat-welcome-logo {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp";
+ min-height: 4.773rem;
+ min-width: 4.773rem;
+ font-size: 3.076rem;
+ background-color: #ecdfe0;
+ color: #110d0e; }
+
+.sidebar-chat-chip {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ padding: 0.341rem 0.477rem; }
+
+.sidebar-chat-chip-action {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ background-color: #272021;
+ color: #d6c1c4; }
+
+.sidebar-chat-chip-action:hover,
+.sidebar-chat-chip-action:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-chat-chip-action:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-chat-chip-action-active {
+ color: #7a6b6e;
+ border: 0.068rem solid #7a6b6e; }
+
+.sidebar-chat-chip-toggle {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ padding: 0.341rem 0.477rem;
+ background-color: #272021;
+ color: #d6c1c4; }
+
+.sidebar-chat-chip-toggle:focus,
+.sidebar-chat-chip-toggle:hover {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-chat-chip-toggle:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-pin {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ min-height: 2.386rem;
+ min-width: 2.386rem;
+ color: #ecdfe0; }
+
+.sidebar-pin:hover,
+.sidebar-pin:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.sidebar-pin:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.sidebar-pin-enabled {
+ background-color: #ffc8d5; }
+ .sidebar-pin-enabled label {
+ color: #66002a; }
+
+.sidebar-pin-enabled:hover,
+.sidebar-pin-enabled:focus {
+ background-color: #f0b4c4; }
+
+.sidebar-pin-enabled:active {
+ background-color: #e0a0b3; }
+
+.sidebar-waifu-heading {
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ padding: 0.341rem;
+ margin-left: -0.136rem;
+ padding-left: 0.818rem; }
+
+.sidebar-waifu-content {
+ margin-left: 0.682rem; }
+
+.sidebar-waifu-txt {
+ font-family: "Lexend", "Noto Sans", sans-serif;
+ margin-left: 0.682rem; }
+
+.sidebar-waifu-image {
+ margin-left: 0.682rem;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center; }
+
+.sidebar-waifu-image-actions {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ padding: 0.313rem; }
+
+.sidebar-waifu-image-action {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 1.875rem;
+ min-height: 1.875rem;
+ background-color: rgba(0, 0, 0, 0.7);
+ color: rgba(255, 255, 255, 0.7); }
+
+.sidebar-waifu-image-action:hover,
+.sidebar-waifu-image-action:focus {
+ background-color: rgba(30, 30, 30, 0.7); }
+
+.sidebar-waifu-image-action:active {
+ background-color: rgba(60, 60, 60, 0.7); }
+
+.session-bg {
+ margin-top: -2.727rem;
+ background-color: rgba(17, 13, 14, 0.64); }
+
+.session-button {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ min-width: 8.182rem;
+ min-height: 8.182rem;
+ background-color: #3d3234;
+ color: #d6c1c4;
+ font-size: 3rem; }
+
+.session-button-focused {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.session-button-desc {
+ background-color: #302728;
+ color: #e1d0d2;
+ border-bottom-left-radius: 1.705rem;
+ border-bottom-right-radius: 1.705rem;
+ padding: 0.205rem 0.341rem;
+ font-weight: 700; }
+
+.session-button-cancel {
+ border-radius: 1.705rem;
+ -gtk-outline-radius: 1.705rem;
+ min-width: 8.182rem;
+ min-height: 5.455rem;
+ background-color: #3d3234;
+ color: #d6c1c4;
+ font-size: 3rem; }
+
+.notif-low {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: rgba(61, 50, 52, 0.45);
+ color: #d6c1c4;
+ padding: 0.818rem;
+ padding-right: 1.363rem; }
+
+.notif-normal {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: rgba(61, 50, 52, 0.45);
+ color: #d6c1c4;
+ padding: 0.818rem;
+ padding-right: 1.363rem; }
+
+.notif-critical {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: #5c3f45;
+ color: #ffd9df;
+ padding: 0.818rem;
+ padding-right: 1.363rem; }
+
+.notif-clicked-low {
+ background-color: rgba(79, 67, 69, 0.429); }
+
+.notif-clicked-normal {
+ background-color: rgba(79, 67, 69, 0.429); }
+
+.notif-clicked-critical {
+ background-color: #64474d; }
+
+.popup-notif-low {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ min-width: 30.682rem;
+ background-color: #110d0e;
+ color: #d6c1c4;
+ padding: 0.818rem;
+ padding-right: 1.363rem; }
+
+.popup-notif-normal {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ min-width: 30.682rem;
+ background-color: #110d0e;
+ color: #d6c1c4;
+ padding: 0.818rem;
+ padding-right: 1.363rem; }
+
+.popup-notif-critical {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ min-width: 30.682rem;
+ background-color: #5c3f45;
+ color: #ffd9df;
+ padding: 0.818rem;
+ padding-right: 1.363rem; }
+
+.popup-notif-clicked-low {
+ background-color: #1e1a1b; }
+
+.popup-notif-clicked-normal {
+ background-color: #1e1a1b; }
+
+.popup-notif-clicked-critical {
+ background-color: #63454b; }
+
+.notif-body-low {
+ color: #a49294; }
+
+.notif-body-normal {
+ color: #a49294; }
+
+.notif-body-critical {
+ color: #c9a6ac; }
+
+.notif-icon {
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ min-width: 3.409rem;
+ min-height: 3.409rem;
+ font-size: 3.409rem; }
+
+.notif-icon-material {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.notif-icon-material-low {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.notif-icon-material-normal {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.notif-icon-material-critical {
+ background-color: #ffd9df;
+ color: #5c3f45; }
+
+.notif-expand-btn {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ min-width: 1.841rem;
+ min-height: 1.841rem; }
+
+.notif-expand-btn:hover,
+.notif-expand-btn:focus {
+ background: rgba(128, 128, 128, 0.3); }
+
+.notif-expand-btn:active {
+ background: rgba(128, 128, 128, 0.7); }
+
+.notif-listaction-btn {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ padding: 0.341rem 0.682rem; }
+
+.notif-listaction-btn:hover,
+.notif-listaction-btn:focus {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.notif-listaction-btn:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.notif-listaction-btn-enabled {
+ background-color: #5c3f45;
+ color: #ffd9df; }
+
+.notif-listaction-btn-enabled:hover,
+.notif-listaction-btn-enabled:focus {
+ background-color: #6c4e54; }
+
+.notif-listaction-btn-enabled:active {
+ background-color: #85666c; }
+
+.osd-notif {
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ background-color: rgba(17, 13, 14, 0.46);
+ min-width: 30.682rem; }
+
+.notif-circprog-low {
+ transition: 0ms linear;
+ min-width: 0.136rem;
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: #ffd9df; }
+
+.notif-circprog-normal {
+ transition: 0ms linear;
+ min-width: 0.136rem;
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: #ffd9df; }
+
+.notif-circprog-critical {
+ transition: 0ms linear;
+ min-width: 0.136rem;
+ min-height: 3.136rem;
+ padding: 0rem;
+ color: #5c3f45; }
+
+.notif-actions {
+ min-height: 2.045rem; }
+
+.notif-action {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem; }
+
+.notif-action-low {
+ background-color: rgba(52, 44, 45, 0.31);
+ color: #d6c1c4; }
+
+.notif-action-low:focus,
+.notif-action-low:hover {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.notif-action-low:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.notif-action-normal {
+ background-color: rgba(52, 44, 45, 0.31);
+ color: #d6c1c4; }
+
+.notif-action-normal:focus,
+.notif-action-normal:hover {
+ background-color: rgba(128, 128, 128, 0.3); }
+
+.notif-action-normal:active {
+ background-color: rgba(128, 128, 128, 0.7); }
+
+.notif-action-critical {
+ background-color: #6c4e54;
+ color: #d6c1c4; }
+
+.notif-action-critical:focus,
+.notif-action-critical:hover {
+ background-color: #795b61; }
+
+.notif-action-critical:active {
+ background-color: #816268; }
+
+@keyframes flyin-top {
+ from {
+ margin-top: -2.795rem; }
+ to {
+ margin-top: 0rem; } }
+
+@keyframes flyin-bottom {
+ from {
+ margin-top: 4.841rem;
+ margin-bottom: -4.841rem; }
+ to {
+ margin-bottom: 0rem;
+ margin-top: 0rem; } }
+
+.osd-music {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.45);
+ margin: 0.476rem;
+ margin-top: 0.313rem;
+ border-radius: 1.159rem;
+ -gtk-outline-radius: 1.159rem;
+ min-width: 29.659rem;
+ background-color: #110d0e;
+ padding: 0rem 1.023rem;
+ background: linear-gradient(127deg, rgba(49, 40, 42, 0.7), rgba(49, 40, 42, 0.55) 70.71%), linear-gradient(217deg, rgba(81, 67, 69, 0.7), rgba(81, 67, 69, 0.55) 70.71%), radial-gradient(circle at 0% 100%, #5c3f45 13%, rgba(0, 0, 0, 0) 100%), linear-gradient(336deg, rgba(92, 63, 69, 0.7), rgba(92, 63, 69, 0.55) 70.71%), linear-gradient(#110d0e, #110d0e); }
+
+.osd-music-cover-fallback {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+ background-color: rgba(34, 27, 28, 0.31);
+ color: #ddcfd1; }
+
+.osd-music-cover {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ margin-top: 1.023rem;
+ margin-bottom: 1.023rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem; }
+
+.osd-music-cover-art {
+ border-radius: 0.818rem;
+ -gtk-outline-radius: 0.818rem;
+ min-width: 7.5rem;
+ min-height: 7.5rem;
+ background-size: cover;
+ background-position: center; }
+
+.osd-music-info {
+ margin: 1.023rem 0rem; }
+
+.osd-music-title {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ font-size: 1.364rem;
+ color: #ddcfd1; }
+
+.osd-music-artists {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif;
+ font-size: 0.955rem;
+ color: rgba(208, 195, 197, 0.9); }
+
+.osd-music-pill {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ min-width: 1.833rem;
+ padding: 0.273rem 0.682rem;
+ background-color: rgba(58, 45, 47, 0.5);
+ color: #ddcfd1; }
+
+.osd-music-controls {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px;
+ font-family: "Gabarito", "Poppins", "Lexend", sans-serif;
+ min-width: 1.833rem;
+ padding: 0.205rem;
+ background-color: rgba(58, 45, 47, 0.5);
+ color: #ddcfd1; }
+
+.osd-music-controlbtn {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 2.045rem;
+ min-height: 2.045rem;
+ border-radius: 9999px;
+ -gtk-outline-radius: 9999px; }
+
+.osd-music-controlbtn:hover,
+.osd-music-controlbtn:focus {
+ background-color: rgba(99, 86, 88, 0.55); }
+
+.osd-music-controlbtn:active {
+ background-color: rgba(114, 101, 103, 0.575); }
+
+.osd-music-controlbtn-txt {
+ transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
+ transition: 100ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp";
+ font-size: 1.364rem;
+ margin: -0.1rem 0rem; }
+
+.osd-music-circprog {
+ transition: 1000ms cubic-bezier(0.1, 1, 0, 1);
+ min-width: 0.409rem;
+ min-height: 3.068rem;
+ padding: 0.273rem;
+ color: #ddcfd1; }
+
+.osd-music-playstate {
+ transition: 300ms cubic-bezier(0.1, 1, 0, 1);
+ min-height: 3.068rem;
+ min-width: 3.068rem;
+ border-radius: 10rem;
+ background-color: rgba(58, 45, 47, 0.5);
+ color: #ddcfd1; }
+
+.osd-music-playstate-btn > label {
+ transition: 50ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp";
+ font-size: 1.364rem;
+ margin: -0.1rem 0rem; }
+
+.growingRadial {
+ transition: 300ms cubic-bezier(0.2, 0, 0, 1); }
+
+.fadingRadial {
+ transition: 50ms cubic-bezier(0.2, 0, 0, 1); }
+
+.sidebar-pinned {
+ margin: 0rem;
+ border-radius: 0rem;
+ border-bottom-right-radius: 1.705rem;
+ border: 0rem solid; }
diff --git a/modules/styling/config/variables.js b/modules/styling/config/variables.js
new file mode 100644
index 0000000..5a07dcb
--- /dev/null
+++ b/modules/styling/config/variables.js
@@ -0,0 +1,7 @@
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+
+// AGS Variables
+export const showMusicControls = Variable(false, {})
+export const showColorScheme = Variable(false, {})
+globalThis['openMusicControls'] = showMusicControls;
+globalThis['openColorScheme'] = showColorScheme;
diff --git a/modules/styling/config/widgets/bar/main.js b/modules/styling/config/widgets/bar/main.js
new file mode 100644
index 0000000..0ee948e
--- /dev/null
+++ b/modules/styling/config/widgets/bar/main.js
@@ -0,0 +1,75 @@
+const { Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+import WindowTitle from "./spaceleft.js";
+import Indicators from "./spaceright.js";
+import Music from "./music.js";
+import System from "./system.js";
+import { RoundedCorner, enableClickthrough } from "../../lib/roundedcorner.js";
+
+const OptionalWorkspaces = async () => {
+ try {
+ return (await import('./workspaces_hyprland.js')).default();
+ } catch {
+ try {
+ return (await import('./workspaces_sway.js')).default();
+ } catch {
+ return null;
+ }
+ }
+};
+
+export const Bar = async (monitor = 0) => {
+ const SideModule = (children) => Widget.Box({
+ className: 'bar-sidemodule',
+ children: children,
+ });
+ const barContent = Widget.CenterBox({
+ className: 'bar-bg',
+ setup: (self) => {
+ const styleContext = self.get_style_context();
+ const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ // execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print);
+ },
+ startWidget: WindowTitle(),
+ centerWidget: Widget.Box({
+ className: 'spacing-h-4',
+ children: [
+ SideModule([Music()]),
+ Widget.Box({
+ homogeneous: true,
+ children: [await OptionalWorkspaces()],
+ }),
+ SideModule([System()]),
+ ]
+ }),
+ endWidget: Indicators(),
+ });
+ return Widget.Window({
+ monitor,
+ name: `bar${monitor}`,
+ anchor: ['top', 'left', 'right'],
+ exclusivity: 'exclusive',
+ visible: true,
+ child: barContent,
+ });
+}
+
+export const BarCornerTopleft = (id = '') => Widget.Window({
+ name: `barcornertl${id}`,
+ layer: 'top',
+ anchor: ['top', 'left'],
+ exclusivity: 'normal',
+ visible: true,
+ child: RoundedCorner('topleft', { className: 'corner', }),
+ setup: enableClickthrough,
+});
+export const BarCornerTopright = (id = '') => Widget.Window({
+ name: `barcornertr${id}`,
+ layer: 'top',
+ anchor: ['top', 'right'],
+ exclusivity: 'normal',
+ visible: true,
+ child: RoundedCorner('topright', { className: 'corner', }),
+ setup: enableClickthrough,
+});
\ No newline at end of file
diff --git a/modules/styling/config/widgets/bar/music.js b/modules/styling/config/widgets/bar/music.js
new file mode 100644
index 0000000..03bdbeb
--- /dev/null
+++ b/modules/styling/config/widgets/bar/music.js
@@ -0,0 +1,182 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
+const { Box, Label, Overlay, Revealer } = Widget;
+const { execAsync, exec } = Utils;
+import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
+import { MaterialIcon } from '../../lib/materialicon.js';
+import { showMusicControls } from '../../variables.js';
+
+function trimTrackTitle(title) {
+ if (!title) return '';
+ const cleanRegexes = [
+ /【[^】]*】/, // Touhou n weeb stuff
+ /\[FREE DOWNLOAD\]/, // F-777
+ ];
+ cleanRegexes.forEach((expr) => title.replace(expr, ''));
+ return title;
+}
+
+const BarGroup = ({ child }) => Widget.Box({
+ className: 'bar-group-margin bar-sides',
+ children: [
+ Widget.Box({
+ className: 'bar-group bar-group-standalone bar-group-pad-system',
+ children: [child],
+ }),
+ ]
+});
+
+const BarResource = (name, icon, command) => {
+ const resourceCircProg = AnimatedCircProg({
+ className: 'bar-batt-circprog',
+ vpack: 'center',
+ hpack: 'center',
+ });
+ const resourceProgress = Overlay({
+ child: Widget.Box({
+ vpack: 'center',
+ className: 'bar-batt',
+ homogeneous: true,
+ children: [
+ MaterialIcon(icon, 'small'),
+ ],
+ }),
+ overlays: [resourceCircProg]
+ });
+ const resourceLabel = Label({
+ className: 'txt-smallie txt-onSurfaceVariant',
+ });
+ const widget = Box({
+ className: 'spacing-h-4 txt-onSurfaceVariant',
+ children: [
+ resourceLabel,
+ resourceProgress,
+ ],
+ setup: (self) => self
+ .poll(5000, () => execAsync(['bash', '-c', command])
+ .then((output) => {
+ resourceCircProg.css = `font-size: ${Number(output)}px;`;
+ resourceLabel.label = `${Math.round(Number(output))}%`;
+ widget.tooltipText = `${name}: ${Math.round(Number(output))}%`;
+ }).catch(print))
+ ,
+ });
+ return widget;
+}
+
+const TrackProgress = () => {
+ const _updateProgress = (circprog) => {
+ const mpris = Mpris.getPlayer('');
+ if (!mpris) return;
+ // Set circular progress value
+ circprog.css = `font-size: ${Math.max(mpris.position / mpris.length * 100, 0)}px;`
+ }
+ return AnimatedCircProg({
+ className: 'bar-music-circprog',
+ vpack: 'center', hpack: 'center',
+ extraSetup: (self) => self
+ .hook(Mpris, _updateProgress)
+ .poll(3000, _updateProgress)
+ ,
+ })
+}
+
+const switchToRelativeWorkspace = async (self, num) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.sendMessage(`dispatch workspace ${num > 0 ? '+' : ''}${num}`);
+ } catch {
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+ }
+}
+
+export default () => {
+ // TODO: use cairo to make button bounce smaller on click, if that's possible
+ const playingState = Widget.Box({ // Wrap a box cuz overlay can't have margins itself
+ homogeneous: true,
+ children: [Widget.Overlay({
+ child: Widget.Box({
+ vpack: 'center',
+ className: 'bar-music-playstate',
+ homogeneous: true,
+ children: [Widget.Label({
+ vpack: 'center',
+ className: 'bar-music-playstate-txt',
+ justification: 'center',
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
+ }),
+ })],
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ if (!mpris) return;
+ label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
+ label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
+ }),
+ }),
+ overlays: [
+ TrackProgress(),
+ ]
+ })]
+ });
+ const trackTitle = Widget.Scrollable({
+ hexpand: true,
+ child: Widget.Label({
+ className: 'txt-smallie txt-onSurfaceVariant',
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ if (mpris)
+ label.label = `${trimTrackTitle(mpris.trackTitle)} • ${mpris.trackArtists.join(', ')}`;
+ else
+ label.label = 'No media';
+ }),
+ })
+ })
+ const musicStuff = Box({
+ className: 'spacing-h-10',
+ hexpand: true,
+ children: [
+ playingState,
+ trackTitle,
+ ]
+ })
+ const systemResources = BarGroup({
+ child: Box({
+ children: [
+ BarResource('RAM Usage', 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
+ Revealer({
+ revealChild: true,
+ transition: 'slide_left',
+ transitionDuration: 200,
+ child: Box({
+ className: 'spacing-h-10 margin-left-10',
+ children: [
+ BarResource('Swap Usage', 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`),
+ BarResource('CPU Usage', 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`),
+ ]
+ }),
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ self.revealChild = (!mpris);
+ }),
+ })
+ ],
+ })
+ });
+ return Widget.EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value),
+ onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']),
+ onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ BarGroup({ child: musicStuff }),
+ systemResources,
+ ]
+ })
+ });
+}
diff --git a/modules/styling/config/widgets/bar/spaceleft.js b/modules/styling/config/widgets/bar/spaceleft.js
new file mode 100644
index 0000000..4f2b854
--- /dev/null
+++ b/modules/styling/config/widgets/bar/spaceleft.js
@@ -0,0 +1,72 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Brightness from '../../services/brightness.js';
+import Indicator from '../../services/indicator.js';
+
+const WindowTitle = async () => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ return Widget.Scrollable({
+ hexpand: true, vexpand: true,
+ hscroll: 'automatic', vscroll: 'never',
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Label({
+ xalign: 0,
+ className: 'txt-smaller bar-topdesc txt',
+ setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
+ label.label = Hyprland.active.client.class.length === 0 ? 'Desktop' : Hyprland.active.client.class;
+ }),
+ }),
+ Widget.Label({
+ xalign: 0,
+ className: 'txt txt-smallie',
+ setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
+ label.label = Hyprland.active.client.title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client.title;
+ }),
+ })
+ ]
+ })
+ });
+ } catch {
+ return null;
+ }
+}
+
+const OptionalWindowTitleInstance = await WindowTitle();
+
+export default () => Widget.EventBox({
+ onScrollUp: () => {
+ Indicator.popup(1); // Since the brightness and speaker are both on the same window
+ Brightness.screen_value += 0.05;
+ },
+ onScrollDown: () => {
+ Indicator.popup(1); // Since the brightness and speaker are both on the same window
+ Brightness.screen_value -= 0.05;
+ },
+ onPrimaryClick: () => {
+ App.toggleWindow('sideleft');
+ },
+ child: Widget.Box({
+ homogeneous: false,
+ children: [
+ Widget.Box({ className: 'bar-corner-spacing' }),
+ Widget.Overlay({
+ overlays: [
+ Widget.Box({ hexpand: true }),
+ Widget.Box({
+ className: 'bar-sidemodule', hexpand: true,
+ children: [Widget.Box({
+ vertical: true,
+ className: 'bar-space-button',
+ children: [
+ OptionalWindowTitleInstance,
+ ]
+ })]
+ }),
+ ]
+ })
+ ]
+ })
+});
\ No newline at end of file
diff --git a/modules/styling/config/widgets/bar/spaceright.js b/modules/styling/config/widgets/bar/spaceright.js
new file mode 100644
index 0000000..2c5c9bd
--- /dev/null
+++ b/modules/styling/config/widgets/bar/spaceright.js
@@ -0,0 +1,82 @@
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
+import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
+const { execAsync } = Utils;
+import Indicator from '../../services/indicator.js';
+import { StatusIcons } from "../../lib/statusicons.js";
+import { Tray } from "./tray.js";
+
+export default () => {
+ const barTray = Tray();
+ const separatorDot = Widget.Revealer({
+ transition: 'slide_left',
+ revealChild: false,
+ attribute: {
+ 'count': SystemTray.items.length,
+ 'update': (self, diff) => {
+ self.attribute.count += diff;
+ self.revealChild = (self.attribute.count > 0);
+ }
+ },
+ child: Widget.Box({
+ vpack: 'center',
+ className: 'separator-circle',
+ }),
+ setup: (self) => self
+ .hook(SystemTray, (self) => self.attribute.update(self, 1), 'added')
+ .hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
+ ,
+ });
+ const barStatusIcons = StatusIcons({
+ className: 'bar-statusicons',
+ setup: (self) => self.hook(App, (self, currentName, visible) => {
+ if (currentName === 'sideright') {
+ self.toggleClassName('bar-statusicons-active', visible);
+ }
+ }),
+ });
+ const actualContent = Widget.Box({
+ hexpand: true,
+ className: 'spacing-h-5 txt',
+ children: [
+ Widget.Box({
+ hexpand: true,
+ className: 'spacing-h-5 txt',
+ children: [
+ Widget.Box({ hexpand: true, }),
+ barTray,
+ separatorDot,
+ barStatusIcons,
+ ],
+ }),
+ ]
+ });
+
+ return Widget.EventBox({
+ onScrollUp: () => {
+ if (!Audio.speaker) return;
+ Audio.speaker.volume += 0.03;
+ Indicator.popup(1);
+ },
+ onScrollDown: () => {
+ if (!Audio.speaker) return;
+ Audio.speaker.volume -= 0.03;
+ Indicator.popup(1);
+ },
+ onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
+ onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
+ onPrimaryClick: () => App.toggleWindow('sideright'),
+ onSecondaryClickRelease: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
+ onMiddleClickRelease: () => execAsync('playerctl play-pause').catch(print),
+ child: Widget.Box({
+ homogeneous: false,
+ children: [
+ actualContent,
+ Widget.Box({ className: 'bar-corner-spacing' }),
+ ]
+ })
+ });
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/bar/system.js b/modules/styling/config/widgets/bar/system.js
new file mode 100644
index 0000000..6318907
--- /dev/null
+++ b/modules/styling/config/widgets/bar/system.js
@@ -0,0 +1,235 @@
+// This is for the right pills of the bar.
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
+const { exec, execAsync } = Utils;
+const { GLib } = imports.gi;
+import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
+import { MaterialIcon } from '../../lib/materialicon.js';
+import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
+import { WWO_CODE, WEATHER_SYMBOL, NIGHT_WEATHER_SYMBOL } from '../../data/weather.js';
+
+const BATTERY_LOW = 20;
+const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`;
+Utils.exec(`mkdir -p ${WEATHER_CACHE_FOLDER}`);
+
+let WEATHER_CITY = '';
+try {
+ WEATHER_CITY = GLib.getenv('AGS_WEATHER_CITY');
+} catch (e) {
+ print(e);
+}
+
+const BatBatteryProgress = () => {
+ const _updateProgress = (circprog) => { // Set circular progress value
+ circprog.css = `font-size: ${Math.abs(Battery.percent)}px;`
+
+ circprog.toggleClassName('bar-batt-circprog-low', Battery.percent <= BATTERY_LOW);
+ circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
+ }
+ return AnimatedCircProg({
+ className: 'bar-batt-circprog',
+ vpack: 'center', hpack: 'center',
+ extraSetup: (self) => self
+ .hook(Battery, _updateProgress)
+ ,
+ })
+}
+
+const BarClock = () => Widget.Box({
+ vpack: 'center',
+ className: 'spacing-h-5 txt-onSurfaceVariant bar-clock-box',
+ children: [
+ Widget.Label({
+ className: 'bar-clock',
+ label: GLib.DateTime.new_now_local().format("%H:%M"),
+ setup: (self) => self.poll(5000, label => {
+ label.label = GLib.DateTime.new_now_local().format("%H:%M");
+ }),
+ }),
+ Widget.Label({
+ className: 'txt-norm',
+ label: '•',
+ }),
+ Widget.Label({
+ className: 'txt-smallie',
+ label: GLib.DateTime.new_now_local().format("%A, %d/%m"),
+ setup: (self) => self.poll(5000, label => {
+ label.label = GLib.DateTime.new_now_local().format("%A, %d/%m");
+ }),
+ }),
+ ],
+});
+
+const UtilButton = ({ name, icon, onClicked }) => Button({
+ vpack: 'center',
+ tooltipText: name,
+ onClicked: onClicked,
+ className: 'bar-util-btn icon-material txt-norm',
+ label: `${icon}`,
+})
+
+const Utilities = () => Box({
+ hpack: 'center',
+ className: 'spacing-h-5 txt-onSurfaceVariant',
+ children: [
+ UtilButton({
+ name: 'Screen snip', icon: 'screenshot_region', onClicked: () => {
+ Utils.execAsync(['bash', '-c', `grim -g "$(slurp -d -c e2e2e2BB -b 31313122 -s 00000000)" - | wl-copy &`])
+ .catch(print)
+ }
+ }),
+ UtilButton({
+ name: 'Color picker', icon: 'colorize', onClicked: () => {
+ Utils.execAsync(['hyprpicker', '-a']).catch(print)
+ }
+ }),
+ UtilButton({
+ name: 'Toggle on-screen keyboard', icon: 'keyboard', onClicked: () => {
+ App.toggleWindow('osk');
+ }
+ }),
+ ]
+})
+
+const BarBattery = () => Box({
+ className: 'spacing-h-4 txt-onSurfaceVariant',
+ children: [
+ Revealer({
+ transitionDuration: 150,
+ revealChild: false,
+ transition: 'slide_right',
+ child: MaterialIcon('bolt', 'norm', { tooltipText: "Charging" }),
+ setup: (self) => self.hook(Battery, revealer => {
+ self.revealChild = Battery.charging;
+ }),
+ }),
+ Label({
+ className: 'txt-smallie txt-onSurfaceVariant',
+ setup: (self) => self.hook(Battery, label => {
+ label.label = `${Battery.percent}%`;
+ }),
+ }),
+ Overlay({
+ child: Widget.Box({
+ vpack: 'center',
+ className: 'bar-batt',
+ homogeneous: true,
+ children: [
+ MaterialIcon('settings_heart', 'small'),
+ ],
+ setup: (self) => self.hook(Battery, box => {
+ box.toggleClassName('bar-batt-low', Battery.percent <= BATTERY_LOW);
+ box.toggleClassName('bar-batt-full', Battery.charged);
+ }),
+ }),
+ overlays: [
+ BatBatteryProgress(),
+ ]
+ }),
+ ]
+});
+
+const BarGroup = ({ child }) => Widget.Box({
+ className: 'bar-group-margin bar-sides',
+ children: [
+ Widget.Box({
+ className: 'bar-group bar-group-standalone bar-group-pad-system',
+ children: [child],
+ }),
+ ]
+});
+const BatteryModule = () => Stack({
+ transition: 'slide_up_down',
+ transitionDuration: 150,
+ children: {
+ 'laptop': Box({
+ className: 'spacing-h-5', children: [
+ BarGroup({ child: Utilities() }),
+ BarGroup({ child: BarBattery() }),
+ ]
+ }),
+ 'desktop': BarGroup({
+ child: Box({
+ hexpand: true,
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon('device_thermostat', 'small'),
+ Label({
+ label: 'Weather',
+ })
+ ],
+ setup: (self) => self.poll(900000, async (self) => {
+ const WEATHER_CACHE_PATH = WEATHER_CACHE_FOLDER + '/wttr.in.txt';
+ const updateWeatherForCity = (city) => execAsync(`curl https://wttr.in/${city}?format=j1`)
+ .then(output => {
+ const weather = JSON.parse(output);
+ Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH)
+ .catch(print);
+ const weatherCode = weather.current_condition[0].weatherCode;
+ const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
+ const temperature = weather.current_condition[0].temp_C;
+ const feelsLike = weather.current_condition[0].FeelsLikeC;
+ const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
+ self.children[0].label = weatherSymbol;
+ self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`;
+ self.tooltipText = weatherDesc;
+ }).catch((err) => {
+ try { // Read from cache
+ const weather = JSON.parse(
+ Utils.readFile(WEATHER_CACHE_PATH)
+ );
+ const weatherCode = weather.current_condition[0].weatherCode;
+ const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
+ const temperature = weather.current_condition[0].temp_C;
+ const feelsLike = weather.current_condition[0].FeelsLikeC;
+ const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
+ self.children[0].label = weatherSymbol;
+ self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`;
+ self.tooltipText = weatherDesc;
+ } catch (err) {
+ print(err);
+ }
+ });
+ if (WEATHER_CITY != '' && WEATHER_CITY != null) {
+ updateWeatherForCity(WEATHER_CITY);
+ }
+ else {
+ Utils.execAsync('curl ipinfo.io')
+ .then(output => {
+ return JSON.parse(output)['city'].toLowerCase();
+ })
+ .then(updateWeatherForCity);
+ }
+ }),
+ })
+ }),
+ },
+ setup: (stack) => Utils.timeout(10, () => {
+ if (!Battery.available) stack.shown = 'desktop';
+ else stack.shown = 'laptop';
+ })
+})
+
+const switchToRelativeWorkspace = async (self, num) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.sendMessage(`dispatch workspace ${num > 0 ? '+' : ''}${num}`);
+ } catch {
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+ }
+}
+
+export default () => Widget.EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ onPrimaryClick: () => App.toggleWindow('sideright'),
+ child: Widget.Box({
+ className: 'spacing-h-5',
+ children: [
+ BarGroup({ child: BarClock() }),
+ BatteryModule(),
+ ]
+ })
+});
diff --git a/modules/styling/config/widgets/bar/tray.js b/modules/styling/config/widgets/bar/tray.js
new file mode 100644
index 0000000..7d18007
--- /dev/null
+++ b/modules/styling/config/widgets/bar/tray.js
@@ -0,0 +1,89 @@
+const { Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
+const { Box, Icon, Button, Revealer } = Widget;
+const { Gravity } = imports.gi.Gdk;
+
+const revealerDuration = 200;
+
+const SysTrayItem = (item) => Button({
+ className: 'bar-systray-item',
+ child: Icon({
+ hpack: 'center',
+ icon: item.icon,
+ setup: (self) => self.hook(item, (self) => self.icon = item.icon),
+ }),
+ setup: (self) => self
+ .hook(item, (self) => self.tooltipMarkup = item['tooltip-markup'])
+ ,
+ onClicked: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
+ onSecondaryClick: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
+});
+
+export const Tray = (props = {}) => {
+ const trayContent = Box({
+ className: 'margin-right-5 spacing-h-15',
+ // attribute: {
+ // items: new Map(),
+ // addItem: (box, item) => {
+ // if (!item) return;
+ // console.log('init item:', item)
+
+ // item.menu.className = 'menu';
+ // if (box.attribute.items.has(item.id) || !item)
+ // return;
+ // const widget = SysTrayItem(item);
+ // box.attribute.items.set(item.id, widget);
+ // box.add(widget);
+ // box.show_all();
+ // },
+ // onAdded: (box, id) => {
+ // console.log('supposed to add', id)
+ // const item = SystemTray.getItem(id);
+ // if (!item) return;
+ // console.log('which is', box.attribute.items.get(id))
+
+ // item.menu.className = 'menu';
+ // if (box.attribute.items.has(id) || !item)
+ // return;
+ // const widget = SysTrayItem(item);
+ // box.attribute.items.set(id, widget);
+ // box.add(widget);
+ // box.show_all();
+ // },
+ // onRemoved: (box, id) => {
+ // console.log('supposed to remove', id)
+ // if (!box.attribute.items.has(id)) return;
+ // console.log('which is', box.attribute.items.get(id))
+ // box.attribute.items.get(id).destroy();
+ // box.attribute.items.delete(id);
+ // },
+ // },
+ // setup: (self) => {
+ // // self.hook(SystemTray, (box, id) => box.attribute.onAdded(box, id), 'added')
+ // // .hook(SystemTray, (box, id) => box.attribute.onRemoved(box, id), 'removed');
+ // // SystemTray.items.forEach(item => self.attribute.addItem(self, item));
+ // // self.chidren = SystemTray.items.map(item => SysTrayItem(item));
+ // console.log(SystemTray.items.map(item => SysTrayItem(item)))
+ // self.chidren = SystemTray.items.map(item => SysTrayItem(item));
+
+ // self.show_all();
+ // },
+ setup: (self) => self
+ .hook(SystemTray, (self) => {
+ self.children = SystemTray.items.map(SysTrayItem);
+ self.show_all();
+ })
+ ,
+ });
+ const trayRevealer = Widget.Revealer({
+ revealChild: true,
+ transition: 'slide_left',
+ transitionDuration: revealerDuration,
+ child: trayContent,
+ });
+ return Box({
+ ...props,
+ children: [trayRevealer],
+ });
+}
diff --git a/modules/styling/config/widgets/bar/workspaces_hyprland.js b/modules/styling/config/widgets/bar/workspaces_hyprland.js
new file mode 100644
index 0000000..41887c5
--- /dev/null
+++ b/modules/styling/config/widgets/bar/workspaces_hyprland.js
@@ -0,0 +1,189 @@
+const { GLib, Gdk, Gtk } = imports.gi;
+const Lang = imports.lang;
+const Cairo = imports.cairo;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+const { Box, DrawingArea, EventBox } = Widget;
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+const NUM_OF_WORKSPACES_SHOWN = 10; // Limit = 53 I think
+const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
+const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
+const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
+
+// Font size = workspace id
+const WorkspaceContents = (count = 10) => {
+ return DrawingArea({
+ // css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
+ attribute: {
+ initialized: false,
+ workspaceMask: 0,
+ workspaceGroup: 0,
+ updateMask: (self) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * NUM_OF_WORKSPACES_SHOWN;
+ // if (self.attribute.initialized) return; // We only need this to run once
+ const workspaces = Hyprland.workspaces;
+ let workspaceMask = 0;
+ for (let i = 0; i < workspaces.length; i++) {
+ const ws = workspaces[i];
+ if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
+ if (workspaces[i].windows > 0)
+ workspaceMask |= (1 << (ws.id - offset));
+ }
+ // console.log('Mask:', workspaceMask.toString(2));
+ self.attribute.workspaceMask = workspaceMask;
+ // self.attribute.initialized = true;
+ self.queue_draw();
+ },
+ toggleMask: (self, occupied, name) => {
+ if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
+ else self.attribute.workspaceMask &= ~(1 << parseInt(name));
+ self.queue_draw();
+ },
+ },
+ setup: (area) => area
+ .hook(Hyprland.active.workspace, (self) => {
+ self.setCss(`font-size: ${(Hyprland.active.workspace.id - 1) % count + 1}px;`);
+ const previousGroup = self.attribute.workspaceGroup;
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
+ if (currentGroup !== previousGroup) {
+ self.attribute.updateMask(self);
+ self.attribute.workspaceGroup = currentGroup;
+ }
+ })
+ .hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
+ .on('draw', Lang.bind(area, (area, cr) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * NUM_OF_WORKSPACES_SHOWN;
+
+ const allocation = area.get_allocation();
+ const { width, height } = allocation;
+
+ const workspaceStyleContext = dummyWs.get_style_context();
+ const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const workspaceRadius = workspaceDiameter / 2;
+ const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
+ const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
+ const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
+ const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
+ const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ area.set_size_request(workspaceDiameter * count, -1);
+ const widgetStyleContext = area.get_style_context();
+ const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
+ const activeWsCenterY = height / 2;
+
+ // Font
+ const layout = PangoCairo.create_layout(cr);
+ const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
+ layout.set_font_description(fontDesc);
+ cr.setAntialias(Cairo.Antialias.BEST);
+ // Get kinda min radius for number indicators
+ layout.set_text("0".repeat(count.toString().length), -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
+ const indicatorGap = workspaceRadius - indicatorRadius;
+
+ // Draw workspace numbers
+ for (let i = 1; i <= count; i++) {
+ if (area.attribute.workspaceMask & (1 << i)) {
+ // Draw bg highlight
+ cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
+ const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
+ const wsCenterY = height / 2;
+ if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+ if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+
+ // Set color for text
+ cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
+ }
+ else
+ cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
+
+ layout.set_text(`${i + offset}`, -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
+ const y = (height - layoutHeight) / 2;
+ cr.moveTo(x, y);
+ PangoCairo.show_layout(cr, layout);
+ cr.stroke();
+ }
+
+ // Draw active ws
+ // base
+ cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
+ cr.fill();
+ // inner decor
+ cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
+ cr.fill();
+ }))
+ ,
+ })
+}
+
+export default () => EventBox({
+ onScrollUp: () => Hyprland.sendMessage(`dispatch workspace -1`),
+ onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`),
+ onMiddleClickRelease: () => App.toggleWindow('overview'),
+ onSecondaryClickRelease: () => App.toggleWindow('osk'),
+ attribute: {
+ clicked: false,
+ ws_group: 0,
+ },
+ child: Box({
+ homogeneous: true,
+ className: 'bar-group-margin',
+ children: [Box({
+ className: 'bar-group bar-group-standalone bar-group-pad',
+ css: 'min-width: 2px;',
+ children: [WorkspaceContents(NUM_OF_WORKSPACES_SHOWN)],
+ })]
+ }),
+ setup: (self) => {
+ self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+ self.on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_SHOWN / widgetWidth);
+ Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`]);
+ })
+ self.on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ // const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_PER_GROUP / widgetWidth) + self.attribute.ws_group * NUM_OF_WORKSPACES_PER_GROUP;
+ // Hyprland.sendMessage(`dispatch workspace ${wsId}`);
+ const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_SHOWN / widgetWidth);
+ Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`]);
+ })
+ self.on('button-release-event', (self) => self.attribute.clicked = false);
+ }
+})
diff --git a/modules/styling/config/widgets/bar/workspaces_sway.js b/modules/styling/config/widgets/bar/workspaces_sway.js
new file mode 100644
index 0000000..53612eb
--- /dev/null
+++ b/modules/styling/config/widgets/bar/workspaces_sway.js
@@ -0,0 +1,184 @@
+const { GLib, Gdk, Gtk } = imports.gi;
+const Lang = imports.lang;
+const Cairo = imports.cairo;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+import Widget from "resource:///com/github/Aylur/ags/widget.js";
+import Sway from "../../services/sway.js";
+import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
+const { execAsync, exec } = Utils;
+const { Box, DrawingArea, EventBox } = Widget;
+
+const NUM_OF_WORKSPACES = 10;
+const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
+const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
+const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
+
+const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
+const switchToRelativeWorkspace = (self, num) =>
+ execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
+
+const WorkspaceContents = (count = 10) => {
+ return DrawingArea({
+ css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
+ attribute: {
+ initialized: false,
+ workspaceMask: 0,
+ updateMask: (self) => {
+ if (self.attribute.initialized) return; // We only need this to run once
+ const workspaces = Sway.workspaces;
+ let workspaceMask = 0;
+ // console.log('----------------')
+ for (let i = 0; i < workspaces.length; i++) {
+ const ws = workspaces[i];
+ // console.log(ws.name, ',', ws.num);
+ if (!Number(ws.name)) return;
+ const id = Number(ws.name);
+ if (id <= 0) continue; // Ignore scratchpads
+ if (id > count) return; // Not rendered
+ if (workspaces[i].windows > 0) {
+ workspaceMask |= (1 << id);
+ }
+ }
+ self.attribute.workspaceMask = workspaceMask;
+ self.attribute.initialized = true;
+ },
+ toggleMask: (self, occupied, name) => {
+ if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
+ else self.attribute.workspaceMask &= ~(1 << parseInt(name));
+ },
+ },
+ setup: (area) => area
+ .hook(Sway.active.workspace, (area) => {
+ area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
+ })
+ .hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
+ // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
+ // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
+ .on('draw', Lang.bind(area, (area, cr) => {
+ const allocation = area.get_allocation();
+ const { width, height } = allocation;
+
+ const workspaceStyleContext = dummyWs.get_style_context();
+ const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ const workspaceRadius = workspaceDiameter / 2;
+ const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
+ const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
+ const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
+ const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+
+ const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
+ const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
+ const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
+ area.set_size_request(workspaceDiameter * count, -1);
+ const widgetStyleContext = area.get_style_context();
+ const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
+ const activeWsCenterY = height / 2;
+
+ // Font
+ const layout = PangoCairo.create_layout(cr);
+ const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
+ layout.set_font_description(fontDesc);
+ cr.setAntialias(Cairo.Antialias.BEST);
+ // Get kinda min radius for number indicators
+ layout.set_text("0".repeat(count.toString().length), -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
+ const indicatorGap = workspaceRadius - indicatorRadius;
+
+ // Draw workspace numbers
+ for (let i = 1; i <= count; i++) {
+ if (area.attribute.workspaceMask & (1 << i)) {
+ // Draw bg highlight
+ cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
+ const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
+ const wsCenterY = height / 2;
+ if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+ if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
+ cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
+ cr.fill();
+ }
+ else {
+ cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
+ cr.fill();
+ }
+
+ // Set color for text
+ cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
+ }
+ else
+ cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
+ layout.set_text(`${i}`, -1);
+ const [layoutWidth, layoutHeight] = layout.get_pixel_size();
+ const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
+ const y = (height - layoutHeight) / 2;
+ cr.moveTo(x, y);
+ // cr.showText(text);
+ PangoCairo.show_layout(cr, layout);
+ cr.stroke();
+ }
+
+ // Draw active ws
+ // base
+ cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
+ cr.fill();
+ // inner decor
+ cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
+ cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
+ cr.fill();
+ }))
+ ,
+ })
+}
+
+export default () => EventBox({
+ onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
+ onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
+ onMiddleClickRelease: () => App.toggleWindow('overview'),
+ onSecondaryClickRelease: () => App.toggleWindow('osk'),
+ attribute: { clicked: false },
+ child: Box({
+ homogeneous: true,
+ className: 'bar-group-margin',
+ children: [Box({
+ className: 'bar-group bar-group-standalone bar-group-pad',
+ css: 'min-width: 2px;',
+ children: [
+ WorkspaceContents(10),
+ ]
+ })]
+ }),
+ setup: (self) => {
+ self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+ self.on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth);
+ switchToWorkspace(wsId);
+ })
+ self.on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ const [_, cursorX, cursorY] = event.get_coords();
+ const widgetWidth = self.get_allocation().width;
+ const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth);
+ switchToWorkspace(wsId);
+ })
+ self.on('button-release-event', (self) => self.attribute.clicked = false);
+ }
+});
diff --git a/modules/styling/config/widgets/cheatsheet/keybinds.js b/modules/styling/config/widgets/cheatsheet/keybinds.js
new file mode 100644
index 0000000..765c511
--- /dev/null
+++ b/modules/styling/config/widgets/cheatsheet/keybinds.js
@@ -0,0 +1,60 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { keybindList } from "../../data/keybinds.js";
+
+export const Keybinds = () => Widget.Box({
+ vertical: false,
+ className: "spacing-h-15",
+ homogeneous: true,
+ children: keybindList.map((group, i) => Widget.Box({ // Columns
+ vertical: true,
+ className: "spacing-v-15",
+ children: group.map((category, i) => Widget.Box({ // Categories
+ vertical: true,
+ className: "spacing-v-15",
+ children: [
+ Widget.Box({ // Category header
+ vertical: false,
+ className: "spacing-h-10",
+ children: [
+ Widget.Label({
+ xalign: 0,
+ className: "icon-material txt txt-larger",
+ label: category.icon,
+ }),
+ Widget.Label({
+ xalign: 0,
+ className: "cheatsheet-category-title txt",
+ label: category.name,
+ }),
+ ]
+ }),
+ Widget.Box({
+ vertical: false,
+ className: "spacing-h-10",
+ children: [
+ Widget.Box({ // Keys
+ vertical: true,
+ homogeneous: true,
+ children: category.binds.map((keybinds, i) => Widget.Box({ // Binds
+ vertical: false,
+ children: keybinds.keys.map((key, i) => Widget.Label({ // Specific keys
+ className: `${['OR', '+'].includes(key) ? 'cheatsheet-key-notkey' : 'cheatsheet-key'} txt-small`,
+ label: key,
+ }))
+ }))
+ }),
+ Widget.Box({ // Actions
+ vertical: true,
+ homogeneous: true,
+ children: category.binds.map((keybinds, i) => Widget.Label({ // Binds
+ xalign: 0,
+ label: keybinds.action,
+ className: "txt chearsheet-action txt-small",
+ }))
+ })
+ ]
+ })
+ ]
+ }))
+ })),
+});
\ No newline at end of file
diff --git a/modules/styling/config/widgets/cheatsheet/main.js b/modules/styling/config/widgets/cheatsheet/main.js
new file mode 100644
index 0000000..d2e41f5
--- /dev/null
+++ b/modules/styling/config/widgets/cheatsheet/main.js
@@ -0,0 +1,91 @@
+const { Gdk, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import { Keybinds } from "./keybinds.js";
+import { setupCursorHover } from "../../lib/cursorhover.js";
+
+const cheatsheetHeader = () => Widget.CenterBox({
+ vertical: false,
+ startWidget: Widget.Box({}),
+ centerWidget: Widget.Box({
+ vertical: true,
+ className: "spacing-h-15",
+ children: [
+ Widget.Box({
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ Widget.Label({
+ hpack: 'center',
+ css: 'margin-right: 0.682rem;',
+ className: 'txt-title txt',
+ label: 'Cheat sheet',
+ }),
+ Widget.Label({
+ vpack: 'center',
+ className: "cheatsheet-key txt-small",
+ label: "",
+ }),
+ Widget.Label({
+ vpack: 'center',
+ className: "cheatsheet-key-notkey txt-small",
+ label: "+",
+ }),
+ Widget.Label({
+ vpack: 'center',
+ className: "cheatsheet-key txt-small",
+ label: "/",
+ })
+ ]
+ }),
+ Widget.Label({
+ useMarkup: true,
+ selectable: true,
+ justify: Gtk.Justification.CENTER,
+ className: 'txt-small txt',
+ label: 'Sheet data stored in ~/.config/ags/data/keybinds.js\nChange keybinds in ~/.config/hypr/keybinds.conf'
+ }),
+ ]
+ }),
+ endWidget: Widget.Button({
+ vpack: 'start',
+ hpack: 'end',
+ className: "cheatsheet-closebtn icon-material txt txt-hugeass",
+ onClicked: () => {
+ App.toggleWindow('cheatsheet');
+ },
+ child: Widget.Label({
+ className: 'icon-material txt txt-hugeass',
+ label: 'close'
+ }),
+ setup: setupCursorHover,
+ }),
+});
+
+const clickOutsideToClose = Widget.EventBox({
+ onPrimaryClick: () => App.closeWindow('cheatsheet'),
+ onSecondaryClick: () => App.closeWindow('cheatsheet'),
+ onMiddleClick: () => App.closeWindow('cheatsheet'),
+});
+
+export default () => Widget.Window({
+ name: 'cheatsheet',
+ exclusivity: 'ignore',
+ keymode: 'exclusive',
+ popup: true,
+ visible: false,
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ clickOutsideToClose,
+ Widget.Box({
+ vertical: true,
+ className: "cheatsheet-bg spacing-v-15",
+ children: [
+ cheatsheetHeader(),
+ Keybinds(),
+ ]
+ }),
+ ],
+ })
+});
diff --git a/modules/styling/config/widgets/desktopbackground/main.js b/modules/styling/config/widgets/desktopbackground/main.js
new file mode 100644
index 0000000..f591fc8
--- /dev/null
+++ b/modules/styling/config/widgets/desktopbackground/main.js
@@ -0,0 +1,24 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+import WallpaperImage from './wallpaper.js';
+import TimeAndLaunchesWidget from './timeandlaunches.js'
+import SystemWidget from './system.js'
+
+export default (monitor) => Widget.Window({
+ name: `desktopbackground${monitor}`,
+ // anchor: ['top', 'bottom', 'left', 'right'],
+ layer: 'background',
+ exclusivity: 'ignore',
+ visible: true,
+ // child: WallpaperImage(monitor),
+ child: Widget.Overlay({
+ child: WallpaperImage(monitor),
+ overlays: [
+ TimeAndLaunchesWidget(),
+ SystemWidget(),
+ ],
+ setup: (self) => {
+ self.set_overlay_pass_through(self.get_children()[1], true);
+ },
+ }),
+});
diff --git a/modules/styling/config/widgets/desktopbackground/system.js b/modules/styling/config/widgets/desktopbackground/system.js
new file mode 100644
index 0000000..a22888f
--- /dev/null
+++ b/modules/styling/config/widgets/desktopbackground/system.js
@@ -0,0 +1,161 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, EventBox, Label, Revealer, Overlay } = Widget;
+import { AnimatedCircProg } from '../../lib/animatedcircularprogress.js'
+import { MaterialIcon } from '../../lib/materialicon.js';
+
+const ResourceValue = (name, icon, interval, valueUpdateCmd, displayFunc, props = {}) => Box({
+ ...props,
+ className: 'bg-system-bg txt',
+ children: [
+ Revealer({
+ transition: 'slide_left',
+ transitionDuration: 200,
+ child: Box({
+ vpack: 'center',
+ vertical: true,
+ className: 'margin-right-15',
+ children: [
+ Label({
+ xalign: 1,
+ className: 'txt-small txt',
+ label: `${name}`,
+ }),
+ Label({
+ xalign: 1,
+ className: 'titlefont txt-norm txt-onSecondaryContainer',
+ setup: (self) => self
+ .poll(interval, (label) => displayFunc(label))
+ ,
+ })
+ ]
+ })
+ }),
+ Overlay({
+ child: AnimatedCircProg({
+ className: 'bg-system-circprog',
+ extraSetup: (self) => self
+ .poll(interval, (self) => {
+ execAsync(['bash', '-c', `${valueUpdateCmd}`]).then((newValue) => {
+ self.css = `font-size: ${Math.round(newValue)}px;`
+ }).catch(print);
+ })
+ ,
+ }),
+ overlays: [
+ MaterialIcon(`${icon}`, 'hugeass'),
+ ],
+ setup: self => self.set_overlay_pass_through(self.get_children()[1], true),
+ }),
+ ]
+})
+
+const resources = Box({
+ vpack: 'fill',
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ ResourceValue('Memory', 'memory', 10000, `free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
+ (label) => {
+ execAsync(['bash', '-c', `free -h | awk '/^Mem/ {print $3 " / " $2}' | sed 's/Gi/Gib/g'`])
+ .then((output) => {
+ label.label = `${output}`
+ }).catch(print);
+ }, { hpack: 'end' }),
+ ResourceValue('Swap', 'swap_horiz', 10000, `free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
+ (label) => {
+ execAsync(['bash', '-c', `free -h | awk '/^Swap/ {if ($2 != "0") print $3 " / " $2; else print "No swap"}' | sed 's/Gi/Gib/g'`])
+ .then((output) => {
+ label.label = `${output}`
+ }).catch(print);
+ }, { hpack: 'end' }),
+ ResourceValue('Disk space', 'hard_drive_2', 3600000, `echo $(df --output=pcent / | tr -dc '0-9')`,
+ (label) => {
+ execAsync(['bash', '-c', `df -h --output=avail / | awk 'NR==2{print $1}'`])
+ .then((output) => {
+ label.label = `${output} available`
+ }).catch(print);
+ }, { hpack: 'end' }),
+ ]
+});
+
+const distroAndVersion = Box({
+ vertical: true,
+ children: [
+ Box({
+ hpack: 'end',
+ children: [
+ Label({
+ className: 'bg-distro-txt',
+ xalign: 0,
+ label: 'Hyping on ',
+ }),
+ Label({
+ className: 'bg-distro-name',
+ xalign: 0,
+ label: '',
+ setup: (label) => {
+ execAsync([`grep`, `-oP`, `PRETTY_NAME="\\K[^"]+`, `/etc/os-release`]).then(distro => {
+ label.label = distro;
+ }).catch(print);
+ },
+ }),
+ ]
+ }),
+ Box({
+ hpack: 'end',
+ children: [
+ Label({
+ className: 'bg-distro-txt',
+ xalign: 0,
+ label: 'with ',
+ }),
+ Label({
+ className: 'bg-distro-name',
+ xalign: 0,
+ label: 'An environment idk',
+ setup: (label) => {
+ // hyprctl will return unsuccessfully if Hyprland isn't running
+ execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(version => {
+ label.label = `Hyprland ${version}`;
+ }).catch(() => execAsync([`bash`, `-c`, `sway -v | cut -d'-' -f1 | sed 's/sway version /v/'`]).then(version => {
+ label.label = `Sway ${version}`;
+ }).catch(print));
+ },
+ }),
+ ]
+ })
+ ]
+})
+
+export default () => Box({
+ hpack: 'end',
+ vpack: 'end',
+ children: [
+ EventBox({
+ child: Box({
+ hpack: 'end',
+ vpack: 'end',
+ className: 'bg-distro-box spacing-v-20',
+ vertical: true,
+ children: [
+ resources,
+ distroAndVersion,
+ ]
+ }),
+ onPrimaryClickRelease: () => {
+ const kids = resources.get_children();
+ for (let i = 0; i < kids.length; i++) {
+ const child = kids[i];
+ const firstChild = child.get_children()[0];
+ firstChild.revealChild = !firstChild.revealChild;
+ }
+
+ },
+ })
+ ],
+})
+
+
+
diff --git a/modules/styling/config/widgets/desktopbackground/timeandlaunches.js b/modules/styling/config/widgets/desktopbackground/timeandlaunches.js
new file mode 100644
index 0000000..8897992
--- /dev/null
+++ b/modules/styling/config/widgets/desktopbackground/timeandlaunches.js
@@ -0,0 +1,75 @@
+const { GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+const { execAsync, exec } = Utils;
+const { Box, Label, Button, Revealer, EventBox } = Widget;
+import { setupCursorHover } from '../../lib/cursorhover.js';
+
+import { quickLaunchItems } from '../../data/quicklaunches.js'
+
+const TimeAndDate = () => Box({
+ vertical: true,
+ className: 'spacing-v--5',
+ children: [
+ Label({
+ className: 'bg-time-clock',
+ xalign: 0,
+ label: GLib.DateTime.new_now_local().format("%H:%M"),
+ setup: (self) => self.poll(5000, label => {
+ label.label = GLib.DateTime.new_now_local().format("%H:%M");
+ }),
+ }),
+ Label({
+ className: 'bg-time-date',
+ xalign: 0,
+ label: GLib.DateTime.new_now_local().format("%A, %d/%m/%Y"),
+ setup: (self) => self.poll(5000, label => {
+ label.label = GLib.DateTime.new_now_local().format("%A, %d/%m/%Y");
+ }),
+ }),
+ ]
+})
+
+const QuickLaunches = () => Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Label({
+ xalign: 0,
+ className: 'bg-quicklaunch-title',
+ label: 'Quick Launches',
+ }),
+ Box({
+ hpack: 'start',
+ className: 'spacing-h-5',
+ children: quickLaunchItems.map((item, i) => Button({
+ onClicked: () => {
+ execAsync(['bash', '-c', `${item["command"]}`]).catch(print);
+ },
+ className: 'bg-quicklaunch-btn',
+ child: Label({
+ label: `${item["name"]}`,
+ }),
+ setup: (self) => {
+ setupCursorHover(self);
+ }
+ })),
+ })
+ ]
+})
+
+export default () => Box({
+ hpack: 'start',
+ vpack: 'end',
+ vertical: true,
+ className: 'bg-time-box spacing-h--10',
+ children: [
+ TimeAndDate(),
+ // QuickLaunches(),
+ ],
+})
+
diff --git a/modules/styling/config/widgets/desktopbackground/wallpaper.js b/modules/styling/config/widgets/desktopbackground/wallpaper.js
new file mode 100644
index 0000000..ad1272a
--- /dev/null
+++ b/modules/styling/config/widgets/desktopbackground/wallpaper.js
@@ -0,0 +1,115 @@
+const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
+const { exec, execAsync } = Utils;
+const { Box, Button, Label, Stack } = Widget;
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+import Wallpaper from '../../services/wallpaper.js';
+import { setupCursorHover } from '../../lib/cursorhover.js';
+
+const SWITCHWALL_SCRIPT_PATH = `${App.configDir}/scripts/color_generation/switchwall.sh`;
+const WALLPAPER_ZOOM_SCALE = 1.25; // For scrolling when we switch workspace
+const MAX_WORKSPACES = 10;
+
+const WALLPAPER_OFFSCREEN_X = (WALLPAPER_ZOOM_SCALE - 1) * SCREEN_WIDTH;
+const WALLPAPER_OFFSCREEN_Y = (WALLPAPER_ZOOM_SCALE - 1) * SCREEN_HEIGHT;
+
+function clamp(x, min, max) {
+ return Math.min(Math.max(x, min), max);
+}
+
+export default (monitor = 0) => {
+ const wallpaperImage = Widget.DrawingArea({
+ attribute: {
+ pixbuf: undefined,
+ workspace: 1,
+ sideleft: 0,
+ sideright: 0,
+ updatePos: (self) => {
+ self.setCss(`font-size: ${self.attribute.workspace - self.attribute.sideleft + self.attribute.sideright}px;`)
+ },
+ },
+ className: 'bg-wallpaper-transition',
+ setup: (self) => {
+ self.set_size_request(SCREEN_WIDTH, SCREEN_HEIGHT);
+ self
+ // TODO: reduced updates using timeouts to reduce lag
+ // .hook(Hyprland.active.workspace, (self) => {
+ // self.attribute.workspace = Hyprland.active.workspace.id
+ // self.attribute.updatePos(self);
+ // })
+ // .hook(App, (box, name, visible) => { // Update on open
+ // if (self.attribute[name] === undefined) return;
+ // self.attribute[name] = (visible ? 1 : 0);
+ // self.attribute.updatePos(self);
+ // })
+ .on('draw', (self, cr) => {
+ if (!self.attribute.pixbuf) return;
+ const styleContext = self.get_style_context();
+ const workspace = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+ // Draw
+ Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
+ -(WALLPAPER_OFFSCREEN_X / (MAX_WORKSPACES + 1) * (clamp(workspace, 0, MAX_WORKSPACES + 1))),
+ -WALLPAPER_OFFSCREEN_Y / 2);
+ cr.paint();
+ })
+ .hook(Wallpaper, (self) => {
+ const wallPath = Wallpaper.get(monitor);
+ if (!wallPath || wallPath === "") return;
+ self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file(wallPath);
+
+ const scale_x = SCREEN_WIDTH * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_width();
+ const scale_y = SCREEN_HEIGHT * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_height();
+ const scale_factor = Math.max(scale_x, scale_y);
+
+ self.attribute.pixbuf = self.attribute.pixbuf.scale_simple(
+ Math.round(self.attribute.pixbuf.get_width() * scale_factor),
+ Math.round(self.attribute.pixbuf.get_height() * scale_factor),
+ GdkPixbuf.InterpType.BILINEAR
+ );
+ self.queue_draw();
+ }, 'updated');
+ ;
+ }
+ ,
+ });
+ const wallpaperPrompt = Box({
+ hpack: 'center',
+ vpack: 'center',
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ Label({
+ hpack: 'center',
+ justification: 'center',
+ className: 'txt-large',
+ label: `No wallpaper loaded.\nAn image ≥ ${SCREEN_WIDTH * WALLPAPER_ZOOM_SCALE} × ${SCREEN_HEIGHT * WALLPAPER_ZOOM_SCALE} is recommended.`,
+ }),
+ Button({
+ hpack: 'center',
+ className: 'btn-primary',
+ label: `Select one`,
+ setup: setupCursorHover,
+ onClicked: (self) => Utils.execAsync([SWITCHWALL_SCRIPT_PATH]),
+ }),
+ ]
+ });
+ const stack = Stack({
+ transition: 'crossfade',
+ transitionDuration: 180,
+ children: {
+ 'image': wallpaperImage,
+ 'prompt': wallpaperPrompt,
+ },
+ setup: (self) => self
+ .hook(Wallpaper, (self) => {
+ const wallPath = Wallpaper.get(monitor);
+ self.shown = ((wallPath && wallPath != "") ? 'image' : 'prompt');
+ }, 'updated')
+ ,
+ })
+ return stack;
+ // return wallpaperImage;
+}
diff --git a/modules/styling/config/widgets/dock/dock.js b/modules/styling/config/widgets/dock/dock.js
new file mode 100644
index 0000000..9a63d35
--- /dev/null
+++ b/modules/styling/config/widgets/dock/dock.js
@@ -0,0 +1,259 @@
+const { Gtk } = imports.gi;
+import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { EventBox } = Widget;
+
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
+const { execAsync, exec } = Utils;
+const { Box, Revealer } = Widget;
+import { setupCursorHover } from "../../lib/cursorhover.js";
+
+const ANIMATION_TIME = 150;
+const pinnedApps = [
+ 'firefox',
+ 'org.gnome.Nautilus',
+];
+
+function substitute(str) {
+ const subs = [
+ { from: 'code-url-handler', to: 'visual-studio-code' },
+ { from: 'Code', to: 'visual-studio-code' },
+ { from: 'GitHub Desktop', to: 'github-desktop' },
+ { from: 'wpsoffice', to: 'wps-office2019-kprometheus' },
+ { from: 'gnome-tweaks', to: 'org.gnome.tweaks' },
+ { from: 'Minecraft* 1.20.1', to: 'minecraft' },
+ { from: '', to: 'image-missing' },
+ ];
+
+ for (const { from, to } of subs) {
+ if (from === str)
+ return to;
+ }
+
+ return str;
+}
+
+const focus = ({ address }) => Utils.execAsync(`hyprctl dispatch focuswindow address:${address}`);
+
+const DockSeparator = (props = {}) => Box({
+ ...props,
+ className: 'dock-separator',
+})
+
+const AppButton = ({ icon, ...rest }) => Widget.Revealer({
+ attribute: {
+ 'workspace': 0
+ },
+ revealChild: false,
+ transition: 'slide_right',
+ transitionDuration: ANIMATION_TIME,
+ child: Widget.Button({
+ ...rest,
+ className: 'dock-app-btn',
+ child: Widget.Box({
+ child: Widget.Overlay({
+ child: Widget.Box({
+ homogeneous: true,
+ className: 'dock-app-icon',
+ child: Widget.Icon({
+ icon: icon,
+ }),
+ }),
+ overlays: [Widget.Box({
+ class_name: 'indicator',
+ vpack: 'end',
+ hpack: 'center',
+ })],
+ }),
+ }),
+ setup: (button) => {
+ setupCursorHover(button);
+ }
+ })
+});
+
+const Taskbar = () => Widget.Box({
+ className: 'dock-apps',
+ attribute: {
+ 'map': new Map(),
+ 'clientSortFunc': (a, b) => {
+ return a.attribute.workspace > b.attribute.workspace;
+ },
+ 'update': (box) => {
+ for (let i = 0; i < Hyprland.clients.length; i++) {
+ const client = Hyprland.clients[i];
+ if (client["pid"] == -1) return;
+ const appClass = substitute(client.class);
+ for (const appName of pinnedApps) {
+ if (appClass.includes(appName.toLowerCase()))
+ return null;
+ }
+ const newButton = AppButton({
+ icon: appClass,
+ tooltipText: `${client.title} (${appClass})`,
+ onClicked: () => focus(client),
+ });
+ newButton.attribute.workspace = client.workspace.id;
+ newButton.revealChild = true;
+ box.attribute.map.set(client.address, newButton);
+ }
+ box.children = Array.from(box.attribute.map.values());
+ },
+ 'add': (box, address) => {
+ if (!address) { // First active emit is undefined
+ box.attribute.update(box);
+ return;
+ }
+ const newClient = Hyprland.clients.find(client => {
+ return client.address == address;
+ });
+ const appClass = substitute(newClient.class);
+
+ const newButton = AppButton({
+ icon: appClass,
+ tooltipText: `${newClient.title} (${appClass})`,
+ onClicked: () => focus(newClient),
+ })
+ newButton.attribute.workspace = newClient.workspace.id;
+ box.attribute.map.set(address, newButton);
+ box.children = Array.from(box.attribute.map.values());
+ newButton.revealChild = true;
+ },
+ 'remove': (box, address) => {
+ if (!address) return;
+
+ const removedButton = box.attribute.map.get(address);
+ if (!removedButton) return;
+ removedButton.revealChild = false;
+
+ Utils.timeout(ANIMATION_TIME, () => {
+ removedButton.destroy();
+ box.attribute.map.delete(address);
+ box.children = Array.from(box.attribute.map.values());
+ })
+ },
+ },
+ setup: (self) => {
+ self.hook(Hyprland, (box, address) => box.attribute.add(box, address), 'client-added')
+ .hook(Hyprland, (box, address) => box.attribute.remove(box, address), 'client-removed')
+ Utils.timeout(100, () => self.attribute.update(self));
+ },
+});
+
+const PinnedApps = () => Widget.Box({
+ class_name: 'dock-apps',
+ homogeneous: true,
+ children: pinnedApps
+ .map(term => ({ app: Applications.query(term)?.[0], term }))
+ .filter(({ app }) => app)
+ .map(({ app, term = true }) => {
+ const newButton = AppButton({
+ icon: app.icon_name,
+ onClicked: () => {
+ for (const client of Hyprland.clients) {
+ if (client.class.toLowerCase().includes(term))
+ return focus(client);
+ }
+
+ app.launch();
+ },
+ onMiddleClick: () => app.launch(),
+ tooltipText: app.name,
+ setup: (self) => {
+ self.revealChild = true;
+ self.hook(Hyprland, button => {
+ const running = Hyprland.clients
+ .find(client => client.class.toLowerCase().includes(term)) || false;
+
+ button.toggleClassName('notrunning', !running);
+ button.toggleClassName('focused', Hyprland.active.client.address == running.address);
+ button.set_tooltip_text(running ? running.title : app.name);
+ }, 'notify::clients')
+ },
+ })
+ newButton.revealChild = true;
+ return newButton;
+ }),
+});
+
+export default () => {
+ const dockContent = Box({
+ className: 'dock-bg spacing-h-5',
+ children: [
+ PinnedApps(),
+ DockSeparator(),
+ Taskbar(),
+ ]
+ })
+ const dockRevealer = Revealer({
+ attribute: {
+ 'updateShow': self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
+ const dockSize = [
+ dockContent.get_allocated_width(),
+ dockContent.get_allocated_height()
+ ]
+ const dockAt = [
+ SCREEN_WIDTH / 2 - dockSize[0] / 2,
+ SCREEN_HEIGHT - dockSize[1],
+ ];
+ const dockLeft = dockAt[0];
+ const dockRight = dockAt[0] + dockSize[0];
+ const dockTop = dockAt[1];
+ const dockBottom = dockAt[1] + dockSize[1];
+
+ const currentWorkspace = Hyprland.active.workspace.id;
+ var toReveal = true;
+ const hyprlandClients = JSON.parse(exec('hyprctl clients -j'));
+ for (const index in hyprlandClients) {
+ const client = hyprlandClients[index];
+ const clientLeft = client.at[0];
+ const clientRight = client.at[0] + client.size[0];
+ const clientTop = client.at[1];
+ const clientBottom = client.at[1] + client.size[1];
+
+ if (client.workspace.id == currentWorkspace) {
+ if (
+ clientLeft < dockRight &&
+ clientRight > dockLeft &&
+ clientTop < dockBottom &&
+ clientBottom > dockTop
+ ) {
+ self.revealChild = false;
+ return;
+ }
+ }
+ }
+ self.revealChild = true;
+ }
+ },
+ revealChild: false,
+ transition: 'slide_up',
+ transitionDuration: 200,
+ child: dockContent,
+ // setup: (self) => self
+ // .hook(Hyprland, (self) => self.attribute.updateShow(self))
+ // .hook(Hyprland.active.workspace, (self) => self.attribute.updateShow(self))
+ // .hook(Hyprland.active.client, (self) => self.attribute.updateShow(self))
+ // .hook(Hyprland, (self) => self.attribute.updateShow(self), 'client-added')
+ // .hook(Hyprland, (self) => self.attribute.updateShow(self), 'client-removed')
+ // ,
+ })
+ return EventBox({
+ onHover: () => {
+ dockRevealer.revealChild = true;
+ },
+ onHoverLost: () => {
+ if (Hyprland.active.client.attribute.class.length === 0) return;
+ dockRevealer.revealChild = false;
+ },
+ child: Box({
+ homogeneous: true,
+ css: 'min-height: 2px;',
+ children: [
+ dockRevealer,
+ ]
+ }),
+ })
+}
diff --git a/modules/styling/config/widgets/dock/main.js b/modules/styling/config/widgets/dock/main.js
new file mode 100644
index 0000000..cc7bb0e
--- /dev/null
+++ b/modules/styling/config/widgets/dock/main.js
@@ -0,0 +1,11 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Dock from './dock.js';
+
+export default () => Widget.Window({
+ name: 'dock',
+ layer: 'bottom',
+ anchor: ['bottom'],
+ exclusivity: 'normal',
+ visible: true,
+ child: Dock(),
+});
diff --git a/modules/styling/config/widgets/indicators/colorscheme.js b/modules/styling/config/widgets/indicators/colorscheme.js
new file mode 100644
index 0000000..54ecef8
--- /dev/null
+++ b/modules/styling/config/widgets/indicators/colorscheme.js
@@ -0,0 +1,51 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
+import { showColorScheme } from '../../variables.js';
+
+const ColorBox = ({
+ name = 'Color',
+ ...rest
+}) => Box({
+ ...rest,
+ homogeneous: true,
+ children: [
+ Label({
+ label: `${name}`,
+ })
+ ]
+})
+
+const ColorschemeContent = () => Box({
+ className: 'osd-colorscheme spacing-v-5',
+ vertical: true,
+ hpack: 'center',
+ children: [
+ Label({
+ xalign: 0,
+ className: 'txt-norm titlefont txt',
+ label: 'Colorscheme',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ children: [
+ ColorBox({ name: 'P', className: 'osd-color osd-color-primary' }),
+ ColorBox({ name: 'P-c', className: 'osd-color osd-color-primaryContainer' }),
+ ColorBox({ name: 'S', className: 'osd-color osd-color-secondary' }),
+ ColorBox({ name: 'S-c', className: 'osd-color osd-color-secondaryContainer' }),
+ ColorBox({ name: 'Sf-v', className: 'osd-color osd-color-surfaceVariant' }),
+ ColorBox({ name: 'Sf', className: 'osd-color osd-color-surface' }),
+ ColorBox({ name: 'Bg', className: 'osd-color osd-color-background' }),
+ ]
+ })
+ ]
+});
+
+export default () => Widget.Revealer({
+ transition: 'slide_down',
+ transitionDuration: 200,
+ child: ColorschemeContent(),
+ setup: (self) => self.hook(showColorScheme, (revealer) => {
+ revealer.revealChild = showColorScheme.value;
+ }),
+})
diff --git a/modules/styling/config/widgets/indicators/indicatorvalues.js b/modules/styling/config/widgets/indicators/indicatorvalues.js
new file mode 100644
index 0000000..2b2980f
--- /dev/null
+++ b/modules/styling/config/widgets/indicators/indicatorvalues.js
@@ -0,0 +1,87 @@
+// This file is for brightness/volume indicators
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
+const { Box, Label, ProgressBar } = Widget;
+import { MarginRevealer } from '../../lib/advancedwidgets.js';
+import Brightness from '../../services/brightness.js';
+import Indicator from '../../services/indicator.js';
+
+const OsdValue = (name, labelSetup, progressSetup, props = {}) => {
+ const valueName = Label({
+ xalign: 0, yalign: 0, hexpand: true,
+ className: 'osd-label',
+ label: `${name}`,
+ });
+ const valueNumber = Label({
+ hexpand: false, className: 'osd-value-txt',
+ setup: labelSetup,
+ });
+ return Box({ // Volume
+ ...props,
+ vertical: true,
+ hexpand: true,
+ className: 'osd-bg osd-value',
+ attribute: {
+ 'disable': () => {
+ valueNumber.label = '';
+ }
+ },
+ children: [
+ Box({
+ vexpand: true,
+ children: [
+ valueName,
+ valueNumber,
+ ]
+ }),
+ ProgressBar({
+ className: 'osd-progress',
+ hexpand: true,
+ vertical: false,
+ setup: progressSetup,
+ })
+ ],
+ });
+}
+
+export default () => {
+ const brightnessIndicator = OsdValue('Brightness',
+ (self) => self.hook(Brightness, self => {
+ self.label = `${Math.round(Brightness.screen_value * 100)}`;
+ }, 'notify::screen-value'),
+ (self) => self.hook(Brightness, (progress) => {
+ const updateValue = Brightness.screen_value;
+ progress.value = updateValue;
+ }, 'notify::screen-value'),
+ )
+
+ const volumeIndicator = OsdValue('Volume',
+ (self) => self.hook(Audio, (label) => {
+ label.label = `${Math.round(Audio.speaker?.volume * 100)}`;
+ }),
+ (self) => self.hook(Audio, (progress) => {
+ const updateValue = Audio.speaker?.volume;
+ if (!isNaN(updateValue)) progress.value = updateValue;
+ }),
+ );
+ return MarginRevealer({
+ transition: 'slide_down',
+ showClass: 'osd-show',
+ hideClass: 'osd-hide',
+ extraSetup: (self) => self
+ .hook(Indicator, (revealer, value) => {
+ if (value > -1) revealer.attribute.show();
+ else revealer.attribute.hide();
+ }, 'popup')
+ ,
+ child: Box({
+ hpack: 'center',
+ vertical: false,
+ className: 'spacing-h--10',
+ children: [
+ brightnessIndicator,
+ volumeIndicator,
+ ]
+ })
+ });
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/indicators/main.js b/modules/styling/config/widgets/indicators/main.js
new file mode 100644
index 0000000..db3fc4c
--- /dev/null
+++ b/modules/styling/config/widgets/indicators/main.js
@@ -0,0 +1,33 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Indicator from '../../services/indicator.js';
+import IndicatorValues from './indicatorvalues.js';
+import MusicControls from './musiccontrols.js';
+import ColorScheme from './colorscheme.js';
+import NotificationPopups from './notificationpopups.js';
+
+export default (monitor = 0) => Widget.Window({
+ name: `indicator${monitor}`,
+ monitor,
+ className: 'indicator',
+ layer: 'overlay',
+ // exclusivity: 'ignore',
+ visible: true,
+ anchor: ['top'],
+ child: Widget.EventBox({
+ onHover: () => { //make the widget hide when hovering
+ Indicator.popup(-1);
+ },
+ child: Widget.Box({
+ vertical: true,
+ className: 'osd-window',
+ css: 'min-height: 2px;',
+ children: [
+ IndicatorValues(),
+ MusicControls(),
+ NotificationPopups(),
+ ColorScheme(),
+ ]
+ })
+ }),
+});
+
diff --git a/modules/styling/config/widgets/indicators/musiccontrols.js b/modules/styling/config/widgets/indicators/musiccontrols.js
new file mode 100644
index 0000000..922d5f5
--- /dev/null
+++ b/modules/styling/config/widgets/indicators/musiccontrols.js
@@ -0,0 +1,458 @@
+const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
+
+const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
+import { MarginRevealer } from '../../lib/advancedwidgets.js';
+import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
+import { MaterialIcon } from '../../lib/materialicon.js';
+import { showMusicControls } from '../../variables.js';
+
+function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/colormode.txt`;
+const lightDark = Utils.readFile(expandTilde(LIGHTDARK_FILE_LOCATION)).trim();
+const COVER_COLORSCHEME_SUFFIX = '_colorscheme.css';
+const PREFERRED_PLAYER = 'plasma-browser-integration';
+var lastCoverPath = '';
+
+function isRealPlayer(player) {
+ return (
+ !player.busName.startsWith('org.mpris.MediaPlayer2.firefox') && // Firefox mpris dbus is useless
+ !player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') && // Doesn't have cover art
+ !player.busName.endsWith('.mpd') // Non-instance mpd bus
+ );
+}
+
+export const getPlayer = (name = PREFERRED_PLAYER) => Mpris.getPlayer(name) || Mpris.players[0] || null;
+function lengthStr(length) {
+ const min = Math.floor(length / 60);
+ const sec = Math.floor(length % 60);
+ const sec0 = sec < 10 ? '0' : '';
+ return `${min}:${sec0}${sec}`;
+}
+function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+function detectMediaSource(link) {
+ if (link.startsWith("file://")) {
+ if (link.includes('firefox-mpris'))
+ return ' Firefox'
+ return " File";
+ }
+ let url = link.replace(/(^\w+:|^)\/\//, '');
+ let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1];
+ if (domain == 'ytimg.com') return ' Youtube';
+ if (domain == 'discordapp.net') return ' Discord';
+ if (domain == 'sndcdn.com') return ' SoundCloud';
+ return domain;
+}
+
+const DEFAULT_MUSIC_FONT = 'Gabarito, sans-serif';
+function getTrackfont(player) {
+ const title = player.trackTitle;
+ const artists = player.trackArtists.join(' ');
+ if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo'))
+ return 'Chakra Petch'; // Rigid square replacement
+ if (title.includes('東方'))
+ return 'Crimson Text, serif'; // Serif for Touhou stuff
+ return DEFAULT_MUSIC_FONT;
+}
+function trimTrackTitle(title) {
+ if (!title) return '';
+ const cleanRegexes = [
+ /【[^】]*】/, // Touhou n weeb stuff
+ /\[FREE DOWNLOAD\]/, // F-777
+ ];
+ cleanRegexes.forEach((expr) => title.replace(expr, ''));
+ return title;
+}
+
+const TrackProgress = ({ player, ...rest }) => {
+ const _updateProgress = (circprog) => {
+ // const player = Mpris.getPlayer();
+ if (!player) return;
+ // Set circular progress (see definition of AnimatedCircProg for explanation)
+ circprog.css = `font-size: ${Math.max(player.position / player.length * 100, 0)}px;`
+ }
+ return AnimatedCircProg({
+ ...rest,
+ className: 'osd-music-circprog',
+ vpack: 'center',
+ extraSetup: (self) => self
+ .hook(Mpris, _updateProgress)
+ .poll(3000, _updateProgress)
+ ,
+ })
+}
+
+const TrackTitle = ({ player, ...rest }) => Label({
+ ...rest,
+ label: 'No music playing',
+ xalign: 0,
+ truncate: 'end',
+ // wrap: true,
+ className: 'osd-music-title',
+ setup: (self) => self.hook(player, (self) => {
+ // Player name
+ self.label = player.trackTitle.length > 0 ? trimTrackTitle(player.trackTitle) : 'No media';
+ // Font based on track/artist
+ const fontForThisTrack = getTrackfont(player);
+ self.css = `font-family: ${fontForThisTrack}, ${DEFAULT_MUSIC_FONT};`;
+ }, 'notify::track-title'),
+});
+
+const TrackArtists = ({ player, ...rest }) => Label({
+ ...rest,
+ xalign: 0,
+ className: 'osd-music-artists',
+ truncate: 'end',
+ setup: (self) => self.hook(player, (self) => {
+ self.label = player.trackArtists.length > 0 ? player.trackArtists.join(', ') : '';
+ }, 'notify::track-artists'),
+})
+
+const CoverArt = ({ player, ...rest }) => {
+ const fallbackCoverArt = Box({ // Fallback
+ className: 'osd-music-cover-fallback',
+ homogeneous: true,
+ children: [Label({
+ className: 'icon-material txt-gigantic txt-thin',
+ label: 'music_note',
+ })]
+ });
+ const coverArtDrawingArea = Widget.DrawingArea({ className: 'osd-music-cover-art' });
+ const coverArtDrawingAreaStyleContext = coverArtDrawingArea.get_style_context();
+ const realCoverArt = Box({
+ className: 'osd-music-cover-art',
+ homogeneous: true,
+ children: [coverArtDrawingArea],
+ attribute: {
+ 'pixbuf': null,
+ 'showImage': (self, imagePath) => {
+ const borderRadius = coverArtDrawingAreaStyleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
+ const frameHeight = coverArtDrawingAreaStyleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
+ const frameWidth = coverArtDrawingAreaStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
+ let imageHeight = frameHeight;
+ let imageWidth = frameWidth;
+ // Get image dimensions
+ execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
+ .then((output) => {
+ const imageDimensions = JSON.parse(output);
+ const imageAspectRatio = imageDimensions.w / imageDimensions.h;
+ const displayedAspectRatio = imageWidth / imageHeight;
+ if (imageAspectRatio >= displayedAspectRatio) {
+ imageWidth = imageHeight * imageAspectRatio;
+ } else {
+ imageHeight = imageWidth / imageAspectRatio;
+ }
+ // Real stuff
+ // TODO: fix memory leak(?)
+ // if (self.attribute.pixbuf) {
+ // self.attribute.pixbuf.unref();
+ // self.attribute.pixbuf = null;
+ // }
+ self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, imageWidth, imageHeight);
+
+ coverArtDrawingArea.set_size_request(frameWidth, frameHeight);
+ coverArtDrawingArea.connect("draw", (widget, cr) => {
+ // Clip a rounded rectangle area
+ cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
+ cr.arc(frameWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
+ cr.arc(frameWidth - borderRadius, frameHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
+ cr.arc(borderRadius, frameHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
+ cr.closePath();
+ cr.clip();
+ // Paint image as bg, centered
+ Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
+ frameWidth / 2 - imageWidth / 2,
+ frameHeight / 2 - imageHeight / 2
+ );
+ cr.paint();
+ });
+ }).catch(print)
+ },
+ 'updateCover': (self) => {
+ // const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this
+ // Player closed
+ // Note that cover path still remains, so we're checking title
+ if (!player || player.trackTitle == "") {
+ self.css = `background-image: none;`;
+ App.applyCss(`${App.configDir}/style.css`);
+ return;
+ }
+
+ const coverPath = player.coverPath;
+ const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`;
+ if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
+ // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
+ Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
+ }
+ lastCoverPath = player.coverPath;
+
+ // If a colorscheme has already been generated, skip generation
+ if (fileExists(stylePath)) {
+ // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
+ self.attribute.showImage(self, coverPath)
+ App.applyCss(stylePath);
+ return;
+ }
+
+ // Generate colors
+ execAsync(['bash', '-c',
+ `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`])
+ .then(() => {
+ exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`)
+ exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`);
+ exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`);
+ // self.css = `background-image: url('${coverPath}');`;
+ Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
+ App.applyCss(`${stylePath}`);
+ })
+ .catch(print);
+ },
+ },
+ setup: (self) => self
+ .hook(player, (self) => {
+ self.attribute.updateCover(self);
+ }, 'notify::cover-path')
+ ,
+ });
+ return Box({
+ ...rest,
+ className: 'osd-music-cover',
+ children: [
+ Widget.Overlay({
+ child: fallbackCoverArt,
+ overlays: [realCoverArt],
+ })
+ ],
+ })
+}
+
+const TrackControls = ({ player, ...rest }) => Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_right',
+ transitionDuration: 200,
+ child: Widget.Box({
+ ...rest,
+ vpack: 'center',
+ className: 'osd-music-controls spacing-h-3',
+ children: [
+ Button({
+ className: 'osd-music-controlbtn',
+ onClicked: () => player.previous(),
+ child: Label({
+ className: 'icon-material osd-music-controlbtn-txt',
+ label: 'skip_previous',
+ })
+ }),
+ Button({
+ className: 'osd-music-controlbtn',
+ onClicked: () => player.next(),
+ child: Label({
+ className: 'icon-material osd-music-controlbtn-txt',
+ label: 'skip_next',
+ })
+ }),
+ ],
+ }),
+ setup: (self) => self.hook(Mpris, (self) => {
+ // const player = Mpris.getPlayer();
+ if (!player)
+ self.revealChild = false;
+ else
+ self.revealChild = true;
+ }, 'notify::play-back-status'),
+});
+
+const TrackSource = ({ player, ...rest }) => Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_left',
+ transitionDuration: 200,
+ child: Widget.Box({
+ ...rest,
+ className: 'osd-music-pill spacing-h-5',
+ homogeneous: true,
+ children: [
+ Label({
+ hpack: 'fill',
+ justification: 'center',
+ className: 'icon-nerd',
+ setup: (self) => self.hook(player, (self) => {
+ self.label = detectMediaSource(player.trackCoverUrl);
+ }, 'notify::cover-path'),
+ }),
+ ],
+ }),
+ setup: (self) => self.hook(Mpris, (self) => {
+ const mpris = Mpris.getPlayer('');
+ if (!mpris)
+ self.revealChild = false;
+ else
+ self.revealChild = true;
+ }),
+});
+
+const TrackTime = ({ player, ...rest }) => {
+ return Widget.Revealer({
+ revealChild: false,
+ transition: 'slide_left',
+ transitionDuration: 200,
+ child: Widget.Box({
+ ...rest,
+ vpack: 'center',
+ className: 'osd-music-pill spacing-h-5',
+ children: [
+ Label({
+ setup: (self) => self.poll(1000, (self) => {
+ // const player = Mpris.getPlayer();
+ if (!player) return;
+ self.label = lengthStr(player.position);
+ }),
+ }),
+ Label({ label: '/' }),
+ Label({
+ setup: (self) => self.hook(Mpris, (self) => {
+ // const player = Mpris.getPlayer();
+ if (!player) return;
+ self.label = lengthStr(player.length);
+ }),
+ }),
+ ],
+ }),
+ setup: (self) => self.hook(Mpris, (self) => {
+ if (!player) self.revealChild = false;
+ else self.revealChild = true;
+ }),
+ })
+}
+
+const PlayState = ({ player }) => {
+ var position = 0;
+ const trackCircProg = TrackProgress({ player: player });
+ return Widget.Button({
+ className: 'osd-music-playstate',
+ child: Widget.Overlay({
+ child: trackCircProg,
+ overlays: [
+ Widget.Button({
+ className: 'osd-music-playstate-btn',
+ onClicked: () => player.playPause(),
+ child: Widget.Label({
+ justification: 'center',
+ hpack: 'fill',
+ vpack: 'center',
+ setup: (self) => self.hook(player, (label) => {
+ label.label = `${player.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
+ }, 'notify::play-back-status'),
+ }),
+ }),
+ ],
+ passThrough: true,
+ })
+ });
+}
+
+const MusicControlsWidget = (player) => Box({
+ className: 'osd-music spacing-h-20 test',
+ children: [
+ CoverArt({ player: player, vpack: 'center' }),
+ Box({
+ vertical: true,
+ className: 'spacing-v-5 osd-music-info',
+ children: [
+ Box({
+ vertical: true,
+ vpack: 'center',
+ hexpand: true,
+ children: [
+ TrackTitle({ player: player }),
+ TrackArtists({ player: player }),
+ ]
+ }),
+ Box({ vexpand: true }),
+ Box({
+ className: 'spacing-h-10',
+ setup: (box) => {
+ box.pack_start(TrackControls({ player: player }), false, false, 0);
+ box.pack_end(PlayState({ player: player }), false, false, 0);
+ box.pack_end(TrackTime({ player: player }), false, false, 0)
+ // box.pack_end(TrackSource({ vpack: 'center', player: player }), false, false, 0);
+ }
+ })
+ ]
+ })
+ ]
+})
+
+export default () => Revealer({
+ transition: 'slide_down',
+ transitionDuration: 150,
+ revealChild: false,
+ child: Box({
+ setup: (self) => self.hook(Mpris, box => {
+ box.children.forEach(child => {
+ child.destroy();
+ child = null;
+ });
+ Mpris.players.forEach((player, i) => {
+ if (isRealPlayer(player)) {
+ const newInstance = MusicControlsWidget(player);
+ box.add(newInstance);
+ }
+ });
+ }, 'notify::players'),
+ }),
+ setup: (self) => self.hook(showMusicControls, (revealer) => {
+ revealer.revealChild = showMusicControls.value;
+ }),
+})
+
+// export default () => MarginRevealer({
+// transition: 'slide_down',
+// revealChild: false,
+// showClass: 'osd-show',
+// hideClass: 'osd-hide',
+// child: Box({
+// setup: (self) => self.hook(Mpris, box => {
+// let foundPlayer = false;
+// Mpris.players.forEach((player, i) => {
+// if (isRealPlayer(player)) {
+// foundPlayer = true;
+// box.children.forEach(child => {
+// child.destroy();
+// child = null;
+// });
+// const newInstance = MusicControlsWidget(player);
+// box.children = [newInstance];
+// }
+// });
+
+// if (!foundPlayer) {
+// const children = box.get_children();
+// for (let i = 0; i < children.length; i++) {
+// const child = children[i];
+// child.destroy();
+// child = null;
+// }
+// return;
+// }
+// }, 'notify::players'),
+// }),
+// setup: (self) => self.hook(showMusicControls, (revealer) => {
+// if (showMusicControls.value) revealer.attribute.show();
+// else revealer.attribute.hide();
+// }),
+// })
diff --git a/modules/styling/config/widgets/indicators/notificationpopups.js b/modules/styling/config/widgets/indicators/notificationpopups.js
new file mode 100644
index 0000000..f3cd59e
--- /dev/null
+++ b/modules/styling/config/widgets/indicators/notificationpopups.js
@@ -0,0 +1,45 @@
+// This file is for popup notifications
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
+const { Box } = Widget;
+import Notification from '../../lib/notification.js';
+
+export default () => Box({
+ vertical: true,
+ hpack: 'center',
+ className: 'osd-notifs spacing-v-5-revealer',
+ attribute: {
+ 'map': new Map(),
+ 'dismiss': (box, id, force = false) => {
+ if (!id || !box.attribute.map.has(id))
+ return;
+ const notifWidget = box.attribute.map.get(id);
+ if (notifWidget == null || notifWidget.attribute.hovered && !force)
+ return; // cuz already destroyed
+
+ notifWidget.revealChild = false;
+ notifWidget.attribute.destroyWithAnims();
+ box.attribute.map.delete(id);
+ },
+ 'notify': (box, id) => {
+ if (!id || Notifications.dnd) return;
+ if (!Notifications.getNotification(id)) return;
+
+ box.attribute.map.delete(id);
+
+ const notif = Notifications.getNotification(id);
+ const newNotif = Notification({
+ notifObject: notif,
+ isPopup: true,
+ });
+ box.attribute.map.set(id, newNotif);
+ box.pack_end(box.attribute.map.get(id), false, false, 0);
+ box.show_all();
+ },
+ },
+ setup: (self) => self
+ .hook(Notifications, (box, id) => box.attribute.notify(box, id), 'notified')
+ .hook(Notifications, (box, id) => box.attribute.dismiss(box, id), 'dismissed')
+ .hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed')
+ ,
+});
diff --git a/modules/styling/config/widgets/onscreenkeyboard/main.js b/modules/styling/config/widgets/onscreenkeyboard/main.js
new file mode 100644
index 0000000..18554c4
--- /dev/null
+++ b/modules/styling/config/widgets/onscreenkeyboard/main.js
@@ -0,0 +1,10 @@
+import PopupWindow from '../../lib/popupwindow.js';
+import OnScreenKeyboard from "./onscreenkeyboard.js";
+
+export default () => PopupWindow({
+ anchor: ['bottom'],
+ name: 'osk',
+ showClassName: 'osk-show',
+ hideClassName: 'osk-hide',
+ child: OnScreenKeyboard(),
+});
diff --git a/modules/styling/config/widgets/onscreenkeyboard/onscreenkeyboard.js b/modules/styling/config/widgets/onscreenkeyboard/onscreenkeyboard.js
new file mode 100644
index 0000000..d641436
--- /dev/null
+++ b/modules/styling/config/widgets/onscreenkeyboard/onscreenkeyboard.js
@@ -0,0 +1,260 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { Box, EventBox, Button, Revealer } = Widget;
+const { execAsync } = Utils;
+import { MaterialIcon } from '../../lib/materialicon.js';
+import { separatorLine } from '../../lib/separator.js';
+import { defaultOskLayout, oskLayouts } from '../../data/keyboardlayouts.js';
+import { setupCursorHoverGrab } from '../../lib/cursorhover.js';
+
+const keyboardLayout = defaultOskLayout;
+const keyboardJson = oskLayouts[keyboardLayout];
+execAsync(`ydotoold`).catch(print); // Start ydotool daemon
+
+function releaseAllKeys() {
+ const keycodes = Array.from(Array(249).keys());
+ execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
+ .then(console.log('[OSK] Released all keys'))
+ .catch(print);
+}
+class ShiftMode {
+ static Off = new ShiftMode('Off');
+ static Normal = new ShiftMode('Normal');
+ static Locked = new ShiftMode('Locked');
+
+ constructor(name) {
+ this.name = name;
+ }
+ toString() {
+ return `ShiftMode.${this.name}`;
+ }
+}
+var modsPressed = false;
+
+const topDecor = Box({
+ vertical: true,
+ children: [
+ Box({
+ hpack: 'center',
+ className: 'osk-dragline',
+ homogeneous: true,
+ children: [EventBox({
+ setup: setupCursorHoverGrab,
+ })]
+ })
+ ]
+});
+
+const keyboardControlButton = (icon, text, runFunction) => Button({
+ className: 'osk-control-button spacing-h-10',
+ onClicked: () => runFunction(),
+ child: Widget.Box({
+ children: [
+ MaterialIcon(icon, 'norm'),
+ Widget.Label({
+ label: `${text}`,
+ }),
+ ]
+ })
+})
+
+const keyboardControls = Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ Button({
+ className: 'osk-control-button txt-norm icon-material',
+ onClicked: () => {
+ releaseAllKeys();
+ App.toggleWindow('osk');
+ },
+ label: 'keyboard_hide',
+ }),
+ Button({
+ className: 'osk-control-button txt-norm',
+ label: `${keyboardJson['name_short']}`,
+ }),
+ Button({
+ className: 'osk-control-button txt-norm icon-material',
+ onClicked: () => { // TODO: Proper clipboard widget, since fuzzel doesn't receive mouse inputs
+ execAsync([`bash`, `-c`, "pkill fuzzel || cliphist list | fuzzel --no-fuzzy --dmenu | cliphist decode | wl-copy"]).catch(print);
+ },
+ label: 'assignment',
+ }),
+ ]
+})
+
+var shiftMode = ShiftMode.Off;
+var shiftButton;
+var rightShiftButton;
+var allButtons = [];
+const keyboardItself = (kbJson) => {
+ return Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: kbJson.keys.map(row => Box({
+ vertical: false,
+ className: 'spacing-h-5',
+ children: row.map(key => {
+ return Button({
+ className: `osk-key osk-key-${key.shape}`,
+ hexpand: ["space", "expand"].includes(key.shape),
+ label: key.label,
+ attribute:
+ {key: key},
+ setup: (button) => {
+ let pressed = false;
+ allButtons = allButtons.concat(button);
+ if (key.keytype == "normal") {
+ button.connect('pressed', () => { // mouse down
+ execAsync(`ydotool key ${key.keycode}:1`);
+ });
+ button.connect('clicked', () => { // release
+ execAsync(`ydotool key ${key.keycode}:0`);
+
+ if (shiftMode == ShiftMode.Normal) {
+ shiftMode = ShiftMode.Off;
+ if (typeof shiftButton !== 'undefined') {
+ execAsync(`ydotool key 42:0`);
+ shiftButton.toggleClassName('osk-key-active', false);
+ }
+ if (typeof rightShiftButton !== 'undefined') {
+ execAsync(`ydotool key 54:0`);
+ rightShiftButton.toggleClassName('osk-key-active', false);
+ }
+ allButtons.forEach(button => {
+ if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label;
+ })
+ }
+ });
+ }
+ else if (key.keytype == "modkey") {
+ button.connect('pressed', () => { // release
+ if (pressed) {
+ execAsync(`ydotool key ${key.keycode}:0`);
+ button.toggleClassName('osk-key-active', false);
+ pressed = false;
+ if (key.keycode == 100) { // Alt Gr button
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.label; });
+ }
+ }
+ else {
+ execAsync(`ydotool key ${key.keycode}:1`);
+ button.toggleClassName('osk-key-active', true);
+ if (!(key.keycode == 42 || key.keycode == 54)) pressed = true;
+ else switch (shiftMode.name) { // This toggles the shift button state
+ case "Off": {
+ shiftMode = ShiftMode.Normal;
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.labelShift; })
+ if (typeof shiftButton !== 'undefined') {
+ shiftButton.toggleClassName('osk-key-active', true);
+ }
+ if (typeof rightShiftButton !== 'undefined') {
+ rightShiftButton.toggleClassName('osk-key-active', true);
+ }
+ } break;
+ case "Normal": {
+ shiftMode = ShiftMode.Locked;
+ if (typeof shiftButton !== 'undefined') shiftButton.label = key.labelCaps;
+ if (typeof rightShiftButton !== 'undefined') rightShiftButton.label = key.labelCaps;
+ } break;
+ case "Locked": {
+ shiftMode = ShiftMode.Off;
+ if (typeof shiftButton !== 'undefined') {
+ shiftButton.label = key.label;
+ shiftButton.toggleClassName('osk-key-active', false);
+ }
+ if (typeof rightShiftButton !== 'undefined') {
+ rightShiftButton.label = key.label;
+ rightShiftButton.toggleClassName('osk-key-active', false);
+ }
+ execAsync(`ydotool key ${key.keycode}:0`);
+
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label; }
+ )};
+ }
+ if (key.keycode == 100) { // Alt Gr button
+ allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.labelAlt; });
+ }
+ modsPressed = true;
+ }
+ });
+ if (key.keycode == 42) shiftButton = button;
+ else if (key.keycode == 54) rightShiftButton = button;
+ }
+ }
+ })
+ })
+ }))
+ })
+}
+
+const keyboardWindow = Box({
+ vexpand: true,
+ hexpand: true,
+ vertical: true,
+ className: 'osk-window spacing-v-5',
+ children: [
+ topDecor,
+ Box({
+ className: 'osk-body spacing-h-10',
+ children: [
+ keyboardControls,
+ separatorLine,
+ keyboardItself(keyboardJson),
+ ],
+ })
+ ],
+ setup: (self) => self.hook(App, (box, name, visible) => { // Update on open
+ if (name == 'osk' && visible) {
+ keyboardWindow.setCss(`margin-bottom: -0px;`);
+ }
+ }),
+});
+
+const gestureEvBox = EventBox({ child: keyboardWindow })
+const gesture = Gtk.GestureDrag.new(gestureEvBox);
+gesture.connect('drag-begin', async () => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.sendMessage('j/cursorpos').then((out) => {
+ gesture.startY = JSON.parse(out).y;
+ }).catch(print);
+ } catch {
+ return;
+ }
+});
+gesture.connect('drag-update', async () => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ Hyprland.sendMessage('j/cursorpos').then((out) => {
+ const currentY = JSON.parse(out).y;
+ const offset = gesture.startY - currentY;
+
+ if (offset > 0) return;
+
+ keyboardWindow.setCss(`
+ margin-bottom: ${offset}px;
+ `);
+ }).catch(print);
+ } catch {
+ return;
+ }
+});
+gesture.connect('drag-end', () => {
+ var offset = gesture.get_offset()[2];
+ if (offset > 50) {
+ App.closeWindow('osk');
+ }
+ else {
+ keyboardWindow.setCss(`
+ transition: margin-bottom 170ms cubic-bezier(0.05, 0.7, 0.1, 1);
+ margin-bottom: 0px;
+ `);
+ }
+})
+
+export default () => gestureEvBox;
diff --git a/modules/styling/config/widgets/overview/actions.js b/modules/styling/config/widgets/overview/actions.js
new file mode 100644
index 0000000..766cf45
--- /dev/null
+++ b/modules/styling/config/widgets/overview/actions.js
@@ -0,0 +1,28 @@
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+
+function moveClientToWorkspace(address, workspace) {
+ Utils.execAsync(['bash', '-c', `hyprctl dispatch movetoworkspacesilent ${workspace},address:${address} &`]);
+}
+
+export function dumpToWorkspace(from, to) {
+ if (from == to) return;
+ Hyprland.clients.forEach(client => {
+ if (client.workspace.id == from) {
+ moveClientToWorkspace(client.address, to);
+ }
+ });
+}
+
+export function swapWorkspace(workspaceA, workspaceB) {
+ if (workspaceA == workspaceB) return;
+ const clientsA = [];
+ const clientsB = [];
+ Hyprland.clients.forEach(client => {
+ if (client.workspace.id == workspaceA) clientsA.push(client.address);
+ if (client.workspace.id == workspaceB) clientsB.push(client.address);
+ });
+
+ clientsA.forEach((address) => moveClientToWorkspace(address, workspaceB));
+ clientsB.forEach((address) => moveClientToWorkspace(address, workspaceA));
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/overview/main.js b/modules/styling/config/widgets/overview/main.js
new file mode 100644
index 0000000..436b444
--- /dev/null
+++ b/modules/styling/config/widgets/overview/main.js
@@ -0,0 +1,18 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { SearchAndWindows } from "./windowcontent.js";
+
+export default () => Widget.Window({
+ name: 'overview',
+ exclusivity: 'ignore',
+ keymode: 'exclusive',
+ popup: true,
+ visible: false,
+ anchor: ['top'],
+ layer: 'overlay',
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ SearchAndWindows(),
+ ]
+ }),
+})
diff --git a/modules/styling/config/widgets/overview/miscfunctions.js b/modules/styling/config/widgets/overview/miscfunctions.js
new file mode 100644
index 0000000..10f3807
--- /dev/null
+++ b/modules/styling/config/widgets/overview/miscfunctions.js
@@ -0,0 +1,143 @@
+const { Gio, GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+import Todo from "../../services/todo.js";
+
+export function hasUnterminatedBackslash(inputString) {
+ // Use a regular expression to match a trailing odd number of backslashes
+ const regex = /\\+$/;
+ return regex.test(inputString);
+}
+
+export function launchCustomCommand(command) {
+ const args = command.split(' ');
+ if (args[0] == '>raw') { // Mouse raw input
+ execAsync([`bash`, `-c`, `hyprctl keyword input:force_no_accel $(( 1 - $(hyprctl getoption input:force_no_accel -j | gojq ".int") ))`, `&`]).catch(print);
+ }
+ else if (args[0] == '>img') { // Change wallpaper
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchwall.sh`, `&`]).catch(print);
+ }
+ else if (args[0] == '>color') { // Generate colorscheme from color picker
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh`, `&`]).catch(print);
+ }
+ else if (args[0] == '>light') { // Light mode
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "-l" > ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>dark') { // Dark mode
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "" > ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`])
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>badapple') { // Light mode
+ execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/applycolor.sh --bad-apple`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>material') { // Light mode
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "material" > ${GLib.get_user_cache_dir()}/ags/user/colorbackend.txt`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>pywal') { // Dark mode
+ execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "pywal" > ${GLib.get_user_cache_dir()}/ags/user/colorbackend.txt`]).catch(print)
+ .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
+ .catch(print);
+ }
+ else if (args[0] == '>todo') { // Todo
+ Todo.add(args.slice(1).join(' '));
+ }
+ else if (args[0] == '>shutdown') { // Shut down
+ execAsync([`bash`, `-c`, `systemctl poweroff`]).catch(print);
+ }
+ else if (args[0] == '>reboot') { // Reboot
+ execAsync([`bash`, `-c`, `systemctl reboot`]).catch(print);
+ }
+ else if (args[0] == '>sleep') { // Sleep
+ execAsync([`bash`, `-c`, `systemctl suspend`]).catch(print);
+ }
+ else if (args[0] == '>logout') { // Log out
+ execAsync([`bash`, `-c`, `pkill Hyprland || pkill sway`]).catch(print);
+ }
+}
+
+export function execAndClose(command, terminal) {
+ App.closeWindow('overview');
+ if (terminal) {
+ execAsync([`bash`, `-c`, `foot fish -C "${command}"`, `&`]).catch(print);
+ }
+ else
+ execAsync(command).catch(print);
+}
+
+export function couldBeMath(str) {
+ const regex = /^[0-9.+*/-]/;
+ return regex.test(str);
+}
+
+export function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+function getFileIcon(fileInfo) {
+ let icon = fileInfo.get_icon();
+ if (icon) {
+ // Get the icon's name
+ return icon.get_names()[0];
+ } else {
+ // Default icon for files
+ return 'text-x-generic';
+ }
+}
+
+export function ls({ path = '~', silent = false }) {
+ let contents = [];
+ try {
+ let expandedPath = expandTilde(path);
+ if (expandedPath.endsWith('/'))
+ expandedPath = expandedPath.slice(0, -1);
+ let folder = Gio.File.new_for_path(expandedPath);
+
+ let enumerator = folder.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+ let fileInfo;
+ while ((fileInfo = enumerator.next_file(null)) !== null) {
+ let fileName = fileInfo.get_display_name();
+ let fileType = fileInfo.get_file_type();
+
+ let item = {
+ parentPath: expandedPath,
+ name: fileName,
+ type: fileType === Gio.FileType.DIRECTORY ? 'folder' : 'file',
+ icon: getFileIcon(fileInfo),
+ };
+
+ // Add file extension for files
+ if (fileType === Gio.FileType.REGULAR) {
+ let fileExtension = fileName.split('.').pop();
+ item.type = `${fileExtension}`;
+ }
+
+ contents.push(item);
+ contents.sort((a, b) => {
+ const aIsFolder = a.type.startsWith('folder');
+ const bIsFolder = b.type.startsWith('folder');
+ if (aIsFolder && !bIsFolder) {
+ return -1;
+ } else if (!aIsFolder && bIsFolder) {
+ return 1;
+ } else {
+ return a.name.localeCompare(b.name); // Sort alphabetically within folders and files
+ }
+ });
+ }
+ } catch (e) {
+ if (!silent) console.log(e);
+ }
+ return contents;
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/overview/overview_hyprland.js b/modules/styling/config/widgets/overview/overview_hyprland.js
new file mode 100644
index 0000000..0ce537b
--- /dev/null
+++ b/modules/styling/config/widgets/overview/overview_hyprland.js
@@ -0,0 +1,446 @@
+// TODO
+// - Make client destroy/create not destroy and recreate the whole thing
+// - Active ws hook optimization: only update when moving to next group
+//
+const { Gdk, Gtk } = imports.gi;
+const { Gravity } = imports.gi.Gdk;
+import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+const { execAsync, exec } = Utils;
+import { setupCursorHoverGrab } from "../../lib/cursorhover.js";
+import { dumpToWorkspace, swapWorkspace } from "./actions.js";
+
+const OVERVIEW_SCALE = 0.18;
+const NUM_OF_WORKSPACE_ROWS = 2;
+const NUM_OF_WORKSPACE_COLS = 5;
+const OVERVIEW_WS_NUM_SCALE = 0.09;
+const NUM_OF_WORKSPACES_SHOWN = NUM_OF_WORKSPACE_COLS * NUM_OF_WORKSPACE_ROWS;
+const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
+const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
+
+const overviewTick = Variable(false);
+
+function iconExists(iconName) {
+ let iconTheme = Gtk.IconTheme.get_default();
+ return iconTheme.has_icon(iconName);
+}
+
+function substitute(str) {
+ const subs = [
+ { from: 'code-url-handler', to: 'visual-studio-code' },
+ { from: 'Code', to: 'visual-studio-code' },
+ { from: 'GitHub Desktop', to: 'github-desktop' },
+ { from: 'wpsoffice', to: 'wps-office2019-kprometheus' },
+ { from: 'gnome-tweaks', to: 'org.gnome.tweaks' },
+ { from: 'Minecraft* 1.20.1', to: 'minecraft' },
+ { from: '', to: 'image-missing' },
+ ];
+
+ for (const { from, to } of subs) {
+ if (from === str)
+ return to;
+ }
+
+ if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case
+ return str;
+}
+export default () => {
+ const clientMap = new Map();
+ let workspaceGroup = 0;
+ const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
+ label: `${label}`,
+ setup: (menuItem) => {
+ let submenu = new Gtk.Menu();
+ submenu.className = 'menu';
+
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ const startWorkspace = offset + 1;
+ const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1;
+ for (let i = startWorkspace; i <= endWorkspace; i++) {
+ let button = new Gtk.MenuItem({
+ label: `Workspace ${i}`
+ });
+ button.connect("activate", () => {
+ // execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
+ actionFunc(thisWorkspace, i);
+ overviewTick.setValue(!overviewTick.value);
+ });
+ submenu.append(button);
+ }
+ menuItem.set_reserve_indicator(true);
+ menuItem.set_submenu(submenu);
+ }
+ })
+
+ const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, title, xwayland }, screenCoords) => {
+ const revealInfoCondition = (Math.min(w, h) * OVERVIEW_SCALE > 70);
+ if (w <= 0 || h <= 0 || (c === '' && title === '')) return null;
+ // Non-primary monitors
+ if (screenCoords.x != 0) x -= screenCoords.x;
+ if (screenCoords.y != 0) y -= screenCoords.y;
+ // Other offscreen adjustments
+ if (x + w <= 0) x += (Math.floor(x / SCREEN_WIDTH) * SCREEN_WIDTH);
+ else if (x < 0) { w = x + w; x = 0; }
+ if (y + h <= 0) x += (Math.floor(y / SCREEN_HEIGHT) * SCREEN_HEIGHT);
+ else if (y < 0) { h = y + h; y = 0; }
+ // Truncate if offscreen
+ if (x + w > SCREEN_WIDTH) w = SCREEN_WIDTH - x;
+ if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y;
+
+ const appIcon = Widget.Icon({
+ icon: substitute(c),
+ size: Math.min(w, h) * OVERVIEW_SCALE / 2.5,
+ });
+ return Widget.Button({
+ attribute: {
+ address, x, y, w, h, ws: id,
+ updateIconSize: (self) => {
+ appIcon.size = Math.min(self.attribute.w, self.attribute.h) * OVERVIEW_SCALE / 2.5;
+ },
+ },
+ className: 'overview-tasks-window',
+ hpack: 'start',
+ vpack: 'start',
+ css: `
+ margin-left: ${Math.round(x * OVERVIEW_SCALE)}px;
+ margin-top: ${Math.round(y * OVERVIEW_SCALE)}px;
+ margin-right: -${Math.round((x + w) * OVERVIEW_SCALE)}px;
+ margin-bottom: -${Math.round((y + h) * OVERVIEW_SCALE)}px;
+ `,
+ onClicked: (self) => {
+ Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
+ App.closeWindow('overview');
+ },
+ onMiddleClickRelease: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`),
+ onSecondaryClick: (button) => {
+ button.toggleClassName('overview-tasks-window-selected', true);
+ const menu = Widget.Menu({
+ className: 'menu',
+ children: [
+ Widget.MenuItem({
+ child: Widget.Label({
+ xalign: 0,
+ label: "Close (Middle-click)",
+ }),
+ onActivate: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`),
+ }),
+ ContextMenuWorkspaceArray({
+ label: "Dump windows to workspace",
+ actionFunc: dumpToWorkspace,
+ thisWorkspace: Number(id)
+ }),
+ ContextMenuWorkspaceArray({
+ label: "Swap windows with workspace",
+ actionFunc: swapWorkspace,
+ thisWorkspace: Number(id)
+ }),
+ ],
+ });
+ menu.connect("deactivate", () => {
+ button.toggleClassName('overview-tasks-window-selected', false);
+ })
+ menu.connect("selection-done", () => {
+ button.toggleClassName('overview-tasks-window-selected', false);
+ })
+ menu.popup_at_widget(button.get_parent(), Gravity.SOUTH, Gravity.NORTH, null); // Show menu below the button
+ button.connect("destroy", () => menu.destroy());
+ },
+ child: Widget.Box({
+ homogeneous: true,
+ child: Widget.Box({
+ vertical: true,
+ vpack: 'center',
+ className: 'spacing-v-5',
+ children: [
+ appIcon,
+ // TODO: Add xwayland tag instead of just having italics
+ // Widget.Revealer({
+ // transition: 'slide_down',
+ // revealChild: revealInfoCondition,
+ // child: Widget.Label({
+ // truncate: 'end',
+ // className: `${xwayland ? 'txt txt-italic' : 'txt'}`,
+ // css: `
+ // font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px;
+ // margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 10}px;
+ // `,
+ // // If the title is too short, include the class
+ // label: (title.length <= 1 ? `${c}: ${title}` : title),
+ // })
+ // })
+ ]
+ })
+ }),
+ tooltipText: `${c}: ${title}`,
+ setup: (button) => {
+ setupCursorHoverGrab(button);
+
+ button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
+ button.drag_source_set_icon_name(substitute(c));
+ // button.drag_source_set_icon_gicon(icon);
+
+ button.connect('drag-begin', (button) => { // On drag start, add the dragging class
+ button.toggleClassName('overview-tasks-window-dragging', true);
+ });
+ button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
+ data.set_text(address, address.length);
+ button.toggleClassName('overview-tasks-window-dragging', false);
+ });
+ },
+ });
+ }
+
+ const Workspace = (index) => {
+ // const fixed = Widget.Fixed({
+ // attribute: {
+ // put: (widget, x, y) => {
+ // fixed.put(widget, x, y);
+ // },
+ // move: (widget, x, y) => {
+ // fixed.move(widget, x, y);
+ // },
+ // }
+ // });
+ const fixed = Widget.Box({
+ attribute: {
+ put: (widget, x, y) => {
+ if (!widget.attribute) return;
+ // Note: x and y are already multiplied by OVERVIEW_SCALE
+ const newCss = `
+ margin-left: ${Math.round(x)}px;
+ margin-top: ${Math.round(y)}px;
+ margin-right: -${Math.round(x + (widget.attribute.w * OVERVIEW_SCALE))}px;
+ margin-bottom: -${Math.round(y + (widget.attribute.h * OVERVIEW_SCALE))}px;
+ `;
+ widget.css = newCss;
+ fixed.pack_start(widget, false, false, 0);
+ },
+ move: (widget, x, y) => {
+ if (!widget) return;
+ if (!widget.attribute) return;
+ // Note: x and y are already multiplied by OVERVIEW_SCALE
+ const newCss = `
+ margin-left: ${Math.round(x)}px;
+ margin-top: ${Math.round(y)}px;
+ margin-right: -${Math.round(x + (widget.attribute.w * OVERVIEW_SCALE))}px;
+ margin-bottom: -${Math.round(y + (widget.attribute.h * OVERVIEW_SCALE))}px;
+ `;
+ widget.css = newCss;
+ },
+ }
+ })
+ const WorkspaceNumber = (index) => Widget.Label({
+ className: 'overview-tasks-workspace-number',
+ label: `${index}`,
+ css: `
+ margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px;
+ font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px;
+ `,
+ })
+ const widget = Widget.Box({
+ className: 'overview-tasks-workspace',
+ vpack: 'center',
+ css: `
+ min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px;
+ min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px;
+ `,
+ children: [Widget.EventBox({
+ hexpand: true,
+ vexpand: true,
+ onPrimaryClick: () => {
+ Hyprland.sendMessage(`dispatch workspace ${index}`)
+ App.closeWindow('overview');
+ },
+ setup: (eventbox) => {
+ eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
+ eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
+ Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`)
+ overviewTick.setValue(!overviewTick.value);
+ });
+ },
+ child: fixed,
+ })],
+ });
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ fixed.attribute.put(WorkspaceNumber(offset + index), 0, 0);
+ widget.clear = () => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ clientMap.forEach((client, address) => {
+ if (!client || client.ws !== offset + index) return;
+ client.destroy();
+ client = null;
+ clientMap.delete(address);
+ });
+ }
+ widget.set = (clientJson, screenCoords) => {
+ let c = clientMap.get(clientJson.address);
+ if (c) {
+ if (c.attribute?.ws !== clientJson.workspace.id) {
+ c.destroy();
+ c = null;
+ clientMap.delete(clientJson.address);
+ }
+ else if (c) {
+ c.attribute.w = clientJson.size[0];
+ c.attribute.h = clientJson.size[1];
+ c.attribute.updateIconSize(c);
+ fixed.attribute.move(c,
+ Math.max(0, clientJson.at[0] * OVERVIEW_SCALE),
+ Math.max(0, clientJson.at[1] * OVERVIEW_SCALE)
+ );
+ return;
+ }
+ }
+ const newWindow = Window(clientJson, screenCoords);
+ if (newWindow === null) return;
+ // clientMap.set(clientJson.address, newWindow);
+ fixed.attribute.put(newWindow,
+ Math.max(0, newWindow.attribute.x * OVERVIEW_SCALE),
+ Math.max(0, newWindow.attribute.y * OVERVIEW_SCALE)
+ );
+ clientMap.set(clientJson.address, newWindow);
+ };
+ widget.unset = (clientAddress) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ let c = clientMap.get(clientAddress);
+ if (!c) return;
+ c.destroy();
+ c = null;
+ clientMap.delete(clientAddress);
+ };
+ widget.show = () => {
+ fixed.show_all();
+ }
+ return widget;
+ };
+
+ const arr = (s, n) => {
+ const array = [];
+ for (let i = 0; i < n; i++)
+ array.push(s + i);
+
+ return array;
+ };
+
+ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
+ children: arr(startWorkspace, workspaces).map(Workspace),
+ attribute: {
+ monitorMap: [],
+ getMonitorMap: (box) => {
+ execAsync('hyprctl -j monitors').then(monitors => {
+ box.attribute.monitorMap = JSON.parse(monitors).reduce((acc, item) => {
+ acc[item.id] = { x: item.x, y: item.y };
+ return acc;
+ }, {});
+ });
+ },
+ update: (box) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ if (!App.getWindow(windowName).visible) return;
+ Hyprland.sendMessage('j/clients').then(clients => {
+ const allClients = JSON.parse(clients);
+ const kids = box.get_children();
+ kids.forEach(kid => kid.clear());
+ // console.log('----------------------------');
+ for (let i = 0; i < allClients.length; i++) {
+ const client = allClients[i];
+ const childID = client.workspace.id - (offset + startWorkspace);
+ if (offset + startWorkspace <= client.workspace.id &&
+ client.workspace.id <= offset + startWorkspace + workspaces) {
+ const screenCoords = box.attribute.monitorMap[client.monitor];
+ if (kids[childID]) {
+ kids[childID].set(client, screenCoords);
+ }
+ continue;
+ }
+ // const modID = client.workspace.id % NUM_OF_WORKSPACES_SHOWN;
+ // console.log(`[${startWorkspace} -> ${startWorkspace + workspaces - 1}] ? (${client.workspace.id} == ${modID})`);
+ // // console.log(`[${startWorkspace} -> ${startWorkspace + workspaces}] ? (${modID})`);
+ // if (startWorkspace <= modID && modID < startWorkspace + workspaces) {
+ // console.log('i care');
+ // const clientWidget = clientMap.get(client.address);
+ // console.log(childID, kids[childID], clientWidget);
+ // if (kids[childID] && clientWidget) {
+ // console.log('hmm remove', clientWidget.attribute)
+ // kids[childID].remove(clientWidget);
+ // }
+ // }
+ }
+ kids.forEach(kid => kid.show());
+ }).catch(print);
+ },
+ updateWorkspace: (box, id) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ if (!( // Not in range, ignore
+ offset + startWorkspace <= id &&
+ id <= offset + startWorkspace + workspaces
+ )) return;
+ // if (!App.getWindow(windowName).visible) return;
+ Hyprland.sendMessage('j/clients').then(clients => {
+ const allClients = JSON.parse(clients);
+ const kids = box.get_children();
+ for (let i = 0; i < allClients.length; i++) {
+ const client = allClients[i];
+ if (client.workspace.id != id) continue;
+ const screenCoords = box.attribute.monitorMap[client.monitor];
+ kids[id - (offset + startWorkspace)]?.set(client, screenCoords);
+ }
+ kids[id - (offset + startWorkspace)]?.show();
+ }).catch(print);
+ },
+ },
+ setup: (box) => {
+ box.attribute.getMonitorMap(box);
+ box
+ .hook(overviewTick, (box) => box.attribute.update(box))
+ .hook(Hyprland, (box, clientAddress) => {
+ const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
+ const kids = box.get_children();
+ const client = Hyprland.getClient(clientAddress);
+ if (!client) return;
+ const id = client.workspace.id;
+
+ box.attribute.updateWorkspace(box, id);
+ kids[id - (offset + startWorkspace)]?.unset(clientAddress);
+ }, 'client-removed')
+ .hook(Hyprland, (box, clientAddress) => {
+ const client = Hyprland.getClient(clientAddress);
+ if (!client) return;
+ box.attribute.updateWorkspace(box, client.workspace.id);
+ }, 'client-added')
+ .hook(Hyprland.active.workspace, (box) => {
+ const previousGroup = box.attribute.workspaceGroup;
+ const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
+ if (currentGroup !== previousGroup) {
+ box.attribute.update(box);
+ workspaceGroup = currentGroup;
+ }
+ // box.attribute.update(box);
+ })
+ .hook(App, (box, name, visible) => { // Update on open
+ if (name == 'overview' && visible) box.attribute.update(box);
+ })
+ },
+ });
+
+ return Widget.Revealer({
+ revealChild: true,
+ transition: 'slide_down',
+ transitionDuration: 200,
+ child: Widget.Box({
+ vertical: true,
+ className: 'overview-tasks',
+ children: Array.from({ length: NUM_OF_WORKSPACE_ROWS }, (_, index) =>
+ OverviewRow({
+ startWorkspace: 1 + index * NUM_OF_WORKSPACE_COLS,
+ workspaces: NUM_OF_WORKSPACE_COLS,
+ })
+ )
+ }),
+ });
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/overview/searchbuttons.js b/modules/styling/config/widgets/overview/searchbuttons.js
new file mode 100644
index 0000000..15130ad
--- /dev/null
+++ b/modules/styling/config/widgets/overview/searchbuttons.js
@@ -0,0 +1,163 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+import { searchItem } from './searchitem.js';
+import { execAndClose, couldBeMath, launchCustomCommand } from './miscfunctions.js';
+
+export const DirectoryButton = ({ parentPath, name, type, icon }) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: 200,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: 'Open',
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: 300,
+ child: actionText,
+ });
+ return Widget.Button({
+ className: 'overview-search-result-btn',
+ onClicked: () => {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open '${parentPath}/${name}'`, `&`]).catch(print);
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Box({
+ className: 'overview-search-results-icon',
+ homogeneous: true,
+ child: Widget.Icon({
+ icon: icon,
+ }),
+ }),
+ Widget.Label({
+ className: 'overview-search-results-txt txt txt-norm',
+ label: name,
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ]
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ })
+}
+
+export const CalculationResultButton = ({ result, text }) => searchItem({
+ materialIconName: 'calculate',
+ name: `Math result`,
+ actionName: "Copy",
+ content: `${result}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ execAsync(['wl-copy', `${result}`]).catch(print);
+ },
+});
+
+export const DesktopEntryButton = (app) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: 200,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: 'Launch',
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: 300,
+ child: actionText,
+ });
+ return Widget.Button({
+ className: 'overview-search-result-btn',
+ onClicked: () => {
+ App.closeWindow('overview');
+ app.launch();
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Box({
+ className: 'overview-search-results-icon',
+ homogeneous: true,
+ child: Widget.Icon({
+ icon: app.iconName,
+ }),
+ }),
+ Widget.Label({
+ className: 'overview-search-results-txt txt txt-norm',
+ label: app.name,
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ]
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ })
+}
+
+export const ExecuteCommandButton = ({ command, terminal = false }) => searchItem({
+ materialIconName: `${terminal ? 'terminal' : 'settings_b_roll'}`,
+ name: `Run command`,
+ actionName: `Execute ${terminal ? 'in terminal' : ''}`,
+ content: `${command}`,
+ onActivate: () => execAndClose(command, terminal),
+ extraClassName: 'techfont',
+})
+
+export const CustomCommandButton = ({ text = '' }) => searchItem({
+ materialIconName: 'settings_suggest',
+ name: 'Action',
+ actionName: 'Run',
+ content: `${text}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ launchCustomCommand(text);
+ },
+});
+
+export const SearchButton = ({ text = '' }) => searchItem({
+ materialIconName: 'travel_explore',
+ name: 'Search Google',
+ actionName: 'Go',
+ content: `${text}`,
+ onActivate: () => {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open 'https://www.google.com/search?q=${text} -site:quora.com' &`]).catch(print); // quora is useless
+ },
+});
\ No newline at end of file
diff --git a/modules/styling/config/widgets/overview/searchitem.js b/modules/styling/config/widgets/overview/searchitem.js
new file mode 100644
index 0000000..276885a
--- /dev/null
+++ b/modules/styling/config/widgets/overview/searchitem.js
@@ -0,0 +1,65 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+
+export const searchItem = ({ materialIconName, name, actionName, content, onActivate, extraClassName = '', ...rest }) => {
+ const actionText = Widget.Revealer({
+ revealChild: false,
+ transition: "crossfade",
+ transitionDuration: 200,
+ child: Widget.Label({
+ className: 'overview-search-results-txt txt txt-small txt-action',
+ label: `${actionName}`,
+ })
+ });
+ const actionTextRevealer = Widget.Revealer({
+ revealChild: false,
+ transition: "slide_left",
+ transitionDuration: 300,
+ child: actionText,
+ })
+ return Widget.Button({
+ className: `overview-search-result-btn txt ${extraClassName}`,
+ onClicked: onActivate,
+ child: Widget.Box({
+ children: [
+ Widget.Box({
+ vertical: false,
+ children: [
+ Widget.Label({
+ className: `icon-material overview-search-results-icon`,
+ label: `${materialIconName}`,
+ }),
+ Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Label({
+ hpack: 'start',
+ className: 'overview-search-results-txt txt-smallie txt-subtext',
+ label: `${name}`,
+ truncate: "end",
+ }),
+ Widget.Label({
+ hpack: 'start',
+ className: 'overview-search-results-txt txt-norm',
+ label: `${content}`,
+ truncate: "end",
+ }),
+ ]
+ }),
+ Widget.Box({ hexpand: true }),
+ actionTextRevealer,
+ ],
+ })
+ ]
+ }),
+ setup: (self) => self
+ .on('focus-in-event', (button) => {
+ actionText.revealChild = true;
+ actionTextRevealer.revealChild = true;
+ })
+ .on('focus-out-event', (button) => {
+ actionText.revealChild = false;
+ actionTextRevealer.revealChild = false;
+ })
+ ,
+ });
+}
diff --git a/modules/styling/config/widgets/overview/windowcontent.js b/modules/styling/config/widgets/overview/windowcontent.js
new file mode 100644
index 0000000..5a9dd88
--- /dev/null
+++ b/modules/styling/config/widgets/overview/windowcontent.js
@@ -0,0 +1,268 @@
+const { Gdk, Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
+const { execAsync, exec } = Utils;
+import { execAndClose, expandTilde, hasUnterminatedBackslash, couldBeMath, launchCustomCommand, ls } from './miscfunctions.js';
+import {
+ CalculationResultButton, CustomCommandButton, DirectoryButton,
+ DesktopEntryButton, ExecuteCommandButton, SearchButton
+} from './searchbuttons.js';
+
+// Add math funcs
+const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
+const pi = Math.PI;
+// trigonometric funcs for deg
+const sind = x => sin(x * pi / 180);
+const cosd = x => cos(x * pi / 180);
+const tand = x => tan(x * pi / 180);
+const cotd = x => cot(x * pi / 180);
+const asind = x => asin(x) * 180 / pi;
+const acosd = x => acos(x) * 180 / pi;
+const atand = x => atan(x) * 180 / pi;
+const acotd = x => acot(x) * 180 / pi;
+
+const MAX_RESULTS = 10;
+const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
+const OVERVIEW_WS_NUM_SCALE = 0.09;
+const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
+const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
+
+function iconExists(iconName) {
+ let iconTheme = Gtk.IconTheme.get_default();
+ return iconTheme.has_icon(iconName);
+}
+
+const OptionalOverview = async () => {
+ try {
+ return (await import('./overview_hyprland.js')).default();
+ } catch {
+ return Widget.Box({});
+ // return (await import('./overview_hyprland.js')).default();
+ }
+};
+
+const overviewContent = await OptionalOverview();
+
+export const SearchAndWindows = () => {
+ var _appSearchResults = [];
+
+ const ClickToClose = ({ ...props }) => Widget.EventBox({
+ ...props,
+ onPrimaryClick: () => App.closeWindow('overview'),
+ onSecondaryClick: () => App.closeWindow('overview'),
+ onMiddleClick: () => App.closeWindow('overview'),
+ });
+ const resultsBox = Widget.Box({
+ className: 'overview-search-results',
+ vertical: true,
+ vexpand: true,
+ });
+ const resultsRevealer = Widget.Revealer({
+ transitionDuration: 200,
+ revealChild: false,
+ transition: 'slide_down',
+ // duration: 200,
+ hpack: 'center',
+ child: resultsBox,
+ });
+ const entryPromptRevealer = Widget.Revealer({
+ transition: 'crossfade',
+ transitionDuration: 150,
+ revealChild: true,
+ hpack: 'center',
+ child: Widget.Label({
+ className: 'overview-search-prompt txt-small txt',
+ label: 'Type to search'
+ }),
+ });
+
+ const entryIconRevealer = Widget.Revealer({
+ transition: 'crossfade',
+ transitionDuration: 150,
+ revealChild: false,
+ hpack: 'end',
+ child: Widget.Label({
+ className: 'txt txt-large icon-material overview-search-icon',
+ label: 'search',
+ }),
+ });
+
+ const entryIcon = Widget.Box({
+ className: 'overview-search-prompt-box',
+ setup: box => box.pack_start(entryIconRevealer, true, true, 0),
+ });
+
+ const entry = Widget.Entry({
+ className: 'overview-search-box txt-small txt',
+ hpack: 'center',
+ onAccept: (self) => { // This is when you hit Enter
+ const text = self.text;
+ if (text.length == 0) return;
+ const isAction = text.startsWith('>');
+ const isDir = (['/', '~'].includes(entry.text[0]));
+
+ if (couldBeMath(text)) { // Eval on typing is dangerous, this is a workaround
+ try {
+ const fullResult = eval(text.replace(/\^/g, "**"));
+ // copy
+ execAsync(['wl-copy', `${fullResult}`]).catch(print);
+ App.closeWindow('overview');
+ return;
+ } catch (e) {
+ // console.log(e);
+ }
+ }
+ if (isDir) {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open "${expandTilde(text)}"`, `&`]).catch(print);
+ return;
+ }
+ if (_appSearchResults.length > 0) {
+ App.closeWindow('overview');
+ _appSearchResults[0].launch();
+ return;
+ }
+ else if (text[0] == '>') { // Custom commands
+ App.closeWindow('overview');
+ launchCustomCommand(text);
+ return;
+ }
+ // Fallback: Execute command
+ if (!isAction && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
+ if (text.startsWith('sudo'))
+ execAndClose(text, true);
+ else
+ execAndClose(text, false);
+ }
+
+ else {
+ App.closeWindow('overview');
+ execAsync(['bash', '-c', `xdg-open 'https://www.google.com/search?q=${text} -site:quora.com' &`]).catch(print); // quora is useless
+ }
+ },
+ onChange: (entry) => { // this is when you type
+ const isAction = entry.text[0] == '>';
+ const isDir = (['/', '~'].includes(entry.text[0]));
+ resultsBox.get_children().forEach(ch => ch.destroy());
+
+ // check empty if so then dont do stuff
+ if (entry.text == '') {
+ resultsRevealer.revealChild = false;
+ overviewContent.revealChild = true;
+ entryPromptRevealer.revealChild = true;
+ entryIconRevealer.revealChild = false;
+ entry.toggleClassName('overview-search-box-extended', false);
+ return;
+ }
+ const text = entry.text;
+ resultsRevealer.revealChild = true;
+ overviewContent.revealChild = false;
+ entryPromptRevealer.revealChild = false;
+ entryIconRevealer.revealChild = true;
+ entry.toggleClassName('overview-search-box-extended', true);
+ _appSearchResults = Applications.query(text);
+
+ // Calculate
+ if (couldBeMath(text)) { // Eval on typing is dangerous; this is a small workaround.
+ try {
+ const fullResult = eval(text.replace(/\^/g, "**"));
+ resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
+ } catch (e) {
+ // console.log(e);
+ }
+ }
+ if (isDir) {
+ var contents = [];
+ contents = ls({ path: text, silent: true });
+ contents.forEach((item) => {
+ resultsBox.add(DirectoryButton(item));
+ })
+ }
+ if (isAction) { // Eval on typing is dangerous, this is a workaround.
+ resultsBox.add(CustomCommandButton({ text: entry.text }));
+ }
+ // Add application entries
+ let appsToAdd = MAX_RESULTS;
+ _appSearchResults.forEach(app => {
+ if (appsToAdd == 0) return;
+ resultsBox.add(DesktopEntryButton(app));
+ appsToAdd--;
+ });
+
+ // Fallbacks
+ // if the first word is an actual command
+ if (!isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
+ resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
+ }
+
+ // Add fallback: search
+ resultsBox.add(SearchButton({ text: entry.text }));
+ resultsBox.show_all();
+ },
+ });
+ return Widget.Box({
+ vertical: true,
+ children: [
+ ClickToClose({ // Top margin. Also works as a click-outside-to-close thing
+ child: Widget.Box({
+ className: 'bar-height',
+ })
+ }),
+ Widget.Box({
+ hpack: 'center',
+ children: [
+ entry,
+ Widget.Box({
+ className: 'overview-search-icon-box',
+ setup: (box) => {
+ box.pack_start(entryPromptRevealer, true, true, 0)
+ // enableClickthrough(box);
+ },
+ }),
+ entryIcon,
+ ]
+ }),
+ overviewContent,
+ resultsRevealer,
+ ],
+ setup: (self) => self
+ .hook(App, (_b, name, visible) => {
+ if (name == 'overview' && !visible) {
+ resultsBox.children = [];
+ entry.set_text('');
+ }
+ })
+ .on('key-press-event', (widget, event) => { // Typing
+ const keyval = event.get_keyval()[1];
+ const modstate = event.get_state()[1];
+ if (modstate & Gdk.ModifierType.CONTROL_MASK) { // Ctrl held
+ if (keyval == Gdk.KEY_b)
+ entry.set_position(Math.max(entry.get_position() - 1, 0));
+ else if (keyval == Gdk.KEY_f)
+ entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length));
+ else if (keyval == Gdk.KEY_n) { // simulate Down arrow
+ entry.get_root_window().simulate_key_press(Gdk.KEY_Down, Gdk.ModifierType.NONE);
+ // entry.get_root_window().simulate_key_release(Gdk.KEY_Down, Gdk.ModifierType.NONE);
+ }
+ else if (keyval == Gdk.KEY_k) { // Delete to end
+ const text = entry.get_text();
+ const pos = entry.get_position();
+ const newText = text.slice(0, pos);
+ entry.set_text(newText);
+ entry.set_position(newText.length);
+ }
+ }
+ else { // Ctrl not held
+ if (keyval >= 32 && keyval <= 126 && widget != entry) {
+ Utils.timeout(1, () => entry.grab_focus());
+ entry.set_text(entry.text + String.fromCharCode(keyval));
+ entry.set_position(-1);
+ }
+ }
+ })
+ ,
+ });
+};
diff --git a/modules/styling/config/widgets/screencorners/main.js b/modules/styling/config/widgets/screencorners/main.js
new file mode 100644
index 0000000..42a8a52
--- /dev/null
+++ b/modules/styling/config/widgets/screencorners/main.js
@@ -0,0 +1,16 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { RoundedCorner, dummyRegion, enableClickthrough } from "../../lib/roundedcorner.js";
+
+export default (monitor = 0, where = 'bottom left') => {
+ const positionString = where.replace(/\s/, ""); // remove space
+ return Widget.Window({
+ monitor,
+ name: `corner${positionString}${monitor}`,
+ layer: 'overlay',
+ anchor: where.split(' '),
+ exclusivity: 'ignore',
+ visible: true,
+ child: RoundedCorner(positionString, { className: 'corner-black', }),
+ setup: enableClickthrough,
+ });
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/session/main.js b/modules/styling/config/widgets/session/main.js
new file mode 100644
index 0000000..acf66dd
--- /dev/null
+++ b/modules/styling/config/widgets/session/main.js
@@ -0,0 +1,13 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import SessionScreen from "./sessionscreen.js";
+
+export default () => Widget.Window({ // On-screen keyboard
+ name: 'session',
+ popup: true,
+ visible: false,
+ keymode: 'exclusive',
+ layer: 'overlay',
+ exclusivity: 'ignore',
+ // anchor: ['top', 'bottom', 'left', 'right'],
+ child: SessionScreen(),
+})
\ No newline at end of file
diff --git a/modules/styling/config/widgets/session/sessionscreen.js b/modules/styling/config/widgets/session/sessionscreen.js
new file mode 100644
index 0000000..8ecb144
--- /dev/null
+++ b/modules/styling/config/widgets/session/sessionscreen.js
@@ -0,0 +1,147 @@
+// This is for the cool memory indicator on the sidebar
+// For the right pill of the bar, see system.js
+const { Gdk, Gtk } = imports.gi;
+import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { exec, execAsync } = Utils;
+
+const SessionButton = (name, icon, command, props = {}) => {
+ const buttonDescription = Widget.Revealer({
+ vpack: 'end',
+ transitionDuration: 200,
+ transition: 'slide_down',
+ revealChild: false,
+ child: Widget.Label({
+ className: 'txt-smaller session-button-desc',
+ label: name,
+ }),
+ });
+ return Widget.Button({
+ onClicked: command,
+ className: 'session-button',
+ child: Widget.Overlay({
+ className: 'session-button-box',
+ child: Widget.Label({
+ vexpand: true,
+ className: 'icon-material',
+ label: icon,
+ }),
+ overlays: [
+ buttonDescription,
+ ]
+ }),
+ onHover: (button) => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
+ button.get_window().set_cursor(cursor);
+ buttonDescription.revealChild = true;
+ },
+ onHoverLost: (button) => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ buttonDescription.revealChild = false;
+ },
+ setup: (self) => self
+ .on('focus-in-event', (self) => {
+ buttonDescription.revealChild = true;
+ self.toggleClassName('session-button-focused', true);
+ })
+ .on('focus-out-event', (self) => {
+ buttonDescription.revealChild = false;
+ self.toggleClassName('session-button-focused', false);
+ })
+ ,
+ ...props,
+ });
+}
+
+export default () => {
+ // lock, logout, sleep
+ // const lockButton = SessionButton('Lock', 'lock', () => { App.closeWindow('session'); execAsync('gtklock') });
+ const lockButton = SessionButton('Lock', 'lock', () => { App.closeWindow('session'); execAsync('swaylock') });
+ const logoutButton = SessionButton('Logout', 'logout', () => { App.closeWindow('session'); execAsync(['bash', '-c', 'pkill Hyprland || pkill sway']) });
+ const sleepButton = SessionButton('Sleep', 'sleep', () => { App.closeWindow('session'); execAsync('systemctl suspend') });
+ // hibernate, shutdown, reboot
+ const hibernateButton = SessionButton('Hibernate', 'downloading', () => { App.closeWindow('session'); execAsync('systemctl hibernate') });
+ const shutdownButton = SessionButton('Shutdown', 'power_settings_new', () => { App.closeWindow('session'); execAsync('systemctl poweroff') });
+ const rebootButton = SessionButton('Reboot', 'restart_alt', () => { App.closeWindow('session'); execAsync('systemctl reboot') });
+ const cancelButton = SessionButton('Cancel', 'close', () => App.closeWindow('session'), { className: 'session-button-cancel' });
+ return Widget.Box({
+ className: 'session-bg',
+ css: `
+ min-width: ${SCREEN_WIDTH * 1.5}px;
+ min-height: ${SCREEN_HEIGHT * 1.5}px;
+ `, // idk why but height = screen height doesn't fill
+ vertical: true,
+ children: [
+ Widget.EventBox({
+ onPrimaryClick: () => App.closeWindow('session'),
+ onSecondaryClick: () => App.closeWindow('session'),
+ onMiddleClick: () => App.closeWindow('session'),
+ }),
+ Widget.Box({
+ hpack: 'center',
+ vexpand: true,
+ vertical: true,
+ children: [
+ Widget.Box({
+ vpack: 'center',
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ Widget.Box({
+ vertical: true,
+ css: 'margin-bottom: 0.682rem;',
+ children: [
+ Widget.Label({
+ className: 'txt-title txt',
+ label: 'Session',
+ }),
+ Widget.Label({
+ justify: Gtk.Justification.CENTER,
+ className: 'txt-small txt',
+ label: 'Use arrow keys to navigate.\nEnter to select, Esc to cancel.'
+ }),
+ ]
+ }),
+ Widget.Box({
+ hpack: 'center',
+ className: 'spacing-h-15',
+ children: [ // lock, logout, sleep
+ lockButton,
+ logoutButton,
+ sleepButton,
+ ]
+ }),
+ Widget.Box({
+ hpack: 'center',
+ className: 'spacing-h-15',
+ children: [ // hibernate, shutdown, reboot
+ hibernateButton,
+ shutdownButton,
+ rebootButton,
+ ]
+ }),
+ Widget.Box({
+ hpack: 'center',
+ className: 'spacing-h-15',
+ children: [ // hibernate, shutdown, reboot
+ cancelButton,
+ ]
+ }),
+ ]
+ })
+ ]
+ })
+ ],
+ setup: (self) => self
+ .hook(App, (_b, name, visible) => {
+ if (visible) lockButton.grab_focus(); // Lock is the default option
+ })
+ ,
+ });
+}
diff --git a/modules/styling/config/widgets/sideleft/apis/ai_chatmessage.js b/modules/styling/config/widgets/sideleft/apis/ai_chatmessage.js
new file mode 100644
index 0000000..d2cf8d2
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/apis/ai_chatmessage.js
@@ -0,0 +1,293 @@
+const { Gdk, Gio, GLib, Gtk } = imports.gi;
+import GtkSource from "gi://GtkSource?version=3.0";
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label, Scrollable } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from "../../../lib/materialicon.js";
+import md2pango from "../../../lib/md2pango.js";
+
+
+const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/data/sourceviewtheme.xml`;
+const CUSTOM_SCHEME_ID = 'custom';
+const USERNAME = GLib.get_user_name();
+const CHATGPT_CURSOR = ' ...';
+
+/////////////////////// Custom source view colorscheme /////////////////////////
+
+function loadCustomColorScheme(filePath) {
+ // Read the XML file content
+ const file = Gio.File.new_for_path(filePath);
+ const [success, contents] = file.load_contents(null);
+
+ if (!success) {
+ logError('Failed to load the XML file.');
+ return;
+ }
+
+ // Parse the XML content and set the Style Scheme
+ const schemeManager = GtkSource.StyleSchemeManager.get_default();
+ schemeManager.append_search_path(file.get_parent().get_path());
+}
+loadCustomColorScheme(CUSTOM_SOURCEVIEW_SCHEME_PATH);
+
+//////////////////////////////////////////////////////////////////////////////
+
+function copyToClipboard(text) {
+ const clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
+ const textVariant = new GLib.Variant('s', text);
+ clipboard.set_text(textVariant, -1);
+ clipboard.store();
+}
+
+function substituteLang(str) {
+ const subs = [
+ { from: 'javascript', to: 'js' },
+ { from: 'bash', to: 'sh' },
+ ];
+
+ for (const { from, to } of subs) {
+ if (from === str)
+ return to;
+ }
+
+ return str;
+}
+
+const HighlightedCode = (content, lang) => {
+ const buffer = new GtkSource.Buffer();
+ const sourceView = new GtkSource.View({
+ buffer: buffer,
+ wrap_mode: Gtk.WrapMode.NONE
+ });
+ const langManager = GtkSource.LanguageManager.get_default();
+ let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language
+ if (displayLang) {
+ buffer.set_language(displayLang);
+ }
+ const schemeManager = GtkSource.StyleSchemeManager.get_default();
+ buffer.set_style_scheme(schemeManager.get_scheme(CUSTOM_SCHEME_ID));
+ buffer.set_text(content, -1);
+ return sourceView;
+}
+
+const TextBlock = (content = '') => Label({
+ hpack: 'fill',
+ className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
+ useMarkup: true,
+ xalign: 0,
+ wrap: true,
+ selectable: true,
+ label: content,
+});
+
+const CodeBlock = (content = '', lang = 'txt') => {
+ const topBar = Box({
+ className: 'sidebar-chat-codeblock-topbar',
+ children: [
+ Label({
+ label: lang,
+ className: 'sidebar-chat-codeblock-topbar-txt',
+ }),
+ Box({
+ hexpand: true,
+ }),
+ Button({
+ className: 'sidebar-chat-codeblock-topbar-btn',
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon('content_copy', 'small'),
+ Label({
+ label: 'Copy',
+ })
+ ]
+ }),
+ onClicked: (self) => {
+ const buffer = sourceView.get_buffer();
+ const copyContent = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), false); // TODO: fix this
+ execAsync([`wl-copy`, `${copyContent}`]).catch(print);
+ },
+ }),
+ ]
+ })
+ // Source view
+ const sourceView = HighlightedCode(content, lang);
+
+ const codeBlock = Box({
+ attribute: {
+ 'updateText': (text) => {
+ sourceView.get_buffer().set_text(text, -1);
+ }
+ },
+ className: 'sidebar-chat-codeblock',
+ vertical: true,
+ children: [
+ topBar,
+ Box({
+ className: 'sidebar-chat-codeblock-code',
+ homogeneous: true,
+ children: [Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ child: sourceView,
+ })],
+ })
+ ]
+ })
+
+ // const schemeIds = styleManager.get_scheme_ids();
+
+ // print("Available Style Schemes:");
+ // for (let i = 0; i < schemeIds.length; i++) {
+ // print(schemeIds[i]);
+ // }
+ return codeBlock;
+}
+
+const Divider = () => Box({
+ className: 'sidebar-chat-divider',
+})
+
+const MessageContent = (content) => {
+ const contentBox = Box({
+ vertical: true,
+ attribute: {
+ 'fullUpdate': (self, content, useCursor = false) => {
+ // Clear and add first text widget
+ const children = contentBox.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+ contentBox.add(TextBlock())
+ // Loop lines. Put normal text in markdown parser
+ // and put code into code highlighter (TODO)
+ let lines = content.split('\n');
+ let lastProcessed = 0;
+ let inCode = false;
+ for (const [index, line] of lines.entries()) {
+ // Code blocks
+ const codeBlockRegex = /^\s*```([a-zA-Z0-9]+)?\n?/;
+ if (codeBlockRegex.test(line)) {
+ const kids = self.get_children();
+ const lastLabel = kids[kids.length - 1];
+ const blockContent = lines.slice(lastProcessed, index).join('\n');
+ if (!inCode) {
+ lastLabel.label = md2pango(blockContent);
+ contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1]));
+ }
+ else {
+ lastLabel.attribute.updateText(blockContent);
+ contentBox.add(TextBlock());
+ }
+
+ lastProcessed = index + 1;
+ inCode = !inCode;
+ }
+ // Breaks
+ const dividerRegex = /^\s*---/;
+ if (!inCode && dividerRegex.test(line)) {
+ const kids = self.get_children();
+ const lastLabel = kids[kids.length - 1];
+ const blockContent = lines.slice(lastProcessed, index).join('\n');
+ lastLabel.label = md2pango(blockContent);
+ contentBox.add(Divider());
+ contentBox.add(TextBlock());
+ lastProcessed = index + 1;
+ }
+ }
+ if (lastProcessed < lines.length) {
+ const kids = self.get_children();
+ const lastLabel = kids[kids.length - 1];
+ let blockContent = lines.slice(lastProcessed, lines.length).join('\n');
+ if (!inCode)
+ lastLabel.label = `${md2pango(blockContent)}${useCursor ? CHATGPT_CURSOR : ''}`;
+ else
+ lastLabel.attribute.updateText(blockContent);
+ }
+ // Debug: plain text
+ // contentBox.add(Label({
+ // hpack: 'fill',
+ // className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
+ // useMarkup: false,
+ // xalign: 0,
+ // wrap: true,
+ // selectable: true,
+ // label: '------------------------------\n' + md2pango(content),
+ // }))
+ contentBox.show_all();
+ }
+ }
+ });
+ contentBox.attribute.fullUpdate(contentBox, content, false);
+ return contentBox;
+}
+
+export const ChatMessage = (message, modelName = 'Model') => {
+ const messageContentBox = MessageContent(message.content);
+ const thisMessage = Box({
+ className: 'sidebar-chat-message',
+ children: [
+ Box({
+ className: `sidebar-chat-indicator ${message.role == 'user' ? 'sidebar-chat-indicator-user' : 'sidebar-chat-indicator-bot'}`,
+ }),
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ hexpand: true,
+ children: [
+ Label({
+ hpack: 'fill',
+ xalign: 0,
+ className: 'txt txt-bold sidebar-chat-name',
+ wrap: true,
+ useMarkup: true,
+ label: (message.role == 'user' ? USERNAME : modelName),
+ }),
+ messageContentBox,
+ ],
+ setup: (self) => self
+ .hook(message, (self, isThinking) => {
+ messageContentBox.toggleClassName('thinking', message.thinking);
+ }, 'notify::thinking')
+ .hook(message, (self) => { // Message update
+ messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
+ }, 'notify::content')
+ .hook(message, (label, isDone) => { // Remove the cursor
+ messageContentBox.attribute.fullUpdate(messageContentBox, message.content, false);
+ }, 'notify::done')
+ ,
+ })
+ ]
+ });
+ return thisMessage;
+}
+
+export const SystemMessage = (content, commandName, scrolledWindow) => {
+ const messageContentBox = MessageContent(content);
+ const thisMessage = Box({
+ className: 'sidebar-chat-message',
+ children: [
+ Box({
+ className: `sidebar-chat-indicator sidebar-chat-indicator-System`,
+ }),
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ hexpand: true,
+ children: [
+ Label({
+ xalign: 0,
+ className: 'txt txt-bold sidebar-chat-name',
+ wrap: true,
+ label: `System • ${commandName}`,
+ }),
+ messageContentBox,
+ ],
+ })
+ ],
+ });
+ return thisMessage;
+}
diff --git a/modules/styling/config/widgets/sideleft/apis/chatgpt.js b/modules/styling/config/widgets/sideleft/apis/chatgpt.js
new file mode 100644
index 0000000..16e54b1
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/apis/chatgpt.js
@@ -0,0 +1,272 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import ChatGPT from '../../../services/chatgpt.js';
+import { MaterialIcon } from "../../../lib/materialicon.js";
+import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
+import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
+import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
+import { markdownTest } from '../../../lib/md2pango.js';
+import { MarginRevealer } from '../../../lib/advancedwidgets.js';
+
+Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
+
+export const chatGPTTabIcon = Icon({
+ hpack: 'center',
+ className: 'sidebar-chat-apiswitcher-icon',
+ icon: `openai-symbolic`,
+});
+
+const ChatGPTInfo = () => {
+ const openAiLogo = Icon({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ icon: `openai-symbolic`,
+ });
+ return Box({
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ openAiLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Assistant (ChatGPT 3.5)',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Powered by OpenAI',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+export const ChatGPTSettings = () => MarginRevealer({
+ transition: 'slide_down',
+ revealChild: true,
+ extraSetup: (self) => self
+ .hook(ChatGPT, (self) => Utils.timeout(200, () => {
+ self.attribute.hide();
+ }), 'newMsg')
+ .hook(ChatGPT, (self) => Utils.timeout(200, () => {
+ self.attribute.show();
+ }), 'clear')
+ ,
+ child: Box({
+ vertical: true,
+ className: 'sidebar-chat-settings',
+ children: [
+ ConfigSegmentedSelection({
+ hpack: 'center',
+ icon: 'casino',
+ name: 'Randomness',
+ desc: 'ChatGPT\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
+ options: [
+ { value: 0.00, name: 'Precise', },
+ { value: 0.50, name: 'Balanced', },
+ { value: 1.00, name: 'Creative', },
+ ],
+ initIndex: 2,
+ onChange: (value, name) => {
+ ChatGPT.temperature = value;
+ },
+ }),
+ ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ className: 'sidebar-chat-settings-toggles',
+ children: [
+ ConfigToggle({
+ icon: 'cycle',
+ name: 'Cycle models',
+ desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.',
+ initValue: ChatGPT.cycleModels,
+ onChange: (self, newValue) => {
+ ChatGPT.cycleModels = newValue;
+ },
+ }),
+ ConfigToggle({
+ icon: 'model_training',
+ name: 'Enhancements',
+ desc: 'Tells ChatGPT:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
+ initValue: ChatGPT.assistantPrompt,
+ onChange: (self, newValue) => {
+ ChatGPT.assistantPrompt = newValue;
+ },
+ }),
+ ]
+ })
+ ]
+ })
+});
+
+export const OpenaiApiKeyInstructions = () => Box({
+ homogeneous: true,
+ children: [Revealer({
+ transition: 'slide_down',
+ transitionDuration: 150,
+ setup: (self) => self
+ .hook(ChatGPT, (self, hasKey) => {
+ self.revealChild = (ChatGPT.key.length == 0);
+ }, 'hasKey')
+ ,
+ child: Button({
+ child: Label({
+ useMarkup: true,
+ wrap: true,
+ className: 'txt sidebar-chat-welcome-txt',
+ justify: Gtk.Justification.CENTER,
+ label: 'An OpenAI API key is required\nYou can grab one here, then enter it below'
+ }),
+ setup: setupCursorHover,
+ onClicked: () => {
+ Utils.execAsync(['bash', '-c', `xdg-open https://platform.openai.com/api-keys &`]);
+ }
+ })
+ })]
+});
+
+const chatGPTWelcome = Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ ChatGPTInfo(),
+ OpenaiApiKeyInstructions(),
+ ChatGPTSettings(),
+ ]
+ })
+});
+
+export const chatContent = Box({
+ className: 'spacing-v-15',
+ vertical: true,
+ setup: (self) => self
+ .hook(ChatGPT, (box, id) => {
+ const message = ChatGPT.messages[id];
+ if (!message) return;
+ box.add(ChatMessage(message, 'ChatGPT'))
+ }, 'newMsg')
+ ,
+});
+
+const clearChat = () => {
+ ChatGPT.clear();
+ const children = chatContent.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+}
+
+export const chatGPTView = Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ chatGPTWelcome,
+ chatContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Always scroll to bottom with new content
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => {
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ })
+ }
+});
+
+const CommandButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: command,
+});
+
+export const chatGPTCommands = Box({
+ className: 'spacing-h-5',
+ children: [
+ Box({ hexpand: true }),
+ CommandButton('/key'),
+ CommandButton('/model'),
+ CommandButton('/clear'),
+ ]
+});
+
+export const sendMessage = (text) => {
+ // Check if text or API key is empty
+ if (text.length == 0) return;
+ if (ChatGPT.key.length == 0) {
+ ChatGPT.key = text;
+ chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key', chatGPTView));
+ text = '';
+ return;
+ }
+ // Commands
+ if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model', chatGPTView))
+ else if (text.startsWith('/prompt')) {
+ const firstSpaceIndex = text.indexOf(' ');
+ const prompt = text.slice(firstSpaceIndex + 1);
+ if (firstSpaceIndex == -1 || prompt.length < 1) {
+ chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
+ }
+ else {
+ ChatGPT.addMessage('user', prompt)
+ }
+ }
+ else if (text.startsWith('/key')) {
+ const parts = text.split(' ');
+ if (parts.length == 1) chatContent.add(SystemMessage(
+ `Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
+ '/key',
+ chatGPTView));
+ else {
+ ChatGPT.key = parts[1];
+ chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key', chatGPTView));
+ }
+ }
+ else if (text.startsWith('/test'))
+ chatContent.add(SystemMessage(markdownTest, `Markdown test`, chatGPTView));
+ else
+ chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView))
+ }
+ else {
+ ChatGPT.send(text);
+ }
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/sideleft/apis/gemini.js b/modules/styling/config/widgets/sideleft/apis/gemini.js
new file mode 100644
index 0000000..6d9da68
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/apis/gemini.js
@@ -0,0 +1,264 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import Gemini from '../../../services/gemini.js';
+import { MaterialIcon } from "../../../lib/materialicon.js";
+import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
+import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
+import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
+import { markdownTest } from '../../../lib/md2pango.js';
+import { MarginRevealer } from '../../../lib/advancedwidgets.js';
+
+Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
+const MODEL_NAME = `Gemini`;
+
+export const geminiTabIcon = Icon({
+ hpack: 'center',
+ className: 'sidebar-chat-apiswitcher-icon',
+ icon: `google-gemini-symbolic`,
+})
+
+const GeminiInfo = () => {
+ const geminiLogo = Icon({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ icon: `google-gemini-symbolic`,
+ });
+ return Box({
+ vertical: true,
+ className: 'spacing-v-15',
+ children: [
+ geminiLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Assistant (Gemini Pro)',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Powered by Google',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+export const GeminiSettings = () => MarginRevealer({
+ transition: 'slide_down',
+ revealChild: true,
+ extraSetup: (self) => self
+ .hook(Gemini, (self) => Utils.timeout(200, () => {
+ self.attribute.hide();
+ }), 'newMsg')
+ .hook(Gemini, (self) => Utils.timeout(200, () => {
+ self.attribute.show();
+ }), 'clear')
+ ,
+ child: Box({
+ vertical: true,
+ className: 'sidebar-chat-settings',
+ children: [
+ ConfigSegmentedSelection({
+ hpack: 'center',
+ icon: 'casino',
+ name: 'Randomness',
+ desc: 'Gemini\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
+ options: [
+ { value: 0.00, name: 'Precise', },
+ { value: 0.50, name: 'Balanced', },
+ { value: 1.00, name: 'Creative', },
+ ],
+ initIndex: 2,
+ onChange: (value, name) => {
+ Gemini.temperature = value;
+ },
+ }),
+ ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
+ Box({
+ vertical: true,
+ hpack: 'fill',
+ className: 'sidebar-chat-settings-toggles',
+ children: [
+ ConfigToggle({
+ icon: 'model_training',
+ name: 'Enhancements',
+ desc: 'Tells Gemini:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
+ initValue: Gemini.assistantPrompt,
+ onChange: (self, newValue) => {
+ Gemini.assistantPrompt = newValue;
+ },
+ }),
+ ]
+ })
+ ]
+ })
+});
+
+export const GoogleAiInstructions = () => Box({
+ homogeneous: true,
+ children: [Revealer({
+ transition: 'slide_down',
+ transitionDuration: 150,
+ setup: (self) => self
+ .hook(Gemini, (self, hasKey) => {
+ self.revealChild = (Gemini.key.length == 0);
+ }, 'hasKey')
+ ,
+ child: Button({
+ child: Label({
+ useMarkup: true,
+ wrap: true,
+ className: 'txt sidebar-chat-welcome-txt',
+ justify: Gtk.Justification.CENTER,
+ label: 'A Google AI API key is required\nYou can grab one here, then enter it below'
+ }),
+ setup: setupCursorHover,
+ onClicked: () => {
+ Utils.execAsync(['bash', '-c', `xdg-open https://makersuite.google.com/app/apikey &`]);
+ }
+ })
+ })]
+});
+
+const geminiWelcome = Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ GeminiInfo(),
+ GoogleAiInstructions(),
+ GeminiSettings(),
+ ]
+ })
+});
+
+export const chatContent = Box({
+ className: 'spacing-v-15',
+ vertical: true,
+ setup: (self) => self
+ .hook(Gemini, (box, id) => {
+ const message = Gemini.messages[id];
+ if (!message) return;
+ box.add(ChatMessage(message, MODEL_NAME))
+ }, 'newMsg')
+ ,
+});
+
+const clearChat = () => {
+ Gemini.clear();
+ const children = chatContent.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+}
+
+export const geminiView = Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ geminiWelcome,
+ chatContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Always scroll to bottom with new content
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => {
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ })
+ }
+});
+
+const CommandButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: command,
+});
+
+export const geminiCommands = Box({
+ className: 'spacing-h-5',
+ children: [
+ Box({ hexpand: true }),
+ CommandButton('/key'),
+ CommandButton('/model'),
+ CommandButton('/clear'),
+ ]
+});
+
+export const sendMessage = (text) => {
+ // Check if text or API key is empty
+ if (text.length == 0) return;
+ if (Gemini.key.length == 0) {
+ Gemini.key = text;
+ chatContent.add(SystemMessage(`Key saved to\n\`${Gemini.keyPath}\``, 'API Key', geminiView));
+ text = '';
+ return;
+ }
+ // Commands
+ if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${Gemini.modelName}\``, '/model', geminiView))
+ else if (text.startsWith('/prompt')) {
+ const firstSpaceIndex = text.indexOf(' ');
+ const prompt = text.slice(firstSpaceIndex + 1);
+ if (firstSpaceIndex == -1 || prompt.length < 1) {
+ chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView))
+ }
+ else {
+ Gemini.addMessage('user', prompt)
+ }
+ }
+ else if (text.startsWith('/key')) {
+ const parts = text.split(' ');
+ if (parts.length == 1) chatContent.add(SystemMessage(
+ `Key stored in:\n\`${Gemini.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
+ '/key',
+ geminiView));
+ else {
+ Gemini.key = parts[1];
+ chatContent.add(SystemMessage(`Updated API Key at\n\`${Gemini.keyPath}\``, '/key', geminiView));
+ }
+ }
+ else if (text.startsWith('/test'))
+ chatContent.add(SystemMessage(markdownTest, `Markdown test`, geminiView));
+ else
+ chatContent.add(SystemMessage(`Invalid command.`, 'Error', geminiView))
+ }
+ else {
+ Gemini.send(text);
+ }
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/sideleft/apis/waifu.js b/modules/styling/config/widgets/sideleft/apis/waifu.js
new file mode 100644
index 0000000..94da960
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/apis/waifu.js
@@ -0,0 +1,431 @@
+const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from "../../../lib/materialicon.js";
+import { MarginRevealer } from '../../../lib/advancedwidgets.js';
+import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
+import WaifuService from '../../../services/waifus.js';
+
+async function getImageViewerApp(preferredApp) {
+ Utils.execAsync(['bash', '-c', `command -v ${preferredApp}`])
+ .then((output) => {
+ if (output != '') return preferredApp;
+ else return 'xdg-open';
+ });
+}
+
+const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
+const IMAGE_VIEWER_APP = getImageViewerApp('loupe'); // Gnome's image viewer cuz very comfortable zooming
+const USER_CACHE_DIR = GLib.get_user_cache_dir();
+
+// Create cache folder and clear pics from previous session
+Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
+Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
+
+export function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+const CommandButton = (command) => Button({
+ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
+ onClicked: () => sendMessage(command),
+ setup: setupCursorHover,
+ label: command,
+});
+
+export const waifuTabIcon = Box({
+ hpack: 'center',
+ className: 'sidebar-chat-apiswitcher-icon',
+ homogeneous: true,
+ children: [
+ MaterialIcon('photo_library', 'norm'),
+ ]
+});
+
+const WaifuImage = (taglist) => {
+ const ImageState = (icon, name) => Box({
+ className: 'spacing-h-5 txt',
+ children: [
+ Box({ hexpand: true }),
+ Label({
+ className: 'sidebar-waifu-txt txt-smallie',
+ xalign: 0,
+ label: name,
+ }),
+ MaterialIcon(icon, 'norm'),
+ ]
+ })
+ const ImageAction = ({ name, icon, action }) => Button({
+ className: 'sidebar-waifu-image-action txt-norm icon-material',
+ tooltipText: name,
+ label: icon,
+ onClicked: action,
+ setup: setupCursorHover,
+ })
+ const colorIndicator = Box({
+ className: `sidebar-chat-indicator`,
+ });
+ const downloadState = Stack({
+ homogeneous: false,
+ transition: 'slide_up_down',
+ transitionDuration: 150,
+ children: {
+ 'api': ImageState('api', 'Calling API'),
+ 'download': ImageState('downloading', 'Downloading image'),
+ 'done': ImageState('done', 'Finished!'),
+ 'error': ImageState('error', 'Error'),
+ },
+ });
+ const downloadIndicator = MarginRevealer({
+ vpack: 'center',
+ transition: 'slide_left',
+ revealChild: true,
+ child: downloadState,
+ });
+ const blockHeading = Box({
+ hpack: 'fill',
+ className: 'sidebar-waifu-content spacing-h-5',
+ children: [
+ ...taglist.map((tag) => CommandButton(tag)),
+ Box({ hexpand: true }),
+ downloadIndicator,
+ ]
+ });
+ const blockImageActions = Revealer({
+ transition: 'crossfade',
+ revealChild: false,
+ child: Box({
+ vertical: true,
+ children: [
+ Box({
+ className: 'sidebar-waifu-image-actions spacing-h-3',
+ children: [
+ Box({ hexpand: true }),
+ ImageAction({
+ name: 'Go to source',
+ icon: 'link',
+ action: () => execAsync(['xdg-open', `${thisBlock.attribute.imageData.source}`]).catch(print),
+ }),
+ ImageAction({
+ name: 'Hoard',
+ icon: 'save',
+ action: () => execAsync(['bash', '-c', `mkdir -p ~/Pictures/homework${thisBlock.attribute.isNsfw ? '/🌶️' : ''} && cp ${thisBlock.attribute.imagePath} ~/Pictures/homework${thisBlock.attribute.isNsfw ? '/🌶️/' : ''}`]).catch(print),
+ }),
+ ImageAction({
+ name: 'Open externally',
+ icon: 'open_in_new',
+ action: () => execAsync([IMAGE_VIEWER_APP, `${thisBlock.attribute.imagePath}`]).catch(print),
+ }),
+ ]
+ })
+ ],
+ })
+ })
+ const blockImage = Widget.DrawingArea({
+ className: 'sidebar-waifu-image',
+ });
+ const blockImageRevealer = Revealer({
+ transition: 'slide_down',
+ transitionDuration: 150,
+ revealChild: false,
+ child: Overlay({
+ child: Box({
+ homogeneous: true,
+ className: 'sidebar-waifu-image',
+ children: [blockImage],
+ }),
+ overlays: [blockImageActions],
+ }),
+ });
+ const thisBlock = Box({
+ className: 'sidebar-chat-message',
+ attribute: {
+ 'imagePath': '',
+ 'isNsfw': false,
+ 'imageData': '',
+ 'update': (imageData, force = false) => {
+ thisBlock.attribute.imageData = imageData;
+ const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock.attribute.imageData;
+ thisBlock.attribute.isNsfw = is_nsfw;
+ if (status != 200) {
+ downloadState.shown = 'error';
+ return;
+ }
+ thisBlock.attribute.imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${signature}${extension}`;
+ downloadState.shown = 'download';
+ // Width/height
+ const widgetWidth = Math.min(Math.floor(waifuContent.get_allocated_width() * 0.85), width);
+ const widgetHeight = Math.ceil(widgetWidth * height / width);
+ blockImage.set_size_request(widgetWidth, widgetHeight);
+ const showImage = () => {
+ downloadState.shown = 'done';
+ const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(thisBlock.attribute.imagePath, widgetWidth, widgetHeight);
+ // const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
+
+ blockImage.set_size_request(widgetWidth, widgetHeight);
+ blockImage.connect("draw", (widget, cr) => {
+ const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
+
+ // Draw a rounded rectangle
+ cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
+ cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
+ cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
+ cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
+ cr.closePath();
+ cr.clip();
+
+ // Paint image as bg
+ Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+ cr.paint();
+ });
+
+ // Reveal stuff
+ Utils.timeout(IMAGE_REVEAL_DELAY, () => {
+ blockImageRevealer.revealChild = true;
+ })
+ Utils.timeout(IMAGE_REVEAL_DELAY + blockImageRevealer.transitionDuration,
+ () => blockImageActions.revealChild = true
+ );
+ downloadIndicator.attribute.hide();
+ }
+ // Show
+ if (!force && fileExists(thisBlock.attribute.imagePath)) showImage();
+ else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock.attribute.imagePath}' '${url}'`])
+ .then(showImage)
+ .catch(print);
+ blockHeading.get_children().forEach((child) => {
+ child.setCss(`border-color: ${dominant_color};`);
+ })
+ colorIndicator.css = `background-color: ${dominant_color};`;
+ },
+ },
+ children: [
+ colorIndicator,
+ Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ blockHeading,
+ Box({
+ vertical: true,
+ hpack: 'start',
+ children: [blockImageRevealer],
+ })
+ ]
+ })
+ ],
+ });
+ return thisBlock;
+}
+
+const WaifuInfo = () => {
+ const waifuLogo = Label({
+ hpack: 'center',
+ className: 'sidebar-chat-welcome-logo',
+ label: 'photo_library',
+ })
+ return Box({
+ vertical: true,
+ vexpand: true,
+ className: 'spacing-v-15',
+ children: [
+ waifuLogo,
+ Label({
+ className: 'txt txt-title-small sidebar-chat-welcome-txt',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Waifus',
+ }),
+ Box({
+ className: 'spacing-h-5',
+ hpack: 'center',
+ children: [
+ Label({
+ className: 'txt-smallie txt-subtext',
+ wrap: true,
+ justify: Gtk.Justification.CENTER,
+ label: 'Powered by waifu.im',
+ }),
+ Button({
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'info',
+ tooltipText: 'A free Waifu API. An alternative to waifu.pics.',
+ setup: setupCursorHoverInfo,
+ }),
+ ]
+ }),
+ ]
+ });
+}
+
+const waifuWelcome = Box({
+ vexpand: true,
+ homogeneous: true,
+ child: Box({
+ className: 'spacing-v-15',
+ vpack: 'center',
+ vertical: true,
+ children: [
+ WaifuInfo(),
+ ]
+ })
+});
+
+const waifuContent = Box({
+ className: 'spacing-v-15',
+ vertical: true,
+ attribute: {
+ 'map': new Map(),
+ },
+ setup: (self) => self
+ .hook(WaifuService, (box, id) => {
+ if (id === undefined) return;
+ const newImageBlock = WaifuImage(WaifuService.queries[id]);
+ box.add(newImageBlock);
+ box.show_all();
+ box.attribute.map.set(id, newImageBlock);
+ }, 'newResponse')
+ .hook(WaifuService, (box, id) => {
+ if (id === undefined) return;
+ const data = WaifuService.responses[id];
+ if (!data) return;
+ const imageBlock = box.attribute.map.get(id);
+ imageBlock.attribute.update(data);
+ }, 'updateResponse')
+ ,
+});
+
+export const waifuView = Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ waifuWelcome,
+ waifuContent,
+ ]
+ }),
+ setup: (scrolledWindow) => {
+ // Show scrollbar
+ scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ const vScrollbar = scrolledWindow.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ // Avoid click-to-scroll-widget-to-view behavior
+ Utils.timeout(1, () => {
+ const viewport = scrolledWindow.child;
+ viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
+ })
+ // Always scroll to bottom with new content
+ const adjustment = scrolledWindow.get_vadjustment();
+ adjustment.connect("changed", () => {
+ adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
+ })
+ }
+});
+
+const waifuTags = Revealer({
+ revealChild: false,
+ transition: 'crossfade',
+ transitionDuration: 150,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ hexpand: true,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ CommandButton('waifu'),
+ CommandButton('maid'),
+ CommandButton('uniform'),
+ CommandButton('oppai'),
+ CommandButton('selfies'),
+ CommandButton('marin-kitagawa'),
+ CommandButton('raiden-shogun'),
+ CommandButton('mori-calliope'),
+ ]
+ })
+ }),
+ Box({ className: 'separator-line' }),
+ ]
+ })
+});
+
+export const waifuCommands = Box({
+ className: 'spacing-h-5',
+ setup: (self) => {
+ self.pack_end(CommandButton('/clear'), false, false, 0);
+ self.pack_start(Button({
+ className: 'sidebar-chat-chip-toggle',
+ setup: setupCursorHover,
+ label: 'Tags →',
+ onClicked: () => {
+ waifuTags.revealChild = !waifuTags.revealChild;
+ }
+ }), false, false, 0);
+ self.pack_start(waifuTags, true, true, 0);
+ }
+});
+
+const clearChat = () => {
+ waifuContent.attribute.map.clear();
+ const kids = waifuContent.get_children();
+ for (let i = 0; i < kids.length; i++) {
+ const child = kids[i];
+ if (child) child.destroy();
+ }
+}
+
+const DummyTag = (width, height, url, color = '#9392A6') => {
+ return { // Needs timeout or inits won't make it
+ status: 200,
+ url: url,
+ extension: '',
+ signature: 0,
+ source: url,
+ dominant_color: color,
+ is_nsfw: false,
+ width: width,
+ height: height,
+ tags: ['/test'],
+ };
+}
+
+export const sendMessage = (text) => {
+ // Do something on send
+ // Commands
+ if (text.startsWith('/')) {
+ if (text.startsWith('/clear')) clearChat();
+ else if (text.startsWith('/test')) {
+ const newImage = WaifuImage(['/test']);
+ waifuContent.add(newImage);
+ Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
+ DummyTag(300, 200, 'https://picsum.photos/600/400'),
+ true
+ ));
+ }
+ else if (text.startsWith('/chino')) {
+ const newImage = WaifuImage(['/chino']);
+ waifuContent.add(newImage);
+ Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
+ DummyTag(300, 400, 'https://chino.pages.dev/chino', '#B2AEF3'),
+ true
+ ));
+ }
+ else if (text.startsWith('/place')) {
+ const newImage = WaifuImage(['/place']);
+ waifuContent.add(newImage);
+ Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
+ DummyTag(400, 600, 'https://placewaifu.com/image/400/600', '#F0A235'),
+ true
+ ));
+ }
+
+ }
+ else WaifuService.fetch(text);
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/sideleft/apiwidgets.js b/modules/styling/config/widgets/sideleft/apiwidgets.js
new file mode 100644
index 0000000..f754fae
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/apiwidgets.js
@@ -0,0 +1,225 @@
+const { Gtk, Gdk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import AgsWidget from "resource:///com/github/Aylur/ags/widgets/widget.js";
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js";
+import { contentStack } from './sideleft.js';
+// APIs
+import ChatGPT from '../../services/chatgpt.js';
+import Gemini from '../../services/gemini.js';
+import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js';
+import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
+import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js';
+import { enableClickthrough } from '../../lib/roundedcorner.js';
+const TextView = Widget.subclass(Gtk.TextView, "AgsTextView");
+
+
+const EXPAND_INPUT_THRESHOLD = 30;
+const APIS = [
+ {
+ name: 'Assistant (Gemini Pro)',
+ sendCommand: geminiSendMessage,
+ contentWidget: geminiView,
+ commandBar: geminiCommands,
+ tabIcon: geminiTabIcon,
+ placeholderText: 'Message Gemini...',
+ },
+ {
+ name: 'Assistant (ChatGPT 3.5)',
+ sendCommand: chatGPTSendMessage,
+ contentWidget: chatGPTView,
+ commandBar: chatGPTCommands,
+ tabIcon: chatGPTTabIcon,
+ placeholderText: 'Message ChatGPT...',
+ },
+ {
+ name: 'Waifus',
+ sendCommand: waifuSendMessage,
+ contentWidget: waifuView,
+ commandBar: waifuCommands,
+ tabIcon: waifuTabIcon,
+ placeholderText: 'Enter tags',
+ },
+];
+let currentApiId = 0;
+APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true);
+
+function apiSendMessage(textView) {
+ // Get text
+ const buffer = textView.get_buffer();
+ const [start, end] = buffer.get_bounds();
+ const text = buffer.get_text(start, end, true).trimStart();
+ if (!text || text.length == 0) return;
+ // Send
+ APIS[currentApiId].sendCommand(text)
+ // Reset
+ buffer.set_text("", -1);
+ chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
+ chatEntry.set_valign(Gtk.Align.CENTER);
+}
+
+export const chatEntry = TextView({
+ hexpand: true,
+ wrapMode: Gtk.WrapMode.WORD_CHAR,
+ acceptsTab: false,
+ className: 'sidebar-chat-entry txt txt-smallie',
+ setup: (self) => self
+ .hook(ChatGPT, (self) => {
+ if (APIS[currentApiId].name != 'Assistant (ChatGPT 3.5)') return;
+ self.placeholderText = (ChatGPT.key.length > 0 ? 'Message ChatGPT...' : 'Enter OpenAI API Key...');
+ }, 'hasKey')
+ .hook(Gemini, (self) => {
+ if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return;
+ self.placeholderText = (Gemini.key.length > 0 ? 'Message Gemini...' : 'Enter Google AI API Key...');
+ }, 'hasKey')
+ .on("key-press-event", (widget, event) => {
+ const keyval = event.get_keyval()[1];
+ if (event.get_keyval()[1] === Gdk.KEY_Return && event.get_state()[1] == Gdk.ModifierType.MOD2_MASK) {
+ apiSendMessage(widget);
+ return true;
+ }
+ // Global keybinds
+ if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] === Gdk.KEY_Page_Down) {
+ const toSwitchTab = contentStack.get_visible_child();
+ toSwitchTab.attribute.nextTab();
+ }
+ else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] === Gdk.KEY_Page_Up) {
+ const toSwitchTab = contentStack.get_visible_child();
+ toSwitchTab.attribute.prevTab();
+ }
+ })
+ ,
+});
+
+chatEntry.get_buffer().connect("changed", (buffer) => {
+ const bufferText = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), true);
+ chatSendButton.toggleClassName('sidebar-chat-send-available', bufferText.length > 0);
+ chatPlaceholderRevealer.revealChild = (bufferText.length == 0);
+ if (buffer.get_line_count() > 1 || bufferText.length > EXPAND_INPUT_THRESHOLD) {
+ chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', true);
+ chatEntry.set_valign(Gtk.Align.FILL);
+ chatPlaceholder.set_valign(Gtk.Align.FILL);
+ }
+ else {
+ chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
+ chatEntry.set_valign(Gtk.Align.CENTER);
+ chatPlaceholder.set_valign(Gtk.Align.CENTER);
+ }
+});
+
+const chatEntryWrapper = Scrollable({
+ className: 'sidebar-chat-wrapper',
+ hscroll: 'never',
+ vscroll: 'always',
+ child: chatEntry,
+});
+
+const chatSendButton = Button({
+ className: 'txt-norm icon-material sidebar-chat-send',
+ vpack: 'end',
+ label: 'arrow_upward',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ APIS[currentApiId].sendCommand(chatEntry.get_buffer().text);
+ chatEntry.get_buffer().set_text("", -1);
+ },
+});
+
+const chatPlaceholder = Label({
+ className: 'txt-subtext txt-smallie margin-left-5',
+ hpack: 'start',
+ vpack: 'center',
+ label: APIS[currentApiId].placeholderText,
+});
+
+const chatPlaceholderRevealer = Revealer({
+ revealChild: true,
+ transition: 'crossfade',
+ transitionDuration: 200,
+ child: chatPlaceholder,
+ setup: enableClickthrough,
+});
+
+const textboxArea = Box({ // Entry area
+ className: 'sidebar-chat-textarea',
+ children: [
+ Overlay({
+ passThrough: true,
+ child: chatEntryWrapper,
+ overlays: [chatPlaceholderRevealer],
+ }),
+ Box({ className: 'width-10' }),
+ chatSendButton,
+ ]
+});
+
+const apiContentStack = Stack({
+ vexpand: true,
+ transition: 'slide_left_right',
+ transitionDuration: 160,
+ children: APIS.reduce((acc, api) => {
+ acc[api.name] = api.contentWidget;
+ return acc;
+ }, {}),
+})
+
+const apiCommandStack = Stack({
+ transition: 'slide_up_down',
+ transitionDuration: 160,
+ children: APIS.reduce((acc, api) => {
+ acc[api.name] = api.commandBar;
+ return acc;
+ }, {}),
+})
+
+function switchToTab(id) {
+ APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', false);
+ APIS[id].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true);
+ apiContentStack.shown = APIS[id].name;
+ apiCommandStack.shown = APIS[id].name;
+ chatPlaceholder.label = APIS[id].placeholderText;
+ currentApiId = id;
+}
+
+const apiSwitcher = CenterBox({
+ centerWidget: Box({
+ className: 'sidebar-chat-apiswitcher spacing-h-5',
+ hpack: 'center',
+ children: APIS.map((api, id) => Button({
+ child: api.tabIcon,
+ tooltipText: api.name,
+ setup: setupCursorHover,
+ onClicked: () => {
+ switchToTab(id);
+ }
+ })),
+ }),
+ endWidget: Button({
+ hpack: 'end',
+ className: 'txt-subtext txt-norm icon-material',
+ label: 'lightbulb',
+ tooltipText: 'Use PageUp/PageDown to switch between API pages',
+ setup: setupCursorHoverInfo,
+ }),
+})
+
+export default Widget.Box({
+ attribute: {
+ 'nextTab': () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1)),
+ 'prevTab': () => switchToTab(Math.max(0, currentApiId - 1)),
+ },
+ vertical: true,
+ className: 'spacing-v-10',
+ homogeneous: false,
+ children: [
+ apiSwitcher,
+ apiContentStack,
+ apiCommandStack,
+ textboxArea,
+ ],
+});
diff --git a/modules/styling/config/widgets/sideleft/main.js b/modules/styling/config/widgets/sideleft/main.js
new file mode 100644
index 0000000..8a76a96
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/main.js
@@ -0,0 +1,12 @@
+import PopupWindow from '../../lib/popupwindow.js';
+import SidebarLeft from "./sideleft.js";
+
+export default () => PopupWindow({
+ keymode: 'exclusive',
+ anchor: ['left', 'top', 'bottom'],
+ name: 'sideleft',
+ layer: 'top',
+ showClassName: 'sideleft-show',
+ hideClassName: 'sideleft-hide',
+ child: SidebarLeft(),
+});
diff --git a/modules/styling/config/widgets/sideleft/sideleft.js b/modules/styling/config/widgets/sideleft/sideleft.js
new file mode 100644
index 0000000..d67d87a
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/sideleft.js
@@ -0,0 +1,211 @@
+const { Gdk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import { MaterialIcon } from "../../lib/materialicon.js";
+import { setupCursorHover } from "../../lib/cursorhover.js";
+import { NavigationIndicator } from "../../lib/navigationindicator.js";
+import toolBox from './toolbox.js';
+import apiWidgets from './apiwidgets.js';
+import apiwidgets, { chatEntry } from './apiwidgets.js';
+
+const contents = [
+ {
+ name: 'apis',
+ content: apiWidgets,
+ materialIcon: 'api',
+ friendlyName: 'APIs',
+ },
+ {
+ name: 'tools',
+ content: toolBox,
+ materialIcon: 'home_repair_service',
+ friendlyName: 'Tools',
+ },
+]
+let currentTabId = 0;
+
+export const contentStack = Stack({
+ vexpand: true,
+ transition: 'slide_left_right',
+ transitionDuration: 160,
+ children: contents.reduce((acc, item) => {
+ acc[item.name] = item.content;
+ return acc;
+ }, {}),
+})
+
+function switchToTab(id) {
+ const allTabs = navTabs.get_children();
+ const tabButton = allTabs[id];
+ allTabs[currentTabId].toggleClassName('sidebar-selector-tab-active', false);
+ allTabs[id].toggleClassName('sidebar-selector-tab-active', true);
+ contentStack.shown = contents[id].name;
+ if (tabButton) {
+ // Fancy highlighter line width
+ const buttonWidth = tabButton.get_allocated_width();
+ const highlightWidth = tabButton.get_children()[0].get_allocated_width();
+ navIndicator.css = `
+ font-size: ${id}px;
+ padding: 0px ${(buttonWidth - highlightWidth) / 2}px;
+ `;
+ }
+ currentTabId = id;
+}
+const SidebarTabButton = (navIndex) => Widget.Button({
+ // hexpand: true,
+ className: 'sidebar-selector-tab',
+ onClicked: (self) => {
+ switchToTab(navIndex);
+ },
+ child: Box({
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon(contents[navIndex].materialIcon, 'larger'),
+ Label({
+ className: 'txt txt-smallie',
+ label: `${contents[navIndex].friendlyName}`,
+ })
+ ]
+ }),
+ setup: (button) => Utils.timeout(1, () => {
+ setupCursorHover(button);
+ button.toggleClassName('sidebar-selector-tab-active', currentTabId == navIndex);
+ }),
+});
+
+const navTabs = Box({
+ homogeneous: true,
+ children: contents.map((item, id) =>
+ SidebarTabButton(id, item.materialIcon, item.friendlyName)
+ ),
+});
+
+const navIndicator = NavigationIndicator(2, false, { // The line thing
+ className: 'sidebar-selector-highlight',
+ css: 'font-size: 0px; padding: 0rem 4.160rem;', // Shushhhh
+});
+
+const navBar = Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ navTabs,
+ navIndicator,
+ ]
+});
+
+const pinButton = Button({
+ attribute: {
+ 'enabled': false,
+ 'toggle': (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-pin-enabled', self.attribute.enabled);
+
+ const sideleftWindow = App.getWindow('sideleft');
+ const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
+
+ sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled);
+
+ if (self.attribute.enabled) {
+ sideleftWindow.exclusivity = 'exclusive';
+ }
+ else {
+ sideleftWindow.exclusivity = 'normal';
+ }
+ },
+ },
+ vpack: 'start',
+ className: 'sidebar-pin',
+ child: MaterialIcon('push_pin', 'larger'),
+ tooltipText: 'Pin sidebar (Ctrl+P)',
+ onClicked: (self) => self.attribute.toggle(self),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.hook(App, (self, currentName, visible) => {
+ if (currentName === 'sideleft' && visible) self.grab_focus();
+ })
+ },
+})
+
+export default () => Box({
+ // vertical: true,
+ vexpand: true,
+ hexpand: true,
+ css: 'min-width: 2px;',
+ children: [
+ EventBox({
+ onPrimaryClick: () => App.closeWindow('sideleft'),
+ onSecondaryClick: () => App.closeWindow('sideleft'),
+ onMiddleClick: () => App.closeWindow('sideleft'),
+ }),
+ Box({
+ vertical: true,
+ vexpand: true,
+ className: 'sidebar-left spacing-v-10',
+ children: [
+ Box({
+ className: 'spacing-h-10',
+ children: [
+ navBar,
+ pinButton,
+ ]
+ }),
+ contentStack,
+ ],
+ setup: (self) => self
+ .hook(App, (self, currentName, visible) => {
+ if (currentName === 'sideleft')
+ self.toggleClassName('sidebar-pinned', pinButton.attribute.enabled && visible);
+ })
+ ,
+ }),
+ ],
+ setup: (self) => self
+ .on('key-press-event', (widget, event) => { // Handle keybinds
+ if (event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) { // Ctrl held
+ // Pin sidebar
+ if (event.get_keyval()[1] == Gdk.KEY_p)
+ pinButton.attribute.toggle(pinButton);
+ // Switch sidebar tab
+ else if (event.get_keyval()[1] === Gdk.KEY_Tab)
+ switchToTab((currentTabId + 1) % contents.length);
+ else if (event.get_keyval()[1] === Gdk.KEY_Page_Up)
+ switchToTab(Math.max(currentTabId - 1, 0));
+ else if (event.get_keyval()[1] === Gdk.KEY_Page_Down)
+ switchToTab(Math.min(currentTabId + 1, contents.length - 1));
+ }
+ if (contentStack.shown == 'apis') { // If api tab is focused
+ // Focus entry when typing
+ if ((
+ !(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
+ widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
+ ||
+ ((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] === Gdk.KEY_v)
+ ) {
+ chatEntry.grab_focus();
+ const buffer = chatEntry.get_buffer();
+ buffer.set_text(buffer.text + String.fromCharCode(event.get_keyval()[1]), -1);
+ buffer.place_cursor(buffer.get_iter_at_offset(-1));
+ }
+ // Switch API type
+ else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] === Gdk.KEY_Page_Down) {
+ const toSwitchTab = contentStack.get_visible_child();
+ toSwitchTab.attribute.nextTab();
+ }
+ else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
+ event.get_keyval()[1] === Gdk.KEY_Page_Up) {
+ const toSwitchTab = contentStack.get_visible_child();
+ toSwitchTab.attribute.prevTab();
+ }
+ }
+
+ })
+ ,
+});
diff --git a/modules/styling/config/widgets/sideleft/toolbox.js b/modules/styling/config/widgets/sideleft/toolbox.js
new file mode 100644
index 0000000..9ecb98b
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/toolbox.js
@@ -0,0 +1,19 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
+const { execAsync, exec } = Utils;
+import QuickScripts from './tools/quickscripts.js';
+import ColorPicker from './tools/colorpicker.js';
+
+export default Scrollable({
+ hscroll: "never",
+ vscroll: "automatic",
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-10',
+ children: [
+ QuickScripts(),
+ ColorPicker(),
+ ]
+ })
+});
diff --git a/modules/styling/config/widgets/sideleft/tools/color.js b/modules/styling/config/widgets/sideleft/tools/color.js
new file mode 100644
index 0000000..f93b56b
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/tools/color.js
@@ -0,0 +1,199 @@
+// It's weird, I know
+const { Gio, GLib } = imports.gi;
+import Service from 'resource:///com/github/Aylur/ags/service.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { exec, execAsync } = Utils;
+
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+export class ColorPickerSelection extends Service {
+ static {
+ Service.register(this, {
+ 'picked': [],
+ 'assigned': ['int'],
+ 'hue': [],
+ 'sl': [],
+ });
+ }
+
+ _hue = 198;
+ _xAxis = 94;
+ _yAxis = 80;
+
+ get hue() { return this._hue; }
+ set hue(value) {
+ this._hue = clamp(value, 0, 360);
+ this.emit('hue');
+ this.emit('picked');
+ this.emit('changed');
+ }
+ get xAxis() { return this._xAxis; }
+ set xAxis(value) {
+ this._xAxis = clamp(value, 0, 100);
+ this.emit('sl');
+ this.emit('picked');
+ this.emit('changed');
+ }
+ get yAxis() { return this._yAxis; }
+ set yAxis(value) {
+ this._yAxis = clamp(value, 0, 100);
+ this.emit('sl');
+ this.emit('picked');
+ this.emit('changed');
+ }
+ setColorFromHex(hexString, id) {
+ const hsl = hexToHSL(hexString);
+ this._hue = hsl.hue;
+ this._xAxis = hsl.saturation;
+ // this._yAxis = hsl.lightness;
+ this._yAxis = (100 - hsl.saturation / 2) / 100 * hsl.lightness;
+ // console.log(this._hue, this._xAxis, this._yAxis)
+ this.emit('assigned', id);
+ this.emit('changed');
+ }
+
+ constructor() {
+ super();
+ this.emit('changed');
+ }
+}
+
+
+export function hslToRgbValues(h, s, l) {
+ h /= 360;
+ s /= 100;
+ l /= 100;
+ let r, g, b;
+ if (s === 0) {
+ r = g = b = l; // achromatic
+ } else {
+ const hue2rgb = (p, q, t) => {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
+ if (t < 1 / 2) return q;
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+ return p;
+ };
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1 / 3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1 / 3);
+ }
+ const to255 = x => Math.round(x * 255);
+ r = to255(r);
+ g = to255(g);
+ b = to255(b);
+ return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`;
+ // return `rgb(${r},${g},${b})`;
+}
+export function hslToHex(h, s, l) {
+ h /= 360;
+ s /= 100;
+ l /= 100;
+ let r, g, b;
+ if (s === 0) {
+ r = g = b = l; // achromatic
+ } else {
+ const hue2rgb = (p, q, t) => {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
+ if (t < 1 / 2) return q;
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+ return p;
+ };
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1 / 3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1 / 3);
+ }
+ const toHex = x => {
+ const hex = Math.round(x * 255).toString(16);
+ return hex.length === 1 ? "0" + hex : hex;
+ };
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+}
+
+// export function hexToHSL(hex) {
+// // Remove the '#' if present
+// hex = hex.replace(/^#/, '');
+// // Parse the hex value into RGB components
+// const bigint = parseInt(hex, 16);
+// const r = (bigint >> 16) & 255;
+// const g = (bigint >> 8) & 255;
+// const b = bigint & 255;
+// // Normalize RGB values to range [0, 1]
+// const normalizedR = r / 255;
+// const normalizedG = g / 255;
+// const normalizedB = b / 255;
+// // Find the maximum and minimum values
+// const max = Math.max(normalizedR, normalizedG, normalizedB);
+// const min = Math.min(normalizedR, normalizedG, normalizedB);
+// // Calculate the lightness
+// const lightness = (max + min) / 2;
+// // If the color is grayscale, set saturation to 0
+// if (max === min) {
+// return {
+// hue: 0,
+// saturation: 0,
+// lightness: lightness * 100 // Convert to percentage
+// };
+// }
+// // Calculate the saturation
+// const d = max - min;
+// const saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min);
+// // Calculate the hue
+// let hue;
+// if (max === normalizedR) {
+// hue = ((normalizedG - normalizedB) / d + (normalizedG < normalizedB ? 6 : 0)) * 60;
+// } else if (max === normalizedG) {
+// hue = ((normalizedB - normalizedR) / d + 2) * 60;
+// } else {
+// hue = ((normalizedR - normalizedG) / d + 4) * 60;
+// }
+// return {
+// hue: Math.round(hue),
+// saturation: Math.round(saturation * 100), // Convert to percentage
+// lightness: Math.round(lightness * 100) // Convert to percentage
+// };
+// }
+
+export function hexToHSL(hex) {
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+
+ var r = parseInt(result[1], 16);
+ var g = parseInt(result[2], 16);
+ var b = parseInt(result[3], 16);
+
+ r /= 255, g /= 255, b /= 255;
+ var max = Math.max(r, g, b), min = Math.min(r, g, b);
+ var h, s, l = (max + min) / 2;
+
+ if (max == min) {
+ h = s = 0; // achromatic
+ } else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+
+ s = s * 100;
+ s = Math.round(s);
+ l = l * 100;
+ l = Math.round(l);
+ h = Math.round(360 * h);
+
+ return {
+ hue: h,
+ saturation: s,
+ lightness: l
+ };
+}
diff --git a/modules/styling/config/widgets/sideleft/tools/colorpicker.js b/modules/styling/config/widgets/sideleft/tools/colorpicker.js
new file mode 100644
index 0000000..016eeba
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/tools/colorpicker.js
@@ -0,0 +1,284 @@
+// TODO: Make selection update when entry changes
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Variable from 'resource:///com/github/Aylur/ags/variable.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Scrollable } = Widget;
+import SidebarModule from './module.js';
+import { MaterialIcon } from '../../../lib/materialicon.js';
+import { setupCursorHover } from '../../../lib/cursorhover.js';
+
+import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js';
+
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+export default () => {
+ const selectedColor = new ColorPickerSelection();
+ function shouldUseBlackColor() {
+ return ((selectedColor.xAxis < 40 || (45 <= selectedColor.hue && selectedColor.hue <= 195)) &&
+ selectedColor.yAxis > 60);
+ }
+ const colorBlack = 'rgba(0,0,0,0.9)';
+ const colorWhite = 'rgba(255,255,255,0.9)';
+ const hueRange = Box({
+ homogeneous: true,
+ className: 'sidebar-module-colorpicker-wrapper',
+ children: [Box({
+ className: 'sidebar-module-colorpicker-hue',
+ css: `background: linear-gradient(to bottom, #ff6666, #ffff66, #66dd66, #66ffff, #6666ff, #ff66ff, #ff6666);`,
+ })],
+ });
+ const hueSlider = Box({
+ vpack: 'start',
+ className: 'sidebar-module-colorpicker-cursorwrapper',
+ css: `margin-top: ${13.636 * selectedColor.hue / 360}rem;`,
+ homogeneous: true,
+ children: [Box({
+ className: 'sidebar-module-colorpicker-hue-cursor',
+ })],
+ setup: (self) => self.hook(selectedColor, () => {
+ const widgetHeight = hueRange.children[0].get_allocated_height();
+ self.setCss(`margin-top: ${13.636 * selectedColor.hue / 360}rem;`)
+ }),
+ });
+ const hueSelector = Box({
+ children: [EventBox({
+ child: Overlay({
+ child: hueRange,
+ overlays: [hueSlider],
+ }),
+ attribute: {
+ clicked: false,
+ setHue: (self, event) => {
+ const widgetHeight = hueRange.children[0].get_allocated_height();
+ const [_, cursorX, cursorY] = event.get_coords();
+ const cursorYPercent = clamp(cursorY / widgetHeight, 0, 1);
+ selectedColor.hue = Math.round(cursorYPercent * 360);
+ }
+ },
+ setup: (self) => self
+ .on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ self.attribute.setHue(self, event);
+ })
+ .on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ self.attribute.setHue(self, event);
+ })
+ .on('button-release-event', (self) => self.attribute.clicked = false)
+ ,
+ })]
+ });
+ const saturationAndLightnessRange = Box({
+ homogeneous: true,
+ children: [Box({
+ className: 'sidebar-module-colorpicker-saturationandlightness',
+ attribute: {
+ update: (self) => {
+ // css: `background: linear-gradient(to right, #ffffff, color);`,
+ self.setCss(`background:
+ linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)),
+ linear-gradient(to right, #ffffff, ${hslToHex(selectedColor.hue, 100, 50)});
+ `);
+ },
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ })],
+ });
+ const saturationAndLightnessCursor = Box({
+ className: 'sidebar-module-colorpicker-saturationandlightness-cursorwrapper',
+ children: [Box({
+ vpack: 'start',
+ hpack: 'start',
+ homogeneous: true,
+ css: `
+ margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
+ margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
+ `, // Why 13.636rem? see class name in stylesheet
+ attribute: {
+ update: (self) => {
+ const allocation = saturationAndLightnessRange.children[0].get_allocation();
+ self.setCss(`
+ margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
+ margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
+ `); // Why 13.636rem? see class name in stylesheet
+ }
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ children: [Box({
+ className: 'sidebar-module-colorpicker-saturationandlightness-cursor',
+ css: `
+ background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
+ border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
+ `,
+ attribute: {
+ update: (self) => {
+ self.setCss(`
+ background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
+ border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
+ `);
+ }
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ })],
+ })]
+ });
+ const saturationAndLightnessSelector = Box({
+ homogeneous: true,
+ className: 'sidebar-module-colorpicker-saturationandlightness-wrapper',
+ children: [EventBox({
+ child: Overlay({
+ child: saturationAndLightnessRange,
+ overlays: [saturationAndLightnessCursor],
+ }),
+ attribute: {
+ clicked: false,
+ setSaturationAndLightness: (self, event) => {
+ const allocation = saturationAndLightnessRange.children[0].get_allocation();
+ const [_, cursorX, cursorY] = event.get_coords();
+ const cursorXPercent = clamp(cursorX / allocation.width, 0, 1);
+ const cursorYPercent = clamp(cursorY / allocation.height, 0, 1);
+ selectedColor.xAxis = Math.round(cursorXPercent * 100);
+ selectedColor.yAxis = Math.round(100 - cursorYPercent * 100);
+ }
+ },
+ setup: (self) => self
+ .on('motion-notify-event', (self, event) => {
+ if (!self.attribute.clicked) return;
+ self.attribute.setSaturationAndLightness(self, event);
+ })
+ .on('button-press-event', (self, event) => {
+ if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
+ self.attribute.clicked = true;
+ self.attribute.setSaturationAndLightness(self, event);
+ })
+ .on('button-release-event', (self) => self.attribute.clicked = false)
+ ,
+ })]
+ });
+ const resultColorBox = Box({
+ className: 'sidebar-module-colorpicker-result-box',
+ homogeneous: true,
+ css: `background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`,
+ children: [Label({
+ className: 'txt txt-small',
+ label: 'Result',
+ }),],
+ attribute: {
+ update: (self) => {
+ self.setCss(`background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`);
+ self.children[0].setCss(`color: ${shouldUseBlackColor() ? colorBlack : colorWhite};`)
+ }
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ ,
+ });
+ const ResultBox = ({ colorSystemName, updateCallback, copyCallback }) => Box({
+ children: [
+ Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ Label({
+ xalign: 0,
+ className: 'txt-tiny',
+ label: colorSystemName,
+ }),
+ Overlay({
+ child: Entry({
+ widthChars: 10,
+ className: 'txt-small techfont',
+ attribute: {
+ id: 0,
+ update: updateCallback,
+ },
+ setup: (self) => self
+ .hook(selectedColor, self.attribute.update, 'sl')
+ .hook(selectedColor, self.attribute.update, 'hue')
+ .hook(selectedColor, self.attribute.update, 'assigned')
+ // .on('activate', (self) => {
+ // const newColor = self.text;
+ // if (newColor.length != 7) return;
+ // selectedColor.setColorFromHex(self.text, self.attribute.id);
+ // })
+ ,
+ }),
+ })
+ ]
+ }),
+ Button({
+ child: MaterialIcon('content_copy', 'norm'),
+ onClicked: (self) => {
+ copyCallback(self);
+ self.child.label = 'done';
+ Utils.timeout(1000, () => self.child.label = 'content_copy');
+ },
+ setup: setupCursorHover,
+ })
+ ]
+ });
+ const resultHex = ResultBox({
+ colorSystemName: 'Hex',
+ updateCallback: (self, id) => {
+ if (id && self.attribute.id === id) return;
+ self.text = hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
+ },
+ copyCallback: () => Utils.execAsync(['wl-copy', `${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}`]),
+ })
+ const resultRgb = ResultBox({
+ colorSystemName: 'RGB',
+ updateCallback: (self, id) => {
+ if (id && self.attribute.id === id) return;
+ self.text = hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
+ },
+ copyCallback: () => Utils.execAsync(['wl-copy', `rgb(${hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))})`]),
+ })
+ const resultHsl = ResultBox({
+ colorSystemName: 'HSL',
+ updateCallback: (self, id) => {
+ if (id && self.attribute.id === id) return;
+ self.text = `${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%`;
+ },
+ copyCallback: () => Utils.execAsync(['wl-copy', `hsl(${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%)`]),
+ })
+ const result = Box({
+ className: 'sidebar-module-colorpicker-result-area spacing-v-5 txt',
+ hexpand: true,
+ vertical: true,
+ children: [
+ resultColorBox,
+ resultHex,
+ resultRgb,
+ resultHsl,
+ ]
+ })
+ return SidebarModule({
+ icon: MaterialIcon('colorize', 'norm'),
+ name: 'Color picker',
+ revealChild: false,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ hueSelector,
+ saturationAndLightnessSelector,
+ result,
+ ]
+ })
+ });
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/sideleft/tools/module.js b/modules/styling/config/widgets/sideleft/tools/module.js
new file mode 100644
index 0000000..eae2714
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/tools/module.js
@@ -0,0 +1,56 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import { setupCursorHover } from '../../../lib/cursorhover.js';
+import { MaterialIcon } from '../../../lib/materialicon.js';
+const { Box, Button, Icon, Label, Revealer } = Widget;
+
+export default ({
+ icon,
+ name,
+ child,
+ revealChild = true,
+}) => {
+ const headerButtonIcon = MaterialIcon(revealChild ? 'expand_less' : 'expand_more', 'norm');
+ const header = Button({
+ onClicked: () => {
+ content.revealChild = !content.revealChild;
+ headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
+ },
+ setup: setupCursorHover,
+ child: Box({
+ className: 'txt spacing-h-10',
+ children: [
+ icon,
+ Label({
+ className: 'txt-norm',
+ label: `${name}`,
+ }),
+ Box({
+ hexpand: true,
+ }),
+ Box({
+ className: 'sidebar-module-btn-arrow',
+ homogeneous: true,
+ children: [headerButtonIcon],
+ })
+ ]
+ })
+ });
+ const content = Revealer({
+ revealChild: revealChild,
+ transition: 'slide_down',
+ transitionDuration: 200,
+ child: Box({
+ className: 'margin-top-5',
+ homogeneous: true,
+ children: [child],
+ }),
+ });
+ return Box({
+ className: 'sidebar-module',
+ vertical: true,
+ children: [
+ header,
+ content,
+ ]
+ });
+}
\ No newline at end of file
diff --git a/modules/styling/config/widgets/sideleft/tools/quickscripts.js b/modules/styling/config/widgets/sideleft/tools/quickscripts.js
new file mode 100644
index 0000000..3ff907d
--- /dev/null
+++ b/modules/styling/config/widgets/sideleft/tools/quickscripts.js
@@ -0,0 +1,95 @@
+const { Gtk } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, Button, EventBox, Icon, Label, Scrollable } = Widget;
+import SidebarModule from './module.js';
+import { MaterialIcon } from '../../../lib/materialicon.js';
+import { setupCursorHover } from '../../../lib/cursorhover.js';
+
+Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
+const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2'`).trim();
+const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'pop' || distroID == 'raspbian' || distroID == 'kali' || distroID == 'elementary');
+const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros');
+const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`);
+
+const scripts = [
+ {
+ icon: 'nixos-symbolic',
+ name: 'Trim system generations to 5',
+ command: `sudo ${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 system`,
+ enabled: distroID == 'nixos',
+ },
+ {
+ icon: 'nixos-symbolic',
+ name: 'Trim home manager generations to 5',
+ command: `${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 home-manager`,
+ enabled: distroID == 'nixos',
+ },
+ {
+ icon: 'ubuntu-symbolic',
+ name: 'Update packages',
+ command: `sudo apt update && sudo apt upgrade -y`,
+ enabled: isDebianDistro,
+ },
+ {
+ icon: 'fedora-symbolic',
+ name: 'Update packages',
+ command: `sudo dnf upgrade -y`,
+ enabled: distroID == 'fedora',
+ },
+ {
+ icon: 'arch-symbolic',
+ name: 'Update packages',
+ command: `sudo pacman -Syyu`,
+ enabled: isArchDistro,
+ },
+ {
+ icon: 'flatpak-symbolic',
+ name: 'Uninstall unused flatpak packages',
+ command: `flatpak uninstall --unused`,
+ enabled: hasFlatpak,
+ },
+];
+
+export default () => SidebarModule({
+ icon: MaterialIcon('code', 'norm'),
+ name: 'Quick scripts',
+ child: Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: scripts.map((script) => {
+ if (!script.enabled) return null;
+ const scriptStateIcon = MaterialIcon('not_started', 'norm');
+ return Box({
+ className: 'spacing-h-5 txt',
+ children: [
+ Icon({
+ className: 'sidebar-module-btn-icon txt-large',
+ icon: script.icon,
+ }),
+ Label({
+ className: 'txt-small',
+ hpack: 'start',
+ hexpand: true,
+ label: script.name,
+ tooltipText: script.command,
+ }),
+ Button({
+ className: 'sidebar-module-scripts-button',
+ child: scriptStateIcon,
+ onClicked: () => {
+ App.closeWindow('sideleft');
+ execAsync([`bash`, `-c`, `foot fish -C "${script.command}"`]).catch(print)
+ .then(() => {
+ scriptStateIcon.label = 'done';
+ })
+ },
+ setup: setupCursorHover,
+ }),
+ ],
+ })
+ }),
+ })
+});
\ No newline at end of file
diff --git a/modules/styling/config/widgets/sideright/calendar.js b/modules/styling/config/widgets/sideright/calendar.js
new file mode 100644
index 0000000..579a21f
--- /dev/null
+++ b/modules/styling/config/widgets/sideright/calendar.js
@@ -0,0 +1,207 @@
+const { Gio } = imports.gi;
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label } = Widget;
+import { MaterialIcon } from "../../lib/materialicon.js";
+import { getCalendarLayout } from "../../lib/calendarlayout.js";
+import { setupCursorHover } from "../../lib/cursorhover.js";
+
+import { TodoWidget } from "./todolist.js";
+
+let calendarJson = getCalendarLayout(undefined, true);
+let monthshift = 0;
+
+function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+function getDateInXMonthsTime(x) {
+ var currentDate = new Date(); // Get the current date
+ var targetMonth = currentDate.getMonth() + x; // Calculate the target month
+ var targetYear = currentDate.getFullYear(); // Get the current year
+
+ // Adjust the year and month if necessary
+ targetYear += Math.floor(targetMonth / 12);
+ targetMonth = (targetMonth % 12 + 12) % 12;
+
+ // Create a new date object with the target year and month
+ var targetDate = new Date(targetYear, targetMonth, 1);
+
+ // Set the day to the last day of the month to get the desired date
+ // targetDate.setDate(0);
+
+ return targetDate;
+}
+
+const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:
+ { day: 'Mo', today: 0 },
+ { day: 'Tu', today: 0 },
+ { day: 'We', today: 0 },
+ { day: 'Th', today: 0 },
+ { day: 'Fr', today: 0 },
+ { day: 'Sa', today: 0 },
+ { day: 'Su', today: 0 },
+]
+
+const CalendarDay = (day, today) => Widget.Button({
+ className: `sidebar-calendar-btn ${today == 1 ? 'sidebar-calendar-btn-today' : (today == -1 ? 'sidebar-calendar-btn-othermonth' : '')}`,
+ child: Widget.Overlay({
+ child: Box({}),
+ overlays: [Label({
+ hpack: 'center',
+ className: 'txt-smallie txt-semibold sidebar-calendar-btn-txt',
+ label: String(day),
+ })],
+ })
+})
+
+const CalendarWidget = () => {
+ const calendarMonthYear = Widget.Button({
+ className: 'txt txt-large sidebar-calendar-monthyear-btn',
+ onClicked: () => shiftCalendarXMonths(0),
+ setup: (button) => {
+ button.label = `${new Date().toLocaleString('default', { month: 'long' })} ${new Date().getFullYear()}`;
+ setupCursorHover(button);
+ }
+ });
+ const addCalendarChildren = (box, calendarJson) => {
+ const children = box.get_children();
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child.destroy();
+ }
+ box.children = calendarJson.map((row, i) => Widget.Box({
+ className: 'spacing-h-5',
+ children: row.map((day, i) => CalendarDay(day.day, day.today)),
+ }))
+ }
+ function shiftCalendarXMonths(x) {
+ if (x == 0) monthshift = 0;
+ else monthshift += x;
+ var newDate;
+ if (monthshift == 0) newDate = new Date();
+ else newDate = getDateInXMonthsTime(monthshift);
+
+ calendarJson = getCalendarLayout(newDate, (monthshift == 0));
+ calendarMonthYear.label = `${monthshift == 0 ? '' : '• '}${newDate.toLocaleString('default', { month: 'long' })} ${newDate.getFullYear()}`;
+ addCalendarChildren(calendarDays, calendarJson);
+ }
+ const calendarHeader = Widget.Box({
+ className: 'spacing-h-5 sidebar-calendar-header',
+ setup: (box) => {
+ box.pack_start(calendarMonthYear, false, false, 0);
+ box.pack_end(Widget.Box({
+ className: 'spacing-h-5',
+ children: [
+ Button({
+ className: 'sidebar-calendar-monthshift-btn',
+ onClicked: () => shiftCalendarXMonths(-1),
+ child: MaterialIcon('chevron_left', 'norm'),
+ setup: setupCursorHover,
+ }),
+ Button({
+ className: 'sidebar-calendar-monthshift-btn',
+ onClicked: () => shiftCalendarXMonths(1),
+ child: MaterialIcon('chevron_right', 'norm'),
+ setup: setupCursorHover,
+ })
+ ]
+ }), false, false, 0);
+ }
+ })
+ const calendarDays = Widget.Box({
+ hexpand: true,
+ vertical: true,
+ className: 'spacing-v-5',
+ setup: (box) => {
+ addCalendarChildren(box, calendarJson);
+ }
+ });
+ return Widget.EventBox({
+ onScrollUp: () => shiftCalendarXMonths(-1),
+ onScrollDown: () => shiftCalendarXMonths(1),
+ child: Widget.Box({
+ hpack: 'center',
+ children: [
+ Widget.Box({
+ hexpand: true,
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ calendarHeader,
+ Widget.Box({
+ homogeneous: true,
+ className: 'spacing-h-5',
+ children: weekDays.map((day, i) => CalendarDay(day.day, day.today))
+ }),
+ calendarDays,
+ ]
+ })
+ ]
+ })
+ });
+};
+
+const defaultShown = 'calendar';
+const contentStack = Widget.Stack({
+ hexpand: true,
+ children: {
+ 'calendar': CalendarWidget(),
+ 'todo': TodoWidget(),
+ // 'stars': Widget.Label({ label: 'GitHub feed will be here' }),
+ },
+ transition: 'slide_up_down',
+ transitionDuration: 180,
+ setup: (stack) => Utils.timeout(1, () => {
+ stack.shown = defaultShown;
+ })
+})
+
+const StackButton = (stackItemName, icon, name) => Widget.Button({
+ className: 'button-minsize sidebar-navrail-btn txt-small spacing-h-5',
+ onClicked: (button) => {
+ contentStack.shown = stackItemName;
+ const kids = button.get_parent().get_children();
+ for (let i = 0; i < kids.length; i++) {
+ if (kids[i] != button) kids[i].toggleClassName('sidebar-navrail-btn-active', false);
+ else button.toggleClassName('sidebar-navrail-btn-active', true);
+ }
+ },
+ child: Box({
+ className: 'spacing-v-5',
+ vertical: true,
+ children: [
+ Label({
+ className: `txt icon-material txt-hugeass`,
+ label: icon,
+ }),
+ Label({
+ label: name,
+ className: 'txt txt-smallie',
+ }),
+ ]
+ }),
+ setup: (button) => Utils.timeout(1, () => {
+ setupCursorHover(button);
+ button.toggleClassName('sidebar-navrail-btn-active', defaultShown === stackItemName);
+ })
+});
+
+export const ModuleCalendar = () => Box({
+ className: 'sidebar-group spacing-h-5',
+ setup: (box) => {
+ box.pack_start(Box({
+ vpack: 'center',
+ homogeneous: true,
+ vertical: true,
+ className: 'sidebar-navrail spacing-v-10',
+ children: [
+ StackButton('calendar', 'calendar_month', 'Calendar'),
+ StackButton('todo', 'lists', 'To Do'),
+ // StackButton(box, 'stars', 'star', 'GitHub'),
+ ]
+ }), false, false, 0);
+ box.pack_end(contentStack, false, false, 0);
+ }
+})
diff --git a/modules/styling/config/widgets/sideright/main.js b/modules/styling/config/widgets/sideright/main.js
new file mode 100644
index 0000000..561d771
--- /dev/null
+++ b/modules/styling/config/widgets/sideright/main.js
@@ -0,0 +1,11 @@
+import PopupWindow from '../../lib/popupwindow.js';
+import SidebarRight from "./sideright.js";
+
+export default () => PopupWindow({
+ keymode: 'exclusive',
+ anchor: ['right', 'top', 'bottom'],
+ name: 'sideright',
+ showClassName: 'sideright-show',
+ hideClassName: 'sideright-hide',
+ child: SidebarRight(),
+});
diff --git a/modules/styling/config/widgets/sideright/notificationlist.js b/modules/styling/config/widgets/sideright/notificationlist.js
new file mode 100644
index 0000000..f30802d
--- /dev/null
+++ b/modules/styling/config/widgets/sideright/notificationlist.js
@@ -0,0 +1,142 @@
+// This file is for the notification list on the sidebar
+// For the popup notifications, see onscreendisplay.js
+// The actual widget for each single notification is in lib/notification.js
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
+const { Box, Button, Label, Scrollable, Stack } = Widget;
+import { MaterialIcon } from "../../lib/materialicon.js";
+import { setupCursorHover } from "../../lib/cursorhover.js";
+import Notification from "../../lib/notification.js";
+
+export default (props) => {
+ const notifEmptyContent = Box({
+ homogeneous: true,
+ children: [Box({
+ vertical: true,
+ vpack: 'center',
+ className: 'txt spacing-v-10',
+ children: [
+ Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ MaterialIcon('notifications_active', 'gigantic'),
+ Label({ label: 'No notifications', className: 'txt-small' }),
+ ]
+ }),
+ ]
+ })]
+ });
+ const notificationList = Box({
+ vertical: true,
+ vpack: 'start',
+ className: 'spacing-v-5-revealer',
+ setup: (self) => self
+ .hook(Notifications, (box, id) => {
+ if (box.get_children().length == 0) { // On init there's no notif, or 1st notif
+ Notifications.notifications
+ .forEach(n => {
+ box.pack_end(Notification({
+ notifObject: n,
+ isPopup: false,
+ }), false, false, 0)
+ });
+ box.show_all();
+ return;
+ }
+ // 2nd or later notif
+ const notif = Notifications.getNotification(id);
+ const NewNotif = Notification({
+ notifObject: notif,
+ isPopup: false,
+ });
+ if (NewNotif) {
+ box.pack_end(NewNotif, false, false, 0);
+ box.show_all();
+ }
+ }, 'notified')
+ .hook(Notifications, (box, id) => {
+ if (!id) return;
+ for (const ch of box.children) {
+ if (ch._id === id) {
+ ch.attribute.destroyWithAnims();
+ }
+ }
+ }, 'closed')
+ ,
+ });
+ const ListActionButton = (icon, name, action) => Button({
+ className: 'notif-listaction-btn',
+ onClicked: action,
+ child: Box({
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon(icon, 'norm'),
+ Label({
+ className: 'txt-small',
+ label: name,
+ })
+ ]
+ }),
+ setup: setupCursorHover,
+ });
+ const silenceButton = ListActionButton('notifications_paused', 'Silence', (self) => {
+ Notifications.dnd = !Notifications.dnd;
+ self.toggleClassName('notif-listaction-btn-enabled', Notifications.dnd);
+ });
+ const clearButton = ListActionButton('clear_all', 'Clear', () => {
+ // Manual destruction is not necessary
+ // since Notifications.clear() sends destroy signals to every notif
+ Notifications.clear();
+ });
+ const listTitle = Box({
+ vpack: 'start',
+ className: 'sidebar-group-invisible txt spacing-h-5',
+ children: [
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-title-small margin-left-10',
+ // ^ (extra margin on the left so that it looks similarly spaced
+ // when compared to borderless "Clear" button on the right)
+ label: 'Notifications',
+ }),
+ silenceButton,
+ clearButton,
+ ]
+ });
+ const notifList = Scrollable({
+ hexpand: true,
+ hscroll: 'never',
+ vscroll: 'automatic',
+ child: Box({
+ vexpand: true,
+ // homogeneous: true,
+ children: [notificationList],
+ }),
+ setup: (self) => {
+ const vScrollbar = self.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ }
+ });
+ const listContents = Stack({
+ transition: 'crossfade',
+ transitionDuration: 150,
+ children: {
+ 'empty': notifEmptyContent,
+ 'list': notifList,
+ },
+ setup: (self) => self
+ .hook(Notifications, (self) => self.shown = (Notifications.notifications.length > 0 ? 'list' : 'empty'))
+ ,
+ });
+ return Box({
+ ...props,
+ className: 'sidebar-group spacing-v-5',
+ vertical: true,
+ children: [
+ listTitle,
+ listContents,
+ ]
+ });
+}
diff --git a/modules/styling/config/widgets/sideright/quicktoggles.js b/modules/styling/config/widgets/sideright/quicktoggles.js
new file mode 100644
index 0000000..6d55f26
--- /dev/null
+++ b/modules/styling/config/widgets/sideright/quicktoggles.js
@@ -0,0 +1,226 @@
+const { GLib } = imports.gi;
+import App from 'resource:///com/github/Aylur/ags/app.js';
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+
+import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
+import Network from 'resource:///com/github/Aylur/ags/service/network.js';
+const { execAsync, exec } = Utils;
+import { BluetoothIndicator, NetworkIndicator } from "../../lib/statusicons.js";
+import { setupCursorHover } from "../../lib/cursorhover.js";
+import { MaterialIcon } from '../../lib/materialicon.js';
+
+function expandTilde(path) {
+ if (path.startsWith('~')) {
+ return GLib.get_home_dir() + path.slice(1);
+ } else {
+ return path;
+ }
+}
+
+export const ToggleIconWifi = (props = {}) => Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Wifi | Right-click to configure',
+ onClicked: () => Network.toggleWifi(),
+ onSecondaryClickRelease: () => {
+ execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center wifi', '&']);
+ App.closeWindow('sideright');
+ },
+ child: NetworkIndicator(),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.hook(Network, button => {
+ button.toggleClassName('sidebar-button-active', [Network.wifi?.internet, Network.wired?.internet].includes('connected'))
+ button.tooltipText = (`${Network.wifi?.ssid} | Right-click to configure` || 'Unknown');
+ });
+ },
+ ...props,
+});
+
+export const ToggleIconBluetooth = (props = {}) => Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Bluetooth | Right-click to configure',
+ onClicked: () => {
+ const status = Bluetooth?.enabled;
+ if (status)
+ exec('rfkill block bluetooth');
+ else
+ exec('rfkill unblock bluetooth');
+ },
+ onSecondaryClickRelease: () => {
+ execAsync(['bash', '-c', 'blueberry &']);
+ App.closeWindow('sideright');
+ },
+ child: BluetoothIndicator(),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.hook(Bluetooth, button => {
+ button.toggleClassName('sidebar-button-active', Bluetooth?.enabled)
+ });
+ },
+ ...props,
+});
+
+export const HyprToggleIcon = async (icon, name, hyprlandConfigValue, props = {}) => {
+ try {
+ return Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: `${name}`,
+ onClicked: (button) => {
+ // Set the value to 1 - value
+ Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
+ const currentOption = JSON.parse(result).int;
+ execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
+ button.toggleClassName('sidebar-button-active', currentOption == 0);
+ }).catch(print);
+ },
+ child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
+ setup: button => {
+ button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
+ setupCursorHover(button);
+ },
+ ...props,
+ })
+ } catch {
+ return null;
+ }
+}
+
+export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make this work
+ attribute: {
+ enabled: false,
+ yellowlight: undefined,
+ },
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Night Light',
+ onClicked: (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ // if (self.attribute.enabled) Utils.execAsync(['bash', '-c', 'wlsunset & disown'])
+ if (self.attribute.enabled) Utils.execAsync('wlsunset')
+ else Utils.execAsync('pkill wlsunset');
+ },
+ child: MaterialIcon('nightlight', 'norm'),
+ setup: (self) => {
+ setupCursorHover(self);
+ self.attribute.enabled = !!exec('pidof wlsunset');
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ },
+ ...props,
+});
+
+export const ModuleInvertColors = async (props = {}) => {
+ try {
+ const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
+ return Widget.Button({
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Color inversion',
+ onClicked: (button) => {
+ // const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
+ Hyprland.sendMessage('j/getoption decoration:screen_shader')
+ .then((output) => {
+ const shaderPath = JSON.parse(output)["str"].trim();
+ if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
+ execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print);
+ button.toggleClassName('sidebar-button-active', false);
+ }
+ else {
+ Hyprland.sendMessage(`j/keyword decoration:screen_shader ${expandTilde('~/.config/hypr/shaders/invert.frag')}`)
+ .catch(print);
+ button.toggleClassName('sidebar-button-active', true);
+ }
+ })
+ },
+ child: MaterialIcon('invert_colors', 'norm'),
+ setup: setupCursorHover,
+ ...props,
+ })
+ } catch {
+ return null;
+ };
+}
+
+export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
+ attribute: {
+ enabled: false,
+ inhibitor: undefined,
+ },
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Keep system awake',
+ onClicked: (self) => {
+ self.attribute.enabled = !self.attribute.enabled;
+ self.toggleClassName('sidebar-button-active', self.attribute.enabled);
+ if (self.attribute.enabled) {
+ self.attribute.inhibitor = Utils.subprocess(
+ [`${App.configDir}/scripts/wayland-idle-inhibitor.py`],
+ (output) => print(output),
+ (err) => logError(err),
+ self,
+ );
+ }
+ else {
+ self.attribute.inhibitor.force_exit();
+ }
+ },
+ child: MaterialIcon('coffee', 'norm'),
+ setup: setupCursorHover,
+ ...props,
+});
+
+export const ModuleEditIcon = (props = {}) => Widget.Button({ // TODO: Make this work
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ onClicked: () => {
+ execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center', '&']);
+ App.toggleWindow('sideright');
+ },
+ child: MaterialIcon('edit', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
+
+export const ModuleReloadIcon = (props = {}) => Widget.Button({
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Reload Environment config',
+ onClicked: () => {
+ execAsync(['bash', '-c', 'hyprctl reload || swaymsg reload &']);
+ App.toggleWindow('sideright');
+ },
+ child: MaterialIcon('refresh', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
+
+export const ModuleSettingsIcon = (props = {}) => Widget.Button({
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Open Settings',
+ onClicked: () => {
+ execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center', '&']);
+ App.toggleWindow('sideright');
+ },
+ child: MaterialIcon('settings', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
+
+export const ModulePowerIcon = (props = {}) => Widget.Button({
+ ...props,
+ className: 'txt-small sidebar-iconbutton',
+ tooltipText: 'Session',
+ onClicked: () => {
+ App.toggleWindow('session');
+ App.closeWindow('sideright');
+ },
+ child: MaterialIcon('power_settings_new', 'norm'),
+ setup: button => {
+ setupCursorHover(button);
+ }
+})
+
+
+
diff --git a/modules/styling/config/widgets/sideright/sideright.js b/modules/styling/config/widgets/sideright/sideright.js
new file mode 100644
index 0000000..911ed14
--- /dev/null
+++ b/modules/styling/config/widgets/sideright/sideright.js
@@ -0,0 +1,86 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { execAsync, exec } = Utils;
+const { Box, EventBox } = Widget;
+import {
+ ToggleIconBluetooth,
+ ToggleIconWifi,
+ HyprToggleIcon,
+ ModuleNightLight,
+ ModuleInvertColors,
+ ModuleIdleInhibitor,
+ ModuleEditIcon,
+ ModuleReloadIcon,
+ ModuleSettingsIcon,
+ ModulePowerIcon
+} from "./quicktoggles.js";
+import ModuleNotificationList from "./notificationlist.js";
+import { ModuleCalendar } from "./calendar.js";
+
+const timeRow = Box({
+ className: 'spacing-h-5 sidebar-group-invisible-morehorizpad',
+ children: [
+ Widget.Label({
+ hpack: 'center',
+ className: 'txt-small txt',
+ setup: (self) => self
+ .poll(5000, label => {
+ execAsync(['bash', '-c', `w | sed -n '1p' | cut -d, -f1 | cut -d' ' -f4-`])
+ .then(upTimeString => {
+ label.label = `Uptime: ${upTimeString}`;
+ }).catch(print);
+ })
+ ,
+ }),
+ Widget.Box({ hexpand: true }),
+ // ModuleEditIcon({ hpack: 'end' }), // TODO: Make this work
+ ModuleReloadIcon({ hpack: 'end' }),
+ ModuleSettingsIcon({ hpack: 'end' }),
+ ModulePowerIcon({ hpack: 'end' }),
+ ]
+});
+
+const togglesBox = Widget.Box({
+ hpack: 'center',
+ className: 'sidebar-togglesbox spacing-h-10',
+ children: [
+ ToggleIconWifi(),
+ ToggleIconBluetooth(),
+ await HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', {}),
+ await HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
+ ModuleNightLight(),
+ await ModuleInvertColors(),
+ ModuleIdleInhibitor(),
+ ]
+})
+
+export default () => Box({
+ vexpand: true,
+ hexpand: true,
+ css: 'min-width: 2px;',
+ children: [
+ EventBox({
+ onPrimaryClick: () => App.closeWindow('sideright'),
+ onSecondaryClick: () => App.closeWindow('sideright'),
+ onMiddleClick: () => App.closeWindow('sideright'),
+ }),
+ Box({
+ vertical: true,
+ vexpand: true,
+ className: 'sidebar-right spacing-v-15',
+ children: [
+ Box({
+ vertical: true,
+ className: 'spacing-v-5',
+ children: [
+ timeRow,
+ // togglesFlowBox,
+ togglesBox,
+ ]
+ }),
+ ModuleNotificationList({ vexpand: true, }),
+ ModuleCalendar(),
+ ]
+ }),
+ ]
+});
diff --git a/modules/styling/config/widgets/sideright/todolist.js b/modules/styling/config/widgets/sideright/todolist.js
new file mode 100644
index 0000000..30a7941
--- /dev/null
+++ b/modules/styling/config/widgets/sideright/todolist.js
@@ -0,0 +1,279 @@
+import Widget from 'resource:///com/github/Aylur/ags/widget.js';
+import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
+const { Box, Button, Label, Revealer } = Widget;
+import { MaterialIcon } from "../../lib/materialicon.js";
+import Todo from "../../services/todo.js";
+import { setupCursorHover } from "../../lib/cursorhover.js";
+import { NavigationIndicator } from "../../lib/navigationindicator.js";
+
+const defaultTodoSelected = 'undone';
+
+const todoListItem = (task, id, isDone, isEven = false) => {
+ const crosser = Widget.Box({
+ className: 'sidebar-todo-crosser',
+ });
+ const todoContent = Widget.Box({
+ className: 'sidebar-todo-item spacing-h-5',
+ children: [
+ Widget.Label({
+ hexpand: true,
+ xalign: 0,
+ wrap: true,
+ className: 'txt txt-small sidebar-todo-txt',
+ label: task.content,
+ selectable: true,
+ }),
+ Widget.Button({ // Check/Uncheck
+ vpack: 'center',
+ className: 'txt sidebar-todo-item-action',
+ child: MaterialIcon(`${isDone ? 'remove_done' : 'check'}`, 'norm', { vpack: 'center' }),
+ onClicked: (self) => {
+ const contentWidth = todoContent.get_allocated_width();
+ crosser.toggleClassName('sidebar-todo-crosser-crossed', true);
+ crosser.css = `margin-left: -${contentWidth}px;`;
+ Utils.timeout(200, () => {
+ widgetRevealer.revealChild = false;
+ })
+ Utils.timeout(350, () => {
+ if (isDone)
+ Todo.uncheck(id);
+ else
+ Todo.check(id);
+ })
+ },
+ setup: setupCursorHover,
+ }),
+ Widget.Button({ // Remove
+ vpack: 'center',
+ className: 'txt sidebar-todo-item-action',
+ child: MaterialIcon('delete_forever', 'norm', { vpack: 'center' }),
+ onClicked: () => {
+ const contentWidth = todoContent.get_allocated_width();
+ crosser.toggleClassName('sidebar-todo-crosser-removed', true);
+ crosser.css = `margin-left: -${contentWidth}px;`;
+ Utils.timeout(200, () => {
+ widgetRevealer.revealChild = false;
+ })
+ Utils.timeout(350, () => {
+ Todo.remove(id);
+ })
+ },
+ setup: setupCursorHover,
+ }),
+ crosser,
+ ]
+ });
+ const widgetRevealer = Widget.Revealer({
+ revealChild: true,
+ transition: 'slide_down',
+ transitionDuration: 150,
+ child: todoContent,
+ })
+ return widgetRevealer;
+}
+
+const todoItems = (isDone) => Widget.Scrollable({
+ hscroll: 'never',
+ vscroll: 'automatic',
+ child: Widget.Box({
+ vertical: true,
+ setup: (self) => self
+ .hook(Todo, (self) => {
+ self.children = Todo.todo_json.map((task, i) => {
+ if (task.done != isDone) return null;
+ return todoListItem(task, i, isDone);
+ })
+ if (self.children.length == 0) {
+ self.homogeneous = true;
+ self.children = [
+ Widget.Box({
+ hexpand: true,
+ vertical: true,
+ vpack: 'center',
+ className: 'txt',
+ children: [
+ MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'gigantic'),
+ Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
+ ]
+ })
+ ]
+ }
+ else self.homogeneous = false;
+ }, 'updated')
+ ,
+ }),
+ setup: (listContents) => {
+ const vScrollbar = listContents.get_vscrollbar();
+ vScrollbar.get_style_context().add_class('sidebar-scrollbar');
+ }
+});
+
+const UndoneTodoList = () => {
+ const newTaskButton = Revealer({
+ transition: 'slide_left',
+ transitionDuration: 200,
+ revealChild: true,
+ child: Button({
+ className: 'txt-small sidebar-todo-new',
+ halign: 'end',
+ vpack: 'center',
+ label: '+ New task',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ newTaskButton.revealChild = false;
+ newTaskEntryRevealer.revealChild = true;
+ confirmAddTask.revealChild = true;
+ cancelAddTask.revealChild = true;
+ newTaskEntry.grab_focus();
+ }
+ })
+ });
+ const cancelAddTask = Revealer({
+ transition: 'slide_right',
+ transitionDuration: 200,
+ revealChild: false,
+ child: Button({
+ className: 'txt-norm icon-material sidebar-todo-add',
+ halign: 'end',
+ vpack: 'center',
+ label: 'close',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ newTaskEntryRevealer.revealChild = false;
+ confirmAddTask.revealChild = false;
+ cancelAddTask.revealChild = false;
+ newTaskButton.revealChild = true;
+ newTaskEntry.text = '';
+ }
+ })
+ });
+ const newTaskEntry = Widget.Entry({
+ // hexpand: true,
+ vpack: 'center',
+ className: 'txt-small sidebar-todo-entry',
+ placeholderText: 'Add a task...',
+ onAccept: ({ text }) => {
+ if (text == '') return;
+ Todo.add(text)
+ newTaskEntry.text = '';
+ },
+ onChange: ({ text }) => confirmAddTask.child.toggleClassName('sidebar-todo-add-available', text != ''),
+ });
+ const newTaskEntryRevealer = Revealer({
+ transition: 'slide_right',
+ transitionDuration: 200,
+ revealChild: false,
+ child: newTaskEntry,
+ });
+ const confirmAddTask = Revealer({
+ transition: 'slide_right',
+ transitionDuration: 200,
+ revealChild: false,
+ child: Button({
+ className: 'txt-norm icon-material sidebar-todo-add',
+ halign: 'end',
+ vpack: 'center',
+ label: 'arrow_upward',
+ setup: setupCursorHover,
+ onClicked: (self) => {
+ if (newTaskEntry.text == '') return;
+ Todo.add(newTaskEntry.text);
+ newTaskEntry.text = '';
+ }
+ })
+ });
+ return Box({ // The list, with a New button
+ vertical: true,
+ className: 'spacing-v-5',
+ setup: (box) => {
+ box.pack_start(todoItems(false), true, true, 0);
+ box.pack_start(Box({
+ setup: (self) => {
+ self.pack_start(cancelAddTask, false, false, 0);
+ self.pack_start(newTaskEntryRevealer, true, true, 0);
+ self.pack_start(confirmAddTask, false, false, 0);
+ self.pack_start(newTaskButton, false, false, 0);
+ }
+ }), false, false, 0);
+ },
+ });
+}
+
+const todoItemsBox = Widget.Stack({
+ vpack: 'fill',
+ transition: 'slide_left_right',
+ children: {
+ 'undone': UndoneTodoList(),
+ 'done': todoItems(true),
+ },
+});
+
+export const TodoWidget = () => {
+ const TodoTabButton = (isDone, navIndex) => Widget.Button({
+ hexpand: true,
+ className: 'sidebar-selector-tab',
+ onClicked: (button) => {
+ todoItemsBox.shown = `${isDone ? 'done' : 'undone'}`;
+ const kids = button.get_parent().get_children();
+ for (let i = 0; i < kids.length; i++) {
+ if (kids[i] != button) kids[i].toggleClassName('sidebar-selector-tab-active', false);
+ else button.toggleClassName('sidebar-selector-tab-active', true);
+ }
+ // Fancy highlighter line width
+ const buttonWidth = button.get_allocated_width();
+ const highlightWidth = button.get_children()[0].get_allocated_width();
+ navIndicator.css = `
+ font-size: ${navIndex}px;
+ padding: 0px ${(buttonWidth - highlightWidth) / 2}px;
+ `;
+ },
+ child: Box({
+ hpack: 'center',
+ className: 'spacing-h-5',
+ children: [
+ MaterialIcon(`${isDone ? 'task_alt' : 'format_list_bulleted'}`, 'larger'),
+ Label({
+ className: 'txt txt-smallie',
+ label: `${isDone ? 'Done' : 'Unfinished'}`,
+ })
+ ]
+ }),
+ setup: (button) => Utils.timeout(1, () => {
+ setupCursorHover(button);
+ button.toggleClassName('sidebar-selector-tab-active', defaultTodoSelected === `${isDone ? 'done' : 'undone'}`);
+ }),
+ });
+ const undoneButton = TodoTabButton(false, 0);
+ const doneButton = TodoTabButton(true, 1);
+ const navIndicator = NavigationIndicator(2, false, { // The line thing
+ className: 'sidebar-selector-highlight',
+ css: 'font-size: 0px; padding: 0rem 1.636rem;', // Shush
+ })
+ return Widget.Box({
+ hexpand: true,
+ vertical: true,
+ className: 'spacing-v-10',
+ setup: (box) => { // undone/done selector rail
+ box.pack_start(Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Box({
+ className: 'sidebar-selectors spacing-h-5',
+ homogeneous: true,
+ setup: (box) => {
+ box.pack_start(undoneButton, false, true, 0);
+ box.pack_start(doneButton, false, true, 0);
+ }
+ }),
+ Widget.Box({
+ className: 'sidebar-selector-highlight-offset',
+ homogeneous: true,
+ children: [navIndicator]
+ })
+ ]
+ }), false, false, 0);
+ box.pack_end(todoItemsBox, true, true, 0);
+ },
+ });
+};
+
diff --git a/modules/styling/stylix.nix b/modules/styling/stylix.nix
index f5c82a3..4b31070 100644
--- a/modules/styling/stylix.nix
+++ b/modules/styling/stylix.nix
@@ -7,8 +7,7 @@
cfg = config.modules.themes.stylix;
inherit (config.modules.themes.stylix) scheme image;
inherit (config.modules.themes.stylix.cursor) size;
- inherit (config.modules.themes.stylix.fontsizes) terminal popups;
- inherit (config.modules.other.system) username;
+ inherit (config.modules.themes.stylix.fontsizes) terminal popups applications;
in {
options.modules.themes.stylix = {
enable = lib.mkEnableOption "stylix";
@@ -43,38 +42,47 @@ in {
description = "Popup font size";
type = lib.types.int;
};
+ applications = lib.mkOption {
+ description = "Application font size";
+ type = lib.types.int;
+ };
};
};
config = lib.mkIf cfg.enable {
stylix = {
enable = true;
+ homeManagerIntegration.followSystem = true;
base16Scheme = scheme;
inherit image;
polarity = "dark";
autoEnable = true;
cursor = {
inherit size;
- #size = 16;
package = pkgs.bibata-cursors;
name = "Bibata-Modern-Classic";
};
fonts = {
sizes = {
- inherit terminal popups;
+ inherit terminal popups applications;
};
monospace = {
package =
pkgs.nerdfonts.override {fonts = ["JetBrainsMono"];};
name = "JetBrainsMono";
};
- serif = {
- package = pkgs.noto-fonts;
- name = "Noto Serif";
- };
- sansSerif = {
- package = pkgs.lexend;
- name = "Lexend";
- };
+
+ serif = config.stylix.fonts.monospace;
+ sansSerif = config.stylix.fonts.monospace;
+
+ # serif = {
+ #
+ # package = pkgs.noto-fonts;
+ # name = "Noto Serif";
+ # };
+ # sansSerif = {
+ # package = pkgs.lexend;
+ # name = "Lexend";
+ # };
emoji = {
package = pkgs.noto-fonts-emoji;
name = "Noto Color Emoji";
@@ -95,6 +103,7 @@ in {
lightdm.enable = true;
nixos-icons.enable = true;
nixvim.enable = true;
+ plymouth.enable = true;
plymouth.logoAnimated = true;
};
};
diff --git a/modules/tui/neovim.nix b/modules/tui/neovim.nix
index be1c7c9..c0fac38 100644
--- a/modules/tui/neovim.nix
+++ b/modules/tui/neovim.nix
@@ -6,10 +6,10 @@
...
}:
with lib; let
- cfg = config.modules.programs.neovim;
- username = config.modules.other.system.username;
+ cfg = config.modules.programs.neovim-old;
+ inherit (config.modules.other.system) username;
in {
- options.modules.programs.neovim.enable = mkEnableOption "neovim";
+ options.modules.programs.neovim-old.enable = mkEnableOption "neovim-old";
config = mkIf cfg.enable {
home-manager.users.${username} = {
diff --git a/options/common/bluetooth.nix b/options/common/bluetooth.nix
index f08d5aa..45c65e9 100644
--- a/options/common/bluetooth.nix
+++ b/options/common/bluetooth.nix
@@ -1,23 +1,8 @@
-{
- config,
- lib,
- pkgs,
- ...
-}: let
- inherit (config.modules.other.system) username;
-in {
+_: {
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
};
services.blueman.enable = true;
-
- # Bluetooth headset media control buttons
- systemd.user.services.mpris-proxy = {
- description = "Mpris proxy";
- after = ["network.target" "sound.target"];
- wantedBy = ["default.target"];
- serviceConfig.ExecStart = "${pkgs.bluez}/bin/mpris-proxy";
- };
}
diff --git a/options/common/gpu/nvidia.nix b/options/common/gpu/nvidia.nix
index 8c0e0fd..315d919 100644
--- a/options/common/gpu/nvidia.nix
+++ b/options/common/gpu/nvidia.nix
@@ -8,7 +8,8 @@
enable = true;
package = pkgs.mesa.drivers;
extraPackages = with pkgs; [
- vpl-gpu-rt
+ intel-compute-runtime
+ intel-media-driver
];
};
hardware.nvidia = {