claude-mpm 5.4.55__py3-none-any.whl → 5.4.85__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +63 -241
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +36 -9
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/skills.py +166 -14
- claude_mpm/cli/executor.py +1 -0
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/startup.py +223 -388
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -2
- claude_mpm/core/interactive_session.py +7 -7
- claude_mpm/core/output_style_manager.py +21 -13
- claude_mpm/core/unified_config.py +50 -8
- claude_mpm/core/unified_paths.py +30 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +8 -0
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +7 -4
- claude_mpm/services/agents/startup_sync.py +5 -2
- claude_mpm/services/pm_skills_deployer.py +4 -0
- claude_mpm/services/skills/git_skill_source_manager.py +24 -8
- claude_mpm/services/skills/selective_skill_deployer.py +82 -83
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/pm-bug-reporting/pm-bug-reporting.md +248 -0
- claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/pm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/METADATA +47 -23
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/RECORD +159 -47
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
# Platform Integration
|
|
2
|
+
|
|
3
|
+
Comprehensive guide to integrating with native platform features across Windows, macOS, and Linux in Rust desktop applications.
|
|
4
|
+
|
|
5
|
+
## File System Access
|
|
6
|
+
|
|
7
|
+
### File Dialogs
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use tauri::api::dialog::{FileDialogBuilder, MessageDialogBuilder, MessageDialogKind};
|
|
11
|
+
|
|
12
|
+
#[tauri::command]
|
|
13
|
+
async fn open_file_dialog() -> Result<Option<String>, String> {
|
|
14
|
+
let path = FileDialogBuilder::new()
|
|
15
|
+
.add_filter("Text Files", &["txt", "md"])
|
|
16
|
+
.add_filter("All Files", &["*"])
|
|
17
|
+
.set_title("Select a file")
|
|
18
|
+
.pick_file();
|
|
19
|
+
|
|
20
|
+
Ok(path.map(|p| p.to_string_lossy().to_string()))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[tauri::command]
|
|
24
|
+
async fn open_folder_dialog() -> Result<Option<String>, String> {
|
|
25
|
+
let path = FileDialogBuilder::new()
|
|
26
|
+
.set_title("Select a folder")
|
|
27
|
+
.pick_folder();
|
|
28
|
+
|
|
29
|
+
Ok(path.map(|p| p.to_string_lossy().to_string()))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[tauri::command]
|
|
33
|
+
async fn save_file_dialog() -> Result<Option<String>, String> {
|
|
34
|
+
let path = FileDialogBuilder::new()
|
|
35
|
+
.add_filter("JSON Files", &["json"])
|
|
36
|
+
.set_file_name("untitled.json")
|
|
37
|
+
.save_file();
|
|
38
|
+
|
|
39
|
+
Ok(path.map(|p| p.to_string_lossy().to_string()))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[tauri::command]
|
|
43
|
+
async fn show_message(title: String, message: String) -> Result<(), String> {
|
|
44
|
+
MessageDialogBuilder::new(title, message)
|
|
45
|
+
.kind(MessageDialogKind::Info)
|
|
46
|
+
.show();
|
|
47
|
+
|
|
48
|
+
Ok(())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[tauri::command]
|
|
52
|
+
async fn confirm_dialog(title: String, message: String) -> Result<bool, String> {
|
|
53
|
+
let confirmed = MessageDialogBuilder::new(title, message)
|
|
54
|
+
.kind(MessageDialogKind::Warning)
|
|
55
|
+
.buttons(tauri::api::dialog::MessageDialogButtons::OkCancel)
|
|
56
|
+
.show();
|
|
57
|
+
|
|
58
|
+
Ok(confirmed)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Safe File System Operations
|
|
63
|
+
|
|
64
|
+
```rust
|
|
65
|
+
use std::path::{Path, PathBuf};
|
|
66
|
+
use std::fs;
|
|
67
|
+
|
|
68
|
+
// Validate file paths to prevent directory traversal
|
|
69
|
+
fn validate_path(path: &str, base_dir: &Path) -> Result<PathBuf, String> {
|
|
70
|
+
let path = Path::new(path);
|
|
71
|
+
|
|
72
|
+
// Canonicalize to resolve .. and symlinks
|
|
73
|
+
let canonical = path
|
|
74
|
+
.canonicalize()
|
|
75
|
+
.map_err(|_| "Invalid path".to_string())?;
|
|
76
|
+
|
|
77
|
+
// Ensure path is within base directory
|
|
78
|
+
if !canonical.starts_with(base_dir) {
|
|
79
|
+
return Err("Path outside allowed directory".to_string());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Ok(canonical)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[tauri::command]
|
|
86
|
+
async fn read_file_safe(app: tauri::AppHandle, relative_path: String) -> Result<String, String> {
|
|
87
|
+
let app_dir = app
|
|
88
|
+
.path()
|
|
89
|
+
.app_data_dir()
|
|
90
|
+
.map_err(|e| e.to_string())?;
|
|
91
|
+
|
|
92
|
+
let file_path = validate_path(&relative_path, &app_dir)?;
|
|
93
|
+
|
|
94
|
+
fs::read_to_string(file_path).map_err(|e| e.to_string())
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[tauri::command]
|
|
98
|
+
async fn write_file_safe(
|
|
99
|
+
app: tauri::AppHandle,
|
|
100
|
+
relative_path: String,
|
|
101
|
+
content: String,
|
|
102
|
+
) -> Result<(), String> {
|
|
103
|
+
let app_dir = app
|
|
104
|
+
.path()
|
|
105
|
+
.app_data_dir()
|
|
106
|
+
.map_err(|e| e.to_string())?;
|
|
107
|
+
|
|
108
|
+
// Ensure directory exists
|
|
109
|
+
fs::create_dir_all(&app_dir).map_err(|e| e.to_string())?;
|
|
110
|
+
|
|
111
|
+
let file_path = app_dir.join(&relative_path);
|
|
112
|
+
|
|
113
|
+
// Security check
|
|
114
|
+
let canonical = file_path
|
|
115
|
+
.canonicalize()
|
|
116
|
+
.or_else(|_| {
|
|
117
|
+
// File doesn't exist yet, validate parent
|
|
118
|
+
file_path
|
|
119
|
+
.parent()
|
|
120
|
+
.ok_or("Invalid path")?
|
|
121
|
+
.canonicalize()
|
|
122
|
+
.map(|p| p.join(file_path.file_name().unwrap()))
|
|
123
|
+
})
|
|
124
|
+
.map_err(|_| "Invalid path".to_string())?;
|
|
125
|
+
|
|
126
|
+
if !canonical.starts_with(&app_dir) {
|
|
127
|
+
return Err("Path outside allowed directory".to_string());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs::write(canonical, content).map_err(|e| e.to_string())
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### File Watching
|
|
135
|
+
|
|
136
|
+
```rust
|
|
137
|
+
use notify::{Watcher, RecursiveMode, Event};
|
|
138
|
+
use std::sync::mpsc::channel;
|
|
139
|
+
use std::time::Duration;
|
|
140
|
+
|
|
141
|
+
struct FileWatcher {
|
|
142
|
+
watcher: notify::RecommendedWatcher,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
impl FileWatcher {
|
|
146
|
+
fn new(app_handle: tauri::AppHandle) -> Result<Self, String> {
|
|
147
|
+
let (tx, rx) = channel();
|
|
148
|
+
|
|
149
|
+
let mut watcher = notify::recommended_watcher(tx)
|
|
150
|
+
.map_err(|e| e.to_string())?;
|
|
151
|
+
|
|
152
|
+
// Spawn task to handle events
|
|
153
|
+
tokio::spawn(async move {
|
|
154
|
+
while let Ok(event) = rx.recv() {
|
|
155
|
+
if let Ok(Event { kind, paths, .. }) = event {
|
|
156
|
+
let _ = app_handle.emit("file-changed", FileChangeEvent {
|
|
157
|
+
kind: format!("{:?}", kind),
|
|
158
|
+
paths: paths.iter().map(|p| p.to_string_lossy().to_string()).collect(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
Ok(Self { watcher })
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fn watch(&mut self, path: &str) -> Result<(), String> {
|
|
168
|
+
self.watcher
|
|
169
|
+
.watch(Path::new(path), RecursiveMode::Recursive)
|
|
170
|
+
.map_err(|e| e.to_string())
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fn unwatch(&mut self, path: &str) -> Result<(), String> {
|
|
174
|
+
self.watcher
|
|
175
|
+
.unwatch(Path::new(path))
|
|
176
|
+
.map_err(|e| e.to_string())
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[derive(Clone, serde::Serialize)]
|
|
181
|
+
struct FileChangeEvent {
|
|
182
|
+
kind: String,
|
|
183
|
+
paths: Vec<String>,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[tauri::command]
|
|
187
|
+
fn watch_directory(
|
|
188
|
+
watcher: tauri::State<FileWatcher>,
|
|
189
|
+
path: String,
|
|
190
|
+
) -> Result<(), String> {
|
|
191
|
+
watcher.inner().lock().unwrap().watch(&path)
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## System Tray Integration
|
|
196
|
+
|
|
197
|
+
### Cross-Platform System Tray
|
|
198
|
+
|
|
199
|
+
```rust
|
|
200
|
+
use tauri::{
|
|
201
|
+
menu::{Menu, MenuItem, Submenu},
|
|
202
|
+
tray::{TrayIconBuilder, TrayIconEvent},
|
|
203
|
+
Manager, Runtime,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> Result<(), Box<dyn std::error::Error>> {
|
|
207
|
+
// Create menu items
|
|
208
|
+
let show_item = MenuItem::with_id(app, "show", "Show Window", true, None::<&str>)?;
|
|
209
|
+
let hide_item = MenuItem::with_id(app, "hide", "Hide Window", true, None::<&str>)?;
|
|
210
|
+
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
|
211
|
+
|
|
212
|
+
// Create submenu
|
|
213
|
+
let settings_menu = Submenu::with_items(
|
|
214
|
+
app,
|
|
215
|
+
"Settings",
|
|
216
|
+
true,
|
|
217
|
+
&[
|
|
218
|
+
&MenuItem::with_id(app, "preferences", "Preferences", true, None::<&str>)?,
|
|
219
|
+
&MenuItem::with_id(app, "about", "About", true, None::<&str>)?,
|
|
220
|
+
],
|
|
221
|
+
)?;
|
|
222
|
+
|
|
223
|
+
// Create menu
|
|
224
|
+
let menu = Menu::with_items(app, &[&show_item, &hide_item, &settings_menu, &quit_item])?;
|
|
225
|
+
|
|
226
|
+
// Build tray icon
|
|
227
|
+
let _tray = TrayIconBuilder::new()
|
|
228
|
+
.icon(app.default_window_icon().unwrap().clone())
|
|
229
|
+
.menu(&menu)
|
|
230
|
+
.tooltip("My Application")
|
|
231
|
+
.on_menu_event(|app, event| match event.id.as_ref() {
|
|
232
|
+
"show" => {
|
|
233
|
+
if let Some(window) = app.get_webview_window("main") {
|
|
234
|
+
let _ = window.show();
|
|
235
|
+
let _ = window.set_focus();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
"hide" => {
|
|
239
|
+
if let Some(window) = app.get_webview_window("main") {
|
|
240
|
+
let _ = window.hide();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
"quit" => {
|
|
244
|
+
app.exit(0);
|
|
245
|
+
}
|
|
246
|
+
"preferences" => {
|
|
247
|
+
// Open preferences window
|
|
248
|
+
println!("Open preferences");
|
|
249
|
+
}
|
|
250
|
+
"about" => {
|
|
251
|
+
// Show about dialog
|
|
252
|
+
println!("Show about dialog");
|
|
253
|
+
}
|
|
254
|
+
_ => {}
|
|
255
|
+
})
|
|
256
|
+
.on_tray_icon_event(|tray, event| {
|
|
257
|
+
if let TrayIconEvent::Click { .. } = event {
|
|
258
|
+
// Handle tray icon click
|
|
259
|
+
let app = tray.app_handle();
|
|
260
|
+
if let Some(window) = app.get_webview_window("main") {
|
|
261
|
+
if window.is_visible().unwrap_or(false) {
|
|
262
|
+
let _ = window.hide();
|
|
263
|
+
} else {
|
|
264
|
+
let _ = window.show();
|
|
265
|
+
let _ = window.set_focus();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
.build(app)?;
|
|
271
|
+
|
|
272
|
+
Ok(())
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
fn main() {
|
|
276
|
+
tauri::Builder::default()
|
|
277
|
+
.setup(|app| {
|
|
278
|
+
create_tray(app.handle())?;
|
|
279
|
+
Ok(())
|
|
280
|
+
})
|
|
281
|
+
.run(tauri::generate_context!())
|
|
282
|
+
.expect("error while running tauri application");
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Dynamic Tray Menu Updates
|
|
287
|
+
|
|
288
|
+
```rust
|
|
289
|
+
use std::sync::Mutex;
|
|
290
|
+
|
|
291
|
+
struct TrayState {
|
|
292
|
+
is_recording: Mutex<bool>,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#[tauri::command]
|
|
296
|
+
fn toggle_recording(
|
|
297
|
+
app: tauri::AppHandle,
|
|
298
|
+
state: tauri::State<TrayState>,
|
|
299
|
+
) -> Result<(), String> {
|
|
300
|
+
let mut is_recording = state.is_recording.lock().unwrap();
|
|
301
|
+
*is_recording = !*is_recording;
|
|
302
|
+
|
|
303
|
+
// Update tray menu
|
|
304
|
+
let tray = app.tray_by_id("main").ok_or("Tray not found")?;
|
|
305
|
+
|
|
306
|
+
let menu_item = tray
|
|
307
|
+
.get_item("toggle_recording")
|
|
308
|
+
.ok_or("Menu item not found")?;
|
|
309
|
+
|
|
310
|
+
menu_item
|
|
311
|
+
.set_text(if *is_recording {
|
|
312
|
+
"Stop Recording"
|
|
313
|
+
} else {
|
|
314
|
+
"Start Recording"
|
|
315
|
+
})
|
|
316
|
+
.map_err(|e| e.to_string())?;
|
|
317
|
+
|
|
318
|
+
Ok(())
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Native Notifications
|
|
323
|
+
|
|
324
|
+
### Cross-Platform Notifications
|
|
325
|
+
|
|
326
|
+
```rust
|
|
327
|
+
use tauri::Notification;
|
|
328
|
+
|
|
329
|
+
#[tauri::command]
|
|
330
|
+
fn send_notification(
|
|
331
|
+
app: tauri::AppHandle,
|
|
332
|
+
title: String,
|
|
333
|
+
body: String,
|
|
334
|
+
) -> Result<(), String> {
|
|
335
|
+
Notification::new(&app.config().identifier)
|
|
336
|
+
.title(title)
|
|
337
|
+
.body(body)
|
|
338
|
+
.icon("icon")
|
|
339
|
+
.show()
|
|
340
|
+
.map_err(|e| e.to_string())
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[tauri::command]
|
|
344
|
+
fn send_notification_with_action(
|
|
345
|
+
app: tauri::AppHandle,
|
|
346
|
+
title: String,
|
|
347
|
+
body: String,
|
|
348
|
+
) -> Result<(), String> {
|
|
349
|
+
// Note: Actions are platform-dependent
|
|
350
|
+
#[cfg(target_os = "macos")]
|
|
351
|
+
{
|
|
352
|
+
Notification::new(&app.config().identifier)
|
|
353
|
+
.title(title)
|
|
354
|
+
.body(body)
|
|
355
|
+
.sound("default")
|
|
356
|
+
.show()
|
|
357
|
+
.map_err(|e| e.to_string())
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[cfg(not(target_os = "macos"))]
|
|
361
|
+
{
|
|
362
|
+
Notification::new(&app.config().identifier)
|
|
363
|
+
.title(title)
|
|
364
|
+
.body(body)
|
|
365
|
+
.show()
|
|
366
|
+
.map_err(|e| e.to_string())
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Notification with User Interaction
|
|
372
|
+
|
|
373
|
+
```rust
|
|
374
|
+
use tauri::{Emitter, Manager};
|
|
375
|
+
|
|
376
|
+
#[tauri::command]
|
|
377
|
+
async fn send_interactive_notification(
|
|
378
|
+
app: tauri::AppHandle,
|
|
379
|
+
title: String,
|
|
380
|
+
body: String,
|
|
381
|
+
) -> Result<(), String> {
|
|
382
|
+
// Send notification
|
|
383
|
+
Notification::new(&app.config().identifier)
|
|
384
|
+
.title(&title)
|
|
385
|
+
.body(&body)
|
|
386
|
+
.show()
|
|
387
|
+
.map_err(|e| e.to_string())?;
|
|
388
|
+
|
|
389
|
+
// Listen for notification clicks (platform-dependent)
|
|
390
|
+
// This is a simplified example; real implementation needs platform-specific code
|
|
391
|
+
|
|
392
|
+
Ok(())
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Auto-Updates
|
|
397
|
+
|
|
398
|
+
### Tauri Updater Integration
|
|
399
|
+
|
|
400
|
+
```rust
|
|
401
|
+
use tauri_plugin_updater::UpdaterExt;
|
|
402
|
+
|
|
403
|
+
#[tauri::command]
|
|
404
|
+
async fn check_for_updates(app: tauri::AppHandle) -> Result<Option<String>, String> {
|
|
405
|
+
let update = app
|
|
406
|
+
.updater()
|
|
407
|
+
.check()
|
|
408
|
+
.await
|
|
409
|
+
.map_err(|e| e.to_string())?;
|
|
410
|
+
|
|
411
|
+
if let Some(update) = update {
|
|
412
|
+
Ok(Some(format!(
|
|
413
|
+
"Update available: {} (current: {})",
|
|
414
|
+
update.version,
|
|
415
|
+
update.current_version
|
|
416
|
+
)))
|
|
417
|
+
} else {
|
|
418
|
+
Ok(None)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#[tauri::command]
|
|
423
|
+
async fn install_update(app: tauri::AppHandle) -> Result<(), String> {
|
|
424
|
+
let update = app
|
|
425
|
+
.updater()
|
|
426
|
+
.check()
|
|
427
|
+
.await
|
|
428
|
+
.map_err(|e| e.to_string())?;
|
|
429
|
+
|
|
430
|
+
if let Some(update) = update {
|
|
431
|
+
// Download and install
|
|
432
|
+
update
|
|
433
|
+
.download_and_install(
|
|
434
|
+
|chunk_length, content_length| {
|
|
435
|
+
println!(
|
|
436
|
+
"Downloaded {} of {:?}",
|
|
437
|
+
chunk_length,
|
|
438
|
+
content_length
|
|
439
|
+
);
|
|
440
|
+
},
|
|
441
|
+
|| {
|
|
442
|
+
println!("Download finished");
|
|
443
|
+
},
|
|
444
|
+
)
|
|
445
|
+
.await
|
|
446
|
+
.map_err(|e| e.to_string())?;
|
|
447
|
+
|
|
448
|
+
// Restart app
|
|
449
|
+
app.restart();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
Ok(())
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Setup auto-update check
|
|
456
|
+
fn main() {
|
|
457
|
+
tauri::Builder::default()
|
|
458
|
+
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
459
|
+
.setup(|app| {
|
|
460
|
+
let handle = app.handle().clone();
|
|
461
|
+
|
|
462
|
+
// Check for updates on startup
|
|
463
|
+
tauri::async_runtime::spawn(async move {
|
|
464
|
+
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
|
465
|
+
|
|
466
|
+
if let Ok(Some(update)) = handle.updater().check().await {
|
|
467
|
+
println!("Update available: {}", update.version);
|
|
468
|
+
|
|
469
|
+
// Emit event to frontend
|
|
470
|
+
let _ = handle.emit("update-available", &update.version);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
Ok(())
|
|
475
|
+
})
|
|
476
|
+
.run(tauri::generate_context!())
|
|
477
|
+
.expect("error while running tauri application");
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Deep Linking / Custom URL Schemes
|
|
482
|
+
|
|
483
|
+
### Register URL Scheme
|
|
484
|
+
|
|
485
|
+
**tauri.conf.json:**
|
|
486
|
+
```json
|
|
487
|
+
{
|
|
488
|
+
"bundle": {
|
|
489
|
+
"macOS": {
|
|
490
|
+
"associatedDomains": ["myapp://"],
|
|
491
|
+
"category": "public.app-category.developer-tools"
|
|
492
|
+
},
|
|
493
|
+
"windows": {
|
|
494
|
+
"webviewInstallMode": {
|
|
495
|
+
"type": "downloadBootstrapper"
|
|
496
|
+
},
|
|
497
|
+
"protocols": [
|
|
498
|
+
{
|
|
499
|
+
"name": "myapp",
|
|
500
|
+
"schemes": ["myapp"]
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Handle Deep Links
|
|
509
|
+
|
|
510
|
+
```rust
|
|
511
|
+
use tauri::{Emitter, Manager};
|
|
512
|
+
|
|
513
|
+
fn main() {
|
|
514
|
+
tauri::Builder::default()
|
|
515
|
+
.setup(|app| {
|
|
516
|
+
// Register URL handler
|
|
517
|
+
app.listen_any("deep-link://", |event| {
|
|
518
|
+
println!("Received deep link: {:?}", event.payload());
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
Ok(())
|
|
522
|
+
})
|
|
523
|
+
.plugin(tauri_plugin_deep_link::init())
|
|
524
|
+
.run(tauri::generate_context!())
|
|
525
|
+
.expect("error while running tauri application");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#[tauri::command]
|
|
529
|
+
fn handle_url(app: tauri::AppHandle, url: String) -> Result<(), String> {
|
|
530
|
+
println!("Handling URL: {}", url);
|
|
531
|
+
|
|
532
|
+
// Parse URL and navigate
|
|
533
|
+
if url.starts_with("myapp://open/") {
|
|
534
|
+
let file = url.strip_prefix("myapp://open/").unwrap();
|
|
535
|
+
app.emit("open-file", file).map_err(|e| e.to_string())?;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
Ok(())
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Platform-Specific Features
|
|
543
|
+
|
|
544
|
+
### Windows
|
|
545
|
+
|
|
546
|
+
```rust
|
|
547
|
+
#[cfg(target_os = "windows")]
|
|
548
|
+
mod windows {
|
|
549
|
+
use winapi::um::winuser::{MessageBoxW, MB_OK};
|
|
550
|
+
use std::ffi::OsStr;
|
|
551
|
+
use std::os::windows::ffi::OsStrExt;
|
|
552
|
+
|
|
553
|
+
pub fn show_native_message_box(title: &str, message: &str) {
|
|
554
|
+
let title_wide: Vec<u16> = OsStr::new(title)
|
|
555
|
+
.encode_wide()
|
|
556
|
+
.chain(std::iter::once(0))
|
|
557
|
+
.collect();
|
|
558
|
+
|
|
559
|
+
let message_wide: Vec<u16> = OsStr::new(message)
|
|
560
|
+
.encode_wide()
|
|
561
|
+
.chain(std::iter::once(0))
|
|
562
|
+
.collect();
|
|
563
|
+
|
|
564
|
+
unsafe {
|
|
565
|
+
MessageBoxW(
|
|
566
|
+
std::ptr::null_mut(),
|
|
567
|
+
message_wide.as_ptr(),
|
|
568
|
+
title_wide.as_ptr(),
|
|
569
|
+
MB_OK,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Windows Registry access
|
|
575
|
+
use winreg::enums::*;
|
|
576
|
+
use winreg::RegKey;
|
|
577
|
+
|
|
578
|
+
pub fn read_registry_value(key_path: &str, value_name: &str) -> Option<String> {
|
|
579
|
+
let hklm = RegKey::predef(HKEY_CURRENT_USER);
|
|
580
|
+
let key = hklm.open_subkey(key_path).ok()?;
|
|
581
|
+
key.get_value(value_name).ok()
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
pub fn write_registry_value(
|
|
585
|
+
key_path: &str,
|
|
586
|
+
value_name: &str,
|
|
587
|
+
value: &str,
|
|
588
|
+
) -> Result<(), std::io::Error> {
|
|
589
|
+
let hklm = RegKey::predef(HKEY_CURRENT_USER);
|
|
590
|
+
let (key, _) = hklm.create_subkey(key_path)?;
|
|
591
|
+
key.set_value(value_name, &value)?;
|
|
592
|
+
Ok(())
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
#[tauri::command]
|
|
597
|
+
#[cfg(target_os = "windows")]
|
|
598
|
+
fn windows_specific_feature() -> Result<String, String> {
|
|
599
|
+
windows::show_native_message_box("Title", "Message");
|
|
600
|
+
|
|
601
|
+
let value = windows::read_registry_value(
|
|
602
|
+
"Software\\MyApp",
|
|
603
|
+
"Setting1",
|
|
604
|
+
)
|
|
605
|
+
.unwrap_or_default();
|
|
606
|
+
|
|
607
|
+
Ok(value)
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### macOS
|
|
612
|
+
|
|
613
|
+
```rust
|
|
614
|
+
#[cfg(target_os = "macos")]
|
|
615
|
+
mod macos {
|
|
616
|
+
use cocoa::base::nil;
|
|
617
|
+
use cocoa::foundation::NSString;
|
|
618
|
+
use objc::{class, msg_send, sel, sel_impl};
|
|
619
|
+
|
|
620
|
+
pub fn set_dock_badge(label: &str) {
|
|
621
|
+
unsafe {
|
|
622
|
+
let app = cocoa::appkit::NSApp();
|
|
623
|
+
let dock_tile: cocoa::base::id = msg_send![app, dockTile];
|
|
624
|
+
|
|
625
|
+
let badge_label = NSString::alloc(nil).init_str(label);
|
|
626
|
+
let _: () = msg_send![dock_tile, setBadgeLabel: badge_label];
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
pub fn clear_dock_badge() {
|
|
631
|
+
unsafe {
|
|
632
|
+
let app = cocoa::appkit::NSApp();
|
|
633
|
+
let dock_tile: cocoa::base::id = msg_send![app, dockTile];
|
|
634
|
+
let _: () = msg_send![dock_tile, setBadgeLabel: nil];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Access macOS services
|
|
639
|
+
use std::process::Command;
|
|
640
|
+
|
|
641
|
+
pub fn trigger_notification_center(title: &str, message: &str) {
|
|
642
|
+
let script = format!(
|
|
643
|
+
r#"display notification "{}" with title "{}""#,
|
|
644
|
+
message, title
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
Command::new("osascript")
|
|
648
|
+
.arg("-e")
|
|
649
|
+
.arg(script)
|
|
650
|
+
.output()
|
|
651
|
+
.ok();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#[tauri::command]
|
|
656
|
+
#[cfg(target_os = "macos")]
|
|
657
|
+
fn macos_specific_feature(badge: String) -> Result<(), String> {
|
|
658
|
+
macos::set_dock_badge(&badge);
|
|
659
|
+
Ok(())
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#[tauri::command]
|
|
663
|
+
#[cfg(target_os = "macos")]
|
|
664
|
+
fn clear_badge() -> Result<(), String> {
|
|
665
|
+
macos::clear_dock_badge();
|
|
666
|
+
Ok(())
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Linux
|
|
671
|
+
|
|
672
|
+
```rust
|
|
673
|
+
#[cfg(target_os = "linux")]
|
|
674
|
+
mod linux {
|
|
675
|
+
use std::process::Command;
|
|
676
|
+
|
|
677
|
+
pub fn send_desktop_notification(title: &str, message: &str) -> Result<(), String> {
|
|
678
|
+
Command::new("notify-send")
|
|
679
|
+
.arg(title)
|
|
680
|
+
.arg(message)
|
|
681
|
+
.output()
|
|
682
|
+
.map_err(|e| e.to_string())?;
|
|
683
|
+
|
|
684
|
+
Ok(())
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// D-Bus integration
|
|
688
|
+
use dbus::blocking::Connection;
|
|
689
|
+
use std::time::Duration;
|
|
690
|
+
|
|
691
|
+
pub fn get_desktop_environment() -> Result<String, Box<dyn std::error::Error>> {
|
|
692
|
+
let conn = Connection::new_session()?;
|
|
693
|
+
let proxy = conn.with_proxy(
|
|
694
|
+
"org.freedesktop.portal.Desktop",
|
|
695
|
+
"/org/freedesktop/portal/desktop",
|
|
696
|
+
Duration::from_millis(5000),
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
// Query desktop environment
|
|
700
|
+
// This is a simplified example
|
|
701
|
+
Ok("Unknown".to_string())
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
#[tauri::command]
|
|
706
|
+
#[cfg(target_os = "linux")]
|
|
707
|
+
fn linux_specific_feature(title: String, message: String) -> Result<(), String> {
|
|
708
|
+
linux::send_desktop_notification(&title, &message)
|
|
709
|
+
}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
## Permissions and Security
|
|
713
|
+
|
|
714
|
+
### Scope Configuration
|
|
715
|
+
|
|
716
|
+
```rust
|
|
717
|
+
use tauri::Manager;
|
|
718
|
+
|
|
719
|
+
fn main() {
|
|
720
|
+
tauri::Builder::default()
|
|
721
|
+
.setup(|app| {
|
|
722
|
+
// Configure file system scope
|
|
723
|
+
let scope = app.fs_scope();
|
|
724
|
+
|
|
725
|
+
// Allow access to specific directories
|
|
726
|
+
let app_data_dir = app.path().app_data_dir()?;
|
|
727
|
+
scope.allow_directory(&app_data_dir, true)?;
|
|
728
|
+
|
|
729
|
+
let documents_dir = app.path().document_dir()?;
|
|
730
|
+
scope.allow_directory(&documents_dir, false)?;
|
|
731
|
+
|
|
732
|
+
Ok(())
|
|
733
|
+
})
|
|
734
|
+
.run(tauri::generate_context!())
|
|
735
|
+
.expect("error while running tauri application");
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Runtime Permission Checks
|
|
740
|
+
|
|
741
|
+
```rust
|
|
742
|
+
use tauri::Manager;
|
|
743
|
+
|
|
744
|
+
#[tauri::command]
|
|
745
|
+
fn read_file_with_permission(
|
|
746
|
+
app: tauri::AppHandle,
|
|
747
|
+
path: String,
|
|
748
|
+
) -> Result<String, String> {
|
|
749
|
+
let scope = app.fs_scope();
|
|
750
|
+
|
|
751
|
+
// Check if path is allowed
|
|
752
|
+
if !scope.is_allowed(&path) {
|
|
753
|
+
return Err("Access denied: path not in scope".to_string());
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
std::fs::read_to_string(&path).map_err(|e| e.to_string())
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
#[tauri::command]
|
|
760
|
+
fn request_file_access(
|
|
761
|
+
app: tauri::AppHandle,
|
|
762
|
+
path: String,
|
|
763
|
+
) -> Result<(), String> {
|
|
764
|
+
let scope = app.fs_scope();
|
|
765
|
+
|
|
766
|
+
// Request access (user must approve via dialog)
|
|
767
|
+
scope
|
|
768
|
+
.allow_file(&path)
|
|
769
|
+
.map_err(|e| e.to_string())?;
|
|
770
|
+
|
|
771
|
+
Ok(())
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
These platform integration patterns enable full access to native OS features while maintaining cross-platform compatibility and security.
|