Document SDL_tray, add set icon/tooltip, fix bugs

This commit is contained in:
Semphris 2024-09-17 16:36:27 -04:00
parent 8c238346a3
commit 921c3e3c6c
8 changed files with 366 additions and 21 deletions

View File

@ -42,29 +42,266 @@ typedef struct SDL_Tray SDL_Tray;
typedef struct SDL_TrayMenu SDL_TrayMenu;
typedef struct SDL_TrayEntry SDL_TrayEntry;
/**
* Flags that control the creation of system tray entries.
*
* Some of these flags are mandatory; exactly one of them must be specified at
* the time a tray entry is created. Other flags are optional; zero or more of
* those can be OR'ed together with the mandatory flag.
*/
typedef enum {
/* Mandatory; must be specified at creation time */
/** Make the entry a simple button. This is a mandatory flag. */
SDL_TRAYENTRY_BUTTON = 0,
SDL_TRAYENTRY_CHECKBOX = (1 << 0),
SDL_TRAYENTRY_SUBMENU = (1 << 1),
/** Make the entry a checkbox. This is a mandatory flag. */
SDL_TRAYENTRY_CHECKBOX,
/** Prepatre the entry to have a submenu. This is a mandatory flag. */
SDL_TRAYENTRY_SUBMENU,
/* Optional; can be changed later */
SDL_TRAYENTRY_DISABLED = (1 << 16),
SDL_TRAYENTRY_CHECKED = (1 << 17),
/** Make the entry disabled. */
SDL_TRAYENTRY_DISABLED = (1 << 31),
/** Make the entry checked. This is valid only for checkboxes. */
SDL_TRAYENTRY_CHECKED = (1 << 30),
} SDL_TrayEntryFlags;
/**
* A callback that is invoked when a tray entry is selected.
*
* \param userdata an optional pointer to pass extra data to the callback when
* it will be invoked.
* \param entry the tray entry that was selected.
*/
typedef void (*SDL_TrayCallback)(void *userdata, SDL_TrayEntry *entry);
/**
* Create an icon to be placed in the operating system's tray, or equivalent.
*
* Many platforms advise not using a system tray unless persistence is a
* necessary feature.
*
* \param icon a surface to be used as icon. May be NULL.
* \param tooltip a tooltip to be displayed when the mouse hovers the icon. Not
* supported on all platforms. May be NULL.
* \returns The newly created system tray icon.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_SetTrayIcon
* \sa SDL_SetTrayTooltip
* \sa SDL_CreateTrayMenu
* \sa SDL_DestroyTray
*/
extern SDL_DECLSPEC SDL_Tray *SDLCALL SDL_CreateTray(SDL_Surface *icon, const char *tooltip);
/**
* Updates the system tray icon's icon.
*
* \param tray the tray icon to be updated.
* \param icon the new icon. May be NULL.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateTray
* \sa SDL_SetTrayTooltip
* \sa SDL_CreateTrayMenu
* \sa SDL_DestroyTray
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon);
/**
* Updates the system tray icon's tooltip.
*
* \param tray the tray icon to be updated.
* \param tooltip the new tooltip. May be NULL.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateTray
* \sa SDL_SetTrayIcon
* \sa SDL_CreateTrayMenu
* \sa SDL_DestroyTray
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip);
/**
* Create a menu for a system tray.
*
* This should be called at most once per tray icon.
*
* This function does the same thing as SDL_CreateTraySubmenu, except that it
* takes a SDL_Tray instead of a SDL_TrayEntry.
*
* A menu does not need to be destroyed; it will be destroyed with the tray.
*
* \param tray the tray to bind the menu to.
* \returns the newly created menu.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateTray
* \sa SDL_CreateTraySubmenu
* \sa SDL_AppendTrayEntry
* \sa SDL_AppendTraySeparator
*/
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTrayMenu(SDL_Tray *tray);
/**
* Create a submenu for a system tray entry.
*
* This should be called at most once per tray entry.
*
* This function does the same thing as SDL_CreateTrayMenu, except that it
* takes a SDL_TrayEntry instead of a SDL_Tray.
*
* A menu does not need to be destroyed; it will be destroyed with the tray.
*
* \param entry the tray entry to bind the menu to.
* \returns the newly created menu.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateTrayMenu
* \sa SDL_AppendTrayEntry
* \sa SDL_AppendTraySeparator
*/
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTraySubmenu(SDL_TrayEntry *entry);
/**
* Create a menu item (entry) and append it to the given menu.
*
* An entry does not need to be destroyed; it will be destroyed with the tray.
*
* \param menu the menu to append the entry to.
* \param label the label to be displayed on the entry.
* \param flags a combination of flags, some of which are mandatory. See SDL_TrayEntryFlags.
* \returns the newly created entry.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_TrayEntryFlags
* \sa SDL_CreateTrayMenu
* \sa SDL_CreateTraySubmenu
* \sa SDL_SetTrayEntryChecked
* \sa SDL_GetTrayEntryChecked
* \sa SDL_SetTrayEntryEnabled
* \sa SDL_GetTrayEntryEnabled
* \sa SDL_SetTrayEntryCallback
* \sa SDL_AppendTraySeparator
*/
extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_AppendTrayEntry(SDL_TrayMenu *menu, const char *label, SDL_TrayEntryFlags flags);
/**
* Append a separator to the given menu.
*
* \param menu the menu to append the entry to.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateTrayMenu
* \sa SDL_CreateTraySubmenu
* \sa SDL_AppendTrayEntry
*/
extern SDL_DECLSPEC void SDLCALL SDL_AppendTraySeparator(SDL_TrayMenu *menu);
/**
* Sets whether or not an entry is checked.
*
* The entry must have been created with the SDL_TRAYENTRY_CHECKBOX flag.
*
* \param entry the entry to be updated.
* \param checked SDL_TRUE if the entry should be checked; SDL_FALSE otherwise.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_TrayEntryFlags
* \sa SDL_AppendTrayEntry
* \sa SDL_GetTrayEntryChecked
* \sa SDL_SetTrayEntryEnabled
* \sa SDL_GetTrayEntryEnabled
* \sa SDL_SetTrayEntryCallback
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked);
/**
* Gets whether or not an entry is checked.
*
* The entry must have been created with the SDL_TRAYENTRY_CHECKBOX flag.
*
* \param entry the entry to be read.
* \returns SDL_TRUE if the entry is checked; SDL_FALSE otherwise.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_TrayEntryFlags
* \sa SDL_AppendTrayEntry
* \sa SDL_SetTrayEntryChecked
* \sa SDL_SetTrayEntryEnabled
* \sa SDL_GetTrayEntryEnabled
* \sa SDL_SetTrayEntryCallback
*/
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_GetTrayEntryChecked(SDL_TrayEntry *entry);
/**
* Sets whether or not an entry is enabled.
*
* \param entry the entry to be updated.
* \param enabled SDL_TRUE if the entry should be enabled; SDL_FALSE otherwise.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AppendTrayEntry
* \sa SDL_SetTrayEntryChecked
* \sa SDL_GetTrayEntryChecked
* \sa SDL_GetTrayEntryEnabled
* \sa SDL_SetTrayEntryCallback
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, SDL_bool enabled);
/**
* Gets whether or not an entry is enabled.
*
* \param entry the entry to be read.
* \returns SDL_TRUE if the entry is enabled; SDL_FALSE otherwise.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AppendTrayEntry
* \sa SDL_SetTrayEntryChecked
* \sa SDL_GetTrayEntryChecked
* \sa SDL_SetTrayEntryEnabled
* \sa SDL_SetTrayEntryCallback
*/
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry);
/**
* Sets a callback to be invoked when the entry is selected.
*
* \param entry the entry to be updated.
* \param callback a callback to be invoked when the entry is selected.
* \param userdata an optional pointer to pass extra data to the callback when
* it will be invoked.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_TrayCallback
* \sa SDL_AppendTrayEntry
* \sa SDL_SetTrayEntryChecked
* \sa SDL_GetTrayEntryChecked
* \sa SDL_SetTrayEntryEnabled
* \sa SDL_GetTrayEntryEnabled
*/
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata);
/**
* Destroys a tray object.
*
* This also destroys all associated menus and entries.
*
* \param tray the tray icon to be destroyed.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateTray
*/
extern SDL_DECLSPEC void SDLCALL SDL_DestroyTray(SDL_Tray *tray);
/* Ends C function definitions when using C++ */

View File

@ -1178,6 +1178,8 @@ SDL3_0.0.0 {
SDL_GetTrayEntryEnabled;
SDL_SetTrayEntryCallback;
SDL_DestroyTray;
SDL_SetTrayIcon;
SDL_SetTrayTooltip;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@ -1203,3 +1203,5 @@
#define SDL_GetTrayEntryEnabled SDL_GetTrayEntryEnabled_REAL
#define SDL_SetTrayEntryCallback SDL_SetTrayEntryCallback_REAL
#define SDL_DestroyTray SDL_DestroyTray_REAL
#define SDL_SetTrayIcon SDL_SetTrayIcon_REAL
#define SDL_SetTrayTooltip SDL_SetTrayTooltip_REAL

View File

@ -1209,3 +1209,5 @@ SDL_DYNAPI_PROC(void,SDL_SetTrayEntryEnabled,(SDL_TrayEntry *a, SDL_bool b),(a,b
SDL_DYNAPI_PROC(SDL_bool,SDL_GetTrayEntryEnabled,(SDL_TrayEntry *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_SetTrayEntryCallback,(SDL_TrayEntry *a, SDL_TrayCallback b, void *c),(a,b,c),)
SDL_DYNAPI_PROC(void,SDL_DestroyTray,(SDL_Tray *a),(a),)
SDL_DYNAPI_PROC(void,SDL_SetTrayIcon,(SDL_Tray *a, SDL_Surface *b),(a,b),)
SDL_DYNAPI_PROC(void,SDL_SetTrayTooltip,(SDL_Tray *a, const char *b),(a,b),)

View File

@ -82,6 +82,12 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
tray->statusItem = [tray->statusBar statusItemWithLength:NSVariableStatusItemLength];
[app activateIgnoringOtherApps:TRUE];
if (tooltip) {
tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
} else {
tray->statusItem.button.toolTip = nil;
}
if (icon) {
SDL_Surface *iconfmt = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
if (!iconfmt) {
@ -118,6 +124,55 @@ skip_putting_an_icon:
return tray;
}
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (!icon) {
tray->statusItem.button.image = nil;
return;
}
SDL_Surface *iconfmt = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
if (!iconfmt) {
/* TODO: Ignore errors silently, as in SDL_CreateTray? */
tray->statusItem.button.image = nil;
return;
}
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&iconfmt->pixels
pixelsWide:iconfmt->w
pixelsHigh:iconfmt->h
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:iconfmt->pitch
bitsPerPixel:32];
NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(iconfmt->w, iconfmt->h)];
[iconimg addRepresentation:bitmap];
/* A typical icon size is 22x22 on macOS. Failing to resize the icon
may give oversized status bar buttons. */
NSImage *iconimg22 = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)];
[iconimg22 lockFocus];
[iconimg setSize:NSMakeSize(22, 22)];
[iconimg drawInRect:NSMakeRect(0, 0, 22, 22)];
[iconimg22 unlockFocus];
tray->statusItem.button.image = iconimg22;
SDL_DestroySurface(iconfmt);
}
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
{
if (tooltip) {
tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
} else {
tray->statusItem.button.toolTip = nil;
}
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
NSMenu *menu = [[NSMenu alloc] init];

View File

@ -116,6 +116,12 @@ void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
static SDL_bool gtk_is_init = SDL_FALSE;
static int main_gtk_thread(void *data)
{
gtk_main();
return 0;
}
static void *libappindicator = NULL;
static void *libgtk = NULL;
static void *libgdk = NULL;
@ -201,6 +207,8 @@ static SDL_bool init_gtk(void)
gtk_is_init = SDL_TRUE;
SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
return SDL_TRUE;
}
@ -236,12 +244,6 @@ static void call_callback(GtkMenuItem *item, gpointer ptr)
}
}
static int main_gtk_thread(void *data)
{
gtk_main();
return 0;
}
/* TODO: Replace this with a safer alternative */
static SDL_bool get_tmp_filename(char *buffer, size_t size)
{
@ -262,8 +264,6 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return NULL;
}
SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
SDL_Tray *tray = (SDL_Tray *) SDL_malloc(sizeof(SDL_Tray));
if (!tray) {
@ -278,11 +278,25 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
tray->indicator = app_indicator_new(TRAY_APPINDICATOR_ID, tray->icon_path,
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
//app_indicator_set_icon(indicator, tray->icon);
return tray;
}
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (icon) {
SDL_SaveBMP(icon, tray->icon_path);
app_indicator_set_icon(tray->indicator, tray->icon_path);
} else {
app_indicator_set_icon(tray->indicator, NULL);
}
}
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
{
/* AppIndicator provides no tooltip support. */
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
tray->menu.menu = (GtkMenuShell *)gtk_menu_new();

View File

@ -175,7 +175,7 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
tray->entries = NULL;
char classname[32];
SDL_snprintf(classname, sizeof(classname), "SDLTray%lld", get_next_id());
SDL_snprintf(classname, sizeof(classname), "SDLTray%d", (unsigned int) get_next_id());
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASS wc;
@ -219,6 +219,40 @@ no_icon:
return tray;
}
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (tray->icon) {
DestroyIcon(tray->icon);
}
if (icon) {
SDL_Surface *iconfmt = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
if (!iconfmt) {
/* TODO: Ignore errors silently, as in SDL_CreateTray? */
return;
}
tray->nid.hIcon = CreateIconFromRGBA(iconfmt->w, iconfmt->h, iconfmt->pixels);
tray->icon = tray->nid.hIcon;
} else {
tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
tray->icon = tray->nid.hIcon;
}
Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
}
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
{
if (tooltip) {
mbstowcs_s(NULL, tray->nid.szTip, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip), tooltip, _TRUNCATE);
} else {
tray->nid.szTip[0] = '\0';
}
Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
tray->menu.hMenu = CreatePopupMenu();
@ -286,7 +320,7 @@ void SDL_AppendTraySeparator(SDL_TrayMenu *menu)
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked)
{
CheckMenuItem(entry->menu->hMenu, entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(entry->menu->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
}
SDL_bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
@ -295,14 +329,14 @@ SDL_bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE;
GetMenuItemInfo(entry->menu->hMenu, entry->id, FALSE, &mii);
GetMenuItemInfo(entry->menu->hMenu, (UINT) entry->id, FALSE, &mii);
return !!(mii.fState & MFS_CHECKED);
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, SDL_bool enabled)
{
EnableMenuItem(entry->menu->hMenu, entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
EnableMenuItem(entry->menu->hMenu, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
}
SDL_bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
@ -311,7 +345,7 @@ SDL_bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE;
GetMenuItemInfo(entry->menu->hMenu, entry->id, FALSE, &mii);
GetMenuItemInfo(entry->menu->hMenu, (UINT) entry->id, FALSE, &mii);
return !!(mii.fState & MFS_ENABLED);
}

View File

@ -45,7 +45,6 @@ int main(int argc, char **argv)
SDL_Event e;
while (SDL_WaitEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
SDL_Log("Someone said quit\n");
break;
}
}