Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src-tauri/packaging/flatpak/de.u8array.zplab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Flathub manifest. Flathub builds this itself from the release deb, release.yml has no Flatpak leg.
app-id: de.u8array.zplab
runtime: org.gnome.Platform
runtime-version: "50"
sdk: org.gnome.Sdk
command: ZPLab
finish-args:
# Raw TCP to printers, Labelary previews, the loopback MCP server.
- --share=network
# The system print queue runs through libcups.
- --socket=cups
# The usblp nodes under /dev/usb are outside --device=usb.
- --device=all
# Saved printer and database passwords live in the Secret Service.
- --talk-name=org.freedesktop.secrets
- --socket=wayland
- --socket=fallback-x11
- --share=ipc
# The GTK file dialogs are not portal aware.
- --filesystem=home
modules:
- name: zplab
buildsystem: simple
rename-desktop-file: ZPLab.desktop
rename-icon: ZPLab
build-commands:
- ar x ZPLab.deb
- tar -xf data.tar.*
- install -Dm755 usr/bin/ZPLab /app/bin/ZPLab
- install -Dm755 usr/bin/zplab-mcp /app/bin/zplab-mcp
- install -Dm644 usr/share/applications/ZPLab.desktop /app/share/applications/ZPLab.desktop
- for size in 32x32 128x128; do install -Dm644 "usr/share/icons/hicolor/$size/apps/ZPLab.png" "/app/share/icons/hicolor/$size/apps/ZPLab.png"; done
# Tauri files the 256 px icon under a size no theme lookup reads.
- install -Dm644 usr/share/icons/hicolor/256x256@2/apps/ZPLab.png /app/share/icons/hicolor/256x256/apps/ZPLab.png
- install -Dm644 de.u8array.zplab.metainfo.xml /app/share/metainfo/de.u8array.zplab.metainfo.xml
# The packaged metainfo launches the deb's desktop file name.
- sed -i 's#<launchable type="desktop-id">ZPLab.desktop</launchable>#<launchable type="desktop-id">de.u8array.zplab.desktop</launchable>#' /app/share/metainfo/de.u8array.zplab.metainfo.xml
sources:
- type: file
dest-filename: ZPLab.deb
only-arches: [x86_64]
url: https://github.com/u8array/ZPLab/releases/download/v0.7.1/ZPLab_0.7.1_amd64.deb
sha256: fc9044106420d1349227c74d259949c73305709882da3e733d8e347648ba9c4a
x-checker-data:
type: json
url: https://api.github.com/repos/u8array/ZPLab/releases/latest
version-query: .tag_name | sub("^v"; "")
url-query: .assets[] | select(.name | test("_amd64\\.deb$")) | .browser_download_url
# The deb of a release carries the metainfo as it was before that release's own entry.
- type: file
path: ../linux/de.u8array.zplab.metainfo.xml
3 changes: 3 additions & 0 deletions src-tauri/packaging/flatpak/flathub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"only-arches": ["x86_64"]
}
9 changes: 9 additions & 0 deletions src-tauri/packaging/linux/de.u8array.zplab.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,13 @@
<url type="vcs-browser">https://github.com/u8array/ZPLab</url>

<content_rating type="oars-1.1" />

