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.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
|
|
@ -86,7 +86,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "evdev-shortcut"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"evdev",
|
||||
|
|
@ -97,6 +97,7 @@ dependencies = [
|
|||
"test-case",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
|
@ -336,9 +337,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
|
@ -579,6 +580,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.2"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
description = "Global shortcuts using evdev"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/icewind1991/evdev-shortcut"
|
||||
rust-version = "1.78.0"
|
||||
|
||||
[dependencies]
|
||||
evdev = { version = "0.12.1", optional = true, features = ["tokio"] }
|
||||
|
|
@ -20,7 +21,13 @@ tracing = "0.1.37"
|
|||
test-case = "3.1.0"
|
||||
glob = "0.3.1"
|
||||
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
||||
tokio-stream = "0.1.17"
|
||||
|
||||
[features]
|
||||
listener = ["evdev", "futures", "async-stream"]
|
||||
default = ["listener"]
|
||||
|
||||
[[example]]
|
||||
name = "listen"
|
||||
path = "examples/listen.rs"
|
||||
required-features = ["listener"]
|
||||
|
|
@ -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.
|
||||
See [shortcutd](https://github.com/icewind1991/shortcutd) for a solution to running the elevated input handling in a separate process.
|
||||
Note that raw access to evdev devices is a privileged operation and usually
|
||||
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 futures::{pin_mut, StreamExt};
|
||||
use std::pin::pin;
|
||||
use tokio_stream::StreamExt;
|
||||
use glob::GlobError;
|
||||
use evdev_shortcut::{Key, Modifier, Shortcut, ShortcutListener};
|
||||
|
||||
|
|
@ -11,9 +12,7 @@ async fn main() {
|
|||
let devices =
|
||||
glob::glob("/dev/input/by-id/*-kbd").unwrap().collect::<Result<Vec<PathBuf>, GlobError>>().unwrap();
|
||||
|
||||
let stream = listener.listen(&devices).unwrap();
|
||||
|
||||
pin_mut!(stream);
|
||||
let mut stream = pin!(listener.listen(&devices).unwrap());
|
||||
|
||||
while let Some(event) = stream.next().await {
|
||||
println!("{} {}", event.shortcut, event.state);
|
||||
|
|
|
|||
95
flake.lock
generated
95
flake.lock
generated
|
|
@ -1,64 +1,98 @@
|
|||
{
|
||||
"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": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1686242667,
|
||||
"narHash": "sha256-I7Kwp06WX/9E+rEND1i1wjdKQQm3XiDxYOyNK9fuJu0=",
|
||||
"owner": "icewind1991",
|
||||
"repo": "naersk",
|
||||
"rev": "6d245a3bbb2ee31ec726bb57b9a8b206302e7110",
|
||||
"lastModified": 1749473391,
|
||||
"narHash": "sha256-NkkS2d7OvXL9VJS1pae+txd43vUIMWtxlIBdsKCRtYg=",
|
||||
"owner": "nix-community",
|
||||
"repo": "flakelight",
|
||||
"rev": "22c8488d41c4e4678d32538f855bc05b3ba5142e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "icewind1991",
|
||||
"repo": "naersk",
|
||||
"rev": "6d245a3bbb2ee31ec726bb57b9a8b206302e7110",
|
||||
"owner": "nix-community",
|
||||
"repo": "flakelight",
|
||||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1686059680,
|
||||
"narHash": "sha256-sp0WlCIeVczzB0G8f8iyRg3IYW7KG31mI66z7HIZwrI=",
|
||||
"lastModified": 1749237914,
|
||||
"narHash": "sha256-N5waoqWt8aMr/MykZjSErOokYH6rOsMMXu3UOVH5kiw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a558f7ac29f50c4b937fb5c102f587678ae1c9fb",
|
||||
"rev": "70c74b02eac46f4e4aa071e45a6189ce0f6d9265",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-23.05",
|
||||
"ref": "nixos-25.05",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay",
|
||||
"utils": "utils"
|
||||
"flakelight": "flakelight",
|
||||
"mill-scale": "mill-scale",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"mill-scale",
|
||||
"flakelight",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1686191569,
|
||||
"narHash": "sha256-8ey5FOXNms9piFGTn6vJeAQmSKk+NL7GTMSoVttsNTs=",
|
||||
"lastModified": 1742697269,
|
||||
"narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "b4b71458b92294e8f1c3a112d972e3cff8a2ab71",
|
||||
"rev": "01973c84732f9275c50c5f075dd1f54cc04b3316",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -66,21 +100,6 @@
|
|||
"repo": "rust-overlay",
|
||||
"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",
|
||||
|
|
|
|||
51
flake.nix
51
flake.nix
|
|
@ -1,48 +1,15 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-23.05";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
naersk.url = "github:icewind1991/naersk?rev=6d245a3bbb2ee31ec726bb57b9a8b206302e7110";
|
||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
||||
rust-overlay.inputs.flake-utils.follows = "utils";
|
||||
nixpkgs.url = "nixpkgs/nixos-25.05";
|
||||
flakelight = {
|
||||
url = "github:nix-community/flakelight";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
utils,
|
||||
naersk,
|
||||
rust-overlay,
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (system: let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
pkgs = (import nixpkgs) {
|
||||
inherit system overlays;
|
||||
mill-scale = {
|
||||
url = "git+https://codeberg.org/icewind/mill-scale";
|
||||
inputs.flakelight.follows = "flakelight";
|
||||
};
|
||||
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];
|
||||
};
|
||||
});
|
||||
outputs = {mill-scale, ...}:
|
||||
mill-scale ./. {};
|
||||
}
|
||||
|
|
|
|||
40
src/lib.rs
40
src/lib.rs
|
|
@ -11,9 +11,12 @@
|
|||
//! ```rust,no_run
|
||||
//! # use std::path::PathBuf;
|
||||
//! # 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 futures::stream::StreamExt;
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # #[cfg(feature = "listener")]
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let listener = ShortcutListener::new();
|
||||
|
|
@ -30,6 +33,10 @@
|
|||
//! }
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! # #[cfg(not(feature = "listener"))]
|
||||
//! # fn main() {
|
||||
//! # // placeholder so we can build the doc example without default features
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
pub use keycodes::Key;
|
||||
|
|
@ -134,21 +141,26 @@ pub struct ModifierList(u8);
|
|||
|
||||
impl ModifierList {
|
||||
pub fn new(modifiers: &[Modifier]) -> Self {
|
||||
ModifierList(modifiers
|
||||
ModifierList(
|
||||
modifiers
|
||||
.iter()
|
||||
.fold(0, |mask, modifier| mask | modifier.mask()))
|
||||
.fold(0, |mask, modifier| mask | modifier.mask()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mask(&self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn modifiers(&self) -> impl Iterator<Item=Modifier> {
|
||||
pub fn modifiers(&self) -> impl Iterator<Item = Modifier> {
|
||||
let mask = self.mask();
|
||||
ALL_MODIFIERS.iter().copied().filter(move |modifier| {
|
||||
for combined in COMBINED_MODIFIERS {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +190,8 @@ impl FromStr for ModifierList {
|
|||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let modifiers = s.split('>')
|
||||
let modifiers = s
|
||||
.split('>')
|
||||
.filter(|part| !part.is_empty())
|
||||
.map(|part| {
|
||||
if !part.starts_with('<') {
|
||||
|
|
@ -251,8 +264,8 @@ impl Display for Shortcut {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_case::test_case;
|
||||
use crate::{Key, Modifier, ModifierList, Shortcut};
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case("KeyP", Shortcut::new(& [], 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::Shift, Modifier::Meta])]
|
||||
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 {
|
||||
self.to_string()
|
||||
.replace(['<', '>'], "")
|
||||
.replace('-', "_")
|
||||
self.to_string().replace(['<', '>'], "").replace('-', "_")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +336,7 @@ mod triggered_tests {
|
|||
#[test_case("<Ctrl><Alt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyRightAlt, Key::KeyLeft] => true)]
|
||||
fn shortcut_triggered(s: &str, keys: &[Key]) -> bool {
|
||||
let shortcut: Shortcut = s.parse().unwrap();
|
||||
shortcut.is_triggered(&keys.into_iter().copied().collect())
|
||||
shortcut.is_triggered(&keys.iter().copied().collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::{DeviceOpenError, Key, Shortcut, ShortcutEvent, ShortcutState};
|
||||
use async_stream::stream;
|
||||
use evdev::Device;
|
||||
use futures::pin_mut;
|
||||
use futures::stream::iter;
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::{Shortcut, DeviceOpenError, Key, ShortcutEvent, ShortcutState};
|
||||
use std::path::Path;
|
||||
use async_stream::stream;
|
||||
use futures::pin_mut;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::stream::{iter};
|
||||
use tracing::{debug, trace, info};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
/// A listener for shortcut events
|
||||
///
|
||||
|
|
@ -42,19 +42,28 @@ impl ShortcutListener {
|
|||
/// 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.
|
||||
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 devices = devices
|
||||
.iter()
|
||||
.map(|path| {
|
||||
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");
|
||||
res
|
||||
})
|
||||
.collect::<Result<Vec<Device>, DeviceOpenError>>()?;
|
||||
let events = iter(devices.into_iter().flat_map(|device| device.into_event_stream()))
|
||||
let events = iter(
|
||||
devices
|
||||
.into_iter()
|
||||
.flat_map(|device| device.into_event_stream()),
|
||||
)
|
||||
.flatten();
|
||||
|
||||
Ok(stream! {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue