terrible nixos module

This commit is contained in:
nora 2025-09-18 21:30:58 +02:00
parent cead214aa1
commit b2ff5b0763
8 changed files with 128 additions and 22 deletions

View file

@ -3,6 +3,12 @@ name = "clippyboard"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "clippyboard-daemon"
[[bin]]
name = "clippyboard-select"
[dependencies]
ciborium = "0.2.2"
dirs = "6.0.0"

View file

@ -21,7 +21,9 @@
];
postFixup = ''
wrapProgram $out/bin/clippyboard \
wrapProgram $out/bin/clippyboard-select \
--suffix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath buildInputs}
wrapProgram $out/bin/clippyboard-daemon \
--suffix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath buildInputs}
'';

27
flake.lock generated Normal file
View file

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1758029226,
"narHash": "sha256-TjqVmbpoCqWywY9xIZLTf6ANFvDCXdctCjoYuYPYdMI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "08b8f92ac6354983f5382124fef6006cade4a1c1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

47
flake.nix Normal file
View file

@ -0,0 +1,47 @@
# warning: this flake is probably terrible, whatever
{
description = "clippyboard: a clipboard manager";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { nixpkgs, ... }:
let
lib = nixpkgs.lib;
clippyboard-package = ./default.nix;
systems = lib.intersectLists lib.systems.flakeExposed lib.platforms.linux;
forAllSystems = lib.genAttrs systems;
in
{
packages = forAllSystems (system: { default = nixpkgs.${system}.callPackage clippyboard-package { }; });
nixosModules.default = { lib, config, pkgs, ... }:
let
cfg = config.services.clippyboard;
clippyboard = pkgs.callPackage clippyboard-package { };
in
{
options.services.clippyboard = {
enable = lib.mkEnableOption "Enable the clippyboard daemon and clippyboard program";
};
config = lib.mkIf cfg.enable {
nixpkgs.overlays = [
(final: prev: {
clipboard = clippyboard;
})
];
systemd.user.services.clippyboard = {
description = "a clipboard manager";
wantedBy = [ "graphical-session.target" ];
after = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = lib.getExe' clippyboard "clippyboard-daemon";
};
};
environment.systemPackages = [ clippyboard ];
};
};
};
}

View file

@ -0,0 +1,4 @@
fn main() -> Result<(), eyre::Error> {
let socket_path: std::path::PathBuf = clippyboard::socket_path()?;
clippyboard::daemon::main(&socket_path)
}

View file

@ -0,0 +1,4 @@
fn main() -> Result<(), eyre::Error> {
let socket_path = clippyboard::socket_path()?;
clippyboard::display::main(&socket_path)
}

View file

@ -8,11 +8,16 @@ use rustix::event::PollFd;
use rustix::event::PollFlags;
use rustix::fs::OFlags;
use std::collections::HashMap;
use std::convert::Infallible;
use std::io;
use std::io::ErrorKind;
use std::io::PipeReader;
use std::io::{BufReader, BufWriter, PipeWriter, Read, Write};
use std::os::fd::AsFd;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock, atomic::AtomicU64};
use std::time::Duration;
use std::time::SystemTime;
@ -430,11 +435,24 @@ fn read_fd_into_history(
}
pub fn main(socket_path: &PathBuf) -> eyre::Result<()> {
let Err(err) = main_inner(socket_path);
if let Some(ioerr) = err.downcast_ref::<io::Error>()
&& ioerr.kind() == ErrorKind::AddrInUse
{
// no cleanup
return Err(err);
}
cleanup(socket_path);
Err(err)
}
pub fn main_inner(socket_path: &PathBuf) -> eyre::Result<Infallible> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("info")))
.init();
let _ = std::fs::remove_file(&socket_path); // lol
let socket = UnixListener::bind(&socket_path)
.wrap_err_with(|| format!("binding path {}", socket_path.display()))?;
@ -481,9 +499,11 @@ pub fn main(socket_path: &PathBuf) -> eyre::Result<()> {
rustix::fs::fcntl_setfl(notify_write_recv.as_fd(), OFlags::NONBLOCK).expect("todo");
rustix::fs::fcntl_setfl(conn.as_fd(), OFlags::NONBLOCK).expect("TODO");
let socket_path_clone = socket_path.to_owned();
std::thread::spawn(move || {
if let Err(err) = dispatch_wayland(queue, wl_state, notify_write_recv) {
error!("error on Wayland thread: {err:?}");
cleanup(&socket_path_clone);
std::process::exit(1);
}
});
@ -507,5 +527,13 @@ pub fn main(socket_path: &PathBuf) -> eyre::Result<()> {
}
}
Ok(())
unreachable!("socket.incoming will never return None")
}
fn cleanup(socket_path: &PathBuf) {
static HAS_DONE_CLEANUP: AtomicBool = AtomicBool::new(false);
if !HAS_DONE_CLEANUP.swap(true, Ordering::Relaxed) {
let _ = std::fs::remove_file(&socket_path);
}
}

View file

@ -1,9 +1,9 @@
mod daemon;
mod display;
pub mod daemon;
pub mod display;
use eyre::{OptionExt, bail};
use eyre::OptionExt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::sync::Arc;
use std::{path::PathBuf, sync::Arc};
const MAX_ENTRY_SIZE: u64 = 50_000_000;
const MAX_HISTORY_BYTE_SIZE: usize = 100_000_000;
@ -33,20 +33,8 @@ const MESSAGE_READ: u8 = 1;
/// Argument: One u64-bit LE value, the ID
const MESSAGE_COPY: u8 = 2;
fn main() -> eyre::Result<()> {
let Some(mode) = std::env::args().nth(1) else {
bail!("missing mode");
};
let socket_path = dirs::runtime_dir()
pub fn socket_path() -> eyre::Result<PathBuf> {
Ok(dirs::runtime_dir()
.ok_or_eyre("missing XDG_RUNTIME_DIR")?
.join("clippyboard.sock");
match mode.as_str() {
"daemon" => daemon::main(&socket_path)?,
"display" => display::main(&socket_path)?,
_ => panic!("invalid mode, supported: daemon, display"),
}
Ok(())
.join("clippyboard.sock"))
}