<releases>
<release version="0.7.1" date="2026-10-03">
<url type="details">https://github.com/u8array/ZPLab/releases/tag/v0.7.1</url>
</release>
<release version="0.7.0" date="2026-09-30">
<url type="details">https://github.com/u8array/ZPLab/releases/tag/v0.7.0</url>
</release>
</releases>
</component>
5 changes: 5 additions & 0 deletions src-tauri/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ pub fn is_packaged() -> bool {
pub fn is_packaged() -> bool {
false
}

/// The file marks every sandbox, even one started with a cleared environment.
pub fn is_flatpak() -> bool {
cfg!(target_os = "linux") && std::path::Path::new("/.flatpak-info").exists()
}
6 changes: 4 additions & 2 deletions src-tauri/src/updates.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use tauri::{AppHandle, Config};

/// Tauri's Windows updater runs the NSIS setup, which cannot replace a packaged install.
/// The Store variant also ships without plugins.updater.
/// The Store variant also ships without plugins.updater. Flathub updates a Flatpak itself.
pub fn self_update_enabled(config: &Config) -> bool {
config.plugins.0.contains_key("updater") && !crate::package::is_packaged()
config.plugins.0.contains_key("updater")
&& !crate::package::is_packaged()
&& !crate::package::is_flatpak()
}

#[tauri::command]
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ const UDEV_RULE: &str = include_str!("../packaging/udev/70-zplab.rules");
#[cfg(target_os = "linux")]
#[tauri::command]
pub async fn setup_usb_access() -> Result<(), String> {
// No pkexec in the sandbox and the host /etc is out of reach.
if crate::package::is_flatpak() {
return Err("flatpak".to_string());
}
// Used by AppImage (no installer) and as a repair path. Writes to
// /etc/udev/rules.d (writable on immutable distros, the right place for a
// runtime rule); set -e so a failed step surfaces instead of being swallowed.
Expand Down
2 changes: 1 addition & 1 deletion src/components/Output/PrintToZebraDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export function PrintToZebraDialog({ zpl, onClose }: Props) {
setUsbStatus({ type: "idle" });
} catch (e) {
// Failed or cancelled: keep usbNeedsSetup so the button stays available.
setUsbStatus({ type: "error", message: errorMessage(e) });
setUsbStatus({ type: "error", message: e === "flatpak" ? t.zebraPrint.usbSetupFlatpak : errorMessage(e) });
}
}

Expand Down
60 changes: 60 additions & 0 deletions src/components/Output/PrintToZebraDialog.usb.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// @vitest-environment jsdom
import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
import { render, cleanup, act, fireEvent, waitFor } from "@testing-library/react";
import { PrintToZebraDialog } from "./PrintToZebraDialog";
import { useLabelStore } from "../../store/labelStore";
import { DEFAULT_PRINT_TARGET } from "../../lib/printTarget";
import { fallbackTranslations as en } from "../../locales";

vi.mock("../../lib/platform", async (importOriginal) => ({ ...(await importOriginal<Record<string, unknown>>()), isDesktopShell: true }));
vi.mock("../../lib/localPrint", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
listLocalPrinters: vi.fn().mockResolvedValue([]),
}));
// vi.mock is hoisted above this const, so the factory reaches it through a closure.
const setupUsbAccess = vi.fn();
vi.mock("../../lib/usbPrint", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
listUsbPrinters: vi.fn().mockResolvedValue([{ id: "usb1", name: "Zebra ZD230", vendor_id: "0a5f" }]),
sendZplUsb: vi.fn().mockResolvedValue({ kind: "permission_denied" }),
setupUsbAccess: () => setupUsbAccess(),
}));

afterEach(cleanup);

beforeEach(() => {
setupUsbAccess.mockReset();
act(() => {
useLabelStore.setState({ zebraPrintSource: "label", dataset: null, columnMapping: null, printTarget: { ...DEFAULT_PRINT_TARGET, transport: "usb" } });
});
});

