added stuff
This commit is contained in:
parent
937f28770d
commit
236b8c2a6b
907 changed files with 70990 additions and 0 deletions
7
nyx/homes/notashelf/services/default.nix
Normal file
7
nyx/homes/notashelf/services/default.nix
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
imports = [
|
||||
./wayland # services that are wayland-only
|
||||
./x11 # services that are x11-only
|
||||
./shared # services that should be enabled regardless
|
||||
];
|
||||
}
|
14
nyx/homes/notashelf/services/shared/default.nix
Normal file
14
nyx/homes/notashelf/services/shared/default.nix
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
imports = [
|
||||
./dunst
|
||||
./media
|
||||
|
||||
./kdeconnect.nix
|
||||
./mail.nix
|
||||
./nextcloud.nix
|
||||
./polkit.nix
|
||||
./transience.nix
|
||||
./tray.nix
|
||||
./udiskie.nix
|
||||
];
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#95AEC7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v2"></path>
|
||||
<path d="M12 20v2"></path>
|
||||
<path d="m4.93 4.93 1.41 1.41"></path>
|
||||
<path d="m17.66 17.66 1.41 1.41"></path>
|
||||
<path d="M2 12h2"></path>
|
||||
<path d="M20 12h2"></path>
|
||||
<path d="m6.34 17.66-1.41 1.41"></path>
|
||||
<path d="m19.07 4.93-1.41 1.41"></path>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 508 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#95AEC7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic-off"><line x1="1" y1="1" x2="23" y2="23"></line><path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6"></path><path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
|
After Width: | Height: | Size: 489 B |
1
nyx/homes/notashelf/services/shared/dunst/assets/mic.svg
Normal file
1
nyx/homes/notashelf/services/shared/dunst/assets/mic.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#95AEC7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
|
After Width: | Height: | Size: 413 B |
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="192"
|
||||
height="192"
|
||||
fill="#616e88"
|
||||
viewBox="0 0 256 256"
|
||||
version="1.1"
|
||||
id="svg891"
|
||||
sodipodi:docname="notification.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs895" />
|
||||
<sodipodi:namedview
|
||||
id="namedview893"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4375"
|
||||
inkscape:cx="95.652174"
|
||||
inkscape:cy="75.826087"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="728"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="40"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg891" />
|
||||
<g
|
||||
id="g979"
|
||||
transform="matrix(0.66642001,0,0,0.66847835,45.341226,50.623186)">
|
||||
<path
|
||||
d="m 208,128 v 80 a 8,8 0 0 1 -8,8 H 48 a 8,8 0 0 1 -8,-8 V 56 a 8,8 0 0 1 8,-8 h 80"
|
||||
fill="none"
|
||||
stroke="#616e88"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="16"
|
||||
id="path887" />
|
||||
<circle
|
||||
cx="196"
|
||||
cy="60"
|
||||
r="28"
|
||||
fill="none"
|
||||
stroke="#616e88"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="16"
|
||||
id="circle889" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#95AEC7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-x"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line></svg>
|
After Width: | Height: | Size: 365 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#95AEC7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
|
After Width: | Height: | Size: 354 B |
100
nyx/homes/notashelf/services/shared/dunst/default.nix
Normal file
100
nyx/homes/notashelf/services/shared/dunst/default.nix
Normal file
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
osConfig,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf;
|
||||
inherit (osConfig) modules;
|
||||
inherit (modules.style.colorScheme) colors;
|
||||
|
||||
dev = modules.device;
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
config = mkIf (builtins.elem dev.type acceptedTypes) {
|
||||
services.dunst = {
|
||||
enable = true;
|
||||
iconTheme = {
|
||||
package = config.gtk.iconTheme.package;
|
||||
name = "Papirus-Dark";
|
||||
};
|
||||
|
||||
settings = {
|
||||
global = {
|
||||
follow = "mouse";
|
||||
width = 320;
|
||||
height = 280;
|
||||
origin = "top-center";
|
||||
alignment = "left";
|
||||
vertical_alignment = "center";
|
||||
ellipsize = "middle";
|
||||
offset = "17x17";
|
||||
padding = 15;
|
||||
horizontal_padding = 15;
|
||||
text_icon_padding = 15;
|
||||
icon_position = "left";
|
||||
min_icon_size = 48;
|
||||
max_icon_size = 64;
|
||||
progress_bar = true;
|
||||
progress_bar_height = 8;
|
||||
progress_bar_frame_width = 1;
|
||||
progress_bar_min_width = 150;
|
||||
progress_bar_max_width = 300;
|
||||
separator_height = 2;
|
||||
frame_width = 2;
|
||||
frame_color = "#${colors.base0E}";
|
||||
separator_color = "frame";
|
||||
corner_radius = 8;
|
||||
transparency = 0;
|
||||
gap_size = 8;
|
||||
line_height = 0;
|
||||
notification_limit = 3;
|
||||
idle_threshold = 120;
|
||||
history_length = 20;
|
||||
show_age_threshold = 60;
|
||||
markup = "full";
|
||||
font = "Iosevka 16";
|
||||
word_wrap = "yes";
|
||||
sort = "yes";
|
||||
shrink = "no";
|
||||
indicate_hidden = "yes";
|
||||
sticky_history = "yes";
|
||||
ignore_newline = "no";
|
||||
show_indicators = "no";
|
||||
stack_duplicates = true;
|
||||
always_run_script = true;
|
||||
hide_duplicate_count = false;
|
||||
ignore_dbusclose = false;
|
||||
force_xwayland = false;
|
||||
force_xinerama = false;
|
||||
mouse_left_click = "do_action";
|
||||
mouse_middle_click = "close_all";
|
||||
mouse_right_click = "close_current";
|
||||
};
|
||||
|
||||
fullscreen_delay_everything = {fullscreen = "delay";};
|
||||
|
||||
urgency_low = {
|
||||
timeout = 3;
|
||||
background = "#${colors.base02}";
|
||||
foreground = "#${colors.base05}";
|
||||
highlight = "#${colors.base0C}";
|
||||
};
|
||||
|
||||
urgency_normal = {
|
||||
timeout = 7;
|
||||
background = "#${colors.base02}";
|
||||
foreground = "#${colors.base05}";
|
||||
highlight = "#${colors.base0C}";
|
||||
};
|
||||
|
||||
urgency_critical = {
|
||||
timeout = 0;
|
||||
background = "#${colors.base02}";
|
||||
foreground = "#${colors.base05}";
|
||||
highlight = "#${colors.base08}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
6
nyx/homes/notashelf/services/shared/kdeconnect.nix
Normal file
6
nyx/homes/notashelf/services/shared/kdeconnect.nix
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
services.kdeconnect = {
|
||||
enable = false;
|
||||
indicator = true;
|
||||
};
|
||||
}
|
117
nyx/homes/notashelf/services/shared/mail.nix
Normal file
117
nyx/homes/notashelf/services/shared/mail.nix
Normal file
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
osConfig,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
config = let
|
||||
extraMailboxes = ["Archive" "Drafts" "Junk" "Sent" "Trash"];
|
||||
in {
|
||||
accounts.email = {
|
||||
maildirBasePath = config.xdg.userDirs.extraConfig.XDG_MAIL_DIR;
|
||||
accounts = {
|
||||
notashelf = let
|
||||
gpgKey = "0xBA46BCC36E912922";
|
||||
name = "NotAShelf";
|
||||
in {
|
||||
primary = true;
|
||||
address = "raf@notashelf.dev";
|
||||
aliases = ["me@notashelf.dev"];
|
||||
userName = "raf@notashelf.dev";
|
||||
realName = name; # very real, I know
|
||||
passwordCommand = ''
|
||||
# this is a really bad way of getting the password
|
||||
# but home-manager does not provide a passwordFile option
|
||||
tail ${osConfig.age.secrets.client-email.path} -n 1
|
||||
'';
|
||||
|
||||
signature = {
|
||||
showSignature = "append";
|
||||
text = ''
|
||||
--
|
||||
${name}
|
||||
|
||||
Want to use GPG encryption with me? Try my GPG key:
|
||||
[${gpgKey}](https://github.com/notashelf.gpg)
|
||||
'';
|
||||
};
|
||||
|
||||
folders = {
|
||||
inbox = "Inbox";
|
||||
drafts = "Drafts";
|
||||
sent = "Sent";
|
||||
trash = "Trash";
|
||||
};
|
||||
|
||||
imap = {
|
||||
host = "notashelf.dev";
|
||||
tls.enable = true;
|
||||
};
|
||||
|
||||
smtp = {
|
||||
host = "notashelf.dev";
|
||||
tls.enable = true;
|
||||
};
|
||||
|
||||
gpg = {
|
||||
key = gpgKey;
|
||||
signByDefault = true;
|
||||
};
|
||||
|
||||
msmtp.enable = true;
|
||||
mbsync = {
|
||||
enable = true;
|
||||
create = "maildir"; # funny as it is, this is not a path - it's an instruction
|
||||
expunge = "both";
|
||||
};
|
||||
|
||||
neomutt = {
|
||||
enable = true;
|
||||
inherit extraMailboxes;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user = {
|
||||
timers."mbsync" = {
|
||||
Unit.Description = "Automatic mbsync synchronization";
|
||||
Timer = {
|
||||
OnBootSec = "30";
|
||||
OnUnitActiveSec = "5m";
|
||||
};
|
||||
Install.WantedBy = ["timers.target"];
|
||||
};
|
||||
|
||||
services."genFolders" = {
|
||||
Unit.Description = "Generate folders for email accounts";
|
||||
Install.WantedBy = ["multi-user.target"];
|
||||
Service = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = let
|
||||
script = pkgs.writeShellApplication {
|
||||
name = "genFolders";
|
||||
text = ''
|
||||
# move to the current user's home directory
|
||||
# FIXME: pretty sure this can also be set as the service's runtime dir
|
||||
cd "${config.home.homeDirectory}/Mail"
|
||||
|
||||
# iterate over dirs and create those that do not exist
|
||||
for dir in ${toString extraMailboxes}; do
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo -en "$dir does not exist, creating...\n";
|
||||
mkdir "$dir"
|
||||
echo "Done creating $dir"
|
||||
fi
|
||||
done
|
||||
'';
|
||||
};
|
||||
in "${script}/bin/genFolders";
|
||||
|
||||
# set runtime dir to the current user's home directory
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
9
nyx/homes/notashelf/services/shared/media/default.nix
Normal file
9
nyx/homes/notashelf/services/shared/media/default.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
imports = [
|
||||
./easyeffects
|
||||
./mpd
|
||||
|
||||
./noisetorch.nix
|
||||
./spotifyd.nix
|
||||
];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
osConfig,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.modules) mkIf;
|
||||
|
||||
dev = osConfig.modules.device;
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
config = mkIf (builtins.elem dev.type acceptedTypes) {
|
||||
services.easyeffects = {
|
||||
enable = true;
|
||||
preset = "quiet";
|
||||
};
|
||||
|
||||
xdg.configFile."easyeffects/output/quiet.json".source = ./quiet.json;
|
||||
};
|
||||
}
|
258
nyx/homes/notashelf/services/shared/media/easyeffects/quiet.json
Normal file
258
nyx/homes/notashelf/services/shared/media/easyeffects/quiet.json
Normal file
|
@ -0,0 +1,258 @@
|
|||
{
|
||||
"output": {
|
||||
"blocklist": ["mpd", "mpd.pipewire"],
|
||||
"compressor#0": {
|
||||
"attack": 19.969999999999995,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"bypass": false,
|
||||
"dry": -100.0,
|
||||
"hpf-frequency": 10.0,
|
||||
"hpf-mode": "off",
|
||||
"input-gain": 14.9,
|
||||
"knee": -6.499999999999998,
|
||||
"lpf-frequency": 20000.0,
|
||||
"lpf-mode": "off",
|
||||
"makeup": 0.0,
|
||||
"mode": "Upward",
|
||||
"output-gain": -8.3,
|
||||
"ratio": 3.9400000000000013,
|
||||
"release": 100.0,
|
||||
"release-threshold": -100.0,
|
||||
"sidechain": {
|
||||
"lookahead": 0.0,
|
||||
"mode": "RMS",
|
||||
"preamp": 0.0,
|
||||
"reactivity": 10.0,
|
||||
"source": "Middle",
|
||||
"type": "Feed-forward"
|
||||
},
|
||||
"threshold": -12.0,
|
||||
"wet": 0.0
|
||||
},
|
||||
"multiband_compressor#0": {
|
||||
"band0": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 500.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 10.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false
|
||||
},
|
||||
"band1": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": true,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 1000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 500.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 500.0
|
||||
},
|
||||
"band2": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": true,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 2000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 1000.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 1000.0
|
||||
},
|
||||
"band3": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": true,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 4000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 2000.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 2000.0
|
||||
},
|
||||
"band4": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": false,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 8000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 4000.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 4000.0
|
||||
},
|
||||
"band5": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": false,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 12000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 8000.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 8000.0
|
||||
},
|
||||
"band6": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": false,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 16000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 12000.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 12000.0
|
||||
},
|
||||
"band7": {
|
||||
"attack-threshold": -12.0,
|
||||
"attack-time": 20.0,
|
||||
"boost-amount": 6.0,
|
||||
"boost-threshold": -72.0,
|
||||
"compression-mode": "Downward",
|
||||
"compressor-enable": true,
|
||||
"enable-band": false,
|
||||
"external-sidechain": false,
|
||||
"knee": -6.0,
|
||||
"makeup": 0.0,
|
||||
"mute": false,
|
||||
"ratio": 1.0,
|
||||
"release-threshold": -100.0,
|
||||
"release-time": 100.0,
|
||||
"sidechain-custom-highcut-filter": false,
|
||||
"sidechain-custom-lowcut-filter": false,
|
||||
"sidechain-highcut-frequency": 20000.0,
|
||||
"sidechain-lookahead": 0.0,
|
||||
"sidechain-lowcut-frequency": 16000.0,
|
||||
"sidechain-mode": "RMS",
|
||||
"sidechain-preamp": 0.0,
|
||||
"sidechain-reactivity": 10.0,
|
||||
"sidechain-source": "Middle",
|
||||
"solo": false,
|
||||
"split-frequency": 16000.0
|
||||
},
|
||||
"bypass": true,
|
||||
"compressor-mode": "Modern",
|
||||
"dry": -100.0,
|
||||
"envelope-boost": "None",
|
||||
"input-gain": 19.2,
|
||||
"output-gain": -0.8,
|
||||
"wet": 0.0
|
||||
},
|
||||
"plugins_order": ["multiband_compressor#0", "compressor#0"]
|
||||
}
|
||||
}
|
111
nyx/homes/notashelf/services/shared/media/mpd/default.nix
Normal file
111
nyx/homes/notashelf/services/shared/media/mpd/default.nix
Normal file
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
osConfig,
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.modules) mkIf;
|
||||
inherit (osConfig) modules;
|
||||
|
||||
env = modules.usrEnv;
|
||||
srv = env.services;
|
||||
in {
|
||||
config = mkIf srv.media.mpd.enable {
|
||||
home.packages = with pkgs; [
|
||||
playerctl # CLI interface for playerctld
|
||||
mpc_cli # CLI interface for mpd
|
||||
cava # CLI music visualizer (cavalier is a gui alternative)
|
||||
];
|
||||
|
||||
services = {
|
||||
playerctld.enable = true;
|
||||
mpris-proxy.enable = true;
|
||||
mpd-mpris.enable = true;
|
||||
|
||||
# music player daemon service
|
||||
mpd = {
|
||||
enable = true;
|
||||
musicDirectory = "${config.home.homeDirectory}/Media/Music";
|
||||
network = {
|
||||
startWhenNeeded = true;
|
||||
listenAddress = "127.0.0.1";
|
||||
port = 6600;
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
auto_update "yes"
|
||||
volume_normalization "yes"
|
||||
restore_paused "yes"
|
||||
filesystem_charset "UTF-8"
|
||||
|
||||
audio_output {
|
||||
type "pipewire"
|
||||
name "PipeWire"
|
||||
}
|
||||
|
||||
audio_output {
|
||||
type "fifo"
|
||||
name "Visualiser"
|
||||
path "/tmp/mpd.fifo"
|
||||
format "44100:16:2"
|
||||
}
|
||||
|
||||
audio_output {
|
||||
type "httpd"
|
||||
name "lossless"
|
||||
encoder "flac"
|
||||
port "8000"
|
||||
max_clients "8"
|
||||
mixer_type "software"
|
||||
format "44100:16:2"
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
# MPRIS 2 support to mpd
|
||||
mpdris2 = {
|
||||
enable = true;
|
||||
notifications = true;
|
||||
multimediaKeys = true;
|
||||
mpd = {
|
||||
# for some reason config.xdg.userDirs.music is not a "path" - possibly because it has $HOME in its name?
|
||||
inherit (config.services.mpd) musicDirectory;
|
||||
};
|
||||
};
|
||||
|
||||
# discord rich presence for mpd
|
||||
mpd-discord-rpc = {
|
||||
enable = true;
|
||||
settings = {
|
||||
format = {
|
||||
details = "$title";
|
||||
state = "On $album by $artist";
|
||||
large_text = "$album";
|
||||
small_image = "";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
programs = {
|
||||
/*
|
||||
# yams service
|
||||
# TODO: figure out a way to provide the lastfm authentication declaratively
|
||||
|
||||
systemd.user.services.yams = {
|
||||
Unit = {
|
||||
Description = "Last.FM scrobbler for MPD";
|
||||
After = ["mpd.service"];
|
||||
};
|
||||
Service = {
|
||||
ExecStart = "${pkgs.yams}/bin/yams -N";
|
||||
Environment = "NON_INTERACTIVE=1";
|
||||
Restart = "always";
|
||||
};
|
||||
Install.WantedBy = ["default.target"];
|
||||
};
|
||||
*/
|
||||
};
|
||||
};
|
||||
}
|
64
nyx/homes/notashelf/services/shared/media/noisetorch.nix
Normal file
64
nyx/homes/notashelf/services/shared/media/noisetorch.nix
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
osConfig,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf mkEnableOption mkOption literalExpression types;
|
||||
|
||||
cfg = config.services.noisetorch;
|
||||
|
||||
dev = osConfig.modules.device;
|
||||
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
options = {
|
||||
services.noisetorch = {
|
||||
enable = mkEnableOption "noisetorch service";
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.noisetorch;
|
||||
defaultText = literalExpression "pkgs.noisetorch";
|
||||
description = "Which package to use for noisetorch";
|
||||
};
|
||||
threshold = mkOption {
|
||||
type = types.int;
|
||||
default = -1;
|
||||
description = "Voice activation threshold (default -1)";
|
||||
};
|
||||
device = mkOption {
|
||||
type = types.str;
|
||||
description = "Use the specified source/sink device ID";
|
||||
};
|
||||
deviceUnit = mkOption {
|
||||
type = types.str;
|
||||
description = "Systemd device unit which is providing the audio device";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enable && builtins.elem dev.type acceptedTypes) {
|
||||
home.packages = [cfg.package];
|
||||
|
||||
systemd.user.services.noisetorch = {
|
||||
Unit = {
|
||||
Description = "Noisetorch Noise Cancelling";
|
||||
Requires = "${cfg.deviceUnit}";
|
||||
After = "${cfg.deviceUnit}";
|
||||
};
|
||||
Install = {
|
||||
WantedBy = ["default.target"];
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
RemainAfterExit = "yes";
|
||||
ExecStart = "${cfg.package}/bin/noisetorch -i -s ${cfg.device} -t ${builtins.toString cfg.threshold}";
|
||||
ExecStop = "${cfg.package}/bin/noisetorch -u";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
Nice = -10;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
38
nyx/homes/notashelf/services/shared/media/spotifyd.nix
Normal file
38
nyx/homes/notashelf/services/shared/media/spotifyd.nix
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
osConfig,
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf;
|
||||
dev = osConfig.modules.device;
|
||||
|
||||
credientals = {
|
||||
password_cmd = "${pkgs.coreutils}/bin/tail -1 /run/agenix/spotify";
|
||||
username_cmd = "${pkgs.coreutils}/bin/head -1 /run/agenix/spotify";
|
||||
};
|
||||
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
config = mkIf (builtins.elem dev.type acceptedTypes) {
|
||||
services = {
|
||||
spotifyd = {
|
||||
enable = false;
|
||||
package = pkgs.spotifyd.override {withMpris = true;};
|
||||
settings.global = {
|
||||
inherit (credientals) password_cmd username_cmd;
|
||||
cache_path = "${config.xdg.cacheHome}/spotifyd";
|
||||
device_type = "computer";
|
||||
use_mpris = true;
|
||||
autoplay = true;
|
||||
|
||||
# audio settings
|
||||
volume_normalisation = true;
|
||||
backend = "pulseaudio";
|
||||
bitrate = 320;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
33
nyx/homes/notashelf/services/shared/nextcloud.nix
Normal file
33
nyx/homes/notashelf/services/shared/nextcloud.nix
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
lib,
|
||||
osConfig,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf;
|
||||
|
||||
dev = osConfig.modules.device;
|
||||
vid = osConfig.modules.system.video;
|
||||
env = osConfig.modules.usrEnv;
|
||||
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
config = mkIf ((builtins.elem dev.type acceptedTypes) && (vid.enable && env.isWayland)) {
|
||||
/*
|
||||
services = {
|
||||
nextcloud-client.enable = true;
|
||||
nextcloud-client.startInBackground = true;
|
||||
};
|
||||
*/
|
||||
|
||||
home.packages = [pkgs.nextcloud-client];
|
||||
systemd.user.services.nextcloud = lib.mkGraphicalService {
|
||||
Unit.Description = "Nextcloud client service";
|
||||
Service = {
|
||||
ExecStart = "${pkgs.nextcloud-client}/bin/nextcloud --background";
|
||||
Restart = "always";
|
||||
Slice = "background.slice";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
22
nyx/homes/notashelf/services/shared/polkit.nix
Normal file
22
nyx/homes/notashelf/services/shared/polkit.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
lib,
|
||||
osConfig,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf;
|
||||
inherit (osConfig) modules;
|
||||
|
||||
dev = modules.device;
|
||||
sys = modules.system;
|
||||
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
config = mkIf ((builtins.elem dev.type acceptedTypes) && sys.video.enable) {
|
||||
# gnome polkit agent
|
||||
systemd.user.services.polkit-pantheon-authentication-agent-1 = lib.mkGraphicalService {
|
||||
#Service.ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1";
|
||||
Service.ExecStart = "${pkgs.pantheon.pantheon-agent-polkit}/libexec/policykit-1-pantheon/io.elementary.desktop.agent-polkit";
|
||||
};
|
||||
};
|
||||
}
|
14
nyx/homes/notashelf/services/shared/transience.nix
Normal file
14
nyx/homes/notashelf/services/shared/transience.nix
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
osConfig,
|
||||
self,
|
||||
...
|
||||
}: let
|
||||
sys = osConfig.modules.system;
|
||||
in {
|
||||
imports = [self.homeManagerModules.transience];
|
||||
services.transience = {
|
||||
enable = false;
|
||||
directories = [];
|
||||
user = sys.mainUser;
|
||||
};
|
||||
}
|
19
nyx/homes/notashelf/services/shared/tray.nix
Normal file
19
nyx/homes/notashelf/services/shared/tray.nix
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
osConfig,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
env = osConfig.modules.usrEnv;
|
||||
in {
|
||||
# assume system is headless if desktop is not set
|
||||
config = lib.mkIf (env.desktop != "") {
|
||||
# fake a tray to let apps start
|
||||
# https://github.com/nix-community/home-manager/issues/2064
|
||||
systemd.user.targets.tray = {
|
||||
Unit = {
|
||||
Description = "Home Manager System Tray";
|
||||
Requires = ["graphical-session-pre.target"];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
3
nyx/homes/notashelf/services/shared/udiskie.nix
Normal file
3
nyx/homes/notashelf/services/shared/udiskie.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
services.udiskie.enable = true;
|
||||
}
|
1
nyx/homes/notashelf/services/wayland/ags/.envrc
Normal file
1
nyx/homes/notashelf/services/wayland/ags/.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use nix
|
108
nyx/homes/notashelf/services/wayland/ags/.eslintrc.yml
Normal file
108
nyx/homes/notashelf/services/wayland/ags/.eslintrc.yml
Normal file
|
@ -0,0 +1,108 @@
|
|||
root: true
|
||||
env:
|
||||
es2021: true
|
||||
extends:
|
||||
- "eslint:recommended"
|
||||
- "plugin:@typescript-eslint/recommended"
|
||||
parser: "@typescript-eslint/parser"
|
||||
parserOptions:
|
||||
ecmaVersion: 2022
|
||||
sourceType: "module"
|
||||
project: "./tsconfig.json"
|
||||
warnOnUnsupportedTypeScriptVersion: false
|
||||
ignorePatterns:
|
||||
- types/
|
||||
- node_modules/
|
||||
- bin/
|
||||
- result/
|
||||
- style/
|
||||
plugins:
|
||||
- "@typescript-eslint"
|
||||
rules:
|
||||
"@typescript-eslint/ban-ts-comment":
|
||||
- "off"
|
||||
"@typescript-eslint/no-non-null-assertion":
|
||||
- "off"
|
||||
"@typescript-eslint/no-explicit-any":
|
||||
- "off"
|
||||
"@typescript-eslint/no-unused-vars":
|
||||
- error
|
||||
- varsIgnorePattern: (^unused|_$)
|
||||
argsIgnorePattern: ^(unused|_)
|
||||
"@typescript-eslint/no-empty-interface":
|
||||
- "off"
|
||||
|
||||
comma-dangle:
|
||||
- error
|
||||
- always-multiline
|
||||
comma-spacing:
|
||||
- error
|
||||
- before: false
|
||||
after: true
|
||||
comma-style:
|
||||
- error
|
||||
- last
|
||||
curly:
|
||||
- error
|
||||
- multi-or-nest
|
||||
- consistent
|
||||
dot-location:
|
||||
- error
|
||||
- property
|
||||
eol-last:
|
||||
- error
|
||||
indent:
|
||||
- error
|
||||
- 4
|
||||
- SwitchCase: 1
|
||||
keyword-spacing:
|
||||
- error
|
||||
- before: true
|
||||
lines-between-class-members:
|
||||
- error
|
||||
- always
|
||||
- exceptAfterSingleLine: true
|
||||
padded-blocks:
|
||||
- error
|
||||
- never
|
||||
- allowSingleLineBlocks: false
|
||||
prefer-const:
|
||||
- error
|
||||
quotes:
|
||||
- error
|
||||
- double
|
||||
- avoidEscape: true
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
nonblock-statement-body-position:
|
||||
- error
|
||||
- below
|
||||
no-trailing-spaces:
|
||||
- error
|
||||
no-useless-escape:
|
||||
- off
|
||||
max-len:
|
||||
- error
|
||||
- code: 100
|
||||
func-call-spacing:
|
||||
- error
|
||||
array-bracket-spacing:
|
||||
- error
|
||||
space-before-blocks:
|
||||
- error
|
||||
key-spacing:
|
||||
- error
|
||||
object-curly-spacing:
|
||||
- error
|
||||
- always
|
||||
|
||||
globals:
|
||||
globalThis: readonly
|
||||
imports: readonly
|
||||
Intl: readonly
|
||||
log: readonly
|
||||
logError: readonly
|
||||
print: readonly
|
||||
printerr: readonly
|
||||
console: readonly
|
1
nyx/homes/notashelf/services/wayland/ags/.gitignore
vendored
Normal file
1
nyx/homes/notashelf/services/wayland/ags/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules/
|
1
nyx/homes/notashelf/services/wayland/ags/.prettierrc
Normal file
1
nyx/homes/notashelf/services/wayland/ags/.prettierrc
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
14
nyx/homes/notashelf/services/wayland/ags/.stylelintrc.yml
Normal file
14
nyx/homes/notashelf/services/wayland/ags/.stylelintrc.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
extends: stylelint-config-standard-scss
|
||||
ignoreFiles:
|
||||
- "**/*.js"
|
||||
- "**/*.ts"
|
||||
rules:
|
||||
selector-type-no-unknown: null
|
||||
declaration-empty-line-before: null
|
||||
no-descending-specificity: null
|
||||
selector-pseudo-class-no-unknown: null
|
||||
color-function-notation: legacy
|
||||
alpha-value-notation: number
|
||||
scss/operator-no-unspaced: null
|
||||
scss/no-global-function-names: null
|
||||
scss/dollar-variable-empty-line-before: null
|
29
nyx/homes/notashelf/services/wayland/ags/README.md
Normal file
29
nyx/homes/notashelf/services/wayland/ags/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Ags Configuration
|
||||
|
||||
A complete-ish shell replacement with a strong dependency on Hyprland.
|
||||
Currently features a drop-in replacement for my old Waybar configuration
|
||||
paired with a few other features that I found interesting, such as a program
|
||||
launcher and desktop right click capture.
|
||||
|
||||
## Developing Locally
|
||||
|
||||
This configuration is primarily tied to a systemd user service - the
|
||||
dependencies will be made available to ags inside a wrapper, so you do not
|
||||
need to add anything to your `home.packages`. If developing locally, those
|
||||
dependencies will need to be available inside your devshell. Take a look at the
|
||||
`dependencies` list in `default.nix` and enter a shell with the required packages
|
||||
to be able to run `ags -c ./config.js`. Currently `sassc` and `python3` are
|
||||
necessary to be able to start the bar. If you skip this step, ags will not actually
|
||||
display the bar.
|
||||
|
||||
## Credits
|
||||
|
||||
I have taken inspiration or/and code snippets from the cool people below. If you like
|
||||
this configuration, consider giving them a star on their respective repositories.
|
||||
|
||||
- [Exoess](https://github.com/exoess/.files) - initially based on their configuration
|
||||
- [SoraTenshi](https://github.com/SoraTenshi/ags-env) - the connection widget and weather module inspiration
|
||||
- [Fufexan](https://github.com/fufexan/dotfiles/tree/main/home/programs/ags) - cool dude overall, inspiration
|
||||
for a few widgets and his willingness to help with my skill issues
|
||||
|
||||
And of course [Aylur](https://github.com/Aylur) for his awesome work on AGS.
|
40
nyx/homes/notashelf/services/wayland/ags/bin/hyprctl_swallow
Executable file
40
nyx/homes/notashelf/services/wayland/ags/bin/hyprctl_swallow
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env bash
|
||||
# vim: syntax=bash
|
||||
|
||||
notifySend="notify-send"
|
||||
|
||||
getSwallowStatus() {
|
||||
output=$(hyprctl getoption misc:enable_swallow)
|
||||
if [[ $output == *"int: 1"* ]]; then
|
||||
status=false
|
||||
else
|
||||
status=true
|
||||
fi
|
||||
echo "{\"status\": $status}"
|
||||
}
|
||||
|
||||
switchSwallowStatus() {
|
||||
enable=$1
|
||||
if [ "$enable" = true ]; then
|
||||
statusMsg="Turned on swallowing"
|
||||
keyword="true"
|
||||
else
|
||||
statusMsg="Turned off swallowing"
|
||||
keyword="false"
|
||||
fi
|
||||
hyprctl keyword misc:enable_swallow $keyword
|
||||
$notifySend "Hyprland" "$statusMsg"
|
||||
}
|
||||
|
||||
if [ $# -gt 0 ] && [ "${1}" = "query" ]; then
|
||||
getSwallowStatus
|
||||
exit 0
|
||||
fi
|
||||
|
||||
output=$(hyprctl getoption misc:enable_swallow)
|
||||
|
||||
if [[ $output == *"int: 1"* ]]; then
|
||||
switchSwallowStatus false
|
||||
else
|
||||
switchSwallowStatus true
|
||||
fi
|
21
nyx/homes/notashelf/services/wayland/ags/bin/move_window
Executable file
21
nyx/homes/notashelf/services/wayland/ags/bin/move_window
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
# vim: syntax=sh
|
||||
|
||||
move_window() {
|
||||
local position="$1"
|
||||
local size="$2"
|
||||
|
||||
if [[ -z "$position" || -z "$size" ]]; then
|
||||
echo "Error: Both position and size are required." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hyprctl --batch "dispatch moveactive exact ${position//,/ }; dispatch resizeactive exact ${size//x/ }"
|
||||
}
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Usage: $0 <position> <size>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
move_window "$1" "$2"
|
23
nyx/homes/notashelf/services/wayland/ags/bin/open_window
Executable file
23
nyx/homes/notashelf/services/wayland/ags/bin/open_window
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
# vim: syntax=bash
|
||||
|
||||
open_window() {
|
||||
local position="$1"
|
||||
local size="$2"
|
||||
local command="$3"
|
||||
|
||||
# Validate input
|
||||
if [[ -z "$position" || -z "$size" || -z "$command" ]]; then
|
||||
echo "Error: Position, size, and command are required." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hyprctl dispatch exec "[float; move ${position//,/ }; size ${size//x/ }] $command"
|
||||
}
|
||||
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "Usage: $0 <position> <size> <command>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
open_window "$1" "$2" "$3"
|
178
nyx/homes/notashelf/services/wayland/ags/bin/weather
Executable file
178
nyx/homes/notashelf/services/wayland/ags/bin/weather
Executable file
|
@ -0,0 +1,178 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: syntax=python
|
||||
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CACHE_EXPIRATION = 60
|
||||
XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
|
||||
CACHE_DIR = os.path.join(XDG_CACHE_HOME, "zephyr")
|
||||
FALLBACK_CACHE_DIR = "/tmp"
|
||||
CACHE_FILE = os.path.join(CACHE_DIR, "zephyr_cache.json")
|
||||
|
||||
SUNNY = "\udb81\udda8"
|
||||
CLOUDY = "\ue312"
|
||||
RAIN = "\ue318"
|
||||
SNOW = "\ue31a"
|
||||
THUNDERSTORM = "\ue31d"
|
||||
PARTLY_CLOUDY = "\ue302"
|
||||
CLEAR = "\ue30d"
|
||||
|
||||
HOURS_AGO_THRESHOLD = 2
|
||||
TEMP_THRESHOLD_COLD = 10
|
||||
TEMP_THRESHOLD_HOT = 0
|
||||
|
||||
|
||||
def ensure_cache_directory():
|
||||
try:
|
||||
if not os.path.exists(CACHE_DIR):
|
||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating cache directory: {e}")
|
||||
|
||||
|
||||
def get_weather_data():
|
||||
ensure_cache_directory()
|
||||
try:
|
||||
response = requests.get("https://wttr.in/?format=j1")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching weather data: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_cached_weather_data():
|
||||
try:
|
||||
if os.path.exists(CACHE_FILE):
|
||||
with open(CACHE_FILE, "r") as cache_file:
|
||||
cached_data = json.load(cache_file)
|
||||
cache_time = datetime.strptime(
|
||||
cached_data["timestamp"], "%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
if datetime.now() - cache_time < timedelta(minutes=CACHE_EXPIRATION):
|
||||
return cached_data["data"]
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading cached data: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def cache_weather_data(data):
|
||||
try:
|
||||
with open(CACHE_FILE, "w") as cache_file:
|
||||
cached_data = {
|
||||
"data": data,
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
json.dump(cached_data, cache_file)
|
||||
except Exception as e:
|
||||
logger.error(f"Error caching data: {e}")
|
||||
|
||||
|
||||
def format_time(time):
|
||||
return time.replace("00", "").zfill(2)
|
||||
|
||||
|
||||
def format_temp(temp):
|
||||
return f" {temp}°".ljust(4)
|
||||
|
||||
|
||||
def get_emoji_for_condition(condition):
|
||||
emoji_map = {
|
||||
"Sunny": SUNNY,
|
||||
"Partly cloudy": PARTLY_CLOUDY,
|
||||
"Overcast": CLOUDY,
|
||||
"Patchy rain nearby": RAIN,
|
||||
"Clear": CLEAR,
|
||||
"Fog": "\ue313",
|
||||
"Frost": "\udb83\udf29",
|
||||
"Thunder": THUNDERSTORM,
|
||||
"Snow": SNOW,
|
||||
"Windy": "\u27A7",
|
||||
"Mist": "\u2601",
|
||||
"Drizzle": "\u2601",
|
||||
"Heavy rain": "\u2614",
|
||||
"Sleet": "\u2744",
|
||||
"Wintry mix": "\u2744",
|
||||
"Clear/Sunny": CLEAR,
|
||||
"Clear/Mostly clear": CLEAR,
|
||||
"Clear/Mostly clear (night)": CLEAR,
|
||||
"Drizzle (night)": "\u2601",
|
||||
}
|
||||
return emoji_map.get(condition, "")
|
||||
|
||||
|
||||
def format_conditions(hour):
|
||||
condition_probabilities = {
|
||||
"chanceoffog": "Fog",
|
||||
"chanceoffrost": "Frost",
|
||||
"chanceofovercast": "Overcast",
|
||||
"chanceofrain": "Rain",
|
||||
"chanceofsnow": "Snow",
|
||||
"chanceofsunshine": "Sunshine",
|
||||
"chanceofthunder": "Thunder",
|
||||
"chanceofwindy": "Wind",
|
||||
}
|
||||
if "chanceofpartlycloudy" in hour:
|
||||
condition_probabilities["chanceofpartlycloudy"] = "Partly Cloudy"
|
||||
conditions = []
|
||||
for event, description in condition_probabilities.items():
|
||||
if event in hour:
|
||||
probability = int(hour[event])
|
||||
if probability > 0:
|
||||
emoji = get_emoji_for_condition(description)
|
||||
conditions.append(f"{emoji} {description} {probability}%")
|
||||
return ", ".join(conditions)
|
||||
|
||||
|
||||
def format_weather_data(weather_data):
|
||||
current_condition = weather_data["current_condition"][0]
|
||||
temp = int(current_condition["FeelsLikeC"])
|
||||
temp_sign = "+" if TEMP_THRESHOLD_HOT > temp > TEMP_THRESHOLD_COLD else ""
|
||||
formatted_data = {
|
||||
"text": f" {SUNNY} \n {temp_sign}{temp}°",
|
||||
"tooltip": f"{current_condition['weatherDesc'][0]['value']} {current_condition['temp_C']}°\n"
|
||||
f"Feels like: {current_condition['FeelsLikeC']}°\n"
|
||||
f"Wind: {current_condition['windspeedKmph']}Km/h\n"
|
||||
f"Humidity: {current_condition['humidity']}%\n",
|
||||
}
|
||||
for i, day in enumerate(weather_data["weather"]):
|
||||
formatted_data["tooltip"] += f"\n"
|
||||
if i == 0:
|
||||
formatted_data["tooltip"] += "Today, "
|
||||
if i == 1:
|
||||
formatted_data["tooltip"] += "Tomorrow, "
|
||||
formatted_data["tooltip"] += f"{day['date']}\n"
|
||||
formatted_data["tooltip"] += f"⬆️ {day['maxtempC']}° ⬇️ {day['mintempC']}° "
|
||||
formatted_data[
|
||||
"tooltip"
|
||||
] += f"🌅 {day['astronomy'][0]['sunrise']} 🌇 {day['astronomy'][0]['sunset']}\n"
|
||||
now = datetime.now()
|
||||
for hour in day["hourly"]:
|
||||
hour_time = format_time(hour["time"])
|
||||
if i == 0 and int(hour_time) < now.hour - HOURS_AGO_THRESHOLD:
|
||||
continue
|
||||
formatted_data[
|
||||
"tooltip"
|
||||
] += f"{hour_time} {get_emoji_for_condition(hour['weatherDesc'][0]['value'])} {format_temp(hour['FeelsLikeC'])} {hour['weatherDesc'][0]['value']}, {format_conditions(hour)}\n"
|
||||
return formatted_data
|
||||
|
||||
|
||||
def main():
|
||||
weather_data = get_weather_data()
|
||||
if weather_data is None:
|
||||
weather_data = get_cached_weather_data()
|
||||
if weather_data:
|
||||
formatted_data = format_weather_data(weather_data)
|
||||
cache_weather_data(formatted_data)
|
||||
print(json.dumps(formatted_data))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
34
nyx/homes/notashelf/services/wayland/ags/config.js
Normal file
34
nyx/homes/notashelf/services/wayland/ags/config.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { App, Notifications } from "./js/imports.js";
|
||||
const css = App.configDir + "/style.css";
|
||||
|
||||
// Windows
|
||||
import { AppLauncher } from "./js/windows/launcher/index.js";
|
||||
import { Bar } from "./js/windows/bar/index.js";
|
||||
import { Desktop } from "./js/windows/desktop/index.js";
|
||||
import { Popups } from "./js/windows/popups/index.js";
|
||||
import { Notifs } from "./js/windows/notifications/index.js";
|
||||
import { Media } from "./js/windows/music/index.js";
|
||||
|
||||
App.connect("config-parsed", () => print("config parsed"));
|
||||
|
||||
Notifications.popupTimeout = 5000;
|
||||
Notifications.forceTimeout = false;
|
||||
Notifications.cacheActions = true;
|
||||
|
||||
// Main config
|
||||
export default {
|
||||
style: css,
|
||||
closeWindowDelay: {
|
||||
launcher: 300,
|
||||
music: 300,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any[]} windows
|
||||
*/
|
||||
function addWindows(windows) {
|
||||
windows.forEach((win) => App.addWindow(win));
|
||||
}
|
||||
|
||||
addWindows([AppLauncher(), Bar(), Media(), Desktop(), Popups(), Notifs()]);
|
122
nyx/homes/notashelf/services/wayland/ags/default.nix
Normal file
122
nyx/homes/notashelf/services/wayland/ags/default.nix
Normal file
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
inputs,
|
||||
osConfig,
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.fileset) fileFilter unions difference toSource;
|
||||
inherit (lib.modules) mkIf;
|
||||
inherit (osConfig.modules) device;
|
||||
|
||||
# dependencies required for the ags runtime to function properly
|
||||
# some of those dependencies are used internally for setting variables
|
||||
# or basic functionality where built-in services do not suffice
|
||||
coreDeps = with pkgs; [
|
||||
inputs.hyprpicker.packages.${pkgs.system}.default
|
||||
inputs.hyprland.packages.${pkgs.system}.default
|
||||
config.programs.foot.package
|
||||
|
||||
# basic functionality
|
||||
inotify-tools
|
||||
gtk3
|
||||
|
||||
# script and service helpers
|
||||
bash
|
||||
brightnessctl
|
||||
coreutils
|
||||
gawk
|
||||
gvfs
|
||||
imagemagick
|
||||
libnotify
|
||||
procps
|
||||
ripgrep
|
||||
slurp
|
||||
sysstat
|
||||
|
||||
# for weather widget
|
||||
(python3.withPackages (ps: [ps.requests]))
|
||||
];
|
||||
|
||||
# applications that are not necessarily required to compile ags
|
||||
# but are used by the widgets to launch certain applications
|
||||
widgetDeps = with pkgs; [
|
||||
pavucontrol
|
||||
networkmanagerapplet
|
||||
blueman
|
||||
];
|
||||
|
||||
dependencies = coreDeps ++ widgetDeps;
|
||||
filterNixFiles = fileFilter (file: lib.hasSuffix ".nix" file.name) ./.;
|
||||
|
||||
baseSrc = unions [
|
||||
# runtime executables
|
||||
./bin
|
||||
|
||||
# ags widgets and utilities
|
||||
./js
|
||||
./config.js
|
||||
|
||||
# compiled stylesheet
|
||||
# should be generated using the below command
|
||||
# `sassc -t compressed style/main.scss style.css`
|
||||
./style.css
|
||||
];
|
||||
|
||||
filter = difference baseSrc filterNixFiles;
|
||||
|
||||
cfg = config.programs.ags;
|
||||
acceptedTypes = ["desktop" "laptop" "lite" "hybrid"];
|
||||
in {
|
||||
imports = [inputs.ags.homeManagerModules.default];
|
||||
config = mkIf (builtins.elem device.type acceptedTypes) {
|
||||
programs.ags = {
|
||||
enable = true;
|
||||
configDir = toSource {
|
||||
root = ./.;
|
||||
fileset = filter;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.services.ags = {
|
||||
Install.WantedBy = ["graphical-session.target"];
|
||||
|
||||
Unit = {
|
||||
Description = "Aylur's Gtk Shell (Ags)";
|
||||
After = ["graphical-session-pre.target"];
|
||||
PartOf = [
|
||||
"tray.target"
|
||||
"graphical-session.target"
|
||||
];
|
||||
};
|
||||
|
||||
Service = {
|
||||
Type = "simple";
|
||||
|
||||
Environment = "PATH=/run/wrappers/bin:${lib.makeBinPath dependencies}";
|
||||
ExecStart = "${cfg.package}/bin/ags";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR2 $MAINPID"; # hot-reloading
|
||||
|
||||
# runtime
|
||||
RuntimeDirectory = "ags";
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = "read-only";
|
||||
CacheDirectory = ["ags"];
|
||||
ReadWritePaths = [
|
||||
# socket access
|
||||
"%t" # /run/user/1000 for the socket
|
||||
"/tmp/hypr" # hyprland socket
|
||||
|
||||
# for thumbnail caching
|
||||
"~/notashelf/.local/share/firefox-mpris/"
|
||||
"~/.cache/ags/media"
|
||||
];
|
||||
|
||||
# restart on failure
|
||||
Restart = "on-failure";
|
||||
KillMode = "mixed";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
119
nyx/homes/notashelf/services/wayland/ags/js/icons.js
Normal file
119
nyx/homes/notashelf/services/wayland/ags/js/icons.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
export const Icon = {
|
||||
settings: "org.gnome.Settings-symbolic",
|
||||
tick: "object-select-symbolic",
|
||||
audio: {
|
||||
mic: {
|
||||
muted: "microphone-disabled-symbolic",
|
||||
unmuted: "microphone-sensitivity-high-symbolic",
|
||||
},
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
type: {
|
||||
headset: "audio-headphones-symbolic",
|
||||
speaker: "audio-speakers-symbolic",
|
||||
card: "audio-card-symbolic",
|
||||
},
|
||||
mixer: "tool-symbolic",
|
||||
},
|
||||
apps: {
|
||||
apps: "view-app-grid-symbolic",
|
||||
search: "folder-saved-search-symbolic",
|
||||
},
|
||||
bluetooth: {
|
||||
enabled: "bluetooth-active-symbolic",
|
||||
disabled: "bluetooth-disabled-symbolic",
|
||||
},
|
||||
brightness: {
|
||||
indicator: "display-brightness-symbolic",
|
||||
keyboard: "keyboard-brightness-symbolic",
|
||||
screen: ["", "", "", "", "", "", "", "", "", "", ""],
|
||||
},
|
||||
powermenu: {
|
||||
sleep: "weather-clear-night-symbolic",
|
||||
reboot: "system-reboot-symbolic",
|
||||
logout: "system-log-out-symbolic",
|
||||
shutdown: "system-shutdown-symbolic",
|
||||
lock: "system-lock-screen-symbolic",
|
||||
close: "window-close-symbolic",
|
||||
},
|
||||
recorder: {
|
||||
recording: "media-record-symbolic",
|
||||
},
|
||||
notifications: {
|
||||
noisy: "preferences-system-notifications-symbolic",
|
||||
silent: "notifications-disabled-symbolic",
|
||||
critical: "messagebox_critical-symbolic",
|
||||
chat: "notification-symbolic",
|
||||
close: "window-close-symbolic",
|
||||
},
|
||||
header: {
|
||||
refresh: "view-refresh-symbolic",
|
||||
settings: "settings-symbolic",
|
||||
power: "system-shutdown-symbolic",
|
||||
},
|
||||
trash: {
|
||||
full: "user-trash-full-symbolic",
|
||||
empty: "user-trash-symbolic",
|
||||
},
|
||||
mpris: {
|
||||
fallback: "audio-x-generic-symbolic",
|
||||
shuffle: {
|
||||
enabled: "media-playlist-shuffle-symbolic",
|
||||
disabled: "media-playlist-no-shuffle-symbolic",
|
||||
},
|
||||
loop: {
|
||||
none: "media-playlist-no-repeat-symbolic",
|
||||
track: "media-playlist-repeat-song-symbolic",
|
||||
playlist: "media-playlist-repeat-symbolic",
|
||||
},
|
||||
playing: "media-playback-pause-symbolic",
|
||||
paused: "media-playback-start-symbolic",
|
||||
stopped: "media-playback-stop-symbolic",
|
||||
prev: "media-skip-backward-symbolic",
|
||||
next: "media-skip-forward-symbolic",
|
||||
},
|
||||
ui: {
|
||||
send: "mail-send-symbolic",
|
||||
arrow: {
|
||||
right: "pan-end-symbolic",
|
||||
left: "pan-start-symbolic",
|
||||
down: "pan-down-symbolic",
|
||||
up: "pan-up-symbolic",
|
||||
},
|
||||
},
|
||||
speaker: {
|
||||
overamplified: "\uf14b",
|
||||
high: "\ue050",
|
||||
medium: "\ue04d",
|
||||
low: "\ue04e",
|
||||
muted: "\ue04f",
|
||||
},
|
||||
microphone: {
|
||||
overamplified: "\ue029",
|
||||
high: "\ue029",
|
||||
medium: "\ue029",
|
||||
low: "\ue029",
|
||||
muted: "\ue02b",
|
||||
},
|
||||
wired: {
|
||||
power: "",
|
||||
poweroff: "",
|
||||
},
|
||||
wifi: {
|
||||
none: "",
|
||||
bad: "",
|
||||
low: "",
|
||||
normal: "",
|
||||
good: "",
|
||||
},
|
||||
system: {
|
||||
cpu: "org.gnome.SystemMonitor-symbolic",
|
||||
ram: "drive-harddisk-solidstate-symbolic",
|
||||
temp: "temperature-symbolic",
|
||||
},
|
||||
};
|
27
nyx/homes/notashelf/services/wayland/ags/js/imports.js
Normal file
27
nyx/homes/notashelf/services/wayland/ags/js/imports.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
export const require = async (file) => (await import(resource(file))).default;
|
||||
export const resource = (file) => `resource:///com/github/Aylur/ags/${file}.js`;
|
||||
export const fromService = async (file) => await require(`service/${file}`);
|
||||
export const requireCustom = async (/** @type {string} */ path) =>
|
||||
(await import(path)).default;
|
||||
|
||||
export const App = await require("app");
|
||||
export const GLib = await requireCustom("gi://GLib");
|
||||
export const Gtk = await requireCustom("gi://Gtk?version=3.0");
|
||||
export const Service = await require("service");
|
||||
export const Utils = await import(resource("utils"));
|
||||
export const Variable = await require("variable");
|
||||
export const Widget = await require("widget");
|
||||
|
||||
// Services
|
||||
export const Battery = await fromService("battery");
|
||||
export const Bluetooth = await fromService("bluetooth");
|
||||
export const Hyprland = await fromService("hyprland");
|
||||
export const Mpris = await fromService("mpris");
|
||||
export const Network = await fromService("network");
|
||||
export const Applications = await fromService("applications");
|
||||
export const Audio = await fromService("audio");
|
||||
export const Notifications = await fromService("notifications");
|
||||
export const SystemTray = await fromService("systemtray");
|
||||
|
||||
// Extras
|
||||
export const Icons = await requireCustom("./utils/icons.js");
|
|
@ -0,0 +1,50 @@
|
|||
import { Service, Utils } from "../imports.js";
|
||||
const { exec } = Utils;
|
||||
|
||||
class Brightness extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{},
|
||||
{
|
||||
screen: ["float", "rw"],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_screen = 0;
|
||||
|
||||
get screen() {
|
||||
return this._screen;
|
||||
}
|
||||
|
||||
set screen(percent) {
|
||||
if (percent < 0) percent = 0;
|
||||
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
Utils.execAsync(`brightnessctl s ${percent * 100}% -q`)
|
||||
.then(() => {
|
||||
this._screen = percent;
|
||||
this.changed("screen");
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
this._screen =
|
||||
Number(exec("brightnessctl g")) /
|
||||
Number(exec("brightnessctl m"));
|
||||
} catch (error) {
|
||||
console.error("missing dependancy: brightnessctl");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const service = new Brightness();
|
||||
|
||||
globalThis.brightness = service;
|
||||
|
||||
export default service;
|
|
@ -0,0 +1,49 @@
|
|||
import { Service } from "../imports.js";
|
||||
const { Gio } = imports.gi;
|
||||
|
||||
class DirectoryMonitorService extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {});
|
||||
}
|
||||
|
||||
_monitors = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
recursiveDirectoryMonitor(directoryPath) {
|
||||
const directory = Gio.File.new_for_path(directoryPath);
|
||||
const monitor = directory.monitor_directory(
|
||||
Gio.FileMonitorFlags.NONE,
|
||||
null,
|
||||
);
|
||||
this._monitors.push(monitor);
|
||||
|
||||
monitor.connect(
|
||||
"changed",
|
||||
(fileMonitor, file, otherFile, eventType) => {
|
||||
if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
|
||||
this.emit("changed");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const enumerator = directory.enumerate_children(
|
||||
"standard::*",
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
null,
|
||||
);
|
||||
|
||||
let fileInfo;
|
||||
while ((fileInfo = enumerator.next_file(null)) !== null) {
|
||||
const childPath = directoryPath + "/" + fileInfo.get_name();
|
||||
if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) {
|
||||
this.recursiveDirectoryMonitor(childPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const service = new DirectoryMonitorService();
|
||||
export default service;
|
|
@ -0,0 +1,56 @@
|
|||
import { Utils, Service } from "../imports.js";
|
||||
const { subprocess } = Utils;
|
||||
|
||||
class InputMonitorService extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{
|
||||
keypress: ["jsobject"],
|
||||
keyrelease: ["jsobject"],
|
||||
keyrepeat: ["jsobject"],
|
||||
event: ["jsobject"],
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._evtest = subprocess("evtest /dev/input/event3", (str) =>
|
||||
this._handleEvent(str),
|
||||
);
|
||||
}
|
||||
|
||||
_handleEvent(event) {
|
||||
//ignore initial output
|
||||
if (!event.startsWith("Event")) return;
|
||||
//ignore SYN_REPORTS
|
||||
if (event.includes("SYN")) return;
|
||||
const eventData = event.substring(7).split(", ");
|
||||
const eventInfo = {};
|
||||
//evetnInfo structure:
|
||||
//{
|
||||
// time: unix timstamp
|
||||
// type: event type
|
||||
// code: keycode (this is the harware keycode)
|
||||
// value: depends on type, for EV_KEY 0->release, 1->press, 2->repeat(when holding)
|
||||
//}
|
||||
|
||||
eventData.forEach((data) => {
|
||||
const [key, value, value2] = data.split(" ");
|
||||
eventInfo[key] = isNaN(value) ? value : Number(value);
|
||||
if (key === "code") eventInfo["name"] = value2.slice(1, -1);
|
||||
});
|
||||
//only emit on EV_KEY
|
||||
if (eventInfo.type === 1) {
|
||||
if (eventInfo.value === 0) this.emit("keyrelease", eventInfo);
|
||||
if (eventInfo.value === 1) this.emit("keypress", eventInfo);
|
||||
if (eventInfo.value === 2) this.emit("keyrepeat", eventInfo);
|
||||
}
|
||||
//emit on every event, just in case, you need it
|
||||
this.emit("event", eventInfo);
|
||||
}
|
||||
}
|
||||
|
||||
export default new InputMonitorService();
|
21
nyx/homes/notashelf/services/wayland/ags/js/utils/appIcon.js
Normal file
21
nyx/homes/notashelf/services/wayland/ags/js/utils/appIcon.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Widget } from "../imports.js";
|
||||
import { queryExact } from "./global.js";
|
||||
const { Button, Icon } = Widget;
|
||||
|
||||
export default ({
|
||||
appName,
|
||||
onClicked = () => queryExact(appName).launch(),
|
||||
icon = queryExact(appName).iconName,
|
||||
size = 36,
|
||||
...props
|
||||
}) => {
|
||||
const appIcon = Button({
|
||||
onClicked,
|
||||
child: Icon({
|
||||
icon,
|
||||
size,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
return appIcon;
|
||||
};
|
62
nyx/homes/notashelf/services/wayland/ags/js/utils/audio.js
Normal file
62
nyx/homes/notashelf/services/wayland/ags/js/utils/audio.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { Audio, Widget } from "../imports.js";
|
||||
const { Slider, Label } = Widget;
|
||||
const { speaker } = Audio;
|
||||
|
||||
const audio = {
|
||||
mixer: "",
|
||||
mic: {
|
||||
muted: "microphone-disabled-symbolic",
|
||||
low: "microphone-sensitivity-low-symbolic",
|
||||
medium: "microphone-sensitivity-medium-symbolic",
|
||||
high: "microphone-sensitivity-high-symbolic",
|
||||
},
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
type: {
|
||||
headset: "audio-headphones-symbolic",
|
||||
speaker: "audio-speakers-symbolic",
|
||||
card: "audio-card-symbolic",
|
||||
},
|
||||
};
|
||||
|
||||
export const getAudioIcon = (self) => {
|
||||
if (!Audio.speaker) return;
|
||||
|
||||
const { muted, low, medium, high, overamplified } = audio.volume;
|
||||
|
||||
if (Audio.speaker.is_muted) return (self.icon = muted);
|
||||
|
||||
/** @type {Array<[number, string]>} */
|
||||
const cons = [
|
||||
[101, overamplified],
|
||||
[67, high],
|
||||
[34, medium],
|
||||
[1, low],
|
||||
[0, muted],
|
||||
];
|
||||
|
||||
self.icon = cons.find(([n]) => n <= Audio.speaker.volume * 100)?.[1] || "";
|
||||
};
|
||||
|
||||
export const getSliderIcon = () =>
|
||||
Label({
|
||||
className: "volPopupIcon",
|
||||
label: speaker.bind("volume").as((/** @type {number} */ v) => {
|
||||
return ["", "", "", ""][
|
||||
speaker.stream?.isMuted ? 0 : Math.floor((v * 100) / 26)
|
||||
];
|
||||
}),
|
||||
});
|
||||
|
||||
export const volumePercentBar = () =>
|
||||
Slider({
|
||||
className: "volPopupBar",
|
||||
drawValue: false,
|
||||
value: speaker.bind("volume"),
|
||||
onChange: ({ value }) => (speaker.volume = value),
|
||||
});
|
57
nyx/homes/notashelf/services/wayland/ags/js/utils/battery.js
Normal file
57
nyx/homes/notashelf/services/wayland/ags/js/utils/battery.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Battery } from "../imports.js";
|
||||
|
||||
/**
|
||||
* toTime converts a given value to a human-readable
|
||||
* format where the number of hours and minutes are
|
||||
* inferred from time, which is assumed to be in seconds.
|
||||
*
|
||||
* @param {number} time - time in seconds
|
||||
*/
|
||||
export const toTime = (time) => {
|
||||
const MINUTE = 60;
|
||||
const HOUR = MINUTE * 60;
|
||||
|
||||
if (time > 24 * HOUR) return "";
|
||||
|
||||
const hours = Math.round(time / HOUR);
|
||||
const minutes = Math.round((time - hours * HOUR) / MINUTE);
|
||||
|
||||
const hoursDisplay = hours > 0 ? `${hours}h ` : "";
|
||||
const minutesDisplay = minutes > 0 ? `${minutes}m ` : "";
|
||||
|
||||
return `${hoursDisplay}${minutesDisplay}`;
|
||||
};
|
||||
|
||||
export const getBatteryTime = () => {
|
||||
const timeRemaining = Battery.timeRemaining;
|
||||
return timeRemaining > 0 && toTime(timeRemaining) != ""
|
||||
? `${toTime(timeRemaining)}remaining`
|
||||
: "";
|
||||
};
|
||||
|
||||
export const getBatteryPercentage = () => {
|
||||
const percent = Battery.percent;
|
||||
return percent > 0 && percent < 100 ? `${percent}%` : "";
|
||||
};
|
||||
|
||||
export const getBatteryTooltip = () => {
|
||||
const time = getBatteryTime();
|
||||
const percent = Battery.percent;
|
||||
|
||||
return `${percent}% | ${time}`;
|
||||
};
|
||||
|
||||
export const getBatteryIcon = () => {
|
||||
// if Battery.percent is not between 0 and 100, handle the error
|
||||
if (Battery.percent < 0 || Battery.percent > 100)
|
||||
return "Battery percentage is not a valid value!";
|
||||
|
||||
const icons = [
|
||||
["", "", "", "", "", "", "", "", "", "", ""],
|
||||
["", "", "", "", "", "", "", "", "", "", ""],
|
||||
];
|
||||
|
||||
const chargingIndex = Battery.charging ? 1 : 0;
|
||||
const percentIndex = Math.floor(Battery.percent / 10);
|
||||
return icons[chargingIndex][percentIndex].toString();
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
import { Bluetooth, Icons } from "../imports.js";
|
||||
|
||||
export const getBluetoothDevice = (addr) =>
|
||||
Bluetooth.getDevice(addr).alias ?? Bluetooth.getDevice(addr).name;
|
||||
|
||||
export const getBluetoothIcon = (connected) => {
|
||||
if (!Bluetooth.enabled) return Icons.bluetooth.disabled;
|
||||
if (connected.length > 0) return Icons.bluetooth.active;
|
||||
return Icons.bluetooth.disconnected;
|
||||
};
|
||||
|
||||
export const getBluetoothTooltip = (connected) => {
|
||||
if (!Bluetooth.enabled) return "Bluetooth off";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected[0].address);
|
||||
let battery_str = "";
|
||||
|
||||
if (dev.battery_percentage > 0) {
|
||||
battery_str += ` ${dev.battery_percentage}%`;
|
||||
}
|
||||
|
||||
return dev.name + battery_str;
|
||||
}
|
||||
|
||||
return "Bluetooth on";
|
||||
};
|
||||
|
||||
export const getBluetoothClass = (connected) => {
|
||||
if (!Bluetooth.enabled) return "bluetooth-disabled";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected.at(0).address);
|
||||
|
||||
if (dev.battery_percentage <= 25) return "bluetooth-active-low-battery";
|
||||
|
||||
if (dev.battery_percentage > 25) return "bluetooth-paired";
|
||||
}
|
||||
|
||||
return "bluetooth-active";
|
||||
};
|
||||
|
||||
export const getBluetoothLabel = (connected) => {
|
||||
if (!Bluetooth.enabled) return "";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected.at(0).address);
|
||||
|
||||
if (dev.battery_percentage <= 25) return "";
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
36
nyx/homes/notashelf/services/wayland/ags/js/utils/desktop.js
Normal file
36
nyx/homes/notashelf/services/wayland/ags/js/utils/desktop.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Widget } from "../imports.js";
|
||||
import { queryExact } from "./global.js";
|
||||
const { Box, Icon, Label, Button } = Widget;
|
||||
|
||||
/**
|
||||
* Builds a desktop item with a specific name and label.
|
||||
* It uses the `queryExact` function to find the exact application based on its name.
|
||||
* Then, it creates a button widget with the application's icon and label.
|
||||
* When the button is clicked, it launches the application.
|
||||
*
|
||||
* @function buildDesktopItem
|
||||
* @param {string} name - The name of the application.
|
||||
* @param {string} label - The label of the desktop item.
|
||||
* @returns {Object} The desktop item widget.
|
||||
*/
|
||||
export const buildDesktopItem = (name, label) => {
|
||||
const app = queryExact(name);
|
||||
return Button({
|
||||
className: "desktopIcon",
|
||||
cursor: "pointer",
|
||||
onClicked: () => app.launch(),
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Icon({
|
||||
icon: app.iconName,
|
||||
size: 48,
|
||||
}),
|
||||
Label({
|
||||
className: "desktopIconLabel",
|
||||
label,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
38
nyx/homes/notashelf/services/wayland/ags/js/utils/global.js
Normal file
38
nyx/homes/notashelf/services/wayland/ags/js/utils/global.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Applications, Utils } from "../imports.js";
|
||||
const { execAsync } = Utils;
|
||||
const { list, query } = Applications;
|
||||
|
||||
/**
|
||||
* Queries the exact application based on its name.
|
||||
* First tries to find the application in the list of applications.
|
||||
* If it doesn't find it, then it queries the application by its name.
|
||||
*
|
||||
* @function queryExact
|
||||
* @param {string} appName - The name of the application to query.
|
||||
* @returns {Object} The queried application object. Returns null if the application is not found.
|
||||
*/
|
||||
export function queryExact(appName) {
|
||||
return (
|
||||
list.filter(
|
||||
(app) => app.name.toLowerCase() === appName.toLowerCase(),
|
||||
)[0] ?? query(appName)[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to launch an application based on its name.
|
||||
* First it tries to kill the application if it's already running.
|
||||
* Regardless of whether the killing has been successful or not, it
|
||||
* tries to launch the application.
|
||||
*
|
||||
* @function launchApp
|
||||
* @param {string} appName - The name of the application to launch.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function launchApp(appName) {
|
||||
if (queryExact(appName)) {
|
||||
execAsync(["sh", "-c", `killall ${appName}`]);
|
||||
}
|
||||
|
||||
execAsync(["sh", "-c", `${appName}`]);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { Hyprland } from "../imports.js";
|
||||
|
||||
export const getFocusedWorkspace = (self) =>
|
||||
self.children.forEach((btn) => {
|
||||
btn.className =
|
||||
btn.attribute.index === Hyprland.active.workspace.id
|
||||
? "focused"
|
||||
: "";
|
||||
btn.visible = Hyprland.workspaces.some(
|
||||
(ws) => ws.id === btn.attribute.index,
|
||||
);
|
||||
});
|
56
nyx/homes/notashelf/services/wayland/ags/js/utils/icons.js
Normal file
56
nyx/homes/notashelf/services/wayland/ags/js/utils/icons.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
export default {
|
||||
bluetooth: {
|
||||
active: "bluetooth-active-symbolic",
|
||||
disabled: "bluetooth-disabled-symbolic",
|
||||
disconnected: "bluetooth-disconnected-symbolic",
|
||||
},
|
||||
|
||||
brightness: "display-brightness-symbolic",
|
||||
|
||||
media: {
|
||||
play: "media-playback-start-symbolic",
|
||||
pause: "media-playback-pause-symbolic",
|
||||
next: "media-skip-forward-symbolic",
|
||||
previous: "media-skip-backward-symbolic",
|
||||
player: "multimedia-player-symbolic",
|
||||
},
|
||||
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
|
||||
speaker: {
|
||||
overamplified: "\uf14b",
|
||||
high: "\ue050",
|
||||
medium: "\ue04d",
|
||||
low: "\ue04e",
|
||||
muted: "\ue04f",
|
||||
},
|
||||
|
||||
microphone: {
|
||||
overamplified: "\ue029",
|
||||
high: "\ue029",
|
||||
medium: "\ue029",
|
||||
low: "\ue029",
|
||||
muted: "\ue02b",
|
||||
},
|
||||
|
||||
wired: {
|
||||
power: "",
|
||||
poweroff: "",
|
||||
},
|
||||
|
||||
wifi: {
|
||||
none: "",
|
||||
bad: "",
|
||||
low: "",
|
||||
normal: "",
|
||||
good: "",
|
||||
},
|
||||
|
||||
powerButton: "system-shutdown-symbolic",
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export const getLauncherIcon = (self, windowName, visible) => {
|
||||
windowName === "launcher" && (self.child.label = visible ? "" : "");
|
||||
};
|
46
nyx/homes/notashelf/services/wayland/ags/js/utils/mpris.js
Normal file
46
nyx/homes/notashelf/services/wayland/ags/js/utils/mpris.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Icons, Utils } from "../imports.js";
|
||||
import GLib from "gi://GLib";
|
||||
|
||||
export const findPlayer = (players) => {
|
||||
// try to get the first active player
|
||||
const activePlayer = players.find((p) => p.playBackStatus == "Playing");
|
||||
if (activePlayer != null) return activePlayer;
|
||||
|
||||
// otherwise get the first "working" player
|
||||
for (const p of players) {
|
||||
if (p.title != "undefined") return p;
|
||||
}
|
||||
};
|
||||
|
||||
export const mprisStateIcon = (status) => {
|
||||
const state = status == "Playing" ? "pause" : "play";
|
||||
return Icons.media[state];
|
||||
};
|
||||
|
||||
export const MEDIA_CACHE_PATH = Utils.CACHE_DIR + "/media";
|
||||
export const blurredPath = MEDIA_CACHE_PATH + "/blurred";
|
||||
|
||||
export const generateBackground = (cover_path) => {
|
||||
const url = cover_path;
|
||||
if (!url) return "";
|
||||
|
||||
const makeBg = (bg) => `background: center/cover url('${bg}')`;
|
||||
|
||||
const blurred = blurredPath + url.substring(MEDIA_CACHE_PATH.length);
|
||||
|
||||
if (GLib.file_test(blurred, GLib.FileTest.EXISTS)) {
|
||||
return makeBg(blurred);
|
||||
}
|
||||
|
||||
Utils.ensureDirectory(blurredPath);
|
||||
Utils.exec(`convert ${url} -blur 0x22 ${blurred}`);
|
||||
|
||||
return makeBg(blurred);
|
||||
};
|
||||
|
||||
export function lengthStr(length) {
|
||||
const min = Math.floor(length / 60);
|
||||
const sec = Math.floor(length % 60);
|
||||
const sec0 = sec < 10 ? "0" : "";
|
||||
return `${min}:${sec0}${sec}`;
|
||||
}
|
40
nyx/homes/notashelf/services/wayland/ags/js/utils/network.js
Normal file
40
nyx/homes/notashelf/services/wayland/ags/js/utils/network.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Network } from "../imports.js";
|
||||
|
||||
import { Icon } from "../icons.js";
|
||||
const { wifi, wired } = Icon;
|
||||
|
||||
export const getWifiIcon = (strength) => {
|
||||
if (strength < 0.1) return wifi.none;
|
||||
if (strength < 0.26) return wifi.bad;
|
||||
if (strength < 0.51) return wifi.low;
|
||||
if (strength < 0.76) return wifi.normal;
|
||||
if (strength > 0.76) return wifi.good;
|
||||
else return wifi.none;
|
||||
};
|
||||
|
||||
export const getWifiTooltip = (strength, ssid) => {
|
||||
const wifi = Network.wifi;
|
||||
const wifiStrength = `Strength: ${strength * 100}`;
|
||||
|
||||
switch (wifi.internet) {
|
||||
case "connected":
|
||||
return `Connected to ${ssid} | Strength: ${wifiStrength}`;
|
||||
case "connecting":
|
||||
return `Connecting to ${ssid} | Strength: ${wifiStrength}`;
|
||||
case "disconnected":
|
||||
return `Disconnected from ${ssid} | Strength: ${wifiStrength}`;
|
||||
default:
|
||||
return `No connection | Strength: ${wifiStrength}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const getWiredIcon = (internet) => {
|
||||
if (internet === "connected") return wired.power;
|
||||
if (internet === "connecting") return wired.poweroff;
|
||||
if (internet === "disconnected") return wired.poweroff;
|
||||
return wired.poweroff;
|
||||
};
|
||||
|
||||
export const getWiredTooltip = (internet) => {
|
||||
return `Status: ${internet}`;
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
import { App, Widget, Utils } from "../imports.js";
|
||||
const { Box, Revealer, Window } = Widget;
|
||||
|
||||
export default ({
|
||||
onOpen = () => {},
|
||||
onClose = () => {},
|
||||
|
||||
name,
|
||||
child,
|
||||
transition = "slide_up",
|
||||
transitionDuration = 250,
|
||||
...props
|
||||
}) => {
|
||||
const window = Window({
|
||||
name,
|
||||
visible: false,
|
||||
...props,
|
||||
|
||||
child: Box({
|
||||
css: `
|
||||
min-height: 2px;
|
||||
min-width: 2px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition,
|
||||
transitionDuration,
|
||||
child: child || Box(),
|
||||
setup: (self) => {
|
||||
self.hook(App, (rev, currentName, isOpen) => {
|
||||
if (currentName === name) {
|
||||
rev.revealChild = isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
onOpen(window);
|
||||
} else {
|
||||
Utils.timeout(transitionDuration, () => {
|
||||
onClose(window);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
window.getChild = () => window.child.children[0].child;
|
||||
window.setChild = (newChild) => {
|
||||
window.child.children[0].child = newChild;
|
||||
window.child.children[0].show_all();
|
||||
};
|
||||
|
||||
return window;
|
||||
};
|
25
nyx/homes/notashelf/services/wayland/ags/js/utils/swallow.js
Normal file
25
nyx/homes/notashelf/services/wayland/ags/js/utils/swallow.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { App, Utils } from "../imports.js";
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
function genCommand(arg) {
|
||||
return ["sh", "-c", `${App.configDir}/bin/hyprctl_swallow ${arg}`];
|
||||
}
|
||||
|
||||
const swallowQuery = genCommand("query");
|
||||
const swallowToggle = genCommand("toggle");
|
||||
|
||||
export const getSwallowStatus = () => {
|
||||
execAsync(swallowQuery);
|
||||
|
||||
let result = exec("hyprctl -j getoption misc:enable_swallow");
|
||||
return JSON.parse(result).set;
|
||||
};
|
||||
|
||||
export const status = Variable(getSwallowStatus());
|
||||
|
||||
export const toggleSwallowStatus = () => {
|
||||
execAsync(swallowToggle);
|
||||
|
||||
// toggle swallow status
|
||||
status.value = !status.value;
|
||||
};
|
17
nyx/homes/notashelf/services/wayland/ags/js/utils/tray.js
Normal file
17
nyx/homes/notashelf/services/wayland/ags/js/utils/tray.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Widget, SystemTray } from "../imports.js";
|
||||
const { Button, Icon } = Widget;
|
||||
|
||||
export const getTrayItems = (self) => {
|
||||
self.children = SystemTray.items.map((item) =>
|
||||
Button({
|
||||
className: "trayIcon",
|
||||
child: Icon({
|
||||
setup: (self) => self.bind("icon", item, "icon"),
|
||||
}),
|
||||
setup: (self) =>
|
||||
self.bind("tooltip-markup", item, "tooltip-markup"),
|
||||
onPrimaryClick: (_, event) => item.activate(event),
|
||||
onSecondaryClick: (_, event) => item.openMenu(event),
|
||||
}),
|
||||
);
|
||||
};
|
15
nyx/homes/notashelf/services/wayland/ags/js/utils/weather.js
Normal file
15
nyx/homes/notashelf/services/wayland/ags/js/utils/weather.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Variable, App } from "../imports.js";
|
||||
|
||||
export const WeatherValue = Variable(
|
||||
{},
|
||||
{
|
||||
poll: [
|
||||
36000,
|
||||
["sh", "-c", `python ${App.configDir}/bin/weather`],
|
||||
(out) => JSON.parse(out),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
export const getWeatherIcon = (value) => value.text || "...";
|
||||
export const getWeatherTooltip = (value) => value.tooltip || "...";
|
|
@ -0,0 +1,71 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
const { Window, Box, CenterBox } = Widget;
|
||||
|
||||
// Widgets
|
||||
import { LauncherIcon } from "./modules/launcher.js";
|
||||
import { Workspaces } from "./modules/workspaces.js";
|
||||
import { Tray } from "./modules/tray.js";
|
||||
import { BatteryWidget } from "./modules/battery.js";
|
||||
import { Clock } from "./modules/clock.js";
|
||||
import { PowerMenu } from "./modules/power.js";
|
||||
import { Swallow } from "./modules/swallow.js";
|
||||
import { BluetoothWidget } from "./modules/bluetooth.js";
|
||||
import { AudioWidget } from "./modules/audio.js";
|
||||
import { NetworkWidget } from "./modules/network.js";
|
||||
import { SystemUsage } from "./modules/system.js";
|
||||
import { Weather } from "./modules/weather.js";
|
||||
|
||||
const Top = () =>
|
||||
Box({
|
||||
className: "barTop",
|
||||
vertical: true,
|
||||
vpack: "start",
|
||||
children: [LauncherIcon(), SystemUsage(), Weather()],
|
||||
});
|
||||
|
||||
const Center = () =>
|
||||
Box({
|
||||
className: "barCenter",
|
||||
vertical: true,
|
||||
children: [Workspaces()],
|
||||
});
|
||||
|
||||
const Bottom = () =>
|
||||
Box({
|
||||
className: "barBottom",
|
||||
vertical: true,
|
||||
vpack: "end",
|
||||
children: [
|
||||
Tray(),
|
||||
Box({
|
||||
className: "utilsBox",
|
||||
vertical: true,
|
||||
children: [
|
||||
BluetoothWidget(),
|
||||
AudioWidget(),
|
||||
Swallow(),
|
||||
BatteryWidget(),
|
||||
NetworkWidget(),
|
||||
],
|
||||
}),
|
||||
Clock(),
|
||||
PowerMenu(),
|
||||
],
|
||||
});
|
||||
|
||||
export const Bar = ({ monitor } = {}) =>
|
||||
Window({
|
||||
name: "bar",
|
||||
anchor: ["top", "bottom", "left"],
|
||||
exclusivity: "exclusive",
|
||||
layer: "top",
|
||||
margins: [8, 0, 8, 8],
|
||||
monitor,
|
||||
child: CenterBox({
|
||||
className: "bar",
|
||||
vertical: true,
|
||||
startWidget: Top(),
|
||||
centerWidget: Center(),
|
||||
endWidget: Bottom(),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
import { Audio, Widget } from "../../../imports.js";
|
||||
import { getAudioIcon } from "../../../utils/audio.js";
|
||||
import { launchApp } from "../../../utils/global.js";
|
||||
|
||||
const { Button, Icon } = Widget;
|
||||
|
||||
const AudioIcon = () =>
|
||||
Icon({
|
||||
setup: (self) => {
|
||||
self.hook(Audio, getAudioIcon, "speaker-changed");
|
||||
},
|
||||
});
|
||||
|
||||
export const AudioWidget = () => {
|
||||
return Button({
|
||||
className: "audio",
|
||||
cursor: "pointer",
|
||||
visible: true,
|
||||
child: AudioIcon(),
|
||||
onClicked: () => launchApp("pavucontrol"),
|
||||
});
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import { Widget, Battery } from "../../../imports.js";
|
||||
import {
|
||||
getBatteryPercentage,
|
||||
getBatteryTooltip,
|
||||
getBatteryIcon,
|
||||
} from "../../../utils/battery.js";
|
||||
const { Button, Box, Label, Revealer } = Widget;
|
||||
|
||||
const BatIcon = () =>
|
||||
Label({ className: "batIcon" })
|
||||
// NOTE: label needs to be used instead of icon here
|
||||
.bind("label", Battery, "percent", getBatteryIcon)
|
||||
.bind("tooltip-text", Battery, "percent", getBatteryTooltip);
|
||||
|
||||
const BatStatus = () =>
|
||||
Revealer({
|
||||
transition: "slide_down",
|
||||
transition_duration: 200,
|
||||
child: Label().bind("label", Battery, "percent", getBatteryPercentage),
|
||||
});
|
||||
|
||||
export const BatteryWidget = () =>
|
||||
Button({
|
||||
onPrimaryClick: (self) => {
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
child: Box({
|
||||
className: "battery",
|
||||
cursor: "pointer",
|
||||
vertical: true,
|
||||
children: [BatIcon(), BatStatus()],
|
||||
visible: Battery.bind("available"),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import { Bluetooth, Widget, Utils } from "../../../imports.js";
|
||||
import {
|
||||
getBluetoothIcon,
|
||||
getBluetoothLabel,
|
||||
getBluetoothClass,
|
||||
getBluetoothTooltip,
|
||||
} from "../../../utils/bluetooth.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
const BluetoothModule = () =>
|
||||
Label({ className: "bluetoothIcon" })
|
||||
.bind("label", Bluetooth, "connected-devices", getBluetoothIcon)
|
||||
.bind("class", Bluetooth, "connected-devices", getBluetoothClass)
|
||||
.bind("label", Bluetooth, "connected-devices", getBluetoothLabel)
|
||||
.bind(
|
||||
"tooltip-text",
|
||||
Bluetooth,
|
||||
"connected-devices",
|
||||
getBluetoothTooltip,
|
||||
);
|
||||
|
||||
export const BluetoothWidget = () =>
|
||||
Button({
|
||||
className: "bluetooth",
|
||||
cursor: "pointer",
|
||||
child: BluetoothModule(),
|
||||
visible: Bluetooth.connectedDevices.length > 0,
|
||||
onClicked: () => Utils.exec("blueman-applet"),
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import { Widget, Utils } from "../../../imports.js";
|
||||
const { exec, execAsync } = Utils;
|
||||
const { Label, Box } = Widget;
|
||||
|
||||
const Time = () =>
|
||||
Label({
|
||||
className: "timeLabel",
|
||||
setup: (self) => {
|
||||
// the current quote syntax is the only one that works
|
||||
// eslint-disable-next-line quotes
|
||||
self.poll(1000, (self) => (self.label = exec('date "+%H%n%M"')));
|
||||
self.poll(1000, (self) =>
|
||||
execAsync(["date", "+%H%n%M"])
|
||||
.then((time) => (self.label = time))
|
||||
// eslint-disable-next-line no-undef
|
||||
.catch(print.error),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const Clock = () =>
|
||||
Box({
|
||||
className: "clock",
|
||||
vertical: true,
|
||||
children: [Time()],
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { Widget, App } from "../../../imports.js";
|
||||
import { getLauncherIcon } from "../../../utils/launcher.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
export const LauncherIcon = () =>
|
||||
Button({
|
||||
vexpand: false,
|
||||
className: "launcherIcon",
|
||||
cursor: "pointer",
|
||||
child: Label(""),
|
||||
onClicked: () => App.toggleWindow("launcher"),
|
||||
setup: (self) => {
|
||||
self.hook(App, getLauncherIcon, "window-toggled");
|
||||
},
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import { Widget, Utils } from "../../../imports.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
export const Lock = () =>
|
||||
Button({
|
||||
className: "lock",
|
||||
cursor: "pointer",
|
||||
child: Label(""),
|
||||
onClicked: () => Utils.exec("swaylock"),
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import { Network, Widget, Utils } from "../../../imports.js";
|
||||
import {
|
||||
getWifiIcon,
|
||||
getWifiTooltip,
|
||||
getWiredIcon,
|
||||
getWiredTooltip,
|
||||
} from "../../../utils/network.js";
|
||||
const { Stack, Button, Label } = Widget;
|
||||
|
||||
const WifiIndicator = () =>
|
||||
Label({ has_tooltip: true })
|
||||
.bind("label", Network.wifi, "strength", getWifiIcon)
|
||||
.bind("tooltip-text", Network.wifi, "strength", getWifiTooltip);
|
||||
|
||||
const WiredIndicator = () =>
|
||||
Label({ cursor: "pointer" })
|
||||
.bind("label", Network.wired, "internet", getWiredIcon)
|
||||
.bind("tooltip-text", Network.wired, "internet", getWiredTooltip);
|
||||
|
||||
export const NetworkWidget = () =>
|
||||
Button({
|
||||
className: "network",
|
||||
cursor: "pointer",
|
||||
onClicked: () => Utils.exec("nm-connection-editor"),
|
||||
child: Stack({
|
||||
shown: Network.bind("primary").as(
|
||||
(/** @type {any} */ p) => p || "wifi",
|
||||
),
|
||||
children: {
|
||||
wifi: WifiIndicator(),
|
||||
wired: WiredIndicator(),
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import { Widget } from "../../../imports.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
export const PowerMenu = () =>
|
||||
Button({
|
||||
vexpand: false,
|
||||
className: "power",
|
||||
cursor: "pointer",
|
||||
child: Label(""),
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { Widget } from "../../../imports.js";
|
||||
const { Label, Button } = Widget;
|
||||
|
||||
import { toggleSwallowStatus, status } from "../../../utils/swallow.js";
|
||||
|
||||
export const Swallow = () =>
|
||||
Button({
|
||||
className: "swallow",
|
||||
cursor: "pointer",
|
||||
tooltipText: `Swallow: ${status.value}`,
|
||||
onPrimaryClick: toggleSwallowStatus,
|
||||
child: Label({
|
||||
label: "",
|
||||
}),
|
||||
}).hook(status, (self) => (self.tooltipText = `${status.value}`));
|
|
@ -0,0 +1,134 @@
|
|||
import { Variable, Widget } from "../../../imports.js";
|
||||
const { Button, Revealer, Box, Label, CircularProgress } = Widget;
|
||||
|
||||
const getMemClass = (v) => {
|
||||
const val = v * 100;
|
||||
const className = [
|
||||
[100, "memCritical"],
|
||||
[75, "memHigh"],
|
||||
[35, "memMod"],
|
||||
[5, "memLow"],
|
||||
[0, "memIdle"],
|
||||
[-1, "memRevealer"],
|
||||
].find(([threshold]) => threshold <= val)[1];
|
||||
|
||||
return className;
|
||||
};
|
||||
|
||||
const getCpuClass = (v) => {
|
||||
const val = v * 100;
|
||||
|
||||
const className = [
|
||||
[100, "cpuCritical"],
|
||||
[75, "cpuHigh"],
|
||||
[35, "cpuMod"],
|
||||
[5, "cpuLow"],
|
||||
[0, "cpuIdle"],
|
||||
[-1, "cpuRevealer"],
|
||||
].find(([threshold]) => threshold <= val)[1];
|
||||
|
||||
return className;
|
||||
};
|
||||
|
||||
const divide = ([total, free]) => free / total;
|
||||
|
||||
const cpu = Variable(0, {
|
||||
poll: [
|
||||
2000,
|
||||
"top -b -n 1",
|
||||
(out) =>
|
||||
divide([
|
||||
100,
|
||||
out
|
||||
.split("\n")
|
||||
.find((line) => line.includes("Cpu(s)"))
|
||||
.split(/\s+/)[1]
|
||||
.replace(",", "."),
|
||||
]),
|
||||
],
|
||||
});
|
||||
|
||||
const mem = Variable(0, {
|
||||
poll: [
|
||||
2000,
|
||||
"free",
|
||||
(out) =>
|
||||
divide(
|
||||
out
|
||||
.split("\n")
|
||||
.find((line) => line.includes("Mem:"))
|
||||
.split(/\s+/)
|
||||
.splice(1, 2),
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {typeof cpu | typeof ram} process
|
||||
* @param {Array<any>} extraChildren
|
||||
* @param {() => void} onPrimary
|
||||
*/
|
||||
const systemWidget = (name, process, extraChildren = [], onPrimary) =>
|
||||
Button({
|
||||
className: name + "Button",
|
||||
onPrimaryClick: onPrimary,
|
||||
child: Box({
|
||||
className: name,
|
||||
vertical: true,
|
||||
children: [
|
||||
CircularProgress({
|
||||
className: name + "Progress",
|
||||
// binds: [["value", process]],
|
||||
rounded: true,
|
||||
inverted: false,
|
||||
startAt: 0.27,
|
||||
}).bind("value", process),
|
||||
...extraChildren,
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const CPU = systemWidget(
|
||||
"cpu",
|
||||
cpu,
|
||||
[
|
||||
Revealer({
|
||||
transition: "slide_down",
|
||||
child: Label()
|
||||
.bind("label", cpu, "value", (v) => `${Math.floor(v * 100)}%`)
|
||||
.bind("className", cpu, "value", getCpuClass),
|
||||
transition_duration: 250,
|
||||
}),
|
||||
],
|
||||
(self) => {
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
);
|
||||
|
||||
const MEM = systemWidget(
|
||||
"mem",
|
||||
mem,
|
||||
[
|
||||
Revealer({
|
||||
transition: "slide_down",
|
||||
child: Label()
|
||||
.bind("label", mem, "value", (v) => `${Math.floor(v * 100)}%`)
|
||||
.bind("className", cpu, "value", getMemClass),
|
||||
transition_duration: 250,
|
||||
}),
|
||||
],
|
||||
(self) => {
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
);
|
||||
|
||||
export const SystemUsage = () =>
|
||||
Box({
|
||||
className: "systemUsage",
|
||||
vertical: true,
|
||||
cursor: "pointer",
|
||||
children: [CPU, MEM],
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import { Widget, SystemTray } from "../../../imports.js";
|
||||
import { getTrayItems } from "../../../utils/tray.js";
|
||||
const { Box, EventBox, Label, Revealer } = Widget;
|
||||
|
||||
const RevIcon = () =>
|
||||
Label({
|
||||
className: "trayChevron",
|
||||
label: "",
|
||||
});
|
||||
|
||||
const TrayItems = () =>
|
||||
Box({
|
||||
className: "trayIcons",
|
||||
vertical: true,
|
||||
setup: (self) => {
|
||||
self.hook(SystemTray, getTrayItems);
|
||||
},
|
||||
});
|
||||
|
||||
export const Tray = () =>
|
||||
EventBox({
|
||||
onPrimaryClick: (self) => {
|
||||
self.child.children[0].label = self.child.children[1].revealChild
|
||||
? ""
|
||||
: "";
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
child: Box({
|
||||
className: "tray",
|
||||
vertical: true,
|
||||
children: [
|
||||
RevIcon(),
|
||||
Revealer({
|
||||
transition: "slide_up",
|
||||
child: TrayItems(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import { Widget } from "../../../imports.js";
|
||||
import {
|
||||
WeatherValue,
|
||||
getWeatherIcon,
|
||||
getWeatherTooltip,
|
||||
} from "../../../utils/weather.js";
|
||||
const { Label } = Widget;
|
||||
|
||||
const weatherWidget = () =>
|
||||
Label({
|
||||
hexpand: false,
|
||||
vexpand: false,
|
||||
class_name: "weather",
|
||||
setup: (self) => {
|
||||
self.bind("label", WeatherValue, "value", getWeatherIcon);
|
||||
self.bind("tooltip-text", WeatherValue, "value", getWeatherTooltip);
|
||||
},
|
||||
});
|
||||
|
||||
export const Weather = () =>
|
||||
Widget.CenterBox({
|
||||
vertical: true,
|
||||
centerWidget: weatherWidget(),
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import { Widget, Hyprland } from "../../../imports.js";
|
||||
import { getFocusedWorkspace } from "../../../utils/hyprland.js";
|
||||
const { Box, Button } = Widget;
|
||||
const { messageAsync } = Hyprland;
|
||||
|
||||
export const Workspaces = () =>
|
||||
Box({
|
||||
className: "workspaces",
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: Array.from({ length: 10 }, (_, i) => i + 1).map((i) =>
|
||||
Button({
|
||||
cursor: "pointer",
|
||||
attribute: { index: i },
|
||||
onClicked: () => messageAsync(`dispatch workspace ${i}`),
|
||||
onSecondaryClick: () =>
|
||||
messageAsync(`dispatch movetoworkspacesilent ${i}`),
|
||||
}),
|
||||
),
|
||||
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, getFocusedWorkspace);
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
const { Box } = Widget;
|
||||
|
||||
export const DesktopIcons = () =>
|
||||
Box({
|
||||
className: "desktopIcons",
|
||||
vertical: true,
|
||||
hpack: "start",
|
||||
vpack: "start",
|
||||
children: [],
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
import { Widget, Utils } from "../../imports.js";
|
||||
const { Box, EventBox, Label, MenuItem, Menu } = Widget;
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
/**
|
||||
* Creates a menu item with an icon.
|
||||
* @param {string} icon - The icon to display for the menu item.
|
||||
* @param {string} itemLabel - The label for the menu item.
|
||||
* @param {Function} onClick - The function to be executed when the menu item is activated.
|
||||
* @returns {Object} A menu item object with the specified icon, label, and click action.
|
||||
*/
|
||||
function ItemWithIcon(icon, itemLabel, onClick) {
|
||||
return MenuItem({
|
||||
className: "desktopMenuItem",
|
||||
child: Box({
|
||||
children: [
|
||||
Label({
|
||||
className: "desktopMenuItemIcon",
|
||||
label: icon,
|
||||
}),
|
||||
Label(itemLabel),
|
||||
],
|
||||
}),
|
||||
onActivate: onClick,
|
||||
});
|
||||
}
|
||||
|
||||
const Separator = () =>
|
||||
MenuItem({
|
||||
child: Box({
|
||||
className: "separator",
|
||||
css: `
|
||||
min-height: 1px;
|
||||
margin: 3px 6px;
|
||||
`,
|
||||
}),
|
||||
});
|
||||
|
||||
const rioMenu = () => {
|
||||
return [
|
||||
ItemWithIcon("", "Terminal", () =>
|
||||
exec(
|
||||
'sh -c "$HOME/.config/ags/bin/open_window `slurp -d -c 999999 -w 2` foot"',
|
||||
),
|
||||
),
|
||||
ItemWithIcon("", "Resize", () =>
|
||||
exec(
|
||||
'sh -c "$HOME/.config/ags/bin/move_window `slurp -d -c 999999 -w 2`"',
|
||||
),
|
||||
),
|
||||
ItemWithIcon("", "Move", () => exec("hyprctl dispatch submap move")),
|
||||
ItemWithIcon("", "Delete", () => exec("hyprctl kill")),
|
||||
Separator(),
|
||||
];
|
||||
};
|
||||
|
||||
const Powermenu = () => {
|
||||
return MenuItem({
|
||||
className: "desktopMenuItem",
|
||||
child: Box({
|
||||
children: [
|
||||
Label({
|
||||
className: "desktopMenuItemIcon",
|
||||
label: "",
|
||||
}),
|
||||
Label("Powermenu"),
|
||||
],
|
||||
}),
|
||||
submenu: Menu({
|
||||
className: "desktopMenu",
|
||||
children: [
|
||||
ItemWithIcon("", "Lock", () => Utils.exec("gtklock")),
|
||||
ItemWithIcon("", "Log Out", () =>
|
||||
exec("hyprctl dispatch exit"),
|
||||
),
|
||||
ItemWithIcon("", "Suspend", () => exec("systemctl suspend")),
|
||||
ItemWithIcon("", "Reboot", () => exec("systemctl reboot")),
|
||||
ItemWithIcon("", "Shutdown", () => exec("systemctl poweroff")),
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const DesktopMenu = () =>
|
||||
EventBox({
|
||||
onSecondaryClick: (_, event) =>
|
||||
Menu({
|
||||
className: "desktopMenu",
|
||||
children: [
|
||||
...rioMenu(),
|
||||
ItemWithIcon("", "Colorpicker", () =>
|
||||
execAsync(["hyprpicker", "-a", "wl-copy"]),
|
||||
),
|
||||
Separator(),
|
||||
Powermenu(),
|
||||
],
|
||||
}).popup_at_pointer(event),
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
const { Window } = Widget;
|
||||
|
||||
import { DesktopMenu } from "./desktopMenu.js";
|
||||
import { DesktopIcons } from "./desktopIcons.js";
|
||||
|
||||
export const Desktop = ({ monitor } = {}) =>
|
||||
Window({
|
||||
name: "desktop",
|
||||
anchor: ["top", "bottom", "left", "right"],
|
||||
layer: "bottom",
|
||||
monitor,
|
||||
child: Widget.Overlay({
|
||||
child: DesktopMenu(),
|
||||
overlays: [DesktopIcons()],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
import { Widget, App, Applications, Utils, Hyprland } from "../../imports.js";
|
||||
import PopupWindow from "../../utils/popupWindow.js";
|
||||
const { Box, Button, Icon, Label, Scrollable, Entry } = Widget;
|
||||
|
||||
const WINDOW_NAME = "launcher";
|
||||
|
||||
const truncateString = (str, maxLength) =>
|
||||
str.length > maxLength ? `${str.slice(0, maxLength)}...` : str;
|
||||
|
||||
const AppItem = (app) =>
|
||||
Button({
|
||||
className: "launcherApp",
|
||||
onClicked: () => {
|
||||
App.closeWindow(WINDOW_NAME);
|
||||
Hyprland.messageAsync(`dispatch exec gtk-launch ${app.desktop}`);
|
||||
++app.frequency;
|
||||
},
|
||||
setup: (self) => (self.app = app),
|
||||
child: Box({
|
||||
children: [
|
||||
Icon({
|
||||
className: "launcherItemIcon",
|
||||
icon: app.iconName || "",
|
||||
size: 24,
|
||||
}),
|
||||
Box({
|
||||
className: "launcherItem",
|
||||
vertical: true,
|
||||
vpack: "center",
|
||||
children: [
|
||||
Label({
|
||||
className: "launcherItemTitle",
|
||||
label: app.name,
|
||||
xalign: 0,
|
||||
vpack: "center",
|
||||
truncate: "end",
|
||||
}),
|
||||
!!app.description &&
|
||||
Widget.Label({
|
||||
className: "launcherItemDescription",
|
||||
label:
|
||||
truncateString(app.description, 75) || "",
|
||||
wrap: true,
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
vpack: "center",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const Launcher = () => {
|
||||
const list = Box({ vertical: true });
|
||||
|
||||
const entry = Entry({
|
||||
className: "launcherEntry",
|
||||
hexpand: true,
|
||||
text: "-",
|
||||
onAccept: ({ text }) => {
|
||||
const isCommand = text.startsWith(">");
|
||||
const appList = Applications.query(text || "");
|
||||
if (isCommand === true) {
|
||||
App.toggleWindow(WINDOW_NAME);
|
||||
Utils.execAsync(text.slice(1));
|
||||
} else if (appList[0]) {
|
||||
App.toggleWindow(WINDOW_NAME);
|
||||
appList[0].launch();
|
||||
}
|
||||
},
|
||||
onChange: ({ text }) =>
|
||||
list.children.map((item) => {
|
||||
item.visible = item.app.match(text);
|
||||
}),
|
||||
});
|
||||
|
||||
return Widget.Box({
|
||||
className: "launcher",
|
||||
vertical: true,
|
||||
setup: (self) => {
|
||||
self.hook(App, (_, name, visible) => {
|
||||
if (name !== WINDOW_NAME) return;
|
||||
|
||||
list.children = Applications.list.map(AppItem);
|
||||
|
||||
entry.text = "";
|
||||
if (visible) entry.grab_focus();
|
||||
});
|
||||
},
|
||||
children: [
|
||||
entry,
|
||||
Scrollable({
|
||||
hscroll: "never",
|
||||
css: "min-width: 250px; min-height: 360px;",
|
||||
child: list,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export const AppLauncher = () =>
|
||||
PopupWindow({
|
||||
name: WINDOW_NAME,
|
||||
anchor: ["top", "bottom", "right"],
|
||||
margins: [13, 13, 0, 13],
|
||||
layer: "overlay",
|
||||
transition: "slide_down",
|
||||
transitionDuration: 150,
|
||||
popup: true,
|
||||
keymode: "on-demand",
|
||||
child: Launcher(),
|
||||
});
|
29
nyx/homes/notashelf/services/wayland/ags/js/windows/music/controls.js
vendored
Normal file
29
nyx/homes/notashelf/services/wayland/ags/js/windows/music/controls.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Icons, Widget } from "../../imports.js";
|
||||
import { mprisStateIcon } from "../../utils/mpris.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.CenterBox({
|
||||
className: "controls",
|
||||
hpack: "center",
|
||||
|
||||
startWidget: Widget.Button({
|
||||
onClicked: () => player.previous(),
|
||||
child: Widget.Icon(Icons.media.previous),
|
||||
}),
|
||||
|
||||
centerWidget: Widget.Button({
|
||||
onClicked: () => player.playPause(),
|
||||
|
||||
child: Widget.Icon().bind(
|
||||
"icon",
|
||||
player,
|
||||
"play-back-status",
|
||||
mprisStateIcon,
|
||||
),
|
||||
}),
|
||||
|
||||
endWidget: Widget.Button({
|
||||
onClicked: () => player.next(),
|
||||
child: Widget.Icon(Icons.media.next),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({ className: "cover" }).bind(
|
||||
"css",
|
||||
player,
|
||||
"cover-path",
|
||||
(cover) => `background-image: url('${cover ?? ""}')`,
|
||||
);
|
|
@ -0,0 +1,45 @@
|
|||
import { Mpris, Widget } from "../../imports.js";
|
||||
import { findPlayer, generateBackground } from "../../utils/mpris.js";
|
||||
import PopupWindow from "./popup_window.js";
|
||||
|
||||
import Cover from "./cover.js";
|
||||
import { Artists, Title } from "./title_artists.js";
|
||||
import TimeInfo from "./time_info.js";
|
||||
import Controls from "./controls.js";
|
||||
import PlayerInfo from "./player_info.js";
|
||||
|
||||
const Info = (player) =>
|
||||
Widget.Box({
|
||||
className: "info",
|
||||
vertical: true,
|
||||
vexpand: false,
|
||||
hexpand: false,
|
||||
homogeneous: true,
|
||||
|
||||
children: [
|
||||
PlayerInfo(player),
|
||||
Title(player),
|
||||
Artists(player),
|
||||
Controls(player),
|
||||
TimeInfo(player),
|
||||
],
|
||||
});
|
||||
|
||||
const MusicBox = (player) =>
|
||||
Widget.Box({
|
||||
className: "music window",
|
||||
children: [Cover(player), Info(player)],
|
||||
}).bind("css", player, "cover-path", generateBackground);
|
||||
|
||||
export const Media = () =>
|
||||
PopupWindow({
|
||||
monitor: 0,
|
||||
anchor: ["top"],
|
||||
layer: "top",
|
||||
margins: [8, 0, 0, 0],
|
||||
name: "music",
|
||||
child: Widget.Box(),
|
||||
}).bind("child", Mpris, "players", (players) => {
|
||||
if (players.length == 0) return Widget.Box();
|
||||
return MusicBox(findPlayer(players));
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import { Icons, Utils, Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
className: "player-info",
|
||||
vexpand: true,
|
||||
vpack: "start",
|
||||
|
||||
children: [
|
||||
Widget.Icon({
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
className: "player-icon",
|
||||
tooltipText: player.identity ?? "",
|
||||
}).bind("icon", player, "entry", (entry) => {
|
||||
// the Spotify icon is called spotify-client
|
||||
if (entry == "spotify") entry = "spotify-client";
|
||||
return Utils.lookUpIcon(entry ?? "")
|
||||
? entry
|
||||
: Icons.media.player;
|
||||
}),
|
||||
],
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { Widget } from "../../imports.js";
|
||||
const { Box, Revealer, Window } = Widget;
|
||||
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
revealerSetup = null,
|
||||
transition = "crossfade",
|
||||
transitionDuration = 200,
|
||||
...props
|
||||
}) => {
|
||||
const window = Window({
|
||||
name,
|
||||
popup: false,
|
||||
focusable: false,
|
||||
visible: false,
|
||||
...props,
|
||||
|
||||
setup: (self) => (self.getChild = () => child),
|
||||
|
||||
child: Box({
|
||||
css: `
|
||||
min-height: 1px;
|
||||
min-width: 1px;
|
||||
padding: 1px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition,
|
||||
transitionDuration,
|
||||
child: child,
|
||||
|
||||
setup:
|
||||
revealerSetup ??
|
||||
((self) =>
|
||||
self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === name) {
|
||||
self.reveal_child = visible;
|
||||
}
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
return window;
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
import { lengthStr } from "../../utils/mpris.js";
|
||||
|
||||
export const PositionLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "position",
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
|
||||
setup: (self) => {
|
||||
const update = (_, time) => {
|
||||
player.length > 0
|
||||
? (self.label = lengthStr(time || player.position))
|
||||
: (self.visible = !!player);
|
||||
};
|
||||
|
||||
self.hook(player, update, "position").poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export const LengthLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "length",
|
||||
hexpand: true,
|
||||
xalign: 1,
|
||||
})
|
||||
.bind("visible", player, "length", (length) => length > 0)
|
||||
.bind("label", player, "length", (length) => lengthStr(length));
|
||||
|
||||
export const Position = (player) =>
|
||||
Widget.Slider({
|
||||
className: "position",
|
||||
draw_value: false,
|
||||
|
||||
onChange: ({ value }) => (player.position = player.length * value),
|
||||
|
||||
setup: (self) => {
|
||||
const update = () => {
|
||||
if (self.dragging) return;
|
||||
|
||||
self.visible = player.length > 0;
|
||||
|
||||
if (player.length > 0) {
|
||||
self.value = player.position / player.length;
|
||||
}
|
||||
};
|
||||
|
||||
self.hook(player, update)
|
||||
.hook(player, update, "position")
|
||||
.poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
vpack: "end",
|
||||
|
||||
children: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
children: [PositionLabel(player), LengthLabel(player)],
|
||||
}),
|
||||
Position(player),
|
||||
],
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
|
||||
export const Title = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "title",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({
|
||||
className: "title",
|
||||
label: "Nothing playing",
|
||||
}).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-title",
|
||||
(title) => title ?? "Nothing playing",
|
||||
),
|
||||
});
|
||||
|
||||
export const Artists = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "artists",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({ className: "artists" }).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-artists",
|
||||
(artists) => artists.join(", ") ?? "",
|
||||
),
|
||||
});
|
|
@ -0,0 +1,134 @@
|
|||
import { Hyprland, Notifications, Utils, Widget } from "../../imports.js";
|
||||
const { Box, Icon, Label, Button, EventBox, Window } = Widget;
|
||||
const { lookUpIcon } = Utils;
|
||||
|
||||
const closeAll = () => {
|
||||
Notifications.popups.map((n) => n.dismiss());
|
||||
};
|
||||
|
||||
const NotificationIcon = ({ app_entry, app_icon, image }) => {
|
||||
if (image) {
|
||||
return Box({
|
||||
css: `
|
||||
background-image: url("${image}");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
if (lookUpIcon(app_icon)) {
|
||||
return Icon(app_icon);
|
||||
}
|
||||
|
||||
if (app_entry && lookUpIcon(app_entry)) {
|
||||
return Icon(app_entry);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Notification = (notif) => {
|
||||
const icon = Box({
|
||||
vpack: "start",
|
||||
class_name: "icon",
|
||||
// @ts-ignore
|
||||
setup: (/** @type {{ child: any; }} */ self) => {
|
||||
const icon = NotificationIcon(notif);
|
||||
if (icon !== null) self.child = icon;
|
||||
},
|
||||
});
|
||||
|
||||
const title = Label({
|
||||
class_name: "title",
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
hexpand: true,
|
||||
max_width_chars: 24,
|
||||
truncate: "end",
|
||||
wrap: true,
|
||||
label: notif.summary,
|
||||
use_markup: true,
|
||||
});
|
||||
|
||||
const body = Label({
|
||||
class_name: "body",
|
||||
hexpand: true,
|
||||
use_markup: true,
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
max_width_chars: 100,
|
||||
wrap: true,
|
||||
label: notif.body,
|
||||
});
|
||||
|
||||
const actions = Box({
|
||||
class_name: "actions",
|
||||
children: notif.actions
|
||||
.filter(({ id }) => id != "default")
|
||||
.map(({ id, label }) =>
|
||||
Button({
|
||||
class_name: "action-button",
|
||||
on_clicked: () => notif.invoke(id),
|
||||
hexpand: true,
|
||||
child: Label(label),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return EventBox({
|
||||
on_primary_click: () => {
|
||||
if (notif.actions.length > 0) notif.invoke(notif.actions[0].id);
|
||||
},
|
||||
on_middle_click: closeAll,
|
||||
on_secondary_click: () => notif.dismiss(),
|
||||
child: Box({
|
||||
class_name: `notification ${notif.urgency}`,
|
||||
vertical: true,
|
||||
|
||||
children: [
|
||||
Box({
|
||||
class_name: "info",
|
||||
children: [
|
||||
icon,
|
||||
Box({
|
||||
vertical: true,
|
||||
class_name: "text",
|
||||
vpack: "center",
|
||||
|
||||
setup: (self) => {
|
||||
if (notif.body.length > 0)
|
||||
self.children = [title, body];
|
||||
else self.children = [title];
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
actions,
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
let lastMonitor;
|
||||
export const Notifs = () =>
|
||||
Window({
|
||||
name: "notifications",
|
||||
anchor: ["top", "right"],
|
||||
margins: [8, 8, 8, 0],
|
||||
child: Box({
|
||||
css: "padding: 1px;",
|
||||
class_name: "notifications",
|
||||
vertical: true,
|
||||
// @ts-ignore
|
||||
children: Notifications.bind("popups").transform((popups) => {
|
||||
return popups.map(Notification);
|
||||
}),
|
||||
}),
|
||||
}).hook(Hyprland.active, (self) => {
|
||||
// prevent useless resets
|
||||
if (lastMonitor === Hyprland.active.monitor) return;
|
||||
|
||||
self.monitor = Hyprland.active.monitor.id;
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import { Widget, Utils } from "../../imports.js";
|
||||
import Brightness from "../../services/brightness.js";
|
||||
const { Box, Slider, Label, Revealer } = Widget;
|
||||
|
||||
const BrightnessIcon = () =>
|
||||
Label({
|
||||
className: "brtPopupIcon",
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self) => {
|
||||
const icons = ["", "", "", "", "", "", "", "", ""];
|
||||
|
||||
let index = Math.floor((Brightness.screen * 100) / 11);
|
||||
index = Math.max(0, Math.min(index, icons.length - 1));
|
||||
|
||||
if (index >= 0 && index < icons.length) {
|
||||
self.label = icons[index].toString();
|
||||
} else {
|
||||
log("Index out of bounds:", index);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const PercentBar = () =>
|
||||
Slider({
|
||||
className: "brtPopupBar",
|
||||
drawValue: false,
|
||||
onChange: ({ value }) => (Brightness.screen = value),
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self) => (self.value = Brightness.screen));
|
||||
},
|
||||
});
|
||||
|
||||
export const BrightnessPopup = () =>
|
||||
Box({
|
||||
css: `min-height: 1px;
|
||||
min-width: 1px;`,
|
||||
child: Revealer({
|
||||
transition: "slide_up",
|
||||
child: Box({
|
||||
className: "brightnessPopup",
|
||||
children: [BrightnessIcon(), PercentBar()],
|
||||
}),
|
||||
attribute: { count: 0 },
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self) => {
|
||||
self.revealChild = true;
|
||||
self.attribute.count++;
|
||||
Utils.timeout(1500, () => {
|
||||
self.attribute.count--;
|
||||
|
||||
if (self.attribute.count === 0)
|
||||
self.revealChild = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
|
||||
// Widgets
|
||||
import { BrightnessPopup } from "./brightnessPopup.js";
|
||||
import { VolumePopup } from "./volumePopup.js";
|
||||
|
||||
export const Popups = () =>
|
||||
Widget.Window({
|
||||
name: "popups",
|
||||
className: "popups",
|
||||
anchor: ["bottom", "right"],
|
||||
layer: "overlay",
|
||||
margins: [0, 12, 8, 0],
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [BrightnessPopup(), VolumePopup()],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
import { Widget, Utils, Audio } from "../../imports.js";
|
||||
import { getSliderIcon, volumePercentBar } from "../../utils/audio.js";
|
||||
const { Box, Revealer } = Widget;
|
||||
const { speaker } = Audio;
|
||||
const { timeout } = Utils;
|
||||
|
||||
export const VolumePopup = () =>
|
||||
Box({
|
||||
css: `
|
||||
min-height: 2px;
|
||||
min-width: 2px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition: "slide_up",
|
||||
child: Box({
|
||||
className: "volumePopup",
|
||||
children: [getSliderIcon(), volumePercentBar()],
|
||||
}),
|
||||
attribute: { count: 0 },
|
||||
setup: (self) =>
|
||||
self.hook(
|
||||
speaker,
|
||||
() => {
|
||||
self.reveal_child = true;
|
||||
self.attribute.count++;
|
||||
timeout(1500, () => {
|
||||
self.attribute.count--;
|
||||
|
||||
if (self.attribute.count === 0)
|
||||
self.reveal_child = false;
|
||||
});
|
||||
},
|
||||
"notify::volume",
|
||||
),
|
||||
}),
|
||||
});
|
18
nyx/homes/notashelf/services/wayland/ags/package.json
Normal file
18
nyx/homes/notashelf/services/wayland/ags/package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "nyx-ags",
|
||||
"version": "1.5.5",
|
||||
"author": "NotAShelf",
|
||||
"description": "The ags configuretion segment of my NixOS configurations.",
|
||||
"main": "config.js",
|
||||
"scripts": {
|
||||
"lint": "eslint . --fix",
|
||||
"stylelint": "stylelint ./scss --fix",
|
||||
"all": "nix-shell -p nodejs --run \"npm install\" && npm run lint && npm run stylelint && rm -rf node_modules"
|
||||
},
|
||||
"devDependencies": {
|
||||
"stylelint-config-standard-scss": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"eslint": "^8.44.0"
|
||||
}
|
||||
}
|
1709
nyx/homes/notashelf/services/wayland/ags/pnpm-lock.yaml
generated
Normal file
1709
nyx/homes/notashelf/services/wayland/ags/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
28
nyx/homes/notashelf/services/wayland/ags/shell.nix
Normal file
28
nyx/homes/notashelf/services/wayland/ags/shell.nix
Normal file
|
@ -0,0 +1,28 @@
|
|||
{pkgs ? import <nixpkgs> {}}: let
|
||||
# trivial builders
|
||||
inherit (pkgs) mkShell writeShellScriptBin;
|
||||
in
|
||||
mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs-slim
|
||||
# python3 w/ requests is necessary for weather data fetch
|
||||
# ags actually doesn't start without it since it's stored
|
||||
# as a variable
|
||||
(python3.withPackages (ps: [ps.requests]))
|
||||
|
||||
# while developing locally, you need types and other eslint deps
|
||||
# so that our eslint config works properly
|
||||
# pnpm is used to fetch the deps from package.json
|
||||
nodePackages.pnpm
|
||||
|
||||
# dart-sass is for compiling the stylesheets
|
||||
dart-sass
|
||||
(writeShellScriptBin "compile-stylesheet" ''
|
||||
# compile scss files
|
||||
${dart-sass}/bin/sass --verbose \
|
||||
--style compressed \
|
||||
--no-source-map --fatal-deprecation --future-deprecation \
|
||||
./style/main.scss > ./style.css
|
||||
'')
|
||||
];
|
||||
}
|
1
nyx/homes/notashelf/services/wayland/ags/style.css
Normal file
1
nyx/homes/notashelf/services/wayland/ags/style.css
Normal file
File diff suppressed because one or more lines are too long
34
nyx/homes/notashelf/services/wayland/ags/style/bar/_bar.scss
Normal file
34
nyx/homes/notashelf/services/wayland/ags/style/bar/_bar.scss
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Components
|
||||
// top
|
||||
@import "modules/launcher";
|
||||
@import "modules/system";
|
||||
@import "modules/weather";
|
||||
|
||||
// center
|
||||
@import "modules/workspaces";
|
||||
|
||||
// bottom
|
||||
@import "modules/tray";
|
||||
@import "modules/battery";
|
||||
@import "modules/bluetooth";
|
||||
@import "modules/lock";
|
||||
@import "modules/swallow";
|
||||
@import "modules/audio";
|
||||
@import "modules/net";
|
||||
@import "modules/clock";
|
||||
@import "modules/power";
|
||||
|
||||
// general config
|
||||
.bar {
|
||||
@include barWindow;
|
||||
}
|
||||
|
||||
// top section
|
||||
.utilsBox {
|
||||
@include barSection;
|
||||
}
|
||||
|
||||
// bottom section
|
||||
.systemInfo {
|
||||
@include barSection;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.audio {
|
||||
@include barModule;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.battery {
|
||||
@include barModule;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// default state of the bluetooth icon
|
||||
.bluetooth {
|
||||
@include barModule;
|
||||
}
|
||||
|
||||
// if bluetooth is paired
|
||||
// but not active
|
||||
.bluetooth-paired {
|
||||
color: $onSurface;
|
||||
}
|
||||
|
||||
// if bluetooth is paired
|
||||
// and active
|
||||
.bluetooth-active {
|
||||
color: $onSurface;
|
||||
}
|
||||
|
||||
// if bluetooth is disabled
|
||||
.bluetooth-disabled {
|
||||
color: lighten($surfaceVariant, 30%);
|
||||
transition:
|
||||
all 0.3s $materialStandard,
|
||||
border 0.35s $materialStandard;
|
||||
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.clock {
|
||||
background: $surfaceVariant;
|
||||
color: $onPrimary;
|
||||
font-family: $monoFont;
|
||||
font-weight: 800;
|
||||
border-radius: 12px;
|
||||
margin: 6px 4px;
|
||||
padding: 6px;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
.launcherIcon {
|
||||
background: $surfaceVariant;
|
||||
font-family: $iconFont;
|
||||
border-radius: 12px;
|
||||
margin: 6px 4px;
|
||||
padding: 6px;
|
||||
min-height: 1.5rem;
|
||||
transition: all 0.2s $materialAccel;
|
||||
|
||||
&:hover {
|
||||
background: lighten($surfaceVariant, 5%);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.lock {
|
||||
background: $surface;
|
||||
font-size: 24px;
|
||||
border-radius: 12px;
|
||||
margin: 2px 4px;
|
||||
padding: 2px;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.network {
|
||||
@include barModule;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
.power {
|
||||
color: $red;
|
||||
background: $surface;
|
||||
font-size: 24px;
|
||||
border-radius: 12px;
|
||||
margin: 6px 4px;
|
||||
padding: 6px;
|
||||
min-height: 1.5rem;
|
||||
transition: all 0.2s $materialAccel;
|
||||
|
||||
&:hover {
|
||||
background: lighten($surfaceVariant, 5%);
|
||||
color: lighten($red, 5%);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.swallow {
|
||||
@include barModule;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
$size: 1.2rem;
|
||||
|
||||
.systemUsage {
|
||||
color: $onSurface;
|
||||
background: $surfaceVariant;
|
||||
font-family: $iconFont;
|
||||
border-radius: 12px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
// cpu indicator
|
||||
.cpuButton {
|
||||
padding: 6px 2px 3px 2px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.cpuProgress {
|
||||
color: $lavender;
|
||||
padding: 4px 4px;
|
||||
margin: 0.1rem;
|
||||
font-size: 4px;
|
||||
background: $primary;
|
||||
min-height: $size;
|
||||
min-width: $size;
|
||||
}
|
||||
|
||||
// memory indicator
|
||||
.memButton {
|
||||
padding: 3px 2px 6px 2px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.memProgress {
|
||||
color: $blue;
|
||||
padding: 4px 4px;
|
||||
margin: 0.1rem;
|
||||
font-size: 4px;
|
||||
background: $primary;
|
||||
min-height: $size;
|
||||
min-width: $size;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
.tray {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.trayChevron {
|
||||
font-family: $iconFont;
|
||||
}
|
||||
|
||||
.trayIcons {
|
||||
margin: 3px 0 0;
|
||||
}
|
||||
|
||||
.trayIcon {
|
||||
margin: 0 0 3px;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
.weather {
|
||||
background: $surfaceVariant;
|
||||
font-family: "Material Symbols Sharp", Roboto;
|
||||
border-radius: 12px;
|
||||
margin: 6px 4px;
|
||||
padding: 4px;
|
||||
min-height: 1.5rem;
|
||||
min-width: 1rem;
|
||||
transition: all 0.2s $materialAccel;
|
||||
|
||||
&:hover {
|
||||
background: lighten($surfaceVariant, 5%);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue