This commit is contained in:
Robin Appelman 2025-06-09 18:05:39 +02:00
commit d4ce62a42c
19 changed files with 2724 additions and 1988 deletions

View file

@ -20,7 +20,7 @@ jobs:
with: with:
name: ci name: ci
instance: https://cache.icewind.me instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}' authToken: "${{ secrets.ATTIC_TOKEN }}"
- run: nix build .#check - run: nix build .#check
clippy: clippy:
@ -33,7 +33,7 @@ jobs:
with: with:
name: ci name: ci
instance: https://cache.icewind.me instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}' authToken: "${{ secrets.ATTIC_TOKEN }}"
- run: nix build .#clippy - run: nix build .#clippy
matrix: matrix:
@ -44,7 +44,9 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20 - uses: cachix/install-nix-action@v20
- id: set-matrix - id: set-matrix
run: echo "matrix=$(nix eval --json ".#matrix.x86_64-linux")" | tee $GITHUB_OUTPUT run:
echo "matrix=$(nix eval --json ".#matrix.x86_64-linux")" | tee
$GITHUB_OUTPUT
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -59,7 +61,7 @@ jobs:
with: with:
name: ci name: ci
instance: https://cache.icewind.me instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}' authToken: "${{ secrets.ATTIC_TOKEN }}"
- run: nix build .#${{ matrix.target }} - run: nix build .#${{ matrix.target }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:

View file

@ -4,7 +4,6 @@ on:
release: release:
types: [created] types: [created]
jobs: jobs:
matrix: matrix:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -14,7 +13,9 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20 - uses: cachix/install-nix-action@v20
- id: set-matrix - id: set-matrix
run: echo "matrix=$(nix eval --json ".#releaseMatrix.x86_64-linux")" | tee $GITHUB_OUTPUT run:
echo "matrix=$(nix eval --json ".#releaseMatrix.x86_64-linux")" | tee
$GITHUB_OUTPUT
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -28,7 +29,7 @@ jobs:
with: with:
name: ci name: ci
instance: https://cache.icewind.me instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}' authToken: "${{ secrets.ATTIC_TOKEN }}"
- run: nix build .#${{ matrix.target }} - run: nix build .#${{ matrix.target }}
- name: Upload binary to release - name: Upload binary to release
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target target
result result
.direnv .direnv

1666
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,2 +0,0 @@
[workspace]
members = ["server", "client"]

View file

@ -4,18 +4,21 @@ Global shortcuts using evdev
## What ## What
shortcutd is a daemon and client library to allow listening for global shortcuts for systems that don't support it shortcutd is a daemon and client library to allow listening for global shortcuts
otherwise (such as wayland). for systems that don't support it otherwise (such as wayland).
The shortcutd daemon hooks into the evdev system and exposes a dbus interface for clients to hook into to. The shortcutd daemon hooks into the evdev system and exposes a dbus interface
By separating out the code that hooks into evdev (which needs to be done as root) into a separate daemon for clients to hook into to. By separating out the code that hooks into evdev
it allows non-privileged users to hook into global shortcuts. (which needs to be done as root) into a separate daemon it allows non-privileged
users to hook into global shortcuts.
Protection against clients using the shortcutd daemon for a keylogger is done by only allowing 3 shortcuts without modifiers to be registered at the same time. Protection against clients using the shortcutd daemon for a keylogger is done by
only allowing 3 shortcuts without modifiers to be registered at the same time.
## Starting the daemon ## Starting the daemon
- Copy the dbus configuration `nl.icewind.shortcutd.conf` into `/etc/dbus-1/system.d/` - Copy the dbus configuration `nl.icewind.shortcutd.conf` into
`/etc/dbus-1/system.d/`
- Start the daemon as root (or use the provided systemd service). - Start the daemon as root (or use the provided systemd service).
## Rust api ## Rust api
@ -45,8 +48,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
## D-Bus api ## D-Bus api
- register a new shortcut using the `Register` method at `nl.icewind.shortcutd`/`register` - register a new shortcut using the `Register` method at
- listen to the signal at the path returned from the `Register` method to get notified when the shortcut is triggered. `nl.icewind.shortcutd`/`register`
- listen to the signal at the path returned from the `Register` method to get
A boolean parameter is provided with the signal to distinguish shortcut presses from releases. notified when the shortcut is triggered.
A boolean parameter is provided with the signal to distinguish shortcut
presses from releases.

