Skip to content

Commit 085ae2a

Browse files
authored
add support for Window::Focused/Unfocused events on macOS (#171)
trigger `WindowEvent::Focused` and `WindowEvent::Unfocused` events when the plugin window gains/loses focus. implemented by adding observers to `NSNotificationCenter::defaultCenter()` that listen to `NSWindowDidBecomeKeyNotification` and `NSWindowDidResignKeyNotification` notifications on the `NSViews`' window. tested and confirmed to work in Live, Bitwig, FL Studio, Reaper and AudioPluginHost.
1 parent fdc5d28 commit 085ae2a

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

src/macos/view.rs

+79
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ use super::{
2929
/// Name of the field used to store the `WindowState` pointer.
3030
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
3131

32+
#[link(name = "AppKit", kind = "framework")]
33+
extern "C" {
34+
static NSWindowDidBecomeKeyNotification: id;
35+
static NSWindowDidResignKeyNotification: id;
36+
}
37+
3238
macro_rules! add_simple_mouse_class_method {
3339
($class:ident, $sel:ident, $event:expr) => {
3440
#[allow(non_snake_case)]
@@ -94,6 +100,18 @@ macro_rules! add_simple_keyboard_class_method {
94100
};
95101
}
96102

103+
unsafe fn register_notification(observer: id, notification_name: id, object: id) {
104+
let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter];
105+
106+
let _: () = msg_send![
107+
notification_center,
108+
addObserver:observer
109+
selector:sel!(handleNotification:)
110+
name:notification_name
111+
object:object
112+
];
113+
}
114+
97115
pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
98116
let class = create_view_class();
99117

@@ -103,6 +121,9 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
103121

104122
view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height)));
105123

124+
register_notification(view, NSWindowDidBecomeKeyNotification, nil);
125+
register_notification(view, NSWindowDidResignKeyNotification, nil);
126+
106127
let _: id = msg_send![
107128
view,
108129
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
@@ -124,6 +145,14 @@ unsafe fn create_view_class() -> &'static Class {
124145
sel!(acceptsFirstResponder),
125146
property_yes as extern "C" fn(&Object, Sel) -> BOOL,
126147
);
148+
class.add_method(
149+
sel!(becomeFirstResponder),
150+
become_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
151+
);
152+
class.add_method(
153+
sel!(resignFirstResponder),
154+
resign_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
155+
);
127156
class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL);
128157
class.add_method(
129158
sel!(preservesContentInLiveResize),
@@ -177,6 +206,10 @@ unsafe fn create_view_class() -> &'static Class {
177206
dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger,
178207
);
179208
class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id));
209+
class.add_method(
210+
sel!(handleNotification:),
211+
handle_notification as extern "C" fn(&Object, Sel, id),
212+
);
180213

181214
add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left);
182215
add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left);
@@ -208,6 +241,25 @@ extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL
208241
YES
209242
}
210243

244+
extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL {
245+
let state = unsafe { WindowState::from_view(this) };
246+
let is_key_window = unsafe {
247+
let window: id = msg_send![this, window];
248+
let is_key_window: BOOL = msg_send![window, isKeyWindow];
249+
is_key_window == YES
250+
};
251+
if is_key_window {
252+
state.trigger_event(Event::Window(WindowEvent::Focused));
253+
}
254+
YES
255+
}
256+
257+
extern "C" fn resign_first_responder(this: &Object, _sel: Sel) -> BOOL {
258+
let state = unsafe { WindowState::from_view(this) };
259+
state.trigger_event(Event::Window(WindowEvent::Unfocused));
260+
YES
261+
}
262+
211263
extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL {
212264
let state = unsafe { WindowState::from_view(this) };
213265

@@ -473,3 +525,30 @@ extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) {
473525

474526
on_event(&state, MouseEvent::DragLeft);
475527
}
528+
529+
extern "C" fn handle_notification(this: &Object, _cmd: Sel, notification: id) {
530+
unsafe {
531+
let state = WindowState::from_view(this);
532+
533+
// The subject of the notication, in this case an NSWindow object.
534+
let notification_object: id = msg_send![notification, object];
535+
536+
// The NSWindow object associated with our NSView.
537+
let window: id = msg_send![this, window];
538+
539+
let first_responder: id = msg_send![window, firstResponder];
540+
541+
// Only trigger focus events if the NSWindow that's being notified about is our window,
542+
// and if the window's first responder is our NSView.
543+
// If the first responder isn't our NSView, the focus events will instead be triggered
544+
// by the becomeFirstResponder and resignFirstResponder methods on the NSView itself.
545+
if notification_object == window && first_responder == this as *const Object as id {
546+
let is_key_window: BOOL = msg_send![window, isKeyWindow];
547+
state.trigger_event(Event::Window(if is_key_window == YES {
548+
WindowEvent::Focused
549+
} else {
550+
WindowEvent::Unfocused
551+
}));
552+
}
553+
}
554+
}

src/macos/window.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ use core_foundation::runloop::{
1313
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
1414
};
1515
use keyboard_types::KeyboardEvent;
16-
16+
use objc::class;
1717
use objc::{msg_send, runtime::Object, sel, sel_impl};
18-
1918
use raw_window_handle::{
2019
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
2120
RawDisplayHandle, RawWindowHandle,
@@ -83,6 +82,11 @@ impl WindowInner {
8382
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
8483
}
8584

85+
// Deregister NSView from NotificationCenter.
86+
let notification_center: id =
87+
msg_send![class!(NSNotificationCenter), defaultCenter];
88+
let () = msg_send![notification_center, removeObserver:self.ns_view];
89+
8690
drop(window_state);
8791

8892
// Close the window if in non-parented mode

0 commit comments

Comments
 (0)