diff options
Diffstat (limited to '')
243 files changed, 13230 insertions, 5290 deletions
diff --git a/.envrc b/.envrc index 294de504..f1c78da8 100644 --- a/.envrc +++ b/.envrc @@ -11,3 +11,5 @@ # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. use flake + +PATH_add ./scripts diff --git a/flake.lock b/flake.lock index 82daaa1a..65390eb9 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1750173260, - "narHash": "sha256-9P1FziAwl5+3edkfFcr5HeGtQUtrSdk/MksX39GieoA=", + "lastModified": 1762618334, + "narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=", "owner": "ryantm", "repo": "agenix", - "rev": "531beac616433bac6f9e2a19feb8e99a22a66baf", + "rev": "fcdea223397448d35d9b31f798479227e80183f6", "type": "github" }, "original": { @@ -31,22 +31,31 @@ }, "beautysh": { "inputs": { + "flake-parts": [ + "flake-parts" + ], + "git-hooks-nix": [ + "git-hooks" + ], + "nix-github-actions": [ + "nix-github-actions" + ], "nixpkgs": [ "nixpkgs" ], - "poetry2nix": [ - "poetry2nix" + "pyproject-build-systems": "pyproject-build-systems", + "pyproject-nix": "pyproject-nix", + "treefmt-nix": [ + "treefmt-nix" ], - "utils": [ - "flake-utils" - ] + "uv2nix": "uv2nix" }, "locked": { - "lastModified": 1680308980, - "narHash": "sha256-aUEHV0jk2qIFP3jlsWYWhBbm+w/N9gzH3e4I5DcdB5s=", + "lastModified": 1764601770, + "narHash": "sha256-jM4ChpHM/YTr2YIL5uZ1j4K8SoSrQUSJed2g9Z9GbVY=", "owner": "lovesegfault", "repo": "beautysh", - "rev": "9845efc3ea3e86cc0d41465d720a47f521b2799c", + "rev": "18907582aeb81b3aa0457183f42e95e16ab68c92", "type": "github" }, "original": { @@ -57,11 +66,11 @@ }, "crane": { "locked": { - "lastModified": 1750266157, - "narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=", + "lastModified": 1768700043, + "narHash": "sha256-rfs2aP+wdueJZ6uABaj0e0PavQyzkRJuJX30HNcBPTg=", "owner": "ipetkov", "repo": "crane", - "rev": "e37c943371b73ed87faf33f7583860f81f1d5a48", + "rev": "935de8bd6838d940988bb065be2a2034259327b9", "type": "github" }, "original": { @@ -77,11 +86,11 @@ ] }, "locked": { - "lastModified": 1741473158, - "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", + "lastModified": 1764011051, + "narHash": "sha256-M7SZyPZiqZUR/EiiBJnmyUbOi5oE/03tCeFrTiUZchI=", "owner": "numtide", "repo": "devshell", - "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", + "rev": "17ed8d9744ebe70424659b0ef74ad6d41fc87071", "type": "github" }, "original": { @@ -97,11 +106,11 @@ ] }, "locked": { - "lastModified": 1750680230, - "narHash": "sha256-kD88T/NqmcgfOBFAwphN30ccaUdj6K6+LG0XdM2w2LA=", + "lastModified": 1768727946, + "narHash": "sha256-le2GY+ZR6uRHMuOAc60sBR3gBD2BEk1qOZ3S5C/XFpU=", "owner": "nix-community", "repo": "disko", - "rev": "8fd2d6c75009ac75f9a6fb18c33a239806778d01", + "rev": "558e84658d0eafc812497542ad6ca0d9654b3b0f", "type": "github" }, "original": { @@ -113,11 +122,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -133,11 +142,11 @@ ] }, "locked": { - "lastModified": 1749398372, - "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=", + "lastModified": 1768135262, + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569", + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", "type": "github" }, "original": { @@ -179,11 +188,11 @@ ] }, "locked": { - "lastModified": 1750684550, - "narHash": "sha256-uLtw0iF9mQ94L831NOlQLPX9wm0qzd5yim3rcwACEoM=", + "lastModified": 1767281941, + "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "fae816c55a75675f30d18c9cbdecc13b970d95d4", + "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", "type": "github" }, "original": { @@ -199,11 +208,11 @@ ] }, "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "lastModified": 1762808025, + "narHash": "sha256-XmjITeZNMTQXGhhww6ed/Wacy2KzD6svioyCX7pkUu4=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", "type": "github" }, "original": { @@ -239,11 +248,11 @@ ] }, "locked": { - "lastModified": 1750730235, - "narHash": "sha256-rZErlxiV7ssvI8t7sPrKU+fRigNc2KvoKZG3gtUtK50=", + "lastModified": 1768707181, + "narHash": "sha256-GdwFfnwdUgABFpc4sAmX7GYx8eQs6cEjOPo6nBJ0YaI=", "owner": "nix-community", "repo": "home-manager", - "rev": "d07e9cceb4994ed64a22b9b36f8b76923e87ac38", + "rev": "83bcb17377f0242376a327e742e9404e9a528647", "type": "github" }, "original": { @@ -254,12 +263,20 @@ } }, "impermanence": { + "inputs": { + "home-manager": [ + "home-manager" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1737831083, - "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", + "lastModified": 1768741755, + "narHash": "sha256-plWg64Pku30ZDY0MV2wMlHdZbrqLeQLdCwF3LKJ2aig=", "owner": "nix-community", "repo": "impermanence", - "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", + "rev": "5eed14a23246e1a6e008374e4df758c10187a972", "type": "github" }, "original": { @@ -280,16 +297,16 @@ ] }, "locked": { - "lastModified": 1748294338, - "narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=", + "lastModified": 1754860581, + "narHash": "sha256-EM0IE63OHxXCOpDHXaTyHIOk2cNvMCGPqLt/IdtVxgk=", "owner": "NuschtOS", "repo": "ixx", - "rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85", + "rev": "babfe85a876162c4acc9ab6fb4483df88fa1f281", "type": "github" }, "original": { "owner": "NuschtOS", - "ref": "v0.0.8", + "ref": "v0.1.1", "repo": "ixx", "type": "github" } @@ -299,16 +316,10 @@ "crane": [ "crane" ], - "flake-compat": [ - "flake-compat" - ], - "flake-parts": [ - "flake-parts" - ], "nixpkgs": [ "nixpkgs-stable" ], - "pre-commit-hooks-nix": [ + "pre-commit": [ "pre-commit-hooks" ], "rust-overlay": [ @@ -316,16 +327,16 @@ ] }, "locked": { - "lastModified": 1737639419, - "narHash": "sha256-AEEDktApTEZ5PZXNDkry2YV2k6t0dTgLPEmAZbnigXU=", + "lastModified": 1765382359, + "narHash": "sha256-RJmgVDzjRI18BWVogG6wpsl1UCuV6ui8qr4DJ1LfWZ8=", "owner": "nix-community", "repo": "lanzaboote", - "rev": "a65905a09e2c43ff63be8c0e86a93712361f871e", + "rev": "e8c096ade12ec9130ff931b0f0e25d2f1bc63607", "type": "github" }, "original": { "owner": "nix-community", - "ref": "v0.4.2", + "ref": "v1.0.0", "repo": "lanzaboote", "type": "github" } @@ -353,11 +364,11 @@ ] }, "locked": { - "lastModified": 1750618568, - "narHash": "sha256-w9EG5FOXrjXGfbqCcQg9x1lMnTwzNDW5BMXp8ddy15E=", + "lastModified": 1768561867, + "narHash": "sha256-prGOZ+w3pZfGTRxworKcJliCNsewF0L4HUPjgU/6eaw=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "1dd19f19e4b53a1fd2e8e738a08dd5fe635ec7e5", + "rev": "8b720b9662d4dd19048664b7e4216ce530591adc", "type": "github" }, "original": { @@ -369,16 +380,15 @@ "nix-github-actions": { "inputs": { "nixpkgs": [ - "poetry2nix", "nixpkgs" ] }, "locked": { - "lastModified": 1729742964, - "narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=", + "lastModified": 1737420293, + "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", "owner": "nix-community", "repo": "nix-github-actions", - "rev": "e04df33f62cdcf93d73e9a04142464753a16db67", + "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", "type": "github" }, "original": { @@ -394,11 +404,11 @@ ] }, "locked": { - "lastModified": 1750565152, - "narHash": "sha256-A6ZIoIgaPPkzIVxKuaxwEJicPOeTwC/MD9iuC3FVhDM=", + "lastModified": 1765267181, + "narHash": "sha256-d3NBA9zEtBu2JFMnTBqWj7Tmi7R5OikoU2ycrdhQEws=", "owner": "nix-community", "repo": "nix-index-database", - "rev": "78cd697acc2e492b4e92822a4913ffad279c20e6", + "rev": "82befcf7dc77c909b0f2a09f5da910ec95c5b78f", "type": "github" }, "original": { @@ -417,11 +427,11 @@ ] }, "locked": { - "lastModified": 1747663185, - "narHash": "sha256-Obh50J+O9jhUM/FgXtI3he/QRNiV9+J53+l+RlKSaAk=", + "lastModified": 1764234087, + "narHash": "sha256-NHF7QWa0ZPT8hsJrvijREW3+nifmF2rTXgS2v0tpcEA=", "owner": "nix-community", "repo": "nixos-generators", - "rev": "ee07ba0d36c38e9915c55d2ac5a8fb0f05f2afcc", + "rev": "032a1878682fafe829edfcf5fdfad635a2efe748", "type": "github" }, "original": { @@ -432,27 +442,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750506804, - "narHash": "sha256-VLFNc4egNjovYVxDGyBYTrvVCgDYgENp5bVi9fPTDYc=", + "lastModified": 1768701115, + "narHash": "sha256-kkRg+hOTrRye6nDf6WBG8Ue7/ETUhfXdFSIVEXV2XXo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4206c4cb56751df534751b058295ea61357bbbaa", + "rev": "a48c9dbce9a434f3647d4d9eab783eca11a242c4", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable", + "ref": "nixos-unstable-small", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-lib": { "locked": { - "lastModified": 1750555020, - "narHash": "sha256-/MjivcZIz8dyLOTFdJzS5Yazt2QCePQBh8uZooODaYw=", + "lastModified": 1768699094, + "narHash": "sha256-uapeGJTPPbmqjdo5zlrWFEIys1XIOQ66sKP3A5UNEOk=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "6fb7349157ee1bffd053b1fdd454aa74ff7b4aee", + "rev": "78975aaec5a67ea502e15836919b89d7df96ac27", "type": "github" }, "original": { @@ -463,16 +473,16 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1750622754, - "narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=", + "lastModified": 1768621446, + "narHash": "sha256-6YwHV1cjv6arXdF/PQc365h1j+Qje3Pydk501Rm4Q+4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1", + "rev": "72ac591e737060deab2b86d6952babd1f896d7c5", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.05", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } @@ -485,19 +495,16 @@ "nixpkgs": [ "nixpkgs" ], - "nuschtosSearch": [ - "nuschtosSearch" - ], "systems": [ "systems" ] }, "locked": { - "lastModified": 1750751277, - "narHash": "sha256-wdUjRFiiHK8sDmntP1wLYZEqKgju8bGedwulu+myD6E=", + "lastModified": 1768486009, + "narHash": "sha256-I7ymDe6UQooHy9I9wrafKCCDnRbox/EMWAgJgpm7fGs=", "owner": "nix-community", "repo": "nixvim", - "rev": "3843b6226193bd2c40de0aea1343065b1bf9d3e4", + "rev": "03a638205b5cb04ba9c2ed6c604e137b15f07fa1", "type": "github" }, "original": { @@ -517,11 +524,11 @@ ] }, "locked": { - "lastModified": 1749730855, - "narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=", + "lastModified": 1768249818, + "narHash": "sha256-ANfn5OqIxq3HONPIXZ6zuI5sLzX1sS+2qcf/Pa0kQEc=", "owner": "NuschtOS", "repo": "search", - "rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742", + "rev": "b6f77b88e9009bfde28e2130e218e5123dc66796", "type": "github" }, "original": { @@ -535,7 +542,9 @@ "flake-utils": [ "flake-utils" ], - "nix-github-actions": "nix-github-actions", + "nix-github-actions": [ + "nix-github-actions" + ], "nixpkgs": [ "nixpkgs" ], @@ -573,11 +582,11 @@ ] }, "locked": { - "lastModified": 1750684550, - "narHash": "sha256-uLtw0iF9mQ94L831NOlQLPX9wm0qzd5yim3rcwACEoM=", + "lastModified": 1767281941, + "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "fae816c55a75675f30d18c9cbdecc13b970d95d4", + "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", "type": "github" }, "original": { @@ -586,6 +595,56 @@ "type": "github" } }, + "pyproject-build-systems": { + "inputs": { + "nixpkgs": [ + "beautysh", + "nixpkgs" + ], + "pyproject-nix": [ + "beautysh", + "pyproject-nix" + ], + "uv2nix": [ + "beautysh", + "uv2nix" + ] + }, + "locked": { + "lastModified": 1763662255, + "narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=", + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "rev": "042904167604c681a090c07eb6967b4dd4dae88c", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "type": "github" + } + }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "beautysh", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1764134915, + "narHash": "sha256-xaKvtPx6YAnA3HQVp5LwyYG1MaN4LLehpQI8xEdBvBY=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "2c8df1383b32e5443c921f61224b198a2282a657", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, "qmk_firmware": { "inputs": { "flake-utils": [ @@ -602,11 +661,11 @@ ] }, "locked": { - "lastModified": 1738406121, - "narHash": "sha256-N3scpt+nwPbqzsRs4gjZpDiBivfPRQJ0D75bPeYhHME=", + "lastModified": 1753517111, + "narHash": "sha256-dv5P3ahDICDacdzEmcyxrtKgbRWhVFiKQaLEz+WniGM=", "ref": "prime", - "rev": "2b6889092e5839c8987e800f1d3d847dd5b50153", - "revCount": 19, + "rev": "4dff2e6ba5c9c80de3e3d2213ad28802814c3bba", + "revCount": 39, "type": "git", "url": "https://git.foss-syndicate.org/bpeetz/qmk_layout.git" }, @@ -634,6 +693,7 @@ "lanzaboote": "lanzaboote", "library": "library", "nix-darwin": "nix-darwin", + "nix-github-actions": "nix-github-actions", "nix-index-database": "nix-index-database", "nixos-generators": "nixos-generators", "nixpkgs": "nixpkgs", @@ -658,11 +718,11 @@ ] }, "locked": { - "lastModified": 1750732748, - "narHash": "sha256-HR2b3RHsPeJm+Fb+1ui8nXibgniVj7hBNvUbXEyz0DU=", + "lastModified": 1768704795, + "narHash": "sha256-Y33TAp2BHEcuspYvcmBXXD0qdvjftv73PwyKTDOjoSY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "4b4494b2ba7e8a8041b2e28320b2ee02c115c75f", + "rev": "4b7472a78857ac789fb26616040f55cfcbd36c6e", "type": "github" }, "original": { @@ -757,11 +817,11 @@ ] }, "locked": { - "lastModified": 1749194973, - "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=", + "lastModified": 1768158989, + "narHash": "sha256-67vyT1+xClLldnumAzCTBvU0jLZ1YBcf4vANRWP3+Ak=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5", + "rev": "e96d59dff5c0d7fddb9d113ba108f03c3ef99eca", "type": "github" }, "original": { @@ -769,6 +829,31 @@ "repo": "treefmt-nix", "type": "github" } + }, + "uv2nix": { + "inputs": { + "nixpkgs": [ + "beautysh", + "nixpkgs" + ], + "pyproject-nix": [ + "beautysh", + "pyproject-nix" + ] + }, + "locked": { + "lastModified": 1764546642, + "narHash": "sha256-pCzgOjGEZyH7xKmpckdJzWyO0kvTIlaTK+ed/wguv5Y=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "0c56de7543459a23d0ebb7977fd555ced5d842ae", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "uv2nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 925d90f8..206a440f 100644 --- a/flake.nix +++ b/flake.nix @@ -13,8 +13,8 @@ inputs = { # base - nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; # open nixpkgs prs @@ -39,6 +39,12 @@ nixpkgs.follows = "nixpkgs"; }; }; + nix-github-actions = { + url = "github:nix-community/nix-github-actions"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; git-hooks = { url = "github:cachix/git-hooks.nix"; inputs = { @@ -54,6 +60,7 @@ nixpkgs.follows = "nixpkgs"; flake-utils.follows = "flake-utils"; treefmt-nix.follows = "treefmt-nix"; + nix-github-actions.follows = "nix-github-actions"; }; }; flake-parts = { @@ -81,8 +88,10 @@ url = "github:lovesegfault/beautysh"; inputs = { nixpkgs.follows = "nixpkgs"; - poetry2nix.follows = "poetry2nix"; - utils.follows = "flake-utils"; + flake-parts.follows = "flake-parts"; + git-hooks-nix.follows = "git-hooks"; + treefmt-nix.follows = "treefmt-nix"; + nix-github-actions.follows = "nix-github-actions"; }; }; devshell = { @@ -138,7 +147,6 @@ inputs = { flake-parts.follows = "flake-parts"; nixpkgs.follows = "nixpkgs"; - nuschtosSearch.follows = "nuschtosSearch"; systems.follows = "systems"; }; }; @@ -154,6 +162,8 @@ impermanence = { url = "github:nix-community/impermanence"; inputs = { + nixpkgs.follows = "nixpkgs"; + home-manager.follows = "home-manager"; }; }; nixos-generators = { @@ -175,14 +185,12 @@ }; }; lanzaboote = { - url = "github:nix-community/lanzaboote/v0.4.2"; + url = "github:nix-community/lanzaboote/v1.0.0"; inputs = { nixpkgs.follows = "nixpkgs-stable"; - flake-compat.follows = "flake-compat"; - flake-parts.follows = "flake-parts"; crane.follows = "crane"; rust-overlay.follows = "rust-overlay"; - pre-commit-hooks-nix.follows = "pre-commit-hooks"; + pre-commit.follows = "pre-commit-hooks"; }; }; nix-index-database = { diff --git a/flake/default.nix b/flake/default.nix index e80e6ead..83138f32 100644 --- a/flake/default.nix +++ b/flake/default.nix @@ -45,7 +45,7 @@ inherit (pkgs) lib; extraModules = { nixvim = { - homeManagerModule = modules.nixVim.homeManagerModules.nixvim; + homeManagerModule = modules.nixVim.homeModules.nixvim; }; agenix = modules.agenix.nixosModules.default; disko = modules.disko.nixosModules.default; @@ -106,6 +106,7 @@ in { # nix pkgs.alejandra pkgs.deadnix + pkgs.nvd ]; }; }; diff --git a/hosts/by-name/apzu/configuration.nix b/hosts/by-name/apzu/configuration.nix index d4aad0f9..a5b10e9f 100644 --- a/hosts/by-name/apzu/configuration.nix +++ b/hosts/by-name/apzu/configuration.nix @@ -7,12 +7,7 @@ # # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{ - lib, - pkgs, - baseLib, - ... -}: { +{...}: { imports = [ ./hardware.nix ]; @@ -25,92 +20,14 @@ enable = true; }; - # TODO: Hard-code all the uids/gids <2025-05-13> impermanence.directories = [ - "/var/lib/nixos" "/var/log" ]; programs = { - yambar = { - laptop = true; - backlight = "intel_backlight"; - }; river.init = { mappings = { layout = "dvorak-modified"; - keymap = - { - # Focus change - "<Meta-T>" = {command = ["focus-view" "next"];}; - "<Meta-N>" = {command = ["focus-view" "previous"];}; - "<Meta+Ctrl-T>" = {command = ["focus-output" "next"];}; - "<Meta+Ctrl-N>" = {command = ["focus-output" "previous"];}; - - # Standard programs - "<Meta-<ENTER>>" = {command = ["spawn" "${lib.getExe pkgs.alacritty}"];}; - "<Meta+Shift-q>" = {command = ["exit"];}; - "<Meta-L>" = {command = ["spawn" "${lib.getExe pkgs.lock}"];}; - - # Screenshot - "<PRINTSCREEN>" = {command = ["spawn" "${lib.getExe pkgs.screenshot_persistent}"];}; - - # Audio - "<MEDIA_RAISEVOLUME>" = { - command = ["spawn" "${lib.getExe' pkgs.wireplumber "wpctl"} set-volume @DEFAULT_SINK@ 5%+"]; - modes = ["normal" "locked"]; - }; - "<MEDIA_LOWERVOLUME>" = { - command = ["spawn" "${lib.getExe' pkgs.wireplumber "wpctl"} set-volume @DEFAULT_SINK@ 5%-"]; - modes = ["normal" "locked"]; - }; - "<MEDIA_MUTEVOLUME>" = { - command = ["spawn" "${lib.getExe pkgs.mpp} toggle"]; - modes = ["normal" "locked"]; - }; - - # Launcher - "<Meta-R>" = {command = ["spawn" "${lib.getExe pkgs.rofi} -show combi -modes combi -combi-modes 'window, drun, run' -show-icons"];}; - "<Meta-<F1>>" = {command = ["spawn" "${lib.getExe pkgs.tskm} open select"];}; - "<Meta-<F2>>" = {command = ["spawn" "${lib.getExe pkgs.keepassxc}"];}; - "<Meta-<F3>>" = {command = ["spawn" "${lib.getExe pkgs.signal-desktop}"];}; - # "<Meta-<F4>>" = {command = ["spawn" "${lib.getExe pkgs.steam}"];}; - - # Client - "<Meta-f>" = {command = ["toggle-fullscreen"];}; - "<Meta+Shift-c>" = {command = ["close"];}; - "<Meta+Ctrl- >" = {command = ["toggle-float"];}; - "<Meta+Ctrl-<ENTER>>" = {command = ["zoom"];}; - "<Meta-o>" = {command = ["send-to-output" "next"];}; - "<Meta+Shift-T>" = {command = ["swap" "next"];}; - "<Meta+Shift-N>" = {command = ["swap" "previous"];}; - - # Toggle all tags - "<Meta-0>" = {command = ["set-focused-tags" (builtins.toString ((baseLib.pow 2 32) - 1))];}; - "<Meta+Shift-0>" = {command = ["set-view-tags" (builtins.toString ((baseLib.pow 2 32) - 1))];}; - - # Mouse - "<Meta-<MOUSE_LEFT>>" = { - command = ["move-view"]; - map_mode = "MapMouse"; - }; - "<Meta-<MOUSE_RIGHT>>" = { - command = ["resize-view"]; - map_mode = "MapMouse"; - }; - } - // ( - builtins.foldl' (acc: elem: acc // elem) {} ( - builtins.map (index: let - num = builtins.toString index; - index2tag = input: builtins.toString (baseLib.pow 2 (input - 1)); - in { - "<Meta-${num}>" = {command = ["set-focused-tags" (index2tag index)];}; - "<Meta+Shift-${num}>" = {command = ["set-view-tags" (index2tag index)];}; - "<Meta+Shift+Ctrl-${num}>" = {command = ["toggle-view-tags" (index2tag index)];}; - }) (builtins.genList (i: i + 1) 9) - ) - ); }; screenSetupCode = {}; }; @@ -122,7 +39,12 @@ hostName = "apzu"; mode = "NetworkManager"; }; - services.unison.foreign.address = "tiamat.fritz.box"; + + services = { + unison.foreign.address = "tiamat.fritz.box"; + upower.enable = true; + }; + nixpkgs = { enable = true; systemName = "x86_64-linux"; diff --git a/hosts/by-name/tiamat/configuration.nix b/hosts/by-name/tiamat/configuration.nix index efb77b96..18393543 100644 --- a/hosts/by-name/tiamat/configuration.nix +++ b/hosts/by-name/tiamat/configuration.nix @@ -46,91 +46,13 @@ init = { mappings = { layout = "us"; - keymap = let - map = key: "<Alt+Ctrl+Meta+Shift-${key}>"; - in - (lib.mapAttrs' (name: value: lib.nameValuePair (map name) value) { - # Movement - "A" = {command = ["exit"];}; - "B" = {command = ["close"];}; - - "C" = {command = ["focus-view" "previous"];}; - "D" = {command = ["focus-view" "next"];}; - - "E" = {command = ["swap" "previous"];}; - "F" = {command = ["swap" "next"];}; - - "G" = {command = ["zoom"];}; - - "H" = {command = ["toggle-fullscreen"];}; - "I" = {command = ["toggle-float"];}; - - "J" = {command = ["send-to-output" "next"];}; - - "K" = {command = ["spawn" "${lib.getExe pkgs.alacritty}"];}; - "L" = {command = ["spawn" "${lib.getExe pkgs.screenshot_persistent}"];}; - - # Audio - # "M" = {command = ["spawn" "video-pause toggle"]; modes = ["normal" "locked"]; }; - "N" = { - command = ["spawn" "${lib.getExe pkgs.mpp} toggle"]; - modes = ["normal" "locked"]; - }; - - # Launcher - "O" = {command = ["spawn" "${lib.getExe pkgs.rofi} -show combi -modes combi -combi-modes 'window,drun,run' -show-icons"];}; - "P" = {command = ["spawn" "${lib.getExe pkgs.tskm} open select"];}; - "Q" = {command = ["spawn" "${lib.getExe pkgs.keepassxc}"];}; - # "R" = {command = ["spawn" "nheko"];}; - "S" = {command = ["spawn" "${lib.getExe pkgs.signal-desktop}"];}; - "T" = {command = ["spawn" "${lib.getExe pkgs.lock}"];}; - - "U" = {command = ["focus-output" "next"];}; - "V" = {command = ["focus-previous-tags"];}; - "W" = {command = ["send-to-previous-tags"];}; - # "X" = {command = ["spawn" "bemenu-run"];}; - # "Y" = {command = ["spawn" "bemenu-run"];}; - - # Toggle all tags - "0" = {command = ["set-focused-tags" "${builtins.toString ((libraries.base.pow 2 32) - 1)}"];}; - - # Support Unicode input - "Z" = {command = ["spawn" "${lib.getExe externalBinaries.qmk_firmware.packages.${system}.qmk_unicode_type} 106 65377"];}; - }) - // ({ - # TODO: add toggle-focus mapping - - # Toggle all tags - "<Alt+Ctrl+Shift-0>" = { - command = [ - "set-view-tags" - "${builtins.toString - ((libraries.base.pow 2 32) - 1)}" - ]; - }; - - # Mouse - "<Meta-<MOUSE_LEFT>>" = { - command = ["move-view"]; - map_mode = "MapMouse"; - }; - "<Meta-<MOUSE_RIGHT>>" = { - command = ["resize-view"]; - map_mode = "MapMouse"; - }; - } - // ( - builtins.foldl' (acc: elem: acc // elem) {} ( - builtins.map (index: let - num = builtins.toString index; - index2tag = input: builtins.toString (libraries.base.pow 2 (input - 1)); - in { - "${map num}" = {command = ["set-focused-tags" (index2tag index)];}; - "<Alt+Ctrl+Shift-${num}>" = {command = ["set-view-tags" (index2tag index)];}; - # "<Super+Shift+Ctrl-${num}>" = {command = ["toggle-view-tags" (index2tag index)];}; - }) (builtins.genList (i: i + 1) 9) - ) - )); + keymap = { + # Support Unicode input + "<Alt+Ctrl+Meta+Shift-Z>" = [ + "spawn" + "${lib.getExe externalBinaries.qmk_firmware.packages.${system}.qmk_unicode_type} 106 65377" + ]; + }; }; screenSetupCode = { "DP-2" = {pos = "2560,0";}; diff --git a/modules/by-name/ad/adb/module.nix b/modules/by-name/ad/adb/module.nix index 71bd3c9b..89cb1439 100644 --- a/modules/by-name/ad/adb/module.nix +++ b/modules/by-name/ad/adb/module.nix @@ -10,6 +10,7 @@ { lib, config, + pkgs, ... }: let cfg = config.soispha.services.adb; @@ -25,7 +26,13 @@ in { }; config = lib.mkIf cfg.enable { - programs.adb.enable = true; - users.users."${cfg.user}".extraGroups = ["adbusers"]; + environment.systemPackages = [ + pkgs.android-tools + ]; + + users = { + users."${cfg.user}".extraGroups = ["adbusers"]; + groups.adbusers.gid = config.soispha.constants.ids.gids.adbusers; + }; }; } diff --git a/modules/by-name/al/alacritty/module.nix b/modules/by-name/al/alacritty/module.nix new file mode 100644 index 00000000..73860a57 --- /dev/null +++ b/modules/by-name/al/alacritty/module.nix @@ -0,0 +1,468 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + config, + lib, + pkgs, + libraries, + ... +}: let + cfg = config.soispha.programs.alacritty; +in { + options.soispha.programs.alacritty = { + enable = libraries.base.options.mkEnable "alacritty"; + + package = lib.mkPackageOption pkgs "alacritty" {}; + }; + + config = lib.mkIf cfg.enable { + home-manager.users.soispha = { + home.sessionVariables = { + # This is **not** the TERM variable but a special one to signify my favorite terminal. + TERMINAL = "alacritty"; + }; + + programs.alacritty = { + enable = true; + inherit (cfg) package; + + settings = { + bell = { + duration = 0; + }; + + colors = builtins.fromTOML (builtins.readFile ./theme.toml); + + cursor = { + blink_interval = 750; + blink_timeout = 5; + style = { + blinking = "On"; + shape = "Beam"; + }; + thickness = 0.15; + unfocused_hollow = true; + vi_mode_style = "None"; + }; + + env = { + COLORTERM = "truecolor"; + TERM = "alacritty"; + }; + + font = { + builtin_box_drawing = true; + glyph_offset = { + x = -1; + y = -1; + }; + normal = { + family = "SauceCodePro Nerd Font Mono"; + style = "Regular"; + }; + offset = { + x = -1; + y = -1; + }; + size = 12; + }; + + general = { + ipc_socket = true; + live_config_reload = true; + }; + + hints = { + alphabet = "jfkdls;ahgurieowpq"; + enabled = [ + { + binding = { + key = "U"; + mods = "Control|Shift"; + }; + command = "xdg-open"; + hyperlinks = true; + mouse = { + enabled = true; + }; + persist = false; + post_processing = true; + regex = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\\\s{-}\\\\^⟨⟩`]+"; + } + { + action = "Paste"; + binding = { + key = "T"; + mods = "Control|Shift"; + }; + post_processing = false; + regex = "([^ '\"`=:\\\\[\\\\(]*/)([^/: '\"`\\\\)\\\\]]*)"; + } + { + action = "Paste"; + binding = { + key = "H"; + mods = "Control|Shift"; + }; + post_processing = false; + regex = ''([a-z0-9]{7,40})\\s''; + } + ]; + }; + + keyboard = { + bindings = [ + { + action = "Paste"; + key = "P"; + mods = "Control"; + } + { + action = "Paste"; + key = "Insert"; + mods = "Shift"; + } + { + chars = "gc"; + key = "Slash"; + mods = "Control"; + } + { + action = "Copy"; + key = "Y"; + mods = "Control"; + } + { + action = "ResetFontSize"; + key = "Key0"; + mods = "Control"; + } + { + action = "IncreaseFontSize"; + key = "Equals"; + mods = "Control"; + } + { + action = "IncreaseFontSize"; + key = "Plus"; + mods = "Control"; + } + { + action = "DecreaseFontSize"; + key = "Minus"; + mods = "Control"; + } + { + action = "ToggleViMode"; + key = "Space"; + mods = "Control"; + } + { + action = "ScrollToBottom"; + key = "Space"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ScrollToBottom"; + key = "I"; + mode = "Vi"; + } + { + action = "ToggleViMode"; + key = "I"; + mode = "Vi"; + } + { + action = "ScrollToBottom"; + key = "C"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ToggleViMode"; + key = "C"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ClearSelection"; + key = "Escape"; + mode = "Vi"; + } + { + action = "ScrollLineUp"; + key = "Y"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ScrollLineDown"; + key = "E"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ScrollToTop"; + key = "G"; + mode = "Vi"; + } + { + action = "ScrollToBottom"; + key = "G"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "ScrollPageUp"; + key = "B"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ScrollPageDown"; + key = "F"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ScrollHalfPageUp"; + key = "U"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ScrollHalfPageDown"; + key = "D"; + mode = "Vi"; + mods = "Control"; + } + { + action = "Copy"; + key = "Y"; + mode = "Vi"; + } + { + action = "ClearSelection"; + key = "Y"; + mode = "Vi"; + } + { + action = "ToggleNormalSelection"; + key = "V"; + mode = "Vi"; + } + { + action = "ToggleLineSelection"; + key = "V"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "ToggleBlockSelection"; + key = "V"; + mode = "Vi"; + mods = "Control"; + } + { + action = "ToggleSemanticSelection"; + key = "V"; + mode = "Vi"; + mods = "Alt"; + } + { + action = "Open"; + key = "Return"; + mode = "Vi"; + } + { + action = "Up"; + key = "K"; + mode = "Vi"; + } + { + action = "Down"; + key = "J"; + mode = "Vi"; + } + { + action = "Left"; + key = "H"; + mode = "Vi"; + } + { + action = "Right"; + key = "L"; + mode = "Vi"; + } + { + action = "Up"; + key = "Up"; + mode = "Vi"; + } + { + action = "Down"; + key = "Down"; + mode = "Vi"; + } + { + action = "Left"; + key = "Left"; + mode = "Vi"; + } + { + action = "Right"; + key = "Right"; + mode = "Vi"; + } + { + action = "First"; + key = "Key0"; + mode = "Vi"; + } + { + action = "Last"; + key = "Key4"; + mode = "Vi"; + } + { + action = "FirstOccupied"; + key = "Key6"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "High"; + key = "H"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "Middle"; + key = "M"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "Low"; + key = "L"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "SemanticLeft"; + key = "B"; + mode = "Vi"; + } + { + action = "SemanticRight"; + key = "W"; + mode = "Vi"; + } + { + action = "SemanticRightEnd"; + key = "E"; + mode = "Vi"; + } + { + action = "WordLeft"; + key = "B"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "WordRight"; + key = "W"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "WordRightEnd"; + key = "E"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "Bracket"; + key = "Key5"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "SearchForward"; + key = "Slash"; + mode = "Vi"; + } + { + action = "SearchBackward"; + key = "Slash"; + mode = "Vi"; + mods = "Shift"; + } + { + action = "SearchNext"; + key = "N"; + mode = "Vi"; + } + { + action = "SearchPrevious"; + key = "N"; + mode = "Vi"; + mods = "Shift"; + } + ]; + }; + + mouse = { + bindings = [ + { + action = "Copy"; + mouse = "Middle"; + } + ]; + hide_when_typing = + false; + }; + + scrolling = { + history = 10000; + multiplier = 3; + }; + + selection = { + save_to_clipboard = false; + semantic_escape_chars = ",│`|:\"' ()[]{}<>\t"; + }; + + window = { + class = { + general = "Alacritty"; + instance = "Alacritty"; + }; + decorations = "none"; + decorations_theme_variant = "None"; + dimensions = { + columns = 0; + lines = 0; + }; + dynamic_title = true; + opacity = 0.9; + padding = { + x = 5; + y = 5; + }; + startup_mode = "Windowed"; + title = "Alacritty"; + }; + }; + }; + }; + }; +} diff --git a/modules/home.legacy/conf/alacritty/toml/colorscheme.toml b/modules/by-name/al/alacritty/theme.toml index 13c796c9..2b326e43 100644 --- a/modules/home.legacy/conf/alacritty/toml/colorscheme.toml +++ b/modules/by-name/al/alacritty/theme.toml @@ -12,45 +12,45 @@ ## name: carbonfox ## upstream: https://github.com/edeneast/nightfox.nvim/raw/main/extra/carbonfox/alacritty.toml -[colors.primary] +[primary] background = "#161616" foreground = "#f2f4f8" dim_foreground = "#b6b8bb" bright_foreground = "#f9fbff" -[colors.cursor] +[cursor] text = "#f2f4f8" cursor = "#b6b8bb" -[colors.vi_mode_cursor] +[vi_mode_cursor] text = "#f2f4f8" cursor = "#33b1ff" -[colors.search.matches] +[search.matches] foreground = "#f2f4f8" background = "#525253" -[colors.search.focused_match] +[search.focused_match] foreground = "#f2f4f8" background = "#3ddbd9" -[colors.footer_bar] +[footer_bar] foreground = "#f2f4f8" background = "#353535" -[colors.hints.start] +[hints.start] foreground = "#f2f4f8" background = "#3ddbd9" -[colors.hints.end] +[hints.end] foreground = "#f2f4f8" background = "#353535" -[colors.selection] +[selection] text = "#f2f4f8" background = "#2a2a2a" -[colors.normal] +[normal] black = "#282828" red = "#ee5396" green = "#25be6a" @@ -60,7 +60,7 @@ magenta = "#be95ff" cyan = "#33b1ff" white = "#dfdfe0" -[colors.bright] +[bright] black = "#484848" red = "#f16da6" green = "#46c880" @@ -70,7 +70,7 @@ magenta = "#c8a5ff" cyan = "#52bdff" white = "#e4e4e5" -[colors.dim] +[dim] black = "#222222" red = "#ca4780" green = "#1fa25a" @@ -80,10 +80,10 @@ magenta = "#a27fd9" cyan = "#2b96d9" white = "#bebebe" -[[colors.indexed_colors]] +[[indexed_colors]] index = 16 color = "#3ddbd9" -[[colors.indexed_colors]] +[[indexed_colors]] index = 17 color = "#ff7eb6" diff --git a/modules/by-name/ba/backup/module.nix b/modules/by-name/ba/backup/module.nix index d0805092..7a788764 100644 --- a/modules/by-name/ba/backup/module.nix +++ b/modules/by-name/ba/backup/module.nix @@ -11,6 +11,7 @@ lib, config, pkgs, + libraries, ... }: let cfg = config.soispha.services.backup; @@ -18,6 +19,8 @@ snapshotDir = "/srv/last_snapshot"; in { options.soispha.services.backup = { + enable = libraries.base.options.mkEnable "backups via restic"; + storagebox = { enable = lib.mkEnableOption "remote backups"; user = lib.mkOption { @@ -56,7 +59,16 @@ in { }; }; - config = { + config = lib.mkIf cfg.enable { + soispha.impermanence.directories = lib.mkMerge [ + (lib.mkIf cfg.storagebox.enable [ + "/var/cache/restic-backups-storagebox" + ]) + (lib.mkIf cfg.local.enable [ + "/var/cache/restic-backups-local" + ]) + ]; + age.secrets = { resticStorageboxSshKey = lib.mkIf cfg.storagebox.enable { file = cfg.storagebox.sshKey; diff --git a/modules/by-name/bo/boot/module.nix b/modules/by-name/bo/boot/module.nix index dfcd14b7..4b95aedf 100644 --- a/modules/by-name/bo/boot/module.nix +++ b/modules/by-name/bo/boot/module.nix @@ -46,7 +46,7 @@ in { # # stay on disk forever) <2024-05-11> # copyExtraFiles = '' # echo "[systemd-boot] copying files to ${bootMountPoint}" - # empty_file=$(mktemp) + # empty_file=$(mktemp boot_empty_file_XXX) # # ${lib.concatStrings (lib.mapAttrsToList (n: v: # /* diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix new file mode 100644 index 00000000..493ead1f --- /dev/null +++ b/modules/by-name/co/constants/module.nix @@ -0,0 +1,59 @@ +# This file is inspired by the `nixos/modules/misc/ids.nix` +# file in nixpkgs. +{lib, ...}: { + options.soispha.constants = { + ids.uids = lib.mkOption { + internal = true; + description = '' + The user IDs used in this nixos config. + ''; + type = lib.types.attrsOf (lib.types.ints.between 0 1000); + }; + ids.gids = lib.mkOption { + internal = true; + description = '' + The group IDs used in this nixos config. + ''; + type = lib.types.attrsOf (lib.types.ints.between 0 1000); + }; + }; + + config.soispha.constants = { + ids.uids = { + # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="` + + wpa_supplicant = 986; + dhcpcd = 992; + systemd-oom = 993; + sshd = 994; + rtkit = 995; + nscd = 996; + nm-iodine = 997; + fwupd-refresh = 998; + avahi = 999; + + # As per the NixOS file, the uids should not be greater or equal to 400; + }; + ids.gids = { + # Please add your groups to the users and inherit them here. + # This avoids having an user/group id mismatch. + + wpa_supplicant = 986; + dhcpcd = 987; + lpadmin = 988; + resolvconf = 989; + systemd-oom = 990; + systemd-coredump = 991; + sshd = 992; + rtkit = 993; + polkituser = 994; + nscd = 995; + msr = 996; + fwupd-refresh = 997; + avahi = 998; + adbusers = 999; + + # The gid should match the uid. Thus should not be >= 400; + }; + }; +} diff --git a/modules/by-name/co/coredump/module.nix b/modules/by-name/co/coredump/module.nix new file mode 100644 index 00000000..79e764eb --- /dev/null +++ b/modules/by-name/co/coredump/module.nix @@ -0,0 +1,17 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.systemd.coredump; +in { + options.soispha.systemd.coredump = { + enable = (lib.mkEnableOption "oomd") // {default = config.systemd.coredump.enable;}; + }; + + config = lib.mkIf cfg.enable { + users = { + groups.systemd-coredump.gid = config.soispha.constants.ids.gids.systemd-coredump; + }; + }; +} diff --git a/modules/by-name/dh/dhcpcd/module.nix b/modules/by-name/dh/dhcpcd/module.nix new file mode 100644 index 00000000..5fee0100 --- /dev/null +++ b/modules/by-name/dh/dhcpcd/module.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.dhcpcd; +in { + options.soispha.dhcpcd = { + enable = (lib.mkEnableOption "dhcpcd") // {default = config.networking.dhcpcd.enable;}; + }; + + config = lib.mkIf cfg.enable { + users = { + users.dhcpcd = { + uid = config.soispha.constants.ids.uids.dhcpcd; + group = "dhcpcd"; + }; + groups.dhcpcd.gid = config.soispha.constants.ids.gids.dhcpcd; + }; + }; +} diff --git a/modules/by-name/di/disks/module.nix b/modules/by-name/di/disks/module.nix index d88fa3df..3e9d4614 100644 --- a/modules/by-name/di/disks/module.nix +++ b/modules/by-name/di/disks/module.nix @@ -17,7 +17,7 @@ # FIXME: The iso redeploy requires a bigger efi partition <2024-05-12> cfg = config.soispha.disks; defaultMountOptions = [ - "compress-force=zstd:15" # This saves disk space, at a performance cost + "compress=zstd:3" # This saves disk space, at a performance cost "noatime" # should have some performance upsides, and I don't use it anyways "lazytime" # make time changes in memory ]; @@ -86,7 +86,10 @@ in { }; "swap" = { mountpoint = "/swap"; - mountOptions = defaultMountOptions; + mountOptions = [ + "noatime" # should have some performance upsides, and I don't use it anyways + "lazytime" # make time changes in memory + ]; }; }; }; @@ -109,11 +112,20 @@ in { nodev = { "/" = { fsType = "tmpfs"; - mountOptions = ["defaults" "size=4G" "mode=755"]; + mountOptions = ["defaults" "size=25%" "mode=0755"]; }; "/tmp" = { fsType = "tmpfs"; - mountOptions = ["defaults" "size=16G" "mode=755"]; + mountOptions = ["defaults" "size=50%" "mode=0755"]; + }; + "/nix/var/nix/b" = { + fsType = "tmpfs"; + mountOptions = [ + "defaults" + "noswap" # Otherwise, we might run into io-based slowdowns + "size=80%" + "mode=0755" + ]; }; }; }; diff --git a/modules/by-name/fo/fonts/module.nix b/modules/by-name/fo/fonts/module.nix index f8752ae6..4bf9a612 100644 --- a/modules/by-name/fo/fonts/module.nix +++ b/modules/by-name/fo/fonts/module.nix @@ -20,10 +20,14 @@ in { fonts = lib.mkOption { type = lib.types.listOf lib.types.package; - example = lib.literalExpression ''with pkgs.nerdfonts; [SourceCodePro Overpass FiraCode]''; - default = with pkgs.nerd-fonts; [ - sauce-code-pro - overpass + example = lib.literalExpression '' [ + pkgs.nerd-fonts.source-code-pro + pkgs.nerd-fonts.overpass + pkgs.nerd-fonts.fira-code + ]''; + default = [ + pkgs.nerd-fonts.sauce-code-pro + pkgs.nerd-fonts.overpass ]; description = "The nerd-fonts to install"; }; @@ -35,8 +39,8 @@ in { fonts = { packages = cfg.fonts - ++ (with pkgs; [liberation_ttf]) - ++ lib.optional cfg.enableEmoji pkgs.noto-fonts-emoji; + ++ [pkgs.liberation_ttf] + ++ lib.optional cfg.enableEmoji pkgs.noto-fonts-color-emoji; fontconfig = { # NOTE: This is responsible for color emoji support <2023-08-28> diff --git a/modules/by-name/fw/fwupd/module.nix b/modules/by-name/fw/fwupd/module.nix index 0c4a7bf3..7252c170 100644 --- a/modules/by-name/fw/fwupd/module.nix +++ b/modules/by-name/fw/fwupd/module.nix @@ -17,7 +17,13 @@ in { options.soispha.services.fwupd = { enable = lib.mkEnableOption "fwupd"; }; + config = lib.mkIf cfg.enable { services.fwupd.enable = true; + + users = { + users.fwupd-refresh.uid = config.soispha.constants.ids.uids.fwupd-refresh; + groups.fwupd-refresh.gid = config.soispha.constants.ids.gids.fwupd-refresh; + }; }; } diff --git a/modules/by-name/gi/git/module.nix b/modules/by-name/gi/git/module.nix index a70c38d7..77cfce54 100644 --- a/modules/by-name/gi/git/module.nix +++ b/modules/by-name/gi/git/module.nix @@ -21,21 +21,37 @@ in { enable = lib.mkEnableOption "an opinionated git config"; defaultBranchName = lib.mkOption { type = lib.types.str; - description = "The Name of the default branch."; + description = "The name of the default branch."; default = "prime"; }; }; config = lib.mkIf cfg.enable { home-manager.users.soispha = { + programs.delta = { + enable = true; + enableGitIntegration = true; + options = { + decorations = { + commit-decoration-style = "bold yellow box ul"; + file-decoration-style = "none"; + file-style = "bold yellow ul"; + }; + keep-plus-minus-markers = true; + features = "decorations"; + whitespace-error-style = "22 reverse"; + }; + }; + programs.git = { enable = true; #package = pkgs.gitAndTools.gitFull; # TODO: for git send-email support - aliases = import ./aliases.nix { - inherit lib; - inherit (cfg) defaultBranchName; - }; - extraConfig = { + settings = { + alias = import ./aliases.nix { + inherit lib; + inherit (cfg) defaultBranchName; + }; + core = { excludesFile = "${gitIgnoreFile}"; }; @@ -100,19 +116,7 @@ in { }; }; }; - delta = { - enable = true; - options = { - decorations = { - commit-decoration-style = "bold yellow box ul"; - file-decoration-style = "none"; - file-style = "bold yellow ul"; - }; - keep-plus-minus-markers = true; - features = "decorations"; - whitespace-error-style = "22 reverse"; - }; - }; + signing = { key = "8321ED3A8DB999A51F3BF80FF2682914EA42DE26"; signByDefault = true; diff --git a/modules/by-name/ha/hardware/module.nix b/modules/by-name/ha/hardware/module.nix index 599f5d23..69ef819f 100644 --- a/modules/by-name/ha/hardware/module.nix +++ b/modules/by-name/ha/hardware/module.nix @@ -48,7 +48,7 @@ in { extraPackages = builtins.attrValues { inherit (pkgs) - vaapiVdpau + libva-vdpau-driver libvdpau-va-gl ; }; diff --git a/modules/by-name/hl/lhedger/module.nix b/modules/by-name/hl/lhedger/module.nix new file mode 100644 index 00000000..68de85d0 --- /dev/null +++ b/modules/by-name/hl/lhedger/module.nix @@ -0,0 +1,57 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + config, + lib, + pkgs, + libraries, + ... +}: let + cfg = config.soispha.hledger; + ledgerFile = "${config.home-manager.users.soispha.xdg.dataHome}/hledger/2025.journal"; +in { + options.soispha.hledger = { + enable = libraries.base.options.mkEnable "hledger"; + }; + + config = lib.mkIf cfg.enable { + environment.variables = { + LEDGER_FILE = ledgerFile; + }; + + home-manager.users.soispha = { + home.packages = [ + pkgs.hledger + + (pkgs.writeShellApplication { + name = "hledger-edit"; + + text = '' + "$EDITOR" ${lib.strings.escapeShellArg ledgerFile} + ''; + + inheritPath = true; # needs access to nvim + }) + ]; + + xdg.configFile = { + "hledger/hledger.conf".text = + # ledger + '' + # Actually enforce more checks. + --strict + + # Use boxdrawing characters when possible. + --pretty + ''; + }; + }; + }; +} diff --git a/modules/by-name/i3/i3bar-river/module.nix b/modules/by-name/i3/i3bar-river/module.nix index b32ec6a5..8a2203aa 100644 --- a/modules/by-name/i3/i3bar-river/module.nix +++ b/modules/by-name/i3/i3bar-river/module.nix @@ -119,7 +119,7 @@ in { settings = { theme = { - palette = { + focused = { # Colors background = toString cfg.colors.none; color = toString cfg.colors.white; # Only used, if blocks do not specify one @@ -146,7 +146,7 @@ in { show_tags = true; }; - unfocused_palette = { + unfocused = { hide_inactive_tags = true; color = toString (substractColor cfg.colors.white cfg.colors.unfocused_offset); diff --git a/modules/by-name/i3/i3status-rust/module.nix b/modules/by-name/i3/i3status-rust/module.nix index 5bd911d8..10cb4475 100644 --- a/modules/by-name/i3/i3status-rust/module.nix +++ b/modules/by-name/i3/i3status-rust/module.nix @@ -63,7 +63,7 @@ in { block = "custom"; interval = "once"; persistent = true; - command = mkScript "mpd_song_name.sh" [pkgs.mpc pkgs.coreutils]; + command = mkScript "mpd_song_name.sh" [pkgs.mpc pkgs.mpdpopm pkgs.coreutils]; hide_when_empty = true; shell = "${lib.getExe pkgs.dash}"; format = " $text.str(max_width:60,rot_interval:0.5) "; @@ -81,11 +81,12 @@ in { } { block = "memory"; - format = " $icon $mem_used_percents {($swap_used_percents.eng(range:1..)) | }"; + format = " $icon $mem_used_percents{ ($swap_used_percents.eng(range:1..))|} "; } { block = "amd_gpu"; - format = " $icon $utilization (^icon_memory_mem $vram_used_percents) "; + format = " $icon $utilization (^icon_memory_mem $vram_used_percents) "; + error_format = ""; } { @@ -117,6 +118,7 @@ in { } { block = "battery"; + format = " $icon $percentage{ $time_remaining.dur(hms:true, min_unit:m)|}{ $power|} "; missing_format = ""; } diff --git a/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh b/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh index 28921520..edd5da2c 100755 --- a/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh +++ b/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh @@ -16,8 +16,14 @@ while true; do if [ "$state" = "playing" ]; then song="$(mpc --format '[[%artist% - ]%title%]|[%file%]' current)" progress="$(mpc status "%currenttime%/%totaltime%")" + base_rating="$(mpdpopm rating get)" - echo "$song :: $progress" + rating="" + if [ "$base_rating" != 0 ]; then + rating=" ($base_rating)" + fi + + echo "$song :: ${progress}${rating}" else # The song has stopped, we are done displaying it. echo "" diff --git a/modules/by-name/ia/iamb/module.nix b/modules/by-name/ia/iamb/module.nix new file mode 100644 index 00000000..ef46f5e0 --- /dev/null +++ b/modules/by-name/ia/iamb/module.nix @@ -0,0 +1,59 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + config, + lib, + libraries, + ... +}: let + cfg = config.soispha.programs.iamb; +in { + options.soispha.programs.iamb = { + enable = libraries.base.options.mkEnable "iamb"; + }; + + config = lib.mkIf cfg.enable { + soispha.impermanence.userDirectories = [ + ".config/iamb/profiles" + ]; + + home-manager.users.soispha = { + programs.iamb = { + enable = true; + + settings = { + default_profile = "soispha"; + + profiles = { + soispha = { + user_id = "@soispha:vhack.eu"; + url = "https://matrix.vhack.eu"; + }; + }; + + settings = { + username_display = "displayname"; + + notifications = { + enabled = true; + }; + + image_preview = { + size = { + height = 10; + width = 66; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/modules/by-name/im/impermanence/module.nix b/modules/by-name/im/impermanence/module.nix index e31cfaaf..78ad570f 100644 --- a/modules/by-name/im/impermanence/module.nix +++ b/modules/by-name/im/impermanence/module.nix @@ -31,6 +31,21 @@ in { })); description = "The directories to persist"; }; + + userDirectories = lib.mkOption { + type = lib.types.listOf (lib.types.either lib.types.str (lib.types.submodule { + options = { + directory = lib.mkOption { + type = lib.types.str; + }; + mode = lib.mkOption { + type = lib.types.str; + }; + }; + })); + default = []; + description = "The directories to persist"; + }; }; imports = [ @@ -53,14 +68,13 @@ in { ".local/state/wireplumber" ".config/Signal" - ".config/iamb/profiles" ".cache" "media" "repos" - "school" - ]; + "documents" + ] ++ cfg.userDirectories; }; files = [ diff --git a/modules/by-name/le/less/module.nix b/modules/by-name/le/less/module.nix index 0dd45a90..e3b47f13 100644 --- a/modules/by-name/le/less/module.nix +++ b/modules/by-name/le/less/module.nix @@ -31,7 +31,7 @@ in { programs.less = { enable = true; - keys = + config = builtins.readFile ./command.less + builtins.readFile ./line-edit.less + builtins.readFile ./env.less; diff --git a/modules/by-name/lf/lf/commands/base.sh b/modules/by-name/lf/lf/commands/base.sh index 99852c3e..7003d76c 100755 --- a/modules/by-name/lf/lf/commands/base.sh +++ b/modules/by-name/lf/lf/commands/base.sh @@ -41,11 +41,11 @@ prompt() { # set -- "$@" "$file" # done < "$(echo "$fx" | tmp)" tmp() { - __base_tmp_temporary_file="$(mktemp --tmpdir="$__base_tmp_temporary_directory")" + __base_tmp_temporary_file="$(mktemp -t --tmpdir="$__base_tmp_temporary_directory" lf_commands_tmp_fun_XXXXXXXX )" cat >"$__base_tmp_temporary_file" echo "$__base_tmp_temporary_file" } -__base_tmp_temporary_directory="$(mktemp --directory)" +__base_tmp_temporary_directory="$(mktemp -t --directory lf_commands_tmp_fun_XXXXXXXXXXXX)" trap 'rm --recursive "$__base_tmp_temporary_directory"' EXIT # Run a lf command on the current lf client diff --git a/modules/by-name/lf/lf/commands/default.nix b/modules/by-name/lf/lf/commands/default.nix index f953834b..055bfa2c 100644 --- a/modules/by-name/lf/lf/commands/default.nix +++ b/modules/by-name/lf/lf/commands/default.nix @@ -48,20 +48,20 @@ in { archive_compress = shell { name = "archive_compress"; - dependencies = with pkgs; [ - fzf - gnutar - xz - p7zip - zip + dependencies = [ + pkgs.fzf + pkgs.gnutar + pkgs.xz + pkgs.p7zip + pkgs.zip ]; }; archive_decompress = pipe { name = "archive_decompress"; - dependencies = with pkgs; [ - gnutar - unzip - p7zip + dependencies = [ + pkgs.gnutar + pkgs.unzip + pkgs.p7zip ]; }; @@ -122,10 +122,10 @@ in { }; set_wallpaper = pipe { name = "set_wallpaper"; - dependencies = with pkgs; [ - river # for `riverctl` - swaybg - procps + dependencies = [ + pkgs.river-classic # for `riverctl` + pkgs.swaybg + pkgs.procps ]; }; @@ -140,16 +140,28 @@ in { }; trash_clear = shell { name = "trash_clear"; - dependencies = with pkgs; [conceal fzf gawk trashy gnused]; + dependencies = [ + pkgs.conceal + pkgs.fzf + pkgs.gawk + pkgs.trashy + pkgs.gnused + ]; }; trash_restore = shell { name = "trash_restore"; - dependencies = with pkgs; [conceal fzf gawk trashy gnused]; + dependencies = [ + pkgs.conceal + pkgs.fzf + pkgs.gawk + pkgs.trashy + pkgs.gnused + ]; }; view_file = async { name = "view_file"; - dependencies = with pkgs; [file]; + dependencies = [pkgs.file]; keepPath = true; }; } diff --git a/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix b/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix index e30aab3e..bc8abf8f 100644 --- a/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix +++ b/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix @@ -14,7 +14,7 @@ matches.mime = ["application/pdf"]; priority = 1; dependencies = [ - pkgs.poppler_utils # for `pdftoppm` + pkgs.poppler-utils # for `pdftoppm` pkgs.chafa pkgs.gnused pkgs.coreutils diff --git a/modules/by-name/lf/lf/ctpv/prev/audio/audio.sh b/modules/by-name/lf/lf/ctpv/prev/audio/audio.sh index 324a2170..22894ca2 100644 --- a/modules/by-name/lf/lf/ctpv/prev/audio/audio.sh +++ b/modules/by-name/lf/lf/ctpv/prev/audio/audio.sh @@ -19,7 +19,7 @@ audio() { ffmpegthumbnailer -i "$f" -s 0 -q 5 -t 10 -o "$cache_f" 2>/dev/null } -x="$(ffmpeg -hide_banner -i "$f" 2>&1)" +x="$(ffprobe -hide_banner "$f" 2>&1)" printf '%s\n' "$x" y=$((y + $(printf '%s\n' "$x" | wc -l))) diff --git a/modules/by-name/lf/lf/ctpv/prev/text/default.nix b/modules/by-name/lf/lf/ctpv/prev/text/default.nix index edb6e09d..ca042646 100644 --- a/modules/by-name/lf/lf/ctpv/prev/text/default.nix +++ b/modules/by-name/lf/lf/ctpv/prev/text/default.nix @@ -21,7 +21,10 @@ bat = { priority = 0; previewer = ./bat.sh; - matches.mime = ["text/*"]; + matches.mime = [ + "text/*" + "application/postscript" + ]; dependencies = [ pkgs.bat ]; diff --git a/modules/by-name/lf/lf/keybindings/default.nix b/modules/by-name/lf/lf/keybindings/default.nix index d4c2a6a3..fbc33f6f 100644 --- a/modules/by-name/lf/lf/keybindings/default.nix +++ b/modules/by-name/lf/lf/keybindings/default.nix @@ -94,7 +94,7 @@ gc = "cd ~/.config"; gl = "cd ~/.local"; gE = "cd /etc"; - gd = "cd ${downloadDir}"; + gD = "cd ${downloadDir}"; "gU." = "cd /usr"; gUs = " cd /usr/share"; diff --git a/modules/by-name/lf/lf/module.nix b/modules/by-name/lf/lf/module.nix index ea9970ef..8dfd0c52 100644 --- a/modules/by-name/lf/lf/module.nix +++ b/modules/by-name/lf/lf/module.nix @@ -100,8 +100,8 @@ in { # and keep running through `autoquit = false`. # (Otherwise, the remote command is silently dropped: https://github.com/gokcehan/lf/issues/495) &{{ - tmp="$(mktemp)" - ${lib.getExe pkgs.lf-make-map} --depth 4 generate ~/media ~/repos ~/school >"$tmp" + tmp="$(mktemp -t lf_make_map_dynamic_mapping_source_XXXXX)" + ${lib.getExe pkgs.lf-make-map} --depth 4 generate ~/media ~/repos ~/documents >"$tmp" lf -remote "send $id source $tmp" sleep 1 diff --git a/modules/by-name/lf/lf/wrappers/ll/ll.sh b/modules/by-name/lf/lf/wrappers/ll/ll.sh index e012cffa..ce29fd97 100755 --- a/modules/by-name/lf/lf/wrappers/ll/ll.sh +++ b/modules/by-name/lf/lf/wrappers/ll/ll.sh @@ -10,7 +10,7 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -last_directory="$(mktemp)" +last_directory="$(mktemp -t ll_last_directory_XXXXXXX)" cleanup() { rm "$last_directory" } diff --git a/modules/by-name/lo/locale/keymaps/dvorak_modified.xkb b/modules/by-name/lo/locale/keymaps/dvorak_modified.xkb index 63f5d4fb..3893e605 100644 --- a/modules/by-name/lo/locale/keymaps/dvorak_modified.xkb +++ b/modules/by-name/lo/locale/keymaps/dvorak_modified.xkb @@ -2,10 +2,18 @@ partial alphanumeric_keys xkb_symbols "dvorak-modified" { name[Group1]= "Dvorak English with additional keys"; + // German + key <AC01> {[ a, A, adiaeresis, Adiaeresis]}; key <AC02> {[ o, O, odiaeresis, Odiaeresis]}; key <AC04> {[ u, U, udiaeresis, Udiaeresis]}; - key <AC01> {type[Group1]="EIGHT_LEVEL", - [ a, A, adiaeresis, Adiaeresis, aring, Aring]}; + + // Missing Swedish letter (the rest is in the German ones) + key <AC07> {[ h, H, aring, Aring]}; + + // Slovenian + key <AC10> {[ s, S, scaron, Scaron]}; + key <AD08> {[ c, C, ccaron, Ccaron]}; + key <AB10> {[ z, Z, zcaron, Zcaron]}; include "us(dvorak)" diff --git a/modules/by-name/lo/locale/module.nix b/modules/by-name/lo/locale/module.nix index 17096731..3c9c646c 100644 --- a/modules/by-name/lo/locale/module.nix +++ b/modules/by-name/lo/locale/module.nix @@ -37,10 +37,10 @@ in { }; i18n = { - defaultLocale = "en_CA.UTF-8"; + defaultLocale = "sv_SE.UTF-8"; extraLocaleSettings = { - LANGUAGE = "en_CA:en_US:en"; - LC_TIME = "en_DK.UTF-8"; + LANGUAGE = "sv_SE:en_CA:en_US:en"; + LC_TIME = "sv_SE.UTF-8"; LC_COLLATE = "C.UTF-8"; }; }; diff --git a/modules/by-name/mp/mpd/module.nix b/modules/by-name/mp/mpd/module.nix index b7c7ab5a..37c648c6 100644 --- a/modules/by-name/mp/mpd/module.nix +++ b/modules/by-name/mp/mpd/module.nix @@ -71,6 +71,7 @@ in { extraConfig = '' metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc,comment" + # Updated by the beets `mpdupdate` plugin auto_update "no" @@ -82,6 +83,8 @@ in { replaygain "track" replaygain_limit "yes" + sticker_file "${cfg.directories.data}/sticker.sql" + #database { # plugin "simple" # path "~/.local/share/mpd/db diff --git a/modules/by-name/mp/mpdpopm/module.nix b/modules/by-name/mp/mpdpopm/module.nix new file mode 100644 index 00000000..3524554c --- /dev/null +++ b/modules/by-name/mp/mpdpopm/module.nix @@ -0,0 +1,65 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + config, + pkgs, + libraries, + lib, + ... +}: let + cfg = config.soispha.services.mpdpopm; + + settingsFormat = pkgs.formats.json {}; +in { + options.soispha.services.mpdpopm = { + enable = libraries.base.options.mkEnable "mpdpopm"; + + settings = lib.mkOption { + # Setting this type allows for correct merging behavior + inherit (settingsFormat) type; + default = {}; + description = '' + Configuration for foo, see + <link xlink:href="https://example.com/docs/foo"/> + for supported settings. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + soispha.services.mpdpopm.settings = { + version = "1"; + log = "${config.home-manager.users.soispha.xdg.dataHome}/mpdpopm/log"; + + conn.Local = { + path = config.home-manager.users.soispha.home.sessionVariables.MPD_HOST; + }; + + local_music_dir = config.soispha.services.mpd.directories.music; + }; + + home-manager.users.soispha = { + systemd.user.services.mpdpopm = { + Unit = { + Description = "mpdpopm ratings and playcounts for MPD"; + Requires = ["mpd.service"]; + After = ["mpd.service"]; + }; + + Service = { + Restart = "on-failure"; + ExecStart = "${lib.getExe' pkgs.mpdpopm "mpdpopmd"} --config ${settingsFormat.generate "config.json" cfg.settings}"; + }; + + Install = {WantedBy = ["default.target"];}; + }; + }; + }; +} diff --git a/modules/by-name/mp/mpv/module.nix b/modules/by-name/mp/mpv/module.nix index 7c8435d5..4416b295 100644 --- a/modules/by-name/mp/mpv/module.nix +++ b/modules/by-name/mp/mpv/module.nix @@ -46,8 +46,8 @@ in { max_width = 250; }; }; - scripts = with pkgs.mpvScripts; [ - thumbfast + scripts = [ + pkgs.mpvScripts.thumbfast ]; }; }; diff --git a/modules/by-name/ms/msr/module.nix b/modules/by-name/ms/msr/module.nix new file mode 100644 index 00000000..521b3a40 --- /dev/null +++ b/modules/by-name/ms/msr/module.nix @@ -0,0 +1,17 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.msr; +in { + options.soispha.msr = { + enable = (lib.mkEnableOption "msr") // {default = config.hardware.cpu.x86.msr.enable;}; + }; + + config = lib.mkIf cfg.enable { + users = { + groups.msr.gid = config.soispha.constants.ids.gids.msr; + }; + }; +} diff --git a/modules/by-name/ne/networking/module.nix b/modules/by-name/ne/networking/module.nix index 6f8633e8..075a1a5a 100644 --- a/modules/by-name/ne/networking/module.nix +++ b/modules/by-name/ne/networking/module.nix @@ -99,10 +99,18 @@ in { soispha.impermanence.directories = [ "/etc/NetworkManager" ]; - - users.users."${cfg.userName}".extraGroups = [ - "networkmanager" # allows to configure NetworkManager as this user - ]; + users = { + users = { + "${cfg.userName}".extraGroups = [ + "networkmanager" # allows to configure NetworkManager as this user + ]; + nm-iodine.uid = config.soispha.constants.ids.uids.nm-iodine; + wpa_supplicant.uid = config.soispha.constants.ids.uids.wpa_supplicant; + }; + groups = { + wpa_supplicant.gid = config.soispha.constants.ids.gids.wpa_supplicant; + }; + }; }) ]); } diff --git a/modules/by-name/ni/nix-index/command_not_found.sh b/modules/by-name/ni/nix-index/command_not_found.sh index f650cf7b..579f9db4 100644 --- a/modules/by-name/ni/nix-index/command_not_found.sh +++ b/modules/by-name/ni/nix-index/command_not_found.sh @@ -26,12 +26,12 @@ command_not_found_handle() { toplevel=nixpkgs # nixpkgs should always be available even in NixOS cmd="$1" - attrs=$(nix-locate --minimal --no-group --type x --type s --top-level --whole-name --at-root "/bin/$cmd") + attrs=$(nix-locate --minimal --no-group --type x --type s --whole-name --at-root "/bin/$cmd") len=$(if [ -n "$attrs" ]; then echo "$attrs" | wc -l; else echo 0; fi) case "$len" in 0) - eprintln "$cmd: command not found" + printf "%s: command not found\n" "$cmd" >&2 ;; 1) # If only one package provides this, then we can invoke it diff --git a/modules/by-name/ni/nix-index/module.nix b/modules/by-name/ni/nix-index/module.nix index 06acfc8a..5ddaece3 100644 --- a/modules/by-name/ni/nix-index/module.nix +++ b/modules/by-name/ni/nix-index/module.nix @@ -23,7 +23,7 @@ in { soispha.programs.zsh.integrations.nix-index = ./command_not_found.sh; home-manager.users.soispha = { imports = [ - modules.nix-index-database.hmModules.nix-index + modules.nix-index-database.homeModules.nix-index ]; programs.nix-index = { diff --git a/modules/by-name/ni/nixos-option/module.nix b/modules/by-name/ni/nixos-option/module.nix new file mode 100644 index 00000000..0053d357 --- /dev/null +++ b/modules/by-name/ni/nixos-option/module.nix @@ -0,0 +1,18 @@ +{ + config, + lib, + libraries, + ... +}: let + cfg = config.soispha.programs.nixos-option; +in { + options.soispha.programs.nixos-option = { + enable = libraries.base.options.mkEnable "nixos-option"; + }; + + config = lib.mkIf cfg.enable { + # NOTE: We disable nixos-option here explicitly, because I never used it, and it + # depends on cppnix. <2025-12-11> + system.tools.nixos-option.enable = false; + }; +} diff --git a/modules/by-name/ni/nixos-shell/module.nix b/modules/by-name/ni/nixos-shell/module.nix index 219f080d..8dda2890 100644 --- a/modules/by-name/ni/nixos-shell/module.nix +++ b/modules/by-name/ni/nixos-shell/module.nix @@ -91,7 +91,7 @@ in { modules = [ { # TODO(@bpeetz): This should be bumped each release. <2025-05-17> - system.stateVersion = "25.05"; + system.stateVersion = "25.11"; } cfg.configuration.value diff --git a/modules/by-name/ni/nixos-shell/nixos-shell.sh b/modules/by-name/ni/nixos-shell/nixos-shell.sh index 390e60b1..3b34019a 100755 --- a/modules/by-name/ni/nixos-shell/nixos-shell.sh +++ b/modules/by-name/ni/nixos-shell/nixos-shell.sh @@ -10,7 +10,7 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -SHARED_DIR="$(mktemp --directory)" +SHARED_DIR="$(mktemp -t --directory "nixos_shell_XXXXXXXXX")" cleanup() { rm --recursive "$SHARED_DIR" } diff --git a/modules/by-name/ni/nixpkgs/config.nix b/modules/by-name/ni/nixpkgs/config.nix deleted file mode 100644 index ea8f3c45..00000000 --- a/modules/by-name/ni/nixpkgs/config.nix +++ /dev/null @@ -1,43 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{ - cfg, - lib, - packageSets, - ... -}: let - myPkgsOverlay = self: super: packageSets.soispha; -in { - nixpkgs = { - hostPlatform = cfg.systemName; - - overlays = [ - myPkgsOverlay - ]; - - config = { - # TODO: this fails because of the root tempsize, which should be increased - # contentAddressedByDefault = true; - - hostSystem = cfg.systemName; - - allowUnfreePredicate = pkg: - builtins.elem (lib.getName pkg) [ - "pypemicro" # required by pynitrokey - - # TODO(@bpeetz): Allow moving them to their respective module. <2025-04-25> - "steam" - "steam-unwrapped" - "steam-original" - "steam-run" - ]; - }; - }; -} diff --git a/modules/by-name/ni/nixpkgs/module.nix b/modules/by-name/ni/nixpkgs/module.nix index fcde9505..502bcff2 100644 --- a/modules/by-name/ni/nixpkgs/module.nix +++ b/modules/by-name/ni/nixpkgs/module.nix @@ -44,12 +44,6 @@ in { allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "pypemicro" # required by pynitrokey - - # TODO(@bpeetz): Allow moving them to their respective module. <2025-04-25> - "steam" - "steam-unwrapped" - "steam-original" - "steam-run" ]; }; }; diff --git a/modules/by-name/ns/nscd/module.nix b/modules/by-name/ns/nscd/module.nix new file mode 100644 index 00000000..94ca4874 --- /dev/null +++ b/modules/by-name/ns/nscd/module.nix @@ -0,0 +1,18 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.nscd; +in { + options.soispha.nscd = { + enable = (lib.mkEnableOption "nscd") // {default = config.services.nscd.enableNsncd;}; + }; + + config = lib.mkIf cfg.enable { + users = { + users.nscd.uid = config.soispha.constants.ids.uids.nscd; + groups.nscd.gid = config.soispha.constants.ids.gids.nscd; + }; + }; +} diff --git a/modules/by-name/nv/nvim/module.nix b/modules/by-name/nv/nvim/module.nix index 9b44906a..81d7febf 100644 --- a/modules/by-name/nv/nvim/module.nix +++ b/modules/by-name/nv/nvim/module.nix @@ -44,7 +44,7 @@ in { config = lib.mkIf cfg.enable { home-manager.users.soispha = { imports = [ - modules.nixvim.homeManagerModules.nixvim + modules.nixvim.homeModules.nixvim ]; home.sessionVariables = { @@ -73,21 +73,21 @@ in { --------------------------------------------------------------------------- ''; - extraPackages = with pkgs; [ + extraPackages = [ /* These are mostly linters and formatters used for different file types. Including them here is fine, as they are not necessarily-sync able to different people. */ # nix - alejandra - statix + pkgs.alejandra + pkgs.statix # yaml - yamllint + pkgs.yamllint # shell - shellcheck - shfmt + pkgs.shellcheck + pkgs.shfmt ]; }; }; diff --git a/modules/by-name/nv/nvim/plgs/femaco/default.nix b/modules/by-name/nv/nvim/plgs/femaco/default.nix index 0388844a..eac6b393 100644 --- a/modules/by-name/nv/nvim/plgs/femaco/default.nix +++ b/modules/by-name/nv/nvim/plgs/femaco/default.nix @@ -14,7 +14,8 @@ }: let cfg = config.soispha.programs.nvim; in { - home-manager.users.soispha.programs.nixvim = lib.mkIf cfg.enable { + # TODO: Re-active this plugin, when it does no longer depend on `treesitter-legacy` <2026-01-18> + home-manager.users.soispha.programs.nixvim = lib.mkIf false { plugins.femaco = { enable = true; }; diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix index fba1113f..3d55adfa 100644 --- a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix +++ b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix @@ -25,9 +25,9 @@ in { cmd = {"openscad-lsp", "--stdio", "--fmt-style", "WebKit"}, } ''; - extraPackages = with pkgs; [ - openscad-lsp - clang-tools # Need to satisfy `clang-format` (which is used by openscad-lsp) + extraPackages = [ + pkgs.openscad-lsp + pkgs.clang-tools # Need to satisfy `clang-format` (which is used by openscad-lsp) ]; }; } diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix index 82d3b5a9..404784f1 100644 --- a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix +++ b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix @@ -24,8 +24,8 @@ in { require('lspconfig').quick_lint_js.setup{ } ''; - extraPackages = with pkgs; [ - quick-lint-js + extraPackages = [ + pkgs.quick-lint-js ]; }; } diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/ruff-lsp.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/ruff-lsp.nix index 4f9834ee..3ad3ad8f 100644 --- a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/ruff-lsp.nix +++ b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/ruff-lsp.nix @@ -21,6 +21,6 @@ in { enable = true; }; }; - extraPackages = with pkgs; [ruff]; + extraPackages = [pkgs.ruff]; }; } diff --git a/modules/by-name/nv/nvim/plgs/lspkind/default.nix b/modules/by-name/nv/nvim/plgs/lspkind/default.nix index 6e966ad1..3846fa32 100644 --- a/modules/by-name/nv/nvim/plgs/lspkind/default.nix +++ b/modules/by-name/nv/nvim/plgs/lspkind/default.nix @@ -16,6 +16,6 @@ in { home-manager.users.soispha.programs.nixvim.plugins.lspkind = lib.mkIf cfg.enable { enable = true; - preset = "default"; # "codicons" is only for a font patched with vscode-codeicons. + settings.preset = "default"; # "codicons" is only for a font patched with vscode-codeicons. }; } diff --git a/modules/by-name/nv/nvim/plgs/neorg/default.nix b/modules/by-name/nv/nvim/plgs/neorg/default.nix index ea451d3a..5ed87f0e 100644 --- a/modules/by-name/nv/nvim/plgs/neorg/default.nix +++ b/modules/by-name/nv/nvim/plgs/neorg/default.nix @@ -14,7 +14,9 @@ }: let cfg = config.soispha.programs.nvim; in { - home-manager.users.soispha.programs.nixvim = lib.mkIf cfg.enable { + # TODO: Re-active, whence neorg actually builds with all the treesitter grammars + # activated <2026-01-18> + home-manager.users.soispha.programs.nixvim = lib.mkIf false { extraFiles = { "ftplugin/norg.lua".text = '' ${lib.strings.fileContents ./key_mappings.lua} diff --git a/modules/by-name/nv/nvim/plgs/treesitter/default.nix b/modules/by-name/nv/nvim/plgs/treesitter/default.nix index ed1499f8..1377cb8a 100644 --- a/modules/by-name/nv/nvim/plgs/treesitter/default.nix +++ b/modules/by-name/nv/nvim/plgs/treesitter/default.nix @@ -26,45 +26,23 @@ in { plugins.treesitter = { enable = true; + highlight.enable = true; + indent.enable = true; + folding.enable = false; + # inject nixvim specific highlighting (eg in extraConfigLua). nixvimInjections = true; - nixGrammars = true; - grammarPackages = - # Append the `tree-sitter-yts` grammar to all the other grammars. - # TODO: Find a better way to do this. <2024-11-08> - config.home-manager.users.soispha.programs.nixvim.plugins.treesitter.package.passthru.allGrammars - ++ [pkgs.tree-sitter-yts]; - - settings = { - auto_install = false; - ensureInstalled = "all"; - indent.enable = true; - disabledLanguages = []; + grammarPackages = pkgs.vimPlugins.nvim-treesitter.allGrammars ++ [pkgs.tree-sitter-yts]; - highlight = { - enable = true; - disable = []; + # Register the parser to filetype + languageRegister.yts = "yts"; + }; - # Setting this to true will run `:h syntax` and tree-sitter at the same time. - # Set this to `true` if you depend on 'syntax' being enabled (like for indentation). - # Using this option may slow down your editor; and you may see some duplicate highlights. - # Instead of true it can also be a list of languages - additionalVimRegexHighlighting = []; - }; + # TODO: Is that necessary? <2026-01-18> + # extraPlugins = [pkgs.tree-sitter-yts]; - incrementalSelection = { - enable = true; - keymaps = { - # TODO: include these in the which-key description - initSelection = "gnn"; # set to `false` to disable one of the mappings - nodeIncremental = "grn"; - scopeIncremental = "grc"; - nodeDecremental = "grm"; - }; - }; - }; - }; + # TODO: Remove this by moving the queries in the package to `queries/<lang>/...`' <2026-01-18> extraFiles = { "queries/yts/highlights.scm".text = '' ${lib.strings.fileContents "${pkgs.tree-sitter-yts}/queries/highlights.scm"} diff --git a/modules/by-name/nv/nvim/plgs/vim-tex/default.nix b/modules/by-name/nv/nvim/plgs/vim-tex/default.nix index 739b69d9..71422f94 100644 --- a/modules/by-name/nv/nvim/plgs/vim-tex/default.nix +++ b/modules/by-name/nv/nvim/plgs/vim-tex/default.nix @@ -14,7 +14,8 @@ }: let cfg = config.soispha.programs.nvim; in { - home-manager.users.soispha.programs.nixvim = lib.mkIf cfg.enable { + # TODO: Do I still need this module at all? <2026-01-18> + home-manager.users.soispha.programs.nixvim = lib.mkIf false { opts.conceallevel = 0; plugins.vimtex = { diff --git a/modules/by-name/oo/oomd/module.nix b/modules/by-name/oo/oomd/module.nix new file mode 100644 index 00000000..ca332939 --- /dev/null +++ b/modules/by-name/oo/oomd/module.nix @@ -0,0 +1,18 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.systemd.oomd; +in { + options.soispha.systemd.oomd = { + enable = (lib.mkEnableOption "oomd") // {default = config.systemd.oomd.enable;}; + }; + + config = lib.mkIf cfg.enable { + users = { + users.systemd-oom.uid = config.soispha.constants.ids.uids.systemd-oom; + groups.systemd-oom.gid = config.soispha.constants.ids.gids.systemd-oom; + }; + }; +} diff --git a/modules/by-name/op/openssh/module.nix b/modules/by-name/op/openssh/module.nix index 97cf7fd7..f77c357b 100644 --- a/modules/by-name/op/openssh/module.nix +++ b/modules/by-name/op/openssh/module.nix @@ -7,18 +7,36 @@ # # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{...}: { - services.openssh = { - enable = true; - hostKeys = [ - { - path = "/srv/sshd/ssh_host_ed25519_key"; - rounds = 1000; - type = "ed25519"; - } - ]; - settings = { - PasswordAuthentication = false; +{ + config, + lib, + libraries, + ... +}: let + cfg = config.soispha.services.openssh; +in { + options.soispha.services.openssh = { + enable = libraries.base.options.mkEnable "openssh"; + }; + + config = lib.mkIf cfg.enable { + services.openssh = { + enable = true; + hostKeys = [ + { + path = "/srv/sshd/ssh_host_ed25519_key"; + rounds = 1000; + type = "ed25519"; + } + ]; + + settings = { + PasswordAuthentication = false; + }; + }; + users = { + users.sshd.uid = config.soispha.constants.ids.uids.sshd; + groups.sshd.gid = config.soispha.constants.ids.gids.sshd; }; }; } diff --git a/modules/by-name/po/polkit/module.nix b/modules/by-name/po/polkit/module.nix index c6d1c750..d8dd51b0 100644 --- a/modules/by-name/po/polkit/module.nix +++ b/modules/by-name/po/polkit/module.nix @@ -17,7 +17,10 @@ in { options.soispha.polkit = { enable = lib.mkEnableOption "polkit"; }; + config = lib.mkIf cfg.enable { security.polkit.enable = true; + + users.groups.polkituser.gid = config.soispha.constants.ids.gids.polkituser; }; } diff --git a/modules/by-name/pi/printing/module.nix b/modules/by-name/pr/printing/module.nix index cfcd2154..2e230570 100644 --- a/modules/by-name/pi/printing/module.nix +++ b/modules/by-name/pr/printing/module.nix @@ -19,6 +19,10 @@ in { }; config = lib.mkIf cfg.enable { + soispha.impermanence.directories = [ + "/var/lib/cups" + ]; + services.avahi = { enable = true; nssmdns4 = true; @@ -26,29 +30,21 @@ in { openFirewall = true; }; + users = { + users.avahi.uid = config.soispha.constants.ids.uids.avahi; + groups.avahi.gid = config.soispha.constants.ids.gids.avahi; + groups.lpadmin.gid = config.soispha.constants.ids.gids.lpadmin; + }; + services.printing = { enable = true; startWhenNeeded = true; webInterface = true; # deletes `/var/cache/cups`, `/var/lib/cups` and `/var/spool/cups` on cups startup - stateless = true; + stateless = false; drivers = []; }; - - hardware = { - printers = { - ensurePrinters = [ - { - name = "Brother"; - description = "Brother DCP-9022CDW"; - model = "everywhere"; - deviceUri = "dnssd://Brother%20DCP-9022CDW._ipp._tcp.local/?uuid=e3248000-80ce-11db-8000-30055c773bcf"; - } - ]; - ensureDefaultPrinter = "Brother"; - }; - }; }; } diff --git a/modules/by-name/qu/qutebrowser/include/redirects.py b/modules/by-name/qu/qutebrowser/include/redirects.py index 23456a25..63a44ecf 100644 --- a/modules/by-name/qu/qutebrowser/include/redirects.py +++ b/modules/by-name/qu/qutebrowser/include/redirects.py @@ -38,7 +38,7 @@ REDIRECT_MAP: typing.Dict[str, typing.Callable[..., typing.Optional[bool]]] = { # Source: https://libredirect.github.io/ "medium.com": partial(farside_redir, "scribe"), "stackoverflow.com": partial(farside_redir, "anonymousoverflow"), - "goodreads.com": partial(farside_redir, "biblioreads"), + # "goodreads.com": partial(farside_redir, "biblioreads"), } diff --git a/modules/by-name/qu/qutebrowser/settings/default.nix b/modules/by-name/qu/qutebrowser/settings/default.nix index a637796f..282c5816 100644 --- a/modules/by-name/qu/qutebrowser/settings/default.nix +++ b/modules/by-name/qu/qutebrowser/settings/default.nix @@ -7,7 +7,7 @@ second = 1000 * millisecond; wordlist = - pkgs.runCommandNoCCLocal "wordlist" { + pkgs.runCommandLocal "wordlist" { nativeBuildInputs = [pkgs.python3]; } '' @@ -410,8 +410,7 @@ in { session = { default_name = null; - # TODO(@bpeetz): See https://github.com/qutebrowser/qutebrowser/issues/67 <2025-06-04> - lazy_restore = false; + lazy_restore = true; }; spellcheck = { @@ -497,10 +496,10 @@ in { open_base_url = false; # Of search engine. searchengines = rec { - DEFAULT = leta; + DEFAULT = duckduckgo; - leta = "https://leta.mullvad.net/search?q={}"; - "@ls" = leta; + duckduckgo = "https://duckduckgo.com/search?q={}"; + "@du" = duckduckgo; # NIX "@np" = "https://search.nixos.org/packages?type=packages&query={}"; # Nix packages diff --git a/modules/by-name/re/resolvconf/module.nix b/modules/by-name/re/resolvconf/module.nix new file mode 100644 index 00000000..e1817e2b --- /dev/null +++ b/modules/by-name/re/resolvconf/module.nix @@ -0,0 +1,15 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.resolvconf; +in { + options.soispha.resolvconf = { + enable = lib.mkEnableOption "resolvconf" // {default = config.networking.resolvconf.enable;}; + }; + + config = lib.mkIf cfg.enable { + users.groups.resolvconf.gid = config.soispha.constants.ids.gids.resolvconf; + }; +} diff --git a/modules/by-name/ri/river/init_base.sh b/modules/by-name/ri/river/init_base.sh index b68d147b..edd7827f 100755 --- a/modules/by-name/ri/river/init_base.sh +++ b/modules/by-name/ri/river/init_base.sh @@ -10,8 +10,9 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -# NOTE: Keep this in sync with the file from `river-start` <2025-02-03> -RIVER_LOG_FILE="$HOME/.cache/river/log" +RIVER_LOG_FILE="$HOME/.cache/river/init-log" + +echo >"$RIVER_LOG_FILE" err_fail() { if ! "$@"; then diff --git a/modules/by-name/ri/river/keymap.nix b/modules/by-name/ri/river/keymap.nix new file mode 100644 index 00000000..9bcbf387 --- /dev/null +++ b/modules/by-name/ri/river/keymap.nix @@ -0,0 +1,191 @@ +{ + lib, + pkgs, + libraries, + config, + ... +}: let + index2tag = input: builtins.toString (libraries.base.pow 2 (input - 1)); + + mkTagCommand = name: index: [name (index2tag index)]; + mkSpawn' = pkg: binaryName: args: further: (further + // (let + maybeSpace = + if args == "" + then "" + else " "; + maybeQuote = + if args == "" + then "" + else "\""; + in { + command = [ + "spawn" + "${lib.getExe pkgs.notify-run} \"${lib.getExe' pkg binaryName}\"${maybeSpace}${maybeQuote}${args}${maybeQuote}" + ]; + + description = "${binaryName}${maybeSpace}${args}"; + })); + + mkSpawn = pkg: args: further: (mkSpawn' pkg pkg.meta.mainProgram args further); + + setMode = path: { + command = [ + "enter-mode" + (builtins.concatStringsSep "" path) + ]; + }; + + cfg = config.soispha.programs.river; +in { + soispha.programs.river.init.mappings.keymap = lib.mkIf cfg.enable (lib.fixedPoints.fix + (self: { + "<LEFT_SUPER>" = { + # Spawn standard programs + "r" = { + "a" = mkSpawn pkgs.alacritty "" {}; + "b" = mkSpawn pkgs.tskm "open select" {once = true;}; + "k" = mkSpawn pkgs.keepassxc "" {once = true;}; + "s" = mkSpawn pkgs.signal-desktop "" {once = true;}; + "p" = mkSpawn pkgs.screenshot_persistent "" {once = true;}; + "i" = mkSpawn pkgs.anki "" {once = true;}; + }; + + # Client changes + "c" = { + "F" = setMode ["<LEFT_META>" "f"]; + + "f" = ["toggle-fullscreen"]; + "c" = ["close"]; + " " = ["toggle-float"]; + "<ENTER>" = ["zoom"]; + }; + + # This is a fill in for <Super-L>, as that is otherwise nearly impossible to input. + "l" = self."<LEFT_SUPER>".x.l; + + # River compositor control. + "x" = { + "q" = ["exit"]; + "l" = mkSpawn pkgs.lock "" {once = true;}; + "h" = mkSpawn' pkgs.procps "pkill" "--signal USR1 i3bar-river" {once = true;}; + }; + + # Media control + "m" = { + "u" = mkSpawn' pkgs.wireplumber "wpctl" "set-volume @DEFAULT_SINK@ 5%+" {}; + "d" = mkSpawn' pkgs.wireplumber "wpctl" "set-volume @DEFAULT_SINK@ 5%-" {}; + "m" = mkSpawn' pkgs.wireplumber "wpctl" "set-mute @DEFAULT_SINK@ toggle" {}; + "n" = mkSpawn pkgs.mpp "next" {}; + "p" = mkSpawn pkgs.mpp "prev" {}; + "t" = mkSpawn pkgs.mpp "toggle" {}; + "g" = mkSpawn' pkgs.mpdpopm "mpdpopm" "rating inc" {once = true;}; + "b" = mkSpawn' pkgs.mpdpopm "mpdpopm" "rating decr" {once = true;}; + }; + + # Select tags for view. + "v" = { + "c" = setMode ["<LEFT_META>" "c"]; + "f" = setMode ["<LEFT_META>" "f"]; + + "t" = ["swap" "next"]; + "n" = ["swap" "previous"]; + "T" = ["send-to-output" "next"]; + "N" = ["send-to-output" "previous"]; + + "0" = ["set-view-tags" (builtins.toString ((libraries.base.pow 2 32) - 1))]; + "p" = ["send-to-previous-tags"]; + + "1" = mkTagCommand "set-view-tags" 1; + "2" = mkTagCommand "set-view-tags" 2; + "3" = mkTagCommand "set-view-tags" 3; + "4" = mkTagCommand "set-view-tags" 4; + "5" = mkTagCommand "set-view-tags" 5; + "6" = mkTagCommand "set-view-tags" 6; + "7" = mkTagCommand "set-view-tags" 7; + "8" = mkTagCommand "set-view-tags" 8; + "9" = mkTagCommand "set-view-tags" 9; + + # Add tags to view. + "a" = { + "1" = mkTagCommand "toggle-view-tags" 1; + "2" = mkTagCommand "toggle-view-tags" 2; + "3" = mkTagCommand "toggle-view-tags" 3; + "4" = mkTagCommand "toggle-view-tags" 4; + "5" = mkTagCommand "toggle-view-tags" 5; + "6" = mkTagCommand "toggle-view-tags" 6; + "7" = mkTagCommand "toggle-view-tags" 7; + "8" = mkTagCommand "toggle-view-tags" 8; + "9" = mkTagCommand "toggle-view-tags" 9; + }; + }; + + # Select tags to focus, + # and change focus + "f" = { + "c" = setMode ["<LEFT_META>" "c"]; + "v" = setMode ["<LEFT_META>" "v"]; + + "t" = ["focus-view" "next"]; + "n" = ["focus-view" "previous"]; + "T" = ["focus-output" "next"]; + "N" = ["focus-output" "previous"]; + + "0" = ["set-focused-tags" (builtins.toString ((libraries.base.pow 2 32) - 1))]; + "p" = ["focus-previous-tags"]; + + "1" = mkTagCommand "set-focused-tags" 1; + "2" = mkTagCommand "set-focused-tags" 2; + "3" = mkTagCommand "set-focused-tags" 3; + "4" = mkTagCommand "set-focused-tags" 4; + "5" = mkTagCommand "set-focused-tags" 5; + "6" = mkTagCommand "set-focused-tags" 6; + "7" = mkTagCommand "set-focused-tags" 7; + "8" = mkTagCommand "set-focused-tags" 8; + "9" = mkTagCommand "set-focused-tags" 9; + }; + }; + + # Screen locking + "<Super-l>" = self."<LEFT_SUPER>".x.l; + + # Audio + "<MEDIA_RAISEVOLUME>" = + self."<LEFT_SUPER>".m.u + // { + allow_locked = true; + }; + "<MEDIA_LOWERVOLUME>" = + self."<LEFT_SUPER>".m.d + // { + allow_locked = true; + }; + "<MEDIA_MUTEVOLUME>" = + self."<LEFT_SUPER>".m.m + // { + allow_locked = true; + }; + "<MEDIA_TRACKNEXT>" = + self."<LEFT_SUPER>".m.n + // { + allow_locked = true; + }; + "<MEDIA_TRACKPREVIOUS>" = + self."<LEFT_SUPER>".m.p + // { + allow_locked = true; + }; + "<MEDIA_PLAY>" = + self."<LEFT_SUPER>".m.t + // { + allow_locked = true; + }; + + # Mouse + "<Alt-<MOUSE_LEFT>>" = ["move-view"]; + "<Alt-<MOUSE_RIGHT>>" = ["resize-view"]; + + # Screenshot + "<PRINTSCREEN>" = self."<LEFT_SUPER>".r.p; + })); +} diff --git a/modules/by-name/ri/river/module.nix b/modules/by-name/ri/river/module.nix index 38d4bdef..893fdc77 100644 --- a/modules/by-name/ri/river/module.nix +++ b/modules/by-name/ri/river/module.nix @@ -17,7 +17,7 @@ }: let cfg = config.soispha.programs.river; esa = lib.strings.escapeShellArg; - riverctl = lib.getExe' pkgs.river "riverctl"; + riverctl = lib.getExe' cfg.package "riverctl"; mkOutputFlags = output: flags: let expandedFlags = builtins.concatStringsSep " " (lib.attrsets.mapAttrsToList (flag: value: "--${esa flag} ${esa value}") flags); @@ -36,10 +36,22 @@ longRunningPrograms = builtins.concatStringsSep "\n" (builtins.map mkLrProgram cfg.init.backgroundStart); keymapFormat = pkgs.formats.json {}; + keymapGenerate = name: value: + pkgs.runCommandLocal "mk-${name}-and-check" { + nativeBuildInputs = [pkgs.river-mk-keymap]; + preferLocalBuild = true; + + env = { + JSON_FILE = keymapFormat.generate name value; + }; + } '' + river-mk-keymap --keymap "$JSON_FILE" init --dry-run; + cp "$JSON_FILE" "$out" + ''; keymappings = '' err_fail ${riverctl} keyboard-layout ${esa cfg.init.mappings.layout} - err_fail ${lib.getExe pkgs.river-mk-keymap} ${keymapFormat.generate "keys.json" cfg.init.mappings.keymap} + err_fail ${lib.getExe pkgs.river-mk-keymap} --keymap ${keymapGenerate "keys.json" cfg.init.mappings.keymap} init ''; mkRule = { @@ -72,6 +84,8 @@ in { options.soispha.programs.river = { enable = lib.mkEnableOption "river"; + package = lib.mkPackageOption pkgs "river-classic" {}; + unicodeInput = { enable = lib.mkEnableOption "udev rules for rawhid based unicode input"; }; @@ -175,6 +189,10 @@ in { }; }; + imports = [ + ./keymap.nix + ]; + config = lib.mkIf cfg.enable { services.udev.packages = lib.mkIf cfg.unicodeInput.enable [externalBinaries.qmk_firmware.packages.${system}.qmk_unicode_type]; @@ -194,6 +212,7 @@ in { home.packages = [ (pkgs.callPackage ./river-start/package.nix {}) + pkgs.swallow ]; xdg.configFile."river/init" = { @@ -219,7 +238,7 @@ in { + mkHeading "Background services" longRunningPrograms + mkHeading "Layout Setup" '' err_fail ${riverctl} default-layout rivertile - ${lib.getExe' pkgs.river "rivertile"} -main-ratio 0.5 -view-padding 1 -outer-padding 0 + ${lib.getExe' cfg.package "rivertile"} -main-ratio 0.5 -view-padding 1 -outer-padding 0 ''; }; }; diff --git a/modules/by-name/ri/river/river-start/package.nix b/modules/by-name/ri/river/river-start/package.nix index 10957cc0..3ae204b7 100644 --- a/modules/by-name/ri/river/river-start/package.nix +++ b/modules/by-name/ri/river/river-start/package.nix @@ -9,13 +9,13 @@ # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. { writeShellApplication, - river, + river-classic, }: writeShellApplication { name = "river-start"; text = builtins.readFile ./river-start.sh; runtimeInputs = [ - river + river-classic ]; meta = { mainProgram = "river-start"; diff --git a/modules/by-name/ri/river/river-start/river-start.sh b/modules/by-name/ri/river/river-start/river-start.sh index b4c5b0a6..55eddfcf 100755 --- a/modules/by-name/ri/river/river-start/river-start.sh +++ b/modules/by-name/ri/river/river-start/river-start.sh @@ -10,8 +10,7 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -# NOTE: Keep this in sync with the file from `base_init.sh` <2025-02-03> -RIVER_LOG_FILE="$HOME/.cache/river/log" +RIVER_LOG_FILE="$HOME/.cache/river/wm-log" [ -d "$(dirname "$RIVER_LOG_FILE")" ] || mkdir --parents "$(dirname "$RIVER_LOG_FILE")" diff --git a/modules/by-name/ro/rofi/module.nix b/modules/by-name/ro/rofi/module.nix new file mode 100644 index 00000000..f2b404d6 --- /dev/null +++ b/modules/by-name/ro/rofi/module.nix @@ -0,0 +1,31 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + config, + lib, + libraries, + ... +}: let + cfg = config.soispha.programs.rofi; +in { + options.soispha.programs.rofi = { + enable = libraries.base.options.mkEnable "rofi"; + }; + + config = lib.mkIf cfg.enable { + home-manager.users.soispha = { + programs.rofi = { + enable = true; + terminal = lib.getExe config.soispha.programs.alacritty.package; + theme = ./nord-twoLines.rasi; + }; + }; + }; +} diff --git a/modules/home.legacy/conf/rofi/nord-twoLines.rasi b/modules/by-name/ro/rofi/nord-twoLines.rasi index 612b907f..612b907f 100644 --- a/modules/home.legacy/conf/rofi/nord-twoLines.rasi +++ b/modules/by-name/ro/rofi/nord-twoLines.rasi diff --git a/modules/home.legacy/conf/rofi/nord-twoLines.rasi.license b/modules/by-name/ro/rofi/nord-twoLines.rasi.license index eae6a84c..eae6a84c 100644 --- a/modules/home.legacy/conf/rofi/nord-twoLines.rasi.license +++ b/modules/by-name/ro/rofi/nord-twoLines.rasi.license diff --git a/modules/by-name/so/sound/module.nix b/modules/by-name/so/sound/module.nix index 622cc01d..06df63e5 100644 --- a/modules/by-name/so/sound/module.nix +++ b/modules/by-name/so/sound/module.nix @@ -9,13 +9,14 @@ # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. { config, + libraries, lib, ... }: let cfg = config.soispha.sound; in { options.soispha.sound = { - enable = lib.mkEnableOption "sound based on pipewire"; + enable = libraries.base.options.mkEnable "sound based on pipewire"; }; config = lib.mkIf cfg.enable { @@ -30,6 +31,11 @@ in { jack.enable = true; }; + users = { + users.rtkit.uid = config.soispha.constants.ids.uids.rtkit; + groups.rtkit.gid = config.soispha.constants.ids.gids.rtkit; + }; + # TODO: Find a better way to set the default volume <2024-03-10> # # environment.etc.pipewire-pulse-config = { diff --git a/modules/by-name/ss/ssh/module.nix b/modules/by-name/ss/ssh/module.nix index 91cc4aeb..f7218e36 100644 --- a/modules/by-name/ss/ssh/module.nix +++ b/modules/by-name/ss/ssh/module.nix @@ -14,6 +14,18 @@ ... }: let cfg = config.soispha.programs.ssh; + + mkDefaultMatchBlock = userKnownHostsFile: { + addKeysToAgent = "no"; + compression = true; + controlMaster = "no"; + controlPersist = "no"; + forwardAgent = false; + hashKnownHosts = false; + serverAliveCountMax = 3; + serverAliveInterval = 240; + inherit userKnownHostsFile; + }; in { options.soispha.programs.ssh = { enable = lib.mkEnableOption "ssh config"; @@ -34,21 +46,21 @@ in { home-manager.users = { root.programs.ssh = { enable = true; - compression = true; - hashKnownHosts = false; - serverAliveInterval = 240; - userKnownHostsFile = builtins.toString (pkgs.writeTextFile { - name = "root-known-hosts"; - text = cfg.rootKnownHosts; - }); + enableDefaultConfig = false; + + matchBlocks."*" = mkDefaultMatchBlock ( + builtins.toString (pkgs.writeTextFile { + name = "root-known-hosts"; + text = cfg.rootKnownHosts; + }) + ); }; soispha.programs.ssh = { enable = true; - compression = true; - hashKnownHosts = false; - serverAliveInterval = 240; - userKnownHostsFile = "${config.home-manager.users.soispha.xdg.dataHome}/ssh/known_hosts"; + enableDefaultConfig = false; + + matchBlocks."*" = mkDefaultMatchBlock "${config.home-manager.users.soispha.xdg.dataHome}/ssh/known_hosts"; }; }; }; diff --git a/modules/by-name/st/steam/module.nix b/modules/by-name/st/steam/module.nix index a02b2e4b..87cdc709 100644 --- a/modules/by-name/st/steam/module.nix +++ b/modules/by-name/st/steam/module.nix @@ -10,7 +10,6 @@ { lib, config, - pkgs, ... }: let cfg = config.soispha.programs.steam; @@ -22,8 +21,8 @@ in { config = lib.mkIf cfg.enable { nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ - # NOTE: These are not really applied. Look at - # <modules/by-name/ni/nixpkgs/config.nix> instead. <2025-04-25> + # NOTE: These are not really applied. Needs to be at + # <modules/by-name/ni/nixpkgs/config.nix> for some reason. <2025-04-25> "steam" "steam-unwrapped" "steam-original" diff --git a/modules/by-name/ta/taskwarrior/module.nix b/modules/by-name/ta/taskwarrior/module.nix index bb881768..d757be90 100644 --- a/modules/by-name/ta/taskwarrior/module.nix +++ b/modules/by-name/ta/taskwarrior/module.nix @@ -16,7 +16,7 @@ cfg = config.soispha.programs.taskwarrior; hooksDir = - pkgs.runCommandNoCCLocal "mk-taskwarrior-hooks" {} + pkgs.runCommandLocal "mk-taskwarrior-hooks" {} ('' mkdir "$out" '' diff --git a/modules/by-name/up/upower/module.nix b/modules/by-name/up/upower/module.nix new file mode 100644 index 00000000..f5a62ed7 --- /dev/null +++ b/modules/by-name/up/upower/module.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + ... +}: let + cfg = config.soispha.services.upower; +in { + options.soispha.services.upower = { + enable = lib.mkEnableOption "upower"; + }; + + config = lib.mkIf cfg.enable { + services.upower = { + enable = true; + usePercentageForPolicy = true; + + percentageLow = 15; + percentageAction = 10; + + criticalPowerAction = "Hibernate"; + }; + }; +} diff --git a/modules/by-name/us/users/module.nix b/modules/by-name/us/users/module.nix index 555e61f9..65b75d2e 100644 --- a/modules/by-name/us/users/module.nix +++ b/modules/by-name/us/users/module.nix @@ -25,7 +25,7 @@ in { }; groups = lib.mkOption { type = lib.types.listOf lib.types.str; - default = ["wheel"]; + default = ["wheel" "audio"]; description = "The groups the soispha user should be part of"; }; diff --git a/modules/by-name/xd/xdg/module.nix b/modules/by-name/xd/xdg/module.nix index 496ed129..9ff71ca0 100644 --- a/modules/by-name/xd/xdg/module.nix +++ b/modules/by-name/xd/xdg/module.nix @@ -100,20 +100,18 @@ in { }; config = { common = { - # NOTE: The next entry is supposedly needed for gtk based apps <2023-08-31> - default = ["wlr" "gtk"]; + default = ["wlr"]; "org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"]; }; # TODO: Also activate, when on another wlr-based compositor <2023-11-25> river = { - default = ["wlr" "gtk"]; + default = ["wlr"]; "org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"]; }; }; extraPortals = [ - pkgs.xdg-desktop-portal-gtk pkgs.xdg-desktop-portal-wlr pkgs.xdg-desktop-portal-termfilechooser ]; diff --git a/modules/by-name/ya/yambar/config/config.yml b/modules/by-name/ya/yambar/config/config.yml deleted file mode 100644 index 14da18fd..00000000 --- a/modules/by-name/ya/yambar/config/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - ---- -# Config file for yambar -# Note that this may be version-dependent, this file is written for v1.8.0 - -bar: - right: - # - network: - # name: wlp5s0 - # poll-interval: 10 - # content: - # map: - # on-click: /bin/sh -c "nmtui" - # conditions: - # ~carrier: {empty: {}} - # carrier: - # string: {text: " {ssid} ", deco: *combutil} - # - network: - # name: enp4s0 - # content: - # map: - # on-click: /bin/sh -c "nmtui" - # conditions: - # ~carrier: - # string: {text: " Eth failed ", deco: *combutil} - # carrier: {empty: {}} - # - script: # Grade average - # path: grade_average_script - # content: - # string: - # text: " {grade} " - # deco: *combmem - # - script: # tray - # path: /home/dt/.config/yambar/scripts/yambar-tray - # content: - # empty: {} - # - script: - # path: /home/dt/.config/yambar/scripts/yambar-tray-width - # poll-interval: 10 - # content: - # string: - # text: "{padding}" - # deco: *combmem diff --git a/modules/by-name/ya/yambar/module.nix b/modules/by-name/ya/yambar/module.nix deleted file mode 100644 index 40edf978..00000000 --- a/modules/by-name/ya/yambar/module.nix +++ /dev/null @@ -1,78 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{ - config, - pkgs, - lib, - ... -}: let - cfg = config.soispha.programs.yambar; - - makeScript = { - name, - dependencies, - ... - }: - lib.getExe (pkgs.writeShellApplication { - inherit name; - text = builtins.readFile ./scripts/${name}.sh; - runtimeInputs = dependencies ++ (builtins.attrValues {inherit (pkgs) dash;}); - }); - - mkWrapper = bin: cmd: - pkgs.writeShellScript cmd '' - ${bin} ${cmd} - ''; -in { - options.soispha.programs.yambar = { - enable = lib.mkEnableOption "yambar"; - - laptop = lib.mkEnableOption "laptop specific settings"; - backlight = lib.mkOption { - type = lib.types.str; - example = "intel_backlight"; - description = "Which backlight to query for the screen brightness"; - }; - }; - - config = lib.mkIf cfg.enable { - home-manager.users.soispha = { - programs.yambar = { - enable = true; - settings = import ./settings { - inherit lib; - inherit (cfg) laptop; - laptopBacklightName = cfg.backlight; - - scripts = { - mpd_song_name_script = makeScript { - dependencies = [pkgs.mpc]; - name = "mpd_song_name"; - }; - - volume_script = makeScript { - dependencies = with pkgs; [pulseaudio gawk coreutils]; - name = "sound-volume"; - }; - - cpu_script = mkWrapper (lib.getExe pkgs.yambar-modules) "cpu"; - - memory_script = mkWrapper (lib.getExe pkgs.yambar-modules) "memory"; - - disk_script = makeScript { - dependencies = with pkgs; [gawk btrfs-progs coreutils]; - name = "disk"; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/by-name/ya/yambar/scripts/disk.sh b/modules/by-name/ya/yambar/scripts/disk.sh deleted file mode 100755 index 4efe1384..00000000 --- a/modules/by-name/ya/yambar/scripts/disk.sh +++ /dev/null @@ -1,31 +0,0 @@ -#! /usr/bin/env dash - -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# shellcheck source=/dev/null -SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH - -# Main loop -while true; do - # vars - used_space=$(btrfs filesystem usage /srv 2>/dev/null | awk '{if ( /Used:/ ) { print $2 } } ' | head -n1) - all_space=$(btrfs filesystem usage /srv 2>/dev/null | awk '{if ( /Device size:/ ) { print $3 } } ' | head -n1 | tr -d "GiB") - - # Check space available (4) and percentage used (5) - spaceperc=$(echo "$(echo "$used_space" | tr -d "GiB")" "$all_space" | awk '{div=$1/$2;div *= 100; printf"%2d%%\n",div }') - - echo "diskspace|string|$used_space" - echo "diskperc|string|$spaceperc" - echo "" - sleep 1 -done - -# vim: ft=sh diff --git a/modules/by-name/ya/yambar/scripts/mpd_song_name.sh b/modules/by-name/ya/yambar/scripts/mpd_song_name.sh deleted file mode 100755 index 5c288d1a..00000000 --- a/modules/by-name/ya/yambar/scripts/mpd_song_name.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env dash - -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# shellcheck source=/dev/null -SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH - -while true; do - state="$(mpc status '%state%')" - - if [ "$state" = "playing" ]; then - song="$(mpc --format '[[%artist% - ]%title%]|[%file%]' current)" - echo "playing|bool|true" - echo "song|string|$song :: $(mpc status "%currenttime%/%totaltime%")" - else - echo "playing|bool|false" - fi - echo "" # commit - - sleep 2 -done - -# vim: ft=sh diff --git a/modules/by-name/ya/yambar/scripts/network.sh b/modules/by-name/ya/yambar/scripts/network.sh deleted file mode 100755 index d642e6c7..00000000 --- a/modules/by-name/ya/yambar/scripts/network.sh +++ /dev/null @@ -1,57 +0,0 @@ -#! /usr/bin/env dash - -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# shellcheck source=/dev/null -SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH - -retest=120 -retest_if_con_fails=10 -backend=nmcli - -case "$backend" in -"nmcli") # Test for connectivity with nmcli - while true; do - connection_status=$(nmcli networking connectivity) - if [ "$connection_status" = "full" ]; then - echo "internet|string|Connected" - echo "" - sleep $retest - else - echo "internet|string|Disconnected" - echo "" - sleep $retest_if_con_fails - fi - done - ;; -"ping") # Test for connectivity with ping - ip_address='8.8.8.8' - ping_number=3 - - while true; do - - ping_result=$(mktmp) - ping $ip_address -c $ping_number -q | awk 'BEGIN {FS="/"} END {print $5}' >"$ping_result" - - if [ "$(wc -l <"$ping_result")" -eq 0 ]; then - echo "med|string|No connection" - echo "" - sleep $retest_if_con_fails - else - echo "med|string|$(cat "$ping_result") ms" - echo "" - sleep $retest - fi - done - ;; -esac - -# vim: ft=sh diff --git a/modules/by-name/ya/yambar/scripts/sound-volume.sh b/modules/by-name/ya/yambar/scripts/sound-volume.sh deleted file mode 100755 index 9d9f7be8..00000000 --- a/modules/by-name/ya/yambar/scripts/sound-volume.sh +++ /dev/null @@ -1,30 +0,0 @@ -#! /usr/bin/env dash - -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# shellcheck source=/dev/null -SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH - -while true; do - volume="$(pactl get-sink-volume 0 | awk 'BEGIN { FS="/" } {gsub("%","",$2); gsub(" ","",$2)} {printf $2}')" - - if [ "$volume" -eq 0 ]; then - echo "muted|bool|true" - else - echo "volume|string|$volume" - echo "muted|bool|false" - fi - echo "" - - sleep 3 -done - -# vim: ft=sh diff --git a/modules/by-name/ya/yambar/settings/default.nix b/modules/by-name/ya/yambar/settings/default.nix deleted file mode 100644 index ebe17261..00000000 --- a/modules/by-name/ya/yambar/settings/default.nix +++ /dev/null @@ -1,383 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -# TODO: This should not do something depending on whether the host is a laptop or not. It -# should instead match on monitor-width or scale factor. <2024-11-29> -{ - lib, - laptop ? false, - laptopBacklightName ? null, - scripts, -}: let - mkUnderline = color: { - underline = { - inherit color; - size = 3; - }; - }; - - shellExec = cmd: ''sh -c "${cmd}"''; - - values = { - foreground = { - blue = "99d1dbff"; - focus = "e78284ff"; - green = "a6e3a1dd"; - lavendar = "b4befedd"; - mauve = "cba6f7dd"; - none = "00000000"; - normal = "c6ceefff"; - peach = "fab387dd"; - sapphire = "74c7ecdd"; - teal = "94e2d5dd"; - }; - font = { - main = "Source Code Pro:pixelsize=${ - builtins.toString ( - if laptop - then 22 - else 26 - ) - }"; - aws = "Font Awesome 5 Free:style=solid:pixelsize=${ - builtins.toString ( - if laptop - then 20 - else 23 - ) - }"; - }; - background = { - normal = "303446ff"; - tag = "585b70ff"; - tag2 = "45475aff"; - urgent = "e78284ff"; - }; - backgroundBlock = { - normal = {background = {color = values.background.normal;};}; - urgent = {background = {color = values.background.urgent;};}; - }; - underline = { - battery = mkUnderline values.foreground.sapphire; - clock = mkUnderline values.foreground.teal; - focused = mkUnderline values.foreground.focus; - resources = mkUnderline values.foreground.green; - title = mkUnderline values.background.tag; - urgent = mkUnderline values.foreground.blue; - utils = mkUnderline values.foreground.peach; - weather = mkUnderline values.foreground.lavendar; - }; - combination = { - battery.stack = [ - values.backgroundBlock.normal - values.underline.battery - ]; - clock.stack = [ - values.backgroundBlock.normal - values.underline.clock - ]; - resources.stack = [ - values.backgroundBlock.normal - values.underline.resources - ]; - utils.stack = [ - values.backgroundBlock.normal - values.underline.utils - ]; - weather.stack = [ - values.backgroundBlock.normal - values.underline.weather - ]; - }; - }; -in { - bar = { - background = values.foreground.none; - foreground = values.foreground.normal; - - font = values.font.main; - height = - if laptop - then 25 - else 45; - margin = - if laptop - then 5 - else 10; - location = "top"; - layer = "bottom"; - spacing = 0; - - border = { - margin = 0; - top-margin = 10; - }; - - left = [ - { - river = let - tag_base_setting = { - map = let - normal = { - string = { - margin = 10; - text = "{id}"; - }; - }; - in { - default = normal; - conditions = { - "state == focused" = { - string = { - deco = { - stack = [ - {background = {color = values.background.tag;};} - values.underline.focused - ]; - }; - margin = 10; - text = "{id}"; - }; - }; - "state == invisible" = { - map = { - conditions = { - occupied = { - string = { - deco = {background = {color = values.background.tag2;};}; - margin = 10; - text = "{id}"; - }; - }; - "~occupied" = normal; - }; - }; - }; - "state == unfocused" = { - string = { - deco = {background = {color = values.background.tag2;};}; - margin = 10; - text = "{id}"; - }; - }; - "state == urgent" = { - string = { - deco = { - stack = [ - {background = {color = values.background.urgent;};} - values.underline.urgent - ]; - }; - margin = 10; - text = "{id}"; - }; - }; - }; - }; - }; - in { - content = { - map = { - conditions = { - "id == 1" = tag_base_setting; - "id == 2" = tag_base_setting; - "id == 3" = tag_base_setting; - "id == 4" = tag_base_setting; - "id == 5" = tag_base_setting; - "id == 6" = tag_base_setting; - "id == 7" = tag_base_setting; - "id == 8" = tag_base_setting; - "id == 9" = tag_base_setting; - }; - on-click = { - left = shellExec "riverctl set-focused-tags $((1 << ({id} - 1)))"; - middle = shellExec "riverctl toggle-view-tags $((1 << ({id} -1)))"; - right = shellExec "riverctl toggle-focused-tags $((1 << ({id} -1)))"; - }; - }; - }; - title = { - map = { - conditions = {"title == \"\"" = {string = {text = "";};};}; - default = { - string = { - max = 35; - deco = values.underline.title; - left-margin = 12; - right-margin = 12; - text = "{title}"; - }; - }; - }; - }; - }; - } - ]; - - center = [ - { - clock = { - date-format = "%d/%m/%y (%a)"; - time-format = "%H:%M:%S %Z"; - foreground = values.foreground.blue; - content = { - string = { - deco = values.combination.clock; - text = " {date} {time} "; - }; - }; - }; - } - ]; - - right = - [ - { - script = { - path = scripts.mpd_song_name_script; - content.map.conditions = { - playing = { - string = { - deco = values.combination.weather; - text = "{song} "; - }; - }; - "~playing" = { - string = { - deco = values.combination.weather; - text = ""; - }; - }; - }; - }; - } - { - script = { - path = scripts.volume_script; - content.map = { - on-click = shellExec "pavucontrol"; - conditions = { - muted = { - string = { - deco = values.backgroundBlock.urgent; - text = " "; - }; - }; - "~muted" = { - string = { - deco = values.combination.utils; - text = " {volume}% "; - }; - }; - }; - }; - }; - } - { - script = { - path = scripts.cpu_script; - content.string = { - deco = values.combination.resources; - text = " {cpu}% "; - }; - }; - } - { - script = { - path = scripts.memory_script; - content.map.conditions = { - swapstate = { - string = { - deco = values.combination.resources; - text = " {memperc}%({swapperc}%) "; - }; - }; - "~swapstate" = { - string = { - deco = values.combination.resources; - text = " {memperc}% "; - }; - }; - }; - }; - } - { - script = { - path = scripts.disk_script; - content.string = { - deco = values.combination.resources; - text = " {diskspace}({diskperc})"; - }; - }; - } - ] - ++ lib.optionals laptop [ - { - backlight = { - name = laptopBacklightName; - content.string = { - text = " {percent}% "; - deco = values.combination.utils; - }; - }; - } - { - battery = { - name = "BAT0"; - poll-interval = 300; - content.list.items = [ - { - ramp = { - tag = "capacity"; - items = let - stack = [ - values.backgroundBlock.normal - values.underline.battery - ]; - in [ - { - string = { - text = " {capacity}%({estimate}) "; - deco = - values.backgroundBlock.urgent; - }; - } - { - string = { - text = " {capacity}%({estimate}) "; - deco.stack = stack; - }; - } - { - string = { - text = " {capacity}%({estimate}) "; - deco.stack = stack; - }; - } - { - string = { - text = " {capacity}%({estimate}) "; - deco.stack = stack; - }; - } - { - string = { - text = " {capacity}%({estimate}) "; - deco.stack = stack; - }; - } - ]; - }; - } - ]; - }; - } - ]; - }; -} diff --git a/modules/by-name/yt/yt/external_commands_script.sh b/modules/by-name/yt/yt/external_commands_script.sh new file mode 100755 index 00000000..2e59e94a --- /dev/null +++ b/modules/by-name/yt/yt/external_commands_script.sh @@ -0,0 +1,9 @@ +#! /usr/bin/env sh + +riverctl focus-output next + +alacritty --title "floating please" --command "$@" + +riverctl focus-output next + +# vim: ft=sh diff --git a/modules/by-name/yt/yt/input.conf b/modules/by-name/yt/yt/input.conf deleted file mode 100644 index 68dad824..00000000 --- a/modules/by-name/yt/yt/input.conf +++ /dev/null @@ -1,14 +0,0 @@ -c script-message yt-comments-external -C script-message yt-comments-local - -d script-message yt-description-external -D script-message yt-description-local - -WHEEL_LEFT playlist-prev -WHEEL_RIGHT playlist-next - -q script-message yt-mark-watched -Q script-message yt-mark-picked -r script-message yt-check-new-videos - -P quit diff --git a/modules/by-name/yt/yt/module.nix b/modules/by-name/yt/yt/module.nix index 4e7e90fe..81bacf44 100644 --- a/modules/by-name/yt/yt/module.nix +++ b/modules/by-name/yt/yt/module.nix @@ -10,9 +10,102 @@ { config, lib, + pkgs, ... }: let cfg = config.soispha.programs.yt; + + mkConfig = (pkgs.formats.toml {}).generate; + + mpvInputConfig = { + "c" = "script-message yt-comments-external"; + "C" = "script-message yt-comments-local"; + + "d" = "script-message yt-description-external"; + "D" = "script-message yt-description-local"; + + "t" = "script-message yt-thumbnail-external"; + "I" = "script-message yt-info-external"; + + "WHEEL_LEFT" = "playlist-prev"; + "WHEEL_RIGHT" = "playlist-next"; + + "q" = "script-message yt-mark-watched"; + "Q" = "script-message yt-mark-picked"; + "r" = "script-message yt-check-new-videos"; + + "P" = "quit"; + }; + + mpvConf = { + "volume" = 75; + }; + + ytConfig = { + download = { + max_cache_size = "5 GiB"; + }; + + commands = { + image_show = [ + (lib.getExe + (pkgs.writeShellApplication { + name = "show_thumbnail"; + text = '' + imv -w "floating please" "$1" + ''; + runtimeInputs = [ + pkgs.imv + ]; + + inheritPath = false; + })) + ]; + + external_spawn = [ + (lib.getExe + (pkgs.writeShellApplication { + name = "start_external_command"; + text = builtins.readFile ./external_commands_script.sh; + runtimeInputs = [ + pkgs.river-classic + pkgs.alacritty + pkgs.less + ]; + + inheritPath = false; + })) + ]; + + url_opener = ["qutebrowser-timesinks.youtube"]; + }; + }; + + inherit (lib) generators; + inherit (builtins) typeOf stringLength; + + renderOption = option: + rec { + int = toString option; + float = int; + bool = lib.hm.booleans.yesNo option; + string = option; + } + .${ + typeOf option + }; + + renderOptionValue = value: let + rendered = renderOption value; + length = toString (stringLength rendered); + in "%${length}%${rendered}"; + + renderOptions = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault {mkValueString = renderOptionValue;} "="; + listsAsDuplicateKeys = true; + }; + + renderBindings = bindings: lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${value}") bindings); in { options.soispha.programs.yt = { enable = lib.mkEnableOption "the yt cli client"; @@ -21,9 +114,9 @@ in { config = { home-manager.users.soispha = lib.mkIf cfg.enable { xdg.configFile = { - "yt/mpv.conf".source = ./mpv.conf; - "yt/mpv.input.conf".source = ./input.conf; - "yt/config.toml".source = ./config.toml; + "yt/mpv.conf".text = renderOptions mpvConf; + "yt/mpv.input.conf".text = renderBindings mpvInputConfig; + "yt/config.toml".source = mkConfig "config.toml" ytConfig; }; }; }; diff --git a/modules/by-name/yt/yt/mpv.conf b/modules/by-name/yt/yt/mpv.conf deleted file mode 100644 index 52a40823..00000000 --- a/modules/by-name/yt/yt/mpv.conf +++ /dev/null @@ -1 +0,0 @@ -volume=75 diff --git a/modules/by-name/yt/yt/mpv.conf.license b/modules/by-name/yt/yt/mpv.conf.license deleted file mode 100644 index eae6a84c..00000000 --- a/modules/by-name/yt/yt/mpv.conf.license +++ /dev/null @@ -1,9 +0,0 @@ -nixos-config - My current NixOS configuration - -Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -SPDX-License-Identifier: GPL-3.0-or-later - -This file is part of my nixos-config. - -You should have received a copy of the License along with this program. -If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. diff --git a/modules/by-name/zs/zsh/config/command_not_found/command_not_found_insult.sh b/modules/by-name/zs/zsh/config/command_not_found/command_not_found_insult.sh deleted file mode 100644 index a5d71939..00000000 --- a/modules/by-name/zs/zsh/config/command_not_found/command_not_found_insult.sh +++ /dev/null @@ -1,309 +0,0 @@ -#! /usr/bin/env bash - -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -print_message() { - - local messages - local message - - ### STANDARD INSULTS ### - declare -a _array1=( - "(╯°□°)╯︵ ┻━┻" - "¯\_(ツ)_/¯" - "ACHTUNG! ALLES TURISTEN UND NONTEKNISCHEN LOOKENPEEPERS! DAS KOMPUTERMASCHINE IST NICHT FÜR DER GEFINGERPOKEN UND MITTENGRABEN! ODERWISE IST EASY TO SCHNAPPEN DER SPRINGENWERK, BLOWENFUSEN UND POPPENCORKEN MIT SPITZENSPARKEN. IST NICHT FÜR GEWERKEN BEI DUMMKOPFEN. DER RUBBERNECKEN SIGHTSEEREN KEEPEN DAS COTTONPICKEN HÄNDER IN DAS POCKETS MUSS. ZO RELAXEN UND WATSCHEN DER BLINKENLICHTEN." - "And the Darwin Award goes to.... ${USER}!" - "Allowing you to survive childbirth was medical malpractice." - "Are you always this stupid or are you making a special effort today?!" - "Are you even trying?!" - "Bad." - "Boooo!" - "Brains aren't everything. In your case they're nothing." - "Commands, random gibberish, who cares!" - "Come on! You can do it!" - "Don't you have anything better to do?!" - "Don't you know anything?" - "Dropped on your head as a baby, eh?" - "error code: 1D10T" - "Even your mom loves you only as a friend." - "ERROR_INCOMPETENT_USER" - "Fake it till you make it!" - "Go outside." - "Haha, n00b!" - "How many times do I have to flush before you go away?" - "I am _seriously_ considering 'rm -rf /'-ing myself..." - "I don't know what makes you so stupid, but it really works." - "I was going to give you a nasty look, but I see you already have one." - "If beauty fades then you have nothing to worry about." - "If brains were gasoline you wouldn’t have enough to propel a flea’s motorcycle around a doughnut." - "If ignorance is bliss, you must be the happiest person on earth." - "If shit was music, you'd be an orchestra." - "If what you don't know can't hurt you, you're invulnerable." - "Incompetence is also a form of competence" - "I’d slap you, but that’d be animal abuse." - "I’ve heard of being hit with the ugly stick, but you must have been beaten senseless with it." - "Keep trying, someday you'll do something intelligent!" - "Let’s play horse. I’ll be the front end. And you be yourself." - "Life is good, you should get one." - "lol" - "lol... plz" - "My keyboard is not a touch screen!" - "My uptime is longer than your relationships." - "Nice try." - "n00b alert!" - "Pathetic" - "Perhaps computers are not for you..." - "Perhaps you should leave the command line alone..." - "Please step away from the keyboard!" - "plz uninstall" - "Pro tip: type a valid command!" - "Rose are red. Violets are blue. I have five fingers. The middle one's for you." - "RTFM!" - "Sorry what? I don’t understand idiot language." - "The degree of your stupidity is enough to boil water." - "The worst one today!" - "This is not a search engine." - "This is not Windows" - "This is why nobody likes you." - "This is why you get to see your children only once a month." - "Try using your brain the next time!" - "Two wrongs don't make a right, take your parents as an example." - "Typing incorrect commands, eh?" - "u suk" - "What if... you type an actual command the next time!" - "What if I told you... it is possible to type valid commands." - "What is this...? Amateur hour!?" - "Why are you so stupid?!" - "Why are you doing this to me?!" - "Why did the chicken cross the road? To get the hell away from you." - "Wow! That was impressively wrong!" - "Y u no speak computer???" - "You are not as bad as people say, you are much, much worse." - "You are not useless since you can still be used as a bad example." - "You must have been born on a highway because that's where most accidents happen." - "Your application for reduced salary has been sent!" - "Your mom had a severe case of diarrhea when you were born." - "You're proof that god has a sense of humor." - "You’re so dumb your first words were DUH." - "You're so fat, people jog around you for exercise." - "You’re the reason Santa says ho, ho, ho, on Christmas!" - ) - ### SHAKESPEARE INSULTS ### - declare -a array2=( - "A most notable coward, an infinite and endless liar, an hourly promise breaker, the owner of no one good quality." - "Away, you starvelling, you elf-skin, you dried neat's-tongue, bull's-pizzle, you stock-fish!" - "Away, you three-inch fool! " - "Come, come, you froward and unable worms!" - "Go, prick thy face, and over-red thy fear, Thou lily-liver’d boy." - "His wit's as thick as a Tewkesbury mustard." - "I am pigeon-liver'd and lack gall." - "I am sick when I do look on thee " - "I must tell you friendly in your ear, sell when you can, you are not for all markets." - "If thou wilt needs marry, marry a fool; for wise men know well enough what monsters you make of them." - "I'll beat thee, but I would infect my hands." - "I scorn you, scurvy companion. " - "Methink'st thou art a general offence and every man should beat thee." - "More of your conversation would infect my brain." - "My wife's a hobby horse!" - "Peace, ye fat guts!" - "Poisonous bunch-backed toad! " - "The rankest compound of villainous smell that ever offended nostril" - "The tartness of his face sours ripe grapes." - "There's no more faith in thee than in a stewed prune." - "Thine forward voice, now, is to speak well of thine friend; thine backward voice is to utter foul speeches and to detract." - "That trunk of humours, that bolting-hutch of beastliness, that swollen parcel of dropsies, that huge bombard of sack, that stuffed cloak-bag of guts, that roasted Manningtree ox with pudding in his belly, that reverend vice, that grey Iniquity, that father ruffian, that vanity in years?" - "Thine face is not worth sunburning." - "This woman's an easy glove, my lord, she goes off and on at pleasure." - "Thou art a boil, a plague sore." - "Was the Duke a flesh-monger, a fool and a coward?" - "Thou art as fat as butter." - "Here is the babe, as loathsome as a toad." - "Like the toad; ugly and venomous." - "Thou art unfit for any place but hell." - "Thou cream faced loon" - "Thou clay-brained guts, thou knotty-pated fool, thou whoreson obscene greasy tallow-catch!" - "Thou damned and luxurious mountain goat." - "Thou elvish-mark'd, abortive, rooting hog!" - "Thou leathern-jerkin, crystal-button, knot-pated, agatering, puke-stocking, caddis-garter, smooth-tongue, Spanish pouch!" - "Thou lump of foul deformity" - "That poisonous bunch-back'd toad!" - "Thou sodden-witted lord! Thou hast no more brain than I have in mine elbows " - "Thou subtle, perjur'd, false, disloyal man!" - "Thou whoreson zed , thou unnecessary letter!" - "Thy sin’s not accidental, but a trade." - "Thy tongue outvenoms all the worms of Nile." - "Would thou wert clean enough to spit upon" - "Would thou wouldst burst!" - "You poor, base, rascally, cheating lack-linen mate! " - "You are as a candle, the better burnt out." - "You scullion! You rampallian! You fustilarian! I’ll tickle your catastrophe!" - "You starvelling, you eel-skin, you dried neat's-tongue, you bull's-pizzle, you stock-fish-O for breath to utter what is like thee!-you tailor's-yard, you sheath, you bow-case, you vile standing tuck!" - "Your brain is as dry as the remainder biscuit after voyage." - "Virginity breeds mites, much like a cheese." - "Villain, I have done thy mother" - ) - ### MARTIN LUTHER INSULTS ### - declare -a array3=( - "You live like simple cattle or irrational pigs and, despite the fact that the gospel has returned, have mastered the fine art of misusing all your freedom." - "You shameful gluttons and servants of your bellies are better suited to be swineherds and keepers of dogs." - "You deserve not only to be given no food to eat, but also to have the dogs set upon you and to be pelted with horse manure." - "Oh, what mad senseless fools you are!" - "For this you deserve to have God deprive you of his Word and blessing and once again allow preachers of lies to arise who lead you to the devil - and wring sweat and blood out of you besides." - "All your holiness is only stench and filth, and it merits nothing but wrath and damnation." - "May your grain spoil in the barn, your beer in the cellar, your cattle perish in the stall. Yes, your entire hoard ought to be consumed by rust so that you will never enjoy it." - "You relish and delight in the chance to stir up someone else's dirt like pigs that roll in manure and root around in it with their snouts." - "Your sin smells to high heaven." - "Your words are so foolishly and ignorantly composed that I cannot believe you understand them." - "You are the most insane heretics and ingrafters of heretical perversity." - "What you say is a blasphemy that has made you worthy of a thousand deaths." - "Behold, indeed, this little golden work of a golden teacher! It is a work most worthy of golden letters, and lest there be something about it which is not golden, it must be handed down by golden disciples, namely, by those about whom it is said, 'The idols of the nations are silver and gold. They have eyes, but they see not.'" - "You are worthy only to be mocked by the words of error." - "It is presumptuous for people who are as ignorant as you are not to take up the work of a herdsman." - "What bilgewater of heresies has ever been spoken so heretically as what you have said?" - "What do you mean when you say this? Are you dreaming in the throes of a fever or are you laboring under a madness?" - "Your astute minds have been completely turned into stinking mushrooms." - "You are the prostitute of heretics!" - "I am tired of the pestilent voice of your sirens." - "You are a bungling magpie, croaking loudly." - "You forgot to purge yourself with hellabore while you were preparing to fabricate this lie." - "You are more corrupt than any Babylon or Sodom ever was, and, as far as I can see, are characterized by a completely depraved, hopeless, and notorious godlessness." - "Your home, once the holiest of all, has become the most licentious den of thieves, the most shameless of all brothels, the kingdom of sin, death, and hell. It is so bad that even Antichrist himself, if he should come, could think of nothing to add to its wickedness." - "What devilish unchristian thing would you not undertake?" - "You are an extraordinary creature, being neither God nor man. Perhaps you are the devil himself." - "Even if the Antichrist appears, what greater evil can he do than what you have done and do daily?" - "It may be that you want to build yourself a heaven of your own, like those jugglers build themselves out of linen cloth at the Shrove Tuesday carnival. Is it not disgusting that we have to hear such foolish and childish things from you?" - "In our country, fruit grows on trees and from trees, and meditation upon sin grows from contrition. But in your land, trees may grow on fruits, contrition from sins, people walk on their ears, and everything is upside down." - "O you wolf in Christendom!" - "You know less than does a log on the ground." - "I think that all the devils have at once entered into you." - "You are worse than all the devils. What you have done, no devil has ever done. Your end is near, you son of perdition and Antichrist! Stop now, you are going to far!" - "You are the true, chief, and final Antichrist." - "How far will you go, O devilish pride?" - "All Christians should be on guard against your antichristian poison." - "I think you received these ideas in your pipe dreams." - "You are in all you do the very opposite of Christ as befits a true Antichrist." - "You are a person of sin and the child of perdition, leading all the world with you to the devil, using your lying and deceitful ways." - "You are not a pious fraud, but an infernal, diabolical, antichristian fraud." - "You are the Roman Nimrod and a teacher of sin." - "It is the old dragon from the abyss of hell who is standing before me!" - "You hold fast to human dreams and the doctrines of devils." - "If you who are assembled in a council are so frivolous and irresponsible as to waste time and money on unnecessary questions, when it is the business of a council to deal only with the important and necessary matters, we should not only refuse to obey you, but consider you insane or criminals." - "Even Lucifer was not guilty of so great a sacrilege in heaven, for he only presumed to be God's equal. God help us!" - "You condemned the holy gospel and replaced it with the teaching of the dragon from hell." - "Your words are un-Christian, antichristian, and spoken by the inspiration of the evil spirit." - "What happened to the house built on sand in Matthew 7 will also happen to you." - "Must we believe your nightmares?" - "Look how this great heretic speaks brazenly and sacrilegiously." - "You run against God with the horns of your pride up in the air and thus plunge into the abyss of hell. Woe unto you, Antichrist!" - "You are the devil's most dangerous tool!" - "It seems I must have liars and villains for opponents. I am not worthy in the sight of God that a godly and honorable person should discuss these matters with me in a Christian way. This is my greatest lament." - "May the Lord Jesus protect me and all devout souls from your contagion and your company!" - "This venom - the mere smell of which kills a man!" - "You are a Baal-zebub - that is, a man of flies." - "You are full of poisonous refuse and insane foolishness." - "You are ignorant, stupid, godless blasphemers." - "You moderate enforcer and eulogizer of moderation. You are one of those bloody and deceitful people who affect modesty in words and appearance, but who meanwhile breathe out threats and blood." - "We leave you to your own devices, for nothing properly suits you except hypocrisy, flattery, and lies." - "In lying fashion you ignore what even children know." - "The reward of such flattery is what your crass stupidity deserves. Therefore, we shall turn from you, a sevenfold stupid and blasphemous wise person." - "People of your sort are hirelings, dumb dogs unable to bark, who see the wolf coming and flee or, rather, join up with the wolf." - "You are a wolf and apostle of Satan." - "You are the ultimate scourges of the world, the Antichrist together with your sophists and bishops." - "You cowardly slave, you corrupt sycophant, with your sickening advice!" - "You are idiots and swine." - "Every letter of yours breathes Moabitish pride. So much can a single bull inflate a single bubble that you practically make distinguished asses into gods." - "You sophistic worms, grasshoppers, locusts, frogs and lice!" - 'You completely close your mind and do nothing but shout, "Anathema, anathema, anathema!" so that by your own voice you are judged mad.' - "Let this generation of vipers prepare itself for unquenchable fire!" - "You rush forward as an ass under the pelt of a lion." - "In appearance and words you simulate modesty, but you are so swollen with haughtiness, arrogance, pride, malice, villainy, rashness, superciliousness, ignorance, and stupidity that there is nothing to surpass you." - "Blind moles!" - "We despise your whorish impudence." - ) - ### EDIT THIS LINE IF YOU ONLY WANT TO USE CERTAIN INSULT LISTS ### - messages=( - # "${array1[@]}" ## normal ones - "${array2[@]}" ## Shakespeare - "${array3[@]}" ## Luther - ) - - # If CMD_NOT_FOUND_MSGS array is populated use those messages instead of the defaults - [[ -n ${CMD_NOT_FOUND_MSGS} ]] && messages=("${CMD_NOT_FOUND_MSGS[@]}") - - # If CMD_NOT_FOUND_MSGS_APPEND array is populated append those to the existing messages - [[ -n ${CMD_NOT_FOUND_MSGS_APPEND} ]] && messages+=("${CMD_NOT_FOUND_MSGS_APPEND[@]}") - - # Seed RANDOM with an integer of some length - RANDOM=$(od -vAn -N4 -tu </dev/urandom) - - # Print a randomly selected message, but only about half the time to annoy the user a - # little bit less. - if [[ $((RANDOM % 2)) -lt 1 ]]; then - message=${messages[RANDOM % ${#messages[@]}]} - printf "\n %s%s%s\n\n" "$(tput bold)$(tput setaf 1)" "$message" "$(tput sgr0)" >&2 - fi -} - -function_exists() { - # Zsh returns 0 even on non existing functions with -F so use -f - declare -f "$1" >/dev/null - return $? -} - -# -# The idea below is to copy any existing handlers to another function -# name and insert the message in front of the old handler in the -# new handler. By default, neither bash or zsh has has a handler function -# defined, so the default behaviour is replicated. -# -# Also, ensure the handler is only copied once. If we do not ensure this -# the handler would add itself recursively if this file happens to be -# sourced multiple times in the same shell, resulting in a neverending -# stream of messages. -# - -# -# Zsh -# -if function_exists command_not_found_handler; then - if ! function_exists orig_command_not_found_handler; then - eval "orig_$(declare -f command_not_found_handler)" - fi -else - orig_command_not_found_handler() { - printf "zsh: command not found: %s\n" "$1" >&2 - return 127 - } -fi - -command_not_found_handler() { - print_message - orig_command_not_found_handler "$@" -} - -# -# Bash -# -if function_exists command_not_found_handle; then - if ! function_exists orig_command_not_found_handle; then - eval "orig_$(declare -f command_not_found_handle)" - fi -else - orig_command_not_found_handle() { - printf "%s: %s: command not found\n" "$0" "$1" >&2 - return 127 - } -fi - -command_not_found_handle() { - print_message - orig_command_not_found_handle "$@" -} diff --git a/modules/by-name/zs/zsh/module.nix b/modules/by-name/zs/zsh/module.nix index bf7e2a71..fee2eae3 100644 --- a/modules/by-name/zs/zsh/module.nix +++ b/modules/by-name/zs/zsh/module.nix @@ -10,13 +10,11 @@ { config, lib, - system, - pkgs, ... }: let cfg = config.soispha.programs.zsh; - zDotDir = ".config/zsh"; + zDotDir = "${config.home-manager.users.soispha.xdg.configHome}/zsh"; sourceFile = path: "source ${path}\n"; @@ -45,7 +43,7 @@ in { config = lib.mkIf cfg.enable { environment.variables = { - ZDOTDIR = "${config.home-manager.users.soispha.home.homeDirectory}/${zDotDir}"; + ZDOTDIR = zDotDir; }; home-manager.users.soispha = { @@ -66,8 +64,6 @@ in { autocd = true; - # Must be relative to the users home directory (for whatever reason) - # Thus no `${homeConfig.xdg.configHome}` dotDir = zDotDir; # TODO: Remove the whole history and replace it completely with `atuin` <2024-10-21> @@ -111,10 +107,7 @@ in { + sourceFile ./plugins/zsh-history-substring-search.zsh ); end = lib.modules.mkAfter ( - # NOTE(@bpeetz): Put this here, so that command handler in `extraFiles` - # are not overriding this. <2025-05-16> - sourceFile ./config/command_not_found/command_not_found_insult.sh - + sourceFile ./config/keymaps_start.zsh + sourceFile ./config/keymaps_start.zsh + sourceFile ./config/keymaps/command.zsh + sourceFile ./config/keymaps/emacs.zsh + sourceFile ./config/keymaps/isearch.zsh diff --git a/modules/common/default.nix b/modules/common/default.nix index a272bd52..f9831351 100644 --- a/modules/common/default.nix +++ b/modules/common/default.nix @@ -159,7 +159,8 @@ "~/.local/share/unison" # These are just to big to be synchronized (# TODO: Work around that <2024-08-31> ) - "~/media/music" + "~/media/music/beets.old" + "~/media/music/deerix" "~/.local/share/Steam" ] ++ homeManagerSymlinks; @@ -171,7 +172,7 @@ "~/.local/.Trash-1000" "~/media" - "~/school" + "~/documents" "~/repos" ]; }; @@ -264,9 +265,7 @@ }; backgroundStart = [ # TODO(@bpeetz): Move these to systemd units/their own modules <2025-05-18> - pkgs.gammastep - pkgs.mako ["${lib.getExe pkgs.swaybg}" "--image" "${./abstract-nord.png}"] pkgs.swayidle pkgs.alacritty @@ -274,7 +273,7 @@ }; }; mpv.enable = true; - steam.enable = true; + steam.enable = false; ssh.enable = true; swaylock.enable = true; timewarrior.enable = true; @@ -289,13 +288,11 @@ projects = builtins.fromJSON (builtins.readFile ./projects.json); }; nix-index.enable = true; - yambar.enable = false; yt.enable = true; zathura.enable = true; zsh.enable = true; }; - sound.enable = true; version.enable = true; }; } diff --git a/modules/common/projects.json b/modules/common/projects.json index df3b0c60..e8e9bc39 100644 --- a/modules/common/projects.json +++ b/modules/common/projects.json @@ -12,6 +12,7 @@ "pc": {} } }, + "esc": {}, "latex": { "prefix": "programming/latex" }, @@ -48,6 +49,7 @@ "gpg": {}, "keyboard": {}, "laptop": {}, + "music": {}, "nvim": {}, "rss": {}, "shell": {}, diff --git a/modules/default.nix b/modules/default.nix index ef885a21..f83cf683 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -15,7 +15,6 @@ (libraries.extra.mkByName { baseDirectory = ./by-name; fileName = "module.nix"; - finalizeFunction = name: value: value; }); in { imports = files; diff --git a/modules/home.legacy/conf/alacritty/default.nix b/modules/home.legacy/conf/alacritty/default.nix deleted file mode 100644 index 493c4114..00000000 --- a/modules/home.legacy/conf/alacritty/default.nix +++ /dev/null @@ -1,39 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{lib, ...}: let - config_file = '' - ${lib.strings.fileContents ./toml/general.toml} - ${lib.strings.fileContents ./toml/bell.toml} - ${lib.strings.fileContents ./toml/colorscheme.toml} - ${lib.strings.fileContents ./toml/cursor.toml} - ${lib.strings.fileContents ./toml/env.toml} - ${lib.strings.fileContents ./toml/font.toml} - ${lib.strings.fileContents ./toml/hints.toml} - ${lib.strings.fileContents ./toml/keyboard_bindings.toml} - ${lib.strings.fileContents ./toml/mouse.toml} - ${lib.strings.fileContents ./toml/mouse_bindings.toml} - ${lib.strings.fileContents ./toml/scrolling.toml} - ${lib.strings.fileContents ./toml/selection.toml} - ${lib.strings.fileContents ./toml/window.toml} - ''; -in { - home.sessionVariables = { - # This is **not** the TERM variable but a special one to signify my favorite terminal. - TERMINAL = "alacritty"; - - # These two here should be set by alacritty at start-up - # TERM = "alacritty"; - # COLORTERM = "truecolor"; - }; - programs.alacritty = { - enable = true; - }; - xdg.configFile."alacritty/alacritty.toml".text = config_file; -} diff --git a/modules/home.legacy/conf/alacritty/toml/bell.toml b/modules/home.legacy/conf/alacritty/toml/bell.toml deleted file mode 100644 index 821306cc..00000000 --- a/modules/home.legacy/conf/alacritty/toml/bell.toml +++ /dev/null @@ -1,17 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Bell -# -# The bell is rung every time the BEL control character is received. -[bell] -# Duration of the visual bell flash in milliseconds. A `duration` of `0` will -# disable the visual bell animation. -duration = 0 diff --git a/modules/home.legacy/conf/alacritty/toml/cursor.toml b/modules/home.legacy/conf/alacritty/toml/cursor.toml deleted file mode 100644 index 6e633b5a..00000000 --- a/modules/home.legacy/conf/alacritty/toml/cursor.toml +++ /dev/null @@ -1,21 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[cursor] -blink_interval = 750 -blink_timeout = 5 -thickness = 0.15 -unfocused_hollow = true -vi_mode_style = "None" - -# Cursor style -[cursor.style] -blinking = "On" -shape = "Beam" diff --git a/modules/home.legacy/conf/alacritty/toml/general.toml b/modules/home.legacy/conf/alacritty/toml/general.toml deleted file mode 100644 index 588d8ea0..00000000 --- a/modules/home.legacy/conf/alacritty/toml/general.toml +++ /dev/null @@ -1,24 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Configuration for Alacritty, the GPU enhanced terminal emulator. - -[general] -# Live config reload (changes require restart) -live_config_reload = true - -# Startup directory -# -# Directory the shell is started in. If this is unset, or `None`, the working -# directory of the parent process will be used. -#working_directory: None - -# Offer IPC using `alacritty msg` (unix only) -ipc_socket = true diff --git a/modules/home.legacy/conf/alacritty/toml/hints.toml b/modules/home.legacy/conf/alacritty/toml/hints.toml deleted file mode 100644 index da18dc59..00000000 --- a/modules/home.legacy/conf/alacritty/toml/hints.toml +++ /dev/null @@ -1,35 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[hints] -alphabet = "jfkdls;ahgurieowpq" - -[[hints.enabled]] -command = "xdg-open" # On Linux/BSD -hyperlinks = true -post_processing = true -persist = false -mouse.enabled = true -binding = { key = "U", mods = "Control|Shift" } -regex = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)[^\u0000-\u001F\u007F-\u009F<>\"\\s{-}\\^⟨⟩`]+" - - -[[hints.enabled]] -action = "Paste" -post_processing = false -binding = { key = "T", mods = "Control|Shift" } -regex = '''([^ '"`=:\[\(]*/)([^/: '"`\)\]]*)''' - - -[[hints.enabled]] -action = "Paste" -post_processing = false -binding = { key = "H", mods = "Control|Shift" } -regex = '([a-z0-9]{7,40})\s' diff --git a/modules/home.legacy/conf/alacritty/toml/keyboard_bindings.toml b/modules/home.legacy/conf/alacritty/toml/keyboard_bindings.toml deleted file mode 100644 index f2a6eb55..00000000 --- a/modules/home.legacy/conf/alacritty/toml/keyboard_bindings.toml +++ /dev/null @@ -1,307 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[[keyboard.bindings]] -action = "Paste" -key = "P" -mods = "Control" - -[[keyboard.bindings]] -action = "Paste" -key = "Insert" -mods = "Shift" - -[[keyboard.bindings]] -chars = "gc" -key = "Slash" -mods = "Control" - -[[keyboard.bindings]] -action = "Copy" -key = "Y" -mods = "Control" - -[[keyboard.bindings]] -action = "ResetFontSize" -key = "Key0" -mods = "Control" - -[[keyboard.bindings]] -action = "IncreaseFontSize" -key = "Equals" -mods = "Control" - -[[keyboard.bindings]] -action = "IncreaseFontSize" -key = "Plus" -mods = "Control" - -[[keyboard.bindings]] -action = "DecreaseFontSize" -key = "Minus" -mods = "Control" - -[[keyboard.bindings]] -action = "ToggleViMode" -key = "Space" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollToBottom" -key = "Space" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollToBottom" -key = "I" -mode = "Vi" - -[[keyboard.bindings]] -action = "ToggleViMode" -key = "I" -mode = "Vi" - -[[keyboard.bindings]] -action = "ScrollToBottom" -key = "C" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ToggleViMode" -key = "C" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ClearSelection" -key = "Escape" -mode = "Vi" - -[[keyboard.bindings]] -action = "ScrollLineUp" -key = "Y" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollLineDown" -key = "E" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollToTop" -key = "G" -mode = "Vi" - -[[keyboard.bindings]] -action = "ScrollToBottom" -key = "G" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "ScrollPageUp" -key = "B" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollPageDown" -key = "F" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollHalfPageUp" -key = "U" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ScrollHalfPageDown" -key = "D" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "Copy" -key = "Y" -mode = "Vi" - -[[keyboard.bindings]] -action = "ClearSelection" -key = "Y" -mode = "Vi" - -[[keyboard.bindings]] -action = "ToggleNormalSelection" -key = "V" -mode = "Vi" - -[[keyboard.bindings]] -action = "ToggleLineSelection" -key = "V" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "ToggleBlockSelection" -key = "V" -mode = "Vi" -mods = "Control" - -[[keyboard.bindings]] -action = "ToggleSemanticSelection" -key = "V" -mode = "Vi" -mods = "Alt" - -[[keyboard.bindings]] -action = "Open" -key = "Return" -mode = "Vi" - -[[keyboard.bindings]] -action = "Up" -key = "K" -mode = "Vi" - -[[keyboard.bindings]] -action = "Down" -key = "J" -mode = "Vi" - -[[keyboard.bindings]] -action = "Left" -key = "H" -mode = "Vi" - -[[keyboard.bindings]] -action = "Right" -key = "L" -mode = "Vi" - -[[keyboard.bindings]] -action = "Up" -key = "Up" -mode = "Vi" - -[[keyboard.bindings]] -action = "Down" -key = "Down" -mode = "Vi" - -[[keyboard.bindings]] -action = "Left" -key = "Left" -mode = "Vi" - -[[keyboard.bindings]] -action = "Right" -key = "Right" -mode = "Vi" - -[[keyboard.bindings]] -action = "First" -key = "Key0" -mode = "Vi" - -[[keyboard.bindings]] -action = "Last" -key = "Key4" -mode = "Vi" - -[[keyboard.bindings]] -action = "FirstOccupied" -key = "Key6" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "High" -key = "H" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "Middle" -key = "M" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "Low" -key = "L" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "SemanticLeft" -key = "B" -mode = "Vi" - -[[keyboard.bindings]] -action = "SemanticRight" -key = "W" -mode = "Vi" - -[[keyboard.bindings]] -action = "SemanticRightEnd" -key = "E" -mode = "Vi" - -[[keyboard.bindings]] -action = "WordLeft" -key = "B" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "WordRight" -key = "W" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "WordRightEnd" -key = "E" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "Bracket" -key = "Key5" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "SearchForward" -key = "Slash" -mode = "Vi" - -[[keyboard.bindings]] -action = "SearchBackward" -key = "Slash" -mode = "Vi" -mods = "Shift" - -[[keyboard.bindings]] -action = "SearchNext" -key = "N" -mode = "Vi" - -[[keyboard.bindings]] -action = "SearchPrevious" -key = "N" -mode = "Vi" -mods = "Shift" diff --git a/modules/home.legacy/conf/alacritty/toml/mouse.toml b/modules/home.legacy/conf/alacritty/toml/mouse.toml deleted file mode 100644 index cbc9cd32..00000000 --- a/modules/home.legacy/conf/alacritty/toml/mouse.toml +++ /dev/null @@ -1,12 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[mouse] -hide_when_typing = false diff --git a/modules/home.legacy/conf/alacritty/toml/mouse_bindings.toml b/modules/home.legacy/conf/alacritty/toml/mouse_bindings.toml deleted file mode 100644 index e566a452..00000000 --- a/modules/home.legacy/conf/alacritty/toml/mouse_bindings.toml +++ /dev/null @@ -1,13 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[[mouse.bindings]] -action = "Copy" -mouse = "Middle" diff --git a/modules/home.legacy/conf/alacritty/toml/scrolling.toml b/modules/home.legacy/conf/alacritty/toml/scrolling.toml deleted file mode 100644 index 09240872..00000000 --- a/modules/home.legacy/conf/alacritty/toml/scrolling.toml +++ /dev/null @@ -1,13 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[scrolling] -history = 10000 -multiplier = 3 diff --git a/modules/home.legacy/conf/alacritty/toml/selection.toml b/modules/home.legacy/conf/alacritty/toml/selection.toml deleted file mode 100644 index ebc8c849..00000000 --- a/modules/home.legacy/conf/alacritty/toml/selection.toml +++ /dev/null @@ -1,13 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -[selection] -save_to_clipboard = false -semantic_escape_chars = ",│`|:\"' ()[]{}<>\t" diff --git a/modules/home.legacy/conf/alacritty/yaml/base.yml b/modules/home.legacy/conf/alacritty/yaml/base.yml deleted file mode 100644 index 637d0d2e..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/base.yml +++ /dev/null @@ -1,27 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - ---- -# Configuration for Alacritty, the GPU enhanced terminal emulator. - -# If `true`, bold text is drawn using the bright color variants. -#draw_bold_text_with_bright_colors: true # TODO: - -# Live config reload (changes require restart) -live_config_reload: true - -# Startup directory -# -# Directory the shell is started in. If this is unset, or `None`, the working -# directory of the parent process will be used. -#working_directory: None - -# Offer IPC using `alacritty msg` (unix only) -ipc_socket: true diff --git a/modules/home.legacy/conf/alacritty/yaml/bell.yml b/modules/home.legacy/conf/alacritty/yaml/bell.yml deleted file mode 100644 index 4331a121..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/bell.yml +++ /dev/null @@ -1,52 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Bell -# -# The bell is rung every time the BEL control character is received. -bell: - # Visual Bell Animation - # - # Animation effect for flashing the screen when the visual bell is rung. - # - # Values for `animation`: - # - Ease - # - EaseOut - # - EaseOutSine - # - EaseOutQuad - # - EaseOutCubic - # - EaseOutQuart - # - EaseOutQuint - # - EaseOutExpo - # - EaseOutCirc - # - Linear - #animation: EaseOutExpo - - # Duration of the visual bell flash in milliseconds. A `duration` of `0` will - # disable the visual bell animation. - duration: 0 - - # Visual bell animation color. - #color: '#ffffff' - - # Bell Command - # - # This program is executed whenever the bell is rung. - # - # When set to `command: None`, no command will be executed. - # - # Example: - # command: - # program: notify-send - # args: ["Hello, World!"] - # - # command: - # program: notify-send - # args: ["The bell in alacritty was rung!"] diff --git a/modules/home.legacy/conf/alacritty/yaml/colors.yml b/modules/home.legacy/conf/alacritty/yaml/colors.yml deleted file mode 100644 index 05d2e0e6..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/colors.yml +++ /dev/null @@ -1,157 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Colors (Tomorrow Night) -colors: - # Default colors - primary: - background: '#191919' - foreground: '#d8dee9' - - # Bright and dim foreground colors - # - # The dimmed foreground color is calculated automatically if it is not - # present. If the bright foreground color is not set, or - # `draw_bold_text_with_bright_colors` is `false`, the normal foreground - # color will be used. - #dim_foreground: '#828482' - #bright_foreground: '#eaeaea' - - # Cursor colors - # - # Colors which should be used to draw the terminal cursor. - # - # Allowed values are CellForeground/CellBackground, which reference the - # affected cell, or hexadecimal colors like #ff00ff. - cursor: - text: '#191919' - cursor: '#d8dee9' - - # Vi mode cursor colors - # - # Colors for the cursor when the vi mode is active. - # - # Allowed values are CellForeground/CellBackground, which reference the - # affected cell, or hexadecimal colors like #ff00ff. - vi_mode_cursor: - text: CellBackground - cursor: CellForeground - - # Search colors - # - # Colors used for the search bar and match highlighting. - search: - # Allowed values are CellForeground/CellBackground, which reference the - # affected cell, or hexadecimal colors like #ff00ff. - matches: - foreground: '#000000' - background: '#ffffff' - focused_match: - foreground: '#ffffff' - background: '#000000' - - # Keyboard hints - hints: - # First character in the hint label - # - # Allowed values are CellForeground/CellBackground, which reference the - # affected cell, or hexadecimal colors like #ff00ff. - start: - foreground: '#1d1f21' - background: '#e9ff5e' - - # All characters after the first one in the hint label - # - # Allowed values are CellForeground/CellBackground, which reference the - # affected cell, or hexadecimal colors like #ff00ff. - end: - foreground: '#e9ff5e' - background: '#1d1f21' - - # Line indicator - # - # Color used for the indicator displaying the position in history during - # search and vi mode. - # - # By default, these will use the opposing primary color. - #line_indicator: - # foreground: None - # background: None - - # Footer bar - # - # Color used for the footer bar on the bottom, used by search regex input, - # hyperlink URI preview, etc. - # - footer_bar: - background: '#c5c8c6' - foreground: '#1d1f21' - - # Selection colors - # - # Colors which should be used to draw the selection area. - # - # Allowed values are CellForeground/CellBackground, which reference the - # affected cell, or hexadecimal colors like #ff00ff. - selection: - text: '#191919' - background: '#d8dee9' - - # Normal colors - normal: - black: '#191919' - red: '#b02626' - green: '#40a62f' - yellow: '#f2e635' - blue: '#314ad0' - magenta: '#b30ad0' - cyan: '#32d0fc' - white: '#acadb1' - - # Bright colors - bright: - black: '#36393d' - red: '#ce2727' - green: '#47c930' - yellow: '#fff138' - blue: '#2e4bea' - magenta: '#cc15ed' - cyan: '#54d9ff' - white: '#dbdbdb' - - # Dim colors - # - # If the dim colors are not set, they will be calculated automatically based - # on the `normal` colors. - dim: - black: '#676f78' - red: '#b55454' - green: '#78a670' - yellow: '#faf380' - blue: '#707fd0' - magenta: '#c583d0' - cyan: '#8adaf1' - white: '#e0e3e7' - # Indexed Colors - # - # The indexed colors include all colors from 16 to 256. - # When these are not set, they're filled with sensible defaults. - # - # Example: - # `- { index: 16, color: '#ff00ff' }` - # - #indexed_colors: [] - - # Transparent cell backgrounds - # - # Whether or not `window.opacity` applies to all cell backgrounds or only to - # the default background. When set to `true` all cells will be transparent - # regardless of their background color. - #transparent_background_colors: false # TODO: diff --git a/modules/home.legacy/conf/alacritty/yaml/colorscheme.yml b/modules/home.legacy/conf/alacritty/yaml/colorscheme.yml deleted file mode 100644 index 0b07fe80..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/colorscheme.yml +++ /dev/null @@ -1,41 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Nightfox Alacritty Colors -# Style: carbonfox -# Upstream: https://github.com/edeneast/nightfox.nvim/raw/main/extra/carbonfox/nightfox_alacritty.yml -colors: - # Default colors - primary: - background: '0x161616' - foreground: '0xf2f4f8' - # Normal colors - normal: - black: '0x282828' - red: '0xee5396' - green: '0x25be6a' - yellow: '0x08bdba' - blue: '0x78a9ff' - magenta: '0xbe95ff' - cyan: '0x33b1ff' - white: '0xdfdfe0' - # Bright colors - bright: - black: '0x484848' - red: '0xf16da6' - green: '0x46c880' - yellow: '0x2dc7c4' - blue: '0x8cb6ff' - magenta: '0xc8a5ff' - cyan: '0x52bdff' - white: '0xe4e4e5' - indexed_colors: - - { index: 16, color: '0x3ddbd9' } - - { index: 17, color: '0xff7eb6' } diff --git a/modules/home.legacy/conf/alacritty/yaml/cursor.yml b/modules/home.legacy/conf/alacritty/yaml/cursor.yml deleted file mode 100644 index 5a3946f2..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/cursor.yml +++ /dev/null @@ -1,53 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -cursor: - # Cursor style - style: - # Cursor shape - # - # Values for `shape`: - # - ▇ Block - # - _ Underline - # - | Beam - shape: Beam - - # Cursor blinking state - # - # Values for `blinking`: - # - Never: Prevent the cursor from ever blinking - # - Off: Disable blinking by default - # - On: Enable blinking by default - # - Always: Force the cursor to always blink - blinking: On - - # Vi mode cursor style - # - # If the vi mode cursor style is `None` or not specified, it will fall back to - # the style of the active value of the normal cursor. - # - # See `cursor.style` for available options. - vi_mode_style: None - - # Cursor blinking interval in milliseconds. - blink_interval: 750 - - # Time after which cursor stops blinking, in seconds. - # - # Specifying '0' will disable timeout for blinking. - blink_timeout: 5 - - # If this is `true`, the cursor will be rendered as a hollow box when the - # window is not focused. - unfocused_hollow: true - - # Thickness of the cursor relative to the cell width as floating point number - # from `0.0` to `1.0`. - thickness: 0.15 diff --git a/modules/home.legacy/conf/alacritty/yaml/debug.yml b/modules/home.legacy/conf/alacritty/yaml/debug.yml deleted file mode 100644 index e876d801..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/debug.yml +++ /dev/null @@ -1,39 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -#debug: - # Display the time it takes to redraw each frame. - #render_timer: false - - # Keep the log file after quitting Alacritty. - #persistent_logging: false - - # Log level - # - # Values for `log_level`: - # - Off - # - Error - # - Warn - # - Info - # - Debug - # - Trace - #log_level: Warn - - # Renderer override. - # - glsl3 - # - gles2 - # - gles2_pure - #renderer: None - - # Print all received window events. - #print_events: false - - # Highlight window damage information. - #highlight_damage: false diff --git a/modules/home.legacy/conf/alacritty/yaml/env.yml b/modules/home.legacy/conf/alacritty/yaml/env.yml deleted file mode 100644 index 585416a1..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/env.yml +++ /dev/null @@ -1,21 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Any items in the `env` entry below will be added as -# environment variables. Some entries may override variables -# set by alacritty itself. -env: - # TERM variable - # - # This value is used to set the `$TERM` environment variable for - # each instance of Alacritty. If it is not present, alacritty will - # check the local terminfo database and use `alacritty` if it is - # available, otherwise `xterm-256color` is used. - TERM: alacritty diff --git a/modules/home.legacy/conf/alacritty/yaml/font.yml b/modules/home.legacy/conf/alacritty/yaml/font.yml deleted file mode 100644 index a711f231..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/font.yml +++ /dev/null @@ -1,83 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Font configuration -font: - # Normal (roman) font face - normal: - # Font family - # - # Default: - # - (macOS) Menlo - # - (Linux/BSD) monospace - # - (Windows) Consolas - # family: Source Code Pro - # family: SauceCodePro Nerd Font - family: SauceCodePro Nerd Font Mono - - # The `style` can be specified to pick a specific face. - style: Regular - - # Bold font face - #bold: - # Font family - # - # If the bold family is not specified, it will fall back to the - # value specified for the normal font. - # family: Source Code Pro - - # The `style` can be specified to pick a specific face. - #style: Bold - - # Italic font face - #italic: - # Font family - # - # If the italic family is not specified, it will fall back to the - # value specified for the normal font. - # family: Source Code Pro - - # The `style` can be specified to pick a specific face. - #style: Italic - - # Bold italic font face - #bold_italic: - # Font family - # - # If the bold italic family is not specified, it will fall back to the - # value specified for the normal font. - # family: Source Code Pro - - # The `style` can be specified to pick a specific face. - #style: Bold Italic - - # Point size - size: 12.0 - - # Offset is the extra space around each character. `offset.y` can be thought - # of as modifying the line spacing, and `offset.x` as modifying the letter - # spacing. - offset: - x: -1 - y: -1 - - # Glyph offset determines the locations of the glyphs within their cells with - # the default being at the bottom. Increasing `x` moves the glyph to the - # right, increasing `y` moves the glyph upward. - glyph_offset: - x: -1 - y: -1 - - # Use built-in font for box drawing characters. - # - # If `true`, Alacritty will use a custom built-in font for box drawing - # characters (Unicode points 2500 - 259f). - # - builtin_box_drawing: true # TODO: diff --git a/modules/home.legacy/conf/alacritty/yaml/hints.yml b/modules/home.legacy/conf/alacritty/yaml/hints.yml deleted file mode 100644 index b9d75378..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/hints.yml +++ /dev/null @@ -1,87 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Hints -# -# Terminal hints can be used to find text or hyperlink in the visible part of -# the terminal and pipe it to other applications. -hints: - # Keys used for the hint labels. - alphabet: "jfkdls;ahgurieowpq" - - # List with all available hints - # - # Each hint must have any of `regex` or `hyperlinks` field and either an - # `action` or a `command` field. The fields `mouse`, `binding` and - # `post_processing` are optional. - # - # The `hyperlinks` option will cause OSC 8 escape sequence hyperlinks to be - # highlighted. - # - # The fields `command`, `binding.key`, `binding.mods`, `binding.mode` and - # `mouse.mods` accept the same values as they do in the `key_bindings` section. - # - # The `mouse.enabled` field controls if the hint should be underlined while - # the mouse with all `mouse.mods` keys held or the vi mode cursor is above it. - # - # If the `post_processing` field is set to `true`, heuristics will be used to - # shorten the match if there are characters likely not to be part of the hint - # (e.g. a trailing `.`). This is most useful for URIs and applies only to - # `regex` matches. - # - # Values for `action`: - # - Copy - # Copy the hint's text to the clipboard. - # - Paste - # Paste the hint's text to the terminal or search. - # - Select - # Select the hint's text. - # - MoveViModeCursor - # Move the vi mode cursor to the beginning of the hint. - enabled: - - regex: "(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\ - [^\u0000-\u001F\u007F-\u009F<>\"\\s{-}\\^⟨⟩`]+" - hyperlinks: true - command: xdg-open - post_processing: true - mouse: - enabled: true - mods: None - binding: - key: U - mods: Control|Shift - - - regex: "([^ '\"`=:\\[\\(]*/)([^/: '\"`\\)\\]]*)" - action: Paste - post_procesing: false - binding: - key: T - mods: Control|Shift - - - regex: "([a-z0-9]{7})\\s" - action: Paste - post_procesing: false - binding: - key: H - mods: Control|Shift - - # multi regex for different purposes: - # 2. UUIDs - # 3. hex (for example signatures) - # 4. IP addresses -# - regex: "([[:alnum:]_$%&+=/@-]+)\ -#|([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\ -#|([0-9a-f]{12,128})\ -#|([[:digit:]]{1,3}.[[:digit:]]{1,3}.[[:digit:]]{1,3}.[[:digit:]]{1,3})" -# action: Copy -# post_processing: false -# binding: -# key: U -# mods: Control|Shift diff --git a/modules/home.legacy/conf/alacritty/yaml/key_bindings.yml b/modules/home.legacy/conf/alacritty/yaml/key_bindings.yml deleted file mode 100644 index 6bf31719..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/key_bindings.yml +++ /dev/null @@ -1,392 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Key bindings -# -# Key bindings are specified as a list of objects. For example, this is the -# default paste binding: -# -# `- { key: V, mods: Control|Shift, action: Paste }` -# -# Each key binding will specify a: -# -# - `key`: Identifier of the key pressed -# -# - A-Z -# - F1-F24 -# - Key0-Key9 -# -# A full list with available key codes can be found here: -# https://docs.rs/winit/*/winit/event/enum.VirtualKeyCode.html#variants -# -# Instead of using the name of the keys, the `key` field also supports using -# the scancode of the desired key. Scancodes have to be specified as a -# decimal number. This command will allow you to display the hex scancodes -# for certain keys: -# -# `showkey --scancodes`. -# -# Then exactly one of: -# -# - `chars`: Send a byte sequence to the running application -# -# The `chars` field writes the specified string to the terminal. This makes -# it possible to pass escape sequences. To find escape codes for bindings -# like `PageUp` (`"\x1b[5~"`), you can run the command `showkey -a` outside -# of tmux. Note that applications use terminfo to map escape sequences back -# to keys. It is therefore required to update the terminfo when changing an -# escape sequence. -# -# - `action`: Execute a predefined action -# -# - ToggleViMode -# - SearchForward -# Start searching toward the right of the search origin. -# - SearchBackward -# Start searching toward the left of the search origin. -# - Copy -# - Paste -# - IncreaseFontSize -# - DecreaseFontSize -# - ResetFontSize -# - ScrollPageUp -# - ScrollPageDown -# - ScrollHalfPageUp -# - ScrollHalfPageDown -# - ScrollLineUp -# - ScrollLineDown -# - ScrollToTop -# - ScrollToBottom -# - ClearHistory -# Remove the terminal's scrollback history. -# - Hide -# Hide the Alacritty window. -# - Minimize -# Minimize the Alacritty window. -# - Quit -# Quit Alacritty. -# - ToggleFullscreen -# - ToggleMaximized -# - SpawnNewInstance -# Spawn a new instance of Alacritty. -# - CreateNewWindow -# Create a new Alacritty window from the current process. -# - ClearLogNotice -# Clear Alacritty's UI warning and error notice. -# - ClearSelection -# Remove the active selection. -# - ReceiveChar -# - None -# -# - Vi mode exclusive actions: -# -# - Open -# Perform the action of the first matching hint under the vi mode cursor -# with `mouse.enabled` set to `true`. -# - ToggleNormalSelection -# - ToggleLineSelection -# - ToggleBlockSelection -# - ToggleSemanticSelection -# Toggle semantic selection based on `selection.semantic_escape_chars`. -# - CenterAroundViCursor -# Center view around vi mode cursor -# -# - Vi mode exclusive cursor motion actions: -# -# - Up -# One line up. -# - Down -# One line down. -# - Left -# One character left. -# - Right -# One character right. -# - First -# First column, or beginning of the line when already at the first column. -# - Last -# Last column, or beginning of the line when already at the last column. -# - FirstOccupied -# First non-empty cell in this terminal row, or first non-empty cell of -# the line when already at the first cell of the row. -# - High -# Top of the screen. -# - Middle -# Center of the screen. -# - Low -# Bottom of the screen. -# - SemanticLeft -# Start of the previous semantically separated word. -# - SemanticRight -# Start of the next semantically separated word. -# - SemanticLeftEnd -# End of the previous semantically separated word. -# - SemanticRightEnd -# End of the next semantically separated word. -# - WordLeft -# Start of the previous whitespace separated word. -# - WordRight -# Start of the next whitespace separated word. -# - WordLeftEnd -# End of the previous whitespace separated word. -# - WordRightEnd -# End of the next whitespace separated word. -# - Bracket -# Character matching the bracket at the cursor's location. -# - SearchNext -# Beginning of the next match. -# - SearchPrevious -# Beginning of the previous match. -# - SearchStart -# Start of the match to the left of the vi mode cursor. -# - SearchEnd -# End of the match to the right of the vi mode cursor. -# -# - Search mode exclusive actions: -# - SearchFocusNext -# Move the focus to the next search match. -# - SearchFocusPrevious -# Move the focus to the previous search match. -# - SearchConfirm -# - SearchCancel -# - SearchClear -# Reset the search regex. -# - SearchDeleteWord -# Delete the last word in the search regex. -# - SearchHistoryPrevious -# Go to the previous regex in the search history. -# - SearchHistoryNext -# Go to the next regex in the search history. -# -# - macOS exclusive actions: -# - ToggleSimpleFullscreen -# Enter fullscreen without occupying another space. -# -# - Linux/BSD exclusive actions: -# -# - CopySelection -# Copy from the selection buffer. -# - PasteSelection -# Paste from the selection buffer. -# -# - `command`: Fork and execute a specified command plus arguments -# -# The `command` field must be a map containing a `program` string and an -# `args` array of command line parameter strings. For example: -# `{ program: "alacritty", args: ["-e", "vttest"] }` -# -# And optionally: -# -# - `mods`: Key modifiers to filter binding actions -# -# - Command -# - Control -# - Option -# - Super -# - Shift -# - Alt -# -# Multiple `mods` can be combined using `|` like this: -# `mods: Control|Shift`. -# Whitespace and capitalization are relevant and must match the example. -# -# - `mode`: Indicate a binding for only specific terminal reported modes -# -# This is mainly used to send applications the correct escape sequences -# when in different modes. -# -# - AppCursor -# - AppKeypad -# - Search -# - Alt -# - Vi -# -# A `~` operator can be used before a mode to apply the binding whenever -# the mode is *not* active, e.g. `~Alt`. -# -# Bindings are always filled by default, but will be replaced when a new -# binding with the same triggers is defined. To unset a default binding, it can -# be mapped to the `ReceiveChar` action. Alternatively, you can use `None` for -# a no-op if you do not wish to receive input characters for that binding. -# -# If the same trigger is assigned to multiple actions, all of them are executed -# in the order they were defined in. -key_bindings: -# -# - - { key: P, mods: Control, action: Paste } - - { key: Insert, mods: Shift, action: Paste } - - { key: Slash, mods: Control, chars: "gc" } - - { key: Y, mods: Control, action: Copy } - - { key: Key0, mods: Control, action: ResetFontSize } - - { key: Equals, mods: Control, action: IncreaseFontSize } - - { key: Plus, mods: Control, action: IncreaseFontSize } - - { key: Minus, mods: Control, action: DecreaseFontSize } - - # Vi Mode - - { key: Space, mods: Control, action: ToggleViMode } - - { key: Space, mods: Control, mode: Vi, action: ScrollToBottom } - - { key: I, mode: Vi, action: ScrollToBottom } - - { key: I, mode: Vi, action: ToggleViMode } - - { key: C, mods: Control, mode: Vi, action: ScrollToBottom } - - { key: C, mods: Control, mode: Vi, action: ToggleViMode } - - { key: Escape, mode: Vi, action: ClearSelection } - - { key: Y, mods: Control, mode: Vi, action: ScrollLineUp } - - { key: E, mods: Control, mode: Vi, action: ScrollLineDown } - - { key: G, mode: Vi, action: ScrollToTop } - - { key: G, mods: Shift, mode: Vi, action: ScrollToBottom } - - { key: B, mods: Control, mode: Vi, action: ScrollPageUp } - - { key: F, mods: Control, mode: Vi, action: ScrollPageDown } - - { key: U, mods: Control, mode: Vi, action: ScrollHalfPageUp } - - { key: D, mods: Control, mode: Vi, action: ScrollHalfPageDown } - - { key: Y, mode: Vi, action: Copy } - - { key: Y, mode: Vi, action: ClearSelection } - - { key: V, mode: Vi, action: ToggleNormalSelection } - - { key: V, mods: Shift, mode: Vi, action: ToggleLineSelection } - - { key: V, mods: Control, mode: Vi, action: ToggleBlockSelection } - - { key: V, mods: Alt, mode: Vi, action: ToggleSemanticSelection } - - { key: Return, mode: Vi, action: Open } - - { key: K, mode: Vi, action: Up } - - { key: J, mode: Vi, action: Down } - - { key: H, mode: Vi, action: Left } - - { key: L, mode: Vi, action: Right } - - { key: Up, mode: Vi, action: Up } - - { key: Down, mode: Vi, action: Down } - - { key: Left, mode: Vi, action: Left } - - { key: Right, mode: Vi, action: Right } - - { key: Key0, mode: Vi, action: First } - - { key: Key4, mode: Vi, action: Last } - - { key: Key6, mods: Shift, mode: Vi, action: FirstOccupied } - - { key: H, mods: Shift, mode: Vi, action: High } - - { key: M, mods: Shift, mode: Vi, action: Middle } - - { key: L, mods: Shift, mode: Vi, action: Low } - - { key: B, mode: Vi, action: SemanticLeft } - - { key: W, mode: Vi, action: SemanticRight } - - { key: E, mode: Vi, action: SemanticRightEnd } - - { key: B, mods: Shift, mode: Vi, action: WordLeft } - - { key: W, mods: Shift, mode: Vi, action: WordRight } - - { key: E, mods: Shift, mode: Vi, action: WordRightEnd } - - { key: Key5, mods: Shift, mode: Vi, action: Bracket } - - { key: Slash, mode: Vi, action: SearchForward } - - { key: Slash, mods: Shift, mode: Vi, action: SearchBackward } - - { key: N, mode: Vi, action: SearchNext } - - { key: N, mods: Shift, mode: Vi, action: SearchPrevious } -# -# -# - #- { key: Paste, action: Paste } - #- { key: Copy, action: Copy } - #- { key: L, mods: Control, action: ClearLogNotice } - #- { key: L, mods: Control, mode: ~Vi|~Search, chars: "\x0c" } - #- { key: PageUp, mods: Shift, mode: ~Alt, action: ScrollPageUp } - #- { key: PageDown, mods: Shift, mode: ~Alt, action: ScrollPageDown } - #- { key: Home, mods: Shift, mode: ~Alt, action: ScrollToTop } - #- { key: End, mods: Shift, mode: ~Alt, action: ScrollToBottom } - - # Vi Mode - #- { key: Space, mods: Shift|Control, mode: ~Search, action: ToggleViMode } - #- { key: Space, mods: Shift|Control, mode: Vi|~Search, action: ScrollToBottom } - #- { key: Escape, mode: Vi|~Search, action: ClearSelection } - #- { key: I, mode: Vi|~Search, action: ToggleViMode } - #- { key: I, mode: Vi|~Search, action: ScrollToBottom } - #- { key: C, mods: Control, mode: Vi|~Search, action: ToggleViMode } - #- { key: Y, mods: Control, mode: Vi|~Search, action: ScrollLineUp } - #- { key: E, mods: Control, mode: Vi|~Search, action: ScrollLineDown } - #- { key: G, mode: Vi|~Search, action: ScrollToTop } - #- { key: G, mods: Shift, mode: Vi|~Search, action: ScrollToBottom } - #- { key: B, mods: Control, mode: Vi|~Search, action: ScrollPageUp } - #- { key: F, mods: Control, mode: Vi|~Search, action: ScrollPageDown } - #- { key: U, mods: Control, mode: Vi|~Search, action: ScrollHalfPageUp } - #- { key: D, mods: Control, mode: Vi|~Search, action: ScrollHalfPageDown } - #- { key: Y, mode: Vi|~Search, action: Copy } - #- { key: Y, mode: Vi|~Search, action: ClearSelection } - #- { key: Copy, mode: Vi|~Search, action: ClearSelection } - #- { key: V, mode: Vi|~Search, action: ToggleNormalSelection } - #- { key: V, mods: Shift, mode: Vi|~Search, action: ToggleLineSelection } - #- { key: V, mods: Control, mode: Vi|~Search, action: ToggleBlockSelection } - #- { key: V, mods: Alt, mode: Vi|~Search, action: ToggleSemanticSelection } - #- { key: Return, mode: Vi|~Search, action: Open } - #- { key: Z, mode: Vi|~Search, action: CenterAroundViCursor } - #- { key: K, mode: Vi|~Search, action: Up } - #- { key: J, mode: Vi|~Search, action: Down } - #- { key: H, mode: Vi|~Search, action: Left } - #- { key: L, mode: Vi|~Search, action: Right } - #- { key: Up, mode: Vi|~Search, action: Up } - #- { key: Down, mode: Vi|~Search, action: Down } - #- { key: Left, mode: Vi|~Search, action: Left } - #- { key: Right, mode: Vi|~Search, action: Right } - #- { key: Key0, mode: Vi|~Search, action: First } - #- { key: Key4, mods: Shift, mode: Vi|~Search, action: Last } - #- { key: Key6, mods: Shift, mode: Vi|~Search, action: FirstOccupied } - #- { key: H, mods: Shift, mode: Vi|~Search, action: High } - #- { key: M, mods: Shift, mode: Vi|~Search, action: Middle } - #- { key: L, mods: Shift, mode: Vi|~Search, action: Low } - #- { key: B, mode: Vi|~Search, action: SemanticLeft } - #- { key: W, mode: Vi|~Search, action: SemanticRight } - #- { key: E, mode: Vi|~Search, action: SemanticRightEnd } - #- { key: B, mods: Shift, mode: Vi|~Search, action: WordLeft } - #- { key: W, mods: Shift, mode: Vi|~Search, action: WordRight } - #- { key: E, mods: Shift, mode: Vi|~Search, action: WordRightEnd } - #- { key: Key5, mods: Shift, mode: Vi|~Search, action: Bracket } - #- { key: Slash, mode: Vi|~Search, action: SearchForward } - #- { key: Slash, mods: Shift, mode: Vi|~Search, action: SearchBackward } - #- { key: N, mode: Vi|~Search, action: SearchNext } - #- { key: N, mods: Shift, mode: Vi|~Search, action: SearchPrevious } - - # Search Mode - #- { key: Return, mode: Search|Vi, action: SearchConfirm } - #- { key: Escape, mode: Search, action: SearchCancel } - #- { key: C, mods: Control, mode: Search, action: SearchCancel } - #- { key: U, mods: Control, mode: Search, action: SearchClear } - #- { key: W, mods: Control, mode: Search, action: SearchDeleteWord } - #- { key: P, mods: Control, mode: Search, action: SearchHistoryPrevious } - #- { key: N, mods: Control, mode: Search, action: SearchHistoryNext } - #- { key: Up, mode: Search, action: SearchHistoryPrevious } - #- { key: Down, mode: Search, action: SearchHistoryNext } - #- { key: Return, mode: Search|~Vi, action: SearchFocusNext } - #- { key: Return, mods: Shift, mode: Search|~Vi, action: SearchFocusPrevious } - - # (Windows, Linux, and BSD only) - #- { key: V, mods: Control|Shift, mode: ~Vi, action: Paste } - #- { key: C, mods: Control|Shift, action: Copy } - #- { key: F, mods: Control|Shift, mode: ~Search, action: SearchForward } - #- { key: B, mods: Control|Shift, mode: ~Search, action: SearchBackward } - #- { key: C, mods: Control|Shift, mode: Vi|~Search, action: ClearSelection } - #- { key: Insert, mods: Shift, action: PasteSelection } - #- { key: Key0, mods: Control, action: ResetFontSize } - #- { key: Equals, mods: Control, action: IncreaseFontSize } - #- { key: Plus, mods: Control, action: IncreaseFontSize } - #- { key: NumpadAdd, mods: Control, action: IncreaseFontSize } - #- { key: Minus, mods: Control, action: DecreaseFontSize } - #- { key: NumpadSubtract, mods: Control, action: DecreaseFontSize } - - # (Windows only) - #- { key: Return, mods: Alt, action: ToggleFullscreen } - - # (macOS only) - #- { key: K, mods: Command, mode: ~Vi|~Search, chars: "\x0c" } - #- { key: K, mods: Command, mode: ~Vi|~Search, action: ClearHistory } - #- { key: Key0, mods: Command, action: ResetFontSize } - #- { key: Equals, mods: Command, action: IncreaseFontSize } - #- { key: Plus, mods: Command, action: IncreaseFontSize } - #- { key: NumpadAdd, mods: Command, action: IncreaseFontSize } - #- { key: Minus, mods: Command, action: DecreaseFontSize } - #- { key: NumpadSubtract, mods: Command, action: DecreaseFontSize } - #- { key: V, mods: Command, action: Paste } - #- { key: C, mods: Command, action: Copy } - #- { key: C, mods: Command, mode: Vi|~Search, action: ClearSelection } - #- { key: H, mods: Command, action: Hide } - #- { key: H, mods: Command|Alt, action: HideOtherApplications } - #- { key: M, mods: Command, action: Minimize } - #- { key: Q, mods: Command, action: Quit } - #- { key: W, mods: Command, action: Quit } - #- { key: N, mods: Command, action: CreateNewWindow } - #- { key: F, mods: Command|Control, action: ToggleFullscreen } - #- { key: F, mods: Command, mode: ~Search, action: SearchForward } - #- { key: B, mods: Command, mode: ~Search, action: SearchBackward } diff --git a/modules/home.legacy/conf/alacritty/yaml/mouse.yml b/modules/home.legacy/conf/alacritty/yaml/mouse.yml deleted file mode 100644 index c5d2acbb..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/mouse.yml +++ /dev/null @@ -1,21 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -mouse: - # Click settings - # - # The `double_click` and `triple_click` settings control the time - # alacritty should wait for accepting multiple clicks as one double - # or triple click. - double_click: { threshold: 300 } - triple_click: { threshold: 300 } - - # If this is `true`, the cursor is temporarily hidden when typing. - hide_when_typing: false diff --git a/modules/home.legacy/conf/alacritty/yaml/mouse_bindings.yml b/modules/home.legacy/conf/alacritty/yaml/mouse_bindings.yml deleted file mode 100644 index 82e2b92b..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/mouse_bindings.yml +++ /dev/null @@ -1,42 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Mouse bindings -# -# Mouse bindings are specified as a list of objects, much like the key -# bindings further below. -# -# To trigger mouse bindings when an application running within Alacritty -# captures the mouse, the `Shift` modifier is automatically added as a -# requirement. -# -# Each mouse binding will specify a: -# -# - `mouse`: -# -# - Middle -# - Left -# - Right -# - Numeric identifier such as `5` -# -# - `action` (see key bindings for actions not exclusive to mouse mode) -# -# - Mouse exclusive actions: -# -# - ExpandSelection -# Expand the selection to the current mouse cursor location. -# -# And optionally: -# -# - `mods` (see key bindings) -mouse_bindings: -# - { mouse: Right, action: ExpandSelection } -# - { mouse: Right, mods: Control, action: ExpandSelection } - - { mouse: Middle, action: Copy } diff --git a/modules/home.legacy/conf/alacritty/yaml/selection.yml b/modules/home.legacy/conf/alacritty/yaml/selection.yml deleted file mode 100644 index 100118fc..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/selection.yml +++ /dev/null @@ -1,17 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -selection: - # This string contains all characters that are used as separators for - # "semantic words" in Alacritty. - semantic_escape_chars: ",│`|:\"' ()[]{}<>\t" - - # When set to `true`, selected text will be copied to the primary clipboard. - save_to_clipboard: false diff --git a/modules/home.legacy/conf/alacritty/yaml/shell.yml b/modules/home.legacy/conf/alacritty/yaml/shell.yml deleted file mode 100644 index 4da99581..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/shell.yml +++ /dev/null @@ -1,23 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# Shell -# -# You can set `shell.program` to the path of your favorite shell, e.g. -# `/bin/fish`. Entries in `shell.args` are passed unmodified as arguments to the -# shell. -# -# Default: -# - (Linux/BSD/macOS) `$SHELL` or the user's login shell, if `$SHELL` is unset -# - (Windows) powershell -#shell: -# program: /bin/bash -# args: -# - --login diff --git a/modules/home.legacy/conf/alacritty/yaml/window.yml b/modules/home.legacy/conf/alacritty/yaml/window.yml deleted file mode 100644 index 2d4006ba..00000000 --- a/modules/home.legacy/conf/alacritty/yaml/window.yml +++ /dev/null @@ -1,99 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -window: - # Window dimensions (changes require restart) - # - # Number of lines/columns (not pixels) in the terminal. Both lines and columns - # must be non-zero for this to take effect. The number of columns must be at - # least `2`, while using a value of `0` for columns and lines will fall back - # to the window manager's recommended size - dimensions: - columns: 0 - lines: 0 - - # Window position (changes require restart) - # - # Specified in number of pixels. - # If the position is not set, the window manager will handle the placement. - #position: - # x: 0 - # y: 0 - - # Window padding (changes require restart) - # - # Blank space added around the window in pixels. This padding is scaled - # by DPI and the specified value is always added at both opposing sides. - padding: - x: 5 - y: 5 - - # Spread additional padding evenly around the terminal content. - #dynamic_padding: false - - # Window decorations - # - # Values for `decorations`: - # - full: Borders and title bar - # - none: Neither borders nor title bar - # - # Values for `decorations` (macOS only): - # - transparent: Title bar, transparent background and title bar buttons - # - buttonless: Title bar, transparent background and no title bar buttons - decorations: none - - # Background opacity - # - # Window opacity as a floating point number from `0.0` to `1.0`. - # The value `0.0` is completely transparent and `1.0` is opaque. - opacity: 0.9 - - # Startup Mode (changes require restart) - # - # Values for `startup_mode`: - # - Windowed - # - Maximized - # - Fullscreen - # - # Values for `startup_mode` (macOS only): - # - SimpleFullscreen - startup_mode: Windowed - - # Window title - title: Alacritty - - # Allow terminal applications to change Alacritty's window title. - dynamic_title: true - - # Window class (Linux/BSD only): - class: - # Application instance name - instance: Alacritty - # General application class - general: Alacritty - - # Decorations theme variant - # - # Override the variant of the System theme/GTK theme/Wayland client side - # decorations. Commonly supported values are `Dark`, `Light`, and `None` for - # auto pick-up. Set this to `None` to use the default theme variant. - decorations_theme_variant: None - - # Resize increments - # - # Prefer resizing window by discrete steps equal to cell dimensions. - #resize_increments: false - - # Make `Option` key behave as `Alt` (macOS only): - # - OnlyLeft - # - OnlyRight - # - Both - # - None (default) - #option_as_alt: None diff --git a/modules/home.legacy/conf/beets/default.nix b/modules/home.legacy/conf/beets/default.nix index 612a5f03..0879b1a5 100644 --- a/modules/home.legacy/conf/beets/default.nix +++ b/modules/home.legacy/conf/beets/default.nix @@ -42,7 +42,7 @@ in { }; replace = { - "[/]" = "\\"; + "[/]" = "\\\\"; "[\\x00-\\x1f]" = ""; "\\s+$" = ""; "^\\s+" = ""; @@ -67,9 +67,19 @@ in { paths = let join = lib.strings.concatStringsSep "/"; in { - "albumtype:live" = join ["[Live, please delete]" "$genre" "$first_artist" "$album ($albumtype)" "$track $title"]; - - default = "$albumartist/$album%aunique{}/$track $title"; + "albumtype:live" = join [ + "[Live, please delete]" + "$genre" + "$primary_artist" # (= $albumartists[0]). From inline plugin + "$album ($albumtype)" + "$track $title" + ]; + + default = join [ + "$primary_artist" # (= $albumartists[0]). From inline plugin + "$album%aunique{}" + "$track $title" + ]; }; inherit plugins; @@ -95,18 +105,17 @@ in { }; mpdIntegration = { - enableStats = true; enableUpdate = true; host = config.home.sessionVariables.MPD_HOST; }; }; - # Use the json formatter instead of the YAML one, as the YAML formatter mangles the - # longer python inline strings. - # YAML is a superset of JSON. - xdg.configFile."beets/config.yaml".source = - lib.mkForce - ((pkgs.formats.json {}).generate - "beets-config" - config.programs.beets.settings); + # # Use the json formatter instead of the YAML one, as the YAML formatter mangles the + # # longer python inline strings. + # # YAML is a superset of JSON. + # xdg.configFile."beets/config.yaml".source = + # lib.mkForce + # ((pkgs.formats.json {}).generate + # "beets-config" + # config.programs.beets.settings); } diff --git a/modules/home.legacy/conf/beets/plugins.nix b/modules/home.legacy/conf/beets/plugins.nix index a3abb580..aaeab843 100644 --- a/modules/home.legacy/conf/beets/plugins.nix +++ b/modules/home.legacy/conf/beets/plugins.nix @@ -20,6 +20,8 @@ # Extract things from the music file # "xtractor" + "musicbrainz" + # Calculate replay gain "replaygain" @@ -29,11 +31,7 @@ # Show tags on files/queries "info" - # Create playlist from `play_count`/`skip_count` (gathered by the `mpdstats` - # plugin) - # Note that this should come _before_ the `mpdupdate` plugin, to ensure that - # `mpdupgate` can propagate changed playlist to `mpd`. - "smartplaylist" + "inline" # Warn, when importing a matching item "ihate" @@ -56,6 +54,5 @@ # Allow beets to understand deezer id's # "deezer" - "mpdstats" # Transfer MPD stats to beets "mpdupdate" # Update MPD database on import ] diff --git a/modules/home.legacy/conf/beets/plugins/default.nix b/modules/home.legacy/conf/beets/plugins/default.nix index ddba95e0..2b2bd607 100644 --- a/modules/home.legacy/conf/beets/plugins/default.nix +++ b/modules/home.legacy/conf/beets/plugins/default.nix @@ -12,9 +12,9 @@ ./badfiles ./duplicates ./ihate + ./inline ./lyrics ./replaygain - ./smartplaylist # ./xtractor ]; } diff --git a/modules/home.legacy/conf/beets/plugins/ihate/default.nix b/modules/home.legacy/conf/beets/plugins/ihate/default.nix index 51cb6f11..2356ec3b 100644 --- a/modules/home.legacy/conf/beets/plugins/ihate/default.nix +++ b/modules/home.legacy/conf/beets/plugins/ihate/default.nix @@ -11,6 +11,7 @@ programs.beets.settings.ihate = { warn = [ "title:commentary" + "title:live" "albumtype:live" ]; }; diff --git a/modules/home.legacy/conf/iamb/default.nix b/modules/home.legacy/conf/beets/plugins/inline/default.nix index 764e2efc..bf476c9f 100644 --- a/modules/home.legacy/conf/iamb/default.nix +++ b/modules/home.legacy/conf/beets/plugins/inline/default.nix @@ -8,5 +8,9 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. {...}: { - xdg.configFile."iamb/config.json".source = ./config.json; + programs.beets.settings.item_fields = { + primary_artist = + # python + ''albumartists[0]''; + }; } diff --git a/modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix b/modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix deleted file mode 100644 index 9b52c1ad..00000000 --- a/modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix +++ /dev/null @@ -1,42 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{config, ...}: { - programs.beets.settings.smartplaylist = { - relative_to = config.services.mpd.musicDirectory; - playlist_dir = config.services.mpd.playlistDirectory; - forward_slash = false; - - # Show the real m3u file paths, when running `--pretend` - pretend_paths = true; - - playlists = [ - { - name = "artists-$first_artist.m3u"; - query = ""; - } - { - name = "ratings-good.m3u"; - query = "rating:0.7..1.0"; - } - { - name = "ratings-mediocre.m3u"; - query = "rating:0.4..0.7"; - } - { - name = "ratings-bad.m3u"; - query = "rating:0.0..0.4"; - } - { - name = "not_played.m3u"; - query = "-play_count: artist:"; - } - ]; - }; -} diff --git a/modules/home.legacy/conf/default.nix b/modules/home.legacy/conf/default.nix index 622f32d5..89502a64 100644 --- a/modules/home.legacy/conf/default.nix +++ b/modules/home.legacy/conf/default.nix @@ -9,17 +9,13 @@ # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. {...}: { imports = [ - ./alacritty ./beets ./btop ./dconf ./gammastep ./gtk ./himalaya - ./hyfetch - ./iamb ./keepassxc - ./latexindent ./mail ./mbsync ./mumble @@ -27,10 +23,7 @@ ./npm ./prusa_slicer ./python - ./rclone - ./rofi ./starship ./swayidle - ./tridactyl ]; } diff --git a/modules/home.legacy/conf/hyfetch/default.nix b/modules/home.legacy/conf/hyfetch/default.nix deleted file mode 100644 index bac0731e..00000000 --- a/modules/home.legacy/conf/hyfetch/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{...}: { - programs.hyfetch = { - enable = true; - settings = { - preset = "rainbow"; - mode = "rgb"; - light_dark = "dark"; - lightness = 0.65; - color_align = { - mode = "horizontal"; - custom_colors = []; - fore_back = null; - }; - backend = "neofetch"; - distro = null; - pride_month_shown = []; - pride_month_disable = false; - }; - }; -} diff --git a/modules/home.legacy/conf/iamb/config.json b/modules/home.legacy/conf/iamb/config.json deleted file mode 100644 index ca06d543..00000000 --- a/modules/home.legacy/conf/iamb/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "profiles": { - "soispha": { - "user_id": "@soispha:vhack.eu", - "url": "https://matrix.vhack.eu" - } - }, - "default_profile": "soispha" -} diff --git a/modules/home.legacy/conf/iamb/config.json.license b/modules/home.legacy/conf/iamb/config.json.license deleted file mode 100644 index eae6a84c..00000000 --- a/modules/home.legacy/conf/iamb/config.json.license +++ /dev/null @@ -1,9 +0,0 @@ -nixos-config - My current NixOS configuration - -Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -SPDX-License-Identifier: GPL-3.0-or-later - -This file is part of my nixos-config. - -You should have received a copy of the License along with this program. -If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. diff --git a/modules/home.legacy/conf/latexindent/default.nix b/modules/home.legacy/conf/latexindent/default.nix deleted file mode 100644 index aeedd411..00000000 --- a/modules/home.legacy/conf/latexindent/default.nix +++ /dev/null @@ -1,13 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{...}: { - xdg.configFile."latexindent/indentconfig.yaml".source = ./indentconfig.yaml; - xdg.configFile."latexindent/mysettings.yaml".source = ./mysettings.yaml; -} diff --git a/modules/home.legacy/conf/latexindent/indentconfig.yaml b/modules/home.legacy/conf/latexindent/indentconfig.yaml deleted file mode 100644 index 6465a17a..00000000 --- a/modules/home.legacy/conf/latexindent/indentconfig.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -paths: -- /home/dt/.config/latexindent/mysettings.yaml diff --git a/modules/home.legacy/conf/latexindent/mysettings.yaml b/modules/home.legacy/conf/latexindent/mysettings.yaml deleted file mode 100644 index 122821da..00000000 --- a/modules/home.legacy/conf/latexindent/mysettings.yaml +++ /dev/null @@ -1,682 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -# defaultSettings.yaml for latexindent.pl, version 3.19.1, 2022-12-04 -# a script that aims to -# beautify .tex, .sty, .cls files -# -# (or latexindent.exe if you're on Windows) -# -#--------------------------------------------------------------------------------------- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# See http://www.gnu.org/licenses/. -# -# Chris Hughes, 2017 -# -# For all communication, please visit: https://github.com/cmhughes/latexindent.pl -# -#--------------------------------------------------------------------------------------- -# You should feel encouraged to change anything you like in these settings, but -# it would probably be better to have your own user settings -# files somewhere else - remember that this file may be overwritten -# when you update your tex distribution. Please see the manual linked from: -# -# https://github.com/cmhughes/latexindent.pl -# -# for details of how to create and configure your own settings files. -# -# Please read the manual (linked from above) first to understand what each switch does. -# -#--------------------------------------------------------------------------------------- - -# latexindent can be called to act on a file without using the file's extension, -# e.g, simply -# latexindent myfile -# in which case the choice of file extension is chosen -# according to the choices made in fileExtensionPreference -# Other file extensions can be added. -fileExtensionPreference: - .tex: 1 - .sty: 2 - .cls: 3 - .bib: 4 - -# default file extension of backup file (if -w switch is active) -# for example, if your .tex file is called -# myfile.tex -# and you specify the backupExtension as BACKUP.bak then your -# backup file will be -# myfileBACKUP.bak -backupExtension: .bak - -# only one backup per file: -# - if onlyOneBackUp is 0 then, as a safety measure, -# the number on the extension increments by 1 each time: -# -# myfile.bak0, myfile.bak1, myfile.bak2 -# - if you set onlyOnebackUp to 1, then the backup file will -# be overwritten each time (not recommended until you trust the script) -onlyOneBackUp: 0 - -# some users may want a finite number of backup files, -# say at most 3; in which case, they can change this maxNumberOfBackUps. -# -# If maxNumberOfBackUps is set to 0 (or less) then infinitely -# many backups are possible, unless onlyOneBackUp is switched on -maxNumberOfBackUps: 0 - -# some users may wish to cycle through back up files. -# -# for example, with maxNumberOfBackUps: 4, they may -# wish to delete the oldest back up file, and keep only the most recent. -# -# copy myfile.bak1 to myfile.bak0 -# copy myfile.bak2 to myfile.bak1 -# copy myfile.bak3 to myfile.bak2 -# copy myfile.bak4 to myfile.bak3 -# -# the back up will be written to myfile.bak4 -cycleThroughBackUps: 0 - -# preferences for information displayed in the log file -logFilePreferences: - showEveryYamlRead: 1 - showAmalgamatedSettings: 0 - showDecorationStartCodeBlockTrace: 0 - showDecorationFinishCodeBlockTrace: 0 - endLogFileWith: '--------------' - showGitHubInfoFooter: 1 - Dumper: - Terse: 1 - Indent: 1 - Useqq: 1 - Deparse: 1 - Quotekeys: 0 - Sortkeys: 1 - Pair: " => " - -# verbatim environments specified -# in this field will not be changed at all! -verbatimEnvironments: - verbatim: 1 - lstlisting: 1 - minted: 1 - -# verbatim commands such as \verb! body !, \lstinline$something else$ -verbatimCommands: - verb: 1 - lstinline: 1 - -# no indent blocks (not necessarily verbatim -# environments) which are marked as %\begin{noindent} -# or anything else that you detail in the following -noIndentBlock: - noindent: 1 - cmhtest: 1 - -# \begin{document} and \end{document} are treated differently -# by latexindent within filecontents environments -fileContentsEnvironments: - filecontents: 1 - filecontents*: 1 - -# indent preamble -indentPreamble: 1 - -# assume no preamble in cls, sty, by default -lookForPreamble: - .tex: 1 - .sty: 0 - .cls: 0 - .bib: 0 - -# some preambles can contain \begin and \end statements -# that are not in their 'standard environment block', for example, -# consider the following key = values: -# preheadhook={\begin{mdframed}[style=myframedstyle]}, -# postfoothook=\end{mdframed}, -preambleCommandsBeforeEnvironments: 0 - -# default value of indentation -defaultIndent: " " - -# remove trailing whitespace from all lines -removeTrailingWhitespace: - beforeProcessing: 0 - afterProcessing: 1 - -# name of code blocks that should have their body aligned at ampersand delimiters -lookForAlignDelims: - tabular: - delims: 1 - alignDoubleBackSlash: 1 - spacesBeforeDoubleBackSlash: 1 - multiColumnGrouping: 0 - alignRowsWithoutMaxDelims: 1 - spacesBeforeAmpersand: 1 - spacesAfterAmpersand: 1 - justification: left - alignFinalDoubleBackSlash: 0 - dontMeasure: 0 - delimiterRegEx: '(?<!\\)(&)' - delimiterJustification: left - lookForChildCodeBlocks: 1 - tabularx: - delims: 1 - longtable: 1 - tabu: 1 - array: 1 - matrix: 1 - listabla: 1 - # amsmath - align: 1 - align*: 1 - alignat: 1 - alignat*: 1 - aligned: 1 - bmatrix: 1 - Bmatrix: 1 - cases: 1 - flalign: 1 - flalign*: 1 - pmatrix: 1 - vmatrix: 1 - Vmatrix: 1 - # mathtools - cases*: 1 - dcases: 1 - dcases*: 1 - rcases: 1 - rcases*: 1 - drcases: 1 - drcases*: 1 - # nicematrix - NiceTabular: 1 - NiceMatrix: 1 - pNiceMatrix: 1 - bNiceMatrix: 1 - BNiceMatrix: 1 - vNiceMatrix: 1 - VNiceMatrix: 1 - NiceArray: 1 - pNiceArrayC: 1 - bNiceArrayC: 1 - BNiceArrayC: 1 - vNiceArrayC: 1 - VNiceArrayC: 1 - NiceArrayCwithDelims: 1 - pNiceArrayRC: 1 - bNiceArrayRC: 1 - BNiceArrayRC: 1 - vNiceArrayRC: 1 - VNiceArrayRC: 1 - NiceArrayRCwithDelims: 1 - # tabularray - tblr: 1 - longtblr: 1 - talltblr: 1 - -# if you want the script to look for \item commands -# and format it, as follows (for example), -# \begin{itemize} -# \item content here -# next line is indented -# next line is indented -# \item another item -# \end{itemize} -# then populate indentAfterItems. See also itemNames -indentAfterItems: - itemize: 1 - itemize*: 1 - enumerate: 1 - enumerate*: 1 - description: 1 - description*: 1 - list: 1 - -# if you want to use other names for your items (for example, \part) -# then populate them here; note that you can trick latexindent.pl -# into indenting all kinds of commands (within environments specified in -# indentAfterItems) using this technique. -itemNames: - item: 1 - myitem: 1 - -# specialBeginEnd is, by default, mathmode focus, although -# there's no restrictions -specialBeginEnd: - displayMath: - begin: '\\\[' - end: '\\\]' - lookForThis: 1 - inlineMath: - begin: '(?<!\$)(?<!\\)\$(?!\$)' - end: '(?<!\\)\$(?!\$)' - lookForThis: 1 - displayMathTeX: - begin: '\$\$' - end: '\$\$' - lookForThis: 1 - specialBeforeCommand: 0 - -# if you want to add indentation after -# a heading, such as \part, \chapter, etc -# then populate it in here - you can add -# an indent rule to indentRules if you would -# like something other than defaultIndent -# -# you can also change the level if you like, -# or add your own title command -indentAfterHeadings: - part: - indentAfterThisHeading: 0 - level: 1 - chapter: - indentAfterThisHeading: 0 - level: 2 - section: - indentAfterThisHeading: 0 - level: 3 - subsection: - indentAfterThisHeading: 0 - level: 4 - subsection*: - indentAfterThisHeading: 0 - level: 4 - subsubsection: - indentAfterThisHeading: 0 - level: 5 - paragraph: - indentAfterThisHeading: 0 - level: 6 - subparagraph: - indentAfterThisHeading: 0 - level: 7 - -# maximum indentation, off by default -maximumIndentation: -1 - -# if you don't want to have additional indentation -# in a code block, then add it to noAdditionalIndent; note that -# code blocks in this field will inherit -# the *current* level of indentation they just won't -# get any *additional* indentation -noAdditionalIndent: - myexample: 0 - mydefinition: 0 - problem: 0 - exercises: 0 - mysolution: 0 - foreach: 0 - widepage: 0 - comment: 0 - document: 0 - frame: 0 - -# if you have indent rules for particular code blocks -# then you can populate them in indentRules; for example, you might just want -# to use a space " " or maybe a double tab " " -indentRules: - myenvironment: " " - anotherenvironment: " " - chapter: " " - section: " " - item: " " - myitem: " " - -# set noAdditionalIndent globally for codeblocks -noAdditionalIndentGlobal: - environments: 0 - commands: 0 - optionalArguments: 0 - mandatoryArguments: 0 - ifElseFi: 0 - items: 0 - keyEqualsValuesBracesBrackets: 0 - namedGroupingBracesBrackets: 0 - UnNamedGroupingBracesBrackets: 0 - specialBeginEnd: 0 - afterHeading: 0 - filecontents: 0 - -# set indentRules globally for codeblocks; these need -# to be horizontal spaces, if they are to be used -indentRulesGlobal: - environments: 0 - commands: 0 - optionalArguments: 0 - mandatoryArguments: 0 - ifElseFi: 0 - items: 0 - keyEqualsValuesBracesBrackets: 0 - namedGroupingBracesBrackets: 0 - UnNamedGroupingBracesBrackets: 0 - specialBeginEnd: 0 - afterHeading: 0 - filecontents: 0 - -# command code block details -commandCodeBlocks: - roundParenthesesAllowed: 1 - stringsAllowedBetweenArguments: - - - amalgamate: 1 - - 'node' - - 'at' - - 'to' - - 'decoration' - - '\+\+' - - '\-\-' - - '\#\#\d' - commandNameSpecial: - - - amalgamate: 1 - - '@ifnextchar\[' - -# change dos line breaks into unix -dos2unixlinebreaks: 1 - -# modifyLineBreaks will only be searched if the -m -# switch is active -# -# poly-switch examples: -# -# BeginStartsOnOwnLine: -# modify line breaks before a begin statement -# -# when set to -1, e.g -# some text some text -# \begin{myenvironment} -# will be changed to -# some text some text \begin{myenvironment} -# when set to 0, the switch is ignored -# when set to 1, e.g -# some text some text \begin{myenvironment} -# will be changed to -# some text some text -# \begin{myenvironment} -# when set to 2, e.g -# some text some text \begin{myenvironment} -# will be changed to -# some text some text% -# \begin{myenvironment} -# when set to 3, e.g -# some text some text \begin{myenvironment} -# will be changed to -# some text some text -# -# \begin{myenvironment} -# -# BodyStartsOnOwnLine: -# modify line breaks before the beginning of the body -# -# when set to -1, e.g -# \begin{myenv} -# body text body text -# will be changed to -# \begin{myenv}body text body text -# when set to 0, the switch is ignored -# when set to 1, e.g -# \begin{myenv}body text body text -# will be changed to -# \begin{myenv} -# body text body text -# when set to 2, e.g -# \begin{myenv}body text body text -# will be changed to -# \begin{myenv}% -# body text body text -# when set to 3, e.g -# \begin{myenv}body text body text -# will be changed to -# \begin{myenv} -# -# body text body text -# -# EndStartsOnOwnLine: -# modify line breaks before the end statement -# -# when set to -1, e.g -# some text some text -# \end{myenvironment} -# will be changed to -# some text some text \end{myenvironment} -# when set to 0, the switch is ignored -# when set to 1, e.g -# some text some text \end{myenvironment} -# will be changed to -# some text some text -# \end{myenvironment} -# when set to 2, e.g -# some text some text \end{myenvironment} -# will be changed to -# some text some text% -# \end{myenvironment} -# when set to 3, e.g -# some text some text \end{myenvironment} -# will be changed to -# some text some text -# -# \end{myenvironment} -# -# EndFinishesWithLineBreak: -# modify line breaks after the end statement -# -# when set to -1, e.g -# \end{myenvironment} -# some text some text -# will be changed to -# \end{myenvironment}some text some text -# when set to 0, the switch is ignored -# when set to 1, e.g -# \end{myenvironment}some text some text -# will be changed to -# \end{myenvironment} -# some text some text -# when set to 2, e.g -# \end{myenvironment}some text some text -# will be changed to -# \end{myenvironment}% -# some text some text -# when set to 3, e.g -# \end{myenvironment}some text some text -# will be changed to -# \end{myenvironment} -# -# some text some text -# -# you can specify settings on a per-name basis -modifyLineBreaks: - preserveBlankLines: 1 - condenseMultipleBlankLinesInto: 1 - oneSentencePerLine: - manipulateSentences: 0 - removeSentenceLineBreaks: 1 - multipleSpacesToSingle: 1 - textWrapSentences: 0 # setting to 1 disables main textWrap routine - sentenceIndent: "" - sentencesFollow: - par: 1 - blankLine: 1 - fullStop: 1 - exclamationMark: 1 - questionMark: 1 - rightBrace: 1 - commentOnPreviousLine: 1 - other: 0 - sentencesBeginWith: - A-Z: 1 - a-z: 0 - other: 0 - sentencesEndWith: - basicFullStop: 0 - betterFullStop: 1 - exclamationMark: 1 - questionMark: 1 - other: 0 - textWrapOptions: - columns: 0 - multipleSpacesToSingle: 1 - removeBlockLineBreaks: 1 - blocksFollow: - headings: 1 - commentOnPreviousLine: 1 - par: 1 - blankLine: 1 - verbatim: 1 - filecontents: 1 - other: '\\\]|\\item(?:\h|\[)' # regex - blocksBeginWith: - A-Z: 1 - a-z: 1 - 0-9: 0 - other: 0 # regex - blocksEndBefore: - commentOnOwnLine: 1 - verbatim: 1 - filecontents: 1 - other: '\\begin\{|\\\[|\\end\{' # regex - huge: overflow # forbid mid-word line breaks - separator: "" - # poly-switches below here - environments: - BeginStartsOnOwnLine: 0 - BodyStartsOnOwnLine: 0 - EndStartsOnOwnLine: 0 - EndFinishesWithLineBreak: 0 - equation*: - BeginStartsOnOwnLine: 0 - BodyStartsOnOwnLine: 0 - EndStartsOnOwnLine: 0 - EndFinishesWithLineBreak: 0 - ifElseFi: - IfStartsOnOwnLine: 0 - BodyStartsOnOwnLine: 0 - OrStartsOnOwnLine: 0 - OrFinishesWithLineBreak: 0 - ElseStartsOnOwnLine: 0 - ElseFinishesWithLineBreak: 0 - FiStartsOnOwnLine: 0 - FiFinishesWithLineBreak: 0 - ifnum: - IfStartsOnOwnLine: 0 - BodyStartsOnOwnLine: 0 - OrStartsOnOwnLine: 0 - OrFinishesWithLineBreak: 0 - ElseStartsOnOwnLine: 0 - ElseFinishesWithLineBreak: 0 - FiStartsOnOwnLine: 0 - FiFinishesWithLineBreak: 0 - commands: - CommandStartsOnOwnLine: 0 - CommandNameFinishesWithLineBreak: 0 - optionalArguments: - LSqBStartsOnOwnLine: 0 - OptArgBodyStartsOnOwnLine: 0 - RSqBStartsOnOwnLine: 0 - RSqBFinishesWithLineBreak: 0 - mandatoryArguments: - LCuBStartsOnOwnLine: 0 - MandArgBodyStartsOnOwnLine: 0 - RCuBStartsOnOwnLine: 0 - RCuBFinishesWithLineBreak: 0 - keyEqualsValuesBracesBrackets: - KeyStartsOnOwnLine: 0 - EqualsStartsOnOwnLine: 0 - EqualsFinishesWithLineBreak: 0 - items: - ItemStartsOnOwnLine: 0 - ItemFinishesWithLineBreak: 0 - namedGroupingBracesBrackets: - NameStartsOnOwnLine: 0 - NameFinishesWithLineBreak: 0 - specialBeginEnd: - SpecialBeginStartsOnOwnLine: 0 - SpecialBodyStartsOnOwnLine: 0 - SpecialEndStartsOnOwnLine: 0 - SpecialEndFinishesWithLineBreak: 0 - verbatim: - VerbatimBeginStartsOnOwnLine: 0 - VerbatimEndFinishesWithLineBreak: 0 - -# replacements, only active when either -r or -rr switches are active -replacements: - - - amalgamate: 1 - - - this: 'latexindent.pl' - that: 'pl.latexindent' - lookForThis: 0 - when: before - -# fineTuning allows you to tweak the internal pattern matching that -# is central to latexindent.pl -fineTuning: - environments: - name: '[a-zA-Z@\*0-9_\\]+' - ifElseFi: - name: '(?!@?if[a-zA-Z@]*?\{)@?if[a-zA-Z@]*?' - commands: - name: '[+a-zA-Z@\*0-9_\:]+?' - items: - canBeFollowedBy: '(?:\[[^]]*?\])|(?:<[^>]*?>)' - keyEqualsValuesBracesBrackets: - name: '[a-zA-Z@\*0-9_\/.:\#-]+[a-zA-Z@\*0-9_\/.\h\{\}:\#-]*?' - follow: '(?:(?<!\\)\{)|,|(?:(?<!\\)\[)' - namedGroupingBracesBrackets: - name: '[0-9\.a-zA-Z@\*><]+?' - follow: '\h|\R|\{|\[|\$|\)|\(' - UnNamedGroupingBracesBrackets: - follow: '\{|\[|,|&|\)|\(|\$' - arguments: - before: '(?:#\d\h*;?,?\/?)+|\<.*?\>' - between: '_|\^|\*' - trailingComments: - notPreceededBy: '(?<!\\)' - modifyLineBreaks: - doubleBackSlash: '\\\\(?:\h*\[\h*\d+\h*[a-zA-Z]+\h*\])?' - comma: ',' - betterFullStop: |- - (?x) # ignore spaces in the below - (?: # - \.\) # .) - (?!\h*[a-z]) # not *followed by* a-z - ) # - | # OR - (?: # - (?<! # not *preceeded by* - (?: # - (?:[eE]\.[gG]) # e.g OR E.g OR e.G OR E.G - | # - (?:[iI]\.[eE]) # i.e OR I.e OR i.E OR I.E - | # - (?:etc) # etc - ) # - ) # - ) # - \. # . - (?! # not *followed by* - (?: # - [a-zA-Z0-9-~,] # - | # - \), # ), - | # - \)\. # ). - ) # - ) # diff --git a/modules/home.legacy/conf/mail/default.nix b/modules/home.legacy/conf/mail/default.nix index 17957c82..1805606c 100644 --- a/modules/home.legacy/conf/mail/default.nix +++ b/modules/home.legacy/conf/mail/default.nix @@ -58,5 +58,5 @@ in { inherit accounts; }; - programs.git.extraConfig = accountCredentials; + programs.git.settings = accountCredentials; } diff --git a/modules/home.legacy/conf/rclone/default.nix b/modules/home.legacy/conf/rclone/default.nix deleted file mode 100644 index 87de601b..00000000 --- a/modules/home.legacy/conf/rclone/default.nix +++ /dev/null @@ -1,12 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{...}: { - xdg.configFile."rclone/rclone.conf".source = ./rclone.conf; -} diff --git a/modules/home.legacy/conf/rclone/rclone.conf b/modules/home.legacy/conf/rclone/rclone.conf deleted file mode 100644 index 9e1c4f08..00000000 --- a/modules/home.legacy/conf/rclone/rclone.conf +++ /dev/null @@ -1,10 +0,0 @@ -[vhack1] -type = sftp -host = server1.vhack.eu -user = soispha -key_use_agent = true -known_hosts_file = ~/.local/share/ssh/known_hosts -shell_type = unix -md5sum_command = md5sum -sha1sum_command = sha1sum - diff --git a/modules/home.legacy/conf/rclone/rclone.conf.license b/modules/home.legacy/conf/rclone/rclone.conf.license deleted file mode 100644 index eae6a84c..00000000 --- a/modules/home.legacy/conf/rclone/rclone.conf.license +++ /dev/null @@ -1,9 +0,0 @@ -nixos-config - My current NixOS configuration - -Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -SPDX-License-Identifier: GPL-3.0-or-later - -This file is part of my nixos-config. - -You should have received a copy of the License along with this program. -If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. diff --git a/modules/home.legacy/conf/tridactyl/config.vim b/modules/home.legacy/conf/tridactyl/config.vim deleted file mode 100644 index aa843bc8..00000000 --- a/modules/home.legacy/conf/tridactyl/config.vim +++ /dev/null @@ -1,57 +0,0 @@ -" nixos-config - My current NixOS configuration -" -" Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -" SPDX-License-Identifier: GPL-3.0-or-later -" -" This file is part of my nixos-config. -" -" You should have received a copy of the License along with this program. -" If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -" vim: filetype=vim - -" This wipes all existing settings. This means that if a setting in this file -" is removed, then it will return to default. In other words, this file serves -" as an enforced single point of truth for Tridactyl's configuration. -sanitize tridactyllocal tridactylsync - -" Just use a blank page for new tab. It would be nicer to use the standard -" Firefox homepage, but Tridactyl doesn't support this yet. -"TODO: use custome file -"set newtab file:///home/soispha/new.html - -" Set a nice colorscheme -colorscheme midnight - -" Delete temp files after use -alias editor_rm composite editor | jsb -p tri.native.run(`rm -f '${JS_ARG[0]}'`) -bind --mode=insert <C-i> editor_rm -bind --mode=input <C-i> editor_rm - -" Use vim in tmux for editor. -set editorcmd alacritty -e nvim - -" Ctrl-F should use the browser's native 'find' functionality. -unbind <C-f> - -" But also support Tridactyl search too. -bind / fillcmdline find -bind ? fillcmdline find -? -bind l findnext 1 -bind L findnext -1 -" Remove search highlighting. -bind ,<Space> nohlsearch -" Use sensitive case. Smart case would be nice here, but it doesn't work. -set findcase smartcase - -" Smooth scrolling, yes please. This is still a bit janky in Tridactyl. -set smoothscroll true - -" The default jump of 10 is a bit much. -bind t scrollline 5 -bind n scrollline -5 - -" K and J should move between tabs. x should close them. -bind T tabprev -bind N tabnext -bind x tabclose diff --git a/modules/home.legacy/conf/tridactyl/default.nix b/modules/home.legacy/conf/tridactyl/default.nix deleted file mode 100644 index de92c665..00000000 --- a/modules/home.legacy/conf/tridactyl/default.nix +++ /dev/null @@ -1,12 +0,0 @@ -# nixos-config - My current NixOS configuration -# -# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of my nixos-config. -# -# You should have received a copy of the License along with this program. -# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{...}: { - xdg.configFile."tridactyl/tridactylrc".source = ./config.vim; -} diff --git a/modules/home.legacy/default.nix b/modules/home.legacy/default.nix index fd543661..8fb209f3 100644 --- a/modules/home.legacy/default.nix +++ b/modules/home.legacy/default.nix @@ -40,7 +40,7 @@ in { enable = true; createDirectories = true; desktop = null; - documents = "${homeDirectory}/school/general"; + documents = "${homeDirectory}/documents/general"; download = "/tmp/download"; music = "${homeDirectory}/media/music"; pictures = "${homeDirectory}/media/pictures"; diff --git a/pkgs/by-name/fu/fupdate-flake/fupdate-flake.sh b/pkgs/by-name/fu/fupdate-flake/fupdate-flake.sh index 5b8d9469..00c1e443 100755 --- a/pkgs/by-name/fu/fupdate-flake/fupdate-flake.sh +++ b/pkgs/by-name/fu/fupdate-flake/fupdate-flake.sh @@ -169,7 +169,7 @@ update() { "$update_script" "$@" if [ -f "flake.lock" ] && grep '[^0-9]_[0-9]' flake.lock --quiet; then - batgrep '[^0-9]_[0-9]' flake.lock + grep '[^0-9]_[0-9]' flake.lock die "Your flake.nix contains duplicate inputs!" fi } diff --git a/pkgs/by-name/fu/fupdate-flake/package.nix b/pkgs/by-name/fu/fupdate-flake/package.nix index c8646f26..4e21cd23 100644 --- a/pkgs/by-name/fu/fupdate-flake/package.nix +++ b/pkgs/by-name/fu/fupdate-flake/package.nix @@ -13,9 +13,6 @@ coreutils, fd, gnugrep, - bat-extras, # For `batgrep` - bat, # used by batgrep - gnused, # required by batgrep git, }: writeShellApplication { @@ -29,9 +26,6 @@ writeShellApplication { coreutils fd gnugrep - bat-extras.batgrep - bat # Used by `batgrep` - gnused # Required by `batgrep` git ]; } diff --git a/pkgs/by-name/fu/fupdate-sys/fupdate-sys.sh b/pkgs/by-name/fu/fupdate-sys/fupdate-sys.sh index 4ec3e9e8..57ced766 100755 --- a/pkgs/by-name/fu/fupdate-sys/fupdate-sys.sh +++ b/pkgs/by-name/fu/fupdate-sys/fupdate-sys.sh @@ -151,7 +151,7 @@ msg2 "Updating git repository..." git pull --rebase # We use a tempfile, to make this truly async. -default_branch=$(mktemp) +default_branch=$(mktemp -t fupdate_flake_XXXX) cleanup() { rm "$default_branch" } diff --git a/pkgs/by-name/fu/fupdate/Cargo.lock b/pkgs/by-name/fu/fupdate/Cargo.lock index d2d07c28..9e72636a 100644 --- a/pkgs/by-name/fu/fupdate/Cargo.lock +++ b/pkgs/by-name/fu/fupdate/Cargo.lock @@ -13,9 +13,9 @@ version = 4 [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -43,35 +43,35 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "clap" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.54" +version = "4.5.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" +checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" dependencies = [ "clap", "clap_lex", @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -142,39 +142,39 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "is_executable" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" +checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" dependencies = [ - "winapi", + "windows-sys 0.60.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -193,9 +193,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -204,9 +204,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -215,42 +215,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -263,48 +257,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" diff --git a/pkgs/by-name/fu/fupdate/Cargo.toml b/pkgs/by-name/fu/fupdate/Cargo.toml index 3acd3bca..b62cee51 100644 --- a/pkgs/by-name/fu/fupdate/Cargo.toml +++ b/pkgs/by-name/fu/fupdate/Cargo.toml @@ -16,9 +16,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.98" -clap = { version = "4.5.40", features = ["derive"] } -clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] } +anyhow = "1.0.100" +clap = { version = "4.5.54", features = ["derive"] } +clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] } [profile.release] lto = true diff --git a/pkgs/by-name/fu/fupdate/flake.lock b/pkgs/by-name/fu/fupdate/flake.lock index a267d6fb..1e997998 100644 --- a/pkgs/by-name/fu/fupdate/flake.lock +++ b/pkgs/by-name/fu/fupdate/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1750731501, - "narHash": "sha256-Ah4qq+SbwMaGkuXCibyg+Fwn00el4KmI3XFX6htfDuk=", + "lastModified": 1768661221, + "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "69dfebb3d175bde602f612915c5576a41b18486b", + "rev": "3327b113f2ef698d380df83fbccefad7e83d7769", "type": "github" }, "original": { diff --git a/pkgs/by-name/fu/fupdate/flake.nix b/pkgs/by-name/fu/fupdate/flake.nix index 0ebceece..f06e27ec 100644 --- a/pkgs/by-name/fu/fupdate/flake.nix +++ b/pkgs/by-name/fu/fupdate/flake.nix @@ -19,13 +19,13 @@ pkgs = nixpkgs.legacyPackages."${system}"; in { devShells."${system}".default = pkgs.mkShell { - packages = with pkgs; [ - cargo - clippy - rustc - rustfmt + packages = [ + pkgs.cargo + pkgs.clippy + pkgs.rustc + pkgs.rustfmt - cargo-edit + pkgs.cargo-edit ]; }; }; diff --git a/pkgs/by-name/gi/git-edit-index/git-edit-index.sh b/pkgs/by-name/gi/git-edit-index/git-edit-index.sh index 46fbc9c5..a9434381 100755 --- a/pkgs/by-name/gi/git-edit-index/git-edit-index.sh +++ b/pkgs/by-name/gi/git-edit-index/git-edit-index.sh @@ -56,7 +56,7 @@ materialize_file() { } edit() { - files_to_add="$(mktemp)" + files_to_add="$(mktemp -t git_edit_index_XXXXX)" cleanup() { rm "$files_to_add" } diff --git a/pkgs/by-name/i3/i3bar-river-patched/0002-feat-crate-bar-Put-the-leftmost-block-in-the-middle-.patch b/pkgs/by-name/i3/i3bar-river-patched/0001-feat-crate-bar-Put-the-leftmost-block-in-the-middle-.patch index 6f4bd528..7bfdd7bc 100644 --- a/pkgs/by-name/i3/i3bar-river-patched/0002-feat-crate-bar-Put-the-leftmost-block-in-the-middle-.patch +++ b/pkgs/by-name/i3/i3bar-river-patched/0001-feat-crate-bar-Put-the-leftmost-block-in-the-middle-.patch @@ -1,4 +1,4 @@ -From b8568a2b626bd4d5f50ee24c304d19177bda5d4b Mon Sep 17 00:00:00 2001 +From 8ae692a461fad2f23231d50b78bb706408facfe6 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz <benedikt.peetz@b-peetz.de> Date: Tue, 20 May 2025 19:58:57 +0200 Subject: [PATCH] feat(crate::bar): Put the leftmost block in the middle of the @@ -7,21 +7,21 @@ Subject: [PATCH] feat(crate::bar): Put the leftmost block in the middle of the This is a workaround for the limitation in the i3 blocks protocol, as this does not allow for centred blocks. --- - src/bar.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++---------- - 1 file changed, 53 insertions(+), 11 deletions(-) + src/bar.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/src/bar.rs b/src/bar.rs -index fb88150..e66c2cf 100644 +index 96533e3..76f8025 100644 --- a/src/bar.rs +++ b/src/bar.rs -@@ -344,16 +344,56 @@ impl Bar { +@@ -338,16 +338,55 @@ impl Bar { } // Display the blocks - render_blocks( - &cairo_ctx, -- ss, -- &self.output, +- &ss.config, +- palette, - ss.blocks_cache.get_computed(), - &mut self.blocks_btns, - offset_left, @@ -36,8 +36,8 @@ index fb88150..e66c2cf 100644 + + let other_start = render_blocks( + &cairo_ctx, -+ ss, -+ &self.output, ++ &ss.config, ++ palette, + blocks, + &mut self.blocks_btns, + offset_left, @@ -49,7 +49,9 @@ index fb88150..e66c2cf 100644 + // left, if the others are spanning over the middle. + let mut start = (width_f / 2.0) - (first_block.full.width / 2.0); + if start + first_block.full.width > other_start { -+ start = other_start - first_block.full.width - first_block.block.separator_block_width as f64; ++ start = other_start ++ - first_block.full.width ++ - first_block.block.separator_block_width as f64; + } + + first_block.full.render( @@ -57,10 +59,7 @@ index fb88150..e66c2cf 100644 + RenderOptions { + x_offset: start, + bar_height: height_f, -+ fg_color: first_block -+ .block -+ .color -+ .unwrap_or(sfo!(ss, &self.output, color)), ++ fg_color: first_block.block.color.unwrap_or(palette.color), + bg_color: first_block.block.background, + r_left: ss.config.blocks_r, + r_right: ss.config.blocks_r, @@ -81,7 +80,7 @@ index fb88150..e66c2cf 100644 self.viewport .set_destination(conn, self.width as i32, self.height as i32); -@@ -428,7 +468,7 @@ fn render_blocks( +@@ -422,7 +461,7 @@ fn render_blocks( offset_left: f64, full_width: f64, full_height: f64, @@ -90,7 +89,7 @@ index fb88150..e66c2cf 100644 context.rectangle(offset_left, 0.0, full_width - offset_left, full_height); context.clip(); -@@ -513,6 +553,7 @@ fn render_blocks( +@@ -507,6 +546,7 @@ fn render_blocks( } // Render blocks @@ -98,7 +97,7 @@ index fb88150..e66c2cf 100644 buttons.clear(); for series in blocks_computed { let s_len = series.blocks.len(); -@@ -560,6 +601,7 @@ fn render_blocks( +@@ -550,6 +590,7 @@ fn render_blocks( } context.reset_clip(); diff --git a/pkgs/by-name/i3/i3bar-river-patched/0001-revert-use-std-io-pipe.patch b/pkgs/by-name/i3/i3bar-river-patched/0001-revert-use-std-io-pipe.patch deleted file mode 100644 index e1564753..00000000 --- a/pkgs/by-name/i3/i3bar-river-patched/0001-revert-use-std-io-pipe.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 017ed59239e48ca689af36f1ddaec877a3c86175 Mon Sep 17 00:00:00 2001 -From: Benedikt Peetz <benedikt.peetz@b-peetz.de> -Date: Tue, 20 May 2025 17:10:47 +0200 -Subject: [PATCH] Revert "use std::io::pipe" - -This reverts commit c9cee90d765198cf72c5a5155ee0d8ab566d98a9 as this -commit requires rustc 1.87.0 ---- - src/main.rs | 24 +++++++++++++++++++----- - 1 file changed, 19 insertions(+), 5 deletions(-) - -diff --git a/src/main.rs b/src/main.rs -index 4a86593..2e7aeb2 100644 ---- a/src/main.rs -+++ b/src/main.rs -@@ -18,8 +18,8 @@ mod text; - mod utils; - mod wm_info_provider; - --use std::io::{self, ErrorKind, Read}; --use std::os::fd::AsRawFd; -+use std::io::{self, ErrorKind}; -+use std::os::fd::{AsRawFd, RawFd}; - use std::path::PathBuf; - - use clap::Parser; -@@ -40,7 +40,7 @@ struct Cli { - fn main() -> anyhow::Result<()> { - let args = Cli::parse(); - -- let (mut sig_read, sig_write) = io::pipe()?; -+ let [sig_read, sig_write] = pipe(libc::O_NONBLOCK | libc::O_CLOEXEC)?; - signal_hook::low_level::pipe::register(SIGUSR1, sig_write)?; - - let mut conn = Connection::connect()?; -@@ -55,8 +55,12 @@ fn main() -> anyhow::Result<()> { - Ok(event_loop::Action::Keep) - }); - -- el.register_with_fd(sig_read.as_raw_fd(), move |ctx| { -- sig_read.read_exact(&mut [0u8]).unwrap(); -+ el.register_with_fd(sig_read, move |ctx| { -+ let mut buf = [0u8]; -+ assert_eq!( -+ unsafe { libc::read(sig_read, buf.as_mut_ptr().cast(), 1) }, -+ 1 -+ ); - ctx.state.toggle_visibility(ctx.conn); - Ok(event_loop::Action::Keep) - }); -@@ -104,3 +108,13 @@ fn main() -> anyhow::Result<()> { - el.run(&mut conn, &mut state)?; - unreachable!(); - } -+ -+// TODO: remove once Rust 1.87.0 is stable -+fn pipe(flags: libc::c_int) -> io::Result<[RawFd; 2]> { -+ let mut fds = [0; 2]; -+ if unsafe { libc::pipe2(fds.as_mut_ptr(), flags) } == -1 { -+ Err(io::Error::last_os_error()) -+ } else { -+ Ok(fds) -+ } -+} --- -2.49.0 - diff --git a/pkgs/by-name/i3/i3bar-river-patched/package.nix b/pkgs/by-name/i3/i3bar-river-patched/package.nix index 88af5d8a..26f11ab3 100644 --- a/pkgs/by-name/i3/i3bar-river-patched/package.nix +++ b/pkgs/by-name/i3/i3bar-river-patched/package.nix @@ -13,35 +13,23 @@ rustPlatform, pkg-config, pango, - fetchpatch2, }: rustPlatform.buildRustPackage { pname = "i3bar-river-patched"; version = "1.1.0-unstable-2025-05-20"; src = fetchFromGitHub { - owner = "MaxVerevkin"; + owner = "bpeetz"; repo = "i3bar-river"; - rev = "73446cac559b10adf4beb5567a816d1be5273457"; - hash = "sha256-NxlFKTnd2erHtSG56aWlZEkWVzBqe2hqQuVAWDdBq2c="; + rev = "d460a9a283426e9474a0034a146d09816e92f571"; + hash = "sha256-E04b2FzEhOX5NyE/VpEGdg27Sg+1+lSSRZbGyX6PXrk="; }; - useFetchCargoVendor = true; - cargoHash = "sha256-8sub8cXC/1iDY6v/9opO4FiLAo9CFrGJSDPNQydGvhQ="; + cargoHash = "sha256-jIB4XH67FmtPxAatHkuW8v5mNgr/KsyriaBNZ5t2dLo="; cargoPatches = [ - # Add a separate theme for unfocused outputs. - (fetchpatch2 { - name = "Add support for special theme for unfocused outputs"; - url = "https://patch-diff.githubusercontent.com/raw/MaxVerevkin/i3bar-river/pull/44.patch"; - hash = "sha256-yH3K52kAXGW19maP77gOTHSauqQX7Px8qCZDua6wo4w="; - }) - - # TODO(@bpeetz): Remove this patch once the rustc update hits unstable. <2025-05-20> - ./0001-revert-use-std-io-pipe.patch - - # TODO(@bpeetz): Open an issues, whether something like that could be upstreamed. <2025-05-20> - ./0002-feat-crate-bar-Put-the-leftmost-block-in-the-middle-.patch + # TODO(@bpeetz): Open an issues, whether something like that could be up-streamed. <2025-05-20> + ./0001-feat-crate-bar-Put-the-leftmost-block-in-the-middle-.patch ]; # Remove the WMs that I don't use. diff --git a/pkgs/by-name/i3/i3status-rust-patched/package.nix b/pkgs/by-name/i3/i3status-rust-patched/package.nix index 9f172d49..a103e275 100644 --- a/pkgs/by-name/i3/i3status-rust-patched/package.nix +++ b/pkgs/by-name/i3/i3status-rust-patched/package.nix @@ -9,7 +9,6 @@ # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. { i3status-rust, - fetchpatch2, }: i3status-rust.overrideAttrs (final: prev: { pname = "${prev.pname}-patched"; @@ -18,17 +17,6 @@ i3status-rust.overrideAttrs (final: prev: { (prev.patches or []) ++ [ # Btrfs support for disk_space block. - (fetchpatch2 { - name = "disk_space: Support btrfs backend"; - url = "https://patch-diff.githubusercontent.com/raw/greshake/i3status-rust/pull/2159.patch"; - hash = "sha256-S2/biX6FTLJNfI9QVgwr+V8IGMRnSFIZnTrhc+1LvqQ="; - }) - - # Correctly calculate the used memory. - (fetchpatch2 { - name = "memory: Avoid estimating available memory, use kernel estimate instead"; - url = "https://patch-diff.githubusercontent.com/raw/greshake/i3status-rust/pull/2160.patch"; - hash = "sha256-1wB2KpXhC/UIxAgRioOYj/bnrzRSuaHAdbeoZ2O5E/Y="; - }) + ./patches/0001-disk_space-Support-btrfs-backend.patch ]; }) diff --git a/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch new file mode 100644 index 00000000..8ef0af2e --- /dev/null +++ b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch @@ -0,0 +1,190 @@ +From 78d2936b67064e3b5e700a2859d00ea3dd6eda4c Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Sun, 18 May 2025 20:22:04 +0200 +Subject: [PATCH 1/3] disk_space: Support btrfs backend + +Btrfs is too smart for the statvfs based backend (i.e., only counting +blocks leads to wrong numbers). + +For example, a btrfs disk with a lot of de-duplicated blocks (via the copy +on write mechanism) might have a drastically over-reported disk usage. + +The btrfs backend is currently implemented by parsing the output of the +`btrfs filesystem usage --raw` command. This is suboptimal, as this now +relies on the command output not changing. + +Vendoring the algorithm used internally by the `btrfs` command does not +seem to be a reasonable alternative, considering that the code[1] is +rather complex, low level and would require semi-constant maintenance. +Additionally, the c code would need bindings to be usable from rust. + +I assume, that the `btrfs` command output will stay rather similar in +the future, as a lot of tools rely on directly parsing it (see the +various scripts in the issue, this commit fixes). + +[1]: https://github.com/kdave/btrfs-progs/blob/eeab081e9d9fbdf4583122ed1caedf541383cf2d/cmds/filesystem-usage.c#L442 + +Fixes: #1654 +--- + src/blocks/disk_space.rs | 112 +++++++++++++++++++++++++++++++++++---- + 1 file changed, 101 insertions(+), 11 deletions(-) + +diff --git a/src/blocks/disk_space.rs b/src/blocks/disk_space.rs +index 79bfebd27..da0d3f518 100644 +--- a/src/blocks/disk_space.rs ++++ b/src/blocks/disk_space.rs +@@ -12,6 +12,7 @@ + //! `alert` | A value which will trigger critical block state | `10.0` + //! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"` + //! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None` ++//! `backend` | The backend to use when querying disk usage. Possible values are `"vfs"` (like `du(1)`) and `"btrfs"` | `"vfs"` + //! + //! Placeholder | Value | Type | Unit + //! -------------|--------------------------------------------------------------------|--------|------- +@@ -63,9 +64,12 @@ + + // make_log_macro!(debug, "disk_space"); + ++use std::cell::OnceCell; ++ + use super::prelude::*; + use crate::formatting::prefix::Prefix; + use nix::sys::statvfs::statvfs; ++use tokio::process::Command; + + #[derive(Copy, Clone, Debug, Deserialize, SmartDefault)] + #[serde(rename_all = "lowercase")] +@@ -76,11 +80,20 @@ pub enum InfoType { + Used, + } + ++#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)] ++#[serde(rename_all = "lowercase")] ++pub enum Backend { ++ #[default] ++ Vfs, ++ Btrfs, ++} ++ + #[derive(Deserialize, Debug, SmartDefault)] + #[serde(deny_unknown_fields, default)] + pub struct Config { + #[default("/".into())] + pub path: ShellString, ++ pub backend: Backend, + pub info_type: InfoType, + pub format: FormatConfig, + pub format_alt: Option<FormatConfig>, +@@ -128,17 +141,9 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + loop { + let mut widget = Widget::new().with_format(format.clone()); + +- let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?; +- +- // Casting to be compatible with 32-bit systems +- #[allow(clippy::unnecessary_cast)] +- let (total, used, available, free) = { +- let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64); +- let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64)) +- * (statvfs.fragment_size() as u64); +- let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64); +- let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64); +- (total, used, available, free) ++ let (total, used, available, free) = match config.backend { ++ Backend::Vfs => get_vfs(&*path)?, ++ Backend::Btrfs => get_btrfs(&path).await?, + }; + + let result = match config.info_type { +@@ -205,3 +210,88 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + } + } + } ++ ++fn get_vfs<P>(path: &P) -> Result<(u64, u64, u64, u64)> ++where ++ P: ?Sized + nix::NixPath, ++{ ++ let statvfs = statvfs(path).error("failed to retrieve statvfs")?; ++ ++ // Casting to be compatible with 32-bit systems ++ #[allow(clippy::unnecessary_cast)] ++ { ++ let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64); ++ let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64)) ++ * (statvfs.fragment_size() as u64); ++ let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64); ++ let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64); ++ ++ Ok((total, used, available, free)) ++ } ++} ++ ++async fn get_btrfs(path: &str) -> Result<(u64, u64, u64, u64)> { ++ const OUTPUT_CHANGED: &str = "Btrfs filesystem usage output format changed"; ++ ++ fn remove_estimate_min(estimate_str: &str) -> Result<&str> { ++ estimate_str.trim_matches('\t') ++ .split_once("\t") ++ .ok_or(Error::new(OUTPUT_CHANGED)) ++ .map(|v| v.0) ++ } ++ ++ macro_rules! get { ++ ($source:expr, $name:expr, $variable:ident) => { ++ get!(@pre_op (|a| {Ok::<_, Error>(a)}), $source, $name, $variable) ++ }; ++ (@pre_op $function:expr, $source:expr, $name:expr, $variable:ident) => { ++ if $source.starts_with(concat!($name, ":")) { ++ let (found_name, variable_str) = ++ $source.split_once(":").ok_or(Error::new(OUTPUT_CHANGED))?; ++ ++ let variable_str = $function(variable_str)?; ++ ++ debug_assert_eq!(found_name, $name); ++ $variable ++ .set(variable_str.trim().parse().error(OUTPUT_CHANGED)?) ++ .map_err(|_| Error::new(OUTPUT_CHANGED))?; ++ } ++ }; ++ } ++ ++ let filesystem_usage = Command::new("btrfs") ++ .args(["filesystem", "usage", "--raw", path]) ++ .output() ++ .await ++ .error("Failed to collect btrfs filesystem usage info")? ++ .stdout; ++ ++ { ++ let final_total = OnceCell::new(); ++ let final_used = OnceCell::new(); ++ let final_free = OnceCell::new(); ++ ++ let mut lines = filesystem_usage.lines(); ++ while let Some(line) = lines ++ .next_line() ++ .await ++ .error("Failed to read output of btrfs filesystem usage")? ++ { ++ let line = line.trim(); ++ ++ // See btrfs-filesystem(8) for an explanation for the rows. ++ get!(line, "Device size", final_total); ++ get!(line, "Used", final_used); ++ get!(@pre_op remove_estimate_min, line, "Free (estimated)", final_free); ++ } ++ ++ Ok(( ++ *final_total.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ *final_used.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ // HACK(@bpeetz): We also return the free disk space as the available one, because btrfs ++ // does not tell us which disk space is reserved for the fs. <2025-05-18> ++ *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ )) ++ } ++} +-- +2.49.0 + diff --git a/pkgs/by-name/lf/lf-make-map/Cargo.lock b/pkgs/by-name/lf/lf-make-map/Cargo.lock index 3cf20840..1e293fde 100644 --- a/pkgs/by-name/lf/lf-make-map/Cargo.lock +++ b/pkgs/by-name/lf/lf-make-map/Cargo.lock @@ -12,12 +12,6 @@ version = 4 [[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -28,9 +22,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -43,9 +37,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -58,18 +52,18 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -78,9 +72,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "autocfg" @@ -90,32 +84,32 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -125,9 +119,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -135,9 +129,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -147,9 +141,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -159,9 +153,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -176,6 +170,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -189,9 +189,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", @@ -224,15 +224,15 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "keymaps" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a522bbaa39bddd54945580e369ed37113ea96f4cb8f0322be0d5e04aa4d7293" +checksum = "ea59e8e461942cf1d6a7ad938848d6fd2e40eb43799c21192c09226ecc86710f" dependencies = [ "thiserror", ] @@ -261,15 +261,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "num-traits" @@ -288,33 +288,33 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -352,9 +352,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -372,18 +372,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -423,35 +423,22 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -459,40 +446,40 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -503,9 +490,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -514,9 +501,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -525,97 +512,33 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/pkgs/by-name/lf/lf-make-map/Cargo.toml b/pkgs/by-name/lf/lf-make-map/Cargo.toml index 31a08dde..15d48115 100644 --- a/pkgs/by-name/lf/lf-make-map/Cargo.toml +++ b/pkgs/by-name/lf/lf-make-map/Cargo.toml @@ -17,9 +17,9 @@ edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.98" -clap = { version = "4.5.40", features = ["derive", "env"] } -keymaps = "1.1.1" -log = "0.4.27" +anyhow = "1.0.100" +clap = { version = "4.5.54", features = ["derive", "env"] } +keymaps = "1.2.0" +log = "0.4.29" stderrlog = "0.6.0" walkdir = "2.5.0" diff --git a/pkgs/by-name/lf/lf-make-map/flake.lock b/pkgs/by-name/lf/lf-make-map/flake.lock index a267d6fb..1e997998 100644 --- a/pkgs/by-name/lf/lf-make-map/flake.lock +++ b/pkgs/by-name/lf/lf-make-map/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1750731501, - "narHash": "sha256-Ah4qq+SbwMaGkuXCibyg+Fwn00el4KmI3XFX6htfDuk=", + "lastModified": 1768661221, + "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "69dfebb3d175bde602f612915c5576a41b18486b", + "rev": "3327b113f2ef698d380df83fbccefad7e83d7769", "type": "github" }, "original": { diff --git a/pkgs/by-name/lf/lf-make-map/flake.nix b/pkgs/by-name/lf/lf-make-map/flake.nix index 20925aca..8ce8ff0f 100644 --- a/pkgs/by-name/lf/lf-make-map/flake.nix +++ b/pkgs/by-name/lf/lf-make-map/flake.nix @@ -19,13 +19,13 @@ pkgs = nixpkgs.legacyPackages."${system}"; in { devShells."${system}".default = pkgs.mkShell { - packages = with pkgs; [ - cargo - clippy - rustc - rustfmt + packages = [ + pkgs.cargo + pkgs.clippy + pkgs.rustc + pkgs.rustfmt - cargo-edit + pkgs.cargo-edit ]; }; }; diff --git a/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs b/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs index 10b612fd..5fd046fb 100644 --- a/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs +++ b/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs @@ -157,7 +157,7 @@ This should not happen, please report the bug!", }; assert_eq!( - value.len(), + value.chars().count(), number_of_chars, "'{}' does not have expected length of: {}", value, diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/child_insert/test.sh b/pkgs/by-name/lf/lf-make-map/tests/cases/child_insert/test.sh index 90ebe1ce..af6a1391 100755 --- a/pkgs/by-name/lf/lf-make-map/tests/cases/child_insert/test.sh +++ b/pkgs/by-name/lf/lf-make-map/tests/cases/child_insert/test.sh @@ -10,7 +10,7 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -test="$(mktemp --directory)" +test="$(mktemp --directory -t lf_make_map_test_XXXX)" cleanup() { rm --recursive "$test" diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh b/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh index 6e127d28..22f97009 100755 --- a/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh +++ b/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh @@ -12,7 +12,7 @@ # We need to hard code this, so that our output matches the golden sample. base="/tmp/tmp.DfcgjemfCG" -test="$(mktemp --directory)" +test="$(mktemp --directory -t lf_make_temp_test_XXXXX)" [ -d "$base" ] && { echo "$base already exists!" diff --git a/pkgs/by-name/mp/mpdpopm/.envrc b/pkgs/by-name/mp/mpdpopm/.envrc new file mode 100644 index 00000000..9f477e71 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/.envrc @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# Mpdpopm - A mpd rating tracker +# +# Copyright (C) 2026 Benedikt Peetz, Michael Herstine <sp1ff@pobox.com> <benedikt.peetz@b-peetz.de, sp1ff@pobox.com> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Mpdpopm. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use flake || use nix +watch_file flake.nix + +PATH_add ./scripts +PATH_add ./target/debug/ +PATH_add ./target/release/ + +if on_git_branch; then + echo && git status --short --branch +fi diff --git a/pkgs/by-name/mp/mpdpopm/.gitignore b/pkgs/by-name/mp/mpdpopm/.gitignore new file mode 100644 index 00000000..c80d7eef --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/.gitignore @@ -0,0 +1,16 @@ +# Mpdpopm - A mpd rating tracker +# +# Copyright (C) 2026 Benedikt Peetz, Michael Herstine <sp1ff@pobox.com> <benedikt.peetz@b-peetz.de, sp1ff@pobox.com> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Mpdpopm. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +# build +/target +/result + +# dev env +.direnv diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.lock b/pkgs/by-name/mp/mpdpopm/Cargo.lock new file mode 100644 index 00000000..8b61799a --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/Cargo.lock @@ -0,0 +1,1449 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "regex-automata", + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mpdpopm" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "boolinator", + "chrono", + "clap", + "errno", + "futures", + "lalrpop", + "lalrpop-util", + "lazy_static", + "os_str_bytes", + "pin-project", + "rand", + "regex", + "serde", + "serde_json", + "shlex", + "tokio", + "toml", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "os_str_bytes" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63eceb7b5d757011a87d08eb2123db15d87fb0c281f65d101ce30a1e96c3ad5c" +dependencies = [ + "memchr", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.toml b/pkgs/by-name/mp/mpdpopm/Cargo.toml new file mode 100644 index 00000000..71232236 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/Cargo.toml @@ -0,0 +1,46 @@ +# Mpdpopm - A mpd rating tracker +# +# Copyright (C) 2026 Benedikt Peetz, Michael Herstine <sp1ff@pobox.com> <benedikt.peetz@b-peetz.de, sp1ff@pobox.com> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Mpdpopm. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +[package] +name = "mpdpopm" +description = "Maintain ratings & playcounts for your mpd server" +version = "0.1.0" +edition = "2024" +license = "AGPL-3.0-or-later" +homepage = "" +repository = "https://git.vhack.eu/bpeetz/nixos-config" +authors = ["Benedikt Peetz", "Mechael Herstine"] +keywords = ["mpd", "music", "daemon"] +categories = ["multimedia", "network-programming", "database"] + +[build-dependencies] +lalrpop = { version = "0.22", features = ["lexer"] } + +[dependencies] +async-trait = "0.1" +boolinator = "2.4" +chrono = "0.4" +clap = {version = "4.5", features = ["derive"]} +errno = "0.3" +futures = "0.3" +lalrpop-util = { version = "0.22", features = ["lexer"] } +lazy_static = "1.5" +os_str_bytes = "7.1" +pin-project = "1.1" +regex = "1.12" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.149" +toml = "0.9" +tokio = { version = "1.49", features = ["io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] } +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.22", features = ["env-filter"]} +anyhow = "1.0.100" +shlex = "1.3.0" +rand = "0.9.2" diff --git a/pkgs/by-name/mp/mpdpopm/README.md b/pkgs/by-name/mp/mpdpopm/README.md new file mode 100644 index 00000000..3c2d961b --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/README.md @@ -0,0 +1,260 @@ + +# Table of Contents + +1. [Introduction](#orgb2618c9) +2. [What Can You Do With It?](#orgf1adf2c) +3. [Licsense](#org3f75b89) +4. [Prerequisites](#org67de102) +5. [Installing](#installing) + 1. [Use the pre-built binaries](#orgb2e3434) + 2. [Crates.io](#org971a8b3) + 3. [Use the Debian package](#org55e51f8) + 4. [Use the Arch package](#org49ada47) + 5. [Autotools source distributions](#org9c94559) + 6. [Building from source](#org64bc5dd) +6. [Getting Started](#getting_started) + 1. [Program Structure](#org4a22fae) + 2. [Getting Set-up](#orgfbd2d7d) + 1. [MPD](#orgb37b483) + 2. [mppopmd](#org38f4b69) + 3. [mppopm](#orgfa9dacf) +7. [Status & Roadmap](#orgd90c7da) + + + +<a id="orgb2618c9"></a> + +# Introduction + +[mpdpopm](https://github.com/sp1ff/mpdpopm) provides a companion daemon to [MPD](https://www.musicpd.org/) for maintaining play counts, ratings and last-played timestamps, along with an associated CLI for talking to the companion daemon. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust (which I prefer to Go), it will maintain this information in your sticker database. Along the lines of [mpdcron](https://alip.github.io/mpdcron), it will also allow you to keep that information up-to-date in your tags by invoking external (user-provided & -configured) commands. + +This README focuses on obtaining & installing [mpdpopm](https://github.com/sp1ff/mpdpopm); the user manual is distributed with the package in [Texinfo](https://www.gnu.org/software/texinfo/) format. The HTML version of the user manual is hosted on my personal [site](https://www.unwoundstack.com/doc/mpdpopm/curr). + + +<a id="orgf1adf2c"></a> + +# What Can You Do With It? + +Once you've [installed](#installing) & [started](#getting_started) [mpdpopm](https://github.com/sp1ff/mpdpopm), its daemon (`mppopmd`) will sit in the background noting the songs you play and updating play counts & last played timestamps in your [MPD](https://www.musicpd.org/) sticker database. If you'd like to rate a song, you can send `mppopmd` a message using your favorte MPD client, or with the `mppopm` CLI that comes along with this package; `mppopmd` will note the rating, as well. + +If you'd like to make use of this information in your song selection, you can ask `mppopmd` to queue-up songs on this basis by saying things like: + + mppopm findadd "(rating > 128)" + +to add all songs with a rating greater than 128 to the play queue, or + + mppopm findadd "(lastplayed <= \"2022-12-28\")" + +to add all songs that haven't been played in the last year. + + +<a id="org3f75b89"></a> + +# Licsense + +[mpdpopm](https://github.com/sp1ff/mpdpopm) is GPL v3 software. + + +<a id="org67de102"></a> + +# Prerequisites + +[Music Player Daemon](https://www.musicpd.org/): "Music Player Daemon (MPD) is a flexible, powerful, server-side application for playing music. Through plugins and libraries it can play a variety of sound files while being controlled by its network protocol." If you're reading this, I assume you're already running MPD, so this document won't have much to say on installing & configuring it other than that you **do** need to setup the sticker database by setting `sticker_file` in your configuration. + +If you choose to use the pre-built binaries or the Debian or Arch packages (available under [releases](https://github.com/sp1ff/mpdpopm/releases)), that's all you'll need– you can jump ahead to the section entitled [Installing](#getting_started), below. + +If you would prefer to download [mpdpopm](https://github.com/sp1ff/mpdpopm) from [crates.io](https://crates.io/crates/mpdpopm), you'll need need the [Rust](https://www.rust-lang.org/tools/install) toolchain ("Rust is a memory- & thread-safe language with no runtime or garbage collector"). Installing the toolchain is easy: + + curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh + +[mpdpopm](https://github.com/sp1ff/mpdpopm) is also available as an Autotools source distribution (also under [releases](https://github.com/sp1ff/mpdpopm/releases)), and of course you can just clone the repo & build the project from source. In either of those two cases you'll need the Gnu [Autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) installed in addition to Rust. In the former case, grab the tarball in the format of your choice & perform the usual "./configure && make && make install" incantation. In the latter, you'll need to invoke "./bootstrap" after you clone the repo. Again, if you're considering that route, I assume you're familiar with the Autotools & won't say much about them here. + + +<a id="installing"></a> + +# Installing + +As mentioned above, you can install [mpdpopm](https://github.com/sp1ff/mpdpopm) in a few different ways. In increasing order of complexity: + + +<a id="orgb2e3434"></a> + +## Use the pre-built binaries + +Thanks to a suggestion by [m040601](https://github.com/m040601), you can download pre-built binaries for each [release](https://github.com/sp1ff/mpdpopm/releases). At the time of this writing, only Linux & MacOS are supported, and only on x86<sub>64</sub> at that. If that works for you, you can do something like: + + cd /tmp + curl -L --output mpdpopm-0.3.5.tar.gz https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5-x86_64-unknown-linux.tar.gz + tar xf mpdpopm-0.3.5.tar.gz + tree mpdpopm-0.3.5-x86_64-unknown-linux/ + mpdpopm-0.3.5-x86_64-unknown-linux/ + ├── bin + │ ├── mppopm + │ └── mppopmd + └── doc + ├── AUTHORS + ├── ChangeLog + ├── COPYING + ├── NEWS + ├── README.org + ├── THANKS + ├── mppopmd.conf + ├── mppopmd.info + └── mppopmd.service + + 2 directories, 10 files + +Copy the binaries `mppopmd` (the daemon) and `mppopm` (the CLI) to a convenient place (e.g. `/usr/local/bin` or `$HOME/.local/bin`) and proceed to [Getting Started](#getting_started), below. + + +<a id="org971a8b3"></a> + +## Crates.io + +If you've got the Rust toolchain installed, just say `cargo install mpdpopm`. The binaries will now be in `$HOME/.cargo/bin`, and you can proceed to [Getting Started](#getting_started), below. + + +<a id="org55e51f8"></a> + +## Use the Debian package + +If you're running on a Debian-based Linux distribution, and you're on an x86<sub>64</sub> processor, I've begun providing a Debian binary package, courtesy of the very cool [cargo-deb](https://github.com/mmstick/cargo-deb) Cargo helper command. Just do: + + cd /tmp + curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm_0.3.5_amd64.deb + sudo dpkg -i mpdpopm_0.3.5_amd64.deb + +The binaries will be placed in `/usr/local/bin`, and you can proceed to [Getting Started](#getting_started), below. + + +<a id="org49ada47"></a> + +## Use the Arch package + +If you're running on an Arch-based Linux distribution, I maintain a few packages in the [AUR](https://aur.archlinux.org/): + +- [mpdpopm](https://aur.archlinux.org/packages/mpdpopm): which will grab the latest release & build it locally +- [mpdpopm-git](https://aur.archlinux.org/packages/mpdpopm-git): grab `HEAD` from `master` & build it locally +- [mpdpopm-bin](https://aur.archlinux.org/packages/mpdpopm-bin): grab the pre-built binaries from the latest release & install 'em + +You can clone the git repo for whichever package you'd like to use (remotes at link), do `makepkg` & then use pacman to install the package you just built, or use an AUR package manager (I use `yay`, e.g.) + + +<a id="org9c94559"></a> + +## Autotools source distributions + +If you've got the Rust toolchain as well as Autotools installed, you can build from source via Autotools: + + cd /tmp + curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5.tar.xz + tar xf mpdpopm-0.3.5.tar.xz + cd mpdpopm-0.3.5 + ./configure + make + make check + sudo make install + +All the usual `configure` options apply (`--prefix`, e.g.) In particular, you can say `--enable-debug` to produce debug builds. + + +<a id="org64bc5dd"></a> + +## Building from source + +Finally, and again if you have the build toolchain (Rust & Autotools) installed, you can build from source: + + git clone git@github.com:sp1ff/mpdpopm.git + cd mpdpopm + ./bootstrap + ./configure + make + make check + sudo make install + +Notice the call to `./bootstrap`, in this case. + + +<a id="getting_started"></a> + +# Getting Started + +This README provides a "quick-start" guide to getting mpdpopm up & running. For detailed user docs, refer to the [manual](https://www.unwoundstack.com/doc/mpdpopm/curr). + + +<a id="org4a22fae"></a> + +## Program Structure + +[mpdpopm](https://github.com/sp1ff/mpdpopm) provides two programs: + +1. `mppopmd` is the companion daemon process +2. `mppopm` is the associated command-line interface to the daemon + +`mppopmd` will monitor `mpd` for song playback & note when songs complete; this is how it knows to increment the playcount & update the last played timestamp for each song to which you listen. `mppopmd` records this information (i.e. play counts, last played and ratings) using `mpd` [stickers](https://www.musicpd.org/doc/html/protocol.html#stickers). A sticker is a little bit of textual information which clients can attach to songs in the form of a name-value pair. [mpdpopm](https://github.com/sp1ff/mpdpopm) defines a new sticker name for each of these items & udpates the values for each song when & as requested. + +Of course, other `mpd` clients will not, in general, be aware of `mppopmd` or the stickers it sets: you the user will have to bridge that gap. You could of course just fire-up `netcat` & start sending commands over the MPD protocol using `sendmessage`, but that's not particularly convenient– that's where `mppopm` comes in. `mppopm` is the client interface; one can through it instruct `mppopmd` to set ratings, get & set the various stickers mpdpopm knows about, and even search for songs in terms of mpdpopm attributes & add them to the play queue. + + +<a id="orgfbd2d7d"></a> + +## Getting Set-up + + +<a id="orgb37b483"></a> + +### MPD + +If you're reading this, I assume you already have MPD up & running, so this section will be brief. One note, prompted by user [m040601](https://github.com/m040601), however: as mentioned above, [mpdpopm](https://github.com/sp1ff/mpdpopm) leverages the MPD sticker database. I was chagrined to find that if you do not configure MPD to maintain a sticker database, all sticker commands will simply be disabled. Therefore, before setting up [mpdpopm](https://github.com/sp1ff/mpdpopm), find your `mpd` configuration file and check to be sure you have a `sticker_file` entry; something like this: + + sticker_file "/home/sp1ff/lib/mpd/sticker.sql" + +Check also that the you have write access to the named file & its parent directory. + + +<a id="org38f4b69"></a> + +### mppopmd + +The daemon depends on a configuration file that you'll need to provide. Most `mppopmd` configuration items have sensible defaults, but there are a few that will need to be customized to your MPD setup. A sample configuration file is provided with all distributions; see also the user [manual](https://www.unwoundstack.com/doc/mpdpopm/curr#mppopmd-Configuration) for detailed documentation. + +You'll likely want to run the program in the foreground initially for ease of trouble-shooting, but after that you'll probably want to run it as a daemon. Again see the [manual](https://www.unwoundstack.com/doc/mpdopmd/curr#mppopmd-as-a-Daemon) for detailed instructions. + +Once you've got the daemon running to your satisfaction, if you're on a systemd-based Linux distribution, have a look at the sample systemd unit file thanks to [tanshoku](https://github.com/tanshoku). + +[tanshoku](https://github.com/tanshoku) was kind enough to contribute a systemd unit for this purpose. At present, the build does not install it, but provides it as an example and leaves it to the user to install should they desire (and after they have edited it to suit their configuration). You can find it in `${prefix}/share/mpdpopm/examples` for the Autotools distribution, `/usr/local/share/mpdpopm/examples` for the Debian package, and in the `doc` folder for the pre-built binaries. + + +<a id="orgfa9dacf"></a> + +### mppopm + +At this point, [mpdpopm](https://github.com/sp1ff/mpdpopm) will happily monitor your playback history & keep play counts & last played timestamps for you. If you would like to rate tracks, however, you will need to somehow induce your favorite mpd client to send a "rating" message to the [mpdpopm](https://github.com/sp1ff/mpdpopm) commands channel ("unwoundstack.com:commands" by default). Since this is unlikely to be convenient, I wrote an mpd client for the purpose: a little CLI called `mppopm`. You can simply execute + + mppopm set-rating '*****' + +to set the current track's rating to five "stars" (say `mppopm --help` for an explanation of the rating system; in brief– it's Winamp's). NB. the set rating command by default produces no output; if you want confirmation that something's happening, use the `-v` flag. + +The CLI offers "get" & "set" commands for play counts, last played timestamps & the rating. It also provides commands for searching your songs on the basis of play count, rating & last played times in addition to the usual artist, title &c. Say `mppopm --help` for a full list of options, including how to tell it where the mpd server can be found on your network. + + +<a id="orgd90c7da"></a> + +# Status & Roadmap + +I am currently using [mpdpopm](https://github.com/sp1ff/mpdpopm) day in & day out with my music collection, but it's early days; I have chosen the version number (0.n) in the hopes of indicating that. Right now, mpdpopm is the bare-bones of an app: it's plumbing, not the sink. + +Heretofore, you could use the `mppopm` CLI to, say, rate the current song, but in order to actually <span class="underline">do</span> anything with that rating in the future, you'd have had to write some kind of mpd client for yourself. With the 0.2 release, I've added support for extended MPD filter syntax that allows queries that include the stickers that [mpdpopm](https://github.com/sp1ff/mpdpopm) manages– so you can now, for instance, say: + + mppopm findadd "(artist =~ \"foo\") and (rating > 175)" + +MPD will handle the "artist =~" clause & [mpdpopm](https://github.com/sp1ff/mpdpopm) the "rating >" clause, as well as combining the results. + +This will hopefully be a start to making [mpdpopm](https://github.com/sp1ff/mpdpopm) into a more of a user-facing application than a developer-facing utlity. + +Windows support may be some time coming; the daemon depends on Unix signal handling, the MPD Unix socket, and the Unix daemon logic, especially `fork` & `exec`… if you'd like to run it on Windows, let me know– if there's enough interest, and I can get some kind of Windows VM setup, I'll look at a port. + +Longer-term, I see [mpdpopm](https://github.com/sp1ff/mpdpopm) as a "dual" to mpd– mpd commits to never altering your files. mpdpopm will take on that task in terms of tags, at least. To address the "plumbing, not the sink" problem, I'd like to author a client that will handle player control (of course), but also visualization & tag editing– a complete music library solution. + +Suggestions, bug reports & PRs welcome! + diff --git a/pkgs/by-name/mp/mpdpopm/README.org b/pkgs/by-name/mp/mpdpopm/README.org new file mode 100644 index 00000000..ebc91262 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/README.org @@ -0,0 +1,214 @@ +#+TITLE: README +#+AUTHOR: Michael Herstine +#+DESCRIPTION: mpdpopm +#+EMAIL: sp1ff@pobox.com +#+DATE: <2025-10-19 Sun 19:17> +#+AUTODATE: t + +* Introduction + +[[https://github.com/sp1ff/mpdpopm][mpdpopm]] provides a companion daemon to [[https://www.musicpd.org/][MPD]] for maintaining play counts, ratings and last-played timestamps, along with an associated CLI for talking to the companion daemon. Similar to [[https://github.com/vincent-petithory/mpdfav][mpdfav]], but written in Rust (which I prefer to Go), it will maintain this information in your sticker database. Along the lines of [[https://alip.github.io/mpdcron][mpdcron]], it will also allow you to keep that information up-to-date in your tags by invoking external (user-provided & -configured) commands. + +This README focuses on obtaining & installing [[https://github.com/sp1ff/mpdpopm][mpdpopm]]; the user manual is distributed with the package in [[https://www.gnu.org/software/texinfo/][Texinfo]] format. The HTML version of the user manual is hosted on my personal [[https://www.unwoundstack.com/doc/mpdpopm/curr][site]]. + +* What Can You Do With It? + +Once you've [[#installing][installed]] & [[#getting_started][started]] [[https://github.com/sp1ff/mpdpopm][mpdpopm]], its daemon (=mppopmd=) will sit in the background noting the songs you play and updating play counts & last played timestamps in your [[https://www.musicpd.org/][MPD]] sticker database. If you'd like to rate a song, you can send =mppopmd= a message using your favorte MPD client, or with the =mppopm= CLI that comes along with this package; =mppopmd= will note the rating, as well. + +If you'd like to make use of this information in your song selection, you can ask =mppopmd= to queue-up songs on this basis by saying things like: + +#+BEGIN_SRC bash +mppopm findadd "(rating > 128)" +#+END_SRC + +to add all songs with a rating greater than 128 to the play queue, or + +#+BEGIN_SRC bash +mppopm findadd "(lastplayed <= \"2022-12-28\")" +#+END_SRC + +to add all songs that haven't been played in the last year. + +* Licsense + +[[https://github.com/sp1ff/mpdpopm][mpdpopm]] is GPL v3 software. + +* Prerequisites + +[[https://www.musicpd.org/][Music Player Daemon]]: "Music Player Daemon (MPD) is a flexible, powerful, server-side application for playing music. Through plugins and libraries it can play a variety of sound files while being controlled by its network protocol." If you're reading this, I assume you're already running MPD, so this document won't have much to say on installing & configuring it other than that you *do* need to setup the sticker database by setting =sticker_file= in your configuration. + +If you choose to use the pre-built binaries or the Debian or Arch packages (available under [[https://github.com/sp1ff/mpdpopm/releases][releases]]), that's all you'll need-- you can jump ahead to the section entitled [[#getting_started][Installing]], below. + +If you would prefer to download [[https://github.com/sp1ff/mpdpopm][mpdpopm]] from [[https://crates.io/crates/mpdpopm][crates.io]], you'll need need the [[https://www.rust-lang.org/tools/install][Rust]] toolchain ("Rust is a memory- & thread-safe language with no runtime or garbage collector"). Installing the toolchain is easy: + +#+BEGIN_SRC bash +curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh +#+END_SRC + +[[https://github.com/sp1ff/mpdpopm][mpdpopm]] is also available as an Autotools source distribution (also under [[https://github.com/sp1ff/mpdpopm/releases][releases]]), and of course you can just clone the repo & build the project from source. In either of those two cases you'll need the Gnu [[https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html][Autotools]] installed in addition to Rust. In the former case, grab the tarball in the format of your choice & perform the usual "./configure && make && make install" incantation. In the latter, you'll need to invoke "./bootstrap" after you clone the repo. Again, if you're considering that route, I assume you're familiar with the Autotools & won't say much about them here. + +* Installing + :PROPERTIES: + :CUSTOM_ID: installing + :END: + +As mentioned above, you can install [[https://github.com/sp1ff/mpdpopm][mpdpopm]] in a few different ways. In increasing order of complexity: + +** Use the pre-built binaries + +Thanks to a suggestion by [[https://github.com/m040601][m040601]], you can download pre-built binaries for each [[https://github.com/sp1ff/mpdpopm/releases][release]]. At the time of this writing, only Linux & MacOS are supported, and only on x86_64 at that. If that works for you, you can do something like: + +#+BEGIN_SRC bash +cd /tmp +curl -L --output mpdpopm-0.3.5.tar.gz https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5-x86_64-unknown-linux.tar.gz +tar xf mpdpopm-0.3.5.tar.gz +tree mpdpopm-0.3.5-x86_64-unknown-linux/ +mpdpopm-0.3.5-x86_64-unknown-linux/ +├── bin +│ ├── mppopm +│ └── mppopmd +└── doc + ├── AUTHORS + ├── ChangeLog + ├── COPYING + ├── NEWS + ├── README.org + ├── THANKS + ├── mppopmd.conf + ├── mppopmd.info + └── mppopmd.service + +2 directories, 10 files +#+END_SRC + +Copy the binaries =mppopmd= (the daemon) and =mppopm= (the CLI) to a convenient place (e.g. =/usr/local/bin= or =$HOME/.local/bin=) and proceed to [[#getting_started][Getting Started]], below. + +** Crates.io + +If you've got the Rust toolchain installed, just say =cargo install mpdpopm=. The binaries will now be in =$HOME/.cargo/bin=, and you can proceed to [[#getting_started][Getting Started]], below. + +** Use the Debian package + +If you're running on a Debian-based Linux distribution, and you're on an x86_64 processor, I've begun providing a Debian binary package, courtesy of the very cool [[https://github.com/mmstick/cargo-deb][cargo-deb]] Cargo helper command. Just do: + +#+BEGIN_SRC bash +cd /tmp +curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm_0.3.5_amd64.deb +sudo dpkg -i mpdpopm_0.3.5_amd64.deb +#+END_SRC + +The binaries will be placed in =/usr/local/bin=, and you can proceed to [[#getting_started][Getting Started]], below. + +** Use the Arch package + +If you're running on an Arch-based Linux distribution, I maintain a few packages in the [[https://aur.archlinux.org/][AUR]]: + + - [[https://aur.archlinux.org/packages/mpdpopm][mpdpopm]]: which will grab the latest release & build it locally + - [[https://aur.archlinux.org/packages/mpdpopm-git][mpdpopm-git]]: grab =HEAD= from =master= & build it locally + - [[https://aur.archlinux.org/packages/mpdpopm-bin][mpdpopm-bin]]: grab the pre-built binaries from the latest release & install 'em + +You can clone the git repo for whichever package you'd like to use (remotes at link), do =makepkg= & then use pacman to install the package you just built, or use an AUR package manager (I use =yay=, e.g.) +** Autotools source distributions + +If you've got the Rust toolchain as well as Autotools installed, you can build from source via Autotools: + +#+BEGIN_SRC bash +cd /tmp +curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5.tar.xz +tar xf mpdpopm-0.3.5.tar.xz +cd mpdpopm-0.3.5 +./configure +make +make check +sudo make install +#+END_SRC + +All the usual =configure= options apply (=--prefix=, e.g.) In particular, you can say =--enable-debug= to produce debug builds. + +** Building from source + +Finally, and again if you have the build toolchain (Rust & Autotools) installed, you can build from source: + +#+BEGIN_SRC bash +git clone git@github.com:sp1ff/mpdpopm.git +cd mpdpopm +./bootstrap +./configure +make +make check +sudo make install +#+END_SRC + +Notice the call to =./bootstrap=, in this case. + +* Getting Started + :PROPERTIES: + :CUSTOM_ID: getting_started + :END: + +This README provides a "quick-start" guide to getting mpdpopm up & running. For detailed user docs, refer to the [[https://www.unwoundstack.com/doc/mpdpopm/curr][manual]]. + +** Program Structure + +[[https://github.com/sp1ff/mpdpopm][mpdpopm]] provides two programs: + + 1. =mppopmd= is the companion daemon process + 2. =mppopm= is the associated command-line interface to the daemon + +=mppopmd= will monitor =mpd= for song playback & note when songs complete; this is how it knows to increment the playcount & update the last played timestamp for each song to which you listen. =mppopmd= records this information (i.e. play counts, last played and ratings) using =mpd= [[https://www.musicpd.org/doc/html/protocol.html#stickers][stickers]]. A sticker is a little bit of textual information which clients can attach to songs in the form of a name-value pair. [[https://github.com/sp1ff/mpdpopm][mpdpopm]] defines a new sticker name for each of these items & udpates the values for each song when & as requested. + +Of course, other =mpd= clients will not, in general, be aware of =mppopmd= or the stickers it sets: you the user will have to bridge that gap. You could of course just fire-up =netcat= & start sending commands over the MPD protocol using =sendmessage=, but that's not particularly convenient-- that's where =mppopm= comes in. =mppopm= is the client interface; one can through it instruct =mppopmd= to set ratings, get & set the various stickers mpdpopm knows about, and even search for songs in terms of mpdpopm attributes & add them to the play queue. + +** Getting Set-up + +*** MPD + +If you're reading this, I assume you already have MPD up & running, so this section will be brief. One note, prompted by user [[https://github.com/m040601][m040601]], however: as mentioned above, [[https://github.com/sp1ff/mpdpopm][mpdpopm]] leverages the MPD sticker database. I was chagrined to find that if you do not configure MPD to maintain a sticker database, all sticker commands will simply be disabled. Therefore, before setting up [[https://github.com/sp1ff/mpdpopm][mpdpopm]], find your =mpd= configuration file and check to be sure you have a =sticker_file= entry; something like this: + +#+BEGIN_EXAMPLE + sticker_file "/home/sp1ff/lib/mpd/sticker.sql" +#+END_EXAMPLE + +Check also that the you have write access to the named file & its parent directory. + +*** mppopmd + +The daemon depends on a configuration file that you'll need to provide. Most =mppopmd= configuration items have sensible defaults, but there are a few that will need to be customized to your MPD setup. A sample configuration file is provided with all distributions; see also the user [[https://www.unwoundstack.com/doc/mpdpopm/curr#mppopmd-Configuration][manual]] for detailed documentation. + +You'll likely want to run the program in the foreground initially for ease of trouble-shooting, but after that you'll probably want to run it as a daemon. Again see the [[https://www.unwoundstack.com/doc/mpdopmd/curr#mppopmd-as-a-Daemon][manual]] for detailed instructions. + +Once you've got the daemon running to your satisfaction, if you're on a systemd-based Linux distribution, have a look at the sample systemd unit file thanks to [[https://github.com/tanshoku][tanshoku]]. + +[[https://github.com/tanshoku][tanshoku]] was kind enough to contribute a systemd unit for this purpose. At present, the build does not install it, but provides it as an example and leaves it to the user to install should they desire (and after they have edited it to suit their configuration). You can find it in =${prefix}/share/mpdpopm/examples= for the Autotools distribution, =/usr/local/share/mpdpopm/examples= for the Debian package, and in the =doc= folder for the pre-built binaries. + +*** mppopm + +At this point, [[https://github.com/sp1ff/mpdpopm][mpdpopm]] will happily monitor your playback history & keep play counts & last played timestamps for you. If you would like to rate tracks, however, you will need to somehow induce your favorite mpd client to send a "rating" message to the [[https://github.com/sp1ff/mpdpopm][mpdpopm]] commands channel ("unwoundstack.com:commands" by default). Since this is unlikely to be convenient, I wrote an mpd client for the purpose: a little CLI called =mppopm=. You can simply execute + +#+BEGIN_SRC bash +mppopm set-rating '*****' +#+END_SRC + +to set the current track's rating to five "stars" (say =mppopm --help= for an explanation of the rating system; in brief-- it's Winamp's). NB. the set rating command by default produces no output; if you want confirmation that something's happening, use the =-v= flag. + +The CLI offers "get" & "set" commands for play counts, last played timestamps & the rating. It also provides commands for searching your songs on the basis of play count, rating & last played times in addition to the usual artist, title &c. Say =mppopm --help= for a full list of options, including how to tell it where the mpd server can be found on your network. + +* Status & Roadmap + +I am currently using [[https://github.com/sp1ff/mpdpopm][mpdpopm]] day in & day out with my music collection, but it's early days; I have chosen the version number (0.n) in the hopes of indicating that. Right now, mpdpopm is the bare-bones of an app: it's plumbing, not the sink. + +Heretofore, you could use the =mppopm= CLI to, say, rate the current song, but in order to actually _do_ anything with that rating in the future, you'd have had to write some kind of mpd client for yourself. With the 0.2 release, I've added support for extended MPD filter syntax that allows queries that include the stickers that [[https://github.com/sp1ff/mpdpopm][mpdpopm]] manages-- so you can now, for instance, say: + +#+BEGIN_EXAMPLE +mppopm findadd "(artist =~ \"foo\") and (rating > 175)" +#+END_EXAMPLE + +MPD will handle the "artist =~" clause & [[https://github.com/sp1ff/mpdpopm][mpdpopm]] the "rating >" clause, as well as combining the results. + +This will hopefully be a start to making [[https://github.com/sp1ff/mpdpopm][mpdpopm]] into a more of a user-facing application than a developer-facing utlity. + +Windows support may be some time coming; the daemon depends on Unix signal handling, the MPD Unix socket, and the Unix daemon logic, especially =fork= & =exec=... if you'd like to run it on Windows, let me know-- if there's enough interest, and I can get some kind of Windows VM setup, I'll look at a port. + +Longer-term, I see [[https://github.com/sp1ff/mpdpopm][mpdpopm]] as a "dual" to mpd-- mpd commits to never altering your files. mpdpopm will take on that task in terms of tags, at least. To address the "plumbing, not the sink" problem, I'd like to author a client that will handle player control (of course), but also visualization & tag editing-- a complete music library solution. + +Suggestions, bug reports & PRs welcome! diff --git a/pkgs/by-name/mp/mpdpopm/build.rs b/pkgs/by-name/mp/mpdpopm/build.rs new file mode 100644 index 00000000..04586f29 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/build.rs @@ -0,0 +1,15 @@ +extern crate lalrpop; +fn main() { + let out_dir = std::env::var("OUT_DIR").unwrap(); + + lalrpop::Configuration::new() + .emit_comments(true) + .emit_whitespace(true) + .log_verbose() + .set_out_dir(out_dir) + .process_dir("./") + .unwrap(); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=./src/filters.lalrpop"); +} diff --git a/pkgs/by-name/mp/mpdpopm/config.lsp b/pkgs/by-name/mp/mpdpopm/config.lsp new file mode 100644 index 00000000..0e9b587d --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/config.lsp @@ -0,0 +1,10 @@ +{ + "conn": { + "Local": { + "path": "/run/user/1000/mpd/socket" + } + }, + "local_music_dir": "/home/soispha/media/music/beets", + "log": "/home/soispha/.local/share/mpdpopm/log", + "version": "1" +} diff --git a/pkgs/by-name/mp/mpdpopm/flake.lock b/pkgs/by-name/mp/mpdpopm/flake.lock new file mode 100644 index 00000000..c1d50dc3 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1769237874, + "narHash": "sha256-saOixpqPT4fiE/M8EfHv9I98f3sSEvt6nhMJ/z0a7xI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "523257564973361cc3e55e3df3e77e68c20b0b80", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1768158989, + "narHash": "sha256-67vyT1+xClLldnumAzCTBvU0jLZ1YBcf4vANRWP3+Ak=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "e96d59dff5c0d7fddb9d113ba108f03c3ef99eca", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/pkgs/by-name/mp/mpdpopm/flake.nix b/pkgs/by-name/mp/mpdpopm/flake.nix new file mode 100644 index 00000000..f6b622fe --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/flake.nix @@ -0,0 +1,66 @@ +# Mpdpopm - A mpd rating tracker +# +# Copyright (C) 2026 Benedikt Peetz, Michael Herstine <sp1ff@pobox.com> <benedikt.peetz@b-peetz.de, sp1ff@pobox.com> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Mpdpopm. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. +{ + description = "A mpd rating tracker"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + }; + + outputs = { + self, + nixpkgs, + treefmt-nix, + ... + }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages."${system}"; + + treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;}; + in { + checks."${system}" = { + formatting = treefmtEval.config.build.check self; + }; + + formatter."${system}" = treefmtEval.config.build.wrapper; + + devShells."${system}".default = pkgs.mkShell { + packages = [ + # rust stuff + pkgs.cargo + pkgs.clippy + pkgs.rustc + pkgs.rustfmt + pkgs.mold + + pkgs.cargo-edit + pkgs.cargo-expand + pkgs.cargo-flamegraph + + # Releng + pkgs.git-bug + pkgs.reuse + pkgs.cocogitto + + # Perf + pkgs.hyperfine + ]; + }; + }; +} +# vim: ts=2 + diff --git a/modules/home.legacy/conf/rofi/default.nix b/pkgs/by-name/mp/mpdpopm/package.nix index 3de22ea0..907bb1cf 100644 --- a/modules/home.legacy/conf/rofi/default.nix +++ b/pkgs/by-name/mp/mpdpopm/package.nix @@ -7,13 +7,23 @@ # # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{pkgs, ...}: { - programs.rofi = { - enable = true; - package = pkgs.rofi-wayland; - terminal = "${pkgs.alacritty}/bin/alacritty"; - # show-icons = true; - # location = "center"; - theme = ./nord-twoLines.rasi; +{ + rustPlatform, + lib, +}: +rustPlatform.buildRustPackage (finalAttrs: { + pname = "mpdpopm"; + version = "0.1.0"; + + buildInputs = []; + nativeBuildInputs = [ ]; + + src = ./.; + cargoLock = { + lockFile = ./Cargo.lock; }; -} + + meta = { + mainProgram = "mpdpopm"; + }; +}) diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs new file mode 100644 index 00000000..faa651bf --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs @@ -0,0 +1,604 @@ +// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! # mppopm +//! +//! mppopmd client +//! +//! # Introduction +//! +//! `mppopmd` is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts & +//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust +//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as +//! the sticker database, by invoking external commands to keep your tags up-to-date (something +//! along the lines of [mpdcron](https://alip.github.io/mpdcron)). `mppopm` is a command-line client +//! for `mppopmd`. Run `mppopm --help` for detailed usage. + +use mpdpopm::{ + clients::{Client, PlayerStatus}, + config::{self, Config}, + filters::ExpressionParser, + filters_ast::{FilterStickerNames, evaluate}, + messanges::COMMAND_CHANNEL, + storage::{last_played, play_count, rating}, +}; + +use anyhow::{Context, Result, anyhow, bail}; +use clap::{Parser, Subcommand}; +use tracing::{debug, info, level_filters::LevelFilter, trace}; +use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt}; + +use std::path::PathBuf; + +/// Map `tracks' argument(s) to a Vec of String containing one or more mpd URIs +/// +/// Several sub-commands take zero or more positional arguments meant to name tracks, with the +/// convention that zero indicates that the sub-command should use the currently playing track. +/// This is a convenience function for mapping the value returned by [`get_many`] to a +/// convenient representation of the user's intentions. +/// +/// [`get_many`]: [`clap::ArgMatches::get_many`] +async fn map_tracks(client: &mut Client, args: Option<Vec<String>>) -> Result<Vec<String>> { + let files = match args { + Some(iter) => iter, + None => { + let file = provide_file(client, None).await?; + vec![file] + } + }; + Ok(files) +} + +async fn provide_file(client: &mut Client, maybe_file: Option<String>) -> Result<String> { + let file = match maybe_file { + Some(file) => file, + None => { + match client + .status() + .await + .context("Failed to get status of client")? + { + PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => curr + .file + .to_str() + .ok_or_else(|| anyhow!("Path is not utf8: `{}`", curr.file.display()))? + .to_string(), + PlayerStatus::Stopped => { + bail!("Player is stopped"); + } + } + } + }; + + Ok(file) +} + +/// Retrieve ratings for one or more tracks +async fn get_ratings( + client: &mut Client, + tracks: Option<Vec<String>>, + with_uri: bool, +) -> Result<()> { + let mut ratings: Vec<(String, i8)> = Vec::new(); + + for file in map_tracks(client, tracks).await? { + let rating = rating::get(client, &file).await?; + + ratings.push((file, rating.unwrap_or_default())); + } + + if ratings.len() == 1 && !with_uri { + println!("{}", ratings[0].1); + } else { + for pair in ratings { + println!("{}: {}", pair.0, pair.1); + } + } + + Ok(()) +} + +/// Rate a track +async fn set_rating(client: &mut Client, rating: i8, arg: Option<String>) -> Result<()> { + let is_current = arg.is_none(); + let file = provide_file(client, arg).await?; + + rating::set(client, &file, rating).await?; + + match is_current { + false => info!("Set the rating for \"{}\" to \"{}\".", file, rating), + true => info!("Set the rating for the current song to \"{}\".", rating), + } + + Ok(()) +} + +/// Rate a track by incrementing the current rating +async fn inc_rating(client: &mut Client, arg: Option<String>) -> Result<()> { + let is_current = arg.is_none(); + let file = provide_file(client, arg).await?; + + let now = rating::get(client, &file).await?; + + rating::set(client, &file, now.unwrap_or_default().saturating_add(1)).await?; + + match is_current { + false => info!("Incremented the rating for \"{}\".", file), + true => info!("Incremented the rating for the current song."), + } + + Ok(()) +} + +/// Rate a track by decrementing the current rating +async fn decr_rating(client: &mut Client, arg: Option<String>) -> Result<()> { + let is_current = arg.is_none(); + let file = provide_file(client, arg).await?; + + let now = rating::get(client, &file).await?; + + rating::set(client, &file, now.unwrap_or_default().saturating_sub(1)).await?; + + match is_current { + false => info!("Decremented the rating for \"{}\".", file), + true => info!("Decremented the rating for the current song."), + } + + Ok(()) +} + +/// Retrieve the playcount for one or more tracks +async fn get_play_counts( + client: &mut Client, + tracks: Option<Vec<String>>, + with_uri: bool, +) -> Result<()> { + let mut playcounts: Vec<(String, usize)> = Vec::new(); + for file in map_tracks(client, tracks).await? { + let playcount = play_count::get(client, &file).await?.unwrap_or_default(); + playcounts.push((file, playcount)); + } + + if playcounts.len() == 1 && !with_uri { + println!("{}", playcounts[0].1); + } else { + for pair in playcounts { + println!("{}: {}", pair.0, pair.1); + } + } + + Ok(()) +} + +/// Set the playcount for a track +async fn set_play_counts(client: &mut Client, playcount: usize, arg: Option<String>) -> Result<()> { + let is_current = arg.is_none(); + let file = provide_file(client, arg).await?; + + play_count::set(client, &file, playcount).await?; + + match is_current { + false => info!("Set the playcount for \"{}\" to \"{}\".", file, playcount), + true => info!( + "Set the playcount for the current song to \"{}\".", + playcount + ), + } + + Ok(()) +} + +/// Retrieve the last played time for one or more tracks +async fn get_last_playeds( + client: &mut Client, + tracks: Option<Vec<String>>, + with_uri: bool, +) -> Result<()> { + let mut lastplayeds: Vec<(String, Option<u64>)> = Vec::new(); + for file in map_tracks(client, tracks).await? { + let lastplayed = last_played::get(client, &file).await?; + lastplayeds.push((file, lastplayed)); + } + + if lastplayeds.len() == 1 && !with_uri { + println!( + "{}", + match lastplayeds[0].1 { + Some(t) => format!("{}", t), + None => String::from("N/A"), + } + ); + } else { + for pair in lastplayeds { + println!( + "{}: {}", + pair.0, + match pair.1 { + Some(t) => format!("{}", t), + None => String::from("N/A"), + } + ); + } + } + + Ok(()) +} + +/// Set the playcount for a track +async fn set_last_playeds(client: &mut Client, lastplayed: u64, arg: Option<String>) -> Result<()> { + let is_current = arg.is_none(); + let file = provide_file(client, arg).await?; + + last_played::set(client, &file, lastplayed).await?; + + match is_current { + false => info!("Set last played for \"{}\" to \"{}\".", file, lastplayed), + true => info!( + "Set last played for the current song to \"{}\".", + lastplayed + ), + } + + Ok(()) +} + +/// Retrieve the list of stored playlists +async fn get_playlists(client: &mut Client) -> Result<()> { + let mut pls = client.get_stored_playlists().await?; + pls.sort(); + println!("Stored playlists:"); + for pl in pls { + println!("{}", pl); + } + Ok(()) +} + +/// Add songs selected by filter to the queue +async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> Result<()> { + let ast = match ExpressionParser::new().parse(filter) { + Ok(ast) => ast, + Err(err) => { + bail!("Failed to parse filter: `{}`", err) + } + }; + + debug!("ast: {:#?}", ast); + + let mut results = Vec::new(); + for song in evaluate(&ast, case_sensitive, client, &FilterStickerNames::default()) + .await + .context("Failed to evaluate filter")? + { + let out = client.add(&song).await; + + if out.is_ok() { + eprintln!("Added: `{}`", song) + } + + results.push(out); + } + + match results.into_iter().collect::<Result<Vec<()>>>() { + Ok(_) => Ok(()), + Err(err) => Err(err), + } +} + +/// `mppopmd' client +#[derive(Parser)] +struct Args { + /// path to configuration file + #[arg(short, long)] + config: Option<PathBuf>, + + /// enable verbose logging + #[arg(short, long)] + verbose: bool, + + /// enable debug loggin (implies --verbose) + #[arg(short, long)] + debug: bool, + + #[command(subcommand)] + command: SubCommand, +} + +#[derive(Subcommand)] +enum RatingCommand { + /// retrieve the rating for one or more tracks + /// + /// With no arguments, retrieve the rating of the current song & print it + /// on stdout. With one argument, retrieve that track's rating & print it + /// on stdout. With multiple arguments, print their ratings on stdout, one + /// per line, prefixed by the track name. + /// + /// Ratings are expressed as an integer between -128 & 128, exclusive, with + /// the convention that 0 denotes "un-rated". + #[clap(verbatim_doc_comment)] + Get { + /// Always show the song URI, even when there is only one track + #[arg(short, long)] + with_uri: bool, + + tracks: Option<Vec<String>>, + }, + + /// set the rating for one track + /// + /// With one argument, set the rating of the current song to that argument. + /// With a second argument, rate that song at the first argument. Ratings + /// may be expressed a an integer between 0 & 255, inclusive. + #[clap(verbatim_doc_comment)] + Set { rating: i8, track: Option<String> }, + + /// increment the rating for one track + /// + /// With one argument, increment the rating of the current song. + /// With a second argument, rate that song at the first argument. + #[clap(verbatim_doc_comment)] + Inc { track: Option<String> }, + + /// decrement the rating for one track + /// + /// With one argument, decrement the rating of the current song. + /// With a second argument, rate that song at the first argument. + #[clap(verbatim_doc_comment)] + Decr { track: Option<String> }, +} + +#[derive(Subcommand)] +enum PlayCountCommand { + /// retrieve the play count for one or more tracks + /// + /// With no arguments, retrieve the play count of the current song & print it + /// on stdout. With one argument, retrieve that track's play count & print it + /// on stdout. With multiple arguments, print their play counts on stdout, one + /// per line, prefixed by the track name. + #[clap(verbatim_doc_comment)] + Get { + /// Always show the song URI, even when there is only one track + #[arg(short, long)] + with_uri: bool, + + tracks: Option<Vec<String>>, + }, + + /// set the play count for one track + /// + /// With one argument, set the play count of the current song to that argument. With a + /// second argument, set the play count for that song to the first. + #[clap(verbatim_doc_comment)] + Set { + play_count: usize, + track: Option<String>, + }, +} + +#[derive(Subcommand)] +enum LastPlayedCommand { + /// retrieve the last played timestamp for one or more tracks + /// + /// With no arguments, retrieve the last played timestamp of the current + /// song & print it on stdout. With one argument, retrieve that track's + /// last played time & print it on stdout. With multiple arguments, print + /// their last played times on stdout, one per line, prefixed by the track + /// name. + /// + /// The last played timestamp is expressed in seconds since Unix epoch. + #[clap(verbatim_doc_comment)] + Get { + /// Always show the song URI, even when there is only one track + #[arg(short, long)] + with_uri: bool, + + tracks: Option<Vec<String>>, + }, + + /// set the last played timestamp for one track + /// + /// With one argument, set the last played time of the current song. With two + /// arguments, set the last played time for the second argument to the first. + /// The last played timestamp is expressed in seconds since Unix epoch. + #[clap(verbatim_doc_comment)] + Set { + last_played: u64, + track: Option<String>, + }, +} + +#[derive(Subcommand)] +enum PlaylistsCommand { + /// retrieve the list of stored playlists + #[clap(verbatim_doc_comment)] + Get {}, +} + +#[derive(Subcommand)] +enum DjCommand { + /// Activate the automatic DJ mode on the mpdpopmd daemon. + /// + /// In this mode, the daemon will automatically add new tracks to the playlist based on a + /// recommendation algorithm. + #[clap(verbatim_doc_comment)] + Start {}, + + /// Deactivate the automatic DJ mode on the mpdpopmd daemon. + /// + /// In this mode, the daemon will automatically add new tracks to the playlist based on a + /// recommendation algorithm. + #[clap(verbatim_doc_comment)] + Stop {}, +} + +#[derive(Subcommand)] +enum SubCommand { + /// Change details about rating. + Rating { + #[command(subcommand)] + command: RatingCommand, + }, + + /// Change details about play count. + PlayCount { + #[command(subcommand)] + command: PlayCountCommand, + }, + + /// Change details about last played date. + LastPlayed { + #[command(subcommand)] + command: LastPlayedCommand, + }, + + /// Change details about generated playlists. + Playlists { + #[command(subcommand)] + command: PlaylistsCommand, + }, + + /// search for songs matching matching a filter and add them to the queue + /// + /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow + /// searches on attributes managed by mpdpopm: rating, playcount & last played time. + /// + /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search + /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax + /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>. + /// + /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is + /// expressed as an unsigned integer, with zero interpreted as "not set". For instance: + /// + /// mppopm searchadd "(rating > 2)" + /// + /// Will add all songs in the library with a rating sticker > 2 to the play queue. + /// + /// mppopm also introduces OR clauses (MPD only supports AND), so that: + /// + /// mppopm searchadd "((rating > 2) AND (artist =~ \"pogues\"))" + /// + /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than + /// 2. + #[clap(verbatim_doc_comment)] + Searchadd { + filter: String, + + /// Respect the casing, when performing the filter evaluation. + #[arg(short, long, default_value_t = false)] + case_sensitive: bool, + }, + + /// Modify the automatic DJ mode on the mpdpopmd daemon. + /// + /// In this mode, the daemon will automatically add new tracks to the playlist based on a + /// recommendation algorithm. + Dj { + #[command(subcommand)] + command: DjCommand, + }, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Args::parse(); + + let config = if let Some(configpath) = &args.config { + match std::fs::read_to_string(configpath) { + Ok(text) => config::from_str(&text).with_context(|| { + format!("Failed to parse config file at: `{}`", configpath.display()) + })?, + Err(err) => { + // Either they did _not_, in which case they probably want to know that the config + // file they explicitly asked for does not exist, or there was some other problem, + // in which case we're out of options, anyway. Either way: + bail!( + "Failed to read config file at: `{}`, because: {err}", + configpath.display() + ) + } + } + } else { + Config::default() + }; + + // Handle log verbosity: debug => verbose + let lf = match (args.verbose, args.debug) { + (_, true) => LevelFilter::TRACE, + (true, false) => LevelFilter::DEBUG, + _ => LevelFilter::WARN, + }; + + tracing::subscriber::set_global_default( + Registry::default() + .with( + tracing_subscriber::fmt::Layer::default() + .compact() + .with_writer(std::io::stdout), + ) + .with( + EnvFilter::builder() + .with_default_directive(lf.into()) + .from_env() + .unwrap(), + ), + ) + .unwrap(); + + trace!("logging configured."); + + let mut client = match config.conn { + config::Connection::Local { path } => Client::open(path).await?, + config::Connection::TCP { host, port } => { + Client::connect(format!("{}:{}", host, port)).await? + } + }; + + match args.command { + SubCommand::Rating { command } => match command { + RatingCommand::Get { with_uri, tracks } => { + get_ratings(&mut client, tracks, with_uri).await + } + RatingCommand::Set { rating, track } => set_rating(&mut client, rating, track).await, + RatingCommand::Inc { track } => inc_rating(&mut client, track).await, + RatingCommand::Decr { track } => decr_rating(&mut client, track).await, + }, + SubCommand::PlayCount { command } => match command { + PlayCountCommand::Get { with_uri, tracks } => { + get_play_counts(&mut client, tracks, with_uri).await + } + PlayCountCommand::Set { play_count, track } => { + set_play_counts(&mut client, play_count, track).await + } + }, + SubCommand::LastPlayed { command } => match command { + LastPlayedCommand::Get { with_uri, tracks } => { + get_last_playeds(&mut client, tracks, with_uri).await + } + LastPlayedCommand::Set { last_played, track } => { + set_last_playeds(&mut client, last_played, track).await + } + }, + SubCommand::Playlists { command } => match command { + PlaylistsCommand::Get {} => get_playlists(&mut client).await, + }, + SubCommand::Searchadd { + filter, + case_sensitive, + } => searchadd(&mut client, &filter, case_sensitive).await, + SubCommand::Dj { command } => match command { + DjCommand::Start {} => client.send_message(COMMAND_CHANNEL, "dj start").await, + DjCommand::Stop {} => client.send_message(COMMAND_CHANNEL, "dj stop").await, + }, + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs new file mode 100644 index 00000000..643611d6 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs @@ -0,0 +1,150 @@ +// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! # mppopmd +//! +//! Maintain ratings & playcounts for your mpd server. +//! +//! # Introduction +//! +//! This is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts & +//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust +//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as +//! the sticker database, by invoking external commands to keep your tags up-to-date (something +//! along the lines of [mpdcron](https://alip.github.io/mpdcron)). + +use mpdpopm::{ + config::{self, Config}, + mpdpopm, +}; + +use anyhow::{Context, Result, bail}; +use clap::Parser; +use tracing::{info, level_filters::LevelFilter}; +use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt}; + +use std::{io, path::PathBuf, sync::MutexGuard}; + +pub struct MyMutexGuardWriter<'a>(MutexGuard<'a, std::fs::File>); + +impl io::Write for MyMutexGuardWriter<'_> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + + #[inline] + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { + self.0.write_vectored(bufs) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { + self.0.write_fmt(fmt) + } +} + +/// mpd + POPM +/// +/// `mppopmd' is a companion daemon for `mpd' that maintains playcounts & ratings, +/// as well as implementing some handy functions. It maintains ratings & playcounts in the sticker +/// database, but it allows you to keep that information in your tags, as well, by invoking external +/// commands to keep your tags up-to-date. +#[derive(Parser)] +struct Args { + /// path to configuration file + #[arg(short, long)] + config: Option<PathBuf>, + + /// enable verbose logging + #[arg(short, long)] + verbose: bool, + + /// enable debug loggin (implies --verbose) + #[arg(short, long)] + debug: bool, +} + +/// Entry point for `mpdopmd'. +/// +/// Do *not* use the #[tokio::main] attribute here! If this program is asked to daemonize (the usual +/// case), we will fork after tokio has started its thread pool, with disastrous consequences. +/// Instead, stay synchronous until we've daemonized (or figured out that we don't need to), and +/// only then fire-up the tokio runtime. +fn main() -> Result<()> { + use mpdpopm::vars::VERSION; + + let args = Args::parse(); + + let config = if let Some(cfgpath) = &args.config { + match std::fs::read_to_string(cfgpath) { + Ok(text) => config::from_str(&text).with_context(|| { + format!("Failed to parse config file at: `{}`", cfgpath.display()) + })?, + // The config file (defaulted or not) either didn't exist, or we were unable to read its + // contents... + Err(err) => { + // Either they did _not_, in which case they probably want to know that the config + // file they explicitly asked for does not exist, or there was some other problem, + // in which case we're out of options, anyway. Either way: + bail!( + "No config file could be read at: `{}`, because: {err}", + cfgpath.display() + ) + } + } + } else { + Config::default() + }; + + // `--verbose' & `--debug' work as follows: if `--debug' is present, log at level Trace, no + // matter what. Else, if `--verbose' is present, log at level Debug. Else, log at level Info. + let lf = match (args.verbose, args.debug) { + (_, true) => LevelFilter::TRACE, + (true, false) => LevelFilter::DEBUG, + _ => LevelFilter::INFO, + }; + + let filter = EnvFilter::builder() + .with_default_directive(lf.into()) + .from_env() + .context("Failed to construct env filter")?; + + let formatter: Box<dyn Layer<Registry> + Send + Sync> = { + Box::new( + tracing_subscriber::fmt::Layer::default() + .compact() + .with_writer(io::stdout), + ) + }; + + tracing::subscriber::set_global_default(Registry::default().with(formatter).with(filter)) + .unwrap(); + + info!("mppopmd {VERSION} logging at level {lf:#?}."); + let rt = tokio::runtime::Runtime::new().unwrap(); + + rt.block_on(mpdpopm(config)).context("Main mpdpopm failed") +} diff --git a/pkgs/by-name/mp/mpdpopm/src/clients.rs b/pkgs/by-name/mp/mpdpopm/src/clients.rs new file mode 100644 index 00000000..b934714a --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/clients.rs @@ -0,0 +1,1200 @@ +// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! mpd clients and associated utilities. +//! +//! # Introduction +//! +//! This module contains basic types implementing various MPD client operations (cf. the [mpd +//! protocol](http://www.musicpd.org/doc/protocol/)). Since issuing the "idle" command will tie up +//! the connection, MPD clients often use multiple connections to the server (one to listen for +//! updates, one or more on which to issue commands). This modules provides two different client +//! types: [Client] for general-purpose use and [IdleClient] for long-lived connections listening +//! for server notifiations. +//! +//! Note that there *is* another idiom (used in [libmpdel](https://github.com/mpdel/libmpdel), +//! e.g.): open a single connection & issue an "idle" command. When you want to issue a command, +//! send a "noidle", then the command, then "idle" again. This isn't a race condition, as the +//! server will buffer any changes that took place when you were not idle & send them when you +//! re-issue the "idle" command. This crate however takes the approach of two channels (like +//! [mpdfav](https://github.com/vincent-petithory/mpdfav)). + +use anyhow::{Context, Error, Result, anyhow, bail, ensure}; +use async_trait::async_trait; +use regex::Regex; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::{TcpStream, ToSocketAddrs, UnixStream}; +use tracing::{debug, info}; + +use lazy_static::lazy_static; + +use std::{ + collections::HashMap, + convert::TryFrom, + fmt, + marker::{Send, Unpin}, + path::{Path, PathBuf}, + str::FromStr, +}; + +// Some default error context messages +const ENCODING_SNAFU: &str = "Failed to interpete text as utf8"; +const IO_SNAFU: &str = "Failed read from mpd socket"; + +/// A description of the current track, suitable for our purposes (as in, it only tracks the +/// attributes needed for this module's functionality). +#[derive(Clone, Debug)] +pub struct CurrentSong { + /// Identifier, unique within the play queue, identifying this particular track; if the same + /// file is listed twice in the `mpd' play queue each instance will get a distinct songid + pub songid: u64, + + /// Path, relative to `mpd' music directory root of this track + pub file: std::path::PathBuf, + + /// Elapsed time, in seconds, in this track + pub elapsed: f64, + + /// Total track duration, in seconds + pub duration: f64, +} + +impl CurrentSong { + fn new(songid: u64, file: std::path::PathBuf, elapsed: f64, duration: f64) -> CurrentSong { + CurrentSong { + songid, + file, + elapsed, + duration, + } + } + /// Compute the ratio of the track that has elapsed, expressed as a floating point between 0 & 1 + pub fn played_pct(&self) -> f64 { + self.elapsed / self.duration + } +} + +/// The MPD player itself can be in one of three states: playing, paused or stopped. In the first +/// two there is a "current" song. +#[derive(Clone, Debug)] +pub enum PlayerStatus { + Play(CurrentSong), + Pause(CurrentSong), + Stopped, +} + +impl PlayerStatus { + pub fn current_song(&self) -> Option<&CurrentSong> { + match self { + PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => Some(curr), + PlayerStatus::Stopped => None, + } + } +} + +/// A trait representing a simple, textual request/response protocol like that +/// [employed](https://www.musicpd.org/doc/html/protocol.html) by [MPD](https://www.musicpd.org/): +/// the caller sends a textual command & the server responds with a (perhaps multi-line) textual +/// response. +/// +/// This trait also enables unit testing client implementations. Note that it is async-- cf. +/// [async_trait](https://docs.rs/async-trait/latest/async_trait/). +#[async_trait] +pub trait RequestResponse { + async fn req(&mut self, msg: &str) -> Result<String>; + /// The hint is used to size the buffer prior to reading the response + async fn req_w_hint(&mut self, msg: &str, hint: usize) -> Result<String>; +} + +#[cfg(test)] +pub mod test_mock { + use super::*; + + /// Mock is an implementation of [`RequestRespone`] that checks expected requests & responses, + /// and will panic if it sees anything unexpected + pub struct Mock { + inmsgs: Vec<String>, + outmsgs: Vec<String>, + } + + impl Mock { + pub fn new(convo: &[(&str, &str)]) -> Mock { + let (left, right): (Vec<&str>, Vec<&str>) = convo.iter().copied().rev().unzip(); + Mock { + inmsgs: left.iter().map(|x| x.to_string()).collect(), + outmsgs: right.iter().map(|x| x.to_string()).collect(), + } + } + } + + #[async_trait] + impl RequestResponse for Mock { + async fn req(&mut self, msg: &str) -> Result<String> { + self.req_w_hint(msg, 512).await + } + async fn req_w_hint(&mut self, msg: &str, _hint: usize) -> Result<String> { + assert_eq!(msg, self.inmsgs.pop().unwrap()); + Ok(self.outmsgs.pop().unwrap()) + } + } + + #[tokio::test] + async fn mock_smoke_test() { + let mut mock = Mock::new(&[("ping", "pong"), ("from", "to")]); + assert_eq!(mock.req("ping").await.unwrap(), "pong"); + assert_eq!(mock.req("from").await.unwrap(), "to"); + } + + #[tokio::test] + #[should_panic] + async fn mock_negative_test() { + let mut mock = Mock::new(&[("ping", "pong")]); + assert_eq!(mock.req("ping").await.unwrap(), "pong"); + let _should_panic = mock.req("not there!").await.unwrap(); + } +} + +/// [MPD](https://www.musicpd.org/) connections talk the same +/// [protocol](https://www.musicpd.org/doc/html/protocol.html) over either a TCP or a Unix socket. +/// +/// # Examples +/// +/// Implementations are provided for tokio [UnixStream] and [TcpStream], but [MpdConnection] is a +/// trait that can work in terms of any asynchronous communications channel (so long as it is also +/// [Send] and [Unpin] so async executors can pass them between threads. +/// +/// To create a connection to an `MPD` server over a Unix domain socket: +/// +/// ```no_run +/// use std::path::Path; +/// use tokio::net::UnixStream; +/// use mpdpopm::clients::MpdConnection; +/// let local_conn = MpdConnection::<UnixStream>::connect(Path::new("/var/run/mpd/mpd.sock")); +/// ``` +/// +/// In this example, `local_conn` is a Future that will resolve to a Result containing the +/// [MpdConnection] Unix domain socket implementation once the socket has been established, the MPD +/// server greets us & the protocol version has been parsed. +/// +/// or over a TCP socket: +/// +/// ```no_run +/// use std::net::SocketAddrV4; +/// use tokio::net::{TcpStream, ToSocketAddrs}; +/// use mpdpopm::clients::MpdConnection; +/// let tcp_conn = MpdConnection::<TcpStream>::connect("localhost:6600".parse::<SocketAddrV4>().unwrap()); +/// ``` +/// +/// Here, `tcp_conn` is a Future that will resolve to a Result containing the [MpdConnection] TCP +/// implementation on successful connection to the MPD server (i.e. the connection is established, +/// the server greets us & we parse the protocol version). +/// +/// +pub struct MpdConnection<T: AsyncRead + AsyncWrite + Send + Unpin> { + sock: T, + _protocol_ver: String, +} + +/// MpdConnection implements RequestResponse using the usual (async) socket I/O +/// +/// The callers need not include the trailing newline in their requests; the implementation will +/// append it. +#[async_trait] +impl<T> RequestResponse for MpdConnection<T> +where + T: AsyncRead + AsyncWrite + Send + Unpin, +{ + async fn req(&mut self, msg: &str) -> Result<String> { + self.req_w_hint(msg, 512).await + } + async fn req_w_hint(&mut self, msg: &str, hint: usize) -> Result<String> { + self.sock + .write_all(format!("{}\n", msg).as_bytes()) + .await + .context(IO_SNAFU)?; + let mut buf = Vec::with_capacity(hint); + + // Given the request/response nature of the MPD protocol, our callers expect a complete + // response. Therefore we need to loop here until we see either "...^OK\n" or + // "...^ACK...\n". + let mut cb = 0; // # bytes read so far + let mut more = true; // true as long as there is more to read + while more { + cb += self.sock.read_buf(&mut buf).await.context(IO_SNAFU)?; + + // The shortest complete response has three bytes. If the final byte in `buf' is not a + // newline, then don't bother looking further. + if cb > 2 && char::from(buf[cb - 1]) == '\n' { + // If we're here, `buf' *may* contain a complete response. Search backward for the + // previous newline. It may not exist: many responses are of the form "OK\n". + let mut idx = cb - 2; + while idx > 0 { + if char::from(buf[idx]) == '\n' { + idx += 1; + break; + } + idx -= 1; + } + + if (idx + 2 < cb && char::from(buf[idx]) == 'O' && char::from(buf[idx + 1]) == 'K') + || (idx + 3 < cb + && char::from(buf[idx]) == 'A' + && char::from(buf[idx + 1]) == 'C' + && char::from(buf[idx + 2]) == 'K') + { + more = false; + } + } + } + + // Only doing this to trouble-shoot issue 11 + String::from_utf8(buf.clone()).context(ENCODING_SNAFU) + } +} + +/// Utility function to parse the initial response to a connection from mpd +async fn parse_connect_rsp<T>(sock: &mut T) -> Result<String> +where + T: AsyncReadExt + AsyncWriteExt + Send + Unpin, +{ + let mut buf = Vec::with_capacity(32); + let _cb = sock.read_buf(&mut buf).await.context(IO_SNAFU)?; + + // Only doing this to trouble-shoot issue 11 + let text = String::from_utf8(buf.clone()).context(ENCODING_SNAFU)?; + + ensure!( + text.starts_with("OK MPD "), + "failed to connect: {}", + text.trim() + ); + info!("Connected {}.", text[7..].trim()); + Ok(text[7..].trim().to_string()) +} + +impl MpdConnection<TcpStream> { + pub async fn connect<A: ToSocketAddrs>(addr: A) -> Result<Box<dyn RequestResponse>> { + let mut sock = TcpStream::connect(addr).await.context(IO_SNAFU)?; + let proto_ver = parse_connect_rsp(&mut sock).await?; + Ok(Box::new(MpdConnection::<TcpStream> { + sock, + _protocol_ver: proto_ver, + })) + } +} + +impl MpdConnection<UnixStream> { + // NTS: we have to box the return value because a `dyn RequestResponse` isn't Sized. + pub async fn connect<P: AsRef<Path>>(pth: P) -> Result<Box<dyn RequestResponse>> { + let mut sock = UnixStream::connect(pth).await.context(IO_SNAFU)?; + let proto_ver = parse_connect_rsp(&mut sock).await?; + Ok(Box::new(MpdConnection::<UnixStream> { + sock, + _protocol_ver: proto_ver, + })) + } +} + +/// Quote an argument by backslash-escaping " & \ characters +pub fn quote(text: &str) -> String { + if text.contains(&[' ', '\t', '\'', '"'][..]) { + let mut s = String::from("\""); + for c in text.chars() { + if c == '"' || c == '\\' { + s.push('\\'); + } + s.push(c); + } + s.push('"'); + s + } else { + text.to_string() + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Client // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// General-purpose [mpd](https://www.musicpd.org) +/// [client](https://www.musicpd.org/doc/html/protocol.html): "general-purpose" in the sense that we +/// send commands through it; the interface is narrowly scoped to this program's needs. +/// +/// # Introduction +/// +/// This is the primary abstraction of the MPD client protocol, written for the convenience of +/// [mpdpopm](crate). Construct instances with a TCP socket, a Unix socket, or any [RequestResponse] +/// implementation. You can then carry out assorted operations in the MPD client protocol by +/// invoking its methods. +/// +/// ```no_run +/// use std::path::Path; +/// use mpdpopm::clients::Client; +/// let client = Client::open(Path::new("/var/run/mpd.sock")); +/// ``` +/// +/// `client` is now a [Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html) that +/// resolves to a [Client] instance talking to `/var/run/mpd.sock`. +/// +/// ```no_run +/// use mpdpopm::clients::Client; +/// let client = Client::connect("localhost:6600"); +/// ``` +/// +/// `client` is now a [Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html) that +/// resolves to a [Client] instance talking TCP to the MPD server on localhost at port 6600. +pub struct Client { + stream: Box<dyn RequestResponse>, +} + +// Thanks to <https://stackoverflow.com/questions/35169259/how-to-make-a-compiled-regexp-a-global-variable> +lazy_static! { + static ref RE_STATE: regex::Regex = Regex::new(r"(?m)^state: (play|pause|stop)$").unwrap(); + static ref RE_SONGID: regex::Regex = Regex::new(r"(?m)^songid: ([0-9]+)$").unwrap(); + static ref RE_ELAPSED: regex::Regex = Regex::new(r"(?m)^elapsed: ([.0-9]+)$").unwrap(); + static ref RE_FILE: regex::Regex = Regex::new(r"(?m)^file: (.*)$").unwrap(); + static ref RE_DURATION: regex::Regex = Regex::new(r"(?m)^duration: (.*)$").unwrap(); +} + +impl Client { + pub async fn connect<A: ToSocketAddrs>(addrs: A) -> Result<Client> { + Self::new(MpdConnection::<TcpStream>::connect(addrs).await?) + } + + pub async fn open<P: AsRef<Path>>(pth: P) -> Result<Client> { + Self::new(MpdConnection::<UnixStream>::connect(pth).await?) + } + + pub fn new(stream: Box<dyn RequestResponse>) -> Result<Client> { + Ok(Client { stream }) + } +} + +impl Client { + /// Retrieve the current server status. + pub async fn status(&mut self) -> Result<PlayerStatus> { + // We begin with sending the "status" command: "Reports the current status of the player and + // the volume level." Per the docs, "MPD may omit lines which have no (known) value", so I + // can't really count on particular lines being there. Tho nothing is said in the docs, I + // also don't want to depend on the order. + let text = self.stream.req("status").await?; + + let proto = || -> Error { anyhow!("Failed to parse mpd status output (with regexes)") }; + + // I first thought to avoid the use (and cost) of regular expressions by just doing + // sub-string searching on "state: ", but when I realized I needed to only match at the + // beginning of a line I bailed & just went ahead. This makes for more succinct code, since + // I can't count on order, either. + let state = RE_STATE + .captures(&text) + .ok_or_else(proto)? + .get(1) + .ok_or_else(proto)? + .as_str(); + + match state { + "stop" => Ok(PlayerStatus::Stopped), + "play" | "pause" => { + let songid = RE_SONGID + .captures(&text) + .ok_or_else(proto)? + .get(1) + .ok_or_else(proto)? + .as_str() + .parse::<u64>() + .context("Failed to parse songid as u64")?; + + let elapsed = RE_ELAPSED + .captures(&text) + .ok_or_else(proto)? + .get(1) + .ok_or_else(proto)? + .as_str() + .parse::<f64>() + .context("failed to parse `elapsed` as f64")?; + + // navigate from `songid'-- don't send a "currentsong" message-- the current song + // could have changed + let text = self.stream.req(&format!("playlistid {}", songid)).await?; + + let file = RE_FILE + .captures(&text) + .ok_or_else(proto)? + .get(1) + .ok_or_else(proto)? + .as_str(); + let duration = RE_DURATION + .captures(&text) + .ok_or_else(proto)? + .get(1) + .ok_or_else(proto)? + .as_str() + .parse::<f64>() + .context("Failed to parse `duration` as f64")?; + + let curr = CurrentSong::new(songid, PathBuf::from(file), elapsed, duration); + + if state == "play" { + Ok(PlayerStatus::Play(curr)) + } else { + Ok(PlayerStatus::Pause(curr)) + } + } + _ => bail!("Encountered unknow state `{}`", state), + } + } + + /// Retrieve a song sticker by name + pub async fn get_sticker<T: FromStr>( + &mut self, + file: &str, + sticker_name: &str, + ) -> Result<Option<T>> + where + <T as FromStr>::Err: std::error::Error + Sync + Send + 'static, + { + let msg = format!("sticker get song {} {}", quote(file), quote(sticker_name)); + let text = self.stream.req(&msg).await?; + debug!("Sent message `{}'; got `{}'", &msg, &text); + + let prefix = format!("sticker: {}=", sticker_name); + if text.starts_with(&prefix) { + let s = text[prefix.len()..] + .split('\n') + .next() + .with_context(|| format!("Failed to parse `{}` as get_sticker response", text))?; + Ok(Some(T::from_str(s).with_context(|| { + format!( + "Failed to parse sticker value as correct type: `{}`", + sticker_name + ) + })?)) + } else { + // ACK_ERROR_NO_EXIST = 50 (Ack.hxx:17) + ensure!( + text.starts_with("ACK [50@0]"), + "Missing no sticker response" + ); + Ok(None) + } + } + + /// Set a song sticker by name + pub async fn set_sticker<T: std::fmt::Display>( + &mut self, + file: &str, + sticker_name: &str, + sticker_value: &T, + ) -> Result<()> { + let value_as_str = format!("{}", sticker_value); + let msg = format!( + "sticker set song {} {} {}", + quote(file), + quote(sticker_name), + quote(&value_as_str) + ); + let text = self.stream.req(&msg).await?; + debug!("Sent `{}'; got `{}'", &msg, &text); + + ensure!(text.starts_with("OK"), "Set sticker, not acknowledged"); + Ok(()) + } + + /// Send a file to a playlist + pub async fn send_to_playlist(&mut self, file: &str, pl: &str) -> Result<()> { + let msg = format!("playlistadd {} {}", quote(pl), quote(file)); + let text = self.stream.req(&msg).await?; + debug!("Sent `{}'; got `{}'.", &msg, &text); + ensure!(text.starts_with("OK"), "send_to_playlist not acknowledged"); + Ok(()) + } + + /// Send an arbitrary message + pub async fn send_message(&mut self, chan: &str, msg: &str) -> Result<()> { + let msg = format!("sendmessage {} {}", chan, quote(msg)); + let text = self.stream.req(&msg).await?; + debug!("Sent `{}'; got `{}'.", &msg, &text); + + ensure!(text.starts_with("OK"), "Send_message not acknowledged"); + Ok(()) + } + + /// Update a URI + pub async fn update(&mut self, uri: &str) -> Result<u64> { + let msg = format!("update \"{}\"", uri); + let text = self.stream.req(&msg).await?; + debug!("Sent `{}'; got `{}'.", &msg, &text); + + // We expect a response of the form: + // updating_db: JOBID + // OK + // on success, and + // ACK ERR + // on failure. + + let prefix = "updating_db: "; + ensure!( + text.starts_with(prefix), + "update response doesn't start with correct prefix" + ); + text[prefix.len()..].split('\n').collect::<Vec<&str>>()[0] + .to_string() + .parse::<u64>() + .context("Failed to treat update job id as u64") + } + + /// Get the list of stored playlists + pub async fn get_stored_playlists(&mut self) -> Result<std::vec::Vec<String>> { + let text = self.stream.req("listplaylists").await?; + debug!("Sent listplaylists; got `{}'.", &text); + + // We expect a response of the form: + // playlist: a + // Last-Modified: 2020-03-13T17:20:16Z + // playlsit: b + // Last-Modified: 2020-03-13T17:20:16Z + // ... + // OK + // + // or + // + // ACK... + ensure!( + !text.starts_with("ACK"), + "get_stored_playlists response not acknowledged" + ); + Ok(text + .lines() + .filter_map(|x| x.strip_prefix("playlist: ").map(String::from)) + .collect::<Vec<String>>()) + } + + /// Process a search (either find or search) response + fn search_rsp_to_uris(&self, text: &str) -> Result<std::vec::Vec<String>> { + // We expect a response of the form: + // file: P/Pogues, The - A Pistol For Paddy Garcia.mp3 + // Last-Modified: 2007-12-26T19:18:00Z + // Format: 44100:24:2 + // ... + // file: P/Pogues, The - Billy's Bones.mp3 + // ... + // OK + // + // or + // + // ACK... + ensure!(!text.starts_with("ACK"), "rsp_to_uris not acknowledged"); + Ok(text + .lines() + .filter_map(|x| x.strip_prefix("file: ").map(String::from)) + .collect::<Vec<String>>()) + } + + /// Search the database for songs matching filter (unary operator) + /// + /// Set `case` to true to request a case-sensitive search (false yields case-insensitive) + pub async fn find1( + &mut self, + cond: &str, + val: &str, + case: bool, + ) -> Result<std::vec::Vec<String>> { + let cmd = format!( + "{} {}", + if case { "find" } else { "search" }, + quote(&format!("({} {})", cond, val)) + ); + let text = self.stream.req(&cmd).await?; + self.search_rsp_to_uris(&text) + } + + /// Search the database for songs matching filter (case-sensitive, binary operator) + /// + /// Set `case` to true to request a case-sensitive search (false yields case-insensitive) + pub async fn find2( + &mut self, + attr: &str, + op: &str, + val: &str, + case: bool, + ) -> Result<std::vec::Vec<String>> { + let cmd = format!( + "{} {}", + if case { "find" } else { "search" }, + quote(&format!("({} {} {})", attr, op, val)) + ); + debug!("find2 sending ``{}''", cmd); + let text = self.stream.req(&cmd).await?; + self.search_rsp_to_uris(&text) + } + + /// Retrieve all instances of a given sticker under the music directory + /// + /// Return a mapping from song URI to textual sticker value + pub async fn get_stickers(&mut self, sticker: &str) -> Result<HashMap<String, String>> { + let text = self + .stream + .req(&format!("sticker find song \"\" {}", sticker)) + .await?; + + // We expect a response of the form: + // + // file: U-Z/Zafari - Addis Adaba.mp3 + // sticker: unwoundstack.com:rating=64 + // ... + // file: U-Z/Zero 7 - In Time (Album Version).mp3 + // sticker: unwoundstack.com:rating=255 + // OK + // + // or + // + // ACK ... + ensure!(!text.starts_with("ACK"), "get_stickers not ACKed"); + let mut m = HashMap::new(); + let mut lines = text.lines(); + loop { + let file = lines.next().context("get_stickers no new line")?; + if "OK" == file { + break; + } + let val = lines.next().context("get_stickers no val")?; + + m.insert( + String::from(&file[6..]), + String::from(&val[10 + sticker.len()..]), + ); + } + Ok(m) + } + + /// Retrieve the song URIs of all songs in the database + /// + /// Returns a vector of String + pub async fn get_all_songs(&mut self) -> Result<std::vec::Vec<String>> { + let text = self.stream.req("find \"(base '')\"").await?; + // We expect a response of the form: + // file: 0-A/A Positive Life - Lighten Up!.mp3 + // Last-Modified: 2020-11-18T22:47:07Z + // Format: 44100:24:2 + // Time: 399 + // duration: 398.550 + // Artist: A Positive Life + // Title: Lighten Up! + // Genre: Electronic + // file: 0-A/A Positive Life - Pleidean Communication.mp3 + // ... + // OK + // + // or "ACK..." + ensure!(!text.starts_with("ACK"), "get_all_songs not ACKed"); + Ok(text + .lines() + .filter_map(|x| x.strip_prefix("file: ").map(String::from)) + .collect::<Vec<String>>()) + } + + pub async fn add(&mut self, uri: &str) -> Result<()> { + let msg = format!("add {}", quote(uri)); + let text = self.stream.req(&msg).await?; + debug!("Sent `{}'; got `{}'.", &msg, &text); + + ensure!(text.starts_with("OK"), "add not Oked"); + Ok(()) + } +} + +#[cfg(test)] +mod client_tests { + use super::test_mock::Mock; + use super::*; + + /// Some basic "smoke" tests + #[tokio::test] + async fn client_smoke_test() { + let mock = Box::new(Mock::new(&[( + "sticker get song foo.mp3 stick", + "sticker: stick=splat\nOK\n", + )])); + let mut cli = Client::new(mock).unwrap(); + let val = cli + .get_sticker::<String>("foo.mp3", "stick") + .await + .unwrap() + .unwrap(); + assert_eq!(val, "splat"); + } + + /// Test the `status' method + #[tokio::test] + async fn test_status() { + let mock = Box::new(Mock::new(&[ + ( + "status", + // When the server is playing or paused, the response will look something like this: + "volume: -1 +repeat: 0 +random: 0 +single: 0 +consume: 0 +playlist: 3 +playlistlength: 87 +mixrampdb: 0.000000 +state: play +song: 14 +songid: 15 +time: 141:250 +bitrate: 128 +audio: 44100:24:2 +nextsong: 15 +nextsongid: 16 +elapsed: 140.585 +OK", + ), + // Should respond with a playlist id request + ( + "playlistid 15", + // Should look something like this: + "file: U-Z/U2 - Who's Gonna RIDE Your WILD HORSES.mp3 +Last-Modified: 2004-12-24T19:26:13Z +Artist: U2 +Title: Who's Gonna RIDE Your WILD HOR +Genre: Pop +Time: 316 +Pos: 41 +Id: 42 +duration: 249.994 +OK", + ), + ( + "status", + // But if the state is "stop", much of that will be missing; it will look more like: + "volume: -1 +repeat: 0 +random: 0 +single: 0 +consume: 0 +playlist: 84 +playlistlength: 27 +mixrampdb: 0.000000 +state: stop +OK", + ), + // Finally, let's simulate something being really wrong + ( + "status", + "volume: -1 +repeat: 0 +state: no-idea!?", + ), + ])); + let mut cli = Client::new(mock).unwrap(); + let stat = cli.status().await.unwrap(); + match stat { + PlayerStatus::Play(curr) => { + assert_eq!(curr.songid, 15); + assert_eq!( + curr.file.to_str().unwrap(), + "U-Z/U2 - Who's Gonna RIDE Your WILD HORSES.mp3" + ); + assert_eq!(curr.elapsed, 140.585); + assert_eq!(curr.duration, 249.994); + } + _ => panic!(), + } + + let stat = cli.status().await.unwrap(); + match stat { + PlayerStatus::Stopped => (), + _ => panic!(), + } + + let stat = cli.status().await; + match stat { + Err(_) => (), + Ok(_) => panic!(), + } + } + + /// Test the `get_sticker' method + #[tokio::test] + async fn test_get_sticker() { + let mock = Box::new(Mock::new(&[ + ( + "sticker get song foo.mp3 stick", + // On success, should get something like this... + "sticker: stick=2\nOK\n", + ), + ( + "sticker get song foo.mp3 stick", + // and on failure, something like this: + "ACK [50@0] {sticker} no such sticker\n", + ), + ( + "sticker get song foo.mp3 stick", + // Finally, let's try something nuts + "", + ), + ( + "sticker get song \"filename_with\\\"doublequotes\\\".flac\" unwoundstack.com:playcount", + "sticker: unwoundstack.com:playcount=11\nOK\n", + ), + ])); + let mut cli = Client::new(mock).unwrap(); + let val = cli + .get_sticker::<String>("foo.mp3", "stick") + .await + .unwrap() + .unwrap(); + assert_eq!(val, "2"); + let _val = cli + .get_sticker::<String>("foo.mp3", "stick") + .await + .unwrap() + .is_none(); + let _val = cli + .get_sticker::<String>("foo.mp3", "stick") + .await + .unwrap_err(); + let val = cli + .get_sticker::<String>( + "filename_with\"doublequotes\".flac", + "unwoundstack.com:playcount", + ) + .await + .unwrap() + .unwrap(); + assert_eq!(val, "11"); + } + + /// Test the `set_sticker' method + #[tokio::test] + async fn test_set_sticker() { + let mock = Box::new(Mock::new(&[ + ("sticker set song foo.mp3 stick 2", "OK\n"), + ( + "sticker set song foo.mp3 stick 2", + "ACK [50@0] {sticker} some error", + ), + ( + "sticker set song foo.mp3 stick 2", + "this makes no sense as a response", + ), + ])); + let mut cli = Client::new(mock).unwrap(); + let () = cli.set_sticker("foo.mp3", "stick", &"2").await.unwrap(); + let _val = cli.set_sticker("foo.mp3", "stick", &"2").await.unwrap_err(); + let _val = cli.set_sticker("foo.mp3", "stick", &"2").await.unwrap_err(); + } + + /// Test the `send_to_playlist' method + #[tokio::test] + async fn test_send_to_playlist() { + let mock = Box::new(Mock::new(&[ + ("playlistadd foo.m3u foo.mp3", "OK\n"), + ( + "playlistadd foo.m3u foo.mp3", + "ACK [101@0] {playlist} some error\n", + ), + ])); + let mut cli = Client::new(mock).unwrap(); + let () = cli.send_to_playlist("foo.mp3", "foo.m3u").await.unwrap(); + let _val = cli + .send_to_playlist("foo.mp3", "foo.m3u") + .await + .unwrap_err(); + } + + /// Test the `update' method + #[tokio::test] + async fn test_update() { + let mock = Box::new(Mock::new(&[ + ("update \"foo.mp3\"", "updating_db: 2\nOK\n"), + ("update \"foo.mp3\"", "ACK [50@0] {update} blahblahblah"), + ("update \"foo.mp3\"", "this makes no sense as a response"), + ])); + let mut cli = Client::new(mock).unwrap(); + let _val = cli.update("foo.mp3").await.unwrap(); + let _val = cli.update("foo.mp3").await.unwrap_err(); + let _val = cli.update("foo.mp3").await.unwrap_err(); + } + + /// Test retrieving stored playlists + #[tokio::test] + async fn test_get_stored_playlists() { + let mock = Box::new(Mock::new(&[ + ( + "listplaylists", + "playlist: saturday-afternoons-in-santa-cruz +Last-Modified: 2020-03-13T17:20:16Z +playlist: gaelic-punk +Last-Modified: 2020-05-24T00:36:02Z +playlist: morning-coffee +Last-Modified: 2020-03-13T17:20:16Z +OK +", + ), + ("listplaylists", "ACK [1@0] {listplaylists} blahblahblah"), + ])); + + let mut cli = Client::new(mock).unwrap(); + let val = cli.get_stored_playlists().await.unwrap(); + assert_eq!( + val, + vec![ + String::from("saturday-afternoons-in-santa-cruz"), + String::from("gaelic-punk"), + String::from("morning-coffee") + ] + ); + let _val = cli.get_stored_playlists().await.unwrap_err(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// IdleClient // +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq)] +pub enum IdleSubSystem { + Player, + Message, +} + +impl TryFrom<&str> for IdleSubSystem { + type Error = Error; + fn try_from(text: &str) -> std::result::Result<Self, Self::Error> { + let x = text.to_lowercase(); + if x == "player" { + Ok(IdleSubSystem::Player) + } else if x == "message" { + Ok(IdleSubSystem::Message) + } else { + bail!("{}", text) + } + } +} + +impl fmt::Display for IdleSubSystem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + IdleSubSystem::Player => write!(f, "Player"), + IdleSubSystem::Message => write!(f, "Message"), + } + } +} + +/// [MPD](https://www.musicpd.org) client for "idle" connections. +/// +/// # Introduction +/// +/// This is an MPD client designed to "idle": it opens a long-lived connection to the MPD server and +/// waits for MPD to respond with a message indicating that there's been a change to a subsystem of +/// interest. At present, there are only two subsystems in which [mpdpopm](crate) is interested: the player +/// & messages (cf. [IdleSubSystem]). +/// +/// ```no_run +/// use std::path::Path; +/// use tokio::runtime::Runtime; +/// use mpdpopm::clients::IdleClient; +/// +/// let mut rt = Runtime::new().unwrap(); +/// rt.block_on( async { +/// let mut client = IdleClient::open(Path::new("/var/run/mpd.sock")).await.unwrap(); +/// client.subscribe("player").await.unwrap(); +/// client.idle().await.unwrap(); +/// // Arrives here when the player's state changes +/// }) +/// ``` +/// +/// `client` is now a [Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html) that +/// resolves to an [IdleClient] instance talking to `/var/run/mpd.sock`. +/// +pub struct IdleClient { + conn: Box<dyn RequestResponse>, +} + +impl IdleClient { + /// Create a new [mpdpopm::client::IdleClient][IdleClient] instance from something that + /// implements [ToSocketAddrs] + pub async fn connect<A: ToSocketAddrs>(addr: A) -> Result<IdleClient> { + Self::new(MpdConnection::<TcpStream>::connect(addr).await?) + } + + pub async fn open<P: AsRef<Path>>(pth: P) -> Result<IdleClient> { + Self::new(MpdConnection::<UnixStream>::connect(pth).await?) + } + + pub fn new(stream: Box<dyn RequestResponse>) -> Result<IdleClient> { + Ok(IdleClient { conn: stream }) + } + + /// Subscribe to an mpd channel + pub async fn subscribe(&mut self, chan: &str) -> Result<()> { + let text = self.conn.req(&format!("subscribe {}", chan)).await?; + debug!("Sent subscribe message for {}; got `{}'.", chan, text); + ensure!(text.starts_with("OK"), "subscribe not Ok: `{}`", text); + debug!("Subscribed to {}.", chan); + Ok(()) + } + + /// Enter idle state-- return the subsystem that changed, causing the connection to return. NB + /// this may block for some time. + pub async fn idle(&mut self) -> Result<IdleSubSystem> { + let text = self.conn.req("idle player message").await?; + debug!("Sent idle message; got `{}'.", text); + + // If the player state changes, we'll get: "changed: player\nOK\n" + // + // If a ratings message is sent, we'll get: "changed: message\nOK\n", to which we respond + // "readmessages", which should give us something like: + // + // channel: ratings + // message: 255 + // OK + // + // We remain subscribed, but we need to send a new idle message. + + ensure!(text.starts_with("changed: "), "idle not OK: `{}`", text); + let idx = text.find('\n').context("idle has no newline")?; + + let result = IdleSubSystem::try_from(&text[9..idx])?; + let text = text[idx + 1..].to_string(); + ensure!(text.starts_with("OK"), "idle not OKed"); + + Ok(result) + } + + /// This method simply returns the results of a "readmessages" as a HashMap of channel name to + /// Vec of (String) messages for that channel + pub async fn get_messages(&mut self) -> Result<HashMap<String, Vec<String>>> { + let text = self.conn.req("readmessages").await?; + debug!("Sent readmessages; got `{}'.", text); + + // We expect something like: + // + // channel: ratings + // message: 255 + // OK + // + // We remain subscribed, but we need to send a new idle message. + + let mut m: HashMap<String, Vec<String>> = HashMap::new(); + + // Populate `m' with a little state machine: + enum State { + Init, + Running, + Finished, + } + let mut state = State::Init; + let mut chan = String::new(); + let mut msgs: Vec<String> = Vec::new(); + for line in text.lines() { + match state { + State::Init => { + ensure!(line.starts_with("channel: "), "no `channel: ` given"); + chan = String::from(&line[9..]); + state = State::Running; + } + State::Running => { + if let Some(stripped) = line.strip_prefix("message: ") { + msgs.push(String::from(stripped)); + } else if let Some(stripped) = line.strip_prefix("channel: ") { + match m.get_mut(&chan) { + Some(v) => v.append(&mut msgs), + None => { + m.insert(chan.clone(), msgs.clone()); + } + } + chan = String::from(stripped); + msgs = Vec::new(); + } else if line == "OK" { + match m.get_mut(&chan) { + Some(v) => v.append(&mut msgs), + None => { + m.insert(chan.clone(), msgs.clone()); + } + } + state = State::Finished; + } else { + bail!("Failed to get messages: `{}`", text) + } + } + State::Finished => { + // Should never be here! + bail!("Failed to get messages: `{}`", text) + } + } + } + + Ok(m) + } +} + +#[cfg(test)] +/// Let's test IdleClient! +mod idle_client_tests { + + use super::test_mock::Mock; + use super::*; + + /// Some basic "smoke" tests + #[tokio::test] + async fn test_get_messages() { + let mock = Box::new(Mock::new(&[( + "readmessages", + // If a ratings message is sent, we'll get: "changed: message\nOK\n", to which we + // respond "readmessages", which should give us something like: + // + // channel: ratings + // message: 255 + // OK + // + // We remain subscribed, but we need to send a new idle message. + "channel: ratings +message: 255 +message: 128 +channel: send-to-playlist +message: foo.m3u +OK +", + )])); + let mut cli = IdleClient::new(mock).unwrap(); + let hm = cli.get_messages().await.unwrap(); + let val = hm.get("ratings").unwrap(); + assert_eq!(val.len(), 2); + let val = hm.get("send-to-playlist").unwrap(); + assert!(val.len() == 1); + } + + /// Test issue #1 + #[tokio::test] + async fn test_issue_1() { + let mock = Box::new(Mock::new(&[( + "readmessages", + "channel: playcounts +message: a +channel: playcounts +message: b +OK +", + )])); + let mut cli = IdleClient::new(mock).unwrap(); + let hm = cli.get_messages().await.unwrap(); + let val = hm.get("playcounts").unwrap(); + assert_eq!(val.len(), 2); + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/config.rs b/pkgs/by-name/mp/mpdpopm/src/config.rs new file mode 100644 index 00000000..b4fe3c53 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/config.rs @@ -0,0 +1,286 @@ +// Copyright (C) 2021-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! # mpdpopm Configuration +//! +//! ## Introduction +//! +//! This module defines the configuration struct & handles deserialization thereof. +//! +//! ## Discussion +//! +//! In the first releases of [mpdpopm](crate) I foolishly forgot to add a version field to the +//! configuration structure. I am now paying for my sin by having to attempt serializing two +//! versions until one succeeds. +//! +//! The idiomatic approach to versioning [serde](https://docs.serde.rs/serde/) structs seems to be +//! using an +//! [enumeration](https://www.reddit.com/r/rust/comments/44dds3/handling_multiple_file_versions_with_serde_or/). This +//! implementation *now* uses that, but that leaves us with the problem of handling the initial, +//! un-tagged version. I proceed as follows: +//! +//! 1. attempt to deserialize as a member of the modern enumeration +//! 2. if that succeeds, with the most-recent version, we're good +//! 3. if that succeeds with an archaic version, convert to the most recent and warn the user +//! 4. if that fails, attempt to deserialize as the initial struct version +//! 5. if that succeeds, convert to the most recent & warn the user +//! 6. if that fails, I'm kind of stuck because I don't know what the user was trying to express; +//! bundle-up all the errors, report 'em & urge the user to use the most recent version +use crate::vars::{LOCALSTATEDIR, PREFIX}; + +use anyhow::{Result, bail}; +use serde::{Deserialize, Serialize}; + +use std::{env, path::PathBuf}; + +/// [mpdpopm](crate) can communicate with MPD over either a local Unix socket, or over regular TCP +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub enum Connection { + /// Local Unix socket-- payload is the path to the socket + Local { path: PathBuf }, + /// TCP-- payload is the hostname & port number + TCP { host: String, port: u16 }, +} + +impl Connection { + pub fn new() -> Result<Self> { + let env = match env::var("MPD_HOST") { + Ok(env) => Some(env), + Err(err) => match err { + env::VarError::NotPresent => None, + env::VarError::NotUnicode(_) => { + bail!("Failed to get `MPD_HOST` env var: {err}") + } + }, + } + .unwrap_or("/run/mpd/socket".to_owned()); + + if env.starts_with("/") { + // We assume that this is a path to a local socket + Ok(Self::Local { + path: PathBuf::from(env), + }) + } else { + todo!("Not yet able to auto-parse, MPD_HOST for remote connection") + } + } +} + +impl Default for Connection { + fn default() -> Self { + Self::new().expect("Could not generate default connection") + } +} + +#[cfg(test)] +mod test_connection { + use super::Connection; + + #[test] + fn test_serde() { + use serde_json::to_string; + + use std::path::PathBuf; + + let text = to_string(&Connection::Local { + path: PathBuf::from("/var/run/mpd.sock"), + }) + .unwrap(); + + assert_eq!( + text, + String::from(r#"{"Local":{"path":"/var/run/mpd.sock"}}"#) + ); + + let text = to_string(&Connection::TCP { + host: String::from("localhost"), + port: 6600, + }) + .unwrap(); + assert_eq!( + text, + String::from(r#"{"TCP":{"host":"localhost","port":6600}}"#) + ); + } +} + +/// This is the most recent `mppopmd` configuration struct. +#[derive(Deserialize, Debug, Serialize)] +#[serde(default)] +pub struct Config { + /// Configuration format version-- must be "1" + // Workaround to https://github.com/rotty/lexpr-rs/issues/77 + // When this gets fixed, I can remove this element from the struct & deserialize as + // a Configurations element-- the on-disk format will be the same. + #[serde(rename = "version")] + _version: String, + + /// Location of log file + pub log: PathBuf, + + /// How to connect to mpd + pub conn: Connection, + + /// The `mpd' root music directory, relative to the host on which *this* daemon is running + pub local_music_dir: PathBuf, + + /// Percentage threshold, expressed as a number between zero & one, for considering a song to + /// have been played + pub played_thresh: f64, + + /// The interval, in milliseconds, at which to poll `mpd' for the current state + pub poll_interval_ms: u64, + + /// Channel to setup for assorted commands-- channel names must satisfy "[-a-zA-Z-9_.:]+" + pub commands_chan: String, +} + +impl Default for Config { + fn default() -> Self { + Self::new().unwrap() + } +} + +impl Config { + fn new() -> Result<Self> { + Ok(Self { + _version: String::from("1"), + log: [LOCALSTATEDIR, "log", "mppopmd.log"].iter().collect(), + conn: Connection::new()?, + local_music_dir: [PREFIX, "Music"].iter().collect(), + played_thresh: 0.6, + poll_interval_ms: 5000, + commands_chan: String::from("unwoundstack.com:commands"), + }) + } +} + +pub fn from_str(text: &str) -> Result<Config> { + let cfg: Config = match serde_json::from_str(text) { + Ok(cfg) => cfg, + Err(err_outer) => { + bail!("Failed to parse config: `{}`", err_outer) + } + }; + Ok(cfg) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[ignore = "We changed the config format to json"] + fn test_from_str() { + let cfg = Config::default(); + assert_eq!(cfg.commands_chan, String::from("unwoundstack.com:commands")); + + assert_eq!( + serde_json::to_string(&cfg).unwrap(), + format!( + r#"((version . "1") (log . "{}/log/mppopmd.log") (conn TCP (host . "localhost") (port . 6600)) (local_music_dir . "{}/Music") (playcount_sticker . "unwoundstack.com:playcount") (lastplayed_sticker . "unwoundstack.com:lastplayed") (played_thresh . 0.6) (poll_interval_ms . 5000) (commands_chan . "unwoundstack.com:commands") (playcount_command . "") (playcount_command_args) (rating_sticker . "unwoundstack.com:rating") (ratings_command . "") (ratings_command_args) (gen_cmds))"#, + LOCALSTATEDIR, PREFIX + ) + ); + + let cfg: Config = serde_json::from_str( + r#" +((version . "1") + (log . "/usr/local/var/log/mppopmd.log") + (conn TCP (host . "localhost") (port . 6600)) + (local_music_dir . "/usr/local/Music") + (playcount_sticker . "unwoundstack.com:playcount") + (lastplayed_sticker . "unwoundstack.com:lastplayed") + (played_thresh . 0.6) + (poll_interval_ms . 5000) + (commands_chan . "unwoundstack.com:commands") + (playcount_command . "") + (playcount_command_args) + (rating_sticker . "unwoundstack.com:rating") + (ratings_command . "") + (ratings_command_args) + (gen_cmds)) +"#, + ) + .unwrap(); + assert_eq!(cfg._version, String::from("1")); + + let cfg: Config = serde_json::from_str( + r#" +((version . "1") + (log . "/usr/local/var/log/mppopmd.log") + (conn Local (path . "/home/mgh/var/run/mpd/mpd.sock")) + (local_music_dir . "/usr/local/Music") + (playcount_sticker . "unwoundstack.com:playcount") + (lastplayed_sticker . "unwoundstack.com:lastplayed") + (played_thresh . 0.6) + (poll_interval_ms . 5000) + (commands_chan . "unwoundstack.com:commands") + (playcount_command . "") + (playcount_command_args) + (rating_sticker . "unwoundstack.com:rating") + (ratings_command . "") + (ratings_command_args) + (gen_cmds)) +"#, + ) + .unwrap(); + assert_eq!(cfg._version, String::from("1")); + assert_eq!( + cfg.conn, + Connection::Local { + path: PathBuf::from("/home/mgh/var/run/mpd/mpd.sock") + } + ); + + // Test fallback to "v0" of the config struct + let cfg = from_str(r#" +((log . "/home/mgh/var/log/mppopmd.log") + (host . "192.168.1.14") + (port . 6600) + (local_music_dir . "/space/mp3") + (playcount_sticker . "unwoundstack.com:playcount") + (lastplayed_sticker . "unwoundstack.com:lastplayed") + (played_thresh . 0.6) + (poll_interval_ms . 5000) + (playcount_command . "/usr/local/bin/scribbu") + (playcount_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-C" "%playcount" "%full-file")) + (commands_chan . "unwoundstack.com:commands") + (rating_sticker . "unwoundstack.com:rating") + (ratings_command . "/usr/local/bin/scribbu") + (ratings_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-r" "%rating" "%full-file")) + (gen_cmds . + (((name . "set-genre") + (formal_parameters . (Literal Track)) + (default_after . 1) + (cmd . "/usr/local/bin/scribbu") + (args . ("genre" "-a" "-C" "-g" "%1" "%full-file")) + (update . TrackOnly)) + ((name . "set-xtag") + (formal_parameters . (Literal Track)) + (default_after . 1) + (cmd . "/usr/local/bin/scribbu") + (args . ("xtag" "-A" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file")) + (update . TrackOnly)) + ((name . "merge-xtag") + (formal_parameters . (Literal Track)) + (default_after . 1) + (cmd . "/usr/local/bin/scribbu") + (args . ("xtag" "-m" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file")) + (update . TrackOnly))))) +"#).unwrap(); + assert_eq!(cfg.log, PathBuf::from("/home/mgh/var/log/mppopmd.log")); + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs new file mode 100644 index 00000000..5ddfc7cb --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs @@ -0,0 +1,155 @@ +use std::collections::HashSet; + +use anyhow::{Context, Result}; +use rand::{Rng, distr, seq::SliceRandom}; +use tracing::info; + +use crate::{clients::Client, storage}; + +pub(crate) trait Algorithm { + async fn next_track(&mut self, client: &mut Client) -> Result<String>; +} + +/// Generates generic discovery playlist, that fulfills following requirements: +/// - Will (eventually) include every not-played song. (So it can be used to rank a library) +/// - Returns liked songs more often then not-played or negative songs. +pub(crate) struct Discovery { + already_done: HashSet<String>, +} + +impl Algorithm for Discovery { + async fn next_track(&mut self, client: &mut Client) -> Result<String> { + macro_rules! take { + ($first:expr, $second:expr, $third:expr) => {{ + $first.pop().map_or_else( + || { + $second.pop().map_or_else( + || { + $third.pop().map_or_else( + || { + unreachable!( + "This means that there are no songs in the libary" + ) + }, + |val| { + tracing::info!( + "Selecting a `{}` track for the next entry in the queue", + stringify!($third) + ); + Ok::<_, anyhow::Error>(val) + }, + ) + }, + |val| { + tracing::info!( + "Selecting a `{}` track for the next entry in the queue", + stringify!($second) + ); + Ok::<_, anyhow::Error>(val) + }, + ) + }, + |val| { + tracing::info!( + "Selecting a `{}` track for the next entry in the queue", + stringify!($first) + ); + Ok::<_, anyhow::Error>(val) + }, + ) + }}; + } + + let mut rng = rand::rng(); + let (mut positive, mut neutral, mut negative) = { + let tracks = { + let mut base = client + .get_all_songs() + .await? + .into_iter() + .filter(|song| !self.already_done.contains(song)) + .collect::<Vec<_>>(); + + if base.is_empty() { + // We could either have no tracks in the library, + // or we actually already listed to everything. + self.already_done = HashSet::new(); + + info!("Resetting already done songs, as we have no more to choose from"); + + base = client.get_all_songs().await?; + } + + base + }; + + let mut positive = vec![]; + let mut neutral = vec![]; + let mut negative = vec![]; + + for track in tracks { + let weight = Self::weight_track(client, &track).await?; + + match weight { + 1..=i64::MAX => positive.push(track), + 0 => neutral.push(track), + i64::MIN..0 => negative.push(track), + } + } + + // Avoid an inherit ordering, that might be returned by the `Client::get_all_songs()` function. + positive.shuffle(&mut rng); + neutral.shuffle(&mut rng); + negative.shuffle(&mut rng); + + (positive, neutral, negative) + }; + + let pick = rng.sample( + distr::weighted::WeightedIndex::new([0.65, 0.5, 0.2].iter()) + .expect("to be valid, as hardcoded"), + ); + + let next = match pick { + 0 => take!(positive, neutral, negative), + 1 => take!(neutral, positive, negative), + 2 => take!(negative, neutral, positive), + _ => unreachable!("These indexes are not possible"), + }?; + + self.already_done.insert(next.clone()); + + Ok(next) + } +} + +impl Discovery { + pub(crate) fn new() -> Self { + Self { + already_done: HashSet::new(), + } + } + + /// Calculate a recommendation score for a track. + /// + /// The algorithm maps tracks, that the user likes to a high score and songs that the user + /// dislikes to a lower number. + /// Currently, only the rating, skip count and play count are considered. Similarity scores, + /// fetched from e.g. last.fm should be included in the future. + async fn weight_track(client: &mut Client, track: &str) -> Result<i64> { + let rating = i32::from(storage::rating::get(client, track).await?.unwrap_or(0)); + let play_count = i32::try_from(storage::play_count::get(client, track).await?.unwrap_or(0)) + .context("`play_count` too big")?; + let skip_count = i32::try_from(storage::skip_count::get(client, track).await?.unwrap_or(0)) + .context("`skip_count` too big")?; + + let output: f64 = + 1.0 * f64::from(rating) + 0.3 * f64::from(play_count) + -0.6 * f64::from(skip_count); + + let weight = output.round() as i64; + + // info!("`{track}`: {weight}"); + + Ok(weight) + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs b/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs new file mode 100644 index 00000000..a211a571 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use tracing::info; + +use crate::{clients::Client, dj::algorithms::Algorithm}; + +pub(crate) mod algorithms; + +pub(crate) struct Dj<A: Algorithm> { + algo: A, +} + +impl<A: Algorithm> Dj<A> { + pub(crate) fn new(algo: A) -> Self { + Self { algo } + } + + /// Add the next track to the playlist. + /// + /// This should be called after the previous track is finished, to avoid unbounded growth. + pub(crate) async fn add_track(&mut self, client: &mut Client) -> Result<()> { + let next = self.algo.next_track(client).await?; + + info!("Adding `{next}`, due to active dj mode"); + client.add(&next).await?; + + Ok(()) + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop new file mode 100644 index 00000000..970fc040 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop @@ -0,0 +1,160 @@ +// Copyright (C) 2020-2025 Michael Herstine <sp1ff@pobox.com> -*- mode: rust; rust-format-on-save: nil -*- +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +use lalrpop_util::ParseError; + +use crate::filters_ast::{ + Conjunction, + Disjunction, + Expression, + OpCode, + Selector, + Term, + Value, + expect_quoted, + parse_iso_8601 +}; +use tracing::debug; + +grammar; + +pub ExprOp: OpCode = { + "==" => OpCode::Equality, + "!=" => OpCode::Inequality, + "contains" => OpCode::Contains, + "=~" => OpCode::RegexMatch, + "!~" => OpCode::RegexExclude, + ">" => OpCode::GreaterThan, + "<" => OpCode::LessThan, + ">=" => OpCode::GreaterThanEqual, + "<=" => OpCode::LessThanEqual, +}; + +pub ExprSel: Selector = { + r"(?i)artist" => Selector::Artist, + r"(?i)album" => Selector::Album, + r"(?i)albumartist" => Selector::AlbumArtist, + r"(?i)title" => Selector::Title, + r"(?i)track" => Selector::Track, + r"(?i)name" => Selector::Name, + r"(?i)genre" => Selector::Genre, + r"(?i)date" => Selector::Date, + r"(?i)originaldate" => Selector::OriginalDate, + r"(?i)composer" => Selector::Composer, + r"(?i)performer" => Selector::Performer, + r"(?i)conductor" => Selector::Conductor, + r"(?i)work" => Selector::Work, + r"(?i)grouping" => Selector::Grouping, + r"(?i)comment" => Selector::Comment, + r"(?i)disc" => Selector::Disc, + r"(?i)label" => Selector::Label, + r"(?i)musicbrainz_aristid" => Selector::MusicbrainzAristID, + r"(?i)musicbrainz_albumid" => Selector::MusicbrainzAlbumID, + r"(?i)musicbrainz_albumartistid" => Selector::MusicbrainzAlbumArtistID, + r"(?i)musicbrainz_trackid" => Selector::MusicbrainzTrackID, + r"(?i)musicbrainz_releasetrackid" => Selector::MusicbrainzReleaseTrackID, + r"(?i)musicbrainz_workid" => Selector::MusicbrainzWorkID, + r"(?i)file" => Selector::File, + r"(?i)base" => Selector::Base, + r"(?i)modified-since" => Selector::ModifiedSince, + r"(?i)audioformat" => Selector::AudioFormat, + r"(?i)rating" => Selector::Rating, + r"(?i)playcount" => Selector::PlayCount, + r"(?i)lastplayed" => Selector::LastPlayed, + r"(?i)skipped" => Selector::Skipped, +}; + +pub Token: Value = { + <s:r"(-)?[0-9]+"> =>? { + debug!("matched token: ``{}''.", s); + // We need to yield a Result<Value, ParseError> + match s.parse::<usize>() { + Ok(n) => Ok(Value::Uint(n)), + Err(_) => match s.parse::<i64>() { + Ok(n) => Ok(Value::Int(n)), + Err(_) => Err( + ParseError::User { + error: "Internal parse error while parsing unsigned int" + } + ) + } + } + }, + <s:r#""([ \t'a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\"|\\')+""#> => { + debug!("matched token: ``{}''.", s); + let s = expect_quoted(s).unwrap(); + match parse_iso_8601(&mut s.as_bytes()) { + Ok(x) => Value::UnixEpoch(x), + Err(_) => Value::Text(s), + } + }, + <s:r#"'([ \t"a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\'|\\")+'"#> => { + debug!("matched token: ``{}''.", s); + let s = expect_quoted(s).unwrap(); + match parse_iso_8601(&mut s.as_bytes()) { + Ok(x) => Value::UnixEpoch(x), + Err(_) => Value::Text(s), + } + }, +}; + +pub Term: Box<Term> = { + <t:ExprSel> <o:ExprOp> <u:Token> => { + debug!("matched binary condition: ``({}, {:#?}, {:#?})''", t, o, u); + Box::new(Term::BinaryCondition(t, o, u)) + }, + <t:ExprSel> <u:Token> => { + debug!("matched unary condition: ``({}, {:#?})''", t, u); + Box::new(Term::UnaryCondition(t, u)) + }, +} + +pub Conjunction: Box<Conjunction> = { + <e1:Expression> "AND" <e2:Expression> => { + debug!("matched conjunction: ``({:#?}, {:#?})''", e1, e2); + Box::new(Conjunction::Simple(e1, e2)) + }, + <c:Conjunction> "AND" <e:Expression> => { + debug!("matched conjunction: ``({:#?}, {:#?})''", c, e); + Box::new(Conjunction::Compound(c, e)) + }, +} + +pub Disjunction: Box<Disjunction> = { + <e1:Expression> "OR" <e2:Expression> => { + debug!("matched disjunction: ``({:#?}, {:#?})''", e1, e2); + Box::new(Disjunction::Simple(e1, e2)) + }, + <c:Disjunction> "OR" <e:Expression> => { + debug!("matched disjunction: ``({:#?}, {:#?})''", c, e); + Box::new(Disjunction::Compound(c, e)) + }, +} + +pub Expression: Box<Expression> = { + "(" <t:Term> ")" => { + debug!("matched parenthesized term: ``({:#?})''", t); + Box::new(Expression::Simple(t)) + }, + "(" "!" <e:Expression> ")" => Box::new(Expression::Negation(e)), + "(" <c:Conjunction> ")" => { + debug!("matched parenthesized conjunction: ``({:#?})''", c); + Box::new(Expression::Conjunction(c)) + }, + "(" <c:Disjunction> ")" => { + debug!("matched parenthesized disjunction: ``({:#?})''", c); + Box::new(Expression::Disjunction(c)) + }, +} diff --git a/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs new file mode 100644 index 00000000..9c68d329 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs @@ -0,0 +1,1022 @@ +// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! Types for building the Abstract Syntax Tree when parsing filters +//! +//! This module provides support for our [lalrpop](https://github.com/lalrpop/lalrpop) grammar. + +use crate::clients::Client; +use crate::storage::{last_played, play_count, rating, skip_count}; + +use anyhow::{Context, Error, Result, anyhow, bail}; +use boolinator::Boolinator; +use chrono::prelude::*; +use tracing::debug; + +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; + +/// The operations that can appear in a filter term +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum OpCode { + Equality, + Inequality, + Contains, + RegexMatch, + RegexExclude, + GreaterThan, + LessThan, + GreaterThanEqual, + LessThanEqual, +} + +impl std::fmt::Display for OpCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + OpCode::Equality => "==", + OpCode::Inequality => "!=", + OpCode::Contains => "contains", + OpCode::RegexMatch => "=~", + OpCode::RegexExclude => "!~", + OpCode::GreaterThan => ">", + OpCode::LessThan => "<", + OpCode::GreaterThanEqual => ">=", + OpCode::LessThanEqual => "<=", + } + ) + } +} + +/// The song attributes that can appear on the LHS of a filter term +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Selector { + Artist, + Album, + AlbumArtist, + Title, + Track, + Name, + Genre, + Date, + OriginalDate, + Composer, + Performer, + Conductor, + Work, + Grouping, + Comment, + Disc, + Label, + MusicbrainzAristID, + MusicbrainzAlbumID, + MusicbrainzAlbumArtistID, + MusicbrainzTrackID, + MusicbrainzReleaseTrackID, + MusicbrainzWorkID, + File, + Base, + ModifiedSince, + AudioFormat, + Rating, + PlayCount, + LastPlayed, + Skipped, +} + +impl std::fmt::Display for Selector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Selector::Artist => "artist", + Selector::Album => "album", + Selector::AlbumArtist => "albumartist", + Selector::Title => "title", + Selector::Track => "track", + Selector::Name => "name", + Selector::Genre => "genre", + Selector::Date => "date", + Selector::OriginalDate => "originaldate", + Selector::Composer => "composer", + Selector::Performer => "performer", + Selector::Conductor => "conductor", + Selector::Work => "work", + Selector::Grouping => "grouping", + Selector::Comment => "comment", + Selector::Disc => "disc", + Selector::Label => "label", + Selector::MusicbrainzAristID => "musicbrainz_aristid", + Selector::MusicbrainzAlbumID => "musicbrainz_albumid", + Selector::MusicbrainzAlbumArtistID => "musicbrainz_albumartistid", + Selector::MusicbrainzTrackID => "musicbrainz_trackid", + Selector::MusicbrainzReleaseTrackID => "musicbrainz_releasetrackid", + Selector::MusicbrainzWorkID => "musicbrainz_workid", + Selector::File => "file", + Selector::Base => "base", + Selector::ModifiedSince => "modified-since", + Selector::AudioFormat => "AudioFormat", + Selector::Rating => "rating", + Selector::PlayCount => "playcount", + Selector::LastPlayed => "lastplayed", + Selector::Skipped => "skipped", + } + ) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Text(String), + UnixEpoch(i64), + Uint(usize), + Int(i64), +} + +fn quote_value(x: &Value) -> String { + match x { + Value::Text(s) => { + let mut ret = String::new(); + + ret.push('"'); + for c in s.chars() { + if c == '"' || c == '\\' { + ret.push('\\'); + } + ret.push(c); + } + ret.push('"'); + ret + } + Value::UnixEpoch(n) => { + format!("'{}'", n) + } + Value::Uint(n) => { + format!("'{}'", n) + } + Value::Int(n) => { + format!("'{}'", n) + } + } +} + +#[derive(Clone, Debug)] +pub enum Term { + UnaryCondition(Selector, Value), + BinaryCondition(Selector, OpCode, Value), +} + +#[derive(Clone, Debug)] +pub enum Conjunction { + Simple(Box<Expression>, Box<Expression>), + Compound(Box<Conjunction>, Box<Expression>), +} + +#[derive(Clone, Debug)] +pub enum Disjunction { + Simple(Box<Expression>, Box<Expression>), + Compound(Box<Disjunction>, Box<Expression>), +} + +#[derive(Clone, Debug)] +pub enum Expression { + Simple(Box<Term>), + Negation(Box<Expression>), + Conjunction(Box<Conjunction>), + Disjunction(Box<Disjunction>), +} + +#[cfg(test)] +mod smoke_tests { + use super::*; + use crate::filters::*; + + #[test] + fn test_opcodes() { + assert!(ExprOpParser::new().parse("==").unwrap() == OpCode::Equality); + assert!(ExprOpParser::new().parse("!=").unwrap() == OpCode::Inequality); + assert!(ExprOpParser::new().parse("contains").unwrap() == OpCode::Contains); + assert!(ExprOpParser::new().parse("=~").unwrap() == OpCode::RegexMatch); + assert!(ExprOpParser::new().parse("!~").unwrap() == OpCode::RegexExclude); + assert!(ExprOpParser::new().parse(">").unwrap() == OpCode::GreaterThan); + assert!(ExprOpParser::new().parse("<").unwrap() == OpCode::LessThan); + assert!(ExprOpParser::new().parse(">=").unwrap() == OpCode::GreaterThanEqual); + assert!(ExprOpParser::new().parse("<=").unwrap() == OpCode::LessThanEqual); + } + + #[test] + fn test_conditions() { + assert!(TermParser::new().parse("base 'foo'").is_ok()); + assert!(TermParser::new().parse("artist == 'foo'").is_ok()); + assert!( + TermParser::new() + .parse(r#"artist =~ "foo bar \"splat\"!""#) + .is_ok() + ); + assert!(TermParser::new().parse("artist =~ 'Pogues'").is_ok()); + + match *TermParser::new() + .parse(r#"base "/Users/me/My Music""#) + .unwrap() + { + Term::UnaryCondition(a, b) => { + assert!(a == Selector::Base); + assert!(b == Value::Text(String::from(r#"/Users/me/My Music"#))); + } + _ => { + unreachable!(); + } + } + + match *TermParser::new() + .parse(r#"artist =~ "foo bar \"splat\"!""#) + .unwrap() + { + Term::BinaryCondition(t, op, s) => { + assert!(t == Selector::Artist); + assert!(op == OpCode::RegexMatch); + assert!(s == Value::Text(String::from(r#"foo bar "splat"!"#))); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_expressions() { + assert!(ExpressionParser::new().parse("( base 'foo' )").is_ok()); + assert!(ExpressionParser::new().parse("(base \"foo\")").is_ok()); + assert!( + ExpressionParser::new() + .parse("(!(artist == 'value'))") + .is_ok() + ); + assert!( + ExpressionParser::new() + .parse(r#"((!(artist == "foo bar")) AND (base "/My Music"))"#) + .is_ok() + ); + } + + #[test] + fn test_quoted_expr() { + eprintln!("test_quoted_expr"); + assert!( + ExpressionParser::new() + .parse(r#"(artist =~ "foo\\bar\"")"#) + .is_ok() + ); + } + + #[test] + fn test_real_expression() { + let result = ExpressionParser::new() + .parse(r#"(((Artist =~ 'Flogging Molly') OR (artist =~ 'Dropkick Murphys') OR (artist =~ 'Pogues')) AND ((rating > 128) OR (rating == 0)))"#); + eprintln!("{:#?}", result); + assert!(result.is_ok()); + } + + #[test] + fn test_conjunction() { + assert!(ExpressionParser::new() + .parse( + r#"((base "foo") AND (artist == "foo bar") AND (!(file == '/net/mp3/A/a.mp3')))"# + ) + .is_ok()); + + eprintln!("=============================================================================="); + eprintln!("{:#?}", ExpressionParser::new() + .parse( + r#"((base 'foo') AND (artist == "foo bar") AND ((!(file == "/net/mp3/A/a.mp3")) OR (file == "/pub/mp3/A/a.mp3")))"# + )); + assert!(ExpressionParser::new() + .parse( + r#"((base 'foo') AND (artist == "foo bar") AND ((!(file == '/net/mp3/A/a.mp3')) OR (file == '/pub/mp3/A/a.mp3')))"# + ) + .is_ok()); + } + + #[test] + fn test_disjunction() { + assert!(ExpressionParser::new(). + parse(r#"((artist =~ 'Flogging Molly') OR (artist =~ 'Dropkick Murphys') OR (artist =~ 'Pogues'))"#) + .is_ok()); + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum EvalOp { + And, + Or, + Not, +} + +impl std::fmt::Display for EvalOp { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + EvalOp::And => write!(f, "And"), + EvalOp::Or => write!(f, "Or"), + EvalOp::Not => write!(f, "Not"), + } + } +} + +fn peek(buf: &[u8]) -> Option<char> { + match buf.len() { + 0 => None, + _ => Some(buf[0] as char), + } +} + +// advancing a slice by `i` indicies can *not* be this difficult +/// Pop a single byte off of `buf` +fn take1(buf: &mut &[u8], i: usize) -> Result<()> { + if i > buf.len() { + bail!("Bad iso-8601 string: `{:#?}`", buf); + } + let (_first, second) = buf.split_at(i); + *buf = second; + Ok(()) +} + +/// Pop `i` bytes off of `buf` & parse them as a T +fn take2<T>(buf: &mut &[u8], i: usize) -> Result<T> +where + T: FromStr, + <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static, +{ + // 1. check len + if i > buf.len() { + bail!("Bad iso-8601 string: `{:#?}`", buf); + } + + let (first, second) = buf.split_at(i); + *buf = second; + // 2. convert to a string + let s = std::str::from_utf8(first).context("Bad iso-8601 string")?; + // 3. parse as a T + s.parse::<T>() + .context("Failed to parse iso-8601 string as T") +} + +/// Parse a timestamp in ISO 8601 format to a chrono DateTime instance +/// +/// Surprisingly, I was unable to find an ISO 8601 parser in Rust. I *did* find a crate named +/// iso-8601 that promised to do this, but it seemed derelict & I couldn't see what to do with the +/// parse output in any event. The ISO 8601 format is simple enough that I've chosen to simply +/// hand-parse it. +pub fn parse_iso_8601(buf: &mut &[u8]) -> Result<i64> { + // I wonder if `nom` would be a better choice? + + // The first four characters must be the year (expanded year representation is not supported by + // this parser). + + let year: i32 = take2(buf, 4)?; + + // Now at this point: + // 1. we may be done (i.e. buf.len() == 0) + // 2. we may have the timestamp (peek(buf) => Some('T')) + // - day & month := 0, consume the 'T', move on to parsing the time + // 3. we may have a month in extended format (i.e. peek(buf) => Some('-') + // - consume the '-', parse the month & move on to parsing the day + // 4. we may have a month in basic format (take(buf, 2) => Some('\d\d') + // - parse the month & move on to parsing the day + let mut month = 1; + let mut day = 1; + let mut hour = 0; + let mut minute = 0; + let mut second = 0; + if !buf.is_empty() { + let next = peek(buf); + if next != Some('T') { + let mut ext_fmt = false; + if next == Some('-') { + take1(buf, 1)?; + ext_fmt = true; + } + month = take2(buf, 2)?; + + // At this point: + // 1. we may be done (i.e. buf.len() == 0) + // 2. we may have the timestamp (peek(buf) => Some('T')) + // 3. we may have the day (in basic or extended format) + if !buf.is_empty() && peek(buf) != Some('T') { + if ext_fmt { + take1(buf, 1)?; + } + day = take2(buf, 2)?; + } + } + + // Parse time: at this point, buf will either be empty or begin with 'T' + if !buf.is_empty() { + take1(buf, 1)?; + // If there's a T, there must at least be an hour + hour = take2(buf, 2)?; + if !buf.is_empty() { + let mut ext_fmt = false; + if peek(buf) == Some(':') { + take1(buf, 1)?; + ext_fmt = true; + } + minute = take2(buf, 2)?; + if !buf.is_empty() { + if ext_fmt { + take1(buf, 1)?; + } + second = take2(buf, 2)?; + } + } + } + + // At this point, there may be a timezone + if !buf.is_empty() { + if peek(buf) == Some('Z') { + return Ok(Utc + .with_ymd_and_hms(year, month, day, hour, minute, second) + .single() + .ok_or(anyhow!("bad iso-8601 string"))? + .timestamp()); + } else { + let next = peek(buf); + if next != Some('-') && next != Some('+') { + bail!("bad iso-8601 string") + } + let west = next == Some('-'); + take1(buf, 1)?; + + let hours: i32 = take2(buf, 2)?; + let mut minutes = 0; + + if !buf.is_empty() { + if peek(buf) == Some(':') { + take1(buf, 1)?; + } + minutes = take2(buf, 2)?; + } + + if west { + return Ok(FixedOffset::west_opt(hours * 3600 + minutes * 60) + .ok_or(anyhow!("Bad iso-8601 string"))? + .with_ymd_and_hms(year, month, day, hour, minute, second) + .single() + .ok_or(anyhow!("Bad iso-8601 string"))? + .timestamp()); + } else { + return Ok(FixedOffset::east_opt(hours * 3600 + minutes * 60) + .ok_or(anyhow!("Bad iso-8601 string"))? + .with_ymd_and_hms(year, month, day, hour, minute, second) + .single() + .ok_or(anyhow!("Bad iso-8601 string"))? + .timestamp()); + } + } + } + } + Ok(Local + .with_ymd_and_hms(year, month, day, hour, minute, second) + .single() + .ok_or(anyhow!("Bad iso-8601 string"))? + .timestamp()) +} + +#[cfg(test)] +mod iso_8601_tests { + + use super::*; + + #[test] + fn smoke_tests() { + let mut b = "19700101T00:00:00Z".as_bytes(); + let t = parse_iso_8601(&mut b).unwrap(); + assert!(t == 0); + + let mut b = "19700101T00:00:01Z".as_bytes(); + let t = parse_iso_8601(&mut b).unwrap(); + assert!(t == 1); + + let mut b = "20210327T02:26:53Z".as_bytes(); + let t = parse_iso_8601(&mut b).unwrap(); + assert_eq!(t, 1616812013); + + let mut b = "20210327T07:29:05-07:00".as_bytes(); + let t = parse_iso_8601(&mut b).unwrap(); + assert_eq!(t, 1616855345); + + let mut b = "2021".as_bytes(); + // Should resolve to midnight, Jan 1 2021 in local time; don't want to test against the + // timestamp; just make sure it parses + parse_iso_8601(&mut b).unwrap(); + } +} + +/// "Un-quote" a token +/// +/// Textual tokens must be quoted, and double-quote & backslashes within backslash-escaped. If the +/// string is quoted with single-quotes, then any single-quotes inside the string will also need +/// to be escaped. +/// +/// In fact, *any* characters within may be harmlessly backslash escaped; the MPD implementation +/// walks the the string, skipping backslashes as it goes, so this implementation will do the same. +/// I have named this method in imitation of the corresponding MPD function. +pub fn expect_quoted(qtext: &str) -> Result<String> { + let mut iter = qtext.chars(); + let quote = iter.next(); + if quote.is_none() { + return Ok(String::new()); + } + + if quote != Some('\'') && quote != Some('"') { + bail!("Expected text to be quoted: `{}`", qtext); + } + + let mut ret = String::new(); + + // Walk qtext[1..]; copying characters to `ret'. If a '\' is found, skip to the next character + // (even if that is a '\'). The last character in qtext should be the closing quote. + let mut this = iter.next(); + while this != quote { + if this == Some('\\') { + this = iter.next(); + } + match this { + Some(c) => ret.push(c), + None => { + bail!("Expected text to be quoted: `{}`", qtext); + } + } + this = iter.next(); + } + + Ok(ret) +} + +#[cfg(test)] +mod quoted_tests { + + use super::*; + + #[test] + fn smoke_tests() { + let b = r#""foo bar \"splat!\"""#; + let s = expect_quoted(b).unwrap(); + assert!(s == r#"foo bar "splat!""#); + } +} + +/// Create a closure that will carry out an operator on its argument +/// +/// Call this function with an [OpCode] and a value of type `T`. `T` must be [PartialEq], +/// [`PartialOrd`] and [`Copy`]-- an integral type will do. It will return a closure that will carry +/// out the given [OpCode] against the given value. For instance, +/// `make_numeric_closure::<u8>(OpCode::Equality, 11)` will return a closure that takes a `u8` & +/// will return true if its argument is 11 (and false otherwise). +/// +/// If [OpCode] is not pertinent to a numeric type, then this function will return Err. +fn make_numeric_closure<'a, T: 'a + PartialEq + PartialOrd + Copy>( + op: OpCode, + val: T, +) -> Result<impl Fn(T) -> bool + 'a> { + // Rust closures each have their own type, so this was the only way I could find to + // return them from match arms. This seems ugly; perhaps there's something I'm + // missing. + // + // I have no idea why I have to make these `move` closures; T is constrained to by Copy-able, + // so I would have expected the closure to just take a copy. + match op { + OpCode::Equality => Ok(Box::new(move |x: T| x == val) as Box<dyn Fn(T) -> bool>), + OpCode::Inequality => Ok(Box::new(move |x: T| x != val) as Box<dyn Fn(T) -> bool>), + OpCode::GreaterThan => Ok(Box::new(move |x: T| x > val) as Box<dyn Fn(T) -> bool>), + OpCode::LessThan => Ok(Box::new(move |x: T| x < val) as Box<dyn Fn(T) -> bool>), + OpCode::GreaterThanEqual => Ok(Box::new(move |x: T| x >= val) as Box<dyn Fn(T) -> bool>), + OpCode::LessThanEqual => Ok(Box::new(move |x: T| x <= val) as Box<dyn Fn(T) -> bool>), + _ => bail!("Invalid operant: `{op}`"), + } +} + +async fn eval_numeric_sticker_term< + // The `FromStr' trait bound is really weird, but if I don't constrain the associated + // Err type to be `ParseIntError' the compiler complains about not being able to convert + // it to type `Error'. I'm probably still "thinking in C++" and imagining the compiler + // instantiating this function for each type (u8, usize, &c) instead of realizing that the Rust + // compiler is processing this as a first-class function. + // + // For instance, I can do the conversion manually, so long as I constrain the Err type + // to implement std::error::Error. I should probably be doing that, but it clutters the + // code. I'll figure it out when I need to extend this function to handle non-integral types + // :) + T: PartialEq + PartialOrd + Copy + FromStr<Err = std::num::ParseIntError> + std::fmt::Display, +>( + sticker: &str, + client: &mut Client, + op: OpCode, + numeric_val: T, + default_val: T, +) -> Result<HashSet<String>> { + let cmp = make_numeric_closure(op, numeric_val)?; + // It would be better to idle on the sticker DB & just update our collection on change, but for + // a first impl. this will do. + // + // Call `get_stickers'; this will return a HashMap from song URIs to ratings expressed as text + // (as all stickers are). This stanza will drain that collection into a new one with the ratings + // expressed as T. + // + // The point is that conversion from text to rating, lastplayed, or whatever can fail; the + // invocation of `collect' will call `from_iter' to convert a collection of Result-s to a Result + // of a collection. + let mut m = client + .get_stickers(sticker) + .await + .context("Failed to get stickers from client")? + .drain() + .map(|(k, v)| v.parse::<T>().map(|x| (k, x))) + .collect::<std::result::Result<HashMap<String, T>, _>>() + .context("Failed to parse sticker as T")?; + // `m' is now a map of song URI to rating/playcount/wathever (expressed as a T)... for all songs + // that have the salient sticker. + // + // This seems horribly inefficient, but I'm going to fetch all the song URIs in the music DB, + // and augment `m' with entries of `default_val' for any that are not already there. + client + .get_all_songs() + .await + .context("Failed to get all songs from client")? + .drain(..) + .for_each(|song| { + m.entry(song).or_insert(default_val); + }); + + // Now that we don't have to worry about operations that can fail, we can use + // `filter_map'. + Ok(m.drain() + .filter_map(|(k, v)| cmp(v).as_some(k)) + .collect::<HashSet<String>>()) +} + +/// Convenience struct collecting the names for assorted stickers on which one may search +/// +/// While the search terms 'rating', 'playcount' &c are fixed & part of the filter grammar offered +/// by mpdpopm, the precise names of the corresponding stickers are configurable & hence must be +/// passed in. Three references to str is already unweildy IMO, and since I expect the number of +/// stickers on which one can search to grow further, I decided to wrap 'em up in a struct. The +/// lifetime is there to support the caller just using a reference to an existing string rather than +/// making a copy. +pub struct FilterStickerNames<'a> { + rating: &'a str, + playcount: &'a str, + lastplayed: &'a str, + skipped: &'a str, +} + +impl FilterStickerNames<'static> { + pub fn new() -> FilterStickerNames<'static> { + Self::default() + } +} + +impl Default for FilterStickerNames<'static> { + fn default() -> Self { + Self { + rating: rating::STICKER, + playcount: play_count::STICKER, + lastplayed: last_played::STICKER, + skipped: skip_count::STICKER, + } + } +} + +/// Evaluate a Term +/// +/// Take a Term from the Abstract Syntax tree & resolve it to a collection of song URIs. Set `case` +/// to `true` to search case-sensitively & `false` to make the search case-insensitive. +async fn eval_term<'a>( + term: &Term, + case: bool, + client: &mut Client, + stickers: &FilterStickerNames<'a>, +) -> Result<HashSet<String>> { + match term { + Term::UnaryCondition(op, val) => Ok(client + .find1(&format!("{}", op), "e_value(val), case) + .await + .context("Failed to find1 on client")? + .drain(..) + .collect()), + Term::BinaryCondition(attr, op, val) => { + if *attr == Selector::Rating { + let value = match val { + Value::Int(n) => *n as i128, + Value::Uint(n) => *n as i128, + _ => bail!("filter ratings expect an int; got {:#?}", val), + }; + + let val: i8 = value.try_into().with_context(|| { + format!( + "Failed to convert `{}` into a number from -128 to 128!", + value + ) + })?; + Ok(eval_numeric_sticker_term(stickers.rating, client, *op, val, 0).await?) + } else if *attr == Selector::PlayCount { + match val { + Value::Uint(n) => { + Ok( + eval_numeric_sticker_term(stickers.playcount, client, *op, *n, 0) + .await?, + ) + } + _ => bail!("filter play_count expect an unsigned int; got {:#?}", val), + } + } else if *attr == Selector::LastPlayed { + match val { + Value::UnixEpoch(t) => { + Ok( + eval_numeric_sticker_term(stickers.lastplayed, client, *op, *t, 0) + .await?, + ) + } + _ => bail!("filter last_played expect an unix epoch; got {:#?}", val), + } + } else if *attr == Selector::Skipped { + match val { + Value::Uint(t) => { + Ok(eval_numeric_sticker_term(stickers.skipped, client, *op, *t, 0).await?) + } + _ => bail!("filter skipped expect an unsigned int; got {:#?}", val), + } + } else { + Ok(client + .find2( + &format!("{}", attr), + &format!("{}", op), + "e_value(val), + case, + ) + .await + .context("Failed to `find2` on client")? + .drain(..) + .collect()) + } + } + } +} + +/// The evaluation stack contains logical operators & sets of song URIs +#[derive(Debug)] +enum EvalStackNode { + Op(EvalOp), + Result(HashSet<String>), +} + +async fn negate_result( + res: &HashSet<String>, + client: &mut Client, +) -> std::result::Result<HashSet<String>, Error> { + Ok(client + .get_all_songs() + .await + .context("Failed to get all songs from client")? + .drain(..) + .filter_map(|song| { + // Some(thing) adds thing, None elides it + if !res.contains(&song) { + Some(song) + } else { + None + } + }) + .collect::<HashSet<String>>()) +} + +/// Reduce the evaluation stack as far as possible. +/// +/// We can pop the stack in two cases: +/// +/// 1. S.len() > 2 and S[-3] is either And or Or, and both S[-1] & S[-2] are Result-s +/// 2. S.len() > 1, S[-2] is Not, and S[-1] is a Result +async fn reduce(stack: &mut Vec<EvalStackNode>, client: &mut Client) -> Result<()> { + loop { + let mut reduced = false; + let n = stack.len(); + if n > 1 { + // Take care to compute the reduction *before* popping the stack-- thank you, borrow + // checker! + let reduction = if let (EvalStackNode::Op(EvalOp::Not), EvalStackNode::Result(r)) = + (&stack[n - 2], &stack[n - 1]) + { + Some(negate_result(r, client).await?) + } else { + None + }; + + if let Some(res) = reduction { + stack.pop(); + stack.pop(); + stack.push(EvalStackNode::Result(res)); + reduced = true; + } + } + let n = stack.len(); + if n > 2 { + // Take care to compute the reduction *before* popping the stack-- thank you, borrow + // checker! + let and_reduction = if let ( + EvalStackNode::Op(EvalOp::And), + EvalStackNode::Result(r1), + EvalStackNode::Result(r2), + ) = (&stack[n - 3], &stack[n - 2], &stack[n - 1]) + { + Some(r1.intersection(r2).cloned().collect()) + } else { + None + }; + + if let Some(res) = and_reduction { + stack.pop(); + stack.pop(); + stack.pop(); + stack.push(EvalStackNode::Result(res)); + reduced = true; + } + } + let n = stack.len(); + if n > 2 { + let or_reduction = if let ( + EvalStackNode::Op(EvalOp::Or), + EvalStackNode::Result(r1), + EvalStackNode::Result(r2), + ) = (&stack[n - 3], &stack[n - 2], &stack[n - 1]) + { + Some(r1.union(r2).cloned().collect()) + } else { + None + }; + + if let Some(res) = or_reduction { + stack.pop(); + stack.pop(); + stack.pop(); + stack.push(EvalStackNode::Result(res)); + reduced = true; + } + } + + if !reduced { + break; + } + } + + Ok(()) +} + +/// Evaluate an abstract syntax tree (AST) +pub async fn evaluate<'a>( + expr: &Expression, + case: bool, + client: &mut Client, + stickers: &FilterStickerNames<'a>, +) -> Result<HashSet<String>> { + // We maintain *two* stacks, one for parsing & one for evaluation. Let sp (for "stack(parse)") + // be a stack of references to nodes in the parse tree. + let mut sp = Vec::new(); + // Initialize it with the root; as we walk the tree, we'll pop the "most recent" node, and push + // children. + sp.push(expr); + + // Let se (for "stack(eval)") be a stack of operators & URIs. + let mut se = Vec::new(); + + // Simple DFS traversal of the AST: + while let Some(node) = sp.pop() { + // and dispatch based on what we've got: + match node { + // 1. we have a simple term: this can be immediately resolved to a set of song URIs. Do + // so & push the resulting set onto the evaluation stack. + Expression::Simple(bt) => se.push(EvalStackNode::Result( + eval_term(bt, case, client, stickers).await?, + )), + // 2. we have a negation: push the "not" operator onto the evaluation stack & the child + // onto the parse stack. + Expression::Negation(be) => { + se.push(EvalStackNode::Op(EvalOp::Not)); + sp.push(be); + } + // 3. conjunction-- push the "and" operator onto the evaluation stack & the children + // onto the parse stack (be sure to push the right-hand child first, so it will be + // popped second) + // bc is &Box<Conjunction<'a>>, so &**bc is &Conjunction<'a> + Expression::Conjunction(bc) => { + let mut conj = &**bc; + loop { + match conj { + Conjunction::Simple(bel, ber) => { + se.push(EvalStackNode::Op(EvalOp::And)); + sp.push(&**ber); + sp.push(&**bel); + break; + } + Conjunction::Compound(bc, be) => { + se.push(EvalStackNode::Op(EvalOp::And)); + sp.push(&**be); + conj = bc; + } + } + } + } + Expression::Disjunction(bt) => { + let mut disj = &**bt; + loop { + match disj { + Disjunction::Simple(bel, ber) => { + se.push(EvalStackNode::Op(EvalOp::Or)); + sp.push(ber); + sp.push(bel); + break; + } + Disjunction::Compound(bd, be) => { + se.push(EvalStackNode::Op(EvalOp::Or)); + sp.push(&**be); + disj = bd; + } + } + } + } + } + + reduce(&mut se, client).await?; + } + + // At this point, sp is empty, but there had better be something on se. Keep reducing the stack + // until either we can't any further (in which case we error) or there is only one element left + // (in which case we return that). + reduce(&mut se, client).await?; + + // Now, se had better have one element, and that element had better be a Result. + if 1 != se.len() { + debug!("Too many ({}) operands left on stack:", se.len()); + se.iter() + .enumerate() + .for_each(|(i, x)| debug!(" {}: {:#?}", i, x)); + bail!("The number of operants is too big `{}`", se.len()); + } + + let ret = se.pop().unwrap(); + match ret { + EvalStackNode::Result(result) => Ok(result), + EvalStackNode::Op(op) => { + debug!("Operator left on stack (!?): {:#?}", op); + bail!("Operator left on stack: {op}") + } + } +} + +#[cfg(test)] +mod evaluation_tests { + + use super::*; + use crate::filters::*; + + use crate::clients::Client; + use crate::clients::test_mock::Mock; + + #[tokio::test] + async fn smoke() { + let mock = Box::new(Mock::new(&[( + r#"find "(base \"foo\")""#, + "file: foo/a.mp3 +Artist: The Foobars +file: foo/b.mp3 +Title: b! +OK", + )])); + let mut cli = Client::new(mock).unwrap(); + + let stickers = FilterStickerNames::new(); + + let expr = ExpressionParser::new().parse(r#"(base "foo")"#).unwrap(); + let result = evaluate(&expr, true, &mut cli, &stickers).await; + assert!(result.is_ok()); + + let g: HashSet<String> = ["foo/a.mp3", "foo/b.mp3"] + .iter() + .map(|x| x.to_string()) + .collect(); + assert!(result.unwrap() == g); + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs new file mode 100644 index 00000000..cc2765dc --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs @@ -0,0 +1,185 @@ +// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! # mpdpopm +//! +//! Maintain ratings & playcounts for your mpd server. +//! +//! # Introduction +//! +//! This is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts & +//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust +//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as +//! the sticker database, by invoking external commands to keep your tags up-to-date (something +//! along the lines of [mpdcron](https://alip.github.io/mpdcron)). +//! +//! # Commands +//! +//! I'm currently sending all commands over one (configurable) channel. +//! + +#![recursion_limit = "512"] // for the `select!' macro + +pub mod clients; +pub mod config; +pub mod dj; +pub mod filters_ast; +pub mod messanges; +pub mod playcounts; +pub mod storage; +pub mod vars; + +#[rustfmt::skip] +#[allow(clippy::extra_unused_lifetimes)] +#[allow(clippy::needless_lifetimes)] +#[allow(clippy::let_unit_value)] +#[allow(clippy::just_underscores_and_digits)] +pub mod filters { + include!(concat!(env!("OUT_DIR"), "/src/filters.rs")); +} + +use crate::{ + clients::{Client, IdleClient, IdleSubSystem}, + config::{Config, Connection}, + messanges::MessageQueue, + playcounts::PlayState, +}; + +use anyhow::{Context, Error}; +use futures::{future::FutureExt, pin_mut, select}; +use tokio::{ + signal, + signal::unix::{SignalKind, signal}, + time::{Duration, sleep}, +}; +use tracing::{debug, error, info}; + +/// Core `mppopmd' logic +pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> { + info!("mpdpopm {} beginning.", vars::VERSION); + + let mut client = + match cfg.conn { + Connection::Local { ref path } => Client::open(path) + .await + .with_context(|| format!("Failed to open socket at `{}`", path.display()))?, + Connection::TCP { ref host, port } => Client::connect(format!("{}:{}", host, port)) + .await + .with_context(|| format!("Failed to connect to client at `{}:{}`", host, port))?, + }; + + let mut state = PlayState::new(&mut client, cfg.played_thresh) + .await + .context("Failed to construct PlayState")?; + + let mut idle_client = match cfg.conn { + Connection::Local { ref path } => IdleClient::open(path) + .await + .context("Failed to open idle client")?, + Connection::TCP { ref host, port } => IdleClient::connect(format!("{}:{}", host, port)) + .await + .context("Failed to connect to TCP idle client")?, + }; + + let mut mqueue = MessageQueue::new(); + + idle_client + .subscribe(&cfg.commands_chan) + .await + .context("Failed to subscribe to idle_client")?; + + let mut hup = signal(SignalKind::hangup()).unwrap(); + let mut kill = signal(SignalKind::terminate()).unwrap(); + let ctrl_c = signal::ctrl_c().fuse(); + + let sighup = hup.recv().fuse(); + let sigkill = kill.recv().fuse(); + + let tick = sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse(); + pin_mut!(ctrl_c, sighup, sigkill, tick); + + let mut done = false; + let mut msg_check_needed = false; + while !done { + debug!("selecting..."); + { + // `idle_client' mutably borrowed here + let mut idle = Box::pin(idle_client.idle().fuse()); + loop { + select! { + _ = ctrl_c => { + info!("got ctrl-C"); + done = true; + break; + }, + _ = sighup => { + info!("got SIGHUP"); + done = true; + break; + }, + _ = sigkill => { + info!("got SIGKILL"); + done = true; + break; + }, + _ = tick => { + tick.set(sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse()); + state.update(&mut client) + .await + .context("PlayState update failed")?; + }, + res = idle => match res { + Ok(subsys) => { + debug!("subsystem {} changed", subsys); + if subsys == IdleSubSystem::Player { + state.update(&mut client) + .await + .context("PlayState update failed")?; + + mqueue + .advance_dj(&mut client) + .await + .context("MessageQueue tick failed")?; + } else if subsys == IdleSubSystem::Message { + msg_check_needed = true; + } + break; + }, + Err(err) => { + debug!("error {err:#?} on idle"); + done = true; + break; + } + } + } + } + } + + if msg_check_needed { + msg_check_needed = false; + + // Check for any messages that have come in; if there's an error there's not a lot we + // can do about it (suppose some client fat-fingers a command name, e.g.)-- just log it + // & move on. + if let Err(err) = mqueue.check_messages(&mut client, &mut idle_client).await { + error!("Error while processing messages: {err:#?}"); + } + } + } + + info!("mpdpopm exiting."); + + Ok(()) +} diff --git a/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs b/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs new file mode 100644 index 00000000..c5320dd9 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs @@ -0,0 +1,110 @@ +use anyhow::{Context, Result, anyhow, bail, ensure}; +use clap::{Parser, Subcommand}; +use shlex::Shlex; +use tracing::info; + +use crate::{ + clients::{Client, IdleClient}, + dj::{Dj, algorithms::Discovery}, +}; + +pub const COMMAND_CHANNEL: &str = "unwoundstack.com:commands"; + +#[derive(Parser)] +struct Commands { + #[command(subcommand)] + command: SubCommand, +} + +#[derive(Parser)] +enum SubCommand { + Dj { + #[command(subcommand)] + command: DjCommand, + }, +} + +#[derive(Subcommand)] +enum DjCommand { + Start {}, + Stop {}, +} + +pub(crate) struct MessageQueue { + dj: Option<Dj<Discovery>>, +} + +impl MessageQueue { + pub(crate) fn new() -> Self { + Self { dj: None } + } + + pub(crate) async fn advance_dj(&mut self, client: &mut Client) -> Result<()> { + if let Some(dj) = self.dj.as_mut() { + dj.add_track(client).await?; + } + + Ok(()) + } + + /// Read messages off the commands channel & dispatch 'em + pub(crate) async fn check_messages( + &mut self, + client: &mut Client, + idle_client: &mut IdleClient, + ) -> Result<()> { + let m = idle_client + .get_messages() + .await + .context("Failed to `get_messages` from client")?; + + for (chan, msgs) in m { + ensure!(chan == COMMAND_CHANNEL, "Unknown channel: `{}`", chan); + + for msg in msgs { + self.process(client, msg).await?; + } + } + + Ok(()) + } + + /// Process a single command + pub(crate) async fn process(&mut self, client: &mut Client, msg: String) -> Result<()> { + let split = { + let mut shl = Shlex::new(&msg); + let res: Vec<_> = shl.by_ref().collect(); + + if shl.had_error { + bail!("Failed to parse command '{msg}'") + } + + assert_eq!(shl.line_no, 1, "A unexpected newline appeared"); + assert!(!res.is_empty()); + + let mut base = vec!["base".to_owned()]; + base.extend(res); + base + }; + + let args = Commands::parse_from(split); + + match args.command { + SubCommand::Dj { command } => match command { + DjCommand::Start {} => { + info!("Dj started"); + self.dj = Some(Dj::new(Discovery::new())); + self.advance_dj(client).await?; + } + DjCommand::Stop {} => { + self.dj + .take() + .ok_or_else(|| anyhow!("Tried to disable already disabled dj mode"))?; + info!("Dj stopped"); + } + }, + } + + Ok(()) + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/playcounts.rs b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs new file mode 100644 index 00000000..417b3e7e --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs @@ -0,0 +1,313 @@ +// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> +// +// This file is part of mpdpopm. +// +// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with mpdpopm. If not, +// see <http://www.gnu.org/licenses/>. + +//! playcounts -- managing play counts & lastplayed times +//! +//! # Introduction +//! +//! Play counts & last played timestamps are maintained so long as [PlayState::update] is called +//! regularly (every few seconds, say). For purposes of library maintenance, however, they can be +//! set explicitly: +//! +//! - `setpc PLAYCOUNT( TRACK)?` +//! - `setlp LASTPLAYED( TRACK)?` +//! + +use crate::clients::{Client, PlayerStatus}; +use crate::storage::{last_played, play_count, skip_count}; + +use anyhow::{Context, Error, Result, anyhow}; +use tracing::{debug, info}; + +use std::time::SystemTime; + +/// Current server state in terms of the play status (stopped/paused/playing, current track, elapsed +/// time in current track, &c) +#[derive(Debug)] +pub struct PlayState { + /// Last known server status + last_server_stat: PlayerStatus, + + /// true if we have already incremented the last known track's playcount + have_incr_play_count: bool, + + /// Percentage threshold, expressed as a number between zero & one, for considering a song to + /// have been played + played_thresh: f64, + last_song_was_skipped: bool, +} + +impl PlayState { + /// Create a new PlayState instance; async because it will reach out to the mpd server + /// to get current status. + pub async fn new( + client: &mut Client, + played_thresh: f64, + ) -> std::result::Result<PlayState, Error> { + Ok(PlayState { + last_server_stat: client.status().await?, + have_incr_play_count: false, + last_song_was_skipped: false, + played_thresh, + }) + } + + /// Retrieve a copy of the last known player status + pub fn last_status(&self) -> PlayerStatus { + self.last_server_stat.clone() + } + + /// Poll the server-- update our status; maybe increment the current track's play count; the + /// caller must arrange to have this method invoked periodically to keep our state fresh + pub async fn update(&mut self, client: &mut Client) -> Result<()> { + let new_stat = client + .status() + .await + .context("Failed to get client status")?; + + match (&self.last_server_stat, &new_stat) { + (PlayerStatus::Play(last), PlayerStatus::Play(curr)) + | (PlayerStatus::Pause(last), PlayerStatus::Play(curr)) + | (PlayerStatus::Play(last), PlayerStatus::Pause(curr)) + | (PlayerStatus::Pause(last), PlayerStatus::Pause(curr)) => { + // Last we knew, we were playing, and we're playing now. + if last.songid != curr.songid { + debug!("New songid-- resetting PC incremented flag."); + + if !self.have_incr_play_count { + // We didn't mark the previous song as played. + // As such, the user must have skipped it :( + self.last_song_was_skipped = true; + } + + self.have_incr_play_count = false; + } else if last.elapsed > curr.elapsed + && self.have_incr_play_count + && curr.elapsed / curr.duration <= 0.1 + { + debug!("Re-play-- resetting PC incremented flag."); + self.have_incr_play_count = false; + } + } + (PlayerStatus::Stopped, PlayerStatus::Play(_)) + | (PlayerStatus::Stopped, PlayerStatus::Pause(_)) + | (PlayerStatus::Pause(_), PlayerStatus::Stopped) + | (PlayerStatus::Play(_), PlayerStatus::Stopped) => { + self.have_incr_play_count = false; + } + (PlayerStatus::Stopped, PlayerStatus::Stopped) => (), + } + + match &new_stat { + PlayerStatus::Play(curr) => { + let pct = curr.played_pct(); + debug!("Updating status: {:.3}% complete.", 100.0 * pct); + if !self.have_incr_play_count && pct >= self.played_thresh { + info!( + "Increment play count for '{}' (songid: {}) at {} played.", + curr.file.display(), + curr.songid, + curr.elapsed / curr.duration + ); + + let file = curr.file.to_str().ok_or_else(|| { + anyhow!("Failed to parse path as utf8: `{}`", curr.file.display()) + })?; + + let curr_pc = play_count::get(client, file).await?.unwrap_or_default(); + + debug!("Current PC is {}.", curr_pc); + + last_played::set( + client, + file, + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .context("Failed to get system time")? + .as_secs(), + ) + .await?; + self.have_incr_play_count = true; + + play_count::set(client, file, curr_pc + 1).await?; + } else if self.last_song_was_skipped { + self.last_song_was_skipped = false; + let last = self + .last_server_stat + .current_song() + .expect("To exist, as it was skipped"); + + info!( + "Marking '{}' (songid: {}) as skipped at {}.", + last.file.display(), + last.songid, + last.elapsed / last.duration + ); + + let file = last.file.to_str().ok_or_else(|| { + anyhow!("Failed to parse path as utf8: `{}`", last.file.display()) + })?; + + let skip_count = skip_count::get(client, file).await?.unwrap_or_default(); + skip_count::set(client, file, skip_count + 1).await?; + } + } + PlayerStatus::Pause(_) | PlayerStatus::Stopped => (), + }; + + self.last_server_stat = new_stat; + Ok(()) // No need to update the DB + } +} + +#[cfg(test)] +mod player_state_tests { + use super::*; + use crate::clients::test_mock::Mock; + + /// "Smoke" tests for player state + #[tokio::test] + async fn player_state_smoke() { + let mock = Box::new(Mock::new(&[ + ( + "status", + "repeat: 0 +random: 1 +single: 0 +consume: 1 +playlist: 2 +playlistlength: 66 +mixrampdb: 0.000000 +state: stop +xfade: 5 +song: 51 +songid: 52 +nextsong: 11 +nextsongid: 12 +OK +", + ), + ( + "status", + "volume: 100 +repeat: 0 +random: 1 +single: 0 +consume: 1 +playlist: 2 +playlistlength: 66 +mixrampdb: 0.000000 +state: play +xfade: 5 +song: 51 +songid: 52 +time: 5:228 +elapsed: 5.337 +bitrate: 192 +duration: 227.637 +audio: 44100:24:2 +nextsong: 11 +nextsongid: 12 +OK +", + ), + ( + "playlistid 52", + "file: E/Enya - Wild Child.mp3 +Last-Modified: 2008-11-09T00:06:30Z +Artist: Enya +Title: Wild Child +Album: A Day Without Rain (Japanese Retail) +Date: 2000 +Genre: Celtic +Time: 228 +duration: 227.637 +Pos: 51 +Id: 52 +OK +", + ), + ( + "status", + "volume: 100 +repeat: 0 +random: 1 +single: 0 +consume: 1 +playlist: 2 +playlistlength: 66 +mixrampdb: 0.000000 +state: play +xfade: 5 +song: 51 +songid: 52 +time: 5:228 +elapsed: 200 +bitrate: 192 +duration: 227.637 +audio: 44100:24:2 +nextsong: 11 +nextsongid: 12 +OK +", + ), + ( + "playlistid 52", + "file: E/Enya - Wild Child.mp3 +Last-Modified: 2008-11-09T00:06:30Z +Artist: Enya +Title: Wild Child +Album: A Day Without Rain (Japanese Retail) +Date: 2000 +Genre: Celtic +Time: 228 +duration: 227.637 +Pos: 51 +Id: 52 +OK +", + ), + ( + "sticker get song \"E/Enya - Wild Child.mp3\" unwoundstack.com:playcount", + "sticker: unwoundstack.com:playcount=11\nOK\n", + ), + ( + &format!( + "sticker set song \"E/Enya - Wild Child.mp3\" unwoundstack.com:lastplayed {}", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + ), + "OK\n", + ), + ( + "sticker set song \"E/Enya - Wild Child.mp3\" unwoundstack.com:playcount 12", + "OK\n", + ), + ])); + + let mut cli = Client::new(mock).unwrap(); + let mut ps = PlayState::new(&mut cli, 0.6).await.unwrap(); + let check = match ps.last_status() { + PlayerStatus::Play(_) | PlayerStatus::Pause(_) => false, + PlayerStatus::Stopped => true, + }; + assert!(check); + + ps.update(&mut cli).await.unwrap(); + ps.update(&mut cli).await.unwrap() + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs new file mode 100644 index 00000000..a6f20d5b --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs @@ -0,0 +1,145 @@ +use anyhow::Result; + +pub mod play_count { + use anyhow::Context; + + use crate::clients::Client; + + use super::Result; + + pub const STICKER: &str = "unwoundstack.com:playcount"; + + /// Retrieve the play count for a track + pub async fn get(client: &mut Client, file: &str) -> Result<Option<usize>> { + match client + .get_sticker::<usize>(file, STICKER) + .await + .context("Failed to get sticker from client")? + { + Some(n) => Ok(Some(n)), + None => Ok(None), + } + } + + /// Set the play count for a track-- this will run the associated command, if any + pub async fn set(client: &mut Client, file: &str, play_count: usize) -> Result<()> { + client + .set_sticker(file, STICKER, &format!("{}", play_count)) + .await + .context("Failed to set_sticker on client")?; + + Ok(()) + } + + #[cfg(test)] + mod pc_lp_tests { + use super::*; + use crate::{clients::test_mock::Mock, storage::play_count}; + + /// "Smoke" tests for play counts & last played times + #[tokio::test] + async fn pc_smoke() { + let mock = Box::new(Mock::new(&[ + ( + "sticker get song a unwoundstack.com:playcount", + "sticker: unwoundstack.com:playcount=11\nOK\n", + ), + ( + "sticker get song a unwoundstack.com:playcount", + "ACK [50@0] {sticker} no such sticker\n", + ), + ("sticker get song a unwoundstack.com:playcount", "splat!"), + ])); + let mut cli = Client::new(mock).unwrap(); + + assert_eq!(play_count::get(&mut cli, "a").await.unwrap().unwrap(), 11); + let val = play_count::get(&mut cli, "a").await.unwrap(); + assert!(val.is_none()); + play_count::get(&mut cli, "a").await.unwrap_err(); + } + } +} + +pub mod skip_count { + use anyhow::Context; + + use crate::clients::Client; + + use super::Result; + + pub(crate) const STICKER: &str = "unwoundstack.com:skipped_count"; + + /// Retrieve the skip count for a track + pub async fn get(client: &mut Client, file: &str) -> Result<Option<usize>> { + match client + .get_sticker::<usize>(file, STICKER) + .await + .context("Failed to get_sticker on client")? + { + Some(n) => Ok(Some(n)), + None => Ok(None), + } + } + + /// Set the skip count for a track + pub async fn set(client: &mut Client, file: &str, skip_count: usize) -> Result<()> { + client + .set_sticker(file, STICKER, &format!("{}", skip_count)) + .await + .context("Failed to set_sticker on client") + } +} + +pub mod last_played { + use anyhow::Context; + + use crate::clients::Client; + + use super::Result; + + pub const STICKER: &str = "unwoundstack.com:lastplayed"; + + /// Retrieve the last played timestamp for a track (seconds since Unix epoch) + pub async fn get(client: &mut Client, file: &str) -> Result<Option<u64>> { + client + .get_sticker::<u64>(file, STICKER) + .await + .context("Falied to get_sticker on client") + } + + /// Set the last played for a track + pub async fn set(client: &mut Client, file: &str, last_played: u64) -> Result<()> { + client + .set_sticker(file, STICKER, &format!("{}", last_played)) + .await + .context("Failed to set_sticker on client")?; + Ok(()) + } +} + +pub mod rating { + use anyhow::Context; + + use crate::clients::Client; + + use super::Result; + + pub const STICKER: &str = "unwoundstack.com:ratings_count"; + + /// Retrieve the rating count for a track + pub async fn get(client: &mut Client, file: &str) -> Result<Option<i8>> { + client + .get_sticker::<i8>(file, STICKER) + .await + .context("Failed to get_sticker on client") + } + + /// Set the rating count for a track + pub async fn set(client: &mut Client, file: &str, rating_count: i8) -> Result<()> { + client + .set_sticker(file, STICKER, &format!("{}", rating_count)) + .await + .context("Failed to set_sticker on client")?; + Ok(()) + } +} diff --git a/pkgs/by-name/mp/mpdpopm/src/vars.rs b/pkgs/by-name/mp/mpdpopm/src/vars.rs new file mode 100644 index 00000000..7cacec66 --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/src/vars.rs @@ -0,0 +1,4 @@ +pub static VERSION: &str = env!("CARGO_PKG_VERSION"); +pub static AUTHOR: &str = env!("CARGO_PKG_AUTHORS"); +pub static LOCALSTATEDIR: &str = "/home/soispha/.local/state"; +pub static PREFIX: &str = "/home/soispha/.local/share/mpdpopm"; diff --git a/pkgs/by-name/mp/mpdpopm/update.sh b/pkgs/by-name/mp/mpdpopm/update.sh new file mode 100755 index 00000000..e0c0821b --- /dev/null +++ b/pkgs/by-name/mp/mpdpopm/update.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +# Mpdpopm - A mpd rating tracker +# +# Copyright (C) 2026 Benedikt Peetz, Michael Herstine <sp1ff@pobox.com> <benedikt.peetz@b-peetz.de, sp1ff@pobox.com> +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# This file is part of Mpdpopm. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/agpl.txt>. + +[ "$1" = "upgrade" ] && cargo upgrade +cargo update diff --git a/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh b/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh index fa1cac49..6c96a9d9 100755 --- a/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh +++ b/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh @@ -16,6 +16,6 @@ die() { } cd "$XDG_MUSIC_DIR/beets" || die "No music dir!" -exiftool "$(mpc --format '%file%' current)" -json | jq '.[0].Lyrics' --raw-output | less +exiftool "$(mpc --format '%file%' current)" -json | jq '.[0] | if has("Lyrics") then .Lyrics elif has("Lyrics-xxx") then ."Lyrics-xxx" else "<No lyrics key>" end' --raw-output | less # vim: ft=sh diff --git a/pkgs/by-name/mp/mpp/mpp.sh b/pkgs/by-name/mp/mpp/mpp.sh index 3ac98ddb..e52bfcdc 100755 --- a/pkgs/by-name/mp/mpp/mpp.sh +++ b/pkgs/by-name/mp/mpp/mpp.sh @@ -23,6 +23,10 @@ case "${1-}" in shift 1 mpp-beetrm "$@" ;; +"popm") + shift 1 + mpdpopm "$@" + ;; *) mpc "$@" ;; diff --git a/pkgs/by-name/mp/mpp/package.nix b/pkgs/by-name/mp/mpp/package.nix index f0e454f7..fe08ba46 100644 --- a/pkgs/by-name/mp/mpp/package.nix +++ b/pkgs/by-name/mp/mpp/package.nix @@ -16,6 +16,7 @@ mpp-searchadd, mpp-lyrics, mpp-beetrm, + mpdpopm, # Build dependencies fd, zsh, @@ -30,6 +31,7 @@ mpp-searchadd mpp-lyrics mpp-beetrm + mpdpopm ]; }; diff --git a/modules/home.legacy/conf/alacritty/toml/font.toml b/pkgs/by-name/no/notify-run/.envrc index fd8b0b94..880b1809 100644 --- a/modules/home.legacy/conf/alacritty/toml/font.toml +++ b/pkgs/by-name/no/notify-run/.envrc @@ -1,3 +1,5 @@ +#!/usr/bin/env sh + # nixos-config - My current NixOS configuration # # Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> @@ -8,18 +10,13 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -[font] -builtin_box_drawing = true -size = 12.0 - -[font.glyph_offset] -x = -1 -y = -1 +use flake || use nix +watch_file flake.nix -[font.normal] -family = "SauceCodePro Nerd Font Mono" -style = "Regular" +PATH_add ./target/debug +PATH_add ./target/release +PATH_add ./scripts -[font.offset] -x = -1 -y = -1 +if on_git_branch; then + echo && git status --short --branch +fi diff --git a/modules/by-name/yt/yt/config.toml b/pkgs/by-name/no/notify-run/.gitignore index aecb74ba..8f29eabf 100644 --- a/modules/by-name/yt/yt/config.toml +++ b/pkgs/by-name/no/notify-run/.gitignore @@ -8,5 +8,9 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -[download] -max_cache_size = "5 GiB" +# build +/target +/result + +# dev env +.direnv diff --git a/pkgs/by-name/no/notify-run/Cargo.lock b/pkgs/by-name/no/notify-run/Cargo.lock new file mode 100644 index 00000000..1e065d25 --- /dev/null +++ b/pkgs/by-name/no/notify-run/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "notify-run" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/modules/home.legacy/conf/alacritty/yaml/scrolling.yml b/pkgs/by-name/no/notify-run/Cargo.toml index 0d108f76..c4b9a659 100644 --- a/modules/home.legacy/conf/alacritty/yaml/scrolling.yml +++ b/pkgs/by-name/no/notify-run/Cargo.toml @@ -8,10 +8,13 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -scrolling: - # Maximum number of lines in the scrollback buffer. - # Specifying '0' will disable scrolling. - history: 10000 +[package] +name = "notify-run" +description = "An safe way to run applications that might fail" +version = "0.1.0" +edition = "2024" - # Scrolling distance multiplier. - multiplier: 3 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.100" diff --git a/pkgs/by-name/no/notify-run/flake.lock b/pkgs/by-name/no/notify-run/flake.lock new file mode 100644 index 00000000..1e997998 --- /dev/null +++ b/pkgs/by-name/no/notify-run/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1768661221, + "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3327b113f2ef698d380df83fbccefad7e83d7769", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/modules/by-name/yt/yt/input.conf.license b/pkgs/by-name/no/notify-run/flake.lock.license index eae6a84c..eae6a84c 100644 --- a/modules/by-name/yt/yt/input.conf.license +++ b/pkgs/by-name/no/notify-run/flake.lock.license diff --git a/pkgs/by-name/no/notify-run/flake.nix b/pkgs/by-name/no/notify-run/flake.nix new file mode 100644 index 00000000..07be3258 --- /dev/null +++ b/pkgs/by-name/no/notify-run/flake.nix @@ -0,0 +1,34 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + description = "An safe way to run applications, that might fail"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = {nixpkgs, ...}: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages."${system}"; + in { + devShells."${system}".default = pkgs.mkShell { + packages = [ + pkgs.cargo + pkgs.clippy + pkgs.rustc + pkgs.rustfmt + + pkgs.cargo-edit + ]; + }; + }; +} +# vim: ts=2 + diff --git a/pkgs/by-name/no/notify-run/package.nix b/pkgs/by-name/no/notify-run/package.nix new file mode 100644 index 00000000..1f9337be --- /dev/null +++ b/pkgs/by-name/no/notify-run/package.nix @@ -0,0 +1,41 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + rustPlatform, + lib, + libnotify, + makeWrapper, +}: +rustPlatform.buildRustPackage (finalAttrs: { + pname = "notify-run"; + version = "0.1.0"; + + buildInputs = [ + libnotify + ]; + nativeBuildInputs = [ + makeWrapper + ]; + + src = ./.; + cargoLock = { + lockFile = ./Cargo.lock; + }; + + postInstall = '' + # NOTE: We cannot clear the path, because we need access to the programs to start. <2025-12-03> + wrapProgram $out/bin/notify-run \ + --prefix PATH : ${lib.makeBinPath finalAttrs.buildInputs} + ''; + + meta = { + mainProgram = "notify-run"; + }; +}) diff --git a/pkgs/by-name/no/notify-run/src/main.rs b/pkgs/by-name/no/notify-run/src/main.rs new file mode 100644 index 00000000..a6a0165a --- /dev/null +++ b/pkgs/by-name/no/notify-run/src/main.rs @@ -0,0 +1,65 @@ +// nixos-config - My current NixOS configuration +// +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of my nixos-config. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::{env::args, path::PathBuf, process::Command}; + +use anyhow::{Context, Result}; + +fn main() -> Result<()> { + let args = args().skip(1).collect::<Vec<_>>(); + + let mut cmd = Command::new(&args[0]); + if let Some(arguments) = args.get(1) { + cmd.args(arguments.split(" ").collect::<Vec<_>>().as_slice()); + } + + eprintln!("Spawning {:?}", cmd); + + let output = cmd + .output() + .with_context(|| format!("Failed to spawn and await output of {:?}", cmd))?; + + if !output.status.success() { + let mut notify_send = Command::new("notify-send"); + notify_send.args([ + format!("Command {:?} failed", cmd).as_str(), + &String::from_utf8_lossy(output.stderr.as_slice()), + ]); + + notify_send.status().with_context(|| { + format!( + "Failed to run `notify-send` to tell about failed command ({:?}).", + cmd + ) + })?; + } else { + let name = PathBuf::from(&args[0]) + .file_name() + .expect("this to be a command, and thus have a file_name") + .to_string_lossy() + .to_string(); + + print!("{}", append_name(&name, &output.stdout)); + eprint!("{}", append_name(&name, &output.stderr)); + } + + Ok(()) +} + +fn append_name(name: &str, base: &[u8]) -> String { + let base = String::from_utf8_lossy(base).to_string(); + + let mut output = String::new(); + for line in base.lines() { + output.push_str(format!("{name}> {line}\n").as_str()); + } + + output +} diff --git a/modules/home.legacy/conf/alacritty/toml/env.toml b/pkgs/by-name/no/notify-run/update.sh index 307f1f1d..23d90a86 100644..100755 --- a/modules/home.legacy/conf/alacritty/toml/env.toml +++ b/pkgs/by-name/no/notify-run/update.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env sh + # nixos-config - My current NixOS configuration # # Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> @@ -8,6 +10,5 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -[env] -TERM = "alacritty" -COLORTERM = "truecolor" +[ "$1" = "upgrade" ] && cargo upgrade +cargo update diff --git a/pkgs/by-name/ri/river-mk-keymap/Cargo.lock b/pkgs/by-name/ri/river-mk-keymap/Cargo.lock index c58253b2..ef1ffbf7 100644 --- a/pkgs/by-name/ri/river-mk-keymap/Cargo.lock +++ b/pkgs/by-name/ri/river-mk-keymap/Cargo.lock @@ -12,10 +12,26 @@ version = 4 [[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -28,9 +44,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -43,18 +59,18 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -63,15 +79,55 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -79,9 +135,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -91,9 +147,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -103,9 +159,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -114,6 +170,202 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "font-kit" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" +dependencies = [ + "bitflags 2.10.0", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -121,87 +373,257 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "keymaps" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a522bbaa39bddd54945580e369ed37113ea96f4cb8f0322be0d5e04aa4d7293" +checksum = "ea59e8e461942cf1d6a7ad938848d6fd2e40eb43799c21192c09226ecc86710f" dependencies = [ "serde", "thiserror", ] [[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] name = "river-mk-keymap" version = "0.1.0" dependencies = [ + "ab_glyph", "anyhow", "clap", + "font-kit", "keymaps", + "memmap2", + "rustix", "serde", "serde_json", + "shlex", + "thiserror", + "vte", + "wayland-client", + "wayland-protocols-wlr", + "wayland-scanner", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", ] [[package]] -name = "ryu" -version = "1.0.20" +name = "rustix" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -210,17 +632,30 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -228,9 +663,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.103" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -239,18 +674,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -258,10 +693,16 @@ dependencies = [ ] [[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -270,74 +711,169 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "windows-sys" -version = "0.59.0" +name = "vte" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +dependencies = [ + "arrayvec", + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wayland-backend" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ - "windows-targets", + "bitflags 2.10.0", + "rustix", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "wayland-protocols" +version = "0.32.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "wayland-protocols-wlr" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "windows_i686_gnu" -version = "0.52.6" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] [[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "wio" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "zmij" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" diff --git a/pkgs/by-name/ri/river-mk-keymap/Cargo.toml b/pkgs/by-name/ri/river-mk-keymap/Cargo.toml index ef2f0499..31247cd7 100644 --- a/pkgs/by-name/ri/river-mk-keymap/Cargo.toml +++ b/pkgs/by-name/ri/river-mk-keymap/Cargo.toml @@ -16,11 +16,21 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.98" -clap = { version = "4.5.40", features = ["derive"] } -keymaps = { version = "1.1.1", features = ["serde", "mouse-keys"] } -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" +ab_glyph = "0.2.32" +anyhow = "1.0.100" +clap = { version = "4.5.54", features = ["derive"] } +font-kit = "0.14.3" +keymaps = { version = "1.2.0", features = ["serde", "mouse-keys", "modifier-keys"] } +memmap2 = "0.9.9" +rustix = { version = "1.1.3", features = ["fs", "shm"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +shlex = "1.3.0" +thiserror = "2.0.17" +vte = "0.15.0" +wayland-client = {version = "0.31.12", default-features = false} +wayland-protocols-wlr = { version = "0.3.10", features = ["client"] } +wayland-scanner = {version = "0.31.8", default-features = false} [profile.release] lto = true diff --git a/pkgs/by-name/ri/river-mk-keymap/TODO b/pkgs/by-name/ri/river-mk-keymap/TODO deleted file mode 100644 index be77953e..00000000 --- a/pkgs/by-name/ri/river-mk-keymap/TODO +++ /dev/null @@ -1 +0,0 @@ -Look at https://github.com/stefur/flow for river wayland inclusion diff --git a/pkgs/by-name/ri/river-mk-keymap/TODO.license b/pkgs/by-name/ri/river-mk-keymap/TODO.license deleted file mode 100644 index eae6a84c..00000000 --- a/pkgs/by-name/ri/river-mk-keymap/TODO.license +++ /dev/null @@ -1,9 +0,0 @@ -nixos-config - My current NixOS configuration - -Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> -SPDX-License-Identifier: GPL-3.0-or-later - -This file is part of my nixos-config. - -You should have received a copy of the License along with this program. -If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. diff --git a/pkgs/by-name/ri/river-mk-keymap/contrib/example.json b/pkgs/by-name/ri/river-mk-keymap/contrib/example.json index c8673f9a..bddd61c0 100644 --- a/pkgs/by-name/ri/river-mk-keymap/contrib/example.json +++ b/pkgs/by-name/ri/river-mk-keymap/contrib/example.json @@ -1,5 +1,8 @@ { - "<M-a>": { - "command": ["focus-view", "next"] + "Kbad": { + "command": ["spawn", "/nix/store/1xfyw9c5ala73y8sayrsf98vcrr3jrww-libnotify-0.8.6/bin/notify-send hi"] + }, + "Kbae": { + "command": ["e"] } } diff --git a/pkgs/by-name/ri/river-mk-keymap/contrib/init.json b/pkgs/by-name/ri/river-mk-keymap/contrib/init.json new file mode 100644 index 00000000..a5f24307 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/contrib/init.json @@ -0,0 +1,261 @@ +{ + "<Alt+Ctrl+Super+Shift-Z>": [ + "spawn", + "/nix/store/h71ca2rxlnlcyv4604ih2b2gla5ly27d-qmk-unicode-type-1.0.0/bin/qmk-unicode-type 106 65377" + ], + "<MEDIA_LOWERVOLUME>": { + "allow_locked": true, + "command": [ + "spawn", + "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%-" + ] + }, + "<MEDIA_MUTEVOLUME>": { + "allow_locked": true, + "command": [ + "spawn", + "/nix/store/08bgv5x7gfhkczf0lgrpim1rw51jlxvn-mpp/bin/mpp toggle" + ] + }, + "<MEDIA_RAISEVOLUME>": { + "allow_locked": true, + "command": [ + "spawn", + "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%+" + ] + }, + "<LEFT_SUPER>": { + "c": { + "<ENTER>": [ + "zoom" + ], + " ": [ + "toggle-float" + ], + "c": [ + "close" + ], + "f": [ + "toggle-fullscreen" + ], + "n": [ + "swap", + "previous" + ], + "o": [ + "send-to-output", + "next" + ], + "t": [ + "swap", + "next" + ] + }, + "f": { + "0": [ + "set-focused-tags", + "4294967295" + ], + "1": [ + "set-focused-tags", + "1" + ], + "2": [ + "set-focused-tags", + "2" + ], + "3": [ + "set-focused-tags", + "4" + ], + "4": [ + "set-focused-tags", + "8" + ], + "5": [ + "set-focused-tags", + "16" + ], + "6": [ + "set-focused-tags", + "32" + ], + "7": [ + "set-focused-tags", + "64" + ], + "8": [ + "set-focused-tags", + "128" + ], + "9": [ + "set-focused-tags", + "256" + ], + "<Ctrl-n>": [ + "focus-output", + "previous" + ], + "<Ctrl-t>": [ + "focus-output", + "next" + ], + "n": [ + "focus-view", + "previous" + ], + "p": [ + "focus-previous-tags" + ], + "t": [ + "focus-view", + "next" + ] + }, + "m": { + "l": { + "command": [ + "spawn", + "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%-" + ], + "description": "wpctl set-volume @DEFAULT_SINK@ 5%-" + }, + "m": [ + "spawn", + "/nix/store/08bgv5x7gfhkczf0lgrpim1rw51jlxvn-mpp/bin/mpp toggle" + ], + "r": [ + "spawn", + "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%+" + ] + }, + "r": { + "a": [ + "spawn", + "/nix/store/h601phmb09d9dwwziwsim6m0r31qajr3-alacritty-0.15.1/bin/alacritty" + ], + "b": [ + "spawn", + "/nix/store/k8gfhk1lglwr8k6477ygkr9hh037a4kw-tskm-0.1.0/bin/tskm open select" + ], + "k": [ + "spawn", + "/nix/store/xpinf75gxhl8aglw2z7631k89iiml7rz-keepassxc-2.7.10/bin/keepassxc" + ], + "p": [ + "spawn", + "/nix/store/skgvjhmqp3jbmaw70xlz86a66lg13395-screenshot_persistent/bin/screenshot_persistent" + ], + "s": [ + "spawn", + "/nix/store/zvzr8cj57jhxyrzjym2rv3w95w7zw901-signal-desktop-7.56.1/bin/signal-desktop" + ] + }, + "v": { + "0": [ + "set-view-tags", + "4294967295" + ], + "1": [ + "set-view-tags", + "1" + ], + "2": [ + "set-view-tags", + "2" + ], + "3": [ + "set-view-tags", + "4" + ], + "4": [ + "set-view-tags", + "8" + ], + "5": [ + "set-view-tags", + "16" + ], + "6": [ + "set-view-tags", + "32" + ], + "7": [ + "set-view-tags", + "64" + ], + "8": [ + "set-view-tags", + "128" + ], + "9": [ + "set-view-tags", + "256" + ], + "a": { + "1": [ + "toggle-view-tags", + "1" + ], + "2": [ + "toggle-view-tags", + "2" + ], + "3": [ + "toggle-view-tags", + "4" + ], + "4": [ + "toggle-view-tags", + "8" + ], + "5": [ + "toggle-view-tags", + "16" + ], + "6": [ + "toggle-view-tags", + "32" + ], + "7": [ + "toggle-view-tags", + "64" + ], + "8": [ + "toggle-view-tags", + "128" + ], + "9": [ + "toggle-view-tags", + "256" + ] + }, + "p": [ + "send-to-previous-tags" + ] + }, + "x": { + "l": [ + "spawn", + "/nix/store/4gp8yj8cz3d78hn01firv7dlqf4ap1fj-lock/bin/lock" + ], + "q": [ + "exit" + ] + } + }, + "<Super-<MOUSE_LEFT>>": [ + "move-view" + ], + "<Super-<MOUSE_RIGHT>>": [ + "resize-view" + ], + "<Super-L>": [ + "spawn", + "/nix/store/4gp8yj8cz3d78hn01firv7dlqf4ap1fj-lock/bin/lock" + ], + "<PRINTSCREEN>": [ + "spawn", + "/nix/store/skgvjhmqp3jbmaw70xlz86a66lg13395-screenshot_persistent/bin/screenshot_persistent" + ] +} diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.lock b/pkgs/by-name/ri/river-mk-keymap/flake.lock index 412714b7..1e997998 100644 --- a/pkgs/by-name/ri/river-mk-keymap/flake.lock +++ b/pkgs/by-name/ri/river-mk-keymap/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1749903597, - "narHash": "sha256-jp0D4vzBcRKwNZwfY4BcWHemLGUs4JrS3X9w5k/JYDA=", + "lastModified": 1768661221, + "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e", + "rev": "3327b113f2ef698d380df83fbccefad7e83d7769", "type": "github" }, "original": { diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.nix b/pkgs/by-name/ri/river-mk-keymap/flake.nix index e15e99fa..b338e4c9 100644 --- a/pkgs/by-name/ri/river-mk-keymap/flake.nix +++ b/pkgs/by-name/ri/river-mk-keymap/flake.nix @@ -17,15 +17,27 @@ outputs = {nixpkgs, ...}: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages."${system}"; + + nativeBuildInputs = [ + pkgs.pkg-config + ]; + + buildInputs = [ + pkgs.wayland + pkgs.libxkbcommon + pkgs.fontconfig + ]; in { devShells."${system}".default = pkgs.mkShell { - packages = with pkgs; [ - cargo - clippy - rustc - rustfmt + inherit nativeBuildInputs buildInputs; + + packages = [ + pkgs.cargo + pkgs.clippy + pkgs.rustc + pkgs.rustfmt - cargo-edit + pkgs.cargo-edit ]; }; }; diff --git a/pkgs/by-name/ri/river-mk-keymap/package.nix b/pkgs/by-name/ri/river-mk-keymap/package.nix index 7d6d4f3a..bb3dc285 100644 --- a/pkgs/by-name/ri/river-mk-keymap/package.nix +++ b/pkgs/by-name/ri/river-mk-keymap/package.nix @@ -7,7 +7,13 @@ # # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -{rustPlatform}: +{ + rustPlatform, + pkg-config, + wayland, + libxkbcommon, + fontconfig, +}: rustPlatform.buildRustPackage { pname = "river-mk-keymap"; version = "0.1.0"; @@ -17,6 +23,16 @@ rustPlatform.buildRustPackage { lockFile = ./Cargo.lock; }; + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + wayland + libxkbcommon + fontconfig + ]; + meta = { mainProgram = "river-mk-keymap"; }; diff --git a/pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml b/pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml new file mode 100644 index 00000000..aa5fc4dc --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="river_control_unstable_v1"> + <copyright> + Copyright 2020 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + </copyright> + + <interface name="zriver_control_v1" version="1"> + <description summary="run compositor commands"> + This interface allows clients to run compositor commands and receive a + success/failure response with output or a failure message respectively. + + Each command is built up in a series of add_argument requests and + executed with a run_command request. The first argument is the command + to be run. + + A complete list of commands should be made available in the man page of + the compositor. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the river_control object"> + This request indicates that the client will not use the + river_control object any more. Objects that have been created + through this instance are not affected. + </description> + </request> + + <request name="add_argument"> + <description summary="add an argument to the current command"> + Arguments are stored by the server in the order they were sent until + the run_command request is made. + </description> + <arg name="argument" type="string" summary="the argument to add"/> + </request> + + <request name="run_command"> + <description summary="run the current command"> + Execute the command built up using the add_argument request for the + given seat. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="callback" type="new_id" interface="zriver_command_callback_v1" + summary="callback object"/> + </request> + </interface> + + <interface name="zriver_command_callback_v1" version="1"> + <description summary="callback object"> + This object is created by the run_command request. Exactly one of the + success or failure events will be sent. This object will be destroyed + by the compositor after one of the events is sent. + </description> + + <event name="success" type="destructor"> + <description summary="command successful"> + Sent when the command has been successfully received and executed by + the compositor. Some commands may produce output, in which case the + output argument will be a non-empty string. + </description> + <arg name="output" type="string" summary="the output of the command"/> + </event> + + <event name="failure" type="destructor"> + <description summary="command failed"> + Sent when the command could not be carried out. This could be due to + sending a non-existent command, no command, not enough arguments, too + many arguments, invalid arguments, etc. + </description> + <arg name="failure_message" type="string" + summary="a message explaining why failure occurred"/> + </event> + </interface> +</protocol> diff --git a/pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml b/pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml new file mode 100644 index 00000000..e9629dde --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="river_status_unstable_v1"> + <copyright> + Copyright 2020 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + </copyright> + + <interface name="zriver_status_manager_v1" version="4"> + <description summary="manage river status objects"> + A global factory for objects that receive status information specific + to river. It could be used to implement, for example, a status bar. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the river_status_manager object"> + This request indicates that the client will not use the + river_status_manager object any more. Objects that have been created + through this instance are not affected. + </description> + </request> + + <request name="get_river_output_status"> + <description summary="create an output status object"> + This creates a new river_output_status object for the given wl_output. + </description> + <arg name="id" type="new_id" interface="zriver_output_status_v1"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + + <request name="get_river_seat_status"> + <description summary="create a seat status object"> + This creates a new river_seat_status object for the given wl_seat. + </description> + <arg name="id" type="new_id" interface="zriver_seat_status_v1"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + </interface> + + <interface name="zriver_output_status_v1" version="4"> + <description summary="track output tags and focus"> + This interface allows clients to receive information about the current + windowing state of an output. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the river_output_status object"> + This request indicates that the client will not use the + river_output_status object any more. + </description> + </request> + + <event name="focused_tags"> + <description summary="focused tags of the output"> + Sent once binding the interface and again whenever the tag focus of + the output changes. + </description> + <arg name="tags" type="uint" summary="32-bit bitfield"/> + </event> + + <event name="view_tags"> + <description summary="tag state of an output's views"> + Sent once on binding the interface and again whenever the tag state + of the output changes. + </description> + <arg name="tags" type="array" summary="array of 32-bit bitfields"/> + </event> + + <event name="urgent_tags" since="2"> + <description summary="tags of the output with an urgent view"> + Sent once on binding the interface and again whenever the set of + tags with at least one urgent view changes. + </description> + <arg name="tags" type="uint" summary="32-bit bitfield"/> + </event> + + <event name="layout_name" since="4"> + <description summary="name of the layout"> + Sent once on binding the interface should a layout name exist and again + whenever the name changes. + </description> + <arg name="name" type="string" summary="layout name"/> + </event> + + <event name="layout_name_clear" since="4"> + <description summary="name of the layout"> + Sent when the current layout name has been removed without a new one + being set, for example when the active layout generator disconnects. + </description> + </event> + </interface> + + <interface name="zriver_seat_status_v1" version="3"> + <description summary="track seat focus"> + This interface allows clients to receive information about the current + focus of a seat. Note that (un)focused_output events will only be sent + if the client has bound the relevant wl_output globals. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the river_seat_status object"> + This request indicates that the client will not use the + river_seat_status object any more. + </description> + </request> + + <event name="focused_output"> + <description summary="the seat focused an output"> + Sent on binding the interface and again whenever an output gains focus. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <event name="unfocused_output"> + <description summary="the seat unfocused an output"> + Sent whenever an output loses focus. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <event name="focused_view"> + <description summary="information on the focused view"> + Sent once on binding the interface and again whenever the focused + view or a property thereof changes. The title may be an empty string + if no view is focused or the focused view did not set a title. + </description> + <arg name="title" type="string" summary="title of the focused view"/> + </event> + + <event name="mode" since="3"> + <description summary="the active mode changed"> + Sent once on binding the interface and again whenever a new mode + is entered (e.g. with riverctl enter-mode foobar). + </description> + <arg name="name" type="string" summary="name of the mode"/> + </event> + </interface> +</protocol> diff --git a/pkgs/by-name/ri/river-mk-keymap/src/cli.rs b/pkgs/by-name/ri/river-mk-keymap/src/cli.rs index e3c49310..ad872cc9 100644 --- a/pkgs/by-name/ri/river-mk-keymap/src/cli.rs +++ b/pkgs/by-name/ri/river-mk-keymap/src/cli.rs @@ -16,6 +16,20 @@ use clap::Parser; #[command(author, version, about, long_about = None)] /// A tool to manage your key mappings for the river window manager pub(super) struct Args { - /// Path to mappings JSON file - pub path: PathBuf, + #[command(subcommand)] + pub command: SubCommand, + + #[arg(long, short)] + /// Path to mapping config JSON file + pub keymap: PathBuf, +} + +#[derive(clap::Subcommand, Clone, Debug)] +pub(super) enum SubCommand { + Init { + #[arg(short, long, default_value_t = false)] + /// Only show what would be done, don't actually perform the init. + dry_run: bool, + }, + ShowHelp {}, } diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs index e948ccfe..8372b61d 100644 --- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs +++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs @@ -8,112 +8,306 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -use std::process::Command; +use std::{env::current_exe, path::Path, process::Command}; -use keymaps::key_repr::{KeyValue, MediaKeyCode, MouseKeyValue}; +use anyhow::{bail, Result}; +use keymaps::key_repr::{Key, KeyValue, Keys, MediaKeyCode, ModifierKeyCode, MouseKeyValue}; +use rustix::path::Arg; -use super::{KeyMap, MapMode}; +use super::KeyMap; impl KeyMap { - #[must_use] - pub fn to_commands(self) -> Vec<Command> { - self.0 - .iter() - .flat_map(|(key, value)| { - let key = key.last().expect("Will exist"); - let mods = { - let modifiers = key.modifiers(); - let mut output = vec![]; - - if modifiers.alt() { - output.push("Alt"); - } - if modifiers.ctrl() { - output.push("Control"); - } - if modifiers.meta() { - output.push("Super"); - } - if modifiers.shift() { - output.push("Shift"); - } - if output.is_empty() { - "None".to_owned() - } else { - output.join("+") - } - }; - let key_value = match key.value() { - KeyValue::Backspace => "BackSpace".to_owned(), - KeyValue::Enter => "Enter".to_owned(), - KeyValue::Left => "Left".to_owned(), - KeyValue::Right => "Right".to_owned(), - KeyValue::Up => "Up".to_owned(), - KeyValue::Down => "Down".to_owned(), - KeyValue::Home => "Home".to_owned(), - KeyValue::End => "End".to_owned(), - KeyValue::PageUp => "Page_Up".to_owned(), - KeyValue::PageDown => "Page_Down".to_owned(), - KeyValue::Tab => "Tab".to_owned(), - KeyValue::BackTab => "BackTab".to_owned(), - KeyValue::Delete => "Delete".to_owned(), - KeyValue::Insert => "Insert".to_owned(), - KeyValue::F(num) => format!("F{num}"), - KeyValue::Char(a) => a.to_string(), - KeyValue::Null => "Null".to_owned(), - KeyValue::Esc => "Esc".to_owned(), - KeyValue::CapsLock => "CapsLock".to_owned(), - KeyValue::ScrollLock => "ScrollLock".to_owned(), - KeyValue::NumLock => "NumLock".to_owned(), - KeyValue::PrintScreen => "Print".to_owned(), - KeyValue::Pause => "Pause".to_owned(), - KeyValue::Menu => "Menu".to_owned(), - KeyValue::KeypadBegin => "KeypadBegin".to_owned(), - KeyValue::Media(media_key_code) => match media_key_code { - MediaKeyCode::Play => "XF86AudioPlay".to_owned(), - MediaKeyCode::Pause => "XF86AudioPause".to_owned(), - MediaKeyCode::PlayPause => "XF86AudioPlayPause".to_owned(), - MediaKeyCode::Reverse => "XF86AudioReverse".to_owned(), - MediaKeyCode::Stop => "XF86AudioStop".to_owned(), - MediaKeyCode::FastForward => "XF86AudioFastForward".to_owned(), - MediaKeyCode::Rewind => "XF86AudioRewind".to_owned(), - MediaKeyCode::TrackNext => "XF86AudioTrackNext".to_owned(), - MediaKeyCode::TrackPrevious => "XF86AudioTrackPrevious".to_owned(), - MediaKeyCode::Record => "XF86AudioRecord".to_owned(), - MediaKeyCode::LowerVolume => "XF86AudioLowerVolume".to_owned(), - MediaKeyCode::RaiseVolume => "XF86AudioRaiseVolume".to_owned(), - MediaKeyCode::MuteVolume => "XF86AudioMuteVolume".to_owned(), - }, - KeyValue::MouseKey(mouse_key_value) => match mouse_key_value { - MouseKeyValue::Left => "BTN_LEFT".to_owned(), - MouseKeyValue::Right => "BTN_RIGHT".to_owned(), - MouseKeyValue::Middle => "BTN_MIDDLE".to_owned(), - }, - _ => todo!(), + /// # Errors + /// If impossible requests are made. + /// + /// # Panics + /// If internal assertions fail. + #[allow(clippy::too_many_lines)] + pub fn to_commands(self, keymap_path: &Path) -> Result<Vec<Command>> { + self.0.iter().try_for_each(|(keys, value)| { + let (prefix, last) = keys.split_at(keys.len() - 1); + let prefix = prefix.to_owned(); + + if value.allow_locked && !prefix.is_empty() { + bail!( + "Only single key mappings can be used \ + in locked mode, but '{}' contains multiple ('{}').", + Keys::from(keys), + Keys::from(prefix), + ) + } + + if !prefix.is_empty() + && [ + "<ESC>".parse().expect("hardcoded"), + "<BACKSPACE>".parse().expect("hardcoded"), + ] + .contains(&last[0]) + { + bail!( + "You cannot use <ESC> or <BACKSPACE> as the final part of a \ + prefixed mapping, as that is used to return \ + to 'normal' or the upper mode; found in '{}'", + Keys::from(keys), + ) + } + + Ok(()) + })?; + + let mut output: Vec<_> = self + .0 + .into_iter() + .flat_map(|(keys, value)| { + let (prefix, mapping) = keys.split_at(keys.len() - 1); + + let (final_mode, mut base): (Option<String>, _) = + prefix + .iter() + .fold((None, vec![]), |(acc_mode, mut acc_vec), key| { + // Declare intermediate modes for each key. + let mode_name: String = { + let base = key.to_string_repr(); + + if let Some(result) = &acc_mode { + result.to_owned() + base.as_str() + } else { + base + } + }; + + let mut riverctl = Command::new("riverctl"); + riverctl.args(["declare-mode", mode_name.as_str()]); + + let mut output = vec![riverctl]; + + // Provide keymaps for entering and leaving the mode + if let Some(acc_mode) = acc_mode.clone() { + output.extend(key_to_command( + key.to_owned(), + &["enter-mode".to_owned(), mode_name.clone()], + &acc_mode, + false, + )); + } else { + // Also spawn the help display if we start from the “normal” mode. + output.extend(key_to_command( + key.to_owned(), + &[ + "spawn".to_owned(), + format!( + "{} && sleep 1 && {}", + shlex::try_join([ + "riverctl", + "enter-mode", + mode_name.as_str() + ]) + .expect("Should work"), + shlex::try_join([ + current_exe() + .expect("Should have a current exe") + .as_os_str() + .as_str() + .expect("Should be valid utf8"), + "--keymap", + keymap_path.to_str().expect("Should be valid utf8"), + "show-help", + ]) + .expect("Should work"), + ), + ], + "normal", + false, + )); + } + + // Provide a mapping for going up a mode + output.extend(key_to_command( + "<BACKSPACE>".parse().expect("Hardcoded"), + &[ + "enter-mode".to_owned(), + acc_mode.unwrap_or("normal".to_owned()), + ], + &mode_name, + false, + )); + + // Another one for going back to normal. + output.extend(key_to_command( + "<ESC>".parse().expect("Hardcoded"), + &["enter-mode".to_owned(), "normal".to_owned()], + &mode_name, + false, + )); + + acc_vec.extend(output); + + (Some(mode_name), acc_vec) + }); + + let command = if value.once { + vec![ + "spawn".to_owned(), + format!( + "riverctl {} && {}", + shlex::try_join(value.command.iter().map(String::as_str)) + .expect("Should work"), + shlex::try_join(["riverctl", "enter-mode", "normal"]) + .expect("Should work"), + ), + ] + } else { + value.command }; + base.extend(key_to_command( + mapping[0], + &command, + final_mode.as_ref().map_or("normal", |v| v.as_str()), + value.allow_locked, + )); - value - .modes - .iter() - .map(|mode| { - let mut riverctl = Command::new("riverctl"); - riverctl.args([value.map_mode.as_command(), mode, &mods, &key_value]); - - riverctl.args(value.command.iter().map(String::as_str)); - riverctl - }) - .collect::<Vec<_>>() + base }) - .collect() + .collect(); + + output.sort_by_cached_key(|cmd| format!("{cmd:?}")); + output.dedup_by_key(|cmd| format!("{cmd:?}")); + + Ok(output) } } -impl MapMode { - pub(crate) fn as_command(self) -> &'static str { - match self { - MapMode::Map => "map", - MapMode::MapMouse => "map-pointer", - MapMode::Unmap => "unmap", +fn key_value_to_xkb_common_name(value: KeyValue) -> (String, Vec<&'static str>) { + let mut extra_modifiers = vec![]; + + let output = match value { + KeyValue::Backspace => "BackSpace".to_owned(), + KeyValue::Enter => "Return".to_owned(), + KeyValue::Left => "Left".to_owned(), + KeyValue::Right => "Right".to_owned(), + KeyValue::Up => "Up".to_owned(), + KeyValue::Down => "Down".to_owned(), + KeyValue::Home => "Home".to_owned(), + KeyValue::End => "End".to_owned(), + KeyValue::PageUp => "Page_Up".to_owned(), + KeyValue::PageDown => "Page_Down".to_owned(), + KeyValue::Tab => "Tab".to_owned(), + KeyValue::BackTab => "BackTab".to_owned(), + KeyValue::Delete => "Delete".to_owned(), + KeyValue::Insert => "Insert".to_owned(), + KeyValue::F(num) => format!("F{num}"), + KeyValue::Char(a) => { + // River does not differentiate between 'a' and 'A', + // so we need to do it beforehand. + if a.is_ascii_uppercase() { + extra_modifiers.push("Shift"); + } + + if a == ' ' { + "Space".to_string() + } else { + a.to_string() + } } + KeyValue::Null => "Null".to_owned(), + KeyValue::Esc => "Escape".to_owned(), + KeyValue::CapsLock => "CapsLock".to_owned(), + KeyValue::ScrollLock => "ScrollLock".to_owned(), + KeyValue::NumLock => "NumLock".to_owned(), + KeyValue::PrintScreen => "Print".to_owned(), + KeyValue::Pause => "Pause".to_owned(), + KeyValue::Menu => "Menu".to_owned(), + KeyValue::KeypadBegin => "KeypadBegin".to_owned(), + KeyValue::Media(media_key_code) => match media_key_code { + MediaKeyCode::Play => "XF86AudioPlay".to_owned(), + MediaKeyCode::Pause => "XF86AudioPause".to_owned(), + MediaKeyCode::PlayPause => "XF86AudioPlayPause".to_owned(), + MediaKeyCode::Reverse => "XF86AudioReverse".to_owned(), + MediaKeyCode::Stop => "XF86AudioStop".to_owned(), + MediaKeyCode::FastForward => "XF86AudioFastForward".to_owned(), + MediaKeyCode::Rewind => "XF86AudioRewind".to_owned(), + MediaKeyCode::TrackNext => "XF86AudioNext".to_owned(), + MediaKeyCode::TrackPrevious => "XF86AudioPrev".to_owned(), + MediaKeyCode::Record => "XF86AudioRecord".to_owned(), + MediaKeyCode::LowerVolume => "XF86AudioLowerVolume".to_owned(), + MediaKeyCode::RaiseVolume => "XF86AudioRaiseVolume".to_owned(), + MediaKeyCode::MuteVolume => "XF86AudioMute".to_owned(), + }, + KeyValue::MouseKey(mouse_key_value) => match mouse_key_value { + MouseKeyValue::Left => "BTN_LEFT".to_owned(), + MouseKeyValue::Right => "BTN_RIGHT".to_owned(), + MouseKeyValue::Middle => "BTN_MIDDLE".to_owned(), + }, + KeyValue::ModifierKey(modifier_key_code) => match modifier_key_code { + ModifierKeyCode::LeftAlt => "ALT_L".to_owned(), + ModifierKeyCode::RightAlt => "ALT_R".to_owned(), + ModifierKeyCode::LeftCtrl => "CTRL_L".to_owned(), + ModifierKeyCode::RightCtrl => "CTRL_R".to_owned(), + ModifierKeyCode::LeftMeta => "SUPER_L".to_owned(), + ModifierKeyCode::RightMeta => "SUPER_R".to_owned(), + ModifierKeyCode::LeftShift => "SHIFT_L".to_owned(), + ModifierKeyCode::RightShift => "SHIFT_R".to_owned(), + }, + other => todo!("Key value: {other} not known."), + }; + + (output, extra_modifiers) +} + +fn key_to_command(key: Key, command: &[String], mode: &str, allow_locked: bool) -> Vec<Command> { + let mut modifiers = { + let modifiers = key.modifiers(); + let mut output = vec![]; + + if modifiers.alt() { + output.push("Alt"); + } + if modifiers.ctrl() { + output.push("Control"); + } + if modifiers.meta() { + output.push("Super"); + } + if modifiers.shift() { + output.push("Shift"); + } + output + }; + + let (key_value, extra_modifiers) = key_value_to_xkb_common_name(key.value()); + modifiers.extend(extra_modifiers); + + let map_mode = if let KeyValue::MouseKey(_) = key.value() { + "map-pointer" + } else { + "map" + }; + + let modifiers = if modifiers.is_empty() { + "None".to_owned() + } else { + modifiers.join("+") + }; + + let mut output = vec![{ + let mut riverctl = Command::new("riverctl"); + riverctl.args([map_mode, mode, &modifiers, &key_value]); + + riverctl.args(command.iter().map(String::as_str)); + + riverctl + }]; + + if allow_locked { + output.push({ + let mut riverctl = Command::new("riverctl"); + riverctl.args([map_mode, "locked", &modifiers, &key_value]); + + riverctl.args(command.iter().map(String::as_str)); + + riverctl + }); } + + output } diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs index 2c82ee05..16dc02f4 100644 --- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs +++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs @@ -8,40 +8,100 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr}; +use std::{fmt::Display, ops::Deref, str::FromStr}; -use anyhow::Context; -use keymaps::{key_repr::Key, map_tree::MapTrie}; +use anyhow::{anyhow, bail, Context, Result}; +use keymaps::{ + key_repr::{Key, Keys}, + map_tree::MapTrie, +}; use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; pub mod commands; -#[derive(Deserialize, Serialize, Debug)] -#[allow(clippy::module_name_repetitions)] -pub struct RawKeyMap(HashMap<Key, KeyConfig>); - #[derive(Clone, Deserialize, Serialize, Debug, PartialEq, PartialOrd)] -/// What values to use for: `riverctl <map_mode> <mode> <mods> <key> <command..>` +/// What values to use for: `riverctl <command..>` +#[serde(deny_unknown_fields)] pub struct KeyConfig { command: Vec<String>, - #[serde(default = "default_mode")] - modes: Vec<String>, + /// Whether to allow this key mapping in the “locked” mode. + #[serde(default)] + allow_locked: bool, + + /// Whether to go back to the normal mode, after running this command. + #[serde(default)] + once: bool, - #[serde(default = "MapMode::default")] - map_mode: MapMode, + /// Use a different description to display this command, instead of the `command`. + description: Option<String>, } impl FromStr for KeyMap { type Err = anyhow::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { - let raw: RawKeyMap = - serde_json::from_str(s).context("Failed to parse the keymap config file as json.")?; + fn decode_value( + output: &mut MapTrie<KeyConfig>, + current_key: Vec<Key>, + value: &Value, + ) -> Result<()> { + let key_config = if let Some(value) = value.as_array() { + KeyConfig { + command: value + .iter() + .map(|v| v.as_str().map(ToOwned::to_owned)) + .collect::<Option<_>>() + .ok_or(anyhow!("A array contained a non-string value: {value:#?}"))?, + allow_locked: false, + once: false, + description: None, + } + } else if let Some(object) = value.as_object() { + if object.contains_key("command") { + serde_json::from_value(value.to_owned()) + .with_context(|| format!("Failed to parse key config: {value:#?}"))? + } else { + for (key, value) in object { + let mut local_current_key = current_key.clone(); + local_current_key.push( + Key::from_str(key) + .with_context(|| format!("Failed to parse key '{key}'"))?, + ); + + decode_value(output, local_current_key, value)?; + } + return Ok(()); + } + } else { + bail!("Value ({}) is invalid (not array or object).", value) + }; + + output + .insert(¤t_key, key_config.clone()) + .with_context(|| { + format!( + "Failed to insert mapping {} -> {key_config}", + Keys::from(current_key) + ) + })?; + + Ok(()) + } + let mut out = MapTrie::<KeyConfig>::new(); - for (key, value) in raw.0 { - out.insert(&[key], value.clone()) - .with_context(|| format!("Failed to insert mapping {key} -> {value}"))?; + + let raw: Map<String, Value> = + serde_json::from_str(s).context("Failed to parse the keymap config file as json.")?; + + for (key, value) in raw { + decode_value( + &mut out, + vec![Key::from_str(&key) + .with_context(|| format!("Failed to parse key ('{key}')"))?], + &value, + )?; } Ok(Self(out)) @@ -49,25 +109,11 @@ impl FromStr for KeyMap { } impl Display for KeyConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.command.join(" ").as_str()) - } -} - -fn default_mode() -> Vec<String> { - vec!["normal".to_owned()] -} - -#[derive(Copy, Deserialize, Serialize, Debug, Clone, Default, PartialEq, PartialOrd)] -enum MapMode { - #[default] - Map, - MapMouse, - Unmap, -} - -impl Display for MapMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - <Self as std::fmt::Debug>::fmt(self, f) + if let Some(desc) = &self.description { + f.write_str(desc) + } else { + f.write_str(self.command.join(" ").as_str()) + } } } diff --git a/pkgs/by-name/ri/river-mk-keymap/src/main.rs b/pkgs/by-name/ri/river-mk-keymap/src/main.rs index 63955f7f..18c291cf 100644 --- a/pkgs/by-name/ri/river-mk-keymap/src/main.rs +++ b/pkgs/by-name/ri/river-mk-keymap/src/main.rs @@ -13,31 +13,57 @@ use std::fs; use anyhow::Context; use clap::Parser; -mod cli; +pub mod cli; pub mod key_map; +pub mod wayland; use crate::{cli::Args, key_map::KeyMap}; fn main() -> Result<(), anyhow::Error> { let args = Args::parse(); - let keymap_file = fs::read_to_string(&args.path) - .with_context(|| format!("Failed to open keymap file at: '{}'.", args.path.display()))?; - - let keymap: KeyMap = keymap_file - .parse() - .with_context(|| format!("Failed to parse keymap file at: {}", args.path.display()))?; - - // println!("{keymap}"); - // println!("Commands:"); - for mut command in keymap.to_commands() { - // println!("Executing {command:?}"); - let status = command - .status() - .with_context(|| format!("Failed to run command: '{command:?}'"))?; - - if !status.success() { - eprintln!("Command ('{command:?}') returned with non zero exit code: {status}"); + + let keymap_path = &args.keymap.canonicalize().with_context(|| { + format!( + "Failed to canonicalize kepmay path: '{}'", + args.keymap.display() + ) + })?; + + let config = { + let keymap_file = fs::read_to_string(keymap_path).with_context(|| { + format!( + "Failed to open keymap file at: '{}'.", + keymap_path.display() + ) + })?; + + let keymap: KeyMap = keymap_file.parse().with_context(|| { + format!("Failed to parse keymap file at: {}", keymap_path.display()) + })?; + + keymap + }; + + match args.command { + cli::SubCommand::Init { dry_run } => { + println!("{config}"); + for mut command in config.to_commands(keymap_path)? { + if dry_run { + println!("{command:?}"); + } else { + let status = command + .status() + .with_context(|| format!("Failed to run command: '{command:?}'"))?; + + if !status.success() { + eprintln!( + "Command ('{command:?}') returned with non zero exit code: {status}" + ); + } + } + } } + cli::SubCommand::ShowHelp {} => wayland::main(config)?, } Ok(()) diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs new file mode 100644 index 00000000..0517ecf2 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs @@ -0,0 +1,173 @@ +use std::mem; + +use vte::{Params, Parser, Perform}; + +#[derive(Debug, Clone, Copy)] +pub(crate) enum Color { + Black, + Red, + Green, + Yellow, + Blue, + Purple, + Cyan, + White, +} + +#[derive(Debug)] +struct Cleaner { + current_color: Option<Color>, + styles: StyledString, + current: String, +} + +#[derive(Debug)] +struct StyledStringInner { + val: String, + color: Option<Color>, +} + +pub(crate) struct StyledChar { + ch: char, + color: Option<Color>, +} + +impl StyledChar { + pub(crate) fn as_char(&self) -> char { + self.ch + } + + pub(crate) fn is_bold(&self) -> bool { + self.color.is_some() + } + + pub(crate) fn color(&self) -> Option<Color> { + self.color + } +} + +#[derive(Debug)] +pub(crate) struct StyledString { + inner: Vec<StyledStringInner>, +} + +impl StyledString { + fn push(&mut self, val: StyledStringInner) { + self.inner.push(val); + } + + pub(crate) fn chars(&self) -> impl Iterator<Item = StyledChar> + use<'_> { + self.inner.iter().flat_map(|inner| { + inner.val.chars().map(|ch| StyledChar { + ch, + color: inner.color, + }) + }) + } +} + +impl Cleaner { + fn reset_color(&mut self) { + self.styles.push(StyledStringInner { + val: mem::take(&mut self.current), + color: mem::take(&mut self.current_color), + }); + } + + fn set_color(&mut self, color: Color) { + self.current_color = Some(color); + } + + fn add_char(&mut self, c: char) { + self.current.push(c); + } +} + +impl Perform for Cleaner { + fn print(&mut self, c: char) { + self.add_char(c); + } + + fn execute(&mut self, byte: u8) { + if byte == b'\n' { + self.reset_color(); + self.add_char('\n'); + self.reset_color(); + } else { + eprintln!("Unknown [execute]: {byte:02x}"); + } + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + eprintln!( + "Unknown [hook] params={params:?}, intermediates={intermediates:?}, ignore={ignore:?}, char={c:?}" + ); + } + + fn put(&mut self, byte: u8) { + eprintln!("Unknonw [put] {byte:02x}"); + } + + fn unhook(&mut self) { + eprintln!("Unknown [unhook]"); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + eprintln!("Unkown [osc_dispatch] params={params:?} bell_terminated={bell_terminated}"); + } + + fn csi_dispatch(&mut self, params: &Params, _: &[u8], _: bool, c: char) { + let params: Vec<u16> = params.iter().flatten().copied().collect(); + + if c != 'm' { + return; + } + + // See: https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124 + match params[..] { + [0] => self.reset_color(), + // [0, regular] if matches!(regular, 30..=37) => {} + [1, bold] if matches!(bold, 30..=37) => match bold { + 30 => self.set_color(Color::Black), + 31 => self.set_color(Color::Red), + 32 => self.set_color(Color::Green), + 36 => self.set_color(Color::Yellow), + 34 => self.set_color(Color::Blue), + 35 => self.set_color(Color::Purple), + 33 => self.set_color(Color::Cyan), + 37 => self.set_color(Color::White), + _ => unreachable!("Was filtered out"), + }, + // [4, underline] if matches!(underline, 30..=37) => {} + // [background] if matches!(background, 40..=47) => {} + _ => todo!(), + } + + // println!( + // "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", + // params, intermediates, ignore, c + // ); + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + eprintln!( + "Unkown [esc_dispatch] intermediates={intermediates:?}, ignore={ignore:?}, byte={byte:02x}" + ); + } +} + +pub(crate) fn parse(input: &str) -> StyledString { + let mut statemachine = Parser::new(); + let mut performer = Cleaner { + current_color: None, + styles: StyledString { inner: vec![] }, + current: String::new(), + }; + + let buf: Vec<_> = input.bytes().collect(); + + statemachine.advance(&mut performer, &buf[..]); + + assert!(performer.current.is_empty() && performer.current_color.is_none()); + performer.styles +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs new file mode 100644 index 00000000..c6e04fdf --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs @@ -0,0 +1,214 @@ +use std::num::NonZero; + +use keymaps::key_repr::Key; +use wayland_client::{ + globals::GlobalListContents, + protocol::{ + wl_buffer::WlBuffer, wl_compositor::WlCompositor, wl_registry, wl_seat::WlSeat, + wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface, + }, + Connection, Dispatch, QueueHandle, +}; + +use wayland_protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::ZwlrLayerShellV1, + zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1}, +}; + +use crate::wayland::{ + ansi, render, + river::protocols::river_protocols::{ + zriver_seat_status_v1::{self, ZriverSeatStatusV1}, + zriver_status_manager_v1::ZriverStatusManagerV1, + }, + AppData, +}; + +impl Dispatch<ZriverSeatStatusV1, ()> for AppData { + fn event( + state: &mut Self, + _: &ZriverSeatStatusV1, + event: <ZriverSeatStatusV1 as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + if let zriver_seat_status_v1::Event::Mode { name } = event { + let new_text = { + if name == "normal" { + // We are back at the normal mode. + // There is no need to display the mappings anymore, exit. + state.should_exit = true; + return; + } else if let Ok(keys) = Key::parse_multiple(&name) { + if let Some(val) = state.config.get(&keys) { + ansi::parse(val.to_string().as_str()) + } else { + // Mode name not know, do nothing. + return; + } + } else { + // Mode name not valid, do nothing. + return; + } + }; + + let px_height; + (state.pixel_data, (state.max_px_width, px_height)) = + render::text(&new_text).expect("Works?"); + + // We add the `5` here, so that our letters don't stop exactly at the border. + state + .window + .0 + .set_size(state.max_px_width + 5, px_height + 5); + state.window.1.commit(); + + if state.configured { + state.draw(); + } + } + } +} + +impl Dispatch<ZwlrLayerSurfaceV1, ()> for AppData { + fn event( + state: &mut Self, + proxy: &ZwlrLayerSurfaceV1, + event: <ZwlrLayerSurfaceV1 as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + match event { + zwlr_layer_surface_v1::Event::Configure { + serial, + width, + height, + } => { + state.buffer = None; + + proxy.ack_configure(serial); + + state.width = NonZero::new(width).map_or_else(|| state.width, NonZero::get); + state.height = NonZero::new(height).map_or_else(|| state.height, NonZero::get); + + state.draw(); + + state.configured = true; + } + zwlr_layer_surface_v1::Event::Closed => { + state.should_exit = true; + } + _ => (), + } + } +} + +impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for AppData { + fn event( + _: &mut AppData, + _: &wl_registry::WlRegistry, + _: wl_registry::Event, + _: &GlobalListContents, + _: &Connection, + _: &QueueHandle<AppData>, + ) { + } +} + +impl Dispatch<WlShmPool, ()> for AppData { + fn event( + _: &mut Self, + _: &WlShmPool, + _: <WlShmPool as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<WlShm, ()> for AppData { + fn event( + _: &mut Self, + _: &WlShm, + _: <WlShm as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<WlSurface, ()> for AppData { + fn event( + _: &mut Self, + _: &WlSurface, + _: <WlSurface as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<WlCompositor, ()> for AppData { + fn event( + _: &mut Self, + _: &WlCompositor, + _: <WlCompositor as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<WlSeat, ()> for AppData { + fn event( + _: &mut Self, + _: &WlSeat, + _: <WlSeat as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<WlBuffer, ()> for AppData { + fn event( + _: &mut Self, + _: &WlBuffer, + _: <WlBuffer as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<ZriverStatusManagerV1, ()> for AppData { + fn event( + _: &mut Self, + _: &ZriverStatusManagerV1, + _: <ZriverStatusManagerV1 as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<ZwlrLayerShellV1, ()> for AppData { + fn event( + _: &mut Self, + _: &ZwlrLayerShellV1, + _: <ZwlrLayerShellV1 as wayland_client::Proxy>::Event, + (): &(), + _: &Connection, + _: &QueueHandle<Self>, + ) { + } +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs new file mode 100644 index 00000000..44c010d5 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs @@ -0,0 +1,272 @@ +#![allow( + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_possible_truncation +)] + +use anyhow::Result; +use wayland_client::{ + globals::registry_queue_init, + protocol::{ + wl_compositor::WlCompositor, + wl_seat::WlSeat, + wl_shm::{self, WlShm}, + wl_surface::WlSurface, + }, + Connection, +}; +use wayland_protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::{self, ZwlrLayerShellV1}, + zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1}, +}; + +use crate::{ + key_map::KeyMap, + wayland::{ + ansi::Color, + river::protocols::river_protocols::zriver_status_manager_v1::ZriverStatusManagerV1, + shm::slot::{Buffer, SlotPool}, + }, +}; + +mod ansi; +mod render; +mod river; +mod shm; + +mod dispatches; + +struct AppData { + pool: SlotPool, + window: (ZwlrLayerSurfaceV1, WlSurface), + + configured: bool, + buffer: Option<Buffer>, + + width: u32, + height: u32, + + max_px_width: u32, + pixel_data: (Vec<f32>, Vec<Option<Color>>), + + config: KeyMap, + should_exit: bool, +} + +impl AppData { + #[allow(clippy::too_many_lines)] + fn draw(&mut self) { + let width = self.width; + let height = self.height; + let stride = self.width as i32 * 4; + + let buffer = self.buffer.get_or_insert_with(|| { + self.pool + .create_buffer( + width as i32, + height as i32, + stride, + wl_shm::Format::Argb8888, + ) + .expect("Works?") + .0 + }); + + let canvas = if let Some(canvas) = self.pool.canvas(buffer) { + canvas + } else { + // This should be rare, but if the compositor has not released the previous + // buffer, we need double-buffering. + let (second_buffer, canvas) = self + .pool + .create_buffer( + self.width as i32, + self.height as i32, + stride, + wl_shm::Format::Argb8888, + ) + .expect("create buffer"); + *buffer = second_buffer; + canvas + }; + + // Draw to the window. + { + canvas + .chunks_exact_mut(stride as usize) + .enumerate() + .for_each(|(row_index, row)| { + // let row_slice = row_slice(self.height, row_index as u32, 0.97); + // let allowed_columns = (f64::from(self.width) * row_slice).ceil() as usize; + + row.chunks_exact_mut(4) + .enumerate() + .for_each(|(column_index, chunk)| { + // const BACKGROUND_COLOR: u32 = 0xee58_5b70; + const BACKGROUND_COLOR: u32 = 0xee00_0000; + + assert!(column_index as u32 <= self.width); + + // if column_index > allowed_columns + // || column_index < (self.width as usize - allowed_columns) + // { + // let array: &mut [u8; 4] = chunk.try_into().unwrap(); + // *array = 0u32.to_le_bytes(); + // return; + // } + + if column_index >= (self.max_px_width as usize) { + let array: &mut [u8; 4] = chunk.try_into().unwrap(); + *array = BACKGROUND_COLOR.to_le_bytes(); + } else { + assert!(column_index < self.max_px_width as usize); + + let position = + column_index + row_index * self.max_px_width as usize; + + if let Some(coverage) = &self.pixel_data.0.get(position) { + let a = (BACKGROUND_COLOR & (0xff << (6 * 4))) >> 24; + + let (r, g, b) = if let Some(color) = self + .pixel_data + .1 + .get(position) + .expect("If the pixel is set, the color will too") + { + let (r, g, b) = match color { + Color::Black => (0, 0, 0), + Color::Red => (0xff, 0, 0), + Color::Green => (0, 0xff, 0), + Color::Yellow => (0xff, 0xff, 0), + Color::Blue => (0, 0, 0xff), + Color::Purple => (0x80, 0, 0x80), + Color::Cyan => (0, 0xff, 0xff), + Color::White => (0xff, 0xff, 0xff), + }; + + let r = (r as f32 * **coverage).ceil() as u32; + let g = (g as f32 * **coverage).ceil() as u32; + let b = (b as f32 * **coverage).ceil() as u32; + + (r, g, b) + } else { + let r = (255.0 * **coverage).ceil() as u32; + let g = (255.0 * **coverage).ceil() as u32; + let b = (255.0 * **coverage).ceil() as u32; + + (r, g, b) + }; + + let color: u32 = (a << 24) + (r << 16) + (g << 8) + b; + + let array: &mut [u8; 4] = chunk.try_into().unwrap(); + *array = color.to_le_bytes(); + } else { + let array: &mut [u8; 4] = chunk.try_into().unwrap(); + *array = BACKGROUND_COLOR.to_le_bytes(); + } + } + }); + }); + } + + self.window + .1 + .damage_buffer(0, 0, self.width as i32, self.height as i32); + + buffer.attach_to(&self.window.1).expect("works"); + self.window.1.commit(); + } +} + +/// # Errors +/// If a protocol error arises. +pub fn main(config: KeyMap) -> Result<()> { + let conn = Connection::connect_to_env()?; + let (globals, mut queue) = registry_queue_init::<AppData>(&conn)?; + let qh = queue.handle(); + + let seat: WlSeat = globals.bind(&qh, 9..=9, ())?; + let status_manager: ZriverStatusManagerV1 = globals.bind(&qh, 4..=4, ())?; + let _seat_status = status_manager.get_river_seat_status(&seat, &qh, ()); + + let compositor: WlCompositor = globals.bind(&qh, 6..=6, ())?; + let shm: WlShm = globals.bind(&qh, 1..=1, ())?; + // let xdg_wm: XdgWmBase = globals.bind(&qh, 5..=5, ())?; + + let surface = compositor.create_surface(&qh, ()); + let pool = SlotPool::new(1024 * 1024, &shm)?; + + let zwlr_layer_shell: ZwlrLayerShellV1 = globals.bind(&qh, 4..=4, ())?; + let layer_surface = zwlr_layer_shell.get_layer_surface( + &surface, + None, + zwlr_layer_shell_v1::Layer::Overlay, + "river-mk-keymap which-key".to_owned(), + &qh, + (), + ); + + layer_surface.set_size(256, 256); + layer_surface + .set_anchor(zwlr_layer_surface_v1::Anchor::Left | zwlr_layer_surface_v1::Anchor::Top); + + surface.commit(); + + let mut me = AppData { + config, + should_exit: false, + + configured: false, + buffer: None, + + width: 256, + height: 256, + + max_px_width: 0, + pixel_data: (vec![], vec![]), + + window: (layer_surface, surface), + + pool, + }; + + loop { + queue.blocking_dispatch(&mut me)?; + + if me.should_exit { + break; + } + } + + Ok(()) +} + +// /// Calculate which amount of the current row (`i`) should be painted, if we want a corner +// /// rounding of percent `p` and have an total of `n` rows. +// fn row_slice(n_u32: u32, i_u32: u32, p: f64) -> f64 { +// fn within_tolerance(a: f64, b: f64) -> bool { +// const ALLOWED_ERROR: f64 = 0.000_000_1; +// +// (a - b).abs() < ALLOWED_ERROR +// } +// +// let i = f64::from(i_u32); +// let n = f64::from(n_u32); +// +// let out = p + (1.0 - p) * (PI * i / n).sin(); +// +// assert!(out >= 0.0); +// assert!(out <= 1.0); +// +// if i_u32 == 0 || i_u32 == n_u32 { +// assert!(within_tolerance(out, p)); +// } +// +// if i_u32 < n_u32 / 2 { +// assert!(within_tolerance(out, row_slice(n_u32, n_u32 - i_u32, p))); +// } +// +// out +// } diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs new file mode 100644 index 00000000..7f0aaec9 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs @@ -0,0 +1,57 @@ +use ab_glyph::{point, Font, Glyph, Point, ScaleFont}; + +use crate::wayland::ansi::{StyledChar, StyledString}; + +/// Simple paragraph layout for glyphs into `target`. +/// Starts at position `(0, ascent)`. +/// +/// This is for testing and examples. +pub(super) fn layout_paragraph<F, SF, BF, BSF>( + font: SF, + bold_font: BSF, + position: Point, + max_width: f32, + text: &StyledString, + target: &mut Vec<(Glyph, StyledChar)>, +) where + F: Font, + SF: ScaleFont<F>, + BF: Font, + BSF: ScaleFont<BF>, +{ + let v_advance = font.height() + font.line_gap(); + let mut caret = position + point(0.0, font.ascent()); + let mut last_glyph: Option<Glyph> = None; + + for c in text.chars() { + if c.as_char().is_control() { + if c.as_char() == '\n' { + caret = point(position.x, caret.y + v_advance); + last_glyph = None; + } + continue; + } + + let mut glyph = if c.is_bold() { + bold_font.scaled_glyph(c.as_char()) + } else { + font.scaled_glyph(c.as_char()) + }; + + if let Some(previous) = last_glyph.take() { + caret.x += font.kern(previous.id, glyph.id); + } + glyph.position = caret; + + last_glyph = Some(glyph.clone()); + caret.x += font.h_advance(glyph.id); + + if !c.as_char().is_whitespace() && caret.x > position.x + max_width { + caret = point(position.x, caret.y + v_advance); + glyph.position = caret; + last_glyph = None; + } + + target.push((glyph, c)); + } +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs new file mode 100644 index 00000000..e92def3c --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs @@ -0,0 +1,129 @@ +use std::{fs::File, io::Read}; + +use ab_glyph::{point, Font, FontVec, PxScale, ScaleFont}; +use anyhow::{Context, Result}; +use font_kit::{ + family_name::FamilyName, handle::Handle, properties::Properties, source::SystemSource, +}; + +use crate::wayland::ansi::{Color, StyledString}; + +mod layout; + +fn get_font(weight: f32) -> Result<impl Font> { + let handle = SystemSource::new() + .select_best_match( + &[FamilyName::Monospace], + Properties::new().weight(font_kit::properties::Weight(weight)), + ) + .context("Failed to find a monospace font")?; + + match handle { + Handle::Path { path, font_index } => { + let data = { + let mut buffer = vec![]; + + let mut file = File::open(&path)?; + file.read_to_end(&mut buffer)?; + buffer + }; + + FontVec::try_from_vec_and_index(data, font_index).with_context(|| { + format!( + "Failed to load font at '{}' with index {}", + path.display(), + font_index + ) + }) + } + Handle::Memory { .. } => unimplemented!(), + } +} + +pub(super) type ColorVec = (Vec<f32>, Vec<Option<Color>>); +pub(super) fn text(input: &StyledString) -> Result<(ColorVec, (u32, u32))> { + let normal_font = get_font(400.0)?; + let bold_font = get_font(600.0)?; + + let height: f32 = 15.0; + let px_height = height.ceil() as usize; + + let scale = PxScale { + x: height, + y: height, + }; + + let scaled_font = normal_font.into_scaled(scale); + let bold_scaled_font = bold_font.into_scaled(scale); + + let mut glyphs = Vec::new(); + layout::layout_paragraph( + &scaled_font, + &bold_scaled_font, + point(0.0, 0.0), + 9999.0, + input, + &mut glyphs, + ); + + let px_width = glyphs + .iter() + .fold(0.0, |acc, (g, c)| { + let next = g.position.x + + if c.is_bold() { + bold_scaled_font.h_advance(g.id) + } else { + scaled_font.h_advance(g.id) + }; + + if next > acc { + next + } else { + acc + } + }) + .ceil() as usize; + + // Rasterise to a f32 alpha vec + let mut pixel_data = vec![0.0; px_width * px_height]; + let mut color_data = vec![None; px_width * px_height]; + for (g, c) in glyphs { + let maybe_glyph = if c.is_bold() { + bold_scaled_font.outline_glyph(g) + } else { + scaled_font.outline_glyph(g) + }; + + if let Some(og) = maybe_glyph { + let bounds = og.px_bounds(); + og.draw(|x, y, v| { + let x = x as f32 + bounds.min.x; + let y = y as f32 + bounds.min.y; + let next_idx = x as usize + y as usize * px_width; + + assure_idx(&mut pixel_data, next_idx, 0.0); + assure_idx(&mut color_data, next_idx, None); + + // save the coverage alpha + pixel_data[next_idx] += v; + color_data[next_idx] = c.color(); + }); + } + } + + let len = pixel_data.len(); + Ok(( + (pixel_data, color_data), + (px_width as u32, (len / px_width) as u32), + )) +} + +fn assure_idx<T: Copy + Clone>(pixel_data: &mut Vec<T>, next_idx: usize, fill: T) { + let last = pixel_data.len() - 1; + + if next_idx > last { + let needed = next_idx - last; + + pixel_data.extend(vec![fill; needed]); + } +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs new file mode 100644 index 00000000..f17c7ac8 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs @@ -0,0 +1 @@ +pub(crate) mod protocols; diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs new file mode 100644 index 00000000..e54b65e1 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs @@ -0,0 +1,28 @@ +pub(crate) mod river_protocols { + use wayland_client; + // import objects from the core protocol if needed + use wayland_client::protocol::{wl_output, wl_seat}; + + // This module hosts a low-level representation of the protocol objects + // you will not need to interact with it yourself, but the code generated + // by the generate_client_code! macro will use it + // import the interfaces from the core protocol if needed + + #[allow(non_upper_case_globals)] + pub(crate) mod __status { + use wayland_client::backend as wayland_backend; + use wayland_client::protocol::__interfaces::{ + wl_output_interface, wl_seat_interface, WL_OUTPUT_INTERFACE, WL_SEAT_INTERFACE, + }; + wayland_scanner::generate_interfaces!("./resources/river-status-unstable-v1.xml"); + } + + use self::__status::{ + ZRIVER_OUTPUT_STATUS_V1_INTERFACE, ZRIVER_SEAT_STATUS_V1_INTERFACE, + ZRIVER_STATUS_MANAGER_V1_INTERFACE, + }; + + // This macro generates the actual types that represent the wayland objects of + // your custom protocol + wayland_scanner::generate_client_code!("./resources/river-status-unstable-v1.xml"); +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs new file mode 100644 index 00000000..65d3c590 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs @@ -0,0 +1,21 @@ +#![allow(dead_code)] + +pub(crate) mod multi; +pub(crate) mod raw; +pub(crate) mod slot; + +use std::io; + +use wayland_client::globals::GlobalError; + +/// An error that may occur when creating a pool. +#[derive(Debug, thiserror::Error)] +pub enum CreatePoolError { + /// The [`wl_shm`] global is not bound. + #[error(transparent)] + Global(#[from] GlobalError), + + /// Error while allocating the shared memory. + #[error(transparent)] + Create(#[from] io::Error), +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs new file mode 100644 index 00000000..0b1fdc1b --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs @@ -0,0 +1,437 @@ +//! A pool implementation which automatically manage buffers. +//! +//! This pool is built on the [`RawPool`]. +//! +//! The [`MultiPool`] takes a key which is used to identify buffers and tries to return the buffer associated to the key +//! if possible. If no buffer in the pool is associated to the key, it will create a new one. +//! +//! # Example +//! +//! ```rust +//! use smithay_client_toolkit::reexports::client::{ +//! QueueHandle, +//! protocol::wl_surface::WlSurface, +//! protocol::wl_shm::Format, +//! }; +//! use smithay_client_toolkit::shm::multi::MultiPool; +//! +//! struct WlFoo { +//! // The surface we'll draw on and the index of buffer associated to it +//! surface: (WlSurface, usize), +//! pool: MultiPool<(WlSurface, usize)> +//! } +//! +//! impl WlFoo { +//! fn draw(&mut self, qh: &QueueHandle<WlFoo>) { +//! let surface = &self.surface.0; +//! // We'll increment "i" until the pool can create a new buffer +//! // if there's no buffer associated with our surface and "i" or if +//! // a buffer with the obuffer associated with our surface and "i" is free for use. +//! // +//! // There's no limit to the amount of buffers we can allocate to our surface but since +//! // shm buffers are released fairly fast, it's unlikely we'll need more than double buffering. +//! for i in 0..2 { +//! self.surface.1 = i; +//! if let Ok((offset, buffer, slice)) = self.pool.create_buffer( +//! 100, +//! 100 * 4, +//! 100, +//! &self.surface, +//! Format::Argb8888, +//! ) { +//! /* +//! insert drawing code here +//! */ +//! surface.attach(Some(buffer), 0, 0); +//! surface.commit(); +//! // We exit the function after the draw. +//! return; +//! } +//! } +//! /* +//! If there's no buffer available we can for example request a frame callback +//! and trigger a redraw when it fires. +//! (not shown in this example) +//! */ +//! } +//! } +//! +//! fn draw(slice: &mut [u8]) { +//! todo!() +//! } +//! +//! ``` +//! + +use std::borrow::Borrow; +use std::io; +use std::os::unix::io::OwnedFd; + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use wayland_client::backend::protocol::Message; +use wayland_client::backend::{Backend, ObjectData, ObjectId}; +use wayland_client::{ + protocol::{wl_buffer, wl_shm}, + Proxy, +}; + +use crate::wayland::shm::CreatePoolError; + +use super::raw::RawPool; + +#[derive(Debug, thiserror::Error)] +pub(crate) enum PoolError { + #[error("buffer is currently used")] + InUse, + #[error("buffer is overlapping another")] + Overlap, + #[error("buffer could not be found")] + NotFound, +} + +/// This pool manages buffers associated with keys. +/// Only one buffer can be attributed to a given key. +#[derive(Debug)] +pub(crate) struct MultiPool<K> { + buffer_list: Vec<BufferSlot<K>>, + pub(crate) inner: RawPool, +} + +#[derive(Debug, thiserror::Error)] +pub(crate) struct BufferSlot<K> { + free: Arc<AtomicBool>, + size: usize, + used: usize, + offset: usize, + buffer: Option<wl_buffer::WlBuffer>, + key: K, +} + +impl<K> Drop for BufferSlot<K> { + fn drop(&mut self) { + self.destroy().ok(); + } +} + +impl<K> BufferSlot<K> { + pub(crate) fn destroy(&self) -> Result<(), PoolError> { + self.buffer + .as_ref() + .ok_or(PoolError::NotFound) + .and_then(|buffer| { + self.free + .load(Ordering::Relaxed) + .then(|| buffer.destroy()) + .ok_or(PoolError::InUse) + }) + } +} + +impl<K> MultiPool<K> { + pub(crate) fn new(shm: &wl_shm::WlShm) -> Result<Self, CreatePoolError> { + Ok(Self { + inner: RawPool::new(4096, shm)?, + buffer_list: Vec::new(), + }) + } + + /// Resizes the memory pool, notifying the server the pool has changed in size. + /// + /// The [`wl_shm`] protocol only allows the pool to be made bigger. If the new size is smaller than the + /// current size of the pool, this function will do nothing. + pub(crate) fn resize(&mut self, size: usize) -> io::Result<()> { + self.inner.resize(size) + } + + /// Removes the buffer with the given key from the pool and rearranges the others. + pub(crate) fn remove<Q>(&mut self, key: &Q) -> Option<BufferSlot<K>> + where + Q: PartialEq, + K: Borrow<Q>, + { + self.buffer_list + .iter() + .enumerate() + .find(|(_, slot)| slot.key.borrow().eq(key)) + .map(|(i, _)| i) + .map(|i| self.buffer_list.remove(i)) + } + + /// Insert a buffer into the pool. + /// + /// The parameters are: + /// + /// - `width`: the width of this buffer (in pixels) + /// - `height`: the height of this buffer (in pixels) + /// - `stride`: distance (in bytes) between the beginning of a row and the next one + /// - `key`: a borrowed form of the stored key type + /// - `format`: the encoding format of the pixels. + pub(crate) fn insert<Q>( + &mut self, + width: i32, + stride: i32, + height: i32, + key: &Q, + format: wl_shm::Format, + ) -> Result<usize, PoolError> + where + K: Borrow<Q>, + Q: PartialEq + ToOwned<Owned = K>, + { + let mut offset = 0; + let mut found_key = false; + let size = (stride * height) as usize; + let mut index = Err(PoolError::NotFound); + + for (i, buf_slot) in self.buffer_list.iter_mut().enumerate() { + if buf_slot.key.borrow().eq(key) { + found_key = true; + if buf_slot.free.load(Ordering::Relaxed) { + // Destroys the buffer if it's resized + if size != buf_slot.used { + if let Some(buffer) = buf_slot.buffer.take() { + buffer.destroy(); + } + } + // Increases the size of the Buffer if it's too small and add 5% padding. + // It is possible this buffer overlaps the following but the else if + // statement prevents this buffer from being returned if that's the case. + buf_slot.size = buf_slot.size.max(size + size / 20); + index = Ok(i); + } else { + index = Err(PoolError::InUse); + } + // If a buffer is resized, it is likely that the followings might overlap + } else if offset > buf_slot.offset { + // When the buffer is free, it's safe to shift it because we know the compositor won't try to read it. + if buf_slot.free.load(Ordering::Relaxed) { + if offset != buf_slot.offset { + if let Some(buffer) = buf_slot.buffer.take() { + buffer.destroy(); + } + } + buf_slot.offset = offset; + } else { + // If one of the overlapping buffers is busy, then no buffer can be returned because it could result in a data race. + index = Err(PoolError::InUse); + } + } else if found_key { + break; + } + let size = (buf_slot.size + 63) & !63; + offset += size; + } + + if !found_key { + if let Err(err) = index { + return self + .dyn_resize(offset, width, stride, height, key.to_owned(), format) + .map(|()| self.buffer_list.len() - 1) + .ok_or(err); + } + } + + index + } + + /// Retreives the buffer associated with the given key. + /// + /// The parameters are: + /// + /// - `width`: the width of this buffer (in pixels) + /// - `height`: the height of this buffer (in pixels) + /// - `stride`: distance (in bytes) between the beginning of a row and the next one + /// - `key`: a borrowed form of the stored key type + /// - `format`: the encoding format of the pixels. + pub(crate) fn get<Q>( + &mut self, + width: i32, + stride: i32, + height: i32, + key: &Q, + format: wl_shm::Format, + ) -> Option<(usize, &wl_buffer::WlBuffer, &mut [u8])> + where + Q: PartialEq, + K: Borrow<Q>, + { + let len = self.inner.len(); + let size = (stride * height) as usize; + let buf_slot = self + .buffer_list + .iter_mut() + .find(|buf_slot| buf_slot.key.borrow().eq(key))?; + + if buf_slot.size >= size { + return None; + } + + buf_slot.used = size; + let offset = buf_slot.offset; + if buf_slot.buffer.is_none() { + if offset + size > len { + self.inner.resize(offset + size + size / 20).ok()?; + } + let free = Arc::new(AtomicBool::new(true)); + let data = BufferObjectData { free: free.clone() }; + let buffer = self.inner.create_buffer_raw( + offset as i32, + width, + height, + stride, + format, + Arc::new(data), + ); + buf_slot.free = free; + buf_slot.buffer = Some(buffer); + } + let buf = buf_slot.buffer.as_ref()?; + buf_slot.free.store(false, Ordering::Relaxed); + Some((offset, buf, &mut self.inner.mmap()[offset..][..size])) + } + + /// Returns the buffer associated with the given key and its offset (usize) in the mempool. + /// + /// The parameters are: + /// + /// - `width`: the width of this buffer (in pixels) + /// - `height`: the height of this buffer (in pixels) + /// - `stride`: distance (in bytes) between the beginning of a row and the next one + /// - `key`: a borrowed form of the stored key type + /// - `format`: the encoding format of the pixels. + /// + /// The offset can be used to determine whether or not a buffer was moved in the mempool + /// and by consequence if it should be damaged partially or fully. + pub(crate) fn create_buffer<Q>( + &mut self, + width: i32, + stride: i32, + height: i32, + key: &Q, + format: wl_shm::Format, + ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError> + where + K: Borrow<Q>, + Q: PartialEq + ToOwned<Owned = K>, + { + let index = self.insert(width, stride, height, key, format)?; + self.get_at(index, width, stride, height, format) + } + + /// Retreives the buffer at the given index. + fn get_at( + &mut self, + index: usize, + width: i32, + stride: i32, + height: i32, + format: wl_shm::Format, + ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError> { + let len = self.inner.len(); + let size = (stride * height) as usize; + let buf_slot = self.buffer_list.get_mut(index).ok_or(PoolError::NotFound)?; + + if size > buf_slot.size { + return Err(PoolError::Overlap); + } + + buf_slot.used = size; + let offset = buf_slot.offset; + if buf_slot.buffer.is_none() { + if offset + size > len { + self.inner + .resize(offset + size + size / 20) + .map_err(|_| PoolError::Overlap)?; + } + let free = Arc::new(AtomicBool::new(true)); + let data = BufferObjectData { free: free.clone() }; + let buffer = self.inner.create_buffer_raw( + offset as i32, + width, + height, + stride, + format, + Arc::new(data), + ); + buf_slot.free = free; + buf_slot.buffer = Some(buffer); + } + buf_slot.free.store(false, Ordering::Relaxed); + let buf = buf_slot.buffer.as_ref().unwrap(); + Ok((offset, buf, &mut self.inner.mmap()[offset..][..size])) + } + + /// Calcule the offet and size of a buffer based on its stride. + fn offset(mut offset: i32, stride: i32, height: i32) -> (usize, usize) { + // bytes per pixel + let size = stride * height; + // 5% padding. + offset += offset / 20; + offset = (offset + 63) & !63; + (offset as usize, size as usize) + } + + #[allow(clippy::too_many_arguments)] + /// Resizes the pool and appends a new buffer. + fn dyn_resize( + &mut self, + offset: usize, + width: i32, + stride: i32, + height: i32, + key: K, + format: wl_shm::Format, + ) -> Option<()> { + let (offset, size) = Self::offset(offset as i32, stride, height); + if self.inner.len() < offset + size { + self.resize(offset + size + size / 20).ok()?; + } + let free = Arc::new(AtomicBool::new(true)); + let data = BufferObjectData { free: free.clone() }; + let buffer = self.inner.create_buffer_raw( + offset as i32, + width, + height, + stride, + format, + Arc::new(data), + ); + self.buffer_list.push(BufferSlot { + offset, + used: 0, + free, + buffer: Some(buffer), + size, + key, + }); + Some(()) + } +} + +struct BufferObjectData { + free: Arc<AtomicBool>, +} + +impl ObjectData for BufferObjectData { + fn event( + self: Arc<Self>, + _backend: &Backend, + msg: Message<ObjectId, OwnedFd>, + ) -> Option<Arc<dyn ObjectData>> { + debug_assert!(wayland_client::backend::protocol::same_interface( + msg.sender_id.interface(), + wl_buffer::WlBuffer::interface() + )); + debug_assert!(msg.opcode == 0); + + // wl_buffer only has a single event: wl_buffer.release + self.free.store(true, Ordering::Relaxed); + + None + } + + fn destroyed(&self, _: ObjectId) {} +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs new file mode 100644 index 00000000..a12afaa0 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs @@ -0,0 +1,290 @@ +//! A raw shared memory pool handler. +//! +//! This is intended as a safe building block for higher level shared memory pool abstractions and is not +//! encouraged for most library users. + +use rustix::{ + io::Errno, + shm::{Mode, OFlags}, +}; +use std::{ + fs::File, + io, + ops::Deref, + os::unix::prelude::{AsFd, BorrowedFd, OwnedFd}, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +use memmap2::MmapMut; +use wayland_client::{ + backend::ObjectData, + protocol::{wl_buffer, wl_shm, wl_shm_pool}, + Dispatch, Proxy, QueueHandle, WEnum, +}; + +use super::CreatePoolError; + +/// A raw handler for file backed shared memory pools. +/// +/// This type of pool will create the SHM memory pool and provide a way to resize the pool. +/// +/// This pool does not release buffers. If you need this, use one of the higher level pools. +#[derive(Debug)] +pub struct RawPool { + pool: DestroyOnDropPool, + len: usize, + mem_file: File, + mmap: MmapMut, +} + +impl RawPool { + pub fn new(len: usize, shm: &wl_shm::WlShm) -> Result<RawPool, CreatePoolError> { + let shm_fd = RawPool::create_shm_fd()?; + let mem_file = File::from(shm_fd); + mem_file.set_len(len as u64)?; + + let pool = shm + .send_constructor( + wl_shm::Request::CreatePool { + fd: mem_file.as_fd(), + size: len as i32, + }, + Arc::new(ShmPoolData), + ) + .unwrap_or_else(|_| Proxy::inert(shm.backend().clone())); + let mmap = unsafe { MmapMut::map_mut(&mem_file)? }; + + Ok(RawPool { + pool: DestroyOnDropPool(pool), + len, + mem_file, + mmap, + }) + } + + /// Resizes the memory pool, notifying the server the pool has changed in size. + /// + /// The [`wl_shm`] protocol only allows the pool to be made bigger. If the new size is smaller than the + /// current size of the pool, this function will do nothing. + pub fn resize(&mut self, size: usize) -> io::Result<()> { + if size > self.len { + self.len = size; + self.mem_file.set_len(size as u64)?; + self.pool.resize(size as i32); + self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?; + } + + Ok(()) + } + + /// Returns a reference to the underlying shared memory file using the memmap2 crate. + pub fn mmap(&mut self) -> &mut MmapMut { + &mut self.mmap + } + + /// Returns the size of the mempool + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len + } + + /// Create a new buffer to this pool. + /// + /// ## Parameters + /// - `offset`: the offset (in bytes) from the beginning of the pool at which this buffer starts. + /// - `width` and `height`: the width and height of the buffer in pixels. + /// - `stride`: distance (in bytes) between the beginning of a row and the next one. + /// - `format`: the encoding format of the pixels. + /// + /// The encoding format of the pixels must be supported by the compositor or else a protocol error is + /// risen. You can ensure the format is supported by listening to [`Shm::formats`](crate::shm::Shm::formats). + /// + /// Note this function only creates the [`wl_buffer`] object, you will need to write to the pixels using the + /// [`io::Write`] implementation or [`RawPool::mmap`]. + #[allow(clippy::too_many_arguments)] + pub fn create_buffer<D, U>( + &mut self, + offset: i32, + width: i32, + height: i32, + stride: i32, + format: wl_shm::Format, + udata: U, + qh: &QueueHandle<D>, + ) -> wl_buffer::WlBuffer + where + D: Dispatch<wl_buffer::WlBuffer, U> + 'static, + U: Send + Sync + 'static, + { + self.pool + .create_buffer(offset, width, height, stride, format, qh, udata) + } + + /// Create a new buffer to this pool. + /// + /// This is identical to [`Self::create_buffer`], but allows using a custom [`ObjectData`] + /// implementation instead of relying on the [Dispatch] interface. + #[allow(clippy::too_many_arguments)] + pub fn create_buffer_raw( + &mut self, + offset: i32, + width: i32, + height: i32, + stride: i32, + format: wl_shm::Format, + data: Arc<dyn ObjectData + 'static>, + ) -> wl_buffer::WlBuffer { + self.pool + .send_constructor( + wl_shm_pool::Request::CreateBuffer { + offset, + width, + height, + stride, + format: WEnum::Value(format), + }, + data, + ) + .unwrap_or_else(|_| Proxy::inert(self.pool.backend().clone())) + } + + /// Returns the pool object used to communicate with the server. + pub fn pool(&self) -> &wl_shm_pool::WlShmPool { + &self.pool + } +} + +impl AsFd for RawPool { + fn as_fd(&self) -> BorrowedFd<'_> { + self.mem_file.as_fd() + } +} + +impl From<RawPool> for OwnedFd { + fn from(pool: RawPool) -> Self { + pool.mem_file.into() + } +} + +impl io::Write for RawPool { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + io::Write::write(&mut self.mem_file, buf) + } + + fn flush(&mut self) -> io::Result<()> { + io::Write::flush(&mut self.mem_file) + } +} + +impl io::Seek for RawPool { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> { + io::Seek::seek(&mut self.mem_file, pos) + } +} + +impl RawPool { + fn create_shm_fd() -> io::Result<OwnedFd> { + #[cfg(target_os = "linux")] + { + match RawPool::create_memfd() { + Ok(fd) => return Ok(fd), + + // Not supported, use fallback. + Err(Errno::NOSYS) => (), + + Err(err) => return Err(Into::<io::Error>::into(err)), + } + } + + let time = SystemTime::now(); + let mut mem_file_handle = format!( + "/smithay-client-toolkit-{}", + time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + + loop { + let flags = OFlags::CREATE | OFlags::EXCL | OFlags::RDWR; + + let mode = Mode::RUSR | Mode::WUSR; + + match rustix::shm::open(mem_file_handle.as_str(), flags, mode) { + Ok(fd) => match rustix::shm::unlink(mem_file_handle.as_str()) { + Ok(()) => return Ok(fd), + + Err(errno) => { + return Err(errno.into()); + } + }, + + Err(Errno::EXIST) => { + // Change the handle if we happen to be duplicate. + let time = SystemTime::now(); + + mem_file_handle = format!( + "/smithay-client-toolkit-{}", + time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + } + + Err(Errno::INTR) => (), + + Err(err) => return Err(err.into()), + } + } + } + + #[cfg(target_os = "linux")] + fn create_memfd() -> rustix::io::Result<OwnedFd> { + use rustix::fs::{MemfdFlags, SealFlags}; + + loop { + let name = c"smithay-client-toolkit"; + let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC; + + match rustix::fs::memfd_create(name, flags) { + Ok(fd) => { + // We only need to seal for the purposes of optimization, ignore the errors. + let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL); + return Ok(fd); + } + + Err(Errno::INTR) => (), + + Err(err) => return Err(err), + } + } + } +} + +#[derive(Debug)] +struct DestroyOnDropPool(wl_shm_pool::WlShmPool); + +impl Deref for DestroyOnDropPool { + type Target = wl_shm_pool::WlShmPool; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for DestroyOnDropPool { + fn drop(&mut self) { + self.0.destroy(); + } +} + +#[derive(Debug)] +struct ShmPoolData; + +impl ObjectData for ShmPoolData { + fn event( + self: Arc<Self>, + _: &wayland_client::backend::Backend, + _: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>, + ) -> Option<Arc<(dyn ObjectData + 'static)>> { + unreachable!("wl_shm_pool has no events") + } + + fn destroyed(&self, _: wayland_client::backend::ObjectId) {} +} diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs new file mode 100644 index 00000000..ab52c5f6 --- /dev/null +++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs @@ -0,0 +1,596 @@ +//! A pool implementation based on buffer slots + +use std::io; +use std::{ + os::unix::io::{AsRawFd, OwnedFd}, + sync::{ + atomic::{AtomicU8, AtomicUsize, Ordering}, + Arc, Mutex, Weak, + }, +}; + +use wayland_client::backend::protocol::Message; +use wayland_client::backend::{ObjectData, ObjectId}; +use wayland_client::{ + protocol::{wl_buffer, wl_shm, wl_surface}, + Proxy, +}; + +use crate::wayland::shm::raw::RawPool; +use crate::wayland::shm::CreatePoolError; + +#[derive(Debug, thiserror::Error)] +pub(crate) enum CreateBufferError { + /// Slot creation error. + #[error(transparent)] + Io(#[from] io::Error), + + /// Pool mismatch. + #[error("Incorrect pool for slot")] + PoolMismatch, + + /// Slot size mismatch + #[error("Requested buffer size is too large for slot")] + SlotTooSmall, +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum ActivateSlotError { + /// Buffer was already active + #[error("Buffer was already active")] + AlreadyActive, +} + +#[derive(Debug)] +pub(crate) struct SlotPool { + pub(crate) inner: RawPool, + free_list: Arc<Mutex<Vec<FreelistEntry>>>, +} + +#[derive(Debug)] +struct FreelistEntry { + offset: usize, + len: usize, +} + +/// A chunk of memory allocated from a [`SlotPool`] +/// +/// Retaining this object is only required if you wish to resize or change the buffer's format +/// without changing the contents of the backing memory. +#[derive(Debug)] +pub(crate) struct Slot { + inner: Arc<SlotInner>, +} + +#[derive(Debug)] +struct SlotInner { + free_list: Weak<Mutex<Vec<FreelistEntry>>>, + offset: usize, + len: usize, + active_buffers: AtomicUsize, + /// Count of all "real" references to this slot. This includes all Slot objects and any + /// [`BufferData`] object that is not in the DEAD state. When this reaches zero, the memory for + /// this slot will return to the [`free_list`]. It is not possible for it to reach zero and have a + /// Slot or Buffer referring to it. + all_refs: AtomicUsize, +} + +/// A wrapper around a [`wl_buffer::WlBuffer`] which has been allocated via a [`SlotPool`]. +/// +/// When this object is dropped, the buffer will be destroyed immediately if it is not active, or +/// upon the server's release if it is. +#[derive(Debug)] +pub(crate) struct Buffer { + inner: wl_buffer::WlBuffer, + height: i32, + stride: i32, + slot: Slot, +} + +/// [`ObjectData`] for the [`WlBuffer`] +#[derive(Debug)] +struct BufferData { + inner: Arc<SlotInner>, + state: AtomicU8, +} + +// These constants define the value of BufferData::state, since AtomicEnum does not exist. +impl BufferData { + /// Buffer is counted in [`active_buffers`] list; will return to INACTIVE on Release. + const ACTIVE: u8 = 0; + + /// Buffer is not counted in [`active_buffers`] list, but also has not been destroyed. + const INACTIVE: u8 = 1; + + /// Buffer is counted in [`active_buffers`] list; will move to DEAD on Release + const DESTROY_ON_RELEASE: u8 = 2; + + /// Buffer has been destroyed + const DEAD: u8 = 3; + + /// Value that is [`ORed`] on buffer release to transition to the next state + const RELEASE_SET: u8 = 1; + + /// Value that is [`ORed`] on buffer destroy to transition to the next state + const DESTROY_SET: u8 = 2; + + /// Call after successfully transitioning the state to DEAD + fn record_death(&self) { + drop(Slot { + inner: self.inner.clone(), + }); + } +} + +impl SlotPool { + pub(crate) fn new(len: usize, shm: &wl_shm::WlShm) -> Result<Self, CreatePoolError> { + let inner = RawPool::new(len, shm)?; + let free_list = Arc::new(Mutex::new(vec![FreelistEntry { + offset: 0, + len: inner.len(), + }])); + Ok(SlotPool { inner, free_list }) + } + + /// Create a new buffer in a new slot. + /// + /// This returns the buffer and the canvas. The parameters are: + /// + /// - `width`: the width of this buffer (in pixels) + /// - `height`: the height of this buffer (in pixels) + /// - `stride`: distance (in bytes) between the beginning of a row and the next one + /// - `format`: the encoding format of the pixels. Using a format that was not + /// advertised to the `wl_shm` global by the server is a protocol error and will + /// terminate your connection. + /// + /// The [Slot] for this buffer will have exactly the size required for the data. It can be + /// accessed via [`Buffer::slot`] to create additional buffers that point to the same data. This + /// is required if you wish to change formats, buffer dimensions, or attach a canvas to + /// multiple surfaces. + /// + /// For more control over sizing, use [`Self::new_slot`] and [`Self::create_buffer_in`]. + pub(crate) fn create_buffer( + &mut self, + width: i32, + height: i32, + stride: i32, + format: wl_shm::Format, + ) -> Result<(Buffer, &mut [u8]), CreateBufferError> { + let len = (height as usize) * (stride as usize); + let slot = self.new_slot(len)?; + let buffer = self.create_buffer_in(&slot, width, height, stride, format)?; + let canvas = self.raw_data_mut(&slot); + Ok((buffer, canvas)) + } + + /// Get the bytes corresponding to a given slot or buffer if drawing to the slot is permitted. + /// + /// Returns `None` if there are active buffers in the slot or if the slot does not correspond + /// to this pool. + pub(crate) fn canvas(&mut self, key: &impl CanvasKey) -> Option<&mut [u8]> { + key.canvas(self) + } + + /// Returns the size, in bytes, of this pool. + #[allow(clippy::len_without_is_empty)] + pub(crate) fn len(&self) -> usize { + self.inner.len() + } + + /// Resizes the memory pool, notifying the server the pool has changed in size. + /// + /// This is an optimization; the pool automatically resizes when you allocate new slots. + pub(crate) fn resize(&mut self, size: usize) -> io::Result<()> { + let old_len = self.inner.len(); + self.inner.resize(size)?; + let new_len = self.inner.len(); + if old_len == new_len { + return Ok(()); + } + // add the new memory to the freelist + let mut free = self.free_list.lock().unwrap(); + if let Some(FreelistEntry { offset, len }) = free.last_mut() { + if *offset + *len == old_len { + *len += new_len - old_len; + return Ok(()); + } + } + free.push(FreelistEntry { + offset: old_len, + len: new_len - old_len, + }); + Ok(()) + } + + fn alloc(&mut self, size: usize) -> io::Result<usize> { + let mut free = self.free_list.lock().unwrap(); + for FreelistEntry { offset, len } in free.iter_mut() { + if *len >= size { + let rv = *offset; + *len -= size; + *offset += size; + return Ok(rv); + } + } + let mut rv = self.inner.len(); + let mut pop_tail = false; + if let Some(FreelistEntry { offset, len }) = free.last() { + if offset + len == self.inner.len() { + rv -= len; + pop_tail = true; + } + } + // resize like Vec::reserve, always at least doubling + let target = std::cmp::max(rv + size, self.inner.len() * 2); + self.inner.resize(target)?; + // adjust the end of the freelist here + if pop_tail { + free.pop(); + } + if target > rv + size { + free.push(FreelistEntry { + offset: rv + size, + len: target - rv - size, + }); + } + Ok(rv) + } + + fn free(free_list: &Mutex<Vec<FreelistEntry>>, mut offset: usize, mut len: usize) { + let mut free = free_list.lock().unwrap(); + let mut nf = Vec::with_capacity(free.len() + 1); + for &FreelistEntry { + offset: ioff, + len: ilen, + } in free.iter() + { + if ioff + ilen == offset { + offset = ioff; + len += ilen; + continue; + } + if ioff == offset + len { + len += ilen; + continue; + } + if ioff > offset + len && len != 0 { + nf.push(FreelistEntry { offset, len }); + len = 0; + } + if ilen != 0 { + nf.push(FreelistEntry { + offset: ioff, + len: ilen, + }); + } + } + if len != 0 { + nf.push(FreelistEntry { offset, len }); + } + *free = nf; + } + + /// Create a new slot with the given size in bytes. + pub(crate) fn new_slot(&mut self, mut len: usize) -> io::Result<Slot> { + len = (len + 63) & !63; + let offset = self.alloc(len)?; + + Ok(Slot { + inner: Arc::new(SlotInner { + free_list: Arc::downgrade(&self.free_list), + offset, + len, + active_buffers: AtomicUsize::new(0), + all_refs: AtomicUsize::new(1), + }), + }) + } + + /// Get the bytes corresponding to a given slot. + /// + /// Note: prefer using [`Self::canvas`], which will prevent drawing to a buffer that has not been + /// released by the server. + /// + /// Returns an empty buffer if the slot does not belong to this pool. + pub(crate) fn raw_data_mut(&mut self, slot: &Slot) -> &mut [u8] { + if slot.inner.free_list.as_ptr() == Arc::as_ptr(&self.free_list) { + &mut self.inner.mmap()[slot.inner.offset..][..slot.inner.len] + } else { + &mut [] + } + } + + /// Create a new buffer corresponding to a slot. + /// + /// The parameters are: + /// + /// - `width`: the width of this buffer (in pixels) + /// - `height`: the height of this buffer (in pixels) + /// - `stride`: distance (in bytes) between the beginning of a row and the next one + /// - `format`: the encoding format of the pixels. Using a format that was not + /// advertised to the `wl_shm` global by the server is a protocol error and will + /// terminate your connection + pub(crate) fn create_buffer_in( + &mut self, + slot: &Slot, + width: i32, + height: i32, + stride: i32, + format: wl_shm::Format, + ) -> Result<Buffer, CreateBufferError> { + let offset = slot.inner.offset as i32; + let len = (height as usize) * (stride as usize); + if len > slot.inner.len { + return Err(CreateBufferError::SlotTooSmall); + } + + if slot.inner.free_list.as_ptr() != Arc::as_ptr(&self.free_list) { + return Err(CreateBufferError::PoolMismatch); + } + + let slot = slot.clone(); + // take a ref for the BufferData, which will be destroyed by BufferData::record_death + slot.inner.all_refs.fetch_add(1, Ordering::Relaxed); + let data = Arc::new(BufferData { + inner: slot.inner.clone(), + state: AtomicU8::new(BufferData::INACTIVE), + }); + let buffer = self + .inner + .create_buffer_raw(offset, width, height, stride, format, data); + Ok(Buffer { + inner: buffer, + height, + stride, + slot, + }) + } +} + +impl Clone for Slot { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + inner.all_refs.fetch_add(1, Ordering::Relaxed); + Slot { inner } + } +} + +impl Drop for Slot { + fn drop(&mut self) { + if self.inner.all_refs.fetch_sub(1, Ordering::Relaxed) == 1 { + if let Some(free_list) = self.inner.free_list.upgrade() { + SlotPool::free(&free_list, self.inner.offset, self.inner.len); + } + } + } +} + +impl Drop for SlotInner { + fn drop(&mut self) { + debug_assert_eq!(*self.all_refs.get_mut(), 0); + } +} + +/// A helper trait for [`SlotPool::canvas`]. +pub(crate) trait CanvasKey { + fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]>; +} + +impl Slot { + /// Return true if there are buffers referencing this slot whose contents are being accessed + /// by the server. + pub(crate) fn has_active_buffers(&self) -> bool { + self.inner.active_buffers.load(Ordering::Relaxed) != 0 + } + + /// Returns the size, in bytes, of this slot. + #[allow(clippy::len_without_is_empty)] + pub(crate) fn len(&self) -> usize { + self.inner.len + } + + /// Get the bytes corresponding to a given slot if drawing to the slot is permitted. + /// + /// Returns `None` if there are active buffers in the slot or if the slot does not correspond + /// to this pool. + pub(crate) fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { + if self.has_active_buffers() { + return None; + } + if self.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) { + Some(&mut pool.inner.mmap()[self.inner.offset..][..self.inner.len]) + } else { + None + } + } +} + +impl CanvasKey for Slot { + fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { + self.canvas(pool) + } +} + +impl Buffer { + /// Attach a buffer to a surface. + /// + /// This marks the slot as active until the server releases the buffer, which will happen + /// automatically assuming the surface is committed without attaching a different buffer. + /// + /// Note: if you need to ensure that [`canvas()`](Buffer::canvas) calls never return data that + /// could be attached to a surface in a multi-threaded client, make this call while you have + /// exclusive access to the corresponding [`SlotPool`]. + pub(crate) fn attach_to(&self, surface: &wl_surface::WlSurface) -> Result<(), ActivateSlotError> { + self.activate()?; + surface.attach(Some(&self.inner), 0, 0); + Ok(()) + } + + /// Get the inner buffer. + pub(crate) fn wl_buffer(&self) -> &wl_buffer::WlBuffer { + &self.inner + } + + pub(crate) fn height(&self) -> i32 { + self.height + } + + pub(crate) fn stride(&self) -> i32 { + self.stride + } + + fn data(&self) -> Option<&BufferData> { + self.inner.object_data()?.downcast_ref() + } + + /// Get the bytes corresponding to this buffer if drawing is permitted. + /// + /// This may be smaller than the canvas associated with the slot. + pub(crate) fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { + let len = (self.height as usize) * (self.stride as usize); + if self.slot.inner.active_buffers.load(Ordering::Relaxed) != 0 { + return None; + } + if self.slot.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) { + Some(&mut pool.inner.mmap()[self.slot.inner.offset..][..len]) + } else { + None + } + } + + /// Get the slot corresponding to this buffer. + pub(crate) fn slot(&self) -> Slot { + self.slot.clone() + } + + /// Manually mark a buffer as active. + /// + /// An active buffer prevents drawing on its slot until a Release event is received or until + /// manually deactivated. + pub(crate) fn activate(&self) -> Result<(), ActivateSlotError> { + let data = self.data().expect("UserData type mismatch"); + + // This bitwise AND will transition INACTIVE -> ACTIVE, or do nothing if the buffer was + // already ACTIVE. No other ordering is required, as the server will not send a Release + // until we send our attach after returning Ok. + match data + .state + .fetch_and(!BufferData::RELEASE_SET, Ordering::Relaxed) + { + BufferData::INACTIVE => { + data.inner.active_buffers.fetch_add(1, Ordering::Relaxed); + Ok(()) + } + BufferData::ACTIVE => Err(ActivateSlotError::AlreadyActive), + _ => unreachable!("Invalid state in BufferData"), + } + } + + /// Manually mark a buffer as inactive. + /// + /// This should be used when the buffer was manually marked as active or when a buffer was + /// attached to a surface but not committed. Calling this function on a buffer that was + /// committed to a surface risks making the surface contents undefined. + pub(crate) fn deactivate(&self) -> Result<(), ActivateSlotError> { + let data = self.data().expect("UserData type mismatch"); + + // Same operation as the Release event, but we know the Buffer was not dropped. + match data + .state + .fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed) + { + BufferData::ACTIVE => { + data.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); + Ok(()) + } + BufferData::INACTIVE => Err(ActivateSlotError::AlreadyActive), + _ => unreachable!("Invalid state in BufferData"), + } + } +} + +impl CanvasKey for Buffer { + fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { + self.canvas(pool) + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + if let Some(data) = self.data() { + match data + .state + .fetch_or(BufferData::DESTROY_SET, Ordering::Relaxed) + { + BufferData::ACTIVE => { + // server is using the buffer, let ObjectData handle the destroy + } + BufferData::INACTIVE => { + data.record_death(); + self.inner.destroy(); + } + _ => unreachable!("Invalid state in BufferData"), + } + } + } +} + +impl ObjectData for BufferData { + fn event( + self: Arc<Self>, + handle: &wayland_client::backend::Backend, + msg: Message<ObjectId, OwnedFd>, + ) -> Option<Arc<dyn ObjectData>> { + debug_assert!(wayland_client::backend::protocol::same_interface( + msg.sender_id.interface(), + wl_buffer::WlBuffer::interface() + )); + debug_assert!(msg.opcode == 0); + + match self + .state + .fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed) + { + BufferData::ACTIVE => { + self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); + } + BufferData::INACTIVE => { + // possible spurious release, or someone called deactivate incorrectly + eprintln!("Unexpected WlBuffer::Release on an inactive buffer"); + } + BufferData::DESTROY_ON_RELEASE => { + self.record_death(); + self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); + + // The Destroy message is identical to Release message (no args, same ID), so just reply + handle + .send_request(msg.map_fd(|x| x.as_raw_fd()), None, None) + .expect("Unexpected invalid ID"); + } + BufferData::DEAD => { + // no-op, this object is already unusable + } + _ => unreachable!("Invalid state in BufferData"), + } + + None + } + + fn destroyed(&self, _: ObjectId) {} +} + +impl Drop for BufferData { + fn drop(&mut self) { + let state = *self.state.get_mut(); + if state == BufferData::ACTIVE || state == BufferData::DESTROY_ON_RELEASE { + // Release the active-buffer count + self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); + } + + if state != BufferData::DEAD { + // nobody has ever transitioned state to DEAD, so we are responsible for freeing the + // extra reference + self.record_death(); + } + } +} diff --git a/pkgs/by-name/sc/screenshot_persistent/screenshot_persistent.sh b/pkgs/by-name/sc/screenshot_persistent/screenshot_persistent.sh index 0eeb75c0..f67293cd 100755 --- a/pkgs/by-name/sc/screenshot_persistent/screenshot_persistent.sh +++ b/pkgs/by-name/sc/screenshot_persistent/screenshot_persistent.sh @@ -10,7 +10,7 @@ # shellcheck shell=bash -tmp="$(mktemp)" +tmp="$(mktemp -t screenshot_persistent_XXXXX)" if grim -g "$(slurp)" "$tmp"; then name="$(rofi -dmenu -p "Name of screenshot: " -l 0)" diff --git a/modules/home.legacy/conf/alacritty/toml/window.toml b/pkgs/by-name/sw/swallow/package.nix index edc9cf6e..16608143 100644 --- a/modules/home.legacy/conf/alacritty/toml/window.toml +++ b/pkgs/by-name/sw/swallow/package.nix @@ -7,22 +7,19 @@ # # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +{ + writeShellApplication, + # Dependencies + river-classic, +}: +writeShellApplication { + name = "swallow"; + text = builtins.readFile ./swallow.sh; -[window] -decorations = "none" -decorations_theme_variant = "None" -dynamic_title = true -opacity = 0.9 -startup_mode = "Windowed" -title = "Alacritty" -[window.class] -general = "Alacritty" -instance = "Alacritty" + # We need to inherit the path, so that we can spawn stuff in a swallowed mode. + inheritPath = true; -[window.dimensions] -columns = 0 -lines = 0 - -[window.padding] -x = 5 -y = 5 + runtimeInputs = [ + river-classic + ]; +} diff --git a/pkgs/by-name/sw/swallow/swallow.sh b/pkgs/by-name/sw/swallow/swallow.sh new file mode 100755 index 00000000..922a21b3 --- /dev/null +++ b/pkgs/by-name/sw/swallow/swallow.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env dash +# Based on: https://codeberg.org/nirodhvana/reservoir/src/commit/2fa8c14877799a03bb927f048c2907dbb418fd68/dot-local/bin/gobble + +# Inspired by https://github.com/swindlesmccoop/not-just-dotfiles/blob/master/.local/bin/swallow + +swallow_tag=$((1 << 9)) +eat() { + riverctl set-view-tags $swallow_tag +} + +throwup() { + riverctl set-focused-tags $swallow_tag && + riverctl send-to-previous-tags && + riverctl focus-previous-tags +} + +if [ -z "$*" ]; then + printf "ERROR: No Arguments Supplied\n" +else + eat && "$@" + + throwup +fi diff --git a/pkgs/by-name/tr/tree-sitter-yts/package.nix b/pkgs/by-name/tr/tree-sitter-yts/package.nix index 62ecf063..eef65714 100644 --- a/pkgs/by-name/tr/tree-sitter-yts/package.nix +++ b/pkgs/by-name/tr/tree-sitter-yts/package.nix @@ -14,46 +14,10 @@ nodejs, tree-sitter, }: -stdenv.mkDerivation { - pname = "yts-grammar"; - version = "1.0.0"; +tree-sitter.buildGrammar { + language = "yts"; + version = "0.0.1+rev=0bb9a60"; inherit (yt) src; sourceRoot = "yt/tree-sitter-yts"; - - nativeBuildInputs = [nodejs tree-sitter]; - - CFLAGS = ["-Isrc" "-O2"]; - CXXFLAGS = ["-Isrc" "-O2"]; - - stripDebugList = ["parser"]; - - configurePhase = '' - tree-sitter generate - ''; - - # When both scanner.{c,cc} exist, we should not link both since they may be the same but in - # different languages. Just randomly prefer C++ if that happens. - buildPhase = '' - runHook preBuild - if [[ -e src/scanner.cc ]]; then - $CXX -fPIC -c src/scanner.cc -o scanner.o $CXXFLAGS - elif [[ -e src/scanner.c ]]; then - $CC -fPIC -c src/scanner.c -o scanner.o $CFLAGS - fi - $CC -fPIC -c src/parser.c -o parser.o $CFLAGS - rm -rf parser - $CXX -shared -o parser *.o - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - mkdir $out - mv parser $out/ - if [[ -d queries ]]; then - cp -r queries $out - fi - runHook postInstall - ''; } diff --git a/pkgs/by-name/ts/tskm/Cargo.lock b/pkgs/by-name/ts/tskm/Cargo.lock index 26ccc3d1..2e253ebd 100644 --- a/pkgs/by-name/ts/tskm/Cargo.lock +++ b/pkgs/by-name/ts/tskm/Cargo.lock @@ -30,12 +30,6 @@ dependencies = [ ] [[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -46,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -61,9 +55,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -76,29 +70,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arraydeque" @@ -114,15 +108,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -132,26 +126,26 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -162,9 +156,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -172,9 +166,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -184,9 +178,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.54" +version = "4.5.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" +checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" dependencies = [ "clap", "clap_lex", @@ -196,9 +190,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -208,9 +202,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -226,9 +220,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -251,7 +245,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -287,10 +281,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] name = "flate2" -version = "1.1.2" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -304,34 +304,34 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] @@ -345,9 +345,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] @@ -367,7 +367,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -384,9 +384,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -408,9 +408,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -421,9 +421,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -434,11 +434,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -449,42 +448,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -494,9 +489,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -515,41 +510,41 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_executable" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" +checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" dependencies = [ - "winapi", + "windows-sys 0.60.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -557,15 +552,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", @@ -583,27 +578,27 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "md5" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miniz_oxide" @@ -612,6 +607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -631,9 +627,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "option-ext" @@ -643,9 +639,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pkg-config" @@ -655,27 +651,27 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -688,11 +684,11 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror", ] @@ -713,15 +709,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -734,18 +724,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -754,14 +754,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -771,6 +772,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -778,9 +785,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stderrlog" @@ -802,28 +809,27 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "strum_macros" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", "syn", ] [[package]] name = "syn" -version = "2.0.104" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -872,18 +878,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -901,9 +907,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -930,20 +936,21 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -960,13 +967,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -999,45 +1006,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1045,62 +1039,40 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -1111,9 +1083,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -1122,9 +1094,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -1133,194 +1105,128 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows-link", ] [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yaml-rust2" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce2a4ff45552406d02501cea6c18d8a7e50228e7736a872951fe2fe75c91be7" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" dependencies = [ "arraydeque", "encoding_rs", @@ -1329,11 +1235,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -1341,9 +1246,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -1353,18 +1258,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -1394,9 +1299,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -1405,9 +1310,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -1416,11 +1321,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" diff --git a/pkgs/by-name/ts/tskm/Cargo.toml b/pkgs/by-name/ts/tskm/Cargo.toml index 2655254b..49774037 100644 --- a/pkgs/by-name/ts/tskm/Cargo.toml +++ b/pkgs/by-name/ts/tskm/Cargo.toml @@ -16,19 +16,19 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = { version = "1.0.98", default-features = false } -clap = { version = "4.5.40", features = [ "derive", "std", "color", "help", "usage", "error-context", "suggestions", ], default-features = false } -clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] } +anyhow = { version = "1.0.100", default-features = false } +clap = { version = "4.5.54", features = [ "derive", "std", "color", "help", "usage", "error-context", "suggestions", ], default-features = false } +clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] } dirs = { version = "6.0.0", default-features = false } -log = { version = "0.4.27", default-features = false } -serde = { version = "1.0.219", features = ["derive"], default-features = false } -serde_json = { version = "1.0.140", default-features = false } +log = { version = "0.4.29", default-features = false } +serde = { version = "1.0.228", features = ["derive"], default-features = false } +serde_json = { version = "1.0.149", default-features = false } stderrlog = { version = "0.6.0", default-features = false } taskchampion = { version = "2.0.3", default-features = false } -url = { version = "2.5.4", features = ["serde"], default-features = false } +url = { version = "2.5.8", features = ["serde", "std"], default-features = false } walkdir = { version = "2.5.0", default-features = false } -md5 = { version = "0.7.0", default-features = false } -yaml-rust2 = "0.10.3" +md5 = { version = "0.8.0", default-features = false } +yaml-rust2 = "0.10.4" [profile.release] lto = true diff --git a/pkgs/by-name/ts/tskm/flake.lock b/pkgs/by-name/ts/tskm/flake.lock index a267d6fb..1e997998 100644 --- a/pkgs/by-name/ts/tskm/flake.lock +++ b/pkgs/by-name/ts/tskm/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1750731501, - "narHash": "sha256-Ah4qq+SbwMaGkuXCibyg+Fwn00el4KmI3XFX6htfDuk=", + "lastModified": 1768661221, + "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "69dfebb3d175bde602f612915c5576a41b18486b", + "rev": "3327b113f2ef698d380df83fbccefad7e83d7769", "type": "github" }, "original": { diff --git a/pkgs/by-name/ts/tskm/flake.nix b/pkgs/by-name/ts/tskm/flake.nix index 583d4923..6217f942 100644 --- a/pkgs/by-name/ts/tskm/flake.nix +++ b/pkgs/by-name/ts/tskm/flake.nix @@ -23,13 +23,13 @@ pkgs.sqlite ]; - packages = with pkgs; [ - cargo - clippy - rustc - rustfmt + packages = [ + pkgs.cargo + pkgs.clippy + pkgs.rustc + pkgs.rustfmt - cargo-edit + pkgs.cargo-edit ]; }; }; diff --git a/pkgs/by-name/ts/tskm/src/browser/mod.rs b/pkgs/by-name/ts/tskm/src/browser/mod.rs index 29abfcbd..2129982f 100644 --- a/pkgs/by-name/ts/tskm/src/browser/mod.rs +++ b/pkgs/by-name/ts/tskm/src/browser/mod.rs @@ -1,5 +1,5 @@ use std::{ - env, + env, fs, io::Write, os::unix::net::UnixStream, path::PathBuf, @@ -14,11 +14,14 @@ use url::Url; use crate::{state::State, task}; #[allow(clippy::too_many_lines)] -pub fn open_in_browser( +pub fn open_in_browser<U>( selected_project: &task::Project, state: &mut State, - urls: Option<Vec<Url>>, -) -> Result<()> { + urls: Option<Vec<U>>, +) -> Result<()> +where + U: Into<Url>, +{ let old_project: Option<task::Project> = task::Project::get_current().context("Failed to get currently active project")?; let old_task: Option<task::Task> = @@ -101,11 +104,35 @@ pub fn open_in_browser( ) })); - if ipc_socket_path.exists() { - let mut stream = UnixStream::connect(ipc_socket_path)?; + let socket = if ipc_socket_path.exists() { + match UnixStream::connect(&ipc_socket_path) { + Ok(ok) => Some(ok), + Err(err) => match err.kind() { + std::io::ErrorKind::ConnectionRefused => { + // There is no qutebrowser listening to our connection. + fs::remove_file(&ipc_socket_path).with_context(|| { + format!( + "Failed to remove orphaned qutebrowser socket: {}", + ipc_socket_path.display() + ) + })?; + None + } + _ => Err(err).with_context(|| { + format!( + "Failed to connect to qutebrowser's ipc socket at: {}", + ipc_socket_path.display() + ) + })?, + }, + } + } else { + None + }; + if let Some(mut stream) = socket { let real_url = if let Some(urls) = urls { - urls.into_iter().map(|url| url.to_string()).collect() + urls.into_iter().map(|url| url.into().to_string()).collect() } else { // Always add a new tab, so that qutebrowser is marked as “urgent”. vec!["qute://start".to_owned()] @@ -129,7 +156,10 @@ pub fn open_in_browser( ExitStatus::default() } else { let args = if let Some(urls) = urls { - urls.iter().map(|url| url.to_string()).collect() + urls.into_iter() + .map(Into::<Url>::into) + .map(|u| u.to_string()) + .collect() } else { vec![] }; diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs index 23d9545f..359c1050 100644 --- a/pkgs/by-name/ts/tskm/src/cli.rs +++ b/pkgs/by-name/ts/tskm/src/cli.rs @@ -13,11 +13,11 @@ use std::{ffi::OsStr, path::PathBuf}; use anyhow::{bail, Result}; use clap::{builder::StyledStr, ArgAction, Parser, Subcommand, ValueEnum}; use clap_complete::{ArgValueCompleter, CompletionCandidate}; -use url::Url; use crate::{ interface::{ input::{Input, Tag}, + open::UrlLike, project::ProjectName, }, state, task, @@ -127,7 +127,7 @@ pub enum OpenCommand { project: task::Project, /// The URLs to open. - urls: Option<Vec<Url>>, + urls: Option<Vec<UrlLike>>, }, /// Open a selected project in it's Qutebrowser profile. @@ -136,7 +136,7 @@ pub enum OpenCommand { /// projects. Select { /// The URLs to open. - urls: Option<Vec<Url>>, + urls: Option<Vec<UrlLike>>, }, /// List all open tabs in the project. diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs index 0cf60b41..3897a63b 100644 --- a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs +++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs @@ -43,7 +43,7 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> { project.to_project_display(), if is_empty { "is empty" } else { "is not empty" } ); - open_in_browser(project, state, None).with_context(|| { + open_in_browser(project, state, None::<Vec<Url>>).with_context(|| { format!( "Failed to open project ('{}') in qutebrowser", project.to_project_display() diff --git a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs index e302c7d1..e403b4a8 100644 --- a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs +++ b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs @@ -8,7 +8,11 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -use std::{fs::File, io::Read, str::FromStr}; +use std::{ + fs::{self, File}, + io::Read, + str::FromStr, +}; use anyhow::{anyhow, Context, Result}; use taskchampion::chrono::NaiveDateTime; @@ -20,6 +24,30 @@ use crate::task::Project; pub mod handle; pub use handle::handle; +/// An Url that also accepts file paths +#[derive(Debug, Clone)] +pub struct UrlLike(Url); + +impl FromStr for UrlLike { + type Err = url::ParseError; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + if let Ok(u) = fs::canonicalize(s) { + Ok(Self(Url::from_file_path(u).expect( + "The path could be canonicalized, as such it is valid for this", + ))) + } else { + Url::from_str(s).map(Self) + } + } +} + +impl From<UrlLike> for Url { + fn from(value: UrlLike) -> Self { + value.0 + } +} + impl Project { pub(super) fn get_sessionstore(&self) -> Result<SessionStore> { let path = dirs::data_local_dir() diff --git a/pkgs/by-name/yt/yt/package.nix b/pkgs/by-name/yt/yt/package.nix index ce30f7cc..4e1b2ecb 100644 --- a/pkgs/by-name/yt/yt/package.nix +++ b/pkgs/by-name/yt/yt/package.nix @@ -15,22 +15,25 @@ gitUpdater, # buildInputs mpv-unwrapped, - python3Packages, - python3, - ffmpeg, + ffmpeg-headless, openssl, libffi, + zlib, + curl, + deno, # NativeBuildInputs + python3, makeWrapper, llvmPackages_latest, glibc, - mold-wrapped, + mold, sqlite, fd, pkg-config, SDL2, }: let - version = "1.7.0"; + version = "1.9.0"; + python = python3.withPackages (ps: [ps.yt-dlp]); in rustPlatform.buildRustPackage (finalAttrs: { inherit version; @@ -39,15 +42,20 @@ in src = fetchgit { url = "https://git.foss-syndicate.org/bpeetz/clients/yt"; tag = "v${version}"; - hash = "sha256-7PWGXucGBsn3IuainNZ23IW5t19SyEATwqGxYgZGqrw="; + hash = "sha256-/Isgqe7Hda/1kwYY+ciQH/NBAcWvM92vDxWZ9svlQAM="; }; + cargoHash = "sha256-U0alYK9mhz6esVf0mad9o7Ra6tRaL9HKCOftyOg34HE="; + buildInputs = [ - python3Packages.yt-dlp mpv-unwrapped.dev - ffmpeg + ffmpeg-headless openssl libffi + zlib + curl.dev + python + deno ]; nativeBuildInputs = [ @@ -56,7 +64,7 @@ in sqlite fd pkg-config - mold-wrapped + mold ]; checkInputs = [ @@ -75,7 +83,10 @@ in DATABASE_URL = "sqlite://database.sqlx"; # Required by yt_dlp - FFMPEG_LOCATION = "${lib.getExe ffmpeg}"; + FFMPEG_LOCATION = "${lib.getExe ffmpeg-headless}"; + + # Tell pyo3 which python to use. + PYO3_PYTHON = lib.getExe python; # Needed for the libmpv2. C_INCLUDE_PATH = "${glibc.dev}/include"; @@ -84,34 +95,22 @@ in }; doCheck = true; + checkFlags = [ + # All of these tests try to connect to the internet to download test data. + "--skip=select::base::test_base" + "--skip=select::file::test_file" + "--skip=select::options::test_options" + "--skip=subscriptions::import_export::test_import_export" + "--skip=subscriptions::naming_subscriptions::test_naming_subscriptions" + "--skip=videos::downloading::test_downloading" + ]; prePatch = '' # Generate the sqlite db, so that we can run the comp-time sqlite checks. bash ./scripts/mkdb.sh ''; - cargoHash = "sha256-5c2VKjYj8pFHx+aLgGcnqbegIhJia6vB73cwuYjs7Sg="; - - postInstall = let - collectDeps = pkg: let - next = pkg.propagatedBuildInputs or []; - in - [pkg] - ++ next - ++ (lib.flatten (builtins.map collectDeps next)); - - loadPythonDep = der: "${der}/lib/python${lib.versions.majorMinor python3.version}/site-packages"; - - pythonPath = builtins.concatStringsSep ":" (lib.lists.unique ( - builtins.map loadPythonDep ( - (collectDeps python3Packages.yt-dlp) - ++ [ - # HACK(@bpeetz): These packages are not picked up in the traversal up top. <2025-06-16> - python3Packages.chardet - ] - ) - )); - in '' + postInstall = '' installShellCompletion --cmd yt \ --bash <(COMPLETE=bash $out/bin/yt) \ --fish <(COMPLETE=fish $out/bin/yt) \ @@ -120,7 +119,6 @@ in # NOTE: We cannot clear the path, because we need access to the $EDITOR. <2025-04-04> wrapProgram $out/bin/yt \ --prefix PATH : ${lib.makeBinPath finalAttrs.buildInputs} \ - --set YTDLP_NO_PLUGINS 1 \ - --set PYTHONPATH ${pythonPath} + --set YTDLP_NO_PLUGINS 1 ''; }) diff --git a/pkgs/update_pkgs.sh b/pkgs/update_pkgs.sh index d046ee76..d036142e 100755 --- a/pkgs/update_pkgs.sh +++ b/pkgs/update_pkgs.sh @@ -19,7 +19,7 @@ cd "$(dirname "$0")" || die "Bug: run with the wrong first arg: '$0'!" cd ./by-name || die "(BUG): The directory './by-name' does not exist?" # First check if all the update scripts conform to the standard -files_with_update="$(mktemp)" +files_with_update="$(mktemp -t update_pkgs_XXXXX)" trap 'rm "$files_with_update"' EXIT fd '^update.sh$' . --type file --extension sh --max-depth 3 | while read -r file; do diff --git a/build.sh b/scripts/build.sh index de4d536c..f3661978 100755 --- a/build.sh +++ b/scripts/build.sh @@ -10,7 +10,7 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -host="${1-tiamat}" +host="${1-$(hostname)}" [ "$#" -gt 0 ] && shift 1 root="$(git rev-parse --show-toplevel)" @@ -23,7 +23,9 @@ check() { if [ -s "$file" ]; then rm "$file" elif ! [ -e "$file" ]; then - : "Ignore not existing files" + rm "$file" || { + : "Ignore not existing files" + } else echo "ERROR: '$file' is not a symlink. Not removing it." 1>&2 exit 1 @@ -31,7 +33,7 @@ check() { } build_system() { - _val="$(nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --print-out-paths --no-link "$@")" + _val="$(nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --print-out-paths --no-link --option max-jobs 1 "$@")" exit_val="$?" if [ "$exit_val" -ne 0 ]; then @@ -52,4 +54,6 @@ home="$(grep ExecStart= "$SYSTEM_OUT/etc/systemd/system/home-manager-soispha.ser check "$HOME_OUT" ln --symbolic "$home" "$HOME_OUT" +nvd diff /run/current-system "$SYSTEM_OUT" + # vim: ft=sh diff --git a/scripts/why-depends b/scripts/why-depends new file mode 100755 index 00000000..1afe9150 --- /dev/null +++ b/scripts/why-depends @@ -0,0 +1,23 @@ +#! /usr/bin/env sh + +search_string="$1-" +shift 1 + +if [ "$1" != "" ]; then + # Add the version + search_string="${search_string}${1}$" + shift 1 +fi + +if [ "$1" = "--running" ]; then + shift 1 + base="/run/current-system" +else + host="$(hostname)" + base=".#nixosConfigurations.$host.config.system.build.toplevel" +fi + + +fd "$search_string" /nix/store --type directory --threads 1 --exec nix why-depends "$@" "$base" + +# vim: ft=sh diff --git a/update.sh b/update.sh index 8f7d338e..980df783 100755 --- a/update.sh +++ b/update.sh @@ -25,7 +25,7 @@ __update_sh_run nix flake update __update_sh_run ./pkgs/update_pkgs.sh "$@" # __update_sh_run nix flake check -__update_sh_run ./build.sh +__update_sh_run build.sh printf "\033[31;1m%s\033[0m\033[33;1m%s\033[0m\n" "Also update out-of-tree dependencies, like " "yt" 2>&1 |
