Overview of Tauri System Integration and Native Features
Tauri’s System Integration Capabilities
Tauri leverages Rust backend and system-native Webview to provide deep integration with the operating system:
- File System: Read/write files and path management.
- Notifications: Send system-level notifications.
- Clipboard: Access and modify clipboard content.
- System Tray: Create tray icons and menus.
- Dialogs: Invoke native file selection dialogs.
- Network: Support for HTTP and WebSocket.
- Storage: Integration with SQLite, IndexedDB, and LocalStorage.
Native Features and Plugin Mechanism
- Built-in Features: Accessible via
tauri::apiand command system. - Plugins: Extend functionality, e.g.,
tauri-plugin-fs,tauri-plugin-notification.
Development Environment Setup
Initialize Project
npx create-tauri-app my-tauri-app
cd my-tauri-app
npm installProject Structure
my-tauri-app/
├── src/ # Frontend code
│ ├── index.html
│ ├── main.js
├── src-tauri/ # Rust backend code
│ ├── Cargo.toml
│ ├── tauri.conf.json
│ └── src/
│ └── main.rs
├── package.jsonFile System Operations
Reading and Writing Files
Configuring Permissions
src-tauri/tauri.conf.json
{
"tauri": {
"allowlist": {
"fs": {
"readFile": true,
"writeFile": true,
"scope": ["$APP/*"]
}
}
}
}Rust Backend
src-tauri/src/main.rs
use tauri::api::file::{read_text, write_text};
#[tauri::command]
fn save_file(path: String, content: String) -> Result<(), String> {
write_text(path, &content).map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
read_text(path).map_err(|e| e.to_string())
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![save_file, read_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Frontend Invocation
src/main.js
const { invoke } = window.__TAURI__.tauri;
async function saveFile() {
const content = document.getElementById('content').value;
await invoke('save_file', { path: 'tasks.txt', content });
}
async function loadFile() {
const content = await invoke('read_file', { path: 'tasks.txt' });
document.getElementById('output').textContent = content;
}File Path Management
Example Code
src-tauri/src/main.rs
use tauri::api::path::app_data_dir;
#[tauri::command]
fn get_app_path(config: tauri::Config) -> String {
app_data_dir(&config).unwrap().to_string_lossy().into_owned()
}Analysis
app_data_dir: Retrieves the application data directory.- Scope: Restricts file access range.
System Notifications
Sending Notifications
Configuring Permissions
{
"tauri": {
"allowlist": {
"notification": {
"all": true
}
}
}
}Rust Backend
src-tauri/src/main.rs
use tauri::api::notification::Notification;
#[tauri::command]
fn send_notification(title: String, body: String, app: tauri::AppHandle) {
Notification::new(&app.config().tauri.bundle.identifier)
.title(title)
.body(body)
.show()
.unwrap();
}Frontend Invocation
src/main.js
async function notify() {
await invoke('send_notification', { title: 'Task Added', body: 'A new task has been added!' });
}Configuring Notification Permissions
- Checking Permissions:
use tauri::api::notification::is_permission_granted;
#[tauri::command]
fn check_notification_permission(app: tauri::AppHandle) -> bool {
is_permission_granted(&app).unwrap_or(false)
}Clipboard Operations
Reading and Writing to Clipboard
Configuring Permissions
{
"tauri": {
"allowlist": {
"clipboard": {
"all": true
}
}
}
}Frontend Invocation
src/main.js
const { writeText, readText } = window.__TAURI__.clipboard;
async function copyToClipboard() {
await writeText('Hello from Tauri!');
}
async function pasteFromClipboard() {
const text = await readText();
document.getElementById('output').textContent = text;
}Clipboard Event Listening
- Using a Plugin (e.g.,
tauri-plugin-clipboard-manager):
cargo add tauri-plugin-clipboard-managerRust Backend
src-tauri/src/main.rs
use tauri_plugin_clipboard_manager::ClipboardExt;
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.setup(|app| {
app.clipboard().listen(|event| {
println!("Clipboard updated: {:?}", event.payload);
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}System Tray Icons and Menus
Creating a System Tray
Configuring Permissions
{
"tauri": {
"systemTray": {
"iconPath": "icons/icon.png"
}
}
}Rust Backend
src-tauri/src/main.rs
use tauri::tray::{SystemTray, SystemTrayEvent};
use tauri::Manager;
fn main() {
tauri::Builder::default()
.system_tray(SystemTray::new().with_tooltip("Tauri App"))
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::LeftClick { .. } => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
}
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Adding a Tray Menu
Example Code
use tauri::menu::{Menu, MenuItem};
fn main() {
let quit = MenuItem::new("quit", "Quit", None, None).unwrap();
let menu = Menu::new().unwrap().add_item(quit).unwrap();
tauri::Builder::default()
.system_tray(SystemTray::new().with_menu(menu))
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::MenuItemClick { id, .. } if id == "quit" => app.exit(0),
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}System Dialogs
Opening a File Dialog
Configuring Permissions
{
"tauri": {
"allowlist": {
"dialog": {
"open": true
}
}
}
}Frontend Invocation
src/main.js
const { open } = window.__TAURI__.dialog;
async function openFile() {
const selected = await open({
multiple: false,
filters: [{ name: 'Text', extensions: ['txt'] }],
});
document.getElementById('output').textContent = selected || 'No file selected';
}Saving a File Dialog
Configuring Permissions
{
"tauri": {
"allowlist": {
"dialog": {
"save": true
}
}
}
}Frontend Invocation
const { save } = window.__TAURI__.dialog;
async function saveFile() {
const filePath = await save({
defaultPath: 'tasks.txt',
});
if (filePath) await invoke('save_file', { path: filePath, content: 'Hello, Tauri!' });
}Network Requests and WebSocket
HTTP Requests
Configuring Permissions
{
"tauri": {
"allowlist": {
"http": {
"all": true,
"scope": ["https://api.example.com/*"]
}
}
}
}Frontend Invocation
src/main.js
const { fetch } = window.__TAURI__.http;
async function fetchData() {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
});
const data = await response.json();
document.getElementById('output').textContent = JSON.stringify(data);
}WebSocket Communication
Configuring Permissions
{
"tauri": {
"allowlist": {
"websocket": {
"all": true
}
}
}
}Frontend Invocation
src/main.js
const { WebSocketClient } = window.__TAURI__.websocket;
async function connectWebSocket() {
const ws = await WebSocketClient.connect('ws://echo.websocket.org');
ws.on('message', (msg) => {
document.getElementById('output').textContent = msg.data;
});
ws.send('Hello, WebSocket!');
}Data Storage
SQLite
Installing Plugin
cargo add tauri-plugin-sql --features sqliteRust Backend
src-tauri/src/main.rs
use tauri_plugin_sql::{Builder as SqlBuilder, Migration, MigrationKind};
fn main() {
let migrations = vec![
Migration {
version: 1,
description: "create tasks table",
sql: "CREATE TABLE tasks (id INTEGER PRIMARY KEY, text TEXT)",
kind: MigrationKind::Up,
},
];
tauri::Builder::default()
.plugin(SqlBuilder::default().add_migrations("sqlite:tasks.db", migrations).build())
.invoke_handler(tauri::generate_handler![save_task])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn save_task(text: String, app: tauri::AppHandle) -> Result<(), String> {
let db = app.sql().get("sqlite:tasks.db").unwrap();
db.execute("INSERT INTO tasks (text) VALUES (?)", [text]).map_err(|e| e.to_string())?;
Ok(())
}Frontend Invocation
async function saveTask() {
const text = document.getElementById('taskInput').value;
await invoke('save_task', { text });
}IndexedDB
Frontend Implementation
const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('TasksDB', 1);
request.onupgradeneeded = () => {
const db = request.result;
db.createObjectStore('tasks', { keyPath: 'id' });
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
async function saveToIndexedDB(task) {
const db = await openDB();
const tx = db.transaction('tasks', 'readwrite');
const store = tx.objectStore('tasks');
store.put({ id: Date.now(), text: task });
await tx.complete;
}LocalStorage
Frontend Implementation
function saveToLocalStorage(task) {
const tasks = JSON.parse(localStorage.getItem('tasks') || '[]');
tasks.push({ id: Date.now(), text: task });
localStorage.setItem('tasks', JSON.stringify(tasks));
}
function loadFromLocalStorage() {
return JSON.parse(localStorage.getItem('tasks') || '[]');
}Comprehensive Case Study: Multifunctional Task Manager
Requirements Analysis
Build a task manager that:
- Saves tasks to the file system.
- Sends system notifications for reminders.
- Copies tasks to the clipboard.
- Controls via system tray icon.
- Selects files using dialogs.
- Fetches data via network requests.
- Stores tasks in SQLite.
Implementation Code Breakdown
src-tauri/tauri.conf.json
{
"tauri": {
"allowlist": {
"fs": { "all": true },
"notification": { "all": true },
"clipboard": { "all": true },
"dialog": { "all": true },
"http": { "all": true, "scope": ["https://api.example.com/*"] },
"systemTray": { "iconPath": "icons/icon.png" }
}
}
}src-tauri/Cargo.toml
[dependencies]
tauri = { version = "1.5", features = ["api-all"] }
tauri-plugin-sql = { version = "1.1", features = ["sqlite"] }src-tauri/src/main.rs
use tauri::tray::{SystemTray, SystemTrayEvent};
use tauri::api::notification::Notification;
use tauri::Manager;
use tauri_plugin_sql::{Builder as SqlBuilder, Migration, MigrationKind};
#[tauri::command]
fn add_task(text: String, app: tauri::AppHandle) -> Result<(), String> {
let db = app.sql().get("sqlite:tasks.db").unwrap();
db.execute("INSERT INTO tasks (text) VALUES (?)", [text.clone()]).map_err(|e| e.to_string())?;
Notification::new(&app.config().tauri.bundle.identifier)
.title("Task Added")
.body(&text)
.show()
.unwrap();
Ok(())
}
#[tauri::command]
fn get_tasks(app: tauri::AppHandle) -> Result<Vec<String>, String> {
let db = app.sql().get("sqlite:tasks.db").unwrap();
let mut stmt = db.prepare("SELECT text FROM tasks").unwrap();
let tasks: Vec<String> = stmt.query_map([], |row| row.get(0)).unwrap().map(Result::unwrap).collect();
Ok(tasks)
}
fn main() {
let migrations = vec![
Migration {
version: 1,
description: "create tasks",
sql: "CREATE TABLE tasks (id INTEGER PRIMARY KEY, text TEXT)",
kind: MigrationKind::Up,
},
];
let tray = SystemTray::new().with_tooltip("Task Manager");
tauri::Builder::default()
.plugin(SqlBuilder::default().add_migrations("sqlite:tasks.db", migrations).build())
.system_tray(tray)
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::LeftClick { .. } => {
app.get_window("main").unwrap().show().unwrap();
}
_ => {}
})
.invoke_handler(tauri::generate_handler![add_task, get_tasks])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}src/main.js
const { invoke, dialog, clipboard, http } = window.__TAURI__;
async function addTask() {
const task = document.getElementById('taskInput').value.trim();
if (task) {
await invoke('add_task', { text: task });
await clipboard.writeText(task);
await updateTasks();
}
}
async function updateTasks() {
const tasks = await invoke('get_tasks');
document.getElementById('taskList').innerHTML = tasks.map(t => `<li>${t}</li>`).join('');
}
async function openFile() {
const selected = await dialog.open({ filters: [{ name: 'Text', extensions: ['txt'] }] });
if (selected) {
const response = await http.fetch('https://api.example.com/data', { method: 'GET' });
document.getElementById('output').textContent = response.data.message;
}
}
document.getElementById('addButton').addEventListener('click', addTask);
document.getElementById('openButton').addEventListener('click', openFile);
window.addEventListener('DOMContentLoaded', updateTasks);src/index.html
<!DOCTYPE html>
<html>
<head>
<title>Task Manager</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; text-align: center; }
ul { list-style: none; padding: 0; }
li { padding: 10px; background-color: #f0f0f0; margin-bottom: 5px; }
input { padding: 8px; width: 200px; }
button { padding: 8px 16px; background-color: #007bff; color: white; border: none; }
</style>
</head>
<body>
<h1>Task Manager</h1>
<input id="taskInput" placeholder="Enter task">
<button id="addButton">Add</button>
<button id="openButton">Open File</button>
<ul id="taskList"></ul>
<div id="output"></div>
<script src="main.js"></script>
</body>
</html>Running the Application
npm run tauri devAnalysis
- File System: Saves tasks to files.
- Notifications: Sends reminders when tasks are added.
- Clipboard: Copies task text.
- System Tray: Controls window visibility.
- Dialogs: Selects files.
- Network: Demonstrates HTTP requests.
- SQLite: Persists tasks.



