mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-05-25 14:09:10 +00:00
Add system tray support (#10873)
This commit is contained in:
parent
17a029502a
commit
01b9b0edb7
20 changed files with 3125 additions and 1 deletions
|
@ -78,6 +78,7 @@ LOCAL_SRC_FILES := \
|
||||||
$(wildcard $(LOCAL_PATH)/src/time/unix/*.c) \
|
$(wildcard $(LOCAL_PATH)/src/time/unix/*.c) \
|
||||||
$(wildcard $(LOCAL_PATH)/src/timer/*.c) \
|
$(wildcard $(LOCAL_PATH)/src/timer/*.c) \
|
||||||
$(wildcard $(LOCAL_PATH)/src/timer/unix/*.c) \
|
$(wildcard $(LOCAL_PATH)/src/timer/unix/*.c) \
|
||||||
|
$(wildcard $(LOCAL_PATH)/src/tray/dummy/*.c) \
|
||||||
$(wildcard $(LOCAL_PATH)/src/video/*.c) \
|
$(wildcard $(LOCAL_PATH)/src/video/*.c) \
|
||||||
$(wildcard $(LOCAL_PATH)/src/video/android/*.c) \
|
$(wildcard $(LOCAL_PATH)/src/video/android/*.c) \
|
||||||
$(wildcard $(LOCAL_PATH)/src/video/yuv2rgb/*.c))
|
$(wildcard $(LOCAL_PATH)/src/video/yuv2rgb/*.c))
|
||||||
|
|
|
@ -1567,6 +1567,9 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
|
||||||
CheckVivante()
|
CheckVivante()
|
||||||
CheckVulkan()
|
CheckVulkan()
|
||||||
CheckQNXScreen()
|
CheckQNXScreen()
|
||||||
|
|
||||||
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/unix/*.c")
|
||||||
|
set(HAVE_SDL_TRAY TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
|
@ -2075,6 +2078,9 @@ elseif(WINDOWS)
|
||||||
set(HAVE_RENDER_VULKAN TRUE)
|
set(HAVE_RENDER_VULKAN TRUE)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/windows/*.c")
|
||||||
|
set(HAVE_SDL_TRAY TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(SDL_HIDAPI)
|
if(SDL_HIDAPI)
|
||||||
|
@ -2351,6 +2357,11 @@ elseif(APPLE)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(MACOS)
|
||||||
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m")
|
||||||
|
set(HAVE_SDL_TRAY TRUE)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Minimum version for $<LINK_LIBRARY:feature,library-list>
|
# Minimum version for $<LINK_LIBRARY:feature,library-list>
|
||||||
|
@ -2973,6 +2984,8 @@ if(SDL_VIDEO)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
sdl_glob_sources(${SDL3_SOURCE_DIR}/src/tray/*.c)
|
||||||
|
|
||||||
if(SDL_GPU)
|
if(SDL_GPU)
|
||||||
if(HAVE_D3D11_H)
|
if(HAVE_D3D11_H)
|
||||||
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d11/*.c")
|
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d11/*.c")
|
||||||
|
@ -3055,6 +3068,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_glob_sources(${SDL3_SOURCE_DIR}/src/tray/dummy/*.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")
|
||||||
|
|
|
@ -592,6 +592,7 @@
|
||||||
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
|
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
|
||||||
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
|
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
|
||||||
|
<ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
|
||||||
|
@ -827,6 +828,16 @@
|
||||||
<ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
|
<ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
|
||||||
<ClCompile Include="..\..\src\time\SDL_time.c" />
|
<ClCompile Include="..\..\src\time\SDL_time.c" />
|
||||||
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
|
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
|
||||||
|
<ClCompile Include="..\..\src\tray\dummy\SDL_tray.c">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Desktop.x64'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Desktop.x64'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Xbox.Scarlett.x64'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Xbox.Scarlett.x64'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Xbox.XboxOne.x64'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Xbox.XboxOne.x64'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
|
||||||
|
@ -855,6 +866,7 @@
|
||||||
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
|
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
|
||||||
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
|
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
|
||||||
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
|
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
|
||||||
|
<ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
|
||||||
|
@ -889,4 +901,4 @@
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -181,6 +181,7 @@
|
||||||
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
|
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
|
||||||
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
|
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
|
||||||
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
|
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
|
||||||
|
<ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
|
||||||
|
@ -217,6 +218,8 @@
|
||||||
<ClCompile Include="..\..\src\storage\SDL_storage.c" />
|
<ClCompile Include="..\..\src\storage\SDL_storage.c" />
|
||||||
<ClCompile Include="..\..\src\time\SDL_time.c" />
|
<ClCompile Include="..\..\src\time\SDL_time.c" />
|
||||||
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
|
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
|
||||||
|
<ClCompile Include="..\..\src\tray\dummy\SDL_tray.c" />
|
||||||
|
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c" />
|
||||||
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_lsx.c" />
|
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_lsx.c" />
|
||||||
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_sse.c" />
|
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_sse.c" />
|
||||||
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_std.c" />
|
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_std.c" />
|
||||||
|
@ -435,6 +438,7 @@
|
||||||
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
|
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
|
||||||
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
|
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
|
||||||
|
<ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
|
||||||
|
|
|
@ -489,6 +489,7 @@
|
||||||
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
|
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
|
||||||
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
|
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
|
||||||
|
<ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
|
||||||
|
@ -671,6 +672,7 @@
|
||||||
<ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
|
<ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
|
||||||
<ClCompile Include="..\..\src\time\SDL_time.c" />
|
<ClCompile Include="..\..\src\time\SDL_time.c" />
|
||||||
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
|
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
|
||||||
|
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c" />
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
|
||||||
|
@ -701,6 +703,7 @@
|
||||||
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
|
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
|
||||||
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
|
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
|
||||||
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
|
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
|
||||||
|
<ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
|
||||||
|
|
|
@ -690,6 +690,9 @@
|
||||||
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_std_func.h">
|
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_std_func.h">
|
||||||
<Filter>video\yuv2rgb</Filter>
|
<Filter>video\yuv2rgb</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h">
|
||||||
|
<Filter>video\windows</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h">
|
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h">
|
||||||
<Filter>video\windows</Filter>
|
<Filter>video\windows</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -1229,6 +1232,9 @@
|
||||||
<ClCompile Include="..\..\src\time\windows\SDL_systime.c">
|
<ClCompile Include="..\..\src\time\windows\SDL_systime.c">
|
||||||
<Filter>time\windows</Filter>
|
<Filter>time\windows</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c">
|
||||||
|
<Filter>video</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\video\SDL_RLEaccel.c">
|
<ClCompile Include="..\..\src\video\SDL_RLEaccel.c">
|
||||||
<Filter>video</Filter>
|
<Filter>video</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -1301,6 +1307,9 @@
|
||||||
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c">
|
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c">
|
||||||
<Filter>video\dummy</Filter>
|
<Filter>video\dummy</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c">
|
||||||
|
<Filter>video\windows</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c">
|
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c">
|
||||||
<Filter>video\windows</Filter>
|
<Filter>video\windows</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
|
@ -81,6 +81,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>
|
||||||
|
|
431
include/SDL3/SDL_tray.h
Normal file
431
include/SDL3/SDL_tray.h
Normal file
|
@ -0,0 +1,431 @@
|
||||||
|
/*
|
||||||
|
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 required; 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 required flag.
|
||||||
|
*
|
||||||
|
* \since This datatype is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
*/
|
||||||
|
typedef Uint32 SDL_TrayEntryFlags;
|
||||||
|
|
||||||
|
#define SDL_TRAYENTRY_BUTTON 0x00000001u /**< Make the entry a simple button. Required. */
|
||||||
|
#define SDL_TRAYENTRY_CHECKBOX 0x00000002u /**< Make the entry a checkbox. Required. */
|
||||||
|
#define SDL_TRAYENTRY_SUBMENU 0x00000004u /**< Prepare the entry to have a submenu. Required */
|
||||||
|
#define SDL_TRAYENTRY_DISABLED 0x80000000u /**< Make the entry disabled. Optional. */
|
||||||
|
#define SDL_TRAYENTRY_CHECKED 0x40000000u /**< Make the entry checked. This is valid only for checkboxes. Optional. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* \sa SDL_SetTrayEntryCallback
|
||||||
|
*/
|
||||||
|
typedef void (SDLCALL *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. Avoid needlessly creating a tray icon, as the user may
|
||||||
|
* feel like it clutters their interface.
|
||||||
|
*
|
||||||
|
* Using tray icons require the video subsystem.
|
||||||
|
*
|
||||||
|
* \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_CreateTrayMenu
|
||||||
|
* \sa SDL_GetTrayMenu
|
||||||
|
* \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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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_GetTrayMenu
|
||||||
|
* \sa SDL_GetTrayMenuParentTray
|
||||||
|
*/
|
||||||
|
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_InsertTrayEntryAt
|
||||||
|
* \sa SDL_GetTraySubmenu
|
||||||
|
* \sa SDL_GetTrayMenuParentEntry
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTraySubmenu(SDL_TrayEntry *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a previously created tray menu.
|
||||||
|
*
|
||||||
|
* You should have called SDL_CreateTrayMenu() on the tray object. This
|
||||||
|
* function allows you to fetch it again later.
|
||||||
|
*
|
||||||
|
* This function does the same thing as SDL_GetTraySubmenu(), 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 entry 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_CreateTrayMenu
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_GetTrayMenu(SDL_Tray *tray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a previously created tray entry submenu.
|
||||||
|
*
|
||||||
|
* You should have called SDL_CreateTraySubenu() on the entry object. This
|
||||||
|
* function allows you to fetch it again later.
|
||||||
|
*
|
||||||
|
* This function does the same thing as SDL_GetTrayMenu(), 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_InsertTrayEntryAt
|
||||||
|
* \sa SDL_CreateTraySubmenu
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_GetTraySubmenu(SDL_TrayEntry *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of entries in the menu, in order.
|
||||||
|
*
|
||||||
|
* \param menu The menu to get entries from.
|
||||||
|
* \param size An optional pointer to obtain the number of entries in the menu.
|
||||||
|
* \returns the entries within the given menu. The pointer becomes invalid when
|
||||||
|
* any function that inserts or deletes entries in the menu is called.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_RemoveTrayEntry
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC const SDL_TrayEntry **SDLCALL SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a tray entry.
|
||||||
|
*
|
||||||
|
* \param entry The entry to be deleted.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC void SDLCALL SDL_RemoveTrayEntry(SDL_TrayEntry *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a tray entry at a given position.
|
||||||
|
*
|
||||||
|
* If label is NULL, the entry will be a separator. Many functions won't work
|
||||||
|
* for an entry that is a separator.
|
||||||
|
*
|
||||||
|
* 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 pos the desired position for the new entry. Entries at or following
|
||||||
|
* this place will be moved. If pos is -1, the entry is appended.
|
||||||
|
* \param label the text to be displayed on the entry, or NULL for a separator.
|
||||||
|
* \param flags a combination of flags, some of which are mandatory.
|
||||||
|
* \returns the newly created entry, or NULL if pos is out of bounds.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_TrayEntryFlags
|
||||||
|
* \sa SDL_GetTrayEntries
|
||||||
|
* \sa SDL_RemoveTrayEntry
|
||||||
|
* \sa SDL_GetTrayEntryParent
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the label of an entry.
|
||||||
|
*
|
||||||
|
* An entry cannot change between a separator and an ordinary entry; that is,
|
||||||
|
* it is not possible to set a non-NULL label on an entry that has a NULL label
|
||||||
|
* (separators), or to set a NULL label to an entry that has a non-NULL label.
|
||||||
|
* The function will silently fail if that happens.
|
||||||
|
*
|
||||||
|
* \param entry the entry to be updated.
|
||||||
|
* \param label the new label for the entry.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
* \sa SDL_GetTrayEntryLabel
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the label of an entry.
|
||||||
|
*
|
||||||
|
* If the returned value is NULL, the entry is a separator.
|
||||||
|
*
|
||||||
|
* \param entry the entry to be read.
|
||||||
|
* \returns the label of the entry.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
* \sa SDL_SetTrayEntryLabel
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC const char *SDLCALL SDL_GetTrayEntryLabel(SDL_TrayEntry *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
* \sa SDL_GetTrayEntryChecked
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, 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_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
* \sa SDL_SetTrayEntryChecked
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC 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_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
* \sa SDL_GetTrayEntryEnabled
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, 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_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
* \sa SDL_SetTrayEntryEnabled
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC 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_GetTrayEntries
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the menu contianing a certain tray entry.
|
||||||
|
*
|
||||||
|
* \param entry the entry for which to get the parent menu.
|
||||||
|
* \returns the parent menu.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_InsertTrayEntryAt
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_GetTrayEntryParent(SDL_TrayEntry *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the entry for which the menu is a submenu, if the current menu is a
|
||||||
|
* submenu.
|
||||||
|
*
|
||||||
|
* Either this function or SDL_GetTrayMenuParentTray() will return non-NULL for
|
||||||
|
* any given menu.
|
||||||
|
*
|
||||||
|
* \param menu the menu for which to get the parent entry.
|
||||||
|
* \returns the parent entry, or NULL if this menu is not a submenu.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_CreateTraySubmenu
|
||||||
|
* \sa SDL_GetTrayMenuParentTray
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the tray for which this menu is the first-level menu, if the current
|
||||||
|
* menu isn't a submenu.
|
||||||
|
*
|
||||||
|
* Either this function or SDL_GetTrayMenuParentEntry() will return non-NULL for
|
||||||
|
* any given menu.
|
||||||
|
*
|
||||||
|
* \param menu the menu for which to get the parent enttrayry.
|
||||||
|
* \returns the parent tray, or NULL if this menu is a submenu.
|
||||||
|
*
|
||||||
|
* \since This function is available since SDL 3.0.0.
|
||||||
|
*
|
||||||
|
* \sa SDL_CreateTrayMenu
|
||||||
|
* \sa SDL_GetTrayMenuParentEntry
|
||||||
|
*/
|
||||||
|
extern SDL_DECLSPEC SDL_Tray *SDLCALL SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu);
|
||||||
|
|
||||||
|
/* Ends C function definitions when using C++ */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#include <SDL3/SDL_close_code.h>
|
||||||
|
|
||||||
|
#endif /* SDL_tray_h_ */
|
|
@ -1208,6 +1208,27 @@ SDL3_0.0.0 {
|
||||||
SDL_RenderTextureAffine;
|
SDL_RenderTextureAffine;
|
||||||
SDL_WaitAndAcquireGPUSwapchainTexture;
|
SDL_WaitAndAcquireGPUSwapchainTexture;
|
||||||
SDL_RenderDebugTextFormat;
|
SDL_RenderDebugTextFormat;
|
||||||
|
SDL_CreateTray;
|
||||||
|
SDL_SetTrayIcon;
|
||||||
|
SDL_SetTrayTooltip;
|
||||||
|
SDL_CreateTrayMenu;
|
||||||
|
SDL_CreateTraySubmenu;
|
||||||
|
SDL_GetTrayMenu;
|
||||||
|
SDL_GetTraySubmenu;
|
||||||
|
SDL_GetTrayEntries;
|
||||||
|
SDL_RemoveTrayEntry;
|
||||||
|
SDL_InsertTrayEntryAt;
|
||||||
|
SDL_SetTrayEntryLabel;
|
||||||
|
SDL_GetTrayEntryLabel;
|
||||||
|
SDL_SetTrayEntryChecked;
|
||||||
|
SDL_GetTrayEntryChecked;
|
||||||
|
SDL_SetTrayEntryEnabled;
|
||||||
|
SDL_GetTrayEntryEnabled;
|
||||||
|
SDL_SetTrayEntryCallback;
|
||||||
|
SDL_DestroyTray;
|
||||||
|
SDL_GetTrayEntryParent;
|
||||||
|
SDL_GetTrayMenuParentEntry;
|
||||||
|
SDL_GetTrayMenuParentTray;
|
||||||
# extra symbols go here (don't modify this line)
|
# extra symbols go here (don't modify this line)
|
||||||
local: *;
|
local: *;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1233,3 +1233,24 @@
|
||||||
#define SDL_RenderTextureAffine SDL_RenderTextureAffine_REAL
|
#define SDL_RenderTextureAffine SDL_RenderTextureAffine_REAL
|
||||||
#define SDL_WaitAndAcquireGPUSwapchainTexture SDL_WaitAndAcquireGPUSwapchainTexture_REAL
|
#define SDL_WaitAndAcquireGPUSwapchainTexture SDL_WaitAndAcquireGPUSwapchainTexture_REAL
|
||||||
#define SDL_RenderDebugTextFormat SDL_RenderDebugTextFormat_REAL
|
#define SDL_RenderDebugTextFormat SDL_RenderDebugTextFormat_REAL
|
||||||
|
#define SDL_CreateTray SDL_CreateTray_REAL
|
||||||
|
#define SDL_SetTrayIcon SDL_SetTrayIcon_REAL
|
||||||
|
#define SDL_SetTrayTooltip SDL_SetTrayTooltip_REAL
|
||||||
|
#define SDL_CreateTrayMenu SDL_CreateTrayMenu_REAL
|
||||||
|
#define SDL_CreateTraySubmenu SDL_CreateTraySubmenu_REAL
|
||||||
|
#define SDL_GetTrayMenu SDL_GetTrayMenu_REAL
|
||||||
|
#define SDL_GetTraySubmenu SDL_GetTraySubmenu_REAL
|
||||||
|
#define SDL_GetTrayEntries SDL_GetTrayEntries_REAL
|
||||||
|
#define SDL_RemoveTrayEntry SDL_RemoveTrayEntry_REAL
|
||||||
|
#define SDL_InsertTrayEntryAt SDL_InsertTrayEntryAt_REAL
|
||||||
|
#define SDL_SetTrayEntryLabel SDL_SetTrayEntryLabel_REAL
|
||||||
|
#define SDL_GetTrayEntryLabel SDL_GetTrayEntryLabel_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_GetTrayEntryParent SDL_GetTrayEntryParent_REAL
|
||||||
|
#define SDL_GetTrayMenuParentEntry SDL_GetTrayMenuParentEntry_REAL
|
||||||
|
#define SDL_GetTrayMenuParentTray SDL_GetTrayMenuParentTray_REAL
|
||||||
|
|
|
@ -1241,3 +1241,24 @@ SDL_DYNAPI_PROC(bool,SDL_WaitAndAcquireGPUSwapchainTexture,(SDL_GPUCommandBuffer
|
||||||
#ifndef SDL_DYNAPI_PROC_NO_VARARGS
|
#ifndef SDL_DYNAPI_PROC_NO_VARARGS
|
||||||
SDL_DYNAPI_PROC(bool,SDL_RenderDebugTextFormat,(SDL_Renderer *a,float b,float c,SDL_PRINTF_FORMAT_STRING const char *d,...),(a,b,c,d),return)
|
SDL_DYNAPI_PROC(bool,SDL_RenderDebugTextFormat,(SDL_Renderer *a,float b,float c,SDL_PRINTF_FORMAT_STRING const char *d,...),(a,b,c,d),return)
|
||||||
#endif
|
#endif
|
||||||
|
SDL_DYNAPI_PROC(SDL_Tray*,SDL_CreateTray,(SDL_Surface *a,const char *b),(a,b),return)
|
||||||
|
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),)
|
||||||
|
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_TrayMenu*,SDL_GetTrayMenu,(SDL_Tray *a),(a),return)
|
||||||
|
SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_GetTraySubmenu,(SDL_TrayEntry *a),(a),return)
|
||||||
|
SDL_DYNAPI_PROC(const SDL_TrayEntry**,SDL_GetTrayEntries,(SDL_TrayMenu *a,int *b),(a,b),return)
|
||||||
|
SDL_DYNAPI_PROC(void,SDL_RemoveTrayEntry,(SDL_TrayEntry *a),(a),)
|
||||||
|
SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_InsertTrayEntryAt,(SDL_TrayMenu *a,int b,const char *c,SDL_TrayEntryFlags d),(a,b,c,d),return)
|
||||||
|
SDL_DYNAPI_PROC(void,SDL_SetTrayEntryLabel,(SDL_TrayEntry *a,const char *b),(a,b),)
|
||||||
|
SDL_DYNAPI_PROC(const char*,SDL_GetTrayEntryLabel,(SDL_TrayEntry *a),(a),return)
|
||||||
|
SDL_DYNAPI_PROC(void,SDL_SetTrayEntryChecked,(SDL_TrayEntry *a,bool b),(a,b),)
|
||||||
|
SDL_DYNAPI_PROC(bool,SDL_GetTrayEntryChecked,(SDL_TrayEntry *a),(a),return)
|
||||||
|
SDL_DYNAPI_PROC(void,SDL_SetTrayEntryEnabled,(SDL_TrayEntry *a,bool b),(a,b),)
|
||||||
|
SDL_DYNAPI_PROC(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(SDL_TrayMenu*,SDL_GetTrayEntryParent,(SDL_TrayEntry *a),(a),return)
|
||||||
|
SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_GetTrayMenuParentEntry,(SDL_TrayMenu *a),(a),return)
|
||||||
|
SDL_DYNAPI_PROC(SDL_Tray*,SDL_GetTrayMenuParentTray,(SDL_TrayMenu *a),(a),return)
|
||||||
|
|
458
src/tray/cocoa/SDL_tray.m
Normal file
458
src/tray/cocoa/SDL_tray.m
Normal file
|
@ -0,0 +1,458 @@
|
||||||
|
/*
|
||||||
|
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>
|
||||||
|
|
||||||
|
#include "../../video/SDL_surface_c.h"
|
||||||
|
|
||||||
|
/* applicationDockMenu */
|
||||||
|
|
||||||
|
struct SDL_TrayMenu {
|
||||||
|
NSMenu *nsmenu;
|
||||||
|
|
||||||
|
size_t nEntries;
|
||||||
|
SDL_TrayEntry **entries;
|
||||||
|
|
||||||
|
SDL_Tray *parent_tray;
|
||||||
|
SDL_TrayEntry *parent_entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDL_TrayEntry {
|
||||||
|
NSMenuItem *nsitem;
|
||||||
|
|
||||||
|
SDL_TrayEntryFlags flags;
|
||||||
|
SDL_TrayCallback callback;
|
||||||
|
void *userdata;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
|
||||||
|
SDL_TrayMenu *parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDL_Tray {
|
||||||
|
NSStatusBar *statusBar;
|
||||||
|
NSStatusItem *statusItem;
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu;
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
|
||||||
|
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->callback) {
|
||||||
|
entry->callback(entry->userdata, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
static void DestroySDLMenu(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < menu->nEntries; i++) {
|
||||||
|
if (menu->entries[i] && menu->entries[i]->submenu) {
|
||||||
|
DestroySDLMenu(menu->entries[i]->submenu);
|
||||||
|
}
|
||||||
|
SDL_free(menu->entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(menu->entries);
|
||||||
|
|
||||||
|
if (menu->parent_entry) {
|
||||||
|
[menu->parent_entry->parent->nsmenu setSubmenu:nil forItem:menu->parent_entry->nsitem];
|
||||||
|
} else if (menu->parent_tray) {
|
||||||
|
[menu->parent_tray->statusItem setMenu:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
SDL_TrayMenu *menu = SDL_malloc(sizeof(SDL_TrayMenu));
|
||||||
|
|
||||||
|
if (!menu) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_memset((void *) menu, 0, sizeof(*menu));
|
||||||
|
|
||||||
|
NSMenu *nsmenu = [[NSMenu alloc] init];
|
||||||
|
[nsmenu setAutoenablesItems:FALSE];
|
||||||
|
|
||||||
|
[tray->statusItem setMenu:nsmenu];
|
||||||
|
|
||||||
|
tray->menu = menu;
|
||||||
|
menu->nsmenu = nsmenu;
|
||||||
|
menu->nEntries = 0;
|
||||||
|
menu->entries = NULL;
|
||||||
|
menu->parent_tray = tray;
|
||||||
|
menu->parent_entry = NULL;
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
return tray->menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (entry->submenu) {
|
||||||
|
SDL_SetError("Tray entry submenu already exists");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
|
||||||
|
SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu = SDL_malloc(sizeof(SDL_TrayMenu));
|
||||||
|
|
||||||
|
if (!menu) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_memset((void *) menu, 0, sizeof(*menu));
|
||||||
|
|
||||||
|
NSMenu *nsmenu = [[NSMenu alloc] init];
|
||||||
|
[nsmenu setAutoenablesItems:FALSE];
|
||||||
|
|
||||||
|
entry->submenu = menu;
|
||||||
|
menu->nsmenu = nsmenu;
|
||||||
|
menu->nEntries = 0;
|
||||||
|
menu->entries = NULL;
|
||||||
|
menu->parent_tray = NULL;
|
||||||
|
menu->parent_entry = entry;
|
||||||
|
|
||||||
|
[entry->parent->nsmenu setSubmenu:nsmenu forItem:entry->nsitem];
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->submenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
|
||||||
|
{
|
||||||
|
if (size) {
|
||||||
|
*size = menu->nEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (const SDL_TrayEntry **) menu->entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu = entry->parent;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < menu->nEntries - 1; i++) {
|
||||||
|
if (menu->entries[i] == entry) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
menu->entries[i] = menu->entries[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->submenu) {
|
||||||
|
DestroySDLMenu(entry->submenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->nEntries--;
|
||||||
|
SDL_TrayEntry ** new_entries = SDL_realloc(menu->entries, menu->nEntries * sizeof(SDL_TrayEntry *));
|
||||||
|
|
||||||
|
/* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
|
||||||
|
if (new_entries) {
|
||||||
|
menu->entries = new_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
[menu->nsmenu removeItem:entry->nsitem];
|
||||||
|
|
||||||
|
SDL_free(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
|
||||||
|
{
|
||||||
|
if (pos < -1 || pos > (int) menu->nEntries) {
|
||||||
|
SDL_InvalidParamError("pos");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == -1) {
|
||||||
|
pos = menu->nEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_memset((void *) entry, 0, sizeof(*entry));
|
||||||
|
|
||||||
|
SDL_TrayEntry **new_entries = (SDL_TrayEntry **) SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(SDL_TrayEntry *));
|
||||||
|
|
||||||
|
if (!new_entries) {
|
||||||
|
SDL_free(entry);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->entries = new_entries;
|
||||||
|
menu->nEntries++;
|
||||||
|
|
||||||
|
for (int i = menu->nEntries - 1; i > pos; i--) {
|
||||||
|
menu->entries[i] = menu->entries[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entries[pos] = entry;
|
||||||
|
|
||||||
|
NSMenuItem *nsitem;
|
||||||
|
if (label == NULL) {
|
||||||
|
nsitem = [NSMenuItem separatorItem];
|
||||||
|
} else {
|
||||||
|
nsitem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label] action:@selector(menu:) keyEquivalent:@""];
|
||||||
|
[nsitem setEnabled:((flags & SDL_TRAYENTRY_DISABLED) ? FALSE : TRUE)];
|
||||||
|
[nsitem setState:((flags & SDL_TRAYENTRY_CHECKED) ? NSControlStateValueOn : NSControlStateValueOff)];
|
||||||
|
[nsitem setRepresentedObject:[NSValue valueWithPointer:entry]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[menu->nsmenu insertItem:nsitem atIndex:pos];
|
||||||
|
|
||||||
|
entry->nsitem = nsitem;
|
||||||
|
entry->flags = flags;
|
||||||
|
entry->callback = NULL;
|
||||||
|
entry->userdata = NULL;
|
||||||
|
entry->submenu = NULL;
|
||||||
|
entry->parent = menu;
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
|
||||||
|
{
|
||||||
|
[entry->nsitem setTitle:[NSString stringWithUTF8String:label]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return [[entry->nsitem title] UTF8String];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
|
||||||
|
{
|
||||||
|
[entry->nsitem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->nsitem.state == NSControlStateValueOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Cannot update check for entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[entry->nsitem setEnabled:(enabled ? YES : NO)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Cannot fetch check for entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry->nsitem.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
|
||||||
|
{
|
||||||
|
entry->callback = callback;
|
||||||
|
entry->userdata = userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
return menu->parent_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
return menu->parent_tray;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_DestroyTray(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
if (!tray) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[NSStatusBar systemStatusBar] removeStatusItem:tray->statusItem];
|
||||||
|
|
||||||
|
if (tray->menu) {
|
||||||
|
DestroySDLMenu(tray->menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(tray);
|
||||||
|
}
|
139
src/tray/dummy/SDL_tray.c
Normal file
139
src/tray/dummy/SDL_tray.c
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_DestroyTray(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
SDL_Unsupported();
|
||||||
|
}
|
664
src/tray/unix/SDL_tray.c
Normal file
664
src/tray/unix/SDL_tray.c
Normal file
|
@ -0,0 +1,664 @@
|
||||||
|
/*
|
||||||
|
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>
|
||||||
|
|
||||||
|
/* getpid() */
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been
|
||||||
|
written nevertheless to make future maintenance easier. */
|
||||||
|
#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);
|
||||||
|
void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position);
|
||||||
|
void (*gtk_widget_destroy)(GtkWidget *widget);
|
||||||
|
const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item);
|
||||||
|
void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label);
|
||||||
|
gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item);
|
||||||
|
gboolean (*gtk_widget_get_sensitive)(GtkWidget *widget);
|
||||||
|
|
||||||
|
#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 int main_gtk_thread(void *data)
|
||||||
|
{
|
||||||
|
gtk_main();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef APPINDICATOR_HEADER
|
||||||
|
|
||||||
|
static void quit_gtk(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool init_gtk(void)
|
||||||
|
{
|
||||||
|
SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static bool gtk_is_init = false;
|
||||||
|
|
||||||
|
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 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *appindicator_names[] = {
|
||||||
|
"libayatana-appindicator3.so",
|
||||||
|
"libayatana-appindicator3.so.1",
|
||||||
|
"libayatana-appindicator.so",
|
||||||
|
"libappindicator3.so",
|
||||||
|
"libappindicator3.so.1",
|
||||||
|
"libappindicator.so",
|
||||||
|
"libappindicator.so.1",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *gtk_names[] = {
|
||||||
|
"libgtk-3.so",
|
||||||
|
"libgtk-3.so.0",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *gdk_names[] = {
|
||||||
|
"libgdk-3.so",
|
||||||
|
"libgdk-3.so.0",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *find_lib(const char **names)
|
||||||
|
{
|
||||||
|
const char **name_ptr = names;
|
||||||
|
void *handle = NULL;
|
||||||
|
|
||||||
|
do {
|
||||||
|
handle = dlopen(*name_ptr, RTLD_LAZY);
|
||||||
|
} while (*++name_ptr && !handle);
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool init_gtk(void)
|
||||||
|
{
|
||||||
|
if (gtk_is_init) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
libappindicator = find_lib(appindicator_names);
|
||||||
|
libgtk = find_lib(gtk_names);
|
||||||
|
libgdk = find_lib(gdk_names);
|
||||||
|
|
||||||
|
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");
|
||||||
|
gtk_menu_shell_insert = dlsym(libgtk, "gtk_menu_shell_insert");
|
||||||
|
gtk_widget_destroy = dlsym(libgtk, "gtk_widget_destroy");
|
||||||
|
gtk_menu_item_get_label = dlsym(libgtk, "gtk_menu_item_get_label");
|
||||||
|
gtk_menu_item_set_label = dlsym(libgtk, "gtk_menu_item_set_label");
|
||||||
|
gtk_check_menu_item_get_active = dlsym(libgtk, "gtk_check_menu_item_get_active");
|
||||||
|
gtk_widget_get_sensitive = dlsym(libgtk, "gtk_widget_get_sensitive");
|
||||||
|
|
||||||
|
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 ||
|
||||||
|
!gtk_menu_shell_insert ||
|
||||||
|
!gtk_widget_destroy ||
|
||||||
|
!g_signal_connect_data ||
|
||||||
|
!app_indicator_new ||
|
||||||
|
!app_indicator_set_status ||
|
||||||
|
!app_indicator_set_icon ||
|
||||||
|
!app_indicator_set_menu ||
|
||||||
|
!gtk_menu_item_get_label ||
|
||||||
|
!gtk_menu_item_set_label ||
|
||||||
|
!gtk_check_menu_item_get_active ||
|
||||||
|
!gtk_widget_get_sensitive) {
|
||||||
|
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 = true;
|
||||||
|
|
||||||
|
SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct SDL_TrayMenu {
|
||||||
|
GtkMenuShell *menu;
|
||||||
|
|
||||||
|
size_t nEntries;
|
||||||
|
SDL_TrayEntry **entries;
|
||||||
|
|
||||||
|
SDL_Tray *parent_tray;
|
||||||
|
SDL_TrayEntry *parent_entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDL_TrayEntry {
|
||||||
|
SDL_TrayMenu *parent;
|
||||||
|
GtkWidget *item;
|
||||||
|
|
||||||
|
/* Checkboxes are "activated" when programmatically checked/unchecked; this
|
||||||
|
is a workaround. */
|
||||||
|
bool ignore_signal;
|
||||||
|
|
||||||
|
SDL_TrayEntryFlags flags;
|
||||||
|
SDL_TrayCallback callback;
|
||||||
|
void *userdata;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDL_Tray {
|
||||||
|
AppIndicator *indicator;
|
||||||
|
SDL_TrayMenu *menu;
|
||||||
|
char icon_path[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void call_callback(GtkMenuItem *item, gpointer ptr)
|
||||||
|
{
|
||||||
|
SDL_TrayEntry *entry = ptr;
|
||||||
|
|
||||||
|
/* Not needed with AppIndicator, may be needed with other frameworks */
|
||||||
|
/* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
|
||||||
|
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
|
||||||
|
} */
|
||||||
|
|
||||||
|
if (entry->ignore_signal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->callback) {
|
||||||
|
entry->callback(entry->userdata, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Since AppIndicator deals only in filenames, which are inherently subject to
|
||||||
|
timing attacks, don't bother generating a secure filename. */
|
||||||
|
static 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_%d.bmp", getpid(), count++);
|
||||||
|
|
||||||
|
return would_have_written > 0 && would_have_written < size - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_appindicator_id(void)
|
||||||
|
{
|
||||||
|
static int count = 0;
|
||||||
|
static char buffer[256];
|
||||||
|
|
||||||
|
int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++);
|
||||||
|
|
||||||
|
if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) {
|
||||||
|
SDL_SetError("Couldn't fit %d bytes in buffer of size %ld", would_have_written, sizeof(buffer));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroySDLMenu(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < menu->nEntries; i++) {
|
||||||
|
if (menu->entries[i] && menu->entries[i]->submenu) {
|
||||||
|
DestroySDLMenu(menu->entries[i]->submenu);
|
||||||
|
}
|
||||||
|
SDL_free(menu->entries[i]);
|
||||||
|
}
|
||||||
|
SDL_free(menu->entries);
|
||||||
|
SDL_free(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
|
||||||
|
{
|
||||||
|
if (init_gtk() != 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(get_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 (*tray->icon_path) {
|
||||||
|
SDL_RemovePath(tray->icon_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AppIndicator caches the icon files; always change filename to avoid caching */
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
get_tmp_filename(tray->icon_path, sizeof(tray->icon_path));
|
||||||
|
SDL_SaveBMP(icon, tray->icon_path);
|
||||||
|
app_indicator_set_icon(tray->indicator, tray->icon_path);
|
||||||
|
} else {
|
||||||
|
*tray->icon_path = '\0';
|
||||||
|
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 = SDL_malloc(sizeof(SDL_TrayMenu));
|
||||||
|
|
||||||
|
if (!tray->menu) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tray->menu->menu = (GtkMenuShell *)gtk_menu_new();
|
||||||
|
tray->menu->parent_tray = tray;
|
||||||
|
tray->menu->parent_entry = NULL;
|
||||||
|
tray->menu->nEntries = 0;
|
||||||
|
tray->menu->entries = NULL;
|
||||||
|
|
||||||
|
app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu->menu));
|
||||||
|
|
||||||
|
return tray->menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
return tray->menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (entry->submenu) {
|
||||||
|
SDL_SetError("Tray entry submenu already exists");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
|
||||||
|
SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->submenu = SDL_malloc(sizeof(SDL_TrayMenu));
|
||||||
|
|
||||||
|
if (!entry->submenu) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->submenu->menu = (GtkMenuShell *)gtk_menu_new();
|
||||||
|
entry->submenu->parent_tray = NULL;
|
||||||
|
entry->submenu->parent_entry = entry;
|
||||||
|
entry->submenu->nEntries = 0;
|
||||||
|
entry->submenu->entries = NULL;
|
||||||
|
|
||||||
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu));
|
||||||
|
|
||||||
|
return entry->submenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->submenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
|
||||||
|
{
|
||||||
|
if (size) {
|
||||||
|
*size = menu->nEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (const SDL_TrayEntry **) menu->entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu = entry->parent;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < menu->nEntries - 1; i++) {
|
||||||
|
if (menu->entries[i] == entry) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
menu->entries[i] = menu->entries[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->submenu) {
|
||||||
|
DestroySDLMenu(entry->submenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->nEntries--;
|
||||||
|
SDL_TrayEntry ** new_entries = SDL_realloc(menu->entries, menu->nEntries * sizeof(SDL_TrayEntry *));
|
||||||
|
|
||||||
|
/* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
|
||||||
|
if (new_entries) {
|
||||||
|
menu->entries = new_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy(entry->item);
|
||||||
|
SDL_free(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
|
||||||
|
{
|
||||||
|
if (pos < -1 || pos > (int) menu->nEntries) {
|
||||||
|
SDL_InvalidParamError("pos");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == -1) {
|
||||||
|
pos = menu->nEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_memset((void *) entry, 0, sizeof(*entry));
|
||||||
|
entry->parent = menu;
|
||||||
|
entry->item = NULL;
|
||||||
|
entry->ignore_signal = false;
|
||||||
|
entry->flags = flags;
|
||||||
|
entry->callback = NULL;
|
||||||
|
entry->userdata = NULL;
|
||||||
|
entry->submenu = NULL;
|
||||||
|
|
||||||
|
if (label == NULL) {
|
||||||
|
entry->item = gtk_separator_menu_item_new();
|
||||||
|
} else 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(menu->entries, (menu->nEntries + 1) * sizeof(SDL_TrayEntry *));
|
||||||
|
|
||||||
|
if (!new_entries) {
|
||||||
|
SDL_free(entry);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->entries = new_entries;
|
||||||
|
menu->nEntries++;
|
||||||
|
|
||||||
|
for (int i = menu->nEntries - 1; i > pos; i--) {
|
||||||
|
menu->entries[i] = menu->entries[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entries[pos] = entry;
|
||||||
|
|
||||||
|
gtk_widget_show(entry->item);
|
||||||
|
gtk_menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos);
|
||||||
|
|
||||||
|
g_signal_connect(entry->item, "activate", G_CALLBACK(call_callback), entry);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
|
||||||
|
{
|
||||||
|
gtk_menu_item_set_label(GTK_MENU_ITEM(entry->item), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return gtk_menu_item_get_label(GTK_MENU_ITEM(entry->item));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Cannot update check for entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->ignore_signal = true;
|
||||||
|
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked);
|
||||||
|
entry->ignore_signal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Cannot fetch check for entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
|
||||||
|
{
|
||||||
|
gtk_widget_set_sensitive(entry->item, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return gtk_widget_get_sensitive(entry->item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
|
||||||
|
{
|
||||||
|
entry->callback = callback;
|
||||||
|
entry->userdata = userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
return menu->parent_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
return menu->parent_tray;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_DestroyTray(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
if (!tray) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tray->menu) {
|
||||||
|
DestroySDLMenu(tray->menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*tray->icon_path) {
|
||||||
|
SDL_RemovePath(tray->icon_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(tray);
|
||||||
|
}
|
589
src/tray/windows/SDL_tray.c
Normal file
589
src/tray/windows/SDL_tray.c
Normal file
|
@ -0,0 +1,589 @@
|
||||||
|
/*
|
||||||
|
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 "../../video/windows/SDL_surface_utils.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define WM_TRAYICON (WM_USER + 1)
|
||||||
|
|
||||||
|
struct SDL_TrayMenu {
|
||||||
|
HMENU hMenu;
|
||||||
|
|
||||||
|
size_t nEntries;
|
||||||
|
SDL_TrayEntry **entries;
|
||||||
|
|
||||||
|
SDL_Tray *parent_tray;
|
||||||
|
SDL_TrayEntry *parent_entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDL_TrayEntry {
|
||||||
|
SDL_TrayMenu *parent;
|
||||||
|
UINT_PTR id;
|
||||||
|
|
||||||
|
char label_cache[4096];
|
||||||
|
SDL_TrayEntryFlags flags;
|
||||||
|
SDL_TrayCallback callback;
|
||||||
|
void *userdata;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDL_Tray {
|
||||||
|
NOTIFYICONDATAW nid;
|
||||||
|
HWND hwnd;
|
||||||
|
HICON icon;
|
||||||
|
SDL_TrayMenu *menu;
|
||||||
|
};
|
||||||
|
|
||||||
|
static UINT_PTR get_next_id(void)
|
||||||
|
{
|
||||||
|
static UINT_PTR next_id = 0;
|
||||||
|
return ++next_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_TrayEntry *find_entry_in_menu(SDL_TrayMenu *menu, UINT_PTR id)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < menu->nEntries; i++) {
|
||||||
|
SDL_TrayEntry *entry = menu->entries[i];
|
||||||
|
|
||||||
|
if (entry->id == id) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->submenu) {
|
||||||
|
SDL_TrayEntry *e = find_entry_in_menu(entry->submenu, id);
|
||||||
|
|
||||||
|
if (e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_TrayEntry *find_entry_with_id(SDL_Tray *tray, UINT_PTR id)
|
||||||
|
{
|
||||||
|
if (!tray->menu) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return find_entry_in_menu(tray->menu, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||||
|
SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||||
|
SDL_TrayEntry *entry = NULL;
|
||||||
|
|
||||||
|
if (!tray) {
|
||||||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_TRAYICON:
|
||||||
|
if (LOWORD(lParam) == WM_CONTEXTMENU || LOWORD(lParam) == WM_LBUTTONUP) {
|
||||||
|
SetForegroundWindow(hwnd);
|
||||||
|
TrackPopupMenu(tray->menu->hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_COMMAND:
|
||||||
|
entry = find_entry_with_id(tray, LOWORD(wParam));
|
||||||
|
|
||||||
|
if (entry && (entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry && entry->callback) {
|
||||||
|
entry->callback(entry->userdata, entry);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroySDLMenu(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < menu->nEntries; i++) {
|
||||||
|
if (menu->entries[i] && menu->entries[i]->submenu) {
|
||||||
|
DestroySDLMenu(menu->entries[i]->submenu);
|
||||||
|
}
|
||||||
|
SDL_free(menu->entries[i]);
|
||||||
|
}
|
||||||
|
SDL_free(menu->entries);
|
||||||
|
DestroyMenu(menu->hMenu);
|
||||||
|
SDL_free(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static wchar_t *convert_label(const char *in)
|
||||||
|
{
|
||||||
|
const char *c;
|
||||||
|
char *c2;
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
for (c = in; *c; c++) {
|
||||||
|
len += (*c == '&') ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *escaped = SDL_malloc(SDL_strlen(in) + len + 1);
|
||||||
|
|
||||||
|
if (!escaped) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (c = in, c2 = escaped; *c;) {
|
||||||
|
if (*c == '&') {
|
||||||
|
*c2++ = *c;
|
||||||
|
}
|
||||||
|
|
||||||
|
*c2++ = *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*c2 = '\0';
|
||||||
|
|
||||||
|
int len_w = MultiByteToWideChar(CP_UTF8, 0, escaped, len + 1, NULL, 0);
|
||||||
|
wchar_t *out = (wchar_t *)SDL_malloc(len_w * sizeof(wchar_t));
|
||||||
|
|
||||||
|
if (!out) {
|
||||||
|
SDL_free(escaped);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, escaped, -1, out, len_w);
|
||||||
|
|
||||||
|
SDL_free(escaped);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register_tray_window_class(void)
|
||||||
|
{
|
||||||
|
static bool init = false;
|
||||||
|
|
||||||
|
if (init) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HINSTANCE hInstance = GetModuleHandle(NULL);
|
||||||
|
WNDCLASSW wc;
|
||||||
|
ZeroMemory(&wc, sizeof(WNDCLASS));
|
||||||
|
wc.lpfnWndProc = TrayWindowProc;
|
||||||
|
wc.hInstance = hInstance;
|
||||||
|
wc.lpszClassName = L"SDLTrayRunner";
|
||||||
|
|
||||||
|
RegisterClassW(&wc);
|
||||||
|
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = NULL;
|
||||||
|
|
||||||
|
register_tray_window_class();
|
||||||
|
|
||||||
|
HINSTANCE hInstance = GetModuleHandle(NULL);
|
||||||
|
tray->hwnd = CreateWindowExW(0, L"SDLTrayRunner", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL);
|
||||||
|
|
||||||
|
ZeroMemory(&tray->nid, sizeof(NOTIFYICONDATAW));
|
||||||
|
tray->nid.cbSize = sizeof(NOTIFYICONDATAW);
|
||||||
|
tray->nid.hWnd = tray->hwnd;
|
||||||
|
tray->nid.uID = (UINT) get_next_id();
|
||||||
|
tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
|
||||||
|
tray->nid.uCallbackMessage = WM_TRAYICON;
|
||||||
|
tray->nid.uVersion = NOTIFYICON_VERSION_4;
|
||||||
|
mbstowcs_s(NULL, tray->nid.szTip, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip), tooltip, _TRUNCATE);
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
tray->nid.hIcon = CreateIconFromSurface(icon);
|
||||||
|
|
||||||
|
if (!tray->nid.hIcon) {
|
||||||
|
tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
tray->icon = tray->nid.hIcon;
|
||||||
|
} else {
|
||||||
|
tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||||
|
tray->icon = tray->nid.hIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell_NotifyIconW(NIM_ADD, &tray->nid);
|
||||||
|
Shell_NotifyIconW(NIM_SETVERSION, &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) {
|
||||||
|
tray->nid.hIcon = CreateIconFromSurface(icon);
|
||||||
|
|
||||||
|
if (!tray->nid.hIcon) {
|
||||||
|
tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = SDL_malloc(sizeof(SDL_TrayMenu));
|
||||||
|
|
||||||
|
if (!tray->menu) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_memset((void *) tray->menu, 0, sizeof(*tray->menu));
|
||||||
|
|
||||||
|
tray->menu->hMenu = CreatePopupMenu();
|
||||||
|
tray->menu->parent_tray = tray;
|
||||||
|
|
||||||
|
return tray->menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
return tray->menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!entry->submenu) {
|
||||||
|
SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry->submenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->submenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
|
||||||
|
{
|
||||||
|
if (size) {
|
||||||
|
*size = (int) menu->nEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (const SDL_TrayEntry **) menu->entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu = entry->parent;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < menu->nEntries - 1; i++) {
|
||||||
|
if (menu->entries[i] == entry) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
menu->entries[i] = menu->entries[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->submenu) {
|
||||||
|
DestroySDLMenu(entry->submenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->nEntries--;
|
||||||
|
SDL_TrayEntry ** new_entries = SDL_realloc(menu->entries, menu->nEntries * sizeof(SDL_TrayEntry *));
|
||||||
|
|
||||||
|
/* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
|
||||||
|
if (new_entries) {
|
||||||
|
menu->entries = new_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DeleteMenu(menu->hMenu, (UINT) entry->id, MF_BYCOMMAND)) {
|
||||||
|
/* This is somewhat useless since we don't return anything, but might help with eventual bugs */
|
||||||
|
SDL_SetError("Couldn't destroy tray entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
|
||||||
|
{
|
||||||
|
if (pos < -1 || pos > (int) menu->nEntries) {
|
||||||
|
SDL_InvalidParamError("pos");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int windows_compatible_pos = pos;
|
||||||
|
|
||||||
|
if (pos == -1) {
|
||||||
|
pos = (int) menu->nEntries;
|
||||||
|
} else if (pos == menu->nEntries) {
|
||||||
|
windows_compatible_pos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t *label_w = NULL;
|
||||||
|
|
||||||
|
if (label && !(label_w = convert_label(label))) {
|
||||||
|
SDL_free(entry);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->parent = menu;
|
||||||
|
entry->flags = flags;
|
||||||
|
entry->callback = NULL;
|
||||||
|
entry->userdata = NULL;
|
||||||
|
entry->submenu = NULL;
|
||||||
|
SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label ? label : "");
|
||||||
|
|
||||||
|
if (label != NULL && flags & SDL_TRAYENTRY_SUBMENU) {
|
||||||
|
entry->submenu = SDL_malloc(sizeof(SDL_TrayMenu));
|
||||||
|
|
||||||
|
if (!entry->submenu) {
|
||||||
|
SDL_free(entry);
|
||||||
|
SDL_free(label_w);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->submenu->hMenu = CreatePopupMenu();
|
||||||
|
entry->submenu->nEntries = 0;
|
||||||
|
entry->submenu->entries = NULL;
|
||||||
|
|
||||||
|
entry->id = (UINT_PTR) entry->submenu->hMenu;
|
||||||
|
} else {
|
||||||
|
entry->id = get_next_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry **new_entries = (SDL_TrayEntry **) SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(SDL_TrayEntry **));
|
||||||
|
|
||||||
|
if (!new_entries) {
|
||||||
|
SDL_free(entry);
|
||||||
|
SDL_free(label_w);
|
||||||
|
if (entry->submenu) {
|
||||||
|
DestroyMenu(entry->submenu->hMenu);
|
||||||
|
SDL_free(entry->submenu);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->entries = new_entries;
|
||||||
|
menu->nEntries++;
|
||||||
|
|
||||||
|
for (int i = (int) menu->nEntries - 1; i > pos; i--) {
|
||||||
|
menu->entries[i] = menu->entries[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entries[pos] = entry;
|
||||||
|
|
||||||
|
if (label == NULL) {
|
||||||
|
InsertMenuW(menu->hMenu, windows_compatible_pos, MF_SEPARATOR | MF_BYPOSITION, entry->id, NULL);
|
||||||
|
} else {
|
||||||
|
UINT mf = MF_STRING | MF_BYPOSITION;
|
||||||
|
if (flags & SDL_TRAYENTRY_SUBMENU) {
|
||||||
|
mf = MF_POPUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & SDL_TRAYENTRY_DISABLED) {
|
||||||
|
mf |= MF_DISABLED | MF_GRAYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & SDL_TRAYENTRY_CHECKED) {
|
||||||
|
mf |= MF_CHECKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertMenuW(menu->hMenu, windows_compatible_pos, mf, entry->id, label_w);
|
||||||
|
|
||||||
|
SDL_free(label_w);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
|
||||||
|
{
|
||||||
|
SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label);
|
||||||
|
|
||||||
|
wchar_t *label_w = convert_label(label);
|
||||||
|
|
||||||
|
if (!label_w) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MENUITEMINFOW mii;
|
||||||
|
mii.cbSize = sizeof(MENUITEMINFOW);
|
||||||
|
mii.fMask = MIIM_STRING;
|
||||||
|
|
||||||
|
mii.dwTypeData = label_w;
|
||||||
|
mii.cch = (UINT) wcslen(label_w);
|
||||||
|
|
||||||
|
if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, TRUE, &mii)) {
|
||||||
|
SDL_SetError("Couldn't update tray entry label");
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(label_w);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->label_cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Can't check/uncheck tray entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckMenuItem(entry->parent->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Can't get check status of tray entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MENUITEMINFOW mii;
|
||||||
|
mii.cbSize = sizeof(MENUITEMINFOW);
|
||||||
|
mii.fMask = MIIM_STATE;
|
||||||
|
|
||||||
|
GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
|
||||||
|
|
||||||
|
return !!(mii.fState & MFS_CHECKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Cannot update check for entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableMenuItem(entry->parent->hMenu, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||||
|
SDL_SetError("Cannot fetch check for entry not created with SDL_TRAYENTRY_CHECKBOX");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MENUITEMINFOW mii;
|
||||||
|
mii.cbSize = sizeof(MENUITEMINFOW);
|
||||||
|
mii.fMask = MIIM_STATE;
|
||||||
|
|
||||||
|
GetMenuItemInfoW(entry->parent->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
return entry->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
return menu->parent_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
|
||||||
|
{
|
||||||
|
return menu->parent_tray;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL_DestroyTray(SDL_Tray *tray)
|
||||||
|
{
|
||||||
|
if (!tray) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell_NotifyIconW(NIM_DELETE, &tray->nid);
|
||||||
|
|
||||||
|
if (tray->menu) {
|
||||||
|
DestroySDLMenu(tray->menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tray->icon) {
|
||||||
|
DestroyIcon(tray->icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tray->hwnd) {
|
||||||
|
DestroyWindow(tray->hwnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(tray);
|
||||||
|
}
|
95
src/video/windows/SDL_surface_utils.c
Normal file
95
src/video/windows/SDL_surface_utils.c
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
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 "SDL_surface_utils.h"
|
||||||
|
|
||||||
|
#include "../SDL_surface_c.h"
|
||||||
|
|
||||||
|
HICON CreateIconFromSurface(SDL_Surface *surface)
|
||||||
|
{
|
||||||
|
SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
||||||
|
if (!s) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The dimensions will be needed after s is freed */
|
||||||
|
const int width = s->w;
|
||||||
|
const int height = s->h;
|
||||||
|
|
||||||
|
BITMAPINFO bmpInfo;
|
||||||
|
ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));
|
||||||
|
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmpInfo.bmiHeader.biWidth = width;
|
||||||
|
bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */
|
||||||
|
bmpInfo.bmiHeader.biPlanes = 1;
|
||||||
|
bmpInfo.bmiHeader.biBitCount = 32;
|
||||||
|
bmpInfo.bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
HDC hdc = GetDC(NULL);
|
||||||
|
void* pBits = NULL;
|
||||||
|
HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0);
|
||||||
|
if (!hBitmap) {
|
||||||
|
ReleaseDC(NULL, hdc);
|
||||||
|
SDL_DestroySurface(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_memcpy(pBits, s->pixels, width * height * 4);
|
||||||
|
|
||||||
|
SDL_DestroySurface(s);
|
||||||
|
|
||||||
|
HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL);
|
||||||
|
if (!hMask) {
|
||||||
|
DeleteObject(hBitmap);
|
||||||
|
ReleaseDC(NULL, hdc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HDC hdcMem = CreateCompatibleDC(hdc);
|
||||||
|
HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask);
|
||||||
|
|
||||||
|
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];
|
||||||
|
COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255);
|
||||||
|
SetPixel(hdcMem, x, y, maskColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ICONINFO iconInfo;
|
||||||
|
iconInfo.fIcon = TRUE;
|
||||||
|
iconInfo.xHotspot = 0;
|
||||||
|
iconInfo.yHotspot = 0;
|
||||||
|
iconInfo.hbmMask = hMask;
|
||||||
|
iconInfo.hbmColor = hBitmap;
|
||||||
|
|
||||||
|
HICON hIcon = CreateIconIndirect(&iconInfo);
|
||||||
|
|
||||||
|
SelectObject(hdcMem, oldBitmap);
|
||||||
|
DeleteDC(hdcMem);
|
||||||
|
DeleteObject(hBitmap);
|
||||||
|
DeleteObject(hMask);
|
||||||
|
ReleaseDC(NULL, hdc);
|
||||||
|
|
||||||
|
return hIcon;
|
||||||
|
}
|
38
src/video/windows/SDL_surface_utils.h
Normal file
38
src/video/windows/SDL_surface_utils.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
#ifndef SDL_surface_utils_h_
|
||||||
|
#define SDL_surface_utils_h_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern HICON CreateIconFromSurface(SDL_Surface *surface);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -407,6 +407,7 @@ add_sdl_test_executable(testdialog SOURCES testdialog.c)
|
||||||
add_sdl_test_executable(testtime SOURCES testtime.c)
|
add_sdl_test_executable(testtime SOURCES testtime.c)
|
||||||
add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c)
|
add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c)
|
||||||
add_sdl_test_executable(testmodal SOURCES testmodal.c)
|
add_sdl_test_executable(testmodal SOURCES testmodal.c)
|
||||||
|
add_sdl_test_executable(testtray SOURCES testtray.c)
|
||||||
|
|
||||||
|
|
||||||
add_sdl_test_executable(testprocess
|
add_sdl_test_executable(testprocess
|
||||||
|
|
BIN
test/sdl-test_round.bmp
Normal file
BIN
test/sdl-test_round.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 144 KiB |
599
test/testtray.c
Normal file
599
test/testtray.c
Normal file
|
@ -0,0 +1,599 @@
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
#include <SDL3/SDL_test.h>
|
||||||
|
|
||||||
|
static void SDLCALL tray_quit(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Event e;
|
||||||
|
e.type = SDL_EVENT_QUIT;
|
||||||
|
SDL_PushEvent(&e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL apply_icon(void *ptr, const char * const *filelist, int filter)
|
||||||
|
{
|
||||||
|
if (!*filelist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Surface *icon = SDL_LoadBMP(*filelist);
|
||||||
|
|
||||||
|
if (!icon) {
|
||||||
|
SDL_Log("Couldn't load icon '%s': %s", *filelist, SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *tray = (SDL_Tray *) ptr;
|
||||||
|
SDL_SetTrayIcon(tray, icon);
|
||||||
|
|
||||||
|
SDL_DestroySurface(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL change_icon(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_DialogFileFilter filters[] = {
|
||||||
|
{ "BMP image files", "bmp" },
|
||||||
|
{ "All files", "*" },
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_ShowOpenFileDialog(apply_icon, ptr, NULL, filters, 2, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL print_entry(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_Log("Clicked on button '%s'\n", SDL_GetTrayEntryLabel(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL set_entry_enabled(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayEntry *target = (SDL_TrayEntry *) ptr;
|
||||||
|
SDL_SetTrayEntryEnabled(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL set_entry_disabled(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayEntry *target = (SDL_TrayEntry *) ptr;
|
||||||
|
SDL_SetTrayEntryEnabled(target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL set_entry_checked(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayEntry *target = (SDL_TrayEntry *) ptr;
|
||||||
|
SDL_SetTrayEntryChecked(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL set_entry_unchecked(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayEntry *target = (SDL_TrayEntry *) ptr;
|
||||||
|
SDL_SetTrayEntryChecked(target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL remove_entry(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayEntry *target = (SDL_TrayEntry *) ptr;
|
||||||
|
SDL_RemoveTrayEntry(target);
|
||||||
|
|
||||||
|
SDL_TrayMenu *ctrl_submenu = SDL_GetTrayEntryParent(entry);
|
||||||
|
SDL_TrayEntry *ctrl_entry = SDL_GetTrayMenuParentEntry(ctrl_submenu);
|
||||||
|
|
||||||
|
if (!ctrl_entry) {
|
||||||
|
SDL_Log("Attempt to remove a menu that isn't a submenu. This shouldn't happen.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_RemoveTrayEntry(ctrl_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL append_button_to(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayMenu *menu = (SDL_TrayMenu *) ptr;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
SDL_TrayEntry *new_ctrl;
|
||||||
|
SDL_TrayEntry *new_ctrl_remove;
|
||||||
|
SDL_TrayEntry *new_ctrl_enabled;
|
||||||
|
SDL_TrayEntry *new_ctrl_disabled;
|
||||||
|
SDL_TrayEntry *new_example;
|
||||||
|
|
||||||
|
new_ctrl = SDL_InsertTrayEntryAt(SDL_GetTrayEntryParent(entry), -1, "New button", SDL_TRAYENTRY_SUBMENU);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't insert entry in control tray: %s\n", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
submenu = SDL_CreateTraySubmenu(new_ctrl);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't create control tray entry submenu: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_example = SDL_InsertTrayEntryAt(menu, -1, "New button", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_example == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry in example tray: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_example, print_entry, NULL);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_remove = SDL_InsertTrayEntryAt(submenu, -1, "Remove", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_remove == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_remove: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_remove, remove_entry, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_enabled = SDL_InsertTrayEntryAt(submenu, -1, "Enable", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_enabled == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_enabled: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_enabled, set_entry_enabled, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_disabled = SDL_InsertTrayEntryAt(submenu, -1, "Disable", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_disabled == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_disabled: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_disabled, set_entry_disabled, new_example);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL append_checkbox_to(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayMenu *menu = (SDL_TrayMenu *) ptr;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
SDL_TrayEntry *new_ctrl;
|
||||||
|
SDL_TrayEntry *new_ctrl_remove;
|
||||||
|
SDL_TrayEntry *new_ctrl_enabled;
|
||||||
|
SDL_TrayEntry *new_ctrl_disabled;
|
||||||
|
SDL_TrayEntry *new_ctrl_checked;
|
||||||
|
SDL_TrayEntry *new_ctrl_unchecked;
|
||||||
|
SDL_TrayEntry *new_example;
|
||||||
|
|
||||||
|
new_ctrl = SDL_InsertTrayEntryAt(SDL_GetTrayEntryParent(entry), -1, "New checkbox", SDL_TRAYENTRY_SUBMENU);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't insert entry in control tray: %s\n", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
submenu = SDL_CreateTraySubmenu(new_ctrl);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't create control tray entry submenu: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_example = SDL_InsertTrayEntryAt(menu, -1, "New checkbox", SDL_TRAYENTRY_CHECKBOX);
|
||||||
|
|
||||||
|
if (new_example == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry in example tray: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_example, print_entry, NULL);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_remove = SDL_InsertTrayEntryAt(submenu, -1, "Remove", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_remove == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_remove: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_remove, remove_entry, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_enabled = SDL_InsertTrayEntryAt(submenu, -1, "Enable", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_enabled == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_enabled: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_enabled, set_entry_enabled, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_disabled = SDL_InsertTrayEntryAt(submenu, -1, "Disable", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_disabled == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_disabled: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_disabled, set_entry_disabled, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_checked = SDL_InsertTrayEntryAt(submenu, -1, "Check", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_checked == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_checked: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_checked, set_entry_checked, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_unchecked = SDL_InsertTrayEntryAt(submenu, -1, "Uncheck", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_unchecked == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_unchecked: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_unchecked, set_entry_unchecked, new_example);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL append_separator_to(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayMenu *menu = (SDL_TrayMenu *) ptr;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
SDL_TrayEntry *new_ctrl;
|
||||||
|
SDL_TrayEntry *new_ctrl_remove;
|
||||||
|
SDL_TrayEntry *new_example;
|
||||||
|
|
||||||
|
new_ctrl = SDL_InsertTrayEntryAt(SDL_GetTrayEntryParent(entry), -1, "[Separator]", SDL_TRAYENTRY_SUBMENU);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't insert entry in control tray: %s\n", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
submenu = SDL_CreateTraySubmenu(new_ctrl);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't create control tray entry submenu: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_example = SDL_InsertTrayEntryAt(menu, -1, NULL, SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_example == NULL) {
|
||||||
|
SDL_Log("Couldn't insert separator in example tray: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_remove = SDL_InsertTrayEntryAt(submenu, -1, "Remove", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_remove == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_remove: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_remove, remove_entry, new_example);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDLCALL append_submenu_to(void *ptr, SDL_TrayEntry *entry)
|
||||||
|
{
|
||||||
|
SDL_TrayMenu *menu = (SDL_TrayMenu *) ptr;
|
||||||
|
SDL_TrayMenu *submenu;
|
||||||
|
SDL_TrayMenu *entry_submenu;
|
||||||
|
SDL_TrayEntry *new_ctrl;
|
||||||
|
SDL_TrayEntry *new_ctrl_remove;
|
||||||
|
SDL_TrayEntry *new_ctrl_enabled;
|
||||||
|
SDL_TrayEntry *new_ctrl_disabled;
|
||||||
|
SDL_TrayEntry *new_example;
|
||||||
|
|
||||||
|
new_ctrl = SDL_InsertTrayEntryAt(SDL_GetTrayEntryParent(entry), -1, "New submenu", SDL_TRAYENTRY_SUBMENU);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't insert entry in control tray: %s\n", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
submenu = SDL_CreateTraySubmenu(new_ctrl);
|
||||||
|
|
||||||
|
if (!new_ctrl) {
|
||||||
|
SDL_Log("Couldn't create control tray entry submenu: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_example = SDL_InsertTrayEntryAt(menu, -1, "New submenu", SDL_TRAYENTRY_SUBMENU);
|
||||||
|
|
||||||
|
if (new_example == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry in example tray: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_example, print_entry, NULL);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
entry_submenu = SDL_CreateTraySubmenu(new_example);
|
||||||
|
|
||||||
|
if (entry_submenu == NULL) {
|
||||||
|
SDL_Log("Couldn't create new entry submenu: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_remove = SDL_InsertTrayEntryAt(submenu, -1, "Remove", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_remove == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_remove: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_remove, remove_entry, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_enabled = SDL_InsertTrayEntryAt(submenu, -1, "Enable", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_enabled == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_enabled: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_enabled, set_entry_enabled, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
new_ctrl_disabled = SDL_InsertTrayEntryAt(submenu, -1, "Disable", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (new_ctrl_disabled == NULL) {
|
||||||
|
SDL_Log("Couldn't insert new_ctrl_disabled: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(new_ctrl_disabled, set_entry_disabled, new_example);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
SDL_InsertTrayEntryAt(submenu, -1, NULL, 0);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newbtn = SDL_InsertTrayEntryAt(submenu, -1, "Create button", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (entry_newbtn == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry_newbtn: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newbtn, append_button_to, entry_submenu);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newchk = SDL_InsertTrayEntryAt(submenu, -1, "Create checkbox", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (entry_newchk == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry_newchk: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newchk, append_checkbox_to, entry_submenu);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newsub = SDL_InsertTrayEntryAt(submenu, -1, "Create submenu", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (entry_newsub == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry_newsub: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newsub, append_submenu_to, entry_submenu);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newsep = SDL_InsertTrayEntryAt(submenu, -1, "Create separator", SDL_TRAYENTRY_BUTTON);
|
||||||
|
|
||||||
|
if (entry_newsep == NULL) {
|
||||||
|
SDL_Log("Couldn't insert entry_newsep: %s\n", SDL_GetError());
|
||||||
|
SDL_RemoveTrayEntry(new_ctrl);
|
||||||
|
SDL_RemoveTrayEntry(new_example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newsep, append_separator_to, entry_submenu);
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
SDL_InsertTrayEntryAt(submenu, -1, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
SDLTest_CommonState *state;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Initialize test framework */
|
||||||
|
state = SDLTest_CommonCreateState(argv, 0);
|
||||||
|
if (state == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse commandline */
|
||||||
|
for (i = 1; i < argc;) {
|
||||||
|
int consumed;
|
||||||
|
|
||||||
|
consumed = SDLTest_CommonArg(state, i);
|
||||||
|
|
||||||
|
if (consumed <= 0) {
|
||||||
|
static const char *options[] = { NULL };
|
||||||
|
SDLTest_CommonLogUsage(state, argv[0], options);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
|
SDL_Log("SDL_Init failed (%s)", SDL_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Resource paths? */
|
||||||
|
SDL_Surface *icon = SDL_LoadBMP("../test/sdl-test_round.bmp");
|
||||||
|
|
||||||
|
if (!icon) {
|
||||||
|
SDL_Log("Couldn't load icon 1, proceeding without: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Surface *icon2 = SDL_LoadBMP("../test/speaker.bmp");
|
||||||
|
|
||||||
|
if (!icon2) {
|
||||||
|
SDL_Log("Couldn't load icon 2, proceeding without: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *tray = SDL_CreateTray(icon, "SDL Tray control menu");
|
||||||
|
|
||||||
|
if (!tray) {
|
||||||
|
SDL_Log("Couldn't create control tray: %s", SDL_GetError());
|
||||||
|
goto quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Tray *tray2 = SDL_CreateTray(icon2, "SDL Tray example");
|
||||||
|
|
||||||
|
if (!tray2) {
|
||||||
|
SDL_Log("Couldn't create example tray: %s", SDL_GetError());
|
||||||
|
goto clean_tray1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_DestroySurface(icon);
|
||||||
|
SDL_DestroySurface(icon2);
|
||||||
|
|
||||||
|
#define CHECK(name) \
|
||||||
|
if (!name) { \
|
||||||
|
SDL_Log("Couldn't create " #name ": %s", SDL_GetError()); \
|
||||||
|
goto clean_all; \
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu = SDL_CreateTrayMenu(tray);
|
||||||
|
CHECK(menu);
|
||||||
|
|
||||||
|
SDL_TrayMenu *menu2 = SDL_CreateTrayMenu(tray2);
|
||||||
|
CHECK(menu2);
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_quit = SDL_InsertTrayEntryAt(menu, -1, "Quit", SDL_TRAYENTRY_BUTTON);
|
||||||
|
CHECK(entry_quit);
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_quit, tray_quit, NULL);
|
||||||
|
|
||||||
|
SDL_InsertTrayEntryAt(menu, -1, NULL, 0);
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_icon = SDL_InsertTrayEntryAt(menu, -1, "Change icon", SDL_TRAYENTRY_BUTTON);
|
||||||
|
CHECK(entry_icon);
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_icon, change_icon, tray2);
|
||||||
|
|
||||||
|
SDL_InsertTrayEntryAt(menu, -1, NULL, 0);
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newbtn = SDL_InsertTrayEntryAt(menu, -1, "Create button", SDL_TRAYENTRY_BUTTON);
|
||||||
|
CHECK(entry_newbtn);
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newbtn, append_button_to, menu2);
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newchk = SDL_InsertTrayEntryAt(menu, -1, "Create checkbox", SDL_TRAYENTRY_BUTTON);
|
||||||
|
CHECK(entry_newchk);
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newchk, append_checkbox_to, menu2);
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newsub = SDL_InsertTrayEntryAt(menu, -1, "Create submenu", SDL_TRAYENTRY_BUTTON);
|
||||||
|
CHECK(entry_newsub);
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newsub, append_submenu_to, menu2);
|
||||||
|
|
||||||
|
SDL_TrayEntry *entry_newsep = SDL_InsertTrayEntryAt(menu, -1, "Create separator", SDL_TRAYENTRY_BUTTON);
|
||||||
|
CHECK(entry_newsep);
|
||||||
|
|
||||||
|
SDL_SetTrayEntryCallback(entry_newsep, append_separator_to, menu2);
|
||||||
|
|
||||||
|
SDL_InsertTrayEntryAt(menu, -1, NULL, 0);
|
||||||
|
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_WaitEvent(&e)) {
|
||||||
|
if (e.type == SDL_EVENT_QUIT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_all:
|
||||||
|
SDL_DestroyTray(tray2);
|
||||||
|
|
||||||
|
clean_tray1:
|
||||||
|
SDL_DestroyTray(tray);
|
||||||
|
|
||||||
|
quit:
|
||||||
|
SDL_Quit();
|
||||||
|
SDLTest_CommonDestroyState(state);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue