@ -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/**"], |
||||
}, |
||||
}, |
||||
})); |
||||