@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
{ |
||||||
|
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
# Tauri + React |
||||||
|
|
||||||
|
This template should help get you started developing with Tauri and React in Vite. |
||||||
|
|
||||||
|
## Recommended IDE Setup |
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>Tauri + React</title> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="/src/main.jsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
{ |
||||||
|
"name": "printer", |
||||||
|
"private": true, |
||||||
|
"version": "0.1.0", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "vite build", |
||||||
|
"preview": "vite preview", |
||||||
|
"tauri": "tauri" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@tailwindcss/vite": "^4.1.11", |
||||||
|
"@tauri-apps/api": "^2", |
||||||
|
"@tauri-apps/plugin-dialog": "^2.3.2", |
||||||
|
"@tauri-apps/plugin-opener": "^2", |
||||||
|
"react": "^18.3.1", |
||||||
|
"react-dom": "^18.3.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@tauri-apps/cli": "^2", |
||||||
|
"@vitejs/plugin-react": "^4.3.4", |
||||||
|
"autoprefixer": "^10.4.21", |
||||||
|
"postcss": "^8.5.6", |
||||||
|
"tailwindcss": "^4.1.11", |
||||||
|
"vite": "^6.0.3" |
||||||
|
} |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,7 @@ |
|||||||
|
# Generated by Cargo |
||||||
|
# will have compiled files and executables |
||||||
|
/target/ |
||||||
|
|
||||||
|
# Generated by Tauri |
||||||
|
# will have schema files for capabilities auto-completion |
||||||
|
/gen/schemas |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
|
||||||
|
added 1 package, and audited 169 packages in 3s |
||||||
|
|
||||||
|
25 packages are looking for funding |
||||||
|
run `npm fund` for details |
||||||
|
|
||||||
|
found 0 vulnerabilities |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
[package] |
||||||
|
name = "printer" |
||||||
|
version = "0.1.0" |
||||||
|
description = "A Tauri App" |
||||||
|
authors = ["you"] |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[lib] |
||||||
|
# The `_lib` suffix may seem redundant but it is necessary |
||||||
|
# to make the lib name unique and wouldn't conflict with the bin name. |
||||||
|
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 |
||||||
|
name = "printer_lib" |
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"] |
||||||
|
|
||||||
|
[build-dependencies] |
||||||
|
tauri-build = { version = "2", features = [] } |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
tauri = { version = "2", features = [] } |
||||||
|
tauri-plugin-opener = "2" |
||||||
|
serde = { version = "1", features = ["derive"] } |
||||||
|
serde_json = "1" |
||||||
|
printers = "2.2.0" |
||||||
|
winprint = "0.2.0" |
||||||
|
tauri-plugin-dialog = "2" |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
fn main() { |
||||||
|
tauri_build::build() |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"$schema": "../gen/schemas/desktop-schema.json", |
||||||
|
"identifier": "default", |
||||||
|
"description": "Capability for the main window", |
||||||
|
"windows": [ |
||||||
|
"main" |
||||||
|
], |
||||||
|
"permissions": [ |
||||||
|
"core:default", |
||||||
|
"opener:default", |
||||||
|
"dialog:default" |
||||||
|
] |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 50 KiB |
@ -0,0 +1,136 @@ |
|||||||
|
// use printers::{get_default_printer, get_printers};
|
||||||
|
// use printers::common::base::job::PrinterJobOptions;
|
||||||
|
|
||||||
|
// #[tauri::command]
|
||||||
|
// fn get_all_printers(){
|
||||||
|
|
||||||
|
// let printers=get_printers();
|
||||||
|
// for printer in printers {
|
||||||
|
// println!("{:?}", printer);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[tauri::command]
|
||||||
|
// fn print_document(document_path: String) {
|
||||||
|
|
||||||
|
// println!("Printing document: {}", document_path);
|
||||||
|
|
||||||
|
// let default_printer = get_default_printer();
|
||||||
|
// if let Some(ref printer) = default_printer {
|
||||||
|
|
||||||
|
// println!("Default Printer: {:?}", printer);
|
||||||
|
|
||||||
|
// let job_id = default_printer.unwrap().print_file(document_path.as_str(), PrinterJobOptions::none());
|
||||||
|
|
||||||
|
// } else {
|
||||||
|
// println!("No default printer found.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
use std::collections::HashMap; |
||||||
|
use std::path::Path; |
||||||
|
|
||||||
|
use winprint::printer::{FilePrinter, ImagePrinter, PdfiumPrinter, PrinterDevice}; |
||||||
|
use winprint::ticket::{ |
||||||
|
FeatureOptionPackWithPredefined, PredefinedMediaName, PrintCapabilities, PrintTicket, |
||||||
|
PrintTicketBuilder, |
||||||
|
}; |
||||||
|
|
||||||
|
#[tauri::command] |
||||||
|
fn get_all_printers() -> Vec<String> { |
||||||
|
let printers = PrinterDevice::all().expect("Failed to get printers"); |
||||||
|
let printer_names: Vec<String> = printers.iter().map(|p| p.name().to_string()).collect(); |
||||||
|
println!("Available Printers: {:?}", printer_names); |
||||||
|
|
||||||
|
return printer_names; |
||||||
|
} |
||||||
|
|
||||||
|
fn get_my_device(device_name: String) -> Option<PrinterDevice> { |
||||||
|
let printers = PrinterDevice::all().ok()?; |
||||||
|
printers.into_iter().find(|x| x.name() == device_name) |
||||||
|
} |
||||||
|
|
||||||
|
#[tauri::command] |
||||||
|
fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>> { |
||||||
|
let my_device = match get_my_device(device_name) { |
||||||
|
Some(device) => device, |
||||||
|
None => { |
||||||
|
println!("Printer not found."); |
||||||
|
return HashMap::new(); |
||||||
|
} |
||||||
|
}; |
||||||
|
println!("My Printer: {:?}", my_device.name()); |
||||||
|
match PrintCapabilities::fetch(&my_device) { |
||||||
|
Ok(capabilities) => { |
||||||
|
let dpi_opts = capabilities.page_resolutions(); |
||||||
|
let color_opts = capabilities.page_output_colors(); |
||||||
|
let size_opts = capabilities.page_media_sizes(); |
||||||
|
|
||||||
|
let dpi_opts_value: Vec<String> = dpi_opts |
||||||
|
.filter_map(|c| Some(format!("{}x{}", c.dpi().0, c.dpi().1))) |
||||||
|
.collect(); |
||||||
|
let color_opts_value: Vec<String> = color_opts |
||||||
|
.filter_map(|c| c.as_predefined_name().map(|n| format!("{:?}", n))) |
||||||
|
.collect(); |
||||||
|
let size_opts_value: Vec<String> = size_opts |
||||||
|
.filter_map(|c| c.as_predefined_name().map(|n| format!("{:?}", n))) |
||||||
|
.collect(); |
||||||
|
|
||||||
|
println!("DPI Options: {:?}", dpi_opts_value); |
||||||
|
println!("Color Options: {:?}", color_opts_value); |
||||||
|
println!("Size Options: {:?}", size_opts_value); |
||||||
|
|
||||||
|
let mut map = HashMap::new(); |
||||||
|
map.insert("dpi_options".to_string(), dpi_opts_value); |
||||||
|
map.insert("color_options".to_string(), color_opts_value); |
||||||
|
map.insert("size_options".to_string(), size_opts_value); |
||||||
|
map |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
println!("Failed to fetch capabilities: {:?}", e); |
||||||
|
HashMap::new() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[tauri::command] |
||||||
|
fn print_document(device_name: String, document_path: String, dpi: String, color: String, size: String) { |
||||||
|
println!("Printing document: {}, {}, {}, {}", document_path, size, dpi, color); |
||||||
|
let my_device = match get_my_device(device_name) { |
||||||
|
Some(device) => device, |
||||||
|
None => { |
||||||
|
println!("My Printer not found."); |
||||||
|
return; |
||||||
|
} |
||||||
|
}; |
||||||
|
println!("My Printer: {:?}", my_device.name()); |
||||||
|
|
||||||
|
let capabilities = PrintCapabilities::fetch(&my_device).unwrap(); |
||||||
|
let a6_media = capabilities |
||||||
|
.page_media_sizes() |
||||||
|
.find(|x| format!("{:?}", x.as_predefined_name()) == size) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
let mut builder = PrintTicketBuilder::new(&my_device).unwrap(); |
||||||
|
builder.merge(a6_media).unwrap(); |
||||||
|
|
||||||
|
let ticket = builder.build().unwrap(); |
||||||
|
|
||||||
|
// let theprinter = PdfiumPrinter::new(my_device);
|
||||||
|
let theprinter = ImagePrinter::new(my_device); |
||||||
|
let path = Path::new(&document_path); |
||||||
|
theprinter.print(path, ticket).unwrap(); |
||||||
|
} |
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)] |
||||||
|
pub fn run() { |
||||||
|
tauri::Builder::default() |
||||||
|
.plugin(tauri_plugin_dialog::init()) |
||||||
|
.plugin(tauri_plugin_opener::init()) |
||||||
|
.invoke_handler(tauri::generate_handler![ |
||||||
|
get_all_printers, |
||||||
|
print_document, |
||||||
|
get_printer_capabilities |
||||||
|
]) |
||||||
|
.run(tauri::generate_context!()) |
||||||
|
.expect("error while running tauri application"); |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] |
||||||
|
|
||||||
|
fn main() { |
||||||
|
printer_lib::run() |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
{ |
||||||
|
"$schema": "https://schema.tauri.app/config/2", |
||||||
|
"productName": "printer", |
||||||
|
"version": "0.1.0", |
||||||
|
"identifier": "com.printer.app", |
||||||
|
"build": { |
||||||
|
"beforeDevCommand": "npm run dev", |
||||||
|
"devUrl": "http://localhost:1420", |
||||||
|
"beforeBuildCommand": "npm run build", |
||||||
|
"frontendDist": "../dist" |
||||||
|
}, |
||||||
|
"app": { |
||||||
|
"windows": [ |
||||||
|
{ |
||||||
|
"title": "printer", |
||||||
|
"width": 350, |
||||||
|
"height": 480 |
||||||
|
} |
||||||
|
], |
||||||
|
"security": { |
||||||
|
"csp": null |
||||||
|
} |
||||||
|
}, |
||||||
|
"bundle": { |
||||||
|
"active": true, |
||||||
|
"targets": "all", |
||||||
|
"icon": [ |
||||||
|
"icons/32x32.png", |
||||||
|
"icons/128x128.png", |
||||||
|
"icons/128x128@2x.png", |
||||||
|
"icons/icon.icns", |
||||||
|
"icons/icon.ico" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
@import "tailwindcss"; |
||||||
|
|
||||||
|
main { |
||||||
|
@apply flex flex-col items-center justify-start min-h-screen gap-4 p-4; |
||||||
|
} |
||||||
|
button{ |
||||||
|
@apply px-4 py-2 bg-fuchsia-400 text-black font-bold rounded hover:bg-fuchsia-500; |
||||||
|
} |
||||||
|
select, input[type="file"] { |
||||||
|
@apply px-4 py-2 border border-gray-300 rounded; |
||||||
|
} |
||||||
@ -0,0 +1,151 @@ |
|||||||
|
import { invoke } from "@tauri-apps/api/core"; |
||||||
|
import { open } from '@tauri-apps/plugin-dialog'; |
||||||
|
import "./App.css"; |
||||||
|
import { useEffect, useState } from "react"; |
||||||
|
|
||||||
|
function App() { |
||||||
|
|
||||||
|
const [printer, setPrinter] = useState(""); |
||||||
|
const [selectedFile, setSelectedFile] = useState(); |
||||||
|
|
||||||
|
function getPrinters() { |
||||||
|
invoke("get_all_printers") |
||||||
|
.then((printers) => { |
||||||
|
console.log("Printers:", printers); |
||||||
|
|
||||||
|
// Clear the existing options in the select element |
||||||
|
const printerSelect = document.getElementById("printer-select"); |
||||||
|
printerSelect.innerHTML = ""; |
||||||
|
|
||||||
|
for(const printer of printers) { |
||||||
|
// invoke("get_printer_capabilities", { deviceName: printer }) |
||||||
|
// .then((capabilities) => { |
||||||
|
// console.log(`Capabilities for ${printer}:`, capabilities); |
||||||
|
// }) |
||||||
|
// .catch((error) => { |
||||||
|
// console.error(`Error fetching capabilities for ${printer}:`, error); |
||||||
|
// }); |
||||||
|
const option = document.createElement("option"); |
||||||
|
option.value = printer; |
||||||
|
option.textContent = printer; |
||||||
|
printerSelect.appendChild(option); |
||||||
|
} |
||||||
|
|
||||||
|
// Set the first printer as the default selected printer |
||||||
|
if (printers.length > 0) { |
||||||
|
setPrinter(printers[0]); |
||||||
|
printerSelect.value = printers[0]; |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
.catch((error) => { |
||||||
|
console.error("Error fetching printers:", error); |
||||||
|
}); |
||||||
|
} |
||||||
|
function printDocument() { |
||||||
|
|
||||||
|
|
||||||
|
const file= selectedFile; |
||||||
|
if (!file) { |
||||||
|
console.error("No document selected for printing."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
invoke("print_document",{ |
||||||
|
deviceName: printer, // Use the selected printer |
||||||
|
documentPath: file, // Use the selected file path |
||||||
|
dpi: document.getElementById("dpi_options").value, |
||||||
|
color: document.getElementById("color_options").value, |
||||||
|
size: document.getElementById("size_options").value, |
||||||
|
}) |
||||||
|
.then(() => { |
||||||
|
console.log("Print job sent successfully."); |
||||||
|
}) |
||||||
|
.catch((error) => { |
||||||
|
console.error("Error sending print job:", error); |
||||||
|
}); |
||||||
|
} |
||||||
|
function openFile(){ |
||||||
|
open({ |
||||||
|
multiple: false, |
||||||
|
filters: [ |
||||||
|
{ name: 'PDF Files', extensions: ['pdf'] }, |
||||||
|
{ name: 'Image Files', extensions: ['png', 'jpg', 'jpeg'] }, |
||||||
|
] |
||||||
|
}).then((filePath) => { |
||||||
|
if (filePath) { |
||||||
|
setSelectedFile(filePath); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
useEffect(()=>{ |
||||||
|
|
||||||
|
if(!printer) return; |
||||||
|
invoke("get_printer_capabilities", { deviceName: printer }) |
||||||
|
.then((capabilities) => { |
||||||
|
console.log(`Capabilities for ${printer}:`, capabilities); |
||||||
|
|
||||||
|
|
||||||
|
if(capabilities.color_options){ |
||||||
|
const colorOptionsSelect = document.getElementById("color_options"); |
||||||
|
colorOptionsSelect.innerHTML = ""; |
||||||
|
for(const colorOption of capabilities.color_options) { |
||||||
|
const option = document.createElement("option"); |
||||||
|
option.value = colorOption; |
||||||
|
option.textContent = colorOption; |
||||||
|
colorOptionsSelect.appendChild(option); |
||||||
|
} |
||||||
|
} |
||||||
|
if(capabilities.dpi_options){ |
||||||
|
const dpiOptionsSelect = document.getElementById("dpi_options"); |
||||||
|
dpiOptionsSelect.innerHTML = ""; |
||||||
|
for(const dpiOption of capabilities.dpi_options) { |
||||||
|
const option = document.createElement("option"); |
||||||
|
option.value = dpiOption; |
||||||
|
option.textContent = dpiOption; |
||||||
|
dpiOptionsSelect.appendChild(option); |
||||||
|
} |
||||||
|
} |
||||||
|
if(capabilities.size_options){ |
||||||
|
const sizeOptionsSelect = document.getElementById("size_options"); |
||||||
|
sizeOptionsSelect.innerHTML = ""; |
||||||
|
for(const sizeOption of capabilities.size_options) { |
||||||
|
const option = document.createElement("option"); |
||||||
|
option.value = sizeOption; |
||||||
|
option.textContent = sizeOption; |
||||||
|
sizeOptionsSelect.appendChild(option); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}) |
||||||
|
.catch((error) => { |
||||||
|
console.error(`Error fetching capabilities for ${printer}:`, error); |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
},[printer]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
|
||||||
|
getPrinters(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<main className="container *:w-full"> |
||||||
|
<button className="!w-auto self-start !bg-fuchsia-200" onClick={getPrinters}>refresh printers</button> |
||||||
|
<select id="printer-select" value={printer} onChange={(e) => setPrinter(e.target.value)}></select> |
||||||
|
<select id="size_options"></select> |
||||||
|
<select id="dpi_options"></select> |
||||||
|
<select id="color_options"></select> |
||||||
|
<div className="flex flex-row gap-2"> |
||||||
|
<label id="document_path_label" className="flex-1 break-all">{selectedFile ? `${selectedFile}` : "Select a document to print:"}</label> |
||||||
|
<button onClick={openFile}>open</button> |
||||||
|
</div> |
||||||
|
<button onClick={printDocument}>print</button> |
||||||
|
</main> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default App; |
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,9 @@ |
|||||||
|
import React from "react"; |
||||||
|
import ReactDOM from "react-dom/client"; |
||||||
|
import App from "./App"; |
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")).render( |
||||||
|
<React.StrictMode> |
||||||
|
<App /> |
||||||
|
</React.StrictMode>, |
||||||
|
); |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
/** @type {import('tailwindcss').Config} */ |
||||||
|
module.exports = { |
||||||
|
content: [ |
||||||
|
"./src/**/*.{js,jsx,ts,tsx}", // Adjust based on your framework and file types
|
||||||
|
], |
||||||
|
theme: { |
||||||
|
extend: {}, |
||||||
|
}, |
||||||
|
plugins: [], |
||||||
|
}; |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
import { defineConfig } from "vite"; |
||||||
|
import tailwindcss from '@tailwindcss/vite'; |
||||||
|
import react from "@vitejs/plugin-react"; |
||||||
|
|
||||||
|
const host = process.env.TAURI_DEV_HOST; |
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(async () => ({ |
||||||
|
plugins: [react(), tailwindcss()], |
||||||
|
|
||||||
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
|
//
|
||||||
|
// 1. prevent vite from obscuring rust errors
|
||||||
|
clearScreen: false, |
||||||
|
// 2. tauri expects a fixed port, fail if that port is not available
|
||||||
|
server: { |
||||||
|
port: 1420, |
||||||
|
strictPort: true, |
||||||
|
host: host || false, |
||||||
|
hmr: host |
||||||
|
? { |
||||||
|
protocol: "ws", |
||||||
|
host, |
||||||
|
port: 1421, |
||||||
|
} |
||||||
|
: undefined, |
||||||
|
watch: { |
||||||
|
// 3. tell vite to ignore watching `src-tauri`
|
||||||
|
ignored: ["**/src-tauri/**"], |
||||||
|
}, |
||||||
|
}, |
||||||
|
})); |
||||||