This commit is contained in:
Semphriss 2024-09-19 15:42:07 -03:00 committed by GitHub
commit 161efb1d1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1572 additions and 0 deletions

View File

@ -242,6 +242,7 @@ define_sdl_subsystem(Hidapi)
define_sdl_subsystem(Power) define_sdl_subsystem(Power)
define_sdl_subsystem(Sensor) define_sdl_subsystem(Sensor)
define_sdl_subsystem(Dialog) define_sdl_subsystem(Dialog)
define_sdl_subsystem(Tray)
cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF) cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF)
if(SDL_FRAMEWORK) if(SDL_FRAMEWORK)
@ -2909,6 +2910,19 @@ int main(void)
endif() endif()
endif() endif()
if (SDL_TRAY)
if(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
sdl_sources(${SDL3_SOURCE_DIR}/src/tray/unix/SDL_tray.c)
set(HAVE_SDL_TRAY TRUE)
elseif(WINDOWS)
sdl_sources(${SDL3_SOURCE_DIR}/src/tray/windows/SDL_tray.c)
set(HAVE_SDL_TRAY TRUE)
elseif(MACOS)
sdl_sources(${SDL3_SOURCE_DIR}/src/tray/cocoa/SDL_tray.m)
set(HAVE_SDL_TRAY TRUE)
endif()
endif()
# Platform-independent options # Platform-independent options
if(SDL_VIDEO) if(SDL_VIDEO)
@ -3002,6 +3016,10 @@ if(NOT HAVE_SDL_PROCESS)
set(SDL_PROCESS_DUMMY 1) set(SDL_PROCESS_DUMMY 1)
sdl_glob_sources(${SDL3_SOURCE_DIR}/src/process/dummy/*.c) sdl_glob_sources(${SDL3_SOURCE_DIR}/src/process/dummy/*.c)
endif() endif()
if(NOT HAVE_SDL_TRAY)
set(SDL_TRAY_DUMMY 1)
sdl_sources(${SDL3_SOURCE_DIR}/src/tray/dummy/SDL_tray.c)
endif()
if(NOT HAVE_CAMERA) if(NOT HAVE_CAMERA)
set(SDL_CAMERA_DRIVER_DUMMY 1) set(SDL_CAMERA_DRIVER_DUMMY 1)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c")

View File

@ -77,6 +77,7 @@
#include <SDL3/SDL_thread.h> #include <SDL3/SDL_thread.h>
#include <SDL3/SDL_time.h> #include <SDL3/SDL_time.h>
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include <SDL3/SDL_tray.h>
#include <SDL3/SDL_touch.h> #include <SDL3/SDL_touch.h>
#include <SDL3/SDL_version.h> #include <SDL3/SDL_version.h>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>

313
include/SDL3/SDL_tray.h Normal file
View File

@ -0,0 +1,313 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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.
*/
/**
* # CategoryTray
*
* System tray menu support.
*/
#ifndef SDL_tray_h_
#define SDL_tray_h_
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
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 {
/** Make the entry a simple button. This is a mandatory flag. */
SDL_TRAYENTRY_BUTTON = 0,
/** 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,
/** 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++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_tray_h_ */

View File

@ -1171,6 +1171,19 @@ SDL3_0.0.0 {
SDL_wcsnstr; SDL_wcsnstr;
SDL_wcsstr; SDL_wcsstr;
SDL_wcstol; SDL_wcstol;
SDL_CreateTray;
SDL_CreateTrayMenu;
SDL_CreateTraySubmenu;
SDL_AppendTrayEntry;
SDL_AppendTraySeparator;
SDL_SetTrayEntryChecked;
SDL_GetTrayEntryChecked;
SDL_SetTrayEntryEnabled;
SDL_GetTrayEntryEnabled;
SDL_SetTrayEntryCallback;
SDL_DestroyTray;
SDL_SetTrayIcon;
SDL_SetTrayTooltip;
# extra symbols go here (don't modify this line) # extra symbols go here (don't modify this line)
local: *; local: *;
}; };

View File

@ -1196,3 +1196,16 @@
#define SDL_wcsnstr SDL_wcsnstr_REAL #define SDL_wcsnstr SDL_wcsnstr_REAL
#define SDL_wcsstr SDL_wcsstr_REAL #define SDL_wcsstr SDL_wcsstr_REAL
#define SDL_wcstol SDL_wcstol_REAL #define SDL_wcstol SDL_wcstol_REAL
#define SDL_CreateTray SDL_CreateTray_REAL
#define SDL_CreateTrayMenu SDL_CreateTrayMenu_REAL
#define SDL_CreateTraySubmenu SDL_CreateTraySubmenu_REAL
#define SDL_AppendTrayEntry SDL_AppendTrayEntry_REAL
#define SDL_AppendTraySeparator SDL_AppendTraySeparator_REAL
#define SDL_SetTrayEntryChecked SDL_SetTrayEntryChecked_REAL
#define SDL_GetTrayEntryChecked SDL_GetTrayEntryChecked_REAL
#define SDL_SetTrayEntryEnabled SDL_SetTrayEntryEnabled_REAL
#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

@ -1202,3 +1202,16 @@ SDL_DYNAPI_PROC(size_t,SDL_wcsnlen,(const wchar_t *a, size_t b),(a,b),return)
SDL_DYNAPI_PROC(wchar_t*,SDL_wcsnstr,(const wchar_t *a, const wchar_t *b, size_t c),(a,b,c),return) SDL_DYNAPI_PROC(wchar_t*,SDL_wcsnstr,(const wchar_t *a, const wchar_t *b, size_t c),(a,b,c),return)
SDL_DYNAPI_PROC(wchar_t*,SDL_wcsstr,(const wchar_t *a, const wchar_t *b),(a,b),return) SDL_DYNAPI_PROC(wchar_t*,SDL_wcsstr,(const wchar_t *a, const wchar_t *b),(a,b),return)
SDL_DYNAPI_PROC(long,SDL_wcstol,(const wchar_t *a, wchar_t **b, int c),(a,b,c),return) SDL_DYNAPI_PROC(long,SDL_wcstol,(const wchar_t *a, wchar_t **b, int c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_Tray*,SDL_CreateTray,(SDL_Surface *a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_CreateTrayMenu,(SDL_Tray *a),(a),return)
SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_CreateTraySubmenu,(SDL_TrayEntry *a),(a),return)
SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_AppendTrayEntry,(SDL_TrayMenu *a, const char *b, SDL_TrayEntryFlags c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_AppendTraySeparator,(SDL_TrayMenu *a),(a),)
SDL_DYNAPI_PROC(void,SDL_SetTrayEntryChecked,(SDL_TrayEntry *a, SDL_bool b),(a,b),)
SDL_DYNAPI_PROC(SDL_bool,SDL_GetTrayEntryChecked,(SDL_TrayEntry *a),(a),return)
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),)

277
src/tray/cocoa/SDL_tray.m Normal file
View File

@ -0,0 +1,277 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
#include <Cocoa/Cocoa.h>
struct SDL_TrayMenu {
SDL_Tray *tray;
NSMenu *menu;
};
struct SDL_TrayEntry {
SDL_Tray *tray;
SDL_TrayMenu *menu;
NSMenuItem *item;
SDL_TrayCallback callback;
void *userdata;
SDL_TrayMenu submenu;
};
struct SDL_Tray {
NSStatusBar *statusBar;
NSStatusItem *statusItem;
SDL_TrayMenu menu;
size_t nEntries;
SDL_TrayEntry **entries;
};
static NSApplication *app = NULL;
@interface AppDelegate: NSObject <NSApplicationDelegate>
- (IBAction)menu:(id)sender;
@end
@implementation AppDelegate{}
- (IBAction)menu:(id)sender
{
SDL_TrayEntry *entry = [[sender representedObject] pointerValue];
if (entry && entry->callback) {
entry->callback(entry->userdata, entry);
}
}
@end
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
SDL_Tray *tray = (SDL_Tray *) SDL_malloc(sizeof(SDL_Tray));
AppDelegate *delegate = [[AppDelegate alloc] init];
app = [NSApplication sharedApplication];
[app setDelegate:delegate];
if (!tray) {
return NULL;
}
SDL_memset((void *) tray, 0, sizeof(*tray));
tray->statusItem = nil;
tray->statusBar = [NSStatusBar systemStatusBar];
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) {
goto skip_putting_an_icon;
}
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);
}
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];
[menu setAutoenablesItems:FALSE];
[tray->statusItem setMenu:menu];
tray->menu.menu = menu;
tray->menu.tray = tray;
return &tray->menu;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
NSMenu *menu = [[NSMenu alloc] init];
[menu setAutoenablesItems:FALSE];
entry->submenu.menu = menu;
entry->submenu.tray = entry->tray;
[entry->menu->menu setSubmenu:menu forItem: entry->item];
return &entry->submenu;
}
SDL_TrayEntry *SDL_AppendTrayEntry(SDL_TrayMenu *menu, const char *label, SDL_TrayEntryFlags flags)
{
SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
if (!entry) {
return NULL;
}
SDL_memset((void *) entry, 0, sizeof(*entry));
entry->tray = menu->tray;
entry->menu = menu;
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label] action:@selector(menu:) keyEquivalent:@""];
entry->item = item;
SDL_TrayEntry **new_entries = (SDL_TrayEntry **) SDL_realloc(entry->tray->entries, (entry->tray->nEntries + 1) * sizeof(SDL_TrayEntry **));
if (!new_entries) {
SDL_free(entry);
return NULL;
}
new_entries[entry->tray->nEntries] = entry;
entry->tray->entries = new_entries;
entry->tray->nEntries++;
[item setEnabled:((flags & SDL_TRAYENTRY_DISABLED) ? FALSE : TRUE)];
[item setState:((flags & SDL_TRAYENTRY_CHECKED) ? NSControlStateValueOn : NSControlStateValueOff)];
[item setRepresentedObject:[NSValue valueWithPointer:entry]];
[menu->menu addItem:item];
return entry;
}
void SDL_AppendTraySeparator(SDL_TrayMenu *menu)
{
[menu->menu addItem:[NSMenuItem separatorItem]];
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked)
{
[entry->item setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
}
SDL_bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
return entry->item.state == NSControlStateValueOn;
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, SDL_bool enabled)
{
[entry->item setState:(enabled ? YES : NO)];
}
SDL_bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
return entry->item.enabled;
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
entry->callback = callback;
entry->userdata = userdata;
}
void SDL_DestroyTray(SDL_Tray *tray)
{
[[NSStatusBar systemStatusBar] removeStatusItem:tray->statusItem];
for (int i = 0; i < tray->nEntries; i++) {
SDL_free(tray->entries[i]);
}
SDL_free(tray->entries);
SDL_free(tray);
}

83
src/tray/dummy/SDL_tray.c Normal file
View File

@ -0,0 +1,83 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
SDL_Unsupported();
return NULL;
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
SDL_Unsupported();
return NULL;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
SDL_Unsupported();
return NULL;
}
SDL_TrayEntry *SDL_AppendTrayEntry(SDL_TrayMenu *menu, const char *label, SDL_TrayEntryFlags flags)
{
SDL_Unsupported();
return NULL;
}
void SDL_AppendTraySeparator(SDL_TrayMenu *menu)
{
SDL_Unsupported();
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked)
{
SDL_Unsupported();
}
SDL_bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
SDL_Unsupported();
return false;
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, SDL_bool enabled)
{
SDL_Unsupported();
}
SDL_bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
SDL_Unsupported();
return false;
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
SDL_Unsupported();
}
void SDL_DestroyTray(SDL_Tray *tray)
{
SDL_Unsupported();
}

404
src/tray/unix/SDL_tray.c Normal file
View File

@ -0,0 +1,404 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
#include <dlfcn.h>
#define TRAY_APPINDICATOR_ID "tray-id"
#ifdef APPINDICATOR_HEADER
#include APPINDICATOR_HEADER
#else
/* ------------------------------------------------------------------------- */
/* BEGIN THIRD-PARTY HEADER CONTENT */
/* ------------------------------------------------------------------------- */
// Glib 2.0
typedef unsigned long gulong;
typedef void* gpointer;
typedef char gchar;
typedef int gint;
typedef unsigned int guint;
typedef gint gboolean;
typedef void (*GCallback)(void);
typedef struct _GClosure GClosure;
typedef void (*GClosureNotify) (gpointer data, GClosure *closure);
typedef gboolean (*GSourceFunc) (gpointer user_data);
typedef enum
{
G_CONNECT_AFTER = 1 << 0,
G_CONNECT_SWAPPED = 1 << 1
} GConnectFlags;
gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
#define g_signal_connect(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
#define _G_TYPE_CIC(ip, gt, ct) ((ct*) ip)
#define G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) (_G_TYPE_CIC ((instance), (g_type), c_type))
#define G_CALLBACK(f) ((GCallback) (f))
#define FALSE 0
#define TRUE 1
// GTK 3.0
typedef struct _GtkMenu GtkMenu;
typedef struct _GtkMenuItem GtkMenuItem;
typedef struct _GtkMenuShell GtkMenuShell;
typedef struct _GtkWidget GtkWidget;
typedef struct _GtkCheckMenuItem GtkCheckMenuItem;
gboolean (*gtk_init_check)(int *argc, char ***argv);
void (*gtk_main)(void);
GtkWidget* (*gtk_menu_new)(void);
GtkWidget* (*gtk_separator_menu_item_new)(void);
GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label);
void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu);
GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label);
void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active);
void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive);
void (*gtk_widget_show)(GtkWidget *widget);
void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child);
#define GTK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem))
#define GTK_WIDGET(widget) (G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget))
#define GTK_CHECK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CHECK_MENU_ITEM, GtkCheckMenuItem))
#define GTK_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU, GtkMenu))
// 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;
AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name);
void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
/* ------------------------------------------------------------------------- */
/* END THIRD-PARTY HEADER CONTENT */
/* ------------------------------------------------------------------------- */
#endif
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;
static void quit_gtk(void)
{
if (libappindicator) {
dlclose(libappindicator);
libappindicator = NULL;
}
if (libgtk) {
dlclose(libgtk);
libgtk = NULL;
}
if (libgdk) {
dlclose(libgdk);
libgdk = NULL;
}
gtk_is_init = SDL_FALSE;
}
static SDL_bool init_gtk(void)
{
if (gtk_is_init) {
return SDL_TRUE;
}
libappindicator = dlopen("libayatana-appindicator3.so", RTLD_LAZY);
libgtk = dlopen("libgtk-3.so", RTLD_LAZY);
libgdk = dlopen("libgdk-3.so", RTLD_LAZY);
if (!libappindicator || !libgtk || !libgdk) {
quit_gtk();
return SDL_SetError("Could not load GTK/AppIndicator libraries");
}
gtk_init_check = dlsym(libgtk, "gtk_init_check");
gtk_main = dlsym(libgtk, "gtk_main");
gtk_menu_new = dlsym(libgtk, "gtk_menu_new");
gtk_separator_menu_item_new = dlsym(libgtk, "gtk_separator_menu_item_new");
gtk_menu_item_new_with_label = dlsym(libgtk, "gtk_menu_item_new_with_label");
gtk_menu_item_set_submenu = dlsym(libgtk, "gtk_menu_item_set_submenu");
gtk_check_menu_item_new_with_label = dlsym(libgtk, "gtk_check_menu_item_new_with_label");
gtk_check_menu_item_set_active = dlsym(libgtk, "gtk_check_menu_item_set_active");
gtk_widget_set_sensitive = dlsym(libgtk, "gtk_widget_set_sensitive");
gtk_widget_show = dlsym(libgtk, "gtk_widget_show");
gtk_menu_shell_append = dlsym(libgtk, "gtk_menu_shell_append");
g_signal_connect_data = dlsym(libgdk, "g_signal_connect_data");
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 (!gtk_init_check ||
!gtk_main ||
!gtk_menu_new ||
!gtk_separator_menu_item_new ||
!gtk_menu_item_new_with_label ||
!gtk_menu_item_set_submenu ||
!gtk_check_menu_item_new_with_label ||
!gtk_check_menu_item_set_active ||
!gtk_widget_set_sensitive ||
!gtk_widget_show ||
!gtk_menu_shell_append ||
!g_signal_connect_data ||
!app_indicator_new ||
!app_indicator_set_status ||
!app_indicator_set_icon ||
!app_indicator_set_menu) {
quit_gtk();
return SDL_SetError("Could not load GTK/AppIndicator functions");
}
if (gtk_init_check(0, NULL) == FALSE) {
quit_gtk();
return SDL_SetError("Could not init GTK");
}
gtk_is_init = SDL_TRUE;
SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
return SDL_TRUE;
}
struct SDL_TrayMenu {
SDL_Tray *tray;
GtkMenuShell *menu;
};
struct SDL_TrayEntry {
SDL_Tray *tray;
SDL_TrayMenu *menu;
GtkWidget *item;
SDL_TrayCallback callback;
void *userdata;
SDL_TrayMenu submenu;
};
struct SDL_Tray {
AppIndicator *indicator;
SDL_TrayMenu menu;
char icon_path[256];
size_t nEntries;
SDL_TrayEntry **entries;
};
static void call_callback(GtkMenuItem *item, gpointer ptr)
{
SDL_TrayEntry *entry = ptr;
if (entry->callback) {
entry->callback(entry->userdata, entry);
}
}
/* TODO: Replace this with a safer alternative */
static SDL_bool get_tmp_filename(char *buffer, size_t size)
{
static int count = 0;
if (size < 64) {
return SDL_SetError("Can't create temporary file for icon: size %ld < 64", size);
}
int would_have_written = SDL_snprintf(buffer, size, "/tmp/sdl_appindicator_icon_%d.bmp", count++);
return would_have_written > 0 && would_have_written < size - 1;
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
if (init_gtk() != SDL_TRUE) {
return NULL;
}
SDL_Tray *tray = (SDL_Tray *) SDL_malloc(sizeof(SDL_Tray));
if (!tray) {
return NULL;
}
SDL_memset((void *) tray, 0, sizeof(*tray));
get_tmp_filename(tray->icon_path, sizeof(tray->icon_path));
SDL_SaveBMP(icon, tray->icon_path);
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);
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();
tray->menu.tray = tray;
app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu.menu));
return &tray->menu;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
entry->submenu.tray = entry->tray;
entry->submenu.menu = (GtkMenuShell *)gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu.menu));
return &entry->submenu;
}
SDL_TrayEntry *SDL_AppendTrayEntry(SDL_TrayMenu *menu, const char *label, SDL_TrayEntryFlags flags)
{
SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
if (!entry) {
return NULL;
}
SDL_memset((void *) entry, 0, sizeof(*entry));
entry->tray = menu->tray;
entry->menu = menu;
if (flags & SDL_TRAYENTRY_CHECKBOX) {
entry->item = gtk_check_menu_item_new_with_label(label);
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), !!(flags & SDL_TRAYENTRY_CHECKED));
} else {
entry->item = gtk_menu_item_new_with_label(label);
}
gtk_widget_set_sensitive(entry->item, !(flags & SDL_TRAYENTRY_DISABLED));
SDL_TrayEntry **new_entries = (SDL_TrayEntry **) SDL_realloc(entry->tray->entries, (entry->tray->nEntries + 1) * sizeof(SDL_TrayEntry **));
if (!new_entries) {
SDL_free(entry);
return NULL;
}
new_entries[entry->tray->nEntries] = entry;
entry->tray->entries = new_entries;
entry->tray->nEntries++;
gtk_widget_show(entry->item);
gtk_menu_shell_append(menu->menu, entry->item);
return entry;
}
void SDL_AppendTraySeparator(SDL_TrayMenu *menu)
{
GtkWidget *item = gtk_separator_menu_item_new();
gtk_widget_show(item);
gtk_menu_shell_append(menu->menu, item);
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked)
{
SDL_Unsupported();
}
SDL_bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
SDL_Unsupported();
return false;
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, SDL_bool enabled)
{
SDL_Unsupported();
}
SDL_bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
SDL_Unsupported();
return false;
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
entry->callback = callback;
entry->userdata = userdata;
g_signal_connect(entry->item, "activate", G_CALLBACK(call_callback), entry);
}
void SDL_DestroyTray(SDL_Tray *tray)
{
for (int i = 0; i < tray->nEntries; i++) {
SDL_free(tray->entries[i]);
}
SDL_free(tray->entries);
SDL_free(tray);
}

381
src/tray/windows/SDL_tray.c Normal file
View File

@ -0,0 +1,381 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
#include <windows.h>
#include <shellapi.h>
#include <stdlib.h>
#define WM_TRAYICON (WM_USER + 1)
struct SDL_TrayMenu {
SDL_Tray *tray;
HMENU hMenu;
};
struct SDL_TrayEntry {
SDL_Tray *tray;
SDL_TrayMenu *menu;
UINT_PTR id;
SDL_TrayCallback callback;
void *userdata;
SDL_TrayMenu submenu;
};
struct SDL_Tray {
NOTIFYICONDATAW nid;
HWND hwnd;
HICON icon;
SDL_TrayMenu menu;
size_t nEntries;
SDL_TrayEntry **entries;
};
static UINT_PTR get_next_id(void)
{
static UINT_PTR next_id = 0;
return ++next_id;
}
LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (!tray) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_TRAYICON:
if (lParam == WM_RBUTTONUP || lParam == WM_LBUTTONUP) {
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(hwnd);
TrackPopupMenu(tray->menu.hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, pt.x, pt.y, 0, hwnd, NULL);
}
break;
case WM_COMMAND:
for (int i = 0; i < tray->nEntries; i++) {
if (tray->entries[i]->id == LOWORD(wParam)) {
SDL_TrayEntry *entry = tray->entries[i];
if (entry->callback) {
entry->callback(entry->userdata, entry);
}
break;
}
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
HICON CreateIconFromRGBA(int width, int height, const BYTE* rgbaData) {
// Create a BITMAPINFO structure
BITMAPINFO bmpInfo;
ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = width;
bmpInfo.bmiHeader.biHeight = -height; // Negative to indicate a top-down bitmap
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32; // 32 bits for RGBA
bmpInfo.bmiHeader.biCompression = BI_RGB;
// Create a DIB section
HDC hdc = GetDC(NULL);
void* pBits = NULL;
HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0);
if (!hBitmap) {
ReleaseDC(NULL, hdc);
return NULL; // Handle error
}
// Copy the RGBA data to the bitmap
memcpy(pBits, rgbaData, width * height * 4); // 4 bytes per pixel
// Create a mask bitmap (1 bit per pixel)
HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL);
if (!hMask) {
DeleteObject(hBitmap);
ReleaseDC(NULL, hdc);
return NULL; // Handle error
}
// Create a compatible DC for the mask
HDC hdcMem = CreateCompatibleDC(hdc);
HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask);
// Create the mask based on the alpha channel
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4;
BYTE alpha = pixel[3]; // Alpha channel
// Set the mask pixel: 0 for transparent, 1 for opaque
COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255);
SetPixel(hdcMem, x, y, maskColor);
}
}
// Create the icon using CreateIconIndirect
ICONINFO iconInfo;
iconInfo.fIcon = TRUE; // TRUE for icon, FALSE for cursor
iconInfo.xHotspot = 0; // Hotspot x
iconInfo.yHotspot = 0; // Hotspot y
iconInfo.hbmMask = hMask; // Mask bitmap
iconInfo.hbmColor = hBitmap; // Color bitmap
HICON hIcon = CreateIconIndirect(&iconInfo);
// Clean up
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hBitmap);
DeleteObject(hMask);
ReleaseDC(NULL, hdc);
return hIcon;
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
SDL_Tray *tray = SDL_malloc(sizeof(SDL_Tray));
if (!tray) {
return NULL;
}
tray->hwnd = NULL;
tray->menu.tray = tray;
tray->menu.hMenu = NULL;
tray->nEntries = 0;
tray->entries = NULL;
char classname[32];
SDL_snprintf(classname, sizeof(classname), "SDLTray%d", (unsigned int) get_next_id());
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASS wc;
ZeroMemory(&wc, sizeof(WNDCLASS));
wc.lpfnWndProc = TrayWindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = classname;
RegisterClass(&wc);
tray->hwnd = CreateWindowEx(0, classname, "", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
ShowWindow(tray->hwnd, SW_HIDE);
ZeroMemory(&tray->nid, sizeof(NOTIFYICONDATAW));
tray->nid.cbSize = sizeof(NOTIFYICONDATAW);
tray->nid.hWnd = tray->hwnd;
tray->nid.uID = 1;
tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
tray->nid.uCallbackMessage = WM_TRAYICON;
mbstowcs_s(NULL, tray->nid.szTip, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip), tooltip, _TRUNCATE);
if (icon) {
SDL_Surface *iconfmt = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
if (!iconfmt) {
goto no_icon;
}
tray->nid.hIcon = CreateIconFromRGBA(iconfmt->w, iconfmt->h, iconfmt->pixels);
tray->icon = tray->nid.hIcon;
} else {
no_icon:
tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
tray->icon = tray->nid.hIcon;
}
Shell_NotifyIconW(NIM_ADD, &tray->nid);
SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray);
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();
return &tray->menu;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
return &entry->submenu;
}
SDL_TrayEntry *SDL_AppendTrayEntry(SDL_TrayMenu *menu, const char *label, SDL_TrayEntryFlags flags)
{
SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
if (!entry) {
return NULL;
}
entry->tray = menu->tray;
entry->menu = menu;
entry->id = get_next_id();
entry->callback = NULL;
entry->userdata = NULL;
entry->submenu.tray = entry->tray;
entry->submenu.hMenu = NULL;
SDL_TrayEntry **new_entries = (SDL_TrayEntry **) SDL_realloc(entry->tray->entries, (entry->tray->nEntries + 1) * sizeof(SDL_TrayEntry **));
if (!new_entries) {
SDL_free(entry);
return NULL;
}
new_entries[entry->tray->nEntries] = entry;
entry->tray->entries = new_entries;
entry->tray->nEntries++;
UINT mf = MF_STRING;
if (flags & SDL_TRAYENTRY_SUBMENU) {
mf = MF_POPUP;
entry->submenu.tray = entry->tray;
entry->submenu.hMenu = CreatePopupMenu();
entry->id = (UINT_PTR) entry->submenu.hMenu;
}
if (flags & SDL_TRAYENTRY_DISABLED) {
mf |= MF_DISABLED | MF_GRAYED;
}
if (flags & SDL_TRAYENTRY_CHECKED) {
mf |= MF_CHECKED;
}
AppendMenuA(entry->menu->hMenu, mf, entry->id, label);
return entry;
}
void SDL_AppendTraySeparator(SDL_TrayMenu *menu)
{
AppendMenu(menu->hMenu, MF_SEPARATOR, 0, NULL);
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked)
{
CheckMenuItem(entry->menu->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
}
SDL_bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE;
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, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
}
SDL_bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE;
GetMenuItemInfo(entry->menu->hMenu, (UINT) entry->id, FALSE, &mii);
return !!(mii.fState & MFS_ENABLED);
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
entry->callback = callback;
entry->userdata = userdata;
}
void SDL_DestroyTray(SDL_Tray *tray)
{
Shell_NotifyIconW(NIM_DELETE, &tray->nid);
for (int i = 0; i < tray->nEntries; i++) {
if (tray->entries[i]->submenu.hMenu) {
DestroyMenu(tray->entries[i]->submenu.hMenu);
}
SDL_free(tray->entries[i]);
}
SDL_free(tray->entries);
if (tray->menu.hMenu) {
DestroyMenu(tray->menu.hMenu);
}
if (tray->icon) {
DestroyIcon(tray->icon);
}
SDL_free(tray);
}

