/* Metacity X managed windows */
/*
* Copyright (C) 2001 Havoc Pennington, Anders Carlsson
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <config.h>
#include "window.h"
#include "util.h"
#include "frame.h"
#include "errors.h"
#include "workspace.h"
#include "stack.h"
#include "keybindings.h"
#include "ui.h"
#include "place.h"
#include "session.h"
#include "effects.h"
#include "prefs.h"
#include "resizepopup.h"
#include "xprops.h"
#include <X11/Xatom.h>
typedef enum
{
META_IS_CONFIGURE_REQUEST = 1 << 0,
META_DO_GRAVITY_ADJUST = 1 << 1,
META_USER_MOVE_RESIZE = 1 << 2
} MetaMoveResizeFlags;
typedef enum
{
WIN_HINTS_SKIP_FOCUS = (1<<0), /* "alt-tab" skips this win */
WIN_HINTS_SKIP_WINLIST = (1<<1), /* not in win list */
WIN_HINTS_SKIP_TASKBAR = (1<<2), /* not on taskbar */
WIN_HINTS_GROUP_TRANSIENT = (1<<3), /* ??????? */
WIN_HINTS_FOCUS_ON_CLICK = (1<<4), /* app only accepts focus when clicked */
WIN_HINTS_DO_NOT_COVER = (1<<5) /* attempt to not cover this window */
} GnomeWinHints;
static int destroying_windows_disallowed = 0;
static void constrain_size (MetaWindow *window,
MetaFrameGeometry *fgeom,
int width,
int height,
int *new_width,
int *new_height);
static void constrain_position (MetaWindow *window,
MetaFrameGeometry *fgeom,
int x,
int y,
int *new_x,
int *new_y);
static int update_size_hints (MetaWindow *window);
static int update_title (MetaWindow *window);
static int update_protocols (MetaWindow *window);
static int update_wm_hints (MetaWindow *window);
static void update_net_wm_state (MetaWindow *window);
static void update_mwm_hints (MetaWindow *window);
static int update_wm_class (MetaWindow *window);
static int update_transient_for (MetaWindow *window);
static void update_sm_hints (MetaWindow *window);
static void update_role (MetaWindow *window);
static void update_net_wm_type (MetaWindow *window);
static int update_initial_workspace (MetaWindow *window);
static int update_icon_name (MetaWindow *window);
static int update_icon (MetaWindow *window);
static void update_struts (MetaWindow *window);
static void recalc_window_type (MetaWindow *window);
static void recalc_window_features (MetaWindow *window);
static void recalc_do_not_cover_struts(MetaWindow *window);
static void invalidate_work_areas (MetaWindow *window);
static int set_wm_state (MetaWindow *window,
int state);
static int set_net_wm_state (MetaWindow *window);
static void send_configure_notify (MetaWindow *window);
static gboolean process_property_notify (MetaWindow *window,
XPropertyEvent *event);
static void meta_window_show (MetaWindow *window);
static void meta_window_hide (MetaWindow *window);
static GList* meta_window_get_workspaces (MetaWindow *window);
static gboolean meta_window_get_icon_geometry (MetaWindow *window,
MetaRectangle *rect);
static void adjust_for_gravity (MetaWindow *window,
MetaFrameGeometry *fgeom,
gboolean coords_assume_border,
int x,
int y,
int *xp,
int *yp);
static void meta_window_move_resize_internal (MetaWindow *window,
MetaMoveResizeFlags flags,
int resize_gravity,
int root_x_nw,
int root_y_nw,
int w,
int h);
void meta_window_move_resize_now (MetaWindow *window);
static char* get_text_property (MetaDisplay *display,
Window xwindow,
Atom atom);
void meta_window_unqueue_calc_showing (MetaWindow *window);
void meta_window_flush_calc_showing (MetaWindow *window);
void meta_window_unqueue_move_resize (MetaWindow *window);
void meta_window_flush_move_resize (MetaWindow *window);
static void meta_window_apply_session_info (MetaWindow *window,
const MetaWindowSessionInfo *info);
static const char*
wm_state_to_string (int state)
{
switch (state)
{
case NormalState:
return "NormalState";
case IconicState:
return "IconicState";
case WithdrawnState:
return "WithdrawnState";
}
return "Unknown";
}
MetaWindow*
meta_window_new (MetaDisplay *display, Window xwindow,
gboolean must_be_viewable)
{
MetaWindow *window;
XWindowAttributes attrs;
GSList *tmp;
MetaWorkspace *space;
gulong existing_wm_state;
char *str;
unsigned long cardinal;
meta_verbose ("Attempting to manage 0x%lx\n", xwindow);
if (xwindow == display->no_focus_window)
{
meta_verbose ("Not managing no_focus_window 0x%lx\n",
xwindow);
return NULL;
}
/* Grab server */
meta_display_grab (display);
meta_error_trap_push (display);
XGetWindowAttributes (display->xdisplay,
xwindow, &attrs);
if (meta_error_trap_pop (display))
{
meta_verbose ("Failed to get attributes for window 0x%lx\n",
xwindow);
meta_display_ungrab (display);
return NULL;
}
if (attrs.override_redirect)
{
meta_verbose ("Deciding not to manage override_redirect window 0x%lx\n", xwindow);
meta_display_ungrab (display);
return NULL;
}
meta_verbose ("must_be_viewable = %d attrs.map_state = %d (%s)\n",
must_be_viewable,
attrs.map_state,
(attrs.map_state == IsUnmapped) ?
"IsUnmapped" :
(attrs.map_state == IsViewable) ?
"IsViewable" :
(attrs.map_state == IsUnviewable) ?
"IsUnviewable" :
"(unknown)");
existing_wm_state = WithdrawnState;
if (must_be_viewable && attrs.map_state != IsViewable)
{
/* Only manage if WM_STATE is IconicState or NormalState */
gulong state;
/* WM_STATE isn't a cardinal, it's type WM_STATE, but is an int */
if (!(meta_prop_get_cardinal_with_atom_type (display, xwindow,
display->atom_wm_state,
display->atom_wm_state,
&state) &&
(state == IconicState || state == NormalState)))
{
meta_verbose ("Deciding not to manage unmapped or unviewable window 0x%lx\n", xwindow);
meta_display_ungrab (display);
return NULL;
}
existing_wm_state = state;
meta_verbose ("WM_STATE of %lx = %s\n", xwindow,
wm_state_to_string (existing_wm_state));
}
meta_error_trap_push (display);
XAddToSaveSet (display->xdisplay, xwindow);
XSelectInput (display->xdisplay, xwindow,
PropertyChangeMask |
EnterWindowMask | LeaveWindowMask |
FocusChangeMask);
/* Get rid of any borders */
if (attrs.border_width != 0)
XSetWindowBorderWidth (display->xdisplay, xwindow, 0);
/* Get rid of weird gravities */
if (attrs.win_gravity != NorthWestGravity)
{
XSetWindowAttributes set_attrs;
set_attrs.win_gravity = NorthWestGravity;
XChangeWindowAttributes (display->xdisplay,
xwindow,
CWWinGravity,
&set_attrs);
}
if (meta_error_trap_pop (display) != Success)
{
meta_verbose ("Window 0x%lx disappeared just as we tried to manage it\n",
xwindow);
meta_display_ungrab (display);
return NULL;
}
g_assert (!attrs.override_redirect);
window = g_new (MetaWindow, 1);
window->dialog_pid = -1;
window->dialog_pipe = -1;
window->xwindow = xwindow;
/* this is in window->screen->display, but that's too annoying to
* type
*/
window->display = display;
window->workspaces = NULL;
window->screen = NULL;
tmp = display->screens;
while (tmp != NULL)
{
if (((MetaScreen *)tmp->data)->xscreen == attrs.screen)
{
window->screen = tmp->data;
break;
}
tmp = tmp->next;
}
g_assert (window->screen);
/* avoid tons of stack updates */
meta_stack_freeze (window->screen->stack);
/* Remember this rect is the actual window size */
window->rect.x = attrs.x;
window->rect.y = attrs.y;
window->rect.width = attrs.width;
window->rect.height = attrs.height;
window->size_hints.flags = 0;
/* And border width, size_hints are the "request" */
window->border_width = attrs.border_width;
window->size_hints.x = attrs.x;
window->size_hints.y = attrs.y;
window->size_hints.width = attrs.width;
window->size_hints.height = attrs.height;
/* And this is our unmaximized size */
window->saved_rect = window->rect;
window->user_rect = window->rect;
window->depth = attrs.depth;
window->xvisual = attrs.visual;
window->title = NULL;
window->icon_name = NULL;
window->icon = NULL;
window->mini_icon = NULL;
meta_icon_cache_init (&window->icon_cache);
window->wm_hints_pixmap = None;
window->wm_hints_mask = None;
window->desc = g_strdup_printf ("0x%lx", window->xwindow);
window->frame = NULL;
window->has_focus = FALSE;
window->user_has_move_resized = FALSE;
window->maximized = FALSE;
window->fullscreen = FALSE;
window->on_all_workspaces = FALSE;
window->shaded = FALSE;
window->initially_iconic = FALSE;
window->minimized = FALSE;
window->iconic = FALSE;
window->mapped = attrs.map_state != IsUnmapped;
/* if already mapped we don't want to do the placement thing */
window->placed = window->mapped;
if (window->placed)
meta_topic (META_DEBUG_PLACEMENT,
"Not placing window 0x%lx since it's already mapped\n",
xwindow);
window->unmanaging = FALSE;
window->calc_showing_queued = FALSE;
window->move_resize_queued = FALSE;
window->keys_grabbed = FALSE;
window->grab_on_frame = FALSE;
window->all_keys_grabbed = FALSE;
window->withdrawn = FALSE;
window->initial_workspace_set = FALSE;
window->calc_placement = FALSE;
window->unmaps_pending = 0;
window->mwm_decorated = TRUE;
window->mwm_has_close_func = TRUE;
window->mwm_has_minimize_func = TRUE;
window->mwm_has_maximize_func = TRUE;
window->mwm_has_move_func = TRUE;
window->mwm_has_resize_func = TRUE;
window->decorated = TRUE;
window->has_close_func = TRUE;
window->has_minimize_func = TRUE;
window->has_maximize_func = TRUE;
window->has_move_func = TRUE;
window->has_resize_func = TRUE;
window->has_shade_func = TRUE;
window->has_fullscreen_func = TRUE;
window->wm_state_modal = FALSE;
window->wm_state_skip_taskbar = FALSE;
window->wm_state_skip_pager = FALSE;
window->res_class = NULL;
window->res_name = NULL;
window->role = NULL;
window->sm_client_id = NULL;
window->wm_client_machine = NULL;
window->net_wm_pid = -1;
window->xtransient_for = None;
window->xgroup_leader = None;
window->xclient_leader = None;
window->type = META_WINDOW_NORMAL;
window->type_atom = None;
window->has_struts = FALSE;
window->left_strut = 0;
window->right_strut = 0;
window->top_strut = 0;
window->bottom_strut = 0;
window->layer = META_LAYER_NORMAL;
window->stack_op = NULL;
window->initial_workspace = 0; /* not used */
meta_display_register_x_window (display, &window->xwindow, window);
if (meta_prop_get_latin1_string (window->display, window->xwindow,
window->display->atom_wm_client_machine,
&str))
{
window->wm_client_machine = g_strdup (str);
meta_XFree (str);
meta_verbose ("Window has client machine \"%s\"\n",
window->wm_client_machine);
}
if (meta_prop_get_cardinal (window->display, window->xwindow,
window->display->atom_net_wm_pid,
&cardinal))
{
if (cardinal <= 0)
meta_warning (_("Application set a bogus _NET_WM_PID %ld\n"),
cardinal);
else
{
window->net_wm_pid = cardinal;
meta_verbose ("Window has _NET_WM_PID %d\n",
window->net_wm_pid);
}
}
update_size_hints (window);
update_title (window);
update_protocols (window);
update_wm_hints (window);
update_struts (window);
update_net_wm_state (window);
/* Initially maximize if window is fullscreen; FIXME
* assume fullscreen state instead once we have that state...
*/
if (!window->maximized &&
attrs.x == 0 && attrs.y == 0 &&
attrs.width == window->screen->width &&
attrs.height == window->screen->height)
window->maximized = TRUE;
update_mwm_hints (window);
update_wm_class (window);
update_transient_for (window);
update_sm_hints (window); /* must come after transient_for */
update_role (window);
update_net_wm_type (window);
update_initial_workspace (window);
update_icon_name (window);
update_icon (window);
if (!window->mapped &&
(window->size_hints.flags & PPosition) == 0 &&
(window->size_hints.flags & USPosition) == 0)
{
/* ignore current window position */
window->size_hints.x = 0;
window->size_hints.y = 0;
}
if (window->initially_iconic)
{
/* WM_HINTS said minimized */
window->minimized = TRUE;
meta_verbose ("Window %s asked to start out minimized\n", window->desc);
}
if (existing_wm_state == IconicState)
{
/* WM_STATE said minimized */
window->minimized = TRUE;
meta_verbose ("Window %s had preexisting WM_STATE = IconicState, minimizing\n",
window->desc);
/* Assume window was previously placed, though perhaps it's
* been iconic its whole life, we have no way of knowing.
*/
window->placed = TRUE;
}
/* FIXME we have a tendency to set this then immediately
* change it again.
*/
set_wm_state (window, window->iconic ? IconicState : NormalState);
set_net_wm_state (window);
if (window->decorated)
meta_window_ensure_frame (window);
meta_window_grab_keys (window);
meta_display_grab_window_buttons (window->display, window->xwindow);
meta_display_grab_focus_window_button (window->display, window->xwindow);
/* For the workspace, first honor hints,
* if that fails put transients with parents,
* otherwise put window on active space
*/
if (window->initial_workspace_set)
{
if (window->initial_workspace == (int) 0xFFFFFFFF)
{
meta_topic (META_DEBUG_PLACEMENT,
"Window %s is initially on all spaces\n",
window->desc);
meta_workspace_add_window (window->screen->active_workspace, window);
window->on_all_workspaces = TRUE;
}
else
{
meta_topic (META_DEBUG_PLACEMENT,
"Window %s is initially on space %d\n",
window->desc, window->initial_workspace);
space =
meta_display_get_workspace_by_screen_index (window->display,
window->screen,
window->initial_workspace);
if (space)
meta_workspace_add_window (space, window);
}
}
if (window->workspaces == NULL &&
window->xtransient_for != None)
{
/* Try putting dialog on parent's workspace */
MetaWindow *parent;
parent = meta_display_lookup_x_window (window->display,
window->xtransient_for);
if (parent)
{
GList *tmp_list;
meta_topic (META_DEBUG_PLACEMENT,
"Putting window %s on some workspaces as parent %s\n",
window->desc, parent->desc);
if (parent->on_all_workspaces)
window->on_all_workspaces = TRUE;
tmp_list = parent->workspaces;
while (tmp_list != NULL)
{
meta_workspace_add_window (tmp_list->data, window);
tmp_list = tmp_list->next;
}
}
}
if (window->workspaces == NULL)
{
meta_topic (META_DEBUG_PLACEMENT,
"Putting window %s on active workspace\n",
window->desc);
space = window->screen->active_workspace;
meta_workspace_add_window (space, window);
}
/* Only accept USPosition on normal windows because the app is full
* of shit claiming the user set -geometry for a dialog or dock
*/
if (window->type == META_WINDOW_NORMAL &&
(window->size_hints.flags & USPosition))
{
/* don't constrain with placement algorithm */
window->placed = TRUE;
meta_topic (META_DEBUG_PLACEMENT,
"Honoring USPosition for %s instead of using placement algorithm\n", window->desc);
}
/* Assume the app knows best how to place these. */
if (window->type == META_WINDOW_DESKTOP ||
window->type == META_WINDOW_DOCK ||
window->type == META_WINDOW_TOOLBAR ||
window->type == META_WINDOW_MENU ||
window->type == META_WINDOW_UTILITY)
{
if (window->size_hints.flags & PPosition)
{
window->placed = TRUE;
meta_verbose ("Not placing non-normal non-dialog window with PPosition set\n");
}
}
if (window->type == META_WINDOW_DESKTOP ||
window->type == META_WINDOW_DOCK)
{
/* Change the default, but don't enforce this if
* the user focuses the dock/desktop and unsticks it
* using key shortcuts
*/
window->on_all_workspaces = TRUE;
}
/* for the various on_all_workspaces = TRUE possible above */
meta_window_set_current_workspace_hint (window);
/* Put our state back where it should be,
* passing TRUE for is_configure_request, ICCCM says
* initial map is handled same as c
|