diff --git a/README.md b/README.md index 0b673afc..c65e854e 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ does not find Doxygen, the documentation will not be generated. - Added `glfwGetKeyName` for querying the layout-specific name of printable keys - Added `glfwWaitEventsTimeout` for waiting for events for a set amount of time + - Added `glfwSetWindowIcon` for setting the icon of a window - Added `glfwGetTimerValue` and `glfwGetTimerFrequency` for raw timer access - Added `GLFWuint64` for platform-independent 64-bit unsigned values - Added `GLFW_NO_API` for creating window without contexts @@ -221,6 +222,7 @@ skills. - Peoro - Braden Pellett - Arturo J. PĂ©rez + - Orson Peters - Emmanuel Gil Peyrot - Cyril Pichard - Pieroman @@ -246,6 +248,7 @@ skills. - TTK-Bandit - Sergey Tikhomirov - A. Tombs + - Ioannis Tsakpinis - Samuli Tuomola - urraka - Jari Vetoniemi diff --git a/docs/news.dox b/docs/news.dox index b1d0e30d..37c591e0 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -32,6 +32,11 @@ GLFW now supports window maximization with @ref glfwMaximizeWindow and the [GLFW_MAXIMIZED](@ref window_attribs_wnd) window hint and attribute. +@subsection news_32_icon Window icon support + +GLFW now supports setting the icon of windows with @ref glfwSetWindowIcon. + + @subsection news_32_focus Window input focus control GLFW now supports giving windows input focus with @ref glfwFocusWindow. diff --git a/docs/window.dox b/docs/window.dox index a40005d9..17bba73e 100644 --- a/docs/window.dox +++ b/docs/window.dox @@ -617,6 +617,26 @@ glfwSetWindowTitle(window, u8"This is always a UTF-8 string"); @endcode +@subsection window_icon Window icon + +Decorated windows have icons on some platforms. You can set this icon by +specifying a list of candidate images with @ref glfwSetWindowIcon. + +@code +GLFWimage images[2]; +images[0] = load_icon("my_icon.png"); +images[1] = load_icon("my_icon_small.png"); + +glfwSetWindowIcon(window, 2, images); +@endcode + +To revert to the default window icon, pass in an empty image array. + +@code +glfwSetWindowIcon(window, 0, NULL); +@endcode + + @subsection window_monitor Window monitor Full screen windows are associated with a specific monitor. You can get the diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 06b64f0c..7557f1cf 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1913,6 +1913,45 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); */ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title); +/*! @brief Sets the icon for the specified window. + * + * This function sets the icon of the specified window. If passed an array of + * candidate images, those of or closest to the sizes desired by the system are + * selected. If no images are specified, the window reverts to its default + * icon. + * + * The desired image sizes varies depending on platform and system settings. + * The selected images will be rescaled as needed. Good sizes include 16x16, + * 32x32 and 48x48. + * + * @param[in] window The window whose icon to set. + * @param[in] count The number of images in the specified array, or zero to + * revert to the default window icon. + * @param[in] images The images to create the icon from. This is ignored if + * count is zero. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @pointer_lifetime The specified image data is copied before this function + * returns. + * + * @remark @osx The GLFW window has no icon, as it is not a document + * window, but the dock icon will be the same as the application bundle's icon. + * For more information on bundles, see the + * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) + * in the Mac Developer Library. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref window_icon + * + * @since Added in version 3.2. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* images); + /*! @brief Retrieves the position of the client area of the specified window. * * This function retrieves the position, in screen coordinates, of the diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 7c3f5606..c7d0a06d 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1032,6 +1032,12 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char *title) [window->ns.object setTitle:[NSString stringWithUTF8String:title]]; } +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + // Regular windows do not have icons +} + void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { const NSRect contentRect = diff --git a/src/internal.h b/src/internal.h index c9ad097c..e1466dc5 100644 --- a/src/internal.h +++ b/src/internal.h @@ -617,6 +617,11 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window); */ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title); +/*! @copydoc glfwSetWindowIcon + * @ingroup platform + */ +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images); + /*! @copydoc glfwGetWindowPos * @ingroup platform */ diff --git a/src/mir_window.c b/src/mir_window.c index b3c3acf2..3b256081 100644 --- a/src/mir_window.c +++ b/src/mir_window.c @@ -403,6 +403,13 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) mir_surface_spec_release(spec); } +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + _glfwInputError(GLFW_PLATFORM_ERROR, + "Mir: Unsupported function %s", __PRETTY_FUNCTION__); +} + void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { MirSurfaceSpec* spec; diff --git a/src/win32_platform.h b/src/win32_platform.h index 4ac274b6..433c4587 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -191,6 +191,8 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)( typedef struct _GLFWwindowWin32 { HWND handle; + HICON bigIcon; + HICON smallIcon; GLFWbool cursorTracked; GLFWbool iconified; diff --git a/src/win32_window.c b/src/win32_window.c index 0d0ec89f..8d7e2ab7 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -27,6 +27,7 @@ #include "internal.h" +#include #include #include #include @@ -66,6 +67,112 @@ static DWORD getWindowExStyle(const _GLFWwindow* window) return style; } +// Returns the image whose area most closely matches the desired one +// +static const GLFWimage* chooseImage(int count, const GLFWimage* images, + int width, int height) +{ + int i, leastDiff = INT_MAX; + const GLFWimage* closest = NULL; + + for (i = 0; i < count; i++) + { + const int currDiff = abs(images[i].width * images[i].height - + width * height); + if (currDiff < leastDiff) + { + closest = images + i; + leastDiff = currDiff; + } + } + + return closest; +} + +// Creates an RGBA icon or cursor +// +static HICON createIcon(const GLFWimage* image, + int xhot, int yhot, GLFWbool icon) +{ + int i; + HDC dc; + HICON handle; + HBITMAP color, mask; + BITMAPV5HEADER bi; + ICONINFO ii; + unsigned char* target = NULL; + unsigned char* source = image->pixels; + + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(BITMAPV5HEADER); + bi.bV5Width = image->width; + bi.bV5Height = -image->height; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00ff0000; + bi.bV5GreenMask = 0x0000ff00; + bi.bV5BlueMask = 0x000000ff; + bi.bV5AlphaMask = 0xff000000; + + dc = GetDC(NULL); + color = CreateDIBSection(dc, + (BITMAPINFO*) &bi, + DIB_RGB_COLORS, + (void**) &target, + NULL, + (DWORD) 0); + ReleaseDC(NULL, dc); + + if (!color) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Failed to create RGBA bitmap"); + return NULL; + } + + mask = CreateBitmap(image->width, image->height, 1, 1, NULL); + if (!mask) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Failed to create mask bitmap"); + DeleteObject(color); + return NULL; + } + + for (i = 0; i < image->width * image->height; i++) + { + target[0] = source[2]; + target[1] = source[1]; + target[2] = source[0]; + target[3] = source[3]; + target += 4; + source += 4; + } + + ZeroMemory(&ii, sizeof(ii)); + ii.fIcon = icon; + ii.xHotspot = xhot; + ii.yHotspot = yhot; + ii.hbmMask = mask; + ii.hbmColor = color; + + handle = CreateIconIndirect(&ii); + + DeleteObject(color); + DeleteObject(mask); + + if (!handle) + { + if (icon) + _glfwInputError(GLFW_PLATFORM_ERROR, "Win32: Failed to create icon"); + else + _glfwInputError(GLFW_PLATFORM_ERROR, "Win32: Failed to create cursor"); + } + + return handle; +} + // Translate client window size to full window size according to styles // static void getFullWindowSize(DWORD style, DWORD exStyle, @@ -886,6 +993,12 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) } destroyWindow(window); + + if (window->win32.bigIcon) + DestroyIcon(window->win32.bigIcon); + + if (window->win32.smallIcon) + DestroyIcon(window->win32.smallIcon); } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) @@ -902,6 +1015,37 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) free(wideTitle); } +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + HICON bigIcon = NULL, smallIcon = NULL; + + if (count) + { + const GLFWimage* bigImage = chooseImage(count, images, + GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON)); + const GLFWimage* smallImage = chooseImage(count, images, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON)); + + bigIcon = createIcon(bigImage, 0, 0, GLFW_TRUE); + smallIcon = createIcon(smallImage, 0, 0, GLFW_TRUE); + } + + SendMessage(window->win32.handle, WM_SETICON, ICON_BIG, (LPARAM) bigIcon); + SendMessage(window->win32.handle, WM_SETICON, ICON_SMALL, (LPARAM) smallIcon); + + if (window->win32.bigIcon) + DestroyIcon(window->win32.bigIcon); + + if (window->win32.smallIcon) + DestroyIcon(window->win32.smallIcon); + + window->win32.bigIcon = bigIcon; + window->win32.smallIcon = smallIcon; +} + void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { POINT pos = { 0, 0 }; @@ -1236,61 +1380,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) { - HDC dc; - HBITMAP bitmap, mask; - BITMAPV5HEADER bi; - ICONINFO ii; - DWORD* target = 0; - BYTE* source = (BYTE*) image->pixels; - int i; - - ZeroMemory(&bi, sizeof(bi)); - bi.bV5Size = sizeof(BITMAPV5HEADER); - bi.bV5Width = image->width; - bi.bV5Height = -image->height; - bi.bV5Planes = 1; - bi.bV5BitCount = 32; - bi.bV5Compression = BI_BITFIELDS; - bi.bV5RedMask = 0x00ff0000; - bi.bV5GreenMask = 0x0000ff00; - bi.bV5BlueMask = 0x000000ff; - bi.bV5AlphaMask = 0xff000000; - - dc = GetDC(NULL); - bitmap = CreateDIBSection(dc, (BITMAPINFO*) &bi, DIB_RGB_COLORS, - (void**) &target, NULL, (DWORD) 0); - ReleaseDC(NULL, dc); - - if (!bitmap) - return GLFW_FALSE; - - mask = CreateBitmap(image->width, image->height, 1, 1, NULL); - if (!mask) - { - DeleteObject(bitmap); - return GLFW_FALSE; - } - - for (i = 0; i < image->width * image->height; i++, target++, source += 4) - { - *target = (source[3] << 24) | - (source[0] << 16) | - (source[1] << 8) | - source[2]; - } - - ZeroMemory(&ii, sizeof(ii)); - ii.fIcon = FALSE; - ii.xHotspot = xhot; - ii.yHotspot = yhot; - ii.hbmMask = mask; - ii.hbmColor = bitmap; - - cursor->win32.handle = (HCURSOR) CreateIconIndirect(&ii); - - DeleteObject(bitmap); - DeleteObject(mask); - + cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE); if (!cursor->win32.handle) return GLFW_FALSE; diff --git a/src/window.c b/src/window.c index 8e055b3b..8f627ecc 100644 --- a/src/window.c +++ b/src/window.c @@ -447,6 +447,18 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* handle, const char* title) _glfwPlatformSetWindowTitle(window, title); } +GLFWAPI void glfwSetWindowIcon(GLFWwindow* handle, + int count, const GLFWimage* images) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + assert(count >= 0); + assert(count == 0 || images != NULL); + + _GLFW_REQUIRE_INIT(); + _glfwPlatformSetWindowIcon(window, count, images); +} + GLFWAPI void glfwGetWindowPos(GLFWwindow* handle, int* xpos, int* ypos) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/wl_window.c b/src/wl_window.c index f1d5b520..3920e82e 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -383,6 +383,13 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) wl_shell_surface_set_title(window->wl.shell_surface, title); } +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + // TODO + fprintf(stderr, "_glfwPlatformSetWindowIcon not implemented yet\n"); +} + void _glfwPlatformGetWindowPos(_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 e73bb131..e1c17a3f 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -454,6 +454,8 @@ static void detectEWMH(void) getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_NAME"); _glfw.x11.NET_WM_ICON_NAME = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_ICON_NAME"); + _glfw.x11.NET_WM_ICON = + getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_ICON"); _glfw.x11.NET_WM_PID = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_PID"); _glfw.x11.NET_WM_PING = diff --git a/src/x11_platform.h b/src/x11_platform.h index caad2530..92957587 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -167,6 +167,7 @@ typedef struct _GLFWlibraryX11 Atom WM_DELETE_WINDOW; Atom NET_WM_NAME; Atom NET_WM_ICON_NAME; + Atom NET_WM_ICON; Atom NET_WM_PID; Atom NET_WM_PING; Atom NET_WM_STATE; diff --git a/src/x11_window.c b/src/x11_window.c index c4bbf9f1..e198b815 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1536,6 +1536,51 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) XFlush(_glfw.x11.display); } +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + if (count) + { + int i, j, longCount = 0; + + for (i = 0; i < count; i++) + longCount += 2 + images[i].width * images[i].height; + + long* icon = calloc(longCount, sizeof(long)); + long* target = icon; + + for (i = 0; i < count; i++) + { + *target++ = images[i].width; + *target++ = images[i].height; + + for (j = 0; j < images[i].width * images[i].height; i++) + { + *target++ = (images[i].pixels[i * 4 + 0] << 16) | + (images[i].pixels[i * 4 + 1] << 8) | + (images[i].pixels[i * 4 + 2] << 0) | + (images[i].pixels[i * 4 + 3] << 24); + } + } + + XChangeProperty(_glfw.x11.display, window->x11.handle, + _glfw.x11.NET_WM_ICON, + XA_CARDINAL, 32, + PropModeReplace, + (unsigned char*) icon, + longCount); + + free(icon); + } + else + { + XDeleteProperty(_glfw.x11.display, window->x11.handle, + _glfw.x11.NET_WM_ICON); + } + + XFlush(_glfw.x11.display); +} + void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { Window child;