-
Notifications
You must be signed in to change notification settings - Fork 65
macOS support #40
base: master
Are you sure you want to change the base?
macOS support #40
Changes from 11 commits
ab393c4
b35ae22
5d715e7
2bd330c
d417575
3a332a0
28f1a15
515bda1
b38a456
1a4d0e0
aaa3c32
3a00d0d
2d98b78
6db1648
a5cc6bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,307 @@ | ||
use crate::Error; | ||
use std; | ||
//! Contains the implementation of the Mac OS X tray icon in the top bar. | ||
|
||
pub struct Window {} | ||
use std::{ | ||
self, | ||
cell::RefCell, | ||
ffi::c_void, | ||
mem, | ||
rc::Rc, | ||
sync::{ | ||
Mutex, | ||
mpsc::Sender, | ||
}, | ||
}; | ||
use cocoa::{ | ||
appkit::{ | ||
NSApp, NSApplication, NSApplicationActivateIgnoringOtherApps, NSButton, NSImage, | ||
NSMenu, NSMenuItem, NSRunningApplication, NSStatusBar, NSStatusItem, | ||
NSSquareStatusItemLength | ||
}, | ||
base::{id, nil, YES}, | ||
foundation::{NSData, NSSize, NSAutoreleasePool, NSString} | ||
}; | ||
use objc::{ | ||
Message, | ||
declare::ClassDecl, | ||
runtime::{Class, Object, Sel} | ||
}; | ||
use objc_foundation::{INSObject, NSObject}; | ||
use objc_id::Id; | ||
use crate::{Application, BoxedError, Callback, Error}; | ||
|
||
/// The generation representation of the Mac OS X application. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A typo in a comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 2d98b78 |
||
pub struct Window { | ||
/// A reference to systray::Application for callbacks | ||
systray_application: Option<Rc<RefCell<Box<Application>>>>, | ||
/// A mutable reference to the `NSApplication` instance of the currently running application. | ||
application: Mutex<id>, | ||
/// It seems that we have to use `NSAutoreleasePool` to prevent memory leaks. | ||
autorelease_pool: Mutex<id>, | ||
/// `NSMenu` for menu items. | ||
menu: Mutex<id>, | ||
} | ||
|
||
impl Window { | ||
/// Creates a new instance of the `Window`. | ||
pub fn new() -> Result<Window, Error> { | ||
Err(Error::NotImplementedError) | ||
Ok(Window { | ||
systray_application: None, | ||
application: unsafe { Mutex::from(NSApp()) }, | ||
autorelease_pool: unsafe { Mutex::from(NSAutoreleasePool::new(nil)) }, | ||
menu: unsafe { Mutex::from(NSMenu::new(nil).autorelease()) }, | ||
}) | ||
} | ||
pub fn quit(&self) { | ||
unimplemented!() | ||
|
||
/// Sets the systray application | ||
pub fn set_systray_application(&mut self, application_raw_ptr: *mut Application){ | ||
let application = unsafe { Box::from_raw(application_raw_ptr) }; | ||
self.systray_application = Some(Rc::from(RefCell::from(application))); | ||
} | ||
|
||
/// Closes the current application. | ||
pub fn quit(&mut self) { | ||
if let Ok(application) = self.application.get_mut() { | ||
unsafe { application.stop_(nil); } | ||
} | ||
} | ||
|
||
/// Sets the tooltip (not available for this platform). | ||
pub fn set_tooltip(&self, _: &str) -> Result<(), Error> { | ||
unimplemented!() | ||
Err(Error::OsError("This operating system does not support tooltips for the tray \ | ||
items".to_owned())) | ||
} | ||
pub fn add_menu_item<F>(&self, _: &str, _: F) -> Result<u32, Error> | ||
where | ||
F: std::ops::Fn(&Window) -> () + 'static, | ||
|
||
/// Sets the application icon displayed in the tray bar. Accepts a `buffer` to the underlying | ||
/// image, you can pass even encoded PNG images here. Supports the same list of formats as | ||
/// `NSImage`. | ||
pub fn set_icon_from_buffer(&mut self, buffer: &'static [u8], _: u32, _: u32) | ||
-> Result<(), Error> | ||
{ | ||
unimplemented!() | ||
const ICON_WIDTH: f64 = 18.0; | ||
const ICON_HEIGHT: f64 = 18.0; | ||
|
||
let tray_entry = unsafe { | ||
NSStatusBar::systemStatusBar(nil).statusItemWithLength_(NSSquareStatusItemLength) | ||
.autorelease() | ||
}; | ||
|
||
let nsdata = unsafe { | ||
NSData::dataWithBytes_length_(nil, | ||
buffer.as_ptr() as *const std::os::raw::c_void, | ||
buffer.len() as u64).autorelease() | ||
}; | ||
if nsdata == nil { | ||
return Err(Error::OsError("Could not create `NSData` out of the passed buffer" | ||
.to_owned())); | ||
} | ||
|
||
let nsimage = unsafe { NSImage::initWithData_(NSImage::alloc(nil), nsdata).autorelease() }; | ||
if nsimage == nil { | ||
return Err(Error::OsError("Could not create `NSImage` out of the created \ | ||
`NSData` buffer".to_owned())); | ||
} | ||
|
||
unsafe { | ||
let new_size = NSSize::new(ICON_WIDTH, ICON_HEIGHT); | ||
let _: () = msg_send![nsimage, setSize:new_size]; | ||
tray_entry.button().setImage_(nsimage); | ||
if let Ok(menu) = self.menu.get_mut(){ | ||
tray_entry.setMenu_(*menu); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Starts the application event loop. Calling this function will block the current thread. | ||
pub fn wait_for_message(&mut self) { | ||
unimplemented!() | ||
if let Ok(application) = self.application.get_mut() { | ||
unsafe { | ||
application.activateIgnoringOtherApps_(YES); | ||
NSRunningApplication::currentApplication(nil) | ||
.activateWithOptions_(NSApplicationActivateIgnoringOtherApps); | ||
application.run(); | ||
} | ||
} | ||
} | ||
pub fn set_icon_from_buffer(&self, _: &[u8], _: u32, _: u32) -> Result<(), Error> { | ||
|
||
pub fn set_icon_from_resource(&self, resource_name: &str) -> Result<(), Error> { | ||
unimplemented!() | ||
} | ||
|
||
pub fn set_icon_from_file(&mut self, icon_file: &str) -> Result<(), Error> { | ||
const ICON_WIDTH: f64 = 18.0; | ||
const ICON_HEIGHT: f64 = 18.0; | ||
|
||
let tray_entry = unsafe { | ||
NSStatusBar::systemStatusBar(nil).statusItemWithLength_(NSSquareStatusItemLength) | ||
.autorelease() | ||
}; | ||
|
||
let path = unsafe { | ||
NSString::alloc(nil).init_str(icon_file) | ||
}; | ||
if path == nil { | ||
return Err(Error::OsError("Could not create `NSString` out of the passed &str" | ||
.to_owned())); | ||
} | ||
|
||
let nsimage = unsafe { NSImage::initWithContentsOfFile_(NSImage::alloc(nil), path).autorelease() }; | ||
if nsimage == nil { | ||
return Err(Error::OsError("Could not create `NSImage` out of the created \ | ||
`NSData` buffer".to_owned())); | ||
} | ||
|
||
unsafe { | ||
let new_size = NSSize::new(ICON_WIDTH, ICON_HEIGHT); | ||
let _: () = msg_send![nsimage, setSize:new_size]; | ||
tray_entry.button().setImage_(nsimage); | ||
if let Ok(menu) = self.menu.get_mut(){ | ||
tray_entry.setMenu_(*menu); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn add_menu_separator(&mut self, item_idx: u32) -> Result<(), Error> { | ||
let item = unsafe { | ||
NSMenuItem::separatorItem(nil) | ||
}; | ||
if item == nil { | ||
return Err(Error::OsError("Could not create `NSMenuItem`." | ||
.to_owned())); | ||
} | ||
|
||
unsafe { | ||
if let Ok(menu) = self.menu.get_mut(){ | ||
NSMenu::addItem_(*menu, item); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn add_menu_entry(&mut self, item_idx: u32, item_name: &str, callback: Callback) -> Result<(), Error> { | ||
let blank_key = unsafe { NSString::alloc(nil).init_str("") }; | ||
if blank_key == nil { | ||
return Err(Error::OsError("Could not create blank `NSString`." | ||
.to_owned())); | ||
} | ||
|
||
let title = unsafe { NSString::alloc(nil).init_str(item_name) }; | ||
if title == nil { | ||
return Err(Error::OsError("Could not create `NSString` from the item name." | ||
.to_owned())); | ||
} | ||
|
||
let action = sel!(call); | ||
|
||
let item = unsafe { | ||
NSMenuItem::alloc(nil) | ||
.initWithTitle_action_keyEquivalent_(title, action, blank_key) | ||
}; | ||
if item == nil { | ||
return Err(Error::OsError("Could not create `NSMenuItem`." | ||
.to_owned())); | ||
} | ||
|
||
unsafe { | ||
if let Some(app) = &self.systray_application { | ||
let _ : () = msg_send![item, setTarget: CocoaCallback::from(app.clone(), callback)]; | ||
} | ||
if let Ok(menu) = self.menu.get_mut(){ | ||
NSMenu::addItem_(*menu, item); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn shutdown(&self) -> Result<(), Error> { | ||
Ok(()) | ||
} | ||
} | ||
|
||
unsafe impl Send for Window {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It seems like making There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed in 3a00d0d |
||
|
||
// Devired from https://github.com/rust-sysbar/rust-sysbar/blob/master/src/mac_os/mod.rs | ||
// Copyright (c) 2017 The rs-barfly Developers | ||
// Copyright (c) 2017 The rust-sysbar Developers | ||
|
||
pub struct CocoaCallbackState { | ||
application: Rc<RefCell<Box<Application>>>, | ||
callback: Callback | ||
} | ||
|
||
enum CocoaCallback {} | ||
|
||
impl CocoaCallback { | ||
pub fn from(application: Rc<RefCell<Box<Application>>>, callback: Callback) -> Id<Self> { | ||
let ccs = CocoaCallbackState { | ||
application, | ||
callback | ||
}; | ||
let bccs = Box::new(ccs); | ||
|
||
let ptr = Box::into_raw(bccs); | ||
let ptr = ptr as *mut c_void as usize; | ||
let mut oid = <CocoaCallback as INSObject>::new(); | ||
(*oid).setptr(ptr); | ||
oid | ||
} | ||
|
||
fn setptr(&mut self, uptr: usize) { | ||
unsafe { | ||
let obj = &mut *(self as *mut _ as *mut ::objc::runtime::Object); | ||
obj.set_ivar("_cbptr", uptr); | ||
} | ||
} | ||
} | ||
|
||
impl CocoaCallbackState { | ||
pub fn call(&mut self) -> Result<(), BoxedError> { | ||
if let Ok(mut application) = self.application.try_borrow_mut() { | ||
return (*self.callback)(&mut application); | ||
} | ||
Err(Box::from(Error::OsError("Unable to borrow the application".to_owned()))) | ||
} | ||
} | ||
|
||
unsafe impl Message for CocoaCallback {} | ||
|
||
impl INSObject for CocoaCallback { | ||
fn class() -> &'static Class { | ||
let cname = "CCCallback"; | ||
|
||
let mut _class = Class::get(cname); | ||
if _class.is_none() { | ||
let superclass = NSObject::class(); | ||
let mut decl = ClassDecl::new(&cname, superclass).unwrap(); | ||
decl.add_ivar::<usize>("_cbptr"); | ||
|
||
extern "C" fn sysbar_callback_call(obj: &Object, _: Sel) { | ||
unsafe { | ||
let pointer_value: usize = *obj.get_ivar("_cbptr"); | ||
let callback_pointer = pointer_value as *mut c_void as *mut CocoaCallbackState; | ||
let mut boxed_callback: Box<CocoaCallbackState> = Box::from_raw(callback_pointer); | ||
{ | ||
boxed_callback.call(); | ||
} | ||
mem::forget(boxed_callback); | ||
} | ||
} | ||
|
||
unsafe { | ||
decl.add_method( | ||
sel!(call), | ||
sysbar_callback_call as extern "C" fn(&Object, Sel), | ||
); | ||
} | ||
|
||
decl.register(); | ||
_class = Class::get(cname); | ||
} | ||
_class.unwrap() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to have some more strict version requirement for production usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 6db1648