mirror of https://github.com/libsdl-org/SDL
Merge 921c3e3c6c
into 88a01fbc96
This commit is contained in:
commit
161efb1d1d
|
@ -242,6 +242,7 @@ define_sdl_subsystem(Hidapi)
|
|||
define_sdl_subsystem(Power)
|
||||
define_sdl_subsystem(Sensor)
|
||||
define_sdl_subsystem(Dialog)
|
||||
define_sdl_subsystem(Tray)
|
||||
|
||||
cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF)
|
||||
if(SDL_FRAMEWORK)
|
||||
|
@ -2909,6 +2910,19 @@ int main(void)
|
|||
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
|
||||
|
||||
if(SDL_VIDEO)
|
||||
|
@ -3002,6 +3016,10 @@ if(NOT HAVE_SDL_PROCESS)
|
|||
set(SDL_PROCESS_DUMMY 1)
|
||||
sdl_glob_sources(${SDL3_SOURCE_DIR}/src/process/dummy/*.c)
|
||||
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)
|
||||
set(SDL_CAMERA_DRIVER_DUMMY 1)
|
||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c")
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
#include <SDL3/SDL_thread.h>
|
||||
#include <SDL3/SDL_time.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <SDL3/SDL_tray.h>
|
||||
#include <SDL3/SDL_touch.h>
|
||||
#include <SDL3/SDL_version.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
|
|
@ -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_ */
|
|
@ -1171,6 +1171,19 @@ SDL3_0.0.0 {
|
|||
SDL_wcsnstr;
|
||||
SDL_wcsstr;
|
||||
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)
|
||||
local: *;
|
||||
};
|
||||
|
|
|
@ -1196,3 +1196,16 @@
|
|||
#define SDL_wcsnstr SDL_wcsnstr_REAL
|
||||
#define SDL_wcsstr SDL_wcsstr_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
|
||||
|
|
|
@ -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_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(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),)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue