Skip to content

Commit c38b73e

Browse files
committed
Report whether a window can be seen (#74)
Window::GetOcclusionState() returns WindowOcclusionState Unknown, Visible or Occluded, IsOcclusionStateSupported() says whether anything but Unknown is reported, and WindowOcclusionChangedEvent carries each change after a window's first reading, on the Window and on WindowManager. - macOS: NSWindow.occlusionState and its change notification. - Windows: hidden, minimized and cloaked windows are occluded; otherwise the opaque windows above it in the Z order, clipped to the screens, must cover it entirely (click-through and translucent layered windows do not). System-wide WinEvents (show/hide/reorder, location, cloak, minimize, foreground) arm a 100 ms timer that recomputes the process's windows, so listeners never run inside another window's message handling. - Linux: hidden and minimized windows are occluded; a shown window is Unknown, since X11 visibility is meaningless under a compositing manager (GDK reports one even on Xvfb + Openbox) and Wayland does not tell. - Android, iOS, OpenHarmony: Unknown. tests/window_occlusion_test shows real windows, so it is run by hand. window_property_changed_test gets a longer CTest timeout: GTK start-up and the keep-above probe take up to 20 s on WSL.
1 parent 7330aa6 commit c38b73e

15 files changed

Lines changed: 562 additions & 10 deletions

‎src/capi/window_c.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,28 @@ bool native_window_is_visible(native_window_t window) {
193193
}
194194
}
195195

