From 4d7e0c64e1511ef5745b646f7eb311ee918ec029 Mon Sep 17 00:00:00 2001 From: Daniel Abramov Date: Wed, 1 Mar 2017 16:28:06 +0100 Subject: [PATCH] Add basic tray icon implementation for Mac OS --- Cargo.toml | 23 ++--- examples/rust-logo.png | Bin 0 -> 7460 bytes examples/{systray-example.rs => trayicon.rs} | 18 +++- src/api/cocoa/mod.rs | 98 +++++++++++++++++-- src/lib.rs | 8 ++ 5 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 examples/rust-logo.png rename examples/{systray-example.rs => trayicon.rs} (71%) diff --git a/Cargo.toml b/Cargo.toml index 5728a7b..9495b7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,20 +10,21 @@ readme = "README.md" keywords = ["gui"] [dependencies] +libc="0.2" log="0.3" [target.'cfg(target_os = "windows")'.dependencies] -winapi="0.2" -user32-sys="0.2" -kernel32-sys="0.2" -libc="0.2" +winapi = "0.2" +user32-sys = "0.2" +kernel32-sys = "0.2" +shell32-sys = "0.1" [target.'cfg(target_os = "linux")'.dependencies] -gtk="^0.1.2" -glib="^0.1.2" -libappindicator="0.2" +gtk = "^0.1.2" +glib = "^0.1.2" +libappindicator = "0.2" -# [target.'cfg(target_os = "macos")'.dependencies] -# objc="*" -# cocoa="*" -# core-foundation="*" +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2" +cocoa = "0.9" +core-foundation = "0.4" diff --git a/examples/rust-logo.png b/examples/rust-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f9668c5000e28660db940a2dc6d0a79c0ed3a5ad GIT binary patch literal 7460 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 literal 0 HcmV?d00001 diff --git a/examples/systray-example.rs b/examples/trayicon.rs similarity index 71% rename from examples/systray-example.rs rename to examples/trayicon.rs index 837e20b..b3e3234 100644 --- a/examples/systray-example.rs +++ b/examples/trayicon.rs @@ -1,6 +1,6 @@ extern crate systray; -//#[cfg(target_os = "windows")] +#[cfg(target_os = "windows")] fn main() { let mut app; match systray::Application::new() { @@ -27,7 +27,15 @@ fn main() { app.wait_for_message(); } -// #[cfg(not(target_os = "windows"))] -// fn main() { -// panic!("Not implemented on this platform!"); -// } +#[cfg(target_os = "macos")] +fn main() { + let mut app; + match systray::Application::new() { + Ok(w) => app = w, + Err(_) => panic!("Can't create tray icon app!") + } + + const ICON_BUFFER: &'static [u8] = include_bytes!("rust-logo.png"); + app.set_icon_from_buffer(ICON_BUFFER, 256, 256).unwrap(); + app.wait_for_message(); +} diff --git a/src/api/cocoa/mod.rs b/src/api/cocoa/mod.rs index 686f2fc..e326405 100644 --- a/src/api/cocoa/mod.rs +++ b/src/api/cocoa/mod.rs @@ -1,28 +1,110 @@ +//! Contains the implementation of the Mac OS X tray icon in the top bar. + use std; -use {SystrayError}; +use cocoa::appkit::{NSApp, NSApplication, NSButton, NSImage, NSStatusBar, NSStatusItem, + NSSquareStatusItemLength}; +use cocoa::base::{id, nil}; +use cocoa::foundation::{NSData, NSSize, NSAutoreleasePool}; +use libc::c_void; + +use {SystrayEvent, SystrayError}; + +/// The generation representation of the Mac OS X application. pub struct Window { + /// A mutable reference to the `NSApplication` instance of the currently running application. + application: id, + /// It seems that we have to use `NSAutoreleasePool` to prevent memory leaks. + autorelease_pool: id, } impl Window { - pub fn new() -> Result { - Err(SystrayError::NotImplementedError) + /// Creates a new instance of the `Window`. + pub fn new(_: std::sync::mpsc::Sender) -> Result { + Ok(Window { + application: unsafe { NSApp() }, + autorelease_pool: unsafe { NSAutoreleasePool::new(nil) }, + }) } + + /// Closes the current application. pub fn quit(&self) { + unsafe { msg_send![self.application, terminate] }; + } + + pub fn shutdown(&self) -> Result<(), SystrayError> { unimplemented!() } + + /// Sets the tooltip (not available for this platfor). pub fn set_tooltip(&self, _: &String) -> Result<(), SystrayError> { + Err(SystrayError::OsError("This operating system does not support tooltips for the tray \ + items".to_owned())) + } + + /// Adds an additional item to the tray icon menu. + pub fn add_menu_entry(&self, _: u32, _: &String) -> Result { unimplemented!() } - pub fn add_menu_item(&self, _: &String, _: F) -> Result - where F: std::ops::Fn(&Window) -> () + 'static - { + + pub fn add_menu_separator(&self, _: u32) -> Result<(), SystrayError> { unimplemented!() } - pub fn wait_for_message(&mut self) { + + /// 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: &[u8], _: u32, _: u32) + -> Result<(), SystrayError> + { + const ICON_WIDTH: f64 = 18.0; + const ICON_HEIGHT: f64 = 18.0; + + let tray_entry = unsafe { + NSStatusBar::systemStatusBar(nil).statusItemWithLength_(NSSquareStatusItemLength) + }; + + let nsdata = unsafe { + NSData::dataWithBytes_length_(nil, + buffer.as_ptr() as *const c_void, + buffer.len() as u64).autorelease() + }; + if nsdata == nil { + return Err(SystrayError::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(SystrayError::OsError("Could not create `NSImage` out of the created \ + `NSData` buffer".to_owned())); + } + + unsafe { + let new_size = NSSize::new(ICON_WIDTH, ICON_HEIGHT); + msg_send![nsimage, setSize:new_size]; + tray_entry.button().setImage_(nsimage); + } + + Ok(()) + } + + pub fn set_icon_from_file(&self, _: &String) -> Result<(), SystrayError> { unimplemented!() } - pub fn set_icon_from_buffer(&self, _: &[u8], _: u32, _: u32) -> Result<(), SystrayError> { + + pub fn set_icon_from_resource(&self, _: &String) -> Result<(), SystrayError> { unimplemented!() } + + /// Starts the application event loop. Calling this function will block the current thread. + pub fn wait_for_message(&mut self) { + unsafe { self.application.run() }; + } +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { self.autorelease_pool.drain() }; + } } diff --git a/src/lib.rs b/src/lib.rs index 785f180..186de76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,14 @@ extern crate kernel32; #[cfg(target_os = "windows")] extern crate user32; #[cfg(target_os = "windows")] +extern crate shell32; + +#[cfg(target_os = "macos")] +extern crate cocoa; +#[cfg(target_os = "macos")] +#[macro_use] +extern crate objc; + extern crate libc; #[cfg(target_os = "linux")] extern crate gtk;