Skip to content

Commit

Permalink
Add system tray support
Browse files Browse the repository at this point in the history
  • Loading branch information
Semphris committed Sep 16, 2024
1 parent c499f79 commit 8c23834
Show file tree
Hide file tree
Showing 11 changed files with 1,227 additions and 0 deletions.
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions include/SDL3/SDL.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
76 changes: 76 additions & 0 deletions include/SDL3/SDL_tray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <[email protected]>
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;

typedef enum {
/* Mandatory; must be specified at creation time */
SDL_TRAYENTRY_BUTTON = 0,
SDL_TRAYENTRY_CHECKBOX = (1 << 0),
SDL_TRAYENTRY_SUBMENU = (1 << 1),

/* Optional; can be changed later */
SDL_TRAYENTRY_DISABLED = (1 << 16),
SDL_TRAYENTRY_CHECKED = (1 << 17),
} SDL_TrayEntryFlags;

typedef void (*SDL_TrayCallback)(void *userdata, SDL_TrayEntry *entry);

extern SDL_DECLSPEC SDL_Tray *SDLCALL SDL_CreateTray(SDL_Surface *icon, const char *tooltip);
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTrayMenu(SDL_Tray *tray);
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTraySubmenu(SDL_TrayEntry *entry);
extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_AppendTrayEntry(SDL_TrayMenu *menu, const char *label, SDL_TrayEntryFlags flags);
extern SDL_DECLSPEC void SDLCALL SDL_AppendTraySeparator(SDL_TrayMenu *menu);
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, SDL_bool checked);
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_GetTrayEntryChecked(SDL_TrayEntry *entry);
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, SDL_bool enabled);
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry);
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata);
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_ */
11 changes: 11 additions & 0 deletions src/dynapi/SDL_dynapi.sym
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,17 @@ 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;
# extra symbols go here (don't modify this line)
local: *;
};
11 changes: 11 additions & 0 deletions src/dynapi/SDL_dynapi_overrides.h
Original file line number Diff line number Diff line change
Expand Up @@ -1192,3 +1192,14 @@
#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
11 changes: 11 additions & 0 deletions src/dynapi/SDL_dynapi_procs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1198,3 +1198,14 @@ 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),)
222 changes: 222 additions & 0 deletions src/tray/cocoa/SDL_tray.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <[email protected]>
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 (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;
}

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);
}
Loading

0 comments on commit 8c23834

Please sign in to comment.