/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* Mutter X window decorations */ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2003, 2004 Red Hat, Inc. * Copyright (C) 2005 Elijah Newren * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "config.h" #include "core/frame.h" #include "backends/x11/meta-backend-x11.h" #include "compositor/compositor-private.h" #include "core/bell.h" #include "core/keybindings-private.h" #include "meta/meta-x11-errors.h" #include "x11/meta-x11-display-private.h" #include "x11/window-props.h" #include #include #define EVENT_MASK (SubstructureRedirectMask | \ StructureNotifyMask | SubstructureNotifyMask | \ PropertyChangeMask | FocusChangeMask) void meta_window_ensure_frame (MetaWindow *window) { MetaX11Display *x11_display = window->display->x11_display; unsigned long data[1] = { 1 }; meta_x11_error_trap_push (x11_display); XChangeProperty (x11_display->xdisplay, window->xwindow, x11_display->atom__MUTTER_NEEDS_FRAME, XA_CARDINAL, 32, PropModeReplace, (guchar*) data, 1); meta_x11_error_trap_pop (x11_display); } void meta_window_set_frame_xwindow (MetaWindow *window, Window xframe) { MetaX11Display *x11_display = window->display->x11_display; XSetWindowAttributes attrs; gulong create_serial = 0; MetaFrame *frame; if (window->frame) return; frame = g_new0 (MetaFrame, 1); frame->window = window; frame->xwindow = xframe; frame->rect = window->rect; frame->child_x = 0; frame->child_y = 0; frame->bottom_height = 0; frame->right_width = 0; frame->borders_cached = FALSE; meta_sync_counter_init (&frame->sync_counter, window, frame->xwindow); window->frame = frame; meta_verbose ("Frame geometry %d,%d %dx%d", frame->rect.x, frame->rect.y, frame->rect.width, frame->rect.height); meta_verbose ("Setting frame 0x%lx for window %s, " "frame geometry %d,%d %dx%d", xframe, window->desc, frame->rect.x, frame->rect.y, frame->rect.width, frame->rect.height); meta_stack_tracker_record_add (window->display->stack_tracker, frame->xwindow, create_serial); meta_verbose ("Frame for %s is 0x%lx", frame->window->desc, frame->xwindow); meta_x11_error_trap_push (x11_display); attrs.event_mask = EVENT_MASK; XChangeWindowAttributes (x11_display->xdisplay, frame->xwindow, CWEventMask, &attrs); if (META_X11_DISPLAY_HAS_SHAPE (x11_display)) XShapeSelectInput (x11_display->xdisplay, frame->xwindow, ShapeNotifyMask); meta_x11_display_register_x_window (x11_display, &frame->xwindow, window); if (window->mapped) { window->mapped = FALSE; /* the reparent will unmap the window, * we don't want to take that as a withdraw */ meta_topic (META_DEBUG_WINDOW_STATE, "Incrementing unmaps_pending on %s for reparent", window->desc); window->unmaps_pending += 1; } meta_stack_tracker_record_remove (window->display->stack_tracker, window->xwindow, XNextRequest (x11_display->xdisplay)); XReparentWindow (x11_display->xdisplay, window->xwindow, frame->xwindow, frame->child_x, frame->child_y); window->reparents_pending += 1; /* FIXME handle this error */ meta_x11_error_trap_pop (x11_display); /* Ensure focus is restored after the unmap/map events triggered * by XReparentWindow(). */ if (meta_window_has_focus (window)) window->restore_focus_on_map = TRUE; /* stick frame to the window */ window->frame = frame; meta_window_reload_property_from_xwindow (window, frame->xwindow, x11_display->atom__NET_WM_SYNC_REQUEST_COUNTER, TRUE); meta_window_reload_property_from_xwindow (window, frame->xwindow, x11_display->atom__NET_WM_OPAQUE_REGION, TRUE); meta_x11_error_trap_push (x11_display); XMapWindow (x11_display->xdisplay, frame->xwindow); meta_x11_error_trap_pop (x11_display); /* Move keybindings to frame instead of window */ meta_window_grab_keys (window); /* Even though the property was already set, notify * on it so other bits of the machinery catch up * on the new frame. */ g_object_notify (G_OBJECT (window), "decorated"); } void meta_window_destroy_frame (MetaWindow *window) { MetaFrame *frame; MetaFrameBorders borders; MetaX11Display *x11_display; if (window->frame == NULL) return; x11_display = window->display->x11_display; meta_verbose ("Unframing window %s", window->desc); frame = window->frame; meta_frame_calc_borders (frame, &borders); /* Unparent the client window; it may be destroyed, * thus the error trap. */ meta_x11_error_trap_push (x11_display); if (window->mapped) { window->mapped = FALSE; /* Keep track of unmapping it, so we * can identify a withdraw initiated * by the client. */ meta_topic (META_DEBUG_WINDOW_STATE, "Incrementing unmaps_pending on %s for reparent back to root", window->desc); window->unmaps_pending += 1; } if (!x11_display->closing) { if (!window->unmanaging) { meta_stack_tracker_record_add (window->display->stack_tracker, window->xwindow, XNextRequest (x11_display->xdisplay)); } XReparentWindow (x11_display->xdisplay, window->xwindow, x11_display->xroot, /* Using anything other than client root window coordinates * coordinates here means we'll need to ensure a configure * notify event is sent; see bug 399552. */ window->frame->rect.x + borders.invisible.left, window->frame->rect.y + borders.invisible.top); window->reparents_pending += 1; } if (META_X11_DISPLAY_HAS_SHAPE (x11_display)) XShapeSelectInput (x11_display->xdisplay, frame->xwindow, NoEventMask); XDeleteProperty (x11_display->xdisplay, window->xwindow, x11_display->atom__MUTTER_NEEDS_FRAME); meta_x11_error_trap_pop (x11_display); /* Ensure focus is restored after the unmap/map events triggered * by XReparentWindow(). */ if (meta_window_has_focus (window)) window->restore_focus_on_map = TRUE; meta_x11_display_unregister_x_window (x11_display, frame->xwindow); window->frame = NULL; if (window->frame_bounds) { cairo_region_destroy (window->frame_bounds); window->frame_bounds = NULL; } g_clear_pointer (&window->opaque_region, cairo_region_destroy); /* Move keybindings to window instead of frame */ meta_window_grab_keys (window); meta_sync_counter_clear (&frame->sync_counter); g_free (frame); /* Put our state back where it should be */ if (!window->unmanaging) meta_compositor_sync_updates_frozen (window->display->compositor, window); meta_window_queue (window, META_QUEUE_CALC_SHOWING); meta_window_queue (window, META_QUEUE_MOVE_RESIZE); } void meta_frame_borders_clear (MetaFrameBorders *self) { self->visible.top = self->invisible.top = self->total.top = 0; self->visible.bottom = self->invisible.bottom = self->total.bottom = 0; self->visible.left = self->invisible.left = self->total.left = 0; self->visible.right = self->invisible.right = self->total.right = 0; } static void meta_frame_query_borders (MetaFrame *frame, MetaFrameBorders *borders) { MetaWindow *window = frame->window; MetaX11Display *x11_display = window->display->x11_display; int format, res; Atom type; unsigned long nitems, bytes_after; unsigned char *data; if (!frame->xwindow) return; meta_x11_error_trap_push (x11_display); res = XGetWindowProperty (x11_display->xdisplay, frame->xwindow, x11_display->atom__GTK_FRAME_EXTENTS, 0, 4, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (unsigned char **) &data); if (meta_x11_error_trap_pop_with_return (x11_display) != Success) return; if (res == Success && nitems == 4) { borders->invisible = (MetaFrameBorder) { ((long *) data)[0], ((long *) data)[1], ((long *) data)[2], ((long *) data)[3], }; } g_clear_pointer (&data, XFree); meta_x11_error_trap_push (x11_display); res = XGetWindowProperty (x11_display->xdisplay, frame->xwindow, x11_display->atom__MUTTER_FRAME_EXTENTS, 0, 4, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (unsigned char **) &data); if (meta_x11_error_trap_pop_with_return (x11_display) != Success) return; if (res == Success && nitems == 4) { borders->visible = (MetaFrameBorder) { ((long *) data)[0], ((long *) data)[1], ((long *) data)[2], ((long *) data)[3], }; } g_clear_pointer (&data, XFree); borders->total = (MetaFrameBorder) { borders->invisible.left + frame->cached_borders.visible.left, borders->invisible.right + frame->cached_borders.visible.right, borders->invisible.top + frame->cached_borders.visible.top, borders->invisible.bottom + frame->cached_borders.visible.bottom, }; } void meta_frame_calc_borders (MetaFrame *frame, MetaFrameBorders *borders) { /* Save on if statements and potential uninitialized values * in callers -- if there's no frame, then zero the borders. */ if (frame == NULL) meta_frame_borders_clear (borders); else { if (!frame->borders_cached) { meta_frame_query_borders (frame, &frame->cached_borders); frame->borders_cached = TRUE; } *borders = frame->cached_borders; } } void meta_frame_clear_cached_borders (MetaFrame *frame) { frame->borders_cached = FALSE; } gboolean meta_frame_sync_to_window (MetaFrame *frame, gboolean need_resize) { MetaWindow *window = frame->window; MetaX11Display *x11_display = window->display->x11_display; meta_topic (META_DEBUG_GEOMETRY, "Syncing frame geometry %d,%d %dx%d (SE: %d,%d)", frame->rect.x, frame->rect.y, frame->rect.width, frame->rect.height, frame->rect.x + frame->rect.width, frame->rect.y + frame->rect.height); meta_x11_error_trap_push (x11_display); XMoveResizeWindow (x11_display->xdisplay, frame->xwindow, frame->rect.x, frame->rect.y, frame->rect.width, frame->rect.height); meta_x11_error_trap_pop (x11_display); return need_resize; } cairo_region_t * meta_frame_get_frame_bounds (MetaFrame *frame) { MetaFrameBorders borders; cairo_region_t *bounds; meta_frame_calc_borders (frame, &borders); /* FIXME: currently just the client area, should shape closer to * frame border, incl. rounded corners. */ bounds = cairo_region_create_rectangle (&(cairo_rectangle_int_t) { borders.total.left, borders.total.top, frame->rect.width - borders.total.left - borders.total.right, frame->rect.height - borders.total.top - borders.total.bottom, }); return bounds; } void meta_frame_get_mask (MetaFrame *frame, cairo_rectangle_int_t *frame_rect, cairo_t *cr) { MetaFrameBorders borders; meta_frame_calc_borders (frame, &borders); cairo_rectangle (cr, 0, 0, frame->rect.width, frame->rect.height); cairo_set_source_rgb (cr, 0, 0, 0); cairo_fill (cr); } Window meta_frame_get_xwindow (MetaFrame *frame) { return frame->xwindow; } gboolean meta_frame_handle_xevent (MetaFrame *frame, XEvent *xevent) { MetaWindow *window = frame->window; MetaX11Display *x11_display = window->display->x11_display; if (xevent->xany.type == PropertyNotify && xevent->xproperty.state == PropertyNewValue && (xevent->xproperty.atom == x11_display->atom__GTK_FRAME_EXTENTS || xevent->xproperty.atom == x11_display->atom__MUTTER_FRAME_EXTENTS)) { meta_window_frame_size_changed (window); meta_window_queue (window, META_QUEUE_MOVE_RESIZE); return TRUE; } else if (xevent->xany.type == PropertyNotify && xevent->xproperty.state == PropertyNewValue && (xevent->xproperty.atom == x11_display->atom__NET_WM_SYNC_REQUEST_COUNTER || xevent->xproperty.atom == x11_display->atom__NET_WM_OPAQUE_REGION)) { meta_window_reload_property_from_xwindow (window, frame->xwindow, xevent->xproperty.atom, FALSE); return TRUE; } return FALSE; } GSubprocess * meta_frame_launch_client (MetaX11Display *x11_display, const char *display_name) { g_autoptr(GSubprocessLauncher) launcher = NULL; g_autoptr (GError) error = NULL; GSubprocess *proc; const char *args[2]; args[0] = MUTTER_LIBEXECDIR "/mutter-x11-frames"; args[1] = NULL; launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_setenv (launcher, "DISPLAY", display_name, TRUE); proc = g_subprocess_launcher_spawnv (launcher, args, &error); if (error) { if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) { /* Fallback case for uninstalled tests, relies on CWD being * the builddir, as it is the case during "ninja test". */ g_clear_error (&error); args[0] = "./src/frames/mutter-x11-frames"; proc = g_subprocess_launcher_spawnv (launcher, args, &error); } if (error) { g_warning ("Could not launch X11 frames client: %s", error->message); return NULL; } } return proc; } /** * meta_frame_type_to_string: * @type: a #MetaFrameType * * Converts a frame type enum value to the name string that would * appear in the theme definition file. * * Return value: the string value */ const char * meta_frame_type_to_string (MetaFrameType type) { switch (type) { case META_FRAME_TYPE_NORMAL: return "normal"; case META_FRAME_TYPE_DIALOG: return "dialog"; case META_FRAME_TYPE_MODAL_DIALOG: return "modal_dialog"; case META_FRAME_TYPE_UTILITY: return "utility"; case META_FRAME_TYPE_MENU: return "menu"; case META_FRAME_TYPE_BORDER: return "border"; case META_FRAME_TYPE_ATTACHED: return "attached"; case META_FRAME_TYPE_LAST: break; } return ""; } MetaSyncCounter * meta_frame_get_sync_counter (MetaFrame *frame) { return &frame->sync_counter; } void meta_frame_set_opaque_region (MetaFrame *frame, cairo_region_t *region) { MetaWindow *window = frame->window; if (cairo_region_equal (frame->opaque_region, region)) return; g_clear_pointer (&frame->opaque_region, cairo_region_destroy); if (region != NULL) frame->opaque_region = cairo_region_reference (region); meta_compositor_window_shape_changed (window->display->compositor, window); }