1151
client/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -5,19 +5,20 @@ authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2021" edition = "2021"
description = "shortcutd client library" description = "shortcutd client library"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/icewind1991/shortcutd" repository = "https://codeberg.org/icewind/shortcutd"
readme = "../README.md" readme = "../README.md"
rust-version = "1.80.1"
[lib] [lib]
name = "shortcutd" name = "shortcutd"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
futures = "0.3.28" futures = "0.3.31"
zbus = { version = "3.13.1", features = ["tokio"], default-features = false } zbus = { version = "5.7.1", features = ["tokio"], default-features = false }
evdev-shortcut = { version = "0.1.4", default_features = false } zbus_macros = "5.7.1"
evdev-shortcut = { version = "0.1.5", default-features = false }
[dev-dependencies] [dev-dependencies]
test-case = "3.1.0" tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } clap = { version = "4.5.39", features = ["derive"] }
clap = { version = "4.3.4", features = ["derive"] }

104
client/flake.lock generated Normal file
View file

@ -0,0 +1,104 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1742394900,
"narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
"owner": "ipetkov",
"repo": "crane",
"rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flakelight": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1749473391,
"narHash": "sha256-NkkS2d7OvXL9VJS1pae+txd43vUIMWtxlIBdsKCRtYg=",
"owner": "nix-community",
"repo": "flakelight",
"rev": "22c8488d41c4e4678d32538f855bc05b3ba5142e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "flakelight",
"type": "github"
}
},
"mill-scale": {
"inputs": {
"crane": "crane",
"flakelight": [
"flakelight"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1749479717,
"narHash": "sha256-A/QfxOzt5cOLiz/Fn0xTQ3OGaJkIgq6KwnBv7AXRoG4=",
"path": "/home/robin/Projects/mill-scale",
"type": "path"
},
"original": {
"path": "/home/robin/Projects/mill-scale",
"type": "path"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1749237914,
"narHash": "sha256-N5waoqWt8aMr/MykZjSErOokYH6rOsMMXu3UOVH5kiw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "70c74b02eac46f4e4aa071e45a6189ce0f6d9265",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-25.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"flakelight": "flakelight",
"mill-scale": "mill-scale",
"nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"mill-scale",
"flakelight",
"nixpkgs"
]
},
"locked": {
"lastModified": 1742697269,
"narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "01973c84732f9275c50c5f075dd1f54cc04b3316",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

16
client/flake.nix Normal file
View file

@ -0,0 +1,16 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-25.05";
flakelight = {
url = "github:nix-community/flakelight";
inputs.nixpkgs.follows = "nixpkgs";
};
mill-scale = {
# url = "git+https://codeberg.org/icewind/mill-scale";
url = "path:/home/robin/Projects/mill-scale";
inputs.flakelight.follows = "flakelight";
};
};
outputs = {mill-scale, ...}:
mill-scale ./. {};
}

View file

@ -1,20 +1,20 @@
use zbus::dbus_proxy;
use zbus::fdo; use zbus::fdo;
use zbus_macros::proxy;
#[dbus_proxy( #[proxy(
interface = "nl.icewind.shortcutd", interface = "nl.icewind.shortcutd",
default_service = "nl.icewind.shortcutd", default_service = "nl.icewind.shortcutd",
default_path = "/register" default_path = "/register"
)] )]
trait Register { pub(crate) trait Register {
async fn register(&self, shortcut: &str) -> fdo::Result<String>; async fn register(&self, shortcut: &str) -> fdo::Result<String>;
} }
#[dbus_proxy( #[proxy(
interface = "nl.icewind.shortcutd", interface = "nl.icewind.shortcutd",
default_service = "nl.icewind.shortcutd" default_service = "nl.icewind.shortcutd"
)] )]
trait ShortcutSignal { pub(crate) trait ShortcutSignal {
#[dbus_proxy(signal)] #[zbus(signal)]
async fn triggered(&self, pressed: bool) -> fdo::Result<()>; async fn triggered(&self, pressed: bool) -> fdo::Result<()>;
} }

