diff options
Diffstat (limited to 'lib')
| -rwxr-xr-x | lib/Makefile | 40 | ||||
| -rw-r--r-- | lib/libsystray_darwin_386.a | bin | 0 -> 29632 bytes | |||
| -rw-r--r-- | lib/libsystray_darwin_amd64.a | bin | 0 -> 29632 bytes | |||
| -rw-r--r-- | lib/systray_darwin.c | 163 | ||||
| -rw-r--r-- | lib/systray_linux.c | 148 | ||||
| -rw-r--r-- | lib/systray_windows.c | 255 |
6 files changed, 606 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile new file mode 100755 index 0000000..f7ea313 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,40 @@ +CC = gcc + +ifndef os + os = linux +endif + +ifndef arch + arch = 386 +endif + +CCFLAGS = -fPIC -Wall -Wextra -D$(os) -finline-functions -O3 -fno-strict-aliasing -fvisibility=hidden + +ifeq ($(os), darwin) + CCFLAGS += -DDARWIN -x objective-c -fobjc-arc +endif + +ifeq ($(arch), 386) + CCFLAGS += -DIA32 +endif + +ifeq ($(arch), amd64) + CCFLAGS += -DAMD64 +endif + +OBJS = systray_$(os)_$(arch).o +DIR = $(shell pwd) + +all: ../libsystray_$(os)_$(arch).a + +%_$(os)_$(arch).o: %_$(os).c + $(CC) $(CCFLAGS) -o $@ $< -c + +libsystray_$(os)_$(arch).a: $(OBJS) + ar -rcs $@ $^ + +../libsystray_$(os)_$(arch).a: libsystray_$(os)_$(arch).a + cp libsystray_$(os)_$(arch).a ../ + +clean: + rm -f *.o *.a ../libsystray*.a diff --git a/lib/libsystray_darwin_386.a b/lib/libsystray_darwin_386.a Binary files differnew file mode 100644 index 0000000..e34ebba --- /dev/null +++ b/lib/libsystray_darwin_386.a diff --git a/lib/libsystray_darwin_amd64.a b/lib/libsystray_darwin_amd64.a Binary files differnew file mode 100644 index 0000000..79ae71f --- /dev/null +++ b/lib/libsystray_darwin_amd64.a diff --git a/lib/systray_darwin.c b/lib/systray_darwin.c new file mode 100644 index 0000000..fffc49e --- /dev/null +++ b/lib/systray_darwin.c @@ -0,0 +1,163 @@ +#import <Cocoa/Cocoa.h> +#include "../systray.h" + +@interface MenuItem : NSObject +{ + @public + NSNumber* menuId; + NSString* title; + NSString* tooltip; + short disabled; + short checked; +} +-(id) initWithId: (int)theMenuId + withTitle: (const char*)theTitle + withTooltip: (const char*)theTooltip + withDisabled: (short)theDisabled + withChecked: (short)theChecked; + @end + @implementation MenuItem + -(id) initWithId: (int)theMenuId + withTitle: (const char*)theTitle + withTooltip: (const char*)theTooltip + withDisabled: (short)theDisabled + withChecked: (short)theChecked +{ + menuId = [NSNumber numberWithInt:theMenuId]; + title = [[NSString alloc] initWithCString:theTitle + encoding:NSUTF8StringEncoding]; + tooltip = [[NSString alloc] initWithCString:theTooltip + encoding:NSUTF8StringEncoding]; + disabled = theDisabled; + checked = theChecked; + return self; +} +@end + +@interface AppDelegate: NSObject <NSApplicationDelegate> + - (void) add_or_update_menu_item:(MenuItem*) item; + - (IBAction)menuHandler:(id)sender; + @property (assign) IBOutlet NSWindow *window; + @end + + @implementation AppDelegate +{ + NSStatusItem *statusItem; + NSMenu *menu; + NSCondition* cond; +} + +@synthesize window = _window; +#pragma GCC diagnostic ignored "-Wunused-parameter" +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + NSZone *menuZone = [NSMenu menuZone]; + self->menu = [[NSMenu allocWithZone:menuZone] init]; + [self->menu setAutoenablesItems: FALSE]; + [self->statusItem setMenu:self->menu]; + systray_ready(); +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification +{ + [[NSStatusBar systemStatusBar] removeStatusItem: statusItem]; +} +#pragma GCC diagnostic warning "-Wunused-parameter" + +- (void)setIcon:(NSImage *)image { + [statusItem setImage:image]; +} + +- (void)setTitle:(NSString *)title { + [statusItem setTitle:title]; +} + +- (void)setTooltip:(NSString *)tooltip { + [statusItem setToolTip:tooltip]; +} + +- (IBAction)menuHandler:(id)sender +{ + NSNumber* menuId = [sender representedObject]; + systray_menu_item_selected(menuId.intValue); +} + +- (void) add_or_update_menu_item:(MenuItem*) item +{ + NSMenuItem* menuItem; + int existedMenuIndex = [menu indexOfItemWithRepresentedObject: item->menuId]; + if (existedMenuIndex == -1) { + menuItem = [menu addItemWithTitle:item->title action:@selector(menuHandler:) keyEquivalent:@""]; + [menuItem setTarget:self]; + [menuItem setRepresentedObject: item->menuId]; + + } + else { + menuItem = [menu itemAtIndex: existedMenuIndex]; + [menuItem setTitle:item->title]; + } + [menuItem setToolTip:item->tooltip]; + if (item->disabled == 1) { + [menuItem setEnabled:FALSE]; + } else { + [menuItem setEnabled:TRUE]; + } + if (item->checked == 1) { + [menuItem setState:NSOnState]; + } else { + [menuItem setState:NSOffState]; + } +} + +- (void) quit +{ + [NSApp terminate:self]; +} + +@end + +int nativeLoop(void) { + AppDelegate *delegate = [[AppDelegate alloc] init]; + [[NSApplication sharedApplication] setDelegate:delegate]; + [NSApp run]; + return EXIT_SUCCESS; +} + +void runInMainThread(SEL method, id object) { + [(AppDelegate*)[NSApp delegate] + performSelectorOnMainThread:method + withObject:object + waitUntilDone: YES]; +} + +void setIcon(const char* iconBytes, int length) { + NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; + NSImage *image = [[NSImage alloc] initWithData:buffer]; + runInMainThread(@selector(setIcon:), (id)image); +} + +void setTitle(char* ctitle) { + NSString* title = [[NSString alloc] initWithCString:ctitle + encoding:NSUTF8StringEncoding]; + free(ctitle); + runInMainThread(@selector(setTitle:), (id)title); +} + +void setTooltip(char* ctooltip) { + NSString* tooltip = [[NSString alloc] initWithCString:ctooltip + encoding:NSUTF8StringEncoding]; + free(ctooltip); + runInMainThread(@selector(setTooltip:), (id)tooltip); +} + +void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked) { + MenuItem* item = [[MenuItem alloc] initWithId: menuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked]; + free(title); + free(tooltip); + runInMainThread(@selector(add_or_update_menu_item:), (id)item); +} + +void quit() { + runInMainThread(@selector(quit), nil); +} diff --git a/lib/systray_linux.c b/lib/systray_linux.c new file mode 100644 index 0000000..9358890 --- /dev/null +++ b/lib/systray_linux.c @@ -0,0 +1,148 @@ +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <libappindicator/app-indicator.h> +#include "../systray.h" + +static AppIndicator *global_app_indicator; +static GtkWidget *global_tray_menu = NULL; +static GList *global_menu_items = NULL; +static GArray *global_temp_icon_file_names = NULL; + +typedef struct { + GtkWidget *menu_item; + char *menu_id; +} MenuItemNode; + +typedef struct { + char* menu_id; + char* title; + char* tooltip; + short disabled; + short checked; +} MenuItemInfo; + +int nativeLoop(void) { + gtk_init(0, NULL); + global_app_indicator = app_indicator_new("systray", "", + APP_INDICATOR_CATEGORY_APPLICATION_STATUS); + app_indicator_set_status(global_app_indicator, APP_INDICATOR_STATUS_ACTIVE); + global_tray_menu = gtk_menu_new(); + app_indicator_set_menu(global_app_indicator, GTK_MENU(global_tray_menu)); + global_temp_icon_file_names = g_array_new(TRUE, FALSE, sizeof(char*)); + systray_ready(); + gtk_main(); + return; +} + +// runs in main thread, should always return FALSE to prevent gtk to execute it again +gboolean do_set_icon(gpointer data) { + GBytes* bytes = (GBytes*)data; + char* temp_file_name = malloc(PATH_MAX); + strcpy(temp_file_name, "/tmp/systray_XXXXXX"); + int fd = mkstemp(temp_file_name); + if (fd == -1) { + printf("failed to create temp icon file %s: %s\n", temp_file_name, strerror(errno)); + return FALSE; + } + g_array_append_val(global_temp_icon_file_names, temp_file_name); + gsize size = 0; + gconstpointer icon_data = g_bytes_get_data(bytes, &size); + ssize_t written = write(fd, icon_data, size); + close(fd); + if(written != size) { + printf("failed to write temp icon file %s: %s\n", temp_file_name, strerror(errno)); + return FALSE; + } + app_indicator_set_icon_full(global_app_indicator, temp_file_name, ""); + app_indicator_set_attention_icon_full(global_app_indicator, temp_file_name, ""); + g_bytes_unref(bytes); + return FALSE; +} + +// runs in main thread, should always return FALSE to prevent gtk to execute it again +gboolean do_add_or_update_menu_item(gpointer data) { + MenuItemInfo *mii = (MenuItemInfo*)data; + GList* it; + for(it = global_menu_items; it != NULL; it = it->next) { + MenuItemNode* item = (MenuItemNode*)(it->data); + if(strcmp(item->menu_id, mii->menu_id) == 0){ + gtk_menu_item_set_label(GTK_MENU_ITEM(item->menu_item), mii->title); + free(mii->menu_id); + break; + } + } + + // menu id doesn't exist, add new item + if(it == NULL) { + GtkWidget *menu_item = gtk_menu_item_new_with_label(mii->title); + g_signal_connect_swapped(G_OBJECT(menu_item), "activate", G_CALLBACK(systray_menu_item_selected), mii->menu_id); + gtk_menu_shell_append(GTK_MENU_SHELL(global_tray_menu), menu_item); + + MenuItemNode* new_item = malloc(sizeof(MenuItemNode)); + new_item->menu_id = mii->menu_id; + new_item->menu_item = menu_item; + GList* new_node = malloc(sizeof(GList)); + new_node->data = new_item; + new_node->next = global_menu_items; + if(global_menu_items != NULL) { + global_menu_items->prev = new_node; + } + global_menu_items = new_node; + it = new_node; + } + GtkWidget * menu_item = GTK_WIDGET(((MenuItemNode*)(it->data))->menu_item); + gtk_widget_set_sensitive(menu_item, mii->disabled == 1 ? FALSE : TRUE); + gtk_widget_show_all(global_tray_menu); + + free(mii->title); + free(mii->tooltip); + free(mii); + return FALSE; +} + +// runs in main thread, should always return FALSE to prevent gtk to execute it again +gboolean do_quit(gpointer data) { + int i; + for (i = 0; i < INT_MAX; ++i) { + char * temp_file_name = g_array_index(global_temp_icon_file_names, char*, i); + if (temp_file_name == NULL) { + break; + } + int ret = unlink(temp_file_name); + if (ret == -1) { + printf("failed to remove temp icon file %s: %s\n", temp_file_name, strerror(errno)); + } + } + gtk_main_quit(); + return FALSE; +} + +void setIcon(const char* iconBytes, int length) { + GBytes* bytes = g_bytes_new_static(iconBytes, length); + g_idle_add(do_set_icon, bytes); +} + +void setTitle(char* ctitle) { + app_indicator_set_label(global_app_indicator, ctitle, ""); + free(ctitle); +} + +void setTooltip(char* ctooltip) { + free(ctooltip); +} + +void add_or_update_menu_item(char* menu_id, char* title, char* tooltip, short disabled, short checked) { + MenuItemInfo *mii = malloc(sizeof(MenuItemInfo)); + mii->menu_id = menu_id; + mii->title = title; + mii->tooltip = tooltip; + mii->disabled = disabled; + mii->checked = checked; + g_idle_add(do_add_or_update_menu_item, mii); +} + +void quit() { + g_idle_add(do_quit, NULL); +} diff --git a/lib/systray_windows.c b/lib/systray_windows.c new file mode 100644 index 0000000..a60448a --- /dev/null +++ b/lib/systray_windows.c @@ -0,0 +1,255 @@ +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +#include <shellapi.h> +#include "../systray.h" + +// Message posted into message loop when Notification Icon is clicked +#define WM_SYSTRAY_MESSAGE (WM_USER + 1) + +static NOTIFYICONDATA nid; +static HWND hWnd; +static HMENU hTrayMenu; + +void reportWindowsError(const char* action) { + LPTSTR pErrMsg = NULL; + DWORD errCode = GetLastError(); + DWORD result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER| + FORMAT_MESSAGE_FROM_SYSTEM| + FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, + errCode, + LANG_NEUTRAL, + pErrMsg, + 0, + NULL); + printf("Systray error %s: %d %s\n", action, errCode, pErrMsg); +} + +wchar_t* UTF8ToUnicode(const char* str) { + wchar_t* result; + int textLen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL ,0); + result = (wchar_t *)calloc((textLen+1), sizeof(wchar_t)); + int converted = MultiByteToWideChar(CP_UTF8, 0, str, -1, (LPWSTR)result, textLen); + // Ensure result is alway zero terminated in case either syscall failed + if (converted == 0) { + reportWindowsError("convert UTF8 to UNICODE"); + result[0] = L'\0'; + } + return result; +} + +void ShowMenu(HWND hWnd) { + POINT p; + if (0 == GetCursorPos(&p)) { + reportWindowsError("get tray menu position"); + return; + }; + SetForegroundWindow(hWnd); // Win32 bug work-around + TrackPopupMenu(hTrayMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hWnd, NULL); + +} + +char* GetMenuItemId(int index) { + MENUITEMINFO menuItemInfo; + menuItemInfo.cbSize = sizeof(MENUITEMINFO); + menuItemInfo.fMask = MIIM_DATA; + if (0 == GetMenuItemInfo(hTrayMenu, index, TRUE, &menuItemInfo)) { + reportWindowsError("get menu item id"); + return NULL; + } + return (char*)menuItemInfo.dwItemData; +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_MENUCOMMAND: + systray_menu_item_selected(GetMenuItemId(wParam)); + break; + case WM_DESTROY: + Shell_NotifyIcon(NIM_DELETE, &nid); + PostQuitMessage(0); + break; + case WM_SYSTRAY_MESSAGE: + switch(lParam) { + case WM_RBUTTONUP: + ShowMenu(hWnd); + break; + case WM_LBUTTONUP: + ShowMenu(hWnd); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + }; + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +void MyRegisterClass(HINSTANCE hInstance, TCHAR* szWindowClass) { + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + + RegisterClassEx(&wcex); +} + +HWND InitInstance(HINSTANCE hInstance, int nCmdShow, TCHAR* szWindowClass) { + HWND hWnd = CreateWindow(szWindowClass, TEXT(""), WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); + if (!hWnd) { + return 0; + } + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + return hWnd; +} + + +BOOL createMenu() { + hTrayMenu = CreatePopupMenu(); + MENUINFO menuInfo; + menuInfo.cbSize = sizeof(MENUINFO); + menuInfo.fMask = MIM_APPLYTOSUBMENUS | MIM_STYLE; + menuInfo.dwStyle = MNS_NOTIFYBYPOS; + return SetMenuInfo(hTrayMenu, &menuInfo); +} + +BOOL addNotifyIcon() { + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hWnd; + nid.uID = 100; + nid.uCallbackMessage = WM_SYSTRAY_MESSAGE; + nid.uFlags = NIF_MESSAGE; + return Shell_NotifyIcon(NIM_ADD, &nid); +} + +int nativeLoop(void) { + HINSTANCE hInstance = GetModuleHandle(NULL); + TCHAR* szWindowClass = TEXT("SystrayClass"); + MyRegisterClass(hInstance, szWindowClass); + hWnd = InitInstance(hInstance, FALSE, szWindowClass); // Don't show window + if (!hWnd) { + return EXIT_FAILURE; + } + if (!createMenu() || !addNotifyIcon()) { + return EXIT_FAILURE; + } + systray_ready(); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return EXIT_SUCCESS; +} + + +void setIcon(const char* iconBytes, int length) { + HICON hIcon; + { + // This is really hacky, but LoadImage won't let me load an image from memory. + // So we have to write out a temporary file, load it from there, then delete the file. + + // From http://msdn.microsoft.com/en-us/library/windows/desktop/aa363875.aspx + TCHAR szTempFileName[MAX_PATH+1]; + TCHAR lpTempPathBuffer[MAX_PATH+1]; + int dwRetVal = GetTempPath(MAX_PATH+1, lpTempPathBuffer); + if (dwRetVal > MAX_PATH+1 || (dwRetVal == 0)) { + reportWindowsError("get temp icon path"); + return; + } + + int uRetVal = GetTempFileName(lpTempPathBuffer, TEXT("systray_"), 0, szTempFileName); + if (uRetVal == 0) { + reportWindowsError("get temp icon file name"); + return; + } + + FILE* fIcon = _wfopen(szTempFileName, TEXT("wb")); + if (length != fwrite(iconBytes, 1, length, fIcon)) { + printf("error write temp icon file\n"); + }; + fclose(fIcon); + + hIcon = LoadImage(NULL, szTempFileName, IMAGE_ICON, 64, 64, LR_LOADFROMFILE); + + _wremove(szTempFileName); + } + + nid.hIcon = hIcon; + nid.uFlags = NIF_ICON; + Shell_NotifyIcon(NIM_MODIFY, &nid); +} + +// Don't support for Windows +void setTitle(char* ctitle) { + free(ctitle); +} + +void setTooltip(char* ctooltip) { + wchar_t* tooltip = UTF8ToUnicode(ctooltip); + wcsncpy(nid.szTip, tooltip, 64); + nid.uFlags = NIF_TIP; + Shell_NotifyIcon(NIM_MODIFY, &nid); + free(tooltip); + free(ctooltip); +} + +void add_or_update_menu_item(char* menuId, char* ctitle, char* ctooltip, short disabled, short checked) { + wchar_t* title = UTF8ToUnicode(ctitle); + MENUITEMINFO menuItemInfo; + menuItemInfo.cbSize = sizeof(MENUITEMINFO); + menuItemInfo.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_STATE; + menuItemInfo.fType = MFT_STRING; + menuItemInfo.dwTypeData = title; + menuItemInfo.cch = wcslen(title) + 1; + menuItemInfo.dwItemData = (ULONG_PTR)menuId; + menuItemInfo.fState = 0; + if (disabled == 1) { + menuItemInfo.fState |= MFS_DISABLED; + } + if (checked == 1) { + menuItemInfo.fState |= MFS_CHECKED; + } + + int itemCount = GetMenuItemCount(hTrayMenu); + int i; + for (i = 0; i < itemCount; i++) { + char * idString = GetMenuItemId(i); + if (NULL == idString) { + continue; + } + if (strcmp(menuId, idString) == 0) { + free(idString); + SetMenuItemInfo(hTrayMenu, i, TRUE, &menuItemInfo); + break; + } + } + if (i == itemCount) { + InsertMenuItem(hTrayMenu, -1, TRUE, &menuItemInfo); + } + free(title); + free(ctitle); + free(ctooltip); +} + +void quit() { + PostMessage(hWnd, WM_DESTROY, 0, 0); +} |
