diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b18604b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Serge Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/example.c b/example.c new file mode 100644 index 0000000..c42ffe2 --- /dev/null +++ b/example.c @@ -0,0 +1,101 @@ +#include +#include + +#if defined (_WIN32) || defined (_WIN64) +#define TRAY_WINAPI 1 +#elif defined (__linux__) || defined (linux) || defined (__linux) +#define TRAY_APPINDICATOR 1 +#elif defined (__APPLE__) || defined (__MACH__) +#define TRAY_APPKIT 1 +#endif + +#include "tray.h" + +#if TRAY_APPINDICATOR +#define TRAY_ICON1 "indicator-messages" +#define TRAY_ICON2 "indicator-messages-new" +#elif TRAY_APPKIT +#define TRAY_ICON1 "icon.png" +#define TRAY_ICON2 "icon.png" +#elif TRAY_WINAPI +#define TRAY_ICON1 "icon.ico" +#define TRAY_ICON2 "icon.ico" +#endif + +static struct tray tray; + +static void toggle_cb(struct tray_menu *item) { + printf("toggle cb\n"); + item->checked = !item->checked; + tray_update(&tray); +} + +static void hello_cb(struct tray_menu *item) { + (void)item; + printf("hello cb\n"); + if (strcmp(tray.icon, TRAY_ICON1) == 0) { + tray.icon = TRAY_ICON2; + } else { + tray.icon = TRAY_ICON1; + } + tray_update(&tray); +} + +static void quit_cb(struct tray_menu *item) { + (void)item; + printf("quit cb\n"); + tray_exit(); +} + +static void submenu_cb(struct tray_menu *item) { + (void)item; + printf("submenu: clicked on %s\n", item->text); + tray_update(&tray); +} + +// Test tray init +static struct tray tray = { + .icon = TRAY_ICON1, + .menu = + (struct tray_menu[]){ + {.text = "Hello", .cb = hello_cb}, + {.text = "Checked", .checked = 1, .cb = toggle_cb}, + {.text = "Disabled", .disabled = 1}, + {.text = "-"}, + {.text = "SubMenu", + .submenu = + (struct tray_menu[]){ + {.text = "FIRST", .checked = 1, .cb = submenu_cb}, + {.text = "SECOND", + .submenu = + (struct tray_menu[]){ + {.text = "THIRD", + .submenu = + (struct tray_menu[]){ + {.text = "7", .cb = submenu_cb}, + {.text = "-"}, + {.text = "8", .cb = submenu_cb}, + {.text = NULL}}}, + {.text = "FOUR", + .submenu = + (struct tray_menu[]){ + {.text = "5", .cb = submenu_cb}, + {.text = "6", .cb = submenu_cb}, + {.text = NULL}}}, + {.text = NULL}}}, + {.text = NULL}}}, + {.text = "-"}, + {.text = "Quit", .cb = quit_cb}, + {.text = NULL}}, +}; + +int main() { + if (tray_init(&tray) < 0) { + printf("failed to create tray\n"); + return 1; + } + while (tray_loop(1) == 0) { + printf("iteration\n"); + } + return 0; +} diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..bb96b6e Binary files /dev/null and b/icon.ico differ diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..bb96b6e Binary files /dev/null and b/icon.png differ diff --git a/pf.ico b/pf.ico new file mode 100644 index 0000000..bb96b6e Binary files /dev/null and b/pf.ico differ diff --git a/pf.png b/pf.png new file mode 100644 index 0000000..bb96b6e Binary files /dev/null and b/pf.png differ diff --git a/stats.c b/stats.c new file mode 100644 index 0000000..6f5c357 --- /dev/null +++ b/stats.c @@ -0,0 +1,66 @@ + +#include + +#include "api.h" + +void print_stats(enum ps ps) { + int procs = SDL_GetCPUCount(); + int d ; + int L1; + char L1suf[6]; + char dsuf[3]; + SDL_PowerState pwr; + int psec;//seconds of battery life left + int ppct;//percentage of battery life left + char psecsuf[8]; + const char*pwrdesc; + /*--system RAM--*/ + d = SDL_GetSystemRAM(); + if(d>1024) { + d /= 1024; + memcpy(dsuf,"gb",3); + } + else memcpy(dsuf,"mb",3); + /*--L1--*/ + L1 = SDL_GetCPUCacheLineSize(); + if(L1>1024*1024) { + L1 /= 1024 * 1024; + memcpy(L1suf,"mb",3); + } else if(L1>1024) { + L1 /= 1024; + memcpy(L1suf,"kb",3); + } else { + memcpy(L1suf,"bytes",6); + } + /*--power state--*/ + pwr = SDL_GetPowerInfo(&psec, &ppct); + if(psec == -1) psec = 60*60; + if(psec>=60*60) { + psec /= 60*60; + memcpy(psecsuf,psec>1?"hours":"hour\0\0",6); + } else if(psec>=60) { + psec /= 60; + memcpy(psecsuf,psec>1?"minutes":"minute\0\0",8); + } else { + memcpy(psecsuf,psec>1?"seconds":"second\0\0",8); + } + switch(pwr){ + case SDL_POWERSTATE_ON_BATTERY: pwrdesc="not plugged in, running on the battery";break; + case SDL_POWERSTATE_NO_BATTERY: pwrdesc="plugged in, no battery available";break; + case SDL_POWERSTATE_CHARGING: pwrdesc="plugged in, charging battery";break; + case SDL_POWERSTATE_CHARGED: pwrdesc="plugged in, battery charged";break; + default: + case SDL_POWERSTATE_UNKNOWN: pwrdesc="cannot determine power status"; + } + /*--print stats--*/ + switch(ps){ + case ps_shutdown: + printf("SDL Quit Battery %d%% (%d%s)\n",ppct,psec,psecsuf); + break; + default: + case ps_startup: + printf("SDL %s %d%sRAM %dProc%s (L1align=%d%s) Battery %d%% (%d%s)\n",SDL_GetPlatform(),d,dsuf,procs,procs>1?"s":"",L1,L1suf, + ppct,psec,psecsuf); + } + +} diff --git a/sync2git.bat b/sync2git.bat index d4e945f..e3bc508 100644 --- a/sync2git.bat +++ b/sync2git.bat @@ -1,8 +1,9 @@ set GITDIR=D:\dev\db\purrforce\ copy /B /Y api.h %GITDIR% -copy /B /Y error.c %GITDIR% copy /B /Y ls.bat %GITDIR% -copy /B /Y main.c %GITDIR% +copy /B /Y *.c %GITDIR% +copy /B /Y images %GITDIR% +copy /B /Y tray %GITDIR% copy /B /Y make.bat %GITDIR% copy /B /Y Makefile %GITDIR% copy /B /Y pull_fromgit.bat %GITDIR% diff --git a/systray.c b/systray.c new file mode 100644 index 0000000..4baa2a6 --- /dev/null +++ b/systray.c @@ -0,0 +1,4 @@ +#include "tray/tray.h" + +#include "api.h" + diff --git a/tray.h b/tray.h new file mode 100644 index 0000000..4ba47ea --- /dev/null +++ b/tray.h @@ -0,0 +1,370 @@ +#ifndef TRAY_H +#define TRAY_H + +struct tray_menu; + +struct tray { + char *icon; + struct tray_menu *menu; +}; + +struct tray_menu { + char *text; + int disabled; + int checked; + + void (*cb)(struct tray_menu *); + void *context; + + struct tray_menu *submenu; +}; + +static void tray_update(struct tray *tray); + +#if defined(TRAY_APPINDICATOR) + +#include +#include + +#define TRAY_APPINDICATOR_ID "tray-id" + +static AppIndicator *indicator = NULL; +static int loop_result = 0; + +static void _tray_menu_cb(GtkMenuItem *item, gpointer data) { + (void)item; + struct tray_menu *m = (struct tray_menu *)data; + m->cb(m); +} + +static GtkMenuShell *_tray_menu(struct tray_menu *m) { + GtkMenuShell *menu = (GtkMenuShell *)gtk_menu_new(); + for (; m != NULL && m->text != NULL; m++) { + GtkWidget *item; + if (strcmp(m->text, "-") == 0) { + item = gtk_separator_menu_item_new(); + } else { + if (m->submenu != NULL) { + item = gtk_menu_item_new_with_label(m->text); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), + GTK_WIDGET(_tray_menu(m->submenu))); + } else { + item = gtk_check_menu_item_new_with_label(m->text); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), !!m->checked); + } + gtk_widget_set_sensitive(item, !m->disabled); + if (m->cb != NULL) { + g_signal_connect(item, "activate", G_CALLBACK(_tray_menu_cb), m); + } + } + gtk_widget_show(item); + gtk_menu_shell_append(menu, item); + } + return menu; +} + +static int tray_init(struct tray *tray) { + if (gtk_init_check(0, NULL) == FALSE) { + return -1; + } + indicator = app_indicator_new(TRAY_APPINDICATOR_ID, tray->icon, + APP_INDICATOR_CATEGORY_APPLICATION_STATUS); + app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE); + tray_update(tray); + return 0; +} + +static int tray_loop(int blocking) { + gtk_main_iteration_do(blocking); + return loop_result; +} + +static void tray_update(struct tray *tray) { + app_indicator_set_icon(indicator, tray->icon); + // GTK is all about reference counting, so previous menu should be destroyed + // here + app_indicator_set_menu(indicator, GTK_MENU(_tray_menu(tray->menu))); +} + +static void tray_exit() { loop_result = -1; } + +#elif defined(TRAY_APPKIT) + +#include +#include + +static id app; +static id pool; +static id statusBar; +static id statusItem; +static id statusBarButton; + +static id _tray_menu(struct tray_menu *m) { + id menu = objc_msgSend((id)objc_getClass("NSMenu"), sel_registerName("new")); + objc_msgSend(menu, sel_registerName("autorelease")); + objc_msgSend(menu, sel_registerName("setAutoenablesItems:"), false); + + for (; m != NULL && m->text != NULL; m++) { + if (strcmp(m->text, "-") == 0) { + objc_msgSend(menu, sel_registerName("addItem:"), + objc_msgSend((id)objc_getClass("NSMenuItem"), sel_registerName("separatorItem"))); + } else { + id menuItem = objc_msgSend((id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); + objc_msgSend(menuItem, sel_registerName("autorelease")); + objc_msgSend(menuItem, sel_registerName("initWithTitle:action:keyEquivalent:"), + objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), m->text), + sel_registerName("menuCallback:"), + objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "")); + + objc_msgSend(menuItem, sel_registerName("setEnabled:"), (m->disabled ? false : true)); + objc_msgSend(menuItem, sel_registerName("setState:"), (m->checked ? 1 : 0)); + objc_msgSend(menuItem, sel_registerName("setRepresentedObject:"), + objc_msgSend((id)objc_getClass("NSValue"), sel_registerName("valueWithPointer:"), m)); + + objc_msgSend(menu, sel_registerName("addItem:"), menuItem); + + if (m->submenu != NULL) { + objc_msgSend(menu, sel_registerName("setSubmenu:forItem:"), _tray_menu(m->submenu), menuItem); + } + } + } + + return menu; +} + +static void menu_callback(id self, SEL cmd, id sender) { + struct tray_menu *m = + (struct tray_menu *)objc_msgSend(objc_msgSend(sender, sel_registerName("representedObject")), + sel_registerName("pointerValue")); + + if (m != NULL && m->cb != NULL) { + m->cb(m); + } +} + +static int tray_init(struct tray *tray) { + pool = objc_msgSend((id)objc_getClass("NSAutoreleasePool"), + sel_registerName("new")); + + objc_msgSend((id)objc_getClass("NSApplication"), + sel_registerName("sharedApplication")); + + Class trayDelegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "Tray", 0); + class_addProtocol(trayDelegateClass, objc_getProtocol("NSApplicationDelegate")); + class_addMethod(trayDelegateClass, sel_registerName("menuCallback:"), (IMP)menu_callback, "v@:@"); + objc_registerClassPair(trayDelegateClass); + + id trayDelegate = objc_msgSend((id)trayDelegateClass, + sel_registerName("new")); + + app = objc_msgSend((id)objc_getClass("NSApplication"), + sel_registerName("sharedApplication")); + + objc_msgSend(app, sel_registerName("setDelegate:"), trayDelegate); + + statusBar = objc_msgSend((id)objc_getClass("NSStatusBar"), + sel_registerName("systemStatusBar")); + + statusItem = objc_msgSend(statusBar, sel_registerName("statusItemWithLength:"), -1.0); + + objc_msgSend(statusItem, sel_registerName("retain")); + objc_msgSend(statusItem, sel_registerName("setHighlightMode:"), true); + statusBarButton = objc_msgSend(statusItem, sel_registerName("button")); + tray_update(tray); + objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), true); + return 0; +} + +static int tray_loop(int blocking) { + id until = (blocking ? + objc_msgSend((id)objc_getClass("NSDate"), sel_registerName("distantFuture")) : + objc_msgSend((id)objc_getClass("NSDate"), sel_registerName("distantPast"))); + + id event = objc_msgSend(app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), + ULONG_MAX, + until, + objc_msgSend((id)objc_getClass("NSString"), + sel_registerName("stringWithUTF8String:"), + "kCFRunLoopDefaultMode"), + true); + if (event) { + objc_msgSend(app, sel_registerName("sendEvent:"), event); + } + return 0; +} + +static void tray_update(struct tray *tray) { + objc_msgSend(statusBarButton, sel_registerName("setImage:"), + objc_msgSend((id)objc_getClass("NSImage"), sel_registerName("imageNamed:"), + objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), tray->icon))); + + objc_msgSend(statusItem, sel_registerName("setMenu:"), _tray_menu(tray->menu)); +} + +static void tray_exit() { objc_msgSend(app, sel_registerName("terminate:"), app); } + +#elif defined(TRAY_WINAPI) +#include + +#include + +#define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1) +#define WC_TRAY_CLASS_NAME "TRAY" +#define ID_TRAY_FIRST 1000 + +static WNDCLASSEX wc; +static NOTIFYICONDATA nid; +static HWND hwnd; +static HMENU hmenu = NULL; + +static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) { + switch (msg) { + case WM_CLOSE: + DestroyWindow(hwnd); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + case WM_TRAY_CALLBACK_MESSAGE: + if (lparam == WM_LBUTTONUP || lparam == WM_RBUTTONUP) { + POINT p; + GetCursorPos(&p); + SetForegroundWindow(hwnd); + WORD cmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | + TPM_RETURNCMD | TPM_NONOTIFY, + p.x, p.y, 0, hwnd, NULL); + SendMessage(hwnd, WM_COMMAND, cmd, 0); + return 0; + } + break; + case WM_COMMAND: + if (wparam >= ID_TRAY_FIRST) { + MENUITEMINFO item = { + .cbSize = sizeof(MENUITEMINFO), .fMask = MIIM_ID | MIIM_DATA, + }; + if (GetMenuItemInfo(hmenu, wparam, FALSE, &item)) { + struct tray_menu *menu = (struct tray_menu *)item.dwItemData; + if (menu != NULL && menu->cb != NULL) { + menu->cb(menu); + } + } + return 0; + } + break; + } + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +static HMENU _tray_menu(struct tray_menu *m, UINT *id) { + HMENU hmenu = CreatePopupMenu(); + for (; m != NULL && m->text != NULL; m++, (*id)++) { + if (strcmp(m->text, "-") == 0) { + InsertMenu(hmenu, *id, MF_SEPARATOR, TRUE, ""); + } else { + MENUITEMINFO item; + memset(&item, 0, sizeof(item)); + item.cbSize = sizeof(MENUITEMINFO); + item.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA; + item.fType = 0; + item.fState = 0; + if (m->submenu != NULL) { + item.fMask = item.fMask | MIIM_SUBMENU; + item.hSubMenu = _tray_menu(m->submenu, id); + } + if (m->disabled) { + item.fState |= MFS_DISABLED; + } + if (m->checked) { + item.fState |= MFS_CHECKED; + } + item.wID = *id; + item.dwTypeData = m->text; + item.dwItemData = (ULONG_PTR)m; + + InsertMenuItem(hmenu, *id, TRUE, &item); + } + } + return hmenu; +} + +static int tray_init(struct tray *tray) { + memset(&wc, 0, sizeof(wc)); + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = _tray_wnd_proc; + wc.hInstance = GetModuleHandle(NULL); + wc.lpszClassName = WC_TRAY_CLASS_NAME; + if (!RegisterClassEx(&wc)) { + return -1; + } + + hwnd = CreateWindowEx(0, WC_TRAY_CLASS_NAME, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (hwnd == NULL) { + return -1; + } + UpdateWindow(hwnd); + + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hwnd; + nid.uID = 0; + nid.uFlags = NIF_ICON | NIF_MESSAGE; + nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE; + Shell_NotifyIcon(NIM_ADD, &nid); + + tray_update(tray); + return 0; +} + +static int tray_loop(int blocking) { + MSG msg; + if (blocking) { + GetMessage(&msg, NULL, 0, 0); + } else { + PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + } + if (msg.message == WM_QUIT) { + return -1; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + return 0; +} + +static void tray_update(struct tray *tray) { + HMENU prevmenu = hmenu; + UINT id = ID_TRAY_FIRST; + hmenu = _tray_menu(tray->menu, &id); + SendMessage(hwnd, WM_INITMENUPOPUP, (WPARAM)hmenu, 0); + HICON icon; + ExtractIconEx(tray->icon, 0, NULL, &icon, 1); + if (nid.hIcon) { + DestroyIcon(nid.hIcon); + } + nid.hIcon = icon; + Shell_NotifyIcon(NIM_MODIFY, &nid); + + if (prevmenu != NULL) { + DestroyMenu(prevmenu); + } +} + +static void tray_exit() { + Shell_NotifyIcon(NIM_DELETE, &nid); + if (nid.hIcon != 0) { + DestroyIcon(nid.hIcon); + } + if (hmenu != 0) { + DestroyMenu(hmenu); + } + PostQuitMessage(0); + UnregisterClass(WC_TRAY_CLASS_NAME, GetModuleHandle(NULL)); +} +#else +static int tray_init(struct tray *tray) { return -1; } +static int tray_loop(int blocking) { return -1; } +static void tray_update(struct tray *tray) {} +static void tray_exit(); +#endif + +#endif /* TRAY_H */