Skip to content

Commit 4137ab4

Browse files
caesarlucasfernog
andauthored
feat(macos): add tabbing_identifier option, closes #2804, #3912 (#5399)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
1 parent bddf59e commit 4137ab4

File tree

13 files changed

+180
-75
lines changed

13 files changed

+180
-75
lines changed

.changes/automatic-tabbing.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri": minor
3+
"tauri-runtime-wry": minor
4+
---
5+
6+
Disable automatic window tabbing on macOS when the `tabbing_identifier` option is not defined, the window is transparent or does not have decorations.

.changes/tabbing-identifier-api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"api": minor
3+
---
4+
5+
Added `tabbingIdentifier` window option for macOS.

.changes/tabbing-identifier.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"tauri": minor
3+
"tauri-runtime": minor
4+
"tauri-runtime-wry": minor
5+
"api": minor
6+
---
7+
8+
Added `tabbing_identifier` to the window builder on macOS.

core/tauri-runtime-wry/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,8 @@ impl From<CursorIcon> for CursorIconWrapper {
680680
pub struct WindowBuilderWrapper {
681681
inner: WryWindowBuilder,
682682
center: bool,
683+
#[cfg(target_os = "macos")]
684+
tabbing_identifier: Option<String>,
683685
menu: Option<Menu>,
684686
}
685687

@@ -711,6 +713,9 @@ impl WindowBuilder for WindowBuilderWrapper {
711713
window = window
712714
.hidden_title(config.hidden_title)
713715
.title_bar_style(config.title_bar_style);
716+
if let Some(identifier) = &config.tabbing_identifier {
717+
window = window.tabbing_identifier(identifier);
718+
}
714719
}
715720

716721
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
@@ -878,6 +883,13 @@ impl WindowBuilder for WindowBuilderWrapper {
878883
self
879884
}
880885

886+
#[cfg(target_os = "macos")]
887+
fn tabbing_identifier(mut self, identifier: &str) -> Self {
888+
self.inner = self.inner.with_tabbing_identifier(identifier);
889+
self.tabbing_identifier.replace(identifier.into());
890+
self
891+
}
892+
881893
fn icon(mut self, icon: Icon) -> Result<Self> {
882894
self.inner = self
883895
.inner
@@ -2930,6 +2942,16 @@ fn create_webview<T: UserEvent>(
29302942
.with_drag_and_drop(webview_attributes.file_drop_handler_enabled);
29312943
}
29322944

2945+
#[cfg(target_os = "macos")]
2946+
{
2947+
if window_builder.tabbing_identifier.is_none()
2948+
|| window_builder.inner.window.transparent
2949+
|| !window_builder.inner.window.decorations
2950+
{
2951+
window_builder.inner = window_builder.inner.with_automatic_window_tabbing(false);
2952+
}
2953+
}
2954+
29332955
let is_window_transparent = window_builder.inner.window.transparent;
29342956
let menu_items = if let Some(menu) = window_builder.menu {
29352957
let mut menu_items = HashMap::new();

core/tauri-runtime/src/webview.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ pub trait WindowBuilder: WindowBuilderBase {
219219
#[must_use]
220220
fn hidden_title(self, hidden: bool) -> Self;
221221

222+
/// Defines the window [tabbing identifier] for macOS.
223+
///
224+
/// Windows with matching tabbing identifiers will be grouped together.
225+
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
226+
///
227+
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
228+
#[cfg(target_os = "macos")]
229+
#[must_use]
230+
fn tabbing_identifier(self, identifier: &str) -> Self;
231+
222232
/// Forces a theme or uses the system settings if None was provided.
223233
fn theme(self, theme: Option<Theme>) -> Self;
224234

core/tauri-utils/src/config.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,14 @@ pub struct WindowConfig {
873873
/// Whether clicking an inactive window also clicks through to the webview.
874874
#[serde(default, alias = "accept-first-mouse")]
875875
pub accept_first_mouse: bool,
876+
/// Defines the window [tabbing identifier] for macOS.
877+
///
878+
/// Windows with matching tabbing identifiers will be grouped together.
879+
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
880+
///
881+
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
882+
#[serde(default, alias = "tabbing-identifier")]
883+
pub tabbing_identifier: Option<String>,
876884
}
877885

878886
impl Default for WindowConfig {
@@ -905,6 +913,7 @@ impl Default for WindowConfig {
905913
title_bar_style: Default::default(),
906914
hidden_title: false,
907915
accept_first_mouse: false,
916+
tabbing_identifier: None,
908917
}
909918
}
910919
}
@@ -3037,6 +3046,7 @@ mod build {
30373046
let title_bar_style = &self.title_bar_style;
30383047
let hidden_title = self.hidden_title;
30393048
let accept_first_mouse = self.accept_first_mouse;
3049+
let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
30403050

30413051
literal_struct!(
30423052
tokens,
@@ -3067,7 +3077,8 @@ mod build {
30673077
theme,
30683078
title_bar_style,
30693079
hidden_title,
3070-
accept_first_mouse
3080+
accept_first_mouse,
3081+
tabbing_identifier
30713082
);
30723083
}
30733084
}

core/tauri/src/test/mock_runtime.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@ impl WindowBuilder for MockWindowBuilder {
283283
self
284284
}
285285

286+
#[cfg(target_os = "macos")]
287+
fn tabbing_identifier(self, identifier: &str) -> Self {
288+
self
289+
}
290+
286291
fn theme(self, theme: Option<Theme>) -> Self {
287292
self
288293
}

core/tauri/src/window.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,19 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
462462
self
463463
}
464464

465+
/// Defines the window [tabbing identifier] for macOS.
466+
///
467+
/// Windows with matching tabbing identifiers will be grouped together.
468+
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
469+
///
470+
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
471+
#[cfg(target_os = "macos")]
472+
#[must_use]
473+
pub fn tabbing_identifier(mut self, identifier: &str) -> Self {
474+
self.window_builder = self.window_builder.tabbing_identifier(identifier);
475+
self
476+
}
477+
465478
// ------------------------------------------- Webview attributes -------------------------------------------
466479

467480
/// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,

examples/multiwindow/index.html

Lines changed: 75 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,89 @@
11
<!DOCTYPE html>
22
<html>
3-
<head>
4-
<style>
5-
#response {
6-
white-space: pre-wrap;
7-
}
8-
</style>
9-
</head>
103

11-
<body>
12-
<div id="window-label"></div>
13-
<div id="container"></div>
14-
<div id="response"></div>
4+
<head>
5+
<style>
6+
#response {
7+
white-space: pre-wrap;
8+
}
9+
</style>
10+
</head>
1511

