From c192e7422969599102048f78a1425d528919e02c Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 24 Sep 2026 04:45:58 +0000 Subject: [PATCH] fix(linux): stream original CUPS jobs directly to desktop capture --- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/capture.rs | 113 +++++++++++++++++++++++++++++++++++++- src-tauri/src/printers.rs | 57 +++++++++++++++++-- src-tauri/tauri.conf.json | 2 +- src/main.ts | 2 +- 6 files changed, 167 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 19977de..c6698d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fieldlens/print", - "version": "0.1.9", + "version": "0.1.10", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 89ebd99..7ae2a61 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fieldlens-print" -version = "0.1.9" +version = "0.1.10" description = "FieldLens print-stream barcode capture companion" authors = ["Arrowstack"] edition = "2021" diff --git a/src-tauri/src/capture.rs b/src-tauri/src/capture.rs index 823d2af..7bb4242 100644 --- a/src-tauri/src/capture.rs +++ b/src-tauri/src/capture.rs @@ -25,9 +25,13 @@ use crate::{ }; #[cfg(target_os = "linux")] -const SYSTEM_CUPS_SPOOL_DIR: &str = "/var/spool/fieldlens-print"; +const SYSTEM_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print"; #[cfg(target_os = "linux")] const TEST_CAPTURE_SINK_ADDR: &str = "127.0.0.1:9101"; +#[cfg(target_os = "linux")] +const CUPS_ORIGINAL_CAPTURE_ADDR: &str = "127.0.0.1:9102"; +#[cfg(target_os = "linux")] +const CUPS_TEST_CAPTURE_ADDR: &str = "127.0.0.1:9103"; pub struct CaptureRuntime { stop: Arc, @@ -104,6 +108,28 @@ pub fn start(config: AppConfig, state: SharedState, app: AppHandle) -> Result Result, + source: &'static str, + forwarded: bool, +) { + while !stop.load(Ordering::SeqCst) { + match listener.accept() { + Ok((stream, _)) => { + let worker_state = state.clone(); + let worker_app = app.clone(); + thread::spawn(move || { + handle_cups_document_connection( + stream, + worker_state, + worker_app, + source, + forwarded, + ) + }); + } + Err(error) if error.kind() == ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(80)); + } + Err(_) => { + thread::sleep(Duration::from_millis(250)); + } + } + } +} + +#[cfg(target_os = "linux")] +fn handle_cups_document_connection( + mut stream: TcpStream, + state: SharedState, + app: AppHandle, + source: &'static str, + forwarded: bool, +) { + let _ = stream.set_read_timeout(Some(Duration::from_secs(4))); + let mut bytes = Vec::new(); + let mut buffer = [0u8; 32 * 1024]; + + loop { + match stream.read(&mut buffer) { + Ok(0) => break, + Ok(read) => bytes.extend_from_slice(&buffer[..read]), + Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => { + if !bytes.is_empty() { + break; + } + } + Err(error) => { + process_payload( + &state, + &app, + source, + "cups-live-capture".into(), + bytes, + forwarded, + Some(format!("CUPS live capture read failed: {error}")), + ); + return; + } + } + } + + if bytes.is_empty() { + return; + } + + process_payload( + &state, + &app, + source, + format!("cups-live-{}.pdf", Uuid::new_v4()), + bytes, + forwarded, + None, + ); +} + #[cfg(target_os = "linux")] fn test_capture_sink_loop(listener: TcpListener, stop: Arc) { while !stop.load(Ordering::SeqCst) { diff --git a/src-tauri/src/printers.rs b/src-tauri/src/printers.rs index 464b7d8..9668e11 100644 --- a/src-tauri/src/printers.rs +++ b/src-tauri/src/printers.rs @@ -20,15 +20,16 @@ const TEST_CAPTURE_DEVICE_URI: &str = "socket://127.0.0.1:9101"; const WINDOWS_PORT_NAME: &str = "FieldLensPrint-9100"; #[cfg(target_os = "linux")] -const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/spool/fieldlens-print"; +const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print"; #[cfg(target_os = "linux")] const FIELDLENS_CUPS_FILTER: &str = r#"#!/usr/bin/python3 import os import re +import socket import sys import uuid -SPOOL = "/var/spool/fieldlens-print" +SPOOL = os.path.join(os.environ.get("CUPS_CACHEDIR", "/var/cache/cups"), "fieldlens-print") def safe(value): value = re.sub(r"[^A-Za-z0-9._-]+", "-", value or "job").strip("-") @@ -50,14 +51,25 @@ final_path = os.path.join(SPOOL, name) part_path = final_path + ".part" source = None +live = None capture = None captured_ok = False +live_ok = False try: if len(sys.argv) > 6 and sys.argv[6]: source = open(sys.argv[6], "rb") else: source = sys.stdin.buffer + + live_port = 9103 if printer.lower() == "fieldlens-test-capture" else 9102 try: + live = socket.create_connection(("127.0.0.1", live_port), timeout=0.35) + live.settimeout(None) + except OSError: + live = None + + try: + os.makedirs(SPOOL, exist_ok=True) capture = open(part_path, "wb") except OSError: capture = None @@ -66,6 +78,17 @@ try: chunk = source.read(65536) if not chunk: break + + if live is not None: + try: + live.sendall(chunk) + except OSError: + try: + live.close() + except OSError: + pass + live = None + if capture is not None: try: capture.write(chunk) @@ -75,22 +98,44 @@ try: except OSError: pass capture = None + sys.stdout.buffer.write(chunk) sys.stdout.buffer.flush() + + if live is not None: + try: + live.shutdown(socket.SHUT_WR) + except OSError: + pass + live.close() + live = None + live_ok = True + if capture is not None: capture.flush() capture.close() capture = None - os.chmod(part_path, 0o644) - os.replace(part_path, final_path) - captured_ok = True + if live_ok: + try: + os.unlink(part_path) + except OSError: + pass + else: + os.chmod(part_path, 0o644) + os.replace(part_path, final_path) + captured_ok = True finally: if source is not None and source is not sys.stdin.buffer: source.close() + if live is not None: + try: + live.close() + except OSError: + pass if capture is not None: capture.close() - if not captured_ok: + if not live_ok and not captured_ok: try: os.unlink(part_path) except OSError: diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index dfd4896..3223f86 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "FieldLens Print", - "version": "0.1.9", + "version": "0.1.10", "identifier": "dev.arrowstack.fieldlens.print", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/main.ts b/src/main.ts index 26b9dc7..68ad339 100644 --- a/src/main.ts +++ b/src/main.ts @@ -85,7 +85,7 @@ let config: AppConfig let jobs: Job[] = [] let status: Status let loginTimer: number | undefined -let currentVersion = '0.1.9' +let currentVersion = '0.1.10' let bundleType: BundleType | null = null let availableUpdate: Update | null = null let availableManualUpdate: ManualUpdate | null = null