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 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
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 = "0.2.7"
objc-foundation = "0.1.1"
objc_id = "0.1.1"
cocoa = "0.20.0"
core-foundation = "0.7.0"
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!");
// }
}
303 changes: 290 additions & 13 deletions src/api/cocoa/mod.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,305 @@
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 general representation of the Mac OS X application.
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(())
}
}

// 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