├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── home ├── ags │ ├── .gitignore │ ├── README.md │ ├── config.js │ ├── default.nix │ ├── package-lock.json │ ├── package.json │ ├── scss │ │ ├── common │ │ │ ├── a11y-button.scss │ │ │ ├── button.scss │ │ │ ├── floating-widget.scss │ │ │ ├── hidden.scss │ │ │ ├── menu.scss │ │ │ ├── scrollable.scss │ │ │ ├── slider.scss │ │ │ ├── spacing.scss │ │ │ ├── switch.scss │ │ │ ├── text-border.scss │ │ │ ├── tooltip.scss │ │ │ ├── unset.scss │ │ │ └── widget.scss │ │ ├── main.scss │ │ ├── variables.scss │ │ └── widgets │ │ │ ├── about.scss │ │ │ ├── applauncher.scss │ │ │ ├── bar.scss │ │ │ ├── dashboard.scss │ │ │ ├── desktop.scss │ │ │ ├── dock.scss │ │ │ ├── lockscreen.scss │ │ │ ├── media.scss │ │ │ ├── notifications.scss │ │ │ ├── osd.scss │ │ │ ├── overview.scss │ │ │ ├── powermenu.scss │ │ │ ├── quicksettings.scss │ │ │ └── settings.scss │ ├── setup.sh │ ├── src │ │ ├── applauncher │ │ │ ├── AppItem.js │ │ │ └── Applauncher.js │ │ ├── bar │ │ │ ├── PanelButton.js │ │ │ ├── TopBar.js │ │ │ └── buttons │ │ │ │ ├── BatteryBar.js │ │ │ │ ├── ColorPicker.js │ │ │ │ ├── DateButton.js │ │ │ │ ├── FocusedClient.js │ │ │ │ ├── MediaIndicator.js │ │ │ │ ├── NotificationIndicator.js │ │ │ │ ├── OverviewButton.js │ │ │ │ ├── PowerMenu.js │ │ │ │ ├── ScreenRecord.js │ │ │ │ ├── SubMenu.js │ │ │ │ ├── SwayWorkspaces.js │ │ │ │ ├── SysTray.js │ │ │ │ ├── System.js │ │ │ │ ├── SystemIndicators.js │ │ │ │ ├── Taskbar.js │ │ │ │ └── Workspaces.js │ │ ├── dashboard │ │ │ ├── Dashboard.js │ │ │ ├── DateColumn.js │ │ │ └── NotificationColumn.js │ │ ├── desktop │ │ │ ├── Desktop.js │ │ │ └── DesktopMenu.js │ │ ├── icons.js │ │ ├── main.js │ │ ├── misc │ │ │ ├── Avatar.js │ │ │ ├── BatteryIcon.js │ │ │ ├── Clock.js │ │ │ ├── FontIcon.js │ │ │ ├── HoverRevealer.js │ │ │ ├── IconBrowser.js │ │ │ ├── Notification.js │ │ │ ├── PopupWindow.js │ │ │ ├── Progress.js │ │ │ ├── RegularWindow.js │ │ │ └── mpris.js │ │ ├── notifications │ │ │ └── Notifications.js │ │ ├── options.js │ │ ├── powermenu │ │ │ ├── PowerMenu.js │ │ │ ├── ShadedPopup.js │ │ │ └── Verification.js │ │ ├── quicksettings │ │ │ ├── QuickSettings.js │ │ │ ├── ToggleButton.js │ │ │ └── widgets │ │ │ │ ├── Bluetooth.js │ │ │ │ ├── Brightness.js │ │ │ │ ├── DND.js │ │ │ │ ├── Media.js │ │ │ │ ├── MicMute.js │ │ │ │ ├── Network.js │ │ │ │ ├── Theme.js │ │ │ │ └── Volume.js │ │ ├── services │ │ │ ├── brightness.js │ │ │ ├── colorpicker.js │ │ │ ├── lockscreen.js │ │ │ ├── onScreenIndicator.js │ │ │ ├── powermenu.js │ │ │ └── screenrecord.js │ │ ├── settings │ │ │ ├── SettingsDialog.js │ │ │ ├── globals.js │ │ │ ├── option.js │ │ │ ├── scss.js │ │ │ ├── setup.js │ │ │ ├── theme.js │ │ │ └── wallpaper.js │ │ ├── themes.js │ │ ├── utils.js │ │ └── variables.js │ ├── tsconfig.json │ └── types ├── default.nix ├── dev.nix ├── foot.nix ├── git.nix ├── gtk.nix ├── hyprland.nix ├── modules │ └── pointer.nix ├── starship.nix ├── sway.nix ├── vesktop │ ├── default.nix │ └── settings.json ├── wezterm │ ├── default.nix │ └── wezterm.lua ├── zed │ ├── default.nix │ └── settings.json └── zsh.nix ├── pkgs ├── default.nix ├── gimp-devel │ ├── default.nix │ ├── meson-gtls.diff │ └── pygimp-interp.diff ├── neovim.nix ├── snd-hda-intel.nix ├── standalone.nix ├── webcord.nix └── xq.nix └── system ├── configuration.nix ├── hardware-configuration.nix └── snd-hda-intel-razer.patch /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ossian Mapes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ozwaldorf's flake 2 | 3 | ## Overview 4 | 5 | ``` 6 | ├── home : Home manager configuration 7 | ├── pkgs : Custom package overlay 8 | └── system : System configurations 9 | ``` 10 | 11 | ## Usage 12 | 13 | Install the system and reboot (x86_64-linux): 14 | 15 | ```sh 16 | git clone https://github.com/ozwaldorf/flake && cd flake 17 | 18 | # copy your hardware config in 19 | cp /etc/nixos/hardware-configuration.nix system 20 | 21 | # build and reboot 22 | sudo nixos-rebuild switch --upgrade-all --flake . && reboot 23 | ``` 24 | 25 | Run standalone neovim: 26 | 27 | ```sh 28 | nix run github:ozwaldorf/flake\#neovim 29 | ``` 30 | 31 | Dev environment: 32 | 33 | ```sh 34 | nix run github:ozwaldorf/flake 35 | ``` 36 | 37 | ## Cache 38 | 39 | Binary caches are provided via https://garnix.io 40 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "ozwaldorf's flake"; 3 | 4 | inputs = { 5 | # Nix libraries 6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | home-manager = { 8 | url = "github:nix-community/home-manager"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | nixvim = { 12 | url = "github:nix-community/nixvim"; 13 | inputs.nixpkgs.follows = "nixpkgs"; 14 | }; 15 | carburetor = { 16 | url = "github:ozwaldorf/carburetor"; 17 | }; 18 | 19 | # Applications 20 | ags = { 21 | url = "github:ozwaldorf/ags"; 22 | # inputs.nixpkgs.follows = "nixpkgs"; 23 | }; 24 | zoom-sync = { 25 | url = "github:ozwaldorf/zoom-sync"; 26 | inputs.nixpkgs.follows = "nixpkgs"; 27 | }; 28 | neovim-nightly-overlay = { 29 | url = "github:nix-community/neovim-nightly-overlay"; 30 | inputs.nixpkgs.follows = "nixpkgs"; 31 | }; 32 | }; 33 | 34 | nixConfig = { 35 | extra-substituters = [ "https://cache.garnix.io" ]; 36 | extra-trusted-public-keys = [ "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ]; 37 | }; 38 | 39 | outputs = 40 | { 41 | self, 42 | nixpkgs, 43 | home-manager, 44 | ... 45 | }@inputs: 46 | let 47 | # Nixos system variables 48 | hostname = "onix"; 49 | username = "oz"; 50 | 51 | # Absolute path to the directory containing this flake. 52 | # Used for creating "out of store" symlinks. For example, mostly 53 | # in order to allow in-app setting changes to modify the flake. 54 | flakeDirectory = "/etc/nixos"; 55 | 56 | # Flake utilities 57 | overlay = import ./pkgs inputs; 58 | pkgsFor = 59 | system: 60 | import nixpkgs { 61 | inherit system; 62 | overlays = [ 63 | inputs.carburetor.overlays.insert 64 | # inputs.neovim-nightly-overlay.overlays.default 65 | (final: prev: { 66 | # Force insert flake packages that dont have builtin overlays. 67 | ags = inputs.ags.packages.${prev.system}.default; 68 | zoom-sync = inputs.zoom-sync.packages.${prev.system}.default; 69 | }) 70 | 71 | # Custom packages 72 | overlay 73 | ]; 74 | config.allowUnfree = true; 75 | }; 76 | forAllSystems = 77 | f: nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (system: f (pkgsFor system)); 78 | in 79 | { 80 | # Full nixos system and home configuration 81 | nixosConfigurations = { 82 | ${hostname} = nixpkgs.lib.nixosSystem rec { 83 | system = "x86_64-linux"; 84 | specialArgs = { 85 | inherit 86 | inputs 87 | system 88 | username 89 | hostname 90 | flakeDirectory 91 | ; 92 | pkgs = pkgsFor system; 93 | homeDirectory = "/home/" + username; 94 | }; 95 | 96 | modules = [ 97 | # System level config 98 | ./system/configuration.nix 99 | 100 | # User level config 101 | { 102 | home-manager = { 103 | useGlobalPkgs = true; 104 | useUserPackages = true; 105 | extraSpecialArgs = specialArgs; 106 | users.${username} = import ./home; 107 | }; 108 | } 109 | 110 | # Modules 111 | home-manager.nixosModules.home-manager 112 | ]; 113 | }; 114 | }; 115 | 116 | # Flake outputs 117 | overlays.default = overlay; 118 | packages = forAllSystems ( 119 | pkgs: (pkgs.lib.attrsets.getAttrs (builtins.attrNames (self.overlays.default null null)) pkgs) 120 | ); 121 | 122 | # `nix run` for a standalone headless environment starting with zsh 123 | apps = forAllSystems (pkgs: { 124 | default = { 125 | type = "app"; 126 | program = "${pkgs.standalone}/bin/zsh"; 127 | }; 128 | }); 129 | 130 | # `nix fmt` 131 | formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style); 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /home/ags/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /home/ags/README.md: -------------------------------------------------------------------------------- 1 | # Gnuhome AGS 2 | 3 | Recreation of Gnome 45 in AGS 4 | 5 | ## Attributions 6 | 7 | Most of this setup is taken from https://github.com/Aylur/dotfiles/tree/main/ags 8 | -------------------------------------------------------------------------------- /home/ags/config.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import main from "./src/main.js"; 3 | 4 | const v = { 5 | ags: `v${pkg.version}`, 6 | expected: `v1.7.3`, 7 | }; 8 | 9 | function mismatch() { 10 | print(`my config expects ${v.expected}, but your ags is ${v.ags}`); 11 | App.connect("config-parsed", (app) => app.Quit()); 12 | return {}; 13 | } 14 | 15 | export default main; 16 | -------------------------------------------------------------------------------- /home/ags/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | config, 5 | flakeDirectory, 6 | ... 7 | }: 8 | { 9 | imports = [ inputs.ags.homeManagerModules.default ]; 10 | 11 | home.packages = with pkgs; [ 12 | libdbusmenu-gtk3 13 | brightnessctl 14 | playerctl 15 | hyprpicker 16 | playerctl 17 | sassc 18 | ]; 19 | 20 | programs.ags = { 21 | enable = true; 22 | configDir = config.lib.file.mkOutOfStoreSymlink flakeDirectory + "/home/ags"; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /home/ags/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ozwaldorf-ags-dotfiles", 3 | "version": "1.5.5", 4 | "description": "My config files for AGS", 5 | "main": "config.js", 6 | "scripts": { 7 | "lint": "eslint . --fix", 8 | "stylelint": "stylelint ./scss --fix" 9 | }, 10 | "author": "Ossian, Aylur", 11 | "kofi": "https://ko-fi.com/aylur", 12 | "devDependencies": { 13 | "@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.0", 14 | "@girs/gobject-2.0": "^2.76.1-3.2.3", 15 | "@girs/gtk-3.0": "^3.24.39-3.2.2", 16 | "@girs/gvc-1.0": "^1.0.0-3.1.0", 17 | "@girs/nm-1.0": "^1.43.1-3.1.0", 18 | "stylelint-config-standard-scss": "^10.0.0", 19 | "@typescript-eslint/eslint-plugin": "^5.33.0", 20 | "@typescript-eslint/parser": "^5.33.0", 21 | "eslint": "^8.44.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /home/ags/scss/common/a11y-button.scss: -------------------------------------------------------------------------------- 1 | @import "./button"; 2 | 3 | @mixin accs-button($flat: false, $reactive: true) { 4 | @include button($flat: true, $reactive: false, $focusable: false); 5 | color: $fg-color; 6 | 7 | > * { 8 | border-radius: $radii; 9 | transition: $transition; 10 | 11 | @if $flat { 12 | background-color: transparent; 13 | box-shadow: none; 14 | } @else { 15 | background-color: $widget-bg; 16 | box-shadow: inset 0 0 0 $border-width $border-color; 17 | } 18 | } 19 | 20 | @if $reactive { 21 | &:focus > *, 22 | &.focused > * { 23 | @include button-focus; 24 | } 25 | 26 | &:hover > * { 27 | @include button-hover; 28 | } 29 | 30 | &:active, 31 | &.active, 32 | &.on, 33 | &:checked { 34 | > * { 35 | @include button-active; 36 | } 37 | 38 | &:hover > * { 39 | box-shadow: 40 | inset 0 0 0 $border-width $border-color, 41 | inset 0 0 0 99px $hover; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /home/ags/scss/common/button.scss: -------------------------------------------------------------------------------- 1 | @mixin button-focus() { 2 | box-shadow: inset 0 0 0 $border-width $accent; 3 | background-color: $hover; 4 | color: $hover-fg; 5 | } 6 | 7 | @mixin button-hover() { 8 | box-shadow: inset 0 0 0 $border-width $border-color; 9 | background-color: $hover; 10 | color: $hover-fg; 11 | } 12 | 13 | @mixin button-active() { 14 | box-shadow: inset 0 0 0 $border-width $border-color; 15 | background-color: $hover; 16 | color: $hover-fg; 17 | } 18 | 19 | @mixin button-disabled() { 20 | box-shadow: none; 21 | background-color: transparent; 22 | color: transparentize($fg-color, 0.7); 23 | } 24 | 25 | @mixin button($flat: false, $reactive: true, $radii: $radii, $focusable: true) { 26 | all: unset; 27 | transition: $transition; 28 | border-radius: $radii; 29 | color: $fg-color; 30 | 31 | @if $flat { 32 | background-color: transparent; 33 | background-image: none; 34 | box-shadow: none; 35 | } @else { 36 | background-color: $widget-bg; 37 | box-shadow: inset 0 0 0 $border-width $border-color; 38 | } 39 | 40 | @if $reactive { 41 | @if $focusable { 42 | &:focus { 43 | @include button-focus; 44 | } 45 | } 46 | 47 | &:hover { 48 | @include button-hover; 49 | } 50 | 51 | &:active, 52 | &.on, 53 | &.active, 54 | &:checked { 55 | @include button-active; 56 | 57 | &:hover { 58 | box-shadow: 59 | inset 0 0 0 $border-width $border-color, 60 | inset 0 0 0 99px $hover; 61 | } 62 | } 63 | } 64 | 65 | &:disabled { 66 | @include button-disabled; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /home/ags/scss/common/floating-widget.scss: -------------------------------------------------------------------------------- 1 | @mixin floating-widget { 2 | @if $drop-shadow { 3 | box-shadow: 0 0 5px 0 $shadow; 4 | } 5 | 6 | margin: max($spacing, 8px); 7 | border: $border-width solid $popover-border-color; 8 | background-color: $bg-color; 9 | color: $fg-color; 10 | border-radius: $popover-radius; 11 | padding: $popover-padding; 12 | } 13 | -------------------------------------------------------------------------------- /home/ags/scss/common/hidden.scss: -------------------------------------------------------------------------------- 1 | @mixin hidden { 2 | background-color: transparent; 3 | background-image: none; 4 | border-color: transparent; 5 | box-shadow: none; 6 | -gtk-icon-transform: scale(0); 7 | 8 | * { 9 | background-color: transparent; 10 | background-image: none; 11 | border-color: transparent; 12 | box-shadow: none; 13 | -gtk-icon-transform: scale(0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /home/ags/scss/common/menu.scss: -------------------------------------------------------------------------------- 1 | window.popup { 2 | > * { 3 | border: none; 4 | box-shadow: none; 5 | } 6 | 7 | menu { 8 | border-radius: $popover-radius; 9 | background-color: $bg-color; 10 | padding: $popover-padding; 11 | border: $border-width solid $popover-border-color; 12 | 13 | separator { 14 | background-color: $border-color; 15 | } 16 | 17 | menuitem { 18 | @include button; 19 | padding: $spacing/2; 20 | margin: $spacing/2 0; 21 | &:first-child { 22 | margin-top: 0; 23 | } 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /home/ags/scss/common/scrollable.scss: -------------------------------------------------------------------------------- 1 | @mixin scrollable { 2 | scrollbar, 3 | scrollbar * { 4 | all: unset; 5 | } 6 | 7 | scrollbar.vertical { 8 | transition: $transition; 9 | background-color: transparentize($bg-color, 0.7); 10 | 11 | &:hover { 12 | background-color: transparentize($bg-color, 0.3); 13 | 14 | slider { 15 | background-color: transparentize($fg-color, 0.3); 16 | min-width: 0.6em; 17 | } 18 | } 19 | } 20 | 21 | scrollbar.vertical slider { 22 | background-color: transparentize($fg-color, 0.5); 23 | border-radius: $radii; 24 | min-width: 0.4em; 25 | min-height: 2em; 26 | transition: $transition; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /home/ags/scss/common/slider.scss: -------------------------------------------------------------------------------- 1 | @import "./unset"; 2 | 3 | @mixin slider( 4 | $width: 0.3em, 5 | $slider-width: 0.5em, 6 | $gradient: $active-gradient, 7 | $slider: true, 8 | $focusable: true, 9 | $radii: $radii 10 | ) { 11 | @include unset($rec: true); 12 | 13 | trough { 14 | transition: $transition; 15 | border-radius: $radii; 16 | border: $border; 17 | background-color: $widget-bg; 18 | min-height: $width; 19 | min-width: $width; 20 | 21 | highlight, 22 | progress { 23 | border-radius: max($radii - $border-width, 0); 24 | background-image: $gradient; 25 | min-height: $width; 26 | min-width: $width; 27 | } 28 | } 29 | 30 | slider { 31 | box-shadow: none; 32 | background-color: transparent; 33 | border: $border-width solid transparent; 34 | transition: $transition; 35 | border-radius: $radii; 36 | min-height: $width; 37 | min-width: $width; 38 | margin: -$slider-width; 39 | } 40 | 41 | &:hover { 42 | trough { 43 | background-color: $hover; 44 | } 45 | 46 | slider { 47 | @if $slider { 48 | background-color: $fg-color; 49 | border-color: $border-color; 50 | 51 | @if $drop-shadow { 52 | box-shadow: 0 0 3px 0 $shadow; 53 | } 54 | } 55 | } 56 | } 57 | 58 | &:disabled { 59 | highlight, 60 | progress { 61 | background-color: transparentize($fg-color, 0.4); 62 | background-image: none; 63 | } 64 | } 65 | 66 | @if $focusable { 67 | trough:focus { 68 | background-color: $hover; 69 | box-shadow: inset 0 0 0 $border-width $accent; 70 | 71 | slider { 72 | @if $slider { 73 | background-color: $fg-color; 74 | box-shadow: inset 0 0 0 $border-width $accent; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /home/ags/scss/common/spacing.scss: -------------------------------------------------------------------------------- 1 | @mixin spacing($multiplier: 1, $spacing: $spacing, $rec: false) { 2 | &.horizontal > * { 3 | margin: 0 $spacing * $multiplier / 2; 4 | &:first-child { 5 | margin-left: 0; 6 | } 7 | &:last-child { 8 | margin-right: 0; 9 | } 10 | } 11 | 12 | &.vertical > * { 13 | margin: $spacing * $multiplier / 2 0; 14 | &:first-child { 15 | margin-top: 0; 16 | } 17 | &:last-child { 18 | margin-bottom: 0; 19 | } 20 | } 21 | 22 | @if $rec { 23 | box { 24 | &.horizontal > * { 25 | margin: 0 $spacing * $multiplier / 2; 26 | &:first-child { 27 | margin-left: 0; 28 | } 29 | &:last-child { 30 | margin-right: 0; 31 | } 32 | } 33 | 34 | &.vertical > * { 35 | margin: $spacing * $multiplier / 2 0; 36 | &:first-child { 37 | margin-top: 0; 38 | } 39 | &:last-child { 40 | margin-bottom: 0; 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /home/ags/scss/common/switch.scss: -------------------------------------------------------------------------------- 1 | @import "./button"; 2 | 3 | @mixin switch { 4 | @include button; 5 | 6 | slider { 7 | background-color: $accent-fg; 8 | border-radius: $radii; 9 | min-width: 24px; 10 | min-height: 24px; 11 | } 12 | 13 | image { 14 | color: transparent; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /home/ags/scss/common/text-border.scss: -------------------------------------------------------------------------------- 1 | @mixin text-border { 2 | text-shadow: 3 | -1 * $border-width -1 * $border-width 0 $border-color, 4 | $border-width $border-width 0 $border-color, 5 | -1 * $border-width $border-width 0 $border-color, 6 | $border-width -1 * $border-width 0 $border-color; 7 | 8 | -gtk-icon-shadow: 9 | -1 * $border-width -1 * $border-width 0 $border-color, 10 | $border-width $border-width 0 $border-color, 11 | -1 * $border-width $border-width 0 $border-color, 12 | $border-width -1 * $border-width 0 $border-color; 13 | } 14 | -------------------------------------------------------------------------------- /home/ags/scss/common/tooltip.scss: -------------------------------------------------------------------------------- 1 | tooltip { 2 | * { 3 | all: unset; 4 | } 5 | 6 | background-color: transparent; 7 | border: none; 8 | 9 | > * > * { 10 | background-color: $bg-color; 11 | border-radius: $radii; 12 | border: $border-width solid $popover-border-color; 13 | color: $fg-color; 14 | padding: 8px; 15 | margin: 4px; 16 | box-shadow: 0 0 3px 0 $shadow; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /home/ags/scss/common/unset.scss: -------------------------------------------------------------------------------- 1 | @mixin unset($rec: false) { 2 | all: unset; 3 | 4 | @if $rec { 5 | * { 6 | all: unset; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /home/ags/scss/common/widget.scss: -------------------------------------------------------------------------------- 1 | @mixin widget { 2 | transition: $transition; 3 | border-radius: $radii; 4 | color: $fg-color; 5 | background-color: $widget-bg; 6 | border: $border; 7 | } 8 | -------------------------------------------------------------------------------- /home/ags/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import "/tmp/ags/scss/options"; 2 | @import "./variables"; 3 | 4 | // common 5 | @import "./common/unset"; 6 | @import "./common/widget"; 7 | @import "./common/button"; 8 | @import "./common/a11y-button"; 9 | @import "./common/floating-widget"; 10 | @import "./common/slider"; 11 | @import "./common/scrollable"; 12 | @import "./common/switch"; 13 | @import "./common/hidden"; 14 | @import "./common/text-border"; 15 | @import "./common/tooltip"; 16 | @import "./common/menu"; 17 | @import "./common/spacing"; 18 | 19 | // widgets 20 | @import "./widgets/about"; 21 | @import "./widgets/applauncher"; 22 | @import "./widgets/bar"; 23 | @import "./widgets/desktop"; 24 | @import "./widgets/notifications"; 25 | @import "./widgets/overview"; 26 | @import "./widgets/osd"; 27 | @import "./widgets/dashboard"; 28 | @import "./widgets/dock"; 29 | @import "./widgets/powermenu"; 30 | @import "./widgets/lockscreen"; 31 | @import "./widgets/media"; 32 | @import "./widgets/quicksettings"; 33 | @import "./widgets/settings"; 34 | 35 | // additional overrides 36 | @import "/tmp/ags/scss/additional"; 37 | -------------------------------------------------------------------------------- /home/ags/scss/variables.scss: -------------------------------------------------------------------------------- 1 | // variables are defined in options.js 2 | // these ones are derived from those 3 | 4 | $hover: transparentize($_widget-bg, ($widget-opacity * 0.9) / 100); 5 | $widget-bg: transparentize($_widget-bg, $widget-opacity / 100); 6 | $active-gradient: linear-gradient($accent-gradient); 7 | 8 | $hover-fg: if( 9 | $color-scheme == "dark", 10 | lighten($fg-color, 10%), 11 | darken($fg-color, 8%) 12 | ); 13 | 14 | $border-color: transparentize($_border-color, $border-opacity / 100); 15 | $border: $border-width solid $border-color; 16 | 17 | $text-shadow: 2px 2px 2px $shadow; 18 | 19 | $popover-border-color: transparentize( 20 | $_border-color, 21 | max(($border-opacity - 1) / 100, 0) 22 | ); 23 | $popover-padding: $padding * $popover-padding-multiplier; 24 | $popover-radius: if($radii == 0, 0, $radii + $popover-padding); 25 | 26 | $wm-gaps: floor($spacing * $wm-gaps-multiplier); 27 | 28 | $shader-fg: #fff; 29 | 30 | * { 31 | font-size: $font-size; 32 | } 33 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/about.scss: -------------------------------------------------------------------------------- 1 | window#about { 2 | @include unset; 3 | 4 | .window-content { 5 | @include floating-widget; 6 | min-width: 300px; 7 | } 8 | 9 | .avatar { 10 | min-width: 200px; 11 | min-height: 200px; 12 | background-size: cover; 13 | border: $border; 14 | margin: $spacing 0; 15 | } 16 | 17 | .labels { 18 | .title { 19 | font-size: 1.2em; 20 | } 21 | 22 | .author { 23 | color: transparentize($fg-color, 0.2); 24 | } 25 | 26 | .version { 27 | margin-top: $spacing; 28 | margin-bottom: $spacing * 2; 29 | border-radius: $radii; 30 | background-color: $widget-bg; 31 | color: $accent; 32 | padding: $padding; 33 | } 34 | } 35 | 36 | .buttons { 37 | padding-bottom: $popover-padding; 38 | 39 | button { 40 | @include button; 41 | padding: $padding; 42 | 43 | &:first-child { 44 | border-radius: $radii $radii 0 0; 45 | } 46 | 47 | &:last-child { 48 | border-radius: 0 0 $radii $radii; 49 | } 50 | } 51 | } 52 | 53 | .dont-show { 54 | @include button; 55 | padding: $padding; 56 | 57 | image { 58 | font-size: 1.4em; 59 | color: $red; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/applauncher.scss: -------------------------------------------------------------------------------- 1 | window#applauncher .window-content { 2 | @include floating_widget; 3 | 4 | entry { 5 | @include button; 6 | padding: $padding; 7 | margin-bottom: $spacing; 8 | 9 | label, 10 | image { 11 | color: $fg-color; 12 | } 13 | } 14 | 15 | separator { 16 | min-height: 1px; 17 | background-color: $hover; 18 | } 19 | 20 | scrolledwindow { 21 | @include scrollable; 22 | min-width: $applauncher-width; 23 | min-height: $applauncher-height; 24 | } 25 | 26 | button.app-item { 27 | @include button($flat: true, $reactive: false); 28 | > box { 29 | @include spacing(0.5); 30 | } 31 | transition: $transition; 32 | padding: $padding; 33 | 34 | label { 35 | transition: $transition; 36 | 37 | &.title { 38 | color: $fg-color; 39 | } 40 | 41 | &.description { 42 | color: transparentize($fg-color, 0.3); 43 | } 44 | } 45 | 46 | image { 47 | transition: $transition; 48 | } 49 | 50 | &:hover, 51 | &:focus { 52 | .title { 53 | color: $accent; 54 | } 55 | 56 | image { 57 | -gtk-icon-shadow: 2px 2px $accent; 58 | } 59 | } 60 | 61 | &:active { 62 | background-color: transparentize($accent, 0.5); 63 | border-radius: $radii; 64 | box-shadow: inset 0 0 0 $border-width $border-color; 65 | 66 | .title { 67 | color: $fg-color; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/bar.scss: -------------------------------------------------------------------------------- 1 | $bar-spacing: $spacing / 2; 2 | $button-radius: if( 3 | $bar-style == "floating", 4 | max(0, $radii - $bar-spacing), 5 | $radii 6 | ); 7 | 8 | @mixin panel-button($flat: $bar-flat-buttons, $reactive: true) { 9 | @include unset; 10 | 11 | @if $bar-style == "separated" { 12 | transition: $transition; 13 | 14 | > * { 15 | @include floating-widget; 16 | border-radius: $radii; 17 | margin: $wm-gaps $bar-spacing; 18 | transition: $transition; 19 | } 20 | 21 | &:hover > * { 22 | color: $hover-fg; 23 | 24 | @if $drop-shadow { 25 | box-shadow: 26 | 0 0 min(6px, $spacing/2) 0 $shadow, 27 | inset 0 0 0 99px $hover; 28 | } @else { 29 | box-shadow: inset 0 0 0 99px $hover; 30 | } 31 | } 32 | 33 | &:active > *, 34 | &.active > * { 35 | // label, 36 | // image { 37 | // color: $accent-fg; 38 | // } 39 | // background-image: $active-gradient; 40 | // background-color: $accent; 41 | } 42 | } @else { 43 | @include accs-button($flat, $reactive); 44 | 45 | > * { 46 | border-radius: $button-radius; 47 | margin: 4px; 48 | } 49 | } 50 | 51 | label, 52 | image { 53 | // font-weight: bold; 54 | } 55 | 56 | > * { 57 | padding: $padding * 0.3 $padding * 0.4; 58 | } 59 | } 60 | 61 | .panel { 62 | @if $bar-style == "normal" { 63 | background-color: $bg-color; 64 | padding: 2 0 2 0; 65 | } 66 | 67 | @if not $screen-corners and $bar-style == "normal" { 68 | @if $bar-position == "bottom" { 69 | border-top: $border; 70 | } @else { 71 | border-bottom: $border; 72 | } 73 | } 74 | 75 | @if $bar-style == "floating" { 76 | @include floating-widget; 77 | border-radius: $radii; 78 | margin: $wm-gaps; 79 | padding: 0; 80 | } 81 | 82 | @if $bar-style == "separated" { 83 | > .end > button:last-child > * { 84 | margin-right: $wm-gaps; 85 | } 86 | 87 | > .start > button:first-child > * { 88 | margin-left: $wm-gaps; 89 | } 90 | } 91 | 92 | .panel-button { 93 | @include panel-button; 94 | } 95 | 96 | .tray-item, 97 | .color-picker { 98 | @include panel-button($flat: true); 99 | } 100 | 101 | .sub-menu { 102 | color: transparentize($fg-color, 0.5) 103 | } 104 | 105 | separator { 106 | background-color: transparentize($fg-color, 0.86); 107 | border-radius: $radii; 108 | min-height: 5px; 109 | min-width: 5px; 110 | } 111 | 112 | .overview { 113 | label { 114 | color: transparentize($accent, 0.2); 115 | } 116 | &:hover label { 117 | color: $accent; 118 | } 119 | &:active label, 120 | &.active label { 121 | color: $accent-fg; 122 | } 123 | } 124 | 125 | .powermenu, 126 | .recorder { 127 | image { 128 | color: transparentize($red, 0.3); 129 | } 130 | &:hover image { 131 | color: transparentize($red, 0.15); 132 | } 133 | &:active image { 134 | color: $red; 135 | } 136 | } 137 | 138 | .focused-client > box > box, 139 | .quicksettings > box { 140 | @include spacing(1, if($bar-spacing == 0, $padding / 2, $bar-spacing)); 141 | } 142 | 143 | /* stylelint-disable-next-line selector-not-notation */ 144 | .quicksettings:not(.active):not(:active) { 145 | .bluetooth { 146 | // color: $blue; 147 | } 148 | 149 | .battery { 150 | &.low { 151 | color: $red; 152 | } 153 | &.charged, 154 | &.charging { 155 | color: $green; 156 | } 157 | } 158 | } 159 | 160 | .media { 161 | &.spotify image { 162 | color: $green; 163 | } 164 | &.firefox image { 165 | color: $orange; 166 | } 167 | &.mpv image { 168 | color: $magenta; 169 | } 170 | } 171 | 172 | .notifications { 173 | revealer > label { 174 | padding-right: 4px; 175 | } 176 | image { 177 | color: $yellow; 178 | } 179 | } 180 | 181 | .workspaces button { 182 | all: unset; 183 | 184 | .indicator { 185 | font-size: 0; 186 | min-width: 6px; 187 | min-height: 6px; 188 | border-radius: $radii * 0.6; 189 | box-shadow: inset 0 0 0 $border-width $border-color; 190 | margin: 0 $padding/5; 191 | transition: $transition/2; 192 | background-color: transparentize($fg-color, 0.8); 193 | } 194 | 195 | &.occupied .indicator { 196 | background-color: transparentize($fg-color, 0.5); 197 | min-width: 6px; 198 | min-height: 6px; 199 | } 200 | 201 | &:hover .indicator { 202 | box-shadow: inset 0 0 0 10px transparentize($fg-color, 0.8); 203 | } 204 | 205 | &.active .indicator, 206 | &:active .indicator { 207 | // background-color: $accent; 208 | background-color: $fg-color; 209 | } 210 | 211 | &.active .indicator { 212 | min-width: 32px; 213 | min-height: 8px; 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/dashboard.scss: -------------------------------------------------------------------------------- 1 | @mixin calendar { 2 | @include widget; 3 | padding: $padding * 2 $padding * 2; 4 | 5 | calendar { 6 | all: unset; 7 | 8 | &.button { 9 | // @include button($flat: true); 10 | } 11 | &.day { 12 | color: red; 13 | } 14 | 15 | &:selected { 16 | box-shadow: 17 | // inset 0 -8px 0 0 transparentize($accent, 0.5), 18 | inset 0 0 0 1px $accent; 19 | border-radius: $radii * 0.6; 20 | } 21 | 22 | &.header { 23 | background-color: transparent; 24 | border: none; 25 | color: transparentize($fg-color, 0.5); 26 | } 27 | 28 | &.highlight { 29 | background-color: transparent; 30 | color: transparentize($accent, 0.5); 31 | } 32 | 33 | &:indeterminate { 34 | color: transparentize($fg-color, 0.9); 35 | } 36 | 37 | font-size: 1.1em; 38 | padding: 2px; 39 | } 40 | } 41 | 42 | window#dashboard .window-content { 43 | @include floating-widget; 44 | 45 | .notifications { 46 | min-width: $notifications-width; 47 | 48 | .header { 49 | margin-bottom: $spacing; 50 | margin-right: $spacing; 51 | 52 | > label { 53 | margin-left: $radii / 2; 54 | } 55 | 56 | button { 57 | @include button; 58 | padding: $padding/2 $padding; 59 | } 60 | } 61 | 62 | .notification-scrollable { 63 | @include scrollable; 64 | } 65 | 66 | .notification-list { 67 | margin-right: $spacing; 68 | } 69 | 70 | .notification { 71 | @include notification; 72 | 73 | > box { 74 | @include widget; 75 | padding: $padding; 76 | margin-bottom: $spacing; 77 | } 78 | } 79 | 80 | .placeholder { 81 | image { 82 | font-size: 7em; 83 | } 84 | label { 85 | font-size: 1.2em; 86 | } 87 | } 88 | } 89 | 90 | separator { 91 | background-color: $popover-border-color; 92 | min-width: 2px; 93 | border-radius: $radii; 94 | margin-right: $spacing; 95 | } 96 | 97 | .datemenu, 98 | .system-info { 99 | @include spacing; 100 | } 101 | 102 | .clock-box { 103 | padding: $padding; 104 | 105 | .clock { 106 | font-size: 5em; 107 | } 108 | 109 | .uptime { 110 | color: transparentize($fg-color, 0.2); 111 | } 112 | } 113 | 114 | .calendar { 115 | @include calendar; 116 | } 117 | 118 | .circular-progress-box { 119 | @include widget; 120 | padding: 2 * $padding / 3; 121 | 122 | .circular-progress { 123 | min-height: $sys-info-size; 124 | min-width: $sys-info-size; 125 | margin: $padding/2; 126 | font-size: 0.7em; 127 | background-color: $bg-color; 128 | color: $accent; 129 | 130 | image { 131 | font-size: 1.8em; 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/desktop.scss: -------------------------------------------------------------------------------- 1 | window.corner .corner { 2 | background-color: $bg-color; 3 | border-radius: $radii * 2; 4 | } 5 | 6 | window.desktop { 7 | @if $bar-style == "normal" { 8 | border-radius: if($screen-corners, $radii * 2, 0); 9 | box-shadow: inset 0 0 $wm-gaps / 2 0 $shadow; 10 | } 11 | 12 | .clock-box-shadow { 13 | border: 5px solid $wallpaper-fg; 14 | border-radius: $radii; 15 | 16 | .clock-box { 17 | border-radius: max($radii - 5px, 0); 18 | padding: 0 14px; 19 | 20 | .clock { 21 | color: $wallpaper-fg; 22 | font-size: 140px; 23 | font-family: $mono-font; 24 | } 25 | 26 | .separator-box { 27 | padding: 24px 12px; 28 | 29 | separator { 30 | border-radius: $radii; 31 | min-width: 16px; 32 | min-height: 16px; 33 | background-color: $wallpaper-fg; 34 | } 35 | } 36 | } 37 | } 38 | 39 | .date { 40 | color: $wallpaper-fg; 41 | font-size: 48px; 42 | } 43 | 44 | @if $drop-shadow { 45 | .clock-box-shadow, 46 | separator { 47 | box-shadow: 2px 2px 2px 0 $shadow; 48 | } 49 | 50 | .clock-box { 51 | box-shadow: inset 2px 2px 2px 0 $shadow; 52 | } 53 | 54 | label { 55 | text-shadow: $text-shadow; 56 | } 57 | } @else { 58 | .clock-box-shadow { 59 | box-shadow: 60 | 0 0 0 $border-width $border-color, 61 | inset 0 0 0 $border-width $border-color; 62 | } 63 | 64 | separator { 65 | border: $border; 66 | } 67 | 68 | label { 69 | @include text-border; 70 | } 71 | } 72 | } 73 | 74 | .desktop-menu { 75 | image { 76 | margin-left: -14px; 77 | margin-right: 6px; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/dock.scss: -------------------------------------------------------------------------------- 1 | @mixin dock($spacing: $spacing * 0.7) { 2 | separator { 3 | border-radius: $radii; 4 | background-color: transparentize($fg-color, 0.8); 5 | margin: 0 $spacing; 6 | min-width: 2px; 7 | min-height: 2em; 8 | } 9 | 10 | button { 11 | @include accs-button($flat: true); 12 | 13 | .box { 14 | margin: $spacing / 2; 15 | } 16 | 17 | image { 18 | margin: $padding; 19 | 20 | @if $color-scheme == "light" { 21 | -gtk-icon-shadow: $text-shadow; 22 | } 23 | } 24 | 25 | .indicator { 26 | min-width: 8px; 27 | min-height: 8px; 28 | background-color: $fg-color; 29 | border-radius: $radii; 30 | margin-bottom: $padding/2; 31 | 32 | &.focused { 33 | background-image: $active-gradient; 34 | } 35 | } 36 | } 37 | } 38 | 39 | window.floating-dock .dock { 40 | @include dock; 41 | @include floating-widget; 42 | border-radius: if($radii == 0, 0, $radii + $spacing / 2); 43 | padding: $spacing / 2; 44 | } 45 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/lockscreen.scss: -------------------------------------------------------------------------------- 1 | window.lockscreen { 2 | background-color: rgba(0, 0, 0, 0.25); 3 | 4 | .avatar { 5 | @include widget; 6 | border-radius: $radii * 2; 7 | min-height: 200px; 8 | min-width: 200px; 9 | } 10 | 11 | .content { 12 | @include floating-widget; 13 | padding: $spacing * 4; 14 | } 15 | 16 | spinner { 17 | margin-top: $spacing * 2; 18 | } 19 | 20 | entry { 21 | @include button; 22 | margin-top: $spacing * 2; 23 | padding: $spacing; 24 | min-height: 20px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/media.scss: -------------------------------------------------------------------------------- 1 | @mixin player-color($color) { 2 | button { 3 | .shuffle.enabled { 4 | color: $color; 5 | } 6 | 7 | .loop { 8 | &.playlist, 9 | &.track { 10 | color: $color; 11 | } 12 | } 13 | 14 | &:active label { 15 | color: $color; 16 | } 17 | } 18 | 19 | .position-slider:hover trough { 20 | background-color: transparentize($color, 0.5); 21 | } 22 | 23 | .player-icon { 24 | color: $color; 25 | } 26 | } 27 | 28 | @mixin media() { 29 | @include widget; 30 | 31 | label { 32 | color: $shader-fg; 33 | text-shadow: $text-shadow; 34 | } 35 | 36 | .blurred-cover, 37 | .cover { 38 | background-size: cover; 39 | background-position: center; 40 | border-radius: $radii * 0.8; 41 | opacity: 0.8; 42 | } 43 | 44 | .cover { 45 | min-height: 150px; 46 | min-width: 150px; 47 | box-shadow: 2px 2px 2px 0 $shadow; 48 | margin: $padding; 49 | opacity: 0.9; 50 | } 51 | 52 | .labels { 53 | margin-top: $padding; 54 | 55 | label { 56 | font-size: 1.1em; 57 | text-shadow: $text-shadow; 58 | 59 | &.title { 60 | font-weight: bold; 61 | } 62 | } 63 | } 64 | 65 | .position-slider { 66 | @include slider( 67 | $width: 0.4em, 68 | $slider: false, 69 | $gradient: linear-gradient($shader-fg, $shader-fg), 70 | $radii: 0 71 | ); 72 | margin-bottom: $padding/2; 73 | 74 | trough { 75 | border: none; 76 | background-color: transparentize($shader-fg, 0.7); 77 | margin: 0 $padding 0 $padding/2; 78 | } 79 | } 80 | 81 | .footer-box { 82 | margin: -$padding/2 $padding $padding/2; 83 | 84 | image { 85 | -gtk-icon-shadow: $text-shadow; 86 | } 87 | } 88 | 89 | .controls button { 90 | @include unset; 91 | 92 | label { 93 | padding: 0 0.3em 0 0.5em; 94 | font-size: 2em; 95 | color: transparentize($shader-fg, 0.2); 96 | transition: $transition; 97 | 98 | &.shuffle, 99 | &.loop { 100 | font-size: 1.4em; 101 | } 102 | } 103 | 104 | &:hover label { 105 | color: transparentize($shader-fg, 0.1); 106 | } 107 | 108 | &:active label { 109 | color: $shader-fg; 110 | } 111 | } 112 | 113 | &.spotify { 114 | @include player-color($green); 115 | } 116 | &.firefox { 117 | @include player-color($orange); 118 | } 119 | &.mpv { 120 | @include player-color($magenta); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/notifications.scss: -------------------------------------------------------------------------------- 1 | @mixin notification() { 2 | &.critical > box { 3 | box-shadow: inset 0 0 0.5em 0 $red; 4 | } 5 | 6 | &:hover button.close-button { 7 | @include button-hover; 8 | background-color: transparentize($red, 0.5); 9 | } 10 | 11 | .content { 12 | .title { 13 | margin-right: $spacing; 14 | color: $fg-color; 15 | font-size: 1.1em; 16 | } 17 | 18 | .time { 19 | color: transparentize($fg-color, 0.2); 20 | } 21 | 22 | .description { 23 | font-size: 0.9em; 24 | color: transparentize($fg-color, 0.2); 25 | } 26 | 27 | .icon { 28 | border-radius: $radii * 0.8; 29 | margin-right: $spacing; 30 | 31 | &.img { 32 | border: $border; 33 | } 34 | } 35 | } 36 | 37 | box.actions { 38 | @include spacing(0.5); 39 | margin-top: $spacing; 40 | 41 | button { 42 | @include button; 43 | border-radius: $radii * 0.8; 44 | font-size: 1.2em; 45 | padding: $padding * 0.7; 46 | } 47 | } 48 | 49 | button.close-button { 50 | @include button($flat: true); 51 | margin-left: $spacing / 2; 52 | border-radius: $radii * 0.8; 53 | min-width: 1.2em; 54 | min-height: 1.2em; 55 | 56 | &:hover { 57 | background-color: transparentize($red, 0.2); 58 | } 59 | 60 | &:active { 61 | background-image: linear-gradient($red, $red); 62 | } 63 | } 64 | } 65 | 66 | window.notifications { 67 | @include unset; 68 | 69 | .notification { 70 | @include notification; 71 | 72 | > box { 73 | @include floating-widget; 74 | border-radius: $radii; 75 | } 76 | 77 | .description { 78 | min-width: 350px; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/osd.scss: -------------------------------------------------------------------------------- 1 | window.indicator .progress { 2 | @include floating-widget; 3 | padding: $padding / 2; 4 | border-radius: if($radii == 0, 0, $radii + $padding / 2); 5 | 6 | .fill { 7 | border-radius: $radii; 8 | background-color: $accent; 9 | color: $accent-fg; 10 | 11 | image { 12 | -gtk-icon-transform: scale(0.7); 13 | } 14 | 15 | .font-icon { 16 | font-size: 34px; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/overview.scss: -------------------------------------------------------------------------------- 1 | window#overview .window-content { 2 | @include floating-widget; 3 | @include spacing; 4 | 5 | .workspace { 6 | &.active > widget { 7 | border-color: $accent; 8 | } 9 | 10 | > widget { 11 | @include widget; 12 | border-radius: if($radii == 0, 0, $radii + $padding); 13 | 14 | &:drop(active) { 15 | border-color: $accent; 16 | } 17 | } 18 | } 19 | 20 | .client { 21 | @include button; 22 | border-radius: $radii; 23 | margin: $padding; 24 | 25 | &.hidden { 26 | @include hidden; 27 | transition: 0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/powermenu.scss: -------------------------------------------------------------------------------- 1 | window#powermenu, 2 | window#verification { 3 | .shader { 4 | background-color: rgba(0, 0, 0, 0.05); 5 | } 6 | } 7 | 8 | window#verification .window-content { 9 | @include floating-widget; 10 | min-width: 300px; 11 | min-height: 100px; 12 | 13 | .text-box { 14 | .title { 15 | font-size: 1.6em; 16 | } 17 | 18 | .desc { 19 | color: transparentize($fg-color, 0.1); 20 | font-size: 1.1em; 21 | } 22 | } 23 | 24 | .buttons { 25 | @include spacing; 26 | margin-top: $padding; 27 | 28 | button { 29 | @include button; 30 | font-size: 1.5em; 31 | padding: $padding; 32 | } 33 | } 34 | } 35 | 36 | window#powermenu .window-content { 37 | @include floating-widget; 38 | @include spacing(2); 39 | padding: $popover-padding + $spacing * 1.5; 40 | border-radius: if( 41 | $radii == 0, 42 | 0, 43 | $popover-radius + ($popover-padding + $spacing * 1.5) 44 | ); 45 | 46 | button { 47 | @include unset; 48 | 49 | image { 50 | @include button; 51 | border-radius: $popover-radius; 52 | min-width: 1.7em; 53 | min-height: 1.7em; 54 | font-size: 4em; 55 | } 56 | 57 | label, 58 | image { 59 | color: transparentize($fg-color, 0.1); 60 | } 61 | 62 | &:hover { 63 | image { 64 | @include button-hover; 65 | } 66 | label { 67 | color: $fg-color; 68 | } 69 | } 70 | &:focus image { 71 | @include button-focus; 72 | } 73 | &:active image { 74 | @include button-active; 75 | } 76 | 77 | &:focus, 78 | &:active { 79 | label { 80 | color: $accent; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/quicksettings.scss: -------------------------------------------------------------------------------- 1 | window#quicksettings .window-content { 2 | @include floating-widget; 3 | @include spacing; 4 | 5 | min-width: 30em; 6 | 7 | .avatar { 8 | @include widget; 9 | padding: 1.5em; 10 | // margin: 1em; 11 | // opacity: 0.9; 12 | } 13 | 14 | .header { 15 | @include spacing($rec: true); 16 | 17 | button, 18 | .uptime, 19 | .battery { 20 | @include button; 21 | padding: $padding / 1.5; 22 | font-weight: bold; 23 | min-height: 20px; 24 | min-width: 25px; 25 | 26 | image { 27 | font-size: 1.2em; 28 | } 29 | } 30 | 31 | .battery { 32 | @include spacing($multiplier: 0.5); 33 | } 34 | } 35 | 36 | .battery-progress { 37 | label { 38 | color: $accent-fg; 39 | font-weight: bold; 40 | } 41 | 42 | &.charging label { 43 | font-size: $padding * 2; 44 | } 45 | 46 | &.half label { 47 | color: $fg-color; 48 | } 49 | 50 | progressbar { 51 | @include slider($width: $padding * 3.6); 52 | } 53 | 54 | &.low progressbar { 55 | @include slider( 56 | $width: $padding * 3.6, 57 | $gradient: linear-gradient(to right, $red, $red) 58 | ); 59 | } 60 | } 61 | 62 | .sliders-box { 63 | // @include widget; 64 | @include spacing($rec: true); 65 | @include spacing(0); 66 | // padding: $padding; 67 | 68 | button { 69 | @include button($flat: true); 70 | padding: $padding / 2; 71 | // padding-left: $padding / 4; 72 | } 73 | 74 | scale { 75 | @include slider; 76 | // margin-left: $spacing * -0.5; 77 | padding-right: $padding / 2; 78 | } 79 | 80 | .menu { 81 | margin: $spacing 0; 82 | background-color: $bg-color; 83 | border: $border-width solid $popover-border-color; 84 | border-radius: $radii; 85 | } 86 | } 87 | 88 | .mixer-item { 89 | scale { 90 | @include slider($width: 7px); 91 | } 92 | image { 93 | font-size: 1.2em; 94 | } 95 | } 96 | 97 | .row { 98 | @include spacing($rec: true); 99 | } 100 | 101 | .menu { 102 | @include unset; 103 | @include widget; 104 | @include spacing($rec: true); 105 | padding: $padding; 106 | margin-top: $spacing; 107 | 108 | .title { 109 | @include spacing(0.5); 110 | } 111 | 112 | separator { 113 | margin: 0 $radii / 2; 114 | } 115 | 116 | button { 117 | @include button($flat: true); 118 | padding: $padding / 2; 119 | } 120 | 121 | switch { 122 | @include switch; 123 | } 124 | } 125 | 126 | .toggle-button { 127 | @include button; 128 | font-weight: 600; 129 | 130 | .label-box { 131 | @include spacing(0.5); 132 | } 133 | 134 | button { 135 | @include button($flat: true); 136 | padding: $padding; 137 | 138 | &:first-child { 139 | border-top-right-radius: 0; 140 | border-bottom-right-radius: 0; 141 | } 142 | 143 | &:last-child { 144 | border-top-left-radius: 0; 145 | border-bottom-left-radius: 0; 146 | border-left: 1px solid $bg-color; 147 | background-color: lighten($bg-color, 10); 148 | 149 | &:hover { 150 | background-color: lighten($bg-color, 15); 151 | } 152 | } 153 | } 154 | 155 | &.active { 156 | background-color: $accent; 157 | 158 | label, 159 | image { 160 | color: $accent-fg; 161 | } 162 | 163 | button { 164 | &:last-child { 165 | background-color: lighten($accent, 5); 166 | 167 | &:hover { 168 | background-color: lighten($accent, 10); 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | .simple-toggle { 176 | @include button; 177 | padding: $padding $padding * 1.1; 178 | } 179 | 180 | .media { 181 | @include spacing; 182 | 183 | .player { 184 | @include media; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /home/ags/scss/widgets/settings.scss: -------------------------------------------------------------------------------- 1 | window#settings-dialog { 2 | background-color: $bg-color; 3 | 4 | .page { 5 | @include scrollable; 6 | } 7 | 8 | .page-content { 9 | margin: $spacing; 10 | } 11 | 12 | .sidebar-box { 13 | @include spacing($rec: true); 14 | background-color: $widget-bg; 15 | border-right: $border; 16 | padding: $spacing / 2; 17 | 18 | button { 19 | @include button($flat: true); 20 | padding: $padding / 2 $padding * 2; 21 | } 22 | 23 | scrolledwindow { 24 | @include scrollable; 25 | } 26 | } 27 | 28 | .sidebar-header { 29 | background-color: $widget-bg; 30 | border-right: $border; 31 | border-bottom: $border; 32 | padding: $spacing / 2; 33 | 34 | button { 35 | @include button($flat: true); 36 | padding: $padding / 2 $padding; 37 | } 38 | 39 | button:last-child { 40 | margin-left: $spacing / 2; 41 | } 42 | } 43 | 44 | .sidebar-footer { 45 | background-color: $widget-bg; 46 | border-right: $border; 47 | border-top: $border; 48 | padding: $spacing / 2; 49 | 50 | button { 51 | @include button($flat: true); 52 | padding: $padding / 2 $padding; 53 | } 54 | } 55 | 56 | entry.search { 57 | @include button; 58 | border-radius: 0; 59 | padding: $padding; 60 | } 61 | 62 | .row { 63 | @include widget; 64 | border-radius: 0; 65 | border-bottom-width: 0; 66 | padding: $padding; 67 | transition: border-radius 0; 68 | 69 | &:last-child { 70 | border-radius: 0 0 $radii $radii; 71 | border-bottom-width: $border-width; 72 | } 73 | 74 | &:first-child { 75 | border-radius: $radii $radii 0 0; 76 | } 77 | 78 | &:first-child:last-child { 79 | border-radius: $radii; 80 | } 81 | 82 | .overlay-padding { 83 | min-height: $font-size * 3; 84 | } 85 | 86 | &:hover { 87 | background-color: $hover; 88 | } 89 | 90 | entry, 91 | button { 92 | @include button; 93 | padding: $padding; 94 | } 95 | 96 | switch { 97 | @include switch; 98 | } 99 | 100 | spinbutton { 101 | @include unset; 102 | 103 | entry { 104 | border-radius: $radii 0 0 $radii; 105 | } 106 | 107 | button { 108 | border-radius: 0; 109 | } 110 | 111 | button:last-child { 112 | border-radius: 0 $radii $radii 0; 113 | } 114 | } 115 | 116 | .enum-setter { 117 | label { 118 | background-color: $widget-bg; 119 | border-top: $border; 120 | border-bottom: $border; 121 | padding: 0 $padding; 122 | } 123 | 124 | button:first-child { 125 | border-radius: $radii 0 0 $radii; 126 | } 127 | 128 | button:last-child { 129 | border-radius: 0 $radii $radii 0; 130 | } 131 | } 132 | } 133 | 134 | .id, 135 | .note { 136 | font-size: 0.8em; 137 | color: transparentize($fg-color, $amount: 0.5); 138 | } 139 | 140 | .id { 141 | font-family: $mono-font; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /home/ags/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | dir="${1:-$HOME/.config/ags}" 4 | 5 | mkdir -p /tmp/ags-config 6 | cd /tmp/ags-config 7 | 8 | # clone 9 | echo "cloning ags" 10 | git clone https://github.com/Aylur/ags.git 11 | cd ags 12 | npm i 13 | 14 | # generate 15 | echo "generating types..." 16 | tsc -d --declarationDir dts --emitDeclarationOnly 17 | 18 | # fix paths 19 | find ./dts -type f | xargs sed -i 's/gi:\/\/Gtk?version=3.0/node_modules\/@girs\/gtk-3.0\/gtk-3.0/g' 20 | find ./dts -type f | xargs sed -i 's/gi:\/\/GObject/node_modules\/@girs\/gobject-2.0\/gobject-2.0/g' 21 | find ./dts -type f | xargs sed -i 's/gi:\/\/Gio/node_modules\/@girs\/gio-2.0\/gio-2.0/g' 22 | find ./dts -type f | xargs sed -i 's/gi:\/\/GLib/node_modules\/@girs\/glib-2.0\/glib-2.0/g' 23 | find ./dts -type f | xargs sed -i 's/gi:\/\/GdkPixbuf/node_modules\/@girs\/gdkpixbuf-2.0\/gdkpixbuf-2.0/g' 24 | find ./dts -type f | xargs sed -i 's/gi:\/\/Gdk/node_modules\/@girs\/gdk-2.0\/gdk-2.0/g' 25 | find ./dts -type f | xargs sed -i 's/gi:\/\/Gvc/node_modules\/@girs\/gvc-1.0\/gvc-1.0/g' 26 | find ./dts -type f | xargs sed -i 's/gi:\/\/NM/node_modules\/@girs\/nm-1.0\/nm-1.0/g' 27 | find ./dts -type f | xargs sed -i 's/gi:\/\/DbusmenuGtk3/node_modules\/@girs\/dbusmenugtk3-0.4\/dbusmenugtk3-0.4/g' 28 | 29 | # move 30 | mv dts $dir/.types 31 | echo "types moved to $dir/.types" 32 | 33 | # gen ags.d.ts 34 | function mod { 35 | echo "declare module '$1' { 36 | const exports: typeof import('$2') 37 | export = exports 38 | }" 39 | } 40 | 41 | function resource { 42 | mod "resource:///com/github/Aylur/ags/$1.js" "./$1" 43 | } 44 | 45 | function gi { 46 | mod "gi://$1" "node_modules/@girs/$2/$2" 47 | } 48 | 49 | dts="$dir/types/ags.d.ts" 50 | 51 | echo " 52 | declare function print(...args: any[]): void; 53 | 54 | declare module console { 55 | export function error(obj: object, others?: object[]): void; 56 | export function error(msg: string, subsitutions?: any[]): void; 57 | export function log(obj: object, others?: object[]): void; 58 | export function log(msg: string, subsitutions?: any[]): void; 59 | export function warn(obj: object, others?: object[]): void; 60 | export function warn(msg: string, subsitutions?: any[]): void; 61 | } 62 | " >$dts 63 | 64 | for file in ./src/*.ts; do 65 | f=$(basename -s .ts $file) 66 | if [[ "$f" != "main" && "$f" != "client" ]]; then 67 | resource "$(basename -s .ts $file)" >>$dts 68 | fi 69 | done 70 | 71 | for file in ./src/service/*.ts; do 72 | resource "service/$(basename -s .ts $file)" >>$dts 73 | done 74 | 75 | for file in ./src/widgets/*.ts; do 76 | resource "widgets/$(basename -s .ts $file)" >>$dts 77 | done 78 | 79 | gi "Gtk" "gtk-3.0" >>$dts 80 | gi "GObject" "gobject-2.0" >>$dts 81 | gi "Gio" "gio-2.0" >>$dts 82 | gi "GLib" "glib-2.0" >>$dts 83 | 84 | # remove tmp 85 | rm -rf /tmp/ags-config 86 | 87 | # npm i 88 | echo "npm install @girs" 89 | cd $dir 90 | npm i 91 | -------------------------------------------------------------------------------- /home/ags/src/applauncher/AppItem.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import App from "resource:///com/github/Aylur/ags/app.js"; 3 | import options from "../options.js"; 4 | import { lookUpIcon } from "resource:///com/github/Aylur/ags/utils.js"; 5 | 6 | /** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */ 7 | export default (app) => { 8 | const title = Widget.Label({ 9 | class_name: "title", 10 | label: app.name, 11 | xalign: 0, 12 | vpack: "center", 13 | truncate: "end", 14 | }); 15 | 16 | const description = Widget.Label({ 17 | class_name: "description", 18 | label: app.description || "", 19 | wrap: true, 20 | xalign: 0, 21 | justification: "left", 22 | vpack: "center", 23 | }); 24 | 25 | const icon = Widget.Icon({ 26 | icon: lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "", 27 | binds: [["size", options.applauncher.icon_size]], 28 | }); 29 | 30 | const textBox = Widget.Box({ 31 | vertical: true, 32 | vpack: "center", 33 | children: app.description ? [title, description] : [title], 34 | }); 35 | 36 | return Widget.Button({ 37 | class_name: "app-item", 38 | setup: (self) => (self.app = app), 39 | on_clicked: () => { 40 | App.closeWindow("applauncher"); 41 | app.launch(); 42 | }, 43 | child: Widget.Box({ 44 | children: [icon, textBox], 45 | }), 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /home/ags/src/applauncher/Applauncher.js: -------------------------------------------------------------------------------- 1 | import Widget from 'resource:///com/github/Aylur/ags/widget.js'; 2 | import App from 'resource:///com/github/Aylur/ags/app.js'; 3 | import Applications from 'resource:///com/github/Aylur/ags/service/applications.js'; 4 | import PopupWindow from '../misc/PopupWindow.js'; 5 | import AppItem from './AppItem.js'; 6 | import icons from '../icons.js'; 7 | import { launchApp } from '../utils.js'; 8 | import options from '../options.js'; 9 | 10 | const WINDOW_NAME = 'applauncher'; 11 | 12 | const Applauncher = () => { 13 | const mkItems = () => [ 14 | Widget.Separator({ hexpand: true }), 15 | ...Applications.query('').flatMap(app => Widget.Revealer({ 16 | setup: w => w.attribute = { app, revealer: w }, 17 | child: Widget.Box({ 18 | vertical: true, 19 | children: [ 20 | Widget.Separator({ hexpand: true }), 21 | AppItem(app), 22 | Widget.Separator({ hexpand: true }), 23 | ], 24 | }), 25 | })), 26 | Widget.Separator({ hexpand: true }), 27 | ]; 28 | 29 | let items = mkItems(); 30 | 31 | const list = Widget.Box({ 32 | class_name: 'app-list', 33 | vertical: true, 34 | children: items, 35 | }); 36 | 37 | const entry = Widget.Entry({ 38 | hexpand: true, 39 | primary_icon_name: icons.apps.search, 40 | 41 | // set some text so on-change works the first time 42 | text: '-', 43 | on_accept: ({ text }) => { 44 | const list = Applications.query(text || ''); 45 | if (list[0]) { 46 | App.toggleWindow(WINDOW_NAME); 47 | launchApp(list[0]); 48 | } 49 | }, 50 | on_change: ({ text }) => items.map(item => { 51 | if (item.attribute) { 52 | const { app, revealer } = item.attribute; 53 | revealer.reveal_child = app.match(text); 54 | } 55 | }), 56 | }); 57 | 58 | return Widget.Box({ 59 | vertical: true, 60 | children: [ 61 | entry, 62 | Widget.Scrollable({ 63 | hscroll: 'never', 64 | child: list, 65 | }), 66 | ], 67 | setup: self => self.hook(App, (_, win, visible) => { 68 | if (win !== WINDOW_NAME) 69 | return; 70 | 71 | entry.text = '-'; 72 | entry.text = ''; 73 | if (visible) { 74 | entry.grab_focus(); 75 | } 76 | else { 77 | items = mkItems(); 78 | list.children = items; 79 | } 80 | }), 81 | }); 82 | }; 83 | 84 | export default () => PopupWindow({ 85 | name: WINDOW_NAME, 86 | transition: 'slide_down', 87 | child: Applauncher(), 88 | anchor: ['center', 'center'], 89 | }); 90 | -------------------------------------------------------------------------------- /home/ags/src/bar/PanelButton.js: -------------------------------------------------------------------------------- 1 | import Widget from 'resource:///com/github/Aylur/ags/widget.js'; 2 | import App from 'resource:///com/github/Aylur/ags/app.js'; 3 | 4 | /** 5 | * @typedef {Object} PanelButtonProps 6 | * @property {import('types/widgets/button').ButtonProps['child']} content 7 | * @property {string=} window 8 | */ 9 | 10 | /** 11 | * @param {import('types/widgets/button').ButtonProps & PanelButtonProps} o 12 | */ 13 | export default ({ 14 | class_name, 15 | content, 16 | window = '', 17 | setup, 18 | ...rest 19 | }) => Widget.Button({ 20 | class_name: `panel-button ${class_name}`, 21 | child: Widget.Box({ children: [content] }), 22 | setup: self => { 23 | let open = false; 24 | 25 | self.hook(App, (_, win, visible) => { 26 | if (win !== window) 27 | return; 28 | 29 | if (open && !visible) { 30 | open = false; 31 | self.toggleClassName('active', false); 32 | } 33 | 34 | if (visible) { 35 | open = true; 36 | self.toggleClassName('active'); 37 | } 38 | }); 39 | 40 | if (setup) 41 | setup(self); 42 | }, 43 | ...rest, 44 | }); 45 | -------------------------------------------------------------------------------- /home/ags/src/bar/TopBar.js: -------------------------------------------------------------------------------- 1 | import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import Variable from "resource:///com/github/Aylur/ags/variable.js"; 4 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 5 | import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; 6 | import OverviewButton from "./buttons/OverviewButton.js"; 7 | import MediaIndicator from "./buttons/MediaIndicator.js"; 8 | import DateButton from "./buttons/DateButton.js"; 9 | import NotificationIndicator from "./buttons/NotificationIndicator.js"; 10 | import SysTray from "./buttons/SysTray.js"; 11 | import ColorPicker from "./buttons/ColorPicker.js"; 12 | import SystemIndicators from "./buttons/SystemIndicators.js"; 13 | import ScreenRecord from "./buttons/ScreenRecord.js"; 14 | import SubMenu from "./buttons/SubMenu.js"; 15 | import Recorder from "../services/screenrecord.js"; 16 | import options from "../options.js"; 17 | 18 | const submenuItems = Variable(1); 19 | SystemTray.connect("changed", () => { 20 | submenuItems.setValue(SystemTray.items.length + 1); 21 | }); 22 | 23 | /** 24 | * @template T 25 | * @param {T=} service 26 | * @param {(self: T) => boolean=} condition 27 | */ 28 | const SeparatorDot = (service, condition) => { 29 | const visibility = (self) => { 30 | if (!options.bar.separators.value) return (self.visible = false); 31 | 32 | self.visible = condition && service 33 | ? condition(service) 34 | : options.bar.separators.value; 35 | }; 36 | 37 | const conn = service ? [[service, visibility]] : []; 38 | return Widget.Separator({ 39 | connections: [["draw", visibility], ...conn], 40 | binds: [["visible", options.bar.separators]], 41 | vpack: "center", 42 | }); 43 | }; 44 | 45 | const OptionalWorkspaces = async () => { 46 | try { 47 | return (await import('./buttons/Workspaces.js')).default(); 48 | } catch { 49 | return (await import('./buttons/SwayWorkspaces.js')).default(); 50 | } 51 | }; 52 | 53 | const Start = () => 54 | Widget.Box({ 55 | class_name: "start", 56 | setup: async (box) => { 57 | box.children = [ 58 | await OptionalWorkspaces(), 59 | SeparatorDot(), 60 | Widget.Box({ hexpand: true }), 61 | NotificationIndicator(), 62 | SeparatorDot(Notifications, (n) => n.notifications.length > 0 || n.dnd), 63 | ]; 64 | }, 65 | }); 66 | 67 | const Center = () => 68 | Widget.Box({ 69 | class_name: "center", 70 | children: [DateButton()], 71 | }); 72 | 73 | const End = () => 74 | Widget.Box({ 75 | class_name: "end", 76 | children: [ 77 | SeparatorDot(Mpris, (m) => m.players.length > 0), 78 | MediaIndicator(), 79 | Widget.Box({ hexpand: true }), 80 | 81 | SubMenu({ 82 | items: submenuItems, 83 | children: [SysTray(), ColorPicker()], 84 | }), 85 | 86 | SeparatorDot(), 87 | ScreenRecord(), 88 | SeparatorDot(Recorder, (r) => r.recording), 89 | SystemIndicators(), 90 | ], 91 | }); 92 | 93 | /** @param {number} monitor */ 94 | export default (monitor) => 95 | Widget.Window({ 96 | name: `bar${monitor}`, 97 | class_name: "transparent", 98 | exclusivity: "exclusive", 99 | monitor, 100 | binds: [ 101 | [ 102 | "anchor", 103 | options.bar.position, 104 | "value", 105 | (pos) => [pos, "right"], 106 | ], 107 | ], 108 | child: Widget.Box({ 109 | class_name: "panel", 110 | children: [ 111 | Start(), 112 | Center(), 113 | End(), 114 | ] 115 | }), 116 | }); 117 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/BatteryBar.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; 3 | import icons from "../../icons.js"; 4 | import FontIcon from "../../misc/FontIcon.js"; 5 | import options from "../../options.js"; 6 | import PanelButton from "../PanelButton.js"; 7 | 8 | const Indicator = () => 9 | Widget.Stack({ 10 | items: [ 11 | ["false", Widget.Icon({ binds: [["icon", Battery, "icon-name"]] })], 12 | ["true", FontIcon(icons.battery.charging)], 13 | ], 14 | connections: [ 15 | [ 16 | Battery, 17 | (stack) => { 18 | stack.shown = `${Battery.charging || Battery.charged}`; 19 | }, 20 | ], 21 | ], 22 | }); 23 | 24 | const PercentLabel = () => 25 | Widget.Revealer({ 26 | transition: "slide_right", 27 | binds: [["reveal-child", options.battery.show_percentage]], 28 | child: Widget.Label({ 29 | binds: [["label", Battery, "percent", (p) => `${p}%`]], 30 | }), 31 | }); 32 | 33 | const LevelBar = () => 34 | Widget.LevelBar({ 35 | connections: [ 36 | [ 37 | options.battery.bar.full, 38 | (self) => { 39 | const full = options.battery.bar.full.value; 40 | self.vpack = full ? "fill" : "center"; 41 | self.hpack = full ? "fill" : "center"; 42 | }, 43 | ], 44 | ], 45 | binds: [["value", Battery, "percent", (p) => p / 100]], 46 | }); 47 | 48 | const WholeButton = () => 49 | Widget.Overlay({ 50 | class_name: "whole-button", 51 | child: LevelBar(), 52 | pass_through: true, 53 | overlays: [ 54 | Widget.Box({ 55 | hpack: "center", 56 | children: [ 57 | FontIcon({ 58 | icon: icons.battery.charging, 59 | binds: [["visible", Battery, "charging"]], 60 | }), 61 | Widget.Box({ 62 | hpack: "center", 63 | vpack: "center", 64 | child: PercentLabel(), 65 | }), 66 | ], 67 | }), 68 | ], 69 | }); 70 | 71 | export default () => 72 | PanelButton({ 73 | class_name: "battery-bar", 74 | on_clicked: () => { 75 | const v = options.battery.show_percentage.value; 76 | options.battery.show_percentage.value = !v; 77 | }, 78 | content: Widget.Box({ 79 | connections: [ 80 | [ 81 | Battery, 82 | (w) => { 83 | w.toggleClassName("charging", Battery.charging || Battery.charged); 84 | w.toggleClassName( 85 | "medium", 86 | Battery.percent < options.battery.medium.value, 87 | ); 88 | w.toggleClassName( 89 | "low", 90 | Battery.percent < options.battery.low.value, 91 | ); 92 | w.toggleClassName("half", Battery.percent < 48); 93 | }, 94 | ], 95 | ], 96 | binds: [ 97 | ["visible", Battery, "available"], 98 | [ 99 | "children", 100 | options.battery.bar.full, 101 | "value", 102 | (full) => 103 | full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()], 104 | ], 105 | ], 106 | }), 107 | }); 108 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/ColorPicker.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Colors from "../../services/colorpicker.js"; 3 | import PanelButton from "../PanelButton.js"; 4 | import Gdk from "gi://Gdk"; 5 | 6 | export default () => 7 | PanelButton({ 8 | class_name: "color-picker", 9 | content: Widget.Icon("color-select-symbolic"), 10 | binds: [["tooltip-text", Colors, "colors", (v) => `${v.length} colors`]], 11 | on_clicked: () => Colors.pick(), 12 | 13 | on_secondary_click: (btn) => { 14 | if (Colors.colors.length === 0) return; 15 | 16 | Widget.Menu({ 17 | class_name: "colorpicker", 18 | children: Colors.colors.map((color) => 19 | Widget.MenuItem({ 20 | child: Widget.Label(color), 21 | css: `background-color: ${color}`, 22 | on_activate: () => Colors.wlCopy(color), 23 | }) 24 | ), 25 | }).popup_at_widget(btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null); 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/DateButton.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import Clock from "../../misc/Clock.js"; 3 | import PanelButton from "../PanelButton.js"; 4 | 5 | export default ({ format = "%H:%M" } = {}) => 6 | PanelButton({ 7 | class_name: "dashboard panel-button", 8 | on_clicked: () => App.toggleWindow("dashboard"), 9 | window: "dashboard", 10 | content: Clock({ format }), 11 | }); 12 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/FocusedClient.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import PanelButton from "../PanelButton.js"; 5 | import options from "../../options.js"; 6 | import { substitute } from "../../utils.js"; 7 | 8 | export const ClientLabel = () => 9 | Widget.Label({ 10 | binds: [ 11 | [ 12 | "label", 13 | Hyprland.active.client, 14 | "class", 15 | (c) => { 16 | const { titles } = options.substitutions; 17 | return substitute(titles, c); 18 | }, 19 | ], 20 | ], 21 | }); 22 | 23 | export const ClientIcon = () => 24 | Widget.Icon({ 25 | connections: [ 26 | [ 27 | Hyprland.active.client, 28 | (self) => { 29 | const { icons } = options.substitutions; 30 | const { client } = Hyprland.active; 31 | 32 | const classIcon = substitute(icons, client.class) + "-symbolic"; 33 | const titleIcon = substitute(icons, client.class) + "-symbolic"; 34 | 35 | const hasTitleIcon = Utils.lookUpIcon(titleIcon); 36 | const hasClassIcon = Utils.lookUpIcon(classIcon); 37 | 38 | if (hasClassIcon) self.icon = classIcon; 39 | 40 | if (hasTitleIcon) self.icon = titleIcon; 41 | 42 | self.visible = !!(hasTitleIcon || hasClassIcon); 43 | }, 44 | ], 45 | ], 46 | }); 47 | 48 | export default () => 49 | PanelButton({ 50 | class_name: "focused-client", 51 | content: Widget.Box({ 52 | children: [ClientIcon(), ClientLabel()], 53 | binds: [["tooltip-text", Hyprland.active, "client", (c) => c.title]], 54 | }), 55 | }); 56 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/MediaIndicator.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import HoverRevealer from "../../misc/HoverRevealer.js"; 5 | import * as mpris from "../../misc/mpris.js"; 6 | import options from "../../options.js"; 7 | 8 | export const getPlayer = (name = options.mpris.preferred.value) => 9 | Mpris.getPlayer(name) || Mpris.players[0] || null; 10 | 11 | /** 12 | * @param {Object} o 13 | * @param {import('types/service/mpris').MprisPlayer} o.player 14 | * @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction 15 | */ 16 | const Indicator = ({ player, direction = "right" }) => 17 | HoverRevealer({ 18 | class_name: `media panel-button ${player.name}`, 19 | direction, 20 | on_primary_click: () => player.playPause(), 21 | on_scroll_up: () => player.next(), 22 | on_scroll_down: () => player.previous(), 23 | on_secondary_click: () => player.playPause(), 24 | indicator: mpris.PlayerIcon(player), 25 | child: Widget.Label({ 26 | vexpand: true, 27 | truncate: "end", 28 | max_width_chars: 40, 29 | connections: [ 30 | [ 31 | player, 32 | (label) => { 33 | label.label = `${ 34 | player.track_artists.join(", ") 35 | } - ${player.track_title}`; 36 | }, 37 | ], 38 | ], 39 | }), 40 | connections: [ 41 | [ 42 | player, 43 | (revealer) => { 44 | if (revealer._current === player.track_title) return; 45 | 46 | revealer._current = player.track_title; 47 | revealer.reveal_child = true; 48 | Utils.timeout(3000, () => { 49 | revealer.reveal_child = false; 50 | }); 51 | }, 52 | ], 53 | ], 54 | }); 55 | 56 | /** 57 | * @param {Object} o 58 | * @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction 59 | */ 60 | export default ({ direction = "right" } = {}) => { 61 | let current = null; 62 | 63 | const update = (box) => { 64 | const player = getPlayer(); 65 | box.visible = !!player; 66 | 67 | if (!player) { 68 | current = null; 69 | return; 70 | } 71 | 72 | if (current === player) return; 73 | 74 | current = player; 75 | box.children = [Indicator({ player, direction })]; 76 | }; 77 | 78 | return Widget.Box({ 79 | connections: [ 80 | [options.mpris.preferred, update], 81 | [Mpris, update, "notify::players"], 82 | ], 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/NotificationIndicator.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 4 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 5 | import icons from "../../icons.js"; 6 | import HoverRevealer from "../../misc/HoverRevealer.js"; 7 | 8 | /** 9 | * @param {Object} o 10 | * @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction 11 | */ 12 | export default ({ direction = "left" } = {}) => 13 | HoverRevealer({ 14 | class_name: "notifications panel-button", 15 | eventboxConnections: [ 16 | ["button-press-event", () => App.openWindow("dashboard")], 17 | [ 18 | Notifications, 19 | ( 20 | box, 21 | ) => (box.visible = Notifications.notifications.length > 0 || 22 | Notifications.dnd), 23 | ], 24 | ], 25 | connections: [ 26 | [ 27 | Notifications, 28 | (revealer) => { 29 | const title = Notifications.notifications[0]?.summary; 30 | if (revealer._title === title) return; 31 | 32 | revealer._title = title; 33 | revealer.reveal_child = true; 34 | Utils.timeout(3000, () => { 35 | revealer.reveal_child = false; 36 | }); 37 | }, 38 | ], 39 | ], 40 | direction, 41 | indicator: Widget.Icon({ 42 | binds: [ 43 | [ 44 | "icon", 45 | Notifications, 46 | "dnd", 47 | (dnd) => dnd ? icons.notifications.silent : icons.notifications.noisy, 48 | ], 49 | ], 50 | }), 51 | child: Widget.Label({ 52 | truncate: "end", 53 | max_width_chars: 40, 54 | binds: [ 55 | [ 56 | "label", 57 | Notifications, 58 | "notifications", 59 | (n) => n.reverse()[0]?.summary || "", 60 | ], 61 | ], 62 | }), 63 | }); 64 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/OverviewButton.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import PanelButton from "../PanelButton.js"; 3 | import FontIcon from "../../misc/FontIcon.js"; 4 | import { distroIcon } from "../../variables.js"; 5 | import options from "../../options.js"; 6 | 7 | export default () => 8 | PanelButton({ 9 | class_name: "overview", 10 | window: "overview", 11 | on_clicked: () => App.toggleWindow("applauncher"), 12 | content: FontIcon({ 13 | binds: [ 14 | [ 15 | "icon", 16 | options.bar.icon, 17 | "value", 18 | (v) => { 19 | return v === "distro-icon" ? distroIcon : v; 20 | }, 21 | ], 22 | ], 23 | }), 24 | }); 25 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/PowerMenu.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import App from "resource:///com/github/Aylur/ags/app.js"; 3 | import icons from "../../icons.js"; 4 | import PanelButton from "../PanelButton.js"; 5 | 6 | export default () => 7 | PanelButton({ 8 | class_name: "powermenu", 9 | content: Widget.Icon(icons.powermenu.shutdown), 10 | on_clicked: () => App.openWindow("powermenu"), 11 | }); 12 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/ScreenRecord.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import PanelButton from "../PanelButton.js"; 3 | import Recorder from "../../services/screenrecord.js"; 4 | import icons from "../../icons.js"; 5 | 6 | export default () => 7 | PanelButton({ 8 | class_name: "recorder", 9 | on_clicked: () => Recorder.stop(), 10 | binds: [["visible", Recorder, "recording"]], 11 | content: Widget.Box({ 12 | children: [ 13 | Widget.Icon(icons.recorder.recording), 14 | Widget.Label({ 15 | binds: [ 16 | [ 17 | "label", 18 | Recorder, 19 | "timer", 20 | (time) => { 21 | const sec = time % 60; 22 | const min = Math.floor(time / 60); 23 | return `${min}:${sec < 10 ? "0" + sec : sec}`; 24 | }, 25 | ], 26 | ], 27 | }), 28 | ], 29 | }), 30 | }); 31 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/SubMenu.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | import Variable from "resource:///com/github/Aylur/ags/variable.js"; 4 | import icons from "../../icons.js"; 5 | import options from "../../options.js"; 6 | 7 | /** 8 | * @param {import('types/widgets/revealer').default} revealer 9 | * @param {'left' | 'right' | 'up' | 'down'} direction 10 | * @param {import('types/variable').Variable} items 11 | */ 12 | const Arrow = (revealer, direction, items) => { 13 | let deg = 0; 14 | 15 | const icon = Widget.Icon({ 16 | icon: icons.ui.trayarrow[direction], 17 | }); 18 | 19 | const animate = () => { 20 | const t = options.transition.value / 20; 21 | const step = revealer.reveal_child ? 10 : -10; 22 | for (let i = 0; i < 18; ++i) { 23 | Utils.timeout(t * i, () => { 24 | deg += step; 25 | icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`); 26 | }); 27 | } 28 | }; 29 | 30 | return Widget.Button({ 31 | class_name: "panel-button sub-menu", 32 | connections: [ 33 | [ 34 | items, 35 | (btn) => { 36 | btn.tooltip_text = `${items.value} Items`; 37 | }, 38 | ], 39 | ], 40 | on_clicked: () => { 41 | animate(); 42 | revealer.reveal_child = !revealer.reveal_child; 43 | }, 44 | child: icon, 45 | }); 46 | }; 47 | 48 | /** 49 | * @param {Object} o 50 | * @param {import('types/widgets/box').default['children']} o.children 51 | * @param {'left' | 'right' | 'up' | 'down'=} o.direction 52 | * @param {import('types/variable').Variable} o.items 53 | */ 54 | export default ({ children, direction = "left", items = Variable(0) }) => { 55 | const posStart = direction === "up" || direction === "left"; 56 | const posEnd = direction === "down" || direction === "right"; 57 | const revealer = Widget.Revealer({ 58 | transition: `slide_${direction}`, 59 | child: Widget.Box({ 60 | children, 61 | }), 62 | }); 63 | 64 | return Widget.Box({ 65 | vertical: direction === "up" || direction === "down", 66 | children: [ 67 | posStart && revealer, 68 | Arrow(revealer, direction, items), 69 | posEnd && revealer, 70 | ], 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/SwayWorkspaces.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Sway from "resource:///com/github/Aylur/ags/service/sway.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import options from "../../options.js"; 5 | import { range } from "../../utils.js"; 6 | 7 | 8 | const dispatch = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`); 9 | 10 | const Workspaces = () => { 11 | const ws = options.workspaces.value || 20; 12 | return Widget.Box({ 13 | children: range(ws).map((i) => 14 | Widget.Button({ 15 | setup: (btn) => (btn.id = i), 16 | on_clicked: () => dispatch(i), 17 | child: Widget.Label({ 18 | label: `${i}`, 19 | class_name: "indicator", 20 | vpack: "center", 21 | }), 22 | connections: [ 23 | [ 24 | Sway, 25 | (btn) => { 26 | btn.toggleClassName("active", Sway.active.workspace.name == i); 27 | btn.toggleClassName( 28 | "occupied", 29 | Sway.getWorkspace(`${i}`)?.nodes.length > 0, 30 | ); 31 | }, 32 | ], 33 | ], 34 | }) 35 | ), 36 | connections: [ 37 | [ 38 | Sway.active.workspace, 39 | (box) => 40 | box.children.map((btn) => { 41 | btn.visible = Sway.workspaces.some( 42 | (ws) => ws.name == btn.id, 43 | ); 44 | }), 45 | ], 46 | ], 47 | }); 48 | }; 49 | 50 | export default () => 51 | Widget.EventBox({ 52 | class_name: "workspaces panel-button", 53 | child: Widget.Box({ 54 | // its nested like this to keep it consistent with other PanelButton widgets 55 | child: Widget.EventBox({ 56 | on_scroll_up: () => dispatch("next"), 57 | on_scroll_down: () => dispatch("prev"), 58 | class_name: "eventbox", 59 | binds: [["child", options.workspaces, "value", Workspaces]], 60 | }), 61 | }), 62 | }); 63 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/SysTray.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js"; 3 | import PanelButton from "../PanelButton.js"; 4 | import Gdk from "gi://Gdk"; 5 | 6 | /** @param {import('types/service/systemtray').TrayItem} item */ 7 | const SysTrayItem = (item) => 8 | PanelButton({ 9 | class_name: "tray-item", 10 | content: Widget.Icon({ binds: [["icon", item, "icon"]] }), 11 | binds: [["tooltipMarkup", item, "tooltip-markup"]], 12 | setup: (self) => { 13 | const id = item.menu?.connect("popped-up", (menu) => { 14 | self.toggleClassName("active"); 15 | menu.connect("notify::visible", (menu) => { 16 | self.toggleClassName("active", menu.visible); 17 | }); 18 | menu.disconnect(id); 19 | }); 20 | 21 | if (id) self.connect("destroy", () => item.menu?.disconnect(id)); 22 | }, 23 | 24 | // @ts-expect-error popup_at_widget missing from types? 25 | on_primary_click: (btn) => 26 | item.menu?.popup_at_widget( 27 | btn, 28 | Gdk.Gravity.SOUTH, 29 | Gdk.Gravity.NORTH, 30 | null, 31 | ), 32 | 33 | // @ts-expect-error popup_at_widget missing from types? 34 | on_secondary_click: (btn) => 35 | item.menu?.popup_at_widget( 36 | btn, 37 | Gdk.Gravity.SOUTH, 38 | Gdk.Gravity.NORTH, 39 | null, 40 | ), 41 | }); 42 | 43 | export default () => 44 | Widget.Box({ 45 | binds: [["children", SystemTray, "items", (i) => i.map(SysTrayItem)]], 46 | }); 47 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/System.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import PanelButton from "../PanelButton.js"; 3 | import * as variables from "../../variables.js"; 4 | import icons from "../../icons.js"; 5 | 6 | /** @param {'cpu' | 'ram'} type */ 7 | const System = (type) => { 8 | const icon = Widget.Icon({ 9 | class_name: "icon", 10 | icon: icons.system[type], 11 | }); 12 | 13 | const progress = Widget.Box({ 14 | class_name: "progress", 15 | child: Widget.CircularProgress({ 16 | binds: [["value", variables[type]]], 17 | }), 18 | }); 19 | 20 | const revealer = Widget.Revealer({ 21 | transition: "slide_right", 22 | child: Widget.Label({ 23 | binds: [ 24 | [ 25 | "label", 26 | variables[type], 27 | "value", 28 | (v) => { 29 | return ` ${type}: ${Math.round(v * 100)}%`; 30 | }, 31 | ], 32 | ], 33 | }), 34 | }); 35 | 36 | return PanelButton({ 37 | class_name: `system ${type}`, 38 | on_clicked: () => (revealer.reveal_child = !revealer.reveal_child), 39 | content: Widget.EventBox({ 40 | on_hover: () => (revealer.reveal_child = true), 41 | on_hover_lost: () => (revealer.reveal_child = false), 42 | child: Widget.Box({ 43 | children: [ 44 | icon, 45 | Widget.Box({ 46 | class_name: "revealer", 47 | child: revealer, 48 | }), 49 | progress, 50 | ], 51 | }), 52 | }), 53 | }); 54 | }; 55 | 56 | export const CPU = () => System("cpu"); 57 | export const RAM = () => System("ram"); 58 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/SystemIndicators.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 4 | import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js"; 5 | import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; 6 | import Network from "resource:///com/github/Aylur/ags/service/network.js"; 7 | import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; 8 | import HoverRevealer from "../../misc/HoverRevealer.js"; 9 | import PanelButton from "../PanelButton.js"; 10 | import Indicator from "../../services/onScreenIndicator.js"; 11 | import icons from "../../icons.js"; 12 | import FontIcon from "../../misc/FontIcon.js"; 13 | 14 | const MicrophoneIndicator = () => 15 | Widget.Icon({ 16 | connections: [ 17 | [ 18 | Audio, 19 | (icon) => { 20 | if (!Audio.microphone) return; 21 | 22 | const { muted, low, medium, high } = icons.audio.mic; 23 | if (Audio.microphone.is_muted) return (icon.icon = muted); 24 | 25 | /** @type {Array<[number, string]>} */ 26 | const cons = [ 27 | [67, high], 28 | [34, medium], 29 | [1, low], 30 | [0, muted], 31 | ]; 32 | icon.icon = cons.find(([n]) => 33 | n <= Audio.microphone.volume * 100 34 | )?.[1] || ""; 35 | 36 | icon.visible = Audio.recorders.length > 0 || 37 | Audio.microphone.is_muted; 38 | }, 39 | ], 40 | ], 41 | }); 42 | 43 | const DNDIndicator = () => 44 | Widget.Icon({ 45 | icon: icons.notifications.silent, 46 | binds: [["visible", Notifications, "dnd"]], 47 | }); 48 | 49 | const BluetoothDevicesIndicator = () => 50 | Widget.Box({ 51 | connections: [ 52 | [ 53 | Bluetooth, 54 | (box) => { 55 | box.children = Bluetooth.connectedDevices.map(({ iconName, name }) => 56 | HoverRevealer({ 57 | indicator: Widget.Icon(iconName + "-symbolic"), 58 | child: Widget.Label(name), 59 | }) 60 | ); 61 | 62 | box.visible = Bluetooth.connectedDevices.length > 0; 63 | }, 64 | "notify::connected-devices", 65 | ], 66 | ], 67 | }); 68 | 69 | const BluetoothIndicator = () => 70 | Widget.Icon({ 71 | class_name: "bluetooth", 72 | icon: icons.bluetooth.enabled, 73 | binds: [["visible", Bluetooth, "enabled"]], 74 | }); 75 | 76 | const NetworkIndicator = () => 77 | Widget.Icon({ 78 | connections: [ 79 | [ 80 | Network, 81 | (self) => { 82 | const icon = Network[Network.primary || "wifi"]?.iconName; 83 | self.icon = icon || ""; 84 | self.visible = icon; 85 | }, 86 | ], 87 | ], 88 | }); 89 | 90 | const AudioIndicator = () => 91 | Widget.Icon({ 92 | connections: [ 93 | [ 94 | Audio, 95 | (icon) => { 96 | if (!Audio.speaker) return; 97 | 98 | const { muted, low, medium, high, overamplified } = 99 | icons.audio.volume; 100 | if (Audio.speaker.is_muted) return (icon.icon = muted); 101 | 102 | /** @type {Array<[number, string]>} */ 103 | const cons = [ 104 | [101, overamplified], 105 | [67, high], 106 | [34, medium], 107 | [1, low], 108 | [0, muted], 109 | ]; 110 | icon.icon = cons.find(([n]) => 111 | n <= Audio.speaker.volume * 100 112 | )?.[1] || ""; 113 | }, 114 | "speaker-changed", 115 | ], 116 | ], 117 | }); 118 | 119 | const BatteryIndicator = () => 120 | Widget.Icon({ 121 | class_name: "battery horizontal", 122 | binds: [["icon", Battery, "icon-name"]], 123 | }); 124 | 125 | export default () => 126 | PanelButton({ 127 | class_name: "quicksettings panel-button", 128 | onClicked: () => App.toggleWindow("quicksettings"), 129 | onScrollUp: () => { 130 | Audio.speaker.volume += 0.02; 131 | Indicator.speaker(); 132 | }, 133 | onScrollDown: () => { 134 | Audio.speaker.volume -= 0.02; 135 | Indicator.speaker(); 136 | }, 137 | connections: [ 138 | [ 139 | App, 140 | (btn, win, visible) => { 141 | btn.toggleClassName("active", win === "quicksettings" && visible); 142 | }, 143 | ], 144 | ], 145 | child: Widget.Box({ 146 | children: [ 147 | DNDIndicator(), 148 | BluetoothDevicesIndicator(), 149 | BluetoothIndicator(), 150 | NetworkIndicator(), 151 | AudioIndicator(), 152 | MicrophoneIndicator(), 153 | BatteryIndicator(), 154 | ], 155 | }), 156 | }); 157 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/Taskbar.js: -------------------------------------------------------------------------------- 1 | import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; 2 | import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; 3 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 4 | import PanelButton from "../PanelButton.js"; 5 | import { launchApp } from "../../utils.js"; 6 | import icons from "../../icons.js"; 7 | 8 | const focus = ({ address }) => 9 | Hyprland.sendMessage(`dispatch focuswindow address:${address}`); 10 | 11 | /** @param {import('types/widgets/box').default} box */ 12 | const setChildren = (box) => (box.children = Hyprland.clients.map((client) => { 13 | if (Hyprland.active.workspace.id !== client.workspace.id) return; 14 | 15 | for (const app of Applications.list) { 16 | if (client.class && app.match(client.class)) { 17 | return PanelButton({ 18 | content: Widget.Icon(app.icon_name || icons.fallback.executable), 19 | tooltip_text: app.name, 20 | on_primary_click: () => focus(client), 21 | on_middle_click: () => launchApp(app), 22 | }); 23 | } 24 | } 25 | })); 26 | 27 | export default () => 28 | Widget.Box({ 29 | connections: [ 30 | [Hyprland, setChildren, "notify::clients"], 31 | [Hyprland, setChildren, "notify::active"], 32 | ], 33 | }); 34 | -------------------------------------------------------------------------------- /home/ags/src/bar/buttons/Workspaces.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import options from "../../options.js"; 5 | import { range } from "../../utils.js"; 6 | 7 | /** @param {any} arg */ 8 | const dispatch = (arg) => Utils.execAsync(`hyprctl dispatch workspace ${arg}`); 9 | 10 | const Workspaces = () => { 11 | const ws = options.workspaces.value; 12 | return Widget.Box({ 13 | children: range(ws || 20).map((i) => 14 | Widget.Button({ 15 | setup: (btn) => (btn.id = i), 16 | on_clicked: () => dispatch(i), 17 | child: Widget.Label({ 18 | label: `${i}`, 19 | class_name: "indicator", 20 | vpack: "center", 21 | }), 22 | connections: [ 23 | [ 24 | Hyprland, 25 | (btn) => { 26 | btn.toggleClassName("active", Hyprland.active.workspace.id === i); 27 | btn.toggleClassName( 28 | "occupied", 29 | Hyprland.getWorkspace(i)?.windows || 0 > 0, 30 | ); 31 | }, 32 | ], 33 | ], 34 | }) 35 | ), 36 | connections: ws ? [] : [ 37 | [ 38 | Hyprland.active.workspace, 39 | (box) => 40 | box.children.map((btn) => { 41 | btn.visible = Hyprland.workspaces.some( 42 | (ws) => ws.id === btn.id, 43 | ); 44 | }), 45 | ], 46 | ], 47 | }); 48 | }; 49 | 50 | export default () => 51 | Widget.EventBox({ 52 | class_name: "workspaces panel-button", 53 | child: Widget.Box({ 54 | // its nested like this to keep it consistent with other PanelButton widgets 55 | child: Widget.EventBox({ 56 | on_scroll_up: () => dispatch("m+1"), 57 | on_scroll_down: () => dispatch("m-1"), 58 | class_name: "eventbox", 59 | binds: [["child", options.workspaces, "value", Workspaces]], 60 | }), 61 | }), 62 | }); 63 | -------------------------------------------------------------------------------- /home/ags/src/dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import DateColumn from "./DateColumn.js"; 3 | import NotificationColumn from "./NotificationColumn.js"; 4 | import PopupWindow from "../misc/PopupWindow.js"; 5 | import options from "../options.js"; 6 | 7 | export default () => 8 | PopupWindow({ 9 | name: "dashboard", 10 | connections: [ 11 | [ 12 | options.bar.position, 13 | (self) => { 14 | self.anchor = [options.bar.position.value, "right"]; 15 | if (options.bar.position.value === "top") { 16 | self.transition = "slide_down"; 17 | } 18 | 19 | if (options.bar.position.value === "bottom") { 20 | self.transition = "slide_up"; 21 | } 22 | }, 23 | ], 24 | ], 25 | child: Widget.Box({ 26 | children: [ 27 | NotificationColumn(), 28 | Widget.Separator({ orientation: 1 }), 29 | DateColumn(), 30 | ], 31 | }), 32 | }); 33 | -------------------------------------------------------------------------------- /home/ags/src/dashboard/DateColumn.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import icons from "../icons.js"; 3 | import Clock from "../misc/Clock.js"; 4 | import * as vars from "../variables.js"; 5 | import options from "../options.js"; 6 | 7 | /** 8 | * @param {'cpu' | 'ram' | 'temp'} type 9 | * @param {string} title 10 | * @param {string} unit 11 | */ 12 | const SysProgress = (type, title, unit) => 13 | Widget.Box({ 14 | class_name: `circular-progress-box ${type}`, 15 | hexpand: true, 16 | binds: [ 17 | [ 18 | "tooltipText", 19 | vars[type], 20 | "value", 21 | (v) => `${title}: ${Math.floor(v * 100)}${unit}`, 22 | ], 23 | ], 24 | child: Widget.CircularProgress({ 25 | hexpand: true, 26 | class_name: `circular-progress ${type}`, 27 | child: Widget.Icon(icons.system[type]), 28 | start_at: 0.75, 29 | binds: [ 30 | ["value", vars[type]], 31 | ["rounded", options.radii, "value", (v) => v > 0], 32 | ], 33 | }), 34 | }); 35 | 36 | export default () => 37 | Widget.Box({ 38 | vertical: true, 39 | class_name: "datemenu vertical", 40 | children: [ 41 | Widget.Box({ 42 | class_name: "clock-box", 43 | vertical: true, 44 | children: [ 45 | Clock({ format: "%H:%M" }), 46 | Widget.Label({ 47 | class_name: "uptime", 48 | binds: [["label", vars.uptime, "value", (t) => `uptime: ${t}`]], 49 | }), 50 | ], 51 | }), 52 | Widget.Box({ 53 | class_name: "calendar", 54 | children: [ 55 | Widget.Calendar({ 56 | hexpand: true, 57 | hpack: "center", 58 | }), 59 | ], 60 | }), 61 | Widget.Box({ 62 | class_name: "system-info horizontal", 63 | children: [ 64 | SysProgress("cpu", "Cpu", "%"), 65 | SysProgress("ram", "Ram", "%"), 66 | SysProgress("temp", "Temperature", "°"), 67 | ], 68 | }), 69 | ], 70 | }); 71 | -------------------------------------------------------------------------------- /home/ags/src/dashboard/NotificationColumn.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 3 | import icons from "../icons.js"; 4 | import Notification from "../misc/Notification.js"; 5 | import { timeout } from "resource:///com/github/Aylur/ags/utils.js"; 6 | 7 | const ClearButton = () => 8 | Widget.Button({ 9 | on_clicked: () => { 10 | const list = Array.from(Notifications.notifications); 11 | for (let i = 0; i < list.length; i++) { 12 | timeout(50 * i, () => list[i]?.close()); 13 | } 14 | }, 15 | binds: [["sensitive", Notifications, "notifications", (n) => n.length > 0]], 16 | child: Widget.Box({ 17 | children: [ 18 | Widget.Label("Clear "), 19 | Widget.Icon({ 20 | binds: [ 21 | [ 22 | "icon", 23 | Notifications, 24 | "notifications", 25 | (n) => (n.length > 0 ? icons.trash.full : icons.trash.empty), 26 | ], 27 | ], 28 | }), 29 | ], 30 | }), 31 | }); 32 | 33 | const Header = () => 34 | Widget.Box({ 35 | class_name: "header", 36 | children: [ 37 | Widget.Label({ label: "Notifications", hexpand: true, xalign: 0 }), 38 | ClearButton(), 39 | ], 40 | }); 41 | 42 | const NotificationList = () => 43 | Widget.Box({ 44 | vertical: true, 45 | vexpand: true, 46 | connections: [ 47 | [ 48 | Notifications, 49 | (box) => { 50 | box.children = Notifications.notifications 51 | .reverse() 52 | .map(Notification); 53 | 54 | box.visible = Notifications.notifications.length > 0; 55 | }, 56 | ], 57 | ], 58 | }); 59 | 60 | const Placeholder = () => 61 | Widget.Box({ 62 | class_name: "placeholder", 63 | vertical: true, 64 | vpack: "center", 65 | hpack: "center", 66 | vexpand: true, 67 | hexpand: true, 68 | children: [ 69 | Widget.Icon(icons.notifications.silent), 70 | Widget.Label("Your inbox is empty"), 71 | ], 72 | binds: [["visible", Notifications, "notifications", (n) => n.length === 0]], 73 | }); 74 | 75 | export default () => 76 | Widget.Box({ 77 | class_name: "notifications", 78 | vertical: true, 79 | children: [ 80 | Header(), 81 | Widget.Scrollable({ 82 | vexpand: true, 83 | class_name: "notification-scrollable", 84 | child: Widget.Box({ 85 | class_name: "notification-list", 86 | vertical: true, 87 | children: [NotificationList(), Placeholder()], 88 | }), 89 | }), 90 | ], 91 | }); 92 | -------------------------------------------------------------------------------- /home/ags/src/desktop/Desktop.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import DesktopMenu from "./DesktopMenu.js"; 3 | import options from "../options.js"; 4 | 5 | /** @param {number} monitor */ 6 | export default (monitor) => 7 | Widget.Window({ 8 | monitor, 9 | name: `desktop${monitor}`, 10 | layer: "bottom", 11 | class_name: "desktop", 12 | anchor: ["top", "bottom", "left", "right"], 13 | child: Widget.EventBox({ 14 | on_secondary_click: (_, event) => DesktopMenu().popup_at_pointer(event), 15 | }), 16 | }); 17 | -------------------------------------------------------------------------------- /home/ags/src/desktop/DesktopMenu.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import App from "resource:///com/github/Aylur/ags/app.js"; 3 | import PowerMenu from "../services/powermenu.js"; 4 | import icons from "../icons.js"; 5 | import Gtk from "gi://Gtk"; 6 | import { openSettings } from "../settings/theme.js"; 7 | 8 | /** 9 | * @param {string} label 10 | * @param {string} icon 11 | * @param {import('types/widgets/menu').MenuItemProps['on_activate']} on_activate 12 | */ 13 | const Item = (label, icon, on_activate) => 14 | Widget.MenuItem({ 15 | on_activate, 16 | child: Widget.Box({ 17 | children: [ 18 | Widget.Icon(icon), 19 | Widget.Label({ 20 | label, 21 | hexpand: true, 22 | xalign: 0, 23 | }), 24 | ], 25 | }), 26 | }); 27 | 28 | export default () => 29 | Widget.Menu({ 30 | class_name: "desktop-menu", 31 | children: [ 32 | Widget.MenuItem({ 33 | child: Widget.Box({ 34 | children: [ 35 | Widget.Icon(icons.powermenu.shutdown), 36 | Widget.Label({ 37 | label: "System", 38 | hexpand: true, 39 | xalign: 0, 40 | }), 41 | ], 42 | }), 43 | submenu: Widget.Menu({ 44 | children: [ 45 | Item("Shutdown", icons.powermenu.shutdown, () => 46 | PowerMenu.action("shutdown")), 47 | Item("Log Out", icons.powermenu.logout, () => 48 | PowerMenu.action("logout")), 49 | Item("Reboot", icons.powermenu.reboot, () => 50 | PowerMenu.action("reboot")), 51 | Item("Sleep", icons.powermenu.sleep, () => 52 | PowerMenu.action("reboot")), 53 | ], 54 | }), 55 | }), 56 | Item("Applications", icons.apps.apps, () => 57 | App.openWindow("applauncher")), 58 | new Gtk.SeparatorMenuItem(), 59 | Item("Settings", icons.ui.settings, openSettings), 60 | ], 61 | }); 62 | -------------------------------------------------------------------------------- /home/ags/src/icons.js: -------------------------------------------------------------------------------- 1 | export default { 2 | lock: "system-lock-screen-symbolic", 3 | fallback: { 4 | executable: "application-x-executable-symbolic", 5 | }, 6 | audio: { 7 | mic: { 8 | muted: "microphone-disabled-symbolic", 9 | low: "microphone-sensitivity-low-symbolic", 10 | medium: "microphone-sensitivity-medium-symbolic", 11 | high: "microphone-sensitivity-high-symbolic", 12 | }, 13 | volume: { 14 | muted: "audio-volume-muted-symbolic", 15 | low: "audio-volume-low-symbolic", 16 | medium: "audio-volume-medium-symbolic", 17 | high: "audio-volume-high-symbolic", 18 | overamplified: "audio-volume-overamplified-symbolic", 19 | }, 20 | type: { 21 | headset: "audio-headphones-symbolic", 22 | speaker: "audio-speakers-symbolic", 23 | card: "audio-card-symbolic", 24 | }, 25 | mixer: "", 26 | }, 27 | asusctl: { 28 | profile: { 29 | Balanced: "power-profile-balanced-symbolic", 30 | Quiet: "power-profile-power-saver-symbolic", 31 | Performance: "power-profile-performance-symbolic", 32 | }, 33 | mode: { 34 | Integrated: "", 35 | Hybrid: "󰢮", 36 | }, 37 | }, 38 | apps: { 39 | apps: "view-app-grid-symbolic", 40 | search: "folder-saved-search-symbolic", 41 | }, 42 | battery: { 43 | charging: "󱐋", 44 | warning: "battery-empty-symbolic", 45 | }, 46 | bluetooth: { 47 | enabled: "bluetooth-active-symbolic", 48 | disabled: "bluetooth-disabled-symbolic", 49 | }, 50 | brightness: { 51 | indicator: "display-brightness-symbolic", 52 | keyboard: "keyboard-brightness-symbolic", 53 | screen: "display-brightness-symbolic", 54 | }, 55 | powermenu: { 56 | sleep: "weather-clear-night-symbolic", 57 | reboot: "system-reboot-symbolic", 58 | logout: "system-log-out-symbolic", 59 | shutdown: "system-shutdown-symbolic", 60 | }, 61 | recorder: { 62 | recording: "media-record-symbolic", 63 | }, 64 | notifications: { 65 | noisy: "preferences-system-notifications-symbolic", 66 | silent: "notifications-disabled-symbolic", 67 | }, 68 | trash: { 69 | full: "user-trash-full-symbolic", 70 | empty: "user-trash-symbolic", 71 | }, 72 | mpris: { 73 | fallback: "audio-x-generic-symbolic", 74 | shuffle: { 75 | enabled: "󰒟", 76 | disabled: "󰒟", 77 | }, 78 | loop: { 79 | none: "󰓦", 80 | track: "󰓦", 81 | playlist: "󰑐", 82 | }, 83 | playing: "󰏦", 84 | paused: "󰐍", 85 | stopped: "󰐍", 86 | prev: "󰒮", 87 | next: "󰒭", 88 | }, 89 | ui: { 90 | close: "window-close-symbolic", 91 | info: "info-symbolic", 92 | menu: "open-menu-symbolic", 93 | link: "external-link-symbolic", 94 | settings: "emblem-system-symbolic", 95 | tick: "object-select-symbolic", 96 | arrow: { 97 | right: "draw-arrow-forward", 98 | left: "draw-arrow-back", 99 | down: "draw-arrow-down", 100 | up: "draw-arrow-up", 101 | }, 102 | trayarrow: { 103 | right: 'pan-end-symbolic', 104 | left: 'pan-start-symbolic', 105 | }, 106 | }, 107 | system: { 108 | cpu: "org.gnome.SystemMonitor-symbolic", 109 | ram: "drive-harddisk-solidstate-symbolic", 110 | temp: "sensors-temperature-symbolic", 111 | }, 112 | dialog: { 113 | Search: "", 114 | Applauncher: "󰵆", 115 | Bar: "", 116 | Border: "󰃇", 117 | Color: "󰏘", 118 | Desktop: "", 119 | Font: "", 120 | General: "󰒓", 121 | Miscellaneous: "󰠱", 122 | Theme: "󰃟", 123 | Notifications: "󰂚 ", 124 | }, 125 | }; 126 | -------------------------------------------------------------------------------- /home/ags/src/main.js: -------------------------------------------------------------------------------- 1 | import TopBar from "./bar/TopBar.js"; 2 | import Dashboard from "./dashboard/Dashboard.js"; 3 | import QuickSettings from "./quicksettings/QuickSettings.js"; 4 | import AppLauncher from "./applauncher/Applauncher.js"; 5 | import PowerMenu from "./powermenu/PowerMenu.js"; 6 | import Verification from "./powermenu/Verification.js"; 7 | import Desktop from "./desktop/Desktop.js"; 8 | import Notifications from "./notifications/Notifications.js"; 9 | import { init } from "./settings/setup.js"; 10 | import { forMonitors } from "./utils.js"; 11 | import options from "./options.js"; 12 | 13 | const windows = () => [ 14 | forMonitors(TopBar), 15 | forMonitors(Desktop), 16 | forMonitors(Notifications), 17 | QuickSettings(), 18 | Dashboard(), 19 | AppLauncher(), 20 | PowerMenu(), 21 | Verification(), 22 | ]; 23 | 24 | export default { 25 | onConfigParsed: init, 26 | windows: windows().flat(1), 27 | maxStreamVolume: 1.0, 28 | cacheNotificationActions: false, 29 | closeWindowDelay: { 30 | quicksettings: options.transition.value, 31 | dashboard: options.transition.value, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /home/ags/src/misc/Avatar.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import options from "../options.js"; 3 | 4 | /** @param {import('types/widgets/box').BoxProps=} props */ 5 | export default (props) => 6 | Widget.Box({ 7 | ...props, 8 | class_name: "avatar", 9 | connections: [ 10 | [ 11 | options.desktop.avatar, 12 | (box) => 13 | box.setCss(` 14 | background-image: url('${options.desktop.avatar.value}'); 15 | background-size: cover; 16 | `), 17 | ], 18 | [ 19 | "draw", 20 | (box) => { 21 | const h = box.get_allocated_height(); 22 | box.set_size_request(Math.ceil(h * 1.1), -1); 23 | }, 24 | ], 25 | ], 26 | }); 27 | -------------------------------------------------------------------------------- /home/ags/src/misc/BatteryIcon.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; 3 | 4 | export default () => 5 | Widget.Icon({ 6 | class_name: "battery", 7 | binds: [["icon", Battery, "icon-name"]], 8 | connections: [ 9 | [ 10 | Battery, 11 | (icon) => { 12 | icon.toggleClassName("charging", Battery.charging); 13 | icon.toggleClassName("charged", Battery.charged); 14 | icon.toggleClassName("low", Battery.percent < 30); 15 | }, 16 | ], 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /home/ags/src/misc/Clock.js: -------------------------------------------------------------------------------- 1 | import { clock } from '../variables.js'; 2 | import Widget from 'resource:///com/github/Aylur/ags/widget.js'; 3 | 4 | /** 5 | * @param {import('types/widgets/label').Props & { 6 | * format?: string, 7 | * interval?: number, 8 | * }} o 9 | */ 10 | export default ({ 11 | format = '%H:%M:%S %B %e. %A', 12 | ...rest 13 | } = {}) => Widget.Label({ 14 | class_name: 'clock', 15 | label: clock.bind('value').transform(time => { 16 | return time.format(format) || 'wrong format'; 17 | }), 18 | ...rest, 19 | }); 20 | -------------------------------------------------------------------------------- /home/ags/src/misc/FontIcon.js: -------------------------------------------------------------------------------- 1 | import Gtk from "gi://Gtk"; 2 | import AgsLabel from "resource:///com/github/Aylur/ags/widgets/label.js"; 3 | import GObject from "gi://GObject"; 4 | 5 | class FontIcon extends AgsLabel { 6 | static { 7 | GObject.registerClass(this); 8 | } 9 | 10 | /** @param {string | import('types/widgets/label').Props & { icon?: string }} params */ 11 | constructor(params = "") { 12 | const { icon = "", ...rest } = params; 13 | super(typeof params === "string" ? {} : rest); 14 | this.toggleClassName("font-icon"); 15 | 16 | if (typeof params === "object") this.icon = icon; 17 | 18 | if (typeof params === "string") this.icon = params; 19 | } 20 | 21 | get icon() { 22 | return this.label; 23 | } 24 | set icon(icon) { 25 | this.label = icon; 26 | } 27 | 28 | get size() { 29 | return this.get_style_context().get_property( 30 | "font-size", 31 | Gtk.StateFlags.NORMAL, 32 | ); 33 | } 34 | 35 | /** @returns {[number, number]} */ 36 | vfunc_get_preferred_height() { 37 | return [this.size, this.size]; 38 | } 39 | 40 | /** @returns {[number, number]} */ 41 | vfunc_get_preferred_width() { 42 | return [this.size, this.size]; 43 | } 44 | } 45 | 46 | /** @param {string | import('types/widgets/label').Props & { icon?: string }} params */ 47 | export default (params) => new FontIcon(params); 48 | -------------------------------------------------------------------------------- /home/ags/src/misc/HoverRevealer.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | 4 | /** 5 | * @typedef {import('types/widgets/eventbox').EventBoxProps & { 6 | * indicator?: import('types/widgets/box').BoxProps['child'] 7 | * direction?: 'left' | 'right' | 'down' | 'up' 8 | * duration?: number 9 | * eventboxConnections?: import('types/widgets/box').BoxProps['connections'] 10 | * connections?: import('types/widgets/revealer').RevealerProps['connections'] 11 | * }} HoverRevealProps 12 | */ 13 | 14 | /** 15 | * @param {HoverRevealProps} props 16 | */ 17 | export default ({ 18 | indicator, 19 | child, 20 | direction = "left", 21 | duration = 300, 22 | connections = [], 23 | eventboxConnections = [], 24 | binds = [], 25 | ...rest 26 | }) => { 27 | let open = false; 28 | const vertical = direction === "down" || direction === "up"; 29 | const posStart = direction === "down" || direction === "right"; 30 | const posEnd = direction === "up" || direction === "left"; 31 | 32 | const revealer = Widget.Revealer({ 33 | transition: `slide_${direction}`, 34 | connections, 35 | binds, 36 | transition_duration: duration, 37 | child, 38 | }); 39 | 40 | const eventbox = Widget.EventBox({ 41 | ...rest, 42 | connections: eventboxConnections, 43 | on_hover: () => { 44 | if (open) return; 45 | 46 | revealer.reveal_child = true; 47 | Utils.timeout(duration, () => (open = true)); 48 | }, 49 | on_hover_lost: () => { 50 | if (!open) return; 51 | 52 | revealer.reveal_child = false; 53 | open = false; 54 | }, 55 | child: Widget.Box({ 56 | vertical, 57 | children: [posStart && indicator, revealer, posEnd && indicator], 58 | }), 59 | }); 60 | 61 | return Widget.Box({ 62 | children: [eventbox], 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /home/ags/src/misc/IconBrowser.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import RegularWindow from "./RegularWindow.js"; 3 | import Gtk from "gi://Gtk"; 4 | 5 | export default () => { 6 | const selected = Widget.Label({ 7 | css: "font-size: 1.2em;", 8 | }); 9 | 10 | const flowbox = Widget.FlowBox({ 11 | min_children_per_line: 10, 12 | setup: (self) => { 13 | self.connect("child-activated", (_, child) => { 14 | selected.label = child.get_child().iconName; 15 | }); 16 | 17 | Gtk.IconTheme.get_default() 18 | .list_icons(null) 19 | .sort() 20 | .map((icon) => { 21 | !icon.endsWith(".symbolic") && 22 | self.insert( 23 | Widget.Icon({ 24 | icon, 25 | size: 38, 26 | }), 27 | -1, 28 | ); 29 | }); 30 | 31 | self.show_all(); 32 | }, 33 | }); 34 | 35 | const entry = Widget.Entry({ 36 | on_change: ({ text }) => 37 | flowbox.get_children().forEach((child) => { 38 | child.visible = child.get_child().iconName.includes(text); 39 | }), 40 | }); 41 | 42 | return RegularWindow({ 43 | name: "icons", 44 | visible: true, 45 | child: Widget.Box({ 46 | css: "padding: 30px;", 47 | spacing: 20, 48 | vertical: true, 49 | children: [ 50 | entry, 51 | Widget.Scrollable({ 52 | hscroll: "never", 53 | vscroll: "always", 54 | hexpand: true, 55 | vexpand: true, 56 | css: "min-width: 500px;" + "min-height: 500px;", 57 | child: flowbox, 58 | }), 59 | selected, 60 | ], 61 | }), 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /home/ags/src/misc/Notification.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | import GLib from "gi://GLib"; 4 | 5 | /** @param {import('types/service/notifications').Notification} n */ 6 | const NotificationIcon = ({ app_entry, app_icon, image }) => { 7 | if (image) { 8 | return Widget.Box({ 9 | vpack: "start", 10 | hexpand: false, 11 | class_name: "icon img", 12 | css: ` 13 | background-image: url("${image}"); 14 | background-size: cover; 15 | background-repeat: no-repeat; 16 | background-position: center; 17 | min-width: 78px; 18 | min-height: 78px; 19 | `, 20 | }); 21 | } 22 | 23 | let icon = "dialog-information-symbolic"; 24 | if (Utils.lookUpIcon(app_icon)) icon = app_icon; 25 | 26 | if (Utils.lookUpIcon(app_entry || "")) icon = app_entry || ""; 27 | 28 | return Widget.Box({ 29 | vpack: "start", 30 | hexpand: false, 31 | class_name: "icon", 32 | css: ` 33 | min-width: 78px; 34 | min-height: 78px; 35 | `, 36 | child: Widget.Icon({ 37 | icon, 38 | size: 58, 39 | hpack: "center", 40 | hexpand: true, 41 | vpack: "center", 42 | vexpand: true, 43 | }), 44 | }); 45 | }; 46 | 47 | /** @param {import('types/service/notifications').Notification} notification */ 48 | export default (notification) => { 49 | const content = Widget.Box({ 50 | class_name: "content", 51 | children: [ 52 | NotificationIcon(notification), 53 | Widget.Box({ 54 | hexpand: true, 55 | vertical: true, 56 | children: [ 57 | Widget.Box({ 58 | children: [ 59 | Widget.Label({ 60 | class_name: "title", 61 | xalign: 0, 62 | justification: "left", 63 | hexpand: true, 64 | max_width_chars: 24, 65 | truncate: "end", 66 | wrap: true, 67 | label: notification.summary, 68 | use_markup: true, 69 | }), 70 | Widget.Label({ 71 | class_name: "time", 72 | vpack: "start", 73 | label: GLib.DateTime.new_from_unix_local( 74 | notification.time, 75 | ).format("%H:%M"), 76 | }), 77 | Widget.Button({ 78 | class_name: "close-button", 79 | vpack: "start", 80 | child: Widget.Icon("window-close-symbolic"), 81 | on_clicked: () => notification.close(), 82 | }), 83 | ], 84 | }), 85 | Widget.Label({ 86 | class_name: "description", 87 | hexpand: true, 88 | use_markup: true, 89 | xalign: 0, 90 | justification: "left", 91 | label: notification.body, 92 | wrap: true, 93 | }), 94 | ], 95 | }), 96 | ], 97 | }); 98 | 99 | const actionsbox = Widget.Revealer({ 100 | transition: "slide_down", 101 | child: Widget.EventBox({ 102 | child: Widget.Box({ 103 | class_name: "actions horizontal", 104 | children: notification.actions.map((action) => 105 | Widget.Button({ 106 | class_name: "action-button", 107 | on_clicked: () => notification.invoke(action.id), 108 | hexpand: true, 109 | child: Widget.Label(action.label), 110 | }) 111 | ), 112 | }), 113 | }), 114 | }); 115 | 116 | return Widget.EventBox({ 117 | class_name: `notification ${notification.urgency}`, 118 | vexpand: false, 119 | on_primary_click: () => notification.dismiss(), 120 | on_hover() { 121 | actionsbox.reveal_child = true; 122 | }, 123 | on_hover_lost() { 124 | actionsbox.reveal_child = true; 125 | notification.dismiss(); 126 | }, 127 | child: Widget.Box({ 128 | vertical: true, 129 | children: [content, notification.actions.length > 0 && actionsbox], 130 | }), 131 | }); 132 | }; 133 | -------------------------------------------------------------------------------- /home/ags/src/misc/PopupWindow.js: -------------------------------------------------------------------------------- 1 | import AgsWindow from 'resource:///com/github/Aylur/ags/widgets/window.js'; 2 | import App from 'resource:///com/github/Aylur/ags/app.js'; 3 | import Widget from 'resource:///com/github/Aylur/ags/widget.js'; 4 | import options from '../options.js'; 5 | import GObject from 'gi://GObject'; 6 | 7 | const keyGrabber = Widget.Window({ 8 | name: 'key-grabber', 9 | popup: true, 10 | anchor: ['top', 'left', 'right', 'bottom'], 11 | css: 'background-color: transparent;', 12 | visible: false, 13 | exclusivity: 'ignore', 14 | keymode: 'on-demand', 15 | layer: 'top', 16 | attribute: { list: [] }, 17 | setup: self => self.on('notify::visible', ({ visible }) => { 18 | if (!visible) 19 | self.attribute?.list.forEach(name => App.closeWindow(name)); 20 | }), 21 | child: Widget.EventBox({ vexpand: true }).on('button-press-event', () => { 22 | App.closeWindow('key-grabber'); 23 | keyGrabber.attribute?.list.forEach(name => App.closeWindow(name)); 24 | }), 25 | }); 26 | 27 | // add before any PopupWindow is instantiated 28 | App.addWindow(keyGrabber); 29 | 30 | export class PopupWindow extends AgsWindow { 31 | static { GObject.registerClass(this); } 32 | 33 | constructor({ name, child, transition = 'none', visible = false, ...rest }) { 34 | super({ 35 | ...rest, 36 | name, 37 | popup: true, 38 | keymode: 'exclusive', 39 | layer: 'overlay', 40 | class_names: ['popup-window', name], 41 | }); 42 | 43 | child.toggleClassName('window-content'); 44 | this.revealer = Widget.Revealer({ 45 | transition, 46 | child, 47 | transition_duration: options.transition.value, 48 | setup: self => self.hook(App, (_, wname, visible) => { 49 | if (wname === name) 50 | this.revealer.reveal_child = visible; 51 | }), 52 | }); 53 | 54 | this.child = Widget.Box({ 55 | css: 'padding: 1px;', 56 | child: this.revealer, 57 | }); 58 | 59 | this.show_all(); 60 | this.visible = visible; 61 | 62 | keyGrabber.bind('visible', this, 'visible'); 63 | keyGrabber.attribute?.list.push(name); 64 | } 65 | 66 | set transition(dir) { this.revealer.transition = dir; } 67 | get transition() { return this.revealer.transition; } 68 | } 69 | 70 | /** @param {import('types/widgets/window').WindowProps & { 71 | * name: string 72 | * child: import('types/widgets/box').default 73 | * transition?: import('types/widgets/revealer').RevealerProps['transition'] 74 | * }} config 75 | */ 76 | export default config => new PopupWindow(config); 77 | -------------------------------------------------------------------------------- /home/ags/src/misc/Progress.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | 4 | export default ({ 5 | height = 18, 6 | width = 180, 7 | vertical = false, 8 | child, 9 | ...props 10 | }) => { 11 | const fill = Widget.Box({ 12 | class_name: "fill", 13 | hexpand: vertical, 14 | vexpand: !vertical, 15 | hpack: vertical ? "fill" : "start", 16 | vpack: vertical ? "end" : "fill", 17 | children: [child], 18 | }); 19 | 20 | let fill_size = 0; 21 | 22 | return Widget.Box({ 23 | ...props, 24 | class_name: "progress", 25 | css: ` 26 | min-width: ${width}px; 27 | min-height: ${height}px; 28 | `, 29 | children: [fill], 30 | setup: (progress) => (progress.setValue = (value) => { 31 | if (value < 0) return; 32 | 33 | const axis = vertical ? "height" : "width"; 34 | const axisv = vertical ? height : width; 35 | const min = vertical ? width : height; 36 | const preferred = (axisv - min) * value + min; 37 | 38 | if (!fill_size) { 39 | fill_size = preferred; 40 | fill.setCss(`min-${axis}: ${preferred}px;`); 41 | return; 42 | } 43 | 44 | const frames = 10; 45 | const goal = preferred - fill_size; 46 | const step = goal / frames; 47 | 48 | for (let i = 0; i < frames; ++i) { 49 | Utils.timeout(5 * i, () => { 50 | fill_size += step; 51 | fill.setCss(`min-${axis}: ${fill_size}px`); 52 | }); 53 | } 54 | }), 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /home/ags/src/misc/RegularWindow.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Gtk from "gi://Gtk"; 3 | 4 | /** @type {function(Gtk.Window.ConstructorProperties & import('types/widgets/widget').BaseProps): Gtk.Window } */ 5 | export default Widget.subclass(Gtk.Window, "RegularWindow"); 6 | -------------------------------------------------------------------------------- /home/ags/src/notifications/Notifications.js: -------------------------------------------------------------------------------- 1 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import Notification from "../misc/Notification.js"; 5 | import options from "../options.js"; 6 | 7 | /** @param {import('types/widgets/revealer').default} parent */ 8 | const Popups = (parent) => { 9 | const map = new Map(); 10 | 11 | const onDismissed = (_, id, force = false) => { 12 | if (!id || !map.has(id)) return; 13 | 14 | if (map.get(id).isHovered() && !force) return; 15 | 16 | if (map.size - 1 === 0) parent.reveal_child = false; 17 | 18 | Utils.timeout(200, () => { 19 | map.get(id)?.destroy(); 20 | map.delete(id); 21 | }); 22 | }; 23 | 24 | /** @param {import('types/widgets/box').default} box */ 25 | const onNotified = (box, id) => { 26 | if (!id || Notifications.dnd) return; 27 | 28 | const n = Notifications.getNotification(id); 29 | if (!n) return; 30 | 31 | if (options.notifications.black_list.value.includes(n.app_name || "")) { 32 | return; 33 | } 34 | 35 | map.delete(id); 36 | map.set(id, Notification(n)); 37 | box.children = Array.from(map.values()).reverse(); 38 | Utils.timeout(10, () => { 39 | parent.reveal_child = true; 40 | }); 41 | }; 42 | 43 | return Widget.Box({ 44 | vertical: true, 45 | connections: [ 46 | [Notifications, onNotified, "notified"], 47 | [Notifications, onDismissed, "dismissed"], 48 | [Notifications, (box, id) => onDismissed(box, id, true), "closed"], 49 | ], 50 | }); 51 | }; 52 | 53 | /** @param {import('types/widgets/revealer').RevealerProps['transition']} transition */ 54 | const PopupList = (transition = "slide_down") => 55 | Widget.Box({ 56 | css: "padding: 1px", 57 | children: [ 58 | Widget.Revealer({ 59 | transition, 60 | setup: (self) => (self.child = Popups(self)), 61 | }), 62 | ], 63 | }); 64 | 65 | /** @param {number} monitor */ 66 | export default (monitor) => 67 | Widget.Window({ 68 | monitor, 69 | name: `notifications${monitor}`, 70 | class_name: "notifications", 71 | binds: [["anchor", options.notifications.position]], 72 | child: PopupList(), 73 | }); 74 | -------------------------------------------------------------------------------- /home/ags/src/powermenu/PowerMenu.js: -------------------------------------------------------------------------------- 1 | import Widget from 'resource:///com/github/Aylur/ags/widget.js'; 2 | import icons from '../icons.js'; 3 | import PowerMenu from '../services/powermenu.js'; 4 | import ShadedPopup from './ShadedPopup.js'; 5 | 6 | /** 7 | * @param {'sleep' | 'reboot' | 'logout' | 'shutdown'} action 8 | * @param {string} label 9 | */ 10 | const SysButton = (action, label) => Widget.Button({ 11 | on_clicked: () => PowerMenu.action(action), 12 | child: Widget.Box({ 13 | vertical: true, 14 | children: [ 15 | Widget.Icon(icons.powermenu[action]), 16 | Widget.Label(label), 17 | ], 18 | }), 19 | }); 20 | 21 | export default () => ShadedPopup({ 22 | name: 'powermenu', 23 | expand: true, 24 | child: Widget.Box({ 25 | children: [ 26 | SysButton('sleep', 'Sleep'), 27 | SysButton('reboot', 'Reboot'), 28 | SysButton('logout', 'Log Out'), 29 | SysButton('shutdown', 'Shutdown'), 30 | ], 31 | }), 32 | }); 33 | -------------------------------------------------------------------------------- /home/ags/src/powermenu/ShadedPopup.js: -------------------------------------------------------------------------------- 1 | import App from 'resource:///com/github/Aylur/ags/app.js'; 2 | import Widget from 'resource:///com/github/Aylur/ags/widget.js'; 3 | 4 | /** @param {string} windowName */ 5 | const Padding = windowName => Widget.EventBox({ 6 | class_name: 'padding', 7 | hexpand: true, 8 | vexpand: true, 9 | setup: w => w.on('button-press-event', () => App.toggleWindow(windowName)), 10 | }); 11 | 12 | /** 13 | * @template {import('gi://Gtk?version=3.0').default.Widget} T 14 | * @param {import('types/widgets/window').WindowProps & { 15 | * name: string 16 | * child: import('types/widgets/box').default 17 | * }} o 18 | */ 19 | export default ({ name, child, ...rest }) => Widget.Window({ 20 | ...rest, 21 | class_names: ['popup-window', name], 22 | name, 23 | visible: false, 24 | popup: true, 25 | keymode: 'on-demand', 26 | setup() { 27 | child.toggleClassName('window-content'); 28 | }, 29 | child: Widget.CenterBox({ 30 | class_name: 'shader', 31 | css: 'min-width: 5000px; min-height: 3000px;', 32 | start_widget: Padding(name), 33 | end_widget: Padding(name), 34 | center_widget: Widget.CenterBox({ 35 | vertical: true, 36 | start_widget: Padding(name), 37 | end_widget: Padding(name), 38 | center_widget: child, 39 | }), 40 | }), 41 | }); 42 | -------------------------------------------------------------------------------- /home/ags/src/powermenu/Verification.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import App from "resource:///com/github/Aylur/ags/app.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import PowerMenu from "../services/powermenu.js"; 5 | import ShadedPopup from "./ShadedPopup.js"; 6 | 7 | export default () => 8 | ShadedPopup({ 9 | name: "verification", 10 | expand: true, 11 | child: Widget.Box({ 12 | vertical: true, 13 | children: [ 14 | Widget.Box({ 15 | class_name: "text-box", 16 | vertical: true, 17 | children: [ 18 | Widget.Label({ 19 | class_name: "title", 20 | binds: [["label", PowerMenu, "title"]], 21 | }), 22 | Widget.Label({ 23 | class_name: "desc", 24 | label: "Are you sure?", 25 | }), 26 | ], 27 | }), 28 | Widget.Box({ 29 | class_name: "buttons horizontal", 30 | vexpand: true, 31 | vpack: "end", 32 | homogeneous: true, 33 | children: [ 34 | Widget.Button({ 35 | child: Widget.Label("No"), 36 | on_clicked: () => App.toggleWindow("verification"), 37 | }), 38 | Widget.Button({ 39 | child: Widget.Label("Yes"), 40 | on_clicked: () => Utils.exec(PowerMenu.cmd), 41 | }), 42 | ], 43 | }), 44 | ], 45 | }), 46 | }); 47 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/QuickSettings.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import PopupWindow from "../misc/PopupWindow.js"; 3 | import { AppMixer, Microhone, SinkSelector, Volume } from "./widgets/Volume.js"; 4 | import { NetworkToggle, WifiSelection } from "./widgets/Network.js"; 5 | import { BluetoothDevices, BluetoothToggle } from "./widgets/Bluetooth.js"; 6 | import { ThemeSelector, ThemeToggle } from "./widgets/Theme.js"; 7 | import App from "resource:///com/github/Aylur/ags/app.js"; 8 | import Media from "./widgets/Media.js"; 9 | import Brightness from "./widgets/Brightness.js"; 10 | import DND from "./widgets/DND.js"; 11 | import MicMute from "./widgets/MicMute.js"; 12 | import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; 13 | import PowerMenu from "../services/powermenu.js"; 14 | import Avatar from "../misc/Avatar.js"; 15 | import options from "../options.js"; 16 | import icons from "../icons.js"; 17 | import { openSettings } from "../settings/theme.js"; 18 | import { uptime } from "../variables.js"; 19 | 20 | const Row = (toggles = [], menus = []) => 21 | Widget.Box({ 22 | vertical: true, 23 | children: [ 24 | Widget.Box({ 25 | class_name: "row horizontal", 26 | children: toggles, 27 | }), 28 | ...menus, 29 | ], 30 | }); 31 | 32 | const Homogeneous = (toggles) => 33 | Widget.Box({ 34 | homogeneous: true, 35 | children: toggles, 36 | }); 37 | 38 | export default () => 39 | PopupWindow({ 40 | name: "quicksettings", 41 | connections: [ 42 | [ 43 | options.bar.position, 44 | (self) => { 45 | self.anchor = ["right", options.bar.position.value]; 46 | if (options.bar.position.value === "top") { 47 | self.transition = "slide_down"; 48 | } 49 | 50 | if (options.bar.position.value === "bottom") { 51 | self.transition = "slide_up"; 52 | } 53 | }, 54 | ], 55 | ], 56 | child: Widget.Box({ 57 | vertical: true, 58 | children: [ 59 | Widget.Box({ 60 | class_name: "header horizontal", 61 | children: [ 62 | Avatar(), 63 | Widget.Box({ 64 | hpack: "start", 65 | class_name: "battery horizontal", 66 | children: [ 67 | Widget.Icon({ binds: [["icon", Battery, "icon-name"]] }), 68 | Widget.Label({ 69 | binds: [["label", Battery, "percent", (p) => `${p}%`]], 70 | }), 71 | ], 72 | }), 73 | Widget.Box({ 74 | hpack: "end", 75 | vpack: "center", 76 | hexpand: true, 77 | children: [ 78 | // Widget.Label({ 79 | // class_name: "uptime", 80 | // binds: [["label", uptime, "value", (v) => `up: ${v}`]], 81 | // }), 82 | DND(), 83 | MicMute(), 84 | Widget.Button({ 85 | on_clicked: () => { 86 | App.closeWindow("quicksettings"); 87 | openSettings(); 88 | }, 89 | child: Widget.Icon(icons.ui.settings), 90 | }), 91 | Widget.Button({ 92 | on_clicked: () => { 93 | App.closeWindow("quicksettings"); 94 | App.openWindow("powermenu"); 95 | }, 96 | child: Widget.Icon(icons.powermenu.shutdown), 97 | }), 98 | ], 99 | }), 100 | ], 101 | }), 102 | Widget.Box({ 103 | class_name: "sliders-box vertical", 104 | vertical: true, 105 | children: [ 106 | Row([Volume()], [SinkSelector(), AppMixer()]), 107 | Microhone(), 108 | Brightness(), 109 | ], 110 | }), 111 | Row( 112 | [Homogeneous([NetworkToggle(), BluetoothToggle()])], 113 | [WifiSelection(), BluetoothDevices()], 114 | ), 115 | Row([ThemeToggle()], [ThemeSelector()]), 116 | Media(), 117 | ], 118 | }), 119 | }); 120 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/ToggleButton.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import App from "resource:///com/github/Aylur/ags/app.js"; 3 | import Variable from "resource:///com/github/Aylur/ags/variable.js"; 4 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 5 | import icons from "../icons.js"; 6 | 7 | /** name of the currently opened menu */ 8 | export const opened = Variable(""); 9 | App.connect("window-toggled", (_, name, visible) => { 10 | if (name === "quicksettings" && !visible) { 11 | Utils.timeout(500, () => (opened.value = "")); 12 | } 13 | }); 14 | 15 | /** 16 | * @param {string} name - menu name 17 | * @param {(() => void) | false=} activate 18 | */ 19 | export const Arrow = (name, activate) => { 20 | let deg = 0; 21 | let iconOpened = false; 22 | return Widget.Button({ 23 | child: Widget.Icon({ 24 | icon: icons.ui.arrow.right, 25 | connections: [ 26 | [ 27 | opened, 28 | (icon) => { 29 | if ( 30 | (opened.value === name && !iconOpened) || 31 | (opened.value !== name && iconOpened) 32 | ) { 33 | const step = opened.value === name ? 10 : -10; 34 | iconOpened = !iconOpened; 35 | for (let i = 0; i < 9; ++i) { 36 | Utils.timeout(15 * i, () => { 37 | deg += step; 38 | icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`); 39 | }); 40 | } 41 | } 42 | }, 43 | ], 44 | ], 45 | }), 46 | on_clicked: () => { 47 | opened.value = opened.value === name ? "" : name; 48 | if (typeof activate === "function") activate(); 49 | }, 50 | }); 51 | }; 52 | 53 | /** 54 | * @param {Object} o 55 | * @param {string} o.name - menu name 56 | * @param {import('gi://Gtk').Gtk.Widget} o.icon 57 | * @param {import('gi://Gtk').Gtk.Widget} o.label 58 | * @param {() => void} o.activate 59 | * @param {() => void} o.deactivate 60 | * @param {boolean=} o.activateOnArrow 61 | * @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection 62 | */ 63 | export const ArrowToggleButton = ({ 64 | name, 65 | icon, 66 | label, 67 | activate, 68 | deactivate, 69 | activateOnArrow = true, 70 | connection: [service, condition], 71 | }) => 72 | Widget.Box({ 73 | class_name: "toggle-button", 74 | connections: [ 75 | [ 76 | service, 77 | (box) => { 78 | box.toggleClassName("active", condition()); 79 | }, 80 | ], 81 | ], 82 | children: [ 83 | Widget.Button({ 84 | child: Widget.Box({ 85 | hexpand: true, 86 | class_name: "label-box horizontal", 87 | children: [icon, label], 88 | }), 89 | on_clicked: () => { 90 | if (condition()) { 91 | deactivate(); 92 | if (opened.value === name) opened.value = ""; 93 | } else { 94 | activate(); 95 | } 96 | }, 97 | }), 98 | Arrow(name, activateOnArrow && activate), 99 | ], 100 | }); 101 | 102 | /** 103 | * @param {Object} o 104 | * @param {string} o.name - menu name 105 | * @param {import('gi://Gtk').Gtk.Widget} o.icon 106 | * @param {import('gi://Gtk').Gtk.Widget} o.title 107 | * @param {import('gi://Gtk').Gtk.Widget[]} o.content 108 | */ 109 | export const Menu = ({ name, icon, title, content }) => 110 | Widget.Revealer({ 111 | transition: "slide_down", 112 | binds: [["reveal-child", opened, "value", (v) => v === name]], 113 | child: Widget.Box({ 114 | class_names: ["menu", name], 115 | vertical: true, 116 | children: [ 117 | Widget.Box({ 118 | class_name: "title horizontal", 119 | children: [icon, title], 120 | }), 121 | Widget.Separator(), 122 | // Widget.Scrollable({ 123 | // child: Widget.Box({ 124 | // vertical: true, 125 | // children: content, 126 | // }), 127 | // }), 128 | ...content, 129 | ], 130 | }), 131 | }); 132 | 133 | /** 134 | * @param {Object} o 135 | * @param {import('gi://Gtk').Gtk.Widget} o.icon 136 | * @param {() => void} o.toggle 137 | * @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection 138 | */ 139 | export const SimpleToggleButton = ({ 140 | icon, 141 | toggle, 142 | connection: [service, condition], 143 | }) => 144 | Widget.Button({ 145 | class_name: "simple-toggle", 146 | connections: [ 147 | [ 148 | service, 149 | (box) => { 150 | box.toggleClassName("active", condition()); 151 | }, 152 | ], 153 | ], 154 | child: icon, 155 | on_clicked: toggle, 156 | }); 157 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/Bluetooth.js: -------------------------------------------------------------------------------- 1 | import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import icons from "../../icons.js"; 4 | import { ArrowToggleButton, Menu } from "../ToggleButton.js"; 5 | 6 | export const BluetoothToggle = () => 7 | ArrowToggleButton({ 8 | name: "bluetooth", 9 | icon: Widget.Icon({ 10 | connections: [ 11 | [ 12 | Bluetooth, 13 | (icon) => { 14 | icon.icon = Bluetooth.enabled 15 | ? icons.bluetooth.enabled 16 | : icons.bluetooth.disabled; 17 | }, 18 | ], 19 | ], 20 | }), 21 | label: Widget.Label({ 22 | truncate: "end", 23 | connections: [ 24 | [ 25 | Bluetooth, 26 | (label) => { 27 | if (!Bluetooth.enabled) return (label.label = "Disabled"); 28 | 29 | if (Bluetooth.connectedDevices.length === 0) { 30 | return (label.label = "Not Connected"); 31 | } 32 | 33 | if (Bluetooth.connectedDevices.length === 1) { 34 | return (label.label = Bluetooth.connectedDevices[0].alias); 35 | } 36 | 37 | label.label = `${Bluetooth.connectedDevices.length} Connected`; 38 | }, 39 | ], 40 | ], 41 | }), 42 | connection: [Bluetooth, () => Bluetooth.enabled], 43 | deactivate: () => (Bluetooth.enabled = false), 44 | activate: () => (Bluetooth.enabled = true), 45 | }); 46 | 47 | const DeviceItem = (device) => 48 | Widget.Box({ 49 | children: [ 50 | Widget.Icon(device.icon_name + "-symbolic"), 51 | Widget.Label(device.name), 52 | Widget.Label({ 53 | label: `${device.battery_percentage}%`, 54 | binds: [["visible", device, "battery-percentage", (p) => p > 0]], 55 | }), 56 | Widget.Box({ hexpand: true }), 57 | Widget.Spinner({ 58 | binds: [ 59 | ["active", device, "connecting"], 60 | ["visible", device, "connecting"], 61 | ], 62 | }), 63 | Widget.Switch({ 64 | active: device.connected, 65 | binds: [["visible", device, "connecting", (c) => !c]], 66 | connections: [ 67 | [ 68 | "notify::active", 69 | ({ active }) => { 70 | device.setConnection(active); 71 | }, 72 | ], 73 | ], 74 | }), 75 | ], 76 | }); 77 | 78 | export const BluetoothDevices = () => 79 | Menu({ 80 | name: "bluetooth", 81 | icon: Widget.Icon(icons.bluetooth.disabled), 82 | title: Widget.Label("Bluetooth"), 83 | content: [ 84 | Widget.Box({ 85 | hexpand: true, 86 | vertical: true, 87 | binds: [ 88 | [ 89 | "children", 90 | Bluetooth, 91 | "devices", 92 | (ds) => ds.filter((d) => d.name).map(DeviceItem), 93 | ], 94 | ], 95 | }), 96 | ], 97 | }); 98 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/Brightness.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import icons from "../../icons.js"; 3 | import Brightness from "../../services/brightness.js"; 4 | 5 | const BrightnessSlider = () => 6 | Widget.Slider({ 7 | draw_value: false, 8 | hexpand: true, 9 | binds: [["value", Brightness, "screen"]], 10 | on_change: ({ value }) => (Brightness.screen = value), 11 | }); 12 | 13 | export default () => 14 | Widget.Box({ 15 | children: [ 16 | Widget.Button({ 17 | child: Widget.Icon(icons.brightness.indicator), 18 | binds: [ 19 | [ 20 | "tooltip-text", 21 | Brightness, 22 | "screen", 23 | (v) => `Screen Brightness: ${Math.floor(v * 100)}%`, 24 | ], 25 | ], 26 | }), 27 | BrightnessSlider(), 28 | ], 29 | }); 30 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/DND.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 3 | import icons from "../../icons.js"; 4 | import { SimpleToggleButton } from "../ToggleButton.js"; 5 | 6 | export default () => 7 | SimpleToggleButton({ 8 | icon: Widget.Icon({ 9 | connections: [ 10 | [ 11 | Notifications, 12 | (icon) => { 13 | icon.icon = Notifications.dnd 14 | ? icons.notifications.silent 15 | : icons.notifications.noisy; 16 | }, 17 | "notify::dnd", 18 | ], 19 | ], 20 | }), 21 | toggle: () => (Notifications.dnd = !Notifications.dnd), 22 | connection: [Notifications, () => Notifications.dnd], 23 | }); 24 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/Media.js: -------------------------------------------------------------------------------- 1 | import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import * as mpris from "../../misc/mpris.js"; 4 | import options from "../../options.js"; 5 | 6 | /** @param {import('types/service/mpris').MprisPlayer} player */ 7 | const Footer = (player) => 8 | Widget.CenterBox({ 9 | class_name: "footer-box", 10 | children: [ 11 | Widget.Box({ 12 | class_name: "position", 13 | children: [ 14 | mpris.PositionLabel(player), 15 | mpris.Slash(player), 16 | mpris.LengthLabel(player), 17 | ], 18 | }), 19 | Widget.Box({ 20 | class_name: "controls", 21 | children: [ 22 | mpris.ShuffleButton(player), 23 | mpris.PreviousButton(player), 24 | mpris.PlayPauseButton(player), 25 | mpris.NextButton(player), 26 | mpris.LoopButton(player), 27 | ], 28 | }), 29 | mpris.PlayerIcon(player, { 30 | symbolic: false, 31 | hexpand: true, 32 | hpack: "end", 33 | }), 34 | ], 35 | }); 36 | 37 | /** @param {import('types/service/mpris').MprisPlayer} player */ 38 | const PlayerBox = (player) => 39 | Widget.Box({ 40 | class_name: `player ${player.name}`, 41 | child: mpris.BlurredCoverArt(player, { 42 | child: Widget.Box({ 43 | children: [ 44 | mpris.CoverArt(player, { 45 | hpack: "end", 46 | hexpand: false, 47 | }), 48 | Widget.Box({ 49 | hexpand: true, 50 | vertical: true, 51 | class_name: "labels", 52 | children: [ 53 | mpris.TitleLabel(player, { 54 | xalign: 0, 55 | justification: "left", 56 | wrap: true, 57 | }), 58 | mpris.ArtistLabel(player, { 59 | xalign: 0, 60 | justification: "left", 61 | wrap: true, 62 | }), 63 | Widget.Box({ 64 | vpack: "end", 65 | vexpand: true, 66 | vertical: true, 67 | children: [mpris.PositionSlider(player), Footer(player)], 68 | }), 69 | ], 70 | }), 71 | ], 72 | }), 73 | }), 74 | }); 75 | 76 | export default () => 77 | Widget.Box({ 78 | vertical: true, 79 | class_name: "media vertical", 80 | connections: [ 81 | [ 82 | "draw", 83 | (self) => { 84 | self.visible = Mpris.players.length > 0; 85 | }, 86 | ], 87 | ], 88 | binds: [ 89 | [ 90 | "children", 91 | Mpris, 92 | "players", 93 | (ps) => 94 | ps 95 | .filter((p) => !options.mpris.black_list.value.includes(p.identity)) 96 | .map(PlayerBox), 97 | ], 98 | ], 99 | }); 100 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/MicMute.js: -------------------------------------------------------------------------------- 1 | import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; 2 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 3 | import icons from "../../icons.js"; 4 | import { SimpleToggleButton } from "../ToggleButton.js"; 5 | 6 | export default () => 7 | SimpleToggleButton({ 8 | icon: Widget.Icon({ 9 | connections: [ 10 | [ 11 | Audio, 12 | (icon) => { 13 | icon.icon = Audio.microphone?.is_muted 14 | ? icons.audio.mic.muted 15 | : icons.audio.mic.high; 16 | }, 17 | "microphone-changed", 18 | ], 19 | ], 20 | }), 21 | toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted), 22 | connection: [Audio, () => Audio.microphone?.is_muted], 23 | }); 24 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/Network.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import Network from "resource:///com/github/Aylur/ags/service/network.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import icons from "../../icons.js"; 5 | import { ArrowToggleButton, Menu } from "../ToggleButton.js"; 6 | import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; 7 | 8 | export const NetworkToggle = () => 9 | ArrowToggleButton({ 10 | name: "network", 11 | icon: Widget.Icon({ 12 | connections: [ 13 | [ 14 | Network, 15 | (icon) => { 16 | icon.icon = Network.wifi.icon_name || ""; 17 | }, 18 | ], 19 | ], 20 | }), 21 | label: Widget.Label({ 22 | truncate: "end", 23 | connections: [ 24 | [ 25 | Network, 26 | (label) => { 27 | label.label = Network.wifi.ssid || "Not Connected"; 28 | }, 29 | ], 30 | ], 31 | }), 32 | connection: [Network, () => Network.wifi.enabled], 33 | deactivate: () => (Network.wifi.enabled = false), 34 | activate: () => { 35 | Network.wifi.enabled = true; 36 | Network.wifi.scan(); 37 | }, 38 | }); 39 | 40 | export const WifiSelection = () => 41 | Menu({ 42 | name: "network", 43 | icon: Widget.Icon({ 44 | connections: [ 45 | [ 46 | Network, 47 | (icon) => { 48 | icon.icon = Network.wifi.icon_name; 49 | }, 50 | ], 51 | ], 52 | }), 53 | title: Widget.Label("Wifi Selection"), 54 | content: [ 55 | Widget.Box({ 56 | vertical: true, 57 | connections: [ 58 | [ 59 | Network, 60 | (box) => (box.children = Network.wifi?.access_points.map((ap) => 61 | Widget.Button({ 62 | on_clicked: () => 63 | Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`), 64 | child: Widget.Box({ 65 | children: [ 66 | Widget.Icon(ap.iconName), 67 | Widget.Label(ap.ssid || ""), 68 | ap.active && 69 | Widget.Icon({ 70 | icon: icons.ui.tick, 71 | hexpand: true, 72 | hpack: "end", 73 | }), 74 | ], 75 | }), 76 | }) 77 | )), 78 | ], 79 | ], 80 | }), 81 | Widget.Separator(), 82 | Widget.Button({ 83 | on_clicked: () => 84 | Applications.query("gnome-control-center")?.[0].launch(), 85 | child: Widget.Box({ 86 | children: [Widget.Icon(icons.ui.settings), Widget.Label("Network")], 87 | }), 88 | }), 89 | ], 90 | }); 91 | -------------------------------------------------------------------------------- /home/ags/src/quicksettings/widgets/Theme.js: -------------------------------------------------------------------------------- 1 | import Widget from "resource:///com/github/Aylur/ags/widget.js"; 2 | import { ArrowToggleButton, Menu, opened } from "../ToggleButton.js"; 3 | import themes from "../../themes.js"; 4 | import icons from "../../icons.js"; 5 | import options from "../../options.js"; 6 | import { openSettings, setTheme } from "../../settings/theme.js"; 7 | 8 | export const ThemeToggle = () => 9 | ArrowToggleButton({ 10 | name: "theme", 11 | icon: Widget.Label({ binds: [["label", options.theme.icon]] }), 12 | label: Widget.Label({ binds: [["label", options.theme.name]] }), 13 | connection: [opened, () => opened.value === "theme"], 14 | activate: () => opened.setValue("theme"), 15 | activateOnArrow: false, 16 | deactivate: () => {}, 17 | }); 18 | 19 | export const ThemeSelector = () => 20 | Menu({ 21 | name: "theme", 22 | icon: Widget.Label({ 23 | binds: [["label", options.theme.icon]], 24 | }), 25 | title: Widget.Label("Theme Selector"), 26 | content: [ 27 | ...themes.map(({ name, icon }) => 28 | Widget.Button({ 29 | on_clicked: () => setTheme(name), 30 | child: Widget.Box({ 31 | children: [ 32 | Widget.Label(icon), 33 | Widget.Label(name), 34 | Widget.Icon({ 35 | icon: icons.ui.tick, 36 | hexpand: true, 37 | hpack: "end", 38 | binds: [ 39 | ["visible", options.theme.name, "value", (v) => v === name], 40 | ], 41 | }), 42 | ], 43 | }), 44 | }) 45 | ), 46 | Widget.Separator(), 47 | Widget.Button({ 48 | on_clicked: openSettings, 49 | child: Widget.Box({ 50 | children: [ 51 | Widget.Icon(icons.ui.settings), 52 | Widget.Label("Theme Settings"), 53 | ], 54 | }), 55 | }), 56 | ], 57 | }); 58 | -------------------------------------------------------------------------------- /home/ags/src/services/brightness.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 2 | import Service from "resource:///com/github/Aylur/ags/service.js"; 3 | import options from "../options.js"; 4 | import { dependencies } from "../utils.js"; 5 | 6 | const KBD = options.brightnessctlKBD; 7 | 8 | class Brightness extends Service { 9 | static { 10 | Service.register( 11 | this, 12 | {}, 13 | { 14 | screen: ["float", "rw"], 15 | kbd: ["int", "rw"], 16 | }, 17 | ); 18 | } 19 | 20 | #kbd = 0; 21 | #kbdMax = 3; 22 | #screen = 0; 23 | 24 | get kbd() { 25 | return this.#kbd; 26 | } 27 | get screen() { 28 | return this.#screen; 29 | } 30 | 31 | set kbd(value) { 32 | if (!dependencies(["brightnessctl"])) return; 33 | 34 | if (value < 0 || value > this.#kbdMax) return; 35 | 36 | Utils.execAsync(`brightnessctl -d ${KBD} s ${value} -q`) 37 | .then(() => { 38 | this.#kbd = value; 39 | this.changed("kbd"); 40 | }) 41 | .catch(console.error); 42 | } 43 | 44 | set screen(percent) { 45 | if (!dependencies(["brightnessctl"])) return; 46 | 47 | if (percent < 0) percent = 0; 48 | 49 | if (percent > 1) percent = 1; 50 | 51 | Utils.execAsync(`brightnessctl s ${percent * 100}% -q`) 52 | .then(() => { 53 | this.#screen = percent; 54 | this.changed("screen"); 55 | }) 56 | .catch(console.error); 57 | } 58 | 59 | constructor() { 60 | super(); 61 | 62 | if (dependencies(["brightnessctl"])) { 63 | this.#kbd = Number(Utils.exec(`brightnessctl -d ${KBD} g`)); 64 | this.#kbdMax = Number(Utils.exec(`brightnessctl -d ${KBD} m`)); 65 | this.#screen = Number(Utils.exec("brightnessctl g")) / 66 | Number(Utils.exec("brightnessctl m")); 67 | } 68 | } 69 | } 70 | 71 | export default new Brightness(); 72 | -------------------------------------------------------------------------------- /home/ags/src/services/colorpicker.js: -------------------------------------------------------------------------------- 1 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 2 | import { Variable } from "resource:///com/github/Aylur/ags/variable.js"; 3 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 4 | import Service from "resource:///com/github/Aylur/ags/service.js"; 5 | import { dependencies } from "../utils.js"; 6 | 7 | const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json"; 8 | 9 | class Colors extends Service { 10 | static { 11 | Service.register( 12 | this, 13 | {}, 14 | { 15 | colors: ["jsobject"], 16 | }, 17 | ); 18 | } 19 | 20 | /** @type {Variable} */ 21 | #colors = new Variable([]); 22 | get colors() { 23 | return this.#colors.value; 24 | } 25 | 26 | #notifID = 0; 27 | 28 | constructor() { 29 | super(); 30 | 31 | this.#colors.connect("changed", () => this.changed("colors")); 32 | 33 | Utils.readFileAsync(COLORS_CACHE) 34 | .then((out) => this.#colors.setValue(JSON.parse(out || "[]"))) 35 | .catch(() => print("no colorpicker cache found")); 36 | } 37 | 38 | /** @param {string} color */ 39 | wlCopy(color) { 40 | Utils.execAsync(["wl-copy", color]).catch((err) => console.error(err)); 41 | } 42 | 43 | async pick() { 44 | if (!dependencies(["hyprpicker"])) return; 45 | 46 | const color = await Utils.execAsync("hyprpicker"); 47 | if (!color) return; 48 | 49 | this.wlCopy(color); 50 | const list = this.#colors.value; 51 | if (!list.includes(color)) { 52 | list.push(color); 53 | if (list.length > 10) list.shift(); 54 | 55 | this.#colors.value = list; 56 | Utils.writeFile(JSON.stringify(list, null, 2), COLORS_CACHE).catch( 57 | (err) => console.error(err), 58 | ); 59 | } 60 | 61 | this.#notifID = Notifications.Notify( 62 | "Color Picker", 63 | this.#notifID, 64 | "color-select-symbolic", 65 | color, 66 | "", 67 | [], 68 | {}, 69 | ); 70 | } 71 | } 72 | 73 | export default new Colors(); 74 | -------------------------------------------------------------------------------- /home/ags/src/services/lockscreen.js: -------------------------------------------------------------------------------- 1 | import Service from "resource:///com/github/Aylur/ags/service.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | import App from "resource:///com/github/Aylur/ags/app.js"; 4 | const authpy = App.configDir + "/lockscreen/auth.py"; 5 | 6 | class Lockscreen extends Service { 7 | static { 8 | Service.register(this, { 9 | lock: ["boolean"], 10 | authenticating: ["boolean"], 11 | }); 12 | } 13 | 14 | lockscreen() { 15 | this.emit("lock", true); 16 | } 17 | 18 | /** @param {string} password */ 19 | auth(password) { 20 | this.emit("authenticating", true); 21 | Utils.execAsync([authpy, password]) 22 | .then((out) => { 23 | this.emit("lock", out !== "True"); 24 | this.emit("authenticating", false); 25 | }) 26 | .catch((err) => console.error(err)); 27 | } 28 | } 29 | 30 | export default new Lockscreen(); 31 | -------------------------------------------------------------------------------- /home/ags/src/services/onScreenIndicator.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 2 | import Service from "resource:///com/github/Aylur/ags/service.js"; 3 | import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; 4 | import icons from "../icons.js"; 5 | import { getAudioTypeIcon } from "../utils.js"; 6 | import Brightness from "./brightness.js"; 7 | 8 | class Indicator extends Service { 9 | static { 10 | Service.register(this, { 11 | popup: ["double", "string"], 12 | }); 13 | } 14 | 15 | #delay = 1500; 16 | #count = 0; 17 | 18 | /** 19 | * @param {number} value - 0 < v < 1 20 | * @param {string} icon 21 | */ 22 | popup(value, icon) { 23 | this.emit("popup", value, icon); 24 | this.#count++; 25 | Utils.timeout(this.#delay, () => { 26 | this.#count--; 27 | 28 | if (this.#count === 0) this.emit("popup", -1, icon); 29 | }); 30 | } 31 | 32 | speaker() { 33 | this.popup( 34 | Audio.speaker?.volume || 0, 35 | getAudioTypeIcon(Audio.speaker?.icon_name || ""), 36 | ); 37 | } 38 | 39 | display() { 40 | // brightness is async, so lets wait a bit 41 | Utils.timeout( 42 | 10, 43 | () => this.popup(Brightness.screen, icons.brightness.screen), 44 | ); 45 | } 46 | 47 | kbd() { 48 | // brightness is async, so lets wait a bit 49 | Utils.timeout( 50 | 10, 51 | () => 52 | this.popup((Brightness.kbd * 33 + 1) / 100, icons.brightness.keyboard), 53 | ); 54 | } 55 | 56 | connect(event = "popup", callback) { 57 | return super.connect(event, callback); 58 | } 59 | } 60 | 61 | export default new Indicator(); 62 | -------------------------------------------------------------------------------- /home/ags/src/services/powermenu.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import Service from "resource:///com/github/Aylur/ags/service.js"; 3 | 4 | class PowerMenu extends Service { 5 | static { 6 | Service.register( 7 | this, 8 | {}, 9 | { 10 | title: ["string"], 11 | cmd: ["string"], 12 | }, 13 | ); 14 | } 15 | 16 | #title = ""; 17 | #cmd = ""; 18 | 19 | get title() { 20 | return this.#title; 21 | } 22 | get cmd() { 23 | return this.#cmd; 24 | } 25 | 26 | /** @param {'sleep' | 'reboot' | 'logout' | 'shutdown'} action */ 27 | action(action) { 28 | [this.#cmd, this.#title] = { 29 | sleep: ["systemctl suspend", "Sleep"], 30 | reboot: ["systemctl reboot", "Reboot"], 31 | logout: ["swaymsg exit", "Log Out"], 32 | shutdown: ["shutdown now", "Shutdown"], 33 | }[action]; 34 | 35 | this.notify("cmd"); 36 | this.notify("title"); 37 | this.emit("changed"); 38 | App.closeWindow("powermenu"); 39 | App.openWindow("verification"); 40 | } 41 | } 42 | 43 | export default new PowerMenu(); 44 | -------------------------------------------------------------------------------- /home/ags/src/services/screenrecord.js: -------------------------------------------------------------------------------- 1 | import Service from "resource:///com/github/Aylur/ags/service.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | import App from "resource:///com/github/Aylur/ags/app.js"; 4 | import GLib from "gi://GLib"; 5 | import { dependencies } from "../utils.js"; 6 | 7 | const now = () => GLib.DateTime.new_now_local().format("%Y-%m-%d_%H-%M-%S"); 8 | 9 | class Recorder extends Service { 10 | static { 11 | Service.register( 12 | this, 13 | {}, 14 | { 15 | timer: ["int"], 16 | recording: ["boolean"], 17 | }, 18 | ); 19 | } 20 | 21 | #path = GLib.get_home_dir() + "/Videos/Screencasting"; 22 | #file = ""; 23 | #interval = 0; 24 | 25 | recording = false; 26 | timer = 0; 27 | 28 | async start() { 29 | if (!dependencies(["slurp", "wf-recorder"])) return; 30 | 31 | if (this.recording) return; 32 | 33 | const area = await Utils.execAsync("slurp"); 34 | Utils.ensureDirectory(this.#path); 35 | this.#file = `${this.#path}/${now()}.mp4`; 36 | Utils.execAsync(["wf-recorder", "-g", area, "-f", this.#file]); 37 | this.recording = true; 38 | this.changed("recording"); 39 | 40 | this.timer = 0; 41 | this.#interval = Utils.interval(1000, () => { 42 | this.changed("timer"); 43 | this.timer++; 44 | }); 45 | } 46 | 47 | async stop() { 48 | if (!dependencies(["notify-send"])) return; 49 | 50 | if (!this.recording) return; 51 | 52 | Utils.execAsync("killall -INT wf-recorder"); 53 | this.recording = false; 54 | this.changed("recording"); 55 | GLib.source_remove(this.#interval); 56 | 57 | const res = await Utils.execAsync([ 58 | "notify-send", 59 | "-A", 60 | "files=Show in Files", 61 | "-A", 62 | "view=View", 63 | "-i", 64 | "video-x-generic-symbolic", 65 | "Screenrecord", 66 | this.#file, 67 | ]); 68 | 69 | if (res === "files") Utils.execAsync("xdg-open " + this.#path); 70 | 71 | if (res === "view") Utils.execAsync("xdg-open " + this.#file); 72 | } 73 | 74 | async screenshot(full = false) { 75 | if (!dependencies(["slurp", "wayshot"])) return; 76 | 77 | const path = GLib.get_home_dir() + "/Pictures/Screenshots"; 78 | const file = `${path}/${now()}.png`; 79 | Utils.ensureDirectory(path); 80 | 81 | await Utils.execAsync( 82 | ["wayshot", "-f", file].concat( 83 | full ? [] : ["-s", await Utils.execAsync("slurp")], 84 | ), 85 | ); 86 | 87 | Utils.execAsync(["bash", "-c", `wl-copy < ${file}`]); 88 | 89 | const res = await Utils.execAsync([ 90 | "notify-send", 91 | "-A", 92 | "files=Show in Files", 93 | "-A", 94 | "view=View", 95 | "-A", 96 | "edit=Edit", 97 | "-i", 98 | file, 99 | "Screenshot", 100 | file, 101 | ]); 102 | if (res === "files") Utils.execAsync("xdg-open " + path); 103 | 104 | if (res === "view") Utils.execAsync("xdg-open " + file); 105 | 106 | if (res === "edit") Utils.execAsync(["swappy", "-f", file]); 107 | 108 | App.closeWindow("dashboard"); 109 | } 110 | } 111 | 112 | export default new Recorder(); 113 | -------------------------------------------------------------------------------- /home/ags/src/settings/globals.js: -------------------------------------------------------------------------------- 1 | import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; 2 | 3 | export async function globals() { 4 | globalThis.options = (await import("../options.js")).default; 5 | globalThis.iconBrowser = (await import("../misc/IconBrowser.js")).default; 6 | globalThis.app = ( 7 | await import("resource:///com/github/Aylur/ags/app.js") 8 | ).default; 9 | globalThis.audio = ( 10 | await import("resource:///com/github/Aylur/ags/service/audio.js") 11 | ).default; 12 | globalThis.recorder = (await import("../services/screenrecord.js")).default; 13 | globalThis.brightness = (await import("../services/brightness.js")).default; 14 | globalThis.indicator = ( 15 | await import("../services/onScreenIndicator.js") 16 | ).default; 17 | 18 | Mpris.players.forEach((player) => { 19 | player.connect("changed", (player) => { 20 | globalThis.mpris = player || Mpris.players[0]; 21 | }); 22 | }); 23 | 24 | Mpris.connect("player-added", (mpris, bus) => { 25 | mpris.getPlayer(bus)?.connect("changed", (player) => { 26 | globalThis.mpris = player || Mpris.players[0]; 27 | }); 28 | }); 29 | 30 | Mpris.connect("player-closed", () => { 31 | globalThis.mpris = Mpris.players[0]; 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /home/ags/src/settings/option.js: -------------------------------------------------------------------------------- 1 | import { 2 | CACHE_DIR, 3 | readFile, 4 | writeFile, 5 | } from "resource:///com/github/Aylur/ags/utils.js"; 6 | import { exec } from "resource:///com/github/Aylur/ags/utils.js"; 7 | import options from "../options.js"; 8 | import Service from "resource:///com/github/Aylur/ags/service.js"; 9 | import { reloadScss } from "./scss.js"; 10 | const CACHE_FILE = CACHE_DIR + "/options.json"; 11 | 12 | /** object that holds the overriedden values */ 13 | let cacheObj = JSON.parse(readFile(CACHE_FILE) || "{}"); 14 | 15 | /** 16 | * @template T 17 | * @typedef {Object} OptionConfig 18 | * @property {string=} scss - name of scss variable set to "exclude" to not include it in the generated scss file 19 | * @property {string=} unit - scss unit on numbers, default is "px" 20 | * @property {string=} title 21 | * @property {string=} note 22 | * @property {string=} category 23 | * @property {boolean=} noReload - don't reload css & hyprland on change 24 | * @property {boolean=} persist - ignore reset call 25 | * @property {'object' | 'string' | 'img' | 'number' | 'float' | 'font' | 'enum' =} type 26 | * @property {Array =} enums 27 | * @property {(value: T) => any=} format 28 | * @property {(value: T) => any=} scssFormat 29 | */ 30 | 31 | /** @template T */ 32 | export class Opt extends Service { 33 | static { 34 | Service.register( 35 | this, 36 | {}, 37 | { 38 | value: ["jsobject"], 39 | }, 40 | ); 41 | } 42 | 43 | #value; 44 | #scss = ""; 45 | unit = "px"; 46 | noReload = false; 47 | persist = false; 48 | id = ""; 49 | title = ""; 50 | note = ""; 51 | type = ""; 52 | category = ""; 53 | 54 | /** @type {Array} */ 55 | enums = []; 56 | 57 | /** @type {(v: T) => any} */ 58 | format = (v) => v; 59 | 60 | /** @type {(v: T) => any} */ 61 | scssFormat = (v) => v; 62 | 63 | /** 64 | * @param {T} value 65 | * @param {OptionConfig =} config 66 | */ 67 | constructor(value, config) { 68 | super(); 69 | this.#value = value; 70 | this.defaultValue = value; 71 | this.type = typeof value; 72 | 73 | if (config) Object.keys(config).forEach((c) => (this[c] = config[c])); 74 | 75 | import("../options.js").then(this.#init.bind(this)); 76 | } 77 | 78 | set scss(scss) { 79 | this.#scss = scss; 80 | } 81 | get scss() { 82 | return this.#scss || this.id.split(".").join("-").split("_").join("-"); 83 | } 84 | 85 | #init() { 86 | getOptions(); // sets the ids as a side effect 87 | 88 | if (cacheObj[this.id] !== undefined) this.setValue(cacheObj[this.id]); 89 | 90 | const words = this.id 91 | .split(".") 92 | .flatMap((w) => w.split("_")) 93 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)); 94 | 95 | this.title ||= words.join(" "); 96 | this.category ||= words.length === 1 ? "General" : words.at(0) || "General"; 97 | 98 | this.connect("changed", () => { 99 | cacheObj[this.id] = this.value; 100 | writeFile(JSON.stringify(cacheObj, null, 2), CACHE_FILE); 101 | }); 102 | } 103 | 104 | get value() { 105 | return this.#value; 106 | } 107 | set value(value) { 108 | this.setValue(value); 109 | } 110 | 111 | /** @param {T} value */ 112 | setValue(value, reload = false) { 113 | if (typeof value !== typeof this.defaultValue) { 114 | console.error( 115 | Error( 116 | `WrongType: Option "${this.id}" can't be set to ${value}, ` + 117 | `expected "${typeof this.defaultValue}", but got "${typeof value}"`, 118 | ), 119 | ); 120 | 121 | return; 122 | } 123 | 124 | if (this.value !== value) { 125 | this.#value = this.format(value); 126 | this.changed("value"); 127 | 128 | if (reload && !this.noReload) { 129 | reloadScss(); 130 | } 131 | } 132 | } 133 | 134 | reset(reload = false) { 135 | if (!this.persist) this.setValue(this.defaultValue, reload); 136 | } 137 | } 138 | 139 | /** 140 | * @template T 141 | * @param {T} value 142 | * @param {OptionConfig =} config 143 | * @returns {Opt} 144 | */ 145 | export function Option(value, config) { 146 | return new Opt(value, config); 147 | } 148 | 149 | /** @returns {Array>} */ 150 | export function getOptions(object = options, path = "") { 151 | return Object.keys(object).flatMap((key) => { 152 | /** @type Option */ 153 | const obj = object[key]; 154 | const id = path ? path + "." + key : key; 155 | 156 | if (obj instanceof Opt) { 157 | obj.id = id; 158 | return obj; 159 | } 160 | 161 | if (typeof obj === "object") return getOptions(obj, id); 162 | 163 | return []; 164 | }); 165 | } 166 | 167 | export function resetOptions() { 168 | exec(`rm -rf ${CACHE_FILE}`); 169 | cacheObj = {}; 170 | getOptions().forEach((opt) => opt.reset()); 171 | } 172 | 173 | export function getValues() { 174 | const obj = {}; 175 | for (const opt of getOptions()) { 176 | if (opt.category !== "exclude") obj[opt.id] = opt.value; 177 | } 178 | 179 | return JSON.stringify(obj, null, 2); 180 | } 181 | 182 | /** @param {string | object} config */ 183 | export function apply(config) { 184 | const options = getOptions(); 185 | const settings = typeof config === "string" ? JSON.parse(config) : config; 186 | 187 | for (const id of Object.keys(settings)) { 188 | const opt = options.find((opt) => opt.id === id); 189 | if (!opt) { 190 | print(`No option with id: "${id}"`); 191 | continue; 192 | } 193 | 194 | opt.setValue(settings[id]); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /home/ags/src/settings/scss.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 3 | import { getOptions } from "./option.js"; 4 | import { dependencies } from "../utils.js"; 5 | 6 | export function scssWatcher() { 7 | return Utils.subprocess( 8 | [ 9 | "inotifywait", 10 | "--recursive", 11 | "--event", 12 | "create,modify", 13 | "-m", 14 | App.configDir + "/scss", 15 | ], 16 | reloadScss, 17 | () => print("missing dependancy for css hotreload: inotify-tools"), 18 | ); 19 | } 20 | 21 | /** 22 | * generate an scss file that makes every option available as a variable 23 | * based on the passed scss parameter or the path in the object 24 | * 25 | * e.g 26 | * options.bar.style.value => $bar-style 27 | */ 28 | export async function reloadScss() { 29 | if (!dependencies(["sassc"])) return; 30 | 31 | const opts = getOptions(); 32 | const vars = opts.map((opt) => { 33 | if (opt.scss === "exclude") return ""; 34 | 35 | const unit = typeof opt.value === "number" ? opt.unit : ""; 36 | const value = opt.scssFormat ? opt.scssFormat(opt.value) : opt.value; 37 | return `$${opt.scss}: ${value}${unit};`; 38 | }); 39 | 40 | const bar_style = opts.find((opt) => opt.id === "bar.style")?.value || ""; 41 | const additional = bar_style === "normal" ? "//" : ` 42 | window#quicksettings .window-content { 43 | margin-right: $wm-gaps; 44 | } 45 | `; 46 | 47 | try { 48 | const tmp = "/tmp/ags/scss"; 49 | Utils.ensureDirectory(tmp); 50 | await Utils.writeFile(vars.join("\n"), `${tmp}/options.scss`); 51 | await Utils.writeFile(additional, `${tmp}/additional.scss`); 52 | await Utils.execAsync( 53 | `sassc ${App.configDir}/scss/main.scss ${tmp}/style.css`, 54 | ); 55 | App.resetCss(); 56 | App.applyCss(`${tmp}/style.css`); 57 | } catch (error) { 58 | if (error instanceof Error) console.error(error.message); 59 | 60 | if (typeof error === "string") console.error(error); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /home/ags/src/settings/setup.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 2 | import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; 3 | import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; 4 | import options from "../options.js"; 5 | import icons from "../icons.js"; 6 | import { reloadScss, scssWatcher } from "./scss.js"; 7 | import { wallpaper } from "./wallpaper.js"; 8 | import { globals } from "./globals.js"; 9 | import Gtk from "gi://Gtk"; 10 | 11 | export function init() { 12 | notificationBlacklist(); 13 | warnOnLowBattery(); 14 | globals(); 15 | tmux(); 16 | gsettigsColorScheme(); 17 | gtkFontSettings(); 18 | scssWatcher(); 19 | dependandOptions(); 20 | 21 | reloadScss(); 22 | // wallpaper(); 23 | // showAbout(); 24 | } 25 | 26 | function dependandOptions() { 27 | options.bar.style.connect("changed", ({ value }) => { 28 | if (value !== "normal") { 29 | options.desktop.screen_corners.setValue(false, true); 30 | } 31 | }); 32 | } 33 | 34 | function tmux() { 35 | if (!Utils.exec("which tmux")) return; 36 | 37 | /** @param {string} scss */ 38 | function getColor(scss) { 39 | if (scss.includes("#")) return scss; 40 | 41 | if (scss.includes("$")) { 42 | const opt = options 43 | .list() 44 | .find((opt) => opt.scss === scss.replace("$", "")); 45 | return opt?.value; 46 | } 47 | } 48 | 49 | options.theme.accent.accent.connect( 50 | "changed", 51 | ({ value }) => 52 | Utils.execAsync(`tmux set @main_accent ${getColor(value)}`).catch((err) => 53 | console.error(err.message) 54 | ), 55 | ); 56 | } 57 | 58 | function gsettigsColorScheme() { 59 | if (!Utils.exec("which gsettings")) return; 60 | 61 | options.theme.scheme.connect("changed", ({ value }) => { 62 | const gsettings = "gsettings set org.gnome.desktop.interface color-scheme"; 63 | Utils.execAsync(`${gsettings} "prefer-${value}"`).catch((err) => 64 | console.error(err.message) 65 | ); 66 | }); 67 | } 68 | 69 | function gtkFontSettings() { 70 | const settings = Gtk.Settings.get_default(); 71 | if (!settings) { 72 | console.error(Error("Gtk.Settings unavailable")); 73 | return; 74 | } 75 | 76 | const callback = () => { 77 | const { size, font } = options.font; 78 | settings.gtk_font_name = `${font.value} ${size.value}`; 79 | }; 80 | 81 | options.font.font.connect("notify::value", callback); 82 | options.font.size.connect("notify::value", callback); 83 | } 84 | 85 | function notificationBlacklist() { 86 | Notifications.connect("notified", (_, id) => { 87 | const n = Notifications.getNotification(id); 88 | options.notifications.black_list.value.forEach((item) => { 89 | if (n?.app_name.includes(item) || n?.app_entry?.includes(item)) n.close(); 90 | }); 91 | }); 92 | } 93 | 94 | function warnOnLowBattery() { 95 | Battery.connect("notify::percent", () => { 96 | const low = options.battery.low.value; 97 | if ( 98 | Battery.percent !== low || 99 | Battery.percent !== low / 2 || 100 | !Battery.charging 101 | ) { 102 | return; 103 | } 104 | 105 | Utils.execAsync([ 106 | "notify-send", 107 | `${Battery.percent}% Battery Percentage`, 108 | "-i", 109 | icons.battery.warning, 110 | "-u", 111 | "critical", 112 | ]); 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /home/ags/src/settings/theme.js: -------------------------------------------------------------------------------- 1 | import App from "resource:///com/github/Aylur/ags/app.js"; 2 | import options from "../options.js"; 3 | import themes from "../themes.js"; 4 | import { reloadScss } from "./scss.js"; 5 | import { wallpaper } from "./wallpaper.js"; 6 | 7 | /** @param {string} name */ 8 | export function setTheme(name) { 9 | options.reset(); 10 | const theme = themes.find((t) => t.name === name); 11 | if (!theme) return print("No theme named " + name); 12 | 13 | options.apply(theme.options); 14 | reloadScss(); 15 | } 16 | 17 | export const WP = App.configDir + "/assets/"; 18 | 19 | export const lightColors = { 20 | "theme.scheme": "light", 21 | "color.red": "#e55f86", 22 | "color.green": "#00D787", 23 | "color.yellow": "#EBFF71", 24 | "color.blue": "#51a4e7", 25 | "color.magenta": "#9077e7", 26 | "color.teal": "#51e6e6", 27 | "color.orange": "#E79E64", 28 | "theme.bg": "#fffffa", 29 | "theme.fg": "#141414", 30 | }; 31 | 32 | export const Theme = ({ name, icon = " ", ...options }) => ({ 33 | name, 34 | icon, 35 | options: { 36 | "theme.name": name, 37 | "theme.icon": icon, 38 | ...options, 39 | }, 40 | }); 41 | 42 | let settingsDialog; 43 | export async function openSettings() { 44 | if (settingsDialog) return settingsDialog.present(); 45 | 46 | try { 47 | settingsDialog = (await import("./SettingsDialog.js")).default; 48 | settingsDialog.present(); 49 | } catch (error) { 50 | if (error instanceof Error) console.error(error.message); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /home/ags/src/settings/wallpaper.js: -------------------------------------------------------------------------------- 1 | import options from "../options.js"; 2 | import { exec, execAsync } from "resource:///com/github/Aylur/ags/utils.js"; 3 | import { dependencies } from "../utils.js"; 4 | 5 | export function initWallpaper() { 6 | if (dependencies(["swww"])) { 7 | exec("swww init"); 8 | 9 | options.desktop.wallpaper.img.connect("changed", wallpaper); 10 | } 11 | } 12 | 13 | export function wallpaper() { 14 | if (!dependencies(["swww"])) return; 15 | 16 | execAsync([ 17 | "swww", 18 | "img", 19 | "--transition-type", 20 | "grow", 21 | "--transition-pos", 22 | exec("hyprctl cursorpos").replace(" ", ""), 23 | options.desktop.wallpaper.img.value, 24 | ]).catch((err) => console.error(err)); 25 | } 26 | -------------------------------------------------------------------------------- /home/ags/src/themes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Theme is a set of options that will be applied 3 | * ontop of the default values. see options.js for possible options 4 | */ 5 | import { lightColors, Theme, WP } from "./settings/theme.js"; 6 | 7 | export default [ 8 | Theme({ 9 | name: "Carburetor", 10 | "spacing": 16, 11 | "padding": 18, 12 | "radii": 30, 13 | "popover_padding_multiplier": 1, 14 | "color.red": "#fa4d56", 15 | "color.green": "#42be65", 16 | "color.yellow": "#fddc69", 17 | "color.blue": "#4589ff", 18 | "color.magenta": "#be95ff", 19 | "color.teal": "#3ddbd9", 20 | "color.orange": "#ff832b", 21 | "theme.scheme": "dark", 22 | "theme.bg": "#161616", 23 | "theme.fg": "#f4f4f4", 24 | "theme.accent.accent": "$blue", 25 | "theme.accent.fg": "#f4f4f4", 26 | "theme.accent.gradient": "to right, $accent, lighten($accent, 6%)", 27 | "theme.widget.bg": "$fg-color", 28 | "theme.widget.opacity": 94, 29 | "border.color": "$fg-color", 30 | "border.opacity": 100, 31 | "border.width": 0, 32 | "hypr.inactive_border": "rgba(333333ff)", 33 | "hypr.wm_gaps_multiplier": 2, 34 | "font.font": "Cantarell 10", 35 | "font.mono": "Berkeley Mono Regular", 36 | "font.size": 12, 37 | "applauncher.width": 500, 38 | "applauncher.height": 500, 39 | "applauncher.icon_size": 52, 40 | "bar.position": "top", 41 | "bar.style": "normal", 42 | "bar.flat_buttons": true, 43 | "bar.separators": false, 44 | "bar.icon": "distro-icon", 45 | "battery.bar.width": 50, 46 | "battery.bar.height": 13, 47 | "battery.bar.full": false, 48 | "battery.low": 30, 49 | "battery.medium": 50, 50 | "desktop.wallpaper.fg": "#fff", 51 | "desktop.avatar": "/home/oz/.config/term.png", 52 | "desktop.screen_corners": false, 53 | "desktop.clock.enable": true, 54 | "desktop.clock.position": "center center", 55 | "desktop.drop_shadow": true, 56 | "desktop.shadow": "rgba(0, 0, 0, .6)", 57 | "desktop.dock.icon_size": 56, 58 | "desktop.dock.pinned_apps": [ 59 | "firefox", 60 | "org.wezfurlong.wezterm", 61 | "org.gnome.Nautilus", 62 | "org.gnome.Calendar", 63 | "obsidian", 64 | "transmission-gtk", 65 | "caprine", 66 | "teams-for-linux", 67 | "discord", 68 | "spotify", 69 | "com.usebottles.bottles", 70 | "org.gnome.Software", 71 | ], 72 | "notifications.black_list": [ 73 | "Spotify", 74 | ], 75 | "notifications.position": [ 76 | "bottom", 77 | ], 78 | "notifications.width": 550, 79 | "dashboard.sys_info_size": 70, 80 | "mpris.black_list": [ 81 | "Caprine", 82 | ], 83 | "mpris.preferred": "spotify", 84 | "workspaces": 7, 85 | }), 86 | ]; 87 | -------------------------------------------------------------------------------- /home/ags/src/utils.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; 2 | import cairo from "cairo"; 3 | import icons from "./icons.js"; 4 | import Gdk from "gi://Gdk"; 5 | 6 | /** 7 | * @param {number} length 8 | * @param {number=} start 9 | * @returns {Array} 10 | */ 11 | export function range(length, start = 1) { 12 | return Array.from({ length }, (_, i) => i + start); 13 | } 14 | 15 | /** 16 | * @param {Array<[string, string] | string[]>} collection 17 | * @param {string} item 18 | * @returns {string} 19 | */ 20 | export function substitute(collection, item) { 21 | return collection.find(([from]) => from === item)?.[1] || item; 22 | } 23 | 24 | /** 25 | * @param {(monitor: number) => any} widget 26 | * @returns {Array} 27 | */ 28 | export function forMonitors(widget) { 29 | const n = Gdk.Display.get_default()?.get_n_monitors() || 1; 30 | return range(n, 0).map(widget).flat(1); 31 | } 32 | 33 | /** 34 | * @param {import('gi://Gtk').Gtk.Widget} widget 35 | * @returns {any} - missing cairo type 36 | */ 37 | export function createSurfaceFromWidget(widget) { 38 | const alloc = widget.get_allocation(); 39 | const surface = new cairo.ImageSurface( 40 | cairo.Format.ARGB32, 41 | alloc.width, 42 | alloc.height, 43 | ); 44 | const cr = new cairo.Context(surface); 45 | cr.setSourceRGBA(255, 255, 255, 0); 46 | cr.rectangle(0, 0, alloc.width, alloc.height); 47 | cr.fill(); 48 | widget.draw(cr); 49 | 50 | return surface; 51 | } 52 | 53 | /** @param {string} icon */ 54 | export function getAudioTypeIcon(icon) { 55 | const substitues = [ 56 | ["audio-headset-bluetooth", icons.audio.type.headset], 57 | ["audio-card-analog-usb", icons.audio.type.speaker], 58 | ["audio-card-analog-pci", icons.audio.type.card], 59 | ]; 60 | 61 | return substitute(substitues, icon); 62 | } 63 | 64 | /** @param {import('types/service/applications').Application} app */ 65 | export function launchApp(app) { 66 | Utils.execAsync(["hyprctl", "dispatch", "exec", `sh -c ${app.executable}`]); 67 | app.frequency += 1; 68 | } 69 | 70 | /** @param {Array} bins */ 71 | export function dependencies(bins) { 72 | const deps = bins.map((bin) => { 73 | const has = Utils.exec(`which ${bin}`); 74 | if (!has) print(`missing dependency: ${bin}`); 75 | 76 | return !!has; 77 | }); 78 | 79 | return deps.every((has) => has); 80 | } 81 | -------------------------------------------------------------------------------- /home/ags/src/variables.js: -------------------------------------------------------------------------------- 1 | import Variable from "resource:///com/github/Aylur/ags/variable.js"; 2 | import GLib from "gi://GLib"; 3 | import options from "./options.js"; 4 | 5 | const intval = options.systemFetchInterval; 6 | 7 | export const clock = Variable(GLib.DateTime.new_now_local(), { 8 | poll: [1000, () => GLib.DateTime.new_now_local()], 9 | }); 10 | 11 | export const uptime = Variable("", { 12 | poll: [ 13 | 60_000, 14 | "cat /proc/uptime", 15 | (line) => { 16 | const uptime = Number.parseInt(line.split(".")[0]) / 60; 17 | const h = Math.floor(uptime / 60); 18 | const s = Math.floor(uptime % 60); 19 | return `${h}:${s < 10 ? "0" + s : s}`; 20 | }, 21 | ], 22 | }); 23 | 24 | export const distro = GLib.get_os_info("ID"); 25 | 26 | export const distroIcon = (() => { 27 | switch (distro) { 28 | case "fedora": 29 | return ""; 30 | case "arch": 31 | return ""; 32 | case "nixos": 33 | return ""; 34 | case "debian": 35 | return ""; 36 | case "opensuse-tumbleweed": 37 | return ""; 38 | case "ubuntu": 39 | return ""; 40 | case "endeavouros": 41 | return ""; 42 | default: 43 | return ""; 44 | } 45 | })(); 46 | 47 | /** @type {function([string, string] | string[]): number} */ 48 | const divide = ([total, free]) => 49 | Number.parseInt(free) / Number.parseInt(total); 50 | 51 | export const cpu = Variable(0, { 52 | poll: [ 53 | intval, 54 | "top -b -n 1", 55 | (out) => 56 | divide([ 57 | "100", 58 | out 59 | .split("\n") 60 | .find((line) => line.includes("Cpu(s)")) 61 | ?.split(/\s+/)[1] 62 | .replace(",", ".") || "0", 63 | ]), 64 | ], 65 | }); 66 | 67 | export const ram = Variable(0, { 68 | poll: [ 69 | intval, 70 | "free", 71 | (out) => 72 | divide( 73 | out 74 | .split("\n") 75 | .find((line) => line.includes("Mem:")) 76 | ?.split(/\s+/) 77 | .splice(1, 2) || ["1", "1"], 78 | ), 79 | ], 80 | }); 81 | 82 | export const temp = Variable(0, { 83 | poll: [ 84 | intval, 85 | "cat " + options.temperature, 86 | (n) => { 87 | return Number.parseInt(n) / 100_000; 88 | }, 89 | ], 90 | }); 91 | -------------------------------------------------------------------------------- /home/ags/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "lib": [ 6 | "ES2022" 7 | ], 8 | "allowJs": true, 9 | "checkJs": true, 10 | "strict": true, 11 | "noImplicitAny": false, 12 | "baseUrl": ".", 13 | "typeRoots": [ 14 | "./types/ags.d.ts", 15 | "./node_modules/@girs" 16 | ], 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /home/ags/types: -------------------------------------------------------------------------------- 1 | /home/oz/.local/share/com.github.Aylur.ags/types -------------------------------------------------------------------------------- /home/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | username, 5 | homeDirectory, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | # carburetor theming 11 | inputs.carburetor.homeManagerModules.default 12 | 13 | ./foot.nix # Terminal 14 | ./zsh.nix # Shell 15 | ./starship.nix # Prompt 16 | ./git.nix # Git 17 | ./dev.nix # Dev utils 18 | 19 | ./hyprland.nix # window manager 20 | ./gtk.nix # gtk theming 21 | ./ags # bar, app launcher 22 | ./vesktop # discord 23 | ./zed # zed editor 24 | ]; 25 | 26 | home = { 27 | inherit username homeDirectory; 28 | stateVersion = "24.05"; 29 | packages = with pkgs; [ 30 | lutgen 31 | ]; 32 | }; 33 | 34 | programs.home-manager.enable = true; 35 | } 36 | -------------------------------------------------------------------------------- /home/dev.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | home = { 4 | packages = with pkgs; [ 5 | neovim 6 | 7 | deno 8 | nodejs 9 | gnumake 10 | 11 | inotify-tools 12 | nix-tree 13 | 14 | ripgrep-all 15 | ripgrep 16 | xq 17 | 18 | gh 19 | bottom 20 | ]; 21 | 22 | sessionVariables = { 23 | EDITOR = "nvim"; 24 | CARGO_NET_GIT_FETCH_WITH_CLI = "true"; 25 | npm_config_prefix = "$HOME/.npm/"; 26 | PATH = "$HOME/.cargo/bin:$HOME/.npm/bin:$PATH"; 27 | }; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /home/foot.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | carburetor.themes.foot.enable = true; 4 | programs.foot = { 5 | enable = true; 6 | settings = { 7 | main = { 8 | font = "Berkeley Mono:size=9"; 9 | dpi-aware = "yes"; 10 | pad = "14x14 center"; 11 | term = "xterm-256color"; 12 | }; 13 | colors.alpha = "0.8"; 14 | mouse.hide-when-typing = "no"; 15 | cursor = { 16 | style = "beam"; 17 | blink = "yes"; 18 | }; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /home/git.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | programs.git = { 4 | enable = true; 5 | 6 | userName = "ozwaldorf"; 7 | userEmail = "self@ossian.dev"; 8 | signing = { 9 | key = "~/.ssh/id_ecdsa.pub"; 10 | signByDefault = true; 11 | }; 12 | extraConfig = { 13 | url."ssh://git@github.com".insteadOf = "https://github.com"; 14 | url."ssh://git@github.com/".insteadOf = "github:"; 15 | gpg.format = "ssh"; 16 | init.defaultBranch = "main"; 17 | core.editor = "nvim"; 18 | core.commentChar = "!"; 19 | }; 20 | ignores = [ 21 | ".envrc" 22 | ".direnv" 23 | "result" 24 | ]; 25 | delta = { 26 | enable = true; 27 | options = { 28 | navigate = true; 29 | side-by-side = true; 30 | true-color = "never"; 31 | 32 | features = "unobtrusive-line-numbers decorations"; 33 | unobtrusive-line-numbers = { 34 | line-numbers = true; 35 | line-numbers-left-format = "{nm:>4}│"; 36 | line-numbers-right-format = "{np:>4}│"; 37 | line-numbers-left-style = "grey"; 38 | line-numbers-right-style = "grey"; 39 | }; 40 | decorations = { 41 | commit-decoration-style = "bold grey box ul"; 42 | file-style = "bold blue"; 43 | file-decoration-style = "ul"; 44 | hunk-header-decoration-style = "box"; 45 | }; 46 | }; 47 | }; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /home/gtk.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | imports = [ ./modules/pointer.nix ]; 4 | 5 | home = { 6 | # Standalone gnome desktop apps 7 | packages = with pkgs; [ 8 | pavucontrol # volume control 9 | wdisplays # display control 10 | gnome-system-monitor # resource monitor 11 | 12 | nautilus # file explorer 13 | simple-scan # document scanner 14 | torrential # torrent manager 15 | 16 | eog # photo viewer 17 | celluloid # video player 18 | evince # document viewer 19 | gnome-characters # character viewer 20 | gnome-font-viewer # font viewer 21 | ]; 22 | 23 | pointerCursorPatch = { 24 | package = pkgs.bibata-cursors; 25 | name = "Bibata-Modern-Classic"; 26 | size = 48; 27 | x11.enable = true; 28 | gtk = { 29 | enable = true; 30 | size = 24; 31 | }; 32 | }; 33 | }; 34 | 35 | gtk.enable = true; 36 | 37 | carburetor.themes.gtk = { 38 | enable = true; 39 | transparency = true; 40 | icon = true; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /home/starship.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | programs.starship = { 4 | enable = true; 5 | settings = { 6 | format = "$directory$all$cmd_duration$jobs$status$shell$line_break$env_var$username$sudo$character"; 7 | right_format = "$battery$time"; 8 | add_newline = true; 9 | character = { 10 | format = "$symbol "; 11 | success_symbol = "[●](bright-green)"; 12 | error_symbol = "[●](red)"; 13 | vicmd_symbol = "[◆](blue)"; 14 | }; 15 | sudo = { 16 | format = "[$symbol]($style)"; 17 | style = "bright-purple"; 18 | symbol = ":"; 19 | disabled = false; 20 | }; 21 | username = { 22 | style_user = "yellow bold"; 23 | style_root = "purple bold"; 24 | format = "[$user]($style) ▻ "; 25 | disabled = false; 26 | show_always = false; 27 | }; 28 | directory = { 29 | home_symbol = "⌂"; 30 | truncation_length = 2; 31 | truncation_symbol = "□ "; 32 | read_only = " △"; 33 | use_os_path_sep = true; 34 | style = "bright-blue"; 35 | }; 36 | git_branch = { 37 | format = "[$symbol $branch(:$remote_branch)]($style) "; 38 | symbol = "[△](green)"; 39 | style = "green"; 40 | }; 41 | git_status = { 42 | format = "($ahead_behind$staged$renamed$modified$untracked$deleted$conflicted$stashed)"; 43 | conflicted = "[◪ ]( bright-magenta)"; 44 | ahead = "[▲ [$count](bold white) ](green)"; 45 | behind = "[▼ [$count](bold white) ](red)"; 46 | diverged = "[◇ [$ahead_count](bold green)/[$behind_count](bold red) ](bright-magenta)"; 47 | untracked = "[○ ](bright-yellow)"; 48 | stashed = "[$count ](bold white)"; 49 | renamed = "[● ](bright-blue)"; 50 | modified = "[● ](yellow)"; 51 | staged = "[● ](bright-cyan)"; 52 | deleted = "[✕ ](red)"; 53 | }; 54 | deno = { 55 | format = "deno [∫ $version](blue ) "; 56 | version_format = "$major.$minor"; 57 | }; 58 | nodejs = { 59 | format = "node [◫ ($version)]( bright-green) "; 60 | detect_files = [ "package.json" ]; 61 | version_format = "$major.$minor"; 62 | }; 63 | rust = { 64 | format = "rs [$symbol$version]($style) "; 65 | symbol = "⊃ "; 66 | version_format = "$major.$minor"; 67 | style = "red"; 68 | }; 69 | package = { 70 | format = "pkg [$symbol$version]($style) "; 71 | version_format = "$major.$minor"; 72 | symbol = "◫ "; 73 | style = "bright-yellow "; 74 | }; 75 | nix_shell = { 76 | symbol = "⊛ "; 77 | format = "nix [$symbol$state $name]($style) "; 78 | }; 79 | }; 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /home/sway.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs, ... }: 2 | { 3 | wayland.windowManager.sway = 4 | let 5 | mod = "Mod4"; 6 | in 7 | { 8 | enable = true; 9 | checkConfig = false; 10 | package = pkgs.swayfx; 11 | config = { 12 | startup = [ 13 | { 14 | command = "ags"; 15 | always = true; 16 | } 17 | { command = "swww init"; } 18 | { command = "gnome-keyring-daemon --start"; } 19 | { command = "systemctl --user start polkit-gnome-authentication-agent-1"; } 20 | ]; 21 | modifier = mod; 22 | keybindings = lib.mkOptionDefault { 23 | "${mod}+Return" = "exec wezterm"; 24 | "${mod}+d" = "exec ags -t applauncher"; 25 | "${mod}+e" = "exec firefox"; 26 | "${mod}+Shift+r" = "reload"; 27 | "${mod}+Shift+e" = "exit"; 28 | "Print" = "exec grimhot copy anything"; 29 | }; 30 | terminal = "wezterm"; 31 | focus.followMouse = true; 32 | gaps = { 33 | outer = 0; 34 | inner = 20; 35 | }; 36 | bars = [ ]; 37 | }; 38 | extraConfig = '' 39 | default_border none 40 | 41 | # Carburetor palette Border BG Text Indicator 42 | client.focused #4589ffFF #4589ffFF #161616 #4589ffFF 43 | client.urgent #FF832BFF #FF832BFF #161616 #FF832BFF 44 | client.focused_inactive #1616167E #1616167E #f4f4f4 #1616167E 45 | client.unfocused #1616167E #1616167E #f4f4f4 #1616167E 46 | 47 | # swayfx stuff 48 | corner_radius 10 49 | blur on 50 | blur_passes 3 51 | blur_radius 8 52 | shadows on 53 | #shadow_offset 0 0 54 | shadow_blur_radius 50 55 | shadow_color #000000FF 56 | #shadow_inactive_color #000000B0 57 | ''; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /home/vesktop/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | flakeDirectory, 5 | ... 6 | }: 7 | { 8 | home.packages = [ 9 | (pkgs.vesktop.override { 10 | withTTS = false; 11 | withSystemVencord = true; 12 | }) 13 | ]; 14 | 15 | carburetor.themes.vesktop.enable = true; 16 | 17 | xdg.configFile = { 18 | # # Install horizontal server list css 19 | # "vesktop/themes/horizontal.css".source = builtins.fetchurl { 20 | # url = "https://betterdiscord.app/Download\?id=124"; 21 | # sha256 = "0c9gmi6axjlk5w1ivdjnx2mz5qr9hc10f55gjmjaxy8lnwc7p9jc"; 22 | # }; 23 | 24 | # Out of store symlink to the configuration, allowing settings changes 25 | # in the UI to reflect in the code. 26 | "vesktop/settings/settings.json".source = 27 | config.lib.file.mkOutOfStoreSymlink flakeDirectory + "/home/vesktop/settings.json"; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /home/wezterm/default.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | programs.wezterm = { 4 | enable = true; 5 | enableZshIntegration = true; 6 | enableBashIntegration = true; 7 | extraConfig = builtins.readFile ./wezterm.lua; 8 | }; 9 | 10 | carburetor.themes.wezterm.enable = true; 11 | } 12 | -------------------------------------------------------------------------------- /home/wezterm/wezterm.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require 'wezterm' 2 | 3 | local tab_colors = { 4 | "Navy", "Red", "Green", "Olive", "Maroon", "Purple", "Teal", "Lime", "Yellow", "Blue", "Fuchsia", "Aqua" 5 | } 6 | local tab_bg = "rgba(22,22,22,0.8)" 7 | 8 | wezterm.on( 9 | 'format-tab-title', 10 | function(tab) 11 | if tab.is_active then 12 | local accent = tab_colors[(tab.tab_index % #tab_colors) + 1] 13 | return wezterm.format({ 14 | { Background = { Color = tab_bg } }, 15 | { Foreground = { AnsiColor = accent } }, 16 | { Text = ' ' .. wezterm.nerdfonts.ple_left_half_circle_thick }, 17 | { Background = { AnsiColor = accent } }, 18 | { Foreground = { Color = tab_bg } }, 19 | { Text = tostring(tab.tab_index) }, 20 | { Background = { Color = tab_bg } }, 21 | { Foreground = { AnsiColor = accent } }, 22 | { Text = wezterm.nerdfonts.ple_right_half_circle_thick }, 23 | }) 24 | else 25 | return ' ' .. tab.tab_index 26 | end 27 | end 28 | ) 29 | 30 | return { 31 | --default_prog = { "nvim", "+terminal", "+startinsert" }, 32 | font = wezterm.font_with_fallback { 33 | 'Berkeley Mono', 34 | --'Dank Mono', 35 | --'Gintronic', 36 | --'PragmataPro Mono Liga', 37 | --'Fira Code Nerd Font', 38 | 'Symbols Nerd Font', 39 | }, 40 | font_size = 10, 41 | window_background_opacity = 0.8, 42 | text_background_opacity = 1.0, 43 | -- window_decorations = "NONE", 44 | window_padding = { 45 | left = '4.5cell', 46 | right = '4.0cell', 47 | top = '2.2cell', 48 | bottom = '2.0cell', 49 | }, 50 | color_scheme = "Carburator", 51 | inactive_pane_hsb = { 52 | saturation = 1.0, 53 | brightness = 1.0, 54 | }, 55 | use_fancy_tab_bar = false, 56 | tab_bar_at_bottom = false, 57 | colors = { 58 | tab_bar = { 59 | active_tab = { bg_color = tab_bg, fg_color = "#f4f4f4" }, 60 | inactive_tab = { bg_color = tab_bg, fg_color = "#f4f4f4" }, 61 | inactive_tab_hover = { bg_color = tab_bg, fg_color = "#f4f4f4", italic = false } 62 | } 63 | }, 64 | tab_bar_style = { 65 | new_tab = "", 66 | new_tab_hover = "" 67 | }, 68 | tab_max_width = 32, 69 | hide_tab_bar_if_only_one_tab = true, 70 | default_cursor_style = 'BlinkingBlock', 71 | warn_about_missing_glyphs = false 72 | } 73 | -------------------------------------------------------------------------------- /home/zed/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | flakeDirectory, 5 | ... 6 | }: 7 | { 8 | home.packages = with pkgs; [ 9 | zed-editor 10 | nixd 11 | ]; 12 | 13 | carburetor.themes.zed.enable = true; 14 | 15 | xdg.configFile."zed/settings.json".source = 16 | config.lib.file.mkOutOfStoreSymlink flakeDirectory + "/home/zed/settings.json"; 17 | } 18 | -------------------------------------------------------------------------------- /home/zed/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "carburetor regular (rosewater) - No Italics", 3 | "ui_font_family": "Berkeley Mono", 4 | "buffer_font_family": "Berkeley Mono", 5 | "buffer_font_features": {}, 6 | "load_direnv": "shell_hook", 7 | "tab_size": 2, 8 | "ui_font_size": 14, 9 | "buffer_font_size": 14, 10 | "vim_mode": true, 11 | "inlay_hints": { 12 | "enabled": true, 13 | "show_type_hints": true, 14 | "show_parameter_hints": true, 15 | "show_other_hints": true, 16 | "edit_debounce_ms": 700, 17 | "scroll_debounce_ms": 50 18 | }, 19 | "git": { 20 | "git_gutter": "tracked_files", 21 | "inline_blame": { 22 | "enabled": true 23 | } 24 | }, 25 | "lsp": { 26 | "bash-language-server": { 27 | "binary": { 28 | "path": "/etc/profiles/per-user/oz/bin/bash-language-server", 29 | "arguments": ["start"] 30 | } 31 | }, 32 | "clangd": { 33 | "binary": { 34 | "path": "/etc/profiles/per-user/oz/bin/clangd", 35 | "arguments": ["--background-index", "--compile-commands-dir=build"] 36 | } 37 | }, 38 | "rust-analyzer": { 39 | "binary": { 40 | "path": "/run/current-system/sw/bin/bash", 41 | "arguments": [ 42 | "-c", 43 | "if [ -e flake.nix ]; then nix develop --command rust-analyzer; else rust-analyzer; fi" 44 | ] 45 | }, 46 | "initialization_options": { 47 | "checkOnSave": { 48 | "command": "clippy" 49 | } 50 | } 51 | }, 52 | "json-language-server": { 53 | "binary": { 54 | "path": "/etc/profiles/per-user/oz/bin/vscode-json-languageserver", 55 | "arguments": ["--stdio"] 56 | } 57 | }, 58 | "nixd": { 59 | "binary": { 60 | "path": "/usr/bin/env", 61 | "arguments": ["nil"] 62 | } 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /home/zsh.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | home.packages = with pkgs; [ 4 | eza 5 | bat 6 | curl 7 | tree 8 | onefetch 9 | (fortune.override { withOffensive = true; }) 10 | ]; 11 | 12 | programs.nix-index = { 13 | enable = true; 14 | enableZshIntegration = true; 15 | }; 16 | 17 | programs.zsh = { 18 | enable = true; 19 | 20 | history = { 21 | save = 1000000; 22 | ignoreAllDups = true; 23 | ignoreSpace = true; 24 | share = true; 25 | }; 26 | 27 | # inline history suggestions 28 | autosuggestion = { 29 | enable = true; 30 | highlight = "fg=244"; 31 | }; 32 | syntaxHighlighting.enable = true; 33 | autocd = true; 34 | enableVteIntegration = true; 35 | enableCompletion = false; # autocomplete will compinit on its own 36 | 37 | plugins = [ 38 | { 39 | name = "zsh-window-title"; 40 | src = pkgs.fetchFromGitHub { 41 | owner = "olets"; 42 | repo = "zsh-window-title"; 43 | rev = "v1.2.0"; 44 | sha256 = "RqJmb+XYK35o+FjUyqGZHD6r1Ku1lmckX41aXtVIUJQ="; 45 | }; 46 | } 47 | { 48 | name = "zsh-autocomplete"; 49 | src = pkgs.zsh-autocomplete; 50 | file = "share/zsh-autocomplete/zsh-autocomplete.plugin.zsh"; 51 | } 52 | { 53 | name = "vi-mode"; 54 | src = pkgs.zsh-vi-mode; 55 | file = "share/zsh-vi-mode/zsh-vi-mode.plugin.zsh"; 56 | } 57 | ]; 58 | 59 | shellAliases = with { ls_args = "--git --icons"; }; { 60 | ls = "eza -lh ${ls_args}"; 61 | la = "eza -lah ${ls_args}"; 62 | l = "eza -lah ${ls_args}"; 63 | lg = "eza -lah ${ls_args} --git-ignore"; 64 | cat = "bat"; 65 | cp = "cp -i"; # Confirm before overwriting something 66 | df = "df -h"; # Human-readable sizes 67 | free = "free -m"; # Show sizes in MB 68 | clip = "wl-copy"; 69 | curl = "curl -s"; 70 | commit = "git commit"; 71 | add = "git add"; 72 | rebase = "git stash && git pull --rebase && git stash pop"; 73 | ytop = "ytop -spfa"; 74 | n = "nvim"; 75 | c = "cargo"; 76 | rs = "rust-script"; 77 | wq = "exit"; 78 | icat = "wezterm imgcat"; 79 | fuck = "thefuck"; 80 | switch = "sudo nixos-rebuild switch"; 81 | }; 82 | 83 | sessionVariables = { 84 | # Let's break up words more 85 | WORDCHARS = "*?[]~=&;!#$%^(){}<>"; 86 | PATH = "$HOME/.deno/bin:$PATH"; 87 | }; 88 | 89 | initExtraFirst = '' 90 | ## Options section 91 | setopt interactive_comments # Enable autocomplete 92 | setopt complete_aliases # Complete aliased commands 93 | setopt correct # Auto correct mistakes 94 | setopt extendedglob # Extended globbing. Allows using regular expressions with * 95 | setopt nocaseglob # Case insensitive globbing 96 | setopt rcexpandparam # Array expension with parameters 97 | setopt nocheckjobs # Don't warn about running processes when exiting 98 | setopt numericglobsort # Sort filenames numerically when it makes sense 99 | 100 | # autocompletions config 101 | zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # Case insensitive tab completion 102 | zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}" # Colored completion (different colors for dirs/files/etc) 103 | zstyle ':completion:*' rehash true # automatically find new executables in path 104 | zstyle ':completion::complete:*' gain-privileges 1 105 | zstyle -e ':autocomplete:*:*' list-lines 'reply=( $(( LINES / 3 )) )' 106 | 107 | # Cycle/enter autocomplete with tab 108 | bindkey '^I' menu-select 109 | bindkey "$terminfo[kcbt]" menu-select 110 | 111 | # Navigate words 112 | bindkey '^[[1;5D' backward-word # Ctrl + right key 113 | bindkey '^[[1;5C' forward-word # Ctrl + left key 114 | bindkey '^H' backward-kill-word # delete previous word with ctrl+backspace 115 | # bindkey '^[[Z' undo # Shift+tab undo last action 116 | 117 | function nixpkgs() { 118 | NIXPKGS_ALLOW_UNFREE=1 nix shell "''${@/#/nixpkgs#}" 119 | } 120 | 121 | function nixpkg() { 122 | PKG="$1"; shift 123 | NIXPKGS_ALLOW_UNFREE=1 nix run "nixpkgs#$PKG" -- $@ 124 | } 125 | 126 | 127 | # foot integration 128 | function osc7-pwd() { 129 | emulate -L zsh # also sets localoptions for us 130 | setopt extendedglob 131 | local LC_ALL=C 132 | printf '\e]7;file://%s%s\e\' $HOST ''${PWD//(#m)([^@-Za-z&-;_~])/%''${(l:2::0:)$(([##16]#MATCH))}} 133 | } 134 | function chpwd-osc7-pwd() { 135 | (( ZSH_SUBSHELL )) || osc7-pwd 136 | } 137 | add-zsh-hook -Uz chpwd chpwd-osc7-pwd 138 | ''; 139 | 140 | initExtra = '' 141 | fortune -s 142 | ''; 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /pkgs/default.nix: -------------------------------------------------------------------------------- 1 | inputs: final: prev: { 2 | webcord = import ./webcord.nix prev; 3 | # xq = import ./xq.nix prev; 4 | 5 | neovim = import ./neovim.nix { 6 | inherit inputs; 7 | pkgs = prev; 8 | }; 9 | standalone = import ./standalone.nix { 10 | inherit inputs; 11 | pkgs = final; 12 | }; 13 | 14 | snd-hda-intel = prev.callPackage (import ./snd-hda-intel.nix) { }; 15 | } 16 | -------------------------------------------------------------------------------- /pkgs/gimp-devel/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | pkgs.callPackage ( 3 | { 4 | stdenv, 5 | lib, 6 | aalib, 7 | alsa-lib, 8 | appstream, 9 | appstream-glib, 10 | babl, 11 | bashInteractive, 12 | cairo, 13 | desktop-file-utils, 14 | fetchurl, 15 | findutils, 16 | gdk-pixbuf, 17 | gegl, 18 | gexiv2, 19 | ghostscript, 20 | gi-docgen, 21 | gjs, 22 | glib, 23 | glib-networking, 24 | gobject-introspection, 25 | gtk3, 26 | isocodes, 27 | lcms, 28 | libarchive, 29 | libgudev, 30 | libheif, 31 | libjxl, 32 | libmng, 33 | libmypaint, 34 | librsvg, 35 | libwebp, 36 | libwmf, 37 | libxslt, 38 | luajit, 39 | meson, 40 | qoi, 41 | cfitsio, 42 | mypaint-brushes1, 43 | ninja, 44 | openexr, 45 | perl538, 46 | pkg-config, 47 | poppler, 48 | poppler_data, 49 | python3, 50 | shared-mime-info, 51 | vala, 52 | wrapGAppsHook, 53 | xorg, 54 | xvfb-run, 55 | 56 | }: 57 | let 58 | python = python3.withPackages (pp: [ pp.pygobject3 ]); 59 | lua = luajit.withPackages (ps: [ ps.lgi ]); 60 | in 61 | stdenv.mkDerivation (finalAttrs: { 62 | pname = "gimp"; 63 | version = "2.99.18"; 64 | 65 | outputs = [ 66 | "out" 67 | "dev" 68 | ]; 69 | 70 | src = fetchurl { 71 | url = "http://download.gimp.org/pub/gimp/v${lib.versions.majorMinor finalAttrs.version}/gimp-${finalAttrs.version}.tar.xz"; 72 | hash = "sha256-jBu3qUrA1NDN5NcB2LNWOHwuzYervTW799Ii1A9t224="; 73 | }; 74 | 75 | patches = [ 76 | ./meson-gtls.diff 77 | ./pygimp-interp.diff 78 | ]; 79 | 80 | nativeBuildInputs = [ 81 | pkg-config 82 | libxslt 83 | ghostscript 84 | libarchive 85 | bashInteractive 86 | libheif 87 | libwebp 88 | libmng 89 | aalib 90 | libjxl 91 | isocodes 92 | perl538 93 | appstream 94 | meson 95 | xvfb-run 96 | gi-docgen 97 | findutils 98 | vala 99 | alsa-lib 100 | ninja 101 | wrapGAppsHook 102 | ]; 103 | 104 | buildInputs = [ 105 | gjs 106 | lua 107 | qoi 108 | babl 109 | appstream-glib 110 | gegl 111 | gtk3 112 | glib 113 | gdk-pixbuf 114 | cairo 115 | gexiv2 116 | lcms 117 | libjxl 118 | cfitsio 119 | poppler 120 | poppler_data 121 | openexr 122 | libmng 123 | librsvg 124 | desktop-file-utils 125 | libwmf 126 | ghostscript 127 | aalib 128 | shared-mime-info 129 | libwebp 130 | libheif 131 | xorg.libXpm 132 | xorg.libXmu 133 | glib-networking 134 | libmypaint 135 | mypaint-brushes1 136 | gobject-introspection 137 | python 138 | libgudev 139 | ]; 140 | 141 | preConfigure = "patchShebangs tools/gimp-mkenums app/tests/create_test_env.sh plug-ins/script-fu/scripts/ts-helloworld.scm"; 142 | 143 | mesonFlags = [ "-Dilbm=disabled" ]; 144 | 145 | enableParallelBuilding = true; 146 | 147 | doCheck = false; 148 | 149 | meta = with lib; { 150 | description = "The GNU Image Manipulation Program: Development Edition"; 151 | homepage = "https://www.gimp.org/"; 152 | license = licenses.gpl3Plus; 153 | platforms = platforms.unix; 154 | mainProgram = "gimp"; 155 | }; 156 | }) 157 | ) { } 158 | -------------------------------------------------------------------------------- /pkgs/gimp-devel/meson-gtls.diff: -------------------------------------------------------------------------------- 1 | --- a/meson.build 2 | +++ b/meson.build 3 | @@ -437,7 +437,7 @@ 4 | glib_networking_works_run = cc.run( 5 | '''#include 6 | int main() { 7 | - return !g_tls_backend_supports_tls (g_tls_backend_get_default ()); 8 | + return g_tls_backend_supports_tls (g_tls_backend_get_default ()); 9 | }''', 10 | dependencies: gio, 11 | ) 12 | -------------------------------------------------------------------------------- /pkgs/gimp-devel/pygimp-interp.diff: -------------------------------------------------------------------------------- 1 | --- a/plug-ins/python/pygimp.interp.in 2 | +++ b/plug-ins/python/pygimp.interp.in 3 | @@ -2,4 +2,4 @@ 4 | python3=@PYTHON_PATH@python.exe 5 | /usr/bin/python=@PYTHON_PATH@python.exe 6 | /usr/bin/python3=@PYTHON_PATH@python.exe 7 | +:Python:E::py::@PYTHON_PATH@: 8 | -:Python:E::py::python3: 9 | -------------------------------------------------------------------------------- /pkgs/snd-hda-intel.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | kernel ? pkgs.linuxPackages_latest.kernel, 5 | patches ? [ ], 6 | }: 7 | 8 | # Build an in-tree kernel module (based of amdgpu) 9 | pkgs.stdenv.mkDerivation { 10 | inherit patches; 11 | pname = "snd-hda-intel"; 12 | modulePath = "sound/pci/hda"; 13 | 14 | inherit (kernel) 15 | src 16 | version 17 | postPatch 18 | nativeBuildInputs 19 | ; 20 | kernel_dev = kernel.dev; 21 | kernelVersion = kernel.modDirVersion; 22 | buildPhase = '' 23 | BUILT_KERNEL=$kernel_dev/lib/modules/$kernelVersion/build 24 | cp $BUILT_KERNEL/Module.symvers . 25 | cp $BUILT_KERNEL/.config . 26 | cp $kernel_dev/vmlinux . 27 | make "-j$NIX_BUILD_CORES" modules_prepare 28 | make "-j$NIX_BUILD_CORES" M=$modulePath modules 29 | ''; 30 | installPhase = '' 31 | make \ 32 | INSTALL_MOD_PATH="$out" \ 33 | XZ="xz -T$NIX_BUILD_CORES" \ 34 | M="$modulePath" \ 35 | modules_install 36 | ''; 37 | meta = { 38 | description = "snd-hda-intel standalone kernel module with patches exposed"; 39 | license = lib.licenses.gpl3; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /pkgs/standalone.nix: -------------------------------------------------------------------------------- 1 | { inputs, pkgs }: 2 | let 3 | username = "standalone"; 4 | homeDirectory = "/tmp/${username}"; 5 | module = inputs.home-manager.lib.homeManagerConfiguration { 6 | inherit pkgs; 7 | modules = [ 8 | { 9 | home = { 10 | inherit username homeDirectory; 11 | stateVersion = "24.05"; 12 | packages = with pkgs; [ 13 | neovim 14 | xq 15 | bottom 16 | ]; 17 | }; 18 | } 19 | ../home/zsh.nix 20 | ../home/starship.nix 21 | ]; 22 | }; 23 | in 24 | pkgs.symlinkJoin { 25 | name = "standalone"; 26 | paths = [ 27 | module.config.home-files 28 | module.config.home.packages 29 | module.config.home.activationPackage # debug 30 | ]; 31 | buildInputs = with pkgs; [ makeWrapper ]; 32 | postBuild = '' 33 | # Wrap starship with the generated config 34 | wrapProgram $out/bin/starship \ 35 | --set STARSHIP_CONFIG "$out/.config/starship.toml" 36 | 37 | # Wrap zsh with the generated config 38 | wrapProgram $out/bin/zsh \ 39 | --run " 40 | # Setup .nix-profile/ 41 | rm -rf ${homeDirectory} 42 | mkdir -p ${homeDirectory}/.nix-profile 43 | ln -s $out ${homeDirectory}/.nix-profile 44 | ln -s $out/bin ${homeDirectory}/.nix-profile 45 | ln -s $out/sbin ${homeDirectory}/.nix-profile 46 | ln -s $out/lib ${homeDirectory}/.nix-profile 47 | ln -s $out/libexec ${homeDirectory}/.nix-profile 48 | ln -s $out/etc ${homeDirectory}/.nix-profile 49 | export PATH="$out/bin:\$PATH" 50 | " --set ZDOTDIR "$out" 51 | ''; 52 | } 53 | -------------------------------------------------------------------------------- /pkgs/webcord.nix: -------------------------------------------------------------------------------- 1 | pkgs: 2 | 3 | pkgs.webcord.overrideAttrs (old: { 4 | patches = old.patches or [ ] ++ [ 5 | # Automatically connect to the local arRPC bridge on startup 6 | (pkgs.writeText "arrpc_bridge.patch" '' 7 | --- a/sources/code/main/windows/main.ts 8 | +++ b/sources/code/main/windows/main.ts 9 | @@ -402,6 +402,87 @@ export default function createMainWindow(...flags:MainWindowFlags): BrowserWindo 10 | return media(constrains); 11 | }]); 12 | Object.defineProperty(navigator.mediaDevices.getUserMedia, "name", {value: "getUserMedia"}); 13 | + 14 | + // connect to arRPC bridge websocket 15 | + let Dispatcher, lookupAsset, lookupApp, apps = {}; 16 | + const ws = new WebSocket('ws://127.0.0.1:1337'); 17 | + console.log("[arRPC] Connected to websocket!"); 18 | + 19 | + ws.onmessage = async x => { 20 | + console.log("[arRPC] Received message: " + x.data); 21 | + msg = JSON.parse(x.data); 22 | + 23 | + if (!Dispatcher) { 24 | + let wpRequire; 25 | + window.webpackChunkdiscord_app.push([[ Symbol() ], {}, x => wpRequire = x]); 26 | + window.webpackChunkdiscord_app.pop(); 27 | + 28 | + const modules = wpRequire.c; 29 | + 30 | + for (const id in modules) { 31 | + const mod = modules[id].exports; 32 | + if (!mod?.__esModule) continue; 33 | + 34 | + for (const prop in mod) { 35 | + if (!mod.hasOwnProperty(prop)) continue; 36 | + 37 | + const candidate = mod[prop]; 38 | + if (candidate && candidate.register && candidate.wait) { 39 | + Dispatcher = candidate; 40 | + break; 41 | + } 42 | + } 43 | + 44 | + if (Dispatcher) break; 45 | + } 46 | + 47 | + const factories = wpRequire.m; 48 | + for (const id in factories) { 49 | + if (factories[id].toString().includes('getAssetImage: size must === [number, number] for Twitch')) { 50 | + const mod = wpRequire(id); 51 | + 52 | + // fetchAssetIds 53 | + const _lookupAsset = Object.values(mod).find(e => typeof e === 'function' && e.toString().includes('APPLICATION_ASSETS_FETCH_SUCCESS')); 54 | + if (_lookupAsset) lookupAsset = async (appId, name) => (await _lookupAsset(appId, [ name, undefined ]))[0]; 55 | + } 56 | + 57 | + if (lookupAsset) break; 58 | + } 59 | + 60 | + for (const id in factories) { 61 | + if (factories[id].toString().includes('APPLICATION_RPC(')) { 62 | + const mod = wpRequire(id); 63 | + 64 | + // fetchApplicationsRPC 65 | + const _lookupApp = Object.values(mod).find(e => { 66 | + if (typeof e !== 'function') return; 67 | + const str = e.toString(); 68 | + return str.includes(',coverImage:') && str.includes('INVALID_ORIGIN'); 69 | + }); 70 | + if (_lookupApp) lookupApp = async appId => { 71 | + let socket = {}; 72 | + await _lookupApp(socket, appId); 73 | + return socket.application; 74 | + }; 75 | + } 76 | + 77 | + if (lookupApp) break; 78 | + } 79 | + } 80 | + 81 | + if (msg.activity?.assets?.large_image) msg.activity.assets.large_image = await lookupAsset(msg.activity.application_id, msg.activity.assets.large_image); 82 | + if (msg.activity?.assets?.small_image) msg.activity.assets.small_image = await lookupAsset(msg.activity.application_id, msg.activity.assets.small_image); 83 | + 84 | + if (msg.activity) { 85 | + const appId = msg.activity.application_id; 86 | + if (!apps[appId]) apps[appId] = await lookupApp(appId); 87 | + 88 | + const app = apps[appId]; 89 | + if (!msg.activity.name) msg.activity.name = app.name; 90 | + } 91 | + 92 | + Dispatcher.dispatch({ type: 'LOCAL_ACTIVITY_UPDATE', ...msg }); // set RPC status 93 | + }; 94 | }`; 95 | win.webContents.executeJavaScript(`''${functionString};0`) 96 | .then(() => internalWindowEvents.emit("api", api.replaceAll("'","\\'"))) 97 | '') 98 | ]; 99 | }) 100 | -------------------------------------------------------------------------------- /pkgs/xq.nix: -------------------------------------------------------------------------------- 1 | pkgs: 2 | 3 | # Use my fork with toml and QoL features 4 | pkgs.xq.overrideAttrs (old: rec { 5 | version = "0-unstable-2024-10-22"; 6 | src = pkgs.fetchFromGitHub { 7 | owner = "ozwaldorf"; 8 | repo = "xq"; 9 | rev = "266ff0a80f1d5dd1a8f03c3cb70e84984d9a8664"; 10 | hash = "sha256-iUxb0j6eZ/WdiwOCVS4sn4VXQrwJDQVzFjaxqPza+10="; 11 | }; 12 | cargoDeps = old.cargoDeps.overrideAttrs (_: { 13 | inherit src; 14 | outputHash = "sha256-eHJDtr3sy3WQUYaVEkrssVtEOGZCAX9/VwxP16RtglU="; 15 | }); 16 | }) 17 | -------------------------------------------------------------------------------- /system/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | # Do not modify this file! It was generated by ‘nixos-generate-config’ 2 | # and may be overwritten by future invocations. Please make changes 3 | # to /etc/nixos/configuration.nix instead. 4 | { 5 | config, 6 | lib, 7 | pkgs, 8 | modulesPath, 9 | ... 10 | }: 11 | 12 | { 13 | imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; 14 | 15 | boot.initrd.availableKernelModules = [ 16 | "xhci_pci" 17 | "thunderbolt" 18 | "nvme" 19 | "usbhid" 20 | "usb_storage" 21 | "sd_mod" 22 | "rtsx_pci_sdmmc" 23 | ]; 24 | boot.initrd.kernelModules = [ ]; 25 | boot.kernelModules = [ "kvm-intel" ]; 26 | boot.extraModulePackages = [ ]; 27 | 28 | fileSystems."/" = { 29 | device = "/dev/disk/by-uuid/e6ba1b37-10d1-44af-b4b7-57fd0752f1b7"; 30 | fsType = "ext4"; 31 | }; 32 | 33 | boot.initrd.luks.devices."luks-f0d4830a-a4c2-4863-8966-0f3944d1feb3".device = "/dev/disk/by-uuid/f0d4830a-a4c2-4863-8966-0f3944d1feb3"; 34 | 35 | fileSystems."/boot" = { 36 | device = "/dev/disk/by-uuid/2EB6-7A04"; 37 | fsType = "vfat"; 38 | }; 39 | 40 | swapDevices = [ ]; 41 | 42 | # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 43 | # (the default) this is the recommended approach. When using systemd-networkd it's 44 | # still possible to use this option, but it's recommended to use it in conjunction 45 | # with explicit per-interface declarations with `networking.interfaces..useDHCP`. 46 | networking.useDHCP = lib.mkDefault true; 47 | # networking.interfaces.enp0s20f0u4.useDHCP = lib.mkDefault true; 48 | # networking.interfaces.enp0s20f0u6u4c2.useDHCP = lib.mkDefault true; 49 | 50 | nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 51 | hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 52 | } 53 | --------------------------------------------------------------------------------