Skip to content

Commit fc745d5

Browse files
devvaannshabose
authored andcommitted
fix(tauri): add native macOS screen color picker for styles-bar eyedropper
WebKit (the app's WKWebView) lacks the web EyeDropper API, so the styles-bar color picker routes the eyedropper through the editor to a new pick_screen_color Tauri command backed by NSColorSampler. Registers the command in the invoke handler. Non-macOS returns an explicit error.
1 parent d045aa6 commit fc745d5

1 file changed

Lines changed: 79 additions & 1 deletion

File tree

src-tauri/src/main.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,84 @@ struct CaptureRect {
425425
height: f64,
426426
}
427427

428+
// macOS native screen eyedropper for the styles-bar color picker. WebKit (the
429+
// app's WKWebView) does not implement the web EyeDropper API, so the previewed
430+
// page routes here through the editor. Shows the system color sampler
431+
// (NSColorSampler, macOS 10.15+) and resolves with the picked color as
432+
// "#rrggbb", or "" when the user dismisses the sampler without choosing.
433+
#[tauri::command]
434+
async fn pick_screen_color(window: tauri::Window) -> Result<String, String> {
435+
#[cfg(not(target_os = "macos"))]
436+
{
437+
let _ = &window;
438+
return Err("pick_screen_color is only implemented on macOS".to_string());
439+
}
440+
441+
#[cfg(target_os = "macos")]
442+
{
443+
let (tx, rx) = tokio::sync::oneshot::channel::<Result<String, String>>();
444+
445+
// with_webview runs the closure on the main (UI) thread, which
446+
// NSColorSampler requires; the webview pointer itself is unused — the
447+
// sampler is a screen-wide system picker independent of the webview.
448+
window.with_webview(move |_webview| {
449+
unsafe {
450+
let cls = match objc::runtime::Class::get("NSColorSampler") {
451+
Some(c) => c,
452+
None => {
453+
let _ = tx.send(Err(
454+
"NSColorSampler is unavailable on this macOS version".to_string()));
455+
return;
456+
}
457+
};
458+
// `new` returns a +1-retained instance; intentionally never
459+
// released so it outlives the async selection handler.
460+
let sampler: cocoa::base::id = msg_send![cls, new];
461+
462+
// Wrap the sender so the handler closure is Fn (not FnOnce).
463+
let tx = std::sync::Arc::new(std::sync::Mutex::new(Some(tx)));
464+
465+
let handler = block::ConcreteBlock::new(move |color: cocoa::base::id| {
466+
let mut guard = tx.lock().unwrap();
467+
let tx = match guard.take() {
468+
Some(tx) => tx,
469+
None => return,
470+
};
471+
if color.is_null() {
472+
// dismissed without choosing a color
473+
let _ = tx.send(Ok(String::new()));
474+
return;
475+
}
476+
// Normalize to sRGB before reading components — the sampled
477+
// color can arrive in an arbitrary color space.
478+
let srgb_space: cocoa::base::id =
479+
msg_send![class!(NSColorSpace), sRGBColorSpace];
480+
let srgb: cocoa::base::id = msg_send![color, colorUsingColorSpace: srgb_space];
481+
if srgb.is_null() {
482+
let _ = tx.send(Ok(String::new()));
483+
return;
484+
}
485+
let r: f64 = msg_send![srgb, redComponent];
486+
let g: f64 = msg_send![srgb, greenComponent];
487+
let b: f64 = msg_send![srgb, blueComponent];
488+
let to_byte = |v: f64| -> u8 {
489+
let n = (v * 255.0).round();
490+
if n < 0.0 { 0 } else if n > 255.0 { 255 } else { n as u8 }
491+
};
492+
let hex = format!("#{:02x}{:02x}{:02x}", to_byte(r), to_byte(g), to_byte(b));
493+
let _ = tx.send(Ok(hex));
494+
});
495+
let handler = handler.copy();
496+
let selection_handler: &block::Block<(cocoa::base::id,), ()> = &handler;
497+
498+
let () = msg_send![sampler, showSamplerWithSelectionHandler: selection_handler];
499+
}
500+
}).map_err(|e| e.to_string())?;
501+
502+
return rx.await.map_err(|_| "Color picker channel closed".to_string())?;
503+
}
504+
}
505+
428506
#[tauri::command]
429507
async fn capture_page(window: tauri::Window, rect: Option<CaptureRect>) -> Result<Vec<u8>, String> {
430508
#[cfg(target_os = "linux")]
@@ -883,7 +961,7 @@ fn main() {
883961
put_item, get_item, get_all_items, delete_item,
884962
trust_window_aes_key, remove_trust_window_aes_key,
885963
_get_windows_drives, _rename_path, show_in_folder, move_to_trash, zoom_window,
886-
_get_clipboard_files, _open_url_in_browser_win, capture_page])
964+
_get_clipboard_files, _open_url_in_browser_win, capture_page, pick_screen_color])
887965
.setup(|app| {
888966
init::init_app(app);
889967
#[cfg(target_os = "linux")]

0 commit comments

Comments
 (0)