SNI/DBus tray support (#15189)

This commit is contained in:
eafton
2026-04-11 00:11:38 +03:00
committed by GitHub
parent 4aa0a6e2bf
commit 8c024f4f3a
10 changed files with 2659 additions and 584 deletions

View File

@@ -22,6 +22,82 @@
#include "./SDL_list.h"
// Append
bool SDL_ListAppend(SDL_ListNode **head, void *ent)
{
SDL_ListNode *cursor;
SDL_ListNode *node;
if (!head) {
return false;
}
node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
if (!node) {
return false;
}
node->entry = ent;
node->next = NULL;
if (*head) {
cursor = *head;
while (cursor->next) {
cursor = cursor->next;
}
cursor->next = node;
} else {
*head = node;
}
return true;
}
bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent)
{
SDL_ListNode *cursor;
SDL_ListNode *node;
int i;
if (pos == -1) {
return SDL_ListAppend(head, ent);
}
if (!pos) {
node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
if (!node) {
return false;
}
node->entry = ent;
if (*head) {
node->next = *head;
} else {
node->next = NULL;
}
*head = node;
}
cursor = *head;
for (i = 1; i < pos - 1 && cursor; i++) {
cursor = cursor->next;
}
if (!cursor) {
return SDL_ListAppend(head, ent);
}
node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
if (!node) {
return false;
}
node->entry = ent;
node->next = cursor->next;
cursor->next = node;
return true;
}
// Push
bool SDL_ListAdd(SDL_ListNode **head, void *ent)
{
@@ -84,3 +160,14 @@ void SDL_ListClear(SDL_ListNode **head)
SDL_free(tmp);
}
}
int SDL_ListCountEntries(SDL_ListNode **head)
{
SDL_ListNode *node;
int count = 0;
for (node = *head; node; node = node->next) {
++count;
}
return count;
}

View File

@@ -28,9 +28,12 @@ typedef struct SDL_ListNode
struct SDL_ListNode *next;
} SDL_ListNode;
bool SDL_ListAppend(SDL_ListNode **head, void *ent);
bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent);
bool SDL_ListAdd(SDL_ListNode **head, void *ent);
void SDL_ListPop(SDL_ListNode **head, void **ent);
void SDL_ListRemove(SDL_ListNode **head, void *ent);
void SDL_ListClear(SDL_ListNode **head);
int SDL_ListCountEntries(SDL_ListNode **head);
#endif // SDL_list_h_

64
src/SDL_menu.h Normal file
View File

@@ -0,0 +1,64 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_menu_h_
#define SDL_menu_h_
#include "./SDL_list.h"
typedef enum SDL_MenuItemType
{
SDL_MENU_ITEM_TYPE_NORMAL,
SDL_MENU_ITEM_TYPE_SEPERATOR,
SDL_MENU_ITEM_TYPE_CHECKBOX
} SDL_MenuItemType;
typedef enum SDL_MenuItemFlags
{
SDL_MENU_ITEM_FLAGS_NONE = 0,
SDL_MENU_ITEM_FLAGS_DISABLED = 1 << 0,
SDL_MENU_ITEM_FLAGS_CHECKED = 1 << 1,
SDL_MENU_ITEM_FLAGS_BAR_ITEM = 1 << 2
} SDL_MenuItemFlags;
/* Do not create this struct directly, users of this structure like the DBUSMENU layer use extended versions of it which need to be allocated by specfic functions. */
/* This struct is meant to be in an SDL_List just like sub_menu */
typedef struct SDL_MenuItem
{
/* Basic properties */
const char *utf8;
SDL_MenuItemType type;
SDL_MenuItemFlags flags;
/* Callback */
void *cb_data;
void (*cb)(struct SDL_MenuItem *, void *);
/* Submenu, set to NULL if none */
SDL_ListNode *sub_menu;
/* User data slots */
void *udata;
void *udata2;
void *udata3;
} SDL_MenuItem;
#endif // SDL_menu_h_