56
test/testtray.c Normal file
View File

@ -0,0 +1,56 @@
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
void SDLCALL trayprint(void *ptr, SDL_TrayEntry *entry)
{
SDL_Log("%s\n", (const char *) ptr);
}
void SDLCALL trayhello(void *ptr, SDL_TrayEntry *entry)
{
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
}
void SDLCALL trayexit(void *ptr, SDL_TrayEntry *entry)
{
SDL_Event e;
e.type = SDL_EVENT_QUIT;
SDL_PushEvent(&e);
}
int main(int argc, char **argv)
{
SDL_Init(SDL_INIT_VIDEO);
SDL_Surface *icon = SDL_LoadBMP("../test/trashcan.bmp");
SDL_Tray *tray = SDL_CreateTray(icon, "Hello!");
SDL_TrayMenu *menu = SDL_CreateTrayMenu(tray);
SDL_TrayEntry *entry = SDL_AppendTrayEntry(menu, "Hello!", SDL_TRAYENTRY_CHECKBOX | SDL_TRAYENTRY_CHECKED);
SDL_TrayEntry *entrysm = SDL_AppendTrayEntry(menu, "Submenu", SDL_TRAYENTRY_SUBMENU);
SDL_TrayEntry *entryd = SDL_AppendTrayEntry(menu, "Disabled", SDL_TRAYENTRY_BUTTON | SDL_TRAYENTRY_DISABLED);
SDL_AppendTraySeparator(menu);
SDL_TrayEntry *entry2 = SDL_AppendTrayEntry(menu, "Exit", SDL_TRAYENTRY_BUTTON);
SDL_SetTrayEntryCallback(entry, trayhello, NULL);
SDL_SetTrayEntryCallback(entry2, trayexit, NULL);
SDL_TrayMenu *sm = SDL_CreateTraySubmenu(entrysm);
SDL_TrayEntry *sme1 = SDL_AppendTrayEntry(sm, "Hello", SDL_TRAYENTRY_BUTTON);
SDL_TrayEntry *sme2 = SDL_AppendTrayEntry(sm, "World", SDL_TRAYENTRY_BUTTON);
SDL_SetTrayEntryCallback(sme1, trayprint, "Hello,");
SDL_SetTrayEntryCallback(sme2, trayprint, "world!");
SDL_DestroySurface(icon);
SDL_Event e;
while (SDL_WaitEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
break;
}
}
SDL_DestroyTray(tray);
SDL_Quit();
return 0;
}