134
flake.lock generated
View file

@ -1,117 +1,98 @@
{ {
"nodes": { "nodes": {
"cross-naersk": { "crane": {
"locked": {
"lastModified": 1742394900,
"narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
"owner": "ipetkov",
"repo": "crane",
"rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flakelight": {
"inputs": { "inputs": {
"naersk": [
"naersk"
],
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1686502198, "lastModified": 1749473391,
"narHash": "sha256-Ti8l6OOa/vpawiCJqiidE3vfsvVDfTQW4ls2PMSfoJE=", "narHash": "sha256-NkkS2d7OvXL9VJS1pae+txd43vUIMWtxlIBdsKCRtYg=",
"owner": "icewind1991", "owner": "nix-community",
"repo": "cross-naersk", "repo": "flakelight",
"rev": "2c2b9aae13700e4c3593aeef609dddb788fe5ae0", "rev": "22c8488d41c4e4678d32538f855bc05b3ba5142e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "icewind1991", "owner": "nix-community",
"repo": "cross-naersk", "repo": "flakelight",
"type": "github" "type": "github"
} }
}, },
"flake-utils": { "mill-scale": {
"inputs": { "inputs": {
"systems": "systems" "crane": "crane",
"flakelight": [
"flakelight"
],
"rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1685518550, "lastModified": 1749480618,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", "narHash": "sha256-mJmGXHDpWcdEF22YWxy7ZT8VWca2spjRPhFbNM2aMLM=",
"owner": "numtide", "ref": "refs/heads/main",
"repo": "flake-utils", "rev": "8a5baa8225d420686434805087758044330f5b66",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", "revCount": 51,
"type": "github" "type": "git",
"url": "https://codeberg.org/icewind/mill-scale"
}, },
"original": { "original": {
"owner": "numtide", "type": "git",
"repo": "flake-utils", "url": "https://codeberg.org/icewind/mill-scale"
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1686242667,
"narHash": "sha256-I7Kwp06WX/9E+rEND1i1wjdKQQm3XiDxYOyNK9fuJu0=",
"owner": "icewind1991",
"repo": "naersk",
"rev": "6d245a3bbb2ee31ec726bb57b9a8b206302e7110",
"type": "github"
},
"original": {
"owner": "icewind1991",
"repo": "naersk",
"rev": "6d245a3bbb2ee31ec726bb57b9a8b206302e7110",
"type": "github"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1686331006, "lastModified": 1749237914,
"narHash": "sha256-hElRDWUNG655aqF0awu+h5cmDN+I/dQcChRt2tGuGGU=", "narHash": "sha256-N5waoqWt8aMr/MykZjSErOokYH6rOsMMXu3UOVH5kiw=",
"path": "/nix/store/wcgkp46gj4ca0700wprx42r51b60ws4l-source",
"rev": "85bcb95aa83be667e562e781e9d186c57a07d757",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1687018574,
"narHash": "sha256-g3PubZfjWbHG4/tUt13NTlX+mgHbgAYWa5YJMyVJxjo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "819fab520f664d72da4420a9705cf24932c684bb", "rev": "70c74b02eac46f4e4aa071e45a6189ce0f6d9265",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "release-23.05", "ref": "nixos-25.05",
"type": "indirect" "type": "indirect"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"cross-naersk": "cross-naersk", "flakelight": "flakelight",
"flake-utils": "flake-utils", "mill-scale": "mill-scale",
"naersk": "naersk", "nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay"
} }
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [ "nixpkgs": [
"mill-scale",
"flakelight",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1686191569, "lastModified": 1742697269,
"narHash": "sha256-8ey5FOXNms9piFGTn6vJeAQmSKk+NL7GTMSoVttsNTs=", "narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "b4b71458b92294e8f1c3a112d972e3cff8a2ab71", "rev": "01973c84732f9275c50c5f075dd1f54cc04b3316",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -119,21 +100,6 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

211
flake.nix
View file

@ -1,193 +1,38 @@
{ {
inputs = { inputs = {
nixpkgs.url = "nixpkgs/release-23.05"; nixpkgs.url = "nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils"; flakelight = {
naersk.url = "github:icewind1991/naersk?rev=6d245a3bbb2ee31ec726bb57b9a8b206302e7110"; url = "github:nix-community/flakelight";
rust-overlay.url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; };
rust-overlay.inputs.flake-utils.follows = "flake-utils"; mill-scale = {
cross-naersk.url = "github:icewind1991/cross-naersk"; url = "git+https://codeberg.org/icewind/mill-scale";
cross-naersk.inputs.nixpkgs.follows = "nixpkgs"; inputs.flakelight.follows = "flakelight";
cross-naersk.inputs.naersk.follows = "naersk"; };
}; };
outputs = {mill-scale, ...}:
outputs = { mill-scale ./server {
self, withOverlays = [
flake-utils, (import ./nix/overlay.nix)
cross-naersk,
naersk,
nixpkgs,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
overlays = [(import rust-overlay)];
pkgs = (import nixpkgs) {
inherit system overlays;
};
lib = pkgs.lib;
inherit (builtins) map replaceStrings;
inherit (lib.strings) hasInfix;
inherit (lib.attrsets) nameValuePair genAttrs listToAttrs;
hostTarget = pkgs.hostPlatform.config;
targets = [
"x86_64-unknown-linux-musl"
hostTarget
]; ];
releaseTargets = lib.lists.remove hostTarget targets; crossTargets = [
"x86_64-unknown-linux-musl"
];
toolchain = pkgs.rust-bin.stable.latest.default.override {inherit targets;}; nixosModules = {outputs, ...}: {
assetNameForTarget = replaceStrings ["-unknown" "-gnu" "-musl" "eabihf" "-pc"] ["" "" "" "" ""]; default = {
pkgs,
cross-naersk' = pkgs.callPackage cross-naersk {inherit naersk;}; config,
src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(server|client)(/.*)?"]; lib,
naerskOpt = { ...
pname = "shortcutd"; }: {
root = src; imports = [./nix/module.nix];
}; config = lib.mkIf config.services.shortcutd.enable {
nixpkgs.overlays = [outputs.overlays.default];
buildMatrix = targets: { services.shortcutd.package = lib.mkDefault pkgs.shortcutd;
include =
map (target: {
inherit target;
artifact_name = "shortcutd";
asset_name = "shortcutd-${assetNameForTarget target}";
})
targets
++ map (target: {
target = "${target}-example-client";
artifact_name = "client";
asset_name = "example-client-${assetNameForTarget target}";
})
targets;
};
serverPackages = genAttrs targets (target:
(cross-naersk' target).buildPackage {
pname = "shortcutd";
root = src;
postInstall = ''
mkdir -p $out/etc/dbus-1/system.d/
cp ${./nixos-nl.icewind.shortcutd.conf} $out/etc/dbus-1/system.d/nl.icewind.shortcutd.conf
'';
});
clientPackages = listToAttrs (map (target:
nameValuePair "${target}-example-client" ((cross-naersk' target).buildPackage {
pname = "shortcutd-example-client";
root = src;
overrideMain = x: {
preConfigure = ''
cargo_build_options="$cargo_build_options --example client"
'';
};
}))
targets);
in rec {
packages =
serverPackages
// clientPackages
// rec {
shortcutd = packages.${hostTarget};
example-client = packages."${hostTarget}-example-client";
check = (cross-naersk' hostTarget).buildPackage (naerskOpt
// {
mode = "check";
});
clippy = (cross-naersk' hostTarget).buildPackage (naerskOpt
// {
mode = "clippy";
});
default = shortcutd;
};
inherit targets;
inherit releaseTargets;
matrix = buildMatrix targets;
releaseMatrix = buildMatrix releaseTargets;
apps = rec {
tf-demo-parser = flake-utils.lib.mkApp {
drv = packages.tf-demo-parser;
exePath = "/bin/parse_demo";
};
default = tf-demo-parser;
};
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [rust-bin.stable.latest.default bacon cargo-edit cargo-outdated rustfmt clippy cargo-audit hyperfine valgrind];
};
})
// {
nixosModule = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.services.shortcutd;
in {
options.services.shortcutd = {
enable = mkEnableOption "Enables the shortcutd service";
log = mkOption rec {
type = types.str;
default = "WARN";
example = "INFO";
description = "log level";
};
};
config = mkIf cfg.enable {
services.dbus.packages = [self.packages.${pkgs.system}.default];
users.users.shortcutd = {
isSystemUser = true;
group = "shortcutd";
};
users.groups.shortcutd = {};
systemd.services."shortcutd" = {
wantedBy = ["multi-user.target"];
environment = {
RUST_LOG = cfg.log;
};
serviceConfig = let
pkg = self.packages.${pkgs.system}.default;
in {
User = "shortcutd";
Restart = "on-failure";
ExecStart = "${pkg}/bin/shortcutd";
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
NoNewPrivileges = true;
CapabilityBoundingSet = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
SystemCallArchitectures = "native";
ProtectKernelModules = true;
RestrictNamespaces = true;
MemoryDenyWriteExecute = true;
ProtectHostname = true;
LockPersonality = true;
ProtectKernelTunables = true;
RestrictRealtime = true;
SystemCallFilter = ["@system-service" "~@resources" "~@privileged"];
RestrictAddressFamilies = ["AF_UNIX"];
IPAddressDeny = "any";
PrivateUsers = true;
RestrictSUIDSGID = true;
PrivateNetwork = true;
UMask = "0077";
SupplementaryGroups = ["input"];
};
};
}; };
}; };
};
}; };
} }

69
nix/module.nix Normal file
View file

@ -0,0 +1,69 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.services.shortcutd;
in {
options.services.shortcutd = {
enable = mkEnableOption "Enables the shortcutd service";
log = mkOption rec {
type = types.str;
default = "WARN";
example = "INFO";
description = "log level";
};
};
config = mkIf cfg.enable {
services.dbus.packages = [self.packages.${pkgs.system}.default];
users.users.shortcutd = {
isSystemUser = true;
group = "shortcutd";
};
users.groups.shortcutd = {};
systemd.services."shortcutd" = {
wantedBy = ["multi-user.target"];
environment = {
RUST_LOG = cfg.log;
};
serviceConfig = let
pkg = self.packages.${pkgs.system}.default;
in {
User = "shortcutd";
Restart = "on-failure";
ExecStart = "${pkg}/bin/shortcutd";
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
NoNewPrivileges = true;
CapabilityBoundingSet = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
SystemCallArchitectures = "native";
ProtectKernelModules = true;
RestrictNamespaces = true;
MemoryDenyWriteExecute = true;
ProtectHostname = true;
LockPersonality = true;
ProtectKernelTunables = true;
RestrictRealtime = true;
SystemCallFilter = ["@system-service" "~@resources" "~@privileged"];
RestrictAddressFamilies = ["AF_UNIX"];
IPAddressDeny = "any";
PrivateUsers = true;
RestrictSUIDSGID = true;
PrivateNetwork = true;
UMask = "0077";
SupplementaryGroups = ["input"];
};
};
};
}

3
nix/overlay.nix Normal file
View file

@ -0,0 +1,3 @@
final: prev: {
shortcutd = final.callPackage ./package.nix {};
}

27
nix/package.nix Normal file
View file

@ -0,0 +1,27 @@
{
rustPlatform,
lib,
}: let
inherit (lib.sources) sourceByRegex;
inherit (builtins) fromTOML readFile;
in
rustPlatform.buildRustPackage rec {
pname = "shortcutd";
version = (fromTOML (readFile ../server/Cargo.toml)).package.version;
src = sourceByRegex ../server ["Cargo.*" "(src)(/.*)?"];
postPatch = ''
cp ${../Cargo.lock} Cargo.lock
chmod 0755 Cargo.lock
'';
postInstall = ''
mkdir -p $out/etc/dbus-1/system.d/
cp ${../nixos-nl.icewind.shortcutd.conf} $out/etc/dbus-1/system.d/nl.icewind.shortcutd.conf
'';
cargoLock = {
lockFile = ../Cargo.lock;
};
}

1215
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -5,21 +5,19 @@ authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2021" edition = "2021"
description = "shortcutd server" description = "shortcutd server"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/icewind1991/shortcutd" repository = "https://codeberg.org/icewind/shortcutd"
[[bin]] [[bin]]
name = "shortcutd" name = "shortcutd"
path = "src/server.rs" path = "src/server.rs"
[dependencies] [dependencies]
main_error = "0.1.0" main_error = "0.1.2"
glob = "0.3.0" glob = "0.3.2"
evdev-shortcut = "0.1.4" evdev-shortcut = "0.1.5"
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] }
futures = "0.3.28" futures = "0.3.31"
zbus = { version = "3.13.1", features = ["tokio"], default-features = false } zbus = { version = "5.7.1", features = ["tokio"], default-features = false }
tracing = "0.1.37" zbus_macros = "5.7.1"
tracing-subscriber = "0.3.17" tracing = "0.1.41"
tracing-subscriber = "0.3.19"
[dev-dependencies]
test-case = "3.1.0"

View file

@ -3,9 +3,11 @@ use futures::stream::StreamExt;
use glob::GlobError; use glob::GlobError;
use main_error::MainError; use main_error::MainError;
use std::path::PathBuf; use std::path::PathBuf;
use std::pin::pin;
use tracing::{error, info}; use tracing::{error, info};
use zbus::export::futures_util::pin_mut; use zbus::conn::Builder;
use zbus::{dbus_interface, fdo, ConnectionBuilder, ObjectServer, SignalContext}; use zbus::{fdo, object_server::SignalEmitter, ObjectServer};
use zbus_macros::interface;
struct Register { struct Register {
listener: ShortcutListener, listener: ShortcutListener,
@ -14,7 +16,7 @@ struct Register {
const MAX_BARE: usize = 3; const MAX_BARE: usize = 3;
#[dbus_interface(name = "nl.icewind.shortcutd")] #[interface(name = "nl.icewind.shortcutd")]
impl Register { impl Register {
async fn register( async fn register(
&mut self, &mut self,
@ -48,10 +50,10 @@ impl Register {
struct ShortcutSignal; struct ShortcutSignal;
#[dbus_interface(name = "nl.icewind.shortcutd")] #[interface(name = "nl.icewind.shortcutd")]
impl ShortcutSignal { impl ShortcutSignal {
#[dbus_interface(signal)] #[zbus(signal)]
async fn triggered(signal_ctxt: &SignalContext<'_>, pressed: bool) -> zbus::Result<()>; async fn triggered(signal_context: &SignalEmitter<'_>, pressed: bool) -> zbus::Result<()>;
} }
#[tokio::main] #[tokio::main]
@ -61,13 +63,13 @@ async fn main() -> Result<(), MainError> {
glob::glob("/dev/input/by-id/*-kbd")?.collect::<Result<Vec<PathBuf>, GlobError>>()?; glob::glob("/dev/input/by-id/*-kbd")?.collect::<Result<Vec<PathBuf>, GlobError>>()?;
let listener = ShortcutListener::new(); let listener = ShortcutListener::new();
let shortcut_events = listener.listen(&devices)?; let mut shortcut_events = pin!(listener.listen(&devices)?);
let bus = Register { let bus = Register {
listener, listener,
bare_count: 0, bare_count: 0,
}; };
let conn = ConnectionBuilder::system() let conn = Builder::system()
.map_err(|e| { .map_err(|e| {
error!(error = ?e, "error while connecting to dbus system socket"); error!(error = ?e, "error while connecting to dbus system socket");
e e
@ -83,13 +85,12 @@ async fn main() -> Result<(), MainError> {
let server = conn.object_server(); let server = conn.object_server();
pin_mut!(shortcut_events);
while let Some(event) = shortcut_events.next().await { while let Some(event) = shortcut_events.next().await {
let event: ShortcutEvent = event; let event: ShortcutEvent = event;
let identifier = format!("/{}", event.shortcut.identifier()); let identifier = format!("/{}", event.shortcut.identifier());
if let Ok(signal_interface) = server.interface::<_, ShortcutSignal>(identifier).await { if let Ok(signal_interface) = server.interface::<_, ShortcutSignal>(identifier).await {
if let Err(e) = ShortcutSignal::triggered( if let Err(e) = ShortcutSignal::triggered(
signal_interface.signal_context(), signal_interface.signal_emitter(),
event.state == ShortcutState::Pressed, event.state == ShortcutState::Pressed,
) )
.await .await