fix(desktop): honor macOS title-bar double-click preference (#1674)

Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
klopez4212
2026-07-09 17:27:08 +01:00
committed by GitHub
parent 58fe9388c8
commit 6b2147a5d1
5 changed files with 199 additions and 5 deletions

View File

@@ -67,6 +67,91 @@ fn perform_sidebar_default_haptic() {
}
}
/// Performs the window action matching the macOS "double-click a window's
/// title bar to" preference (`AppleActionOnDoubleClick`).
///
/// macOS values are `Minimize`, `Maximize` (default when unset), `Fill`, or
/// `None`.
/// The desktop app uses a web-based title-bar drag region, so the frontend
/// forwards double-clicks here and suppresses Tauri's injected drag-region
/// handler, whose default macOS path hardcodes maximize.
///
/// For `Fill`, resize to the current monitor work area instead of using
/// Tauri's maximize path, which maps to macOS zoom for titled, resizable
/// windows.
///
/// On non-macOS platforms this always toggles maximize (the historical
/// behavior).
#[tauri::command]
fn title_bar_double_click(window: tauri::Window) {
#[cfg(target_os = "macos")]
{
let action = {
let output = std::process::Command::new("defaults")
.args(["read", "-g", "AppleActionOnDoubleClick"])
.output();
match output {
Ok(output) if output.status.success() => {
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
_ => "Maximize".to_string(),
}
};
match action.as_str() {
"None" => {}
"Minimize" => {
let _ = window.minimize();
}
"Fill" => {
fill_window(&window);
}
// "Maximize" or any unexpected value.
_ => {
toggle_maximize(&window);
}
}
}
#[cfg(not(target_os = "macos"))]
{
toggle_maximize(&window);
}
}
/// Fills the current display work area, excluding system UI like the menu bar
/// and Dock.
#[cfg(target_os = "macos")]
fn fill_window(window: &tauri::Window) {
match window.current_monitor() {
Ok(Some(monitor)) => {
if window.is_maximized().unwrap_or(false) {
let _ = window.unmaximize();
}
let work_area = monitor.work_area();
let _ = window.set_position(work_area.position);
let _ = window.set_size(work_area.size);
}
_ => {
let _ = window.maximize();
}
}
}
/// Toggles the window between maximized and its previous size, matching the
/// historical double-click behavior.
fn toggle_maximize(window: &tauri::Window) {
match window.is_maximized() {
Ok(true) => {
let _ = window.unmaximize();
}
_ => {
let _ = window.maximize();
}
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let builder = tauri::Builder::default()
@@ -441,6 +526,7 @@ pub fn run() {
Ok(())
})
.invoke_handler(tauri::generate_handler![
title_bar_double_click,
get_identity,
get_nsec,
import_identity,

View File

@@ -11,10 +11,65 @@ export type AppView =
| "projects";
const WINDOW_DRAG_HANDLE_HEIGHT = 44;
const TAURI_DRAG_REGION_ATTR = "data-tauri-drag-region";
const WINDOW_DRAG_INTERACTIVE_SELECTOR =
'button, a, input, textarea, select, [role="button"], [contenteditable="true"]';
'button, a, input, textarea, select, label, summary, [role="button"], [role="link"], [role="menuitem"], [role="tab"], [role="checkbox"], [role="radio"], [role="switch"], [role="option"], [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
const CLICKABLE_TAGS = new Set([
"A",
"BUTTON",
"INPUT",
"SELECT",
"TEXTAREA",
"LABEL",
"SUMMARY",
]);
const INTERACTIVE_ROLES = new Set([
"button",
"link",
"menuitem",
"tab",
"checkbox",
"radio",
"switch",
"option",
]);
function isClickableElement(element: HTMLElement) {
return (
CLICKABLE_TAGS.has(element.tagName) ||
(element.hasAttribute("contenteditable") &&
element.getAttribute("contenteditable") !== "false") ||
(element.hasAttribute("tabindex") &&
element.getAttribute("tabindex") !== "-1") ||
INTERACTIVE_ROLES.has(element.getAttribute("role") ?? "")
);
}
function isTauriDragRegionEvent(event: MouseEvent | PointerEvent) {
const path = event.composedPath();
const directTarget = path[0];
for (const item of path) {
if (!(item instanceof HTMLElement)) continue;
const attr = item.getAttribute(TAURI_DRAG_REGION_ATTR);
if (isClickableElement(item) && attr === null) return false;
if (attr === null) continue;
if (attr === "false") return false;
if (attr === "deep") return true;
if (attr === "" || attr === "true") return item === directTarget;
}
return false;
}
export function isWindowDragHandleEvent(event: MouseEvent | PointerEvent) {
if (isTauriDragRegionEvent(event)) {
return true;
}
if (event.clientY > WINDOW_DRAG_HANDLE_HEIGHT) {
return false;
}

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { isWindowDragHandleEvent } from "@/app/AppShell.helpers";
import { performTitleBarDoubleClickAction } from "@/shared/lib/titleBarActions";
export function useTauriWindowDrag() {
React.useEffect(() => {
@@ -17,19 +18,35 @@ export function useTauriWindowDrag() {
void getCurrentWindow().startDragging();
}
function stopTauriDragRegionHandler(event: MouseEvent) {
if (event.button !== 0 || !isWindowDragHandleEvent(event)) {
return;
}
// Tauri's injected data-tauri-drag-region listener hardcodes maximize on
// double-click. Buzz handles drag and double-click itself so macOS title
// bar preferences can be respected.
event.stopImmediatePropagation();
}
function handleDoubleClick(event: MouseEvent) {
if (event.button !== 0 || !isWindowDragHandleEvent(event)) {
return;
}
event.preventDefault();
void getCurrentWindow().toggleMaximize();
event.stopImmediatePropagation();
void performTitleBarDoubleClickAction();
}
window.addEventListener("pointerdown", handlePointerDown, true);
window.addEventListener("mousedown", stopTauriDragRegionHandler, true);
window.addEventListener("mouseup", stopTauriDragRegionHandler, true);
window.addEventListener("dblclick", handleDoubleClick, true);
return () => {
window.removeEventListener("pointerdown", handlePointerDown, true);
window.removeEventListener("mousedown", stopTauriDragRegionHandler, true);
window.removeEventListener("mouseup", stopTauriDragRegionHandler, true);
window.removeEventListener("dblclick", handleDoubleClick, true);
};
}, []);

View File

@@ -0,0 +1,22 @@
import { invoke, isTauri } from "@tauri-apps/api/core";
/**
* Runs the window action that matches the OS "double-click a window's title
* bar to" preference (macOS `AppleActionOnDoubleClick`).
*
* The app uses a web-based title-bar drag region, so the OS does not act on a
* title-bar double-click on its own. The Rust `title_bar_double_click` command
* reads the preference and dispatches a single matching action (native
* `miniaturize:` for minimize, so the window genies into the Dock).
*/
export async function performTitleBarDoubleClickAction(): Promise<void> {
if (!isTauri()) {
return;
}
try {
await invoke("title_bar_double_click");
} catch {
// No-op on failure; a missed titlebar action is not worth surfacing.
}
}

View File

@@ -1,9 +1,11 @@
import { getCurrentWindow } from "@tauri-apps/api/window";
import * as React from "react";
import { performTitleBarDoubleClickAction } from "@/shared/lib/titleBarActions";
const WINDOW_DRAG_HANDLE_HEIGHT = 44;
const WINDOW_DRAG_INTERACTIVE_SELECTOR =
'button, a, input, textarea, select, [role="button"], [contenteditable="true"]';
'button, a, input, textarea, select, label, summary, [role="button"], [role="link"], [role="menuitem"], [role="tab"], [role="checkbox"], [role="radio"], [role="switch"], [role="option"], [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
function isWindowDragHandleEvent(event: MouseEvent | PointerEvent) {
if (event.clientY > WINDOW_DRAG_HANDLE_HEIGHT) {
@@ -31,19 +33,32 @@ export function StartupWindowDragRegion() {
void getCurrentWindow().startDragging();
}
function stopTauriDragRegionHandler(event: MouseEvent) {
if (event.button !== 0 || !isWindowDragHandleEvent(event)) {
return;
}
event.stopImmediatePropagation();
}
function handleDoubleClick(event: MouseEvent) {
if (event.button !== 0 || !isWindowDragHandleEvent(event)) {
return;
}
event.preventDefault();
void getCurrentWindow().toggleMaximize();
event.stopImmediatePropagation();
void performTitleBarDoubleClickAction();
}
window.addEventListener("pointerdown", handlePointerDown, true);
window.addEventListener("mousedown", stopTauriDragRegionHandler, true);
window.addEventListener("mouseup", stopTauriDragRegionHandler, true);
window.addEventListener("dblclick", handleDoubleClick, true);
return () => {
window.removeEventListener("pointerdown", handlePointerDown, true);
window.removeEventListener("mousedown", stopTauriDragRegionHandler, true);
window.removeEventListener("mouseup", stopTauriDragRegionHandler, true);
window.removeEventListener("dblclick", handleDoubleClick, true);
};
}, []);
@@ -52,7 +67,6 @@ export function StartupWindowDragRegion() {
<div
aria-hidden="true"
className="pointer-events-none fixed inset-x-0 top-0 z-20 h-10 select-none"
data-tauri-drag-region
/>
);
}