Skip to content
Open
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
32 changes: 26 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ authors = [
"Robbert van der Helm <mail@robbertvanderhelm.nl>",
"Adrien Prokopowicz <prokopylmc@gmail.com>"
]
edition = "2018"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Low-level windowing system geared towards making audio plugin UIs."
keywords = ["windowing", "audio", "plugin"]
Expand All @@ -23,7 +23,14 @@ exclude = [".github"]

[features]
default = []
opengl = ["uuid", "x11/glx"]
opengl = [
"uuid",
"x11/glx",
"objc2-core-foundation/CFBundle",
"objc2-app-kit/NSOpenGL",
"objc2-app-kit/NSOpenGLView",
"objc2-app-kit/objc2-open-gl"
]

[dependencies]
keyboard-types = { version = "0.6.1", default-features = false }
Expand All @@ -39,10 +46,23 @@ winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "
uuid = { version = "0.8", features = ["v4"], optional = true }

[target.'cfg(target_os="macos")'.dependencies]
cocoa = "0.24.0"
core-foundation = "0.9.1"
objc = "0.2.7"
uuid = { version = "0.8", features = ["v4"] }
uuid = { version = "1.23.1", features = ["v4"] }
objc2 = "0.6.4"
objc2-core-foundation = { version = "0.3.2", default-features = false, features = ["std", "CFString"] }
objc2-foundation = { version = "0.3.2", default-features = false, features = ["std", "NSEnumerator"] }
objc2-app-kit = { version = "0.3.2", default-features = false, features = [
"NSApplication",
"NSDragging",
"NSEvent",
"NSGraphics",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSTrackingArea",
"NSView",
"NSWindow",
"objc2-core-foundation"
] }

[dev-dependencies]
rtrb = "0.2"
Expand Down
113 changes: 46 additions & 67 deletions src/gl/macos.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
use std::ffi::c_void;
use std::str::FromStr;

use raw_window_handle::RawWindowHandle;
#![allow(deprecated)] // OpenGL is deprecated on macOS

use cocoa::appkit::{
use super::{GlConfig, GlError, Profile};
use objc2::rc::Retained;
use objc2::AllocAnyThread;
use objc2::{MainThreadMarker, MainThreadOnly};
use objc2_app_kit::{
NSOpenGLContext, NSOpenGLContextParameter, NSOpenGLPFAAccelerated, NSOpenGLPFAAlphaSize,
NSOpenGLPFAColorSize, NSOpenGLPFADepthSize, NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample,
NSOpenGLPFAOpenGLProfile, NSOpenGLPFASampleBuffers, NSOpenGLPFASamples, NSOpenGLPFAStencilSize,
NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core,
NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView,
};
use cocoa::base::{id, nil, YES};
use cocoa::foundation::NSSize;

use core_foundation::base::TCFType;
use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName};
use core_foundation::string::CFString;

use objc::{msg_send, sel, sel_impl};

use super::{GlConfig, GlError, Profile};
use objc2_core_foundation::{CFBundle, CFString};
use objc2_foundation::NSSize;
use raw_window_handle::RawWindowHandle;
use std::ffi::c_void;
use std::ptr::NonNull;

pub type CreationFailedError = ();
pub struct GlContext {
view: id,
context: id,
view: Retained<NSOpenGLView>,
context: Retained<NSOpenGLContext>,
}

impl GlContext {
Expand All @@ -35,11 +31,10 @@ impl GlContext {
return Err(GlError::InvalidWindowHandle);
};

if handle.ns_view.is_null() {
let parent_view = handle.ns_view.cast::<NSView>();
let Some(parent_view) = parent_view.as_ref() else {
return Err(GlError::InvalidWindowHandle);
}

let parent_view = handle.ns_view as id;
};

let version = if config.version < (3, 2) && config.profile == Profile::Compatibility {
NSOpenGLProfileVersionLegacy
Expand Down Expand Up @@ -76,33 +71,29 @@ impl GlContext {

attrs.push(0);

let pixel_format = NSOpenGLPixelFormat::alloc(nil).initWithAttributes_(&attrs);

if pixel_format == nil {
return Err(GlError::CreationFailed(()));
}

let view =
NSOpenGLView::alloc(nil).initWithFrame_pixelFormat_(parent_view.frame(), pixel_format);
let pixel_format = NSOpenGLPixelFormat::initWithAttributes(
NSOpenGLPixelFormat::alloc(),
NonNull::new(attrs.as_mut_ptr()).unwrap(),
)
.ok_or(GlError::CreationFailed(()))?;

if view == nil {
return Err(GlError::CreationFailed(()));
}
let view = NSOpenGLView::initWithFrame_pixelFormat(
NSOpenGLView::alloc(MainThreadMarker::new().unwrap()),
parent_view.frame(),
Some(&pixel_format),
)
.ok_or(GlError::CreationFailed(()))?;

view.setWantsBestResolutionOpenGLSurface_(YES);
view.setWantsBestResolutionOpenGLSurface(true);

NSOpenGLView::display_(view);
parent_view.addSubview_(view);
view.display();
parent_view.addSubview(&view);

let context: id = msg_send![view, openGLContext];
let () = msg_send![context, retain];
let context = view.openGLContext().ok_or(GlError::CreationFailed(()))?;

context.setValues_forParameter_(
&(config.vsync as i32),
NSOpenGLContextParameter::NSOpenGLCPSwapInterval,
);
let value = config.vsync as i32;

let () = msg_send![pixel_format, release];
context.setValues_forParameter((&value).into(), NSOpenGLContextParameter::SwapInterval);

Ok(GlContext { view, context })
}
Expand All @@ -112,47 +103,35 @@ impl GlContext {
}

pub unsafe fn make_not_current(&self) {
NSOpenGLContext::clearCurrentContext(self.context);
if Some(&self.context) == NSOpenGLContext::currentContext().as_ref() {
NSOpenGLContext::clearCurrentContext();
}
}

pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
let symbol_name = CFString::from_str(symbol).unwrap();
let framework_name = CFString::from_str("com.apple.opengl").unwrap();
let framework =
unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) };
let symbol_name = CFString::from_str(symbol);
let framework_name = CFString::from_static_str("com.apple.opengl");
let framework = CFBundle::bundle_with_identifier(Some(&framework_name)).unwrap();

unsafe { CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()) }
CFBundle::function_pointer_for_name(&framework, Some(&symbol_name))
}

pub fn swap_buffers(&self) {
unsafe {
self.context.flushBuffer();
let () = msg_send![self.view, setNeedsDisplay: YES];
}
self.context.flushBuffer();
self.view.setNeedsDisplay(true);
}

/// On macOS the `NSOpenGLView` needs to be resized separtely from our main view.
pub(crate) fn resize(&self, size: NSSize) {
unsafe { NSView::setFrameSize(self.view, size) };
unsafe {
let _: () = msg_send![self.view, setNeedsDisplay: YES];
}
self.view.setFrameSize(size);
self.view.setNeedsDisplay(true);
}

/// Pointer to the `NSOpenGLView` this context renders into. Used by
/// the parent `NSView`'s `hitTest:` override to collapse hits on the
/// render subview to the parent, so AppKit routes `mouseDown:` on
/// first click in non-key windows.
pub(crate) fn ns_view(&self) -> id {
self.view
}
}

impl Drop for GlContext {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.context, release];
let () = msg_send![self.view, release];
}
pub(crate) fn ns_view(&self) -> &NSOpenGLView {
&self.view
}
}
4 changes: 2 additions & 2 deletions src/gl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl GlContext {

/// On macOS the `NSOpenGLView` needs to be resized separtely from our main view.
#[cfg(target_os = "macos")]
pub(crate) fn resize(&self, size: cocoa::foundation::NSSize) {
pub(crate) fn resize(&self, size: objc2_foundation::NSSize) {
self.context.resize(size);
}

Expand All @@ -118,7 +118,7 @@ impl GlContext {
/// render subview to the parent, so AppKit routes `mouseDown:` on
/// first click in non-key windows.
#[cfg(target_os = "macos")]
pub(crate) fn ns_view(&self) -> cocoa::base::id {
pub(crate) fn ns_view(&self) -> &objc2_app_kit::NSView {
self.context.ns_view()
}
}
129 changes: 57 additions & 72 deletions src/macos/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,10 @@

//! Conversion of platform keyboard event into cross-platform event.

use std::cell::Cell;

use cocoa::appkit::{NSEvent, NSEventModifierFlags, NSEventType};
use cocoa::base::id;
use cocoa::foundation::NSString;
use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Modifiers};
use objc::{msg_send, sel, sel_impl};

use crate::keyboard::code_to_location;

pub(crate) fn from_nsstring(s: id) -> String {
unsafe {
let slice = std::slice::from_raw_parts(s.UTF8String() as *const _, s.len());
let result = std::str::from_utf8_unchecked(slice);
result.into()
}
}
use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Modifiers};
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventType};
use std::cell::Cell;

/// State for processing of keyboard events.
///
Expand Down Expand Up @@ -279,72 +266,70 @@ impl KeyboardState {
self.last_mods.get()
}

