Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/base64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-macos-sign": patch:enhance
---

Do not rely on system base64 CLI to decode certificates.
5 changes: 5 additions & 0 deletions .changes/data-tauri-drag-region-deep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": minor:feat
---

Add `data-tauri-drag-region="deep"` so clicks on non-clickable children will drag as well. Can still opt out of drag on some regions using `data-tauri-drag-region="false"`
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/tauri-macos-sign/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ log = { version = "0.4.21", features = ["kv"] }
apple-codesign = { version = "0.27", default-features = false }
chrono = "0.4"
p12 = "0.6"
base64 = "0.22"
43 changes: 16 additions & 27 deletions crates/tauri-macos-sign/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ pub enum Error {
},
#[error("failed to encode DER: {error}")]
FailedToEncodeDER { error: std::io::Error },
#[error("failed to decode base64 certificate: {0}")]
Base64Decode(base64::DecodeError),
#[error("certificate missing common name")]
CertificateMissingCommonName,
#[error("certificate missing organization unit for common name {common_name}")]
Expand Down Expand Up @@ -329,36 +331,23 @@ impl NotarytoolCmdExt for Command {
}
}

fn decode_base64(base64: &OsStr, out_path: &Path) -> Result<()> {
let tmp_dir = tempfile::tempdir().map_err(Error::TempDir)?;
fn decode_base64(base64_input: &OsStr, out_path: &Path) -> Result<()> {
use base64::Engine;

let src_path = tmp_dir.path().join("src");
let base64 = base64
let input = base64_input
.to_str()
.expect("failed to convert base64 to string")
.as_bytes();

// as base64 contain whitespace decoding may be broken
// https://github.com/marshallpierce/rust-base64/issues/105
// we'll use builtin base64 command from the OS
std::fs::write(&src_path, base64).map_err(|error| Error::Fs {
context: "failed to write base64 to temp file",
path: src_path.clone(),
error,
})?;
.expect("failed to convert base64 to string");

assert_command(
std::process::Command::new("base64")
.arg("--decode")
.arg("-i")
.arg(&src_path)
.arg("-o")
.arg(out_path)
.piped(),
"failed to decode certificate",
)
.map_err(|error| Error::CommandFailed {
command: "base64 --decode".to_string(),
// strip whitespace before decoding
let cleaned: String = input.chars().filter(|c| !c.is_ascii_whitespace()).collect();

let decoded = base64::engine::general_purpose::STANDARD
.decode(&cleaned)
.map_err(Error::Base64Decode)?;

std::fs::write(out_path, &decoded).map_err(|error| Error::Fs {
context: "failed to write decoded certificate",
path: out_path.to_path_buf(),
error,
})?;

Expand Down
80 changes: 63 additions & 17 deletions crates/tauri/src/window/scripts/drag.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,78 @@
// SPDX-License-Identifier: MIT

;(function () {
const osName = __TEMPLATE_os_name__

//-----------------------//
// drag on mousedown and maximize on double click on Windows and Linux
// while macOS macos maximization should be on mouseup and if the mouse
// while macOS maximization should be on mouseup and if the mouse
// moves after the double click, it should be cancelled (see https://github.com/tauri-apps/tauri/issues/8306)
//-----------------------//
const TAURI_DRAG_REGION_ATTR = 'data-tauri-drag-region'

function isClickableElement(el) {
const tag = el.tagName && el.tagName.toLowerCase()

return (
tag === 'a'
|| tag === 'button'
|| tag === 'input'
|| tag === 'select'
|| tag === 'textarea'
|| tag === 'label'
|| tag === 'summary'
|| (el.hasAttribute('contenteditable')
&& el.getAttribute('contenteditable') !== 'false')
|| (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1')
)
}

// Walk the composed path from target upward. If a clickable element or a
// data-tauri-drag-region="false" element is encountered, return false (don't drag).
// Otherwise return true.
//
// Supported values for data-tauri-drag-region:
// (bare / no value) → self: only direct clicks on this element trigger drag
// "deep" → deep: clicks anywhere in the subtree trigger drag
// "false" → disabled: drag is blocked here (and for ancestors)
function isDragRegion(composedPath) {
for (const el of composedPath) {
if (!(el instanceof HTMLElement)) continue

// if we hit a clickable element or a disabled drag region, don't drag
if (
isClickableElement(el)
|| el.getAttribute(TAURI_DRAG_REGION_ATTR) === 'false'
) {
return false
}

const attr = el.getAttribute(TAURI_DRAG_REGION_ATTR)
if (attr !== null) {
// deep: the whole subtree is a drag region
if (attr === 'deep') return true
// bare (or any unrecognized value): self-only
if (el === composedPath[0]) return true
// click was on a child of a self-only region — stop walking, don't drag
return false
}
}

return false
}

const osName = __TEMPLATE_os_name__

// initial mousedown position for macOS
let initialX = 0
let initialY = 0

document.addEventListener('mousedown', (e) => {
const attr = e.target.getAttribute(TAURI_DRAG_REGION_ATTR)
if (
// element has the magic data attribute
attr !== null
// and not false
&& attr !== 'false'
// and was left mouse button
&& e.button === 0
// was left mouse button
e.button === 0
// and was normal click to drag or double click to maximize
&& (e.detail === 1 || e.detail === 2)
// and is drag region
&& isDragRegion(e.composedPath())
) {
// macOS maximization happens on `mouseup`,
// so we save needed state and early return
Expand All @@ -48,23 +96,21 @@
window.__TAURI_INTERNALS__.invoke('plugin:window|' + cmd)
}
})

// on macOS we maximize on mouseup instead, to match the system behavior where maximization can be canceled
// if the mouse moves outside the data-tauri-drag-region
if (osName === 'macos') {
document.addEventListener('mouseup', (e) => {
const attr = e.target.getAttribute(TAURI_DRAG_REGION_ATTR)
if (
// element has the magic data attribute
attr !== null
// and not false
&& attr !== 'false'
// and was left mouse button
&& e.button === 0
// was left mouse button
e.button === 0
// and was double click
&& e.detail === 2
// and the cursor hasn't moved from initial mousedown
&& e.clientX === initialX
&& e.clientY === initialY
// and the event path contains a drag region (with no clickable element in between)
&& isDragRegion(e.composedPath())
) {
window.__TAURI_INTERNALS__.invoke(
'plugin:window|internal_toggle_maximize'
Expand Down
Loading