mirror of
https://codeberg.org/icewind/evdev-shortcut.git
synced 2026-06-03 10:04:11 +02:00
flake + ci rework
This commit is contained in:
parent
025ee6a620
commit
6e13b8e412
10 changed files with 178 additions and 165 deletions
28
.forgejo/workflows/ci.yml
Normal file
28
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
name: "CI"
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checks:
|
||||||
|
runs-on: nix
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://codeberg.org/icewind/attic-action@v1
|
||||||
|
with:
|
||||||
|
name: link
|
||||||
|
instance: https://cache.icewind.link
|
||||||
|
authToken: "${{ secrets.ATTIC_TOKEN }}"
|
||||||
|
- run: nix flake check --keep-going
|
||||||
|
|
||||||
|
semver:
|
||||||
|
runs-on: nix
|
||||||
|
needs: checks
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://codeberg.org/icewind/attic-action@v1
|
||||||
|
with:
|
||||||
|
name: ci
|
||||||
|
instance: https://cache.icewind.me
|
||||||
|
authToken: "${{ secrets.ATTIC_TOKEN }}"
|
||||||
|
- run: nix run .#semver-checks
|
||||||
44
.github/workflows/ci.yml
vendored
44
.github/workflows/ci.yml
vendored
|
|
@ -1,44 +0,0 @@
|
||||||
name: "CI"
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v20
|
|
||||||
- uses: icewind1991/attic-action@v1
|
|
||||||
with:
|
|
||||||
name: ci
|
|
||||||
instance: https://cache.icewind.me
|
|
||||||
authToken: '${{ secrets.ATTIC_TOKEN }}'
|
|
||||||
- run: nix build .#check
|
|
||||||
|
|
||||||
clippy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: check
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v20
|
|
||||||
- uses: icewind1991/attic-action@v1
|
|
||||||
with:
|
|
||||||
name: ci
|
|
||||||
instance: https://cache.icewind.me
|
|
||||||
authToken: '${{ secrets.ATTIC_TOKEN }}'
|
|
||||||
- run: nix build .#clippy
|
|
||||||
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: check
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v20
|
|
||||||
- uses: icewind1991/attic-action@v1
|
|
||||||
with:
|
|
||||||
name: ci
|
|
||||||
instance: https://cache.icewind.me
|
|
||||||
authToken: '${{ secrets.ATTIC_TOKEN }}'
|
|
||||||
- run: nix build .#test
|
|
||||||
|
|
||||||
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
|
|
@ -86,7 +86,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "evdev-shortcut"
|
name = "evdev-shortcut"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"evdev",
|
"evdev",
|
||||||
|
|
@ -97,6 +97,7 @@ dependencies = [
|
||||||
"test-case",
|
"test-case",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -336,9 +337,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.9"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-utils"
|
name = "pin-utils"
|
||||||
|
|
@ -579,6 +580,17 @@ dependencies = [
|
||||||
"syn 2.0.18",
|
"syn 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-stream"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
description = "Global shortcuts using evdev"
|
description = "Global shortcuts using evdev"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/icewind1991/evdev-shortcut"
|
repository = "https://github.com/icewind1991/evdev-shortcut"
|
||||||
|
rust-version = "1.78.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
evdev = { version = "0.12.1", optional = true, features = ["tokio"] }
|
evdev = { version = "0.12.1", optional = true, features = ["tokio"] }
|
||||||
|
|
@ -20,7 +21,13 @@ tracing = "0.1.37"
|
||||||
test-case = "3.1.0"
|
test-case = "3.1.0"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
||||||
|
tokio-stream = "0.1.17"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
listener = ["evdev", "futures", "async-stream"]
|
listener = ["evdev", "futures", "async-stream"]
|
||||||
default = ["listener"]
|
default = ["listener"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "listen"
|
||||||
|
path = "examples/listen.rs"
|
||||||
|
required-features = ["listener"]
|
||||||
12
README.md
12
README.md
|
|
@ -15,13 +15,13 @@ use futures::stream::StreamExt;
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let listener = ShortcutListener::new();
|
let listener = ShortcutListener::new();
|
||||||
listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
|
listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
|
||||||
|
|
||||||
let devices =
|
let devices =
|
||||||
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 stream = listener.listen(&devices)?;
|
let stream = listener.listen(&devices)?;
|
||||||
pin!(stream);
|
pin!(stream);
|
||||||
|
|
||||||
while let Some(event) = stream.next().await {
|
while let Some(event) = stream.next().await {
|
||||||
println!("{} {}", event.shortcut, event.state);
|
println!("{} {}", event.shortcut, event.state);
|
||||||
}
|
}
|
||||||
|
|
@ -29,5 +29,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that raw access to evdev devices is a privileged operation and usually requires running with elevated privileges.
|
Note that raw access to evdev devices is a privileged operation and usually
|
||||||
See [shortcutd](https://github.com/icewind1991/shortcutd) for a solution to running the elevated input handling in a separate process.
|
requires running with elevated privileges. See
|
||||||
|
[shortcutd](https://github.com/icewind1991/shortcutd) for a solution to running
|
||||||
|
the elevated input handling in a separate process.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use futures::{pin_mut, StreamExt};
|
use std::pin::pin;
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
use glob::GlobError;
|
use glob::GlobError;
|
||||||
use evdev_shortcut::{Key, Modifier, Shortcut, ShortcutListener};
|
use evdev_shortcut::{Key, Modifier, Shortcut, ShortcutListener};
|
||||||
|
|
||||||
|
|
@ -11,9 +12,7 @@ async fn main() {
|
||||||
let devices =
|
let devices =
|
||||||
glob::glob("/dev/input/by-id/*-kbd").unwrap().collect::<Result<Vec<PathBuf>, GlobError>>().unwrap();
|
glob::glob("/dev/input/by-id/*-kbd").unwrap().collect::<Result<Vec<PathBuf>, GlobError>>().unwrap();
|
||||||
|
|
||||||
let stream = listener.listen(&devices).unwrap();
|
let mut stream = pin!(listener.listen(&devices).unwrap());
|
||||||
|
|
||||||
pin_mut!(stream);
|
|
||||||
|
|
||||||
while let Some(event) = stream.next().await {
|
while let Some(event) = stream.next().await {
|
||||||
println!("{} {}", event.shortcut, event.state);
|
println!("{} {}", event.shortcut, event.state);
|
||||||
|
|
|
||||||
95
flake.lock
generated
95
flake.lock
generated
|
|
@ -1,64 +1,98 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"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": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1686242667,
|
"lastModified": 1749473391,
|
||||||
"narHash": "sha256-I7Kwp06WX/9E+rEND1i1wjdKQQm3XiDxYOyNK9fuJu0=",
|
"narHash": "sha256-NkkS2d7OvXL9VJS1pae+txd43vUIMWtxlIBdsKCRtYg=",
|
||||||
"owner": "icewind1991",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "flakelight",
|
||||||
"rev": "6d245a3bbb2ee31ec726bb57b9a8b206302e7110",
|
"rev": "22c8488d41c4e4678d32538f855bc05b3ba5142e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "icewind1991",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "flakelight",
|
||||||
"rev": "6d245a3bbb2ee31ec726bb57b9a8b206302e7110",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mill-scale": {
|
||||||
|
"inputs": {
|
||||||
|
"crane": "crane",
|
||||||
|
"flakelight": [
|
||||||
|
"flakelight"
|
||||||
|
],
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1749481047,
|
||||||
|
"narHash": "sha256-5VnLJdD91sTcJNvCaxH5r46Yl+RmyCcLftScxkEqP3k=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "642a7528ddea74bbe649913c5c3bb630eea02ecb",
|
||||||
|
"revCount": 52,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://codeberg.org/icewind/mill-scale"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://codeberg.org/icewind/mill-scale"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1686059680,
|
"lastModified": 1749237914,
|
||||||
"narHash": "sha256-sp0WlCIeVczzB0G8f8iyRg3IYW7KG31mI66z7HIZwrI=",
|
"narHash": "sha256-N5waoqWt8aMr/MykZjSErOokYH6rOsMMXu3UOVH5kiw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a558f7ac29f50c4b937fb5c102f587678ae1c9fb",
|
"rev": "70c74b02eac46f4e4aa071e45a6189ce0f6d9265",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"id": "nixpkgs",
|
||||||
"ref": "nixos-23.05",
|
"ref": "nixos-25.05",
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"naersk": "naersk",
|
"flakelight": "flakelight",
|
||||||
"nixpkgs": "nixpkgs",
|
"mill-scale": "mill-scale",
|
||||||
"rust-overlay": "rust-overlay",
|
"nixpkgs": "nixpkgs"
|
||||||
"utils": "utils"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": [
|
|
||||||
"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": {
|
||||||
|
|
@ -66,21 +100,6 @@
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1667395993,
|
|
||||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
55
flake.nix
55
flake.nix
|
|
@ -1,48 +1,15 @@
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-23.05";
|
nixpkgs.url = "nixpkgs/nixos-25.05";
|
||||||
utils.url = "github:numtide/flake-utils";
|
flakelight = {
|
||||||
naersk.url = "github:icewind1991/naersk?rev=6d245a3bbb2ee31ec726bb57b9a8b206302e7110";
|
url = "github:nix-community/flakelight";
|
||||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
};
|
||||||
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
mill-scale = {
|
||||||
rust-overlay.inputs.flake-utils.follows = "utils";
|
url = "git+https://codeberg.org/icewind/mill-scale";
|
||||||
|
inputs.flakelight.follows = "flakelight";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
outputs = {mill-scale, ...}:
|
||||||
outputs = {
|
mill-scale ./. {};
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
utils,
|
|
||||||
naersk,
|
|
||||||
rust-overlay,
|
|
||||||
}:
|
|
||||||
utils.lib.eachDefaultSystem (system: let
|
|
||||||
overlays = [ (import rust-overlay) ];
|
|
||||||
pkgs = (import nixpkgs) {
|
|
||||||
inherit system overlays;
|
|
||||||
};
|
|
||||||
lib = pkgs.lib;
|
|
||||||
naersk' = pkgs.callPackage naersk {};
|
|
||||||
src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(src)(/.*)?"];
|
|
||||||
nearskOpt = {
|
|
||||||
pname = "evdev-shortcut";
|
|
||||||
root = src;
|
|
||||||
};
|
|
||||||
in rec {
|
|
||||||
packages = {
|
|
||||||
check = naersk'.buildPackage (nearskOpt // {
|
|
||||||
mode = "check";
|
|
||||||
});
|
|
||||||
clippy = naersk'.buildPackage (nearskOpt // {
|
|
||||||
mode = "clippy";
|
|
||||||
});
|
|
||||||
test = naersk'.buildPackage (nearskOpt // {
|
|
||||||
mode = "test";
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
nativeBuildInputs = with pkgs; [rustc cargo bacon cargo-edit cargo-outdated clippy cargo-audit cargo-msrv cargo-fuzz];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
src/lib.rs
44
src/lib.rs
|
|
@ -11,9 +11,12 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # use std::path::PathBuf;
|
//! # use std::path::PathBuf;
|
||||||
//! # use glob::GlobError;
|
//! # use glob::GlobError;
|
||||||
//! # use evdev_shortcut::{ShortcutListener, Shortcut, Modifier, Key};
|
//! # #[cfg(feature = "listener")]
|
||||||
|
//! # use evdev_shortcut::{ShortcutListener};
|
||||||
|
//! # use evdev_shortcut::{Shortcut, Modifier, Key};
|
||||||
//! # use tokio::pin;
|
//! # use tokio::pin;
|
||||||
//! # use futures::stream::StreamExt;
|
//! # use tokio_stream::StreamExt;
|
||||||
|
//! # #[cfg(feature = "listener")]
|
||||||
//! # #[tokio::main]
|
//! # #[tokio::main]
|
||||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let listener = ShortcutListener::new();
|
//! let listener = ShortcutListener::new();
|
||||||
|
|
@ -30,6 +33,10 @@
|
||||||
//! }
|
//! }
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
|
//! # #[cfg(not(feature = "listener"))]
|
||||||
|
//! # fn main() {
|
||||||
|
//! # // placeholder so we can build the doc example without default features
|
||||||
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
pub use keycodes::Key;
|
pub use keycodes::Key;
|
||||||
|
|
@ -134,21 +141,26 @@ pub struct ModifierList(u8);
|
||||||
|
|
||||||
impl ModifierList {
|
impl ModifierList {
|
||||||
pub fn new(modifiers: &[Modifier]) -> Self {
|
pub fn new(modifiers: &[Modifier]) -> Self {
|
||||||
ModifierList(modifiers
|
ModifierList(
|
||||||
.iter()
|
modifiers
|
||||||
.fold(0, |mask, modifier| mask | modifier.mask()))
|
.iter()
|
||||||
|
.fold(0, |mask, modifier| mask | modifier.mask()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mask(&self) -> u8 {
|
pub fn mask(&self) -> u8 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn modifiers(&self) -> impl Iterator<Item=Modifier> {
|
pub fn modifiers(&self) -> impl Iterator<Item = Modifier> {
|
||||||
let mask = self.mask();
|
let mask = self.mask();
|
||||||
ALL_MODIFIERS.iter().copied().filter(move |modifier| {
|
ALL_MODIFIERS.iter().copied().filter(move |modifier| {
|
||||||
for combined in COMBINED_MODIFIERS {
|
for combined in COMBINED_MODIFIERS {
|
||||||
// if <Ctrl> is enabled, don't emit <LeftCtrl> and <RightCtrl>
|
// if <Ctrl> is enabled, don't emit <LeftCtrl> and <RightCtrl>
|
||||||
if combined != modifier && combined.mask() & modifier.mask() == modifier.mask() && combined.mask() & mask == combined.mask() {
|
if combined != modifier
|
||||||
|
&& combined.mask() & modifier.mask() == modifier.mask()
|
||||||
|
&& combined.mask() & mask == combined.mask()
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +190,8 @@ impl FromStr for ModifierList {
|
||||||
type Err = ParseError;
|
type Err = ParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let modifiers = s.split('>')
|
let modifiers = s
|
||||||
|
.split('>')
|
||||||
.filter(|part| !part.is_empty())
|
.filter(|part| !part.is_empty())
|
||||||
.map(|part| {
|
.map(|part| {
|
||||||
if !part.starts_with('<') {
|
if !part.starts_with('<') {
|
||||||
|
|
@ -251,8 +264,8 @@ impl Display for Shortcut {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use test_case::test_case;
|
|
||||||
use crate::{Key, Modifier, ModifierList, Shortcut};
|
use crate::{Key, Modifier, ModifierList, Shortcut};
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
#[test_case("KeyP", Shortcut::new(& [], Key::KeyP))]
|
#[test_case("KeyP", Shortcut::new(& [], Key::KeyP))]
|
||||||
#[test_case("<Ctrl>-KeyP", Shortcut::new(& [Modifier::Ctrl], Key::KeyP))]
|
#[test_case("<Ctrl>-KeyP", Shortcut::new(& [Modifier::Ctrl], Key::KeyP))]
|
||||||
|
|
@ -267,7 +280,10 @@ mod tests {
|
||||||
#[test_case(& [Modifier::LeftAlt, Modifier::LeftCtrl])]
|
#[test_case(& [Modifier::LeftAlt, Modifier::LeftCtrl])]
|
||||||
#[test_case(& [Modifier::Shift, Modifier::Meta])]
|
#[test_case(& [Modifier::Shift, Modifier::Meta])]
|
||||||
fn test_modifier_list(modifiers: &[Modifier]) {
|
fn test_modifier_list(modifiers: &[Modifier]) {
|
||||||
assert_eq!(modifiers.to_vec(), ModifierList::new(modifiers).modifiers().collect::<Vec<_>>())
|
assert_eq!(
|
||||||
|
modifiers.to_vec(),
|
||||||
|
ModifierList::new(modifiers).modifiers().collect::<Vec<_>>()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,9 +296,7 @@ impl Shortcut {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identifier(&self) -> String {
|
pub fn identifier(&self) -> String {
|
||||||
self.to_string()
|
self.to_string().replace(['<', '>'], "").replace('-', "_")
|
||||||
.replace(['<', '>'], "")
|
|
||||||
.replace('-', "_")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,7 +336,7 @@ mod triggered_tests {
|
||||||
#[test_case("<Ctrl><Alt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyRightAlt, Key::KeyLeft] => true)]
|
#[test_case("<Ctrl><Alt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyRightAlt, Key::KeyLeft] => true)]
|
||||||
fn shortcut_triggered(s: &str, keys: &[Key]) -> bool {
|
fn shortcut_triggered(s: &str, keys: &[Key]) -> bool {
|
||||||
let shortcut: Shortcut = s.parse().unwrap();
|
let shortcut: Shortcut = s.parse().unwrap();
|
||||||
shortcut.is_triggered(&keys.into_iter().copied().collect())
|
shortcut.is_triggered(&keys.iter().copied().collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,4 +367,4 @@ impl Display for ShortcutState {
|
||||||
pub struct ShortcutEvent {
|
pub struct ShortcutEvent {
|
||||||
pub shortcut: Shortcut,
|
pub shortcut: Shortcut,
|
||||||
pub state: ShortcutState,
|
pub state: ShortcutState,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
|
use crate::{DeviceOpenError, Key, Shortcut, ShortcutEvent, ShortcutState};
|
||||||
|
use async_stream::stream;
|
||||||
use evdev::Device;
|
use evdev::Device;
|
||||||
|
use futures::pin_mut;
|
||||||
|
use futures::stream::iter;
|
||||||
|
use futures::{Stream, StreamExt};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use crate::{Shortcut, DeviceOpenError, Key, ShortcutEvent, ShortcutState};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use async_stream::stream;
|
use std::sync::{Arc, Mutex};
|
||||||
use futures::pin_mut;
|
use tracing::{debug, info, trace};
|
||||||
use futures::{Stream, StreamExt};
|
|
||||||
use futures::stream::{iter};
|
|
||||||
use tracing::{debug, trace, info};
|
|
||||||
|
|
||||||
/// A listener for shortcut events
|
/// A listener for shortcut events
|
||||||
///
|
///
|
||||||
|
|
@ -42,20 +42,29 @@ impl ShortcutListener {
|
||||||
/// Listen for shortcuts on the provided set of input devices.
|
/// Listen for shortcuts on the provided set of input devices.
|
||||||
///
|
///
|
||||||
/// Note that you need to register shortcuts using [add](ShortcutListener::add) to get any events.
|
/// Note that you need to register shortcuts using [add](ShortcutListener::add) to get any events.
|
||||||
pub fn listen<P: AsRef<Path>>(&self, devices: &[P]) -> Result<impl Stream<Item=ShortcutEvent>, DeviceOpenError> {
|
pub fn listen<P: AsRef<Path>>(
|
||||||
|
&self,
|
||||||
|
devices: &[P],
|
||||||
|
) -> Result<impl Stream<Item = ShortcutEvent>, DeviceOpenError> {
|
||||||
let shortcuts = self.shortcuts.clone();
|
let shortcuts = self.shortcuts.clone();
|
||||||
|
|
||||||
let devices = devices
|
let devices = devices
|
||||||
.iter()
|
.iter()
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let res = Device::open(path).map_err(|_| DeviceOpenError { device: path.into() });
|
let res = Device::open(path).map_err(|_| DeviceOpenError {
|
||||||
|
device: path.into(),
|
||||||
|
});
|
||||||
debug!(device = ?path, success = res.is_ok(), "opening input device");
|
debug!(device = ?path, success = res.is_ok(), "opening input device");
|
||||||
res
|
res
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<Device>, DeviceOpenError>>()?;
|
.collect::<Result<Vec<Device>, DeviceOpenError>>()?;
|
||||||
let events = iter(devices.into_iter().flat_map(|device| device.into_event_stream()))
|
let events = iter(
|
||||||
.flatten();
|
devices
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|device| device.into_event_stream()),
|
||||||
|
)
|
||||||
|
.flatten();
|
||||||
|
|
||||||
Ok(stream! {
|
Ok(stream! {
|
||||||
let mut active_keys = HashSet::new();
|
let mut active_keys = HashSet::new();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue