-
-
Notifications
You must be signed in to change notification settings - Fork 81
feat: Implement adjustable region selector handles (Issue #49) #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
31cdb38
faf0d88
0defcc3
cfcf34d
8dab9b9
6617b8c
a396a13
25129fa
3fbdb94
4a8eb29
897b5c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |||||||||||||||||||||||||||
| use std::path::PathBuf; | ||||||||||||||||||||||||||||
| use std::process::{Command, Stdio}; | ||||||||||||||||||||||||||||
| use std::sync::Mutex; | ||||||||||||||||||||||||||||
| use tauri::{AppHandle, Manager}; | ||||||||||||||||||||||||||||
| use tauri::{AppHandle, Emitter, Manager}; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #[cfg(target_os = "macos")] | ||||||||||||||||||||||||||||
| use objc2::msg_send; | ||||||||||||||||||||||||||||
|
|
@@ -512,3 +512,107 @@ pub async fn native_capture_ocr_region(save_dir: String) -> Result<String, Strin | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Ok(recognized_text) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Open region selector window with captured screenshots | ||||||||||||||||||||||||||||
| /// Captures all monitors and opens a fullscreen region selector window | ||||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||||
| pub async fn open_region_selector( | ||||||||||||||||||||||||||||
| app_handle: AppHandle, | ||||||||||||||||||||||||||||
| save_dir: String, | ||||||||||||||||||||||||||||
| ) -> Result<(), String> { | ||||||||||||||||||||||||||||
| // Capture all monitors | ||||||||||||||||||||||||||||
| let monitor_shots = capture_monitors(&save_dir)?; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Create the region selector window if it doesn't exist | ||||||||||||||||||||||||||||
| let window_label = "region-selector"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if let Some(existing_window) = app_handle.get_webview_window(window_label) { | ||||||||||||||||||||||||||||
| // Close existing window if any | ||||||||||||||||||||||||||||
| existing_window.close().ok(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Create new fullscreen window for region selection | ||||||||||||||||||||||||||||
| let window = tauri::WebviewWindowBuilder::new( | ||||||||||||||||||||||||||||
| &app_handle, | ||||||||||||||||||||||||||||
| window_label, | ||||||||||||||||||||||||||||
| tauri::WebviewUrl::App("index.html?region-selector=1".into()), | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| .title("Region Selector") | ||||||||||||||||||||||||||||
| .fullscreen(true) | ||||||||||||||||||||||||||||
| .decorations(false) | ||||||||||||||||||||||||||||
| .resizable(false) | ||||||||||||||||||||||||||||
| .always_on_top(true) | ||||||||||||||||||||||||||||
| .visible(false) // Start hidden, will show after setup | ||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to create region selector window: {}", e))?; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Show the window first | ||||||||||||||||||||||||||||
| window | ||||||||||||||||||||||||||||
| .show() | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to show region selector window: {}", e))?; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Give the React component time to mount and set up event listeners | ||||||||||||||||||||||||||||
| // Increased from 100ms to 800ms to ensure reliable event delivery | ||||||||||||||||||||||||||||
| // Use async sleep to avoid blocking the runtime thread | ||||||||||||||||||||||||||||
| tauri::async_runtime::sleep(std::time::Duration::from_millis(800)).await; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Emit event with screenshot data to the window | ||||||||||||||||||||||||||||
| window | ||||||||||||||||||||||||||||
| .emit( | ||||||||||||||||||||||||||||
| "region-selector-show", | ||||||||||||||||||||||||||||
| serde_json::json!({ | ||||||||||||||||||||||||||||
| "screenshotPath": monitor_shots.first().map(|m| m.path.clone()).unwrap_or_default(), | ||||||||||||||||||||||||||||
| "monitorShots": monitor_shots, | ||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to emit region selector event: {}", e))?; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Emit capture complete event to main window | ||||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||||
| pub async fn emit_capture_complete(app_handle: AppHandle, path: String) -> Result<(), String> { | ||||||||||||||||||||||||||||
| if let Some(main_window) = app_handle.get_webview_window("main") { | ||||||||||||||||||||||||||||
| main_window | ||||||||||||||||||||||||||||
| .emit("capture-complete", serde_json::json!({ "path": path })) | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to emit capture complete: {}", e))?; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Restore and focus main window | ||||||||||||||||||||||||||||
| main_window.show().ok(); | ||||||||||||||||||||||||||||
| main_window.set_focus().ok(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Clean up a temporary file safely | ||||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||||
| pub async fn cleanup_temp_file(path: String) -> Result<(), String> { | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||
| // Security check: ensure the file is within the system temp directory | ||||||||||||||||||||||||||||
| let temp_dir = std::env::temp_dir(); | ||||||||||||||||||||||||||||
| let canonical_temp = temp_dir.canonicalize().unwrap_or(temp_dir); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| let path_buf = PathBuf::from(&path); | ||||||||||||||||||||||||||||
| let canonical_path = path_buf.canonicalize().map_err(|e| format!("Invalid path: {}", e))?; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if !canonical_path.starts_with(&canonical_temp) { | ||||||||||||||||||||||||||||
| return Err("Security violation: Attempted to delete file outside of temp directory".to_string()); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| std::fs::remove_file(&canonical_path) | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to remove temp file: {}", e)) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Restore the main window | ||||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||||
| pub async fn restore_main_window(app_handle: AppHandle) -> Result<(), String> { | ||||||||||||||||||||||||||||
| if let Some(main_window) = app_handle.get_webview_window("main") { | ||||||||||||||||||||||||||||
| main_window | ||||||||||||||||||||||||||||
| .show() | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to show main window: {}", e))?; | ||||||||||||||||||||||||||||
| main_window | ||||||||||||||||||||||||||||
| .set_focus() | ||||||||||||||||||||||||||||
| .map_err(|e| format!("Failed to focus main window: {}", e))?; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
happy-dom@20.xrequires Node >= 20 (per itsengines). Might be worth double-checking CI/dev tooling is pinned to Node 20+ so installs/tests don’t start failing unexpectedly.