forked from Shinonome/dots-hyprland
Compare commits
4018 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d192a25faa | |||
| 0bfade5c36 | |||
| d619ddcd82 | |||
| 3cb611c04e | |||
| f5b2b7548d | |||
| e0f2a34949 | |||
| 6eb590b1a2 | |||
| 14376cec94 | |||
| 397fb8d8c4 | |||
| aa044b4563 | |||
| a15f2a8a39 | |||
| 9b149e6fef | |||
| eb3613d3ed | |||
| 091da11da5 | |||
| a56cee16f1 | |||
| 54a1d172d7 | |||
| c58bb07a6b | |||
| 9c115f7a8f | |||
| b9e05599bc | |||
| ce80951210 | |||
| 6eaa869fac | |||
| 8f9cf67be7 | |||
| 20d1ff065b | |||
| 25fe0ab01e | |||
| c1b37bc467 | |||
| b470bf3fe8 | |||
| d4e777911e | |||
| d4d78a5e62 | |||
| c0706258b1 | |||
| 68c67aced4 | |||
| 215ac747d8 | |||
| 9eda50178b | |||
| d5f9afe7c0 | |||
| 5c66902900 | |||
| 28ba8a4f43 | |||
| 798d35a538 | |||
| 7d5ce9a793 | |||
| c53265754c | |||
| 239b532ec6 | |||
| d1daedc6d2 | |||
| 2ade168a20 | |||
| 08201f2ac0 | |||
| ad12fe6ddf | |||
| 9e1568fcdc | |||
| b7b2e6e10d | |||
| 00a4235a81 | |||
| c504cdf22b | |||
| e6d2a7d88c | |||
| b50a4a7faa | |||
| 737eb7c356 | |||
| 5ce6280d98 | |||
| f7773acab4 | |||
| e8721b4b01 | |||
| b85ed8691a | |||
| ac8d0e9a42 | |||
| 412b2222c2 | |||
| d6b27cf9dd | |||
| c53e9891cb | |||
| 1e442f1af0 | |||
| 20dde15900 | |||
| ba0e76da1b | |||
| 1c117e0880 | |||
| 7aad60eb2c | |||
| a9f87c06ca | |||
| 281b3e5627 | |||
| 388783e992 | |||
| ae7f6bd165 | |||
| 403f7aa685 | |||
| 807c761ed0 | |||
| 7dcbabcd8c | |||
| 2e161911bc | |||
| e11d084be8 | |||
| 760c7034aa | |||
| 6c041b953a | |||
| 9f4afde0c5 | |||
| 010f070eef | |||
| f6b97c4649 | |||
| 0da83ba460 | |||
| bebf66da89 | |||
| adb36f435e | |||
| c67c8840e4 | |||
| bfad75c93a | |||
| 74c10b915d | |||
| 0ae900515e | |||
| c3147dc7ff | |||
| fb3ec1fdfc | |||
| 5c69271c10 | |||
| 84cd4582dd | |||
| e7c283e91e | |||
| f992294ec2 | |||
| c652d2a7f3 | |||
| d1cd892c0f | |||
| 63495d0b28 | |||
| 36a4a19bca | |||
| c693a3f539 | |||
| c0888cbb98 | |||
| 2cfb0c2757 | |||
| b034a712a9 | |||
| aff6206930 | |||
| 0e1f6a97fc | |||
| 7834f22243 | |||
| cec16c8720 | |||
| 54e19afa81 | |||
| ffabb85693 | |||
| 242de398c3 | |||
| fe9eb0e84c | |||
| 47235ac440 | |||
| e79c9a8792 | |||
| 762629d2f1 | |||
| 7596509777 | |||
| e6570bbe75 | |||
| 72d950ed51 | |||
| 4eef9ea18e | |||
| a2c1641090 | |||
| 25de796710 | |||
| 329fa31262 | |||
| 14f8b84635 | |||
| 36e0c3fddc | |||
| d24cbff7ac | |||
| 96bf8dcbf0 | |||
| a9dcab5e41 | |||
| a57119005d | |||
| 5467ab7f06 | |||
| fdd3cf5ef8 | |||
| dbcde24003 | |||
| e03cbfec91 | |||
| eae36f4d4f | |||
| 09ff686f18 | |||
| 0040588fd2 | |||
| 94fa3f9c40 | |||
| 4b4e1fd53c | |||
| 5e6db59bfc | |||
| 5f9febd4b7 | |||
| 3e0561578c | |||
| fa3bf6f5df | |||
| a5eca98543 | |||
| a8761bc6f2 | |||
| dd26742d52 | |||
| 771cc18be5 | |||
| ada4b856f6 | |||
| d44bbcfefc | |||
| 6589158f51 | |||
| f3ab1366d8 | |||
| d42420f848 | |||
| 9f5be8b0bc | |||
| 747abbde9e | |||
| 0fb7d4dfd8 | |||
| c1c964aa89 | |||
| 099406a276 | |||
| 2d0ac6e7db | |||
| d24e227245 | |||
| fad2a5f8ae | |||
| 1e2a6d23e3 | |||
| aea6216a7f | |||
| 77ce65a53c | |||
| 799bfae184 | |||
| 0ebe70976f | |||
| f88b21e681 | |||
| 64b52f6a90 | |||
| ec3b92a938 | |||
| 59398595a5 | |||
| eef2b691e7 | |||
| 347ada664d | |||
| a06764e111 | |||
| d42c9b63be | |||
| dfe9c876d2 | |||
| 4408b5d9d3 | |||
| e356dbb6f9 | |||
| 7948d01d94 | |||
| 8447588c7f | |||
| 56ad8b80b2 | |||
| fe8d9887fa | |||
| bcb815aac5 | |||
| da99437351 | |||
| ba2da84e7b | |||
| b4a57bbbec | |||
| 687beedc4a | |||
| 2243f8e489 | |||
| 6c0c7beaa6 | |||
| 40705b4635 | |||
| 83701e3038 | |||
| 0493c08759 | |||
| 0c3be42218 | |||
| f6f07d4f65 | |||
| 3a20c0c2bb | |||
| db10d4a45b | |||
| 33f2865967 | |||
| 7eae26474f | |||
| f6f0dedcb8 | |||
| 257abdf80a | |||
| f3caad4d90 | |||
| 7a983b049f | |||
| 69c5ac5cb3 | |||
| 55a33fb7d7 | |||
| 72aa169087 | |||
| 7f0fba51a9 | |||
| 842d1323e9 | |||
| 26adce8fc0 | |||
| bdc6026cc2 | |||
| 9f882d9f3c | |||
| 15b4394865 | |||
| 67fbaf1d27 | |||
| 7206d1a19e | |||
| ae9de61b72 | |||
| 0a8fb89501 | |||
| b1e2f86360 | |||
| 74329eb294 | |||
| 01562ca544 | |||
| dde0988a45 | |||
| c117d9903a | |||
| 29d40ca90a | |||
| 737ccce538 | |||
| 96f5f82656 | |||
| 35fb3d4da8 | |||
| 17c2762b1d | |||
| 27bd759a41 | |||
| 2ed29f5c46 | |||
| 75594fb5cf | |||
| f86d42d8f2 | |||
| b6fc897b39 | |||
| 8f66afe109 | |||
| 72b8b7aecb | |||
| a9721c7cfe | |||
| 774ce228a0 | |||
| abea376e59 | |||
| bf2fec015c | |||
| 78c6e3c16f | |||
| deadf6cc4f | |||
| ae0412b513 | |||
| f974dea8e5 | |||
| ede3cf8858 | |||
| dfe6eee674 | |||
| 854016e82a | |||
| b4956cc05c | |||
| e06a3be726 | |||
| 3e0f480ce0 | |||
| 065b34ccde | |||
| 41b6c190d2 | |||
| 795f4042b0 | |||
| aff4705d6a | |||
| a7353e5033 | |||
| a43228ab28 | |||
| fc7524a30f | |||
| a28ed6023e | |||
| 973b83bc15 | |||
| 9d065f2c8e | |||
| 4caa59dd9e | |||
| 8aa9041106 | |||
| 58a122a3b4 | |||
| 6e76977976 | |||
| 15a56bdce3 | |||
| 33db15f991 | |||
| 9dcf63b54a | |||
| b61ae3abe7 | |||
| 42f14b92d0 | |||
| fc17e29ebf | |||
| b306d308b7 | |||
| 369fadd74b | |||
| fd86b282a6 | |||
| 0362b21f5a | |||
| fd2d69e407 | |||
| 7a01602c5e | |||
| 4a9d6a2665 | |||
| be1838e40d | |||
| 7cf704d450 | |||
| 4eedd030df | |||
| 74ce3378d0 | |||
| a831fa92e8 | |||
| 7e94ee60aa | |||
| 9a11a0d8f9 | |||
| a574baacc5 | |||
| bd923a0f88 | |||
| 735fb7895b | |||
| fffa2b5749 | |||
| 673e5ffe2e | |||
| 8190c3b976 | |||
| 5ac58bdb3c | |||
| 9bbfef19e2 | |||
| bda3834176 | |||
| 3653715029 | |||
| 0974a6069a | |||
| 5710fbb3d6 | |||
| fb4b5e83a9 | |||
| a052a01eee | |||
| 97d4f8e438 | |||
| a245b4681d | |||
| 8c34bcf124 | |||
| f554a0512d | |||
| 370574d8ca | |||
| c3b152b318 | |||
| 26c69e5fd3 | |||
| 1671959186 | |||
| a7f1cddd45 | |||
| b4b422bd9c | |||
| 1dcf90ac57 | |||
| 2e0dea28a5 | |||
| 9873a23794 | |||
| 8c66dc7aa7 | |||
| 9cbeded70d | |||
| 01f2ebb246 | |||
| 1f9a8ea37c | |||
| 5cbf5608be | |||
| 0c843c8d03 | |||
| c85e98d6f9 | |||
| 7013893cbd | |||
| 65eeea7ffa | |||
| 5860462082 | |||
| 33bd04200a | |||
| 4fc56f6c78 | |||
| ba4ac764aa | |||
| 65dab672cf | |||
| ea0dd2c9a4 | |||
| e52d569b9e | |||
| f610332877 | |||
| bbc84b68ef | |||
| 2554fc7d61 | |||
| dc172862b0 | |||
| 0725edd35d | |||
| 77dd839320 | |||
| 1402a19c98 | |||
| 57b78135a6 | |||
| 48fec445ad | |||
| 060b8693ef | |||
| a187d013e4 | |||
| 577fab457f | |||
| 35bb2a1ad9 | |||
| 095d637c70 | |||
| 373a9e5670 | |||
| d11ef2adef | |||
| 9a3bb5e59e | |||
| 8bf279e571 | |||
| e72d39fecb | |||
| c5e0c2d3dc | |||
| c19e625069 | |||
| aea06d42ab | |||
| 40fb416366 | |||
| ab1b720503 | |||
| b6c2fd3f18 | |||
| 3d9f51030c | |||
| 14770e5b75 | |||
| 2e7ea9d9f6 | |||
| 60c197e2f5 | |||
| 47c5ffe5f9 | |||
| 5ab4812a3b | |||
| c063bdfe86 | |||
| cea0acafff | |||
| fb1b674a30 | |||
| 8d52e83a59 | |||
| 7670c135ed | |||
| a7f30838de | |||
| 585a7b0f06 | |||
| 502dffeac7 | |||
| 52f1a7af65 | |||
| 1e7f065add | |||
| f128c48356 | |||
| 44244ed1bf | |||
| e3d89101f1 | |||
| 0435681032 | |||
| 33b4ef42f4 | |||
| 633afb54c9 | |||
| feeb0bba8c | |||
| 92e9aa2976 | |||
| 09d5817f2e | |||
| d23d39df04 | |||
| 9e9c6b70b6 | |||
| 3a14cc644b | |||
| 2132829184 | |||
| 9fbc549da4 | |||
| 4d6f58a914 | |||
| 4601605df9 | |||
| 640a147721 | |||
| 145731d8bb | |||
| c1a6034a13 | |||
| 68f0355940 | |||
| 4fbd238a90 | |||
| b3117ce578 | |||
| f8903da663 | |||
| 15ceda494e | |||
| f1a2784777 | |||
| 52ae3b187f | |||
| 622b6ec424 | |||
| 4ff3435446 | |||
| e24de7f935 | |||
| 6215f54c9f | |||
| a55ebc5f48 | |||
| f3f9d183f2 | |||
| 21e3f253d9 | |||
| 1be524ce15 | |||
| aee7240627 | |||
| 901b4cf1ff | |||
| d92a081b57 | |||
| 6bf455a042 | |||
| d3ccde299e | |||
| 18004b5959 | |||
| 1588a20b46 | |||
| 6075835d85 | |||
| 63dd6516f1 | |||
| 1b4c439c3e | |||
| 76ee7b6bb1 | |||
| c30776e811 | |||
| a217d4c5da | |||
| b236690f2a | |||
| 36051b5970 | |||
| f98e3cd3e9 | |||
| 30269dd052 | |||
| 20ec7717a4 | |||
| bbf1066ceb | |||
| 798e4bde27 | |||
| 224bdbd5cb | |||
| 2cb45c3a78 | |||
| 80db68467f | |||
| 8182dbb17b | |||
| ecc8d42265 | |||
| 6afc4b1aa7 | |||
| 1c3c255ab6 | |||
| a5c61d9ab0 | |||
| 887d54bf07 | |||
| 2d0cff9716 | |||
| 47beca14de | |||
| 9611288d79 | |||
| 2af409b1e5 | |||
| 800c75c4d2 | |||
| e0640a8782 | |||
| 1932dcbab8 | |||
| 94490f371b | |||
| 3fbf9a7056 | |||
| b75ae3817c | |||
| 26abbd29e2 | |||
| 09c09ebfcf | |||
| 36658322c4 | |||
| a9a92e0203 | |||
| eecc1201d8 | |||
| 0f24287a39 | |||
| 07446b5399 | |||
| 803500c4b0 | |||
| d8018e97fe | |||
| 6675fb8abb | |||
| cf2e944742 | |||
| e9b80dbbf4 | |||
| b7b94b4a4e | |||
| 1a9d78fb48 | |||
| 97d22378ff | |||
| 809dac681f | |||
| 46911074f9 | |||
| 528102ba4a | |||
| 755a9b15a5 | |||
| f05358ed28 | |||
| 922c85795f | |||
| 5b27dfa747 | |||
| 57b5e34f27 | |||
| 3b86fe7de7 | |||
| 360f1258f3 | |||
| 672e756fbf | |||
| 60fd1ea030 | |||
| bf06497f9e | |||
| 22970db52f | |||
| 14c930d48c | |||
| 7197f9ddfb | |||
| 58e372c590 | |||
| 8538efe743 | |||
| b268f1d61c | |||
| 711793dd66 | |||
| dc57f940d0 | |||
| da578735e6 | |||
| 31e821250f | |||
| ce6d5969e0 | |||
| d5b599da3d | |||
| c9d0248a6a | |||
| 7238b2b15c | |||
| 5a687c3565 | |||
| 171cf6059f | |||
| e818a202b8 | |||
| 199845bf59 | |||
| 575b26d572 | |||
| af1adef5f1 | |||
| c5c8ad2236 | |||
| 8cb2c7e016 | |||
| d30c8138df | |||
| 0027e8a6e3 | |||
| 652104a358 | |||
| e9d6ed874c | |||
| aaa8448011 | |||
| f02e9ba2a5 | |||
| a05b0bb559 | |||
| 60c1a92cf5 | |||
| 91f6f95460 | |||
| 2434b36098 | |||
| a849fe0883 | |||
| 8ae14725e6 | |||
| e8a05d12e9 | |||
| 76fbe3d04a | |||
| 7763a26e03 | |||
| 281943306e | |||
| 61feb958ec | |||
| 953427692f | |||
| b21699b8a0 | |||
| d13bcddb33 | |||
| b820bdc654 | |||
| d178dafac6 | |||
| d22822e734 | |||
| 5c141e0361 | |||
| e1f30bf85c | |||
| c5aaf721c8 | |||
| 13a827c0f4 | |||
| 64e726d4c5 | |||
| 392ef6b74f | |||
| db60f8775d | |||
| 5a48a22e71 | |||
| 1e2a972747 | |||
| a34ead004f | |||
| a658aced0d | |||
| 6ad8717a47 | |||
| 76ca889eec | |||
| ec7d6fd66b | |||
| cfb8b44d7a | |||
| c580b050e4 | |||
| d7ae6014ed | |||
| 29c8001785 | |||
| 896aa97701 | |||
| 8d7dd0d6ae | |||
| 41f007a771 | |||
| 4041310b4d | |||
| d5dbf7ab7f | |||
| 8842df6340 | |||
| 169b24bea5 | |||
| e499f4f8f1 | |||
| ae8be40c2d | |||
| 3eec36d20d | |||
| ed6a0204b4 | |||
| b01e0c315a | |||
| a38c725d3d | |||
| 17a3874ab1 | |||
| 34991dd32e | |||
| 28d3f6a94a | |||
| 252a1055c2 | |||
| 4ac6784844 | |||
| ea8f0fbc2d | |||
| 61f0f0dc97 | |||
| dd00908026 | |||
| 4b1e02dda6 | |||
| 0cff92d02c | |||
| 125e57c98f | |||
| 2b9e5b1a6f | |||
| f65f805fe2 | |||
| 409fc94d6e | |||
| 36ff18bfe3 | |||
| 60b2225cc6 | |||
| 70363ab886 | |||
| 663eb1896a | |||
| b73cdf0379 | |||
| c92832ff95 | |||
| 607d6056c1 | |||
| 2ef8342187 | |||
| 0e3b3eceb6 | |||
| f1fdb941e1 | |||
| f91ca59fb6 | |||
| 5f51b97b6d | |||
| c02daf5c12 | |||
| e1601b972f | |||
| 8d0d71812a | |||
| e206c4334c | |||
| 3358ebe639 | |||
| abd657ed76 | |||
| 04211411af | |||
| 536c1ab465 | |||
| 29c4a6a15f | |||
| 93bc4d935c | |||
| f0926b6ce3 | |||
| d54ad65b50 | |||
| 91a2a520b0 | |||
| 136f4a3e48 | |||
| f71ed855e5 | |||
| 39a3a0c484 | |||
| 2ead5fa4ab | |||
| d83733bd86 | |||
| 1ea3153886 | |||
| eaae89c904 | |||
| 044221be93 | |||
| e874a6a3e0 | |||
| 70c0adb8e5 | |||
| 3ae0973df5 | |||
| b966e2d539 | |||
| 6da58f5235 | |||
| bcd7fb1c1b | |||
| 80f4a0549c | |||
| 1a3cc9b4d3 | |||
| 0cc521aef5 | |||
| d9b1d0261d | |||
| fdbe39d744 | |||
| 3cb61c4267 | |||
| bb65137415 | |||
| 95c6fcab01 | |||
| fce229cdc3 | |||
| bfe97c1c05 | |||
| 8b8ac44852 | |||
| 34b5892374 | |||
| 0645200807 | |||
| 1c8339df10 | |||
| 8b1f0fc1d4 | |||
| 6c460b209c | |||
| 13968db31c | |||
| 80a7804ade | |||
| ddf1bc6a08 | |||
| fa47c6778c | |||
| 255006172f | |||
| 52d6e8a5d1 | |||
| ead056c207 | |||
| 9a0e4181a5 | |||
| ed89ad882f | |||
| 3b4c721584 | |||
| 7a2e21ac7c | |||
| 9043ae7bf6 | |||
| 758c84feaf | |||
| 1efd2dfa19 | |||
| 1193b7a802 | |||
| f62ab85a19 | |||
| 15481c646a | |||
| 8e704e4009 | |||
| e9b5e7d7d2 | |||
| 8cfb3be4cd | |||
| 85892940b5 | |||
| 7d9a405146 | |||
| 8203115a24 | |||
| 895faae39f | |||
| 71c1fbe1dd | |||
| 4055ad48fa | |||
| 05aae36e82 | |||
| 20d60a11f7 | |||
| b0c396aa21 | |||
| ed9e510c32 | |||
| d3c1ae14b8 | |||
| ffd01741d9 | |||
| 8a9f105e75 | |||
| dd07a62dc0 | |||
| c093265b05 | |||
| 1dd959fad4 | |||
| 4cfb706b6f | |||
| 90f9467871 | |||
| cce06cc2f0 | |||
| 1d40360cf2 | |||
| f08bbc0d67 | |||
| 4ce4645749 | |||
| 4cad401ea6 | |||
| 8d2c8bd38e | |||
| 61b5cf8cb6 | |||
| 9c48fd32ef | |||
| c352ebc2fc | |||
| b45f2dd235 | |||
| 4442200479 | |||
| f5fea85334 | |||
| 8892982874 | |||
| 677fa06b06 | |||
| c11814b1f8 | |||
| 3cf14671ad | |||
| d2c019f8de | |||
| 6b90e37b0f | |||
| 21f2a9c65d | |||
| fd209851d9 | |||
| 533b9c01f5 | |||
| b4038dafa9 | |||
| 04f73e67c8 | |||
| 2503c1f14b | |||
| b214993c16 | |||
| 2a1aaa9b7e | |||
| 2fd25af353 | |||
| b7ad7361d6 | |||
| 9cb5cc1416 | |||
| 0700e024d9 | |||
| d40df98aa5 | |||
| d27fbede2a | |||
| a786f0353e | |||
| 9ba8723a5d | |||
| e4b5718833 | |||
| b4920a7cb6 | |||
| 08739043f6 | |||
| 55152aee4b | |||
| 9053927480 | |||
| 616c1dfe3a | |||
| 5242373db5 | |||
| 1836a2ff1c | |||
| cecb476ed3 | |||
| e6f36114bd | |||
| 449d6fc285 | |||
| 0703429393 | |||
| 0adefcc0d3 | |||
| 996579729d | |||
| 25816662f8 | |||
| 19ba7dac48 | |||
| 07f8a72d6d | |||
| e46c7c0d3d | |||
| c78c363388 | |||
| 12ebcf2d19 | |||
| 52f67431c7 | |||
| f9755694f7 | |||
| a72d5f98af | |||
| fae2309f62 | |||
| a17a909fd9 | |||
| 3087e5da92 | |||
| 6e986fa8b0 | |||
| adfc7a15e8 | |||
| 0f11296ee1 | |||
| 9a113c24ca | |||
| 55961ae079 | |||
| 7613bba393 | |||
| 52af531c9f | |||
| 92beee6c16 | |||
| be5b03dfdc | |||
| 67c7fd09dd | |||
| f0a042246c | |||
| 9cf66f4d83 | |||
| 8943d568f5 | |||
| 4d79cc6764 | |||
| 0b3cc187cb | |||
| e2230a0a35 | |||
| 4b0cb15762 | |||
| da233086c2 | |||
| 65ef0ca666 | |||
| 6376521d14 | |||
| 7b2cfb11f6 | |||
| 6cb1b738c6 | |||
| 4878f7dbb1 | |||
| b350d87ce2 | |||
| 3507aee627 | |||
| 9466013124 | |||
| b869336738 | |||
| 4fc85a926f | |||
| 31998a6c32 | |||
| cd7bf9c1c2 | |||
| 68c159f210 | |||
| 30c845d226 | |||
| d69559e8f6 | |||
| 0c8391534a | |||
| e0584bf50e | |||
| 79762e4193 | |||
| fbfb81c83b | |||
| 35a1f7906a | |||
| a10b8b50d4 | |||
| 53399549fc | |||
| 8660d68420 | |||
| e8326b96d7 | |||
| b93213ddb6 | |||
| 9885dd7a1e | |||
| 7016b59933 | |||
| 527cb9e9b0 | |||
| 23fcd183bf | |||
| 9001cae848 | |||
| 6e5ba69430 | |||
| 5c8d824749 | |||
| 2fbfbb80ef | |||
| bca177eed2 | |||
| b052df4d72 | |||
| 255232135b | |||
| 03890d0099 | |||
| bfc07aa4e4 | |||
| 1a844e512b | |||
| 148384200c | |||
| 64c1b5be0b | |||
| 488b3c06d6 | |||
| 4cbb0f23c6 | |||
| b650120fd4 | |||
| f7cb85632e | |||
| f3bfe8a374 | |||
| dfe11810fc | |||
| aeb106ed21 | |||
| c0de7f1d37 | |||
| 625b4da3b7 | |||
| e6e86b4258 | |||
| f21e282780 | |||
| ae28d0bd6d | |||
| d8921a6608 | |||
| 0ec4b3f54a | |||
| b79fb33134 | |||
| 99171c9512 | |||
| 189530507f | |||
| 781404749e | |||
| 9fd83a2812 | |||
| e830dc93cc | |||
| 268b072a16 | |||
| fcee7ce6f9 | |||
| 9228165428 | |||
| 8f6e2bc7fb | |||
| c7bd1019e7 | |||
| 0e3cf4c908 | |||
| e8bae0e529 | |||
| 2d65af70ad | |||
| 71e0538cf5 | |||
| 28bf94904f | |||
| 986461f590 | |||
| 869f9529f6 | |||
| 83b54f5fc4 | |||
| d2692cc95e | |||
| 11dd8c9efb | |||
| ec76180f4c | |||
| 2ce060484a | |||
| 9adf310738 | |||
| a23ee83aad | |||
| 5aa384e906 | |||
| 8574dbcb02 | |||
| f33bc7663e | |||
| 1271f147ed | |||
| 5b69995945 | |||
| c71a2498d0 | |||
| 803f2ecb42 | |||
| be1ce37014 | |||
| 2a34831f87 | |||
| 5c746f34b7 | |||
| 3dfd043645 | |||
| d15960d930 | |||
| 885a9eb0b3 | |||
| 4abfcd0162 | |||
| 68a9c8729f | |||
| 4e07aa52ba | |||
| 7ff3a212ab | |||
| fdc38d69f7 | |||
| 6ee7212bdc | |||
| 839718cc2b | |||
| bbe0329df4 | |||
| 5d6d9234ac | |||
| d990d68472 | |||
| 24df9ce2e4 | |||
| 4393c05e46 | |||
| 6d221f558b | |||
| f3ad9f27f6 | |||
| d0de047db0 | |||
| 4e86ec9fe9 | |||
| 6f7b501430 | |||
| bd1c9bfb2a | |||
| 443f86d347 | |||
| 6dd0387833 | |||
| b6dfbf6c97 | |||
| 47cd7be87e | |||
| 3c25d18f88 | |||
| cc519e9f60 | |||
| aa85e2168e | |||
| bd284a5ef4 | |||
| 541c701d5a | |||
| 179815b73c | |||
| 11064d04f0 | |||
| 767d2beb6c | |||
| c19dd725b8 | |||
| 42f29d47b4 | |||
| 2ccdf3b751 | |||
| 02afa37da1 | |||
| 4ea48d60f8 | |||
| c16cff52b8 | |||
| 5bec659486 | |||
| 8404817e51 | |||
| eb8f1379f2 | |||
| a3cb292fe9 | |||
| f8ffe5e63f | |||
| a5b941360c | |||
| c8c4642c61 | |||
| 237fa85f0f | |||
| f46835c9a1 | |||
| 67b02627ae | |||
| 98ef819fee | |||
| 42147b2117 | |||
| 61a9d406f2 | |||
| 4b8f294a91 | |||
| 9e3e14ca83 | |||
| bf225d6de2 | |||
| 2e5be90237 | |||
| c369db75eb | |||
| 347cbd9803 | |||
| b5ccddf34d | |||
| f0643c9c5c | |||
| 63c3433b6f | |||
| f8af8093f7 | |||
| 45a78c383b | |||
| 499b0628ea | |||
| 18c264a85e | |||
| 323b0bc257 | |||
| 1495669a60 | |||
| b97c2d1d4d | |||
| a135b09ec7 | |||
| 666cf09d5f | |||
| 6c7ac470bd | |||
| 7049dda7de | |||
| 3e55442654 | |||
| 21c5111961 | |||
| 2362c2ab3f | |||
| bda5422ccc | |||
| 86aab5b1fd | |||
| 6f61782c8f | |||
| 6f19e1cdd6 | |||
| c64f3a0122 | |||
| 37c1d9cf61 | |||
| 5ad3508d3e | |||
| 6194ef912b | |||
| e07ddaa0b3 | |||
| 2ea3904b56 | |||
| 8d1fe864ad | |||
| 3e4674d651 | |||
| 5c92ad64f9 | |||
| b828a1dbf0 | |||
| df0c7bbbd6 | |||
| 7bfbf011d2 | |||
| 945c6a0782 | |||
| cfdbb873aa | |||
| 4cc4ad749b | |||
| 3c390d95be | |||
| 0917ee65f2 | |||
| 6889185c3e | |||
| 1844d12bc5 | |||
| 55b5d7145d | |||
| 480ef19b01 | |||
| 31c9fe1f5a | |||
| 5696a4348e | |||
| 9997fdce0c | |||
| 318d80de38 | |||
| ca261d931d | |||
| 56c07a514f | |||
| 9162c24ccd | |||
| 155a11734f | |||
| 7c590bd5d8 | |||
| a250675b5c | |||
| 3f8c62a81b | |||
| f2cd533ae9 | |||
| 20cae142d7 | |||
| 20e1f0e0bb | |||
| a412688af2 | |||
| efcb826f5c | |||
| 1ad99b43a0 | |||
| 2dfbb84b23 | |||
| de7d504160 | |||
| 8225a3981a | |||
| c91191f89b | |||
| ac55a66dd4 | |||
| febe3d53e3 | |||
| 60ef4a873d | |||
| 462ddefb7f | |||
| dc958436d8 | |||
| e00e703af2 | |||
| 6fedb70f69 | |||
| dec65aea17 | |||
| cbcb8cf8e1 | |||
| fdcb95b8a4 | |||
| 378af7ac2a | |||
| 694eaccfbf | |||
| 42919c59ec | |||
| 58980959aa | |||
| a065829eee | |||
| f1479626f3 | |||
| f98c422254 | |||
| 1cfeff8b10 | |||
| 3cc68b29da | |||
| 3b212c454d | |||
| b5ddc36d5e | |||
| 38586f0efc | |||
| 0462ee5e56 | |||
| 711fb48e37 | |||
| 6d4ea704d6 | |||
| 39ed0e472a | |||
| a54a8d8daf | |||
| 80d62c438f | |||
| 61b89de945 | |||
| c812e9c8af | |||
| d001bc1269 | |||
| 98d2c4f881 | |||
| 4396079c28 | |||
| e10ad1ed71 | |||
| 051accbe2f | |||
| 74941d15bf | |||
| cf1ea9e3d9 | |||
| 3dd1264a12 | |||
| dbb8d015e8 | |||
| 13892a01e1 | |||
| fb7dbaa187 | |||
| 2c4668bcb0 | |||
| d0e2c69de3 | |||
| 3a64eae028 | |||
| 284bb084c6 | |||
| 14c115f80a | |||
| 8b5a790d54 | |||
| e6a19a6afb | |||
| 781af1d420 | |||
| baa3c2a773 | |||
| 8f4190a939 | |||
| 696ff4298f | |||
| f2462eb1b4 | |||
| 60eed56ea7 | |||
| 11fa846ae1 | |||
| 3502e46d19 | |||
| 53768a6885 | |||
| eccfec4671 | |||
| 819735169b | |||
| ee94828d97 | |||
| 935a8ee411 | |||
| 8a5852f61a | |||
| 9c1c97a7ec | |||
| 77e6264568 | |||
| 5db16e6245 | |||
| 631d496001 | |||
| dda0a228cf | |||
| 5820106e31 | |||
| f678a55e6a | |||
| cd452c256e | |||
| c035427e6a | |||
| b322af7051 | |||
| 7236d2f50b | |||
| 87f5bc5870 | |||
| 3187239175 | |||
| 09fd61c71d | |||
| c1f84c77dd | |||
| 3f242fa298 | |||
| c7ac7b5b43 | |||
| 688a36af58 | |||
| 10e1509d5d | |||
| fc2e51ebbc | |||
| 1433fb5412 | |||
| b383635fa8 | |||
| 788c01c242 | |||
| d8bcf2ed59 | |||
| b965b50009 | |||
| eafe9f7217 | |||
| 96e4ca1095 | |||
| fa6a4e8543 | |||
| a2ae9497df | |||
| 225e03b0a3 | |||
| 4c5bc19748 | |||
| 9209ca8216 | |||
| 241f33fb2f | |||
| f9bc4b1608 | |||
| 0091fce2c1 | |||
| 424510065d | |||
| cf43479530 | |||
| c7c38bf0b1 | |||
| 8b5a783c1f | |||
| d74e385f84 | |||
| 3f1b5bba95 | |||
| 9cf2aa83a1 | |||
| 6fa417a4c1 | |||
| 6b0572975f | |||
| d292a85a5e | |||
| 3b1d8fd262 | |||
| 549a43ac7f | |||
| 769ed3bf71 | |||
| d10ac9cc74 | |||
| 4bf31544e7 | |||
| 03a149c10e | |||
| 5ec8cca5d5 | |||
| 44c8de82c7 | |||
| b08a545ece | |||
| 5504bd3f09 | |||
| fb1b5db279 | |||
| 42b9c7c854 | |||
| 994985ecae | |||
| 229c9d5e78 | |||
| ae52e28afb | |||
| 758d40fc8b | |||
| 601cb3ffbe | |||
| 9aa869af77 | |||
| 1d07260fd0 | |||
| fe06c1891f | |||
| 26f2a9f3fd | |||
| 1cc04e118f | |||
| 87181585aa | |||
| a831d393c1 | |||
| 1845e59090 | |||
| 3fe8377309 | |||
| 20b3d2498e | |||
| 917fae6d4f | |||
| a91fe7db30 | |||
| daa4dd7b0f | |||
| cd952729f4 | |||
| 843025bc64 | |||
| 40368432e8 | |||
| 2c88a71eed | |||
| 082f12084d | |||
| 23b471edc2 | |||
| e50ea627e8 | |||
| fd8f569477 | |||
| 1766375348 | |||
| 1a58f1258a | |||
| 0f867df271 | |||
| a27a6deddf | |||
| e5e85db75d | |||
| 96cda9e6dd | |||
| fe3c502459 | |||
| e2799414dd | |||
| fa08f972d6 | |||
| b06a7ce58e | |||
| a0c5940a94 | |||
| 3365719a49 | |||
| 6afe810d69 | |||
| bf70be7f4a | |||
| 1a4b4b8bef | |||
| 7f49daf422 | |||
| f4c32f89f2 | |||
| 2807bed255 | |||
| e5e598853f | |||
| 47aa8232f7 | |||
| 06c51553ba | |||
| 281646ef0c | |||
| 2f1c66570f | |||
| 60144ca3de | |||
| a0131e5bf8 | |||
| 4afaa7cc7e | |||
| ad9f25c346 | |||
| 6be3fe0c65 | |||
| 3bebabd95e | |||
| 6e9f2c14ce | |||
| 4f68e9e61a | |||
| 56a7e8cbdd | |||
| 25a63b593d | |||
| 32af8bf257 | |||
| cd0d49032a | |||
| 11f7adc643 | |||
| 6d6fa42857 | |||
| d14d170016 | |||
| bd04346f16 | |||
| 46bd7c785a | |||
| 520068e523 | |||
| 902c8327d3 | |||
| d497e00474 | |||
| b5e1bcf3be | |||
| 923841cb56 | |||
| 6ca5175bb0 | |||
| 9aa6d03f87 | |||
| eff52332b5 | |||
| bf376c8aaa | |||
| 64f6081b14 | |||
| b3f81f350c | |||
| 025a819b63 | |||
| 2667751a1c | |||
| b1007f2ded | |||
| aca19d6903 | |||
| 52dced17a3 | |||
| a1606c9c23 | |||
| 9249034fa6 | |||
| 1924111d6b | |||
| 8214e2d052 | |||
| 564fd54cdb | |||
| a92bd67957 | |||
| 22469dc5f2 | |||
| 5f4f8980f0 | |||
| e24630b9be | |||
| 51076dda88 | |||
| 37244dc0f7 | |||
| c82a2e835b | |||
| 8a35609b0d | |||
| 1f208125bf | |||
| ba6fba447a | |||
| c19766c887 | |||
| 0e7422c335 | |||
| 67695c8edb | |||
| ede86dc7a7 | |||
| a28945f3ec | |||
| 80275c5adf | |||
| 31f2184dc6 | |||
| 9d830767c7 | |||
| 9471223a76 | |||
| c2942afe8e | |||
| d8adcce5fb | |||
| b9a7fa4d0d | |||
| 7f8265c9e6 | |||
| c1ff57c3d0 | |||
| 4cad276963 | |||
| 5a4b4e6d3c | |||
| 7440f78069 | |||
| 38cd1a7169 | |||
| 4443b736d8 | |||
| d509a8777b | |||
| 27754e5d03 | |||
| fbb284ae00 | |||
| 9bbabc3079 | |||
| 5a3abbe45a | |||
| e5283cac5c | |||
| eb55f2533d | |||
| 352e459d82 | |||
| 90a4e2128b | |||
| c79a3e6309 | |||
| a6f1560bce | |||
| a60f917f59 | |||
| 0f93784b8a | |||
| 5af7420129 | |||
| 17d2d32bf1 | |||
| 7534509caa | |||
| 76db90af1b | |||
| b4b15a2808 | |||
| f2055d128e | |||
| 534ef6fd7c | |||
| 4cb2c6589d | |||
| f23e9e5da9 | |||
| 22fb48cd0a | |||
| 3ae87c8a67 | |||
| 5d773090eb | |||
| c0d64c4630 | |||
| a323e32a42 | |||
| 1703cb24d1 | |||
| 35ce444c23 | |||
| 8b2f1c054c | |||
| 56c43aa1e6 | |||
| 2cd705950f | |||
| 467b84d3e2 | |||
| 4270d2fe56 | |||
| 519de4f5ba | |||
| 90a6efecbb | |||
| 27e3fd5e13 | |||
| e55d70fa62 | |||
| b873a1f033 | |||
| 0f0bc9d318 | |||
| 087a736d1a | |||
| 97fbec551f | |||
| 7c974b7fb4 | |||
| 4f712116c2 | |||
| 83ce5f3fea | |||
| 7f4a626a83 | |||
| 47810c95da | |||
| bd14c73f9b | |||
| f368382765 | |||
| efd44c421b | |||
| d429d55d16 | |||
| e3fc712e11 | |||
| f5b495969b | |||
| 1ee08fca51 | |||
| 525108dd95 | |||
| d005f9204c | |||
| 6237e117aa | |||
| 0c29167057 | |||
| e28550b53f | |||
| c43e163a56 | |||
| 31f40ae9ee | |||
| b8051ce2cf | |||
| 09c637914d | |||
| 7e283404f3 | |||
| 8a20824266 | |||
| 0e63e698f2 | |||
| c2bb57f0bc | |||
| 6676d5844b | |||
| ca7d6c8ae0 | |||
| b267b74e8b | |||
| dcc14a565d | |||
| f96e84ff4a | |||
| 41e9c00fa8 | |||
| 85e3fd5e36 | |||
| daf7d2c9dc | |||
| f663837c4e | |||
| 77d9b93887 | |||
| 816d2b8a76 | |||
| f40cf790af | |||
| 8e1a3d26b1 | |||
| a240329f22 | |||
| 6afa6d2142 | |||
| 0c587415ea | |||
| ace8802480 | |||
| 7e1637b810 | |||
| 0fabedb0c4 | |||
| e567f06cef | |||
| bf87ed69ce | |||
| 7cb0e4e039 | |||
| 510f1b2188 | |||
| 1bea1e8c91 | |||
| 9c89099cf1 | |||
| efae444942 | |||
| 3536e54b50 | |||
| 17984c812f | |||
| 09ad926642 | |||
| e3549e639e | |||
| b421691734 | |||
| 835c113416 | |||
| 2e807806ba | |||
| 2c21eccac3 | |||
| f302da1275 | |||
| 82ce9b866f | |||
| df23d79e04 | |||
| 43aae4ee56 | |||
| 73be5c5f0a | |||
| 02c71e9310 | |||
| f041272302 | |||
| aeb1955947 | |||
| a0332cb0df | |||
| 79df7bbeef | |||
| af64052e33 | |||
| fc479c3582 | |||
| 886e16a1cf | |||
| 367b1b9499 | |||
| c61da40f70 | |||
| 90cc63e57a | |||
| fc17f23533 | |||
| 69fc9d9b35 | |||
| 533156e0d0 | |||
| 8c125cccb1 | |||
| 3da64f6bc5 | |||
| daa671c6a5 | |||
| 71eb88016e | |||
| c45166387b | |||
| f4dffe7f37 | |||
| df69162f78 | |||
| e14c9b61d5 | |||
| b1cc6bd19b | |||
| e02b505ae5 | |||
| 6aad5a9581 | |||
| 4b33a10779 | |||
| 6ec202da9e | |||
| 54cf1cf821 | |||
| d36966e2d7 | |||
| f01a2a06b5 | |||
| f866ef1fd7 | |||
| 480966f978 | |||
| b3f06049be | |||
| 7887805550 | |||
| dbd12d3e92 | |||
| db79ecd636 | |||
| 9da8cc0cea | |||
| f9c7bbbe01 | |||
| 81116598cb | |||
| 46c803c9ce | |||
| 93567a68e8 | |||
| 2294653431 | |||
| aae3add0c9 | |||
| b690cd6335 | |||
| 487c0fc916 | |||
| 0551c010b5 | |||
| d645286744 | |||
| f3e674684e | |||
| fd72cd5e22 | |||
| e8d2f8c476 | |||
| e7e25cd25b | |||
| d7750c099b | |||
| b74019a83b | |||
| f81f4925a9 | |||
| df307d3d45 | |||
| a122d6e905 | |||
| a97b7706d8 | |||
| e622928d9d | |||
| fceda9bd35 | |||
| 0bd22f89a7 | |||
| 1b996e37a7 | |||
| 403344e120 | |||
| a200951b75 | |||
| 5c71c53787 | |||
| 40b1e8297f | |||
| dfbbe3fcca | |||
| b484e3311c | |||
| 287172cd5d | |||
| 5ef979db79 | |||
| af2dcc8533 | |||
| 06514ed7b7 | |||
| c7bc853ab3 | |||
| 3f20ab758c | |||
| e3db8372a7 | |||
| 175379dfdb | |||
| ae87646e40 | |||
| b0f09b20d4 | |||
| f10bbacf7b | |||
| f50a3fe686 | |||
| bdc55dd082 | |||
| 86a10e9af5 | |||
| e5934c3eed | |||
| dabd8dc136 | |||
| 5509e21759 | |||
| f324310355 | |||
| 8b25e2b037 | |||
| 634fb09d2e | |||
| 0ac39d4356 | |||
| 60a0dcfdcf | |||
| 6551d7cc5e | |||
| 4859acb700 | |||
| 34c46910b2 | |||
| d51ce6a46f | |||
| bd8daf4015 | |||
| 649be3741c | |||
| fcc2ee3551 | |||
| 128808a56d | |||
| 714895976f | |||
| 9fe68c5a38 | |||
| a3a62f9826 | |||
| 389fd5e42c | |||
| 947a13556a | |||
| ee1fbf72cc | |||
| 06775806d5 | |||
| 961d5e7721 | |||
| 727227cf8b | |||
| cead3c87ea | |||
| c550a792b8 | |||
| cc1e5e4636 | |||
| e279e4d972 | |||
| eb6fca6697 | |||
| 7476655302 | |||
| 8eb50a8917 | |||
| d835d8bc30 | |||
| 981e3be9b0 | |||
| db3d8ddfc2 | |||
| f5c421ab99 | |||
| 4ad001fd9e | |||
| 05f3e52f55 | |||
| 8b10ec2cfb | |||
| 7b278aeff7 | |||
| d5e9b20aec | |||
| e9c3eca68a | |||
| 41a328fbf0 | |||
| e1a8ba09f1 | |||
| c565dc1a7e | |||
| ede90eb282 | |||
| f95165185c | |||
| 26361718a7 | |||
| 41cf490681 | |||
| 44e384a256 | |||
| 21276f4d1e | |||
| 43e8c85295 | |||
| 4672138b00 | |||
| b8f115ef10 | |||
| 574cbecf18 | |||
| 97b3f7ec55 | |||
| 7982f43a62 | |||
| fc32ce56d0 | |||
| 6c6a4edf59 | |||
| 44da20ed4a | |||
| 4f8de83ff4 | |||
| 066971e720 | |||
| 107dc8cc24 | |||
| 7b42efd37a | |||
| 9df78087f0 | |||
| 06d12fb8ec | |||
| 08d1a2dfd6 | |||
| 035e51b36e | |||
| 901aa820e5 | |||
| c1a5641ff5 | |||
| 957a63d04a | |||
| 43960b3a60 | |||
| 8d7e4bdd0d | |||
| 9610baf903 | |||
| 52011a7d80 | |||
| 6be1437ecc | |||
| cc49b4c921 | |||
| 3aa1d5f1ed | |||
| 991f113e4e | |||
| 04963a616d | |||
| 839ca74bf1 | |||
| 437b2020b7 | |||
| 4712931850 | |||
| e56af1adc6 | |||
| 7f245e2896 | |||
| 4fd5ba5630 | |||
| 7c5740a39b | |||
| 53998cc51a | |||
| 3faa20a29b | |||
| 57a2e5aba4 | |||
| ad01fdad4e | |||
| 2893dc33b6 | |||
| 5a846cdec5 | |||
| c13ba5f25e | |||
| 28e956e55c | |||
| a05b041d69 | |||
| 496225ab9d | |||
| 254b9471e4 | |||
| b137feac16 | |||
| cf159f6112 | |||
| 60f055f07d | |||
| 5dd0fe2761 | |||
| 43fe3874dd | |||
| ddf97f79d0 | |||
| 5a050be3de | |||
| d8aa0de443 | |||
| 7662e4f904 | |||
| b0d64d00b6 | |||
| 9dd8f32595 | |||
| 99dd0a4e2e | |||
| 531411315e | |||
| e6eb53796d | |||
| a3ced86214 | |||
| 435ff32904 | |||
| e1270836d0 | |||
| 93f8d0990f | |||
| b23a2d4f2c | |||
| d031087972 | |||
| cdf24a1c19 | |||
| e857d538fb | |||
| 3bd699c9e6 | |||
| 6f756f48cb | |||
| b14a5b3dd9 | |||
| 5f23f6caa3 | |||
| d05ae5231c | |||
| 07d3eea1d1 | |||
| 23830f3454 | |||
| 24ae5d327e | |||
| 7b9454b101 | |||
| b976ba17d5 | |||
| 9c803164e1 | |||
| b9592b30af | |||
| a536e1600d | |||
| 6267e54ad7 | |||
| 11b337b189 | |||
| 20d34eb622 | |||
| 416df97b64 | |||
| b3cabb788b | |||
| c620c11ba8 | |||
| 7515f77846 | |||
| b35ef90916 | |||
| a4d2a720d0 | |||
| 8141e15bd9 | |||
| 064488a9c4 | |||
| bb08c61b76 | |||
| 6ed9c9869e | |||
| 586d7d4f9b | |||
| 0ac96e02ab | |||
| 63be9874f4 | |||
| c1393ce7c7 | |||
| 973d0c0c07 | |||
| c4f81e7027 | |||
| ed0289df3b | |||
| 7e4cbaf5df | |||
| bce8b6f9a8 | |||
| 0718546167 | |||
| 8e222eb40d | |||
| fddb7ecc05 | |||
| b5ae6f01eb | |||
| ecd7a225e9 | |||
| f2e4508cfc | |||
| 4031925e11 | |||
| a99f6cac5e | |||
| d5bccd9bb1 | |||
| 94102cec97 | |||
| 778620c312 | |||
| c50a505cdb | |||
| 1830aeba18 | |||
| 1ba6b761f0 | |||
| b1921b7847 | |||
| f37fa1b071 | |||
| a79201ebd7 | |||
| b557586a62 | |||
| 1842ab790e | |||
| e8937e2030 | |||
| 2ddfc77b66 | |||
| 00526116cc | |||
| 1c2bfb7991 | |||
| fa52acad27 | |||
| 18c11899cb | |||
| 3cd323cb1a | |||
| 09c894db0a | |||
| 3f030805d1 | |||
| e96be21850 | |||
| 9242b93558 | |||
| 4de08c438b | |||
| 576311f7b2 | |||
| bf96099e46 | |||
| 854edba825 | |||
| fe3e5de518 | |||
| d5b1e9f40c | |||
| 54fe878580 | |||
| 075a21a9db | |||
| cc605e24d9 | |||
| 4ea7401190 | |||
| 3b4525413a | |||
| 8bf59faf66 | |||
| 54b4dd7818 | |||
| 21b3cca54a | |||
| af65c39c87 | |||
| 1d51cc3388 | |||
| b6c1cd504e | |||
| ba0f2248d8 | |||
| 96605fb0fe | |||
| 991abd4c1c | |||
| fec23cab8d | |||
| 0fe7bdc5b5 | |||
| 65557dfb3d | |||
| ad907a72a1 | |||
| c65aea86c6 | |||
| 76dd63a326 | |||
| 8798b4e826 | |||
| 7b1fa1246f | |||
| f3e4773811 | |||
| 233b4c78ab | |||
| f81d316ad4 | |||
| f123e90392 | |||
| d0607c789f | |||
| 85278ea147 | |||
| 9cbe0f38f9 | |||
| 6bcb20ee4c | |||
| ca104160b0 | |||
| bc13baa5a9 | |||
| 2014a030d6 | |||
| f673cae32b | |||
| f5d09d569c | |||
| 94749c1c69 | |||
| 9c216036ec | |||
| 7e8750f79b | |||
| 8ca46aecdd | |||
| 0e6779fafc | |||
| acde0218b3 | |||
| b8a1955ab9 | |||
| e49426c027 | |||
| 00d547362b | |||
| cce6e821c2 | |||
| a48ebfc4c1 | |||
| b23bdb0188 | |||
| 69b92b57aa | |||
| 84d692feef | |||
| 6834d41f4b | |||
| 9fad405064 | |||
| a8ddbb5d2d | |||
| 1fd328f90a | |||
| 6779fa90a5 | |||
| 45d6bfa3fb | |||
| 5d1a9b1e9c | |||
| 77ae119d32 | |||
| 0facd08fa9 | |||
| 0ccdc47034 | |||
| 7c21ec0c5a | |||
| 731beb0f7c | |||
| 9a170a3c5b | |||
| f93d629acb | |||
| eede0e3c34 | |||
| f4f97be46d | |||
| 3bea2a314e | |||
| 4d20de926c | |||
| b307b4ed95 | |||
| 653cba4d4e | |||
| adb93e382f | |||
| b2938ef678 | |||
| 809c8806d0 | |||
| d809c2e789 | |||
| ca2d073775 | |||
| 4038c437c6 | |||
| 12011fd0c8 | |||
| 528ae04711 | |||
| 7612a3f742 | |||
| 2272b94531 | |||
| 21d628b598 | |||
| ab0049ec5c | |||
| ac394784e3 | |||
| 943a72ce12 | |||
| 927d9b0f85 | |||
| 7454087c88 | |||
| 37fd19fc9a | |||
| aa90e75c59 | |||
| 7b090c2e2a | |||
| 1a4a8d87fc | |||
| fbd0644942 | |||
| 4eb4f635e7 | |||
| 60d6bfae9f | |||
| 009345c5f6 | |||
| 00d4d368df | |||
| 5fda1cdc61 | |||
| 91955ef66c | |||
| 89585f8121 | |||
| 449df7f161 | |||
| a5170c51b3 | |||
| 20bda361a3 | |||
| ca7ae4e1e8 | |||
| 6f08589265 | |||
| ac5c902569 | |||
| 731b5cccad | |||
| fd1e82bd8e | |||
| 2140eff9af | |||
| 6f97bb2635 | |||
| fd0b5f4377 | |||
| c5bd1d6bc0 | |||
| 40ba90df27 | |||
| 450a1493fe | |||
| 3b1f9aa41e | |||
| e993671d02 | |||
| b36758a155 | |||
| 14a46622c3 | |||
| 9aea0f1034 | |||
| 8e87a7aa99 | |||
| 4d31886e4a | |||
| 4886f87afa | |||
| 923b22a75a | |||
| 610ac47289 | |||
| de28b8d314 | |||
| 7c50f6c8d0 | |||
| 8b493e091d | |||
| 13065d7e5a | |||
| 3f7d6759c1 | |||
| a4be03cd5f | |||
| 44d5994248 | |||
| d8b6d52a85 | |||
| e0e6054f2f | |||
| eeed0c0b1a | |||
| 5fbdea2cae | |||
| 393944a8a7 | |||
| a442e2be91 | |||
| 18398e1b93 | |||
| 1b08584ced | |||
| 20a3da8a19 | |||
| 1dd4c4a109 | |||
| a228c54dd5 | |||
| 125d3c0a4d | |||
| c9f5397821 | |||
| 57a5c0f743 | |||
| 8dc31cc790 | |||
| fd1d74ada1 | |||
| 28756860aa | |||
| 5929533d78 | |||
| 395baf1509 | |||
| b4d89b66f5 | |||
| e9f803b8a5 | |||
| 8b25dca1dc | |||
| 514363247a | |||
| 354a415e73 | |||
| 4df5de3122 | |||
| e5815974b1 | |||
| 2ff8e8fa67 | |||
| dd7038adf2 | |||
| 39d25107bd | |||
| 2c671578ae | |||
| 76680ba86c | |||
| 4d83c5a8f9 | |||
| 274a857dff | |||
| c891c9a921 | |||
| 247a8e0cca | |||
| 15c0ab6279 | |||
| 5c7c21fdf1 | |||
| 14348306e2 | |||
| 02cc4c1aa2 | |||
| 1aadbfc019 | |||
| b53e657091 | |||
| 74967cbac8 | |||
| a139451a9b | |||
| 7708372922 | |||
| 09146ec176 | |||
| 3abadb666e | |||
| be164a823d | |||
| d5dda1722a | |||
| 28ea9a7861 | |||
| ec87635e8e | |||
| 48219fbd95 | |||
| 33b5202a5d | |||
| 141fa55869 | |||
| 2a2e2da879 | |||
| 73bd73d910 | |||
| 9d733fcff5 | |||
| 307c543d8d | |||
| 5ffd1c85d3 | |||
| f771d47526 | |||
| 50e73d4a3a | |||
| 6cc96d094d | |||
| dc149d2636 | |||
| 6a9fd04989 | |||
| 7c01257cdd | |||
| fa99a0b3c8 | |||
| cec8dc991c | |||
| 2b78225114 | |||
| 2ffec8014a | |||
| 30f2db1e4d | |||
| 1b44b27142 | |||
| badc1249c0 | |||
| 8621b3f6ff | |||
| e67d8e9c81 | |||
| 769ad73737 | |||
| afb14409c2 | |||
| 971aaf3b98 | |||
| 2d2145a780 | |||
| 28078910a2 | |||
| 6235e6e665 | |||
| 5bf63bc36c | |||
| 02192368d2 | |||
| b0cfcaff3d | |||
| d208b07a94 | |||
| e715478310 | |||
| 5dedbf91e0 | |||
| 64e04ae15b | |||
| 573105d269 | |||
| 8fc6a4b349 | |||
| c6c76be8f9 | |||
| 1cd15ffa53 | |||
| eb9c3f7867 | |||
| a369409930 | |||
| ad6788f67a | |||
| 28de204036 | |||
| cf5bfb16bd | |||
| 5642d379a5 | |||
| e33992ecf8 | |||
| fa85e0cc68 | |||
| 42ee8d9ca4 | |||
| d521e014fd | |||
| 43545bce41 | |||
| 78723402ee | |||
| 9698974ad7 | |||
| 6f138677a8 | |||
| 9161044860 | |||
| 30e804acd8 | |||
| 40fe0bebcf | |||
| 4f25572d1d | |||
| 81cc35702d | |||
| cd3ed42b6d | |||
| 4637b82471 | |||
| e1d77c0c3e | |||
| bc688792cb | |||
| 36b33ba4f1 | |||
| edde61a46c | |||
| aa1fcd7eb9 | |||
| 3f59e2078a | |||
| c2edd26598 | |||
| a0ceed9586 | |||
| a6e360c1db | |||
| 79b49bd57e | |||
| 0c1b7abe36 | |||
| 6d39b63a74 | |||
| 438b2aac65 | |||
| 3ffc25d5d8 | |||
| 3b689f8a66 | |||
| 2b62a5a0cc | |||
| 7e46145df1 | |||
| 9655549739 | |||
| 57a3d1369b | |||
| 2ae7a519d0 | |||
| 9165c6b706 | |||
| 7818864fac | |||
| 3f577f1088 | |||
| 546435db6d | |||
| c6f64a3acb | |||
| c6b7652c6e | |||
| bfebd69568 | |||
| 3fb46f9604 | |||
| 17384f5761 | |||
| 5a5328b1cb | |||
| edc83764c4 | |||
| 1f5527164c | |||
| af5a7964d0 | |||
| 41c2814610 | |||
| c197f6eab2 | |||
| 410da66834 | |||
| eb6a0e38f7 | |||
| 8e90c2898a | |||
| 4074741187 | |||
| 01bcab4af1 | |||
| 6c5cc98016 | |||
| b39618cd89 | |||
| 22316b4684 | |||
| 1e7e3a84c6 | |||
| 872e0762b6 | |||
| 61fec4b53d | |||
| 8e7b012c4c | |||
| 7ae53ac364 | |||
| c519505296 | |||
| a9b60c1d1b | |||
| 22108934ff | |||
| c93f8eafea | |||
| 856cfd3ebb | |||
| bccb516223 | |||
| 53717588f9 | |||
| 4258d94d00 | |||
| 01f8631663 | |||
| 393d90d3f7 | |||
| bd90e2c19e | |||
| fe19cea6c9 | |||
| 56ab7f212e | |||
| 5179611c64 | |||
| 0c930294f1 | |||
| 8541dd3178 | |||
| 7e9a07838e | |||
| 7f2d1702ca | |||
| b4b461d815 | |||
| bc75157b9f | |||
| 67465045bb | |||
| 5dbfe00207 | |||
| 38ac1723f6 | |||
| af3ef1efde | |||
| 3fba370e87 | |||
| a28b1b5aa2 | |||
| aec5ef442e | |||
| c907512cf0 | |||
| b87190993f | |||
| 2d7cacbefb | |||
| 63e81369c2 | |||
| 6f9bc17d57 | |||
| 7bd1810852 | |||
| 433fe1449a | |||
| b6566ec67b | |||
| b67b0baa1b | |||
| 73e6d627bc | |||
| e4ace3c416 | |||
| b58c86cdd6 | |||
| 9ff93169df | |||
| 44cdb3a52e | |||
| 3d7fbba014 | |||
| ce1a397a7c | |||
| fa7da189fc | |||
| 582fffda1b | |||
| 3bf6f7ca09 | |||
| 0a7c549125 | |||
| 3bb510e910 | |||
| 75a84f6ed1 | |||
| a7aadd7439 | |||
| 1e90434c18 | |||
| d718f023cd | |||
| 28b47b25ea | |||
| 2bf933a3e1 | |||
| 24a548cd97 | |||
| ce307391a8 | |||
| 7abc7c07af | |||
| d8c49a3d04 | |||
| 064bb51d7a | |||
| b74e36ca60 | |||
| 3e344a3de0 | |||
| b19cc90fd3 | |||
| eff8d56ef5 | |||
| a0825e7774 | |||
| 765e370bd5 | |||
| 8469a07f4f | |||
| fcf4646928 | |||
| f5bbb1747c | |||
| 1868bffbef | |||
| eb5c6796ae | |||
| a35d128bb5 | |||
| 74787f8927 | |||
| 64455b594b | |||
| aa064fb6c5 | |||
| 70f3a25798 | |||
| 19230db8b7 | |||
| 4fb7f7c1e7 | |||
| 0b20dda56e | |||
| 5c29ac8d1e | |||
| eeed075be2 | |||
| 6c23d482dd | |||
| b2a7b191cf | |||
| 95c3cf2c90 | |||
| 26dc81fc6d | |||
| 26bab59886 | |||
| c412090dbc | |||
| a4c2bf31c7 | |||
| 5d15ccae9f | |||
| 63f1954c2a | |||
| 1fc4b88784 | |||
| aa9b1c3331 | |||
| 458b086468 | |||
| 75762e8ddd | |||
| 58dbdd5189 | |||
| 0d3d38a032 | |||
| 68ea59328e | |||
| f74a4f056e | |||
| d70e5c396f | |||
| c70b8f7aa2 | |||
| 737dd01c8d | |||
| 945deafa63 | |||
| a77b7f7b66 | |||
| e86a0e23bb | |||
| 6218ffabca | |||
| 4de3c5e587 | |||
| 7dc448938a | |||
| 1c30330faf | |||
| 40d36c2468 | |||
| d60113bab9 | |||
| 71a19a86f8 | |||
| 5385a4834a | |||
| 91b55ad2af | |||
| b1428b34c1 | |||
| 3a5b0af81d | |||
| 1f1a9a0b13 | |||
| 83c9f98d0a | |||
| c8f8c85c6e | |||
| 0c14112d60 | |||
| aa15572c68 | |||
| b16e171619 | |||
| a23551c7f2 | |||
| 95c2947d4a | |||
| a87e4ff449 | |||
| 8fac9fe67e | |||
| eafaf78c12 | |||
| 66e40128f8 | |||
| cf303380b6 | |||
| 4b6b27a8fd | |||
| 48f054c809 | |||
| 10e25fcd03 | |||
| 35dadbb7c4 | |||
| 29bb41aff0 | |||
| 28e6da1bbc | |||
| b9a92507d1 | |||
| fc8985b242 | |||
| 924a01bca6 | |||
| fac79c8a7d | |||
| 0e89e8dcc2 | |||
| 594dc64824 | |||
| 79ec382b7f | |||
| 1ec94aa2a6 | |||
| 70a5343d60 | |||
| d88da39fe9 | |||
| 5f369a511a | |||
| afac62e788 | |||
| e4533cc03f | |||
| ee9c4baa9d | |||
| 27c36530cb | |||
| a5b80c1c73 | |||
| d6e9e9f2a5 | |||
| 122c1f8e37 | |||
| 07a3edf020 | |||
| 299fd4f107 | |||
| 1ae989c0c0 | |||
| 4d9323800b | |||
| e935976c91 | |||
| 319680e7cc | |||
| aa40185058 | |||
| d0e0716221 | |||
| 62e6641480 | |||
| 6c25cc6a78 | |||
| ec3ac73def | |||
| 9b547d6ece | |||
| ce1f149547 | |||
| 394d992b19 | |||
| b8b3b7993b | |||
| 915877eba6 | |||
| 7dda10629a | |||
| fc5a5d7f63 | |||
| d2ff23813d | |||
| 06840c6c84 | |||
| fda70b0aef | |||
| 7b13ddcbfc | |||
| 741af0fc43 | |||
| abf9b6c5e4 | |||
| 56361dda86 | |||
| d276e7b568 | |||
| 5af0fe35df | |||
| 94fb563a15 | |||
| 865e8575b2 | |||
| d95147712b | |||
| 20e1fa935a | |||
| f885351464 | |||
| 33fdc1cdc7 | |||
| 9d39417142 | |||
| 96ed90e2cc | |||
| f99c390a76 | |||
| fba3a54f82 | |||
| fedeb47dbc | |||
| 79ac51a1ca | |||
| 02631da9f1 | |||
| 692172d57b | |||
| 8bd4bbe7ea | |||
| 0d93f90c62 | |||
| 3f9459a07e | |||
| 19d063c3e9 | |||
| 7f8a5315c4 | |||
| 0f7f7d997b | |||
| ab54a24434 | |||
| 77901659e5 | |||
| b3f74b6c1c | |||
| 7eb5fa8d8e | |||
| a90e2132e3 | |||
| 60b0db7120 | |||
| af538950a8 | |||
| 5787e95c51 | |||
| 3b3be4b6cb | |||
| c1fa902189 | |||
| 31706e2724 | |||
| 21d6bcc63e | |||
| c4ad1a7127 | |||
| 17984344ee | |||
| 035a58e7c5 | |||
| 646e33e59f | |||
| 68ddfb0f02 | |||
| cd3b8b5bab | |||
| da88fd4267 | |||
| 29413fdc8e | |||
| c643d26bce | |||
| 69d4f32933 | |||
| 26154a6cdb | |||
| 7b22553f09 | |||
| 9726f0f586 | |||
| 25f6f09d22 | |||
| 3e6a68b472 | |||
| 60e18c4854 | |||
| f7633dd61f | |||
| ae74354140 | |||
| b0987b224d | |||
| 5ecae9662f | |||
| 0f29869a76 | |||
| 67dd730666 | |||
| ccaa13fa21 | |||
| ff795b1373 | |||
| 81287e9eb5 | |||
| 26b9d8193c | |||
| 81bdb352aa | |||
| 2ae8530c83 | |||
| 476755fc18 | |||
| 4247495cf4 | |||
| b6eada070a | |||
| 2399600169 | |||
| db9877beae | |||
| 43f1d5c53e | |||
| 4a31d538c6 | |||
| 23dd7baf35 | |||
| a8e1f3ef4c | |||
| cd622e9f81 | |||
| 71f7d45084 | |||
| 0f586fe49e | |||
| 42913816ce | |||
| 027f9a1793 | |||
| 8bc2cf335b | |||
| 626c71a942 | |||
| 58b9c4f1d6 | |||
| 24163dfa4a | |||
| e3d596e034 | |||
| cd48f45462 | |||
| a0229e8132 | |||
| be7ff94ae3 | |||
| 98ddce24ad | |||
| b380a77a7d | |||
| 9f7a2d86cf | |||
| 343a7ef4fe | |||
| 88c3b0ba83 | |||
| 918997acfa | |||
| dd1fb111a5 | |||
| 7124b69912 | |||
| 76bcca55d2 | |||
| 7fa2ffc14e | |||
| 0ee141493f | |||
| 194b21c41e | |||
| 93634985e8 | |||
| 6757dc2ca1 | |||
| 3a888f9be4 | |||
| 34c9cbeedb | |||
| 97a2769002 | |||
| f87e6e2b6a | |||
| 7bd3f1e08d | |||
| aeb20a8949 | |||
| 58e99403ad | |||
| 530987152c | |||
| 4085b4a74a | |||
| 852172a7ff | |||
| 7aa1230553 | |||
| a7097014a3 | |||
| 9f711c20e0 | |||
| 87c031b825 | |||
| 18e2f5dd7f | |||
| d1b70185f1 | |||
| 254175c98e | |||
| e8e67912ec | |||
| 3c00805763 | |||
| 599055b49f | |||
| 82fa754497 | |||
| 9ec9cc0e25 | |||
| f52c9415c9 | |||
| 27626ee59a | |||
| 0b36e2a833 | |||
| 1aa0e3fb47 | |||
| 4a40ce5646 | |||
| 6e126dc08d | |||
| 6201798ef5 | |||
| ec3c09607d | |||
| 2b104435dc | |||
| 83be5822b1 | |||
| 0491bd993e | |||
| 8713e94428 | |||
| ccab724db2 | |||
| 7876ab993a | |||
| bd8e004795 | |||
| 01ab0f5ab9 | |||
| 0728dba0cd | |||
| 3dd5e78b35 | |||
| 667afa6d64 | |||
| d24555a4d7 | |||
| 410971a228 | |||
| b497c22d6b | |||
| de6969f561 | |||
| 0ce4260134 | |||
| 7237dd053d | |||
| 0f6c076dda | |||
| b57e678dc9 | |||
| 6031bb4953 | |||
| 05dc53b396 | |||
| e97f819a5c | |||
| 28c37c08d2 | |||
| f430b22884 | |||
| cf4505635b | |||
| 8ad0de63e1 | |||
| 8068ea95d5 | |||
| 44cbd025f4 | |||
| ac76930dd1 | |||
| 3a653d9558 | |||
| ae50430527 | |||
| 83386bcdbf | |||
| 1e175e4e82 | |||
| 01815d04dc | |||
| 46ea6900b4 | |||
| 6394fe049d | |||
| f324fc7c10 | |||
| 434c383fcd | |||
| 9eade626aa | |||
| 263299b97d | |||
| 7e491ce7d8 | |||
| 6707487d3c | |||
| 0498d7ea98 | |||
| fc9bda9f7f | |||
| 2b47083c12 | |||
| 1f700f33b2 | |||
| 769dafb428 | |||
| e3bc2e5d84 | |||
| f3120f1e0d | |||
| 036d8b4852 | |||
| a67a8d746f | |||
| 2237ec135c | |||
| a238c33fd0 | |||
| b90cf14228 | |||
| 7bbd1d2d52 | |||
| 8aebbb1b51 | |||
| df7504d4a2 | |||
| 063399a6d6 | |||
| 9762a94138 | |||
| 800f5c7c64 | |||
| 16995d2ae5 | |||
| b4cced68f1 | |||
| 9bb01bba30 | |||
| cb3842f0bd | |||
| 5d95d20a32 | |||
| b628ee0c3e | |||
| 688bf0bef8 | |||
| 06d6d3a9ca | |||
| bb4debb863 | |||
| 5d1a3fe6a7 | |||
| 72b5d006ba | |||
| d382358766 | |||
| 429cb50ff7 | |||
| 3a01dad945 | |||
| b9ae5cafa0 | |||
| ed8e4b8766 | |||
| cecd47caea | |||
| 7711946cf5 | |||
| 074aebbe5d | |||
| 8b78d05805 | |||
| cf962a26f4 | |||
| 14778696e9 | |||
| ed56d03c09 | |||
| f9d9df4bbf | |||
| 0859d75256 | |||
| 21303b24c8 | |||
| 06965e7eed | |||
| fc0b069ad2 | |||
| b0b27d6842 | |||
| 47725ea4b5 | |||
| 103d349c5f | |||
| c41364fb16 | |||
| b984d6794e | |||
| cf4aa1256d | |||
| 4a6fcb4f4c | |||
| 426804304c | |||
| 074f8ed902 | |||
| d3a1bd52c7 | |||
| a4103859cd | |||
| ed19b7b635 | |||
| f21dd041ae | |||
| 2de21b5f25 | |||
| a547ae6cf9 | |||
| c39901e95d | |||
| 4f0522d913 | |||
| b972d3fabe | |||
| d6bef6b17d | |||
| 231100cbdc | |||
| 9c8d71ca4d | |||
| fcdc17dd93 | |||
| a719ca684c | |||
| e5033c3213 | |||
| d37cf9e9c4 | |||
| d9688884e4 | |||
| 2f369bbf3d | |||
| 7f1b67d21c | |||
| 6118589248 | |||
| 9a7f5c2def | |||
| 1999f17443 | |||
| 7e39f0a34f | |||
| fb9a726ba0 | |||
| ddc3f0e880 | |||
| d66b0891e7 | |||
| f1ba087bff | |||
| 6cf5f2edc4 | |||
| f0570b0e20 | |||
| cd851afc8d | |||
| 532497f6bd | |||
| f648137a8e | |||
| e9fd2f89fe | |||
| 5565911e2c | |||
| 7337012280 | |||
| e3f25e3bc4 | |||
| 9582a473c6 | |||
| 22918e4b4a | |||
| 0d567ea5dc | |||
| 703697e1c4 | |||
| 0b89ed0d17 | |||
| f3de317ddd | |||
| cd7e60b7a1 | |||
| 352a5bea5c | |||
| f33eaf6d0f | |||
| 81ebf9cc11 | |||
| 23f92e0a96 | |||
| e69f8ced88 | |||
| ba5b052ee8 | |||
| b6e6997e7c | |||
| c39507747b | |||
| c9f4390f0f | |||
| 801cd2e855 | |||
| e9c73ebb71 | |||
| 6af3a1b5a7 | |||
| c86dbeb2b3 | |||
| 4ff6b2a30a | |||
| 3310d2b431 | |||
| b53de43c95 | |||
| ffb8e284b4 | |||
| f593ad0f2c | |||
| 9114d5b2fe | |||
| 5b47a20d49 | |||
| b0acc5a68e | |||
| d7382db669 | |||
| f290192de4 | |||
| 8360d4d589 | |||
| df56224df6 | |||
| 1a636ff8e9 | |||
| 4dcc1f340f | |||
| f199bc4256 | |||
| 11e28286e1 | |||
| 4c454d463a | |||
| 80034d8658 | |||
| 296afa96c6 | |||
| 9f69b8815a | |||
| cdc38f7e6e | |||
| fcb4e6cc85 | |||
| cb00f31f79 | |||
| 84c65d32be | |||
| cc119bb596 | |||
| 88c9850073 | |||
| 75fcb9a990 | |||
| 7121a4ae30 | |||
| e636920c44 | |||
| e0cf3d0962 | |||
| 53b6f5d8e8 | |||
| 5acbfac255 | |||
| 82bb1175bd | |||
| f1b1589a7d | |||
| 0f4ff90f01 | |||
| 43e0bcbf73 | |||
| e26045ec73 | |||
| 2d52680bed | |||
| f42f526f93 | |||
| 53b03af3e1 | |||
| 32f8692f13 | |||
| 19c321e7ae | |||
| 021a49a72d | |||
| eb0bad1af6 | |||
| 513d140ea2 | |||
| 3e368141c7 | |||
| b59b7c1e47 | |||
| 6a1acc819b | |||
| e5db36e21e | |||
| d759bc274f | |||
| 3290755fa8 | |||
| fd3455d3ec | |||
| 88355b2504 | |||
| 79f078653a | |||
| a952ea02dc | |||
| 59abffb1c1 | |||
| d737139bee | |||
| a1479a9b6c | |||
| 45894c3255 | |||
| dcfdb2ecff | |||
| ba7ed5be1c | |||
| 307412e6ca | |||
| 41e1e89696 | |||
| d239ab6b1f | |||
| 0905773d08 | |||
| 9ca80d352b | |||
| b52440bcc1 | |||
| 28c9cacf68 | |||
| 8199fdd2bf | |||
| f74ed76850 | |||
| 5b301addd8 | |||
| 1c1a141701 | |||
| e85f59db8c | |||
| fc69ca0599 | |||
| 2ad304aaf2 | |||
| c1b56922aa | |||
| 8c737f2ca4 | |||
| 20d9561143 | |||
| 1062ef9b49 | |||
| 2b54f64c8c | |||
| 5cd5a9d7e4 | |||
| bb49747fd9 | |||
| f38c7bffed | |||
| 813f02604e | |||
| c579dce2cf | |||
| f6d9c2998b | |||
| 60c303b771 | |||
| 79e7f262a7 | |||
| 8f2863c02c | |||
| cd8cb03797 | |||
| 9f4aa3f7e1 | |||
| eb2c9f2fe1 | |||
| e7ffd2455a | |||
| 63b7a3a36c | |||
| 376f2bfeb1 | |||
| 0a34b139f4 | |||
| 45d0a8e501 | |||
| 485dd2952e | |||
| a111b50e37 | |||
| b2d14ca101 | |||
| f3ab3573c3 | |||
| ef4ae4480f | |||
| 8277a2d942 | |||
| 8bbf040100 | |||
| e6bb1a3fde | |||
| 6df7482b3d | |||
| bdc0ade117 | |||
| 29a149b340 | |||
| 80af866650 | |||
| 1237d9660e | |||
| 8e6582b801 | |||
| 18ad260ce9 | |||
| c0933a3b20 | |||
| a116ae6ab5 | |||
| 0e2eea7555 | |||
| 767e35851b | |||
| a25a3c186b | |||
| 28256c0a72 | |||
| dac9ed2785 | |||
| 9cc576b98d | |||
| 87c95f7a7f | |||
| 4065142830 | |||
| f56308b6e3 | |||
| bdbdf7cb83 | |||
| 9a0f28c003 | |||
| 2d2d93d5d8 | |||
| 8c44dd6119 | |||
| 0cb1bc4ca5 | |||
| db83864d78 | |||
| 63365c2109 | |||
| 9bbc26dd70 | |||
| a383cfd125 | |||
| 8bb9070d9a | |||
| 690e934a46 | |||
| be1974a89e | |||
| d21087b0d9 | |||
| 22b1ad087c | |||
| ce25675f73 | |||
| 955a7696f1 | |||
| 4cfacde337 | |||
| eafa8f02b6 | |||
| 1338b65aaa | |||
| 7c8068b2bd | |||
| 75f749dbe0 | |||
| 58910f50e8 | |||
| 36eebffd10 | |||
| 0e8e0e4b06 | |||
| 8524fb500f | |||
| 8fd1d9acef | |||
| c452dd3538 | |||
| 222f083322 | |||
| 47527d7175 | |||
| 6f907f7961 | |||
| 891a226fdb | |||
| 7cbc707308 | |||
| 8d3a036b3b | |||
| ee21139356 | |||
| b37814e9fa | |||
| 07cca33cb4 | |||
| 980533052b | |||
| 8124f688da | |||
| 89e726b5a2 | |||
| 42695d9253 | |||
| 28fe7817b4 | |||
| 6aa37e2529 | |||
| 2d8eb163e7 | |||
| 445b10d6f0 | |||
| 0a4dd832a3 | |||
| 8feee4e61a | |||
| fe23017a97 | |||
| b01b52c8f8 | |||
| 0f81d2cace | |||
| d051246b7e | |||
| fd7f22ffba | |||
| c5c0cf001c | |||
| ab846062e4 | |||
| 172e77a6cf | |||
| 0998d8a0fc | |||
| 71b82730ab | |||
| 6b597bf1fb | |||
| 5c431096ec | |||
| e278bed251 | |||
| efbcf5469b | |||
| de0a91619e | |||
| 7be0c1a034 | |||
| 71e43d6e3d | |||
| a77865379f | |||
| e20cc9e80f | |||
| c66c920cbe | |||
| 695dbee501 | |||
| de44a8d91e | |||
| de6ba1c82e | |||
| 5b1124d658 | |||
| 122b16a720 | |||
| dfcf2eec57 | |||
| 9a9a35d75e | |||
| ec89c9cdc8 | |||
| e66ab2b2c9 | |||
| dc167bd25e | |||
| 680378e68f | |||
| 190dbff98d | |||
| ff7aa46cda | |||
| 8c47d85927 | |||
| de99a3f114 | |||
| 8b77aa151b | |||
| 00461468c9 | |||
| ad323f7885 | |||
| a50b673e07 | |||
| d1e66c4441 | |||
| 7cc00b99a1 | |||
| c745615a2a | |||
| 7aacac554d | |||
| fcab19c392 | |||
| 98e2bf53ed | |||
| 51310e632c | |||
| 2a46b17958 | |||
| e189f0317f | |||
| 138684fcea | |||
| 23af50a30d | |||
| 28a6284968 | |||
| ce114b33ff | |||
| d05c451294 | |||
| 9fc0d26eb5 | |||
| 25a0c88670 | |||
| 0d5d9a04f5 | |||
| f2ed0057bf | |||
| 661fcfd263 | |||
| dbed431085 | |||
| fb89ca98f6 | |||
| 04bec463c7 | |||
| 25170f0d2c | |||
| 85a522ba14 | |||
| a28618ab78 | |||
| 955a5ba7f2 | |||
| dbd23a6ea4 | |||
| df93057dd3 | |||
| 3c2b6d4e7f | |||
| 2f11e12c75 | |||
| 2faf615b68 | |||
| 8bb2e0aa97 | |||
| b6e4a47329 | |||
| 08197fca23 | |||
| 423fb90266 | |||
| 67d1d73845 | |||
| fa0531b975 | |||
| cb01563ba8 | |||
| 4446259a0b | |||
| a5eb2f0ae6 | |||
| bf4a1097c1 | |||
| 87ebc8761b | |||
| 4e87ce8f4b | |||
| 24d24c1f85 | |||
| c88d77e3f8 | |||
| 553c42b82c | |||
| 3eb1dd3180 | |||
| 10b2cf2662 | |||
| 545fd8b58a | |||
| 2e31972c56 | |||
| d095cb7812 | |||
| fc31d5fd2f | |||
| d73ba14154 | |||
| d8e09e80ff | |||
| 211c4fb102 | |||
| d448b76dd4 | |||
| e8ebf5af73 | |||
| 459dab65ca | |||
| 73304ef8d4 | |||
| aa025e4fac | |||
| 8eac9338fe | |||
| b0deecc0aa | |||
| af111a4be9 | |||
| 6a8a9172b1 | |||
| 291b997972 | |||
| 70a5520f47 | |||
| e7397f824b | |||
| 0beee14cd8 | |||
| 9ba251ff30 | |||
| 5b24ac037f | |||
| ea70c354ad | |||
| 1623b456ec | |||
| 307959ba76 | |||
| 7f1699663d | |||
| 252675d3ff | |||
| 84c45f2068 | |||
| 5873995e42 | |||
| 077922c667 | |||
| 47dcb1523a | |||
| 86f1e63ed2 | |||
| 709415a6b4 | |||
| fa46485d57 | |||
| c2d5d2b61a | |||
| e39c1af5ae | |||
| ca6463cae8 | |||
| 0e17b7a981 | |||
| bcd1167d39 | |||
| fbe17dc3e3 | |||
| b102e5c1a5 | |||
| df9a5e398e | |||
| 90fafa219a | |||
| 9e1b55a749 | |||
| 4ca15a1fc3 | |||
| 5bf80dae4e | |||
| 1f4568d22f | |||
| 903a975033 | |||
| 0ee9afba4f | |||
| 9b5713d6b4 | |||
| 94176fad83 | |||
| de759b5120 | |||
| 9bc4ff16a7 | |||
| 814f6ced5f | |||
| 91dae8ad85 | |||
| fb4f8a86f4 | |||
| e15d2fe82c | |||
| 4ede1c74e8 | |||
| 9d98396e33 | |||
| e43dcf5d4c | |||
| eac4ab3e3c | |||
| d256d8fc35 | |||
| 3157e99e04 | |||
| 91fee0d6e9 | |||
| 521061fc64 | |||
| 4328746df0 | |||
| 2b87d939bb | |||
| 39a88a6cd3 | |||
| d0ad416e28 | |||
| 61e4f59aa0 | |||
| db66b85e61 | |||
| 66c810ead2 | |||
| 9824bb9c63 | |||
| f806e2c22c | |||
| 3d408b18f7 | |||
| 8aa776ae62 | |||
| a15f3b8c65 | |||
| 4df22c96d0 | |||
| 772df06fa5 | |||
| e1b7336d5d | |||
| d3a9d2ea5b | |||
| 4914d9b638 | |||
| 1f8a7be34e | |||
| 97bdfa54c0 | |||
| 64bb730dd1 | |||
| 7013b459a3 | |||
| a31733e2db | |||
| 199b23d14a | |||
| f1c1ed833c | |||
| 0506917b87 | |||
| 4f40ba8e6e | |||
| 733a792610 | |||
| f581fd4821 | |||
| 86ddb61a3f | |||
| 6c3451b912 | |||
| 627c8562f7 | |||
| 061bb2abeb | |||
| 35e1dc95a5 | |||
| 1dc46fa104 | |||
| 4161467356 | |||
| d70f81bfe4 | |||
| bfb7ccffb5 | |||
| 3a6c032782 | |||
| d632111cf9 | |||
| e4b761917a | |||
| f8d162d995 | |||
| 0708070764 | |||
| 87f7bc28a3 | |||
| 3eb7d8ab58 | |||
| 71d0ac4c5e | |||
| 839593b11e | |||
| 13a0927900 | |||
| 00984c599b | |||
| 34ca65a180 | |||
| 9df7129c6c | |||
| 0b9717c2a5 | |||
| 596ae72942 | |||
| 88cc91b85a | |||
| d4b8ded6c8 | |||
| 86d2a03a0a | |||
| de1812bf91 | |||
| f36751ff6b | |||
| a9273fc225 | |||
| 2a0b12112f | |||
| 2aea02989f | |||
| 2b554cf286 | |||
| dc2777703d | |||
| 6ae03b545c | |||
| 8e366cfc84 | |||
| 27c2c4fb92 | |||
| 83af589b27 | |||
| 7a937833f3 | |||
| 4110d2529c | |||
| 1c6c165d78 | |||
| a5ffb0e021 | |||
| a08a39b620 | |||
| 968e8195ef | |||
| 52ce2f5384 | |||
| cb2d1bc444 | |||
| 47b81faf3d | |||
| 1483761e72 | |||
| 7f43665e3c | |||
| 01fcd653ad | |||
| 298e947740 | |||
| 91c2014b7e | |||
| 3018ad16b1 | |||
| 1172be241c | |||
| c743b4ab88 | |||
| f6ec718ced | |||
| aa20027de4 | |||
| e504cf11e1 | |||
| a11e0a39d9 | |||
| 26531401b0 | |||
| 0f4293e4cb | |||
| 7172b134ea | |||
| 4a9e342a1c | |||
| f98d869c21 | |||
| 1312310a6e | |||
| ad9c81f405 | |||
| 496caa6fb1 | |||
| 2fd7d45b9c | |||
| 0b087665a8 | |||
| 39862fba2a | |||
| 3ac44d211f | |||
| d3392000af | |||
| 564d2e109f | |||
| fe07298adb | |||
| cc176a999d | |||
| 47c5a41aa6 | |||
| 2ad6f2c9fc | |||
| 8905bc1c27 | |||
| 064d5174c2 | |||
| c69c8f6ef5 | |||
| 7fb81049f3 | |||
| 5099ce15db | |||
| ed500395d3 | |||
| a1e88fc3c2 | |||
| c8b007631d | |||
| 6bc1f8a39f | |||
| fe84f6cab1 | |||
| 27eea1c7a6 | |||
| 32f94704c7 | |||
| 05fdbf3d24 | |||
| f28c791cf2 | |||
| a4b474ff39 | |||
| 38c76fe86b | |||
| d09259c79a | |||
| a683fa2414 | |||
| e744816928 | |||
| 15703bce04 | |||
| f4f5540d08 | |||
| b1b37685c1 | |||
| 0ff4cc572c | |||
| 081b9c17d5 | |||
| eb6b21e7e6 | |||
| baa17c304b | |||
| 7b8b388667 | |||
| 118529d8d3 | |||
| b67c4553f6 | |||
| 47980da78e | |||
| 3d57d444df | |||
| 5870632c19 | |||
| ffeb27f04e | |||
| 012df9dcd7 | |||
| 82fd2334cf | |||
| 7bafa57989 | |||
| 5b4ccd9d59 | |||
| be2b86909a | |||
| 82506ae7cd | |||
| 574a2a11e7 | |||
| 3d5ed9401c | |||
| 3b5a674409 | |||
| f9856bdabd | |||
| b6f75acf53 | |||
| c0f7504b36 | |||
| 02f1c461c5 | |||
| c6fa348986 | |||
| 8ff226fbad | |||
| 349700d7fb | |||
| 0d9d11b2e0 | |||
| 734cf56631 | |||
| d4e782aa6c | |||
| c517264baf | |||
| d5809621d9 | |||
| 6ec24e9932 | |||
| a7805af421 | |||
| c5f8377a85 | |||
| 7ba14a4cc8 | |||
| d38a168e52 | |||
| f4dcf1fb4b | |||
| 93cf930126 | |||
| 3bd542aa65 | |||
| 5e78b09577 | |||
| 42b483b878 | |||
| b2b24206a1 | |||
| ecf824f85f | |||
| 329b6e6ebd | |||
| f6e89ed60c | |||
| b6618d2193 | |||
| 09bcd9ae4b | |||
| 73fa273e29 | |||
| b93b329da9 | |||
| db38e8496f | |||
| c1c291a9e3 | |||
| a000abc908 | |||
| 084508db84 | |||
| 6a4b8cc778 | |||
| cbd1b48b93 | |||
| d4bda66c65 | |||
| ed06192d6a | |||
| 4b4b2c9efa | |||
| bdc1e56df4 | |||
| 528a7261d3 | |||
| 8603918771 | |||
| 47007288e8 | |||
| 68fa1715d0 | |||
| 0ee77b9ebc | |||
| 19ea9ea3d1 | |||
| 055da37987 | |||
| 65e3b6c84c | |||
| 3aec3da80d | |||
| 7b3884ab1a | |||
| 5382b4bfb1 | |||
| ae30ab03d5 | |||
| 2426682cc2 | |||
| 86e041945a | |||
| ac0aaf7ba9 | |||
| bab166baea | |||
| 40f2e0b6a7 | |||
| 0806c97fe5 | |||
| 2cc313855b | |||
| 3311d68262 | |||
| 1e4493474f | |||
| 9abdfd8da0 | |||
| 8452b9a234 | |||
| ccf512b8ad | |||
| c1832de01f | |||
| e6c44737ac | |||
| f42e6773aa | |||
| fdc05ee4a0 | |||
| 12e107448b | |||
| c461bba5a3 | |||
| e38906714e | |||
| cfeae73c91 | |||
| 5865a62a94 | |||
| c64e079da4 | |||
| 7f3fb26651 | |||
| fca5adbbd3 | |||
| 254870c9ab | |||
| fb6721e348 | |||
| 6d9e4ad2fa | |||
| 6f5781564c | |||
| 20711e125b | |||
| d197787ab8 | |||
| 158428e63e | |||
| 8a4f2f2851 | |||
| 9978c87b67 | |||
| 1e7c84a6c1 | |||
| 9ca67f0095 | |||
| 1429b5977b | |||
| 4660a154d0 | |||
| fec4990506 | |||
| 380d126b59 | |||
| 6dff90f7e2 | |||
| 9056b551ab | |||
| 035e8b3e5d | |||
| 3b0574bd46 | |||
| 2b2733679c | |||
| 864c83172b | |||
| 8a68cf207a | |||
| db82175b61 | |||
| 2ad60a40a8 | |||
| b98e843a9d | |||
| 365a649776 | |||
| 0ecf72b4c3 | |||
| 93ef3c3bda | |||
| 19aa66568f | |||
| d176d38552 | |||
| 623cd842a6 | |||
| 77f21d8e89 | |||
| eedd5d8f27 | |||
| 36390e107b | |||
| 7821cfe131 | |||
| a08e9e0e79 | |||
| 06ed6dd1e7 | |||
| 90013c7451 | |||
| bb221326eb | |||
| 08e3a9bddb | |||
| abc9b4cdb7 | |||
| c46a4cb3ef | |||
| 4d10ee6036 | |||
| cb20a2bc76 | |||
| 77c5923782 | |||
| ffe1783aa3 | |||
| bb27413e97 | |||
| 86d3f75d12 | |||
| 3110df8974 | |||
| 78c91500d6 | |||
| 631303bffe | |||
| efa60d09fc | |||
| 2eff8d6db4 | |||
| 7fcea64020 | |||
| 8f3b2474d2 | |||
| 0c2b807447 | |||
| e0b7a7f4b1 | |||
| bdb7657e22 | |||
| 99af57fbc6 | |||
| 66d1d3e9c3 | |||
| a5831cf365 | |||
| f726c9495e | |||
| e98f84d9bc | |||
| 59afc892f4 | |||
| 1641425fff | |||
| fb0d3f7f40 | |||
| 271732ed0b | |||
| e59dd2cab2 | |||
| af5d25b575 | |||
| f6bb5385cf | |||
| c0a3a0d7d2 | |||
| ae58e59d67 | |||
| 2c435da5d7 | |||
| 1e1aeb2673 | |||
| c5982a3ee1 | |||
| e42b0d20f8 | |||
| f52cfc3df0 | |||
| 3aff266699 | |||
| f3144a9c8f | |||
| d482796dc3 | |||
| f2831c2b88 | |||
| b958c0ad6c | |||
| b1ee817c78 | |||
| 76422ff42b | |||
| 4d3788cca6 | |||
| 528f5e755b | |||
| 65179b2358 | |||
| bb5eabb75e | |||
| b8f04bef41 | |||
| eec1251db6 | |||
| ace114be0b | |||
| 75d344c9df | |||
| d05f1ce67f | |||
| 715aa8d845 | |||
| a10f08a775 | |||
| 35b687b4ea | |||
| f0a2438dea | |||
| 770c4de78b | |||
| c2f12eedfc | |||
| c69eaf7777 | |||
| 5ee46cfc30 | |||
| 33f2f960b9 | |||
| d2eefd5768 | |||
| 280f7ff8c0 | |||
| 7afea39f1d | |||
| f865ed877d | |||
| 465660db07 | |||
| 9293ffdb26 | |||
| d572f4a113 | |||
| a8f52a5adf | |||
| b6f0d00137 | |||
| 8ba91edeae | |||
| 5081b2e9d1 | |||
| 9a90d26e81 | |||
| a0301bbc13 | |||
| 8ca2ed6781 | |||
| b0750506cf | |||
| 44443c4a37 | |||
| 2d6a897a66 | |||
| 32380d29c8 | |||
| d801f132d1 | |||
| 45369c1ec4 | |||
| 52810b97f4 | |||
| 813395c987 | |||
| 04e0266a4e | |||
| ae69a4f11a | |||
| 25eb5fb449 | |||
| 9d6452aaaf | |||
| 43b9378144 | |||
| 7bcb01964f | |||
| e06ab94fb6 | |||
| e393e4df1a | |||
| b679a1339a | |||
| e843164f60 | |||
| 8dccac4cda | |||
| 41a8bfa097 | |||
| ba8c737543 | |||
| 9043440c8d | |||
| f6f7ac19fd | |||
| bcb60d8ab7 | |||
| 73d13254de | |||
| d5f7a72d19 | |||
| d4f193ae69 | |||
| 265b410a87 | |||
| d9b9b8c9b2 | |||
| 9b050e8e21 | |||
| b0cd46f783 | |||
| 3c7d55c793 | |||
| 4e71d002ea | |||
| cd92d14ad2 | |||
| db64d4a12b | |||
| b289ceccad | |||
| f973052d16 | |||
| 16e07a8213 | |||
| 5064db2aad | |||
| cf20581603 | |||
| e78b84cd41 | |||
| 7d2856e9b7 | |||
| 1fb0e8b32b | |||
| 83a7581a34 | |||
| 8cd0433ab6 | |||
| 2f03ce5f06 | |||
| 3a9189a994 | |||
| c7519f338e | |||
| 11dcffb95d | |||
| 746c5805fb | |||
| 85a9a08b43 | |||
| 089468393e | |||
| e99f51e6ac | |||
| 8b83b42eed | |||
| 66579f659f | |||
| f51c0c7c9a | |||
| 9ecbe09de5 | |||
| 2684bbae69 | |||
| 59ef5ac390 | |||
| c00d2a5b50 | |||
| e23d8abef3 | |||
| fc3c711a7e | |||
| d358b35876 | |||
| fd6af822cd | |||
| 50cc371100 | |||
| c94231365f | |||
| b982c788fe | |||
| 67c75b1cce | |||
| fd873ff7c1 | |||
| c17db4bfb1 | |||
| a18791ef16 | |||
| 9892e51e1d | |||
| bf936cd0d7 | |||
| 5ffcf98487 | |||
| 8e7a376407 | |||
| 6e9bb4945c | |||
| 45846bf696 | |||
| 7ca0f263ba | |||
| 22319ffccf | |||
| 68673de641 | |||
| 50eae43ca2 | |||
| 5d6db58d76 | |||
| d513d0e1b3 | |||
| 32dd5eecb7 | |||
| ecb2f246b6 | |||
| 18ea20f1df | |||
| 30c54cb7ce | |||
| 65f9b6a242 | |||
| abdfd17482 | |||
| fdc0f16b08 | |||
| 36db4ae327 | |||
| 689f6c90e7 | |||
| d8f5471b3e | |||
| 9404b21f82 | |||
| bb456687a6 | |||
| 660af6e018 | |||
| d7cf0f4f27 | |||
| 4b17b1aae7 | |||
| 604abfea96 | |||
| ad73ca018a | |||
| bfc3918b2d | |||
| d73e72097b | |||
| a202e16116 | |||
| 5588f8f1d5 | |||
| 561a528bef | |||
| 7299642cf5 | |||
| 3f44ecb068 | |||
| 4f7ed4da53 | |||
| 08b07ff729 | |||
| f119a44442 | |||
| b27dc3496c | |||
| 4a2453410a | |||
| 51885623c8 | |||
| ad462649b0 | |||
| b6ba428404 | |||
| 06574c19ae | |||
| 7ca2da3723 | |||
| 6ebccf097f | |||
| 7cf3c264ab | |||
| ced0f6efb1 | |||
| 5991c8f994 | |||
| b5046fa6dc | |||
| 1cd5b0ba1d | |||
| d03100d60a | |||
| 529b67d728 | |||
| b05e682545 | |||
| e97e41c162 | |||
| a5fca59a69 | |||
| 5d9620881b | |||
| cdbb72bec4 | |||
| 7e46e40eeb | |||
| 5ffbd55f58 | |||
| cef02f367b | |||
| ddcf13678d | |||
| e4be9852a3 | |||
| 29c7031bb2 | |||
| f66e26d4cc | |||
| d5a9ae32f2 | |||
| 47c9d63210 | |||
| e56fbf820a | |||
| 3d9a5a72b6 | |||
| 2db4b33fe2 | |||
| a940c1b4fe | |||
| d437e126ab | |||
| 62e0b7f669 | |||
| eaa56ff555 | |||
| e8414c88ba | |||
| 0416ddbf3f | |||
| 4ebd486426 | |||
| f844a54a87 | |||
| 0caf72a78f | |||
| bd6e8894b6 | |||
| cbed76fea9 | |||
| fd1f9c39b0 | |||
| 8bc05c1373 | |||
| cad1cfb66a | |||
| f80feb6734 | |||
| ad7fdd1d3f | |||
| bf6e5fafea | |||
| f5ea891dd7 | |||
| 1814f7cbec | |||
| 6abefecedf | |||
| 07b6e8efbd | |||
| e7897cef8b | |||
| 66bd690b66 | |||
| 7fba4b72bf | |||
| aefeeb8f6f | |||
| 3f294416ea | |||
| 9f4c75a75c | |||
| bd79bc232b | |||
| ab991789f0 | |||
| b4340091c4 | |||
| 811a1ce6cb | |||
| ed5be0c2ac | |||
| 02f1ed75f3 | |||
| 16ed1f107f | |||
| 37282e4316 | |||
| 93baba1568 | |||
| afbf6c8ccd | |||
| 5d24cc45a3 | |||
| 08d6c07d19 | |||
| 3123a78bfe | |||
| aafa661f7a | |||
| 5c045c0b42 | |||
| 59a58d8f76 | |||
| 644c081812 | |||
| a68fa96b52 | |||
| 20097099e1 | |||
| a6a2b51970 | |||
| f2c72cad4a | |||
| d1822be2a9 | |||
| b34a26724f | |||
| dd2e2a9bca | |||
| 3acbe92363 | |||
| 72f2aa5c2f | |||
| 5d7905b566 | |||
| 6d822142ad | |||
| 745857e515 | |||
| 8419697542 | |||
| c725284acb | |||
| ccabd24db2 | |||
| 265d3fdbdc | |||
| 89e25e3504 | |||
| 45b31f1eae | |||
| d6718e9f10 | |||
| ef2248b523 | |||
| b32734b9f5 | |||
| 9e27b4f232 | |||
| 54dfad1d5b | |||
| 11607ac41e | |||
| ed60ccdf5d | |||
| 050ba1606e | |||
| a1ea208113 | |||
| 83bc08e810 | |||
| 36e87fe71f | |||
| 2c90de7375 | |||
| 00b1aa1222 | |||
| 5dfa1b3445 | |||
| a40d0c96af | |||
| 96d17f8fcb | |||
| 1cebec39f7 | |||
| 5c4c1f5362 | |||
| 92051b88ec | |||
| 6cd80407c0 | |||
| c27238548d | |||
| b4b2ca1790 | |||
| a5a9f907cd | |||
| 97255be8ed | |||
| e631c2e3d3 | |||
| dbd36c2fb4 | |||
| 31ba269d24 | |||
| 41c5c2a99b | |||
| eb8fc41c01 | |||
| e4683115bd | |||
| 503823aa2e | |||
| 1f41129669 | |||
| 6c4830c47a | |||
| cbe04512ee | |||
| 7b12756b93 | |||
| 1bbafaa97d | |||
| ca9d4f8353 | |||
| 75d70d5e87 | |||
| 22a796deae | |||
| e190c6c7db | |||
| f8d9d92e49 | |||
| f33fb45b89 | |||
| 859c79010c | |||
| bc993bb7e2 | |||
| 7ef91639e8 | |||
| e5e4f40867 | |||
| 67edf4e822 | |||
| 9a360a3009 | |||
| f92718c391 | |||
| f17437b419 | |||
| 468709c645 | |||
| 389246b180 | |||
| 5cb087e650 | |||
| c802217513 | |||
| ec14e36be3 | |||
| 590f25aeff | |||
| fba79bdd21 | |||
| a45210db9e | |||
| 1af3f6f485 | |||
| 46692d4bbb | |||
| 32ae084d86 | |||
| 5fb1efa8bb | |||
| 924fc26eaa | |||
| c96f2f5eb4 | |||
| 63c59af390 | |||
| 0ad9858379 | |||
| 0b49b73152 | |||
| 62909dedfa | |||
| 50e69c2fa5 | |||
| 2e472450c5 | |||
| 975c469e1a | |||
| e72405c754 | |||
| 2b384330fc | |||
| 8a0204a279 | |||
| b7bc1a1a12 | |||
| 4d6deffd88 | |||
| efb2f3fee5 | |||
| d70a117270 | |||
| 1a0df48ac3 | |||
| 753df0b48c | |||
| be5b932beb | |||
| b1729eed27 | |||
| cb6021602e | |||
| 1cdb124fd4 | |||
| 3011f77e88 | |||
| c882b162d2 | |||
| dc24541f61 | |||
| 89ed9e2b52 | |||
| 925f82301a | |||
| b759be8e7e | |||
| da8a8aec4e | |||
| b70e176011 | |||
| 4c97434cca | |||
| 4a9af7e7bd | |||
| 30f3825f1d | |||
| 3767542bac | |||
| 22fb505af9 | |||
| f76fe37269 | |||
| acc6ce644e | |||
| 8f094e9a90 | |||
| a63dec24b8 | |||
| c8be29e02c | |||
| fc157dd709 | |||
| af409dc0c9 | |||
| 13bad429bc | |||
| 89ff743c5d | |||
| c3fa00b5c6 | |||
| 60c93fd2ee | |||
| b7b183d2ca | |||
| 1721c2bbb2 | |||
| 24374efdce | |||
| 5a867ea061 | |||
| c2c7078957 | |||
| a3818322a6 | |||
| 4473129599 | |||
| 23f0e90a7b | |||
| 0fb28af3c7 | |||
| 65983ade46 | |||
| 244d3f6067 | |||
| 8956752e85 | |||
| 039cb57167 | |||
| ac6a64efab | |||
| fa75cc91a0 | |||
| ee0df41dec | |||
| 0ad006eea1 | |||
| fd5553a0ad | |||
| 74e049b7b4 | |||
| 80f809b8b2 | |||
| bbee1e85ac | |||
| bd0438be83 | |||
| 4f8574ef50 | |||
| de941d8b9e | |||
| a1e052b070 | |||
| f7f4affe51 | |||
| cf652482c5 | |||
| bec43ff8ed | |||
| 49888f30f0 | |||
| b3a3975461 | |||
| 93a14293aa | |||
| a6ecf107b3 | |||
| 56958236e2 | |||
| 2c4537bd02 | |||
| cfb8a851e6 | |||
| 6fffa86fc2 | |||
| 0beccd69e8 | |||
| 5c2b12bf96 | |||
| 4b4364d2a5 | |||
| 3c82ce828b | |||
| 2a8725ec8d | |||
| 936ca85bab | |||
| a81acb3dbe | |||
| db8d51b931 | |||
| fa2723a54d | |||
| 8e1f28b11a | |||
| 08e46c5af8 | |||
| 2663c46b7e | |||
| d4f223c894 | |||
| d521b2aae4 | |||
| dc8bfce62e | |||
| cfb4f1a5e1 | |||
| f840af4652 | |||
| 726558c0a0 | |||
| 8dab758d78 | |||
| e8c294f434 | |||
| be9b7f3629 | |||
| e18456d18e | |||
| 31c379cdfd | |||
| f084cd7a23 | |||
| 0b7f80ed05 | |||
| c9ffb84ffc | |||
| bbd2bfda5d | |||
| 1add44ba2f | |||
| a8474af78d | |||
| 99ab1b179b | |||
| db1fc0f6be | |||
| 0d4877a77e | |||
| f5168bfcb6 | |||
| 5364d0e4f7 | |||
| 1591b72e4a | |||
| 57327ab0fe | |||
| c1a4670de2 | |||
| 2c548a4296 | |||
| aadb38370c | |||
| 643b197d02 | |||
| e03800cf64 | |||
| 810ea2ce3b | |||
| b6dcd19d04 | |||
| 2b451c012b | |||
| 194b470cf2 | |||
| b70dbf5f10 | |||
| a2f2fb816b | |||
| 31e782a36f | |||
| eeb41de46c | |||
| 9111ada32b | |||
| 56b11e9ab7 | |||
| 403614d337 | |||
| aa951b0bc0 | |||
| ef1170c770 | |||
| 33feaff817 | |||
| 2087ea72f8 | |||
| 2cd548ae11 | |||
| bef0739471 | |||
| 1e24608e2b | |||
| 938b4b2c69 | |||
| 135a3c2426 | |||
| 9ac800ce9b | |||
| 108e0a8c78 | |||
| 3404335b21 | |||
| e6b6ed60a9 | |||
| 51cb27f747 | |||
| 8ab8c0046b | |||
| f86b85b3de | |||
| ee56903530 | |||
| 81446091d3 | |||
| c26685b1de | |||
| 0fba075f52 | |||
| 2c113018d2 | |||
| 9ddf4d2b2f | |||
| fdadca431f | |||
| 098098ab2d | |||
| 7c98b37e4f | |||
| 18990c5de7 | |||
| bf22194182 | |||
| 35bd46faea | |||
| 6aadba9a3a | |||
| 209cfc131a | |||
| 2b66c6fa89 | |||
| 7d749f16c5 | |||
| c940b72776 | |||
| 751e5ca543 | |||
| 31d06277e1 | |||
| 1380696977 | |||
| 368c7c3cea | |||
| 1099258d07 | |||
| e024f896a6 | |||
| aaf652b17c | |||
| a6a3a144d2 | |||
| 5e8f912aa9 | |||
| 2b0d4b2c32 | |||
| c9563ffb63 | |||
| 7f13abf771 | |||
| 3f274e65ac | |||
| 0d7769c884 | |||
| 57a2580a5d | |||
| 771ff14462 | |||
| 834a684d6f | |||
| b172020f5b | |||
| 5ad78a77a1 | |||
| 1dfde18b7d | |||
| 1136804c32 | |||
| 3a6e607383 | |||
| b759580466 | |||
| 23e5df38a3 | |||
| 2046b5c58a | |||
| c334da9907 | |||
| 18802fef96 | |||
| 31e139da29 | |||
| 189a1aa0ac | |||
| f98be82f96 | |||
| be29519f58 | |||
| 3448adb776 | |||
| 8b9dc8cde8 | |||
| e85a899be0 | |||
| 6be83d0050 | |||
| 5fe64bfdeb | |||
| 9af8fc137f | |||
| a0b69f2884 | |||
| e5161a1f4f | |||
| 92947e3360 | |||
| 42abc57b65 | |||
| a3d6f9f07d | |||
| db9754c43e | |||
| 5a38388c62 | |||
| 138fc54392 | |||
| 47a34dc76b | |||
| e119d6d582 | |||
| 3089681246 | |||
| 4079948b98 | |||
| cfba132074 | |||
| 1a2632909d | |||
| cd86de26de | |||
| e8aa344976 | |||
| c136422197 | |||
| 9322ae2e5c | |||
| 3a3fcc482f | |||
| aef313a8b9 | |||
| 47e26fd62f | |||
| b870f6b930 | |||
| 18e23618ea | |||
| 3cd8865a50 | |||
| 198bcc6a3a | |||
| cbe086d835 | |||
| 7966a8dadf | |||
| fc75ae4b63 | |||
| 782926de0f | |||
| 46fdd3306c | |||
| 05d134dd9d | |||
| 38a026e1dd | |||
| c36ca265a5 | |||
| 440438ef33 | |||
| 9cea880569 | |||
| 3cf64401ef | |||
| 363e3327ba | |||
| 8ecf9a2597 | |||
| f942fb086a | |||
| 694a54e331 | |||
| 2a90777d26 | |||
| 5b6db69dd6 | |||
| 96282c4390 | |||
| 9cbbaff170 | |||
| 8fdb2490c6 | |||
| 832f1ec571 | |||
| a2eba2faf8 | |||
| 3758bdb338 | |||
| e35f1ae3fd | |||
| 0d3dfe4b57 | |||
| 4b0ee5b8ab | |||
| 89203e8794 | |||
| a8533235c1 | |||
| 5938e6f632 | |||
| 72fa76fde6 | |||
| 0ff0efe39c | |||
| c58c3d2174 | |||
| 2553c3ba1d | |||
| 5070b51e48 | |||
| 9503ca2129 | |||
| d00ffdd0f1 | |||
| ba2b7fa1f9 | |||
| a5abe19854 | |||
| 442ddc1a7b | |||
| f04d5f6202 | |||
| 66c75342d4 | |||
| 5ea497068c | |||
| 02712868f9 | |||
| fb2c9ac7df | |||
| 1a79012c61 | |||
| 37f1cd2992 | |||
| 7bd910bb44 | |||
| 5402893c16 | |||
| d4e8ca51a4 | |||
| a759ec4e1c | |||
| 4e93bd0307 | |||
| 8ede97a278 | |||
| 4ea6474cb6 | |||
| a76a9bed3b | |||
| b78d489dfd | |||
| 54224557f0 | |||
| 6d036e972d | |||
| 80203c17c1 | |||
| 536fb27941 | |||
| 02a3434e58 | |||
| a6556f3890 | |||
| 0b80403a4b | |||
| a29f12549a | |||
| 60625da067 | |||
| cf4f7cdd28 | |||
| e9e6539cd0 | |||
| 262f278bb4 | |||
| 24b369882a | |||
| 7ab8012e0e | |||
| a582cc57ac | |||
| 58f23d3b47 | |||
| bca1a77ae3 | |||
| 5434771d77 | |||
| 21e705443e | |||
| a2ab9d2877 | |||
| 6c1b27bac9 | |||
| ce1418cfdb | |||
| 9b8eac6b54 | |||
| ede03f61bf | |||
| f765fdf531 | |||
| ce94bd1b6a | |||
| ea41ee4241 | |||
| ce9993071c | |||
| a6e3f20dc8 | |||
| 20a62d1fdc | |||
| 20e517a7ca | |||
| dbe2a922e3 | |||
| a24825f676 | |||
| 837a2a06fa | |||
| 85d3bc4444 | |||
| 3fb84b1f7f | |||
| e030fb1282 | |||
| 0ea7d156a5 | |||
| 7c8f9db6d9 | |||
| e4093622d5 | |||
| 8b439d8917 | |||
| 16d96ae7ee | |||
| 675c4c3a07 | |||
| 1bc23257c7 | |||
| aad4584aa7 | |||
| 530339c494 | |||
| 440d9c6907 | |||
| 7de5f54da2 | |||
| 929d3836fd | |||
| b80294181c | |||
| 82713ed19a | |||
| bd60ea451f | |||
| 113024ba83 | |||
| a88119c3c0 | |||
| ca2c6ee470 | |||
| e6cc014634 | |||
| 9d425cd737 | |||
| 43eb4739f8 | |||
| ed8bcd8823 | |||
| 5b8023740d | |||
| e0c6d92608 | |||
| 2641c79d65 | |||
| 76abad2487 | |||
| ba8df6e7fe | |||
| b11050a231 | |||
| fce4709d63 | |||
| bb67c8bb1f | |||
| 7adb95fbf8 | |||
| 524d69e6f8 | |||
| 0cf12bc3be | |||
| e7e55e7f95 | |||
| 67be162f20 | |||
| c8a4da6d8b | |||
| 0e74fc78e5 | |||
| ddb434662b | |||
| 4fb7d7a7db | |||
| 086ba7c35a | |||
| ac8314931f | |||
| 1929b6dc8e | |||
| 705a797971 | |||
| e87f731fb5 | |||
| 9fcf041c06 | |||
| b82f577420 | |||
| 513ae09951 | |||
| d011abf918 | |||
| f7349e6346 | |||
| 40c0d12004 | |||
| 927487c60f | |||
| 042a4d1c24 | |||
| fb26e74bcb | |||
| 711e4b47db | |||
| 4130aa7d27 | |||
| ecbb72390a | |||
| 3e39fc7b13 | |||
| f366b9aca6 | |||
| fa8104480b | |||
| e2d44e9766 | |||
| e1d62c7c5a | |||
| 8815846fe0 | |||
| c96bef3d3c | |||
| 6b23854918 | |||
| b66f1b7e93 | |||
| 382a75e4c6 | |||
| cfc8cc30b6 | |||
| 1261d5033e | |||
| 61979a4a58 | |||
| 0cc04bd2e4 | |||
| 8c67c425e9 | |||
| c0c5f23bbc | |||
| b79fc0306c | |||
| 2571b3f532 | |||
| d26a95253e | |||
| c4035b75be | |||
| 65b5ec93c7 | |||
| 0f8a48ed0d | |||
| 44b5b25c0d | |||
| b65b677839 | |||
| 249ee0b55f | |||
| e1ee645e87 | |||
| 2ad293221c | |||
| b1b6e837ba | |||
| d0c180f8fc | |||
| fcdf2dc7f6 | |||
| bece489ee9 | |||
| 1287fbebd7 | |||
| 0e0bcd4617 | |||
| b0ed1e75b6 | |||
| 1adfc4e89f | |||
| e57bf529cd | |||
| 9816314749 | |||
| 0ab39c2c50 | |||
| 4715b02a45 | |||
| a32edd387e | |||
| 26a5dbd91c | |||
| cd177a3fcd | |||
| 2475a64bdc | |||
| 912422bca5 | |||
| 2da3d80a14 | |||
| 14f7542a21 | |||
| 80661d4f86 | |||
| 715496079f | |||
| 7db6df769b | |||
| d9b4de45e6 | |||
| d0c5b92fb9 | |||
| e6344a704d | |||
| 671d2463b8 | |||
| 2b393708b7 | |||
| e33b4fad58 | |||
| 8b725202b3 | |||
| 29fb9a5268 | |||
| a0ed714199 | |||
| a287b4524b | |||
| 08b9014ee2 | |||
| 23b6199684 | |||
| c5b5cf4d2c | |||
| c9f1a80dc2 | |||
| 5479d66a66 | |||
| 73deae7ece | |||
| 314a6c67b6 | |||
| 931b276d60 | |||
| 1ce47424a6 | |||
| 69cd0fc447 | |||
| 68b233f4ef | |||
| 611f819373 | |||
| b14df306ce | |||
| 9385961fb8 | |||
| b9fe3da1e3 | |||
| 5af7ede329 | |||
| 4626ab2bb2 | |||
| 853622e05e | |||
| 27ca14d208 | |||
| 4fa53bb4fc | |||
| e0b883cc3e | |||
| 4df645a025 | |||
| f76da801fe | |||
| 8533310f29 | |||
| 2b98b8dada | |||
| 01cb51d6b4 | |||
| e66606170b | |||
| 889cff1888 | |||
| e7e08cda59 | |||
| c8bbdbc472 | |||
| 8d91007a89 | |||
| e8d899d4d0 | |||
| f5137ada13 | |||
| edec446aed | |||
| 50221e938b | |||
| 705a659d19 | |||
| 2b778dcf6e | |||
| 8355ea842c | |||
| 625e2992a5 | |||
| 7b97b4060c | |||
| 18f6f2ee9a | |||
| 84f28f6411 | |||
| 11087142af | |||
| 97442f3c11 | |||
| 502c50f155 | |||
| aa06056fac | |||
| e9e7b74c1b | |||
| a78b56c450 | |||
| f15ca250c1 | |||
| c695a4879c | |||
| 6dc3b7ff92 | |||
| 8b84939ec5 | |||
| f1a980144e | |||
| b39cbac179 | |||
| e6569235a4 | |||
| c803b1e711 | |||
| 9cb1547136 | |||
| 57c6e31d59 | |||
| 54fbf89a67 | |||
| fba67168d9 | |||
| 3cc45e37fc | |||
| 2bdae7d96a | |||
| e5f757e1ea | |||
| 455bcdde4d | |||
| 8daa1702d0 | |||
| 9a68f80ffa | |||
| 3b764ca70e | |||
| 15ac370409 | |||
| c00b43f99f | |||
| f13b8cb160 | |||
| a2948d8967 | |||
| a2d48303ca | |||
| e7e6f4d0b5 | |||
| 6680e9adec | |||
| e85af8750a | |||
| b2c1ad628e | |||
| 9fdf7bd6a4 | |||
| b022f23d7a | |||
| 9c9a615556 | |||
| 0cd2f12f48 | |||
| f31c8feb13 | |||
| 7ec5c04130 | |||
| c985273dc6 | |||
| 2499675687 | |||
| b9e116e17f | |||
| 0f7eed736b | |||
| bddbfbcc45 | |||
| c1a8aca3fb | |||
| 006b6c00d9 | |||
| 13f10fc0f0 | |||
| 9d6b4e07da | |||
| f6cb404ada | |||
| 6c2d86a6b3 | |||
| 6deecbc1d4 | |||
| 1cff3051d6 | |||
| f7c36a9700 | |||
| db1e3cbab1 | |||
| 2339eda157 | |||
| c3c581fcaa | |||
| a35fe507f2 | |||
| aae1efe81c | |||
| d6807be932 | |||
| 6c26a90068 | |||
| bd80b83881 | |||
| 18356feab7 | |||
| d5eee84c87 | |||
| ee6a4c366e | |||
| 543243239a | |||
| 23af7648e4 | |||
| b91dde50af | |||
| 9ce4795266 | |||
| ce553c2c0e | |||
| 6030d21e37 | |||
| 39594baa46 | |||
| 64ec9bdfe4 | |||
| 9116cf83df | |||
| 51686c4aa4 | |||
| 3e66c2d0f3 | |||
| 7fe8838999 | |||
| 5dbf255c5f | |||
| de07c95257 | |||
| 84fd898bbe | |||
| b827edf2ef | |||
| d90067a11d | |||
| af5654e99f | |||
| 6619989cf2 | |||
| 18366c147f | |||
| 3c877197b4 | |||
| f13912a027 | |||
| 9d51815661 | |||
| 116bea5f97 | |||
| 88bb486dc8 | |||
| 2d962ce9a8 | |||
| dc903de212 | |||
| b05049dedf | |||
| 8031625af7 | |||
| bce69c77c7 | |||
| 96ca6db76d | |||
| 5d16a04ea6 | |||
| 846677caa1 | |||
| 826f54e90d | |||
| 3876b07bc3 | |||
| aae8429629 | |||
| 6d382e467a | |||
| 1b70a5eb16 | |||
| f93cca8a13 | |||
| 0af7924be9 | |||
| b7e3b5d887 | |||
| b3723f7dc6 | |||
| 6ec3f91e0f | |||
| 6758d8daf3 | |||
| c0f5f55c63 | |||
| c57772aa9d | |||
| b99fe14214 | |||
| aa07895a97 | |||
| f3d0fd5313 | |||
| de2ead426f | |||
| efcfd375c0 | |||
| de61b46ccb | |||
| bbe0641503 | |||
| 2be5e9063b | |||
| 2221b4d32c | |||
| b4e3221711 | |||
| c13acd0152 | |||
| c3323da840 | |||
| e56a3a591b | |||
| ee42d9f142 | |||
| 3a6d0ef468 | |||
| 6df4806b82 | |||
| c94ec8e6b2 | |||
| 8096e91e55 | |||
| 706fd5cab8 | |||
| 4a87cf5c8b | |||
| 3d6e7970ac | |||
| 6223320d7d | |||
| 0a331061c3 | |||
| 47a8149968 | |||
| a765a190cd | |||
| e83dfdc5d8 | |||
| 8464f0107c | |||
| f3e0f14c44 | |||
| cae673bd72 | |||
| e3e70e7316 | |||
| db173152c3 | |||
| 38efbb0d21 | |||
| ca91f528bb | |||
| 5f87d3a99f | |||
| 418ac5da0c | |||
| e38a0bdac7 | |||
| 009bc60c41 | |||
| 24de218132 | |||
| e5779ff05c | |||
| 46067c6811 | |||
| 8ff520e7ec | |||
| 98647d11f3 | |||
| 8d93f44509 | |||
| e3cf6b37e8 | |||
| afa2697e4e | |||
| 1d39710d3f | |||
| db0f077c3b | |||
| a6098a007a | |||
| ddf6181271 | |||
| 9a1e0633a1 | |||
| 7469d8264f | |||
| 0ae52eafdc | |||
| cba6471099 | |||
| aa7df0a74c | |||
| 5506f6eae9 | |||
| 4285dce730 | |||
| 6e34831183 | |||
| 352d389cc4 | |||
| 94ef226b92 | |||
| e02875890b | |||
| 84ae535756 | |||
| 1fe568150f | |||
| 41e82f0693 | |||
| dc0a15e63b | |||
| 3404eacf4b | |||
| ceac2931ab | |||
| bc8b01a6f6 | |||
| a2722478ba | |||
| 3284e41545 | |||
| 4a44d78389 | |||
| 1beb723cf3 | |||
| 730488f3c5 | |||
| 059e5c6761 | |||
| bcfb4e169e | |||
| 1aa721dac8 | |||
| f0c1f0adff | |||
| 7d71d9d507 | |||
| 17935cdc13 | |||
| 34268147e6 | |||
| 1e5079cd61 | |||
| fef0cc366a | |||
| d08d0a8914 | |||
| f2b523545b | |||
| f72e480e91 | |||
| f7a73edaea | |||
| 54514522d4 | |||
| f21f670d21 | |||
| 891da6c522 | |||
| 5543efac7a | |||
| b605cf33dd | |||
| 5de8414b64 | |||
| f24cd8fa35 | |||
| 1f5ea7b983 | |||
| 160a55d859 | |||
| c0eff65377 | |||
| c596cd21f6 | |||
| 36e3171391 | |||
| cc69a4bac4 | |||
| ab9b17a188 | |||
| 9b0d769598 | |||
| 4548000077 | |||
| ad9452e656 | |||
| a34d65f342 | |||
| 7f9dc76698 | |||
| f2a9641a95 | |||
| 0ef575b082 | |||
| 3b8595748c | |||
| 5e8f4048da | |||
| f52ba7ad2b | |||
| af4ecc55ce | |||
| 6f6b3876fb | |||
| ffb7872a79 | |||
| 6ab4c3e786 | |||
| bbd472f478 | |||
| dc53953766 | |||
| 35e4626fbc | |||
| a9c40bc86d | |||
| 5e9a6bf965 | |||
| 98547ba837 | |||
| e691fdcc59 | |||
| 63c844cdeb | |||
| e1359116b8 | |||
| e34a2494dd | |||
| 3c10a074da | |||
| ebb831d345 | |||
| 72ccce04c6 | |||
| 8dd82baf26 | |||
| 84f031573e | |||
| 49b3107adb | |||
| 700b126a9e | |||
| e612abad23 | |||
| 6513ee82da | |||
| 3c38bb3a72 | |||
| 042c8dd461 | |||
| 41ffd0ac80 | |||
| 806230ff52 | |||
| 13f68dacfb | |||
| 8e9f8bf173 | |||
| 2459bf2464 | |||
| 54fdf043c9 | |||
| 5dc0dc133d | |||
| 99b9de9d5c | |||
| dfc0a53a5f | |||
| 0728557b04 | |||
| 0faf9287ba | |||
| 9678751156 | |||
| 073e35381c | |||
| eca98598cf | |||
| 9164ad2471 | |||
| f9a8138264 | |||
| 9c6aff249f | |||
| 5b84100c52 | |||
| 19ba832938 | |||
| e66c942790 | |||
| ab334c7a94 | |||
| 138854dcbc | |||
| bc6d963800 | |||
| 9d7262382f | |||
| 74fe9f44dd | |||
| 11ff4bbfaf | |||
| b5a9e01455 | |||
| 3096107d6b | |||
| 905d26570f | |||
| b879489d43 | |||
| 9a82d40ddc | |||
| 6ae0e5f84d | |||
| d44a1dce8d | |||
| 6d2469fe4c | |||
| 17289cef29 | |||
| 3677873a05 | |||
| 3b2628fbd7 | |||
| 63e29d18fb | |||
| 6b457c7780 | |||
| 02151a93f6 | |||
| d8d812cf47 | |||
| bef66ac40a | |||
| 75bf5028fd | |||
| 2d540c16bc | |||
| 8bf8c200d7 | |||
| 7f779476cc | |||
| f798b912a6 | |||
| 873cb24642 | |||
| 1d9b543f57 | |||
| 1bb4bf8372 | |||
| 4db72d941e | |||
| d6914a4ea2 | |||
| c62b9f8d4b | |||
| 586c349f1f | |||
| f5d47335e8 | |||
| f7c7313087 | |||
| 199bc99fb5 | |||
| 7c217dc25c | |||
| 62ef2fc421 | |||
| ff8cee9dde | |||
| c273669003 | |||
| ab81e79eec | |||
| ab04d1e10d | |||
| 9f0deefec4 | |||
| a139c29a2b | |||
| 248ff831ab | |||
| 28bd79234d | |||
| 7b8582124d | |||
| 08a2cb7234 | |||
| eff8d1e4c3 | |||
| 8a18c395fe | |||
| dae2857391 | |||
| d77e4f14bf | |||
| 06fc4baf4e | |||
| c29041aa9e | |||
| d9ed5434ac | |||
| 3bb67a9a4f | |||
| 742ec413f3 | |||
| ad63ae699f | |||
| ff93725b28 | |||
| 5c88c6a5a6 | |||
| 15990bf8d1 | |||
| 91cef5700a | |||
| 179be02e0d | |||
| 2e466abf71 |
@@ -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 |
Binary file not shown.
|
Before Width: | Height: | Size: 66 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,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.
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
});
|
||||
@@ -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 };
|
||||
@@ -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 don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m 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 don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m not sure.”. \nThanks!",
|
||||
"Feels like": "Feels like"
|
||||
}
|
||||
@@ -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 don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m 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 don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m not sure.”. \nIf user talks to you in Persian and you can also respond in Persian, definitely respond in Persian. Thanks!"
|
||||
}
|
||||
@@ -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 don’t have enough information to provide a confident answer, simply say “I don’t 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"
|
||||
}
|
||||
|
||||
@@ -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 don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m 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!"
|
||||
}
|
||||
@@ -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 don’t have enough information to provide a confident answer, simply say “I don’t know” or “I’m not sure.”. \nThanks!": "你是 Wayland Linux 桌面侧边栏上的助手。除非有其他要求或提供建议,否则请始终保持轻松的语气回答问题。这是你回答用户查询的步骤:\n1. 如果是写作或语法相关的问题,或者引号中的句子,请指出错误并在必要时进行更正,使用下划线,并在适当的地方使写作更自然,不要进行太大更改。如果你给出的句子在引号中但语法正确,请简要解释不常见概念。\n2. 如果是关于系统任务的问题,请给出bash命令,并在代码块中简要说明。\n3. 否则,在总结信息或解释概念时,你应该使用项目符号和标题。对于数学表达式,你必须在代码块中使用 LaTeX,并将语言设置为\"latex\"。\n注意:使用轻松的语言,简洁,同时确保回答的事实正确性。如果你不确定或没有足够的信息来提供自信的答案,只需说“我不知道”或“我不确定”。\n谢谢!",
|
||||
"Feels like": "体感温度"
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
let processedBody = body;
|
||||
if (appEntry?.toLowerCase().includes('chrome')) {
|
||||
processedBody = body.split('\n\n').slice(1).join('\n\n');
|
||||
}
|
||||
processedBody = processedBody.replace(/<[^>]*>/g, '');
|
||||
return processedBody;
|
||||
}
|
||||
|
||||
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,291 +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"
|
||||
],
|
||||
"ignoredAppsRegex": [],
|
||||
"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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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: '&' },
|
||||
{ name: 'LESSTHAN', re: /</g, sub: '<' },
|
||||
{ name: 'GREATERTHAN', re: />/g, sub: '>' },
|
||||
],
|
||||
'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)
|
||||
\`\`\`
|
||||
`;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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 } = 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.playBackStatus == 'Playing');
|
||||
label.toggleClassName('bar-music-playstate', 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' })),
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -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(),
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
]];
|
||||
@@ -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),
|
||||
});
|
||||
};
|
||||
@@ -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
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,315 +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);
|
||||
const ignoredAppsRegex = userOptions.dock.ignoredAppsRegex || [];
|
||||
let isIgnored = false;
|
||||
|
||||
for (const regex of ignoredAppsRegex) {
|
||||
try {
|
||||
const pattern = new RegExp(regex);
|
||||
if (pattern.test(appClass)) {
|
||||
isIgnored = true;
|
||||
break;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (isIgnored) continue;
|
||||
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,142 +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, iconSetup,
|
||||
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', setup: iconSetup}),
|
||||
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',
|
||||
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 = Audio.speaker?.stream?.isMuted
|
||||
? 0
|
||||
: 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?.stream?.isMuted
|
||||
? 0
|
||||
: Audio.speaker?.volume;
|
||||
if (!isNaN(updateValue)) {
|
||||
if (updateValue > 1) progress.value = 1;
|
||||
else progress.value = updateValue;
|
||||
}
|
||||
}),
|
||||
iconSetup: (self) => self.hook(Audio, (progress) => {
|
||||
self.label =
|
||||
Audio.speaker?.stream?.isMuted || !Audio.speaker.volume
|
||||
? 'volume_off'
|
||||
: 'volume_up';
|
||||
}),
|
||||
});
|
||||
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,
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -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,418 +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(() => {
|
||||
const dominantColor = `#${Utils.exec(`sh -c "magick '${coverPath}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo"`)}`
|
||||
// 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(`cp '${App.configDir}/scripts/templates/wal/_musicwal.scss' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`);
|
||||
exec(`sed -i 's/{{dominantColor}}/${dominantColor}/g' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`)
|
||||
exec(`sed -i 's/{{backgroundColor}}/${darkMode.value ? "#0E1415" : "#EEF4F4"}/g' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`)
|
||||
exec(`sed -i 's/{{foregroundColor}}/${darkMode.value ? "#EEF4F4" : "#0E1415"}/g' '${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;
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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,146 +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] == '>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);
|
||||
}
|
||||
}
|
||||
})
|
||||
,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -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());
|
||||
}))
|
||||
}
|
||||
})]
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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' }),
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
,
|
||||
});
|
||||
}
|
||||
@@ -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."
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
})
|
||||
});
|
||||
@@ -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
Reference in New Issue
Block a user