describe("PrintToZebraDialog USB access", () => {
it("shows the manual route when the sandbox refuses to install the udev rule", async () => {
setupUsbAccess.mockRejectedValue("flatpak");
const r = render(<PrintToZebraDialog zpl="^XA^XZ" onClose={vi.fn()} />);
await waitFor(() => expect(r.getByText(/Zebra ZD230/)).toBeTruthy());
await act(async () => {
fireEvent.click(r.getByText(en.zebraPrint.send));
});
await act(async () => {
fireEvent.click(r.getByText(en.zebraPrint.usbSetupAccess));
});
expect(setupUsbAccess).toHaveBeenCalledTimes(1);
expect(r.getByText(en.zebraPrint.usbSetupFlatpak)).toBeTruthy();
expect(r.getByText(en.zebraPrint.usbSetupAccess)).toBeTruthy();
});

it("keeps the raw reason for any other failure", async () => {
setupUsbAccess.mockRejectedValue("setup cancelled");
const r = render(<PrintToZebraDialog zpl="^XA^XZ" onClose={vi.fn()} />);
await waitFor(() => expect(r.getByText(/Zebra ZD230/)).toBeTruthy());
await act(async () => {
fireEvent.click(r.getByText(en.zebraPrint.send));
});
await act(async () => {
fireEvent.click(r.getByText(en.zebraPrint.usbSetupAccess));
});
expect(r.getByText("setup cancelled")).toBeTruthy();
});
});
3 changes: 2 additions & 1 deletion src/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const ar = {
checkUpdates: 'التحقق من التحديثات',
checkingUpdates: 'جارٍ التحقق…',
upToDate: 'أنت على أحدث إصدار.',
updatesViaStore: 'التحديثات تصل عبر Microsoft Store.',
updatesViaStore: 'التحديثات تصل عبر المتجر الذي ثبّت ZPLab.',
},
preview: {
providerHeading: 'عارض المعاينة',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const ar = {
tabUsb: 'USB',
usbPermissionDenied: 'لا يمكن الوصول إلى طابعة USB. انقر على «إعداد الوصول» لمنح الإذن.',
usbSetupAccess: 'إعداد الوصول',
usbSetupFlatpak: 'داخل Flatpak، لا يمكن للتطبيق تثبيت قاعدة udev بنفسه. ثبّت الملف 70-zplab.rules من مستودع ZPLab على النظام المضيف، ثم أعد توصيل الطابعة.',
usbNotFound: 'لم يتم العثور على طابعة USB، أعد توصيلها وقم بالتحديث.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const bg = {
checkUpdates: 'Проверка за актуализации',
checkingUpdates: 'Проверка…',
upToDate: 'Използвате най-новата версия.',
updatesViaStore: 'Актуализациите пристигат през Microsoft Store.',
updatesViaStore: 'Актуализациите пристигат през магазина, който инсталира ZPLab.',
},
preview: {
providerHeading: 'Визуализатор за преглед',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const bg = {
tabUsb: 'USB',
usbPermissionDenied: 'Няма достъп до USB принтера. Щракнете върху Настройване на достъп, за да го предоставите.',
usbSetupAccess: 'Настройване на достъп',
usbSetupFlatpak: 'В Flatpak приложението не може само да инсталира правилото за udev. Инсталирайте 70-zplab.rules от хранилището на ZPLab на хост системата, след което включете принтера отново.',
usbNotFound: 'USB принтерът не е намерен, включете го отново и обновете.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const cs = {
checkUpdates: 'Zkontrolovat aktualizace',
checkingUpdates: 'Kontrola…',
upToDate: 'Máte nejnovější verzi.',
updatesViaStore: 'Aktualizace přicházejí přes Microsoft Store.',
updatesViaStore: 'Aktualizace přicházejí přes obchod, který nainstaloval ZPLab.',
},
preview: {
providerHeading: 'Vykreslovač náhledu',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const cs = {
tabUsb: 'USB',
usbPermissionDenied: 'Bez přístupu k tiskárně USB. Přístup udělíte kliknutím na Nastavit přístup.',
usbSetupAccess: 'Nastavit přístup',
usbSetupFlatpak: 'Uvnitř Flatpaku si aplikace nemůže pravidlo udev nainstalovat sama. Nainstalujte 70-zplab.rules z repozitáře ZPLab na hostitelský systém a tiskárnu znovu připojte.',
usbNotFound: 'Tiskárna USB nebyla nalezena, znovu ji zapojte a obnovte.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const da = {
checkUpdates: 'Søg efter opdateringer',
checkingUpdates: 'Søger…',
upToDate: 'Du har den nyeste version.',
updatesViaStore: 'Opdateringer kommer via Microsoft Store.',
updatesViaStore: 'Opdateringer kommer via den butik, der installerede ZPLab.',
},
preview: {
providerHeading: 'Forhåndsvisningsrenderer',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const da = {
tabUsb: 'USB',
usbPermissionDenied: 'Ingen adgang til USB-printeren. Klik på Konfigurer adgang for at give den.',
usbSetupAccess: 'Konfigurer adgang',
usbSetupFlatpak: 'Inde i Flatpak kan appen ikke selv installere udev-reglen. Installer 70-zplab.rules fra ZPLab-repositoriet på værtssystemet, og tilslut derefter printeren igen.',
usbNotFound: 'USB-printer ikke fundet. Tilslut den igen, og opdater.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const de = {
checkUpdates: 'Nach Updates suchen',
checkingUpdates: 'Suche läuft…',
upToDate: 'Du bist auf dem neuesten Stand.',
updatesViaStore: 'Updates kommen über den Microsoft Store.',
updatesViaStore: 'Updates kommen über den Store, der ZPLab installiert hat.',
},
preview: {
providerHeading: 'Vorschau-Renderer',
Expand Down Expand Up @@ -1048,6 +1048,7 @@ const de = {
tabUsb: 'USB',
usbPermissionDenied: 'Kein Zugriff auf den USB-Drucker. Klicke auf Zugriff einrichten.',
usbSetupAccess: 'Zugriff einrichten',
usbSetupFlatpak: 'Innerhalb von Flatpak kann die App die udev-Regel nicht selbst installieren. Installiere 70-zplab.rules aus dem ZPLab-Repository auf dem Host und schließe den Drucker danach erneut an.',
usbNotFound: 'USB-Drucker nicht gefunden, neu einstecken und aktualisieren.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const el = {
checkUpdates: 'Έλεγχος για ενημερώσεις',
checkingUpdates: 'Έλεγχος…',
upToDate: 'Έχετε την πιο πρόσφατη έκδοση.',
updatesViaStore: 'Οι ενημερώσεις έρχονται μέσω του Microsoft Store.',
updatesViaStore: 'Οι ενημερώσεις έρχονται μέσω του καταστήματος που εγκατέστησε το ZPLab.',
},
preview: {
providerHeading: 'Μηχανή απόδοσης προεπισκόπησης',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const el = {
tabUsb: 'USB',
usbPermissionDenied: 'Δεν υπάρχει πρόσβαση στον εκτυπωτή USB. Κάντε κλικ στο Ρύθμιση πρόσβασης για να την παραχωρήσετε.',
usbSetupAccess: 'Ρύθμιση πρόσβασης',
usbSetupFlatpak: 'Μέσα στο Flatpak η εφαρμογή δεν μπορεί να εγκαταστήσει μόνη της τον κανόνα udev. Εγκαταστήστε το 70-zplab.rules από το αποθετήριο ZPLab στο σύστημα υποδοχής και συνδέστε ξανά τον εκτυπωτή.',
usbNotFound: 'Ο εκτυπωτής USB δεν βρέθηκε. Συνδέστε τον ξανά και ανανεώστε.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const en = {
checkUpdates: 'Check for updates',
checkingUpdates: 'Checking…',
upToDate: 'You are up to date.',
updatesViaStore: 'Updates arrive through the Microsoft Store.',
updatesViaStore: 'Updates arrive through the store that installed ZPLab.',
},
preview: {
providerHeading: 'Preview renderer',
Expand Down Expand Up @@ -1048,6 +1048,7 @@ const en = {
tabUsb: 'USB',
usbPermissionDenied: 'No access to the USB printer. Click Set up access to grant it.',
usbSetupAccess: 'Set up access',
usbSetupFlatpak: 'Inside Flatpak the app cannot install the udev rule itself. Install 70-zplab.rules from the ZPLab repository on the host, then re-plug the printer.',
usbNotFound: 'USB printer not found, re-plug it and refresh.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const es = {
checkUpdates: 'Buscar actualizaciones',
checkingUpdates: 'Comprobando…',
upToDate: 'Ya tienes la última versión.',
updatesViaStore: 'Las actualizaciones llegan a través de Microsoft Store.',
updatesViaStore: 'Las actualizaciones llegan a través de la tienda que instaló ZPLab.',
},
preview: {
providerHeading: 'Renderizador de vista previa',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const es = {
tabUsb: 'USB',
usbPermissionDenied: 'Sin acceso a la impresora USB. Haz clic en Configurar acceso para concederlo.',
usbSetupAccess: 'Configurar acceso',
usbSetupFlatpak: 'Dentro de Flatpak, la aplicación no puede instalar la regla udev por sí misma. Instala 70-zplab.rules desde el repositorio de ZPLab en el host y luego vuelve a conectar la impresora.',
usbNotFound: 'Impresora USB no encontrada, vuelve a conectarla y actualiza.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/et.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const et = {
checkUpdates: 'Otsi uuendusi',
checkingUpdates: 'Otsin…',
upToDate: 'Teil on uusim versioon.',
updatesViaStore: 'Uuendused tulevad Microsoft Store kaudu.',
updatesViaStore: 'Uuendused tulevad poe kaudu, mis paigaldas rakenduse ZPLab.',
},
preview: {
providerHeading: 'Eelvaate renderdaja',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const et = {
tabUsb: 'USB',
usbPermissionDenied: 'USB-printerile puudub juurdepääs. Klõpsa Seadista juurdepääs, et see anda.',
usbSetupAccess: 'Seadista juurdepääs',
usbSetupFlatpak: 'Flatpaki sees ei saa rakendus ise udev-reeglit paigaldada. Paigalda 70-zplab.rules ZPLab-i hoidlast hostsüsteemi ja ühenda printer uuesti.',
usbNotFound: 'USB-printerit ei leitud. Ühenda see uuesti ja värskenda.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const fa = {
checkUpdates: 'بررسی به‌روزرسانی‌ها',
checkingUpdates: 'در حال بررسی…',
upToDate: 'شما از آخرین نسخه استفاده می‌کنید.',
updatesViaStore: 'به‌روزرسانی‌ها از طریق Microsoft Store می‌رسند.',
updatesViaStore: 'به‌روزرسانی‌ها از طریق فروشگاهی می‌رسند که ZPLab را نصب کرده است.',
},
preview: {
providerHeading: 'موتور پیش‌نمایش',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const fa = {
tabUsb: 'USB',
usbPermissionDenied: 'دسترسی به چاپگر USB وجود ندارد. برای اعطای دسترسی روی «تنظیم دسترسی» کلیک کنید.',
usbSetupAccess: 'تنظیم دسترسی',
usbSetupFlatpak: 'داخل Flatpak، برنامه نمی‌تواند قانون udev را خودش نصب کند. فایل 70-zplab.rules را از مخزن ZPLab روی میزبان نصب کنید، سپس چاپگر را دوباره وصل کنید.',
usbNotFound: 'چاپگر USB یافت نشد، آن را دوباره وصل کنید و به‌روزرسانی کنید.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/fi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const fi = {
checkUpdates: 'Tarkista päivitykset',
checkingUpdates: 'Tarkistetaan…',
upToDate: 'Sinulla on uusin versio.',
updatesViaStore: 'Päivitykset saapuvat Microsoft Storen kautta.',
updatesViaStore: 'Päivitykset saapuvat sen kaupan kautta, joka asensi sovelluksen ZPLab.',
},
preview: {
providerHeading: 'Esikatselun renderöijä',
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const fi = {
tabUsb: 'USB',
usbPermissionDenied: 'Ei käyttöoikeutta USB-tulostimeen. Napsauta Määritä käyttöoikeus myöntääksesi sen.',
usbSetupAccess: 'Määritä käyttöoikeus',
usbSetupFlatpak: 'Flatpakin sisällä sovellus ei voi asentaa udev-sääntöä itse. Asenna 70-zplab.rules ZPLab-tietovarastosta isäntäjärjestelmään ja kytke tulostin uudelleen.',
usbNotFound: 'USB-tulostinta ei löytynyt. Kytke se uudelleen ja päivitä.',
},
pdfExport: {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ const fr = {
checkUpdates: 'Rechercher des mises à jour',
checkingUpdates: 'Vérification…',
upToDate: 'Vous êtes à jour.',
updatesViaStore: 'Les mises à jour arrivent via le Microsoft Store.',
updatesViaStore: 'Les mises à jour arrivent via la boutique qui a installé ZPLab.',
},
preview: {
providerHeading: "Moteur d'aperçu",
Expand Down Expand Up @@ -1790,6 +1790,7 @@ const fr = {
tabUsb: 'USB',
usbPermissionDenied: "Aucun accès à l'imprimante USB. Clique sur Configurer l'accès pour l'autoriser.",
usbSetupAccess: "Configurer l'accès",
usbSetupFlatpak: "À l'intérieur de Flatpak, l'application ne peut pas installer la règle udev elle-même. Installe 70-zplab.rules depuis le dépôt ZPLab sur l'hôte, puis rebranche l'imprimante.",
usbNotFound: 'Imprimante USB introuvable, rebranche-la et actualise.',
},
pdfExport: {
Expand Down
Loading
Loading