File diff suppressed because it is too large Load Diff

View File

@@ -28,14 +28,20 @@
#define SDL_USE_LIBDBUS 1
#include <dbus/dbus.h>
#include "../../SDL_list.h"
#include "../../SDL_menu.h"
#ifndef DBUS_TIMEOUT_USE_DEFAULT
#define DBUS_TIMEOUT_USE_DEFAULT -1
#endif
#ifndef DBUS_TIMEOUT_INFINITE
#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff)
#define DBUS_TIMEOUT_INFINITE ((int)0x7fffffff)
#endif
#ifndef DBUS_TYPE_UNIX_FD
#define DBUS_TYPE_UNIX_FD ((int) 'h')
#define DBUS_TYPE_UNIX_FD ((int)'h')
#endif
#ifndef DBUS_ERROR_UNKNOWN_PROPERTY
#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
#endif
typedef struct SDL_DBusContext
@@ -95,6 +101,15 @@ typedef struct SDL_DBusContext
void (*free_string_array)(char **);
void (*shutdown)(void);
/* New symbols for SNI and menu export */
int (*bus_request_name)(DBusConnection *, const char *, unsigned int, DBusError *);
dbus_bool_t (*message_is_method_call)(DBusMessage *, const char *, const char *);
DBusMessage *(*message_new_error)(DBusMessage *, const char *, const char *);
DBusMessage *(*message_new_method_return)(DBusMessage *);
dbus_bool_t (*message_iter_append_fixed_array)(DBusMessageIter *, int, const void *, int);
void (*message_iter_get_fixed_array)(DBusMessageIter *, void *, int *);
dbus_bool_t (*connection_unregister_object_path)(DBusConnection *, const char *);
dbus_bool_t (*connection_get_object_path_data)(DBusConnection *, const char *, void **);
} SDL_DBusContext;
extern void SDL_DBus_Init(void);
@@ -128,6 +143,15 @@ extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_
extern int SDL_DBus_CameraPortalRequestAccess(void);
// Menu export functions
#define SDL_DBUS_UPDATE_MENU_FLAGS_NONE 0
extern SDL_MenuItem *SDL_DBus_CreateMenuItem(void);
extern const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu);
extern void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu, const char *path, void (*cb)(SDL_ListNode *, const char *, void *), void *cbdata, unsigned char flags);
extern void SDL_DBus_RegisterMenuOpenCallback(SDL_ListNode *menu, bool (*cb)(SDL_ListNode *, void *), void *cbdata);
extern void SDL_DBus_TransferMenuItemProperties(SDL_MenuItem *src, SDL_MenuItem *dst);
extern void SDL_DBus_RetractMenu(SDL_DBusContext *ctx, DBusConnection *conn, const char **path);
#endif // HAVE_DBUS_DBUS_H
#endif // SDL_dbus_h_

View File

@@ -20,11 +20,10 @@
*/
#include "SDL_internal.h"
#include "../video/SDL_sysvideo.h"
#include "../events/SDL_events_c.h"
#include "../video/SDL_sysvideo.h"
#include "SDL_tray_utils.h"
static int active_trays = 0;
void SDL_RegisterTray(SDL_Tray *tray)
@@ -91,3 +90,8 @@ bool SDL_HasActiveTrays(void)
{
return (active_trays > 0);
}
int SDL_GetActiveTrayCount(void)
{
return active_trays;
}

View File

@@ -26,3 +26,4 @@ extern void SDL_RegisterTray(SDL_Tray *tray);
extern void SDL_UnregisterTray(SDL_Tray *tray);
extern void SDL_CleanupTrays(void);
extern bool SDL_HasActiveTrays(void);
extern int SDL_GetActiveTrayCount(void);

