Skip to content

Commit 480a076

Browse files
committed
Make Window::SetHasShadow work on Windows and add SetVisibleInTaskbar
SetHasShadow was a no-op on Windows, so a frameless window kept the drop shadow the compositor draws around its frame. Turn off non-client rendering instead, which takes the shadow with it, and remember the flag on the HWND so HasShadow() can answer. SetVisibleInTaskbar is new: it adds and removes the window's taskbar button through ITaskbarList, the only way to change it on a window that is already on screen. The shell hands a window a fresh button every time it is shown, so the flag is applied again after each show. macOS has no per-window taskbar; it drops the window from the application's Window menu instead. Linux sets the window manager's skip-taskbar hint. The three mobile platforms record nothing.
1 parent 4a9b52e commit 480a076

9 files changed

Lines changed: 193 additions & 3 deletions

File tree

‎src/capi/window_c.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,33 @@ bool native_window_is_visible_on_all_workspaces(native_window_t window) {
10061006
}
10071007
}
10081008

1009+
void native_window_set_visible_in_taskbar(native_window_t window, bool is_visible_in_taskbar) {
1010+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
1011+
if (!self) {
1012+
return;
1013+
}
1014+
try {
1015+
self->SetVisibleInTaskbar(is_visible_in_taskbar);
1016+
return;
1017+
} catch (...) {
1018+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_set_visible_in_taskbar");
1019+
return;
1020+
}
1021+
}
1022+
1023+
bool native_window_is_visible_in_taskbar(native_window_t window) {
1024+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
1025+
if (!self) {
1026+
return false;
1027+
}
1028+
try {
1029+
return self->IsVisibleInTaskbar();
1030+
} catch (...) {
1031+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_is_visible_in_taskbar");
1032+
return false;
1033+
}
1034+
}
1035+
10091036
void native_window_set_ignore_mouse_events(native_window_t window, bool is_ignore_mouse_events) {
10101037
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
10111038
if (!self) {

‎src/capi/window_c.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,12 @@ void native_window_set_visible_on_all_workspaces(native_window_t window, bool is
308308
FFI_PLUGIN_EXPORT
309309
bool native_window_is_visible_on_all_workspaces(native_window_t window);
310310

311+
FFI_PLUGIN_EXPORT
312+
void native_window_set_visible_in_taskbar(native_window_t window, bool is_visible_in_taskbar);
313+
314+
FFI_PLUGIN_EXPORT
315+
bool native_window_is_visible_in_taskbar(native_window_t window);
316+
311317
FFI_PLUGIN_EXPORT
312318
void native_window_set_ignore_mouse_events(native_window_t window, bool is_ignore_mouse_events);
313319

‎src/platform/android/window_android.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,14 @@ bool Window::IsVisibleOnAllWorkspaces() const {
375375
return false;
376376
}
377377

378+
void Window::SetVisibleInTaskbar(bool is_visible_in_taskbar) {
379+
ALOGW("SetVisibleInTaskbar not supported on Android");
380+
}
381+
382+
bool Window::IsVisibleInTaskbar() const {
383+
return false;
384+
}
385+
378386
void Window::SetIgnoreMouseEvents(bool is_ignore_mouse_events) {
379387
ALOGW("SetIgnoreMouseEvents not supported on Android");
380388
}

‎src/platform/ios/window_ios.mm‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,14 @@
418418
return false;
419419
}
420420

421+
void Window::SetVisibleInTaskbar(bool is_visible_in_taskbar) {
422+
// Not applicable to iOS
423+
}
424+
425+
bool Window::IsVisibleInTaskbar() const {
426+
return false;
427+
}
428+
421429
void Window::SetIgnoreMouseEvents(bool is_ignore_mouse_events) {
422430
// Not applicable to iOS
423431
}

‎src/platform/linux/window_linux.cpp‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,18 @@ bool Window::IsVisibleOnAllWorkspaces() const {
695695
return state & GDK_WINDOW_STATE_STICKY;
696696
}
697697

698+
void Window::SetVisibleInTaskbar(bool is_visible_in_taskbar) {
699+
if (pimpl_->widget_ && GTK_IS_WINDOW(pimpl_->widget_)) {
700+
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(pimpl_->widget_), !is_visible_in_taskbar);
701+
}
702+
}
703+
704+
bool Window::IsVisibleInTaskbar() const {
705+
if (!pimpl_->widget_ || !GTK_IS_WINDOW(pimpl_->widget_))
706+
return true;
707+
return !gtk_window_get_skip_taskbar_hint(GTK_WINDOW(pimpl_->widget_));
708+
}
709+
698710
void Window::SetIgnoreMouseEvents(bool is_ignore_mouse_events) {
699711
// This would involve setting input shapes or event masks
700712
// Provide stub implementation

‎src/platform/macos/window_macos.mm‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,16 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
686686
return [pimpl_->ns_window_ collectionBehavior] & NSWindowCollectionBehaviorCanJoinAllSpaces;
687687
}
688688

689+
void Window::SetVisibleInTaskbar(bool is_visible_in_taskbar) {
690+
// The Dock lists applications, not windows; the closest per-window list is the
691+
// application's Window menu.
692+
[pimpl_->ns_window_ setExcludedFromWindowsMenu:!is_visible_in_taskbar];
693+
}
694+
695+
bool Window::IsVisibleInTaskbar() const {
696+
return ![pimpl_->ns_window_ isExcludedFromWindowsMenu];
697+
}
698+
689699
void Window::SetIgnoreMouseEvents(bool is_ignore_mouse_events) {
690700
[pimpl_->ns_window_ setIgnoresMouseEvents:is_ignore_mouse_events];
691701
}

‎src/platform/ohos/window_ohos.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,14 @@ bool Window::IsVisibleOnAllWorkspaces() const {
359359
return false;
360360
}
361361

362+
void Window::SetVisibleInTaskbar(bool is_visible_in_taskbar) {
363+
// SetVisibleInTaskbar not supported on OpenHarmony
364+
}
365+
366+
bool Window::IsVisibleInTaskbar() const {
367+
return false;
368+
}
369+
362370
void Window::SetIgnoreMouseEvents(bool is_ignore_mouse_events) {
363371
// SetIgnoreMouseEvents not supported on OpenHarmony
364372
}

‎src/platform/windows/window_windows.cpp‎

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <dwmapi.h>
55
#include <windows.h>
66
#include <commctrl.h>
7+
#include <shobjidl.h>
78
#include <cmath>
89
#include <iostream>
910
#include <optional>
@@ -16,6 +17,7 @@
1617
#include "window_message_dispatcher.h"
1718

1819
#pragma comment(lib, "dwmapi.lib")
20+
#pragma comment(lib, "ole32.lib")
1921

2022
namespace nativeapi {
2123

@@ -24,6 +26,33 @@ static const wchar_t* kWindowIdProperty = L"NativeAPIWindowId";
2426
// Set while the title bar is hidden. Kept on the HWND, like the style itself, so every
2527
// wrapper of the window agrees and the frame handling below outlives any one wrapper.
2628
static const wchar_t* kTitleBarHiddenProperty = L"NativeAPITitleBarHidden";
29+
// The other two window flags the system cannot be asked about afterwards, kept on the
30+
// HWND for the same reason.
31+
static const wchar_t* kNoShadowProperty = L"NativeAPINoShadow";
32+
static const wchar_t* kHiddenFromTaskbarProperty = L"NativeAPIHiddenFromTaskbar";
33+
34+
// Adds or removes the window's taskbar button. The shell owns that button, so this is
35+
// the only way to change it on a window that is already on screen; the window style
36+
// alternative (WS_EX_TOOLWINDOW) only takes effect while the window is hidden and also
37+
// drops it from Alt+Tab.
38+
static void ApplyTaskbarVisibility(HWND hwnd, bool is_visible) {
39+
const HRESULT com = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
40+
ITaskbarList* taskbar = nullptr;
41+
if (SUCCEEDED(CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER,
42+
IID_PPV_ARGS(&taskbar)))) {
43+
if (SUCCEEDED(taskbar->HrInit())) {
44+
if (is_visible)
45+
taskbar->AddTab(hwnd);
46+
else
47+
taskbar->DeleteTab(hwnd);
48+
}
49+
taskbar->Release();
50+
}
51+
// RPC_E_CHANGED_MODE says the thread already belongs to another apartment, which is
52+
// then not ours to leave.
53+
if (SUCCEEDED(com))
54+
CoUninitialize();
55+
}
2756

2857
#ifndef NATIVEAPI_ENABLE_WINUI3
2958
// A window with WS_THICKFRAME but no WS_CAPTION keeps its sizing frame on all sides.
@@ -117,10 +146,21 @@ static LRESULT CALLBACK WindowLifetimeProc(HWND hwnd, UINT message, WPARAM wp, L
117146
RemoveWindowSubclass(hwnd, WindowLifetimeProc, subclass_id);
118147
RemovePropW(hwnd, kWindowIdProperty);
119148
RemovePropW(hwnd, kTitleBarHiddenProperty);
149+
RemovePropW(hwnd, kNoShadowProperty);
150+
RemovePropW(hwnd, kHiddenFromTaskbarProperty);
120151
const auto result = DefSubclassProc(hwnd, message, wp, lp);
121152
WindowRegistry::GetInstance().Remove(static_cast<WindowId>(reference));
122153
return result;
123154
}
155+
if (message == WM_WINDOWPOSCHANGED) {
156+
const auto* pos = reinterpret_cast<const WINDOWPOS*>(lp);
157+
const LRESULT result = DefSubclassProc(hwnd, message, wp, lp);
158+
// The shell gives a window a fresh taskbar button every time it is shown, so a
159+
// window that is meant to stay out of the taskbar has to leave it again.
160+
if (pos && (pos->flags & SWP_SHOWWINDOW) && GetPropW(hwnd, kHiddenFromTaskbarProperty))
161+
ApplyTaskbarVisibility(hwnd, false);
162+
return result;
163+
}
124164
#ifndef NATIVEAPI_ENABLE_WINUI3
125165
if (message == WM_NCCALCSIZE || message == WM_NCHITTEST || message == WM_PARENTNOTIFY) {
126166
if (auto handled = HandleHiddenTitleBarFrame(hwnd, message, wp, lp)) return *handled;
@@ -1086,12 +1126,25 @@ TitleBarStyle Window::GetTitleBarStyle() const {
10861126
}
10871127

10881128
void Window::SetHasShadow(bool has_shadow) {
1089-
// Windows shadow is typically handled automatically
1090-
// Custom shadow implementation would be complex
1129+
if (!pimpl_->hwnd_)
1130+
return;
1131+
1132+
// The shadow is part of the frame the desktop compositor draws around the window, so
1133+
// it goes away with the rest of that frame. On a window that still has a title bar
1134+
// the caption is drawn there too and the compositor keeps both.
1135+
DWMNCRENDERINGPOLICY policy = has_shadow ? DWMNCRP_USEWINDOWSTYLE : DWMNCRP_DISABLED;
1136+
if (FAILED(DwmSetWindowAttribute(pimpl_->hwnd_, DWMWA_NCRENDERING_POLICY, &policy,
1137+
sizeof(policy))))
1138+
return;
1139+
1140+
if (has_shadow)
1141+
RemovePropW(pimpl_->hwnd_, kNoShadowProperty);
1142+
else
1143+
SetPropW(pimpl_->hwnd_, kNoShadowProperty, reinterpret_cast<HANDLE>(1));
10911144
}
10921145

10931146
bool Window::HasShadow() const {
1094-
return true; // Windows typically have shadows by default
1147+
return pimpl_->hwnd_ && !GetPropW(pimpl_->hwnd_, kNoShadowProperty);
10951148
}
10961149

10971150
void Window::SetOpacity(float opacity) {
@@ -1224,6 +1277,21 @@ bool Window::IsVisibleOnAllWorkspaces() const {
12241277
return false; // Not supported on Windows by default
12251278
}
12261279

1280+
void Window::SetVisibleInTaskbar(bool is_visible_in_taskbar) {
1281+
if (!pimpl_->hwnd_)
1282+
return;
1283+
1284+
if (is_visible_in_taskbar)
1285+
RemovePropW(pimpl_->hwnd_, kHiddenFromTaskbarProperty);
1286+
else
1287+
SetPropW(pimpl_->hwnd_, kHiddenFromTaskbarProperty, reinterpret_cast<HANDLE>(1));
1288+
ApplyTaskbarVisibility(pimpl_->hwnd_, is_visible_in_taskbar);
1289+
}
1290+
1291+
bool Window::IsVisibleInTaskbar() const {
1292+
return pimpl_->hwnd_ && !GetPropW(pimpl_->hwnd_, kHiddenFromTaskbarProperty);
1293+
}
1294+
12271295
void Window::SetIgnoreMouseEvents(bool is_ignore_mouse_events) {
12281296
if (pimpl_->hwnd_) {
12291297
LONG exStyle = GetWindowLong(pimpl_->hwnd_, GWL_EXSTYLE);

‎src/window.h‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,13 +736,25 @@ class Window : public NativeObjectProvider, public std::enable_shared_from_this<
736736
*
737737
* Controls the drop shadow effect around the window. On some platforms,
738738
* this may affect window compositing and visual effects.
739+
*
740+
* @note Platform availability:
741+
* - macOS: ✅ Fully supported - Drops the shadow of any window
742+
* - Windows: ⚠️ Frameless windows only - The desktop compositor always draws the
743+
* shadow of a window that has a title bar; with TitleBarStyle::Hidden the shadow
744+
* follows this flag
745+
* - Linux: ⚠️ Recorded only - The window manager owns the shadow
746+
* - Android: ❌ Not applicable - Always ignored
747+
* - iOS: ❌ Not applicable - Always ignored
748+
* - OpenHarmony: ❌ Not applicable - Always ignored
739749
*/
740750
void SetHasShadow(bool has_shadow);
741751

742752
/**
743753
* @brief Checks if the window currently displays a shadow.
744754
*
745755
* @return true if shadow is enabled, false otherwise
756+
*
757+
* @see SetHasShadow() for platform availability.
746758
*/
747759
bool HasShadow() const;
748760

@@ -819,6 +831,37 @@ class Window : public NativeObjectProvider, public std::enable_shared_from_this<
819831
*/
820832
bool IsVisibleOnAllWorkspaces() const;
821833

834+
/**
835+
* @brief Sets whether the window is listed in the taskbar.
836+
*
837+
* @param is_visible_in_taskbar true to list the window, false to hide it from the
838+
* taskbar
839+
*
840+
* A window hidden from the taskbar keeps its own appearance and behavior; only the
841+
* shell's list of open windows drops it. Useful for overlays, tool palettes and
842+
* windows an app shows from its tray icon. The window stays reachable through
843+
* Alt+Tab on the platforms noted below.
844+
*
845+
* @note Platform availability:
846+
* - macOS: ⚠️ Window menu only - The Dock lists applications, not windows, so the
847+
* window is only dropped from the application's Window menu
848+
* - Windows: ✅ Fully supported - Adds or removes the window's taskbar button
849+
* - Linux: ✅ Fully supported - Sets the window manager's skip-taskbar hint
850+
* - Android: ❌ Not applicable - Always ignored
851+
* - iOS: ❌ Not applicable - Always ignored
852+
* - OpenHarmony: ❌ Not applicable - Always ignored
853+
*/
854+
void SetVisibleInTaskbar(bool is_visible_in_taskbar);
855+
856+
/**
857+
* @brief Checks if the window is listed in the taskbar.
858+
*
859+
* @return true if the window has a taskbar entry, false if it is hidden from it
860+
*
861+
* @see SetVisibleInTaskbar() for platform availability.
862+
*/
863+
bool IsVisibleInTaskbar() const;
864+
822865
/**
823866
* @brief Sets whether the window ignores mouse input events.
824867
*

0 commit comments

Comments
 (0)