196+
native_window_occlusion_state_t native_window_get_occlusion_state(native_window_t window) {
197+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
198+
if (!self) {
199+
return (native_window_occlusion_state_t)NATIVE_WINDOW_OCCLUSION_STATE_UNKNOWN;
200+
}
201+
try {
202+
return to_c_window_occlusion_state(self->GetOcclusionState());
203+
} catch (...) {
204+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_get_occlusion_state");
205+
return (native_window_occlusion_state_t)NATIVE_WINDOW_OCCLUSION_STATE_UNKNOWN;
206+
}
207+
}
208+
209+
bool native_window_is_occlusion_state_supported(void) {
210+
try {
211+
return nativeapi::Window::IsOcclusionStateSupported();
212+
} catch (...) {
213+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_is_occlusion_state_supported");
214+
return false;
215+
}
216+
}
217+
196218
void native_window_maximize(native_window_t window) {
197219
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
198220
if (!self) {
@@ -1630,6 +1652,11 @@ bool to_c_window_event(const nativeapi::WindowEvent& event, native_window_event_
16301652
out->data.property_changed.property = to_c_window_property(typed->GetProperty());
16311653
return true;
16321654
}
1655+
if (const auto* typed = dynamic_cast<const nativeapi::WindowOcclusionChangedEvent*>(&event)) {
1656+
out->type = NATIVE_WINDOW_EVENT_TYPE_OCCLUSION_CHANGED;
1657+
out->data.occlusion_changed.occlusion_state = to_c_window_occlusion_state(typed->GetOcclusionState());
1658+
return true;
1659+
}
16331660
return false;
16341661
}
16351662

‎src/capi/window_c.h‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ typedef enum {
5757
NATIVE_WINDOW_PROPERTY_TITLE_BAR_STYLE = 10,
5858
} native_window_property_t;
5959

60+
typedef enum {
61+
NATIVE_WINDOW_OCCLUSION_STATE_UNKNOWN = 0,
62+
NATIVE_WINDOW_OCCLUSION_STATE_VISIBLE = 1,
63+
NATIVE_WINDOW_OCCLUSION_STATE_OCCLUDED = 2,
64+
} native_window_occlusion_state_t;
65+
6066
typedef enum {
6167
NATIVE_VISUAL_EFFECT_NONE = 0,
6268
NATIVE_VISUAL_EFFECT_BLUR = 1,
@@ -111,6 +117,7 @@ typedef enum {
111117
NATIVE_WINDOW_EVENT_TYPE_EXITED_FULL_SCREEN = 10,
112118
NATIVE_WINDOW_EVENT_TYPE_CLOSE_REQUESTED = 11,
113119
NATIVE_WINDOW_EVENT_TYPE_PROPERTY_CHANGED = 12,
120+
NATIVE_WINDOW_EVENT_TYPE_OCCLUSION_CHANGED = 13,
114121
} native_window_event_type_t;
115122

116123
/// One WindowEvent, tagged by its concrete type.
@@ -133,6 +140,9 @@ typedef struct {
133140
struct {
134141
native_window_property_t property;
135142
} property_changed;
143+
struct {
144+
native_window_occlusion_state_t occlusion_state;
145+
} occlusion_changed;
136146
} data;
137147
} native_window_event_t;
138148

@@ -181,6 +191,12 @@ void native_window_hide(native_window_t window);
181191
FFI_PLUGIN_EXPORT
182192
bool native_window_is_visible(native_window_t window);
183193

194+
FFI_PLUGIN_EXPORT
195+
native_window_occlusion_state_t native_window_get_occlusion_state(native_window_t window);
196+
197+
FFI_PLUGIN_EXPORT
198+
bool native_window_is_occlusion_state_supported(void);
199+
184200
FFI_PLUGIN_EXPORT
185201
void native_window_maximize(native_window_t window);
186202

@@ -528,6 +544,8 @@ inline native_window_corner_preference_t to_c_window_corner_preference(nativeapi
528544
inline nativeapi::WindowCornerPreference to_cpp_window_corner_preference(native_window_corner_preference_t value);
529545
inline native_window_property_t to_c_window_property(nativeapi::WindowProperty value);
530546
inline nativeapi::WindowProperty to_cpp_window_property(native_window_property_t value);
547+
inline native_window_occlusion_state_t to_c_window_occlusion_state(nativeapi::WindowOcclusionState value);
548+
inline nativeapi::WindowOcclusionState to_cpp_window_occlusion_state(native_window_occlusion_state_t value);
531549
inline native_visual_effect_t to_c_visual_effect(nativeapi::VisualEffect value);
532550
inline nativeapi::VisualEffect to_cpp_visual_effect(native_visual_effect_t value);
533551
inline native_resize_edge_t to_c_resize_edge(nativeapi::ResizeEdge value);
@@ -643,6 +661,32 @@ inline nativeapi::WindowProperty to_cpp_window_property(native_window_property_t
643661
}
644662
}
645663

664+
inline native_window_occlusion_state_t to_c_window_occlusion_state(nativeapi::WindowOcclusionState value) {
665+
switch (value) {
666+
case nativeapi::WindowOcclusionState::Unknown:
667+
return NATIVE_WINDOW_OCCLUSION_STATE_UNKNOWN;
668+
case nativeapi::WindowOcclusionState::Visible:
669+
return NATIVE_WINDOW_OCCLUSION_STATE_VISIBLE;
670+
case nativeapi::WindowOcclusionState::Occluded:
671+
return NATIVE_WINDOW_OCCLUSION_STATE_OCCLUDED;
672+
default:
673+
return NATIVE_WINDOW_OCCLUSION_STATE_UNKNOWN;
674+
}
675+
}
676+
677+
inline nativeapi::WindowOcclusionState to_cpp_window_occlusion_state(native_window_occlusion_state_t value) {
678+
switch (value) {
679+
case NATIVE_WINDOW_OCCLUSION_STATE_UNKNOWN:
680+
return nativeapi::WindowOcclusionState::Unknown;
681+
case NATIVE_WINDOW_OCCLUSION_STATE_VISIBLE:
682+
return nativeapi::WindowOcclusionState::Visible;
683+
case NATIVE_WINDOW_OCCLUSION_STATE_OCCLUDED:
684+
return nativeapi::WindowOcclusionState::Occluded;
685+
default:
686+
return static_cast<nativeapi::WindowOcclusionState>(value);
687+
}
688+
}
689+
646690
inline native_visual_effect_t to_c_visual_effect(nativeapi::VisualEffect value) {
647691
switch (value) {
648692
case nativeapi::VisualEffect::None:

‎src/platform/android/window_android.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ bool Window::IsVisible() const {
9797
return pimpl_->native_window_ != nullptr;
9898
}
9999

100+
WindowOcclusionState Window::GetOcclusionState() const {
101+
return WindowOcclusionState::Unknown;
102+
}
103+
104+
bool Window::IsOcclusionStateSupported() {
105+
return false;
106+
}
107+
100108
void Window::Maximize() {
101109
// Maximize is not applicable to Android Activities
102110
ALOGW("Maximize not supported on Android");

‎src/platform/ios/window_ios.mm‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@
8888
return pimpl_->ui_window_ && !pimpl_->ui_window_.hidden;
8989
}
9090

91+
WindowOcclusionState Window::GetOcclusionState() const {
92+
return WindowOcclusionState::Unknown;
93+
}
94+
95+
bool Window::IsOcclusionStateSupported() {
96+
return false;
97+
}
98+
9199
void Window::Maximize() {
92100
// Maximize is not applicable to iOS (fullscreen is used instead)
93101
}

‎src/platform/linux/window_linux.cpp‎

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,12 @@ static void RefreshPropertiesOf(GtkWidget* widget) {
782782
detail::WindowPropertyDispatch::Refresh(window);
783783
}
784784

785+
static void RefreshOcclusionOf(GtkWidget* widget) {
786+
if (!widget || gtk_widget_in_destruction(widget)) return;
787+
Window window(widget);
788+
detail::WindowPropertyDispatch::RefreshOcclusion(window);
789+
}
790+
785791
static bool ObserveWindowProperties(GtkWidget* widget) {
786792
if (!widget || !GTK_IS_WINDOW(widget) || g_object_get_data(G_OBJECT(widget), kPropertyObserverKey))
787793
return false;
@@ -795,9 +801,16 @@ static bool ObserveWindowProperties(GtkWidget* widget) {
795801
G_CALLBACK(+[](GtkWidget* self, GdkEventWindowState* event, gpointer) {
796802
if (event->changed_mask & (GDK_WINDOW_STATE_ABOVE | GDK_WINDOW_STATE_BELOW))
797803
RefreshPropertiesOf(self);
804+
if (event->changed_mask &
805+
(GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN))
806+
RefreshOcclusionOf(self);
798807
return FALSE;
799808
}),
800809
nullptr);
810+
// Occlusion: shown and hidden (minimizing is the window-state-event above).
811+
auto on_mapping = +[](GtkWidget* self, gpointer) { RefreshOcclusionOf(self); };
812+
g_signal_connect_after(widget, "map", G_CALLBACK(on_mapping), nullptr);
813+
g_signal_connect_after(widget, "unmap", G_CALLBACK(on_mapping), nullptr);
801814
return true;
802815
}
803816

@@ -843,8 +856,11 @@ Window::Window() {
843856

844857
// Only create the instance, don't show the window
845858
pimpl_ = std::make_unique<Impl>(widget, gdk_window);
846-
if (ObserveWindowProperties(widget))
847-
detail::WindowPropertyDispatch::Refresh(*this); // The values changes are compared with.
859+
if (ObserveWindowProperties(widget)) {
860+
// The values later changes are compared with.
861+
detail::WindowPropertyDispatch::Refresh(*this);
862+
detail::WindowPropertyDispatch::RefreshOcclusion(*this);
863+
}
848864
}
849865

850866
Window::Window(void* native_window) {
@@ -898,8 +914,11 @@ Window::Window(void* native_window) {
898914
}
899915

900916
pimpl_ = std::make_unique<Impl>(widget, gdk_window);
901-
if (ObserveWindowProperties(widget))
902-
detail::WindowPropertyDispatch::Refresh(*this); // The values changes are compared with.
917+
if (ObserveWindowProperties(widget)) {
918+
// The values later changes are compared with.
919+
detail::WindowPropertyDispatch::Refresh(*this);
920+
detail::WindowPropertyDispatch::RefreshOcclusion(*this);
921+
}
903922
}
904923

905924
Window::~Window() {
@@ -1194,6 +1213,25 @@ bool Window::IsVisible() const {
11941213
return false;
11951214
}
11961215

1216+
// Whether other windows cover a shown window is not known: X11's
1217+
// VisibilityNotify is meaningless under the compositing managers every
1218+
// desktop runs (GDK reports one even on Xvfb with Openbox), and Wayland tells
1219+
// clients nothing about it.
1220+
WindowOcclusionState Window::GetOcclusionState() const {
1221+
GtkWidget* widget = pimpl_->widget_;
1222+
GdkWindow* surface = pimpl_->gdk_window_;
1223+
if (!widget && !surface) return WindowOcclusionState::Unknown;
1224+
if (!IsVisible() || (widget && !gtk_widget_get_mapped(widget)) ||
1225+
(surface && (gdk_window_get_state(surface) &
1226+
(GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN))))
1227+
return WindowOcclusionState::Occluded;
1228+
return WindowOcclusionState::Unknown;
1229+
}
1230+
1231+
bool Window::IsOcclusionStateSupported() {
1232+
return true;
1233+
}
1234+
11971235
void Window::Maximize() {
11981236
if (pimpl_->gdk_window_) {
11991237
AllowProgrammaticWindowControl(pimpl_->widget_, pimpl_->gdk_window_, true);

‎src/platform/macos/window_macos.mm‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,9 @@ static void NativeApiObserveWindowProperties(NSWindow* window) {
455455
pimpl_ = std::make_unique<Impl>(id, ns_window);
456456
if (ns_window && !objc_getAssociatedObject(ns_window, kWindowPropertyObserverKey)) {
457457
NativeApiObserveWindowProperties(ns_window);
458-
detail::WindowPropertyDispatch::Refresh(*this); // The values changes are compared with.
458+
// The values later changes are compared with.
459+
detail::WindowPropertyDispatch::Refresh(*this);
460+
detail::WindowPropertyDispatch::RefreshOcclusion(*this);
459461
}
460462
}
461463

@@ -503,6 +505,17 @@ static void NativeApiObserveWindowProperties(NSWindow* window) {
503505
return [pimpl_->ns_window_ isVisible];
504506
}
505507

508+
WindowOcclusionState Window::GetOcclusionState() const {
509+
NSWindow* window = pimpl_->ns_window_;
510+
if (!window) return WindowOcclusionState::Unknown;
511+
return ([window occlusionState] & NSWindowOcclusionStateVisible) ? WindowOcclusionState::Visible
512+
: WindowOcclusionState::Occluded;
513+
}
514+
515+
bool Window::IsOcclusionStateSupported() {
516+
return true;
517+
}
518+
506519
void Window::Maximize() {
507520
if (!IsMaximized()) {
508521
[pimpl_->ns_window_ zoom:nil];

‎src/platform/macos/window_manager_macos.mm‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "../../window.h"
1111
#include "../../window_manager.h"
12+
#include "../../window_property_dispatch.h"
1213
#include "../../window_registry.h"
1314
#include "coordinate_utils_macos.h"
1415

@@ -443,6 +444,9 @@ static WindowId ResolveWindowId(NSWindow* ns_window) {
443444
manager_->DispatchWindowEvent(restored_event);
444445
}
445446
}
447+
} else if (event_type == "occlusion") {
448+
Window wrapper((__bridge void*)window);
449+
detail::WindowPropertyDispatch::RefreshOcclusion(wrapper);
446450
} else if (event_type == "moved") {
447451
// Same top-left origin as Window::GetPosition()
448452
CGPoint top_left = NSRectExt::topLeft([window frame]);

‎src/platform/ohos/window_ohos.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ bool Window::IsVisible() const {
9292
return pimpl_->native_window_ != nullptr;
9393
}
9494

95+
WindowOcclusionState Window::GetOcclusionState() const {
96+
return WindowOcclusionState::Unknown;
97+
}
98+
99+
bool Window::IsOcclusionStateSupported() {
100+
return false;
101+
}
102+
95103
void Window::Maximize() {
96104
// Maximize is not applicable to OpenHarmony Abilities
97105
// Maximize not supported on OpenHarmony

0 commit comments

Comments
 (0)