GDK W32: Use layered windows

Toplevels are now true layered windows that are moved,
resized and repainted via UpdateLayeredWindow() API call.
This achieves transparency without any extra effort,
and prevents window size and window contents desychronization
(bug 761629).

This also changes the way CSD windows are detected. We now
use window decorations to detect CSDiness of a window,
and to decide whether a window should be layered (CSD windows should
be) or not.

Decorations are now stored in the window implementation,
not as a quark-based property of the window-as-gobject.

https://bugzilla.gnome.org/show_bug.cgi?id=748872
This commit is contained in:
Руслан Ижбулатов 2016-02-23 09:20:55 +00:00
parent e03946bd28
commit c05f254a6e
4 changed files with 314 additions and 42 deletions

View File

@ -1645,17 +1645,14 @@ handle_nchittest (HWND hwnd,
gint *ret_valp)
{
RECT rect;
LONG style;
if (window == NULL || window->input_shape == NULL)
return FALSE;
style = GetWindowLong (hwnd, GWL_STYLE);
/* Assume that these styles are incompatible with CSD,
* so there's no reason for us to override the defaults.
/* If the window has decorations, DefWindowProc() will take
* care of NCHITTEST.
*/
if (style & (WS_BORDER | WS_THICKFRAME))
if (!_gdk_win32_window_lacks_wm_decorations (window))
return FALSE;
if (!GetWindowRect (hwnd, &rect))

View File

@ -535,6 +535,8 @@ void gdk_win32_window_end_move_resize_drag (GdkWindow *window);
gboolean _gdk_win32_window_fill_min_max_info (GdkWindow *window,
MINMAXINFO *mmi);
gboolean _gdk_win32_window_lacks_wm_decorations (GdkWindow *window);
/* Initialization */
void _gdk_win32_windowing_init (void);
void _gdk_dnd_init (void);

View File

@ -181,22 +181,38 @@ gdk_window_impl_win32_finalize (GObject *object)
window_impl->hicon_small = NULL;
}
g_free (window_impl->decorations);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gdk_win32_window_begin_paint (GdkWindow *window)
{
GdkWindowImplWin32 *impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
return !impl->layered;
}
static void
gdk_win32_window_end_paint (GdkWindow *window)
{
GdkWindowImplWin32 *impl;
RECT window_rect;
gint x, y;
HDC hdc;
POINT window_position;
SIZE window_size;
POINT source_point;
BLENDFUNCTION blender;
cairo_t *cr;
if (window == NULL || GDK_WINDOW_DESTROYED (window))
return;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
if (!impl->drag_move_resize_context.native_move_resize_pending)
if (!impl->layered && !impl->drag_move_resize_context.native_move_resize_pending)
return;
impl->drag_move_resize_context.native_move_resize_pending = FALSE;
@ -216,16 +232,57 @@ gdk_win32_window_end_paint (GdkWindow *window)
window_rect.top -= _gdk_offset_y;
window_rect.bottom -= _gdk_offset_y;
GDK_NOTE (EVENTS, g_print ("Setting window position ... "));
if (!impl->layered)
{
GDK_NOTE (EVENTS, g_print ("Setting window position ... "));
API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
SWP_NOZORDER_SPECIFIED,
window_rect.left, window_rect.top,
window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top,
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW));
API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
SWP_NOZORDER_SPECIFIED,
window_rect.left, window_rect.top,
window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top,
SWP_NOACTIVATE | SWP_NOZORDER));
GDK_NOTE (EVENTS, g_print (" ... set window position\n"));
GDK_NOTE (EVENTS, g_print (" ... set window position\n"));
return;
}
window_position.x = window_rect.left;
window_position.y = window_rect.top;
window_size.cx = window_rect.right - window_rect.left;
window_size.cy = window_rect.bottom - window_rect.top;
cairo_surface_flush (impl->cairo_surface);
/* we always draw in the top-left corner of the surface */
source_point.x = source_point.y = 0;
blender.BlendOp = AC_SRC_OVER;
blender.BlendFlags = 0;
blender.AlphaFormat = AC_SRC_ALPHA;
blender.SourceConstantAlpha = impl->layered_opacity * 255;
/* Update cache surface contents */
cr = cairo_create (impl->cache_surface);
cairo_set_source_surface (cr, window->current_paint.surface, 0, 0);
gdk_cairo_region (cr, window->current_paint.region);
cairo_clip (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_paint (cr);
cairo_destroy (cr);
cairo_surface_flush (impl->cache_surface);
hdc = cairo_win32_surface_get_dc (impl->cache_surface);
/* Move, resize and redraw layered window in one call */
API_CALL (UpdateLayeredWindow, (GDK_WINDOW_HWND (window), NULL,
&window_position, &window_size,
hdc, &source_point,
0, &blender, ULW_ALPHA));
}
void
@ -242,6 +299,7 @@ _gdk_win32_adjust_client_rect (GdkWindow *window,
gboolean
_gdk_win32_window_enable_transparency (GdkWindow *window)
{
GdkWindowImplWin32 *impl;
GdkScreen *screen;
DWM_BLURBEHIND blur_behind;
HRGN empty_region;
@ -251,6 +309,12 @@ _gdk_win32_window_enable_transparency (GdkWindow *window)
if (window == NULL || GDK_WINDOW_HWND (window) == NULL)
return FALSE;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
/* layered windows don't need blurbehind for transparency */
if (impl->layered)
return TRUE;
screen = gdk_window_get_screen (window);
if (!gdk_screen_is_composited (screen))
@ -537,6 +601,8 @@ _gdk_win32_display_create_window_impl (GdkDisplay *display,
(gdk_screen_get_rgba_visual (screen) == attributes->visual));
impl->override_redirect = override_redirect;
impl->layered = FALSE;
impl->layered_opacity = 1.0;
/* wclass is not any longer set always, but if is ... */
if ((attributes_mask & GDK_WA_WMCLASS) == GDK_WA_WMCLASS)
@ -2435,6 +2501,72 @@ update_single_bit (LONG *style,
*style &= ~style_bit;
}
/*
* Returns TRUE if window has no decorations.
* Usually it means CSD windows, because GTK
* calls gdk_window_set_decorations (window, 0);
* This is used to decide whether a toplevel should
* be made layered, thus it
* only returns TRUE for toplevels (until GTK minimal
* system requirements are lifted to Windows 8 or newer,
* because only toplevels can be layered).
*/
gboolean
_gdk_win32_window_lacks_wm_decorations (GdkWindow *window)
{
GdkWindowImplWin32 *impl;
LONG style;
gboolean has_any_decorations;
if (GDK_WINDOW_DESTROYED (window))
return FALSE;
/* only toplevels can be layered */
if (!WINDOW_IS_TOPLEVEL (window))
return FALSE;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
/* This is because GTK calls gdk_window_set_decorations (window, 0),
* even though GdkWMDecoration docs indicate that 0 does NOT mean
* "no decorations".
*/
if (impl->decorations &&
*impl->decorations == 0)
return TRUE;
if (GDK_WINDOW_HWND (window) == 0)
return FALSE;
style = GetWindowLong (GDK_WINDOW_HWND (window), GWL_STYLE);
if (style == 0)
{
DWORD w32_error = GetLastError ();
GDK_NOTE (MISC, g_print ("Failed to get style of window %p (handle %p): %lu\n",
window, GDK_WINDOW_HWND (window), w32_error));
return FALSE;
}
/* Keep this in sync with update_style_bits() */
/* We don't check what get_effective_window_decorations()
* has to say, because it gives suggestions based on
* various hints, while we want *actual* decorations,
* or their absence.
*/
has_any_decorations = FALSE;
if (style & (WS_BORDER | WS_THICKFRAME | WS_CAPTION |
WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX))
has_any_decorations = TRUE;
else
GDK_NOTE (MISC, g_print ("Window %p (handle %p): has no decorations (style %lx)\n",
window, GDK_WINDOW_HWND (window), style));
return !has_any_decorations;
}
static void
update_style_bits (GdkWindow *window)
{
@ -2480,9 +2612,22 @@ update_style_bits (GdkWindow *window)
new_exstyle &= ~WS_EX_TOOLWINDOW;
}
/* We can get away with using layered windows
* only when no decorations are needed. It can mean
* CSD or borderless non-CSD windows (tooltips?).
*/
if (_gdk_win32_window_lacks_wm_decorations (window))
impl->layered = g_strcmp0 (g_getenv ("GDK_WIN32_LAYERED"), "0") != 0;
if (impl->layered)
new_exstyle |= WS_EX_LAYERED;
else
new_exstyle &= ~WS_EX_LAYERED;
if (get_effective_window_decorations (window, &decorations))
{
all = (decorations & GDK_DECOR_ALL);
/* Keep this in sync with the test in _gdk_win32_window_lacks_wm_decorations() */
update_single_bit (&new_style, all, decorations & GDK_DECOR_BORDER, WS_BORDER);
update_single_bit (&new_style, all, decorations & GDK_DECOR_RESIZEH, WS_THICKFRAME);
update_single_bit (&new_style, all, decorations & GDK_DECOR_TITLE, WS_CAPTION);
@ -2583,25 +2728,17 @@ update_system_menu (GdkWindow *window)
}
}
static GQuark
get_decorations_quark ()
{
static GQuark quark = 0;
if (!quark)
quark = g_quark_from_static_string ("gdk-window-decorations");
return quark;
}
static void
gdk_win32_window_set_decorations (GdkWindow *window,
GdkWMDecoration decorations)
GdkWMDecoration decorations)
{
GdkWindowImplWin32 *impl;
GdkWMDecoration* decorations_copy;
g_return_if_fail (GDK_IS_WINDOW (window));
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
GDK_NOTE (MISC, g_print ("gdk_window_set_decorations: %p: %s %s%s%s%s%s%s\n",
GDK_WINDOW_HWND (window),
(decorations & GDK_DECOR_ALL ? "clearing" : "setting"),
@ -2612,26 +2749,30 @@ gdk_win32_window_set_decorations (GdkWindow *window,
(decorations & GDK_DECOR_MINIMIZE ? "MINIMIZE " : ""),
(decorations & GDK_DECOR_MAXIMIZE ? "MAXIMIZE " : "")));
decorations_copy = g_malloc (sizeof (GdkWMDecoration));
*decorations_copy = decorations;
g_object_set_qdata_full (G_OBJECT (window), get_decorations_quark (), decorations_copy, g_free);
if (!impl->decorations)
impl->decorations = g_malloc (sizeof (GdkWMDecoration));
*impl->decorations = decorations;
update_style_bits (window);
}
static gboolean
gdk_win32_window_get_decorations (GdkWindow *window,
GdkWMDecoration *decorations)
GdkWMDecoration *decorations)
{
GdkWMDecoration* decorations_set;
GdkWindowImplWin32 *impl;
g_return_val_if_fail (GDK_IS_WINDOW (window), FALSE);
decorations_set = g_object_get_qdata (G_OBJECT (window), get_decorations_quark ());
if (decorations_set)
*decorations = *decorations_set;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
return (decorations_set != NULL);
if (impl->decorations == NULL)
return FALSE;
*decorations = *impl->decorations;
return TRUE;
}
static GQuark
@ -2907,6 +3048,7 @@ gdk_win32_window_do_move_resize_drag (GdkWindow *window,
rect.top != new_rect.top))
{
POINT window_position;
BLENDFUNCTION blender;
context->native_move_resize_pending = FALSE;
@ -2924,12 +3066,22 @@ gdk_win32_window_do_move_resize_drag (GdkWindow *window,
window_position.x = new_rect.left;
window_position.y = new_rect.top;
/* Move immediately, no need to wait for redraw */
API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
SWP_NOZORDER_SPECIFIED,
window_position.x, window_position.y,
0, 0,
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE));
blender.BlendOp = AC_SRC_OVER;
blender.BlendFlags = 0;
blender.AlphaFormat = AC_SRC_ALPHA;
blender.SourceConstantAlpha = impl->layered_opacity * 255;
/* Size didn't change, so move immediately, no need to wait for redraw */
if (impl->layered)
API_CALL (UpdateLayeredWindow, (GDK_WINDOW_HWND (window), NULL,
&window_position, NULL, NULL, NULL,
0, &blender, ULW_ALPHA));
else
API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
SWP_NOZORDER_SPECIFIED,
window_position.x, window_position.y,
0, 0,
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE));
}
}
@ -3507,6 +3659,7 @@ gdk_win32_window_set_opacity (GdkWindow *window,
LONG exstyle;
typedef BOOL (WINAPI *PFN_SetLayeredWindowAttributes) (HWND, COLORREF, BYTE, DWORD);
PFN_SetLayeredWindowAttributes setLayeredWindowAttributes = NULL;
GdkWindowImplWin32 *impl;
g_return_if_fail (GDK_IS_WINDOW (window));
@ -3518,6 +3671,14 @@ gdk_win32_window_set_opacity (GdkWindow *window,
else if (opacity > 1)
opacity = 1;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
impl->layered_opacity = opacity;
if (impl->layered)
/* Layered windows have opacity applied elsewhere */
return;
exstyle = GetWindowLong (GDK_WINDOW_HWND (window), GWL_EXSTYLE);
if (!(exstyle & WS_EX_LAYERED))
@ -3589,6 +3750,12 @@ _gdk_win32_impl_acquire_dc (GdkWindowImplWin32 *impl)
GDK_WINDOW_DESTROYED (impl->wrapper))
return NULL;
/* We don't call this function for layered windows, but
* in case we do...
*/
if (impl->layered)
return NULL;
if (!impl->hdc)
{
impl->hdc = GetDC (impl->handle);
@ -3617,6 +3784,9 @@ _gdk_win32_impl_acquire_dc (GdkWindowImplWin32 *impl)
static void
_gdk_win32_impl_release_dc (GdkWindowImplWin32 *impl)
{
if (impl->layered)
return;
g_return_if_fail (impl->hdc_count > 0);
impl->hdc_count--;
@ -3653,6 +3823,75 @@ gdk_win32_cairo_surface_destroy (void *data)
impl->cairo_surface = NULL;
}
static cairo_surface_t *
gdk_win32_ref_cairo_surface_layered (GdkWindow *window,
GdkWindowImplWin32 *impl)
{
gint x, y, width, height;
RECT window_rect;
gdk_window_get_position (window, &x, &y);
window_rect.left = x;
window_rect.top = y;
window_rect.right = window_rect.left + gdk_window_get_width (window);
window_rect.bottom = window_rect.top + gdk_window_get_height (window);
/* Turn client area into window area */
_gdk_win32_adjust_client_rect (window, &window_rect);
width = window_rect.right - window_rect.left;
height = window_rect.bottom - window_rect.top;
if (width > impl->dib_width ||
height > impl->dib_height)
{
cairo_surface_t *new_cache;
cairo_t *cr;
/* Create larger cache surface, copy old cache surface over it */
new_cache = cairo_win32_surface_create_with_dib (CAIRO_FORMAT_ARGB32, width, height);
if (impl->cache_surface)
{
cr = cairo_create (new_cache);
cairo_set_source_surface (cr, impl->cache_surface, 0, 0);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_paint (cr);
cairo_destroy (cr);
cairo_surface_flush (new_cache);
cairo_surface_destroy (impl->cache_surface);
}
impl->cache_surface = new_cache;
if (impl->cairo_surface)
cairo_surface_destroy (impl->cairo_surface);
impl->cairo_surface = NULL;
}
/* This is separate, because cairo_surface gets killed
* off frequently by outside code, whereas cache_surface
* is only killed by us, above.
*/
if (!impl->cairo_surface)
{
impl->cairo_surface = cairo_win32_surface_create_with_dib (CAIRO_FORMAT_ARGB32, width, height);
impl->dib_width = width;
impl->dib_height = height;
cairo_surface_set_user_data (impl->cairo_surface, &gdk_win32_cairo_key,
impl, gdk_win32_cairo_surface_destroy);
}
else
{
cairo_surface_reference (impl->cairo_surface);
}
return impl->cairo_surface;
}
static cairo_surface_t *
gdk_win32_ref_cairo_surface (GdkWindow *window)
{
@ -3662,6 +3901,9 @@ gdk_win32_ref_cairo_surface (GdkWindow *window)
GDK_WINDOW_DESTROYED (impl->wrapper))
return NULL;
if (impl->layered)
return gdk_win32_ref_cairo_surface_layered (window, impl);
if (!impl->cairo_surface)
{
HDC hdc = _gdk_win32_impl_acquire_dc (impl);
@ -3714,6 +3956,7 @@ gdk_window_impl_win32_class_init (GdkWindowImplWin32Class *klass)
impl_class->destroy_foreign = gdk_win32_window_destroy_foreign;
impl_class->get_shape = gdk_win32_window_get_shape;
//FIXME?: impl_class->get_input_shape = gdk_win32_window_get_input_shape;
impl_class->begin_paint = gdk_win32_window_begin_paint;
impl_class->end_paint = gdk_win32_window_end_paint;
//impl_class->beep = gdk_x11_window_beep;

View File

@ -126,12 +126,42 @@ struct _GdkWindowImplWin32
guint inhibit_configure : 1;
guint override_redirect : 1;
/* Set to TRUE if window is using true layered mode adjustments
* via UpdateLayeredWindow().
* Layered windows that get SetLayeredWindowAttributes() called
* on them are not true layered windows.
*/
guint layered : 1;
/* GDK does not keep window contents around, it just draws new
* stuff over the window where changes occurred.
* cache_surface retains old window contents, because
* UpdateLayeredWindow() doesn't do partial redraws.
*/
cairo_surface_t *cache_surface;
cairo_surface_t *cairo_surface;
/* Unlike window-backed surfaces, DIB-backed surface
* does not provide a way to query its size,
* so we have to remember it ourselves.
*/
gint dib_width;
gint dib_height;
/* If the client wants uniformly-transparent window,
* we remember the opacity value here and apply it
* during UpdateLayredWindow() call, for layered windows.
*/
gdouble layered_opacity;
HDC hdc;
int hdc_count;
HBITMAP saved_dc_bitmap; /* Original bitmap for dc */
GdkW32DragMoveResizeContext drag_move_resize_context;
/* Decorations set by gdk_window_set_decorations() or NULL if unset */
GdkWMDecoration* decorations;
};
struct _GdkWindowImplWin32Class