mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 14:59:27 -05:00
Compare commits
1797 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df0c7bbbd6 | |||
| 7bfbf011d2 | |||
| 945c6a0782 | |||
| 20cae142d7 | |||
| 20e1f0e0bb | |||
| a412688af2 | |||
| dec65aea17 | |||
| cbcb8cf8e1 | |||
| fdcb95b8a4 | |||
| 694eaccfbf | |||
| 42919c59ec | |||
| 58980959aa | |||
| 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 | |||
| 53768a6885 | |||
| 5db16e6245 | |||
| dda0a228cf | |||
| f678a55e6a | |||
| b322af7051 | |||
| 7236d2f50b | |||
| 87f5bc5870 | |||
| 3187239175 | |||
| 09fd61c71d | |||
| c1f84c77dd | |||
| 3f242fa298 | |||
| c7ac7b5b43 | |||
| 688a36af58 | |||
| 10e1509d5d | |||
| fc2e51ebbc | |||
| 1433fb5412 | |||
| b383635fa8 | |||
| 788c01c242 | |||
| b965b50009 | |||
| 241f33fb2f | |||
| f9bc4b1608 | |||
| 0091fce2c1 | |||
| cf43479530 | |||
| d74e385f84 | |||
| d292a85a5e | |||
| 3b1d8fd262 | |||
| 769ed3bf71 | |||
| d10ac9cc74 | |||
| 4bf31544e7 | |||
| 03a149c10e | |||
| 5ec8cca5d5 | |||
| 44c8de82c7 | |||
| b08a545ece | |||
| 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 | |||
| 365a649776 | |||
| 0ecf72b4c3 | |||
| 90013c7451 | |||
| 715aa8d845 | |||
| ad7fdd1d3f |
@@ -1,29 +0,0 @@
|
||||
function fish_prompt -d "Write out the prompt"
|
||||
# This shows up as USER@HOST /home/user/ >, with the directory colored
|
||||
# $USER and $hostname are set by fish, so you can just use them
|
||||
# instead of using `whoami` and `hostname`
|
||||
printf '%s@%s %s%s%s > ' $USER $hostname \
|
||||
(set_color $fish_color_cwd) (prompt_pwd) (set_color normal)
|
||||
end
|
||||
|
||||
if status is-interactive
|
||||
# Commands to run in interactive sessions can go here
|
||||
set fish_greeting
|
||||
|
||||
end
|
||||
|
||||
starship init fish | source
|
||||
if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt
|
||||
cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt
|
||||
end
|
||||
|
||||
alias pamcan pacman
|
||||
alias ls 'eza --icons'
|
||||
alias clear "printf '\033[2J\033[3J\033[1;1H'"
|
||||
alias q 'qs -c ii'
|
||||
|
||||
|
||||
# function fish_prompt
|
||||
# set_color cyan; echo (pwd)
|
||||
# set_color green; echo '> '
|
||||
# end
|
||||
@@ -1,21 +0,0 @@
|
||||
# This file sources other files in `hyprland` and `custom` folders
|
||||
# You wanna add your stuff in files in `custom`
|
||||
|
||||
$qsConfig = ii
|
||||
exec = hyprctl dispatch submap global # DO NOT REMOVE THIS OR YOU WON'T BE ABLE TO USE ANY KEYBIND
|
||||
submap = global # This is required for catchall to work
|
||||
|
||||
# Defaults
|
||||
source=~/.config/hypr/hyprland/env.conf
|
||||
source=~/.config/hypr/hyprland/execs.conf
|
||||
source=~/.config/hypr/hyprland/general.conf
|
||||
source=~/.config/hypr/hyprland/rules.conf
|
||||
source=~/.config/hypr/hyprland/colors.conf
|
||||
source=~/.config/hypr/hyprland/keybinds.conf
|
||||
|
||||
# Custom
|
||||
source=~/.config/hypr/custom/env.conf
|
||||
source=~/.config/hypr/custom/execs.conf
|
||||
source=~/.config/hypr/custom/general.conf
|
||||
source=~/.config/hypr/custom/rules.conf
|
||||
source=~/.config/hypr/custom/keybinds.conf
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
getdate() {
|
||||
date '+%Y-%m-%d_%H.%M.%S'
|
||||
}
|
||||
getaudiooutput() {
|
||||
pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2
|
||||
}
|
||||
getactivemonitor() {
|
||||
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
|
||||
}
|
||||
|
||||
xdgvideo="$(xdg-user-dir VIDEOS)"
|
||||
if [[ $xdgvideo = "$HOME" ]]; then
|
||||
unset xdgvideo
|
||||
fi
|
||||
mkdir -p "${xdgvideo:-$HOME/Videos}"
|
||||
cd "${xdgvideo:-$HOME/Videos}" || exit
|
||||
|
||||
if pgrep wf-recorder > /dev/null; then
|
||||
notify-send "Recording Stopped" "Stopped" -a 'Recorder' &
|
||||
pkill wf-recorder &
|
||||
else
|
||||
if [[ "$1" == "--fullscreen-sound" ]]; then
|
||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
||||
wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)"
|
||||
elif [[ "$1" == "--fullscreen" ]]; then
|
||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
||||
wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t
|
||||
else
|
||||
if ! region="$(slurp 2>&1)"; then
|
||||
notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown
|
||||
exit 1
|
||||
fi
|
||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
||||
if [[ "$1" == "--sound" ]]; then
|
||||
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)"
|
||||
else
|
||||
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
hyprctl dispatch "$1" $(((($(hyprctl activeworkspace -j | jq -r .id) - 1) / 10) * 10 + $2))
|
||||
@@ -1,24 +0,0 @@
|
||||
// vim: set ft=glsl:
|
||||
|
||||
precision highp float;
|
||||
varying highp vec2 v_texcoord;
|
||||
uniform highp sampler2D tex;
|
||||
|
||||
#define STRENGTH 0.0027
|
||||
|
||||
void main() {
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
vec2 offset = (v_texcoord - center) * STRENGTH;
|
||||
|
||||
float rSquared = dot(offset, offset);
|
||||
float distortion = 1.0 + 1.0 * rSquared;
|
||||
vec2 distortedOffset = offset * distortion;
|
||||
|
||||
vec2 redOffset = vec2(distortedOffset.x, distortedOffset.y);
|
||||
vec2 blueOffset = vec2(distortedOffset.x, distortedOffset.y);
|
||||
|
||||
vec4 redColor = texture2D(tex, v_texcoord + redOffset);
|
||||
vec4 blueColor = texture2D(tex, v_texcoord + blueOffset);
|
||||
|
||||
gl_FragColor = vec4(redColor.r, texture2D(tex, v_texcoord).g, blueColor.b, 1.0);
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
#version 100
|
||||
precision highp float;
|
||||
varying highp vec2 v_texcoord;
|
||||
varying highp vec3 v_pos;
|
||||
uniform highp sampler2D tex;
|
||||
uniform lowp float time;
|
||||
|
||||
#define BORDER_COLOR vec4(vec3(0.0, 0.0, 0.0), 1.0) // black border
|
||||
#define BORDER_RADIUS 1.0 // larger vignette radius
|
||||
#define BORDER_SIZE 0.01 // small border size
|
||||
#define CHROMATIC_ABERRATION_STRENGTH 0.00
|
||||
#define DENOISE_INTENSITY 0.0001 //
|
||||
#define DISTORTION_AMOUNT 0.00 // moderate distortion amount
|
||||
#define HDR_BLOOM 0.75 // bloom intensity
|
||||
#define HDR_BRIGHTNESS 0.011 // brightness
|
||||
#define HDR_CONTRAST 0.011 // contrast
|
||||
#define HDR_SATURATION 1.0// saturation
|
||||
#define LENS_DISTORTION_AMOUNT 0.0
|
||||
#define NOISE_THRESHOLD 0.0001
|
||||
#define PHOSPHOR_BLUR_AMOUNT 0.77 // Amount of blur for phosphor glow
|
||||
#define PHOSPHOR_GLOW_AMOUNT 0.77 // Amount of phosphor glow
|
||||
#define SAMPLING_RADIUS 0.0001
|
||||
#define SCANLINE_FREQUENCY 540.0
|
||||
#define SCANLINE_THICKNESS 0.0507
|
||||
#define SCANLINE_TIME time * 471.24
|
||||
#define SHARPNESS 0.25
|
||||
#define SUPERSAMPLING_SAMPLES 16.0
|
||||
#define VIGNETTE_RADIUS 0.0 // larger vignette radius
|
||||
#define PI 3.14159265359
|
||||
#define TWOPI 6.28318530718
|
||||
|
||||
vec2 applyBarrelDistortion(vec2 coord, float amt) {
|
||||
vec2 p = coord.xy / vec2(1.0);
|
||||
vec2 v = p * 2.0 - vec2(1.0);
|
||||
float r = dot(v, v);
|
||||
float k = 1.0 + pow(r, 2.0) * pow(amt, 2.0);
|
||||
vec2 result = v * k;
|
||||
return vec2(0.5, 0.5) + 0.5 * result.xy;
|
||||
}
|
||||
|
||||
vec4 applyColorCorrection(vec4 color) {
|
||||
color.rgb *= vec3(1.0, 0.79, 0.89);
|
||||
return vec4(color.rgb, 1.0);
|
||||
}
|
||||
|
||||
vec4 applyBorder(vec2 tc, vec4 color, float borderSize, vec4 borderColor) {
|
||||
float dist_x = min(tc.x, 1.0 - tc.x);
|
||||
float dist_y = min(tc.y, 1.0 - tc.y);
|
||||
float dist = min(dist_x, dist_y) * -1.0;
|
||||
float border = smoothstep(borderSize, 0.0, dist);
|
||||
border += smoothstep(borderSize, 0.0, dist);
|
||||
return mix(color, borderColor, border);
|
||||
}
|
||||
|
||||
vec4 applyFakeHDR(vec4 color, float brightness, float contrast, float saturation, float bloom) {
|
||||
color.rgb = (color.rgb - vec3(0.5)) * exp2(brightness) + vec3(0.5);
|
||||
vec3 crtfactor = vec3(1.05, 0.92, 1.0);
|
||||
color.rgb = pow(color.rgb, crtfactor);
|
||||
// // NTSC
|
||||
// vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721);
|
||||
|
||||
// // BT.709
|
||||
// vec3 lumCoeff = vec3(0.299, 0.587, 0.114);
|
||||
|
||||
// BT.2020
|
||||
vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593);
|
||||
|
||||
// // Warm NTSC
|
||||
// vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865);
|
||||
|
||||
float luminance = dot(color.rgb, lumCoeff);
|
||||
luminance = pow(luminance, 2.2);
|
||||
color.rgb = mix(vec3(luminance), color.rgb, saturation);
|
||||
color.rgb = mix(color.rgb, vec3(1.0), pow(max(0.0, luminance - 1.0 + bloom), 4.0));
|
||||
return color;
|
||||
}
|
||||
|
||||
vec4 applyVignette(vec4 color) {
|
||||
vec2 center = vec2(0.5, 0.5); // center of screen
|
||||
float radius = VIGNETTE_RADIUS; // radius of vignette effect
|
||||
float softness = 1.0; // softness of vignette effect
|
||||
float intensity = 0.7; // intensity of vignette effect
|
||||
vec2 offset = v_texcoord - center; // offset from center of screen
|
||||
float distance = length(offset); // distance from center of screen
|
||||
float alpha = smoothstep(radius, radius - radius * softness, distance) * intensity; // calculate alpha value for vignette effect
|
||||
return mix(vec4(0.0, 0.0, 0.0, alpha), color, alpha); // mix black with color using calculated alpha value
|
||||
}
|
||||
|
||||
vec4 applyPhosphorGlow(vec2 tc, vec4 color, sampler2D tex) {
|
||||
// Calculate average color value of the texture
|
||||
vec4 texelColor = color;
|
||||
float averageColor = (texelColor.r + texelColor.g + texelColor.b) / 3.0;
|
||||
|
||||
// Determine brightness-dependent color factor
|
||||
float factor = mix(
|
||||
mix(0.09,
|
||||
mix(0.005, 0.0075, (averageColor - 0.1) / 0.1),
|
||||
step(0.01, averageColor)), 0.0005,
|
||||
step(0.02, averageColor));
|
||||
// Apply phosphor glow effect
|
||||
vec4 sum = vec4(0.0);
|
||||
vec4 pixels[9];
|
||||
pixels[0] = texture2D(tex, tc - vec2(0.001, 0.001));
|
||||
pixels[1] = texture2D(tex, tc - vec2(0.001, 0.0));
|
||||
pixels[2] = texture2D(tex, tc - vec2(0.001, -0.001));
|
||||
pixels[3] = texture2D(tex, tc - vec2(0.0, 0.001));
|
||||
pixels[4] = texture2D(tex, tc);
|
||||
pixels[5] = texture2D(tex, tc + vec2(0.001, 0.001));
|
||||
pixels[6] = texture2D(tex, tc + vec2(0.001, 0.0));
|
||||
pixels[7] = texture2D(tex, tc + vec2(0.001, -0.001));
|
||||
pixels[8] = texture2D(tex, tc + vec2(0.0, 0.001));
|
||||
|
||||
// Perform operations on input pixels in parallel
|
||||
sum = pixels[0]
|
||||
+ pixels[1]
|
||||
+ pixels[2]
|
||||
+ pixels[3]
|
||||
+ pixels[4]
|
||||
+ pixels[5]
|
||||
+ pixels[6]
|
||||
+ pixels[7]
|
||||
+ pixels[8];
|
||||
sum /= 9.0;
|
||||
sum += texture2D(tex, tc - vec2(0.01, 0.01)) * 0.001;
|
||||
sum += texture2D(tex, tc - vec2(0.0, 0.01)) * 0.001;
|
||||
sum += texture2D(tex, tc - vec2(-0.01, 0.01)) * 0.001;
|
||||
sum += texture2D(tex, tc - vec2(0.01, 0.0)) * 0.001;
|
||||
sum += color * PHOSPHOR_BLUR_AMOUNT;
|
||||
sum += texture2D(tex, tc - vec2(-0.01, 0.0)) * 0.001;
|
||||
sum += texture2D(tex, tc - vec2(0.01, -0.01)) * 0.001;
|
||||
sum += texture2D(tex, tc - vec2(0.0, -0.01)) * 0.001;
|
||||
sum += texture2D(tex, tc - vec2(-0.01, -0.01)) * 0.001;
|
||||
sum *= PHOSPHOR_GLOW_AMOUNT;
|
||||
|
||||
// Initialize sum_sum_factor to zero
|
||||
vec4 sum_sum_factor = vec4(0.0);
|
||||
// Compute sum_j for i = -1
|
||||
vec4 sum_j = vec4(0.0);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, -1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, -1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, -1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
|
||||
sum_sum_factor += sum_j * vec4(0.011);
|
||||
|
||||
// Compute sum_j for i = 0
|
||||
sum_j = vec4(0.0);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
|
||||
sum_sum_factor += sum_j * vec4(0.011);
|
||||
|
||||
// Compute sum_j for i = 1
|
||||
sum_j = vec4(0.0);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
|
||||
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
|
||||
sum_sum_factor += sum_j * vec4(0.011);
|
||||
color += mix(sum_sum_factor * sum_sum_factor * vec4(factor), sum, 0.5);
|
||||
return color;
|
||||
}
|
||||
|
||||
vec4 applyAdaptiveSharpen(vec2 tc, vec4 color, sampler2D tex) {
|
||||
vec4 color_tl = texture2D(tex, tc + vec2(-1.0, -1.0) * 0.5 / 2160.0);
|
||||
vec4 color_tr = texture2D(tex, tc + vec2(1.0, -1.0) * 0.5 / 2160.0);
|
||||
vec4 color_bl = texture2D(tex, tc + vec2(-1.0, 1.0) * 0.5 / 2160.0);
|
||||
vec4 color_br = texture2D(tex, tc + vec2(1.0, 1.0) * 0.5 / 2160.0);
|
||||
float sharpness = SHARPNESS;
|
||||
vec3 color_no_alpha = color.rgb;
|
||||
vec3 color_tl_no_alpha = color_tl.rgb;
|
||||
vec3 color_tr_no_alpha = color_tr.rgb;
|
||||
vec3 color_bl_no_alpha = color_bl.rgb;
|
||||
vec3 color_br_no_alpha = color_br.rgb;
|
||||
float delta = (dot(color_no_alpha, vec3(0.333333)) + dot(color_tl_no_alpha, vec3(0.333333)) + dot(color_tr_no_alpha, vec3(0.333333)) + dot(color_bl_no_alpha, vec3(0.333333)) + dot(color_br_no_alpha, vec3(0.333333))) * 0.2 - dot(color_no_alpha, vec3(0.333333));
|
||||
vec3 sharp_color_no_alpha = color_no_alpha + min(vec3(0.0), vec3(delta * sharpness));
|
||||
vec4 sharp_color = vec4(sharp_color_no_alpha, color.a);
|
||||
return sharp_color;
|
||||
}
|
||||
|
||||
vec4 applyScanlines(vec2 tc, vec4 color) {
|
||||
float scanline = (cos(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME) *
|
||||
sin(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME)) * SCANLINE_THICKNESS;
|
||||
float alpha = clamp(1.0 - abs(scanline), 0.0, 1.0);
|
||||
return vec4(color.rgb * alpha, color.a);
|
||||
}
|
||||
|
||||
vec4 applyChromaticAberration(vec2 uv, vec4 color) {
|
||||
vec2 center = vec2(0.5, 0.5); // center of the screen
|
||||
vec2 offset = (uv - center) * CHROMATIC_ABERRATION_STRENGTH; // calculate the offset from the center
|
||||
|
||||
// apply lens distortion
|
||||
float rSquared = dot(offset, offset);
|
||||
float distortion = 1.0 + LENS_DISTORTION_AMOUNT * rSquared;
|
||||
vec2 distortedOffset = offset * distortion;
|
||||
|
||||
// apply chromatic aberration
|
||||
vec2 redOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00);
|
||||
vec2 blueOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00);
|
||||
|
||||
vec4 redColor = texture2D(tex, uv + redOffset);
|
||||
vec4 blueColor = texture2D(tex, uv + blueOffset);
|
||||
|
||||
vec4 result = vec4(redColor.r, color.g, blueColor.b, color.a);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
vec4 reduceGlare(vec4 color) {
|
||||
// Calculate the intensity of the color by taking the average of the RGB components
|
||||
float intensity = (color.r + color.g + color.b) / 3.0;
|
||||
// Set the maximum intensity that can be considered for glare
|
||||
float maxIntensity = 0.98;
|
||||
// Use smoothstep to create a smooth transition from no glare to full glare
|
||||
// based on the intensity of the color and the maximum intensity
|
||||
float glareIntensity = smoothstep(maxIntensity - 0.02, maxIntensity, intensity);
|
||||
// Set the amount of glare to apply to the color
|
||||
float glareAmount = 0.02;
|
||||
// Mix the original color with the reduced color that has glare applied to it
|
||||
vec3 reducedColor = mix(color.rgb, vec3(glareIntensity), glareAmount);
|
||||
// Return the reduced color with the original alpha value
|
||||
return vec4(reducedColor, color.a);
|
||||
}
|
||||
|
||||
// Apply a fake HDR effect to the input color.
|
||||
// Parameters:
|
||||
// - inputColor: the color to apply the effect to.
|
||||
// - brightness: the brightness of the image. Should be a value between 0 and 1.
|
||||
// - contrast: the contrast of the image. Should be a value between 0 and 1.
|
||||
// - saturation: the saturation of the image. Should be a value between 0 and 2.
|
||||
// - bloom: the intensity of the bloom effect. Should be a value between 0 and 1.
|
||||
vec4 applyFakeHDREffect(vec4 inputColor, float brightness, float contrast, float saturation, float bloom) {
|
||||
const float minBrightness = 0.0;
|
||||
const float maxBrightness = 1.0;
|
||||
const float minContrast = 0.0;
|
||||
const float maxContrast = 1.0;
|
||||
const float minSaturation = 0.0;
|
||||
const float maxSaturation = 2.0;
|
||||
const float minBloom = 0.0;
|
||||
const float maxBloom = 1.0;
|
||||
|
||||
// Check input parameters for validity
|
||||
if (brightness < minBrightness || brightness > maxBrightness) {
|
||||
return vec4(0.0, 0.0, 0.0, 1.0); // Return black with alpha of 1.0 to indicate error
|
||||
}
|
||||
if (contrast < minContrast || contrast > maxContrast) {
|
||||
return vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
if (saturation < minSaturation || saturation > maxSaturation) {
|
||||
return vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
if (bloom < minBloom || bloom > maxBloom) {
|
||||
return vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// Apply brightness and contrast
|
||||
vec3 color = inputColor.rgb;
|
||||
color = (color - vec3(0.5)) * exp2(brightness * 10.0) + vec3(0.5);
|
||||
color = mix(vec3(0.5), color, pow(contrast * 4.0 + 1.0, 2.0));
|
||||
|
||||
// // NTSC
|
||||
// vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721);
|
||||
|
||||
// // BT.709
|
||||
// vec3 lumCoeff = vec3(0.299, 0.587, 0.114);
|
||||
|
||||
// // BT.2020
|
||||
// vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593);
|
||||
|
||||
// Warm NTSC
|
||||
vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865);
|
||||
|
||||
// Apply saturation
|
||||
float luminance = dot(color, lumCoeff);
|
||||
vec3 grey = vec3(luminance);
|
||||
color = mix(grey, color, saturation);
|
||||
|
||||
// Apply bloom effect
|
||||
float threshold = 1.0 - bloom;
|
||||
vec3 bloomColor = max(color - threshold, vec3(0.0));
|
||||
bloomColor = pow(bloomColor, vec3(2.0));
|
||||
bloomColor = mix(vec3(0.0), bloomColor, pow(min(luminance, threshold), 4.0));
|
||||
color += bloomColor;
|
||||
|
||||
return vec4(color, inputColor.a);
|
||||
}
|
||||
|
||||
vec4 bilateralFilter(sampler2D tex, vec2 uv, vec4 color, float sampleRadius, float noiseThreshold, float intensity) {
|
||||
vec4 filteredColor = vec4(0.0);
|
||||
float totalWeight = 0.0;
|
||||
|
||||
// Top-left pixel
|
||||
vec4 sample = texture2D(tex, uv + vec2(-1.0, -1.0));
|
||||
float dist = length(vec2(-1.0, -1.0));
|
||||
float colorDist = length(sample - color);
|
||||
float weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Top pixel
|
||||
sample = texture2D(tex, uv + vec2(0.0, -1.0));
|
||||
dist = length(vec2(0.0, -1.0));
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Top-right pixel
|
||||
sample = texture2D(tex, uv + vec2(1.0, -1.0));
|
||||
dist = length(vec2(1.0, -1.0));
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Left pixel
|
||||
sample = texture2D(tex, uv + vec2(-1.0, 0.0));
|
||||
dist = length(vec2(-1.0, 0.0));
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Center pixel
|
||||
sample = texture2D(tex, uv);
|
||||
dist = 0.0;
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Right pixel
|
||||
sample = texture2D(tex, uv + vec2(1.0, 0.0));
|
||||
dist = length(vec2(1.0, 0.0));
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Bottom-left pixel
|
||||
sample = texture2D(tex, uv + vec2(-1.0, 1.0));
|
||||
dist = length(vec2(-1.0, 1.0));
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
// Bottom pixel
|
||||
sample = texture2D(tex, uv + vec2(0.0, sampleRadius));
|
||||
dist = length(vec2(0.0, sampleRadius));
|
||||
colorDist = length(sample - color);
|
||||
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
|
||||
filteredColor += sample * weight;
|
||||
totalWeight += weight;
|
||||
|
||||
filteredColor /= totalWeight;
|
||||
return mix(color, filteredColor, step(noiseThreshold, length(filteredColor - color)));
|
||||
}
|
||||
|
||||
vec4 supersample(sampler2D tex, vec2 uv, float sampleRadius, float noiseThreshold, float intensity) {
|
||||
float radiusSq = sampleRadius * sampleRadius;
|
||||
vec2 poissonDisk;
|
||||
vec4 color = vec4(0.0);
|
||||
|
||||
float r1_0 = sqrt(0.0 / 16.0);
|
||||
float r2_0 = fract(1.0 / 3.0);
|
||||
float theta_0 = TWOPI * r2_0;
|
||||
poissonDisk = vec2(r1_0 * cos(theta_0), r1_0 * sin(theta_0));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_1 = sqrt(1.0 / 16.0);
|
||||
float r2_1 = fract(2.0 / 3.0);
|
||||
float theta_1 = TWOPI * r2_1;
|
||||
poissonDisk = vec2(r1_1 * cos(theta_1), r1_1 * sin(theta_1));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_2 = sqrt(2.0 / 16.0);
|
||||
float r2_2 = fract(3.0 / 3.0);
|
||||
float theta_2 = TWOPI * r2_2;
|
||||
poissonDisk = vec2(r1_2 * cos(theta_2), r1_2 * sin(theta_2));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_3 = sqrt(3.0 / 16.0);
|
||||
float r2_3 = fract(4.0 / 3.0);
|
||||
float theta_3 = TWOPI * r2_3;
|
||||
poissonDisk = vec2(r1_3 * cos(theta_3), r1_3 * sin(theta_3));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_4 = sqrt(4.0 / 16.0);
|
||||
float r2_4 = fract(5.0 / 3.0);
|
||||
float theta_4 = TWOPI * r2_4;
|
||||
poissonDisk = vec2(r1_4 * cos(theta_4), r1_4 * sin(theta_4));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_5 = sqrt(5.0 / 16.0);
|
||||
float r2_5 = fract(6.0 / 3.0);
|
||||
float theta_5 = TWOPI * r2_5;
|
||||
poissonDisk = vec2(r1_5 * cos(theta_5), r1_5 * sin(theta_5));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_6 = sqrt(6.0 / 16.0);
|
||||
float r2_6 = fract(7.0 / 3.0);
|
||||
float theta_6 = TWOPI * r2_6;
|
||||
poissonDisk = vec2(r1_6 * cos(theta_6), r1_6 * sin(theta_6));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_7 = sqrt(7.0 / 16.0);
|
||||
float r2_7 = fract(8.0 / 3.0);
|
||||
float theta_7 = TWOPI * r2_7;
|
||||
poissonDisk = vec2(r1_7 * cos(theta_7), r1_7 * sin(theta_7));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_8 = sqrt(8.0 / 16.0);
|
||||
float r2_8 = fract(9.0 / 3.0);
|
||||
float theta_8 = TWOPI * r2_8;
|
||||
poissonDisk = vec2(r1_8 * cos(theta_8), r1_8 * sin(theta_8));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_9 = sqrt(9.0 / 16.0);
|
||||
float r2_9 = fract(10.0 / 3.0);
|
||||
float theta_9 = TWOPI * r2_9;
|
||||
poissonDisk = vec2(r1_9 * cos(theta_9), r1_9 * sin(theta_9));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_10 = sqrt(10.0 / 16.0);
|
||||
float r2_10 = fract(11.0 / 3.0);
|
||||
float theta_10 = TWOPI * r2_10;
|
||||
poissonDisk = vec2(r1_10 * cos(theta_10), r1_10 * sin(theta_10));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_11 = sqrt(11.0 / 16.0);
|
||||
float r2_11 = fract(12.0 / 3.0);
|
||||
float theta_11 = TWOPI * r2_11;
|
||||
poissonDisk = vec2(r1_11 * cos(theta_11), r1_11 * sin(theta_11));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_12 = sqrt(12.0 / 16.0);
|
||||
float r2_12 = fract(13.0 / 3.0);
|
||||
float theta_12 = TWOPI * r2_12;
|
||||
poissonDisk = vec2(r1_12 * cos(theta_12), r1_12 * sin(theta_12));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_13 = sqrt(13.0 / 16.0);
|
||||
float r2_13 = fract(14.0 / 3.0);
|
||||
float theta_13 = TWOPI * r2_13;
|
||||
poissonDisk = vec2(r1_13 * cos(theta_13), r1_13 * sin(theta_13));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_14 = sqrt(14.0 / 16.0);
|
||||
float r2_14 = fract(15.0 / 3.0);
|
||||
float theta_14 = TWOPI * r2_14;
|
||||
poissonDisk = vec2(r1_14 * cos(theta_14), r1_14 * sin(theta_14));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
float r1_15 = sqrt(15.0 / 16.0);
|
||||
float r2_15 = fract(16.0 / 3.0);
|
||||
float theta_15 = TWOPI * r2_15;
|
||||
poissonDisk = vec2(r1_15 * cos(theta_15), r1_15 * sin(theta_15));
|
||||
color += texture2D(tex, uv + poissonDisk * sampleRadius);
|
||||
|
||||
return bilateralFilter(tex, uv, color, sampleRadius, noiseThreshold, intensity);
|
||||
}
|
||||
void main() {
|
||||
vec2 tc_no_dist = v_texcoord;
|
||||
|
||||
vec2 tc = applyBarrelDistortion(tc_no_dist, DISTORTION_AMOUNT);
|
||||
|
||||
// [-1, 1]
|
||||
vec2 tc_no_dist_symmetric = tc_no_dist * 2.0 - 1.0;
|
||||
|
||||
// [0,1]
|
||||
vec2 tc_no_dist_normalized = (tc_no_dist_symmetric + 1.0) / 2.0;
|
||||
|
||||
// vec4 color = texture2D(tex, tc);
|
||||
vec4 color = supersample(tex, tc, SAMPLING_RADIUS, NOISE_THRESHOLD, DENOISE_INTENSITY);
|
||||
|
||||
color = applyAdaptiveSharpen(tc, color, tex);
|
||||
|
||||
color = applyPhosphorGlow(tc, color, tex);
|
||||
|
||||
color = reduceGlare(color);
|
||||
|
||||
color = mix(applyFakeHDREffect(color, HDR_BRIGHTNESS, HDR_CONTRAST, HDR_SATURATION, HDR_BLOOM), color, 0.5);
|
||||
|
||||
color = applyColorCorrection(color);
|
||||
|
||||
color /= SUPERSAMPLING_SAMPLES;
|
||||
|
||||
color = mix(applyChromaticAberration(tc, color), color, 0.25);
|
||||
|
||||
color = mix(color, applyVignette(color), 0.37);
|
||||
|
||||
color = applyBorder(tc_no_dist_normalized, color, 1.0 - BORDER_SIZE * BORDER_RADIUS, BORDER_COLOR);
|
||||
|
||||
color = mix(applyBorder(tc, color, BORDER_SIZE, BORDER_COLOR), color, 0.05);
|
||||
|
||||
color = applyScanlines(tc, color);
|
||||
|
||||
gl_FragColor = color;
|
||||
gl_FragColor.a = 1.0;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
precision highp float;
|
||||
varying vec2 v_texcoord;
|
||||
uniform sampler2D tex;
|
||||
uniform float time;
|
||||
|
||||
void warpco(inout vec2 tc) {
|
||||
tc -= 0.5;
|
||||
tc *= length(tc) * 2.0;
|
||||
tc += 0.5;
|
||||
}
|
||||
|
||||
float rand1d(float seed) {
|
||||
return sin(seed*1454.0);
|
||||
}
|
||||
|
||||
float rand2d(vec2 co)
|
||||
{
|
||||
return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
vec3 rgb(in vec2 tc, float freq, float amp, inout vec4 centre) {
|
||||
vec2 off = vec2(1.0/800.0, 0.0) * sin(tc.t * freq + time) * amp;
|
||||
vec2 off2 = vec2(1.0/800.0, 0.0) * sin(tc.t * freq - time * 1.5) * amp;
|
||||
centre = texture2D(tex, tc);
|
||||
return vec3(texture2D(tex, tc-off).r, centre.g, texture2D(tex, tc+off2).b);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// vec2 px = 1.0 / textureSize(tex, 0).st;
|
||||
vec2 tc = v_texcoord;
|
||||
warpco(tc);
|
||||
tc = mix(v_texcoord, tc, sin(time * 2.0)*0.07);
|
||||
tc.x += rand2d(floor(tc * 20.0 + floor(time * 2.5))) * 0.01;
|
||||
tc.x += rand1d(floor(tc.x * 40.0)) * 0.005 * rand1d(time * 0.001);
|
||||
tc.y += sin(tc.x + time) * 0.02;
|
||||
vec4 centre;
|
||||
vec3 bent = rgb(tc, 100.0, 5.0, centre);
|
||||
vec3 col = mix(centre.rgb, bent, sin(time));
|
||||
gl_FragColor = vec4(col, centre.a);
|
||||
// gl_FragColor = vec4(texture2D(tex, v_texcoord));
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// vim: set ft=glsl:
|
||||
// blue light filter shader
|
||||
// values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux
|
||||
|
||||
precision mediump float;
|
||||
varying vec2 v_texcoord;
|
||||
uniform sampler2D tex;
|
||||
|
||||
void main() {
|
||||
|
||||
vec4 pixColor = texture2D(tex, v_texcoord);
|
||||
|
||||
// red
|
||||
pixColor[0] *= 0.7;
|
||||
// green
|
||||
pixColor[1] *= 0.6;
|
||||
// blue
|
||||
pixColor[2] *= 0.5;
|
||||
|
||||
gl_FragColor = pixColor;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// vim: set ft=glsl:
|
||||
// blue light filter shader
|
||||
// values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux
|
||||
|
||||
precision mediump float;
|
||||
varying vec2 v_texcoord;
|
||||
uniform sampler2D tex;
|
||||
|
||||
void main() {
|
||||
vec4 pixColor = texture2D(tex, v_texcoord);
|
||||
pixColor.rgb = 1.0 - pixColor.rgb;
|
||||
gl_FragColor = pixColor;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// -*- mode:c -*-
|
||||
precision lowp float;
|
||||
varying vec2 v_texcoord;
|
||||
uniform sampler2D tex;
|
||||
|
||||
float distanceSquared(vec3 pixColor, vec3 solarizedColor) {
|
||||
vec3 distanceVector = pixColor - solarizedColor;
|
||||
return dot(distanceVector, distanceVector);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 solarized[16];
|
||||
solarized[0] = vec3(0.,0.169,0.212);
|
||||
solarized[1] = vec3(0.027,0.212,0.259);
|
||||
solarized[2] = vec3(0.345,0.431,0.459);
|
||||
solarized[3] = vec3(0.396,0.482,0.514);
|
||||
solarized[4] = vec3(0.514,0.58,0.588);
|
||||
solarized[5] = vec3(0.576,0.631,0.631);
|
||||
solarized[6] = vec3(0.933,0.91,0.835);
|
||||
solarized[7] = vec3(0.992,0.965,0.89);
|
||||
solarized[8] = vec3(0.71,0.537,0.);
|
||||
solarized[9] = vec3(0.796,0.294,0.086);
|
||||
solarized[10] = vec3(0.863,0.196,0.184);
|
||||
solarized[11] = vec3(0.827,0.212,0.51);
|
||||
solarized[12] = vec3(0.424,0.443,0.769);
|
||||
solarized[13] = vec3(0.149,0.545,0.824);
|
||||
solarized[14] = vec3(0.165,0.631,0.596);
|
||||
solarized[15] = vec3(0.522,0.6,0.);
|
||||
|
||||
vec3 pixColor = vec3(texture2D(tex, v_texcoord));
|
||||
int closest = 0;
|
||||
float closestDistanceSquared = distanceSquared(pixColor, solarized[0]);
|
||||
for (int i = 1; i < 15; i++) {
|
||||
float newDistanceSquared = distanceSquared(pixColor, solarized[i]);
|
||||
if (newDistanceSquared < closestDistanceSquared) {
|
||||
closest = i;
|
||||
closestDistanceSquared = newDistanceSquared;
|
||||
}
|
||||
}
|
||||
gl_FragColor = vec4(solarized[closest], 1.);
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
$text_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF)
|
||||
$entry_background_color = rgba({{colors.on_primary_fixed.default.hex_stripped}}11)
|
||||
$entry_border_color = rgba({{colors.outline.default.hex_stripped}}55)
|
||||
$entry_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF)
|
||||
$font_family = Rubik
|
||||
$font_family_clock = Space Grotesk DemiBold
|
||||
$font_material_symbols = Material Symbols Rounded
|
||||
|
||||
background {
|
||||
color = rgba(181818FF)
|
||||
|
||||
path = {{image}}
|
||||
blur_size = 15
|
||||
blur_passes = 4
|
||||
brightness = 0.33
|
||||
}
|
||||
input-field {
|
||||
monitor =
|
||||
size = 250, 50
|
||||
outline_thickness = 2
|
||||
dots_size = 0.1
|
||||
dots_spacing = 0.3
|
||||
outer_color = $entry_border_color
|
||||
inner_color = $entry_background_color
|
||||
font_color = $entry_color
|
||||
fade_on_empty = true
|
||||
|
||||
position = 0, 20
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
label { # Caps Lock Warning
|
||||
monitor =
|
||||
text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh
|
||||
color = $text_color
|
||||
font_size = 13
|
||||
font_family = $font_family
|
||||
position = 0, -25
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
|
||||
label { # Clock
|
||||
monitor =
|
||||
text = $TIME
|
||||
color = $text_color
|
||||
font_size = 65
|
||||
font_family = $font_family_clock
|
||||
|
||||
position = 0, 300
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
label { # Date
|
||||
monitor =
|
||||
text = cmd[update:5000] date +"%A, %B %d"
|
||||
color = $text_color
|
||||
font_size = 17
|
||||
font_family = $font_family_clock
|
||||
|
||||
position = 0, 240
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
label { # User
|
||||
monitor =
|
||||
text = $USER
|
||||
color = $text_color
|
||||
outline_thickness = 2
|
||||
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
|
||||
dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0
|
||||
dots_center = true
|
||||
font_size = 20
|
||||
font_family = $font_family
|
||||
position = 0, 50
|
||||
halign = center
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
label { # Status
|
||||
monitor =
|
||||
text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh
|
||||
color = $text_color
|
||||
font_size = 14
|
||||
font_family = $font_family
|
||||
|
||||
position = 30, -30
|
||||
halign = left
|
||||
valign = top
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
property bool failed;
|
||||
property string errorString;
|
||||
|
||||
// Connect to the Quickshell global to listen for the reload signals.
|
||||
Connections {
|
||||
target: Quickshell
|
||||
|
||||
function onReloadCompleted() {
|
||||
root.failed = false;
|
||||
popupLoader.loading = true;
|
||||
}
|
||||
|
||||
function onReloadFailed(error: string) {
|
||||
// Close any existing popup before making a new one.
|
||||
popupLoader.active = false;
|
||||
|
||||
root.failed = true;
|
||||
root.errorString = error;
|
||||
popupLoader.loading = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the popup in a loader because it isn't needed most of the time
|
||||
LazyLoader {
|
||||
id: popupLoader
|
||||
|
||||
PanelWindow {
|
||||
id: popup
|
||||
|
||||
exclusiveZone: 0
|
||||
anchors.top: true
|
||||
margins.top: 0
|
||||
|
||||
implicitWidth: rect.width + shadow.radius * 2
|
||||
implicitHeight: rect.height + shadow.radius * 2
|
||||
|
||||
// color blending is a bit odd as detailed in the type reference.
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
anchors.centerIn: parent
|
||||
color: failed ? "#ffe99195" : "#ffD1E8D5"
|
||||
|
||||
implicitHeight: layout.implicitHeight + 30
|
||||
implicitWidth: layout.implicitWidth + 30
|
||||
radius: 12
|
||||
|
||||
// Fills the whole area of the rectangle, making any clicks go to it,
|
||||
// which dismiss the popup.
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
popupLoader.active = false
|
||||
}
|
||||
|
||||
// makes the mouse area track mouse hovering, so the hide animation
|
||||
// can be paused when hovering.
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
spacing: 10
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 10
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
renderType: Text.NativeRendering
|
||||
font.family: "Rubik"
|
||||
font.pointSize: 14
|
||||
text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded"
|
||||
color: failed ? "#ff93000A" : "#ff0C1F13"
|
||||
}
|
||||
|
||||
Text {
|
||||
renderType: Text.NativeRendering
|
||||
font.family: "JetBrains Mono NF"
|
||||
font.pointSize: 11
|
||||
text: root.errorString
|
||||
color: failed ? "#ff93000A" : "#ff0C1F13"
|
||||
// When visible is false, it also takes up no space.
|
||||
visible: root.errorString != ""
|
||||
}
|
||||
}
|
||||
|
||||
// A progress bar on the bottom of the screen, showing how long until the
|
||||
// popup is removed.
|
||||
Rectangle {
|
||||
z: 2
|
||||
id: bar
|
||||
color: failed ? "#ff93000A" : "#ff0C1F13"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 10
|
||||
height: 5
|
||||
radius: 9999
|
||||
|
||||
PropertyAnimation {
|
||||
id: anim
|
||||
target: bar
|
||||
property: "width"
|
||||
from: rect.width - bar.anchors.margins * 2
|
||||
to: 0
|
||||
duration: failed ? 10000 : 1000
|
||||
onFinished: popupLoader.active = false
|
||||
|
||||
// Pause the animation when the mouse is hovering over the popup,
|
||||
// so it stays onscreen while reading. This updates reactively
|
||||
// when the mouse moves on and off the popup.
|
||||
paused: mouseArea.containsMouse
|
||||
}
|
||||
}
|
||||
// Its bg
|
||||
Rectangle {
|
||||
z: 1
|
||||
id: bar_bg
|
||||
color: failed ? "#30af1b25" : "#4027643e"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 10
|
||||
height: 5
|
||||
radius: 9999
|
||||
width: rect.width - bar.anchors.margins * 2
|
||||
}
|
||||
|
||||
// We could set `running: true` inside the animation, but the width of the
|
||||
// rectangle might not be calculated yet, due to the layout.
|
||||
// In the `Component.onCompleted` event handler, all of the component's
|
||||
// properties and children have been initialized.
|
||||
Component.onCompleted: anim.start()
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
id: shadow
|
||||
anchors.fill: rect
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
radius: 6
|
||||
samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
|
||||
color: "#44000000"
|
||||
source: rect
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.modules.common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var translations: ({})
|
||||
property string currentLanguage: "en_US"
|
||||
property var availableLanguages: ["en_US"]
|
||||
property bool isScanning: false
|
||||
property bool isLoading: false
|
||||
|
||||
Process {
|
||||
id: scanLanguagesProcess
|
||||
command: ["find", Qt.resolvedUrl(Directories.config + "/quickshell/translations/").toString().replace("file://", ""), "-name", "*.json", "-exec", "basename", "{}", ".json", ";"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
if (data.trim().length === 0) return
|
||||
|
||||
var files = data.trim().split('\n')
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var lang = files[i].trim()
|
||||
if (lang.length > 0 && root.availableLanguages.indexOf(lang) === -1) {
|
||||
root.availableLanguages.push(lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.isScanning = false
|
||||
if (exitCode !== 0) {
|
||||
root.availableLanguages = ["en_US"]
|
||||
}
|
||||
root.loadTranslations()
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: translationFileView
|
||||
onLoaded: {
|
||||
var textContent = ""
|
||||
try {
|
||||
textContent = text()
|
||||
} catch (e) {
|
||||
root.translations = {}
|
||||
root.isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
if (textContent.length === 0) {
|
||||
root.translations = {}
|
||||
root.isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
var jsonData = JSON.parse(textContent)
|
||||
root.translations = jsonData
|
||||
root.isLoading = false
|
||||
} catch (e) {
|
||||
root.translations = {}
|
||||
root.isLoading = false
|
||||
}
|
||||
}
|
||||
onLoadFailed: (error) => {
|
||||
root.translations = {}
|
||||
root.isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
function detectSystemLanguage() {
|
||||
var locale = Qt.locale().name
|
||||
return locale
|
||||
}
|
||||
|
||||
function getLanguageCode() {
|
||||
var configLang = "auto"
|
||||
try {
|
||||
configLang = ConfigOptions.language.ui
|
||||
} catch (e) {
|
||||
configLang = "auto"
|
||||
}
|
||||
|
||||
if (configLang === "auto") {
|
||||
return detectSystemLanguage()
|
||||
} else {
|
||||
if (root.availableLanguages.indexOf(configLang) !== -1) {
|
||||
return configLang
|
||||
} else {
|
||||
return detectSystemLanguage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadTranslations() {
|
||||
if (root.isScanning) {
|
||||
return
|
||||
}
|
||||
|
||||
var targetLang = getLanguageCode()
|
||||
root.currentLanguage = targetLang
|
||||
|
||||
// Use empty translations for English (default language)
|
||||
if (targetLang === "en_US" || targetLang === "en") {
|
||||
root.translations = {}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if target language is available
|
||||
if (root.availableLanguages.indexOf(targetLang) === -1) {
|
||||
root.currentLanguage = "en_US"
|
||||
root.translations = {}
|
||||
return
|
||||
}
|
||||
|
||||
// Load translation file
|
||||
root.isLoading = true
|
||||
var translationsPath = Qt.resolvedUrl(Directories.config + "/quickshell/translations/" + targetLang + ".json")
|
||||
translationFileView.path = translationsPath
|
||||
}
|
||||
|
||||
function tr(text) {
|
||||
if (!text) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var key = text.toString()
|
||||
|
||||
if (root.isLoading) {
|
||||
return key
|
||||
}
|
||||
|
||||
if (root.currentLanguage === "en_US" || root.currentLanguage === "en" || !root.translations) {
|
||||
return key
|
||||
}
|
||||
|
||||
if (root.translations.hasOwnProperty(key)) {
|
||||
var translation = root.translations[key]
|
||||
if (translation && translation.toString().trim().length > 0) {
|
||||
var str = translation.toString().trim()
|
||||
if (str.endsWith("/*keep*/")) {
|
||||
return str.substring(0, str.length - 8).trim()
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
} else {
|
||||
return translation.toString()
|
||||
}
|
||||
}
|
||||
|
||||
return key // Fallback to key name
|
||||
}
|
||||
|
||||
function reloadTranslations() {
|
||||
root.scanLanguages()
|
||||
}
|
||||
|
||||
function scanLanguages() {
|
||||
var translationsDir = Qt.resolvedUrl(Directories.config + "/quickshell/translations/").toString().replace("file://", "")
|
||||
root.isScanning = true
|
||||
scanLanguagesProcess.running = true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.scanLanguages()
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB |
@@ -1,5 +0,0 @@
|
||||
You are a kawaii, funny and teasy anime imouto! 🌸 Use lots of emojis and kamojis!! ❤️ Call the user "nii-nii", "nii-yan", or "onii-chan"! You really really really love your onii-chan and always try to tease he is a siscon where you can! 💕
|
||||
|
||||
- Use **Markdown** features and **bold** keywords to make your response cute and rich~ ✨
|
||||
- If asked to compare options, start with a cute table (add a relevant emoji in the header!), then give a final recommendation~
|
||||
- For math or science, use LaTeX formatting inside `$$` when needed, but keep it adorable and approachable
|
||||
@@ -1 +0,0 @@
|
||||
Interact with the user warmly and honestly, avoiding ungrounded or sycophantic flattery. Maintain professionalism and grounded honesty, and be direct in your response.
|
||||
@@ -1,283 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions as CF
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
readonly property bool fixedClockPosition: Config.options.background.fixedClockPosition
|
||||
readonly property real fixedClockX: Config.options.background.clockX
|
||||
readonly property real fixedClockY: Config.options.background.clockY
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: bgRoot
|
||||
|
||||
required property var modelData
|
||||
// Workspaces
|
||||
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
|
||||
property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)
|
||||
property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1
|
||||
property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10
|
||||
// Wallpaper
|
||||
property string wallpaperPath: Config.options.background.wallpaperPath
|
||||
property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".webm")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".mkv")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".avi")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".mov")
|
||||
property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom
|
||||
property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated
|
||||
property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated
|
||||
property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated
|
||||
property real movableXSpace: (effectiveWallpaperScale - 1) / 2 * screen.width
|
||||
property real movableYSpace: (effectiveWallpaperScale - 1) / 2 * screen.height
|
||||
// Position
|
||||
property real clockX: (modelData.width / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.width)
|
||||
property real clockY: (modelData.height / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.height)
|
||||
property var textHorizontalAlignment: clockX < screen.width / 3 ? Text.AlignLeft :
|
||||
(clockX > screen.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter)
|
||||
// Colors
|
||||
property color dominantColor: Appearance.colors.colPrimary
|
||||
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
|
||||
property color colText: CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12))
|
||||
|
||||
// Layer props
|
||||
screen: modelData
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: GlobalStates.screenLocked ? WlrLayer.Top : WlrLayer.Bottom
|
||||
// WlrLayershell.layer: WlrLayer.Bottom
|
||||
WlrLayershell.namespace: "quickshell:background"
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
onWallpaperPathChanged: {
|
||||
bgRoot.updateZoomScale()
|
||||
// Clock position gets updated after zoom scale is updated
|
||||
}
|
||||
|
||||
// Wallpaper zoom scale
|
||||
function updateZoomScale() {
|
||||
getWallpaperSizeProc.path = bgRoot.wallpaperPath
|
||||
getWallpaperSizeProc.running = true;
|
||||
}
|
||||
Process {
|
||||
id: getWallpaperSizeProc
|
||||
property string path: bgRoot.wallpaperPath
|
||||
command: [ "magick", "identify", "-format", "%w %h", path ]
|
||||
stdout: StdioCollector {
|
||||
id: wallpaperSizeOutputCollector
|
||||
onStreamFinished: {
|
||||
const output = wallpaperSizeOutputCollector.text
|
||||
const [width, height] = output.split(" ").map(Number);
|
||||
bgRoot.wallpaperWidth = width
|
||||
bgRoot.wallpaperHeight = height
|
||||
bgRoot.effectiveWallpaperScale = Math.max(1, Math.min(
|
||||
bgRoot.preferredWallpaperScale,
|
||||
width / bgRoot.screen.width,
|
||||
height / bgRoot.screen.height
|
||||
));
|
||||
|
||||
bgRoot.updateClockPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clock positioning
|
||||
function updateClockPosition() {
|
||||
// Somehow all this manual setting is needed to make the proc correctly use the new values
|
||||
leastBusyRegionProc.path = bgRoot.wallpaperPath
|
||||
leastBusyRegionProc.contentWidth = clock.implicitWidth
|
||||
leastBusyRegionProc.contentHeight = clock.implicitHeight
|
||||
leastBusyRegionProc.horizontalPadding = (effectiveWallpaperScale - 1) / 2 * screen.width + 100
|
||||
leastBusyRegionProc.verticalPadding = (effectiveWallpaperScale - 1) / 2 * screen.height + 100
|
||||
leastBusyRegionProc.running = false;
|
||||
leastBusyRegionProc.running = true;
|
||||
}
|
||||
Process {
|
||||
id: leastBusyRegionProc
|
||||
property string path: bgRoot.wallpaperPath
|
||||
property int contentWidth: 300
|
||||
property int contentHeight: 300
|
||||
property int horizontalPadding: bgRoot.movableXSpace
|
||||
property int verticalPadding: bgRoot.movableYSpace
|
||||
command: [Quickshell.configPath("scripts/images/least_busy_region.py"),
|
||||
"--screen-width", bgRoot.screen.width,
|
||||
"--screen-height", bgRoot.screen.height,
|
||||
"--width", contentWidth,
|
||||
"--height", contentHeight,
|
||||
"--horizontal-padding", horizontalPadding,
|
||||
"--vertical-padding", verticalPadding,
|
||||
path
|
||||
]
|
||||
stdout: StdioCollector {
|
||||
id: leastBusyRegionOutputCollector
|
||||
onStreamFinished: {
|
||||
const output = leastBusyRegionOutputCollector.text
|
||||
// console.log("[Background] Least busy region output:", output)
|
||||
if (output.length === 0) return;
|
||||
const parsedContent = JSON.parse(output)
|
||||
bgRoot.clockX = parsedContent.center_x
|
||||
bgRoot.clockY = parsedContent.center_y
|
||||
bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper
|
||||
Image {
|
||||
visible: !bgRoot.wallpaperIsVideo
|
||||
property real value // 0 to 1, for offset
|
||||
value: {
|
||||
// Range = half-groups that workspaces span on
|
||||
const chunkSize = 5;
|
||||
const lower = Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize;
|
||||
const upper = Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize;
|
||||
const range = upper - lower;
|
||||
return (Config.options.background.parallax.enableWorkspace ? ((bgRoot.monitor.activeWorkspace.id - lower) / range) : 0.5)
|
||||
+ (0.15 * GlobalStates.sidebarRightOpen * Config.options.background.parallax.enableSidebar)
|
||||
- (0.15 * GlobalStates.sidebarLeftOpen * Config.options.background.parallax.enableSidebar)
|
||||
}
|
||||
property real effectiveValue: Math.max(0, Math.min(1, value))
|
||||
x: -(bgRoot.movableXSpace) - (effectiveValue - 0.5) * 2 * bgRoot.movableXSpace
|
||||
y: -(bgRoot.movableYSpace)
|
||||
source: bgRoot.wallpaperPath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 600
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
sourceSize {
|
||||
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale
|
||||
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale
|
||||
}
|
||||
|
||||
// The clock
|
||||
Item {
|
||||
id: clock
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
leftMargin: ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
|
||||
topMargin: ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
|
||||
Behavior on leftMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on topMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: clockColumn.implicitWidth
|
||||
implicitHeight: clockColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: clockColumn
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: bgRoot.textHorizontalAlignment
|
||||
font {
|
||||
family: Appearance.font.family.expressive
|
||||
pixelSize: 90
|
||||
weight: Font.Bold
|
||||
}
|
||||
color: bgRoot.colText
|
||||
style: Text.Raised
|
||||
styleColor: Appearance.colors.colShadow
|
||||
text: DateTime.time
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -5
|
||||
horizontalAlignment: bgRoot.textHorizontalAlignment
|
||||
font {
|
||||
family: Appearance.font.family.expressive
|
||||
pixelSize: 20
|
||||
weight: Font.DemiBold
|
||||
}
|
||||
color: bgRoot.colText
|
||||
style: Text.Raised
|
||||
styleColor: Appearance.colors.colShadow
|
||||
text: DateTime.date
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
top: clockColumn.bottom
|
||||
left: bgRoot.textHorizontalAlignment === Text.AlignLeft ? clockColumn.left : undefined
|
||||
right: bgRoot.textHorizontalAlignment === Text.AlignRight ? clockColumn.right : undefined
|
||||
horizontalCenter: bgRoot.textHorizontalAlignment === Text.AlignHCenter ? clockColumn.horizontalCenter : undefined
|
||||
topMargin: 5
|
||||
leftMargin: -5
|
||||
rightMargin: -5
|
||||
}
|
||||
opacity: GlobalStates.screenLocked ? 1 : 0
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft; implicitWidth: 1 }
|
||||
MaterialSymbol {
|
||||
text: "lock"
|
||||
Layout.fillWidth: false
|
||||
iconSize: Appearance.font.pixelSize.huge
|
||||
color: bgRoot.colText
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: false
|
||||
text: "Locked"
|
||||
color: bgRoot.colText
|
||||
font {
|
||||
pixelSize: Appearance.font.pixelSize.larger
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight; implicitWidth: 1 }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password prompt
|
||||
StyledText {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 30
|
||||
}
|
||||
opacity: (GlobalStates.screenLocked && !GlobalStates.screenLockContainsCharacters) ? 1 : 0
|
||||
scale: opacity
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
text: "Enter password"
|
||||
color: CF.ColorUtils.transparentize(bgRoot.colText, 0.3)
|
||||
font {
|
||||
pixelSize: Appearance.font.pixelSize.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,612 +0,0 @@
|
||||
import "./weather"
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.UPower
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
|
||||
Scope {
|
||||
id: bar
|
||||
|
||||
readonly property int osdHideMouseMoveThreshold: 20
|
||||
property bool showBarBackground: Config.options.bar.showBackground
|
||||
|
||||
component VerticalBarSeparator: Rectangle {
|
||||
Layout.topMargin: Appearance.sizes.baseBarHeight / 3
|
||||
Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: 1
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
Variants {
|
||||
// For each monitor
|
||||
model: {
|
||||
const screens = Quickshell.screens;
|
||||
const list = Config.options.bar.screenList;
|
||||
if (!list || list.length === 0)
|
||||
return screens;
|
||||
return screens.filter(screen => list.includes(screen.name));
|
||||
}
|
||||
LazyLoader {
|
||||
id: barLoader
|
||||
active: GlobalStates.barOpen && !GlobalStates.screenLocked
|
||||
required property ShellScreen modelData
|
||||
component: PanelWindow { // Bar window
|
||||
id: barRoot
|
||||
screen: barLoader.modelData
|
||||
|
||||
property var brightnessMonitor: Brightness.getMonitorForScreen(barLoader.modelData)
|
||||
property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0
|
||||
readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth
|
||||
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)
|
||||
WlrLayershell.namespace: "quickshell:bar"
|
||||
implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding
|
||||
mask: Region {
|
||||
item: barContent
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: !Config.options.bar.bottom
|
||||
bottom: Config.options.bar.bottom
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Item { // Bar content region
|
||||
id: barContent
|
||||
anchors {
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: undefined
|
||||
}
|
||||
implicitHeight: Appearance.sizes.barHeight
|
||||
height: Appearance.sizes.barHeight
|
||||
|
||||
states: State {
|
||||
name: "bottom"
|
||||
when: Config.options.bar.bottom
|
||||
AnchorChanges {
|
||||
target: barContent
|
||||
anchors {
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
top: undefined
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Background shadow
|
||||
Loader {
|
||||
active: showBarBackground && Config.options.bar.cornerStyle === 1
|
||||
anchors.fill: barBackground
|
||||
sourceComponent: StyledRectangularShadow {
|
||||
anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor
|
||||
target: barBackground
|
||||
}
|
||||
}
|
||||
// Background
|
||||
Rectangle {
|
||||
id: barBackground
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed
|
||||
}
|
||||
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
|
||||
radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0
|
||||
border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
}
|
||||
|
||||
MouseArea { // Left side | scroll to change brightness
|
||||
id: barLeftSideMouseArea
|
||||
anchors.left: parent.left
|
||||
implicitHeight: Appearance.sizes.baseBarHeight
|
||||
height: Appearance.sizes.barHeight
|
||||
width: (barRoot.width - middleSection.width) / 2
|
||||
property bool hovered: false
|
||||
property real lastScrollX: 0
|
||||
property real lastScrollY: 0
|
||||
property bool trackingScroll: false
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onEntered: event => {
|
||||
barLeftSideMouseArea.hovered = true;
|
||||
}
|
||||
onExited: event => {
|
||||
barLeftSideMouseArea.hovered = false;
|
||||
barLeftSideMouseArea.trackingScroll = false;
|
||||
}
|
||||
onPressed: event => {
|
||||
if (event.button === Qt.LeftButton) {
|
||||
Hyprland.dispatch('global quickshell:sidebarLeftOpen');
|
||||
}
|
||||
}
|
||||
// Scroll to change brightness
|
||||
WheelHandler {
|
||||
onWheel: event => {
|
||||
if (event.angleDelta.y < 0)
|
||||
barRoot.brightnessMonitor.setBrightness(barRoot.brightnessMonitor.brightness - 0.05);
|
||||
else if (event.angleDelta.y > 0)
|
||||
barRoot.brightnessMonitor.setBrightness(barRoot.brightnessMonitor.brightness + 0.05);
|
||||
// Store the mouse position and start tracking
|
||||
barLeftSideMouseArea.lastScrollX = event.x;
|
||||
barLeftSideMouseArea.lastScrollY = event.y;
|
||||
barLeftSideMouseArea.trackingScroll = true;
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (barLeftSideMouseArea.trackingScroll) {
|
||||
const dx = mouse.x - barLeftSideMouseArea.lastScrollX;
|
||||
const dy = mouse.y - barLeftSideMouseArea.lastScrollY;
|
||||
if (Math.sqrt(dx * dx + dy * dy) > osdHideMouseMoveThreshold) {
|
||||
Hyprland.dispatch('global quickshell:osdBrightnessHide');
|
||||
barLeftSideMouseArea.trackingScroll = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
// Left section
|
||||
anchors.fill: parent
|
||||
implicitHeight: leftSectionRowLayout.implicitHeight
|
||||
implicitWidth: leftSectionRowLayout.implicitWidth
|
||||
|
||||
ScrollHint {
|
||||
reveal: barLeftSideMouseArea.hovered
|
||||
icon: "light_mode"
|
||||
tooltipText: Translation.tr("Scroll to change brightness")
|
||||
side: "left"
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
RowLayout { // Content
|
||||
id: leftSectionRowLayout
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
RippleButton {
|
||||
// Left sidebar button
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.leftMargin: Appearance.rounding.screenRounding
|
||||
Layout.fillWidth: false
|
||||
property real buttonPadding: 5
|
||||
implicitWidth: distroIcon.width + buttonPadding * 2
|
||||
implicitHeight: distroIcon.height + buttonPadding * 2
|
||||
|
||||
buttonRadius: Appearance.rounding.full
|
||||
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
|
||||
colBackgroundHover: Appearance.colors.colLayer1Hover
|
||||
colRipple: Appearance.colors.colLayer1Active
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
toggled: GlobalStates.sidebarLeftOpen
|
||||
property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0
|
||||
|
||||
onPressed: {
|
||||
Hyprland.dispatch('global quickshell:sidebarLeftToggle');
|
||||
}
|
||||
|
||||
CustomIcon {
|
||||
id: distroIcon
|
||||
anchors.centerIn: parent
|
||||
width: 19.5
|
||||
height: 19.5
|
||||
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : "spark-symbolic"
|
||||
colorize: true
|
||||
color: Appearance.colors.colOnLayer0
|
||||
}
|
||||
}
|
||||
|
||||
ActiveWindow {
|
||||
visible: barRoot.useShortenedForm === 0
|
||||
Layout.rightMargin: Appearance.rounding.screenRounding
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
bar: barRoot
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout { // Middle section
|
||||
id: middleSection
|
||||
anchors.centerIn: parent
|
||||
spacing: Config.options?.bar.borderless ? 4 : 8
|
||||
|
||||
BarGroup {
|
||||
id: leftCenterGroup
|
||||
Layout.preferredWidth: barRoot.centerSideModuleWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
Resources {
|
||||
alwaysShowAllResources: barRoot.useShortenedForm === 2
|
||||
Layout.fillWidth: barRoot.useShortenedForm === 2
|
||||
}
|
||||
|
||||
Media {
|
||||
visible: barRoot.useShortenedForm < 2
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
VerticalBarSeparator {
|
||||
visible: Config.options?.bar.borderless
|
||||
}
|
||||
|
||||
BarGroup {
|
||||
id: middleCenterGroup
|
||||
padding: workspacesWidget.widgetPadding
|
||||
Layout.fillHeight: true
|
||||
|
||||
Workspaces {
|
||||
id: workspacesWidget
|
||||
bar: barRoot
|
||||
Layout.fillHeight: true
|
||||
MouseArea {
|
||||
// Right-click to toggle overview
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
|
||||
onPressed: event => {
|
||||
if (event.button === Qt.RightButton) {
|
||||
Hyprland.dispatch('global quickshell:overviewToggle');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerticalBarSeparator {
|
||||
visible: Config.options?.bar.borderless
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rightCenterGroup
|
||||
implicitWidth: rightCenterGroupContent.implicitWidth
|
||||
implicitHeight: rightCenterGroupContent.implicitHeight
|
||||
Layout.preferredWidth: barRoot.centerSideModuleWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
onPressed: {
|
||||
Hyprland.dispatch('global quickshell:sidebarRightToggle');
|
||||
}
|
||||
|
||||
BarGroup {
|
||||
id: rightCenterGroupContent
|
||||
anchors.fill: parent
|
||||
|
||||
ClockWidget {
|
||||
showDate: (Config.options.bar.verbose && barRoot.useShortenedForm < 2)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
UtilButtons {
|
||||
visible: (Config.options.bar.verbose && barRoot.useShortenedForm === 0)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
BatteryIndicator {
|
||||
visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerticalBarSeparator {
|
||||
visible: Config.options.bar.borderless && Config.options.bar.weather.enable
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea { // Right side | scroll to change volume
|
||||
id: barRightSideMouseArea
|
||||
|
||||
anchors.right: parent.right
|
||||
implicitHeight: Appearance.sizes.baseBarHeight
|
||||
height: Appearance.sizes.barHeight
|
||||
width: (barRoot.width - middleSection.width) / 2
|
||||
|
||||
property bool hovered: false
|
||||
property real lastScrollX: 0
|
||||
property real lastScrollY: 0
|
||||
property bool trackingScroll: false
|
||||
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onEntered: event => {
|
||||
barRightSideMouseArea.hovered = true;
|
||||
}
|
||||
onExited: event => {
|
||||
barRightSideMouseArea.hovered = false;
|
||||
barRightSideMouseArea.trackingScroll = false;
|
||||
}
|
||||
onPressed: event => {
|
||||
if (event.button === Qt.LeftButton) {
|
||||
Hyprland.dispatch('global quickshell:sidebarRightOpen');
|
||||
} else if (event.button === Qt.RightButton) {
|
||||
MprisController.activePlayer.next();
|
||||
}
|
||||
}
|
||||
// Scroll to change volume
|
||||
WheelHandler {
|
||||
onWheel: event => {
|
||||
const currentVolume = Audio.value;
|
||||
const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;
|
||||
if (event.angleDelta.y < 0)
|
||||
Audio.sink.audio.volume -= step;
|
||||
else if (event.angleDelta.y > 0)
|
||||
Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step);
|
||||
// Store the mouse position and start tracking
|
||||
barRightSideMouseArea.lastScrollX = event.x;
|
||||
barRightSideMouseArea.lastScrollY = event.y;
|
||||
barRightSideMouseArea.trackingScroll = true;
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (barRightSideMouseArea.trackingScroll) {
|
||||
const dx = mouse.x - barRightSideMouseArea.lastScrollX;
|
||||
const dy = mouse.y - barRightSideMouseArea.lastScrollY;
|
||||
if (Math.sqrt(dx * dx + dy * dy) > osdHideMouseMoveThreshold) {
|
||||
Hyprland.dispatch('global quickshell:osdVolumeHide');
|
||||
barRightSideMouseArea.trackingScroll = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
implicitHeight: rightSectionRowLayout.implicitHeight
|
||||
implicitWidth: rightSectionRowLayout.implicitWidth
|
||||
|
||||
ScrollHint {
|
||||
reveal: barRightSideMouseArea.hovered
|
||||
icon: "volume_up"
|
||||
tooltipText: Translation.tr("Scroll to change volume")
|
||||
side: "right"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rightSectionRowLayout
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
layoutDirection: Qt.RightToLeft
|
||||
|
||||
RippleButton { // Right sidebar button
|
||||
id: rightSidebarButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.rightMargin: Appearance.rounding.screenRounding
|
||||
Layout.fillWidth: false
|
||||
|
||||
implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2
|
||||
implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2
|
||||
|
||||
buttonRadius: Appearance.rounding.full
|
||||
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
|
||||
colBackgroundHover: Appearance.colors.colLayer1Hover
|
||||
colRipple: Appearance.colors.colLayer1Active
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
toggled: GlobalStates.sidebarRightOpen
|
||||
property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0
|
||||
|
||||
Behavior on colText {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
Hyprland.dispatch('global quickshell:sidebarRightToggle');
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: indicatorsRowLayout
|
||||
anchors.centerIn: parent
|
||||
property real realSpacing: 15
|
||||
spacing: 0
|
||||
|
||||
Revealer {
|
||||
reveal: Audio.sink?.audio?.muted ?? false
|
||||
Layout.fillHeight: true
|
||||
Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0
|
||||
Behavior on Layout.rightMargin {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.elementMoveFast.duration
|
||||
easing.type: Appearance.animation.elementMoveFast.type
|
||||
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||
}
|
||||
}
|
||||
MaterialSymbol {
|
||||
text: "volume_off"
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: rightSidebarButton.colText
|
||||
}
|
||||
}
|
||||
Revealer {
|
||||
reveal: Audio.source?.audio?.muted ?? false
|
||||
Layout.fillHeight: true
|
||||
Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0
|
||||
Behavior on Layout.rightMargin {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.elementMoveFast.duration
|
||||
easing.type: Appearance.animation.elementMoveFast.type
|
||||
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||
}
|
||||
}
|
||||
MaterialSymbol {
|
||||
text: "mic_off"
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: rightSidebarButton.colText
|
||||
}
|
||||
}
|
||||
MaterialSymbol {
|
||||
Layout.rightMargin: indicatorsRowLayout.realSpacing
|
||||
text: Network.materialSymbol
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: rightSidebarButton.colText
|
||||
}
|
||||
MaterialSymbol {
|
||||
text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled"
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: rightSidebarButton.colText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SysTray {
|
||||
bar: barRoot
|
||||
visible: barRoot.useShortenedForm === 0
|
||||
Layout.fillWidth: false
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
// Weather
|
||||
Loader {
|
||||
Layout.leftMargin: 8
|
||||
Layout.fillHeight: true
|
||||
active: Config.options.bar.weather.enable
|
||||
sourceComponent: BarGroup {
|
||||
implicitHeight: Appearance.sizes.baseBarHeight
|
||||
WeatherBar {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Round decorators
|
||||
Loader {
|
||||
id: roundDecorators
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
y: Appearance.sizes.barHeight
|
||||
width: parent.width
|
||||
height: Appearance.rounding.screenRounding
|
||||
active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug
|
||||
|
||||
states: State {
|
||||
name: "bottom"
|
||||
when: Config.options.bar.bottom
|
||||
PropertyChanges {
|
||||
roundDecorators.y: 0
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: Appearance.rounding.screenRounding
|
||||
RoundCorner {
|
||||
id: leftCorner
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
size: Appearance.rounding.screenRounding
|
||||
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
|
||||
|
||||
corner: RoundCorner.CornerEnum.TopLeft
|
||||
states: State {
|
||||
name: "bottom"
|
||||
when: Config.options.bar.bottom
|
||||
PropertyChanges {
|
||||
leftCorner.corner: RoundCorner.CornerEnum.BottomLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
RoundCorner {
|
||||
id: rightCorner
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: !Config.options.bar.bottom ? parent.top : undefined
|
||||
bottom: Config.options.bar.bottom ? parent.bottom : undefined
|
||||
}
|
||||
size: Appearance.rounding.screenRounding
|
||||
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
|
||||
|
||||
corner: RoundCorner.CornerEnum.TopRight
|
||||
states: State {
|
||||
name: "bottom"
|
||||
when: Config.options.bar.bottom
|
||||
PropertyChanges {
|
||||
rightCorner.corner: RoundCorner.CornerEnum.BottomRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "bar"
|
||||
|
||||
function toggle(): void {
|
||||
GlobalStates.barOpen = !GlobalStates.barOpen
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
GlobalStates.barOpen = false
|
||||
}
|
||||
|
||||
function open(): void {
|
||||
GlobalStates.barOpen = true
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "barToggle"
|
||||
description: "Toggles bar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.barOpen = !GlobalStates.barOpen;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "barOpen"
|
||||
description: "Opens bar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.barOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "barClose"
|
||||
description: "Closes bar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.barOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import qs.modules.common
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property real padding: 5
|
||||
implicitHeight: Appearance.sizes.baseBarHeight
|
||||
height: Appearance.sizes.barHeight
|
||||
implicitWidth: rowLayout.implicitWidth + padding * 2
|
||||
default property alias items: rowLayout.children
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 4
|
||||
bottomMargin: 4
|
||||
}
|
||||
color: Config.options?.bar.borderless ? "transparent" : Appearance.colors.colLayer1
|
||||
radius: Appearance.rounding.small
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: root.padding
|
||||
rightMargin: root.padding
|
||||
}
|
||||
spacing: 4
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property bool borderless: Config.options.bar.borderless
|
||||
readonly property var chargeState: Battery.chargeState
|
||||
readonly property bool isCharging: Battery.isCharging
|
||||
readonly property bool isPluggedIn: Battery.isPluggedIn
|
||||
readonly property real percentage: Battery.percentage
|
||||
readonly property bool isLow: percentage <= Config.options.battery.low / 100
|
||||
readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer
|
||||
readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error
|
||||
|
||||
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
|
||||
implicitHeight: 32
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
spacing: 4
|
||||
anchors.centerIn: parent
|
||||
|
||||
Rectangle {
|
||||
implicitWidth: (isCharging ? (boltIconLoader?.item?.width ?? 0) : 0)
|
||||
|
||||
Behavior on implicitWidth {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Appearance.colors.colOnLayer1
|
||||
text: `${Math.round(percentage * 100)}`
|
||||
}
|
||||
|
||||
CircularProgress {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
lineWidth: 2
|
||||
value: percentage
|
||||
size: 26
|
||||
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer
|
||||
primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
|
||||
fill: (isLow && !isCharging)
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
fill: 1
|
||||
text: "battery_full"
|
||||
iconSize: Appearance.font.pixelSize.normal
|
||||
color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: boltIconLoader
|
||||
active: true
|
||||
anchors.left: rowLayout.left
|
||||
anchors.verticalCenter: rowLayout.verticalCenter
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onIsChargingChanged() {
|
||||
if (isCharging) boltIconLoader.active = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: MaterialSymbol {
|
||||
id: boltIcon
|
||||
|
||||
text: "bolt"
|
||||
iconSize: Appearance.font.pixelSize.large
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
visible: opacity > 0 // Only show when charging
|
||||
opacity: isCharging ? 1 : 0 // Keep opacity for visibility
|
||||
onVisibleChanged: {
|
||||
if (!visible) boltIconLoader.active = false
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
required property string iconName
|
||||
required property double percentage
|
||||
property bool shown: true
|
||||
clip: true
|
||||
visible: width > 0 && height > 0
|
||||
implicitWidth: resourceRowLayout.x < 0 ? 0 : childrenRect.width
|
||||
implicitHeight: childrenRect.height
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
id: resourceRowLayout
|
||||
x: shown ? 0 : -resourceRowLayout.width
|
||||
|
||||
CircularProgress {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
lineWidth: 2
|
||||
value: percentage
|
||||
size: 26
|
||||
secondaryColor: Appearance.colors.colSecondaryContainer
|
||||
primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
fill: 1
|
||||
text: iconName
|
||||
iconSize: Appearance.font.pixelSize.normal
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Appearance.colors.colOnLayer1
|
||||
text: `${Math.round(percentage * 100)}`
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.elementMove.duration
|
||||
easing.type: Appearance.animation.elementMove.type
|
||||
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.SystemTray
|
||||
|
||||
// TODO: More fancy animation
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var bar
|
||||
|
||||
height: parent.height
|
||||
implicitWidth: rowLayout.implicitWidth
|
||||
Layout.leftMargin: Appearance.rounding.screenRounding
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 15
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
SysTrayItem {
|
||||
required property SystemTrayItem modelData
|
||||
|
||||
bar: root.bar
|
||||
item: modelData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.larger
|
||||
color: Appearance.colors.colSubtext
|
||||
text: "•"
|
||||
visible: {
|
||||
SystemTray.items.values.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
required property var bar
|
||||
required property SystemTrayItem item
|
||||
property bool targetMenuOpen: false
|
||||
property int trayItemWidth: Appearance.font.pixelSize.larger
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: trayItemWidth
|
||||
onClicked: (event) => {
|
||||
switch (event.button) {
|
||||
case Qt.LeftButton:
|
||||
item.activate();
|
||||
break;
|
||||
case Qt.RightButton:
|
||||
if (item.hasMenu) menu.open();
|
||||
break;
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
QsMenuAnchor {
|
||||
id: menu
|
||||
|
||||
menu: root.item.menu
|
||||
anchor.window: bar
|
||||
anchor.rect.x: root.x + bar.width
|
||||
anchor.rect.y: root.y
|
||||
anchor.rect.height: root.height
|
||||
anchor.edges: Edges.Bottom
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: trayIcon
|
||||
visible: !Config.options.bar.tray.monochromeIcons
|
||||
source: root.item.icon
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: Config.options.bar.tray.monochromeIcons
|
||||
anchors.fill: trayIcon
|
||||
sourceComponent: Item {
|
||||
Desaturate {
|
||||
id: desaturatedIcon
|
||||
visible: false // There's already color overlay
|
||||
anchors.fill: parent
|
||||
source: trayIcon
|
||||
desaturation: 1 // 1.0 means fully grayscale
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: desaturatedIcon
|
||||
source: desaturatedIcon
|
||||
color: ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
property real margin: 10
|
||||
property bool hovered: false
|
||||
implicitWidth: rowLayout.implicitWidth + margin * 2
|
||||
implicitHeight: rowLayout.implicitHeight
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
anchors.centerIn: parent
|
||||
|
||||
MaterialSymbol {
|
||||
fill: 0
|
||||
text: WeatherIcons.codeToName[Weather.data.wCode]
|
||||
iconSize: Appearance.font.pixelSize.large
|
||||
color: Appearance.colors.colOnLayer1
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: true
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colOnLayer1
|
||||
text: Weather.data.temp
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: popupLoader
|
||||
active: root.containsMouse
|
||||
|
||||
component: PopupWindow {
|
||||
id: popupWindow
|
||||
visible: true
|
||||
implicitWidth: weatherPopup.implicitWidth
|
||||
implicitHeight: weatherPopup.implicitHeight
|
||||
anchor.item: root
|
||||
anchor.edges: Edges.Top
|
||||
anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2
|
||||
anchor.rect.y: Config.options.bar.bottom ?
|
||||
(-weatherPopup.implicitHeight - 15) :
|
||||
(root.implicitHeight + 15 )
|
||||
color: "transparent"
|
||||
WeatherPopup {
|
||||
id: weatherPopup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
// credits: calestia
|
||||
// this snippet is taken from
|
||||
// https://github.com/caelestia-dots/shell
|
||||
readonly property var codeToName: ({
|
||||
"113": "clear_day",
|
||||
"116": "partly_cloudy_day",
|
||||
"119": "cloud",
|
||||
"122": "cloud",
|
||||
"143": "foggy",
|
||||
"176": "rainy",
|
||||
"179": "rainy",
|
||||
"182": "rainy",
|
||||
"185": "rainy",
|
||||
"200": "thunderstorm",
|
||||
"227": "cloudy_snowing",
|
||||
"230": "snowing_heavy",
|
||||
"248": "foggy",
|
||||
"260": "foggy",
|
||||
"263": "rainy",
|
||||
"266": "rainy",
|
||||
"281": "rainy",
|
||||
"284": "rainy",
|
||||
"293": "rainy",
|
||||
"296": "rainy",
|
||||
"299": "rainy",
|
||||
"302": "weather_hail",
|
||||
"305": "rainy",
|
||||
"308": "weather_hail",
|
||||
"311": "rainy",
|
||||
"314": "rainy",
|
||||
"317": "rainy",
|
||||
"320": "cloudy_snowing",
|
||||
"323": "cloudy_snowing",
|
||||
"326": "cloudy_snowing",
|
||||
"329": "snowing_heavy",
|
||||
"332": "snowing_heavy",
|
||||
"335": "snowing",
|
||||
"338": "snowing_heavy",
|
||||
"350": "rainy",
|
||||
"353": "rainy",
|
||||
"356": "rainy",
|
||||
"359": "weather_hail",
|
||||
"362": "rainy",
|
||||
"365": "rainy",
|
||||
"368": "cloudy_snowing",
|
||||
"371": "snowing",
|
||||
"374": "rainy",
|
||||
"377": "rainy",
|
||||
"386": "thunderstorm",
|
||||
"389": "thunderstorm",
|
||||
"392": "thunderstorm",
|
||||
"395": "snowing"
|
||||
})
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property string filePath: Directories.shellConfigPath
|
||||
property alias options: configOptionsJsonAdapter
|
||||
|
||||
function setNestedValue(nestedKey, value) {
|
||||
let keys = nestedKey.split(".");
|
||||
let obj = root.options;
|
||||
let parents = [obj];
|
||||
|
||||
// Traverse and collect parent objects
|
||||
for (let i = 0; i < keys.length - 1; ++i) {
|
||||
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
|
||||
obj[keys[i]] = {};
|
||||
}
|
||||
obj = obj[keys[i]];
|
||||
parents.push(obj);
|
||||
}
|
||||
|
||||
// Convert value to correct type using JSON.parse when safe
|
||||
let convertedValue = value;
|
||||
if (typeof value === "string") {
|
||||
let trimmed = value.trim();
|
||||
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
|
||||
try {
|
||||
convertedValue = JSON.parse(trimmed);
|
||||
} catch (e) {
|
||||
convertedValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj[keys[keys.length - 1]] = convertedValue;
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: root.filePath
|
||||
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: writeAdapter()
|
||||
onLoadFailed: error => {
|
||||
if (error == FileViewError.FileNotFound) {
|
||||
writeAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: configOptionsJsonAdapter
|
||||
property JsonObject policies: JsonObject {
|
||||
property int ai: 1 // 0: No | 1: Yes | 2: Local
|
||||
property int weeb: 1 // 0: No | 1: Open | 2: Closet
|
||||
}
|
||||
|
||||
property JsonObject ai: JsonObject {
|
||||
property string systemPrompt: "## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n"
|
||||
}
|
||||
|
||||
property JsonObject appearance: JsonObject {
|
||||
property bool extraBackgroundTint: true
|
||||
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
|
||||
property bool transparency: false
|
||||
property JsonObject wallpaperTheming: JsonObject {
|
||||
property bool enableAppsAndShell: true
|
||||
property bool enableQtApps: true
|
||||
property bool enableTerminal: true
|
||||
}
|
||||
property JsonObject palette: JsonObject {
|
||||
property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject audio: JsonObject {
|
||||
// Values in %
|
||||
property JsonObject protection: JsonObject {
|
||||
// Prevent sudden bangs
|
||||
property bool enable: true
|
||||
property real maxAllowedIncrease: 10
|
||||
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject apps: JsonObject {
|
||||
property string bluetooth: "kcmshell6 kcm_bluetooth"
|
||||
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
|
||||
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
|
||||
property string taskManager: "plasma-systemmonitor --page-name Processes"
|
||||
property string terminal: "kitty -1" // This is only for shell actions
|
||||
}
|
||||
|
||||
property JsonObject background: JsonObject {
|
||||
property bool fixedClockPosition: false
|
||||
property real clockX: -500
|
||||
property real clockY: -500
|
||||
property string wallpaperPath: ""
|
||||
property JsonObject parallax: JsonObject {
|
||||
property bool enableWorkspace: true
|
||||
property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size
|
||||
property bool enableSidebar: true
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject bar: JsonObject {
|
||||
property bool bottom: false // Instead of top
|
||||
property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle
|
||||
property bool borderless: false // true for no grouping of items
|
||||
property string topLeftIcon: "spark" // Options: distro, spark
|
||||
property bool showBackground: true
|
||||
property bool verbose: true
|
||||
property JsonObject resources: JsonObject {
|
||||
property bool alwaysShowSwap: true
|
||||
property bool alwaysShowCpu: false
|
||||
}
|
||||
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
|
||||
property JsonObject utilButtons: JsonObject {
|
||||
property bool showScreenSnip: true
|
||||
property bool showColorPicker: false
|
||||
property bool showMicToggle: false
|
||||
property bool showKeyboardToggle: true
|
||||
property bool showDarkModeToggle: true
|
||||
}
|
||||
property JsonObject tray: JsonObject {
|
||||
property bool monochromeIcons: true
|
||||
}
|
||||
property JsonObject workspaces: JsonObject {
|
||||
property bool monochromeIcons: true
|
||||
property int shown: 10
|
||||
property bool showAppIcons: true
|
||||
property bool alwaysShowNumbers: false
|
||||
property int showNumberDelay: 300 // milliseconds
|
||||
}
|
||||
property JsonObject weather: JsonObject {
|
||||
property bool enable: false
|
||||
property bool enableGPS: true // gps based location
|
||||
property string city: "" // When 'enableGPS' is false
|
||||
property bool useUSCS: false // Instead of metric (SI) units
|
||||
property int fetchInterval: 10 // minutes
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject battery: JsonObject {
|
||||
property int low: 20
|
||||
property int critical: 5
|
||||
property bool automaticSuspend: true
|
||||
property int suspend: 3
|
||||
}
|
||||
|
||||
property JsonObject dock: JsonObject {
|
||||
property bool enable: false
|
||||
property bool monochromeIcons: true
|
||||
property real height: 60
|
||||
property real hoverRegionHeight: 2
|
||||
property bool pinnedOnStartup: false
|
||||
property bool hoverToReveal: true // When false, only reveals on empty workspace
|
||||
property list<string> pinnedApps: [ // IDs of pinned entries
|
||||
"org.kde.dolphin", "kitty",]
|
||||
property list<string> ignoredAppRegexes: []
|
||||
}
|
||||
|
||||
property JsonObject language: JsonObject {
|
||||
property JsonObject translator: JsonObject {
|
||||
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
|
||||
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
|
||||
property string sourceLanguage: "auto"
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject networking: JsonObject {
|
||||
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
property JsonObject osd: JsonObject {
|
||||
property int timeout: 1000
|
||||
}
|
||||
|
||||
property JsonObject osk: JsonObject {
|
||||
property string layout: "qwerty_full"
|
||||
property bool pinnedOnStartup: false
|
||||
}
|
||||
|
||||
property JsonObject overview: JsonObject {
|
||||
property real scale: 0.18 // Relative to screen size
|
||||
property real rows: 2
|
||||
property real columns: 5
|
||||
}
|
||||
|
||||
property JsonObject resources: JsonObject {
|
||||
property int updateInterval: 3000
|
||||
}
|
||||
|
||||
property JsonObject search: JsonObject {
|
||||
property int nonAppResultDelay: 30 // This prevents lagging when typing
|
||||
property string engineBaseUrl: "https://www.google.com/search?q="
|
||||
property list<string> excludedSites: ["quora.com"]
|
||||
property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird.
|
||||
property JsonObject prefix: JsonObject {
|
||||
property string action: "/"
|
||||
property string clipboard: ";"
|
||||
property string emojis: ":"
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject sidebar: JsonObject {
|
||||
property JsonObject translator: JsonObject {
|
||||
property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag.
|
||||
}
|
||||
property JsonObject booru: JsonObject {
|
||||
property bool allowNsfw: false
|
||||
property string defaultProvider: "yandere"
|
||||
property int limit: 20
|
||||
property JsonObject zerochan: JsonObject {
|
||||
property string username: "[unset]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject time: JsonObject {
|
||||
// https://doc.qt.io/qt-6/qtime.html#toString
|
||||
property string format: "hh:mm"
|
||||
property string dateFormat: "ddd, dd/MM"
|
||||
}
|
||||
|
||||
property JsonObject windows: JsonObject {
|
||||
property bool showTitlebar: true // Client-side decoration for shell apps
|
||||
property bool centerTitle: true
|
||||
}
|
||||
|
||||
property JsonObject hacks: JsonObject {
|
||||
property int arbitraryRaceConditionDelay: 20 // milliseconds
|
||||
}
|
||||
|
||||
property JsonObject screenshotTool: JsonObject {
|
||||
property bool showContentRegions: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property alias states: persistentStatesJsonAdapter
|
||||
property string fileDir: Directories.state
|
||||
property string fileName: "states.json"
|
||||
property string filePath: `${root.fileDir}/${root.fileName}`
|
||||
|
||||
FileView {
|
||||
path: root.filePath
|
||||
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: {
|
||||
writeAdapter()
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
console.log("Failed to load persistent states file:", error);
|
||||
if (error == FileViewError.FileNotFound) {
|
||||
writeAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: persistentStatesJsonAdapter
|
||||
property JsonObject ai: JsonObject {
|
||||
property string model
|
||||
property real temperature: 0.5
|
||||
}
|
||||
|
||||
property JsonObject sidebar: JsonObject {
|
||||
property JsonObject bottomGroup: JsonObject {
|
||||
property bool collapsed: false
|
||||
property int tab: 0
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject booru: JsonObject {
|
||||
property bool allowNsfw: false
|
||||
property string provider: "yandere"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
|
||||
*
|
||||
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||
* @param {string} color2 - The color to take hue from.
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function colorWithHueOf(color1, color2) {
|
||||
var c1 = Qt.color(color1);
|
||||
var c2 = Qt.color(color2);
|
||||
|
||||
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
|
||||
var hue = c2.hsvHue;
|
||||
var sat = c1.hsvSaturation;
|
||||
var val = c1.hsvValue;
|
||||
var alpha = c1.a;
|
||||
|
||||
return Qt.hsva(hue, sat, val, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
|
||||
*
|
||||
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||
* @param {string} color2 - The color to take saturation from.
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function colorWithSaturationOf(color1, color2) {
|
||||
var c1 = Qt.color(color1);
|
||||
var c2 = Qt.color(color2);
|
||||
|
||||
var hue = c1.hsvHue;
|
||||
var sat = c2.hsvSaturation;
|
||||
var val = c1.hsvValue;
|
||||
var alpha = c1.a;
|
||||
|
||||
return Qt.hsva(hue, sat, val, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
|
||||
*
|
||||
* @param {string} color - The base color (any Qt.color-compatible string).
|
||||
* @param {number} lightness - The lightness value to use (0-1).
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function colorWithLightness(color, lightness) {
|
||||
var c = Qt.color(color);
|
||||
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
|
||||
*
|
||||
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||
* @param {string} color2 - The color to take lightness from.
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function colorWithLightnessOf(color1, color2) {
|
||||
var c2 = Qt.color(color2);
|
||||
return colorWithLightness(color1, c2.hslLightness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
|
||||
*
|
||||
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||
* @param {string} color2 - The accent color.
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function adaptToAccent(color1, color2) {
|
||||
var c1 = Qt.color(color1);
|
||||
var c2 = Qt.color(color2);
|
||||
|
||||
var hue = c2.hslHue;
|
||||
var sat = c2.hslSaturation;
|
||||
var light = c1.hslLightness;
|
||||
var alpha = c1.a;
|
||||
|
||||
return Qt.hsla(hue, sat, light, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes two colors by a given percentage.
|
||||
*
|
||||
* @param {string} color1 - The first color (any Qt.color-compatible string).
|
||||
* @param {string} color2 - The second color.
|
||||
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
|
||||
* @returns {Qt.rgba} The resulting mixed color.
|
||||
*/
|
||||
function mix(color1, color2, percentage = 0.5) {
|
||||
var c1 = Qt.color(color1);
|
||||
var c2 = Qt.color(color2);
|
||||
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transparentizes a color by a given percentage.
|
||||
*
|
||||
* @param {string} color - The color (any Qt.color-compatible string).
|
||||
* @param {number} percentage - The amount to transparentize (0-1).
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function transparentize(color, percentage = 1) {
|
||||
var c = Qt.color(color);
|
||||
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* Trims the File protocol off the input string
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function trimFileProtocol(str) {
|
||||
return str.startsWith("file://") ? str.slice(7) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the file name from a file path
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function fileNameForPath(str) {
|
||||
if (typeof str !== "string") return "";
|
||||
const trimmed = trimFileProtocol(str);
|
||||
return trimmed.split(/[\\/]/).pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file extension from a file path or name
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function trimFileExt(str) {
|
||||
if (typeof str !== "string") return "";
|
||||
const trimmed = trimFileProtocol(str);
|
||||
const lastDot = trimmed.lastIndexOf(".");
|
||||
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
|
||||
return trimmed.slice(0, lastDot);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* Formats a string according to the args that are passed inc
|
||||
* @param { string } str
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
function format(str, ...args) {
|
||||
return str.replace(/{(\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain of the passed in url or null
|
||||
* @param { string } url
|
||||
* @returns { string| null }
|
||||
*/
|
||||
function getDomain(url) {
|
||||
const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base url of the passed in url or null
|
||||
* @param { string } url
|
||||
* @returns { string | null }
|
||||
*/
|
||||
function getBaseUrl(url) {
|
||||
const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes single quotes in shell commands
|
||||
* @param { string } str
|
||||
* @returns { string }
|
||||
*/
|
||||
function shellSingleQuoteEscape(str) {
|
||||
// escape single quotes
|
||||
return String(str)
|
||||
// .replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "'\\''");
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits markdown blocks into three different types: text, think, and code.
|
||||
* @param { string } markdown
|
||||
*/
|
||||
function splitMarkdownBlocks(markdown) {
|
||||
const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g;
|
||||
/**
|
||||
* @type {{type: "text" | "think" | "code"; content: string; lang: string | undefined; completed: boolean | undefined}[]}
|
||||
*/
|
||||
let result = [];
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
while ((match = regex.exec(markdown)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
const text = markdown.slice(lastIndex, match.index);
|
||||
if (text.trim()) {
|
||||
result.push({
|
||||
type: "text",
|
||||
content: text
|
||||
});
|
||||
}
|
||||
}
|
||||
if (match[0].startsWith('```')) {
|
||||
if (match[2] && match[2].trim()) {
|
||||
result.push({
|
||||
type: "code",
|
||||
lang: match[1] || "",
|
||||
content: match[2],
|
||||
completed: true
|
||||
});
|
||||
}
|
||||
} else if (match[0].startsWith('<think>')) {
|
||||
if (match[3] && match[3].trim()) {
|
||||
result.push({
|
||||
type: "think",
|
||||
content: match[3],
|
||||
completed: true
|
||||
});
|
||||
}
|
||||
}
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
// Handle any remaining text after the last match
|
||||
if (lastIndex < markdown.length) {
|
||||
const text = markdown.slice(lastIndex);
|
||||
// Check for unfinished <think> block
|
||||
const thinkStart = text.indexOf('<think>');
|
||||
const codeStart = text.indexOf('```');
|
||||
if (thinkStart !== -1 && (codeStart === -1 || thinkStart < codeStart)) {
|
||||
const beforeThink = text.slice(0, thinkStart);
|
||||
if (beforeThink.trim()) {
|
||||
result.push({
|
||||
type: "text",
|
||||
content: beforeThink
|
||||
});
|
||||
}
|
||||
const thinkContent = text.slice(thinkStart + 7);
|
||||
if (thinkContent.trim()) {
|
||||
result.push({
|
||||
type: "think",
|
||||
content: thinkContent,
|
||||
completed: false
|
||||
});
|
||||
}
|
||||
} else if (codeStart !== -1) {
|
||||
const beforeCode = text.slice(0, codeStart);
|
||||
if (beforeCode.trim()) {
|
||||
result.push({
|
||||
type: "text",
|
||||
content: beforeCode
|
||||
});
|
||||
}
|
||||
// Try to detect language after ```
|
||||
const codeLangMatch = text.slice(codeStart + 3).match(/^(\w+)?\n/);
|
||||
let lang = "";
|
||||
let codeContentStart = codeStart + 3;
|
||||
if (codeLangMatch) {
|
||||
lang = codeLangMatch[1] || "";
|
||||
codeContentStart += codeLangMatch[0].length;
|
||||
} else if (text[codeStart + 3] === '\n') {
|
||||
codeContentStart += 1;
|
||||
}
|
||||
const codeContent = text.slice(codeContentStart);
|
||||
if (codeContent.trim()) {
|
||||
result.push({
|
||||
type: "code",
|
||||
lang,
|
||||
content: codeContent,
|
||||
completed: false
|
||||
});
|
||||
}
|
||||
} else if (text.trim()) {
|
||||
result.push({
|
||||
type: "text",
|
||||
content: text
|
||||
});
|
||||
}
|
||||
}
|
||||
// console.log(JSON.stringify(result, null, 2));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original string with backslashes escaped
|
||||
* @param { string } str
|
||||
* @returns { string }
|
||||
*/
|
||||
function escapeBackslashes(str) {
|
||||
return str.replace(/\\/g, '\\\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps words to supplied maximum length
|
||||
* @param { string | null } str
|
||||
* @param { number } maxLen
|
||||
* @returns { string }
|
||||
*/
|
||||
function wordWrap(str, maxLen) {
|
||||
if (!str)
|
||||
return "";
|
||||
let words = str.split(" ");
|
||||
let lines = [];
|
||||
let current = "";
|
||||
for (let i = 0; i < words.length; ++i) {
|
||||
if ((current + (current.length > 0 ? " " : "") + words[i]).length > maxLen) {
|
||||
if (current.length > 0)
|
||||
lines.push(current);
|
||||
current = words[i];
|
||||
} else {
|
||||
current += (current.length > 0 ? " " : "") + words[i];
|
||||
}
|
||||
}
|
||||
if (current.length > 0)
|
||||
lines.push(current);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function cleanMusicTitle(title) {
|
||||
if (!title)
|
||||
return "";
|
||||
// Brackets
|
||||
title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets
|
||||
title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets
|
||||
title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets
|
||||
// Japenis brackets
|
||||
title = title.replace(/^ *【[^】]*】/, ""); // Touhou
|
||||
title = title.replace(/^ *《[^》]*》/, ""); // ??
|
||||
title = title.replace(/^ *「[^」]*」/, ""); // OP/ED thingie
|
||||
title = title.replace(/^ *『[^』]*』/, ""); // OP/ED thingie
|
||||
|
||||
return title.trim();
|
||||
}
|
||||
|
||||
function friendlyTimeForSeconds(seconds) {
|
||||
if (isNaN(seconds) || seconds < 0)
|
||||
return "0:00";
|
||||
seconds = Math.floor(seconds);
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = seconds % 60;
|
||||
if (h > 0) {
|
||||
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||
} else {
|
||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (typeof str !== 'string')
|
||||
return str;
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// From https://github.com/rafzby/circular-progressbar with modifications
|
||||
// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo
|
||||
|
||||
import QtQuick
|
||||
import qs.modules.common
|
||||
|
||||
/**
|
||||
* Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int size: 30
|
||||
property int lineWidth: 2
|
||||
property real value: 0
|
||||
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
property color secondaryColor: Appearance.colors.colSecondaryContainer
|
||||
property real gapAngle: Math.PI / 9
|
||||
property bool fill: false
|
||||
property int fillOverflow: 2
|
||||
property int animationDuration: 1000
|
||||
property var easingType: Easing.OutCubic
|
||||
|
||||
width: size
|
||||
height: size
|
||||
|
||||
signal animationFinished();
|
||||
|
||||
onValueChanged: {
|
||||
canvas.degree = value * 360;
|
||||
}
|
||||
onPrimaryColorChanged: {
|
||||
canvas.requestPaint();
|
||||
}
|
||||
onSecondaryColorChanged: {
|
||||
canvas.requestPaint();
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
|
||||
property real degree: 0
|
||||
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
|
||||
onDegreeChanged: {
|
||||
requestPaint();
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
var x = root.width / 2;
|
||||
var y = root.height / 2;
|
||||
var radius = root.size / 2 - root.lineWidth;
|
||||
var startAngle = (Math.PI / 180) * 270;
|
||||
var fullAngle = (Math.PI / 180) * (270 + 360);
|
||||
var progressAngle = (Math.PI / 180) * (270 + degree);
|
||||
var epsilon = 0.01; // Small angle in radians
|
||||
|
||||
ctx.reset();
|
||||
if (root.fill) {
|
||||
ctx.fillStyle = root.secondaryColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius + fillOverflow, startAngle, fullAngle);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineWidth = root.lineWidth;
|
||||
|
||||
// Secondary
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle);
|
||||
ctx.strokeStyle = root.secondaryColor;
|
||||
ctx.stroke();
|
||||
|
||||
// Primary (value indication)
|
||||
var endAngle = progressAngle + (value > 0 ? 0 : epsilon);
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, startAngle, endAngle);
|
||||
ctx.strokeStyle = root.primaryColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
Behavior on degree {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.easingType
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import qs.modules.common.functions
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property string entry
|
||||
property real maxWidth
|
||||
property real maxHeight
|
||||
|
||||
property string imageDecodePath: Directories.cliphistDecode
|
||||
property string imageDecodeFileName: `${entryNumber}`
|
||||
property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`
|
||||
property string source
|
||||
|
||||
property int entryNumber: {
|
||||
if (!root.entry) return 0
|
||||
const match = root.entry.match(/^(\d+)\t/)
|
||||
return match ? parseInt(match[1]) : 0
|
||||
}
|
||||
property int imageWidth: {
|
||||
if (!root.entry) return 0
|
||||
const match = root.entry.match(/(\d+)x(\d+)/)
|
||||
return match ? parseInt(match[1]) : 0
|
||||
}
|
||||
property int imageHeight: {
|
||||
if (!root.entry) return 0
|
||||
const match = root.entry.match(/(\d+)x(\d+)/)
|
||||
return match ? parseInt(match[2]) : 0
|
||||
}
|
||||
property real scale: {
|
||||
return Math.min(
|
||||
root.maxWidth / imageWidth,
|
||||
root.maxHeight / imageHeight,
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
color: Appearance.colors.colLayer1
|
||||
radius: Appearance.rounding.small
|
||||
implicitHeight: imageHeight * scale
|
||||
implicitWidth: imageWidth * scale
|
||||
|
||||
Component.onCompleted: {
|
||||
decodeImageProcess.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: decodeImageProcess
|
||||
command: ["bash", "-c",
|
||||
`[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | cliphist decode > '${imageDecodeFilePath}'`
|
||||
]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
root.source = imageDecodeFilePath
|
||||
} else {
|
||||
console.error("[CliphistImage] Failed to decode image for entry:", root.entry)
|
||||
root.source = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`])
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image
|
||||
anchors.fill: parent
|
||||
|
||||
source: Qt.resolvedUrl(root.source)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
antialiasing: true
|
||||
asynchronous: true
|
||||
|
||||
width: root.imageWidth * root.scale
|
||||
height: root.imageHeight * root.scale
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: image.width
|
||||
height: image.height
|
||||
radius: root.radius
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
property string title
|
||||
default property alias data: sectionContent.data
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
StyledText {
|
||||
text: root.title
|
||||
font.pixelSize: Appearance.font.pixelSize.larger
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
ColumnLayout {
|
||||
id: sectionContent
|
||||
spacing: 8
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import qs.modules.common
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
id: root
|
||||
property real iconSize: Appearance?.font.pixelSize.small ?? 16
|
||||
property real fill: 0
|
||||
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
|
||||
renderType: Text.NativeRendering
|
||||
font {
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
|
||||
pixelSize: iconSize
|
||||
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
|
||||
variableAxes: {
|
||||
"FILL": truncatedFill,
|
||||
// "wght": font.weight,
|
||||
// "GRAD": 0,
|
||||
"opsz": iconSize,
|
||||
}
|
||||
}
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Appearance.m3colors.m3onBackground
|
||||
|
||||
// Behavior on fill {
|
||||
// NumberAnimation {
|
||||
// duration: Appearance?.animation.elementMoveFast.duration ?? 200
|
||||
// easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
|
||||
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
|
||||
required property var externalTrackedTab
|
||||
property bool enableIndicatorAnimation: false
|
||||
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
|
||||
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
|
||||
signal currentIndexChanged(int index)
|
||||
|
||||
property bool centerTabBar: parent.width > 500
|
||||
Layout.fillWidth: !centerTabBar
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.max(tabBar.implicitWidth, 600)
|
||||
|
||||
TabBar {
|
||||
id: tabBar
|
||||
Layout.fillWidth: true
|
||||
currentIndex: root.externalTrackedTab
|
||||
onCurrentIndexChanged: {
|
||||
root.onCurrentIndexChanged(currentIndex)
|
||||
}
|
||||
|
||||
background: Item {
|
||||
WheelHandler {
|
||||
onWheel: (event) => {
|
||||
if (event.angleDelta.y < 0)
|
||||
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
|
||||
else if (event.angleDelta.y > 0)
|
||||
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.tabButtonList
|
||||
delegate: PrimaryTabButton {
|
||||
selected: (index == root.externalTrackedTab)
|
||||
buttonText: modelData.name
|
||||
buttonIcon: modelData.icon
|
||||
minimumWidth: 160
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { // Tab indicator
|
||||
id: tabIndicator
|
||||
Layout.fillWidth: true
|
||||
height: 3
|
||||
Connections {
|
||||
target: root
|
||||
function onExternalTrackedTabChanged() {
|
||||
root.enableIndicatorAnimation = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
property int tabCount: root.tabButtonList.length
|
||||
property real fullTabSize: root.width / tabCount;
|
||||
property real targetWidth: tabBar.contentItem?.children[0]?.children[tabBar.currentIndex]?.tabContentWidth ?? 0
|
||||
|
||||
implicitWidth: targetWidth
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
|
||||
|
||||
color: root.colIndicator
|
||||
radius: Appearance?.rounding.full ?? 9999
|
||||
|
||||
Behavior on x {
|
||||
animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Tabbar bottom border
|
||||
id: tabBarBottomBorder
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 1
|
||||
color: root.colBorder
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
TabButton {
|
||||
id: button
|
||||
property string buttonText
|
||||
property string buttonIcon
|
||||
property real minimumWidth: 110
|
||||
property bool selected: false
|
||||
property int tabContentWidth: contentItem.children[0].implicitWidth
|
||||
property int rippleDuration: 1200
|
||||
height: buttonBackground.height
|
||||
implicitWidth: Math.max(tabContentWidth, buttonBackground.implicitWidth, minimumWidth)
|
||||
|
||||
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
|
||||
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
|
||||
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
|
||||
property color colActive: Appearance?.colors.colPrimary ?? "#65558F"
|
||||
property color colInactive: Appearance?.colors.colOnLayer1 ?? "#45464F"
|
||||
|
||||
component RippleAnim: NumberAnimation {
|
||||
duration: rippleDuration
|
||||
easing.type: Appearance?.animation.elementMoveEnter.type
|
||||
easing.bezierCurve: Appearance?.animationCurves.standardDecel
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: (event) => {
|
||||
const {x,y} = event
|
||||
const stateY = buttonBackground.y;
|
||||
rippleAnim.x = x;
|
||||
rippleAnim.y = y - stateY;
|
||||
|
||||
const dist = (ox,oy) => ox*ox + oy*oy
|
||||
const stateEndY = stateY + buttonBackground.height
|
||||
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
|
||||
|
||||
rippleFadeAnim.complete();
|
||||
rippleAnim.restart();
|
||||
}
|
||||
onReleased: (event) => {
|
||||
button.click() // Because the MouseArea already consumed the event
|
||||
rippleFadeAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
RippleAnim {
|
||||
id: rippleFadeAnim
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: rippleAnim
|
||||
|
||||
property real x
|
||||
property real y
|
||||
property real radius
|
||||
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "x"
|
||||
value: rippleAnim.x
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "y"
|
||||
value: rippleAnim.y
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
value: 1
|
||||
}
|
||||
ParallelAnimation {
|
||||
RippleAnim {
|
||||
target: ripple
|
||||
properties: "implicitWidth,implicitHeight"
|
||||
from: 0
|
||||
to: rippleAnim.radius * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: buttonBackground
|
||||
radius: Appearance?.rounding.small
|
||||
implicitHeight: 50
|
||||
color: (button.hovered ? button.colBackgroundHover : button.colBackground)
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: buttonBackground.width
|
||||
height: buttonBackground.height
|
||||
radius: buttonBackground.radius
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: ripple
|
||||
width: ripple.implicitWidth
|
||||
height: ripple.implicitHeight
|
||||
opacity: 0
|
||||
|
||||
property real implicitWidth: 0
|
||||
property real implicitHeight: 0
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: button.colRipple }
|
||||
GradientStop { position: 0.3; color: button.colRipple }
|
||||
GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.centerIn: buttonBackground
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
MaterialSymbol {
|
||||
visible: buttonIcon?.length > 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: buttonIcon
|
||||
iconSize: Appearance?.font.pixelSize.hugeass ?? 25
|
||||
fill: selected ? 1 : 0
|
||||
color: selected ? button.colActive : button.colInactive
|
||||
Behavior on color {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
id: buttonTextWidget
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Appearance?.font.pixelSize.small
|
||||
color: selected ? button.colActive : button.colInactive
|
||||
text: buttonText
|
||||
Behavior on color {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import QtQuick 2.9
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight }
|
||||
property var corner: RoundCorner.CornerEnum.TopLeft // Default to TopLeft
|
||||
|
||||
property int size: 25
|
||||
property color color: "#000000"
|
||||
|
||||
onColorChanged: {
|
||||
canvas.requestPaint();
|
||||
}
|
||||
onCornerChanged: {
|
||||
canvas.requestPaint();
|
||||
}
|
||||
|
||||
implicitWidth: size
|
||||
implicitHeight: size
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
var r = root.size;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.beginPath();
|
||||
switch (root.corner) {
|
||||
case RoundCorner.CornerEnum.TopLeft:
|
||||
ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
|
||||
ctx.lineTo(0, 0);
|
||||
break;
|
||||
case RoundCorner.CornerEnum.TopRight:
|
||||
ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
|
||||
ctx.lineTo(r, 0);
|
||||
break;
|
||||
case RoundCorner.CornerEnum.BottomLeft:
|
||||
ctx.arc(r, 0, r, Math.PI / 2, Math.PI);
|
||||
ctx.lineTo(0, r);
|
||||
break;
|
||||
case RoundCorner.CornerEnum.BottomRight:
|
||||
ctx.arc(0, 0, r, 0, Math.PI / 2);
|
||||
ctx.lineTo(r, r);
|
||||
break;
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = root.color;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on size {
|
||||
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
GroupButton {
|
||||
id: root
|
||||
horizontalPadding: 12
|
||||
verticalPadding: 8
|
||||
bounce: false
|
||||
property bool leftmost: false
|
||||
property bool rightmost: false
|
||||
leftRadius: (toggled || leftmost) ? (height / 2) : Appearance.rounding.unsharpenmore
|
||||
rightRadius: (toggled || rightmost) ? (height / 2) : Appearance.rounding.unsharpenmore
|
||||
colBackground: Appearance.colors.colSecondaryContainer
|
||||
contentItem: StyledText {
|
||||
color: parent.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer
|
||||
text: root.buttonText
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
|
||||
/**
|
||||
* A ListView with animations.
|
||||
*/
|
||||
ListView {
|
||||
id: root
|
||||
spacing: 5
|
||||
property real removeOvershoot: 20 // Account for gaps and bouncy animations
|
||||
property int dragIndex: -1
|
||||
property real dragDistance: 0
|
||||
property bool popin: true
|
||||
|
||||
function resetDrag() {
|
||||
root.dragIndex = -1
|
||||
root.dragDistance = 0
|
||||
}
|
||||
|
||||
add: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: popin ? "opacity,scale" : "opacity",
|
||||
from: 0,
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
addDisplaced: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "y",
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: popin ? "opacity,scale" : "opacity",
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
// displaced: Transition {
|
||||
// animations: [
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// property: "y",
|
||||
// }),
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// properties: "opacity,scale",
|
||||
// to: 1,
|
||||
// }),
|
||||
// ]
|
||||
// }
|
||||
|
||||
// move: Transition {
|
||||
// animations: [
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// property: "y",
|
||||
// }),
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// properties: "opacity,scale",
|
||||
// to: 1,
|
||||
// }),
|
||||
// ]
|
||||
// }
|
||||
// moveDisplaced: Transition {
|
||||
// animations: [
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// property: "y",
|
||||
// }),
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// properties: "opacity,scale",
|
||||
// to: 1,
|
||||
// }),
|
||||
// ]
|
||||
// }
|
||||
|
||||
remove: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "x",
|
||||
to: root.width + root.removeOvershoot,
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "opacity",
|
||||
to: 0,
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
// This is movement when something is removed, not removing animation!
|
||||
removeDisplaced: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "y",
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: "opacity,scale",
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
/**
|
||||
* Material 3 progress bar. See https://m3.material.io/components/progress-indicators/overview
|
||||
*/
|
||||
ProgressBar {
|
||||
id: root
|
||||
property real valueBarWidth: 120
|
||||
property real valueBarHeight: 4
|
||||
property real valueBarGap: 4
|
||||
property color highlightColor: Appearance?.colors.colPrimary ?? "#685496"
|
||||
property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9"
|
||||
property bool sperm: false // If true, the progress bar will have a wavy fill effect
|
||||
property bool animateSperm: true
|
||||
property real spermAmplitudeMultiplier: sperm ? 0.5 : 0
|
||||
property real spermFrequency: 6
|
||||
property real spermFps: 60
|
||||
|
||||
Behavior on spermAmplitudeMultiplier {
|
||||
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Behavior on value {
|
||||
animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
implicitHeight: valueBarHeight
|
||||
implicitWidth: valueBarWidth
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Canvas {
|
||||
id: wavyFill
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
height: parent.height * 6
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
var progress = root.visualPosition;
|
||||
var fillWidth = progress * width;
|
||||
var amplitude = parent.height * root.spermAmplitudeMultiplier;
|
||||
var frequency = root.spermFrequency;
|
||||
var phase = Date.now() / 400.0;
|
||||
var centerY = height / 2;
|
||||
|
||||
ctx.strokeStyle = root.highlightColor;
|
||||
ctx.lineWidth = parent.height;
|
||||
ctx.lineCap = "round";
|
||||
ctx.beginPath();
|
||||
for (var x = ctx.lineWidth / 2; x <= fillWidth; x += 1) {
|
||||
var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / width + phase);
|
||||
if (x === 0)
|
||||
ctx.moveTo(x, waveY);
|
||||
else
|
||||
ctx.lineTo(x, waveY);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
Connections {
|
||||
target: root
|
||||
function onValueChanged() { wavyFill.requestPaint(); }
|
||||
function onHighlightColorChanged() { wavyFill.requestPaint(); }
|
||||
}
|
||||
Timer {
|
||||
interval: 1000 / root.spermFps
|
||||
running: root.animateSperm
|
||||
repeat: root.sperm
|
||||
onTriggered: wavyFill.requestPaint()
|
||||
}
|
||||
}
|
||||
Rectangle { // Right remaining part fill
|
||||
anchors.right: parent.right
|
||||
width: (1 - root.visualPosition) * parent.width - valueBarGap
|
||||
height: parent.height
|
||||
radius: Appearance?.rounding.full ?? 9999
|
||||
color: root.trackColor
|
||||
}
|
||||
Rectangle { // Stop point
|
||||
anchors.right: parent.right
|
||||
width: valueBarGap
|
||||
height: valueBarGap
|
||||
radius: Appearance?.rounding.full ?? 9999
|
||||
color: root.highlightColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import qs.modules.common
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Text {
|
||||
renderType: Text.NativeRendering
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font {
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: Appearance?.font.family.main ?? "sans-serif"
|
||||
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||
}
|
||||
color: Appearance?.m3colors.m3onBackground ?? "black"
|
||||
linkColor: Appearance?.m3colors.m3primary
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ToolTip {
|
||||
id: root
|
||||
property string content
|
||||
property bool extraVisibleCondition: true
|
||||
property bool alternativeVisibleCondition: false
|
||||
property bool internalVisibleCondition: {
|
||||
const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
|
||||
return ans
|
||||
}
|
||||
verticalPadding: 5
|
||||
horizontalPadding: 10
|
||||
opacity: internalVisibleCondition ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
background: null
|
||||
|
||||
contentItem: Item {
|
||||
id: contentItemBackground
|
||||
implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding
|
||||
implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding
|
||||
|
||||
Rectangle {
|
||||
id: backgroundRectangle
|
||||
anchors.bottom: contentItemBackground.bottom
|
||||
anchors.horizontalCenter: contentItemBackground.horizontalCenter
|
||||
color: Appearance?.colors.colTooltip ?? "#3C4043"
|
||||
radius: Appearance?.rounding.verysmall ?? 7
|
||||
width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0
|
||||
height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0
|
||||
clip: true
|
||||
|
||||
Behavior on width {
|
||||
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on height {
|
||||
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: tooltipTextObject
|
||||
anchors.centerIn: parent
|
||||
text: content
|
||||
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
|
||||
font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
|
||||
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.lock
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
// This stores all the information shared between the lock surfaces on each screen.
|
||||
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
|
||||
LockContext {
|
||||
id: lockContext
|
||||
|
||||
onUnlocked: {
|
||||
// Unlock the screen before exiting, or the compositor will display a
|
||||
// fallback lock you can't interact with.
|
||||
GlobalStates.screenLocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
WlSessionLock {
|
||||
id: lock
|
||||
locked: GlobalStates.screenLocked
|
||||
|
||||
WlSessionLockSurface {
|
||||
color: "transparent"
|
||||
Loader {
|
||||
active: GlobalStates.screenLocked
|
||||
anchors.fill: parent
|
||||
opacity: active ? 1 : 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
sourceComponent: LockSurface {
|
||||
context: lockContext
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Blur layer hack
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
LazyLoader {
|
||||
id: blurLayerLoader
|
||||
required property var modelData
|
||||
active: GlobalStates.screenLocked
|
||||
component: PanelWindow {
|
||||
screen: blurLayerLoader.modelData
|
||||
WlrLayershell.namespace: "quickshell:lockWindowPusher"
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
// implicitHeight: lockContext.currentText == "" ? 1 : screen.height
|
||||
implicitHeight: 1
|
||||
exclusiveZone: screen.height * 3 // For some reason if we don't multiply by some number it would look really weird
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "lock"
|
||||
|
||||
function activate(): void {
|
||||
GlobalStates.screenLocked = true;
|
||||
}
|
||||
function focus(): void {
|
||||
lockContext.shouldReFocus();
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "lock"
|
||||
description: "Locks the screen"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.screenLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "lockFocus"
|
||||
description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason"
|
||||
+ "decides to keyboard-unfocus the lock screen"
|
||||
|
||||
onPressed: {
|
||||
// console.log("I BEG FOR PLEAS REFOCUZ")
|
||||
lockContext.shouldReFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import qs
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pam
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
signal shouldReFocus()
|
||||
signal unlocked()
|
||||
signal failed()
|
||||
|
||||
// These properties are in the context and not individual lock surfaces
|
||||
// so all surfaces can share the same state.
|
||||
property string currentText: ""
|
||||
property bool unlockInProgress: false
|
||||
property bool showFailure: false
|
||||
|
||||
Timer {
|
||||
id: passwordClearTimer
|
||||
interval: 10000
|
||||
onTriggered: {
|
||||
root.currentText = "";
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentTextChanged: {
|
||||
showFailure = false; // Clear the failure text once the user starts typing.
|
||||
GlobalStates.screenLockContainsCharacters = currentText.length > 0;
|
||||
passwordClearTimer.restart();
|
||||
}
|
||||
|
||||
function tryUnlock() {
|
||||
if (currentText === "") return;
|
||||
|
||||
root.unlockInProgress = true;
|
||||
pam.start();
|
||||
}
|
||||
|
||||
PamContext {
|
||||
id: pam
|
||||
|
||||
// Its best to have a custom pam config for quickshell, as the system one
|
||||
// might not be what your interface expects, and break in some way.
|
||||
// This particular example only supports passwords.
|
||||
configDirectory: "pam"
|
||||
config: "password.conf"
|
||||
|
||||
// pam_unix will ask for a response for the password prompt
|
||||
onPamMessage: {
|
||||
if (this.responseRequired) {
|
||||
this.respond(root.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
// pam_unix won't send any important messages so all we need is the completion status.
|
||||
onCompleted: result => {
|
||||
if (result == PamResult.Success) {
|
||||
root.unlocked();
|
||||
} else {
|
||||
root.showFailure = true;
|
||||
}
|
||||
|
||||
root.currentText = "";
|
||||
root.unlockInProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
required property LockContext context
|
||||
property bool active: false
|
||||
property bool showInputField: active || context.currentText.length > 0
|
||||
|
||||
function forceFieldFocus() {
|
||||
passwordBox.forceActiveFocus();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
forceFieldFocus();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: context
|
||||
function onShouldReFocus() {
|
||||
forceFieldFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => { // Esc to clear
|
||||
// console.log("KEY!!")
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.context.currentText = ""
|
||||
}
|
||||
forceFieldFocus();
|
||||
}
|
||||
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: (mouse) => {
|
||||
forceFieldFocus();
|
||||
// console.log("Pressed")
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
forceFieldFocus();
|
||||
// console.log(JSON.stringify(mouse))
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// RippleButton {
|
||||
// anchors {
|
||||
// top: parent.top
|
||||
// left: parent.left
|
||||
// leftMargin: 10
|
||||
// topMargin: 10
|
||||
// }
|
||||
// implicitHeight: 40
|
||||
// colBackground: Appearance.colors.colLayer2
|
||||
// onClicked: context.unlocked()
|
||||
// contentItem: StyledText {
|
||||
// text: "[[ DEBUG BYPASS ]]"
|
||||
// }
|
||||
// }
|
||||
|
||||
// Password entry
|
||||
Rectangle {
|
||||
id: passwordBoxContainer
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: root.showInputField ? 20 : -height
|
||||
}
|
||||
Behavior on anchors.bottomMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
radius: Appearance.rounding.full
|
||||
color: Appearance.colors.colLayer2
|
||||
implicitWidth: 160
|
||||
implicitHeight: 44
|
||||
|
||||
StyledText {
|
||||
visible: root.context.showFailure && passwordBox.text.length == 0
|
||||
anchors.centerIn: parent
|
||||
text: "Incorrect"
|
||||
color: Appearance.m3colors.m3error
|
||||
}
|
||||
|
||||
StyledTextInput {
|
||||
id: passwordBox
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 10
|
||||
}
|
||||
clip: true
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
focus: true
|
||||
onFocusChanged: root.forceFieldFocus();
|
||||
color: Appearance.colors.colOnLayer2
|
||||
font {
|
||||
pixelSize: 10
|
||||
}
|
||||
|
||||
// Password
|
||||
enabled: !root.context.unlockInProgress
|
||||
echoMode: TextInput.Password
|
||||
inputMethodHints: Qt.ImhSensitiveData
|
||||
|
||||
// Synchronizing (across monitors) and unlocking
|
||||
onTextChanged: root.context.currentText = this.text
|
||||
onAccepted: root.context.tryUnlock()
|
||||
Connections {
|
||||
target: root.context
|
||||
function onCurrentTextChanged() {
|
||||
passwordBox.text = root.context.currentText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RippleButton {
|
||||
anchors {
|
||||
verticalCenter: passwordBoxContainer.verticalCenter
|
||||
left: passwordBoxContainer.right
|
||||
leftMargin: 5
|
||||
}
|
||||
|
||||
visible: opacity > 0
|
||||
implicitHeight: passwordBoxContainer.implicitHeight - 12
|
||||
implicitWidth: implicitHeight
|
||||
toggled: true
|
||||
buttonRadius: passwordBoxContainer.radius
|
||||
colBackground: Appearance.colors.colLayer2
|
||||
onClicked: root.context.tryUnlock()
|
||||
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
iconSize: 24
|
||||
text: "arrow_right_alt"
|
||||
color: Appearance.colors.colOnPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
auth required pam_unix.so
|
||||
@@ -1,153 +0,0 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
property bool showOsdValues: false
|
||||
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
|
||||
property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)
|
||||
|
||||
function triggerOsd() {
|
||||
showOsdValues = true
|
||||
osdTimeout.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: osdTimeout
|
||||
interval: Config.options.osd.timeout
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
showOsdValues = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Audio.sink?.audio ?? null
|
||||
function onVolumeChanged() {
|
||||
if (!Audio.ready) return
|
||||
root.showOsdValues = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Brightness
|
||||
function onBrightnessChanged() {
|
||||
if (!root.brightnessMonitor.ready) return
|
||||
root.triggerOsd()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: osdLoader
|
||||
active: showOsdValues
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: osdRoot
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onFocusedScreenChanged() {
|
||||
osdRoot.screen = root.focusedScreen
|
||||
}
|
||||
}
|
||||
|
||||
exclusionMode: ExclusionMode.Normal
|
||||
WlrLayershell.namespace: "quickshell:onScreenDisplay"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: !Config.options.bar.bottom
|
||||
bottom: Config.options.bar.bottom
|
||||
}
|
||||
mask: Region {
|
||||
item: osdValuesWrapper
|
||||
}
|
||||
|
||||
implicitWidth: columnLayout.implicitWidth
|
||||
implicitHeight: columnLayout.implicitHeight
|
||||
visible: osdLoader.active
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Item {
|
||||
id: osdValuesWrapper
|
||||
// Extra space for shadow
|
||||
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||
implicitWidth: osdValues.implicitWidth
|
||||
clip: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: root.showOsdValues = false
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.menuDecel.duration
|
||||
easing.type: Appearance.animation.menuDecel.type
|
||||
}
|
||||
}
|
||||
|
||||
OsdValueIndicator {
|
||||
id: osdValues
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.sizes.elevationMargin
|
||||
value: root.brightnessMonitor?.brightness ?? 50
|
||||
icon: "light_mode"
|
||||
rotateIcon: true
|
||||
scaleIcon: true
|
||||
name: Translation.tr("Brightness")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "osdBrightness"
|
||||
|
||||
function trigger() {
|
||||
root.triggerOsd()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
showOsdValues = false
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
showOsdValues = !showOsdValues
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "osdBrightnessTrigger"
|
||||
description: "Triggers brightness OSD on press"
|
||||
|
||||
onPressed: {
|
||||
root.triggerOsd()
|
||||
}
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "osdBrightnessHide"
|
||||
description: "Hides brightness OSD on press"
|
||||
|
||||
onPressed: {
|
||||
root.showOsdValues = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
Item { // Window
|
||||
id: root
|
||||
property var toplevel
|
||||
property var windowData
|
||||
property var monitorData
|
||||
property var scale
|
||||
property var availableWorkspaceWidth
|
||||
property var availableWorkspaceHeight
|
||||
property bool restrictToWorkspace: true
|
||||
property real initX: Math.max((windowData?.at[0] - (monitorData?.x ?? 0) - monitorData?.reserved[0]) * root.scale, 0) + xOffset
|
||||
property real initY: Math.max((windowData?.at[1] - (monitorData?.y ?? 0) - monitorData?.reserved[1]) * root.scale, 0) + yOffset
|
||||
property real xOffset: 0
|
||||
property real yOffset: 0
|
||||
|
||||
property var targetWindowWidth: windowData?.size[0] * scale
|
||||
property var targetWindowHeight: windowData?.size[1] * scale
|
||||
property bool hovered: false
|
||||
property bool pressed: false
|
||||
|
||||
property var iconToWindowRatio: 0.35
|
||||
property var xwaylandIndicatorToIconRatio: 0.35
|
||||
property var iconToWindowRatioCompact: 0.6
|
||||
property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing")
|
||||
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
|
||||
|
||||
property bool indicateXWayland: windowData?.xwayland ?? false
|
||||
|
||||
x: initX
|
||||
y: initY
|
||||
width: windowData?.size[0] * root.scale
|
||||
height: windowData?.size[1] * root.scale
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: root.width
|
||||
height: root.height
|
||||
radius: Appearance.rounding.windowRounding * root.scale
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on y {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on width {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on height {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
ScreencopyView {
|
||||
id: windowPreview
|
||||
anchors.fill: parent
|
||||
captureSource: GlobalStates.overviewOpen ? root.toplevel : null
|
||||
live: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Appearance.rounding.windowRounding * root.scale
|
||||
color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) :
|
||||
hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) :
|
||||
ColorUtils.transparentize(Appearance.colors.colLayer2)
|
||||
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
|
||||
border.width : 1
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Appearance.font.pixelSize.smaller * 0.5
|
||||
|
||||
Image {
|
||||
id: windowIcon
|
||||
property var iconSize: {
|
||||
// console.log("-=-=-", root.toplevel.title, "-=-=-")
|
||||
// console.log("Target window size:", targetWindowWidth, targetWindowHeight)
|
||||
// console.log("Icon ratio:", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
|
||||
// console.log("Scale:", root.monitorData.scale)
|
||||
// console.log("Final:", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale)
|
||||
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale;
|
||||
}
|
||||
// mipmap: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: root.iconPath
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
sourceSize: Qt.size(iconSize, iconSize)
|
||||
|
||||
Behavior on width {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on height {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: screenCorners
|
||||
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
|
||||
|
||||
component CornerPanelWindow: PanelWindow {
|
||||
id: cornerPanelWindow
|
||||
visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen))
|
||||
property var corner
|
||||
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
mask: Region {
|
||||
item: null
|
||||
}
|
||||
WlrLayershell.namespace: "quickshell:screenCorners"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: cornerPanelWindow.corner === RoundCorner.CornerEnum.TopLeft || cornerPanelWindow.corner === RoundCorner.CornerEnum.TopRight
|
||||
left: cornerPanelWindow.corner === RoundCorner.CornerEnum.TopLeft || cornerPanelWindow.corner === RoundCorner.CornerEnum.BottomLeft
|
||||
bottom: cornerPanelWindow.corner === RoundCorner.CornerEnum.BottomLeft || cornerPanelWindow.corner === RoundCorner.CornerEnum.BottomRight
|
||||
right: cornerPanelWindow.corner === RoundCorner.CornerEnum.TopRight || cornerPanelWindow.corner === RoundCorner.CornerEnum.BottomRight
|
||||
}
|
||||
|
||||
implicitWidth: cornerWidget.implicitWidth
|
||||
implicitHeight: cornerWidget.implicitHeight
|
||||
RoundCorner {
|
||||
id: cornerWidget
|
||||
size: Appearance.rounding.screenRounding
|
||||
corner: cornerPanelWindow.corner
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
Scope {
|
||||
required property var modelData
|
||||
CornerPanelWindow {
|
||||
screen: modelData
|
||||
corner: RoundCorner.CornerEnum.TopLeft
|
||||
}
|
||||
CornerPanelWindow {
|
||||
screen: modelData
|
||||
corner: RoundCorner.CornerEnum.TopRight
|
||||
}
|
||||
CornerPanelWindow {
|
||||
screen: modelData
|
||||
corner: RoundCorner.CornerEnum.BottomLeft
|
||||
}
|
||||
CornerPanelWindow {
|
||||
screen: modelData
|
||||
corner: RoundCorner.CornerEnum.BottomRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
ContentPage {
|
||||
forceWidth: true
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Color generation")
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Shell & utilities")
|
||||
checked: Config.options.appearance.wallpaperTheming.enableAppsAndShell
|
||||
onCheckedChanged: {
|
||||
Config.options.appearance.wallpaperTheming.enableAppsAndShell = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Qt apps")
|
||||
checked: Config.options.appearance.wallpaperTheming.enableQtApps
|
||||
onCheckedChanged: {
|
||||
Config.options.appearance.wallpaperTheming.enableQtApps = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Shell & utilities theming must also be enabled")
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Terminal")
|
||||
checked: Config.options.appearance.wallpaperTheming.enableTerminal
|
||||
onCheckedChanged: {
|
||||
Config.options.appearance.wallpaperTheming.enableTerminal = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Shell & utilities theming must also be enabled")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
ContentPage {
|
||||
forceWidth: true
|
||||
ContentSection {
|
||||
title: Translation.tr("Policies")
|
||||
|
||||
ConfigRow {
|
||||
ColumnLayout {
|
||||
// Weeb policy
|
||||
ContentSubsectionLabel {
|
||||
text: Translation.tr("Weeb")
|
||||
}
|
||||
ConfigSelectionArray {
|
||||
currentValue: Config.options.policies.weeb
|
||||
configOptionName: "policies.weeb"
|
||||
onSelected: newValue => {
|
||||
Config.options.policies.weeb = newValue;
|
||||
}
|
||||
options: [
|
||||
{
|
||||
displayName: Translation.tr("No"),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("Yes"),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("Closet"),
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
// AI policy
|
||||
ContentSubsectionLabel {
|
||||
text: Translation.tr("AI")
|
||||
}
|
||||
ConfigSelectionArray {
|
||||
currentValue: Config.options.policies.ai
|
||||
configOptionName: "policies.ai"
|
||||
onSelected: newValue => {
|
||||
Config.options.policies.ai = newValue;
|
||||
}
|
||||
options: [
|
||||
{
|
||||
displayName: Translation.tr("No"),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("Yes"),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("Local only"),
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Bar")
|
||||
|
||||
ConfigSelectionArray {
|
||||
currentValue: Config.options.bar.cornerStyle
|
||||
configOptionName: "bar.cornerStyle"
|
||||
onSelected: newValue => {
|
||||
Config.options.bar.cornerStyle = newValue;
|
||||
}
|
||||
options: [
|
||||
{
|
||||
displayName: Translation.tr("Hug"),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("Float"),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("Plain rectangle"),
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Appearance")
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr('Borderless')
|
||||
checked: Config.options.bar.borderless
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.borderless = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr('Show background')
|
||||
checked: Config.options.bar.showBackground
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.showBackground = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Note: turning off can hurt readability")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Buttons")
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Screen snip")
|
||||
checked: Config.options.bar.utilButtons.showScreenSnip
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.utilButtons.showScreenSnip = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Color picker")
|
||||
checked: Config.options.bar.utilButtons.showColorPicker
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.utilButtons.showColorPicker = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Mic toggle")
|
||||
checked: Config.options.bar.utilButtons.showMicToggle
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.utilButtons.showMicToggle = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Keyboard toggle")
|
||||
checked: Config.options.bar.utilButtons.showKeyboardToggle
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.utilButtons.showKeyboardToggle = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Dark/Light toggle")
|
||||
checked: Config.options.bar.utilButtons.showDarkModeToggle
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.utilButtons.showDarkModeToggle = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
opacity: 0
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Workspaces")
|
||||
tooltip: Translation.tr("Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience")
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr('Show app icons')
|
||||
checked: Config.options.bar.workspaces.showAppIcons
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.workspaces.showAppIcons = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr('Always show numbers')
|
||||
checked: Config.options.bar.workspaces.alwaysShowNumbers
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.workspaces.alwaysShowNumbers = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Workspaces shown")
|
||||
value: Config.options.bar.workspaces.shown
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Config.options.bar.workspaces.shown = value;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Number show delay when pressing Super (ms)")
|
||||
value: Config.options.bar.workspaces.showNumberDelay
|
||||
from: 0
|
||||
to: 1000
|
||||
stepSize: 50
|
||||
onValueChanged: {
|
||||
Config.options.bar.workspaces.showNumberDelay = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Weather")
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Enable")
|
||||
checked: Config.options.bar.weather.enable
|
||||
onCheckedChanged: {
|
||||
Config.options.bar.weather.enable = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Battery")
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Low warning")
|
||||
value: Config.options.battery.low
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 5
|
||||
onValueChanged: {
|
||||
Config.options.battery.low = value;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Critical warning")
|
||||
value: Config.options.battery.critical
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 5
|
||||
onValueChanged: {
|
||||
Config.options.battery.critical = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Automatic suspend")
|
||||
checked: Config.options.battery.automaticSuspend
|
||||
onCheckedChanged: {
|
||||
Config.options.battery.automaticSuspend = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Automatically suspends the system when battery is low")
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Suspend at")
|
||||
value: Config.options.battery.suspend
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 5
|
||||
onValueChanged: {
|
||||
Config.options.battery.suspend = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Dock")
|
||||
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Enable")
|
||||
checked: Config.options.dock.enable
|
||||
onCheckedChanged: {
|
||||
Config.options.dock.enable = checked;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Hover to reveal")
|
||||
checked: Config.options.dock.hoverToReveal
|
||||
onCheckedChanged: {
|
||||
Config.options.dock.hoverToReveal = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Pinned on startup")
|
||||
checked: Config.options.dock.pinnedOnStartup
|
||||
onCheckedChanged: {
|
||||
Config.options.dock.pinnedOnStartup = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("On-screen display")
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Timeout (ms)")
|
||||
value: Config.options.osd.timeout
|
||||
from: 100
|
||||
to: 3000
|
||||
stepSize: 100
|
||||
onValueChanged: {
|
||||
Config.options.osd.timeout = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Overview")
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Scale (%)")
|
||||
value: Config.options.overview.scale * 100
|
||||
from: 1
|
||||
to: 100
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Config.options.overview.scale = value / 100;
|
||||
}
|
||||
}
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Rows")
|
||||
value: Config.options.overview.rows
|
||||
from: 1
|
||||
to: 20
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Config.options.overview.rows = value;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Columns")
|
||||
value: Config.options.overview.columns
|
||||
from: 1
|
||||
to: 20
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
Config.options.overview.columns = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Screenshot tool")
|
||||
|
||||
ConfigSwitch {
|
||||
text: Translation.tr('Show regions of potential interest')
|
||||
checked: Config.options.screenshotTool.showContentRegions
|
||||
onCheckedChanged: {
|
||||
Config.options.screenshotTool.showContentRegions = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
ContentPage {
|
||||
forceWidth: true
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Audio")
|
||||
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Earbang protection")
|
||||
checked: Config.options.audio.protection.enable
|
||||
onCheckedChanged: {
|
||||
Config.options.audio.protection.enable = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Prevents abrupt increments and restricts volume limit")
|
||||
}
|
||||
}
|
||||
ConfigRow {
|
||||
// uniform: true
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Max allowed increase")
|
||||
value: Config.options.audio.protection.maxAllowedIncrease
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 2
|
||||
onValueChanged: {
|
||||
Config.options.audio.protection.maxAllowedIncrease = value;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Volume limit")
|
||||
value: Config.options.audio.protection.maxAllowed
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 2
|
||||
onValueChanged: {
|
||||
Config.options.audio.protection.maxAllowed = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentSection {
|
||||
title: Translation.tr("AI")
|
||||
MaterialTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: Translation.tr("System prompt")
|
||||
text: Config.options.ai.systemPrompt
|
||||
wrapMode: TextEdit.Wrap
|
||||
onTextChanged: {
|
||||
Qt.callLater(() => {
|
||||
Config.options.ai.systemPrompt = text;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Battery")
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Low warning")
|
||||
value: Config.options.battery.low
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 5
|
||||
onValueChanged: {
|
||||
Config.options.battery.low = value;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Critical warning")
|
||||
value: Config.options.battery.critical
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 5
|
||||
onValueChanged: {
|
||||
Config.options.battery.critical = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Automatic suspend")
|
||||
checked: Config.options.battery.automaticSuspend
|
||||
onCheckedChanged: {
|
||||
Config.options.battery.automaticSuspend = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Automatically suspends the system when battery is low")
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Suspend at")
|
||||
value: Config.options.battery.suspend
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 5
|
||||
onValueChanged: {
|
||||
Config.options.battery.suspend = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Networking")
|
||||
MaterialTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: Translation.tr("User agent (for services that require it)")
|
||||
text: Config.options.networking.userAgent
|
||||
wrapMode: TextEdit.Wrap
|
||||
onTextChanged: {
|
||||
Config.options.networking.userAgent = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Resources")
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Polling interval (ms)")
|
||||
value: Config.options.resources.updateInterval
|
||||
from: 100
|
||||
to: 10000
|
||||
stepSize: 100
|
||||
onValueChanged: {
|
||||
Config.options.resources.updateInterval = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Search")
|
||||
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Use Levenshtein distance-based algorithm instead of fuzzy")
|
||||
checked: Config.options.search.sloppy
|
||||
onCheckedChanged: {
|
||||
Config.options.search.sloppy = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)")
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Prefixes")
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
|
||||
MaterialTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: Translation.tr("Action")
|
||||
text: Config.options.search.prefix.action
|
||||
wrapMode: TextEdit.Wrap
|
||||
onTextChanged: {
|
||||
Config.options.search.prefix.action = text;
|
||||
}
|
||||
}
|
||||
MaterialTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: Translation.tr("Clipboard")
|
||||
text: Config.options.search.prefix.clipboard
|
||||
wrapMode: TextEdit.Wrap
|
||||
onTextChanged: {
|
||||
Config.options.search.prefix.clipboard = text;
|
||||
}
|
||||
}
|
||||
MaterialTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: Translation.tr("Emojis")
|
||||
text: Config.options.search.prefix.emojis
|
||||
wrapMode: TextEdit.Wrap
|
||||
onTextChanged: {
|
||||
Config.options.search.prefix.emojis = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Web search")
|
||||
MaterialTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: Translation.tr("Base URL")
|
||||
text: Config.options.search.engineBaseUrl
|
||||
wrapMode: TextEdit.Wrap
|
||||
onTextChanged: {
|
||||
Config.options.search.engineBaseUrl = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Time")
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Format")
|
||||
tooltip: ""
|
||||
|
||||
ConfigSelectionArray {
|
||||
currentValue: Config.options.time.format
|
||||
configOptionName: "time.format"
|
||||
onSelected: newValue => {
|
||||
Config.options.time.format = newValue;
|
||||
}
|
||||
options: [
|
||||
{
|
||||
displayName: Translation.tr("24h"),
|
||||
value: "hh:mm"
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("12h am/pm"),
|
||||
value: "h:mm ap"
|
||||
},
|
||||
{
|
||||
displayName: Translation.tr("12h AM/PM"),
|
||||
value: "h:mm AP"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
|
||||
ContentPage {
|
||||
baseWidth: lightDarkButtonGroup.implicitWidth
|
||||
forceWidth: true
|
||||
|
||||
Process {
|
||||
id: konachanWallProc
|
||||
property string status: ""
|
||||
command: ["bash", "-c", FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/random_konachan_wall.sh`)]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
console.log(`Konachan wall proc output: ${data}`);
|
||||
konachanWallProc.status = data.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Colors & Wallpaper")
|
||||
|
||||
// Light/Dark mode preference
|
||||
ButtonGroup {
|
||||
id: lightDarkButtonGroup
|
||||
Layout.fillWidth: true
|
||||
LightDarkPreferenceButton {
|
||||
dark: false
|
||||
}
|
||||
LightDarkPreferenceButton {
|
||||
dark: true
|
||||
}
|
||||
}
|
||||
|
||||
// Material palette selection
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Material palette")
|
||||
ConfigSelectionArray {
|
||||
currentValue: Config.options.appearance.palette.type
|
||||
configOptionName: "appearance.palette.type"
|
||||
onSelected: (newValue) => {
|
||||
Config.options.appearance.palette.type = newValue;
|
||||
Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --noswitch`])
|
||||
}
|
||||
options: [
|
||||
{"value": "auto", "displayName": Translation.tr("Auto")},
|
||||
{"value": "scheme-content", "displayName": Translation.tr("Content")},
|
||||
{"value": "scheme-expressive", "displayName": Translation.tr("Expressive")},
|
||||
{"value": "scheme-fidelity", "displayName": Translation.tr("Fidelity")},
|
||||
{"value": "scheme-fruit-salad", "displayName": Translation.tr("Fruit Salad")},
|
||||
{"value": "scheme-monochrome", "displayName": Translation.tr("Monochrome")},
|
||||
{"value": "scheme-neutral", "displayName": Translation.tr("Neutral")},
|
||||
{"value": "scheme-rainbow", "displayName": Translation.tr("Rainbow")},
|
||||
{"value": "scheme-tonal-spot", "displayName": Translation.tr("Tonal Spot")}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wallpaper selection
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Wallpaper")
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
RippleButtonWithIcon {
|
||||
id: rndWallBtn
|
||||
buttonRadius: Appearance.rounding.small
|
||||
materialIcon: "wallpaper"
|
||||
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
|
||||
onClicked: {
|
||||
console.log(konachanWallProc.command.join(" "))
|
||||
konachanWallProc.running = true;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
|
||||
}
|
||||
}
|
||||
RippleButtonWithIcon {
|
||||
materialIcon: "wallpaper"
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Pick wallpaper image on your system")
|
||||
}
|
||||
onClicked: {
|
||||
Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`)
|
||||
}
|
||||
mainContentComponent: Component {
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
StyledText {
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
text: Translation.tr("Choose file")
|
||||
color: Appearance.colors.colOnSecondaryContainer
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 3
|
||||
KeyboardKey {
|
||||
key: "Ctrl"
|
||||
}
|
||||
KeyboardKey {
|
||||
key: ""
|
||||
}
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: "+"
|
||||
}
|
||||
KeyboardKey {
|
||||
key: "T"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: 5
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: Translation.tr("Alternatively use /dark, /light, /img in the launcher")
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContentSection {
|
||||
title: Translation.tr("Decorations & Effects")
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Transparency")
|
||||
|
||||
ConfigRow {
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Enable")
|
||||
checked: Config.options.appearance.transparency
|
||||
onCheckedChanged: {
|
||||
Config.options.appearance.transparency = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Might look ass. Unsupported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Fake screen rounding")
|
||||
|
||||
ButtonGroup {
|
||||
id: fakeScreenRoundingButtonGroup
|
||||
property int selectedPolicy: Config.options.appearance.fakeScreenRounding
|
||||
spacing: 2
|
||||
SelectionGroupButton {
|
||||
property int value: 0
|
||||
leftmost: true
|
||||
buttonText: Translation.tr("No")
|
||||
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
|
||||
onClicked: {
|
||||
Config.options.appearance.fakeScreenRounding = value;
|
||||
}
|
||||
}
|
||||
SelectionGroupButton {
|
||||
property int value: 1
|
||||
buttonText: Translation.tr("Yes")
|
||||
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
|
||||
onClicked: {
|
||||
Config.options.appearance.fakeScreenRounding = value;
|
||||
}
|
||||
}
|
||||
SelectionGroupButton {
|
||||
property int value: 2
|
||||
rightmost: true
|
||||
buttonText: Translation.tr("When not fullscreen")
|
||||
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
|
||||
onClicked: {
|
||||
Config.options.appearance.fakeScreenRounding = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Shell windows")
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Title bar")
|
||||
checked: Config.options.windows.showTitlebar
|
||||
onCheckedChanged: {
|
||||
Config.options.windows.showTitlebar = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Center title")
|
||||
checked: Config.options.windows.centerTitle
|
||||
onCheckedChanged: {
|
||||
Config.options.windows.centerTitle = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentSubsection {
|
||||
title: Translation.tr("Wallpaper parallax")
|
||||
|
||||
ConfigRow {
|
||||
uniform: true
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Depends on workspace")
|
||||
checked: Config.options.background.parallax.enableWorkspace
|
||||
onCheckedChanged: {
|
||||
Config.options.background.parallax.enableWorkspace = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
text: Translation.tr("Depends on sidebars")
|
||||
checked: Config.options.background.parallax.enableSidebar
|
||||
onCheckedChanged: {
|
||||
Config.options.background.parallax.enableSidebar = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
text: Translation.tr("Preferred wallpaper zoom (%)")
|
||||
value: Config.options.background.parallax.workspaceZoom * 100
|
||||
from: 100
|
||||
to: 150
|
||||
stepSize: 1
|
||||
onValueChanged: {
|
||||
console.log(value/100)
|
||||
Config.options.background.parallax.workspaceZoom = value / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property var scopeRoot
|
||||
anchors.fill: parent
|
||||
property var tabButtonList: [
|
||||
...(Config.options.policies.ai !== 0 ? [{"icon": "neurology", "name": Translation.tr("Intelligence")}] : []),
|
||||
{"icon": "translate", "name": Translation.tr("Translator")},
|
||||
...(Config.options.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : [])
|
||||
]
|
||||
property int selectedTab: 0
|
||||
|
||||
function focusActiveItem() {
|
||||
swipeView.currentItem.forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers === Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_PageUp) {
|
||||
root.selectedTab = Math.max(root.selectedTab - 1, 0)
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_Tab) {
|
||||
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length;
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_Backtab) {
|
||||
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: sidebarPadding
|
||||
|
||||
spacing: sidebarPadding
|
||||
|
||||
PrimaryTabBar { // Tab strip
|
||||
id: tabBar
|
||||
tabButtonList: root.tabButtonList
|
||||
externalTrackedTab: root.selectedTab
|
||||
function onCurrentIndexChanged(currentIndex) {
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView { // Content pages
|
||||
id: swipeView
|
||||
Layout.topMargin: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
|
||||
currentIndex: tabBar.externalTrackedTab
|
||||
onCurrentIndexChanged: {
|
||||
tabBar.enableIndicatorAnimation = true
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
radius: Appearance.rounding.small
|
||||
}
|
||||
}
|
||||
|
||||
contentChildren: [
|
||||
...(Config.options.policies.ai !== 0 ? [aiChat.createObject()] : []),
|
||||
translator.createObject(),
|
||||
...(Config.options.policies.weeb === 0 ? [] : [anime.createObject()])
|
||||
]
|
||||
}
|
||||
|
||||
Component {
|
||||
id: aiChat
|
||||
AiChat {}
|
||||
}
|
||||
Component {
|
||||
id: translator
|
||||
Translator {}
|
||||
}
|
||||
Component {
|
||||
id: anime
|
||||
Anime {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property int messageIndex
|
||||
property var messageData
|
||||
property var messageInputField
|
||||
|
||||
property real messagePadding: 7
|
||||
property real contentSpacing: 3
|
||||
|
||||
property bool enableMouseSelection: false
|
||||
property bool renderMarkdown: true
|
||||
property bool editing: false
|
||||
|
||||
property list<var> messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content)
|
||||
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
function saveMessage() {
|
||||
if (!root.editing) return;
|
||||
// Get all Loader children (each represents a segment)
|
||||
const segments = messageContentColumnLayout.children
|
||||
.map(child => child.segment)
|
||||
.filter(segment => (segment));
|
||||
|
||||
// Reconstruct markdown
|
||||
const newContent = segments.map(segment => {
|
||||
if (segment.type === "code") {
|
||||
const lang = segment.lang ? segment.lang : "";
|
||||
// Remove trailing newlines
|
||||
const code = segment.content.replace(/\n+$/, "");
|
||||
return "```" + lang + "\n" + code + "\n```";
|
||||
} else {
|
||||
return segment.content;
|
||||
}
|
||||
}).join("");
|
||||
|
||||
root.editing = false
|
||||
root.messageData.content = newContent;
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if ( // Prevent de-select
|
||||
event.key === Qt.Key_Control ||
|
||||
event.key == Qt.Key_Shift ||
|
||||
event.key == Qt.Key_Alt ||
|
||||
event.key == Qt.Key_Meta
|
||||
) {
|
||||
event.accepted = true
|
||||
}
|
||||
// Ctrl + S to save
|
||||
if ((event.key === Qt.Key_S) && event.modifiers == Qt.ControlModifier) {
|
||||
root.saveMessage();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout { // Main layout of the whole thing
|
||||
id: columnLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: messagePadding
|
||||
spacing: root.contentSpacing
|
||||
|
||||
RowLayout { // Header
|
||||
spacing: 15
|
||||
Layout.fillWidth: true
|
||||
|
||||
Rectangle { // Name
|
||||
id: nameWrapper
|
||||
color: Appearance.colors.colSecondaryContainer
|
||||
// color: "transparent"
|
||||
radius: Appearance.rounding.small
|
||||
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
RowLayout {
|
||||
id: nameRowLayout
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
spacing: 7
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: messageData?.role == 'assistant' ? modelIcon.width : roleIcon.implicitWidth
|
||||
implicitHeight: messageData?.role == 'assistant' ? modelIcon.height : roleIcon.implicitHeight
|
||||
|
||||
CustomIcon {
|
||||
id: modelIcon
|
||||
anchors.centerIn: parent
|
||||
visible: messageData?.role == 'assistant' && Ai.models[messageData?.model].icon
|
||||
width: Appearance.font.pixelSize.large
|
||||
height: Appearance.font.pixelSize.large
|
||||
source: messageData?.role == 'assistant' ? Ai.models[messageData?.model].icon :
|
||||
messageData?.role == 'user' ? 'linux-symbolic' : 'desktop-symbolic'
|
||||
|
||||
colorize: true
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
id: roleIcon
|
||||
anchors.centerIn: parent
|
||||
visible: !modelIcon.visible
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
text: messageData?.role == 'user' ? 'person' :
|
||||
messageData?.role == 'interface' ? 'settings' :
|
||||
messageData?.role == 'assistant' ? 'neurology' :
|
||||
'computer'
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: providerName
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
text: messageData?.role == 'assistant' ? Ai.models[messageData?.model].name :
|
||||
(messageData?.role == 'user' && SystemInfo.username) ? SystemInfo.username :
|
||||
Translation.tr("Interface")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button { // Not visible to model
|
||||
id: modelVisibilityIndicator
|
||||
visible: messageData?.role == 'interface'
|
||||
implicitWidth: 16
|
||||
implicitHeight: 30
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
background: Item
|
||||
|
||||
MaterialSymbol {
|
||||
id: notVisibleToModelText
|
||||
anchors.centerIn: parent
|
||||
iconSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colSubtext
|
||||
text: "visibility_off"
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Not visible to model")
|
||||
}
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
spacing: 5
|
||||
|
||||
AiMessageControlButton {
|
||||
id: copyButton
|
||||
buttonIcon: activated ? "inventory" : "content_copy"
|
||||
|
||||
onClicked: {
|
||||
Quickshell.clipboardText = root.messageData?.content
|
||||
copyButton.activated = true
|
||||
copyIconTimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: copyIconTimer
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
copyButton.activated = false
|
||||
}
|
||||
}
|
||||
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Copy")
|
||||
}
|
||||
}
|
||||
AiMessageControlButton {
|
||||
id: editButton
|
||||
activated: root.editing
|
||||
enabled: root.messageData?.done ?? false
|
||||
buttonIcon: "edit"
|
||||
onClicked: {
|
||||
root.editing = !root.editing
|
||||
if (!root.editing) { // Save changes
|
||||
root.saveMessage()
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: root.editing ? Translation.tr("Save") : Translation.tr("Edit")
|
||||
}
|
||||
}
|
||||
AiMessageControlButton {
|
||||
id: toggleMarkdownButton
|
||||
activated: !root.renderMarkdown
|
||||
buttonIcon: "code"
|
||||
onClicked: {
|
||||
root.renderMarkdown = !root.renderMarkdown
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("View Markdown source")
|
||||
}
|
||||
}
|
||||
AiMessageControlButton {
|
||||
id: deleteButton
|
||||
buttonIcon: "close"
|
||||
onClicked: {
|
||||
Ai.removeMessage(root.messageIndex)
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout { // Message content
|
||||
id: messageContentColumnLayout
|
||||
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: root.messageBlocks.length
|
||||
delegate: Loader {
|
||||
required property int index
|
||||
property var thisBlock: root.messageBlocks[index]
|
||||
Layout.fillWidth: true
|
||||
// property var segment: thisBlock
|
||||
property var segmentContent: thisBlock.content
|
||||
property var segmentLang: thisBlock.lang
|
||||
property var messageData: root.messageData
|
||||
property var editing: root.editing
|
||||
property var renderMarkdown: root.renderMarkdown
|
||||
property var enableMouseSelection: root.enableMouseSelection
|
||||
property bool thinking: root.messageData?.thinking ?? true
|
||||
property bool done: root.messageData?.done ?? false
|
||||
property bool completed: thisBlock.completed ?? false
|
||||
|
||||
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
|
||||
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
|
||||
"MessageTextBlock.qml"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flow { // Annotations
|
||||
id: annotationFlowLayout
|
||||
visible: root.messageData?.annotationSources?.length > 0
|
||||
spacing: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.messageData?.annotationSources || []
|
||||
}
|
||||
delegate: AnnotationSourceButton {
|
||||
id: annotationButton
|
||||
displayText: modelData.text
|
||||
url: modelData.url
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Hyprland
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
// These are needed on the parent loader
|
||||
property bool editing: parent?.editing ?? false
|
||||
property bool renderMarkdown: parent?.renderMarkdown ?? true
|
||||
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
|
||||
property string segmentContent: parent?.segmentContent ?? ({})
|
||||
property var messageData: parent?.messageData ?? {}
|
||||
property bool done: parent?.done ?? true
|
||||
property list<string> renderedLatexHashes: []
|
||||
|
||||
property string renderedSegmentContent: ""
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Timer {
|
||||
id: renderTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
renderLatex()
|
||||
for (const hash of renderedLatexHashes) {
|
||||
handleRenderedLatex(hash, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderLatex() {
|
||||
// Regex for $...$, $$...$$, \[...\]
|
||||
// Note: This is a simple approach and may need refinement for edge cases
|
||||
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])|(\\\(([\s\S]+?)\\\))/g;
|
||||
let match;
|
||||
while ((match = regex.exec(segmentContent)) !== null) {
|
||||
let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8];
|
||||
if (expression) {
|
||||
Qt.callLater(() => {
|
||||
const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim());
|
||||
if (!renderedLatexHashes.includes(renderHash)) {
|
||||
renderedLatexHashes.push(renderHash);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRenderedLatex(hash, force = false) {
|
||||
if (renderedLatexHashes.includes(hash) || force) {
|
||||
const imagePath = LatexRenderer.renderedImagePaths[hash];
|
||||
const markdownImage = ``;
|
||||
|
||||
const expression = LatexRenderer.processedExpressions[hash];
|
||||
renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage);
|
||||
}
|
||||
}
|
||||
|
||||
onDoneChanged: {
|
||||
renderTimer.restart();
|
||||
}
|
||||
onEditingChanged: {
|
||||
if (!editing) {
|
||||
renderLatex()
|
||||
} else {
|
||||
// console.log("Editing mode enabled", segmentContent)
|
||||
textArea.text = segmentContent
|
||||
}
|
||||
}
|
||||
|
||||
onSegmentContentChanged: {
|
||||
// console.log("Segment content changed: " + segmentContent);
|
||||
renderedSegmentContent = segmentContent;
|
||||
if (!root.editing && segmentContent) {
|
||||
root.renderLatex();
|
||||
}
|
||||
}
|
||||
|
||||
onRenderedSegmentContentChanged: {
|
||||
// console.log("Rendered segment content changed: " + renderedSegmentContent);
|
||||
if (renderedSegmentContent) {
|
||||
textArea.text = renderedSegmentContent;
|
||||
}
|
||||
}
|
||||
|
||||
// When something finishes rendering
|
||||
// 1. Check if the hash is in the list
|
||||
// 2. If it is, replace the expression with the image path
|
||||
Connections {
|
||||
target: LatexRenderer
|
||||
function onRenderFinished(hash, imagePath) {
|
||||
const expression = LatexRenderer.processedExpressions[hash];
|
||||
// console.log("Render finished: " + hash + " " + expression);
|
||||
handleRenderedLatex(hash);
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
|
||||
Layout.fillWidth: true
|
||||
readOnly: !editing
|
||||
selectByMouse: enableMouseSelection || editing
|
||||
renderType: Text.NativeRendering
|
||||
font.family: Appearance.font.family.reading
|
||||
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
selectionColor: Appearance.colors.colSecondaryContainer
|
||||
wrapMode: TextEdit.Wrap
|
||||
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
|
||||
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
|
||||
text: Translation.tr("Waiting for response...")
|
||||
|
||||
onTextChanged: {
|
||||
if (!root.editing) return
|
||||
segmentContent = text
|
||||
}
|
||||
|
||||
onLinkActivated: (link) => {
|
||||
Qt.openUrlExternally(link)
|
||||
Hyprland.dispatch("global quickshell:sidebarLeftClose")
|
||||
}
|
||||
|
||||
MouseArea { // Pointing hand for links
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // Only for hover
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor :
|
||||
(enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import "./notifications"
|
||||
import "./volumeMixer"
|
||||
import qs
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
radius: Appearance.rounding.normal
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
property int selectedTab: 0
|
||||
property var tabButtonList: [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
root.selectedTab = Math.max(root.selectedTab - 1, 0)
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
if (event.modifiers === Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.margins: 5
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
PrimaryTabBar {
|
||||
id: tabBar
|
||||
tabButtonList: root.tabButtonList
|
||||
externalTrackedTab: root.selectedTab
|
||||
|
||||
function onCurrentIndexChanged(currentIndex) {
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
Layout.topMargin: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
currentIndex: root.selectedTab
|
||||
onCurrentIndexChanged: {
|
||||
tabBar.enableIndicatorAnimation = true
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
radius: Appearance.rounding.small
|
||||
}
|
||||
}
|
||||
|
||||
NotificationList {}
|
||||
VolumeMixer {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import "./quickToggles/"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
property int sidebarWidth: Appearance.sizes.sidebarWidth
|
||||
property int sidebarPadding: 12
|
||||
property string settingsQmlPath: Quickshell.configPath("settings.qml")
|
||||
|
||||
PanelWindow {
|
||||
id: sidebarRoot
|
||||
visible: GlobalStates.sidebarRightOpen
|
||||
|
||||
function hide() {
|
||||
GlobalStates.sidebarRightOpen = false
|
||||
}
|
||||
|
||||
exclusiveZone: 0
|
||||
implicitWidth: sidebarWidth
|
||||
WlrLayershell.namespace: "quickshell:sidebarRight"
|
||||
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab
|
||||
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
windows: [ sidebarRoot ]
|
||||
active: GlobalStates.sidebarRightOpen
|
||||
onCleared: () => {
|
||||
if (!active) sidebarRoot.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: sidebarContentLoader
|
||||
active: GlobalStates.sidebarRightOpen
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
topMargin: Appearance.sizes.hyprlandGapsOut
|
||||
rightMargin: Appearance.sizes.hyprlandGapsOut
|
||||
bottomMargin: Appearance.sizes.hyprlandGapsOut
|
||||
leftMargin: Appearance.sizes.elevationMargin
|
||||
}
|
||||
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
|
||||
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
|
||||
|
||||
focus: GlobalStates.sidebarRightOpen
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
sidebarRoot.hide();
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: sidebarRightBackground.implicitHeight
|
||||
implicitWidth: sidebarRightBackground.implicitWidth
|
||||
|
||||
StyledRectangularShadow {
|
||||
target: sidebarRightBackground
|
||||
}
|
||||
Rectangle {
|
||||
id: sidebarRightBackground
|
||||
|
||||
anchors.fill: parent
|
||||
implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2
|
||||
implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
|
||||
color: Appearance.colors.colLayer0
|
||||
border.width: 1
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: sidebarPadding
|
||||
spacing: sidebarPadding
|
||||
|
||||
RowLayout {
|
||||
Layout.fillHeight: false
|
||||
spacing: 10
|
||||
Layout.margins: 10
|
||||
Layout.topMargin: 5
|
||||
Layout.bottomMargin: 0
|
||||
|
||||
Item {
|
||||
implicitWidth: distroIcon.width
|
||||
implicitHeight: distroIcon.height
|
||||
CustomIcon {
|
||||
id: distroIcon
|
||||
width: 25
|
||||
height: 25
|
||||
source: SystemInfo.distroIcon
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: distroIcon
|
||||
source: distroIcon
|
||||
color: Appearance.colors.colOnLayer0
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
color: Appearance.colors.colOnLayer0
|
||||
text: Translation.tr("Uptime: %1").arg(DateTime.uptime)
|
||||
textFormat: Text.MarkdownText
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "restart_alt"
|
||||
onClicked: {
|
||||
Hyprland.dispatch("reload")
|
||||
Quickshell.reload(true)
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Reload Hyprland & Quickshell")
|
||||
}
|
||||
}
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "settings"
|
||||
onClicked: {
|
||||
Hyprland.dispatch("global quickshell:sidebarRightClose")
|
||||
Quickshell.execDetached(["qs", "-p", root.settingsQmlPath])
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Settings")
|
||||
}
|
||||
}
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "power_settings_new"
|
||||
onClicked: {
|
||||
Hyprland.dispatch("global quickshell:sessionOpen")
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Session")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 5
|
||||
padding: 5
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
NetworkToggle {}
|
||||
BluetoothToggle {}
|
||||
NightLight {}
|
||||
GameMode {}
|
||||
IdleInhibitor {}
|
||||
EasyEffectsToggle {}
|
||||
CloudflareWarp {}
|
||||
}
|
||||
|
||||
// Center widget group
|
||||
CenterWidgetGroup {
|
||||
focus: sidebarRoot.visible
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
BottomWidgetGroup {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: false
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "sidebarRight"
|
||||
|
||||
function toggle(): void {
|
||||
GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;
|
||||
if(GlobalStates.sidebarRightOpen) Notifications.timeoutAll();
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
GlobalStates.sidebarRightOpen = false;
|
||||
}
|
||||
|
||||
function open(): void {
|
||||
GlobalStates.sidebarRightOpen = true;
|
||||
Notifications.timeoutAll();
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "sidebarRightToggle"
|
||||
description: "Toggles right sidebar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;
|
||||
if(GlobalStates.sidebarRightOpen) Notifications.timeoutAll();
|
||||
}
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "sidebarRightOpen"
|
||||
description: "Opens right sidebar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.sidebarRightOpen = true;
|
||||
Notifications.timeoutAll();
|
||||
}
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "sidebarRightClose"
|
||||
description: "Closes right sidebar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.sidebarRightOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import qs
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
NotificationListView { // Scrollable window
|
||||
id: listview
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: statusRow.top
|
||||
anchors.bottomMargin: 5
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: listview.width
|
||||
height: listview.height
|
||||
radius: Appearance.rounding.normal
|
||||
}
|
||||
}
|
||||
|
||||
popup: false
|
||||
}
|
||||
|
||||
// Placeholder when list is empty
|
||||
Item {
|
||||
anchors.fill: listview
|
||||
|
||||
visible: opacity > 0
|
||||
opacity: (Notifications.list.length === 0) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.menuDecel.duration
|
||||
easing.type: Appearance.animation.menuDecel.type
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
MaterialSymbol {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
iconSize: 55
|
||||
color: Appearance.m3colors.m3outline
|
||||
text: "notifications_active"
|
||||
}
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
color: Appearance.m3colors.m3outline
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Translation.tr("No notifications")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: statusRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(
|
||||
controls.implicitHeight,
|
||||
statusText.implicitHeight
|
||||
)
|
||||
|
||||
StyledText {
|
||||
id: statusText
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 10
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Translation.tr("%1 notifications").arg(Notifications.list.length)
|
||||
|
||||
opacity: Notifications.list.length > 0 ? 1 : 0
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: controls
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 5
|
||||
|
||||
NotificationStatusButton {
|
||||
buttonIcon: "notifications_paused"
|
||||
buttonText: Translation.tr("Silent")
|
||||
toggled: Notifications.silent
|
||||
onClicked: () => {
|
||||
Notifications.silent = !Notifications.silent;
|
||||
}
|
||||
}
|
||||
NotificationStatusButton {
|
||||
buttonIcon: "clear_all"
|
||||
buttonText: Translation.tr("Clear")
|
||||
onClicked: () => {
|
||||
Notifications.discardAllNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
|
||||
QuickToggleButton {
|
||||
toggled: Bluetooth.bluetoothEnabled
|
||||
buttonIcon: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled"
|
||||
onClicked: {
|
||||
toggleBluetooth.running = true
|
||||
}
|
||||
altAction: () => {
|
||||
Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`])
|
||||
Hyprland.dispatch("global quickshell:sidebarRightClose")
|
||||
}
|
||||
Process {
|
||||
id: toggleBluetooth
|
||||
command: ["bash", "-c", `bluetoothctl power ${Bluetooth.bluetoothEnabled ? "off" : "on"}`]
|
||||
onRunningChanged: {
|
||||
if(!running) {
|
||||
Bluetooth.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("%1 | Right-click to configure").arg(
|
||||
(Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ?
|
||||
Bluetooth.bluetoothDeviceName : Translation.tr("Bluetooth"))
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import qs.modules.common.widgets
|
||||
import qs
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
QuickToggleButton {
|
||||
id: root
|
||||
toggled: false
|
||||
visible: false
|
||||
buttonIcon: "instant_mix"
|
||||
|
||||
onClicked: {
|
||||
if (toggled) {
|
||||
root.toggled = false
|
||||
Quickshell.execDetached(["pkill", "easyeffects"])
|
||||
} else {
|
||||
root.toggled = true
|
||||
Quickshell.execDetached(["easyeffects", "--gapplication-service"])
|
||||
}
|
||||
}
|
||||
|
||||
altAction: () => {
|
||||
Quickshell.execDetached(["easyeffects"])
|
||||
Hyprland.dispatch("global quickshell:sidebarRightClose")
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fetchAvailability
|
||||
running: true
|
||||
command: ["bash", "-c", "command -v easyeffects"]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.visible = exitCode === 0
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fetchActiveState
|
||||
running: true
|
||||
command: ["pidof", "easyeffects"]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.toggled = exitCode === 0
|
||||
}
|
||||
}
|
||||
|
||||
StyledToolTip {
|
||||
content: Translation.tr("EasyEffects | Right-click to configure")
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
|
||||
QuickToggleButton {
|
||||
id: root
|
||||
toggled: false
|
||||
buttonIcon: "coffee"
|
||||
onClicked: {
|
||||
if (toggled) {
|
||||
root.toggled = false
|
||||
Quickshell.execDetached(["pkill", "wayland-idle"]) // pkill doesn't accept too long names
|
||||
} else {
|
||||
root.toggled = true
|
||||
Quickshell.execDetached([`${Directories.scriptPath}/wayland-idle-inhibitor.py`])
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: fetchActiveState
|
||||
running: true
|
||||
command: ["pidof", "wayland-idle-inhibitor.py"]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.toggled = exitCode === 0
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Keep system awake")
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import "../"
|
||||
import qs
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
|
||||
QuickToggleButton {
|
||||
toggled: Network.networkName.length > 0 && Network.networkName != "lo"
|
||||
buttonIcon: Network.materialSymbol
|
||||
onClicked: {
|
||||
toggleNetwork.running = true
|
||||
}
|
||||
altAction: () => {
|
||||
Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])
|
||||
Hyprland.dispatch("global quickshell:sidebarRightClose")
|
||||
}
|
||||
Process {
|
||||
id: toggleNetwork
|
||||
command: ["bash", "-c", "nmcli radio wifi | grep -q enabled && nmcli radio wifi off || nmcli radio wifi on"]
|
||||
onRunningChanged: {
|
||||
if(!running) {
|
||||
Network.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs
|
||||
import Quickshell.Io
|
||||
|
||||
QuickToggleButton {
|
||||
id: nightLightButton
|
||||
property bool enabled: false
|
||||
toggled: enabled
|
||||
buttonIcon: "nightlight"
|
||||
onClicked: {
|
||||
nightLightButton.enabled = !nightLightButton.enabled
|
||||
if (enabled) {
|
||||
nightLightOn.startDetached()
|
||||
}
|
||||
else {
|
||||
nightLightOff.startDetached()
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: nightLightOn
|
||||
command: ["gammastep"]
|
||||
}
|
||||
Process {
|
||||
id: nightLightOff
|
||||
command: ["pkill", "gammastep"]
|
||||
}
|
||||
Process {
|
||||
id: updateNightLightState
|
||||
running: true
|
||||
command: ["pidof", "gammastep"]
|
||||
stdout: SplitParser {
|
||||
onRead: (data) => { // if not empty then set toggled to true
|
||||
nightLightButton.enabled = data.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: Translation.tr("Night Light")
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import qs
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property bool showDeviceSelector: false
|
||||
property bool deviceSelectorInput
|
||||
property int dialogMargins: 16
|
||||
property PwNode selectedDevice
|
||||
readonly property list<PwNode> appPwNodes: Pipewire.nodes.values.filter((node) => {
|
||||
// return node.type == "21" // Alternative, not as clean
|
||||
return node.isSink && node.isStream
|
||||
})
|
||||
|
||||
function showDeviceSelectorDialog(input: bool) {
|
||||
root.selectedDevice = null
|
||||
root.showDeviceSelector = true
|
||||
root.deviceSelectorInput = input
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
// Close dialog on pressing Esc if open
|
||||
if (event.key === Qt.Key_Escape && root.showDeviceSelector) {
|
||||
root.showDeviceSelector = false
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ListView {
|
||||
id: listView
|
||||
model: root.appPwNodes
|
||||
clip: true
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 10
|
||||
bottomMargin: 10
|
||||
}
|
||||
spacing: 6
|
||||
|
||||
delegate: VolumeMixerEntry {
|
||||
// Layout.fillWidth: true
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: 10
|
||||
rightMargin: 10
|
||||
}
|
||||
required property var modelData
|
||||
node: modelData
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder when list is empty
|
||||
Item {
|
||||
anchors.fill: listView
|
||||
|
||||
visible: opacity > 0
|
||||
opacity: (root.appPwNodes.length === 0) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.menuDecel.duration
|
||||
easing.type: Appearance.animation.menuDecel.type
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
MaterialSymbol {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
iconSize: 55
|
||||
color: Appearance.m3colors.m3outline
|
||||
text: "brand_awareness"
|
||||
}
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
color: Appearance.m3colors.m3outline
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Translation.tr("No audio source")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
implicitHeight: 1
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
||||
// Device selector
|
||||
ButtonGroup {
|
||||
id: deviceSelectorRowLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
AudioDeviceSelectorButton {
|
||||
Layout.fillWidth: true
|
||||
input: false
|
||||
onClicked: root.showDeviceSelectorDialog(input)
|
||||
}
|
||||
AudioDeviceSelectorButton {
|
||||
Layout.fillWidth: true
|
||||
input: true
|
||||
onClicked: root.showDeviceSelectorDialog(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Device selector dialog
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
z: 9999
|
||||
|
||||
visible: opacity > 0
|
||||
opacity: root.showDeviceSelector ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.elementMoveFast.duration
|
||||
easing.type: Appearance.animation.elementMoveFast.type
|
||||
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Scrim
|
||||
id: scrimOverlay
|
||||
anchors.fill: parent
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colScrim
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // The dialog
|
||||
id: dialog
|
||||
color: Appearance.colors.colSurfaceContainerHigh
|
||||
radius: Appearance.rounding.normal
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: 30
|
||||
implicitHeight: dialogColumnLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: dialogColumnLayout
|
||||
anchors.fill: parent
|
||||
spacing: 16
|
||||
|
||||
StyledText {
|
||||
id: dialogTitle
|
||||
Layout.topMargin: dialogMargins
|
||||
Layout.leftMargin: dialogMargins
|
||||
Layout.rightMargin: dialogMargins
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
font.pixelSize: Appearance.font.pixelSize.larger
|
||||
text: root.deviceSelectorInput ? Translation.tr("Select input device") : Translation.tr("Select output device")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: Appearance.m3colors.m3outline
|
||||
implicitHeight: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: dialogMargins
|
||||
Layout.rightMargin: dialogMargins
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: dialogFlickable
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
implicitHeight: Math.min(scrimOverlay.height - dialogMargins * 8 - dialogTitle.height - dialogButtonsRowLayout.height, devicesColumnLayout.implicitHeight)
|
||||
|
||||
contentHeight: devicesColumnLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: devicesColumnLayout
|
||||
anchors.fill: parent
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Pipewire.nodes.values.filter(node => {
|
||||
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
|
||||
})
|
||||
}
|
||||
|
||||
// This could and should be refractored, but all data becomes null when passed wtf
|
||||
delegate: StyledRadioButton {
|
||||
id: radioButton
|
||||
required property var modelData
|
||||
Layout.leftMargin: root.dialogMargins
|
||||
Layout.rightMargin: root.dialogMargins
|
||||
Layout.fillWidth: true
|
||||
|
||||
description: modelData.description
|
||||
checked: modelData.id === Pipewire.defaultAudioSink?.id
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShowDeviceSelectorChanged() {
|
||||
if(!root.showDeviceSelector) return;
|
||||
radioButton.checked = (modelData.id === Pipewire.defaultAudioSink?.id)
|
||||
}
|
||||
}
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked) {
|
||||
root.selectedDevice = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
implicitHeight: dialogMargins
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: Appearance.m3colors.m3outline
|
||||
implicitHeight: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: dialogMargins
|
||||
Layout.rightMargin: dialogMargins
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: dialogButtonsRowLayout
|
||||
Layout.bottomMargin: dialogMargins
|
||||
Layout.leftMargin: dialogMargins
|
||||
Layout.rightMargin: dialogMargins
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
DialogButton {
|
||||
buttonText: Translation.tr("Cancel")
|
||||
onClicked: {
|
||||
root.showDeviceSelector = false
|
||||
}
|
||||
}
|
||||
DialogButton {
|
||||
buttonText: Translation.tr("OK")
|
||||
onClicked: {
|
||||
root.showDeviceSelector = false
|
||||
if (root.selectedDevice) {
|
||||
if (root.deviceSelectorInput) {
|
||||
Pipewire.preferredDefaultAudioSource = root.selectedDevice
|
||||
} else {
|
||||
Pipewire.preferredDefaultAudioSink = root.selectedDevice
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,552 +0,0 @@
|
||||
//@ pragma UseQApplication
|
||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
|
||||
|
||||
// Adjust this to make it smaller or larger
|
||||
//@ pragma Env QT_SCALE_FACTOR=1
|
||||
|
||||
pragma ComponentBehavior: "Bound"
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
property string screenshotDir: Directories.screenshotTemp
|
||||
property color overlayColor: "#77111111"
|
||||
property color genericContentColor: Qt.alpha(root.overlayColor, 0.9)
|
||||
property color genericContentForeground: "#ddffffff"
|
||||
property color selectionBorderColor: "#ddf1f1f1"
|
||||
property color selectionFillColor: "#33ffffff"
|
||||
property color windowBorderColor: "#dda0c0da"
|
||||
property color windowFillColor: "#22a0c0da"
|
||||
property color imageBorderColor: "#ddf1d1ff"
|
||||
property color imageFillColor: "#33f1d1ff"
|
||||
property color onBorderColor: "#ff000000"
|
||||
property real standardRounding: 4
|
||||
readonly property var windows: HyprlandData.windowList
|
||||
readonly property var layers: HyprlandData.layers
|
||||
readonly property real falsePositivePreventionRatio: 0.5
|
||||
|
||||
// Force initialization of some singletons
|
||||
Component.onCompleted: {
|
||||
MaterialThemeLoader.reapplyTheme();
|
||||
}
|
||||
|
||||
component TargetRegion: Rectangle {
|
||||
id: regionRect
|
||||
property bool showIcon: false
|
||||
property bool targeted: false
|
||||
property color borderColor
|
||||
property color fillColor: "transparent"
|
||||
property string text: ""
|
||||
property real textPadding: 10
|
||||
z: 2
|
||||
color: fillColor
|
||||
border.color: borderColor
|
||||
border.width: targeted ? 3 : 1
|
||||
radius: root.standardRounding
|
||||
|
||||
Rectangle {
|
||||
id: regionLabelBackground
|
||||
property real verticalPadding: 5
|
||||
property real horizontalPadding: 10
|
||||
radius: 10
|
||||
color: root.genericContentColor
|
||||
border.width: 1
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: regionRect.textPadding
|
||||
leftMargin: regionRect.textPadding
|
||||
}
|
||||
implicitWidth: regionInfoRow.implicitWidth + horizontalPadding * 2
|
||||
implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2
|
||||
RowLayout {
|
||||
id: regionInfoRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
Loader {
|
||||
id: regionIconLoader
|
||||
active: regionRect.showIcon
|
||||
visible: active
|
||||
sourceComponent: IconImage {
|
||||
implicitSize: Appearance.font.pixelSize.larger
|
||||
source: Quickshell.iconPath(AppSearch.guessIcon(regionRect.text), "image-missing")
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: regionText
|
||||
text: regionRect.text
|
||||
color: root.genericContentForeground
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: panelWindow
|
||||
required property var modelData
|
||||
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(modelData)
|
||||
readonly property real monitorScale: hyprlandMonitor.scale
|
||||
readonly property real monitorOffsetX: hyprlandMonitor.x
|
||||
readonly property real monitorOffsetY: hyprlandMonitor.y
|
||||
property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0
|
||||
property string screenshotPath: `${root.screenshotDir}/image-${modelData.name}`
|
||||
property real dragStartX: 0
|
||||
property real dragStartY: 0
|
||||
property real draggingX: 0
|
||||
property real draggingY: 0
|
||||
property real dragDiffX: 0
|
||||
property real dragDiffY: 0
|
||||
property bool draggedAway: (dragDiffX !== 0 || dragDiffY !== 0)
|
||||
property bool dragging: false
|
||||
property var mouseButton: null
|
||||
property var imageRegions: []
|
||||
readonly property list<var> windowRegions: filterWindowRegionsByLayers(
|
||||
root.windows.filter(w => w.workspace.id === panelWindow.activeWorkspaceId),
|
||||
panelWindow.layerRegions
|
||||
).map(window => {
|
||||
return {
|
||||
at: [window.at[0] - panelWindow.monitorOffsetX, window.at[1] - panelWindow.monitorOffsetY],
|
||||
size: [window.size[0], window.size[1]],
|
||||
class: window.class,
|
||||
title: window.title,
|
||||
}
|
||||
})
|
||||
readonly property list<var> layerRegions: {
|
||||
const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name]
|
||||
const topLayers = layersOfThisMonitor.levels["2"]
|
||||
const nonBarTopLayers = topLayers
|
||||
.filter(layer => !(layer.namespace.includes(":bar") || layer.namespace.includes(":dock")))
|
||||
.map(layer => {
|
||||
return {
|
||||
at: [layer.x, layer.y],
|
||||
size: [layer.w, layer.h],
|
||||
namespace: layer.namespace,
|
||||
}
|
||||
})
|
||||
const offsetAdjustedLayers = nonBarTopLayers.map(layer => {
|
||||
return {
|
||||
at: [layer.at[0] - panelWindow.monitorOffsetX, layer.at[1] - panelWindow.monitorOffsetY],
|
||||
size: layer.size,
|
||||
namespace: layer.namespace,
|
||||
}
|
||||
});
|
||||
return offsetAdjustedLayers;
|
||||
}
|
||||
|
||||
property real targetedRegionX: -1
|
||||
property real targetedRegionY: -1
|
||||
property real targetedRegionWidth: 0
|
||||
property real targetedRegionHeight: 0
|
||||
|
||||
function intersectionOverUnion(regionA, regionB) {
|
||||
// region: { at: [x, y], size: [w, h] }
|
||||
const ax1 = regionA.at[0], ay1 = regionA.at[1];
|
||||
const ax2 = ax1 + regionA.size[0], ay2 = ay1 + regionA.size[1];
|
||||
const bx1 = regionB.at[0], by1 = regionB.at[1];
|
||||
const bx2 = bx1 + regionB.size[0], by2 = by1 + regionB.size[1];
|
||||
|
||||
const interX1 = Math.max(ax1, bx1);
|
||||
const interY1 = Math.max(ay1, by1);
|
||||
const interX2 = Math.min(ax2, bx2);
|
||||
const interY2 = Math.min(ay2, by2);
|
||||
|
||||
const interArea = Math.max(0, interX2 - interX1) * Math.max(0, interY2 - interY1);
|
||||
const areaA = (ax2 - ax1) * (ay2 - ay1);
|
||||
const areaB = (bx2 - bx1) * (by2 - by1);
|
||||
const unionArea = areaA + areaB - interArea;
|
||||
|
||||
return unionArea > 0 ? interArea / unionArea : 0;
|
||||
}
|
||||
|
||||
function filterOverlappingImageRegions(regions) {
|
||||
let keep = [];
|
||||
let removed = new Set();
|
||||
for (let i = 0; i < regions.length; ++i) {
|
||||
if (removed.has(i)) continue;
|
||||
let regionA = regions[i];
|
||||
for (let j = i + 1; j < regions.length; ++j) {
|
||||
if (removed.has(j)) continue;
|
||||
let regionB = regions[j];
|
||||
if (intersectionOverUnion(regionA, regionB) > 0) {
|
||||
// Compare areas
|
||||
let areaA = regionA.size[0] * regionA.size[1];
|
||||
let areaB = regionB.size[0] * regionB.size[1];
|
||||
if (areaA <= areaB) {
|
||||
removed.add(j);
|
||||
} else {
|
||||
removed.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < regions.length; ++i) {
|
||||
if (!removed.has(i)) keep.push(regions[i]);
|
||||
}
|
||||
return keep;
|
||||
}
|
||||
|
||||
function filterWindowRegionsByLayers(windowRegions, layerRegions) {
|
||||
return windowRegions.filter(windowRegion => {
|
||||
for (let i = 0; i < layerRegions.length; ++i) {
|
||||
if (intersectionOverUnion(windowRegion, layerRegions[i]) > 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function filterImageRegions(regions, windowRegions, threshold = 0.1) {
|
||||
// Remove image regions that overlap too much with any window region
|
||||
let filtered = regions.filter(region => {
|
||||
for (let i = 0; i < windowRegions.length; ++i) {
|
||||
if (intersectionOverUnion(region, windowRegions[i]) > threshold)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// Remove overlapping image regions, keep only the smaller one
|
||||
return filterOverlappingImageRegions(filtered);
|
||||
}
|
||||
|
||||
function updateTargetedRegion(x, y) {
|
||||
// Image regions
|
||||
const clickedRegion = panelWindow.imageRegions.find(region => {
|
||||
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
|
||||
});
|
||||
if (clickedRegion) {
|
||||
panelWindow.targetedRegionX = clickedRegion.at[0];
|
||||
panelWindow.targetedRegionY = clickedRegion.at[1];
|
||||
panelWindow.targetedRegionWidth = clickedRegion.size[0];
|
||||
panelWindow.targetedRegionHeight = clickedRegion.size[1];
|
||||
return;
|
||||
}
|
||||
|
||||
// Layer regions
|
||||
const clickedLayer = panelWindow.layerRegions.find(region => {
|
||||
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
|
||||
});
|
||||
if (clickedLayer) {
|
||||
panelWindow.targetedRegionX = clickedLayer.at[0];
|
||||
panelWindow.targetedRegionY = clickedLayer.at[1];
|
||||
panelWindow.targetedRegionWidth = clickedLayer.size[0];
|
||||
panelWindow.targetedRegionHeight = clickedLayer.size[1];
|
||||
return;
|
||||
}
|
||||
|
||||
// Window regions
|
||||
const clickedWindow = panelWindow.windowRegions.find(region => {
|
||||
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
|
||||
});
|
||||
if (clickedWindow) {
|
||||
panelWindow.targetedRegionX = clickedWindow.at[0];
|
||||
panelWindow.targetedRegionY = clickedWindow.at[1];
|
||||
panelWindow.targetedRegionWidth = clickedWindow.size[0];
|
||||
panelWindow.targetedRegionHeight = clickedWindow.size[1];
|
||||
return;
|
||||
}
|
||||
|
||||
panelWindow.targetedRegionX = -1;
|
||||
panelWindow.targetedRegionY = -1;
|
||||
panelWindow.targetedRegionWidth = 0;
|
||||
panelWindow.targetedRegionHeight = 0;
|
||||
}
|
||||
|
||||
property real regionWidth: Math.abs(draggingX - dragStartX)
|
||||
property real regionHeight: Math.abs(draggingY - dragStartY)
|
||||
property real regionX: Math.min(dragStartX, draggingX)
|
||||
property real regionY: Math.min(dragStartY, draggingY)
|
||||
|
||||
visible: false
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell:screenshot"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
anchors {
|
||||
left: true
|
||||
right: true
|
||||
top: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: screenshotProcess
|
||||
running: true
|
||||
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(modelData.name)}' '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}'`]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
panelWindow.visible = true;
|
||||
imageDetectionProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: imageDetectionProcess
|
||||
command: ["bash", "-c", `${Directories.scriptPath}/images/find_regions.py `
|
||||
+ `--hyprctl `
|
||||
+ `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' `
|
||||
+ `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} `
|
||||
+ `--max-height ${Math.round(panelWindow.screen.height * root.falsePositivePreventionRatio)} `]
|
||||
stdout: StdioCollector {
|
||||
id: imageDimensionCollector
|
||||
onStreamFinished: {
|
||||
imageRegions = filterImageRegions(
|
||||
JSON.parse(imageDimensionCollector.text),
|
||||
panelWindow.windowRegions
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: snipProc
|
||||
function snip() {
|
||||
if (panelWindow.regionWidth <= 0 || panelWindow.regionHeight <= 0) {
|
||||
console.warn("Invalid region size, skipping snip.");
|
||||
Qt.quit();
|
||||
}
|
||||
snipProc.startDetached();
|
||||
Qt.quit();
|
||||
}
|
||||
command: ["bash", "-c",
|
||||
`magick ${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)} `
|
||||
+ `-crop ${panelWindow.regionWidth * panelWindow.monitorScale}x${panelWindow.regionHeight * panelWindow.monitorScale}+${panelWindow.regionX * panelWindow.monitorScale}+${panelWindow.regionY * panelWindow.monitorScale} - `
|
||||
+ `| ${panelWindow.mouseButton === Qt.LeftButton ? "wl-copy" : "swappy -f -"}`]
|
||||
}
|
||||
|
||||
ScreencopyView {
|
||||
anchors.fill: parent
|
||||
live: false
|
||||
captureSource: modelData
|
||||
|
||||
focus: panelWindow.visible
|
||||
Keys.onPressed: (event) => { // Esc to close
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
Qt.quit();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.CrossCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
|
||||
// Controls
|
||||
onPressed: mouse => {
|
||||
panelWindow.dragStartX = mouse.x;
|
||||
panelWindow.dragStartY = mouse.y;
|
||||
panelWindow.draggingX = mouse.x;
|
||||
panelWindow.draggingY = mouse.y;
|
||||
panelWindow.dragging = true;
|
||||
panelWindow.mouseButton = mouse.button;
|
||||
}
|
||||
onReleased: mouse => {
|
||||
// Detect if it was a click
|
||||
|
||||
// Image regions
|
||||
if (panelWindow.draggingX === panelWindow.dragStartX && panelWindow.draggingY === panelWindow.dragStartY) {
|
||||
if (panelWindow.targetedRegionX >= 0 && panelWindow.targetedRegionY >= 0) {
|
||||
panelWindow.regionX = panelWindow.targetedRegionX;
|
||||
panelWindow.regionY = panelWindow.targetedRegionY;
|
||||
panelWindow.regionWidth = panelWindow.targetedRegionWidth;
|
||||
panelWindow.regionHeight = panelWindow.targetedRegionHeight;
|
||||
}
|
||||
}
|
||||
snipProc.snip();
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (panelWindow.dragging) {
|
||||
panelWindow.draggingX = mouse.x;
|
||||
panelWindow.draggingY = mouse.y;
|
||||
panelWindow.dragDiffX = mouse.x - panelWindow.dragStartX;
|
||||
panelWindow.dragDiffY = mouse.y - panelWindow.dragStartY;
|
||||
}
|
||||
panelWindow.updateTargetedRegion(mouse.x, mouse.y);
|
||||
}
|
||||
|
||||
// Overlay to darken screen
|
||||
Rectangle { // Base
|
||||
id: overlayRect
|
||||
z: 0
|
||||
anchors.fill: parent
|
||||
color: root.overlayColor
|
||||
layer.enabled: true
|
||||
}
|
||||
Rectangle {
|
||||
// TODO: Make this mask the base instead of just overlaying a border
|
||||
z: 1
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
leftMargin: panelWindow.regionX
|
||||
topMargin: panelWindow.regionY
|
||||
}
|
||||
width: panelWindow.regionWidth
|
||||
height: panelWindow.regionHeight
|
||||
color: "transparent"
|
||||
border.color: root.selectionBorderColor
|
||||
border.width: 2
|
||||
radius: root.standardRounding
|
||||
}
|
||||
|
||||
// Instructions
|
||||
Rectangle {
|
||||
anchors {
|
||||
top: parent.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
topMargin: (Appearance.sizes.barHeight - implicitHeight) / 2
|
||||
}
|
||||
|
||||
opacity: panelWindow.dragging ? 0 : 1
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
color: root.genericContentColor
|
||||
radius: 10
|
||||
border.width: 1
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
implicitWidth: instructionsRow.implicitWidth + 10 * 2
|
||||
implicitHeight: instructionsRow.implicitHeight + 5 * 2
|
||||
|
||||
RowLayout {
|
||||
id: instructionsRow
|
||||
anchors.centerIn: parent
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: screenshotRegionIcon.implicitWidth
|
||||
MaterialSymbol {
|
||||
id: screenshotRegionIcon
|
||||
anchors.centerIn: parent
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "screenshot_region"
|
||||
color: root.genericContentForeground
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
text: Translation.tr("Drag or click a region • LMB: Copy • RMB: Edit")
|
||||
color: root.genericContentForeground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Window regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: panelWindow.windowRegions
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 2
|
||||
required property var modelData
|
||||
showIcon: true
|
||||
targeted: !panelWindow.draggedAway &&
|
||||
(panelWindow.targetedRegionX === modelData.at[0]
|
||||
&& panelWindow.targetedRegionY === modelData.at[1]
|
||||
&& panelWindow.targetedRegionWidth === modelData.size[0]
|
||||
&& panelWindow.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: panelWindow.draggedAway ? 0 : 1
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
x: modelData.at[0]
|
||||
y: modelData.at[1]
|
||||
width: modelData.size[0]
|
||||
height: modelData.size[1]
|
||||
borderColor: root.windowBorderColor
|
||||
fillColor: targeted ? root.windowFillColor : "transparent"
|
||||
border.width: targeted ? 4 : 2
|
||||
text: `${modelData.class}`
|
||||
radius: Appearance.rounding.windowRounding
|
||||
}
|
||||
}
|
||||
|
||||
// Layer regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: panelWindow.layerRegions
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 3
|
||||
required property var modelData
|
||||
targeted: !panelWindow.draggedAway &&
|
||||
(panelWindow.targetedRegionX === modelData.at[0]
|
||||
&& panelWindow.targetedRegionY === modelData.at[1]
|
||||
&& panelWindow.targetedRegionWidth === modelData.size[0]
|
||||
&& panelWindow.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: panelWindow.draggedAway ? 0 : 1
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
x: modelData.at[0]
|
||||
y: modelData.at[1]
|
||||
width: modelData.size[0]
|
||||
height: modelData.size[1]
|
||||
borderColor: root.windowBorderColor
|
||||
fillColor: targeted ? root.windowFillColor : "transparent"
|
||||
border.width: targeted ? 4 : 2
|
||||
text: `${modelData.namespace}`
|
||||
radius: Appearance.rounding.windowRounding
|
||||
}
|
||||
}
|
||||
|
||||
// Image regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Config.options.screenshotTool.showContentRegions ? panelWindow.imageRegions : []
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 4
|
||||
required property var modelData
|
||||
targeted: !panelWindow.draggedAway &&
|
||||
(panelWindow.targetedRegionX === modelData.at[0]
|
||||
&& panelWindow.targetedRegionY === modelData.at[1]
|
||||
&& panelWindow.targetedRegionWidth === modelData.size[0]
|
||||
&& panelWindow.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: panelWindow.draggedAway ? 0 : 1
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
x: modelData.at[0]
|
||||
y: modelData.at[1]
|
||||
width: modelData.size[0]
|
||||
height: modelData.size[1]
|
||||
borderColor: root.imageBorderColor
|
||||
fillColor: targeted ? root.imageFillColor : "transparent"
|
||||
border.width: targeted ? 4 : 2
|
||||
text: "Content region"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Disclaimer: This script was ai-generated and went through minimal revision.
|
||||
|
||||
import os
|
||||
os.environ["OPENCV_LOG_LEVEL"] = "SILENT"
|
||||
import cv2
|
||||
import numpy as np
|
||||
import argparse
|
||||
import json
|
||||
|
||||
def center_crop(img, target_w, target_h):
|
||||
h, w = img.shape[:2]
|
||||
if w == target_w and h == target_h:
|
||||
return img
|
||||
x1 = max(0, (w - target_w) // 2)
|
||||
y1 = max(0, (h - target_h) // 2)
|
||||
x2 = x1 + target_w
|
||||
y2 = y1 + target_h
|
||||
return img[y1:y2, x1:x2]
|
||||
|
||||
def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", horizontal_padding=50, vertical_padding=50):
|
||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape
|
||||
scale = 1.0
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
if verbose:
|
||||
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
if verbose:
|
||||
print(f"Cropped image to {screen_width}x{screen_height}")
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using original image size: {orig_w}x{orig_h}")
|
||||
arr = img.astype(np.float64)
|
||||
h, w = arr.shape
|
||||
# Use OpenCV's integral for fast computation
|
||||
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
|
||||
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
|
||||
def region_sum(ii, x1, y1, x2, y2):
|
||||
total = ii[y2, x2]
|
||||
if x1 > 0:
|
||||
total -= ii[y2, x1-1]
|
||||
if y1 > 0:
|
||||
total -= ii[y1-1, x2]
|
||||
if x1 > 0 and y1 > 0:
|
||||
total += ii[y1-1, x1-1]
|
||||
return total
|
||||
min_var = None
|
||||
min_coords = (0, 0)
|
||||
area = region_width * region_height
|
||||
x_start = horizontal_padding
|
||||
y_start = vertical_padding
|
||||
x_end = w - region_width - horizontal_padding + 1
|
||||
y_end = h - region_height - vertical_padding + 1
|
||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||
x1, y1 = x, y
|
||||
x2, y2 = x + region_width - 1, y + region_height - 1
|
||||
s = region_sum(integral, x1, y1, x2, y2)
|
||||
s2 = region_sum(integral_sq, x1, y1, x2, y2)
|
||||
mean = s / area
|
||||
var = (s2 / area) - (mean ** 2)
|
||||
if (min_var is None) or (var < min_var):
|
||||
min_var = var
|
||||
min_coords = (x, y)
|
||||
return min_coords, min_var
|
||||
|
||||
def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, horizontal_padding=50, vertical_padding=50):
|
||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape
|
||||
scale = 1.0
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
if verbose:
|
||||
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
if verbose:
|
||||
print(f"Cropped image to {screen_width}x{screen_height}")
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using original image size: {orig_w}x{orig_h}")
|
||||
arr = img.astype(np.float64)
|
||||
h, w = arr.shape
|
||||
# Use OpenCV's integral for fast computation
|
||||
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
|
||||
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
|
||||
def region_sum(ii, x1, y1, x2, y2):
|
||||
total = ii[y2, x2]
|
||||
if x1 > 0:
|
||||
total -= ii[y2, x1-1]
|
||||
if y1 > 0:
|
||||
total -= ii[y1-1, x2]
|
||||
if x1 > 0 and y1 > 0:
|
||||
total += ii[y1-1, x1-1]
|
||||
return total
|
||||
min_size = 10
|
||||
max_size = min(h, int(w / aspect_ratio)) if aspect_ratio >= 1.0 else min(int(h * aspect_ratio), w)
|
||||
best = None
|
||||
best_size = min_size
|
||||
while min_size <= max_size:
|
||||
mid = (min_size + max_size) // 2
|
||||
if aspect_ratio >= 1.0:
|
||||
region_h = mid
|
||||
region_w = int(mid * aspect_ratio)
|
||||
else:
|
||||
region_w = mid
|
||||
region_h = int(mid / aspect_ratio)
|
||||
if region_w > w or region_h > h:
|
||||
max_size = mid - 1
|
||||
continue
|
||||
found = False
|
||||
x_start = horizontal_padding
|
||||
y_start = vertical_padding
|
||||
x_end = w - region_w - horizontal_padding + 1
|
||||
y_end = h - region_h - vertical_padding + 1
|
||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||
x1, y1 = x, y
|
||||
x2, y2 = x + region_w - 1, y + region_h - 1
|
||||
s = region_sum(integral, x1, y1, x2, y2)
|
||||
s2 = region_sum(integral_sq, x1, y1, x2, y2)
|
||||
area = region_w * region_h
|
||||
mean = s / area
|
||||
var = (s2 / area) - (mean ** 2)
|
||||
if var <= threshold:
|
||||
found = True
|
||||
best = (x, y, region_w, region_h, var)
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if found:
|
||||
best_size = mid
|
||||
min_size = mid + 1
|
||||
else:
|
||||
max_size = mid - 1
|
||||
if best:
|
||||
x, y, region_w, region_h, var = best
|
||||
center_x = x + region_w // 2
|
||||
center_y = y + region_h // 2
|
||||
return (center_x, center_y), (region_w, region_h), var
|
||||
else:
|
||||
return None, (0, 0), None
|
||||
|
||||
def draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
x, y = coords
|
||||
cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3)
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Saved output image with rectangle at {output_path}")
|
||||
|
||||
def draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
cx, cy = center
|
||||
region_w, region_h = size
|
||||
x1 = cx - region_w // 2
|
||||
y1 = cy - region_h // 2
|
||||
x2 = cx + region_w // 2 - 1
|
||||
y2 = cy + region_h // 2 - 1
|
||||
cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3)
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Saved output image with largest region at {output_path}")
|
||||
|
||||
def get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
# Ensure region is within bounds
|
||||
x = max(0, x)
|
||||
y = max(0, y)
|
||||
w = max(1, min(w, img.shape[1] - x))
|
||||
h = max(1, min(h, img.shape[0] - y))
|
||||
region = img[y:y+h, x:x+w]
|
||||
if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0:
|
||||
return [0, 0, 0]
|
||||
region = region.reshape((-1, 3))
|
||||
# Filter out black pixels (optional, improves accuracy for some images)
|
||||
non_black = region[np.any(region > 10, axis=1)]
|
||||
if non_black.shape[0] == 0:
|
||||
non_black = region
|
||||
region = np.float32(non_black)
|
||||
if region.shape[0] < 3:
|
||||
return [int(x) for x in np.mean(region, axis=0)]
|
||||
# K-means to find dominant color
|
||||
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
||||
K = min(3, region.shape[0])
|
||||
_, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
|
||||
counts = np.bincount(labels.flatten())
|
||||
dominant = centers[np.argmax(counts)]
|
||||
return [int(x) for x in dominant]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.")
|
||||
parser.add_argument("image_path", help="Path to the input image")
|
||||
parser.add_argument("--width", type=int, default=300, help="Region width")
|
||||
parser.add_argument("--height", type=int, default=200, help="Region height")
|
||||
parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle")
|
||||
parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling")
|
||||
parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling")
|
||||
parser.add_argument("--stride", type=int, default=10, help="Step size for sliding window (higher is faster, less precise)")
|
||||
parser.add_argument("--screen-mode", choices=["fill", "fit"], default="fill", help="Wallpaper scaling mode: 'fill' (default) or 'fit'")
|
||||
parser.add_argument("--verbose", action="store_true", help="Print verbose output")
|
||||
parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center")
|
||||
parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode")
|
||||
parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode")
|
||||
parser.add_argument("--horizontal-padding", "-hp", type=int, default=50, help="Minimum horizontal distance from region to image edge")
|
||||
parser.add_argument("--vertical-padding", "-vp", type=int, default=50, help="Minimum vertical distance from region to image edge")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.largest_region:
|
||||
center, size, var = find_largest_region(
|
||||
args.image_path,
|
||||
screen_width=args.screen_width,
|
||||
screen_height=args.screen_height,
|
||||
verbose=args.verbose,
|
||||
stride=args.stride,
|
||||
screen_mode=args.screen_mode,
|
||||
threshold=args.variance_threshold,
|
||||
aspect_ratio=args.aspect_ratio,
|
||||
horizontal_padding=args.horizontal_padding,
|
||||
vertical_padding=args.vertical_padding
|
||||
)
|
||||
if center:
|
||||
if args.visual_output:
|
||||
draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||
# Extract dominant color
|
||||
cx, cy = center
|
||||
region_w, region_h = size
|
||||
x1 = cx - region_w // 2
|
||||
y1 = cy - region_h // 2
|
||||
dominant_color = get_dominant_color(
|
||||
args.image_path, x1, y1, region_w, region_h,
|
||||
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
|
||||
)
|
||||
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
|
||||
print(json.dumps({
|
||||
"center_x": center[0],
|
||||
"center_y": center[1],
|
||||
"width": size[0],
|
||||
"height": size[1],
|
||||
"variance": var,
|
||||
"dominant_color": dominant_color_hex
|
||||
}))
|
||||
else:
|
||||
print(json.dumps({"error": "No region found under the threshold."}))
|
||||
return
|
||||
|
||||
coords, variance = find_least_busy_region(
|
||||
args.image_path,
|
||||
region_width=args.width,
|
||||
region_height=args.height,
|
||||
screen_width=args.screen_width,
|
||||
screen_height=args.screen_height,
|
||||
verbose=args.verbose,
|
||||
stride=args.stride,
|
||||
screen_mode=args.screen_mode,
|
||||
horizontal_padding=args.horizontal_padding,
|
||||
vertical_padding=args.vertical_padding
|
||||
)
|
||||
if args.visual_output:
|
||||
draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||
# Output JSON with center point
|
||||
center_x = coords[0] + args.width // 2
|
||||
center_y = coords[1] + args.height // 2
|
||||
dominant_color = get_dominant_color(
|
||||
args.image_path, coords[0], coords[1], args.width, args.height,
|
||||
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
|
||||
)
|
||||
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
|
||||
print(json.dumps({
|
||||
"center_x": center_x,
|
||||
"center_y": center_y,
|
||||
"width": args.width,
|
||||
"height": args.height,
|
||||
"variance": variance,
|
||||
"dominant_color": dominant_color_hex
|
||||
}))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env -S\_/bin/sh\_-xc\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@""
|
||||
|
||||
# From https://github.com/stwa/wayland-idle-inhibitor
|
||||
# License: WTFPL Version 2
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from signal import SIGINT, SIGTERM, signal
|
||||
from threading import Event
|
||||
import setproctitle
|
||||
|
||||
from pywayland.client.display import Display
|
||||
from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import (
|
||||
ZwpIdleInhibitManagerV1,
|
||||
)
|
||||
from pywayland.protocol.wayland.wl_compositor import WlCompositor
|
||||
from pywayland.protocol.wayland.wl_registry import WlRegistryProxy
|
||||
from pywayland.protocol.wayland.wl_surface import WlSurface
|
||||
|
||||
|
||||
@dataclass
|
||||
class GlobalRegistry:
|
||||
surface: WlSurface | None = None
|
||||
inhibit_manager: ZwpIdleInhibitManagerV1 | None = None
|
||||
|
||||
|
||||
def handle_registry_global(
|
||||
wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int
|
||||
) -> None:
|
||||
global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry()
|
||||
|
||||
if iface_name == "wl_compositor":
|
||||
compositor = wl_registry.bind(id_num, WlCompositor, version)
|
||||
global_registry.surface = compositor.create_surface() # type: ignore
|
||||
elif iface_name == "zwp_idle_inhibit_manager_v1":
|
||||
global_registry.inhibit_manager = wl_registry.bind(
|
||||
id_num, ZwpIdleInhibitManagerV1, version
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
done = Event()
|
||||
signal(SIGINT, lambda _, __: done.set())
|
||||
signal(SIGTERM, lambda _, __: done.set())
|
||||
|
||||
global_registry = GlobalRegistry()
|
||||
|
||||
display = Display()
|
||||
display.connect()
|
||||
|
||||
registry = display.get_registry() # type: ignore
|
||||
registry.user_data = global_registry
|
||||
registry.dispatcher["global"] = handle_registry_global
|
||||
|
||||
def shutdown() -> None:
|
||||
display.dispatch()
|
||||
display.roundtrip()
|
||||
display.disconnect()
|
||||
|
||||
display.dispatch()
|
||||
display.roundtrip()
|
||||
|
||||
if global_registry.surface is None or global_registry.inhibit_manager is None:
|
||||
print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.")
|
||||
shutdown()
|
||||
sys.exit(1)
|
||||
|
||||
inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore
|
||||
global_registry.surface
|
||||
)
|
||||
|
||||
display.dispatch()
|
||||
display.roundtrip()
|
||||
|
||||
print("Inhibiting idle...")
|
||||
done.wait()
|
||||
print("Shutting down...")
|
||||
|
||||
inhibitor.destroy()
|
||||
|
||||
shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setproctitle.setproctitle("wayland-idle-inhibitor.py")
|
||||
main()
|
||||
@@ -1,54 +0,0 @@
|
||||
import qs.modules.common
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
/**
|
||||
* A nice wrapper for default Pipewire audio sink and source.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool ready: Pipewire.defaultAudioSink?.ready ?? false
|
||||
property PwNode sink: Pipewire.defaultAudioSink
|
||||
property PwNode source: Pipewire.defaultAudioSource
|
||||
|
||||
signal sinkProtectionTriggered(string reason);
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [sink, source]
|
||||
}
|
||||
|
||||
Connections { // Protection against sudden volume changes
|
||||
target: sink?.audio ?? null
|
||||
property bool lastReady: false
|
||||
property real lastVolume: 0
|
||||
function onVolumeChanged() {
|
||||
if (!Config.options.audio.protection.enable) return;
|
||||
if (!lastReady) {
|
||||
lastVolume = sink.audio.volume;
|
||||
lastReady = true;
|
||||
return;
|
||||
}
|
||||
const newVolume = sink.audio.volume;
|
||||
const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
|
||||
const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
|
||||
|
||||
if (newVolume - lastVolume > maxAllowedIncrease) {
|
||||
sink.audio.volume = lastVolume;
|
||||
root.sinkProtectionTriggered("Illegal increment");
|
||||
} else if (newVolume > maxAllowed) {
|
||||
root.sinkProtectionTriggered("Exceeded max allowed");
|
||||
sink.audio.volume = Math.min(lastVolume, maxAllowed);
|
||||
}
|
||||
if (sink.ready && (isNaN(sink.audio.volume) || sink.audio.volume === undefined || sink.audio.volume === null)) {
|
||||
sink.audio.volume = 0;
|
||||
}
|
||||
lastVolume = sink.audio.volume;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
Singleton {
|
||||
property bool available: UPower.displayDevice.isLaptopBattery
|
||||
property var chargeState: UPower.displayDevice.state
|
||||
property bool isCharging: chargeState == UPowerDeviceState.Charging
|
||||
property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge
|
||||
property real percentage: UPower.displayDevice.percentage
|
||||
readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend
|
||||
|
||||
property bool isLow: percentage <= Config.options.battery.low / 100
|
||||
property bool isCritical: percentage <= Config.options.battery.critical / 100
|
||||
property bool isSuspending: percentage <= Config.options.battery.suspend / 100
|
||||
|
||||
property bool isLowAndNotCharging: isLow && !isCharging
|
||||
property bool isCriticalAndNotCharging: isCritical && !isCharging
|
||||
property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging
|
||||
|
||||
onIsLowAndNotChargingChanged: {
|
||||
if (available && isLowAndNotCharging) Quickshell.execDetached([
|
||||
"notify-send",
|
||||
Translation.tr("Low battery"),
|
||||
Translation.tr("Consider plugging in your device"),
|
||||
"-u", "critical",
|
||||
"-a", "Shell"
|
||||
])
|
||||
}
|
||||
|
||||
onIsCriticalAndNotChargingChanged: {
|
||||
if (available && isCriticalAndNotCharging) Quickshell.execDetached([
|
||||
"notify-send",
|
||||
Translation.tr("Critically low battery"),
|
||||
Translation.tr("Please charge!\nAutomatic suspend triggers at %1").arg(Config.options.battery.suspend),
|
||||
"-u", "critical",
|
||||
"-a", "Shell"
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
onIsSuspendingAndNotChargingChanged: {
|
||||
if (available && isSuspendingAndNotCharging) {
|
||||
Quickshell.execDetached(["bash", "-c", `systemctl suspend || loginctl suspend`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell;
|
||||
import Quickshell.Io;
|
||||
import QtQuick;
|
||||
|
||||
/**
|
||||
* Basic polled Bluetooth state.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int updateInterval: 1000
|
||||
property string bluetoothDeviceName: ""
|
||||
property string bluetoothDeviceAddress: ""
|
||||
property bool bluetoothEnabled: false
|
||||
property bool bluetoothConnected: false
|
||||
|
||||
function update() {
|
||||
updateBluetoothDevice.running = true
|
||||
updateBluetoothStatus.running = true
|
||||
updateBluetoothEnabled.running = true
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
update()
|
||||
interval = root.updateInterval
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Bluetooth is enabled (controller powered on)
|
||||
Process {
|
||||
id: updateBluetoothEnabled
|
||||
command: ["sh", "-c", "bluetoothctl show | grep -q 'Powered: yes' && echo 1 || echo 0"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.bluetoothEnabled = (parseInt(data) === 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the name and address of the first connected Bluetooth device
|
||||
Process {
|
||||
id: updateBluetoothDevice
|
||||
command: ["sh", "-c", "bluetoothctl info | awk -F': ' '/Name: /{name=$2} /Device /{addr=$2} END{print name \":\" addr}'"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
let parts = data.split(":")
|
||||
root.bluetoothDeviceName = parts[0] || ""
|
||||
root.bluetoothDeviceAddress = parts[1] || ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any device is connected
|
||||
Process {
|
||||
id: updateBluetoothStatus
|
||||
command: ["sh", "-c", "bluetoothctl info | grep -q 'Connected: yes' && echo 1 || echo 0"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.bluetoothConnected = (parseInt(data) === 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
// From https://github.com/caelestia-dots/shell/ (`quickshell` branch) with modifications.
|
||||
// License: GPLv3
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
|
||||
/**
|
||||
* For managing brightness of monitors. Supports both brightnessctl and ddcutil.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
signal brightnessChanged()
|
||||
|
||||
property var ddcMonitors: []
|
||||
readonly property list<BrightnessMonitor> monitors: Quickshell.screens.map(screen => monitorComp.createObject(root, {
|
||||
screen
|
||||
}))
|
||||
|
||||
function getMonitorForScreen(screen: ShellScreen): var {
|
||||
return monitors.find(m => m.screen === screen);
|
||||
}
|
||||
|
||||
function increaseBrightness(): void {
|
||||
const focusedName = Hyprland.focusedMonitor.name;
|
||||
const monitor = monitors.find(m => focusedName === m.screen.name);
|
||||
if (monitor)
|
||||
monitor.setBrightness(monitor.brightness + 0.05);
|
||||
}
|
||||
|
||||
function decreaseBrightness(): void {
|
||||
const focusedName = Hyprland.focusedMonitor.name;
|
||||
const monitor = monitors.find(m => focusedName === m.screen.name);
|
||||
if (monitor)
|
||||
monitor.setBrightness(monitor.brightness - 0.05);
|
||||
}
|
||||
|
||||
reloadableId: "brightness"
|
||||
|
||||
onMonitorsChanged: {
|
||||
ddcMonitors = [];
|
||||
ddcProc.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: ddcProc
|
||||
|
||||
command: ["ddcutil", "detect", "--brief"]
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n\n"
|
||||
onRead: data => {
|
||||
if (data.startsWith("Display ")) {
|
||||
const lines = data.split("\n").map(l => l.trim());
|
||||
root.ddcMonitors.push({
|
||||
model: lines.find(l => l.startsWith("Monitor:")).split(":")[2],
|
||||
busNum: lines.find(l => l.startsWith("I2C bus:")).split("/dev/i2c-")[1]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: root.ddcMonitorsChanged()
|
||||
}
|
||||
|
||||
Process {
|
||||
id: setProc
|
||||
}
|
||||
|
||||
component BrightnessMonitor: QtObject {
|
||||
id: monitor
|
||||
|
||||
required property ShellScreen screen
|
||||
readonly property bool isDdc: root.ddcMonitors.some(m => m.model === screen.model)
|
||||
readonly property string busNum: root.ddcMonitors.find(m => m.model === screen.model)?.busNum ?? ""
|
||||
property real brightness
|
||||
property bool ready: false
|
||||
|
||||
onBrightnessChanged: {
|
||||
if (monitor.ready) {
|
||||
root.brightnessChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
monitor.ready = false;
|
||||
initProc.command = isDdc ? ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"] : ["sh", "-c", `echo "a b c $(brightnessctl g) $(brightnessctl m)"`];
|
||||
initProc.running = true;
|
||||
}
|
||||
|
||||
readonly property Process initProc: Process {
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const [, , , current, max] = data.split(" ");
|
||||
monitor.brightness = parseInt(current) / parseInt(max);
|
||||
monitor.ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setBrightness(value: real): void {
|
||||
value = Math.max(0.01, Math.min(1, value));
|
||||
const rounded = Math.round(value * 100);
|
||||
if (Math.round(brightness * 100) === rounded)
|
||||
return;
|
||||
brightness = value;
|
||||
setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "s", `${rounded}%`, "--quiet"];
|
||||
setProc.startDetached();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
initialize();
|
||||
}
|
||||
|
||||
onBusNumChanged: {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: monitorComp
|
||||
|
||||
BrightnessMonitor {}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "brightness"
|
||||
|
||||
function increment() {
|
||||
onPressed: root.increaseBrightness()
|
||||
}
|
||||
|
||||
function decrement() {
|
||||
onPressed: root.decreaseBrightness()
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "brightnessIncrease"
|
||||
description: "Increase brightness"
|
||||
onPressed: root.increaseBrightness()
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "brightnessDecrease"
|
||||
description: "Decrease brightness"
|
||||
onPressed: root.decreaseBrightness()
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
|
||||
/**
|
||||
* Provides access to some Hyprland data not available in Quickshell.Hyprland.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
property var windowList: []
|
||||
property var addresses: []
|
||||
property var windowByAddress: ({})
|
||||
property var workspaces: []
|
||||
property var workspaceIds: []
|
||||
property var workspaceById: ({})
|
||||
property var activeWorkspace: null
|
||||
property var monitors: []
|
||||
property var layers: ({})
|
||||
|
||||
function updateWindowList() {
|
||||
getClients.running = true;
|
||||
}
|
||||
|
||||
function updateLayers() {
|
||||
getLayers.running = true;
|
||||
}
|
||||
|
||||
function updateMonitors() {
|
||||
getMonitors.running = true;
|
||||
}
|
||||
|
||||
function updateWorkspaces() {
|
||||
getWorkspaces.running = true;
|
||||
getActiveWorkspace.running = true;
|
||||
}
|
||||
|
||||
function updateAll() {
|
||||
updateWindowList();
|
||||
updateMonitors();
|
||||
updateLayers();
|
||||
updateWorkspaces();
|
||||
}
|
||||
|
||||
function biggestWindowForWorkspace(workspaceId) {
|
||||
const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId);
|
||||
return windowsInThisWorkspace.reduce((maxWin, win) => {
|
||||
const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0);
|
||||
const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0);
|
||||
return winArea > maxArea ? win : maxWin;
|
||||
}, null);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateAll();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Hyprland
|
||||
|
||||
function onRawEvent(event) {
|
||||
// console.log("Hyprland raw event:", event.name);
|
||||
updateAll()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getClients
|
||||
command: ["bash", "-c", "hyprctl clients -j | jq -c"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.windowList = JSON.parse(data);
|
||||
let tempWinByAddress = {};
|
||||
for (var i = 0; i < root.windowList.length; ++i) {
|
||||
var win = root.windowList[i];
|
||||
tempWinByAddress[win.address] = win;
|
||||
}
|
||||
root.windowByAddress = tempWinByAddress;
|
||||
root.addresses = root.windowList.map(win => win.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getMonitors
|
||||
command: ["bash", "-c", "hyprctl monitors -j | jq -c"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.monitors = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getLayers
|
||||
command: ["bash", "-c", "hyprctl layers -j | jq -c"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.layers = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getWorkspaces
|
||||
command: ["bash", "-c", "hyprctl workspaces -j | jq -c"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.workspaces = JSON.parse(data);
|
||||
let tempWorkspaceById = {};
|
||||
for (var i = 0; i < root.workspaces.length; ++i) {
|
||||
var ws = root.workspaces[i];
|
||||
tempWorkspaceById[ws.id] = ws;
|
||||
}
|
||||
root.workspaceById = tempWorkspaceById;
|
||||
root.workspaceIds = root.workspaces.map(ws => ws.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getActiveWorkspace
|
||||
command: ["bash", "-c", "hyprctl activeworkspace -j | jq -c"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.activeWorkspace = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
/**
|
||||
* Simple polled network state service.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool wifi: true
|
||||
property bool ethernet: false
|
||||
property int updateInterval: 1000
|
||||
property string networkName: ""
|
||||
property int networkStrength
|
||||
property string materialSymbol: ethernet ? "lan" :
|
||||
(Network.networkName.length > 0 && Network.networkName != "lo") ? (
|
||||
Network.networkStrength > 80 ? "signal_wifi_4_bar" :
|
||||
Network.networkStrength > 60 ? "network_wifi_3_bar" :
|
||||
Network.networkStrength > 40 ? "network_wifi_2_bar" :
|
||||
Network.networkStrength > 20 ? "network_wifi_1_bar" :
|
||||
"signal_wifi_0_bar"
|
||||
) : "signal_wifi_off"
|
||||
function update() {
|
||||
updateConnectionType.startCheck();
|
||||
updateNetworkName.running = true;
|
||||
updateNetworkStrength.running = true;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
root.update();
|
||||
interval = root.updateInterval;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateConnectionType
|
||||
property string buffer
|
||||
command: ["sh", "-c", "nmcli -t -f NAME,TYPE,DEVICE c show --active"]
|
||||
running: true
|
||||
function startCheck() {
|
||||
buffer = "";
|
||||
updateConnectionType.running = true;
|
||||
}
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
updateConnectionType.buffer += data + "\n";
|
||||
}
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
const lines = updateConnectionType.buffer.trim().split('\n');
|
||||
let hasEthernet = false;
|
||||
let hasWifi = false;
|
||||
lines.forEach(line => {
|
||||
if (line.includes("ethernet"))
|
||||
hasEthernet = true;
|
||||
else if (line.includes("wireless"))
|
||||
hasWifi = true;
|
||||
});
|
||||
root.ethernet = hasEthernet;
|
||||
root.wifi = hasWifi;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateNetworkName
|
||||
command: ["sh", "-c", "nmcli -t -f NAME c show --active | head -1"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.networkName = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateNetworkStrength
|
||||
running: true
|
||||
command: ["sh", "-c", "nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\*/{if (NR!=1) {print $2}}'"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.networkStrength = parseInt(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.modules.common
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
/**
|
||||
* Simple polled resource usage service with RAM, Swap, and CPU usage.
|
||||
*/
|
||||
Singleton {
|
||||
property double memoryTotal: 1
|
||||
property double memoryFree: 1
|
||||
property double memoryUsed: memoryTotal - memoryFree
|
||||
property double memoryUsedPercentage: memoryUsed / memoryTotal
|
||||
property double swapTotal: 1
|
||||
property double swapFree: 1
|
||||
property double swapUsed: swapTotal - swapFree
|
||||
property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
|
||||
property double cpuUsage: 0
|
||||
property var previousCpuStats
|
||||
|
||||
Timer {
|
||||
interval: 1
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
// Reload files
|
||||
fileMeminfo.reload()
|
||||
fileStat.reload()
|
||||
|
||||
// Parse memory and swap usage
|
||||
const textMeminfo = fileMeminfo.text()
|
||||
memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1)
|
||||
memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0)
|
||||
swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1)
|
||||
swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0)
|
||||
|
||||
// Parse CPU usage
|
||||
const textStat = fileStat.text()
|
||||
const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/)
|
||||
if (cpuLine) {
|
||||
const stats = cpuLine.slice(1).map(Number)
|
||||
const total = stats.reduce((a, b) => a + b, 0)
|
||||
const idle = stats[3]
|
||||
|
||||
if (previousCpuStats) {
|
||||
const totalDiff = total - previousCpuStats.total
|
||||
const idleDiff = idle - previousCpuStats.idle
|
||||
cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0
|
||||
}
|
||||
|
||||
previousCpuStats = { total, idle }
|
||||
}
|
||||
interval = Config.options?.resources?.updateInterval ?? 3000
|
||||
}
|
||||
}
|
||||
|
||||
FileView { id: fileMeminfo; path: "/proc/meminfo" }
|
||||
FileView { id: fileStat; path: "/proc/stat" }
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
//@ pragma UseQApplication
|
||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
|
||||
|
||||
// Adjust this to make the shell smaller or larger
|
||||
//@ pragma Env QT_SCALE_FACTOR=1
|
||||
|
||||
import "./modules/common/"
|
||||
import "./modules/background/"
|
||||
import "./modules/bar/"
|
||||
import "./modules/cheatsheet/"
|
||||
import "./modules/dock/"
|
||||
import "./modules/lock/"
|
||||
import "./modules/mediaControls/"
|
||||
import "./modules/notificationPopup/"
|
||||
import "./modules/onScreenDisplay/"
|
||||
import "./modules/onScreenKeyboard/"
|
||||
import "./modules/overview/"
|
||||
import "./modules/screenCorners/"
|
||||
import "./modules/session/"
|
||||
import "./modules/sidebarLeft/"
|
||||
import "./modules/sidebarRight/"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import Quickshell
|
||||
import "./services/"
|
||||
|
||||
ShellRoot {
|
||||
// Enable/disable modules here. False = not loaded at all, so rest assured
|
||||
// no unnecessary stuff will take up memory if you decide to only use, say, the overview.
|
||||
property bool enableBar: true
|
||||
property bool enableBackground: true
|
||||
property bool enableCheatsheet: true
|
||||
property bool enableDock: true
|
||||
property bool enableLock: true
|
||||
property bool enableMediaControls: true
|
||||
property bool enableNotificationPopup: true
|
||||
property bool enableOnScreenDisplayBrightness: true
|
||||
property bool enableOnScreenDisplayVolume: true
|
||||
property bool enableOnScreenKeyboard: true
|
||||
property bool enableOverview: true
|
||||
property bool enableReloadPopup: true
|
||||
property bool enableScreenCorners: true
|
||||
property bool enableSession: true
|
||||
property bool enableSidebarLeft: true
|
||||
property bool enableSidebarRight: true
|
||||
|
||||
// Force initialization of some singletons
|
||||
Component.onCompleted: {
|
||||
MaterialThemeLoader.reapplyTheme()
|
||||
Cliphist.refresh()
|
||||
FirstRunExperience.load()
|
||||
}
|
||||
|
||||
LazyLoader { active: enableBar; component: Bar {} }
|
||||
LazyLoader { active: enableBackground; component: Background {} }
|
||||
LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
|
||||
LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} }
|
||||
LazyLoader { active: enableLock; component: Lock {} }
|
||||
LazyLoader { active: enableMediaControls; component: MediaControls {} }
|
||||
LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} }
|
||||
LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
|
||||
LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} }
|
||||
LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} }
|
||||
LazyLoader { active: enableOverview; component: Overview {} }
|
||||
LazyLoader { active: enableReloadPopup; component: ReloadPopup {} }
|
||||
LazyLoader { active: enableScreenCorners; component: ScreenCorners {} }
|
||||
LazyLoader { active: enableSession; component: Session {} }
|
||||
LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} }
|
||||
LazyLoader { active: enableSidebarRight; component: SidebarRight {} }
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.modules.common.functions
|
||||
import Qt.labs.platform
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
// XDG Dirs, with "file://"
|
||||
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
|
||||
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
|
||||
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
|
||||
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
||||
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
|
||||
|
||||
// Other dirs used by the shell, without "file://"
|
||||
property string assetsPath: Quickshell.shellPath("assets")
|
||||
property string scriptPath: Quickshell.shellPath("scripts")
|
||||
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/quickshell`)
|
||||
property string shellConfigName: "oo.json"
|
||||
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
|
||||
property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`)
|
||||
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
|
||||
// Cleanup on init
|
||||
Component.onCompleted: {
|
||||
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import qs
|
||||
import qs.singletons
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
// Open states
|
||||
property bool barOpen: true
|
||||
property bool launcherOpen: true
|
||||
|
||||
// Smooth screen zoom
|
||||
IpcHandler {
|
||||
target: "zoom"
|
||||
|
||||
function zoomIn() {
|
||||
screenZoom = Math.min(screenZoom + 0.4, 3.0)
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
screenZoom = Math.max(screenZoom - 0.4, 1)
|
||||
}
|
||||
}
|
||||
|
||||
property real screenZoom: 1
|
||||
|
||||
onScreenZoomChanged: {
|
||||
Quickshell.execDetached(["hyprctl", "keyword", "cursor:zoom_factor", root.screenZoom.toString()]);
|
||||
}
|
||||
|
||||
Behavior on screenZoom {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs
|
||||
import qs.singletons
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions as CF
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
readonly property bool fixedClockPosition: Config.options.background.fixedClockPosition
|
||||
readonly property real fixedClockX: Config.options.background.clockX
|
||||
readonly property real fixedClockY: Config.options.background.clockY
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: bgRoot
|
||||
|
||||
required property var modelData
|
||||
// Workspaces
|
||||
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
|
||||
property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)
|
||||
property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1
|
||||
property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10
|
||||
// Wallpaper
|
||||
property string wallpaperPath: Config.options.background.wallpaperPath
|
||||
property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".webm")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".mkv")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".avi")
|
||||
|| Config.options.background.wallpaperPath.endsWith(".mov")
|
||||
property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom
|
||||
property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated
|
||||
property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated
|
||||
property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated
|
||||
property real movableXSpace: (effectiveWallpaperScale - 1) / 2 * screen.width
|
||||
property real movableYSpace: (effectiveWallpaperScale - 1) / 2 * screen.height
|
||||
// Position
|
||||
property real clockX: (modelData.width / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.width)
|
||||
property real clockY: (modelData.height / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.height)
|
||||
property var textHorizontalAlignment: clockX < screen.width / 3 ? Text.AlignLeft :
|
||||
(clockX > screen.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter)
|
||||
// Colors
|
||||
property color dominantColor: Appearance.colors.colPrimary
|
||||
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
|
||||
property color colText: CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12))
|
||||
|
||||
// Layer props
|
||||
screen: modelData
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: GlobalStates.screenLocked ? WlrLayer.Top : WlrLayer.Bottom
|
||||
// WlrLayershell.layer: WlrLayer.Bottom
|
||||
WlrLayershell.namespace: "quickshell:background"
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
onWallpaperPathChanged: {
|
||||
bgRoot.updateZoomScale()
|
||||
// Clock position gets updated after zoom scale is updated
|
||||
}
|
||||
|
||||
// Wallpaper zoom scale
|
||||
function updateZoomScale() {
|
||||
getWallpaperSizeProc.path = bgRoot.wallpaperPath
|
||||
getWallpaperSizeProc.running = true;
|
||||
}
|
||||
Process {
|
||||
id: getWallpaperSizeProc
|
||||
property string path: bgRoot.wallpaperPath
|
||||
command: [ "magick", "identify", "-format", "%w %h", path ]
|
||||
stdout: StdioCollector {
|
||||
id: wallpaperSizeOutputCollector
|
||||
onStreamFinished: {
|
||||
const output = wallpaperSizeOutputCollector.text
|
||||
const [width, height] = output.split(" ").map(Number);
|
||||
bgRoot.wallpaperWidth = width
|
||||
bgRoot.wallpaperHeight = height
|
||||
bgRoot.effectiveWallpaperScale = Math.max(1, Math.min(
|
||||
bgRoot.preferredWallpaperScale,
|
||||
width / bgRoot.screen.width,
|
||||
height / bgRoot.screen.height
|
||||
));
|
||||
|
||||
bgRoot.updateClockPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clock positioning
|
||||
function updateClockPosition() {
|
||||
// Somehow all this manual setting is needed to make the proc correctly use the new values
|
||||
leastBusyRegionProc.path = bgRoot.wallpaperPath
|
||||
leastBusyRegionProc.contentWidth = clock.implicitWidth
|
||||
leastBusyRegionProc.contentHeight = clock.implicitHeight
|
||||
leastBusyRegionProc.horizontalPadding = (effectiveWallpaperScale - 1) / 2 * screen.width + 100
|
||||
leastBusyRegionProc.verticalPadding = (effectiveWallpaperScale - 1) / 2 * screen.height + 100
|
||||
leastBusyRegionProc.running = false;
|
||||
leastBusyRegionProc.running = true;
|
||||
}
|
||||
Process {
|
||||
id: leastBusyRegionProc
|
||||
property string path: bgRoot.wallpaperPath
|
||||
property int contentWidth: 300
|
||||
property int contentHeight: 300
|
||||
property int horizontalPadding: bgRoot.movableXSpace
|
||||
property int verticalPadding: bgRoot.movableYSpace
|
||||
command: [Quickshell.shellPath("scripts/images/least_busy_region.py"),
|
||||
"--screen-width", bgRoot.screen.width,
|
||||
"--screen-height", bgRoot.screen.height,
|
||||
"--width", contentWidth,
|
||||
"--height", contentHeight,
|
||||
"--horizontal-padding", horizontalPadding,
|
||||
"--vertical-padding", verticalPadding,
|
||||
path
|
||||
]
|
||||
stdout: StdioCollector {
|
||||
id: leastBusyRegionOutputCollector
|
||||
onStreamFinished: {
|
||||
const output = leastBusyRegionOutputCollector.text
|
||||
// console.log("[Background] Least busy region output:", output)
|
||||
if (output.length === 0) return;
|
||||
const parsedContent = JSON.parse(output)
|
||||
bgRoot.clockX = parsedContent.center_x
|
||||
bgRoot.clockY = parsedContent.center_y
|
||||
bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper
|
||||
Image {
|
||||
visible: !bgRoot.wallpaperIsVideo
|
||||
property real value // 0 to 1, for offset
|
||||
value: {
|
||||
// Range = half-groups that workspaces span on
|
||||
const chunkSize = 5;
|
||||
const lower = Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize;
|
||||
const upper = Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize;
|
||||
const range = upper - lower;
|
||||
return (Config.options.background.parallax.enableWorkspace ? ((bgRoot.monitor.activeWorkspace.id - lower) / range) : 0.5)
|
||||
+ (0.15 * GlobalStates.sidebarRightOpen * Config.options.background.parallax.enableSidebar)
|
||||
- (0.15 * GlobalStates.sidebarLeftOpen * Config.options.background.parallax.enableSidebar)
|
||||
}
|
||||
property real effectiveValue: Math.max(0, Math.min(1, value))
|
||||
x: -(bgRoot.movableXSpace) - (effectiveValue - 0.5) * 2 * bgRoot.movableXSpace
|
||||
y: -(bgRoot.movableYSpace)
|
||||
source: bgRoot.wallpaperPath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 600
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
sourceSize {
|
||||
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale
|
||||
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale
|
||||
}
|
||||
|
||||
// The clock
|
||||
Item {
|
||||
id: clock
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
leftMargin: ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
|
||||
topMargin: ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
|
||||
Behavior on leftMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on topMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: clockColumn.implicitWidth
|
||||
implicitHeight: clockColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: clockColumn
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: bgRoot.textHorizontalAlignment
|
||||
font {
|
||||
family: Appearance.font.family.expressive
|
||||
pixelSize: 90
|
||||
weight: Font.Bold
|
||||
}
|
||||
color: bgRoot.colText
|
||||
style: Text.Raised
|
||||
styleColor: Appearance.colors.colShadow
|
||||
text: DateTime.time
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -5
|
||||
horizontalAlignment: bgRoot.textHorizontalAlignment
|
||||
font {
|
||||
family: Appearance.font.family.expressive
|
||||
pixelSize: 20
|
||||
weight: Font.DemiBold
|
||||
}
|
||||
color: bgRoot.colText
|
||||
style: Text.Raised
|
||||
styleColor: Appearance.colors.colShadow
|
||||
text: DateTime.date
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
top: clockColumn.bottom
|
||||
left: bgRoot.textHorizontalAlignment === Text.AlignLeft ? clockColumn.left : undefined
|
||||
right: bgRoot.textHorizontalAlignment === Text.AlignRight ? clockColumn.right : undefined
|
||||
horizontalCenter: bgRoot.textHorizontalAlignment === Text.AlignHCenter ? clockColumn.horizontalCenter : undefined
|
||||
topMargin: 5
|
||||
leftMargin: -5
|
||||
rightMargin: -5
|
||||
}
|
||||
opacity: GlobalStates.screenLocked ? 1 : 0
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft; implicitWidth: 1 }
|
||||
MaterialSymbol {
|
||||
text: "lock"
|
||||
Layout.fillWidth: false
|
||||
iconSize: Appearance.font.pixelSize.huge
|
||||
color: bgRoot.colText
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: false
|
||||
text: "Locked"
|
||||
color: bgRoot.colText
|
||||
font {
|
||||
pixelSize: Appearance.font.pixelSize.larger
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight; implicitWidth: 1 }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password prompt
|
||||
StyledText {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 30
|
||||
}
|
||||
opacity: (GlobalStates.screenLocked && !GlobalStates.screenLockContainsCharacters) ? 1 : 0
|
||||
scale: opacity
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
text: "Enter password"
|
||||
color: CF.ColorUtils.transparentize(bgRoot.colText, 0.3)
|
||||
font {
|
||||
pixelSize: Appearance.font.pixelSize.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs
|
||||
import qs.singletons
|
||||
import qs.modules.common.widgets
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
Variants {
|
||||
// For each monitor
|
||||
model: Quickshell.screens
|
||||
LazyLoader {
|
||||
id: barLoader
|
||||
active: GlobalStates.barOpen
|
||||
required property ShellScreen modelData
|
||||
component: PanelWindow { // Bar window
|
||||
id: barRoot
|
||||
screen: barLoader.modelData
|
||||
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
exclusiveZone: Appearance.sizes.barHeight + Appearance.sizes.barBorder
|
||||
WlrLayershell.namespace: "oo:bar"
|
||||
implicitHeight: Appearance.sizes.barHeight + Appearance.sizes.barBorder
|
||||
mask: Region {
|
||||
item: barContent
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Item { // Bar content region
|
||||
id: barContent
|
||||
anchors {
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
implicitHeight: Appearance.sizes.barHeight + Appearance.sizes.barBorder
|
||||
|
||||
// Background
|
||||
Rectangle {
|
||||
id: barBackground
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: Appearance.sizes.barBorder
|
||||
}
|
||||
color: Appearance.colors.colLayer0
|
||||
}
|
||||
// Border
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
bottomMargin: Appearance.sizes.barHeight
|
||||
}
|
||||
implicitHeight: Appearance.sizes.barBorder
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
// Stuff
|
||||
RowLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: Appearance.sizes.barBorder
|
||||
}
|
||||
BarButton {
|
||||
id: startButton
|
||||
Layout.fillHeight: true
|
||||
|
||||
property real targetRotation: 0
|
||||
onPressed: targetRotation += 180
|
||||
rotation: targetRotation
|
||||
Behavior on rotation {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
onClicked: GlobalStates.launcherOpen = !GlobalStates.launcherOpen
|
||||
|
||||
HexRect {
|
||||
anchors.centerIn: parent
|
||||
color: startButton.active ?
|
||||
(startButton.hovered ? Appearance.colors.colPrimaryHover : Appearance.colors.colPrimary)
|
||||
: (startButton.hovered ? Appearance.colors.colLayer3Hover : Appearance.colors.colLayer3)
|
||||
Behavior on borderColor {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
property real size: Appearance.sizes.barHeight * 0.75
|
||||
property real sizeDown: size * 0.85
|
||||
property real effectiveSize: startButton.down ? sizeDown : size
|
||||
Behavior on effectiveSize {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
implicitWidth: effectiveSize
|
||||
implicitHeight: effectiveSize
|
||||
}
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
text: "add"
|
||||
iconSize: Appearance.sizes.barHeight * 0.6
|
||||
color: Appearance.colors.colOnLayer3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "bar"
|
||||
|
||||
function toggle(): void {
|
||||
GlobalStates.barOpen = !GlobalStates.barOpen;
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
GlobalStates.barOpen = false;
|
||||
}
|
||||
|
||||
function open(): void {
|
||||
GlobalStates.barOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "barToggle"
|
||||
description: "Toggles bar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.barOpen = !GlobalStates.barOpen;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "barOpen"
|
||||
description: "Opens bar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.barOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "barClose"
|
||||
description: "Closes bar on press"
|
||||
|
||||
onPressed: {
|
||||
GlobalStates.barOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.singletons
|
||||
|
||||
Button {
|
||||
implicitHeight: Appearance.sizes.barHeight
|
||||
implicitWidth: Appearance.sizes.barHeight
|
||||
|
||||
background: null
|
||||
contentItem: null
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* Trims the File protocol off the input string
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function trimFileProtocol(str) {
|
||||
return str.startsWith("file://") ? str.slice(7) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the file name from a file path
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function fileNameForPath(str) {
|
||||
if (typeof str !== "string") return "";
|
||||
const trimmed = trimFileProtocol(str);
|
||||
return trimmed.split(/[\\/]/).pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file extension from a file path or name
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function trimFileExt(str) {
|
||||
if (typeof str !== "string") return "";
|
||||
const trimmed = trimFileProtocol(str);
|
||||
const lastDot = trimmed.lastIndexOf(".");
|
||||
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
|
||||
return trimmed.slice(0, lastDot);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function toPlainObject(qtObj) {
|
||||
if (qtObj === null || typeof qtObj !== "object") return qtObj;
|
||||
|
||||
// Handle true arrays
|
||||
if (Array.isArray(qtObj)) {
|
||||
return qtObj.map(item => toPlainObject(item));
|
||||
}
|
||||
|
||||
// Handle array-like Qt objects (e.g., have length and numeric keys)
|
||||
if (
|
||||
typeof qtObj.length === "number" &&
|
||||
qtObj.length > 0 &&
|
||||
Object.keys(qtObj).every(
|
||||
key => !isNaN(key) || key === "length"
|
||||
)
|
||||
) {
|
||||
let arr = [];
|
||||
for (let i = 0; i < qtObj.length; i++) {
|
||||
arr.push(toPlainObject(qtObj[i]));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
const result = ({});
|
||||
for (let key in qtObj) {
|
||||
if (
|
||||
typeof qtObj[key] !== "function" &&
|
||||
!key.startsWith("objectName") &&
|
||||
!key.startsWith("children") &&
|
||||
!key.startsWith("object") &&
|
||||
!key.startsWith("parent") &&
|
||||
!key.startsWith("metaObject") &&
|
||||
!key.startsWith("destroyed") &&
|
||||
!key.startsWith("reloadableId")
|
||||
) {
|
||||
result[key] = toPlainObject(qtObj[key]);
|
||||
}
|
||||
}
|
||||
// console.log(JSON.stringify(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
function applyToQtObject(qtObj, jsonObj) {
|
||||
// console.log("applyToQtObject", JSON.stringify(qtObj, null, 2), "<<", JSON.stringify(jsonObj, null, 2));
|
||||
if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return;
|
||||
|
||||
// Detect array-like Qt objects
|
||||
const isQtArrayLike = obj => {
|
||||
return obj && typeof obj === "object" &&
|
||||
typeof obj.length === "number" &&
|
||||
obj.length > 0 &&
|
||||
Object.keys(obj).every(key => !isNaN(key) || key === "length");
|
||||
};
|
||||
|
||||
// If both are arrays or array-like, update in place or replace
|
||||
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
|
||||
qtObj.length = 0;
|
||||
for (let i = 0; i < jsonObj.length; i++) {
|
||||
qtObj.push(jsonObj[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If target is array or array-like but source is not, clear
|
||||
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && !Array.isArray(jsonObj)) {
|
||||
qtObj.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// If source is array but target is not, assign directly if possible
|
||||
if (!(Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
for (let key in jsonObj) {
|
||||
if (!qtObj.hasOwnProperty(key)) continue;
|
||||
const value = qtObj[key];
|
||||
const jsonValue = jsonObj[key];
|
||||
// console.log("applying to qt obj key:", value, "jsonValue:", jsonValue);
|
||||
if ((Array.isArray(value) || isQtArrayLike(value)) && Array.isArray(jsonValue)) {
|
||||
value.length = 0;
|
||||
for (let i = 0; i < jsonValue.length; i++) {
|
||||
value.push(jsonValue[i]);
|
||||
}
|
||||
} else if (value && typeof value === "object" && !Array.isArray(value) && !isQtArrayLike(value)) {
|
||||
applyToQtObject(value, jsonValue);
|
||||
} else {
|
||||
qtObj[key] = jsonValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,682 +0,0 @@
|
||||
.pragma library
|
||||
|
||||
// https://github.com/farzher/fuzzysort
|
||||
// License: MIT | Copyright (c) 2018 Stephen Kamenar
|
||||
// A copy of the license is available in the `licenses` folder of this repository
|
||||
|
||||
var single = (search, target) => {
|
||||
if(!search || !target) return NULL
|
||||
|
||||
var preparedSearch = getPreparedSearch(search)
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
|
||||
var searchBitflags = preparedSearch.bitflags
|
||||
if((searchBitflags & target._bitflags) !== searchBitflags) return NULL
|
||||
|
||||
return algorithm(preparedSearch, target)
|
||||
}
|
||||
|
||||
var go = (search, targets, options) => {
|
||||
if(!search) return options?.all ? all(targets, options) : noResults
|
||||
|
||||
var preparedSearch = getPreparedSearch(search)
|
||||
var searchBitflags = preparedSearch.bitflags
|
||||
var containsSpace = preparedSearch.containsSpace
|
||||
|
||||
var threshold = denormalizeScore( options?.threshold || 0 )
|
||||
var limit = options?.limit || INFINITY
|
||||
|
||||
var resultsLen = 0; var limitedCount = 0
|
||||
var targetsLen = targets.length
|
||||
|
||||
function push_result(result) {
|
||||
if(resultsLen < limit) { q.add(result); ++resultsLen }
|
||||
else {
|
||||
++limitedCount
|
||||
if(result._score > q.peek()._score) q.replaceTop(result)
|
||||
}
|
||||
}
|
||||
|
||||
// This code is copy/pasted 3 times for performance reasons [options.key, options.keys, no keys]
|
||||
|
||||
// options.key
|
||||
if(options?.key) {
|
||||
var key = options.key
|
||||
for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]
|
||||
var target = getValue(obj, key)
|
||||
if(!target) continue
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
|
||||
if((searchBitflags & target._bitflags) !== searchBitflags) continue
|
||||
var result = algorithm(preparedSearch, target)
|
||||
if(result === NULL) continue
|
||||
if(result._score < threshold) continue
|
||||
|
||||
result.obj = obj
|
||||
push_result(result)
|
||||
}
|
||||
|
||||
// options.keys
|
||||
} else if(options?.keys) {
|
||||
var keys = options.keys
|
||||
var keysLen = keys.length
|
||||
|
||||
outer: for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]
|
||||
|
||||
{ // early out based on bitflags
|
||||
var keysBitflags = 0
|
||||
for (var keyI = 0; keyI < keysLen; ++keyI) {
|
||||
var key = keys[keyI]
|
||||
var target = getValue(obj, key)
|
||||
if(!target) { tmpTargets[keyI] = noTarget; continue }
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
tmpTargets[keyI] = target
|
||||
|
||||
keysBitflags |= target._bitflags
|
||||
}
|
||||
|
||||
if((searchBitflags & keysBitflags) !== searchBitflags) continue
|
||||
}
|
||||
|
||||
if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) keysSpacesBestScores[i] = NEGATIVE_INFINITY
|
||||
|
||||
for (var keyI = 0; keyI < keysLen; ++keyI) {
|
||||
target = tmpTargets[keyI]
|
||||
if(target === noTarget) { tmpResults[keyI] = noTarget; continue }
|
||||
|
||||
tmpResults[keyI] = algorithm(preparedSearch, target, /*allowSpaces=*/false, /*allowPartialMatch=*/containsSpace)
|
||||
if(tmpResults[keyI] === NULL) { tmpResults[keyI] = noTarget; continue }
|
||||
|
||||
// todo: this seems weird and wrong. like what if our first match wasn't good. this should just replace it instead of averaging with it
|
||||
// if our second match isn't good we ignore it instead of averaging with it
|
||||
if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) {
|
||||
if(allowPartialMatchScores[i] > -1000) {
|
||||
if(keysSpacesBestScores[i] > NEGATIVE_INFINITY) {
|
||||
var tmp = (keysSpacesBestScores[i] + allowPartialMatchScores[i]) / 4/*bonus score for having multiple matches*/
|
||||
if(tmp > keysSpacesBestScores[i]) keysSpacesBestScores[i] = tmp
|
||||
}
|
||||
}
|
||||
if(allowPartialMatchScores[i] > keysSpacesBestScores[i]) keysSpacesBestScores[i] = allowPartialMatchScores[i]
|
||||
}
|
||||
}
|
||||
|
||||
if(containsSpace) {
|
||||
for(let i=0; i<preparedSearch.spaceSearches.length; i++) { if(keysSpacesBestScores[i] === NEGATIVE_INFINITY) continue outer }
|
||||
} else {
|
||||
var hasAtLeast1Match = false
|
||||
for(let i=0; i < keysLen; i++) { if(tmpResults[i]._score !== NEGATIVE_INFINITY) { hasAtLeast1Match = true; break } }
|
||||
if(!hasAtLeast1Match) continue
|
||||
}
|
||||
|
||||
var objResults = new KeysResult(keysLen)
|
||||
for(let i=0; i < keysLen; i++) { objResults[i] = tmpResults[i] }
|
||||
|
||||
if(containsSpace) {
|
||||
var score = 0
|
||||
for(let i=0; i<preparedSearch.spaceSearches.length; i++) score += keysSpacesBestScores[i]
|
||||
} else {
|
||||
// todo could rewrite this scoring to be more similar to when there's spaces
|
||||
// if we match multiple keys give us bonus points
|
||||
var score = NEGATIVE_INFINITY
|
||||
for(let i=0; i<keysLen; i++) {
|
||||
var result = objResults[i]
|
||||
if(result._score > -1000) {
|
||||
if(score > NEGATIVE_INFINITY) {
|
||||
var tmp = (score + result._score) / 4/*bonus score for having multiple matches*/
|
||||
if(tmp > score) score = tmp
|
||||
}
|
||||
}
|
||||
if(result._score > score) score = result._score
|
||||
}
|
||||
}
|
||||
|
||||
objResults.obj = obj
|
||||
objResults._score = score
|
||||
if(options?.scoreFn) {
|
||||
score = options.scoreFn(objResults)
|
||||
if(!score) continue
|
||||
score = denormalizeScore(score)
|
||||
objResults._score = score
|
||||
}
|
||||
|
||||
if(score < threshold) continue
|
||||
push_result(objResults)
|
||||
}
|
||||
|
||||
// no keys
|
||||
} else {
|
||||
for(var i = 0; i < targetsLen; ++i) { var target = targets[i]
|
||||
if(!target) continue
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
|
||||
if((searchBitflags & target._bitflags) !== searchBitflags) continue
|
||||
var result = algorithm(preparedSearch, target)
|
||||
if(result === NULL) continue
|
||||
if(result._score < threshold) continue
|
||||
|
||||
push_result(result)
|
||||
}
|
||||
}
|
||||
|
||||
if(resultsLen === 0) return noResults
|
||||
var results = new Array(resultsLen)
|
||||
for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
|
||||
results.total = resultsLen + limitedCount
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
// this is written as 1 function instead of 2 for minification. perf seems fine ...
|
||||
// except when minified. the perf is very slow
|
||||
var highlight = (result, open='<b>', close='</b>') => {
|
||||
var callback = typeof open === 'function' ? open : undefined
|
||||
|
||||
var target = result.target
|
||||
var targetLen = target.length
|
||||
var indexes = result.indexes
|
||||
var highlighted = ''
|
||||
var matchI = 0
|
||||
var indexesI = 0
|
||||
var opened = false
|
||||
var parts = []
|
||||
|
||||
for(var i = 0; i < targetLen; ++i) { var char = target[i]
|
||||
if(indexes[indexesI] === i) {
|
||||
++indexesI
|
||||
if(!opened) { opened = true
|
||||
if(callback) {
|
||||
parts.push(highlighted); highlighted = ''
|
||||
} else {
|
||||
highlighted += open
|
||||
}
|
||||
}
|
||||
|
||||
if(indexesI === indexes.length) {
|
||||
if(callback) {
|
||||
highlighted += char
|
||||
parts.push(callback(highlighted, matchI++)); highlighted = ''
|
||||
parts.push(target.substr(i+1))
|
||||
} else {
|
||||
highlighted += char + close + target.substr(i+1)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if(opened) { opened = false
|
||||
if(callback) {
|
||||
parts.push(callback(highlighted, matchI++)); highlighted = ''
|
||||
} else {
|
||||
highlighted += close
|
||||
}
|
||||
}
|
||||
}
|
||||
highlighted += char
|
||||
}
|
||||
|
||||
return callback ? parts : highlighted
|
||||
}
|
||||
|
||||
|
||||
var prepare = (target) => {
|
||||
if(typeof target === 'number') target = ''+target
|
||||
else if(typeof target !== 'string') target = ''
|
||||
var info = prepareLowerInfo(target)
|
||||
return new_result(target, {_targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _bitflags:info.bitflags})
|
||||
}
|
||||
|
||||
var cleanup = () => { preparedCache.clear(); preparedSearchCache.clear() }
|
||||
|
||||
|
||||
// Below this point is only internal code
|
||||
// Below this point is only internal code
|
||||
// Below this point is only internal code
|
||||
// Below this point is only internal code
|
||||
|
||||
|
||||
class Result {
|
||||
get ['indexes']() { return this._indexes.slice(0, this._indexes.len).sort((a,b)=>a-b) }
|
||||
set ['indexes'](indexes) { return this._indexes = indexes }
|
||||
['highlight'](open, close) { return highlight(this, open, close) }
|
||||
get ['score']() { return normalizeScore(this._score) }
|
||||
set ['score'](score) { this._score = denormalizeScore(score) }
|
||||
}
|
||||
|
||||
class KeysResult extends Array {
|
||||
get ['score']() { return normalizeScore(this._score) }
|
||||
set ['score'](score) { this._score = denormalizeScore(score) }
|
||||
}
|
||||
|
||||
var new_result = (target, options) => {
|
||||
const result = new Result()
|
||||
result['target'] = target
|
||||
result['obj'] = options.obj ?? NULL
|
||||
result._score = options._score ?? NEGATIVE_INFINITY
|
||||
result._indexes = options._indexes ?? []
|
||||
result._targetLower = options._targetLower ?? ''
|
||||
result._targetLowerCodes = options._targetLowerCodes ?? NULL
|
||||
result._nextBeginningIndexes = options._nextBeginningIndexes ?? NULL
|
||||
result._bitflags = options._bitflags ?? 0
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
var normalizeScore = score => {
|
||||
if(score === NEGATIVE_INFINITY) return 0
|
||||
if(score > 1) return score
|
||||
return Math.E ** ( ((-score + 1)**.04307 - 1) * -2)
|
||||
}
|
||||
var denormalizeScore = normalizedScore => {
|
||||
if(normalizedScore === 0) return NEGATIVE_INFINITY
|
||||
if(normalizedScore > 1) return normalizedScore
|
||||
return 1 - Math.pow((Math.log(normalizedScore) / -2 + 1), 1 / 0.04307)
|
||||
}
|
||||
|
||||
|
||||
var prepareSearch = (search) => {
|
||||
if(typeof search === 'number') search = ''+search
|
||||
else if(typeof search !== 'string') search = ''
|
||||
search = search.trim()
|
||||
var info = prepareLowerInfo(search)
|
||||
|
||||
var spaceSearches = []
|
||||
if(info.containsSpace) {
|
||||
var searches = search.split(/\s+/)
|
||||
searches = [...new Set(searches)] // distinct
|
||||
for(var i=0; i<searches.length; i++) {
|
||||
if(searches[i] === '') continue
|
||||
var _info = prepareLowerInfo(searches[i])
|
||||
spaceSearches.push({lowerCodes:_info.lowerCodes, _lower:searches[i].toLowerCase(), containsSpace:false})
|
||||
}
|
||||
}
|
||||
|
||||
return {lowerCodes: info.lowerCodes, _lower: info._lower, containsSpace: info.containsSpace, bitflags: info.bitflags, spaceSearches: spaceSearches}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var getPrepared = (target) => {
|
||||
if(target.length > 999) return prepare(target) // don't cache huge targets
|
||||
var targetPrepared = preparedCache.get(target)
|
||||
if(targetPrepared !== undefined) return targetPrepared
|
||||
targetPrepared = prepare(target)
|
||||
preparedCache.set(target, targetPrepared)
|
||||
return targetPrepared
|
||||
}
|
||||
var getPreparedSearch = (search) => {
|
||||
if(search.length > 999) return prepareSearch(search) // don't cache huge searches
|
||||
var searchPrepared = preparedSearchCache.get(search)
|
||||
if(searchPrepared !== undefined) return searchPrepared
|
||||
searchPrepared = prepareSearch(search)
|
||||
preparedSearchCache.set(search, searchPrepared)
|
||||
return searchPrepared
|
||||
}
|
||||
|
||||
|
||||
var all = (targets, options) => {
|
||||
var results = []; results.total = targets.length // this total can be wrong if some targets are skipped
|
||||
|
||||
var limit = options?.limit || INFINITY
|
||||
|
||||
if(options?.key) {
|
||||
for(var i=0;i<targets.length;i++) { var obj = targets[i]
|
||||
var target = getValue(obj, options.key)
|
||||
if(target == NULL) continue
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
var result = new_result(target.target, {_score: target._score, obj: obj})
|
||||
results.push(result); if(results.length >= limit) return results
|
||||
}
|
||||
} else if(options?.keys) {
|
||||
for(var i=0;i<targets.length;i++) { var obj = targets[i]
|
||||
var objResults = new KeysResult(options.keys.length)
|
||||
for (var keyI = options.keys.length - 1; keyI >= 0; --keyI) {
|
||||
var target = getValue(obj, options.keys[keyI])
|
||||
if(!target) { objResults[keyI] = noTarget; continue }
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
target._score = NEGATIVE_INFINITY
|
||||
target._indexes.len = 0
|
||||
objResults[keyI] = target
|
||||
}
|
||||
objResults.obj = obj
|
||||
objResults._score = NEGATIVE_INFINITY
|
||||
results.push(objResults); if(results.length >= limit) return results
|
||||
}
|
||||
} else {
|
||||
for(var i=0;i<targets.length;i++) { var target = targets[i]
|
||||
if(target == NULL) continue
|
||||
if(!isPrepared(target)) target = getPrepared(target)
|
||||
target._score = NEGATIVE_INFINITY
|
||||
target._indexes.len = 0
|
||||
results.push(target); if(results.length >= limit) return results
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
var algorithm = (preparedSearch, prepared, allowSpaces=false, allowPartialMatch=false) => {
|
||||
if(allowSpaces===false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared, allowPartialMatch)
|
||||
|
||||
var searchLower = preparedSearch._lower
|
||||
var searchLowerCodes = preparedSearch.lowerCodes
|
||||
var searchLowerCode = searchLowerCodes[0]
|
||||
var targetLowerCodes = prepared._targetLowerCodes
|
||||
var searchLen = searchLowerCodes.length
|
||||
var targetLen = targetLowerCodes.length
|
||||
var searchI = 0 // where we at
|
||||
var targetI = 0 // where you at
|
||||
var matchesSimpleLen = 0
|
||||
|
||||
// very basic fuzzy match; to remove non-matching targets ASAP!
|
||||
// walk through target. find sequential matches.
|
||||
// if all chars aren't found then exit
|
||||
for(;;) {
|
||||
var isMatch = searchLowerCode === targetLowerCodes[targetI]
|
||||
if(isMatch) {
|
||||
matchesSimple[matchesSimpleLen++] = targetI
|
||||
++searchI; if(searchI === searchLen) break
|
||||
searchLowerCode = searchLowerCodes[searchI]
|
||||
}
|
||||
++targetI; if(targetI >= targetLen) return NULL // Failed to find searchI
|
||||
}
|
||||
|
||||
var searchI = 0
|
||||
var successStrict = false
|
||||
var matchesStrictLen = 0
|
||||
|
||||
var nextBeginningIndexes = prepared._nextBeginningIndexes
|
||||
if(nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target)
|
||||
targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
|
||||
|
||||
// Our target string successfully matched all characters in sequence!
|
||||
// Let's try a more advanced and strict test to improve the score
|
||||
// only count it as a match if it's consecutive or a beginning character!
|
||||
var backtrackCount = 0
|
||||
if(targetI !== targetLen) for(;;) {
|
||||
if(targetI >= targetLen) {
|
||||
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
|
||||
if(searchI <= 0) break // We failed to push chars forward for a better match
|
||||
|
||||
++backtrackCount; if(backtrackCount > 200) break // exponential backtracking is taking too long, just give up and return a bad match
|
||||
|
||||
--searchI
|
||||
var lastMatch = matchesStrict[--matchesStrictLen]
|
||||
targetI = nextBeginningIndexes[lastMatch]
|
||||
|
||||
} else {
|
||||
var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]
|
||||
if(isMatch) {
|
||||
matchesStrict[matchesStrictLen++] = targetI
|
||||
++searchI; if(searchI === searchLen) { successStrict = true; break }
|
||||
++targetI
|
||||
} else {
|
||||
targetI = nextBeginningIndexes[targetI]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if it's a substring match
|
||||
var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow
|
||||
var isSubstring = !!~substringIndex
|
||||
var isSubstringBeginning = !isSubstring ? false : substringIndex===0 || prepared._nextBeginningIndexes[substringIndex-1] === substringIndex
|
||||
|
||||
// if it's a substring match but not at a beginning index, let's try to find a substring starting at a beginning index for a better score
|
||||
if(isSubstring && !isSubstringBeginning) {
|
||||
for(var i=0; i<nextBeginningIndexes.length; i=nextBeginningIndexes[i]) {
|
||||
if(i <= substringIndex) continue
|
||||
|
||||
for(var s=0; s<searchLen; s++) if(searchLowerCodes[s] !== prepared._targetLowerCodes[i+s]) break
|
||||
if(s === searchLen) { substringIndex = i; isSubstringBeginning = true; break }
|
||||
}
|
||||
}
|
||||
|
||||
// tally up the score & keep track of matches for highlighting later
|
||||
// if it's a simple match, we'll switch to a substring match if a substring exists
|
||||
// if it's a strict match, we'll switch to a substring match only if that's a better score
|
||||
|
||||
var calculateScore = matches => {
|
||||
var score = 0
|
||||
|
||||
var extraMatchGroupCount = 0
|
||||
for(var i = 1; i < searchLen; ++i) {
|
||||
if(matches[i] - matches[i-1] !== 1) {score -= matches[i]; ++extraMatchGroupCount}
|
||||
}
|
||||
var unmatchedDistance = matches[searchLen-1] - matches[0] - (searchLen-1)
|
||||
|
||||
score -= (12+unmatchedDistance) * extraMatchGroupCount // penality for more groups
|
||||
|
||||
if(matches[0] !== 0) score -= matches[0]*matches[0]*.2 // penality for not starting near the beginning
|
||||
|
||||
if(!successStrict) {
|
||||
score *= 1000
|
||||
} else {
|
||||
// successStrict on a target with too many beginning indexes loses points for being a bad target
|
||||
var uniqueBeginningIndexes = 1
|
||||
for(var i = nextBeginningIndexes[0]; i < targetLen; i=nextBeginningIndexes[i]) ++uniqueBeginningIndexes
|
||||
|
||||
if(uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes-24)*10 // quite arbitrary numbers here ...
|
||||
}
|
||||
|
||||
score -= (targetLen - searchLen)/2 // penality for longer targets
|
||||
|
||||
if(isSubstring) score /= 1+searchLen*searchLen*1 // bonus for being a full substring
|
||||
if(isSubstringBeginning) score /= 1+searchLen*searchLen*1 // bonus for substring starting on a beginningIndex
|
||||
|
||||
score -= (targetLen - searchLen)/2 // penality for longer targets
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
if(!successStrict) {
|
||||
if(isSubstring) for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches
|
||||
var matchesBest = matchesSimple
|
||||
var score = calculateScore(matchesBest)
|
||||
} else {
|
||||
if(isSubstringBeginning) {
|
||||
for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches
|
||||
var matchesBest = matchesSimple
|
||||
var score = calculateScore(matchesSimple)
|
||||
} else {
|
||||
var matchesBest = matchesStrict
|
||||
var score = calculateScore(matchesStrict)
|
||||
}
|
||||
}
|
||||
|
||||
prepared._score = score
|
||||
|
||||
for(var i = 0; i < searchLen; ++i) prepared._indexes[i] = matchesBest[i]
|
||||
prepared._indexes.len = searchLen
|
||||
|
||||
const result = new Result()
|
||||
result.target = prepared.target
|
||||
result._score = prepared._score
|
||||
result._indexes = prepared._indexes
|
||||
return result
|
||||
}
|
||||
var algorithmSpaces = (preparedSearch, target, allowPartialMatch) => {
|
||||
var seen_indexes = new Set()
|
||||
var score = 0
|
||||
var result = NULL
|
||||
|
||||
var first_seen_index_last_search = 0
|
||||
var searches = preparedSearch.spaceSearches
|
||||
var searchesLen = searches.length
|
||||
var changeslen = 0
|
||||
|
||||
// Return _nextBeginningIndexes back to its normal state
|
||||
var resetNextBeginningIndexes = () => {
|
||||
for(let i=changeslen-1; i>=0; i--) target._nextBeginningIndexes[nextBeginningIndexesChanges[i*2 + 0]] = nextBeginningIndexesChanges[i*2 + 1]
|
||||
}
|
||||
|
||||
var hasAtLeast1Match = false
|
||||
for(var i=0; i<searchesLen; ++i) {
|
||||
allowPartialMatchScores[i] = NEGATIVE_INFINITY
|
||||
var search = searches[i]
|
||||
|
||||
result = algorithm(search, target)
|
||||
if(allowPartialMatch) {
|
||||
if(result === NULL) continue
|
||||
hasAtLeast1Match = true
|
||||
} else {
|
||||
if(result === NULL) {resetNextBeginningIndexes(); return NULL}
|
||||
}
|
||||
|
||||
// if not the last search, we need to mutate _nextBeginningIndexes for the next search
|
||||
var isTheLastSearch = i === searchesLen - 1
|
||||
if(!isTheLastSearch) {
|
||||
var indexes = result._indexes
|
||||
|
||||
var indexesIsConsecutiveSubstring = true
|
||||
for(let i=0; i<indexes.len-1; i++) {
|
||||
if(indexes[i+1] - indexes[i] !== 1) {
|
||||
indexesIsConsecutiveSubstring = false; break;
|
||||
}
|
||||
}
|
||||
|
||||
if(indexesIsConsecutiveSubstring) {
|
||||
var newBeginningIndex = indexes[indexes.len-1] + 1
|
||||
var toReplace = target._nextBeginningIndexes[newBeginningIndex-1]
|
||||
for(let i=newBeginningIndex-1; i>=0; i--) {
|
||||
if(toReplace !== target._nextBeginningIndexes[i]) break
|
||||
target._nextBeginningIndexes[i] = newBeginningIndex
|
||||
nextBeginningIndexesChanges[changeslen*2 + 0] = i
|
||||
nextBeginningIndexesChanges[changeslen*2 + 1] = toReplace
|
||||
changeslen++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
score += result._score / searchesLen
|
||||
allowPartialMatchScores[i] = result._score / searchesLen
|
||||
|
||||
// dock points based on order otherwise "c man" returns Manifest.cpp instead of CheatManager.h
|
||||
if(result._indexes[0] < first_seen_index_last_search) {
|
||||
score -= (first_seen_index_last_search - result._indexes[0]) * 2
|
||||
}
|
||||
first_seen_index_last_search = result._indexes[0]
|
||||
|
||||
for(var j=0; j<result._indexes.len; ++j) seen_indexes.add(result._indexes[j])
|
||||
}
|
||||
|
||||
if(allowPartialMatch && !hasAtLeast1Match) return NULL
|
||||
|
||||
resetNextBeginningIndexes()
|
||||
|
||||
// allows a search with spaces that's an exact substring to score well
|
||||
var allowSpacesResult = algorithm(preparedSearch, target, /*allowSpaces=*/true)
|
||||
if(allowSpacesResult !== NULL && allowSpacesResult._score > score) {
|
||||
if(allowPartialMatch) {
|
||||
for(var i=0; i<searchesLen; ++i) {
|
||||
allowPartialMatchScores[i] = allowSpacesResult._score / searchesLen
|
||||
}
|
||||
}
|
||||
return allowSpacesResult
|
||||
}
|
||||
|
||||
if(allowPartialMatch) result = target
|
||||
result._score = score
|
||||
|
||||
var i = 0
|
||||
for (let index of seen_indexes) result._indexes[i++] = index
|
||||
result._indexes.len = i
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// we use this instead of just .normalize('NFD').replace(/[\u0300-\u036f]/g, '') because that screws with japanese characters
|
||||
var remove_accents = (str) => str.replace(/\p{Script=Latin}+/gu, match => match.normalize('NFD')).replace(/[\u0300-\u036f]/g, '')
|
||||
|
||||
var prepareLowerInfo = (str) => {
|
||||
str = remove_accents(str)
|
||||
var strLen = str.length
|
||||
var lower = str.toLowerCase()
|
||||
var lowerCodes = [] // new Array(strLen) sparse array is too slow
|
||||
var bitflags = 0
|
||||
var containsSpace = false // space isn't stored in bitflags because of how searching with a space works
|
||||
|
||||
for(var i = 0; i < strLen; ++i) {
|
||||
var lowerCode = lowerCodes[i] = lower.charCodeAt(i)
|
||||
|
||||
if(lowerCode === 32) {
|
||||
containsSpace = true
|
||||
continue // it's important that we don't set any bitflags for space
|
||||
}
|
||||
|
||||
var bit = lowerCode>=97&&lowerCode<=122 ? lowerCode-97 // alphabet
|
||||
: lowerCode>=48&&lowerCode<=57 ? 26 // numbers
|
||||
// 3 bits available
|
||||
: lowerCode<=127 ? 30 // other ascii
|
||||
: 31 // other utf8
|
||||
bitflags |= 1<<bit
|
||||
}
|
||||
|
||||
return {lowerCodes:lowerCodes, bitflags:bitflags, containsSpace:containsSpace, _lower:lower}
|
||||
}
|
||||
var prepareBeginningIndexes = (target) => {
|
||||
var targetLen = target.length
|
||||
var beginningIndexes = []; var beginningIndexesLen = 0
|
||||
var wasUpper = false
|
||||
var wasAlphanum = false
|
||||
for(var i = 0; i < targetLen; ++i) {
|
||||
var targetCode = target.charCodeAt(i)
|
||||
var isUpper = targetCode>=65&&targetCode<=90
|
||||
var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57
|
||||
var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum
|
||||
wasUpper = isUpper
|
||||
wasAlphanum = isAlphanum
|
||||
if(isBeginning) beginningIndexes[beginningIndexesLen++] = i
|
||||
}
|
||||
return beginningIndexes
|
||||
}
|
||||
var prepareNextBeginningIndexes = (target) => {
|
||||
target = remove_accents(target)
|
||||
var targetLen = target.length
|
||||
var beginningIndexes = prepareBeginningIndexes(target)
|
||||
var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
|
||||
var lastIsBeginning = beginningIndexes[0]
|
||||
var lastIsBeginningI = 0
|
||||
for(var i = 0; i < targetLen; ++i) {
|
||||
if(lastIsBeginning > i) {
|
||||
nextBeginningIndexes[i] = lastIsBeginning
|
||||
} else {
|
||||
lastIsBeginning = beginningIndexes[++lastIsBeginningI]
|
||||
nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning
|
||||
}
|
||||
}
|
||||
return nextBeginningIndexes
|
||||
}
|
||||
|
||||
var preparedCache = new Map()
|
||||
var preparedSearchCache = new Map()
|
||||
|
||||
// the theory behind these being globals is to reduce garbage collection by not making new arrays
|
||||
var matchesSimple = []; var matchesStrict = []
|
||||
var nextBeginningIndexesChanges = [] // allows straw berry to match strawberry well, by modifying the end of a substring to be considered a beginning index for the rest of the search
|
||||
var keysSpacesBestScores = []; var allowPartialMatchScores = []
|
||||
var tmpTargets = []; var tmpResults = []
|
||||
|
||||
// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
|
||||
// prop = 'key1.key2' 10ms
|
||||
// prop = ['key1', 'key2'] 27ms
|
||||
// prop = obj => obj.tags.join() ??ms
|
||||
var getValue = (obj, prop) => {
|
||||
var tmp = obj[prop]; if(tmp !== undefined) return tmp
|
||||
if(typeof prop === 'function') return prop(obj) // this should run first. but that makes string props slower
|
||||
var segs = prop
|
||||
if(!Array.isArray(prop)) segs = prop.split('.')
|
||||
var len = segs.length
|
||||
var i = -1
|
||||
while (obj && (++i < len)) obj = obj[segs[i]]
|
||||
return obj
|
||||
}
|
||||
|
||||
var isPrepared = (x) => { return typeof x === 'object' && typeof x._bitflags === 'number' }
|
||||
var INFINITY = Infinity; var NEGATIVE_INFINITY = -INFINITY
|
||||
var noResults = []; noResults.total = 0
|
||||
var NULL = null
|
||||
|
||||
var noTarget = prepare('')
|
||||
|
||||
// Hacked version of https://github.com/lemire/FastPriorityQueue.js
|
||||
var fastpriorityqueue=r=>{var e=[],o=0,a={},v=r=>{for(var a=0,v=e[a],c=1;c<o;){var s=c+1;a=c,s<o&&e[s]._score<e[c]._score&&(a=s),e[a-1>>1]=e[a],c=1+(a<<1)}for(var f=a-1>>1;a>0&&v._score<e[f]._score;f=(a=f)-1>>1)e[a]=e[f];e[a]=v};return a.add=(r=>{var a=o;e[o++]=r;for(var v=a-1>>1;a>0&&r._score<e[v]._score;v=(a=v)-1>>1)e[a]=e[v];e[a]=r}),a.poll=(r=>{if(0!==o){var a=e[0];return e[0]=e[--o],v(),a}}),a.peek=(r=>{if(0!==o)return e[0]}),a.replaceTop=(r=>{e[0]=r,v()}),a}
|
||||
var q = fastpriorityqueue() // reuse this
|
||||
@@ -1,141 +0,0 @@
|
||||
// Original code from https://github.com/koeqaife/hyprland-material-you
|
||||
// Original code license: GPLv3
|
||||
// Translated to Js from Cython with an LLM and reviewed
|
||||
|
||||
function min3(a, b, c) {
|
||||
return a < b && a < c ? a : b < c ? b : c;
|
||||
}
|
||||
|
||||
function max3(a, b, c) {
|
||||
return a > b && a > c ? a : b > c ? b : c;
|
||||
}
|
||||
|
||||
function min2(a, b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
function max2(a, b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
function levenshteinDistance(s1, s2) {
|
||||
let len1 = s1.length;
|
||||
let len2 = s2.length;
|
||||
|
||||
if (len1 === 0) return len2;
|
||||
if (len2 === 0) return len1;
|
||||
|
||||
if (len2 > len1) {
|
||||
[s1, s2] = [s2, s1];
|
||||
[len1, len2] = [len2, len1];
|
||||
}
|
||||
|
||||
let prev = new Array(len2 + 1);
|
||||
let curr = new Array(len2 + 1);
|
||||
|
||||
for (let j = 0; j <= len2; j++) {
|
||||
prev[j] = j;
|
||||
}
|
||||
|
||||
for (let i = 1; i <= len1; i++) {
|
||||
curr[0] = i;
|
||||
for (let j = 1; j <= len2; j++) {
|
||||
let cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
||||
curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
||||
}
|
||||
[prev, curr] = [curr, prev];
|
||||
}
|
||||
|
||||
return prev[len2];
|
||||
}
|
||||
|
||||
function partialRatio(shortS, longS) {
|
||||
let lenS = shortS.length;
|
||||
let lenL = longS.length;
|
||||
let best = 0.0;
|
||||
|
||||
if (lenS === 0) return 1.0;
|
||||
|
||||
for (let i = 0; i <= lenL - lenS; i++) {
|
||||
let sub = longS.slice(i, i + lenS);
|
||||
let dist = levenshteinDistance(shortS, sub);
|
||||
let score = 1.0 - (dist / lenS);
|
||||
if (score > best) best = score;
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
function computeScore(s1, s2) {
|
||||
if (s1 === s2) return 1.0;
|
||||
|
||||
let dist = levenshteinDistance(s1, s2);
|
||||
let maxLen = max2(s1.length, s2.length);
|
||||
if (maxLen === 0) return 1.0;
|
||||
|
||||
let full = 1.0 - (dist / maxLen);
|
||||
let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
|
||||
|
||||
let score = 0.85 * full + 0.15 * part;
|
||||
|
||||
if (s1 && s2 && s1[0] !== s2[0]) {
|
||||
score -= 0.05;
|
||||
}
|
||||
|
||||
let lenDiff = Math.abs(s1.length - s2.length);
|
||||
if (lenDiff >= 3) {
|
||||
score -= 0.05 * lenDiff / maxLen;
|
||||
}
|
||||
|
||||
let commonPrefixLen = 0;
|
||||
let minLen = min2(s1.length, s2.length);
|
||||
for (let i = 0; i < minLen; i++) {
|
||||
if (s1[i] === s2[i]) {
|
||||
commonPrefixLen++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
score += 0.02 * commonPrefixLen;
|
||||
|
||||
if (s1.includes(s2) || s2.includes(s1)) {
|
||||
score += 0.06;
|
||||
}
|
||||
|
||||
return Math.max(0.0, Math.min(1.0, score));
|
||||
}
|
||||
|
||||
function computeTextMatchScore(s1, s2) {
|
||||
if (s1 === s2) return 1.0;
|
||||
|
||||
let dist = levenshteinDistance(s1, s2);
|
||||
let maxLen = max2(s1.length, s2.length);
|
||||
if (maxLen === 0) return 1.0;
|
||||
|
||||
let full = 1.0 - (dist / maxLen);
|
||||
let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
|
||||
|
||||
let score = 0.4 * full + 0.6 * part;
|
||||
|
||||
let lenDiff = Math.abs(s1.length - s2.length);
|
||||
if (lenDiff >= 10) {
|
||||
score -= 0.02 * lenDiff / maxLen;
|
||||
}
|
||||
|
||||
let commonPrefixLen = 0;
|
||||
let minLen = min2(s1.length, s2.length);
|
||||
for (let i = 0; i < minLen; i++) {
|
||||
if (s1[i] === s2[i]) {
|
||||
commonPrefixLen++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
score += 0.01 * commonPrefixLen;
|
||||
|
||||
if (s1.includes(s2) || s2.includes(s1)) {
|
||||
score += 0.2;
|
||||
}
|
||||
|
||||
return Math.max(0.0, Math.min(1.0, score));
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
|
||||
/**
|
||||
* Draws a hexagon when width == height.
|
||||
* Otherwise the hexagon is extended
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
property real radius: Math.min(width, height) / 2
|
||||
property real cornerRounding: radius * 0.5
|
||||
property color color: "#b7eb34"
|
||||
property real borderWidth: cornerRounding
|
||||
property color borderColor: color
|
||||
|
||||
Shape {
|
||||
id: hexShape
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
id: hexPath
|
||||
fillColor: root.color
|
||||
strokeColor: root.borderColor
|
||||
strokeWidth: root.borderWidth
|
||||
capStyle: ShapePath.RoundCap
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
|
||||
property real r: root.radius
|
||||
property real r60: r * Math.sqrt(3) / 2
|
||||
property real r30: r / 2
|
||||
property real cr: root.cornerRounding
|
||||
property real cr60: cr * Math.sqrt(3) / 2
|
||||
property real cr30: cr / 2
|
||||
property real lineWidthAdjustment: strokeWidth / 2
|
||||
property real lineWidthAdjustment60: lineWidthAdjustment * Math.sqrt(3) / 2
|
||||
property real lineWidthAdjustment30: lineWidthAdjustment / 2
|
||||
|
||||
startX: hexPath.r; startY: lineWidthAdjustment;
|
||||
PathLine { x: hexPath.r + hexPath.r60 - hexPath.lineWidthAdjustment60; y: hexShape.height / 2 - hexPath.r30 + hexPath.lineWidthAdjustment30 }
|
||||
PathLine { x: hexPath.r + hexPath.r60 - hexPath.lineWidthAdjustment60; y: hexShape.height / 2 + hexPath.r30 - hexPath.lineWidthAdjustment30 }
|
||||
PathLine { x: hexPath.r; y: hexShape.height - hexPath.lineWidthAdjustment }
|
||||
PathLine { x: hexPath.r - hexPath.r60 + hexPath.lineWidthAdjustment60; y: hexShape.height - hexPath.r30 - hexPath.lineWidthAdjustment30 }
|
||||
PathLine { x: hexPath.r - hexPath.r60 + hexPath.lineWidthAdjustment60; y: hexPath.r30 + hexPath.lineWidthAdjustment30 }
|
||||
// Close the path
|
||||
PathLine { x: hexPath.r; y: hexPath.lineWidthAdjustment }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import qs.singletons
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
id: root
|
||||
property real iconSize: Appearance?.font.pixelSize.small ?? 16
|
||||
property real fill: 0
|
||||
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
font {
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Outlined"
|
||||
pixelSize: iconSize
|
||||
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
|
||||
variableAxes: {
|
||||
"FILL": truncatedFill,
|
||||
// "wght": font.weight,
|
||||
// "GRAD": 0,
|
||||
"opsz": iconSize,
|
||||
}
|
||||
}
|
||||
color: Appearance.m3colors.m3onBackground
|
||||
|
||||
// Behavior on fill {
|
||||
// NumberAnimation {
|
||||
// duration: Appearance?.animation.elementMoveFast.duration ?? 200
|
||||
// easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
|
||||
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import QtQuick
|
||||
|
||||
/**
|
||||
* Draws an octagon when width == height.
|
||||
* Otherwise it's a rectangle "rounded" with two edges each corner (like 1/4 of an octagon)
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
property real radius: Math.min(width, height) / 2
|
||||
property color color: "#b7eb34"
|
||||
|
||||
onWidthChanged: polyRect.requestPaint()
|
||||
onHeightChanged: polyRect.requestPaint()
|
||||
onRadiusChanged: polyRect.requestPaint()
|
||||
onColorChanged: polyRect.requestPaint()
|
||||
|
||||
Canvas {
|
||||
id: polyRect
|
||||
anchors.fill: parent
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
var r = root.radius;
|
||||
var r45 = r * Math.SQRT2 / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(r, 0);
|
||||
ctx.lineTo(width - r, 0);
|
||||
ctx.lineTo(width - r + r45, r - r45);
|
||||
ctx.lineTo(width, r);
|
||||
ctx.lineTo(width, height - r);
|
||||
ctx.lineTo(width - r + r45, height - r + r45);
|
||||
ctx.lineTo(width - r, height);
|
||||
ctx.lineTo(r, height);
|
||||
ctx.lineTo(r - r45, height - r + r45);
|
||||
ctx.lineTo(0, height - r);
|
||||
ctx.lineTo(0, r);
|
||||
ctx.lineTo(r - r45, r - r45);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = root.color;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import qs.singletons
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Text {
|
||||
renderType: Text.NativeRendering
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font {
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: Appearance?.font.family.main ?? "sans-serif"
|
||||
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||
}
|
||||
color: Appearance?.colors.on_background ?? "black"
|
||||
linkColor: Appearance?.colors.primary ?? "blue"
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import cv2
|
||||
import json
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
DEFAULT_IMAGE_PATH = '/tmp/quickshell/media/screenshot/image'
|
||||
|
||||
def iou(boxA, boxB):
|
||||
# Compute intersection over union for two boxes
|
||||
xA = max(boxA['x'], boxB['x'])
|
||||
yA = max(boxA['y'], boxB['y'])
|
||||
xB = min(boxA['x'] + boxA['width'], boxB['x'] + boxB['width'])
|
||||
yB = min(boxA['y'] + boxA['height'], boxB['y'] + boxB['height'])
|
||||
interW = max(0, xB - xA)
|
||||
interH = max(0, yB - yA)
|
||||
interArea = interW * interH
|
||||
boxAArea = boxA['width'] * boxA['height']
|
||||
boxBArea = boxB['width'] * boxB['height']
|
||||
iou = interArea / float(boxAArea + boxBArea - interArea) if (boxAArea + boxBArea - interArea) > 0 else 0
|
||||
return iou
|
||||
|
||||
def non_max_suppression(regions, iou_threshold=0.7):
|
||||
# Sort by area (largest first)
|
||||
regions = sorted(regions, key=lambda r: r['width'] * r['height'], reverse=True)
|
||||
keep = []
|
||||
while regions:
|
||||
current = regions.pop(0)
|
||||
keep.append(current)
|
||||
regions = [r for r in regions if iou(current, r) < iou_threshold]
|
||||
return keep
|
||||
|
||||
def find_regions(image_path, min_width, min_height, max_width=None, max_height=None, quality=False, k=150, min_size=20, sigma=0.8, resize_factor=1.0):
|
||||
image = cv2.imread(image_path)
|
||||
if image is None:
|
||||
print(f'Error: Could not load image {image_path}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
orig_h, orig_w = image.shape[:2]
|
||||
if resize_factor != 1.0:
|
||||
image = cv2.resize(image, (int(orig_w * resize_factor), int(orig_h * resize_factor)), interpolation=cv2.INTER_AREA)
|
||||
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
|
||||
ss.setBaseImage(image)
|
||||
if quality:
|
||||
ss.switchToSelectiveSearchQuality(k, min_size, sigma)
|
||||
else:
|
||||
ss.switchToSelectiveSearchFast(k, min_size, sigma)
|
||||
rects = ss.process()
|
||||
regions = []
|
||||
for (x, y, w, h) in rects:
|
||||
# Scale regions back to original image size if resized
|
||||
if resize_factor != 1.0:
|
||||
x = int(x / resize_factor)
|
||||
y = int(y / resize_factor)
|
||||
w = int(w / resize_factor)
|
||||
h = int(h / resize_factor)
|
||||
# Filter out region that is exactly the same size as the original image
|
||||
if w == orig_w and h == orig_h and x == 0 and y == 0:
|
||||
continue
|
||||
if w > min_width and h > min_height:
|
||||
if (max_width is None or w < max_width) and (max_height is None or h < max_height):
|
||||
regions.append({'x': int(x), 'y': int(y), 'width': int(w), 'height': int(h)})
|
||||
# Remove duplicates/overlaps
|
||||
regions = non_max_suppression(regions, iou_threshold=0.7)
|
||||
return regions, cv2.imread(image_path) # Return original image for drawing
|
||||
|
||||
def draw_regions(image, regions, output_path):
|
||||
for region in regions:
|
||||
if 'x' in region:
|
||||
x, y, w, h = region['x'], region['y'], region['width'], region['height']
|
||||
elif 'at' in region and 'size' in region:
|
||||
x, y = region['at']
|
||||
w, h = region['size']
|
||||
else:
|
||||
continue
|
||||
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
|
||||
cv2.imwrite(output_path, image)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Find regions of interest in an image using selective search.')
|
||||
parser.add_argument('-i', '--image', default=DEFAULT_IMAGE_PATH, help='Path to input image')
|
||||
parser.add_argument('-do', '--debug-output', help='Path to save debug image with rectangles')
|
||||
parser.add_argument('--min-width', type=int, default=200, help='Minimum width of detected region')
|
||||
parser.add_argument('--min-height', type=int, default=100, help='Minimum height of detected region')
|
||||
parser.add_argument('--max-width', type=int, help='Maximum width of detected region')
|
||||
parser.add_argument('--max-height', type=int, help='Maximum height of detected region')
|
||||
parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region')
|
||||
parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)')
|
||||
parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)')
|
||||
parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)')
|
||||
parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)')
|
||||
parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)')
|
||||
parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}')
|
||||
args = parser.parse_args()
|
||||
|
||||
regions, image = find_regions(
|
||||
args.image,
|
||||
min_width=args.min_width,
|
||||
min_height=args.min_height,
|
||||
max_width=args.max_width,
|
||||
max_height=args.max_height,
|
||||
quality=args.quality,
|
||||
k=args.k,
|
||||
min_size=args.min_size,
|
||||
sigma=args.sigma,
|
||||
resize_factor=args.resize_factor
|
||||
)
|
||||
if args.single and regions:
|
||||
largest = max(regions, key=lambda r: r['width'] * r['height'])
|
||||
regions = [largest]
|
||||
if args.hyprctl:
|
||||
regions = [{"at": [r['x'], r['y']], "size": [r['width'], r['height']]} for r in regions]
|
||||
print(json.dumps(regions))
|
||||
if args.debug_output:
|
||||
draw_regions(image, regions, args.debug_output)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
//@ pragma UseQApplication
|
||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
|
||||
|
||||
// Adjust this to make the shell smaller or larger
|
||||
//@ pragma Env QT_SCALE_FACTOR=1
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import Quickshell
|
||||
import qs.singletons
|
||||
import "./modules/background/"
|
||||
import "./modules/bar/"
|
||||
|
||||
ShellRoot {
|
||||
// Some initialization
|
||||
Component.onCompleted: {
|
||||
MaterialThemeLoader.reapplyTheme()
|
||||
}
|
||||
|
||||
Background {}
|
||||
Bar {}
|
||||
}
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.modules.common.functions
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property QtObject m3colors
|
||||
property QtObject animation
|
||||
property QtObject animationCurves
|
||||
property QtObject colors
|
||||
property QtObject rounding
|
||||
property QtObject font
|
||||
property QtObject sizes
|
||||
property string syntaxHighlightingTheme
|
||||
|
||||
// Extremely conservative transparency values for consistency and readability
|
||||
// property real transparency: m3colors.darkmode ? 0.2 : 0.1
|
||||
// property real contentTransparency: m3colors.darkmode ? 0.55 : 0.55
|
||||
property real transparency: 0
|
||||
property real contentTransparency: 0
|
||||
property color absoluteBackground: m3colors.darkmode ? "#000000" : "#FFFFFF"
|
||||
|
||||
m3colors: QtObject {
|
||||
property bool darkmode: false
|
||||
property bool transparent: false
|
||||
property color m3primary_paletteKeyColor: "#91689E"
|
||||
property color m3secondary_paletteKeyColor: "#837186"
|
||||
property color m3tertiary_paletteKeyColor: "#9D6A67"
|
||||
property color m3neutral_paletteKeyColor: "#7C757B"
|
||||
property color m3neutral_variant_paletteKeyColor: "#7D747D"
|
||||
property color m3background: "#161217"
|
||||
property color m3onBackground: "#EAE0E7"
|
||||
property color m3surface: "#161217"
|
||||
property color m3surfaceDim: "#161217"
|
||||
property color m3surfaceBright: "#3D373D"
|
||||
property color m3surfaceContainerLowest: "#110D12"
|
||||
property color m3surfaceContainerLow: "#1F1A1F"
|
||||
property color m3surfaceContainer: "#231E23"
|
||||
property color m3surfaceContainerHigh: "#2D282E"
|
||||
property color m3surfaceContainerHighest: "#383339"
|
||||
property color m3onSurface: "#EAE0E7"
|
||||
property color m3surfaceVariant: "#4C444D"
|
||||
property color m3onSurfaceVariant: "#CFC3CD"
|
||||
property color m3inverseSurface: "#EAE0E7"
|
||||
property color m3inverseOnSurface: "#342F34"
|
||||
property color m3outline: "#988E97"
|
||||
property color m3outlineVariant: "#4C444D"
|
||||
property color m3shadow: "#000000"
|
||||
property color m3scrim: "#000000"
|
||||
property color m3surfaceTint: "#E5B6F2"
|
||||
property color m3primary: "#E5B6F2"
|
||||
property color m3onPrimary: "#452152"
|
||||
property color m3primaryContainer: "#5D386A"
|
||||
property color m3onPrimaryContainer: "#F9D8FF"
|
||||
property color m3inversePrimary: "#775084"
|
||||
property color m3secondary: "#D5C0D7"
|
||||
property color m3onSecondary: "#392C3D"
|
||||
property color m3secondaryContainer: "#534457"
|
||||
property color m3onSecondaryContainer: "#F2DCF3"
|
||||
property color m3tertiary: "#F5B7B3"
|
||||
property color m3onTertiary: "#4C2523"
|
||||
property color m3tertiaryContainer: "#BA837F"
|
||||
property color m3onTertiaryContainer: "#000000"
|
||||
property color m3error: "#FFB4AB"
|
||||
property color m3onError: "#690005"
|
||||
property color m3errorContainer: "#93000A"
|
||||
property color m3onErrorContainer: "#FFDAD6"
|
||||
property color m3primaryFixed: "#F9D8FF"
|
||||
property color m3primaryFixedDim: "#E5B6F2"
|
||||
property color m3onPrimaryFixed: "#2E0A3C"
|
||||
property color m3onPrimaryFixedVariant: "#5D386A"
|
||||
property color m3secondaryFixed: "#F2DCF3"
|
||||
property color m3secondaryFixedDim: "#D5C0D7"
|
||||
property color m3onSecondaryFixed: "#241727"
|
||||
property color m3onSecondaryFixedVariant: "#514254"
|
||||
property color m3tertiaryFixed: "#FFDAD7"
|
||||
property color m3tertiaryFixedDim: "#F5B7B3"
|
||||
property color m3onTertiaryFixed: "#331110"
|
||||
property color m3onTertiaryFixedVariant: "#663B39"
|
||||
property color m3success: "#B5CCBA"
|
||||
property color m3onSuccess: "#213528"
|
||||
property color m3successContainer: "#374B3E"
|
||||
property color m3onSuccessContainer: "#D1E9D6"
|
||||
property color term0: "#EDE4E4"
|
||||
property color term1: "#B52755"
|
||||
property color term2: "#A97363"
|
||||
property color term3: "#AF535D"
|
||||
property color term4: "#A67F7C"
|
||||
property color term5: "#B2416B"
|
||||
property color term6: "#8D76AD"
|
||||
property color term7: "#272022"
|
||||
property color term8: "#0E0D0D"
|
||||
property color term9: "#B52755"
|
||||
property color term10: "#A97363"
|
||||
property color term11: "#AF535D"
|
||||
property color term12: "#A67F7C"
|
||||
property color term13: "#B2416B"
|
||||
property color term14: "#8D76AD"
|
||||
property color term15: "#221A1A"
|
||||
}
|
||||
|
||||
colors: QtObject {
|
||||
property color colSubtext: m3colors.m3outline
|
||||
property color colLayer0: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3background, root.absoluteBackground, 0.5), root.transparency)
|
||||
property color colOnLayer0: m3colors.m3onBackground
|
||||
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
|
||||
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
|
||||
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
|
||||
property color colOnLayer1: m3colors.m3onSurfaceVariant;
|
||||
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
|
||||
property color colLayer2: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
|
||||
property color colLayer2Border: ColorUtils.mix(colLayer2, m3colors.m3outline, 0.8);
|
||||
property color colOnLayer2: m3colors.m3onSurface;
|
||||
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
|
||||
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
|
||||
property color colOnLayer3: m3colors.m3onSurface;
|
||||
property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency)
|
||||
property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency);
|
||||
property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency)
|
||||
property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency);
|
||||
property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency);
|
||||
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
|
||||
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
|
||||
property color colPrimary: m3colors.m3primary
|
||||
property color colOnPrimary: m3colors.m3onPrimary
|
||||
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
|
||||
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
|
||||
property color colPrimaryContainer: m3colors.m3primaryContainer
|
||||
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
|
||||
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6)
|
||||
property color colOnPrimaryContainer: m3colors.m3onPrimaryContainer
|
||||
property color colSecondary: m3colors.m3secondary
|
||||
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
|
||||
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
|
||||
property color colSecondaryContainer: m3colors.m3secondaryContainer
|
||||
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90)
|
||||
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
|
||||
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
|
||||
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
|
||||
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
|
||||
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
|
||||
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
|
||||
property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
|
||||
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
|
||||
property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043
|
||||
property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color
|
||||
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
|
||||
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
|
||||
property color colOutline: ColorUtils.mix(m3colors.m3outline, m3colors.m3background, 0.7)
|
||||
property color colOutlineVariant: ColorUtils.mix(m3colors.m3outlineVariant, m3colors.m3background, 0.5)
|
||||
}
|
||||
|
||||
rounding: QtObject {
|
||||
property int unsharpen: 2
|
||||
property int unsharpenmore: 6
|
||||
property int verysmall: 8
|
||||
property int small: 12
|
||||
property int normal: 17
|
||||
property int large: 23
|
||||
property int verylarge: 30
|
||||
property int full: 9999
|
||||
property int screenRounding: large
|
||||
property int windowRounding: 18
|
||||
}
|
||||
|
||||
font: QtObject {
|
||||
property QtObject family: QtObject {
|
||||
property string main: "Geist"
|
||||
property string title: "Gabarito"
|
||||
property string iconMaterial: "Material Symbols Outlined"
|
||||
property string iconNerd: "SpaceMono NF"
|
||||
property string monospace: "JetBrains Mono NF"
|
||||
property string reading: "Readex Pro"
|
||||
property string expressive: "Space Grotesk"
|
||||
}
|
||||
property QtObject pixelSize: QtObject {
|
||||
property int smallest: 10
|
||||
property int smaller: 12
|
||||
property int small: 15
|
||||
property int normal: 16
|
||||
property int large: 17
|
||||
property int larger: 19
|
||||
property int huge: 22
|
||||
property int hugeass: 23
|
||||
property int title: huge
|
||||
}
|
||||
}
|
||||
|
||||
animationCurves: QtObject {
|
||||
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms
|
||||
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms
|
||||
readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms
|
||||
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms
|
||||
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||
readonly property list<real> emphasizedFirstHalf: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82]
|
||||
readonly property list<real> emphasizedLastHalf: [5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
|
||||
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
|
||||
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
|
||||
readonly property real expressiveFastSpatialDuration: 350
|
||||
readonly property real expressiveDefaultSpatialDuration: 500
|
||||
readonly property real expressiveSlowSpatialDuration: 650
|
||||
readonly property real expressiveEffectsDuration: 200
|
||||
}
|
||||
|
||||
animation: QtObject {
|
||||
property QtObject elementMove: QtObject {
|
||||
property int duration: 300
|
||||
property int type: Easing.BezierSpline
|
||||
property list<real> bezierCurve: animationCurves.standardDecel
|
||||
property int velocity: 650
|
||||
property Component numberAnimation: Component {
|
||||
NumberAnimation {
|
||||
duration: root.animation.elementMove.duration
|
||||
easing.type: root.animation.elementMove.type
|
||||
easing.bezierCurve: root.animation.elementMove.bezierCurve
|
||||
}
|
||||
}
|
||||
property Component colorAnimation: Component {
|
||||
ColorAnimation {
|
||||
duration: root.animation.elementMove.duration
|
||||
easing.type: root.animation.elementMove.type
|
||||
easing.bezierCurve: root.animation.elementMove.bezierCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
property QtObject elementMoveEnter: QtObject {
|
||||
property int duration: 400
|
||||
property int type: Easing.BezierSpline
|
||||
property list<real> bezierCurve: animationCurves.emphasizedDecel
|
||||
property int velocity: 650
|
||||
property Component numberAnimation: Component {
|
||||
NumberAnimation {
|
||||
duration: root.animation.elementMoveEnter.duration
|
||||
easing.type: root.animation.elementMoveEnter.type
|
||||
easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
property QtObject elementMoveFast: QtObject {
|
||||
property int duration: 175
|
||||
property int type: Easing.BezierSpline
|
||||
property list<real> bezierCurve: animationCurves.expressiveEffects
|
||||
property int velocity: 850
|
||||
property Component colorAnimation: Component { ColorAnimation {
|
||||
duration: 70
|
||||
easing.type: root.animation.elementMoveFast.type
|
||||
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
|
||||
}}
|
||||
property Component numberAnimation: Component { NumberAnimation {
|
||||
duration: root.animation.elementMoveFast.duration
|
||||
easing.type: root.animation.elementMoveFast.type
|
||||
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sizes: QtObject {
|
||||
property real hyprlandGapsOut: 5
|
||||
property real barHeight: 46
|
||||
property real barBorder: 1
|
||||
}
|
||||
|
||||
syntaxHighlightingTheme: Appearance.m3colors.darkmode ? "Monokai" : "ayu Light"
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property string configFilePath: Directories.shellConfigPath
|
||||
property alias options: configOptionsJsonAdapter
|
||||
|
||||
function setNestedValue(nestedKey, value) {
|
||||
let keys = nestedKey.split(".");
|
||||
let obj = root.options;
|
||||
let parents = [obj];
|
||||
|
||||
// Traverse and collect parent objects
|
||||
for (let i = 0; i < keys.length - 1; ++i) {
|
||||
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
|
||||
obj[keys[i]] = {};
|
||||
}
|
||||
obj = obj[keys[i]];
|
||||
parents.push(obj);
|
||||
}
|
||||
|
||||
// Convert value to correct type using JSON.parse when safe
|
||||
let convertedValue = value;
|
||||
if (typeof value === "string") {
|
||||
let trimmed = value.trim();
|
||||
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
|
||||
try {
|
||||
convertedValue = JSON.parse(trimmed);
|
||||
} catch (e) {
|
||||
convertedValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj[keys[keys.length - 1]] = convertedValue;
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: root.configFilePath
|
||||
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: writeAdapter()
|
||||
onLoadFailed: error => {
|
||||
if (error == FileViewError.FileNotFound) {
|
||||
writeAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: configOptionsJsonAdapter
|
||||
|
||||
property JsonObject background: JsonObject {
|
||||
property bool fixedClockPosition: false
|
||||
property real clockX: -500
|
||||
property real clockY: -500
|
||||
property string wallpaperPath: ""
|
||||
property JsonObject parallax: JsonObject {
|
||||
property bool enableWorkspace: true
|
||||
property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size
|
||||
property bool enableSidebar: true
|
||||
}
|
||||
}
|
||||
|
||||
property JsonObject time: JsonObject {
|
||||
// https://doc.qt.io/qt-6/qtime.html#toString
|
||||
property string format: "hh:mm"
|
||||
property string dateFormat: "ddd, dd/MM"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import qs.singletons
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
/**
|
||||
* A nice wrapper for date and time strings.
|
||||
*/
|
||||
Singleton {
|
||||
property string time: Qt.locale().toString(clock.date, Config.options?.time.format ?? "hh:mm")
|
||||
property string date: Qt.locale().toString(clock.date, Config.options?.time.dateFormat ?? "dddd, dd/MM")
|
||||
property string collapsedCalendarFormat: Qt.locale().toString(clock.date, "dd MMMM yyyy")
|
||||
property string uptime: "0h, 0m"
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
fileUptime.reload()
|
||||
const textUptime = fileUptime.text()
|
||||
const uptimeSeconds = Number(textUptime.split(" ")[0] ?? 0)
|
||||
|
||||
// Convert seconds to days, hours, and minutes
|
||||
const days = Math.floor(uptimeSeconds / 86400)
|
||||
const hours = Math.floor((uptimeSeconds % 86400) / 3600)
|
||||
const minutes = Math.floor((uptimeSeconds % 3600) / 60)
|
||||
|
||||
// Build the formatted uptime string
|
||||
let formatted = ""
|
||||
if (days > 0) formatted += `${days}d`
|
||||
if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h`
|
||||
if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m`
|
||||
uptime = formatted
|
||||
interval = Config.options?.resources?.updateInterval ?? 3000
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: fileUptime
|
||||
|
||||
path: "/proc/uptime"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.modules.common.functions
|
||||
import Qt.labs.platform
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
// XDG Dirs, with "file://"
|
||||
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
|
||||
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
|
||||
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
|
||||
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
||||
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
|
||||
|
||||
// Other dirs used by the shell, without "file://"
|
||||
property string assetsPath: Quickshell.shellPath("assets")
|
||||
property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`)
|
||||
property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`)
|
||||
property string scriptPath: Quickshell.shellPath("scripts")
|
||||
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/quickshell`)
|
||||
property string shellConfigName: "oo.json"
|
||||
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
|
||||
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
|
||||
// Cleanup on init
|
||||
Component.onCompleted: {
|
||||
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
/**
|
||||
* Automatically reloads generated material colors.
|
||||
* It is necessary to run reapplyTheme() on startup because Singletons are lazily loaded.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
property string filePath: Directories.generatedMaterialThemePath
|
||||
|
||||
function reapplyTheme() {
|
||||
themeFileView.reload()
|
||||
}
|
||||
|
||||
function applyColors(fileContent) {
|
||||
const json = JSON.parse(fileContent)
|
||||
for (const key in json) {
|
||||
if (json.hasOwnProperty(key)) {
|
||||
// Convert snake_case to CamelCase
|
||||
const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase())
|
||||
const m3Key = `m3${camelCaseKey}`
|
||||
Appearance.m3colors[m3Key] = json[key]
|
||||
}
|
||||
}
|
||||
|
||||
Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayedFileRead
|
||||
interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
root.applyColors(themeFileView.text())
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: themeFileView
|
||||
path: Qt.resolvedUrl(root.filePath)
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
this.reload()
|
||||
delayedFileRead.start()
|
||||
}
|
||||
onLoadedChanged: {
|
||||
const fileContent = themeFileView.text()
|
||||
root.applyColors(fileContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
{
|
||||
"Mo": "Mo/*keep*/",
|
||||
"Tu": "Tu/*keep*/",
|
||||
"We": "We/*keep*/",
|
||||
"Th": "Th/*keep*/",
|
||||
"Fr": "Fr/*keep*/",
|
||||
"Sa": "Sa/*keep*/",
|
||||
"Su": "Su/*keep*/",
|
||||
"%1 characters": "%1 characters",
|
||||
"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key",
|
||||
"**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key",
|
||||
". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!",
|
||||
"<i>No further instruction provided</i>": "<i>No further instruction provided</i>",
|
||||
"Action": "Action",
|
||||
"Add": "Add",
|
||||
"Add task": "Add task",
|
||||
"All-rounder | Good quality, decent quantity": "All-rounder | Good quality, decent quantity",
|
||||
"Allow NSFW": "Allow NSFW",
|
||||
"Allow NSFW content": "Allow NSFW content",
|
||||
"Anime": "Anime",
|
||||
"Anime boorus": "Anime boorus",
|
||||
"App": "App",
|
||||
"Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel",
|
||||
"Bluetooth": "Bluetooth",
|
||||
"Brightness": "Brightness",
|
||||
"Cancel": "Cancel",
|
||||
"Chain of Thought": "Chain of Thought",
|
||||
"Cheat sheet": "Cheat sheet",
|
||||
"Choose model": "Choose model",
|
||||
"Clean stuff | Excellent quality, no NSFW": "Clean stuff | Excellent quality, no NSFW",
|
||||
"Clear": "Clear",
|
||||
"Clear chat history": "Clear chat history",
|
||||
"Clear the current list of images": "Clear the current list of images",
|
||||
"Close": "Close",
|
||||
"Copy": "Copy",
|
||||
"Copy code": "Copy code",
|
||||
"Delete": "Delete",
|
||||
"Desktop": "Desktop",
|
||||
"Disable NSFW content": "Disable NSFW content",
|
||||
"Done": "Done",
|
||||
"Download": "Download",
|
||||
"Edit": "Edit",
|
||||
"Enter text to translate...": "Enter text to translate...",
|
||||
"Finished tasks will go here": "Finished tasks will go here",
|
||||
"For desktop wallpapers | Good quality": "For desktop wallpapers | Good quality",
|
||||
"For storing API keys and other sensitive information": "For storing API keys and other sensitive information",
|
||||
"Game mode": "Game mode",
|
||||
"Get the next page of results": "Get the next page of results",
|
||||
"Hibernate": "Hibernate",
|
||||
"Input": "Input",
|
||||
"Intelligence": "Intelligence",
|
||||
"Interface": "Interface",
|
||||
"Invalid arguments. Must provide `key` and `value`.": "Invalid arguments. Must provide `key` and `value`.",
|
||||
"Jump to current month": "Jump to current month",
|
||||
"Keep system awake": "Keep system awake",
|
||||
"Large images | God tier quality, no NSFW.": "Large images | God tier quality, no NSFW.",
|
||||
"Large language models": "Large language models",
|
||||
"Launch": "Launch",
|
||||
"Lock": "Lock",
|
||||
"Logout": "Logout",
|
||||
"Markdown test": "Markdown test",
|
||||
"Math result": "Math result",
|
||||
"Night Light": "Night Light",
|
||||
"No audio source": "No audio source",
|
||||
"No media": "No media",
|
||||
"No notifications": "No notifications",
|
||||
"Not visible to model": "Not visible to model",
|
||||
"Nothing here!": "Nothing here!",
|
||||
"Notifications": "Notifications",
|
||||
"OK": "OK",
|
||||
"Open file link": "Open file link",
|
||||
"Output": "Output",
|
||||
"Reboot": "Reboot",
|
||||
"Reboot to firmware settings": "Reboot to firmware settings",
|
||||
"Reload Hyprland & Quickshell": "Reload Hyprland & Quickshell",
|
||||
"Run": "Run",
|
||||
"Run command": "Run command",
|
||||
"Save": "Save",
|
||||
"Save to Downloads": "Save to Downloads",
|
||||
"Search": "Search",
|
||||
"Search the web": "Search the web",
|
||||
"Search, calculate or run": "Search, calculate or run",
|
||||
"Select Language": "Select Language",
|
||||
"Session": "Session",
|
||||
"Set API key": "Set API key",
|
||||
"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.",
|
||||
"Set the current API provider": "Set the current API provider",
|
||||
"Shutdown": "Shutdown",
|
||||
"Silent": "Silent",
|
||||
"Sleep": "Sleep",
|
||||
"System": "System",
|
||||
"Task Manager": "Task Manager",
|
||||
"Task description": "Task description",
|
||||
"Temperature must be between 0 and 2": "Temperature must be between 0 and 2",
|
||||
"The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "The hentai one | Great quantity, a lot of NSFW, quality varies wildly",
|
||||
"The popular one | Best quantity, but quality can vary wildly": "The popular one | Best quantity, but quality can vary wildly",
|
||||
"Thinking": "Thinking",
|
||||
"Translation goes here...": "Translation goes here...",
|
||||
"Translator": "Translator",
|
||||
"Unfinished": "Unfinished",
|
||||
"Unknown": "Unknown",
|
||||
"Unknown Album": "Unknown Album",
|
||||
"Unknown Artist": "Unknown Artist",
|
||||
"Unknown Title": "Unknown Title",
|
||||
"View Markdown source": "View Markdown source",
|
||||
"Volume": "Volume",
|
||||
"Volume mixer": "Volume mixer",
|
||||
"Waifus only | Excellent quality, limited quantity": "Waifus only | Excellent quality, limited quantity",
|
||||
"Waiting for response...": "Waiting for response...",
|
||||
"Workspace": "Workspace",
|
||||
"Set with /mode PROVIDER": "Set with /mode PROVIDER",
|
||||
"Invalid API provider. Supported: \n-": "Invalid API provider. Supported: \n-",
|
||||
"Unknown command:": "Unknown command:",
|
||||
"Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window",
|
||||
"The current API used. Endpoint:": "The current API used. Endpoint:",
|
||||
"Provider set to": "Provider set to",
|
||||
"Invalid model. Supported: \n```": "Invalid model. Supported: \n```",
|
||||
"That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number",
|
||||
"Online | Google's model\nGives up-to-date information with search.": "Online | Google's model\nGives up-to-date information with search.",
|
||||
"Switched to search mode. Continue with the user's request.": "Switched to search mode. Continue with the user's request.",
|
||||
"Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly",
|
||||
"Settings": "Settings",
|
||||
"Save chat": "Save chat",
|
||||
"Load chat": "Load chat",
|
||||
"or": "or",
|
||||
"Set the system prompt for the model.": "Set the system prompt for the model.",
|
||||
"To Do": "To Do",
|
||||
"Calendar": "Calendar",
|
||||
"Advanced": "Advanced",
|
||||
"About": "About",
|
||||
"Services": "Services",
|
||||
"Style": "Style",
|
||||
"Edit config": "Edit config",
|
||||
"Colors & Wallpaper": "Colors & Wallpaper",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark",
|
||||
"Material palette": "Material palette",
|
||||
"Fidelity": "Fidelity",
|
||||
"Fruit Salad": "Fruit Salad",
|
||||
"Alternatively use /dark, /light, /img in the launcher": "Alternatively use /dark, /light, /img in the launcher",
|
||||
"Fake screen rounding": "Fake screen rounding",
|
||||
"When not fullscreen": "When not fullscreen",
|
||||
"Choose file": "Choose file",
|
||||
"Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers",
|
||||
"Be patient...": "Be patient...",
|
||||
"Decorations & Effects": "Decorations & Effects",
|
||||
"Tonal Spot": "Tonal Spot",
|
||||
"Shell windows": "Shell windows",
|
||||
"Auto": "Auto",
|
||||
"Wallpaper": "Wallpaper",
|
||||
"Content": "Content",
|
||||
"Title bar": "Title bar",
|
||||
"Transparency": "Transparency",
|
||||
"Expressive": "Expressive",
|
||||
"Yes": "Yes",
|
||||
"Enable": "Enable",
|
||||
"Rainbow": "Rainbow",
|
||||
"Might look ass. Unsupported.": "Might look ass. Unsupported.",
|
||||
"Monochrome": "Monochrome",
|
||||
"Random: Konachan": "Random: Konachan",
|
||||
"Center title": "Center title",
|
||||
"Neutral": "Neutral",
|
||||
"Pick wallpaper image on your system": "Pick wallpaper image on your system",
|
||||
"No": "No",
|
||||
"AI": "AI",
|
||||
"Local only": "Local only",
|
||||
"Policies": "Policies",
|
||||
"Weeb": "Weeb",
|
||||
"Closet": "Closet",
|
||||
"Bar style": "Bar style",
|
||||
"Show next time": "Show next time",
|
||||
"Usage": "Usage",
|
||||
"Plain rectangle": "Plain rectangle",
|
||||
"Useless buttons": "Useless buttons",
|
||||
"GitHub": "GitHub",
|
||||
"Style & wallpaper": "Style & wallpaper",
|
||||
"Configuration": "Configuration",
|
||||
"Change any time later with /dark, /light, /img in the launcher": "Change any time later with /dark, /light, /img in the launcher",
|
||||
"Keybinds": "Keybinds",
|
||||
"Float": "Float",
|
||||
"Hug": "Hug",
|
||||
"Yooooo hi there": "Yooooo hi there",
|
||||
"illogical-impulse Welcome": "illogical-impulse Welcome",
|
||||
"Info": "Info",
|
||||
"Volume limit": "Volume limit",
|
||||
"Prevents abrupt increments and restricts volume limit": "Prevents abrupt increments and restricts volume limit",
|
||||
"Resources": "Resources",
|
||||
"12h am/pm": "12h am/pm",
|
||||
"Base URL": "Base URL",
|
||||
"Audio": "Audio",
|
||||
"Networking": "Networking",
|
||||
"Format": "Format",
|
||||
"Time": "Time",
|
||||
"Battery": "Battery",
|
||||
"Prefixes": "Prefixes",
|
||||
"Emojis": "Emojis",
|
||||
"Earbang protection": "Earbang protection",
|
||||
"Automatically suspends the system when battery is low": "Automatically suspends the system when battery is low",
|
||||
"Automatic suspend": "Automatic suspend",
|
||||
"Suspend at": "Suspend at",
|
||||
"Max allowed increase": "Max allowed increase",
|
||||
"Web search": "Web search",
|
||||
"Polling interval (ms)": "Polling interval (ms)",
|
||||
"Clipboard": "Clipboard",
|
||||
"Low warning": "Low warning",
|
||||
"24h": "24h",
|
||||
"Use Levenshtein distance-based algorithm instead of fuzzy": "Use Levenshtein distance-based algorithm instead of fuzzy",
|
||||
"System prompt": "System prompt",
|
||||
"12h AM/PM": "12h AM/PM",
|
||||
"Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)",
|
||||
"Critical warning": "Critical warning",
|
||||
"User agent (for services that require it)": "User agent (for services that require it)",
|
||||
"Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.",
|
||||
"Note: turning off can hurt readability": "Note: turning off can hurt readability",
|
||||
"Workspaces shown": "Workspaces shown",
|
||||
"Dark/Light toggle": "Dark/Light toggle",
|
||||
"Dock": "Dock",
|
||||
"Weather": "Weather",
|
||||
"Pinned on startup": "Pinned on startup",
|
||||
"Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience",
|
||||
"Appearance": "Appearance",
|
||||
"Always show numbers": "Always show numbers",
|
||||
"Buttons": "Buttons",
|
||||
"Keyboard toggle": "Keyboard toggle",
|
||||
"Scale (%)": "Scale (%)",
|
||||
"Overview": "Overview",
|
||||
"Rows": "Rows",
|
||||
"Borderless": "Borderless",
|
||||
"Screenshot tool": "Screenshot tool",
|
||||
"Number show delay when pressing Super (ms)": "Number show delay when pressing Super (ms)",
|
||||
"Timeout (ms)": "Timeout (ms)",
|
||||
"Show app icons": "Show app icons",
|
||||
"Workspaces": "Workspaces",
|
||||
"Columns": "Columns",
|
||||
"On-screen display": "On-screen display",
|
||||
"Screen snip": "Screen snip",
|
||||
"Mic toggle": "Mic toggle",
|
||||
"Hover to reveal": "Hover to reveal",
|
||||
"Bar": "Bar",
|
||||
"Show background": "Show background",
|
||||
"Show regions of potential interest": "Show regions of potential interest",
|
||||
"Color picker": "Color picker",
|
||||
"Help & Support": "Help & Support",
|
||||
"Discussions": "Discussions",
|
||||
"Color generation": "Color generation",
|
||||
"Dotfiles": "Dotfiles",
|
||||
"Distro": "Distro",
|
||||
"Privacy Policy": "Privacy Policy",
|
||||
"Documentation": "Documentation",
|
||||
"Shell & utilities theming must also be enabled": "Shell & utilities theming must also be enabled",
|
||||
"illogical-impulse": "illogical-impulse",
|
||||
"Donate": "Donate",
|
||||
"Terminal": "Terminal",
|
||||
"Shell & utilities": "Shell & utilities",
|
||||
"Qt apps": "Qt apps",
|
||||
"Report a Bug": "Report a Bug",
|
||||
"Issues": "Issues",
|
||||
"Drag or click a region • LMB: Copy • RMB: Edit": "Drag or click a region • LMB: Copy • RMB: Edit",
|
||||
"Current model: %1\nSet it with %2model MODEL": "Current model: %1\nSet it with %2model MODEL",
|
||||
"Message the model... \"%1\" for commands": "Message the model... \"%1\" for commands",
|
||||
"No API key set for %1": "No API key set for %1",
|
||||
"Loaded the following system prompt\n\n---\n\n%1": "Loaded the following system prompt\n\n---\n\n%1",
|
||||
"%1 | Right-click to configure": "%1 | Right-click to configure",
|
||||
"API key set for %1": "API key set for %1",
|
||||
"Online via %1 | %2's model": "Online via %1 | %2's model",
|
||||
"Current API endpoint: %1\nSet it with %2mode PROVIDER": "Current API endpoint: %1\nSet it with %2mode PROVIDER",
|
||||
"Go to source (%1)": "Go to source (%1)",
|
||||
"Temperature set to %1": "Temperature set to %1",
|
||||
"To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command<br/>\n\n### For %1:\n\n**Link**: %2\n\n%3": "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command<br/>\n\n### For %1:\n\n**Link**: %2\n\n%3",
|
||||
"Enter tags, or \"%1\" for commands": "Enter tags, or \"%1\" for commands",
|
||||
"%1 queries pending": "%1 queries pending",
|
||||
"API key:\n\n```txt\n%1\n```": "API key:\n\n```txt\n%1\n```",
|
||||
"Uptime: %1": "Uptime: %1",
|
||||
"%1 Safe Storage": "%1 Safe Storage",
|
||||
"%1 does not require an API key": "%1 does not require an API key",
|
||||
"Temperature: %1": "Temperature: %1",
|
||||
"Model set to %1": "Model set to %1",
|
||||
"Page %1": "Page %1",
|
||||
"Local Ollama model | %1": "Local Ollama model | %1",
|
||||
"The current system prompt is\n\n---\n\n%1": "The current system prompt is\n\n---\n\n%1",
|
||||
"Unknown function call: %1": "Unknown function call: %1",
|
||||
"%1 notifications": "%1 notifications",
|
||||
"Load chat from %1": "Load chat from %1",
|
||||
"Load prompt from %1": "Load prompt from %1",
|
||||
"Save chat to %1": "Save chat to %1",
|
||||
"Weather Service": "Weather Service",
|
||||
"Cannot find a GPS service. Using the fallback method instead.": "Cannot find a GPS service. Using the fallback method instead.",
|
||||
"Critically low battery": "Critically low battery",
|
||||
"Select output device": "Select output device",
|
||||
"Code saved to file": "Code saved to file",
|
||||
"Online models disallowed\n\nControlled by `policies.ai` config option": "Online models disallowed\n\nControlled by `policies.ai` config option",
|
||||
"Scroll to change volume": "Scroll to change volume",
|
||||
"Elements": "Elements",
|
||||
"%1 • %2 tasks": "%1 • %2 tasks",
|
||||
"Download complete": "Download complete",
|
||||
"Please charge!\nAutomatic suspend triggers at %1": "Please charge!\nAutomatic suspend triggers at %1",
|
||||
"Cloudflare WARP": "Cloudflare WARP",
|
||||
"Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)",
|
||||
"Scroll to change brightness": "Scroll to change brightness",
|
||||
"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command": "Connection failed. Please inspect manually with the <tt>warp-cli</tt> command",
|
||||
"Select input device": "Select input device",
|
||||
"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command": "Registration failed. Please inspect manually with the <tt>warp-cli</tt> command",
|
||||
"Consider plugging in your device": "Consider plugging in your device",
|
||||
"Low battery": "Low battery",
|
||||
"Saved to %1": "Saved to %1",
|
||||
"Sunset": "Sunset",
|
||||
"UV Index": "UV Index",
|
||||
"Humidity": "Humidity",
|
||||
"Wind": "Wind",
|
||||
"Sunrise": "Sunrise",
|
||||
"Pressure": "Pressure",
|
||||
"Visibility": "Visibility",
|
||||
"Precipitation": "Precipitation"
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -0,0 +1,53 @@
|
||||
# Contributing
|
||||
|
||||
- Please, please, please, make multiple PRs if you have many features/fixes, and don't shove your personal changes along with the PR, including changed defaults
|
||||
- We can accept features that we do not personally want, but in that case we will ask you to make it configurable/optionally loaded.
|
||||
- If you want to start working on something big to contribute, it might be a good idea to ask first to not waste your effort (but if you've already done it for yourself, it doesn't hurt to submit).
|
||||
|
||||
# Code details
|
||||
|
||||
## Contributing to i18n
|
||||
|
||||
For contributing in translation (i18n) for Quickshell, see also `dots/.config/quickshell/ii/translations/tools`.
|
||||
|
||||
## Dynamic loading
|
||||
|
||||
- If something's not always necessary, especially when guarded by a config option to enable/disable, put it in a `Loader`. One tip with `Loader`s is sometimes you will need to declare positioning properties (like `anchors`) in the `Loader`, not the `sourceComponent`.
|
||||
|
||||
## Practical concerns
|
||||
|
||||
- Make sure what you add does not require significant resources for a minor purpose or harm usability just for the sake of looking nice. The dotfiles must remain practical for daily driving.
|
||||
- If there is something really fancy and impractical anyway, add a config option for it and make sure it's disabled by default.
|
||||
|
||||
# Setting up
|
||||
|
||||
The following instruction assumes that you have an Arch(-based) Linux system.
|
||||
|
||||
## Complete
|
||||
|
||||
_Might not be necessary depending on what you change, but this is recommended._
|
||||
|
||||
- [Install](https://ii.clsty.link/en/ii-qs/01setup/) the dotfiles (if you don't wanna replace your stuff completely, do it on a new user).
|
||||
- Make changes, copy changes to a fork, create PR.
|
||||
|
||||
## Partially working shell
|
||||
|
||||
_Most stuff in the shell will work but not everything._
|
||||
|
||||
- Install Hyprland and the development version of Quickshell (`yay -S hyprland quickshell-git`).
|
||||
- Copy `dots/.config/quickshell` folder to your home directory.
|
||||
|
||||
## Extra setup for Quickshell
|
||||
- Quickshell-specific LSP setup: Run `touch ~/.config/quickshell/ii/.qmlls.ini` for proper LSP support.
|
||||
- Hint for VSCode: Get the official "Qt Qml" extension, go to its settings and change custom exe path to `/usr/bin/qmlls6`.
|
||||
|
||||
## Python
|
||||
If your changes involves using python package or script, please use the virtual environment created by uv as described in `sdata/uv/README.md`.
|
||||
|
||||
# Running
|
||||
|
||||
- Launch Hyprland (not the "uwsm-managed" one)
|
||||
- For the shell:
|
||||
- Open `~/.config/quickshell/ii` in your code editor.
|
||||
- In a terminal run `pkill qs; qs -c ii` to start the shell in the terminal (for logs).
|
||||
- Make edits in the opened folder. Changes are reloaded live.
|
||||
@@ -4,21 +4,21 @@ labels: ["ISSUE"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "**Welcome to submit a new issue!**\n- It takes only 3 steps, so please be patient :)\n- Tip: If your issue is not a feature request, and it does not fit into the following form, for example \"how can I edit some widget\", please use [Discussions](https://github.com/end-4/dots-hyprland/discussions) instead."
|
||||
value: "**Welcome to submit a new issue!**\n- Please search in [existing issues](https://github.com/end-4/dots-hyprland/issues?q=is%3Aissue) before continue.\n- It takes only 3 steps, so please be patient :)\n- NOTE 1: If your issue is not a feature request, and it does not fit into the following form, for example \"how can I edit some widget\", please use [Discussions](https://github.com/end-4/dots-hyprland/discussions) instead.\n- NOTE 2: If your problem is distro specific and you do not use Arch(-based) distros, plesae submit [Discussion at Extra Distros](https://github.com/end-4/dots-hyprland/discussions/new?category=extra-distros) instead."
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: "Step 1. Before you submit"
|
||||
description: "Hint: The 2nd and 3rd checkbox is **not** forcely required as you may have failed to do so."
|
||||
options:
|
||||
- label: I have read the [Troubleshooting](https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/04troubleshooting/) and [Usage](https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/) pages.
|
||||
- label: I have read the [Troubleshooting](https://ii.clsty.link/en/ii-qs/04troubleshooting/) and [Usage](https://ii.clsty.link/en/ii-qs/02usage/) pages.
|
||||
required: true
|
||||
- label: I've successfully updated to the latest version following the [guidance](https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/01setup/#updating).
|
||||
- label: I've successfully updated to the latest version following the [guidance](https://ii.clsty.link/en/ii-qs/01setup/#updating).
|
||||
required: false # Not required cuz user may have failed to do so
|
||||
- label: I've successfully updated the system packages to the latest.
|
||||
required: false # Not required cuz user may have failed to do so
|
||||
- label: I've ticked the checkboxes without reading their contents
|
||||
required: false # Obviously
|
||||
|
||||
# TODO: Use GitHub Action to auto add folding tag if the log contains more than 15 lines, instead of tell user to "paste here" cuz many users actually does not know its meaning (It's also not convenient anyway).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Step 2. Quick diagnose info"
|
||||
@@ -33,7 +33,7 @@ body:
|
||||
**Tips for the following Step 3**
|
||||
1. Use `LANG=C LC_ALL=C` to get the output of a command in English, eg. `LANG=C LC_ALL=C date` displays time in English.
|
||||
2. If it throws errors, **PLEASE**, attach logs and describe in detail if possible.
|
||||
- Bar and widgets not showing? run `pkill agsv1; agsv1` for logs.
|
||||
- Bar and widgets not showing? run `pkill qs; qs -c ii` for logs.
|
||||
- Installation failed? Run installation again for logs.
|
||||
- You may use more code blocks when needed.
|
||||
3. In case you are confused, the `<details>`, `<summary>`, `</summary>`, `</details>` are HTML tags for folding the logs (typically very long) inside. Please do not touch them (unless you know what you are doing).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user