16-
<script>
17-
var WebviewWindow = window.__TAURI__.window.WebviewWindow
18-
var appWindow = window.__TAURI__.window.appWindow
19-
var windowLabel = appWindow.label
20-
var windowLabelContainer = document.getElementById('window-label')
21-
windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
12+
<body>
13+
<div id="window-label"></div>
14+
<div id="container"></div>
15+
<div id="response"></div>
2216

23-
var container = document.getElementById('container')
17+
<script>
18+
var WebviewWindow = window.__TAURI__.window.WebviewWindow
19+
var appWindow = window.__TAURI__.window.appWindow
20+
var windowLabel = appWindow.label
21+
var windowLabelContainer = document.getElementById('window-label')
22+
windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
2423

25-
function createWindowMessageBtn(label) {
26-
var tauriWindow = WebviewWindow.getByLabel(label)
27-
var button = document.createElement('button')
28-
button.innerText = 'Send message to ' + label
29-
button.addEventListener('click', function () {
30-
tauriWindow.emit('clicked', 'message from ' + windowLabel)
31-
})
32-
container.appendChild(button)
33-
}
24+
var container = document.getElementById('container')
3425

35-
// global listener
36-
window.__TAURI__.event.listen('clicked', function (event) {
37-
responseContainer.innerHTML +=
38-
'Got ' + JSON.stringify(event) + ' on global listener\n\n'
39-
})
40-
window.__TAURI__.event.listen('tauri://window-created', function (event) {
41-
createWindowMessageBtn(event.payload.label)
26+
function createWindowMessageBtn(label) {
27+
var tauriWindow = WebviewWindow.getByLabel(label)
28+
var button = document.createElement('button')
29+
button.innerText = 'Send message to ' + label
30+
button.addEventListener('click', function () {
31+
tauriWindow.emit('clicked', 'message from ' + windowLabel)
4232
})
33+
container.appendChild(button)
34+
}
4335

44-
var responseContainer = document.getElementById('response')
45-
// listener tied to this window
46-
appWindow.listen('clicked', function (event) {
47-
responseContainer.innerText +=
48-
'Got ' + JSON.stringify(event) + ' on window listener\n\n'
49-
})
36+
// global listener
37+
window.__TAURI__.event.listen('clicked', function (event) {
38+
responseContainer.innerHTML +=
39+
'Got ' + JSON.stringify(event) + ' on global listener\n\n'
40+
})
41+
window.__TAURI__.event.listen('tauri://window-created', function (event) {
42+
createWindowMessageBtn(event.payload.label)
43+
})
5044

51-
var createWindowButton = document.createElement('button')
52-
createWindowButton.innerHTML = 'Create window'
53-
createWindowButton.addEventListener('click', function () {
54-
var webviewWindow = new WebviewWindow(
55-
Math.random().toString().replace('.', '')
56-
)
57-
webviewWindow.once('tauri://created', function () {
58-
responseContainer.innerHTML += 'Created new webview'
59-
})
60-
webviewWindow.once('tauri://error', function (e) {
61-
responseContainer.innerHTML += 'Error creating new webview'
62-
})
63-
})
64-
container.appendChild(createWindowButton)
45+
var responseContainer = document.getElementById('response')
46+
// listener tied to this window
47+
appWindow.listen('clicked', function (event) {
48+
responseContainer.innerText +=
49+
'Got ' + JSON.stringify(event) + ' on window listener\n\n'
50+
})
6551

66-
var globalMessageButton = document.createElement('button')
67-
globalMessageButton.innerHTML = 'Send global message'
68-
globalMessageButton.addEventListener('click', function () {
69-
// emit to all windows
70-
window.__TAURI__.event.emit('clicked', 'message from ' + windowLabel)
52+
var createWindowButton = document.createElement('button')
53+
createWindowButton.innerHTML = 'Create window'
54+
createWindowButton.addEventListener('click', function () {
55+
var webviewWindow = new WebviewWindow(
56+
Math.random().toString().replace('.', ''),
57+
{
58+
tabbingIdentifier: windowLabel
59+
}
60+
)
61+
webviewWindow.once('tauri://created', function () {
62+
responseContainer.innerHTML += 'Created new webview'
63+
})
64+
webviewWindow.once('tauri://error', function (e) {
65+
responseContainer.innerHTML += 'Error creating new webview'
7166
})
72-
container.appendChild(globalMessageButton)
67+
})
68+
container.appendChild(createWindowButton)
7369

74-
var allWindows = window.__TAURI__.window.getAll()
75-
for (var index in allWindows) {
76-
var label = allWindows[index].label
77-
if (label === windowLabel) {
78-
continue
79-
}
80-
createWindowMessageBtn(label)
70+
var globalMessageButton = document.createElement('button')
71+
globalMessageButton.innerHTML = 'Send global message'
72+
globalMessageButton.addEventListener('click', function () {
73+
// emit to all windows
74+
window.__TAURI__.event.emit('clicked', 'message from ' + windowLabel)
75+
})
76+
container.appendChild(globalMessageButton)
77+
78+
var allWindows = window.__TAURI__.window.getAll()
79+
for (var index in allWindows) {
80+
var label = allWindows[index].label
81+
if (label === windowLabel) {
82+
continue
8183
}
82-
</script>
83-
</body>
84-
</html>
84+
createWindowMessageBtn(label)
85+
}
86+
</script>
87+
</body>
88+
89+
</html>

examples/multiwindow/main.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ fn main() {
1818
});
1919
})
2020
.setup(|app| {
21-
WindowBuilder::new(
21+
#[allow(unused_mut)]
22+
let mut builder = WindowBuilder::new(
2223
app,
2324
"Rust".to_string(),
2425
tauri::WindowUrl::App("index.html".into()),
25-
)
26-
.title("Tauri - Rust")
27-
.build()?;
26+
);
27+
#[cfg(target_os = "macos")]
28+
{
29+
builder = builder.tabbing_identifier("Rust");
30+
}
31+
let _window = builder.title("Tauri - Rust").build()?;
2832
Ok(())
2933
})
3034
.run(tauri::generate_context!(

0 commit comments

Comments
 (0)