diff options
| author | Rémi Verschelde <rverschelde@gmail.com> | 2024-02-13 17:22:55 +0100 |
|---|---|---|
| committer | Rémi Verschelde <rverschelde@gmail.com> | 2024-02-13 17:22:55 +0100 |
| commit | 7069b3d35191c29bb0c7b6d733c07c8644d05107 (patch) | |
| tree | 657d8f780586f7a48f6a894119b6955d1b91da09 /platform | |
| parent | b9a07ad341b2cbc3e35efff74b8d578383c2b8ea (diff) | |
| parent | 8da36031e4a52b78d972fc132528a705a3af0750 (diff) | |
| download | redot-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/SCsub | 1 | ||||
| -rw-r--r-- | platform/macos/display_server_macos.h | 14 | ||||
| -rw-r--r-- | platform/macos/display_server_macos.mm | 125 | ||||
| -rw-r--r-- | platform/macos/godot_status_item.h | 51 | ||||
| -rw-r--r-- | platform/macos/godot_status_item.mm | 101 | ||||
| -rw-r--r-- | platform/windows/display_server_windows.cpp | 205 | ||||
| -rw-r--r-- | platform/windows/display_server_windows.h | 13 |
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); |
