diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4fc27126..2b707da2 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 @@ -228,6 +229,7 @@ video tutorials. - Yoshinori Sano - Brandon Schaefer - Sebastian Schuberth + - Jan Schuerkamp - Scr3amer - Jan Schuerkamp - Christian Sdunek diff --git a/README.md b/README.md index 52306188..c457ff49 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ information on what to include when reporting a bug. ## Changelog since 3.4 + - 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..d6147b3c 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1288,6 +1288,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 +3466,46 @@ 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 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..338c0d37 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -526,6 +526,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowCocoa, .setWindowTitle = _glfwSetWindowTitleCocoa, .setWindowIcon = _glfwSetWindowIconCocoa, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorCocoa, .getWindowPos = _glfwGetWindowPosCocoa, .setWindowPos = _glfwSetWindowPosCocoa, .getWindowSize = _glfwGetWindowSizeCocoa, @@ -646,6 +647,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..e4609cf2 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,7 @@ 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 _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..597359fe 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,62 @@ 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 _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos) { @autoreleasepool { diff --git a/src/internal.h b/src/internal.h index 4f097aa8..df428f2c 100644 --- a/src/internal.h +++ b/src/internal.h @@ -718,6 +718,7 @@ struct _GLFWplatform void (*destroyWindow)(_GLFWwindow*); void (*setWindowTitle)(_GLFWwindow*,const char*); void (*setWindowIcon)(_GLFWwindow*,int,const GLFWimage*); + void (*setWindowProgressIndicator)(_GLFWwindow*,const int,double); void (*getWindowPos)(_GLFWwindow*,int*,int*); void (*setWindowPos)(_GLFWwindow*,int,int); void (*getWindowSize)(_GLFWwindow*,int*,int*); @@ -884,6 +885,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..dfde76bb 100644 --- a/src/null_init.c +++ b/src/null_init.c @@ -72,6 +72,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowNull, .setWindowTitle = _glfwSetWindowTitleNull, .setWindowIcon = _glfwSetWindowIconNull, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorNull, .getWindowPos = _glfwGetWindowPosNull, .setWindowPos = _glfwSetWindowPosNull, .getWindowSize = _glfwGetWindowSizeNull, diff --git a/src/null_platform.h b/src/null_platform.h index dbcb835b..f2547825 100644 --- a/src/null_platform.h +++ b/src/null_platform.h @@ -223,6 +223,7 @@ 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 _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..18369c0e 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -186,6 +186,10 @@ void _glfwSetWindowIconNull(_GLFWwindow* window, int count, const GLFWimage* ima { } +void _glfwSetWindowProgressIndicatorNull(_GLFWwindow* window, int progressState, double value) +{ +} + 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..a6c7a858 --- /dev/null +++ b/src/posix_dbus.c @@ -0,0 +1,473 @@ +//======================================================================== +// 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); +} + +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..3adf2bad --- /dev/null +++ b/src/posix_dbus.h @@ -0,0 +1,158 @@ +//======================================================================== +// 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); + +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..5753c5aa 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -635,6 +635,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowWin32, .setWindowTitle = _glfwSetWindowTitleWin32, .setWindowIcon = _glfwSetWindowIconWin32, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorWin32, .getWindowPos = _glfwGetWindowPosWin32, .setWindowPos = _glfwSetWindowPosWin32, .getWindowSize = _glfwGetWindowSizeWin32, diff --git a/src/win32_platform.h b/src/win32_platform.h index a2f86852..08442bf8 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 // @@ -433,6 +506,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 +601,6 @@ typedef struct _GLFWcursorWin32 HCURSOR handle; } _GLFWcursorWin32; - GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform); int _glfwInitWin32(void); void _glfwTerminateWin32(void); @@ -546,6 +621,7 @@ 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 _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..64a89c9a 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1268,6 +1268,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,6 +1427,10 @@ 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; @@ -1568,6 +1584,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 +1649,53 @@ 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 or 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"); +} + void _glfwGetWindowPosWin32(_GLFWwindow* window, int* xpos, int* ypos) { POINT pos = { 0, 0 }; diff --git a/src/window.c b/src/window.c index e03121a4..90029994 100644 --- a/src/window.c +++ b/src/window.c @@ -579,6 +579,31 @@ 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 glfwGetWindowPos(GLFWwindow* handle, int* xpos, int* ypos) { if (xpos) diff --git a/src/wl_init.c b/src/wl_init.c index ef9e4503..8d12f85d 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -477,6 +477,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowWayland, .setWindowTitle = _glfwSetWindowTitleWayland, .setWindowIcon = _glfwSetWindowIconWayland, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorWayland, .getWindowPos = _glfwGetWindowPosWayland, .setWindowPos = _glfwSetWindowPosWayland, .getWindowSize = _glfwGetWindowSizeWayland, @@ -563,6 +564,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 +1005,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..a0613db4 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -619,6 +619,7 @@ 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 _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..10fcc6d2 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,15 @@ 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 _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..10edaf9a 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -1208,6 +1208,7 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) .destroyWindow = _glfwDestroyWindowX11, .setWindowTitle = _glfwSetWindowTitleX11, .setWindowIcon = _glfwSetWindowIconX11, + .setWindowProgressIndicator = _glfwSetWindowProgressIndicatorX11, .getWindowPos = _glfwGetWindowPosX11, .setWindowPos = _glfwSetWindowPosX11, .getWindowSize = _glfwGetWindowSizeX11, @@ -1320,6 +1321,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 +1654,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..94d62e50 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -905,6 +905,7 @@ 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 _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..f6e11d25 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,15 @@ 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 _glfwGetWindowPosX11(_GLFWwindow* window, int* xpos, int* ypos) { Window dummy; diff --git a/tests/window.c b/tests/window.c index ffea5dbf..e87cc37d 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, 700, "Window Features", NULL, NULL); if (!window) { glfwTerminate(); @@ -441,6 +441,29 @@ 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_end(nk);