Compare commits
21
Commits
master
...
49dbc262df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49dbc262df
|
||
|
|
50729452e6
|
||
|
|
80b48c08c4
|
||
|
|
e7c6c74964
|
||
|
|
39e1ed44aa
|
||
|
|
cac976dd0c
|
||
|
|
ef17f0956f
|
||
|
|
9600cdfc29
|
||
|
|
2608e1b547
|
||
|
|
d37cfd92dd
|
||
|
|
6d61c18792
|
||
|
|
cb90233704
|
||
|
|
517f779e52
|
||
|
|
9a993644a3 | ||
|
|
0e8a2de41b
|
||
|
|
08680760dd
|
||
|
|
df76d4d1be
|
||
|
|
7c403114ec
|
||
|
|
a2afca2d74
|
||
|
|
ca361ec226
|
||
|
|
f26e36bbb2
|
No files matched your search
Generated
+90
-81
@@ -9,11 +9,11 @@
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1789138122,
|
||||
"narHash": "sha256-mh+ZiGCtHAo0xIi1yaNjHsftfnlWz9JiYwHCyT16yIE=",
|
||||
"lastModified": 1792853286,
|
||||
"narHash": "sha256-l/Oh5k1NQqQuwh4gBF3VcmCF0WGiCT/LqfvY5huhOq4=",
|
||||
"owner": "ezKEa",
|
||||
"repo": "aagl-gtk-on-nix",
|
||||
"rev": "d82d6d6f66caf4b63fb154e62fad83c627874127",
|
||||
"rev": "e9a3b8fdb50a7d77a90c02c84e298c286310385b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -64,11 +64,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786464219,
|
||||
"narHash": "sha256-WKqWL8r7CyTDYueTr2ffJ9ya50dellv6IR1JX5PghDY=",
|
||||
"lastModified": 1791041429,
|
||||
"narHash": "sha256-Y/Q4JtMo6nHB6XS1LPSfK7Y4AnuNl9jci/5sCps1MRc=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"rev": "f3d1804205e8158c15595cdda1b566f93349ffae",
|
||||
"rev": "52ea11f06dd492f34b3cc0b39a9c805beecafcff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -173,11 +173,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1789770686,
|
||||
"narHash": "sha256-uZkBR7yHdIKUFB5SZdfgh1qkGfI3XmYmI/lTiquxbck=",
|
||||
"lastModified": 1792526520,
|
||||
"narHash": "sha256-3hSJlv8mUVnZPMWK2+CM7a9O+nR6Z4mCh27NfLCmTPc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "725ea35e410ad83be4931d1bff7e090eacaf3563",
|
||||
"rev": "cedd626e11b635470fa6f9fdb8ad79b0075dd7f9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -332,11 +332,11 @@
|
||||
"rust-overlay": "rust-overlay_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790052168,
|
||||
"narHash": "sha256-cfZ9EL0rRfoB7KnZRgRmk36YJhjfutqDjzew1lJYwEg=",
|
||||
"lastModified": 1792684031,
|
||||
"narHash": "sha256-wBjRHntxoxT+fo85i0rct/NWN/OG/Z6nVky/Efb2gjE=",
|
||||
"owner": "mattwparas",
|
||||
"repo": "helix",
|
||||
"rev": "7915ec19939d8ae3ab4cfff278eaf23ae3784351",
|
||||
"rev": "424add100eac9ca258d969b985a8ac370469ec05",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -353,11 +353,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790292277,
|
||||
"narHash": "sha256-z+knvdMItRrMonmG4RIsqKYIa/hwYUzKjotDTfC5DDc=",
|
||||
"lastModified": 1793193205,
|
||||
"narHash": "sha256-X6xa6sGA7RnJQuZGbFMC+K+KToWAlBDCCjJXfTwEPsw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "4b9add8645d5e2b0f7de18f7a442d08fe2bdcc91",
|
||||
"rev": "095fef2f265c3c0da4fe315efba8f8a04495ab34",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -411,11 +411,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786464367,
|
||||
"narHash": "sha256-k58p4wbzIXWyRWrW84pP8tD+iaZSSYiiM+fr0Auk4oU=",
|
||||
"lastModified": 1791066858,
|
||||
"narHash": "sha256-hLh6ALHrGpgYm8viWCo1pO+ErC22YDGdAvVPcfYsXA8=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "7c895c44e3ca6d28ed68ddd80ec02b02b925e7fc",
|
||||
"rev": "80c7d00f2236732853e390019ff8d79d17fa3612",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -432,6 +432,7 @@
|
||||
"hyprland-guiutils": "hyprland-guiutils",
|
||||
"hyprland-protocols": "hyprland-protocols",
|
||||
"hyprlang": "hyprlang",
|
||||
"hyprtoolkit": "hyprtoolkit",
|
||||
"hyprutils": "hyprutils",
|
||||
"hyprwayland-scanner": "hyprwayland-scanner",
|
||||
"hyprwire": "hyprwire",
|
||||
@@ -441,16 +442,16 @@
|
||||
"xdph": "xdph"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786555543,
|
||||
"narHash": "sha256-sZWUA497m3OuyZqt1KUa5cPR43Zk1X/L3qZa64mIxDs=",
|
||||
"lastModified": 1793183825,
|
||||
"narHash": "sha256-MMn1clKPz3FyrFEq39OqXZ2SO3QBaOOFtAFVIcO+VvU=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "Hyprland",
|
||||
"rev": "1256bd180e7677aadd715e59351c09fb16e48efc",
|
||||
"rev": "0aa6745f705f2c4b0a5004eb019ef76e5bbdb426",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"ref": "1256bd1",
|
||||
"ref": "v0.57.0",
|
||||
"repo": "Hyprland",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -469,7 +470,10 @@
|
||||
"hyprland",
|
||||
"hyprlang"
|
||||
],
|
||||
"hyprtoolkit": "hyprtoolkit",
|
||||
"hyprtoolkit": [
|
||||
"hyprland",
|
||||
"hyprtoolkit"
|
||||
],
|
||||
"hyprutils": [
|
||||
"hyprland",
|
||||
"hyprutils"
|
||||
@@ -488,11 +492,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786464504,
|
||||
"narHash": "sha256-7sHwM86KILQyHDHDuE2SDBlQ2jvZ0EW3hY7sW009/cg=",
|
||||
"lastModified": 1791805511,
|
||||
"narHash": "sha256-p7R3xnbJf6MqvgWAmR86PkHUba97Fotm8RR16tYDF9I=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-guiutils",
|
||||
"rev": "4c30cf3097ea963c0e250749ee0c59f8b08816d6",
|
||||
"rev": "204f39d6c7a4aca7042587d4638e19cdeccf63bc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -518,11 +522,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790062321,
|
||||
"narHash": "sha256-MKb+sbGuRyd+As1fNoxPNJRC6B+hLahvSPGuwPskOXw=",
|
||||
"lastModified": 1792508453,
|
||||
"narHash": "sha256-y+V5RFJcjnV1+ZzImkfiSJ97iDIflanjP2OR9lemvTs=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-plugins",
|
||||
"rev": "b29b8293039490d0361e9f15289d361653d8aabf",
|
||||
"rev": "d2427d4d15b36df40468f5a58bbd67cdba19835d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -543,11 +547,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1772460177,
|
||||
"narHash": "sha256-/6G/MsPvtn7bc4Y32pserBT/Z4SUUdBd4XYJpOEKVR4=",
|
||||
"lastModified": 1792149240,
|
||||
"narHash": "sha256-g09dB6NCTzD4F6YgYrGXRhUfG52xwxUtlMAbHvv/1gE=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-protocols",
|
||||
"rev": "1cb6db5fd6bb8aee419f4457402fa18293ace917",
|
||||
"rev": "044a434ec371f985655389bcef09286b1af3d77c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -589,46 +593,39 @@
|
||||
"inputs": {
|
||||
"aquamarine": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"aquamarine"
|
||||
],
|
||||
"hyprgraphics": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"hyprgraphics"
|
||||
],
|
||||
"hyprlang": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"hyprlang"
|
||||
],
|
||||
"hyprutils": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"hyprutils"
|
||||
],
|
||||
"hyprwayland-scanner": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"hyprwayland-scanner"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"hyprland",
|
||||
"hyprland-guiutils",
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1785930473,
|
||||
"narHash": "sha256-DitTu625BhEYpZjtjxtGpjrEJwPwW+X/+jJvhSZNSJM=",
|
||||
"lastModified": 1791849226,
|
||||
"narHash": "sha256-q9Kgz/ZSo6ftVliPGQuRZYWYp3eK4vggO0IR6LcsWl0=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprtoolkit",
|
||||
"rev": "af515b69dfbe366dc7873aa1475cb2f4db3ebad7",
|
||||
"rev": "2907e0d1f8a3a540737a0e738acc40d48ad7ba0d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -649,11 +646,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786464080,
|
||||
"narHash": "sha256-W1hxvumEM57yV+QwsZ4QdAHEqOkr7e4S8VAkLX60qDE=",
|
||||
"lastModified": 1792673440,
|
||||
"narHash": "sha256-7l2+xIZ7IU9r4h9ngSrGH7oQs8XZj0NR0V+96wkN8zk=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "c157fe1e3092b980cc69315a6631f89aff09dcce",
|
||||
"rev": "0d13d5a624eb093b600d1b2cbe448fb613c86424",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -703,11 +700,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786464294,
|
||||
"narHash": "sha256-ZQsZ2WvBdkboCIyh8LStDPdAIARmxzn0XMNxxoOhjPE=",
|
||||
"lastModified": 1792081530,
|
||||
"narHash": "sha256-SRs96bR3VbViiQ1vOhZOqy/HxczIWCoHaGKhncwr1js=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwire",
|
||||
"rev": "4ce7cd6b6128c1ac41caf23c58a30a26b327f9dd",
|
||||
"rev": "a546e42d45e161682f729ebf52375bd4800d7041",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -718,11 +715,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1786247143,
|
||||
"narHash": "sha256-8S3Kcxs7D4UtxJxSJZz0m14CGhuW0MxfrIwJxeGWGnQ=",
|
||||
"lastModified": 1792210252,
|
||||
"narHash": "sha256-Mh0PUaIv4nFY+8wAGonaEh8iBzwsmdNlwjVHmFg1Biw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "279b4a8275f032c566576b3f181fa0f27197f588",
|
||||
"rev": "09063bd1f2b36fd9fd793c36dfade7bd40a93dfc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -750,11 +747,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1790218706,
|
||||
"narHash": "sha256-6e4Na3z008XpdVyOXgfasXIn1aN+z8AXFG+jXdQSyuI=",
|
||||
"lastModified": 1793183032,
|
||||
"narHash": "sha256-56mFMf8heLNfYhcgbrh4GpN5x+G9JOfTFXZC3n6w0qM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c508844df6c28fa6dabc1b6af70f3ccbd65c5201",
|
||||
"rev": "08d3a97586f6b6343b3b5e799980773629432610",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -766,11 +763,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1790185690,
|
||||
"narHash": "sha256-xJ+X4hBtOcAFGBOe5nAMyMUeF9foJBmIOu3NjBqBycU=",
|
||||
"lastModified": 1793110635,
|
||||
"narHash": "sha256-dhoW6VgFWlaodSY5jrBn+CF2FLmaUvDVgM7+C7T2+NM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4975466d324710c576dc11ad614684e6bd8cad8e",
|
||||
"rev": "31ecbaca1bdefa45dffc409f0250bd8039ea0010",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -814,11 +811,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784288435,
|
||||
"narHash": "sha256-ReRHaLgr/uVqdD8afFSn+myXIfpHeOhP0yYe0TJqAA8=",
|
||||
"lastModified": 1791909798,
|
||||
"narHash": "sha256-VAhpCOmrun/KXimYfR8NM88bciKCui7CqHHhThSzgag=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "43b3c1ab9d40fb1dbb008f451988a91e375825e9",
|
||||
"rev": "1ba1dbfe28738c9a71c266be6efa5b89e75491a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -896,11 +893,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1789543870,
|
||||
"narHash": "sha256-45ikFmtfgBZgMePIm4GevJ9VjDSWO3PGQgOuuwHDpes=",
|
||||
"lastModified": 1791013993,
|
||||
"narHash": "sha256-BiFPwFsbFnZH5GfWbQFKYuQNg+67aNQCoRSRNTeJFQc=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "35ca0490d13a3d38c4602d0eb9600a30fa63a367",
|
||||
"rev": "7470661ba2b51156048af08464b82020223dc942",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -916,11 +913,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790247521,
|
||||
"narHash": "sha256-TNfgoHsqsYYvaJImlWctfRkh4PseTagzegU1Dgdbehw=",
|
||||
"lastModified": 1793052255,
|
||||
"narHash": "sha256-Y/JIHTnMlOabDfyaSBubWGLDIVGihLEUuoMhFvQKaDo=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "2bd00bd9bb35fe6d114888c8f1c2e946c541dd8f",
|
||||
"rev": "60182817ee048ac94b4c68ea76d5ef2e8aac730d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -949,11 +946,11 @@
|
||||
"tinted-zed": "tinted-zed"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1789937675,
|
||||
"narHash": "sha256-/F9gQpAlPCxheQ9ht/5Fka2JEr/FfuOh9R7ySa9SAGc=",
|
||||
"lastModified": 1792920263,
|
||||
"narHash": "sha256-2pkE97wOXAYIn2/Nhs9y+iR0qVT+g/3UtukXdkywxXc=",
|
||||
"owner": "danth",
|
||||
"repo": "stylix",
|
||||
"rev": "abff36bfab11cbdcd0723f41c1b0921e211be6ac",
|
||||
"rev": "3e27b6c499e507df6e3bb8eca3614f5c95e28140",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1080,11 +1077,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790276835,
|
||||
"narHash": "sha256-Y/bf7OmtwrEhdRDTkh4OVAEZX5ixL+io0W/UPJuto+g=",
|
||||
"lastModified": 1793198875,
|
||||
"narHash": "sha256-CZR26099bnlOWgLpMOPwdpmocSAGetbepjkP661qml4=",
|
||||
"owner": "Alexays",
|
||||
"repo": "Waybar",
|
||||
"rev": "16843896794a9c595139318420f81f40e84f8c78",
|
||||
"rev": "f6f73c81818b0a6753c1efb68e0fae2149eac028",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1095,6 +1092,14 @@
|
||||
},
|
||||
"xdph": {
|
||||
"inputs": {
|
||||
"aquamarine": [
|
||||
"hyprland",
|
||||
"aquamarine"
|
||||
],
|
||||
"hyprgraphics": [
|
||||
"hyprland",
|
||||
"hyprgraphics"
|
||||
],
|
||||
"hyprland-protocols": [
|
||||
"hyprland",
|
||||
"hyprland-protocols"
|
||||
@@ -1103,6 +1108,10 @@
|
||||
"hyprland",
|
||||
"hyprlang"
|
||||
],
|
||||
"hyprtoolkit": [
|
||||
"hyprland",
|
||||
"hyprtoolkit"
|
||||
],
|
||||
"hyprutils": [
|
||||
"hyprland",
|
||||
"hyprutils"
|
||||
@@ -1121,11 +1130,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786464334,
|
||||
"narHash": "sha256-/TBQT5rhBB2Dm4HoZzhGDaCwYmRTs3W3DPhMXFWc/BU=",
|
||||
"lastModified": 1791822904,
|
||||
"narHash": "sha256-gsxPblA9YV+By4SxXWkj2HGGk7MKGxiNj/T9bU4q31U=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "xdg-desktop-portal-hyprland",
|
||||
"rev": "9f0e9ff02739cd538d39bd706422dc50e9ca60dd",
|
||||
"rev": "0121b2e3f85c7ebf6280173f1054f7e5606262d1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1143,11 +1152,11 @@
|
||||
"rust-overlay": "rust-overlay_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790032246,
|
||||
"narHash": "sha256-njp/fLXNAru7mTWbs0p/iHZDMRZE7jLxjsnQei10rNY=",
|
||||
"lastModified": 1793195788,
|
||||
"narHash": "sha256-S+4LGf9l8RDFqAJ87HUorHPVasfnnMTE/ysaj3SWIk8=",
|
||||
"owner": "sxyazi",
|
||||
"repo": "yazi",
|
||||
"rev": "0ea4c5d9ef75d7165c2e3b4eb8d713fc11f26c96",
|
||||
"rev": "ec4ce8f27d7a34045791606ebe83b0795defe4b6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1159,11 +1168,11 @@
|
||||
"yazi-plugins": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1789736971,
|
||||
"narHash": "sha256-O1yYAhsf7xMqUrTTSLac06WSxCvUQqedH3DWqGwn/Ok=",
|
||||
"lastModified": 1793196370,
|
||||
"narHash": "sha256-0nD3jSkUt7mcsfPnJ3yBwdJYRY6JVSxVAj4eXEgBWus=",
|
||||
"owner": "yazi-rs",
|
||||
"repo": "plugins",
|
||||
"rev": "f703392df78b5fba5e8f9f1ad0b1cb6d3def9736",
|
||||
"rev": "896fa5aec1db0163e2f65d07a130d064de53b0b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
};
|
||||
|
||||
hyprland = {
|
||||
url = "github:hyprwm/Hyprland/1256bd1";
|
||||
url = "github:hyprwm/Hyprland/v0.57.0";
|
||||
# inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
];
|
||||
|
||||
nixpkgs.config.allowBroken = true;
|
||||
time.timeZone = "Europe/Berlin";
|
||||
programs.gamemode.enable = true;
|
||||
services.printing.enable = true;
|
||||
virtualisation.virtualbox.host.enable = true;
|
||||
|
||||
@@ -8,10 +8,27 @@ let
|
||||
};
|
||||
in {
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
# Keep the animated wallpaper visible through the panel's translucent surface.
|
||||
layer_rule = [
|
||||
{
|
||||
match.namespace = "^waybar$";
|
||||
blur = true;
|
||||
# Blur the glass, but exclude its low-alpha CSS shadow from the mask.
|
||||
ignore_alpha = 0.2;
|
||||
xray = true;
|
||||
}
|
||||
{
|
||||
match.namespace = "^walker$";
|
||||
blur = true;
|
||||
ignore_alpha = 0.08;
|
||||
xray = true;
|
||||
}
|
||||
];
|
||||
|
||||
monitor = [
|
||||
{
|
||||
output = "DP-3";
|
||||
mode = "1920x1080@165";
|
||||
mode = "2560x1440@100";
|
||||
position = "0x0";
|
||||
scale = 1;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
nix.settings.cores = 3;
|
||||
nixpkgs.config.allowBroken = true;
|
||||
time.timeZone = "Europe/Berlin";
|
||||
programs.gamemode.enable = true;
|
||||
hardware.bluetooth.enable = true;
|
||||
host.laptop = true;
|
||||
|
||||
+8
-1
@@ -1,4 +1,4 @@
|
||||
{ pkgs, inputs, ... }: {
|
||||
{ pkgs, inputs, lib, config, ... }: {
|
||||
imports = [
|
||||
./host/bluetooth.nix
|
||||
./host/bootloader.nix
|
||||
@@ -24,6 +24,13 @@
|
||||
hyprland.package = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.hyprland;
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
inputs.hyprland.inputs.hyprland-guiutils.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||
];
|
||||
|
||||
security.wrappers.Hyprland.source = lib.mkForce
|
||||
"${config.programs.hyprland.package}/bin/.Hyprland-wrapped";
|
||||
|
||||
xdg.portal = {
|
||||
enable = true;
|
||||
xdgOpenUsePortal = true;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
./user/sops.nix
|
||||
./user/ssh.nix
|
||||
./user/walker.nix
|
||||
./user/wob.nix
|
||||
./user/yazi.nix
|
||||
./user/zsh.nix
|
||||
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
|
||||
// reminders: what the clock rings for, what the calendar marks, and what the
|
||||
// widgets and the lock screen list as coming up
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// [{ id, year, month, day, hour, minute, name, repeat }]; repeat is
|
||||
// "" | "daily" | "weekdays" | "weekly", counted from the stored date
|
||||
property alias items: store.items
|
||||
property int tick: 0
|
||||
// reminders due and not yet dealt with, oldest first
|
||||
property var ringing: []
|
||||
readonly property var firing: root.ringing.length > 0 ? root.ringing[0] : null
|
||||
property var snoozedUntil: ({})
|
||||
property var dismissedAt: ({})
|
||||
property string checkedDay: ""
|
||||
// when this copy of the shell started, so a reload does not ring out loud
|
||||
readonly property real bornAt: Date.now()
|
||||
// changes once a minute; what the countdown text needs, instead of every tick
|
||||
property string minuteKey: ""
|
||||
|
||||
readonly property var next: {
|
||||
root.tick;
|
||||
CalendarSource.tick;
|
||||
return root.upcoming(1)[0] || null;
|
||||
}
|
||||
readonly property bool soon: {
|
||||
var n = root.next;
|
||||
return n !== null && n.at.getTime() - Loc.nowMs() <= 7 * 86400000;
|
||||
}
|
||||
|
||||
readonly property var repeatOptions: [
|
||||
{ "key": "", "label": "Once" },
|
||||
{ "key": "daily", "label": "Daily" },
|
||||
{ "key": "weekdays", "label": "Weekdays" },
|
||||
{ "key": "weekly", "label": "Weekly" }
|
||||
]
|
||||
|
||||
function dayStart(y, m, d) {
|
||||
return new Date(y, m, d, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// does this reminder happen on the given day?
|
||||
function occursOn(r, y, m, d) {
|
||||
var day = root.dayStart(y, m, d);
|
||||
var start = root.dayStart(r.year, r.month, r.day);
|
||||
if (day < start)
|
||||
return false;
|
||||
|
||||
switch (r.repeat || "") {
|
||||
case "daily":
|
||||
return true;
|
||||
case "weekdays":
|
||||
return day.getDay() !== 0 && day.getDay() !== 6;
|
||||
case "weekly":
|
||||
return day.getDay() === start.getDay();
|
||||
}
|
||||
return day.getTime() === start.getTime();
|
||||
}
|
||||
|
||||
// the next time a reminder rings at or after `from`
|
||||
// `next` re-reads this every second for every reminder, so it must not walk
|
||||
// the calendar: a one-off happens on exactly its own date, and daily,
|
||||
// weekdays and weekly all come round inside a week of whichever is later,
|
||||
// today or the date the repeat starts from
|
||||
function nextAt(r, from) {
|
||||
if (!r.repeat) {
|
||||
var at = new Date(r.year, r.month, r.day, r.hour, r.minute, 0, 0);
|
||||
return at >= from ? at : null;
|
||||
}
|
||||
var fromDay = root.dayStart(from.getFullYear(), from.getMonth(), from.getDate());
|
||||
var startDay = root.dayStart(r.year, r.month, r.day);
|
||||
var probe = startDay > fromDay ? startDay : fromDay;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (root.occursOn(r, probe.getFullYear(), probe.getMonth(), probe.getDate())) {
|
||||
var a = new Date(probe.getFullYear(), probe.getMonth(), probe.getDate(), r.hour, r.minute, 0, 0);
|
||||
if (a >= from)
|
||||
return a;
|
||||
|
||||
}
|
||||
probe.setDate(probe.getDate() + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function upcoming(limit) {
|
||||
var now = Loc.now();
|
||||
var out = [];
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
var at = root.nextAt(root.items[i], now);
|
||||
if (at)
|
||||
out.push({ "item": root.items[i], "at": at });
|
||||
|
||||
}
|
||||
out = out.concat(CalendarSource.upcoming());
|
||||
out.sort((a, b) => {
|
||||
return a.at - b.at;
|
||||
});
|
||||
return out.slice(0, limit || out.length);
|
||||
}
|
||||
|
||||
function forDay(y, m, d) {
|
||||
var local = root.items.filter((r) => {
|
||||
return root.occursOn(r, y, m, d);
|
||||
});
|
||||
var fromThunderbird = CalendarSource.forDay(y, m, d).map((event) => CalendarSource.asAgendaItem(event));
|
||||
return local.concat(fromThunderbird).sort((a, b) => {
|
||||
return (a.hour * 60 + a.minute) - (b.hour * 60 + b.minute);
|
||||
});
|
||||
}
|
||||
|
||||
function hasOn(y, m, d) {
|
||||
if (CalendarSource.hasOn(y, m, d))
|
||||
return true;
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
if (root.occursOn(root.items[i], y, m, d))
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function add(y, m, d, hour, minute, name, repeat) {
|
||||
root.items = root.items.concat([{
|
||||
"id": Date.now() + "-" + Math.random().toString(36).slice(2, 7),
|
||||
"year": y,
|
||||
"month": m,
|
||||
"day": d,
|
||||
"hour": hour,
|
||||
"minute": minute,
|
||||
"name": name,
|
||||
"repeat": repeat || ""
|
||||
}]);
|
||||
root.tick++;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
root.items = root.items.filter((r) => {
|
||||
return r.id !== id;
|
||||
});
|
||||
root.ringing = root.ringing.filter((r) => {
|
||||
return r.id !== id;
|
||||
});
|
||||
root.tick++;
|
||||
}
|
||||
|
||||
function timeText(r) {
|
||||
if (!r)
|
||||
return "";
|
||||
|
||||
if (Prefs.clock24h)
|
||||
return String(r.hour).padStart(2, "0") + ":" + String(r.minute).padStart(2, "0");
|
||||
|
||||
var h = r.hour % 12 === 0 ? 12 : r.hour % 12;
|
||||
return h + ":" + String(r.minute).padStart(2, "0") + " " + (r.hour < 12 ? "AM" : "PM");
|
||||
}
|
||||
|
||||
function untilText(at) {
|
||||
if (!at)
|
||||
return "";
|
||||
|
||||
var mins = Math.round((at - Loc.now()) / 60000);
|
||||
if (mins < 1)
|
||||
return "now";
|
||||
|
||||
if (mins < 60)
|
||||
return "in " + mins + " min";
|
||||
|
||||
var hrs = Math.round(mins / 60);
|
||||
if (hrs < 24)
|
||||
return "in " + hrs + (hrs === 1 ? " hour" : " hours");
|
||||
|
||||
var days = Math.round(hrs / 24);
|
||||
return days === 1 ? "tomorrow" : "in " + days + " days";
|
||||
}
|
||||
|
||||
function repeatText(r) {
|
||||
switch (r.repeat || "") {
|
||||
case "daily":
|
||||
return "Every day";
|
||||
case "weekdays":
|
||||
return "Weekdays";
|
||||
case "weekly":
|
||||
return "Every " + root.dayStart(r.year, r.month, r.day).toLocaleDateString(Qt.locale(), "dddd");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function snooze(id, minutes) {
|
||||
var m = Object.assign({}, root.snoozedUntil);
|
||||
m[id] = Loc.nowMs() + minutes * 60000;
|
||||
root.snoozedUntil = m;
|
||||
root.ringing = root.ringing.filter((q) => {
|
||||
return q.id !== id;
|
||||
});
|
||||
}
|
||||
|
||||
// a one-off goes once it has rung; a repeating one waits for its next turn
|
||||
function dismiss(id) {
|
||||
var m = Object.assign({}, root.dismissedAt);
|
||||
m[id] = Loc.nowMs();
|
||||
root.dismissedAt = m;
|
||||
root.ringing = root.ringing.filter((q) => {
|
||||
return q.id !== id;
|
||||
});
|
||||
var r = root.items.find((x) => {
|
||||
return x.id === id;
|
||||
});
|
||||
if (r && !r.repeat)
|
||||
root.remove(id);
|
||||
|
||||
}
|
||||
|
||||
function prune(now) {
|
||||
var today = root.dayStart(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
var kept = root.items.filter((r) => {
|
||||
return r.repeat || root.dayStart(r.year, r.month, r.day) >= today;
|
||||
});
|
||||
if (kept.length !== root.items.length)
|
||||
root.items = kept;
|
||||
|
||||
}
|
||||
|
||||
function check() {
|
||||
var now = Loc.now();
|
||||
var key = now.getFullYear() + "-" + now.getMonth() + "-" + now.getDate();
|
||||
if (root.checkedDay !== key) {
|
||||
root.prune(now);
|
||||
root.checkedDay = key;
|
||||
}
|
||||
var mkey = key + "-" + now.getHours() + "-" + now.getMinutes();
|
||||
if (root.minuteKey !== mkey)
|
||||
root.minuteKey = mkey;
|
||||
|
||||
var fresh = false;
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
var r = root.items[i];
|
||||
if (root.ringing.some((q) => {
|
||||
return q.id === r.id;
|
||||
}))
|
||||
continue;
|
||||
|
||||
var until = root.snoozedUntil[r.id];
|
||||
if (until && now.getTime() < until)
|
||||
continue;
|
||||
|
||||
// today's occurrence, if there is one and it has come round
|
||||
if (!root.occursOn(r, now.getFullYear(), now.getMonth(), now.getDate()))
|
||||
continue;
|
||||
|
||||
var due = new Date(now.getFullYear(), now.getMonth(), now.getDate(), r.hour, r.minute, 0, 0);
|
||||
if (now < due)
|
||||
continue;
|
||||
|
||||
var last = root.dismissedAt[r.id];
|
||||
if (last && last >= due.getTime())
|
||||
continue;
|
||||
|
||||
// an hour late is too late to ring
|
||||
if (!until && now.getTime() - due.getTime() > 3600000)
|
||||
continue;
|
||||
|
||||
root.ringing = root.ringing.concat([r]);
|
||||
// a reload forgets dismissals, so one already overdue as the shell
|
||||
// starts rings without a sound; after that, one that came due while
|
||||
// the machine slept still gets it
|
||||
if (until || Date.now() - root.bornAt > 5000 || now.getTime() - due.getTime() < 60000)
|
||||
fresh = true;
|
||||
|
||||
}
|
||||
if (fresh && Prefs.reminderSound)
|
||||
Sounds.play("reminder", 1);
|
||||
|
||||
root.tick++;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: root.check()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveDebounce
|
||||
|
||||
interval: 300
|
||||
onTriggered: file.writeAdapter()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: file
|
||||
|
||||
path: Quickshell.env("HOME") + "/.config/quickshell/lucidbar/clock_reminders.json"
|
||||
blockLoading: true
|
||||
onAdapterUpdated: saveDebounce.restart()
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: store
|
||||
|
||||
property var items: []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
pragma Singleton
|
||||
|
||||
// Read-only events already synchronized by Thunderbird. This deliberately does
|
||||
// not connect to DAV or store credentials; Thunderbird remains the DAV client.
|
||||
Singleton {
|
||||
id: source
|
||||
|
||||
property var events: []
|
||||
property int tick: 0
|
||||
property string error: ""
|
||||
property real updatedAt: 0
|
||||
|
||||
function refresh() {
|
||||
if (reader.running)
|
||||
return;
|
||||
reader.command = ["python3", Quickshell.env("HOME") + "/.config/quickshell/thunderbird-calendar.py"];
|
||||
reader.running = true;
|
||||
}
|
||||
|
||||
function forDay(year, month, day) {
|
||||
source.tick;
|
||||
return source.events.filter((event) => {
|
||||
const date = new Date(event.startMs);
|
||||
return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
|
||||
});
|
||||
}
|
||||
|
||||
function upcoming(limit) {
|
||||
source.tick;
|
||||
const now = Loc.nowMs();
|
||||
return source.events.filter((event) => event.startMs >= now).slice(0, limit || source.events.length).map((event) => ({
|
||||
"item": source.asAgendaItem(event),
|
||||
"at": new Date(event.startMs)
|
||||
}));
|
||||
}
|
||||
|
||||
function hasOn(year, month, day) {
|
||||
return source.forDay(year, month, day).length > 0;
|
||||
}
|
||||
|
||||
function asAgendaItem(event) {
|
||||
const date = new Date(event.startMs);
|
||||
return {
|
||||
"id": "thunderbird:" + event.id,
|
||||
"name": event.name,
|
||||
"calendar": event.calendar,
|
||||
"source": "thunderbird",
|
||||
"startMs": event.startMs,
|
||||
"endMs": event.endMs,
|
||||
"hour": date.getHours(),
|
||||
"minute": date.getMinutes(),
|
||||
"repeat": ""
|
||||
};
|
||||
}
|
||||
|
||||
Process {
|
||||
id: reader
|
||||
|
||||
command: []
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text.trim() === "")
|
||||
return;
|
||||
try {
|
||||
const result = JSON.parse(this.text);
|
||||
if (!result.error) {
|
||||
source.events = result.events || [];
|
||||
source.error = "";
|
||||
source.updatedAt = Date.now();
|
||||
source.tick++;
|
||||
} else {
|
||||
source.error = result.error;
|
||||
}
|
||||
} catch (e) {
|
||||
source.error = "Could not read Thunderbird calendar cache";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (code) => {
|
||||
if (code !== 0 && source.error === "")
|
||||
source.error = "Could not read Thunderbird calendar cache";
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 300000
|
||||
repeat: true
|
||||
running: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: source.refresh()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.lucidui
|
||||
|
||||
// the month on the left, the picked day's reminders and a way to add one on the right
|
||||
Item {
|
||||
id: page
|
||||
|
||||
property var host: null
|
||||
property var selected: {
|
||||
const n = Loc.now();
|
||||
return { "year": n.getFullYear(), "month": n.getMonth(), "day": n.getDate() };
|
||||
}
|
||||
readonly property date selectedDate: new Date(page.selected.year, page.selected.month, page.selected.day)
|
||||
readonly property var dayItems: {
|
||||
Agenda.checkedDay;
|
||||
return Agenda.forDay(page.selected.year, page.selected.month, page.selected.day);
|
||||
}
|
||||
property int newHour: 9
|
||||
property int newMinute: 0
|
||||
// the hour is freshTime's next-hour guess, not one anybody chose
|
||||
property bool hourGuessed: false
|
||||
// am/pm was set by hand, so a typed hour keeps to it
|
||||
property bool halfPicked: false
|
||||
property string newRepeat: ""
|
||||
readonly property bool pm: page.newHour >= 12
|
||||
readonly property int shownHour: Prefs.clock24h ? page.newHour : (page.newHour % 12 === 0 ? 12 : page.newHour % 12)
|
||||
// a one-off set before now would never ring
|
||||
readonly property bool past: {
|
||||
Agenda.minuteKey;
|
||||
return page.newRepeat === "" && page.gone(page.newHour, page.newMinute);
|
||||
}
|
||||
readonly property bool shown: page.visible && (!page.host || page.host.expanded)
|
||||
|
||||
onShownChanged: if (page.shown)
|
||||
page.freshTime()
|
||||
|
||||
function selectToday() {
|
||||
const n = Loc.now();
|
||||
page.selected = { "year": n.getFullYear(), "month": n.getMonth(), "day": n.getDate() };
|
||||
month.goToday();
|
||||
page.freshTime();
|
||||
}
|
||||
|
||||
// earlier than the minute we are in, on the picked day
|
||||
function gone(hour, minute) {
|
||||
const n = Loc.now();
|
||||
return new Date(page.selected.year, page.selected.month, page.selected.day, hour, minute) < new Date(n.getFullYear(), n.getMonth(), n.getDate(), n.getHours(), n.getMinutes());
|
||||
}
|
||||
|
||||
function isToday() {
|
||||
return page.gone(0, 0) && !page.gone(23, 59);
|
||||
}
|
||||
|
||||
// every opening of the clock starts on today, unless a reminder is half written
|
||||
function startVisit() {
|
||||
if (nameField.text !== "")
|
||||
return ;
|
||||
|
||||
const n = Loc.now();
|
||||
page.selected = { "year": n.getFullYear(), "month": n.getMonth(), "day": n.getDate() };
|
||||
if (!month.isCurrent)
|
||||
month.goToday();
|
||||
|
||||
page.freshTime();
|
||||
}
|
||||
|
||||
// 9:00, or the next hour once 9:00 has gone today; a time already set for a name stays
|
||||
function freshTime() {
|
||||
if (nameField.text !== "")
|
||||
return ;
|
||||
|
||||
page.newHour = 9;
|
||||
page.newMinute = 0;
|
||||
page.hourGuessed = false;
|
||||
page.halfPicked = false;
|
||||
const next = Loc.now().getHours() + 1;
|
||||
if (page.gone(9, 0) && next < 24 && !page.gone(next, 0)) {
|
||||
page.newHour = next;
|
||||
page.hourGuessed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// a typed 12-hour time means the half still to come: 12:04 at noon is pm
|
||||
function preferUpcoming() {
|
||||
if (Prefs.clock24h || !page.past)
|
||||
return ;
|
||||
|
||||
const flipped = (page.newHour + 12) % 24;
|
||||
if (!page.gone(flipped, page.newMinute))
|
||||
page.newHour = flipped;
|
||||
|
||||
}
|
||||
|
||||
function focusName() {
|
||||
nameField.input.forceActiveFocus();
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const name = nameField.text.trim();
|
||||
if (name === "" || page.past)
|
||||
return ;
|
||||
|
||||
Agenda.add(page.selected.year, page.selected.month, page.selected.day, page.newHour, page.newMinute, name, page.newRepeat);
|
||||
nameField.text = "";
|
||||
page.newRepeat = "";
|
||||
}
|
||||
|
||||
function step(minutes) {
|
||||
page.hourGuessed = false;
|
||||
let t = (page.newHour * 60 + page.newMinute + minutes + 1440) % 1440;
|
||||
page.newHour = Math.floor(t / 60);
|
||||
page.newMinute = t % 60;
|
||||
}
|
||||
|
||||
// typed hours are 0-23, or 1-12 next to the am/pm toggle; on today a
|
||||
// 12-hour one is its next turn, not the half the guessed hour left behind
|
||||
function typeHour(h) {
|
||||
page.hourGuessed = false;
|
||||
if (Prefs.clock24h) {
|
||||
page.newHour = Math.min(23, h);
|
||||
} else if (page.isToday() && !page.halfPicked) {
|
||||
// the minutes are still to come, so only an hour wholly gone is past
|
||||
const h12 = Math.min(12, h) % 12;
|
||||
page.newHour = page.gone(h12, 59) ? h12 + 12 : h12;
|
||||
} else {
|
||||
page.newHour = Math.min(12, h) % 12 + (page.pm ? 12 : 0);
|
||||
page.preferUpcoming();
|
||||
}
|
||||
}
|
||||
|
||||
// minutes typed under the guessed hour mean the next time the clock shows
|
||||
// them: at 12:19, 20 is 12:20 rather than 1:20
|
||||
function typeMinute(m) {
|
||||
page.newMinute = Math.min(59, m);
|
||||
if (page.hourGuessed && page.isToday()) {
|
||||
const h = Loc.now().getHours();
|
||||
if (!page.gone(h, page.newMinute))
|
||||
page.newHour = h;
|
||||
else if (h + 1 < 24)
|
||||
page.newHour = h + 1;
|
||||
}
|
||||
page.preferUpcoming();
|
||||
}
|
||||
|
||||
function setPm(wantPm) {
|
||||
page.hourGuessed = false;
|
||||
page.halfPicked = true;
|
||||
page.newHour = page.newHour % 12 + (wantPm ? 12 : 0);
|
||||
}
|
||||
|
||||
// two digits you type into, each holding its own value until focus leaves
|
||||
component TimeDigit: Item {
|
||||
id: digit
|
||||
|
||||
property int value: 0
|
||||
property int span: 60
|
||||
property Item nextDigit: null
|
||||
readonly property bool typing: field.activeFocus
|
||||
|
||||
signal typed(int v)
|
||||
signal meridiem(bool wantPm)
|
||||
signal accepted()
|
||||
|
||||
function show() {
|
||||
field.text = String(digit.value).padStart(2, "0");
|
||||
}
|
||||
|
||||
function take() {
|
||||
field.forceActiveFocus();
|
||||
field.selectAll();
|
||||
}
|
||||
|
||||
function nudge(by) {
|
||||
digit.typed((digit.value + by + digit.span) % digit.span);
|
||||
digit.show();
|
||||
}
|
||||
|
||||
width: Theme.dp(30)
|
||||
height: Theme.dp(30)
|
||||
onValueChanged: if (!field.activeFocus)
|
||||
digit.show()
|
||||
Component.onCompleted: digit.show()
|
||||
|
||||
TextInput {
|
||||
id: field
|
||||
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
maximumLength: 2
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
selectByMouse: true
|
||||
color: Theme.text
|
||||
selectionColor: Theme.alpha(Theme.primary, 0.35)
|
||||
selectedTextColor: Theme.text
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.typeSize("labelLarge")
|
||||
font.variableAxes: Theme.axes(Theme.typeSize("labelLarge"), 640, 0)
|
||||
font.features: ({
|
||||
"tnum": 1
|
||||
})
|
||||
onTextEdited: {
|
||||
if (field.text !== "")
|
||||
digit.typed(parseInt(field.text, 10));
|
||||
|
||||
if (field.text.length === 2 && digit.nextDigit)
|
||||
digit.nextDigit.take();
|
||||
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
if (field.activeFocus)
|
||||
Qt.callLater(field.selectAll);
|
||||
else
|
||||
digit.show();
|
||||
}
|
||||
onAccepted: digit.accepted()
|
||||
Keys.onUpPressed: digit.nudge(1)
|
||||
Keys.onDownPressed: digit.nudge(-1)
|
||||
Keys.onTabPressed: digit.nextDigit ? digit.nextDigit.take() : digit.show()
|
||||
Keys.onPressed: (e) => {
|
||||
const c = e.text.toLowerCase();
|
||||
if (c === "a" || c === "p")
|
||||
digit.meridiem(c === "p");
|
||||
|
||||
}
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /[0-9]{0,2}/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - Theme.dp(6)
|
||||
height: Theme.dp(2)
|
||||
radius: 1
|
||||
color: digit.typing ? Theme.primary : Theme.alpha(Theme.subtext, 0.4)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.durFastEffects
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
implicitHeight: Theme.dp(404)
|
||||
|
||||
Connections {
|
||||
function onExpandedChanged() {
|
||||
if (page.host.expanded)
|
||||
page.startVisit();
|
||||
|
||||
}
|
||||
|
||||
target: page.host
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: monthCard
|
||||
|
||||
width: Theme.dp(312)
|
||||
height: parent.height
|
||||
radius: Theme.shapeXl
|
||||
color: Theme.withBlur(Theme.surfaceHigh)
|
||||
|
||||
MonthView {
|
||||
id: month
|
||||
|
||||
anchors.centerIn: parent
|
||||
cell: Theme.dp(40)
|
||||
selected: page.selected
|
||||
onPicked: (y, m, d) => {
|
||||
page.selected = { "year": y, "month": m, "day": d };
|
||||
if (m !== month.monthIndex)
|
||||
month.page(y * 12 + m > month.year * 12 + month.monthIndex ? 1 : -1);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: monthCard.right
|
||||
anchors.leftMargin: Theme.dp(12)
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
radius: Theme.shapeXl
|
||||
color: Theme.withBlur(Theme.surfaceHigh)
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.dp(18)
|
||||
|
||||
Column {
|
||||
id: dayHead
|
||||
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
LText {
|
||||
role: "titleLarge"
|
||||
weight: 560
|
||||
text: page.selectedDate.toLocaleDateString(Qt.locale(), "dddd")
|
||||
}
|
||||
|
||||
LText {
|
||||
role: "bodyMedium"
|
||||
color: Theme.subtext
|
||||
text: page.selectedDate.toLocaleDateString(Qt.locale(), "d MMMM yyyy") + (page.dayItems.length ? " · " + page.dayItems.length + (page.dayItems.length === 1 ? " reminder" : " reminders") : "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: list
|
||||
|
||||
anchors.top: dayHead.bottom
|
||||
anchors.topMargin: Theme.dp(12)
|
||||
anchors.bottom: form.top
|
||||
anchors.bottomMargin: Theme.dp(12)
|
||||
width: parent.width
|
||||
contentHeight: items.implicitHeight
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: items
|
||||
|
||||
width: list.width
|
||||
spacing: Theme.dp(2)
|
||||
|
||||
Repeater {
|
||||
model: page.dayItems
|
||||
|
||||
ListItem {
|
||||
id: row
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: items.width
|
||||
first: row.index === 0
|
||||
last: row.index === page.dayItems.length - 1
|
||||
containerColor: Theme.withBlur(Theme.surfaceHighest)
|
||||
clickable: false
|
||||
headline: row.modelData.name
|
||||
supporting: Agenda.timeText(row.modelData) + (row.modelData.repeat ? " · " + Agenda.repeatText(row.modelData) : "") + (row.modelData.source === "thunderbird" ? " · " + row.modelData.calendar : "")
|
||||
icon: row.modelData.source === "thunderbird" ? "event" : (row.modelData.repeat ? "repeat" : "notifications")
|
||||
iconColor: Theme.primary
|
||||
trailing: [
|
||||
IconButton {
|
||||
visible: row.modelData.source !== "thunderbird"
|
||||
icon: "delete"
|
||||
size: "xs"
|
||||
onClicked: Agenda.remove(row.modelData.id)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
visible: page.dayItems.length === 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.dp(24)
|
||||
spacing: Theme.dp(8)
|
||||
|
||||
Icon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "event_available"
|
||||
size: Theme.dp(32)
|
||||
color: Theme.subtextDim
|
||||
}
|
||||
|
||||
LText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
role: "bodyMedium"
|
||||
color: Theme.subtext
|
||||
text: "A free day"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
id: form
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
spacing: Theme.dp(10)
|
||||
|
||||
TextField {
|
||||
id: nameField
|
||||
|
||||
width: parent.width
|
||||
label: "New reminder"
|
||||
placeholder: "What should it say?"
|
||||
supporting: page.past && nameField.text.trim() !== "" ? (page.gone(0, 0) && !page.gone(23, 59) ? Agenda.timeText({ "hour": page.newHour, "minute": page.newMinute }) + " has already gone by today" : "That day has already gone by") : ""
|
||||
containerColor: Theme.withBlur(Theme.surfaceHighest)
|
||||
onAccepted: page.submit()
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
width: parent.width
|
||||
size: "xs"
|
||||
options: Agenda.repeatOptions
|
||||
current: page.newRepeat
|
||||
onPicked: (k) => {
|
||||
return page.newRepeat = k;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.dp(8)
|
||||
|
||||
Rectangle {
|
||||
id: timeBox
|
||||
|
||||
width: timeRow.implicitWidth + Theme.dp(20)
|
||||
height: Theme.dp(36)
|
||||
radius: Theme.dp(18)
|
||||
color: Theme.withBlur(Theme.surfaceHighest)
|
||||
|
||||
Row {
|
||||
id: timeRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.dp(2)
|
||||
|
||||
TimeDigit {
|
||||
id: hourDigit
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
value: page.shownHour
|
||||
span: Prefs.clock24h ? 24 : 12
|
||||
nextDigit: minuteDigit
|
||||
onTyped: (v) => {
|
||||
return page.typeHour(v);
|
||||
}
|
||||
onMeridiem: (wantPm) => {
|
||||
return page.setPm(wantPm);
|
||||
}
|
||||
onAccepted: page.submit()
|
||||
}
|
||||
|
||||
LText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
role: "labelLarge"
|
||||
weight: 640
|
||||
color: Theme.subtext
|
||||
text: ":"
|
||||
}
|
||||
|
||||
TimeDigit {
|
||||
id: minuteDigit
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
value: page.newMinute
|
||||
span: 60
|
||||
onTyped: (v) => {
|
||||
return page.typeMinute(v);
|
||||
}
|
||||
onMeridiem: (wantPm) => {
|
||||
return page.setPm(wantPm);
|
||||
}
|
||||
onAccepted: page.submit()
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !Prefs.clock24h
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.dp(32)
|
||||
height: Theme.dp(30)
|
||||
|
||||
LText {
|
||||
anchors.centerIn: parent
|
||||
role: "labelMedium"
|
||||
weight: 640
|
||||
color: Theme.primary
|
||||
text: page.pm ? "PM" : "AM"
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
radius: Theme.rad(8)
|
||||
onClicked: page.setPm(!page.pm)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the wheel moves a minute at a time
|
||||
WheelHandler {
|
||||
onWheel: (e) => {
|
||||
return page.step(e.angleDelta.y > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - timeBox.width - addBtn.width - Theme.dp(16)
|
||||
height: 1
|
||||
}
|
||||
|
||||
Button {
|
||||
id: addBtn
|
||||
|
||||
variant: "filled"
|
||||
size: "xs"
|
||||
icon: "add"
|
||||
text: "Add"
|
||||
disabled: nameField.text.trim() === "" || page.past
|
||||
onClicked: page.submit()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,611 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs
|
||||
|
||||
Variants {
|
||||
id: variants
|
||||
|
||||
model: Quickshell.screens
|
||||
|
||||
Scope {
|
||||
id: unit
|
||||
|
||||
required property var modelData
|
||||
|
||||
PanelWindow {
|
||||
id: layer
|
||||
|
||||
PaletteFade {
|
||||
active: layer.visible
|
||||
}
|
||||
|
||||
readonly property bool suppressed: Prefs.widgetHideFullscreen && layer.fullscreenUp
|
||||
readonly property var workspaceMonitor: Hyprland.monitorFor(layer.screen)
|
||||
readonly property var activeWorkspace: workspaceMonitor ? workspaceMonitor.activeWorkspace : null
|
||||
readonly property bool fullscreenUp: {
|
||||
const workspace = layer.activeWorkspace;
|
||||
if (!workspace)
|
||||
return false;
|
||||
|
||||
return Hyprland.toplevels.values.some((t) => {
|
||||
const o = t.lastIpcObject || {};
|
||||
return o.workspace && o.workspace.id === workspace.id &&
|
||||
(o.fullscreen || 0) > 0 && o.mapped !== false;
|
||||
});
|
||||
}
|
||||
|
||||
screen: unit.modelData
|
||||
visible: Prefs.loaded && Widgets.loaded && (deck.panelLive || ((Prefs.widgetsEnabled || deck.stowing) && Widgets.count > 0 && !layer.suppressed))
|
||||
color: "transparent"
|
||||
// reserves nothing and refuses to be shrunk into the bar and dock's strips,
|
||||
// so a card can be dragged to a true screen edge and sit under the dock.
|
||||
// setting exclusiveZone at all would silently undo this
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
// the widget panel lifts the whole board over the windows until it has slid away
|
||||
WlrLayershell.layer: deck.panelLive ? WlrLayer.Overlay : (Prefs.widgetOnTop ? WlrLayer.Top : WlrLayer.Bottom)
|
||||
WlrLayershell.keyboardFocus: deck.panelUp ? WlrKeyboardFocus.Exclusive : (deck.wantsKeyboard ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None)
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
// lastIpcObject only moves when something asks it to, so nudge it on the
|
||||
// events that can change whether a window is fullscreen
|
||||
Connections {
|
||||
function onRawEvent(event) {
|
||||
if (event.name === "fullscreen" || event.name === "activewindowv2" || event.name === "closewindow" || event.name === "workspace" || event.name === "focusedmon")
|
||||
fullscreenPoll.restart();
|
||||
|
||||
}
|
||||
|
||||
enabled: Prefs.widgetHideFullscreen
|
||||
target: Hyprland
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: stowEnd
|
||||
|
||||
onTriggered: deck.stowing = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: fullscreenPoll
|
||||
|
||||
interval: 120
|
||||
onTriggered: Hyprland.refreshToplevels()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: deck
|
||||
|
||||
// bumped whenever the repeater's children change, so frameAt() bindings re-run
|
||||
property int rev: 0
|
||||
property Item dragFrame: null
|
||||
|
||||
readonly property string screenName: layer.screen ? layer.screen.name : ""
|
||||
readonly property bool isPrimary: layer.screen !== null && layer.screen === Monitors.mainScreen
|
||||
// the widget panel is open on this screen, and still on its way out
|
||||
readonly property bool panelUp: Widgets.panelScreen !== "" && Widgets.panelScreen === deck.screenName
|
||||
readonly property bool panelLive: deck.panelUp || panel.shown
|
||||
// the cards are out: on, or the panel is up to place them
|
||||
readonly property bool widgetsOn: Prefs.widgetsEnabled || deck.panelLive
|
||||
// put away a moment ago: the board stays up while the cards go
|
||||
property bool stowing: false
|
||||
readonly property int stowStep: Math.round(30 * Theme.motionScale)
|
||||
readonly property int wakeStep: Math.round(55 * Theme.motionScale)
|
||||
|
||||
// the order the cards go and come back in: down the screen, then across
|
||||
function orderOf(f) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var o = rep.itemAt(i);
|
||||
if (o && o !== f && o.onThisScreen && (o.y < f.y - 1 || (Math.abs(o.y - f.y) <= 1 && o.x < f.x)))
|
||||
n++;
|
||||
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
onWidgetsOnChanged: {
|
||||
if (deck.widgetsOn) {
|
||||
stowEnd.stop();
|
||||
deck.stowing = false;
|
||||
} else {
|
||||
stowEnd.interval = rep.count * deck.stowStep + Theme.durMedium + 80;
|
||||
deck.stowing = true;
|
||||
stowEnd.restart();
|
||||
}
|
||||
}
|
||||
readonly property int frameCount: rep.count
|
||||
readonly property bool editing: Widgets.editUid !== "" && deck.editFrame !== null
|
||||
// only ask for keys at all once something you can type into is placed;
|
||||
// on-demand hands focus back the moment a window is clicked, which
|
||||
// exclusive would not
|
||||
readonly property bool wantsKeyboard: {
|
||||
for (var i = 0; i < Widgets.model.count; i++) {
|
||||
var e = Widgets.model.get(i);
|
||||
if (!e.closing && (e.wtype === "notes" || e.wtype === "todo" || e.wtype === "github"))
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
readonly property bool grabbing: Widgets.dragUid !== "" || Widgets.menuUid !== "" || deck.editing
|
||||
readonly property Item menuFrame: {
|
||||
var _ = deck.rev;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (f && f.uid === Widgets.menuUid && f.visible)
|
||||
return f;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
readonly property Item editFrame: {
|
||||
var _ = deck.rev;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (f && f.uid === Widgets.editUid && f.visible)
|
||||
return f;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function frameAt(i) {
|
||||
var _ = deck.rev;
|
||||
return (i >= 0 && i < rep.count) ? rep.itemAt(i) : null;
|
||||
}
|
||||
|
||||
function frameFor(uid) {
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (f && f.uid === uid)
|
||||
return f;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// a board point over the open panel, where a dragged card is let go of
|
||||
function binHit(px, py) {
|
||||
return deck.panelUp && panel.sheetHit(px, py);
|
||||
}
|
||||
|
||||
// the topmost card under a board point, for the menu overlay: it
|
||||
// covers the screen while it is up, so the cards never see the press
|
||||
function frameUnder(px, py) {
|
||||
var best = null;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (!f || !f.visible || f.closing)
|
||||
continue;
|
||||
|
||||
if (px < f.x || px > f.x + f.width || py < f.y || py > f.y + f.height)
|
||||
continue;
|
||||
|
||||
if (best === null || f.z >= best.z)
|
||||
best = f;
|
||||
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: {
|
||||
var busy = Widgets.editUid !== "" || Widgets.menuUid !== "" || Object.keys(Widgets.selection).length > 0;
|
||||
Widgets.editUid = "";
|
||||
Widgets.menuUid = "";
|
||||
Widgets.clearSelection();
|
||||
if (!busy && deck.panelUp)
|
||||
Widgets.closePanel();
|
||||
|
||||
}
|
||||
onWidthChanged: deck.report()
|
||||
onHeightChanged: deck.report()
|
||||
Component.onCompleted: deck.report()
|
||||
|
||||
function report() {
|
||||
if (deck.isPrimary && deck.width > 0) {
|
||||
Widgets.canvasW = deck.width;
|
||||
Widgets.canvasH = deck.height;
|
||||
}
|
||||
}
|
||||
|
||||
// the dim behind the cards while the widget panel is open, and the way out of it
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.alpha(Theme.cScrim, Theme.blurAmount > 0 ? 0.3 : 0.5)
|
||||
opacity: panel.reveal
|
||||
visible: opacity > 0.01
|
||||
|
||||
WheelHandler {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
enabled: deck.panelUp
|
||||
onWheel: (event) => {
|
||||
return event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// click-through everywhere except the cards, so this only fires for the
|
||||
// open menu, a note being typed in, or the widget panel
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: deck.grabbing || deck.panelUp
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: {
|
||||
var busy = Widgets.menuUid !== "" || Widgets.editUid !== "";
|
||||
Widgets.menuUid = "";
|
||||
Widgets.editUid = "";
|
||||
if (!busy && deck.panelUp) {
|
||||
Widgets.clearSelection();
|
||||
Widgets.closePanel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// the cards stack among themselves, under the guides and the panel
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Repeater {
|
||||
id: rep
|
||||
|
||||
model: Widgets.model
|
||||
onItemAdded: deck.rev++
|
||||
onItemRemoved: deck.rev++
|
||||
|
||||
WidgetFrame {
|
||||
board: deck
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: deck.height
|
||||
x: deck.dragFrame ? deck.dragFrame.guideX : -1
|
||||
color: Theme.accent
|
||||
opacity: (deck.dragFrame && deck.dragFrame.guideX >= 0) ? 0.65 : 0
|
||||
visible: opacity > 0.01
|
||||
z: 900
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.durQuick
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
width: deck.width
|
||||
y: deck.dragFrame ? deck.dragFrame.guideY : -1
|
||||
color: Theme.accent
|
||||
opacity: (deck.dragFrame && deck.dragFrame.guideY >= 0) ? 0.65 : 0
|
||||
visible: opacity > 0.01
|
||||
z: 900
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.durQuick
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WidgetPanel {
|
||||
id: panel
|
||||
|
||||
anchors.fill: parent
|
||||
z: 1000
|
||||
board: deck
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
x: 0
|
||||
y: 0
|
||||
width: (deck.grabbing || deck.panelUp) ? layer.width : 0
|
||||
height: (deck.grabbing || deck.panelUp) ? layer.height : 0
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(0)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(1)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(2)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(3)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(4)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(5)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(6)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(7)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(8)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(9)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(10)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(11)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(12)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(13)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(14)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(15)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(16)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(17)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(18)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(19)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the open panel frosts the whole screen behind its dim, cards and sheet alike
|
||||
BackgroundEffect.blurRegion: Theme.blurAmount > 0 ? (deck.panelUp ? wholeBlur : widgetBlur) : null
|
||||
|
||||
Region {
|
||||
id: wholeBlur
|
||||
|
||||
width: layer.width
|
||||
height: layer.height
|
||||
}
|
||||
|
||||
Region {
|
||||
id: widgetBlur
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(0)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(1)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(2)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(3)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(4)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(5)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(6)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(7)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(8)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(9)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(10)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(11)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(12)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(13)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(14)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(15)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(16)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(17)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(18)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(19)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// an options panel on the widget layer would sit under every window, so it
|
||||
// gets an overlay of its own that only exists while the panel is up
|
||||
PanelWindow {
|
||||
id: menuLayer
|
||||
|
||||
// the frame the panel belongs to, held past the close so the fade-out
|
||||
// keeps its geometry and its contents
|
||||
property Item held: null
|
||||
|
||||
readonly property Item target: deck.menuFrame
|
||||
|
||||
screen: unit.modelData
|
||||
// up the moment the menu opens, so the fade-in plays inside a mapped
|
||||
// surface, and stays up until the fade-out finishes
|
||||
visible: menuLayer.target !== null || menu.visible
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: menuLayer.target !== null ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
onTargetChanged: if (menuLayer.target)
|
||||
menuLayer.held = menuLayer.target
|
||||
onVisibleChanged: if (!menuLayer.visible)
|
||||
menuLayer.held = null
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: Widgets.menuUid = ""
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: (mouse) => {
|
||||
var f = mouse.button === Qt.RightButton ? deck.frameUnder(mouse.x, mouse.y) : null;
|
||||
var was = Widgets.menuUid;
|
||||
Widgets.menuUid = "";
|
||||
// a right press on another card carries the panel over to it,
|
||||
// the way it did when the panel still sat on the widget layer
|
||||
if (f && f.uid !== was)
|
||||
f.toggleMenu(mouse.x - f.x, mouse.y - f.y);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
WidgetMenu {
|
||||
id: menu
|
||||
|
||||
frame: menuLayer.held
|
||||
fieldW: menuLayer.width
|
||||
fieldH: menuLayer.height
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// no input once the panel starts closing, so the fade-out is never a
|
||||
// dead region over the screen
|
||||
mask: Region {
|
||||
width: menuLayer.target ? menuLayer.width : 0
|
||||
height: menuLayer.target ? menuLayer.height : 0
|
||||
}
|
||||
|
||||
BackgroundEffect.blurRegion: Theme.blurAmount > 0 ? menuBlur : null
|
||||
|
||||
Region {
|
||||
id: menuBlur
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
part: "menu"
|
||||
panel: menu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+12
-1
@@ -1,6 +1,17 @@
|
||||
{ inputs, pkgs, lib, config, ... }: let
|
||||
helixSteel =
|
||||
inputs.helix-steel.packages.${pkgs.system}.default.overrideAttrs (old: {
|
||||
(inputs.helix-steel.packages.${pkgs.system}.default.override {
|
||||
grammarOverlays = [
|
||||
(_: prev: {
|
||||
perl = prev.perl.overrideAttrs (old: {
|
||||
postPatch = (old.postPatch or "") + ''
|
||||
substituteInPlace src/bsearch.c --replace-fail 'void *bsearch(' 'void *tsp_bsearch('
|
||||
substituteInPlace src/tsp_unicode.h --replace-fail 'return bsearch(' 'return tsp_bsearch('
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
}).overrideAttrs (old: {
|
||||
cargoBuildFlags = (old.cargoBuildFlags or []) ++ [
|
||||
"--features"
|
||||
"steel,git"
|
||||
|
||||
@@ -32,6 +32,7 @@ in {
|
||||
ghostty
|
||||
pamixer
|
||||
clipse
|
||||
cliphist
|
||||
wl-clipboard
|
||||
wl-clip-persist
|
||||
xclip
|
||||
@@ -152,6 +153,11 @@ in {
|
||||
|
||||
bind = [
|
||||
(bind "SUPER + V" (exec "ghostty --title=clipse -e clipse"))
|
||||
(hyprLua.bind "SUPER + SHIFT + V" (hyprLua.execLauncher "qs ipc call launcher clipboard"))
|
||||
(bind "Print" (exec "qs ipc call snap toggle"))
|
||||
(bind "SUPER + Print" (exec "qs ipc call screenshot full"))
|
||||
(bind "CTRL + Print" (exec "qs ipc call snap region"))
|
||||
(bind "ALT + Print" (exec "qs ipc call snap window"))
|
||||
(bind "SUPER + RETURN" (exec "ghostty"))
|
||||
(bind "SUPER + Q" "hl.dsp.window.close()")
|
||||
(bind "SUPER + M" "hl.dsp.exit()")
|
||||
|
||||
@@ -7,6 +7,12 @@ in rec {
|
||||
|
||||
exec = command: "hl.dsp.exec_cmd(${quote command})";
|
||||
|
||||
# Hyprland 0.57 can leave exclusive layer-shell launchers unfocused when
|
||||
# follow_mouse = 1 and the pointer is already over a regular window.
|
||||
execLauncher = command: ''
|
||||
hl.dsp.exec_cmd(${quote ("hyprctl eval 'hl.config({ input = { follow_mouse = 2 } })' && " + command)})
|
||||
'';
|
||||
|
||||
bind = keys: dispatcher: {
|
||||
_args = [
|
||||
keys
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pinnedApps": []
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
{
|
||||
"accentPunch": 1,
|
||||
"audioCombine": "",
|
||||
"audioMeters": true,
|
||||
"audioMoveStreams": true,
|
||||
"autoColorMode": false,
|
||||
"barAutoHide": false,
|
||||
"barEnabled": true,
|
||||
"barFullCorner": 18,
|
||||
"barHeight": 35,
|
||||
"barHoverGrow": 3,
|
||||
"barHoverOpen": true,
|
||||
"barJoinDividers": false,
|
||||
"barJoins": "[]",
|
||||
"barLayout": "{\"left\":[\"start\",\"workspaces\",\"media\",\"tray\"],\"center\":[\"clock\"],\"right\":[\"window\",\"notifications\",\"system\",\"privacy\",\"power\"]}",
|
||||
"barMotionScale": 1.35,
|
||||
"barNotchFlare": 14,
|
||||
"barPopupGap": 10,
|
||||
"barPopupMode": true,
|
||||
"barPopupNotched": false,
|
||||
"barSideMargin": 17,
|
||||
"barSpacing": 8,
|
||||
"barStyle": "full",
|
||||
"barTopMargin": 5,
|
||||
"barZoneGap": 26,
|
||||
"btAgent": true,
|
||||
"btReceive": true,
|
||||
"btReceiveAutoPaired": false,
|
||||
"btReceiveFolder": "",
|
||||
"btScanOnOpen": true,
|
||||
"btShowUnnamed": false,
|
||||
"clipboardEnabled": true,
|
||||
"clock24h": true,
|
||||
"clockDateFormat": "long",
|
||||
"clockShowDate": true,
|
||||
"clockShowSeconds": true,
|
||||
"clockShowTimer": true,
|
||||
"clockShowWeather": true,
|
||||
"clockStyle": "accent",
|
||||
"clockWorldZones": "Europe/Berlin,Europe/Kiev,America/Chicago,Asia/Tokyo",
|
||||
"cornerScale": 1,
|
||||
"desktopIconSize": "medium",
|
||||
"desktopIconSpacing": "normal",
|
||||
"desktopIcons": false,
|
||||
"desktopIconsAutoArrange": false,
|
||||
"desktopIconsCorner": "left",
|
||||
"desktopIconsDrives": true,
|
||||
"desktopIconsHidden": false,
|
||||
"desktopIconsHideExt": false,
|
||||
"desktopIconsHome": false,
|
||||
"desktopIconsOpen": "double",
|
||||
"desktopIconsShown": true,
|
||||
"desktopIconsSort": "name",
|
||||
"desktopIconsSortDesc": false,
|
||||
"desktopIconsSpring": true,
|
||||
"desktopIconsThumbs": true,
|
||||
"desktopIconsTrash": true,
|
||||
"desktopKeys": "{}",
|
||||
"desktopMenu": true,
|
||||
"desktopSelection": true,
|
||||
"dndAllowCritical": true,
|
||||
"dndFullscreen": false,
|
||||
"doNotDisturb": false,
|
||||
"dockAutoHide": true,
|
||||
"dockBottomMargin": 13,
|
||||
"dockDynamicHide": true,
|
||||
"dockEnabled": true,
|
||||
"dockHoverEffect": 1,
|
||||
"dockIconPadding": 0,
|
||||
"dockIconSize": 41,
|
||||
"dockIconTiles": true,
|
||||
"dockMagnify": true,
|
||||
"dockNotchFlare": 14,
|
||||
"dockRadius": 40,
|
||||
"dockShowIndicators": true,
|
||||
"dockShowRunning": true,
|
||||
"dockShowTooltips": true,
|
||||
"dockSpacing": 12,
|
||||
"dockSplitGap": 8,
|
||||
"dockStyle": "notch",
|
||||
"envAdopted": true,
|
||||
"envAppFont": "Pixeloid Sans",
|
||||
"envAppFontSize": 13,
|
||||
"envApplyGtk": true,
|
||||
"envApplyHypr": true,
|
||||
"envApplyQt": true,
|
||||
"envColorScheme": "dark",
|
||||
"envCursorShadow": true,
|
||||
"envCursorSize": 32,
|
||||
"envCursorTheme": "Kurumi",
|
||||
"envDocumentFont": "Pixeloid Sans",
|
||||
"envDocumentFontSize": 13,
|
||||
"envFontSync": false,
|
||||
"envGtkTheme": "Adwaita",
|
||||
"envIconSet": "gravity",
|
||||
"envIconTheme": "Pop-Dark",
|
||||
"envLegacyIcons": false,
|
||||
"envMonoFont": "JetBrains Mono",
|
||||
"envMonoFontSize": 13,
|
||||
"envQtPlatformTheme": "",
|
||||
"envQtStyle": "kvantum",
|
||||
"fontFamily": "Pixeloid Sans",
|
||||
"fontScale": 1,
|
||||
"gameModeOffCmd": "",
|
||||
"gameModeOnCmd": "",
|
||||
"gameModeStatusCmd": "",
|
||||
"glassApps": "vscodium",
|
||||
"glassValues": "vscodium=0.9",
|
||||
"gpsEnabled": false,
|
||||
"hiddenThemes": "",
|
||||
"hyprOptions": "{\"general.layout\":\"scrolling\"}",
|
||||
"idleAdopted": true,
|
||||
"idleAutostart": true,
|
||||
"idleDim": true,
|
||||
"idleDimAfter": 120,
|
||||
"idleDimKeyboard": true,
|
||||
"idleDimLevel": 10,
|
||||
"idleEnabled": false,
|
||||
"idleKeepAwake": false,
|
||||
"idleLock": true,
|
||||
"idleLockAfter": 600,
|
||||
"idleLockBeforeSleep": true,
|
||||
"idleRespectInhibitors": true,
|
||||
"idleScreenOff": true,
|
||||
"idleScreenOffAfter": 900,
|
||||
"idleSuspend": false,
|
||||
"idleSuspendAfter": 1800,
|
||||
"idleSuspendOnAc": false,
|
||||
"idleWakeAfterSleep": true,
|
||||
"idleWhileMedia": true,
|
||||
"jellyAmount": 1,
|
||||
"jellyMotion": true,
|
||||
"kdeConnectEnabled": true,
|
||||
"launcherActionHints": true,
|
||||
"launcherAddressFirst": true,
|
||||
"launcherAppActions": true,
|
||||
"launcherAppDescriptions": true,
|
||||
"launcherCalculator": true,
|
||||
"launcherContentScale": 0.8,
|
||||
"launcherDensity": "compact",
|
||||
"launcherFrequentFirst": true,
|
||||
"launcherHiddenApps": "",
|
||||
"launcherModeBar": true,
|
||||
"launcherPowerButtons": "lock,suspend,reboot,shutdown",
|
||||
"launcherPowerChips": false,
|
||||
"launcherPowerSearch": true,
|
||||
"launcherSearchEngine": "duckduckgo",
|
||||
"launcherSearchPosition": "bottom",
|
||||
"launcherSettingsResults": true,
|
||||
"launcherWebRow": true,
|
||||
"launcherWidth": "wide",
|
||||
"launcherWindows": true,
|
||||
"liveDecoder": "cuda",
|
||||
"liveGameMode": "pause",
|
||||
"livePauseUnder": "any",
|
||||
"liveWallpapers": true,
|
||||
"locationLabel": "Nizhny Novgorod, Nizhny Novgorod Oblast, Russia",
|
||||
"locationLat": 56.32867,
|
||||
"locationLon": 44.00205,
|
||||
"locationName": "Nizhniy Novgorod",
|
||||
"locationTz": "Europe/Moscow",
|
||||
"matugenContrast": 0,
|
||||
"matugenScheme": "scheme-tonal-spot",
|
||||
"matugenSourceImage": "",
|
||||
"matugenSourceIndex": 0,
|
||||
"mediaArtist": true,
|
||||
"mediaHideIdle": true,
|
||||
"mediaPanelStyle": "side",
|
||||
"mediaPlayButton": true,
|
||||
"mediaStyle": "disc",
|
||||
"mediaTitleWidth": 300,
|
||||
"mediaWheelVolume": true,
|
||||
"monitorBarScreen": "",
|
||||
"monitorDockScreen": "",
|
||||
"monitorSetups": "{}",
|
||||
"monitorShellScreen": "",
|
||||
"monitorWorkspaces": "{}",
|
||||
"motionScale": 1,
|
||||
"nightLight": false,
|
||||
"nightLightFrom": 1260,
|
||||
"nightLightSchedule": "off",
|
||||
"nightLightTemp": 4000,
|
||||
"nightLightTo": 420,
|
||||
"notifGrouping": true,
|
||||
"notifInlineReply": true,
|
||||
"notifMaxHistory": 50,
|
||||
"notifMutedApps": "",
|
||||
"notifProgress": true,
|
||||
"notifSeenApps": "AyuGram Desktop,Clock,Hyprshot,Lucid test,starship,vesktop,Vivaldi,wallpaper_changer",
|
||||
"notifShowIcons": true,
|
||||
"notifSound": true,
|
||||
"notifSoundName": "pulse",
|
||||
"notifSoundUrgentOnly": false,
|
||||
"notifSoundVolume": 0.6,
|
||||
"notifTimestamps": true,
|
||||
"notificationsStyle": "badge",
|
||||
"osdCapsLock": true,
|
||||
"osdNumLock": true,
|
||||
"pinBadge": "top-right",
|
||||
"pinBorder": "tertiary",
|
||||
"pinPulse": true,
|
||||
"pomodoroAutoFocus": true,
|
||||
"pomodoroAutoStart": true,
|
||||
"pomodoroFocus": 25,
|
||||
"pomodoroGoal": 0,
|
||||
"pomodoroLong": 15,
|
||||
"pomodoroRepeat": true,
|
||||
"pomodoroRounds": 4,
|
||||
"pomodoroShort": 5,
|
||||
"pomodoroSilence": false,
|
||||
"powerModuleActions": "lock,suspend,hibernate,logout,reboot,shutdown",
|
||||
"powerModuleConfirm": true,
|
||||
"powerModulePanelStyle": "list",
|
||||
"powerModuleStyle": "icon",
|
||||
"powerModuleUptime": true,
|
||||
"privacyAlwaysShown": false,
|
||||
"privacyStyle": "marks",
|
||||
"privacyToast": true,
|
||||
"privacyWatch": "mic,camera,screen",
|
||||
"quietFrom": 1320,
|
||||
"quietHours": false,
|
||||
"quietTo": 420,
|
||||
"recordCountdown": 3,
|
||||
"recordEncoder": "auto",
|
||||
"recordFolder": "",
|
||||
"recordFps": 0,
|
||||
"reminderSound": true,
|
||||
"shellGap": 0,
|
||||
"shotFolder": "",
|
||||
"shotLoupe": true,
|
||||
"shotOcrLang": "eng+rus",
|
||||
"shotPreview": "preview",
|
||||
"shotPreviewSeconds": 6,
|
||||
"shotRoundWindows": true,
|
||||
"shotSaveFile": true,
|
||||
"showClock": true,
|
||||
"showKbLayout": true,
|
||||
"showMedia": true,
|
||||
"showNotifications": true,
|
||||
"showPower": true,
|
||||
"showPrivacy": true,
|
||||
"showStart": true,
|
||||
"showSystem": true,
|
||||
"showTray": true,
|
||||
"showWindow": true,
|
||||
"showWorkspaces": true,
|
||||
"soundOnBattery": true,
|
||||
"soundOnBluetooth": true,
|
||||
"soundOnBrightness": true,
|
||||
"soundOnCamera": true,
|
||||
"soundOnCaps": true,
|
||||
"soundOnCapture": true,
|
||||
"soundOnCharger": true,
|
||||
"soundOnMic": true,
|
||||
"soundOnUsb": true,
|
||||
"soundOnVolume": true,
|
||||
"specialBlur": false,
|
||||
"specialComms": true,
|
||||
"specialCommsApps": "auto",
|
||||
"specialCustom": "[]",
|
||||
"specialDim": 0.2,
|
||||
"specialGaps": 0,
|
||||
"specialHideOnSwitch": false,
|
||||
"specialKeepApps": true,
|
||||
"specialMusic": true,
|
||||
"specialMusicApps": "auto",
|
||||
"specialScratchpad": true,
|
||||
"specialSysmon": true,
|
||||
"specialSysmonApps": "auto",
|
||||
"specialTodo": true,
|
||||
"specialTodoApps": "auto",
|
||||
"startModuleStyle": "distro",
|
||||
"startupApps": "[]",
|
||||
"startupEnv": "[]",
|
||||
"startupXdg": false,
|
||||
"storageLowPercent": 10,
|
||||
"storageLowWarn": true,
|
||||
"storageTrashDays": 0,
|
||||
"surfaceDarkness": -1,
|
||||
"surfaceTint": -1,
|
||||
"sysSoundVolume": 0.6,
|
||||
"sysSounds": true,
|
||||
"systemTiles": "dnd,awake,dark,airplane,location,mic,capture,record,picker,keyboard,timer,session,gamemode",
|
||||
"themeColour": "#6750a4",
|
||||
"themeOrder": "",
|
||||
"timeZoneAuto": true,
|
||||
"timerSound": true,
|
||||
"toastBodyLines": 4,
|
||||
"toastCriticalSticky": true,
|
||||
"toastEnabled": true,
|
||||
"toastMaxVisible": 3,
|
||||
"toastOnAudio": true,
|
||||
"toastOnBattery": true,
|
||||
"toastOnBluetooth": true,
|
||||
"toastOnCamera": true,
|
||||
"toastOnDisplays": true,
|
||||
"toastOnGameMode": true,
|
||||
"toastOnLayout": true,
|
||||
"toastOnPower": true,
|
||||
"toastOnUsb": true,
|
||||
"toastOnWifi": true,
|
||||
"toastShowActions": true,
|
||||
"toastShowBody": true,
|
||||
"toastTimeout": 5,
|
||||
"toastUseAppTimeout": true,
|
||||
"trayHidden": "",
|
||||
"trayIconColor": "original",
|
||||
"trayStyle": "collapsed",
|
||||
"uiScale": 1.1,
|
||||
"units": "metric",
|
||||
"updateBeta": false,
|
||||
"updateCheck": true,
|
||||
"urgentBadge": "top-left",
|
||||
"urgentPulse": true,
|
||||
"wallTransAngle": 45,
|
||||
"wallTransBezier": ".54,0,.34,.99",
|
||||
"wallTransCustomPresets": "",
|
||||
"wallTransDuration": 1,
|
||||
"wallTransOrigin": "center",
|
||||
"wallTransPreset": "soft",
|
||||
"wallTransType": "fade",
|
||||
"wallTransWave": "20,20",
|
||||
"wallpaperBentoHero": "medium",
|
||||
"wallpaperBentoPrev": true,
|
||||
"wallpaperFolder": "/home/sweetbread/Wallpapers",
|
||||
"wallpaperPickerKeys": "sides",
|
||||
"wallpaperPickerStyle": "strip",
|
||||
"wallpaperPillsShape": "uniform",
|
||||
"wallpaperStripSides": 2,
|
||||
"wallpaperStripSteps": "normal",
|
||||
"wallpaperTilesAspect": "square",
|
||||
"wallpaperTilesRows": 2,
|
||||
"weekStartMonday": true,
|
||||
"widgetHideFullscreen": true,
|
||||
"widgetLockAll": false,
|
||||
"widgetOnTop": false,
|
||||
"widgetSnap": true,
|
||||
"widgetsEnabled": true,
|
||||
"windowModuleMiddleClose": false,
|
||||
"windowModuleScroll": true,
|
||||
"windowModuleText": "title",
|
||||
"windowModuleWidth": 260,
|
||||
"workspacesByDisplay": true,
|
||||
"workspacesShown": 1,
|
||||
"workspacesStyle": "numbers",
|
||||
"workspacesWheel": true
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import "./luciddocks"
|
||||
import "./lucidedit"
|
||||
import "./lucidosd"
|
||||
import "./lucidprefs"
|
||||
import "./lucidshot"
|
||||
import "./lucidwidgets"
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
Component.onCompleted: {
|
||||
void Clip.probed;
|
||||
void Capture.state;
|
||||
}
|
||||
|
||||
function announceShot(entry) {
|
||||
if (Prefs.shotPreview === "preview") {
|
||||
shotCards.show(entry);
|
||||
return;
|
||||
}
|
||||
if (Prefs.shotPreview !== "notify")
|
||||
return;
|
||||
const where = String(entry.file).replace(Quickshell.env("HOME"), "~");
|
||||
if (entry.kind === "video")
|
||||
Quickshell.execDetached(["notify-send", "-a", "LucidShot", "-i", "media-record", "Recording saved", where]);
|
||||
else
|
||||
Quickshell.execDetached(["sh", "-c", "a=$(notify-send -a LucidShot -i \"$1\" -A 'edit=Mark up' -A 'open=Open' --wait \"$2\" \"$3\"); case \"$a\" in edit) qs ipc call snap edit \"$1\" ;; open) xdg-open \"$1\" ;; esac; true", "sh", entry.file, entry.temp ? "Screenshot copied" : "Screenshot saved", entry.temp ? "It is on the clipboard only" : where]);
|
||||
}
|
||||
|
||||
ShotEngine {
|
||||
id: shotEngine
|
||||
cards: shotCards
|
||||
onReady: (entry) => {
|
||||
if (entry.owner === "overlay") {
|
||||
shotCards.update(entry);
|
||||
return;
|
||||
}
|
||||
if (entry.owner === "pin")
|
||||
return;
|
||||
root.announceShot(entry);
|
||||
}
|
||||
onFailed: (id, why) => toastMod.popup("warning", why, true)
|
||||
onTextRead: (r) => {
|
||||
if (!shotEngine.claim(r.id))
|
||||
return;
|
||||
if (r.status === "ok") {
|
||||
if (Prefs.shotPreview === "preview")
|
||||
shotCards.show({ "id": r.id, "kind": "text", "text": r.text, "codes": r.codes, "screen": Monitors.focusedScreen ? Monitors.focusedScreen.name : "" });
|
||||
else
|
||||
toastMod.popup("copy", "Text copied", false);
|
||||
} else if (r.status === "notool") {
|
||||
toastMod.popup("alert", "Install tesseract to copy text", true);
|
||||
} else {
|
||||
toastMod.popup("alert", "No text found", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShotOverlay {
|
||||
id: shotOverlay
|
||||
engine: shotEngine
|
||||
cards: shotCards
|
||||
pins: shotPins
|
||||
onNotify: (icon, text, warn) => toastMod.popup(icon, text, warn)
|
||||
onSwatch: (hex, text) => toastMod.popupSwatch(hex, text)
|
||||
onFreezeFailed: (why) => toastMod.popup("alert", "Couldn't capture the screen", true)
|
||||
}
|
||||
|
||||
ShotCards {
|
||||
id: shotCards
|
||||
engine: shotEngine
|
||||
pins: shotPins
|
||||
overlay: shotOverlay
|
||||
}
|
||||
|
||||
PinBoard {
|
||||
id: shotPins
|
||||
engine: shotEngine
|
||||
}
|
||||
|
||||
RecordHud {}
|
||||
|
||||
Connections {
|
||||
target: Capture
|
||||
function onSaved(file, seconds) {
|
||||
root.announceShot({ "id": "rec-" + Date.now(), "kind": "video", "file": file, "duration": seconds, "screen": Capture.output });
|
||||
}
|
||||
function onFailed(why) {
|
||||
toastMod.popup("warning", why, true);
|
||||
}
|
||||
}
|
||||
|
||||
LucidBar {
|
||||
id: barRoot
|
||||
dock: dockRoot
|
||||
toastMod: toastMod
|
||||
}
|
||||
|
||||
Dock {
|
||||
id: dockRoot
|
||||
}
|
||||
|
||||
Toast {
|
||||
id: toastMod
|
||||
}
|
||||
|
||||
Osd {
|
||||
}
|
||||
|
||||
ToastEvents {
|
||||
toast: toastMod
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: toastMod
|
||||
property: "screen"
|
||||
value: Monitors.shellPlacement
|
||||
when: Monitors.shellPlacement !== null
|
||||
}
|
||||
|
||||
WidgetLayer {
|
||||
}
|
||||
|
||||
WidgetIpc {
|
||||
}
|
||||
|
||||
EditLayer {
|
||||
}
|
||||
|
||||
SettingsHost {
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: Monitors
|
||||
property: "barWindows"
|
||||
value: barRoot.instances
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
{
|
||||
"nextId": 31,
|
||||
"boardFull": true,
|
||||
"laidScale": 1.1,
|
||||
"preset": "",
|
||||
"saved": [
|
||||
{
|
||||
"id": "user-mvzmbsn0",
|
||||
"name": "Mine",
|
||||
"refW": 3440,
|
||||
"refH": 1440,
|
||||
"refScale": 1.1,
|
||||
"saved": 1793656367208,
|
||||
"cards": [
|
||||
{
|
||||
"uid": "w1",
|
||||
"type": "clock",
|
||||
"variant": "poster",
|
||||
"at": "tr",
|
||||
"x": 3122,
|
||||
"y": 93,
|
||||
"w": 216,
|
||||
"h": 272,
|
||||
"zoom": 1.25,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"hourMode": "auto",
|
||||
"seconds": false,
|
||||
"dot": true,
|
||||
"showDate": true,
|
||||
"shape": "cookie9",
|
||||
"zones": "eu",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w2",
|
||||
"type": "system",
|
||||
"variant": "tiles",
|
||||
"at": "bl",
|
||||
"x": 20,
|
||||
"y": 1057,
|
||||
"w": 280,
|
||||
"h": 220,
|
||||
"zoom": 1.5,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"showCpu": true,
|
||||
"showRam": true,
|
||||
"showDisk": true,
|
||||
"showTemp": true,
|
||||
"showGpu": false,
|
||||
"interval": "5",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w3",
|
||||
"type": "progress",
|
||||
"variant": "dots",
|
||||
"at": "tr",
|
||||
"x": 3007,
|
||||
"y": 483,
|
||||
"w": 300,
|
||||
"h": 212,
|
||||
"zoom": 1.25,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"span": "day",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w4",
|
||||
"type": "network",
|
||||
"variant": "graph",
|
||||
"at": "bl",
|
||||
"x": 20,
|
||||
"y": 836,
|
||||
"w": 308,
|
||||
"h": 186,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"units": "bytes",
|
||||
"showAddress": true,
|
||||
"interval": "1",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w5",
|
||||
"type": "media",
|
||||
"variant": "card",
|
||||
"at": "tl",
|
||||
"x": 20,
|
||||
"y": 93,
|
||||
"w": 288,
|
||||
"h": 435,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"artTint": true,
|
||||
"showProgress": true,
|
||||
"scroll": true,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w7",
|
||||
"type": "games",
|
||||
"variant": "list",
|
||||
"at": "br",
|
||||
"x": 2925,
|
||||
"y": 958,
|
||||
"w": 300,
|
||||
"h": 280,
|
||||
"zoom": 1.5,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"order": "recent",
|
||||
"gameModeOnLaunch": false,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w8",
|
||||
"type": "kdeconnect",
|
||||
"variant": "remote",
|
||||
"at": "tr",
|
||||
"x": 2713,
|
||||
"y": 93,
|
||||
"w": 300,
|
||||
"h": 214,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"showShare": true,
|
||||
"showRing": true,
|
||||
"showClipboard": true,
|
||||
"showBrowse": true,
|
||||
"deviceId": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w9",
|
||||
"type": "fetch",
|
||||
"variant": "card",
|
||||
"at": "tl",
|
||||
"x": 375,
|
||||
"y": 105,
|
||||
"w": 330,
|
||||
"h": 344,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"last": [],
|
||||
"stash": [
|
||||
{
|
||||
"uid": "w19",
|
||||
"type": "notes",
|
||||
"variant": "sticky",
|
||||
"wx": 53,
|
||||
"wy": 515,
|
||||
"bw": 244,
|
||||
"bh": 244,
|
||||
"pinned": false,
|
||||
"zoom": 1.33,
|
||||
"zOrder": 66,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"tint": "tertiary",
|
||||
"edited": 0,
|
||||
"text": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"uid": "w24",
|
||||
"type": "clock",
|
||||
"variant": "poster",
|
||||
"wx": 3122,
|
||||
"wy": 93,
|
||||
"bw": 216,
|
||||
"bh": 272,
|
||||
"pinned": false,
|
||||
"zoom": 1.25,
|
||||
"zOrder": 80,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"hourMode": "auto",
|
||||
"seconds": false,
|
||||
"dot": true,
|
||||
"showDate": true,
|
||||
"shape": "cookie9",
|
||||
"zones": "eu",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w26",
|
||||
"type": "progress",
|
||||
"variant": "dots",
|
||||
"wx": 3007,
|
||||
"wy": 483,
|
||||
"bw": 300,
|
||||
"bh": 212,
|
||||
"pinned": false,
|
||||
"zoom": 1.25,
|
||||
"zOrder": 82,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"span": "day",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w2",
|
||||
"type": "system",
|
||||
"variant": "tiles",
|
||||
"wx": 20,
|
||||
"wy": 1057,
|
||||
"bw": 280,
|
||||
"bh": 220,
|
||||
"pinned": false,
|
||||
"zoom": 1.5,
|
||||
"zOrder": 81,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"showCpu": true,
|
||||
"showRam": true,
|
||||
"showDisk": true,
|
||||
"showTemp": true,
|
||||
"showGpu": false,
|
||||
"interval": "5",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w4",
|
||||
"type": "network",
|
||||
"variant": "graph",
|
||||
"wx": 20,
|
||||
"wy": 836,
|
||||
"bw": 308,
|
||||
"bh": 186,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 88,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"units": "bytes",
|
||||
"showAddress": true,
|
||||
"interval": "1",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w5",
|
||||
"type": "media",
|
||||
"variant": "card",
|
||||
"wx": 20,
|
||||
"wy": 93,
|
||||
"bw": 288,
|
||||
"bh": 435,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 89,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"artTint": true,
|
||||
"showProgress": true,
|
||||
"scroll": true,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w7",
|
||||
"type": "games",
|
||||
"variant": "list",
|
||||
"wx": 2925,
|
||||
"wy": 958,
|
||||
"bw": 300,
|
||||
"bh": 280,
|
||||
"pinned": false,
|
||||
"zoom": 1.5,
|
||||
"zOrder": 85,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"order": "recent",
|
||||
"gameModeOnLaunch": false,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w8",
|
||||
"type": "kdeconnect",
|
||||
"variant": "remote",
|
||||
"wx": 2713,
|
||||
"wy": 93,
|
||||
"bw": 300,
|
||||
"bh": 214,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 86,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"showShare": true,
|
||||
"showRing": true,
|
||||
"showClipboard": true,
|
||||
"showBrowse": true,
|
||||
"deviceId": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w9",
|
||||
"type": "fetch",
|
||||
"variant": "terminal",
|
||||
"wx": 353,
|
||||
"wy": 93,
|
||||
"bw": 380,
|
||||
"bh": 318,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 90,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"nextId": 3,
|
||||
"boardFull": true,
|
||||
"instances": [
|
||||
{
|
||||
"uid": "w1",
|
||||
"type": "clock",
|
||||
"variant": "digital",
|
||||
"wx": 40,
|
||||
"wy": 70
|
||||
},
|
||||
{
|
||||
"uid": "w2",
|
||||
"type": "system",
|
||||
"variant": "rings",
|
||||
"wx": 40,
|
||||
"wy": 250
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only bridge from Thunderbird's synchronized CalDAV event cache."""
|
||||
|
||||
import configparser
|
||||
import datetime as dt
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HOME = Path.home()
|
||||
DB_NAME = "cache.sqlite"
|
||||
WINDOW_START = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=30)
|
||||
WINDOW_END = dt.datetime.now(dt.timezone.utc) + dt.timedelta(days=400)
|
||||
|
||||
|
||||
def active_profile():
|
||||
ini = configparser.ConfigParser(interpolation=None)
|
||||
ini.read(HOME / ".thunderbird" / "profiles.ini", encoding="utf-8")
|
||||
for section in ini.sections():
|
||||
if not section.startswith("Profile") or ini.get(section, "Default", fallback="0") != "1":
|
||||
continue
|
||||
profile = Path(ini.get(section, "Path"))
|
||||
if ini.get(section, "IsRelative", fallback="1") == "1":
|
||||
profile = HOME / ".thunderbird" / profile
|
||||
return profile
|
||||
raise FileNotFoundError("No default Thunderbird profile")
|
||||
|
||||
|
||||
def calendar_registry(profile):
|
||||
prefs = profile / "prefs.js"
|
||||
pattern = re.compile(r'^user_pref\("calendar\.registry\.([^.]+)\.([^\"]+)",\s*(.*)\);$')
|
||||
registry = {}
|
||||
if not prefs.exists():
|
||||
return registry
|
||||
with prefs.open(encoding="utf-8", errors="replace") as source:
|
||||
for line in source:
|
||||
match = pattern.match(line.strip())
|
||||
if not match:
|
||||
continue
|
||||
calendar_id, key, raw = match.groups()
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
registry.setdefault(calendar_id, {})[key] = value
|
||||
return {
|
||||
calendar_id: data
|
||||
for calendar_id, data in registry.items()
|
||||
if data.get("type") == "caldav" and not data.get("disabled", False)
|
||||
}
|
||||
|
||||
|
||||
def stable_snapshot(database, target_dir):
|
||||
"""Copy DB and WAL together; retry if Thunderbird writes during the copy."""
|
||||
target = target_dir / DB_NAME
|
||||
sidecars = [Path(str(database) + suffix) for suffix in ("", "-wal", "-shm")]
|
||||
for _ in range(4):
|
||||
before = []
|
||||
for path in sidecars:
|
||||
try:
|
||||
stat = path.stat()
|
||||
before.append((stat.st_size, stat.st_mtime_ns))
|
||||
except FileNotFoundError:
|
||||
before.append(None)
|
||||
for path, state in zip(sidecars, before):
|
||||
dest = Path(str(target) + str(path)[len(str(database)):])
|
||||
if state is None:
|
||||
dest.unlink(missing_ok=True)
|
||||
else:
|
||||
shutil.copy2(path, dest)
|
||||
after = []
|
||||
for path in sidecars:
|
||||
try:
|
||||
stat = path.stat()
|
||||
after.append((stat.st_size, stat.st_mtime_ns))
|
||||
except FileNotFoundError:
|
||||
after.append(None)
|
||||
if before == after:
|
||||
return target
|
||||
time.sleep(0.05)
|
||||
raise RuntimeError("Thunderbird calendar cache changed during snapshot")
|
||||
|
||||
|
||||
def parse_until(value):
|
||||
if not value:
|
||||
return None
|
||||
value = value.strip()
|
||||
try:
|
||||
if re.fullmatch(r"\d{8}", value):
|
||||
return dt.datetime.strptime(value, "%Y%m%d").replace(tzinfo=dt.timezone.utc)
|
||||
return dt.datetime.strptime(value.rstrip("Z"), "%Y%m%dT%H%M%S").replace(tzinfo=dt.timezone.utc)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def parse_recurrence(text):
|
||||
rules = []
|
||||
excluded = set()
|
||||
for line in text.replace("\r", "").split("\n"):
|
||||
if line.startswith("RRULE:"):
|
||||
rules.append(dict(part.split("=", 1) for part in line[6:].split(";") if "=" in part))
|
||||
elif line.startswith("EXDATE") and ":" in line:
|
||||
values = line.split(":", 1)[1].split(",")
|
||||
for value in values:
|
||||
parsed = parse_until(value)
|
||||
if parsed:
|
||||
excluded.add(int(parsed.timestamp() * 1000))
|
||||
return rules, excluded
|
||||
|
||||
|
||||
def month_dates(year, month, base_day, rule):
|
||||
import calendar
|
||||
|
||||
month_days = int(calendar.monthrange(year, month)[1])
|
||||
if "BYMONTHDAY" in rule:
|
||||
days = []
|
||||
for raw in rule["BYMONTHDAY"].split(","):
|
||||
value = int(raw)
|
||||
day = value if value > 0 else month_days + value + 1
|
||||
if 1 <= day <= month_days:
|
||||
days.append(day)
|
||||
return sorted(set(days))
|
||||
if "BYDAY" not in rule:
|
||||
return [base_day] if base_day <= month_days else []
|
||||
|
||||
weekdays = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, "SU": 6}
|
||||
result = []
|
||||
for entry in rule["BYDAY"].split(","):
|
||||
match = re.fullmatch(r"([+-]?\d+)?(MO|TU|WE|TH|FR|SA|SU)", entry)
|
||||
if not match:
|
||||
continue
|
||||
ordinal, name = match.groups()
|
||||
weekday = weekdays[name]
|
||||
matching = [day for day in range(1, month_days + 1) if dt.date(year, month, day).weekday() == weekday]
|
||||
if ordinal:
|
||||
index = int(ordinal)
|
||||
if index < 0:
|
||||
index = len(matching) + index + 1
|
||||
if 1 <= index <= len(matching):
|
||||
result.append(matching[index - 1])
|
||||
else:
|
||||
result.extend(matching)
|
||||
return sorted(set(result))
|
||||
|
||||
|
||||
def occurrences(start, rule, excluded):
|
||||
freq = rule.get("FREQ", "").upper()
|
||||
interval = max(1, int(rule.get("INTERVAL", "1")))
|
||||
count_limit = int(rule["COUNT"]) if rule.get("COUNT", "").isdigit() else None
|
||||
until = parse_until(rule.get("UNTIL"))
|
||||
candidates = []
|
||||
base_date = start.date()
|
||||
|
||||
if freq == "DAILY":
|
||||
day = base_date
|
||||
while day <= WINDOW_END.date():
|
||||
delta = (day - base_date).days
|
||||
if delta % interval == 0:
|
||||
candidates.append(dt.datetime.combine(day, start.timetz()))
|
||||
day += dt.timedelta(days=1)
|
||||
elif freq == "WEEKLY":
|
||||
days = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, "SU": 6}
|
||||
weekdays = sorted({days[x] for x in rule.get("BYDAY", "").split(",") if x in days}) or [base_date.weekday()]
|
||||
week = base_date - dt.timedelta(days=base_date.weekday())
|
||||
while week <= WINDOW_END.date():
|
||||
weeks = (week - (base_date - dt.timedelta(days=base_date.weekday()))).days // 7
|
||||
if weeks % interval == 0:
|
||||
for weekday in weekdays:
|
||||
day = week + dt.timedelta(days=weekday)
|
||||
if day >= base_date:
|
||||
candidates.append(dt.datetime.combine(day, start.timetz()))
|
||||
week += dt.timedelta(days=7)
|
||||
elif freq == "MONTHLY":
|
||||
month_index = base_date.year * 12 + base_date.month - 1
|
||||
last_index = WINDOW_END.year * 12 + WINDOW_END.month - 1
|
||||
for index in range(month_index, last_index + 1, interval):
|
||||
year, month0 = divmod(index, 12)
|
||||
for day in month_dates(year, month0 + 1, base_date.day, rule):
|
||||
date = dt.date(year, month0 + 1, day)
|
||||
if date >= base_date:
|
||||
candidates.append(dt.datetime.combine(date, start.timetz()))
|
||||
elif freq == "YEARLY":
|
||||
for year in range(base_date.year, WINDOW_END.year + 1, interval):
|
||||
months = [int(x) for x in rule.get("BYMONTH", str(base_date.month)).split(",")]
|
||||
for month in months:
|
||||
for day in month_dates(year, month, base_date.day, rule):
|
||||
date = dt.date(year, month, day)
|
||||
if date >= base_date:
|
||||
candidates.append(dt.datetime.combine(date, start.timetz()))
|
||||
|
||||
candidates.sort()
|
||||
result = []
|
||||
for ordinal, occurrence in enumerate(candidates):
|
||||
if count_limit is not None and ordinal >= count_limit:
|
||||
break
|
||||
if until and occurrence > until:
|
||||
break
|
||||
stamp = int(occurrence.timestamp() * 1000)
|
||||
if WINDOW_START.timestamp() * 1000 <= stamp <= WINDOW_END.timestamp() * 1000 and stamp not in excluded:
|
||||
result.append(stamp)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
profile = active_profile()
|
||||
registry = calendar_registry(profile)
|
||||
database = profile / "calendar-data" / DB_NAME
|
||||
events = []
|
||||
if registry and database.exists():
|
||||
with tempfile.TemporaryDirectory(prefix="lucid-tb-calendar-") as td:
|
||||
snapshot = stable_snapshot(database, Path(td))
|
||||
db = sqlite3.connect(snapshot)
|
||||
db.row_factory = sqlite3.Row
|
||||
recurrence = {}
|
||||
for row in db.execute("SELECT cal_id, item_id, icalString FROM cal_recurrence"):
|
||||
recurrence[(row["cal_id"], row["item_id"])] = parse_recurrence(row["icalString"] or "")
|
||||
|
||||
rows = list(db.execute(
|
||||
"SELECT cal_id, id, title, event_start, event_end, recurrence_id, ical_status "
|
||||
"FROM cal_events WHERE event_start IS NOT NULL"
|
||||
))
|
||||
overrides = set()
|
||||
for row in rows:
|
||||
if row["recurrence_id"] is not None:
|
||||
try:
|
||||
overrides.add((row["cal_id"], row["id"], int(row["recurrence_id"])))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
for row in rows:
|
||||
cal_id = row["cal_id"]
|
||||
if cal_id not in registry or (row["ical_status"] or "").upper() == "CANCELLED":
|
||||
continue
|
||||
try:
|
||||
start_us = int(row["event_start"])
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
start_ms = start_us // 1000
|
||||
duration_ms = max(0, int(row["event_end"] or row["event_start"]) // 1000 - start_ms)
|
||||
title = (row["title"] or "(untitled event)").strip()
|
||||
cal_name = registry[cal_id].get("name") or "CalDAV"
|
||||
rule_data = recurrence.get((cal_id, row["id"]))
|
||||
starts = [start_ms]
|
||||
if rule_data:
|
||||
rules, excluded = rule_data
|
||||
if rules:
|
||||
base = dt.datetime.fromtimestamp(start_ms / 1000, dt.timezone.utc)
|
||||
starts = occurrences(base, rules[0], excluded)
|
||||
for occurrence_ms in starts:
|
||||
if (cal_id, row["id"], occurrence_ms * 1000) in overrides:
|
||||
continue
|
||||
if not (WINDOW_START.timestamp() * 1000 <= occurrence_ms <= WINDOW_END.timestamp() * 1000):
|
||||
continue
|
||||
events.append({
|
||||
"id": f"{cal_id}:{row['id']}:{occurrence_ms}",
|
||||
"name": title,
|
||||
"calendar": cal_name,
|
||||
"startMs": occurrence_ms,
|
||||
"endMs": occurrence_ms + duration_ms,
|
||||
})
|
||||
db.close()
|
||||
|
||||
events.sort(key=lambda event: (event["startMs"], event["name"].casefold()))
|
||||
print(json.dumps({"events": events}, ensure_ascii=False, separators=(",", ":")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
# Keep refresh failures out of Lucid's console: current calendar events
|
||||
# remain visible until the next successful sync.
|
||||
print(json.dumps({"events": [], "error": "Thunderbird calendar cache unavailable"}))
|
||||
+183
-3
@@ -1,17 +1,197 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
{ config, lib, osConfig, ... }: let
|
||||
hyprLua = import ./hyprland/lua.nix { inherit lib; };
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
accent = colors.${config.stylix.accent};
|
||||
in {
|
||||
services = {
|
||||
walker = {
|
||||
enable = true;
|
||||
systemd.enable = true;
|
||||
|
||||
theme = {
|
||||
name = "stylix";
|
||||
# Follow Walker's default CSS structure so provider layouts keep their styling.
|
||||
# https://github.com/abenz1267/walker/blob/master/resources/themes/default/style.css
|
||||
style = ''
|
||||
@define-color window_bg_color ${colors.base00};
|
||||
@define-color surface_color ${colors.base01};
|
||||
@define-color border_color ${colors.base03};
|
||||
@define-color accent_bg_color ${accent};
|
||||
@define-color theme_fg_color ${colors.base05};
|
||||
@define-color error_bg_color ${colors.base08};
|
||||
@define-color error_fg_color ${colors.base00};
|
||||
|
||||
* {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
popover {
|
||||
background: @surface_color;
|
||||
border: 1px solid @border_color;
|
||||
border-radius: 18px;
|
||||
padding: 10px;
|
||||
font-family: "${config.stylix.fonts.sansSerif.name}";
|
||||
font-size: ${toString config.stylix.fonts.sizes.applications}pt;
|
||||
}
|
||||
|
||||
.box-wrapper {
|
||||
box-shadow:
|
||||
0 19px 38px rgba(0, 0, 0, 0.3),
|
||||
0 15px 12px rgba(0, 0, 0, 0.22);
|
||||
background: @window_bg_color;
|
||||
border: 1px solid @border_color;
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
font-family: "${config.stylix.fonts.sansSerif.name}";
|
||||
font-size: ${toString config.stylix.fonts.sizes.applications}pt;
|
||||
}
|
||||
|
||||
.normal-icons { -gtk-icon-size: 16px; }
|
||||
.large-icons { -gtk-icon-size: 32px; }
|
||||
scrollbar { opacity: 0; }
|
||||
|
||||
.search-container { border-radius: 10px; }
|
||||
|
||||
.input {
|
||||
caret-color: @theme_fg_color;
|
||||
background: @surface_color;
|
||||
padding: 10px;
|
||||
color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.input placeholder { opacity: 0.5; }
|
||||
.input selection { background: ${colors.base02}; }
|
||||
|
||||
.preview-box,
|
||||
.elephant-hint,
|
||||
.placeholder,
|
||||
.list { color: @theme_fg_color; }
|
||||
|
||||
.item-box {
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.item-quick-activation {
|
||||
background: alpha(@accent_bg_color, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
child:selected .item-box,
|
||||
row:selected .item-box { background: alpha(@accent_bg_color, 0.25); }
|
||||
|
||||
.item-subtext {
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.providerlist .item-subtext {
|
||||
font-size: unset;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.item-image-text { font-size: 28px; }
|
||||
|
||||
.preview {
|
||||
border: 1px solid alpha(@accent_bg_color, 0.25);
|
||||
border-radius: 10px;
|
||||
color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.calc .item-text,
|
||||
.symbols .item-image,
|
||||
.todo.urgent { font-size: 24px; }
|
||||
|
||||
.todo.done .item-text-box { opacity: 0.25; }
|
||||
.todo.active { font-weight: bold; }
|
||||
.bluetooth.disconnected { opacity: 0.5; }
|
||||
.preview .large-icons { -gtk-icon-size: 64px; }
|
||||
|
||||
.keybinds {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid @border_color;
|
||||
font-size: 12px;
|
||||
color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.keybind-button { opacity: 0.5; }
|
||||
.keybind-button:hover { opacity: 0.75; }
|
||||
.keybind-bind {
|
||||
text-transform: lowercase;
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.keybind-label {
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @theme_fg_color;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 10px;
|
||||
background: @error_bg_color;
|
||||
color: @error_fg_color;
|
||||
}
|
||||
|
||||
:not(.calc).current { font-style: italic; }
|
||||
|
||||
.preview-content.archlinuxpkgs,
|
||||
.preview-content.dnfpackages,
|
||||
.preview-content.aptpackages {
|
||||
font-family: "${config.stylix.fonts.monospace.name}";
|
||||
}
|
||||
'' + lib.optionalString (config.home.username == "chest" && osConfig.networking.hostName == "Eclipse") ''
|
||||
/* Keep the existing layout; let the compositor blur the wallpaper. */
|
||||
window { background: transparent; }
|
||||
.box-wrapper {
|
||||
background: alpha(@surface_color, 0.62);
|
||||
border: 2px solid alpha(@accent_bg_color, 0.38);
|
||||
padding: 19px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.input {
|
||||
background: alpha(${colors.base02}, 0.42);
|
||||
border-radius: 10px;
|
||||
}
|
||||
popover {
|
||||
background: alpha(@surface_color, 0.86);
|
||||
border: 2px solid alpha(@accent_bg_color, 0.38);
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
/* Paint only the rounded item, never both its row and child. */
|
||||
child, row,
|
||||
child:hover, row:hover,
|
||||
child:selected, row:selected {
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
}
|
||||
child:hover .item-box,
|
||||
row:hover .item-box {
|
||||
background: alpha(@accent_bg_color, 0.12);
|
||||
}
|
||||
child:selected .item-box,
|
||||
row:selected .item-box {
|
||||
background: alpha(@accent_bg_color, 0.25);
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
elephant.enable = true;
|
||||
};
|
||||
|
||||
xdg.configFile = lib.optionalAttrs (config.home.username == "chest") {
|
||||
"elephant/desktopapplications.toml".text = ''
|
||||
history_when_empty = true
|
||||
'';
|
||||
};
|
||||
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
(hyprLua.bind "SUPER + D" (hyprLua.exec "walker"))
|
||||
(hyprLua.bind "SUPER + D"
|
||||
(if config.home.username == "sweetbread"
|
||||
then hyprLua.execLauncher "qs ipc call launcher toggle"
|
||||
else hyprLua.exec "walker"))
|
||||
];
|
||||
}
|
||||
@@ -1,4 +1,29 @@
|
||||
{ pkgs, config, lib, ... }: let
|
||||
{ pkgs, config, lib, osConfig, inputs, ... }: let
|
||||
mikuFrost = osConfig.networking.hostName == "Eclipse";
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
accent = colors.${config.stylix.accent};
|
||||
# Render the same Stylix templates, changing only native translucency and accent.
|
||||
kvantumConfig = config.lib.stylix.colors {
|
||||
template = builtins.readFile "${inputs.stylix}/modules/qt/kvconfig.mustache";
|
||||
extension = ".kvconfig";
|
||||
};
|
||||
kvantumSvg = config.lib.stylix.colors {
|
||||
template = builtins.readFile "${inputs.stylix}/modules/qt/kvantum.svg.mustache";
|
||||
extension = ".svg";
|
||||
};
|
||||
frostKvantum = pkgs.runCommandLocal "miku-frost-kvantum" {} ''
|
||||
directory="$out/share/Kvantum/Base16Kvantum"
|
||||
mkdir -p "$directory"
|
||||
cp ${kvantumConfig} "$directory/Base16Kvantum.kvconfig"
|
||||
cp ${kvantumSvg} "$directory/Base16Kvantum.svg"
|
||||
substituteInPlace "$directory/Base16Kvantum.kvconfig" \
|
||||
--replace-fail 'translucent_windows=false' 'translucent_windows=true' \
|
||||
--replace-fail 'blurring=false' 'blurring=true' \
|
||||
--replace-fail 'reduce_window_opacity=10' 'reduce_window_opacity=18' \
|
||||
--replace-fail 'highlight.color=${colors.base0E}' 'highlight.color=${accent}'
|
||||
substituteInPlace "$directory/Base16Kvantum.svg" \
|
||||
--replace-fail '${colors.base0D}' '${accent}'
|
||||
'';
|
||||
miku-cursor = pkgs.stdenv.mkDerivation {
|
||||
name = "Miku cursor";
|
||||
|
||||
@@ -86,10 +111,46 @@ in {
|
||||
hyprland.enable = false;
|
||||
waybar.enable = false;
|
||||
kitty.variant256Colors = true;
|
||||
gtk.extraCss = lib.mkIf mikuFrost ''
|
||||
/* Native background alpha keeps text and controls fully opaque. */
|
||||
@define-color accent_color ${accent};
|
||||
@define-color accent_bg_color ${accent};
|
||||
@define-color theme_selected_bg_color ${accent};
|
||||
@define-color window_bg_color alpha(${colors.base01}, 0.78);
|
||||
@define-color view_bg_color alpha(${colors.base00}, 0.48);
|
||||
@define-color headerbar_bg_color alpha(${colors.base01}, 0.76);
|
||||
@define-color headerbar_backdrop_color @window_bg_color;
|
||||
@define-color sidebar_bg_color alpha(${colors.base01}, 0.72);
|
||||
@define-color sidebar_backdrop_color @window_bg_color;
|
||||
@define-color dialog_bg_color alpha(${colors.base01}, 0.84);
|
||||
@define-color popover_bg_color alpha(${colors.base01}, 0.90);
|
||||
@define-color theme_bg_color @window_bg_color;
|
||||
@define-color theme_base_color @view_bg_color;
|
||||
'';
|
||||
};
|
||||
|
||||
base16Scheme = "${pkgs.base16-schemes}/share/themes/eris.yaml";
|
||||
accent = "base0E";
|
||||
# Keep a file-backed scheme for consumers such as Neofetch.
|
||||
base16Scheme = if mikuFrost then toString (pkgs.writeText "miku-frost.yaml" ''
|
||||
scheme: "Miku Frost"
|
||||
author: "chest"
|
||||
base00: "101724"
|
||||
base01: "192534"
|
||||
base02: "253247"
|
||||
base03: "47586B"
|
||||
base04: "93A8BA"
|
||||
base05: "E1EFF2"
|
||||
base06: "F1F8F8"
|
||||
base07: "F9FFFF"
|
||||
base08: "EF829A"
|
||||
base09: "F5AB82"
|
||||
base0A: "EED69A"
|
||||
base0B: "69DCC7"
|
||||
base0C: "43D6CD"
|
||||
base0D: "7BB9E8"
|
||||
base0E: "B29DF4"
|
||||
base0F: "E3A8D8"
|
||||
'') else "${pkgs.base16-schemes}/share/themes/eris.yaml";
|
||||
accent = if mikuFrost then "base0C" else "base0E";
|
||||
polarity = "dark";
|
||||
|
||||
image = pkgs.fetchurl {
|
||||
@@ -132,6 +193,59 @@ in {
|
||||
};
|
||||
};
|
||||
|
||||
qt.kvantum.themes = lib.mkIf mikuFrost (lib.mkForce [ frostKvantum ]);
|
||||
|
||||
# Notifications share the island's surface and rounded, borderless silhouette.
|
||||
services.mako.settings = lib.mkIf mikuFrost {
|
||||
background-color = lib.mkForce "${colors.base01}D9";
|
||||
text-color = lib.mkForce colors.base05;
|
||||
border-size = lib.mkForce 0;
|
||||
border-radius = 22;
|
||||
padding = "12";
|
||||
progress-color = lib.mkForce "over ${accent}33";
|
||||
"urgency=low".background-color = lib.mkForce "${colors.base01}D9";
|
||||
"urgency=critical" = {
|
||||
background-color = lib.mkForce "${colors.base01}F0";
|
||||
text-color = lib.mkForce colors.base08;
|
||||
};
|
||||
};
|
||||
|
||||
# Yazi uses its own true-colour theme rather than the terminal ANSI palette.
|
||||
programs.yazi.theme = lib.mkIf mikuFrost {
|
||||
filetype.rules = lib.mkBefore [
|
||||
{ url = "*/"; fg = colors.base05; bold = false; }
|
||||
];
|
||||
icon.conds = lib.mkBefore [
|
||||
{ "if" = "dir"; text = ""; fg = accent; }
|
||||
];
|
||||
mgr.border_style.fg = lib.mkForce colors.base03;
|
||||
indicator = {
|
||||
current = { fg = colors.base05; bg = lib.mkForce colors.base02; };
|
||||
preview = { fg = colors.base05; bg = lib.mkForce colors.base02; };
|
||||
};
|
||||
tabs = {
|
||||
active = { fg = lib.mkForce colors.base00; bg = lib.mkForce accent; };
|
||||
inactive = { fg = lib.mkForce colors.base04; bg = lib.mkForce colors.base01; };
|
||||
};
|
||||
mode = {
|
||||
normal_main = { fg = lib.mkForce colors.base00; bg = lib.mkForce accent; };
|
||||
normal_alt = { fg = lib.mkForce accent; bg = lib.mkForce colors.base01; };
|
||||
};
|
||||
input = {
|
||||
border.fg = lib.mkForce accent;
|
||||
selected.bg = lib.mkForce colors.base02;
|
||||
};
|
||||
pick.border.fg = lib.mkForce accent;
|
||||
completion.border.fg = lib.mkForce accent;
|
||||
tasks.border.fg = lib.mkForce accent;
|
||||
};
|
||||
|
||||
# Keep the original terminal glass, with colours inherited from Stylix.
|
||||
programs.ghostty.themes.stylix = lib.mkIf mikuFrost {
|
||||
background = lib.mkForce config.lib.stylix.colors.base00;
|
||||
cursor-color = lib.mkForce config.lib.stylix.colors.base0C;
|
||||
};
|
||||
|
||||
dconf.settings = {
|
||||
"org/gnome/desktop/interface".color-scheme = "prefer-dark";
|
||||
};
|
||||
|
||||
+132
-26
@@ -5,7 +5,9 @@
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
}: let
|
||||
mikuFrost = osConfig.networking.hostName == "Eclipse";
|
||||
in {
|
||||
home.packages = with pkgs; [
|
||||
font-awesome
|
||||
nerd-fonts.symbols-only
|
||||
@@ -17,7 +19,11 @@
|
||||
programs.waybar = {
|
||||
enable = true;
|
||||
systemd.enable = true;
|
||||
package = inputs.waybar.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
package = inputs.waybar.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs (old: {
|
||||
postPatch = (old.postPatch or "") + ''
|
||||
mv subprojects/cava-1.0.0 subprojects/libcava
|
||||
'';
|
||||
});
|
||||
|
||||
settings.mainBar = {
|
||||
modules-center = [
|
||||
@@ -55,11 +61,20 @@
|
||||
"pulseaudio"
|
||||
"custom/sep-left"
|
||||
"battery"
|
||||
] ++ lib.optional mikuFrost "custom/sep-battery" ++ [
|
||||
"clock"
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
"hyprland/workspaces" = lib.optionalAttrs mikuFrost {
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
tray = lib.optionalAttrs mikuFrost {
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
cava = {
|
||||
bars = 9;
|
||||
bar_delimiter = 0;
|
||||
@@ -75,6 +90,7 @@
|
||||
};
|
||||
|
||||
pulseaudio = {
|
||||
tooltip = !mikuFrost;
|
||||
format = "{icon} {volume}%";
|
||||
format-icons = {
|
||||
headphone = "";
|
||||
@@ -90,18 +106,20 @@
|
||||
};
|
||||
|
||||
mpris = {
|
||||
tooltip = !mikuFrost;
|
||||
format = "{dynamic}";
|
||||
dynamic-len = if osConfig.host.laptop then 16 else 32;
|
||||
dynamic-order = [ "title" "artist" "album" ];
|
||||
};
|
||||
|
||||
battery = {
|
||||
tooltip = !mikuFrost;
|
||||
interval = 5;
|
||||
states = {
|
||||
warning = 30;
|
||||
critical = 15;
|
||||
};
|
||||
format = "{icon} {capacity}% | ";
|
||||
format = if mikuFrost then "{icon} {capacity}%" else "{icon} {capacity}% | ";
|
||||
format-icons = {
|
||||
default = ["" "" "" "" "" "" "" "" "" "" ""];
|
||||
charging = ["" "" "" "" "" "" "" "" "" "" ""];
|
||||
@@ -113,7 +131,17 @@
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
# Follow battery visibility; the divider has no hover or click target.
|
||||
"custom/sep-battery" = {
|
||||
format = " {} ";
|
||||
exec = "printf '|'";
|
||||
tooltip = false;
|
||||
exec-if = "for battery in /sys/class/power_supply/*; do test \"$(cat \"$battery/type\" 2>/dev/null)\" = Battery && exit 0; done; exit 1";
|
||||
interval = 5;
|
||||
};
|
||||
|
||||
"keyboard-state" = {
|
||||
tooltip = !mikuFrost;
|
||||
capslock = true;
|
||||
format = "{icon}";
|
||||
format-icons = {
|
||||
@@ -123,6 +151,7 @@
|
||||
};
|
||||
|
||||
"hyprland/language" = {
|
||||
tooltip = !mikuFrost;
|
||||
format-en = "en";
|
||||
format-ru = "ru";
|
||||
};
|
||||
@@ -142,6 +171,7 @@
|
||||
};
|
||||
|
||||
network = {
|
||||
tooltip = !mikuFrost;
|
||||
format = "{ifname}";
|
||||
format-wifi = " {essid} ({signalStrength}%)";
|
||||
format-ethernet = "{ifname}";
|
||||
@@ -167,6 +197,7 @@
|
||||
};
|
||||
|
||||
cpu = {
|
||||
tooltip = !mikuFrost;
|
||||
interval = 2;
|
||||
format = "{usage}% ";
|
||||
min-length = 6;
|
||||
@@ -184,47 +215,97 @@
|
||||
style = let
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
accent = colors.${config.stylix.accent};
|
||||
themed = frost: original: if mikuFrost then frost else original;
|
||||
scssFile = pkgs.writeText "waybar.scss" /*scss*/ ''
|
||||
window#waybar {
|
||||
background: transparent;
|
||||
color: ${colors.base05};
|
||||
font-weight: bold;
|
||||
font-size: .85em;
|
||||
& > * { padding: 0 0 6px 0; }
|
||||
& > * { padding: ${themed "0 12px 14px" "0 0 6px 0"}; }
|
||||
}
|
||||
|
||||
${lib.optionalString mikuFrost /*scss*/ ''
|
||||
// Only the centered island paints a surface or a shadow.
|
||||
window#waybar,
|
||||
window#waybar > box {
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
''}
|
||||
|
||||
.modules-center {
|
||||
background: ${colors.base01}CC;
|
||||
border-radius: 12px;
|
||||
background: ${themed "rgba(${colors.base01}, 0.58)" "${colors.base01}CC"};
|
||||
border: none;
|
||||
border-radius: ${themed "22px" "12px"};
|
||||
padding: 10px;
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: ${themed "0 3px 8px rgba(0, 0, 0, 0.18)" "5px 5px 10px rgba(0, 0, 0, 0.4)"};
|
||||
}
|
||||
|
||||
#left,
|
||||
#right,
|
||||
#center {
|
||||
background: ${colors.base02};
|
||||
background: ${themed "rgba(40, 59, 79, 0.68)" colors.base02};
|
||||
padding: 0 2px;
|
||||
margin: 0 8px;
|
||||
border-radius: 8px;
|
||||
border-radius: ${themed "12px" "8px"};
|
||||
}
|
||||
#left { margin-left: 0; }
|
||||
#right { margin-right: 0; }
|
||||
|
||||
#clock { padding-right: 10px; }
|
||||
${lib.optionalString mikuFrost /*scss*/ ''
|
||||
// Keep hover surfaces inside the rounded groups.
|
||||
#language, #keyboard-state, #keyboard-state label,
|
||||
#cava, #mpris, #tray widget, #tray widget > image,
|
||||
#network, #custom-mem, #cpu, #temperature,
|
||||
#pulseaudio, #battery, #clock {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#cava { padding: 0 10px; }
|
||||
#network, #custom-mem, #cpu, #temperature,
|
||||
#pulseaudio, #battery, #clock {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
// Reserve the same width in active, inactive and hover states.
|
||||
#workspaces button {
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
tooltip {
|
||||
color: ${colors.base05};
|
||||
background: rgba(25, 37, 52, 0.94);
|
||||
border: 2px solid rgba(67, 214, 205, 0.38);
|
||||
border-radius: ${themed "12px" "8px"};
|
||||
}
|
||||
tooltip label { padding: 6px; }
|
||||
''}
|
||||
|
||||
#clock { padding-right: ${themed "6px" "10px"}; }
|
||||
|
||||
#cava { padding: 0 10px; color: ${themed accent colors.base05}; }
|
||||
|
||||
#custom-sep-left,
|
||||
#custom-sep-battery {
|
||||
color: ${themed "rgba(147, 168, 186, 0.38)" colors.base05};
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#language { padding: 8px; }
|
||||
|
||||
#workspaces button {
|
||||
color: ${colors.base05};
|
||||
color: ${themed colors.base04 colors.base05};
|
||||
padding: 0 2px;
|
||||
border-radius: 999px;
|
||||
border: 1pt solid transparent;
|
||||
min-width: 20px;
|
||||
min-width: ${themed "25px" "20px"};
|
||||
|
||||
&:hover { background: ${colors.base03}; }
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
|
||||
&.active {
|
||||
background: ${accent};
|
||||
@@ -232,27 +313,50 @@
|
||||
min-width: 25px;
|
||||
|
||||
&:hover {
|
||||
border-color: ${accent};
|
||||
background: ${colors.base03};
|
||||
border-color: ${themed "transparent" accent};
|
||||
background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03};
|
||||
color: ${accent};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#mpris {
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
&:hover { background: ${colors.base03}; }
|
||||
border-radius: ${themed "10px" "8px"};
|
||||
// Do not leave a styled placeholder when the player has no text.
|
||||
padding: ${themed "0" "4px"};
|
||||
border: none;
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
&.playing {
|
||||
border: 2px solid ${accent};
|
||||
padding: 2px;
|
||||
border: ${themed "none" "2px solid ${accent}"};
|
||||
padding: ${themed "0" "2px"};
|
||||
color: ${themed colors.base0E colors.base05};
|
||||
}
|
||||
}
|
||||
|
||||
${if mikuFrost then /*scss*/ ''
|
||||
// Each SNI item is one event box containing one image. Paint it once.
|
||||
#tray > widget {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#tray > widget:hover {
|
||||
background: rgba(67, 214, 205, 0.12);
|
||||
}
|
||||
#tray > widget > image {
|
||||
padding: 8px;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#tray > widget.needs-attention {
|
||||
background: rgba(245, 171, 130, 0.16);
|
||||
}
|
||||
'' else /*scss*/ ''
|
||||
#tray {
|
||||
widget {
|
||||
border: 1pt solid transparent;
|
||||
&:hover { background: ${colors.base03}; }
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
& > image { padding: 8px; }
|
||||
}
|
||||
|
||||
@@ -260,21 +364,23 @@
|
||||
& > .needs-attention { border-color: ${colors.base09}; }
|
||||
}
|
||||
|
||||
''}
|
||||
|
||||
#pulseaudio {
|
||||
&:hover { background: ${colors.base03}; }
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
&.muted {
|
||||
background: ${colors.base08};
|
||||
color: ${colors.base01};
|
||||
|
||||
&:hover {
|
||||
color: ${colors.base08};
|
||||
background: ${colors.base03};
|
||||
background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#network {
|
||||
&:hover { background: ${colors.base02}; }
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base02}; }
|
||||
&.disconnected {
|
||||
color: ${colors.base00};
|
||||
background: ${colors.base08};
|
||||
@@ -282,7 +388,7 @@
|
||||
}
|
||||
|
||||
#system .drawer-child > * {
|
||||
margin-right: 4px
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#keyboard-state label.locked {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{ pkgs, ... }: {
|
||||
imports = [ ./modules/lucid.nix ];
|
||||
|
||||
programs.hyprlock.enable = true;
|
||||
home.packages = with pkgs; [
|
||||
android-studio
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ pkgs, lib, config, osConfig, inputs, ... }: {
|
||||
{ pkgs, lib, config, osConfig, ... }: {
|
||||
wayland.windowManager.hyprland = let
|
||||
colors = config.lib.stylix.colors;
|
||||
accent = colors.${config.stylix.accent};
|
||||
@@ -10,7 +10,7 @@
|
||||
} /*py*/ ''
|
||||
import requests as requests
|
||||
from random import choice
|
||||
from os import listdir, makedirs, remove, replace, system
|
||||
from os import listdir, makedirs, remove, replace
|
||||
from os.path import exists
|
||||
from subprocess import run
|
||||
|
||||
@@ -123,7 +123,10 @@
|
||||
finally:
|
||||
if filename is not None:
|
||||
notify("Applying wallpaper", 100)
|
||||
system(f"awww img {folder}/{filename} --transition-type center")
|
||||
run([
|
||||
"${config.home.homeDirectory}/.config/hypr/scripts/wallpaper/set-wallpaper.sh",
|
||||
f"{folder}/{filename}",
|
||||
], check=False)
|
||||
'';
|
||||
in {
|
||||
settings = {
|
||||
@@ -188,10 +191,6 @@
|
||||
{ leaf = "workspaces"; enabled = true; speed = 6; bezier = "default"; }
|
||||
];
|
||||
|
||||
on = [
|
||||
(hyprLua.onStart [ "${lib.getExe wallpaper_changer}" ])
|
||||
];
|
||||
|
||||
workspace_rule = [
|
||||
{
|
||||
workspace = "2";
|
||||
@@ -200,10 +199,8 @@
|
||||
];
|
||||
|
||||
bind = [
|
||||
(hyprLua.bind "Print" (hyprLua.exec "${lib.getExe pkgs.hyprshot} -z -o ~/Screenshots -m active -m output"))
|
||||
(hyprLua.bind "CTRL + Print" (hyprLua.exec "${lib.getExe pkgs.hyprshot} -z -o ~/Screenshots -m region"))
|
||||
(hyprLua.bind "ALT + Print" (hyprLua.exec "${lib.getExe pkgs.hyprshot} -z -o ~/Screenshots -m active -m window"))
|
||||
(hyprLua.bind "SUPER + W" (hyprLua.exec "${lib.getExe wallpaper_changer}"))
|
||||
(hyprLua.bind "SUPER + W" (hyprLua.execLauncher "qs ipc call launcher wallpaper"))
|
||||
(hyprLua.bind "SUPER + SHIFT + W" (hyprLua.exec "${lib.getExe wallpaper_changer}"))
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
{ config, lib, osConfig, pkgs, ... }:
|
||||
let
|
||||
liveDecoder = if builtins.elem osConfig.networking.hostName [ "Rias" "Eclipse" ] then "cuda" else "vaapi";
|
||||
source = pkgs.fetchgit {
|
||||
url = "https://github.com/Sn3akyy1/lucid.git";
|
||||
rev = "bdd7f79da18efd837f79dcecbe5d2ec178abc10c";
|
||||
hash = "sha256-b6qylKjCGYy2Yo64i1w9g4oGrYgcXf502jY9qNNWhSI=";
|
||||
sparseCheckout = [
|
||||
"/*"
|
||||
"!/wallpapers/*"
|
||||
"!/assets/*.webp"
|
||||
];
|
||||
nonConeMode = true;
|
||||
};
|
||||
wallpaperDir = "${config.home.homeDirectory}/Wallpapers";
|
||||
wallpaperScript = "${config.home.homeDirectory}/.config/hypr/scripts/wallpaper/set-wallpaper.sh";
|
||||
widgetLayout = if osConfig.networking.hostName == "Rias"
|
||||
then ../../../modules/user/lucid-widgets-rias.json
|
||||
else ../../../modules/user/lucid-widgets.json;
|
||||
defaultWallpaper = "${wallpaperDir}/bluehour.jpg";
|
||||
hyprLua = import ../../../modules/user/hyprland/lua.nix { inherit lib; };
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
paletteFile = pkgs.runCommand "lucid-stylix-palette.json" {
|
||||
nativeBuildInputs = [ pkgs.python3 ];
|
||||
} ''
|
||||
python3 - "$out" ${lib.escapeShellArg config.stylix.base16Scheme} \
|
||||
${lib.escapeShellArg "${source}/support/lucid"} \
|
||||
${lib.escapeShellArg config.stylix.accent} <<'PY'
|
||||
import json
|
||||
import runpy
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
output, scheme_path, scripts, accent_key = sys.argv[1:]
|
||||
sys.path.insert(0, scripts)
|
||||
from lucid_palette import at_tone
|
||||
importer = runpy.run_path(str(Path(scripts) / "add-theme.py"))
|
||||
scheme = importer["parse_base16"](scheme_path, Path(scheme_path).read_text())
|
||||
if scheme is None:
|
||||
raise SystemExit("Stylix scheme is not valid Base16")
|
||||
bg, fg, accents, hints = importer["to_palette"](scheme)
|
||||
accent_key = accent_key.lower()
|
||||
hints["primary"] = scheme["colors"][accent_key]
|
||||
hints["secondary"] = scheme["colors"]["base0d" if accent_key != "base0d" else "base0e"]
|
||||
hints["tertiary"] = scheme["colors"]["base0c" if accent_key == "base0b" else "base0b"]
|
||||
palette = importer["build_palette"](
|
||||
bg, fg, accents, hints, mode="dark" if importer["is_dark"](bg) else "light"
|
||||
)
|
||||
if importer["is_dark"](bg):
|
||||
colors = scheme["colors"]
|
||||
# Keep the Base16 dark surface ladder. Lucid's built-in Mocha uses base00
|
||||
# for its main panels, with base01/base02 as the raised surfaces; the
|
||||
# generic Material builder starts at base02 and makes the whole shell pale.
|
||||
background_tone = importer["tone"](bg)
|
||||
darkest = importer["hx"](at_tone(bg, background_tone - 4))
|
||||
near_background = importer["hx"](at_tone(bg, background_tone - 2))
|
||||
palette.update({
|
||||
"surface": colors["base00"],
|
||||
"surface_dim": darkest,
|
||||
"surface_bright": importer["hx"](at_tone(bg, background_tone + 8)),
|
||||
"surface_container_lowest": darkest,
|
||||
"surface_container_low": near_background,
|
||||
"surface_container": colors["base00"],
|
||||
"surface_container_high": colors["base01"],
|
||||
"surface_container_highest": colors["base02"],
|
||||
"surface_variant": colors["base01"],
|
||||
"on_surface_variant": colors["base04"],
|
||||
"outline": colors["base03"],
|
||||
"outline_variant": colors["base01"],
|
||||
})
|
||||
Path(output).write_text(json.dumps(palette, indent=2) + "\n")
|
||||
PY
|
||||
'';
|
||||
themeMeta = pkgs.writeText "lucid-stylix-meta.json" (builtins.toJSON {
|
||||
id = "stylix";
|
||||
name = "Stylix";
|
||||
desc = "Active Stylix palette";
|
||||
swatchBg = colors.base00;
|
||||
swatchAccent = colors.${config.stylix.accent};
|
||||
source = "Home Manager";
|
||||
detected = "base16";
|
||||
user = true;
|
||||
});
|
||||
themeName = pkgs.writeText "lucid-current-theme" "stylix\n";
|
||||
applyTheme = pkgs.writeShellScript "lucid-apply-theme" ''
|
||||
exec "$HOME/.config/lucid/apply-theme-upstream.sh" --shell-only "$@"
|
||||
'';
|
||||
qmlPackages = with pkgs.qt6; [ qt5compat qtmultimedia ];
|
||||
tesseract = pkgs.tesseract4.override { enableLanguages = [ "eng" "rus" ]; };
|
||||
lucid = pkgs.writeShellScript "lucid" ''
|
||||
export NIXPKGS_QT6_QML_IMPORT_PATH="${lib.makeSearchPath "lib/qt-6/qml" qmlPackages}:''${NIXPKGS_QT6_QML_IMPORT_PATH:-}"
|
||||
export QT_PLUGIN_PATH="${lib.makeSearchPath "lib/qt-6/plugins" qmlPackages}:''${QT_PLUGIN_PATH:-}"
|
||||
export PATH="${lib.makeBinPath (with pkgs; [ bash cliphist coreutils curl ffmpeg grim imagemagick jq libnotify pulseaudio python3 wf-recorder wl-clipboard xdg-utils ])}:${tesseract}/bin:$PATH"
|
||||
exec ${pkgs.quickshell}/bin/qs --no-duplicate --path "${config.home.homeDirectory}/.config/quickshell"
|
||||
'';
|
||||
startWallpaper = pkgs.writeShellScript "lucid-wallpaper-start" ''
|
||||
export PATH="${lib.makeBinPath (with pkgs; [ awww bash coreutils ffmpeg imagemagick jq util-linux ])}:$PATH"
|
||||
selected="${defaultWallpaper}"
|
||||
if [ -s "${config.home.homeDirectory}/.cache/current_live_wallpaper" ]; then
|
||||
saved=$(${pkgs.coreutils}/bin/head -n1 "${config.home.homeDirectory}/.cache/current_live_wallpaper")
|
||||
if [ -f "$saved" ]; then
|
||||
selected="$saved"
|
||||
fi
|
||||
fi
|
||||
if [ -s "${config.home.homeDirectory}/.cache/current_wallpaper" ]; then
|
||||
saved=$(${pkgs.coreutils}/bin/cat "${config.home.homeDirectory}/.cache/current_wallpaper")
|
||||
if [ "$selected" = "${defaultWallpaper}" ] && [ -f "$saved" ]; then
|
||||
selected="$saved"
|
||||
fi
|
||||
fi
|
||||
if [ ! -f "$selected" ]; then
|
||||
echo "Lucid startup wallpaper not found; leaving the current wallpaper unchanged" >&2
|
||||
exit 0
|
||||
fi
|
||||
exec "${wallpaperScript}" "$selected"
|
||||
'';
|
||||
in {
|
||||
programs.waybar.enable = lib.mkForce false;
|
||||
services.mako.enable = lib.mkForce false;
|
||||
home.packages = with pkgs; [ cliphist ffmpeg grim imagemagick libnotify wf-recorder tesseract ];
|
||||
|
||||
home.activation.installLucid = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
target="${config.home.homeDirectory}/.config/quickshell"
|
||||
$DRY_RUN_CMD mkdir -p "$target"
|
||||
$DRY_RUN_CMD ${pkgs.rsync}/bin/rsync -rltp --chmod=Du+rwx,Fu+rw \
|
||||
--exclude='/shell.qml' \
|
||||
--exclude='/LucidBar.qml' \
|
||||
--exclude='/lucidprefs/prefs.json' \
|
||||
--exclude='/lucidbar/blur.json' \
|
||||
--exclude='/lucidbar/clock_reminders.json' \
|
||||
--exclude='/lucidbar/mpris_shazam.json' \
|
||||
--exclude='/luciddocks/pinned.json' \
|
||||
--exclude='/luciddocks/usage.json' \
|
||||
--exclude='/luciddocks/wallpaper.json' \
|
||||
--exclude='/lucidmoji/config.json' \
|
||||
--exclude='/lucidmoji/state.json' \
|
||||
--exclude='/lucidkeys/state.json' \
|
||||
--exclude='/lucidwidgets/widgets.json' \
|
||||
"${source}/" "$target/"
|
||||
dock_qml="$target/luciddocks/Dock.qml"
|
||||
if grep -Fq '/var/lib/snapd/desktop/applications; do' "$dock_qml"; then
|
||||
$DRY_RUN_CMD sed -i \
|
||||
's#/var/lib/snapd/desktop/applications; do#/var/lib/snapd/desktop/applications $HOME/.nix-profile/share/applications $HOME/.local/state/nix/profile/share/applications /etc/profiles/per-user/$USER/share/applications /nix/profile/share/applications /nix/var/nix/profiles/default/share/applications /run/current-system/sw/share/applications; do#' \
|
||||
"$dock_qml"
|
||||
elif ! grep -Fq '/etc/profiles/per-user/$USER/share/applications; do' "$dock_qml"; then
|
||||
echo "Lucid launcher application scan pattern changed; refusing to skip Nix profiles" >&2
|
||||
exit 1
|
||||
fi
|
||||
keyboard_focus='WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None'
|
||||
keyboard_focus_fixed='WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None'
|
||||
if grep -Fq "$keyboard_focus" "$dock_qml"; then
|
||||
$DRY_RUN_CMD sed -i \
|
||||
's/WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None/WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None/' \
|
||||
"$dock_qml"
|
||||
elif ! grep -Fq "$keyboard_focus_fixed" "$dock_qml"; then
|
||||
echo "Lucid launcher keyboard focus pattern changed; refusing to skip focus fix" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq 'Nix focus workaround for Hyprland 0.57' "$dock_qml"; then
|
||||
if [ "$(grep -Fxc ' onMenuOpenChanged: {' "$dock_qml")" -ne 1 ]; then
|
||||
echo "Lucid menu-open handler changed; refusing to skip focus workaround" >&2
|
||||
exit 1
|
||||
fi
|
||||
$DRY_RUN_CMD sed -i '/ onMenuOpenChanged: {/a\
|
||||
// Nix focus workaround for Hyprland 0.57: detach pointer and keyboard focus while menus are open.\
|
||||
Quickshell.execDetached(["hyprctl", "eval", "hl.config({ input = { follow_mouse = " + (dockWindow.menuOpen ? 2 : 1) + " } })"]);' "$dock_qml"
|
||||
fi
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/lucid-shell.qml} "$target/shell.qml"
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/LucidBar.qml} "$target/LucidBar.qml"
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/Agenda.qml} "$target/Agenda.qml"
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/CalendarSource.qml} "$target/CalendarSource.qml"
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/ClockCalendar.qml} "$target/lucidbar/ClockCalendar.qml"
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/thunderbird-calendar.py} "$target/thunderbird-calendar.py"
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/WidgetLayer.qml} "$target/lucidwidgets/WidgetLayer.qml"
|
||||
$DRY_RUN_CMD mkdir -p "${wallpaperDir}" \
|
||||
"${config.home.homeDirectory}/.config/hypr/scripts/wallpaper"
|
||||
$DRY_RUN_CMD install -m755 "${source}/support/wallpaper/set-wallpaper.sh" \
|
||||
"${wallpaperScript}"
|
||||
|
||||
lucid_dir="${config.home.homeDirectory}/.config/lucid"
|
||||
$DRY_RUN_CMD mkdir -p "$lucid_dir/themes"
|
||||
$DRY_RUN_CMD ${pkgs.rsync}/bin/rsync -rltp --chmod=Du+rwx,Fu+rw --ignore-existing \
|
||||
"${source}/support/lucid/themes/" "$lucid_dir/themes/"
|
||||
for script in palette-edit.py lucid_palette.py add-theme.py scheme-gallery.py gen-light-palette.py; do
|
||||
$DRY_RUN_CMD install -m644 "${source}/support/lucid/$script" "$lucid_dir/$script"
|
||||
done
|
||||
$DRY_RUN_CMD install -m755 "${source}/support/lucid/apply-theme.sh" \
|
||||
"$lucid_dir/apply-theme-upstream.sh"
|
||||
$DRY_RUN_CMD install -m755 ${applyTheme} \
|
||||
"$lucid_dir/apply-theme.sh"
|
||||
|
||||
if [ ! -e "$target/lucidprefs/prefs.json" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/lucid-prefs.json} "$target/lucidprefs/prefs.json"
|
||||
fi
|
||||
if [ -z "''${DRY_RUN_CMD:-}" ] && [ -f "$target/lucidprefs/prefs.json" ]; then
|
||||
prefs_tmp=$(${pkgs.coreutils}/bin/mktemp "$target/lucidprefs/prefs.json.XXXXXX")
|
||||
if ${pkgs.jq}/bin/jq --arg font ${lib.escapeShellArg config.stylix.fonts.sansSerif.name} \
|
||||
--arg wallpaperDir ${lib.escapeShellArg wallpaperDir} \
|
||||
--arg decoder ${lib.escapeShellArg liveDecoder} \
|
||||
'.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .wallpaperFolder = $wallpaperDir | .liveDecoder = $decoder' \
|
||||
"$target/lucidprefs/prefs.json" > "$prefs_tmp"; then
|
||||
${pkgs.coreutils}/bin/chmod 644 "$prefs_tmp"
|
||||
${pkgs.coreutils}/bin/mv "$prefs_tmp" "$target/lucidprefs/prefs.json"
|
||||
else
|
||||
${pkgs.coreutils}/bin/rm -f "$prefs_tmp"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ ! -e "$target/luciddocks/pinned.json" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${../../../modules/user/lucid-pinned.json} "$target/luciddocks/pinned.json"
|
||||
fi
|
||||
if [ ! -e "$target/lucidwidgets/widgets.json" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${widgetLayout} "$target/lucidwidgets/widgets.json"
|
||||
fi
|
||||
|
||||
for pair in \
|
||||
'blur.json lucidbar/blur.json' \
|
||||
'clock_reminders.json lucidbar/clock_reminders.json' \
|
||||
'mpris_shazam.json lucidbar/mpris_shazam.json' \
|
||||
'usage.json luciddocks/usage.json' \
|
||||
'wallpaper.json luciddocks/wallpaper.json' \
|
||||
'moji-config.json lucidmoji/config.json' \
|
||||
'moji-state.json lucidmoji/state.json' \
|
||||
'keys-state.json lucidkeys/state.json'; do
|
||||
set -- $pair
|
||||
if [ ! -e "$target/$2" ]; then
|
||||
$DRY_RUN_CMD install -m644 "${source}/defaults/$1" "$target/$2"
|
||||
fi
|
||||
done
|
||||
|
||||
$DRY_RUN_CMD mkdir -p "${config.home.homeDirectory}/.config/lucid/themes/stylix" \
|
||||
"${config.home.homeDirectory}/.cache/quickshell"
|
||||
$DRY_RUN_CMD install -m644 ${paletteFile} \
|
||||
"${config.home.homeDirectory}/.config/lucid/themes/stylix/quickshell.json"
|
||||
$DRY_RUN_CMD install -m644 ${themeMeta} \
|
||||
"${config.home.homeDirectory}/.config/lucid/themes/stylix/meta.json"
|
||||
if [ ! -e "${config.home.homeDirectory}/.cache/current_theme" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${themeName} \
|
||||
"${config.home.homeDirectory}/.cache/current_theme"
|
||||
fi
|
||||
if [ ! -e "${config.home.homeDirectory}/.cache/current_theme" ] || \
|
||||
[ "$(${pkgs.coreutils}/bin/cat "${config.home.homeDirectory}/.cache/current_theme")" = stylix ]; then
|
||||
$DRY_RUN_CMD install -m644 ${paletteFile} \
|
||||
"${config.home.homeDirectory}/.cache/quickshell/matugen.json"
|
||||
fi
|
||||
'';
|
||||
|
||||
systemd.user.services.lucid = {
|
||||
Unit = {
|
||||
Description = "Lucid bar, dock and desktop widgets";
|
||||
After = [ "graphical-session.target" ];
|
||||
PartOf = [ "graphical-session.target" ];
|
||||
};
|
||||
Service = {
|
||||
ExecStartPre = "${startWallpaper}";
|
||||
ExecStart = "${lucid}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
};
|
||||
Install.WantedBy = [ "hyprland-session.target" ];
|
||||
};
|
||||
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
on = [ (hyprLua.onStart [ "systemctl --user start lucid.service" ]) ];
|
||||
bind = [
|
||||
(hyprLua.bind "SUPER + SHIFT + G" (hyprLua.exec "qs ipc call widgets panel"))
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -22,7 +22,11 @@
|
||||
in {
|
||||
enable = true;
|
||||
systemd.enable = true;
|
||||
package = inputs.waybar.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
package = inputs.waybar.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs (old: {
|
||||
postPatch = (old.postPatch or "") + ''
|
||||
mv subprojects/cava-1.0.0 subprojects/libcava
|
||||
'';
|
||||
});
|
||||
|
||||
settings.mainBar = {
|
||||
spacing = 8;
|
||||
|
||||
Reference in new issue
Block a user