Forked from
leap / bitmask-vpn
1015 commits behind the upstream repository.
systray_linux.c 5.99 KiB
#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 char temp_file_name[PATH_MAX] = "";
typedef struct {
GtkWidget *menu_item;
int menu_id;
} MenuItemNode;
typedef struct {
int 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));
systray_ready();
gtk_main();
systray_on_exit();
return 0;
}
void _unlink_temp_file() {
if (strlen(temp_file_name) != 0) {
int ret = unlink(temp_file_name);
if (ret == -1) {
printf("failed to remove temp icon file %s: %s\n", temp_file_name, strerror(errno));
}
temp_file_name[0] = '\0';
}
}
// runs in main thread, should always return FALSE to prevent gtk to execute it again
gboolean do_set_icon(gpointer data) {
_unlink_temp_file();
char *tmpdir = getenv("TMPDIR");
if (NULL == tmpdir) {
tmpdir = "/tmp";
}
strncpy(temp_file_name, tmpdir, PATH_MAX-1);
strncat(temp_file_name, "/systray_XXXXXX", PATH_MAX-1);
temp_file_name[PATH_MAX-1] = '\0';
GBytes* bytes = (GBytes*)data;
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;
}
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;
}
void _systray_menu_item_selected(int *id) {
systray_menu_item_selected(*id);
}
// 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(item->menu_id == mii->menu_id){
gtk_menu_item_set_label(GTK_MENU_ITEM(item->menu_item), mii->title);
break;
}
}
// menu id doesn't exist, add new item
if(it == NULL) {
GtkWidget *menu_item = gtk_menu_item_new_with_label(mii->title);
int *id = malloc(sizeof(int));
*id = mii->menu_id;
g_signal_connect_swapped(G_OBJECT(menu_item), "activate", G_CALLBACK(_systray_menu_item_selected), 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(menu_item);
free(mii->title);
free(mii->tooltip);
free(mii);
return FALSE;
}
gboolean do_add_separator(gpointer data) {
GtkWidget *separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(global_tray_menu), separator);
gtk_widget_show(separator);
return FALSE;
}
// runs in main thread, should always return FALSE to prevent gtk to execute it again
gboolean do_hide_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(item->menu_id == mii->menu_id){
gtk_widget_hide(GTK_WIDGET(item->menu_item));
break;
}
}
return FALSE;
}
// runs in main thread, should always return FALSE to prevent gtk to execute it again
gboolean do_show_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(item->menu_id == mii->menu_id){
gtk_widget_show(GTK_WIDGET(item->menu_item));
break;
}
}
return FALSE;
}
// runs in main thread, should always return FALSE to prevent gtk to execute it again
gboolean do_quit(gpointer data) {
_unlink_temp_file();
// app indicator doesn't provide a way to remove it, hide it as a workaround
app_indicator_set_status(global_app_indicator, APP_INDICATOR_STATUS_PASSIVE);
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(int 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 add_separator(int menu_id) {
MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
mii->menu_id = menu_id;
g_idle_add(do_add_separator, mii);
}
void hide_menu_item(int menu_id) {
MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
mii->menu_id = menu_id;
g_idle_add(do_hide_menu_item, mii);
}
void show_menu_item(int menu_id) {
MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
mii->menu_id = menu_id;
g_idle_add(do_show_menu_item, mii);
}
void quit() {
g_idle_add(do_quit, NULL);
}