summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2024-02-13 17:22:55 +0100
committerRémi Verschelde <rverschelde@gmail.com>2024-02-13 17:22:55 +0100
commit7069b3d35191c29bb0c7b6d733c07c8644d05107 (patch)
tree657d8f780586f7a48f6a894119b6955d1b91da09 /platform
parentb9a07ad341b2cbc3e35efff74b8d578383c2b8ea (diff)
parent8da36031e4a52b78d972fc132528a705a3af0750 (diff)
downloadredot-engine-7069b3d35191c29bb0c7b6d733c07c8644d05107.tar.gz
Merge pull request #80211 from bruvzg/status_ind
Implement support for application status indicators (tray icons).
Diffstat (limited to 'platform')
-rw-r--r--platform/macos/SCsub1
-rw-r--r--platform/macos/display_server_macos.h14
-rw-r--r--platform/macos/display_server_macos.mm125
-rw-r--r--platform/macos/godot_status_item.h51
-rw-r--r--platform/macos/godot_status_item.mm101
-rw-r--r--platform/windows/display_server_windows.cpp205
-rw-r--r--platform/windows/display_server_windows.h13
7 files changed, 510 insertions, 0 deletions
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 5a93c3a09f..4dfafc56f8 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -14,6 +14,7 @@ files = [
"display_server_macos.mm",
"godot_button_view.mm",
"godot_content_view.mm",
+ "godot_status_item.mm",
"godot_window_delegate.mm",
"godot_window.mm",
"key_mapping_macos.mm",
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index e298b54970..10c8abe663 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -201,6 +201,14 @@ private:
HashMap<WindowID, WindowData> windows;
+ struct IndicatorData {
+ id view;
+ id item;
+ };
+
+ IndicatorID indicator_id_counter = 0;
+ HashMap<IndicatorID, IndicatorData> indicators;
+
IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID;
struct MenuCall {
@@ -486,6 +494,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
+ virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
+ virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
+ virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
+ virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
+ virtual void delete_status_indicator(IndicatorID p_id) override;
+
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);
static Vector<String> get_rendering_drivers_func();
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index ad8afaf46b..99d4554a42 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -35,6 +35,7 @@
#include "godot_menu_delegate.h"
#include "godot_menu_item.h"
#include "godot_open_save_delegate.h"
+#include "godot_status_item.h"
#include "godot_window.h"
#include "godot_window_delegate.h"
#include "key_mapping_macos.h"
@@ -838,6 +839,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_EXTEND_TO_TITLE:
case FEATURE_SCREEN_CAPTURE:
+ case FEATURE_STATUS_INDICATOR:
return true;
default: {
}
@@ -4296,6 +4298,124 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) {
}
}
+DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
+ NSImage *nsimg = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nullptr
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ if (imgrep) {
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->get_data().ptr();
+
+ /* Premultiply the alpha channel */
+ for (int i = 0; i < len; i++) {
+ uint8_t alpha = r[i * 4 + 3];
+ pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+ pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+ pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ if (nsimg) {
+ [nsimg addRepresentation:imgrep];
+ }
+ }
+ }
+
+ IndicatorData idat;
+
+ idat.item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
+ idat.view = [[GodotStatusItemView alloc] init];
+
+ [idat.view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+ [idat.view setImage:nsimg];
+ [idat.view setCallback:p_callback];
+ [idat.item setView:idat.view];
+
+ IndicatorID iid = indicator_id_counter++;
+ indicators[iid] = idat;
+
+ return iid;
+}
+
+void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NSImage *nsimg = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nullptr
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ if (imgrep) {
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->get_data().ptr();
+
+ /* Premultiply the alpha channel */
+ for (int i = 0; i < len; i++) {
+ uint8_t alpha = r[i * 4 + 3];
+ pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+ pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+ pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ if (nsimg) {
+ [nsimg addRepresentation:imgrep];
+ }
+ }
+ }
+
+ [indicators[p_id].view setImage:nsimg];
+}
+
+void DisplayServerMacOS::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [indicators[p_id].view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+}
+
+void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [indicators[p_id].view setCallback:p_callback];
+}
+
+void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [[NSStatusBar systemStatusBar] removeStatusItem:indicators[p_id].item];
+ indicators.erase(p_id);
+}
+
DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, r_error));
if (r_error != OK) {
@@ -4700,6 +4820,11 @@ DisplayServerMacOS::~DisplayServerMacOS() {
screen_keep_on_assertion = kIOPMNullAssertionID;
}
+ // Destroy all status indicators.
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ [[NSStatusBar systemStatusBar] removeStatusItem:E->value.item];
+ }
+
// Destroy all windows.
for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) {
HashMap<WindowID, WindowData>::Iterator F = E;
diff --git a/platform/macos/godot_status_item.h b/platform/macos/godot_status_item.h
new file mode 100644
index 0000000000..1827baa9bd
--- /dev/null
+++ b/platform/macos/godot_status_item.h
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* godot_status_item.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GODOT_STATUS_ITEM_H
+#define GODOT_STATUS_ITEM_H
+
+#include "core/input/input_enums.h"
+#include "core/variant/callable.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotStatusItemView : NSView {
+ NSImage *image;
+ Callable cb;
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index;
+- (void)setImage:(NSImage *)image;
+- (void)setCallback:(const Callable &)callback;
+
+@end
+
+#endif // GODOT_STATUS_ITEM_H
diff --git a/platform/macos/godot_status_item.mm b/platform/macos/godot_status_item.mm
new file mode 100644
index 0000000000..71ed0a0f71
--- /dev/null
+++ b/platform/macos/godot_status_item.mm
@@ -0,0 +1,101 @@
+/**************************************************************************/
+/* godot_status_item.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "godot_status_item.h"
+
+#include "display_server_macos.h"
+
+@implementation GodotStatusItemView
+
+- (id)init {
+ self = [super init];
+ image = nullptr;
+ return self;
+}
+
+- (void)setImage:(NSImage *)newImage {
+ image = newImage;
+ [self setNeedsDisplayInRect:self.frame];
+}
+
+- (void)setCallback:(const Callable &)callback {
+ cb = callback;
+}
+
+- (void)drawRect:(NSRect)rect {
+ if (image) {
+ [image drawInRect:rect];
+ }
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (!ds) {
+ return;
+ }
+
+ if (cb.is_valid()) {
+ Variant v_button = index;
+ Variant v_pos = ds->mouse_get_position();
+ Variant *v_args[2] = { &v_button, &v_pos };
+ Variant ret;
+ Callable::CallError ce;
+ cb.callp((const Variant **)&v_args, 2, ret, ce);
+ }
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ [super mouseDown:event];
+ if (([event modifierFlags] & NSEventModifierFlagControl)) {
+ [self processMouseEvent:event index:MouseButton::RIGHT];
+ } else {
+ [self processMouseEvent:event index:MouseButton::LEFT];
+ }
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+ [super rightMouseDown:event];
+
+ [self processMouseEvent:event index:MouseButton::RIGHT];
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+ [super otherMouseDown:event];
+
+ if ((int)[event buttonNumber] == 2) {
+ [self processMouseEvent:event index:MouseButton::MIDDLE];
+ } else if ((int)[event buttonNumber] == 3) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1];
+ } else if ((int)[event buttonNumber] == 4) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2];
+ }
+}
+
+@end
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 80863441ce..e0bad55b20 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -62,6 +62,8 @@
#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#endif
+#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1)
+
#if defined(__GNUC__)
// Workaround GCC warning from -Wcast-function-type.
#define GetProcAddress (void *)GetProcAddress
@@ -107,6 +109,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE:
+ case FEATURE_STATUS_INDICATOR:
return true;
default:
return false;
@@ -2842,6 +2845,172 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) {
}
}
+DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
+ HICON hicon = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon;
+ if (img != icon) {
+ img = img->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+ }
+
+ int w = img->get_width();
+ int h = img->get_height();
+
+ // Create temporary bitmap buffer.
+ int icon_len = 40 + h * w * 4;
+ Vector<BYTE> v;
+ v.resize(icon_len);
+ BYTE *icon_bmp = v.ptrw();
+
+ encode_uint32(40, &icon_bmp[0]);
+ encode_uint32(w, &icon_bmp[4]);
+ encode_uint32(h * 2, &icon_bmp[8]);
+ encode_uint16(1, &icon_bmp[12]);
+ encode_uint16(32, &icon_bmp[14]);
+ encode_uint32(BI_RGB, &icon_bmp[16]);
+ encode_uint32(w * h * 4, &icon_bmp[20]);
+ encode_uint32(0, &icon_bmp[24]);
+ encode_uint32(0, &icon_bmp[28]);
+ encode_uint32(0, &icon_bmp[32]);
+ encode_uint32(0, &icon_bmp[36]);
+
+ uint8_t *wr = &icon_bmp[40];
+ const uint8_t *r = img->get_data().ptr();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
+ uint8_t *wpx = &wr[(i * w + j) * 4];
+ wpx[0] = rpx[2];
+ wpx[1] = rpx[1];
+ wpx[2] = rpx[0];
+ wpx[3] = rpx[3];
+ }
+ }
+
+ hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
+ }
+
+ IndicatorData idat;
+ idat.callback = p_callback;
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = indicator_id_counter;
+ ndat.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
+ ndat.uCallbackMessage = WM_INDICATOR_CALLBACK_MESSAGE;
+ ndat.hIcon = hicon;
+ memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR));
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_ADD, &ndat);
+ Shell_NotifyIconW(NIM_SETVERSION, &ndat);
+
+ IndicatorID iid = indicator_id_counter++;
+ indicators[iid] = idat;
+
+ return iid;
+}
+
+void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ HICON hicon = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon;
+ if (img != icon) {
+ img = img->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+ }
+
+ int w = img->get_width();
+ int h = img->get_height();
+
+ // Create temporary bitmap buffer.
+ int icon_len = 40 + h * w * 4;
+ Vector<BYTE> v;
+ v.resize(icon_len);
+ BYTE *icon_bmp = v.ptrw();
+
+ encode_uint32(40, &icon_bmp[0]);
+ encode_uint32(w, &icon_bmp[4]);
+ encode_uint32(h * 2, &icon_bmp[8]);
+ encode_uint16(1, &icon_bmp[12]);
+ encode_uint16(32, &icon_bmp[14]);
+ encode_uint32(BI_RGB, &icon_bmp[16]);
+ encode_uint32(w * h * 4, &icon_bmp[20]);
+ encode_uint32(0, &icon_bmp[24]);
+ encode_uint32(0, &icon_bmp[28]);
+ encode_uint32(0, &icon_bmp[32]);
+ encode_uint32(0, &icon_bmp[36]);
+
+ uint8_t *wr = &icon_bmp[40];
+ const uint8_t *r = img->get_data().ptr();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
+ uint8_t *wpx = &wr[(i * w + j) * 4];
+ wpx[0] = rpx[2];
+ wpx[1] = rpx[1];
+ wpx[2] = rpx[0];
+ wpx[3] = rpx[3];
+ }
+ }
+
+ hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
+ }
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = p_id;
+ ndat.uFlags = NIF_ICON;
+ ndat.hIcon = hicon;
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_MODIFY, &ndat);
+}
+
+void DisplayServerWindows::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = p_id;
+ ndat.uFlags = NIF_TIP;
+ memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR));
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_MODIFY, &ndat);
+}
+
+void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ indicators[p_id].callback = p_callback;
+}
+
+void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = p_id;
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_DELETE, &ndat);
+ indicators.erase(p_id);
+}
+
void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
@@ -3351,6 +3520,30 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
} break;
+ case WM_INDICATOR_CALLBACK_MESSAGE: {
+ if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN || lParam == WM_MBUTTONDOWN || lParam == WM_XBUTTONDOWN) {
+ IndicatorID iid = (IndicatorID)wParam;
+ MouseButton mb = MouseButton::LEFT;
+ if (lParam == WM_RBUTTONDOWN) {
+ mb = MouseButton::RIGHT;
+ } else if (lParam == WM_MBUTTONDOWN) {
+ mb = MouseButton::MIDDLE;
+ } else if (lParam == WM_XBUTTONDOWN) {
+ mb = MouseButton::MB_XBUTTON1;
+ }
+ if (indicators.has(iid)) {
+ if (indicators[iid].callback.is_valid()) {
+ Variant v_button = mb;
+ Variant v_pos = mouse_get_position();
+ Variant *v_args[2] = { &v_button, &v_pos };
+ Variant ret;
+ Callable::CallError ce;
+ indicators[iid].callback.callp((const Variant **)&v_args, 2, ret, ce);
+ }
+ }
+ return 0;
+ }
+ } break;
case WM_CLOSE: // Did we receive a close message?
{
if (windows[window_id].focus_timer_id != 0U) {
@@ -5166,6 +5359,18 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
+ // Destroy all status indicators.
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = E->key;
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_DELETE, &ndat);
+ }
+
if (mouse_monitor) {
UnhookWindowsHookEx(mouse_monitor);
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 91e7424de9..e66c533da5 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -447,6 +447,13 @@ class DisplayServerWindows : public DisplayServer {
WNDPROC user_proc = nullptr;
+ struct IndicatorData {
+ Callable callback;
+ };
+
+ IndicatorID indicator_id_counter = 0;
+ HashMap<IndicatorID, IndicatorData> indicators;
+
void _send_window_event(const WindowData &wd, WindowEvent p_event);
void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
@@ -655,6 +662,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
+ virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
+ virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
+ virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
+ virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
+ virtual void delete_status_indicator(IndicatorID p_id) override;
+
virtual void set_context(Context p_context) override;
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);