fix(linux): make installed printer forwarding reliable
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
This commit is contained in:
parent
60c88aa8fc
commit
869f14cf19
@ -52,9 +52,15 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
say "Installing CUPS printer tools..."
|
||||||
|
$sudo_cmd apt-get update
|
||||||
|
$sudo_cmd apt-get install -y cups-client policykit-1 || $sudo_cmd apt-get install -y cups-client
|
||||||
say "Installing FieldLens Print..."
|
say "Installing FieldLens Print..."
|
||||||
$sudo_cmd apt-get install -y "$package_path"
|
$sudo_cmd apt-get install -y "$package_path"
|
||||||
elif command -v apt >/dev/null 2>&1; then
|
elif command -v apt >/dev/null 2>&1; then
|
||||||
|
say "Installing CUPS printer tools..."
|
||||||
|
$sudo_cmd apt update
|
||||||
|
$sudo_cmd apt install -y cups-client policykit-1 || $sudo_cmd apt install -y cups-client
|
||||||
say "Installing FieldLens Print..."
|
say "Installing FieldLens Print..."
|
||||||
$sudo_cmd apt install -y "$package_path"
|
$sudo_cmd apt install -y "$package_path"
|
||||||
elif command -v dpkg >/dev/null 2>&1; then
|
elif command -v dpkg >/dev/null 2>&1; then
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fieldlens/print",
|
"name": "@fieldlens/print",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fieldlens-print"
|
name = "fieldlens-print"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "FieldLens print-stream barcode capture companion"
|
description = "FieldLens print-stream barcode capture companion"
|
||||||
authors = ["Arrowstack"]
|
authors = ["Arrowstack"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@ -11,6 +11,9 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -170,14 +173,25 @@ fn handle_raw_connection(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tcp_requested = !config.forward_addr.trim().is_empty();
|
||||||
|
let (forwarded, final_error) = if forward_error.is_some() {
|
||||||
|
(false, forward_error)
|
||||||
|
} else if tcp_requested {
|
||||||
|
(true, None)
|
||||||
|
} else if !config.forward_printer.trim().is_empty() {
|
||||||
|
forward_to_system_printer(&config.forward_printer, &bytes)
|
||||||
|
} else {
|
||||||
|
(false, None)
|
||||||
|
};
|
||||||
|
|
||||||
process_payload(
|
process_payload(
|
||||||
state,
|
state,
|
||||||
app,
|
app,
|
||||||
"raw-proxy",
|
"raw-proxy",
|
||||||
label,
|
label,
|
||||||
bytes,
|
bytes,
|
||||||
!config.forward_addr.trim().is_empty() && forward_error.is_none(),
|
forwarded,
|
||||||
forward_error,
|
final_error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +251,7 @@ fn spool_folder_loop(
|
|||||||
|
|
||||||
match fs::read(&path) {
|
match fs::read(&path) {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
let (forwarded, error) = forward_all(&config.forward_addr, &bytes);
|
let (forwarded, error) = forward_payload(&config, &bytes);
|
||||||
process_payload(
|
process_payload(
|
||||||
&state,
|
&state,
|
||||||
&app,
|
&app,
|
||||||
@ -287,6 +301,16 @@ fn unique_processed_path(dir: &std::path::Path, filename: &str) -> PathBuf {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn forward_payload(config: &AppConfig, bytes: &[u8]) -> (bool, Option<String>) {
|
||||||
|
if !config.forward_addr.trim().is_empty() {
|
||||||
|
return forward_all(&config.forward_addr, bytes);
|
||||||
|
}
|
||||||
|
if !config.forward_printer.trim().is_empty() {
|
||||||
|
return forward_to_system_printer(&config.forward_printer, bytes);
|
||||||
|
}
|
||||||
|
(false, None)
|
||||||
|
}
|
||||||
|
|
||||||
fn forward_all(address: &str, bytes: &[u8]) -> (bool, Option<String>) {
|
fn forward_all(address: &str, bytes: &[u8]) -> (bool, Option<String>) {
|
||||||
if address.trim().is_empty() {
|
if address.trim().is_empty() {
|
||||||
return (false, None);
|
return (false, None);
|
||||||
@ -306,6 +330,57 @@ fn forward_all(address: &str, bytes: &[u8]) -> (bool, Option<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn forward_to_system_printer(printer: &str, bytes: &[u8]) -> (bool, Option<String>) {
|
||||||
|
let mut child = match Command::new("lp")
|
||||||
|
.args(["-d", printer, "-o", "raw", "-t", "FieldLens Print"])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(child) => child,
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return (
|
||||||
|
false,
|
||||||
|
Some("CUPS client tools are missing; install cups-client".into()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
return (false, Some(format!("forward to CUPS printer failed: {error}")))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
if let Err(error) = stdin.write_all(bytes) {
|
||||||
|
let _ = child.kill();
|
||||||
|
return (false, Some(format!("forward to CUPS printer failed: {error}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match child.wait_with_output() {
|
||||||
|
Ok(output) if output.status.success() => (true, None),
|
||||||
|
Ok(output) => (
|
||||||
|
false,
|
||||||
|
Some(format!(
|
||||||
|
"CUPS forward failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr).trim()
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Err(error) => (false, Some(format!("CUPS forward failed: {error}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
fn forward_to_system_printer(printer: &str, _bytes: &[u8]) -> (bool, Option<String>) {
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
Some(format!(
|
||||||
|
"installed-printer RAW forwarding is not available for '{printer}' without a network RAW/TCP address"
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn process_payload(
|
fn process_payload(
|
||||||
state: &SharedState,
|
state: &SharedState,
|
||||||
app: &AppHandle,
|
app: &AppHandle,
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
use std::{
|
use std::process::Command;
|
||||||
fs,
|
|
||||||
process::Command,
|
#[cfg(target_os = "windows")]
|
||||||
};
|
use std::fs;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
pub const VIRTUAL_PRINTER_NAME: &str = "FieldLens Print";
|
pub const VIRTUAL_PRINTER_NAME: &str = "FieldLens Print";
|
||||||
@ -77,7 +80,7 @@ pub fn install_virtual_printer(target_printer: &str) -> Result<VirtualPrinterSta
|
|||||||
install_windows_virtual_printer(target_printer)?;
|
install_windows_virtual_printer(target_printer)?;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
install_linux_virtual_printer()?;
|
install_linux_virtual_printer(target_printer)?;
|
||||||
|
|
||||||
virtual_printer_status()
|
virtual_printer_status()
|
||||||
}
|
}
|
||||||
@ -242,10 +245,15 @@ fn ps_quote(value: &str) -> String {
|
|||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn list_linux_printers() -> Result<Vec<SystemPrinter>> {
|
fn list_linux_printers() -> Result<Vec<SystemPrinter>> {
|
||||||
let output = Command::new("lpstat")
|
let output = match Command::new("lpstat").arg("-v").output() {
|
||||||
.arg("-v")
|
Ok(output) => output,
|
||||||
.output()
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||||
.context("query CUPS printers with lpstat")?;
|
return Err(anyhow!(
|
||||||
|
"CUPS client tools are missing. Install the cups-client package, then reopen FieldLens Print."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error).context("query CUPS printers with lpstat"),
|
||||||
|
};
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
@ -289,27 +297,42 @@ fn socket_uri_to_address(uri: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn install_linux_virtual_printer() -> Result<()> {
|
fn install_linux_virtual_printer(target_printer: &str) -> Result<()> {
|
||||||
let args = [
|
let printers = list_linux_printers()?;
|
||||||
"-p",
|
if !printers.iter().any(|printer| printer.name == target_printer && !printer.is_virtual) {
|
||||||
"FieldLens-Print",
|
return Err(anyhow!(
|
||||||
"-E",
|
"selected printer '{target_printer}' is not installed or available in CUPS"
|
||||||
"-v",
|
));
|
||||||
"socket://127.0.0.1:9100",
|
}
|
||||||
"-m",
|
|
||||||
"raw",
|
let ppd_path = PathBuf::from("/etc/cups/ppd").join(format!("{target_printer}.ppd"));
|
||||||
"-D",
|
let mut args = vec![
|
||||||
VIRTUAL_PRINTER_NAME,
|
"-p".to_string(),
|
||||||
|
"FieldLens-Print".to_string(),
|
||||||
|
"-E".to_string(),
|
||||||
|
"-v".to_string(),
|
||||||
|
"socket://127.0.0.1:9100".to_string(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let direct = Command::new("lpadmin").args(args).status();
|
if ppd_path.exists() {
|
||||||
|
args.push("-P".to_string());
|
||||||
|
args.push(ppd_path.to_string_lossy().into_owned());
|
||||||
|
} else {
|
||||||
|
args.push("-m".to_string());
|
||||||
|
args.push("raw".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push("-D".to_string());
|
||||||
|
args.push(VIRTUAL_PRINTER_NAME.to_string());
|
||||||
|
|
||||||
|
let direct = Command::new("lpadmin").args(&args).status();
|
||||||
if matches!(direct, Ok(status) if status.success()) {
|
if matches!(direct, Ok(status) if status.success()) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = Command::new("pkexec")
|
let status = Command::new("pkexec")
|
||||||
.arg("lpadmin")
|
.arg("lpadmin")
|
||||||
.args(args)
|
.args(&args)
|
||||||
.status()
|
.status()
|
||||||
.context("run elevated CUPS printer setup")?;
|
.context("run elevated CUPS printer setup")?;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "FieldLens Print",
|
"productName": "FieldLens Print",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"identifier": "dev.arrowstack.fieldlens.print",
|
"identifier": "dev.arrowstack.fieldlens.print",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
@ -34,7 +34,17 @@
|
|||||||
"installMode": "currentUser"
|
"installMode": "currentUser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"createUpdaterArtifacts": true
|
"createUpdaterArtifacts": true,
|
||||||
|
"linux": {
|
||||||
|
"deb": {
|
||||||
|
"depends": [
|
||||||
|
"cups-client"
|
||||||
|
],
|
||||||
|
"recommends": [
|
||||||
|
"policykit-1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
|
|||||||
11
src/main.ts
11
src/main.ts
@ -69,7 +69,7 @@ let config: AppConfig
|
|||||||
let jobs: Job[] = []
|
let jobs: Job[] = []
|
||||||
let status: Status
|
let status: Status
|
||||||
let loginTimer: number | undefined
|
let loginTimer: number | undefined
|
||||||
let currentVersion = '0.1.2'
|
let currentVersion = '0.1.3'
|
||||||
let bundleType: BundleType | null = null
|
let bundleType: BundleType | null = null
|
||||||
let availableUpdate: Update | null = null
|
let availableUpdate: Update | null = null
|
||||||
let updateBusy = false
|
let updateBusy = false
|
||||||
@ -221,7 +221,8 @@ function renderPrinters() {
|
|||||||
const current = select.value || config?.forwardPrinter || ''
|
const current = select.value || config?.forwardPrinter || ''
|
||||||
const options = systemPrinters.filter((printer) => !printer.isVirtual)
|
const options = systemPrinters.filter((printer) => !printer.isVirtual)
|
||||||
|
|
||||||
select.innerHTML = '<option value="">Choose a printer…</option>' + options.map((printer) => {
|
const emptyLabel = options.length ? 'Choose a printer…' : 'No installed printers found'
|
||||||
|
select.innerHTML = `<option value="">${emptyLabel}</option>` + options.map((printer) => {
|
||||||
const suffix = printer.address ? ` · ${printer.address}` : printer.port ? ` · ${printer.port}` : ''
|
const suffix = printer.address ? ` · ${printer.address}` : printer.port ? ` · ${printer.port}` : ''
|
||||||
return `<option value="${escapeHtml(printer.name)}">${escapeHtml(printer.name + suffix)}</option>`
|
return `<option value="${escapeHtml(printer.name)}">${escapeHtml(printer.name + suffix)}</option>`
|
||||||
}).join('')
|
}).join('')
|
||||||
@ -234,10 +235,16 @@ function renderPrinters() {
|
|||||||
help.textContent = `Detected RAW network endpoint: ${selected.address}`
|
help.textContent = `Detected RAW network endpoint: ${selected.address}`
|
||||||
} else if (selected) {
|
} else if (selected) {
|
||||||
help.textContent = 'No RAW/TCP address was exposed for this queue. Enter the printer IP:9100 manually.'
|
help.textContent = 'No RAW/TCP address was exposed for this queue. Enter the printer IP:9100 manually.'
|
||||||
|
} else if (config?.forwardPrinter && !options.some((printer) => printer.name === config.forwardPrinter)) {
|
||||||
|
help.textContent = `Previously selected printer "${config.forwardPrinter}" is not installed or available. Add it in system printer settings, then Refresh printers.`
|
||||||
|
} else if (!options.length) {
|
||||||
|
help.textContent = 'No installed CUPS printers were found. Add the physical printer in system settings, then Refresh printers.'
|
||||||
} else {
|
} else {
|
||||||
help.textContent = 'Select the physical printer that should receive the original print bytes.'
|
help.textContent = 'Select the physical printer that should receive the original print bytes.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
el<HTMLButtonElement>('install-printer').disabled = !options.length
|
||||||
|
|
||||||
const virtualState = el('virtual-printer-state')
|
const virtualState = el('virtual-printer-state')
|
||||||
virtualState.textContent = virtualPrinterStatus?.detail ?? 'Printer status unavailable.'
|
virtualState.textContent = virtualPrinterStatus?.detail ?? 'Printer status unavailable.'
|
||||||
virtualState.classList.toggle('ready', Boolean(virtualPrinterStatus?.installed))
|
virtualState.classList.toggle('ready', Boolean(virtualPrinterStatus?.installed))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user