First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -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.
|
||||
@@ -0,0 +1,47 @@
|
||||
ifeq ($(OS),Windows_NT)
|
||||
CC=gcc
|
||||
RM=del /q
|
||||
LIB_NAME=zt_desktop_tray.lib
|
||||
TRAY_LDFLAGS :=
|
||||
ifeq ($(ZT_OFFICIAL_RELEASE),1)
|
||||
OPT_FLAGS := -O
|
||||
else
|
||||
OPT_FLAGS := -Og
|
||||
endif
|
||||
ifeq ($(WIN_32BIT),1)
|
||||
TRAY_CFLAGS := $(OPT_FLAGS) -DTRAY_WINAPI=1 -std=c99 -m32 -fno-exceptions -static
|
||||
else
|
||||
TRAY_CFLAGS := $(OPT_FLAGS) -DTRAY_WINAPI=1 -std=c99 -m64 -fno-exceptions -static
|
||||
endif
|
||||
else ifeq ($(shell uname -s),Linux)
|
||||
RM=rm -f
|
||||
LIB_NAME=libzt_desktop_tray.a
|
||||
ifeq ($(ZT_OFFICIAL_RELEASE),1)
|
||||
OPT_FLAGS := -O
|
||||
else
|
||||
OPT_FLAGS := -Og
|
||||
endif
|
||||
TRAY_CFLAGS := $(OPT_FLAGS) -DTRAY_APPINDICATOR=1 $(shell pkg-config --cflags appindicator3-0.1) -std=c99
|
||||
TRAY_LDFLAGS := $(shell pkg-config --libs appindicator3-0.1)
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
RM=rm -f
|
||||
LIB_NAME=libzt_desktop_tray.a
|
||||
ifeq ($(ZT_OFFICIAL_RELEASE),1)
|
||||
OPT_FLAGS := -Os
|
||||
else
|
||||
OPT_FLAGS := -Og
|
||||
endif
|
||||
TRAY_CFLAGS := $(OPT_FLAGS) -arch x86_64 -arch arm64 -DTRAY_APPKIT=1 -std=c99 -mmacosx-version-min=10.13
|
||||
TRAY_LDFLAGS := -framework Cocoa
|
||||
endif
|
||||
|
||||
CFLAGS := $(TRAY_CFLAGS)
|
||||
LDFLAGS := $(TRAY_LDFLAGS)
|
||||
|
||||
all: zt_lib
|
||||
|
||||
zt_lib: zt_desktop_tray.o
|
||||
ar rcs ${LIB_NAME} zt_desktop_tray.o
|
||||
|
||||
clean:
|
||||
-$(RM) *.o *.a example *.exe *.lib
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
Tray
|
||||
----
|
||||
|
||||
Cross-platform, single header, super tiny C99 implementation of a system tray icon with a popup menu.
|
||||
|
||||
Works well on:
|
||||
|
||||
* Linux/Gtk (libappindicator)
|
||||
* Windows XP or newer (shellapi.h)
|
||||
* MacOS (Cocoa/AppKit)
|
||||
|
||||
There is also a stub implementation that returns errors on attempt to create a tray menu.
|
||||
|
||||
# Setup
|
||||
|
||||
Before you can compile `tray`, you'll need to add an environment definition before the line where you include `tray.h`.
|
||||
|
||||
**For Windows:**
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TRAY_WINAPI 1
|
||||
|
||||
#include "tray.h"
|
||||
...
|
||||
```
|
||||
|
||||
**For Linux:**
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TRAY_APPINDICATOR 1
|
||||
|
||||
#include "tray.h"
|
||||
...
|
||||
```
|
||||
|
||||
**For Mac:**
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TRAY_APPKIT 1
|
||||
|
||||
#include "tray.h"
|
||||
...
|
||||
```
|
||||
|
||||
# Demo
|
||||
|
||||
The included example `.c` files can be compiled based on your environment.
|
||||
|
||||
For example, to compile and run the program on Windows:
|
||||
|
||||
```shell
|
||||
$> gcc example_windows.c [Enter]
|
||||
```
|
||||
|
||||
This will compile and build `a.out`. To run it:
|
||||
|
||||
```
|
||||
$> a [Enter]
|
||||
```
|
||||
|
||||
# Example
|
||||
|
||||
```c
|
||||
struct tray tray = {
|
||||
.icon = "icon.png",
|
||||
.menu = (struct tray_menu[]){{"Toggle me", 0, 0, toggle_cb, NULL},
|
||||
{"-", 0, 0, NULL, NULL},
|
||||
{"Quit", 0, 0, quit_cb, NULL},
|
||||
{NULL, 0, 0, NULL, NULL}},
|
||||
};
|
||||
|
||||
void toggle_cb(struct tray_menu *item) {
|
||||
item->checked = !item->checked;
|
||||
tray_update(&tray);
|
||||
}
|
||||
|
||||
void quit_cb(struct tray_menu *item) {
|
||||
tray_exit();
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
tray_init(&tray);
|
||||
while (tray_loop(1) == 0);
|
||||
tray_exit();
|
||||
|
||||
```
|
||||
|
||||
# API
|
||||
|
||||
Tray structure defines an icon and a menu.
|
||||
Menu is a NULL-terminated array of items.
|
||||
Menu item defines menu text, menu checked and disabled (grayed) flags and a
|
||||
callback with some optional context pointer.
|
||||
|
||||
```c
|
||||
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;
|
||||
};
|
||||
```
|
||||
|
||||
* `int tray_init(struct tray *)` - creates tray icon. Returns -1 if tray icon/menu can't be created.
|
||||
* `void tray_update(struct tray *)` - updates tray icon and menu.
|
||||
* `int tray_loop(int blocking)` - runs one iteration of the UI loop. Returns -1 if `tray_exit()` has been called.
|
||||
* `void tray_exit()` - terminates UI loop.
|
||||
|
||||
All functions are meant to be called from the UI thread only.
|
||||
|
||||
Menu arrays must be terminated with a NULL item, e.g. the last item in the
|
||||
array must have text field set to NULL.
|
||||
|
||||
## Roadmap
|
||||
|
||||
* [x] Cross-platform tray icon
|
||||
* [x] Cross-platform tray popup menu
|
||||
* [x] Separators in the menu
|
||||
* [x] Disabled/enabled menu items
|
||||
* [x] Checked/unchecked menu items
|
||||
* [x] Nested menus
|
||||
* [ ] Icons for menu items
|
||||
* [x] Rewrite ObjC code in C using ObjC Runtime (now ObjC code breaks many linters and static analyzers)
|
||||
* [ ] Call GTK code using dlopen/dlsym (to make binaries run safely if Gtk libraries are not available)
|
||||
|
||||
## License
|
||||
|
||||
This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php),
|
||||
so feel free to integrate it in your commercial products.
|
||||
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
#ifndef TRAY_H
|
||||
#define TRAY_H
|
||||
|
||||
/* Max time to block in tray_loop() */
|
||||
#define MAX_WAIT_SECONDS 5
|
||||
|
||||
struct tray_menu;
|
||||
|
||||
struct tray
|
||||
{
|
||||
char *icon;
|
||||
struct tray_menu *menu;
|
||||
};
|
||||
|
||||
struct tray_menu
|
||||
{
|
||||
char *text;
|
||||
wchar_t *wtext; /* Windows only */
|
||||
int disabled;
|
||||
int checked;
|
||||
|
||||
void (*cb)(struct tray_menu *);
|
||||
|
||||
void *context;
|
||||
|
||||
struct tray_menu *submenu;
|
||||
};
|
||||
|
||||
void tray_update(struct tray *tray);
|
||||
|
||||
#if defined(TRAY_APPINDICATOR)
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <libappindicator/app-indicator.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int tray_loop(int blocking) {
|
||||
gtk_main_iteration_do(blocking);
|
||||
return loop_result;
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
void tray_exit() { loop_result = -1; }
|
||||
|
||||
#elif defined(TRAY_APPKIT)
|
||||
|
||||
#include <objc/objc-runtime.h>
|
||||
#include <limits.h>
|
||||
|
||||
static id app = NULL;
|
||||
static id statusBar = NULL;
|
||||
static id statusItem = NULL;
|
||||
static id statusBarButton = NULL;
|
||||
static id rootMenu = NULL;
|
||||
static id kCFRunLoopDefaultMode = NULL;
|
||||
|
||||
static id _tray_menu(struct tray_menu *m, id menu) {
|
||||
if (menu) {
|
||||
((void(*)(id, SEL))objc_msgSend)(menu, sel_registerName("removeAllItems"));
|
||||
} else {
|
||||
menu = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSMenu"), sel_registerName("new"));
|
||||
menu = ((id(*)(id, SEL))objc_msgSend)(menu, sel_registerName("autorelease"));
|
||||
((void(*)(id, SEL, bool))objc_msgSend)(menu, sel_registerName("setAutoenablesItems:"), false);
|
||||
}
|
||||
|
||||
for (; m != NULL && m->text != NULL; m++) {
|
||||
if (strcmp(m->text, "-") == 0) {
|
||||
id menuItem = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSMenuItem"), sel_registerName("separatorItem"));
|
||||
menuItem = ((id(*)(id, SEL))objc_msgSend)(menuItem, sel_registerName("autorelease"));
|
||||
((void(*)(id, SEL, id))objc_msgSend)(menu, sel_registerName("addItem:"), menuItem);
|
||||
} else {
|
||||
id menuItem = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSMenuItem"), sel_registerName("alloc"));
|
||||
((void(*)(id, SEL))objc_msgSend)(menuItem, sel_registerName("autorelease"));
|
||||
((void(*)(id, SEL, id, SEL, id))objc_msgSend)(
|
||||
menuItem,
|
||||
sel_registerName("initWithTitle:action:keyEquivalent:"),
|
||||
((id(*)(id, SEL, char *))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), m->text),
|
||||
sel_registerName("menuCallback:"),
|
||||
((id(*)(id, SEL, char *))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), ""));
|
||||
((void(*)(id, SEL, bool))objc_msgSend)(menuItem, sel_registerName("setEnabled:"), (m->disabled ? false : true));
|
||||
((void(*)(id, SEL, int))objc_msgSend)(menuItem, sel_registerName("setState:"), (m->checked ? 1 : 0));
|
||||
((void(*)(id, SEL, id))objc_msgSend)(menuItem, sel_registerName("setRepresentedObject:"),
|
||||
((id(*)(id, SEL, struct tray_menu*))objc_msgSend)((id)objc_getClass("NSValue"), sel_registerName("valueWithPointer:"), m));
|
||||
((void(*)(id, SEL, id))objc_msgSend)(menu, sel_registerName("addItem:"), menuItem);
|
||||
if (m->submenu != NULL) {
|
||||
((void(*)(id, SEL, id, id))objc_msgSend)(menu, sel_registerName("setSubmenu:forItem:"), _tray_menu(m->submenu, NULL), menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
static void menu_callback(id self, SEL cmd, id sender) {
|
||||
id asdf = ((id(*)(id, SEL))objc_msgSend)(sender, sel_registerName("representedObject"));
|
||||
struct tray_menu *m = ((struct tray_menu*(*)(id, SEL))objc_msgSend)(asdf,sel_registerName("pointerValue"));
|
||||
if (m != NULL && m->cb != NULL) {
|
||||
m->cb(m);
|
||||
}
|
||||
}
|
||||
|
||||
int tray_init(struct tray *tray) {
|
||||
kCFRunLoopDefaultMode = ((id(*)(id, SEL, char*))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "kCFRunLoopDefaultMode");
|
||||
|
||||
((void(*)(id, SEL))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 = ((id(*)(id, SEL))objc_msgSend)((id)trayDelegateClass, sel_registerName("new"));
|
||||
|
||||
app = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication"));
|
||||
((void(*)(id, SEL,id))objc_msgSend)(app, sel_registerName("setDelegate:"), trayDelegate);
|
||||
|
||||
statusBar = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSStatusBar"), sel_registerName("systemStatusBar"));
|
||||
statusItem = ((id(*)(id, SEL, double))objc_msgSend)(statusBar, sel_registerName("statusItemWithLength:"), -1.0);
|
||||
((void(*)(id, SEL))objc_msgSend)(statusItem, sel_registerName("retain"));
|
||||
((void(*)(id, SEL, bool))objc_msgSend)(statusItem, sel_registerName("setHighlightMode:"), true);
|
||||
statusBarButton = ((id(*)(id, SEL))objc_msgSend)(statusItem, sel_registerName("button"));
|
||||
|
||||
tray_update(tray);
|
||||
/*((void(*)(id, SEL, bool))objc_msgSend)(app, sel_registerName("activateIgnoringOtherApps:"), true);*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tray_loop(int blocking) {
|
||||
id pool = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSAutoreleasePool"), sel_registerName("new"));
|
||||
|
||||
id until;
|
||||
if (blocking) {
|
||||
until = ((id(*)(id, SEL, double))objc_msgSend)((id)objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), (double)MAX_WAIT_SECONDS);
|
||||
} else {
|
||||
until = ((id(*)(id, SEL))objc_msgSend)((id)objc_getClass("NSDate"), sel_registerName("distantPast"));
|
||||
}
|
||||
|
||||
id event = ((id(*)(id, SEL, unsigned long, id, id, bool))objc_msgSend)(app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), ULONG_MAX, until, kCFRunLoopDefaultMode, true);
|
||||
if (event) {
|
||||
((void(*)(id, SEL, id))objc_msgSend)(app, sel_registerName("sendEvent:"), event);
|
||||
}
|
||||
|
||||
((void(*)(id, SEL))objc_msgSend)(pool, sel_registerName("drain"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tray_update(struct tray *tray) {
|
||||
if (tray->icon) {
|
||||
((void(*)(id, SEL, id))objc_msgSend)(statusBarButton, sel_registerName("setImage:"), ((id(*)(id, SEL, id))objc_msgSend)((id)objc_getClass("NSImage"), sel_registerName("imageNamed:"), ((id(*)(id, SEL, char *))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), tray->icon)));
|
||||
}
|
||||
id wasMenuSet = rootMenu;
|
||||
rootMenu = _tray_menu(tray->menu, rootMenu);
|
||||
if (!wasMenuSet)
|
||||
((void(*)(id, SEL, id))objc_msgSend)(statusItem, sel_registerName("setMenu:"), rootMenu);
|
||||
}
|
||||
|
||||
void tray_exit() {
|
||||
((void(*)(id, SEL, id))objc_msgSend)(app, sel_registerName("terminate:"), app);
|
||||
rootMenu = NULL;
|
||||
}
|
||||
|
||||
#elif defined(TRAY_WINAPI)
|
||||
|
||||
#define UNICODE
|
||||
#define _UNICODE
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <Shobjidl.h>
|
||||
|
||||
#define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1)
|
||||
#define WC_TRAY_CLASS_NAME L"com.zerotier.zerotier.DesktopUI"
|
||||
#define ID_TRAY_FIRST 1000
|
||||
|
||||
static WNDCLASSEX wc;
|
||||
static NOTIFYICONDATA nid;
|
||||
static HWND hwnd;
|
||||
static HMENU hmenu = NULL;
|
||||
static UINT_PTR window_timer = 0;
|
||||
|
||||
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->wtext != NULL; m++, (*id)++) {
|
||||
if (!wcscmp(m->wtext, L"-")) {
|
||||
InsertMenuW(hmenu, *id, MF_SEPARATOR, TRUE, L"");
|
||||
} else {
|
||||
MENUITEMINFOW item;
|
||||
memset(&item, 0, sizeof(item));
|
||||
item.cbSize = sizeof(MENUITEMINFOW);
|
||||
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->wtext;
|
||||
item.dwItemData = (ULONG_PTR)m;
|
||||
InsertMenuItemW(hmenu, *id, TRUE, &item);
|
||||
}
|
||||
}
|
||||
return hmenu;
|
||||
}
|
||||
|
||||
SHSTDAPI SetCurrentProcessExplicitAppUserModelID(PCWSTR AppID);
|
||||
|
||||
int tray_init(struct tray *tray) {
|
||||
SetCurrentProcessExplicitAppUserModelID(L"com.zerotier.zerotier.DesktopUI");
|
||||
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 = CreateWindowExW(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);
|
||||
|
||||
window_timer = SetTimer(NULL, 0, (MAX_WAIT_SECONDS * 1000), NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
ExtractIconExA(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);
|
||||
}
|
||||
}
|
||||
|
||||
void tray_exit() {
|
||||
if (window_timer != 0)
|
||||
KillTimer(NULL, window_timer);
|
||||
window_timer = 0;
|
||||
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
|
||||
|
||||
int tray_init(struct tray *tray)
|
||||
{ return -1; }
|
||||
|
||||
int tray_loop(int blocking)
|
||||
{ return -1; }
|
||||
|
||||
void tray_update(struct tray *tray)
|
||||
{}
|
||||
|
||||
void tray_exit();
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* TRAY_H */
|
||||
@@ -0,0 +1,99 @@
|
||||
/* This builds the tray and some assorted utility functions so that it can be included from Rust code. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pthread.h>
|
||||
#include <pthread/qos.h>
|
||||
#include <sys/qos.h>
|
||||
extern void c_set_this_thread_to_background_priority()
|
||||
{
|
||||
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
|
||||
}
|
||||
extern void c_set_this_thread_to_foreground_priority()
|
||||
{
|
||||
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined (_WIN32) || defined (_WIN64)
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
/*
|
||||
#define RELEASEKEY(Key) if (Key) RegCloseKey(Key);
|
||||
extern int c_windows_is_dark_theme()
|
||||
{
|
||||
HKEY hKey = NULL;
|
||||
DWORD Value1 = 0;
|
||||
DWORD Value2 = 0;
|
||||
DWORD size = 0;
|
||||
if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey)) {
|
||||
return 0;
|
||||
}
|
||||
if (RegQueryValueExA(hKey, "AppsUseLightTheme", NULL, NULL, (LPBYTE)&Value1, &size)) {
|
||||
if (RegQueryValueExA(hKey, "SystemUsesLightTheme", NULL, NULL, (LPBYTE)&Value2, &size)) {
|
||||
RELEASEKEY(hKey);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
RELEASEKEY(hKey);
|
||||
return ((Value1 | Value2) == 0) ? 1 : 0;
|
||||
}
|
||||
*/
|
||||
extern void c_windows_post_to_clipboard(const char *const data)
|
||||
{
|
||||
if (!data)
|
||||
return;
|
||||
const size_t len = strlen(data) + 1;
|
||||
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len);
|
||||
memcpy(GlobalLock(hMem), data, len);
|
||||
GlobalUnlock(hMem);
|
||||
OpenClipboard(0);
|
||||
EmptyClipboard();
|
||||
SetClipboardData(CF_TEXT, hMem);
|
||||
CloseClipboard();
|
||||
}
|
||||
extern unsigned int c_windows_get_from_clipboard(char *buf, unsigned int len)
|
||||
{
|
||||
if ((!buf)||(!len))
|
||||
return 0;
|
||||
buf[0] = 0;
|
||||
OpenClipboard(0);
|
||||
HANDLE hData = GetClipboardData(CF_TEXT);
|
||||
if (hData != NULL) {
|
||||
LPTSTR lptstr = GlobalLock(hData);
|
||||
if (lptstr != NULL) {
|
||||
int rl = (int)WideCharToMultiByte(CP_UTF8, 0, lptstr, -1, buf, (int)len, NULL, NULL);
|
||||
GlobalUnlock(lptstr);
|
||||
buf[len - 1] = 0;
|
||||
return (rl > 0) ? (unsigned int)rl : 0;
|
||||
}
|
||||
}
|
||||
CloseClipboard();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
extern void c_lock_down_file(const char *path, int is_dir)
|
||||
{
|
||||
#if defined (_WIN32) || defined (_WIN64)
|
||||
abort();
|
||||
#else
|
||||
chmod(path,is_dir ? 0700 : 0600);
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user