Skip to content
This repository has been archived by the owner on Dec 30, 2020. It is now read-only.

macOS support #40

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 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
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ gtk= "0.8.1"
glib= "0.9.3"
libappindicator= "0.5.1"

# [target.'cfg(target_os = "macos")'.dependencies]
# objc="*"
# cocoa="*"
# core-foundation="*"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "*"
objc-foundation = "*"
objc_id = "*"
cocoa = "*"
core-foundation = "*"
Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6db1648

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ from [winapi-rs, by retep998](https://github.com/retep998/winapi-rs).
This code is covered under the MIT license. This code will be removed
once winapi-rs has a 0.3 crate available.

systray-rs includes some code
from [rust-sysbar, by rust-sysbar](https://github.com/rust-sysbar/rust-sysbar).
This code is covered under the MIT license.

systray-rs is BSD licensed.

Copyright (c) 2016-2020, Nonpolynomial Labs, LLC
Expand Down
Binary file added examples/rust-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 6 additions & 8 deletions examples/systray-example.rs → examples/trayicon.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#![windows_subsystem = "windows"]

//#[cfg(target_os = "windows")]
fn main() -> Result<(), systray::Error> {
let mut app;
match systray::Application::new() {
Expand All @@ -9,7 +8,11 @@ fn main() -> Result<(), systray::Error> {
}
// w.set_icon_from_file(&"C:\\Users\\qdot\\code\\git-projects\\systray-rs\\resources\\rust.ico".to_string());
// w.set_tooltip(&"Whatever".to_string());
app.set_icon_from_file("/usr/share/gxkb/flags/ua.png")?;

let file_path = std::path::Path::new(file!());
let icon_path = file_path.with_file_name("rust-logo.png");

app.set_icon_from_file(icon_path.to_str().unwrap())?;

app.add_menu_item("Print a thing", |_| {
println!("Printing a thing!");
Expand All @@ -35,9 +38,4 @@ fn main() -> Result<(), systray::Error> {
println!("Waiting on message!");
app.wait_for_message()?;
Ok(())
}

// #[cfg(not(target_os = "windows"))]
// fn main() {
// panic!("Not implemented on this platform!");
// }
}
305 changes: 292 additions & 13 deletions src/api/cocoa/mod.rs
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A typo in a comment.

Copy link
Author

@alexandrvicente alexandrvicente Apr 2, 2020

Choose a reason for hiding this comment

The 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 {}
Copy link
Contributor

@daniel-abramov daniel-abramov Mar 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem right. Window contains an Rc which is clearly !Send, so just declaring Window to be Send is not sound.

It seems like making Window implement Send is not the best idea, not only because it contains types which are not Send, but also due to the fact that on OS X most of the things you can do with a window must be done from the main thread. The only way to really make it Send is to use GCD inside each function the user may call on a Window after moving it into a different thread, i.e. make sure that that all actions we apply to Window are executed in a main thread.

Copy link
Author

Choose a reason for hiding this comment

The 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()
}
}
Loading