426 Commits
v4 .. v3
Author SHA1 Message Date
janishutz 75a9e6d953 feat: add current gtk config 2026-06-15 09:31:06 +02:00
janishutz 85f3db3616 fix: add libastal meta package to install 2026-06-15 09:19:13 +02:00
janishutz fc51bb593a fix: trailing commas, explicitly installed pkgs list 2026-06-13 18:08:26 +02:00
janishutz 14ef414781 Update README.md 2026-06-13 15:09:31 +02:00
janishutz 205e667343 feat(hypr): New window rules 2026-06-11 13:12:51 +02:00
janishutz b7e431b3fa feat(hypr): improve muting bind 2026-06-07 14:34:10 +02:00
janishutz 9602c673e5 [Hypr] move lua setup to new folder, add bind to toggle muting of default mic 2026-05-30 10:40:54 +02:00
janishutz e73e7b2353 feat: fix eslint config 2026-05-30 10:14:24 +02:00
janishutz 820e4c9982 [Hypr] Start new hyprland config 2026-05-12 11:43:42 +02:00
janishutz 4b9bbde225 [Hyprland] Temporary fix before full config rewrite in lua 2026-05-11 12:31:40 +02:00
janishutz 82f375794b [Yazi] Use package manager for themes 2026-05-11 12:03:06 +02:00
janishutz a87f908b4d [Hyrpland] Improve bind 2026-05-09 14:39:42 +02:00
janishutz e44f8728a8 [Hypridle] Improve memory footprint 2026-04-29 11:50:07 +02:00
janishutz bb737e524e [Build] Change wallpaper 2026-04-23 16:45:28 +02:00
janishutz fe14b2c78f [Astal] Notifications: Delete not working version 2026-04-23 10:45:03 +02:00
janishutz 50012b04bc [Hyprland] Add windowrule for steam games 2026-04-09 15:16:48 +02:00
janishutz 5e8f2e8930 [Hyprland] Speed up animations 2026-03-25 14:49:33 +01:00
janishutz af675c0683 [Hyprland] Improve touchpad binds 2026-03-25 12:24:26 +01:00
janishutz e03eb2ddd9 [Hyprland] Add more dwindle binds 2026-03-18 16:06:05 +01:00
janishutz 9816e79eec [Hyprland] Add binds to disable trackpad 2026-03-18 11:09:58 +01:00
janishutz 3600c5bcd3 [Fish] Add git pull rebase alias 2026-03-12 17:39:25 +01:00
janishutz 42800f5803 [Hyprland] Default to 120hz on laptop 2026-03-12 11:21:16 +01:00
janishutz bd46b4d609 [Fish] Add alias for commonly accidentally run command 2026-03-11 18:08:16 +01:00
janishutz 79e23782cc [Hyprland] Add windowrules for LÖVR 2026-03-11 16:50:01 +01:00
janishutz aec0f4efd1 [Yazi] Add better keymap for eth stuff 2026-03-11 14:16:10 +01:00
janishutz 35cc75ee2e Revert "[Hyprland] Move to qalculate-gtk from qt"
This reverts commit 53d90fc8f5.
2026-02-25 17:37:26 +01:00
janishutz 53d90fc8f5 [Hyprland] Move to qalculate-gtk from qt 2026-02-25 17:35:16 +01:00
janishutz 5f1f69a6db [Build] Use other wallpaper 2026-02-25 09:38:59 +01:00
janishutz c37a641060 [Yazi] Add new keybind 2026-02-23 19:09:46 +01:00
janishutz d6a6d62ad6 [Yazi] Finish new semester update 2026-02-20 16:37:45 +01:00
janishutz 01ed1dbdda [Fish] Update to 4.3 fish 2026-02-20 12:35:33 +01:00
janishutz 2ac5a4af14 [Yazi] Add new semester binds 2026-02-17 15:33:15 +01:00
janishutz e7b418007f [Yazi] Keybinds 2026-02-17 13:58:02 +01:00
janishutz 0b896e404c [Hyprland] Windowrules 2026-02-17 13:57:55 +01:00
janishutz c38907ec39 [Hyprland] add launch command for qutebrowser 2026-02-02 09:07:09 +01:00
janishutz 1721651319 [Hyprland] Add binds for qalculate 2026-01-23 13:41:07 +01:00
janishutz e4692c456c [Hyprland] Make steam not start in floating mode 2026-01-22 19:48:26 +01:00
janishutz 6ee881c7dc [Hyprland] Update for new keyboard having native ability to swap caps and escape 2026-01-19 11:08:22 +01:00
janishutz 2649ee4b56 [Hyprland] Remove hyprpm reload 2026-01-09 17:41:47 +01:00
janishutz 4024b9e830 [Setup] Improve color extraction 2026-01-09 16:23:30 +01:00
janishutz 11767c3009 [Astal] Prepare for reloadable style 2026-01-09 16:04:51 +01:00
janishutz c2cd216dac [Scripts] Another incorrect filename fix 2026-01-09 15:04:45 +01:00
janishutz 5c245f9c35 [Scripts] Fix incorrect filename 2026-01-09 14:46:04 +01:00
janishutz 54fb71eeae [Scripts] Add script to add latexindent 2026-01-09 14:45:09 +01:00
janishutz 44642960ad [Hyprland] Remove vivado shenanigans 2026-01-09 07:45:59 +01:00
janishutz f8b7f00f44 [Hyprland] Flat accel profile 2026-01-06 14:03:59 +01:00
janishutz 069139641f [Yazi] Update to new conf 2026-01-06 13:03:33 +01:00
janishutz 17689ac0e8 [Hyprland] Update config to newest version 2026-01-01 15:10:39 +01:00
janishutz 5581267a73 [Fish] Add alias for flutter via fvm 2026-01-01 12:50:09 +01:00
janishutz e2fd60187a [Hyprland] Improve scrolling 2025-12-19 11:08:09 +01:00
janishutz 37ff1af0ed [Kitty] Improve scrolling 2025-12-19 11:04:12 +01:00
janishutz 37849dee8f [Hyprland] Finish change to us-intl layout 2025-12-14 11:05:19 +01:00
janishutz fe36f97774 [Eslint] More fixes 2025-12-13 14:29:12 +01:00
janishutz e9399cf7bb [Eslint] Fix typing 2025-12-13 14:28:22 +01:00
janishutz c453e7aa90 [Hyprland] improve notifications 2025-12-09 11:26:04 +01:00
janishutz 5c069beb2b [Hyprland] Add notifications to layout change 2025-12-09 11:24:07 +01:00
janishutz 0a555e9e5a [Hyprland] update bind 2025-12-09 11:22:52 +01:00
janishutz c7e82042af [Hyprland] update keyboard layout and add layout change binds 2025-12-09 11:17:07 +01:00
janishutz c421ea2cb3 [Hyprland] Fix binds 2025-12-08 10:29:08 +01:00
janishutz 84b008e29c [Hyprland] Extract gestures into separate file 2025-12-08 10:28:11 +01:00
janishutz 007c3d0bf0 [Eslint] Fix config error 2025-12-04 14:50:46 +01:00
janishutz 8872abecbd [Eslint] Update config 2025-12-04 11:22:01 +01:00
janishutz 841575d4e3 [Fish] Update alias 2025-11-27 16:46:14 +01:00
janishutz befe9696b0 [Fish] Fix alias 2025-11-27 16:45:38 +01:00
janishutz 4b12ae2c12 [Scripts] Fixes for vpn script 2025-11-24 14:25:13 +01:00
janishutz 5227e5f05e [Scripts] Fix vpn script 2025-11-23 13:36:06 +01:00
janishutz b8098eac8b [Scripts] Improve VPN script 2025-11-18 09:38:24 +01:00
janishutz 5b610643c7 [Install] Fix bt 2025-11-17 09:17:27 +01:00
janishutz 4fce8c955c [Hyprland] Fix cursor size 2025-11-12 10:32:15 +01:00
janishutz eabb972cb8 [Binds] Fix swapescape 2025-11-11 09:58:48 +01:00
janishutz 688b94c3ad [Binds] Add binds to dynamically enable and disable caps:swapescape 2025-11-11 09:50:31 +01:00
janishutz d4689d66cd [FMT] Improve latexindent config 2025-11-11 08:05:53 +01:00
janishutz e28e3e6207 [Install] Add usbutils 2025-11-06 17:47:18 +01:00
janishutz b45b3d60d1 [Scripts] Add pdf wordcount script 2025-11-04 15:51:49 +01:00
janishutz 8d2e3cea85 [Hyprland] Move conflicting bind 2025-11-03 17:13:44 +01:00
janishutz 8a42f9bfa2 [Install] Add timesyncd enable 2025-11-03 10:15:00 +01:00
janishutz 09916ae68e [Hyprland] Add more launch commands 2025-11-02 10:49:33 +01:00
janishutz 5b5fa9ad4e [Astal] Fix launch on new astal version 2025-11-01 10:37:30 +01:00
janishutz 814b975533 [Linecount] Add cloc and alias for it 2025-10-30 15:31:47 +01:00
janishutz d0a450d4e8 [Setup] Make cleaner 2025-10-30 10:42:50 +01:00
janishutz 786d5c5fe0 [Yazi] Fix themes 2025-10-30 10:42:42 +01:00
janishutz 5ba2894101 [Hyprland] Improve windowrules 2025-10-30 10:24:08 +01:00
janishutz 499bf793b2 [Hyprland] Add some nice windorules 2025-10-30 10:24:08 +01:00
janishutz f1c57b8ecc [Scripts] Add nvim update script 2025-10-29 15:19:18 +01:00
janishutz 20cc042b64 [Scripts] Add batch video conversion script 2025-10-25 16:26:39 +02:00
janishutz ae9dcaa074 [Hyprland] Windowrules: Add for BeatSaber 2025-10-22 17:01:28 +02:00
janishutz 21b68b1e68 [Hyprland] Fix alvr launch bind typo 2025-10-22 12:55:57 +02:00
janishutz fa4929a94b [Hyprland] Screenshot binds exit submap 2025-10-22 12:53:44 +02:00
janishutz 6b4d0e22b1 [Kitty] Remove default mapping interfering with new nvim bind 2025-10-21 08:44:42 +02:00
janishutz d5615892e7 [Hyprland] Add launch command for alvr and bsm 2025-10-17 17:30:04 +02:00
janishutz 81d0df6023 [Hyprland] Fix notifications submap 2025-10-17 10:58:39 +02:00
janishutz 9cf14d3b55 [Astal] Format 2025-10-17 10:47:05 +02:00
janishutz 06325b09b8 [Hyprland] Update readmes 2025-10-17 10:44:33 +02:00
janishutz 539ec34b4c [Astal] Finish modes, move tray into quickactions menu 2025-10-17 10:44:33 +02:00
janishutz 00d4f101fc [Hyprland] Remove some quick access binds (unnecessary) 2025-10-17 09:55:15 +02:00
janishutz c9a89cf545 [Setup] Fix platform wrong 2025-10-17 09:54:16 +02:00
janishutz 7ba9a65f19 [Build] Add linter 2025-10-17 09:47:40 +02:00
janishutz 52ced69697 [Hyprland] Fix submaps 2025-10-17 09:47:25 +02:00
janishutz 47bbdd9f85 [Astal] Add mode indicator 2025-10-17 09:47:18 +02:00
janishutz 94a57be9f7 [Astal] Add colours 2025-10-17 07:29:18 +02:00
janishutz 9d779269eb [Hyprland] Notifications binds 2025-10-17 06:52:15 +02:00
janishutz da61dc869d [Astal] Add new mode for hyprland 2025-10-17 06:51:05 +02:00
janishutz e44cc65f1f [Astal] Start adding hyprland module 2025-10-16 22:25:48 +02:00
janishutz 0392f8fc0f [Hyprland] Launch submap autoreset 2025-10-16 22:25:35 +02:00
janishutz 3c76f0412a [Setup] Fix hyprmode apply 2025-10-16 17:05:11 +02:00
janishutz 627c005fe3 [Hyprland] Fix submap 2025-10-16 17:05:03 +02:00
janishutz 6540bda07a [Hyprland] Add new bind layout 2025-10-16 16:18:08 +02:00
janishutz a9c73ba9d9 [Scripts] Improve VPN script 2025-10-16 10:44:27 +02:00
janishutz 30488edaed [Fish] Fix latexdocs alias 2025-10-10 15:05:28 +02:00
janishutz 8ed711d300 [Fish] Improve latex doc opening 2025-10-09 21:08:06 +02:00
janishutz a8043a9899 [Hyprland] Speed up animations even more 2025-10-09 11:02:51 +02:00
janishutz aa9ebaf22b [Astal] Battery widget: Show waiting 2025-10-09 09:33:21 +02:00
janishutz 2314a17066 [Install] Add ntp 2025-10-06 13:11:34 +02:00
janishutz 7051688fa5 [Clang] Formatter: Only merge empty functions 2025-10-05 12:13:56 +02:00
janishutz 0f4f601a2d [Clang] Improve formatter config 2025-10-05 12:09:53 +02:00
janishutz f38e2ff94f [Clang] Improve formatter config 2025-10-05 11:58:55 +02:00
janishutz 50c5cf945d [Clang] Improve formatter config 2025-10-05 08:38:26 +02:00
janishutz 903da245ac [Termfilechooser] Fix 2025-10-04 08:53:07 +02:00
janishutz 2d15296597 [Fish] Force pager into pager mode and not cat 2025-10-02 14:39:15 +02:00
janishutz 3a619cbf92 [Hyprland] Add new keybind on laptop 2025-10-02 14:21:32 +02:00
janishutz 530022ee59 [Hyprlock] Update clock 2025-10-02 08:39:03 +02:00
janishutz 5c01a5f806 [LatexIndent] Fix path 2025-10-01 15:34:14 +02:00
janishutz b6f5ad9bcf [Install] Add man 2025-10-01 13:44:15 +02:00
janishutz 92dc644ee2 [Fish] Add alias for gcc with error flags 2025-10-01 12:42:14 +02:00
janishutz ddc39f4928 [Setup] Fix indentconfig copying 2025-10-01 10:48:30 +02:00
janishutz a93d7494d3 [Install] Add sysstat package 2025-10-01 10:47:13 +02:00
janishutz 706cff40b3 [Setup] Fix errors with startup 2025-09-30 14:03:26 +02:00
janishutz e35845099a [Hyprland] Update for new folder structure 2025-09-30 07:41:44 +02:00
janishutz c44ef61cf2 [Fish] Update for new folder structure 2025-09-30 07:41:14 +02:00
janishutz ef65f827a2 [Yazi] Update keymap for new structure 2025-09-29 11:48:29 +02:00
jh04 6b8f415703 [Eslint] Update rules 2025-09-28 17:08:48 +02:00
janishutz f7004d9f25 [All] Remove old files 2025-09-27 08:07:58 +02:00
jh04 58d5b6cf69 [Setup] Add reload to make sure config reload happens properly 2025-09-26 14:04:47 +02:00
jh04 c55f70b852 [Fish] Add alias for latex docs 2025-09-26 14:03:21 +02:00
janishutz 97e02d81af [Clang] Fix config file 2025-09-25 16:42:26 +02:00
janishutz a545bb0066 [Clang] Fix incorrect config 2025-09-25 16:40:53 +02:00
janishutz 35d51f80df [Clang] Finish up config 2025-09-25 15:57:39 +02:00
janishutz d638aa0434 [Clang] Fix setup script 2025-09-25 15:52:55 +02:00
janishutz 258fc61669 [Clang] Add formatter config 2025-09-25 15:52:16 +02:00
janishutz 6dc15047d4 [Eslint] Add sort-imports rule 2025-09-25 15:20:13 +02:00
janishutz 0725763b95 [Hyprland] Configure gestures 2025-09-25 15:04:47 +02:00
jh04 29b8f188e9 [Yazi] Update keymap 2025-09-25 11:04:07 +02:00
jh04 959732ffd1 [Yazi] Update keymap 2025-09-23 15:19:36 +02:00
janishutz be3497de77 [Indent] Add more rules 2025-09-23 11:14:26 +02:00
janishutz 17d83a53b7 [Hyprland] configure gestures 2025-09-23 11:12:53 +02:00
janishutz 122f3ab070 [Hyprland] Add special workspace config 2025-09-22 16:17:22 +02:00
janishutz 46a732e8f7 [Install] Add dep for latexindent 2025-09-22 13:44:53 +02:00
janishutz 3373ae3b8c [Install] Add dep for latexindent 2025-09-22 13:37:50 +02:00
janishutz edd2db313c [Install] Add dep for latexindent 2025-09-22 13:36:37 +02:00
janishutz ecfd517a4a [Hyprland] Tweak animation duration 2025-09-22 13:01:13 +02:00
janishutz 25419067d4 [Install] Add note on using nwg-look 2025-09-22 12:53:46 +02:00
janishutz 36faff6b5f [Install] Add langgerman to install 2025-09-22 08:44:27 +02:00
janishutz ffe516175c [Eslint] Revert EOL decision 2025-09-20 08:12:20 +02:00
janishutz f58f22cadd [Install] Add german latex extras 2025-09-20 07:17:56 +02:00
janishutz 7e9faff6d6 [Astal] Make brightness adjust only in 5% intervals 2025-09-18 07:52:41 +02:00
janishutz ffa25045e5 [zathura] Add poppler to install 2025-09-17 16:17:43 +02:00
jh04 66dcd82952 [Hyprland] Fix bind 2025-09-16 17:31:28 +02:00
jh04 40013a61d1 [Hyprland] Fix some binds 2025-09-16 17:30:37 +02:00
janishutz ee26c0c5c2 [Hyprland] Fix errors 2025-09-16 13:03:25 +02:00
janishutz 02a351768f [Fish] Add pager alias 2025-09-16 12:59:20 +02:00
janishutz 9539f09e83 [Install] Add pager 2025-09-16 12:59:20 +02:00
jh04 25a78f126e [Astal] Add battery low notification 2025-09-15 14:42:05 +02:00
jh04 e0c339785d [Eslint] Update eol-last 2025-09-15 10:32:59 +02:00
jh04 e6f57831b1 [Eslint] Enforce newline 2025-09-15 10:27:26 +02:00
jh04 4e323d932f [Hyprland] Update binds 2025-09-15 09:20:29 +02:00
janishutz 64d8ca7ab4 [Install] Add eslint_d 2025-09-14 07:29:12 +02:00
jh04 7de17aaeb7 [Yazi] Update keymaps for ETH folders 2025-09-13 17:24:37 +02:00
janishutz 4f0c6a45cf [Install] Add http-server 2025-09-13 08:29:42 +02:00
janishutz a2dbdd78d3 [Setup] Don't do autologin (will make optional) 2025-09-13 08:22:11 +02:00
jh04 e4a76c494c [Greetd] Add auto-unlock of keyring 2025-09-12 17:26:53 +02:00
janishutz 1ee3f01883 [Setup] Add note 2025-09-12 13:12:13 +02:00
janishutz a89ed00a17 [Greetd] Add autologin 2025-09-12 12:26:47 +02:00
janishutz 64e671119d [Hyprland] Add dimming around rofi layer 2025-09-12 11:54:55 +02:00
janishutz 7b7955ac29 [Build] Run config build 2025-09-12 11:52:00 +02:00
jh04 823a8d8fbc [Setup] Make it quicker for small changes 2025-09-12 11:49:34 +02:00
jh04 088562cd53 [System] Remove mkinitcpio config 2025-09-12 11:49:26 +02:00
janishutz e9b8c35b52 [Scripts] Rename 2025-09-12 11:42:53 +02:00
janishutz 5a1811979e [Rofi] Fix positioning 2025-09-12 11:41:21 +02:00
janishutz 274890281c [Install] Add ripgrep 2025-09-12 10:40:46 +02:00
janishutz 0606ec06f5 [Hyprland] Add new rules 2025-09-12 10:40:11 +02:00
jh04 f22f8da68c [Install] Add git-credential-manager 2025-09-12 10:19:00 +02:00
jh04 409078edf9 [Install] Add ruby 2025-09-12 10:15:18 +02:00
jh04 2840c9aa70 [Fish] Add fish_variables 2025-09-12 10:13:22 +02:00
jh04 f4aedf7b5e [Install] Fix packages 2025-09-12 10:10:58 +02:00
jh04 f51cc0049a [Fish] Disable greeting 2025-09-12 10:10:48 +02:00
jh04 c01529a599 [Fish] Remove autojump from config 2025-09-12 09:57:40 +02:00
jh04 121f02c785 [Yazi] Fix keymap overlap 2025-09-12 09:57:33 +02:00
jh04 f6cd98e458 [Install] Add more utilities 2025-09-12 09:57:25 +02:00
jh04 cc9f38c71c [Install] Add more features 2025-09-12 09:29:18 +02:00
jh04 606a1fec1b [Install] Add auto-install of arch4edu and yay 2025-09-12 08:46:19 +02:00
janishutz d3bf33a142 [Install] Add seahorse to install 2025-09-12 06:59:29 +02:00
janishutz 91a6566d68 [Install] Finish up 2025-09-11 20:25:42 +02:00
jh04 ce0d951a54 [Install] Reorganize 2025-09-11 09:22:29 +02:00
jh04 106fe9cd9e Restructure bar restart and fix typos in ethz-vpn 2025-09-09 13:28:31 +02:00
jh04 a199569aac [Eslint] Increase max line length 2025-08-28 15:44:56 +02:00
jh04 80d16ab6ff [Install] Properly install yazi plugins 2025-08-22 14:21:19 +02:00
janishutz 1c8b72867e [Yazi] Add new plugin 2025-08-22 12:00:06 +02:00
jh04 9d59b24995 [Scripts] Add script to update open-webui 2025-08-21 12:14:30 +02:00
jh04 e78d07b25b [Base] Remove unnecessary files 2025-08-20 17:25:48 +02:00
jh04 a2c11e5cd1 [Yazi] Add keybind to open in evince 2025-08-05 11:21:27 +02:00
janishutz 70aceee3ca [Build] Build config 2025-07-31 09:08:07 +02:00
janishutz 8e6340e4c7 [Hyprlock] Update to V0.9 2025-07-31 09:07:19 +02:00
janishutz 46a4d7628d [Yazi] Add keybinds, start configuring openers 2025-07-28 11:10:03 +02:00
janishutz 2ec9356aca [Scripts] Prepare for cleanup 2025-07-22 15:52:52 +02:00
janishutz 3c19159c23 [Install] Use bvi instead of ghex 2025-07-16 15:38:45 +02:00
jh04 f6afd785fd [Setup] Fix yazi plugin updates 2025-06-22 15:21:45 +02:00
jh04 e20a33e132 [Setup] Fix 2025-06-22 14:49:58 +02:00
jh04 4683a171b9 [Astal] Finish up audio fix 2025-06-18 11:59:03 +02:00
jh04 4189258def [Astal] Fix audio issues 2025-06-18 11:40:58 +02:00
janishutz 2e8f1ec9ae [Hypr] Add easy docked switching 2025-06-12 10:08:57 +02:00
janishutz 78c97c5d84 [Hypr] Add docked config 2025-06-12 10:05:16 +02:00
jh04 4540dc00e7 [Eslint] Fix newline 2025-06-11 10:56:47 +02:00
jh04 294d7a9a98 [Eslint] Add max-len config 2025-06-11 09:48:34 +02:00
jh04 77dd4b4ca8 [Eslint] Add function-paren-newline option 2025-06-11 09:43:48 +02:00
jh04 7394ab63bf [Eslint] Change a few options 2025-06-10 17:23:43 +02:00
jh04 7f851fac73 [Eslint] Fix errors with single-line interface defintions 2025-06-07 11:18:58 +02:00
jh04 a5a6d9a916 [Eslint] Fix issues with end of file extra line 2025-06-06 15:36:12 +02:00
jh04 3967a4cd5c [latexfmt] Update align delims 2025-06-06 13:27:45 +02:00
jh04 f684f52d8f [latexfmt] Add indents for code 2025-06-06 13:20:52 +02:00
jh04 1079f62c06 [Eslint] Fix no-extra-parens config 2025-06-06 13:20:39 +02:00
jh04 1f460ccc38 [Eslint] Add support for mjs and cjs, format config 2025-06-06 11:46:57 +02:00
jh04 8b7f15eddf [Eslint] Add padding guides 2025-06-06 11:36:04 +02:00
jh04 0750c45111 [latexfmt] Finish up latexindent config 2025-06-06 10:59:16 +02:00
jh04 0db1815665 [Eslint] Fix spelling error in config 2025-06-06 10:35:04 +02:00
jh04 d028b027c5 [Eslint] Add more style options 2025-06-06 10:34:01 +02:00
jh04 e91f5aa5d6 [Eslint] Fix end of file newline 2025-06-06 10:27:49 +02:00
jh04 6105341234 [Eslint] Finish up config 2025-06-06 09:33:57 +02:00
jh04 6a362a963f [Eslint] Fix config 2025-06-05 19:59:45 +02:00
jh04 9005bd089a [Hyprland] Increase border size 2025-06-05 17:53:49 +02:00
jh04 1921545d08 [Hyprland] Fix keybind for cliphist 2025-06-05 15:40:00 +02:00
jh04 05b463699f [Install] Add cliphist 2025-06-05 15:38:19 +02:00
jh04 6f33358f4a [Hyprland] Add cliphist 2025-06-05 15:37:44 +02:00
jh04 909983d069 [Scripts] Fix indentconfig.yaml 2025-06-05 14:10:04 +02:00
jh04 a259206de8 [Scripts] add indentconfig.yaml 2025-06-05 13:33:52 +02:00
jh04 3f058cbfd3 [Scripts] Add a script to automatically rename files
This script automatically fixes any file name errors to ensure all files
are named correctly. Use with caution, as I have not yet tested it much
2025-06-04 14:26:32 +02:00
janishutz 318fc39b61 [Astal] Fix syntax 2025-05-26 15:05:07 +02:00
janishutz 6b1c8dec58 [Setup] Fix syntax error 2025-05-22 16:47:46 +02:00
janishutz 3541ec68e7 [Hyprland] Update binds (remove unnecessary ones) 2025-05-22 16:47:33 +02:00
janishutz 3a3674670b [Hyprland] Update binds 2025-05-22 16:43:27 +02:00
janishutz b2c6d72a2f [Hypridle] Improve idle manager 2025-05-22 11:46:20 +02:00
janishutz b9c68535d4 [Hypridle] Remove 100s timout 2025-05-22 11:45:28 +02:00
janishutz ae5226202a [Install] Remove TODO 2025-05-22 11:27:07 +02:00
janishutz 580aee4ad5 [Vimiv] Add notes and alias for fish 2025-05-22 10:12:12 +02:00
jh04 a8bf678d45 [Astal] Increase max-width of hyprland active window 2025-05-20 17:04:28 +02:00
janishutz 73feae1142 [Hyprland] Change to dwindle layout 2025-05-20 15:19:18 +02:00
janishutz b26b870bb2 [Hyprland] Resize window keybinds 2025-05-20 14:35:39 +02:00
janishutz 2d3a6ad33f [Astal] Remove debug code 2025-05-20 14:24:25 +02:00
janishutz ef9439b92c [Hyprland] Improve gestures 2025-05-19 14:29:40 +02:00
jh04 c635f3bfa9 [Build] Run build 2025-05-19 10:02:44 +02:00
jh04 7c3eb71150 [Astal] Update memory icon 2025-05-19 09:47:12 +02:00
janishutz 4fef18041f [Notes] Add another todo 2025-05-16 07:39:25 +02:00
janishutz df3384c667 [Notes] Mark completed items, add new todo 2025-05-16 07:37:49 +02:00
janishutz 7a97d7425e [Bar] Ellipsize too large text 2025-05-15 17:07:06 +02:00
jh04 73ca687358 [Fish] Add more aliases 2025-05-13 17:26:44 +02:00
jh04 88441958d9 [Yazi] Add more keybinds 2025-05-13 17:26:36 +02:00
janishutz e59af388b1 [Kitty] Finish improved cursor_trail settings 2025-05-12 14:17:24 +02:00
janishutz 39da9176ad [Build] Run build 2025-05-12 13:49:46 +02:00
janishutz e4ee9c9dc8 [Kitty] Improve cursor trail 2025-05-12 13:48:54 +02:00
jh04 feef8452f4 [Install] Add new programs, restructure 2025-05-09 11:12:02 +02:00
janishutz c3ff5a58d6 [AGS] Improve Battery display 2025-05-08 12:53:53 +02:00
janishutz a005189aef [Setup] Make bars restart, will be writing new notifications stuff soon 2025-05-08 10:55:31 +02:00
janishutz e98f5c2bd7 [Build] Run build 2025-05-08 10:43:00 +02:00
janishutz e7f6be4291 [AGS] QuickView: Use better battery icon 2025-05-08 10:42:55 +02:00
janishutz 4fa7dc3c1a [Eslint] Update arrow-parens to as-needed 2025-05-05 16:15:19 +02:00
janishutz 2ccb49f24b [Eslint] Finish config 2025-05-05 15:50:56 +02:00
jh04 ec04ebee66 [Player] Fix time display 2025-05-05 12:01:07 +02:00
jh04 2b2f1c2b66 [Build] Run build 2025-05-03 16:57:46 +02:00
jh04 21f7e53b2b [Build] Fix changeable colours 2025-05-03 16:57:39 +02:00
janishutz 5f09b7b915 [Install] Add new program 2025-04-29 15:35:19 +02:00
janishutz 688013a6b8 [Hyprland] Add logging for bar & notifier to temp folder 2025-04-29 14:47:56 +02:00
jh04 4e7c3ceee8 [Yazi] Remove plugins from here 2025-04-28 10:16:30 +02:00
jh04 390874374e [Hyprland] Hyprlock: Animations 2025-04-28 10:16:30 +02:00
janishutz 7f7a755e32 [Hyprland] Fix merge error 2025-04-28 08:56:38 +02:00
jh04 ac6b47449a [README] Update 2025-04-27 17:11:41 +02:00
jh04 ce5f530ed2 Merge V2 complete 2025-04-27 17:07:20 +02:00
jh04 62789224a7 Merge 2025-04-27 16:56:43 +02:00
janishutz 30a2b2a0ad [AGS] Sysinfo: Launch terminal asynchronously 2025-04-27 10:50:05 +02:00
janishutz 710eeb7fb4 [AGS] Brightness: Fix, add CLI 2025-04-27 10:48:07 +02:00
janishutz 364b5cb0ef [AGS] Bar: Add tooltips 2025-04-27 10:10:16 +02:00
janishutz d9777df0fb [Lint] Add linter config for eslint, not done yet 2025-04-27 10:01:54 +02:00
jh04 dabb325436 [AGS] Fix Bluetooth menu 2025-04-26 21:14:05 +02:00
jh04 08347196f2 [Wlogout] Design fix 2025-04-26 21:00:25 +02:00
jh04 3553d03b25 [Rofi] Design update 2025-04-26 21:00:15 +02:00
jh04 f4b58b08ba [AGS] Fixes, BT apparently crashing 2025-04-26 21:00:01 +02:00
janishutz 1a4610a2d6 [AGS] Setup script improvements 2025-04-26 16:05:07 +02:00
janishutz 8c4af4f817 [Build] Run build, remove useless colours 2025-04-26 15:54:35 +02:00
jh04 4aa4bc57b9 [Wlogout] Improve looks 2025-04-26 15:35:44 +02:00
jh04 11c961bfe7 [AGS] SysInfo: Re-Structure for better perf 2025-04-26 15:32:47 +02:00
jh04 090492ae51 [Build] Give option to customize colours 2025-04-26 15:10:26 +02:00
jh04 b4b8d04e6a [AGS] SysInfo: Improve performance 2025-04-26 15:07:49 +02:00
jh04 2fd32da1b2 [AGS] Battery: Design complete 2025-04-26 13:30:22 +02:00
jh04 86e963e408 [AGS] Battery, Brightness: Improve design 2025-04-26 13:29:16 +02:00
jh04 678afa99d6 [AGS] Battery: Try to improve design 2025-04-26 13:26:52 +02:00
jh04 acca70d47f [AGS] Battery: Improve design 2025-04-26 13:24:34 +02:00
jh04 bec7474793 [AGS] Networking: Fix button display 2025-04-26 13:22:32 +02:00
jh04 414d42072a [AGS] Brightness improvements 2025-04-26 13:20:37 +02:00
jh04 1239f49cc7 [AGS] Brightness control 2025-04-26 13:18:00 +02:00
jh04 a7e6584f78 [AGS] Improve brightness controls 2025-04-26 13:07:55 +02:00
jh04 d72c4fdf86 [AGS] Potential fix for network crash 2025-04-26 13:03:46 +02:00
janishutz a8e0c903c4 [Build] Do full build 2025-04-26 12:20:32 +02:00
jh04 69d9b510f1 [AGS] Remove network speed info 2025-04-26 12:18:42 +02:00
jh04 f66c8639d2 [Greetd] Add configs 2025-04-26 12:14:57 +02:00
jh04 519eea601c [Build] Run build 2025-04-26 12:10:09 +02:00
jh04 609f662eb5 [Notes] Tick off completed items 2025-04-26 12:10:04 +02:00
jh04 651030fd75 [Setup] Improve script 2025-04-26 12:09:55 +02:00
jh04 212296a7d5 [AGS] Add new colour option 2025-04-26 12:09:38 +02:00
jh04 f85c5f1b10 [Rofi] Add renderables 2025-04-26 12:09:30 +02:00
jh04 590ccb5c9e [Rofi] Add configs 2025-04-26 12:09:07 +02:00
jh04 c7064dbea0 [Bar] Small bugfix for workspace number 2025-04-26 12:08:58 +02:00
jh04 afe25a322a [AGS] Launcher: Prepare 2025-04-26 12:08:33 +02:00
jh04 02861efcae [AGS] Remove old modules 2025-04-26 12:08:22 +02:00
jh04 6c1c9ad8cf [Build] Restructure 2025-04-26 12:08:16 +02:00
jh04 f2bdddb9b6 [AGS] Add new notifications setup 2025-04-26 09:49:03 +02:00
jh04 e19a1179d5 [AGS] Fix Bluetooth crashes 2025-04-26 09:38:21 +02:00
janishutz 33fcdd72f7 [Notes] Tick off what is completed 2025-04-26 08:55:22 +02:00
jh04 bf7d61945b [AGS] Possibly fixed 2025-04-25 21:36:19 +02:00
jh04 03d9d89102 [AGS] Fix crashing 2025-04-25 20:56:59 +02:00
jh04 3e5136fdbb [Meson] Prepare 2025-04-25 18:14:42 +02:00
jh04 99a7a59cf7 [Build] Rebuild 2025-04-25 17:22:15 +02:00
jh04 54e216b5ec [AGS] Fixes for bar 2025-04-25 17:22:07 +02:00
jh04 19d59347b6 [AGS] Cleanup 2025-04-25 16:58:19 +02:00
jh04 9f754b454b [Build] Fix up 2025-04-25 16:33:48 +02:00
jh04 7f21fb3ee3 [Build] Do full build 2025-04-25 16:16:14 +02:00
jh04 0b349fb038 [Build] Done 2025-04-25 15:36:22 +02:00
jh04 db16e830d1 [Gtk-Theme] Done 2025-04-25 15:36:18 +02:00
jh04 d120b7a49e [Gtk-Theme] Pre-Processing + Some other work 2025-04-25 10:05:42 +02:00
jh04 0b849bad4f Fish change alias bt 2025-04-25 08:55:02 +02:00
jh04 722be458ec [Gtk-Theme] First setup 2025-04-25 08:52:52 +02:00
jh04 aba9e0f30a [Build] Prepare for improved rendering capbilities 2025-04-25 07:43:53 +02:00
janishutz 49446646f3 [Notes] Tick off what is complete 2025-04-25 06:58:26 +02:00
janishutz b5ad7e3034 [Notes] Some changes 2025-04-25 06:53:17 +02:00
jh04 10136ab9de [AGS] Bar: Done (WiFi still missing, will be added at some later point) 2025-04-24 16:58:30 +02:00
jh04 e93e051094 [AGS] Bar: BT, Audio, SysInfo, Brightness 2025-04-23 20:04:48 +02:00
jh04 69484fc302 [AGS] Bar: Improve QuickActions 2025-04-22 17:38:41 +02:00
jh04 8a2180e120 [AGS] GTK 4 Migration: Done, Start adding QuickActions
Probably gonna abandon the QuickActions, as that is just way too much
effort for what it does. Will be providing keybinds for doing what I
wanted to do there in Hyprland
2025-04-22 15:30:41 +02:00
jh04 8b70f80e60 [AGS] GTK4 Migration: Partially complete 2025-04-21 21:48:29 +02:00
jh04 0022880936 Actually create kitty config 2025-04-21 20:26:45 +02:00
jh04 7380c75818 [AGS] Save for migration to GTK4 2025-04-21 17:26:52 +02:00
jh04 f4b259dd13 [AGS] Bar: Start sysinfo 2025-04-21 12:44:41 +02:00
jh04 b2f1d8fd9e [AGS] Bar progress 2025-04-19 18:20:32 +02:00
jh04 78e472beb8 [AGS] Redo basic setup 2025-04-19 15:20:50 +02:00
jh04 a9c7b7d7ee [AGS] Prepare bar 2025-04-19 15:08:42 +02:00
janishutz 1d5240b7c6 Add terminal file chooser 2025-04-14 16:51:42 +02:00
janishutz 196d553627 [Icons] Add gen list 2025-04-14 16:49:24 +02:00
janishutz ed17459e2a [Launcher] Improve type definitions 2025-04-14 16:17:21 +02:00
janishutz 0b0e83f505 Improve notifications 2025-04-14 15:23:28 +02:00
janishutz d9fdf1ee6d [Launcher] Add helper for fzf 2025-04-10 15:01:02 +02:00
janishutz f4cfa20dea Add fastfetch config 2025-04-10 11:35:39 +02:00
jh04 4dc14cf4e3 [AGS] Setup 2025-04-09 19:25:06 +02:00
jh04 d866102fcc Improve copying in Zathura 2025-04-09 19:20:00 +02:00
jh04 921373f049 Update package list 2025-04-09 10:13:54 +02:00
jh04 1c39478650 Remove VSCode forced workspace 2025-04-05 15:19:55 +02:00
janishutz c70a3ffdb4 [Launcher] Prepare component 2025-04-04 07:43:08 +02:00
jh04 a845e086fd Change switch workspace keybind 2025-04-03 11:06:32 +02:00
jh04 b363438f6a Add zathura launch bind 2025-04-03 08:35:54 +02:00
jh04 3bb9c6e661 [Install] Add notes 2025-04-02 11:14:12 +02:00
jh04 7d9da82b6c Add restore plugin, improve ouch for yazi 2025-04-02 11:14:00 +02:00
jh04 c52c5b5a42 Improve Zathura, move to zoxide, add yazi plugins 2025-04-02 11:08:15 +02:00
jh04 97c49f17e6 Add nohlsearch to keybind zathura 2025-04-02 10:40:04 +02:00
jh04 134519558a Remove some zathura binds that come by default 2025-04-02 10:31:09 +02:00
jh04 9e5ae7db83 Update zathura binds 2025-04-01 17:52:33 +02:00
jh04 a31b3481c3 Make zathura open on 2nd workspace 2025-04-01 17:44:07 +02:00
jh04 1c757d1173 Add zathura config 2025-04-01 17:40:25 +02:00
jh04 89b97c4ba1 Add Zathura config 2025-04-01 17:40:10 +02:00
jh04 2ba627a370 Hyprland revert mouse focus change 2025-04-01 15:57:44 +02:00
jh04 995de3617c Hyprland change mouse focus 2025-04-01 13:50:28 +02:00
janishutz dda261ac3c [Notifications] Only enable menu if there are notifications 2025-03-31 17:52:12 +02:00
janishutz a477078f16 Hyprland fix vivado window rules 2025-03-31 15:33:15 +02:00
janishutz f93eeb5d87 Hyprland improve vivado 2025-03-31 15:17:32 +02:00
jh04 5b0e3767bd Add yazi keybinds 2025-03-31 10:50:44 +02:00
jh04 791f10aad4 [Launcher] Prepare UI 2025-03-30 10:56:18 +02:00
janishutz 51962c424b Hyprland fix for V0.48 2025-03-30 08:09:44 +02:00
janishutz 666a047814 [Launcher] fzf, cmd processing, args 2025-03-30 08:00:10 +02:00
jh04 c0e9062230 [Yazi] Add additional themes 2025-03-29 13:37:42 +01:00
jh04 c9442acce8 [Build] Improve treeWalker, renderer & wallpaper finder 2025-03-29 10:30:08 +01:00
jh04 a6ce7ccdce Fix path for scripts 2025-03-28 13:50:42 +01:00
janishutz 0f0e0e8671 improve notifications keybinds 2025-03-28 11:45:42 +01:00
janishutz 527f2012de [Notifications] Finish up toggle 2025-03-28 07:37:14 +01:00
janishutz c6061e0f12 Remove quit keybind from hyprland. Can be done through wlogout 2025-03-27 16:23:12 +01:00
jh04 c2f38bc39a [Build] Improve rendering, add fs parsing
FS parsing needs a lot of improvements (namely ignoring gitignore
files). Probably gonna migrate to a library for this
2025-03-25 20:06:50 +01:00
jh04 bab328c2d3 [Build] Tons of improvements 2025-03-25 19:51:37 +01:00
janishutz c53069f0df [Bar] Prepare 2025-03-25 17:13:03 +01:00
janishutz 7c15b0b320 [Launcher] Setup 2025-03-25 17:12:57 +01:00
janishutz 3d74676f6d [QuickActions] Prep 2025-03-25 17:12:48 +01:00
janishutz 994d1ac1b4 [Hypr] Hyprlock template 2025-03-25 17:12:36 +01:00
janishutz aea9229c54 [Build] Prep 2025-03-25 17:12:09 +01:00
janishutz 49595c3811 Restructure, move launcher to astal gtk4 2025-03-25 13:51:56 +01:00
janishutz 4a769d9eb0 Add more notes 2025-03-24 15:36:51 +01:00
jh04 cac0e41a2e Change shortcuts for fish 2025-03-24 09:12:07 +01:00
jh04 840691ec67 Improve launcher 2025-03-23 14:11:02 +01:00
jh04 d387d02534 [Notifications] Add toggle 2025-03-23 14:10:54 +01:00
jh04 c4797dd592 Remove killactive from MainMod + C 2025-03-22 20:41:22 +01:00
jh04 7ff986e7a6 [Notifications] Improve handling of centre 2025-03-22 14:28:31 +01:00
jh04 8986ad894e Improve keybindings 2025-03-22 11:31:46 +01:00
jh04 709af40296 Restructure, prepare launcher 2025-03-22 11:26:57 +01:00
jh04 3060c2b06e Restructure 2025-03-22 11:26:26 +01:00
jh04 f127ea8a41 Fix binds for new notification handler 2025-03-21 18:25:36 +01:00
jh04 1f9feeb85f Prepare QuickActions 2025-03-21 18:24:36 +01:00
jh04 9d0574ecab Remove old preparation 2025-03-21 18:24:27 +01:00
jh04 e483d7de23 Finish basic notification setup 2025-03-21 18:23:44 +01:00
jh04 7fe86701b3 Prepare Launcher 2025-03-21 18:23:35 +01:00
jh04 ca3d9c32e9 Prepare Bar 2025-03-21 18:23:29 +01:00
jh04 bb75cfabaf Add binds for new notification handler 2025-03-21 18:10:15 +01:00
jh04 c5b1f64376 Add ags stuff, but will probably redo entirely 2025-03-21 16:16:56 +01:00
janishutz 847ecc8ef7 Prepare for new configs 2025-03-20 18:57:39 +01:00
janishutz e79534ff45 Add new installed packages list 2025-03-20 13:20:54 +01:00
janishutz 906498cf67 Clean up rofi config 2025-03-20 13:20:46 +01:00
janishutz 62c7a61dd2 Add ETHZ-VPN script by Glib 2025-03-20 13:12:22 +01:00
janishutz 5ff87ba160 Add new shortcuts for yazi 2025-03-20 12:55:07 +01:00
jh04 f2958885db Update configs from PC 2025-03-20 08:38:40 +01:00
jh04 11a765543f fix setup script 2025-03-18 17:44:24 +01:00
janishutz b2ebac4094 [yarzi] Add keybind to go to trash 2025-03-18 16:10:30 +01:00
janishutz a3693a8ad1 Improve config collection, add rofi config for pc 2025-03-18 14:19:19 +01:00
janishutz 87298afcb5 Update rofi collection script 2025-03-18 14:12:15 +01:00
janishutz e894d77ebb Waybar: Add keyboard-state for caps 2025-03-18 13:42:39 +01:00
janishutz c876804917 Swap ESC and Caps through hyprland 2025-03-18 10:39:49 +01:00
janishutz 86aa16c028 Improve kmonad, not ready to be used 2025-03-18 10:24:43 +01:00
janishutz 7ca01632d3 Add initial kmonad setup 2025-03-17 17:21:20 +01:00
janishutz cdf2efa1a4 Add refocus rule 2025-03-17 16:51:50 +01:00
janishutz 5373190dda Improve yazi keymap 2025-03-17 14:16:16 +01:00
jh04 a9d92efa18 Improve install scripts, add more shortcuts, improve hypridle 2025-03-12 16:08:58 +01:00
janishutz b54a382841 Improve yazi config 2025-03-11 09:47:39 +01:00
janishutz 7a578cfb00 Add lazygit 2025-03-06 11:40:16 +01:00
janishutz 4fa6cb1616 Improve laptop config, add aliases 2025-03-04 14:49:52 +01:00
jh04 7cf3693887 Push new package list 2025-03-03 10:25:07 +01:00
jh04 c1afb50799 Upload from GitHub 2025-02-28 11:41:18 +01:00
355 changed files with 37621 additions and 2952 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
node_modules/
package-lock.json
+29 -37
View File
@@ -1,55 +1,47 @@
<div id="title" align="center">
<img src="https://static.janishutz.com/logo.jpg" width="300">
<h1>janishutz dotfiles</h1>
<h1>janishutz Hyprland</h1>
</div>
Full system configuration using [Decman](https://github.com/kiviktnm/decman).
Collection of dotfiles for my personal Hyprland setup, running on Arch Linux.
Includes a setup and install script (that one is not complete yet though).
For my neovim config, see [here](https://git.janishutz.com/janishutz/nvim)
*Screenshot outdated, a new screenshot will be added when this update is completed*
![screenshot of desktop with 2 screens](./assets/screenshot.png)
The whole desktop will likely eventually move back to Waybar (because it has evolved massively since I last used it and now supports custom menus)
DankMaterialShell is a stop-gap solution, which, albeit it looking nice,
doesn't go well with my ethos of using non-slop programs.
Luckily, I can keep most of my configs when moving to something else, because I will be using Matugen.
The plan is to use some of the config templates from DMS (or some other source) for matugen for my own desktop with some changes
Furthermore, my system may migrate to NixOS by early September 2026, to finally have my laptop and desktop configs synced properly
# New Version
This version of the dotfiles (and all branches apart from `new`) are deprecated and in maintenance mode only,
while a new version is developed on the [new branch](https://git.janishutz.com/janishutz/dotfiles/src/branch/new)
## Features
- DankMaterialShell
- Automatically generated GTK Theme and theming of the rest of the desktop using `matugen`
- Astal4 based Status Bar and Quick Actions menu
- System info
- Hyprland info
- Date & Time
- Bluetooth picker
- some networking settings (more coming later)
- Audio and brightness control
- battery monitoring
- Logout, Reboot, Shutdown, etc
- Rofi config for App launcher
- Wlogout config
- Theming script that generates a GTK theme and theming for bar, Hyprland, etc
- Fish config (with some handy aliases, based on one from ohh-my-fish)
- Fastfetch config
- kitty config with cursor trail
- alacritty config
- notification manager uses `swaync`
- Linter configs (eslint, clang-format, latexindent and more)
- Linter configs (currently only eslint, which is not complete yet)
- mpv config
- zathura configs
- yazi configs with links to various directories I use commonly plus a few plugins and themes that are applied by the script
- Astal3 based Notifications (due to be migrated to Astal4)
## Installing
Clone your repo to any folder and adapt what you need. Some folders are still hard-coded, which I will be changing later on, so you will likely run into issues.
You may then run the `setup` script. That won't install all dependencies though. The `install` script is what serves that purpose, but that is not complete yet
## TODOs
- [ ] Migrate back to Waybar
- [X] Rewrite Hyprland configs using lua
- [X] Waylogout still needed?
- [X] Add the formatter configs back in
- [X] Add the scripts back in
- [ ] ~Finish decman configuration to copy all the files~
- [X] Custom yazi theme
- [ ] Notifier (swaync??)
- [ ] Launcher (hyprlauncher??)
- [ ] dgop as hw monitor
- [ ] No need for nm-applet (even with waybar -> nmcli or nmtui)
- [ ] Adapt the GTK, etc config from DMS for my own design
- [ ] Update neovim design
- [ ] Nix config
- [ ] home-manager or what for dotfiles management on nix?
- [ ] Yazi more directory links
- [ ] Hyprland keybind panel?
- [ ] Migrate to zsh (or another POSIX compliant shell)
- [ ] Wallpaper picker (for regenerating stuff using matugen). TUI using kitty graphics? Or any lightweight toolkit?
## Setting up to develop
Clone this repo locally. For `config/astal` and `config/ags`, you will want to run `ags types -d .` in every directory where there is a `app.ts` file as well as `mkdir node_modules && cd node_modules && ln -sf /usr/share/astal/gjs/ ./astal` to prepare for development.
The `config/ags` directory contains gtk3 config for astal, whereas in `config/astal`, gtk4 configs can be found. All modules but for the notifications are written in Gtk 4.
-16
View File
@@ -1,16 +0,0 @@
# TODOs
- [X] Notifications management via dms
- [X] Launcher via dms
- [X] DMS screenshots
- [X] Hyprland theming using mutagen with dms integration
- [ ] DMS config sync to here using script
- [ ] Remove `decman` because it's a pain and use smaller scope `archmgr` (only copy all files to locations always and package diff)
- [ ] Generate matugen stuff from colour in new script
- [X] Binds for other DMS features (with subcommand)
- [X] Finish decman config
- [X] Remove wlogout and replace with dms setup
- [ ] Yazi theming
- [X] Kitty theming
- [ ] Fish theming
- [ ] Fastfetch theming
- [X] DMS instead of Hyprlock for locker?
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

+58
View File
@@ -0,0 +1,58 @@
// Using commonjs instead of ejs, because more widely compatible
const inquirer = require( 'inquirer' );
const fs = require( 'fs' );
const path = require( 'path' );
const os = require( 'os' );
const render = require( './helpers/render' );
const { treeWalker } = require('./helpers/util');
// Prompt user to select a wallpaper (if no path is passed as argument)
const wallpapers = treeWalker( path.join( os.homedir(), '/NextCloud/Wallpapers' ), '*' );
// const wallpapers = fs.readdirSync( path.join( os.homedir(), '/NextCloud/Wallpapers' ) );
const wallpaperChoices = [];
wallpapers.forEach(element => {
const name = element.split( '/' );
wallpaperChoices.push( { 'name': name[ name.length - 1 ].split( '.' )[ 0 ], 'value': element } );
});
// Selection options
const chooseWallpaper = {
'type': 'list',
'name': 'wallpaper',
'message': 'Choose the wallpaper to be used',
'choices': wallpaperChoices,
};
const chooseLockpaper = {
'type': 'list',
'name': 'lockpaper',
'message': 'Choose the lockscreen wallpaper to be used',
'choices': wallpaperChoices,
};
const chooseTheme = {
'type': 'list',
'name': 'theme',
'message': 'Choose the general colourway to be used',
'choices': [
{ name: 'Nordic', value: 'nordic' },
{ name: 'Deep-Dark', value: 'deep-dark' },
{ name: 'Material-You', value: 'material' },
{ name: 'Light', value: 'light' },
{ name: 'Bright', value: 'bright' },
]
}
// TODO: Add argument parsing
const args = process.argv.slice( 2 );
inquirer.default.prompt( [
chooseWallpaper,
chooseLockpaper,
chooseTheme
] ).then( answers => {
render( answers.wallpaper, answers.lockpaper, answers.theme );
} ).catch( () => {
process.exit( 1 );
} );
+487
View File
@@ -0,0 +1,487 @@
const util = require( './util' );
const renderColourAsRGB = util.renderColourAsRGB;
const renderColourAsRGBA = util.renderColourAsRGBA;
const renderColourAsHex = util.renderColourAsHex;
// ───────────────────────────────────────────────────────────────────
// ╭───────────────────────────────────────────────╮
// │ Theme generator (returns theme as object) │
// ╰───────────────────────────────────────────────╯
// ───────────────────────────────────────────────────────────────────
module.exports.generateTheme = (
theme, wallpaper, lockpaper, palette
) => {
return {
'wallpaper-path': wallpaper,
'lockpaper-path': lockpaper,
// ┌ ┐
// │ Colours │
// └ ┘
// ── Foreground ───────────────────────────────────────────────────
'colour-foreground-hex': renderColourAsHex( colours.foreground[ theme ] ),
'colour-foreground-rgb': renderColourAsRGB( colours.foreground[ theme ] ),
'colour-foreground-rgba': renderColourAsRGBA( colours.foreground[ theme ], 0.5 ),
'colour-foreground-rgba-07': renderColourAsRGBA( colours.foreground[ theme ], 0.7 ),
'colour-foreground-rgba-06': renderColourAsRGBA( colours.foreground[ theme ], 0.6 ),
'colour-foreground-rgba-05': renderColourAsRGBA( colours.foreground[ theme ], 0.5 ),
'colour-foreground-rgba-03': renderColourAsRGBA( colours.foreground[ theme ], 0.3 ),
'colour-foreground-rgba-02': renderColourAsRGBA( colours.foreground[ theme ], 0.2 ),
'colour-foreground-rgba-01': renderColourAsRGBA( colours.foreground[ theme ], 0.1 ),
// ── Accent foreground colour ─────────────────────────────────────
'colour-foreground-accent-hex': renderColourAsHex( colours['foreground-accent'][ theme ] ),
'colour-foreground-accent-rgba-07': renderColourAsRGBA( colours['foreground-accent'][ theme ], 0.7 ),
'colour-foreground-accent-rgba-06': renderColourAsRGBA( colours['foreground-accent'][ theme ], 0.6 ),
'colour-foreground-accent-rgba-05': renderColourAsRGBA( colours['foreground-accent'][ theme ], 0.5 ),
'colour-foreground-accent-rgba-03': renderColourAsRGBA( colours['foreground-accent'][ theme ], 0.3 ),
'colour-foreground-accent-rgba-02': renderColourAsRGBA( colours['foreground-accent'][ theme ], 0.2 ),
// ── Accent colour ────────────────────────────────────────────────
'colour-accent-hex': renderColourAsHex( palette[ 0 ] ),
'colour-accent-rgb': renderColourAsRGB( palette[ 0 ] ),
'colour-accent-rgba': renderColourAsRGBA( palette[ 0 ], 0.3 ),
'colour-accent-rgba-07': renderColourAsRGBA( palette[ 0 ], 0.7 ),
'colour-accent-rgba-05': renderColourAsRGBA( palette[ 0 ], 0.5 ),
'colour-accent-rgba-03': renderColourAsRGBA( palette[ 0 ], 0.3 ),
'colour-accent-rgba-02': renderColourAsRGBA( palette[ 0 ], 0.2 ),
'colour-accent-rgba-015': renderColourAsRGBA( palette[ 0 ], 0.15 ),
'colour-accent-rgba-011': renderColourAsRGBA( palette[ 0 ], 0.11 ),
'colour-accent-rgba-007': renderColourAsRGBA( palette[ 0 ], 0.07 ),
'colour-accent-hyprland': util.renderColourAsRGBAHex( palette[ 0 ], 0.8 ),
// ───────────────────────────────────────────────────────────────────
'colour-accent-gradient-1-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-2-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 2, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-3-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 3, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-4-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 4, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-5-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 5, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-inverse-1-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, 1 / gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-inverse-2-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, 1 / gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-inverse-3-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, 1 / gradientMultipliers[ theme ]
) ),
// ── Secondary accent ─────────────────────────────────────────────
'colour-accent-2-hex': renderColourAsHex( palette[ 1 ] ),
'colour-accent-2-rgb': renderColourAsRGB( palette[ 1 ] ),
'colour-accent-2-rgba-07': renderColourAsRGBA( palette[ 1 ], 0.7 ),
'colour-accent-2-rgba-05': renderColourAsRGBA( palette[ 1 ], 0.5 ),
'colour-accent-2-rgba-03': renderColourAsRGBA( palette[ 1 ], 0.3 ),
'colour-accent-2-rgba-02': renderColourAsRGBA( palette[ 1 ], 0.2 ),
'colour-accent-2-rgba-015': renderColourAsRGBA( palette[ 1 ], 0.15 ),
'colour-accent-2-rgba-01': renderColourAsRGBA( palette[ 1 ], 0.1 ),
'colour-accent-2-hyprland': util.renderColourAsRGBAHex( palette[ 1 ], 0.8 ),
// ── Tertiary accent ──────────────────────────────────────────────
'colour-accent-3-hex': renderColourAsHex( palette[ 2 ] ),
'colour-accent-3-rgb': renderColourAsRGB( palette[ 2 ] ),
'colour-accent-3-hyprland': util.renderColourAsRGBAHex( palette[ 2 ], 0.8 ),
// ── Background ───────────────────────────────────────────────────
'colour-background-hex': renderColourAsHex( colours.background[ theme ] ),
'colour-background-rgb': renderColourAsRGB( colours.background[ theme ] ),
'colour-background-rgba': renderColourAsRGBA( colours.background[ theme ], 0.5 ),
'colour-background-rgba-07': renderColourAsRGBA( colours.background[ theme ], 0.7 ),
'colour-background-rgba-05': renderColourAsRGBA( colours.background[ theme ], 0.5 ),
'colour-background-rgba-03': renderColourAsRGBA( colours.background[ theme ], 0.3 ),
'colour-background-rgba-02': renderColourAsRGBA( colours.background[ theme ], 0.2 ),
'colour-background-rgba-015': renderColourAsRGBA( colours.background[ theme ], 0.15 ),
'colour-background-rgba-011': renderColourAsRGBA( colours.background[ theme ], 0.11 ),
// ── Background Alternative ───────────────────────────────────────
'colour-background-alternative-hex': renderColourAsHex( colours[ 'background-alternative' ][ theme ] ),
'colour-background-alternative-rgb': renderColourAsRGB( colours[ 'background-alternative' ][ theme ] ),
'colour-background-alternative-rgba-07': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.7 ),
'colour-background-alternative-rgba-06': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.6 ),
'colour-background-alternative-rgba-05': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.5 ),
'colour-background-alternative-rgba-04': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.4 ),
'colour-background-alternative-rgba-03': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.3 ),
'colour-background-alternative-rgba-02': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.2 ),
'colour-background-alternative-rgba-015': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.15 ),
'colour-background-alternative-rgba-01': renderColourAsRGBA( colours[ 'background-alternative' ][ theme ], 0.1 ),
// ── Background Tertiary ──────────────────────────────────────────
'colour-background-tertiary-hex': renderColourAsHex( colours[ 'background-tertiary' ][ theme ] ),
'colour-background-tertiary-rgb': renderColourAsRGB( colours[ 'background-tertiary' ][ theme ] ),
'colour-background-tertiary-rgba-05': renderColourAsRGBA( colours[ 'background-tertiary' ][ theme ], 0.5 ),
'colour-background-tertiary-rgba-02': renderColourAsRGBA( colours[ 'background-tertiary' ][ theme ], 0.2 ),
// ── Shadow ───────────────────────────────────────────────────────
'colour-shadow-hex': renderColourAsHex( colours.shadow[ theme ] ),
'colour-shadow-rgb': renderColourAsRGB( colours.shadow[ theme ] ),
'colour-shadow-hyprland': util.renderColourAsRGBHex( colours.shadow[ theme ] ),
'colour-shadow-rgba-07': renderColourAsRGBA( colours.shadow[ theme ], 0.7 ),
'colour-shadow-rgba-05': renderColourAsRGBA( colours.shadow[ theme ], 0.5 ),
'colour-shadow-rgba-03': renderColourAsRGBA( colours.shadow[ theme ], 0.3 ),
'colour-shadow-rgba-02': renderColourAsRGBA( colours.shadow[ theme ], 0.2 ),
'colour-shadow-rgba-015': renderColourAsRGBA( colours.shadow[ theme ], 0.15 ),
'colour-shadow-rgba-011': renderColourAsRGBA( colours.shadow[ theme ], 0.11 ),
// ── Inactive ─────────────────────────────────────────────────────
'colour-inactive-hex': renderColourAsHex( colours.inactive[ theme ] ),
'colour-inactive-rgb': renderColourAsRGB( colours.inactive[ theme ] ),
'colour-inactive-rgba-07': renderColourAsRGBA( colours.inactive[ theme ], 0.7 ),
'colour-inactive-rgba-05': renderColourAsRGBA( colours.inactive[ theme ], 0.5 ),
'colour-inactive-rgba-03': renderColourAsRGBA( colours.inactive[ theme ], 0.3 ),
'colour-inactive-rgba-02': renderColourAsRGBA( colours.inactive[ theme ], 0.2 ),
'colour-inactive-rgba-015': renderColourAsRGBA( colours.inactive[ theme ], 0.15 ),
// ───────────────────────────────────────────────────────────────────
'colour-inactive-background-hex': renderColourAsHex( colours[ 'inactive-background' ][ theme ] ),
'colour-inactive-background-rgb': renderColourAsRGB( colours[ 'inactive-background' ][ theme ] ),
'colour-inactive-background-hyprland': util.renderColourAsRGBHex( colours[ 'inactive-background' ][ theme ] ),
'colour-inactive-background-rgba-07': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.7 ),
'colour-inactive-background-rgba-05': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.5 ),
'colour-inactive-background-rgba-04': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.4 ),
'colour-inactive-background-rgba-03': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.3 ),
'colour-inactive-background-rgba-02': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.2 ),
'colour-inactive-background-rgba-015': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.15 ),
'colour-inactive-background-rgba-01': renderColourAsRGBA( colours[ 'inactive-background' ][ theme ], 0.1 ),
// ┌ ┐
// │ Fonts │
// └ ┘
'font-primary': fonts.primary[ theme ],
'font-accent': fonts.accent[ theme ],
'font-mono': fonts.mono[ theme ],
// ┌ ┐
// │ Icon Theme │
// └ ┘
'icon-theme': iconTheme[ theme ],
// ┌ ┐
// │ yazi theme │
// └ ┘
'yazi-theme': yaziThemes[ theme ],
// ┌ ┐
// │ Path to this repo on disk │
// └ ┘
'path-to-dotfiles': __dirname.slice( 0, __dirname.length - 5 ),
};
};
// ───────────────────────────────────────────────────────────────────
// ╭───────────────────────────────────────────────╮
// │ Theme definitions │
// ╰───────────────────────────────────────────────╯
// ───────────────────────────────────────────────────────────────────
const gradientMultipliers = {
'nordic': 0.9,
'deep-dark': 0.8,
'material': 0.85,
'light': 1.1,
'bright': 1.15,
'test': 0.75
};
const colours = {
'foreground': {
'nordic': [
200,
220,
255
],
'deep-dark': [
230,
230,
230
],
'material': [
255,
255,
255
], // TODO: Will be calculated by material theme generator
'light': [
40,
40,
40
],
'bright': [
0,
0,
0
],
'test': [
0,
0,
0
],
},
'foreground-accent': {
'nordic': [
255,
255,
255
],
'deep-dark': [
255,
255,
255
],
'material': [
200,
200,
200
], // TODO: Will be calculated by material theme generator
'light': [
0,
0,
0
],
'bright': [
50,
50,
50
],
'test': [
0,
0,
0
],
},
'background': {
'nordic': [
10,
10,
15
],
'deep-dark': [
20,
20,
20
],
'material': [
30,
30,
30
], // TODO: Will be calculated by material theme generator
'light': [
230,
230,
230
],
'bright': [
255,
255,
255
],
'test': [
255,
255,
255
],
},
'background-alternative': {
'nordic': [
20,
20,
25
],
'deep-dark': [
30,
30,
30
],
'material': [
40,
40,
40
], // TODO: Will be calculated by material theme generator
'light': [
210,
210,
210
],
'bright': [
230,
230,
230
],
'test': [
255,
255,
0
] // brown
},
'background-tertiary': {
'nordic': [
0,
0,
0
],
'deep-dark': [
45,
45,
45
],
'material': [
0,
0,
0
], // TODO: Will be calculated by material theme generator
'light': [
180,
180,
180
],
'bright': [
200,
200,
200
],
'test': [
255,
0,
255
] // purple
},
'shadow': {
'nordic': [
0,
0,
2
],
'deep-dark': [
40,
40,
40
],
'material': [
30,
30,
30
], // TODO: Will be calculated by material theme generator
'light': [
190,
190,
190
],
'bright': [
150,
150,
150
],
'test': [
120,
0,
0
] // dark red
},
'inactive': {
'nordic': [
200,
200,
200
],
'deep-dark': [
200,
200,
200
],
'material': [
200,
200,
200
], // TODO: Will be calculated by material theme generator
'light': [
65,
65,
65
],
'bright': [
60,
60,
60
],
'test': [
150,
150,
150
]
},
'inactive-background': {
'nordic': [
0,
0,
0
],
'deep-dark': [
0,
0,
0
],
'material': [
255,
255,
255
], // TODO: Will be calculated by material theme generator
'light': [
80,
80,
80
],
'bright': [
60,
60,
60
],
'test': [
60,
60,
60
]
}
};
const fonts = {
'primary': {
'nordic': 'Comfortaa',
'deep-dark': 'Comfortaa',
'material': 'Comfortaa',
'light': 'Adwaita Sans',
'bright': 'Adwaita Sans Extralight'
},
'accent': {
'nordic': 'Adwaita Sans',
'deep-dark': 'Adwaita Sans',
'material': 'Adwaita Sans',
'light': 'Cantarell',
'bright': 'Contarell Thin'
},
'mono': {
'nordic': 'Source Code Pro',
'deep-dark': 'Source Code Pro',
'material': 'Source Code Pro',
'light': 'Jetbrains Mono',
'bright': 'Jetbrains Mono',
}
};
const iconTheme = {
'nordic': 'Candy',
'deep-dark': 'Candy',
'material': 'Candy',
'light': 'Candy',
'bright': 'Candy'
};
const yaziThemes = {
'nordic': 'tokyo-night',
'deep-dark': 'dracula',
'material': 'dracula',
'light': 'catppuccin-latte',
'bright': 'catppuccin-latte',
};
+132
View File
@@ -0,0 +1,132 @@
const mustache = require( 'mustache' );
const colorThief = require( '@janishutz/colorthief' );
const fs = require( 'fs' );
const path = require( 'path' );
const util = require( './util' );
const generateTheme = require( './generateTheme' );
const chalk = require( 'chalk' ).default;
const inquirer = require( 'inquirer' ).default;
const build = ( wallpaper, lockpaper, theme ) => {
console.log( '\n=> Extracting colours' );
// Extract colour palette from chosen wallpaper using Color-Thief
colorThief.getPalette( wallpaper, 20 ).then( palette => {
palette = util.removeUselessColours( palette );
// Define view options (for rendering with mustache)
if ( theme === 'test' ) {
palette = [ [ 255, 0, 0 ], [ 0, 255, 0 ], [ 0, 0, 255 ] ];
}
console.log( 'The following colours will be used based on your wallpaper: ' );
let col = palette[ 0 ];
console.log( ' => Primary accent colour: ' + chalk.rgb( col[ 0 ], col[ 1 ], col[ 2 ] )( util.renderColourAsHex( col ) ) );
col = palette[ 1 ];
console.log( ' => Secondary accent colour: ' + chalk.rgb( col[ 0 ], col[ 1 ], col[ 2 ] )( util.renderColourAsHex( col ) ) );
col = palette[ 2 ];
console.log( ' => Tertiary accent colour: ' + chalk.rgb( col[ 0 ], col[ 1 ], col[ 2 ] )( util.renderColourAsHex( col ) ) );
inquirer.prompt( [{
type: 'confirm',
name: 'confirm-proceed-build',
message: 'Okay to proceed with these colours?'
} ] ).then( answer => {
if ( answer['confirm-proceed-build'] ) proceedWithBuild( wallpaper, lockpaper, theme, palette );
else {
// Have the user pick any other of the extracted colours instead
let counter = -1;
const colourOptions = palette.map( c => {
counter++;
return {
name: chalk.rgb( c[ 0 ], c[ 1 ], c[ 2 ] )( util.renderColourAsHex( c ) ),
value: counter
}
} )
inquirer.prompt( [
{
'type': 'list',
'message': 'Pick the primary accent colour',
'choices': colourOptions,
'name': 'primary'
},
{
'type': 'list',
'message': 'Pick the secondary accent colour',
'choices': colourOptions,
'name': 'secondary'
},
{
'type': 'list',
'message': 'Pick the tertiary accent colour',
'choices': colourOptions,
'name': 'tertiary'
}
] ).then( result => {
const p = [ palette[ result.primary ], palette[ result.secondary ], palette[ result.tertiary ] ];
proceedWithBuild( wallpaper, lockpaper, theme, p );
} ).catch( e => {
console.error( e );
process.exit( 1 );
} );
}
} ).catch( e => {
console.error( e );
process.exit( 1 );
} );
} ).catch( e => {
console.error( e );
console.error( '\n=> Failed to load image or retrieve colour palette from it' );
process.exit( 1 );
} );
}
const proceedWithBuild = ( wallpaper, lockpaper, theme, palette ) => {
const view = generateTheme.generateTheme( theme, wallpaper, lockpaper, palette );
try {
fs.mkdir( path.join( __dirname, '/dist' ) );
} catch ( e ) {
}
// recursively index files from config directory -> Maybe add a file to each
// directory to indicate whether or not to index files in it?
const fileList = util.treeWalker( path.join( __dirname, '/../../renderable/' ), '*', [ 'node_modules', '@girs', '.gitignore', '.git', 'flavours' ] );
for (let index = 0; index < fileList.length; index++) {
try {
render( fileList[ index ], view );
} catch ( e ) {
console.error( '=> Render failed for ' + fileList[ index ] + ' with error ' + e );
}
}
util.themePreProcessor( path.join( __dirname, '/../../gtk-theme/src/gtk-4.0/gtk.css' ), 'src', 'dist' );
util.themePreProcessor( path.join( __dirname, '/../../gtk-theme/src/gtk-3.0/gtk.css' ), 'src', 'dist' );
render( path.join( __dirname, '/../../gtk-theme/src/colours.css' ), view, 'src', 'dist' );
}
/**
* @param {string} templatePath - absolute path to config directory
* @param {object} view - rendering config passed to mustache
* @param {string} originalDir - The original directory, defaults to renderable
* @param {string} newDir - the output directory override, defaults to config
*/
const render = ( templatePath, view, originalDir = 'renderable', newDir = 'config' ) => {
// Load template from disk (all can be found in <project-root>/renderable)
// TODO: Make exclusion better plus copy other files maybe?
const template = '' + fs.readFileSync( templatePath );
const outPath = path.join( templatePath.replace( originalDir, newDir ) );
console.log( '=> Rendering to ' + outPath );
try {
fs.mkdirSync( path.dirname( outPath ), {
recursive: true,
} );
} catch ( e ) {
console.error( e );
}
fs.writeFileSync( outPath, mustache.render( template, view ) );
}
module.exports = build;
+218
View File
@@ -0,0 +1,218 @@
const convert = require( 'color-convert' );
const fs = require( 'fs' );
const path = require( 'path' );
/**
* Recursively find all files with extension in a directory
* @param {string} dir The directory to search. Either absolute or relative path
* @param {string} extension The file extension to look for
* @returns {string[]} returns a list of html files with their full path
*/
const treeWalker = (
dir, extension, ignoreList
) => {
const ls = fs.readdirSync( dir );
const fileList = [];
for ( let file in ls ) {
if ( fs.statSync( path.join( dir, ls[ file ] ) ).isDirectory() ) {
// Filter ignored directories
if ( ignoreList === undefined || !ignoreList.includes( ls[ file ] ) ) {
const newFiles = treeWalker(
path.join( dir, ls[ file ] ), extension, ignoreList
);
for ( let file = 0; file < newFiles.length; file++ ) {
fileList.push( newFiles[ file ] );
}
}
} else if ( extension == '*' || ls[ file ].includes( extension ) ) {
if ( ignoreList === undefined || !ignoreList.includes( ls[ file ] ) ) {
fileList.push( path.join( dir, ls[ file ] ) );
}
}
}
return fileList;
};
const renderColourAsHex = colour => {
return '#' + convert.default.rgb.hex(
colour[ 0 ], colour[ 1 ], colour[ 2 ]
);
};
const renderColourAsRGB = colour => {
return `rgb(${ colour[ 0 ] }, ${ colour[ 1 ] }, ${ colour[ 2 ] })`;
};
const renderColourAsRGBA = ( colour, ambiance ) => {
return `rgba(${ colour[ 0 ] }, ${ colour[ 1 ] }, ${ colour[ 2 ] }, ${ ambiance })`;
};
const renderColourAsRGBHex = colour => {
const hexCol = convert.default.rgb.hex(
colour[ 0 ], colour[ 1 ], colour[ 2 ]
);
return `rgb(${ hexCol })`.toLowerCase();
};
function decimalToHex ( decimal ) {
const hexValue = Math.round( decimal * 255 );
return hexValue.toString( 16 ).padStart( 2, '0' );
}
const renderColourAsRGBAHex = ( colour, ambiance ) => {
const hexCol = convert.default.rgb.hex(
colour[ 0 ], colour[ 1 ], colour[ 2 ]
);
return `rgba(${ hexCol }${ decimalToHex( ambiance ) })`.toLowerCase();
};
const removeUselessColours = palette => {
const p = [];
for ( let i = 0; i < palette.length; i++ ) {
const el = palette[ i ];
const luminance = calculateLuminance( palette[ i ] );
if ( luminance < 210 && luminance > 40 ) {
p.push( palette[ i ] );
}
for ( let j = 0; j < el.length; j++ ) {
if ( el[j] > 70 ) {
p.push( palette[ i ] );
break;
}
}
}
return p;
};
const calculateLuminance = colour => {
return colour[ 0 ] + colour[ 1 ] + colour[ 2 ] / 3;
};
/*
* Replace the colours with variable names
*/
const replacements = {
'#0f1011': '@bg',
'rgba(9, 9, 10, 0.9)': '@bg_rgba_07',
'rgba(26, 28, 30, 0.3)': '@bg_rgba_05',
'#000': '@bg_accent',
'#000000': '@bg_accent',
'rgba(0, 0, 0, 0.7)': '@bg_accent_rgba_07',
'rgba(0, 0, 0, 0.6)': '@bg_accent_rgba_06',
'rgba(0, 0, 0, 0.5)': '@bg_accent_rgba_05',
'rgba(0, 0, 0, 0.4)': '@bg_accent_rgba_04',
'rgba(0, 0, 0, 0.3)': '@bg_accent_rgba_03',
'rgba(0, 0, 0, 0.12)': '@bg_accent_rgba_015',
'rgba(0, 0, 0, 0.08)': '@bg_accent_rgba_01',
'rgba(9, 9, 10, 0.9)': '@bg_inactive',
'#80868b': '@inactive',
'rgba(128, 134, 139, 0.7)': '@inactive_rgba_07',
'rgba(128, 134, 139, 0.5)': '@inactive_rgba_05',
'rgba(128, 134, 139, 0.3)': '@inactive_rgba_03',
'rgba(128, 134, 139, 0.2)': '@inactive_rgba_02',
// '#555A': '@shadow_rgba',
// '#555': '@shadow',
'#387db7': '@accent',
'rgba(56, 125, 183, 0.5)': '@accent_rgba_05',
'rgba(56, 125, 183, 0.32)': '@accent_rgba_03',
'rgba(56, 125, 183, 0.24)': '@accent_rgba_02',
'rgba(56, 125, 183, 0.16)': '@accent_rgba_015',
'rgba(56, 125, 183, 0.12)': '@accent_rgba_011',
'rgba(56, 125, 183, 0.08)': '@accent_rgba_007',
'#1a1a1b': '@accent_gradient_5',
'#1f1f21': '@accent_gradient_4',
'#1a2530': '@accent_gradient_3',
'#1c2c3b': '@accent_gradient_2',
'#1e3040': '@accent_gradient_1',
'#4887bd': '@accent_gradient_inverse_1',
'#508dc0': '@accent_gradient_inverse_2',
'#5892c3': '@accent_gradient_inverse_3',
'#673ab7': '@accent2',
'rgba(103, 58, 183, 0.12)': '@accent2_rgba_015',
'#fff': '@fg_accent',
'rgba(255, 255, 255, 0.7)': '@fg_accent_rgba_07',
'rgba(255, 255, 255, 0.6)': '@fg_accent_rgba_06',
'rgba(255, 255, 255, 0.5)': '@fg_accent_rgba_05',
'rgba(255, 255, 255, 0.3)': '@fg_accent_rgba_03',
'rgba(255, 255, 255, 0.2)': '@fg_accent_rgba_02',
'#9e9e9e': '@fg',
'rgba(158, 158, 158, 0.7)': '@fg_rgba_07',
'rgba(158, 158, 158, 0.6)': '@fg_rgba_06',
'rgba(158, 158, 158, 0.5)': '@fg_rgba_05',
'rgba(158, 158, 158, 0.3)': '@fg_rgba_03',
'rgba(158, 158, 158, 0.2)': '@fg_rgba_02',
'rgba(158, 158, 158, 0.1168)': '@fg_rgba_01'
};
const themePreProcessor = (
file, replacement, out
) => {
const colours = Object.keys( replacements );
let data = '' + fs.readFileSync( file );
for ( let index = 0; index < colours.length; index++ ) {
const colour = colours[index];
data = data.replaceAll( colour, replacements[ colour ] );
}
const outPath = file.replace( replacement, out );
try {
fs.mkdirSync( path.dirname( outPath ), {
'recursive': true,
} );
} catch ( e ) {
console.error( e );
}
fs.writeFileSync( outPath, data );
};
const getGradientColour = (
colour, index, multiplier
) => {
if ( index === 0 ) {
return [
colour[ 0 ] * multiplier,
colour[ 1 ] * multiplier,
colour[ 2 ] * multiplier
];
}
const gradient = getGradientColour(
colour, index - 1, multiplier
);
return [
Math.min( 255, gradient[ 0 ] * multiplier ),
Math.min( 255, gradient[ 1 ] * multiplier ),
Math.min( 255, gradient[ 2 ] * multiplier )
];
};
module.exports = {
treeWalker,
renderColourAsHex,
renderColourAsRGB,
renderColourAsRGBA,
renderColourAsRGBHex,
renderColourAsRGBAHex,
themePreProcessor,
getGradientColour,
removeUselessColours
};
+23
View File
@@ -0,0 +1,23 @@
{
"name": "janishutz-config-build",
"version": "1.0.0",
"description": "Build janishutz's dotfiles configs",
"repository": {
"type": "git",
"url": "https://git.janishutz.com/janishutz/dotfiles"
},
"license": "GPL-3.0-or-later",
"author": "janishutz",
"type": "commonjs",
"main": "build.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@janishutz/colorthief": "^3.0.2",
"chalk": "^5.4.1",
"color-convert": "^3.0.1",
"inquirer": "^12.5.0",
"mustache": "^4.2.0"
}
}
+50
View File
@@ -0,0 +1,50 @@
const fs = require( 'fs' );
const path = require( 'path' );
const data = '' + fs.readFileSync( '/usr/share/themes/Material-Black-Blueberry/gtk-4.0/gtk.css' );
let lineNumber = 1;
const indexer = {};
for ( let i = 0; i < data.length; i++ ) {
const char = data[i];
if ( char === '\n' ) {
lineNumber++;
} else if ( char === '#' ) {
const extract = data.substring( i );
const col = extract.slice( 0, extract.indexOf( '\n' ) );
if ( !indexer[ col ] ) {
indexer[ col ] = [];
}
indexer[ col ].push( lineNumber );
} else if ( char === 'r' ) {
const extract = data.substring( i );
if ( extract.slice( 0, 3 ) === 'rgb' ) {
const col = extract.slice( 0, extract.indexOf( '\n' ) );
if ( !indexer[ col ] ) {
indexer[ col ] = [];
}
indexer[ col ].push( lineNumber );
}
}
}
// Output
const keys = Object.keys( indexer );
for ( let i = 0; i < keys.length; i++ ) {
const element = keys[i];
if ( element.length <= 25 ) {
console.log( 'Colour ' + element.slice( 0, element.length - 1 ) + ' appears on ' + indexer[ element ] );
}
}
-10
View File
@@ -1,10 +0,0 @@
grub_theme = {
"desktop": "/usr/share/grub/themes/monterey-grub-theme/theme.txt",
"laptop": "/usr/share/grub/themes/monterey-grub-theme/theme.txt",
}
grub_os_prober = {"desktop": True, "laptop": False}
kernel_args_all = {"desktop": "", "laptop": ""}
kernel_args_debug_only = {"desktop": "", "laptop": ""}
kernel_args_normal_only = {"desktop": "", "laptop": ""}
user = "janis"
+131
View File
@@ -0,0 +1,131 @@
; thunar GtkAccelMap rc-file -*- scheme -*-
; this file is an automated accelerator map dump
;
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-by-type" "")
; (gtk_accel_path "<Actions>/ThunarStatusBar/toggle-last-modified" "")
; (gtk_accel_path "<Actions>/Thunarwindow/menu" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/cut" "<Primary>x")
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-by-size" "")
; (gtk_accel_path "<Actions>/ThunarWindow/file-menu" "")
; (gtk_accel_path "<Actions>/ThunarBookmarks/e92146de8ffbb74bfd99784ed40bdeef" "")
; (gtk_accel_path "<Actions>/ThunarWindow/close-tab" "<Primary>w")
; (gtk_accel_path "<Actions>/ThunarWindow/switch-previous-tab-alt" "<Primary><Shift>ISO_Left_Tab")
; (gtk_accel_path "<Actions>/ThunarStatusBar/toggle-size" "")
; (gtk_accel_path "<Actions>/ThunarWindow/new-window" "<Primary>n")
; (gtk_accel_path "<Actions>/ThunarWindow/clear-directory-specific-settings" "")
; (gtk_accel_path "<Actions>/ThunarWindow/close-window" "<Primary>q")
; (gtk_accel_path "<Actions>/ThunarWindow/open-parent" "<Alt>Up")
; (gtk_accel_path "<Actions>/ThunarWindow/view-side-pane-menu" "")
; (gtk_accel_path "<Actions>/ThunarStatusBar/toggle-size-in-bytes" "")
; (gtk_accel_path "<Actions>/ThunarWindow/switch-previous-tab" "<Primary>Page_Up")
; (gtk_accel_path "<Actions>/ThunarActionManager/open" "<Primary>o")
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-ascending" "")
; (gtk_accel_path "<Actions>/ThunarWindow/toggle-split-view" "F3")
; (gtk_accel_path "<Actions>/ThunarActionManager/copy-2" "<Primary>Insert")
; (gtk_accel_path "<Actions>/ThunarActionManager/trash-delete" "Delete")
; (gtk_accel_path "<Actions>/ThunarWindow/view-configure-toolbar" "")
; (gtk_accel_path "<Actions>/ThunarWindow/open-recent" "")
; (gtk_accel_path "<Actions>/ThunarBookmarks/7a203ca58c14c76ec1b07d08eaba7e8a" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/forward" "<Alt>Right")
; (gtk_accel_path "<Actions>/ThunarActionManager/restore" "")
; (gtk_accel_path "<Actions>/ThunarWindow/open-location-alt" "<Alt>d")
; (gtk_accel_path "<Actions>/ThunarStandardView/select-by-pattern" "<Primary>s")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-out-alt" "<Primary>KP_Subtract")
; (gtk_accel_path "<Actions>/ThunarWindow/contents" "F1")
; (gtk_accel_path "<Actions>/ThunarWindow/open-file-menu" "F10")
; (gtk_accel_path "<Actions>/ThunarWindow/show-highlight" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-descending" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-by-name" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/select-all-files" "<Primary>a")
; (gtk_accel_path "<Actions>/ThunarActionManager/execute" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/properties" "<Alt>Return")
; (gtk_accel_path "<Actions>/ThunarActionManager/cut-2" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-by-dtime" "")
; (gtk_accel_path "<Actions>/ThunarBookmarks/de6d2099b95ef15820d04f5c3a6dc0f8" "")
; (gtk_accel_path "<Actions>/ThunarWindow/open-templates" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/paste-2" "<Shift>Insert")
; (gtk_accel_path "<Actions>/ThunarWindow/switch-next-tab" "<Primary>Page_Down")
; (gtk_accel_path "<Actions>/ThunarStatusBar/toggle-filetype" "")
; (gtk_accel_path "<Actions>/ThunarWindow/close-all-windows" "<Primary><Shift>w")
; (gtk_accel_path "<Actions>/ThunarStandardView/create-document" "")
; (gtk_accel_path "<Actions>/ThunarWindow/detach-tab" "")
; (gtk_accel_path "<Actions>/ThunarWindow/cancel-search" "Escape")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-in-alt2" "<Primary>equal")
; (gtk_accel_path "<Actions>/ThunarStatusBar/toggle-hidden-count" "")
; (gtk_accel_path "<Actions>/ThunarShortcutsPane/sendto-shortcuts" "<Primary>d")
; (gtk_accel_path "<Actions>/ThunarActionManager/undo" "<Primary>z")
; (gtk_accel_path "<Actions>/ThunarBookmarks/0064c8b8c2b8ae1954479b6f2feab576" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/toggle-sort-order" "")
; (gtk_accel_path "<Actions>/ThunarWindow/view-location-selector-entry" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/paste" "<Primary>v")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-in-alt1" "<Primary>KP_Add")
; (gtk_accel_path "<Actions>/ThunarWindow/view-menubar" "<Primary>m")
; (gtk_accel_path "<Actions>/ThunarStandardView/back" "<Alt>Left")
; (gtk_accel_path "<Actions>/ThunarWindow/open-desktop" "")
; (gtk_accel_path "<Actions>/ThunarWindow/view-as-detailed-list" "<Primary>2")
; (gtk_accel_path "<Actions>/ThunarActionManager/restore-show" "")
; (gtk_accel_path "<Actions>/ThunarWindow/sendto-menu" "")
; (gtk_accel_path "<Actions>/ThunarStatusBar/toggle-display-name" "")
; (gtk_accel_path "<Actions>/ThunarWindow/go-menu" "")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-out" "<Primary>minus")
; (gtk_accel_path "<Actions>/ThunarWindow/remove-from-recent" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/open-with-other" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/invert-selection" "<Primary><Shift>i")
; (gtk_accel_path "<Actions>/ThunarWindow/view-side-pane-shortcuts" "<Primary>b")
; (gtk_accel_path "<Actions>/ThunarWindow/reload-alt-2" "Reload")
; (gtk_accel_path "<Actions>/ThunarBookmarks/fb8a2fb8c18b440d557134b69e8bfc79" "")
; (gtk_accel_path "<Actions>/ThunarWindow/view-location-selector-menu" "")
; (gtk_accel_path "<Actions>/ThunarWindow/edit-menu" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/copy" "<Primary>c")
; (gtk_accel_path "<Actions>/ThunarStandardView/sort-by-mtime" "")
; (gtk_accel_path "<Actions>/ThunarWindow/reload-alt-1" "F5")
; (gtk_accel_path "<Actions>/ThunarStandardView/forward-alt" "Forward")
; (gtk_accel_path "<Actions>/ThunarActionManager/move-to-trash" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/delete-3" "<Shift>KP_Delete")
; (gtk_accel_path "<Actions>/ThunarWindow/bookmarks-menu" "")
; (gtk_accel_path "<Actions>/ThunarWindow/reload" "<Primary>r")
; (gtk_accel_path "<Actions>/ThunarBookmarks/ab554ce947264f765ed1ba66309937f1" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/arrange-items-menu" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/unselect-all-files" "Escape")
; (gtk_accel_path "<Actions>/ThunarWindow/open-computer" "")
; (gtk_accel_path "<Actions>/ThunarWindow/toggle-image-preview" "")
; (gtk_accel_path "<Actions>/ThunarWindow/toggle-side-pane" "F9")
; (gtk_accel_path "<Actions>/ThunarWindow/view-as-icons" "<Primary>1")
; (gtk_accel_path "<Actions>/ThunarActionManager/delete-2" "<Shift>Delete")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-in" "<Primary>plus")
; (gtk_accel_path "<Actions>/ThunarStandardView/rename" "F2")
; (gtk_accel_path "<Actions>/ThunarWindow/open-location" "<Primary>l")
; (gtk_accel_path "<Actions>/ThunarWindow/view-as-compact-list" "<Primary>3")
; (gtk_accel_path "<Actions>/ThunarWindow/view-menu" "")
; (gtk_accel_path "<Actions>/ThunarWindow/search" "<Primary>f")
; (gtk_accel_path "<Actions>/ThunarWindow/new-tab" "<Primary>t")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-reset" "<Primary>0")
; (gtk_accel_path "<Actions>/ThunarWindow/contents/help-menu" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/open-in-new-tab" "<Primary><Shift>p")
; (gtk_accel_path "<Actions>/ThunarWindow/view-location-selector-buttons" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/back-alt2" "Back")
; (gtk_accel_path "<Actions>/ThunarActionManager/redo" "<Primary><Shift>z")
; (gtk_accel_path "<Actions>/ThunarWindow/open-trash" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/open-in-new-window" "<Primary><Shift>o")
; (gtk_accel_path "<Actions>/ThunarWindow/view-statusbar" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/open-location" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/duplicate" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/trash-delete-2" "KP_Delete")
; (gtk_accel_path "<Actions>/ThunarStandardView/back-alt1" "BackSpace")
; (gtk_accel_path "<Actions>/ThunarStandardView/create-folder" "<Primary><Shift>n")
; (gtk_accel_path "<Actions>/ThunarWindow/open-home" "<Alt>Home")
; (gtk_accel_path "<Actions>/ThunarWindow/switch-focused-split-view-pane" "")
; (gtk_accel_path "<Actions>/ThunarWindow/show-hidden" "<Primary>h")
; (gtk_accel_path "<Actions>/ThunarStandardView/set-default-app" "")
; (gtk_accel_path "<Actions>/ThunarWindow/empty-trash" "")
; (gtk_accel_path "<Actions>/ThunarWindow/preferences" "")
; (gtk_accel_path "<Actions>/ThunarActionManager/delete" "")
; (gtk_accel_path "<Actions>/ThunarWindow/open-network" "")
; (gtk_accel_path "<Actions>/ThunarWindow/view-side-pane-tree" "<Primary>e")
; (gtk_accel_path "<Actions>/ThunarWindow/open-file-system" "")
; (gtk_accel_path "<Actions>/ThunarWindow/search-alt" "Search")
; (gtk_accel_path "<Actions>/ThunarWindow/switch-next-tab-alt" "<Primary>Tab")
; (gtk_accel_path "<Actions>/ThunarActionManager/sendto-desktop" "")
; (gtk_accel_path "<Actions>/ThunarStandardView/make-link" "")
; (gtk_accel_path "<Actions>/ThunarWindow/zoom-reset-alt" "<Primary>KP_0")
; (gtk_accel_path "<Actions>/ThunarWindow/about" "")
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action>
<icon>utilities-terminal</icon>
<name>Open Terminal Here</name>
<submenu></submenu>
<unique-id>1675076590898177-1</unique-id>
<command>terminator --working-directory %f</command>
<description>Example for a custom action</description>
<range></range>
<patterns>*</patterns>
<startup-notify/>
<directories/>
</action>
<action>
<icon>com.github.artemanufrij.findfileconflicts</icon>
<name>Fix filenames</name>
<submenu></submenu>
<unique-id>1691249342110327-1</unique-id>
<command>/home/janis/scripts/removeSpaces.sh %f</command>
<description>A script that fixes the filenames in a directory recursively</description>
<range>*</range>
<patterns>*</patterns>
</action>
</actions>
+2
View File
@@ -0,0 +1,2 @@
node_modules/
@girs/
+19
View File
@@ -0,0 +1,19 @@
import { App } from "astal/gtk4"
import style from "./style.scss"
import Bar from "./ui/Launcher"
App.start({
css: style,
main() {
App.get_monitors().map(Bar)
},
requestHandler(request, res) {
if ( request === 'open' ) {
res( 'ok' );
} else if ( request === 'close' ) {
res( 'ok' );
} else if ( request === 'toggle' ) {
res( 'ok' );
}
}
})
+75
View File
@@ -0,0 +1,75 @@
/*
* dotfiles - components.d.ts
*
* Created by Janis Hutz 03/22/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
import type { UIComponent, ResultElement } from "./rendering";
export interface App extends ResultElement {
/**
* The app start command that will be executed
*/
command: string;
}
// TODO: Finish
export interface DictionaryEntry extends ResultElement {
/**
* Execute no command
*/
action: null;
/**
* The dictionary definition
*/
definition: string;
}
export interface CMDOutput extends ResultElement {
/**
* Stdout from the command that was run
*/
result: string;
}
export interface Calculation extends ResultElement {
/**
* THe calculation result
*/
result: string;
}
/* ************* *
* UI Components *
* ************* */
export interface LargeUIComponent extends UIComponent {
/**
* The number of items to display per line. Image size will automatically be scaled
* based on width
*/
itemsPerLine: number;
}
export interface MediumUIComponent extends UIComponent {}
export interface ListUIComponent extends UIComponent {}
export interface DictionaryUIComponent extends UIComponent {
elements: DictionaryEntry[];
}
export interface CMDOutputUIComponent extends UIComponent {
elements: CMDOutput[];
}
export interface CalculationUIComponent extends UIComponent {
elements: Calculation[];
}
+60
View File
@@ -0,0 +1,60 @@
/*
* dotfiles - rendering.d.ts
*
* Created by Janis Hutz 03/22/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
export interface UIComponent {
/**
* The title of the component (like a category name), shown above small divider line
*/
title: string;
/**
* ResultElement list, made up of all elements that should be shown
*/
elements: ResultElement[];
/**
* Choose how many elements to show before truncating (will expand when command is run)
*/
truncate: number;
/**
* The weight of the element (determines order)
*/
weight: number;
}
export interface ResultElement {
/**
* The name of the result element
*/
name: string;
/**
* Path to the image to be displayed in the UI
*/
img: string;
/**
* The weight of the element (determines order)
*/
weight: number;
/**
* The action to be executed
*/
action: Action;
/**
* The font size of the text (optional)
*/
fontSize: number | undefined;
}
type Action = '' | null;
+21
View File
@@ -0,0 +1,21 @@
declare const SRC: string
declare module "inline:*" {
const content: string
export default content
}
declare module "*.scss" {
const content: string
export default content
}
declare module "*.blp" {
const content: string
export default content
}
declare module "*.css" {
const content: string
export default content
}
+32
View File
@@ -0,0 +1,32 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fast_fuzzy_1 = require("fast-fuzzy");
const fs_1 = __importDefault(require("fs"));
// Get list source from args
// ARGS: type source
// Then we read query from stdin to not restart indexing & the like for each keystroke
let data = [];
if (process.argv[2] === 'fs') {
if (process.argv[3].includes('.json')) {
data = JSON.parse('' + fs_1.default.readFileSync(process.argv[3]));
}
else if (process.argv[3].includes('.txt')) {
data = ('' + fs_1.default.readFileSync(process.argv[3])).split(',');
}
else if (fs_1.default.statSync(process.argv[3]).isDirectory()) {
data = fs_1.default.readdirSync(process.argv[3]);
}
}
else if (process.argv[2] === 'arg') {
data = process.argv[3].split(',');
}
else {
throw new Error('Invalid argument at position 1. Can be either fs or arg, not ' + process.argv[2]);
}
process.stdin.on("data", (query) => {
// On stdin submit (which the other client will have to support) process data
console.log((0, fast_fuzzy_1.search)(query.toString(), data));
});
@@ -0,0 +1,18 @@
{
"name": "fzf",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@types/node": "^22.14.0"
},
"dependencies": {
"fast-fuzzy": "^1.12.0"
}
}
@@ -0,0 +1,27 @@
import { search } from 'fast-fuzzy';
import fs from 'fs';
// Get list source from args
// ARGS: type source
// Then we read query from stdin to not restart indexing & the like for each keystroke
let data: string[] = [];
if ( process.argv[ 2 ] === 'fs' ) {
if ( process.argv[ 3 ].includes( '.json' ) ) {
data = JSON.parse( '' + fs.readFileSync( process.argv[ 3 ] ) );
} else if ( process.argv[ 3 ].includes( '.txt' ) ) {
data = ( '' + fs.readFileSync( process.argv[ 3 ] ) ).split( ',' );
} else if ( fs.statSync( process.argv[ 3 ] ).isDirectory() ) {
data = fs.readdirSync( process.argv[ 3 ] );
}
} else if ( process.argv[ 2 ] === 'arg' ) {
data = process.argv[ 3 ].split( ',' );
} else {
throw new Error( 'Invalid argument at position 1. Can be either fs or arg, not ' + process.argv[ 2 ] );
}
process.stdin.on( "data", ( query ) => {
// On stdin submit (which the other client will have to support) process data
console.log( search( query.toString(), data ) );
} );
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"types": ["node"],
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": [ "./src/**/*" ],
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "astal-shell",
"dependencies": {
"astal": "/usr/share/astal/gjs"
}
}
+20
View File
@@ -0,0 +1,20 @@
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
$fg-color: #{"@theme_fg_color"};
$bg-color: #{"@theme_bg_color"};
window.Bar {
background: transparent;
color: $fg-color;
font-weight: bold;
>centerbox {
background: $bg-color;
border-radius: 10px;
margin: 8px;
}
button {
border-radius: 8px;
margin: 2px;
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"experimentalDecorators": true,
"strict": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
// "checkJs": true,
// "allowJs": true,
"jsx": "react-jsx",
"jsxImportSource": "astal/gtk4",
}
}
+32
View File
@@ -0,0 +1,32 @@
import { App, Astal, Gtk, Gdk } from "astal/gtk4"
import { Variable } from "astal"
function hide() {
App.get_window("launcher")!.hide()
}
export default function Launcher(monitor: Gdk.Monitor) {
const { CENTER } = Gtk.Align
const width = Variable(1000)
const text = Variable("")
return <window
name="launcher"
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM}
exclusivity={Astal.Exclusivity.IGNORE}
keymode={Astal.Keymode.ON_DEMAND}
application={App}
onShow={(self) => {
text.set("")
width.set(self.get_current_monitor().get_width_mm())
}}
onKeyPressed={(self, keyval, keycode) => {
print( 'key pressed: ' + keyval + ' which has code ' + keycode );
}}>
<box>
</box>
</window>
}
@@ -0,0 +1,3 @@
export default () => {
return <box></box>
}
File renamed without changes.
File renamed without changes.
+12
View File
@@ -0,0 +1,12 @@
/*
* dotfiles - file.ts
*
* Created by Janis Hutz 03/22/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
import { readFileAsync, writeFileAsync, monitorFile } from "astal";
+24
View File
@@ -0,0 +1,24 @@
/*
* dotfiles - fzf.ts
*
* Created by Janis Hutz 03/30/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
import AstalApps from "gi://AstalApps?version=0.1"
// TODO: For all astal apps, read a global colours config file
const fzfApplication = ( query: string ) => {
const apps = new AstalApps.Apps()
return apps.fuzzy_query( query );
}
const fzfCmd = ( query: string ) => {
}
export default {
fzfApplication
}
+51
View File
@@ -0,0 +1,51 @@
/*
* dotfiles - search.ts
*
* Created by Janis Hutz 03/22/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
import subprocessRunner from "./subprocessRunner";
import fzf from "./fzf";
const preprocess = ( input: string ) => {
// Find out what kind of instruction to process
if ( input.startsWith( ':' ) ) {
processCommand( input.substring( 1, input.indexOf( ' ' ) ), input.substring( input.indexOf( ' ' ) ).split( ' ' ) );
} else if ( input.startsWith( '!' ) ) {
processBang( input.substring( 1, input.indexOf( ' ' ) ), input.substring( input.indexOf( ' ' ) ) );
} else {
// Determine if entered string is calculation or not
// We can easily do that by asking qalc (qalculate cli) if this is fine
subprocessRunner.executeCommand( 'qalc "' + input + '"' ).then( out => {
// we get a calculation result here
print( out );
processCalculation( out );
} ).catch( err => {
processSearch( input );
print( err );
} );
}
}
const processSearch = ( input: string ) => {
}
const processCalculation = ( output: string ) => {
}
const processCommand = ( cmd: string, args: string[] ) => {
}
const processBang = ( bang: string, input: string ) => {
}
export default {
preprocess
}
@@ -0,0 +1,43 @@
/*
* dotfiles - subprocessRunner.ts
*
* Created by Janis Hutz 03/22/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
import { subprocess, execAsync, Process } from "astal/process";
// TODO: Get cwd and the likes to then use that to run JavaScript files with node / python with python, etc
/**
* Run a subprocess. If you simply want to run a command that doesn't need continuous updates
* run executeCommand instead.
* @param cmd - The command to be run
* @param onOut - Calback function for stdout of the subprocess
* @param onErr - [TODO:description]
* @returns [TODO:return]
*/
const startSubProcess = (
cmd: string | string[],
onOut: (stdout: string) => void,
onErr: (stderr: string) => void | undefined,
): Process => {
return subprocess( cmd, onOut, onErr );
};
/**
* Run a command. If you need continuous updates, run startSubProcess instead
* @param cmd - The command to be run. Either a string or an array of strings
* @returns A Promise resolving to stdout of the command
*/
const executeCommand = (cmd: string | string[]): Promise<string> => {
return execAsync( cmd );
};
export default {
startSubProcess,
executeCommand
}
+2
View File
@@ -0,0 +1,2 @@
node_modules/
@girs/
+32
View File
@@ -0,0 +1,32 @@
import { App } from "astal/gtk3"
import style from "./style.scss"
import not from "./handler"
App.start({
instanceName: "notifier",
css: style,
main() {
not.startNotificationHandler( 0, App.get_monitors()[0] )
},
requestHandler(request, res) {
if ( request == 'show' ) {
not.openNotificationMenu( 0 );
res( 'Showing all open notifications' );
} else if ( request == 'hide' ) {
not.closeNotificationMenu( 0 );
res( 'Hid all notifications' );
} else if ( request == 'clear' ) {
not.clearAllNotifications( 0 );
res( 'Cleared all notifications' );
} else if ( request == 'clear-newest' ) {
not.clearNewestNotifications( 0 );
res( 'Cleared newest notification' );
} else if ( request == 'toggle' ) {
not.toggleNotificationMenu( 0 );
res( 'Toggled notifications' );
} else {
res( 'Unknown command!' );
}
},
})
+21
View File
@@ -0,0 +1,21 @@
declare const SRC: string
declare module "inline:*" {
const content: string
export default content
}
declare module "*.scss" {
const content: string
export default content
}
declare module "*.blp" {
const content: string
export default content
}
declare module "*.css" {
const content: string
export default content
}
+206
View File
@@ -0,0 +1,206 @@
/*
* dotfiles - handler.ts
*
* Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
import { Astal, Gtk, Gdk } from "astal/gtk3"
import Notifd from "gi://AstalNotifd";
import Notification from "./notifications/notifications";
import { type Subscribable } from "astal/binding";
import { Variable, bind, timeout } from "astal"
// Config
const TIMEOUT_DELAY = 5000;
class Notifier implements Subscribable {
private display: Map<number, Gtk.Widget> = new Map();
private notifications: Map<number, Notifd.Notification> = new Map();
private notifd: Notifd.Notifd;
private subscriberData: Variable<Gtk.Widget[]> = Variable( [] );
private instanceID: number;
private menuOpen: boolean;
/**
* Sets up the notifier
*/
constructor( id: number ) {
this.instanceID = id;
this.menuOpen = false;
this.notifd = Notifd.get_default();
this.notifd.ignoreTimeout = true;
this.notifd.connect( 'notified', ( _, id ) => {
this.add( id );
} );
this.notifd.connect( 'resolved', ( _, id ) => {
this.delete( id );
} );
}
private notify () {
this.subscriberData.set( [ ...this.display.values() ].reverse() );
}
private add ( id: number ) {
this.notifications.set( id, this.notifd.get_notification( id )! );
this.show( id );
}
/**
* Show an element on screen
* @param id - The id of the element to be shown
*/
private show ( id: number ) {
this.set( id, Notification( {
notification: this.notifications.get( id )!,
onHoverLost: () => {
if ( !this.menuOpen ) {
this.hide( id );
}
},
setup: () => timeout( TIMEOUT_DELAY, () => {
if ( !this.menuOpen ) {
this.hide( id );
}
} ),
id: id,
delete: deleteHelper,
instanceID: this.instanceID
} ) )
}
/**
* Set a selected widget to be shown
* @param id - The id of the element to be referenced for later
* @param widget - A GTK widget instance
*/
private set ( id: number, widget: Gtk.Widget ) {
this.display.get( id )?.destroy();
this.display.set( id, widget );
this.notify();
}
/**
* Hide, not delete notification (= send to notification centre)
* @param id - The id of the notification to hide
*/
private hide ( id: number ) {
this.display.get( id )?.destroy();
this.display.delete( id );
this.notify();
}
/**
* Delete a notification (from notification centre too)
* @param id - The id of the notification to hide
*/
delete ( id: number ) {
this.hide( id );
this.notifications.get( id )?.dismiss();
this.notifications.delete( id );
if ( this.notifications.size == 0 ) {
this.menuOpen = false;
}
}
openNotificationMenu () {
// Show all notifications that have not been cleared
if ( this.notifications.size > 0 ) {
this.menuOpen = true;
this.notifications.forEach( ( _, id ) => {
this.show( id );
} )
}
}
hideNotifications () {
this.menuOpen = false;
this.notifications.forEach( ( _, id ) => {
this.hide( id );
} );
}
toggleNotificationMenu () {
if ( this.menuOpen ) {
this.hideNotifications();
} else {
this.openNotificationMenu();
}
}
clearAllNotifications () {
this.menuOpen = false;
this.notifications.forEach( ( _, id ) => {
this.delete( id );
} )
}
clearNewestNotification () {
this.delete( [ ...this.notifications.keys() ][0] );
}
subscribe(callback: (value: unknown) => void): () => void {
return this.subscriberData.subscribe( callback );
}
get() {
return this.subscriberData.get();
}
}
const notifiers: Map<number, Notifier> = new Map();
const deleteHelper = ( id: number, instanceID: number ) => {
notifiers.get( instanceID )?.delete( id );
}
const openNotificationMenu = ( id: number ) => {
notifiers.get( id )?.openNotificationMenu();
}
const closeNotificationMenu = ( id: number ) => {
notifiers.get( id )?.hideNotifications();
}
const toggleNotificationMenu = ( id: number ) => {
notifiers.get( id )?.toggleNotificationMenu();
}
const clearAllNotifications = ( id: number ) => {
notifiers.get( id )?.clearAllNotifications();
}
const clearNewestNotifications = ( id: number ) => {
notifiers.get( id )?.clearNewestNotification();
}
const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => {
const { TOP, RIGHT } = Astal.WindowAnchor
const notifier: Notifier = new Notifier( id );
notifiers.set( id, notifier );
return <window
className="NotificationHandler"
gdkmonitor={gdkmonitor}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | RIGHT}>
<box vertical noImplicitDestroy>
{bind(notifier)}
</box>
</window>
}
export default {
startNotificationHandler,
openNotificationMenu,
closeNotificationMenu,
clearAllNotifications,
clearNewestNotifications,
toggleNotificationMenu
}
@@ -0,0 +1,125 @@
@use "sass:string";
@function gtkalpha($c, $a) {
@return string.unquote("alpha(#{$c},#{$a})");
}
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
$fg-color: #{"@theme_fg_color"};
$bg-color: #{"@theme_bg_color"};
$error: red;
window.NotificationPopups {
all: unset;
}
eventbox.Notification {
&:first-child>box {
margin-top: 1rem;
}
&:last-child>box {
margin-bottom: 1rem;
}
// eventboxes can not take margins so we style its inner box instead
>box {
min-width: 400px;
border-radius: 13px;
background-color: $bg-color;
margin: .5rem 1rem .5rem 1rem;
box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
border: 1pt solid gtkalpha($fg-color, .03);
}
&.critical>box {
border: 1pt solid gtkalpha($error, .4);
.header {
.app-name {
color: gtkalpha($error, .8);
}
.app-icon {
color: gtkalpha($error, .6);
}
}
}
.header {
padding: .5rem;
color: gtkalpha($fg-color, 0.5);
.app-icon {
margin: 0 .4rem;
}
.app-name {
margin-right: .3rem;
font-weight: bold;
&:first-child {
margin-left: .4rem;
}
}
.time {
margin: 0 .4rem;
}
button {
padding: .2rem;
min-width: 0;
min-height: 0;
}
}
separator {
margin: 0 .4rem;
background-color: gtkalpha($fg-color, .1);
}
.content {
margin: 1rem;
margin-top: .5rem;
.summary {
font-size: 1.2em;
color: $fg-color;
}
.body {
color: gtkalpha($fg-color, 0.8);
}
.image {
border: 1px solid gtkalpha($fg-color, .02);
margin-right: .5rem;
border-radius: 9px;
min-width: 100px;
min-height: 100px;
background-size: cover;
background-position: center;
}
}
.actions {
margin: 1rem;
margin-top: 0;
button {
margin: 0 .3rem;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
}
@@ -0,0 +1,112 @@
// From astal examples
import { GLib } from "astal"
import { Gtk, Astal } from "astal/gtk3"
import { type EventBox } from "astal/gtk3/widget"
import Notifd from "gi://AstalNotifd"
const isIcon = (icon: string) =>
!!Astal.Icon.lookup_icon(icon)
const fileExists = (path: string) =>
GLib.file_test(path, GLib.FileTest.EXISTS)
const time = (time: number, format = "%H:%M") => GLib.DateTime
.new_from_unix_local(time)
.format(format)!
const urgency = (n: Notifd.Notification) => {
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency
// match operator when?
switch (n.urgency) {
case LOW: return "low"
case CRITICAL: return "critical"
case NORMAL:
default: return "normal"
}
}
type Props = {
delete( id: number, instanceID: number ): void
setup(self: EventBox): void
onHoverLost(self: EventBox): void
notification: Notifd.Notification
id: number
instanceID: number
}
export default function Notification(props: Props) {
const { notification: n, onHoverLost, setup, id: id, delete: del, instanceID: instance } = props
const { START, CENTER, END } = Gtk.Align
return <eventbox
className={`Notification ${urgency(n)}`}
setup={setup}
onHoverLost={onHoverLost}>
<box vertical>
<box className="header">
{(n.appIcon || n.desktopEntry) && <icon
className="app-icon"
visible={Boolean(n.appIcon || n.desktopEntry)}
icon={n.appIcon || n.desktopEntry}
/>}
<label
className="app-name"
halign={START}
truncate
label={n.appName || "Unknown"}
/>
<label
className="time"
hexpand
halign={END}
label={time(n.time)}
/>
<button onClicked={() => del( id, instance )}>
<icon icon="window-close-symbolic" />
</button>
</box>
<Gtk.Separator visible />
<box className="content">
{n.image && fileExists(n.image) && <box
valign={START}
className="image"
css={`background-image: url('${n.image}')`}
/>}
{n.image && isIcon(n.image) && <box
expand={false}
valign={START}
className="icon-image">
<icon icon={n.image} expand halign={CENTER} valign={CENTER} />
</box>}
<box vertical>
<label
className="summary"
halign={START}
xalign={0}
label={n.summary}
truncate
/>
{n.body && <label
className="body"
wrap
useMarkup
halign={START}
xalign={0}
justifyFill
label={n.body}
/>}
</box>
</box>
{n.get_actions().length > 0 && <box className="actions">
{n.get_actions().map(({ label, id }) => (
<button
hexpand
onClicked={() => n.invoke(id)}>
<label label={label} halign={CENTER} hexpand />
</button>
))}
</box>}
</box>
</eventbox>
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "astal-shell",
"dependencies": {
"astal": "/usr/share/astal/gjs"
}
}
+2
View File
@@ -0,0 +1,2 @@
// Import notification box style
@use "./notifications/notifications.scss"
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"experimentalDecorators": true,
"strict": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
// "checkJs": true,
// "allowJs": true,
"jsx": "react-jsx",
"jsxImportSource": "astal/gtk3",
}
}
-10
View File
@@ -1,10 +0,0 @@
[general]
import = [
"~/.config/alacritty/dank-theme.toml"
]
[font]
normal = {
family = "Source Code Pro", style = "Regular"
}
size = 12.0
-31
View File
@@ -1,31 +0,0 @@
[colors.primary]
background = '#0e1513'
foreground = '#dde4e1'
[colors.selection]
text = '#dde4e1'
background = '#005049'
[colors.cursor]
text = '#0e1513'
cursor = '#82d5c9'
[colors.normal]
black = '#0e1513'
red = '#f49c6e'
green = '#6ad571'
yellow = '#fff372'
blue = '#6bcabc'
magenta = '#1c6258'
cyan = '#82d5c9'
white = '#d2dfdd'
[colors.bright]
black = '#808a88'
red = '#ffc09f'
green = '#a5ffab'
yellow = '#fff7a5'
blue = '#9ceadf'
magenta = '#b9fff4'
cyan = '#d2fff8'
white = '#f8fffe'
+2
View File
@@ -0,0 +1,2 @@
node_modules/
@girs/
+119
View File
@@ -0,0 +1,119 @@
import {
App
} from 'astal/gtk4';
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import Bar from './components/bar/Bar';
import Brightness from './util/brightness';
import {
hyprToGdk
} from './util/hyprland';
import style from './style.scss';
App.start( {
'instanceName': 'runner',
'css': style,
main () {
const hypr = AstalHyprland.get_default();
const bars = new Map<number, string>();
const barCreator = ( monitor: AstalHyprland.Monitor ) => {
const gdkMonitor = hyprToGdk( monitor );
if ( gdkMonitor ) {
print( 'Bar added for screen ' + monitor.get_id() );
bars.set( monitor.get_id(), Bar.BarLauncher( gdkMonitor ) );
}
};
for ( const monitor of hypr.monitors ) {
barCreator( monitor );
}
hypr.connect( 'monitor-added', ( _, monitor ) => {
barCreator( monitor );
} );
hypr.connect( 'monitor-removed', ( _, monitor ) => {
const windowName = bars.get( monitor );
if ( windowName ) {
const win = App.get_window( windowName );
if ( win ) {
App.toggle_window( windowName );
win.set_child( null );
App.remove_window( win );
print( 'Bar removed for screen', monitor );
}
bars.delete( monitor );
}
} );
// const monitors = App.get_monitors();
// print( "adding bar to monitors" );
// for (let index = 0; index < monitors.length; index++) {
// Bar.BarLauncher( monitors[ index ] );
// }
// Launcher();
},
requestHandler ( request, res ) {
const args = request.trimStart().split( ' ' );
if ( args[ 0 ] === 'notifier' ) {
res( 'Not available here yet, run astal -i notifier ' + args[ 1 ] );
// res( notifications.cliHandler( args ) );
} else if ( args[ 0 ] === 'bar' ) {
res( Bar.cliHandler( args ) );
} else if ( args[ 0 ] === 'brightness' ) {
try {
const brightness = Brightness.get_default();
if ( brightness.screenAvailable ) {
if ( args[ 1 ] === 'increase' ) {
brightness.screen += args.length > 1 ? parseInt( args[ 2 ] ) / 100 : 1;
} else if ( args[ 1 ] === 'decrease' ) {
brightness.screen -= args.length > 1 ? parseInt( args[ 2 ] ) / 100 : 1;
} else if ( args[ 1 ] === 'set' ) {
if ( args.length > 1 ) {
brightness.screen = parseInt( args[ 2 ] ) / 100;
} else {
res( 'Argument <brightness> unspecified' );
return;
}
} else {
res( 'Unknown command ' + args[ 1 ] );
return;
}
res( 'Ok' );
} else {
res( 'No controllable screen available' );
}
} catch ( e ) {
print( e );
res( 'Error running brightness change' );
}
} else if ( args[ 0 ] === 'reload-style' ) {
res( 'Reloading style sheets' );
App.apply_css( '', true );
// TODO: Compile scss and then load it
}
// } else if ( args[ 0 ] === 'launcher' ) {
// if ( args[ 1 ] === 'show' ) {
// App.get_window( 'launcher' )?.show();
// res( '[Launcher] Shown' );
// } else if ( args[ 1 ] === 'hide' ) {
// App.get_window( 'launcher' )?.hide();
// res( '[Launcher] Hidden' );
// } else if ( args[ 1 ] === 'toggle' ) {
// App.toggle_window( 'launcher' );
// res( '[Launcher] Toggled' );
// } else {
// res( '[Launcher] unknown command' );
// }
// }
},
} );
+1
View File
@@ -0,0 +1 @@
false
@@ -0,0 +1,91 @@
import Audio from './modules/Audio/Audio';
import {
BatteryBox
} from './modules/Battery';
import Bluetooth from './modules/Bluetooth/Bluetooth';
import Brightness from './modules/Brightness/Brightness';
import {
Gtk
} from 'astal/gtk4';
import Network from './modules/Networking/Network';
import Player from './modules/Player/Player';
import Power from './modules/Power';
import SysTray from './modules/SysTray';
import {
exec
} from 'astal';
const QuickActions = () => {
const popover = new Gtk.Popover( {
'cssClasses': [ 'quick-actions-wrapper' ]
} );
popover.set_child( renderQuickActions() );
return popover;
};
const renderQuickActions = () => {
const user = exec( '/bin/sh -c whoami' );
const profile = exec( '/bin/fish -c get-profile-picture' );
const cwd = exec( 'pwd' );
const um = Power.UserMenu();
return (
<box visible cssClasses={[
'quick-actions',
'popover-box'
]} vertical>
<centerbox
startWidget={
<button
onClicked={() => um.popup()}
cssClasses={[ 'stealthy-button' ]}
child={
<box>
{um}
<Gtk.Frame
cssClasses={[ 'avatar-icon' ]}
child={
<image
file={
profile !== ''
? profile
: cwd
+ '/no-avatar-icon.jpg'
}
></image>
}
></Gtk.Frame>
<label label={user}></label>
</box>
}
></button>
}
endWidget={
<box
hexpand={false}
>
<BatteryBox></BatteryBox>
<SysTray.SystemTray></SysTray.SystemTray>
<Power.Power></Power.Power>
</box>
}
></centerbox>
<Gtk.Separator marginTop={10} marginBottom={20}></Gtk.Separator>
<box>
<Bluetooth.BluetoothModule></Bluetooth.BluetoothModule>
<Network.Network></Network.Network>
</box>
<Gtk.Separator marginTop={10} marginBottom={10}></Gtk.Separator>
<Brightness.BrightnessModule></Brightness.BrightnessModule>
<Audio.AudioModule></Audio.AudioModule>
<Player.PlayerModule></Player.PlayerModule>
</box>
);
};
// TODO: Expose additional functions to be usable through CLI
export default {
QuickActions,
};
+35
View File
@@ -0,0 +1,35 @@
import { Gtk } from "astal/gtk4";
import Power from "./modules/Power";
import Audio from "./modules/Audio/Audio";
import Bluetooth from "./modules/Bluetooth/Bluetooth";
import Brightness from "./modules/Brightness/Brightness";
import Player from "./modules/Player/Player";
import { BatteryBox } from "./modules/Battery";
const QuickActions = () => {
const popover = new Gtk.Overlay( { cssClasses: [ 'quick-actions-wrapper' ] } );
popover.set_child(renderQuickActions());
return popover;
};
const renderQuickActions = () => {
return (
<box visible cssClasses={["quick-actions", "popover-box"]} vertical setup={ self }>
<box halign={Gtk.Align.END}>
<BatteryBox></BatteryBox>
<Power></Power>
</box>
<Bluetooth.BluetoothModule></Bluetooth.BluetoothModule>
<Gtk.Separator marginTop={10} marginBottom={10}></Gtk.Separator>
<Brightness.BrightnessModule></Brightness.BrightnessModule>
<Audio.AudioModule></Audio.AudioModule>
<Gtk.Separator marginTop={20} marginBottom={10}></Gtk.Separator>
<Player.PlayerModule></Player.PlayerModule>
</box>
);
};
// TODO: Expose additional functions to be usable through CLI
export default {
QuickActions,
};
@@ -0,0 +1,3 @@
.audio-box {
min-width: 320px;
}
@@ -0,0 +1,191 @@
import { bind } from "astal";
import { Gtk } from "astal/gtk4";
import AstalWp from "gi://AstalWp";
const wp = AstalWp.get_default()!;
const AudioModule = () => {
const setVolumeSpeaker = (volume: number) => {
wp.defaultSpeaker.set_volume(volume / 100);
};
const setVolumeMicrophone = (volume: number) => {
wp.defaultMicrophone.set_volume(volume / 100);
};
const speakerSelector = SinkSelectPopover(AstalWp.MediaClass.AUDIO_SINK);
const micSelector = SinkSelectPopover(AstalWp.MediaClass.AUDIO_SOURCE);
return (
<box cssClasses={["audio-box"]} vertical>
<box hexpand vexpand>
<button
onClicked={() =>
wp.defaultSpeaker.set_mute(
!wp.defaultSpeaker.get_mute(),
)
}
tooltipText={"Mute audio output"}
child={
<image
iconName={bind(wp.defaultSpeaker, "volumeIcon")}
marginEnd={3}
></image>
}
></button>
<label
label={bind(wp.defaultSpeaker, "volume").as(
v => Math.round(100 * v) + "%",
)}
></label>
<slider
value={bind(wp.defaultSpeaker, "volume").as(v => 100 * v)}
max={100}
min={0}
step={1}
hexpand
vexpand
onChangeValue={self => setVolumeSpeaker(self.value)}
></slider>
<button
cssClasses={["sink-select-button"]}
tooltipText={"Pick audio output"}
child={
<box>
<image iconName={"speaker-symbolic"}></image>
{speakerSelector}
</box>
}
onClicked={() => speakerSelector.popup()}
></button>
</box>
<box hexpand vexpand>
<button
onClicked={() =>
wp.defaultMicrophone.set_mute(
!wp.defaultMicrophone.get_mute(),
)
}
tooltipText={"Mute audio input"}
child={
<image
iconName={bind(wp.defaultMicrophone, "volumeIcon")}
marginEnd={3}
></image>
}
></button>
<label
label={bind(wp.defaultMicrophone, "volume").as(
v => Math.round(100 * v) + "%",
)}
></label>
<slider
value={bind(wp.defaultMicrophone, "volume").as(
v => 100 * v,
)}
max={100}
min={0}
step={1}
hexpand
vexpand
onChangeValue={self => setVolumeMicrophone(self.value)}
></slider>
<button
cssClasses={["sink-select-button"]}
tooltipText={"Select audio input"}
child={
<box>
<image iconName={"microphone"}></image>
{micSelector}
</box>
}
onClicked={() => micSelector.popup()}
></button>
</box>
</box>
);
};
const SinkPicker = (type: AstalWp.MediaClass) => {
const devices = bind(wp, "nodes");
wp.connect("ready", () => {
const dev = wp.get_nodes()!
for (let i = 0; i < dev.length; i++) {
const d = dev[i];
}
})
return (
<box vertical>
<label
label={`Available Audio ${type === AstalWp.MediaClass.AUDIO_SINK ? "Output" : type === AstalWp.MediaClass.AUDIO_SOURCE ? "Input" : ""} Devices`}
cssClasses={[ 'title-2' ]}
></label>
<Gtk.Separator marginBottom={5} marginTop={3}></Gtk.Separator>
<box vertical cssClasses={["sink-picker"]}>
{devices.as(d => {
return d.map(device => {
if (device.get_media_class() !== type) {
return <box cssClasses={[ 'empty' ]}></box>;
}
return (
<button
cssClasses={bind(device, "id").as(id => {
if (
id ===
(type ===
AstalWp.MediaClass.AUDIO_SINK
? wp.defaultSpeaker.id
: type ===
AstalWp.MediaClass
.AUDIO_SOURCE
? wp.defaultMicrophone.id
: "")
) {
return [
"sink-option",
"currently-selected-sink-option",
];
} else {
return ["sink-option"];
}
})}
child={
<box halign={Gtk.Align.START}>
<image
iconName={bind(device, "icon").as(
icon => icon,
)}
marginEnd={3}
></image>
<label
label={bind(
device,
"description",
).as(t => t ?? "")}
></label>
</box>
}
onClicked={() => {
device.set_is_default(true);
}}
></button>
);
});
})}
</box>
</box>
);
};
const SinkSelectPopover = (type: AstalWp.MediaClass) => {
const popover = new Gtk.Popover();
popover.set_child(SinkPicker(type));
return popover;
};
export default {
AudioModule,
};
@@ -0,0 +1,51 @@
{devices.as(d => {
return d.map(device => {
if (device.get_media_class() !== type) {
return <box cssClasses={[ 'empty' ]}></box>;
}
return (
<button
cssClasses={bind(device, "id").as(id => {
if (
id ===
(type ===
AstalWp.MediaClass.AUDIO_SPEAKER
? wp.defaultSpeaker.id
: type ===
AstalWp.MediaClass
.AUDIO_MICROPHONE
? wp.defaultMicrophone.id
: "")
) {
return [
"sink-option",
"currently-selected-sink-option",
];
} else {
return ["sink-option"];
}
})}
child={
<box halign={Gtk.Align.START}>
<image
iconName={bind(device, "icon").as(
icon => icon,
)}
marginEnd={3}
></image>
<label
label={bind(
device,
"description",
).as(t => t ?? "")}
></label>
</box>
}
onClicked={() => {
device.set_is_default(true);
}}
></button>
);
});
})}
@@ -0,0 +1,57 @@
import { bind } from "astal";
import Battery from "gi://AstalBattery";
export const BatteryBox = () => {
const battery = Battery.get_default();
const batteryEnergy = (energyRate: number) => {
return energyRate > 0.1 ? `${Math.round(energyRate * 10) / 10} W ` : "";
};
return (
<box
cssClasses={["battery-info"]}
visible={bind(battery, "isBattery")}
hexpand={false}
vexpand={false}
>
<image
iconName={bind(battery, "batteryIconName")}
tooltipText={bind(battery, "energyRate").as(er =>
batteryEnergy(er),
)}
/>
<label
label={bind(battery, "percentage").as(
p => ` ${Math.round(p * 100)}%`,
)}
marginEnd={3}
/>
<label
cssClasses={["battery-time"]}
visible={bind(battery, "charging").as(c => !c)}
label={bind(battery, "timeToEmpty").as(t => `(${toTime(t)})`)}
tooltipText={bind(battery, 'energyRate').as(er => `Time to empty. Power usage: ${batteryEnergy(er)}`)}
/>
<label
cssClasses={["battery-time"]}
visible={bind(battery, "charging")}
label={bind(battery, "timeToFull").as(t => `(${toTime(t)})`)}
tooltipText={bind(battery, 'energyRate').as(er => `Time to full. Charge rate: ${batteryEnergy(er)}`)}
/>
</box>
);
};
const MINUTE = 60;
const HOUR = MINUTE * 60;
const toTime = (time: number) => {
if (!time) return "Waiting on BIOS"
if (time > 24 * HOUR) return "24h+";
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}`;
};
@@ -0,0 +1,222 @@
import { bind, interval, readFile, timeout, writeFile } from "astal";
import { Gtk } from "astal/gtk4";
import AstalBluetooth from "gi://AstalBluetooth";
import BTDevice from "./Device";
const ALIGN = Gtk.Align;
const bt = AstalBluetooth.get_default();
const BluetoothModule = () => {
const picker = BluetoothPicker();
const openBTPicker = () => {
try {
bt.adapter.start_discovery();
} catch (e) {
printerr(e);
}
picker.popup();
};
return (
<box>
<button
cssClasses={bind(bt, "isPowered").as(powered =>
powered
? ["toggle-button", "toggle-on"]
: ["toggle-button"],
)}
onClicked={() => {
try {
bt.adapter.set_powered(!bt.adapter.get_powered())
} catch (_) { }
}}
child={
<box vertical>
<label
cssClasses={["title-2"]}
label={"Bluetooth"}
halign={ALIGN.CENTER}
valign={ALIGN.CENTER}
></label>
<box halign={ALIGN.CENTER} valign={ALIGN.CENTER}>
<label
visible={bind(bt, "isPowered").as(
p => !p,
)}
label="Disabled"
></label>
<label
visible={bind(bt, "isPowered")}
label={bind(bt, "devices").as(devices => {
let count = 0;
devices.forEach(device => {
if (device.connected) {
count++;
}
});
return `On (${count} ${count === 1 ? "client" : "clients"} connected)`;
})}
></label>
</box>
<label></label>
</box>
}
></button>
<button
cssClasses={["actions-button"]}
visible={bind(bt, "isPowered")}
child={
<box>
<image iconName={"arrow-right-symbolic"}></image>
{picker}
</box>
}
tooltipText={"View available devices"}
onClicked={() => openBTPicker()}
></button>
</box>
);
};
const BluetoothPickerList = () => {
let btEnableState = false;
try {
btEnableState = readFile("./btconf") === "true" ? true : false;
} catch (_) { }
if (bt.get_adapter()) {
print('Setting BT state to ' + btEnableState);
bt.adapter.set_powered(btEnableState);
} else {
timeout(5000, () => {
if (bt.get_adapter()) {
print('Setting BT state to ' + btEnableState);
bt.adapter.set_powered(btEnableState);
} else {
timeout(5000, () => {
try {
bt.adapter.set_powered(btEnableState);
} catch (_) { }
})
}
})
}
const updateState = () => {
btEnableState = !btEnableState;
writeFile("./btconf", "" + btEnableState);
};
return (
<box
vertical
onDestroy={() => {
try {
bt.adapter.stop_discovery()
} catch (_) { }
}}
cssClasses={["popover-box"]}
>
<label cssClasses={["title"]} label={"Bluetooth"}></label>
<Gtk.Separator marginTop={3} marginBottom={5}></Gtk.Separator>
<centerbox
startWidget={<label label={"Turn on at startup"}></label>}
endWidget={
<switch
valign={ALIGN.END}
halign={ALIGN.END}
active={btEnableState}
onButtonPressed={() => updateState()}
></switch>
}
></centerbox>
<label
marginTop={10}
label={"Connected & Trusted devices"}
cssClasses={["title-2"]}
></label>
<Gtk.Separator marginTop={3} marginBottom={5}></Gtk.Separator>
<box vertical cssClasses={["devices-list"]}>
{bind(bt, "devices").as(devices => {
return devices
.filter(device => {
if (device.get_connected() || device.get_paired()) {
return device;
}
})
.map(device => {
return <BTDevice device={device}></BTDevice>;
});
})}
</box>
<label
visible={bind(bt, "devices").as(devices => {
return (
devices.filter(device => {
if (device.get_connected() || device.get_paired()) {
return device;
}
}).length === 0
);
})}
label={"No connected / trusted devices"}
cssClasses={["bt-no-found", "bt-conn-list"]}
></label>
<label
label={"Discovered bluetooth devices"}
cssClasses={["title-2"]}
></label>
<Gtk.Separator marginBottom={5} marginTop={3}></Gtk.Separator>
<box vertical>
{bind(bt, "devices").as(devices => {
return devices
.filter(data => {
if (!data.get_connected() && !data.get_paired()) {
return data;
}
})
.map(device => {
return <BTDevice device={device}></BTDevice>;
});
})}
</box>
<label
visible={bind(bt, "devices").as(devices => {
return (
devices.filter(device => {
if (
!device.get_connected() &&
!device.get_paired()
) {
return device;
}
}).length === 0
);
})}
label={"No discovered devices"}
cssClasses={["bt-no-found"]}
></label>
</box>
);
};
const BluetoothPicker = () => {
const popover = new Gtk.Popover();
popover.set_child(BluetoothPickerList());
popover.connect("closed", () => {
try {
bt.adapter.stop_discovery();
} catch (e) {
printerr(e);
}
});
return popover;
};
export default {
BluetoothModule,
};
@@ -0,0 +1,72 @@
import { bind } from "astal";
import AstalBluetooth from "gi://AstalBluetooth";
const BTDevice = ({ device }: { device: AstalBluetooth.Device }) => {
return (
<button
visible={bind(device, "name").as(n => n !== null)}
child={
<centerbox
startWidget={
<box>
<image
iconName={"chronometer-reset"}
tooltipText={"Device is currently connecting"}
visible={bind(device, "connecting")}
></image>
<image
iconName={bind(device, "icon")}
marginEnd={3}
></image>
</box>
}
centerWidget={
<label
label={bind(device, "name").as(n => n ?? "No name")}
marginEnd={5}
></label>
}
endWidget={
<box>
<label
label={bind(device, "batteryPercentage").as(
bat => (bat >= 0 ? bat + "%" : "?%"),
)}
tooltipText={"Device's battery percentage"}
marginEnd={3}
></label>
<image
iconName={bind(device, "paired").as(v =>
v ? "network-bluetooth-activated-symbolic" : "bluetooth-disconnected-symbolic",
)}
></image>
<button tooltipText={"Device trusted status"} child={
<image
iconName={bind(device, "trusted").as(v =>
v ? "checkbox" : "window-close-symbolic",
)}
></image>
} onClicked={() => device.set_trusted( !device.get_trusted() )}
cssClasses={[ 'button-no-margin' ]}
></button>
</box>
}
></centerbox>
}
onClicked={() => {
connectOrPair( device );
}}
></button>
);
};
const connectOrPair = (device: AstalBluetooth.Device) => {
if ( device.get_paired() ) {
device.connect_device(() => { });
// Show failed message if tried to connect and failed
} else {
device.pair();
}
};
export default BTDevice;
@@ -0,0 +1,30 @@
import { bind } from "astal";
import Brightness from "../../../../util/brightness";
const brightness = Brightness.get_default();
const BrightnessModule = () => {
const setBrightness = (value: number) => {
brightness.screen = value;
}
return (
<box visible={bind(brightness, 'screenAvailable')}>
<image iconName={"brightness-high-symbolic"}></image>
<label label={bind(brightness, "screen").as(b => `${Math.round(100 * b)}%`)}></label>
<slider
value={Math.round( brightness.screen * 100) / 100}
hexpand
max={1}
min={0.01}
step={0.05}
vexpand
onChangeValue={self => setBrightness(self.value)}
></slider>
</box>
);
};
export default {
BrightnessModule
};
@@ -0,0 +1,221 @@
import { execAsync, bind } from "astal";
import Network from "gi://AstalNetwork";
import { App, Gtk } from "astal/gtk4";
import { NetworkItem } from "./modules/NetworkItem";
import { PasswordDialog } from "./modules/PasswordDialog";
import {
availableNetworks,
savedNetworks,
activeNetwork,
showPasswordDialog,
scanNetworks,
getSavedNetworks,
disconnectNetwork,
forgetNetwork,
isExpanded,
refreshIntervalId,
} from "./networkinghelper";
// Main WiFi Box component
export const WiFiBox = () => {
const network = Network.get_default();
// Initial scan when component is first used
setTimeout(() => {
scanNetworks();
getSavedNetworks();
}, 100);
return (
<box vertical cssClasses={["wifi-menu", "toggle"]}>
{/* WiFi Toggle Header */}
<box cssClasses={["toggle", "wifi-toggle"]}>
<button
onClicked={() => {
if (network.wifi.enabled) {
network.wifi.set_enabled(false);
} else network.wifi.set_enabled(true);
}}
cssClasses={bind(network.wifi, "enabled").as((enabled) =>
enabled ? ["button"] : ["button-disabled"],
)}
>
<image iconName={bind(network.wifi, "icon_name")} />
</button>
<button
hexpand={true}
onClicked={() => {
if (network.wifi.enabled) {
isExpanded.set(!isExpanded.get());
if (isExpanded.get()) {
scanNetworks();
getSavedNetworks();
}
}
}}
>
<box hexpand={true}>
<label
hexpand={true}
xalign={0}
label={bind(network.wifi, "ssid").as(
(ssid) =>
ssid || (network.wifi.enabled ? "Not Connected" : "WiFi Off"),
)}
/>
<image
iconName="pan-end-symbolic"
halign={Gtk.Align.END}
cssClasses={bind(isExpanded).as((expanded) =>
expanded
? ["arrow-indicator", "arrow-down"]
: ["arrow-indicator"],
)}
/>
</box>
</button>
</box>
{/* Networks List Revealer */}
<revealer
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
transitionDuration={300}
revealChild={bind(isExpanded)}
setup={() => {
const clearScanInterval = () => {
if (refreshIntervalId.get()) {
clearInterval( parseInt( '' + refreshIntervalId.get() ));
refreshIntervalId.set(null);
}
};
bind(isExpanded).subscribe((expanded) => {
// Clear existing interval
clearScanInterval();
if (expanded) {
// Scan networks
network.wifi?.scan();
// Set up new interval if WiFi is enabled
if (network.wifi?.enabled) {
refreshIntervalId.set(
setInterval(() => {
scanNetworks();
getSavedNetworks();
print("updated");
}, 10000),
);
}
} else {
// Apply revealer bug fix when collapsed
App.toggle_window("system-menu");
App.toggle_window("system-menu");
}
});
// Monitor window toggling
const windowListener = App.connect("window-toggled", (_, window) => {
if (window.name === "system-menu" && isExpanded.get()) {
isExpanded.set(false);
}
});
// Clean up resources when component is destroyed
return () => {
App.disconnect(windowListener);
clearScanInterval();
};
}}
>
<box vertical cssClasses={["network-list"]}>
<box visible={showPasswordDialog( v => v )}>
<PasswordDialog />
</box>
<label label="Available Networks" cssClasses={["section-label"]} />
<label label="No networks found" cssClasses={["empty-label"]} visible={availableNetworks( net => net.length === 0 )}/>
<box visible={availableNetworks( networks => networks.length > 1 )}>
{availableNetworks( networks =>
networks.map( (network) => <NetworkItem network={network} />)
)}
</box>
{savedNetworks((networks) => {
// Filter out networks already shown in available networks
const filteredNetworks = networks.filter(
(ssid) => !availableNetworks.get().some((n) => n.ssid === ssid)
);
// Only render the section if there are filtered networks to show
return filteredNetworks.length > 0 ? (
<box vertical>
<label label="Saved Networks" cssClasses={["section-label"]} />
{filteredNetworks.map((ssid) => (
<box cssClasses={["saved-network"]}>
<label label={ssid} />
<box hexpand={true} />
<button
label="Forget"
cssClasses={["forget-button", "button"]}
onClicked={() => forgetNetwork(ssid)}
/>
</box>
))}
</box>
) : (
<box></box>
);
})}
<box hexpand>
<button
halign={Gtk.Align.START}
cssClasses={["refresh-button"]}
onClicked={() => {
scanNetworks();
getSavedNetworks();
}}
>
<image iconName="view-refresh-symbolic" />
</button>
{/* Connected Network Options */}
<box hexpand>
{activeNetwork((active) =>
active ? (
<box vertical cssClasses={["connected-network"]} hexpand>
<button
label="Disconnect"
cssClasses={["disconnect-button"]}
onClicked={() => disconnectNetwork(active.ssid)}
/>
</box>
) : (
""
),
)}
</box>
<button
cssClasses={["settings-button"]}
halign={Gtk.Align.END}
hexpand={false}
onClicked={() => {
execAsync([
"sh",
"-c",
"XDG_CURRENT_DESKTOP=GNOME gnome-control-center wifi",
]);
isExpanded.set(false);
}}
>
<image iconName={"emblem-system-symbolic"} />
</button>
) : (
""
),
)}
</box>
</box>
</revealer>
</box>
);
};
@@ -0,0 +1,2 @@
# Source
This is a modified version from [MatShell](https://github.com/Neurian/matshell)
@@ -0,0 +1,14 @@
import AstalNetwork from "gi://AstalNetwork?version=0.1";
interface CurrentWiFi {
ssid: string;
strength: number;
secured: boolean;
}
interface WiFiDetails extends CurrentWiFi {
active: boolean;
accessPoint: AstalNetwork.AccessPoint;
iconName: string;
}
@@ -0,0 +1,161 @@
// From https://github.com/Neurarian/matshell/blob/master/utils/wifi.ts
import { execAsync, Variable } from "astal";
import Network from "gi://AstalNetwork";
import { CurrentWiFi, WiFiDetails } from "./network";
// State trackers
export const availableNetworks: Variable<WiFiDetails[]> = Variable([]);
export const savedNetworks: Variable<string[]> = Variable([]);
export const activeNetwork: Variable<CurrentWiFi | null> = Variable(null);
export const isConnecting: Variable<boolean> = Variable(false);
export const showPasswordDialog: Variable<boolean> = Variable(false);
export const errorMessage: Variable<string> = Variable("");
export const isExpanded: Variable<boolean> = Variable(false);
export const passwordInput: Variable<string> = Variable("");
export const selectedNetwork: Variable<null | WiFiDetails> = Variable(null);
export const refreshIntervalId: Variable<
number | null | ReturnType<typeof setTimeout>
> = Variable(null);
// Function to scan for available networks
export const scanNetworks = () => {
const network = Network.get_default();
if (network && network.wifi) {
network.wifi.scan();
// Get available networks from access points
const networks: WiFiDetails[] = network.wifi.accessPoints
.map(ap => ({
ssid: ap.ssid,
strength: ap.strength,
secured: ap.flags !== 0,
active: network.wifi.activeAccessPoint?.ssid === ap.ssid,
accessPoint: ap,
iconName: ap.iconName,
}))
.filter(n => n.ssid);
// Sort by signal strength
networks.sort((a, b) => b.strength - a.strength);
// Remove duplicates (same SSID)
const uniqueNetworks: WiFiDetails[] = [];
const seen = new Set();
networks.forEach(network => {
if (!seen.has(network.ssid)) {
seen.add(network.ssid);
uniqueNetworks.push(network);
}
});
availableNetworks.set(uniqueNetworks);
// Update active network
if (network.wifi.activeAccessPoint) {
activeNetwork.set({
ssid: network.wifi.activeAccessPoint.ssid,
strength: network.wifi.activeAccessPoint.strength,
secured: network.wifi.activeAccessPoint.flags !== 0,
});
} else {
activeNetwork.set(null);
}
}
};
// Function to list saved networks
export const getSavedNetworks = () => {
execAsync(["bash", "-c", "nmcli -t -f NAME,TYPE connection show"])
.then(output => {
if (typeof output === "string") {
const savedWifiNetworks = output
.split("\n")
.filter(line => line.includes("802-11-wireless"))
.map(line => line.split(":")[0].trim());
savedNetworks.set(savedWifiNetworks);
}
})
.catch(error => console.error("Error fetching saved networks:", error));
};
// Function to connect to a network
export const connectToNetwork = (
ssid: string,
password: null | string = null,
) => {
isConnecting.set(true);
errorMessage.set("");
const network = Network.get_default();
const currentSsid = network.wifi.ssid;
// Function to perform the actual connection
const performConnection = () => {
let command = "";
if (password) {
// Connect with password
command = `echo '${password}' | nmcli device wifi connect "${ssid}" --ask`;
} else {
// Connect without password (saved or open network)
command = `nmcli connection up "${ssid}" || nmcli device wifi connect "${ssid}"`;
}
execAsync(["bash", "-c", command])
.then(() => {
showPasswordDialog.set(false);
isConnecting.set(false);
scanNetworks(); // Refresh network list
})
.catch(error => {
console.error("Connection error:", error);
errorMessage.set("Failed to connect. Check password.");
isConnecting.set(false);
});
};
// If already connected to a network, disconnect first
if (currentSsid && currentSsid !== ssid) {
console.log(
`Disconnecting from ${currentSsid} before connecting to ${ssid}`,
);
execAsync(["bash", "-c", `nmcli connection down "${currentSsid}"`])
.then(() => {
// Wait a moment for the disconnection to complete fully
setTimeout(() => {
performConnection();
}, 500); // 500ms delay for clean disconnection
})
.catch(error => {
console.error("Disconnect error:", error);
// Continue with connection attempt even if disconnect fails
performConnection();
});
} else {
// No active connection or connecting to same network (reconnect case)
performConnection();
}
};
// Function to disconnect from a network
export const disconnectNetwork = (ssid: string) => {
execAsync(["bash", "-c", `nmcli connection down "${ssid}"`])
.then(() => {
scanNetworks(); // Refresh network list
})
.catch(error => {
console.error("Disconnect error:", error);
});
};
// Function to forget a saved network
export const forgetNetwork = (ssid: string) => {
execAsync(["bash", "-c", `nmcli connection delete "${ssid}"`])
.then(() => {
getSavedNetworks(); // Refresh saved networks list
scanNetworks(); // Refresh network list
})
.catch(error => {
console.error("Forget network error:", error);
});
};
@@ -0,0 +1,106 @@
import { bind } from "astal";
import AstalNetwork from "gi://AstalNetwork";
import networkHelper from "./network-helper";
import NetworkMenu from "./NetworkMenu";
const net = AstalNetwork.get_default();
const STATE = AstalNetwork.DeviceState;
const Network = () => {
const netMenu = NetworkMenu.NetworkMenu();
return (
<box>
<button
cssClasses={networkHelper.networkEnabled(en => {
if (en) return ["toggle-button", "toggle-on"];
else return ["toggle-button"];
})}
onClicked={() =>
networkHelper.setNetworking(
!networkHelper.networkEnabled.get(),
)
}
child={
<box vertical>
<label
label={bind(net.wifi, "enabled").as(
stat => `Network (${stat ? "WiFi" : "Wired"})`,
)}
cssClasses={["title-2"]}
></label>
<box child=
{bind(net, 'wired').as(v => {
if (v) {
return <label
label={bind(net.wired, "state").as(state => {
if (state === STATE.ACTIVATED) {
return (
"Wired. IP: " + networkHelper.getIP()
);
} else if (state === STATE.DISCONNECTED) {
return "Disconnected";
} else if (state === STATE.FAILED) {
return "Error";
} else if (
state === STATE.PREPARE ||
state === STATE.CONFIG ||
state === STATE.IP_CHECK ||
state === STATE.IP_CONFIG
) {
return "Connecting...";
} else {
return "Unavailable";
}
})}
visible={bind(net.wifi, "enabled").as(v => !v)}
></label>
} else {
return <label
label={"State unavailable"}
visible={bind(net.wifi, "enabled").as(v => !v)}
></label>
}
})}></box>
<label
label={bind(net.wifi, "state").as(state => {
if (state === STATE.ACTIVATED) {
return `${net.wifi.get_ssid()} (${networkHelper.getIP()})`;
} else if (state === STATE.DISCONNECTED) {
return "Disconnected";
} else if (state === STATE.FAILED) {
return "Error";
} else if (
state === STATE.PREPARE ||
state === STATE.CONFIG ||
state === STATE.IP_CHECK ||
state === STATE.IP_CONFIG
) {
return "Connecting...";
} else {
return "Unavailable";
}
})}
visible={bind(net.wifi, "enabled")}
></label>
</box>
}
></button >
<button
cssClasses={["actions-button"]}
visible={networkHelper.networkEnabled()}
onClicked={() => netMenu.popup()}
child={
<box>
<image iconName={"arrow-right-symbolic"}></image>
{netMenu}
</box>
}
tooltipText={"View available devices"}
></button>
</box >
);
};
export default {
Network,
};
@@ -0,0 +1,18 @@
import { Gtk } from "astal/gtk4";
const NetworkMenu = () => {
const popover = new Gtk.Popover();
popover.set_child( renderMenu() );
return popover;
};
const renderMenu = () => {
return <box vertical>
<image iconName={"appointment-soon-symbolic"} iconSize={Gtk.IconSize.LARGE}></image>
<label label={"Coming later"}></label>
</box>;
};
export default {
NetworkMenu,
};
@@ -0,0 +1,91 @@
import { bind } from "astal";
import { Gtk } from "astal/gtk4";
import AstalNetwork from "gi://AstalNetwork";
import networkHelper from "./network-helper";
const net = AstalNetwork.get_default();
const STATE = AstalNetwork.DeviceState;
const WiFiList = () => {
const popover = new Gtk.Popover({ cssClasses: ["WiFiPicker"] });
return popover;
};
const renderWiFiList = () => {
return <box>
<label label="Test"></label>
</box>
}
const Network = () => {
const wifiList = WiFiList();
return (
<box>
<button
cssClasses={networkHelper.networkEnabled(en => {
if (en) return ["network-button", "net-on"];
else return ["network-button"];
})}
onClicked={() =>
networkHelper.setNetworking(
!networkHelper.networkEnabled.get(),
)
}
child={<box vertical>
<label label="Wired" cssClasses={[ 'button-name' ]}></label>
<label label={bind( net.wired, 'state' ).as( state => {
if ( state === STATE.ACTIVATED ) {
return 'Connected. IP: ' + networkHelper.getIP();
} else if ( state === STATE.DISCONNECTED ) {
return 'Disconnected';
} else if ( state === STATE.FAILED ) {
return 'Error';
} else if ( state === STATE.PREPARE || state === STATE.CONFIG || state === STATE.IP_CHECK || state === STATE.IP_CONFIG ) {
return 'Connecting...';
} else {
return 'Unavailable';
}
} )}></label>
</box>}
></button>
<box>
<button
cssClasses={bind(net.wifi, "enabled").as(b => {
const classes = ["network-button"];
if (b) {
classes.push("wifi-on");
}
return classes;
})}
child={<box vertical>
<label label="WiFi" cssClasses={[ 'button-name' ]}></label>
<label label={bind( net.wifi, 'state' ).as( state => {
if ( state === STATE.ACTIVATED ) {
return 'Connected. IP: ' + networkHelper.getIP();
} else if ( state === STATE.DISCONNECTED ) {
return 'Disconnected';
} else if ( state === STATE.FAILED ) {
return 'Error';
} else if ( state === STATE.PREPARE || state === STATE.CONFIG || state === STATE.IP_CHECK || state === STATE.IP_CONFIG ) {
return 'Connecting...';
} else {
return 'Unavailable';
}
} )} visible={bind(net.wifi, 'enabled').as( en => en )}></label>
<label label="Disabled" visible={bind(net.wifi, 'enabled').as( en => !en )}></label>
</box>}
></button>
<button
cssClasses={["network-button-context"]}
visible={bind(net.wifi, "enabled").as(b => b)}
onClicked={() => wifiList.popup()}
></button>
</box>
{wifiList}
</box>
);
};
export default Network;
@@ -0,0 +1,28 @@
import { exec, Variable } from "astal";
import AstalNetwork from "gi://AstalNetwork";
const networkEnabled = Variable( exec( 'nmcli networking connectivity' ) !== 'none' );
const network = AstalNetwork.get_default();
const setNetworking = ( status: boolean ) => {
if ( status === true ) {
exec( 'nmcli networking on' );
networkEnabled.set( true );
} else {
exec( 'nmcli networking off' );
networkEnabled.set( false );
}
}
const getIP = () => {
return exec( `/bin/bash -c "ip addr show | grep 'inet ' | awk '{print $2}' | grep -v '127'"` ).split( '/' )[ 0 ];
}
export default {
networkEnabled,
setNetworking,
getIP
}
@@ -0,0 +1,14 @@
import AstalNetwork from "gi://AstalNetwork?version=0.1";
interface CurrentWiFi {
ssid: string;
strength: number;
secured: boolean;
}
interface WiFiDetails extends CurrentWiFi {
active: boolean;
accessPoint: AstalNetwork.AccessPoint;
iconName: string;
}
@@ -0,0 +1,161 @@
// From https://github.com/Neurarian/matshell/blob/master/utils/wifi.ts
import { execAsync, Variable } from "astal";
import Network from "gi://AstalNetwork";
import { CurrentWiFi, WiFiDetails } from "./network";
// State trackers
export const availableNetworks: Variable<WiFiDetails[]> = Variable([]);
export const savedNetworks: Variable<string[]> = Variable([]);
export const activeNetwork: Variable<CurrentWiFi | null> = Variable(null);
export const isConnecting: Variable<boolean> = Variable(false);
export const showPasswordDialog: Variable<boolean> = Variable(false);
export const errorMessage: Variable<string> = Variable("");
export const isExpanded: Variable<boolean> = Variable(false);
export const passwordInput: Variable<string> = Variable("");
export const selectedNetwork: Variable<null | WiFiDetails> = Variable(null);
export const refreshIntervalId: Variable<
number | null | ReturnType<typeof setTimeout>
> = Variable(null);
// Function to scan for available networks
export const scanNetworks = () => {
const network = Network.get_default();
if (network && network.wifi) {
network.wifi.scan();
// Get available networks from access points
const networks: WiFiDetails[] = network.wifi.accessPoints
.map(ap => ({
ssid: ap.ssid,
strength: ap.strength,
secured: ap.flags !== 0,
active: network.wifi.activeAccessPoint?.ssid === ap.ssid,
accessPoint: ap,
iconName: ap.iconName,
}))
.filter(n => n.ssid);
// Sort by signal strength
networks.sort((a, b) => b.strength - a.strength);
// Remove duplicates (same SSID)
const uniqueNetworks: WiFiDetails[] = [];
const seen = new Set();
networks.forEach(network => {
if (!seen.has(network.ssid)) {
seen.add(network.ssid);
uniqueNetworks.push(network);
}
});
availableNetworks.set(uniqueNetworks);
// Update active network
if (network.wifi.activeAccessPoint) {
activeNetwork.set({
ssid: network.wifi.activeAccessPoint.ssid,
strength: network.wifi.activeAccessPoint.strength,
secured: network.wifi.activeAccessPoint.flags !== 0,
});
} else {
activeNetwork.set(null);
}
}
};
// Function to list saved networks
export const getSavedNetworks = () => {
execAsync(["bash", "-c", "nmcli -t -f NAME,TYPE connection show"])
.then(output => {
if (typeof output === "string") {
const savedWifiNetworks = output
.split("\n")
.filter(line => line.includes("802-11-wireless"))
.map(line => line.split(":")[0].trim());
savedNetworks.set(savedWifiNetworks);
}
})
.catch(error => console.error("Error fetching saved networks:", error));
};
// Function to connect to a network
export const connectToNetwork = (
ssid: string,
password: null | string = null,
) => {
isConnecting.set(true);
errorMessage.set("");
const network = Network.get_default();
const currentSsid = network.wifi.ssid;
// Function to perform the actual connection
const performConnection = () => {
let command = "";
if (password) {
// Connect with password
command = `echo '${password}' | nmcli device wifi connect "${ssid}" --ask`;
} else {
// Connect without password (saved or open network)
command = `nmcli connection up "${ssid}" || nmcli device wifi connect "${ssid}"`;
}
execAsync(["bash", "-c", command])
.then(() => {
showPasswordDialog.set(false);
isConnecting.set(false);
scanNetworks(); // Refresh network list
})
.catch(error => {
console.error("Connection error:", error);
errorMessage.set("Failed to connect. Check password.");
isConnecting.set(false);
});
};
// If already connected to a network, disconnect first
if (currentSsid && currentSsid !== ssid) {
console.log(
`Disconnecting from ${currentSsid} before connecting to ${ssid}`,
);
execAsync(["bash", "-c", `nmcli connection down "${currentSsid}"`])
.then(() => {
// Wait a moment for the disconnection to complete fully
setTimeout(() => {
performConnection();
}, 500); // 500ms delay for clean disconnection
})
.catch(error => {
console.error("Disconnect error:", error);
// Continue with connection attempt even if disconnect fails
performConnection();
});
} else {
// No active connection or connecting to same network (reconnect case)
performConnection();
}
};
// Function to disconnect from a network
export const disconnectNetwork = (ssid: string) => {
execAsync(["bash", "-c", `nmcli connection down "${ssid}"`])
.then(() => {
scanNetworks(); // Refresh network list
})
.catch(error => {
console.error("Disconnect error:", error);
});
};
// Function to forget a saved network
export const forgetNetwork = (ssid: string) => {
execAsync(["bash", "-c", `nmcli connection delete "${ssid}"`])
.then(() => {
getSavedNetworks(); // Refresh saved networks list
scanNetworks(); // Refresh network list
})
.catch(error => {
console.error("Forget network error:", error);
});
};
@@ -0,0 +1,56 @@
$fg-color: #{"@theme_fg_color"};
$bg-color: #{"@theme_bg_color"};
box.players-box {
margin-top: 20px;
}
box.player {
padding: 0.6rem;
.cover-art {
min-width: 100px;
min-height: 100px;
border-radius: 9px;
margin-right: 0.6rem;
background-size: contain;
background-position: center;
}
.title {
font-weight: bold;
font-size: 1.1em;
}
scale {
padding: 0;
margin: 0.4rem 0;
border-radius: 20px;
trough {
min-height: 8px;
border-radius: 20px;
}
highlight {
background-color: $fg-color;
border-radius: 20px;
}
slider {
all: unset;
border-radius: 20px;
}
}
centerbox.actions {
min-width: 220px;
button {
min-width: 0;
min-height: 0;
padding: 0.4rem;
margin: 0 0.2rem;
}
}
}
@@ -0,0 +1,155 @@
import { bind } from "astal";
import { Gtk } from "astal/gtk4";
import AstalMpris from "gi://AstalMpris";
import Pango from "gi://Pango?version=1.0";
const ALIGN = Gtk.Align;
const mpris = AstalMpris.get_default();
mpris.connect("player-added", p => {
print("Player added:", p);
});
const PlayerModule = () => {
return (
<box vertical cssClasses={ [ 'players-box' ] }>
<label label={"Music Players"} halign={ALIGN.CENTER} cssClasses={[ 'title-2' ]}></label>
<Gtk.Separator marginTop={3} marginBottom={5}></Gtk.Separator>
<box cssClasses={["players"]}>
{bind(mpris, "players").as(players => {
return players.map(player => {
return <PlayerItem player={player}></PlayerItem>;
});
})}
</box>
<label label={"No playback active"} visible={bind(mpris, "players").as( players => players.length === 0 )}></label>
</box>
);
};
// TODO: Update widths
const pbStatus = AstalMpris.PlaybackStatus;
const PlayerItem = ({ player }: { player: AstalMpris.Player }) => {
return (
<box cssClasses={["player"]}>
<image
cssClasses={["cover-art"]}
file={bind(player, "coverArt")}
hexpand
vexpand
></image>
<box vertical>
<label
label={bind(player, "title").as(
title => title ?? "Unknown title",
)}
cssClasses={["title"]}
halign={ALIGN.START}
valign={ALIGN.START}
maxWidthChars={30}
ellipsize={Pango.EllipsizeMode.END}
></label>
<label
label={bind(player, "artist").as(
artist => artist ?? "Unknown artist",
)}
halign={ALIGN.START}
valign={ALIGN.START}
maxWidthChars={30}
ellipsize={Pango.EllipsizeMode.END}
></label>
<slider
visible={bind(player, "length").as(l => l > 0)}
value={bind(player, "position")}
min={0}
max={bind(player, "length")}
onChangeValue={v =>
player.set_position(v.get_value())
}
></slider>
<centerbox
cssClasses={["actions"]}
startWidget={
<label
label={bind(player, "position").as(v =>
secondsToFriendlyTime(v),
)}
hexpand
cssClasses={["position"]}
></label>
}
centerWidget={
<box>
<button
visible={bind(player, "canGoPrevious")}
child={
<image
iconName={
"media-skip-backward-symbolic"
}
></image>
}
onClicked={() => player.previous()}
></button>
<button
visible={bind(player, "canControl")}
child={
<image
iconName={bind(
player,
"playbackStatus",
).as(status => {
if (status === pbStatus.PLAYING) {
return "media-playback-pause-symbolic";
} else {
return "media-playback-start-symbolic";
}
})}
></image>
}
onClicked={() => player.play_pause()}
></button>
<button
visible={bind(player, "canGoNext")}
child={
<image
iconName={"media-skip-forward-symbolic"}
></image>
}
onClicked={() => player.next()}
></button>
</box>
}
endWidget={
<label
cssClasses={["length"]}
hexpand
label={bind(player, "length").as(v =>
secondsToFriendlyTime(v),
)}
></label>
}
></centerbox>
</box>
</box>
);
};
const secondsToFriendlyTime = (time: number) => {
const m = Math.floor(time / 60);
const minutes = Math.floor(m % 60);
const hours = Math.floor(m / 60 % 24);
const seconds = Math.floor(time % 60);
if (hours > 0) {
return `${hours}:${expandTime(minutes)}:${expandTime(seconds)}`;
} else {
return `${minutes}:${expandTime(seconds)}`;
}
};
const expandTime = (time: number): string => {
return time < 10 ? `0${time}` : "" + time;
};
export default {
PlayerModule,
};
@@ -0,0 +1,93 @@
import {
exec
} from 'astal';
import {
Gtk
} from 'astal/gtk4';
const PowerMenu = (): Gtk.Popover => {
const popover = new Gtk.Popover( {
'cssClasses': [ 'PowerMenu' ]
} );
const powerMenuBox = () => {
return (
<box>
<button
cssClasses={[ 'power-button' ]}
child={
<image iconName={'system-shutdown-symbolic'}></image>
}
onClicked={() => exec( '/bin/sh -c \'shutdown now\'' )}
></button>
<button
cssClasses={[ 'power-button' ]}
child={<image iconName={'system-reboot-symbolic'}></image>}
onClicked={() => exec( '/bin/sh -c \'reboot\'' )}
></button>
<button
cssClasses={[ 'power-button' ]}
child={<image iconName={'system-suspend-symbolic'}></image>}
onClicked={() => exec( '/bin/sh -c \'systemctl suspend\'' )}
></button>
</box>
);
};
popover.set_child( powerMenuBox() );
return popover;
};
const Power = () => {
const pm = PowerMenu();
return (
<button
widthRequest={0}
hexpand={false}
vexpand={false}
cssClasses={[ 'power-menu-button' ]}
child={
<box>
<image iconName={'system-shutdown-symbolic'}></image>
{pm}
</box>
}
onClicked={() => pm.popup()}
/>
);
};
const UserMenu = (): Gtk.Popover => {
const popover = new Gtk.Popover();
const powerMenuBox = () => {
return (
<box>
<button
cssClasses={[ 'power-button' ]}
child={
<image iconName={'system-lock-screen-symbolic'}></image>
}
onClicked={() => exec( '/bin/sh -c \'hyprlock\'' )}
></button>
<button
cssClasses={[ 'power-button' ]}
child={<image iconName={'system-log-out-symbolic'}></image>}
onClicked={() => exec( '/bin/sh -c \'hyprctl dispatch exit 0\'' )
}
></button>
</box>
);
};
popover.set_child( powerMenuBox() );
return popover;
};
export default {
Power,
UserMenu
};
@@ -0,0 +1,88 @@
import AstalTray from 'gi://AstalTray';
import {
GObject
} from 'astal';
import {
Gtk
} from 'astal/gtk4';
const SYNC = GObject.BindingFlags.SYNC_CREATE;
const SysTray = () => {
const trayBox = new Gtk.Box( {
'cssClasses': [ '' ]
} );
const tray = AstalTray.get_default();
const trayItems = new Map<string, Gtk.MenuButton>();
const trayAddedHandler = tray.connect( 'item-added', ( _, id ) => {
const item = tray.get_item( id );
const popover = Gtk.PopoverMenu.new_from_model( item.menu_model );
const icon = new Gtk.Image();
const button = new Gtk.MenuButton( {
popover,
'child': icon,
'cssClasses': [ 'tray-item' ],
} );
item.bind_property(
'gicon', icon, 'gicon', SYNC
);
popover.insert_action_group( 'dbusmenu', item.action_group );
item.connect( 'notify::action-group', () => {
popover.insert_action_group( 'dbusmenu', item.action_group );
} );
trayItems.set( id, button );
trayBox.append( button );
} );
const trayRemovedHandler = tray.connect( 'item-removed', ( _, id ) => {
const button = trayItems.get( id );
if ( button ) {
trayBox.remove( button );
button.run_dispose();
trayItems.delete( id );
}
} );
trayBox.connect( 'destroy', () => {
tray.disconnect( trayAddedHandler );
tray.disconnect( trayRemovedHandler );
} );
return trayBox;
};
const TrayPopover = () => {
const popover = new Gtk.Popover( {
'cssClasses': [ 'TrayPopover' ]
} );
popover.set_child( SysTray() );
return popover;
};
const SystemTray = () => {
const systray = TrayPopover();
return (
<button
widthRequest={0}
hexpand={false}
vexpand={false}
cssClasses={[ 'power-menu-button' ]}
child={
<box>
<image iconName={'systemtray'}></image>
{systray}
</box>
}
onClicked={() => systray.popup()}
/>
);
};
export default {
SystemTray
};
@@ -0,0 +1,56 @@
@use "./modules/Player/Player.scss";
@use "./modules/Audio/Audio.scss";
@use "../../util/colours.scss" as *;
.quick-actions-wrapper {
min-width: 520px;
}
box.quick-actions {
padding: 10px;
}
popover * {
border-radius: 20px;
}
button {
margin: 4px;
}
.button-no-margin {
margin-top: 0;
margin-bottom: 0;
}
.devices-list {
margin-bottom: 20px;
}
button.toggle-button {
min-width: 220px;
border-radius: 50px;
&.toggle-on {
min-width: 190px;
margin-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
background-color: $accent-color;
}
}
button.actions-button {
margin-left: 0;
border-radius: 0;
background-color: $accent-color;
border-top-right-radius: 50px;
border-bottom-right-radius: 50px;
}
.avatar-icon {
border-radius: 100px;
min-height: 40px;
min-width: 40px;
margin-right: 10px;
}
+84
View File
@@ -0,0 +1,84 @@
import {
App, Astal, Gdk, Gtk
} from 'astal/gtk4';
import Calendar from './modules/Calendar';
import {
CenterBox
} from 'astal/gtk4/widget';
import Hyprland from './modules/Hyprland';
import QuickView from './modules/QuickView';
import SystemInfo from './modules/SystemInfo';
const Bar = ( {
gdkmonitor, name
}: {
'gdkmonitor': Gdk.Monitor,
'name': string
} ) => {
const {
TOP, LEFT, RIGHT
} = Astal.WindowAnchor;
return (
<window
gdkmonitor={gdkmonitor}
cssClasses={[ 'Bar' ]}
name={name}
namespace={'bar'}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | LEFT | RIGHT}
visible
application={App}
child={
<CenterBox
orientation={Gtk.Orientation.HORIZONTAL}
startWidget={
<box
hexpand
halign={Gtk.Align.START}
>
<Hyprland.ModeStatus />
<Calendar.Time />
<Hyprland.Workspace />
</box>
}
centerWidget={<Hyprland.ActiveWindow />}
endWidget={
<box
hexpand
halign={Gtk.Align.END}
cssClasses={[ 'BarRight' ]}
>
<SystemInfo.SystemInfo />
<QuickView.QuickView />
</box>
}
></CenterBox>
}
></window>
);
};
const cliHandler = ( args: string[] ): string => {
console.debug( args );
return 'Not implemented';
};
const BarLauncher = ( monitor: Gdk.Monitor ) => {
const windowName = `bar-${ monitor.get_connector() }`;
const createBar = () => {
return <Bar gdkmonitor={monitor} name={windowName}></Bar>;
};
// Actually start the bar
createBar();
return windowName;
};
export default {
BarLauncher,
cliHandler,
};
+111
View File
@@ -0,0 +1,111 @@
@use "../../util/colours.scss" as *;
window.Bar {
font-family: "Comfortaa, sans-serif";
background: transparent;
color: $fg-color;
font-weight: bold;
/* >centerbox { */
/* background: $bg-color; */
/* border-radius: 10px; */
/* margin: 8px; */
/* } */
.mode-status {
color: white;
background-color: #00002dff;
padding-left: 10px;
padding-right: 10px;
margin-left: 5px;
border-radius: 20px;
font-family: $monospace-font;
&.command-mode {
background-color: darkslategray;
color: white;
}
&.windowing-mode {
background-color: darkslategray;
color: white;
}
&.workspace-mode {
background-color: darkblue;
color: white;
}
&.launch-mode {
background-color: darkgreen;
color: white;
}
&.device-mode {
background-color: darkred;
color: white;
}
&.screenshotting-mode {
background-color: purple;
color: white;
}
&.notifications-mode {
background-color: darkorange;
color: white;
}
}
.bar-button {
border-radius: 20px;
margin: 2px;
padding-left: 10px;
padding-right: 10px;
background-color: $bg-color;
& button {
background-color: $bg-color;
}
}
.quick-action-button {
border-radius: 20px;
margin: 2px;
padding-left: 10px;
padding-right: 10px;
background-color: $bg-color;
}
button.workspace-button {
border-radius: 20px;
margin: 1px;
&.focused-workspace-button {
color: $accent-color-2;
}
}
.tray-item {
margin: 0;
padding: 0;
& button {
margin: 2px;
box-shadow: none;
}
}
.time {
min-width: 11rem;
padding: 3px;
& button {
box-shadow: none;
padding: 0;
}
}
.quick-view-symbol {
margin: 4px;
}
}
@@ -0,0 +1,26 @@
import { GLib, Variable } from "astal";
import { Gtk } from "astal/gtk4";
const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => {
const time = Variable<string>("").poll(
1000,
() => GLib.DateTime.new_now_local().format(format)!,
);
return (
<menubutton
cssClasses={["time", "bar-button"]}
hexpand
halign={Gtk.Align.CENTER}
>
<label onDestroy={() => time.drop()} label={time()} halign={Gtk.Align.CENTER}></label>
<popover>
<Gtk.Calendar />
</popover>
</menubutton>
);
};
export default {
Time,
};
@@ -0,0 +1,146 @@
import {
bind,
exec,
readFile
} from 'astal';
import AstalHyprland from 'gi://AstalHyprland';
import {
Gtk
} from 'astal/gtk4';
import Pango from 'gi://Pango?version=1.0';
const hypr = AstalHyprland.get_default();
const Workspace = () => {
return (
<box>
{bind( hypr, 'workspaces' ).as( wss => wss
.filter( ws => !( ws.id >= -99 && ws.id <= -2 ) ) // filter out special workspaces
.sort( ( a, b ) => a.id - b.id )
.map( ws => (
<button
cssClasses={bind( hypr, 'focusedWorkspace' ).as( fw => ws === fw
? [
'focused-workspace-button',
'workspace-button',
]
: [ 'workspace-button' ], )}
onButtonPressed={() => ws.focus()}
child={<label label={String( ws.id )}></label>}
></button>
) ), )}
</box>
);
};
/**
* Displays the name of the currently active window and provides a popover for
* displaying all available clients
*/
const ActiveWindow = () => {
const focused = bind( hypr, 'focusedClient' );
const WindowPopover = (): Gtk.Popover => {
// Set up boxes + Popover
const popover = new Gtk.Popover();
const popoverBox = WindowPopoverBox();
popover.set_child( popoverBox );
return popover;
};
const windowPopover = WindowPopover();
// ───────────────────────────────────────────────────────────────────
// Return fully assembled HyprlandFocusedClient box
// ───────────────────────────────────────────────────────────────────
return (
<box visible={focused.as( Boolean )}>
<button
onClicked={() => windowPopover.popup()}
cssClasses={[ 'bar-button' ]}
child={
focused.as( client => client && (
<label
maxWidthChars={70}
ellipsize={Pango.EllipsizeMode.END}
label={bind( client, 'title' ).as( String )} />
), )
}></button>
{windowPopover}
</box >
);
};
type submaps = 'device' | 'launch' | 'workspace' | 'windowing' | 'screenshotting' | 'notifications' | '';
const ModeStatus = () => {
let isUsingHyprvim = false;
try {
const path = exec( 'bash -c "cd ~ && pwd"' ) + '/.config/hyprvim';
isUsingHyprvim = readFile( path ).trim() === 'y';
} catch ( e ) {
printerr( 'Failed to read hyprvim config', e );
}
const label = new Gtk.Label();
if ( !isUsingHyprvim ) return label;
print( '==> Using hyprvim config' );
const map = {
'device': 'DEV',
'launch': 'LNC',
'workspace': 'WSP',
'windowing': 'WIN',
'screenshotting': 'SCS',
'notifications': 'NOT',
'': 'NRM'
};
label.label = map[''];
label.cssClasses = [ 'mode-status' ];
// TODO: Possibly add popover to it that lists binds
hypr.connect( 'submap', ( _, name: submaps ) => {
label.label = map[name];
label.cssClasses = [
'mode-status',
name + '-mode'
];
} );
return label;
};
const WindowPopoverBox = () => {
return <box vertical>
<label label={'Available Windows'} cssClasses={[ 'title-2' ]}></label>
<Gtk.Separator marginTop={5} marginBottom={5}></Gtk.Separator>
<box vertical>
{bind( hypr, 'clients' ).as( clients => {
return clients.map( client => {
return <button child={
<box>
<label label={bind( client, 'workspace' ).as( w => `(WS ${ w.name })` )}></label>
<label label={bind( client, 'initialClass' ).as( c => `[${ c }]` )}></label>
<label label={bind( client, 'title' )}></label>
</box>
}
onClicked={() => client.focus()}
></button>;
} );
} )}
</box>
</box>;
};
export default {
Workspace,
ActiveWindow,
ModeStatus
};
@@ -0,0 +1,204 @@
import AstalBattery from 'gi://AstalBattery';
import AstalBluetooth from 'gi://AstalBluetooth';
import AstalNetwork from 'gi://AstalNetwork';
import AstalWp from 'gi://AstalWp';
import Brightness from '../../../util/brightness';
import {
Gtk
} from 'astal/gtk4';
import QuickActions from '../../QuickActions/QuickActions';
import {
bind
} from 'astal';
import {
execAsync
} from 'astal';
const STATE = AstalNetwork.DeviceState;
const QuickView = () => {
const qa = QuickActions.QuickActions();
const showQuickActions = () => {
qa.popup();
};
return (
<button
onClicked={() => showQuickActions()}
cssClasses={[ 'quick-action-button' ]}
child={
<box>
<BatteryWidget></BatteryWidget>
<Audio></Audio>
<BluetoothWidget></BluetoothWidget>
<NetworkWidget></NetworkWidget>
<image iconName={'system-shutdown-symbolic'}></image>
{qa}
</box>
}
></button>
);
};
const NetworkWidget = () => {
const network = AstalNetwork.get_default();
return (
<box>
<image
iconName={bind( network, 'state' ).as( state => {
if ( state === AstalNetwork.State.CONNECTING ) {
return 'chronometer-reset-symbolic';
} else if (
state === AstalNetwork.State.CONNECTED_LOCAL
|| state === AstalNetwork.State.CONNECTED_SITE
|| state === AstalNetwork.State.CONNECTED_GLOBAL
) {
return 'network-wired-activated-symbolic';
} else {
return 'paint-unknown-symbolic';
}
} )}
cssClasses={[
'network-widget',
'quick-view-symbol'
]}
visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED, )}
></image>
<image
iconName={bind( network.wifi, 'state' ).as( state => {
if ( state === STATE.ACTIVATED ) {
return network.wifi.iconName;
} else {
return '';
}
} )}
tooltipText={bind( network.wifi, 'ssid' )}
cssClasses={[
'network-widget',
'quick-view-symbol'
]}
visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED, )}
></image>
</box>
);
};
const BluetoothWidget = () => {
const bluetooth = AstalBluetooth.get_default();
const enabled = bind( bluetooth, 'isPowered' );
const connected = bind( bluetooth, 'isConnected' );
// For each connected BT device, render status
return (
<box>
<box visible={enabled.as( e => e )}>
<image
iconName={'bluetooth-active-symbolic'}
visible={connected.as( c => c )}
></image>
<image
iconName={'bluetooth-disconnected-symbolic'}
visible={connected.as( c => !c )}
></image>
</box>
<image
iconName={'bluetooth-disabled-symbolic'}
visible={enabled.as( e => !e )}
></image>
<box>
{bind( bluetooth, 'devices' ).as( devices => {
return devices.map( device => {
return (
<image
iconName={bind( device, 'icon' ).as( icon => icon, )}
visible={bind( device, 'connected' )}
tooltipText={bind( device, 'batteryPercentage' ).as( n => {
return device.get_name() + ': ' + n + '%';
}, )}
></image>
);
} );
} )}
</box>
</box>
);
};
let hasSentNotification = false;
const BatteryWidget = () => {
const battery = AstalBattery.get_default();
if ( battery.get_is_present() ) {
return (
<image
iconName={bind( battery, 'batteryIconName' ).as( icon => icon )}
cssClasses={[ 'quick-view-symbol' ]}
tooltipText={bind( battery, 'percentage' ).as( p => {
const level = Math.round( p * 100 );
if ( level < 20 && !hasSentNotification ) {
hasSentNotification = true;
execAsync( 'bash -c "notify-send \'Battery level below 20%\'"' );
}
return `Battery Level: ${ level }%`;
} )}
></image>
);
} else {
return <box></box>;
}
// Else, no battery available -> Don't show the widget
};
const BrightnessWidget = () => {
const brightness = Brightness.get_default();
const screen_brightness = bind( brightness, 'screen' );
return (
<box cssClasses={[ 'quick-view-symbol' ]}>
<image iconName={'brightness-high-symbolic'}></image>
<label
label={screen_brightness.as( b => '' + Math.round( 100 * b ) )}
visible={bind( brightness, 'screenAvailable' )}
></label>
</box>
);
};
const Audio = () => {
const wireplumber = AstalWp.get_default();
if ( wireplumber ) {
return (
<box orientation={Gtk.Orientation.HORIZONTAL}>
<image
iconName={bind( wireplumber.defaultSpeaker, 'volumeIcon' ).as( icon => icon, )}
cssClasses={[ 'quick-view-symbol' ]}
tooltipText={bind( wireplumber.defaultSpeaker, 'volume' ).as( v => Math.round( 100 * v ) + '%' )}
></image>
<image
iconName={bind( wireplumber.defaultMicrophone,
'volumeIcon', ).as( icon => icon )}
cssClasses={[ 'quick-view-symbol' ]}
tooltipText={bind( wireplumber.defaultMicrophone, 'volume' ).as( v => Math.round( 100 * v ) + '%' )}
></image>
</box>
);
} else {
print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing', );
return <image iconName={'action-unavailable-symbolic'}></image>;
}
};
// cssClasses={[ 'quick-view-symbol' ]}
export default {
QuickView,
BrightnessWidget
};
@@ -0,0 +1,105 @@
import {
Gtk
} from 'astal/gtk4';
import {
execAsync
} from 'astal';
import sysinfo from '../sysinfo';
const info = () => {
return (
<box vertical>
<label
label={'System Information'}
cssClasses={[ 'title-2' ]}
></label>
<Gtk.Separator marginTop={5} marginBottom={10}></Gtk.Separator>
<label
vexpand
halign={Gtk.Align.START}
hexpand
label={sysinfo.ramUsed( used => {
return 'RAM: ' + used + ` (${ sysinfo.ramUtil.get() }%)`;
} )}
></label>
<label
label={sysinfo.systemStats( stats => {
return `CPU: ${ stats.cpuTemp }, ${ stats.cpuClk }
GPU: ${ stats.gpuTemp }, ${ stats.gpuClk } (${ stats.vram } / ${ stats.availableVRAM })
Kernel: ${ stats.kernel }`;
} )}
></label>
<Gtk.Separator marginTop={10}></Gtk.Separator>
<button
onClicked={() => execAsync( '/bin/sh -c "kitty --hold fish -c \'fastfetch\'"' )}
child={
<label label={'View FastFetch'}></label>
}></button>
</box>
);
};
const SystemInformationPanel = () => {
const popover = new Gtk.Popover();
popover.set_child( info() );
return popover;
};
const panel = SystemInformationPanel();
const SystemInfo = () => {
sysinfo.startSysInfoFetcher();
const openSysInfo = async () => {
panel.popup();
sysinfo.refreshStats();
};
if ( sysinfo.enabled ) {
return (
<button
onClicked={() => openSysInfo()}
child={
<box tooltipText={sysinfo.ramUsed( v => v )}>
<box
cssClasses={[ 'quick-view-symbol' ]}
>
<image
iconName={'power-profile-performance-symbolic'}
marginEnd={1}
></image>
<label
label={sysinfo.cpuUtil( util => util )}
marginEnd={5}
></label>
</box>
<box
cssClasses={[ 'quick-view-symbol' ]}
>
<image iconName={'memory'}></image>
<label label={sysinfo.ramUtil( util => util )}></label>
</box>
<box
cssClasses={[ 'quick-view-symbol' ]}
>
<image iconName={'show-gpu-effects-symbolic'}></image>
<label label={sysinfo.gpuUtil( util => util )}></label>
</box>
{panel}
</box>
}
cssClasses={[ 'bar-button' ]}
></button>
);
} else {
return <image iconName={'action-unavailable-symbolic'}></image>;
}
};
export default {
SystemInfo,
panel,
};
+9
View File
@@ -0,0 +1,9 @@
interface Stats {
kernel: string;
cpuTemp: string;
cpuClk: string;
gpuTemp: string;
gpuClk: string;
vram: string;
availableVRAM: string;
}
+137
View File
@@ -0,0 +1,137 @@
import { exec, execAsync, interval, Variable } from "astal";
const FETCH_INTERVAL = 2000;
const cpuUtil = Variable("0%");
const ramUtil = Variable("0%");
const ramUsed = Variable("0MiB");
const gpuUtil = Variable("0%");
let gpuName = "card1";
let enabled = true;
const getStats = (): Stats => {
gpuName = exec(`/bin/bash -c "ls /sys/class/drm/ | grep '^card[0-9]*$'"`);
const cpuNameInSensors = "CPUTIN";
const stats = {
kernel: exec("uname -sr"),
cpuTemp: exec(
`/bin/bash -c "sensors | grep -m1 ${cpuNameInSensors} | awk '{print $2}'"`,
),
cpuClk: exec(
`awk '/cpu MHz/ {sum+=$4; ++n} END {print sum/n " MHz"}' /proc/cpuinfo`,
),
gpuTemp: exec(
`/bin/bash -c "sensors | grep -E 'edge' | awk '{print $2}'"`,
),
gpuClk: exec(
`/bin/bash -c "cat /sys/class/drm/${gpuName}/device/pp_dpm_sclk | grep '\\*' | awk '{print $2 $3}'"`,
),
vram:
Math.round(
parseInt(
exec(
`cat /sys/class/drm/${gpuName}/device/mem_info_vram_used`,
),
) /
1024 /
1024,
) + "MiB",
availableVRAM:
Math.round(
parseInt(
exec(
`cat /sys/class/drm/${gpuName}/device/mem_info_vram_total`,
),
) /
1024 /
1024,
) + "MiB",
};
return stats;
};
const systemStats: Variable<Stats> = Variable(getStats());
const availableFeatures = {
cpu: true,
ram: true,
};
const refreshStats = () => {
systemStats.set(getStats());
}
const featureTest = () => {
print('[SysInfo] Feature test started...');
// Check if awk & sed are available
try {
exec("awk -V");
exec("sed --version");
} catch (e) {
printerr(
"[ SysInfo ] AWK or SED missing! No system info will be available",
);
enabled = false;
return;
}
// Check if mpstat is available
try {
exec("mpstat -V");
} catch (e) {
availableFeatures.cpu = false;
printerr(
"[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!",
);
}
print('[SysInfo] Feature test complete');
};
const sysInfoFetcher = () => {
if (enabled) {
if (availableFeatures.cpu) {
execAsync(`/bin/fish -c cpu-utilization`).then(v => {
cpuUtil.set("" + Math.round(parseFloat(v)));
}).catch(e => {
console.error(e);
});
}
if (availableFeatures.ram) {
execAsync(
`/bin/bash -c "free | awk '/Mem:/ {print $3 \\" \\" $2}'"`,
).then(v => {
const util = parseInt(v.split(' ')[0]);
const available = parseInt(v.split(' ')[1]);
ramUtil.set("" + Math.round(util / available * 100));
ramUsed.set(`${Math.round(util / 1024 / 1024 * 10) / 10} GiB of ${Math.round(available / 1024 / 1024 * 10) / 10} GiB used`);
}).catch(e => {
console.error(e);
});
}
gpuUtil.set(exec("cat /sys/class/drm/card1/device/gpu_busy_percent"));
}
};
let sysInfoFetcherRunning = false;
const startSysInfoFetcher = () => {
if (!sysInfoFetcherRunning) {
sysInfoFetcherRunning = true;
featureTest();
if (enabled) {
// Start interval
interval(FETCH_INTERVAL, sysInfoFetcher);
}
}
}
export default {
startSysInfoFetcher,
enabled,
gpuUtil,
cpuUtil,
ramUsed,
ramUtil,
refreshStats,
systemStats
}
@@ -0,0 +1,87 @@
import { Variable } from "astal";
import { App, Astal, Gdk, Gtk, hook } from "astal/gtk4";
import AstalApps from "gi://AstalApps";
import AppList from "./modules/Apps";
const prefixes = ['='];
function hide() {
App.get_window("launcher")!.hide();
}
const Launcher = () => {
const apps = new AstalApps.Apps();
const width = Variable(1000);
const height = Variable(1000);
const text = Variable("");
const visible = Variable(false);
const onEnter = () => {
// TODO handle custom stuff
apps.fuzzy_query(text.get())?.[0].launch();
hide();
};
return <window
name="launcher"
visible={visible()}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
keymode={Astal.Keymode.ON_DEMAND}
application={App}
onShow={(self) => {
width.set(self.get_current_monitor().geometry.width);
height.set(self.get_current_monitor().geometry.height);
}}
onKeyPressed={(self, keyval) => {
if (keyval === Gdk.KEY_Escape) self.hide();
}}
child={
<box
vertical
cssClasses={["app-launcher-wrapper"]}
widthRequest={width()}
heightRequest={height()}
valign={Gtk.Align.CENTER}
>
<button onClicked={hide} visible={false} />
<box
vertical
cssClasses={["app-launcher"]}
valign={Gtk.Align.CENTER}
halign={Gtk.Align.CENTER}
widthRequest={500}
>
<button onClicked={hide} visible={false}></button>
<box cssClasses={["search"]}>
<image iconName={"system-search-symbolic"}></image>
<entry
placeholderText={"Search..."}
text={text.get()}
setup={self => {
hook(self, App, 'window-toggled', (_, win) => {
if (win.name == 'launcher') {
self.set_text('');
self.grab_focus();
}
})
}}
onNotifyText={self => text.set(self.text)}
primaryIconSensitive
onActivate={onEnter}
hexpand></entry>
</box>
<AppList
hide={hide}
query={text}
visible={text(v => {
return !prefixes.includes(v.slice(0, 1));
})}
></AppList>
</box>
</box>
}
>
</window>
}
export default Launcher;
@@ -0,0 +1,16 @@
@use '../../util/colours.scss' as *;
window {
background: transparent;
}
box.app-launcher-wrapper {
background-color: $shadow-color;
>box.app-launcher {
background-color: $bg-color;
border-radius: 30px;
padding: 20px;
border: 1px solid $accent-color-2;
}
}
@@ -0,0 +1,59 @@
import { Binding, Variable } from "astal";
import { Gtk } from "astal/gtk4";
import AstalApps from "gi://AstalApps";
import Pango from "gi://Pango?version=1.0";
const MAX_ITEMS = 8;
const AppList = ({ hide, query, visible }: { hide: () => void, query: Variable<string>, visible: Binding<Boolean> }) => {
const apps = new AstalApps.Apps();
const list = query((text) => apps.fuzzy_query(text).slice(0, MAX_ITEMS));
return <box>
<box
spacing={6}
vertical
cssClasses={["app-list"]}
visible={list.as(l => l.length > 0)}
>
{list.as(l => l.map(app => <AppButton app={app} hide={hide}></AppButton>))}
</box>
<box
halign={Gtk.Align.CENTER}
cssClasses={["list-empty"]}
vertical
visible={list.as(l => l.length === 0)}
>
<image iconName={"system-search-symbolic"}></image>
<label label={"No match found"}></label>
</box>
</box>
}
const AppButton = ({ app, hide }: { app: AstalApps.Application, hide: () => void }) => {
return <button
onClicked={() => {
hide();
app.launch();
}}
child={
<box>
<image iconName={app.iconName}></image>
<box valign={Gtk.Align.CENTER} vertical>
<label
cssClasses={["title-2"]}
ellipsize={Pango.EllipsizeMode.END}
maxWidthChars={40}
xalign={0}
label={app.name}
></label>
<label
wrap xalign={0}
label={app.description}
></label>
</box>
</box>
}>
</button>
}
export default AppList;
Whitespace-only changes.
@@ -0,0 +1,6 @@
# Source
This has been copied from [matshell](https://github.com/Neurarian/matshell)
It is not yet used, as it has not been adapted yet to feature a notification history.
Potentially, a notification centre will be added to make this here work better. Styling is also missing
@@ -0,0 +1,32 @@
import { Astal } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import Hyprland from "gi://AstalHyprland";
import { bind } from "astal";
import { NotificationWidget } from "./modules/Notification";
import { hyprToGdk } from "../../util/hyprland";
export default function Notifications() {
const notifd = Notifd.get_default();
const hyprland = Hyprland.get_default();
const { TOP, RIGHT } = Astal.WindowAnchor;
return (
<window
name="notifications"
gdkmonitor={bind(hyprland, "focusedMonitor").as(
(focused: Hyprland.Monitor) => hyprToGdk(focused),
)}
anchor={TOP | RIGHT}
visible={bind(notifd, "notifications").as(
(notifications) => notifications.length > 0,
)}
child={
<box vertical={true} cssClasses={["notifications"]}>
{bind(notifd, "notifications").as((notifications) =>
notifications.map((n) => <NotificationWidget notification={n} />),
)}
</box>
}
/>
);
}
@@ -0,0 +1,25 @@
import { Gtk } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import { fileExists, isIcon } from "../../../util/notifd";
export function NotificationIcon(notification: Notifd.Notification) {
if (notification.image || notification.appIcon || notification.desktopEntry) {
const icon = notification.image || notification.appIcon || notification.desktopEntry;
if (fileExists(icon)) {
return (
<box expand={false} valign={Gtk.Align.CENTER}>
<image file={icon} />
</box>
);
} else if (isIcon(icon)) {
return (
<box expand={false} valign={Gtk.Align.CENTER}>
<image iconName={icon} />
</box>
);
}
}
return null;
}
@@ -0,0 +1,126 @@
import { bind } from "astal";
import { Gtk } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import { NotificationIcon } from "./Icon";
import { createTimeoutManager, time, urgency } from "../../../util/notifd";
export function NotificationWidget({
notification,
}: {
notification: Notifd.Notification;
}) {
const { START, CENTER, END } = Gtk.Align;
const actions = notification.actions || [];
const TIMEOUT_DELAY = 3000;
// Keep track of notification validity
const notifd = Notifd.get_default();
const timeoutManager = createTimeoutManager(
() => notification.dismiss(),
TIMEOUT_DELAY,
);
return (
<box
setup={(self) => {
// Set up timeout
timeoutManager.setupTimeout();
const clickGesture = Gtk.GestureClick.new();
clickGesture.set_button(0); // 0 means any button
clickGesture.connect("pressed", (gesture, _) => {
try {
// Get which button was pressed (1=left, 2=middle, 3=right)
const button = gesture.get_current_button();
if (button === 1) {
// PRIMARY/LEFT
if (actions.length > 0) n.invoke(actions[0]);
} else if (button === 2) {
// MIDDLE
notifd.notifications?.forEach((n) => {
n.dismiss();
});
} else if (button === 3) {
// SECONDARY/RIGHT
notification.dismiss();
}
} catch (error) {
console.error(error);
}
});
self.add_controller(clickGesture);
self.connect("unrealize", () => {
timeoutManager.cleanup();
});
}}
onHoverEnter={timeoutManager.handleHover}
onHoverLeave={timeoutManager.handleHoverLost}
vertical
vexpand={false}
cssClasses={["notification", `${urgency(notification)}`]}
name={notification.id.toString()}
>
<box cssClasses={["header"]}>
<label
cssClasses={["app-name"]}
halign={CENTER}
label={bind(notification, "app_name")}
/>
<label
cssClasses={["time"]}
hexpand
halign={END}
label={time(notification.time)}
/>
</box>
<Gtk.Separator />
<box cssClasses={["content"]}>
<box
cssClasses={["thumb"]}
visible={Boolean(NotificationIcon(notification))}
halign={CENTER}
valign={CENTER}
vexpand={true}
child={NotificationIcon(notification)!}
>
</box>
<box
vertical
cssClasses={["text-content"]}
hexpand={true}
halign={CENTER}
valign={CENTER}
>
<label
cssClasses={["title"]}
valign={CENTER}
wrap={false}
label={bind(notification, "summary")}
/>
{notification.body && (
<label
cssClasses={["body"]}
valign={CENTER}
wrap={true}
maxWidthChars={50}
label={bind(notification, "body")}
/>
)}
</box>
</box>
{actions.length > 0 && (
<box cssClasses={["actions"]}>
{actions.map(({ label, action }) => (
<button
hexpand
cssClasses={["action-button"]}
onClicked={() => notification.invoke(action)}
>
<label label={label} halign={CENTER} hexpand />
</button>
))}
</box>
)}
</box>
);
}
@@ -0,0 +1,6 @@
import AstalNotifd from "gi://AstalNotifd"
const notifd = AstalNotifd.get_default()
const getNotifications = () => {
}
@@ -0,0 +1,26 @@
import { Gtk } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import { fileExists, isIcon } from "./helper";
export function NotificationIcon(notification: Notifd.Notification) {
if ( notification.image || notification.appIcon || notification.desktopEntry) {
const icon = notification.image || notification.appIcon || notification.desktopEntry;
if (fileExists(icon)) {
return (
<box hexpand={false} valign={Gtk.Align.CENTER} child={
<image file={icon} />
}>
</box>
);
} else if (isIcon(icon)) {
return (
<box hexpand={false} valign={Gtk.Align.CENTER} child={
<image iconName={icon} />
}>
</box>
);
}
}
return null;
}
@@ -0,0 +1,32 @@
import { Astal } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import Hyprland from "gi://AstalHyprland";
import { bind } from "astal";
import { NotificationWidget } from "./modules/Notification";
import { hyprToGdk } from "../../util/hyprland";
export default function Notifications() {
const notifd = Notifd.get_default();
const hyprland = Hyprland.get_default();
const { TOP, RIGHT } = Astal.WindowAnchor;
return (
<window
name="notifications"
gdkmonitor={bind(hyprland, "focusedMonitor").as(
(focused: Hyprland.Monitor) => hyprToGdk(focused),
)}
anchor={TOP | RIGHT}
visible={bind(notifd, "notifications").as(
(notifications) => notifications.length > 0,
)}
child={
<box vertical={true} cssClasses={["notifications"]}>
{bind(notifd, "notifications").as((notifications) =>
notifications.map((n) => <NotificationWidget notification={n} />),
)}
</box>
}
/>
);
}
Whitespace-only changes.
@@ -0,0 +1,124 @@
@use "sass:string";
@function gtkalpha($c, $a) {
@return string.unquote("alpha(#{$c},#{$a})");
}
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
$fg-color: #{"@theme_fg_color"};
$bg-color: #{"@theme_bg_color"};
$error: red;
window.NotificationHandler {
all: unset;
}
box.notification {
&:first-child {
margin-top: 1rem;
}
&:last-child {
margin-bottom: 1rem;
}
& {
min-width: 400px;
border-radius: 13px;
background-color: $bg-color;
margin: .5rem 1rem .5rem 1rem;
box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
border: 1pt solid gtkalpha($fg-color, .03);
}
&.critical {
border: 1pt solid gtkalpha($error, .4);
.header {
.app-name {
color: gtkalpha($error, .8);
}
.app-icon {
color: gtkalpha($error, .6);
}
}
}
.header {
padding: .5rem;
color: gtkalpha($fg-color, 0.5);
.app-icon {
margin: 0 .4rem;
}
.app-name {
margin-right: .3rem;
font-weight: bold;
&:first-child {
margin-left: .4rem;
}
}
.time {
margin: 0 .4rem;
}
button {
padding: .2rem;
min-width: 0;
min-height: 0;
}
}
separator {
margin: 0 .4rem;
background-color: gtkalpha($fg-color, .1);
}
.content {
margin: 1rem;
margin-top: .5rem;
.summary {
font-size: 1.2em;
color: $fg-color;
}
.body {
color: gtkalpha($fg-color, 0.8);
}
.image {
border: 1px solid gtkalpha($fg-color, .02);
margin-right: .5rem;
border-radius: 9px;
min-width: 100px;
min-height: 100px;
background-size: cover;
background-position: center;
}
}
.actions {
margin: 1rem;
margin-top: 0;
button {
margin: 0 .3rem;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
declare const SRC: string
declare const DATADIR: string
declare module "inline:*" {
const content: string
export default content
}
declare module "*.scss" {
const content: string
export default content
}
declare module "*.blp" {
const content: string
export default content
}
declare module "*.css" {
const content: string
export default content
}
File renamed without changes.
+28
View File
@@ -0,0 +1,28 @@
project('bar-launcher-tools', version: '1.0')
pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name()
main = meson.project_name() + '.built'
custom_target(
command: [
find_program('ags'),
'bundle',
'--root', meson.project_source_root(),
meson.project_source_root() / 'app.ts',
main,
],
output: main,
input: files('app.ts'),
install: true,
install_dir: pkgdatadir,
)
configure_file(
input: files('wrapper.sh'),
output: meson.project_name(),
configuration: {
'MAIN_PROGRAM': pkgdatadir / main,
'LAYER_SHELL_LIBDIR': dependency('gtk4-layer-shell-0').get_variable('libdir'),
},
install: true,
install_dir: get_option('prefix') / get_option('bindir'),
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"name": "astal-shell",
"dependencies": {
"astal": "/usr/share/astal/gjs"
}
}
+24
View File
@@ -0,0 +1,24 @@
/* @use './components/notifications/notifications.scss'; */
@use "./components/bar/bar.scss";
@use "./components/QuickActions/quickactions.scss";
@use "./util/colours.scss" as *;
/* @use "./components/launcher/launcher.scss"; */
* {
font-size: 1rem;
}
empty {
min-width: 0;
background-color: transparent;
}
.title {
font-size: 1.5rem;
font-weight: bold;
}
.title-2 {
font-size: 1.2rem;
font-weight: bold;
}
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"experimentalDecorators": true,
"strict": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
// "checkJs": true,
// "allowJs": true,
"jsx": "react-jsx",
"jsxImportSource": "astal/gtk4",
}
}
Loaded 100 of 355 files, more files were not shown because too many files have changed in this diff. Show more