diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4fc27126..719b6153 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -110,6 +110,7 @@ video tutorials. - IntellectualKitty - Aaron Jacobs - JannikGM + - Andreas O. Jansen - Erik S. V. Jansson - jjYBdx4IL - Peter Johnson @@ -214,6 +215,7 @@ video tutorials. - Guillaume Racicot - Juan Ramos - Christian Rauch + - Simon Richter - Philip Rideout - Eddie Ringle - Max Risuhin diff --git a/README.md b/README.md index 52306188..69f3dfe0 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,8 @@ information on what to include when reporting a bug. ## Changelog since 3.4 + - Added `glfwSetWindowBadge` and `glfwSetWindowBadgeString` for displaying window application badge (#2248) + - Added `glfwSetWindowProgressIndicator` for displaying progress on the dock or taskbar (#2286,#1183) - Added `GLFW_UNLIMITED_MOUSE_BUTTONS` input mode that allows mouse buttons beyond the limit of the mouse button tokens to be reported (#2423) - Updated minimum CMake version to 3.16 (#2541) diff --git a/docs/window.md b/docs/window.md index 371baa56..ec0df60e 100644 --- a/docs/window.md +++ b/docs/window.md @@ -1202,6 +1202,26 @@ not supported, the application as a whole. Once the user has given it attention, the system will automatically end the request. +### Window progress indicator {#window_progress_indicator} + +If you wish to display the progress of some action on the Dock icon or task bar, you can +do this with @ref glfwSetWindowProgressIndicator. + +```c +glfwSetWindowProgressIndicator(window, GLFW_PROGRESS_INDICATOR_NORMAL, 0.5); +``` + +There are different progress states available for you to use: + - @ref GLFW_PROGRESS_INDICATOR_DISABLED + - @ref GLFW_PROGRESS_INDICATOR_INDETERMINATE + - @ref GLFW_PROGRESS_INDICATOR_NORMAL + - @ref GLFW_PROGRESS_INDICATOR_ERROR + - @ref GLFW_PROGRESS_INDICATOR_PAUSED + +The last argument is the progress percentage to display. +It has a valid range of 0.0 to 1.0. + + ### Window damage and refresh {#window_refresh} If you wish to be notified when the contents of a window is damaged and needs diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 79b06288..461289c4 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1129,6 +1129,9 @@ extern "C" { /*! @brief Win32 specific [window hint](@ref GLFW_WIN32_SHOWDEFAULT_hint). */ #define GLFW_WIN32_SHOWDEFAULT 0x00025002 + +#define GLFW_WIN32_GENERIC_BADGE 0x00025003 + /*! @brief Wayland specific * [window hint](@ref GLFW_WAYLAND_APP_ID_hint). * @@ -1288,6 +1291,59 @@ extern "C" { #define GLFW_HAND_CURSOR GLFW_POINTING_HAND_CURSOR /*! @} */ +/*! @addtogroup window + * @{ */ +/*! @brief Disable the progress bar. + * + * Disable the progress bar. + * + * Used by @ref window_progress_indicator. + */ +#define GLFW_PROGRESS_INDICATOR_DISABLED 0 +/*! @brief Display the progress bar in an indeterminate state. + * + * Display the progress bar in an indeterminate state. + * + * @remark @win32 This displays the progress bar animation cycling repeatedly. + * + * @remark @x11 @wayland This behaves like @ref GLFW_PROGRESS_INDICATOR_NORMAL. + * + * @remark @macos This displays a standard indeterminate `NSProgressIndicator`. + * + * Used by @ref window_progress_indicator. + */ +#define GLFW_PROGRESS_INDICATOR_INDETERMINATE 1 +/*! @brief Display the normal progress bar. + * + * Display the normal progress bar. + * + * Used by @ref window_progress_indicator. + */ +#define GLFW_PROGRESS_INDICATOR_NORMAL 2 +/*! @brief Display the progress bar in an error state. + * + * Display the progress bar in an error state. + * + * @remark @win32 This displays a red progress bar. + * + * @remark @x11 @wayland @macos This behaves like @ref GLFW_PROGRESS_INDICATOR_NORMAL. + * + * Used by @ref window_progress_indicator. + */ +#define GLFW_PROGRESS_INDICATOR_ERROR 3 +/*! @brief Display the progress bar in a paused state. + * + * Display the progress bar in a paused state. + * + * @remark @win32 This displays a yellow progress bar. + * + * @remark @x11 @wayland @macos This behaves like @ref GLFW_PROGRESS_INDICATOR_NORMAL. + * + * Used by @ref window_progress_indicator. + */ +#define GLFW_PROGRESS_INDICATOR_PAUSED 4 +/*! @} */ + #define GLFW_CONNECTED 0x00040001 #define GLFW_DISCONNECTED 0x00040002 @@ -3413,6 +3469,106 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title); */ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* images); +/*! @brief Sets the dock or taskbar progress indicator for the specified window. + * + * This function sets the dock or taskbar progress indicator of the specified window. + * + * @param[in] window The window whose progress to set. + * @param[in] progressState The state of the progress to be displayed in the dock + * or taskbar. Valid values are: @ref GLFW_PROGRESS_INDICATOR_DISABLED, + * @ref GLFW_PROGRESS_INDICATOR_INDETERMINATE, @ref GLFW_PROGRESS_INDICATOR_NORMAL, + * @ref GLFW_PROGRESS_INDICATOR_ERROR and @ref GLFW_PROGRESS_INDICATOR_PAUSED. + * @param[in] value The amount of completed progress to set. Valid range is 0.0 to 1.0. + * This is ignored if progressState is set to @ref GLFW_PROGRESS_INDICATOR_DISABLED. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_INVALID_VALUE, @ref GLFW_INVALID_ENUM, @ref GLFW_PLATFORM_ERROR, + * @ref GLFW_FEATURE_UNIMPLEMENTED and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). + * + * @remark @win32 On Windows Vista and earlier, this function will emit + * @ref GLFW_FEATURE_UNAVAILABLE. + * + * @remark @macos There exists only one Dock icon progress bar, and this + * displays the combined values of all the windows. + * + * @remark @x11 @wayland Requires a valid application desktop file with the same name + * as the compiled executable. Due to limitations in the Unity Launcher API + * @ref GLFW_PROGRESS_INDICATOR_INDETERMINATE, @ref GLFW_PROGRESS_INDICATOR_ERROR + * and @ref GLFW_PROGRESS_INDICATOR_PAUSED have the same behaviour as + * @ref GLFW_PROGRESS_INDICATOR_NORMAL. The Unity Launcher API is only known + * to be supported on the Unity and KDE desktop environments; on other desktop + * environments this function may do nothing. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref window_progress_indicator + * + * @since Added in version 3.4. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowProgressIndicator(GLFWwindow* window, int progressState, double value); + +/*! @brief Sets the dock or taskbar badge for the specified window or the application. + * + * This function sets the dock or taskbar badge for the specified window + * or the application as a whole. Any badge set with this function + * overrides the string badge set with @ref glfwSetWindowBadgeString. + * If the platform does not support number badges, the string badge + * is not overridden. + * + * @param[in] window The window whose badge to set. + * @param[in] count The number to set, or `0` to disable it. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR, and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). + * + * @remark @win32 On Windows Vista and earlier, this function will emit + * @ref GLFW_FEATURE_UNAVAILABLE. + * + * @remark @macos Only the Dock icon may contain a badge. Pass a `NULL` + * window handle to set it. Emits @ref GLFW_FEATURE_UNAVAILABLE if a + * valid window handle is passed. + * + * @remark @x11 @wayland @win32 Emits GLFW_FEATURE_UNAVAILABLE if a + * `NULL` window handle is passed. + * + * @thread_safety This function must only be called from the main thread. + * + * @since Added in version 3.4. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowBadge(GLFWwindow* window, int count); + +/*! @brief Sets the dock or taskbar badge for the specified window or the application. + * + * This function sets the dock or taskbar badge for the specified window + * or the application as a whole. Any string badge set with this function + * overrides the number badge set with @ref glfwSetWindowBadge. + * If the platform does not support string badges, the number badge + * is not overridden. + * + * @param[in] window The window whose badge to set. + * @param[in] string The text to set, or `NULL` to disable it. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR, and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). + * + * @remark @macos Only the Dock icon may contain a badge. Pass a `NULL` + * window handle to set it. Emits @ref GLFW_FEATURE_UNAVAILABLE if a + * valid window handle is passed. + * + * @remark @x11 @wayland @win32 Emits GLFW_FEATURE_UNAVAILABLE. + * + * @thread_safety This function must only be called from the main thread. + * + * @since Added in version 3.4. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowBadgeString(GLFWwindow* window, const char* string); + /*! @brief Retrieves the position of the content area of the specified window. * * This function retrieves the position, in screen coordinates, of the diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1a085b2b..0d0ea3cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,8 @@ elseif (WIN32) win32_time.c win32_thread.c) else() target_sources(glfw PRIVATE posix_time.h posix_thread.h posix_module.c - posix_time.c posix_thread.c) + posix_time.c posix_thread.c posix_dbus.h + posix_dbus.c) endif() add_custom_target(update_mappings diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 15dc4ec4..019a0cc5 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -526,6 +526,9 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowCocoa, .setWindowTitle = _glfwSetWindowTitleCocoa, .setWindowIcon = _glfwSetWindowIconCocoa, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorCocoa, + .setWindowBadge = _glfwSetWindowBadgeCocoa, + .setWindowBadgeString = _glfwSetWindowBadgeStringCocoa, .getWindowPos = _glfwGetWindowPosCocoa, .setWindowPos = _glfwSetWindowPosCocoa, .getWindowSize = _glfwGetWindowSizeCocoa, @@ -646,6 +649,12 @@ int _glfwInitCocoa(void) void _glfwTerminateCocoa(void) { @autoreleasepool { + + if (_glfw.ns.dockProgressIndicator.view != nil) + { + [_glfw.ns.dockProgressIndicator.view removeFromSuperview]; + [_glfw.ns.dockProgressIndicator.view release]; + } if (_glfw.ns.inputSource) { diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 4d1d66ae..ba224704 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -156,6 +156,11 @@ typedef struct _GLFWwindowNS // since the last cursor motion event was processed // This is kept to counteract Cocoa doing the same internally double cursorWarpDeltaX, cursorWarpDeltaY; + + struct { + int state; + double value; + } dockProgressIndicator; } _GLFWwindowNS; // Cocoa-specific global data @@ -189,6 +194,13 @@ typedef struct _GLFWlibraryNS PFN_LMGetKbdType GetKbdType; CFStringRef kPropertyUnicodeKeyLayoutData; } tis; + + struct { + id view; + int windowCount; + int indeterminateCount; + double totalValue; + } dockProgressIndicator; } _GLFWlibraryNS; // Cocoa-specific per-monitor data @@ -218,6 +230,9 @@ GLFWbool _glfwCreateWindowCocoa(_GLFWwindow* window, const _GLFWwndconfig* wndco void _glfwDestroyWindowCocoa(_GLFWwindow* window); void _glfwSetWindowTitleCocoa(_GLFWwindow* window, const char* title); void _glfwSetWindowIconCocoa(_GLFWwindow* window, int count, const GLFWimage* images); +void _glfwSetWindowProgressIndicatorCocoa(_GLFWwindow* window, int progressState, double value); +void _glfwSetWindowBadgeCocoa(_GLFWwindow* window, int count); +void _glfwSetWindowBadgeStringCocoa(_GLFWwindow* window, const char* string); void _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosCocoa(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeCocoa(_GLFWwindow* window, int* width, int* height); diff --git a/src/cocoa_window.m b/src/cocoa_window.m index e69b5fe0..d8ef04ca 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -198,6 +198,83 @@ static NSUInteger translateKeyToModifierFlag(int key) // static const NSRange kEmptyRange = { NSNotFound, 0 }; +static NSProgressIndicator* createProgressIndicator(const NSDockTile* dockTile) +{ + NSView* contentView = [dockTile contentView]; + + NSProgressIndicator* indicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, contentView.frame.size.width, 15.0f)]; + + [indicator setStyle:NSProgressIndicatorStyleBar]; + + if (@available(macOS 11.0, *)) + { + [indicator setControlSize:NSControlSizeLarge]; + } + + [indicator setMinValue:0.0f]; + [indicator setMaxValue:1.0f]; + + [indicator sizeToFit]; + + [contentView addSubview:indicator]; + + _glfw.ns.dockProgressIndicator.view = indicator; + + return indicator; +} + +static void setDockProgressIndicator(int progressState, double value) +{ + NSProgressIndicator* indicator = _glfw.ns.dockProgressIndicator.view; + + NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; + + if (indicator == nil) + { + if ([dockTile contentView] == nil) + { + NSImageView *iconView = [[NSImageView alloc] init]; + [iconView setImage:[[NSApplication sharedApplication] applicationIconImage]]; + [dockTile setContentView:iconView]; + [iconView release]; + } + + indicator = createProgressIndicator(dockTile); + } + + // ### Switching from INDETERMINATE to NORMAL, PAUSED or ERROR requires 2 invocations in different frames. + // In MacOS 12 (and probably other versions), an indeterminate progress bar is rendered as a normal bar + // with 0.0 progress. So when calling [progressIndicator setIndeterminate:YES], the indicator actually + // sets its doubleValue to 0.0. + // The bug is caused by NSProgressIndicator not immediately updating its value when it's increasing. + // This code illustrates the exact same problem, but this time from NORMAL, PAUSED and ERROR to INDETERMINATE: + // + // if (progressState == GLFW_PROGRESS_INDICATOR_INDETERMINATE) + // [progressIndicator setDoubleValue:0.75]; + // else + // [progressIndicator setDoubleValue:0.25]; + // + // This is likely a bug in Cocoa. + // + // ### Progress increments are delayed + // What this also means, is that each time the progress increments, the bar's progress will be 1 frame delayed, + // and only updated once a higher or similar value is again set the next frame. + + // Workaround for the aforementioned issues. If there's any versions of MacOS where + // this issue is not present, this should be ommitted in those versions. + if ([indicator isIndeterminate] || [indicator doubleValue] < value) + { + [indicator removeFromSuperview]; + [indicator release]; + indicator = createProgressIndicator(dockTile); + } + + [indicator setIndeterminate:progressState == GLFW_PROGRESS_INDICATOR_INDETERMINATE]; + [indicator setHidden:progressState == GLFW_PROGRESS_INDICATOR_DISABLED]; + [indicator setDoubleValue:value]; + + [dockTile display]; +} //------------------------------------------------------------------------ // Delegate for window related notifications @@ -990,6 +1067,8 @@ GLFWbool _glfwCreateWindowCocoa(_GLFWwindow* window, void _glfwDestroyWindowCocoa(_GLFWwindow* window) { @autoreleasepool { + + _glfwSetWindowProgressIndicatorCocoa(window, GLFW_PROGRESS_INDICATOR_DISABLED, 0.0); if (_glfw.ns.disabledCursorWindow == window) _glfw.ns.disabledCursorWindow = NULL; @@ -1036,6 +1115,107 @@ void _glfwSetWindowIconCocoa(_GLFWwindow* window, "Cocoa: Regular windows do not have icons on macOS"); } +void _glfwSetWindowProgressIndicatorCocoa(_GLFWwindow* window, int progressState, double value) +{ + if (progressState == GLFW_PROGRESS_INDICATOR_ERROR || progressState == GLFW_PROGRESS_INDICATOR_PAUSED) + progressState = GLFW_PROGRESS_INDICATOR_NORMAL; + + const int oldState = window->ns.dockProgressIndicator.state; + const int state = progressState; + + const double oldValue = window->ns.dockProgressIndicator.value; + + if (oldState == state) + { + if (state == GLFW_PROGRESS_INDICATOR_DISABLED || + state == GLFW_PROGRESS_INDICATOR_INDETERMINATE || + oldValue == value) + return; + } + + if (oldState != state) + { + // Reset + if (oldState == GLFW_PROGRESS_INDICATOR_INDETERMINATE) + --_glfw.ns.dockProgressIndicator.indeterminateCount; + if (oldState != GLFW_PROGRESS_INDICATOR_DISABLED) + { + --_glfw.ns.dockProgressIndicator.windowCount; + _glfw.ns.dockProgressIndicator.totalValue -= oldValue; + } + + // Set + if (state == GLFW_PROGRESS_INDICATOR_INDETERMINATE) + ++_glfw.ns.dockProgressIndicator.indeterminateCount; + if (state != GLFW_PROGRESS_INDICATOR_DISABLED) + { + ++_glfw.ns.dockProgressIndicator.windowCount; + _glfw.ns.dockProgressIndicator.totalValue += value; + } + } + else if (state != GLFW_PROGRESS_INDICATOR_DISABLED) + _glfw.ns.dockProgressIndicator.totalValue += (value - oldValue); + + + if (_glfw.ns.dockProgressIndicator.windowCount > _glfw.ns.dockProgressIndicator.indeterminateCount) + { + const double finalValue = _glfw.ns.dockProgressIndicator.totalValue / _glfw.ns.dockProgressIndicator.windowCount; + setDockProgressIndicator(GLFW_PROGRESS_INDICATOR_NORMAL, finalValue); + } + else if (_glfw.ns.dockProgressIndicator.indeterminateCount > 0) + setDockProgressIndicator(GLFW_PROGRESS_INDICATOR_INDETERMINATE, 0.0f); + else + setDockProgressIndicator(GLFW_PROGRESS_INDICATOR_DISABLED, 0.0f); + + window->ns.dockProgressIndicator.state = state; + window->ns.dockProgressIndicator.value = value; +} + +void _glfwSetWindowBadgeCocoa(_GLFWwindow* window, int count) +{ + if (window != NULL) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Cocoa: Cannot set a badge for a window. Pass NULL to set the Dock badge."); + return; + } + + if (count == 0) + { + [NSApp dockTile].badgeLabel = nil; + return; + } + + NSString* string; + + if (count <= 9999) + string = [@(count) stringValue]; + else + string = [[@(9999) stringValue] stringByAppendingString:@"+"]; + + [NSApp dockTile].badgeLabel = string; +} + +void _glfwSetWindowBadgeStringCocoa(_GLFWwindow* window, const char* string) +{ + if (window != NULL) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Cocoa: Cannot set a badge for a window. Pass NULL to set for the application."); + } + + if (string == NULL) + { + [NSApp dockTile].badgeLabel = nil; + return; + } + + NSString* nsString = [NSString stringWithCString:string + encoding:[NSString defaultCStringEncoding]]; + + [NSApp dockTile].badgeLabel = nsString; +} + void _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos) { @autoreleasepool { diff --git a/src/internal.h b/src/internal.h index 4f097aa8..054a89e8 100644 --- a/src/internal.h +++ b/src/internal.h @@ -425,6 +425,7 @@ struct _GLFWwndconfig struct { GLFWbool keymenu; GLFWbool showDefault; + GLFWbool genericBadge; } win32; struct { char appId[256]; @@ -718,6 +719,9 @@ struct _GLFWplatform void (*destroyWindow)(_GLFWwindow*); void (*setWindowTitle)(_GLFWwindow*,const char*); void (*setWindowIcon)(_GLFWwindow*,int,const GLFWimage*); + void (*setWindowProgressIndicator)(_GLFWwindow*,const int,double); + void (*setWindowBadge)(_GLFWwindow*,int); + void (*setWindowBadgeString)(_GLFWwindow*,const char* string); void (*getWindowPos)(_GLFWwindow*,int*,int*); void (*setWindowPos)(_GLFWwindow*,int,int); void (*getWindowSize)(_GLFWwindow*,int*,int*); @@ -884,6 +888,7 @@ struct _GLFWlibrary GLFW_PLATFORM_LIBRARY_WINDOW_STATE GLFW_PLATFORM_LIBRARY_CONTEXT_STATE GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE + GLFW_PLATFORM_LIBRARY_DBUS_STATE }; // Global state shared between compilation units of GLFW diff --git a/src/null_init.c b/src/null_init.c index 8c10f5e6..f6b34397 100644 --- a/src/null_init.c +++ b/src/null_init.c @@ -72,6 +72,9 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowNull, .setWindowTitle = _glfwSetWindowTitleNull, .setWindowIcon = _glfwSetWindowIconNull, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorNull, + .setWindowBadge = _glfwSetWindowBadgeNull, + .setWindowBadgeString = _glfwSetWindowBadgeStringNull, .getWindowPos = _glfwGetWindowPosNull, .setWindowPos = _glfwSetWindowPosNull, .getWindowSize = _glfwGetWindowSizeNull, diff --git a/src/null_platform.h b/src/null_platform.h index dbcb835b..fb39e3e0 100644 --- a/src/null_platform.h +++ b/src/null_platform.h @@ -223,6 +223,9 @@ GLFWbool _glfwCreateWindowNull(_GLFWwindow* window, const _GLFWwndconfig* wndcon void _glfwDestroyWindowNull(_GLFWwindow* window); void _glfwSetWindowTitleNull(_GLFWwindow* window, const char* title); void _glfwSetWindowIconNull(_GLFWwindow* window, int count, const GLFWimage* images); +void _glfwSetWindowProgressIndicatorNull(_GLFWwindow* window, int progressState, double value); +void _glfwSetWindowBadgeNull(_GLFWwindow* window, int count); +void _glfwSetWindowBadgeStringNull(_GLFWwindow* window, const char* string); void _glfwSetWindowMonitorNull(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); void _glfwGetWindowPosNull(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosNull(_GLFWwindow* window, int xpos, int ypos); diff --git a/src/null_window.c b/src/null_window.c index f0e1dcc9..b4aac7dd 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -186,6 +186,18 @@ void _glfwSetWindowIconNull(_GLFWwindow* window, int count, const GLFWimage* ima { } +void _glfwSetWindowProgressIndicatorNull(_GLFWwindow* window, int progressState, double value) +{ +} + +void _glfwSetWindowBadgeNull(_GLFWwindow* window, int count) +{ +} + +void _glfwSetWindowBadgeStringNull(_GLFWwindow* window, const char* string) +{ +} + void _glfwSetWindowMonitorNull(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, diff --git a/src/platform.h b/src/platform.h index 7ce4e015..dc789d81 100644 --- a/src/platform.h +++ b/src/platform.h @@ -187,6 +187,7 @@ #define GLFW_BUILD_COCOA_TIMER #else #define GLFW_BUILD_POSIX_TIMER + #define GLFW_BUILD_POSIX_DBUS #endif #if defined(GLFW_BUILD_WIN32_TIMER) @@ -200,6 +201,13 @@ #define GLFW_PLATFORM_LIBRARY_TIMER_STATE GLFW_POSIX_LIBRARY_TIMER_STATE #endif +#if defined(GLFW_BUILD_POSIX_DBUS) + #include "posix_dbus.h" + #define GLFW_PLATFORM_LIBRARY_DBUS_STATE GLFW_POSIX_LIBRARY_DBUS_STATE +#else + #define GLFW_PLATFORM_LIBRARY_DBUS_STATE +#endif + #if defined(_WIN32) #define GLFW_BUILD_WIN32_MODULE #else diff --git a/src/posix_dbus.c b/src/posix_dbus.c new file mode 100644 index 00000000..4e07706d --- /dev/null +++ b/src/posix_dbus.c @@ -0,0 +1,518 @@ +//======================================================================== +// GLFW 3.4 POSIX - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2023 Camilla Löwy +// +// 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. +// +//======================================================================== +// It is fine to use C99 in this file because it will not be built with VS +//======================================================================== + +#define _GNU_SOURCE + +#include "internal.h" + +#include +#include +#include +#include +#include + +void _glfwInitDBusPOSIX(void) +{ + //Initialize DBus library functions + _glfw.dbus.handle = NULL; + _glfw.dbus.connection = NULL; + + _glfw.dbus.handle = _glfwPlatformLoadModule("libdbus-1.so.3"); + if (!_glfw.dbus.handle) + return; + + _glfw.dbus.error_init = (PFN_dbus_error_init) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_error_init"); + _glfw.dbus.error_is_set = (PFN_dbus_error_is_set) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_error_is_set"); + _glfw.dbus.error_free = (PFN_dbus_error_free) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_error_free"); + _glfw.dbus.connection_unref = (PFN_dbus_connection_unref) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_connection_unref"); + _glfw.dbus.connection_send = (PFN_dbus_connection_send) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_connection_send"); + _glfw.dbus.connection_flush = (PFN_dbus_connection_flush) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_connection_flush"); + _glfw.dbus.bus_request_name = (PFN_dbus_bus_request_name) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_bus_request_name"); + _glfw.dbus.bus_get = (PFN_dbus_bus_get) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_bus_get"); + _glfw.dbus.message_unref = (PFN_dbus_message_unref) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_message_unref"); + _glfw.dbus.message_new_signal = (PFN_dbus_message_new_signal) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_message_new_signal"); + _glfw.dbus.message_iter_init_append = (PFN_dbus_message_iter_init_append) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_message_iter_init_append"); + _glfw.dbus.message_iter_append_basic = (PFN_dbus_message_iter_append_basic) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_message_iter_append_basic"); + _glfw.dbus.message_iter_open_container = (PFN_dbus_message_iter_open_container) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_message_iter_open_container"); + _glfw.dbus.message_iter_close_container = (PFN_dbus_message_iter_close_container) + _glfwPlatformGetModuleSymbol(_glfw.dbus.handle, "dbus_message_iter_close_container"); + + if (!_glfw.dbus.error_init || + !_glfw.dbus.error_is_set || + !_glfw.dbus.error_free || + !_glfw.dbus.connection_unref || + !_glfw.dbus.connection_send || + !_glfw.dbus.connection_flush || + !_glfw.dbus.bus_request_name || + !_glfw.dbus.bus_get || + !_glfw.dbus.message_unref || + !_glfw.dbus.message_new_signal || + !_glfw.dbus.message_iter_init_append || + !_glfw.dbus.message_iter_append_basic || + !_glfw.dbus.message_iter_open_container || + !_glfw.dbus.message_iter_close_container) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "POSIX: Failed to load DBus entry points"); + return; + } + + //Initialize DBus connection + dbus_error_init(&_glfw.dbus.error); + _glfw.dbus.connection = dbus_bus_get(DBUS_BUS_SESSION, &_glfw.dbus.error); + + //Check for errors + if(dbus_error_is_set(&_glfw.dbus.error) || !_glfw.dbus.connection) + { + if(dbus_error_is_set(&_glfw.dbus.error)) + dbus_error_free(&_glfw.dbus.error); + + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to connect to DBus"); + + dbus_connection_unref(_glfw.dbus.connection); + _glfw.dbus.connection = NULL; + return; + } + else + { + //Request name + + _glfwCacheLegalExecutableNameDBusPOSIX(); + if(!_glfw.dbus.legalExecutableName) + return; + + //"org.glfw._" + char* busName = _glfw_calloc(21 + strlen(_glfw.dbus.legalExecutableName), sizeof(char)); + if(!busName) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, "Failed to allocate memory for bus name"); + return; + } + memset(busName, '\0', (21 + strlen(_glfw.dbus.legalExecutableName)) * sizeof(char)); + + const pid_t pid = getpid(); + sprintf(busName, "org.glfw.%s_%d", _glfw.dbus.legalExecutableName, pid); + + const int res = dbus_bus_request_name(_glfw.dbus.connection, busName, DBUS_NAME_FLAG_REPLACE_EXISTING, &_glfw.dbus.error); + + _glfw_free(busName); + + //Check for errors + if(dbus_error_is_set(&_glfw.dbus.error) || res != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + { + if(dbus_error_is_set(&_glfw.dbus.error)) + dbus_error_free(&_glfw.dbus.error); + + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to request DBus name"); + + dbus_connection_unref(_glfw.dbus.connection); + _glfw.dbus.connection = NULL; + } + } + + _glfwCacheFullExecutableNameDBusPOSIX(); + _glfwCacheDesktopFilePathDBusPOSIX(); + _glfwCacheSignalNameDBusPOSIX(); +} + +void _glfwCacheSignalNameDBusPOSIX(void) +{ + if(!_glfw.dbus.legalExecutableName) + return; + + //"/org/glfw/_" + char* signalName = _glfw_calloc(22 + strlen(_glfw.dbus.legalExecutableName), sizeof(char)); + if(!signalName) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, "Failed to allocate memory for signal name"); + return; + } + + memset(signalName, '\0', (22 + strlen(_glfw.dbus.legalExecutableName)) * sizeof(char)); + + const pid_t pid = getpid(); + if(sprintf(signalName, "/org/glfw/%s_%d", _glfw.dbus.legalExecutableName, pid) < 0) + { + _glfwInputError(GLFW_PLATFORM, "Failed to create signal name"); + _glfw_free(signalName); + return; + } + + _glfw.dbus.signalName = signalName; +} + +void _glfwCacheFullExecutableNameDBusPOSIX(void) +{ + char exeName[PATH_MAX]; + memset(exeName, 0, sizeof(char) * PATH_MAX); + if(readlink("/proc/self/exe", exeName, PATH_MAX) == -1) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to get name of the running executable"); + return; + } + char* exeNameEnd = strchr(exeName, '\0'); + char* lastFound = strrchr(exeName, '/'); + if(!lastFound || !exeNameEnd) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to get name of the running executable"); + return; + } + unsigned int exeNameLength = (exeNameEnd - lastFound) - 1; + + char* exeNameFinal = _glfw_calloc(exeNameLength + 1, sizeof(char)); + if(!exeNameFinal) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, "Failed to allocate memory for executable name"); + return; + } + + memset(exeNameFinal, 0, sizeof(char) * (exeNameLength + 1)); + + memcpy(exeNameFinal, (lastFound + 1), exeNameLength); + + _glfw.dbus.fullExecutableName = exeNameFinal; +} + +void _glfwCacheLegalExecutableNameDBusPOSIX(void) +{ + //The executable name is stripped of any illegal characters + //according to the DBus specification + + int i = 0; + int validExeNameLength = 0; + int output = 0; + char exeName[PATH_MAX]; + memset(exeName, 0, sizeof(char) * PATH_MAX); + if(readlink("/proc/self/exe", exeName, PATH_MAX) == -1) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to get name of the running executable"); + return; + } + char* exeNameEnd = strchr(exeName, '\0'); + char* lastFound = strrchr(exeName, '/'); + if(!lastFound || !exeNameEnd) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to get name of the running executable"); + return; + } + unsigned int exeNameLength = (exeNameEnd - lastFound) - 1; + + for(i = 0; i < exeNameLength; ++i) + { + if(isalnum(*(lastFound + 1 + i))) + validExeNameLength++; + } + + char* exeNameFinal = _glfw_calloc(validExeNameLength + 1, sizeof(char)); + if(!exeNameFinal) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, "Failed to allocate memory for executable name"); + return; + } + + memset(exeNameFinal, 0, sizeof(char) * (validExeNameLength + 1)); + + for(i = 0; i < exeNameLength; ++i) + { + if(isalnum(*(lastFound + 1 + i))) + exeNameFinal[output++] = *(lastFound + 1 + i); + } + + _glfw.dbus.legalExecutableName = exeNameFinal; +} + +void _glfwCacheDesktopFilePathDBusPOSIX(void) +{ + if(!_glfw.dbus.fullExecutableName) + return; + + //Cache path of .desktop file + + //Create our final desktop file uri + //"application://.desktop" + unsigned int desktopFileLength = strlen("application://") + strlen(_glfw.dbus.fullExecutableName) + strlen(".desktop") + 1; + _glfw.dbus.desktopFilePath = _glfw_calloc(desktopFileLength, sizeof(char)); + if(!_glfw.dbus.desktopFilePath) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, "Failed to allocate memory for .desktop file path"); + return; + } + + memset(_glfw.dbus.desktopFilePath, 0, sizeof(char) * desktopFileLength); + strcpy(_glfw.dbus.desktopFilePath, "application://"); + memcpy(_glfw.dbus.desktopFilePath + strlen("application://"), _glfw.dbus.fullExecutableName, strlen(_glfw.dbus.fullExecutableName)); + strcpy(_glfw.dbus.desktopFilePath + strlen("application://") + strlen(_glfw.dbus.fullExecutableName), ".desktop"); + _glfw.dbus.desktopFilePath[desktopFileLength - 1] = '\0'; +} + +void _glfwTerminateDBusPOSIX(void) +{ + if(_glfw.dbus.signalName) + _glfw_free(_glfw.dbus.signalName); + + if(_glfw.dbus.legalExecutableName) + _glfw_free(_glfw.dbus.legalExecutableName); + + if(_glfw.dbus.fullExecutableName) + _glfw_free(_glfw.dbus.fullExecutableName); + + if(_glfw.dbus.desktopFilePath) + _glfw_free(_glfw.dbus.desktopFilePath); + + if (_glfw.dbus.connection) + { + dbus_connection_unref(_glfw.dbus.connection); + _glfw.dbus.connection = NULL; + } + + if (_glfw.dbus.handle) + { + _glfwPlatformFreeModule(_glfw.dbus.handle); + _glfw.dbus.handle = NULL; + } +} + +void _glfwUpdateTaskbarProgressDBusPOSIX(dbus_bool_t progressVisible, double progressValue) +{ + struct DBusMessage* msg = NULL; + + if(!_glfw.dbus.handle || !_glfw.dbus.connection || !_glfw.dbus.desktopFilePath || !_glfw.dbus.signalName) + return; + + //Signal signature: + //signal com.canonical.Unity.LauncherEntry.Update (in s app_uri, in a{sv} properties) + + struct DBusMessageIter args; + memset(&args, 0, sizeof(args)); + + if(!_glfwNewMessageSignalDBusPOSIX(_glfw.dbus.signalName, "com.canonical.Unity.LauncherEntry", "Update", &msg)) + return; + + dbus_message_iter_init_append(msg, &args); + + //Setup app_uri parameter + _glfwAppendDataDBusPOSIX(&args, DBUS_TYPE_STRING, &_glfw.dbus.desktopFilePath); + + //Set properties parameter + struct DBusMessageIter sub1; + memset(&sub1, 0, sizeof(sub1)); + + _glfwOpenContainerDBusPOSIX(&args, DBUS_TYPE_ARRAY, "{sv}", &sub1); + + //Set progress visible property + const char* progressVisibleStr = "progress-visible"; + _glfwAppendDictDataDBusPOSIX(&sub1, DBUS_TYPE_STRING, &progressVisibleStr, DBUS_TYPE_BOOLEAN, &progressVisible); + + //Set progress value property + const char* progressStr = "progress"; + _glfwAppendDictDataDBusPOSIX(&sub1, DBUS_TYPE_STRING, &progressStr, DBUS_TYPE_DOUBLE, &progressValue); + + _glfwCloseContainerDBusPOSIX(&args, &sub1); + + _glfwSendMessageDBusPOSIX(msg); + + //Free the message + dbus_message_unref(msg); +} + +void _glfwUpdateBadgeDBusPOSIX(dbus_bool_t badgeVisible, int badgeCount) +{ + struct DBusMessage* msg = NULL; + + if(!_glfw.dbus.handle || !_glfw.dbus.connection || !_glfw.dbus.desktopFilePath || !_glfw.dbus.signalName) + return; + + long long badgeCountLL = badgeCount; + + //Signal signature: + //signal com.canonical.Unity.LauncherEntry.Update (in s app_uri, in a{sv} properties) + + struct DBusMessageIter args; + memset(&args, 0, sizeof(args)); + + if(!_glfwNewMessageSignalDBusPOSIX(_glfw.dbus.signalName, "com.canonical.Unity.LauncherEntry", "Update", &msg)) + return; + + dbus_message_iter_init_append(msg, &args); + + //Setup app_uri parameter + _glfwAppendDataDBusPOSIX(&args, DBUS_TYPE_STRING, &_glfw.dbus.desktopFilePath); + + //Set properties parameter + struct DBusMessageIter sub1; + memset(&sub1, 0, sizeof(sub1)); + + _glfwOpenContainerDBusPOSIX(&args, DBUS_TYPE_ARRAY, "{sv}", &sub1); + + //Set count visible property + const char* countVisibleStr = "count-visible"; + _glfwAppendDictDataDBusPOSIX(&sub1, DBUS_TYPE_STRING, &countVisibleStr, DBUS_TYPE_BOOLEAN, &badgeVisible); + + //Set count value property + const char* countValueStr = "count"; + _glfwAppendDictDataDBusPOSIX(&sub1, DBUS_TYPE_STRING, &countValueStr, DBUS_TYPE_INT64, &badgeCountLL); + + _glfwCloseContainerDBusPOSIX(&args, &sub1); + + _glfwSendMessageDBusPOSIX(msg); + + //Free the message + dbus_message_unref(msg); +} + +dbus_bool_t _glfwNewMessageSignalDBusPOSIX(const char* objectPath, const char* interfaceName, const char* signalName, struct DBusMessage** outMessage) +{ + if(!outMessage) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create new DBus message, output message pointer is NULL"); + return GLFW_FALSE; + } + + *outMessage = dbus_message_new_signal(objectPath, interfaceName, signalName); + if(!(*outMessage)) + { + *outMessage = NULL; + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create new DBus message"); + return GLFW_FALSE; + } + + return GLFW_TRUE; +} + +dbus_bool_t _glfwOpenContainerDBusPOSIX(struct DBusMessageIter* iterator, int DBusType, const char* signature, struct DBusMessageIter* subIterator) +{ + if(DBusType != DBUS_TYPE_ARRAY && DBusType != DBUS_TYPE_STRUCT_OPEN && + DBusType != DBUS_TYPE_STRUCT_CLOSE && DBusType != DBUS_TYPE_VARIANT && + DBusType != DBUS_TYPE_DICT_ENTRY) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Invalid DBUS container type provided"); + return GLFW_FALSE; + } + if(!iterator || !subIterator) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus message iterator is NULL"); + return GLFW_FALSE; + } + + return dbus_message_iter_open_container(iterator, DBusType, signature, subIterator); +} + +dbus_bool_t _glfwCloseContainerDBusPOSIX(struct DBusMessageIter* iterator, struct DBusMessageIter* subIterator) +{ + if(!iterator || !subIterator) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus message iterator is NULL"); + return GLFW_FALSE; + } + + return dbus_message_iter_close_container(iterator, subIterator); +} + +dbus_bool_t _glfwAppendDataDBusPOSIX(struct DBusMessageIter* iterator, int DBusType, const void* data) +{ + if(DBusType == DBUS_TYPE_ARRAY || DBusType == DBUS_TYPE_VARIANT || DBusType == DBUS_TYPE_DICT_ENTRY || DBusType == DBUS_TYPE_STRUCT_OPEN || DBusType == DBUS_TYPE_STRUCT_CLOSE) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Invalid DBus type provided"); + return GLFW_FALSE; + } + if(!iterator) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus message iterator is NULL"); + return GLFW_FALSE; + } + if(!data) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus data to append is NULL"); + return GLFW_FALSE; + } + + return dbus_message_iter_append_basic(iterator, DBusType, data); +} + +dbus_bool_t _glfwAppendDictDataDBusPOSIX(struct DBusMessageIter* iterator, int keyType, const void* keyData, int valueType, const void* valueData) +{ + struct DBusMessageIter keyIterator; + struct DBusMessageIter valueIterator; + memset(&keyIterator, 0, sizeof(keyIterator)); + memset(&valueIterator, 0, sizeof(valueIterator)); + + if(!_glfwOpenContainerDBusPOSIX(iterator, DBUS_TYPE_DICT_ENTRY, NULL, &keyIterator)) + return GLFW_FALSE; + + //Append key data + if(!_glfwAppendDataDBusPOSIX(&keyIterator, keyType, keyData)) + return GLFW_FALSE; + + if(!_glfwOpenContainerDBusPOSIX(&keyIterator, DBUS_TYPE_VARIANT, (const char*)&valueType, &valueIterator)) + return GLFW_FALSE; + + //Append value data + if(!_glfwAppendDataDBusPOSIX(&valueIterator, valueType, valueData)) + return GLFW_FALSE; + + if(!_glfwCloseContainerDBusPOSIX(&keyIterator, &valueIterator)) + return GLFW_FALSE; + + if(!_glfwCloseContainerDBusPOSIX(iterator, &keyIterator)) + return GLFW_FALSE; + + return GLFW_TRUE; +} + +dbus_bool_t _glfwSendMessageDBusPOSIX(struct DBusMessage* message) +{ + if(!message) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus message is NULL"); + return GLFW_FALSE; + } + + unsigned int serial = 0; + if(!dbus_connection_send(_glfw.dbus.connection, message, &serial)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to send DBus message"); + return GLFW_FALSE; + } + + dbus_connection_flush(_glfw.dbus.connection); + + return GLFW_TRUE; +} \ No newline at end of file diff --git a/src/posix_dbus.h b/src/posix_dbus.h new file mode 100644 index 00000000..f25c62c3 --- /dev/null +++ b/src/posix_dbus.h @@ -0,0 +1,159 @@ +//======================================================================== +// GLFW 3.4 POSIX - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2023 Camilla Löwy +// +// 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. +// +//======================================================================== + +//Taken from DBus docs (https://dbus.freedesktop.org/doc/api/html/index.html) +typedef struct DBusConnection DBusConnection; +typedef struct DBusMessage DBusMessage; +typedef unsigned int dbus_bool_t; +typedef unsigned int dbus_uint32_t; + +enum DBusBusType +{ + DBUS_BUS_SESSION, + DBUS_BUS_SYSTEM, + DBUS_BUS_STARTER +}; + +struct DBusError +{ + const char* name; + const char* message; + unsigned int dummy1 : 1; + unsigned int dummy2 : 1; + unsigned int dummy3 : 1; + unsigned int dummy4 : 1; + unsigned int dummy5 : 1; + void* padding1; +}; + +struct DBusMessageIter +{ + void* dummy1; + void* dummy2; + dbus_uint32_t dummy3; + int dummy4, dummy5, dummy6, dummy7, dummy8, dummy9, dummy10, dummy11; + int pad1; + void* pad2; + void* pad3; +}; + +#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 +#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 +#define DBUS_TYPE_STRING (unsigned int)'s' +#define DBUS_TYPE_ARRAY (unsigned int)'a' +#define DBUS_TYPE_DICT_ENTRY (unsigned int)'e' +#define DBUS_TYPE_VARIANT (unsigned int)'v' +#define DBUS_TYPE_BOOLEAN (unsigned int)'b' +#define DBUS_TYPE_DOUBLE (unsigned int)'d' +#define DBUS_TYPE_INT16 (unsigned int)'n' +#define DBUS_TYPE_UINT16 (unsigned int)'q' +#define DBUS_TYPE_INT32 (unsigned int)'i' +#define DBUS_TYPE_UINT32 (unsigned int)'u' +#define DBUS_TYPE_INT64 (unsigned int)'x' +#define DBUS_TYPE_UINT64 (unsigned int)'t' +#define DBUS_TYPE_STRUCT_OPEN (unsigned int)'(' +#define DBUS_TYPE_STRUCT_CLOSE (unsigned int)')' +#define DBUS_TYPE_BYTE (unsigned int)'y' +#define DBUS_TYPE_OBJECT_PATH (unsigned int)'o' +#define DBUS_TYPE_SIGNATURE (unsigned int)'g' + +typedef void (* PFN_dbus_error_init)(struct DBusError*); +typedef dbus_bool_t (* PFN_dbus_error_is_set)(const struct DBusError*); +typedef void (* PFN_dbus_error_free)(struct DBusError*); +typedef void (* PFN_dbus_connection_unref)(DBusConnection*); +typedef dbus_bool_t (* PFN_dbus_connection_send)(DBusConnection*, DBusMessage*, dbus_uint32_t*); +typedef void (* PFN_dbus_connection_flush)(DBusConnection*); +typedef int (* PFN_dbus_bus_request_name)(DBusConnection*, const char*, unsigned int, struct DBusError*); +typedef DBusConnection* (* PFN_dbus_bus_get)(enum DBusBusType, struct DBusError*); +typedef void (* PFN_dbus_message_unref)(DBusMessage*); +typedef DBusMessage* (* PFN_dbus_message_new_signal)(const char*, const char*, const char*); +typedef void (* PFN_dbus_message_iter_init_append)(DBusMessage*, struct DBusMessageIter*); +typedef dbus_bool_t (* PFN_dbus_message_iter_append_basic)(struct DBusMessageIter*, int, const void*); +typedef dbus_bool_t (* PFN_dbus_message_iter_open_container)(struct DBusMessageIter*, int, const char*, struct DBusMessageIter*); +typedef dbus_bool_t (* PFN_dbus_message_iter_close_container)(struct DBusMessageIter*, struct DBusMessageIter*); + +#define dbus_error_init _glfw.dbus.error_init +#define dbus_error_is_set _glfw.dbus.error_is_set +#define dbus_error_free _glfw.dbus.error_free +#define dbus_connection_unref _glfw.dbus.connection_unref +#define dbus_connection_send _glfw.dbus.connection_send +#define dbus_connection_flush _glfw.dbus.connection_flush +#define dbus_bus_request_name _glfw.dbus.bus_request_name +#define dbus_bus_get _glfw.dbus.bus_get +#define dbus_message_unref _glfw.dbus.message_unref +#define dbus_message_new_signal _glfw.dbus.message_new_signal +#define dbus_message_iter_init_append _glfw.dbus.message_iter_init_append +#define dbus_message_iter_append_basic _glfw.dbus.message_iter_append_basic +#define dbus_message_iter_open_container _glfw.dbus.message_iter_open_container +#define dbus_message_iter_close_container _glfw.dbus.message_iter_close_container + +#define GLFW_POSIX_LIBRARY_DBUS_STATE _GLFWDBusPOSIX dbus; + +// POSIX-specific dbus data +// +typedef struct _GLFWDBusPOSIX +{ + void* handle; + + PFN_dbus_error_init error_init; + PFN_dbus_error_is_set error_is_set; + PFN_dbus_error_free error_free; + PFN_dbus_connection_unref connection_unref; + PFN_dbus_connection_send connection_send; + PFN_dbus_connection_flush connection_flush; + PFN_dbus_bus_request_name bus_request_name; + PFN_dbus_bus_get bus_get; + PFN_dbus_message_unref message_unref; + PFN_dbus_message_new_signal message_new_signal; + PFN_dbus_message_iter_init_append message_iter_init_append; + PFN_dbus_message_iter_append_basic message_iter_append_basic; + PFN_dbus_message_iter_open_container message_iter_open_container; + PFN_dbus_message_iter_close_container message_iter_close_container; + + DBusConnection* connection; + struct DBusError error; + + char* desktopFilePath; + char* fullExecutableName; + char* legalExecutableName; + char* signalName; +} _GLFWDBusPOSIX; + +void _glfwInitDBusPOSIX(void); +void _glfwCacheSignalNameDBusPOSIX(void); +void _glfwCacheFullExecutableNameDBusPOSIX(void); +void _glfwCacheLegalExecutableNameDBusPOSIX(void); +void _glfwCacheDesktopFilePathDBusPOSIX(void); +void _glfwTerminateDBusPOSIX(void); +void _glfwUpdateTaskbarProgressDBusPOSIX(dbus_bool_t progressVisible, double progressValue); +void _glfwUpdateBadgeDBusPOSIX(dbus_bool_t badgeVisible, int badgeCount); + +dbus_bool_t _glfwNewMessageSignalDBusPOSIX(const char* objectPath, const char* interfaceName, const char* signalName, struct DBusMessage** outMessage); +dbus_bool_t _glfwOpenContainerDBusPOSIX(struct DBusMessageIter* iterator, int DBusType, const char* signature, struct DBusMessageIter* subIterator); +dbus_bool_t _glfwCloseContainerDBusPOSIX(struct DBusMessageIter* iterator, struct DBusMessageIter* subIterator); +dbus_bool_t _glfwAppendDataDBusPOSIX(struct DBusMessageIter* iterator, int DBusType, const void* data); +dbus_bool_t _glfwAppendDictDataDBusPOSIX(struct DBusMessageIter* iterator, int keyType, const void* keyData, int valueType, const void* valueData); +dbus_bool_t _glfwSendMessageDBusPOSIX(struct DBusMessage* message); \ No newline at end of file diff --git a/src/win32_init.c b/src/win32_init.c index 77ab56ba..9aeef652 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -635,6 +635,9 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowWin32, .setWindowTitle = _glfwSetWindowTitleWin32, .setWindowIcon = _glfwSetWindowIconWin32, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorWin32, + .setWindowBadge = _glfwSetWindowBadgeWin32, + .setWindowBadgeString = _glfwSetWindowBadgeStringWin32, .getWindowPos = _glfwGetWindowPosWin32, .setWindowPos = _glfwSetWindowPosWin32, .getWindowSize = _glfwGetWindowSizeWin32, diff --git a/src/win32_platform.h b/src/win32_platform.h index a2f86852..1bc46308 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -366,6 +366,79 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)( #define GLFW_WGL_CONTEXT_STATE _GLFWcontextWGL wgl; #define GLFW_WGL_LIBRARY_CONTEXT_STATE _GLFWlibraryWGL wgl; +typedef enum +{ + TBPF_NOPROGRESS = 0x0, + TBPF_INDETERMINATE = 0x1, + TBPF_NORMAL = 0x2, + TBPF_ERROR = 0x4, + TBPF_PAUSED = 0x8 +} TBPFLAG; + +static const IID IID_ITaskbarList3 = { 0xea1afb91, 0x9e28, 0x4b86, {0x90, 0xe9, 0x9e, 0x9f, 0x8a, 0x5e, 0xef, 0xaf} }; +static const IID CLSID_TaskbarList = { 0x56fdf344, 0xfd6d, 0x11d0, {0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90} }; + +typedef enum THUMBBUTTONMASK +{ + THB_BITMAP = 0x1, + THB_ICON = 0x2, + THB_TOOLTIP = 0x4, + THB_FLAGS = 0x8 +} THUMBBUTTONMASK; + +typedef enum THUMBBUTTONFLAGS +{ + THBF_ENABLED = 0, + THBF_DISABLED = 0x1, + THBF_DISMISSONCLICK = 0x2, + THBF_NOBACKGROUND = 0x4, + THBF_HIDDEN = 0x8, + THBF_NONINTERACTIVE = 0x10 +} THUMBBUTTONFLAGS; + +typedef struct THUMBBUTTON { + THUMBBUTTONMASK dwMask; + UINT iId; + UINT iBitmap; + HICON hIcon; + WCHAR szTip[260]; + THUMBBUTTONFLAGS dwFlags; +} THUMBBUTTON, *LPTHUMBBUTTON; + +struct _IMAGELIST; +typedef struct _IMAGELIST* HIMAGELIST; + +typedef struct ITaskbarList3 ITaskbarList3; + +typedef struct ITaskbarList3Vtbl +{ + HRESULT(WINAPI* QueryInterface)(struct ITaskbarList3*, const IID* const, void**); + ULONG(WINAPI* AddRef)(struct ITaskbarList3*); + ULONG(WINAPI* Release)(struct ITaskbarList3*); + HRESULT(WINAPI* HrInit)(struct ITaskbarList3*); + HRESULT(WINAPI* AddTab)(struct ITaskbarList3*, HWND); + HRESULT(WINAPI* DeleteTab)(struct ITaskbarList3*, HWND); + HRESULT(WINAPI* ActivateTab)(struct ITaskbarList3*, HWND); + HRESULT(WINAPI* SetActiveAlt)(struct ITaskbarList3*, HWND); + HRESULT(WINAPI* MarkFullscreenWindow)(struct ITaskbarList3*, HWND, BOOL); + HRESULT(WINAPI* SetProgressValue)(struct ITaskbarList3*, HWND, ULONGLONG, ULONGLONG); + HRESULT(WINAPI* SetProgressState)(struct ITaskbarList3*, HWND, TBPFLAG); + HRESULT(WINAPI* RegisterTab)(struct ITaskbarList3*, HWND, HWND); + HRESULT(WINAPI* UnregisterTab)(struct ITaskbarList3*, HWND); + HRESULT(WINAPI* SetTabOrder)(struct ITaskbarList3*, HWND, HWND); + HRESULT(WINAPI* SetTabActive)(struct ITaskbarList3*, HWND, HWND, DWORD); + HRESULT(WINAPI* ThumbBarAddButtons)(struct ITaskbarList3*, HWND, UINT, LPTHUMBBUTTON); + HRESULT(WINAPI* ThumbBarUpdateButtons)(struct ITaskbarList3*, HWND, UINT, LPTHUMBBUTTON); + HRESULT(WINAPI* ThumbBarSetImageList)(struct ITaskbarList3*, HWND, HIMAGELIST); + HRESULT(WINAPI* SetOverlayIcon)(struct ITaskbarList3*, HWND, HICON, LPCWSTR); + HRESULT(WINAPI* SetThumbnailTooltip)(struct ITaskbarList3*, HWND, LPCWSTR); + HRESULT(WINAPI* SetThumbnailClip)(struct ITaskbarList3*, HWND, RECT*); +} ITaskbarList3Vtbl; + +struct ITaskbarList3 +{ + struct ITaskbarList3Vtbl* lpVtbl; +}; // WGL-specific per-context data // @@ -425,6 +498,7 @@ typedef struct _GLFWwindowWin32 GLFWbool scaleToMonitor; GLFWbool keymenu; GLFWbool showDefault; + GLFWbool genericBadge; // Cached size used to filter out duplicate events int width, height; @@ -433,6 +507,9 @@ typedef struct _GLFWwindowWin32 int lastCursorPosX, lastCursorPosY; // The last received high surrogate when decoding pairs of UTF-16 messages WCHAR highSurrogate; + + ITaskbarList3* taskbarList; + UINT taskbarListMsgID; } _GLFWwindowWin32; // Win32-specific global data @@ -525,7 +602,6 @@ typedef struct _GLFWcursorWin32 HCURSOR handle; } _GLFWcursorWin32; - GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform); int _glfwInitWin32(void); void _glfwTerminateWin32(void); @@ -546,6 +622,9 @@ GLFWbool _glfwCreateWindowWin32(_GLFWwindow* window, const _GLFWwndconfig* wndco void _glfwDestroyWindowWin32(_GLFWwindow* window); void _glfwSetWindowTitleWin32(_GLFWwindow* window, const char* title); void _glfwSetWindowIconWin32(_GLFWwindow* window, int count, const GLFWimage* images); +void _glfwSetWindowProgressIndicatorWin32(_GLFWwindow* window, int progressState, double value); +void _glfwSetWindowBadgeWin32(_GLFWwindow* window, int count); +void _glfwSetWindowBadgeStringWin32(_GLFWwindow* window, const char* string); void _glfwGetWindowPosWin32(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosWin32(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeWin32(_GLFWwindow* window, int* width, int* height); diff --git a/src/win32_window.c b/src/win32_window.c index d014944b..48ef836e 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -35,6 +35,7 @@ #include #include #include +#include // Returns the window style for the specified window // @@ -1268,6 +1269,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l } } + if(uMsg == window->win32.taskbarListMsgID) + { + HRESULT res = CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, &IID_ITaskbarList3, (LPVOID*)&window->win32.taskbarList); + if (res != S_OK && window->win32.taskbarList) + window->win32.taskbarList->lpVtbl->Release(window->win32.taskbarList); + else + { + window->win32.taskbarList->lpVtbl->AddRef(window->win32.taskbarList); + window->win32.taskbarList->lpVtbl->HrInit(window->win32.taskbarList); + } + } + return DefWindowProcW(hWnd, uMsg, wParam, lParam); } @@ -1415,11 +1428,16 @@ static int createNativeWindow(_GLFWwindow* window, WM_COPYDATA, MSGFLT_ALLOW, NULL); ChangeWindowMessageFilterEx(window->win32.handle, WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL); + + window->win32.taskbarListMsgID = RegisterWindowMessageW(L"TaskbarButtonCreated"); + if (window->win32.taskbarListMsgID) + ChangeWindowMessageFilterEx(window->win32.handle, window->win32.taskbarListMsgID, MSGFLT_ALLOW, NULL); } window->win32.scaleToMonitor = wndconfig->scaleToMonitor; window->win32.keymenu = wndconfig->win32.keymenu; window->win32.showDefault = wndconfig->win32.showDefault; + window->win32.genericBadge = wndconfig->win32.genericBadge; if (!window->monitor) { @@ -1568,6 +1586,9 @@ void _glfwDestroyWindowWin32(_GLFWwindow* window) if (_glfw.win32.capturedCursorWindow == window) releaseCursor(); + if (window->win32.taskbarList) + window->win32.taskbarList->lpVtbl->Release(window->win32.taskbarList); + if (window->win32.handle) { RemovePropW(window->win32.handle, L"GLFW"); @@ -1630,6 +1651,492 @@ void _glfwSetWindowIconWin32(_GLFWwindow* window, int count, const GLFWimage* im } } +void _glfwSetWindowProgressIndicatorWin32(_GLFWwindow* window, int progressState, double value) +{ + HRESULT res = S_OK; + int winProgressState = 0; + int progressValue = (int)(value * 100.0); + + if(!IsWindows7OrGreater()) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Win32: Taskbar progress is only supported on Windows 7 and newer"); + return; + } + + if(!window->win32.taskbarList) + return; + + res = window->win32.taskbarList->lpVtbl->SetProgressValue(window->win32.taskbarList, window->win32.handle, progressValue, 100); + if(res != S_OK) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to set taskbar progress value"); + return; + } + + switch(progressState) + { + case GLFW_PROGRESS_INDICATOR_INDETERMINATE: + winProgressState = TBPF_INDETERMINATE; + break; + case GLFW_PROGRESS_INDICATOR_NORMAL: + winProgressState = TBPF_NORMAL; + break; + case GLFW_PROGRESS_INDICATOR_ERROR: + winProgressState = TBPF_ERROR; + break; + case GLFW_PROGRESS_INDICATOR_PAUSED: + winProgressState = TBPF_PAUSED; + break; + + default: + winProgressState = TBPF_NOPROGRESS; + break; + } + + res = window->win32.taskbarList->lpVtbl->SetProgressState(window->win32.taskbarList, window->win32.handle, winProgressState); + if (res != S_OK) + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to set taskbar progress state"); +} + +typedef struct +{ + HDC hdc; + HDC hdcMem; + HDC hdcMemMask; + + HBITMAP hBitmap; + HBITMAP hBitmapMask; + HBITMAP hOldBitmap; + HBITMAP hOldBitmapMask; + + HBRUSH hForegroundBrush; + HBRUSH hBackgroundBrush; + HBRUSH hOldBrush; + + HFONT hFont; + HFONT hOldFont; + + COLORREF hOldColor; + + int oldBkMode; + + UINT oldTextAlign; +} BadgeData; + +static void CleanupBadgeData(HWND hWnd, BadgeData* data) +{ + if(!data) + return; + + if(data->oldTextAlign != GDI_ERROR) + SetTextAlign(data->hdcMem, data->oldTextAlign); + + if(data->oldBkMode) + SetBkMode(data->hdcMem, data->oldBkMode); + + if(data->hOldColor != CLR_INVALID) + SetTextColor(data->hdcMem, data->hOldColor); + + if(data->hFont) + DeleteObject(data->hFont); + if(data->hOldFont) + SelectObject(data->hdcMem, data->hOldFont); + + if (data->hForegroundBrush) + DeleteObject(data->hForegroundBrush); + if (data->hBackgroundBrush) + DeleteObject(data->hBackgroundBrush); + + if(data->hOldBrush) + SelectObject(data->hdcMem, data->hOldBrush); + + if(data->hOldBitmap) + SelectObject(data->hdcMem, data->hOldBitmap); + if(data->hOldBitmapMask) + SelectObject(data->hdcMem, data->hOldBitmapMask); + + if(data->hBitmap) + DeleteObject(data->hBitmap); + if(data->hBitmapMask) + DeleteObject(data->hBitmapMask); + + if(data->hdcMem) + DeleteDC(data->hdcMem); + if(data->hdcMemMask) + DeleteDC(data->hdcMemMask); + + if(data->hdc) + ReleaseDC(hWnd, data->hdc); +} + +static HICON GenerateTextBadgeIcon(HWND hWnd, WCHAR* text) +{ + BadgeData badgeData; + + void* bits = NULL; + DWORD* pixels = NULL; + ICONINFO iconInfo; + int width = 16, height = 16; + int fontSize = 16, weight = FW_REGULAR; + RECT contentRect = { 0, 0, width, height }; + HICON hIcon = NULL; + BITMAPINFO bmi = {0}; + int x = 0, y = 0; + + memset(&badgeData, 0, sizeof(BadgeData)); + + if (!text) + return NULL; + + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + badgeData.hdc = GetDC(hWnd); + if(!badgeData.hdc) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hdcMem = CreateCompatibleDC(badgeData.hdc); + if(!badgeData.hdcMem) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hdcMemMask = CreateCompatibleDC(badgeData.hdc); + if(!badgeData.hdcMemMask) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hBitmap = CreateDIBSection(badgeData.hdc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); + if(!badgeData.hBitmap) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + pixels = (DWORD*)bits; + + badgeData.hBitmapMask = CreateCompatibleBitmap(badgeData.hdc, width, height); + if(!badgeData.hBitmapMask) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hOldBitmap = (HBITMAP)SelectObject(badgeData.hdcMem, badgeData.hBitmap); + badgeData.hOldBitmapMask = (HBITMAP)SelectObject(badgeData.hdcMemMask, badgeData.hBitmapMask); + + if(BitBlt(badgeData.hdcMemMask, 0, 0, width, height, badgeData.hdcMem, 0, 0, SRCCOPY) == FALSE) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hBackgroundBrush = CreateSolidBrush(RGB(0x26, 0x25, 0x2D)); + if(!badgeData.hBackgroundBrush) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hOldBrush = (HBRUSH)SelectObject(badgeData.hdcMem, badgeData.hBackgroundBrush); + + if(Ellipse(badgeData.hdcMem, 0, 0, width + 1, height + 1) == FALSE) //17x17 gives a more fancy ellipse + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + //Adjust font size depending on digits to display + if (lstrlen(text) > 2) + { + fontSize = 10; + weight = FW_LIGHT; + } + else if (lstrlen(text) > 1) + fontSize = 14; + + //Create and set font + badgeData.hFont = CreateFont(fontSize, 0, 0, 0, weight, FALSE, FALSE, FALSE, 0, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, + DEFAULT_PITCH | FF_DONTCARE, TEXT("Segoe UI")); + if(!badgeData.hFont) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hOldFont = (HFONT)SelectObject(badgeData.hdcMem, badgeData.hFont); + + //Draw text (center aligned) + badgeData.hOldColor = SetTextColor(badgeData.hdcMem, RGB(255, 255, 255)); //Use white text color + if(badgeData.hOldColor == CLR_INVALID) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.oldBkMode = SetBkMode(badgeData.hdcMem, TRANSPARENT); //Make font background transparent + if(!badgeData.oldBkMode) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.oldTextAlign = SetTextAlign(badgeData.hdcMem, TA_LEFT | TA_TOP | TA_NOUPDATECP); + if(badgeData.oldTextAlign == GDI_ERROR) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + if(!DrawText(badgeData.hdcMem, text, lstrlen(text), &contentRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE)) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + //Transparency + for (y = 0; y < height; ++y) + { + for (x = 0; x < width; ++x) + { + const DWORD pixel = pixels[y * width + x]; + if (pixel == 0x0026252Du || pixel == 0x00FFFFFFu) //Pixel is text or ellipsis + pixels[y * width + x] |= 0xFF000000u; //Set opaque + else + pixels[y * width + x] &= 0xFF000000u; //Set fully transparent + } + } + + SelectObject(badgeData.hdcMem, badgeData.hOldBitmap); + badgeData.hOldBitmap = NULL; + SelectObject(badgeData.hdcMemMask, badgeData.hOldBitmapMask); + badgeData.hOldBitmapMask = NULL; + + //Generate icon from bitmap + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = badgeData.hBitmapMask; + iconInfo.hbmColor = badgeData.hBitmap; + hIcon = CreateIconIndirect(&iconInfo); + + CleanupBadgeData(hWnd, &badgeData); + + return hIcon; +} + +static HICON GenerateGenericBadgeIcon(HWND hWnd) +{ + BadgeData badgeData; + + void* bits = NULL; + DWORD* pixels = NULL; + ICONINFO iconInfo; + int width = 32, height = 32; + HICON hIcon = NULL; + BITMAPINFO bmi; + int x = 0, y = 0; + + memset(&badgeData, 0, sizeof(BadgeData)); + + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + badgeData.hdc = GetDC(hWnd); + if (!badgeData.hdc) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hdcMem = CreateCompatibleDC(badgeData.hdc); + if (!badgeData.hdcMem) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hdcMemMask = CreateCompatibleDC(badgeData.hdc); + if (!badgeData.hdcMemMask) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hBitmap = CreateDIBSection(badgeData.hdc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); + if (!badgeData.hBitmap) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + pixels = (DWORD*)bits; + + badgeData.hBitmapMask = CreateCompatibleBitmap(badgeData.hdc, width, height); + if (!badgeData.hBitmapMask) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hOldBitmap = (HBITMAP)SelectObject(badgeData.hdcMem, badgeData.hBitmap); + badgeData.hOldBitmapMask = (HBITMAP)SelectObject(badgeData.hdcMemMask, badgeData.hBitmapMask); + + if (BitBlt(badgeData.hdcMemMask, 0, 0, width, height, badgeData.hdcMem, 0, 0, SRCCOPY) == FALSE) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hBackgroundBrush = CreateSolidBrush(RGB(0xEB, 0x5A, 0x5E)); + if (!badgeData.hBackgroundBrush) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hOldBrush = (HBRUSH)SelectObject(badgeData.hdcMem, badgeData.hBackgroundBrush); + + if (Ellipse(badgeData.hdcMem, 0, 0, width + 1, height + 1) == FALSE) //17x17 gives a more fancy ellipse + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + badgeData.hForegroundBrush = CreateSolidBrush(RGB(0xFF, 0xFF, 0xFF)); + if (!badgeData.hForegroundBrush) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + SelectObject(badgeData.hdcMem, badgeData.hForegroundBrush); + + if (Ellipse(badgeData.hdcMem, 9, 9, (width - 8), (height - 8)) == FALSE) + { + CleanupBadgeData(hWnd, &badgeData); + return NULL; + } + + SelectObject(badgeData.hdcMem, badgeData.hOldBitmap); + badgeData.hOldBitmap = NULL; + SelectObject(badgeData.hdcMemMask, badgeData.hOldBitmapMask); + badgeData.hOldBitmapMask = NULL; + + //Transparency + for (y = 0; y < height; ++y) + { + for (x = 0; x < width; ++x) + { + const DWORD pixel = pixels[y * width + x]; + if (pixel >= 0x00010101u) //Pixel is ellipsis + pixels[y * width + x] |= 0xFF000000u; //Set opaque + else + pixels[y * width + x] &= 0xFF000000u; //Set fully transparent + } + } + + //Generate icon from bitmap + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = badgeData.hBitmapMask; + iconInfo.hbmColor = badgeData.hBitmap; + hIcon = CreateIconIndirect(&iconInfo); + + CleanupBadgeData(hWnd, &badgeData); + + return hIcon; +} + +void _glfwSetWindowBadgeWin32(_GLFWwindow* window, int count) +{ + HRESULT res = S_OK; + HICON icon = NULL; + char countStr[4]; + WCHAR* countWStr = NULL; + + if (window == NULL) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Win32: Taskbar badge requires a valid window handle"); + return; + } + + if (!IsWindows7OrGreater()) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Win32: Taskbar badge is only supported on Windows 7 and newer"); + return; + } + + if (!window->win32.taskbarList) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to set taskbar badge count"); + return; + } + + count = _glfw_min(count, 999); + if (count > 0) + { + if (window->win32.genericBadge) + icon = GenerateGenericBadgeIcon(window->win32.handle); + else + { + //Convert count to string (its guaranteed to be at max 3 digits) + memset(countStr, 0, 4 * sizeof(char)); + sprintf(countStr, "%d", count); + countWStr = _glfwCreateWideStringFromUTF8Win32(countStr); + if (!countWStr) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to set taskbar badge count"); + return; + } + + icon = GenerateTextBadgeIcon(window->win32.handle, countWStr); + } + + if (!icon) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to set taskbar badge count"); + _glfw_free(countWStr); + return; + } + } + + res = window->win32.taskbarList->lpVtbl->SetOverlayIcon(window->win32.taskbarList, window->win32.handle, icon, countWStr ? countWStr : TEXT("")); + + if (countWStr) + _glfw_free(countWStr); + + if(icon) + DestroyIcon(icon); + + if (res != S_OK) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to set taskbar badge count"); + return; + } +} + +void _glfwSetWindowBadgeStringWin32(_GLFWwindow* window, const char* string) +{ + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Win32: Unable to set a string badge. Only integer badges are supported"); + //In reality you can display a string but it could only be 3 maybe 4 characters long till it becomes an unreadable mess. +} + void _glfwGetWindowPosWin32(_GLFWwindow* window, int* xpos, int* ypos) { POINT pos = { 0, 0 }; diff --git a/src/window.c b/src/window.c index e03121a4..8a59281e 100644 --- a/src/window.c +++ b/src/window.c @@ -379,6 +379,9 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_WIN32_SHOWDEFAULT: _glfw.hints.window.win32.showDefault = value ? GLFW_TRUE : GLFW_FALSE; return; + case GLFW_WIN32_GENERIC_BADGE: + _glfw.hints.window.win32.genericBadge = value ? GLFW_TRUE : GLFW_FALSE; + return; case GLFW_COCOA_GRAPHICS_SWITCHING: _glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE; return; @@ -579,6 +582,55 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* handle, _glfw.platform.setWindowIcon(window, count, images); } +GLFWAPI void glfwSetWindowProgressIndicator(GLFWwindow* handle, int progressState, double value) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + + assert(window != NULL); + + _GLFW_REQUIRE_INIT(); + + if (value < 0.0 || value > 1.0) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid progress amount for window progress indicator"); + return; + } + + if (progressState != GLFW_PROGRESS_INDICATOR_DISABLED && progressState != GLFW_PROGRESS_INDICATOR_INDETERMINATE && + progressState != GLFW_PROGRESS_INDICATOR_NORMAL && progressState != GLFW_PROGRESS_INDICATOR_ERROR && + progressState != GLFW_PROGRESS_INDICATOR_PAUSED) + { + _glfwInputError(GLFW_INVALID_ENUM, "Invalid progress state 0x%08X", progressState); + return; + } + + _glfw.platform.setWindowProgressIndicator(window, progressState, value); +} + +GLFWAPI void glfwSetWindowBadge(GLFWwindow* handle, int count) +{ + _GLFWwindow* window = (_GLFWwindow*)handle; + + _GLFW_REQUIRE_INIT(); + + if (count < 0) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid badge count %d", count); + return; + } + + _glfw.platform.setWindowBadge(window, count); +} + +GLFWAPI void glfwSetWindowBadgeString(GLFWwindow* handle, const char* string) +{ + _GLFWwindow* window = (_GLFWwindow*)handle; + + _GLFW_REQUIRE_INIT(); + + _glfw.platform.setWindowBadgeString(window, string); +} + GLFWAPI void glfwGetWindowPos(GLFWwindow* handle, int* xpos, int* ypos) { if (xpos) diff --git a/src/wl_init.c b/src/wl_init.c index ef9e4503..88621d9f 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -477,6 +477,9 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowWayland, .setWindowTitle = _glfwSetWindowTitleWayland, .setWindowIcon = _glfwSetWindowIconWayland, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorWayland, + .setWindowBadge = _glfwSetWindowBadgeWayland, + .setWindowBadgeString = _glfwSetWindowBadgeStringWayland, .getWindowPos = _glfwGetWindowPosWayland, .setWindowPos = _glfwSetWindowPosWayland, .getWindowSize = _glfwGetWindowSizeWayland, @@ -563,6 +566,8 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) int _glfwInitWayland(void) { + _glfwInitDBusPOSIX(); + // These must be set before any failure checks _glfw.wl.keyRepeatTimerfd = -1; _glfw.wl.cursorTimerfd = -1; @@ -1002,6 +1007,8 @@ void _glfwTerminateWayland(void) close(_glfw.wl.cursorTimerfd); _glfw_free(_glfw.wl.clipboardString); + + _glfwTerminateDBusPOSIX(); } #endif // _GLFW_WAYLAND diff --git a/src/wl_platform.h b/src/wl_platform.h index afa6f50a..3c496295 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -619,6 +619,9 @@ GLFWbool _glfwCreateWindowWayland(_GLFWwindow* window, const _GLFWwndconfig* wnd void _glfwDestroyWindowWayland(_GLFWwindow* window); void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title); void _glfwSetWindowIconWayland(_GLFWwindow* window, int count, const GLFWimage* images); +void _glfwSetWindowProgressIndicatorWayland(_GLFWwindow* window, int progressState, double value); +void _glfwSetWindowBadgeWayland(_GLFWwindow* window, int count); +void _glfwSetWindowBadgeStringWayland(_GLFWwindow* window, const char* string); void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosWayland(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeWayland(_GLFWwindow* window, int* width, int* height); diff --git a/src/wl_window.c b/src/wl_window.c index 72c1a402..c757e133 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -2174,6 +2174,14 @@ GLFWbool _glfwCreateWindowWayland(_GLFWwindow* window, return GLFW_FALSE; } + //Reset progress state as it gets saved between application runs + if(_glfw.dbus.connection) + { + //Window NULL is safe here because it won't get + //used inside the SetWindowTaskbarProgress function + _glfwSetWindowProgressIndicatorWayland(NULL, GLFW_PROGRESS_INDICATOR_DISABLED, 0.0); + } + return GLFW_TRUE; } @@ -2239,6 +2247,35 @@ void _glfwSetWindowIconWayland(_GLFWwindow* window, "Wayland: The platform does not support setting the window icon"); } +void _glfwSetWindowProgressIndicatorWayland(_GLFWwindow* window, const int progressState, double value) +{ + (void)window; + + const dbus_bool_t progressVisible = (progressState != GLFW_PROGRESS_INDICATOR_DISABLED); + + _glfwUpdateTaskbarProgressDBusPOSIX(progressVisible, value); +} + +void _glfwSetWindowBadgeWayland(_GLFWwindow* window, int count) +{ + if (window != NULL) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: Cannot set a badge for a window. Pass NULL to set the application's shared badge."); + return; + } + + const dbus_bool_t badgeVisible = (count > 0); + + _glfwUpdateBadgeDBusPOSIX(badgeVisible, count); +} + +void _glfwSetWindowBadgeStringWayland(_GLFWwindow* window, const char* string) +{ + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: Unable to set a string badge. Only integer badges are supported."); +} + void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos) { // A Wayland client is not aware of its position, so just warn and leave it diff --git a/src/x11_init.c b/src/x11_init.c index 6b34c263..8b004174 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -1208,6 +1208,9 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowX11, .setWindowTitle = _glfwSetWindowTitleX11, .setWindowIcon = _glfwSetWindowIconX11, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorX11, + .setWindowBadge = _glfwSetWindowBadgeX11, + .setWindowBadgeString = _glfwSetWindowBadgeStringX11, .getWindowPos = _glfwGetWindowPosX11, .setWindowPos = _glfwSetWindowPosX11, .getWindowSize = _glfwGetWindowSizeX11, @@ -1320,6 +1323,8 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) int _glfwInitX11(void) { + _glfwInitDBusPOSIX(); + _glfw.x11.xlib.AllocClassHint = (PFN_XAllocClassHint) _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XAllocClassHint"); _glfw.x11.xlib.AllocSizeHints = (PFN_XAllocSizeHints) @@ -1651,6 +1656,8 @@ void _glfwTerminateX11(void) close(_glfw.x11.emptyEventPipe[0]); close(_glfw.x11.emptyEventPipe[1]); } + + _glfwTerminateDBusPOSIX(); } #endif // _GLFW_X11 diff --git a/src/x11_platform.h b/src/x11_platform.h index 30326c5b..5ef51517 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -905,6 +905,9 @@ GLFWbool _glfwCreateWindowX11(_GLFWwindow* window, const _GLFWwndconfig* wndconf void _glfwDestroyWindowX11(_GLFWwindow* window); void _glfwSetWindowTitleX11(_GLFWwindow* window, const char* title); void _glfwSetWindowIconX11(_GLFWwindow* window, int count, const GLFWimage* images); +void _glfwSetWindowProgressIndicatorX11(_GLFWwindow* window, int progressState, double value); +void _glfwSetWindowBadgeX11(_GLFWwindow* window, int count); +void _glfwSetWindowBadgeStringX11(_GLFWwindow* window, const char* string); void _glfwGetWindowPosX11(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosX11(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeX11(_GLFWwindow* window, int* width, int* height); diff --git a/src/x11_window.c b/src/x11_window.c index 322349f0..e07724c5 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2040,6 +2040,14 @@ GLFWbool _glfwCreateWindowX11(_GLFWwindow* window, } } + //Reset progress state as it gets saved between application runs + if(_glfw.dbus.connection) + { + //Window NULL is safe here because it won't get + //used inside the SetWindowTaskbarProgress function + _glfwSetWindowProgressIndicatorX11(NULL, GLFW_PROGRESS_INDICATOR_DISABLED, 0.0); + } + XFlush(_glfw.x11.display); return GLFW_TRUE; } @@ -2152,6 +2160,35 @@ void _glfwSetWindowIconX11(_GLFWwindow* window, int count, const GLFWimage* imag XFlush(_glfw.x11.display); } +void _glfwSetWindowProgressIndicatorX11(_GLFWwindow* window, int progressState, double value) +{ + (void)window; + + const dbus_bool_t progressVisible = (progressState != GLFW_PROGRESS_INDICATOR_DISABLED); + + _glfwUpdateTaskbarProgressDBusPOSIX(progressVisible, value); +} + +void _glfwSetWindowBadgeX11(_GLFWwindow* window, int count) +{ + if (window != NULL) + { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "X11: Cannot set a badge for a window. Pass NULL to set the application's shared badge."); + return; + } + + const dbus_bool_t badgeVisible = (count > 0); + + _glfwUpdateBadgeDBusPOSIX(badgeVisible, count); +} + +void _glfwSetWindowBadgeStringX11(_GLFWwindow* window, const char* string) +{ + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "X11: Unable to set a string badge. Only integer badges are supported."); +} + void _glfwGetWindowPosX11(_GLFWwindow* window, int* xpos, int* ypos) { Window dummy; diff --git a/tests/window.c b/tests/window.c index ffea5dbf..ecacab60 100644 --- a/tests/window.c +++ b/tests/window.c @@ -72,7 +72,7 @@ int main(int argc, char** argv) glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - GLFWwindow* window = glfwCreateWindow(600, 660, "Window Features", NULL, NULL); + GLFWwindow* window = glfwCreateWindow(600, 800, "Window Features", NULL, NULL); if (!window) { glfwTerminate(); @@ -441,6 +441,45 @@ int main(int argc, char** argv) nk_value_bool(nk, "Visible", glfwGetWindowAttrib(window, GLFW_VISIBLE)); nk_value_bool(nk, "Iconified", glfwGetWindowAttrib(window, GLFW_ICONIFIED)); nk_value_bool(nk, "Maximized", glfwGetWindowAttrib(window, GLFW_MAXIMIZED)); + + nk_layout_row_dynamic(nk, 30, 1); + + nk_label(nk, "Window Progress indicator", NK_TEXT_CENTERED); + + nk_layout_row_dynamic(nk, 30, 5); + + static int state = GLFW_PROGRESS_INDICATOR_DISABLED; + static float progress = 0; + if(nk_button_label(nk, "No progress")) + glfwSetWindowProgressIndicator(window, state = GLFW_PROGRESS_INDICATOR_DISABLED, (double) progress); + if (nk_button_label(nk, "Indeterminate")) + glfwSetWindowProgressIndicator(window, state = GLFW_PROGRESS_INDICATOR_INDETERMINATE, (double) progress); + if (nk_button_label(nk, "Normal")) + glfwSetWindowProgressIndicator(window, state = GLFW_PROGRESS_INDICATOR_NORMAL, (double) progress); + if (nk_button_label(nk, "Error")) + glfwSetWindowProgressIndicator(window, state = GLFW_PROGRESS_INDICATOR_ERROR, (double) progress); + if (nk_button_label(nk, "Paused")) + glfwSetWindowProgressIndicator(window, state = GLFW_PROGRESS_INDICATOR_PAUSED, (double) progress); + + nk_label(nk, "Progress: ", NK_TEXT_ALIGN_LEFT); + if (nk_slider_float(nk, 0.0f, &progress, 1.0f, 0.05f)) + glfwSetWindowProgressIndicator(window, state, (double) progress); + + nk_layout_row_dynamic(nk, 30, 1); + + nk_label(nk, "Badge", NK_TEXT_CENTERED); + + static int badgeCount = 0; + nk_layout_row_begin(nk, NK_DYNAMIC, 30, 3); + nk_layout_row_push(nk, 1.0f / 3.f); + nk_labelf(nk, NK_TEXT_LEFT, "Badge count: %d", badgeCount); + nk_layout_row_push(nk, 2.f / 3.f); + if (nk_slider_int(nk, 0, &badgeCount, 10000, 1)) + { + glfwSetWindowBadge(window, badgeCount); + glfwSetWindowBadge(NULL, badgeCount); + } + nk_layout_row_end(nk); } nk_end(nk);