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>
1617#include " window_message_dispatcher.h"
1718
1819#pragma comment(lib, "dwmapi.lib")
20+ #pragma comment(lib, "ole32.lib")
1921
2022namespace 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.
2628static 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
10881128void 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
10931146bool Window::HasShadow () const {
1094- return true ; // Windows typically have shadows by default
1147+ return pimpl_-> hwnd_ && ! GetPropW (pimpl_-> hwnd_ , kNoShadowProperty );
10951148}
10961149
10971150void 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+
12271295void Window::SetIgnoreMouseEvents (bool is_ignore_mouse_events) {
12281296 if (pimpl_->hwnd_ ) {
12291297 LONG exStyle = GetWindowLong (pimpl_->hwnd_ , GWL_EXSTYLE );
0 commit comments