pub(crate) fn process_native_event(&self, event: id) -> Option<KeyboardEvent> {
unsafe {
let event_type = event.eventType();
let key_code = event.keyCode();
let code = key_code_to_code(key_code);
let location = code_to_location(code);
let raw_mods = event.modifierFlags();
let modifiers = make_modifiers(raw_mods);
let state = match event_type {
NSEventType::NSKeyDown => KeyState::Down,
NSEventType::NSKeyUp => KeyState::Up,
NSEventType::NSFlagsChanged => {
// We use `bits` here because we want to distinguish the
// device dependent bits (when both left and right keys
// may be pressed, for example).
let any_down = raw_mods.bits() & !self.last_mods.get().bits();
self.last_mods.set(raw_mods);
if is_modifier_code(code) {
if any_down == 0 {
KeyState::Up
} else {
KeyState::Down
}
pub(crate) fn process_native_event(&self, event: &NSEvent) -> Option<KeyboardEvent> {
let event_type = event.r#type();
let key_code = event.keyCode();
let code = key_code_to_code(key_code);
let location = code_to_location(code);
let raw_mods = event.modifierFlags();
let modifiers = make_modifiers(raw_mods);
let state = match event_type {
NSEventType::KeyDown => KeyState::Down,
NSEventType::KeyUp => KeyState::Up,
NSEventType::FlagsChanged => {
// We use `bits` here because we want to distinguish the
// device dependent bits (when both left and right keys
// may be pressed, for example).
let any_down = raw_mods.bits() & !self.last_mods.get().bits();
self.last_mods.set(raw_mods);
if is_modifier_code(code) {
if any_down == 0 {
KeyState::Up
} else {
// HandleFlagsChanged has some logic for this; it might
// happen when an app is deactivated by Command-Tab. In
// that case, the best thing to do is synthesize the event
// from the modifiers. But a challenge there is that we
// might get multiple events.
return None;
KeyState::Down
}
} else {
// HandleFlagsChanged has some logic for this; it might
// happen when an app is deactivated by Command-Tab. In
// that case, the best thing to do is synthesize the event
// from the modifiers. But a challenge there is that we
// might get multiple events.
return None;
}
// In case another event type ends up here, do not produce any kind of keyboard event.
_ => return None,
};
let is_composing = false;
let repeat: bool = event_type == NSEventType::NSKeyDown && msg_send![event, isARepeat];
let key = if let Some(key) = code_to_key(code) {
key
}
// In case another event type ends up here, do not produce any kind of keyboard event.
_ => return None,
};
let is_composing = false;
let repeat: bool = event_type == NSEventType::KeyDown && event.isARepeat();
let key = if let Some(key) = code_to_key(code) {
key
} else {
let characters = event.characters().map(|c| c.to_string()).unwrap_or_default();
if is_valid_key(&characters) {
Key::Character(characters)
} else {
let characters = from_nsstring(event.characters());
if is_valid_key(&characters) {
Key::Character(characters)
let chars_ignoring =
event.charactersIgnoringModifiers().map(|c| c.to_string()).unwrap_or_default();
if is_valid_key(&chars_ignoring) {
Key::Character(chars_ignoring)
} else {
let chars_ignoring = from_nsstring(event.charactersIgnoringModifiers());
if is_valid_key(&chars_ignoring) {
Key::Character(chars_ignoring)
} else {
// There may be more heroic things we can do here.
Key::Unidentified
}
// There may be more heroic things we can do here.
Key::Unidentified
}
};
let event =
KeyboardEvent { code, key, location, modifiers, state, is_composing, repeat };
Some(event)
}
}
};
let event = KeyboardEvent { code, key, location, modifiers, state, is_composing, repeat };
Some(event)
}
}

const MODIFIER_MAP: &[(NSEventModifierFlags, Modifiers)] = &[
(NSEventModifierFlags::NSShiftKeyMask, Modifiers::SHIFT),
(NSEventModifierFlags::NSAlternateKeyMask, Modifiers::ALT),
(NSEventModifierFlags::NSControlKeyMask, Modifiers::CONTROL),
(NSEventModifierFlags::NSCommandKeyMask, Modifiers::META),
(NSEventModifierFlags::NSAlphaShiftKeyMask, Modifiers::CAPS_LOCK),
(NSEventModifierFlags::Shift, Modifiers::SHIFT),
(NSEventModifierFlags::Option, Modifiers::ALT),
(NSEventModifierFlags::Control, Modifiers::CONTROL),
(NSEventModifierFlags::Command, Modifiers::META),
(NSEventModifierFlags::CapsLock, Modifiers::CAPS_LOCK),
];

pub(crate) fn make_modifiers(raw: NSEventModifierFlags) -> Modifiers {
Expand Down
Loading
Loading