1177
src/tray/unix/SDL_dbustray.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,326 +22,87 @@
#include "SDL_internal.h"
#include "../SDL_tray_utils.h"
#include "../../video/SDL_stb_c.h"
#include "SDL_unixtray.h"
#include <dlfcn.h>
#include <errno.h>
/* getpid() */
#include <unistd.h>
/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been
written nevertheless to make future maintenance easier. */
#ifdef APPINDICATOR_HEADER
#include APPINDICATOR_HEADER
#else
#include "../../core/unix/SDL_gtk.h"
/* ------------------------------------------------------------------------- */
/* BEGIN THIRD-PARTY HEADER CONTENT */
/* ------------------------------------------------------------------------- */
/* AppIndicator */
typedef enum {
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
APP_INDICATOR_CATEGORY_COMMUNICATIONS,
APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
APP_INDICATOR_CATEGORY_HARDWARE,
APP_INDICATOR_CATEGORY_OTHER
} AppIndicatorCategory;
typedef enum {
APP_INDICATOR_STATUS_PASSIVE,
APP_INDICATOR_STATUS_ACTIVE,
APP_INDICATOR_STATUS_ATTENTION
} AppIndicatorStatus;
typedef struct _AppIndicator AppIndicator;
static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name);
static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
/* ------------------------------------------------------------------------- */
/* END THIRD-PARTY HEADER CONTENT */
/* ------------------------------------------------------------------------- */
#endif
static void *libappindicator = NULL;
static void quit_appindicator(void)
{
if (libappindicator) {
dlclose(libappindicator);
libappindicator = NULL;
}
}
const char *appindicator_names[] = {
#ifdef SDL_PLATFORM_OPENBSD
"libayatana-appindicator3.so",
"libappindicator3.so",
#else
"libayatana-appindicator3.so.1",
"libappindicator3.so.1",
#endif
NULL
};
static void *find_lib(const char **names)
{
const char **name_ptr = names;
void *handle = NULL;
do {
handle = dlopen(*name_ptr, RTLD_LAZY);
} while (*++name_ptr && !handle);
return handle;
}
static bool init_appindicator(void)
{
if (libappindicator) {
return true;
}
libappindicator = find_lib(appindicator_names);
if (!libappindicator) {
quit_appindicator();
return SDL_SetError("Could not load AppIndicator libraries");
}
app_indicator_new = dlsym(libappindicator, "app_indicator_new");
app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status");
app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon");
app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu");
if (!app_indicator_new ||
!app_indicator_set_status ||
!app_indicator_set_icon ||
!app_indicator_set_menu) {
quit_appindicator();
return SDL_SetError("Could not load AppIndicator functions");
}
return true;
}
struct SDL_TrayMenu {
GtkMenuShell *menu;
int nEntries;
SDL_TrayEntry **entries;
SDL_Tray *parent_tray;
SDL_TrayEntry *parent_entry;
};
struct SDL_TrayEntry {
SDL_TrayMenu *parent;
GtkWidget *item;
/* Checkboxes are "activated" when programmatically checked/unchecked; this
is a workaround. */
bool ignore_signal;
SDL_TrayEntryFlags flags;
SDL_TrayCallback callback;
void *userdata;
SDL_TrayMenu *submenu;
};
struct SDL_Tray {
AppIndicator *indicator;
SDL_TrayMenu *menu;
char *icon_dir;
char *icon_path;
GtkMenuShell *menu_cached;
};
static void call_callback(GtkMenuItem *item, gpointer ptr)
{
SDL_TrayEntry *entry = ptr;
/* Not needed with AppIndicator, may be needed with other frameworks */
/* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
} */
if (entry->ignore_signal) {
return;
}
if (entry->callback) {
entry->callback(entry->userdata, entry);
}
}
static bool new_tmp_filename(SDL_Tray *tray)
{
static int count = 0;
int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.png", tray->icon_dir, count++);
if (would_have_written >= 0) {
return true;
}
tray->icon_path = NULL;
SDL_SetError("Failed to format new temporary filename");
return false;
}
static const char *get_appindicator_id(void)
{
static int count = 0;
static char buffer[256];
int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++);
if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) {
SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer));
return NULL;
}
return buffer;
}
static void DestroySDLMenu(SDL_TrayMenu *menu)
{
for (int i = 0; i < menu->nEntries; i++) {
if (menu->entries[i] && menu->entries[i]->submenu) {
DestroySDLMenu(menu->entries[i]->submenu);
}
SDL_free(menu->entries[i]);
}
if (menu->menu) {
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
gtk->g.object_unref(menu->menu);
SDL_Gtk_ExitContext(gtk);
}
}
SDL_free(menu->entries);
SDL_free(menu);
}
static SDL_TrayDriver *driver = NULL;
void SDL_UpdateTrays(void)
{
if (SDL_HasActiveTrays()) {
SDL_UpdateGtk();
SDL_Tray **trays;
int active_trays;
int count;
int i;
active_trays = SDL_GetActiveTrayCount();
if (!active_trays) {
return;
}
trays = SDL_calloc(active_trays, sizeof(SDL_Tray *));
if (!trays) {
return;
}
count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, (void **)trays, active_trays);
SDL_assert(count == active_trays);
for (i = 0; i < count; i++) {
if (trays[i]) {
if (SDL_ObjectValid(trays[i], SDL_OBJECT_TYPE_TRAY)) {
trays[i]->driver->UpdateTray(trays[i]);
}
}
}
SDL_free(trays);
}
SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
{
SDL_Tray *tray;
if (!SDL_IsMainThread()) {
SDL_SetError("This function should be called on the main thread");
return NULL;
}
if (!init_appindicator()) {
return NULL;
if (!driver) {
#ifdef SDL_USE_LIBDBUS
driver = SDL_Tray_CreateDBusDriver();
#endif
}
SDL_Surface *icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL);
if (driver) {
tray = driver->CreateTray(driver, props);
SDL_Tray *tray = NULL;
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (!gtk) {
goto tray_error;
}
tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
if (!tray) {
goto tray_error;
}
const gchar *cache_dir = gtk->g.get_user_cache_dir();
if (!cache_dir) {
SDL_SetError("Cannot get user cache directory: %s", strerror(errno));
goto tray_error;
}
char *sdl_dir;
SDL_asprintf(&sdl_dir, "%s/SDL", cache_dir);
if (!SDL_GetPathInfo(sdl_dir, NULL)) {
if (!SDL_CreateDirectory(sdl_dir)) {
SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno));
goto sdl_dir_error;
if (tray) {
SDL_RegisterTray(tray);
}
}
/* On success, g_mkdtemp edits its argument in-place to replace the Xs
* with a random directory name, which it creates safely and atomically.
* On failure, it sets errno. */
SDL_asprintf(&tray->icon_dir, "%s/tray-XXXXXX", sdl_dir);
if (!gtk->g.mkdtemp(tray->icon_dir)) {
SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno));
goto icon_dir_error;
}
if (icon) {
if (!new_tmp_filename(tray)) {
goto icon_dir_error;
}
SDL_SavePNG(icon, tray->icon_path);
} else {
// allocate a dummy icon path
SDL_asprintf(&tray->icon_path, " ");
tray = NULL;
}
tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path,
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
// The tray icon isn't shown before a menu is created; create one early.
tray->menu_cached = (GtkMenuShell *)gtk->g.object_ref_sink(gtk->gtk.menu_new());
app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached));
SDL_RegisterTray(tray);
SDL_Gtk_ExitContext(gtk);
SDL_free(sdl_dir);
return tray;
icon_dir_error:
SDL_free(tray->icon_dir);
sdl_dir_error:
SDL_free(sdl_dir);
tray_error:
SDL_free(tray);
if (gtk) {
SDL_Gtk_ExitContext(gtk);
}
return NULL;
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
SDL_Tray *tray;
SDL_PropertiesID props = SDL_CreateProperties();
SDL_PropertiesID props;
props = SDL_CreateProperties();
if (!props) {
return NULL;
}
if (icon) {
SDL_SetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, icon);
}
if (tooltip) {
SDL_SetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, tooltip);
}
tray = SDL_CreateTrayWithProperties(props);
SDL_DestroyProperties(props);
return tray;
@@ -349,65 +110,41 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY))
{
SDL_InvalidParamError("tray");
return;
}
if (tray->icon_path) {
SDL_RemovePath(tray->icon_path);
SDL_free(tray->icon_path);
tray->icon_path = NULL;
}
/* AppIndicator caches the icon files; always change filename to avoid caching */
if (icon && new_tmp_filename(tray)) {
SDL_SavePNG(icon, tray->icon_path);
app_indicator_set_icon(tray->indicator, tray->icon_path);
} else {
SDL_free(tray->icon_path);
tray->icon_path = NULL;
app_indicator_set_icon(tray->indicator, NULL);
}
tray->driver->SetTrayIcon(tray, icon);
}
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
{
/* AppIndicator provides no tooltip support. */
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY))
{
SDL_InvalidParamError("tray");
return;
}
tray->driver->SetTrayTooltip(tray, tooltip);
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY))
{
SDL_InvalidParamError("tray");
return NULL;
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (!gtk) {
return NULL;
}
tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
if (!tray->menu) {
SDL_Gtk_ExitContext(gtk);
return NULL;
}
tray->menu->menu = gtk->g.object_ref(tray->menu_cached);
tray->menu->parent_tray = tray;
tray->menu->parent_entry = NULL;
tray->menu->nEntries = 0;
tray->menu->entries = NULL;
SDL_Gtk_ExitContext(gtk);
return tray->menu;
return tray->driver->CreateTrayMenu(tray);
}
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY))
{
SDL_InvalidParamError("tray");
return NULL;
}
@@ -417,314 +154,163 @@ SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
if (entry->submenu) {
SDL_SetError("Tray entry submenu already exists");
return NULL;
}
if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
return NULL;
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (!gtk) {
return NULL;
}
entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu));
if (!entry->submenu) {
SDL_Gtk_ExitContext(gtk);
return NULL;
}
entry->submenu->menu = gtk->g.object_ref_sink(gtk->gtk.menu_new());
entry->submenu->parent_tray = NULL;
entry->submenu->parent_entry = entry;
entry->submenu->nEntries = 0;
entry->submenu->entries = NULL;
gtk->gtk.menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu));
SDL_Gtk_ExitContext(gtk);
return entry->submenu;
return entry->parent->parent_tray->driver->CreateTraySubmenu(entry);
}
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
return entry->submenu;
return entry->parent->parent_tray->driver->GetTraySubmenu(entry);
}
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
{
CHECK_PARAM(!menu) {
CHECK_PARAM(!menu)
{
SDL_InvalidParamError("menu");
return NULL;
}
if (count) {
*count = menu->nEntries;
CHECK_PARAM(!count)
{
SDL_InvalidParamError("count");
return NULL;
}
return (const SDL_TrayEntry **)menu->entries;
return (const SDL_TrayEntry **)menu->parent_tray->driver->GetTrayEntries(menu, count);
}
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
{
if (!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return;
}
SDL_TrayMenu *menu = entry->parent;
bool found = false;
for (int i = 0; i < menu->nEntries - 1; i++) {
if (menu->entries[i] == entry) {
found = true;
}
if (found) {
menu->entries[i] = menu->entries[i + 1];
}
}
if (entry->submenu) {
DestroySDLMenu(entry->submenu);
}
menu->nEntries--;
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
/* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
if (new_entries) {
menu->entries = new_entries;
menu->entries[menu->nEntries] = NULL;
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
gtk->gtk.widget_destroy(entry->item);
SDL_Gtk_ExitContext(gtk);
}
SDL_free(entry);
entry->parent->parent_tray->driver->RemoveTrayEntry(entry);
}
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
{
CHECK_PARAM(!menu) {
CHECK_PARAM(!menu)
{
SDL_InvalidParamError("menu");
return NULL;
}
CHECK_PARAM(pos < -1 || pos > menu->nEntries) {
SDL_InvalidParamError("pos");
return NULL;
}
if (pos == -1) {
pos = menu->nEntries;
}
SDL_TrayEntry *entry = NULL;
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (!gtk) {
goto error;
}
entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
if (!entry) {
goto error;
}
entry->parent = menu;
entry->item = NULL;
entry->ignore_signal = false;
entry->flags = flags;
entry->callback = NULL;
entry->userdata = NULL;
entry->submenu = NULL;
if (label == NULL) {
entry->item = gtk->gtk.separator_menu_item_new();
} else if (flags & SDL_TRAYENTRY_CHECKBOX) {
entry->item = gtk->gtk.check_menu_item_new_with_label(label);
gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0);
gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active);
} else {
entry->item = gtk->gtk.menu_item_new_with_label(label);
}
gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0);
gtk->gtk.widget_set_sensitive(entry->item, sensitive);
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
if (!new_entries) {
goto error;
}
menu->entries = new_entries;
menu->nEntries++;
for (int i = menu->nEntries - 1; i > pos; i--) {
menu->entries[i] = menu->entries[i - 1];
}
new_entries[pos] = entry;
new_entries[menu->nEntries] = NULL;
gtk->gtk.widget_show(entry->item);
gtk->gtk.menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos);
gtk->g.signal_connect(entry->item, "activate", call_callback, entry);
SDL_Gtk_ExitContext(gtk);
return entry;
error:
if (entry) {
SDL_free(entry);
}
if (gtk) {
SDL_Gtk_ExitContext(gtk);
}
return NULL;
return menu->parent_tray->driver->InsertTrayEntryAt(menu, pos, label, flags);
}
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
{
if (!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return;
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
gtk->gtk.menu_item_set_label(GTK_MENU_ITEM(entry->item), label);
SDL_Gtk_ExitContext(gtk);
}
entry->parent->parent_tray->driver->SetTrayEntryLabel(entry, label);
}
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
const char *label = NULL;
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
label = gtk->gtk.menu_item_get_label(GTK_MENU_ITEM(entry->item));
SDL_Gtk_ExitContext(gtk);
}
return label;
return entry->parent->parent_tray->driver->GetTrayEntryLabel(entry);
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
{
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return;
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
entry->ignore_signal = true;
gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked);
entry->ignore_signal = false;
SDL_Gtk_ExitContext(gtk);
}
entry->parent->parent_tray->driver->SetTrayEntryChecked(entry, checked);
}
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
return false;
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
bool checked = false;
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
checked = gtk->gtk.check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item));
SDL_Gtk_ExitContext(gtk);
}
return checked;
return entry->parent->parent_tray->driver->GetTrayEntryChecked(entry);
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
{
if (!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return;
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
gtk->gtk.widget_set_sensitive(entry->item, enabled);
SDL_Gtk_ExitContext(gtk);
}
entry->parent->parent_tray->driver->SetTrayEntryEnabled(entry, enabled);
}
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
if (!entry) {
return false;
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
bool enabled = false;
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
enabled = gtk->gtk.widget_get_sensitive(entry->item);
SDL_Gtk_ExitContext(gtk);
}
return enabled;
return entry->parent->parent_tray->driver->GetTrayEntryEnabled(entry);
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
if (!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return;
}
entry->callback = callback;
entry->userdata = userdata;
CHECK_PARAM(!callback)
{
SDL_InvalidParamError("callback");
return;
}
entry->parent->parent_tray->driver->SetTrayEntryCallback(entry, callback, userdata);
}
void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
{
if (!entry) {
return;
}
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return;
}
if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
}
if (entry->callback) {
entry->callback(entry->userdata, entry);
}
entry->parent->parent_tray->driver->GetTrayEntryEnabled(entry);
}
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
CHECK_PARAM(!entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
@@ -734,12 +320,19 @@ SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
{
CHECK_PARAM(!menu)
{
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_entry;
}
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
{
CHECK_PARAM(!menu) {
CHECK_PARAM(!menu)
{
SDL_InvalidParamError("menu");
return NULL;
}
@@ -754,33 +347,10 @@ void SDL_DestroyTray(SDL_Tray *tray)
}
SDL_UnregisterTray(tray);
tray->driver->DestroyTray(tray);
if (tray->menu) {
DestroySDLMenu(tray->menu);
if (!SDL_GetActiveTrayCount()) {
driver->DestroyDriver(driver);
driver = NULL;
}
if (tray->icon_path) {
SDL_RemovePath(tray->icon_path);
SDL_free(tray->icon_path);
}
if (tray->icon_dir) {
SDL_RemovePath(tray->icon_dir);
SDL_free(tray->icon_dir);
}
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (gtk) {
if (tray->menu_cached) {
gtk->g.object_unref(tray->menu_cached);
}
if (tray->indicator) {
gtk->g.object_unref(tray->indicator);
}
SDL_Gtk_ExitContext(gtk);
}
SDL_free(tray);
}

View File

@@ -0,0 +1,81 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_unixtray_h_
#define SDL_unixtray_h_
#include "../../core/linux/SDL_dbus.h"
#include "../SDL_tray_utils.h"
typedef struct SDL_TrayDriver
{
const char *name;
unsigned int count;
SDL_Tray *(*CreateTray)(struct SDL_TrayDriver *, SDL_PropertiesID);
void (*DestroyTray)(SDL_Tray *);
void (*UpdateTray)(SDL_Tray *);
void (*SetTrayIcon)(SDL_Tray *, SDL_Surface *);
void (*SetTrayTooltip)(SDL_Tray *, const char *);
SDL_TrayMenu *(*CreateTrayMenu)(SDL_Tray *);
SDL_TrayEntry *(*InsertTrayEntryAt)(SDL_TrayMenu *, int, const char *, SDL_TrayEntryFlags);
SDL_TrayMenu *(*CreateTraySubmenu)(SDL_TrayEntry *);
SDL_TrayMenu *(*GetTraySubmenu)(SDL_TrayEntry *);
SDL_TrayEntry **(*GetTrayEntries)(SDL_TrayMenu *, int *);
void (*RemoveTrayEntry)(SDL_TrayEntry *);
void (*SetTrayEntryLabel)(SDL_TrayEntry *, const char *);
const char *(*GetTrayEntryLabel)(SDL_TrayEntry *);
void (*SetTrayEntryChecked)(SDL_TrayEntry *, bool);
void (*ClickTrayEntry)(SDL_TrayEntry *);
bool (*GetTrayEntryChecked)(SDL_TrayEntry *);
void (*SetTrayEntryEnabled)(SDL_TrayEntry *, bool);
bool (*GetTrayEntryEnabled)(SDL_TrayEntry *);
void (*SetTrayEntryCallback)(SDL_TrayEntry *, SDL_TrayCallback, void *);
void (*DestroyDriver)(struct SDL_TrayDriver *);
} SDL_TrayDriver;
struct SDL_TrayMenu
{
SDL_Tray *parent_tray;
SDL_TrayEntry *parent_entry;
};
struct SDL_TrayEntry
{
SDL_TrayMenu *parent;
};
struct SDL_Tray
{
SDL_TrayDriver *driver;
SDL_TrayMenu *menu;
};
#ifdef SDL_USE_LIBDBUS
extern SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void);
#endif
#endif // SDL_unixtray_h_