SDL_tray.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "SDL_internal.h"
  19. #include "../SDL_tray_utils.h"
  20. #include <dlfcn.h>
  21. #include <errno.h>
  22. /* getpid() */
  23. #include <unistd.h>
  24. /* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been
  25. written nevertheless to make future maintenance easier. */
  26. #ifdef APPINDICATOR_HEADER
  27. #include APPINDICATOR_HEADER
  28. #else
  29. /* ------------------------------------------------------------------------- */
  30. /* BEGIN THIRD-PARTY HEADER CONTENT */
  31. /* ------------------------------------------------------------------------- */
  32. /* Glib 2.0 */
  33. typedef unsigned long gulong;
  34. typedef void* gpointer;
  35. typedef char gchar;
  36. typedef int gint;
  37. typedef unsigned int guint;
  38. typedef gint gboolean;
  39. typedef void (*GCallback)(void);
  40. typedef struct _GClosure GClosure;
  41. typedef void (*GClosureNotify) (gpointer data, GClosure *closure);
  42. typedef gboolean (*GSourceFunc) (gpointer user_data);
  43. typedef enum
  44. {
  45. G_CONNECT_AFTER = 1 << 0,
  46. G_CONNECT_SWAPPED = 1 << 1
  47. } GConnectFlags;
  48. static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
  49. static void (*g_object_unref)(gpointer object);
  50. static gchar *(*g_mkdtemp)(gchar *template);
  51. static gpointer (*g_object_ref_sink)(gpointer object);
  52. static gpointer (*g_object_ref)(gpointer object);
  53. // glib_typeof requires compiler-specific code and includes that are too complex
  54. // to be worth copy-pasting here
  55. //#define g_object_ref(Obj) ((glib_typeof (Obj)) (g_object_ref) (Obj))
  56. //#define g_object_ref_sink(Obj) ((glib_typeof (Obj)) (g_object_ref_sink) (Obj))
  57. #define g_signal_connect(instance, detailed_signal, c_handler, data) \
  58. g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
  59. #define _G_TYPE_CIC(ip, gt, ct) ((ct*) ip)
  60. #define G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) (_G_TYPE_CIC ((instance), (g_type), c_type))
  61. #define G_CALLBACK(f) ((GCallback) (f))
  62. #define FALSE 0
  63. #define TRUE 1
  64. /* GTK 3.0 */
  65. typedef struct _GtkMenu GtkMenu;
  66. typedef struct _GtkMenuItem GtkMenuItem;
  67. typedef struct _GtkMenuShell GtkMenuShell;
  68. typedef struct _GtkWidget GtkWidget;
  69. typedef struct _GtkCheckMenuItem GtkCheckMenuItem;
  70. static gboolean (*gtk_init_check)(int *argc, char ***argv);
  71. static gboolean (*gtk_main_iteration_do)(gboolean blocking);
  72. static GtkWidget* (*gtk_menu_new)(void);
  73. static GtkWidget* (*gtk_separator_menu_item_new)(void);
  74. static GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label);
  75. static void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu);
  76. static GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label);
  77. static void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active);
  78. static void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive);
  79. static void (*gtk_widget_show)(GtkWidget *widget);
  80. static void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child);
  81. static void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position);
  82. static void (*gtk_widget_destroy)(GtkWidget *widget);
  83. static const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item);
  84. static void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label);
  85. static gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item);
  86. static gboolean (*gtk_widget_get_sensitive)(GtkWidget *widget);
  87. #define GTK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem))
  88. #define GTK_WIDGET(widget) (G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget))
  89. #define GTK_CHECK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CHECK_MENU_ITEM, GtkCheckMenuItem))
  90. #define GTK_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU, GtkMenu))
  91. /* AppIndicator */
  92. typedef enum {
  93. APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
  94. APP_INDICATOR_CATEGORY_COMMUNICATIONS,
  95. APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
  96. APP_INDICATOR_CATEGORY_HARDWARE,
  97. APP_INDICATOR_CATEGORY_OTHER
  98. } AppIndicatorCategory;
  99. typedef enum {
  100. APP_INDICATOR_STATUS_PASSIVE,
  101. APP_INDICATOR_STATUS_ACTIVE,
  102. APP_INDICATOR_STATUS_ATTENTION
  103. } AppIndicatorStatus;
  104. typedef struct _AppIndicator AppIndicator;
  105. static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
  106. static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
  107. static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name);
  108. static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
  109. /* ------------------------------------------------------------------------- */
  110. /* END THIRD-PARTY HEADER CONTENT */
  111. /* ------------------------------------------------------------------------- */
  112. #endif
  113. #ifdef APPINDICATOR_HEADER
  114. static void quit_gtk(void)
  115. {
  116. }
  117. static bool init_gtk(void)
  118. {
  119. return true;
  120. }
  121. #else
  122. static bool gtk_is_init = false;
  123. static void *libappindicator = NULL;
  124. static void *libgtk = NULL;
  125. static void *libgdk = NULL;
  126. static void quit_gtk(void)
  127. {
  128. if (libappindicator) {
  129. dlclose(libappindicator);
  130. libappindicator = NULL;
  131. }
  132. if (libgtk) {
  133. dlclose(libgtk);
  134. libgtk = NULL;
  135. }
  136. if (libgdk) {
  137. dlclose(libgdk);
  138. libgdk = NULL;
  139. }
  140. gtk_is_init = false;
  141. }
  142. const char *appindicator_names[] = {
  143. #ifdef SDL_PLATFORM_OPENBSD
  144. "libayatana-appindicator3.so",
  145. "libappindicator3.so",
  146. #else
  147. "libayatana-appindicator3.so.1",
  148. "libappindicator3.so.1",
  149. #endif
  150. NULL
  151. };
  152. const char *gtk_names[] = {
  153. #ifdef SDL_PLATFORM_OPENBSD
  154. "libgtk-3.so",
  155. #else
  156. "libgtk-3.so.0",
  157. #endif
  158. NULL
  159. };
  160. const char *gdk_names[] = {
  161. #ifdef SDL_PLATFORM_OPENBSD
  162. "libgdk-3.so",
  163. #else
  164. "libgdk-3.so.0",
  165. #endif
  166. NULL
  167. };
  168. static void *find_lib(const char **names)
  169. {
  170. const char **name_ptr = names;
  171. void *handle = NULL;
  172. do {
  173. handle = dlopen(*name_ptr, RTLD_LAZY);
  174. } while (*++name_ptr && !handle);
  175. return handle;
  176. }
  177. static bool init_gtk(void)
  178. {
  179. if (gtk_is_init) {
  180. return true;
  181. }
  182. libappindicator = find_lib(appindicator_names);
  183. libgtk = find_lib(gtk_names);
  184. libgdk = find_lib(gdk_names);
  185. if (!libappindicator || !libgtk || !libgdk) {
  186. quit_gtk();
  187. return SDL_SetError("Could not load GTK/AppIndicator libraries");
  188. }
  189. gtk_init_check = dlsym(libgtk, "gtk_init_check");
  190. gtk_main_iteration_do = dlsym(libgtk, "gtk_main_iteration_do");
  191. gtk_menu_new = dlsym(libgtk, "gtk_menu_new");
  192. gtk_separator_menu_item_new = dlsym(libgtk, "gtk_separator_menu_item_new");
  193. gtk_menu_item_new_with_label = dlsym(libgtk, "gtk_menu_item_new_with_label");
  194. gtk_menu_item_set_submenu = dlsym(libgtk, "gtk_menu_item_set_submenu");
  195. gtk_check_menu_item_new_with_label = dlsym(libgtk, "gtk_check_menu_item_new_with_label");
  196. gtk_check_menu_item_set_active = dlsym(libgtk, "gtk_check_menu_item_set_active");
  197. gtk_widget_set_sensitive = dlsym(libgtk, "gtk_widget_set_sensitive");
  198. gtk_widget_show = dlsym(libgtk, "gtk_widget_show");
  199. gtk_menu_shell_append = dlsym(libgtk, "gtk_menu_shell_append");
  200. gtk_menu_shell_insert = dlsym(libgtk, "gtk_menu_shell_insert");
  201. gtk_widget_destroy = dlsym(libgtk, "gtk_widget_destroy");
  202. gtk_menu_item_get_label = dlsym(libgtk, "gtk_menu_item_get_label");
  203. gtk_menu_item_set_label = dlsym(libgtk, "gtk_menu_item_set_label");
  204. gtk_check_menu_item_get_active = dlsym(libgtk, "gtk_check_menu_item_get_active");
  205. gtk_widget_get_sensitive = dlsym(libgtk, "gtk_widget_get_sensitive");
  206. /* Technically these are GLib or GObject functions, but we can find
  207. * them via GDK */
  208. g_mkdtemp = dlsym(libgdk, "g_mkdtemp");
  209. g_signal_connect_data = dlsym(libgdk, "g_signal_connect_data");
  210. g_object_unref = dlsym(libgdk, "g_object_unref");
  211. g_object_ref_sink = dlsym(libgdk, "g_object_ref_sink");
  212. g_object_ref = dlsym(libgdk, "g_object_ref");
  213. app_indicator_new = dlsym(libappindicator, "app_indicator_new");
  214. app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status");
  215. app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon");
  216. app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu");
  217. if (!gtk_init_check ||
  218. !gtk_main_iteration_do ||
  219. !gtk_menu_new ||
  220. !gtk_separator_menu_item_new ||
  221. !gtk_menu_item_new_with_label ||
  222. !gtk_menu_item_set_submenu ||
  223. !gtk_check_menu_item_new_with_label ||
  224. !gtk_check_menu_item_set_active ||
  225. !gtk_widget_set_sensitive ||
  226. !gtk_widget_show ||
  227. !gtk_menu_shell_append ||
  228. !gtk_menu_shell_insert ||
  229. !gtk_widget_destroy ||
  230. !g_mkdtemp ||
  231. !g_object_ref_sink ||
  232. !g_object_ref ||
  233. !g_signal_connect_data ||
  234. !g_object_unref ||
  235. !app_indicator_new ||
  236. !app_indicator_set_status ||
  237. !app_indicator_set_icon ||
  238. !app_indicator_set_menu ||
  239. !gtk_menu_item_get_label ||
  240. !gtk_menu_item_set_label ||
  241. !gtk_check_menu_item_get_active ||
  242. !gtk_widget_get_sensitive) {
  243. quit_gtk();
  244. return SDL_SetError("Could not load GTK/AppIndicator functions");
  245. }
  246. if (gtk_init_check(0, NULL) == FALSE) {
  247. quit_gtk();
  248. return SDL_SetError("Could not init GTK");
  249. }
  250. gtk_is_init = true;
  251. return true;
  252. }
  253. #endif
  254. struct SDL_TrayMenu {
  255. GtkMenuShell *menu;
  256. int nEntries;
  257. SDL_TrayEntry **entries;
  258. SDL_Tray *parent_tray;
  259. SDL_TrayEntry *parent_entry;
  260. };
  261. struct SDL_TrayEntry {
  262. SDL_TrayMenu *parent;
  263. GtkWidget *item;
  264. /* Checkboxes are "activated" when programmatically checked/unchecked; this
  265. is a workaround. */
  266. bool ignore_signal;
  267. SDL_TrayEntryFlags flags;
  268. SDL_TrayCallback callback;
  269. void *userdata;
  270. SDL_TrayMenu *submenu;
  271. };
  272. /* Template for g_mkdtemp(). The Xs will get replaced with a random
  273. * directory name, which is created safely and atomically. */
  274. #define ICON_DIR_TEMPLATE "/tmp/SDL-tray-XXXXXX"
  275. struct SDL_Tray {
  276. AppIndicator *indicator;
  277. SDL_TrayMenu *menu;
  278. char icon_dir[sizeof(ICON_DIR_TEMPLATE)];
  279. char icon_path[256];
  280. GtkMenuShell *menu_cached;
  281. };
  282. static void call_callback(GtkMenuItem *item, gpointer ptr)
  283. {
  284. SDL_TrayEntry *entry = ptr;
  285. /* Not needed with AppIndicator, may be needed with other frameworks */
  286. /* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
  287. SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
  288. } */
  289. if (entry->ignore_signal) {
  290. return;
  291. }
  292. if (entry->callback) {
  293. entry->callback(entry->userdata, entry);
  294. }
  295. }
  296. static bool new_tmp_filename(SDL_Tray *tray)
  297. {
  298. static int count = 0;
  299. int would_have_written = SDL_snprintf(tray->icon_path, sizeof(tray->icon_path), "%s/%d.bmp", tray->icon_dir, count++);
  300. if (would_have_written > 0 && ((unsigned) would_have_written) < sizeof(tray->icon_path) - 1) {
  301. return true;
  302. }
  303. tray->icon_path[0] = '\0';
  304. SDL_SetError("Failed to format new temporary filename");
  305. return false;
  306. }
  307. static const char *get_appindicator_id(void)
  308. {
  309. static int count = 0;
  310. static char buffer[256];
  311. int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++);
  312. if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) {
  313. SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer));
  314. return NULL;
  315. }
  316. return buffer;
  317. }
  318. static void DestroySDLMenu(SDL_TrayMenu *menu)
  319. {
  320. for (int i = 0; i < menu->nEntries; i++) {
  321. if (menu->entries[i] && menu->entries[i]->submenu) {
  322. DestroySDLMenu(menu->entries[i]->submenu);
  323. }
  324. SDL_free(menu->entries[i]);
  325. }
  326. if (menu->menu) {
  327. g_object_unref(menu->menu);
  328. }
  329. SDL_free(menu->entries);
  330. SDL_free(menu);
  331. }
  332. void SDL_UpdateTrays(void)
  333. {
  334. if (SDL_HasActiveTrays()) {
  335. gtk_main_iteration_do(FALSE);
  336. }
  337. }
  338. SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
  339. {
  340. if (!SDL_IsMainThread()) {
  341. SDL_SetError("This function should be called on the main thread");
  342. return NULL;
  343. }
  344. if (init_gtk() != true) {
  345. return NULL;
  346. }
  347. SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
  348. if (!tray) {
  349. return NULL;
  350. }
  351. /* On success, g_mkdtemp edits its argument in-place to replace the Xs
  352. * with a random directory name, which it creates safely and atomically.
  353. * On failure, it sets errno. */
  354. SDL_strlcpy(tray->icon_dir, ICON_DIR_TEMPLATE, sizeof(tray->icon_dir));
  355. if (!g_mkdtemp(tray->icon_dir)) {
  356. SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno));
  357. SDL_free(tray);
  358. return NULL;
  359. }
  360. if (icon) {
  361. if (!new_tmp_filename(tray)) {
  362. SDL_free(tray);
  363. return NULL;
  364. }
  365. SDL_SaveBMP(icon, tray->icon_path);
  366. }
  367. tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path,
  368. APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
  369. app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
  370. // The tray icon isn't shown before a menu is created; create one early.
  371. tray->menu_cached = (GtkMenuShell *) g_object_ref_sink(gtk_menu_new());
  372. app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached));
  373. SDL_RegisterTray(tray);
  374. return tray;
  375. }
  376. void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
  377. {
  378. if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
  379. return;
  380. }
  381. if (*tray->icon_path) {
  382. SDL_RemovePath(tray->icon_path);
  383. }
  384. /* AppIndicator caches the icon files; always change filename to avoid caching */
  385. if (icon && new_tmp_filename(tray)) {
  386. SDL_SaveBMP(icon, tray->icon_path);
  387. app_indicator_set_icon(tray->indicator, tray->icon_path);
  388. } else {
  389. *tray->icon_path = '\0';
  390. app_indicator_set_icon(tray->indicator, NULL);
  391. }
  392. }
  393. void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
  394. {
  395. /* AppIndicator provides no tooltip support. */
  396. }
  397. SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
  398. {
  399. if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
  400. SDL_InvalidParamError("tray");
  401. return NULL;
  402. }
  403. tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
  404. if (!tray->menu) {
  405. return NULL;
  406. }
  407. tray->menu->menu = g_object_ref(tray->menu_cached);
  408. tray->menu->parent_tray = tray;
  409. tray->menu->parent_entry = NULL;
  410. tray->menu->nEntries = 0;
  411. tray->menu->entries = NULL;
  412. return tray->menu;
  413. }
  414. SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
  415. {
  416. if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
  417. SDL_InvalidParamError("tray");
  418. return NULL;
  419. }
  420. return tray->menu;
  421. }
  422. SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
  423. {
  424. if (!entry) {
  425. SDL_InvalidParamError("entry");
  426. return NULL;
  427. }
  428. if (entry->submenu) {
  429. SDL_SetError("Tray entry submenu already exists");
  430. return NULL;
  431. }
  432. if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
  433. SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
  434. return NULL;
  435. }
  436. entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu));
  437. if (!entry->submenu) {
  438. return NULL;
  439. }
  440. entry->submenu->menu = g_object_ref_sink(gtk_menu_new());
  441. entry->submenu->parent_tray = NULL;
  442. entry->submenu->parent_entry = entry;
  443. entry->submenu->nEntries = 0;
  444. entry->submenu->entries = NULL;
  445. gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu));
  446. return entry->submenu;
  447. }
  448. SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
  449. {
  450. if (!entry) {
  451. SDL_InvalidParamError("entry");
  452. return NULL;
  453. }
  454. return entry->submenu;
  455. }
  456. const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
  457. {
  458. if (!menu) {
  459. SDL_InvalidParamError("menu");
  460. return NULL;
  461. }
  462. if (count) {
  463. *count = menu->nEntries;
  464. }
  465. return (const SDL_TrayEntry **)menu->entries;
  466. }
  467. void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
  468. {
  469. if (!entry) {
  470. return;
  471. }
  472. SDL_TrayMenu *menu = entry->parent;
  473. bool found = false;
  474. for (int i = 0; i < menu->nEntries - 1; i++) {
  475. if (menu->entries[i] == entry) {
  476. found = true;
  477. }
  478. if (found) {
  479. menu->entries[i] = menu->entries[i + 1];
  480. }
  481. }
  482. if (entry->submenu) {
  483. DestroySDLMenu(entry->submenu);
  484. }
  485. menu->nEntries--;
  486. SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
  487. /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
  488. if (new_entries) {
  489. menu->entries = new_entries;
  490. menu->entries[menu->nEntries] = NULL;
  491. }
  492. gtk_widget_destroy(entry->item);
  493. SDL_free(entry);
  494. }
  495. SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
  496. {
  497. if (!menu) {
  498. SDL_InvalidParamError("menu");
  499. return NULL;
  500. }
  501. if (pos < -1 || pos > menu->nEntries) {
  502. SDL_InvalidParamError("pos");
  503. return NULL;
  504. }
  505. if (pos == -1) {
  506. pos = menu->nEntries;
  507. }
  508. SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
  509. if (!entry) {
  510. return NULL;
  511. }
  512. entry->parent = menu;
  513. entry->item = NULL;
  514. entry->ignore_signal = false;
  515. entry->flags = flags;
  516. entry->callback = NULL;
  517. entry->userdata = NULL;
  518. entry->submenu = NULL;
  519. if (label == NULL) {
  520. entry->item = gtk_separator_menu_item_new();
  521. } else if (flags & SDL_TRAYENTRY_CHECKBOX) {
  522. entry->item = gtk_check_menu_item_new_with_label(label);
  523. gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0);
  524. gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active);
  525. } else {
  526. entry->item = gtk_menu_item_new_with_label(label);
  527. }
  528. gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0);
  529. gtk_widget_set_sensitive(entry->item, sensitive);
  530. SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
  531. if (!new_entries) {
  532. SDL_free(entry);
  533. return NULL;
  534. }
  535. menu->entries = new_entries;
  536. menu->nEntries++;
  537. for (int i = menu->nEntries - 1; i > pos; i--) {
  538. menu->entries[i] = menu->entries[i - 1];
  539. }
  540. new_entries[pos] = entry;
  541. new_entries[menu->nEntries] = NULL;
  542. gtk_widget_show(entry->item);
  543. gtk_menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos);
  544. g_signal_connect(entry->item, "activate", G_CALLBACK(call_callback), entry);
  545. return entry;
  546. }
  547. void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
  548. {
  549. if (!entry) {
  550. return;
  551. }
  552. gtk_menu_item_set_label(GTK_MENU_ITEM(entry->item), label);
  553. }
  554. const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
  555. {
  556. if (!entry) {
  557. SDL_InvalidParamError("entry");
  558. return NULL;
  559. }
  560. return gtk_menu_item_get_label(GTK_MENU_ITEM(entry->item));
  561. }
  562. void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
  563. {
  564. if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
  565. return;
  566. }
  567. entry->ignore_signal = true;
  568. gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked);
  569. entry->ignore_signal = false;
  570. }
  571. bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
  572. {
  573. if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
  574. return false;
  575. }
  576. return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item));
  577. }
  578. void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
  579. {
  580. if (!entry) {
  581. return;
  582. }
  583. gtk_widget_set_sensitive(entry->item, enabled);
  584. }
  585. bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
  586. {
  587. if (!entry) {
  588. return false;
  589. }
  590. return gtk_widget_get_sensitive(entry->item);
  591. }
  592. void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
  593. {
  594. if (!entry) {
  595. return;
  596. }
  597. entry->callback = callback;
  598. entry->userdata = userdata;
  599. }
  600. void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
  601. {
  602. if (!entry) {
  603. return;
  604. }
  605. if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
  606. SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
  607. }
  608. if (entry->callback) {
  609. entry->callback(entry->userdata, entry);
  610. }
  611. }
  612. SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
  613. {
  614. if (!entry) {
  615. SDL_InvalidParamError("entry");
  616. return NULL;
  617. }
  618. return entry->parent;
  619. }
  620. SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
  621. {
  622. return menu->parent_entry;
  623. }
  624. SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
  625. {
  626. if (!menu) {
  627. SDL_InvalidParamError("menu");
  628. return NULL;
  629. }
  630. return menu->parent_tray;
  631. }
  632. void SDL_DestroyTray(SDL_Tray *tray)
  633. {
  634. if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
  635. return;
  636. }
  637. SDL_UnregisterTray(tray);
  638. if (tray->menu) {
  639. DestroySDLMenu(tray->menu);
  640. }
  641. if (*tray->icon_path) {
  642. SDL_RemovePath(tray->icon_path);
  643. }
  644. if (*tray->icon_dir) {
  645. SDL_RemovePath(tray->icon_dir);
  646. }
  647. if (tray->menu_cached) {
  648. g_object_unref(tray->menu_cached);
  649. }
  650. if (tray->indicator) {
  651. g_object_unref(tray->indicator);
  652. }
  653. SDL_free(tray);
  654. }