Skip to content

Commit f73c70f

Browse files
feat: add focusable property and Window::set_focusable (#1120)
* Added focusable functionality for windows * Fixed flag logic * linux * macos * change file * also apply on canBecomeMainWindow * add set_focusable * doc * fmt --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
1 parent 4658ebd commit f73c70f

File tree

9 files changed

+104
-9
lines changed

9 files changed

+104
-9
lines changed

.changes/focusable.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tao": patch
3+
---
4+
5+
Added `WindowBuilder::with_focusable` to allow creating unfocusable windows.

.changes/set-focusable.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tao": patch
3+
---
4+
5+
Added `Window::set_focusable`.

src/platform_impl/android/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,10 @@ impl Window {
564564
warn!("set_focus not yet implemented on Android");
565565
}
566566

567+
pub fn set_focusable(&self, _focusable: bool) {
568+
warn!("set_focusable not yet implemented on Android");
569+
}
570+
567571
pub fn is_focused(&self) -> bool {
568572
log::warn!("`Window::is_focused` is ignored on Android");
569573
false

src/platform_impl/ios/window.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ impl Inner {
7272
warn!("set_focus not yet implemented on iOS");
7373
}
7474

75+
pub fn set_focusable(&self, _focusable: bool) {
76+
warn!("set_focusable not yet implemented on iOS");
77+
}
78+
7579
pub fn is_focused(&self) -> bool {
7680
warn!("`Window::is_focused` is ignored on iOS");
7781
false

src/platform_impl/linux/window.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl Window {
8787

8888
let mut window_builder = gtk::ApplicationWindow::builder()
8989
.application(app)
90-
.accept_focus(attributes.focused);
90+
.accept_focus(attributes.focusable && attributes.focused);
9191
if let Parent::ChildOf(parent) = pl_attribs.parent {
9292
window_builder = window_builder.transient_for(&parent);
9393
}
@@ -231,8 +231,8 @@ impl Window {
231231
}
232232

233233
// restore accept-focus after the window has been drawn
234-
// if the window was initially created without focus
235-
if !attributes.focused {
234+
// if the window was initially created without focus and is supposed to be focusable
235+
if attributes.focusable && !attributes.focused {
236236
let signal_id = Arc::new(RefCell::new(None));
237237
let signal_id_ = signal_id.clone();
238238
let id = window.connect_draw(move |window, _| {
@@ -594,6 +594,10 @@ impl Window {
594594
}
595595
}
596596

597+
pub fn set_focusable(&self, focusable: bool) {
598+
self.window.set_accept_focus(focusable);
599+
}
600+
597601
pub fn is_focused(&self) -> bool {
598602
self.window.is_active()
599603
}

src/platform_impl/macos/window.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use core_graphics::{
4848
use objc2::{
4949
msg_send,
5050
rc::Retained,
51-
runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel},
51+
runtime::{AnyClass as Class, AnyObject as Object, Bool, ClassBuilder as ClassDecl, Sel},
5252
};
5353
use objc2_app_kit::{
5454
self as appkit, NSApp, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSEvent,
@@ -242,16 +242,21 @@ fn create_window(
242242
masks |= NSWindowStyleMask::FullSizeContentView;
243243
}
244244

245-
let ns_window = msg_send![WINDOW_CLASS.0, alloc];
246-
let ns_window: Option<Retained<NSWindow>> = msg_send![
245+
let ns_window: id = msg_send![WINDOW_CLASS.0, alloc];
246+
let ns_window_ptr: id = msg_send![
247247
ns_window,
248248
initWithContentRect: frame,
249249
styleMask: masks,
250250
backing: NSBackingStoreType::Buffered,
251251
defer: NO,
252252
];
253253

254-
ns_window.map(|ns_window| {
254+
Retained::retain(ns_window_ptr).and_then(|r| r.downcast::<NSWindow>().ok()).map(|ns_window| {
255+
#[allow(deprecated)]
256+
{
257+
*((*ns_window_ptr).get_mut_ivar::<Bool>("focusable")) = attrs.focusable.into();
258+
}
259+
255260
let title = NSString::from_str(&attrs.title);
256261
ns_window.setReleasedWhenClosed(false);
257262
ns_window.setTitle(&title);
@@ -409,17 +414,26 @@ lazy_static! {
409414
.unwrap();
410415
decl.add_method(
411416
sel!(canBecomeMainWindow),
412-
util::yes as extern "C" fn(_, _) -> _,
417+
is_focusable as extern "C" fn(_, _) -> _,
413418
);
414419
decl.add_method(
415420
sel!(canBecomeKeyWindow),
416-
util::yes as extern "C" fn(_, _) -> _,
421+
is_focusable as extern "C" fn(_, _) -> _,
417422
);
418423
decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _));
424+
// progress bar states, follows ProgressState
425+
decl.add_ivar::<Bool>(CStr::from_bytes_with_nul(b"focusable\0").unwrap());
419426
WindowClass(decl.register())
420427
};
421428
}
422429

430+
extern "C" fn is_focusable(this: &Object, _: Sel) -> Bool {
431+
#[allow(deprecated)] // TODO: Use define_class!
432+
unsafe {
433+
*(this.get_ivar("focusable"))
434+
}
435+
}
436+
423437
extern "C" fn send_event(this: &Object, _sel: Sel, event: &NSEvent) {
424438
unsafe {
425439
let event_type = event.r#type();
@@ -666,6 +680,16 @@ impl UnownedWindow {
666680
}
667681
}
668682

683+
#[inline]
684+
pub fn set_focusable(&self, focusable: bool) {
685+
#[allow(deprecated)] // TODO: Use define_class!
686+
unsafe {
687+
let ns_window =
688+
Retained::into_raw(Retained::cast_unchecked::<Object>(self.ns_window.clone()));
689+
*((*ns_window).get_mut_ivar::<Bool>("focusable")) = focusable.into();
690+
}
691+
}
692+
669693
#[inline]
670694
pub fn is_focused(&self) -> bool {
671695
unsafe { self.ns_window.isKeyWindow() }

src/platform_impl/windows/window.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ impl Window {
185185
}
186186
}
187187

188+
#[inline]
189+
pub fn set_focusable(&self, focusable: bool) {
190+
let window = self.window.0 .0 as isize;
191+
let window_state = Arc::clone(&self.window_state);
192+
self.thread_executor.execute_in_thread(move || {
193+
WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| {
194+
f.set(WindowFlags::FOCUSABLE, focusable)
195+
});
196+
});
197+
}
198+
188199
#[inline]
189200
pub fn is_focused(&self) -> bool {
190201
let window_state = self.window_state.lock();
@@ -1131,6 +1142,8 @@ unsafe fn init<T: 'static>(
11311142
// but we need to have a default for the diffing to work
11321143
window_flags.set(WindowFlags::CLOSABLE, true);
11331144

1145+
window_flags.set(WindowFlags::FOCUSABLE, attributes.focusable);
1146+
11341147
window_flags.set(WindowFlags::MARKER_DONT_FOCUS, !attributes.focused);
11351148

11361149
window_flags.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, pl_attribs.rtl);

src/platform_impl/windows/window_state.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ bitflags! {
117117

118118
const RIGHT_TO_LEFT_LAYOUT = 1 << 22;
119119

120+
const FOCUSABLE = 1 << 23;
121+
120122
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits();
121123
}
122124
}
@@ -291,6 +293,9 @@ impl WindowFlags {
291293
if self.contains(WindowFlags::RIGHT_TO_LEFT_LAYOUT) {
292294
style_ex |= WS_EX_LAYOUTRTL | WS_EX_RTLREADING | WS_EX_RIGHT;
293295
}
296+
if !self.contains(WindowFlags::FOCUSABLE) {
297+
style_ex |= WS_EX_NOACTIVATE;
298+
}
294299

295300
(style, style_ex)
296301
}

src/window.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ pub struct WindowAttributes {
249249
/// **Android / iOS:** Unsupported.
250250
pub focused: bool,
251251

252+
/// Whether the window should be focusable or not.
253+
///
254+
/// ## Platform-specific:
255+
///
256+
/// **Android / iOS:** Unsupported.
257+
pub focusable: bool,
258+
252259
/// Prevents the window contents from being captured by other apps.
253260
///
254261
/// ## Platform-specific
@@ -294,6 +301,7 @@ impl Default for WindowAttributes {
294301
window_icon: None,
295302
preferred_theme: None,
296303
focused: true,
304+
focusable: true,
297305
content_protection: false,
298306
visible_on_all_workspaces: false,
299307
background_color: None,
@@ -538,6 +546,17 @@ impl WindowBuilder {
538546
self.window.focused = focused;
539547
self
540548
}
549+
550+
/// Whether the window will be focusable or not.
551+
///
552+
/// ## Platform-specific:
553+
/// **Android / iOS:** Unsupported.
554+
#[inline]
555+
pub fn with_focusable(mut self, focusable: bool) -> WindowBuilder {
556+
self.window.focusable = focusable;
557+
self
558+
}
559+
541560
/// Prevents the window contents from being captured by other apps.
542561
///
543562
/// ## Platform-specific
@@ -825,6 +844,18 @@ impl Window {
825844
self.window.set_focus()
826845
}
827846

847+
/// Sets whether the window is focusable or not.
848+
///
849+
/// ## Platform-specific
850+
///
851+
/// - **macOS**: If the window is already focused, it is not possible to unfocus it after calling `set_focusable(false)`.
852+
/// In this case, you might consider calling [`Window::set_focus`] but it will move the window to the back i.e. at the bottom in terms of z-order.
853+
/// - **iOS / Android:** Unsupported.
854+
#[inline]
855+
pub fn set_focusable(&self, focusable: bool) {
856+
self.window.set_focusable(focusable)
857+
}
858+
828859
/// Is window active and focused?
829860
///
830861
/// ## Platform-specific

0 commit comments

Comments
 (0)