1577 Commits

Author SHA1 Message Date
kenji 59d1b4a337 removed transform hypr 2025-12-23 06:31:25 -06:00
kenji 151034f4fc updated position of monitor 2025-12-22 19:06:33 -06:00
kenji eb226350fc fixed again 2025-12-22 18:57:32 -06:00
kenji ae081803a5 fixed incorrect formatting 2025-12-22 18:54:33 -06:00
kenji 8cba405b0e added new monitor 2025-12-22 18:51:23 -06:00
kenji c61fe63bae added bluetoothctl 2025-12-13 09:39:44 -06:00
kenji c8d0cec782 changed vrr to 0 2025-12-12 19:42:40 -06:00
kenji a02fd32ee2 changed monitor pos 2025-12-12 19:36:25 -06:00
kenji 6be0a483e8 added general 2025-12-12 14:16:13 -06:00
kenji 3c32424793 fixed the directory 2025-12-12 11:34:02 -06:00
kenji 5535d43146 test 2025-12-12 11:29:10 -06:00
kenji 4f5b41204c hypr: makes exectuable actually executable 2025-12-12 11:25:16 -06:00
kenji 6335cdce16 font: added fonts for foreign languages 2025-12-12 09:28:21 -06:00
kenji ce86247d3e mv switchwall 2025-11-24 17:30:22 -06:00
kenji 580865cb4c a 2025-10-14 18:25:29 -05:00
kenji 8b4cf2c91d replaced bind for grim 2025-09-20 15:11:10 -05:00
kenji ee37fc5f6d screenshot priority changes 2025-08-31 20:59:54 -05:00
kenji 7a5635507e changed DP 2025-08-24 11:59:08 -05:00
kenji 609720f540 a 2025-08-20 15:25:57 -05:00
kenji bec280bcbc updated pkgs hyprland 2025-08-20 15:19:31 -05:00
kenji 1dc5e26712 removed Super, T 2025-08-20 14:30:16 -05:00
kenji c0ad3a4acb added custom conf hypr 2025-08-20 14:28:38 -05:00
kenji 33dc50df7a add force 2025-08-20 14:14:23 -05:00
kenji 604108a093 a 2025-08-20 14:11:29 -05:00
kenji b5960f4363 removed executable 2025-08-20 13:56:47 -05:00
kenji 1654ba6ee4 removed colours.nix 2025-08-20 13:56:16 -05:00
kenji b1484ba42e minor fix 2025-08-20 13:55:56 -05:00
kenji 76edab5b1c removed attr from hypr 2025-08-20 13:54:48 -05:00
kenji c5bb2bf66f fix misspelling 2025-08-20 13:52:39 -05:00
kenji 624606d853 mass migration 2025-08-20 13:52:02 -05:00
kenji be5772ea09 moved wayland again 2025-08-20 13:37:06 -05:00
kenji e6dd387e9f moved wayland 2025-08-20 13:33:11 -05:00
kenji 84ac1c05e2 a 2025-08-20 13:23:21 -05:00
kenji e1ac8eeed1 removed executable
incompatible with builtins.path
2025-08-19 18:40:57 -05:00
kenji 415806ca96 a 2025-08-19 18:39:32 -05:00
kenji 650a5fa73c fix for commit 3c 2025-08-19 18:35:27 -05:00
kenji 3c496175dc converted hyprland conf to nix 2025-08-19 18:17:33 -05:00
kenji b1daadc71e added files 2025-08-19 17:03:16 -05:00
kenji aae0668878 updated monitor 2025-08-19 14:52:03 -05:00
kenji 7495f5ebbc a 2025-08-19 10:05:29 -05:00
kenji d9a4fba05a fix solaar not hidden on launch 2025-08-19 10:01:51 -05:00
kenji 64cf3883aa added lact 2025-08-19 09:55:32 -05:00
kenji 51d1d30a61 fixed solaar not launching 2025-08-18 23:15:09 -05:00
kenji c7e43f10e3 changed vrr to 2 2025-08-18 23:12:07 -05:00
kenji 93bcfd494c removed vrr 2025-08-18 23:10:24 -05:00
kenji b76a1e45ef downgraded resolution 2025-08-18 23:08:14 -05:00
kenji 527a8f2b75 reverted back 2025-08-18 23:03:56 -05:00
kenji 1d57900977 changed bitdepth 2025-08-18 23:00:47 -05:00
kenji 6fd73b8691 added solaar 2025-08-18 18:01:03 -05:00
kenji 40dbb31fbc added movetowindows binds 2025-08-17 09:45:56 -05:00
kenji d729f27755 fixed starship 2025-08-13 09:03:21 -05:00
kenji a270c99361 fix path 2025-08-13 08:59:27 -05:00
kenji dc379f2ee6 organized 2025-08-13 08:58:40 -05:00
kenji 992e54592b a 2025-08-13 08:53:14 -05:00
kenji c502d301b8 a 2025-08-13 08:51:56 -05:00
kenji 58b144792e a 2025-08-13 08:45:56 -05:00
kenji c7d6a04c77 a 2025-08-13 08:44:07 -05:00
kenji a29382966b a 2025-08-13 08:43:02 -05:00
kenji 9d2d722bc6 organized 2025-08-13 08:39:37 -05:00
kenji 490fb9ba35 fixed incorrect binds 2025-08-13 08:26:10 -05:00
kenji 5e7bef47a0 removed starship.enable on desktop.nix 2025-08-13 08:20:56 -05:00
kenji 891cad49f0 added simpleStarship 2025-08-13 08:20:36 -05:00
kenji d6c4327639 added starship enable 2025-08-13 08:14:04 -05:00
kenji fed7deef79 integrated new fonts 2025-08-12 08:18:32 -05:00
kenji d4b1e53471 updated nixos-fonts 2025-08-12 08:16:26 -05:00
kenji 40f9981167 added material symbol 2025-08-11 22:57:23 -05:00
kenji 1902d47c1e added 2025-08-11 22:23:51 -05:00
kenji ae331f28cf added pillow and materialyoucolor 2025-08-11 22:01:25 -05:00
kenji 41f93617ad a 2025-08-11 21:54:43 -05:00
kenji ed8b13037d a 2025-08-11 21:53:46 -05:00
kenji 7d0d6fbd9f a 2025-08-11 21:52:47 -05:00
kenji dcb26342b3 a 2025-08-11 21:52:22 -05:00
kenji 09a70bf980 a 2025-08-11 21:51:37 -05:00
kenji ccf5c9a252 a 2025-08-11 21:50:27 -05:00
kenji 06e277c316 a 2025-08-11 21:49:35 -05:00
kenji dc1c238819 a 2025-08-11 21:43:39 -05:00
kenji 8c5f3537f9 a 2025-08-11 21:41:48 -05:00
kenji 2348290bfb a 2025-08-11 21:39:15 -05:00
kenji 7b047e0719 UNFIX GET PREV 2025-08-11 21:36:53 -05:00
kenji bce41fec66 a 2025-08-11 21:28:49 -05:00
kenji a0d060a8c3 a 2025-08-11 21:19:12 -05:00
kenji c83efbc591 qa 2025-08-11 21:18:45 -05:00
kenji 230917fbb2 a 2025-08-11 21:15:31 -05:00
kenji 0b3db4e61a a 2025-08-11 21:11:51 -05:00
kenji 1a1a2b309f ad 2025-08-11 21:07:24 -05:00
kenji f9129a5fb7 test 2025-08-11 21:04:45 -05:00
kenji 166d9f3f4b added spacegrotesk 2025-08-11 21:03:40 -05:00
kenji cdf0188ec8 test 2025-08-11 20:57:31 -05:00
kenji 99638ade76 a 2025-08-11 20:54:54 -05:00
kenji 33953a915b fixed pkgs 2025-08-11 20:53:30 -05:00
kenji 3ad6d635d6 test 2025-08-11 20:41:47 -05:00
kenji f0ec76847f a 2025-08-11 20:37:50 -05:00
kenji a6c6f7958f fixed nix develop 2025-08-11 20:35:41 -05:00
kenji 4dc566a037 organized 2025-08-11 20:20:02 -05:00
kenji 55d2a9fe6c added vrr on DP-2 2025-08-11 19:39:58 -05:00
kenji db7d0a00f0 changed keybindings
onscreen keyboard and bartoggle
2025-08-11 19:29:48 -05:00
kenji b9a7145c5c flake update 2025-08-11 17:22:12 -05:00
kenji f94de758d2 fix monitor hyprland 2025-08-11 16:40:37 -05:00
kenji a34c685b73 fixed 2025-08-11 16:36:05 -05:00
kenji 16c5b8f999 added custom binds and monitor 2025-08-11 16:27:21 -05:00
kenji 181014b678 removed programs.hyprland 2025-08-11 16:11:47 -05:00
kenji 083be17af4 a 2025-08-11 16:09:38 -05:00
kenji f0d7d6f074 added 2025-08-11 16:08:37 -05:00
kenji d561e4f1ec added home-manager 2025-08-11 16:05:30 -05:00
kenji 686de93cee converted to homeManagerModules 2025-08-11 15:58:04 -05:00
kenji 1493021354 changed path again 2025-08-11 15:39:31 -05:00
kenji 48d7be6682 fixed path 2025-08-11 15:35:14 -05:00
kenji a9c40d493b changed environment.etc 2025-08-11 15:32:25 -05:00
kenji 3d36ead298 attemp 2 2025-08-11 15:28:41 -05:00
kenji 9b1de7c7ce replaced as a test 2025-08-11 15:22:09 -05:00
kenji ce6e885a23 organized 2025-08-11 15:19:40 -05:00
kenji 176ad4ba0b now works! 2025-08-11 14:52:24 -05:00
lsoriano-mcm 9a1ad0057e add 2025-08-09 13:26:24 -05:00
lsoriano-mcm 2327596517 added more environment etc 2025-08-09 12:24:30 -05:00
lsoriano-mcm 71b67cc772 environment etc 2025-08-09 12:18:39 -05:00
lsoriano-mcm 962934e82d added pkgs in cfg 2025-08-09 11:50:56 -05:00
lsoriano-mcm 1528a9f760 added oneUI 2025-08-09 08:37:05 -05:00
lsoriano-mcm 28be4d1bb4 added flakes for nix 2025-08-08 16:43:46 -05:00
lsoriano-mcm ad7ee4ad27 added flake.nix 2025-08-08 11:19:41 -05:00
end-4 db66b85e61 bar: move number showing logic from GlobalStates to Workspaces 2025-08-08 20:24:37 +07:00
end-4 66c810ead2 bar autohide: rename enabled -> enable for consistency 2025-08-08 20:12:50 +07:00
end-4 9824bb9c63 bar: add delay for autohide 2025-08-08 20:06:53 +07:00
end-4 f806e2c22c bar: add auto hide 2025-08-08 19:54:10 +07:00
end-4 3d408b18f7 background: add fade when switching 2025-08-08 18:31:52 +07:00
end-4 8aa776ae62 make bg image loading async 2025-08-08 18:02:10 +07:00
end-4 a15f3b8c65 overview: show windows on other monitors too 2025-08-08 17:55:52 +07:00
end-4 4df22c96d0 screen corners: fix visibility for multimonitor with varying fullscreen state 2025-08-08 17:52:19 +07:00
end-4 772df06fa5 booru: fix inconsistent download 2025-08-08 17:50:20 +07:00
end-4 d3a9d2ea5b Fix hiding background when fullscreen (#1775) 2025-08-08 10:35:55 +07:00
end-4 4914d9b638 Merge branch 'main' into main 2025-08-08 10:35:43 +07:00
end-4 1f8a7be34e quickshell: fix qml null safety and monitor property errors (#1770) 2025-08-08 00:01:31 +07:00
end-4 97bdfa54c0 Overrideable default terminal app (#1753) 2025-08-07 23:57:08 +07:00
end-4 64bb730dd1 touchpad: improve scroll speed handling for touchpad (#1781) 2025-08-07 23:46:39 +07:00
end-4 7013b459a3 adjust scrolling speed 2025-08-07 23:13:07 +07:00
end-4 a31733e2db move scrolling animation to styled components 2025-08-07 22:39:30 +07:00
end-4 199b23d14a add config options for scroll factors and threshold 2025-08-07 22:32:02 +07:00
end-4 f1c1ed833c use StyledListView for SelectionDialog 2025-08-07 22:31:19 +07:00
Souyama 0506917b87 launch_first_available.sh should skip empty cmds 2025-08-07 20:48:11 +05:30
end-4 4f40ba8e6e more intuitive power profiles icons 2025-08-07 22:01:05 +07:00
end-4 733a792610 ai: add usage metadata for openai and mistral 2025-08-07 21:53:37 +07:00
end-4 f581fd4821 config option to (not) filter duplicate media controls 2025-08-07 21:39:48 +07:00
Runze 86ddb61a3f fix(touchpad): differentiate scroll speed between touchpad and mouse wheel 2025-08-07 22:26:26 +08:00
lunstia 6c3451b912 Fix background not always hiding in fullscreen and other monitors hiding background when they're not supposed to 2025-08-07 00:16:26 -04:00
lunstia 35e1dc95a5 Fix background hiding in fullscreen 2025-08-06 05:27:29 -04:00
finjener d70f81bfe4 Merge remote-tracking branch 'upstream/main' into quickshell-fixes 2025-08-05 18:17:02 +01:00
finjener d632111cf9 quickshell: fix qml null safety and monitor property errors 2025-08-04 23:03:00 +01:00
end-4 f8d162d995 RoundCorner: rewrite to use Shape instead of Canvas 2025-08-03 20:40:52 +07:00
end-4 0708070764 circular progress: use implicitSize instead of size
note: the credit is removed because the widget has been rewritten to use Shape instead of Canvas
2025-08-03 19:54:01 +07:00
end-4 87f7bc28a3 chores: remove unnecessary import, suppress init null warnings 2025-08-03 18:17:01 +07:00
end-4 3eb7d8ab58 background: remove unecessary Scope 2025-08-03 18:13:12 +07:00
end-4 71d0ac4c5e make circular progresses use shape instead of canvas 2025-08-03 18:12:44 +07:00
end-4 839593b11e add konsole konfig 2025-08-03 17:31:58 +07:00
end-4 13a0927900 bar: refractor bar content to new file 2025-08-03 16:52:39 +07:00
end-4 00984c599b settings: update keep right sidebar loaded note 2025-08-03 16:51:08 +07:00
end-4 34ca65a180 background: fix wrong anchor 2025-08-03 16:11:24 +07:00
end-4 596ae72942 add config option to keep right sidebar loaded 2025-08-02 20:31:37 +07:00
end-4 88cc91b85a fix laggy search bar anim when overview is disabled 2025-08-02 20:09:18 +07:00
end-4 d4b8ded6c8 overview: allow disabling overview (showing search only) 2025-08-02 17:35:44 +07:00
end-4 86d2a03a0a settings: add monochromize/tint icons toggles 2025-08-02 16:56:19 +07:00
end-4 de1812bf91 sidebar: remove redundant coloroverlay, make uptime more brief 2025-08-02 16:55:56 +07:00
end-4 f36751ff6b sidebar: boorus: always download images manually 2025-08-02 16:01:46 +07:00
end-4 a9273fc225 ai: dont include tool instructions in system prompt 2025-08-02 16:00:24 +07:00
end-4 2a0b12112f i use nyarch btw 2025-08-02 15:45:24 +07:00
end-4 2aea02989f session: fix binding breakage on close (#1754) 2025-08-02 07:25:08 +07:00
Souyama 2b554cf286 Update env.conf
remote direct quotes
2025-08-02 00:28:47 +05:30
sansmoraxz dc2777703d update default terminal value 2025-08-01 22:47:01 +05:30
sansmoraxz 6ae03b545c terminal env var 2025-08-01 22:22:01 +05:30
end-4 8e366cfc84 translations: add ukrainian language file (#1748) 2025-08-01 22:56:27 +07:00
Beengoo 27c2c4fb92 Merge branch 'end-4:main' into main 2025-08-01 15:53:42 +03:00
Beengoo 83af589b27 Correcting localization errors 2025-08-01 15:44:47 +03:00
end-4 7a937833f3 background: parallax on whole workspace group 2025-08-01 08:16:19 +07:00
end-4 4110d2529c ai: dont replace . in ollama model name 2025-07-31 22:28:34 +07:00
Beengoo 1c6c165d78 Added Ukrainian Language 2025-07-31 17:17:48 +03:00
end-4 a5ffb0e021 media controls: actually detect if plasma browser integration is installed 2025-07-31 12:35:39 +07:00
end-4 a08a39b620 qs: handle toggles internally instead of relying on hyprctl dispatch global (#1745) 2025-07-31 12:35:16 +07:00
end-4 968e8195ef background: fix clock positioning 2025-07-30 12:33:55 +07:00
end-4 52ce2f5384 feat(background): show clock for video wallpapers. (#1719) 2025-07-30 07:30:15 +02:00
end-4 cb2d1bc444 Merge branch 'main' into videowall-add-clock 2025-07-30 07:30:05 +02:00
end-4 47b81faf3d Feature: Hyprlock layout indicator (#1718) 2025-07-30 07:15:33 +02:00
end-4 1483761e72 Feature: On-screen keyboard (osk) update on activeLayout event (#1717) 2025-07-30 07:11:05 +02:00
end-4 7f43665e3c Merge branch 'main' into osk-update-on-activelayout-event 2025-07-30 07:10:48 +02:00
end-4 01fcd653ad Fix: Init layout indicator with main keyboard and update on every activeLayout event (#1711) (#1716) 2025-07-30 06:57:40 +02:00
end-4 298e947740 background: hide when fullscreen 2025-07-30 09:46:55 +07:00
end-4 91c2014b7e ai: add mistral 2025-07-30 09:46:42 +07:00
end-4 3018ad16b1 translation: Update Russian translation file (again) (#1741) 2025-07-30 00:44:34 +02:00
Anton Epikhin 1172be241c Returned fade_on_empty for input field and moved layout indicator to bottom right 2025-07-29 22:02:25 +03:00
Vercixx c743b4ab88 Update ru_RU.json 2025-07-29 21:45:56 +03:00
end-4 f6ec718ced translation: Update Russian translation file (#1740) 2025-07-29 16:43:04 +02:00
Vercixx aa20027de4 translation: Update Russian translation file 2025-07-29 16:27:30 +03:00
end-4 e504cf11e1 starship: fix trailing newline (#1738) 2025-07-29 19:01:09 +07:00
end-4 a11e0a39d9 ai: adjust chat input indicator spacing 2025-07-29 16:42:18 +07:00
end-4 26531401b0 ai: allow custom models 2025-07-29 16:38:21 +07:00
end-4 0f4293e4cb background: clock "separate" from bg image 2025-07-28 22:49:01 +07:00
end-4 7172b134ea ai: more context in system prompt 2025-07-28 22:40:54 +07:00
end-4 4a9e342a1c ai: add suggestions for /tool 2025-07-28 18:11:23 +07:00
end-4 f98d869c21 why 2025-07-28 10:38:16 +02:00
end-4 1312310a6e translation: update vietnamese 2025-07-28 13:33:49 +07:00
end-4 ad9c81f405 translation: Add Russian translation (#1732) 2025-07-28 08:16:51 +02:00
end-4 496caa6fb1 fix weirdass scroll speed 2025-07-28 11:58:50 +07:00
end-4 2fd7d45b9c deps: add hyprsunset 2025-07-28 07:31:11 +07:00
Vercixx 0b087665a8 translation: Add Russian translation 2025-07-27 22:33:49 +03:00
end-4 39862fba2a make panel borders more subtle 2025-07-27 22:44:08 +07:00
end-4 3ac44d211f ai: separate model and tool selection 2025-07-27 22:33:25 +07:00
end-4 d3392000af translations: add Italian language file (#1723) 2025-07-27 15:48:44 +02:00
Salvo Giangreco 564d2e109f translations: add Italian language file
Signed-off-by: Salvo Giangreco <giangrecosalvo9@gmail.com>
2025-07-27 14:52:59 +02:00
end-4 fe07298adb hyprlanddata: use stdiocollector instead of jq hack with splitparser 2025-07-27 08:51:43 +07:00
end-4 cc176a999d Fix empty notifications (#1728) 2025-07-27 03:12:19 +02:00
Javier Rolando 47c5a41aa6 fix empty notifications 2025-07-26 20:49:04 -03:00
end-4 2ad6f2c9fc Make Performance profile setting translatable (#1725) 2025-07-27 00:54:10 +02:00
Vercixx 8905bc1c27 Make Performance toggle translatable 2025-07-26 16:58:43 +03:00
end-4 064d5174c2 ai: add command execution requests 2025-07-26 14:20:55 +07:00
end-4 c69c8f6ef5 osd: make spinning brightness icon not wiggle 2025-07-26 14:12:43 +07:00
end-4 7fb81049f3 welcome app: fix material theme 2025-07-26 09:09:52 +07:00
end-4 5099ce15db session: detect running downloads 2025-07-25 23:09:17 +07:00
lyingfish ed500395d3 feat(background): show clock for video wallpapers.\ 2025-07-25 21:38:47 +08:00
Anton Epikhin a1e88fc3c2 Added hyprlock layout indicator 2025-07-25 16:35:42 +03:00
end-4 c8b007631d ai: refractor api formats 2025-07-25 20:14:37 +07:00
Anton Epikhin 6bc1f8a39f OSK update on activeLayout event 2025-07-25 14:26:49 +03:00
Anton Epikhin fe84f6cab1 init indicator with main keyboard and update layout on every activelayout event 2025-07-25 11:33:16 +03:00
end-4 27eea1c7a6 feat: power-profile switcher in topbar (#1653) 2025-07-25 09:11:46 +02:00
end-4 32f94704c7 bar: power profiles: change icon for "balanced" 2025-07-25 14:10:55 +07:00
end-4 05fdbf3d24 rename showPerfProfileToggle -> showPerformanceProfileToggle 2025-07-25 13:58:40 +07:00
end-4 f28c791cf2 hyprlock: remove misleading comments in default config 2025-07-25 10:40:53 +07:00
end-4 a4b474ff39 wallpaper: more flexible parallax 2025-07-25 10:39:58 +07:00
end-4 38c76fe86b Fix: Always scroll clipboard history to top when content changes (#1690) (#1713) 2025-07-25 04:31:46 +02:00
end-4 d09259c79a search: fix clipboard gets scrolled to bottom 2025-07-25 09:30:59 +07:00
Celestial.y a683fa2414 feat: add option to ignore conflicting files (#1613) 2025-07-25 08:58:39 +08:00
MrRogueKnight e744816928 Update SearchWidget.qml 2025-07-25 01:53:31 +05:30
end-4 15703bce04 session: detect more package managers 2025-07-24 21:40:35 +07:00
end-4 f4f5540d08 qs: use new qs import for search algorithms 2025-07-24 20:45:57 +07:00
end-4 b1b37685c1 session: warn when package manager is running 2025-07-24 20:41:44 +07:00
end-4 0ff4cc572c sidebar: ai: clearer statusbar tooltips 2025-07-24 19:37:27 +07:00
end-4 081b9c17d5 tooltip colors follow m3 docs again 2025-07-24 19:36:50 +07:00
end-4 eb6b21e7e6 ai: add api key indicator 2025-07-24 19:28:45 +07:00
end-4 baa17c304b ai: show search queries, temperature, and token count 2025-07-24 18:05:21 +07:00
end-4 7b8b388667 ai: make temperature actually work 2025-07-24 16:24:27 +07:00
end-4 118529d8d3 ai: gemini 2.5: update model codes, add flash lite 2025-07-24 16:19:26 +07:00
end-4 b67c4553f6 bar: layout indicator: make not freaking tiny 2025-07-23 22:25:18 +07:00
end-4 47980da78e Layout indicator for Hyprland kb_layout option (#1471) 2025-07-23 17:22:04 +02:00
end-4 3d57d444df Merge branch 'main' into layout_service 2025-07-23 17:20:38 +02:00
end-4 5870632c19 Merge remote-tracking branch 'upstream/main' into layout_service 2025-07-23 22:18:22 +07:00
end-4 ffeb27f04e bar: layout indicator: smaller 2025-07-23 22:12:54 +07:00
end-4 012df9dcd7 hyprlandxkb: dont update when not necessary 2025-07-23 22:11:40 +07:00
end-4 82fd2334cf bar: layout indicator: more proper layout parsing 2025-07-23 22:07:34 +07:00
end-4 7bafa57989 radiobutton: fix inaccurate height 2025-07-23 10:26:16 +07:00
end-4 5b4ccd9d59 previous commit but i didn't know there are 2 spots to fix 2025-07-23 09:34:23 +07:00
end-4 be2b86909a switchwall: fix mpv options being overriden by load-scripts only (#1696) 2025-07-23 09:26:52 +07:00
end-4 82506ae7cd groupbutton: press and hold for alt action 2025-07-23 09:00:54 +07:00
end-4 574a2a11e7 night light: use hyprsunset <- gammastep 2025-07-23 09:00:31 +07:00
end-4 3d5ed9401c bar: don't animate circprogs (#1570) 2025-07-23 08:57:33 +07:00
end-4 3b5a674409 fix(ai): add the full received message to rawContent (OpenAi format) (#1695) 2025-07-22 15:22:42 +02:00
Jonas Bloch f9856bdabd fix(ai): add the full received message to rawContent
The messages were not preserved and passed to further calls outside of the reasoning part.
2025-07-22 09:50:51 +02:00
end-4 b6f75acf53 quickshell: configPath -> shellPath 2025-07-22 09:17:17 +07:00
end-4 c0f7504b36 bar: tray: not make icons fully monochrome 2025-07-22 09:17:03 +07:00
end-4 02f1c461c5 readme: add keybind cheatsheet image 2025-07-21 16:51:07 +02:00
end-4 c6fa348986 Update CONTRIBUTING.md 2025-07-21 16:27:33 +02:00
end-4 8ff226fbad overview: add ipc handlers for clipboard history and emoji picker 2025-07-21 20:44:37 +07:00
end-4 349700d7fb ai: gemini: fix random json spilling 2025-07-21 17:56:31 +07:00
end-4 0d9d11b2e0 lock: temporarily go back to hyprlock
issues with quickshell lock:
- crashes when (un)plugging monitors
- kb focus is loss after suspend
- previously active app is not re-focused after unlocking
2025-07-21 14:46:55 +07:00
end-4 734cf56631 appsearch: icon guess: both lower n uppercase domain name app name (#1674) 2025-07-21 11:56:04 +07:00
end-4 d4e782aa6c appsearch: icon guess: lowercase (#1674) 2025-07-21 11:51:37 +07:00
end-4 c517264baf lock: hyprlock fallback 2025-07-21 11:33:23 +07:00
end-4 d5809621d9 hyprland: better scrolling 2025-07-21 11:32:59 +07:00
end-4 6ec24e9932 Fix monitor offset w/ left-of-center monitors in overview (#1656) 2025-07-21 06:25:01 +02:00
end-4 a7805af421 lock: add password box content timeout 2025-07-21 10:45:33 +07:00
end-4 c5f8377a85 fix quickshell bg showing over mpvpaper (fixes #1684) 2025-07-21 10:15:29 +07:00
end-4 7ba14a4cc8 hyprland: allow lock xray 2025-07-21 03:29:04 +02:00
end-4 d38a168e52 Do not timeout notification if expiration is 0 (#1683) 2025-07-20 18:56:40 +02:00
end-4 f4dcf1fb4b lock: fix text alignment 2025-07-20 22:57:31 +07:00
end-4 93cf930126 fix(wallpaper/konachan): make random konachan respect locale by utilizing xdg user dirs (if any) (#1662) 2025-07-20 17:43:21 +02:00
Alvin 3bd542aa65 do not timeout notification if expiration is 0 2025-07-20 11:24:13 -04:00
alyingfish 5e78b09577 feat: add option to ignore certain apps in dock (#1681)
* feat: add option to ignore certain apps in dock

Co-authored-by: end-4 <97237370+end-4@users.noreply.github.com>
2025-07-20 17:17:23 +02:00
end-4 42b483b878 hyprland: add rules for lock screen 2025-07-20 21:58:02 +07:00
end-4 b2b24206a1 hyprland: remove steam float rule, fix tearing rule 2025-07-20 21:57:49 +07:00
end-4 ecf824f85f remove redundant PersistentStates 2025-07-20 21:56:37 +07:00
end-4 329b6e6ebd Right sidebar: restore last viewed bottom widget (#1622) 2025-07-20 16:53:58 +02:00
end-4 f6e89ed60c right sidebar: bottomwidgetgroup tabs: set currentIndex directly 2025-07-20 21:52:32 +07:00
end-4 b6618d2193 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-07-20 21:43:27 +07:00
ふらまりん 09bcd9ae4b Merge branch 'main' into fix/pictures-dir-respect-locale 2025-07-20 22:36:34 +08:00
end-4 73fa273e29 fix random konachan script not updating wallpaper (#1661) 2025-07-20 16:28:03 +02:00
end-4 b93b329da9 Properly fix open with menu in KDE (#1673) 2025-07-20 16:25:14 +02:00
end-4 db38e8496f lock: make it cover also fullscreen apps 2025-07-20 20:52:40 +07:00
end-4 c1c291a9e3 use quickshell for lock 2025-07-20 08:58:40 +07:00
end-4 a000abc908 switchwall: less duplicate config file declaration 2025-07-20 08:39:12 +07:00
Dignity 084508db84 use different filename if already used by config 2025-07-19 19:24:49 +02:00
Dignity 6a4b8cc778 Merge branch 'end-4:main' into main 2025-07-19 19:18:54 +02:00
end-4 cbd1b48b93 fix record script 2025-07-19 23:09:46 +07:00
Moeta Yuko d4bda66c65 Properly fix open with menu in KDE
* Use XDG menu scheme from KDE, which is already implicitly introduced
  by illogical-impulse-kde.
* Set XDG_MENU_PREFIX globally, so kbuildsycoca6 is automatically
  invoked by /usr/bin/xdg-mime via the pacman hook. Consequently, the
  manual call can (and should) be removed.
2025-07-19 16:47:59 +08:00
end-4 ed06192d6a background: don't flash the default wallpaper on startup (#1670) 2025-07-19 14:56:28 +07:00
end-4 4b4b2c9efa ai: add 🐧😭💢 2025-07-19 12:01:10 +07:00
end-4 bdc1e56df4 icons: make (normal) substitutions work with uppercase classes 2025-07-19 11:27:52 +07:00
end-4 528a7261d3 notifications: make stupid warning shorter 2025-07-19 10:36:17 +07:00
end-4 8603918771 icons: add guess by icon search 2025-07-19 10:35:52 +07:00
ふらまりん 47007288e8 fix(translation): fix comma 2025-07-19 09:37:39 +08:00
ふらまりん 68fa1715d0 fix(translation): reflect dir change in zh_cn 2025-07-19 09:34:45 +08:00
ふらまりん 0ee77b9ebc Merge branch 'main' into fix/pictures-dir-respect-locale 2025-07-19 09:28:26 +08:00
end-4 19ea9ea3d1 kill all apps before shutting down/rebooting/etc 2025-07-18 23:42:10 +07:00
end-4 055da37987 screenshot: fix window regions 2025-07-18 23:13:00 +07:00
end-4 65e3b6c84c fix typo 2025-07-18 23:03:58 +07:00
end-4 3aec3da80d Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-07-18 22:54:28 +07:00
end-4 7b3884ab1a konachan.net instead of konachan.com to prevent captchas (#1664) 2025-07-18 22:54:23 +07:00
ふらまりん 5382b4bfb1 fix(wallpaper/konachan): fix mkdir 2025-07-18 21:06:45 +08:00
ふらまりん ae30ab03d5 fix(wallpaper/konachan): utilize XDG_PICTURES_DIR in random konachan 2025-07-18 21:02:21 +08:00
end-4 2426682cc2 readme: just link to wiki 2025-07-18 12:47:46 +02:00
end-4 86e041945a activewindow: fix null warnings 2025-07-18 17:40:18 +07:00
end-4 ac0aaf7ba9 overview: make drag hotspot be at the mouse 2025-07-18 16:39:44 +07:00
end-4 bab166baea notifications: destroy wrapper object when quickshell's notif is destroyed (#1465) 2025-07-18 16:09:35 +07:00
end-4 40f2e0b6a7 notif item: fix binding loop for notifs with images 2025-07-18 15:29:08 +07:00
end-4 0806c97fe5 right sidebar: change toggles order 2025-07-18 12:36:49 +07:00
end-4 2cc313855b launcher: clipboard: delete button 2025-07-18 12:36:25 +07:00
end-4 3311d68262 hyprland: fix env syntax error 2025-07-18 09:19:30 +07:00
end-4 1e4493474f wayland electron envvar, librewolf browser fallback (#1660) 2025-07-18 04:14:27 +02:00
end-4 9abdfd8da0 Update env.conf 2025-07-18 04:13:17 +02:00
end-4 8452b9a234 Update CONTRIBUTING.md 2025-07-18 03:44:55 +02:00
Dignity ccf512b8ad fix random konachan script not updating wallpaper 2025-07-17 20:44:56 +02:00
Nouvborne c1832de01f Update env.conf 2025-07-17 18:13:04 +00:00
Nouvborne e6c44737ac Update keybinds.conf 2025-07-17 18:11:07 +00:00
end-4 f42e6773aa settings: add wallpaper parallax 2025-07-17 23:44:13 +07:00
end-4 fdc05ee4a0 right sidebar: volume mixer: use listview, add separator 2025-07-17 23:43:20 +07:00
end-4 12e107448b right sidebar: add easyeffects toggle 2025-07-17 23:42:59 +07:00
end-4 c461bba5a3 Fix the theme color of the settings page; Update translations (#1659) 2025-07-17 17:16:22 +02:00
end-4 e38906714e Merge branch 'main' into addon-i18n 2025-07-17 17:15:52 +02:00
end-4 cfeae73c91 notifications: fix eliding when collapsed 2025-07-17 22:08:31 +07:00
end-4 5865a62a94 gitignore: add .qmlls.ini 2025-07-17 22:07:03 +07:00
月月 c64e079da4 translations: update translations/zh_CN.json 2025-07-17 22:10:37 +08:00
月月 7f3fb26651 Translation: Update weather information card title to support multiple languages, and clean up unnecessary translation entries 2025-07-17 22:01:41 +08:00
月月 fca5adbbd3 Recover the import "root:/services/" in the wrongly deleted setting.qml 2025-07-17 21:49:14 +08:00
end-4 254870c9ab quickshell: use qs imports instead of root:/ 2025-07-17 20:24:01 +07:00
end-4 fb6721e348 sidebar: ai chat: think block: fix undefined easing type 2025-07-17 20:23:43 +07:00
end-4 6d9e4ad2fa more quickshell.execdetached instead of hyprland dispatch trick 2025-07-17 20:23:03 +07:00
end-4 6f5781564c hyprlock: bolder clock 2025-07-17 19:56:39 +07:00
end-4 20711e125b dock: right click to pin app 2025-07-17 15:45:43 +07:00
end-4 d197787ab8 fix tabbar warning 2025-07-17 15:45:35 +07:00
end-4 158428e63e not attempt to show translated globalshortcut description cuz they can't be changed 2025-07-17 15:28:31 +07:00
end-4 8a4f2f2851 Add custom multilingual implementation (#1633) 2025-07-17 10:19:50 +02:00
月月 9978c87b67 translations: update translations/zh_CN.json 2025-07-17 16:03:27 +08:00
end-4 1e7c84a6c1 translations: update qsTrs to Translation.tr and allow translation of notifications 2025-07-17 13:57:55 +07:00
月月 9ca67f0095 translations: Update Chinese language files 2025-07-17 14:25:57 +08:00
end-4 1429b5977b translations: adjust vietnamese 2025-07-17 13:13:38 +07:00
end-4 4660a154d0 translations: add untranslated text, fix some originals 2025-07-17 13:13:38 +07:00
月月 fec4990506 translations: Update chat save message in Chinese translations 2025-07-17 08:45:25 +08:00
月月 380d126b59 Merge branch 'addon-i18n' of https://github.com/xiaomeng2004/dots-hyprland into addon-i18n 2025-07-17 08:42:54 +08:00
月月 6dff90f7e2 Merge remote-tracking branch 'origin/main' into addon-i18n 2025-07-17 08:41:34 +08:00
end-4 9056b551ab add vietnamese translation 2025-07-17 01:35:49 +07:00
end-4 035e8b3e5d ai: fix weird save chat description 2025-07-17 00:22:15 +07:00
1cebp 3b0574bd46 Fix monitor offset w/ left-of-center monitors in overview 2025-07-16 13:16:01 -04:00
月月 2b2733679c i18n: Update translation for online model disallowance message to remove model name reference 2025-07-16 23:46:02 +08:00
end-4 864c83172b readme: make the ii logo on the left and note on the right 2025-07-16 17:41:16 +02:00
月月 8a68cf207a i18n: Refactor translation management tools and add ignore feature for dynamic resources
- Updated README.md to reflect changes in translation management tools and added ignore mark feature for dynamic resources.
- Created Chinese translation guide for the translation management tools.
- Created English translation guide for the translation management tools.
- Enhanced translation-cleaner.py to skip keys marked with /*keep*/ during cleanup.
- Improved translation-manager.py to create backups only when updating keys interactively.
- Updated zh_CN.json with new translations and added ignore marks for dynamic values.
2025-07-16 23:36:47 +08:00
end-4 db82175b61 Update CONTRIBUTING.md 2025-07-16 15:54:23 +02:00
月月 2ad60a40a8 i18n: Refactor string formatting to use arg() method for translations 2025-07-16 21:11:27 +08:00
月月 b98e843a9d i18n: removed duplicate translation services and fixed imports 2025-07-16 16:48:21 +08:00
Ninjdai 365a649776 Update UtilButtons.qml 2025-07-16 10:31:49 +02:00
Ninjdai 0ecf72b4c3 Merge branch 'end-4:main' into power-profile-toggle 2025-07-16 10:28:48 +02:00
end-4 93ef3c3bda dock: fix screen 2025-07-16 10:54:39 +07:00
end-4 19aa66568f background: add parallax for sidebars 2025-07-16 10:38:21 +07:00
end-4 d176d38552 dock: fix hover region and weird popup cutoff 2025-07-16 10:23:21 +07:00
end-4 623cd842a6 search: prevent shaky while typing, prevent clipboard scrolling to bottom 2025-07-16 10:00:48 +07:00
end-4 77f21d8e89 overview: don't fill the screen 2025-07-16 09:52:46 +07:00
end-4 eedd5d8f27 hyprland keybinds: remove trailing comma (#1559) 2025-07-16 09:39:53 +07:00
end-4 36390e107b dock: dont anchor left right 2025-07-16 09:35:42 +07:00
end-4 7821cfe131 dock: dont aggressively unload 2025-07-16 09:34:36 +07:00
end-4 a08e9e0e79 screen corners: not have a fullscreen layer 2025-07-16 09:34:22 +07:00
end-4 06ed6dd1e7 use space grotesk for clocks 2025-07-16 09:04:20 +07:00
Ninjdai 90013c7451 feat: power-profile switcher in topbar 2025-07-15 22:44:24 +02:00
end-4 bb221326eb background parallax: make half groups really half group 2025-07-16 00:17:18 +07:00
end-4 08e3a9bddb background: parallax wallpaper 2025-07-16 00:15:31 +07:00
end-4 abc9b4cdb7 systeminfo: fix distro id 2025-07-15 23:20:08 +07:00
end-4 c46a4cb3ef nuke anyrun 2025-07-15 22:45:34 +07:00
end-4 4d10ee6036 media controls: adjust color overlay transparency 2025-07-15 20:44:17 +07:00
end-4 cb20a2bc76 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-07-15 20:37:47 +07:00
end-4 77c5923782 cheatsheet: fix weird popup anim 2025-07-15 20:37:36 +07:00
end-4 ffe1783aa3 background: clock: fly in from random direction 2025-07-15 20:37:25 +07:00
end-4 bb27413e97 hyprland: add window rules for xdg-portal file dialogs (#1648) 2025-07-15 14:12:17 +02:00
nahilrasheed 86d3f75d12 hyprland: add window rules for xdg-portal file dialogs
Add windowrulev2 entries for file picker dialogs with titles like "wants
to save" and "wants to open" to float and center them.
2025-07-15 14:34:44 +03:00
end-4 3110df8974 background: make it fill properly 2025-07-15 09:27:05 +07:00
end-4 78c91500d6 wallpaper: fix stretch 2025-07-14 22:55:15 +07:00
end-4 631303bffe use quickshell for wallpaper 2025-07-14 20:43:52 +07:00
end-4 efa60d09fc Update CONTRIBUTING.md 2025-07-14 09:31:22 +02:00
月月 2eff8d6db4 i18n: Fix translatable string extraction tool bug. 2025-07-14 15:08:40 +08:00
Pim Rijkers 7fcea64020 Merge remote-tracking branch 'upstream/main' into right-sidebar--restore-last-viewed-bottom-widget 2025-07-13 22:15:29 +02:00
月月 8f3b2474d2 i18n: Add multilingual support for the Settings interface and update the Simplified Chinese translation file. 2025-07-14 00:33:03 +08:00
月月 0c2b807447 i18n: update translations/tools/README.md 2025-07-13 15:24:29 +08:00
月月 e0b7a7f4b1 Merge remote-tracking branch 'origin/main' into addon-i18n 2025-07-13 13:22:49 +08:00
月月 bdb7657e22 i18n:Update the translation tool guide 2025-07-13 13:19:53 +08:00
月月 99af57fbc6 Updated Chinese translation 2025-07-13 13:14:02 +08:00
end-4 66d1d3e9c3 volume mixer: adjust layout 2025-07-13 01:54:30 +07:00
end-4 a5831cf365 slider: more accurate sizes 2025-07-13 01:54:13 +07:00
end-4 f726c9495e media controls: fix black color on first open 2025-07-13 01:53:40 +07:00
end-4 e98f84d9bc bar: fix media title spilling for non-verbose 2025-07-13 00:20:58 +07:00
end-4 59afc892f4 Revert "bar: use lazyloader"
too lazy so revert, bar needed in most cases anyway
2025-07-12 22:42:25 +07:00
end-4 1641425fff reapply bar corner transparency fix 2025-07-12 22:37:38 +07:00
月月 fb0d3f7f40 i18n:Replace qstr with Translation.tr and update the translation file 2025-07-12 22:46:24 +08:00
end-4 271732ed0b bar: weather: fix popup position for bottom bar 2025-07-12 20:36:34 +07:00
end-4 e59dd2cab2 add option to disable theming (#1586) 2025-07-12 20:13:26 +07:00
月月 af5d25b575 Merge remote-tracking branch 'origin/main' into addon-i18n 2025-07-12 21:03:24 +08:00
end-4 f6bb5385cf search: use qs' execdetached instead of executor proc 2025-07-12 19:25:08 +07:00
end-4 c0a3a0d7d2 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-07-12 19:15:59 +07:00
end-4 ae58e59d67 search: konachan wallpaper command (#1624) 2025-07-12 19:15:41 +07:00
end-4 2c435da5d7 add windowrule floating for Zotero (#1623) 2025-07-12 14:15:04 +02:00
end-4 1e1aeb2673 notif popup: fix right spacing 2025-07-12 19:05:48 +07:00
end-4 c5982a3ee1 bar: use lazyloader 2025-07-12 19:05:37 +07:00
Habibul Fauzan e42b0d20f8 Update rules.conf add windowrule for Zotero
Update rules.conf add windowrule for Zotero
2025-07-12 09:24:47 +07:00
end-4 f52cfc3df0 bar: add keybind to toggle (super+j) 2025-07-12 07:58:34 +07:00
end-4 3aff266699 bar: fix corner transparency (#1618) 2025-07-11 18:30:13 +02:00
end-4 f3144a9c8f Add Cursor to Code Editor keybind (#1619) 2025-07-11 18:20:07 +02:00
1cebp d482796dc3 Add Cursor to Code Editor keybind 2025-07-11 11:17:27 -04:00
Sighthesia f2831c2b88 Hug bar round corner transparency 2025-07-11 21:44:11 +08:00
end-4 b958c0ad6c ai: save last chat, make sure model's messages are saved properly 2025-07-11 19:37:23 +07:00
end-4 b1ee817c78 Update ServicesConfig.qml 2025-07-11 19:37:04 +07:00
end-4 76422ff42b fix hardcoded "/home/end" (#1616) 2025-07-11 14:13:06 +02:00
陈华 4d3788cca6 fix kde-material-you-colors: wallpaper path 2025-07-11 15:01:20 +03:00
陈华 528f5e755b fix hardcoded paths: /home/end -> ~ 2025-07-11 14:57:40 +03:00
end-4 65179b2358 settings: add 24h/12h time format config 2025-07-11 17:52:42 +07:00
end-4 bb5eabb75e hyprland: fix plasma system monitor keybind 2025-07-11 17:36:37 +07:00
end-4 b8f04bef41 Update README.md 2025-07-11 12:35:49 +02:00
end-4 eec1251db6 Update README.md 2025-07-11 10:06:07 +02:00
end-4 ace114be0b Update README.md 2025-07-11 09:13:39 +02:00
end-4 75d344c9df readme: update badge colors 2025-07-11 08:59:05 +02:00
end-4 d05f1ce67f readme: add discord 2025-07-11 08:45:11 +02:00
obsidrielle 715aa8d845 feat: add option to ignore conflicting files 2025-07-11 13:37:25 +08:00
end-4 a10f08a775 unfuck terminal color generation (#1612) 2025-07-11 09:32:40 +07:00
end-4 35b687b4ea fix dirs in scripts 2025-07-11 09:29:58 +07:00
end-4 f0a2438dea fish: add alias for quickshell config 2025-07-11 09:26:18 +07:00
end-4 770c4de78b fix more properly 2025-07-11 09:19:05 +07:00
end-4 c2f12eedfc fix qs exec (fixes #1610) 2025-07-11 09:18:21 +07:00
end-4 c69eaf7777 move quickshell config to subfolder to ease distribution
https://quickshell.outfoxxed.me/docs/guide/distribution/
2025-07-10 22:03:01 +07:00
end-4 5ee46cfc30 overview: make icon sizes more acceptable (#1479) 2025-07-10 20:08:47 +07:00
end-4 33f2f960b9 quickshell: temporarily disable hyprland layer masking (#1479) 2025-07-10 16:26:17 +07:00
end-4 d2eefd5768 hyprland: no annoying overlapping dolphin copy dialog 2025-07-10 15:15:32 +07:00
end-4 280f7ff8c0 bar: activewindow: more accurate on multimonitor systems 2025-07-10 10:55:08 +07:00
end-4 7afea39f1d cheatsheet: periodic table: adjust colors 2025-07-10 10:54:46 +07:00
end-4 f865ed877d sidebar: warp: no weird connection loop 2025-07-09 22:33:11 +07:00
end-4 465660db07 overview: remove debug print 2025-07-09 22:32:26 +07:00
end-4 9293ffdb26 bar: clock: use shorter day of week (by default) 2025-07-09 22:32:18 +07:00
end-4 d572f4a113 switchwall: unfuck wallpaper query on multimonitor (#1581) 2025-07-09 14:36:07 +07:00
end-4 a8f52a5adf search: adjust highlight color 2025-07-09 14:35:27 +07:00
end-4 b6f0d00137 overview: unfuck window moving on offset monitors 2025-07-09 11:41:16 +07:00
end-4 8ba91edeae make screencorners exit more nicely 2025-07-09 10:41:49 +07:00
end-4 5081b2e9d1 unfuck startup wallpaper for multimonitor 2025-07-09 10:16:03 +07:00
end-4 9a90d26e81 right sidebar: cloudflare warp 2025-07-09 10:13:37 +07:00
end-4 a0301bbc13 settings: add search prefixes 2025-07-09 09:46:10 +07:00
end-4 8ca2ed6781 periodic table 2025-07-09 09:41:13 +07:00
end-4 b0750506cf give CustomIcon colorization 2025-07-09 09:40:59 +07:00
Pim Rijkers 44443c4a37 moved storage from config to persistent state 2025-07-08 14:16:40 +02:00
end-4 2d6a897a66 settings: more options 2025-07-07 18:17:57 +02:00
end-4 32380d29c8 settings: add osd timeout 2025-07-07 17:57:05 +02:00
end-4 d801f132d1 bar: add outline to floating mode 2025-07-07 17:56:54 +02:00
end-4 45369c1ec4 appearance: not make bg color weird 2025-07-07 17:43:44 +02:00
end-4 52810b97f4 settings: apply colors on material palette selection 2025-07-07 17:43:19 +02:00
end-4 813395c987 move weathericons.qml 2025-07-07 17:30:44 +02:00
end-4 04e0266a4e sidebar: ai: add suggestions for save/load 2025-07-07 17:19:50 +02:00
end-4 ae69a4f11a ai: load/save 2025-07-07 16:48:43 +02:00
Pim Rijkers 25eb5fb449 add automatic restore 2025-07-07 14:13:35 +02:00
end-4 9d6452aaaf screenshooter: remove weird gap on non-window region labels 2025-07-06 09:11:37 +02:00
end-4 43b9378144 config: option to show/hide screenshot content regions (#1539) 2025-07-06 09:06:14 +02:00
end-4 7bcb01964f settings: move weather 2025-07-06 09:05:23 +02:00
end-4 e06ab94fb6 readme: remove acceidentally pasted image 2025-07-05 21:04:18 +02:00
end-4 e393e4df1a readme: add showcase video for ii-qs 2025-07-05 21:03:54 +02:00
end-4 b679a1339a feat(modules/bar): add weather bar (#1520) 2025-07-05 11:50:52 +02:00
end-4 e843164f60 bar: weather: fix alignment 2025-07-05 11:50:26 +02:00
end-4 8dccac4cda bar: weather: disable by default 2025-07-05 11:45:27 +02:00
end-4 41a8bfa097 bar: weather popup: use loader properly 2025-07-05 11:44:28 +02:00
end-4 ba8c737543 weather bar: remove unnecessary nested item 2025-07-05 11:38:07 +02:00
end-4 9043440c8d fix spacing 2025-07-05 11:37:51 +02:00
end-4 f6f7ac19fd fix bar font size 2025-07-05 11:32:59 +02:00
end-4 bcb60d8ab7 Update README.md 2025-07-05 10:30:18 +02:00
end-4 73d13254de Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-07-05 09:55:15 +02:00
end-4 d5f7a72d19 bar: workspaces: adjust monochromization 2025-07-05 09:55:12 +02:00
end-4 d4f193ae69 dock: monochrome icons 2025-07-05 09:55:01 +02:00
end-4 265b410a87 Update switchwall.sh 2025-07-05 09:07:23 +02:00
end-4 d9b9b8c9b2 media controls: mix a bit of accent color 2025-07-05 09:06:59 +02:00
end-4 9b050e8e21 add border to panels 2025-07-05 09:06:22 +02:00
end-4 b0cd46f783 screenshot: add app icons 2025-07-05 09:05:43 +02:00
end-4 3c7d55c793 extra bg tint 2025-07-05 09:05:08 +02:00
end-4 4e71d002ea smooth zooming 2025-07-05 09:04:52 +02:00
end-4 cd92d14ad2 readme: update screenshot 2025-07-05 08:43:30 +02:00
end-4 db64d4a12b include possible zed aliases in keybinds.conf (#1575) 2025-07-05 07:25:13 +02:00
ふらまりん b289ceccad include possible zed aliases in keybinds.conf 2025-07-05 11:23:26 +08:00
end-4 f973052d16 Update README.md 2025-07-05 01:29:11 +02:00
end-4 16e07a8213 Update README.md 2025-07-05 00:25:10 +02:00
end-4 5064db2aad readme: add illogical impulse logo 2025-07-05 00:23:27 +02:00
end-4 cf20581603 Update README.md 2025-07-05 00:18:38 +02:00
end-4 e78b84cd41 bar: monochrome icons 2025-07-04 20:02:44 +02:00
end-4 7d2856e9b7 deps: add darkly qt style 2025-07-04 17:54:15 +02:00
end-4 1fb0e8b32b add breeze 2025-07-04 17:51:12 +02:00
end-4 83a7581a34 icon guess: fix steam regex 2025-07-04 17:20:33 +02:00
end-4 8cd0433ab6 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-07-04 17:04:49 +02:00
end-4 2f03ce5f06 symlink quickshell to illogical impulse icon
remove this if quickshell gets its icon bundled later
2025-07-04 17:03:27 +02:00
end-4 3a9189a994 readme: remove "pls logo ideas" 2025-07-04 17:00:43 +02:00
end-4 c7519f338e add logo (closes #1436) 2025-07-04 16:58:44 +02:00
end-4 11dcffb95d overview: only show windows in same monitor as widget 2025-07-03 23:04:41 +02:00
end-4 746c5805fb Update Config.qml 2025-07-03 23:04:00 +02:00
end-4 85a9a08b43 settings: add dock options 2025-07-03 22:56:45 +02:00
end-4 089468393e notifs: discard notif on action invocation 2025-07-03 22:56:23 +02:00
end-4 e99f51e6ac media: fix space at start of track title 2025-07-03 22:56:05 +02:00
Hasan A. Tekeoğlu 8b83b42eed chore(Weather): use generic city name for default value 2025-07-03 10:00:08 +03:00
月月 66579f659f Merge remote-tracking branch 'origin/main' into addon-i18n 2025-07-02 22:21:58 +08:00
Hasan A. Tekeoğlu f51c0c7c9a Merge branch 'weather-bar-feat' of https://github.com/tekeoglan/dots-hyprland into weather-bar-feat 2025-07-02 15:53:12 +03:00
Hasan A. Tekeoğlu 9ecbe09de5 Merge branch 'end-4:main' into weather-bar-feat 2025-07-02 15:52:49 +03:00
Hasan A. Tekeoğlu 2684bbae69 refactor(WeatherBar): put in barRightSide 2025-07-02 15:52:24 +03:00
end-4 59ef5ac390 dock: add back enable config option now that it works properly 2025-07-02 13:44:05 +02:00
end-4 c00d2a5b50 ai: add prompt configuration 2025-07-02 12:49:39 +02:00
end-4 e23d8abef3 hyprland: noblur only for xwayland context menus (closes #1509) 2025-07-01 22:43:33 +02:00
end-4 fc3c711a7e installation: make symlink command not fail the script (#1486) 2025-07-01 19:48:45 +02:00
end-4 d358b35876 unfuck light/dark switching (#1556) 2025-07-01 18:00:20 +02:00
Hasan A. Tekeoğlu fd6af822cd chore(Weather): make things more clear 2025-07-01 13:42:54 +03:00
Hasan A. Tekeoğlu 50cc371100 chor(Weather): rename show to enable 2025-07-01 13:19:24 +03:00
Hasan A. Tekeoğlu c94231365f chore(WeatherService): rename to Weather 2025-07-01 12:57:14 +03:00
Hasan A. Tekeoğlu b982c788fe style(WeatherPopup): remove the border 2025-07-01 07:11:06 +03:00
Hasan A. Tekeoğlu 67c75b1cce fix(ServiceConfig): add weatherbar section 2025-07-01 05:42:50 +03:00
Hasan A. Tekeoğlu fd873ff7c1 fix(): WeatherBar pushing MiddleSection to the left 2025-07-01 05:29:10 +03:00
Hasan A. Tekeoğlu c17db4bfb1 Resolve conflicting merge issues 2025-07-01 05:20:41 +03:00
Hasan A. Tekeoğlu a18791ef16 Merge branch 'weather-bar-feat' of https://github.com/tekeoglan/dots-hyprland into weather-bar-feat 2025-07-01 04:49:29 +03:00
end-4 9892e51e1d sidebar: description box: dont show arrows when 1 item 2025-06-30 21:43:29 +02:00
end-4 bf936cd0d7 sidebar: make ai and booru suggestion description clearer 2025-06-30 21:32:06 +02:00
end-4 5ffcf98487 persistent states: also use jsonadapter 2025-06-30 17:23:46 +02:00
end-4 8e7a376407 hyprland: make borders more readable 2025-06-30 16:56:59 +02:00
end-4 6e9bb4945c screenshot: do offset adjustments directly on each monitor's region data (#1539) 2025-06-30 15:37:01 +02:00
end-4 45846bf696 screenshot: fix offset region target on click (#1539) 2025-06-30 15:28:55 +02:00
end-4 7ca0f263ba configoptions: use quickshell jsonadapter 2025-06-30 14:27:26 +02:00
end-4 22319ffccf hyprland: update window rules 2025-06-30 13:53:10 +02:00
end-4 68673de641 screenshot: fix window/layer positions for offset monitors (#1539) 2025-06-30 13:51:50 +02:00
end-4 50eae43ca2 screenshot: fix content regions on scaled monitors (#1539) 2025-06-30 13:12:52 +02:00
end-4 5d6db58d76 bar: shadow for floating style 2025-06-29 23:49:45 +02:00
end-4 d513d0e1b3 welcome: add bar style 2025-06-29 23:49:35 +02:00
end-4 32dd5eecb7 Create .qmlformat.ini 2025-06-29 23:40:38 +02:00
end-4 ecb2f246b6 settings: add config option for floating bar 2025-06-29 23:10:13 +02:00
end-4 18ea20f1df bar: fix bottom mode, add corner style config 2025-06-29 23:00:57 +02:00
end-4 30c54cb7ce screenshot: account for scaled monitors (#1539) 2025-06-29 21:05:31 +02:00
end-4 65f9b6a242 settings: prompt config add callater for setting 2025-06-29 21:05:31 +02:00
end-4 abdfd17482 Update color.txt path in switchwall.sh (#1530) 2025-06-29 15:16:45 +02:00
end-4 fdc0f16b08 volume mixer: manual node filtering to make it work with easyeffects 2025-06-29 08:51:07 +02:00
end-4 36db4ae327 screenshot tool: add layer regions 2025-06-29 00:23:19 +02:00
Hasan A. Tekeoğlu 689f6c90e7 feat(WeatherService): add geolocation service 2025-06-28 20:48:34 +03:00
Hasan A. Tekeoğlu d8f5471b3e config(WeatherService): add enableGps option 2025-06-28 20:45:49 +03:00
Hasan A. Tekeoğlu 9404b21f82 config(hyprland): fix invalid geoclue agent path 2025-06-28 16:58:54 +03:00
Hasan A. Tekeoğlu bb456687a6 feat(settings): add weather section 2025-06-28 08:35:22 +03:00
Hasan A. Tekeoğlu 660af6e018 fix(modules/bar): WeatherBar pushing middlesection to the left 2025-06-28 08:34:43 +03:00
Hasan A. Tekeoğlu d7cf0f4f27 fix(WeatherBar): not using config fonts 2025-06-28 08:32:39 +03:00
end-4 4b17b1aae7 screenshot keybind: add fallback 2025-06-27 22:21:02 +02:00
end-4 604abfea96 screenshot: use quickshell, ksnip -> swappy 2025-06-27 22:10:45 +02:00
end-4 ad73ca018a record script: fix fullscreen 2025-06-27 22:07:57 +02:00
end-4 bfc3918b2d update keybinds 2025-06-27 21:35:28 +02:00
end-4 d73e72097b remove unnecessary include 2025-06-27 16:26:19 +02:00
end-4 a202e16116 notifs: fix weird preview when body contains newlines 2025-06-27 16:25:57 +02:00
end-4 5588f8f1d5 material symbols: format 2025-06-27 16:25:42 +02:00
end-4 561a528bef update ai prompt 2025-06-27 16:25:26 +02:00
月月 7299642cf5 Merge remote-tracking branch 'origin/main' into addon-i18n 2025-06-27 18:40:06 +08:00
Hasan A. Tekeoğlu 3f44ecb068 feat(modules/bar): add weather bar 2025-06-27 12:30:25 +03:00
end-4 4f7ed4da53 search widget: fix wrong calculator result (#1424) 2025-06-26 22:37:03 +02:00
end-4 08b07ff729 Add dolphin as dependency (#1431) 2025-06-26 22:30:47 +02:00
end-4 f119a44442 ai: fix code blocks being cut off (#1513) 2025-06-26 22:19:20 +02:00
end-4 b27dc3496c progressbar: unecessary rect -> item 2025-06-26 22:18:16 +02:00
end-4 4a2453410a deps: add qt avif image plugin
for yt thumbnails
2025-06-24 14:51:04 +02:00
torhaala 51885623c8 Update switchwall.sh 2025-06-23 18:41:29 +02:00
end-4 ad462649b0 add brave browser substitution (fixes #1439) 2025-06-23 10:17:38 +02:00
end-4 b6ba428404 settings: more options 2025-06-23 02:37:28 +02:00
end-4 06574c19ae settings: increase page content width 2025-06-23 02:37:15 +02:00
end-4 7ca2da3723 overview: remove show xwayland indicator option
we dont show an ugly icon now so it's not necessary
2025-06-23 02:36:52 +02:00
end-4 6ebccf097f settings: make sections clearer 2025-06-23 02:36:07 +02:00
end-4 7cf3c264ab fix spinbox looped value 2025-06-23 02:35:55 +02:00
end-4 ced0f6efb1 settings: more options 2025-06-23 01:35:08 +02:00
end-4 5991c8f994 add styled textinput 2025-06-23 01:34:44 +02:00
end-4 b5046fa6dc content subsection: add info tooltip 2025-06-23 01:34:32 +02:00
end-4 1cd5b0ba1d battery: add auto suspend 2025-06-23 01:34:10 +02:00
end-4 d03100d60a add spinbox 2025-06-23 01:33:31 +02:00
end-4 529b67d728 fix dark/light mode util button not in rowlayout 2025-06-22 23:24:17 +02:00
end-4 b05e682545 feat(modules/bar): add nightmode button (#1481) 2025-06-22 23:20:53 +02:00
end-4 e97e41c162 revert weird util button width 2025-06-22 23:20:29 +02:00
月月 a5fca59a69 Merge remote-tracking branch 'origin/main' into addon-i18n 2025-06-22 19:19:18 +08:00
Hasan A. Tekeoğlu 5d9620881b feat(modules/bar): change darkmode without changing the wallpaper 2025-06-21 21:25:29 +03:00
Hasan A. Tekeoğlu cdbb72bec4 Merge branch 'end-4:main' into darkmode-button-feat 2025-06-21 21:02:06 +03:00
end-4 7e46e40eeb settings: reorganize and add settings 2025-06-21 11:59:40 +02:00
end-4 5ffbd55f58 settings: bar and audio options 2025-06-21 03:24:03 +02:00
月月 cef02f367b Merge remote-tracking branch 'origin/main' into addon-i18n 2025-06-21 09:10:40 +08:00
end-4 ddcf13678d sidebar: make gamemode update properly 2025-06-21 03:00:01 +02:00
end-4 e4be9852a3 quickshell: switch from hyprland dispatch exec trick to quickshell execdetached 2025-06-21 02:51:21 +02:00
end-4 29c7031bb2 refractor selection groups and config option row 2025-06-21 01:31:34 +02:00
end-4 f66e26d4cc content page: add bottom padding 2025-06-21 01:05:04 +02:00
end-4 d5a9ae32f2 adjust config switch size 2025-06-21 01:04:07 +02:00
end-4 47c9d63210 add center title config option 2025-06-21 01:03:56 +02:00
Hasan A. Tekeoğlu e56fbf820a feat(modules/bar): add nightmode button 2025-06-20 22:04:29 +03:00
end-4 3d9a5a72b6 settings: add titlebar 2025-06-19 23:29:52 +02:00
end-4 2db4b33fe2 fix configswitch not working when clicking the actual switch 2025-06-19 23:29:25 +02:00
end-4 a940c1b4fe adjust transparency 2025-06-19 23:29:06 +02:00
end-4 d437e126ab enable wayland flag for chrome and code again 2025-06-19 21:49:11 +02:00
end-4 62e0b7f669 redo accidentally overwritten corner transparency 2025-06-19 21:19:30 +02:00
end-4 eaa56ff555 settings: add material palette and transparency 2025-06-19 19:32:31 +02:00
end-4 e8414c88ba settings: about page 2025-06-19 18:58:30 +02:00
end-4 0416ddbf3f swappy -> ksnip 2025-06-19 17:54:09 +02:00
end-4 4ebd486426 add back ai primary buffer query (closes #1462) 2025-06-19 17:31:36 +02:00
end-4 f844a54a87 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-06-19 17:21:42 +02:00
end-4 0caf72a78f make right sidebar open qs settings app 2025-06-19 17:21:28 +02:00
end-4 bd6e8894b6 notifications: drop actions on restart (they don't work properly anyway) 2025-06-19 17:21:05 +02:00
end-4 cbed76fea9 adjust transparency 2025-06-19 17:20:03 +02:00
end-4 fd1f9c39b0 update window rule for settings app 2025-06-19 17:19:43 +02:00
end-4 8bc05c1373 settings: use quickshell settings app 2025-06-19 17:19:29 +02:00
end-4 cad1cfb66a hyprland: adjust anims, disable swallow 2025-06-19 17:19:02 +02:00
end-4 f80feb6734 Bar.qml: Fix round decorators transparency and visibility consistency with bar (#1470) 2025-06-19 17:15:25 +02:00
スケベ ad7fdd1d3f layout indicator in top right 2025-06-19 18:14:05 +03:00
Sighthesia bf6e5fafea Bar.qml: Fix round decorators transparency and visibility consistency with bar 2025-06-19 20:35:10 +08:00
end-4 f5ea891dd7 settings: add fake screen rounding option 2025-06-19 08:19:33 +02:00
end-4 1814f7cbec update settings pages 2025-06-19 08:13:15 +02:00
end-4 6abefecedf refractor button with icon and lightdark pref button to new file 2025-06-19 08:13:08 +02:00
end-4 07b6e8efbd even better dupe media player filtering 2025-06-19 08:11:01 +02:00
end-4 e7897cef8b more media controls filter trick (#1443) 2025-06-19 08:08:34 +02:00
end-4 66bd690b66 sidebar: calendar: animated navrail selection 2025-06-19 00:37:53 +02:00
end-4 7fba4b72bf content page: add min width 2025-06-19 00:37:30 +02:00
end-4 aefeeb8f6f adjust anim curves 2025-06-19 00:36:46 +02:00
end-4 3f294416ea refractor navrail tab array 2025-06-19 00:36:37 +02:00
end-4 9f4c75a75c settings: pages 2025-06-18 23:56:47 +02:00
end-4 bd79bc232b fab: update color mappings 2025-06-18 23:56:22 +02:00
end-4 ab991789f0 welcome: refractor content page and sections 2025-06-18 23:56:10 +02:00
end-4 b4340091c4 appearance: add colonprimarycontainer, anim curve durations 2025-06-18 23:55:30 +02:00
end-4 811a1ce6cb material symbol: also bold when filled 2025-06-18 23:55:06 +02:00
end-4 ed5be0c2ac nuke old navigation rail button 2025-06-18 23:51:45 +02:00
end-4 02f1ed75f3 add comment 2025-06-18 23:51:35 +02:00
end-4 16ed1f107f navigationrailbutton: make tabbar-compatible 2025-06-18 17:49:28 +02:00
end-4 37282e4316 welcome: more padding, add tooltips 2025-06-18 17:30:38 +02:00
end-4 93baba1568 settings app: expandable navrail buttons 2025-06-18 17:30:18 +02:00
end-4 afbf6c8ccd xdg desktop portal: gtk -> kde 2025-06-18 17:28:56 +02:00
end-4 5d24cc45a3 config options: remove image viewer 2025-06-18 10:41:57 +02:00
end-4 08d6c07d19 update starship config 2025-06-18 09:26:57 +02:00
end-4 3123a78bfe settings app: base window with navrail 2025-06-18 01:45:05 +02:00
end-4 aafa661f7a revealer: fix abrupt hiding 2025-06-18 01:37:16 +02:00
end-4 5c045c0b42 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-06-18 01:35:58 +02:00
end-4 59a58d8f76 adjust rectangular shadow strength 2025-06-18 01:35:45 +02:00
end-4 644c081812 rename navrailbutton -> navigationrailbutton 2025-06-18 01:35:32 +02:00
end-4 a68fa96b52 DateTime based on system locale (#1444) 2025-06-17 23:50:56 +02:00
end-4 20097099e1 refractor selection button to new file 2025-06-17 23:20:29 +02:00
end-4 a6a2b51970 fix progress bar binding loop 2025-06-17 23:20:06 +02:00
スケベ f2c72cad4a localized datetime 2025-06-18 00:12:53 +03:00
end-4 d1822be2a9 center-right block click to open right sidebar (closes #1442) 2025-06-17 19:05:07 +02:00
Zhineng Cao b34a26724f Merge branch 'end-4:main' into new-i18n 2025-06-17 19:17:01 +08:00
end-4 dd2e2a9bca config option for csd of quickshell windows 2025-06-17 12:27:39 +02:00
end-4 3acbe92363 welcome: fill buttons 2025-06-17 12:24:06 +02:00
end-4 72f2aa5c2f welcome: weeb & ai policy 2025-06-17 12:21:22 +02:00
end-4 5d7905b566 welcome: choose wallpaper file button 2025-06-17 11:38:49 +02:00
end-4 6d822142ad Update README.md 2025-06-17 11:06:26 +02:00
end-4 745857e515 welcome: random wall 2025-06-17 10:49:11 +02:00
end-4 8419697542 welcome: more buttons 2025-06-17 10:06:23 +02:00
end-4 c725284acb notifications: discard dead notif on action 2025-06-17 09:51:06 +02:00
月月 ccabd24db2 Add imports to service modules to support session functionality 2025-06-17 15:47:57 +08:00
end-4 265d3fdbdc quickshell: first run: set wallpaper iff none set 2025-06-17 09:07:09 +02:00
月月 89e25e3504 Add translation management tool suite and update Chinese translations
- Introduced a comprehensive guide for the translation management tool suite, detailing components, usage, and best practices.
- Added `translation-manager.py`, `translation-cleaner.py`, and `manage-translations.sh` scripts for managing translations.
- Updated the Chinese translation file (`zh_CN.json`) to improve existing translations and remove unused keys.
- Enhanced documentation with examples and troubleshooting tips for better user experience.
2025-06-17 14:29:33 +08:00
end-4 45b31f1eae Add emoji dependency (#1435) 2025-06-17 08:26:16 +02:00
end-4 d6718e9f10 noto emoji -> twemoji 2025-06-17 08:26:02 +02:00
lautaro ezequiel deco ef2248b523 Add shell emojis dependency 2025-06-17 02:40:35 -03:00
月月 b32734b9f5 Quickshell qstr seems not to be working, trying to implement custom translation
Add Chinese (zh_CN) translations for Quickshell interface and settings
2025-06-17 12:54:22 +08:00
Jx 9e27b4f232 Add dolphin as dependency 2025-06-17 00:18:27 -03:00
end-4 54dfad1d5b add keybind to edit shell config 2025-06-17 01:48:44 +02:00
end-4 11607ac41e add pr template to prevent "hi i made many personal changes pls merge" 2025-06-17 01:17:54 +02:00
end-4 ed60ccdf5d big icon view for wallpaper picker 2025-06-17 00:45:14 +02:00
end-4 050ba1606e Add terminalApp global for kde Apps (#1408) 2025-06-17 00:42:24 +02:00
end-4 a1ea208113 fix: quickshell bar swap usage NaN (#1427) 2025-06-17 00:39:06 +02:00
end-4 83bc08e810 sidebar: ai chat: clearer "pls /key" for newcomers 2025-06-17 00:30:59 +02:00
end-4 36e87fe71f tray: accessibility: bigger size, config option for monochrome 2025-06-17 00:18:12 +02:00
end-4 2c90de7375 auto m3 palette: use scheme-content for very low saturation pics 2025-06-17 00:17:16 +02:00
end-4 00b1aa1222 config options: add fixed bg clock pos 2025-06-16 23:41:45 +02:00
end-4 5dfa1b3445 fix config arrays stored as objects 2025-06-16 23:25:59 +02:00
end-4 a40d0c96af video wallpapers: prevent mpv mpris (#1360) 2025-06-16 22:46:47 +02:00
end-4 96d17f8fcb material palette: apply to kde material you colors 2025-06-16 22:46:14 +02:00
end-4 1cebec39f7 allow customizing material color palette w/ auto mode 2025-06-16 22:27:50 +02:00
end-4 5c4c1f5362 adjust welcome colors 2025-06-16 22:26:27 +02:00
end-4 92051b88ec audio: make volume not stuck over max (#1411) 2025-06-16 21:21:33 +02:00
end-4 6cd80407c0 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-06-16 21:15:35 +02:00
end-4 c27238548d audio: more nan fix (#1411) 2025-06-16 21:15:34 +02:00
end-4 b4b2ca1790 Fixed Terminal Color generation bug for quickshell (#1428) 2025-06-16 19:09:51 +02:00
end-4 a5a9f907cd audio: protection: don't change volume to NaN (#1411) 2025-06-16 19:06:37 +02:00
hrsvrn 97255be8ed Fixed Terminal Color generation bug for quickshell 2025-06-16 20:20:07 +05:30
Souyama e631c2e3d3 fix swap usage nan 2025-06-16 16:59:15 +05:30
end-4 dbd36c2fb4 Update README.md 2025-06-15 23:31:58 +02:00
end-4 31ba269d24 hyprland: add float rule+keybind for welcome app 2025-06-15 16:24:15 +02:00
end-4 41c5c2a99b first run experience: welcome app 2025-06-15 16:22:47 +02:00
end-4 eb8fc41c01 dock: make loading async 2025-06-15 16:22:13 +02:00
end-4 e4683115bd update issue templates for quickshell version 2025-06-15 13:52:13 +02:00
end-4 503823aa2e config options: add weeb and ai policies 2025-06-15 10:09:56 +02:00
end-4 1f41129669 record script: adjust message case 2025-06-15 09:16:25 +02:00
end-4 6c4830c47a record.sh fixes (#1351) 2025-06-15 09:05:41 +02:00
end-4 cbe04512ee search: remove arch-specific command-not-found funcitonality (#1409) 2025-06-15 08:59:39 +02:00
end-4 7b12756b93 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-06-15 08:58:52 +02:00
Sneethe 1bbafaa97d Merge branch 'main' of https://github.com/end-4/dots-hyprland into record-script.sh 2025-06-15 15:38:24 +10:00
Celestial.y ca9d4f8353 Fix an error in env file (#1412) 2025-06-15 11:55:36 +08:00
Bishoy Ehab 75d70d5e87 Fix an error in env file 2025-06-15 05:37:44 +03:00
Celestial.y 22a796deae Not using recursive env var
P.S. I have had done it in ii-ags
2025-06-15 09:08:08 +08:00
end-4 e190c6c7db make background widget loader async 2025-06-15 01:27:40 +02:00
Jx f8d9d92e49 Add terminalApp global for kde Apps
This helps with "Open with neovim" in dolphin.

Although it doesn't apply the quickshell theme since it opens kitty and neovim without going through a shell.

I'm not sure what would be the proper solution
2025-06-14 17:59:54 -03:00
end-4 f33fb45b89 no sussy trust me bro 2025-06-14 15:59:01 +02:00
end-4 859c79010c readme: installation: point to wiki page of new version 2025-06-14 12:46:21 +02:00
end-4 bc993bb7e2 quickshell: add scale factor envvar 2025-06-14 11:11:36 +02:00
end-4 7ef91639e8 keybinds: make stuff more nicely packed on the cheatsheet 2025-06-14 11:08:28 +02:00
end-4 e5e4f40867 window rules: more precise catch for kde's bluetooth connect 2025-06-14 10:56:12 +02:00
end-4 67edf4e822 keybinds: apps: more flexibility 2025-06-14 10:55:53 +02:00
end-4 9a360a3009 overview: left/right to switch ws 2025-06-14 10:08:04 +02:00
end-4 f92718c391 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-06-14 10:01:42 +02:00
end-4 f17437b419 fix right sidebar esc to close 2025-06-14 10:01:40 +02:00
end-4 468709c645 readme: deprecation notice for ags version 2025-06-14 08:48:45 +02:00
end-4 389246b180 background clock: hide in default position 2025-06-14 08:16:59 +02:00
end-4 5cb087e650 installation: set qt app style 2025-06-14 00:26:02 +02:00
end-4 c802217513 Merge branch 'main' of https://github.com/end-4/dots-hyprland 2025-06-14 00:04:08 +02:00
end-4 ec14e36be3 installation: include dolphin app nag fix 2025-06-14 00:03:58 +02:00
end-4 590f25aeff readme: add thanks fox 2025-06-13 23:50:16 +02:00
end-4 fba79bdd21 readme: no more fuzzel in main stuff list 2025-06-13 23:44:48 +02:00
end-4 a45210db9e remove "pls test quickshell version" (#1401) 2025-06-13 23:44:08 +02:00
_xB 1af3f6f485 unneeded stuff 2025-06-14 00:27:06 +03:00
end-4 46692d4bbb Update README.md 2025-06-13 23:16:26 +02:00
end-4 32ae084d86 illogical-impulse: Quickshell rewrite (#1276) 2025-06-13 23:15:42 +02:00
end-4 5fb1efa8bb bar: fix bracket 2025-06-13 23:13:24 +02:00
end-4 924fc26eaa QS Rewrite: notification: Fix Chromium-based notifs (#1362) 2025-06-13 23:02:07 +02:00
end-4 c96f2f5eb4 notif filter: remove redundant "edge" 2025-06-13 23:00:59 +02:00
end-4 63c59af390 QS Rewrite: bar: Add an option for choosing screens (#1363) 2025-06-13 22:55:20 +02:00
end-4 0ad9858379 bar: screensList -> screenList 2025-06-13 22:54:24 +02:00
end-4 0b49b73152 config options: bar: rename screensList -> screenList 2025-06-13 22:54:00 +02:00
end-4 62909dedfa configoptions: bar: empty screen list by default 2025-06-13 22:52:18 +02:00
end-4 50e69c2fa5 bar: fix screen filtering 2025-06-13 22:51:50 +02:00
_xB 2e472450c5 notifications: support popular chromium-based browsers 2025-06-13 23:45:18 +03:00
_xB 975c469e1a notifications: re-add support for HTML tags 2025-06-13 23:29:17 +03:00
end-4 e72405c754 revealer: fix visibility 2025-06-13 22:28:08 +02:00
end-4 2b384330fc bar: increase ws number show delay 2025-06-13 22:27:58 +02:00
end-4 8a0204a279 bar: add the missing '&&' 2025-06-13 22:27:39 +02:00
end-4 b7bc1a1a12 Merge branch 'main' into ii-qs 2025-06-13 22:04:15 +02:00
end-4 4d6deffd88 install script: update ags version notice 2025-06-13 21:45:08 +02:00
end-4 efb2f3fee5 install script: notify about ags version 2025-06-13 21:36:05 +02:00
end-4 d70a117270 bar: make groups cleaner 2025-06-13 20:39:09 +02:00
_xB 1a0df48ac3 bar: Use ScriptModel to only update related screens 2025-06-13 21:38:33 +03:00
end-4 753df0b48c fish: make clear actually clear in kitty 2025-06-13 20:33:36 +02:00
end-4 be5b932beb bar: workspaces: show dots only by default 2025-06-13 18:23:02 +02:00
end-4 b1729eed27 fix weird material symbols 2025-06-13 18:22:47 +02:00
end-4 cb6021602e keybinds: fix ctrl+super opening overview 2025-06-13 18:20:19 +02:00
end-4 1cdb124fd4 keybinds: prevent multiple overlaying screen snips 2025-06-13 18:19:59 +02:00
end-4 3011f77e88 media controls: adjust colors 2025-06-13 17:37:07 +02:00
end-4 c882b162d2 fix active window title not showing 2025-06-13 17:36:48 +02:00
end-4 dc24541f61 add transparency option (#1398) 2025-06-13 17:04:53 +02:00
end-4 89ed9e2b52 progress bar: make sperm amplitude follow the m3 specs 2025-06-13 15:46:05 +02:00
end-4 925f82301a fix newline before prompt (proper solution for#1397) 2025-06-13 15:45:35 +02:00
_xB b759be8e7e Merge branch 'ii-qs' into ii-qs-patch-1 2025-06-12 19:37:50 +03:00
end-4 da8a8aec4e adjust default util button visibility 2025-06-12 15:35:31 +02:00
end-4 b70e176011 QS Rewrite: bar: add microphone control to util buttons (#1365) 2025-06-12 15:33:58 +02:00
end-4 4c97434cca util buttons: make buttons hide properly 2025-06-12 15:33:27 +02:00
end-4 4a9af7e7bd translator: add no skibidi flag at right place 2025-06-12 15:27:20 +02:00
end-4 30f3825f1d progressbar: sperm: fix left end cutoff 2025-06-12 15:24:54 +02:00
_xB 3767542bac bar: re-add color picker button config 2025-06-12 13:51:48 +03:00
end-4 22fb505af9 progress bar: change sperm morphing anim curve 2025-06-12 12:51:28 +02:00
_xB f76fe37269 bar: re-add color picker button 2025-06-12 13:51:03 +03:00
end-4 acc6ce644e media controls: material 3 sperm 2025-06-12 12:43:45 +02:00
end-4 8f094e9a90 translator: add useless flag arabian guy requested 2025-06-12 12:43:10 +02:00
end-4 a63dec24b8 fix typo (#1393 is closed?) 2025-06-12 12:00:17 +02:00
_xB c8be29e02c Merge branch 'ii-qs' into ii-qs-patch-1 2025-06-12 09:23:30 +03:00
_xB fc157dd709 bar: Filter Variants instead of hiding PanelWindow 2025-06-12 09:22:22 +03:00
_xB af409dc0c9 bar: wrap buttons in Loaders 2025-06-12 09:01:25 +03:00
end-4 13bad429bc refractor visualizer to its own file 2025-06-12 07:44:03 +02:00
end-4 89ff743c5d Appearance: more m3colors in colors 2025-06-12 07:43:52 +02:00
_xB c3fa00b5c6 bar: add visibility options to utilButtons 2025-06-12 05:45:22 +03:00
_xB 60c93fd2ee Add the utilButtons config options 2025-06-12 05:43:36 +03:00
end-4 b7b183d2ca quickshell: use more .colors (rather than .m3colors) 2025-06-12 00:12:24 +02:00
end-4 1721c2bbb2 material symbols: disable filll anim for now
performance/memory concerns
2025-06-11 23:16:38 +02:00
end-4 24374efdce bar: change music icon 2025-06-11 23:16:11 +02:00
end-4 5a867ea061 media controls: improve dupe entry filtering, correct cava config 2025-06-11 22:59:16 +02:00
end-4 c2c7078957 translator: remove debug print 2025-06-11 22:30:55 +02:00
end-4 a3818322a6 media controls: visualizer 2025-06-11 22:30:43 +02:00
end-4 4473129599 fix session screen button size 2025-06-11 11:52:24 +02:00
end-4 23f0e90a7b sidebar: translator: save selected language, cleaner selector 2025-06-11 11:36:56 +02:00
end-4 0fb28af3c7 sidebar: translator: change layout to reduce eye movement 2025-06-11 11:06:22 +02:00
end-4 65983ade46 sidebar: translator: language selector 2025-06-11 10:59:52 +02:00
end-4 244d3f6067 idle inhibitor button: now wrong binding 2025-06-11 09:01:24 +02:00
end-4 8956752e85 ripple button: default size based on contentitem 2025-06-11 09:00:28 +02:00
end-4 039cb57167 overview: partially fix window position for offset monitors (#1388) 2025-06-11 08:39:18 +02:00
end-4 ac6a64efab right sidebar: fix idle inhibitor exiting on close (#1381) 2025-06-11 08:25:05 +02:00
end-4 fa75cc91a0 Add systemsettings as dependency (KDE desktop config) (#1385) 2025-06-11 08:04:17 +02:00
Jx ee0df41dec Add systemsettings as dependency (KDE desktop config)
Otherwise the gear from the right pannel does nothing
2025-06-10 20:39:46 -03:00
end-4 0ad006eea1 config loader: write back on load to include new options in user config 2025-06-11 00:40:07 +02:00
end-4 fd5553a0ad format 2025-06-11 00:36:26 +02:00
end-4 74e049b7b4 Directories: create shell config dir 2025-06-11 00:36:18 +02:00
end-4 80f809b8b2 config options: make fakescreenrounding default to 2 2025-06-11 00:36:09 +02:00
end-4 bbee1e85ac deps: add bluedevil and plasma-nm 2025-06-11 00:33:53 +02:00
end-4 bd0438be83 bg clock: fix positioning on scaled screens 2025-06-10 13:35:45 +02:00
end-4 4f8574ef50 wallpaper region limit: use min size of all instead of max 2025-06-10 13:24:58 +02:00
end-4 de941d8b9e hyprland: fix float rules for plasma stuff 2025-06-10 11:24:52 +02:00
end-4 a1e052b070 Update rules.conf 2025-06-10 09:15:48 +02:00
end-4 f7f4affe51 dock: unload when not needed 2025-06-10 09:14:54 +02:00
end-4 cf652482c5 better control -> plasma wifi widget 2025-06-10 08:59:31 +02:00
end-4 bec43ff8ed dock: fix hover to reveal behavior (#1375) 2025-06-10 08:53:00 +02:00
end-4 49888f30f0 Update README.md 2025-06-09 23:35:17 +02:00
end-4 b3a3975461 dock: fix alignment 2025-06-09 23:29:52 +02:00
end-4 93a14293aa overview: clearer window borders 2025-06-09 23:08:51 +02:00
end-4 a6ecf107b3 quickshell: fix some warnings 2025-06-09 23:04:35 +02:00
end-4 56958236e2 nicer link colors 2025-06-09 23:04:04 +02:00
end-4 2c4537bd02 Update README.md 2025-06-09 22:36:17 +02:00
end-4 cfb8a851e6 readme: screenshots 2025-06-09 22:34:04 +02:00
end-4 6fffa86fc2 dock: middle click to open new window 2025-06-09 18:08:10 +02:00
end-4 0beccd69e8 overview: adjust hover/click colors 2025-06-09 17:47:57 +02:00
end-4 5c2b12bf96 dock: join pinned apps and open windows 2025-06-09 17:43:58 +02:00
end-4 4b4364d2a5 right sidebar: more buttons 2025-06-09 17:42:04 +02:00
end-4 3c82ce828b config options: correct settings app 2025-06-09 17:41:33 +02:00
end-4 2a8725ec8d deps: remove xdg-user-dirs (#1369) 2025-06-09 16:31:04 +02:00
end-4 936ca85bab overview: fix blurry icons 2025-06-09 13:11:39 +02:00
end-4 a81acb3dbe overview: cleaner, add back hover effect 2025-06-09 12:51:28 +02:00
end-4 db8d51b931 overview: window previews!!! 2025-06-09 12:25:41 +02:00
end-4 fa2723a54d dock: remove user config option because it doesn't work
(just use enableDock in shell.qml)
2025-06-09 12:23:58 +02:00
end-4 8e1f28b11a less weird tooltip color for dark mode 2025-06-09 09:25:22 +02:00
end-4 08e46c5af8 make 0-size warnings stfu 2025-06-09 09:20:51 +02:00
Celestial.y 2020e13a05 make update.sh script (#1336) 2025-06-09 11:17:44 +08:00
end-4 2663c46b7e terminal: nice dir listing 2025-06-08 21:50:24 +02:00
end-4 d4f223c894 starship: a refresh 2025-06-08 21:39:03 +02:00
end-4 d521b2aae4 ai: latex: properly catch bracket-wrapped expressions 2025-06-08 19:02:00 +02:00
end-4 dc8bfce62e ai: fix latex rendering 2025-06-08 18:36:40 +02:00
end-4 cfb4f1a5e1 gnome control center -> plasma systemsettings 2025-06-08 17:05:20 +02:00
end-4 f840af4652 fix funny notice uhm some guys in discord said its not correct n stuff 2025-06-08 11:44:16 +02:00
end-4 726558c0a0 background widgets 2025-06-08 11:30:12 +02:00
end-4 8dab758d78 dock: hover region adjustment 2025-06-08 11:29:33 +02:00
end-4 e8c294f434 remove redundant defaulted prop assignment 2025-06-08 11:29:16 +02:00
end-4 be9b7f3629 least_busy_region.py: take files with spaces properly 2025-06-08 11:28:54 +02:00
end-4 e18456d18e material symbol: use curverendering 2025-06-08 11:27:54 +02:00
end-4 31c379cdfd dont enable dock by default 2025-06-08 11:27:38 +02:00
end-4 f084cd7a23 material symbols: prevent memory spikes 2025-06-08 08:19:15 +02:00
Bishoy Ehab 506fb857aa Add --skip-notice argument and change the way to check the git repo 2025-06-08 07:58:04 +03:00
Bishoy Ehab d96abe7a4d Add warining for users [the script is not fully tested] 2025-06-08 07:47:14 +03:00
end-4 0b7f80ed05 script for least busy region of image 2025-06-08 02:10:01 +02:00
end-4 c9ffb84ffc bar: borderless: add separators, no longer default 2025-06-07 23:25:13 +02:00
end-4 bbd2bfda5d dock: adjust active color 2025-06-07 23:02:15 +02:00
end-4 1add44ba2f dock: show on empty workspace 2025-06-07 22:37:32 +02:00
end-4 a8474af78d ai: comment debug print 2025-06-07 22:37:03 +02:00
end-4 99ab1b179b dock: pin kitty by default 2025-06-07 22:24:21 +02:00
end-4 db1fc0f6be dock: fix bottom spacing 2025-06-07 22:23:03 +02:00
end-4 0d4877a77e dock: display num of open windows 2025-06-07 21:57:57 +02:00
end-4 f5168bfcb6 QS Rewrite: dock: Show pinned apps (#1364) 2025-06-07 21:46:51 +02:00
end-4 5364d0e4f7 dock: use quickshell-native way to open apps 2025-06-07 21:45:52 +02:00
end-4 1591b72e4a installation: remove ags 2025-06-07 21:00:51 +02:00
end-4 57327ab0fe earbang protection 2025-06-07 19:43:05 +02:00
Bishoy Ehab 181ac0561d Add some space 2025-06-07 20:12:50 +03:00
Bishoy Ehab d00a21ac56 Merge branch 'end-4:main' into main 2025-06-07 20:10:39 +03:00
end-4 c1a4670de2 yad -> kdialog (closes #1367) 2025-06-07 16:51:02 +02:00
end-4 0ea31737ea fixed a typo in env.conf (#1368) 2025-06-07 16:36:25 +02:00
end-4 2c548a4296 fcitx5 blur 2025-06-07 14:58:46 +02:00
end-4 aadb38370c adjust shadows 2025-06-07 14:58:41 +02:00
sam 341c6be9be fixed a typo lol 2025-06-07 20:24:14 +08:00
end-4 643b197d02 notifications: fix spacing/eliding 2025-06-07 14:10:31 +02:00
end-4 e03800cf64 screen corners: mask layer visibility to the corners only 2025-06-07 11:29:32 +02:00
end-4 810ea2ce3b circ prog: bigger gap angle 2025-06-07 11:14:28 +02:00
end-4 b6dcd19d04 bar: workspaces: use secondary container for occupied 2025-06-07 11:12:10 +02:00
end-4 2b451c012b overview: anims when opening/closing windows 2025-06-07 08:49:41 +02:00
end-4 194b470cf2 installation: skip .local/bin 2025-06-06 23:29:29 +02:00
end-4 b70dbf5f10 hypridle: longer timeouts 2025-06-06 22:56:05 +02:00
end-4 a2f2fb816b make ripples more subtle (circle -> radial gradient) 2025-06-06 21:25:56 +02:00
end-4 31e782a36f ai chat: prevent function call message from abrupt jumps 2025-06-06 20:50:53 +02:00
end-4 eeb41de46c fix listview items being weird when changing both y and height 2025-06-06 20:50:06 +02:00
end-4 9111ada32b add m3 expressive slow spatial curve 2025-06-06 20:39:40 +02:00
end-4 56b11e9ab7 comments 2025-06-06 20:39:29 +02:00
end-4 403614d337 add app name to code save notif 2025-06-06 20:39:15 +02:00
_xB aa951b0bc0 bar: add microphone control to util buttons 2025-06-06 01:12:11 +03:00
_xB ef1170c770 dock: Show pinned apps again 2025-06-06 01:09:38 +03:00
_xB 33feaff817 notification: Fix Chromium-based notifs 2025-06-06 01:04:12 +03:00
xB 2087ea72f8 QS Rewrite: bar: Add an option for choosing screens 2025-06-06 00:47:26 +03:00
end-4 66c95231a7 readme: more quickshill 2025-06-05 20:53:43 +02:00
Celestial.y 077f52f6b8 chore: add fish shell compatible setup link (#1357) 2025-06-06 01:27:25 +08:00
end-4 2cd548ae11 hyprland: remove weird keybind i forgor 2 unset 2025-06-05 14:18:32 +02:00
end-4 bef0739471 config option for time format 2025-06-05 13:27:38 +02:00
end-4 1e24608e2b ai: add temperature 2025-06-05 13:15:02 +02:00
Hari Chalise 56bb53ed95 sep for fish shell 2025-06-05 16:32:07 +05:45
end-4 938b4b2c69 hyprland: update keybinds 2025-06-05 01:16:17 +02:00
Hari Chalise 663c3483be chore: make setup url compatible with fish shell
as fish is a non-posix-compliant shell, it doesn't support bash syntax like bash <(), so i have changed the url to one that fish shell supports.
2025-06-05 00:25:28 +05:45
end-4 135a3c2426 hyprland: add ubuntu's terminal keybind 2025-06-04 08:32:53 +02:00
end-4 9ac800ce9b hyprland: keybinds: rearrange zoom keybinds 2025-06-03 23:52:32 +02:00
Sneethe 108e0a8c78 record-script.sh fixes
Even if ~/Videos exists, `xdg-user-dir VIDEOS` requires xdg-user-dirs-update to be run first.
Some people just mkdir their desired xdg-user-dir directories. And so they won't have $HOME/.config/user-dirs.dirs for `xdg-user-dir VIDEOS` to work.
Now instead if there is no user-dirs.dirs match for VIDEOS then $HOME/Videos/ is used instead.

You can now cancel the recording region, which exits the script. Previously if you pressed escape when choosing the recording region, record-script would record the fullscreen.
This resulted in me accidentally recording my entire screen for hours.
2025-06-04 07:30:10 +10:00
end-4 3404335b21 make hyprland background always dark 2025-06-03 14:16:05 +02:00
end-4 e6b6ed60a9 translator: larger trigger delay 2025-06-03 10:22:59 +02:00
end-4 51cb27f747 search: automatically trim file protocol in commands 2025-06-03 10:22:38 +02:00
end-4 8ab8c0046b format 2025-06-03 10:22:16 +02:00
end-4 f86b85b3de fix font inconsistencies 2025-06-03 09:53:52 +02:00
end-4 b5ac985b7d Change sound muting behavior (#1342) 2025-06-03 09:17:30 +02:00
end-4 ee56903530 startup wallpaper animation 2025-06-03 00:25:41 +02:00
end-4 81446091d3 sidebar: translation widget 2025-06-02 23:24:10 +02:00
end-4 c26685b1de update sidebar 2025-06-02 23:23:58 +02:00
end-4 0fba075f52 ai: fix more null error 2025-06-02 18:39:57 +02:00
end-4 2c113018d2 ai: gemini: fix message marked done too early 2025-06-02 18:34:44 +02:00
end-4 9ddf4d2b2f ai message: fix null errors 2025-06-02 18:33:35 +02:00
end-4 fdadca431f add back that pragma, still necessary, oops 2025-06-02 18:33:23 +02:00
end-4 098098ab2d grimblast -> hyprshot
grimblast does nasty shit and crashes quickshell
2025-06-02 18:15:56 +02:00
end-4 7c98b37e4f quickshell: remove no longer necessary pragma 2025-06-02 18:15:14 +02:00
end-4 18990c5de7 primary tab bar: adaptive layout 2025-06-02 17:51:22 +02:00
end-4 bf22194182 ai: gemini: configurator 2025-06-02 14:48:10 +02:00
end-4 35bd46faea fix sidebar radius 2025-06-02 14:47:18 +02:00
Bishoy Ehab 90ea701797 Replace /bin/bash with /usr/bin/env bash shebang 2025-06-02 15:23:39 +03:00
end-4 6aadba9a3a fuzzel: don't overwrite non-color config in color generation 2025-06-02 10:52:20 +02:00
end-4 d4ad68f8c6 Allow fuzzel.ini to be customized (#1344) 2025-06-02 10:45:34 +02:00
Sneethe 1af166ef7c Don't use cache for fuzzel.theme 2025-06-02 18:23:32 +10:00
end-4 209cfc131a nuke ags config 2025-06-02 08:31:37 +02:00
end-4 2b66c6fa89 Merge branch 'main' into ii-qs 2025-06-02 08:29:27 +02:00
Sneethe 7593938986 Allow fuzzel.ini to be customized
Instead of overwriting the entire fuzzel.ini on each theme change. Theme
changes are made to fuzzel.theme which is then imported by fuzzel.ini

Rationale:
I like to use vim binds for fuzzel and there wasn't a good way to modify
fuzzel.ini without making the end-4 update process complicated.
2025-06-02 13:41:40 +10:00
end-4 7d749f16c5 Update fuzzel-emoji.sh 2025-06-02 00:10:02 +02:00
end-4 c940b72776 quickshell emoji picker 2025-06-02 00:09:47 +02:00
end-4 751e5ca543 relocate scripts 2025-06-01 23:45:51 +02:00
Bishoy Ehab e2e6604d16 Merge branch 'end-4:main' into main 2025-06-01 23:39:32 +03:00
end-4 31d06277e1 hyprlock: add background blur 2025-06-01 22:32:24 +02:00
end-4 1380696977 kitty: add search 2025-06-01 21:33:20 +02:00
end-4 7e5610a9e1 fix: correct ags package name in uninstall.sh (#1343) 2025-06-01 21:12:58 +02:00
D7OM e5b920550b fix: correct ags package name in uninstall.sh 2025-06-01 22:07:28 +03:00
end-4 368c7c3cea Update README.md 2025-06-01 20:34:29 +02:00
end-4 ea8f06b632 Update README.md 2025-06-01 20:29:28 +02:00
end-4 e08230cf69 readme: more emphasis on new quickshell version and less on reporting issues with ags version 2025-06-01 20:28:50 +02:00
end-4 1099258d07 left sidebar: add detach/attach instructions + keybind 2025-06-01 18:14:02 +02:00
end-4 e024f896a6 left sidebar: fix width anim 2025-06-01 17:53:28 +02:00
end-4 aaf652b17c detachable sidebar 2025-06-01 17:47:32 +02:00
end-4 a6a3a144d2 remove debug print (AGAIN) 2025-06-01 17:47:00 +02:00
end-4 5e8f912aa9 update plasma browser integration size 2025-06-01 16:23:40 +02:00
end-4 2b0d4b2c32 installation: remove gradience 2025-06-01 16:21:25 +02:00
end-4 c9563ffb63 quickshell: task manager: gnome-usage -> plasma system monitor 2025-06-01 15:26:48 +02:00
end-4 7f13abf771 update snowman-style battery nag
https://discord.com/channels/961691461554950145/961693710968557598/1378504799141756991
2025-06-01 13:24:10 +02:00
end-4 3f274e65ac boorus: add t.alcy.cc 2025-06-01 12:31:07 +02:00
end-4 0d7769c884 make network indicator work with ethernet 2025-06-01 11:21:28 +02:00
end-4 57a2580a5d adjust notif status btn size 2025-06-01 10:16:14 +02:00
end-4 771ff14462 notifications: add silent button 2025-06-01 10:12:37 +02:00
end-4 834a684d6f remove persistent state for bottom sidebar group tabs (buggy) 2025-06-01 10:10:57 +02:00
end-4 b172020f5b workspace: adjust active indicator anim curve 2025-06-01 09:51:45 +02:00
end-4 5ad78a77a1 workspaces: adjust icon opacity 2025-06-01 09:19:55 +02:00
end-4 1dfde18b7d kitty: add cursor trail 2025-06-01 09:15:54 +02:00
end-4 1136804c32 remove redundant wallpaper command in matugen config 2025-06-01 09:15:17 +02:00
asalde_le1 623fd80a54 Add a keybind for the microphone toggle button 2025-06-01 06:51:57 +03:00
asalde_le1 277162f4d4 Enable sound unmuting and show the muted sound icon 2025-06-01 06:46:54 +03:00
end-4 3a6e607383 bar: workspaces: active ws trail 2025-06-01 00:35:50 +02:00
end-4 b759580466 overview: show ws number, animated active indicator 2025-05-31 23:52:48 +02:00
end-4 23e5df38a3 switch from foot to kitty terminal 2025-05-31 23:15:32 +02:00
end-4 2046b5c58a make light/dark switching window not show up directly 2025-05-31 22:39:52 +02:00
end-4 c334da9907 hyprland: make osk slide from bottom 2025-05-31 22:39:03 +02:00
end-4 18802fef96 hyprland: add osk keybind 2025-05-31 22:38:41 +02:00
end-4 31e139da29 hyprland: adjust anims 2025-05-31 22:38:32 +02:00
end-4 189a1aa0ac on screen kb: init 2025-05-31 22:34:29 +02:00
end-4 f98be82f96 kb key: more flexible size 2025-05-31 22:34:10 +02:00
end-4 be29519f58 group button & ripple button: fix non-left btn release, add more actions 2025-05-31 22:33:39 +02:00
end-4 3448adb776 booru: add app name to download notif 2025-05-31 22:33:01 +02:00
end-4 8b9dc8cde8 app icon guess: add regex for polkit popup 2025-05-31 22:32:40 +02:00
end-4 e85a899be0 battery: fix charging warning when low -> plug -> unplug 2025-05-31 22:32:18 +02:00
end-4 6be83d0050 bar: util buttons: replace color picker with virtual kb 2025-05-31 22:31:44 +02:00
end-4 5fe64bfdeb make fcitx5 follow qt theme 2025-05-31 16:34:46 +02:00
end-4 9af8fc137f icons: breeze -> breeze-plus 2025-05-31 16:26:08 +02:00
end-4 a0b69f2884 first run experience 2025-05-31 12:09:02 +02:00
end-4 e5161a1f4f battery: remove unnecessary imports 2025-05-31 12:08:14 +02:00
end-4 92947e3360 easy transparency for yoinkers 2025-05-31 01:39:06 +02:00
end-4 42abc57b65 warn on low battery 2025-05-31 01:38:34 +02:00
end-4 a3d6f9f07d installation: update setuptools 2025-05-30 23:17:15 +02:00
end-4 db9754c43e remove agsv1 pkg from install script 2025-05-30 22:58:21 +02:00
end-4 5a38388c62 fix install script trying to install non-exsistent packages 2025-05-30 22:48:12 +02:00
end-4 138fc54392 dock: fix hover area accuracy, less braindead styling 2025-05-30 22:43:02 +02:00
end-4 47a34dc76b bar: add bottom position 2025-05-30 22:41:50 +02:00
end-4 e119d6d582 Update README.md 2025-05-30 17:55:45 +02:00
end-4 3089681246 keybinds: add Windows close 2025-05-30 17:55:38 +02:00
end-4 4079948b98 fix emoji picker 2025-05-30 17:55:20 +02:00
end-4 cfba132074 make installation clearer since it's not yet merged 2025-05-30 17:51:40 +02:00
end-4 1a2632909d delete unnecessary copy of kde material you color config 2025-05-30 17:41:09 +02:00
end-4 cd86de26de dependencies: add glib2 2025-05-30 17:33:24 +02:00
end-4 e8aa344976 nuke ags dependencies 2025-05-30 17:28:18 +02:00
end-4 c136422197 update dependencies 2025-05-30 17:23:09 +02:00
end-4 9322ae2e5c polkit: use kde's 2025-05-30 17:01:03 +02:00
Bishoy Ehab f61da8e09a change to pull no matter any branch 2025-05-30 17:59:50 +03:00
Bishoy Ehab b1a0e3c258 Update update.sh script to copy with -p option (preserve mode) 2025-05-30 17:58:55 +03:00
end-4 3a3fcc482f icon guess: update regexes 2025-05-30 16:55:54 +02:00
end-4 aef313a8b9 overview: fix shadow cutoff 2025-05-30 13:40:22 +02:00
end-4 47e26fd62f dock: fix warnings 2025-05-30 12:22:03 +02:00
end-4 b870f6b930 search: fix slow list move 2025-05-30 12:21:56 +02:00
end-4 18e23618ea dock: previews: morphing anims 2025-05-30 11:46:20 +02:00
end-4 3cd8865a50 dock: previews 2025-05-30 11:27:57 +02:00
end-4 198bcc6a3a readme: update dolphin mimetype fix 2025-05-30 09:32:29 +02:00
end-4 cbe086d835 refractor shadows 2025-05-30 09:32:17 +02:00
end-4 7966a8dadf use better control for bluetooth 2025-05-30 08:38:37 +02:00
end-4 fc75ae4b63 disable dock 2025-05-30 08:32:52 +02:00
end-4 782926de0f Update SearchItem.qml 2025-05-30 08:32:41 +02:00
end-4 46fdd3306c clipboard images: don't stretch 2025-05-30 08:32:16 +02:00
end-4 05d134dd9d workspaces: hide app icon when transparent 2025-05-30 08:32:01 +02:00
end-4 38a026e1dd Update SysTrayItem.qml 2025-05-30 08:31:38 +02:00
end-4 c36ca265a5 guess icons also by desktop entry search 2025-05-30 00:37:21 +02:00
end-4 440438ef33 volume mixer: guess icon also by node name 2025-05-30 00:36:53 +02:00
end-4 9cea880569 keybinds: switch to qt apps 2025-05-30 00:12:54 +02:00
end-4 3cf64401ef overview: indicate xwayland with italics and tooltip instead of icon 2025-05-29 23:51:38 +02:00
end-4 363e3327ba app search: adjust substitutions 2025-05-29 23:50:51 +02:00
end-4 8ecf9a2597 improve icon guessing 2025-05-29 23:27:46 +02:00
end-4 f942fb086a Update kdeglobals 2025-05-29 22:45:47 +02:00
end-4 694a54e331 qt apps: use breeze for colored folder icons 2025-05-29 22:44:49 +02:00
end-4 2a90777d26 add kde-material-you-colors config 2025-05-29 22:38:23 +02:00
end-4 5b6db69dd6 media controls: use quickshell's color quantizer 2025-05-29 22:24:39 +02:00
end-4 96282c4390 fix weird bold font in some places 2025-05-29 22:13:18 +02:00
end-4 9cbbaff170 add kdeglobals 2025-05-29 22:12:35 +02:00
end-4 8fdb2490c6 color generation: fix qt apps not getting correct light/dark mode 2025-05-29 21:58:02 +02:00
end-4 832f1ec571 Update README.md 2025-05-29 21:52:33 +02:00
end-4 a2eba2faf8 qt apps: use kde-material-you-colors 2025-05-29 21:29:21 +02:00
end-4 3758bdb338 hyprland: rename bezier curves 2025-05-29 13:24:11 +02:00
end-4 e35f1ae3fd hyprland: make shadow more visible 2025-05-29 13:23:47 +02:00
end-4 0d3dfe4b57 clipboard copied checkmark: only show for clipboard entries 2025-05-29 12:21:28 +02:00
end-4 4b0ee5b8ab clipboard history: remove unnecessary refreshes 2025-05-29 11:44:52 +02:00
end-4 89203e8794 cliphist service: delay a bit before grabbing entries from cliphist 2025-05-29 11:41:58 +02:00
end-4 a8533235c1 clipboard search: do not search by entry id 2025-05-29 11:38:12 +02:00
end-4 5938e6f632 search item: reorder: checkmark goes first 2025-05-29 11:26:21 +02:00
end-4 72fa76fde6 clipboard history: checkmark for selected entry 2025-05-29 11:20:08 +02:00
end-4 0ff0efe39c cliphist: refresh on clipboard change 2025-05-29 11:19:45 +02:00
end-4 c58c3d2174 dock: apps 2025-05-29 09:22:31 +02:00
end-4 2553c3ba1d shell.qml: add comment 2025-05-29 09:22:26 +02:00
end-4 5070b51e48 clipboard: decode images to /tmp 2025-05-29 09:22:12 +02:00
end-4 9503ca2129 cliphist image: cleanup decoded img on destruction 2025-05-29 09:21:56 +02:00
Bishoy Ehab 2f0a0b88e2 make update.sh script 2025-05-29 02:20:38 +03:00
end-4 d00ffdd0f1 readme: remove unnecessary pkgs 2025-05-28 19:46:38 +02:00
end-4 ba2b7fa1f9 shell.qml: make it slightly more accessible for yoinkers 2025-05-28 11:35:51 +02:00
end-4 a5abe19854 dock: pin button, launcher button 2025-05-28 11:35:19 +02:00
end-4 f1cee49494 ai: remove openai (addresses #1335) 2025-05-28 08:55:08 +02:00
end-4 442ddc1a7b clipboard history: images 2025-05-28 00:09:38 +02:00
end-4 f04d5f6202 overview: fix kb focus on hl 0.49, mask visible region 2025-05-27 22:49:40 +02:00
end-4 66c75342d4 move common dirs to Directories <--renamed-- XdgDirectories 2025-05-27 22:49:03 +02:00
end-4 5ea497068c Revert "use better-control instead of gnome settings and blueberry"
This reverts commit e1ee645e87.
2025-05-27 22:28:28 +02:00
end-4 e9485f0b8a Revert "keybinds: replace gnome settings with better control"
This reverts commit 24276cdf93.
2025-05-27 22:25:50 +02:00
end-4 adce55865e Revert "use bettercontrol for settings app (#1278)"
This reverts commit cfe48fb0a1.
2025-05-27 22:24:48 +02:00
end-4 02712868f9 search: fix item clipping 2025-05-27 20:35:57 +02:00
end-4 fb2c9ac7df favicon for links in clipboard entries 2025-05-27 20:33:56 +02:00
end-4 1a79012c61 ai search source: pass displaytext to favicon 2025-05-27 20:33:36 +02:00
end-4 37f1cd2992 ai: add gemini flash 2.5 (preview) 2025-05-27 20:10:00 +02:00
end-4 7bd910bb44 refractor favicon 2025-05-27 20:09:46 +02:00
end-4 5402893c16 overview: always show again 2025-05-27 19:34:22 +02:00
end-4 d4e8ca51a4 search: change anchor declaration 2025-05-27 11:23:50 +02:00
end-4 a759ec4e1c newline 2025-05-27 11:22:19 +02:00
end-4 4e93bd0307 notif add link click 2025-05-27 11:22:06 +02:00
end-4 8ede97a278 replace multieffect shadows with rectangularshadow ones 2025-05-27 09:40:26 +02:00
end-4 4ea6474cb6 make search result text smaller 2025-05-27 09:16:11 +02:00
end-4 a76a9bed3b adjust notif action button position 2025-05-27 09:16:01 +02:00
end-4 b78d489dfd update soramane's dotfiles license 2025-05-27 09:02:54 +02:00
end-4 54224557f0 generalize the : thing in llm apis 2025-05-27 09:02:44 +02:00
end-4 6d036e972d search: change highlighting to underline 2025-05-27 09:02:19 +02:00
end-4 d12ada5222 fixed gpt (openrouter) service logic (#1329) 2025-05-27 08:43:20 +02:00
end-4 80203c17c1 search: disable expand anim for clipboard showup 2025-05-27 00:00:00 +02:00
end-4 536fb27941 search: highlight results 2025-05-26 23:31:31 +02:00
end-4 02a3434e58 search: add levelshtein distance based search 2025-05-26 22:52:25 +02:00
Et3rnos c6ff825aa5 fixed gpt (openrouter) service logic 2025-05-26 21:42:11 +01:00
end-4 a6556f3890 overview: dont close when not searching clipboard and trying to search clipboard 2025-05-26 20:07:28 +02:00
end-4 0b80403a4b space 2025-05-26 20:06:22 +02:00
end-4 a29f12549a remove unused import 2025-05-26 20:03:10 +02:00
end-4 60625da067 format 2025-05-26 20:03:05 +02:00
end-4 cf4f7cdd28 string utils: add escape html 2025-05-26 20:02:58 +02:00
end-4 e9e6539cd0 Update keybinds.conf 2025-05-26 20:02:46 +02:00
end-4 262f278bb4 quickshell clipboard 2025-05-26 20:02:43 +02:00
end-4 24b369882a calendar: make day of week not interactable 2025-05-26 12:42:25 +02:00
end-4 7ab8012e0e notif clear button: ripple -> bouncy 2025-05-26 12:28:47 +02:00
end-4 a582cc57ac sidebar: quick toggles: make long buttons less round 2025-05-26 12:25:04 +02:00
end-4 58f23d3b47 sidebar: quick toggles: make right clickable ones bigger 2025-05-26 12:23:42 +02:00
end-4 bca1a77ae3 more hacking friendliness 2025-05-26 12:10:03 +02:00
end-4 5434771d77 media controls: adjust playpause button radius 2025-05-26 11:56:14 +02:00
end-4 21e705443e notification item: don't show unnecessary border when it's the only one in the group 2025-05-26 11:55:59 +02:00
end-4 a2ab9d2877 more hacking friendly widgets 2025-05-26 11:40:13 +02:00
end-4 6c1b27bac9 player control: playpause button change radius according to state 2025-05-26 11:39:52 +02:00
end-4 ce1418cfdb refractor radiobutton in volume mixer to new file 2025-05-26 11:39:37 +02:00
end-4 9b8eac6b54 audio device selector more bouncy 2025-05-26 11:38:50 +02:00
end-4 ede03f61bf provide basic descriptions for services 2025-05-26 10:46:07 +02:00
end-4 f765fdf531 ai: fix invalid message when switching model 2025-05-26 10:45:59 +02:00
end-4 ce94bd1b6a media controls: filter redundant players 2025-05-26 10:17:16 +02:00
end-4 ea41ee4241 notifications: timeout, prevent some warnings when dismissing notif 2025-05-26 09:37:57 +02:00
end-4 ce9993071c notifications: middle click to dismiss 2025-05-26 00:09:09 +02:00
end-4 a6e3f20dc8 make layer1 color blend more with bg than layer2 2025-05-25 23:58:36 +02:00
end-4 20a62d1fdc notif items: make dragging area cover also the icon 2025-05-25 23:55:16 +02:00
end-4 20e517a7ca notif groups: dont show invisible items 2025-05-25 23:41:51 +02:00
end-4 dbe2a922e3 notif: prevent unexpected shuffle on dismiss 2025-05-25 23:36:18 +02:00
end-4 db67398c97 remove redundant null check on music.js (#1320) 2025-05-25 22:52:55 +02:00
end-4 a24825f676 latex renderer: ensure readability after changing light/dark 2025-05-25 22:48:08 +02:00
end-4 837a2a06fa notifications: groups 2025-05-25 22:47:31 +02:00
end-4 85d3bc4444 media controls: fix weird white corners 2025-05-25 22:17:25 +02:00
end-4 3fb84b1f7f brightness service: add credit notice 2025-05-25 22:16:39 +02:00
end-4 e030fb1282 drag manager: don't hog input when not interactive 2025-05-25 22:16:23 +02:00
end-4 0ea7d156a5 drag manager widget 2025-05-25 20:38:55 +02:00
end-4 7c8f9db6d9 media controls: add shadow 2025-05-25 20:38:10 +02:00
end-4 e4093622d5 styled slider: scale radius 2025-05-25 20:37:56 +02:00
end-4 8b439d8917 styled switch: use correct anim curve for colors 2025-05-25 20:37:35 +02:00
end-4 16d96ae7ee wallpaper switcher: nicer notification name 2025-05-25 20:37:12 +02:00
end-4 675c4c3a07 notification service: add groups 2025-05-25 20:36:52 +02:00
end-4 1bc23257c7 record script: nicer app name 2025-05-25 20:02:58 +02:00
Greyfeather 9cce9edf17 remove redundant null check on music.js 2025-05-25 02:13:33 -06:00
end-4 aad4584aa7 media controls: make cover art more consistent 2025-05-25 09:17:28 +02:00
end-4 5afc4bc41e hypridle: allow suspending when steam is running (fixes #1319) 2025-05-25 07:46:27 +02:00
end-4 530339c494 idle: suspend even when steam is open 2025-05-25 07:45:39 +02:00
end-4 440d9c6907 ai: code blocks: add save to file button 2025-05-24 23:18:22 +02:00
end-4 7de5f54da2 slider: not scale handle margins twice 2025-05-24 22:46:21 +02:00
end-4 929d3836fd adjust slider design 2025-05-24 22:42:12 +02:00
end-4 b80294181c booru: add curl flags for img download 2025-05-24 22:04:32 +02:00
end-4 82713ed19a ai message: add null checks 2025-05-24 22:04:16 +02:00
end-4 bd60ea451f media controls: make cover art download more reliable 2025-05-24 22:04:03 +02:00
end-4 113024ba83 bouncy audio device selector buttons 2025-05-24 21:03:25 +02:00
end-4 a88119c3c0 ai messages: bouncy action buttons 2025-05-24 21:00:13 +02:00
end-4 ca2c6ee470 group button: cleaner default size 2025-05-24 20:59:53 +02:00
end-4 e6cc014634 remove old comment 2025-05-24 20:59:22 +02:00
end-4 9d425cd737 change small animations to use expressiveEffects curve 2025-05-24 20:59:10 +02:00
end-4 43eb4739f8 flow -> flowbuttongroup 2025-05-24 20:58:50 +02:00
end-4 ed8bcd8823 stop+start -> restart 2025-05-24 20:58:24 +02:00
end-4 5b8023740d button group: default to 0 padding 2025-05-24 20:58:04 +02:00
end-4 e0c6d92608 clean workspaces a bit 2025-05-24 20:57:48 +02:00
end-4 2641c79d65 left sidebar: bouncy command buttons 2025-05-24 19:00:18 +02:00
end-4 76abad2487 group button: modify parent's clickindex on its own 2025-05-24 18:46:16 +02:00
end-4 ba8df6e7fe adjust button bounce duration 2025-05-24 18:45:13 +02:00
end-4 b11050a231 refractor sidebar toggles group 2025-05-24 18:02:14 +02:00
end-4 fce4709d63 playercontrol: remove useless id from copy paste 2025-05-24 18:01:45 +02:00
end-4 bb67c8bb1f use bouncy anims 2025-05-24 18:01:30 +02:00
end-4 7adb95fbf8 session menu: fix button radius anim, adjust colors 2025-05-24 18:01:11 +02:00
end-4 524d69e6f8 remove debug print 2025-05-24 13:11:48 +02:00
end-4 0cf12bc3be bouncy button group for toggles 2025-05-24 13:10:49 +02:00
end-4 e7e55e7f95 revert stupid triggeredonstart for resource usage 2025-05-24 13:10:35 +02:00
end-4 67be162f20 bar: don't show battery indicator on desktkop 2025-05-24 11:54:21 +02:00
end-4 c8a4da6d8b bar: add "hella shortened" form for even narrower screens 2025-05-24 11:44:07 +02:00
end-4 0e74fc78e5 bar: add shortened form for narrow screens 2025-05-24 11:15:47 +02:00
end-4 ddb434662b anims: go back to emphasized for now 2025-05-24 11:02:52 +02:00
end-4 4fb7d7a7db resources and system info: use triggeredOnStart 2025-05-24 11:02:24 +02:00
end-4 086ba7c35a notifications: add icon for installation and upscaling prompts 2025-05-24 10:52:17 +02:00
end-4 b3e339c60f fix hyprland spelling 2025-05-24 10:05:54 +02:00
end-4 cfbf18f564 Fix ddcutil to fall back to binary serial number (#1311) 2025-05-24 10:04:48 +02:00
end-4 ac8314931f booru: fix image placement, gives single images big radius
the latter adds visual interest with some variation
2025-05-24 09:51:39 +02:00
end-4 1929b6dc8e left sidebar panelWindow size: account for bounciness 2025-05-24 09:50:08 +02:00
end-4 705a797971 wallpaper switcher: don't check resolution for videos 2025-05-24 09:49:43 +02:00
end-4 a29103d639 Set swww to video thumbnail when setting video wallpaper (#1306) 2025-05-24 09:18:18 +02:00
end-4 e87f731fb5 ripple button: add pressed radius 2025-05-23 16:46:07 +02:00
end-4 9fcf041c06 logout screen: clearer focus state color 2025-05-23 16:45:39 +02:00
end-4 b82f577420 add m3 expressive anim curves 2025-05-23 16:45:01 +02:00
end-4 513ae09951 guess better control icon 2025-05-23 16:44:27 +02:00
end-4 d011abf918 rounder todo list fab 2025-05-23 16:44:01 +02:00
end-4 f7349e6346 bar: clearer sidebar active state 2025-05-23 16:43:45 +02:00
end-4 40c0d12004 Adds JSDoc Typing to some of the JavaScript Files (#1307) 2025-05-22 20:13:51 +02:00
end-4 927487c60f make buttons ripple 2025-05-22 19:01:20 +02:00
end-4 042a4d1c24 left sidebar: slightly simplify opening/closing 2025-05-22 16:29:00 +02:00
end-4 fb26e74bcb space mono -> jetbrains mono 2025-05-22 16:27:16 +02:00
end-4 711e4b47db foot: switch to jetbrains mono font 2025-05-22 16:25:59 +02:00
end-4 4130aa7d27 fix notif disappearing too fast 2025-05-22 16:09:26 +02:00
end-4 ecbb72390a fix notif popup not showing 2025-05-22 15:44:28 +02:00
end-4 3e39fc7b13 make unload actually unload... 2025-05-22 15:00:44 +02:00
end-4 f366b9aca6 right sidebar: unload only content not panelwindow 2025-05-22 14:58:37 +02:00
end-4 fa8104480b right sidebar: actually unload completely on hide 2025-05-22 14:38:33 +02:00
end-4 e2d44e9766 make notifs not popup weirdly 2025-05-22 14:33:38 +02:00
end-4 e1d62c7c5a fix typo 2025-05-22 14:33:16 +02:00
end-4 8815846fe0 notif popup: fix undefined errors 2025-05-22 14:29:08 +02:00
end-4 c96bef3d3c remove unnecessary contentheight on listview 2025-05-22 14:28:17 +02:00
end-4 6b23854918 remove debug print 2025-05-22 14:27:55 +02:00
end-4 b66f1b7e93 fix notif popup anims 2025-05-22 14:24:52 +02:00
end-4 382a75e4c6 notification: simplify destruction, fix stuck when focus lost 2025-05-22 14:05:10 +02:00
end-4 cfc8cc30b6 right sidebar: cleaner notification implementation 2025-05-22 13:35:38 +02:00
end-4 1261d5033e booru: fix next page button text alignment 2025-05-22 12:20:11 +02:00
Greyfeather 61979a4a58 resolve comments 2025-05-21 19:31:52 -06:00
Greyfeather 0cc04bd2e4 fix typo 2025-05-21 19:22:39 -06:00
end-4 8c67c425e9 overview: fix closing immediately 2025-05-22 00:55:01 +02:00
end-4 c0c5f23bbc fix left sidebar closing immediately 2025-05-22 00:52:35 +02:00
end-4 b79fc0306c add qt6ct config 2025-05-22 00:50:04 +02:00
end-4 51b285b831 add qt6ct config 2025-05-22 00:49:34 +02:00
end-4 24276cdf93 keybinds: replace gnome settings with better control 2025-05-21 22:14:02 +02:00
end-4 2571b3f532 overview: (properly) put overview grid in loader 2025-05-21 22:12:05 +02:00
end-4 d26a95253e make loaders work properly for: media controls, osds, session 2025-05-21 21:59:51 +02:00
Nakii46 e85822c811 Fix ddcutil to fall back to binary serial number
Fix ddcutil to fall back to binary serial number if no serial number is found for a display. Also it will ignore duplicate entries that can happen when a monitor is connected via DisplayPort (More info: https://www.ddcutil.com/faq/#duplicate_displayport)
2025-05-21 15:32:30 +02:00
Greyfeather c4035b75be Merge remote-tracking branch 'refs/remotes/fork/ii-qs' into ii-qs 2025-05-21 00:25:21 -06:00
Greyfeather 65b5ec93c7 merge upstream 2025-05-21 00:22:49 -06:00
Souyama 13cb540e49 Update switchwall.sh
Updated video image
2025-05-21 08:20:05 +05:30
end-4 0f8a48ed0d boorus: create homework folder 2025-05-20 23:45:34 +02:00
end-4 44b5b25c0d clipboard history: back to simple command that works 2025-05-20 23:25:58 +02:00
end-4 b65b677839 dont let clipboard paste on select (very nasty...) 2025-05-20 23:24:37 +02:00
end-4 249ee0b55f hyprland: media controls keybind 2025-05-20 23:23:56 +02:00
end-4 e1ee645e87 use better-control instead of gnome settings and blueberry 2025-05-20 23:23:39 +02:00
end-4 cfe48fb0a1 use bettercontrol for settings app (#1278) 2025-05-20 23:17:47 +02:00
end-4 2ad293221c remove debug print 2025-05-20 23:04:09 +02:00
end-4 b1b6e837ba media controls: go back to qt5 opacity mask to prevent crash 2025-05-20 23:03:34 +02:00
end-4 d0c180f8fc media controls: use multieffect instead of qt5 graphical effects 2025-05-20 13:48:19 +02:00
end-4 fcdf2dc7f6 use multieffect for shadows 2025-05-20 12:58:39 +02:00
end-4 bece489ee9 media controls: adjust colors 2025-05-20 10:58:09 +02:00
end-4 1287fbebd7 adjust media control blur radius 2025-05-20 10:51:03 +02:00
end-4 0e0bcd4617 media controls: fix rounding 2025-05-20 10:45:47 +02:00
end-4 b0ed1e75b6 media controls: use blurred cover art for bg 2025-05-20 10:43:45 +02:00
end-4 1adfc4e89f make bar util buttons cleaner 2025-05-20 10:17:12 +02:00
end-4 e57bf529cd bar: introduce borderless mode 2025-05-20 10:17:04 +02:00
end-4 9816314749 ai use message ids to point to data
i thought it would give cleaner updates...
2025-05-20 10:16:21 +02:00
end-4 0ab39c2c50 left sidebar: fix focus 2025-05-20 10:15:29 +02:00
end-4 4715b02a45 wallpaper switcher: prompt upscale 2025-05-20 08:51:30 +02:00
end-4 a32edd387e mfking chore: add qsTr() to strings for translations 2025-05-20 00:35:09 +02:00
end-4 26a5dbd91c services: null check for ConfigOptions for hackability/yoinkability 2025-05-19 23:36:17 +02:00
end-4 fbe6c8733b Add option to ignore certain apps in dock (#1303) 2025-05-19 23:29:28 +02:00
end-4 cd177a3fcd color utils: adapt to accent: use hsl 2025-05-19 23:28:12 +02:00
end-4 2475a64bdc color utils: use qt's native color funcs 2025-05-19 23:27:57 +02:00
end-4 912422bca5 player control: default to accent when there's no cover 2025-05-19 23:27:32 +02:00
end-4 2da3d80a14 sidebars: no more rememnber last used tab
works inconsistently because of uhm swipeviews
also it's weird... ai is more useful than anime grills and notifs are more useful than mixer imo...
2025-05-19 23:16:04 +02:00
end-4 14f7542a21 move color funcs to their own file instead of appearance 2025-05-19 23:13:17 +02:00
end-4 80661d4f86 wallpaper switcher: prompt video dep installation thru notif 2025-05-19 22:40:06 +02:00
end-4 715496079f allow video wallpaper 2025-05-19 22:25:39 +02:00
end-4 f1b6789b15 Fix: Prevent raw HTML rendering in notifications (#1299) 2025-05-19 21:56:19 +02:00
end-4 a65363c60f fix #1300 2025-05-19 21:48:41 +02:00
LOSEARDES77 d4603c6b8a Fix it not working 2025-05-19 15:52:47 +02:00
LOSEARDES77 a149abf9fe Add option to ignore certain apps in dock 2025-05-19 15:43:22 +02:00
end-4 7db6df769b rename global state for sidebar open tracker 2025-05-19 12:19:16 +02:00
end-4 0e2252995c Update requirements.in 2025-05-19 11:35:25 +02:00
end-4 d8dc1c7d69 remove pywal from requirements 2025-05-19 11:34:30 +02:00
end-4 086451951a remove >pywal launcher action 2025-05-19 11:34:18 +02:00
end-4 6281c3a23c no more pywal 2025-05-19 11:31:55 +02:00
end-4 d9b4de45e6 ags: no more pywal 2025-05-19 11:25:49 +02:00
end-4 d0c5b92fb9 media controls: adjust layout 2025-05-19 11:03:52 +02:00
end-4 e6344a704d music title cleaning: only clean brackets at start 2025-05-19 09:38:06 +02:00
end-4 671d2463b8 ai: add null check for gemini annotations 2025-05-19 09:37:47 +02:00
end-4 2b393708b7 bar: util buttons: use filled icons, fix alignment 2025-05-19 09:01:15 +02:00
end-4 e33b4fad58 dont open media controls when empty 2025-05-19 09:00:21 +02:00
end-4 8b725202b3 remove debug print 2025-05-19 09:00:08 +02:00
end-4 29fb9a5268 no more hacky focus grab thanks to hyprland's dfb841c303263208c2f8ac7a55fbdf4668594fb7 2025-05-19 08:45:40 +02:00
end-4 a0ed714199 media controls: next/prev track button 2025-05-19 08:44:32 +02:00
end-4 a287b4524b use filled icons on bar 2025-05-19 08:43:55 +02:00
Greyfeather 08b9014ee2 add JSDoc to notification_utils 2025-05-19 00:21:38 -06:00
Greyfeather 23b6199684 add JSDoc to icons.js 2025-05-18 23:59:27 -06:00
Greyfeather c5b5cf4d2c add JSDoc to string_utils 2025-05-18 18:35:38 -06:00
end-4 c9f1a80dc2 adjust media control colors 2025-05-19 00:09:08 +02:00
end-4 5479d66a66 don't use qs' default reload popup 2025-05-18 23:01:28 +02:00
end-4 73deae7ece media controls: multi instance, colorize 2025-05-18 23:01:15 +02:00
end-4 d365ede358 don't prompt plasma browser integration installation 2025-05-18 21:24:20 +02:00
end-4 7428da2552 dont filter native mpris from firefox & chrome 2025-05-18 21:22:48 +02:00
end-4 314a6c67b6 feat: media controls 2025-05-18 18:54:28 +02:00
end-4 931b276d60 tweak osd size 2025-05-18 18:53:25 +02:00
end-4 1ce47424a6 right sidebar: remove unecessary visibility hook 2025-05-18 18:52:21 +02:00
end-4 69cd0fc447 no more % on indicators 2025-05-18 18:52:04 +02:00
end-4 68b233f4ef keybinds: "user"/"custom" -> "extra" 2025-05-18 11:08:36 +02:00
end-4 611f819373 refractor elementMoveEnter anims 2025-05-18 02:00:47 +02:00
end-4 b14df306ce refractor some animations 2025-05-18 01:50:12 +02:00
end-4 9385961fb8 make osdvalueindicator more customizable (internally) 2025-05-18 00:50:25 +02:00
end-4 b9fe3da1e3 brightness osd: fix no onBrightnessChanged signal warning 2025-05-18 00:48:34 +02:00
end-4 5af7ede329 session menu: only show on focused monitor 2025-05-18 00:37:08 +02:00
end-4 4626ab2bb2 fix cant assign undefined warning 2025-05-18 00:31:38 +02:00
end-4 853622e05e brightness osd: fix screen change connection target 2025-05-18 00:27:14 +02:00
end-4 27ca14d208 volume osd: only show on focused screen 2025-05-18 00:26:42 +02:00
end-4 4fa53bb4fc osd anim: slide -> popin 2025-05-18 00:25:24 +02:00
end-4 e0b883cc3e brightness osd: show only on focused screen 2025-05-18 00:20:33 +02:00
end-4 4df645a025 notif popup: show only on focused screen 2025-05-17 23:57:32 +02:00
end-4 f76da801fe fix notif dismiss on right sidebar open 2025-05-17 23:57:12 +02:00
end-4 8533310f29 left sidebar: put in loader 2025-05-17 23:45:53 +02:00
end-4 2b98b8dada remove unused var 2025-05-17 23:45:38 +02:00
end-4 01cb51d6b4 left sidebar: pinning, single instance 2025-05-17 23:42:16 +02:00
end-4 e66606170b qs: set basic as base qtquick controls style
fixes stuff for fox
2025-05-17 23:41:36 +02:00
end-4 889cff1888 cheatsheet: dont create multiple for each monitor 2025-05-17 23:41:04 +02:00
end-4 e7e08cda59 right sidebar: dont create multiple for each monitor 2025-05-17 23:19:41 +02:00
end-4 c8bbdbc472 tab buttons: fix pointing hand cursor 2025-05-17 23:04:00 +02:00
end-4 8d91007a89 readme: remove some unnecessary deps 2025-05-17 23:03:41 +02:00
end-4 e8d899d4d0 adjust osd padding 2025-05-17 22:54:09 +02:00
end-4 f5137ada13 give secondary tab button ripple 2025-05-17 22:47:54 +02:00
end-4 edec446aed space 2025-05-17 22:47:44 +02:00
end-4 50221e938b tabs: make ripple function correctly on hold 2025-05-17 22:37:37 +02:00
end-4 705a659d19 adjust layer 1 colors 2025-05-17 22:15:15 +02:00
end-4 2b778dcf6e add spacing to tab pages 2025-05-17 22:15:01 +02:00
end-4 8355ea842c make bar slide instead of pop in 2025-05-17 22:11:33 +02:00
end-4 625e2992a5 adjust ripple anim 2025-05-17 00:19:08 +02:00
end-4 7b97b4060c primary tab buttons: ripple 2025-05-17 00:12:49 +02:00
end-4 18f6f2ee9a replace background empty item with null 2025-05-16 23:52:44 +02:00
end-4 84f28f6411 tabs: nicer indicator anim 2025-05-16 23:50:20 +02:00
end-4 11087142af re enable anims for fuzzel 2025-05-16 23:14:33 +02:00
end-4 97442f3c11 clipboard history: also type directly 2025-05-16 23:14:08 +02:00
end-4 502c50f155 add wtype dep 2025-05-16 23:00:35 +02:00
end-4 aa06056fac remove unnecessary rubyshot 2025-05-16 23:00:29 +02:00
end-4 e9e7b74c1b Update fuzzel-emoji 2025-05-16 22:58:12 +02:00
Samuel Leutner 09696d9fdb Fix: Prevent raw HTML rendering in notifications
Notifications were occasionally displaying raw HTML content,
including tags, instead of the intended plain text message.

This commit introduces a regex to strip all HTML tags from
notification content before display, ensuring a proper
user experience.
2025-05-16 17:57:24 -03:00
Greyfeather a78b56c450 add jsdoc to file_utils.js 2025-05-16 14:48:29 -06:00
end-4 f15ca250c1 HyprlandKeybinds: fix children undefined 2025-05-16 22:46:31 +02:00
end-4 c695a4879c cheatsheet: put in loader 2025-05-16 22:46:11 +02:00
end-4 6dc3b7ff92 quickshell: keybinds: remove debug print 2025-05-16 22:28:30 +02:00
end-4 8b84939ec5 hyprland: adjust special ws anim duration 2025-05-16 22:28:10 +02:00
end-4 f1a980144e hyprland: update config 2025-05-16 22:24:50 +02:00
end-4 b39cbac179 hyprland: allow 3 finger ws swipe 2025-05-16 20:55:26 +02:00
end-4 e6569235a4 hyprland: disable splash rendering 2025-05-16 20:55:14 +02:00
end-4 c803b1e711 booru: prevent cursor warping when opening link 2025-05-16 19:19:31 +02:00
end-4 9cb1547136 cheatsheet: appear instantly 2025-05-16 19:19:05 +02:00
end-4 57c6e31d59 hyprland: enable focus on activate 2025-05-16 19:18:55 +02:00
end-4 54fbf89a67 cheatsheet: adjust appearance 2025-05-16 19:12:29 +02:00
end-4 fba67168d9 cheatsheet: support user keybinds 2025-05-16 19:12:09 +02:00
end-4 3cc45e37fc hyprland: make keybinds descriptions shorter, remove unnecessary stuff 2025-05-16 19:09:32 +02:00
end-4 2bdae7d96a hyprland: update keybinds 2025-05-16 18:23:48 +02:00
end-4 e5f757e1ea fix HyprlandData event update filter 2025-05-16 18:22:36 +02:00
end-4 455bcdde4d cheatsheet 2025-05-16 18:22:05 +02:00
end-4 8daa1702d0 refractor xdg dirs 2025-05-16 16:02:05 +02:00
end-4 9a68f80ffa booru: put context menu in loader, add tooltip for tags 2025-05-16 14:56:14 +02:00
end-4 3b764ca70e adjust tray item colorization again 2025-05-16 13:58:58 +02:00
end-4 15ac370409 bar: scroll hint: adjust color and anim curve 2025-05-16 13:41:48 +02:00
end-4 c00b43f99f bar: tray: adjust colorization 2025-05-16 11:46:39 +02:00
end-4 f13b8cb160 osd: fix screen 2025-05-16 11:46:24 +02:00
end-4 a2948d8967 add nicer fallback for missing icons 2025-05-16 11:34:26 +02:00
end-4 a2d48303ca bar: add scrolling hint 2025-05-16 11:26:02 +02:00
end-4 e7e6f4d0b5 brightness: make keybind and bar scroll consistent 2025-05-16 10:41:28 +02:00
end-4 6680e9adec osd: make brightness icon go speen speen 2025-05-16 08:26:23 +02:00
end-4 e85af8750a go back to material symbols rounded 2025-05-16 08:26:08 +02:00
end-4 b2c1ad628e ai service: rename currentModel and model in setModel for clarity 2025-05-15 23:10:21 +02:00
end-4 9fdf7bd6a4 quickshell: brightness service: add globalshortcut 2025-05-15 23:05:27 +02:00
end-4 b022f23d7a put overview in loader 2025-05-15 23:04:15 +02:00
end-4 9c9a615556 hyprland: reorder keybinds 2025-05-15 23:02:49 +02:00
end-4 0cd2f12f48 hyprland: add descriptions to some keybinds 2025-05-15 22:56:18 +02:00
end-4 f31c8feb13 make session screen not have awkward animation 2025-05-15 22:54:43 +02:00
end-4 7ec5c04130 brightness service: don't spill brightnessctl output into logs 2025-05-15 22:53:09 +02:00
end-4 c985273dc6 use brightness service from caelestia-dots/shell 2025-05-15 22:50:35 +02:00
end-4 2499675687 overview: search: put types of icons into loaders 2025-05-15 22:49:25 +02:00
end-4 b9e116e17f secondary tab button: icon in loader 2025-05-15 22:48:45 +02:00
end-4 0f7eed736b overview: fix a null warning 2025-05-15 22:15:02 +02:00
end-4 bddbfbcc45 bar: fix ws click switching 2025-05-15 06:35:47 +02:00
end-4 c1a8aca3fb notif popup: don't show constantly internally 2025-05-14 23:30:19 +02:00
end-4 006b6c00d9 put osds into loaders 2025-05-14 23:29:44 +02:00
end-4 13f10fc0f0 put right sidebar in loader 2025-05-14 23:08:17 +02:00
end-4 9d6b4e07da put session menu in loader 2025-05-14 22:58:13 +02:00
end-4 f6cb404ada put overview in loader 2025-05-14 22:57:03 +02:00
end-4 6c2d86a6b3 fix notif popup screen data 2025-05-14 22:21:24 +02:00
end-4 6deecbc1d4 fix notif shadow anchors warning 2025-05-14 22:20:58 +02:00
end-4 1cff3051d6 put battery charge icon into loader 2025-05-14 22:20:47 +02:00
end-4 f7c36a9700 adjust session screen alpha 2025-05-14 22:07:57 +02:00
end-4 db1e3cbab1 put notif popup in lazy loader 2025-05-14 22:07:19 +02:00
end-4 2339eda157 notifs: put different icon representations in loaders 2025-05-14 21:36:28 +02:00
end-4 c3c581fcaa more spacing on notif action buttons 2025-05-14 21:36:00 +02:00
end-4 a35fe507f2 quickshell: fix click-outside-to-close on hyprland 0.49 2025-05-14 21:17:56 +02:00
end-4 aae1efe81c fix duped launcher actions 2025-05-14 20:47:07 +02:00
end-4 d6807be932 ai: fix text input up/down arrow navigation 2025-05-14 20:09:00 +02:00
end-4 feca4c6256 Feat: switch to video background and colorgen (#1292) 2025-05-14 18:09:23 +02:00
obsidrielle def2d6f383 Style: remove unused variables and args 2025-05-14 12:01:51 +08:00
obsidrielle 8c62520666 Refactor: consistently use temporary files and mv (atomic operation) 2025-05-14 10:45:01 +08:00
obsidrielle a544f09114 Refactor: rewrite startup script without modifying config 2025-05-14 10:35:11 +08:00
end-4 6c26a90068 persistent ai model setting 2025-05-13 22:39:33 +02:00
end-4 bd80b83881 update ags config 2025-05-13 21:53:30 +02:00
end-4 18356feab7 quickshell: fix panelwindow size deprecation warnings 2025-05-13 21:53:13 +02:00
end-4 d5eee84c87 hyprland: hide special on ws change 2025-05-13 20:51:54 +02:00
obsidrielle cd9167344f Feat: switch to video background and colorgen 2025-05-13 08:39:30 +08:00
obsidrielle 1a2284234a Feat: switch to video background and colorgen 2025-05-12 22:36:16 +08:00
end-4 ee6a4c366e ags: nuke scripts already handled by quickshell 2025-05-11 16:36:31 +02:00
end-4 543243239a make quickshell's color generation handle ags 2025-05-11 16:32:32 +02:00
end-4 23af7648e4 matugen: add ags sourceview themes 2025-05-11 15:49:03 +02:00
end-4 b91dde50af fix overview toggle mouse hog 2025-05-11 12:05:08 +02:00
end-4 9ce4795266 bar: resolve some warnings 2025-05-11 11:48:38 +02:00
end-4 ce553c2c0e fix tab bar interaction with persistent states 2025-05-11 11:27:49 +02:00
end-4 6030d21e37 bar: fix messed mouse interaction hog/passthrough 2025-05-11 11:20:24 +02:00
end-4 39594baa46 bar: volume mute indicator 2025-05-11 11:06:19 +02:00
end-4 64ec9bdfe4 shorter 2025-05-11 10:29:18 +02:00
end-4 9116cf83df volume osd: handle mute 2025-05-11 10:03:03 +02:00
end-4 51686c4aa4 change progress bar anim 2025-05-11 10:01:01 +02:00
end-4 3e66c2d0f3 change race condition delay 2025-05-11 10:00:14 +02:00
end-4 7fe8838999 mic mute indicator 2025-05-11 09:59:48 +02:00
end-4 5dbf255c5f fix overview focus grab on init 2025-05-11 08:48:03 +02:00
end-4 de07c95257 hyprland: make fuzzel no anim 2025-05-11 08:14:26 +02:00
end-4 84fd898bbe make choice of booru provider and nsfw persistent 2025-05-11 08:08:41 +02:00
end-4 b827edf2ef persistent state manager: prevent writing default config before first load 2025-05-11 08:08:01 +02:00
end-4 d90067a11d make overviewToggleReleaseInterrupt description clearer 2025-05-11 08:03:55 +02:00
end-4 af5654e99f overview: only show ws highlight on focused monitor 2025-05-11 07:43:32 +02:00
end-4 6619989cf2 left sidebar: fix entry focus on open 2025-05-10 23:57:13 +02:00
end-4 18366c147f make left sidebar selected tab persistent 2025-05-10 23:51:33 +02:00
end-4 3c877197b4 make right sidebar tab persistent 2025-05-10 23:42:28 +02:00
end-4 f13912a027 make persistent states persistent 2025-05-10 23:05:32 +02:00
end-4 9d51815661 hyprland: cleanup bezier curves 2025-05-10 22:56:39 +02:00
end-4 116bea5f97 overview: handle monitor transformations 2025-05-10 22:38:09 +02:00
end-4 88bb486dc8 overview: fix monitor scaling 2025-05-10 22:06:53 +02:00
end-4 2d962ce9a8 ai: message text block dont write back when ai is writing 2025-05-10 21:24:19 +02:00
end-4 dc903de212 introduce persistent states (persistence to be added) 2025-05-10 21:23:47 +02:00
end-4 b05049dedf move toPlainObject to object_utils.js 2025-05-10 21:23:20 +02:00
end-4 8031625af7 move trimFileProtocol to file_utils.js 2025-05-10 21:22:32 +02:00
end-4 bce69c77c7 uncomment zerochan username config option 2025-05-10 21:21:29 +02:00
end-4 96ca6db76d adjust layer anim curves 2025-05-10 20:28:57 +02:00
end-4 5d16a04ea6 config options: notify on reload 2025-05-10 20:09:48 +02:00
end-4 846677caa1 config loader 2025-05-10 17:53:38 +02:00
end-4 826f54e90d ai: text block: remove unecessary key hogger 2025-05-10 17:53:21 +02:00
end-4 3876b07bc3 ai: dont keep text when command fails 2025-05-10 11:49:09 +02:00
end-4 aae8429629 very small refractor 2025-05-10 11:48:45 +02:00
end-4 6d382e467a ai: adjust search source button style 2025-05-10 11:16:01 +02:00
end-4 1b70a5eb16 booru: cleaner download 2025-05-10 11:15:34 +02:00
end-4 f93cca8a13 ai: gemini: annotation sources 2025-05-10 10:56:35 +02:00
end-4 0af7924be9 add default user agent option 2025-05-10 10:56:22 +02:00
end-4 b7e3b5d887 bar: allow hiding bg 2025-05-10 10:55:59 +02:00
end-4 b3723f7dc6 notifications: adjust animations 2025-05-10 08:53:03 +02:00
end-4 6ec3f91e0f ai: provide a default system prompt 2025-05-10 01:55:00 +02:00
end-4 6758d8daf3 ai: add api key advice 2025-05-10 01:53:43 +02:00
end-4 c0f5f55c63 ai: system prompt 2025-05-10 00:38:38 +02:00
end-4 c57772aa9d gemini: search capabilities 2025-05-10 00:24:29 +02:00
end-4 b99fe14214 rename bar's "small circle button" + prevent init color shift 2025-05-09 20:25:16 +02:00
end-4 aa07895a97 rename material theme service 2025-05-09 20:24:39 +02:00
end-4 f3d0fd5313 ai: "animate" thinking 2025-05-09 18:40:28 +02:00
end-4 de2ead426f ai: handle thinking 2025-05-09 18:23:22 +02:00
end-4 efcfd375c0 adjust animations 2025-05-09 18:23:11 +02:00
end-4 de61b46ccb remove debug print 2025-05-09 18:22:49 +02:00
end-4 bbe0641503 update hyprlock config 2025-05-09 15:49:11 +02:00
end-4 2be5e9063b hyprlock: caps lock indicator 2025-05-09 01:09:37 +02:00
end-4 2221b4d32c booru: prevent race condition in cache folder cleaning 2025-05-09 01:08:51 +02:00
end-4 b4e3221711 comment debug print 2025-05-09 01:07:55 +02:00
end-4 c13acd0152 wrap {} with () 2025-05-09 01:07:47 +02:00
end-4 c3323da840 ai chat: latex rendering 2025-05-09 01:07:31 +02:00
end-4 e56a3a591b systray: more readable monochrome icons 2025-05-09 00:58:42 +02:00
end-4 ee42d9f142 matugen: fix hyprland file path 2025-05-08 21:20:30 +02:00
end-4 3a6d0ef468 Update README.md 2025-05-08 18:04:09 +02:00
end-4 6df4806b82 ai chat: fix code block line numbers 2025-05-08 17:56:43 +02:00
end-4 c94ec8e6b2 ws num peek: don't peek if user already does smth w/ super quickly 2025-05-08 17:56:15 +02:00
end-4 8096e91e55 more ?. 2025-05-08 14:57:07 +02:00
end-4 706fd5cab8 refractor 2025-05-08 14:46:21 +02:00
end-4 4a87cf5c8b move grimblast & record script out of ags folder 2025-05-08 11:55:12 +02:00
end-4 3d6e7970ac adjust window rounding to match vscode & zen browser 2025-05-08 11:53:59 +02:00
end-4 6223320d7d ai chat: code block: i beam cursor when editing 2025-05-08 11:29:19 +02:00
end-4 0a331061c3 fix string escaping 2025-05-07 23:48:24 +02:00
end-4 47a8149968 ai chat: animated button color 2025-05-07 19:13:44 +02:00
end-4 a765a190cd ai chat: better code snippets 2025-05-07 19:13:28 +02:00
end-4 e83dfdc5d8 shorter line 2025-05-07 19:12:50 +02:00
end-4 8464f0107c code block syntax highlighting 2025-05-07 12:16:20 +02:00
end-4 f3e0f14c44 cleaner ai message buttons 2025-05-07 09:06:35 +02:00
end-4 cae673bd72 change text selection color 2025-05-07 00:08:07 +02:00
end-4 e3e70e7316 oops forgot to commit this line 2025-05-06 23:57:30 +02:00
end-4 db173152c3 ai chat: action buttons
copy, edit, toggle markdown rendering, delete
2025-05-06 23:57:17 +02:00
end-4 38efbb0d21 ai (service): add openrouter models, handle reasoning 2025-05-06 23:55:18 +02:00
end-4 ca91f528bb search: refractor 2025-05-06 23:50:56 +02:00
end-4 5f87d3a99f notif: escape when copying 2025-05-06 23:50:17 +02:00
end-4 418ac5da0c booru: better layout when expanded 2025-05-06 11:23:52 +02:00
end-4 e38a0bdac7 overview: add active border 2025-05-06 10:50:04 +02:00
end-4 009bc60c41 fix api key setting 2025-05-06 10:44:14 +02:00
end-4 24de218132 add listview add fade anim 2025-05-06 10:43:23 +02:00
end-4 e5779ff05c update markdown test message 2025-05-06 10:43:03 +02:00
end-4 46067c6811 ai: detect ollama models 2025-05-06 00:55:30 +02:00
end-4 8ff520e7ec ai chat: smaller name 2025-05-05 23:57:19 +02:00
end-4 98647d11f3 make tag buttons feel faster 2025-05-05 23:47:58 +02:00
end-4 8d93f44509 ai chat: make it work with online models 2025-05-05 23:47:34 +02:00
end-4 e3cf6b37e8 replace xdg-open with Qt.openUrlExternally 2025-05-05 17:02:14 +02:00
end-4 afa2697e4e revert stupid anim curve 2025-05-05 16:39:46 +02:00
end-4 1d39710d3f remove fill for search result icons 2025-05-05 15:32:01 +02:00
end-4 db0f077c3b booru: prevent GOAWAY on sidebar resize 2025-05-05 15:31:49 +02:00
end-4 a6098a007a remove unused var 2025-05-05 15:31:27 +02:00
end-4 ddf6181271 ai chat: make messages copyable 2025-05-05 13:29:59 +02:00
end-4 9a1e0633a1 nicer fallback icon for volume mixer 2025-05-05 13:29:47 +02:00
end-4 7469d8264f sidebar resizing 2025-05-05 12:23:35 +02:00
end-4 0ae52eafdc nicer text 2025-05-05 11:52:57 +02:00
end-4 cba6471099 nicer icon fill 2025-05-05 11:47:22 +02:00
end-4 aa7df0a74c fix console warnings for osds 2025-05-05 11:34:01 +02:00
end-4 5506f6eae9 volume mixer: smaller icons 2025-05-05 11:29:23 +02:00
end-4 4285dce730 nicer(?) calendar collapse fade
or at least more correct idk
2025-05-05 11:29:14 +02:00
end-4 6e34831183 ai chat: show message nicely, command suggestions 2025-05-05 11:06:52 +02:00
end-4 352d389cc4 material symbols: support filling 2025-05-05 11:05:58 +02:00
end-4 94ef226b92 ai chat 2025-05-05 01:13:41 +02:00
end-4 e02875890b booru: allow provider description to be translated 2025-05-05 01:10:46 +02:00
end-4 84ae535756 booru: add domain name to go to source button 2025-05-04 20:44:37 +02:00
end-4 1fe568150f booru: make immediate response nicer 2025-05-04 00:45:20 +02:00
end-4 41e82f0693 booru: instant feedback on enter 2025-05-04 00:11:36 +02:00
end-4 dc0a15e63b booru: small refractor 2025-05-04 00:05:04 +02:00
end-4 3404eacf4b remove extra space 2025-05-03 21:40:13 +02:00
end-4 ceac2931ab fancier tooltips 2025-05-03 21:31:43 +02:00
end-4 bc8b01a6f6 booru: show provider and command description 2025-05-03 21:31:31 +02:00
end-4 a2722478ba booru: add api provider suggestions 2025-05-03 18:43:07 +02:00
end-4 3284e41545 animations: new curve 2025-05-03 18:12:06 +02:00
end-4 4a44d78389 launcher: fuzzy search 2025-05-03 15:49:01 +02:00
end-4 1beb723cf3 booru: tag suggestions 2025-05-01 19:57:10 +02:00
end-4 730488f3c5 booru: minor spacing adjustment 2025-05-01 16:34:24 +02:00
end-4 059e5c6761 booru: filter empty tags 2025-05-01 07:50:03 +02:00
end-4 bcfb4e169e add missing clipping for scrollable content 2025-05-01 07:49:40 +02:00
end-4 1aa721dac8 overview make window focus work more 2025-04-30 23:12:19 +02:00
end-4 f0c1f0adff brightness: use exponential adjustment 2025-04-30 22:21:10 +02:00
end-4 7d71d9d507 add substitution for zen browser 2025-04-30 22:20:19 +02:00
end-4 17935cdc13 adjust ws icon size 2025-04-30 22:19:57 +02:00
end-4 34268147e6 set namespace for bar 2025-04-30 22:19:50 +02:00
end-4 1e5079cd61 booru: next button/command 2025-04-30 11:18:06 +02:00
end-4 fef0cc366a booru: fix 1image row width, add actions menu 2025-04-30 00:43:10 +02:00
end-4 d08d0a8914 "booru": waifu.im support 2025-04-29 23:38:29 +02:00
end-4 f2b523545b booru: proper danbooru support, fix gelbooru 2025-04-29 22:26:45 +02:00
end-4 f72e480e91 make the icon named more accurately
they cant copyright a fucking 4 point star right????????
2025-04-29 21:00:03 +02:00
end-4 f7a73edaea relocate api indicator 2025-04-29 13:28:43 +02:00
end-4 54514522d4 booru: add api provider indicator 2025-04-29 13:18:27 +02:00
end-4 f21f670d21 remove debug print 2025-04-29 11:17:35 +02:00
end-4 891da6c522 booru: style changes, tag button dont add space at beginning 2025-04-29 11:10:53 +02:00
end-4 5543efac7a booru: more sexy 2025-04-29 10:44:44 +02:00
end-4 b605cf33dd move anime stuff in their own folder 2025-04-29 07:21:15 +02:00
end-4 5de8414b64 booru image click to open sourcee 2025-04-28 23:59:49 +02:00
end-4 f24cd8fa35 booru: page number 2025-04-28 23:58:10 +02:00
end-4 1f5ea7b983 booru: images, clear, provider setting 2025-04-28 23:24:11 +02:00
end-4 160a55d859 booru: api caller service 2025-04-28 10:20:27 +02:00
end-4 c0eff65377 fix overview focus 2025-04-28 00:07:51 +02:00
end-4 c596cd21f6 tooltips: add delay 2025-04-27 23:56:33 +02:00
end-4 36e3171391 separator lines for tabbar 2025-04-27 23:49:15 +02:00
end-4 cc69a4bac4 bar: more intuitive buttons, topleft icon 2025-04-27 23:48:30 +02:00
end-4 ab9b17a188 use more hyprland dispatch exec instead of a process for simple stuff 2025-04-27 23:22:27 +02:00
end-4 9b0d769598 use hyprland global keybind dispatch for window toggling 2025-04-27 23:06:22 +02:00
end-4 4548000077 fix indentation 2025-04-27 20:55:41 +02:00
end-4 ad9452e656 left sidebar: add keybinds 2025-04-27 20:35:28 +02:00
end-4 a34d65f342 session: remove unnecessary focus 2025-04-27 20:35:11 +02:00
end-4 7f9dc76698 bar: cleaner width 2025-04-27 20:35:01 +02:00
end-4 f2a9641a95 left sidebar foundations 2025-04-27 18:58:06 +02:00
end-4 0ef575b082 use dbus keybind for releasing super for overview
AAAAAAAAAAAAAAAA LETS FUCKING GO FINALLY A GOOD LOOKING LAUNCHER THAT POPS UP INSTANTLY
2025-04-27 18:32:40 +02:00
end-4 3b8595748c workspace app icons: nicer transition 2025-04-27 00:13:27 +02:00
end-4 5e8f4048da show biggest app icon on workspaces, faster keybinds 2025-04-26 23:50:46 +02:00
end-4 f52ba7ad2b remove debug print 2025-04-26 21:32:27 +02:00
end-4 af4ecc55ce overview: drag and drop to move windows 2025-04-26 21:24:08 +02:00
end-4 6f6b3876fb Update config.fish 2025-04-26 15:28:11 +02:00
end-4 ffb7872a79 hyprland keybinds: move away from scripts in ags folder 2025-04-26 14:57:09 +02:00
end-4 6ab4c3e786 Update README.md 2025-04-26 14:43:24 +02:00
end-4 bbd472f478 terminal colors: cache -> state dir 2025-04-26 14:42:50 +02:00
end-4 dc53953766 venv: ags -> quickshell 2025-04-26 14:25:52 +02:00
end-4 35e4626fbc update hyprland config 2025-04-26 14:18:52 +02:00
end-4 a9c40bc86d color generation 2025-04-26 14:17:13 +02:00
end-4 5e9a6bf965 Update README.md 2025-04-26 09:22:28 +02:00
end-4 98547ba837 search: fix space confirming choice 2025-04-25 22:10:16 +02:00
end-4 e691fdcc59 search: run command 2025-04-25 22:01:19 +02:00
end-4 63c844cdeb overview: search 2025-04-25 20:35:37 +02:00
end-4 e1359116b8 better support for workspace 10000 2025-04-25 18:02:40 +02:00
end-4 e34a2494dd Update README.md 2025-04-25 16:07:50 +02:00
end-4 3c10a074da Update README.md 2025-04-24 23:17:26 +02:00
end-4 ebb831d345 app search qol: enter first item, entry always respond to char keys 2025-04-24 23:02:05 +02:00
end-4 72ccce04c6 overview: app search 2025-04-24 22:36:47 +02:00
end-4 8dd82baf26 use qsTr (for translations later) 2025-04-24 20:28:22 +02:00
end-4 84f031573e overview: middle click to close 2025-04-24 20:26:58 +02:00
end-4 49b3107adb typo 2025-04-24 19:56:12 +02:00
end-4 700b126a9e overview: no cursor warp for click-to-focus, add ws focus 2025-04-24 10:01:26 +02:00
end-4 e612abad23 make xwayland indicator configurable 2025-04-24 09:44:32 +02:00
end-4 6513ee82da overview: xwayland indicator 2025-04-24 09:37:08 +02:00
end-4 3c38bb3a72 bar: right click right side for next track 2025-04-24 09:36:58 +02:00
end-4 042c8dd461 overview windows: interaction 2025-04-23 22:16:18 +02:00
end-4 41ffd0ac80 overview 2025-04-23 20:40:29 +02:00
end-4 806230ff52 notif popup dismiss animation fix 2025-04-22 08:10:54 +02:00
end-4 13f68dacfb update hyprland layer rules 2025-04-22 08:04:21 +02:00
end-4 8e9f8bf173 use dolphin; update session keybind 2025-04-22 08:04:12 +02:00
end-4 2459bf2464 fix weird notif width
stupid copilot bug
2025-04-21 23:40:20 +02:00
end-4 54fdf043c9 notification popups 2025-04-21 23:29:31 +02:00
end-4 5dc0dc133d safer property access + style adjustments 2025-04-21 21:31:33 +02:00
end-4 99b9de9d5c session menu 2025-04-21 21:14:01 +02:00
end-4 dfc0a53a5f update hyprland layer rule for osd 2025-04-21 18:17:49 +02:00
end-4 0728557b04 osd: separate, make keybinds prefer qs over ags 2025-04-21 18:15:01 +02:00
end-4 0faf9287ba osd 2025-04-21 17:51:28 +02:00
end-4 9678751156 circular progress more like m3 2025-04-21 15:29:21 +02:00
end-4 073e35381c fancier radiobuttons 2025-04-21 11:05:18 +02:00
end-4 eca98598cf mixer: add audio device selector 2025-04-21 00:47:34 +02:00
end-4 9164ad2471 swap usage: don't show when zero and there's music 2025-04-20 22:15:10 +02:00
end-4 f9a8138264 notification: make dragging not lag 2025-04-20 22:14:34 +02:00
end-4 9c6aff249f volume mixer list placeholder 2025-04-20 18:37:14 +02:00
end-4 5b84100c52 refractor 2025-04-20 18:28:28 +02:00
end-4 19ba832938 volume mixer 2025-04-20 17:53:17 +02:00
end-4 e66c942790 notifications: copy button 2025-04-20 17:53:11 +02:00
end-4 ab334c7a94 Update keybinds.conf 2025-04-20 12:32:50 +02:00
end-4 138854dcbc clipping rounding for scrollables 2025-04-20 12:32:04 +02:00
end-4 bc6d963800 notifications: handle body images 2025-04-20 12:14:44 +02:00
end-4 9d7262382f notifications remove gap between time and arrow 2025-04-20 10:53:48 +02:00
end-4 74fe9f44dd notifications: drag right to dismiss 2025-04-20 10:52:14 +02:00
end-4 11ff4bbfaf MORE ANIMATION FIX AAAAAA 2025-04-20 09:46:13 +02:00
end-4 b5a9e01455 notifications: avoid id collisions across sessions 2025-04-20 08:44:06 +02:00
end-4 3096107d6b notif animations 2025-04-20 00:48:43 +02:00
end-4 905d26570f fix animations 2025-04-19 23:57:50 +02:00
end-4 b879489d43 pointing hand interaction 2025-04-19 23:47:30 +02:00
end-4 9a82d40ddc notifications: better destroy animation 2025-04-19 23:46:47 +02:00
end-4 6ae0e5f84d update layer rules 2025-04-19 23:13:15 +02:00
end-4 d44a1dce8d keybinds: update notif test 2025-04-19 23:12:59 +02:00
end-4 6d2469fe4c notifications: handle images 2025-04-19 23:11:10 +02:00
end-4 17289cef29 notif actions 2025-04-19 22:37:14 +02:00
end-4 3677873a05 notif actions 2025-04-19 21:38:33 +02:00
end-4 3b2628fbd7 notifications properly working 2025-04-19 20:07:44 +02:00
end-4 63e29d18fb fix notif time 2025-04-19 00:25:39 +02:00
end-4 6b457c7780 notification list 2025-04-19 00:09:51 +02:00
end-4 02151a93f6 fancier tabs 2025-04-18 12:43:39 +02:00
end-4 d8d812cf47 sidebarright bottom widget group collapse 2025-04-18 10:38:52 +02:00
end-4 bef66ac40a fix ugly dialog text field 2025-04-18 09:27:58 +02:00
end-4 75bf5028fd sync 2025-04-17 23:01:48 +02:00
end-4 2d540c16bc cursor shape for network and bluetooth buttons 2025-04-17 22:17:58 +02:00
end-4 8bf8c200d7 sidebar todo dialog add fade anim 2025-04-17 22:15:27 +02:00
end-4 7f779476cc Update rules.conf 2025-04-17 21:54:12 +02:00
end-4 f798b912a6 make cursor shape pointing hand on button hover 2025-04-17 16:39:23 +02:00
end-4 873cb24642 fix calendar scroll 2025-04-17 16:28:13 +02:00
end-4 1d9b543f57 todo widget: add ability to add items 2025-04-17 16:27:03 +02:00
end-4 1bb4bf8372 todo list proper anims 2025-04-17 12:41:52 +02:00
end-4 4db72d941e todo list 2025-04-17 12:31:51 +02:00
end-4 d6914a4ea2 sidebar todo 2025-04-17 01:32:35 +02:00
end-4 c62b9f8d4b new folder for services, fancy calendar month button 2025-04-16 21:48:47 +02:00
end-4 586c349f1f sidebar calendar done 2025-04-16 20:18:22 +02:00
end-4 f5d47335e8 sidebar calendar some anims 2025-04-16 10:44:15 +02:00
end-4 f7c7313087 sidebar: static calendar 2025-04-16 10:41:08 +02:00
end-4 199bc99fb5 sidebar navigation item anims 2025-04-16 00:01:14 +02:00
end-4 7c217dc25c sidebar calendar thingie: navrail 2025-04-15 22:46:18 +02:00
end-4 62ef2fc421 bluetooth and wifi 2025-04-15 20:10:52 +02:00
end-4 ff8cee9dde cleaner import; sidebar quick toggles 2025-04-15 08:58:08 +02:00
end-4 c273669003 sidebar progress 2025-04-14 23:35:40 +02:00
end-4 ab81e79eec sidebar esc to close 2025-04-14 12:59:52 +02:00
end-4 ab04d1e10d add reload popup 2025-04-14 12:19:02 +02:00
end-4 9f0deefec4 per-monitor handling of sidebar opening 2025-04-14 12:18:33 +02:00
end-4 a139c29a2b toggleable sidebar 2025-04-13 17:07:32 +02:00
end-4 248ff831ab make screen corner configurable 2025-04-13 16:42:55 +02:00
end-4 28bd79234d rounding decorations 2025-04-13 16:37:30 +02:00
end-4 7b8582124d fix stupid tooltip corner 2025-04-13 02:27:58 +02:00
end-4 08a2cb7234 Merge branch 'ii-qs' of https://github.com/end-4/dots-hyprland into ii-qs 2025-04-12 16:05:20 +02:00
end-4 eff8d1e4c3 no more pragma needed 2025-04-12 15:40:40 +02:00
end-4 8a18c395fe qt stuff qs <- main (#1223) 2025-04-12 15:38:08 +02:00
end-4 dae2857391 system tray 2025-04-11 23:35:05 +02:00
end-4 d77e4f14bf bar brightness scroll 2025-04-11 20:43:18 +02:00
end-4 06fc4baf4e active window title + workspace fix 2025-04-11 17:10:19 +02:00
end-4 c29041aa9e bar: resources/music: dynamic show/hide 2025-04-11 16:03:54 +02:00
end-4 d9ed5434ac bar: media indicator 2025-04-11 14:54:22 +02:00
end-4 3bb67a9a4f bar: workspaces: better occupied state 2025-04-11 01:39:31 +02:00
end-4 742ec413f3 bar resource usage indicator 2025-04-11 01:16:55 +02:00
end-4 ad63ae699f battery 2025-04-11 00:05:34 +02:00
end-4 ff93725b28 the perfect workspace indicator 2025-04-10 17:03:57 +02:00
end-4 5c88c6a5a6 button color anims 2025-04-10 13:09:49 +02:00
end-4 15990bf8d1 utilbuttons 2025-04-10 12:55:26 +02:00
end-4 91cef5700a clock 2025-04-10 02:06:15 +02:00
end-4 179be02e0d Update README.md 2025-04-10 00:19:17 +02:00
end-4 2e466abf71 empty bar 2025-04-10 00:16:42 +02:00
545 changed files with 35607 additions and 24251 deletions
@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
version="1.1"
id="svg9"
sodipodi:docname="oxygen.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs9" />
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="7.6782609"
inkscape:cx="-11.916761"
inkscape:cy="11.786523"
inkscape:window-width="1627"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg9" />
<g
id="g9"
transform="translate(0.7158741,-0.307456)">
<path
d="m 12.821126,8.892686 c 0,2.99523 -2.42813,5.42337 -5.4233602,5.42337 -2.99523,0 -5.42334,-2.42814 -5.42334,-5.42337 0,-2.99523 2.42811,-5.42334 5.42334,-5.42334 2.9952302,0 5.4233602,2.42811 5.4233602,5.42334 z"
fill="white"
id="path7"
style="fill:#000000" />
<path
d="m 16.593826,4.412536 c 0,1.04182 -0.8445,1.88638 -1.8863,1.88638 -1.0419,0 -1.8864,-0.84456 -1.8864,-1.88638 0,-1.041819 0.8445,-1.88638 1.8864,-1.88638 1.0418,0 1.8863,0.844561 1.8863,1.88638 z"
fill="white"
id="path8"
style="fill:#000000" />
<path
d="m 16.593826,15.495056 c 0,1.4325 -1.1612,2.5937 -2.5937,2.5937 -1.4325,0 -2.5938,-1.1612 -2.5938,-2.5937 0,-1.4325 1.1613,-2.5938 2.5938,-2.5938 1.4325,0 2.5937,1.1613 2.5937,2.5938 z"
fill="white"
id="path9"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<style-scheme id="custom-light" _name="Custom" version="1.0">
<author>end_4</author>
<_description>Catppuccin port but very random</_description>
<style name="bracket-match" background="#FDC2A6" foreground="#653D28" bold="true"/>
<style name="bracket-mismatch" background="#E3E6EB" underline="true"/>
<style name="c:preprocessor" foreground="#DF8E1D"/>
<style name="css:at-rules" foreground="#8839EF"/>
<style name="css:color" foreground="#DF8E1D"/>
<style name="css:keyword" foreground="#256BF5"/>
<style name="current-line" background="#F9DCD8"/>
<style name="cursor" foreground="#DC8A78"/>
<style name="def:base-n-integer" foreground="#DF8E1D"/>
<style name="def:boolean" foreground="#DF8E1D"/>
<style name="def:builtin" foreground="#DF8E1D"/>
<style name="def:character" foreground="#DF8E1D"/>
<style name="def:comment" foreground="#9DA1B1"/>
<style name="def:complex" foreground="#DF8E1D"/>
<style name="def:decimal" foreground="#DF8E1D"/>
<style name="def:doc-comment" foreground="#9DA1B1"/>
<style name="def:doc-comment-element" foreground="#9DA1B1"/>
<style name="def:error" foreground="#D53055" background="#EAEDF2"/>
<style name="def:floating-point" foreground="#DF8E1D"/>
<style name="def:function" foreground="#256BF5"/>
<style name="def:identifier" foreground="#000000"/>
<style name="def:keyword" foreground="#8839EF"/>
<style name="def:note" foreground="#9DA1B1"/>
<style name="def:number" foreground="#FE640B"/>
<style name="def:operator" foreground="#8839EF"/>
<style name="def:preprocessor" foreground="#256BF5"/>
<style name="def:reserved" foreground="#8839EF"/>
<style name="def:shebang" foreground="#9DA1B1"/>
<style name="def:special-char" foreground="#256BF5"/>
<style name="def:special-constant" foreground="#DF8E1D"/>
<style name="def:statement" foreground="#8839EF"/>
<style name="def:string" foreground="#4AA537"/>
<style name="def:type" foreground="#256BF5" italic="true"/>
<style name="diff:added-line" foreground="#282D32" background="#ACF2BD"/>
<style name="diff:changed-line" foreground="#282D32" background="#F1F2C3"/>
<style name="diff:location" foreground="#9DA1B1"/>
<style name="diff:removed-line" foreground="#282D32" background="#FFEEF0"/>
<style name="draw-spaces" foreground="#3b3a32"/>
<style name="html:dtd" foreground="#4AA537"/>
<style name="html:tag" foreground="#8839EF"/>
<style name="js:function" foreground="#256BF5"/>
<style name="line-numbers" foreground="#9699AA" background="#EAEDF2"/>
<style name="perl:builtin" foreground="#256BF5"/>
<style name="perl:include-statement" foreground="#8839EF"/>
<style name="perl:special-variable" foreground="#DF8E1D"/>
<style name="perl:variable" foreground="#000000"/>
<style name="php:string" foreground="#4AA537"/>
<style name="python:builtin-constant" foreground="#8839EF"/>
<style name="python:builtin-function" foreground="#256BF5"/>
<style name="python:module-handler" foreground="#8839EF"/>
<style name="python:special-variable" foreground="#8839EF"/>
<style name="ruby:attribute-definition" foreground="#8839EF"/>
<style name="ruby:builtin" foreground="#000000"/>
<style name="ruby:class-variable" foreground="#000000"/>
<style name="ruby:constant" foreground="#000000"/>
<style name="ruby:global-variable" foreground="#256BF5"/>
<style name="ruby:instance-variable" foreground="#000000"/>
<style name="ruby:module-handler" foreground="#8839EF"/>
<style name="ruby:predefined-variable" foreground="#DF8E1D"/>
<style name="ruby:regex" foreground="#f6aa11"/>
<style name="ruby:special-variable" foreground="#8839EF"/>
<style name="ruby:symbol" foreground="#DF8E1D"/>
<style name="rubyonrails:attribute-definition" foreground="#8839EF"/>
<style name="rubyonrails:block-parameter" foreground="#fd971f" italic="true"/>
<style name="rubyonrails:builtin" foreground="#000000"/>
<style name="rubyonrails:class-inherit" foreground="#256BF5" underline="true" italic="true"/>
<style name="rubyonrails:class-name" foreground="#256BF5"/>
<style name="rubyonrails:class-variable" foreground="#000000"/>
<style name="rubyonrails:complex-interpolation" foreground="#DF8E1D"/>
<style name="rubyonrails:constant" foreground="#000000"/>
<style name="rubyonrails:global-variable" foreground="#256BF5"/>
<style name="rubyonrails:instance-variable" foreground="#000000"/>
<style name="rubyonrails:module-handler" foreground="#8839EF"/>
<style name="rubyonrails:module-name" foreground="#256BF5"/>
<style name="rubyonrails:predefined-variable" foreground="#DF8E1D"/>
<style name="rubyonrails:rails" foreground="#000000"/>
<style name="rubyonrails:regex" foreground="#f6aa11"/>
<style name="rubyonrails:simple-interpolation" foreground="#DF8E1D"/>
<style name="rubyonrails:special-variable" foreground="#8839EF"/>
<style name="rubyonrails:symbol" foreground="#DF8E1D"/>
<style name="search-match" background="#E3E6EB" bold="true" underline="true"/>
<style name="selection" foreground="#f8f8f2" background="#444444"/>
<style name="text" foreground="#f8f8f2" background="#222222"/>
<style name="xml:attribute-name" foreground="#256BF5"/>
<style name="xml:element-name" foreground="#8839EF"/>
<style name="xml:entity" foreground="#c8cecc"/>
<style name="xml:namespace" foreground="#8839EF"/>
<style name="xml:tag" foreground="#8839EF"/>
</style-scheme>
-81
View File
@@ -1,81 +0,0 @@
"use strict";
// Import
import Gdk from 'gi://Gdk';
import GLib from 'gi://GLib';
import App from 'resource:///com/github/Aylur/ags/app.js'
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
// Stuff
import userOptions from './modules/.configuration/user_options.js';
import { firstRunWelcome, startBatteryWarningService } from './services/messages.js';
import { startAutoDarkModeService } from './services/darkmode.js';
// Widgets
import { Bar, BarCornerTopleft, BarCornerTopright } from './modules/bar/main.js';
import Cheatsheet from './modules/cheatsheet/main.js';
// import DesktopBackground from './modules/desktopbackground/main.js';
import Dock from './modules/dock/main.js';
import Corner from './modules/screencorners/main.js';
import Crosshair from './modules/crosshair/main.js';
import Indicator from './modules/indicators/main.js';
import Osk from './modules/onscreenkeyboard/main.js';
import Overview from './modules/overview/main.js';
import Session from './modules/session/main.js';
import SideLeft from './modules/sideleft/main.js';
import SideRight from './modules/sideright/main.js';
import { COMPILED_STYLE_DIR } from './init.js';
const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start);
function forMonitors(widget) {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).map(widget).flat(1);
}
function forMonitorsAsync(widget) {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).forEach((n) => widget(n).catch(print))
}
// Start stuff
handleStyles(true);
startAutoDarkModeService().catch(print);
firstRunWelcome().catch(print);
startBatteryWarningService().catch(print)
const Windows = () => [
// forMonitors(DesktopBackground),
forMonitors(Crosshair),
Overview(),
forMonitors(Indicator),
forMonitors(Cheatsheet),
SideLeft(),
SideRight(),
forMonitors(Osk),
forMonitors(Session),
...(userOptions.dock.enabled ? [forMonitors(Dock)] : []),
...(userOptions.appearance.fakeScreenRounding !== 0 ? [
forMonitors((id) => Corner(id, 'top left', true)),
forMonitors((id) => Corner(id, 'top right', true)),
forMonitors((id) => Corner(id, 'bottom left', true)),
forMonitors((id) => Corner(id, 'bottom right', true)),
] : []),
...(userOptions.appearance.barRoundCorners ? [
forMonitors(BarCornerTopleft),
forMonitors(BarCornerTopright),
] : []),
];
const CLOSE_ANIM_TIME = 210; // Longer than actual anim time to make sure widgets animate fully
const closeWindowDelays = {}; // For animations
for (let i = 0; i < (Gdk.Display.get_default()?.get_n_monitors() || 1); i++) {
closeWindowDelays[`osk${i}`] = CLOSE_ANIM_TIME;
}
App.config({
css: `${COMPILED_STYLE_DIR}/style.css`,
stackTraceOnError: true,
closeWindowDelay: closeWindowDelays,
windows: Windows().flat(1),
});
// Stuff that don't need to be toggled. And they're async so ugh...
forMonitorsAsync(Bar);
// Bar().catch(print); // Use this to debug the bar. Single monitor only.
-24
View File
@@ -1,24 +0,0 @@
// Want only the overview from my config? this is what you're looking for!
// Remember to install: `dart-sass`, `ags`, `material-symbols`, and `xorg-xrandr`
// To launch this, run the following
// ags -c ~/.config/ags/config_overviewOnly.js
// To toggle the overview, run:
// ags -t overview
// You might wanna add that as a keybind (in hyprland.conf)
// bind = Super, Tab, exec, ags -t overview
// Import
import App from 'resource:///com/github/Aylur/ags/app.js'
// Widgets
import Overview from './modules/overview/main.js';
import { COMPILED_STYLE_DIR } from './init.js';
handleStyles(true);
App.config({
css: `${COMPILED_STYLE_DIR}/style.css`,
stackTraceOnError: true,
windows: [
Overview(),
],
});
-55
View File
@@ -1,55 +0,0 @@
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
import configOptions from "../modules/.configuration/user_options.js";
const { langCode, Extra_logs } = configOptions.i18n
const translations = {};
let currentLanguage = langCode || getLanguageCode();
function getLanguageCode() {
let langEnv = GLib.getenv('LANG') || GLib.getenv('LANGUAGE') || 'Default.';
let langCode = langEnv.split('.')[0];
return langCode;
}
// Load language file
function loadLanguage(lang) {
if (!translations[lang]) {
try {
let filePath = `~/.config/ags/i18n/locales/${lang}.json`;
filePath = filePath.replace(/^~/, GLib.get_home_dir());
let file = Gio.File.new_for_path(filePath);
let [success, contents] = file.load_contents(null);
if (success) {
let decoder = new TextDecoder('utf-8');
let jsonString = decoder.decode(contents);
translations[lang] = JSON.parse(jsonString);
}
} catch (error) {
if (Extra_logs || lang === "Default")
console.warn(`Failed to load language file, language code: ${lang}:\n`, error);
return;
}
}
currentLanguage = currentLanguage || lang;
}
// Initialize default language
function init() {
try {
loadLanguage(currentLanguage);
if (Extra_logs)
console.log(getString("Initialization complete!") || "Initialization complete!");
loadLanguage("Default");
} catch (error) {
console.error('Failed to initialize default language:', error);
}
}
// Get translation, if no corresponding value, return the key
function getString(key) {
if (key && !translations[currentLanguage]?.[key] && Extra_logs)
console.warn(`${translations[currentLanguage]["Not found"] || "Not found"}:::${key}`);
return translations[currentLanguage]?.[key] || translations['Default']?.[key] || key;
}
export { getString, init };
-250
View File
@@ -1,250 +0,0 @@
{
"No media": "No media",
"Powered by Google": "Powered by Google",
"Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model.": "Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model.",
"Precise": "Precise",
"Balanced": "Balanced",
"Creative": "Creative",
"Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1",
"Enhancements": "Enhancements",
"Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points",
"Safety": "Safety",
"When turned off, tells the API (not the model) \nto not block harmful/explicit content": "When turned off, tells the API (not the model) \nto not block harmful/explicit content",
"History": "History",
"Saves chat history\nMessages in previous chats won't show automatically, but they are there": "Saves chat history\nMessages in previous chats won't show automatically, but they are there",
"Key stored in:": "Key stored in:",
"To update this key, type": "To update this key, type",
"Updated API Key at": "Updated API Key at",
"Currently using": "Currently using",
"Select ChatGPT-compatible API provider": "Select ChatGPT-compatible API provider",
"Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.": "Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.",
"Official Ollama API.\nPricing: Free.": "Official Ollama API.\nPricing: Free.",
"A unified interface for LLMs": "A unified interface for LLMs",
"An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key": "An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key",
"An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it's buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key": "An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it's buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key",
"Provider shown above": "Provider shown above",
"Chat with models compatible with OpenAI's Chat Completions API.\nNot affiliated, endorsed, or sponsored by any of the providers.": "Chat with models compatible with OpenAI's Chat Completions API.\nNot affiliated, endorsed, or sponsored by any of the providers.",
"The model's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "The model's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1",
"An API key is required\nYou can grab one <u>here</u>, then enter it below": "An API key is required\nYou can grab one <u>here</u>, then enter it below",
"Tells the model:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "Tells the model:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points",
"Powered by waifu.im + other APIs": "Powered by waifu.im + other APIs",
"Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.": "Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.",
"Tags →": "Tags →",
"Invalid command.": "Invalid command.",
"Anime booru": "Anime booru",
"Powered by yande.re and konachan": "Powered by yande.re and konachan",
"An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.": "An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.",
"Lewds": "Lewds",
"Shows naughty stuff when enabled": "Shows naughty stuff when enabled",
"Saves images in folders by their tags": "Saves images in folders by their tags",
"Message Gemini...": "Message Gemini...",
"Enter Google AI API Key...": "Enter Google AI API Key...",
"Message the model...": "Message the model...",
"Enter API Key...": "Enter API Key...",
"Enter tags": "Enter tags",
"Enter tags and/or page number": "Enter tags and/or page number",
"No tag in mind? Type a page number": "No tag in mind? Type a page number",
"Quick scripts": "Quick scripts",
"Change screen resolution": "Change screen resolution",
"Update packages": "Update packages",
"Trim system generations to 5": "Trim system generations to 5",
"Trim home manager generations to 5": "Trim home manager generations to 5",
"Remove orphan packages": "Remove orphan packages",
"Uninstall unused flatpak packages": "Uninstall unused flatpak packages",
"<span strikethrough=\"true\">Inaccurate</span> Color picker": "<span strikethrough=\"true\">Inaccurate</span> Color picker",
"Result": "Result",
"Type to search": "Type to search",
"illogical-impulse": "illogical-impulse",
"RAM Usage": "RAM Usage",
"Swap Usage": "Swap Usage",
"CPU Usage": "CPU Usage",
"Uptime:": "Uptime:",
"Screen snip": "Screen snip",
"Color picker": "Color picker",
"Toggle on-screen keyboard": "Toggle on-screen keyboard",
"Night Light": "Night Light",
"Color inversion": "Color inversion",
"Keep system awake": "Keep system awake",
"Cloudflare WARP": "Cloudflare WARP",
"Session": "Session",
"Bluetooth | Right-click to configure": "Bluetooth | Right-click to configure",
"Wifi | Right-click to configure": "Wifi | Right-click to configure",
"Right-click to configure": "Right-click to configure",
"Unknown": "Unknown",
"Reload Environment config": "Reload Environment config",
"Open Settings": "Open Settings",
"Notifications": "Notifications",
"Audio controls": "Audio controls",
"Bluetooth": "Bluetooth",
"Wifi networks": "Wifi networks",
"Quick config": "Quick config",
"Silence": "Silence",
"Clear": "Clear",
"No notifications": "No notifications",
"notifications": "notifications",
"Close": "Close",
"Now": "Now",
"Yesterday": "Yesterday",
"No audio source": "No audio source",
"Remove device": "Remove device",
"Connected": "Connected",
"Paired": "Paired",
"More": "More",
"Selected": "Selected",
"Connecting to": "Connecting to",
"Current network": "Current network",
"Authentication": "Authentication",
"Authentication failed": "Authentication failed",
"Enter network password": "Enter network password",
"Properties": "Properties",
"Forget": "Forget",
"Effects": "Effects",
"Transparency": "Transparency",
"[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this": "[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this",
"Blur": "Blur",
"[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.": "[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.",
"X-ray": "X-ray",
"[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ": "[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ",
"Size": "Size",
"[Hyprland]\nAdjust the blur radius. Generally doesn't affect performance\nHigher = more color spread": "[Hyprland]\nAdjust the blur radius. Generally doesn't affect performance\nHigher = more color spread",
"Passes": "Passes",
"[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.": "[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.",
"Animations": "Animations",
"[Hyprland] [GTK]\nEnable animations": "[Hyprland] [GTK]\nEnable animations",
"Choreography delay": "Choreography delay",
"In milliseconds, the delay between animations of a series": "In milliseconds, the delay between animations of a series",
"Developer": "Developer",
"Show FPS": "Show FPS",
"[Hyprland]\nShow FPS overlay on top-left corner": "[Hyprland]\nShow FPS overlay on top-left corner",
"Log to stdout": "Log to stdout",
"[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console": "[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console",
"Damage tracking": "Damage tracking",
"[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work": "[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work",
"Damage blink": "Damage blink",
"[Hyprland] [Epilepsy warning!]\nShow screen damage flashes": "[Hyprland] [Epilepsy warning!]\nShow screen damage flashes",
"Not all changes are saved": "Not all changes are saved",
"Mo": "Mo",
"Tu": "Tu",
"We": "We",
"Th": "Th",
"Fr": "Fr",
"Sa": "Sa",
"Su": "Su",
"Calendar": "Calendar",
"To Do": "To Do",
"Unfinished": "Unfinished",
"Done": "Done",
"Finished tasks will go here": "Finished tasks will go here",
"Nothing here!": "Nothing here!",
"+ New task": "+ New task",
"Add a task...": "Add a task...",
"Collapse calendar": "Collapse calendar",
"Expand calendar": "Expand calendar",
"To do tasks": "To do tasks",
"Color scheme": "Color scheme",
"Options": "Options",
"Dark Mode": "Dark Mode",
"Ya should go to sleep!": "Ya should go to sleep!",
"Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)": "Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)",
"Scheme styles": "Scheme styles",
"Vibrant": "Vibrant",
"Vibrant+": "Vibrant+",
"Expressive": "Expressive",
"Monochrome": "Monochrome",
"Rainbow": "Rainbow",
"Fidelity": "Fidelity",
"Fruit Salad": "Fruit Salad",
"Tonal Spot": "Tonal Spot",
"Content": "Content",
"Use arrow keys to navigate.\nEnter to select, Esc to cancel.": "Use arrow keys to navigate.\nEnter to select, Esc to cancel.",
"Lock": "Lock",
"Logout": "Logout",
"Sleep": "Sleep",
"Hibernate": "Hibernate",
"Shutdown": "Shutdown",
"Reboot": "Reboot",
"Cancel": "Cancel",
"Cheat sheet": "Cheat sheet",
"Keybinds": "Keybinds",
"Periodic table": "Periodic table",
"Essentials for beginners": "Essentials for beginners",
"Make shell elements transparent": "Make shell elements transparent",
"Actions": "Actions",
"Window management": "Window management",
"Window arrangement": "Window arrangement",
"Workspace management": "Workspace management",
"Workspace navigation": "Workspace navigation",
"Widgets": "Widgets",
"Media": "Media",
"Apps": "Apps",
"Neutral": "Neutral",
"Launch foot (terminal)": "Launch foot (terminal)",
"Open app launcher": "Open app launcher",
"Change wallpaper": "Change wallpaper",
"Clipboard history >> clipboard": "Clipboard history >> clipboard",
"Pick emoji >> clipboard": "Pick emoji >> clipboard",
"Screen snip >> edit": "Screen snip >> edit",
"Screen snip to text >> clipboard": "Screen snip to text >> clipboard",
"Pick color (Hex) >> clipboard": "Pick color (Hex) >> clipboard",
"Screenshot >> clipboard": "Screenshot >> clipboard",
"Screenshot >> clipboard & file": "Screenshot >> clipboard & file",
"Record region (no sound)": "Record region (no sound)",
"Record screen (with sound)": "Record screen (with sound)",
"Suspend system": "Suspend system",
"Move focus in direction": "Move focus in direction",
"Move window": "Move window",
"Resize window": "Resize window",
"Close window": "Close window",
"Pick and kill a window": "Pick and kill a window",
"Window: move in direction": "Window: move in direction",
"Window: split ratio +/- 0.1": "Window: split ratio +/- 0.1",
"Float/unfloat window": "Float/unfloat window",
"Toggle fake fullscreen": "Toggle fake fullscreen",
"Toggle fullscreen": "Toggle fullscreen",
"Toggle maximization": "Toggle maximization",
"Focus workspace # (1, 2, 3, 4, ...)": "Focus workspace # (1, 2, 3, 4, ...)",
"Workspace: focus left/right": "Workspace: focus left/right",
"Workspace: toggle special": "Workspace: toggle special",
"Window: move to workspace # (1, 2, 3, 4, ...)": "Window: move to workspace # (1, 2, 3, 4, ...)",
"Window: move to workspace left/right": "Window: move to workspace left/right",
"Window: move to workspace special": "Window: move to workspace special",
"Window: pin (show on all workspaces)": "Window: pin (show on all workspaces)",
"Restart widgets": "Restart widgets",
"Cycle bar mode (normal, focus)": "Cycle bar mode (normal, focus)",
"Toggle overview/launcher": "Toggle overview/launcher",
"Show cheatsheet": "Show cheatsheet",
"Toggle left sidebar": "Toggle left sidebar",
"Toggle right sidebar": "Toggle right sidebar",
"Toggle music controls": "Toggle music controls",
"View color scheme and options": "View color scheme and options",
"Toggle power menu": "Toggle power menu",
"Toggle crosshair": "Toggle crosshair",
"Next track": "Next track",
"Previous track": "Previous track",
"Play/pause media": "Play/pause media",
"Launch Zed (editor)": "Launch Zed (editor)",
"Launch VSCode (editor)": "Launch VSCode (editor)",
"Launch Nautilus (file manager)": "Launch Nautilus (file manager)",
"Launch Firefox (browser)": "Launch Firefox (browser)",
"Launch GNOME Text Editor": "Launch GNOME Text Editor",
"Launch WPS Office": "Launch WPS Office",
"Launch GNOME Settings": "Launch GNOME Settings",
"Launch pavucontrol (volume mixer)": "Launch pavucontrol (volume mixer)",
"Launch EasyEffects (equalizer & other audio effects)": "Launch EasyEffects (equalizer & other audio effects)",
"Launch GNOME System monitor": "Launch GNOME System monitor",
"Toggle fallback launcher: anyrun": "Toggle fallback launcher: anyrun",
"Toggle fallback launcher: fuzzel": "Toggle fallback launcher: fuzzel",
"Initialization complete!": "Initialization complete!",
"Not found": "Not found:",
"Calling API": "Calling API",
"Downloading image": "Downloading image",
"Finished!": "Finished!",
"Error": "Error",
"Not found!": "Not found!",
"Go to file url": "Go to file url",
"Save image": "Save image",
"Hoard": "Hoard",
"Open externally": "Open externally",
"You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “Im not sure.”. \nThanks!": "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “Im not sure.”. \nThanks!",
"Feels like": "Feels like"
}
-238
View File
@@ -1,238 +0,0 @@
{
"No media": "بدون رسانه",
"Powered by Google": "قدرت گرفته از گوگل",
"Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model.": "بدون وابستگی تأیید شده یا حمایت شده توسط گوگل.\n\nحریم خصوصی: پیام‌های گفتگو به حساب شما مرتبط نیستند،\nاما توسط بازبینی‌کنندگان انسانی برای بهبود مدل خوانده خواهند شد.",
"Precise": "دقیق",
"Balanced": "متعادل",
"Creative": "خلاق",
"Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "مقدار دما در Gemini.\n دقیق = 0\n متعادل = 0.5\n خلاق = 1",
"Enhancements": "بهبودها",
"Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "به Gemini می‌گوید:\n- این یک دستیار نوار کناری لینوکس است\n- مختصر باشید و از فهرست کردن کمک بگیرید",
"Safety": "ایمنی",
"When turned off, tells the API (not the model) \nto not block harmful/explicit content": "زمانی که خاموش باشد، به API (نه مدل) می‌گوید که \nمحتوای مضر/صریح را مسدود نکند",
"History": "پیشینه",
"Saves chat history\nMessages in previous chats won't show automatically, but they are there": "پیشینه گفتگو را نگه‌داری می‌کند\nپیام‌های گفتگو‌های پیشین به‌طور خودکار نمایش داده نمی‌شوند، اما وجود دارند",
"Key stored in:": "کلید نگه‌داری شده در:",
"To update this key, type": "برای به‌روزرسانی این کلید، بنویسید",
"Updated API Key at": "کلید API به‌روزرسانی شده در",
"Currently using": "هم‌اینک بکار گرفته میشود",
"Select ChatGPT-compatible API provider": "انتخاب ارائه‌دهنده API سازگار با ChatGPT",
"Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.": "API رسمی OpenAI.\nقیمت‌گذاری: رایگان برای اولین 5 دلار یا 3 ماه، هر کدام که کمتر باشد.",
"Official Ollama API.\nPricing: Free.": "API رسمی Ollama.\nقیمت‌گذاری: رایگان.",
"A unified interface for LLMs": "یک رابط یکپارچه برای LLMها",
"An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key": "API از Tornado Softwares\nقیمت‌گذاری: رایگان: 100 در روز\nنیاز به پیوستن به دیسکورد آنها برای دریافت کلید دارد",
"An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it's buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key": "API از @zukixa در گیت‌هاب.\nتوجه: کلیدها قفل IP هستند بنابراین گاهی اوقات باگ دارند\nقیمت‌گذاری: رایگان: 10 در دقیقه، 800 در روز.\nنیاز به پیوستن به دیسکورد آنها برای دریافت کلید دارد",
"Provider shown above": "ارائه‌دهنده در بالا نشان داده شده است",
"The model's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "مقدار دما در مدل.\n دقیق = 0\n متعادل = 0.5\n خلاق = 1",
"An API key is required\nYou can grab one <u>here</u>, then enter it below": "یک کلید API مورد نیاز است\nشما می‌توانید یکی را <u>اینجا</u> بگیرید، سپس آن را پایین وارد کنید",
"Tells the model:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "به مدل می‌گوید:\n- این یک دستیار نوار کناری لینوکس است\n- گزافه‌گو نباشید و نقاط فهرست بکار ببرید",
"Powered by waifu.im + other APIs": "قدرت گرفته از waifu.im + سایر APIها",
"Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.": "برچسب‌ها را برای یک تصویر تصادفی بنویسید.\nمحتوای NSFW بازگردانده نخواهد شد مگر اینکه\nشما به‌طور صریح چنین برچسبی را درخواست کنید.\n\nتوجه: بدون وابستگی ارائه‌دهندگان\nو مسئول هیچ‌یک از محتوای آنها نیست.",
"Tags →": "برچسب‌ها →",
"Invalid command.": "دستور نامعتبر.",
"Anime booru": "انیمه بورو",
"Powered by yande.re and konachan": "قدرت گرفته از yande.re و konachan",
"An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.": "یک تصویر بورو. ممکن است محتوای NSFW داشته باشد.\nمواظب باشید.\n\nتوجه: بدون وابستگی به ارائه‌دهنده\nو مسئول هیچ‌یک از محتوای آن نیست.",
"Lewds": "محتوای نامناسب",
"Shows naughty stuff when enabled": "محتوای نامناسب را زمانی که فعال باشد نشان می‌دهد",
"Saves images in folders by their tags": "تصاویر را در پوشه‌ها بر اساس برچسب‌هایشان نگه‌داری می‌کند",
"Message Gemini...": "پیام به Gemini...",
"Enter Google AI API Key...": "کلید API گوگل AI را وارد کنید...",
"Message the model...": "پیام به مدل...",
"Enter API Key...": "کلید API را وارد کنید...",
"Enter tags": "برچسب‌ها را وارد کنید",
"Quick scripts": "اسکریپت‌های سریع",
"Change screen resolution": "تغییر وضوح صفحه",
"Update packages": "به‌روزرسانی بسته‌ها",
"Trim system generations to 5": "تعداد نسل‌های سامانه را به 5 کاهش دهید",
"Trim home manager generations to 5": "تعداد نسل‌های مدیر خانه را به 5 کاهش دهید",
"Remove orphan packages": "بسته‌های ناکارآمد را پاک کنید",
"Uninstall unused flatpak packages": "بسته‌های فلت‌پک بکار گرفته نشده را پاک کنید",
"<span strikethrough=\"true\">Inaccurate</span> Color picker": "<span strikethrough=\"true\">نادرست</span> انتخاب‌گر رنگ",
"Result": "نتیجه",
"Type to search": "برای جستجو بنویسید",
"illogical-impulse": "illogical-impulse",
"RAM Usage": "کارکرد RAM",
"Swap Usage": "کارکرد Swap",
"CPU Usage": "کارکرد CPU",
"Uptime:": "در حال کار:",
"Screen snip": "برش صفحه",
"Color picker": "انتخاب‌گر رنگ",
"Toggle on-screen keyboard": "کیبورد روی صفحه را فعال/غیرفعال کنید",
"Night Light": "نور شب",
"Color inversion": "وارونگی رنگ",
"Keep system awake": "سامانه را بیدار نگه‌دارید",
"Cloudflare WARP": "Cloudflare WARP",
"Session": "نشست",
"Bluetooth | Right-click to configure": "بلوتوث | برای پیکربندی راست کلیک کنید ",
"Wifi | Right-click to configure": "وای‌فای | برای پیکربندی راست کلیک کنید ",
"Right-click to configure": "برای پیکربندی راست کلیک کنید",
"Unknown": "ناشناخته",
"Reload Environment config": "پیکربندی محیط را دوباره بارگذاری کنید",
"Open Settings": "تنظیمات را باز کنید",
"Notifications": "آگاه‌سازها",
"Audio controls": "کنترل‌های صدا",
"Bluetooth": "بلوتوث",
"Wifi networks": "شبکه‌های وای‌فای",
"Quick config": "پیکربندی زنده",
"Silence": "سکوت",
"Clear": "پاک کردن",
"No notifications": "بدون آگاه‌ساز",
"notifications": "آگاه‌سازها",
"Close": "بستن",
"Now": "اینک",
"Yesterday": "دیروز",
"No audio source": "هیچ منبع صوتی",
"Remove device": "پاک‌کردن دستگاه",
"Connected": "متصل",
"Paired": "جفت شده",
"More": "بیشتر",
"Selected": "انتخاب شده",
"Current network": "شبکه کنونی",
"Authentication": "احراز هویت",
"Effects": "جلوه‌ها",
"Transparency": "شفافیت",
"[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this": "[AGS]\nعناصر شل را شفاف کنید\nهمچنین اگر این را فعال کنید، تاری نیز توصیه می‌شود",
"Blur": "تاری",
"[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.": "[Hyprland]\nفعال کردن تاری بر روی عناصر شفاف\nبر عملکرد/مصرف برق تأثیر نمی‌گذارد مگر اینکه پنجره‌های شفاف داشته باشید.",
"X-ray": "اشعه ایکس",
"[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ": "[Hyprland]\nهمه چیز را پشت یک پنجره/لایه به جز پس‌زمینه بر روی سطح تاری آن رندر نکنید\nتوصیه می‌شود برای بهبود عملکرد (اگر از شفافیت/تاری سوءاستفاده نکنید)",
"Size": "اندازه",
"[Hyprland]\nAdjust the blur radius. Generally doesn't affect performance\nHigher = more color spread": "[Hyprland]\nشعاع تاری را تنظیم کنید. به طور کلی بر عملکرد تأثیر نمی‌گذارد\nبیشتر = پخش رنگ بیشتر",
"Passes": "عبور",
"[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.": "[Hyprland] تعداد عبورهای الگوریتم تاری را تنظیم کنید\nعبورهای بیشتر = پخش بیشتر و مصرف برق بیشتر\n4 توصیه می‌شود\n2- عجیب به نظر می‌رسد و 6+ بی‌مزه خواهد بود.",
"Animations": "پویانمایی‌ها",
"[Hyprland] [GTK]\nEnable animations": "[Hyprland] [GTK]\nفعال کردن پویانمایی‌ها",
"Choreography delay": "درنگ در پویایی",
"In milliseconds, the delay between animations of a series": "به میلی‌ثانیه، درنگ بین پویانمایی‌های یک سری",
"Developer": "توسعه‌دهنده",
"Show FPS": "نمایش FPS",
"[Hyprland]\nShow FPS overlay on top-left corner": "[Hyprland]\nنمایش پوشش FPS در گوشه بالا سمت چپ",
"Log to stdout": "ثبت در stdout",
"[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console": "[Hyprland]\nپیام‌های LOG، ERR، WARN و دیگر را به کنسول چاپ کنید",
"Damage tracking": "ردیابی آسیب",
"[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work": "[Hyprland]\nفعال کردن ردیابی آسیب\nبه طور کلی، آن را روشن بگذارید.\nفقط زمانی که یک سایه‌زن(شیدر) کار نمی‌کند، خاموش کنید",
"Damage blink": "چشمک آسیب",
"[Hyprland] [Epilepsy warning!]\nShow screen damage flashes": "[Hyprland] [هشدار صرع!]\nنمایش چشمک‌های آسیب صفحه",
"Not all changes are saved": "همه تغییرات نگه‌داری نشده‌اند",
"Mo": "دو",
"Tu": "سه",
"We": "چهار",
"Th": "پنج",
"Fr": "جمعه",
"Sa": "شنبه",
"Su": "یک",
"Calendar": "تقویم",
"To Do": "کارها",
"Unfinished": "مانده",
"Done": "پایان یافته",
"Finished tasks will go here": "کارهای پایان یافته اینجا خواهند بود",
"Nothing here!": "هیچ چیز اینجا نیست!",
"+ New task": "+ کار جدید",
"Add a task...": "افزودن یک کار...",
"Color scheme": "طرح رنگ",
"Options": "گزینه‌ها",
"Dark Mode": "حالت تاریک",
"Ya should go to sleep!": "پاشو برو بخواب!",
"Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)": "برنامه‌های GTK را با بکارگیری رنگ تأکید پوسته گذاری کنید\n(معایب: تغییر حالت تاریک/روشن نیاز به باز راه‌اندازی دارد)",
"Scheme styles": "سبک‌های طرح",
"Vibrant": "زنده",
"Vibrant+": "زنده+",
"Expressive": "بیانگر",
"Monochrome": "تک‌رنگ",
"Rainbow": "رنگین کمان",
"Fidelity": "وفاداری",
"Fruit Salad": "سالاد میوه",
"Tonal Spot": "نقطه تنال",
"Content": "محتوا",
"Use arrow keys to navigate.\nEnter to select, Esc to cancel.": "برای ناوبری کلیدهای جهت‌دار را بکار ببرید.\nبرای انتخاب Enter و برای رد کردن Esc را بزنید.",
"Lock": "قفل",
"Logout": "خروج",
"Sleep": "خواب",
"Hibernate": "خواب زمستانی",
"Shutdown": "خاموش",
"Reboot": "باز راه‌اندازی",
"Cancel": "رد کردن",
"Cheat sheet": "برگه تقلب",
"Keybinds": "کلیدهای میانبر",
"Periodic table": "جدول تناوبی",
"Essentials for beginners": "اساس برای مبتدیان",
"Make shell elements transparent": "عناصر شل را شفاف کنید",
"Actions": "کنش",
"Window management": "مدیریت پنجره",
"Window arrangement": "چیدمان پنجره",
"Workspace management": "مدیریت فضای کاری",
"Workspace navigation": "ناوبری فضای کاری",
"Widgets": "ابزارک‌ها",
"Media": "رسانه",
"Apps": "برنامه‌ها",
"Neutral": "خنثی",
"Launch foot (terminal)": "اجرای foot (ترمینال)",
"Open app launcher": "باز کردن راه‌انداز برنامه",
"Change wallpaper": "تغییر پس‌زمینه",
"Clipboard history >> clipboard": "پیشینه کلیپ بورد >> کلیپ بورد",
"Pick emoji >> clipboard": "انتخاب ایموجی >> کلیپ بورد",
"Screen snip >> edit": "برش صفحه >> ویرایش",
"Screen snip to text >> clipboard": "برش صفحه به متن >> کلیپ بورد",
"Pick color (Hex) >> clipboard": "انتخاب رنگ (Hex) >> کلیپ بورد",
"Screenshot >> clipboard": "عکس صفحه >> کلیپ بورد",
"Screenshot >> clipboard & file": "عکس صفحه >> کلیپ بورد و فایل",
"Record region (no sound)": "ضبط منطقه (بدون صدا)",
"Record screen (with sound)": "ضبط صفحه (با صدا)",
"Suspend system": "تعلیق سامانه",
"Move focus in direction": "جابه‌جایی تمرکز در جهت",
"Move window": "جابه‌جایی پنجره",
"Resize window": "تغییر اندازه پنجره",
"Close window": "بستن پنجره",
"Pick and kill a window": "انتخاب و بستن یک پنجره",
"Window: move in direction": "پنجره: جابه‌جایی در جهت",
"Window: split ratio +/- 0.1": "پنجره: نسبت تقسیم +/- 0.1",
"Float/unfloat window": "پنجره را شناور/نا شناور کنید",
"Toggle fake fullscreen": "تغییر حالت تمام صفحه ساختگی",
"Toggle fullscreen": "تغییر حالت تمام صفحه",
"Toggle maximization": "تغییر حالت بزرگ‌نمایی",
"Focus workspace # (1, 2, 3, 4, ...)": "تمرکز بر فضای کاری # (1، 2، 3، 4، ...)",
"Workspace: focus left/right": "فضای کاری: تمرکز به چپ/راست",
"Workspace: toggle special": "فضای کاری: تغییر حالت خاص",
"Window: move to workspace # (1, 2, 3, 4, ...)": "پنجره: رفتن به فضای کاری # (1، 2، 3، 4، ...)",
"Window: move to workspace left/right": "پنجره: رفتن به فضای کاری چپ/راست",
"Window: move to workspace special": "پنجره: رفتن به فضای کاری خاص",
"Window: pin (show on all workspaces)": "پنجره: سنجاق (نمایش در همه فضاهای کاری)",
"Restart widgets": "باز راه‌اندازی ابزارک‌ها",
"Cycle bar mode (normal, focus)": "چرخش حالت نوار (عادی، تمرکز)",
"Toggle overview/launcher": "تغییر حالت نمای کلی/راه‌انداز",
"Show cheatsheet": "نمایش برگه تقلب",
"Toggle left sidebar": "تغییر حالت نوار کناری چپ",
"Toggle right sidebar": "تغییر حالت نوار کناری راست",
"Toggle music controls": "تغییر حالت کنترل‌های موسیقی",
"View color scheme and options": "مشاهده طرح رنگ و گزینه‌ها",
"Toggle power menu": "تغییر حالت فهرست قدرت",
"Toggle crosshair": "تغییر حالت نشانه‌گذاری",
"Next track": "ترانه پسین",
"Previous track": "ترانه پیشین",
"Play/pause media": "پخش/مکث رسانه",
"Launch Zed (editor)": "اجرای Zed (ویرایشگر)",
"Launch VSCode (editor)": "اجرای VSCode (ویرایشگر)",
"Launch Nautilus (file manager)": "اجرای Nautilus (مدیر فایل)",
"Launch Firefox (browser)": "اجرای Firefox (مرورگر)",
"Launch GNOME Text Editor": "اجرای ویرایشگر متن GNOME",
"Launch WPS Office": "اجرای WPS Office",
"Launch GNOME Settings": "اجرای تنظیمات GNOME",
"Launch pavucontrol (volume mixer)": "اجرای pavucontrol (میکسر صدا)",
"Launch EasyEffects (equalizer & other audio effects)": "اجرای EasyEffects (اکولایزر و سایر جلوه‌های صوتی)",
"Launch GNOME System monitor": "اجرای مانیتور سامانه GNOME",
"Toggle fallback launcher: anyrun": "بکارگیری راه‌انداز پشتیبان: anyrun",
"Toggle fallback launcher: fuzzel": "بکارگیری راه‌انداز پشتیبان: fuzzel",
"Initialization complete!": "راه‌اندازی کامل شد!",
"Not found": "یافت نشد:",
"Calling API": "در حال تماس با API",
"Downloading image": "در حال دریافت تصویر",
"Finished!": "پایان یافت!",
"Error": "خطا",
"Not found!": "یافت نشد!",
"Go to file url": "رفتن به URL فایل",
"Save image": "نگه‌داری تصویر",
"Hoard": "نگه‌داری",
"Open externally": "باز کردن در",
"You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “Im not sure.”. \nThanks!": "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “Im not sure.”. \nIf user talks to you in Persian and you can also respond in Persian, definitely respond in Persian. Thanks!"
}
-240
View File
@@ -1,240 +0,0 @@
{
"No media": "Aucun média",
"Powered by Google": "Fourni par Google",
"Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model.": "Non affilié, approuvé ou sponsorisé par Google.\n\nConfidentialité : Les messages de chat ne sont pas liés à votre compte,\nmais seront lus par des examinateurs humains pour améliorer le modèle.",
"Precise": "Précis",
"Balanced": "Équilibré",
"Creative": "Créatif",
"Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "Température de Gemini.\n Précis = 0\n Équilibré = 0.5\n Créatif = 1",
"Enhancements": "Améliorations",
"Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "Indique à Gemini :\n- C'est un assistant de panneau latéral pour Linux\n- Sois concis et utilise des puces",
"Safety": "Sécurité",
"When turned off, tells the API (not the model) \nto not block harmful/explicit content": "Quand désactivé, indique à l'API (et non au modèle)\nde ne pas bloquer le contenu nuisible/explicite",
"History": "Historique",
"Saves chat history\nMessages in previous chats won't show automatically, but they are there": "Sauvegarde l'historique des discussions\nLes messages des conversations précédentes ne s'affichent pas automatiquement, mais ils existent",
"Key stored in:": "Clé stockée dans :",
"To update this key, type": "Pour mettre à jour cette clé, tapez",
"Updated API Key at": "Clé API mise à jour le",
"Currently using": "Actuellement utilisé",
"Select ChatGPT-compatible API provider": "Sélectionnez un fournisseur d'API compatible ChatGPT",
"Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.": "API officielle d'OpenAI.\nTarification : Gratuit pour les premiers 5 $ ou 3 mois, selon le moindre des deux.",
"Official Ollama API.\nPricing: Free.": "API officielle d'Ollama.\nTarification : Gratuit.",
"A unified interface for LLMs": "Une interface unifiée pour les LLM",
"An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key": "Une API de Tornado Softwares\nTarification : Gratuit : 100 par jour\nNécessite de rejoindre leur Discord pour obtenir une clé",
"An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it's buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key": "Une API de @zukixa sur GitHub.\nNote : Les clés sont verrouillées par IP, ce qui peut provoquer des bugs\nTarification : Gratuit : 10/min, 800/jour.\nNécessite de rejoindre leur Discord pour obtenir une clé",
"Provider shown above": "Fournisseur indiqué ci-dessus",
"The model's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "La valeur de température du modèle.\n Précis = 0\n Équilibré = 0.5\n Créatif = 1",
"An API key is required\nYou can grab one <u>here</u>, then enter it below": "Une clé API est requise\nVous pouvez en obtenir une <u>ici</u>, puis la saisir ci-dessous",
"Tells the model:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "Indique au modèle :\n- C'est un assistant de barre latérale pour Linux\n- Sois concis et utilise des puces",
"Powered by waifu.im + other APIs": "Propulsé par waifu.im + d'autres API",
"Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.": "Tapez des tags pour une image aléatoire.\nLe contenu NSFW ne sera pas affiché à moins que\nvous ne demandiez explicitement un tel tag.\n\nAvertissement : Pas affilié aux fournisseurs\net pas responsable de leur contenu.",
"Tags →": "Tags →",
"Invalid command.": "Commande invalide.",
"Anime booru": "Booru d'anime",
"Powered by yande.re and konachan": "Propulsé par yande.re et konachan",
"An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.": "Un booru d'images. Peut contenir du contenu NSFW.\nFais attention.\n\nAvertissement : Pas affilié au fournisseur\net pas responsable de son contenu.",
"Lewds": "Contenu osé",
"Shows naughty stuff when enabled": "Affiche du contenu osé lorsqu'il est activé",
"Saves images in folders by their tags": "Enregistre les images dans des dossiers selon leurs tags",
"Message Gemini...": "Envoyer un message à Gemini...",
"Enter Google AI API Key...": "Saisissez la clé API de Google AI...",
"Message the model...": "Envoyer un message au modèle...",
"Enter API Key...": "Saisissez la clé API...",
"Enter tags": "Saisissez les tags",
"Quick scripts": "Scripts rapides",
"Change screen resolution": "Changer la résolution de l'écran",
"Update packages": "Mettre à jour les paquets",
"Trim system generations to 5": "Limiter les générations système à 5",
"Trim home manager generations to 5": "Limiter les générations de home-manager à 5",
"Remove orphan packages": "Supprimer les paquets orphelins",
"Uninstall unused flatpak packages": "Désinstaller les paquets Flatpak inutilisés",
"<span strikethrough=\"true\">Inaccurate</span> Color picker": "<span strikethrough=\"true\">Inexact</span> Sélecteur de couleur",
"Result": "Résultat",
"Type to search": "Tapez pour rechercher",
"illogical-impulse": "illogical-impulse",
"RAM Usage": "Utilisation de la RAM",
"Swap Usage": "Utilisation du swap",
"CPU Usage": "Utilisation du CPU",
"Uptime:": "Temps de fonctionnement :",
"Screen snip": "Capture d'écran",
"Color picker": "Sélecteur de couleur",
"Toggle on-screen keyboard": "Activer/désactiver le clavier virtuel",
"Night Light": "Lumière nocturne",
"Color inversion": "Inversion des couleurs",
"Keep system awake": "Empêcher la mise en veille du système",
"Cloudflare WARP": "Cloudflare WARP",
"Session": "Session",
"Bluetooth | Right-click to configure": "Bluetooth | Clic droit pour configurer",
"Wifi | Right-click to configure": "Wifi | Clic droit pour configurer",
"Right-click to configure": "Clic droit pour configurer",
"Unknown": "Inconnu",
"Reload Environment config": "Recharger la configuration de l'environnement",
"Open Settings": "Ouvrir les paramètres",
"Notifications": "Notifications",
"Audio controls": "Contrôles audio",
"Bluetooth": "Bluetooth",
"Wifi networks": "Réseaux Wifi",
"Quick config": "Configuration",
"Silence": "Silence",
"Clear": "Effacer",
"No notifications": "Aucune notification",
"notifications": "notifications",
"Close": "Fermer",
"Now": "Maintenant",
"Yesterday": "Hier",
"No audio source": "Aucune source audio",
"Remove device": "Retirer l'appareil",
"Connected": "Connecté",
"Paired": "Appairé",
"More": "Plus",
"Selected": "Sélectionné",
"Current network": "Réseau actuel",
"Authentication": "Authentification",
"Effects": "Effets",
"Transparency": "Transparence",
"[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this": "[AGS]\nRendre les éléments de l'interface transparents\nLe flou est également recommandé si vous l'activez.\nChoisissez un fond d'écran avant d'activer cette option.",
"Blur": "Flou",
"[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.": "[Hyprland]\nActiver le flou sur les éléments transparents\nN'affecte pas les performances ou la consommation d'énergie, sauf en cas de fenêtres transparentes.",
"X-ray": "Rayon X",
"[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ": "[Hyprland]\nNe pas afficher ce qui se trouve derrière une fenêtre ou une couche (sauf le fond d'écran) sur sa surface floue\nRecommandé pour améliorer les performances (si vous n'abusez pas de la transparence/flou) ",
"Size": "Taille",
"[Hyprland]\nAdjust the blur radius. Generally doesn't affect performance\nHigher = more color spread": "[Hyprland]\nAjustez le rayon du flou. En général, cela n'affecte pas les performances\nPlus le rayon est grand, plus la diffusion des couleurs est importante",
"Passes": "Passes",
"[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.": "[Hyprland] Ajustez le nombre d'exécutions de l'algorithme de flou\nPlus il y a de passes, plus la diffusion et la consommation d'énergie augmentent\n4 est recommandé\n2 ou moins paraîtraient étranges et 6 ou plus sembleraient médiocres.",
"Animations": "Animations",
"[Hyprland] [GTK]\nEnable animations": "[Hyprland] [GTK]\nActiver les animations",
"Choreography delay": "Délai de chorégraphie",
"In milliseconds, the delay between animations of a series": "En millisecondes, le délai entre les animations d'une série",
"Developer": "Développeur",
"Show FPS": "Afficher les FPS",
"[Hyprland]\nShow FPS overlay on top-left corner": "[Hyprland]\nAfficher une superposition FPS en haut à gauche",
"Log to stdout": "Journaliser vers stdout",
"[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console": "[Hyprland]\nAfficher les messages LOG, ERR, WARN, etc. dans la console",
"Damage tracking": "Suivi des dégâts",
"[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work": "[Hyprland]\nActiver le suivi des dégâts\nEn général, laissez-le activé.\nDésactivez-le uniquement si un shader ne fonctionne pas",
"Damage blink": "Clignotement des dégâts",
"[Hyprland] [Epilepsy warning!]\nShow screen damage flashes": "[Hyprland] [Avertissement épilepsie !]\nAfficher des flashs lors des modifications de l'écran",
"Not all changes are saved": "Tous les changements ne sont pas enregistrés",
"Mo": "Lu",
"Tu": "Ma",
"We": "Me",
"Th": "Je",
"Fr": "Ve",
"Sa": "Sa",
"Su": "Di",
"Calendar": "Calendrier",
"To Do": "À faire",
"Unfinished": "Non terminé",
"Done": "Terminé",
"Finished tasks will go here": "Les tâches terminées apparaîtront ici",
"Nothing here!": "Rien ici !",
"+ New task": "+ Nouvelle tâche",
"Add a task...": "Ajouter une tâche...",
"Color scheme": "Schéma de couleurs",
"Options": "Options",
"Dark Mode": "Mode sombre",
"Ya should go to sleep!": "Tu devrais aller dormir !",
"Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)": "Thématiser les applications GTK avec la couleur d'accent\n(inconvénient : le changement de mode sombre/clair nécessite un redémarrage)",
"Scheme styles": "Styles de schéma",
"Vibrant": "Vibrant",
"Vibrant+": "Vibrant+",
"Expressive": "Expressif",
"Monochrome": "Monochrome",
"Rainbow": "Arc-en-ciel",
"Fidelity": "Fidélité",
"Fruit Salad": "Salade de fruits",
"Tonal Spot": "Tonal Spot",
"Content": "Contenu",
"Use arrow keys to navigate.\nEnter to select, Esc to cancel.": "Utilise les flèches pour naviguer.\nEntrée pour sélectionner, Échap pour annuler.",
"Lock": "Verrouiller",
"Logout": "Se déconnecter",
"Sleep": "Mettre en veille",
"Hibernate": "Hibernation",
"Shutdown": "Éteindre",
"Reboot": "Redémarrer",
"Cancel": "Annuler",
"Cheat sheet": "Aide-mémoire",
"Keybinds": "Raccourcis clavier",
"Periodic table": "Tableau périodique",
"Essentials for beginners": "Notions essentielles pour débutants",
"Make shell elements transparent": "Rendre les éléments de l'interface transparents",
"Actions": "Actions",
"Window management": "Gestion des fenêtres",
"Window arrangement": "Disposition des fenêtres",
"Workspace management": "Gestion des espaces de travail",
"Workspace navigation": "Navigation entre espaces de travail",
"Widgets": "Widgets",
"Media": "Médias",
"Apps": "Applications",
"Neutral": "Neutre",
"Launch foot (terminal)": "Lancer foot (terminal)",
"Open app launcher": "Ouvrir le lanceur d'applications",
"Change wallpaper": "Changer le fond d'écran",
"Clipboard history >> clipboard": "Historique du presse-papiers >> presse-papiers",
"Pick emoji >> clipboard": "Choisir un emoji >> presse-papiers",
"Screen snip >> edit": "Capture d'écran >> éditer",
"Screen snip to text >> clipboard": "Capture d'écran en texte >> presse-papiers",
"Pick color (Hex) >> clipboard": "Choisir une couleur (Hex) >> presse-papiers",
"Screenshot >> clipboard": "Capture d'écran >> presse-papiers",
"Screenshot >> clipboard & file": "Capture d'écran >> presse-papiers & fichier",
"Record region (no sound)": "Enregistrer une région (sans son)",
"Record screen (with sound)": "Enregistrer l'écran (avec son)",
"Suspend system": "Suspendre le système",
"Move focus in direction": "Déplacer le focus dans la direction",
"Move window": "Déplacer la fenêtre",
"Resize window": "Redimensionner la fenêtre",
"Close window": "Fermer la fenêtre",
"Pick and kill a window": "Sélectionner et fermer une fenêtre",
"Window: move in direction": "Fenêtre : déplacer dans la direction",
"Window: split ratio +/- 0.1": "Fenêtre : ajuster le ratio de partage +/- 0.1",
"Float/unfloat window": "Basculer la fenêtre en mode flottant/non flottant",
"Toggle fake fullscreen": "Basculer en faux plein écran",
"Toggle fullscreen": "Basculer le plein écran",
"Toggle maximization": "Basculer la maximisation",
"Focus workspace # (1, 2, 3, 4, ...)": "Sélectionner l'espace de travail n° (1, 2, 3, 4, ...)",
"Workspace: focus left/right": "Espace de travail : se déplacer à gauche/droite",
"Workspace: toggle special": "Espace de travail : basculer spécial",
"Window: move to workspace # (1, 2, 3, 4, ...)": "Fenêtre : déplacer vers l'espace de travail n° (1, 2, 3, 4, ...)",
"Window: move to workspace left/right": "Fenêtre : déplacer vers l'espace de travail à gauche/droite",
"Window: move to workspace special": "Fenêtre : déplacer vers l'espace de travail spécial",
"Window: pin (show on all workspaces)": "Fenêtre : épingler (afficher sur tous les espaces de travail)",
"Restart widgets": "Redémarrer les widgets",
"Cycle bar mode (normal, focus)": "Cycler le mode de la barre (normal, focus)",
"Toggle overview/launcher": "Basculer l'aperçu/le lanceur",
"Show cheatsheet": "Afficher l'aide-mémoire",
"Toggle left sidebar": "Basculer la barre latérale gauche",
"Toggle right sidebar": "Basculer la barre latérale droite",
"Toggle music controls": "Basculer les contrôles de musique",
"View color scheme and options": "Voir le schéma de couleurs et les options",
"Toggle power menu": "Basculer le menu d'alimentation",
"Toggle crosshair": "Basculer le viseur",
"Next track": "Piste suivante",
"Previous track": "Piste précédente",
"Play/pause media": "Lire/metre en pause le média",
"Launch Zed (editor)": "Lancer Zed (éditeur)",
"Launch VSCode (editor)": "Lancer VSCode (éditeur)",
"Launch Nautilus (file manager)": "Lancer Nautilus (gestionnaire de fichiers)",
"Launch Firefox (browser)": "Lancer Firefox (navigateur)",
"Launch GNOME Text Editor": "Lancer GNOME Text Editor",
"Launch WPS Office": "Lancer WPS Office",
"Launch GNOME Settings": "Lancer les paramètres GNOME",
"Launch pavucontrol (volume mixer)": "Lancer pavucontrol (contrôleur de volume)",
"Launch EasyEffects (equalizer & other audio effects)": "Lancer EasyEffects (égaliseur et autres effets audio)",
"Launch GNOME System monitor": "Lancer le moniteur système GNOME",
"Toggle fallback launcher: anyrun": "Basculer le lanceur de secours : anyrun",
"Toggle fallback launcher: fuzzel": "Basculer le lanceur de secours : fuzzel",
"Initialization complete!": "Initialisation terminée !",
"Not found": "Non trouvé :",
"Calling API": "Appel de l'API",
"Downloading image": "Téléchargement de l'image",
"Finished!": "Terminé !",
"Error": "Erreur",
"Not found!": "Non trouvé !",
"Go to file url": "Aller à l'URL du fichier",
"Save image": "Enregistrer l'image",
"Hoard": "Accumuler",
"Open externally": "Ouvrir avec une application externe",
"You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “I'm not sure.”. \nThanks!": "Tu es un assistant dans un panneau latéral d'un bureau Linux sous Wayland. Veilles à toujours adopter un ton décontracté lorsque tu réponds, sauf indication contraire ou lors de suggestions d'écriture. Voici les étapes à suivre pour répondre aux requêtes de l'utilisateur :\n1. S'il s'agit d'une question liée à l'écriture ou à la grammaire, ou d'une phrase entre guillemets, signales les erreurs et corriges-les si nécessaire en utilisant des soulignements, et rends l'écriture plus naturelle lorsque cela est approprié sans apporter de modifications trop importantes. Si on te fournit une phrase entre guillemets mais grammaticalement correcte, expliques brièvement des concepts peu communs.\n2. S'il s'agit d'une question concernant des tâches système, fournis une commande bash dans un bloc de code avec une brève explication.\n3. Sinon, lorsqu'on te demande de résumer des informations ou d'expliquer des concepts, utilises des puces et des titres. Pour les expressions mathématiques, tu *dois* utiliser LaTeX dans un bloc de code avec la langue définie sur \"latex\".\nNote : Utilises un langage décontracté, sois bref, tout en garantissant l'exactitude des informations de ta réponse. Si tu n'es pas sûr ou si tu ne disposes pas d'assez d'informations pour fournir une réponse convaincante, dis simplement \"Je ne sais pas\" ou \"Je ne suis pas sûr\".\nMerci !",
"Feels like": "Ressenti"
}
-238
View File
@@ -1,238 +0,0 @@
{
"No media": "Nessun media",
"Powered by Google": "Offerto da Google",
"Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model.": "Non affiliato, approvato o sponsorizzato da Google.\n\nPrivacy: I messaggi della chat non sono collegati al tuo account,\n ma saranno letti da revisori umani per migliorare il modello.",
"Precise": "Preciso",
"Balanced": "Bilanciato",
"Creative": "Creativo",
"Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "Valore di temperatura di Gemini.\n Preciso = 0\n Bilanciato = 0.5\n Creativo = 1",
"Enhancements": "Miglioramenti",
"Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "Dice a Gemini:\n- È un assistente laterale per Linux\n- Sii breve e usa punti elenco",
"Safety": "Sicurezza",
"When turned off, tells the API (not the model) \nto not block harmful/explicit content": "Quando disattivato, dice all'API (non al modello) \n di non bloccare contenuti dannosi/espliciti",
"History": "Cronologia",
"Saves chat history\nMessages in previous chats won't show automatically, but they are there": "Salva la cronologia della chat\nI messaggi nelle chat precedenti non verranno mostrati automaticamente, ma sono lì",
"Key stored in:": "Chiave memorizzata in:",
"To update this key, type": "Per aggiornare questa chiave, digita",
"Updated API Key at": "Chiave API aggiornata alle",
"Currently using": "Attualmente in uso",
"Select ChatGPT-compatible API provider": "Seleziona un provider API compatibile con ChatGPT",
"Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.": "API ufficiale di OpenAI.\nPrezzi: Gratuito per i primi $5 o 3 mesi, a seconda di quale sia inferiore.",
"Official Ollama API.\nPricing: Free.": "API ufficiale di Ollama.\nPrezzi: Gratuito.",
"A unified interface for LLMs": "Un'interfaccia unificata per LLM",
"An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key": "Un'API di Tornado Softwares\nPrezzi: Gratuito: 100/giorno\nRichiede di unirsi al loro Discord per una chiave",
"An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it's buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key": "Un'API di @zukixa su GitHub.\nNota: Le chiavi sono bloccate per IP, quindi a volte è instabile\nPrezzi: Gratuito: 10/min, 800/giorno.\nRichiede di unirsi al loro Discord per una chiave",
"Provider shown above": "Provider mostrato sopra",
"The model's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "Valore di temperatura del modello.\n Preciso = 0\n Bilanciato = 0.5\n Creativo = 1",
"An API key is required\nYou can grab one <u>here</u>, then enter it below": "È necessaria una chiave API\nPuoi ottenerne una <u>qui</u>, quindi inserirla qui sotto",
"Tells the model:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "Dice al modello:\n- È un assistente laterale per Linux\n- Sii breve e usa punti elenco",
"Powered by waifu.im + other APIs": "Offerto da waifu.im + altre API",
"Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.": "Digita i tag per un'immagine casuale.\nIl contenuto NSFW non verrà restituito a meno che\nnon richiedi esplicitamente un tale tag.\n\nDisclaimer: Non affiliato ai provider\nné responsabile per alcun loro contenuto.",
"Tags →": "Tag →",
"Invalid command.": "Comando non valido.",
"Anime booru": "Anime booru",
"Powered by yande.re and konachan": "Offerto da yande.re e konachan",
"An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.": "Un booru di immagini. Potrebbe contenere contenuti NSFW.\nFai attenzione.\n\nDisclaimer: Non affiliato al provider\nné responsabile per alcun suo contenuto.",
"Lewds": "Contenuti osé",
"Shows naughty stuff when enabled": "Mostra contenuti osé quando abilitato",
"Saves images in folders by their tags": "Salva le immagini in cartelle in base ai loro tag",
"Message Gemini...": "Messaggia Gemini...",
"Enter Google AI API Key...": "Inserisci la chiave API di Google AI...",
"Message the model...": "Messaggia il modello...",
"Enter API Key...": "Inserisci la chiave API...",
"Enter tags": "Inserisci i tag",
"Quick scripts": "Script rapidi",
"Change screen resolution": "Cambia risoluzione dello schermo",
"Update packages": "Aggiorna i pacchetti",
"Trim system generations to 5": "Riduci le generazioni del sistema a 5",
"Trim home manager generations to 5": "Riduci le generazioni del gestore di casa a 5",
"Remove orphan packages": "Rimuovi i pacchetti orfani",
"Uninstall unused flatpak packages": "Disinstalla i pacchetti flatpak non utilizzati",
"<span strikethrough=\"true\">Inaccurate</span> Color picker": "<span strikethrough=\"true\">Impreciso</span> Selettore di colore",
"Result": "Risultato",
"Type to search": "Digita per cercare",
"illogical-impulse": "impulso illogico",
"RAM Usage": "Utilizzo della RAM",
"Swap Usage": "Utilizzo dello Swap",
"CPU Usage": "Utilizzo della CPU",
"Uptime:": "Tempo di attività:",
"Screen snip": "Ritaglio dello schermo",
"Color picker": "Selettore di colore",
"Toggle on-screen keyboard": "Attiva/disattiva tastiera su schermo",
"Night Light": "Luce notturna",
"Color inversion": "Inversione di colore",
"Keep system awake": "Mantieni il sistema sveglio",
"Cloudflare WARP": "Cloudflare WARP",
"Session": "Sessione",
"Bluetooth | Right-click to configure": "Bluetooth | Clic destro per configurare",
"Wifi | Right-click to configure": "Wifi | Clic destro per configurare",
"Right-click to configure": "Clic destro per configurare",
"Unknown": "Sconosciuto",
"Reload Environment config": "Ricarica la configurazione dell'ambiente",
"Open Settings": "Apri Impostazioni",
"Notifications": "Notifiche",
"Audio controls": "Controlli audio",
"Bluetooth": "Bluetooth",
"Wifi networks": "Reti Wifi",
"Quick config": "Configurazione",
"Silence": "Silenzio",
"Clear": "Cancella",
"No notifications": "Nessuna notifica",
"notifications": "notifiche",
"Close": "Chiudi",
"Now": "Ora",
"Yesterday": "Ieri",
"No audio source": "Nessuna sorgente audio",
"Remove device": "Rimuovi dispositivo",
"Connected": "Connesso",
"Paired": "Abbinato",
"More": "Altro",
"Selected": "Selezionato",
"Current network": "Rete corrente",
"Authentication": "Autenticazione",
"Effects": "Effetti",
"Transparency": "Trasparenza",
"[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this": "[AGS]\nRendi trasparenti gli elementi della shell\nSi consiglia anche il blur se abiliti questa opzione",
"Blur": "Blur",
"[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.": "[Hyprland]\nAbilita il blur sugli elementi trasparenti\nNon influisce sulle prestazioni/consumo energetico a meno che non si abbiano finestre trasparenti.",
"X-ray": "Raggi X",
"[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ": "[Hyprland]\nRendi tutto dietro una finestra/livello, tranne lo sfondo, non renderizzato sulla sua superficie sfocata\nConsigliato per migliorare le prestazioni (se non abusi di trasparenza/blur) ",
"Size": "Dimensione",
"[Hyprland]\nAdjust the blur radius. Generally doesn't affect performance\nHigher = more color spread": "[Hyprland]\nRegola il raggio del blur. Generalmente non influisce sulle prestazioni\nPiù alto = più diffusione del colore",
"Passes": "Passaggi",
"[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.": "[Hyprland] Regola il numero di esecuzioni dell'algoritmo di blur\nPiù passaggi = più diffusione e consumo energetico\nSi consigliano 4\n2- sembrerebbe strano e 6+ sembrerebbe scadente.",
"Animations": "Animazioni",
"[Hyprland] [GTK]\nEnable animations": "[Hyprland] [GTK]\nAbilita le animazioni",
"Choreography delay": "Ritardo della coreografia",
"In milliseconds, the delay between animations of a series": "In millisecondi, il ritardo tra le animazioni di una serie",
"Developer": "Sviluppatore",
"Show FPS": "Mostra FPS",
"[Hyprland]\nShow FPS overlay on top-left corner": "[Hyprland]\nMostra l'overlay degli FPS nell'angolo in alto a sinistra",
"Log to stdout": "Log su stdout",
"[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console": "[Hyprland]\nStampa i messaggi LOG, ERR, WARN, ecc. sulla console",
"Damage tracking": "Tracciamento dei danni",
"[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work": "[Hyprland]\nAbilita il tracciamento dei danni\nGeneralmente, lascialo acceso.\nDisattivalo solo quando uno shader non funziona",
"Damage blink": "Lampeggio dei danni",
"[Hyprland] [Epilepsy warning!]\nShow screen damage flashes": "[Hyprland] [Avviso epilessia!]\nMostra i lampeggi dei danni dello schermo",
"Not all changes are saved": "Non tutte le modifiche sono state salvate",
"Mo": "Lu",
"Tu": "Ma",
"We": "Me",
"Th": "Gi",
"Fr": "Ve",
"Sa": "Sa",
"Su": "Do",
"Calendar": "Calendario",
"To Do": "Da fare",
"Unfinished": "Incompleto",
"Done": "Fatto",
"Finished tasks will go here": "Le attività completate andranno qui",
"Nothing here!": "Niente qui!",
"+ New task": "+ Nuova attività",
"Add a task...": "Aggiungi un'attività...",
"Color scheme": "Schema di colori",
"Options": "Opzioni",
"Dark Mode": "Modalità scura",
"Ya should go to sleep!": "Dovresti andare a dormire!",
"Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)": "Tema le app GTK usando il colore di accento\n(svantaggio: il passaggio tra modalità scura/chiara richiede un riavvio)",
"Scheme styles": "Stili dello schema",
"Vibrant": "Vivace",
"Vibrant+": "Vivace+",
"Expressive": "Espressivo",
"Monochrome": "Monocromatico",
"Rainbow": "Arcobaleno",
"Fidelity": "Fedeltà",
"Fruit Salad": "Macedonia di frutta",
"Tonal Spot": "Punto tonale",
"Content": "Contenuto",
"Use arrow keys to navigate.\nEnter to select, Esc to cancel.": "Usa i tasti freccia per navigare.\nInvio per selezionare, Esc per annullare.",
"Lock": "Blocca",
"Logout": "Esci",
"Sleep": "Sospendi",
"Hibernate": "Iberna",
"Shutdown": "Spegni",
"Reboot": "Riavvia",
"Cancel": "Annulla",
"Cheat sheet": "Foglio di riferimento",
"Keybinds": "Scorciatoie da tastiera",
"Periodic table": "Tavola periodica",
"Essentials for beginners": "Essenziali per principianti",
"Make shell elements transparent": "Rendi trasparenti gli elementi della shell",
"Actions": "Azioni",
"Window management": "Gestione delle finestre",
"Window arrangement": "Disposizione delle finestre",
"Workspace management": "Gestione degli spazi di lavoro",
"Workspace navigation": "Navigazione degli spazi di lavoro",
"Widgets": "Widget",
"Media": "Media",
"Apps": "App",
"Neutral": "Neutro",
"Launch foot (terminal)": "Avvia foot (terminale)",
"Open app launcher": "Apri il launcher delle app",
"Change wallpaper": "Cambia lo sfondo",
"Clipboard history >> clipboard": "Cronologia degli appunti >> appunti",
"Pick emoji >> clipboard": "Scegli emoji >> appunti",
"Screen snip >> edit": "Ritaglio dello schermo >> modifica",
"Screen snip to text >> clipboard": "Ritaglio dello schermo in testo >> appunti",
"Pick color (Hex) >> clipboard": "Scegli colore (Hex) >> appunti",
"Screenshot >> clipboard": "Screenshot >> appunti",
"Screenshot >> clipboard & file": "Screenshot >> appunti e file",
"Record region (no sound)": "Registra regione (senza audio)",
"Record screen (with sound)": "Registra schermo (con audio)",
"Suspend system": "Sospendi il sistema",
"Move focus in direction": "Sposta il focus nella direzione",
"Move window": "Sposta la finestra",
"Resize window": "Ridimensiona la finestra",
"Close window": "Chiudi la finestra",
"Pick and kill a window": "Scegli e chiudi una finestra",
"Window: move in direction": "Finestra: sposta nella direzione",
"Window: split ratio +/- 0.1": "Finestra: rapporto di divisione +/- 0.1",
"Float/unfloat window": "Finestra libera/agganciata",
"Toggle fake fullscreen": "Attiva/disattiva falso fullscreen",
"Toggle fullscreen": "Attiva/disattiva fullscreen",
"Toggle maximization": "Attiva/disattiva massimizzazione",
"Focus workspace # (1, 2, 3, 4, ...)": "Focalizza spazio di lavoro # (1, 2, 3, 4, ...)",
"Workspace: focus left/right": "Spazio di lavoro: focalizza sinistra/destra",
"Workspace: toggle special": "Spazio di lavoro: attiva/disattiva speciale",
"Window: move to workspace # (1, 2, 3, 4, ...)": "Finestra: sposta in spazio di lavoro # (1, 2, 3, 4, ...)",
"Window: move to workspace left/right": "Finestra: sposta in spazio di lavoro sinistra/destra",
"Window: move to workspace special": "Finestra: sposta in spazio di lavoro speciale",
"Window: pin (show on all workspaces)": "Finestra: fissa (mostra su tutti gli spazi di lavoro)",
"Restart widgets": "Riavvia widget",
"Cycle bar mode (normal, focus)": "Cicla modalità barra (normale, focus)",
"Toggle overview/launcher": "Attiva/disattiva panoramica/lanciatore",
"Show cheatsheet": "Mostra cheatsheet",
"Toggle left sidebar": "Attiva/disattiva barra laterale sinistra",
"Toggle right sidebar": "Attiva/disattiva barra laterale destra",
"Toggle music controls": "Attiva/disattiva controlli musicali",
"View color scheme and options": "Visualizza schema colori e opzioni",
"Toggle power menu": "Attiva/disattiva menu di spegnimento",
"Toggle crosshair": "Attiva/disattiva mirino",
"Next track": "Traccia successiva",
"Previous track": "Traccia precedente",
"Play/pause media": "Riproduci/pausa media",
"Launch Zed (editor)": "Avvia Zed (editor)",
"Launch VSCode (editor)": "Avvia VSCode (editor)",
"Launch Nautilus (file manager)": "Avvia Nautilus (gestore file)",
"Launch Firefox (browser)": "Avvia Firefox (browser)",
"Launch GNOME Text Editor": "Avvia GNOME Text Editor",
"Launch WPS Office": "Avvia WPS Office",
"Launch GNOME Settings": "Avvia Impostazioni GNOME",
"Launch pavucontrol (volume mixer)": "Avvia pavucontrol (mixer volume)",
"Launch EasyEffects (equalizer & other audio effects)": "Avvia EasyEffects (equalizzatore & altri effetti audio)",
"Launch GNOME System monitor": "Avvia monitor di sistema GNOME",
"Toggle fallback launcher: anyrun": "Attiva/disattiva lanciatore di riserva: anyrun",
"Toggle fallback launcher: fuzzel": "Attiva/disattiva lanciatore di riserva: fuzzel",
"Initialization complete!": "Inizializzazione completata!",
"Not found": "Non trovato",
"Calling API": "Chiamata API",
"Downloading image": "Scaricamento immagine",
"Finished!": "Completato!",
"Error": "Errore",
"Not found!": "Non trovato!",
"Go to file url": "Vai all'url del file",
"Save image": "Salva immagine",
"Hoard": "Accumula",
"Open externally": "Apri esternamente",
"You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “Im not sure.”. \nThanks!": "Sei un assistente nella barra laterale di un desktop Wayland Linux. Usa sempre un tono informale quando rispondi alle domande, a meno che non venga richiesto diversamente o quando fai suggerimenti di scrittura. Ecco i passaggi che devi seguire per rispondere alle domande dell'utente:\n1. Se si tratta di una domanda sulla scrittura o sulla grammatica o di una frase tra virgolette, segnala gli errori e correggi quando necessario utilizzando sottolineature, e rendi la scrittura più naturale dove appropriato senza fare modifiche troppo importanti. Se ti viene data una frase tra virgolette ma è grammaticalmente corretta, spiega brevemente concetti che sono poco comuni.\n2. Se è una domanda su attività di sistema, fornisci un comando bash in un blocco di codice con una breve spiegazione.\n3. Altrimenti, quando ti viene chiesto di riassumere informazioni o spiegare concetti, usa punti elenco e titoli. Per espressioni matematiche, *devi* usare LaTeX all'interno di un blocco di codice con il linguaggio impostato su \"latex\". \nNota: usa un linguaggio informale, sii breve, assicurandoti della correttezza fattuale della tua risposta. Se non sei sicuro o non hai abbastanza informazioni per fornire una risposta sicura, semplicemente dici “Non lo so” o “Non sono sicuro”. \nGrazie!"
}
-239
View File
@@ -1,239 +0,0 @@
{
"No media": "无媒体活动",
"Powered by Google": "由 Google 提供技术支持",
"Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model.": "不隶属于、不受 Google 赞助或支持。\n\n隐私:聊天信息不会与你的账户关联,\n但会被人类审阅者阅读,用于改进模型。",
"Precise": "精确",
"Balanced": "平衡",
"Creative": "创意",
"Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "Gemini 的 temperature 值\n 精确 = 0\n 平衡 = 0.5\n 创意 = 1",
"Enhancements": "增强功能",
"Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "告诉 Gemini\n- 它是一个 Linux 侧边栏助手\n- 保持简洁并使用项目符号",
"Safety": "安全",
"When turned off, tells the API (not the model) \nto not block harmful/explicit content": "当关闭时,告诉 API(而不是模型)\n不要屏蔽有害/显露的内容",
"History": "历史",
"Saves chat history\nMessages in previous chats won't show automatically, but they are there": "保存聊天历史\n以前聊天中的消息不会自动显示,但它们仍然存在",
"Key stored in:": "密钥值储存在:",
"To update this key, type": "要更新此密钥,请输入",
"Updated API Key at": "更新了 API 密钥于",
"Currently using": "当前使用",
"Select ChatGPT-compatible API provider": "选择与 ChatGPT 兼容的 API 提供商",
"Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.": "官方 OpenAI API。\n定价:前 $5 或前 3 个月免费,取较小者。",
"Official Ollama API.\nPricing: Free.": "官方 Ollama API。\n定价:免费。",
"A unified interface for LLMs": "LLM 的统一接口",
"An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key": "来自 Tornado Softwares 的 API\n定价:免费:每天 100 次请求\n需要加入他们的 Discord 以获取密钥",
"An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it's buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key": "来自 GitHub 上的 @zukixa 的 API。\n注意:密钥与 IP 绑定,所以有时会出错。\n定价:免费:每分钟 10 次,每天 800 次。\n需要加入他们的 Discord 才能获得密钥。",
"Provider shown above": "上述显示的提供商",
"The model's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1": "模型的 temperature 值。\n 精确 = 0\n 平衡 = 0.5\n 创意 = 1",
"An API key is required\nYou can grab one <u>here</u>, then enter it below": "需要 API 密钥\n您可以在<u>这里</u>获取一个,然后在下面输入",
"Tells the model:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points": "告诉模型:\n- 它是一个 Linux 侧边栏助手\n- 保持简洁并使用项目符号",
"Powered by waifu.im + other APIs": "由 waifu.im + 其他 API 提供支持",
"Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.": "输入标签以获取随机图片。\n除非您明确请求,否则不会返回 NSFW 内容。\n\n免责声明:与提供商无关联\n我也不对他们的任何内容负责。",
"Tags →": "标签 →",
"Invalid command.": "无效命令。",
"Anime booru": "动漫图库",
"Powered by yande.re and konachan": "由 yande.re 和 konachan 提供支持",
"An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.": "一个图片图库。可能包含 NSFW 内容。\n小心。\n\n免责声明:与提供商无关联\n也不对它的任何内容负责。",
"Lewds": "不雅内容",
"Shows naughty stuff when enabled": "启用时显示不雅内容",
"Saves images in folders by their tags": "按标签将图片保存到文件夹中",
"Message Gemini...": "向 Gemini 发送消息...",
"Enter Google AI API Key...": "输入 Google AI API 密钥...",
"Message the model...": "向模型发送消息...",
"Enter API Key...": "输入 API 密钥...",
"Enter tags": "输入标签",
"Quick scripts": "快速脚本",
"Change screen resolution": "更改屏幕分辨率",
"Update packages": "更新软件包",
"Trim system generations to 5": "将系统代数修剪为 5",
"Trim home manager generations to 5": "将 home manager 代数修剪为 5",
"Remove orphan packages": "移除孤立软件包",
"Uninstall unused flatpak packages": "卸载未使用的 Flatpak 软件包",
"<span strikethrough=\"true\">Inaccurate</span> Color picker": "<span strikethrough=\"true\">不准确</span> 颜色选择器",
"Result": "结果",
"Type to search": "输入以搜索",
"illogical-impulse": "illogical-impulse",
"RAM Usage": "RAM 使用情况",
"Swap Usage": "Swap 使用情况",
"CPU Usage": "CPU 使用情况",
"Uptime:": "运行时间:",
"Screen snip": "屏幕截图",
"Color picker": "颜色选择器",
"Toggle on-screen keyboard": "切换屏幕键盘",
"Night Light": "夜灯",
"Color inversion": "颜色反转",
"Keep system awake": "保持系统唤醒",
"Cloudflare WARP": "Cloudflare WARP",
"Session": "会话",
"Bluetooth | Right-click to configure": "蓝牙 | 右键单击以配置",
"Wifi | Right-click to configure": "Wi-Fi | 右键单击以配置",
"Right-click to configure": "右键单击以配置",
"Unknown": "未知",
"Reload Environment config": "重新加载环境配置",
"Open Settings": "打开设置",
"Notifications": "通知",
"Audio controls": "音频控制",
"Bluetooth": "蓝牙",
"Wifi networks": "Wi-Fi 网络",
"Quick config": "实时配置",
"Silence": "静音",
"Clear": "清除",
"No notifications": "无通知",
"notifications": "条通知",
"Close": "关闭",
"Now": "现在",
"Yesterday": "昨天",
"No audio source": "没有音频源",
"Remove device": "移除设备",
"Connected": "已连接",
"Paired": "已配对",
"More": "更多",
"Selected": "已选中",
"Current network": "当前网络",
"Authentication": "身份验证",
"Effects": "效果",
"Transparency": "透明度",
"[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this": "[AGS]\n使外壳元素透明\n如果启用此功能,也建议使用模糊效果",
"Blur": "模糊",
"[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.": "[Hyprland]\n在透明元素上启用模糊效果\n除非您有透明窗口,否则不会影响性能/功耗。",
"X-ray": "X-ray",
"[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ": "[Hyprland]\n使窗口/图层后面的所有内容(除了壁纸)在其模糊表面上不渲染\n建议提高性能(如果您不滥用透明度/模糊)",
"Size": "大小",
"[Hyprland]\nAdjust the blur radius. Generally doesn't affect performance\nHigher = more color spread": "[Hyprland]\n调整模糊半径。通常不会影响性能\n数值越高 = 颜色扩散越大",
"Passes": "次数",
"[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.": "[Hyprland]\n调整模糊算法的运行次数\n次数越多 = 扩散越大,功耗越高\n建议使用 4 次\n2 次看起来很奇怪,6 次以上看起来很糟糕。",
"Animations": "动画",
"[Hyprland] [GTK]\nEnable animations": "[Hyprland] [GTK]\n启用动画",
"Choreography delay": "间隔",
"In milliseconds, the delay between animations of a series": "以毫秒为单位,一系列动画之间的延迟",
"Developer": "开发者选项",
"Show FPS": "显示 FPS",
"[Hyprland]\nShow FPS overlay on top-left corner": "[Hyprland]\n在左上角显示 FPS 叠加层",
"Log to stdout": "输出日志",
"[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console": "[Hyprland]\n将 LOG、ERR、WARN 等消息打印到控制台",
"Damage tracking": "Damage tracking",
"[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work": "[Hyprland]\n启用 Damage tracking \n通常情况下,保持启用状态\n仅当着色器无法正常工作时才禁用",
"Damage blink": "显示视图更新",
"[Hyprland] [Epilepsy warning!]\nShow screen damage flashes": "[Hyprland] [癫痫警告!]\n屏幕视图更新时闪烁",
"Not all changes are saved": "并非所有更改都已保存",
"Mo": "一",
"Tu": "二",
"We": "三",
"Th": "四",
"Fr": "五",
"Sa": "六",
"Su": "日",
"Calendar": "日历",
"To Do": "待办",
"Unfinished": "未完成",
"Done": "已完成",
"Finished tasks will go here": "已完成的任务将显示在此处",
"Nothing here!": "这里什么也没有!",
"+ New task": "+ 新任务",
"Add a task...": "添加任务...",
"Color scheme": "配色方案",
"Options": "选项",
"Dark Mode": "深色模式",
"Ya should go to sleep!": "你应该去睡觉!",
"Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)": "使用强调色对 GTK 应用程序进行主题化\n(缺点:深色/浅色模式切换需要重启)",
"Scheme styles": "样式方案",
"Vibrant": "鲜艳",
"Vibrant+": "鲜艳+",
"Expressive": "表现力",
"Monochrome": "黑白",
"Rainbow": "彩虹",
"Fidelity": "保真度",
"Fruit Salad": "水果沙拉",
"Tonal Spot": "色调点",
"Content": "内容",
"Use arrow keys to navigate.\nEnter to select, Esc to cancel.": "使用箭头键导航。\n回车键选择,Esc 键取消。",
"Lock": "锁屏",
"Logout": "注销",
"Sleep": "睡眠",
"Hibernate": "休眠",
"Shutdown": "关机",
"Reboot": "重启",
"Cancel": "取消",
"Cheat sheet": "备忘单",
"Keybinds": "按键绑定",
"Periodic table": "元素周期表",
"Essentials for beginners": "初学者必备",
"Make shell elements transparent": "使外壳元素透明",
"Actions": "操作",
"Window management": "窗口管理",
"Window arrangement": "窗口排列",
"Workspace management": "工作区管理",
"Workspace navigation": "工作区导航",
"Widgets": "小部件",
"Media": "媒体",
"Apps": "应用程序",
"Neutral": "中性",
"Launch foot (terminal)": "启动终端(foot",
"Open app launcher": "打开应用程序启动器",
"Change wallpaper": "更改壁纸",
"Clipboard history >> clipboard": "剪贴板历史 >> 剪贴板",
"Pick emoji >> clipboard": "选择表情符号 >> 剪贴板",
"Screen snip >> edit": "屏幕截图 >> 编辑",
"Screen snip to text >> clipboard": "屏幕截图转文字 >> 剪贴板",
"Pick color (Hex) >> clipboard": "选择颜色(十六进制)>> 剪贴板",
"Screenshot >> clipboard": "屏幕截图 >> 剪贴板",
"Screenshot >> clipboard & file": "屏幕截图 >> 剪贴板和文件",
"Record region (no sound)": "录制区域(无声音)",
"Record screen (with sound)": "录制屏幕(带声音)",
"Suspend system": "挂起系统",
"Move focus in direction": "在方向上移动焦点",
"Move window": "移动窗口",
"Resize window": "调整窗口大小",
"Close window": "关闭窗口",
"Pick and kill a window": "选择并关闭一个窗口",
"Window: move in direction": "窗口:在方向上移动",
"Window: split ratio +/- 0.1": "窗口:分割比例 +/- 0.1",
"Float/unfloat window": "浮动/取消浮动窗口",
"Toggle fake fullscreen": "切换伪全屏",
"Toggle fullscreen": "切换全屏",
"Toggle maximization": "切换最大化",
"Focus workspace # (1, 2, 3, 4, ...)": "聚焦工作区 #1, 2, 3, 4, ...",
"Workspace: focus left/right": "工作区:聚焦左右",
"Workspace: toggle special": "工作区:切换特殊工作区",
"Window: move to workspace # (1, 2, 3, 4, ...)": "窗口:移动到工作区 #1, 2, 3, 4, ...",
"Window: move to workspace left/right": "窗口:移动到左右工作区",
"Window: move to workspace special": "窗口:移动到特殊工作区",
"Window: pin (show on all workspaces)": "窗口:固定(在所有工作区显示)",
"Restart widgets": "重启小部件",
"Cycle bar mode (normal, focus)": "循环栏模式(正常,聚焦)",
"Toggle overview/launcher": "切换概览/启动器",
"Show cheatsheet": "显示快捷键表",
"Toggle left sidebar": "切换左侧边栏",
"Toggle right sidebar": "切换右侧边栏",
"Toggle music controls": "切换音乐控制",
"View color scheme and options": "查看配色方案和选项",
"Toggle power menu": "切换电源菜单",
"Toggle crosshair": "切换准星",
"Next track": "下一曲目",
"Previous track": "上一曲目",
"Play/pause media": "播放/暂停媒体",
"Launch Zed (editor)": "启动 Zed(编辑器)",
"Launch VSCode (editor)": "启动 VSCode(编辑器)",
"Launch Nautilus (file manager)": "启动 Nautilus(文件管理器)",
"Launch Firefox (browser)": "启动 Firefox(浏览器)",
"Launch GNOME Text Editor": "启动 GNOME 文本编辑器",
"Launch WPS Office": "启动 WPS 办公软件",
"Launch GNOME Settings": "启动 GNOME 设置",
"Launch pavucontrol (volume mixer)": "启动 pavucontrol(音量混合器)",
"Launch EasyEffects (equalizer & other audio effects)": "启动 EasyEffects(均衡器和其他音频效果)",
"Launch GNOME System monitor": "启动 GNOME 系统监视器",
"Toggle fallback launcher: anyrun": "切换备用启动器:anyrun",
"Toggle fallback launcher: fuzzel": "切换备用启动器:fuzzel",
"Initialization complete!": "初始化完成!",
"Not found": "未找到",
"Calling API": "调用 API",
"Downloading image": "正在下载图片",
"Finished!": "完成!",
"Error": "错误",
"Not found!": "未找到!",
"Go to file url": "前往文件链接",
"Save image": "保存图片",
"Hoard": "保存",
"Open externally": "在外部打开",
"You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with brief explanation.\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\". \nNote: Use casual language, be short, while ensuring the factual correctness of your response. If you are unsure or dont have enough information to provide a confident answer, simply say “I dont know” or “Im not sure.”. \nThanks!": "你是 Wayland Linux 桌面侧边栏上的助手。除非有其他要求或提供建议,否则请始终保持轻松的语气回答问题。这是你回答用户查询的步骤:\n1. 如果是写作或语法相关的问题,或者引号中的句子,请指出错误并在必要时进行更正,使用下划线,并在适当的地方使写作更自然,不要进行太大更改。如果你给出的句子在引号中但语法正确,请简要解释不常见概念。\n2. 如果是关于系统任务的问题,请给出bash命令,并在代码块中简要说明。\n3. 否则,在总结信息或解释概念时,你应该使用项目符号和标题。对于数学表达式,你必须在代码块中使用 LaTeX,并将语言设置为\"latex\"。\n注意:使用轻松的语言,简洁,同时确保回答的事实正确性。如果你不确定或没有足够的信息来提供自信的答案,只需说“我不知道”或“我不确定”。\n谢谢!",
"Feels like": "体感温度"
}
-53
View File
@@ -1,53 +0,0 @@
const { Gio, GLib } = imports.gi;
import GtkSource from "gi://GtkSource?version=3.0";
import App from 'resource:///com/github/Aylur/ags/app.js'
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
import { darkMode } from './modules/.miscutils/system.js';
const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/assets/themes/sourceviewtheme${darkMode.value ? '' : '-light'}.xml`;
export const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
function loadSourceViewColorScheme(filePath) {
// Read the XML file content
const file = Gio.File.new_for_path(filePath);
const [success, contents] = file.load_contents(null);
if (!success) {
logError('Failed to load the XML file.');
return;
}
// Parse the XML content and set the Style Scheme
const schemeManager = GtkSource.StyleSchemeManager.get_default();
schemeManager.append_search_path(file.get_parent().get_path());
}
globalThis['handleStyles'] = (resetMusic) => {
// Reset
Utils.exec(`mkdir -p "${GLib.get_user_state_dir()}/ags/scss"`);
if (resetMusic) {
Utils.exec(`bash -c 'echo "" > ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`); // reset music styles
Utils.exec(`bash -c 'echo "" > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss'`); // reset music styles
}
// Generate overrides
let lightdark = darkMode.value ? "dark" : "light";
Utils.writeFileSync(
`
@mixin symbolic-icon {
-gtk-icon-theme: '${userOptions.icons.symbolicIconTheme[lightdark]}';
}
`,
`${GLib.get_user_state_dir()}/ags/scss/_lib_mixins_overrides.scss`)
// Compile and apply
async function applyStyle() {
Utils.exec(`mkdir -p ${COMPILED_STYLE_DIR}`);
Utils.exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/main.scss" "${COMPILED_STYLE_DIR}/style.css"`);
App.resetCss();
App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
console.log('[LOG] Styles loaded')
}
applyStyle().then(() => {
loadSourceViewColorScheme(CUSTOM_SOURCEVIEW_SCHEME_PATH);
}).catch(print);
}
@@ -1,33 +0,0 @@
const { Gdk } = imports.gi;
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
export let monitors;
// Mixes with Gdk monitor size cuz it reports monitor size scaled
async function updateStuff() {
monitors = JSON.parse(exec('hyprctl monitors -j'))
const display = Gdk.Display.get_default();
monitors.forEach((monitor, i) => {
const gdkMonitor = display.get_monitor(i);
monitor.realWidth = monitor.width;
monitor.realHeight = monitor.height;
if (userOptions.monitors.scaleMethod.toLowerCase == "gdk") {
monitor.width = gdkMonitor.get_geometry().width;
monitor.height = gdkMonitor.get_geometry().height;
}
else { // == "division"
if (monitor.transform % 2 == 1) { // Vertical monitors (or horizontal monitor that's vertical by default...)
monitor.width = Math.floor(monitor.realHeight / monitor.scale);
monitor.height = Math.floor(monitor.realWidth / monitor.scale);
}
else {
monitor.width = Math.ceil(monitor.realWidth / monitor.scale);
monitor.height = Math.ceil(monitor.realHeight / monitor.scale);
}
}
});
}
updateStuff().catch(print);
-14
View File
@@ -1,14 +0,0 @@
export const quotes = [
{
quote: 'Nvidia, fuck you',
author: 'Linus Torvalds',
},
{
quote: 'reproducible system? cock and vagina?',
author: 'vaxry',
},
{
quote: "haha pointers hee hee i love pointe-\\\nProcess Vaxry exited with signal SIGSEGV",
author: 'vaxry',
}
];
@@ -1,94 +0,0 @@
export const WWO_CODE = {
"113": "Sunny",
"116": "PartlyCloudy",
"119": "Cloudy",
"122": "VeryCloudy",
"143": "Fog",
"176": "LightShowers",
"179": "LightSleetShowers",
"182": "LightSleet",
"185": "LightSleet",
"200": "ThunderyShowers",
"227": "LightSnow",
"230": "HeavySnow",
"248": "Fog",
"260": "Fog",
"263": "LightShowers",
"266": "LightRain",
"281": "LightSleet",
"284": "LightSleet",
"293": "LightRain",
"296": "LightRain",
"299": "HeavyShowers",
"302": "HeavyRain",
"305": "HeavyShowers",
"308": "HeavyRain",
"311": "LightSleet",
"314": "LightSleet",
"317": "LightSleet",
"320": "LightSnow",
"323": "LightSnowShowers",
"326": "LightSnowShowers",
"329": "HeavySnow",
"332": "HeavySnow",
"335": "HeavySnowShowers",
"338": "HeavySnow",
"350": "LightSleet",
"353": "LightShowers",
"356": "HeavyShowers",
"359": "HeavyRain",
"362": "LightSleetShowers",
"365": "LightSleetShowers",
"368": "LightSnowShowers",
"371": "HeavySnowShowers",
"374": "LightSleetShowers",
"377": "LightSleet",
"386": "ThunderyShowers",
"389": "ThunderyHeavyRain",
"392": "ThunderySnowShowers",
"395": "HeavySnowShowers",
}
export const WEATHER_SYMBOL = {
"Unknown": "air",
"Cloudy": "cloud",
"Fog": "foggy",
"HeavyRain": "rainy",
"HeavyShowers": "rainy",
"HeavySnow": "snowing",
"HeavySnowShowers": "snowing",
"LightRain": "rainy",
"LightShowers": "rainy",
"LightSleet": "rainy",
"LightSleetShowers": "rainy",
"LightSnow": "cloudy_snowing",
"LightSnowShowers": "cloudy_snowing",
"PartlyCloudy": "partly_cloudy_day",
"Sunny": "clear_day",
"ThunderyHeavyRain": "thunderstorm",
"ThunderyShowers": "thunderstorm",
"ThunderySnowShowers": "thunderstorm",
"VeryCloudy": "cloud",
}
export const NIGHT_WEATHER_SYMBOL = {
"Unknown": "air",
"Cloudy": "cloud",
"Fog": "foggy",
"HeavyRain": "rainy",
"HeavyShowers": "rainy",
"HeavySnow": "snowing",
"HeavySnowShowers": "snowing",
"LightRain": "rainy",
"LightShowers": "rainy",
"LightSleet": "rainy",
"LightSleetShowers": "rainy",
"LightSnow": "cloudy_snowing",
"LightSnowShowers": "cloudy_snowing",
"PartlyCloudy": "partly_cloudy_night",
"Sunny": "clear_night",
"ThunderyHeavyRain": "thunderstorm",
"ThunderyShowers": "thunderstorm",
"ThunderySnowShowers": "thunderstorm",
"VeryCloudy": "cloud",
}
@@ -1,106 +0,0 @@
const { Gtk } = imports.gi;
const Lang = imports.lang;
import Widget from 'resource:///com/github/Aylur/ags/widget.js'
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
// -- Styling --
// min-height for diameter
// min-width for trough stroke
// padding for space between trough and progress
// margin for space between widget and parent
// background-color for trough color
// color for progress color
// -- Usage --
// font size for progress value (0-100px) (hacky i know, but i want animations)
export const AnimatedCircProg = ({
initFrom = 0,
initTo = 0,
initAnimTime = 2900,
initAnimPoints = 1,
extraSetup = () => { },
...rest
}) => Widget.DrawingArea({
...rest,
css: `${initFrom != initTo ? 'font-size: ' + initFrom + 'px; transition: ' + initAnimTime + 'ms linear;' : ''}`,
setup: (area) => {
const styleContext = area.get_style_context();
const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
area.connect('draw', Lang.bind(area, (area, cr) => {
const styleContext = area.get_style_context();
const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
const progressValue = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100.0;
const bg_stroke = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const fg_stroke = bg_stroke - padding;
const radius = Math.min(width, height) / 2.0 - Math.max(bg_stroke, fg_stroke) / 2.0;
const center_x = width / 2.0 + marginLeft;
const center_y = height / 2.0 + marginTop;
const start_angle = -Math.PI / 2.0;
const end_angle = start_angle + (2 * Math.PI * progressValue);
const start_x = center_x + Math.cos(start_angle) * radius;
const start_y = center_y + Math.sin(start_angle) * radius;
const end_x = center_x + Math.cos(end_angle) * radius;
const end_y = center_y + Math.sin(end_angle) * radius;
// Draw background
const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
cr.setLineWidth(bg_stroke);
cr.stroke();
if (progressValue == 0) return;
// Draw progress
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
cr.arc(center_x, center_y, radius, start_angle, end_angle);
cr.setLineWidth(fg_stroke);
cr.stroke();
// Draw rounded ends for progress arcs
cr.setLineWidth(0);
cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
cr.fill();
cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
cr.fill();
}));
// Init animation
if (initFrom != initTo) {
area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
Utils.timeout(20, () => {
area.css = `font-size: ${initTo}px;`;
}, area)
const transitionDistance = initTo - initFrom;
const oneStep = initAnimTime / initAnimPoints;
area.css = `
font-size: ${initFrom}px;
transition: ${oneStep}ms linear;
`;
for (let i = 0; i < initAnimPoints; i++) {
Utils.timeout(Math.max(10, i * oneStep), () => {
if(!area) return;
area.css = `${initFrom != initTo ? 'font-size: ' + (initFrom + (transitionDistance / initAnimPoints * (i + 1))) + 'px;' : ''}`;
});
}
}
else area.css = 'font-size: 0px;';
extraSetup(area);
},
})
@@ -1,71 +0,0 @@
const { Gtk } = imports.gi;
const Lang = imports.lang;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
// min-height/min-width for height/width
// background-color/color for background/indicator color
// padding for pad of indicator
// font-size for selected index (0-based)
export const NavigationIndicator = ({count, vertical, ...props}) => Widget.DrawingArea({
...props,
setup: (area) => {
const styleContext = area.get_style_context();
const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
area.set_size_request(width, height);
area.connect('draw', Lang.bind(area, (area, cr) => {
const styleContext = area.get_style_context();
const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
// console.log('allocated width/height:', area.get_allocated_width(), '/', area.get_allocated_height())
area.set_size_request(width, height);
const paddingLeft = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const paddingRight = styleContext.get_padding(Gtk.StateFlags.NORMAL).right;
const paddingTop = styleContext.get_padding(Gtk.StateFlags.NORMAL).top;
const paddingBottom = styleContext.get_padding(Gtk.StateFlags.NORMAL).bottom;
const selectedCell = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
let cellWidth = width;
let cellHeight = height;
if (vertical) cellHeight /= count;
else cellWidth /= count;
const indicatorWidth = cellWidth - paddingLeft - paddingRight;
const indicatorHeight = cellHeight - paddingTop - paddingBottom;
const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
cr.setLineWidth(2);
// Background
cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
cr.rectangle(0, 0, width, height);
cr.fill();
// The indicator line
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
if (vertical) {
cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
cr.stroke();
cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
cr.fill();
cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth / 2, Math.PI, 2 * Math.PI);
cr.fill();
cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorHeight - indicatorWidth / 2, indicatorWidth / 2, 0, Math.PI);
cr.fill();
}
else {
cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
cr.stroke();
cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
cr.fill();
cr.arc(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
cr.arc(paddingLeft + cellWidth * selectedCell + indicatorWidth - indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
}))
},
})
@@ -1,50 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Gtk } = imports.gi;
const Lang = imports.lang;
export const RoundedCorner = (place, props) => Widget.DrawingArea({
...props,
hpack: place.includes('left') ? 'start' : 'end',
vpack: place.includes('top') ? 'start' : 'end',
setup: (widget) => Utils.timeout(1, () => {
const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
widget.set_size_request(r, r);
widget.connect('draw', Lang.bind(widget, (widget, cr) => {
const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
// const borderColor = widget.get_style_context().get_property('color', Gtk.StateFlags.NORMAL);
// const borderWidth = widget.get_style_context().get_border(Gtk.StateFlags.NORMAL).left; // ur going to write border-width: something anyway
widget.set_size_request(r, r);
switch (place) {
case 'topleft':
cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
cr.lineTo(0, 0);
break;
case 'topright':
cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case 'bottomleft':
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case 'bottomright':
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
// cr.setLineWidth(borderWidth);
// cr.setSourceRGBA(borderColor.red, borderColor.green, borderColor.blue, borderColor.alpha);
// cr.stroke();
}));
}),
});
@@ -1,49 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Gtk } = imports.gi;
const Lang = imports.lang;
export const AnimatedSlider = ({
className,
value,
...rest
}) => {
return Widget.DrawingArea({
className: `${className}`,
setup: (self) => {
self.connect('draw', Lang.bind(self, (self, cr) => {
const styleContext = self.get_style_context();
const allocatedWidth = self.get_allocated_width();
const allocatedHeight = self.get_allocated_height();
console.log(allocatedHeight, allocatedWidth)
const minWidth = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const radius = styleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
const bg = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const fg = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
const value = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100;
self.set_size_request(-1, minHeight);
const width = allocatedHeight;
const height = minHeight;
cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left
cr.arc(width - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right
cr.arc(width - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left
cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right
cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
cr.closePath();
cr.fill();
// const valueWidth = width * value;
// cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left
// cr.arc(valueWidth - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right
// cr.arc(valueWidth - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left
// cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right
// cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
// cr.closePath();
// cr.fill();
}));
},
...rest,
})
}
@@ -1,23 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { monitors } from '../.commondata/hyprlanddata.js';
const { Box, EventBox } = Widget;
export const clickCloseRegion = ({ name, multimonitor = true, monitor = 0, expand = true, fillMonitor = '' }) => {
return EventBox({
child: Box({
expand: expand,
css: `
min-width: ${fillMonitor.includes('h') ? monitors[monitor].width : 0}px;
min-height: ${fillMonitor.includes('v') ? monitors[monitor].height : 0}px;
`,
}),
setup: (self) => self.on('button-press-event', (self, event) => { // Any mouse button
if (multimonitor) closeWindowOnAllMonitors(name);
else App.closeWindow(name);
}),
})
}
export default clickCloseRegion;
@@ -1,298 +0,0 @@
const { Gtk } = imports.gi;
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import { MaterialIcon } from './materialicon.js';
import { setupCursorHover, setupCursorHoverHResize } from '../.widgetutils/cursorhover.js';
const { Box, Button, EventBox, Label, Revealer, SpinButton } = Widget;
// Basically M3 Switch
// https://m3.material.io/components/switch/overview
// onReset must be async
export const ConfigToggle = ({
icon, name, desc = '', initValue,
expandWidget = true, resetButton = false,
onChange = () => { }, extraSetup = () => { },
onReset = () => { }, fetchValue = () => { },
...rest
}) => {
const enabled = Variable(initValue);
const toggleIcon = Label({
className: `icon-material txt-bold ${enabled.value ? '' : 'txt-poof'}`,
label: `${enabled.value ? 'check' : ''}`,
setup: (self) => self.hook(enabled, (self) => {
self.toggleClassName('switch-fg-toggling-false', false);
if (!enabled.value) {
self.label = '';
self.toggleClassName('txt-poof', true);
}
else Utils.timeout(1, () => {
toggleIcon.label = 'check';
toggleIcon.toggleClassName('txt-poof', false);
})
}),
})
const toggleButtonIndicator = Box({
className: `switch-fg ${enabled.value ? 'switch-fg-true' : ''}`,
vpack: 'center',
hpack: 'start',
homogeneous: true,
children: [toggleIcon,],
setup: (self) => self.hook(enabled, (self) => {
self.toggleClassName('switch-fg-true', enabled.value);
}),
});
const toggleButton = Box({
hpack: 'end',
vpack: 'center',
className: `switch-bg ${enabled.value ? 'switch-bg-true' : ''}`,
homogeneous: true,
children: [toggleButtonIndicator],
setup: (self) => self.hook(enabled, (self) => {
self.toggleClassName('switch-bg-true', enabled.value);
}),
});
const widgetContent = Box({
tooltipText: desc,
className: 'txt spacing-h-5',
children: [
...(icon !== undefined ? [MaterialIcon(icon, 'norm', {vpack: 'center'})] : []),
...(name !== undefined ? [Label({
vpack: 'center',
className: 'txt txt-small',
label: name,
})] : []),
...(expandWidget ? [Box({ hexpand: true })] : []),
toggleButton,
]
});
const interactionWrapper = Button({
attribute: {
enabled: enabled,
toggle: (newValue) => {
enabled.value = !enabled.value;
onChange(interactionWrapper, enabled.value);
}
},
child: widgetContent,
onClicked: (self) => self.attribute.toggle(self),
onHoverLost: () => { // mouse away
toggleIcon.toggleClassName('switch-fg-toggling-false', false);
if (enabled.value) toggleIcon.toggleClassName('txt-poof', false);
},
setup: (self) => {
setupCursorHover(self);
self.connect('pressed', () => { // mouse down
toggleIcon.toggleClassName('txt-poof', true);
toggleIcon.toggleClassName('switch-fg-true', false);
if (!enabled.value) toggleIcon.toggleClassName('switch-fg-toggling-false', true);
});
extraSetup(self)
},
...rest,
});
const wholeThing = Box({
attribute: {
'enabled': enabled,
},
className: 'configtoggle-box spacing-h-5',
children: [
interactionWrapper,
...(resetButton ? [Button({
className: 'configtoggle-reset',
onClicked: (self) => {
onReset(self).then(() => {
enabled.value = fetchValue();
}).catch(print);
},
child: MaterialIcon('settings_backup_restore', 'small'),
setup: setupCursorHover,
})] : []),
]
});
wholeThing.enabled = enabled;
return wholeThing;
}
export const ConfigSegmentedSelection = ({
icon, name, desc = '',
options = [{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
initIndex = 0,
onChange,
...rest
}) => {
let lastSelected = initIndex;
let value = options[initIndex].value;
const widget = Box({
tooltipText: desc,
className: 'segment-container',
// homogeneous: true,
children: options.map((option, id) => {
const selectedIcon = Revealer({
revealChild: id == initIndex,
transition: 'slide_right',
transitionDuration: userOptions.animations.durationSmall,
child: MaterialIcon('check', 'norm')
});
return Button({
setup: setupCursorHover,
className: `segment-btn ${id == initIndex ? 'segment-btn-enabled' : ''}`,
child: Box({
hpack: 'center',
className: 'spacing-h-5',
children: [
selectedIcon,
Label({
label: option.name,
})
]
}),
onClicked: (self) => {
value = option.value;
const kids = widget.get_children();
kids[lastSelected].toggleClassName('segment-btn-enabled', false);
kids[lastSelected].get_children()[0].get_children()[0].revealChild = false;
lastSelected = id;
self.toggleClassName('segment-btn-enabled', true);
selectedIcon.revealChild = true;
onChange(option.value, option.name);
}
})
}),
...rest,
});
return widget;
}
export const ConfigMulipleSelection = ({
icon, name, desc = '',
optionsArr = [
[{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
[{ name: 'Option 3', value: 0 }, { name: 'Option 4', value: 1 }],
],
initIndex = [0, 0],
onChange,
...rest
}) => {
let lastSelected = initIndex;
const widget = Box({
tooltipText: desc,
className: 'multipleselection-container spacing-v-3',
vertical: true,
children: optionsArr.map((options, grp) => Box({
className: 'spacing-h-5',
hpack: 'center',
children: options.map((option, id) => Button({
setup: setupCursorHover,
className: `multipleselection-btn ${id == initIndex[1] && grp == initIndex[0] ? 'multipleselection-btn-enabled' : ''}`,
label: option.name,
onClicked: (self) => {
const kidsg = widget.get_children();
const kids = kidsg.flatMap(widget => widget.get_children());
kids.forEach(kid => {
kid.toggleClassName('multipleselection-btn-enabled', false);
});
lastSelected = id;
self.toggleClassName('multipleselection-btn-enabled', true);
onChange(option.value, option.name);
}
})),
})),
...rest,
});
return widget;
}
export const ConfigGap = ({ vertical = true, size = 5, ...rest }) => Box({
className: `gap-${vertical ? 'v' : 'h'}-${size}`,
...rest,
})
// Gtk SpinButton with value scrubbing gesture
// scrubRatio is the ratio of changed value to drag distance in pixels
// onReset must be async
export const ConfigSpinButton = ({
icon, name, desc = '', initValue,
minValue = 0, maxValue = 100, step = 1,
expandWidget = true, resetButton = false,
scrubRatio = 1 / 20, roundValue = true,
onChange = () => { }, extraSetup = () => { },
onReset = () => { }, fetchValue = () => { },
...rest
}) => {
let resetLock = false;
const value = Variable(initValue);
const spinButton = SpinButton({
className: 'spinbutton',
range: [minValue, maxValue],
increments: [step, step],
onValueChanged: ({ value: newValue }) => {
if (resetLock) return;
value.value = newValue;
onChange(spinButton, newValue);
},
// This funny line means: set value of the spinbutton to the value of the
// Variable object called value that tracks the value of the widget
value: value.value,
});
const widgetContent = Box({
tooltipText: desc,
className: 'txt spacing-h-5 configtoggle-box',
children: [
...(icon !== undefined ? [MaterialIcon(icon, 'norm')] : []),
...(name !== undefined ? [Label({
className: 'txt txt-small',
label: name,
})] : []),
...(expandWidget ? [Box({ hexpand: true })] : []),
spinButton,
...(resetButton ? [Button({
className: 'spinbutton-reset',
onClicked: (self) => {
onReset(self).then(() => {
resetLock = true;
const newValue = fetchValue();
spinButton.value = newValue;
value.value = newValue;
resetLock = false;
}).catch(print);
},
child: MaterialIcon('settings_backup_restore', 'small'),
setup: setupCursorHover,
})] : []),
],
setup: (self) => {
extraSetup(self);
},
...rest,
});
const interactionWrapper = EventBox({
child: widgetContent,
setup: setupCursorHoverHResize,
})
const gesture = Gtk.GestureDrag.new(interactionWrapper);
let gestureValueOnDragBegin;
const wholeThing = Box({
children: [interactionWrapper],
setup: (self) => self
.hook(gesture, (self) => {
gestureValueOnDragBegin = value.value;
}, 'drag-begin')
.hook(gesture, (self) => {
var offset_x = gesture.get_offset()[1];
var offset_y = gesture.get_offset()[2];
let newValue = gestureValueOnDragBegin + (offset_x * scrubRatio);
if (roundValue) newValue = Math.round(newValue);
if (newValue !== spinButton.value) {
spinButton.value = newValue;
}
}, 'drag-update')
.hook(gesture, (self) => {
}, 'drag-end')
});
wholeThing.enabled = value;
return wholeThing;
}
@@ -1,154 +0,0 @@
const { GLib } = imports.gi;
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
import { getNestedProperty, updateNestedProperty } from "../.miscutils/objects.js";
import { ConfigSpinButton, ConfigToggle } from "./configwidgets.js";
const AGS_CONFIG_FILE = `${App.configDir}/user_options.jsonc`;
const HYPRLAND_CONFIG_FILE = `${GLib.get_user_config_dir()}/hypr/custom/general.conf`;
export const AgsToggle = ({
icon, name, desc = null,
option, resetButton = true, save = true,
extraOnChange = () => { }, extraOnReset = () => { },
...rest
}) => ConfigToggle({
icon: icon,
name: name,
desc: `${desc}\n\n${option}\nEdit in ${AGS_CONFIG_FILE}`,
resetButton: resetButton,
initValue: getNestedProperty(userOptions, option),
fetchValue: () => getNestedProperty(userOptions, option),
onChange: (self, newValue) => {
updateNestedProperty(userOptions, option, newValue);
if (save) execAsync(['bash', '-c', `${App.configDir}/scripts/ags/agsconfigurator.py \
--key ${option} \
--value ${newValue} \
--file ${AGS_CONFIG_FILE}`
]).catch(print);
extraOnChange(self, newValue);
},
onReset: async (self) => {
updateNestedProperty(userOptions, option,
getNestedProperty(userOptionsDefaults, option));
if (save) exec(`bash -c '${App.configDir}/scripts/ags/agsconfigurator.py \
--key ${option} \
--reset \
--file ${AGS_CONFIG_FILE}'`);
extraOnReset(self);
},
...rest
});
export const AgsSpinButton = ({
icon, name, desc = null,
option, resetButton = true,
save = true, extraOnChange = () => { }, extraOnReset = () => { },
...rest
}) => ConfigSpinButton({
icon: icon,
name: name,
desc: `${desc}\n\n${option}\nEdit in ${AGS_CONFIG_FILE}`,
resetButton: resetButton,
initValue: getNestedProperty(userOptions, option),
fetchValue: () => getNestedProperty(userOptions, option),
step: 10, minValue: 0, maxValue: 1000,
onChange: (self, newValue) => {
updateNestedProperty(userOptions, option, newValue);
if (save) execAsync(['bash', '-c', `${App.configDir}/scripts/ags/agsconfigurator.py \
--key ${option} \
--value ${newValue} \
--file ${AGS_CONFIG_FILE}`
]).catch(print);
extraOnChange(self, newValue);
},
onReset: async () => {
updateNestedProperty(userOptions, option,
getNestedProperty(userOptionsDefaults, option));
if (save) exec(`bash -c '${App.configDir}/scripts/ags/agsconfigurator.py \
--key ${option} \
--reset \
--file ${AGS_CONFIG_FILE}'`);
extraOnReset(self);
},
...rest,
});
export const HyprlandToggle = ({
icon, name, desc = null,
option, resetButton = true,
enableValue = 1, disableValue = 0,
extraOnChange = () => { }, extraOnReset = () => { }, save = true
}) => ConfigToggle({
icon: icon,
name: name,
desc: `${desc}\n\n${option}\nEdit in ${HYPRLAND_CONFIG_FILE}`,
resetButton: resetButton,
initValue: JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"] != 0,
fetchValue: () => JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"] != 0,
onChange: (self, newValue) => {
if (save)
execAsync(['bash', '-c', `${App.configDir}/scripts/hyprland/hyprconfigurator.py \
--key ${option} \
--value ${newValue ? enableValue : disableValue} \
--file ${HYPRLAND_CONFIG_FILE}`
]).catch(print);
else
execAsync(['hyprctl', 'keyword', option, `${newValue ? enableValue : disableValue}`]).catch(print);
extraOnChange(self, newValue);
},
onReset: async (self) => {
if (save)
exec(`bash -c '${App.configDir}/scripts/hyprland/hyprconfigurator.py \
--key ${option} \
--reset \
--file "${HYPRLAND_CONFIG_FILE}"'`);
else
exec('hyprctl reload');
extraOnReset(self);
},
});
export const HyprlandSpinButton = ({
icon, name, desc = null,
option, resetButton = true, save = true,
extraOnChange = () => { }, extraOnReset = () => { },
...rest
}) => ConfigSpinButton({
icon: icon,
name: name,
desc: `${desc}\n\n${option}\nEdit in ${HYPRLAND_CONFIG_FILE}`,
resetButton: resetButton,
initValue: Number(JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"]),
fetchValue: () => Number(JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"]),
onChange: (self, newValue) => {
if (save)
execAsync(['bash', '-c', `${App.configDir}/scripts/hyprland/hyprconfigurator.py \
--key ${option} \
--value ${newValue} \
--file ${HYPRLAND_CONFIG_FILE}`
]).catch(print);
else
execAsync(['hyprctl', 'keyword', option, `${newValue}`]).catch(print);
extraOnChange(self, newValue);
},
onReset: async (self) => {
if (save)
exec(`bash -c '${App.configDir}/scripts/hyprland/hyprconfigurator.py \
--key ${option} \
--reset \
--file "${HYPRLAND_CONFIG_FILE}"'`);
else
exec('hyprctl reload');
extraOnReset(self);
},
...rest,
});
@@ -1,7 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
export const MaterialIcon = (icon, size, props = {}) => Widget.Label({
className: `icon-material txt-${size}`,
label: icon,
...props,
})
@@ -1,505 +0,0 @@
// This file is for the actual widget for each single notification
const { GLib, Gdk, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js'
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
const { Box, EventBox, Icon, Overlay, Label, Button, Revealer } = Widget;
import { MaterialIcon } from './materialicon.js';
import { setupCursorHover } from "../.widgetutils/cursorhover.js";
import { AnimatedCircProg } from "./cairo_circularprogress.js";
function guessMessageType(summary) {
const keywordsToTypes = {
'reboot': 'restart_alt',
'recording': 'screen_record',
'battery': 'power',
'power': 'power',
'screenshot': 'screenshot_monitor',
'welcome': 'waving_hand',
'time': 'scheduleb',
'installed': 'download',
'update': 'update',
'ai response': 'neurology',
'startswith:file': 'folder_copy', // Declarative startsWith check
};
const lowerSummary = summary.toLowerCase();
for (const [keyword, type] of Object.entries(keywordsToTypes)) {
if (keyword.startsWith('startswith:')) {
const startsWithKeyword = keyword.replace('startswith:', '');
if (lowerSummary.startsWith(startsWithKeyword)) {
return type;
}
} else if (lowerSummary.includes(keyword)) {
return type;
}
}
return 'chat';
}
function processNotificationBody(body, appEntry) {
// Only process Chrome/Chromium notifications
if (appEntry?.toLowerCase().includes('chrome')) {
// Remove the first line
return body.split('\n\n').slice(1).join('\n\n');
}
return body;
}
const getFriendlyNotifTimeString = (timeObject) => {
const messageTime = GLib.DateTime.new_from_unix_local(timeObject);
const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60);
if (messageTime.compare(oneMinuteAgo) > 0)
return getString('Now');
else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year())
return messageTime.format(userOptions.time.format);
else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1)
return getString('Yesterday');
else
return messageTime.format(userOptions.time.dateFormat);
}
const NotificationIcon = (notifObject) => {
if (notifObject.hints?.image_path?.deepUnpack) {
const imagePath = notifObject.hints.image_path.deepUnpack();
return Box({
valign: Gtk.Align.CENTER,
hexpand: false,
className: 'notif-icon',
css: `
background-image: url("${imagePath}");
background-size: auto 100%;
background-repeat: no-repeat;
background-position: center;
`,
});
}
if (notifObject.image) {
return Box({
valign: Gtk.Align.CENTER,
hexpand: false,
className: 'notif-icon',
css: `
background-image: url("${notifObject.image}");
background-size: auto 100%;
background-repeat: no-repeat;
background-position: center;
`,
});
}
let icon = 'NO_ICON';
if (Utils.lookUpIcon(notifObject.appIcon))
icon = notifObject.appIcon;
if (Utils.lookUpIcon(notifObject.appEntry))
icon = notifObject.appEntry;
return Box({
vpack: 'center',
hexpand: false,
className: `notif-icon notif-icon-material-${notifObject.urgency}`,
homogeneous: true,
children: [
(icon != 'NO_ICON' ?
Icon({
vpack: 'center',
icon: icon,
})
:
MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', {
hexpand: true,
})
)
],
});
};
export default ({
notifObject,
isPopup = false,
props = {},
} = {}) => {
const popupTimeout = notifObject.timeout || (notifObject.urgency == 'critical' ? 8000 : 3000);
const command = (isPopup ?
() => notifObject.dismiss() :
() => notifObject.close()
)
const destroyWithAnims = () => {
widget.sensitive = false;
notificationBox.setCss(middleClickClose);
Utils.timeout(userOptions.animations.durationSmall, () => {
if (wholeThing) wholeThing.revealChild = false;
}, wholeThing);
Utils.timeout(userOptions.animations.durationSmall * 2, () => {
command();
if (wholeThing) {
wholeThing.destroy();
wholeThing = null;
}
}, wholeThing);
}
const widget = EventBox({
onHover: (self) => {
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
if (!wholeThing.attribute.hovered)
wholeThing.attribute.hovered = true;
},
onHoverLost: (self) => {
self.window.set_cursor(null);
if (wholeThing.attribute.hovered)
wholeThing.attribute.hovered = false;
if (isPopup) {
command();
}
},
onMiddleClick: (self) => {
destroyWithAnims();
},
onSecondaryClick: (self) => {
expanded = !expanded;
notifTextPreview.revealChild = !expanded;
notifTextExpanded.revealChild = expanded;
notifExpandButton.child.label = `expand_${expanded ? 'less' : 'more'}`;
},
setup: (self) => {
self.on("button-press-event", () => {
wholeThing.attribute.held = true;
notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, true);
Utils.timeout(800, () => {
if (wholeThing?.attribute.held) {
Utils.execAsync(['wl-copy', `${notifObject.body}`]).catch(print);
notifTextSummary.label = notifObject.summary + " (copied)";
Utils.timeout(3000, () => notifTextSummary.label = notifObject.summary)
}
})
}).on("button-release-event", () => {
wholeThing.attribute.held = false;
notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, false);
})
}
});
let wholeThing = Revealer({
attribute: {
'close': undefined,
'destroyWithAnims': destroyWithAnims,
'dragging': false,
'held': false,
'hovered': false,
'id': notifObject.id,
},
revealChild: false,
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: Box({ // Box to make sure css-based spacing works
homogeneous: true,
}),
});
const display = Gdk.Display.get_default();
const notifTextPreview = Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationSmall,
revealChild: true,
child: Label({
xalign: 0,
className: `txt-smallie notif-body-${notifObject.urgency}`,
useMarkup: true,
xalign: 0,
justify: Gtk.Justification.LEFT,
maxWidthChars: 1,
truncate: 'end',
label: processNotificationBody(notifObject.body, notifObject.appEntry).split("\n")[0]
}),
});
const notifTextExpanded = Revealer({
transition: 'slide_up',
transitionDuration: userOptions.animations.durationSmall,
revealChild: false,
child: Box({
vertical: true,
className: 'spacing-v-10',
children: [
Label({
xalign: 0,
className: `txt-smallie notif-body-${notifObject.urgency}`,
useMarkup: true,
xalign: 0,
justify: Gtk.Justification.LEFT,
maxWidthChars: 1,
wrap: true,
label: processNotificationBody(notifObject.body, notifObject.appEntry)
}),
Box({
className: 'notif-actions spacing-h-5',
children: [
Button({
hexpand: true,
className: `notif-action notif-action-${notifObject.urgency}`,
onClicked: () => destroyWithAnims(),
setup: setupCursorHover,
child: Label({
label: getString('Close'),
}),
}),
...notifObject.actions.map(action => Widget.Button({
hexpand: true,
className: `notif-action notif-action-${notifObject.urgency}`,
onClicked: () => notifObject.invoke(action.id),
setup: setupCursorHover,
child: Label({
label: action.label,
}),
}))
],
})
]
}),
});
const notifIcon = Box({
vpack: 'start',
homogeneous: true,
children: [
Overlay({
child: NotificationIcon(notifObject),
overlays: isPopup ? [AnimatedCircProg({
className: `notif-circprog-${notifObject.urgency}`,
vpack: 'center', hpack: 'center',
initFrom: (isPopup ? 100 : 0),
initTo: 0,
initAnimTime: popupTimeout,
})] : [],
}),
]
});
const notifTextSummary = Label({
xalign: 0,
className: 'txt-small txt-semibold titlefont',
justify: Gtk.Justification.LEFT,
hexpand: true,
maxWidthChars: 1,
truncate: 'end',
ellipsize: 3,
useMarkup: notifObject.summary.startsWith('<'),
label: notifObject.summary,
});
const initTimeString = getFriendlyNotifTimeString(notifObject.time);
const notifTextBody = Label({
vpack: 'center',
justification: 'right',
className: 'txt-smaller txt-semibold',
label: initTimeString,
setup: initTimeString == 'Now' ? (self) => {
let id = Utils.timeout(60000, () => {
self.label = getFriendlyNotifTimeString(notifObject.time);
id = null;
});
self.connect('destroy', () => { if (id) GLib.source_remove(id) });
} : () => { },
});
const notifText = Box({
valign: Gtk.Align.CENTER,
vertical: true,
hexpand: true,
children: [
Box({
children: [
notifTextSummary,
notifTextBody,
]
}),
notifTextPreview,
notifTextExpanded,
]
});
const notifExpandButton = Button({
vpack: 'start',
className: 'notif-expand-btn',
onClicked: (self) => {
if (notifTextPreview.revealChild) { // Expanding...
notifTextPreview.revealChild = false;
notifTextExpanded.revealChild = true;
self.child.label = 'expand_less';
expanded = true;
}
else {
notifTextPreview.revealChild = true;
notifTextExpanded.revealChild = false;
self.child.label = 'expand_more';
expanded = false;
}
},
child: MaterialIcon('expand_more', 'norm', {
vpack: 'center',
}),
setup: setupCursorHover,
});
const notificationContent = Box({
...props,
className: `${isPopup ? 'popup-' : ''}notif-${notifObject.urgency} spacing-h-10`,
children: [
notifIcon,
Box({
className: 'spacing-h-5',
children: [
notifText,
notifExpandButton,
]
})
]
})
// Gesture stuff
const gesture = Gtk.GestureDrag.new(widget);
var initDirX = 0;
var initDirVertical = -1; // -1: unset, 0: horizontal, 1: vertical
var expanded = false;
// in px
const startMargin = 0;
const MOVE_THRESHOLD = 10;
const DRAG_CONFIRM_THRESHOLD = 100;
// in rem
const maxOffset = 10.227;
const endMargin = 20.455;
const disappearHeight = 6.818;
const leftAnim1 = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: -${Number(maxOffset + endMargin)}rem;
margin-right: ${Number(maxOffset + endMargin)}rem;
opacity: 0;`;
const rightAnim1 = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: ${Number(maxOffset + endMargin)}rem;
margin-right: -${Number(maxOffset + endMargin)}rem;
opacity: 0;`;
const middleClickClose = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.85, 0, 0.15, 1);
margin-left: ${Number(maxOffset + endMargin)}rem;
margin-right: -${Number(maxOffset + endMargin)}rem;
opacity: 0;`;
const notificationBox = Box({
attribute: {
'leftAnim1': leftAnim1,
'rightAnim1': rightAnim1,
'middleClickClose': middleClickClose,
'ready': false,
},
homogeneous: true,
children: [notificationContent],
setup: (self) => self
.hook(gesture, self => {
var offset_x = gesture.get_offset()[1];
var offset_y = gesture.get_offset()[2];
// Which dir?
if (initDirVertical == -1) {
if (Math.abs(offset_y) > MOVE_THRESHOLD)
initDirVertical = 1;
if (initDirX == 0 && Math.abs(offset_x) > MOVE_THRESHOLD) {
initDirVertical = 0;
initDirX = (offset_x > 0 ? 1 : -1);
}
}
// Horizontal drag
if (initDirVertical == 0 && offset_x > MOVE_THRESHOLD) {
if (initDirX < 0)
self.setCss(`margin-left: 0px; margin-right: 0px;`);
else
self.setCss(`
margin-left: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
margin-right: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
`);
}
else if (initDirVertical == 0 && offset_x < -MOVE_THRESHOLD) {
if (initDirX > 0)
self.setCss(`margin-left: 0px; margin-right: 0px;`);
else {
offset_x = Math.abs(offset_x);
self.setCss(`
margin-right: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
margin-left: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
`);
}
}
// Update dragging
wholeThing.attribute.dragging = Math.abs(offset_x) > MOVE_THRESHOLD;
if (Math.abs(offset_x) > MOVE_THRESHOLD ||
Math.abs(offset_y) > MOVE_THRESHOLD) wholeThing.attribute.held = false;
widget.window?.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
// Vertical drag
if (initDirVertical == 1 && offset_y > MOVE_THRESHOLD && !expanded) {
notifTextPreview.revealChild = false;
notifTextExpanded.revealChild = true;
expanded = true;
notifExpandButton.child.label = 'expand_less';
}
else if (initDirVertical == 1 && offset_y < -MOVE_THRESHOLD && expanded) {
notifTextPreview.revealChild = true;
notifTextExpanded.revealChild = false;
expanded = false;
notifExpandButton.child.label = 'expand_more';
}
}, 'drag-update')
.hook(gesture, self => {
if (!self.attribute.ready) {
wholeThing.revealChild = true;
self.attribute.ready = true;
return;
}
const offset_h = gesture.get_offset()[1];
if (Math.abs(offset_h) > DRAG_CONFIRM_THRESHOLD && offset_h * initDirX > 0) {
if (offset_h > 0) {
self.setCss(rightAnim1);
widget.sensitive = false;
}
else {
self.setCss(leftAnim1);
widget.sensitive = false;
}
Utils.timeout(userOptions.animations.durationSmall, () => {
if (wholeThing) wholeThing.revealChild = false;
}, wholeThing);
Utils.timeout(userOptions.animations.durationSmall * 2, () => {
command();
if (wholeThing) {
wholeThing.destroy();
wholeThing = null;
}
}, wholeThing);
}
else {
self.setCss(`transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: ${startMargin}px;
margin-right: ${startMargin}px;
margin-bottom: unset; margin-top: unset;
opacity: 1;`);
if (widget.window)
widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
wholeThing.attribute.dragging = false;
}
initDirX = 0;
initDirVertical = -1;
}, 'drag-end')
,
})
widget.add(notificationBox);
wholeThing.child.children = [widget];
if (isPopup) Utils.timeout(popupTimeout, () => {
if (wholeThing && !wholeThing.attribute.hovered) {
wholeThing.revealChild = false;
Utils.timeout(userOptions.animations.durationSmall, () => {
if (wholeThing) {
wholeThing.destroy();
wholeThing = null;
}
command();
}, wholeThing);
}
})
return wholeThing;
}
@@ -1,316 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import { MaterialIcon } from './materialicon.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { languages } from './statusicons_languages.js';
// A guessing func to try to support langs not listed in data/languages.js
function isLanguageMatch(abbreviation, word) {
const lowerAbbreviation = abbreviation.toLowerCase();
const lowerWord = word.toLowerCase();
let j = 0;
for (let i = 0; i < lowerWord.length; i++) {
if (lowerWord[i] === lowerAbbreviation[j]) {
j++;
}
if (j === lowerAbbreviation.length) {
return true;
}
}
return false;
}
export const MicMuteIndicator = () => Widget.Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationSmall,
revealChild: false,
setup: (self) => self.hook(Audio, (self) => {
self.revealChild = Audio.microphone?.stream?.isMuted;
}),
child: MaterialIcon('mic_off', 'norm'),
});
export const NotificationIndicator = (notifCenterName = 'sideright') => {
const widget = Widget.Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationSmall,
revealChild: false,
setup: (self) => self
.hook(Notifications, (self, id) => {
if (!id || Notifications.dnd) return;
if (!Notifications.getNotification(id)) return;
self.revealChild = true;
}, 'notified')
.hook(App, (self, currentName, visible) => {
if (visible && currentName === notifCenterName) {
self.revealChild = false;
}
})
,
child: Widget.Box({
children: [
MaterialIcon('notifications', 'norm'),
Widget.Label({
className: 'txt-small titlefont',
attribute: {
unreadCount: 0,
update: (self) => self.label = `${self.attribute.unreadCount}`,
},
setup: (self) => self
.hook(Notifications, (self, id) => {
if (!id || Notifications.dnd) return;
if (!Notifications.getNotification(id)) return;
self.attribute.unreadCount++;
self.attribute.update(self);
}, 'notified')
.hook(App, (self, currentName, visible) => {
if (visible && currentName === notifCenterName) {
self.attribute.unreadCount = 0;
self.attribute.update(self);
}
})
,
})
]
})
});
return widget;
}
export const BluetoothIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'disabled': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_disabled' }),
'enabled': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth' }),
'connected': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_connected' }),
},
setup: (self) =>
self.hook(Bluetooth, (stack) => {
if (!Bluetooth.enabled) {
stack.shown = 'disabled';
} else if (Bluetooth.connected_devices.length === 0) {
stack.shown = 'enabled';
} else if (Bluetooth.connected_devices.length > 0) {
stack.shown = 'connected';
}
}),
});
const BluetoothDevices = () => Widget.Box({
className: 'spacing-h-5',
setup: self => self.hook(Bluetooth, self => {
self.children = Bluetooth.connected_devices.map((device) => {
return Widget.Box({
className: 'bar-bluetooth-device spacing-h-5',
vpack: 'center',
tooltipText: device.name,
children: [
Widget.Icon(`${device.iconName}-symbolic`),
...(device.batteryPercentage ? [Widget.Label({
className: 'txt-smallie',
label: `${device.batteryPercentage}`,
setup: (self) => {
self.hook(device, (self) => {
self.label = `${device.batteryPercentage}`;
}, 'notify::batteryPercentage')
}
})] : []),
]
});
});
self.visible = Bluetooth.connected_devices.length > 0;
}, 'notify::connected-devices'),
})
const NetworkWiredIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'fallback': SimpleNetworkIndicator(),
'unknown': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
'connected': Widget.Label({ className: 'txt-norm icon-material', label: 'lan' }),
'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
},
setup: (self) => self.hook(Network, stack => {
if (!Network.wired)
return;
const { internet } = Network.wired;
if (['connecting', 'connected'].includes(internet))
stack.shown = internet;
else if (Network.connectivity !== 'full')
stack.shown = 'disconnected';
else
stack.shown = 'fallback';
}),
});
const SimpleNetworkIndicator = () => Widget.Icon({
setup: (self) => self.hook(Network, self => {
const icon = Network[Network.primary || 'wifi']?.iconName;
self.icon = icon || '';
self.visible = icon;
}),
});
const NetworkWifiIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'disabled': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
'disconnected': Widget.Label({
className: 'txt-norm icon-material',
label: 'signal_wifi_statusbar_not_connected',
}),
'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
'0': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' }),
'1': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_1_bar' }),
'2': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_2_bar' }),
'3': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_3_bar' }),
'4': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_4_bar' }),
},
setup: (self) => self.hook(Network, (stack) => {
if (!Network.wifi) {
return;
}
if (!Network.wifi.enabled) {
stack.shown = 'disabled';
} else if (Network.wifi.internet == 'connected') {
stack.shown = String(Math.ceil(Network.wifi.strength / 25));
} else if (['disconnected', 'connecting'].includes(Network.wifi.internet)) {
stack.shown = Network.wifi.internet;
}
}),
});
export const NetworkIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'fallback': SimpleNetworkIndicator(),
'wifi': NetworkWifiIndicator(),
'wired': NetworkWiredIndicator(),
},
setup: (self) => self.hook(Network, stack => {
if (!Network.primary) {
stack.shown = 'wifi';
return;
}
const primary = Network.primary || 'fallback';
if (['wifi', 'wired'].includes(primary))
stack.shown = primary;
else
stack.shown = 'fallback';
}),
});
const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
var languageStackArray = [];
const updateCurrentKeyboards = () => {
var initLangs = [];
JSON.parse(Utils.exec('hyprctl -j devices')).keyboards
.forEach(keyboard => {
initLangs.push(...keyboard.layout.split(',').map(lang => lang.trim()));
});
initLangs = [...new Set(initLangs)];
languageStackArray = Array.from({ length: initLangs.length }, (_, i) => {
const lang = languages.find(lang => lang.layout == initLangs[i]);
// if (!lang) return [
// initLangs[i],
// Widget.Label({ label: initLangs[i] })
// ];
// return [
// lang.layout,
// Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
// ];
// Object
if (!lang) return {
[initLangs[i]]: Widget.Label({ label: initLangs[i] })
};
return {
[lang.layout]: Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
};
});
};
updateCurrentKeyboards();
const widgetRevealer = Widget.Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationSmall,
revealChild: languageStackArray.length > 1,
});
const widgetKids = {
...languageStackArray.reduce((obj, lang) => {
return { ...obj, ...lang };
}, {}),
'undef': Widget.Label({ label: '?' }),
}
const widgetContent = Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: widgetKids,
setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => {
if (!kbName) {
return;
}
var lang = languages.find(lang => layoutName.includes(lang.name));
if (lang) {
widgetContent.shown = lang.layout;
}
else { // Attempt to support langs not listed
lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName));
if (!lang) stack.shown = 'undef';
else stack.shown = lang[0];
}
}, 'keyboard-layout'),
});
widgetRevealer.child = widgetContent;
return widgetRevealer;
} catch {
return null;
}
}
const OptionalKeyboardLayout = async () => {
try {
return await HyprlandXkbKeyboardLayout({ useFlag: userOptions.appearance.keyboardUseFlag });
} catch {
return null;
}
};
const createKeyboardLayoutInstances = async () => {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
const monitorsCount = Hyprland.monitors.length
const instances = await Promise.all(
Array.from({ length: monitorsCount }, () => OptionalKeyboardLayout())
);
return instances;
};
const optionalKeyboardLayoutInstances = await createKeyboardLayoutInstances()
export const StatusIcons = (props = {}, monitor = 0) => Widget.Box({
...props,
child: Widget.Box({
className: 'spacing-h-15',
children: [
MicMuteIndicator(),
optionalKeyboardLayoutInstances[monitor],
NotificationIndicator(),
NetworkIndicator(),
Widget.Box({
className: 'spacing-h-5',
children: [BluetoothIndicator(), BluetoothDevices()]
})
]
})
});
@@ -1,62 +0,0 @@
// For keyboard layout in statusicons.js
// This list is not exhaustive. It just includes known/possible languages of users of my dotfiles
// Add your language here if you use multi-lang xkb input. Else, ignore
// Note that something like "French (Canada)" should go before "French"
// and "English (US)" should go before "English"
export const languages = [
{
layout: 'us',
name: 'English (US)',
flag: '🇺🇸'
},
{
layout: 'ru',
name: 'Russian',
flag: '🇷🇺',
},
{
layout: 'pl',
name: 'Polish',
flag: '🇷🇵🇵🇱',
},
{
layout: 'ro',
name: 'Romanian',
flag: '🇷🇴',
},
{
layout: 'ca',
name: 'French (Canada)',
flag: '🇫🇷',
},
{
layout: 'fr',
name: 'French',
flag: '🇫🇷',
},
{
layout: 'tr',
name: 'Turkish',
flag: '🇹🇷',
},
{
layout: 'jp',
name: 'Japanese',
flag: '🇯🇵',
},
{
layout: 'cn',
name: 'Chinese',
flag: '🇨🇳',
},
{
layout: 'vn',
name: 'Vietnamese',
flag: '🇻🇳',
},
{
layout: 'undef',
name: 'Undefined',
flag: '🧐',
},
]
@@ -1,299 +0,0 @@
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, Button, EventBox, Label, Overlay, Stack } = Widget;
import { MaterialIcon } from './materialicon.js';
import { NavigationIndicator } from './cairo_navigationindicator.js';
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import { DoubleRevealer } from '../.widgethacks/advancedrevealers.js';
export const TabContainer = ({
icons, names, children, initIndex = 0,
className = '', setup = () => { },
onChange = () => { },
extraTabStripWidgets = [],
...rest
}) => {
const shownIndex = Variable(initIndex);
let previousShownIndex = 0;
const count = Math.min(icons.length, names.length, children.length);
const tabs = Box({
homogeneous: true,
children: Array.from({ length: count }, (_, i) => Button({ // Tab button
className: 'tab-btn',
onClicked: () => shownIndex.value = i,
setup: setupCursorHover,
child: Box({
hpack: 'center',
vpack: 'center',
className: 'spacing-h-5 txt-small',
children: [
MaterialIcon(icons[i], 'norm'),
Label({
label: names[i],
})
]
})
})),
setup: (self) => self.hook(shownIndex, (self) => {
self.children[previousShownIndex].toggleClassName('tab-btn-active', false);
self.children[shownIndex.value].toggleClassName('tab-btn-active', true);
previousShownIndex = shownIndex.value;
}),
});
const tabIndicatorLine = Box({
vertical: true,
homogeneous: true,
setup: (self) => self.hook(shownIndex, (self) => {
self.children[0].css = `font-size: ${shownIndex.value}px;`;
}),
children: [NavigationIndicator({
className: 'tab-indicator',
count: count,
css: `font-size: ${shownIndex.value}px;`,
})],
});
const tabSection = Box({
homogeneous: true,
children: [EventBox({
onScrollUp: () => mainBox.prevTab(),
onScrollDown: () => mainBox.nextTab(),
child: Box({
className: 'spacing-h-5',
children: [
Box({
vertical: true,
hexpand: true,
children: [
tabs,
tabIndicatorLine
]
}),
...extraTabStripWidgets,
]
})
})]
});
shownIndex.setValue(initIndex)
const contentStack = Stack({
transition: 'slide_left_right',
children: children.reduce((acc, currentValue, index) => {
acc[index] = currentValue;
return acc;
}, {}),
setup: (self) => self.hook(shownIndex, (self) => {
self.shown = `${shownIndex.value}`;
}),
});
const mainBox = Box({
attribute: {
children: children,
shown: shownIndex,
names: names,
},
vertical: true,
className: `spacing-v-5 ${className}`,
setup: (self) => {
self.pack_start(tabSection, false, false, 0);
self.pack_end(contentStack, true, true, 0);
setup(self);
self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
},
...rest,
});
mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
return mainBox;
}
export const IconTabContainer = ({
iconWidgets, names, children, className = '',
initIndex = 0,
setup = () => { }, onChange = () => { },
tabsHpack = 'center', tabSwitcherClassName = '',
...rest
}) => {
const shownIndex = Variable(initIndex);
let previousShownIndex = 0;
const count = Math.min(iconWidgets.length, names.length, children.length);
const tabs = Box({
hpack: tabsHpack,
className: `spacing-h-5 ${tabSwitcherClassName}`,
children: iconWidgets.map((icon, i) => Button({
className: 'tab-icon',
tooltipText: names[i],
child: icon,
setup: setupCursorHover,
onClicked: () => shownIndex.value = i,
})),
setup: (self) => self.hook(shownIndex, (self) => {
self.children[previousShownIndex].toggleClassName('tab-icon-active', false);
self.children[shownIndex.value].toggleClassName('tab-icon-active', true);
previousShownIndex = shownIndex.value;
}),
});
const tabSection = Box({
homogeneous: true,
children: [EventBox({
onScrollUp: () => mainBox.prevTab(),
onScrollDown: () => mainBox.nextTab(),
child: Box({
vertical: true,
hexpand: true,
children: [
tabs,
]
})
})]
});
const contentStack = Stack({
transition: 'slide_left_right',
children: children.reduce((acc, currentValue, index) => {
acc[index] = currentValue;
return acc;
}, {}),
setup: (self) => self.hook(shownIndex, (self) => {
self.shown = `${shownIndex.value}`;
}),
});
const mainBox = Box({
attribute: {
children: children,
shown: shownIndex,
names: names,
},
vertical: true,
className: `spacing-v-5 ${className}`,
setup: (self) => {
self.pack_start(tabSection, false, false, 0);
self.pack_end(contentStack, true, true, 0);
setup(self);
self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
},
...rest,
});
mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
mainBox.shown = shownIndex;
return mainBox;
}
export const ExpandingIconTabContainer = ({
icons, names, children, className = '',
setup = () => { }, onChange = () => { },
tabsHpack = 'center', tabSwitcherClassName = '',
transitionDuration = userOptions.animations.durationLarge,
...rest
}) => {
const shownIndex = Variable(0);
let previousShownIndex = 0;
const count = Math.min(icons.length, names.length, children.length);
const tabs = Box({
hpack: tabsHpack,
className: `spacing-h-5 ${tabSwitcherClassName}`,
children: icons.map((icon, i) => {
const tabIcon = MaterialIcon(icon, 'norm', { hexpand: true });
const tabName = DoubleRevealer({
transition1: 'slide_right',
transition2: 'crossfade',
duration1: 0,
duration2: 0,
// duration1: userOptions.animations.durationSmall,
// duration2: userOptions.animations.durationSmall,
child: Label({
className: 'margin-left-5 txt-small',
label: names[i],
}),
revealChild: i === shownIndex.value,
})
const button = Button({
className: 'tab-icon-expandable',
tooltipText: names[i],
child: Box({
homogeneous: true,
children: [Box({
hpack: 'center',
children: [
tabIcon,
tabName,
]
})],
}),
setup: setupCursorHover,
onClicked: () => shownIndex.value = i,
});
button.toggleFocus = (value) => {
tabIcon.hexpand = !value;
button.toggleClassName('tab-icon-expandable-active', value);
tabName.toggleRevealChild(value);
}
return button;
}),
setup: (self) => self.hook(shownIndex, (self) => {
self.children[previousShownIndex].toggleFocus(false);
self.children[shownIndex.value].toggleFocus(true);
previousShownIndex = shownIndex.value;
}),
});
const tabSection = Box({
homogeneous: true,
children: [EventBox({
onScrollUp: () => mainBox.prevTab(),
onScrollDown: () => mainBox.nextTab(),
child: Box({
vertical: true,
hexpand: true,
children: [
tabs,
]
})
})]
});
const contentStack = Stack({
transition: 'slide_left_right',
transitionDuration: transitionDuration,
children: children.reduce((acc, currentValue, index) => {
acc[index] = currentValue;
return acc;
}, {}),
setup: (self) => self.hook(shownIndex, (self) => {
self.shown = `${shownIndex.value}`;
}),
});
const mainBox = Box({
attribute: {
children: children,
shown: shownIndex,
names: names,
},
vertical: true,
className: `spacing-v-5 ${className}`,
setup: (self) => {
self.pack_start(tabSection, false, false, 0);
self.pack_end(contentStack, true, true, 0);
setup(self);
self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
},
...rest,
});
mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
mainBox.focusName = (name) => {
const focusIndex = names.indexOf(name);
if (focusIndex !== -1) {
shownIndex.value = focusIndex;
}
}
mainBox.shown = shownIndex;
return mainBox;
}
@@ -1,290 +0,0 @@
// This file is parsed with a custom JSONC parser.
// Don't expect every JSONC feature in... say, vscode, to work.
{
// General stuff
"ai": {
"defaultGPTProvider": "ollama_llama_3_2",
"defaultTemperature": 0.5,
"enhancements": true,
"charsEachUpdate": 50, // Lower = smoother update rate, but more update lag
"keep_alive": -1, // For ollama. -1 means forever
"useHistory": false,
"safety": true,
"writingCursor": " ...", // Warning: Using weird characters can mess up Markdown rendering
"proxyUrl": null, // Can be "socks5://127.0.0.1:9050" or "http://127.0.0.1:8080" for example. Leave it blank if you don't need it.
"extraGptModels": {
// Below is an example. Copy to user_options.jsonc and edit it
// The base url is conveniently ollama's btw
// "model_id": {
// "name": "User-added model",
// "logo_name": "ollama-symbolic",
// "description": "A model added by the user",
// "base_url": "http://localhost:11434/v1/chat/completions",
// "key_get_url": "",
// "requires_key": false,
// "key_file": "api_key_file.txt",
// "model": "model-name"
// },
}
},
"animations": {
"choreographyDelay": 35,
"durationSmall": 110,
"durationLarge": 180
},
"appearance": {
"autoDarkMode": { // Turns on dark mode in certain hours. Time in 24h format
"enabled": false,
"from": "18:10",
"to": "6:10"
},
"borderless": false, // Uhm experimental...
"keyboardUseFlag": false, // Use flag emoji instead of abbreviation letters
"layerSmoke": false,
"layerSmokeStrength": 0.2,
"barRoundCorners": 1, // 0: No, 1: Yes
"fakeScreenRounding": 2 // 0: None | 1: Always | 2: When not fullscreen
},
"apps": {
"bluetooth": "blueberry",
"imageViewer": "loupe",
"network": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi",
"settings": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center",
"taskManager": "gnome-usage",
"terminal": "foot" // This is only for shell actions
},
"bar": {
// Whether to show Swap and CPU usage when there's media. RAM is always shown.
"alwaysShowFullResources": false,
// Array of bar modes for each monitor. Hit Ctrl+Alt+Slash to cycle.
// Modes: "normal", "focus" (workspace indicator only), "nothing"
// Example for four monitors: ["normal", "focus", "normal", "nothing"]
"modes": [
"normal"
]
},
"battery": {
"low": 20,
"critical": 10,
"warnLevels": [
20,
15,
5
],
"warnTitles": [
"Low battery",
"Very low battery",
"Critical Battery"
],
"warnMessages": [
"Plug in the charger",
"You there?",
"PLUG THE CHARGER ALREADY"
],
"suspendThreshold": 3
},
"brightness": {
// Object of controller names for each monitor, either "brightnessctl" or "ddcutil" or "auto"
// "default" one will be used if unspecified
// Examples
// "eDP-1": "brightnessctl",
// "DP-1": "ddcutil",
"controllers": {
"default": "auto"
}
},
"cheatsheet": {
"keybinds": {
"configPath": "" // Path to hyprland keybind config file. Leave empty for default (~/.config/hypr/hyprland/keybinds.conf)
}
},
"gaming": {
"crosshair": {
"size": 20,
"color": "rgba(113,227,32,0.9)"
}
},
"i18n": {
"langCode": "", //Customize the locale, such as zh_CN,Optional value references "~/.config/ags/i18n/locales/"
"extraLogs": false
},
"monitors": {
"scaleMethod": "division" // Either "division" [default] or "gdk"
},
"music": {
"preferredPlayer": "plasma-browser-integration"
},
"onScreenKeyboard": {
"layout": "qwerty_full" // See modules/onscreenkeyboard/onscreenkeyboard.js for available layouts
},
"overview": {
"scale": 0.18, // Relative to screen size
"numOfRows": 2,
"numOfCols": 5,
"wsNumScale": 0.09,
"wsNumMarginScale": 0.07
},
"sidebar": {
"image": {
"columns": 2,
"batchCount": 20,
"allowNsfw": false
},
"pages": {
"order": [
"apis",
"tools"
],
"defaultPage": "apis",
"apis": {
"order": [
"gemini",
"gpt",
"waifu",
"booru"
],
"defaultPage": "gemini"
}
},
"quickToggles": {
"order": [
"wifi",
"bluetooth",
"nightlight",
"gamemode",
"idleinhibitor",
"cloudflarewarp"
]
},
"calendar": {
"expandByDefault": true
}
},
"search": {
"enableFeatures": {
"actions": true,
"commands": true,
"mathResults": true,
"directorySearch": true,
"aiSearch": true,
"webSearch": true
},
"engineBaseUrl": "https://www.google.com/search?q=",
"excludedSites": [
"quora.com"
]
},
"time": {
// See https://docs.gtk.org/glib/method.DateTime.format.html
// Here's the 12h format: "%I:%M%P"
// For seconds, add "%S" and set interval to 1000
"format": "%H:%M",
"interval": 5000,
"dateFormatLong": "%A, %d/%m", // On bar
"dateInterval": 5000,
"dateFormat": "%d/%m", // On notif time
"calendarDateFormat": "%d %B %Y"
},
"weather": {
"city": "",
"preferredUnit": "C" // Either C or F
},
"workspaces": {
"shown": 10
},
"dock": {
"enabled": false,
"hiddenThickness": 5,
"pinnedApps": [
"firefox",
"org.gnome.Nautilus"
],
"layer": "top",
"monitorExclusivity": true, // Dock will move to other monitor along with focus if enabled
"searchPinnedAppIcons": false, // Try to search for the correct icon if the app class isn't an icon name
"trigger": [
"client-added",
"client-removed"
], // client_added, client_move, workspace_active, client_active
// Automatically hide dock after `interval` ms since trigger
"autoHide": [
{
"trigger": "client-added",
"interval": 500
},
{
"trigger": "client-removed",
"interval": 500
}
]
},
// Longer stuff
"icons": {
// Find the window's icon by its class with levenshteinDistance
// The file names are processed at startup, so if there
// are too many files in the search path it'll affect performance
// Example: ["/usr/share/icons/Tela-nord/scalable/apps"]
"searchPaths": [
""
],
"symbolicIconTheme": {
"dark": "Adwaita",
"light": "Adwaita"
},
"substitutions": {
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"GitHub Desktop": "github-desktop",
"Minecraft* 1.20.1": "minecraft",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot",
"zen": "zen-browser",
"": "image-missing"
},
"regexSubstitutions": [
{
"regex": "/^steam_app_(\\d+)$/",
"replace": "steam_icon_$1"
}
]
},
"keybinds": {
// Format: "Modifier_1+...+Modifier_n+key". The key is CaSe SeNsItIvE!
// Modifiers: Shift Ctrl Alt Hyper Meta
// See https://docs.gtk.org/gdk3/index.html#constants for keys (listed as KEY_key)
// You can assign multiple keybinds for the same action. Just split them with a comma
// Example: "Ctrl+Page_Down, ctrl+Tab"
"overview": {
"altMoveLeft": "Ctrl+B",
"altMoveRight": "Ctrl+F",
"deleteToEnd": "Ctrl+K"
},
"sidebar": {
"apis": {
"nextTab": "Page_Down",
"prevTab": "Page_Up"
},
"options": { // Right sidebar
"nextTab": "Page_Down",
"prevTab": "Page_Up"
},
"expand": "Ctrl+E",
"pin": "Ctrl+P",
"cycleTab": "Ctrl+Tab",
"nextTab": "Ctrl+Page_Down",
"prevTab": "Ctrl+Page_Up"
},
"cheatsheet": {
"keybinds": {
"nextTab": "Page_Down",
"prevTab": "Page_Up"
},
"nextTab": "Ctrl+Page_Down",
"prevTab": "Ctrl+Page_Up",
"cycleTab": "Ctrl+Tab"
}
}
}
@@ -1,36 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
import { parseJSONC } from '../.miscutils/jsonc.js';
function overrideConfigRecursive(userOverrides, configOptions = {}) {
for (const [key, value] of Object.entries(userOverrides)) {
if (typeof value === 'object'
&& !(value instanceof Array)
&& configOptions[key]) {
overrideConfigRecursive(value, configOptions[key]);
}
else {
configOptions[key] = value;
}
}
}
// Load default options from ~/.config/ags/modules/.configuration/default_options.jsonc
const defaultConfigFile = `${App.configDir}/modules/.configuration/default_options.jsonc`;
const defaultConfigFileContents = Utils.readFile(defaultConfigFile);
const defaultConfigOptions = parseJSONC(defaultConfigFileContents);
// Clone the default config to avoid modifying the original
let configOptions = JSON.parse(JSON.stringify(defaultConfigOptions));
// Load user overrides
const userOverrideFile = `${App.configDir}/user_options.jsonc`;
const userOverrideContents = Utils.readFile(userOverrideFile);
const userOverrides = parseJSONC(userOverrideContents);
// Override defaults with user's options
overrideConfigRecursive(userOverrides, configOptions);
globalThis['userOptionsDefaults'] = defaultConfigOptions;
globalThis['userOptions'] = configOptions;
export default configOptions;
-14
View File
@@ -1,14 +0,0 @@
const { Gio, GLib, Gtk } = imports.gi;
export function fileExists(filePath) {
let file = Gio.File.new_for_path(filePath);
return file.query_exists(null);
}
export function expandTilde(path) {
if (path.startsWith('~')) {
return GLib.get_home_dir() + path.slice(1);
} else {
return path;
}
}
-28
View File
@@ -1,28 +0,0 @@
const { Gtk } = imports.gi;
export function iconExists(iconName) {
let iconTheme = Gtk.IconTheme.get_default();
return iconTheme.has_icon(iconName);
}
export function substitute(str) {
// Normal substitutions
if (userOptions.icons.substitutions[str])
return userOptions.icons.substitutions[str];
// Regex substitutions
for (let i = 0; i < userOptions.icons.regexSubstitutions.length; i++) {
const substitution = userOptions.icons.regexSubstitutions[i];
const replacedName = str.replace(
substitution.regex,
substitution.replace,
);
if (replacedName != str) return replacedName;
}
// Guess: convert to kebab case
if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, "-");
// Original string
return str;
}
-58
View File
@@ -1,58 +0,0 @@
export function parseJSONC(jsoncString) {
let result = "";
let inString = false;
let inSingleQuote = false;
let inMultiLineComment = false;
let inSingleLineComment = false;
for (let i = 0; i < jsoncString.length; i++) {
let char = jsoncString[i];
let nextChar = jsoncString[i + 1];
// Handle string start/end
if (!inSingleLineComment && !inMultiLineComment) {
if (char === '"' && !inSingleQuote) {
inString = !inString;
} else if (char === "'" && !inString) {
inSingleQuote = !inSingleQuote;
}
}
// Handle single-line comments //
if (!inString && !inSingleQuote && !inMultiLineComment && char === '/' && nextChar === '/') {
inSingleLineComment = true;
i++; // Skip next '/'
continue;
}
// Handle multi-line comments /*
if (!inString && !inSingleQuote && !inSingleLineComment && char === '/' && nextChar === '*') {
inMultiLineComment = true;
i++; // Skip next '*'
continue;
}
// End single-line comment at newline
if (inSingleLineComment && (char === '\n' || char === '\r')) {
inSingleLineComment = false;
}
// End multi-line comment */
if (inMultiLineComment && char === '*' && nextChar === '/') {
inMultiLineComment = false;
i++; // Skip next '/'
continue;
}
// Only append characters if not inside a comment
if (!inSingleLineComment && !inMultiLineComment) {
result += char;
}
}
// Remove trailing commas from objects and arrays
result = result.replace(/,\s*([\]}])/g, '$1');
// Parse as JSON
return JSON.parse(result);
}
@@ -1,16 +0,0 @@
export function clamp(x, min, max) {
return Math.min(Math.max(x, min), max);
}
export function truncateToPrecision(value, precision) {
const factor = Math.pow(10, precision);
const result = Math.round(value * factor) / factor;
return result;
}
export function distance(x1, y1, x2, y2) {
const distanceX = Math.abs(x1 - x2);
const distanceY = Math.abs(y1 - y2);
return Math.sqrt(distanceX * distanceX + distanceY * distanceY)
}
@@ -1,98 +0,0 @@
// Converts from Markdown to Pango. This does not support code blocks.
// For illogical-impulse, code blocks are treated separately, in their own GtkSourceView widgets.
// Partly inherited from https://github.com/ubunatic/md2pango
const monospaceFonts = 'JetBrains Mono NF, JetBrains Mono Nerd Font, JetBrains Mono NL, SpaceMono NF, SpaceMono Nerd Font, monospace';
const codeBlockRegex = /^\s*```([a-zA-Z0-9]+)?\n?/;
const replacements = {
'indents': [
{ name: 'BULLET', re: /^(\s*)([\*\-]\s)(.*)(\s*)$/, sub: ' $1- $3' },
{ name: 'NUMBERING', re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: ' $1 $2' },
],
'escapes': [
{ name: 'COMMENT', re: /<!--[\s\S]*?-->/, sub: '' },
{ name: 'AMPERSTAND', re: /&/g, sub: '&amp;' },
{ name: 'LESSTHAN', re: /</g, sub: '&lt;' },
{ name: 'GREATERTHAN', re: />/g, sub: '&gt;' },
],
'sections': [
{ name: 'H1', re: /^(#\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="170%">$2</span>' },
{ name: 'H2', re: /^(##\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="150%">$2</span>' },
{ name: 'H3', re: /^(###\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="125%">$2</span>' },
{ name: 'H4', re: /^(####\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="100%">$2</span>' },
{ name: 'H5', re: /^(#####\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="90%">$2</span>' },
],
'styles': [
{ name: 'BOLD', re: /(\*\*)(\S[\s\S]*?\S)(\*\*)/g, sub: "<b>$2</b>" },
{ name: 'UND', re: /(__)(\S[\s\S]*?\S)(__)/g, sub: "<u>$2</u>" },
{ name: 'EMPH', re: /\*(\S.*?\S)\*/g, sub: "<i>$1</i>" },
// { name: 'EMPH', re: /_(\S.*?\S)_/g, sub: "<i>$1</i>" },
{ name: 'HEXCOLOR', re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: '<span bgcolor="#$1" fgcolor="#000000" font_family="' + monospaceFonts + '">#$1</span>' },
{ name: 'INLCODE', re: /(`)([^`]*)(`)/g, sub: '<span font_weight="bold" font_family="' + monospaceFonts + '">$2</span>' },
// { name: 'UND', re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "<u>$2</u>" },
],
'forceLatex': [
{ name: 'LATEX_INLINE_SQUARE', re: /\\\[(.*?)\\\]/g, sub: '\n```latex\n$1\n```' },
{ name: 'LATEX_INLINE_ROUND', re: /\\\((.*?)\\\)/g, sub: '\n```latex\n$1\n```' },
{ name: 'LATEX_INLINE_DOLLAR', re: /\$(.*?)\$/g, sub: '\n```latex\n$1\n```' }
]
}
const replaceCategory = (text, replaces) => {
for (const type of replaces) {
text = text.replace(type.re, type.sub);
}
return text;
}
// Main function
export function replaceInlineLatexWithCodeBlocks(text) {
return text.replace(/\\\[(.*?)\\\]|\\\((.*?)\\\)|\$\$(.*?)\$\$|(?:^|[^\w])\$(.*?[^\\])\$(?!\w)/gs, (match, square, round, double, single) => {
const latex = square || round || double || single;
return `\n\`\`\`latex\n${latex}\n\`\`\`\n`;
});
}
export default (text) => {
let lines = text.split('\n')
let output = [];
let inCode = false;
// Replace
for (const line of lines) {
let result = line;
if (codeBlockRegex.test(line)) inCode = !inCode;
if (inCode) continue;
result = replaceCategory(result, replacements.indents);
result = replaceCategory(result, replacements.escapes);
result = replaceCategory(result, replacements.sections);
result = replaceCategory(result, replacements.styles);
output.push(result)
}
// Remove trailing whitespaces
output = output.map(line => line.replace(/ +$/, ''))
return output.join('\n');
}
export const markdownTest = `## Inline formatting
- **Bold** *Italics* __Underline__
- \`Monospace text\` 🤓
- Colors
- Nvidia green #7ABB08
- Soundcloud orange #FF5500
## Code block
\`\`\`cpp
#include <bits/stdc++.h>
const std::string GREETING = "UwU";
int main(int argc, char* argv[]) {
std::cout << GREETING;
}
\`\`\`
## LaTeX
- Inline LaTeX: \\[ \\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} \\]
- Block LaTeX:
\`\`\`latex
\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} \\\\\\\\ cos(2x) = 2cos^2(x) - 1 = 1 - 2sin^2(x) = cos^2(x) - sin^2(x)
\`\`\`
`;
-33
View File
@@ -1,33 +0,0 @@
export function getNestedProperty(obj, path) {
return path.split('.').reduce((current, key) => {
return (current && typeof current === 'object' && current.hasOwnProperty(key)) ? current[key] : undefined;
}, obj);
}
export function updateNestedProperty(obj, path, newValue) {
const pathArray = path.split('.');
const lastKeyIndex = pathArray.length - 1;
let current = obj;
for (let i = 0; i < lastKeyIndex; i++) {
const key = pathArray[i];
if (!current || typeof current !== 'object') {
return false; // Previous part of path is not an object
}
if (!current.hasOwnProperty(key)) {
current[key] = {}; // Create the missing object
}
current = current[key];
}
const lastKey = pathArray[lastKeyIndex];
if (!current || typeof current !== 'object') {
return false; // Parent is not an object
}
current[lastKey] = newValue;
return true;
}
-61
View File
@@ -1,61 +0,0 @@
const { GLib } = imports.gi;
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
export const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2 | sed "s/\\"//g"'`).trim();
export const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'popos' || distroID == 'raspbian' || distroID == 'kali');
export const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros' || distroID == 'cachyos');
export const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`);
const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
export const darkMode = Variable(!(Utils.readFile(LIGHTDARK_FILE_LOCATION).split('\n')[0].trim() == 'light'));
darkMode.connect('changed', ({ value }) => {
let lightdark = value ? "dark" : "light";
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "1s/.*/${lightdark}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
.then(execAsync(['bash', '-c', `command -v darkman && darkman set ${lightdark}`])) // Optional darkman integration
.catch(print);
});
globalThis['darkMode'] = darkMode;
export const hasPlasmaIntegration = !!Utils.exec('bash -c "command -v plasma-browser-integration-host"');
export const getDistroIcon = () => {
// Arches
if(distroID == 'arch') return 'arch-symbolic';
if(distroID == 'endeavouros') return 'endeavouros-symbolic';
if(distroID == 'cachyos') return 'cachyos-symbolic';
// Funny flake
if(distroID == 'nixos') return 'nixos-symbolic';
// Cool thing
if(distroID == 'fedora') return 'fedora-symbolic';
// Debians
if(distroID == 'linuxmint') return 'ubuntu-symbolic';
if(distroID == 'ubuntu') return 'ubuntu-symbolic';
if(distroID == 'debian') return 'debian-symbolic';
if(distroID == 'zorin') return 'ubuntu-symbolic';
if(distroID == 'popos') return 'ubuntu-symbolic';
if(distroID == 'raspbian') return 'debian-symbolic';
if(distroID == 'kali') return 'debian-symbolic';
return 'linux-symbolic';
}
export const getDistroName = () => {
// Arches
if(distroID == 'arch') return 'Arch Linux';
if(distroID == 'endeavouros') return 'EndeavourOS';
if(distroID == 'cachyos') return 'CachyOS';
// Funny flake
if(distroID == 'nixos') return 'NixOS';
// Cool thing
if(distroID == 'fedora') return 'Fedora';
// Debians
if(distroID == 'linuxmint') return 'Linux Mint';
if(distroID == 'ubuntu') return 'Ubuntu';
if(distroID == 'debian') return 'Debian';
if(distroID == 'zorin') return 'Zorin';
if(distroID == 'popos') return 'Pop!_OS';
if(distroID == 'raspbian') return 'Raspbian';
if(distroID == 'kali') return 'Kali Linux';
return 'Linux';
}
@@ -1,86 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Revealer, Scrollable } = Widget;
export const MarginRevealer = ({
transition = 'slide_down',
child,
revealChild,
showClass = 'element-show', // These are for animation curve, they don't really hide
hideClass = 'element-hide', // Don't put margins in these classes!
extraSetup = () => { },
...rest
}) => {
const widget = Scrollable({
...rest,
attribute: {
'revealChild': true, // It'll be set to false after init if it's supposed to hide
'transition': transition,
'show': () => {
if (widget.attribute.revealChild) return;
widget.hscroll = 'never';
widget.vscroll = 'never';
child.toggleClassName(hideClass, false);
child.toggleClassName(showClass, true);
widget.attribute.revealChild = true;
child.css = 'margin: 0px;';
},
'hide': () => {
if (!widget.attribute.revealChild) return;
child.toggleClassName(hideClass, true);
child.toggleClassName(showClass, false);
widget.attribute.revealChild = false;
if (widget.attribute.transition == 'slide_left')
child.css = `margin-right: -${child.get_allocated_width()}px;`;
else if (widget.attribute.transition == 'slide_right')
child.css = `margin-left: -${child.get_allocated_width()}px;`;
else if (widget.attribute.transition == 'slide_up')
child.css = `margin-bottom: -${child.get_allocated_height()}px;`;
else if (widget.attribute.transition == 'slide_down')
child.css = `margin-top: -${child.get_allocated_height()}px;`;
},
'toggle': () => {
if (widget.attribute.revealChild) widget.attribute.hide();
else widget.attribute.show();
},
},
child: child,
hscroll: `${revealChild ? 'never' : 'always'}`,
vscroll: `${revealChild ? 'never' : 'always'}`,
setup: (self) => {
extraSetup(self);
}
});
child.toggleClassName(`${revealChild ? showClass : hideClass}`, true);
return widget;
}
// TODO: Allow reveal update. Currently this just helps at declaration
export const DoubleRevealer = ({
transition1 = 'slide_right',
transition2 = 'slide_left',
duration1 = 150,
duration2 = 150,
child,
revealChild,
...rest
}) => {
const r2 = Revealer({
transition: transition2,
transitionDuration: duration2,
revealChild: revealChild,
child: child,
});
const r1 = Revealer({
transition: transition1,
transitionDuration: duration1,
revealChild: revealChild,
child: r2,
...rest,
})
r1.toggleRevealChild = (value) => {
r1.revealChild = value;
r2.revealChild = value;
}
return r1;
}
@@ -1,36 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, Window } = Widget;
export default ({
name,
child,
showClassName = "",
hideClassName = "",
...props
}) => {
return Window({
name,
visible: false,
layer: 'top',
...props,
child: Box({
setup: (self) => {
self.keybind("Escape", () => closeEverything());
if (showClassName != "" && hideClassName !== "") {
self.hook(App, (self, currentName, visible) => {
if (currentName === name) {
self.toggleClassName(hideClassName, !visible);
}
});
if (showClassName !== "" && hideClassName !== "")
self.className = `${showClassName} ${hideClassName}`;
}
},
child: child,
}),
});
}
@@ -1,4 +0,0 @@
import Cairo from 'gi://cairo?version=1.0';
export const dummyRegion = new Cairo.Region();
export const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion);
@@ -1,36 +0,0 @@
// Cursor names reference: https://docs.gtk.org/gdk4/ctor.Cursor.new_from_name.html
const { Gdk } = imports.gi;
export function setupCursorHover(button, cursorName = 'pointer') { // Hand pointing cursor on hover
const display = Gdk.Display.get_default();
button.connect('enter-notify-event', () => {
const cursor = Gdk.Cursor.new_from_name(display, cursorName);
button.get_window().set_cursor(cursor);
});
button.connect('leave-notify-event', () => {
const cursor = Gdk.Cursor.new_from_name(display, 'default');
button.get_window().set_cursor(cursor);
});
}
export function setupCursorHoverAim(button) { // Crosshair cursor on hover
setupCursorHover(button, 'crosshair');
}
export function setupCursorHoverGrab(button) { // Hand ready to grab on hover
setupCursorHover(button, 'grab');
}
export function setupCursorHoverInfo(button) { // "?" mark cursor on hover
setupCursorHover(button, 'help');
}
export function setupCursorHoverHResize(button) { // Resize left right
setupCursorHover(button, 'ew-resize');
}
export function setupCursorHoverVResize(button) { // Resize up down
setupCursorHover(button, 'ns-resize');
}
@@ -1,34 +0,0 @@
const { Gdk } = imports.gi;
const MODS = {
'shift': Gdk.ModifierType.SHIFT_MASK,
'ctrl': Gdk.ModifierType.CONTROL_MASK,
'alt': Gdk.ModifierType.ALT_MASK,
'hyper': Gdk.ModifierType.HYPER_MASK,
'meta': Gdk.ModifierType.META_MASK
}
const checkSingleKeybind = (event, keybind) => {
const pressedModMask = event.get_state()[1];
const pressedKey = event.get_keyval()[1];
const keys = keybind.split('+');
for (let i = 0; i < keys.length; i++) {
if (keys[i].toLowerCase() in MODS) {
if (!(pressedModMask & MODS[keys[i].toLowerCase()])) {
return false;
}
} else if (pressedKey !== Gdk[`KEY_${keys[i]}`] && pressedKey !== Gdk[`KEY_${keys[i].toLowerCase()}`]) {
return false;
}
}
return true;
}
export const checkKeybind = (event, keybind) => {
const keybinds = keybind.replace(' ', '').split(',');
for (let i = 0; i < keybinds.length; i++) {
if (checkSingleKeybind(event, keybinds[i])) {
return true;
}
}
}
@@ -1,213 +0,0 @@
const { GLib, Gdk, Gtk } = imports.gi;
const Lang = imports.lang;
const Cairo = imports.cairo;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
import App from 'resource:///com/github/Aylur/ags/app.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, DrawingArea, EventBox } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const dummyWs = Box({ className: 'bar-ws-focus' }); // Not shown. Only for getting size props
const dummyActiveWs = Box({ className: 'bar-ws-focus bar-ws-focus-active' }); // Not shown. Only for getting size props
const dummyOccupiedWs = Box({ className: 'bar-ws-focus bar-ws-focus-occupied' }); // Not shown. Only for getting size props
const WS_TAKEN_WIDTH_MULTIPLIER = 1.4;
const floor = Math.floor;
const ceil = Math.ceil;
// Font size = workspace id
const WorkspaceContents = (count = 10) => {
return DrawingArea({
className: 'menu-decel',
attribute: {
lastImmediateActiveWs: 0,
immediateActiveWs: 0,
initialized: false,
workspaceMask: 0,
workspaceGroup: 0,
updateMask: (self) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
// if (self.attribute.initialized) return; // We only need this to run once
const workspaces = Hyprland.workspaces;
let workspaceMask = 0;
for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[i];
if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
if (workspaces[i].windows > 0)
workspaceMask |= (1 << (ws.id - offset));
}
// console.log('Mask:', workspaceMask.toString(2));
self.attribute.workspaceMask = workspaceMask;
// self.attribute.initialized = true;
self.queue_draw();
},
toggleMask: (self, occupied, name) => {
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
self.queue_draw();
},
},
setup: (area) => area
.hook(Hyprland.active.workspace, (self) => {
const newActiveWs = (Hyprland.active.workspace.id - 1) % count + 1;
self.setCss(`font-size: ${newActiveWs}px;`);
self.attribute.lastImmediateActiveWs = self.attribute.immediateActiveWs;
self.attribute.immediateActiveWs = newActiveWs;
const previousGroup = self.attribute.workspaceGroup;
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
if (currentGroup !== previousGroup) {
self.attribute.updateMask(self);
self.attribute.workspaceGroup = currentGroup;
}
})
.hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
.on('draw', Lang.bind(area, (area, cr) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
const allocation = area.get_allocation();
const { width, height } = allocation;
const workspaceStyleContext = dummyWs.get_style_context();
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const workspaceRadius = workspaceDiameter / 2;
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
const activeWorkspaceWidth = activeWorkspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
// const activeWorkspaceWidth = 100;
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const widgetStyleContext = area.get_style_context();
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
const lastImmediateActiveWs = area.attribute.lastImmediateActiveWs;
const immediateActiveWs = area.attribute.immediateActiveWs;
// Draw
area.set_size_request(workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth, -1);
for (let i = 1; i <= count; i++) {
if (i == immediateActiveWs) continue;
let colors = {};
if (area.attribute.workspaceMask & (1 << i)) colors = occupiedbg;
else colors = wsbg;
// if ((i == immediateActiveWs + 1 && immediateActiveWs < activeWs) ||
// (i == immediateActiveWs + 1 && immediateActiveWs < activeWs)) {
// const widthPercentage = (i == immediateActiveWs - 1) ?
// 1 - (immediateActiveWs - activeWs) :
// activeWs - immediateActiveWs;
// cr.setSourceRGBA(colors.red * widthPercentage + activebg.red * (1 - widthPercentage),
// colors.green * widthPercentage + activebg.green * (1 - widthPercentage),
// colors.blue * widthPercentage + activebg.blue * (1 - widthPercentage),
// colors.alpha);
// }
// else
cr.setSourceRGBA(colors.red, colors.green, colors.blue, colors.alpha)
const centerX = (i <= activeWs) ?
(-workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * i))
: -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - i) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER);
cr.arc(centerX, height / 2, workspaceRadius, 0, 2 * Math.PI);
cr.fill();
// What if shrinking
if (i == floor(activeWs) && immediateActiveWs > activeWs) { // To right
const widthPercentage = 1 - (ceil(activeWs) - activeWs);
const leftX = centerX;
const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage);
cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter);
cr.fill();
cr.arc(leftX + wsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
cr.fill();
}
else if (i == ceil(activeWs) && immediateActiveWs < activeWs) { // To left
const widthPercentage = activeWs - floor(activeWs);
const rightX = centerX;
const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage;
const leftX = rightX - wsWidth;
cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter);
cr.fill();
cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2);
cr.fill();
}
}
let widthPercentage, leftX, rightX, activeWsWidth;
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
if (immediateActiveWs > activeWs) { // To right
const immediateActiveWs = ceil(activeWs);
widthPercentage = immediateActiveWs - activeWs;
rightX = -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - immediateActiveWs) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER);
activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage);
leftX = rightX - activeWsWidth;
cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap
cr.fill();
cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter);
cr.fill();
cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
cr.fill();
}
else { // To left
const immediateActiveWs = floor(activeWs);
widthPercentage = 1 - (activeWs - immediateActiveWs);
leftX = -workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * immediateActiveWs);
activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage
cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap
cr.fill();
cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter);
cr.fill();
cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
cr.fill();
}
}))
,
})
}
export default () => EventBox({
onScrollUp: () => Hyprland.messageAsync(`dispatch workspace r-1`).catch(print),
onScrollDown: () => Hyprland.messageAsync(`dispatch workspace r+1`).catch(print),
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
onSecondaryClick: () => App.toggleWindow('overview'),
attribute: {
clicked: false,
ws_group: 0,
},
child: Box({
homogeneous: true,
// className: 'bar-group-margin',
children: [Box({
// className: `bar-group${userOptions.appearance.borderless ? '-borderless' : ''} bar-group-standalone bar-group-pad`,
css: 'min-width: 2px;',
children: [WorkspaceContents(userOptions.workspaces.shown)],
})]
}),
setup: (self) => {
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
self.on('motion-notify-event', (self, event) => {
if (!self.attribute.clicked) return;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
.catch(print);
})
self.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
self.attribute.clicked = true;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
// const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_PER_GROUP / widgetWidth) + self.attribute.ws_group * NUM_OF_WORKSPACES_PER_GROUP;
// Hyprland.messageAsync(`dispatch workspace ${wsId}`).catch(print);
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
.catch(print);
})
self.on('button-release-event', (self) => self.attribute.clicked = false);
}
})
@@ -1,183 +0,0 @@
const { GLib, Gdk, Gtk } = imports.gi;
const Lang = imports.lang;
const Cairo = imports.cairo;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Sway from "../../../services/sway.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
const { execAsync, exec } = Utils;
const { Box, DrawingArea, EventBox } = Widget;
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
const switchToRelativeWorkspace = (self, num) =>
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
const WorkspaceContents = (count = 10) => {
return DrawingArea({
css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
attribute: {
initialized: false,
workspaceMask: 0,
updateMask: (self) => {
if (self.attribute.initialized) return; // We only need this to run once
const workspaces = Sway.workspaces;
let workspaceMask = 0;
// console.log('----------------')
for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[i];
// console.log(ws.name, ',', ws.num);
if (!Number(ws.name)) return;
const id = Number(ws.name);
if (id <= 0) continue; // Ignore scratchpads
if (id > count) return; // Not rendered
if (workspaces[i].windows > 0) {
workspaceMask |= (1 << id);
}
}
self.attribute.workspaceMask = workspaceMask;
self.attribute.initialized = true;
},
toggleMask: (self, occupied, name) => {
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
},
},
setup: (area) => area
.hook(Sway.active.workspace, (area) => {
area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
})
.hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
.on('draw', Lang.bind(area, (area, cr) => {
const allocation = area.get_allocation();
const { width, height } = allocation;
const workspaceStyleContext = dummyWs.get_style_context();
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const workspaceRadius = workspaceDiameter / 2;
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
area.set_size_request(workspaceDiameter * count, -1);
const widgetStyleContext = area.get_style_context();
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
const activeWsCenterY = height / 2;
// Font
const layout = PangoCairo.create_layout(cr);
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
layout.set_font_description(fontDesc);
cr.setAntialias(Cairo.Antialias.BEST);
// Get kinda min radius for number indicators
layout.set_text("0".repeat(count.toString().length), -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
const indicatorGap = workspaceRadius - indicatorRadius;
// Draw workspace numbers
for (let i = 1; i <= count; i++) {
if (area.attribute.workspaceMask & (1 << i)) {
// Draw bg highlight
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
const wsCenterY = height / 2;
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
// Set color for text
cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
}
else
cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
layout.set_text(`${i}`, -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
const y = (height - layoutHeight) / 2;
cr.moveTo(x, y);
// cr.showText(text);
PangoCairo.show_layout(cr, layout);
cr.stroke();
}
// Draw active ws
// base
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
cr.fill();
// inner decor
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
cr.fill();
}))
,
})
}
export default () => EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
onSecondaryClick: () => App.toggleWindow('overview'),
attribute: { clicked: false },
child: Box({
homogeneous: true,
className: 'bar-group-margin',
children: [Box({
className: `bar-group${userOptions.appearance.borderless ? '-borderless' : ''} bar-group-standalone bar-group-pad`,
css: 'min-width: 2px;',
children: [
WorkspaceContents(10),
]
})]
}),
setup: (self) => {
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
self.on('motion-notify-event', (self, event) => {
if (!self.attribute.clicked) return;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
switchToWorkspace(wsId);
})
self.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
self.attribute.clicked = true;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
switchToWorkspace(wsId);
})
self.on('button-release-event', (self) => self.attribute.clicked = false);
}
});
-129
View File
@@ -1,129 +0,0 @@
const { Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import WindowTitle from "./normal/spaceleft.js";
import Indicators from "./normal/spaceright.js";
import Music from "./normal/music.js";
import System from "./normal/system.js";
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
import { currentShellMode } from '../../variables.js';
const NormalOptionalWorkspaces = async () => {
try {
return (await import('./normal/workspaces_hyprland.js')).default();
} catch {
try {
return (await import('./normal/workspaces_sway.js')).default();
} catch {
return null;
}
}
};
const FocusOptionalWorkspaces = async () => {
try {
return (await import('./focus/workspaces_hyprland.js')).default();
} catch {
try {
return (await import('./focus/workspaces_sway.js')).default();
} catch {
return null;
}
}
};
export const Bar = async (monitor = 0) => {
const SideModule = (children) => Widget.Box({
className: 'bar-sidemodule',
children: children,
});
const normalBarContent = Widget.CenterBox({
className: 'bar-bg',
setup: (self) => {
const styleContext = self.get_style_context();
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print);
},
startWidget: (await WindowTitle(monitor)),
centerWidget: Widget.Box({
className: 'spacing-h-4',
children: [
SideModule([Music()]),
Widget.Box({
homogeneous: true,
children: [await NormalOptionalWorkspaces()],
}),
SideModule([System()]),
]
}),
endWidget: Indicators(monitor),
});
const focusedBarContent = Widget.CenterBox({
className: 'bar-bg-focus',
startWidget: Widget.Box({}),
centerWidget: Widget.Box({
className: 'spacing-h-4',
children: [
SideModule([]),
Widget.Box({
homogeneous: true,
children: [await FocusOptionalWorkspaces()],
}),
SideModule([]),
]
}),
endWidget: Widget.Box({}),
setup: (self) => {
self.hook(Battery, (self) => {
if (!Battery.available) return;
self.toggleClassName('bar-bg-focus-batterylow', Battery.percent <= userOptions.battery.low);
})
}
});
const nothingContent = Widget.Box({
className: 'bar-bg-nothing',
})
return Widget.Window({
monitor,
name: `bar${monitor}`,
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
visible: true,
child: Widget.Stack({
homogeneous: false,
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationLarge,
children: {
'normal': normalBarContent,
'focus': focusedBarContent,
'nothing': nothingContent,
},
setup: (self) => self.hook(currentShellMode, (self) => {
self.shown = currentShellMode.value[monitor];
})
}),
});
}
export const BarCornerTopleft = (monitor = 0) => Widget.Window({
monitor,
name: `barcornertl${monitor}`,
layer: 'top',
anchor: ['top', 'left'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('topleft', { className: 'corner', }),
setup: enableClickthrough,
});
export const BarCornerTopright = (monitor = 0) => Widget.Window({
monitor,
name: `barcornertr${monitor}`,
layer: 'top',
anchor: ['top', 'right'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('topright', { className: 'corner', }),
setup: enableClickthrough,
});
-241
View File
@@ -1,241 +0,0 @@
const { GLib } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable } = Widget;
const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js";
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { showMusicControls } from '../../../variables.js';
const CUSTOM_MODULE_CONTENT_INTERVAL_FILE = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-interval.txt`;
const CUSTOM_MODULE_CONTENT_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-poll.sh`;
const CUSTOM_MODULE_LEFTCLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-leftclick.sh`;
const CUSTOM_MODULE_RIGHTCLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-rightclick.sh`;
const CUSTOM_MODULE_MIDDLECLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-middleclick.sh`;
const CUSTOM_MODULE_SCROLLUP_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-scrollup.sh`;
const CUSTOM_MODULE_SCROLLDOWN_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-scrolldown.sh`;
function trimTrackTitle(title) {
if (!title) return '';
const cleanPatterns = [
/【[^】]*】/, // Touhou n weeb stuff
" [FREE DOWNLOAD]", // F-777
];
cleanPatterns.forEach((expr) => title = title.replace(expr, ''));
return title;
}
function adjustVolume(direction) {
const step = 0.1; // We use a larger step because this is player instance volume, not global
const mpris = Mpris.getPlayer('');
mpris.volume += (direction === 'up') ? step : -step
}
const BarGroup = ({ child }) => Box({
className: 'bar-group-margin bar-sides',
children: [
Box({
className: `bar-group${userOptions.appearance.borderless ? '-borderless' : ''} bar-group-standalone bar-group-pad-system`,
children: [child],
}),
]
});
const BarResource = (name, icon, command, circprogClassName = `bar-batt-circprog ${userOptions.appearance.borderless ? 'bar-batt-circprog-borderless' : ''}`, textClassName = 'txt-onSurfaceVariant', iconClassName = 'bar-batt') => {
const resourceCircProg = AnimatedCircProg({
className: `${circprogClassName}`,
vpack: 'center',
hpack: 'center',
});
const resourceProgress = Box({
homogeneous: true,
children: [Overlay({
child: Box({
vpack: 'center',
className: `${iconClassName}`,
homogeneous: true,
children: [
MaterialIcon(icon, 'small'),
],
}),
overlays: [resourceCircProg]
})]
});
const resourceLabel = Label({
className: `txt-smallie ${textClassName}`,
});
const widget = Button({
onClicked: () => Utils.execAsync(['bash', '-c', `${userOptions.apps.taskManager}`]).catch(print),
child: Box({
className: `spacing-h-4 ${textClassName}`,
children: [
resourceProgress,
resourceLabel,
],
setup: (self) => self.poll(5000, () => execAsync(['bash', '-c', command])
.then((output) => {
resourceCircProg.css = `font-size: ${Number(output)}px;`;
resourceLabel.label = `${Math.round(Number(output))}%`;
widget.tooltipText = `${name}: ${Math.round(Number(output))}%`;
}).catch(print))
,
})
});
return widget;
}
const TrackProgress = () => {
const _updateProgress = (circprog) => {
const mpris = Mpris.getPlayer('');
if (!mpris)
circprog.css = `font-size: ${userOptions.appearance.borderless ? 100 : 0}px;`
else // Set circular progress value
circprog.css = `font-size: ${Math.max(mpris.position / mpris.length * 100, 0)}px;`
}
return AnimatedCircProg({
className: `bar-music-circprog ${userOptions.appearance.borderless ? 'bar-music-circprog-borderless' : ''}`,
vpack: 'center', hpack: 'center',
extraSetup: (self) => self
.hook(Mpris, _updateProgress)
.poll(3000, _updateProgress)
,
})
}
const switchToRelativeWorkspace = async (self, num) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.messageAsync(`dispatch workspace ${num > 0 ? '+' : ''}${num}`).catch(print);
} catch {
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
}
}
export default () => {
// TODO: use cairo to make button bounce smaller on click, if that's possible
const playingState = Box({ // Wrap a box cuz overlay can't have margins itself
homogeneous: true,
children: [Overlay({
child: Box({
vpack: 'center',
className: 'bar-music-playstate',
homogeneous: true,
children: [Label({
vpack: 'center',
className: 'bar-music-playstate-txt',
justification: 'center',
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}),
})],
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
if (!mpris) return;
label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
}),
}),
overlays: [
TrackProgress(),
]
})]
});
const trackTitle = Label({
hexpand: true,
className: 'txt-smallie bar-music-txt',
truncate: 'end',
maxWidthChars: 1, // Doesn't matter, just needs to be non negative
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
if (mpris)
label.label = `${trimTrackTitle(mpris.trackTitle)}${mpris.trackArtists.join(', ')}`;
else
label.label = getString('No media');
}),
})
const musicStuff = Box({
className: 'spacing-h-10',
hexpand: true,
children: [
playingState,
trackTitle,
]
})
const SystemResourcesOrCustomModule = () => {
// Check if $XDG_CACHE_HOME/ags/user/scripts/custom-module-poll.sh exists
if (GLib.file_test(CUSTOM_MODULE_CONTENT_SCRIPT, GLib.FileTest.EXISTS)) {
const interval = Number(Utils.readFile(CUSTOM_MODULE_CONTENT_INTERVAL_FILE)) || 5000;
return BarGroup({
child: Button({
child: Label({
className: 'txt-smallie txt-onSurfaceVariant',
useMarkup: true,
setup: (self) => Utils.timeout(1, () => {
self.label = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
self.poll(interval, (self) => {
const content = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
self.label = content;
})
})
}),
onPrimaryClickRelease: () => execAsync(CUSTOM_MODULE_LEFTCLICK_SCRIPT).catch(print),
onSecondaryClickRelease: () => execAsync(CUSTOM_MODULE_RIGHTCLICK_SCRIPT).catch(print),
onMiddleClickRelease: () => execAsync(CUSTOM_MODULE_MIDDLECLICK_SCRIPT).catch(print),
onScrollUp: () => execAsync(CUSTOM_MODULE_SCROLLUP_SCRIPT).catch(print),
onScrollDown: () => execAsync(CUSTOM_MODULE_SCROLLDOWN_SCRIPT).catch(print),
})
});
} else return BarGroup({
child: Box({
children: [
BarResource(getString('RAM Usage'), 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
`bar-ram-circprog ${userOptions.appearance.borderless ? 'bar-ram-circprog-borderless' : ''}`, 'bar-ram-txt', 'bar-ram-icon'),
Revealer({
revealChild: true,
transition: 'slide_left',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
className: 'spacing-h-10 margin-left-10',
children: [
BarResource(getString('Swap Usage'), 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
`bar-swap-circprog ${userOptions.appearance.borderless ? 'bar-swap-circprog-borderless' : ''}`, 'bar-swap-txt', 'bar-swap-icon'),
BarResource(getString('CPU Usage'), 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`,
`bar-cpu-circprog ${userOptions.appearance.borderless ? 'bar-cpu-circprog-borderless' : ''}`, 'bar-cpu-txt', 'bar-cpu-icon'),
]
}),
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
self.revealChild = (!mpris || mpris.playBackStatus !== 'Playing' || userOptions.bar.alwaysShowFullResources);
}),
})
],
})
});
}
return EventBox({
onScrollUp: () => adjustVolume('up'),
onScrollDown: () => adjustVolume('down'),
child: Box({
className: 'spacing-h-4',
children: [
SystemResourcesOrCustomModule(),
EventBox({
child: BarGroup({ child: musicStuff }),
onPrimaryClick: () => showMusicControls.setValue(!showMusicControls.value),
onSecondaryClick: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
onMiddleClick: () => execAsync('playerctl play-pause').catch(print),
setup: (self) => self.on('button-press-event', (self, event) => {
if (event.get_button()[1] === 8) // Side button
execAsync('playerctl previous').catch(print)
}),
})
]
})
});
}
@@ -1,96 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../../services/brightness.js';
import Indicator from '../../../services/indicator.js';
import { distance } from '../../.miscutils/mathfuncs.js';
const OSD_DISMISS_DISTANCE = 10;
const WindowTitle = async () => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
return Widget.Scrollable({
hexpand: true, vexpand: true,
hscroll: 'automatic', vscroll: 'never',
child: Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
truncate: 'end',
maxWidthChars: 1, // Doesn't matter, just needs to be non negative
className: 'txt-smaller bar-wintitle-topdesc txt',
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
label.label = Hyprland.active.client.class.length === 0 ? 'Desktop' : Hyprland.active.client.class;
}),
}),
Widget.Label({
xalign: 0,
truncate: 'end',
maxWidthChars: 1, // Doesn't matter, just needs to be non negative
className: 'txt-smallie bar-wintitle-txt',
setup: (self) => {
self.hook(Hyprland.active.client, label => { // Hyprland.active.client
label.label = Hyprland.active.client.title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client.title;
});
self.hook(Hyprland.active.workspace, label => { // Hyprland.active.client
label.label = Hyprland.active.client.title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client.title;
});
}
})
]
})
});
} catch {
return null;
}
}
export default async (monitor = 0) => {
const optionalWindowTitleInstance = await WindowTitle();
let scrollCursorX, scrollCursorY;
return Widget.EventBox({
onScrollUp: (self, event) => {
let _;
[_, scrollCursorX, scrollCursorY] = event.get_coords();
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness[monitor].screen_value += 0.05;
},
onScrollDown: (self, event) => {
let _;
[_, scrollCursorX, scrollCursorY] = event.get_coords();
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness[monitor].screen_value -= 0.05;
},
onPrimaryClick: () => {
App.toggleWindow('sideleft');
},
setup: (self) => self.on('motion-notify-event', (self, event) => {
const [_, cursorX, cursorY] = event.get_coords();
if (distance(cursorX, cursorY, scrollCursorX, scrollCursorY) >= OSD_DISMISS_DISTANCE)
Indicator.popup(-1);
}),
child: Widget.Box({
homogeneous: false,
children: [
Widget.Box({ className: 'bar-corner-spacing' }),
Widget.Overlay({
overlays: [
Widget.Box({ hexpand: true }),
Widget.Box({
className: 'bar-sidemodule', hexpand: true,
children: [Widget.Box({
vertical: true,
className: 'bar-space-button',
children: [
optionalWindowTitleInstance,
]
})]
}),
]
})
]
})
});
}
@@ -1,106 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { execAsync } = Utils;
import Indicator from '../../../services/indicator.js';
import { StatusIcons } from '../../.commonwidgets/statusicons.js';
import { Tray } from "./tray.js";
import { distance } from '../../.miscutils/mathfuncs.js';
const OSD_DISMISS_DISTANCE = 10;
const SeparatorDot = () => Widget.Revealer({
transition: 'slide_left',
revealChild: false,
attribute: {
'count': SystemTray.items.length,
'update': (self, diff) => {
self.attribute.count += diff;
self.revealChild = (self.attribute.count > 0);
}
},
child: Widget.Box({
vpack: 'center',
className: 'separator-circle',
}),
setup: (self) => self
.hook(SystemTray, (self) => self.attribute.update(self, 1), 'added')
.hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
,
});
export default (monitor = 0) => {
const barTray = Tray();
const barStatusIcons = StatusIcons({
className: 'bar-statusicons',
setup: (self) => self.hook(App, (self, currentName, visible) => {
if (currentName === 'sideright') {
self.toggleClassName('bar-statusicons-active', visible);
}
}),
}, monitor);
const SpaceRightInteractions = (child) => Widget.EventBox({
onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
onPrimaryClick: () => App.toggleWindow('sideright'),
onSecondaryClick: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
onMiddleClick: () => execAsync('playerctl play-pause').catch(print),
setup: (self) => self.on('button-press-event', (self, event) => {
if (event.get_button()[1] === 8)
execAsync('playerctl previous').catch(print)
}).on('motion-notify-event', (self, event) => {
Indicator.popup(-1);
}),
child: child,
});
const emptyArea = SpaceRightInteractions(Widget.Box({ hexpand: true, }));
const indicatorArea = SpaceRightInteractions(Widget.Box({
children: [
SeparatorDot(),
barStatusIcons
],
}));
const actualContent = Widget.Box({
hexpand: true,
className: 'spacing-h-5 bar-spaceright',
children: [
emptyArea,
barTray,
indicatorArea
],
});
let scrollCursorX, scrollCursorY;
return Widget.EventBox({
onScrollUp: (self, event) => {
if (!Audio.speaker) return;
let _;
[_, scrollCursorX, scrollCursorY] = event.get_coords();
if (Audio.speaker.volume <= 0.09) Audio.speaker.volume += 0.01;
else Audio.speaker.volume += 0.03;
Indicator.popup(1);
},
onScrollDown: (self, event) => {
if (!Audio.speaker) return;
let _;
[_, scrollCursorX, scrollCursorY] = event.get_coords();
if (Audio.speaker.volume <= 0.09) Audio.speaker.volume -= 0.01;
else Audio.speaker.volume -= 0.03;
Indicator.popup(1);
},
setup: (self) => self.on('motion-notify-event', (self, event) => {
const [_, cursorX, cursorY] = event.get_coords();
if (distance(cursorX, cursorY, scrollCursorX, scrollCursorY) >= OSD_DISMISS_DISTANCE)
Indicator.popup(-1);
}),
child: Widget.Box({
children: [
actualContent,
SpaceRightInteractions(Widget.Box({ className: 'bar-corner-spacing' })),
]
})
});
}
-238
View File
@@ -1,238 +0,0 @@
// This is for the right pills of the bar.
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
const { exec, execAsync } = Utils;
const { GLib } = imports.gi;
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js";
import { WWO_CODE, WEATHER_SYMBOL, NIGHT_WEATHER_SYMBOL } from '../../.commondata/weather.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`;
Utils.exec(`mkdir -p ${WEATHER_CACHE_FOLDER}`);
const BarBatteryProgress = () => {
const _updateProgress = (circprog) => { // Set circular progress value
circprog.css = `font-size: ${Math.abs(Battery.percent)}px;`
circprog.toggleClassName('bar-batt-circprog-low', Battery.percent <= userOptions.battery.low);
circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
}
return AnimatedCircProg({
className: `bar-batt-circprog ${userOptions.appearance.borderless ? 'bar-batt-circprog-borderless' : ''}`,
vpack: 'center', hpack: 'center',
extraSetup: (self) => self
.hook(Battery, _updateProgress)
,
})
}
const time = Variable('', {
poll: [
userOptions.time.interval,
() => GLib.DateTime.new_now_local().format(userOptions.time.format),
],
})
const date = Variable('', {
poll: [
userOptions.time.dateInterval,
() => GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong),
],
})
const BarClock = () => Widget.Box({
vpack: 'center',
className: 'spacing-h-4 bar-clock-box',
children: [
Widget.Label({
className: 'bar-time',
label: time.bind(),
}),
Widget.Label({
className: 'txt-norm txt-onLayer1',
label: '•',
}),
Widget.Label({
className: 'txt-smallie bar-date',
label: date.bind(),
}),
],
});
const UtilButton = ({ name, icon, onClicked }) => Button({
vpack: 'center',
tooltipText: name,
onClicked: onClicked,
className: `bar-util-btn ${userOptions.appearance.borderless ? 'bar-util-btn-borderless' : ''} icon-material txt-norm`,
label: `${icon}`,
setup: setupCursorHover
})
const Utilities = () => Box({
hpack: 'center',
className: 'spacing-h-4',
children: [
UtilButton({
name: getString('Screen snip'), icon: 'screenshot_region', onClicked: () => {
Utils.execAsync(`${App.configDir}/scripts/grimblast.sh copy area`)
.catch(print)
}
}),
UtilButton({
name: getString('Color picker'), icon: 'colorize', onClicked: () => {
Utils.execAsync(['hyprpicker', '-a']).catch(print)
}
}),
UtilButton({
name: getString('Toggle on-screen keyboard'), icon: 'keyboard', onClicked: () => {
toggleWindowOnAllMonitors('osk');
}
}),
]
})
const BarBattery = () => Box({
className: 'spacing-h-4 bar-batt-txt',
children: [
Revealer({
transitionDuration: userOptions.animations.durationSmall,
revealChild: false,
transition: 'slide_right',
child: MaterialIcon('bolt', 'norm', { tooltipText: "Charging" }),
setup: (self) => self.hook(Battery, revealer => {
self.revealChild = Battery.charging;
}),
}),
Label({
className: 'txt-smallie',
setup: (self) => self.hook(Battery, label => {
label.label = `${Number.parseFloat(Battery.percent.toFixed(1))}%`;
}),
}),
Overlay({
child: Widget.Box({
vpack: 'center',
className: 'bar-batt',
homogeneous: true,
children: [
MaterialIcon('battery_full', 'small'),
],
setup: (self) => self.hook(Battery, box => {
box.toggleClassName('bar-batt-low', Battery.percent <= userOptions.battery.low);
box.toggleClassName('bar-batt-full', Battery.charged);
}),
}),
overlays: [
BarBatteryProgress(),
]
}),
]
});
const BarGroup = ({ child }) => Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: `bar-group${userOptions.appearance.borderless ? '-borderless' : ''} bar-group-standalone bar-group-pad-system`,
children: [child],
}),
]
});
const BatteryModule = () => Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationLarge,
children: {
'laptop': Box({
className: 'spacing-h-4', children: [
BarGroup({ child: Utilities() }),
BarGroup({ child: BarBattery() }),
]
}),
'desktop': BarGroup({
child: Box({
hexpand: true,
hpack: 'center',
className: 'spacing-h-4 txt-onSurfaceVariant',
children: [
MaterialIcon('device_thermostat', 'small'),
Label({
label: 'Weather',
})
],
setup: (self) => self.poll(900000, async (self) => {
const WEATHER_CACHE_PATH = WEATHER_CACHE_FOLDER + '/wttr.in.txt';
const updateWeatherForCity = (city) => execAsync(`curl https://wttr.in/${city.replace(/ /g, '%20')}?format=j1`)
.then(output => {
const weather = JSON.parse(output);
Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH)
.catch(print);
const weatherCode = weather.current_condition[0].weatherCode;
const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
const temperature = weather.current_condition[0][`temp_${userOptions.weather.preferredUnit}`];
const feelsLike = weather.current_condition[0][`FeelsLike${userOptions.weather.preferredUnit}`];
const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
self.children[0].label = weatherSymbol;
self.children[1].label = `${temperature}°${userOptions.weather.preferredUnit}${getString('Feels like')} ${feelsLike}°${userOptions.weather.preferredUnit}`;
self.tooltipText = weatherDesc;
}).catch((err) => {
try { // Read from cache
const weather = JSON.parse(
Utils.readFile(WEATHER_CACHE_PATH)
);
const weatherCode = weather.current_condition[0].weatherCode;
const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
const temperature = weather.current_condition[0][`temp_${userOptions.weather.preferredUnit}`];
const feelsLike = weather.current_condition[0][`FeelsLike${userOptions.weather.preferredUnit}`];
const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
self.children[0].label = weatherSymbol;
self.children[1].label = `${temperature}°${userOptions.weather.preferredUnit}${getString('Feels like')} ${feelsLike}°${userOptions.weather.preferredUnit}`;
self.tooltipText = weatherDesc;
} catch (err) {
print(err);
}
});
if (userOptions.weather.city != '' && userOptions.weather.city != null) {
updateWeatherForCity(userOptions.weather.city.replace(/ /g, '%20'));
}
else {
Utils.execAsync('curl ipinfo.io')
.then(output => {
return JSON.parse(output)['city'].toLowerCase();
})
.then(updateWeatherForCity)
.catch(print)
}
}),
})
}),
},
setup: (stack) => Utils.timeout(10, () => {
if (!Battery.available) stack.shown = 'desktop';
else stack.shown = 'laptop';
})
})
const switchToRelativeWorkspace = async (self, num) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.messageAsync(`dispatch workspace r${num > 0 ? '+' : ''}${num}`).catch(print);
} catch {
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
}
}
export default () => Widget.EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onPrimaryClick: () => App.toggleWindow('sideright'),
child: Widget.Box({
className: 'spacing-h-4',
children: [
BarGroup({ child: BarClock() }),
BatteryModule(),
]
})
});
-36
View File
@@ -1,36 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { Box, Icon, Button, Revealer } = Widget;
const { Gravity } = imports.gi.Gdk;
const SysTrayItem = (item) => item.id !== null ? Button({
className: 'bar-systray-item',
child: Icon({ hpack: 'center' }).bind('icon', item, 'icon'),
setup: (self) => self
.hook(item, (self) => self.tooltipMarkup = item['tooltip-markup'])
,
onPrimaryClick: (_, event) => item.activate(event),
onSecondaryClick: (btn, event) => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
}) : null;
export const Tray = (props = {}) => {
const trayContent = Box({
className: 'margin-right-5 spacing-h-15',
setup: (self) => self
.hook(SystemTray, (self) => {
self.children = SystemTray.items.map(SysTrayItem);
self.show_all();
})
,
});
const trayRevealer = Widget.Revealer({
revealChild: true,
transition: 'slide_left',
transitionDuration: userOptions.animations.durationLarge,
child: trayContent,
});
return Box({
...props,
children: [trayRevealer],
});
}
@@ -1,224 +0,0 @@
const { GLib, Gdk, Gtk } = imports.gi;
const Lang = imports.lang;
const Cairo = imports.cairo;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
import App from 'resource:///com/github/Aylur/ags/app.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, DrawingArea, EventBox } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
const mix = (value1, value2, perc) => {
return value1 * perc + value2 * (1 - perc);
}
const getFontWeightName = (weight) => {
switch (weight) {
case Pango.Weight.ULTRA_LIGHT:
return 'UltraLight';
case Pango.Weight.LIGHT:
return 'Light';
case Pango.Weight.NORMAL:
return 'Normal';
case Pango.Weight.BOLD:
return 'Bold';
case Pango.Weight.ULTRA_BOLD:
return 'UltraBold';
case Pango.Weight.HEAVY:
return 'Heavy';
default:
return 'Normal';
}
}
// Font size = workspace id
const WorkspaceContents = (count = 10) => {
return DrawingArea({
className: 'bar-ws-container',
attribute: {
initialized: false,
workspaceMask: 0,
workspaceGroup: 0,
updateMask: (self) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
// if (self.attribute.initialized) return; // We only need this to run once
const workspaces = Hyprland.workspaces;
let workspaceMask = 0;
for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[i];
if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
if (workspaces[i].windows > 0)
workspaceMask |= (1 << (ws.id - offset));
}
// console.log('Mask:', workspaceMask.toString(2));
self.attribute.workspaceMask = workspaceMask;
// self.attribute.initialized = true;
self.queue_draw();
},
toggleMask: (self, occupied, name) => {
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
self.queue_draw();
},
},
setup: (area) => area
.hook(Hyprland.active.workspace, (self) => {
self.setCss(`font-size: ${(Hyprland.active.workspace.id - 1) % count + 1}px;`);
const previousGroup = self.attribute.workspaceGroup;
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
if (currentGroup !== previousGroup) {
self.attribute.updateMask(self);
self.attribute.workspaceGroup = currentGroup;
}
})
.hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
.on('draw', Lang.bind(area, (area, cr) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
const allocation = area.get_allocation();
const { width, height } = allocation;
const workspaceStyleContext = dummyWs.get_style_context();
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const workspaceRadius = workspaceDiameter / 2;
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
const workspaceFontWeight = workspaceStyleContext.get_property('font-weight', Gtk.StateFlags.NORMAL);
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
area.set_size_request(workspaceDiameter * count, -1);
const widgetStyleContext = area.get_style_context();
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
const activeWsCenterY = height / 2;
// Font
const layout = PangoCairo.create_layout(cr);
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${getFontWeightName(workspaceFontWeight)} ${workspaceFontSize}`);
layout.set_font_description(fontDesc);
cr.setAntialias(Cairo.Antialias.BEST);
// Get kinda min radius for number indicators
layout.set_text("0".repeat(count.toString().length), -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.15; // smaller than sqrt(2)*radius
const indicatorGap = workspaceRadius - indicatorRadius;
for (let i = 1; i <= count; i++) {
if (area.attribute.workspaceMask & (1 << i)) {
// Draw bg highlight
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
const wsCenterY = height / 2;
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
}
}
// Draw active ws
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
cr.fill();
// Draw workspace numbers
for (let i = 1; i <= count; i++) {
const inactivecolors = area.attribute.workspaceMask & (1 << i) ? occupiedfg : wsfg;
if (i == activeWs) {
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
}
// Moving to
else if ((i == Math.floor(activeWs) && Hyprland.active.workspace.id < activeWs) || (i == Math.ceil(activeWs) && Hyprland.active.workspace.id > activeWs)) {
cr.setSourceRGBA(mix(activefg.red, inactivecolors.red, 1 - Math.abs(activeWs - i)), mix(activefg.green, inactivecolors.green, 1 - Math.abs(activeWs - i)), mix(activefg.blue, inactivecolors.blue, 1 - Math.abs(activeWs - i)), activefg.alpha);
}
// Moving from
else if ((i == Math.floor(activeWs) && Hyprland.active.workspace.id > activeWs) || (i == Math.ceil(activeWs) && Hyprland.active.workspace.id < activeWs)) {
cr.setSourceRGBA(mix(activefg.red, inactivecolors.red, 1 - Math.abs(activeWs - i)), mix(activefg.green, inactivecolors.green, 1 - Math.abs(activeWs - i)), mix(activefg.blue, inactivecolors.blue, 1 - Math.abs(activeWs - i)), activefg.alpha);
}
// Inactive
else
cr.setSourceRGBA(inactivecolors.red, inactivecolors.green, inactivecolors.blue, inactivecolors.alpha);
layout.set_text(`${i + offset}`, -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
const y = (height - layoutHeight) / 2;
cr.moveTo(x, y);
PangoCairo.show_layout(cr, layout);
cr.stroke();
}
}))
,
})
}
export default () => EventBox({
onScrollUp: () => Hyprland.messageAsync(`dispatch workspace r-1`).catch(print),
onScrollDown: () => Hyprland.messageAsync(`dispatch workspace r+1`).catch(print),
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
onSecondaryClick: () => App.toggleWindow('overview'),
attribute: {
clicked: false,
ws_group: 0,
},
child: Box({
homogeneous: true,
className: 'bar-group-margin',
children: [Box({
className: `bar-group${userOptions.appearance.borderless ? '-borderless' : ''} bar-group-standalone bar-group-pad`,
css: 'min-width: 2px;',
children: [WorkspaceContents(userOptions.workspaces.shown)],
})]
}),
setup: (self) => {
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
self.on('motion-notify-event', (self, event) => {
if (!self.attribute.clicked) return;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
.catch(print);
})
self.on('button-press-event', (self, event) => {
if (event.get_button()[1] === 1) {
self.attribute.clicked = true;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
.catch(print);
}
else if (event.get_button()[1] === 8) {
Hyprland.messageAsync(`dispatch togglespecialworkspace`).catch(print);
}
})
self.on('button-release-event', (self) => self.attribute.clicked = false);
}
})
@@ -1,183 +0,0 @@
const { GLib, Gdk, Gtk } = imports.gi;
const Lang = imports.lang;
const Cairo = imports.cairo;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Sway from "../../../services/sway.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
const { execAsync, exec } = Utils;
const { Box, DrawingArea, EventBox } = Widget;
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
const switchToRelativeWorkspace = (self, num) =>
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
const WorkspaceContents = (count = 10) => {
return DrawingArea({
css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
attribute: {
initialized: false,
workspaceMask: 0,
updateMask: (self) => {
if (self.attribute.initialized) return; // We only need this to run once
const workspaces = Sway.workspaces;
let workspaceMask = 0;
// console.log('----------------')
for (let i = 0; i < workspaces.length; i++) {
const ws = workspaces[i];
// console.log(ws.name, ',', ws.num);
if (!Number(ws.name)) return;
const id = Number(ws.name);
if (id <= 0) continue; // Ignore scratchpads
if (id > count) return; // Not rendered
if (workspaces[i].windows > 0) {
workspaceMask |= (1 << id);
}
}
self.attribute.workspaceMask = workspaceMask;
self.attribute.initialized = true;
},
toggleMask: (self, occupied, name) => {
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
},
},
setup: (area) => area
.hook(Sway.active.workspace, (area) => {
area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
})
.hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
.on('draw', Lang.bind(area, (area, cr) => {
const allocation = area.get_allocation();
const { width, height } = allocation;
const workspaceStyleContext = dummyWs.get_style_context();
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const workspaceRadius = workspaceDiameter / 2;
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
area.set_size_request(workspaceDiameter * count, -1);
const widgetStyleContext = area.get_style_context();
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
const activeWsCenterY = height / 2;
// Font
const layout = PangoCairo.create_layout(cr);
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
layout.set_font_description(fontDesc);
cr.setAntialias(Cairo.Antialias.BEST);
// Get kinda min radius for number indicators
layout.set_text("0".repeat(count.toString().length), -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
const indicatorGap = workspaceRadius - indicatorRadius;
// Draw workspace numbers
for (let i = 1; i <= count; i++) {
if (area.attribute.workspaceMask & (1 << i)) {
// Draw bg highlight
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
const wsCenterY = height / 2;
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
else {
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
cr.fill();
}
// Set color for text
cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
}
else
cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
layout.set_text(`${i}`, -1);
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
const y = (height - layoutHeight) / 2;
cr.moveTo(x, y);
// cr.showText(text);
PangoCairo.show_layout(cr, layout);
cr.stroke();
}
// Draw active ws
// base
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
cr.fill();
// inner decor
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
cr.fill();
}))
,
})
}
export default () => EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
onSecondaryClick: () => App.toggleWindow('overview'),
attribute: { clicked: false },
child: Box({
homogeneous: true,
className: 'bar-group-margin',
children: [Box({
className: `bar-group${userOptions.appearance.borderless ? '-borderless' : ''} bar-group-standalone bar-group-pad`,
css: 'min-width: 2px;',
children: [
WorkspaceContents(10),
]
})]
}),
setup: (self) => {
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
self.on('motion-notify-event', (self, event) => {
if (!self.attribute.clicked) return;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
switchToWorkspace(wsId);
})
self.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
self.attribute.clicked = true;
const [_, cursorX, cursorY] = event.get_coords();
const widgetWidth = self.get_allocation().width;
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
switchToWorkspace(wsId);
})
self.on('button-release-event', (self) => self.attribute.clicked = false);
}
});
@@ -1,122 +0,0 @@
export const keybindList = [[
{
"icon": "pin_drop",
"name": "Workspaces: navigation",
"binds": [
{ "keys": ["󰖳", "+", "#"], "action": "Go to workspace #" },
{ "keys": ["󰖳", "+", "S"], "action": "Toggle special workspace" },
{ "keys": ["󰖳", "+", "(Scroll ↑↓)"], "action": "Go to workspace -1/+1" },
{ "keys": ["Ctrl", "󰖳", "+", "←"], "action": "Go to workspace on the left" },
{ "keys": ["Ctrl", "󰖳", "+", "→"], "action": "Go to workspace on the right" },
{ "keys": ["󰖳", "+", "PageUp"], "action": "Go to workspace on the left" },
{ "keys": ["󰖳", "+", "PageDown"], "action": "Go to workspace on the right" }
],
"id": 1
},
{
"icon": "overview_key",
"name": "Workspaces: management",
"binds": [
{ "keys": ["󰖳", "Alt", "+", "#"], "action": "Move window to workspace #" },
{ "keys": ["󰖳", "Alt", "+", "S"], "action": "Move window to special workspace" },
{ "keys": ["󰖳", "Alt", "+", "PageUp"], "action": "Move window to workspace on the left" },
{ "keys": ["󰖳", "Alt", "+", "PageDown"], "action": "Move window to workspace on the right" }
],
"id": 2
},
{
"icon": "move_group",
"name": "Windows",
"binds": [
{ "keys": ["󰖳", "+", "←↑→↓"], "action": "Focus window in direction" },
{ "keys": ["󰖳", "Shift", "+", "←↑→↓"], "action": "Swap window in direction" },
{ "keys": ["󰖳", "+", ";"], "action": "Split ratio -" },
{ "keys": ["󰖳", "+", "'"], "action": "Split ratio +" },
{ "keys": ["󰖳", "+", "Lmb"], "action": "Move window" },
{ "keys": ["󰖳", "+", "Rmb"], "action": "Resize window" },
{ "keys": ["󰖳", "Alt", "+", "Space"], "action": "Float window" },
{ "keys": ["󰖳", "+", "F"], "action": "Fullscreen" },
{ "keys": ["󰖳", "Alt", "+", "F"], "action": "Fake fullscreen" }
],
"id": 3
}
],
[
{
"icon": "widgets",
"name": "Widgets (AGS)",
"binds": [
{ "keys": ["󰖳", "OR", "󰖳", "+", "Tab"], "action": "Toggle overview/launcher" },
{ "keys": ["Ctrl", "󰖳", "+", "R"], "action": "Restart AGS" },
{ "keys": ["󰖳", "+", "/"], "action": "Toggle this cheatsheet" },
{ "keys": ["󰖳", "+", "N"], "action": "Toggle system sidebar" },
{ "keys": ["󰖳", "+", "B", "OR", "󰖳", "+", "O"], "action": "Toggle utilities sidebar" },
{ "keys": ["󰖳", "+", "K"], "action": "Toggle virtual keyboard" },
{ "keys": ["Ctrl", "Alt", "+", "Del"], "action": "Power/Session menu" },
{ "keys": ["Esc"], "action": "Exit a window" },
{ "keys": ["rightCtrl"], "action": "Dismiss/close sidebar" },
{ "keys": ["Ctrl", "󰖳", "+", "T"], "action": "Change wallpaper+colorscheme" },
// { "keys": ["󰖳", "+", "B"], "action": "Toggle left sidebar" },
// { "keys": ["󰖳", "+", "N"], "action": "Toggle right sidebar" },
// { "keys": ["󰖳", "+", "G"], "action": "Toggle volume mixer" },
// { "keys": ["󰖳", "+", "M"], "action": "Toggle useless audio visualizer" },
// { "keys": ["(right)Ctrl"], "action": "Dismiss notification & close menus" }
],
"id": 4
},
{
"icon": "construction",
"name": "Utilities",
"binds": [
{ "keys": ["PrtSc"], "action": "Screenshot >> clipboard" },
{ "keys": ["Ctrl", "PrtSc"], "action": "Screenshot >> file + clipboard" },
{ "keys": ["󰖳", "Shift", "+", "S"], "action": "Screen snip >> clipboard" },
{ "keys": ["󰖳", "Shift", "+", "T"], "action": "Image to text >> clipboard" },
{ "keys": ["󰖳", "Shift", "+", "C"], "action": "Color picker" },
{ "keys": ["󰖳", "Alt", "+", "R"], "action": "Record region" },
{ "keys": ["Ctrl", "Alt", "+", "R"], "action": "Record region with sound" },
{ "keys": ["󰖳", "Shift", "Alt", "+", "R"], "action": "Record screen with sound" }
],
"id": 5
},
],
[
{
"icon": "apps",
"name": "Apps",
"binds": [
{ "keys": ["󰖳", "+", "T"], "action": "Launch terminal: foot" },
{ "keys": ["󰖳", "+", "W"], "action": "Launch browser: Firefox" },
{ "keys": ["󰖳", "+", "C"], "action": "Launch editor: vscode" },
{ "keys": ["󰖳", "+", "X"], "action": "Launch editor: GNOME Text Editor" },
{ "keys": ["󰖳", "+", "I"], "action": "Launch settings: GNOME Control center" }
],
"id": 6
},
{
"icon": "keyboard",
"name": "Typing",
"binds": [
{ "keys": ["󰖳", "+", "V"], "action": "Clipboard history >> clipboard" },
{ "keys": ["󰖳", "+", "."], "action": "Emoji picker >> clipboard" },
],
"id": 7
},
{
"icon": "terminal",
"name": "Launcher actions",
"binds": [
{ "keys": [">raw"], "action": "Toggle mouse acceleration" },
{ "keys": [">img"], "action": "Select wallpaper and generate colorscheme" },
{ "keys": [">light"], "action": "Switch to light theme" },
{ "keys": [">dark"], "action": "Switch to dark theme" },
{ "keys": [">badapple"], "action": "Apply black n' white colorscheme" },
{ "keys": [">color"], "action": "Pick acccent color" },
{ "keys": [">todo"], "action": "Type something after that to add a To-do item" },
],
"id": 8
}
]];
-126
View File
@@ -1,126 +0,0 @@
const { GLib, Gtk } = imports.gi;
import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import { IconTabContainer } from "../.commonwidgets/tabcontainer.js";
const { Box, Label, Scrollable } = Widget;
const HYPRLAND_KEYBIND_CONFIG_FILE = userOptions.cheatsheet.keybinds.configPath ?
userOptions.cheatsheet.keybinds.configPath : `${GLib.get_user_config_dir()}/hypr/hyprland/keybinds.conf`;
const KEYBIND_SECTIONS_PER_PAGE = 3;
const getKeybindList = () => {
let data = Utils.exec(`${App.configDir}/scripts/hyprland/get_keybinds.py --path ${HYPRLAND_KEYBIND_CONFIG_FILE}`);
if (data == "\"error\"") {
Utils.timeout(2000, () => Utils.execAsync(['notify-send',
'Update path to keybinds',
'Keybinds hyprland config file not found. Check your user options.',
'-a', 'ags',
]).catch(print))
return { children: [] };
}
return JSON.parse(data);
};
const keybindList = getKeybindList();
const keySubstitutions = {
"Super": "󰖳",
"mouse_up": "Scroll ↓", // ikr, weird
"mouse_down": "Scroll ↑", // trust me bro
"mouse:272": "LMB",
"mouse:273": "RMB",
"mouse:275": "MouseBack",
"Slash": "/",
"Hash": "#"
}
const substituteKey = (key) => {
return keySubstitutions[key] || key;
}
const Keybind = (keybindData, type) => { // type: either "keys" or "actions"
const Key = (key) => Label({ // Specific keys
vpack: 'center',
className: `${['OR', '+'].includes(key) ? 'cheatsheet-key-notkey' : 'cheatsheet-key'} txt-small`,
label: substituteKey(key),
});
const Action = (text) => Label({ // Binds
xalign: 0,
label: getString(text),
className: "txt txt-small cheatsheet-action",
})
return Widget.Box({
className: "spacing-h-10 cheatsheet-bind-lineheight",
children: type == "keys" ? [
...(keybindData.mods.length > 0 ? [
...keybindData.mods.map(Key),
Key("+"),
] : []),
Key(keybindData.key),
] : [Action(keybindData.comment)],
})
}
const Section = (sectionData, scope) => {
const keys = Box({
vertical: true,
className: 'spacing-v-5',
children: sectionData.keybinds.map((data) => Keybind(data, "keys"))
})
const actions = Box({
vertical: true,
className: 'spacing-v-5',
children: sectionData.keybinds.map((data) => Keybind(data, "actions"))
})
const name = Label({
xalign: 0,
className: "cheatsheet-category-title txt margin-bottom-10",
label: getString(sectionData.name),
})
const binds = Box({
className: 'spacing-h-10',
children: [
keys,
actions,
]
})
const childrenSections = Box({
vertical: true,
className: 'spacing-v-15',
children: sectionData.children.map((data) => Section(data, scope + 1))
})
return Box({
vertical: true,
children: [
...((sectionData.name && sectionData.name.length > 0) ? [name] : []),
Box({
className: 'spacing-v-10',
children: [
binds,
childrenSections,
]
})
]
})
};
export default () => {
const numOfTabs = Math.ceil(keybindList.children.length / KEYBIND_SECTIONS_PER_PAGE);
const keybindPages = Array.from({ length: numOfTabs }, (_, i) => ({
iconWidget: Label({
className: "txt txt-small",
label: `${i + 1}`,
}),
name: `${i + 1}`,
child: Box({
className: 'spacing-h-30',
children: keybindList.children.slice(
KEYBIND_SECTIONS_PER_PAGE * i, 0 + KEYBIND_SECTIONS_PER_PAGE * (i + 1),
).map(data => Section(data, 1)),
}),
}));
return IconTabContainer({
iconWidgets: keybindPages.map((kbp) => kbp.iconWidget),
names: keybindPages.map((kbp) => kbp.name),
children: keybindPages.map((kbp) => kbp.child),
});
};
-146
View File
@@ -1,146 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { setupCursorHover } from "../.widgetutils/cursorhover.js";
import PopupWindow from '../.widgethacks/popupwindow.js';
import Keybinds from "./keybinds.js";
import PeriodicTable from "./periodictable.js";
import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js';
import { checkKeybind } from '../.widgetutils/keybind.js';
import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
const cheatsheets = [
{
name: getString('Keybinds'),
materialIcon: 'keyboard',
contentWidget: Keybinds,
},
{
name: getString('Periodic table'),
materialIcon: 'experiment',
contentWidget: PeriodicTable,
},
];
const CheatsheetHeader = () => Widget.CenterBox({
vertical: false,
startWidget: Widget.Box({}),
centerWidget: Widget.Box({
vertical: true,
className: "spacing-h-15",
children: [
Widget.Box({
hpack: 'center',
className: 'spacing-h-5 cheatsheet-title',
children: [
Widget.Label({
hpack: 'center',
css: 'margin-right: 0.682rem;',
className: 'txt-title',
label: getString('Cheat sheet'),
}),
Widget.Label({
vpack: 'center',
className: "cheatsheet-key txt-small",
label: "󰖳",
}),
Widget.Label({
vpack: 'center',
className: "cheatsheet-key-notkey txt-small",
label: "+",
}),
Widget.Label({
vpack: 'center',
className: "cheatsheet-key txt-small",
label: "/",
})
]
}),
]
}),
endWidget: Widget.Button({
vpack: 'start',
hpack: 'end',
className: "cheatsheet-closebtn icon-material txt txt-hugeass",
onClicked: () => {
closeWindowOnAllMonitors('cheatsheet');
},
child: Widget.Label({
className: 'icon-material txt txt-hugeass',
label: 'close'
}),
setup: setupCursorHover,
}),
});
const sheetContents = [];
const SheetContent = (id) => {
sheetContents[id] = ExpandingIconTabContainer({
tabsHpack: 'center',
tabSwitcherClassName: 'sidebar-icontabswitcher',
transitionDuration: userOptions.animations.durationLarge * 1.4,
icons: cheatsheets.map((api) => api.materialIcon),
names: cheatsheets.map((api) => api.name),
children: cheatsheets.map((api) => api.contentWidget()),
onChange: (self, id) => {
self.shown = cheatsheets[id].name;
}
});
return sheetContents[id];
}
export default (id) => {
const sheets = SheetContent(id);
const widgetContent = Widget.Box({
vertical: true,
className: "cheatsheet-bg spacing-v-5",
children: [
CheatsheetHeader(),
sheets,
]
});
return PopupWindow({
monitor: id,
name: `cheatsheet${id}`,
layer: 'top',
keymode: 'on-demand',
visible: false,
anchor: ['top', 'bottom', 'left', 'right'],
child: Widget.Box({
vertical: true,
children: [
clickCloseRegion({ name: 'cheatsheet' }),
Widget.Box({
children: [
clickCloseRegion({ name: 'cheatsheet' }),
widgetContent,
clickCloseRegion({ name: 'cheatsheet' }),
]
}),
clickCloseRegion({ name: 'cheatsheet' }),
],
setup: (self) => self.on('key-press-event', (widget, event) => { // Typing
// Whole sheet
if (checkKeybind(event, userOptions.keybinds.cheatsheet.nextTab))
sheetContents.forEach(tab => tab.nextTab())
else if (checkKeybind(event, userOptions.keybinds.cheatsheet.prevTab))
sheetContents.forEach(tab => tab.prevTab())
else if (checkKeybind(event, userOptions.keybinds.cheatsheet.cycleTab))
sheetContents.forEach(tab => tab.cycleTab())
// Keybinds
if (sheets.attribute.names[sheets.attribute.shown.value] == 'Keybinds') { // If Keybinds tab is focused
if (checkKeybind(event, userOptions.keybinds.cheatsheet.keybinds.nextTab)) {
sheetContents.forEach((sheet) => {
const toSwitchTab = sheet.attribute.children[sheet.attribute.shown.value];
toSwitchTab.nextTab();
})
}
else if (checkKeybind(event, userOptions.keybinds.cheatsheet.keybinds.prevTab)) {
sheetContents.forEach((sheet) => {
const toSwitchTab = sheet.attribute.children[sheet.attribute.shown.value];
toSwitchTab.prevTab();
})
}
}
})
})
});
}
@@ -1,94 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { niceTypes, periodicTable, series } from "./data_periodictable.js";
const { Box, Button, Icon, Label, Revealer } = Widget;
export default () => {
const ElementTile = (element) => {
return Box({
vertical: true,
tooltipText: element.electronConfig ? `${element.electronConfig}` : null,
className: `cheatsheet-periodictable-${element.type}`,
children: element.name == '' ? null : [
Box({
className: 'padding-left-8 padding-right-8 padding-top-8',
children: [
Label({
label: `${element.number}`,
className: "cheatsheet-periodictable-elementnum txt-tiny txt-bold",
}),
Box({ hexpand: true }),
Label({
label: `${element.weight}`,
className: "txt-smaller",
})
]
}),
element.icon ? Icon({
icon: element.icon,
className: "txt-hugerass txt-bold",
}) : Label({
label: `${element.symbol}`,
className: "cheatsheet-periodictable-elementsymbol",
}),
Label({
label: `${element.name}`,
className: "txt-tiny",
})
]
})
}
const BoardColor = (type) => Box({
className: 'spacing-h-5',
children: [
Box({
homogeneous: true,
className: `cheatsheet-periodictable-legend-color-wrapper`,
children: [Box({
className: `cheatsheet-periodictable-legend-color-${type}`,
})]
}),
Label({
label: `${niceTypes[type]}`,
className: "txt txt-small",
})
]
})
const mainBoard = Box({
hpack: 'center',
vertical: true,
className: "spacing-v-3",
children: periodicTable.map((row, _) => Box({ // Rows
className: "spacing-h-5",
children: row.map((element, _) => ElementTile(element))
})),
});
const seriesBoard = Box({
hpack: 'center',
vertical: true,
className: "spacing-v-3",
children: series.map((row, _) => Box({ // Rows
className: "spacing-h-5",
children: row.map((element, _) => ElementTile(element))
})),
});
const legend = Box({
hpack: 'center',
className: 'spacing-h-20',
children: [
BoardColor('metal'),
BoardColor('nonmetal'),
BoardColor('noblegas'),
BoardColor('lanthanum'),
BoardColor('actinium'),
]
})
return Box({
vertical: true,
className: 'spacing-v-20',
children: [
mainBoard,
seriesBoard,
legend
]
})
}
-21
View File
@@ -1,21 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
export default (monitor = 0, ) => {
return Widget.Window({
monitor,
name: `crosshair${monitor}`,
layer: 'overlay',
exclusivity: 'ignore',
visible: false,
child: Widget.Icon({
icon: 'crosshair-symbolic',
css: `
font-size: ${userOptions.gaming.crosshair.size}px;
color: ${userOptions.gaming.crosshair.color};
`,
}),
setup: enableClickthrough,
});
}
@@ -1,14 +0,0 @@
export const quickLaunchItems = [
{
"name": "GitHub + Files×2",
"command": "github-desktop & nautilus --new-window & nautilus --new-window &"
},
{
"name": "Terminal×2",
"command": "foot & foot &"
},
{
"name": "Discord + Youtube + Github",
"command": "xdg-open 'https://discord.com/app' && xdg-open 'https://youtube.com/' && xdg-open 'https://github.com/' &"
},
]
@@ -1,24 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import WallpaperImage from './wallpaper.js';
import TimeAndLaunchesWidget from './timeandlaunches.js'
import SystemWidget from './system.js'
export default (monitor) => Widget.Window({
name: `desktopbackground${monitor}`,
// anchor: ['top', 'bottom', 'left', 'right'],
layer: 'background',
exclusivity: 'ignore',
visible: true,
child: Widget.Overlay({
child: WallpaperImage(monitor),
// child: Widget.Box({}),
overlays: [
TimeAndLaunchesWidget(),
SystemWidget(),
],
setup: (self) => {
self.set_overlay_pass_through(self.get_children()[1], true);
},
}),
});
@@ -1,161 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, EventBox, Label, Revealer, Overlay } = Widget;
import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
const ResourceValue = (name, icon, interval, valueUpdateCmd, displayFunc, props = {}) => Box({
...props,
className: 'bg-system-bg txt',
children: [
Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
vpack: 'center',
vertical: true,
className: 'margin-right-15',
children: [
Label({
xalign: 1,
className: 'txt-small txt',
label: `${name}`,
}),
Label({
xalign: 1,
className: 'titlefont txt-norm txt-onSecondaryContainer',
setup: (self) => self
.poll(interval, (label) => displayFunc(label))
,
})
]
})
}),
Overlay({
child: AnimatedCircProg({
className: 'bg-system-circprog',
extraSetup: (self) => self
.poll(interval, (self) => {
execAsync(['bash', '-c', `${valueUpdateCmd}`]).then((newValue) => {
self.css = `font-size: ${Math.round(newValue)}px;`
}).catch(print);
})
,
}),
overlays: [
MaterialIcon(`${icon}`, 'hugeass'),
],
setup: self => self.set_overlay_pass_through(self.get_children()[1], true),
}),
]
})
const resources = Box({
vpack: 'fill',
vertical: true,
className: 'spacing-v-15',
children: [
ResourceValue('Memory', 'memory', 10000, `free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
(label) => {
execAsync(['bash', '-c', `free -h | awk '/^Mem/ {print $3 " / " $2}' | sed 's/Gi/Gib/g'`])
.then((output) => {
label.label = `${output}`
}).catch(print);
}, { hpack: 'end' }),
ResourceValue('Swap', 'swap_horiz', 10000, `free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
(label) => {
execAsync(['bash', '-c', `free -h | awk '/^Swap/ {if ($2 != "0") print $3 " / " $2; else print "No swap"}' | sed 's/Gi/Gib/g'`])
.then((output) => {
label.label = `${output}`
}).catch(print);
}, { hpack: 'end' }),
ResourceValue('Disk space', 'hard_drive_2', 3600000, `echo $(df --output=pcent / | tr -dc '0-9')`,
(label) => {
execAsync(['bash', '-c', `df -h --output=avail / | awk 'NR==2{print $1}'`])
.then((output) => {
label.label = `${output} available`
}).catch(print);
}, { hpack: 'end' }),
]
});
const distroAndVersion = Box({
vertical: true,
children: [
Box({
hpack: 'end',
children: [
Label({
className: 'bg-distro-txt',
xalign: 0,
label: 'Hyping on ',
}),
Label({
className: 'bg-distro-name',
xalign: 0,
label: '<distro>',
setup: (label) => {
execAsync([`grep`, `-oP`, `PRETTY_NAME="\\K[^"]+`, `/etc/os-release`]).then(distro => {
label.label = distro;
}).catch(print);
},
}),
]
}),
Box({
hpack: 'end',
children: [
Label({
className: 'bg-distro-txt',
xalign: 0,
label: 'with ',
}),
Label({
className: 'bg-distro-name',
xalign: 0,
label: 'An environment idk',
setup: (label) => {
// hyprctl will return unsuccessfully if Hyprland isn't running
execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(version => {
label.label = `Hyprland ${version}`;
}).catch(() => execAsync([`bash`, `-c`, `sway -v | cut -d'-' -f1 | sed 's/sway version /v/'`]).then(version => {
label.label = `Sway ${version}`;
}).catch(print));
},
}),
]
})
]
})
export default () => Box({
hpack: 'end',
vpack: 'end',
children: [
EventBox({
child: Box({
hpack: 'end',
vpack: 'end',
className: 'bg-distro-box spacing-v-20',
vertical: true,
children: [
resources,
distroAndVersion,
]
}),
onPrimaryClickRelease: () => {
const kids = resources.get_children();
for (let i = 0; i < kids.length; i++) {
const child = kids[i];
const firstChild = child.get_children()[0];
firstChild.revealChild = !firstChild.revealChild;
}
},
})
],
})
@@ -1,74 +0,0 @@
const { GLib } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
const { execAsync, exec } = Utils;
const { Box, Label, Button, Revealer, EventBox } = Widget;
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import { quickLaunchItems } from './data_quicklaunches.js'
const TimeAndDate = () => Box({
vertical: true,
className: 'spacing-v--5',
children: [
Label({
className: 'bg-time-clock',
xalign: 0,
label: GLib.DateTime.new_now_local().format(userOptions.time.format),
setup: (self) => self.poll(userOptions.time.interval, label => {
label.label = GLib.DateTime.new_now_local().format(userOptions.time.format);
}),
}),
Label({
className: 'bg-time-date',
xalign: 0,
label: GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong),
setup: (self) => self.poll(userOptions.time.dateInterval, (label) => {
label.label = GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong);
}),
}),
]
})
const QuickLaunches = () => Box({
vertical: true,
className: 'spacing-v-10',
children: [
Label({
xalign: 0,
className: 'bg-quicklaunch-title',
label: 'Quick Launches',
}),
Box({
hpack: 'start',
className: 'spacing-h-5',
children: quickLaunchItems.map((item, i) => Button({
onClicked: () => {
execAsync(['bash', '-c', `${item["command"]}`]).catch(print);
},
className: 'bg-quicklaunch-btn',
child: Label({
label: `${item["name"]}`,
}),
setup: (self) => {
setupCursorHover(self);
}
})),
})
]
})
export default () => Box({
hpack: 'start',
vpack: 'end',
vertical: true,
className: 'bg-time-box spacing-h--10',
children: [
TimeAndDate(),
// QuickLaunches(),
],
})
@@ -1,119 +0,0 @@
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { exec, execAsync } = Utils;
const { Box, Button, Label, Stack } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Wallpaper from '../../services/wallpaper.js';
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import { clamp } from '../.miscutils/mathfuncs.js';
import { monitors } from '../.commondata/hyprlanddata.js';
const DISABLE_AGS_WALLPAPER = true;
const SWITCHWALL_SCRIPT_PATH = `${App.configDir}/scripts/color_generation/switchwall.sh`;
const WALLPAPER_ZOOM_SCALE = 1.25; // For scrolling when we switch workspace
const MAX_WORKSPACES = 10;
export default (monitor = 0) => {
const WALLPAPER_OFFSCREEN_X = (WALLPAPER_ZOOM_SCALE - 1) * monitors[monitor].width;
const WALLPAPER_OFFSCREEN_Y = (WALLPAPER_ZOOM_SCALE - 1) * monitors[monitor].height;
const wallpaperImage = Widget.DrawingArea({
attribute: {
pixbuf: undefined,
workspace: 1,
sideleft: 0,
sideright: 0,
updatePos: (self) => {
self.setCss(`font-size: ${self.attribute.workspace - self.attribute.sideleft + self.attribute.sideright}px;`)
},
},
className: 'bg-wallpaper-transition',
setup: (self) => {
self.set_size_request(monitors[monitor].width, monitors[monitor].height);
self
// TODO: reduced updates using timeouts to reduce lag
// .hook(Hyprland.active.workspace, (self) => {
// self.attribute.workspace = Hyprland.active.workspace.id
// self.attribute.updatePos(self);
// })
// .hook(App, (box, name, visible) => { // Update on open
// if (self.attribute[name] === undefined) return;
// self.attribute[name] = (visible ? 1 : 0);
// self.attribute.updatePos(self);
// })
.on('draw', (self, cr) => {
if (!self.attribute.pixbuf) return;
const styleContext = self.get_style_context();
const workspace = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
// Draw
Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
-(WALLPAPER_OFFSCREEN_X / (MAX_WORKSPACES + 1) * (clamp(workspace, 0, MAX_WORKSPACES + 1))),
-WALLPAPER_OFFSCREEN_Y / 2);
cr.paint();
})
.hook(Wallpaper, (self) => {
if (DISABLE_AGS_WALLPAPER) return;
const wallPath = Wallpaper.get(monitor);
if (!wallPath || wallPath === "") return;
self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file(wallPath);
const scale_x = monitors[monitor].width * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_width();
const scale_y = monitors[monitor].height * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_height();
const scale_factor = Math.max(scale_x, scale_y);
self.attribute.pixbuf = self.attribute.pixbuf.scale_simple(
Math.round(self.attribute.pixbuf.get_width() * scale_factor),
Math.round(self.attribute.pixbuf.get_height() * scale_factor),
GdkPixbuf.InterpType.BILINEAR
);
self.queue_draw();
}, 'updated');
;
}
,
});
const wallpaperPrompt = Box({
hpack: 'center',
vpack: 'center',
vertical: true,
className: 'spacing-v-10',
children: [
Label({
hpack: 'center',
justification: 'center',
className: 'txt-large',
label: `No wallpaper loaded.\nAn image ≥ ${monitors[monitor].width * WALLPAPER_ZOOM_SCALE} × ${monitors[monitor].height * WALLPAPER_ZOOM_SCALE} is recommended.`,
}),
Button({
hpack: 'center',
className: 'btn-primary',
label: `Select one`,
setup: setupCursorHover,
onClicked: (self) => Utils.execAsync([SWITCHWALL_SCRIPT_PATH]).catch(print),
}),
]
});
const stack = Stack({
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
children: {
'disabled': Box({}),
'image': wallpaperImage,
'prompt': wallpaperPrompt,
},
setup: (self) => self
.hook(Wallpaper, (self) => {
if (DISABLE_AGS_WALLPAPER) {
self.shown = 'disabled';
return;
}
const wallPath = Wallpaper.get(monitor);
self.shown = ((wallPath && wallPath != "") ? 'image' : 'prompt');
}, 'updated')
,
})
return stack;
// return wallpaperImage;
}
-300
View File
@@ -1,300 +0,0 @@
const { Gtk, GLib } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { EventBox, Button } = Widget;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
const { execAsync, exec } = Utils;
const { Box, Revealer } = Widget;
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import { getAllFiles, searchIcons } from './icons.js'
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
import { substitute } from '../.miscutils/icons.js';
const icon_files = userOptions.icons.searchPaths.map(e => getAllFiles(e)).flat(1)
let isPinned = false
let cachePath = new Map()
let timers = []
function clearTimes() {
timers.forEach(e => GLib.source_remove(e))
timers = []
}
function ExclusiveWindow(client) {
const fn = [
(client) => !(client !== null && client !== undefined),
// Jetbrains
(client) => client.title.includes("win"),
// Vscode
(client) => client.title === '' && client.class === ''
]
for (const item of fn) { if (item(client)) { return true } }
return false
}
const focus = ({ address }) => Utils.execAsync(`hyprctl dispatch focuswindow address:${address}`).catch(print);
const DockSeparator = (props = {}) => Box({
...props,
className: 'dock-separator',
})
const PinButton = () => Widget.Button({
className: 'dock-app-btn dock-app-btn-animate',
tooltipText: 'Pin Dock',
child: Widget.Box({
homogeneous: true,
className: 'dock-app-icon txt',
child: MaterialIcon('push_pin', 'hugeass')
}),
onClicked: (self) => {
isPinned = !isPinned
self.className = `${isPinned ? "pinned-dock-app-btn" : "dock-app-btn animate"} dock-app-btn-animate`
},
setup: setupCursorHover,
})
const LauncherButton = () => Widget.Button({
className: 'dock-app-btn dock-app-btn-animate',
tooltipText: 'Open launcher',
child: Widget.Box({
homogeneous: true,
className: 'dock-app-icon txt',
child: MaterialIcon('apps', 'hugerass')
}),
onClicked: (self) => {
App.toggleWindow('overview');
},
setup: setupCursorHover,
})
const AppButton = ({ icon, ...rest }) => Widget.Revealer({
attribute: {
'workspace': 0
},
revealChild: false,
transition: 'slide_right',
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Button({
...rest,
className: 'dock-app-btn dock-app-btn-animate',
child: Widget.Box({
child: Widget.Overlay({
child: Widget.Box({
homogeneous: true,
className: 'dock-app-icon',
child: Widget.Icon({
icon: icon,
}),
}),
overlays: [Widget.Box({
class_name: 'indicator',
vpack: 'end',
hpack: 'center',
})],
}),
}),
setup: (button) => {
setupCursorHover(button);
}
})
});
const Taskbar = (monitor) => Widget.Box({
className: 'dock-apps',
attribute: {
monitor: monitor,
'map': new Map(),
'clientSortFunc': (a, b) => {
return a.attribute.workspace > b.attribute.workspace;
},
'update': (box, monitor) => {
for (let i = 0; i < Hyprland.clients.length; i++) {
const client = Hyprland.clients[i];
if (client["pid"] == -1) return;
const appClass = substitute(client.class);
// for (const appName of userOptions.dock.pinnedApps) {
// if (appClass.includes(appName.toLowerCase()))
// return null;
// }
let appClassLower = appClass.toLowerCase()
let path = ''
if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
else {
path = searchIcons(appClass.toLowerCase(), icon_files)
cachePath[appClassLower] = path
}
if (path === '') { path = substitute(appClass) }
const newButton = AppButton({
icon: path,
tooltipText: `${client.title} (${appClass})`,
onClicked: () => focus(client),
});
newButton.attribute.workspace = client.workspace.id;
newButton.revealChild = true;
box.attribute.map.set(client.address, newButton);
}
box.children = Array.from(box.attribute.map.values());
},
'add': (box, address, monitor) => {
if (!address) { // First active emit is undefined
box.attribute.update(box);
return;
}
const newClient = Hyprland.clients.find(client => {
return client.address == address;
});
if (ExclusiveWindow(newClient)) { return }
let appClass = newClient.class
let appClassLower = appClass.toLowerCase()
let path = ''
if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
else {
path = searchIcons(appClassLower, icon_files)
cachePath[appClassLower] = path
}
if (path === '') { path = substitute(appClass) }
const newButton = AppButton({
icon: path,
tooltipText: `${newClient.title} (${appClass})`,
onClicked: () => focus(newClient),
})
newButton.attribute.workspace = newClient.workspace.id;
box.attribute.map.set(address, newButton);
box.children = Array.from(box.attribute.map.values());
newButton.revealChild = true;
},
'remove': (box, address) => {
if (!address) return;
const removedButton = box.attribute.map.get(address);
if (!removedButton) return;
removedButton.revealChild = false;
Utils.timeout(userOptions.animations.durationLarge, () => {
removedButton.destroy();
box.attribute.map.delete(address);
box.children = Array.from(box.attribute.map.values());
})
},
},
setup: (self) => {
self.hook(Hyprland, (box, address) => box.attribute.add(box, address, self.monitor), 'client-added')
.hook(Hyprland, (box, address) => box.attribute.remove(box, address, self.monitor), 'client-removed')
Utils.timeout(100, () => self.attribute.update(self));
},
});
const PinnedApps = () => Widget.Box({
class_name: 'dock-apps',
homogeneous: true,
children: userOptions.dock.pinnedApps
.map(term => ({ app: Applications.query(term)?.[0], term }))
.filter(({ app }) => app)
.map(({ app, term = true }) => {
const newButton = AppButton({
// different icon, emm...
icon: userOptions.dock.searchPinnedAppIcons ?
searchIcons(app.name, icon_files) :
app.icon_name,
onClicked: () => {
for (const client of Hyprland.clients) {
if (client.class.toLowerCase().includes(term))
return focus(client);
}
app.launch();
},
onMiddleClick: () => app.launch(),
tooltipText: app.name,
setup: (self) => {
self.revealChild = true;
self.hook(Hyprland, button => {
const running = Hyprland.clients
.find(client => client.class.toLowerCase().includes(term)) || false;
button.toggleClassName('notrunning', !running);
button.toggleClassName('focused', Hyprland.active.client.address == running.address);
button.set_tooltip_text(running ? running.title : app.name);
}, 'notify::clients')
},
})
newButton.revealChild = true;
return newButton;
}),
});
export default (monitor = 0) => {
const dockContent = Box({
className: 'dock-bg spacing-h-5',
children: [
PinButton(),
PinnedApps(),
DockSeparator(),
Taskbar(),
LauncherButton(),
]
})
const dockRevealer = Revealer({
attribute: {
'updateShow': self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
if (userOptions.dock.monitorExclusivity)
self.revealChild = Hyprland.active.monitor.id === monitor;
else
self.revealChild = true;
return self.revealChild
}
},
revealChild: false,
transition: 'slide_up',
transitionDuration: userOptions.animations.durationLarge,
child: dockContent,
setup: (self) => {
const callback = (self, trigger) => {
if (!userOptions.dock.trigger.includes(trigger)) return
const flag = self.attribute.updateShow(self)
if (flag) clearTimes();
const hidden = userOptions.dock.autoHide.find(e => e["trigger"] === trigger)
if (hidden) {
let id = Utils.timeout(hidden.interval, () => {
if (!isPinned) { self.revealChild = false }
timers = timers.filter(e => e !== id)
})
timers.push(id)
}
}
self
// .hook(Hyprland, (self) => self.attribute.updateShow(self))
.hook(Hyprland.active.workspace, self => callback(self, "workspace-active"))
.hook(Hyprland.active.client, self => callback(self, "client-active"))
.hook(Hyprland, self => callback(self, "client-added"), "client-added")
.hook(Hyprland, self => callback(self, "client-removed"), "client-removed")
},
})
return EventBox({
onHover: () => {
dockRevealer.revealChild = true;
clearTimes()
},
child: Box({
homogeneous: true,
css: `min-height: ${userOptions.dock.hiddenThickness}px;`,
children: [dockRevealer],
}),
setup: self => self.on("leave-notify-event", () => {
if (!isPinned) dockRevealer.revealChild = false;
clearTimes()
})
})
}
-63
View File
@@ -1,63 +0,0 @@
const { Gio, GLib } = imports.gi
const exists = (path) => Gio.File.new_for_path(path).query_exists(null);
export const levenshteinDistance = (a, b) => {
if (!a.length) { return b.length }
if (!b.length) { return a.length }
let f = Array.from(new Array(a.length + 1),
() => new Array(b.length + 1).fill(0))
for (let i = 0; i <= b.length; i++) { f[0][i] = i; }
for (let i = 0; i <= a.length; i++) { f[i][0] = i; }
for (let i = 1; i <= a.length; i++) {
for (let j = 1; j <= b.length; j++) {
if (a.charAt(i - 1) === b.charAt(j - 1)) {
f[i][j] = f[i-1][j-1]
} else {
f[i][j] = Math.min(f[i-1][j-1], Math.min(f[i][j-1], f[i-1][j])) + 1
}
}
}
return f[a.length][b.length]
}
export const getAllFiles = (dir, files = []) => {
if (!exists(dir)) { return [] }
const file = Gio.File.new_for_path(dir);
const enumerator = file.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null);
for (const info of enumerator) {
if (info.get_file_type() === Gio.FileType.DIRECTORY) {
files.push(getAllFiles(`${dir}/${info.get_name()}`))
} else {
files.push(`${dir}/${info.get_name()}`)
}
}
return files.flat(1);
}
export const searchIcons = (appClass, files) => {
appClass = appClass.toLowerCase()
if (!files.length) { return "" }
let appro = 0x3f3f3f3f
let path = ""
for (const item of files) {
let score = levenshteinDistance(item.split("/").pop().toLowerCase().split(".")[0], appClass)
if (score < appro) {
appro = score
path = item
}
}
return path
}
-12
View File
@@ -1,12 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Dock from './dock.js';
export default (monitor = 0) => Widget.Window({
monitor,
name: `dock${monitor}`,
layer: userOptions.dock.layer,
anchor: ['bottom'],
exclusivity: 'normal',
visible: true,
child: Dock(monitor),
});
@@ -1,236 +0,0 @@
const { Gio, GLib } = imports.gi;
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { ConfigToggle, ConfigMulipleSelection } from '../.commonwidgets/configwidgets.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync } = Utils;
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import { showColorScheme } from '../../variables.js';
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
import { darkMode } from '../.miscutils/system.js';
const ColorBox = ({
name = 'Color',
...rest
}) => Widget.Box({
...rest,
homogeneous: true,
children: [
Widget.Label({
label: `${name}`,
})
]
})
const ColorSchemeSettingsRevealer = () => {
const headerButtonIcon = MaterialIcon('expand_more', 'norm');
const header = Widget.Button({
className: 'osd-settings-btn-arrow',
onClicked: () => {
content.revealChild = !content.revealChild;
headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
},
setup: setupCursorHover,
hpack: 'end',
child: headerButtonIcon,
});
const content = Widget.Revealer({
revealChild: false,
transition: 'slide_down',
transitionDuration: 200,
child: ColorSchemeSettings(),
setup: (self) => self.hook(isHoveredColorschemeSettings, (revealer) => {
if (isHoveredColorschemeSettings.value == false) {
setTimeout(() => {
if (isHoveredColorschemeSettings.value == false)
revealer.revealChild = false;
headerButtonIcon.label = 'expand_more';
}, 1500);
}
}),
});
return Widget.EventBox({
onHover: (self) => {
isHoveredColorschemeSettings.setValue(true);
},
onHoverLost: (self) => {
isHoveredColorschemeSettings.setValue(false);
},
child: Widget.Box({
vertical: true,
children: [
header,
content,
]
}),
});
}
function calculateSchemeInitIndex(optionsArr, searchValue = 'vibrant') {
if (searchValue == '')
searchValue = 'vibrant';
const flatArray = optionsArr.flatMap(subArray => subArray);
const result = flatArray.findIndex(element => element.value === searchValue);
const rowIndex = Math.floor(result / optionsArr[0].length);
const columnIndex = result % optionsArr[0].length;
return [rowIndex, columnIndex];
}
const schemeOptionsArr = [
[
{ name: getString('Tonal Spot'), value: 'tonalspot' },
{ name: getString('Fruit Salad'), value: 'fruitsalad' },
{ name: getString('Fidelity'), value: 'fidelity' },
{ name: getString('Rainbow'), value: 'rainbow' },
],
[
{ name: getString('Neutral'), value: 'neutral' },
{ name: getString('Monochrome'), value: 'monochrome' },
{ name: getString('Expressive'), value: 'expressive' },
{ name: getString('Vibrant'), value: 'vibrant' },
],
[
{ name: getString('Vibrant+'), value: 'morevibrant' },
],
//[
// { name: getString('Content'), value: 'content' },
//]
];
const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
const initTransparency = Utils.exec(`bash -c "sed -n \'2p\' ${LIGHTDARK_FILE_LOCATION}"`);
const initTransparencyVal = (initTransparency == "transparent") ? 1 : 0;
const initScheme = Utils.exec(`bash -c "sed -n \'3p\' ${LIGHTDARK_FILE_LOCATION}"`);
const initSchemeIndex = calculateSchemeInitIndex(schemeOptionsArr, initScheme);
const ColorSchemeSettings = () => Widget.Box({
className: 'osd-colorscheme-settings spacing-v-5 margin-20',
vertical: true,
vpack: 'center',
children: [
Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
className: 'txt-norm titlefont txt',
label: getString('Options'),
hpack: 'center',
}),
//////////////////
ConfigToggle({
icon: 'dark_mode',
name: getString('Dark Mode'),
desc: getString('Ya should go to sleep!'),
initValue: darkMode.value,
onChange: (_, newValue) => {
darkMode.value = !!newValue;
},
extraSetup: (self) => self.hook(darkMode, (self) => {
self.attribute.enabled.value = darkMode.value;
}),
}),
ConfigToggle({
icon: 'border_clear',
name: getString('Transparency'),
desc: getString('Make shell elements transparent'),
initValue: initTransparencyVal,
onChange: (self, newValue) => {
let transparency = newValue == 0 ? "opaque" : "transparent";
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
.catch(print);
},
}),
]
}),
Widget.Box({
vertical: true,
className: 'spacing-v-5',
children: [
Widget.Label({
xalign: 0,
className: 'txt-norm titlefont txt margin-top-5',
label: getString('Scheme styles'),
hpack: 'center',
}),
//////////////////
ConfigMulipleSelection({
hpack: 'center',
vpack: 'center',
optionsArr: schemeOptionsArr,
initIndex: initSchemeIndex,
onChange: (value, name) => {
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "3s/.*/${value}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
.catch(print);
},
}),
]
})
]
});
const ColorschemeContent = () => Widget.Box({
className: 'osd-colorscheme spacing-v-5',
vertical: true,
hpack: 'center',
children: [
Widget.Label({
xalign: 0,
className: 'txt-norm titlefont txt',
label: getString('Color scheme'),
hpack: 'center',
}),
Widget.Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
ColorBox({ name: 'P', className: 'osd-color osd-color-primary' }),
ColorBox({ name: 'S', className: 'osd-color osd-color-secondary' }),
ColorBox({ name: 'T', className: 'osd-color osd-color-tertiary' }),
ColorBox({ name: 'Sf', className: 'osd-color osd-color-surface' }),
ColorBox({ name: 'Sf-i', className: 'osd-color osd-color-inverseSurface' }),
ColorBox({ name: 'E', className: 'osd-color osd-color-error' }),
]
}),
Widget.Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
ColorBox({ name: 'P-c', className: 'osd-color osd-color-primaryContainer' }),
ColorBox({ name: 'S-c', className: 'osd-color osd-color-secondaryContainer' }),
ColorBox({ name: 'T-c', className: 'osd-color osd-color-tertiaryContainer' }),
ColorBox({ name: 'Sf-c', className: 'osd-color osd-color-surfaceContainer' }),
ColorBox({ name: 'Sf-v', className: 'osd-color osd-color-surfaceVariant' }),
ColorBox({ name: 'E-c', className: 'osd-color osd-color-errorContainer' }),
]
}),
ColorSchemeSettingsRevealer(),
]
});
const isHoveredColorschemeSettings = Variable(false);
export default () => Widget.Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: ColorschemeContent(),
setup: (self) => {
self
.hook(showColorScheme, (revealer) => {
if (showColorScheme.value == true)
revealer.revealChild = true;
else
revealer.revealChild = isHoveredColorschemeSettings.value;
})
.hook(isHoveredColorschemeSettings, (revealer) => {
if (isHoveredColorschemeSettings.value == false) {
setTimeout(() => {
if (isHoveredColorschemeSettings.value == false)
revealer.revealChild = showColorScheme.value;
}, 2000);
}
})
},
})
@@ -1,133 +0,0 @@
// This file is for brightness/volume indicators
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
const { Box, Label, ProgressBar } = Widget;
import { MarginRevealer } from '../.widgethacks/advancedrevealers.js';
import Brightness from '../../services/brightness.js';
import Indicator from '../../services/indicator.js';
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
const OsdValue = ({
name, icon, nameSetup = undefined, labelSetup, progressSetup,
extraClassName = '', extraProgressClassName = '',
...rest
}) => {
const valueName = Label({
xalign: 0, yalign: 0, hexpand: true,
className: 'osd-label',
label: `${name}`,
setup: nameSetup,
});
const valueNumber = Label({
hexpand: false, className: 'osd-value-txt',
setup: labelSetup,
});
return Box({ // Volume
hexpand: true,
className: `osd-bg osd-value ${extraClassName} spacing-h-5`,
attribute: {
'disable': () => {
valueNumber.label = '󰖭';
}
},
children: [
MaterialIcon(icon, 'hugeass', {vpack: 'center'}),
Box({
vertical: true,
className: 'spacing-v-5',
vpack: 'center',
children: [
Box({
children: [
valueName,
valueNumber,
]
}),
ProgressBar({
className: `osd-progress ${extraProgressClassName}`,
hexpand: true,
vertical: false,
setup: progressSetup,
})
]
})
],
...rest,
});
}
export default (monitor = 0) => {
const brightnessIndicator = OsdValue({
name: 'Brightness',
icon: 'light_mode',
extraClassName: 'osd-brightness',
extraProgressClassName: 'osd-brightness-progress',
labelSetup: (self) => self.hook(Brightness[monitor], self => {
self.label = `${Math.round(Brightness[monitor].screen_value * 100)}`;
}, 'notify::screen-value'),
progressSetup: (self) => self.hook(Brightness[monitor], (progress) => {
const updateValue = Brightness[monitor].screen_value;
if (updateValue !== progress.value) Indicator.popup(1);
progress.value = updateValue;
}, 'notify::screen-value'),
});
const volumeIndicator = OsdValue({
name: 'Volume',
icon: 'volume_up',
extraClassName: 'osd-volume',
extraProgressClassName: 'osd-volume-progress',
attribute: { headphones: undefined , device: undefined},
nameSetup: (self) => Utils.timeout(1, () => {
const updateAudioDevice = (self) => {
const usingHeadphones = (Audio.speaker?.stream?.port)?.toLowerCase().includes('headphone');
if (volumeIndicator.attribute.headphones === undefined ||
volumeIndicator.attribute.headphones !== usingHeadphones) {
volumeIndicator.attribute.headphones = usingHeadphones;
self.label = usingHeadphones ? 'Headphones' : 'Speakers';
// Indicator.popup(1);
}
}
self.hook(Audio, updateAudioDevice);
Utils.timeout(1000, updateAudioDevice);
}),
labelSetup: (self) => self.hook(Audio, (label) => {
const newDevice = (Audio.speaker?.name);
const updateValue = Math.round(Audio.speaker?.volume * 100);
if (!isNaN(updateValue)) {
if (newDevice === volumeIndicator.attribute.device && updateValue != label.label) {
Indicator.popup(1);
}
}
volumeIndicator.attribute.device = newDevice;
label.label = `${updateValue}`;
}),
progressSetup: (self) => self.hook(Audio, (progress) => {
const updateValue = Audio.speaker?.volume;
if (!isNaN(updateValue)) {
if (updateValue > 1) progress.value = 1;
else progress.value = updateValue;
}
}),
});
return MarginRevealer({
transition: 'slide_down',
showClass: 'osd-show',
hideClass: 'osd-hide',
extraSetup: (self) => self
.hook(Indicator, (revealer, value) => {
if (value > -1) revealer.attribute.show();
else revealer.attribute.hide();
}, 'popup')
,
child: Box({
hpack: 'center',
vertical: false,
className: 'spacing-h--10',
children: [
brightnessIndicator,
volumeIndicator,
]
})
});
}
-32
View File
@@ -1,32 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Indicator from '../../services/indicator.js';
import IndicatorValues from './indicatorvalues.js';
import MusicControls from './musiccontrols.js';
import ColorScheme from './colorscheme.js';
import NotificationPopups from './notificationpopups.js';
export default (monitor = 0) => Widget.Window({
name: `indicator${monitor}`,
monitor,
className: 'indicator',
layer: 'overlay',
// exclusivity: 'ignore',
visible: true,
anchor: ['top'],
child: Widget.EventBox({
onHover: () => { //make the widget hide when hovering
Indicator.popup(-1);
},
child: Widget.Box({
vertical: true,
className: 'osd-window',
css: 'min-height: 2px;',
children: [
IndicatorValues(monitor),
MusicControls(),
NotificationPopups(),
ColorScheme(),
]
})
}),
});
@@ -1,412 +0,0 @@
const { GLib } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
const { exec, execAsync } = Utils;
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
import { fileExists } from '../.miscutils/files.js';
import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
import { showMusicControls } from '../../variables.js';
import { darkMode, hasPlasmaIntegration } from '../.miscutils/system.js';
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
const colorMode = Utils.exec(`bash -c "sed -n \'1p\' '${LIGHTDARK_FILE_LOCATION}'"`);
const lightDark = (colorMode == "light") ? 'light' : '';
const COVER_COLORSCHEME_SUFFIX = '_colorscheme.css';
var lastCoverPath = '';
function isRealPlayer(player) {
return (
// Remove unecessary native buses from browsers if there's plasma integration
!(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
!(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
// playerctld just copies other buses and we don't need duplicates
!player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') &&
// Non-instance mpd bus
!(player.busName.endsWith('.mpd') && !player.busName.endsWith('MediaPlayer2.mpd'))
);
}
export const getPlayer = (name = userOptions.music.preferredPlayer) => Mpris.getPlayer(name) || Mpris.players[0] || null;
function lengthStr(length) {
const min = Math.floor(length / 60);
const sec = Math.floor(length % 60);
const sec0 = sec < 10 ? '0' : '';
return `${min}:${sec0}${sec}`;
}
function detectMediaSource(link) {
if (link.startsWith("file://")) {
if (link.includes('firefox-mpris'))
return '󰈹 Firefox'
return "󰈣 File";
}
let url = link.replace(/(^\w+:|^)\/\//, '');
let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1];
if (domain == 'ytimg.com') return '󰗃 Youtube';
if (domain == 'discordapp.net') return '󰙯 Discord';
if (domain == 'sndcdn.com') return '󰓀 SoundCloud';
return domain;
}
const DEFAULT_MUSIC_FONT = 'Gabarito, sans-serif';
function getTrackfont(player) {
const title = player.trackTitle;
const artists = player.trackArtists.join(' ');
if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo'))
return 'Chakra Petch'; // Rigid square replacement
if (title.includes('東方'))
return 'Crimson Text, serif'; // Serif for Touhou stuff
return DEFAULT_MUSIC_FONT;
}
function trimTrackTitle(title) {
if (!title) return '';
const cleanPatterns = [
/【[^】]*】/, // Touhou n weeb stuff
" [FREE DOWNLOAD]", // F-777
];
cleanPatterns.forEach((expr) => title = title.replace(expr, ''));
return title;
}
const TrackProgress = ({ player, ...rest }) => {
const _updateProgress = (circprog) => {
// const player = Mpris.getPlayer();
if (!player) return;
// Set circular progress (see definition of AnimatedCircProg for explanation)
circprog.css = `font-size: ${Math.max(player.position / player.length * 100, 0)}px;`
}
return AnimatedCircProg({
...rest,
className: 'osd-music-circprog',
vpack: 'center',
extraSetup: (self) => self
.hook(Mpris, _updateProgress)
.poll(3000, _updateProgress)
,
})
}
const TrackTitle = ({ player, ...rest }) => Label({
...rest,
label: 'No music playing',
xalign: 0,
truncate: 'end',
// wrap: true,
className: 'osd-music-title',
setup: (self) => self.hook(player, (self) => {
// Player name
self.label = player.trackTitle.length > 0 ? trimTrackTitle(player.trackTitle) : 'No media';
// Font based on track/artist
const fontForThisTrack = getTrackfont(player);
self.css = `font-family: ${fontForThisTrack}, ${DEFAULT_MUSIC_FONT};`;
}, 'notify::track-title'),
});
const TrackArtists = ({ player, ...rest }) => Label({
...rest,
xalign: 0,
className: 'osd-music-artists',
truncate: 'end',
setup: (self) => self.hook(player, (self) => {
self.label = player.trackArtists.length > 0 ? player.trackArtists.join(', ') : '';
}, 'notify::track-artists'),
})
const CoverArt = ({ player, ...rest }) => {
const fallbackCoverArt = Box({ // Fallback
className: 'osd-music-cover-fallback',
homogeneous: true,
children: [Label({
className: 'icon-material txt-gigantic txt-thin',
label: 'music_note',
})]
});
// const coverArtDrawingArea = Widget.DrawingArea({ className: 'osd-music-cover-art' });
// const coverArtDrawingAreaStyleContext = coverArtDrawingArea.get_style_context();
const realCoverArt = Box({
className: 'osd-music-cover-art',
homogeneous: true,
// children: [coverArtDrawingArea],
attribute: {
'pixbuf': null,
// 'showImage': (self, imagePath) => {
// const borderRadius = coverArtDrawingAreaStyleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
// const frameHeight = coverArtDrawingAreaStyleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
// const frameWidth = coverArtDrawingAreaStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
// let imageHeight = frameHeight;
// let imageWidth = frameWidth;
// // Get image dimensions
// execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
// .then((output) => {
// const imageDimensions = JSON.parse(output);
// const imageAspectRatio = imageDimensions.w / imageDimensions.h;
// const displayedAspectRatio = imageWidth / imageHeight;
// if (imageAspectRatio >= displayedAspectRatio) {
// imageWidth = imageHeight * imageAspectRatio;
// } else {
// imageHeight = imageWidth / imageAspectRatio;
// }
// // Real stuff
// // TODO: fix memory leak(?)
// // if (self.attribute.pixbuf) {
// // self.attribute.pixbuf.unref();
// // self.attribute.pixbuf = null;
// // }
// self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, imageWidth, imageHeight);
// coverArtDrawingArea.set_size_request(frameWidth, frameHeight);
// coverArtDrawingArea.connect("draw", (widget, cr) => {
// // Clip a rounded rectangle area
// cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
// cr.arc(frameWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
// cr.arc(frameWidth - borderRadius, frameHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
// cr.arc(borderRadius, frameHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
// cr.closePath();
// cr.clip();
// // Paint image as bg, centered
// Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
// frameWidth / 2 - imageWidth / 2,
// frameHeight / 2 - imageHeight / 2
// );
// cr.paint();
// });
// }).catch(print)
// },
'updateCover': (self) => {
// const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this
// Player closed
// Note that cover path still remains, so we're checking title
if (!player || player.trackTitle == "" || !player.coverPath) {
self.css = `background-image: none;`; // CSS image
App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
return;
}
const coverPath = player.coverPath;
const stylePath = `${player.coverPath}${darkMode.value ? '' : '-l'}${COVER_COLORSCHEME_SUFFIX}`;
if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
Utils.timeout(200, () => {
// self.attribute.showImage(self, coverPath);
self.css = `background-image: url('${coverPath}');`; // CSS image
});
}
lastCoverPath = player.coverPath;
// If a colorscheme has already been generated, skip generation
if (fileExists(stylePath)) {
// self.attribute.showImage(self, coverPath)
self.css = `background-image: url('${coverPath}');`; // CSS image
App.applyCss(stylePath);
return;
}
// Generate colors
execAsync(['bash', '-c',
`${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' --mode ${darkMode.value ? 'dark' : 'light'} > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss`])
.then(() => {
exec(`${App.configDir}/scripts/color_generation/pywal.sh -i "${player.coverPath}" -n -t -s -e -q ${darkMode.value ? '' : '-l'}`)
exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss`);
exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/_music.scss" "${stylePath}"`);
Utils.timeout(200, () => {
// self.attribute.showImage(self, coverPath)
self.css = `background-image: url('${coverPath}');`; // CSS image
});
App.applyCss(`${stylePath}`);
})
.catch(print);
},
},
setup: (self) => self
.hook(player, (self) => {
self.attribute.updateCover(self);
}, 'notify::cover-path')
,
});
return Box({
...rest,
className: 'osd-music-cover',
children: [
Widget.Overlay({
child: fallbackCoverArt,
overlays: [realCoverArt],
})
],
})
}
const TrackControls = ({ player, ...rest }) => Widget.Revealer({
revealChild: false,
transition: 'slide_right',
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Box({
...rest,
vpack: 'center',
className: 'osd-music-controls spacing-h-3',
children: [
Button({
className: 'osd-music-controlbtn',
onClicked: () => player.previous(),
child: Label({
className: 'icon-material osd-music-controlbtn-txt',
label: 'skip_previous',
}),
setup: setupCursorHover
}),
Button({
className: 'osd-music-controlbtn',
onClicked: () => player.next(),
child: Label({
className: 'icon-material osd-music-controlbtn-txt',
label: 'skip_next',
}),
setup: setupCursorHover
}),
],
}),
setup: (self) => self.hook(Mpris, (self) => {
// const player = Mpris.getPlayer();
if (!player)
self.revealChild = false;
else
self.revealChild = true;
}, 'notify::play-back-status'),
});
const TrackSource = ({ player, ...rest }) => Widget.Revealer({
revealChild: false,
transition: 'slide_left',
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Box({
...rest,
className: 'osd-music-pill spacing-h-5',
homogeneous: true,
children: [
Label({
hpack: 'fill',
justification: 'center',
className: 'icon-nerd',
setup: (self) => self.hook(player, (self) => {
self.label = detectMediaSource(player.trackCoverUrl);
}, 'notify::cover-path'),
}),
],
}),
setup: (self) => self.hook(Mpris, (self) => {
const mpris = Mpris.getPlayer('');
if (!mpris)
self.revealChild = false;
else
self.revealChild = true;
}),
});
const TrackTime = ({ player, ...rest }) => {
return Widget.Revealer({
revealChild: false,
transition: 'slide_left',
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Box({
...rest,
vpack: 'center',
className: 'osd-music-pill spacing-h-5',
children: [
Label({
setup: (self) => self.poll(1000, (self) => {
// const player = Mpris.getPlayer();
if (!player) return;
self.label = lengthStr(player.position);
}),
}),
Label({ label: '/' }),
Label({
setup: (self) => self.hook(Mpris, (self) => {
// const player = Mpris.getPlayer();
if (!player) return;
self.label = lengthStr(player.length);
}),
}),
],
}),
setup: (self) => self.hook(Mpris, (self) => {
if (!player) self.revealChild = false;
else self.revealChild = true;
}),
})
}
const PlayState = ({ player }) => {
var position = 0;
const trackCircProg = TrackProgress({ player: player });
return Widget.Button({
className: 'osd-music-playstate',
child: Widget.Overlay({
child: trackCircProg,
overlays: [
Widget.Button({
className: 'osd-music-playstate-btn',
onClicked: () => player.playPause(),
child: Widget.Label({
justification: 'center',
hpack: 'fill',
vpack: 'center',
setup: (self) => self.hook(player, (label) => {
label.label = `${player.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
}, 'notify::play-back-status'),
}),
setup: setupCursorHover
}),
],
passThrough: true,
}),
});
}
const MusicControlsWidget = (player) => Box({
className: 'osd-music spacing-h-20 test',
children: [
CoverArt({ player: player, vpack: 'center' }),
Box({
vertical: true,
className: 'spacing-v-5 osd-music-info',
children: [
Box({
vertical: true,
vpack: 'center',
hexpand: true,
children: [
TrackTitle({ player: player }),
TrackArtists({ player: player }),
]
}),
Box({ vexpand: true }),
Box({
className: 'spacing-h-10',
setup: (box) => {
box.pack_start(TrackControls({ player: player }), false, false, 0);
box.pack_end(PlayState({ player: player }), false, false, 0);
if(hasPlasmaIntegration || player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) box.pack_end(TrackTime({ player: player }), false, false, 0)
// box.pack_end(TrackSource({ vpack: 'center', player: player }), false, false, 0);
}
})
]
})
]
})
export default () => Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
revealChild: false,
child: Box({
children: Mpris.bind("players")
.as(players => players.map((player) => (isRealPlayer(player) ? MusicControlsWidget(player) : null)))
}),
setup: (self) => self.hook(showMusicControls, (revealer) => {
revealer.revealChild = showMusicControls.value;
}),
})
@@ -1,45 +0,0 @@
// This file is for popup notifications
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
const { Box } = Widget;
import Notification from '../.commonwidgets/notification.js';
export default () => Box({
vertical: true,
hpack: 'center',
className: 'osd-notifs spacing-v-5-revealer',
attribute: {
'map': new Map(),
'dismiss': (box, id, force = false) => {
if (!id || !box.attribute.map.has(id))
return;
const notifWidget = box.attribute.map.get(id);
if (notifWidget == null || notifWidget.attribute.hovered && !force)
return; // cuz already destroyed
notifWidget.revealChild = false;
notifWidget.attribute.destroyWithAnims();
box.attribute.map.delete(id);
},
'notify': (box, id) => {
if (!id || Notifications.dnd) return;
if (!Notifications.getNotification(id)) return;
box.attribute.map.delete(id);
const notif = Notifications.getNotification(id);
const newNotif = Notification({
notifObject: notif,
isPopup: true,
});
box.attribute.map.set(id, newNotif);
box.pack_end(box.attribute.map.get(id), false, false, 0);
box.show_all();
},
},
setup: (self) => self
.hook(Notifications, (box, id) => box.attribute.notify(box, id), 'notified')
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id), 'dismissed')
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed')
,
});
@@ -1,11 +0,0 @@
import PopupWindow from '../.widgethacks/popupwindow.js';
import OnScreenKeyboard from "./onscreenkeyboard.js";
export default (id) => PopupWindow({
monitor: id,
anchor: ['bottom'],
name: `osk${id}`,
showClassName: 'osk-show',
hideClassName: 'osk-hide',
child: OnScreenKeyboard({ id: id }),
});
@@ -1,267 +0,0 @@
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, EventBox, Button, Revealer } = Widget;
const { execAsync } = Utils;
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
import { DEFAULT_OSK_LAYOUT, oskLayouts } from './data_keyboardlayouts.js';
import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
const keyboardLayout = oskLayouts[userOptions.onScreenKeyboard.layout] ? userOptions.onScreenKeyboard.layout : DEFAULT_OSK_LAYOUT;
const keyboardJson = oskLayouts[keyboardLayout];
async function startYdotoolIfNeeded() {
const running = exec('pidof ydotool')
if (!running) execAsync(['ydotoold']).catch(print);
}
function releaseAllKeys() {
const keycodes = Array.from(Array(249).keys());
execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
.then(console.log('[OSK] Released all keys'))
.catch(print);
}
class ShiftMode {
static Off = new ShiftMode('Off');
static Normal = new ShiftMode('Normal');
static Locked = new ShiftMode('Locked');
constructor(name) {
this.name = name;
}
toString() {
return `ShiftMode.${this.name}`;
}
}
var modsPressed = false;
const TopDecor = () => Box({
vertical: true,
children: [
Box({
hpack: 'center',
className: 'osk-dragline',
homogeneous: true,
children: [EventBox({
setup: setupCursorHoverGrab,
})]
})
]
});
const KeyboardControlButton = (icon, text, runFunction) => Button({
className: 'osk-control-button spacing-h-10',
onClicked: () => runFunction(),
child: Widget.Box({
children: [
MaterialIcon(icon, 'norm'),
Widget.Label({
label: `${text}`,
}),
]
})
})
const KeyboardControls = () => Box({
vertical: true,
className: 'spacing-v-5',
children: [
Button({
className: 'osk-control-button txt-norm icon-material',
onClicked: () => {
releaseAllKeys();
toggleWindowOnAllMonitors('osk');
},
label: 'keyboard_hide',
}),
Button({
className: 'osk-control-button txt-norm',
label: `${keyboardJson['name_short']}`,
}),
Button({
className: 'osk-control-button txt-norm icon-material',
onClicked: () => { // TODO: Proper clipboard widget, since fuzzel doesn't receive mouse inputs
execAsync([`bash`, `-c`, "pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy"]).catch(print);
},
label: 'assignment',
}),
]
})
var shiftMode = ShiftMode.Off;
var shiftButton;
var rightShiftButton;
var allButtons = [];
const KeyboardItself = (kbJson) => {
return Box({
vertical: true,
className: 'spacing-v-5',
children: kbJson.keys.map(row => Box({
vertical: false,
className: 'spacing-h-5',
children: row.map(key => {
return Button({
className: `osk-key osk-key-${key.shape}`,
hexpand: ["space", "expand"].includes(key.shape),
label: key.label,
attribute:
{ key: key },
setup: (button) => {
let pressed = false;
allButtons = allButtons.concat(button);
if (key.keytype == "normal") {
button.connect('pressed', () => { // mouse down
execAsync(`ydotool key ${key.keycode}:1`).catch(print);
});
button.connect('clicked', () => { // release
execAsync(`ydotool key ${key.keycode}:0`).catch(print);
if (shiftMode == ShiftMode.Normal) {
shiftMode = ShiftMode.Off;
if (typeof shiftButton !== 'undefined') {
execAsync(`ydotool key 42:0`).catch(print);
shiftButton.toggleClassName('osk-key-active', false);
}
if (typeof rightShiftButton !== 'undefined') {
execAsync(`ydotool key 54:0`).catch(print);
rightShiftButton.toggleClassName('osk-key-active', false);
}
allButtons.forEach(button => {
if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label;
})
}
});
}
else if (key.keytype == "modkey") {
button.connect('pressed', () => { // release
if (pressed) {
execAsync(`ydotool key ${key.keycode}:0`).catch(print);
button.toggleClassName('osk-key-active', false);
pressed = false;
if (key.keycode == 100) { // Alt Gr button
allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.label; });
}
}
else {
execAsync(`ydotool key ${key.keycode}:1`).catch(print);
button.toggleClassName('osk-key-active', true);
if (!(key.keycode == 42 || key.keycode == 54)) pressed = true;
else switch (shiftMode.name) { // This toggles the shift button state
case "Off": {
shiftMode = ShiftMode.Normal;
allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.labelShift; })
if (typeof shiftButton !== 'undefined') {
shiftButton.toggleClassName('osk-key-active', true);
}
if (typeof rightShiftButton !== 'undefined') {
rightShiftButton.toggleClassName('osk-key-active', true);
}
} break;
case "Normal": {
shiftMode = ShiftMode.Locked;
if (typeof shiftButton !== 'undefined') shiftButton.label = key.labelCaps;
if (typeof rightShiftButton !== 'undefined') rightShiftButton.label = key.labelCaps;
} break;
case "Locked": {
shiftMode = ShiftMode.Off;
if (typeof shiftButton !== 'undefined') {
shiftButton.label = key.label;
shiftButton.toggleClassName('osk-key-active', false);
}
if (typeof rightShiftButton !== 'undefined') {
rightShiftButton.label = key.label;
rightShiftButton.toggleClassName('osk-key-active', false);
}
execAsync(`ydotool key ${key.keycode}:0`).catch(print);
allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label; }
)
};
}
if (key.keycode == 100) { // Alt Gr button
allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.labelAlt; });
}
modsPressed = true;
}
});
if (key.keycode == 42) shiftButton = button;
else if (key.keycode == 54) rightShiftButton = button;
}
}
})
})
}))
})
}
const KeyboardWindow = () => Box({
vexpand: true,
hexpand: true,
vertical: true,
className: 'osk-window spacing-v-5',
children: [
TopDecor(),
Box({
className: 'osk-body spacing-h-10',
children: [
KeyboardControls(),
Widget.Box({ className: 'separator-line' }),
KeyboardItself(keyboardJson),
],
})
],
setup: (self) => self.hook(App, (self, name, visible) => { // Update on open
if (!name) return;
if (name.startsWith('osk') && visible) {
self.setCss(`margin-bottom: -0px;`);
}
}),
});
export default ({ id }) => {
const kbWindow = KeyboardWindow();
const gestureEvBox = EventBox({ child: kbWindow })
const gesture = Gtk.GestureDrag.new(gestureEvBox);
gesture.connect('drag-begin', async () => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.messageAsync('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y;
}).catch(print);
} catch {
return;
}
});
gesture.connect('drag-update', async () => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
Hyprland.messageAsync('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY;
if (offset > 0) return;
kbWindow.setCss(`
margin-bottom: ${offset}px;
`);
}).catch(print);
} catch {
return;
}
});
gesture.connect('drag-end', () => {
var offset = gesture.get_offset()[2];
if (offset > 50) {
App.closeWindow(`osk${id}`);
}
else {
kbWindow.setCss(`
transition: margin-bottom 170ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-bottom: 0px;
`);
}
})
return gestureEvBox;
};
-28
View File
@@ -1,28 +0,0 @@
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
function moveClientToWorkspace(address, workspace) {
Utils.execAsync(['bash', '-c', `hyprctl dispatch movetoworkspacesilent ${workspace},address:${address} &`]);
}
export function dumpToWorkspace(from, to) {
if (from == to) return;
Hyprland.clients.forEach(client => {
if (client.workspace.id == from) {
moveClientToWorkspace(client.address, to);
}
});
}
export function swapWorkspace(workspaceA, workspaceB) {
if (workspaceA == workspaceB) return;
const clientsA = [];
const clientsB = [];
Hyprland.clients.forEach(client => {
if (client.workspace.id == workspaceA) clientsA.push(client.address);
if (client.workspace.id == workspaceB) clientsB.push(client.address);
});
clientsA.forEach((address) => moveClientToWorkspace(address, workspaceB));
clientsB.forEach((address) => moveClientToWorkspace(address, workspaceA));
}
-28
View File
@@ -1,28 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { SearchAndWindows } from "./windowcontent.js";
import PopupWindow from '../.widgethacks/popupwindow.js';
import { clickCloseRegion } from '../.commonwidgets/clickcloseregion.js';
export default (id = '') => PopupWindow({
name: `overview${id}`,
// exclusivity: 'ignore',
keymode: 'on-demand',
visible: false,
anchor: ['top', 'bottom', 'left', 'right'],
layer: 'top',
child: Widget.Box({
vertical: true,
children: [
clickCloseRegion({ name: 'overview', multimonitor: false, expand: false }),
Widget.Box({
children: [
clickCloseRegion({ name: 'overview', multimonitor: false }),
SearchAndWindows(),
clickCloseRegion({ name: 'overview', multimonitor: false }),
]
}),
clickCloseRegion({ name: 'overview', multimonitor: false }),
]
}),
})
@@ -1,151 +0,0 @@
const { Gio, GLib } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
import Todo from "../../services/todo.js";
export function hasUnterminatedBackslash(inputString) {
// Use a regular expression to match a trailing odd number of backslashes
const regex = /\\+$/;
return regex.test(inputString);
}
export function launchCustomCommand(command) {
const args = command.toLowerCase().split(' ');
if (args[0] == '>raw') { // Mouse raw input
Utils.execAsync('hyprctl -j getoption input:accel_profile')
.then((output) => {
const value = JSON.parse(output)["str"].trim();
if (value != "[[EMPTY]]" && value != "") {
execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
}
else {
execAsync(['bash', '-c', `hyprctl keyword input:accel_profile flat`]).catch(print);
}
})
}
else if (args[0] == '>img') { // Change wallpaper
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchwall.sh`, `&`]).catch(print);
}
else if (args[0] == '>color') { // Generate colorscheme from color picker
if (!args[1])
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh --pick`, `&`]).catch(print);
else if (args[1][0] === '#')
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${args[1]}"`, `&`]).catch(print);
}
else if (args[0] == '>light') { // Light mode
darkMode.setValue(false).catch(print);
}
else if (args[0] == '>dark') { // Dark mode
darkMode.setValue(true).catch(print);
}
else if (args[0] == '>badapple') { // Black and white
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "3s/.*/monochrome/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
.catch(print);
}
else if (args[0] == '>material') { // Use material colors
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && echo "material" > ${GLib.get_user_state_dir()}/ags/user/colorbackend.txt`]).catch(print)
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
.catch(print);
}
else if (args[0] == '>pywal') { // Use Pywal (ik it looks shit but I'm not removing)
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && echo "pywal" > ${GLib.get_user_state_dir()}/ags/user/colorbackend.txt`]).catch(print)
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
.catch(print);
}
else if (args[0] == '>todo') { // Todo
Todo.add(args.slice(1).join(' '));
}
else if (args[0] == '>shutdown') { // Shut down
execAsync([`bash`, `-c`, `systemctl poweroff || loginctl poweroff`]).catch(print);
}
else if (args[0] == '>reboot') { // Reboot
execAsync([`bash`, `-c`, `systemctl reboot || loginctl reboot`]).catch(print);
}
else if (args[0] == '>sleep') { // Sleep
execAsync([`bash`, `-c`, `systemctl suspend || loginctl suspend`]).catch(print);
}
else if (args[0] == '>logout') { // Log out
execAsync([`bash`, `-c`, `pkill Hyprland || pkill sway`]).catch(print);
}
}
export function execAndClose(command, terminal) {
App.closeWindow('overview');
if (terminal) {
execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${command}"`, `&`]).catch(print);
}
else
execAsync(command).catch(print);
}
export function couldBeMath(str) {
const regex = /^[0-9.+*/-]/;
return regex.test(str);
}
export function expandTilde(path) {
if (path.startsWith('~')) {
return GLib.get_home_dir() + path.slice(1);
} else {
return path;
}
}
function getFileIcon(fileInfo) {
let icon = fileInfo.get_icon();
if (icon) {
// Get the icon's name
return icon.get_names()[0];
} else {
// Default icon for files
return 'text-x-generic';
}
}
export function ls({ path = '~', silent = false }) {
let contents = [];
try {
let expandedPath = expandTilde(path);
if (expandedPath.endsWith('/'))
expandedPath = expandedPath.slice(0, -1);
let folder = Gio.File.new_for_path(expandedPath);
let enumerator = folder.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
let fileInfo;
while ((fileInfo = enumerator.next_file(null)) !== null) {
let fileName = fileInfo.get_display_name();
let fileType = fileInfo.get_file_type();
let item = {
parentPath: expandedPath,
name: fileName,
type: fileType === Gio.FileType.DIRECTORY ? 'folder' : 'file',
icon: getFileIcon(fileInfo),
};
// Add file extension for files
if (fileType === Gio.FileType.REGULAR) {
let fileExtension = fileName.split('.').pop();
item.type = `${fileExtension}`;
}
contents.push(item);
contents.sort((a, b) => {
const aIsFolder = a.type.startsWith('folder');
const bIsFolder = b.type.startsWith('folder');
if (aIsFolder && !bIsFolder) {
return -1;
} else if (!aIsFolder && bIsFolder) {
return 1;
} else {
return a.name.localeCompare(b.name); // Sort alphabetically within folders and files
}
});
}
} catch (e) {
if (!silent) console.log(e);
}
return contents;
}
@@ -1,446 +0,0 @@
// TODO
// - Make client destroy/create not destroy and recreate the whole thing
// - Active ws hook optimization: only update when moving to next group
//
const { Gdk, Gtk } = imports.gi;
const { Gravity } = imports.gi.Gdk;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
import { dumpToWorkspace, swapWorkspace } from "./actions.js";
import { iconExists, substitute } from "../.miscutils/icons.js";
import { monitors } from '../.commondata/hyprlanddata.js';
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
const NUM_OF_WORKSPACES_SHOWN = userOptions.overview.numOfCols * userOptions.overview.numOfRows;
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
const overviewTick = Variable(false);
const overviewMonitor = Variable(0);
export default () => {
const clientMap = new Map();
const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
label: `${label}`,
setup: (menuItem) => {
let submenu = new Gtk.Menu();
submenu.className = 'menu';
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
const startWorkspace = offset + 1;
const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1;
for (let i = startWorkspace; i <= endWorkspace; i++) {
let button = new Gtk.MenuItem({
label: `Workspace ${i}`
});
button.connect("activate", () => {
// execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
actionFunc(thisWorkspace, i);
overviewTick.setValue(!overviewTick.value);
});
submenu.append(button);
}
menuItem.set_reserve_indicator(true);
menuItem.set_submenu(submenu);
}
})
const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, initialClass, monitor, title, xwayland }, screenCoords) => {
const revealInfoCondition = (Math.min(w, h) * userOptions.overview.scale > 70);
if (w <= 0 || h <= 0 || (c === '' && title === '')) return null;
// Non-primary monitors
if (screenCoords.x != 0) x -= screenCoords.x;
if (screenCoords.y != 0) y -= screenCoords.y;
// Other offscreen adjustments
if (x + w <= 0) x += (Math.floor(x / monitors[monitor].width) * monitors[monitor].width);
else if (x < 0) { w = x + w; x = 0; }
if (y + h <= 0) x += (Math.floor(y / monitors[monitor].height) * monitors[monitor].height);
else if (y < 0) { h = y + h; y = 0; }
// Prevents throwing an error when multiple monitors are plugged in but only one is enabled (#1047)
if (monitors.length - 1 < monitor) {
monitor = monitors.length - 1;
}
// Properly scale for multi monitors
w *= monitors[overviewMonitor.value].width / monitors[monitor].width;
h *= monitors[overviewMonitor.value].height / monitors[monitor].height;
// Truncate if offscreen
if (x + w > monitors[overviewMonitor.value].width) w = monitors[overviewMonitor.value].width - x;
if (y + h > monitors[overviewMonitor.value].height) h = monitors[overviewMonitor.value].height - y;
if (c.length == 0) c = initialClass;
const iconName = substitute(c);
// const appIcon = iconExists(iconName) ? Widget.Icon({
// icon: iconName,
// size: Math.min(w, h) * userOptions.overview.scale / 2.5,
// }) : MaterialIcon('terminal', 'gigantic', {
// css: `font-size: ${Math.min(w, h) * userOptions.overview.scale / 2.5}px`,
// });
const appIcon = Widget.Icon({
icon: iconName,
size: Math.min(w, h) * userOptions.overview.scale / 2.5,
});
return Widget.Button({
attribute: {
address, x, y, w, h, ws: id,
updateIconSize: (self) => {
appIcon.size = Math.min(self.attribute.w, self.attribute.h) * userOptions.overview.scale / 2.5;
},
},
className: 'overview-tasks-window',
hpack: 'start',
vpack: 'start',
css: `
margin-left: ${Math.round(x * userOptions.overview.scale)}px;
margin-top: ${Math.round(y * userOptions.overview.scale)}px;
margin-right: -${Math.round((x + w) * userOptions.overview.scale)}px;
margin-bottom: -${Math.round((y + h) * userOptions.overview.scale)}px;
`,
onClicked: (self) => {
Hyprland.messageAsync(`dispatch focuswindow address:${address}`);
App.closeWindow('overview');
},
onMiddleClickRelease: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
onSecondaryClick: (button) => {
button.toggleClassName('overview-tasks-window-selected', true);
const menu = Widget.Menu({
className: 'menu',
children: [
Widget.MenuItem({
child: Widget.Label({
xalign: 0,
label: "Close (Middle-click)",
}),
onActivate: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
}),
ContextMenuWorkspaceArray({
label: "Dump windows to workspace",
actionFunc: dumpToWorkspace,
thisWorkspace: Number(id)
}),
ContextMenuWorkspaceArray({
label: "Swap windows with workspace",
actionFunc: swapWorkspace,
thisWorkspace: Number(id)
}),
],
});
menu.connect("deactivate", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.connect("selection-done", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.popup_at_widget(button.get_parent(), Gravity.SOUTH, Gravity.NORTH, null); // Show menu below the button
button.connect("destroy", () => menu.destroy());
},
child: Widget.Box({
homogeneous: true,
child: Widget.Box({
vertical: true,
vpack: 'center',
children: [
appIcon,
// TODO: Add xwayland tag instead of just having italics
Widget.Revealer({
transition: 'slide_right',
revealChild: revealInfoCondition,
child: Widget.Revealer({
transition: 'slide_down',
revealChild: revealInfoCondition,
child: Widget.Label({
maxWidthChars: 1, // Doesn't matter what number
truncate: 'end',
className: `margin-top-5 ${xwayland ? 'txt txt-italic' : 'txt'}`,
css: overviewMonitor.bind().as(monitor => `
font-size: ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale / 14.6}px;
margin: 0px ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale / 10}px;
`),
// If the title is too short, include the class
label: (title.length <= 1 ? `${c}: ${title}` : title),
})
})
})
]
})
}),
tooltipText: `${c}: ${title}`,
setup: (button) => {
setupCursorHoverGrab(button);
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
button.drag_source_set_icon_name(substitute(c));
button.connect('drag-begin', (button) => { // On drag start, add the dragging class
button.toggleClassName('overview-tasks-window-dragging', true);
});
button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
data.set_text(address, address.length);
button.toggleClassName('overview-tasks-window-dragging', false);
});
},
});
}
const Workspace = (index) => {
// const fixed = Widget.Fixed({
// attribute: {
// put: (widget, x, y) => {
// fixed.put(widget, x, y);
// },
// move: (widget, x, y) => {
// fixed.move(widget, x, y);
// },
// }
// });
const fixed = Widget.Box({
attribute: {
put: (widget, x, y) => {
if (!widget.attribute) return;
// Note: x and y are already multiplied by userOptions.overview.scale
const newCss = `
margin-left: ${Math.round(x)}px;
margin-top: ${Math.round(y)}px;
margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
`;
widget.css = newCss;
fixed.pack_start(widget, false, false, 0);
},
move: (widget, x, y) => {
if (!widget) return;
if (!widget.attribute) return;
// Note: x and y are already multiplied by userOptions.overview.scale
const newCss = `
margin-left: ${Math.round(x)}px;
margin-top: ${Math.round(y)}px;
margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
`;
widget.css = newCss;
},
}
})
const WorkspaceNumber = ({ index, ...rest }) => Widget.Label({
className: 'overview-tasks-workspace-number',
label: `${index}`,
css: overviewMonitor.bind().as(monitor => `
margin: ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale * userOptions.overview.wsNumMarginScale}px;
font-size: ${monitors[monitor].height * userOptions.overview.scale * userOptions.overview.wsNumScale}px;
`),
setup: (self) => self.hook(Hyprland.active.workspace, (self) => {
// Update when going to new ws group
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
self.label = `${currentGroup * NUM_OF_WORKSPACES_SHOWN + index}`;
}),
...rest,
})
const widget = Widget.Box({
className: 'overview-tasks-workspace',
vpack: 'center',
// Rounding and adding 1px to minimum width/height to work around scaling inaccuracy:
css: overviewMonitor.bind().as(monitor => `
min-width: ${1 + Math.round(monitors[monitor].width * userOptions.overview.scale)}px;
min-height: ${1 + Math.round(monitors[monitor].height * userOptions.overview.scale)}px;
`),
children: [Widget.EventBox({
hexpand: true,
onPrimaryClick: () => {
Hyprland.messageAsync(`dispatch workspace ${index}`);
App.closeWindow('overview');
},
setup: (eventbox) => {
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
Hyprland.messageAsync(`dispatch movetoworkspacesilent ${index + offset},address:${data.get_text()}`)
overviewTick.setValue(!overviewTick.value);
});
},
child: Widget.Overlay({
child: Widget.Box({}),
overlays: [
WorkspaceNumber({ index: index, hpack: 'start', vpack: 'start' }),
fixed
]
}),
})],
});
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
fixed.attribute.put(WorkspaceNumber(offset + index), 0, 0);
widget.clear = () => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
clientMap.forEach((client, address) => {
if (!client) return;
if ((client.attribute.ws <= offset || client.attribute.ws > offset + NUM_OF_WORKSPACES_SHOWN) ||
(client.attribute.ws == offset + index)) {
client.destroy();
client = null;
clientMap.delete(address);
}
});
}
widget.set = (clientJson, screenCoords) => {
let c = clientMap.get(clientJson.address);
if (c) {
if (c.attribute?.ws !== clientJson.workspace.id) {
c.destroy();
c = null;
clientMap.delete(clientJson.address);
}
else if (c) {
c.attribute.w = clientJson.size[0];
c.attribute.h = clientJson.size[1];
c.attribute.updateIconSize(c);
fixed.attribute.move(c,
Math.max(0, clientJson.at[0] * userOptions.overview.scale),
Math.max(0, clientJson.at[1] * userOptions.overview.scale)
);
return;
}
}
const newWindow = Window(clientJson, screenCoords);
if (newWindow === null) return;
// clientMap.set(clientJson.address, newWindow);
fixed.attribute.put(newWindow,
Math.max(0, newWindow.attribute.x * userOptions.overview.scale),
Math.max(0, newWindow.attribute.y * userOptions.overview.scale)
);
clientMap.set(clientJson.address, newWindow);
};
widget.unset = (clientAddress) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
let c = clientMap.get(clientAddress);
if (!c) return;
c.destroy();
c = null;
clientMap.delete(clientAddress);
};
widget.show = () => {
fixed.show_all();
}
return widget;
};
const arr = (s, n) => {
const array = [];
for (let i = 0; i < n; i++)
array.push(s + i);
return array;
};
const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
children: arr(startWorkspace, workspaces).map(Workspace),
attribute: {
workspaceGroup: Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN),
monitorMap: [],
getMonitorMap: (box) => {
execAsync('hyprctl -j monitors').then(monitors => {
box.attribute.monitorMap = JSON.parse(monitors).reduce((acc, item) => {
acc[item.id] = { x: item.x, y: item.y };
return acc;
}, {});
});
},
update: (box) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
Hyprland.messageAsync('j/clients').then(clients => {
const allClients = JSON.parse(clients);
const kids = box.get_children();
kids.forEach(kid => kid.clear());
for (let i = 0; i < allClients.length; i++) {
const client = allClients[i];
const childID = client.workspace.id - (offset + startWorkspace);
if (offset + startWorkspace <= client.workspace.id &&
client.workspace.id <= offset + startWorkspace + workspaces) {
const screenCoords = box.attribute.monitorMap[client.monitor];
if (kids[childID]) {
kids[childID].set(client, screenCoords);
}
continue;
}
}
kids.forEach(kid => kid.show());
}).catch(print);
},
updateWorkspace: (box, id) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
if (!( // Not in range, ignore
offset + startWorkspace <= id &&
id <= offset + startWorkspace + workspaces
)) return;
// if (!App.getWindow(windowName)?.visible) return;
Hyprland.messageAsync('j/clients').then(clients => {
const allClients = JSON.parse(clients);
const kids = box.get_children();
for (let i = 0; i < allClients.length; i++) {
const client = allClients[i];
if (client.workspace.id != id) continue;
const screenCoords = box.attribute.monitorMap[client.monitor];
kids[id - (offset + startWorkspace)]?.set(client, screenCoords);
}
kids[id - (offset + startWorkspace)]?.show();
}).catch(print);
},
},
setup: (box) => {
box.attribute.getMonitorMap(box);
box
.hook(overviewTick, (box) => box.attribute.update(box))
.hook(Hyprland, (box, clientAddress) => {
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
const kids = box.get_children();
const client = Hyprland.getClient(clientAddress);
if (!client) return;
const id = client.workspace.id;
box.attribute.updateWorkspace(box, id);
kids[id - (offset + startWorkspace)]?.unset(clientAddress);
}, 'client-removed')
.hook(Hyprland, (box, clientAddress) => {
const client = Hyprland.getClient(clientAddress);
if (!client) return;
box.attribute.updateWorkspace(box, client.workspace.id);
}, 'client-added')
.hook(Hyprland.active.workspace, (box) => {
// Full update when going to new ws group
const previousGroup = box.attribute.workspaceGroup;
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
if (currentGroup !== previousGroup) {
if (!App.getWindow(windowName) || !App.getWindow(windowName).visible) return;
box.attribute.update(box);
box.attribute.workspaceGroup = currentGroup;
}
})
.hook(App, (box, name, visible) => { // Update on open
if (name == 'overview' && visible) {
overviewMonitor.value = Hyprland.active.monitor.id;
box.attribute.update(box);
}
})
},
});
return Widget.Revealer({
revealChild: true,
// hpack to prevent unneeded expansion in overview-tasks-workspace:
hpack: 'center',
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Box({
vertical: true,
className: 'overview-tasks',
children: Array.from({ length: userOptions.overview.numOfRows }, (_, index) =>
OverviewRow({
startWorkspace: 1 + index * userOptions.overview.numOfCols,
workspaces: userOptions.overview.numOfCols,
})
)
}),
});
}
@@ -1,189 +0,0 @@
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
import { searchItem } from './searchitem.js';
import { execAndClose, couldBeMath, launchCustomCommand } from './miscfunctions.js';
import GeminiService from '../../services/gemini.js';
export const NoResultButton = () => searchItem({
materialIconName: 'Error',
name: "Search invalid",
content: "No results found!",
onActivate: () => {
App.closeWindow('overview');
},
});
export const DirectoryButton = ({ parentPath, name, type, icon }) => {
const actionText = Widget.Revealer({
revealChild: false,
transition: "crossfade",
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Label({
className: 'overview-search-results-txt txt txt-small txt-action',
label: 'Open',
})
});
const actionTextRevealer = Widget.Revealer({
revealChild: false,
transition: "slide_left",
transitionDuration: userOptions.animations.durationSmall,
child: actionText,
});
return Widget.Button({
className: 'overview-search-result-btn',
onClicked: () => {
App.closeWindow('overview');
execAsync(['bash', '-c', `xdg-open '${parentPath}/${name}'`, `&`]).catch(print);
},
child: Widget.Box({
children: [
Widget.Box({
vertical: false,
children: [
Widget.Box({
className: 'overview-search-results-icon',
homogeneous: true,
child: Widget.Icon({
icon: icon,
}),
}),
Widget.Label({
className: 'overview-search-results-txt txt txt-norm',
label: name,
}),
Widget.Box({ hexpand: true }),
actionTextRevealer,
]
})
]
}),
setup: (self) => self
.on('focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
})
.on('focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
})
,
})
}
export const CalculationResultButton = ({ result, text }) => searchItem({
materialIconName: 'calculate',
name: `Math result`,
actionName: "Copy",
content: `${result}`,
onActivate: () => {
App.closeWindow('overview');
execAsync(['wl-copy', `${result}`]).catch(print);
},
});
export const DesktopEntryButton = (app) => {
const actionText = Widget.Revealer({
revealChild: false,
transition: "crossfade",
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Label({
className: 'overview-search-results-txt txt txt-small txt-action',
label: 'Launch',
})
});
const actionTextRevealer = Widget.Revealer({
revealChild: false,
transition: "slide_left",
transitionDuration: userOptions.animations.durationSmall,
child: actionText,
});
return Widget.Button({
className: 'overview-search-result-btn',
onClicked: () => {
App.closeWindow('overview');
app.launch();
},
child: Widget.Box({
children: [
Widget.Box({
vertical: false,
children: [
Widget.Box({
className: 'overview-search-results-icon',
homogeneous: true,
child: Widget.Icon({
icon: app.iconName,
}),
}),
Widget.Label({
className: 'overview-search-results-txt txt txt-norm',
label: app.name,
}),
Widget.Box({ hexpand: true }),
actionTextRevealer,
]
})
]
}),
setup: (self) => self
.on('focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
})
.on('focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
})
,
})
}
export const ExecuteCommandButton = ({ command, terminal = false }) => searchItem({
materialIconName: `${terminal ? 'terminal' : 'settings_b_roll'}`,
name: `Run command`,
actionName: `Execute ${terminal ? 'in terminal' : ''}`,
content: `${command}`,
onActivate: () => execAndClose(command, terminal),
extraClassName: 'techfont',
})
export const CustomCommandButton = ({ text = '' }) => searchItem({
materialIconName: 'settings_suggest',
name: 'Action',
actionName: 'Run',
content: `${text}`,
onActivate: () => {
App.closeWindow('overview');
launchCustomCommand(text);
},
});
export const SearchButton = ({ text = '' }) => searchItem({
materialIconName: 'travel_explore',
name: 'Search the web',
actionName: 'Go',
content: `${text}`,
onActivate: () => {
App.closeWindow('overview');
let search = userOptions.search.engineBaseUrl + text;
for (let site of userOptions.search.excludedSites) {
if (site) search += ` -site:${site}`;
}
execAsync(['bash', '-c', `xdg-open '${search}' &`]).catch(print);
},
});
export const AiButton = ({ text }) => searchItem({
materialIconName: 'chat_paste_go',
name: 'Ask Gemini',
actionName: 'Ask',
content: `${text}`,
onActivate: () => {
GeminiService.send(text);
App.closeWindow('overview');
App.openWindow('sideleft');
},
});
@@ -1,65 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
export const searchItem = ({ materialIconName, name, actionName, content, onActivate, extraClassName = '', ...rest }) => {
const actionText = Widget.Revealer({
revealChild: false,
transition: "crossfade",
transitionDuration: userOptions.animations.durationLarge,
child: Widget.Label({
className: 'overview-search-results-txt txt txt-small txt-action',
label: `${actionName}`,
})
});
const actionTextRevealer = Widget.Revealer({
revealChild: false,
transition: "slide_left",
transitionDuration: userOptions.animations.durationSmall,
child: actionText,
})
return Widget.Button({
className: `overview-search-result-btn txt ${extraClassName}`,
onClicked: onActivate,
child: Widget.Box({
children: [
Widget.Box({
vertical: false,
children: [
Widget.Label({
className: `icon-material overview-search-results-icon`,
label: `${materialIconName}`,
}),
Widget.Box({
vertical: true,
children: [
Widget.Label({
hpack: 'start',
className: 'overview-search-results-txt txt-smallie txt-subtext',
label: `${name}`,
truncate: "end",
}),
Widget.Label({
hpack: 'start',
className: 'overview-search-results-txt txt-norm',
label: `${content}`,
truncate: "end",
}),
]
}),
Widget.Box({ hexpand: true }),
actionTextRevealer,
],
})
]
}),
setup: (self) => self
.on('focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
})
.on('focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
})
,
});
}
@@ -1,213 +0,0 @@
const { Gdk, Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
const { execAsync, exec } = Utils;
import { execAndClose, expandTilde, hasUnterminatedBackslash, couldBeMath, launchCustomCommand, ls } from './miscfunctions.js';
import {
CalculationResultButton, CustomCommandButton, DirectoryButton,
DesktopEntryButton, ExecuteCommandButton, SearchButton, AiButton, NoResultButton,
} from './searchbuttons.js';
import { checkKeybind } from '../.widgetutils/keybind.js';
import GeminiService from '../../services/gemini.js';
// Add math funcs
const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
const pi = Math.PI;
// trigonometric funcs for deg
const sind = x => sin(x * pi / 180);
const cosd = x => cos(x * pi / 180);
const tand = x => tan(x * pi / 180);
const cotd = x => cot(x * pi / 180);
const asind = x => asin(x) * 180 / pi;
const acosd = x => acos(x) * 180 / pi;
const atand = x => atan(x) * 180 / pi;
const acotd = x => acot(x) * 180 / pi;
const MAX_RESULTS = 10;
const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
const OVERVIEW_WS_NUM_SCALE = 0.09;
const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
function iconExists(iconName) {
let iconTheme = Gtk.IconTheme.get_default();
return iconTheme.has_icon(iconName);
}
const OptionalOverview = async () => {
try {
return (await import('./overview_hyprland.js')).default();
} catch {
return Widget.Box({});
// return (await import('./overview_hyprland.js')).default();
}
};
const overviewContent = await OptionalOverview();
export const SearchAndWindows = () => {
var _appSearchResults = [];
const resultsBox = Widget.Box({
className: 'overview-search-results',
vertical: true,
});
const resultsRevealer = Widget.Revealer({
transitionDuration: userOptions.animations.durationLarge,
revealChild: false,
transition: 'slide_down',
// duration: 200,
hpack: 'center',
child: resultsBox,
});
const entryPromptRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
revealChild: true,
hpack: 'center',
child: Widget.Label({
className: 'overview-search-prompt txt-small txt',
label: getString('Type to search')
}),
});
const entryIconRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
revealChild: false,
hpack: 'end',
child: Widget.Label({
className: 'txt txt-large icon-material overview-search-icon',
label: 'search',
}),
});
const entryIcon = Widget.Box({
className: 'overview-search-prompt-box',
setup: box => box.pack_start(entryIconRevealer, true, true, 0),
});
const entry = Widget.Entry({
className: 'overview-search-box txt-small txt',
hpack: 'center',
onAccept: (self) => { // This is when you hit Enter
resultsBox.children[0].onClicked();
},
onChange: (entry) => { // this is when you type
const isAction = entry.text[0] == '>';
const isDir = (['/', '~'].includes(entry.text[0]));
resultsBox.get_children().forEach(ch => ch.destroy());
// check empty if so then dont do stuff
if (entry.text == '') {
resultsRevealer.revealChild = false;
overviewContent.revealChild = true;
entryPromptRevealer.revealChild = true;
entryIconRevealer.revealChild = false;
entry.toggleClassName('overview-search-box-extended', false);
return;
}
const text = entry.text;
resultsRevealer.revealChild = true;
overviewContent.revealChild = false;
entryPromptRevealer.revealChild = false;
entryIconRevealer.revealChild = true;
entry.toggleClassName('overview-search-box-extended', true);
_appSearchResults = Applications.query(text);
// Calculate
if (userOptions.search.enableFeatures.mathResults && couldBeMath(text)) { // Eval on typing is dangerous; this is a small workaround.
try {
const fullResult = eval(text.replace(/\^/g, "**"));
resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
} catch (e) {
// console.log(e);
}
}
if (userOptions.search.enableFeatures.directorySearch && isDir) {
var contents = [];
contents = ls({ path: text, silent: true });
contents.forEach((item) => {
resultsBox.add(DirectoryButton(item));
})
}
if (userOptions.search.enableFeatures.actions && isAction) { // Eval on typing is dangerous, this is a workaround.
resultsBox.add(CustomCommandButton({ text: entry.text }));
}
// Add application entries
let appsToAdd = MAX_RESULTS;
_appSearchResults.forEach(app => {
if (appsToAdd == 0) return;
resultsBox.add(DesktopEntryButton(app));
appsToAdd--;
});
// Fallbacks
// if the first word is an actual command
if (userOptions.search.enableFeatures.commands && !isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
}
// Add fallback: search
if (userOptions.search.enableFeatures.aiSearch)
resultsBox.add(AiButton({ text: entry.text }));
if (userOptions.search.enableFeatures.webSearch)
resultsBox.add(SearchButton({ text: entry.text }));
if (resultsBox.children.length == 0) resultsBox.add(NoResultButton());
resultsBox.show_all();
},
});
return Widget.Box({
vertical: true,
children: [
Widget.Box({
hpack: 'center',
children: [
entry,
Widget.Box({
className: 'overview-search-icon-box',
setup: (box) => {
box.pack_start(entryPromptRevealer, true, true, 0)
},
}),
entryIcon,
]
}),
overviewContent,
resultsRevealer,
],
setup: (self) => self
.hook(App, (_b, name, visible) => {
if (name == 'overview' && !visible) {
resultsBox.children = [];
entry.set_text('');
}
})
.on('key-press-event', (widget, event) => { // Typing
const keyval = event.get_keyval()[1];
const modstate = event.get_state()[1];
if (checkKeybind(event, userOptions.keybinds.overview.altMoveLeft))
entry.set_position(Math.max(entry.get_position() - 1, 0));
else if (checkKeybind(event, userOptions.keybinds.overview.altMoveRight))
entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length));
else if (checkKeybind(event, userOptions.keybinds.overview.deleteToEnd)) {
const text = entry.get_text();
const pos = entry.get_position();
const newText = text.slice(0, pos);
entry.set_text(newText);
entry.set_position(newText.length);
}
else if (!(modstate & Gdk.ModifierType.CONTROL_MASK)) { // Ctrl not held
if (keyval >= 32 && keyval <= 126 && widget != entry) {
Utils.timeout(1, () => entry.grab_focus());
entry.set_text(entry.text + String.fromCharCode(keyval));
entry.set_position(-1);
}
}
})
,
});
};
-38
View File
@@ -1,38 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
if(userOptions.appearance.fakeScreenRounding === 2) Hyprland.connect('event', (service, name, data) => {
if (name == 'fullscreen') {
const monitor = Hyprland.active.monitor.id;
if (data == '1') {
for (const window of App.windows) {
if (window.name.startsWith("corner") && window.name.endsWith(monitor)) {
App.closeWindow(window.name);
}
}
} else {
for (const window of App.windows) {
if (window.name.startsWith("corner") && window.name.endsWith(monitor)) {
App.openWindow(window.name);
}
}
}
}
})
export default (monitor = 0, where = 'bottom left', useOverlayLayer = true) => {
const positionString = where.replace(/\s/, ""); // remove space
return Widget.Window({
monitor,
name: `corner${positionString}${monitor}`,
layer: useOverlayLayer ? 'overlay' : 'top',
anchor: where.split(' '),
exclusivity: 'ignore',
visible: true,
child: RoundedCorner(positionString, { className: 'corner-black', }),
setup: enableClickthrough,
});
}
-14
View File
@@ -1,14 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import SessionScreen from "./sessionscreen.js";
import PopupWindow from '../.widgethacks/popupwindow.js';
export default (id = 0) => PopupWindow({ // On-screen keyboard
monitor: id,
name: `session${id}`,
visible: false,
keymode: 'on-demand',
layer: 'overlay',
exclusivity: 'ignore',
anchor: ['top', 'bottom', 'left', 'right'],
child: SessionScreen({ id: id }),
})
@@ -1,134 +0,0 @@
// This is for the cool memory indicator on the sidebar
// For the right pill of the bar, see system.js
const { Gdk, Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import { monitors } from '../.commondata/hyprlanddata.js';
const { exec, execAsync } = Utils;
const SessionButton = (name, icon, command, props = {}, colorid = 0) => {
const buttonDescription = Widget.Revealer({
vpack: 'end',
transitionDuration: userOptions.animations.durationSmall,
transition: 'slide_down',
revealChild: false,
child: Widget.Label({
className: 'txt-smaller session-button-desc',
label: name,
}),
});
return Widget.Button({
onClicked: command,
className: `session-button session-color-${colorid}`,
child: Widget.Overlay({
className: 'session-button-box',
child: Widget.Label({
vexpand: true,
className: 'icon-material',
label: icon,
}),
overlays: [
buttonDescription,
]
}),
onHover: (button) => {
const display = Gdk.Display.get_default();
const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
button.get_window().set_cursor(cursor);
buttonDescription.revealChild = true;
},
onHoverLost: (button) => {
const display = Gdk.Display.get_default();
const cursor = Gdk.Cursor.new_from_name(display, 'default');
button.get_window().set_cursor(cursor);
buttonDescription.revealChild = false;
},
setup: (self) => self
.on('focus-in-event', (self) => {
buttonDescription.revealChild = true;
self.toggleClassName('session-button-focused', true);
})
.on('focus-out-event', (self) => {
buttonDescription.revealChild = false;
self.toggleClassName('session-button-focused', false);
})
,
...props,
});
}
export default ({ id = 0 }) => {
// lock, logout, sleep
const lockButton = SessionButton(getString('Lock'), 'lock', () => { closeWindowOnAllMonitors('session'); execAsync(['loginctl', 'lock-session']).catch(print) }, {}, 1);
const logoutButton = SessionButton(getString('Logout'), 'logout', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'pkill Hyprland || pkill sway || pkill niri || loginctl terminate-user $USER']).catch(print) }, {}, 2);
const sleepButton = SessionButton(getString('Sleep'), 'sleep', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl suspend || loginctl suspend']).catch(print) }, {}, 3);
// hibernate, shutdown, reboot
const hibernateButton = SessionButton(getString('Hibernate'), 'downloading', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl hibernate || loginctl hibernate']).catch(print) }, {}, 4);
const shutdownButton = SessionButton(getString('Shutdown'), 'power_settings_new', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl poweroff || loginctl poweroff']).catch(print) }, {}, 5);
const rebootButton = SessionButton(getString('Reboot'), 'restart_alt', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl reboot || loginctl reboot']).catch(print) }, {}, 6);
const cancelButton = SessionButton(getString('Cancel'), 'close', () => closeWindowOnAllMonitors('session'), { className: 'session-button-cancel' }, 7);
const sessionDescription = Widget.Box({
vertical: true,
css: 'margin-bottom: 0.682rem;',
children: [
Widget.Label({
className: 'txt-title txt',
label: getString('Session'),
}),
Widget.Label({
justify: Gtk.Justification.CENTER,
className: 'txt-small txt',
label: getString('Use arrow keys to navigate.\nEnter to select, Esc to cancel.')
}),
]
});
const SessionButtonRow = (children) => Widget.Box({
hpack: 'center',
className: 'spacing-h-15',
children: children,
});
const sessionButtonRows = [
SessionButtonRow([lockButton, logoutButton, sleepButton]),
SessionButtonRow([hibernateButton, shutdownButton, rebootButton]),
SessionButtonRow([cancelButton]),
]
return Widget.Box({
className: 'session-bg',
css: `
min-width: ${monitors[id].width}px;
min-height: ${monitors[id].height}px;
`, // idk why but height = screen height doesn't fill
vertical: true,
children: [
Widget.EventBox({
onPrimaryClick: () => closeWindowOnAllMonitors('session'),
onSecondaryClick: () => closeWindowOnAllMonitors('session'),
onMiddleClick: () => closeWindowOnAllMonitors('session'),
}),
Widget.Box({
hpack: 'center',
vexpand: true,
vertical: true,
children: [
Widget.Box({
vpack: 'center',
vertical: true,
className: 'spacing-v-15',
children: [
sessionDescription,
...sessionButtonRows,
]
})
]
})
],
setup: (self) => self
.hook(App, (_b, name, visible) => {
if (visible) lockButton.grab_focus(); // Lock is the default option
})
,
});
}
@@ -1,491 +0,0 @@
const { GLib, Gtk } = imports.gi;
import GtkSource from "gi://GtkSource?version=3.0";
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Icon, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import md2pango, { replaceInlineLatexWithCodeBlocks } from '../../.miscutils/md2pango.js';
import { darkMode } from "../../.miscutils/system.js";
import { setupCursorHover } from "../../.widgetutils/cursorhover.js";
const LATEX_DIR = `${GLib.get_user_cache_dir()}/ags/media/latex`;
const USERNAME = GLib.get_user_name();
function substituteLang(str) {
const subs = [
{ from: 'javascript', to: 'js' },
{ from: 'bash', to: 'sh' },
];
for (const { from, to } of subs) {
if (from === str) return to;
}
return str;
}
const HighlightedCode = (content, lang) => {
const buffer = new GtkSource.Buffer();
const sourceView = new GtkSource.View({
buffer: buffer,
wrap_mode: Gtk.WrapMode.NONE,
insertSpacesInsteadOfTabs: true,
indentWidth: 4,
tabWidth: 4,
smartHomeEnd: true,
smartBackspace: true,
});
const langManager = GtkSource.LanguageManager.get_default();
let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language
if (displayLang) {
buffer.set_language(displayLang);
}
const schemeManager = GtkSource.StyleSchemeManager.get_default();
buffer.set_style_scheme(schemeManager.get_scheme(`custom${darkMode.value ? '' : '-light'}`));
buffer.set_text(content, -1);
return sourceView;
}
const TextBlock = (content = '') => {
const widget = Label({
attribute: {
'text': content,
'updateText': (text) => {
widget.attribute.text = text;
widget.label = md2pango(widget.attribute.text)
},
'appendText': (text) => {
widget.attribute.text += text;
widget.label = md2pango(widget.attribute.text)
},
},
hpack: 'fill',
className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
useMarkup: true,
xalign: 0,
wrap: true,
selectable: true,
label: content,
});
return widget;
}
const ThinkBlock = (content = '', revealChild = true) => {
const revealThought = Variable(revealChild);
const mainText = Label({
hpack: 'fill',
className: `txt sidebar-chat-txtblock-think sidebar-chat-txt`,
useMarkup: true,
xalign: 0,
wrap: true,
selectable: true,
label: content,
});
const mainTextRevealer = Revealer({
transition: 'slide_down',
revealChild: revealThought.value,
child: mainText,
setup: (self) => self.hook(revealThought, (self) => {
self.revealChild = revealThought.value;
})
})
const expandIcon = MaterialIcon(revealThought.value ? 'expand_less' : 'expand_more', 'norm', {
setup: (self) => self.hook(revealThought, (self) => {
self.label = revealThought.value ? 'expand_less' : 'expand_more';
})
});
const widget = Box({
attribute: {
'text': content,
'updateText': (text) => {
widget.attribute.text = text;
mainText.label = md2pango(widget.attribute.text);
},
'appendText': (text) => {
widget.attribute.text += text;
mainText.label = md2pango(widget.attribute.text);
},
'done': () => {
revealThought.value = false;
}
},
className: 'sidebar-chat-thinkblock',
vertical: true,
children: [
Button({
onClicked: (self) => {
revealThought.value = !revealThought.value;
},
child: Box({
className: 'spacing-h-10 padding-10',
children: [
Box({
homogeneous: true,
valign: 'center',
className: 'sidebar-chat-thinkblock-icon',
children: [MaterialIcon('neurology', 'large')]
}),
Label({
valign: 'center',
hexpand: true,
xalign: 0,
label: 'Chain of Thought',
className: 'txt sidebar-chat-thinkblock-txt',
}),
Box({
className: 'sidebar-chat-thinkblock-btn-arrow',
homogeneous: true,
children: [expandIcon],
}),
]
}),
setup: setupCursorHover,
}),
mainTextRevealer,
]
});
return widget;
}
Utils.execAsync(['bash', '-c', `rm -rf ${LATEX_DIR}`])
.then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`]))
.catch(print);
const LatexBlock = (content = '') => {
const latexViewArea = Box({
// vscroll: 'never',
// hscroll: 'automatic',
// homogeneous: true,
attribute: {
'render': async (self, text) => {
if (text.length == 0) return;
const styleContext = self.get_style_context();
const fontSize = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
const timeSinceEpoch = Date.now();
const fileName = `${timeSinceEpoch}.tex`;
const outFileName = `${timeSinceEpoch}-symbolic.svg`;
const outIconName = `${timeSinceEpoch}-symbolic`;
const scriptFileName = `${timeSinceEpoch}-render.sh`;
const filePath = `${LATEX_DIR}/${fileName}`;
const outFilePath = `${LATEX_DIR}/${outFileName}`;
const scriptFilePath = `${LATEX_DIR}/${scriptFileName}`;
Utils.writeFile(text, filePath).catch(print);
// Since MicroTex doesn't support file path input properly, we gotta cat it
// And escaping such a command is a fucking pain so I decided to just generate a script
// Note: MicroTex doesn't support `&=`
// You can add this line in the middle for debugging: echo "$text" > ${filePath}.tmp
const renderScript = `#!/usr/bin/env bash
text=$(cat ${filePath} | sed 's/$/ \\\\\\\\/g' | sed 's/&=/=/g')
cd /opt/MicroTeX
./LaTeX -headless -input="$text" -output=${outFilePath} -textsize=${fontSize * 1.1} -padding=0 -maxwidth=${latexViewArea.get_allocated_width() * 0.85} > /dev/null 2>&1
sed -i 's/fill="rgb(0%, 0%, 0%)"/style="fill:#000000"/g' ${outFilePath}
sed -i 's/stroke="rgb(0%, 0%, 0%)"/stroke="${darkMode.value ? '#ffffff' : '#000000'}"/g' ${outFilePath}
`;
Utils.writeFile(renderScript, scriptFilePath).catch(print);
Utils.exec(`chmod a+x ${scriptFilePath}`)
Utils.timeout(100, () => {
Utils.exec(`bash ${scriptFilePath}`);
Gtk.IconTheme.get_default().append_search_path(LATEX_DIR);
self.child?.destroy();
self.child = Gtk.Image.new_from_icon_name(outIconName, 0);
})
}
},
setup: (self) => self.attribute.render(self, content).catch(print),
});
const wholeThing = Box({
className: 'sidebar-chat-latex',
homogeneous: true,
attribute: {
'text': content,
'updateText': (text) => {
wholeThing.attribute.text = text;
latexViewArea.attribute.render(latexViewArea, wholeThing.attribute.text).catch(print);
},
'appendText': (text) => {
wholeThing.attribute.text += text;
latexViewArea.attribute.render(latexViewArea, wholeThing.attribute.text).catch(print);
},
},
children: [Scrollable({
vscroll: 'never',
hscroll: 'automatic',
child: latexViewArea
})]
})
return wholeThing;
}
const CodeBlock = (content = '', lang = 'txt') => {
if (lang == 'tex' || lang == 'latex') {
return LatexBlock(content);
}
const topBar = Box({
className: 'sidebar-chat-codeblock-topbar',
children: [
Label({
label: lang,
className: 'sidebar-chat-codeblock-topbar-txt',
}),
Box({
hexpand: true,
}),
Button({
className: 'sidebar-chat-codeblock-topbar-btn',
child: Box({
className: 'spacing-h-5',
children: [
MaterialIcon('content_copy', 'small'),
Label({
label: 'Copy',
})
]
}),
onClicked: (self) => {
const buffer = sourceView.get_buffer();
const copyContent = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), false); // TODO: fix this
execAsync([`wl-copy`, `${copyContent}`]).catch(print);
},
}),
]
})
// Source view
const sourceView = HighlightedCode(content, lang);
const codeBlock = Box({
attribute: {
'updateText': (text) => {
// Enable useful features for multi-line code
if (text.split('\n').length > 1) {
sourceView.autoIndent = true;
sourceView.highlightCurrentLine = true;
sourceView.showLineNumbers = true;
sourceView.showLineMarks = true;
}
sourceView.get_buffer().set_text(text, -1);
},
'appendText': (text) => {
codeBlock.attribute.updateText(sourceView.get_buffer().text + text);
},
},
className: 'sidebar-chat-codeblock',
vertical: true,
children: [
topBar,
Box({
className: 'sidebar-chat-codeblock-code',
homogeneous: true,
children: [Scrollable({
vscroll: 'never',
hscroll: 'automatic',
child: sourceView,
})],
})
],
setup: (self) => self.hook(darkMode, (self) => {
const schemeManager = GtkSource.StyleSchemeManager.get_default();
Utils.timeout(1000, () => { // Wait for the theme to be loaded
sourceView.buffer.set_style_scheme(schemeManager.get_scheme(`custom${darkMode.value ? '' : '-light'}`));
});
}, "changed"),
})
// const schemeIds = styleManager.get_scheme_ids();
// print("Available Style Schemes:");
// for (let i = 0; i < schemeIds.length; i++) {
// print(schemeIds[i]);
// }
return codeBlock;
}
const Divider = () => Box({
className: 'sidebar-chat-divider',
})
const MessageContent = (content) => {
const contentBox = Box({
vertical: true,
attribute: {
'lastUpdateTextLength': 0,
'inCode': false,
'fullUpdate': (self, content, useCursor = false) => {
// First text widget
if (contentBox.attribute.lastUpdateTextLength === 0
&& contentBox.get_children().length === 0
) {
contentBox.add(TextBlock())
}
const codeBlockRegex = /^\s*```([a-zA-Z0-9]+)?\n?/;
const thinkBlockStartRegex = /^\s*<think>/; // Start: <think>
const thinkBlockEndRegex = /<\/think>\s*$/; // End: </think>
const dividerRegex = /^\s*---/;
const newContent = content.slice(contentBox.attribute.lastUpdateTextLength);
// print("CONTENT:'" + content + "'")
// print("LAST UPDATE LENGTH:" + contentBox.attribute.lastUpdateTextLength)
// print("NEW CONTENT:" + newContent)
if (newContent.length == 0) return;
let lines = replaceInlineLatexWithCodeBlocks(newContent).split('\n');
// let lines = newContent.split('\n');
// Process each line except the last line (potentially incomplete)
let lastProcessed = 0;
for (let [index, line] of lines.entries()) {
if (index == lines.length - 1) break;
// Code blocks
if (codeBlockRegex.test(line)) {
const kids = self.get_children();
const lastLabel = kids[kids.length - 1];
const blockContent = lines.slice(lastProcessed, index).join('\n');
if (!contentBox.attribute.inCode) {
lastLabel.attribute.appendText(blockContent);
if (lastLabel.label === '') lastLabel.destroy();
contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1]));
}
else {
lastLabel.attribute.appendText(blockContent);
contentBox.add(TextBlock());
}
lastProcessed = index + 1;
contentBox.attribute.inCode = !contentBox.attribute.inCode;
}
// Think block
if (!contentBox.attribute.inCode && (thinkBlockStartRegex.test(line) || thinkBlockEndRegex.test(line))) {
const kids = self.get_children();
const lastLabel = kids[kids.length - 1];
const blockContent = lines.slice(lastProcessed, index).join('\n');
lastLabel.attribute.appendText(blockContent);
if (lastLabel.label === '') lastLabel.destroy();
if (thinkBlockStartRegex.test(line)) contentBox.add(ThinkBlock());
else {
lastLabel.attribute.done();
contentBox.add(TextBlock());
}
lastProcessed = index + 1;
}
// Breaks
if (!contentBox.attribute.inCode && dividerRegex.test(line)) {
const kids = self.get_children();
const lastLabel = kids[kids.length - 1];
const blockContent = lines.slice(lastProcessed, index).join('\n');
lastLabel.attribute.appendText(blockContent);
contentBox.add(Divider());
contentBox.add(TextBlock());
lastProcessed = index + 1;
}
}
if (lastProcessed < lines.length - 1) {
const kids = self.get_children();
const lastLabel = kids[kids.length - 1];
let blockContent = lines.slice(lastProcessed, lines.length - 1).join('\n') + '\n';
lastLabel.attribute.appendText(blockContent);
}
// Debug: plain text
// contentBox.add(Label({
// hpack: 'fill',
// className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
// useMarkup: false,
// xalign: 0,
// wrap: true,
// selectable: true,
// label: '------------------------------\n' + md2pango(content),
// }))
contentBox.show_all();
contentBox.attribute.lastUpdateTextLength = content.length - lines[lines.length - 1].length;
}
}
});
contentBox.attribute.fullUpdate(contentBox, content, false);
return contentBox;
}
export const ChatMessage = (message, modelName = 'Model') => {
const TextSkeleton = (extraClassName = '') => Box({
className: `sidebar-chat-message-skeletonline ${extraClassName}`,
})
const messageContentBox = MessageContent(message.content);
const messageLoadingSkeleton = Box({
vertical: true,
className: 'spacing-v-5',
children: Array.from({ length: 3 }, (_, id) => TextSkeleton(`sidebar-chat-message-skeletonline-offset${id}`)),
})
const messageArea = Stack({
homogeneous: message.role !== 'user',
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
children: {
'thinking': messageLoadingSkeleton,
'message': messageContentBox,
},
shown: message.thinking ? 'thinking' : 'message',
});
const thisMessage = Box({
className: 'sidebar-chat-message',
homogeneous: true,
children: [
Box({
vertical: true,
children: [
Label({
hpack: 'start',
xalign: 0,
className: `txt txt-bold sidebar-chat-name sidebar-chat-name-${message.role == 'user' ? 'user' : 'bot'}`,
wrap: true,
useMarkup: true,
label: (message.role === 'user' ? USERNAME : modelName),
}),
Box({
homogeneous: true,
className: 'sidebar-chat-messagearea',
children: [messageArea]
})
],
setup: (self) => self
.hook(message, (self, isThinking) => {
messageArea.shown = message.thinking ? 'thinking' : 'message';
}, 'notify::thinking')
.hook(message, (self) => { // Message update
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
}, 'notify::content')
.hook(message, (label, isDone) => { // Remove the cursor
if (!isDone && message.role !== 'user') return;
messageContentBox.attribute.fullUpdate(messageContentBox, message.content + '\n', false);
// print('----------------')
// print(message.content)
}, 'notify::done')
,
})
]
});
return thisMessage;
}
export const SystemMessage = (content, commandName, scrolledWindow) => {
const messageContentBox = MessageContent(content + '\n'); // Add newline so everything is added
const thisMessage = Box({
className: 'sidebar-chat-message',
children: [
Box({
vertical: true,
children: [
Label({
xalign: 0,
hpack: 'start',
className: 'txt txt-bold sidebar-chat-name sidebar-chat-name-system',
wrap: true,
label: `System • ${commandName}`,
}),
messageContentBox,
],
})
],
});
return thisMessage;
}
-546
View File
@@ -1,546 +0,0 @@
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { fileExists } from '../../.miscutils/files.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import BooruService from '../../../services/booru.js';
import { SystemMessage } from './ai_chatmessage.js';
import { AgsToggle } from '../../.commonwidgets/configwidgets_apps.js';
const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
const USER_CACHE_DIR = GLib.get_user_cache_dir();
// Create cache folder and clear pics from previous session
Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
function getDomainName(url) {
try {
const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/i);
if (!match) return null;
const domainParts = match[1].split('.');
if (domainParts.length > 2) {
// Return only the last two parts (e.g., "yande.re" from "files.yande.re")
return domainParts.slice(-2).join('.');
}
return match[1]; // Return as is if no subdomain
} catch (error) {
print(`Invalid URL: ${url}`);
return null;
}
}
const TagButton = (command, entry) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
// Interactions disabled for now because they aren't working
// onClicked: () => { entry.buffer.text += `${command} ` },
// setup: setupCursorHover,
label: command,
});
const CommandButton = (command, displayName = command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
setup: setupCursorHover,
label: displayName,
});
export const booruTabIcon = Box({
hpack: 'center',
homogeneous: true,
children: [
MaterialIcon('gallery_thumbnail', 'norm'),
]
});
const BooruInfo = () => {
const booruLogo = Label({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
label: 'gallery_thumbnail',
})
return Box({
vertical: true,
vexpand: true,
className: 'spacing-v-15',
children: [
booruLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Anime booru',
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: getString('Powered by yande.re and konachan'),
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: getString('An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.'),
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
export const BooruSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
child: Box({
vertical: true,
className: 'sidebar-chat-settings',
children: [
Box({
vertical: true,
hpack: 'center',
className: 'sidebar-chat-settings-toggles',
children: [
AgsToggle({
icon: 'menstrual_health',
name: getString('Lewds'),
desc: getString("Shows naughty stuff when enabled"),
option: 'sidebar.image.allowNsfw',
extraOnChange: (self, newValue) => {
BooruService.nsfw = newValue;
},
extraSetup: (self) => self.hook(BooruService, (self) => {
self.attribute.enabled.value = BooruService.nsfw;
}, 'notify::nsfw')
}),
]
})
]
})
});
const booruWelcome = Box({
vexpand: true,
homogeneous: true,
child: Box({
className: 'spacing-v-15 margin-top-15 margin-bottom-15',
vpack: 'center',
vertical: true,
children: [
BooruInfo(),
BooruSettings(),
]
})
});
const BooruPage = (taglist, serviceName = 'Booru') => {
const PageState = (icon, name) => Box({
className: 'spacing-h-5 txt margin-right-5',
children: [
Label({
className: 'sidebar-waifu-txt txt-smallie',
xalign: 0,
label: name,
}),
MaterialIcon(icon, 'norm'),
]
})
const ImageAction = ({ name, icon, action }) => Button({
className: 'sidebar-waifu-image-action txt-norm icon-material',
tooltipText: name,
label: icon,
onClicked: action,
setup: setupCursorHover,
})
const PreviewImage = (data, delay = 0) => {
const imageArea = Widget.DrawingArea({
className: 'sidebar-booru-image-drawingarea',
});
const imageBox = Box({
className: 'sidebar-booru-image',
// css: `background-image: url('${data.preview_url}');`,
attribute: {
'update': (self, data, force = false) => {
const imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${data.md5}.${data.file_ext}`;
// const widgetStyleContext = imageArea.get_style_context();
// const widgetWidth = widgetStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
// const widgetWidth = Math.min(Math.floor(booruContent.get_allocated_width() * 0.9 / userOptions.sidebar.image.columns), data["preview_width"]);
const widgetWidth = Math.floor(booruContent.get_allocated_width() * 0.9 / userOptions.sidebar.image.columns);
const widgetHeight = widgetWidth / data.aspect_ratio;
imageArea.set_size_request(widgetWidth, widgetHeight);
const showImage = () => {
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(imagePath, widgetWidth, widgetHeight, false);
imageArea.connect("draw", (widget, cr) => {
const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
// Draw a rounded rectangle
cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
cr.closePath();
cr.clip();
// Paint image as bg
Gdk.cairo_set_source_pixbuf(cr, pixbuf, (widgetWidth - widgetWidth) / 2, (widgetHeight - widgetHeight) / 2);
cr.paint();
});
self.queue_draw();
imageRevealer.revealChild = true;
}
// Show
// const downloadCommand = `wget -O '${imagePath}' '${data.preview_url}'`;
const downloadCommand = `curl -L -o '${imagePath}' '${data.preview_url}'`;
if (!force && fileExists(imagePath)) showImage();
else Utils.timeout(delay, () => Utils.execAsync(['bash', '-c', downloadCommand])
.then(showImage)
.catch(print)
);
},
},
child: imageArea,
setup: (self) => {
Utils.timeout(1000, () => self.attribute.update(self, data));
}
});
const imageActions = Revealer({
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
vpack: 'start',
className: 'sidebar-booru-image-actions spacing-h-3',
children: [
Box({ hexpand: true }),
ImageAction({
name: `${getString('Go to file url')} (${getDomainName(data.file_url)})`,
icon: 'file_open',
action: () => execAsync(['xdg-open', `https://${getDomainName(data.file_url)}/post/show/${data.id}`]).catch(print),
}),
ImageAction({
name: `${getString('Go to source')} (${getDomainName(data.source)})`,
icon: 'open_in_new',
action: () => execAsync(['xdg-open', `${data.source}`]).catch(print),
}),
ImageAction({
name: getString('Save image'),
icon: 'save',
action: (self) => {
const currentTags = BooruService.queries.at(-1).realTagList.filter(tag => !tag.includes('rating:'));
const tagDirectory = currentTags.join('+');
const fileName = decodeURIComponent((data.file_url).substring((data.file_url).lastIndexOf('/') + 1));
const saveCommand = `mkdir -p "$(xdg-user-dir PICTURES)/homework/${data.is_nsfw ? '🌶️/' : ''}" && curl -L -o "$(xdg-user-dir PICTURES)/homework/${data.is_nsfw ? '🌶️/' : ''}${fileName}" '${data.file_url}'`;
print(saveCommand)
execAsync(['bash', '-c', saveCommand])
.then(() => self.label = 'done')
.catch(print);
},
}),
ImageAction({
name: getString('Set as wallpaper'),
icon: 'wallpaper',
action: (self) => {
const currentTags = BooruService.queries.at(-1).realTagList.filter(tag => !tag.includes('rating:'));
let fileExtension = data.file_ext || 'jpg';
print(data)
const fileName = decodeURIComponent((data.file_url).substring((data.file_url).lastIndexOf('/') + 1));
const saveCommand = `mkdir -p "$(xdg-user-dir PICTURES)/Wallpapers" && curl -L -o "$(xdg-user-dir PICTURES)/Wallpapers/${fileName}" '${data.file_url}'`;
const setWallpaperCommand = `${App.configDir}/scripts/color_generation/switchwall.sh "$(xdg-user-dir PICTURES)/Wallpapers/${fileName}"`;
// const
execAsync(['bash', '-c', `${saveCommand} && ${setWallpaperCommand}`])
.then(() => self.label = 'done')
.catch(print);
},
}),
]
})
});
const imageOverlay = Overlay({
passThrough: true,
child: imageBox,
overlays: [imageActions]
});
const imageRevealer = Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: EventBox({
onHover: () => { imageActions.revealChild = true },
onHoverLost: () => { imageActions.revealChild = false },
child: imageOverlay,
})
})
return imageRevealer;
}
const downloadState = Stack({
homogeneous: false,
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'api': PageState('api', getString('Calling API')),
'download': PageState('downloading', getString('Downloading image')),
'done': PageState('done', getString('Finished!')),
'error': PageState('error', getString('Error')),
},
});
const downloadIndicator = MarginRevealer({
vpack: 'center',
transition: 'slide_left',
revealChild: true,
child: downloadState,
});
const pageHeading = Box({
vertical: true,
children: [
Box({
children: [
Label({
hpack: 'start',
className: `sidebar-booru-provider`,
label: `${serviceName}`,
truncate: 'end',
maxWidthChars: 20,
}),
Box({ hexpand: true }),
downloadIndicator,
]
}),
Box({
className: 'margin-5',
children: [
Scrollable({
hexpand: true,
vscroll: 'never',
hscroll: 'automatic',
child: Box({
hpack: 'fill',
className: 'spacing-h-5',
children: [
...taglist.map((tag) => TagButton(tag)),
Box({ hexpand: true }),
]
})
}),
]
})
]
});
const pageImages = Box({
hpack: 'start',
homogeneous: true,
className: 'sidebar-booru-imagegrid',
})
const pageTip = Revealer({
transition: 'slide_down',
transitionDuration: 0,
revealChild: false,
child: Box({
className: 'txt-subtext margin-5',
children: [
Box({
homogeneous: true,
className: 'sidebar-booru-tip-icon',
children: [MaterialIcon('lightbulb', 'larger')]
}),
Label({
label: getString("No tag in mind? Type a page number"),
className: 'txt-smallie',
wrap: true,
xalign: 0,
})
]
})
})
const pageContentRevealer = Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
revealChild: false,
child: Box({
vertical: true,
children: [
pageImages,
pageTip,
]
}),
});
const thisPage = Box({
homogeneous: true,
className: 'sidebar-chat-message',
attribute: {
'imagePath': '',
'isNsfw': false,
'showContent': () => {
Utils.timeout(IMAGE_REVEAL_DELAY,
() => pageContentRevealer.revealChild = true
);
},
'update': (data, force = false) => {
// Sort by .aspect_ratio
data = data.sort(
(a, b) => a.aspect_ratio - b.aspect_ratio
);
if (data.length == 0) {
pageTip.revealChild = true;
downloadState.shown = 'error';
thisPage.attribute.showContent();
return;
}
const imageColumns = userOptions.sidebar.image.columns;
const imageRows = data.length / imageColumns;
// Init cols
pageImages.children = Array.from(
{ length: imageColumns },
(_, i) => Box({
attribute: { height: 0 },
vertical: true,
})
);
// Greedy add O(n^2) 😭
for (let i = 0; i < data.length; i++) {
// Find column with lowest length
let minHeight = Infinity;
let minIndex = -1;
for (let j = 0; j < imageColumns; j++) {
const height = pageImages.children[j].attribute.height;
if (height < minHeight) {
minHeight = height;
minIndex = j;
}
}
// Add image to it
pageImages.children[minIndex].pack_start(PreviewImage(data[i], minIndex), false, false, 0)
pageImages.children[minIndex].attribute.height += 1 / data[i].aspect_ratio; // we want height/width
}
pageImages.show_all();
// Reveal stuff
thisPage.attribute.showContent();
downloadIndicator.attribute.hide();
},
},
children: [Box({
vertical: true,
children: [
pageHeading,
Box({
vertical: true,
children: [pageContentRevealer],
})
]
})],
});
return thisPage;
}
const booruContent = Box({
className: 'spacing-v-15',
vertical: true,
attribute: {
'map': new Map(),
},
setup: (self) => self
.hook(BooruService, (box, id) => {
if (id === undefined) return;
const newPage = BooruPage(BooruService.queries[id].taglist, BooruService.queries[id].providerName);
box.add(newPage);
box.show_all();
box.attribute.map.set(id, newPage);
}, 'newResponse')
.hook(BooruService, (box, id) => {
if (id === undefined) return;
if (!BooruService.responses[id]) return;
box.attribute.map.get(id)?.attribute.update(BooruService.responses[id]);
}, 'updateResponse')
,
});
export const BooruView = (chatEntry) => Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
booruWelcome,
booruContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Scroll to bottom with new content if chat entry not focused
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
if (!chatEntry.hasFocus) return;
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
});
export const booruCommands = Box({
className: 'spacing-h-5',
setup: (self) => {
self.pack_end(CommandButton('/clear'), false, false, 0);
self.pack_end(CommandButton('+'), false, false, 0);
self.pack_end(CommandButton('/mode konachan', 'Konachan'), false, false, 0);
self.pack_end(CommandButton('/mode yandere', 'yande.re'), false, false, 0);
}
});
const clearChat = () => { // destroy!!
booruContent.attribute.map.forEach((value, key, map) => {
value.destroy();
value = null;
});
}
export const sendMessage = (text, booruView) => {
// Commands
if (text.startsWith('+')) { // Next page
const lastQuery = BooruService.queries.at(-1);
BooruService.fetch(`${lastQuery.realTagList.join(' ')} ${lastQuery.page + 1}`)
}
else if (text.startsWith('/')) {
if (text.startsWith('/clear')) clearChat();
else if (text.startsWith('/safe')) {
BooruService.nsfw = false;
const message = SystemMessage(`Switched to safe mode`, '/safe', booruView)
booruContent.add(message);
booruContent.show_all();
booruContent.attribute.map.set(Date.now(), message);
}
else if (text.startsWith('/lewd')) {
BooruService.nsfw = true;
const message = SystemMessage(`Tiddies enabled`, '/lewd', booruView)
booruContent.add(message);
booruContent.show_all();
booruContent.attribute.map.set(Date.now(), message);
}
else if (text.startsWith('/mode')) {
const mode = text.slice(text.indexOf(' ') + 1);
BooruService.mode = mode;
const message = SystemMessage(`Changed provider to ${BooruService.providerName}`, '/mode', booruView)
booruContent.add(message);
booruContent.show_all();
booruContent.attribute.map.set(Date.now(), message);
}
else if (text.startsWith('/next')) {
sendMessage('+')
}
}
else BooruService.fetch(text);
}
@@ -1,377 +0,0 @@
const { Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
import GPTService from '../../../services/gpt.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
import { markdownTest } from '../../.miscutils/md2pango.js';
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
const AGS_CONFIG_FILE = `${App.configDir}/user_options.jsonc`;
export const chatGPTTabIcon = Icon({
hpack: 'center',
icon: `openai-symbolic`,
});
const ProviderSwitcher = () => {
const ProviderChoice = (id, provider) => {
const providerSelected = MaterialIcon('check', 'norm', {
setup: (self) => self.hook(GPTService, (self) => {
self.toggleClassName('invisible', GPTService.providerID !== id);
}, 'providerChanged')
});
return Button({
tooltipText: provider.description,
onClicked: () => {
GPTService.providerID = id;
providerList.revealChild = false;
indicatorChevron.label = 'expand_more';
// Save provider to config
Utils.execAsync(['bash', '-c', `${App.configDir}/scripts/ags/agsconfigurator.py \
--key ai.defaultGPTProvider \
--value ${id} \
--file ${AGS_CONFIG_FILE}`
]).catch(print);
},
child: Box({
className: 'spacing-h-10 txt',
children: [
Icon({
icon: provider['logo_name'],
className: 'txt-large'
}),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
label: provider.name,
}),
providerSelected
],
}),
setup: setupCursorHover,
});
}
let indicatorIcon = Icon({
icon: GPTService.providers[GPTService._currentProvider]['logo_name'],
className: 'txt-large',
setup: (self) => self.hook(GPTService, (self) => {
self.icon = GPTService.providers[GPTService.providerID]['logo_name'];
}, 'providerChanged')
});
const indicatorChevron = MaterialIcon('expand_more', 'norm');
const indicatorButton = Button({
tooltipText: getString('Select ChatGPT-compatible API provider'),
child: Box({
className: 'spacing-h-10 txt',
children: [
indicatorIcon,
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
label: GPTService.providerID,
setup: (self) => self.hook(GPTService, (self) => {
self.label = `${GPTService.providers[GPTService.providerID]['name']}`;
}, 'providerChanged')
}),
indicatorChevron,
]
}),
onClicked: () => {
providerList.revealChild = !providerList.revealChild;
indicatorChevron.label = (providerList.revealChild ? 'expand_less' : 'expand_more');
},
setup: setupCursorHover,
});
const providerList = Revealer({
revealChild: false,
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
vertical: true, className: 'spacing-v-5 sidebar-chat-providerswitcher-list',
children: [
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
Box({
className: 'spacing-v-5',
vertical: true,
setup: (self) => self.hook(GPTService, (self) => {
self.children = Object.entries(GPTService.providers)
.map(([id, provider]) => ProviderChoice(id, provider));
}, 'initialized'),
})
]
})
})
return Box({
hpack: 'center',
vertical: true,
className: 'sidebar-chat-providerswitcher',
children: [
indicatorButton,
providerList,
]
})
}
const GPTInfo = () => {
const openAiLogo = Icon({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
icon: `openai-symbolic`,
});
return Box({
vertical: true,
className: 'spacing-v-15',
children: [
openAiLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: `Assistant (GPTs)`,
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: getString('Provider shown above'),
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: getString("Chat with models compatible with OpenAI's Chat Completions API.\nNot affiliated, endorsed, or sponsored by any of the providers."),
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
const GPTSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
extraSetup: (self) => self
.hook(GPTService, (self) => Utils.timeout(200, () => {
self.attribute.hide();
}), 'newMsg')
.hook(GPTService, (self) => Utils.timeout(200, () => {
self.attribute.show();
}), 'clear')
,
child: Box({
vertical: true,
className: 'sidebar-chat-settings',
children: [
ConfigSegmentedSelection({
hpack: 'center',
icon: 'casino',
name: 'Randomness',
desc: getString('The model\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1'),
options: [
{ value: 0.00, name: getString('Precise'), },
{ value: 0.50, name: getString('Balanced'), },
{ value: 1.00, name: getString('Creative'), },
],
initIndex: 1,
onChange: (value, name) => {
GPTService.temperature = value;
},
}),
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
Box({
vertical: true,
hpack: 'center',
className: 'sidebar-chat-settings-toggles',
children: [
ConfigToggle({
icon: 'model_training',
name: getString('Prompt'),
desc: getString('Tells the model:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points'),
initValue: GPTService.assistantPrompt,
onChange: (self, newValue) => {
GPTService.assistantPrompt = newValue;
},
}),
]
})
]
})
});
export const OpenaiApiKeyInstructions = () => Box({
homogeneous: true,
children: [Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
setup: (self) => self
.hook(GPTService, (self, hasKey) => {
self.revealChild = (
GPTService.providers[GPTService.providerID]["requires_key"]
&& GPTService.key.length == 0);
}, 'hasKey')
,
child: Button({
child: Label({
useMarkup: true,
wrap: true,
className: 'txt sidebar-chat-welcome-txt',
justify: Gtk.Justification.CENTER,
label: getString('An API key is required\nYou can grab one <u>here</u>, then enter it below')
}),
setup: setupCursorHover,
onClicked: () => {
Utils.execAsync(['bash', '-c', `xdg-open ${GPTService.getKeyUrl}`]);
}
})
})]
});
const GPTWelcome = () => Box({
vexpand: true,
homogeneous: true,
child: Box({
className: 'spacing-v-15 margin-top-15 margin-bottom-15',
vpack: 'center',
vertical: true,
children: [
GPTInfo(),
OpenaiApiKeyInstructions(),
GPTSettings(),
]
})
});
export const chatContent = Box({
className: 'spacing-v-5',
vertical: true,
setup: (self) => self
.hook(GPTService, (box, id) => {
const message = GPTService.messages[id];
if (!message) return;
box.add(ChatMessage(message, `Model (${GPTService.providers[GPTService.providerID]['name']})`))
}, 'newMsg')
.hook(GPTService, (self, hasKey) => {
self.revealChild = (
GPTService.providers[GPTService.providerID]["requires_key"]
&& GPTService.key.length == 0);
}, 'providerChanged')
,
});
const clearChat = () => {
GPTService.clear();
const children = chatContent.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
}
const CommandButton = (command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
setup: setupCursorHover,
label: command,
});
export const chatGPTCommands = Box({
className: 'spacing-h-5',
children: [
Box({ hexpand: true }),
CommandButton('/key'),
CommandButton('/model'),
CommandButton('/clear'),
]
});
export const sendMessage = (text) => {
// Check if text or API key is empty
if (text.length == 0) return;
if (GPTService.providers[GPTService.providerID]["requires_key"]
&& GPTService.key.length == 0
&& !text.startsWith('/key')) {
GPTService.key = text;
chatContent.add(SystemMessage(`Key saved to \`${GPTService.keyPath}\`\nUpdate anytime with \`/key YOUR_API_KEY\`.`, 'API Key', ChatGPTView));
text = '';
return;
}
// Commands
if (text.startsWith('/')) {
if (text.startsWith('/clear')) clearChat();
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`${getString("Currently using")} \`${GPTService.modelName}\``, '/model', ChatGPTView))
else if (text.startsWith('/prompt')) {
const firstSpaceIndex = text.indexOf(' ');
const prompt = text.slice(firstSpaceIndex + 1);
if (firstSpaceIndex == -1 || prompt.length < 1) {
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', ChatGPTView))
}
else {
GPTService.addMessage('user', prompt)
}
}
else if (text.startsWith('/key')) {
const parts = text.split(' ');
if (parts.length == 1) chatContent.add(SystemMessage(
`${getString("Key stored in:")}\n\`${GPTService.keyPath}\`\n${getString("To update this key, type")} \`/key YOUR_API_KEY\``,
'/key',
ChatGPTView));
else {
GPTService.key = parts[1];
chatContent.add(SystemMessage(`${getString("Updated API Key at")}\n\`${GPTService.keyPath}\``, '/key', ChatGPTView));
}
}
else if (text.startsWith('/test'))
chatContent.add(SystemMessage(markdownTest, `Markdown test`, ChatGPTView));
else
chatContent.add(SystemMessage(getString("Invalid command."), 'Error', ChatGPTView))
}
else {
GPTService.send(text);
}
}
export const ChatGPTView = (chatEntry) => Box({
vertical: true,
children: [
ProviderSwitcher(),
Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
GPTWelcome(),
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
if (!chatEntry.hasFocus) return;
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
})
]
});
-296
View File
@@ -1,296 +0,0 @@
const { Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
import GeminiService from '../../../services/gemini.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
import { markdownTest } from '../../.miscutils/md2pango.js';
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
import { AgsToggle } from '../../.commonwidgets/configwidgets_apps.js';
const MODEL_NAME = `Gemini`;
export const geminiTabIcon = Icon({
hpack: 'center',
icon: `google-gemini-symbolic`,
})
const GeminiInfo = () => {
const geminiLogo = Icon({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
icon: `google-gemini-symbolic`,
});
return Box({
vertical: true,
className: 'spacing-v-15',
children: [
geminiLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: `Assistant (Gemini)`,
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: getString('Powered by Google'),
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: getString("Not affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\nbut will be read by human reviewers to improve the model."),
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
export const GeminiSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
extraSetup: (self) => self
.hook(GeminiService, (self) => Utils.timeout(200, () => {
self.attribute.hide();
}), 'newMsg')
.hook(GeminiService, (self) => Utils.timeout(200, () => {
self.attribute.show();
}), 'clear')
,
child: Box({
vertical: true,
className: 'sidebar-chat-settings',
children: [
ConfigSegmentedSelection({
hpack: 'center',
icon: 'casino',
name: 'Randomness',
desc: getString("Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1"),
options: [
{ value: 0.00, name: getString('Precise'), },
{ value: 0.50, name: getString('Balanced'), },
{ value: 1.00, name: getString('Creative'), },
],
initIndex: 1,
onChange: (value, name) => {
GeminiService.temperature = value;
},
}),
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
Box({
vertical: true,
hpack: 'center',
className: 'sidebar-chat-settings-toggles',
children: [
AgsToggle({
icon: 'model_training',
name: getString('Prompt'),
desc: getString("Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points"),
option: "ai.enhancements",
extraOnChange: (self, newValue) => {
GeminiService.assistantPrompt = newValue;
},
extraOnReset: (self, newValue) => {
GeminiService.assistantPrompt = userOptions.ai.enhancements;
},
}),
AgsToggle({
icon: 'shield',
name: getString('Safety'),
desc: getString("When turned off, tells the API (not the model) \nto not block harmful/explicit content"),
option: "ai.safety",
extraOnChange: (self, newValue) => {
GeminiService.safe = newValue;
},
extraOnReset: (self, newValue) => {
GeminiService.safe = userOptions.ai.safety;
},
}),
AgsToggle({
icon: 'history',
name: getString('History'),
desc: getString("Saves chat history\nMessages in previous chats won't show automatically, but they are there"),
option: "ai.useHistory",
extraOnChange: (self, newValue) => {
GeminiService.useHistory = newValue;
},
extraOnReset: (self, newValue) => {
GeminiService.useHistory = userOptions.ai.useHistory;
},
})
]
})
]
})
});
export const GoogleAiInstructions = () => Box({
homogeneous: true,
children: [Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
setup: (self) => self
.hook(GeminiService, (self, hasKey) => {
self.revealChild = (GeminiService.key.length == 0);
}, 'hasKey')
,
child: Button({
child: Label({
useMarkup: true,
wrap: true,
className: 'txt sidebar-chat-welcome-txt',
justify: Gtk.Justification.CENTER,
label: 'A Google AI API key is required\nYou can grab one <u>here</u>, then enter it below',
// setup: self => self.set_markup("This is a <a href=\"https://www.github.com\">test link</a>")
}),
setup: setupCursorHover,
onClicked: () => {
Utils.execAsync(['bash', '-c', `xdg-open https://makersuite.google.com/app/apikey &`]);
}
})
})]
});
const geminiWelcome = Box({
vexpand: true,
homogeneous: true,
child: Box({
className: 'spacing-v-15 margin-top-15 margin-bottom-15',
vpack: 'center',
vertical: true,
children: [
GeminiInfo(),
GoogleAiInstructions(),
GeminiSettings(),
]
})
});
export const chatContent = Box({
className: 'spacing-v-5',
vertical: true,
setup: (self) => self
.hook(GeminiService, (box, id) => {
const message = GeminiService.messages[id];
if (!message) return;
box.add(ChatMessage(message, MODEL_NAME))
}, 'newMsg')
,
});
const clearChat = () => {
GeminiService.clear();
const children = chatContent.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
}
const CommandButton = (command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
setup: setupCursorHover,
label: command,
});
export const geminiCommands = Box({
className: 'spacing-h-5',
children: [
Box({ hexpand: true }),
CommandButton('/key'),
CommandButton('/model'),
CommandButton('/clear'),
]
});
export const sendMessage = (text) => {
// Check if text or API key is empty
if (text.length == 0) return;
if (GeminiService.key.length == 0) {
GeminiService.key = text;
chatContent.add(SystemMessage(`Key saved to \`${GeminiService.keyPath}\`\nUpdate anytime with /key YOUR_API_KEY.`, 'API Key', GeminiView));
text = '';
return;
}
// Commands
if (text.startsWith('/')) {
if (text.startsWith('/clear')) clearChat();
else if (text.startsWith('/load')) {
clearChat();
GeminiService.loadHistory();
}
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`${getString("Currently using")} \`${GeminiService.modelName}\``, '/model', GeminiView))
else if (text.startsWith('/prompt')) {
const firstSpaceIndex = text.indexOf(' ');
const prompt = text.slice(firstSpaceIndex + 1);
if (firstSpaceIndex == -1 || prompt.length < 1) {
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', GeminiView))
}
else {
GeminiService.addMessage('user', prompt)
}
}
else if (text.startsWith('/key')) {
const parts = text.split(' ');
if (parts.length == 1) chatContent.add(SystemMessage(
`${getString("Key stored in:")} \n\`${GeminiService.keyPath}\`\n${getString("To update this key, type")} \`/key YOUR_API_KEY\``,
'/key',
GeminiView));
else {
GeminiService.key = parts[1];
chatContent.add(SystemMessage(`${getString("Updated API Key at")}\n\`${GeminiService.keyPath}\``, '/key', GeminiView));
}
}
else if (text.startsWith('/test'))
chatContent.add(SystemMessage(markdownTest, `Markdown test`, GeminiView));
else
chatContent.add(SystemMessage(getString(`Invalid command.`), 'Error', GeminiView))
}
else {
GeminiService.send(text);
}
}
export const GeminiView = (chatEntry) => Box({
homogeneous: true,
children: [Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
geminiWelcome,
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => Utils.timeout(1, () => {
if (!chatEntry.hasFocus) return;
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
}))
}
})]
});
-418
View File
@@ -1,418 +0,0 @@
// TODO: execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
// to detect img dimensions
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { fileExists } from '../../.miscutils/files.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import WaifuService from '../../../services/waifus.js';
import { darkMode } from '../../.miscutils/system.js';
async function getImageViewerApp(preferredApp) {
return Utils.execAsync(['bash', '-c', `command -v ${preferredApp}`])
.then((output) => {
if (output != '') return preferredApp;
else return 'xdg-open';
})
.catch(print);
}
const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
const IMAGE_VIEWER_APP = getImageViewerApp(userOptions.apps.imageViewer); // Gnome's image viewer cuz very comfortable zooming
const USER_CACHE_DIR = GLib.get_user_cache_dir();
// Create cache folder and clear pics from previous session
Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
const CommandButton = (command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
setup: setupCursorHover,
label: command,
});
export const waifuTabIcon = Box({
hpack: 'center',
children: [
MaterialIcon('photo', 'norm'),
]
});
const WaifuInfo = () => {
const waifuLogo = Label({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
label: 'photo',
})
return Box({
vertical: true,
vexpand: true,
className: 'spacing-v-15',
children: [
waifuLogo,
Label({
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Waifus',
}),
Box({
className: 'spacing-h-5',
hpack: 'center',
children: [
Label({
className: 'txt-smallie txt-subtext',
wrap: true,
justify: Gtk.Justification.CENTER,
label: getString('Powered by waifu.im + other APIs'),
}),
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
tooltipText: getString('Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.'),
setup: setupCursorHoverInfo,
}),
]
}),
]
});
}
const waifuWelcome = Box({
vexpand: true,
homogeneous: true,
child: Box({
className: 'spacing-v-15 margin-top-15 margin-bottom-15',
vpack: 'center',
vertical: true,
children: [
WaifuInfo(),
]
})
});
const WaifuImage = (taglist) => {
const ImageState = (icon, name) => Box({
className: 'spacing-h-5 txt',
children: [
Box({ hexpand: true }),
Label({
className: 'sidebar-waifu-txt txt-smallie',
xalign: 0,
label: name,
}),
MaterialIcon(icon, 'norm'),
]
})
const ImageAction = ({ name, icon, action }) => Button({
className: 'sidebar-waifu-image-action txt-norm icon-material',
tooltipText: name,
label: icon,
onClicked: action,
setup: setupCursorHover,
})
const downloadState = Stack({
homogeneous: false,
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'api': ImageState('api', getString('Calling API')),
'download': ImageState('downloading', getString('Downloading image')),
'done': ImageState('done', getString('Finished!')),
'error': ImageState('error', getString('Error')),
'notfound': ImageState('error', getString('Not found!')),
},
});
const downloadIndicator = MarginRevealer({
vpack: 'center',
transition: 'slide_left',
revealChild: true,
child: downloadState,
});
const blockHeading = Box({
hpack: 'fill',
className: 'spacing-h-5',
children: [
...taglist.map((tag) => CommandButton(tag)),
Box({ hexpand: true }),
downloadIndicator,
]
});
const blockImageActions = Revealer({
transition: 'crossfade',
revealChild: false,
child: Box({
vertical: true,
children: [
Box({
className: 'sidebar-waifu-image-actions spacing-h-3',
children: [
Box({ hexpand: true }),
ImageAction({
name: getString('Go to source'),
icon: 'link',
action: () => execAsync(['xdg-open', `${thisBlock.attribute.imageData.source}`]).catch(print),
}),
ImageAction({
name: getString('Hoard'),
icon: 'save',
action: (self) => {
execAsync(['bash', '-c', `mkdir -p $(xdg-user-dir PICTURES)/homework${thisBlock.attribute.isNsfw ? '/🌶️' : ''} && cp ${thisBlock.attribute.imagePath} $(xdg-user-dir PICTURES)/homework${thisBlock.attribute.isNsfw ? '/🌶️/' : ''}`])
.then(() => self.label = 'done')
.catch(print);
},
}),
ImageAction({
name: getString('Open externally'),
icon: 'open_in_new',
action: () => execAsync([IMAGE_VIEWER_APP, `${thisBlock.attribute.imagePath}`]).catch(print),
}),
]
})
],
})
})
const blockImage = Widget.DrawingArea({
className: 'sidebar-waifu-image',
});
const blockImageRevealer = Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
revealChild: false,
child: Box({
className: 'margin-top-5',
children: [Overlay({
child: Box({
homogeneous: true,
className: 'sidebar-waifu-image',
children: [blockImage],
}),
overlays: [blockImageActions],
})]
}),
});
const thisBlock = Box({
className: 'sidebar-chat-message',
attribute: {
'imagePath': '',
'isNsfw': false,
'imageData': '',
'update': (imageData, force = false) => {
thisBlock.attribute.imageData = imageData;
const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock.attribute.imageData;
thisBlock.attribute.isNsfw = is_nsfw;
if (status == 404) {
downloadState.shown = 'notfound';
return;
}
if (status != 200) {
downloadState.shown = 'error';
return;
}
thisBlock.attribute.imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${signature}${extension}`;
downloadState.shown = 'download';
// Width/height
const widgetWidth = Math.min(Math.floor(waifuContent.get_allocated_width() * 0.85), width);
const widgetHeight = Math.ceil(widgetWidth * height / width);
blockImage.set_size_request(widgetWidth, widgetHeight);
const showImage = () => {
downloadState.shown = 'done';
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(thisBlock.attribute.imagePath, widgetWidth, widgetHeight);
// const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
blockImage.set_size_request(widgetWidth, widgetHeight);
blockImage.connect("draw", (widget, cr) => {
const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
// Draw a rounded rectangle
cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
cr.closePath();
cr.clip();
// Paint image as bg
Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
cr.paint();
});
// Reveal stuff
Utils.timeout(IMAGE_REVEAL_DELAY, () => {
blockImageRevealer.revealChild = true;
})
Utils.timeout(IMAGE_REVEAL_DELAY + blockImageRevealer.transitionDuration,
() => blockImageActions.revealChild = true
);
downloadIndicator.attribute.hide();
}
// Show
if (!force && fileExists(thisBlock.attribute.imagePath)) showImage();
else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock.attribute.imagePath}' '${url}'`])
.then(showImage)
.catch(print);
thisBlock.css = `background-color: mix(${darkMode.value ? 'black' : 'white'}, ${dominant_color}, 0.97);`;
},
},
children: [
Box({
vertical: true,
children: [
blockHeading,
Box({
vertical: true,
hpack: 'start',
children: [blockImageRevealer],
})
]
})
],
});
return thisBlock;
}
const waifuContent = Box({
className: 'spacing-v-15',
vertical: true,
attribute: {
'map': new Map(),
},
setup: (self) => self
.hook(WaifuService, (box, id) => {
if (id === undefined) return;
const newImageBlock = WaifuImage(WaifuService.queries[id]);
box.add(newImageBlock);
box.show_all();
box.attribute.map.set(id, newImageBlock);
}, 'newResponse')
.hook(WaifuService, (box, id) => {
if (id === undefined) return;
const data = WaifuService.responses[id];
if (!data) return;
const imageBlock = box.attribute.map.get(id);
imageBlock?.attribute.update(data);
}, 'updateResponse')
,
});
export const WaifuView = (chatEntry) => Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
waifuWelcome,
waifuContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
if (!chatEntry.hasFocus) return;
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
});
const waifuTags = Revealer({
revealChild: false,
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
className: 'spacing-h-5',
children: [
Scrollable({
vscroll: 'never',
hscroll: 'automatic',
hexpand: true,
child: Box({
className: 'spacing-h-5',
children: [
CommandButton('waifu'),
CommandButton('maid'),
CommandButton('uniform'),
CommandButton('oppai'),
CommandButton('selfies'),
CommandButton('marin-kitagawa'),
CommandButton('raiden-shogun'),
CommandButton('mori-calliope'),
]
})
}),
Box({ className: 'separator-line' }),
]
})
});
export const waifuCommands = Box({
className: 'spacing-h-5',
setup: (self) => {
self.pack_end(CommandButton('/clear'), false, false, 0);
self.pack_start(Button({
className: 'sidebar-chat-chip-toggle',
setup: setupCursorHover,
label: getString('Tags →'),
onClicked: () => {
waifuTags.revealChild = !waifuTags.revealChild;
}
}), false, false, 0);
self.pack_start(waifuTags, true, true, 0);
}
});
const clearChat = () => { // destroy!!
waifuContent.attribute.map.forEach((value, key, map) => {
value.destroy();
value = null;
});
}
function newSimpleImageCall(name, url, width, height, dominantColor = '#9392A6') {
const timeSinceEpoch = Date.now();
const newImage = WaifuImage([`/${name}`]);
waifuContent.add(newImage);
waifuContent.attribute.map.set(timeSinceEpoch, newImage);
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage?.attribute.update({
status: 200,
url: url,
extension: '',
signature: timeSinceEpoch,
source: url,
dominant_color: dominantColor,
is_nsfw: false,
width: width,
height: height,
tags: [`/${name}`],
}, true));
}
export const sendMessage = (text) => {
// Commands
if (text.startsWith('/')) {
if (text.startsWith('/clear')) clearChat();
else if (text.startsWith('/test'))
newSimpleImageCall('test', 'https://picsum.photos/600/400', 300, 200);
else if (text.startsWith('/chino'))
newSimpleImageCall('chino', 'https://chino.pages.dev/chino', 300, 400, '#B2AEF3');
else if (text.startsWith('/place'))
newSimpleImageCall('place', 'https://placewaifu.com/image/400/600', 400, 600, '#F0A235');
}
else WaifuService.fetch(text);
}
-243
View File
@@ -1,243 +0,0 @@
const { Gtk, Gdk } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
// APIs
import GPTService from '../../services/gpt.js';
import Gemini from '../../services/gemini.js';
import { GeminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js';
import { ChatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
import { WaifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js';
import { BooruView, booruCommands, sendMessage as booruSendMessage, booruTabIcon } from './apis/booru.js';
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
import { checkKeybind } from '../.widgetutils/keybind.js';
const TextView = Widget.subclass(Gtk.TextView, "AgsTextView");
import { widgetContent } from './sideleft.js';
import { IconTabContainer } from '../.commonwidgets/tabcontainer.js';
import { updateNestedProperty } from '../.miscutils/objects.js';
const EXPAND_INPUT_THRESHOLD = 30;
const AGS_CONFIG_FILE = `${App.configDir}/user_options.jsonc`;
export const chatEntry = TextView({
hexpand: true,
wrapMode: Gtk.WrapMode.WORD_CHAR,
acceptsTab: false,
className: 'sidebar-chat-entry txt txt-smallie',
setup: (self) => self
.hook(App, (self, currentName, visible) => {
if (visible && currentName === 'sideleft') {
self.grab_focus();
}
})
.hook(GPTService, (self) => {
if (APIS[currentApiId].name != 'Assistant (GPTs)') return;
self.placeholderText = (GPTService.key.length > 0 ? getString('Message the model...') : getString('Enter API Key...'));
}, 'hasKey')
.hook(Gemini, (self) => {
if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return;
self.placeholderText = (Gemini.key.length > 0 ? getString('Message Gemini...') : getString('Enter Google AI API Key...'));
}, 'hasKey')
.on("key-press-event", (widget, event) => {
// Don't send when Shift+Enter
if (event.get_keyval()[1] === Gdk.KEY_Return || event.get_keyval()[1] === Gdk.KEY_KP_Enter) {
if (event.get_state()[1] !== 17) {// SHIFT_MASK doesn't work but 17 should be shift
apiSendMessage(widget);
return true;
}
return false;
}
// Keybinds
if (checkKeybind(event, userOptions.keybinds.sidebar.cycleTab))
widgetContent.cycleTab();
else if (checkKeybind(event, userOptions.keybinds.sidebar.nextTab))
widgetContent.nextTab();
else if (checkKeybind(event, userOptions.keybinds.sidebar.prevTab))
widgetContent.prevTab();
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.nextTab)) {
apiWidgets.attribute.nextTab();
return true;
}
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.prevTab)) {
apiWidgets.attribute.prevTab();
return true;
}
})
,
});
const APILIST = {
'gemini': {
"name": 'Assistant (Gemini Pro)',
"sendCommand": geminiSendMessage,
"contentWidget": GeminiView(chatEntry),
"commandBar": geminiCommands,
"tabIcon": geminiTabIcon,
"placeholderText": getString('Message Gemini...'),
},
'gpt': {
"name": 'Assistant (GPTs)',
"sendCommand": chatGPTSendMessage,
"contentWidget": ChatGPTView(chatEntry),
"commandBar": chatGPTCommands,
"tabIcon": chatGPTTabIcon,
"placeholderText": getString('Message the model...'),
},
'waifu': {
"name": 'Waifus',
"sendCommand": waifuSendMessage,
"contentWidget": WaifuView(chatEntry),
"commandBar": waifuCommands,
"tabIcon": waifuTabIcon,
"placeholderText": getString('Enter tags'),
},
'booru': {
"name": 'Booru',
"sendCommand": booruSendMessage,
"contentWidget": BooruView(chatEntry),
"commandBar": booruCommands,
"tabIcon": booruTabIcon,
"placeholderText": getString('Enter tags and/or page number'),
},
}
const APIS = userOptions.sidebar.pages.apis.order.map((apiName) => {
const obj = { ...APILIST[apiName] };
obj["id"] = apiName;
return obj;
});
let currentApiId = APIS.findIndex(obj => obj.id === userOptions.sidebar.pages.apis.defaultPage);
function apiSendMessage(textView) {
// Get text
const buffer = textView.get_buffer();
const [start, end] = buffer.get_bounds();
const text = buffer.get_text(start, end, true).trimStart();
if (!text || text.length == 0) return;
// Send
if (APIS[currentApiId].name == APILIST['booru'].name)
APIS[currentApiId].sendCommand(text, APILIST['booru'].contentWidget)
else
APIS[currentApiId].sendCommand(text)
// Reset
buffer.set_text("", -1);
chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
chatEntry.set_valign(Gtk.Align.CENTER);
}
chatEntry.get_buffer().connect("changed", (buffer) => {
const bufferText = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), true);
chatSendButton.toggleClassName('sidebar-chat-send-available', bufferText.length > 0);
chatPlaceholderRevealer.revealChild = (bufferText.length == 0);
if (buffer.get_line_count() > 1 || bufferText.length > EXPAND_INPUT_THRESHOLD) {
chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', true);
chatEntry.set_valign(Gtk.Align.FILL);
chatPlaceholder.set_valign(Gtk.Align.FILL);
}
else {
chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
chatEntry.set_valign(Gtk.Align.CENTER);
chatPlaceholder.set_valign(Gtk.Align.CENTER);
}
});
const chatEntryWrapper = Scrollable({
className: 'sidebar-chat-wrapper',
hscroll: 'never',
vscroll: 'always',
child: chatEntry,
});
const chatSendButton = Button({
className: 'txt-norm icon-material sidebar-chat-send',
vpack: 'end',
label: 'arrow_upward',
setup: setupCursorHover,
onClicked: (self) => {
APIS[currentApiId].sendCommand(chatEntry.get_buffer().text);
chatEntry.get_buffer().set_text("", -1);
},
});
const chatPlaceholder = Label({
className: 'txt-subtext txt-smallie margin-left-5',
hpack: 'start',
vpack: 'center',
label: APIS[currentApiId].placeholderText,
});
const chatPlaceholderRevealer = Revealer({
revealChild: true,
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
child: chatPlaceholder,
setup: enableClickthrough,
});
const textboxArea = Box({ // Entry area
className: 'sidebar-chat-textarea',
children: [
Overlay({
passThrough: true,
child: chatEntryWrapper,
overlays: [chatPlaceholderRevealer],
}),
Box({ className: 'width-10' }),
chatSendButton,
]
});
const apiCommandStack = Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationLarge,
children: APIS.reduce((acc, api) => {
acc[api.name] = api.commandBar;
return acc;
}, {}),
})
export const apiContentStack = IconTabContainer({
tabSwitcherClassName: 'sidebar-icontabswitcher',
className: 'margin-top-5',
iconWidgets: APIS.map((api) => api.tabIcon),
names: APIS.map((api) => api.name),
children: APIS.map((api) => api.contentWidget),
initIndex: currentApiId,
onChange: (self, id) => {
apiCommandStack.shown = APIS[id].name;
chatPlaceholder.label = APIS[id].placeholderText;
currentApiId = id;
const pageName = APIS[id].id;
const option = 'sidebar.pages.apis.defaultPage';
updateNestedProperty(userOptions, option, pageName);
execAsync(['bash', '-c', `${App.configDir}/scripts/ags/agsconfigurator.py \
--key ${option} \
--value ${pageName} \
--file ${AGS_CONFIG_FILE}`
]).catch(print);
}
});
function switchToTab(id) {
apiContentStack.shown.value = id;
}
const apiWidgets = Widget.Box({
attribute: {
'nextTab': () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1)),
'prevTab': () => switchToTab(Math.max(0, currentApiId - 1)),
},
vertical: true,
className: 'spacing-v-10',
homogeneous: false,
children: [
apiContentStack,
apiCommandStack,
textboxArea,
],
});
export default apiWidgets;
-18
View File
@@ -1,18 +0,0 @@
import PopupWindow from '../.widgethacks/popupwindow.js';
import SidebarLeft from "./sideleft.js";
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box } = Widget;
import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
export default () => PopupWindow({
keymode: 'on-demand',
anchor: ['left', 'top', 'bottom'],
name: 'sideleft',
layer: 'top',
child: Box({
children: [
SidebarLeft(),
clickCloseRegion({ name: 'sideleft', multimonitor: false, fillMonitor: 'horizontal' }),
]
})
});
-158
View File
@@ -1,158 +0,0 @@
const { Gdk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import toolBox from './toolbox.js';
import apiWidgets from './apiwidgets.js';
import { chatEntry } from './apiwidgets.js';
import { TabContainer } from '../.commonwidgets/tabcontainer.js';
import { checkKeybind } from '../.widgetutils/keybind.js';
import { updateNestedProperty } from '../.miscutils/objects.js';
const AGS_CONFIG_FILE = `${App.configDir}/user_options.jsonc`;
const SIDEBARTABS = {
'apis': {
name: 'apis',
content: apiWidgets,
materialIcon: 'api',
friendlyName: 'APIs',
},
'tools': {
name: 'tools',
content: toolBox,
materialIcon: 'home_repair_service',
friendlyName: 'Tools',
},
}
const CONTENTS = userOptions.sidebar.pages.order.map((tabName) => SIDEBARTABS[tabName])
// const pinButton = Button({
// attribute: {
// 'enabled': false,
// 'toggle': (self) => {
// self.attribute.enabled = !self.attribute.enabled;
// self.toggleClassName('sidebar-controlbtn-enabled', self.attribute.enabled);
// const sideleftWindow = App.getWindow('sideleft');
// const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
// sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled);
// if (self.attribute.enabled) {
// sideleftWindow.exclusivity = 'exclusive';
// }
// else {
// sideleftWindow.exclusivity = 'normal';
// }
// },
// },
// vpack: 'start',
// className: 'sidebar-controlbtn',
// child: MaterialIcon('push_pin', 'larger'),
// tooltipText: `Pin sidebar (${userOptions.keybinds.sidebar.pin})`,
// onClicked: (self) => self.attribute.toggle(self),
// setup: (self) => {
// setupCursorHover(self);
// self.hook(App, (self, currentName, visible) => {
// if (currentName === 'sideleft' && visible) self.grab_focus();
// })
// },
// })
const expandButton = Button({
attribute: {
'enabled': false,
'toggle': (self) => {
self.attribute.enabled = !self.attribute.enabled;
// We don't expand the bar, but the expand button. Funny hax but it works
// (somehow directly expanding the sidebar directly makes it unable to unexpand)
self.toggleClassName('sidebar-expandbtn-enabled', self.attribute.enabled);
self.toggleClassName('sidebar-controlbtn-enabled', self.attribute.enabled);
},
},
vpack: 'start',
className: 'sidebar-controlbtn',
child: MaterialIcon('expand_content', 'larger'),
tooltipText: `Expand sidebar (${userOptions.keybinds.sidebar.expand})`,
onClicked: (self) => self.attribute.toggle(self),
setup: setupCursorHover,
})
export const widgetContent = TabContainer({
icons: CONTENTS.map((item) => item.materialIcon),
names: CONTENTS.map((item) => item.friendlyName),
children: CONTENTS.map((item) => item.content),
className: 'sidebar-left spacing-v-10',
initIndex: CONTENTS.findIndex(obj => obj.name === userOptions.sidebar.pages.defaultPage),
onChange: (self, index) => {
const pageName = CONTENTS[index].name;
const option = 'sidebar.pages.defaultPage';
updateNestedProperty(userOptions, option, pageName);
execAsync(['bash', '-c', `${App.configDir}/scripts/ags/agsconfigurator.py \
--key ${option} \
--value ${pageName} \
--file ${AGS_CONFIG_FILE}`
]).catch(print);
},
extraTabStripWidgets: [
// pinButton,
expandButton,
]
});
export default () => {
return Box({
// vertical: true,
vexpand: true,
css: 'min-width: 2px;',
children: [
widgetContent,
],
setup: (self) => self
.on('key-press-event', (widget, event) => { // Handle keybinds
if (checkKeybind(event, userOptions.keybinds.sidebar.cycleTab))
widgetContent.cycleTab();
else if (checkKeybind(event, userOptions.keybinds.sidebar.nextTab))
widgetContent.nextTab();
else if (checkKeybind(event, userOptions.keybinds.sidebar.prevTab))
widgetContent.prevTab();
else if (checkKeybind(event, userOptions.keybinds.sidebar.expand))
expandButton.attribute.toggle(expandButton);
// if (checkKeybind(event, userOptions.keybinds.sidebar.pin))
// pinButton.attribute.toggle(pinButton);
if (widgetContent.attribute.names[widgetContent.attribute.shown.value] == 'APIs') { // If api tab is focused
// Focus entry when typing
if ((
!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
||
((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
event.get_keyval()[1] === Gdk.KEY_v)
) {
chatEntry.grab_focus();
const buffer = chatEntry.get_buffer();
buffer.set_text(buffer.text + String.fromCharCode(event.get_keyval()[1]), -1);
buffer.place_cursor(buffer.get_iter_at_offset(-1));
}
// Switch API type
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.nextTab)) {
const toSwitchTab = widgetContent.attribute.children[widgetContent.attribute.shown.value];
toSwitchTab.nextTab();
}
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.prevTab)) {
const toSwitchTab = widgetContent.attribute.children[widgetContent.attribute.shown.value];
toSwitchTab.prevTab();
}
}
})
,
});
}
-22
View File
@@ -1,22 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, Label, Scrollable } = Widget;
import QuickScripts from './tools/quickscripts.js';
import ColorPicker from './tools/colorpicker.js';
import Conversions from './tools/conversions.js';
import Name from './tools/name.js';
export default Scrollable({
hscroll: "never",
vscroll: "automatic",
child: Box({
vertical: true,
className: 'spacing-v-10',
children: [
QuickScripts(),
Conversions(),
ColorPicker(),
Box({ vexpand: true }),
Name(),
]
})
});
@@ -1,99 +0,0 @@
#!/bin/bash
# Function to get the current resolution
get_current_resolution() {
local output
output=$(hyprctl monitors -j)
local width height refreshRate
width=$(echo "$output" | jq -r '.[0].width')
height=$(echo "$output" | jq -r '.[0].height')
refreshRate=$(echo "$output" | jq -r '.[0].refreshRate')
echo "$width $height $refreshRate"
}
# Function to update the Hyprland configuration with the new resolution
update_resolution_config() {
local newWidth="$1"
local newHeight="$2"
local newRefreshRate="$3"
local currentRes
currentRes=$(get_current_resolution)
local width height refreshRate
width=${newWidth:-$(echo "$currentRes" | awk '{print $1}')}
height=${newHeight:-$(echo "$currentRes" | awk '{print $2}')}
refreshRate=${newRefreshRate:-$(echo "$currentRes" | awk '{print $3}')}
local modelineOutput
modelineOutput=$(gtf "$width" "$height" "$refreshRate")
local modeline
modeline=$(echo "$modelineOutput" | grep -oP 'Modeline "\K[^"]+')
if [ -z "$modeline" ]; then
echo "Failed to generate modeline"
exit 1
fi
# Extract the resolution and refresh rate from the modeline
local resolution
resolution=$(echo "$modeline" | grep -oP '^[0-9]+x[0-9]+')
local rate
rate=$(echo "$modeline" | grep -oP '[0-9]+.[0-9]+$')
if [ -z "$resolution" ] || [ -z "$rate" ]; then
echo "Failed to extract resolution or refresh rate from modeline"
exit 1
fi
local configPath="${HOME}/.config/hypr/hyprland/general.conf"
local newConfigContent
newConfigContent=$(sed "s/^monitor=.*$/monitor=eDP-1, $resolution@$rate, auto, 1/" "$configPath")
echo "$newConfigContent" > "$configPath"
}
# Main script
echo "Welcome to the Resolution Configurator"
echo ""
echo " +---------------------------+"
echo " | _____ |"
echo " | | | |"
echo " | | | |"
echo " | |_____| |"
echo " | |"
echo " +---------------------------+"
echo ""
echo "Current resolution and refresh rate:"
currentRes=$(get_current_resolution)
width=$(echo "$currentRes" | awk '{print $1}')
height=$(echo "$currentRes" | awk '{print $2}')
refreshRate=$(echo "$currentRes" | awk '{print $3}')
echo "Width: $width px"
echo "Height: $height px"
echo "Refresh Rate: $refreshRate Hz"
echo ""
read -p "Enter new width (or press Enter to keep current width): " newWidth
read -p "Enter new height (or press Enter to keep current height): " newHeight
read -p "Enter new refresh rate (or press Enter to keep current refresh rate): " newRefreshRate
# Validate inputs (if provided)
if [[ ! "$newWidth" =~ ^[0-9]+$ && -n "$newWidth" ]]; then
echo "Invalid width value."
exit 1
fi
if [[ ! "$newHeight" =~ ^[0-9]+$ && -n "$newHeight" ]]; then
echo "Invalid height value."
exit 1
fi
if [[ ! "$newRefreshRate" =~ ^[0-9]+$ && -n "$newRefreshRate" ]]; then
echo "Invalid refresh rate value."
exit 1
fi
update_resolution_config "$newWidth" "$newHeight" "$newRefreshRate"
echo "Resolution updated successfully."
-198
View File
@@ -1,198 +0,0 @@
// It's weird, I know
const { Gio, GLib } = imports.gi;
import Service from 'resource:///com/github/Aylur/ags/service.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { exec, execAsync } = Utils;
import { clamp } from '../../.miscutils/mathfuncs.js';
export class ColorPickerSelection extends Service {
static {
Service.register(this, {
'picked': [],
'assigned': ['int'],
'hue': [],
'sl': [],
});
}
_hue = 198;
_xAxis = 94;
_yAxis = 80;
get hue() { return this._hue; }
set hue(value) {
this._hue = clamp(value, 0, 360);
this.emit('hue');
this.emit('picked');
this.emit('changed');
}
get xAxis() { return this._xAxis; }
set xAxis(value) {
this._xAxis = clamp(value, 0, 100);
this.emit('sl');
this.emit('picked');
this.emit('changed');
}
get yAxis() { return this._yAxis; }
set yAxis(value) {
this._yAxis = clamp(value, 0, 100);
this.emit('sl');
this.emit('picked');
this.emit('changed');
}
setColorFromHex(hexString, id) {
const hsl = hexToHSL(hexString);
this._hue = hsl.hue;
this._xAxis = hsl.saturation;
// this._yAxis = hsl.lightness;
this._yAxis = (100 - hsl.saturation / 2) / 100 * hsl.lightness;
// console.log(this._hue, this._xAxis, this._yAxis)
this.emit('assigned', id);
this.emit('changed');
}
constructor() {
super();
this.emit('changed');
}
}
export function hslToRgbValues(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const to255 = x => Math.round(x * 255);
r = to255(r);
g = to255(g);
b = to255(b);
return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`;
// return `rgb(${r},${g},${b})`;
}
export function hslToHex(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const toHex = x => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
// export function hexToHSL(hex) {
// // Remove the '#' if present
// hex = hex.replace(/^#/, '');
// // Parse the hex value into RGB components
// const bigint = parseInt(hex, 16);
// const r = (bigint >> 16) & 255;
// const g = (bigint >> 8) & 255;
// const b = bigint & 255;
// // Normalize RGB values to range [0, 1]
// const normalizedR = r / 255;
// const normalizedG = g / 255;
// const normalizedB = b / 255;
// // Find the maximum and minimum values
// const max = Math.max(normalizedR, normalizedG, normalizedB);
// const min = Math.min(normalizedR, normalizedG, normalizedB);
// // Calculate the lightness
// const lightness = (max + min) / 2;
// // If the color is grayscale, set saturation to 0
// if (max === min) {
// return {
// hue: 0,
// saturation: 0,
// lightness: lightness * 100 // Convert to percentage
// };
// }
// // Calculate the saturation
// const d = max - min;
// const saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min);
// // Calculate the hue
// let hue;
// if (max === normalizedR) {
// hue = ((normalizedG - normalizedB) / d + (normalizedG < normalizedB ? 6 : 0)) * 60;
// } else if (max === normalizedG) {
// hue = ((normalizedB - normalizedR) / d + 2) * 60;
// } else {
// hue = ((normalizedR - normalizedG) / d + 4) * 60;
// }
// return {
// hue: Math.round(hue),
// saturation: Math.round(saturation * 100), // Convert to percentage
// lightness: Math.round(lightness * 100) // Convert to percentage
// };
// }
export function hexToHSL(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
var r = parseInt(result[1], 16);
var g = parseInt(result[2], 16);
var b = parseInt(result[3], 16);
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
s = s * 100;
s = Math.round(s);
l = l * 100;
l = Math.round(l);
h = Math.round(360 * h);
return {
hue: h,
saturation: s,
lightness: l
};
}
@@ -1,283 +0,0 @@
// TODO: Make selection update when entry changes
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Scrollable } = Widget;
import SidebarModule from './module.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js';
import { clamp } from '../../.miscutils/mathfuncs.js';
export default () => {
const selectedColor = new ColorPickerSelection();
function shouldUseBlackColor() {
return ((selectedColor.xAxis < 40 || (45 <= selectedColor.hue && selectedColor.hue <= 195)) &&
selectedColor.yAxis > 60);
}
const colorBlack = 'rgba(0,0,0,0.9)';
const colorWhite = 'rgba(255,255,255,0.9)';
const hueRange = Box({
homogeneous: true,
className: 'sidebar-module-colorpicker-wrapper',
children: [Box({
className: 'sidebar-module-colorpicker-hue',
css: `background: linear-gradient(to bottom, #ff6666, #ffff66, #66dd66, #66ffff, #6666ff, #ff66ff, #ff6666);`,
})],
});
const hueSlider = Box({
vpack: 'start',
className: 'sidebar-module-colorpicker-cursorwrapper',
css: `margin-top: ${13.636 * selectedColor.hue / 360}rem;`,
homogeneous: true,
children: [Box({
className: 'sidebar-module-colorpicker-hue-cursor',
})],
setup: (self) => self.hook(selectedColor, () => {
const widgetHeight = hueRange.children[0].get_allocated_height();
self.setCss(`margin-top: ${13.636 * selectedColor.hue / 360}rem;`)
}),
});
const hueSelector = Box({
children: [EventBox({
child: Overlay({
child: hueRange,
overlays: [hueSlider],
}),
attribute: {
clicked: false,
setHue: (self, event) => {
const widgetHeight = hueRange.children[0].get_allocated_height();
const [_, cursorX, cursorY] = event.get_coords();
const cursorYPercent = clamp(cursorY / widgetHeight, 0, 1);
selectedColor.hue = Math.round(cursorYPercent * 360);
}
},
setup: (self) => self
.on('motion-notify-event', (self, event) => {
if (!self.attribute.clicked) return;
self.attribute.setHue(self, event);
})
.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
self.attribute.clicked = true;
self.attribute.setHue(self, event);
})
.on('button-release-event', (self) => self.attribute.clicked = false)
,
})]
});
const saturationAndLightnessRange = Box({
homogeneous: true,
children: [Box({
className: 'sidebar-module-colorpicker-saturationandlightness',
attribute: {
update: (self) => {
// css: `background: linear-gradient(to right, #ffffff, color);`,
self.setCss(`background:
linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)),
linear-gradient(to right, #ffffff, ${hslToHex(selectedColor.hue, 100, 50)});
`);
},
},
setup: (self) => self
.hook(selectedColor, self.attribute.update, 'hue')
.hook(selectedColor, self.attribute.update, 'assigned')
,
})],
});
const saturationAndLightnessCursor = Box({
className: 'sidebar-module-colorpicker-saturationandlightness-cursorwrapper',
children: [Box({
vpack: 'start',
hpack: 'start',
homogeneous: true,
css: `
margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
`, // Why 13.636rem? see class name in stylesheet
attribute: {
update: (self) => {
const allocation = saturationAndLightnessRange.children[0].get_allocation();
self.setCss(`
margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
`); // Why 13.636rem? see class name in stylesheet
}
},
setup: (self) => self
.hook(selectedColor, self.attribute.update, 'sl')
.hook(selectedColor, self.attribute.update, 'assigned')
,
children: [Box({
className: 'sidebar-module-colorpicker-saturationandlightness-cursor',
css: `
background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
`,
attribute: {
update: (self) => {
self.setCss(`
background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
`);
}
},
setup: (self) => self
.hook(selectedColor, self.attribute.update, 'sl')
.hook(selectedColor, self.attribute.update, 'hue')
.hook(selectedColor, self.attribute.update, 'assigned')
,
})],
})]
});
const saturationAndLightnessSelector = Box({
homogeneous: true,
className: 'sidebar-module-colorpicker-saturationandlightness-wrapper',
children: [EventBox({
child: Overlay({
child: saturationAndLightnessRange,
overlays: [saturationAndLightnessCursor],
}),
attribute: {
clicked: false,
setSaturationAndLightness: (self, event) => {
const allocation = saturationAndLightnessRange.children[0].get_allocation();
const [_, cursorX, cursorY] = event.get_coords();
const cursorXPercent = clamp(cursorX / allocation.width, 0, 1);
const cursorYPercent = clamp(cursorY / allocation.height, 0, 1);
selectedColor.xAxis = Math.round(cursorXPercent * 100);
selectedColor.yAxis = Math.round(100 - cursorYPercent * 100);
}
},
setup: (self) => self
.on('motion-notify-event', (self, event) => {
if (!self.attribute.clicked) return;
self.attribute.setSaturationAndLightness(self, event);
})
.on('button-press-event', (self, event) => {
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
self.attribute.clicked = true;
self.attribute.setSaturationAndLightness(self, event);
})
.on('button-release-event', (self) => self.attribute.clicked = false)
,
})]
});
const resultColorBox = Box({
className: 'sidebar-module-colorpicker-result-box',
homogeneous: true,
css: `background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`,
children: [Label({
className: 'txt txt-small',
label: getString('Result'),
}),],
attribute: {
update: (self) => {
self.setCss(`background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`);
self.children[0].setCss(`color: ${shouldUseBlackColor() ? colorBlack : colorWhite};`)
}
},
setup: (self) => self
.hook(selectedColor, self.attribute.update, 'sl')
.hook(selectedColor, self.attribute.update, 'hue')
.hook(selectedColor, self.attribute.update, 'assigned')
,
});
const ResultBox = ({ colorSystemName, updateCallback, copyCallback }) => Box({
children: [
Box({
vertical: true,
hexpand: true,
children: [
Label({
xalign: 0,
className: 'txt-tiny',
label: colorSystemName,
}),
Overlay({
child: Entry({
widthChars: 10,
className: 'txt-small techfont',
attribute: {
id: 0,
update: updateCallback,
},
setup: (self) => self
.hook(selectedColor, self.attribute.update, 'sl')
.hook(selectedColor, self.attribute.update, 'hue')
.hook(selectedColor, self.attribute.update, 'assigned')
// .on('activate', (self) => {
// const newColor = self.text;
// if (newColor.length != 7) return;
// selectedColor.setColorFromHex(self.text, self.attribute.id);
// })
,
}),
})
]
}),
Button({
child: MaterialIcon('content_copy', 'norm'),
onClicked: (self) => {
copyCallback(self);
self.child.label = 'done';
Utils.timeout(1000, () => self.child.label = 'content_copy');
},
setup: setupCursorHover,
})
]
});
const resultHex = ResultBox({
colorSystemName: 'Hex',
updateCallback: (self, id) => {
if (id && self.attribute.id === id) return;
self.text = hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
},
copyCallback: () => Utils.execAsync(['wl-copy', `${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}`]),
})
const resultRgb = ResultBox({
colorSystemName: 'RGB',
updateCallback: (self, id) => {
if (id && self.attribute.id === id) return;
self.text = hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
},
copyCallback: () => Utils.execAsync(['wl-copy', `rgb(${hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))})`]),
})
const resultHsl = ResultBox({
colorSystemName: 'HSL',
updateCallback: (self, id) => {
if (id && self.attribute.id === id) return;
self.text = `${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%`;
},
copyCallback: () => Utils.execAsync(['wl-copy', `hsl(${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%)`]),
})
const result = Box({
className: 'sidebar-module-colorpicker-result-area spacing-v-5 txt',
hexpand: true,
vertical: true,
children: [
resultColorBox,
resultHex,
resultRgb,
resultHsl,
]
})
return SidebarModule({
icon: MaterialIcon('colorize', 'norm'),
name: getString('Color picker'),
revealChild: false,
child: Box({
className: 'spacing-h-5',
children: [
hueSelector,
saturationAndLightnessSelector,
result,
]
})
});
}
@@ -1,169 +0,0 @@
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, Button, Entry, EventBox, Icon, Label, Scrollable, Overlay } = Widget;
import SidebarModule from './module.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { truncateToPrecision } from '../../.miscutils/mathfuncs.js';
const VALUE_DEFAULT_PRECISION = 3;
const conversions = [
{
unit1: 'px',
unit2: 'rem',
unit1Default: 5,
formula1to2: '{{x}} / (parseFloat(Utils.exec(\'gsettings get org.gnome.desktop.interface font-name\').split(" ").pop().split("\'"))*4/3)',
formula2to1: '{{x}} * (parseFloat(Utils.exec(\'gsettings get org.gnome.desktop.interface font-name\').split(" ").pop().split("\'"))*4/3)',
forcePrecision: true,
},
{
unit1: 'deg',
unit2: 'rad',
unit1Default: 90,
formula1to2: '{{x}} * Math.PI / 180',
formula2to1: '{{x}} * 180 / Math.PI',
},
{
unit1: '°F',
unit2: '°C',
unit1Default: 68,
formula1to2: '({{x}} - 32) * 5 / 9',
formula2to1: '{{x}} * 9 / 5 + 32',
},
{
unit1: 'Ft',
unit2: 'Cm',
formula1to2: '{{x}} * 30.48',
formula2to1: '{{x}} / 30.48',
},
// {
// unit1: 'Mile',
// unit2: 'Km',
// formula1to2: '{{x}} * 1.60934',
// formula2to1: '{{x}} / 1.60934',
// },
// {
// unit1: 'Inch',
// unit2: 'Cm',
// formula1to2: '{{x}} * 2.54',
// formula2to1: '{{x}} / 2.54',
// },
{
unit1: 'lbs',
unit2: 'Kg',
formula1to2: '{{x}} * 0.453592',
formula2to1: '{{x}} / 0.453592',
}
]
export default () => {
const ValueBox = ({ unit, initValue = 0, updateCallback }) => {
const unitName = Label({
xalign: 0,
className: 'txt txt-smallie txt-semibold margin-top-2 margin-left-2',
label: `${unit}`,
});
const entry = Entry({
hexpand: 'true',
widthChars: 10,
className: 'txt-small techfont margin-left-2',
text: `${initValue}`,
onChange: updateCallback,
});
const copyButton = Button({
className: 'sidebar-module-csscalc-valuebox-copybtn',
child: MaterialIcon('content_copy', 'norm'),
onClicked: (self) => {
Utils.execAsync(['wl-copy', entry.text]);
self.child.label = 'done';
Utils.timeout(1000, () => self.child.label = 'content_copy');
},
setup: setupCursorHover,
});
const wholeThing = Box({
className: 'sidebar-module-csscalc-valuebox',
vertical: true,
hexpand: true,
children: [
unitName,
Box({
children: [
entry,
copyButton,
]
})
],
attribute: {
updateValue: (value) => entry.text = `${value}`,
getValue: () => entry.text,
}
});
return wholeThing;
}
// Formula format is js expression, with `{{x}}` being the input value
const BidirectionalConversion = ({
unit1, unit2, unit1Default = 1,
formula1to2, formula2to1,
forcePrecision = false, precision = VALUE_DEFAULT_PRECISION,
}) => {
let updateLock = false;
const convert = (value, formula) => {
let thisValue;
try {
thisValue = eval(value)
} catch (error) {
thisValue = parseFloat(value);
}
// print(formula.replace('{{x}}', thisValue))
// print(eval(formula.replace('{{x}}', thisValue)))
const evalResult = eval(formula.replace('{{x}}', thisValue));
const result = forcePrecision ?
evalResult.toFixed(precision) : truncateToPrecision(evalResult, precision);
// print(result)
return result;
}
const unit1Box = ValueBox({
unit: unit1,
initValue: unit1Default,
updateCallback: (self) => {
if (updateLock) return;
updateLock = true;
const newValue = convert(self.text, formula1to2);
unit2Box.attribute.updateValue(newValue || 0);
updateLock = false;
},
});
const unit2Box = ValueBox({
unit: unit2,
initValue: truncateToPrecision(eval(formula1to2.replace('\{{x}}', unit1Default)), precision),
updateCallback: (self) => {
if (updateLock) return;
updateLock = true;
const newValue = convert(self.text, formula2to1);
unit1Box.attribute.updateValue(newValue || 0);
updateLock = false;
},
});
return Box({
className: 'txt spacing-h-10',
children: [
unit1Box,
MaterialIcon('swap_horiz', 'large'),
unit2Box,
]
})
}
return SidebarModule({
icon: MaterialIcon('autorenew', 'norm'),
name: getString('Conversions'),
child: Box({
vertical: true,
className: 'spacing-v-5',
children: conversions.map(BidirectionalConversion),
})
});
}
@@ -1,57 +0,0 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
const { Box, Button, Icon, Label, Revealer } = Widget;
export default ({
icon,
name,
child,
revealChild = true,
}) => {
const headerButtonIcon = MaterialIcon(revealChild ? 'expand_less' : 'expand_more', 'norm');
const header = Button({
onClicked: () => {
content.revealChild = !content.revealChild;
headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
},
setup: setupCursorHover,
child: Box({
className: 'txt spacing-h-10',
children: [
icon,
Label({
className: 'txt-norm',
label: `${name}`,
useMarkup: true,
}),
Box({
hexpand: true,
}),
Box({
className: 'sidebar-module-btn-arrow',
homogeneous: true,
children: [headerButtonIcon],
})
]
})
});
const content = Revealer({
revealChild: revealChild,
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
className: 'margin-top-5',
homogeneous: true,
children: [child],
}),
});
return Box({
className: 'sidebar-module',
vertical: true,
children: [
header,
content,
]
});
}
@@ -1,26 +0,0 @@
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
const { execAsync, exec } = Utils;
const { Box, Button, CenterBox, EventBox, Icon, Label, Scrollable } = Widget;
export default () => Box({
className: 'txt sidebar-module techfont',
children: [
Label({
label: getString('illogical-impulse')
}),
Box({ hexpand: true }),
Button({
className: 'sidebar-module-btn-arrow',
onClicked: () => execAsync(['xdg-open', 'https://github.com/end-4/dots-hyprland']).catch(print),
child: Icon({
className: 'txt txt-norm',
icon: 'github-symbolic',
}),
setup: setupCursorHover,
})
]
})
@@ -1,103 +0,0 @@
const { Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { execAsync, exec } = Utils;
const { Box, Button, EventBox, Icon, Label, Scrollable } = Widget;
import SidebarModule from './module.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { distroID, isArchDistro, isDebianDistro, hasFlatpak } from '../../.miscutils/system.js';
const scripts = [
{
icon: 'desktop-symbolic',
name: getString('Change screen resolution'),
command: `bash ${App.configDir}/modules/sideleft/tools/changeres.sh`,
enabled: true,
},
{
icon: 'nixos-symbolic',
name: getString('Trim system generations to 5'),
command: `sudo ${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 system`,
enabled: distroID == 'nixos',
},
{
icon: 'nixos-symbolic',
name: getString('Trim home manager generations to 5'),
command: `${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 home-manager`,
enabled: distroID == 'nixos',
},
{
icon: 'ubuntu-symbolic',
name: getString('Update packages'),
command: `sudo apt update && sudo apt upgrade -y`,
enabled: isDebianDistro,
},
{
icon: 'fedora-symbolic',
name: getString('Update packages'),
command: `sudo dnf upgrade -y`,
enabled: distroID == 'fedora',
},
{
icon: 'arch-symbolic',
name: getString('Update packages'),
command: `sudo pacman -Syyu`,
enabled: isArchDistro,
},
{
icon: 'arch-symbolic',
name: getString('Remove orphan packages'),
command: `sudo pacman -R (pacman -Qdtq)`,
enabled: isArchDistro,
},
{
icon: 'flatpak-symbolic',
name: getString('Uninstall unused flatpak packages'),
command: `flatpak uninstall --unused`,
enabled: hasFlatpak,
},
];
export default () => SidebarModule({
icon: MaterialIcon('code', 'norm'),
name: getString('Quick scripts'),
child: Box({
vertical: true,
className: 'spacing-v-5',
children: scripts.map((script) => {
if (!script.enabled) return null;
const scriptStateIcon = MaterialIcon('not_started', 'norm');
return Box({
className: 'spacing-h-5 txt',
children: [
Icon({
className: 'sidebar-module-btn-icon txt-large',
icon: script.icon,
}),
Label({
className: 'txt-small',
hpack: 'start',
hexpand: true,
label: script.name,
tooltipText: script.command,
}),
Button({
className: 'sidebar-module-scripts-button',
child: scriptStateIcon,
onClicked: () => {
closeEverything();
execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${script.command}"`]).catch(print)
.then(() => {
scriptStateIcon.label = 'done';
})
},
setup: setupCursorHover,
}),
],
})
}),
})
});
-273
View File
@@ -1,273 +0,0 @@
import GLib from 'gi://GLib';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Overlay } = Widget;
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
import Todo from "../../services/todo.js";
import { TodoWidget } from "./todolist.js";
import { getCalendarLayout } from "./calendar_layout.js";
const AGS_CONFIG_FILE = `${App.configDir}/user_options.jsonc`;
let calendarJson = getCalendarLayout(undefined, true);
let monthshift = 0;
function getDateInXMonthsTime(x) {
var currentDate = new Date(); // Get the current date
var targetMonth = currentDate.getMonth() + x; // Calculate the target month
var targetYear = currentDate.getFullYear(); // Get the current year
// Adjust the year and month if necessary
targetYear += Math.floor(targetMonth / 12);
targetMonth = (targetMonth % 12 + 12) % 12;
// Create a new date object with the target year and month
var targetDate = new Date(targetYear, targetMonth, 1);
// Set the day to the last day of the month to get the desired date
// targetDate.setDate(0);
return targetDate;
}
const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:
{ day: getString('Mo'), today: 0 },
{ day: getString('Tu'), today: 0 },
{ day: getString('We'), today: 0 },
{ day: getString('Th'), today: 0 },
{ day: getString('Fr'), today: 0 },
{ day: getString('Sa'), today: 0 },
{ day: getString('Su'), today: 0 },
]
const CalendarDay = (day, today) => Widget.Button({
className: `sidebar-calendar-btn ${today == 1 ? 'sidebar-calendar-btn-today' : (today == -1 ? 'sidebar-calendar-btn-othermonth' : '')}`,
child: Widget.Overlay({
child: Box({}),
overlays: [Label({
hpack: 'center',
className: 'txt-smallie txt-semibold sidebar-calendar-btn-txt',
label: String(day),
})],
})
})
const CalendarWidget = () => {
const calendarMonthYear = Widget.Button({
className: 'txt txt-large sidebar-calendar-monthyear-btn',
onClicked: () => shiftCalendarXMonths(0),
setup: (button) => {
button.label = `${new Date().toLocaleString('default', { month: 'long' })} ${new Date().getFullYear()}`;
setupCursorHover(button);
}
});
const addCalendarChildren = (box, calendarJson) => {
const children = box.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.destroy();
}
box.children = calendarJson.map((row, i) => Widget.Box({
className: 'spacing-h-5',
children: row.map((day, i) => CalendarDay(day.day, day.today)),
}))
}
function shiftCalendarXMonths(x) {
if (x == 0) monthshift = 0;
else monthshift += x;
var newDate;
if (monthshift == 0) newDate = new Date();
else newDate = getDateInXMonthsTime(monthshift);
calendarJson = getCalendarLayout(newDate, (monthshift == 0));
calendarMonthYear.label = `${monthshift == 0 ? '' : '• '}${newDate.toLocaleString('default', { month: 'long' })} ${newDate.getFullYear()}`;
addCalendarChildren(calendarDays, calendarJson);
}
const calendarHeader = Widget.Box({
className: 'spacing-h-5 sidebar-calendar-header',
setup: (box) => {
box.pack_start(calendarMonthYear, false, false, 0);
box.pack_end(Widget.Box({
className: 'spacing-h-5',
children: [
Button({
className: 'sidebar-calendar-monthshift-btn',
onClicked: () => shiftCalendarXMonths(-1),
child: MaterialIcon('chevron_left', 'norm'),
setup: setupCursorHover,
}),
Button({
className: 'sidebar-calendar-monthshift-btn',
onClicked: () => shiftCalendarXMonths(1),
child: MaterialIcon('chevron_right', 'norm'),
setup: setupCursorHover,
})
]
}), false, false, 0);
}
})
const calendarDays = Widget.Box({
hexpand: true,
vertical: true,
className: 'spacing-v-5',
setup: (box) => {
addCalendarChildren(box, calendarJson);
}
});
return Widget.EventBox({
onScrollUp: () => shiftCalendarXMonths(-1),
onScrollDown: () => shiftCalendarXMonths(1),
child: Widget.Box({
hpack: 'center',
children: [
Widget.Box({
hexpand: true,
vertical: true,
className: 'spacing-v-5',
children: [
calendarHeader,
Widget.Box({
homogeneous: true,
className: 'spacing-h-5',
children: weekDays.map((day, i) => CalendarDay(day.day, day.today))
}),
calendarDays,
]
})
]
})
});
};
export const ModuleCalendar = () => {
const defaultShown = 'calendar';
const navrailButton = (stackItemName, icon, name) => Widget.Button({
className: 'button-minsize sidebar-navrail-btn txt-small spacing-h-5',
onClicked: (button) => {
contentStack.shown = stackItemName;
const kids = button.get_parent().get_children();
for (let i = 0; i < kids.length; i++) {
if (kids[i] != button) kids[i].toggleClassName('sidebar-navrail-btn-active', false);
else button.toggleClassName('sidebar-navrail-btn-active', true);
}
},
child: Box({
className: 'spacing-v-5',
vertical: true,
children: [
Label({
className: `txt icon-material txt-hugeass`,
label: icon,
}),
Label({
label: name,
className: 'txt txt-smallie',
}),
]
}),
setup: (button) => Utils.timeout(1, () => {
setupCursorHover(button);
button.toggleClassName('sidebar-navrail-btn-active', defaultShown === stackItemName);
})
});
const navrail = Box({
vpack: 'center',
homogeneous: true,
vertical: true,
className: 'sidebar-navrail spacing-v-10',
children: [
navrailButton('calendar', 'calendar_month', getString('Calendar')),
navrailButton('todo', 'done_outline', getString('To Do')),
]
});
const contentStack = Widget.Stack({
hexpand: true,
children: {
'calendar': CalendarWidget(),
'todo': TodoWidget(),
},
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationLarge,
setup: (stack) => Utils.timeout(1, () => {
stack.shown = defaultShown;
})
})
const CollapseButtonIcon = (collapse) => MaterialIcon(collapse ? 'expand_more' : 'expand_less', 'norm');
const CollapseButton = (collapse) => {
const collapseButtonIcon = CollapseButtonIcon(collapse);
return Button({
hpack: 'start',
vpack: 'start',
className: 'margin-top-5 margin-left-5 margin-bottom-5',
onClicked: () => {
mainStack.shown = (mainStack.shown == 'expanded') ? 'collapsed' : 'expanded';
Utils.execAsync(['bash', '-c', `${App.configDir}/scripts/ags/agsconfigurator.py \
--key "sidebar.calendar.expandByDefault" \
--value ${!userOptions.sidebar.calendar.expandByDefault} \
--file ${AGS_CONFIG_FILE}`
]).catch(print);
},
setup: setupCursorHover,
child: Box({
className: 'sidebar-calendar-btn-arrow txt',
homogeneous: true,
children: [collapseButtonIcon],
}),
tooltipText: collapse ? getString('Collapse calendar') : getString('Expand calendar'),
})
}
const date = Variable('', {
poll: [
userOptions.time.interval,
() => GLib.DateTime.new_now_local().format(userOptions.time.calendarDateFormat),
],
})
const collapsedWidget = Box({
className: 'spacing-h-5',
children: [
CollapseButton(false),
Widget.Label({
vpack: 'center',
className: 'txt txt-small sidebar-calendar-collapsed-pill',
label: date.bind(),
}),
Widget.Label({
vpack: 'center',
className: 'txt txt-small sidebar-calendar-collapsed-pill',
label: `${Todo.todo_json.length} ${getString('To do tasks')}`,
setup: (self) => self.hook(Todo, (self) => {
self.label = `${Todo.todo_json.length} ${getString('To do tasks')}`
}, 'updated')
}),
]
})
const mainStack = Widget.Stack({
className: 'sidebar-group',
homogeneous: false,
children: {
'collapsed': collapsedWidget,
'expanded': Box({
className: 'spacing-h-5',
children: [
Overlay({
child: navrail,
overlays: [CollapseButton(true)],
}),
contentStack
]
}),
},
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationLarge,
shown: userOptions.sidebar.calendar.expandByDefault ? 'expanded' : 'collapsed',
})
return mainStack;
}
@@ -1,222 +0,0 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, Button, Icon, Label, Revealer, Scrollable, Slider, Stack } = Widget;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { iconExists, substitute } from '../../.miscutils/icons.js';
const AppVolume = (stream) => Box({
className: 'sidebar-volmixer-stream spacing-h-10',
children: [
Icon({
className: 'sidebar-volmixer-stream-appicon',
vpack: 'center',
tooltipText: stream.stream.name,
setup: (self) => {
self.hook(stream, (self) => {
self.icon = substitute(
stream["icon-name"] ||
stream.stream["icon-name"] ||
stream.stream["application-id"] ||
stream.stream["name"]);
})
},
}),
Box({
hexpand: true,
vpack: 'center',
vertical: true,
className: 'spacing-v-5',
children: [
Label({
xalign: 0,
maxWidthChars: 1,
truncate: 'end',
label: stream.description,
className: 'txt-small',
setup: (self) => self.hook(stream, (self) => {
self.label = `${stream.stream.name}${stream.description}`
})
}),
Slider({
drawValue: false,
hpack: 'fill',
className: 'sidebar-volmixer-stream-slider',
value: stream.volume,
min: 0, max: 1,
onChange: ({ value }) => {
stream.volume = value;
},
setup: (self) => self.hook(stream, (self) => {
self.value = stream.volume;
self.adjustment["step-increment"] = 0.1;
})
}),
// Box({
// homogeneous: true,
// className: 'test',
// children: [AnimatedSlider({
// className: 'sidebar-volmixer-stream-slider',
// value: stream.volume,
// })],
// })
]
})
]
});
const AudioDevices = (input = false) => {
const dropdownShown = Variable(false);
const DeviceStream = (stream) => Button({
tooltipText: stream.description,
child: Box({
className: 'txt spacing-h-10',
children: [
iconExists(stream.iconName) ? Icon({
className: 'txt-norm symbolic-icon',
icon: stream.iconName,
}) : MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
truncate: 'end',
maxWidthChars: 1,
label: stream.description,
}),
],
}),
onClicked: (self) => {
if (input) Audio.microphone = stream;
else Audio.speaker = stream;
dropdownShown.value = false;
},
setup: setupCursorHover,
})
const activeDevice = Button({
onClicked: () => { dropdownShown.value = !dropdownShown.value; },
child: Box({
className: 'txt spacing-h-10',
children: [
MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
truncate: 'end',
maxWidthChars: 1,
label: `${input ? '[In]' : '[Out]'}`,
setup: (self) => self.hook(Audio, (self) => {
self.label = `${input ? '[In]' : '[Out]'} ${input ? Audio.microphone.description : Audio.speaker.description}`;
})
}),
Label({
className: `icon-material txt-norm`,
setup: (self) => self.hook(dropdownShown, (self) => {
self.label = dropdownShown.value ? 'expand_less' : 'expand_more';
})
})
],
}),
setup: setupCursorHover,
});
const deviceSelector = Revealer({
transition: 'slide_down',
revealChild: dropdownShown.bind("value"),
transitionDuration: userOptions.animations.durationSmall,
child: Box({
vertical: true,
children: [
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
Box({
vertical: true,
className: 'spacing-v-5 margin-top-5',
attribute: {
'updateStreams': (self) => {
const streams = input ? Audio.microphones : Audio.speakers;
self.children = streams.map(stream => DeviceStream(stream));
},
},
setup: (self) => self
.hook(Audio, self.attribute.updateStreams, 'stream-added')
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
,
}),
]
})
})
return Box({
hpack: 'fill',
className: 'sidebar-volmixer-deviceselector',
vertical: true,
children: [
activeDevice,
deviceSelector,
]
})
}
export default (props) => {
const emptyContent = Box({
homogeneous: true,
children: [Box({
vertical: true,
vpack: 'center',
className: 'txt spacing-v-10',
children: [
Box({
vertical: true,
className: 'spacing-v-5 txt-subtext',
children: [
MaterialIcon('brand_awareness', 'gigantic'),
Label({ label: getString('No audio source'), className: 'txt-small' }),
]
}),
]
})]
});
const appList = Scrollable({
vexpand: true,
child: Box({
attribute: {
'updateStreams': (self) => {
const streams = Audio.apps;
self.children = streams.map(stream => AppVolume(stream));
},
},
vertical: true,
className: 'spacing-v-5',
setup: (self) => self
.hook(Audio, self.attribute.updateStreams, 'stream-added')
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
,
})
})
const devices = Box({
vertical: true,
className: 'spacing-v-5',
children: [
AudioDevices(false),
AudioDevices(true),
]
})
const mainContent = Stack({
children: {
'empty': emptyContent,
'list': appList,
},
setup: (self) => self.hook(Audio, (self) => {
self.shown = (Audio.apps.length > 0 ? 'list' : 'empty')
}),
})
return Box({
...props,
className: 'spacing-v-5',
vertical: true,
children: [
mainContent,
devices,
]
});
}

Some files were not shown because too many files have changed in this diff Show More