flake + ci rework

This commit is contained in:
Robin Appelman 2025-06-08 16:09:28 +02:00
commit a387c7d8a4
9 changed files with 138 additions and 261 deletions

16
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,16 @@
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

View file

@ -2,35 +2,33 @@ name: Release
on:
release:
types: [ created ]
types: [created]
permissions:
contents: write
jobs:
release-binaries:
name: Build release binaries
runs-on: ubuntu-latest
build:
runs-on: nix
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: icewind1991/attic-action@v1
- uses: https://codeberg.org/icewind/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
name: link
instance: https://cache.icewind.link
authToken: "${{ secrets.ATTIC_TOKEN }}"
- run: nix build .#x86_64-unknown-linux-musl
- name: Upload binary to release
uses: svenstaro/upload-release-action@v2
uses: https://github.com/svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: result/bin/evtype
asset_name: evtype
asset_name: logsmash-x86_64-unknown-linux-musl
tag: ${{ github.ref }}
- name: Upload Daemon binary to release
uses: svenstaro/upload-release-action@v2
uses: https://github.com/svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: result/bin/evtype_daemon
asset_name: evtype_daemon
tag: ${{ github.ref }}
tag: ${{ github.ref }}

View file

@ -1,59 +0,0 @@
on: [ push, pull_request ]
name: CI
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- 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@v4
- uses: cachix/install-nix-action@v27
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#clippy
build:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#x86_64-unknown-linux-musl
- uses: actions/upload-artifact@v4
with:
name: evtype
path: result/bin/*
build-nixpkgs:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#evtype

View file

@ -4,28 +4,33 @@ Virtual typing using evdev.
## What
EvType is made to replace the `xdotool type` command for wayland systems, where there is no option for virtual keyboard input.
EvType is made to replace the `xdotool type` command for wayland systems, where
there is no option for virtual keyboard input.
## Usage
- Start the `evtype_daemon` as root using your favorite init daemon (a systemd unit is [included](evtype.service)).
- Start the `evtype_daemon` as root using your favorite init daemon (a systemd
unit is [included](evtype.service)).
- Run `evtype <text>` to enter some text trough the virtual keyboard.
## Why a separate daemon
For security reasons your user generally doesn't have permissions to talk directly to evdev so root permissions are required.
By having a separate daemon do the talking to evdev the `evtype` tool doesn't need to be run as root and only a restricted api
is exposed to non-root users (i.e. only typing printable characters, no keyboard logging).
For security reasons your user generally doesn't have permissions to talk
directly to evdev so root permissions are required. By having a separate daemon
do the talking to evdev the `evtype` tool doesn't need to be run as root and
only a restricted api is exposed to non-root users (i.e. only typing printable
characters, no keyboard logging).
## Limitations
EvType currently always assumes qwerty layout and might result in unexpected results when the system is configured
with a different keyboard layout.
EvType currently always assumes qwerty layout and might result in unexpected
results when the system is configured with a different keyboard layout.
## Programmatic usage
You can use the `evtype_daemon` from your own programs by connecting to the unix socket at `/var/run/evtype/evtype.sock` and
send the text to be typed over the socket.
You can use the `evtype_daemon` from your own programs by connecting to the unix
socket at `/var/run/evtype/evtype.sock` and send the text to be typed over the
socket.
A basic rust example:
@ -38,4 +43,4 @@ fn main() -> Result<(), Box<dyn Error>> {
stream.write_all("hello world".as_bytes())?;
Ok(());
}
```
```

141
flake.lock generated
View file

@ -1,102 +1,98 @@
{
"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": {
"naersk": [
"naersk"
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1748868585,
"narHash": "sha256-DrrbahOQAwvNM8l5EuGxxkVS7X5/S59zcG0N9ZWQFhk=",
"owner": "nix-community",
"repo": "flakelight",
"rev": "dfbecd12d99c1bf82906521a6a7d5b75d2aa1ca2",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "flakelight",
"type": "github"
}
},
"mill-scale": {
"inputs": {
"crane": "crane",
"flakelight": [
"flakelight"
],
"nixpkgs": [
"nixpkgs"
]
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1717704286,
"narHash": "sha256-zrLB/FTKODEAlJjgO8TwbK7teTseYbjLESp8QJ/FJYc=",
"owner": "icewind1991",
"repo": "cross-naersk",
"rev": "9068daceb8f0d248dcf629944f60e92b81391bdb",
"type": "github"
"lastModified": 1749058087,
"narHash": "sha256-+XKj0IFs+7aQXydSttJczhPUz5mYhOhtp97+mfrqPE0=",
"ref": "refs/heads/main",
"rev": "a50219c7eef7771b70474ddb75ed5d285a94ee09",
"revCount": 50,
"type": "git",
"url": "https://codeberg.org/icewind/mill-scale"
},
"original": {
"owner": "icewind1991",
"repo": "cross-naersk",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1721727458,
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
"owner": "nix-community",
"repo": "naersk",
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
"type": "git",
"url": "https://codeberg.org/icewind/mill-scale"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1721949857,
"narHash": "sha256-DID446r8KsmJhbCzx4el8d9SnPiE8qa6+eEQOJ40vR0=",
"lastModified": 1749237914,
"narHash": "sha256-N5waoqWt8aMr/MykZjSErOokYH6rOsMMXu3UOVH5kiw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a1cc729dcbc31d9b0d11d86dc7436163548a9665",
"rev": "70c74b02eac46f4e4aa071e45a6189ce0f6d9265",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-24.05",
"ref": "nixos-25.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"cross-naersk": "cross-naersk",
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
"flakelight": "flakelight",
"mill-scale": "mill-scale",
"nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"mill-scale",
"flakelight",
"nixpkgs"
]
},
"locked": {
"lastModified": 1721960387,
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=",
"lastModified": 1742697269,
"narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699",
"rev": "01973c84732f9275c50c5f075dd1f54cc04b3316",
"type": "github"
},
"original": {
@ -104,21 +100,6 @@
"repo": "rust-overlay",
"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",

132
flake.nix
View file

@ -1,112 +1,36 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
naersk.inputs.nixpkgs.follows = "nixpkgs";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
cross-naersk.url = "github:icewind1991/cross-naersk";
cross-naersk.inputs.nixpkgs.follows = "nixpkgs";
cross-naersk.inputs.naersk.follows = "naersk";
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";
inputs.flakelight.follows = "flakelight";
};
};
outputs = {mill-scale, ...}:
mill-scale ./. {
withOverlays = [
(import ./nix/overlay.nix)
];
crossTargets = [
"x86_64-unknown-linux-musl"
];
outputs = {
self,
nixpkgs,
flake-utils,
naersk,
rust-overlay,
cross-naersk,
}:
flake-utils.lib.eachDefaultSystem (
system: let
overlays = [
(import rust-overlay)
(import ./nix/overlay.nix)
];
pkgs = (import nixpkgs) {
inherit system overlays;
};
inherit (pkgs) lib callPackage rust-bin mkShell;
inherit (lib.sources) sourceByRegex;
inherit (builtins) fromTOML readFile map;
msrv = (fromTOML (readFile ./Cargo.toml)).package.rust-version;
extractorMsrv = (fromTOML (readFile ./logging-extractor/Cargo.toml)).package.rust-version;
msrvToolchain = rust-bin.stable."${msrv}".default;
extractorMsrvToolchain = rust-bin.stable."${extractorMsrv}".default;
naersk' = callPackage naersk {};
msrvNaersk = callPackage naersk {
rustc = msrvToolchain;
cargo = msrvToolchain;
};
extractorMsrvNaersk = callPackage naersk {
rustc = extractorMsrvToolchain;
cargo = extractorMsrvToolchain;
};
cross-naersk' = pkgs.callPackage cross-naersk {inherit naersk;};
buildMatrix = targets: {
include =
map (target: {
inherit target;
artifactSuffix = cross-naersk'.execSufficForTarget target;
})
targets;
};
hostTarget = pkgs.hostPlatform.config;
targets = [
"x86_64-unknown-linux-musl"
hostTarget
];
releaseTargets = lib.lists.remove hostTarget targets;
src = sourceByRegex ./. ["Cargo.*" "(src)(/.*)?"];
nearskOpt = {
pname = "evtype";
root = src;
};
in rec {
packages =
lib.attrsets.genAttrs targets (target:
(cross-naersk'.buildPackage target) nearskOpt)
// {
inherit (pkgs) evtype;
check = msrvNaersk.buildPackage (nearskOpt
// {
mode = "check";
});
clippy = msrvNaersk.buildPackage (nearskOpt
// {
mode = "clippy";
});
default = pkgs.evtype;
nixosModules = {outputs, ...}: {
default = {
pkgs,
config,
lib,
...
}: {
imports = [./nix/module.nix];
config = lib.mkIf config.services.evtype.enable {
nixpkgs.overlays = [outputs.overlays.default];
services.evtype.package = lib.mkDefault pkgs.evtype;
};
apps.default = packages.default;
matrix = buildMatrix targets;
releaseMatrix = buildMatrix releaseTargets;
devShells.default = mkShell {
nativeBuildInputs = with pkgs; [msrvToolchain rustc bacon cargo-msrv cargo-insta samply];
};
}
)
// {
overlays.default = import ./nix/overlay.nix;
nixosModules.default = {
pkgs,
config,
lib,
...
}: {
imports = [./nix/module.nix];
config = lib.mkIf config.services.evtype.enable {
nixpkgs.overlays = [self.overlays.default];
services.evtype.package = lib.mkDefault pkgs.evtype;
};
};
};

View file

@ -59,4 +59,4 @@ in {
};
};
};
}
}

View file

@ -1,4 +1,5 @@
use crate::keyboard::{char_to_key, create_device};
use evdev::uinput::VirtualDevice;
use evdev::{EventType, InputEvent, Key, Synchronization};
use main_error::MainError;
use std::fs;
@ -10,7 +11,6 @@ use std::path::Path;
use std::process::ExitCode;
use std::thread::sleep;
use std::time::Duration;
use evdev::uinput::VirtualDevice;
mod keyboard;
@ -23,13 +23,21 @@ fn type_string(dev: &mut VirtualDevice, text: &str) -> Result<(), MainError> {
dev.emit(&[InputEvent::new(EventType::KEY, Key::KEY_LEFTSHIFT.0, 1)])?;
}
dev.emit(&[InputEvent::new(EventType::KEY, key.0, 1)])?;
dev.emit(&[InputEvent::new(EventType::SYNCHRONIZATION, Synchronization::SYN_REPORT.0, 1)])?;
dev.emit(&[InputEvent::new(
EventType::SYNCHRONIZATION,
Synchronization::SYN_REPORT.0,
1,
)])?;
sleep(TYPE_DELAY);
dev.emit(&[InputEvent::new(EventType::KEY, key.0, 0)])?;
if shift {
dev.emit(&[InputEvent::new(EventType::KEY, Key::KEY_LEFTSHIFT.0, 0)])?;
}
dev.emit(&[InputEvent::new(EventType::SYNCHRONIZATION, Synchronization::SYN_REPORT.0, 0)])?;
dev.emit(&[InputEvent::new(
EventType::SYNCHRONIZATION,
Synchronization::SYN_REPORT.0,
0,
)])?;
sleep(TYPE_DELAY);
}
Ok(())
@ -72,7 +80,8 @@ fn main() -> ExitCode {
ctrlc::set_handler(move || {
let _ = fs::remove_file(path);
std::process::exit(0);
}).ok();
})
.ok();
let mut text_buffer = String::new();
@ -83,7 +92,7 @@ fn main() -> ExitCode {
if let Err(e) = type_string(&mut keyboard, &text_buffer) {
eprintln!("Failed to type string: {e:?}");
}
},
}
Err(e) => eprintln!("Failed to read from socket: {:#}", e),
}
}

View file

@ -1,5 +1,5 @@
use evdev::{AttributeSet, Key};
use evdev::uinput::{VirtualDevice, VirtualDeviceBuilder};
use evdev::{AttributeSet, Key};
use main_error::MainError;
pub fn char_to_key(c: char) -> Result<(Key, bool), MainError> {
@ -155,9 +155,12 @@ pub fn create_device() -> Result<VirtualDevice, MainError> {
Key::KEY_SLASH,
Key::KEY_SPACE,
Key::KEY_ENTER,
].iter().collect::<AttributeSet<_>>();
]
.iter()
.collect::<AttributeSet<_>>();
Ok(VirtualDeviceBuilder::new()?.name("EvType virtual keyboard")
Ok(VirtualDeviceBuilder::new()?
.name("EvType virtual keyboard")
.with_keys(keys)?
.build()?)
}