diff options
Diffstat (limited to 'src/core')
52 files changed, 47155 insertions, 0 deletions
diff --git a/src/core/async-getprop.c b/src/core/async-getprop.c new file mode 100644 index 00000000..80322b41 --- /dev/null +++ b/src/core/async-getprop.c @@ -0,0 +1,680 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Asynchronous X property getting hack */ + +/* + * Copyright (C) 2002 Havoc Pennington + * Copyright (C) 1986, 1998 The Open Group + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation. + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of The Open Group shall not be + * used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization from The Open Group. + */ + +#include <assert.h> + +#undef DEBUG_SPEW +#ifdef DEBUG_SPEW +#include <stdio.h> +#endif + +#include "async-getprop.h" + +#define NEED_REPLIES +#include <X11/Xlibint.h> + +#ifndef NULL +#define NULL ((void*)0) +#endif + +typedef struct _ListNode ListNode; +typedef struct _AgPerDisplayData AgPerDisplayData; + +struct _ListNode +{ + ListNode *next; +}; + +struct _AgGetPropertyTask +{ + ListNode node; + + AgPerDisplayData *dd; + Window window; + Atom property; + + unsigned long request_seq; + int error; + + Atom actual_type; + int actual_format; + + unsigned long n_items; + unsigned long bytes_after; + char *data; + + Bool have_reply; +}; + +struct _AgPerDisplayData +{ + ListNode node; + _XAsyncHandler async; + + Display *display; + ListNode *pending_tasks; + ListNode *pending_tasks_tail; + ListNode *completed_tasks; + ListNode *completed_tasks_tail; + int n_tasks_pending; + int n_tasks_completed; +}; + +static ListNode *display_datas = NULL; +static ListNode *display_datas_tail = NULL; + +static void +append_to_list (ListNode **head, + ListNode **tail, + ListNode *task) +{ + task->next = NULL; + + if (*tail == NULL) + { + assert (*head == NULL); + *head = task; + *tail = task; + } + else + { + (*tail)->next = task; + *tail = task; + } +} + +static void +remove_from_list (ListNode **head, + ListNode **tail, + ListNode *task) +{ + ListNode *prev; + ListNode *node; + + prev = NULL; + node = *head; + while (node != NULL) + { + if (node == task) + { + if (prev) + prev->next = node->next; + else + *head = node->next; + + if (node == *tail) + *tail = prev; + + break; + } + + prev = node; + node = node->next; + } + + /* can't remove what's not there */ + assert (node != NULL); + + node->next = NULL; +} + +static void +move_to_completed (AgPerDisplayData *dd, + AgGetPropertyTask *task) +{ + remove_from_list (&dd->pending_tasks, + &dd->pending_tasks_tail, + &task->node); + + append_to_list (&dd->completed_tasks, + &dd->completed_tasks_tail, + &task->node); + + dd->n_tasks_pending -= 1; + dd->n_tasks_completed += 1; +} + +static AgGetPropertyTask* +find_pending_by_request_sequence (AgPerDisplayData *dd, + unsigned long request_seq) +{ + ListNode *node; + + /* if the sequence is after our last pending task, we + * aren't going to find a match + */ + { + AgGetPropertyTask *task = (AgGetPropertyTask*) dd->pending_tasks_tail; + if (task != NULL) + { + if (task->request_seq < request_seq) + return NULL; + else if (task->request_seq == request_seq) + return task; /* why not check this */ + } + } + + /* Generally we should get replies in the order we sent + * requests, so we should usually be using the task + * at the head of the list, if we use any task at all. + * I'm not sure this is 100% guaranteed, if it is, + * it would be a big speedup. + */ + + node = dd->pending_tasks; + while (node != NULL) + { + AgGetPropertyTask *task = (AgGetPropertyTask*) node; + + if (task->request_seq == request_seq) + return task; + + node = node->next; + } + + return NULL; +} + +static Bool +async_get_property_handler (Display *dpy, + xReply *rep, + char *buf, + int len, + XPointer data) +{ + xGetPropertyReply replbuf; + xGetPropertyReply *reply; + AgGetPropertyTask *task; + AgPerDisplayData *dd; + int bytes_read; + + dd = (AgPerDisplayData*) data; + +#if 0 + printf ("%s: seeing request seq %ld buflen %d\n", __FUNCTION__, + dpy->last_request_read, len); +#endif + + task = find_pending_by_request_sequence (dd, dpy->last_request_read); + + if (task == NULL) + return False; + + assert (dpy->last_request_read == task->request_seq); + + task->have_reply = True; + move_to_completed (dd, task); + + /* read bytes so far */ + bytes_read = SIZEOF (xReply); + + if (rep->generic.type == X_Error) + { + xError errbuf; + + task->error = rep->error.errorCode; + +#ifdef DEBUG_SPEW + printf ("%s: error code = %d (ignoring error, eating %d bytes, generic.length = %ld)\n", + __FUNCTION__, task->error, (SIZEOF (xError) - bytes_read), + rep->generic.length); +#endif + + /* We return True (meaning we consumed the reply) + * because otherwise it would invoke the X error handler, + * and an async API is useless if you have to synchronously + * trap X errors. Also GetProperty can always fail, pretty + * much, so trapping errors is always what you want. + * + * We have to eat all the error reply data here. + * (kind of a charade as we know sizeof(xError) == sizeof(xReply)) + * + * Passing discard = True seems to break things; I don't understand + * why, because there should be no extra data in an error reply, + * right? + */ + _XGetAsyncReply (dpy, (char *)&errbuf, rep, buf, len, + (SIZEOF (xError) - bytes_read) >> 2, /* in 32-bit words */ + False); /* really seems like it should be True */ + + return True; + } + +#ifdef DEBUG_SPEW + printf ("%s: already read %d bytes reading %d more for total of %d; generic.length = %ld\n", + __FUNCTION__, bytes_read, (SIZEOF (xGetPropertyReply) - bytes_read) >> 2, + SIZEOF (xGetPropertyReply), rep->generic.length); +#endif + + /* (kind of a silly as we know sizeof(xGetPropertyReply) == sizeof(xReply)) */ + reply = (xGetPropertyReply *) + _XGetAsyncReply (dpy, (char *)&replbuf, rep, buf, len, + (SIZEOF (xGetPropertyReply) - bytes_read) >> 2, /* in 32-bit words */ + False); /* False means expecting more data to follow, + * don't eat the rest of the reply + */ + + bytes_read = SIZEOF (xGetPropertyReply); + +#ifdef DEBUG_SPEW + printf ("%s: have reply propertyType = %ld format = %d n_items = %ld\n", + __FUNCTION__, reply->propertyType, reply->format, reply->nItems); +#endif + + assert (task->data == NULL); + + /* This is all copied from XGetWindowProperty(). Not sure we should + * LockDisplay(). Not sure I'm passing the right args to + * XGetAsyncData(). Not sure about a lot of things. + */ + + /* LockDisplay (dpy); */ + + if (reply->propertyType != None) + { + long nbytes, netbytes; + + /* this alignment macro from matecorba2 */ +#define ALIGN_VALUE(this, boundary) \ + (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) + + switch (reply->format) + { + /* + * One extra byte is malloced than is needed to contain the property + * data, but this last byte is null terminated and convenient for + * returning string properties, so the client doesn't then have to + * recopy the string to make it null terminated. + */ + case 8: + nbytes = reply->nItems; + /* there's padding to word boundary */ + netbytes = ALIGN_VALUE (nbytes, 4); + if (nbytes + 1 > 0 && + (task->data = (char *) Xmalloc ((unsigned)nbytes + 1))) + { +#ifdef DEBUG_SPEW + printf ("%s: already read %d bytes using %ld, more eating %ld more\n", + __FUNCTION__, bytes_read, nbytes, netbytes); +#endif + /* _XReadPad (dpy, (char *) task->data, netbytes); */ + _XGetAsyncData (dpy, task->data, buf, len, + bytes_read, nbytes, + netbytes); + } + break; + + case 16: + nbytes = reply->nItems * sizeof (short); + netbytes = reply->nItems << 1; + netbytes = ALIGN_VALUE (netbytes, 4); /* align to word boundary */ + if (nbytes + 1 > 0 && + (task->data = (char *) Xmalloc ((unsigned)nbytes + 1))) + { +#ifdef DEBUG_SPEW + printf ("%s: already read %d bytes using %ld more, eating %ld more\n", + __FUNCTION__, bytes_read, nbytes, netbytes); +#endif + /* _XRead16Pad (dpy, (short *) task->data, netbytes); */ + _XGetAsyncData (dpy, task->data, buf, len, + bytes_read, nbytes, netbytes); + } + break; + + case 32: + /* NOTE buffer is in longs to match XGetWindowProperty() */ + nbytes = reply->nItems * sizeof (long); + netbytes = reply->nItems << 2; /* wire size is always 32 bits though */ + if (nbytes + 1 > 0 && + (task->data = (char *) Xmalloc ((unsigned)nbytes + 1))) + { +#ifdef DEBUG_SPEW + printf ("%s: already read %d bytes using %ld more, eating %ld more\n", + __FUNCTION__, bytes_read, nbytes, netbytes); +#endif + + /* We have to copy the XGetWindowProperty() crackrock + * and get format 32 as long even on 64-bit platforms. + */ + if (sizeof (long) == 8) + { + char *netdata; + char *lptr; + char *end_lptr; + + /* Store the 32-bit values in the end of the array */ + netdata = task->data + nbytes / 2; + + _XGetAsyncData (dpy, netdata, buf, len, + bytes_read, netbytes, + netbytes); + + /* Now move the 32-bit values to the front */ + + lptr = task->data; + end_lptr = task->data + nbytes; + while (lptr != end_lptr) + { + *(long*) lptr = *(CARD32*) netdata; + lptr += sizeof (long); + netdata += sizeof (CARD32); + } + } + else + { + /* Here the wire format matches our actual format */ + _XGetAsyncData (dpy, task->data, buf, len, + bytes_read, netbytes, + netbytes); + } + } + break; + + default: + /* + * This part of the code should never be reached. If it is, + * the server sent back a property with an invalid format. + * This is a BadImplementation error. + * + * However this async GetProperty API doesn't report errors + * via the standard X mechanism, so don't do anything about + * it, other than store it in task->error. + */ + { +#if 0 + xError error; +#endif + + task->error = BadImplementation; + +#if 0 + error.sequenceNumber = task->request_seq; + error.type = X_Error; + error.majorCode = X_GetProperty; + error.minorCode = 0; + error.errorCode = BadImplementation; + + _XError (dpy, &error); +#endif + } + + nbytes = netbytes = 0L; + break; + } + + if (task->data == NULL) + { + task->error = BadAlloc; + +#ifdef DEBUG_SPEW + printf ("%s: already read %d bytes eating %ld\n", + __FUNCTION__, bytes_read, netbytes); +#endif + /* _XEatData (dpy, (unsigned long) netbytes); */ + _XGetAsyncData (dpy, NULL, buf, len, + bytes_read, 0, netbytes); + + /* UnlockDisplay (dpy); */ + return BadAlloc; /* not Success */ + } + + (task->data)[nbytes] = '\0'; + } + +#ifdef DEBUG_SPEW + printf ("%s: have data\n", __FUNCTION__); +#endif + + task->actual_type = reply->propertyType; + task->actual_format = reply->format; + task->n_items = reply->nItems; + task->bytes_after = reply->bytesAfter; + + /* UnlockDisplay (dpy); */ + + return True; +} + +static AgPerDisplayData* +get_display_data (Display *display, + Bool create) +{ + ListNode *node; + AgPerDisplayData *dd; + + node = display_datas; + while (node != NULL) + { + dd = (AgPerDisplayData*) node; + + if (dd->display == display) + return dd; + + node = node->next; + } + + if (!create) + return NULL; + + dd = Xcalloc (1, sizeof (AgPerDisplayData)); + if (dd == NULL) + return NULL; + + dd->display = display; + dd->async.next = display->async_handlers; + dd->async.handler = async_get_property_handler; + dd->async.data = (XPointer) dd; + dd->display->async_handlers = &dd->async; + + append_to_list (&display_datas, + &display_datas_tail, + &dd->node); + + return dd; +} + +static void +maybe_free_display_data (AgPerDisplayData *dd) +{ + if (dd->pending_tasks == NULL && + dd->completed_tasks == NULL) + { + DeqAsyncHandler (dd->display, &dd->async); + remove_from_list (&display_datas, &display_datas_tail, + &dd->node); + XFree (dd); + } +} + +AgGetPropertyTask* +ag_task_create (Display *dpy, + Window window, + Atom property, + long offset, + long length, + Bool delete, + Atom req_type) +{ + AgGetPropertyTask *task; + xGetPropertyReq *req; + AgPerDisplayData *dd; + + /* Fire up our request */ + LockDisplay (dpy); + + dd = get_display_data (dpy, True); + if (dd == NULL) + { + UnlockDisplay (dpy); + return NULL; + } + + GetReq (GetProperty, req); + req->window = window; + req->property = property; + req->type = req_type; + req->delete = delete; + req->longOffset = offset; + req->longLength = length; + + /* Queue up our async task */ + task = Xcalloc (1, sizeof (AgGetPropertyTask)); + if (task == NULL) + { + UnlockDisplay (dpy); + return NULL; + } + + task->dd = dd; + task->window = window; + task->property = property; + task->request_seq = dpy->request; + + append_to_list (&dd->pending_tasks, + &dd->pending_tasks_tail, + &task->node); + dd->n_tasks_pending += 1; + + UnlockDisplay (dpy); + + SyncHandle (); + + return task; +} + +static void +free_task (AgGetPropertyTask *task) +{ + remove_from_list (&task->dd->completed_tasks, + &task->dd->completed_tasks_tail, + &task->node); + task->dd->n_tasks_completed -= 1; + maybe_free_display_data (task->dd); + XFree (task); +} + +Status +ag_task_get_reply_and_free (AgGetPropertyTask *task, + Atom *actual_type, + int *actual_format, + unsigned long *nitems, + unsigned long *bytesafter, + unsigned char **prop) +{ + Display *dpy; + + *prop = NULL; + + dpy = task->dd->display; /* Xlib macros require a variable named "dpy" */ + + if (task->error != Success) + { + Status s = task->error; + + free_task (task); + + return s; + } + + if (!task->have_reply) + { + free_task (task); + + return BadAlloc; /* not Success */ + } + + *actual_type = task->actual_type; + *actual_format = task->actual_format; + *nitems = task->n_items; + *bytesafter = task->bytes_after; + + *prop = (unsigned char*) task->data; /* pass out ownership of task->data */ + + SyncHandle (); + + free_task (task); + + return Success; +} + +Bool +ag_task_have_reply (AgGetPropertyTask *task) +{ + return task->have_reply; +} + +Atom +ag_task_get_property (AgGetPropertyTask *task) +{ + return task->property; +} + +Window +ag_task_get_window (AgGetPropertyTask *task) +{ + return task->window; +} + +Display* +ag_task_get_display (AgGetPropertyTask *task) +{ + return task->dd->display; +} + +AgGetPropertyTask* +ag_get_next_completed_task (Display *display) +{ + AgPerDisplayData *dd; + + dd = get_display_data (display, False); + + if (dd == NULL) + return NULL; + +#ifdef DEBUG_SPEW + printf ("%d pending %d completed\n", + dd->n_tasks_pending, + dd->n_tasks_completed); +#endif + + return (AgGetPropertyTask*) dd->completed_tasks; +} + +void* +ag_Xmalloc (unsigned long bytes) +{ + return (void*) Xmalloc (bytes); +} + +void* +ag_Xmalloc0 (unsigned long bytes) +{ + return (void*) Xcalloc (bytes, 1); +} diff --git a/src/core/async-getprop.h b/src/core/async-getprop.h new file mode 100644 index 00000000..c857e930 --- /dev/null +++ b/src/core/async-getprop.h @@ -0,0 +1,67 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Asynchronous X property getting hack */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation. + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of The Open Group shall not be + * used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization from The Open Group. + */ + +#ifndef ASYNC_GETPROP_H +#define ASYNC_GETPROP_H + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +typedef struct _AgGetPropertyTask AgGetPropertyTask; + +AgGetPropertyTask* ag_task_create (Display *display, + Window window, + Atom property, + long offset, + long length, + Bool delete, + Atom req_type); +Status ag_task_get_reply_and_free (AgGetPropertyTask *task, + Atom *actual_type, + int *actual_format, + unsigned long *nitems, + unsigned long *bytesafter, + unsigned char **prop); + +Bool ag_task_have_reply (AgGetPropertyTask *task); +Atom ag_task_get_property (AgGetPropertyTask *task); +Window ag_task_get_window (AgGetPropertyTask *task); +Display* ag_task_get_display (AgGetPropertyTask *task); + +AgGetPropertyTask* ag_get_next_completed_task (Display *display); + +/* so other headers don't have to include internal Xlib goo */ +void* ag_Xmalloc (unsigned long bytes); +void* ag_Xmalloc0 (unsigned long bytes); + +#endif + + + + diff --git a/src/core/atomnames.h b/src/core/atomnames.h new file mode 100644 index 00000000..551482ac --- /dev/null +++ b/src/core/atomnames.h @@ -0,0 +1,166 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002, 2003, 2004 Red Hat, Inc. + * Copyright (C) 2003, 2004 Rob Adams + * Copyright (C) 2004-2006 Elijah Newren + * Copyright (C) 2008 Thomas Thurman + * + * 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. + */ + +/** + * \file atomnames.h A list of atom names. + * + * This is a list of the names of all the X atoms that Marco uses. + * Each is wrapped in a macro "item()" which is undefined here; the + * idea is that when you need to make a big list of all the X atoms, + * you can define item(), include this file, and then undefine it + * again. + * + * If you also define EWMH_ATOMS_ONLY then you will only get _NET_WM_* + * atoms rather than all of them. + */ + +#ifndef item +#error "item(x) must be defined when you include atomnames.h" +#endif + +#ifndef EWMH_ATOMS_ONLY + +item(WM_PROTOCOLS) +item(WM_TAKE_FOCUS) +item(WM_DELETE_WINDOW) +item(WM_STATE) +item(_MOTIF_WM_HINTS) +item(WM_CHANGE_STATE) +item(SM_CLIENT_ID) +item(WM_CLIENT_LEADER) +item(WM_WINDOW_ROLE) +item(UTF8_STRING) +item(WM_ICON_SIZE) +item(_KWM_WIN_ICON) +item(_MARCO_RESTART_MESSAGE) +item(_MARCO_RELOAD_THEME_MESSAGE) +item(_MARCO_SET_KEYBINDINGS_MESSAGE) +item(_MARCO_TOGGLE_VERBOSE) +item(_MATE_PANEL_ACTION) +item(_MATE_PANEL_ACTION_MAIN_MENU) +item(_MATE_PANEL_ACTION_RUN_DIALOG) +item(_MARCO_SENTINEL) +item(_MARCO_VERSION) +item(WM_CLIENT_MACHINE) +item(MANAGER) +item(TARGETS) +item(MULTIPLE) +item(TIMESTAMP) +item(VERSION) +item(ATOM_PAIR) + +/* Oddities: These are used, and we need atoms for them, + * but when we need all _NET_WM hints (i.e. when we're making + * lists of which _NET_WM hints we support in order to advertise + * it) we haven't historically listed them. I don't know what + * the reason for this is. It may be a bug. + */ +item(_NET_WM_SYNC_REQUEST) +item(_NET_WM_SYNC_REQUEST_COUNTER) +item(_NET_WM_VISIBLE_NAME) +item(_NET_WM_VISIBLE_ICON_NAME) +item(_NET_SUPPORTING_WM_CHECK) + +/* But I suppose it's quite reasonable not to advertise using + * _NET_SUPPORTED that we support _NET_SUPPORTED :) + */ +item(_NET_SUPPORTED) + +#endif /* !EWMH_ATOMS_ONLY */ + +/**************************************************************************/ + +item(_NET_WM_NAME) +item(_NET_CLOSE_WINDOW) +item(_NET_WM_STATE) +item(_NET_WM_STATE_SHADED) +item(_NET_WM_STATE_MAXIMIZED_HORZ) +item(_NET_WM_STATE_MAXIMIZED_VERT) +item(_NET_WM_DESKTOP) +item(_NET_NUMBER_OF_DESKTOPS) +item(_NET_CURRENT_DESKTOP) +item(_NET_WM_WINDOW_TYPE) +item(_NET_WM_WINDOW_TYPE_DESKTOP) +item(_NET_WM_WINDOW_TYPE_DOCK) +item(_NET_WM_WINDOW_TYPE_TOOLBAR) +item(_NET_WM_WINDOW_TYPE_MENU) +item(_NET_WM_WINDOW_TYPE_DIALOG) +item(_NET_WM_WINDOW_TYPE_NORMAL) +item(_NET_WM_STATE_MODAL) +item(_NET_CLIENT_LIST) +item(_NET_CLIENT_LIST_STACKING) +item(_NET_WM_STATE_SKIP_TASKBAR) +item(_NET_WM_STATE_SKIP_PAGER) +item(_NET_WM_ICON_NAME) +item(_NET_WM_ICON) +item(_NET_WM_ICON_GEOMETRY) +item(_NET_WM_MOVERESIZE) +item(_NET_ACTIVE_WINDOW) +item(_NET_WM_STRUT) +item(_NET_WM_STATE_HIDDEN) +item(_NET_WM_WINDOW_TYPE_UTILITY) +item(_NET_WM_WINDOW_TYPE_SPLASH) +item(_NET_WM_STATE_FULLSCREEN) +item(_NET_WM_PING) +item(_NET_WM_PID) +item(_NET_WORKAREA) +item(_NET_SHOWING_DESKTOP) +item(_NET_DESKTOP_LAYOUT) +item(_NET_DESKTOP_NAMES) +item(_NET_WM_ALLOWED_ACTIONS) +item(_NET_WM_ACTION_MOVE) +item(_NET_WM_ACTION_RESIZE) +item(_NET_WM_ACTION_SHADE) +item(_NET_WM_ACTION_STICK) +item(_NET_WM_ACTION_MAXIMIZE_HORZ) +item(_NET_WM_ACTION_MAXIMIZE_VERT) +item(_NET_WM_ACTION_CHANGE_DESKTOP) +item(_NET_WM_ACTION_CLOSE) +item(_NET_WM_STATE_ABOVE) +item(_NET_WM_STATE_BELOW) +item(_NET_STARTUP_ID) +item(_NET_WM_STRUT_PARTIAL) +item(_NET_WM_ACTION_FULLSCREEN) +item(_NET_WM_ACTION_MINIMIZE) +item(_NET_FRAME_EXTENTS) +item(_NET_REQUEST_FRAME_EXTENTS) +item(_NET_WM_USER_TIME) +item(_NET_WM_STATE_DEMANDS_ATTENTION) +item(_NET_MOVERESIZE_WINDOW) +item(_NET_DESKTOP_GEOMETRY) +item(_NET_DESKTOP_VIEWPORT) +item(_NET_WM_USER_TIME_WINDOW) +item(_NET_WM_ACTION_ABOVE) +item(_NET_WM_ACTION_BELOW) +item(_NET_WM_STATE_STICKY) +item(_NET_WM_FULLSCREEN_MONITORS) + +#if 0 +/* We apparently never use: */ +/* item(_NET_RESTACK_WINDOW) */ +#endif + +/* eof atomnames.h */ + diff --git a/src/core/bell.c b/src/core/bell.c new file mode 100644 index 00000000..560b3569 --- /dev/null +++ b/src/core/bell.c @@ -0,0 +1,397 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco visual bell */ + +/* + * Copyright (C) 2002 Sun Microsystems Inc. + * Copyright (C) 2005, 2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file bell.c Ring the bell or flash the screen + * + * Sometimes, X programs "ring the bell", whatever that means. Marco lets + * the user configure the bell to be audible or visible (aka visual), and + * if it's visual it can be configured to be frame-flash or fullscreen-flash. + * We never get told about audible bells; X handles them just fine by itself. + * + * Visual bells come in at meta_bell_notify(), which checks we are actually + * in visual mode and calls through to bell_visual_notify(). That + * function then checks what kind of visual flash you like, and calls either + * bell_flash_fullscreen()-- which calls bell_flash_screen() to do + * its work-- or bell_flash_frame(), which flashes the focussed window + * using bell_flash_window_frame(), unless there is no such window, in + * which case it flashes the screen instead. bell_flash_window_frame() + * flashes the frame and calls bell_unflash_frame() as a timeout to + * remove the flash. + * + * The visual bell was the result of a discussion in Bugzilla here: + * <http://bugzilla.gnome.org/show_bug.cgi?id=99886>. + * + * Several of the functions in this file are ifdeffed out entirely if we are + * found not to have the XKB extension, which is required to do these clever + * things with bells; some others are entirely no-ops in that case. + */ + +#include <config.h> +#include "bell.h" +#include "screen-private.h" +#include "prefs.h" +#include <canberra-gtk.h> + +/** + * Flashes one entire screen. This is done by making a window the size of the + * whole screen (or reusing the old one, if it's still around), mapping it, + * painting it white and then black, and then unmapping it. We set saveunder so + * that all the windows behind it come back immediately. + * + * Unlike frame flashes, we don't do fullscreen flashes with a timeout; rather, + * we do them in one go, because we don't have to rely on the theme code + * redrawing the frame for us in order to do the flash. + * + * \param display The display which owns the screen (rather redundant) + * \param screen The screen to flash + * + * \bug The way I read it, this appears not to do the flash + * the first time we flash a particular display. Am I wrong? + * + * \bug This appears to destroy our current XSync status. + */ +static void +bell_flash_screen (MetaDisplay *display, + MetaScreen *screen) +{ + Window root = screen->xroot; + int width = screen->rect.width; + int height = screen->rect.height; + + if (screen->flash_window == None) + { + Visual *visual = (Visual *)CopyFromParent; + XSetWindowAttributes xswa; + int depth = CopyFromParent; + xswa.save_under = True; + xswa.override_redirect = True; + /* + * TODO: use XGetVisualInfo and determine which is an + * overlay, if one is present, and use the Overlay visual + * for this window (for performance reasons). + * Not sure how to tell this yet... + */ + screen->flash_window = XCreateWindow (display->xdisplay, root, + 0, 0, width, height, + 0, depth, + InputOutput, + visual, + /* note: XSun doesn't like SaveUnder here */ + CWSaveUnder | CWOverrideRedirect, + &xswa); + XSelectInput (display->xdisplay, screen->flash_window, ExposureMask); + XMapWindow (display->xdisplay, screen->flash_window); + XSync (display->xdisplay, False); + XFlush (display->xdisplay); + XUnmapWindow (display->xdisplay, screen->flash_window); + } + else + { + /* just draw something in the window */ + GC gc = XCreateGC (display->xdisplay, screen->flash_window, 0, NULL); + XMapWindow (display->xdisplay, screen->flash_window); + XSetForeground (display->xdisplay, gc, + WhitePixel (display->xdisplay, + XScreenNumberOfScreen (screen->xscreen))); + XFillRectangle (display->xdisplay, screen->flash_window, gc, + 0, 0, width, height); + XSetForeground (display->xdisplay, gc, + BlackPixel (display->xdisplay, + XScreenNumberOfScreen (screen->xscreen))); + XFillRectangle (display->xdisplay, screen->flash_window, gc, + 0, 0, width, height); + XFlush (display->xdisplay); + XSync (display->xdisplay, False); + XUnmapWindow (display->xdisplay, screen->flash_window); + XFreeGC (display->xdisplay, gc); + } + + if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK && + !display->mouse_mode) + meta_display_increment_focus_sentinel (display); + XFlush (display->xdisplay); +} + +/** + * Flashes one screen, or all screens, in response to a bell event. + * If the event is on a particular window, flash the screen that + * window is on. Otherwise, flash every screen on this display. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param display The display the event came in on + * \param xkb_ev The bell event + */ +#ifdef HAVE_XKB +static void +bell_flash_fullscreen (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + XkbBellNotifyEvent *xkb_bell_ev = (XkbBellNotifyEvent *) xkb_ev; + MetaScreen *screen; + + g_assert (xkb_ev->xkb_type == XkbBellNotify); + if (xkb_bell_ev->window != None) + { + screen = meta_display_screen_for_xwindow (display, xkb_bell_ev->window); + if (screen) + bell_flash_screen (display, screen); + } + else + { + GSList *screen_list = display->screens; + while (screen_list) + { + screen = (MetaScreen *) screen_list->data; + bell_flash_screen (display, screen); + screen_list = screen_list->next; + } + } +} + +/** + * Makes a frame be not flashed; this is the timeout half of + * bell_flash_window_frame(). This is done simply by clearing the + * flash flag and queuing a redraw of the frame. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param data The frame to unflash, cast to a gpointer so it can go into + * a callback function. + * \return Always FALSE, so we don't get called again. + * + * \bug This is the parallel to bell_flash_window_frame(), so it should + * really be called meta_bell_unflash_window_frame(). + */ +static gboolean +bell_unflash_frame (gpointer data) +{ + MetaFrame *frame = (MetaFrame *) data; + frame->is_flashing = 0; + meta_frame_queue_draw (frame); + return FALSE; +} + +/** + * Makes a frame flash and then return to normal shortly afterwards. + * This is done by setting a flag so that the theme + * code will temporarily draw the frame as focussed if it's unfocussed and + * vice versa, and then queueing a redraw. Lastly, we create a timeout so + * that the flag can be unset and the frame re-redrawn. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param window The window to flash + */ +static void +bell_flash_window_frame (MetaWindow *window) +{ + g_assert (window->frame != NULL); + window->frame->is_flashing = 1; + meta_frame_queue_draw (window->frame); + g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 100, + bell_unflash_frame, window->frame, NULL); +} + +/** + * Flashes the frame of the focussed window. If there is no focussed window, + * flashes the screen. + * + * \param display The display the bell event came in on + * \param xkb_ev The bell event we just received + */ +static void +bell_flash_frame (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent *) xkb_ev; + MetaWindow *window; + + g_assert (xkb_ev->xkb_type == XkbBellNotify); + window = meta_display_lookup_x_window (display, xkb_bell_event->window); + if (!window && (display->focus_window)) + { + window = display->focus_window; + } + if (window && window->frame) + { + bell_flash_window_frame (window); + } + else /* revert to fullscreen flash if there's no focussed window */ + { + bell_flash_fullscreen (display, xkb_ev); + } +} + +/** + * Gives the user some kind of visual bell substitute, in response to a + * bell event. What this is depends on the "visual bell type" pref. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param display The display the bell event came in on + * \param xkb_ev The bell event we just received + * + * \bug This should be merged with meta_bell_notify(). + */ +static void +bell_visual_notify (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + switch (meta_prefs_get_visual_bell_type ()) + { + case META_VISUAL_BELL_FULLSCREEN_FLASH: + bell_flash_fullscreen (display, xkb_ev); + break; + case META_VISUAL_BELL_FRAME_FLASH: + bell_flash_frame (display, xkb_ev); /* does nothing yet */ + break; + case META_VISUAL_BELL_INVALID: + /* do nothing */ + break; + } +} + +void +meta_bell_notify (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + /* flash something */ + if (meta_prefs_get_visual_bell ()) + bell_visual_notify (display, xkb_ev); + + if (meta_prefs_bell_is_audible ()) + { + ca_proplist *p; + XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent*) xkb_ev; + MetaWindow *window; + int res; + + ca_proplist_create (&p); + ca_proplist_sets (p, CA_PROP_EVENT_ID, "bell-window-system"); + ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION, _("Bell event")); + ca_proplist_sets (p, CA_PROP_CANBERRA_CACHE_CONTROL, "permanent"); + + window = meta_display_lookup_x_window (display, xkb_bell_event->window); + if (!window && (display->focus_window) && (display->focus_window->frame)) + window = display->focus_window; + + if (window) + { + ca_proplist_sets (p, CA_PROP_WINDOW_NAME, window->title); + ca_proplist_setf (p, CA_PROP_WINDOW_X11_XID, "%lu", (unsigned long)window->xwindow); + ca_proplist_sets (p, CA_PROP_APPLICATION_NAME, window->res_name); + ca_proplist_setf (p, CA_PROP_APPLICATION_PROCESS_ID, "%d", window->net_wm_pid); + } + + /* First, we try to play a real sound ... */ + res = ca_context_play_full (ca_gtk_context_get (), 1, p, NULL, NULL); + + ca_proplist_destroy (p); + + if (res != CA_SUCCESS && res != CA_ERROR_DISABLED) + { + /* ...and in case that failed we use the classic X11 bell. */ + XkbForceDeviceBell (display->xdisplay, + xkb_bell_event->device, + xkb_bell_event->bell_class, + xkb_bell_event->bell_id, + xkb_bell_event->percent); + } + } +} +#endif /* HAVE_XKB */ + +void +meta_bell_set_audible (MetaDisplay *display, gboolean audible) +{ +} + +gboolean +meta_bell_init (MetaDisplay *display) +{ +#ifdef HAVE_XKB + int xkb_base_error_type, xkb_opcode; + + if (!XkbQueryExtension (display->xdisplay, &xkb_opcode, + &display->xkb_base_event_type, + &xkb_base_error_type, + NULL, NULL)) + { + display->xkb_base_event_type = -1; + g_message ("could not find XKB extension."); + return FALSE; + } + else + { + unsigned int mask = XkbBellNotifyMask; + gboolean visual_bell_auto_reset = FALSE; + /* TRUE if and when non-broken version is available */ + XkbSelectEvents (display->xdisplay, + XkbUseCoreKbd, + XkbBellNotifyMask, + XkbBellNotifyMask); + XkbChangeEnabledControls (display->xdisplay, + XkbUseCoreKbd, + XkbAudibleBellMask, + 0); + if (visual_bell_auto_reset) { + XkbSetAutoResetControls (display->xdisplay, + XkbAudibleBellMask, + &mask, + &mask); + } + return TRUE; + } +#endif + return FALSE; +} + +void +meta_bell_shutdown (MetaDisplay *display) +{ +#ifdef HAVE_XKB + /* TODO: persist initial bell state in display, reset here */ + XkbChangeEnabledControls (display->xdisplay, + XkbUseCoreKbd, + XkbAudibleBellMask, + XkbAudibleBellMask); +#endif +} + +/** + * Deals with a frame being destroyed. This is important because if we're + * using a visual bell, we might be flashing the edges of the frame, and + * so we'd have a timeout function waiting ready to un-flash them. If the + * frame's going away, we can tell the timeout not to bother. + * + * \param frame The frame which is being destroyed + */ +void +meta_bell_notify_frame_destroy (MetaFrame *frame) +{ + if (frame->is_flashing) + g_source_remove_by_funcs_user_data (&g_timeout_funcs, frame); +} diff --git a/src/core/bell.h b/src/core/bell.h new file mode 100644 index 00000000..95e3ea9e --- /dev/null +++ b/src/core/bell.h @@ -0,0 +1,108 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file bell.h Ring the bell or flash the screen + * + * Sometimes, X programs "ring the bell", whatever that means. Marco lets + * the user configure the bell to be audible or visible (aka visual), and + * if it's visual it can be configured to be frame-flash or fullscreen-flash. + * We never get told about audible bells; X handles them just fine by itself. + * + * The visual bell was the result of a discussion in Bugzilla here: + * <http://bugzilla.gnome.org/show_bug.cgi?id=99886>. + */ + +/* + * Copyright (C) 2002 Sun Microsystems Inc. + * + * 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 <X11/Xlib.h> +#ifdef HAVE_XKB +#include <X11/XKBlib.h> +#endif +#include "display-private.h" +#include "frame-private.h" + +#ifdef HAVE_XKB +/** + * Gives the user some kind of visual bell; in fact, this is our response + * to any kind of bell request, but we set it up so that we only get + * notified about visual bells, and X deals with audible ones. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param display The display the bell event came in on + * \param xkb_ev The bell event we just received + */ +void meta_bell_notify (MetaDisplay *display, XkbAnyEvent *xkb_ev); +#endif + +/** + * Turns the bell to audible or visual. This tells X what to do, but + * not Marco; you will need to set the "visual bell" pref for that. + * + * If the configure script found we had no XKB, this is a no-op. + * + * \param display The display we're configuring + * \param audible True for an audible bell, false for a visual bell + */ +void meta_bell_set_audible (MetaDisplay *display, gboolean audible); + +/** + * Initialises the bell subsystem. This involves intialising + * XKB (which, despite being a keyboard extension, is the + * place to look for bell notifications), then asking it + * to send us bell notifications, and then also switching + * off the audible bell if we're using a visual one ourselves. + * + * Unlike most X extensions we use, we only initialise XKB here + * (rather than in main()). It's possible that XKB is not + * installed at all, but if that was known at build time + * we will have HAVE_XKB undefined, which will cause this + * function to be a no-op. + * + * \param display The display which is opening + * + * \bug There is a line of code that's never run that tells + * XKB to reset the bell status after we quit. Bill H said + * (<http://bugzilla.gnome.org/show_bug.cgi?id=99886#c12>) + * that XFree86's implementation is broken so we shouldn't + * call it, but that was in 2002. Is it working now? + */ +gboolean meta_bell_init (MetaDisplay *display); + +/** + * Shuts down the bell subsystem. + * + * \param display The display which is closing + * + * \bug This is never called! If we had XkbSetAutoResetControls + * enabled in meta_bell_init(), this wouldn't be a problem, but + * we don't. + */ +void meta_bell_shutdown (MetaDisplay *display); + +/** + * Deals with a frame being destroyed. This is important because if we're + * using a visual bell, we might be flashing the edges of the frame, and + * so we'd have a timeout function waiting ready to un-flash them. If the + * frame's going away, we can tell the timeout not to bother. + * + * \param frame The frame which is being destroyed + */ +void meta_bell_notify_frame_destroy (MetaFrame *frame); diff --git a/src/core/boxes.c b/src/core/boxes.c new file mode 100644 index 00000000..2ae5f06a --- /dev/null +++ b/src/core/boxes.c @@ -0,0 +1,1926 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Simple box operations */ + +/* + * Copyright (C) 2005, 2006 Elijah Newren + * [meta_rectangle_intersect() is copyright the GTK+ Team according to Havoc, + * see gdkrectangle.c. As far as Havoc knows, he probably wrote + * meta_rectangle_equal(), and I'm guessing it's (C) Red Hat. So...] + * Copyright (C) 1995-2000 GTK+ Team + * Copyright (C) 2002 Red Hat, Inc. + * + * 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 "boxes.h" +#include "util.h" +#include <X11/Xutil.h> /* Just for the definition of the various gravities */ + +char* +meta_rectangle_to_string (const MetaRectangle *rect, + char *output) +{ + /* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit. + * Should be more than enough space. Note that of this space, the + * trailing \0 will be overwritten for all but the last rectangle. + */ + g_snprintf (output, RECT_LENGTH, "%d,%d +%d,%d", + rect->x, rect->y, rect->width, rect->height); + + return output; +} + +char* +meta_rectangle_region_to_string (GList *region, + const char *separator_string, + char *output) +{ + /* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5 + * for each digit. Should be more than enough space. Note that of this + * space, the trailing \0 will be overwritten for all but the last + * rectangle. + */ + char rect_string[RECT_LENGTH]; + + GList *tmp = region; + char *cur = output; + + if (region == NULL) + g_snprintf (output, 10, "(EMPTY)"); + + while (tmp) + { + MetaRectangle *rect = tmp->data; + g_snprintf (rect_string, RECT_LENGTH, "[%d,%d +%d,%d]", + rect->x, rect->y, rect->width, rect->height); + cur = g_stpcpy (cur, rect_string); + tmp = tmp->next; + if (tmp) + cur = g_stpcpy (cur, separator_string); + } + + return output; +} + +char* +meta_rectangle_edge_to_string (const MetaEdge *edge, + char *output) +{ + /* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit. + * Should be more than enough space. Note that of this space, the + * trailing \0 will be overwritten for all but the last rectangle. + * + * Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and + * 2 more spaces, for a total of 10 more. + */ + g_snprintf (output, EDGE_LENGTH, "[%d,%d +%d,%d], %2d, %2d", + edge->rect.x, edge->rect.y, edge->rect.width, edge->rect.height, + edge->side_type, edge->edge_type); + + return output; +} + +char* +meta_rectangle_edge_list_to_string (GList *edge_list, + const char *separator_string, + char *output) +{ + /* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5 for + * each digit. Should be more than enough space. Note that of this + * space, the trailing \0 will be overwritten for all but the last + * rectangle. + * + * Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and + * 2 more spaces, for a total of 10 more. + */ + char rect_string[EDGE_LENGTH]; + + char *cur = output; + GList *tmp = edge_list; + + if (edge_list == NULL) + g_snprintf (output, 10, "(EMPTY)"); + + while (tmp) + { + MetaEdge *edge = tmp->data; + MetaRectangle *rect = &edge->rect; + g_snprintf (rect_string, EDGE_LENGTH, "([%d,%d +%d,%d], %2d, %2d)", + rect->x, rect->y, rect->width, rect->height, + edge->side_type, edge->edge_type); + cur = g_stpcpy (cur, rect_string); + tmp = tmp->next; + if (tmp) + cur = g_stpcpy (cur, separator_string); + } + + return output; +} + +MetaRectangle +meta_rect (int x, int y, int width, int height) +{ + MetaRectangle temporary; + temporary.x = x; + temporary.y = y; + temporary.width = width; + temporary.height = height; + + return temporary; +} + +int +meta_rectangle_area (const MetaRectangle *rect) +{ + g_return_val_if_fail (rect != NULL, 0); + return rect->width * rect->height; +} + +gboolean +meta_rectangle_intersect (const MetaRectangle *src1, + const MetaRectangle *src2, + MetaRectangle *dest) +{ + int dest_x, dest_y; + int dest_w, dest_h; + int return_val; + + g_return_val_if_fail (src1 != NULL, FALSE); + g_return_val_if_fail (src2 != NULL, FALSE); + g_return_val_if_fail (dest != NULL, FALSE); + + return_val = FALSE; + + dest_x = MAX (src1->x, src2->x); + dest_y = MAX (src1->y, src2->y); + dest_w = MIN (src1->x + src1->width, src2->x + src2->width) - dest_x; + dest_h = MIN (src1->y + src1->height, src2->y + src2->height) - dest_y; + + if (dest_w > 0 && dest_h > 0) + { + dest->x = dest_x; + dest->y = dest_y; + dest->width = dest_w; + dest->height = dest_h; + return_val = TRUE; + } + else + { + dest->width = 0; + dest->height = 0; + } + + return return_val; +} + +gboolean +meta_rectangle_equal (const MetaRectangle *src1, + const MetaRectangle *src2) +{ + return ((src1->x == src2->x) && + (src1->y == src2->y) && + (src1->width == src2->width) && + (src1->height == src2->height)); +} + +void +meta_rectangle_union (const MetaRectangle *rect1, + const MetaRectangle *rect2, + MetaRectangle *dest) +{ + int dest_x, dest_y; + int dest_w, dest_h; + + dest_x = rect1->x; + dest_y = rect1->y; + dest_w = rect1->width; + dest_h = rect1->height; + + if (rect2->x < dest_x) + { + dest_w += dest_x - rect2->x; + dest_x = rect2->x; + } + if (rect2->y < dest_y) + { + dest_h += dest_y - rect2->y; + dest_y = rect2->y; + } + if (rect2->x + rect2->width > dest_x + dest_w) + dest_w = rect2->x + rect2->width - dest_x; + if (rect2->y + rect2->height > dest_y + dest_h) + dest_h = rect2->y + rect2->height - dest_y; + + dest->x = dest_x; + dest->y = dest_y; + dest->width = dest_w; + dest->height = dest_h; +} + +gboolean +meta_rectangle_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2) +{ + g_return_val_if_fail (rect1 != NULL, FALSE); + g_return_val_if_fail (rect2 != NULL, FALSE); + + return !((rect1->x + rect1->width <= rect2->x) || + (rect2->x + rect2->width <= rect1->x) || + (rect1->y + rect1->height <= rect2->y) || + (rect2->y + rect2->height <= rect1->y)); +} + +gboolean +meta_rectangle_vert_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2) +{ + return (rect1->y < rect2->y + rect2->height && + rect2->y < rect1->y + rect1->height); +} + +gboolean +meta_rectangle_horiz_overlap (const MetaRectangle *rect1, + const MetaRectangle *rect2) +{ + return (rect1->x < rect2->x + rect2->width && + rect2->x < rect1->x + rect1->width); +} + +gboolean +meta_rectangle_could_fit_rect (const MetaRectangle *outer_rect, + const MetaRectangle *inner_rect) +{ + return (outer_rect->width >= inner_rect->width && + outer_rect->height >= inner_rect->height); +} + +gboolean +meta_rectangle_contains_rect (const MetaRectangle *outer_rect, + const MetaRectangle *inner_rect) +{ + return + inner_rect->x >= outer_rect->x && + inner_rect->y >= outer_rect->y && + inner_rect->x + inner_rect->width <= outer_rect->x + outer_rect->width && + inner_rect->y + inner_rect->height <= outer_rect->y + outer_rect->height; +} + +void +meta_rectangle_resize_with_gravity (const MetaRectangle *old_rect, + MetaRectangle *rect, + int gravity, + int new_width, + int new_height) +{ + /* FIXME: I'm too deep into this to know whether the below comment is + * still clear or not now that I've moved it out of constraints.c. + * boxes.h has a good comment, but I'm not sure if the below info is also + * helpful on top of that (or whether it has superfluous info). + */ + + /* These formulas may look overly simplistic at first but you can work + * everything out with a left_frame_with, right_frame_width, + * border_width, and old and new client area widths (instead of old total + * width and new total width) and you come up with the same formulas. + * + * Also, note that the reason we can treat NorthWestGravity and + * StaticGravity the same is because we're not given a location at + * which to place the window--the window was already placed + * appropriately before. So, NorthWestGravity for this function + * means to just leave the upper left corner of the outer window + * where it already is, and StaticGravity for this function means to + * just leave the upper left corner of the inner window where it + * already is. But leaving either of those two corners where they + * already are will ensure that the other corner is fixed as well + * (since frame size doesn't change)--thus making the two + * equivalent. + */ + + /* First, the x direction */ + int adjust = 0; + switch (gravity) + { + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + rect->x = old_rect->x; + break; + + case NorthGravity: + case CenterGravity: + case SouthGravity: + /* FIXME: Needing to adjust new_width kind of sucks, but not doing so + * would cause drift. + */ + new_width -= (old_rect->width - new_width) % 2; + rect->x = old_rect->x + (old_rect->width - new_width)/2; + break; + + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + rect->x = old_rect->x + (old_rect->width - new_width); + break; + + case StaticGravity: + default: + rect->x = old_rect->x; + break; + } + rect->width = new_width; + + /* Next, the y direction */ + adjust = 0; + switch (gravity) + { + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + rect->y = old_rect->y; + break; + + case WestGravity: + case CenterGravity: + case EastGravity: + /* FIXME: Needing to adjust new_height kind of sucks, but not doing so + * would cause drift. + */ + new_height -= (old_rect->height - new_height) % 2; + rect->y = old_rect->y + (old_rect->height - new_height)/2; + break; + + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + rect->y = old_rect->y + (old_rect->height - new_height); + break; + + case StaticGravity: + default: + rect->y = old_rect->y; + break; + } + rect->height = new_height; +} + +/* Not so simple helper function for get_minimal_spanning_set_for_region() */ +static GList* +merge_spanning_rects_in_region (GList *region) +{ + /* NOTE FOR ANY OPTIMIZATION PEOPLE OUT THERE: Please see the + * documentation of get_minimal_spanning_set_for_region() for performance + * considerations that also apply to this function. + */ + + GList* compare; + compare = region; + + if (region == NULL) + { + meta_warning ("Region to merge was empty! Either you have a some " + "pathological STRUT list or there's a bug somewhere!\n"); + return NULL; + } + + while (compare && compare->next) + { + MetaRectangle *a = compare->data; + GList *other = compare->next; + + g_assert (a->width > 0 && a->height > 0); + + while (other) + { + MetaRectangle *b = other->data; + GList *delete_me = NULL; + + g_assert (b->width > 0 && b->height > 0); + + /* If a contains b, just remove b */ + if (meta_rectangle_contains_rect (a, b)) + { + delete_me = other; + } + /* If b contains a, just remove a */ + else if (meta_rectangle_contains_rect (a, b)) + { + delete_me = compare; + } + /* If a and b might be mergeable horizontally */ + else if (a->y == b->y && a->height == b->height) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + } + /* If a and b are adjacent */ + else if (a->x + a->width == b->x || a->x == b->x + b->width) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + } + } + /* If a and b might be mergeable vertically */ + else if (a->x == b->x && a->width == b->width) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + } + /* If a and b are adjacent */ + else if (a->y + a->height == b->y || a->y == b->y + b->height) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + } + } + + other = other->next; + + /* Delete any rectangle in the list that is no longer wanted */ + if (delete_me != NULL) + { + /* Deleting the rect we compare others to is a little tricker */ + if (compare == delete_me) + { + compare = compare->next; + other = compare->next; + a = compare->data; + } + + /* Okay, we can free it now */ + g_free (delete_me->data); + region = g_list_delete_link (region, delete_me); + } + + } + + compare = compare->next; + } + + return region; +} + +/* Simple helper function for get_minimal_spanning_set_for_region()... */ +static gint +compare_rect_areas (gconstpointer a, gconstpointer b) +{ + const MetaRectangle *a_rect = (gconstpointer) a; + const MetaRectangle *b_rect = (gconstpointer) b; + + int a_area = meta_rectangle_area (a_rect); + int b_area = meta_rectangle_area (b_rect); + + return b_area - a_area; /* positive ret value denotes b > a, ... */ +} + +/* This function is trying to find a "minimal spanning set (of rectangles)" + * for a given region. + * + * The region is given by taking basic_rect, then removing the areas + * covered by all the rectangles in the all_struts list, and then expanding + * the resulting region by the given number of pixels in each direction. + * + * A "minimal spanning set (of rectangles)" is the best name I could come + * up with for the concept I had in mind. Basically, for a given region, I + * want a set of rectangles with the property that a window is contained in + * the region if and only if it is contained within at least one of the + * rectangles. + * + * The GList* returned will be a list of (allocated) MetaRectangles. + * The list will need to be freed by calling + * meta_rectangle_free_spanning_set() on it (or by manually + * implementing that function...) + */ +GList* +meta_rectangle_get_minimal_spanning_set_for_region ( + const MetaRectangle *basic_rect, + const GSList *all_struts) +{ + /* NOTE FOR OPTIMIZERS: This function *might* be somewhat slow, + * especially due to the call to merge_spanning_rects_in_region() (which + * is O(n^2) where n is the size of the list generated in this function). + * This is made more onerous due to the fact that it involves a fair + * number of memory allocation and deallocation calls. However, n is 1 + * for default installations of Mate (because partial struts aren't used + * by default and only partial struts increase the size of the spanning + * set generated). With one partial strut, n will be 2 or 3. With 2 + * partial struts, n will probably be 4 or 5. So, n probably isn't large + * enough to make this worth bothering. Further, it is only called from + * workspace.c:ensure_work_areas_validated (at least as of the time of + * writing this comment), which in turn should only be called if the + * strut list changes or the screen or xinerama size changes. If it ever + * does show up on profiles (most likely because people start using + * ridiculously huge numbers of partial struts), possible optimizations + * include: + * + * (1) rewrite merge_spanning_rects_in_region() to be O(n) or O(nlogn). + * I'm not totally sure it's possible, but with a couple copies of + * the list and sorting them appropriately, I believe it might be. + * (2) only call merge_spanning_rects_in_region() with a subset of the + * full list of rectangles. I believe from some of my preliminary + * debugging and thinking about it that it is possible to figure out + * apriori groups of rectangles which are only merge candidates with + * each other. (See testboxes.c:get_screen_region() when which==2 + * and track the steps of this function carefully to see what gave + * me the hint that this might work) + * (3) figure out how to avoid merge_spanning_rects_in_region(). I think + * it might be possible to modify this function to make that + * possible, and I spent just a little while thinking about it, but n + * wasn't large enough to convince me to care yet. + * (4) Some of the stuff Rob mentioned at http://mail.gnome.org/archives\ + * /marco-devel-list/2005-November/msg00028.html. (Sorry for the + * URL splitting.) + */ + + GList *ret; + GList *tmp_list; + const GSList *strut_iter; + MetaRectangle *temp_rect; + + /* The algorithm is basically as follows: + * Initialize rectangle_set to basic_rect + * Foreach strut: + * Foreach rectangle in rectangle_set: + * - Split the rectangle into new rectangles that don't overlap the + * strut (but which are as big as possible otherwise) + * - Remove the old (pre-split) rectangle from the rectangle_set, + * and replace it with the new rectangles generated from the + * splitting + */ + + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *basic_rect; + ret = g_list_prepend (NULL, temp_rect); + + strut_iter = all_struts; + for (strut_iter = all_struts; strut_iter; strut_iter = strut_iter->next) + { + GList *rect_iter; + MetaRectangle *strut_rect = &((MetaStrut*)strut_iter->data)->rect; + + tmp_list = ret; + ret = NULL; + rect_iter = tmp_list; + while (rect_iter) + { + MetaRectangle *rect = (MetaRectangle*) rect_iter->data; + if (!meta_rectangle_overlap (rect, strut_rect)) + ret = g_list_prepend (ret, rect); + else + { + /* If there is area in rect left of strut */ + if (BOX_LEFT (*rect) < BOX_LEFT (*strut_rect)) + { + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + temp_rect->width = BOX_LEFT (*strut_rect) - BOX_LEFT (*rect); + ret = g_list_prepend (ret, temp_rect); + } + /* If there is area in rect right of strut */ + if (BOX_RIGHT (*rect) > BOX_RIGHT (*strut_rect)) + { + int new_x; + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + new_x = BOX_RIGHT (*strut_rect); + temp_rect->width = BOX_RIGHT(*rect) - new_x; + temp_rect->x = new_x; + ret = g_list_prepend (ret, temp_rect); + } + /* If there is area in rect above strut */ + if (BOX_TOP (*rect) < BOX_TOP (*strut_rect)) + { + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + temp_rect->height = BOX_TOP (*strut_rect) - BOX_TOP (*rect); + ret = g_list_prepend (ret, temp_rect); + } + /* If there is area in rect below strut */ + if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*strut_rect)) + { + int new_y; + temp_rect = g_new (MetaRectangle, 1); + *temp_rect = *rect; + new_y = BOX_BOTTOM (*strut_rect); + temp_rect->height = BOX_BOTTOM (*rect) - new_y; + temp_rect->y = new_y; + ret = g_list_prepend (ret, temp_rect); + } + g_free (rect); + } + rect_iter = rect_iter->next; + } + g_list_free (tmp_list); + } + + /* Sort by maximal area, just because I feel like it... */ + ret = g_list_sort (ret, compare_rect_areas); + + /* Merge rectangles if possible so that the list really is minimal */ + ret = merge_spanning_rects_in_region (ret); + + return ret; +} + +GList* +meta_rectangle_expand_region (GList *region, + const int left_expand, + const int right_expand, + const int top_expand, + const int bottom_expand) +{ + return meta_rectangle_expand_region_conditionally (region, + left_expand, + right_expand, + top_expand, + bottom_expand, + 0, + 0); +} + +GList* +meta_rectangle_expand_region_conditionally (GList *region, + const int left_expand, + const int right_expand, + const int top_expand, + const int bottom_expand, + const int min_x, + const int min_y) +{ + GList *tmp_list = region; + while (tmp_list) + { + MetaRectangle *rect = (MetaRectangle*) tmp_list->data; + if (rect->width >= min_x) + { + rect->x -= left_expand; + rect->width += (left_expand + right_expand); + } + if (rect->height >= min_y) + { + rect->y -= top_expand; + rect->height += (top_expand + bottom_expand); + } + tmp_list = tmp_list->next; + } + + return region; +} + +void +meta_rectangle_expand_to_avoiding_struts (MetaRectangle *rect, + const MetaRectangle *expand_to, + const MetaDirection direction, + const GSList *all_struts) +{ + const GSList *strut_iter; + + /* If someone wants this function to handle more fine-grained + * direction expanding in the future (e.g. only left, or fully + * horizontal plus upward), feel free. But I'm hard-coding for both + * horizontal directions (exclusive-)or both vertical directions. + */ + g_assert ((direction == META_DIRECTION_HORIZONTAL) ^ + (direction == META_DIRECTION_VERTICAL )); + + if (direction == META_DIRECTION_HORIZONTAL) + { + rect->x = expand_to->x; + rect->width = expand_to->width; + } + else + { + rect->y = expand_to->y; + rect->height = expand_to->height; + } + + + /* Run over all struts */ + for (strut_iter = all_struts; strut_iter; strut_iter = strut_iter->next) + { + MetaStrut *strut = (MetaStrut*) strut_iter->data; + + /* Skip struts that don't overlap */ + if (!meta_rectangle_overlap (&strut->rect, rect)) + continue; + + if (direction == META_DIRECTION_HORIZONTAL) + { + if (strut->side == META_SIDE_LEFT) + { + int offset = BOX_RIGHT(strut->rect) - BOX_LEFT(*rect); + rect->x += offset; + rect->width -= offset; + } + else if (strut->side == META_SIDE_RIGHT) + { + int offset = BOX_RIGHT (*rect) - BOX_LEFT(strut->rect); + rect->width -= offset; + } + /* else ignore the strut */ + } + else /* direction == META_DIRECTION_VERTICAL */ + { + if (strut->side == META_SIDE_TOP) + { + int offset = BOX_BOTTOM(strut->rect) - BOX_TOP(*rect); + rect->y += offset; + rect->height -= offset; + } + else if (strut->side == META_SIDE_BOTTOM) + { + int offset = BOX_BOTTOM(*rect) - BOX_TOP(strut->rect); + rect->height -= offset; + } + /* else ignore the strut */ + } + } /* end loop over struts */ +} /* end meta_rectangle_expand_to_avoiding_struts */ + +void +meta_rectangle_free_list_and_elements (GList *filled_list) +{ + g_list_foreach (filled_list, + (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */ + NULL); + g_list_free (filled_list); +} + +gboolean +meta_rectangle_could_fit_in_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + const GList *temp; + gboolean could_fit; + + temp = spanning_rects; + could_fit = FALSE; + while (!could_fit && temp != NULL) + { + could_fit = could_fit || meta_rectangle_could_fit_rect (temp->data, rect); + temp = temp->next; + } + + return could_fit; +} + +gboolean +meta_rectangle_contained_in_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + const GList *temp; + gboolean contained; + + temp = spanning_rects; + contained = FALSE; + while (!contained && temp != NULL) + { + contained = contained || meta_rectangle_contains_rect (temp->data, rect); + temp = temp->next; + } + + return contained; +} + +gboolean +meta_rectangle_overlaps_with_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + const GList *temp; + gboolean overlaps; + + temp = spanning_rects; + overlaps = FALSE; + while (!overlaps && temp != NULL) + { + overlaps = overlaps || meta_rectangle_overlap (temp->data, rect); + temp = temp->next; + } + + return overlaps; +} + + +void +meta_rectangle_clamp_to_fit_into_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect, + const MetaRectangle *min_size) +{ + const GList *temp; + const MetaRectangle *best_rect = NULL; + int best_overlap = 0; + + /* First, find best rectangle from spanning_rects to which we can clamp + * rect to fit into. + */ + for (temp = spanning_rects; temp; temp = temp->next) + { + MetaRectangle *compare_rect = temp->data; + int maximal_overlap_amount_for_compare; + + /* If x is fixed and the entire width of rect doesn't fit in compare, + * skip this rectangle. + */ + if ((fixed_directions & FIXED_DIRECTION_X) && + (compare_rect->x > rect->x || + compare_rect->x + compare_rect->width < rect->x + rect->width)) + continue; + + /* If y is fixed and the entire height of rect doesn't fit in compare, + * skip this rectangle. + */ + if ((fixed_directions & FIXED_DIRECTION_Y) && + (compare_rect->y > rect->y || + compare_rect->y + compare_rect->height < rect->y + rect->height)) + continue; + + /* If compare can't hold the min_size window, skip this rectangle. */ + if (compare_rect->width < min_size->width || + compare_rect->height < min_size->height) + continue; + + /* Determine maximal overlap amount */ + maximal_overlap_amount_for_compare = + MIN (rect->width, compare_rect->width) * + MIN (rect->height, compare_rect->height); + + /* See if this is the best rect so far */ + if (maximal_overlap_amount_for_compare > best_overlap) + { + best_rect = compare_rect; + best_overlap = maximal_overlap_amount_for_compare; + } + } + + /* Clamp rect appropriately */ + if (best_rect == NULL) + { + meta_warning ("No rect whose size to clamp to found!\n"); + + /* If it doesn't fit, at least make it no bigger than it has to be */ + if (!(fixed_directions & FIXED_DIRECTION_X)) + rect->width = min_size->width; + if (!(fixed_directions & FIXED_DIRECTION_Y)) + rect->height = min_size->height; + } + else + { + rect->width = MIN (rect->width, best_rect->width); + rect->height = MIN (rect->height, best_rect->height); + } +} + +void +meta_rectangle_clip_to_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect) +{ + const GList *temp; + const MetaRectangle *best_rect = NULL; + int best_overlap = 0; + + /* First, find best rectangle from spanning_rects to which we will clip + * rect into. + */ + for (temp = spanning_rects; temp; temp = temp->next) + { + MetaRectangle *compare_rect = temp->data; + MetaRectangle overlap; + int maximal_overlap_amount_for_compare; + + /* If x is fixed and the entire width of rect doesn't fit in compare, + * skip the rectangle. + */ + if ((fixed_directions & FIXED_DIRECTION_X) && + (compare_rect->x > rect->x || + compare_rect->x + compare_rect->width < rect->x + rect->width)) + continue; + + /* If y is fixed and the entire height of rect doesn't fit in compare, + * skip the rectangle. + */ + if ((fixed_directions & FIXED_DIRECTION_Y) && + (compare_rect->y > rect->y || + compare_rect->y + compare_rect->height < rect->y + rect->height)) + continue; + + /* Determine maximal overlap amount */ + meta_rectangle_intersect (rect, compare_rect, &overlap); + maximal_overlap_amount_for_compare = meta_rectangle_area (&overlap); + + /* See if this is the best rect so far */ + if (maximal_overlap_amount_for_compare > best_overlap) + { + best_rect = compare_rect; + best_overlap = maximal_overlap_amount_for_compare; + } + } + + /* Clip rect appropriately */ + if (best_rect == NULL) + meta_warning ("No rect to clip to found!\n"); + else + { + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_X)) + { + /* Find the new left and right */ + int new_x = MAX (rect->x, best_rect->x); + rect->width = MIN ((rect->x + rect->width) - new_x, + (best_rect->x + best_rect->width) - new_x); + rect->x = new_x; + } + + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_Y)) + { + /* Clip the top, if needed */ + int new_y = MAX (rect->y, best_rect->y); + rect->height = MIN ((rect->y + rect->height) - new_y, + (best_rect->y + best_rect->height) - new_y); + rect->y = new_y; + } + } +} + +void +meta_rectangle_shove_into_region (const GList *spanning_rects, + FixedDirections fixed_directions, + MetaRectangle *rect) +{ + const GList *temp; + const MetaRectangle *best_rect = NULL; + int best_overlap = 0; + int shortest_distance = G_MAXINT; + + /* First, find best rectangle from spanning_rects to which we will shove + * rect into. + */ + + for (temp = spanning_rects; temp; temp = temp->next) + { + MetaRectangle *compare_rect = temp->data; + int maximal_overlap_amount_for_compare; + int dist_to_compare; + + /* If x is fixed and the entire width of rect doesn't fit in compare, + * skip this rectangle. + */ + if ((fixed_directions & FIXED_DIRECTION_X) && + (compare_rect->x > rect->x || + compare_rect->x + compare_rect->width < rect->x + rect->width)) + continue; + + /* If y is fixed and the entire height of rect doesn't fit in compare, + * skip this rectangle. + */ + if ((fixed_directions & FIXED_DIRECTION_Y) && + (compare_rect->y > rect->y || + compare_rect->y + compare_rect->height < rect->y + rect->height)) + continue; + + /* Determine maximal overlap amount between rect & compare_rect */ + maximal_overlap_amount_for_compare = + MIN (rect->width, compare_rect->width) * + MIN (rect->height, compare_rect->height); + + /* Determine distance necessary to put rect into compare_rect */ + dist_to_compare = 0; + if (compare_rect->x > rect->x) + dist_to_compare += compare_rect->x - rect->x; + if (compare_rect->x + compare_rect->width < rect->x + rect->width) + dist_to_compare += (rect->x + rect->width) - + (compare_rect->x + compare_rect->width); + if (compare_rect->y > rect->y) + dist_to_compare += compare_rect->y - rect->y; + if (compare_rect->y + compare_rect->height < rect->y + rect->height) + dist_to_compare += (rect->y + rect->height) - + (compare_rect->y + compare_rect->height); + + /* See if this is the best rect so far */ + if ((maximal_overlap_amount_for_compare > best_overlap) || + (maximal_overlap_amount_for_compare == best_overlap && + dist_to_compare < shortest_distance)) + { + best_rect = compare_rect; + best_overlap = maximal_overlap_amount_for_compare; + shortest_distance = dist_to_compare; + } + } + + /* Shove rect appropriately */ + if (best_rect == NULL) + meta_warning ("No rect to shove into found!\n"); + else + { + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_X)) + { + /* Shove to the right, if needed */ + if (best_rect->x > rect->x) + rect->x = best_rect->x; + + /* Shove to the left, if needed */ + if (best_rect->x + best_rect->width < rect->x + rect->width) + rect->x = (best_rect->x + best_rect->width) - rect->width; + } + + /* Extra precaution with checking fixed direction shouldn't be needed + * due to logic above, but it shouldn't hurt either. + */ + if (!(fixed_directions & FIXED_DIRECTION_Y)) + { + /* Shove down, if needed */ + if (best_rect->y > rect->y) + rect->y = best_rect->y; + + /* Shove up, if needed */ + if (best_rect->y + best_rect->height < rect->y + rect->height) + rect->y = (best_rect->y + best_rect->height) - rect->height; + } + } +} + +void +meta_rectangle_find_linepoint_closest_to_point (double x1, + double y1, + double x2, + double y2, + double px, + double py, + double *valx, + double *valy) +{ + /* I'll use the shorthand rx, ry for the return values, valx & valy. + * Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2). + * For that to happen, we first need the slope of the line from (x1,y1) + * to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.: + * (ry-y1) (y2-y1) + * ------- = ------- + * (rx-x1) (x2-x1) + * If x1==x2, though, this gives divide by zero errors, so we want to + * rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1): + * (ry-y1)(x2-x1) = (y2-y1)(rx-x1) + * This is a valid requirement even when x1==x2 (when x1==x2, this latter + * equation will basically just mean that rx must be equal to both x1 and + * x2) + * + * The other requirement that we have is that the line from (rx,ry) to + * (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2). So + * we just need to get a vector in the direction of each line, take the + * dot product of the two, and ensure that the result is 0: + * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0. + * + * This gives us two equations and two unknowns: + * + * (ry-y1)(x2-x1) = (y2-y1)(rx-x1) + * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0. + * + * This particular pair of equations is always solvable so long as + * (x1,y1) and (x2,y2) are not the same point (and note that anyone who + * calls this function that way is braindead because it means that they + * really didn't specify a line after all). However, the caller should + * be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like + * 10^{-8} apart in each coordinate), otherwise roundoff error could + * cause issues. Solving these equations by hand (or using Maple(TM) or + * Mathematica(TM) or whatever) results in slightly messy expressions, + * but that's all the below few lines do. + */ + + double diffx, diffy, den; + diffx = x2 - x1; + diffy = y2 - y1; + den = diffx * diffx + diffy * diffy; + + *valx = (py * diffx * diffy + px * diffx * diffx + + y2 * x1 * diffy - y1 * x2 * diffy) / den; + *valy = (px * diffx * diffy + py * diffy * diffy + + x2 * y1 * diffx - x1 * y2 * diffx) / den; +} + +/***************************************************************************/ +/* */ +/* Switching gears to code for edges instead of just rectangles */ +/* */ +/***************************************************************************/ + +gboolean +meta_rectangle_edge_aligns (const MetaRectangle *rect, const MetaEdge *edge) +{ + /* The reason for the usage of <= below instead of < is because we are + * interested in in-the-way-or-adject'ness. So, a left (i.e. vertical + * edge) occupying y positions 0-9 (which has a y of 0 and a height of + * 10) and a rectangle with top at y=10 would be considered to "align" by + * this function. + */ + switch (edge->side_type) + { + case META_SIDE_LEFT: + case META_SIDE_RIGHT: + return BOX_TOP (*rect) <= BOX_BOTTOM (edge->rect) && + BOX_TOP (edge->rect) <= BOX_BOTTOM (*rect); + case META_SIDE_TOP: + case META_SIDE_BOTTOM: + return BOX_LEFT (*rect) <= BOX_RIGHT (edge->rect) && + BOX_LEFT (edge->rect) <= BOX_RIGHT (*rect); + default: + g_assert_not_reached (); + } +} + +static GList* +get_rect_minus_overlap (const GList *rect_in_list, + MetaRectangle *overlap) +{ + MetaRectangle *temp; + MetaRectangle *rect = rect_in_list->data; + GList *ret = NULL; + + if (BOX_LEFT (*rect) < BOX_LEFT (*overlap)) + { + temp = g_new (MetaRectangle, 1); + *temp = *rect; + temp->width = BOX_LEFT (*overlap) - BOX_LEFT (*rect); + ret = g_list_prepend (ret, temp); + } + if (BOX_RIGHT (*rect) > BOX_RIGHT (*overlap)) + { + temp = g_new (MetaRectangle, 1); + *temp = *rect; + temp->x = BOX_RIGHT (*overlap); + temp->width = BOX_RIGHT (*rect) - BOX_RIGHT (*overlap); + ret = g_list_prepend (ret, temp); + } + if (BOX_TOP (*rect) < BOX_TOP (*overlap)) + { + temp = g_new (MetaRectangle, 1); + temp->x = overlap->x; + temp->width = overlap->width; + temp->y = BOX_TOP (*rect); + temp->height = BOX_TOP (*overlap) - BOX_TOP (*rect); + ret = g_list_prepend (ret, temp); + } + if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*overlap)) + { + temp = g_new (MetaRectangle, 1); + temp->x = overlap->x; + temp->width = overlap->width; + temp->y = BOX_BOTTOM (*overlap); + temp->height = BOX_BOTTOM (*rect) - BOX_BOTTOM (*overlap); + ret = g_list_prepend (ret, temp); + } + + return ret; +} + +static GList* +replace_rect_with_list (GList *old_element, + GList *new_list) +{ + GList *ret; + g_assert (old_element != NULL); + + if (!new_list) + { + /* If there is no new list, just remove the old_element */ + ret = g_list_remove_link (old_element, old_element); + } + else + { + /* Fix up the prev and next pointers everywhere */ + ret = new_list; + if (old_element->prev) + { + old_element->prev->next = new_list; + new_list->prev = old_element->prev; + } + if (old_element->next) + { + GList *tmp = g_list_last (new_list); + old_element->next->prev = tmp; + tmp->next = old_element->next; + } + } + + /* Free the old_element and return the appropriate "next" point */ + g_free (old_element->data); + g_list_free_1 (old_element); + return ret; +} + +/* Make a copy of the strut list, make sure that copy only contains parts + * of the old_struts that intersect with the region rect, and then do some + * magic to make all the new struts disjoint (okay, we we break up struts + * that aren't disjoint in a way that the overlapping part is only included + * once, so it's not really magic...). + */ +static GList* +get_disjoint_strut_rect_list_in_region (const GSList *old_struts, + const MetaRectangle *region) +{ + GList *strut_rects; + GList *tmp; + + /* First, copy the list */ + strut_rects = NULL; + while (old_struts) + { + MetaRectangle *cur = &((MetaStrut*)old_struts->data)->rect; + MetaRectangle *copy = g_new (MetaRectangle, 1); + *copy = *cur; + if (meta_rectangle_intersect (copy, region, copy)) + strut_rects = g_list_prepend (strut_rects, copy); + else + g_free (copy); + + old_struts = old_struts->next; + } + + /* Now, loop over the list and check for intersections, fixing things up + * where they do intersect. + */ + tmp = strut_rects; + while (tmp) + { + GList *compare; + + MetaRectangle *cur = tmp->data; + + compare = tmp->next; + while (compare) + { + MetaRectangle *comp = compare->data; + MetaRectangle overlap; + + if (meta_rectangle_intersect (cur, comp, &overlap)) + { + /* Get a list of rectangles for each strut that don't overlap + * the intersection region. + */ + GList *cur_leftover = get_rect_minus_overlap (tmp, &overlap); + GList *comp_leftover = get_rect_minus_overlap (compare, &overlap); + + /* Add the intersection region to cur_leftover */ + MetaRectangle *overlap_allocated = g_new (MetaRectangle, 1); + *overlap_allocated = overlap; + cur_leftover = g_list_prepend (cur_leftover, overlap_allocated); + + /* Fix up tmp, compare, and cur -- maybe struts too */ + if (strut_rects == tmp) + { + strut_rects = replace_rect_with_list (tmp, cur_leftover); + tmp = strut_rects; + } + else + tmp = replace_rect_with_list (tmp, cur_leftover); + compare = replace_rect_with_list (compare, comp_leftover); + + if (compare == NULL) + break; + + cur = tmp->data; + } + + compare = compare->next; + } + + tmp = tmp->next; + } + + return strut_rects; +} + +gint +meta_rectangle_edge_cmp_ignore_type (gconstpointer a, gconstpointer b) +{ + const MetaEdge *a_edge_rect = (gconstpointer) a; + const MetaEdge *b_edge_rect = (gconstpointer) b; + int a_compare, b_compare; + + /* Edges must be both vertical or both horizontal, or it doesn't make + * sense to compare them. + */ + g_assert ((a_edge_rect->rect.width == 0 && b_edge_rect->rect.width == 0) || + (a_edge_rect->rect.height == 0 && b_edge_rect->rect.height == 0)); + + a_compare = b_compare = 0; /* gcc-3.4.2 sucks at figuring initialized'ness */ + + if (a_edge_rect->side_type == META_SIDE_LEFT || + a_edge_rect->side_type == META_SIDE_RIGHT) + { + a_compare = a_edge_rect->rect.x; + b_compare = b_edge_rect->rect.x; + if (a_compare == b_compare) + { + a_compare = a_edge_rect->rect.y; + b_compare = b_edge_rect->rect.y; + } + } + else if (a_edge_rect->side_type == META_SIDE_TOP || + a_edge_rect->side_type == META_SIDE_BOTTOM) + { + a_compare = a_edge_rect->rect.y; + b_compare = b_edge_rect->rect.y; + if (a_compare == b_compare) + { + a_compare = a_edge_rect->rect.x; + b_compare = b_edge_rect->rect.x; + } + } + else + g_assert ("Some idiot wanted to sort sides of different types.\n"); + + return a_compare - b_compare; /* positive value denotes a > b ... */ +} + +/* To make things easily testable, provide a nice way of sorting edges */ +gint +meta_rectangle_edge_cmp (gconstpointer a, gconstpointer b) +{ + const MetaEdge *a_edge_rect = (gconstpointer) a; + const MetaEdge *b_edge_rect = (gconstpointer) b; + + int a_compare, b_compare; + + a_compare = a_edge_rect->side_type; + b_compare = b_edge_rect->side_type; + + if (a_compare == b_compare) + return meta_rectangle_edge_cmp_ignore_type (a, b); + + return a_compare - b_compare; /* positive value denotes a > b ... */ +} + +/* Determine whether two given edges overlap */ +static gboolean +edges_overlap (const MetaEdge *edge1, + const MetaEdge *edge2) +{ + if (edge1->rect.width == 0 && edge2->rect.width == 0) + { + return meta_rectangle_vert_overlap (&edge1->rect, &edge2->rect) && + edge1->rect.x == edge2->rect.x; + } + else if (edge1->rect.height == 0 && edge2->rect.height == 0) + { + return meta_rectangle_horiz_overlap (&edge1->rect, &edge2->rect) && + edge1->rect.y == edge2->rect.y; + } + else + { + return FALSE; + } +} + +static gboolean +rectangle_and_edge_intersection (const MetaRectangle *rect, + const MetaEdge *edge, + MetaEdge *overlap, + int *handle_type) +{ + const MetaRectangle *rect2 = &edge->rect; + MetaRectangle *result = &overlap->rect; + gboolean intersect = TRUE; + + /* We don't know how to set these, so set them to invalid values */ + overlap->edge_type = -1; + overlap->side_type = -1; + + /* Figure out what the intersection is */ + result->x = MAX (rect->x, rect2->x); + result->y = MAX (rect->y, rect2->y); + result->width = MIN (BOX_RIGHT (*rect), BOX_RIGHT (*rect2)) - result->x; + result->height = MIN (BOX_BOTTOM (*rect), BOX_BOTTOM (*rect2)) - result->y; + + /* Find out if the intersection is empty; have to do it this way since + * edges have a thickness of 0 + */ + if ((result->width < 0 || result->height < 0) || + (result->width == 0 && result->height == 0)) + { + result->width = 0; + result->height = 0; + intersect = FALSE; + } + else + { + /* Need to figure out the handle_type, a somewhat weird quantity: + * 0 - overlap is in middle of rect + * -1 - overlap is at the side of rect, and is on the opposite side + * of rect than the edge->side_type side + * 1 - overlap is at the side of rect, and the side of rect it is + * on is the edge->side_type side + */ + switch (edge->side_type) + { + case META_SIDE_LEFT: + if (result->x == rect->x) + *handle_type = 1; + else if (result->x == BOX_RIGHT (*rect)) + *handle_type = -1; + else + *handle_type = 0; + break; + case META_SIDE_RIGHT: + if (result->x == rect->x) + *handle_type = -1; + else if (result->x == BOX_RIGHT (*rect)) + *handle_type = 1; + else + *handle_type = 0; + break; + case META_SIDE_TOP: + if (result->y == rect->y) + *handle_type = 1; + else if (result->y == BOX_BOTTOM (*rect)) + *handle_type = -1; + else + *handle_type = 0; + break; + case META_SIDE_BOTTOM: + if (result->y == rect->y) + *handle_type = -1; + else if (result->y == BOX_BOTTOM (*rect)) + *handle_type = 1; + else + *handle_type = 0; + break; + default: + g_assert_not_reached (); + } + } + return intersect; +} + +/* Add all edges of the given rect to cur_edges and return the result. If + * rect_is_internal is false, the side types are switched (LEFT<->RIGHT and + * TOP<->BOTTOM). + */ +static GList* +add_edges (GList *cur_edges, + const MetaRectangle *rect, + gboolean rect_is_internal) +{ + MetaEdge *temp_edge; + int i; + + for (i=0; i<4; i++) + { + temp_edge = g_new (MetaEdge, 1); + temp_edge->rect = *rect; + switch (i) + { + case 0: + temp_edge->side_type = + rect_is_internal ? META_SIDE_LEFT : META_SIDE_RIGHT; + temp_edge->rect.width = 0; + break; + case 1: + temp_edge->side_type = + rect_is_internal ? META_SIDE_RIGHT : META_SIDE_LEFT; + temp_edge->rect.x += temp_edge->rect.width; + temp_edge->rect.width = 0; + break; + case 2: + temp_edge->side_type = + rect_is_internal ? META_SIDE_TOP : META_SIDE_BOTTOM; + temp_edge->rect.height = 0; + break; + case 3: + temp_edge->side_type = + rect_is_internal ? META_SIDE_BOTTOM : META_SIDE_TOP; + temp_edge->rect.y += temp_edge->rect.height; + temp_edge->rect.height = 0; + break; + } + temp_edge->edge_type = META_EDGE_SCREEN; + cur_edges = g_list_prepend (cur_edges, temp_edge); + } + + return cur_edges; +} + +/* Remove any part of old_edge that intersects remove and add any resulting + * edges to cur_list. Return cur_list when finished. + */ +static GList* +split_edge (GList *cur_list, + const MetaEdge *old_edge, + const MetaEdge *remove) +{ + MetaEdge *temp_edge; + switch (old_edge->side_type) + { + case META_SIDE_LEFT: + case META_SIDE_RIGHT: + g_assert (meta_rectangle_vert_overlap (&old_edge->rect, &remove->rect)); + if (BOX_TOP (old_edge->rect) < BOX_TOP (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.height = BOX_TOP (remove->rect) + - BOX_TOP (old_edge->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + if (BOX_BOTTOM (old_edge->rect) > BOX_BOTTOM (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.y = BOX_BOTTOM (remove->rect); + temp_edge->rect.height = BOX_BOTTOM (old_edge->rect) + - BOX_BOTTOM (remove->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + break; + case META_SIDE_TOP: + case META_SIDE_BOTTOM: + g_assert (meta_rectangle_horiz_overlap (&old_edge->rect, &remove->rect)); + if (BOX_LEFT (old_edge->rect) < BOX_LEFT (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.width = BOX_LEFT (remove->rect) + - BOX_LEFT (old_edge->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + if (BOX_RIGHT (old_edge->rect) > BOX_RIGHT (remove->rect)) + { + temp_edge = g_new (MetaEdge, 1); + *temp_edge = *old_edge; + temp_edge->rect.x = BOX_RIGHT (remove->rect); + temp_edge->rect.width = BOX_RIGHT (old_edge->rect) + - BOX_RIGHT (remove->rect); + cur_list = g_list_prepend (cur_list, temp_edge); + } + break; + default: + g_assert_not_reached (); + } + + return cur_list; +} + +/* Split up edge and remove preliminary edges from strut_edges depending on + * if and how rect and edge intersect. + */ +static void +fix_up_edges (MetaRectangle *rect, MetaEdge *edge, + GList **strut_edges, GList **edge_splits, + gboolean *edge_needs_removal) +{ + MetaEdge overlap; + int handle_type; + + if (!rectangle_and_edge_intersection (rect, edge, &overlap, &handle_type)) + return; + + if (handle_type == 0 || handle_type == 1) + { + /* Put the result of removing overlap from edge into edge_splits */ + *edge_splits = split_edge (*edge_splits, edge, &overlap); + *edge_needs_removal = TRUE; + } + + if (handle_type == -1 || handle_type == 1) + { + /* Remove the overlap from strut_edges */ + /* First, loop over the edges of the strut */ + GList *tmp = *strut_edges; + while (tmp) + { + MetaEdge *cur = tmp->data; + /* If this is the edge that overlaps, then we need to split it */ + if (edges_overlap (cur, &overlap)) + { + GList *delete_me = tmp; + + /* Split this edge into some new ones */ + *strut_edges = split_edge (*strut_edges, cur, &overlap); + + /* Delete the old one */ + tmp = tmp->next; + g_free (cur); + *strut_edges = g_list_delete_link (*strut_edges, delete_me); + } + else + tmp = tmp->next; + } + } +} + +/* This function removes intersections of edges with the rectangles from the + * list of edges. + */ +GList* +meta_rectangle_remove_intersections_with_boxes_from_edges ( + GList *edges, + const GSList *rectangles) +{ + const GSList *rect_iter; + const int opposing = 1; + + /* Now remove all intersections of rectangles with the edge list */ + rect_iter = rectangles; + while (rect_iter) + { + MetaRectangle *rect = rect_iter->data; + GList *edge_iter = edges; + while (edge_iter) + { + MetaEdge *edge = edge_iter->data; + MetaEdge overlap; + int handle; + gboolean edge_iter_advanced = FALSE; + + /* If this edge overlaps with this rect... */ + if (rectangle_and_edge_intersection (rect, edge, &overlap, &handle)) + { + + /* "Intersections" where the edges touch but are opposite + * sides (e.g. a left edge against the right edge) should not + * be split. Note that the comments in + * rectangle_and_edge_intersection() say that opposing edges + * occur when handle is -1, BUT you need to remember that we + * treat the left side of a window as a right edge because + * it's what the right side of the window being moved should + * be-resisted-by/snap-to. So opposing is really 1. Anyway, + * we just keep track of it in the opposing constant set up + * above and if handle isn't equal to that, then we know the + * edge should be split. + */ + if (handle != opposing) + { + /* Keep track of this edge so we can delete it below */ + GList *delete_me = edge_iter; + edge_iter = edge_iter->next; + edge_iter_advanced = TRUE; + + /* Split the edge and add the result to beginning of edges */ + edges = split_edge (edges, edge, &overlap); + + /* Now free the edge... */ + g_free (edge); + edges = g_list_delete_link (edges, delete_me); + } + } + + if (!edge_iter_advanced) + edge_iter = edge_iter->next; + } + + rect_iter = rect_iter->next; + } + + return edges; +} + +/* This function is trying to find all the edges of an onscreen region. */ +GList* +meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect, + const GSList *all_struts) +{ + GList *ret; + GList *fixed_strut_rects; + GList *edge_iter; + const GList *strut_rect_iter; + + /* The algorithm is basically as follows: + * Make sure the struts are disjoint + * Initialize the edge_set to the edges of basic_rect + * Foreach strut: + * Put together a preliminary new edge from the edges of the strut + * Foreach edge in edge_set: + * - Split the edge if it is partially contained inside the strut + * - If the edge matches an edge of the strut (i.e. a strut just + * against the edge of the screen or a not-next-to-edge-of-screen + * strut adjacent to another), then both the edge from the + * edge_set and the preliminary edge for the strut will need to + * be split + * Add any remaining "preliminary" strut edges to the edge_set + */ + + /* Make sure the struts are disjoint */ + fixed_strut_rects = + get_disjoint_strut_rect_list_in_region (all_struts, basic_rect); + + /* Start off the list with the edges of basic_rect */ + ret = add_edges (NULL, basic_rect, TRUE); + + strut_rect_iter = fixed_strut_rects; + while (strut_rect_iter) + { + MetaRectangle *strut_rect = (MetaRectangle*) strut_rect_iter->data; + + /* Get the new possible edges we may need to add from the strut */ + GList *new_strut_edges = add_edges (NULL, strut_rect, FALSE); + + edge_iter = ret; + while (edge_iter) + { + MetaEdge *cur_edge = edge_iter->data; + GList *splits_of_cur_edge = NULL; + gboolean edge_needs_removal = FALSE; + + fix_up_edges (strut_rect, cur_edge, + &new_strut_edges, &splits_of_cur_edge, + &edge_needs_removal); + + if (edge_needs_removal) + { + /* Delete the old edge */ + GList *delete_me = edge_iter; + edge_iter = edge_iter->next; + g_free (cur_edge); + ret = g_list_delete_link (ret, delete_me); + + /* Add the new split parts of the edge */ + ret = g_list_concat (splits_of_cur_edge, ret); + } + else + { + edge_iter = edge_iter->next; + } + + /* edge_iter was already advanced above */ + } + + ret = g_list_concat (new_strut_edges, ret); + strut_rect_iter = strut_rect_iter->next; + } + + /* Sort the list */ + ret = g_list_sort (ret, meta_rectangle_edge_cmp); + + /* Free the fixed struts list */ + meta_rectangle_free_list_and_elements (fixed_strut_rects); + + return ret; +} + +GList* +meta_rectangle_find_nonintersected_xinerama_edges ( + const GList *xinerama_rects, + const GSList *all_struts) +{ + /* This function cannot easily be merged with + * meta_rectangle_find_onscreen_edges() because real screen edges + * and strut edges both are of the type "there ain't anything + * immediately on the other side"; xinerama edges are different. + */ + GList *ret; + const GList *cur; + GSList *temp_rects; + + /* Initialize the return list to be empty */ + ret = NULL; + + /* start of ret with all the edges of xineramas that are adjacent to + * another xinerama. + */ + cur = xinerama_rects; + while (cur) + { + MetaRectangle *cur_rect = cur->data; + const GList *compare = xinerama_rects; + while (compare) + { + MetaRectangle *compare_rect = compare->data; + + /* Check if cur might be horizontally adjacent to compare */ + if (meta_rectangle_vert_overlap(cur_rect, compare_rect)) + { + MetaSide side_type; + int y = MAX (cur_rect->y, compare_rect->y); + int height = MIN (BOX_BOTTOM (*cur_rect) - y, + BOX_BOTTOM (*compare_rect) - y); + int width = 0; + int x; + + if (BOX_LEFT (*cur_rect) == BOX_RIGHT (*compare_rect)) + { + /* compare_rect is to the left of cur_rect */ + x = BOX_LEFT (*cur_rect); + side_type = META_SIDE_LEFT; + } + else if (BOX_RIGHT (*cur_rect) == BOX_LEFT (*compare_rect)) + { + /* compare_rect is to the right of cur_rect */ + x = BOX_RIGHT (*cur_rect); + side_type = META_SIDE_RIGHT; + } + else + /* These rectangles aren't adjacent after all */ + x = INT_MIN; + + /* If the rectangles really are adjacent */ + if (x != INT_MIN) + { + /* We need a left edge for the xinerama on the right, and + * a right edge for the xinerama on the left. Just fill + * up the edges and stick 'em on the list. + */ + MetaEdge *new_edge = g_new (MetaEdge, 1); + + new_edge->rect = meta_rect (x, y, width, height); + new_edge->side_type = side_type; + new_edge->edge_type = META_EDGE_XINERAMA; + + ret = g_list_prepend (ret, new_edge); + } + } + + /* Check if cur might be vertically adjacent to compare */ + if (meta_rectangle_horiz_overlap(cur_rect, compare_rect)) + { + MetaSide side_type; + int x = MAX (cur_rect->x, compare_rect->x); + int width = MIN (BOX_RIGHT (*cur_rect) - x, + BOX_RIGHT (*compare_rect) - x); + int height = 0; + int y; + + if (BOX_TOP (*cur_rect) == BOX_BOTTOM (*compare_rect)) + { + /* compare_rect is to the top of cur_rect */ + y = BOX_TOP (*cur_rect); + side_type = META_SIDE_TOP; + } + else if (BOX_BOTTOM (*cur_rect) == BOX_TOP (*compare_rect)) + { + /* compare_rect is to the bottom of cur_rect */ + y = BOX_BOTTOM (*cur_rect); + side_type = META_SIDE_BOTTOM; + } + else + /* These rectangles aren't adjacent after all */ + y = INT_MIN; + + /* If the rectangles really are adjacent */ + if (y != INT_MIN) + { + /* We need a top edge for the xinerama on the bottom, and + * a bottom edge for the xinerama on the top. Just fill + * up the edges and stick 'em on the list. + */ + MetaEdge *new_edge = g_new (MetaEdge, 1); + + new_edge->rect = meta_rect (x, y, width, height); + new_edge->side_type = side_type; + new_edge->edge_type = META_EDGE_XINERAMA; + + ret = g_list_prepend (ret, new_edge); + } + } + + compare = compare->next; + } + cur = cur->next; + } + + temp_rects = NULL; + for (; all_struts; all_struts = all_struts->next) + temp_rects = g_slist_prepend (temp_rects, + &((MetaStrut*)all_struts->data)->rect); + ret = meta_rectangle_remove_intersections_with_boxes_from_edges (ret, + temp_rects); + g_slist_free (temp_rects); + + /* Sort the list */ + ret = g_list_sort (ret, meta_rectangle_edge_cmp); + + return ret; +} diff --git a/src/core/constraints.c b/src/core/constraints.c new file mode 100644 index 00000000..6abc7d5c --- /dev/null +++ b/src/core/constraints.c @@ -0,0 +1,1382 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco size/position constraints */ + +/* + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003, 2004 Rob Adams + * Copyright (C) 2005, 2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "constraints.h" +#include "workspace.h" +#include "place.h" +#include "prefs.h" + +#include <stdlib.h> +#include <math.h> + +#if 0 + // This is the short and sweet version of how to hack on this file; see + // doc/how-constraints-works.txt for the gory details. The basics of + // understanding this file can be shown by the steps needed to add a new + // constraint, which are: + // 1) Add a new entry in the ConstraintPriority enum; higher values + // have higher priority + // 2) Write a new function following the format of the example below, + // "constrain_whatever". + // 3) Add your function to the all_constraints and all_constraint_names + // arrays (the latter of which is for debugging purposes) + // + // An example constraint function, constrain_whatever: + // + // /* constrain_whatever does the following: + // * Quits (returning true) if priority is higher than PRIORITY_WHATEVER + // * If check_only is TRUE + // * Returns whether the constraint is satisfied or not + // * otherwise + // * Enforces the constraint + // * Note that the value of PRIORITY_WHATEVER is centralized with the + // * priorities of other constraints in the definition of ConstrainPriority + // * for easier maintenance and shuffling of priorities. + // */ + // static gboolean + // constrain_whatever (MetaWindow *window, + // ConstraintInfo *info, + // ConstraintPriority priority, + // gboolean check_only) + // { + // if (priority > PRIORITY_WHATEVER) + // return TRUE; + // + // /* Determine whether constraint applies; note that if the constraint + // * cannot possibly be satisfied, constraint_applies should be set to + // * false. If we don't do this, all constraints with a lesser priority + // * will be dropped along with this one, and we'd rather apply as many as + // * possible. + // */ + // if (!constraint_applies) + // return TRUE; + // + // /* Determine whether constraint is already satisfied; if we're only + // * checking the status of whether the constraint is satisfied, we end + // * here. + // */ + // if (check_only || constraint_already_satisfied) + // return constraint_already_satisfied; + // + // /* Enforce constraints */ + // return TRUE; /* Note that we exited early if check_only is FALSE; also, + // * we know we can return TRUE here because we exited early + // * if the constraint could not be satisfied; not that the + // * return value is heeded in this case... + // */ + // } +#endif + +typedef enum +{ + PRIORITY_MINIMUM = 0, /* Dummy value used for loop start = min(all priorities) */ + PRIORITY_ASPECT_RATIO = 0, + PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA = 0, + PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1, + PRIORITY_SIZE_HINTS_INCREMENTS = 1, + PRIORITY_MAXIMIZATION = 2, + PRIORITY_FULLSCREEN = 2, + PRIORITY_SIZE_HINTS_LIMITS = 3, + PRIORITY_TITLEBAR_VISIBLE = 4, + PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4, + PRIORITY_MAXIMUM = 4 /* Dummy value used for loop end = max(all priorities) */ +} ConstraintPriority; + +typedef enum +{ + ACTION_MOVE, + ACTION_RESIZE, + ACTION_MOVE_AND_RESIZE +} ActionType; + +typedef struct +{ + MetaRectangle orig; + MetaRectangle current; + MetaFrameGeometry *fgeom; + ActionType action_type; + gboolean is_user_action; + + /* I know that these two things probably look similar at first, but they + * have much different uses. See doc/how-constraints-works.txt for for + * explanation of the differences and similarity between resize_gravity + * and fixed_directions + */ + int resize_gravity; + FixedDirections fixed_directions; + + /* work_area_xinerama - current xinerama region minus struts + * entire_xinerama - current xienrama, including strut regions + */ + MetaRectangle work_area_xinerama; + MetaRectangle entire_xinerama; + + /* Spanning rectangles for the non-covered (by struts) region of the + * screen and also for just the current xinerama + */ + GList *usable_screen_region; + GList *usable_xinerama_region; +} ConstraintInfo; + +static gboolean constrain_maximization (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_fullscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_size_increments (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_size_limits (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_aspect_ratio (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_to_single_xinerama (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_fully_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_titlebar_visible (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); +static gboolean constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); + +static void setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new); +static void place_window_if_needed (MetaWindow *window, + ConstraintInfo *info); +static void update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info); +static void extend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom); +static void unextend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom); +static inline void get_size_limits (const MetaWindow *window, + const MetaFrameGeometry *fgeom, + gboolean include_frame, + MetaRectangle *min_size, + MetaRectangle *max_size); + +typedef gboolean (* ConstraintFunc) (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); + +typedef struct { + ConstraintFunc func; + const char* name; +} Constraint; + +static const Constraint all_constraints[] = { + {constrain_maximization, "constrain_maximization"}, + {constrain_fullscreen, "constrain_fullscreen"}, + {constrain_size_increments, "constrain_size_increments"}, + {constrain_size_limits, "constrain_size_limits"}, + {constrain_aspect_ratio, "constrain_aspect_ratio"}, + {constrain_to_single_xinerama, "constrain_to_single_xinerama"}, + {constrain_fully_onscreen, "constrain_fully_onscreen"}, + {constrain_titlebar_visible, "constrain_titlebar_visible"}, + {constrain_partially_onscreen, "constrain_partially_onscreen"}, + {NULL, NULL} +}; + +static gboolean +do_all_constraints (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + const Constraint *constraint; + gboolean satisfied; + + constraint = &all_constraints[0]; + satisfied = TRUE; + while (constraint->func != NULL) + { + satisfied = satisfied && + (*constraint->func) (window, info, priority, check_only); + + if (!check_only) + { + /* Log how the constraint modified the position */ + meta_topic (META_DEBUG_GEOMETRY, + "info->current is %d,%d +%d,%d after %s\n", + info->current.x, info->current.y, + info->current.width, info->current.height, + constraint->name); + } + else if (!satisfied) + { + /* Log which constraint was not satisfied */ + meta_topic (META_DEBUG_GEOMETRY, + "constraint %s not satisfied.\n", + constraint->name); + return FALSE; + } + ++constraint; + } + + return TRUE; +} + +void +meta_window_constrain (MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new) +{ + ConstraintInfo info; + ConstraintPriority priority = PRIORITY_MINIMUM; + gboolean satisfied = FALSE; + + /* WARNING: orig and new specify positions and sizes of the inner window, + * not the outer. This is a common gotcha since half the constraints + * deal with inner window position/size and half deal with outer. See + * doc/how-constraints-works.txt for more information. + */ + meta_topic (META_DEBUG_GEOMETRY, + "Constraining %s in move from %d,%d %dx%d to %d,%d %dx%d\n", + window->desc, + orig->x, orig->y, orig->width, orig->height, + new->x, new->y, new->width, new->height); + + setup_constraint_info (&info, + window, + orig_fgeom, + flags, + resize_gravity, + orig, + new); + place_window_if_needed (window, &info); + + while (!satisfied && priority <= PRIORITY_MAXIMUM) { + gboolean check_only = TRUE; + + /* Individually enforce all the high-enough priority constraints */ + do_all_constraints (window, &info, priority, !check_only); + + /* Check if all high-enough priority constraints are simultaneously + * satisfied + */ + satisfied = do_all_constraints (window, &info, priority, check_only); + + /* Drop the least important constraints if we can't satisfy them all */ + priority++; + } + + /* Make sure we use the constrained position */ + *new = info.current; + + /* We may need to update window->require_fully_onscreen, + * window->require_on_single_xinerama, and perhaps other quantities + * if this was a user move or user move-and-resize operation. + */ + update_onscreen_requirements (window, &info); + + /* Ew, what an ugly way to do things. Destructors (in a real OOP language, + * not gobject-style--gobject would be more pain than it's worth) or + * smart pointers would be so much nicer here. *shrug* + */ + if (!orig_fgeom) + g_free (info.fgeom); +} + +static void +setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new) +{ + const MetaXineramaScreenInfo *xinerama_info; + MetaWorkspace *cur_workspace; + + info->orig = *orig; + info->current = *new; + + /* Create a fake frame geometry if none really exists */ + if (orig_fgeom && !window->fullscreen) + info->fgeom = orig_fgeom; + else + info->fgeom = g_new0 (MetaFrameGeometry, 1); + + if (flags & META_IS_MOVE_ACTION && flags & META_IS_RESIZE_ACTION) + info->action_type = ACTION_MOVE_AND_RESIZE; + else if (flags & META_IS_RESIZE_ACTION) + info->action_type = ACTION_RESIZE; + else if (flags & META_IS_MOVE_ACTION) + info->action_type = ACTION_MOVE; + else + g_error ("BAD, BAD developer! No treat for you! (Fix your calls to " + "meta_window_move_resize_internal()).\n"); + + info->is_user_action = (flags & META_IS_USER_ACTION); + + info->resize_gravity = resize_gravity; + + /* FIXME: fixed_directions might be more sane if we (a) made it + * depend on the grab_op type instead of current amount of movement + * (thus implying that it only has effect when user_action is true, + * and (b) ignored it for aspect ratio windows -- at least in those + * cases where both directions do actually change size. + */ + info->fixed_directions = FIXED_DIRECTION_NONE; + /* If x directions don't change but either y direction does */ + if ( orig->x == new->x && orig->x + orig->width == new->x + new->width && + (orig->y != new->y || orig->y + orig->height != new->y + new->height)) + { + info->fixed_directions = FIXED_DIRECTION_X; + } + /* If y directions don't change but either x direction does */ + if ( orig->y == new->y && orig->y + orig->height == new->y + new->height && + (orig->x != new->x || orig->x + orig->width != new->x + new->width )) + { + info->fixed_directions = FIXED_DIRECTION_Y; + } + /* The point of fixed directions is just that "move to nearest valid + * position" is sometimes a poorer choice than "move to nearest + * valid position but only change this coordinate" for windows the + * user is explicitly moving. This isn't ever true for things that + * aren't explicit user interaction, though, so just clear it out. + */ + if (!info->is_user_action) + info->fixed_directions = FIXED_DIRECTION_NONE; + + xinerama_info = + meta_screen_get_xinerama_for_rect (window->screen, &info->current); + meta_window_get_work_area_for_xinerama (window, + xinerama_info->number, + &info->work_area_xinerama); + + if (!window->fullscreen || window->fullscreen_monitors[0] == -1) + { + info->entire_xinerama = xinerama_info->rect; + } + else + { + int i = 0; + long monitor; + + monitor = window->fullscreen_monitors[i]; + info->entire_xinerama = + window->screen->xinerama_infos[monitor].rect; + for (i = 1; i <= 3; i++) + { + monitor = window->fullscreen_monitors[i]; + meta_rectangle_union (&info->entire_xinerama, + &window->screen->xinerama_infos[monitor].rect, + &info->entire_xinerama); + } + } + + cur_workspace = window->screen->active_workspace; + info->usable_screen_region = + meta_workspace_get_onscreen_region (cur_workspace); + info->usable_xinerama_region = + meta_workspace_get_onxinerama_region (cur_workspace, + xinerama_info->number); + + /* Workaround braindead legacy apps that don't know how to + * fullscreen themselves properly. + */ + if (meta_prefs_get_force_fullscreen() && + meta_rectangle_equal (new, &xinerama_info->rect) && + window->has_fullscreen_func && + !window->fullscreen) + { + /* + meta_topic (META_DEBUG_GEOMETRY, + */ + meta_warning ( + "Treating resize request of legacy application %s as a " + "fullscreen request\n", + window->desc); + meta_window_make_fullscreen_internal (window); + } + + /* Log all this information for debugging */ + meta_topic (META_DEBUG_GEOMETRY, + "Setting up constraint info:\n" + " orig: %d,%d +%d,%d\n" + " new : %d,%d +%d,%d\n" + " fgeom: %d,%d,%d,%d\n" + " action_type : %s\n" + " is_user_action : %s\n" + " resize_gravity : %s\n" + " fixed_directions: %s\n" + " work_area_xinerama: %d,%d +%d,%d\n" + " entire_xinerama : %d,%d +%d,%d\n", + info->orig.x, info->orig.y, info->orig.width, info->orig.height, + info->current.x, info->current.y, + info->current.width, info->current.height, + info->fgeom->left_width, info->fgeom->right_width, + info->fgeom->top_height, info->fgeom->bottom_height, + (info->action_type == ACTION_MOVE) ? "Move" : + (info->action_type == ACTION_RESIZE) ? "Resize" : + (info->action_type == ACTION_MOVE_AND_RESIZE) ? "Move&Resize" : + "Freakin' Invalid Stupid", + (info->is_user_action) ? "true" : "false", + meta_gravity_to_string (info->resize_gravity), + (info->fixed_directions == FIXED_DIRECTION_NONE) ? "None" : + (info->fixed_directions == FIXED_DIRECTION_X) ? "X fixed" : + (info->fixed_directions == FIXED_DIRECTION_Y) ? "Y fixed" : + "Freakin' Invalid Stupid", + info->work_area_xinerama.x, info->work_area_xinerama.y, + info->work_area_xinerama.width, + info->work_area_xinerama.height, + info->entire_xinerama.x, info->entire_xinerama.y, + info->entire_xinerama.width, info->entire_xinerama.height); +} + +static void +place_window_if_needed(MetaWindow *window, + ConstraintInfo *info) +{ + gboolean did_placement; + + /* Do placement if any, so we go ahead and apply position + * constraints in a move-only context. Don't place + * maximized/minimized/fullscreen windows until they are + * unmaximized, unminimized and unfullscreened. + */ + did_placement = FALSE; + if (!window->placed && + window->calc_placement && + !(window->maximized_horizontally || + window->maximized_vertically) && + !window->minimized && + !window->fullscreen) + { + MetaRectangle placed_rect = info->orig; + MetaWorkspace *cur_workspace; + const MetaXineramaScreenInfo *xinerama_info; + + meta_window_place (window, info->fgeom, info->orig.x, info->orig.y, + &placed_rect.x, &placed_rect.y); + did_placement = TRUE; + + /* placing the window may have changed the xinerama. Find the + * new xinerama and update the ConstraintInfo + */ + xinerama_info = + meta_screen_get_xinerama_for_rect (window->screen, &placed_rect); + info->entire_xinerama = xinerama_info->rect; + meta_window_get_work_area_for_xinerama (window, + xinerama_info->number, + &info->work_area_xinerama); + cur_workspace = window->screen->active_workspace; + info->usable_xinerama_region = + meta_workspace_get_onxinerama_region (cur_workspace, + xinerama_info->number); + + + info->current.x = placed_rect.x; + info->current.y = placed_rect.y; + + /* Since we just barely placed the window, there's no reason to + * consider any of the directions fixed. + */ + info->fixed_directions = FIXED_DIRECTION_NONE; + } + + if (window->placed || did_placement) + { + if (window->maximize_horizontally_after_placement || + window->maximize_vertically_after_placement || + window->fullscreen_after_placement) + { + /* define a sane saved_rect so that the user can unmaximize or + * make unfullscreen to something reasonable. + */ + if (info->current.width >= info->work_area_xinerama.width) + { + info->current.width = .75 * info->work_area_xinerama.width; + info->current.x = info->work_area_xinerama.x + + .125 * info->work_area_xinerama.width; + } + if (info->current.height >= info->work_area_xinerama.height) + { + info->current.height = .75 * info->work_area_xinerama.height; + info->current.y = info->work_area_xinerama.y + + .083 * info->work_area_xinerama.height; + } + + if (window->maximize_horizontally_after_placement || + window->maximize_vertically_after_placement) + meta_window_maximize_internal (window, + (window->maximize_horizontally_after_placement ? + META_MAXIMIZE_HORIZONTAL : 0 ) | + (window->maximize_vertically_after_placement ? + META_MAXIMIZE_VERTICAL : 0), &info->current); + + /* maximization may have changed frame geometry */ + if (window->frame && !window->fullscreen) + meta_frame_calc_geometry (window->frame, info->fgeom); + + if (window->fullscreen_after_placement) + { + window->saved_rect = info->current; + window->fullscreen = TRUE; + window->fullscreen_after_placement = FALSE; + } + + window->maximize_horizontally_after_placement = FALSE; + window->maximize_vertically_after_placement = FALSE; + } + if (window->minimize_after_placement) + { + meta_window_minimize (window); + window->minimize_after_placement = FALSE; + } + } +} + +static void +update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info) +{ + gboolean old; + + /* We only apply the various onscreen requirements to normal windows */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + return; + + /* We don't want to update the requirements for fullscreen windows; + * fullscreen windows are specially handled anyway, and it updating + * the requirements when windows enter fullscreen mode mess up the + * handling of the window when it leaves that mode (especially when + * the application sends a bunch of configurerequest events). See + * #353699. + */ + if (window->fullscreen) + return; + + /* USABILITY NOTE: Naturally, I only want the require_fully_onscreen, + * require_on_single_xinerama, and require_titlebar_visible flags to + * *become false* due to user interactions (which is allowed since + * certain constraints are ignored for user interactions regardless of + * the setting of these flags). However, whether to make these flags + * *become true* due to just an application interaction is a little + * trickier. It's possible that users may find not doing that strange + * since two application interactions that resize in opposite ways don't + * necessarily end up cancelling--but it may also be strange for the user + * to have an application resize the window so that it's onscreen, the + * user forgets about it, and then later the app is able to resize itself + * off the screen. Anyway, for now, I think the latter is the more + * problematic case but this may need to be revisited. + */ + + /* The require onscreen/on-single-xinerama and titlebar_visible + * stuff is relative to the outer window, not the inner + */ + extend_by_frame (&info->current, info->fgeom); + + /* Update whether we want future constraint runs to require the + * window to be on fully onscreen. + */ + old = window->require_fully_onscreen; + window->require_fully_onscreen = + meta_rectangle_contained_in_region (info->usable_screen_region, + &info->current); + if (old ^ window->require_fully_onscreen) + meta_topic (META_DEBUG_GEOMETRY, + "require_fully_onscreen for %s toggled to %s\n", + window->desc, + window->require_fully_onscreen ? "TRUE" : "FALSE"); + + /* Update whether we want future constraint runs to require the + * window to be on a single xinerama. + */ + old = window->require_on_single_xinerama; + window->require_on_single_xinerama = + meta_rectangle_contained_in_region (info->usable_xinerama_region, + &info->current); + if (old ^ window->require_on_single_xinerama) + meta_topic (META_DEBUG_GEOMETRY, + "require_on_single_xinerama for %s toggled to %s\n", + window->desc, + window->require_on_single_xinerama ? "TRUE" : "FALSE"); + + /* Update whether we want future constraint runs to require the + * titlebar to be visible. + */ + if (window->frame && window->decorated) + { + MetaRectangle titlebar_rect; + + titlebar_rect = info->current; + titlebar_rect.height = info->fgeom->top_height; + old = window->require_titlebar_visible; + window->require_titlebar_visible = + meta_rectangle_overlaps_with_region (info->usable_screen_region, + &titlebar_rect); + if (old ^ window->require_titlebar_visible) + meta_topic (META_DEBUG_GEOMETRY, + "require_titlebar_visible for %s toggled to %s\n", + window->desc, + window->require_titlebar_visible ? "TRUE" : "FALSE"); + } + + /* Don't forget to restore the position of the window */ + unextend_by_frame (&info->current, info->fgeom); +} + +static void +extend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom) +{ + rect->x -= fgeom->left_width; + rect->y -= fgeom->top_height; + rect->width += fgeom->left_width + fgeom->right_width; + rect->height += fgeom->top_height + fgeom->bottom_height; +} + +static void +unextend_by_frame (MetaRectangle *rect, + const MetaFrameGeometry *fgeom) +{ + rect->x += fgeom->left_width; + rect->y += fgeom->top_height; + rect->width -= fgeom->left_width + fgeom->right_width; + rect->height -= fgeom->top_height + fgeom->bottom_height; +} + +static inline void +get_size_limits (const MetaWindow *window, + const MetaFrameGeometry *fgeom, + gboolean include_frame, + MetaRectangle *min_size, + MetaRectangle *max_size) +{ + /* We pack the results into MetaRectangle structs just for convienience; we + * don't actually use the position of those rects. + */ + min_size->width = window->size_hints.min_width; + min_size->height = window->size_hints.min_height; + max_size->width = window->size_hints.max_width; + max_size->height = window->size_hints.max_height; + + if (include_frame) + { + int fw = fgeom->left_width + fgeom->right_width; + int fh = fgeom->top_height + fgeom->bottom_height; + + min_size->width += fw; + min_size->height += fh; + max_size->width += fw; + max_size->height += fh; + } +} + +static gboolean +constrain_maximization (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle target_size; + MetaRectangle min_size, max_size; + gboolean hminbad, vminbad; + gboolean horiz_equal, vert_equal; + gboolean constraint_already_satisfied; + + if (priority > PRIORITY_MAXIMIZATION) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!window->maximized_horizontally && !window->maximized_vertically) + return TRUE; + + /* Calculate target_size = maximized size of (window + frame) */ + if (window->maximized_horizontally && window->maximized_vertically) + target_size = info->work_area_xinerama; + else + { + /* Amount of maximization possible in a single direction depends + * on which struts could occlude the window given its current + * position. For example, a vertical partial strut on the right + * is only relevant for a horizontally maximized window when the + * window is at a vertical position where it could be occluded + * by that partial strut. + */ + MetaDirection direction; + GSList *active_workspace_struts; + + if (window->maximized_horizontally) + direction = META_DIRECTION_HORIZONTAL; + else + direction = META_DIRECTION_VERTICAL; + active_workspace_struts = window->screen->active_workspace->all_struts; + + target_size = info->current; + extend_by_frame (&target_size, info->fgeom); + meta_rectangle_expand_to_avoiding_struts (&target_size, + &info->entire_xinerama, + direction, + active_workspace_struts); + } + /* Now make target_size = maximized size of client window */ + unextend_by_frame (&target_size, info->fgeom); + + /* Check min size constraints; max size constraints are ignored for maximized + * windows, as per bug 327543. + */ + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + hminbad = target_size.width < min_size.width && window->maximized_horizontally; + vminbad = target_size.height < min_size.height && window->maximized_vertically; + if (hminbad || vminbad) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + horiz_equal = target_size.x == info->current.x && + target_size.width == info->current.width; + vert_equal = target_size.y == info->current.y && + target_size.height == info->current.height; + constraint_already_satisfied = + (horiz_equal || !window->maximized_horizontally) && + (vert_equal || !window->maximized_vertically); + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + if (window->maximized_horizontally) + { + info->current.x = target_size.x; + info->current.width = target_size.width; + } + if (window->maximized_vertically) + { + info->current.y = target_size.y; + info->current.height = target_size.height; + } + return TRUE; +} + +static gboolean +constrain_fullscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle min_size, max_size, xinerama; + gboolean too_big, too_small, constraint_already_satisfied; + + if (priority > PRIORITY_FULLSCREEN) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (!window->fullscreen) + return TRUE; + + xinerama = info->entire_xinerama; + + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + too_big = !meta_rectangle_could_fit_rect (&xinerama, &min_size); + too_small = !meta_rectangle_could_fit_rect (&max_size, &xinerama); + if (too_big || too_small) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + constraint_already_satisfied = + meta_rectangle_equal (&info->current, &xinerama); + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + info->current = xinerama; + return TRUE; +} + +static gboolean +constrain_size_increments (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + int bh, hi, bw, wi, extra_height, extra_width; + int new_width, new_height; + gboolean constraint_already_satisfied; + MetaRectangle *start_rect; + + if (priority > PRIORITY_SIZE_HINTS_INCREMENTS) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't */ + if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || + info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + bh = window->size_hints.base_height; + hi = window->size_hints.height_inc; + bw = window->size_hints.base_width; + wi = window->size_hints.width_inc; + extra_height = (info->current.height - bh) % hi; + extra_width = (info->current.width - bw) % wi; + /* ignore size increments for maximized windows */ + if (window->maximized_horizontally) + extra_width *= 0; + if (window->maximized_vertically) + extra_height *= 0; + /* constraint is satisfied iff there is no extra height or width */ + constraint_already_satisfied = + (extra_height == 0 && extra_width == 0); + + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + new_width = info->current.width - extra_width; + new_height = info->current.height - extra_height; + + /* Adjusting down instead of up (as done in the above two lines) may + * violate minimum size constraints; fix the adjustment if this + * happens. + */ + if (new_width < window->size_hints.min_width) + new_width += ((window->size_hints.min_width - new_width)/wi + 1)*wi; + if (new_height < window->size_hints.min_height) + new_height += ((window->size_hints.min_height - new_height)/hi + 1)*hi; + + /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity + * See bug 448183 + */ + if (info->action_type == ACTION_MOVE_AND_RESIZE) + start_rect = &info->current; + else + start_rect = &info->orig; + + /* Resize to the new size */ + meta_rectangle_resize_with_gravity (start_rect, + &info->current, + info->resize_gravity, + new_width, + new_height); + return TRUE; +} + +static gboolean +constrain_size_limits (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + MetaRectangle min_size, max_size; + gboolean too_big, too_small, constraint_already_satisfied; + int new_width, new_height; + MetaRectangle *start_rect; + + if (priority > PRIORITY_SIZE_HINTS_LIMITS) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't. + * + * Note: The old code didn't apply this constraint for fullscreen or + * maximized windows--but that seems odd to me. *shrug* + */ + if (info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size); + /* We ignore max-size limits for maximized windows; see #327543 */ + if (window->maximized_horizontally) + max_size.width = MAX (max_size.width, info->current.width); + if (window->maximized_vertically) + max_size.height = MAX (max_size.height, info->current.height); + too_small = !meta_rectangle_could_fit_rect (&info->current, &min_size); + too_big = !meta_rectangle_could_fit_rect (&max_size, &info->current); + constraint_already_satisfied = !too_big && !too_small; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + new_width = CLAMP (info->current.width, min_size.width, max_size.width); + new_height = CLAMP (info->current.height, min_size.height, max_size.height); + + /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity + * See bug 448183 + */ + if (info->action_type == ACTION_MOVE_AND_RESIZE) + start_rect = &info->current; + else + start_rect = &info->orig; + + meta_rectangle_resize_with_gravity (start_rect, + &info->current, + info->resize_gravity, + new_width, + new_height); + return TRUE; +} + +static gboolean +constrain_aspect_ratio (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + double minr, maxr; + gboolean constraints_are_inconsistent, constraint_already_satisfied; + int fudge, new_width, new_height; + double best_width, best_height; + double alt_width, alt_height; + MetaRectangle *start_rect; + + if (priority > PRIORITY_ASPECT_RATIO) + return TRUE; + + /* Determine whether constraint applies; exit if it doesn't. */ + minr = window->size_hints.min_aspect.x / + (double)window->size_hints.min_aspect.y; + maxr = window->size_hints.max_aspect.x / + (double)window->size_hints.max_aspect.y; + constraints_are_inconsistent = minr > maxr; + if (constraints_are_inconsistent || + META_WINDOW_MAXIMIZED (window) || window->fullscreen || + info->action_type == ACTION_MOVE) + return TRUE; + + /* Determine whether constraint is already satisfied; exit if it is. We + * need the following to hold: + * + * width + * minr <= ------ <= maxr + * height + * + * But we need to allow for some slight fudging since width and height + * are integers instead of floating point numbers (this is particularly + * important when minr == maxr), so we allow width and height to be off + * a little bit from strictly satisfying these equations. For just one + * sided resizing, we have to make the fudge factor a little bigger + * because of how meta_rectangle_resize_with_gravity treats those as + * being a resize increment (FIXME: I should handle real resize + * increments better here...) + */ + switch (info->resize_gravity) + { + case WestGravity: + case NorthGravity: + case SouthGravity: + case EastGravity: + fudge = 2; + break; + + case NorthWestGravity: + case SouthWestGravity: + case CenterGravity: + case NorthEastGravity: + case SouthEastGravity: + case StaticGravity: + default: + fudge = 1; + break; + } + constraint_already_satisfied = + info->current.width - (info->current.height * minr ) > -minr*fudge && + info->current.width - (info->current.height * maxr ) < maxr*fudge; + if (check_only || constraint_already_satisfied) + return constraint_already_satisfied; + + /*** Enforce constraint ***/ + new_width = info->current.width; + new_height = info->current.height; + + switch (info->resize_gravity) + { + case WestGravity: + case EastGravity: + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_height = CLAMP (new_height, new_width / maxr, new_width / minr); + break; + + case NorthGravity: + case SouthGravity: + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_width = CLAMP (new_width, new_height * minr, new_height * maxr); + break; + + case NorthWestGravity: + case SouthWestGravity: + case CenterGravity: + case NorthEastGravity: + case SouthEastGravity: + case StaticGravity: + default: + /* Find what width would correspond to new_height, and what height would + * correspond to new_width */ + alt_width = CLAMP (new_width, new_height * minr, new_height * maxr); + alt_height = CLAMP (new_height, new_width / maxr, new_width / minr); + + /* The line connecting the points (alt_width, new_height) and + * (new_width, alt_height) provide a range of + * valid-for-the-aspect-ratio-constraint sizes. We want the + * size in that range closest to the value requested, i.e. the + * point on the line which is closest to the point (new_width, + * new_height) + */ + meta_rectangle_find_linepoint_closest_to_point (alt_width, new_height, + new_width, alt_height, + new_width, new_height, + &best_width, &best_height); + + /* Yeah, I suck for doing implicit rounding -- sue me */ + new_width = best_width; + new_height = best_height; + + break; + } + + /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity + * See bug 448183 + */ + if (info->action_type == ACTION_MOVE_AND_RESIZE) + start_rect = &info->current; + else + start_rect = &info->orig; + + meta_rectangle_resize_with_gravity (start_rect, + &info->current, + info->resize_gravity, + new_width, + new_height); + + return TRUE; +} + +static gboolean +do_screen_and_xinerama_relative_constraints ( + MetaWindow *window, + GList *region_spanning_rectangles, + ConstraintInfo *info, + gboolean check_only) +{ + gboolean exit_early = FALSE, constraint_satisfied; + MetaRectangle how_far_it_can_be_smushed, min_size, max_size; + +#ifdef WITH_VERBOSE_MODE + if (meta_is_verbose ()) + { + /* First, log some debugging information */ + char spanning_region[1 + 28 * g_list_length (region_spanning_rectangles)]; + + meta_topic (META_DEBUG_GEOMETRY, + "screen/xinerama constraint; region_spanning_rectangles: %s\n", + meta_rectangle_region_to_string (region_spanning_rectangles, ", ", + spanning_region)); + } +#endif + + /* Determine whether constraint applies; exit if it doesn't */ + how_far_it_can_be_smushed = info->current; + get_size_limits (window, info->fgeom, TRUE, &min_size, &max_size); + extend_by_frame (&info->current, info->fgeom); + + if (info->action_type != ACTION_MOVE) + { + if (!(info->fixed_directions & FIXED_DIRECTION_X)) + how_far_it_can_be_smushed.width = min_size.width; + + if (!(info->fixed_directions & FIXED_DIRECTION_Y)) + how_far_it_can_be_smushed.height = min_size.height; + } + if (!meta_rectangle_could_fit_in_region (region_spanning_rectangles, + &how_far_it_can_be_smushed)) + exit_early = TRUE; + + /* Determine whether constraint is already satisfied; exit if it is */ + constraint_satisfied = + meta_rectangle_contained_in_region (region_spanning_rectangles, + &info->current); + if (exit_early || constraint_satisfied || check_only) + { + unextend_by_frame (&info->current, info->fgeom); + return constraint_satisfied; + } + + /* Enforce constraint */ + + /* Clamp rectangle size for resize or move+resize actions */ + if (info->action_type != ACTION_MOVE) + meta_rectangle_clamp_to_fit_into_region (region_spanning_rectangles, + info->fixed_directions, + &info->current, + &min_size); + + if (info->is_user_action && info->action_type == ACTION_RESIZE) + /* For user resize, clip to the relevant region */ + meta_rectangle_clip_to_region (region_spanning_rectangles, + info->fixed_directions, + &info->current); + else + /* For everything else, shove the rectangle into the relevant region */ + meta_rectangle_shove_into_region (region_spanning_rectangles, + info->fixed_directions, + &info->current); + + unextend_by_frame (&info->current, info->fgeom); + return TRUE; +} + +static gboolean +constrain_to_single_xinerama (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut) and we can't apply it to frameless windows + * or else users will be unable to move windows such as XMMS across xineramas. + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->screen->n_xinerama_infos == 1 || + !window->require_on_single_xinerama || + !window->frame || + info->is_user_action) + return TRUE; + + /* Have a helper function handle the constraint for us */ + return do_screen_and_xinerama_relative_constraints (window, + info->usable_xinerama_region, + info, + check_only); +} + +static gboolean +constrain_fully_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->fullscreen || + !window->require_fully_onscreen || + info->is_user_action) + return TRUE; + + /* Have a helper function handle the constraint for us */ + return do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); +} + +static gboolean +constrain_titlebar_visible (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + gboolean unconstrained_user_action; + gboolean retval; + int bottom_amount; + int horiz_amount_offscreen, vert_amount_offscreen; + int horiz_amount_onscreen, vert_amount_onscreen; + + if (priority > PRIORITY_TITLEBAR_VISIBLE) + return TRUE; + + /* Allow the titlebar beyond the top of the screen only if the user wasn't + * clicking on the frame to start the move. + */ + unconstrained_user_action = + info->is_user_action && !window->display->grab_frame_action; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->fullscreen || + !window->require_titlebar_visible || + !window->decorated || + unconstrained_user_action) + return TRUE; + + /* Determine how much offscreen things are allowed. We first need to + * figure out how much must remain on the screen. For that, we use 25% + * window width/height but clamp to the range of (10,75) pixels. This is + * somewhat of a seat of my pants random guess at what might look good. + * Then, the amount that is allowed off is just the window size minus + * this amount (but no less than 0 for tiny windows). + */ + horiz_amount_onscreen = info->current.width / 4; + vert_amount_onscreen = info->current.height / 4; + horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10, 75); + vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10, 75); + horiz_amount_offscreen = info->current.width - horiz_amount_onscreen; + vert_amount_offscreen = info->current.height - vert_amount_onscreen; + horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0); + vert_amount_offscreen = MAX (vert_amount_offscreen, 0); + /* Allow the titlebar to touch the bottom panel; If there is no titlebar, + * require vert_amount to remain on the screen. + */ + if (window->frame) + { + bottom_amount = info->current.height + info->fgeom->bottom_height; + vert_amount_onscreen = info->fgeom->top_height; + } + else + bottom_amount = vert_amount_offscreen; + + /* Extend the region, have a helper function handle the constraint, + * then return the region to its original size. + */ + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + horiz_amount_offscreen, + horiz_amount_offscreen, + 0, /* Don't let titlebar off */ + bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + -horiz_amount_offscreen, + -horiz_amount_offscreen, + 0, /* Don't let titlebar off */ + -bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + + return retval; +} + +static gboolean +constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only) +{ + gboolean retval; + int top_amount, bottom_amount; + int horiz_amount_offscreen, vert_amount_offscreen; + int horiz_amount_onscreen, vert_amount_onscreen; + + if (priority > PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA) + return TRUE; + + /* Exit early if we know the constraint won't apply--note that this constraint + * is only meant for normal windows (e.g. we don't want docks to be shoved + * "onscreen" by their own strut). + */ + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + return TRUE; + + /* Determine how much offscreen things are allowed. We first need to + * figure out how much must remain on the screen. For that, we use 25% + * window width/height but clamp to the range of (10,75) pixels. This is + * somewhat of a seat of my pants random guess at what might look good. + * Then, the amount that is allowed off is just the window size minus + * this amount (but no less than 0 for tiny windows). + */ + horiz_amount_onscreen = info->current.width / 4; + vert_amount_onscreen = info->current.height / 4; + horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10, 75); + vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10, 75); + horiz_amount_offscreen = info->current.width - horiz_amount_onscreen; + vert_amount_offscreen = info->current.height - vert_amount_onscreen; + horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0); + vert_amount_offscreen = MAX (vert_amount_offscreen, 0); + top_amount = vert_amount_offscreen; + /* Allow the titlebar to touch the bottom panel; If there is no titlebar, + * require vert_amount to remain on the screen. + */ + if (window->frame) + { + bottom_amount = info->current.height + info->fgeom->bottom_height; + vert_amount_onscreen = info->fgeom->top_height; + } + else + bottom_amount = vert_amount_offscreen; + + /* Extend the region, have a helper function handle the constraint, + * then return the region to its original size. + */ + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + horiz_amount_offscreen, + horiz_amount_offscreen, + top_amount, + bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + retval = + do_screen_and_xinerama_relative_constraints (window, + info->usable_screen_region, + info, + check_only); + meta_rectangle_expand_region_conditionally (info->usable_screen_region, + -horiz_amount_offscreen, + -horiz_amount_offscreen, + -top_amount, + -bottom_amount, + horiz_amount_onscreen, + vert_amount_onscreen); + + return retval; +} diff --git a/src/core/constraints.h b/src/core/constraints.h new file mode 100644 index 00000000..d374581f --- /dev/null +++ b/src/core/constraints.h @@ -0,0 +1,48 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco size/position constraints */ + +/* + * Copyright (C) 2002 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_CONSTRAINTS_H +#define META_CONSTRAINTS_H + +#include "util.h" +#include "window-private.h" +#include "frame-private.h" + +typedef enum +{ + META_IS_CONFIGURE_REQUEST = 1 << 0, + META_DO_GRAVITY_ADJUST = 1 << 1, + META_IS_USER_ACTION = 1 << 2, + META_IS_MOVE_ACTION = 1 << 3, + META_IS_RESIZE_ACTION = 1 << 4 +} MetaMoveResizeFlags; + +void meta_window_constrain (MetaWindow *window, + MetaFrameGeometry *orig_fgeom, + MetaMoveResizeFlags flags, + int resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new); + +#endif /* META_CONSTRAINTS_H */ diff --git a/src/core/core.c b/src/core/core.c new file mode 100644 index 00000000..963cbfa7 --- /dev/null +++ b/src/core/core.c @@ -0,0 +1,779 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco interface used by GTK+ UI to talk to core */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "core.h" +#include "frame-private.h" +#include "workspace.h" +#include "prefs.h" + +/* Looks up the MetaWindow representing the frame of the given X window. + * Used as a helper function by a bunch of the functions below. + * + * FIXME: The functions that use this function throw the result away + * after use. Many of these functions tend to be called in small groups, + * which results in get_window() getting called several times in succession + * with the same parameters. We should profile to see whether this wastes + * much time, and if it does we should look into a generalised + * meta_core_get_window_info() which takes a bunch of pointers to variables + * to put its results in, and only fills in the non-null ones. + */ +static MetaWindow * +get_window (Display *xdisplay, + Window frame_xwindow) +{ + MetaDisplay *display; + MetaWindow *window; + + display = meta_display_for_x_display (xdisplay); + window = meta_display_lookup_x_window (display, frame_xwindow); + + if (window == NULL || window->frame == NULL) + { + meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); + return NULL; + } + + return window; +} + +void +meta_core_get (Display *xdisplay, + Window xwindow, + ...) +{ + va_list args; + MetaCoreGetType request; + + MetaDisplay *display = meta_display_for_x_display (xdisplay); + MetaWindow *window = meta_display_lookup_x_window (display, xwindow); + + va_start (args, xwindow); + + request = va_arg (args, MetaCoreGetType); + + /* Now, we special-case the first request slightly. Mostly, requests + * for information on windows which have no frame are errors. + * But sometimes we may want to know *whether* a window has a frame. + * In this case, pass the key META_CORE_WINDOW_HAS_FRAME + * as the *first* request, with a pointer to a boolean; if the window + * has no frame, this will be set to False and meta_core_get will + * exit immediately (so the values of any other requests will be + * undefined). Otherwise it will be set to True and meta_core_get will + * continue happily on its way. + */ + + if (request != META_CORE_WINDOW_HAS_FRAME && + (window == NULL || window->frame == NULL)) { + meta_bug ("No such frame window 0x%lx!\n", xwindow); + return; + } + + while (request != META_CORE_GET_END) { + + gpointer answer = va_arg (args, gpointer); + + switch (request) { + case META_CORE_WINDOW_HAS_FRAME: + *((gboolean*)answer) = window != NULL && window->frame != NULL; + if (!*((gboolean*)answer)) return; /* see above */ + break; + case META_CORE_GET_CLIENT_WIDTH: + *((gint*)answer) = window->rect.width; + break; + case META_CORE_GET_CLIENT_HEIGHT: + *((gint*)answer) = window->rect.height; + break; + case META_CORE_IS_TITLEBAR_ONSCREEN: + *((gboolean*)answer) = meta_window_titlebar_is_onscreen (window); + break; + case META_CORE_GET_CLIENT_XWINDOW: + *((Window*)answer) = window->xwindow; + break; + case META_CORE_GET_FRAME_FLAGS: + *((MetaFrameFlags*)answer) = meta_frame_get_flags (window->frame); + break; + case META_CORE_GET_FRAME_TYPE: + { + MetaFrameType base_type = META_FRAME_TYPE_LAST; + + switch (window->type) + { + case META_WINDOW_NORMAL: + base_type = META_FRAME_TYPE_NORMAL; + break; + + case META_WINDOW_DIALOG: + base_type = META_FRAME_TYPE_DIALOG; + break; + + case META_WINDOW_MODAL_DIALOG: + base_type = META_FRAME_TYPE_MODAL_DIALOG; + break; + + case META_WINDOW_MENU: + base_type = META_FRAME_TYPE_MENU; + break; + + case META_WINDOW_UTILITY: + base_type = META_FRAME_TYPE_UTILITY; + break; + + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + case META_WINDOW_SPLASHSCREEN: + /* No frame */ + base_type = META_FRAME_TYPE_LAST; + break; + + } + + if (base_type == META_FRAME_TYPE_LAST) + { + /* can't add border if undecorated */ + *((MetaFrameType*)answer) = META_FRAME_TYPE_LAST; + } + else if (window->border_only) + { + /* override base frame type */ + *((MetaFrameType*)answer) = META_FRAME_TYPE_BORDER; + } + else + { + *((MetaFrameType*)answer) = base_type; + } + + break; + } + case META_CORE_GET_MINI_ICON: + *((GdkPixbuf**)answer) = window->mini_icon; + break; + case META_CORE_GET_ICON: + *((GdkPixbuf**)answer) = window->icon; + break; + case META_CORE_GET_X: + meta_window_get_position (window, (int*)answer, NULL); + break; + case META_CORE_GET_Y: + meta_window_get_position (window, NULL, (int*)answer); + break; + case META_CORE_GET_FRAME_WORKSPACE: + *((gint*)answer) = meta_window_get_net_wm_desktop (window); + break; + case META_CORE_GET_FRAME_X: + *((gint*)answer) = window->frame->rect.x; + break; + case META_CORE_GET_FRAME_Y: + *((gint*)answer) = window->frame->rect.y; + break; + case META_CORE_GET_FRAME_WIDTH: + *((gint*)answer) = window->frame->rect.width; + break; + case META_CORE_GET_FRAME_HEIGHT: + *((gint*)answer) = window->frame->rect.height; + break; + case META_CORE_GET_SCREEN_WIDTH: + *((gint*)answer) = window->screen->rect.width; + break; + case META_CORE_GET_SCREEN_HEIGHT: + *((gint*)answer) = window->screen->rect.height; + break; + + default: + meta_warning(_("Unknown window information request: %d"), request); + } + + request = va_arg (args, MetaCoreGetType); + } + + va_end (args); +} + +void +meta_core_queue_frame_resize (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + +void +meta_core_user_move (Display *xdisplay, + Window frame_xwindow, + int x, + int y) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_move (window, TRUE, x, y); +} + +void +meta_core_user_resize (Display *xdisplay, + Window frame_xwindow, + int gravity, + int width, + int height) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_resize_with_gravity (window, TRUE, width, height, gravity); +} + +void +meta_core_user_raise (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_raise (window); +} + +void +meta_core_user_lower_and_unfocus (Display *xdisplay, + Window frame_xwindow, + guint32 timestamp) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_lower (window); + + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK && + meta_prefs_get_raise_on_click ()) + { + /* Move window to the back of the focusing workspace's MRU list. + * Do extra sanity checks to avoid possible race conditions. + * (Borrowed from window.c.) + */ + if (window->screen->active_workspace && + meta_window_located_on_workspace (window, + window->screen->active_workspace)) + { + GList* link; + link = g_list_find (window->screen->active_workspace->mru_list, + window); + g_assert (link); + + window->screen->active_workspace->mru_list = + g_list_remove_link (window->screen->active_workspace->mru_list, + link); + g_list_free (link); + + window->screen->active_workspace->mru_list = + g_list_append (window->screen->active_workspace->mru_list, + window); + } + } + + /* focus the default window, if needed */ + if (window->has_focus) + meta_workspace_focus_default_window (window->screen->active_workspace, + NULL, + timestamp); +} + +void +meta_core_user_focus (Display *xdisplay, + Window frame_xwindow, + guint32 timestamp) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_focus (window, timestamp); +} + +void +meta_core_minimize (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_minimize (window); +} + +void +meta_core_maximize (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); +} + +void +meta_core_toggle_maximize_vertically (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + + if (META_WINDOW_MAXIMIZED_VERTICALLY (window)) + meta_window_unmaximize (window, + META_MAXIMIZE_VERTICAL); + else + meta_window_maximize (window, + META_MAXIMIZE_VERTICAL); +} + +void +meta_core_toggle_maximize_horizontally (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + + if (META_WINDOW_MAXIMIZED_HORIZONTALLY (window)) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL); + else + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL); +} + +void +meta_core_toggle_maximize (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + + if (META_WINDOW_MAXIMIZED (window)) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); + else + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); +} + +void +meta_core_unmaximize (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL); +} + +void +meta_core_delete (Display *xdisplay, + Window frame_xwindow, + guint32 timestamp) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_delete (window, timestamp); +} + +void +meta_core_unshade (Display *xdisplay, + Window frame_xwindow, + guint32 timestamp) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_unshade (window, timestamp); +} + +void +meta_core_shade (Display *xdisplay, + Window frame_xwindow, + guint32 timestamp) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_shade (window, timestamp); +} + +void +meta_core_unstick (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_unstick (window); +} + +void +meta_core_make_above (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_make_above (window); +} + +void +meta_core_unmake_above (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_unmake_above (window); +} + +void +meta_core_stick (Display *xdisplay, + Window frame_xwindow) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_stick (window); +} + +void +meta_core_change_workspace (Display *xdisplay, + Window frame_xwindow, + int new_workspace) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + meta_window_change_workspace (window, + meta_screen_get_workspace_by_index (window->screen, + new_workspace)); +} + +int +meta_core_get_num_workspaces (Screen *xscreen) +{ + MetaScreen *screen; + + screen = meta_screen_for_x_screen (xscreen); + + return meta_screen_get_n_workspaces (screen); +} + +int +meta_core_get_active_workspace (Screen *xscreen) +{ + MetaScreen *screen; + + screen = meta_screen_for_x_screen (xscreen); + + return meta_workspace_index (screen->active_workspace); +} + +void +meta_core_show_window_menu (Display *xdisplay, + Window frame_xwindow, + int root_x, + int root_y, + int button, + guint32 timestamp) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_focus (window, timestamp); + + meta_window_show_menu (window, root_x, root_y, button, timestamp); +} + +void +meta_core_get_menu_accelerator (MetaMenuOp menu_op, + int workspace, + unsigned int *keysym, + MetaVirtualModifier *modifiers) +{ + const char *name; + + name = NULL; + + switch (menu_op) + { + case META_MENU_OP_NONE: + /* No keybinding for this one */ + break; + case META_MENU_OP_DELETE: + name = "close"; + break; + case META_MENU_OP_MINIMIZE: + name = "minimize"; + break; + case META_MENU_OP_UNMAXIMIZE: + name = "unmaximize"; + break; + case META_MENU_OP_MAXIMIZE: + name = "maximize"; + break; + case META_MENU_OP_UNSHADE: + case META_MENU_OP_SHADE: + name = "toggle_shaded"; + break; + case META_MENU_OP_UNSTICK: + case META_MENU_OP_STICK: + name = "toggle_on_all_workspaces"; + break; + case META_MENU_OP_ABOVE: + case META_MENU_OP_UNABOVE: + name = "toggle_above"; + break; + case META_MENU_OP_WORKSPACES: + switch (workspace) + { + case 1: + name = "move_to_workspace_1"; + break; + case 2: + name = "move_to_workspace_2"; + break; + case 3: + name = "move_to_workspace_3"; + break; + case 4: + name = "move_to_workspace_4"; + break; + case 5: + name = "move_to_workspace_5"; + break; + case 6: + name = "move_to_workspace_6"; + break; + case 7: + name = "move_to_workspace_7"; + break; + case 8: + name = "move_to_workspace_8"; + break; + case 9: + name = "move_to_workspace_9"; + break; + case 10: + name = "move_to_workspace_10"; + break; + case 11: + name = "move_to_workspace_11"; + break; + case 12: + name = "move_to_workspace_12"; + break; + } + break; + case META_MENU_OP_MOVE: + name = "begin_move"; + break; + case META_MENU_OP_RESIZE: + name = "begin_resize"; + break; + case META_MENU_OP_MOVE_LEFT: + name = "move_to_workspace_left"; + break; + case META_MENU_OP_MOVE_RIGHT: + name = "move_to_workspace_right"; + break; + case META_MENU_OP_MOVE_UP: + name = "move_to_workspace_up"; + break; + case META_MENU_OP_MOVE_DOWN: + name = "move_to_workspace_down"; + break; + case META_MENU_OP_RECOVER: + /* No keybinding for this one */ + break; + } + + if (name) + { + meta_prefs_get_window_binding (name, keysym, modifiers); + } + else + { + *keysym = 0; + *modifiers = 0; + } +} + +const char* +meta_core_get_workspace_name_with_index (Display *xdisplay, + Window xroot, + int index) +{ + MetaDisplay *display; + MetaScreen *screen; + MetaWorkspace *workspace; + + display = meta_display_for_x_display (xdisplay); + screen = meta_display_screen_for_root (display, xroot); + g_assert (screen != NULL); + workspace = meta_screen_get_workspace_by_index (screen, index); + return workspace ? meta_workspace_get_name (workspace) : NULL; +} + +gboolean +meta_core_begin_grab_op (Display *xdisplay, + Window frame_xwindow, + MetaGrabOp op, + gboolean pointer_already_grabbed, + gboolean frame_action, + int button, + gulong modmask, + guint32 timestamp, + int root_x, + int root_y) +{ + MetaWindow *window = get_window (xdisplay, frame_xwindow); + MetaDisplay *display; + MetaScreen *screen; + + display = meta_display_for_x_display (xdisplay); + screen = meta_display_screen_for_xwindow (display, frame_xwindow); + + g_assert (screen != NULL); + + return meta_display_begin_grab_op (display, screen, window, + op, pointer_already_grabbed, + frame_action, + button, modmask, + timestamp, root_x, root_y); +} + +void +meta_core_end_grab_op (Display *xdisplay, + guint32 timestamp) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + meta_display_end_grab_op (display, timestamp); +} + +MetaGrabOp +meta_core_get_grab_op (Display *xdisplay) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + return display->grab_op; +} + +Window +meta_core_get_grab_frame (Display *xdisplay) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + g_assert (display != NULL); + g_assert (display->grab_op == META_GRAB_OP_NONE || + display->grab_screen != NULL); + g_assert (display->grab_op == META_GRAB_OP_NONE || + display->grab_screen->display->xdisplay == xdisplay); + + if (display->grab_op != META_GRAB_OP_NONE && + display->grab_window && + display->grab_window->frame) + return display->grab_window->frame->xwindow; + else + return None; +} + +int +meta_core_get_grab_button (Display *xdisplay) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + if (display->grab_op == META_GRAB_OP_NONE) + return -1; + + return display->grab_button; +} + +void +meta_core_grab_buttons (Display *xdisplay, + Window frame_xwindow) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + meta_verbose ("Grabbing buttons on frame 0x%lx\n", frame_xwindow); + meta_display_grab_window_buttons (display, frame_xwindow); +} + +void +meta_core_set_screen_cursor (Display *xdisplay, + Window frame_on_screen, + MetaCursor cursor) +{ + MetaWindow *window = get_window (xdisplay, frame_on_screen); + + meta_frame_set_screen_cursor (window->frame, cursor); +} + +void +meta_core_increment_event_serial (Display *xdisplay) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + meta_display_increment_event_serial (display); +} + +void +meta_invalidate_default_icons (void) +{ + MetaDisplay *display = meta_get_display (); + GSList *windows; + GSList *l; + + if (display == NULL) + return; /* We can validly be called before the display is opened. */ + + windows = meta_display_list_windows (display); + for (l = windows; l != NULL; l = l->next) + { + MetaWindow *window = (MetaWindow*)l->data; + + if (window->icon_cache.origin == USING_FALLBACK_ICON) + { + meta_icon_cache_free (&(window->icon_cache)); + meta_window_update_icon_now (window); + } + } + + g_slist_free (windows); +} + diff --git a/src/core/delete.c b/src/core/delete.c new file mode 100644 index 00000000..f0590e3b --- /dev/null +++ b/src/core/delete.c @@ -0,0 +1,266 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window deletion */ + +/* + * Copyright (C) 2001, 2002 Havoc Pennington + * Copyright (C) 2004 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#define _GNU_SOURCE +#define _SVID_SOURCE /* for gethostname() */ + +#include <config.h> +#include "util.h" +#include "window-private.h" +#include "errors.h" +#include "workspace.h" + +#include <sys/types.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +static void meta_window_present_delete_dialog (MetaWindow *window, + guint32 timestamp); + +static void +delete_ping_reply_func (MetaDisplay *display, + Window xwindow, + guint32 timestamp, + void *user_data) +{ + meta_topic (META_DEBUG_PING, + "Got reply to delete ping for %s\n", + ((MetaWindow*)user_data)->desc); + + /* we do nothing */ +} + +static void +dialog_exited (GPid pid, + int status, + gpointer user_data) +{ + MetaWindow *ours = (MetaWindow*) user_data; + + ours->dialog_pid = -1; + + /* exit status of 1 means the user pressed "Force Quit" */ + if (WIFEXITED (status) && WEXITSTATUS (status) == 1) + meta_window_kill (ours); +} + +static void +delete_ping_timeout_func (MetaDisplay *display, + Window xwindow, + guint32 timestamp, + void *user_data) +{ + MetaWindow *window = user_data; + char *window_title; + gchar *window_content, *tmp; + GPid dialog_pid; + + meta_topic (META_DEBUG_PING, + "Got delete ping timeout for %s\n", + window->desc); + + if (window->dialog_pid >= 0) + { + meta_window_present_delete_dialog (window, timestamp); + return; + } + + window_title = g_locale_from_utf8 (window->title, -1, NULL, NULL, NULL); + + /* Translators: %s is a window title */ + tmp = g_strdup_printf (_("<tt>%s</tt> is not responding."), + window_title); + window_content = g_strdup_printf ( + "<big><b>%s</b></big>\n\n<i>%s</i>", + tmp, + _("You may choose to wait a short while for it to " + "continue or force the application to quit entirely.")); + + g_free (window_title); + + dialog_pid = + meta_show_dialog ("--question", + window_content, 0, + window->screen->number, + _("_Wait"), _("_Force Quit"), window->xwindow, + NULL, NULL); + + g_free (window_content); + g_free (tmp); + + window->dialog_pid = dialog_pid; + g_child_watch_add (dialog_pid, dialog_exited, window); +} + +void +meta_window_delete (MetaWindow *window, + guint32 timestamp) +{ + meta_error_trap_push (window->display); + if (window->delete_window) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Deleting %s with delete_window request\n", + window->desc); + meta_window_send_icccm_message (window, + window->display->atom_WM_DELETE_WINDOW, + timestamp); + } + else + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Deleting %s with explicit kill\n", + window->desc); + XKillClient (window->display->xdisplay, window->xwindow); + } + meta_error_trap_pop (window->display, FALSE); + + meta_display_ping_window (window->display, + window, + timestamp, + delete_ping_reply_func, + delete_ping_timeout_func, + window); + + if (window->has_focus) + { + /* FIXME Clean this up someday + * http://bugzilla.gnome.org/show_bug.cgi?id=108706 + */ +#if 0 + /* This is unfortunately going to result in weirdness + * if the window doesn't respond to the delete event. + * I don't know how to avoid that though. + */ + meta_topic (META_DEBUG_FOCUS, + "Focusing default window because focus window %s was deleted/killed\n", + window->desc); + meta_workspace_focus_default_window (window->screen->active_workspace, + window); +#else + meta_topic (META_DEBUG_FOCUS, + "Not unfocusing %s on delete/kill\n", + window->desc); +#endif + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Window %s was deleted/killed but didn't have focus\n", + window->desc); + } +} + + +void +meta_window_kill (MetaWindow *window) +{ + char buf[257]; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Killing %s brutally\n", + window->desc); + + if (window->wm_client_machine != NULL && + window->net_wm_pid > 0) + { + if (gethostname (buf, sizeof(buf)-1) == 0) + { + if (strcmp (buf, window->wm_client_machine) == 0) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Killing %s with kill()\n", + window->desc); + + if (kill (window->net_wm_pid, 9) < 0) + meta_topic (META_DEBUG_WINDOW_OPS, + "Failed to signal %s: %s\n", + window->desc, strerror (errno)); + } + } + else + { + meta_warning (_("Failed to get hostname: %s\n"), + strerror (errno)); + } + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Disconnecting %s with XKillClient()\n", + window->desc); + meta_error_trap_push (window->display); + XKillClient (window->display->xdisplay, window->xwindow); + meta_error_trap_pop (window->display, FALSE); +} + +void +meta_window_free_delete_dialog (MetaWindow *window) +{ + if (window->dialog_pid >= 0) + { + kill (window->dialog_pid, 9); + window->dialog_pid = -1; + } +} + +static void +meta_window_present_delete_dialog (MetaWindow *window, guint32 timestamp) +{ + meta_topic (META_DEBUG_PING, + "Presenting existing ping dialog for %s\n", + window->desc); + + if (window->dialog_pid >= 0) + { + GSList *windows; + GSList *tmp; + + /* Activate transient for window that belongs to + * marco-dialog + */ + + windows = meta_display_list_windows (window->display); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->xtransient_for == window->xwindow && + w->res_class && + g_ascii_strcasecmp (w->res_class, "marco-dialog") == 0) + { + meta_window_activate (w, timestamp); + break; + } + + tmp = tmp->next; + } + + g_slist_free (windows); + } +} diff --git a/src/core/display-private.h b/src/core/display-private.h new file mode 100644 index 00000000..692e25f2 --- /dev/null +++ b/src/core/display-private.h @@ -0,0 +1,513 @@ +/* Marco X display handler */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_DISPLAY_PRIVATE_H +#define META_DISPLAY_PRIVATE_H + +#ifndef PACKAGE + #error "config.h not included" +#endif + +#include <glib.h> +#include <X11/Xlib.h> +#include "eventqueue.h" +#include "common.h" +#include "boxes.h" +#include "display.h" + +#ifdef HAVE_STARTUP_NOTIFICATION + #include <libsn/sn.h> +#endif + +#ifdef HAVE_XSYNC + #include <X11/extensions/sync.h> +#endif + +typedef struct _MetaKeyBinding MetaKeyBinding; +typedef struct _MetaStack MetaStack; +typedef struct _MetaUISlave MetaUISlave; +typedef struct _MetaWorkspace MetaWorkspace; + +typedef struct _MetaGroupPropHooks MetaGroupPropHooks; + +typedef struct MetaEdgeResistanceData MetaEdgeResistanceData; + +typedef void (*MetaWindowPingFunc) (MetaDisplay* display, Window xwindow, guint32 timestamp, gpointer user_data); + + +#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ +#define _NET_WM_STATE_ADD 1 /* add/set property */ +#define _NET_WM_STATE_TOGGLE 2 /* toggle property */ + +/* This is basically a bogus number, just has to be large enough + * to handle the expected case of the alt+tab operation, where + * we want to ignore serials from UnmapNotify on the tab popup, + * and the LeaveNotify/EnterNotify from the pointer ungrab + */ +#define N_IGNORED_SERIALS 4 + +struct _MetaDisplay { + char* name; + Display* xdisplay; + + Window leader_window; + Window timestamp_pinging_window; + + /* Pull in all the names of atoms as fields; we will intern them when the + * class is constructed. + */ + #define item(x) Atom atom_##x; + #include "atomnames.h" + #undef item + + /* This is the actual window from focus events, + * not the one we last set + */ + MetaWindow* focus_window; + + /* window we are expecting a FocusIn event for or the current focus + * window if we are not expecting any FocusIn/FocusOut events; not + * perfect because applications can call XSetInputFocus directly. + * (It could also be messed up if a timestamp later than current + * time is sent to meta_display_set_input_focus_window, though that + * would be a programming error). See bug 154598 for more info. + */ + MetaWindow* expected_focus_window; + + /* last timestamp passed to XSetInputFocus */ + guint32 last_focus_time; + + /* last user interaction time in any app */ + guint32 last_user_time; + + /* whether we're using mousenav (only relevant for sloppy&mouse focus modes; + * !mouse_mode means "keynav mode") + */ + guint mouse_mode: 1; + + /* Helper var used when focus_new_windows setting is 'strict'; only + * relevant in 'strict' mode and if the focus window is a terminal. + * In that case, we don't allow new windows to take focus away from + * a terminal, but if the user explicitly did something that should + * allow a different window to gain focus (e.g. global keybinding or + * clicking on a dock), then we will allow the transfer. + */ + guint allow_terminal_deactivation: 1; + + guint static_gravity_works: 1; + + /*< private-ish >*/ + guint error_trap_synced_at_last_pop: 1; + MetaEventQueue* events; + GSList* screens; + MetaScreen* active_screen; + GHashTable* window_ids; + int error_traps; + int (*error_trap_handler) (Display* display, XErrorEvent* error); + int server_grab_count; + + /* serials of leave/unmap events that may + * correspond to an enter event we should + * ignore + */ + unsigned long ignored_serials[N_IGNORED_SERIALS]; + Window ungrab_should_not_cause_focus_window; + + guint32 current_time; + + /* Pings which we're waiting for a reply from */ + GSList* pending_pings; + + /* Pending autoraise */ + guint autoraise_timeout_id; + MetaWindow* autoraise_window; + + /* Alt+click button grabs */ + unsigned int window_grab_modifiers; + + /* current window operation */ + MetaGrabOp grab_op; + MetaScreen* grab_screen; + MetaWindow* grab_window; + Window grab_xwindow; + int grab_button; + int grab_anchor_root_x; + int grab_anchor_root_y; + MetaRectangle grab_anchor_window_pos; + int grab_latest_motion_x; + int grab_latest_motion_y; + gulong grab_mask; + guint grab_have_pointer : 1; + guint grab_have_keyboard : 1; + guint grab_wireframe_active : 1; + guint grab_was_cancelled : 1; /* Only used in wireframe mode */ + guint grab_frame_action : 1; + MetaRectangle grab_wireframe_rect; + MetaRectangle grab_wireframe_last_xor_rect; + MetaRectangle grab_initial_window_pos; + int grab_initial_x, grab_initial_y; /* These are only relevant for */ + gboolean grab_threshold_movement_reached; /* raise_on_click == FALSE. */ + MetaResizePopup* grab_resize_popup; + GTimeVal grab_last_moveresize_time; + guint32 grab_motion_notify_time; + int grab_wireframe_last_display_width; + int grab_wireframe_last_display_height; + GList* grab_old_window_stacking; + MetaEdgeResistanceData* grab_edge_resistance_data; + unsigned int grab_last_user_action_was_snap; + + /* we use property updates as sentinels for certain window focus events + * to avoid some race conditions on EnterNotify events + */ + int sentinel_counter; + + #ifdef HAVE_XKB + int xkb_base_event_type; + guint32 last_bell_time; + #endif + + #ifdef HAVE_XSYNC + /* alarm monitoring client's _NET_WM_SYNC_REQUEST_COUNTER */ + XSyncAlarm grab_sync_request_alarm; + #endif + + int grab_resize_timeout_id; + + /* Keybindings stuff */ + MetaKeyBinding* key_bindings; + int n_key_bindings; + int min_keycode; + int max_keycode; + KeySym* keymap; + int keysyms_per_keycode; + XModifierKeymap* modmap; + unsigned int ignored_modifier_mask; + unsigned int num_lock_mask; + unsigned int scroll_lock_mask; + unsigned int hyper_mask; + unsigned int super_mask; + unsigned int meta_mask; + + /* Xinerama cache */ + unsigned int xinerama_cache_invalidated: 1; + + /* Opening the display */ + unsigned int display_opening: 1; + + /* Closing down the display */ + int closing; + + /* To detect double clicks + * + * https://github.com/stefano-k/Mate-Desktop-Environment/commit/b0e5fb03eb21dae8f02692f11ef391bfc5ccba33 + */ + guint button_click_number; + Window button_click_window; + int button_click_x; + int button_click_y; + guint32 button_click_time; + + /* Managed by group.c */ + GHashTable* groups_by_leader; + + /* currently-active window menu if any */ + MetaWindowMenu* window_menu; + MetaWindow* window_with_menu; + + /* Managed by window-props.c */ + gpointer* prop_hooks_table; + GHashTable* prop_hooks; + + /* Managed by group-props.c */ + MetaGroupPropHooks* group_prop_hooks; + + /* Managed by compositor.c */ + MetaCompositor* compositor; + + #ifdef HAVE_STARTUP_NOTIFICATION + SnDisplay* sn_display; + #endif + + #ifdef HAVE_XSYNC + int xsync_event_base; + int xsync_error_base; + #endif + + #ifdef HAVE_SHAPE + int shape_event_base; + int shape_error_base; + #endif + + #ifdef HAVE_RENDER + int render_event_base; + int render_error_base; + #endif + + #ifdef HAVE_COMPOSITE_EXTENSIONS + int composite_event_base; + int composite_error_base; + int composite_major_version; + int composite_minor_version; + int damage_event_base; + int damage_error_base; + int xfixes_event_base; + int xfixes_error_base; + #endif + + #ifdef HAVE_XSYNC + unsigned int have_xsync : 1; + #define META_DISPLAY_HAS_XSYNC(display) ((display)->have_xsync) + #else + #define META_DISPLAY_HAS_XSYNC(display) FALSE + #endif + + #ifdef HAVE_SHAPE + unsigned int have_shape : 1; + #define META_DISPLAY_HAS_SHAPE(display) ((display)->have_shape) + #else + #define META_DISPLAY_HAS_SHAPE(display) FALSE + #endif + + #ifdef HAVE_RENDER + unsigned int have_render : 1; + #define META_DISPLAY_HAS_RENDER(display) ((display)->have_render) + #else + #define META_DISPLAY_HAS_RENDER(display) FALSE + #endif + + #ifdef HAVE_COMPOSITE_EXTENSIONS + unsigned int have_composite : 1; + unsigned int have_damage : 1; + unsigned int have_xfixes : 1; + #define META_DISPLAY_HAS_COMPOSITE(display) ((display)->have_composite) + #define META_DISPLAY_HAS_DAMAGE(display) ((display)->have_damage) + #define META_DISPLAY_HAS_XFIXES(display) ((display)->have_xfixes) + #else + #define META_DISPLAY_HAS_COMPOSITE(display) FALSE + #define META_DISPLAY_HAS_DAMAGE(display) FALSE + #define META_DISPLAY_HAS_XFIXES(display) FALSE + #endif +}; + +/* Xserver time can wraparound, thus comparing two timestamps needs to take + * this into account. Here's a little macro to help out. If no wraparound + * has occurred, this is equivalent to + * time1 < time2 + * Of course, the rest of the ugliness of this macro comes from accounting + * for the fact that wraparound can occur and the fact that a timestamp of + * 0 must be special-cased since it means older than anything else. + * + * Note that this is NOT an equivalent for time1 <= time2; if that's what + * you need then you'll need to swap the order of the arguments and negate + * the result. + */ +#define XSERVER_TIME_IS_BEFORE_ASSUMING_REAL_TIMESTAMPS(time1, time2) \ + ( (( (time1) < (time2) ) && ( (time2) - (time1) < ((guint32)-1)/2 )) || \ + (( (time1) > (time2) ) && ( (time1) - (time2) > ((guint32)-1)/2 )) \ + ) +#define XSERVER_TIME_IS_BEFORE(time1, time2) \ + ( (time1) == 0 || \ + (XSERVER_TIME_IS_BEFORE_ASSUMING_REAL_TIMESTAMPS(time1, time2) && \ + (time2) != 0) \ + ) + +gboolean meta_display_open (void); +void meta_display_close (MetaDisplay *display, + guint32 timestamp); +MetaScreen* meta_display_screen_for_x_screen (MetaDisplay *display, + Screen *screen); +MetaScreen* meta_display_screen_for_xwindow (MetaDisplay *display, + Window xindow); +void meta_display_grab (MetaDisplay *display); +void meta_display_ungrab (MetaDisplay *display); + +void meta_display_unmanage_screen (MetaDisplay **display, + MetaScreen *screen, + guint32 timestamp); + +void meta_display_unmanage_windows_for_screen (MetaDisplay *display, + MetaScreen *screen, + guint32 timestamp); + +/* Utility function to compare the stacking of two windows */ +int meta_display_stack_cmp (const void *a, + const void *b); + +/* A given MetaWindow may have various X windows that "belong" + * to it, such as the frame window. + */ +MetaWindow* meta_display_lookup_x_window (MetaDisplay *display, + Window xwindow); +void meta_display_register_x_window (MetaDisplay *display, + Window *xwindowp, + MetaWindow *window); +void meta_display_unregister_x_window (MetaDisplay *display, + Window xwindow); +/* Return whether the xwindow is a no focus window for any of the screens */ +gboolean meta_display_xwindow_is_a_no_focus_window (MetaDisplay *display, + Window xwindow); + +GSList* meta_display_list_windows (MetaDisplay *display); + +MetaDisplay* meta_display_for_x_display (Display *xdisplay); +MetaDisplay* meta_get_display (void); + +Cursor meta_display_create_x_cursor (MetaDisplay *display, + MetaCursor cursor); + +void meta_display_set_grab_op_cursor (MetaDisplay *display, + MetaScreen *screen, + MetaGrabOp op, + gboolean change_pointer, + Window grab_xwindow, + guint32 timestamp); + +gboolean meta_display_begin_grab_op (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + MetaGrabOp op, + gboolean pointer_already_grabbed, + gboolean frame_action, + int button, + gulong modmask, + guint32 timestamp, + int root_x, + int root_y); +void meta_display_end_grab_op (MetaDisplay *display, + guint32 timestamp); + +void meta_display_check_threshold_reached (MetaDisplay *display, + int x, + int y); +void meta_display_grab_window_buttons (MetaDisplay *display, + Window xwindow); +void meta_display_ungrab_window_buttons (MetaDisplay *display, + Window xwindow); + +void meta_display_grab_focus_window_button (MetaDisplay *display, + MetaWindow *window); +void meta_display_ungrab_focus_window_button (MetaDisplay *display, + MetaWindow *window); + +/* Next two functions are defined in edge-resistance.c */ +void meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display); +void meta_display_cleanup_edges (MetaDisplay *display); + +/* make a request to ensure the event serial has changed */ +void meta_display_increment_event_serial (MetaDisplay *display); + +void meta_display_update_active_window_hint (MetaDisplay *display); + +guint32 meta_display_get_current_time (MetaDisplay *display); +guint32 meta_display_get_current_time_roundtrip (MetaDisplay *display); + +/* utility goo */ +const char* meta_event_mode_to_string (int m); +const char* meta_event_detail_to_string (int d); + +void meta_display_queue_retheme_all_windows (MetaDisplay *display); +void meta_display_retheme_all (void); + +void meta_display_set_cursor_theme (const char *theme, + int size); + +void meta_display_ping_window (MetaDisplay *display, + MetaWindow *window, + guint32 timestamp, + MetaWindowPingFunc ping_reply_func, + MetaWindowPingFunc ping_timeout_func, + void *user_data); +gboolean meta_display_window_has_pending_pings (MetaDisplay *display, + MetaWindow *window); + +typedef enum +{ + META_TAB_LIST_NORMAL, + META_TAB_LIST_DOCKS, + META_TAB_LIST_GROUP +} MetaTabList; + +typedef enum +{ + META_TAB_SHOW_ICON, /* Alt-Tab mode */ + META_TAB_SHOW_INSTANTLY /* Alt-Esc mode */ +} MetaTabShowType; + +GList* meta_display_get_tab_list (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace); + +MetaWindow* meta_display_get_tab_next (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace, + MetaWindow *window, + gboolean backward); + +MetaWindow* meta_display_get_tab_current (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace); + +int meta_resize_gravity_from_grab_op (MetaGrabOp op); + +gboolean meta_grab_op_is_moving (MetaGrabOp op); +gboolean meta_grab_op_is_resizing (MetaGrabOp op); + +void meta_display_devirtualize_modifiers (MetaDisplay *display, + MetaVirtualModifier modifiers, + unsigned int *mask); + +void meta_display_increment_focus_sentinel (MetaDisplay *display); +void meta_display_decrement_focus_sentinel (MetaDisplay *display); +gboolean meta_display_focus_sentinel_clear (MetaDisplay *display); + +/* meta_display_set_input_focus_window is like XSetInputFocus, except + * that (a) it can't detect timestamps later than the current time, + * since Marco isn't part of the XServer, and thus gives erroneous + * behavior in this circumstance (so don't do it), (b) it uses + * display->last_focus_time since we don't have access to the true + * Xserver one, (c) it makes use of display->user_time since checking + * whether a window should be allowed to be focused should depend + * on user_time events (see bug 167358, comment 15 in particular) + */ +void meta_display_set_input_focus_window (MetaDisplay *display, + MetaWindow *window, + gboolean focus_frame, + guint32 timestamp); + +/* meta_display_focus_the_no_focus_window is called when the + * designated no_focus_window should be focused, but is otherwise the + * same as meta_display_set_input_focus_window + */ +void meta_display_focus_the_no_focus_window (MetaDisplay *display, + MetaScreen *screen, + guint32 timestamp); + +void meta_display_queue_autoraise_callback (MetaDisplay *display, + MetaWindow *window); +void meta_display_remove_autoraise_callback (MetaDisplay *display); + +#endif /* META_DISPLAY_PRIVATE_H */ diff --git a/src/core/display.c b/src/core/display.c new file mode 100644 index 00000000..59ec9021 --- /dev/null +++ b/src/core/display.c @@ -0,0 +1,5355 @@ +/* Marco X display handler */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002, 2003, 2004 Red Hat, Inc. + * Copyright (C) 2003, 2004 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file display.c Handles operations on an X display. + * + * The display is represented as a MetaDisplay struct. + */ + +#include <config.h> +#include "display-private.h" +#include "util.h" +#include "main.h" +#include "screen-private.h" +#include "window-private.h" +#include "window-props.h" +#include "group-props.h" +#include "frame-private.h" +#include "errors.h" +#include "keybindings.h" +#include "prefs.h" +#include "resizepopup.h" +#include "xprops.h" +#include "workspace.h" +#include "bell.h" +#include "effects.h" +#include "compositor.h" +#include <X11/Xatom.h> +#include <X11/cursorfont.h> + +#ifdef HAVE_SOLARIS_XINERAMA + #include <X11/extensions/xinerama.h> +#endif + +#ifdef HAVE_XFREE_XINERAMA + #include <X11/extensions/Xinerama.h> +#endif + +#ifdef HAVE_RANDR + #include <X11/extensions/Xrandr.h> +#endif + +#ifdef HAVE_SHAPE + #include <X11/extensions/shape.h> +#endif + +#ifdef HAVE_RENDER + #include <X11/extensions/Xrender.h> +#endif + +#ifdef HAVE_XKB + #include <X11/XKBlib.h> +#endif + +#ifdef HAVE_XCURSOR + #include <X11/Xcursor/Xcursor.h> +#endif + +#ifdef HAVE_COMPOSITE_EXTENSIONS + #include <X11/extensions/Xcomposite.h> + #include <X11/extensions/Xdamage.h> + #include <X11/extensions/Xfixes.h> + #include <gtk/gtk.h> +#endif + +#include <string.h> + +#define GRAB_OP_IS_WINDOW_SWITCH(g) \ + (g == META_GRAB_OP_KEYBOARD_TABBING_NORMAL || \ + g == META_GRAB_OP_KEYBOARD_TABBING_DOCK || \ + g == META_GRAB_OP_KEYBOARD_TABBING_GROUP || \ + g == META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL || \ + g == META_GRAB_OP_KEYBOARD_ESCAPING_DOCK || \ + g == META_GRAB_OP_KEYBOARD_ESCAPING_GROUP) + +/** + * \defgroup pings Pings + * + * Sometimes we want to see whether a window is responding, + * so we send it a "ping" message and see whether it sends us back a "pong" + * message within a reasonable time. Here we have a system which lets us + * nominate one function to be called if we get the pong in time and another + * function if we don't. The system is rather more complicated than it needs + * to be, since we only ever use it to destroy windows which are asked to + * close themselves and don't do so within a reasonable amount of time, and + * therefore we always use the same callbacks. It's possible that we might + * use it for other things in future, or on the other hand we might decide + * that we're never going to do so and simplify it a bit. + */ + +/** + * Describes a ping on a window. When we send a ping to a window, we build + * one of these structs, and it eventually gets passed to the timeout function + * or to the function which handles the response from the window. If the window + * does or doesn't respond to the ping, we use this information to deal with + * these facts; we have a handler function for each. + * + * \ingroup pings + */ +typedef struct +{ + MetaDisplay *display; + Window xwindow; + guint32 timestamp; + MetaWindowPingFunc ping_reply_func; + MetaWindowPingFunc ping_timeout_func; + void *user_data; + guint ping_timeout_id; +} MetaPingData; + +typedef struct +{ + MetaDisplay *display; + Window xwindow; +} MetaAutoRaiseData; + +/** + * The display we're managing. This is a singleton object. (Historically, + * this was a list of displays, but there was never any way to add more + * than one element to it.) The goofy name is because we don't want it + * to shadow the parameter in its object methods. + */ +static MetaDisplay *the_display = NULL; + +#ifdef WITH_VERBOSE_MODE +static void meta_spew_event (MetaDisplay *display, + XEvent *event); +#endif + +static gboolean event_callback (XEvent *event, + gpointer data); +static Window event_get_modified_window (MetaDisplay *display, + XEvent *event); +static guint32 event_get_time (MetaDisplay *display, + XEvent *event); +static void process_request_frame_extents (MetaDisplay *display, + XEvent *event); +static void process_pong_message (MetaDisplay *display, + XEvent *event); +static void process_selection_request (MetaDisplay *display, + XEvent *event); +static void process_selection_clear (MetaDisplay *display, + XEvent *event); + +static void update_window_grab_modifiers (MetaDisplay *display); + +static void prefs_changed_callback (MetaPreference pref, + void *data); + +static void sanity_check_timestamps (MetaDisplay *display, + guint32 known_good_timestamp); + +MetaGroup* get_focussed_group (MetaDisplay *display); + +/** + * Destructor for MetaPingData structs. Will destroy the + * event source for the struct as well. + * + * \ingroup pings + */ +static void +ping_data_free (MetaPingData *ping_data) +{ + /* Remove the timeout */ + if (ping_data->ping_timeout_id != 0) + g_source_remove (ping_data->ping_timeout_id); + + g_free (ping_data); +} + +/** + * Frees every pending ping structure for the given X window on the + * given display. This means that we also destroy the timeouts. + * + * \param display The display the window appears on + * \param xwindow The X ID of the window whose pings we should remove + * + * \ingroup pings + * + */ +static void +remove_pending_pings_for_window (MetaDisplay *display, Window xwindow) +{ + GSList *tmp; + GSList *dead; + + /* could obviously be more efficient, don't care */ + + /* build list to be removed */ + dead = NULL; + for (tmp = display->pending_pings; tmp; tmp = tmp->next) + { + MetaPingData *ping_data = tmp->data; + + if (ping_data->xwindow == xwindow) + dead = g_slist_prepend (dead, ping_data); + } + + /* remove what we found */ + for (tmp = dead; tmp; tmp = tmp->next) + { + MetaPingData *ping_data = tmp->data; + + display->pending_pings = g_slist_remove (display->pending_pings, ping_data); + ping_data_free (ping_data); + } + + g_slist_free (dead); +} + + +#ifdef HAVE_STARTUP_NOTIFICATION +static void +sn_error_trap_push (SnDisplay *sn_display, + Display *xdisplay) +{ + MetaDisplay *display; + display = meta_display_for_x_display (xdisplay); + if (display != NULL) + meta_error_trap_push (display); +} + +static void +sn_error_trap_pop (SnDisplay *sn_display, + Display *xdisplay) +{ + MetaDisplay *display; + display = meta_display_for_x_display (xdisplay); + if (display != NULL) + meta_error_trap_pop (display, FALSE); +} +#endif + +static void +enable_compositor (MetaDisplay *display, + gboolean composite_windows) +{ + GSList *list; + + if (!META_DISPLAY_HAS_COMPOSITE (display) || + !META_DISPLAY_HAS_DAMAGE (display) || + !META_DISPLAY_HAS_XFIXES (display) || + !META_DISPLAY_HAS_RENDER (display)) + { + meta_warning (_("Missing %s extension required for compositing"), + !META_DISPLAY_HAS_COMPOSITE (display) ? "composite" : + !META_DISPLAY_HAS_DAMAGE (display) ? "damage" : + !META_DISPLAY_HAS_XFIXES (display) ? "xfixes" : "render"); + return; + } + + if (!display->compositor) + display->compositor = meta_compositor_new (display); + + if (!display->compositor) + return; + + for (list = display->screens; list != NULL; list = list->next) + { + MetaScreen *screen = list->data; + + meta_compositor_manage_screen (screen->display->compositor, + screen); + + if (composite_windows) + meta_screen_composite_all_windows (screen); + } +} + +static void +disable_compositor (MetaDisplay *display) +{ + GSList *list; + + if (!display->compositor) + return; + + for (list = display->screens; list != NULL; list = list->next) + { + MetaScreen *screen = list->data; + + meta_compositor_unmanage_screen (screen->display->compositor, + screen); + } + + meta_compositor_destroy (display->compositor); + display->compositor = NULL; +} + +/** + * Opens a new display, sets it up, initialises all the X extensions + * we will need, and adds it to the list of displays. + * + * \return True if the display was opened successfully, and False + * otherwise-- that is, if the display doesn't exist or it already + * has a window manager. + * + * \ingroup main + */ +gboolean +meta_display_open (void) +{ + Display *xdisplay; + GSList *screens; + GSList *tmp; + int i; + guint32 timestamp; + + /* A list of all atom names, so that we can intern them in one go. */ + char *atom_names[] = { +#define item(x) #x, +#include "atomnames.h" +#undef item + }; + Atom atoms[G_N_ELEMENTS(atom_names)]; + + meta_verbose ("Opening display '%s'\n", XDisplayName (NULL)); + + xdisplay = meta_ui_get_display (); + + if (xdisplay == NULL) + { + meta_warning (_("Failed to open X Window System display '%s'\n"), + XDisplayName (NULL)); + return FALSE; + } + + if (meta_is_syncing ()) + XSynchronize (xdisplay, True); + + g_assert (the_display == NULL); + the_display = g_new (MetaDisplay, 1); + + the_display->closing = 0; + + /* here we use XDisplayName which is what the user + * probably put in, vs. DisplayString(display) which is + * canonicalized by XOpenDisplay() + */ + the_display->name = g_strdup (XDisplayName (NULL)); + the_display->xdisplay = xdisplay; + the_display->error_trap_synced_at_last_pop = TRUE; + the_display->error_traps = 0; + the_display->error_trap_handler = NULL; + the_display->server_grab_count = 0; + the_display->display_opening = TRUE; + + the_display->pending_pings = NULL; + the_display->autoraise_timeout_id = 0; + the_display->autoraise_window = NULL; + the_display->focus_window = NULL; + the_display->expected_focus_window = NULL; + the_display->grab_old_window_stacking = NULL; + + the_display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */ + the_display->allow_terminal_deactivation = TRUE; /* Only relevant for when a + terminal has the focus */ + +#ifdef HAVE_XSYNC + the_display->grab_sync_request_alarm = None; +#endif + + /* FIXME copy the checks from GDK probably */ + the_display->static_gravity_works = g_getenv ("MARCO_USE_STATIC_GRAVITY") != NULL; + + meta_bell_init (the_display); + + meta_display_init_keys (the_display); + + update_window_grab_modifiers (the_display); + + meta_prefs_add_listener (prefs_changed_callback, the_display); + + meta_verbose ("Creating %d atoms\n", (int) G_N_ELEMENTS (atom_names)); + XInternAtoms (the_display->xdisplay, atom_names, G_N_ELEMENTS (atom_names), + False, atoms); + { + int i = 0; +#define item(x) the_display->atom_##x = atoms[i++]; +#include "atomnames.h" +#undef item + } + + the_display->prop_hooks = NULL; + meta_display_init_window_prop_hooks (the_display); + the_display->group_prop_hooks = NULL; + meta_display_init_group_prop_hooks (the_display); + + /* Offscreen unmapped window used for _NET_SUPPORTING_WM_CHECK, + * created in screen_new + */ + the_display->leader_window = None; + the_display->timestamp_pinging_window = None; + + the_display->xinerama_cache_invalidated = TRUE; + + the_display->groups_by_leader = NULL; + + the_display->window_with_menu = NULL; + the_display->window_menu = NULL; + + the_display->screens = NULL; + the_display->active_screen = NULL; + +#ifdef HAVE_STARTUP_NOTIFICATION + the_display->sn_display = sn_display_new (the_display->xdisplay, + sn_error_trap_push, + sn_error_trap_pop); +#endif + + the_display->events = NULL; + + /* Get events */ + meta_ui_add_event_func (the_display->xdisplay, + event_callback, + the_display); + + the_display->window_ids = g_hash_table_new (meta_unsigned_long_hash, + meta_unsigned_long_equal); + + i = 0; + while (i < N_IGNORED_SERIALS) + { + the_display->ignored_serials[i] = 0; + ++i; + } + the_display->ungrab_should_not_cause_focus_window = None; + + the_display->current_time = CurrentTime; + the_display->sentinel_counter = 0; + + the_display->grab_resize_timeout_id = 0; + the_display->grab_have_keyboard = FALSE; + +#ifdef HAVE_XKB + the_display->last_bell_time = 0; +#endif + + the_display->grab_op = META_GRAB_OP_NONE; + the_display->grab_wireframe_active = FALSE; + the_display->grab_window = NULL; + the_display->grab_screen = NULL; + the_display->grab_resize_popup = NULL; + + the_display->grab_edge_resistance_data = NULL; + +#ifdef HAVE_XSYNC + { + int major, minor; + + the_display->have_xsync = FALSE; + + the_display->xsync_error_base = 0; + the_display->xsync_event_base = 0; + + /* I don't think we really have to fill these in */ + major = SYNC_MAJOR_VERSION; + minor = SYNC_MINOR_VERSION; + + if (!XSyncQueryExtension (the_display->xdisplay, + &the_display->xsync_event_base, + &the_display->xsync_error_base) || + !XSyncInitialize (the_display->xdisplay, + &major, &minor)) + { + the_display->xsync_error_base = 0; + the_display->xsync_event_base = 0; + } + else + the_display->have_xsync = TRUE; + + meta_verbose ("Attempted to init Xsync, found version %d.%d error base %d event base %d\n", + major, minor, + the_display->xsync_error_base, + the_display->xsync_event_base); + } +#else /* HAVE_XSYNC */ + meta_verbose ("Not compiled with Xsync support\n"); +#endif /* !HAVE_XSYNC */ + + +#ifdef HAVE_SHAPE + { + the_display->have_shape = FALSE; + + the_display->shape_error_base = 0; + the_display->shape_event_base = 0; + + if (!XShapeQueryExtension (the_display->xdisplay, + &the_display->shape_event_base, + &the_display->shape_error_base)) + { + the_display->shape_error_base = 0; + the_display->shape_event_base = 0; + } + else + the_display->have_shape = TRUE; + + meta_verbose ("Attempted to init Shape, found error base %d event base %d\n", + the_display->shape_error_base, + the_display->shape_event_base); + } +#else /* HAVE_SHAPE */ + meta_verbose ("Not compiled with Shape support\n"); +#endif /* !HAVE_SHAPE */ + +#ifdef HAVE_RENDER + { + the_display->have_render = FALSE; + + the_display->render_error_base = 0; + the_display->render_event_base = 0; + + if (!XRenderQueryExtension (the_display->xdisplay, + &the_display->render_event_base, + &the_display->render_error_base)) + { + the_display->render_error_base = 0; + the_display->render_event_base = 0; + } + else + the_display->have_render = TRUE; + + meta_verbose ("Attempted to init Render, found error base %d event base %d\n", + the_display->render_error_base, + the_display->render_event_base); + } +#else /* HAVE_RENDER */ + meta_verbose ("Not compiled with Render support\n"); +#endif /* !HAVE_RENDER */ + +#ifdef HAVE_COMPOSITE_EXTENSIONS + { + the_display->have_composite = FALSE; + + the_display->composite_error_base = 0; + the_display->composite_event_base = 0; + + if (!XCompositeQueryExtension (the_display->xdisplay, + &the_display->composite_event_base, + &the_display->composite_error_base)) + { + the_display->composite_error_base = 0; + the_display->composite_event_base = 0; + } + else + { + the_display->composite_major_version = 0; + the_display->composite_minor_version = 0; + if (XCompositeQueryVersion (the_display->xdisplay, + &the_display->composite_major_version, + &the_display->composite_minor_version)) + { + the_display->have_composite = TRUE; + } + else + { + the_display->composite_major_version = 0; + the_display->composite_minor_version = 0; + } + } + + meta_verbose ("Attempted to init Composite, found error base %d event base %d " + "extn ver %d %d\n", + the_display->composite_error_base, + the_display->composite_event_base, + the_display->composite_major_version, + the_display->composite_minor_version); + + the_display->have_damage = FALSE; + + the_display->damage_error_base = 0; + the_display->damage_event_base = 0; + + if (!XDamageQueryExtension (the_display->xdisplay, + &the_display->damage_event_base, + &the_display->damage_error_base)) + { + the_display->damage_error_base = 0; + the_display->damage_event_base = 0; + } + else + the_display->have_damage = TRUE; + + meta_verbose ("Attempted to init Damage, found error base %d event base %d\n", + the_display->damage_error_base, + the_display->damage_event_base); + + the_display->have_xfixes = FALSE; + + the_display->xfixes_error_base = 0; + the_display->xfixes_event_base = 0; + + if (!XFixesQueryExtension (the_display->xdisplay, + &the_display->xfixes_event_base, + &the_display->xfixes_error_base)) + { + the_display->xfixes_error_base = 0; + the_display->xfixes_event_base = 0; + } + else + the_display->have_xfixes = TRUE; + + meta_verbose ("Attempted to init XFixes, found error base %d event base %d\n", + the_display->xfixes_error_base, + the_display->xfixes_event_base); + } +#else /* HAVE_COMPOSITE_EXTENSIONS */ + meta_verbose ("Not compiled with Composite support\n"); +#endif /* !HAVE_COMPOSITE_EXTENSIONS */ + +#ifdef HAVE_XCURSOR + { + XcursorSetTheme (the_display->xdisplay, meta_prefs_get_cursor_theme ()); + XcursorSetDefaultSize (the_display->xdisplay, meta_prefs_get_cursor_size ()); + } +#else /* HAVE_XCURSOR */ + meta_verbose ("Not compiled with Xcursor support\n"); +#endif /* !HAVE_XCURSOR */ + + /* Create the leader window here. Set its properties and + * use the timestamp from one of the PropertyNotify events + * that will follow. + */ + { + gulong data[1]; + XEvent event; + + /* We only care about the PropertyChangeMask in the next 30 or so lines of + * code. Note that gdk will at some point unset the PropertyChangeMask for + * this window, so we can't rely on it still being set later. See bug + * 354213 for details. + */ + the_display->leader_window = + meta_create_offscreen_window (the_display->xdisplay, + DefaultRootWindow (the_display->xdisplay), + PropertyChangeMask); + + meta_prop_set_utf8_string_hint (the_display, + the_display->leader_window, + the_display->atom__NET_WM_NAME, + "Marco"); + + meta_prop_set_utf8_string_hint (the_display, + the_display->leader_window, + the_display->atom__MARCO_VERSION, + VERSION); + + data[0] = the_display->leader_window; + XChangeProperty (the_display->xdisplay, + the_display->leader_window, + the_display->atom__NET_SUPPORTING_WM_CHECK, + XA_WINDOW, + 32, PropModeReplace, (guchar*) data, 1); + + XWindowEvent (the_display->xdisplay, + the_display->leader_window, + PropertyChangeMask, + &event); + + timestamp = event.xproperty.time; + + /* Make it painfully clear that we can't rely on PropertyNotify events on + * this window, as per bug 354213. + */ + XSelectInput(the_display->xdisplay, + the_display->leader_window, + NoEventMask); + } + + /* Make a little window used only for pinging the server for timestamps; note + * that meta_create_offscreen_window already selects for PropertyChangeMask. + */ + the_display->timestamp_pinging_window = + meta_create_offscreen_window (the_display->xdisplay, + DefaultRootWindow (the_display->xdisplay), + PropertyChangeMask); + + the_display->last_focus_time = timestamp; + the_display->last_user_time = timestamp; + the_display->compositor = NULL; + + screens = NULL; + + i = 0; + while (i < ScreenCount (xdisplay)) + { + MetaScreen *screen; + + screen = meta_screen_new (the_display, i, timestamp); + + if (screen) + screens = g_slist_prepend (screens, screen); + ++i; + } + + the_display->screens = screens; + + if (screens == NULL) + { + /* This would typically happen because all the screens already + * have window managers. + */ + meta_display_close (the_display, timestamp); + return FALSE; + } + + /* We don't composite the windows here because they will be composited + faster with the call to meta_screen_manage_all_windows further down + the code */ + if (meta_prefs_get_compositing_manager ()) + enable_compositor (the_display, FALSE); + + meta_display_grab (the_display); + + /* Now manage all existing windows */ + tmp = the_display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + meta_screen_manage_all_windows (screen); + + tmp = tmp->next; + } + + { + Window focus; + int ret_to; + + /* kinda bogus because GetInputFocus has no possible errors */ + meta_error_trap_push (the_display); + + /* FIXME: This is totally broken; see comment 9 of bug 88194 about this */ + focus = None; + ret_to = RevertToPointerRoot; + XGetInputFocus (the_display->xdisplay, &focus, &ret_to); + + /* Force a new FocusIn (does this work?) */ + + /* Use the same timestamp that was passed to meta_screen_new(), + * as it is the most recent timestamp. + */ + if (focus == None || focus == PointerRoot) + /* Just focus the no_focus_window on the first screen */ + meta_display_focus_the_no_focus_window (the_display, + the_display->screens->data, + timestamp); + else + { + MetaWindow * window; + window = meta_display_lookup_x_window (the_display, focus); + if (window) + meta_display_set_input_focus_window (the_display, window, FALSE, timestamp); + else + /* Just focus the no_focus_window on the first screen */ + meta_display_focus_the_no_focus_window (the_display, + the_display->screens->data, + timestamp); + } + + meta_error_trap_pop (the_display, FALSE); + } + + meta_display_ungrab (the_display); + + /* Done opening new display */ + the_display->display_opening = FALSE; + + return TRUE; +} + +static void +listify_func (gpointer key, gpointer value, gpointer data) +{ + GSList **listp; + + listp = data; + *listp = g_slist_prepend (*listp, value); +} + +static gint +ptrcmp (gconstpointer a, gconstpointer b) +{ + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; +} + +GSList* +meta_display_list_windows (MetaDisplay *display) +{ + GSList *winlist; + GSList *tmp; + GSList *prev; + + winlist = NULL; + g_hash_table_foreach (display->window_ids, + listify_func, + &winlist); + + /* Uniquify the list, since both frame windows and plain + * windows are in the hash + */ + winlist = g_slist_sort (winlist, ptrcmp); + + prev = NULL; + tmp = winlist; + while (tmp != NULL) + { + GSList *next; + + next = tmp->next; + + if (next && + next->data == tmp->data) + { + /* Delete tmp from list */ + + if (prev) + prev->next = next; + + if (tmp == winlist) + winlist = next; + + g_slist_free_1 (tmp); + + /* leave prev unchanged */ + } + else + { + prev = tmp; + } + + tmp = next; + } + + return winlist; +} + +void +meta_display_close (MetaDisplay *display, + guint32 timestamp) +{ + GSList *tmp; + + g_assert (display != NULL); + + if (display->closing != 0) + { + /* The display's already been closed. */ + return; + } + + if (display->error_traps > 0) + meta_bug ("Display closed with error traps pending\n"); + + display->closing += 1; + + meta_prefs_remove_listener (prefs_changed_callback, display); + + meta_display_remove_autoraise_callback (display); + + if (display->grab_old_window_stacking) + g_list_free (display->grab_old_window_stacking); + + /* Stop caring about events */ + meta_ui_remove_event_func (display->xdisplay, + event_callback, + display); + + /* Free all screens */ + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + meta_screen_free (screen, timestamp); + tmp = tmp->next; + } + + g_slist_free (display->screens); + display->screens = NULL; + +#ifdef HAVE_STARTUP_NOTIFICATION + if (display->sn_display) + { + sn_display_unref (display->sn_display); + display->sn_display = NULL; + } +#endif + + /* Must be after all calls to meta_window_free() since they + * unregister windows + */ + g_hash_table_destroy (display->window_ids); + + if (display->leader_window != None) + XDestroyWindow (display->xdisplay, display->leader_window); + + XFlush (display->xdisplay); + + meta_display_free_window_prop_hooks (display); + meta_display_free_group_prop_hooks (display); + + g_free (display->name); + + meta_display_shutdown_keys (display); + + if (display->compositor) + meta_compositor_destroy (display->compositor); + + g_free (display); + display = NULL; + + meta_quit (META_EXIT_SUCCESS); +} + +MetaScreen* +meta_display_screen_for_root (MetaDisplay *display, + Window xroot) +{ + GSList *tmp; + + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + if (xroot == screen->xroot) + return screen; + + tmp = tmp->next; + } + + return NULL; +} + +MetaScreen* +meta_display_screen_for_xwindow (MetaDisplay *display, + Window xwindow) +{ + XWindowAttributes attr; + int result; + + meta_error_trap_push (display); + attr.screen = NULL; + result = XGetWindowAttributes (display->xdisplay, xwindow, &attr); + meta_error_trap_pop (display, TRUE); + + /* Note, XGetWindowAttributes is on all kinds of crack + * and returns 1 on success 0 on failure, rather than Success + * on success. + */ + if (result == 0 || attr.screen == NULL) + return NULL; + + return meta_display_screen_for_x_screen (display, attr.screen); +} + +MetaScreen* +meta_display_screen_for_x_screen (MetaDisplay *display, + Screen *xscreen) +{ + GSList *tmp; + + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + if (xscreen == screen->xscreen) + return screen; + + tmp = tmp->next; + } + + return NULL; +} + +/* Grab/ungrab routines taken from fvwm */ +void +meta_display_grab (MetaDisplay *display) +{ + if (display->server_grab_count == 0) + { + XGrabServer (display->xdisplay); + } + display->server_grab_count += 1; + meta_verbose ("Grabbing display, grab count now %d\n", + display->server_grab_count); +} + +void +meta_display_ungrab (MetaDisplay *display) +{ + if (display->server_grab_count == 0) + meta_bug ("Ungrabbed non-grabbed server\n"); + + display->server_grab_count -= 1; + if (display->server_grab_count == 0) + { + /* FIXME we want to purge all pending "queued" stuff + * at this point, such as window hide/show + */ + XUngrabServer (display->xdisplay); + XFlush (display->xdisplay); + } + + meta_verbose ("Ungrabbing display, grab count now %d\n", + display->server_grab_count); +} + +/** + * Returns the singleton MetaDisplay if "xdisplay" matches the X display it's + * managing; otherwise gives a warning and returns NULL. When we were claiming + * to be able to manage multiple displays, this was supposed to find the + * display out of the list which matched that display. Now it's merely an + * extra sanity check. + * + * \param xdisplay An X display + * \return The singleton X display, or NULL if "xdisplay" isn't the one + * we're managing. + */ +MetaDisplay* +meta_display_for_x_display (Display *xdisplay) +{ + if (the_display->xdisplay == xdisplay) + return the_display; + + meta_warning ("Could not find display for X display %p, probably going to crash\n", + xdisplay); + + return NULL; +} + +/** + * Accessor for the singleton MetaDisplay. + * + * \return The only MetaDisplay there is. This can be NULL, but only + * during startup. + */ +MetaDisplay* +meta_get_display (void) +{ + return the_display; +} + +#ifdef WITH_VERBOSE_MODE +static gboolean dump_events = TRUE; +#endif + +static gboolean +grab_op_is_mouse_only (MetaGrabOp op) +{ + switch (op) + { + case META_GRAB_OP_MOVING: + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_E: + return TRUE; + + default: + return FALSE; + } +} + +static gboolean +grab_op_is_mouse (MetaGrabOp op) +{ + switch (op) + { + case META_GRAB_OP_MOVING: + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_MOVING: + return TRUE; + + default: + return FALSE; + } +} + +static gboolean +grab_op_is_keyboard (MetaGrabOp op) +{ + switch (op) + { + case META_GRAB_OP_KEYBOARD_MOVING: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_TABBING_NORMAL: + case META_GRAB_OP_KEYBOARD_TABBING_DOCK: + case META_GRAB_OP_KEYBOARD_TABBING_GROUP: + case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL: + case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK: + case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP: + case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING: + return TRUE; + + default: + return FALSE; + } +} + +gboolean +meta_grab_op_is_resizing (MetaGrabOp op) +{ + switch (op) + { + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + return TRUE; + + default: + return FALSE; + } +} + +gboolean +meta_grab_op_is_moving (MetaGrabOp op) +{ + switch (op) + { + case META_GRAB_OP_MOVING: + case META_GRAB_OP_KEYBOARD_MOVING: + return TRUE; + + default: + return FALSE; + } +} + +/* Get time of current event, or CurrentTime if none. */ +guint32 +meta_display_get_current_time (MetaDisplay *display) +{ + return display->current_time; +} + +/* Get a timestamp, even if it means a roundtrip */ +guint32 +meta_display_get_current_time_roundtrip (MetaDisplay *display) +{ + guint32 timestamp; + + timestamp = meta_display_get_current_time (display); + if (timestamp == CurrentTime) + { + XEvent property_event; + + /* Using the property XA_PRIMARY because it's safe; nothing + * would use it as a property. The type doesn't matter. + */ + XChangeProperty (display->xdisplay, + display->timestamp_pinging_window, + XA_PRIMARY, XA_STRING, 8, + PropModeAppend, NULL, 0); + XWindowEvent (display->xdisplay, + display->timestamp_pinging_window, + PropertyChangeMask, + &property_event); + timestamp = property_event.xproperty.time; + } + + sanity_check_timestamps (display, timestamp); + + return timestamp; +} + +static void +add_ignored_serial (MetaDisplay *display, + unsigned long serial) +{ + int i; + + /* don't add the same serial more than once */ + if (display->ignored_serials[N_IGNORED_SERIALS-1] == serial) + return; + + /* shift serials to the left */ + i = 0; + while (i < (N_IGNORED_SERIALS - 1)) + { + display->ignored_serials[i] = display->ignored_serials[i+1]; + ++i; + } + /* put new one on the end */ + display->ignored_serials[i] = serial; +} + +static gboolean +serial_is_ignored (MetaDisplay *display, + unsigned long serial) +{ + int i; + + i = 0; + while (i < N_IGNORED_SERIALS) + { + if (display->ignored_serials[i] == serial) + return TRUE; + ++i; + } + return FALSE; +} + +static void +reset_ignores (MetaDisplay *display) +{ + int i; + + i = 0; + while (i < N_IGNORED_SERIALS) + { + display->ignored_serials[i] = 0; + ++i; + } + + display->ungrab_should_not_cause_focus_window = None; +} + +static gboolean +window_raise_with_delay_callback (void *data) +{ + MetaWindow *window; + MetaAutoRaiseData *auto_raise; + + auto_raise = data; + + meta_topic (META_DEBUG_FOCUS, + "In autoraise callback for window 0x%lx\n", + auto_raise->xwindow); + + auto_raise->display->autoraise_timeout_id = 0; + auto_raise->display->autoraise_window = NULL; + + window = meta_display_lookup_x_window (auto_raise->display, + auto_raise->xwindow); + + if (window == NULL) + return FALSE; + + /* If we aren't already on top, check whether the pointer is inside + * the window and raise the window if so. + */ + if (meta_stack_get_top (window->screen->stack) != window) + { + int x, y, root_x, root_y; + Window root, child; + unsigned int mask; + gboolean same_screen; + gboolean point_in_window; + + meta_error_trap_push (window->display); + same_screen = XQueryPointer (window->display->xdisplay, + window->xwindow, + &root, &child, + &root_x, &root_y, &x, &y, &mask); + meta_error_trap_pop (window->display, TRUE); + + point_in_window = + (window->frame && POINT_IN_RECT (root_x, root_y, window->frame->rect)) || + (window->frame == NULL && POINT_IN_RECT (root_x, root_y, window->rect)); + if (same_screen && point_in_window) + meta_window_raise (window); + else + meta_topic (META_DEBUG_FOCUS, + "Pointer not inside window, not raising %s\n", + window->desc); + } + + return FALSE; +} + +void +meta_display_queue_autoraise_callback (MetaDisplay *display, + MetaWindow *window) +{ + MetaAutoRaiseData *auto_raise_data; + + meta_topic (META_DEBUG_FOCUS, + "Queuing an autoraise timeout for %s with delay %d\n", + window->desc, + meta_prefs_get_auto_raise_delay ()); + + auto_raise_data = g_new (MetaAutoRaiseData, 1); + auto_raise_data->display = window->display; + auto_raise_data->xwindow = window->xwindow; + + if (display->autoraise_timeout_id != 0) + g_source_remove (display->autoraise_timeout_id); + + display->autoraise_timeout_id = + g_timeout_add_full (G_PRIORITY_DEFAULT, + meta_prefs_get_auto_raise_delay (), + window_raise_with_delay_callback, + auto_raise_data, + g_free); + display->autoraise_window = window; +} + +#if 0 +static void +handle_net_restack_window (MetaDisplay* display, + XEvent *event) +{ + MetaWindow *window; + + window = meta_display_lookup_x_window (display, + event->xclient.window); + + if (window) + { + /* FIXME: The EWMH includes a sibling for the restack request, but we + * (stupidly) don't currently support these types of raises. + * + * Also, unconditionally following these is REALLY stupid--we should + * combine this code with the stuff in + * meta_window_configure_request() which is smart about whether to + * follow the request or do something else (though not smart enough + * and is also too stupid to handle the sibling stuff). + */ + switch (event->xclient.data.l[2]) + { + case Above: + meta_window_raise (window); + break; + case Below: + meta_window_lower (window); + break; + case TopIf: + case BottomIf: + case Opposite: + break; + } + } +} +#endif + +/* We do some of our event handling in core/frames.c, which expects + * GDK events delivered by GTK+. However, since the transition to + * client side windows, we can't let GDK see button events, since the + * client-side tracking of implicit and explicit grabs it does will + * get confused by our direct use of X grabs. + * + * So we do a very minimal GDK => GTK event conversion here and send on the + * events we care about, and then filter them out so they don't go + * through the normal GDK event handling. + * + * To reduce the amount of code, the only events fields filled out + * below are the ones that frames.c uses. If frames.c is modified to + * use more fields, more fields need to be filled out below. + * + * https://github.com/stefano-k/Mate-Desktop-Environment/commit/b0e5fb03eb21dae8f02692f11ef391bfc5ccba33 + */ + +static gboolean maybe_send_event_to_gtk(MetaDisplay* display, XEvent* xevent) +{ + /* We're always using the default display */ + GdkDisplay* gdk_display = gdk_display_get_default(); + GdkEvent gdk_event; + GdkWindow* gdk_window; + Window window; + + switch (xevent->type) + { + case ButtonPress: + case ButtonRelease: + window = xevent->xbutton.window; + break; + + case MotionNotify: + window = xevent->xmotion.window; + break; + + case EnterNotify: + + case LeaveNotify: + window = xevent->xcrossing.window; + break; + + default: + return FALSE; + } + + gdk_window = gdk_window_lookup_for_display(gdk_display, window); + + if (gdk_window == NULL) + { + return FALSE; + } + + /* If GDK already things it has a grab, we better let it see events; this + * is the menu-navigation case and events need to get sent to the appropriate + * (client-side) subwindow for individual menu items. + */ + + if (gdk_display_pointer_is_grabbed(gdk_display)) + { + return FALSE; + } + + memset(&gdk_event, 0, sizeof(gdk_event)); + + switch (xevent->type) + { + + case ButtonPress: + + case ButtonRelease: + + if (xevent->type == ButtonPress) + { + GtkSettings* settings = gtk_settings_get_default(); + + int double_click_time; + int double_click_distance; + + g_object_get (settings, + "gtk-double-click-time", &double_click_time, + "gtk-double-click-distance", &double_click_distance, + NULL); + + if (xevent->xbutton.button == display->button_click_number && + xevent->xbutton.window == display->button_click_window && + xevent->xbutton.time < display->button_click_time + double_click_time && + ABS(xevent->xbutton.x - display->button_click_x) <= double_click_distance && + ABS (xevent->xbutton.y - display->button_click_y) <= double_click_distance) + { + + gdk_event.button.type = GDK_2BUTTON_PRESS; + display->button_click_number = 0; + } + else + { + gdk_event.button.type = GDK_BUTTON_PRESS; + display->button_click_number = xevent->xbutton.button; + display->button_click_window = xevent->xbutton.window; + display->button_click_time = xevent->xbutton.time; + display->button_click_x = xevent->xbutton.x; + display->button_click_y = xevent->xbutton.y; + } + } + else + { + gdk_event.button.type = GDK_BUTTON_RELEASE; + } + + gdk_event.button.window = gdk_window; + gdk_event.button.button = xevent->xbutton.button; + gdk_event.button.time = xevent->xbutton.time; + gdk_event.button.x = xevent->xbutton.x; + gdk_event.button.y = xevent->xbutton.y; + gdk_event.button.x_root = xevent->xbutton.x_root; + gdk_event.button.y_root = xevent->xbutton.y_root; + + break; + + case MotionNotify: + gdk_event.motion.type = GDK_MOTION_NOTIFY; + gdk_event.motion.window = gdk_window; + break; + + case EnterNotify: + + case LeaveNotify: + gdk_event.crossing.type = xevent->type == EnterNotify ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY; + gdk_event.crossing.window = gdk_window; + gdk_event.crossing.x = xevent->xcrossing.x; + gdk_event.crossing.y = xevent->xcrossing.y; + break; + + default: + g_assert_not_reached(); + break; + } + + /* If we've gotten here, we've filled in the gdk_event and should send it on */ + gtk_main_do_event(&gdk_event); + return TRUE; +} + +/** + * This is the most important function in the whole program. It is the heart, + * it is the nexus, it is the Grand Central Station of Marco's world. + * When we create a MetaDisplay, we ask GDK to pass *all* events for *all* + * windows to this function. So every time anything happens that we might + * want to know about, this function gets called. You see why it gets a bit + * busy around here. Most of this function is a ginormous switch statement + * dealing with all the kinds of events that might turn up. + * + * \param event The event that just happened + * \param data The MetaDisplay that events are coming from, cast to a gpointer + * so that it can be sent to a callback + * + * \ingroup main + */ +static gboolean event_callback(XEvent* event, gpointer data) +{ + MetaWindow *window; + MetaWindow *property_for_window; + MetaDisplay *display; + Window modified; + gboolean frame_was_receiver; + gboolean filter_out_event; + + display = data; + +#ifdef WITH_VERBOSE_MODE + if (dump_events) + meta_spew_event (display, event); +#endif + +#ifdef HAVE_STARTUP_NOTIFICATION + sn_display_process_event (display->sn_display, event); +#endif + + filter_out_event = FALSE; + display->current_time = event_get_time (display, event); + display->xinerama_cache_invalidated = TRUE; + + modified = event_get_modified_window (display, event); + + if (event->type == ButtonPress) + { + /* filter out scrollwheel */ + if (event->xbutton.button == 4 || + event->xbutton.button == 5) + return FALSE; + } + else if (event->type == UnmapNotify) + { + if (meta_ui_window_should_not_cause_focus (display->xdisplay, + modified)) + { + add_ignored_serial (display, event->xany.serial); + meta_topic (META_DEBUG_FOCUS, + "Adding EnterNotify serial %lu to ignored focus serials\n", + event->xany.serial); + } + } + else if (event->type == LeaveNotify && + event->xcrossing.mode == NotifyUngrab && + modified == display->ungrab_should_not_cause_focus_window) + { + add_ignored_serial (display, event->xany.serial); + meta_topic (META_DEBUG_FOCUS, + "Adding LeaveNotify serial %lu to ignored focus serials\n", + event->xany.serial); + } + + if (modified != None) + window = meta_display_lookup_x_window (display, modified); + else + window = NULL; + + /* We only want to respond to _NET_WM_USER_TIME property notify + * events on _NET_WM_USER_TIME_WINDOW windows; in particular, + * responding to UnmapNotify events is kind of bad. + */ + property_for_window = NULL; + if (window && modified == window->user_time_window) + { + property_for_window = window; + window = NULL; + } + + + frame_was_receiver = FALSE; + if (window && + window->frame && + modified == window->frame->xwindow) + { + /* Note that if the frame and the client both have an + * XGrabButton (as is normal with our setup), the event + * goes to the frame. + */ + frame_was_receiver = TRUE; + meta_topic (META_DEBUG_EVENTS, "Frame was receiver of event for %s\n", + window->desc); + } + +#ifdef HAVE_XSYNC + if (META_DISPLAY_HAS_XSYNC (display) && + event->type == (display->xsync_event_base + XSyncAlarmNotify) && + ((XSyncAlarmNotifyEvent*)event)->alarm == display->grab_sync_request_alarm) + { + filter_out_event = TRUE; /* GTK doesn't want to see this really */ + + if (display->grab_op != META_GRAB_OP_NONE && + display->grab_window != NULL && + grab_op_is_mouse (display->grab_op)) + meta_window_handle_mouse_grab_op_event (display->grab_window, event); + } +#endif /* HAVE_XSYNC */ + +#ifdef HAVE_SHAPE + if (META_DISPLAY_HAS_SHAPE (display) && + event->type == (display->shape_event_base + ShapeNotify)) + { + filter_out_event = TRUE; /* GTK doesn't want to see this really */ + + if (window && !frame_was_receiver) + { + XShapeEvent *sev = (XShapeEvent*) event; + + if (sev->kind == ShapeBounding) + { + if (sev->shaped && !window->has_shape) + { + window->has_shape = TRUE; + meta_topic (META_DEBUG_SHAPES, + "Window %s now has a shape\n", + window->desc); + } + else if (!sev->shaped && window->has_shape) + { + window->has_shape = FALSE; + meta_topic (META_DEBUG_SHAPES, + "Window %s no longer has a shape\n", + window->desc); + } + else + { + meta_topic (META_DEBUG_SHAPES, + "Window %s shape changed\n", + window->desc); + } + + if (window->frame) + { + window->frame->need_reapply_frame_shape = TRUE; + meta_warning("from event callback\n"); + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); + } + } + } + else + { + meta_topic (META_DEBUG_SHAPES, + "ShapeNotify not on a client window (window %s frame_was_receiver = %d)\n", + window ? window->desc : "(none)", + frame_was_receiver); + } + } +#endif /* HAVE_SHAPE */ + + if (window && ((event->type == KeyPress) || (event->type == ButtonPress))) + { + if (CurrentTime == display->current_time) + { + /* We can't use missing (i.e. invalid) timestamps to set user time, + * nor do we want to use them to sanity check other timestamps. + * See bug 313490 for more details. + */ + meta_warning ("Event has no timestamp! You may be using a broken " + "program such as xse. Please ask the authors of that " + "program to fix it.\n"); + } + else + { + meta_window_set_user_time (window, display->current_time); + sanity_check_timestamps (display, display->current_time); + } + } + + switch (event->type) + { + case KeyPress: + case KeyRelease: + meta_display_process_key_event (display, window, event); + break; + case ButtonPress: + if ((window && + grab_op_is_mouse (display->grab_op) && + display->grab_button != (int) event->xbutton.button && + display->grab_window == window) || + grab_op_is_keyboard (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Ending grab op %u on window %s due to button press\n", + display->grab_op, + (display->grab_window ? + display->grab_window->desc : + "none")); + if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) + { + MetaScreen *screen; + meta_topic (META_DEBUG_WINDOW_OPS, + "Syncing to old stack positions.\n"); + screen = + meta_display_screen_for_root (display, event->xany.window); + + if (screen!=NULL) + meta_stack_set_positions (screen->stack, + display->grab_old_window_stacking); + } + meta_display_end_grab_op (display, + event->xbutton.time); + } + else if (window && display->grab_op == META_GRAB_OP_NONE) + { + gboolean begin_move = FALSE; + unsigned int grab_mask; + gboolean unmodified; + + grab_mask = display->window_grab_modifiers; + if (g_getenv ("MARCO_DEBUG_BUTTON_GRABS")) + grab_mask |= ControlMask; + + /* Two possible sources of an unmodified event; one is a + * client that's letting button presses pass through to the + * frame, the other is our focus_window_grab on unmodified + * button 1. So for all such events we focus the window. + */ + unmodified = (event->xbutton.state & grab_mask) == 0; + + if (unmodified || + event->xbutton.button == 1) + { + /* don't focus if frame received, will be lowered in + * frames.c or special-cased if the click was on a + * minimize/close button. + */ + if (!frame_was_receiver) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + else + meta_topic (META_DEBUG_FOCUS, + "Not raising window on click due to don't-raise-on-click option\n"); + + /* Don't focus panels--they must explicitly request focus. + * See bug 160470 + */ + if (window->type != META_WINDOW_DOCK) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s due to unmodified button %u press (display.c)\n", + window->desc, event->xbutton.button); + meta_window_focus (window, event->xbutton.time); + } + else + /* However, do allow terminals to lose focus due to new + * window mappings after the user clicks on a panel. + */ + display->allow_terminal_deactivation = TRUE; + } + + /* you can move on alt-click but not on + * the click-to-focus + */ + if (!unmodified) + begin_move = TRUE; + } + else if (!unmodified && event->xbutton.button == meta_prefs_get_mouse_button_resize()) + { + if (window->has_resize_func) + { + gboolean north, south; + gboolean west, east; + int root_x, root_y; + MetaGrabOp op; + + meta_window_get_position (window, &root_x, &root_y); + + west = event->xbutton.x_root < (root_x + 1 * window->rect.width / 3); + east = event->xbutton.x_root > (root_x + 2 * window->rect.width / 3); + north = event->xbutton.y_root < (root_y + 1 * window->rect.height / 3); + south = event->xbutton.y_root > (root_y + 2 * window->rect.height / 3); + + if (north && west) + op = META_GRAB_OP_RESIZING_NW; + else if (north && east) + op = META_GRAB_OP_RESIZING_NE; + else if (south && west) + op = META_GRAB_OP_RESIZING_SW; + else if (south && east) + op = META_GRAB_OP_RESIZING_SE; + else if (north) + op = META_GRAB_OP_RESIZING_N; + else if (west) + op = META_GRAB_OP_RESIZING_W; + else if (east) + op = META_GRAB_OP_RESIZING_E; + else if (south) + op = META_GRAB_OP_RESIZING_S; + else /* Middle region is no-op to avoid user triggering wrong action */ + op = META_GRAB_OP_NONE; + + if (op != META_GRAB_OP_NONE) + meta_display_begin_grab_op (display, + window->screen, + window, + op, + TRUE, + FALSE, + event->xbutton.button, + 0, + event->xbutton.time, + event->xbutton.x_root, + event->xbutton.y_root); + } + } + else if (event->xbutton.button == meta_prefs_get_mouse_button_menu()) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_show_menu (window, + event->xbutton.x_root, + event->xbutton.y_root, + event->xbutton.button, + event->xbutton.time); + } + + if (!frame_was_receiver && unmodified) + { + /* This is from our synchronous grab since + * it has no modifiers and was on the client window + */ + int mode; + + /* When clicking a different app in click-to-focus + * in application-based mode, and the different + * app is not a dock or desktop, eat the focus click. + */ + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK && + meta_prefs_get_application_based () && + !window->has_focus && + window->type != META_WINDOW_DOCK && + window->type != META_WINDOW_DESKTOP && + (display->focus_window == NULL || + !meta_window_same_application (window, + display->focus_window))) + mode = AsyncPointer; /* eat focus click */ + else + mode = ReplayPointer; /* give event back */ + + meta_verbose ("Allowing events mode %s time %u\n", + mode == AsyncPointer ? "AsyncPointer" : "ReplayPointer", + (unsigned int)event->xbutton.time); + + XAllowEvents (display->xdisplay, + mode, event->xbutton.time); + } + + if (begin_move && window->has_move_func) + { + meta_display_begin_grab_op (display, + window->screen, + window, + META_GRAB_OP_MOVING, + TRUE, + FALSE, + event->xbutton.button, + 0, + event->xbutton.time, + event->xbutton.x_root, + event->xbutton.y_root); + } + } + break; + case ButtonRelease: + if (display->grab_window == window && + grab_op_is_mouse (display->grab_op)) + meta_window_handle_mouse_grab_op_event (window, event); + break; + case MotionNotify: + if (display->grab_window == window && + grab_op_is_mouse (display->grab_op)) + meta_window_handle_mouse_grab_op_event (window, event); + break; + case EnterNotify: + if (display->grab_window == window && + grab_op_is_mouse (display->grab_op)) + { + meta_window_handle_mouse_grab_op_event (window, event); + break; + } + + /* If the mouse switches screens, active the default window on the new + * screen; this will make keybindings and workspace-launched items + * actually appear on the right screen. + */ + { + MetaScreen *new_screen = + meta_display_screen_for_root (display, event->xcrossing.root); + + if (new_screen != NULL && display->active_screen != new_screen) + meta_workspace_focus_default_window (new_screen->active_workspace, + NULL, + event->xcrossing.time); + } + + /* Check if we've entered a window; do this even if window->has_focus to + * avoid races. + */ + if (window && !serial_is_ignored (display, event->xany.serial) && + event->xcrossing.mode != NotifyGrab && + event->xcrossing.mode != NotifyUngrab && + event->xcrossing.detail != NotifyInferior && + meta_display_focus_sentinel_clear (display)) + { + switch (meta_prefs_get_focus_mode ()) + { + case META_FOCUS_MODE_SLOPPY: + case META_FOCUS_MODE_MOUSE: + display->mouse_mode = TRUE; + if (window->type != META_WINDOW_DOCK && + window->type != META_WINDOW_DESKTOP) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s due to enter notify with serial %lu " + "at time %lu, and setting display->mouse_mode to " + "TRUE.\n", + window->desc, + event->xany.serial, + event->xcrossing.time); + + meta_window_focus (window, event->xcrossing.time); + + /* stop ignoring stuff */ + reset_ignores (display); + + if (meta_prefs_get_auto_raise ()) + { + meta_display_queue_autoraise_callback (display, window); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Auto raise is disabled\n"); + } + } + /* In mouse focus mode, we defocus when the mouse *enters* + * the DESKTOP window, instead of defocusing on LeaveNotify. + * This is because having the mouse enter override-redirect + * child windows unfortunately causes LeaveNotify events that + * we can't distinguish from the mouse actually leaving the + * toplevel window as we expect. But, since we filter out + * EnterNotify events on override-redirect windows, this + * alternative mechanism works great. + */ + if (window->type == META_WINDOW_DESKTOP && + meta_prefs_get_focus_mode() == META_FOCUS_MODE_MOUSE && + display->expected_focus_window != NULL) + { + meta_topic (META_DEBUG_FOCUS, + "Unsetting focus from %s due to mouse entering " + "the DESKTOP window\n", + display->expected_focus_window->desc); + meta_display_focus_the_no_focus_window (display, + window->screen, + event->xcrossing.time); + } + break; + case META_FOCUS_MODE_CLICK: + break; + } + + if (window->type == META_WINDOW_DOCK) + meta_window_raise (window); + } + break; + case LeaveNotify: + if (display->grab_window == window && + grab_op_is_mouse (display->grab_op)) + meta_window_handle_mouse_grab_op_event (window, event); + else if (window != NULL) + { + if (window->type == META_WINDOW_DOCK && + event->xcrossing.mode != NotifyGrab && + event->xcrossing.mode != NotifyUngrab && + !window->has_focus) + meta_window_lower (window); + } + break; + case FocusIn: + case FocusOut: + if (window) + { + meta_window_notify_focus (window, event); + } + else if (meta_display_xwindow_is_a_no_focus_window (display, + event->xany.window)) + { + meta_topic (META_DEBUG_FOCUS, + "Focus %s event received on no_focus_window 0x%lx " + "mode %s detail %s\n", + event->type == FocusIn ? "in" : + event->type == FocusOut ? "out" : + "???", + event->xany.window, + meta_event_mode_to_string (event->xfocus.mode), + meta_event_detail_to_string (event->xfocus.detail)); + } + else + { + MetaScreen *screen = + meta_display_screen_for_root(display, + event->xany.window); + if (screen == NULL) + break; + + meta_topic (META_DEBUG_FOCUS, + "Focus %s event received on root window 0x%lx " + "mode %s detail %s\n", + event->type == FocusIn ? "in" : + event->type == FocusOut ? "out" : + "???", + event->xany.window, + meta_event_mode_to_string (event->xfocus.mode), + meta_event_detail_to_string (event->xfocus.detail)); + + if (event->type == FocusIn && + event->xfocus.detail == NotifyDetailNone) + { + meta_topic (META_DEBUG_FOCUS, + "Focus got set to None, probably due to " + "brain-damage in the X protocol (see bug " + "125492). Setting the default focus window.\n"); + meta_workspace_focus_default_window (screen->active_workspace, + NULL, + meta_display_get_current_time_roundtrip (display)); + } + else if (event->type == FocusIn && + event->xfocus.mode == NotifyNormal && + event->xfocus.detail == NotifyInferior) + { + meta_topic (META_DEBUG_FOCUS, + "Focus got set to root window, probably due to " + "mate-session logout dialog usage (see bug " + "153220). Setting the default focus window.\n"); + meta_workspace_focus_default_window (screen->active_workspace, + NULL, + meta_display_get_current_time_roundtrip (display)); + } + + } + break; + case KeymapNotify: + break; + case Expose: + break; + case GraphicsExpose: + break; + case NoExpose: + break; + case VisibilityNotify: + break; + case CreateNotify: + break; + + case DestroyNotify: + if (window) + { + /* FIXME: It sucks that DestroyNotify events don't come with + * a timestamp; could we do something better here? Maybe X + * will change one day? + */ + guint32 timestamp; + timestamp = meta_display_get_current_time_roundtrip (display); + + if (display->grab_op != META_GRAB_OP_NONE && + display->grab_window == window) + meta_display_end_grab_op (display, timestamp); + + if (frame_was_receiver) + { + meta_warning ("Unexpected destruction of frame 0x%lx, not sure if this should silently fail or be considered a bug\n", + window->frame->xwindow); + meta_error_trap_push (display); + meta_window_destroy_frame (window->frame->window); + meta_error_trap_pop (display, FALSE); + } + else + { + /* Unmanage destroyed window */ + meta_window_free (window, timestamp); + window = NULL; + } + } + break; + case UnmapNotify: + if (window) + { + /* FIXME: It sucks that UnmapNotify events don't come with + * a timestamp; could we do something better here? Maybe X + * will change one day? + */ + guint32 timestamp; + timestamp = meta_display_get_current_time_roundtrip (display); + + if (display->grab_op != META_GRAB_OP_NONE && + display->grab_window == window && + ((window->frame == NULL) || !window->frame->mapped)) + meta_display_end_grab_op (display, timestamp); + + if (!frame_was_receiver) + { + if (window->unmaps_pending == 0) + { + meta_topic (META_DEBUG_WINDOW_STATE, + "Window %s withdrawn\n", + window->desc); + + meta_effect_run_close (window, NULL, NULL); + + /* Unmanage withdrawn window */ + window->withdrawn = TRUE; + meta_window_free (window, timestamp); + window = NULL; + } + else + { + window->unmaps_pending -= 1; + meta_topic (META_DEBUG_WINDOW_STATE, + "Received pending unmap, %d now pending\n", + window->unmaps_pending); + } + } + + /* Unfocus on UnmapNotify, do this after the possible + * window_free above so that window_free can see if window->has_focus + * and move focus to another window + */ + if (window) + meta_window_notify_focus (window, event); + } + break; + case MapNotify: + break; + case MapRequest: + if (window == NULL) + { + window = meta_window_new (display, event->xmaprequest.window, + FALSE); + } + /* if frame was receiver it's some malicious send event or something */ + else if (!frame_was_receiver && window) + { + meta_verbose ("MapRequest on %s mapped = %d minimized = %d\n", + window->desc, window->mapped, window->minimized); + if (window->minimized) + { + meta_window_unminimize (window); + if (window->workspace != window->screen->active_workspace) + { + meta_verbose ("Changing workspace due to MapRequest mapped = %d minimized = %d\n", + window->mapped, window->minimized); + meta_window_change_workspace (window, + window->screen->active_workspace); + } + } + } + break; + case ReparentNotify: + break; + case ConfigureNotify: + /* Handle screen resize */ + { + MetaScreen *screen; + + screen = meta_display_screen_for_root (display, + event->xconfigure.window); + + if (screen != NULL) + { +#ifdef HAVE_RANDR + /* do the resize the official way */ + XRRUpdateConfiguration (event); +#else + /* poke around in Xlib */ + screen->xscreen->width = event->xconfigure.width; + screen->xscreen->height = event->xconfigure.height; +#endif + + meta_screen_resize (screen, + event->xconfigure.width, + event->xconfigure.height); + } + } + break; + case ConfigureRequest: + /* This comment and code is found in both twm and fvwm */ + /* + * According to the July 27, 1988 ICCCM draft, we should ignore size and + * position fields in the WM_NORMAL_HINTS property when we map a window. + * Instead, we'll read the current geometry. Therefore, we should respond + * to configuration requests for windows which have never been mapped. + */ + if (window == NULL) + { + unsigned int xwcm; + XWindowChanges xwc; + + xwcm = event->xconfigurerequest.value_mask & + (CWX | CWY | CWWidth | CWHeight | CWBorderWidth); + + xwc.x = event->xconfigurerequest.x; + xwc.y = event->xconfigurerequest.y; + xwc.width = event->xconfigurerequest.width; + xwc.height = event->xconfigurerequest.height; + xwc.border_width = event->xconfigurerequest.border_width; + + meta_verbose ("Configuring withdrawn window to %d,%d %dx%d border %d (some values may not be in mask)\n", + xwc.x, xwc.y, xwc.width, xwc.height, xwc.border_width); + meta_error_trap_push (display); + XConfigureWindow (display->xdisplay, event->xconfigurerequest.window, + xwcm, &xwc); + meta_error_trap_pop (display, FALSE); + } + else + { + if (!frame_was_receiver) + meta_window_configure_request (window, event); + } + break; + case GravityNotify: + break; + case ResizeRequest: + break; + case CirculateNotify: + break; + case CirculateRequest: + break; + case PropertyNotify: + { + MetaGroup *group; + MetaScreen *screen; + + if (window && !frame_was_receiver) + meta_window_property_notify (window, event); + else if (property_for_window && !frame_was_receiver) + meta_window_property_notify (property_for_window, event); + + group = meta_display_lookup_group (display, + event->xproperty.window); + if (group != NULL) + meta_group_property_notify (group, event); + + screen = NULL; + if (window == NULL && + group == NULL) /* window/group != NULL means it wasn't a root window */ + screen = meta_display_screen_for_root (display, + event->xproperty.window); + + if (screen != NULL) + { + if (event->xproperty.atom == + display->atom__NET_DESKTOP_LAYOUT) + meta_screen_update_workspace_layout (screen); + else if (event->xproperty.atom == + display->atom__NET_DESKTOP_NAMES) + meta_screen_update_workspace_names (screen); +#if 0 + else if (event->xproperty.atom == + display->atom__NET_RESTACK_WINDOW) + handle_net_restack_window (display, event); +#endif + + /* we just use this property as a sentinel to avoid + * certain race conditions. See the comment for the + * sentinel_counter variable declaration in display.h + */ + if (event->xproperty.atom == + display->atom__MARCO_SENTINEL) + { + meta_display_decrement_focus_sentinel (display); + } + } + } + break; + case SelectionClear: + /* do this here instead of at end of function + * so we can return + */ + + /* FIXME: Clearing display->current_time here makes no sense to + * me; who put this here and why? + */ + display->current_time = CurrentTime; + + process_selection_clear (display, event); + /* Note that processing that may have resulted in + * closing the display... so return right away. + */ + return FALSE; + case SelectionRequest: + process_selection_request (display, event); + break; + case SelectionNotify: + break; + case ColormapNotify: + if (window && !frame_was_receiver) + window->colormap = event->xcolormap.colormap; + break; + case ClientMessage: + if (window) + { + if (!frame_was_receiver) + meta_window_client_message (window, event); + } + else + { + MetaScreen *screen; + + screen = meta_display_screen_for_root (display, + event->xclient.window); + + if (screen) + { + if (event->xclient.message_type == + display->atom__NET_CURRENT_DESKTOP) + { + int space; + MetaWorkspace *workspace; + guint32 time; + + space = event->xclient.data.l[0]; + time = event->xclient.data.l[1]; + + meta_verbose ("Request to change current workspace to %d with " + "specified timestamp of %u\n", + space, time); + + workspace = + meta_screen_get_workspace_by_index (screen, + space); + + /* Handle clients using the older version of the spec... */ + if (time == 0 && workspace) + { + meta_warning ("Received a NET_CURRENT_DESKTOP message " + "from a broken (outdated) client who sent " + "a 0 timestamp\n"); + time = meta_display_get_current_time_roundtrip (display); + } + + if (workspace) + meta_workspace_activate (workspace, time); + else + meta_verbose ("Don't know about workspace %d\n", space); + } + else if (event->xclient.message_type == + display->atom__NET_NUMBER_OF_DESKTOPS) + { + int num_spaces; + + num_spaces = event->xclient.data.l[0]; + + meta_verbose ("Request to set number of workspaces to %d\n", + num_spaces); + + meta_prefs_set_num_workspaces (num_spaces); + } + else if (event->xclient.message_type == + display->atom__NET_SHOWING_DESKTOP) + { + gboolean showing_desktop; + guint32 timestamp; + + showing_desktop = event->xclient.data.l[0] != 0; + /* FIXME: Braindead protocol doesn't have a timestamp */ + timestamp = meta_display_get_current_time_roundtrip (display); + meta_verbose ("Request to %s desktop\n", + showing_desktop ? "show" : "hide"); + + if (showing_desktop) + meta_screen_show_desktop (screen, timestamp); + else + { + meta_screen_unshow_desktop (screen); + meta_workspace_focus_default_window (screen->active_workspace, NULL, timestamp); + } + } + else if (event->xclient.message_type == + display->atom__MARCO_RESTART_MESSAGE) + { + meta_verbose ("Received restart request\n"); + meta_restart (); + } + else if (event->xclient.message_type == + display->atom__MARCO_RELOAD_THEME_MESSAGE) + { + meta_verbose ("Received reload theme request\n"); + meta_ui_set_current_theme (meta_prefs_get_theme (), + TRUE); + meta_display_retheme_all (); + } + else if (event->xclient.message_type == + display->atom__MARCO_SET_KEYBINDINGS_MESSAGE) + { + meta_verbose ("Received set keybindings request = %d\n", + (int) event->xclient.data.l[0]); + meta_set_keybindings_disabled (!event->xclient.data.l[0]); + } + else if (event->xclient.message_type == + display->atom__MARCO_TOGGLE_VERBOSE) + { + meta_verbose ("Received toggle verbose message\n"); + meta_set_verbose (!meta_is_verbose ()); + } + else if (event->xclient.message_type == + display->atom_WM_PROTOCOLS) + { + meta_verbose ("Received WM_PROTOCOLS message\n"); + + if ((Atom)event->xclient.data.l[0] == display->atom__NET_WM_PING) + { + process_pong_message (display, event); + + /* We don't want ping reply events going into + * the GTK+ event loop because gtk+ will treat + * them as ping requests and send more replies. + */ + filter_out_event = TRUE; + } + } + } + + if (event->xclient.message_type == + display->atom__NET_REQUEST_FRAME_EXTENTS) + { + meta_verbose ("Received _NET_REQUEST_FRAME_EXTENTS message\n"); + process_request_frame_extents (display, event); + } + } + break; + case MappingNotify: + { + gboolean ignore_current; + + ignore_current = FALSE; + + /* Check whether the next event is an identical MappingNotify + * event. If it is, ignore the current event, we'll update + * when we get the next one. + */ + if (XPending (display->xdisplay)) + { + XEvent next_event; + + XPeekEvent (display->xdisplay, &next_event); + + if (next_event.type == MappingNotify && + next_event.xmapping.request == event->xmapping.request) + ignore_current = TRUE; + } + + if (!ignore_current) + { + /* Let XLib know that there is a new keyboard mapping. + */ + XRefreshKeyboardMapping (&event->xmapping); + meta_display_process_mapping_event (display, event); + } + } + break; + default: +#ifdef HAVE_XKB + if (event->type == display->xkb_base_event_type) + { + XkbAnyEvent *xkb_ev = (XkbAnyEvent *) event; + + switch (xkb_ev->xkb_type) + { + case XkbBellNotify: + if (XSERVER_TIME_IS_BEFORE(display->last_bell_time, + xkb_ev->time - 100)) + { + display->last_bell_time = xkb_ev->time; + meta_bell_notify (display, xkb_ev); + } + break; + } + } +#endif + break; + } + + if (display->compositor) + { + meta_compositor_process_event (display->compositor, event, window); + } + + /* generates event on wrong window. + * https://github.com/stefano-k/Mate-Desktop-Environment/commit/b0e5fb03eb21dae8f02692f11ef391bfc5ccba33 + */ + if (maybe_send_event_to_gtk(display, event)) + { + filter_out_event = TRUE; + } + + display->current_time = CurrentTime; + return filter_out_event; +} + +/* Return the window this has to do with, if any, rather + * than the frame or root window that was selecting + * for substructure + */ +static Window +event_get_modified_window (MetaDisplay *display, + XEvent *event) +{ + switch (event->type) + { + case KeyPress: + case KeyRelease: + case ButtonPress: + case ButtonRelease: + case MotionNotify: + case FocusIn: + case FocusOut: + case KeymapNotify: + case Expose: + case GraphicsExpose: + case NoExpose: + case VisibilityNotify: + case ResizeRequest: + case PropertyNotify: + case SelectionClear: + case SelectionRequest: + case SelectionNotify: + case ColormapNotify: + case ClientMessage: + case EnterNotify: + case LeaveNotify: + return event->xany.window; + + case CreateNotify: + return event->xcreatewindow.window; + + case DestroyNotify: + return event->xdestroywindow.window; + + case UnmapNotify: + return event->xunmap.window; + + case MapNotify: + return event->xmap.window; + + case MapRequest: + return event->xmaprequest.window; + + case ReparentNotify: + return event->xreparent.window; + + case ConfigureNotify: + return event->xconfigure.window; + + case ConfigureRequest: + return event->xconfigurerequest.window; + + case GravityNotify: + return event->xgravity.window; + + case CirculateNotify: + return event->xcirculate.window; + + case CirculateRequest: + return event->xcirculaterequest.window; + + case MappingNotify: + return None; + + default: +#ifdef HAVE_SHAPE + if (META_DISPLAY_HAS_SHAPE (display) && + event->type == (display->shape_event_base + ShapeNotify)) + { + XShapeEvent *sev = (XShapeEvent*) event; + return sev->window; + } +#endif + + return None; + } +} + +static guint32 +event_get_time (MetaDisplay *display, + XEvent *event) +{ + switch (event->type) + { + case KeyPress: + case KeyRelease: + return event->xkey.time; + + case ButtonPress: + case ButtonRelease: + return event->xbutton.time; + + case MotionNotify: + return event->xmotion.time; + + case PropertyNotify: + return event->xproperty.time; + + case SelectionClear: + case SelectionRequest: + case SelectionNotify: + return event->xselection.time; + + case EnterNotify: + case LeaveNotify: + return event->xcrossing.time; + + case FocusIn: + case FocusOut: + case KeymapNotify: + case Expose: + case GraphicsExpose: + case NoExpose: + case MapNotify: + case UnmapNotify: + case VisibilityNotify: + case ResizeRequest: + case ColormapNotify: + case ClientMessage: + case CreateNotify: + case DestroyNotify: + case MapRequest: + case ReparentNotify: + case ConfigureNotify: + case ConfigureRequest: + case GravityNotify: + case CirculateNotify: + case CirculateRequest: + case MappingNotify: + default: + return CurrentTime; + } +} + +#ifdef WITH_VERBOSE_MODE +const char* +meta_event_detail_to_string (int d) +{ + const char *detail = "???"; + switch (d) + { + /* We are an ancestor in the A<->B focus change relationship */ + case NotifyAncestor: + detail = "NotifyAncestor"; + break; + case NotifyDetailNone: + detail = "NotifyDetailNone"; + break; + /* We are a descendant in the A<->B focus change relationship */ + case NotifyInferior: + detail = "NotifyInferior"; + break; + case NotifyNonlinear: + detail = "NotifyNonlinear"; + break; + case NotifyNonlinearVirtual: + detail = "NotifyNonlinearVirtual"; + break; + case NotifyPointer: + detail = "NotifyPointer"; + break; + case NotifyPointerRoot: + detail = "NotifyPointerRoot"; + break; + case NotifyVirtual: + detail = "NotifyVirtual"; + break; + } + + return detail; +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef WITH_VERBOSE_MODE +const char* +meta_event_mode_to_string (int m) +{ + const char *mode = "???"; + switch (m) + { + case NotifyNormal: + mode = "NotifyNormal"; + break; + case NotifyGrab: + mode = "NotifyGrab"; + break; + case NotifyUngrab: + mode = "NotifyUngrab"; + break; + /* not sure any X implementations are missing this, but + * it seems to be absent from some docs. + */ +#ifdef NotifyWhileGrabbed + case NotifyWhileGrabbed: + mode = "NotifyWhileGrabbed"; + break; +#endif + } + + return mode; +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef WITH_VERBOSE_MODE +static const char* +stack_mode_to_string (int mode) +{ + switch (mode) + { + case Above: + return "Above"; + case Below: + return "Below"; + case TopIf: + return "TopIf"; + case BottomIf: + return "BottomIf"; + case Opposite: + return "Opposite"; + } + + return "Unknown"; +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef WITH_VERBOSE_MODE +static char* +key_event_description (Display *xdisplay, + XEvent *event) +{ + KeySym keysym; + const char *str; + + keysym = XKeycodeToKeysym (xdisplay, event->xkey.keycode, 0); + + str = XKeysymToString (keysym); + + return g_strdup_printf ("Key '%s' state 0x%x", + str ? str : "none", event->xkey.state); +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef HAVE_XSYNC +#ifdef WITH_VERBOSE_MODE +static gint64 +sync_value_to_64 (const XSyncValue *value) +{ + gint64 v; + + v = XSyncValueLow32 (*value); + v |= (((gint64)XSyncValueHigh32 (*value)) << 32); + + return v; +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef WITH_VERBOSE_MODE +static const char* +alarm_state_to_string (XSyncAlarmState state) +{ + switch (state) + { + case XSyncAlarmActive: + return "Active"; + case XSyncAlarmInactive: + return "Inactive"; + case XSyncAlarmDestroyed: + return "Destroyed"; + default: + return "(unknown)"; + } +} +#endif /* WITH_VERBOSE_MODE */ + +#endif /* HAVE_XSYNC */ + +#ifdef WITH_VERBOSE_MODE +static void +meta_spew_event (MetaDisplay *display, + XEvent *event) +{ + const char *name = NULL; + char *extra = NULL; + char *winname; + MetaScreen *screen; + + if (!meta_is_verbose()) + return; + + /* filter overnumerous events */ + if (event->type == Expose || event->type == MotionNotify || + event->type == NoExpose) + return; + + switch (event->type) + { + case KeyPress: + name = "KeyPress"; + extra = key_event_description (display->xdisplay, event); + break; + case KeyRelease: + name = "KeyRelease"; + extra = key_event_description (display->xdisplay, event); + break; + case ButtonPress: + name = "ButtonPress"; + extra = g_strdup_printf ("button %u state 0x%x x %d y %d root 0x%lx same_screen %d", + event->xbutton.button, + event->xbutton.state, + event->xbutton.x, + event->xbutton.y, + event->xbutton.root, + event->xbutton.same_screen); + break; + case ButtonRelease: + name = "ButtonRelease"; + extra = g_strdup_printf ("button %u state 0x%x x %d y %d root 0x%lx same_screen %d", + event->xbutton.button, + event->xbutton.state, + event->xbutton.x, + event->xbutton.y, + event->xbutton.root, + event->xbutton.same_screen); + break; + case MotionNotify: + name = "MotionNotify"; + extra = g_strdup_printf ("win: 0x%lx x: %d y: %d", + event->xmotion.window, + event->xmotion.x, + event->xmotion.y); + break; + case EnterNotify: + name = "EnterNotify"; + extra = g_strdup_printf ("win: 0x%lx root: 0x%lx subwindow: 0x%lx mode: %s detail: %s focus: %d x: %d y: %d", + event->xcrossing.window, + event->xcrossing.root, + event->xcrossing.subwindow, + meta_event_mode_to_string (event->xcrossing.mode), + meta_event_detail_to_string (event->xcrossing.detail), + event->xcrossing.focus, + event->xcrossing.x, + event->xcrossing.y); + break; + case LeaveNotify: + name = "LeaveNotify"; + extra = g_strdup_printf ("win: 0x%lx root: 0x%lx subwindow: 0x%lx mode: %s detail: %s focus: %d x: %d y: %d", + event->xcrossing.window, + event->xcrossing.root, + event->xcrossing.subwindow, + meta_event_mode_to_string (event->xcrossing.mode), + meta_event_detail_to_string (event->xcrossing.detail), + event->xcrossing.focus, + event->xcrossing.x, + event->xcrossing.y); + break; + case FocusIn: + name = "FocusIn"; + extra = g_strdup_printf ("detail: %s mode: %s\n", + meta_event_detail_to_string (event->xfocus.detail), + meta_event_mode_to_string (event->xfocus.mode)); + break; + case FocusOut: + name = "FocusOut"; + extra = g_strdup_printf ("detail: %s mode: %s\n", + meta_event_detail_to_string (event->xfocus.detail), + meta_event_mode_to_string (event->xfocus.mode)); + break; + case KeymapNotify: + name = "KeymapNotify"; + break; + case Expose: + name = "Expose"; + break; + case GraphicsExpose: + name = "GraphicsExpose"; + break; + case NoExpose: + name = "NoExpose"; + break; + case VisibilityNotify: + name = "VisibilityNotify"; + break; + case CreateNotify: + name = "CreateNotify"; + extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx", + event->xcreatewindow.parent, + event->xcreatewindow.window); + break; + case DestroyNotify: + name = "DestroyNotify"; + extra = g_strdup_printf ("event: 0x%lx window: 0x%lx", + event->xdestroywindow.event, + event->xdestroywindow.window); + break; + case UnmapNotify: + name = "UnmapNotify"; + extra = g_strdup_printf ("event: 0x%lx window: 0x%lx from_configure: %d", + event->xunmap.event, + event->xunmap.window, + event->xunmap.from_configure); + break; + case MapNotify: + name = "MapNotify"; + extra = g_strdup_printf ("event: 0x%lx window: 0x%lx override_redirect: %d", + event->xmap.event, + event->xmap.window, + event->xmap.override_redirect); + break; + case MapRequest: + name = "MapRequest"; + extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx\n", + event->xmaprequest.window, + event->xmaprequest.parent); + break; + case ReparentNotify: + name = "ReparentNotify"; + extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx event: 0x%lx\n", + event->xreparent.window, + event->xreparent.parent, + event->xreparent.event); + break; + case ConfigureNotify: + name = "ConfigureNotify"; + extra = g_strdup_printf ("x: %d y: %d w: %d h: %d above: 0x%lx override_redirect: %d", + event->xconfigure.x, + event->xconfigure.y, + event->xconfigure.width, + event->xconfigure.height, + event->xconfigure.above, + event->xconfigure.override_redirect); + break; + case ConfigureRequest: + name = "ConfigureRequest"; + extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx x: %d %sy: %d %sw: %d %sh: %d %sborder: %d %sabove: %lx %sstackmode: %s %s", + event->xconfigurerequest.parent, + event->xconfigurerequest.window, + event->xconfigurerequest.x, + event->xconfigurerequest.value_mask & + CWX ? "" : "(unset) ", + event->xconfigurerequest.y, + event->xconfigurerequest.value_mask & + CWY ? "" : "(unset) ", + event->xconfigurerequest.width, + event->xconfigurerequest.value_mask & + CWWidth ? "" : "(unset) ", + event->xconfigurerequest.height, + event->xconfigurerequest.value_mask & + CWHeight ? "" : "(unset) ", + event->xconfigurerequest.border_width, + event->xconfigurerequest.value_mask & + CWBorderWidth ? "" : "(unset)", + event->xconfigurerequest.above, + event->xconfigurerequest.value_mask & + CWSibling ? "" : "(unset)", + stack_mode_to_string (event->xconfigurerequest.detail), + event->xconfigurerequest.value_mask & + CWStackMode ? "" : "(unset)"); + break; + case GravityNotify: + name = "GravityNotify"; + break; + case ResizeRequest: + name = "ResizeRequest"; + extra = g_strdup_printf ("width = %d height = %d", + event->xresizerequest.width, + event->xresizerequest.height); + break; + case CirculateNotify: + name = "CirculateNotify"; + break; + case CirculateRequest: + name = "CirculateRequest"; + break; + case PropertyNotify: + { + char *str; + const char *state; + + name = "PropertyNotify"; + + meta_error_trap_push (display); + str = XGetAtomName (display->xdisplay, + event->xproperty.atom); + meta_error_trap_pop (display, TRUE); + + if (event->xproperty.state == PropertyNewValue) + state = "PropertyNewValue"; + else if (event->xproperty.state == PropertyDelete) + state = "PropertyDelete"; + else + state = "???"; + + extra = g_strdup_printf ("atom: %s state: %s", + str ? str : "(unknown atom)", + state); + meta_XFree (str); + } + break; + case SelectionClear: + name = "SelectionClear"; + break; + case SelectionRequest: + name = "SelectionRequest"; + break; + case SelectionNotify: + name = "SelectionNotify"; + break; + case ColormapNotify: + name = "ColormapNotify"; + break; + case ClientMessage: + { + char *str; + name = "ClientMessage"; + meta_error_trap_push (display); + str = XGetAtomName (display->xdisplay, + event->xclient.message_type); + meta_error_trap_pop (display, TRUE); + extra = g_strdup_printf ("type: %s format: %d\n", + str ? str : "(unknown atom)", + event->xclient.format); + meta_XFree (str); + } + break; + case MappingNotify: + name = "MappingNotify"; + break; + default: +#ifdef HAVE_XSYNC + if (META_DISPLAY_HAS_XSYNC (display) && + event->type == (display->xsync_event_base + XSyncAlarmNotify)) + { + XSyncAlarmNotifyEvent *aevent = (XSyncAlarmNotifyEvent*) event; + + name = "XSyncAlarmNotify"; + extra = + g_strdup_printf ("alarm: 0x%lx" + " counter_value: %" G_GINT64_FORMAT + " alarm_value: %" G_GINT64_FORMAT + " time: %u alarm state: %s", + aevent->alarm, + (gint64) sync_value_to_64 (&aevent->counter_value), + (gint64) sync_value_to_64 (&aevent->alarm_value), + (unsigned int)aevent->time, + alarm_state_to_string (aevent->state)); + } + else +#endif /* HAVE_XSYNC */ +#ifdef HAVE_SHAPE + if (META_DISPLAY_HAS_SHAPE (display) && + event->type == (display->shape_event_base + ShapeNotify)) + { + XShapeEvent *sev = (XShapeEvent*) event; + + name = "ShapeNotify"; + + extra = + g_strdup_printf ("kind: %s " + "x: %d y: %d w: %u h: %u " + "shaped: %d", + sev->kind == ShapeBounding ? + "ShapeBounding" : + (sev->kind == ShapeClip ? + "ShapeClip" : "(unknown)"), + sev->x, sev->y, sev->width, sev->height, + sev->shaped); + } + else +#endif /* HAVE_SHAPE */ + { + name = "(Unknown event)"; + extra = g_strdup_printf ("type: %d", event->xany.type); + } + break; + } + + screen = meta_display_screen_for_root (display, event->xany.window); + + if (screen) + winname = g_strdup_printf ("root %d", screen->number); + else + winname = g_strdup_printf ("0x%lx", event->xany.window); + + meta_topic (META_DEBUG_EVENTS, + "%s on %s%s %s %sserial %lu\n", name, winname, + extra ? ":" : "", extra ? extra : "", + event->xany.send_event ? "SEND " : "", + event->xany.serial); + + g_free (winname); + + if (extra) + g_free (extra); +} +#endif /* WITH_VERBOSE_MODE */ + +MetaWindow* +meta_display_lookup_x_window (MetaDisplay *display, + Window xwindow) +{ + return g_hash_table_lookup (display->window_ids, &xwindow); +} + +void +meta_display_register_x_window (MetaDisplay *display, + Window *xwindowp, + MetaWindow *window) +{ + g_return_if_fail (g_hash_table_lookup (display->window_ids, xwindowp) == NULL); + + g_hash_table_insert (display->window_ids, xwindowp, window); +} + +void +meta_display_unregister_x_window (MetaDisplay *display, + Window xwindow) +{ + g_return_if_fail (g_hash_table_lookup (display->window_ids, &xwindow) != NULL); + + g_hash_table_remove (display->window_ids, &xwindow); + + /* Remove any pending pings */ + remove_pending_pings_for_window (display, xwindow); +} + +gboolean +meta_display_xwindow_is_a_no_focus_window (MetaDisplay *display, + Window xwindow) +{ + gboolean is_a_no_focus_window = FALSE; + GSList *temp = display->screens; + while (temp != NULL) { + MetaScreen *screen = temp->data; + if (screen->no_focus_window == xwindow) { + is_a_no_focus_window = TRUE; + break; + } + temp = temp->next; + } + + return is_a_no_focus_window; +} + +Cursor +meta_display_create_x_cursor (MetaDisplay *display, + MetaCursor cursor) +{ + Cursor xcursor; + guint glyph; + + switch (cursor) + { + case META_CURSOR_DEFAULT: + glyph = XC_left_ptr; + break; + case META_CURSOR_NORTH_RESIZE: + glyph = XC_top_side; + break; + case META_CURSOR_SOUTH_RESIZE: + glyph = XC_bottom_side; + break; + case META_CURSOR_WEST_RESIZE: + glyph = XC_left_side; + break; + case META_CURSOR_EAST_RESIZE: + glyph = XC_right_side; + break; + case META_CURSOR_SE_RESIZE: + glyph = XC_bottom_right_corner; + break; + case META_CURSOR_SW_RESIZE: + glyph = XC_bottom_left_corner; + break; + case META_CURSOR_NE_RESIZE: + glyph = XC_top_right_corner; + break; + case META_CURSOR_NW_RESIZE: + glyph = XC_top_left_corner; + break; + case META_CURSOR_MOVE_OR_RESIZE_WINDOW: + glyph = XC_fleur; + break; + case META_CURSOR_BUSY: + glyph = XC_watch; + break; + + default: + g_assert_not_reached (); + glyph = 0; /* silence compiler */ + break; + } + + xcursor = XCreateFontCursor (display->xdisplay, glyph); + + return xcursor; +} + +static Cursor +xcursor_for_op (MetaDisplay *display, + MetaGrabOp op) +{ + MetaCursor cursor = META_CURSOR_DEFAULT; + + switch (op) + { + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + cursor = META_CURSOR_SE_RESIZE; + break; + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + cursor = META_CURSOR_SOUTH_RESIZE; + break; + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + cursor = META_CURSOR_SW_RESIZE; + break; + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + cursor = META_CURSOR_NORTH_RESIZE; + break; + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + cursor = META_CURSOR_NE_RESIZE; + break; + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + cursor = META_CURSOR_NW_RESIZE; + break; + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + cursor = META_CURSOR_WEST_RESIZE; + break; + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + cursor = META_CURSOR_EAST_RESIZE; + break; + case META_GRAB_OP_MOVING: + case META_GRAB_OP_KEYBOARD_MOVING: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + cursor = META_CURSOR_MOVE_OR_RESIZE_WINDOW; + break; + + default: + break; + } + + if (cursor == META_CURSOR_DEFAULT) + return None; + return meta_display_create_x_cursor (display, cursor); +} + +void +meta_display_set_grab_op_cursor (MetaDisplay *display, + MetaScreen *screen, + MetaGrabOp op, + gboolean change_pointer, + Window grab_xwindow, + guint32 timestamp) +{ + Cursor cursor; + + cursor = xcursor_for_op (display, op); + +#define GRAB_MASK (PointerMotionMask | \ + ButtonPressMask | ButtonReleaseMask | \ + EnterWindowMask | LeaveWindowMask) + + if (change_pointer) + { + meta_error_trap_push_with_return (display); + XChangeActivePointerGrab (display->xdisplay, + GRAB_MASK, + cursor, + timestamp); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Changed pointer with XChangeActivePointerGrab()\n"); + + if (meta_error_trap_pop_with_return (display, FALSE) != Success) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Error trapped from XChangeActivePointerGrab()\n"); + if (display->grab_have_pointer) + display->grab_have_pointer = FALSE; + } + } + else + { + g_assert (screen != NULL); + + meta_error_trap_push (display); + if (XGrabPointer (display->xdisplay, + grab_xwindow, + False, + GRAB_MASK, + GrabModeAsync, GrabModeAsync, + screen->xroot, + cursor, + timestamp) == GrabSuccess) + { + display->grab_have_pointer = TRUE; + meta_topic (META_DEBUG_WINDOW_OPS, + "XGrabPointer() returned GrabSuccess time %u\n", + timestamp); + } + else + { + meta_topic (META_DEBUG_WINDOW_OPS, + "XGrabPointer() failed time %u\n", + timestamp); + } + meta_error_trap_pop (display, TRUE); + } + +#undef GRAB_MASK + + if (cursor != None) + XFreeCursor (display->xdisplay, cursor); +} + +gboolean +meta_display_begin_grab_op (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + MetaGrabOp op, + gboolean pointer_already_grabbed, + gboolean frame_action, + int button, + gulong modmask, + guint32 timestamp, + int root_x, + int root_y) +{ + Window grab_xwindow; + + if (grab_op_is_mouse (op) && meta_grab_op_is_moving (op)) + { + if (display->compositor) + { + meta_compositor_begin_move (display->compositor, + window, &window->rect, + root_x, root_y); + } + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Doing grab op %u on window %s button %d pointer already grabbed: %d pointer pos %d,%d\n", + op, window ? window->desc : "none", button, pointer_already_grabbed, + root_x, root_y); + + if (display->grab_op != META_GRAB_OP_NONE) + { + if (window) + meta_warning ("Attempt to perform window operation %u on window %s when operation %u on %s already in effect\n", + op, window->desc, display->grab_op, + display->grab_window ? display->grab_window->desc : "none"); + return FALSE; + } + + if (window && + (meta_grab_op_is_moving (op) || meta_grab_op_is_resizing (op))) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + else + { + display->grab_initial_x = root_x; + display->grab_initial_y = root_y; + display->grab_threshold_movement_reached = FALSE; + } + } + + /* FIXME: + * If we have no MetaWindow we do our best + * and try to do the grab on the RootWindow. + * This will fail if anyone else has any + * key grab on the RootWindow. + */ + if (window) + grab_xwindow = window->frame ? window->frame->xwindow : window->xwindow; + else + grab_xwindow = screen->xroot; + + display->grab_have_pointer = FALSE; + + if (pointer_already_grabbed) + display->grab_have_pointer = TRUE; + + meta_display_set_grab_op_cursor (display, screen, op, FALSE, grab_xwindow, + timestamp); + + if (!display->grab_have_pointer) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "XGrabPointer() failed\n"); + return FALSE; + } + + /* Grab keys for keyboard ops and mouse move/resizes; see #126497 */ + if (grab_op_is_keyboard (op) || grab_op_is_mouse_only (op)) + { + if (window) + display->grab_have_keyboard = + meta_window_grab_all_keys (window, timestamp); + + else + display->grab_have_keyboard = + meta_screen_grab_all_keys (screen, timestamp); + + if (!display->grab_have_keyboard) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "grabbing all keys failed, ungrabbing pointer\n"); + XUngrabPointer (display->xdisplay, timestamp); + display->grab_have_pointer = FALSE; + return FALSE; + } + } + + display->grab_op = op; + display->grab_window = window; + display->grab_screen = screen; + display->grab_xwindow = grab_xwindow; + display->grab_button = button; + display->grab_mask = modmask; + display->grab_anchor_root_x = root_x; + display->grab_anchor_root_y = root_y; + display->grab_latest_motion_x = root_x; + display->grab_latest_motion_y = root_y; + display->grab_last_moveresize_time.tv_sec = 0; + display->grab_last_moveresize_time.tv_usec = 0; + display->grab_motion_notify_time = 0; + display->grab_old_window_stacking = NULL; +#ifdef HAVE_XSYNC + display->grab_sync_request_alarm = None; + display->grab_last_user_action_was_snap = FALSE; +#endif + display->grab_was_cancelled = FALSE; + display->grab_frame_action = frame_action; + + if (display->grab_resize_timeout_id) + { + g_source_remove (display->grab_resize_timeout_id); + display->grab_resize_timeout_id = 0; + } + + if (display->grab_window) + { + meta_window_get_client_root_coords (display->grab_window, + &display->grab_initial_window_pos); + display->grab_anchor_window_pos = display->grab_initial_window_pos; + + display->grab_wireframe_active = + (meta_prefs_get_reduced_resources () && !meta_prefs_get_mate_accessibility ()) && + (meta_grab_op_is_resizing (display->grab_op) || + meta_grab_op_is_moving (display->grab_op)); + + if (display->grab_wireframe_active) + { + meta_window_calc_showing (display->grab_window); + meta_window_begin_wireframe (window); + } + +#ifdef HAVE_XSYNC + if (!display->grab_wireframe_active && + meta_grab_op_is_resizing (display->grab_op) && + display->grab_window->sync_request_counter != None) + { + XSyncAlarmAttributes values; + XSyncValue init; + + meta_error_trap_push_with_return (display); + + /* Set the counter to 0, so we know that the application's + * responses to the client messages will always trigger + * a PositiveTransition + */ + + XSyncIntToValue (&init, 0); + XSyncSetCounter (display->xdisplay, + display->grab_window->sync_request_counter, init); + + display->grab_window->sync_request_serial = 0; + display->grab_window->sync_request_time.tv_sec = 0; + display->grab_window->sync_request_time.tv_usec = 0; + + values.trigger.counter = display->grab_window->sync_request_counter; + values.trigger.value_type = XSyncAbsolute; + values.trigger.test_type = XSyncPositiveTransition; + XSyncIntToValue (&values.trigger.wait_value, + display->grab_window->sync_request_serial + 1); + + /* After triggering, increment test_value by this. + * (NOT wait_value above) + */ + XSyncIntToValue (&values.delta, 1); + + /* we want events (on by default anyway) */ + values.events = True; + + display->grab_sync_request_alarm = XSyncCreateAlarm (display->xdisplay, + XSyncCACounter | + XSyncCAValueType | + XSyncCAValue | + XSyncCATestType | + XSyncCADelta | + XSyncCAEvents, + &values); + + if (meta_error_trap_pop_with_return (display, FALSE) != Success) + display->grab_sync_request_alarm = None; + + meta_topic (META_DEBUG_RESIZING, + "Created update alarm 0x%lx\n", + display->grab_sync_request_alarm); + } +#endif + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Grab op %u on window %s successful\n", + display->grab_op, window ? window->desc : "(null)"); + + g_assert (display->grab_window != NULL || display->grab_screen != NULL); + g_assert (display->grab_op != META_GRAB_OP_NONE); + + /* If this is a move or resize, cache the window edges for + * resistance/snapping + */ + if (meta_grab_op_is_resizing (display->grab_op) || + meta_grab_op_is_moving (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Computing edges to resist-movement or snap-to for %s.\n", + window->desc); + meta_display_compute_resistance_and_snapping_edges (display); + } + + /* Save the old stacking */ + if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Saving old stack positions; old pointer was %p.\n", + display->grab_old_window_stacking); + display->grab_old_window_stacking = + meta_stack_get_positions (screen->stack); + } + + /* Do this last, after everything is set up. */ + switch (op) + { + case META_GRAB_OP_KEYBOARD_TABBING_NORMAL: + meta_screen_ensure_tab_popup (screen, + META_TAB_LIST_NORMAL, + META_TAB_SHOW_ICON); + break; + case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL: + meta_screen_ensure_tab_popup (screen, + META_TAB_LIST_NORMAL, + META_TAB_SHOW_INSTANTLY); + break; + + case META_GRAB_OP_KEYBOARD_TABBING_DOCK: + meta_screen_ensure_tab_popup (screen, + META_TAB_LIST_DOCKS, + META_TAB_SHOW_ICON); + break; + case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK: + meta_screen_ensure_tab_popup (screen, + META_TAB_LIST_DOCKS, + META_TAB_SHOW_INSTANTLY); + break; + case META_GRAB_OP_KEYBOARD_TABBING_GROUP: + meta_screen_ensure_tab_popup (screen, + META_TAB_LIST_GROUP, + META_TAB_SHOW_ICON); + break; + case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP: + meta_screen_ensure_tab_popup (screen, + META_TAB_LIST_GROUP, + META_TAB_SHOW_INSTANTLY); + + case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING: + meta_screen_ensure_workspace_popup (screen); + break; + + default: + break; + } + + if (display->grab_window) + { + meta_window_refresh_resize_popup (display->grab_window); + } + + return TRUE; +} + +void +meta_display_end_grab_op (MetaDisplay *display, + guint32 timestamp) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Ending grab op %u at time %u\n", display->grab_op, timestamp); + + if (display->grab_op == META_GRAB_OP_NONE) + return; + + if (display->grab_window != NULL) + display->grab_window->shaken_loose = FALSE; + + if (display->grab_window != NULL && + !meta_prefs_get_raise_on_click () && + (meta_grab_op_is_moving (display->grab_op) || + meta_grab_op_is_resizing (display->grab_op))) + { + /* Only raise the window in orthogonal raise + * ('do-not-raise-on-click') mode if the user didn't try to move + * or resize the given window by at least a threshold amount. + * For raise on click mode, the window was raised at the + * beginning of the grab_op. + */ + if (!display->grab_threshold_movement_reached) + meta_window_raise (display->grab_window); + } + + if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op) || + display->grab_op == META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING) + { + meta_ui_tab_popup_free (display->grab_screen->tab_popup); + display->grab_screen->tab_popup = NULL; + + /* If the ungrab here causes an EnterNotify, ignore it for + * sloppy focus + */ + display->ungrab_should_not_cause_focus_window = display->grab_xwindow; + } + + /* If this was a move or resize clear out the edge cache */ + if (meta_grab_op_is_resizing (display->grab_op) || + meta_grab_op_is_moving (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Clearing out the edges for resistance/snapping"); + meta_display_cleanup_edges (display); + } + + if (display->grab_old_window_stacking != NULL) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Clearing out the old stack position, which was %p.\n", + display->grab_old_window_stacking); + g_list_free (display->grab_old_window_stacking); + display->grab_old_window_stacking = NULL; + } + + if (display->grab_wireframe_active) + { + display->grab_wireframe_active = FALSE; + meta_window_end_wireframe (display->grab_window); + + if (!display->grab_was_cancelled) + { + if (meta_grab_op_is_moving (display->grab_op)) + meta_window_move (display->grab_window, + TRUE, + display->grab_wireframe_rect.x, + display->grab_wireframe_rect.y); + if (meta_grab_op_is_resizing (display->grab_op)) + meta_window_resize_with_gravity (display->grab_window, + TRUE, + display->grab_wireframe_rect.width, + display->grab_wireframe_rect.height, + meta_resize_gravity_from_grab_op (display->grab_op)); + } + meta_window_calc_showing (display->grab_window); + } + + if (display->compositor && + display->grab_window && + grab_op_is_mouse (display->grab_op) && + meta_grab_op_is_moving (display->grab_op)) + { + meta_compositor_end_move (display->compositor, + display->grab_window); + } + + if (display->grab_have_pointer) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Ungrabbing pointer with timestamp %u\n", timestamp); + XUngrabPointer (display->xdisplay, timestamp); + } + + if (display->grab_have_keyboard) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Ungrabbing all keys timestamp %u\n", timestamp); + if (display->grab_window) + meta_window_ungrab_all_keys (display->grab_window, timestamp); + else + meta_screen_ungrab_all_keys (display->grab_screen, timestamp); + } + +#ifdef HAVE_XSYNC + if (display->grab_sync_request_alarm != None) + { + XSyncDestroyAlarm (display->xdisplay, + display->grab_sync_request_alarm); + display->grab_sync_request_alarm = None; + } +#endif /* HAVE_XSYNC */ + + display->grab_window = NULL; + display->grab_screen = NULL; + display->grab_xwindow = None; + display->grab_op = META_GRAB_OP_NONE; + + if (display->grab_resize_popup) + { + meta_ui_resize_popup_free (display->grab_resize_popup); + display->grab_resize_popup = NULL; + } + + if (display->grab_resize_timeout_id) + { + g_source_remove (display->grab_resize_timeout_id); + display->grab_resize_timeout_id = 0; + } +} + +void +meta_display_check_threshold_reached (MetaDisplay *display, + int x, + int y) +{ + /* Don't bother doing the check again if we've already reached the threshold */ + if (meta_prefs_get_raise_on_click () || + display->grab_threshold_movement_reached) + return; + + if (ABS (display->grab_initial_x - x) >= 8 || + ABS (display->grab_initial_y - y) >= 8) + display->grab_threshold_movement_reached = TRUE; +} + +static void +meta_change_button_grab (MetaDisplay *display, + Window xwindow, + gboolean grab, + gboolean sync, + int button, + int modmask) +{ + unsigned int ignored_mask; + + meta_verbose ("%s 0x%lx sync = %d button = %d modmask 0x%x\n", + grab ? "Grabbing" : "Ungrabbing", + xwindow, + sync, button, modmask); + + meta_error_trap_push (display); + + ignored_mask = 0; + while (ignored_mask <= display->ignored_modifier_mask) + { + if (ignored_mask & ~(display->ignored_modifier_mask)) + { + /* Not a combination of ignored modifiers + * (it contains some non-ignored modifiers) + */ + ++ignored_mask; + continue; + } + + if (meta_is_debugging ()) + meta_error_trap_push_with_return (display); + + /* GrabModeSync means freeze until XAllowEvents */ + + if (grab) + XGrabButton (display->xdisplay, button, modmask | ignored_mask, + xwindow, False, + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | PointerMotionHintMask, + sync ? GrabModeSync : GrabModeAsync, + GrabModeAsync, + False, None); + else + XUngrabButton (display->xdisplay, button, modmask | ignored_mask, + xwindow); + + if (meta_is_debugging ()) + { + int result; + + result = meta_error_trap_pop_with_return (display, FALSE); + + if (result != Success) + meta_verbose ("Failed to %s button %d with mask 0x%x for window 0x%lx error code %d\n", + grab ? "grab" : "ungrab", + button, modmask | ignored_mask, xwindow, result); + } + + ++ignored_mask; + } + + meta_error_trap_pop (display, FALSE); +} + +void +meta_display_grab_window_buttons (MetaDisplay *display, + Window xwindow) +{ + /* Grab Alt + button1 for moving window. + * Grab Alt + button2 for resizing window. + * Grab Alt + button3 for popping up window menu. + * Grab Alt + Shift + button1 for snap-moving window. + */ + meta_verbose ("Grabbing window buttons for 0x%lx\n", xwindow); + + /* FIXME If we ignored errors here instead of spewing, we could + * put one big error trap around the loop and avoid a bunch of + * XSync() + */ + + if (display->window_grab_modifiers != 0) + { + gboolean debug = g_getenv ("MARCO_DEBUG_BUTTON_GRABS") != NULL; + int i; + for (i = 1; i < 4; i++) + { + meta_change_button_grab (display, xwindow, + TRUE, + FALSE, + i, display->window_grab_modifiers); + + /* This is for debugging, since I end up moving the Xnest + * otherwise ;-) + */ + if (debug) + meta_change_button_grab (display, xwindow, + TRUE, + FALSE, + i, ControlMask); + } + + /* In addition to grabbing Alt+Button1 for moving the window, + * grab Alt+Shift+Button1 for snap-moving the window. See bug + * 112478. Unfortunately, this doesn't work with + * Shift+Alt+Button1 for some reason; so at least part of the + * order still matters, which sucks (please FIXME). + */ + meta_change_button_grab (display, xwindow, + TRUE, + FALSE, + 1, display->window_grab_modifiers | ShiftMask); + } +} + +void +meta_display_ungrab_window_buttons (MetaDisplay *display, + Window xwindow) +{ + gboolean debug; + int i; + + if (display->window_grab_modifiers == 0) + return; + + debug = g_getenv ("MARCO_DEBUG_BUTTON_GRABS") != NULL; + i = 1; + while (i < 4) + { + meta_change_button_grab (display, xwindow, + FALSE, FALSE, i, + display->window_grab_modifiers); + + if (debug) + meta_change_button_grab (display, xwindow, + FALSE, FALSE, i, ControlMask); + + ++i; + } +} + +/* Grab buttons we only grab while unfocused in click-to-focus mode */ +#define MAX_FOCUS_BUTTON 4 +void +meta_display_grab_focus_window_button (MetaDisplay *display, + MetaWindow *window) +{ + /* Grab button 1 for activating unfocused windows */ + meta_verbose ("Grabbing unfocused window buttons for %s\n", window->desc); + +#if 0 + /* FIXME:115072 */ + /* Don't grab at all unless in click to focus mode. In click to + * focus, we may sometimes be clever about intercepting and eating + * the focus click. But in mouse focus, we never do that since the + * focus window may not be raised, and who wants to think about + * mouse focus anyway. + */ + if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK) + { + meta_verbose (" (well, not grabbing since not in click to focus mode)\n"); + return; + } +#endif + + if (window->have_focus_click_grab) + { + meta_verbose (" (well, not grabbing since we already have the grab)\n"); + return; + } + + /* FIXME If we ignored errors here instead of spewing, we could + * put one big error trap around the loop and avoid a bunch of + * XSync() + */ + + { + int i = 1; + while (i < MAX_FOCUS_BUTTON) + { + meta_change_button_grab (display, + window->xwindow, + TRUE, TRUE, + i, 0); + + ++i; + } + + window->have_focus_click_grab = TRUE; + } +} + +void +meta_display_ungrab_focus_window_button (MetaDisplay *display, + MetaWindow *window) +{ + meta_verbose ("Ungrabbing unfocused window buttons for %s\n", window->desc); + + if (!window->have_focus_click_grab) + return; + + { + int i = 1; + while (i < MAX_FOCUS_BUTTON) + { + meta_change_button_grab (display, window->xwindow, + FALSE, FALSE, i, 0); + + ++i; + } + + window->have_focus_click_grab = FALSE; + } +} + +void +meta_display_increment_event_serial (MetaDisplay *display) +{ + /* We just make some random X request */ + XDeleteProperty (display->xdisplay, display->leader_window, + display->atom__MOTIF_WM_HINTS); +} + +void +meta_display_update_active_window_hint (MetaDisplay *display) +{ + GSList *tmp; + + gulong data[1]; + + if (display->focus_window) + data[0] = display->focus_window->xwindow; + else + data[0] = None; + + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + meta_error_trap_push (display); + XChangeProperty (display->xdisplay, screen->xroot, + display->atom__NET_ACTIVE_WINDOW, + XA_WINDOW, + 32, PropModeReplace, (guchar*) data, 1); + + meta_error_trap_pop (display, FALSE); + + tmp = tmp->next; + } +} + +void +meta_display_queue_retheme_all_windows (MetaDisplay *display) +{ + GSList* windows; + GSList *tmp; + + windows = meta_display_list_windows (display); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); + if (window->frame) + { + window->frame->need_reapply_frame_shape = TRUE; + + meta_frame_queue_draw (window->frame); + } + + tmp = tmp->next; + } + + g_slist_free (windows); +} + +void +meta_display_retheme_all (void) +{ + meta_display_queue_retheme_all_windows (meta_get_display ()); +} + +void +meta_display_set_cursor_theme (const char *theme, + int size) +{ +#ifdef HAVE_XCURSOR + GSList *tmp; + + MetaDisplay *display = meta_get_display (); + + XcursorSetTheme (display->xdisplay, theme); + XcursorSetDefaultSize (display->xdisplay, size); + + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + meta_screen_update_cursor (screen); + + tmp = tmp->next; + } + +#endif +} + +/** + * Stores whether syncing is currently enabled. + */ +static gboolean is_syncing = FALSE; + +/** + * Returns whether X synchronisation is currently enabled. + * + * \return true if we must wait for events whenever we send X requests; + * false otherwise. + * + * \bug This is *only* called by meta_display_open, but by that time + * we have already turned syncing on or off on startup, and we don't + * have any way to do so while Marco is running, so it's rather + * pointless. + */ +gboolean +meta_is_syncing (void) +{ + return is_syncing; +} + +/** + * A handy way to turn on synchronisation on or off for every display. + * + * \bug Of course there is only one display ever anyway, so this can + * be rather hugely simplified. + */ +void +meta_set_syncing (gboolean setting) +{ + if (setting != is_syncing) + { + is_syncing = setting; + if (meta_get_display ()) + XSynchronize (meta_get_display ()->xdisplay, is_syncing); + } +} + +/** + * How long, in milliseconds, we should wait after pinging a window + * before deciding it's not going to get back to us. + */ +#define PING_TIMEOUT_DELAY 5000 + +/** + * Does whatever it is we decided to do when a window didn't respond + * to a ping. We also remove the ping from the display's list of + * pending pings. This function is called by the event loop when the timeout + * times out which we created at the start of the ping. + * + * \param data All the information about this ping. It is a MetaPingData + * cast to a void* in order to be passable to a timeout function. + * This function will also free this parameter. + * + * \return Always returns false, because this function is called as a + * timeout and we don't want to run the timer again. + * + * \ingroup pings + */ +static gboolean +meta_display_ping_timeout (gpointer data) +{ + MetaPingData *ping_data; + + ping_data = data; + + ping_data->ping_timeout_id = 0; + + meta_topic (META_DEBUG_PING, + "Ping %u on window %lx timed out\n", + ping_data->timestamp, ping_data->xwindow); + + (* ping_data->ping_timeout_func) (ping_data->display, ping_data->xwindow, + ping_data->timestamp, ping_data->user_data); + + ping_data->display->pending_pings = + g_slist_remove (ping_data->display->pending_pings, + ping_data); + ping_data_free (ping_data); + + return FALSE; +} + +/** + * Sends a ping request to a window. The window must respond to + * the request within a certain amount of time. If it does, we + * will call one callback; if the time passes and we haven't had + * a response, we call a different callback. The window must have + * the hint showing that it can respond to a ping; if it doesn't, + * we call the "got a response" callback immediately and return. + * This function returns straight away after setting things up; + * the callbacks will be called from the event loop. + * + * \param display The MetaDisplay that the window is on + * \param window The MetaWindow to send the ping to + * \param timestamp The timestamp of the ping. Used for uniqueness. + * Cannot be CurrentTime; use a real timestamp! + * \param ping_reply_func The callback to call if we get a response. + * \param ping_timeout_func The callback to call if we don't get a response. + * \param user_data Arbitrary data that will be passed to the callback + * function. (In practice it's often a pointer to + * the window.) + * + * \bug This should probably be a method on windows, rather than displays + * for one of their windows. + * + * \ingroup pings + */ +void +meta_display_ping_window (MetaDisplay *display, + MetaWindow *window, + guint32 timestamp, + MetaWindowPingFunc ping_reply_func, + MetaWindowPingFunc ping_timeout_func, + gpointer user_data) +{ + MetaPingData *ping_data; + + if (timestamp == CurrentTime) + { + meta_warning ("Tried to ping a window with CurrentTime! Not allowed.\n"); + return; + } + + if (!window->net_wm_ping) + { + if (ping_reply_func) + (* ping_reply_func) (display, window->xwindow, timestamp, user_data); + + return; + } + + ping_data = g_new (MetaPingData, 1); + ping_data->display = display; + ping_data->xwindow = window->xwindow; + ping_data->timestamp = timestamp; + ping_data->ping_reply_func = ping_reply_func; + ping_data->ping_timeout_func = ping_timeout_func; + ping_data->user_data = user_data; + ping_data->ping_timeout_id = g_timeout_add (PING_TIMEOUT_DELAY, + meta_display_ping_timeout, + ping_data); + + display->pending_pings = g_slist_prepend (display->pending_pings, ping_data); + + meta_topic (META_DEBUG_PING, + "Sending ping with timestamp %u to window %s\n", + timestamp, window->desc); + meta_window_send_icccm_message (window, + display->atom__NET_WM_PING, + timestamp); +} + +static void +process_request_frame_extents (MetaDisplay *display, + XEvent *event) +{ + /* The X window whose frame extents will be set. */ + Window xwindow = event->xclient.window; + unsigned long data[4] = { 0, 0, 0, 0 }; + + MotifWmHints *hints = NULL; + gboolean hints_set = FALSE; + + meta_verbose ("Setting frame extents for 0x%lx\n", xwindow); + + /* See if the window is decorated. */ + hints_set = meta_prop_get_motif_hints (display, + xwindow, + display->atom__MOTIF_WM_HINTS, + &hints); + if ((hints_set && hints->decorations) || !hints_set) + { + int top = 0; + int bottom = 0; + int left = 0; + int right = 0; + + MetaScreen *screen; + + screen = meta_display_screen_for_xwindow (display, + event->xclient.window); + if (screen == NULL) + { + meta_warning ("Received request to set _NET_FRAME_EXTENTS " + "on 0x%lx which is on a screen we are not managing\n", + event->xclient.window); + meta_XFree (hints); + return; + } + + /* Return estimated frame extents for a normal window. */ + meta_ui_theme_get_frame_borders (screen->ui, + META_FRAME_TYPE_NORMAL, + 0, + &top, + &bottom, + &left, + &right); + + data[0] = left; + data[1] = right; + data[2] = top; + data[3] = bottom; + } + + meta_topic (META_DEBUG_GEOMETRY, + "Setting _NET_FRAME_EXTENTS on unmanaged window 0x%lx " + "to top = %lu, left = %lu, bottom = %lu, right = %lu\n", + xwindow, data[0], data[1], data[2], data[3]); + + meta_error_trap_push (display); + XChangeProperty (display->xdisplay, xwindow, + display->atom__NET_FRAME_EXTENTS, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 4); + meta_error_trap_pop (display, FALSE); + + meta_XFree (hints); +} + +/** + * Process the pong (the response message) from the ping we sent + * to the window. This involves removing the timeout, calling the + * reply handler function, and freeing memory. + * + * \param display the display we got the pong from + * \param event the XEvent which is a pong; we can tell which + * ping it corresponds to because it bears the + * same timestamp. + * + * \ingroup pings + */ +static void +process_pong_message (MetaDisplay *display, + XEvent *event) +{ + GSList *tmp; + guint32 timestamp = event->xclient.data.l[1]; + + meta_topic (META_DEBUG_PING, "Received a pong with timestamp %u\n", + timestamp); + + for (tmp = display->pending_pings; tmp; tmp = tmp->next) + { + MetaPingData *ping_data = tmp->data; + + if (timestamp == ping_data->timestamp) + { + meta_topic (META_DEBUG_PING, + "Matching ping found for pong %u\n", + ping_data->timestamp); + + /* Remove the ping data from the list */ + display->pending_pings = g_slist_remove (display->pending_pings, + ping_data); + + /* Remove the timeout */ + if (ping_data->ping_timeout_id != 0) + { + g_source_remove (ping_data->ping_timeout_id); + ping_data->ping_timeout_id = 0; + } + + /* Call callback */ + (* ping_data->ping_reply_func) (display, + ping_data->xwindow, + ping_data->timestamp, + ping_data->user_data); + + ping_data_free (ping_data); + + break; + } + } +} + +/** + * Finds whether a window has any pings waiting on it. + * + * \param display The MetaDisplay of the window. + * \param window The MetaWindow whose pings we want to know about. + * + * \return True if there is at least one ping which has been sent + * to the window without getting a response; false otherwise. + * + * \bug This should probably be a method on windows, rather than displays + * for one of their windows. + * + * \ingroup pings + */ +gboolean +meta_display_window_has_pending_pings (MetaDisplay *display, + MetaWindow *window) +{ + GSList *tmp; + + for (tmp = display->pending_pings; tmp; tmp = tmp->next) + { + MetaPingData *ping_data = tmp->data; + + if (ping_data->xwindow == window->xwindow) + return TRUE; + } + + return FALSE; +} + +MetaGroup* +get_focussed_group (MetaDisplay *display) +{ + if (display->focus_window) + return display->focus_window->group; + else + return NULL; +} + +#define IN_TAB_CHAIN(w,t) (((t) == META_TAB_LIST_NORMAL && META_WINDOW_IN_NORMAL_TAB_CHAIN (w)) \ + || ((t) == META_TAB_LIST_DOCKS && META_WINDOW_IN_DOCK_TAB_CHAIN (w)) \ + || ((t) == META_TAB_LIST_GROUP && META_WINDOW_IN_GROUP_TAB_CHAIN (w, get_focussed_group(w->display)))) + +static MetaWindow* +find_tab_forward (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace, + GList *start, + gboolean skip_first) +{ + GList *tmp; + + g_return_val_if_fail (start != NULL, NULL); + g_return_val_if_fail (workspace != NULL, NULL); + + tmp = start; + if (skip_first) + tmp = tmp->next; + + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (window->screen == screen && + IN_TAB_CHAIN (window, type)) + return window; + + tmp = tmp->next; + } + + tmp = workspace->mru_list; + while (tmp != start && tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (IN_TAB_CHAIN (window, type)) + return window; + + tmp = tmp->next; + } + + return NULL; +} + +static MetaWindow* +find_tab_backward (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace, + GList *start, + gboolean skip_last) +{ + GList *tmp; + + g_return_val_if_fail (start != NULL, NULL); + g_return_val_if_fail (workspace != NULL, NULL); + + tmp = start; + if (skip_last) + tmp = tmp->prev; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (window->screen == screen && + IN_TAB_CHAIN (window, type)) + return window; + + tmp = tmp->prev; + } + + tmp = g_list_last (workspace->mru_list); + while (tmp != start) + { + MetaWindow *window = tmp->data; + + if (IN_TAB_CHAIN (window, type)) + return window; + + tmp = tmp->prev; + } + + return NULL; +} + +GList* +meta_display_get_tab_list (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace) +{ + GList *tab_list; + + g_return_val_if_fail (workspace != NULL, NULL); + + /* Windows sellout mode - MRU order. Collect unminimized windows + * then minimized so minimized windows aren't in the way so much. + */ + { + GList *tmp; + + tab_list = NULL; + tmp = workspace->mru_list; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (!window->minimized && + window->screen == screen && + IN_TAB_CHAIN (window, type)) + tab_list = g_list_prepend (tab_list, window); + + tmp = tmp->next; + } + } + + { + GList *tmp; + + tmp = workspace->mru_list; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (window->minimized && + window->screen == screen && + IN_TAB_CHAIN (window, type)) + tab_list = g_list_prepend (tab_list, window); + + tmp = tmp->next; + } + } + + tab_list = g_list_reverse (tab_list); + + { + GSList *tmp; + MetaWindow *l_window; + + tmp = meta_display_list_windows (display); + + /* Go through all windows */ + while (tmp != NULL) + { + l_window=tmp->data; + + /* Check to see if it demands attention */ + if (l_window->wm_state_demands_attention && + l_window->workspace!=workspace && + IN_TAB_CHAIN (l_window, type)) + { + /* if it does, add it to the popup */ + tab_list = g_list_prepend (tab_list, l_window); + } + + tmp = tmp->next; + } /* End while tmp!=NULL */ + } + + return tab_list; +} + +MetaWindow* +meta_display_get_tab_next (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace, + MetaWindow *window, + gboolean backward) +{ + gboolean skip; + GList *tab_list; + MetaWindow *ret; + tab_list = meta_display_get_tab_list(display, + type, + screen, + workspace); + + if (tab_list == NULL) + return NULL; + + if (window != NULL) + { + g_assert (window->display == display); + + if (backward) + ret = find_tab_backward (display, type, screen, workspace, + g_list_find (tab_list, + window), + TRUE); + else + ret = find_tab_forward (display, type, screen, workspace, + g_list_find (tab_list, + window), + TRUE); + } + else + { + skip = display->focus_window != NULL && + tab_list->data == display->focus_window; + if (backward) + ret = find_tab_backward (display, type, screen, workspace, + tab_list, skip); + else + ret = find_tab_forward (display, type, screen, workspace, + tab_list, skip); + } + + g_list_free (tab_list); + return ret; +} + +MetaWindow* +meta_display_get_tab_current (MetaDisplay *display, + MetaTabList type, + MetaScreen *screen, + MetaWorkspace *workspace) +{ + MetaWindow *window; + + window = display->focus_window; + + if (window != NULL && + window->screen == screen && + IN_TAB_CHAIN (window, type) && + (workspace == NULL || + meta_window_located_on_workspace (window, workspace))) + return window; + else + return NULL; +} + +int +meta_resize_gravity_from_grab_op (MetaGrabOp op) +{ + int gravity; + + gravity = -1; + switch (op) + { + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + gravity = NorthWestGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_RESIZING_S: + gravity = NorthGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_RESIZING_SW: + gravity = NorthEastGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_RESIZING_N: + gravity = SouthGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_RESIZING_NE: + gravity = SouthWestGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + case META_GRAB_OP_RESIZING_NW: + gravity = SouthEastGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_RESIZING_E: + gravity = WestGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_RESIZING_W: + gravity = EastGravity; + break; + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + gravity = CenterGravity; + break; + default: + break; + } + + return gravity; +} + +static MetaScreen* +find_screen_for_selection (MetaDisplay *display, + Window owner, + Atom selection) +{ + GSList *tmp; + + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + if (screen->wm_sn_selection_window == owner && + screen->wm_sn_atom == selection) + return screen; + + tmp = tmp->next; + } + + return NULL; +} + +/* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */ +static gboolean +convert_property (MetaDisplay *display, + MetaScreen *screen, + Window w, + Atom target, + Atom property) +{ +#define N_TARGETS 4 + Atom conversion_targets[N_TARGETS]; + long icccm_version[] = { 2, 0 }; + + conversion_targets[0] = display->atom_TARGETS; + conversion_targets[1] = display->atom_MULTIPLE; + conversion_targets[2] = display->atom_TIMESTAMP; + conversion_targets[3] = display->atom_VERSION; + + meta_error_trap_push_with_return (display); + if (target == display->atom_TARGETS) + XChangeProperty (display->xdisplay, w, property, + XA_ATOM, 32, PropModeReplace, + (unsigned char *)conversion_targets, N_TARGETS); + else if (target == display->atom_TIMESTAMP) + XChangeProperty (display->xdisplay, w, property, + XA_INTEGER, 32, PropModeReplace, + (unsigned char *)&screen->wm_sn_timestamp, 1); + else if (target == display->atom_VERSION) + XChangeProperty (display->xdisplay, w, property, + XA_INTEGER, 32, PropModeReplace, + (unsigned char *)icccm_version, 2); + else + { + meta_error_trap_pop_with_return (display, FALSE); + return FALSE; + } + + if (meta_error_trap_pop_with_return (display, FALSE) != Success) + return FALSE; + + /* Be sure the PropertyNotify has arrived so we + * can send SelectionNotify + */ + /* FIXME the error trap pop synced anyway, right? */ + meta_topic (META_DEBUG_SYNC, "Syncing on %s\n", G_STRFUNC); + XSync (display->xdisplay, False); + + return TRUE; +} + +/* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */ +static void +process_selection_request (MetaDisplay *display, + XEvent *event) +{ + XSelectionEvent reply; + MetaScreen *screen; + + screen = find_screen_for_selection (display, + event->xselectionrequest.owner, + event->xselectionrequest.selection); + + if (screen == NULL) + { + char *str; + + meta_error_trap_push (display); + str = XGetAtomName (display->xdisplay, + event->xselectionrequest.selection); + meta_error_trap_pop (display, TRUE); + + meta_verbose ("Selection request with selection %s window 0x%lx not a WM_Sn selection we recognize\n", + str ? str : "(bad atom)", event->xselectionrequest.owner); + + meta_XFree (str); + + return; + } + + reply.type = SelectionNotify; + reply.display = display->xdisplay; + reply.requestor = event->xselectionrequest.requestor; + reply.selection = event->xselectionrequest.selection; + reply.target = event->xselectionrequest.target; + reply.property = None; + reply.time = event->xselectionrequest.time; + + if (event->xselectionrequest.target == display->atom_MULTIPLE) + { + if (event->xselectionrequest.property != None) + { + Atom type, *adata; + int i, format; + unsigned long num, rest; + unsigned char *data; + + meta_error_trap_push_with_return (display); + if (XGetWindowProperty (display->xdisplay, + event->xselectionrequest.requestor, + event->xselectionrequest.property, 0, 256, False, + display->atom_ATOM_PAIR, + &type, &format, &num, &rest, &data) != Success) + { + meta_error_trap_pop_with_return (display, TRUE); + return; + } + + if (meta_error_trap_pop_with_return (display, TRUE) == Success) + { + /* FIXME: to be 100% correct, should deal with rest > 0, + * but since we have 4 possible targets, we will hardly ever + * meet multiple requests with a length > 8 + */ + adata = (Atom*)data; + i = 0; + while (i < (int) num) + { + if (!convert_property (display, screen, + event->xselectionrequest.requestor, + adata[i], adata[i+1])) + adata[i+1] = None; + i += 2; + } + + meta_error_trap_push (display); + XChangeProperty (display->xdisplay, + event->xselectionrequest.requestor, + event->xselectionrequest.property, + display->atom_ATOM_PAIR, + 32, PropModeReplace, data, num); + meta_error_trap_pop (display, FALSE); + meta_XFree (data); + } + } + } + else + { + if (event->xselectionrequest.property == None) + event->xselectionrequest.property = event->xselectionrequest.target; + + if (convert_property (display, screen, + event->xselectionrequest.requestor, + event->xselectionrequest.target, + event->xselectionrequest.property)) + reply.property = event->xselectionrequest.property; + } + + XSendEvent (display->xdisplay, + event->xselectionrequest.requestor, + False, 0L, (XEvent*)&reply); + + meta_verbose ("Handled selection request\n"); +} + +static void +process_selection_clear (MetaDisplay *display, + XEvent *event) +{ + /* We need to unmanage the screen on which we lost the selection */ + MetaScreen *screen; + + screen = find_screen_for_selection (display, + event->xselectionclear.window, + event->xselectionclear.selection); + + + if (screen != NULL) + { + meta_verbose ("Got selection clear for screen %d on display %s\n", + screen->number, display->name); + + meta_display_unmanage_screen (&display, + screen, + event->xselectionclear.time); + + if (!display) + the_display = NULL; + + /* display and screen may both be invalid memory... */ + + return; + } + + { + char *str; + + meta_error_trap_push (display); + str = XGetAtomName (display->xdisplay, + event->xselectionclear.selection); + meta_error_trap_pop (display, TRUE); + + meta_verbose ("Selection clear with selection %s window 0x%lx not a WM_Sn selection we recognize\n", + str ? str : "(bad atom)", event->xselectionclear.window); + + meta_XFree (str); + } +} + +void +meta_display_unmanage_screen (MetaDisplay **displayp, + MetaScreen *screen, + guint32 timestamp) +{ + MetaDisplay *display = *displayp; + + meta_verbose ("Unmanaging screen %d on display %s\n", + screen->number, display->name); + + g_return_if_fail (g_slist_find (display->screens, screen) != NULL); + + meta_screen_free (screen, timestamp); + display->screens = g_slist_remove (display->screens, screen); + + if (display->screens == NULL) + { + meta_display_close (display, timestamp); + *displayp = NULL; + } +} + +void +meta_display_unmanage_windows_for_screen (MetaDisplay *display, + MetaScreen *screen, + guint32 timestamp) +{ + GSList *tmp; + GSList *winlist; + + winlist = meta_display_list_windows (display); + winlist = g_slist_sort (winlist, meta_display_stack_cmp); + + /* Unmanage all windows */ + tmp = winlist; + while (tmp != NULL) + { + meta_window_free (tmp->data, timestamp); + + tmp = tmp->next; + } + g_slist_free (winlist); +} + +int +meta_display_stack_cmp (const void *a, + const void *b) +{ + MetaWindow *aw = (void*) a; + MetaWindow *bw = (void*) b; + + if (aw->screen == bw->screen) + return meta_stack_windows_cmp (aw->screen->stack, aw, bw); + /* Then assume screens are stacked by number */ + else if (aw->screen->number < bw->screen->number) + return -1; + else if (aw->screen->number > bw->screen->number) + return 1; + else + return 0; /* not reached in theory, if windows on same display */ +} + +void +meta_display_devirtualize_modifiers (MetaDisplay *display, + MetaVirtualModifier modifiers, + unsigned int *mask) +{ + *mask = 0; + + if (modifiers & META_VIRTUAL_SHIFT_MASK) + *mask |= ShiftMask; + if (modifiers & META_VIRTUAL_CONTROL_MASK) + *mask |= ControlMask; + if (modifiers & META_VIRTUAL_ALT_MASK) + *mask |= Mod1Mask; + if (modifiers & META_VIRTUAL_META_MASK) + *mask |= display->meta_mask; + if (modifiers & META_VIRTUAL_HYPER_MASK) + *mask |= display->hyper_mask; + if (modifiers & META_VIRTUAL_SUPER_MASK) + *mask |= display->super_mask; + if (modifiers & META_VIRTUAL_MOD2_MASK) + *mask |= Mod2Mask; + if (modifiers & META_VIRTUAL_MOD3_MASK) + *mask |= Mod3Mask; + if (modifiers & META_VIRTUAL_MOD4_MASK) + *mask |= Mod4Mask; + if (modifiers & META_VIRTUAL_MOD5_MASK) + *mask |= Mod5Mask; +} + +static void +update_window_grab_modifiers (MetaDisplay *display) + +{ + MetaVirtualModifier virtual_mods; + unsigned int mods; + + virtual_mods = meta_prefs_get_mouse_button_mods (); + meta_display_devirtualize_modifiers (display, virtual_mods, + &mods); + + display->window_grab_modifiers = mods; +} + +static void +prefs_changed_callback (MetaPreference pref, + void *data) +{ + MetaDisplay *display = data; + + /* It may not be obvious why we regrab on focus mode + * change; it's because we handle focus clicks a + * bit differently for the different focus modes. + */ + if (pref == META_PREF_MOUSE_BUTTON_MODS || + pref == META_PREF_FOCUS_MODE) + { + MetaDisplay *display = data; + GSList *windows; + GSList *tmp; + + windows = meta_display_list_windows (display); + + /* Ungrab all */ + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + meta_display_ungrab_window_buttons (display, w->xwindow); + meta_display_ungrab_focus_window_button (display, w); + tmp = tmp->next; + } + + /* change our modifier */ + if (pref == META_PREF_MOUSE_BUTTON_MODS) + update_window_grab_modifiers (display); + + /* Grab all */ + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + if (w->type != META_WINDOW_DOCK) + { + meta_display_grab_focus_window_button (display, w); + meta_display_grab_window_buttons (display, w->xwindow); + } + tmp = tmp->next; + } + + g_slist_free (windows); + } + else if (pref == META_PREF_AUDIBLE_BELL) + { + meta_bell_set_audible (display, meta_prefs_bell_is_audible ()); + } + else if (pref == META_PREF_COMPOSITING_MANAGER) + { + gboolean cm = meta_prefs_get_compositing_manager (); + + if (cm) + enable_compositor (display, TRUE); + else + disable_compositor (display); + } +} + +void +meta_display_increment_focus_sentinel (MetaDisplay *display) +{ + unsigned long data[1]; + + data[0] = meta_display_get_current_time (display); + + XChangeProperty (display->xdisplay, + ((MetaScreen*) display->screens->data)->xroot, + display->atom__MARCO_SENTINEL, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 1); + + display->sentinel_counter += 1; +} + +void +meta_display_decrement_focus_sentinel (MetaDisplay *display) +{ + display->sentinel_counter -= 1; + + if (display->sentinel_counter < 0) + display->sentinel_counter = 0; +} + +gboolean +meta_display_focus_sentinel_clear (MetaDisplay *display) +{ + return (display->sentinel_counter == 0); +} + +static void +sanity_check_timestamps (MetaDisplay *display, + guint32 timestamp) +{ + if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_focus_time)) + { + meta_warning ("last_focus_time (%u) is greater than comparison " + "timestamp (%u). This most likely represents a buggy " + "client sending inaccurate timestamps in messages such as " + "_NET_ACTIVE_WINDOW. Trying to work around...\n", + display->last_focus_time, timestamp); + display->last_focus_time = timestamp; + } + if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_user_time)) + { + GSList *windows; + GSList *tmp; + + meta_warning ("last_user_time (%u) is greater than comparison " + "timestamp (%u). This most likely represents a buggy " + "client sending inaccurate timestamps in messages such as " + "_NET_ACTIVE_WINDOW. Trying to work around...\n", + display->last_user_time, timestamp); + display->last_user_time = timestamp; + + windows = meta_display_list_windows (display); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time)) + { + meta_warning ("%s appears to be one of the offending windows " + "with a timestamp of %u. Working around...\n", + window->desc, window->net_wm_user_time); + window->net_wm_user_time = timestamp; + } + + tmp = tmp->next; + } + + g_slist_free (windows); + } +} + +static gboolean +timestamp_too_old (MetaDisplay *display, + MetaWindow *window, + guint32 *timestamp) +{ + /* FIXME: If Soeren's suggestion in bug 151984 is implemented, it will allow + * us to sanity check the timestamp here and ensure it doesn't correspond to + * a future time (though we would want to rename to + * timestamp_too_old_or_in_future). + */ + + if (*timestamp == CurrentTime) + { + meta_warning ("Got a request to focus %s with a timestamp of 0. This " + "shouldn't happen!\n", + window ? window->desc : "the no_focus_window"); + meta_print_backtrace (); + *timestamp = meta_display_get_current_time_roundtrip (display); + return FALSE; + } + else if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_focus_time)) + { + if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_user_time)) + { + meta_topic (META_DEBUG_FOCUS, + "Ignoring focus request for %s since %u " + "is less than %u and %u.\n", + window ? window->desc : "the no_focus_window", + *timestamp, + display->last_user_time, + display->last_focus_time); + return TRUE; + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Received focus request for %s which is newer than most " + "recent user_time, but less recent than " + "last_focus_time (%u < %u < %u); adjusting " + "accordingly. (See bug 167358)\n", + window ? window->desc : "the no_focus_window", + display->last_user_time, + *timestamp, + display->last_focus_time); + *timestamp = display->last_focus_time; + return FALSE; + } + } + + return FALSE; +} + +void +meta_display_set_input_focus_window (MetaDisplay *display, + MetaWindow *window, + gboolean focus_frame, + guint32 timestamp) +{ + if (timestamp_too_old (display, window, ×tamp)) + return; + + meta_error_trap_push (display); + XSetInputFocus (display->xdisplay, + focus_frame ? window->frame->xwindow : window->xwindow, + RevertToPointerRoot, + timestamp); + meta_error_trap_pop (display, FALSE); + + display->expected_focus_window = window; + display->last_focus_time = timestamp; + display->active_screen = window->screen; + + if (window != display->autoraise_window) + meta_display_remove_autoraise_callback (window->display); +} + +void +meta_display_focus_the_no_focus_window (MetaDisplay *display, + MetaScreen *screen, + guint32 timestamp) +{ + if (timestamp_too_old (display, NULL, ×tamp)) + return; + + XSetInputFocus (display->xdisplay, + screen->no_focus_window, + RevertToPointerRoot, + timestamp); + display->expected_focus_window = NULL; + display->last_focus_time = timestamp; + display->active_screen = screen; + + meta_display_remove_autoraise_callback (display); +} + +void +meta_display_remove_autoraise_callback (MetaDisplay *display) +{ + if (display->autoraise_timeout_id != 0) + { + g_source_remove (display->autoraise_timeout_id); + display->autoraise_timeout_id = 0; + display->autoraise_window = NULL; + } +} + +#ifdef HAVE_COMPOSITE_EXTENSIONS +void +meta_display_get_compositor_version (MetaDisplay *display, + int *major, + int *minor) +{ + *major = display->composite_major_version; + *minor = display->composite_minor_version; +} +#endif + +Display * +meta_display_get_xdisplay (MetaDisplay *display) +{ + return display->xdisplay; +} + +MetaCompositor * +meta_display_get_compositor (MetaDisplay *display) +{ + return display->compositor; +} + +GSList * +meta_display_get_screens (MetaDisplay *display) +{ + return display->screens; +} + +gboolean +meta_display_has_shape (MetaDisplay *display) +{ + return META_DISPLAY_HAS_SHAPE (display); +} + +MetaWindow * +meta_display_get_focus_window (MetaDisplay *display) +{ + return display->focus_window; +} + +#ifdef HAVE_COMPOSITE_EXTENSIONS +int +meta_display_get_damage_event_base (MetaDisplay *display) +{ + return display->damage_event_base; +} +#endif + +#ifdef HAVE_COMPOSITE_EXTENSIONS +#ifdef HAVE_SHAPE +int +meta_display_get_shape_event_base (MetaDisplay *display) +{ + return display->shape_event_base; +} +#endif +#endif diff --git a/src/core/edge-resistance.c b/src/core/edge-resistance.c new file mode 100644 index 00000000..fd3f2d44 --- /dev/null +++ b/src/core/edge-resistance.c @@ -0,0 +1,1277 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Edge resistance for move/resize operations */ + +/* + * Copyright (C) 2005, 2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "edge-resistance.h" +#include "boxes.h" +#include "display-private.h" +#include "workspace.h" + +/* A simple macro for whether a given window's edges are potentially + * relevant for resistance/snapping during a move/resize operation + */ +#define WINDOW_EDGES_RELEVANT(window, display) \ + meta_window_should_be_showing (window) && \ + window->screen == display->grab_screen && \ + window != display->grab_window && \ + window->type != META_WINDOW_DESKTOP && \ + window->type != META_WINDOW_MENU && \ + window->type != META_WINDOW_SPLASHSCREEN + +struct ResistanceDataForAnEdge +{ + gboolean timeout_setup; + guint timeout_id; + int timeout_edge_pos; + gboolean timeout_over; + GSourceFunc timeout_func; + MetaWindow *window; + int keyboard_buildup; +}; +typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge; + +struct MetaEdgeResistanceData +{ + GArray *left_edges; + GArray *right_edges; + GArray *top_edges; + GArray *bottom_edges; + + ResistanceDataForAnEdge left_data; + ResistanceDataForAnEdge right_data; + ResistanceDataForAnEdge top_data; + ResistanceDataForAnEdge bottom_data; +}; + +/* !WARNING!: this function can return invalid indices (namely, either -1 or + * edges->len); this is by design, but you need to remember this. + */ +static int +find_index_of_edge_near_position (const GArray *edges, + int position, + gboolean want_interval_min, + gboolean horizontal) +{ + /* This is basically like a binary search, except that we're trying to + * find a range instead of an exact value. So, if we have in our array + * Value: 3 27 316 316 316 505 522 800 1213 + * Index: 0 1 2 3 4 5 6 7 8 + * and we call this function with position=500 & want_interval_min=TRUE + * then we should get 5 (because 505 is the first value bigger than 500). + * If we call this function with position=805 and want_interval_min=FALSE + * then we should get 7 (because 800 is the last value smaller than 800). + * A couple more, to make things clear: + * position want_interval_min correct_answer + * 316 TRUE 2 + * 316 FALSE 4 + * 2 FALSE -1 + * 2000 TRUE 9 + */ + int low, high, mid; + int compare; + MetaEdge *edge; + + /* Initialize mid, edge, & compare in the off change that the array only + * has one element. + */ + mid = 0; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + /* Begin the search... */ + low = 0; + high = edges->len - 1; + while (low < high) + { + mid = low + (high - low)/2; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + if (compare == position) + break; + + if (compare > position) + high = mid - 1; + else + low = mid + 1; + } + + /* mid should now be _really_ close to the index we want, so we start + * linearly searching. However, note that we don't know if mid is less + * than or greater than what we need and it's possible that there are + * several equal values equal to what we were searching for and we ended + * up in the middle of them instead of at the end. So we may need to + * move mid multiple locations over. + */ + if (want_interval_min) + { + while (compare >= position && mid > 0) + { + mid--; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + while (compare < position && mid < (int)edges->len - 1) + { + mid++; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + + /* Special case for no values in array big enough */ + if (compare < position) + return edges->len; + + /* Return the found value */ + return mid; + } + else + { + while (compare <= position && mid < (int)edges->len - 1) + { + mid++; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + while (compare > position && mid > 0) + { + mid--; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + } + + /* Special case for no values in array small enough */ + if (compare > position) + return -1; + + /* Return the found value */ + return mid; + } +} + +static gboolean +points_on_same_side (int ref, int pt1, int pt2) +{ + return (pt1 - ref) * (pt2 - ref) > 0; +} + +static int +find_nearest_position (const GArray *edges, + int position, + int old_position, + const MetaRectangle *new_rect, + gboolean horizontal, + gboolean only_forward) +{ + /* This is basically just a binary search except that we're looking + * for the value closest to position, rather than finding that + * actual value. Also, we ignore any edges that aren't relevant + * given the horizontal/vertical position of new_rect. + */ + int low, high, mid; + int compare; + MetaEdge *edge; + int best, best_dist, i; + gboolean edges_align; + + /* Initialize mid, edge, & compare in the off change that the array only + * has one element. + */ + mid = 0; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + /* Begin the search... */ + low = 0; + high = edges->len - 1; + while (low < high) + { + mid = low + (high - low)/2; + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + + if (compare == position) + break; + + if (compare > position) + high = mid - 1; + else + low = mid + 1; + } + + /* mid should now be _really_ close to the index we want, so we + * start searching nearby for something that overlaps and is closer + * than the original position. + */ + best = old_position; + best_dist = INT_MAX; + + /* Start the search at mid */ + edge = g_array_index (edges, MetaEdge*, mid); + compare = horizontal ? edge->rect.x : edge->rect.y; + edges_align = meta_rectangle_edge_aligns (new_rect, edge); + if (edges_align && + (!only_forward || !points_on_same_side (position, compare, old_position))) + { + int dist = ABS (compare - position); + if (dist < best_dist) + { + best = compare; + best_dist = dist; + } + } + + /* Now start searching higher than mid */ + for (i = mid + 1; i < (int)edges->len; i++) + { + edge = g_array_index (edges, MetaEdge*, i); + compare = horizontal ? edge->rect.x : edge->rect.y; + + edges_align = horizontal ? + meta_rectangle_vert_overlap (&edge->rect, new_rect) : + meta_rectangle_horiz_overlap (&edge->rect, new_rect); + + if (edges_align && + (!only_forward || + !points_on_same_side (position, compare, old_position))) + { + int dist = ABS (compare - position); + if (dist < best_dist) + { + best = compare; + best_dist = dist; + } + break; + } + } + + /* Now start searching lower than mid */ + for (i = mid-1; i >= 0; i--) + { + edge = g_array_index (edges, MetaEdge*, i); + compare = horizontal ? edge->rect.x : edge->rect.y; + + edges_align = horizontal ? + meta_rectangle_vert_overlap (&edge->rect, new_rect) : + meta_rectangle_horiz_overlap (&edge->rect, new_rect); + + if (edges_align && + (!only_forward || + !points_on_same_side (position, compare, old_position))) + { + int dist = ABS (compare - position); + if (dist < best_dist) + { + best = compare; + best_dist = dist; + } + break; + } + } + + /* Return the best one found */ + return best; +} + +static gboolean +movement_towards_edge (MetaSide side, int increment) +{ + switch (side) + { + case META_SIDE_LEFT: + case META_SIDE_TOP: + return increment < 0; + case META_SIDE_RIGHT: + case META_SIDE_BOTTOM: + return increment > 0; + default: + g_assert_not_reached (); + } +} + +static gboolean +edge_resistance_timeout (gpointer data) +{ + ResistanceDataForAnEdge *resistance_data = data; + + resistance_data->timeout_over = TRUE; + resistance_data->timeout_id = 0; + (*resistance_data->timeout_func)(resistance_data->window); + + return FALSE; +} + +static int +apply_edge_resistance (MetaWindow *window, + int old_pos, + int new_pos, + const MetaRectangle *old_rect, + const MetaRectangle *new_rect, + GArray *edges, + ResistanceDataForAnEdge *resistance_data, + GSourceFunc timeout_func, + gboolean xdir, + gboolean keyboard_op) +{ + int i, begin, end; + int last_edge; + gboolean increasing = new_pos > old_pos; + int increment = increasing ? 1 : -1; + + const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW = 16; + const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW = 0; + const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA = 32; + const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA = 0; + const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN = 32; + const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN = 0; + const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW = 0; + const int TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA = 0; + const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN = 0; + + /* Quit if no movement was specified */ + if (old_pos == new_pos) + return new_pos; + + /* Remove the old timeout if it's no longer relevant */ + if (resistance_data->timeout_setup && + ((resistance_data->timeout_edge_pos > old_pos && + resistance_data->timeout_edge_pos > new_pos) || + (resistance_data->timeout_edge_pos < old_pos && + resistance_data->timeout_edge_pos < new_pos))) + { + resistance_data->timeout_setup = FALSE; + if (resistance_data->timeout_id != 0) + { + g_source_remove (resistance_data->timeout_id); + resistance_data->timeout_id = 0; + } + } + + /* Get the range of indices in the edge array that we move past/to. */ + begin = find_index_of_edge_near_position (edges, old_pos, increasing, xdir); + end = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir); + + /* begin and end can be outside the array index, if the window is partially + * off the screen + */ + last_edge = edges->len - 1; + begin = CLAMP (begin, 0, last_edge); + end = CLAMP (end, 0, last_edge); + + /* Loop over all these edges we're moving past/to. */ + i = begin; + while ((increasing && i <= end) || + (!increasing && i >= end)) + { + gboolean edges_align; + MetaEdge *edge = g_array_index (edges, MetaEdge*, i); + int compare = xdir ? edge->rect.x : edge->rect.y; + + /* Find out if this edge is relevant */ + edges_align = meta_rectangle_edge_aligns (new_rect, edge) || + meta_rectangle_edge_aligns (old_rect, edge); + + /* Nothing to do unless the edges align */ + if (!edges_align) + { + /* Go to the next edge in the range */ + i += increment; + continue; + } + + /* Rest is easier to read if we split on keyboard vs. mouse op */ + if (keyboard_op) + { + if ((old_pos < compare && compare < new_pos) || + (old_pos > compare && compare > new_pos)) + return compare; + } + else /* mouse op */ + { + int threshold; + + /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving + * towards it, then we may want to have some kind of time delay + * before the user can move past this edge. + */ + if (movement_towards_edge (edge->side_type, increment)) + { + /* First, determine the length of time for the resistance */ + int timeout_length_ms = 0; + switch (edge->edge_type) + { + case META_EDGE_WINDOW: + timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW; + break; + case META_EDGE_XINERAMA: + timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA; + break; + case META_EDGE_SCREEN: + timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN; + break; + } + + if (!resistance_data->timeout_setup && + timeout_length_ms != 0) + { + resistance_data->timeout_id = + g_timeout_add (timeout_length_ms, + edge_resistance_timeout, + resistance_data); + resistance_data->timeout_setup = TRUE; + resistance_data->timeout_edge_pos = compare; + resistance_data->timeout_over = FALSE; + resistance_data->timeout_func = timeout_func; + resistance_data->window = window; + } + if (!resistance_data->timeout_over && + timeout_length_ms != 0) + return compare; + } + + /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the + * user hasn't moved at least threshold pixels past this edge, + * stop movement at this edge. (Note that this is different from + * keyboard resistance precisely because keyboard move ops are + * relative to previous positions, whereas mouse move ops are + * relative to differences in mouse position and mouse position + * is an absolute quantity rather than a relative quantity) + */ + + /* First, determine the threshold */ + threshold = 0; + switch (edge->edge_type) + { + case META_EDGE_WINDOW: + if (movement_towards_edge (edge->side_type, increment)) + threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW; + else + threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW; + break; + case META_EDGE_XINERAMA: + if (movement_towards_edge (edge->side_type, increment)) + threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA; + else + threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA; + break; + case META_EDGE_SCREEN: + if (movement_towards_edge (edge->side_type, increment)) + threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN; + else + threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN; + break; + } + + if (ABS (compare - new_pos) < threshold) + return compare; + } + + /* Go to the next edge in the range */ + i += increment; + } + + return new_pos; +} + +static int +apply_edge_snapping (int old_pos, + int new_pos, + const MetaRectangle *new_rect, + GArray *edges, + gboolean xdir, + gboolean keyboard_op) +{ + int snap_to; + + if (old_pos == new_pos) + return new_pos; + + snap_to = find_nearest_position (edges, + new_pos, + old_pos, + new_rect, + xdir, + keyboard_op); + + /* If mouse snap-moving, the user could easily accidentally move just a + * couple pixels in a direction they didn't mean to move; so ignore snap + * movement in those cases unless it's only a small number of pixels + * anyway. + */ + if (!keyboard_op && + ABS (snap_to - old_pos) >= 8 && + ABS (new_pos - old_pos) < 8) + return old_pos; + else + /* Otherwise, return the snapping position found */ + return snap_to; +} + +/* This function takes the position (including any frame) of the window and + * a proposed new position (ignoring edge resistance/snapping), and then + * applies edge resistance to EACH edge (separately) updating new_outer. + * It returns true if new_outer is modified, false otherwise. + * + * display->grab_edge_resistance_data MUST already be setup or calling this + * function will cause a crash. + */ +static gboolean +apply_edge_resistance_to_each_side (MetaDisplay *display, + MetaWindow *window, + const MetaRectangle *old_outer, + MetaRectangle *new_outer, + GSourceFunc timeout_func, + gboolean auto_snap, + gboolean keyboard_op, + gboolean is_resize) +{ + MetaEdgeResistanceData *edge_data; + MetaRectangle modified_rect; + gboolean modified; + int new_left, new_right, new_top, new_bottom; + + g_assert (display->grab_edge_resistance_data != NULL); + edge_data = display->grab_edge_resistance_data; + + if (auto_snap) + { + /* Do the auto snapping instead of normal edge resistance; in all + * cases, we allow snapping to opposite kinds of edges (e.g. left + * sides of windows to both left and right edges. + */ + + new_left = apply_edge_snapping (BOX_LEFT (*old_outer), + BOX_LEFT (*new_outer), + new_outer, + edge_data->left_edges, + TRUE, + keyboard_op); + + new_right = apply_edge_snapping (BOX_RIGHT (*old_outer), + BOX_RIGHT (*new_outer), + new_outer, + edge_data->right_edges, + TRUE, + keyboard_op); + + new_top = apply_edge_snapping (BOX_TOP (*old_outer), + BOX_TOP (*new_outer), + new_outer, + edge_data->top_edges, + FALSE, + keyboard_op); + + new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer), + BOX_BOTTOM (*new_outer), + new_outer, + edge_data->bottom_edges, + FALSE, + keyboard_op); + } + else + { + /* Disable edge resistance for resizes when windows have size + * increment hints; see #346782. For all other cases, apply + * them. + */ + if (!is_resize || window->size_hints.width_inc == 1) + { + /* Now, apply the normal horizontal edge resistance */ + new_left = apply_edge_resistance (window, + BOX_LEFT (*old_outer), + BOX_LEFT (*new_outer), + old_outer, + new_outer, + edge_data->left_edges, + &edge_data->left_data, + timeout_func, + TRUE, + keyboard_op); + new_right = apply_edge_resistance (window, + BOX_RIGHT (*old_outer), + BOX_RIGHT (*new_outer), + old_outer, + new_outer, + edge_data->right_edges, + &edge_data->right_data, + timeout_func, + TRUE, + keyboard_op); + } + else + { + new_left = new_outer->x; + new_right = new_outer->x + new_outer->width; + } + /* Same for vertical resizes... */ + if (!is_resize || window->size_hints.height_inc == 1) + { + new_top = apply_edge_resistance (window, + BOX_TOP (*old_outer), + BOX_TOP (*new_outer), + old_outer, + new_outer, + edge_data->top_edges, + &edge_data->top_data, + timeout_func, + FALSE, + keyboard_op); + new_bottom = apply_edge_resistance (window, + BOX_BOTTOM (*old_outer), + BOX_BOTTOM (*new_outer), + old_outer, + new_outer, + edge_data->bottom_edges, + &edge_data->bottom_data, + timeout_func, + FALSE, + keyboard_op); + } + else + { + new_top = new_outer->y; + new_bottom = new_outer->y + new_outer->height; + } + } + + /* Determine whether anything changed, and save the changes */ + modified_rect = meta_rect (new_left, + new_top, + new_right - new_left, + new_bottom - new_top); + modified = !meta_rectangle_equal (new_outer, &modified_rect); + *new_outer = modified_rect; + return modified; +} + +void +meta_display_cleanup_edges (MetaDisplay *display) +{ + guint i,j; + MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data; + GHashTable *edges_to_be_freed; + + g_assert (edge_data != NULL); + + /* We first need to clean out any window edges */ + edges_to_be_freed = g_hash_table_new_full (g_direct_hash, g_direct_equal, + g_free, NULL); + for (i = 0; i < 4; i++) + { + GArray *tmp = NULL; + MetaSide side; + switch (i) + { + case 0: + tmp = edge_data->left_edges; + side = META_SIDE_LEFT; + break; + case 1: + tmp = edge_data->right_edges; + side = META_SIDE_RIGHT; + break; + case 2: + tmp = edge_data->top_edges; + side = META_SIDE_TOP; + break; + case 3: + tmp = edge_data->bottom_edges; + side = META_SIDE_BOTTOM; + break; + default: + g_assert_not_reached (); + } + + for (j = 0; j < tmp->len; j++) + { + MetaEdge *edge = g_array_index (tmp, MetaEdge*, j); + if (edge->edge_type == META_EDGE_WINDOW && + edge->side_type == side) + { + /* The same edge will appear in two arrays, and we can't free + * it yet we still need to compare edge->side_type for the other + * array that it is in. So store it in a hash table for later + * freeing. Could also do this in a simple linked list. + */ + g_hash_table_insert (edges_to_be_freed, edge, edge); + } + } + } + + /* Now free all the window edges (the key destroy function is g_free) */ + g_hash_table_destroy (edges_to_be_freed); + + /* Now free the arrays and data */ + g_array_free (edge_data->left_edges, TRUE); + g_array_free (edge_data->right_edges, TRUE); + g_array_free (edge_data->top_edges, TRUE); + g_array_free (edge_data->bottom_edges, TRUE); + edge_data->left_edges = NULL; + edge_data->right_edges = NULL; + edge_data->top_edges = NULL; + edge_data->bottom_edges = NULL; + + /* Cleanup the timeouts */ + if (edge_data->left_data.timeout_setup && + edge_data->left_data.timeout_id != 0) + g_source_remove (edge_data->left_data.timeout_id); + if (edge_data->right_data.timeout_setup && + edge_data->right_data.timeout_id != 0) + g_source_remove (edge_data->right_data.timeout_id); + if (edge_data->top_data.timeout_setup && + edge_data->top_data.timeout_id != 0) + g_source_remove (edge_data->top_data.timeout_id); + if (edge_data->bottom_data.timeout_setup && + edge_data->bottom_data.timeout_id != 0) + g_source_remove (edge_data->bottom_data.timeout_id); + + g_free (display->grab_edge_resistance_data); + display->grab_edge_resistance_data = NULL; +} + +static int +stupid_sort_requiring_extra_pointer_dereference (gconstpointer a, + gconstpointer b) +{ + const MetaEdge * const *a_edge = a; + const MetaEdge * const *b_edge = b; + return meta_rectangle_edge_cmp_ignore_type (*a_edge, *b_edge); +} + +static void +cache_edges (MetaDisplay *display, + GList *window_edges, + GList *xinerama_edges, + GList *screen_edges) +{ + MetaEdgeResistanceData *edge_data; + GList *tmp; + int num_left, num_right, num_top, num_bottom; + int i; + + /* + * 0th: Print debugging information to the log about the edges + */ +#ifdef WITH_VERBOSE_MODE + if (meta_is_verbose()) + { + int max_edges = MAX (MAX( g_list_length (window_edges), + g_list_length (xinerama_edges)), + g_list_length (screen_edges)); + char big_buffer[(EDGE_LENGTH+2)*max_edges]; + + meta_rectangle_edge_list_to_string (window_edges, ", ", big_buffer); + meta_topic (META_DEBUG_EDGE_RESISTANCE, + "Window edges for resistance : %s\n", big_buffer); + + meta_rectangle_edge_list_to_string (xinerama_edges, ", ", big_buffer); + meta_topic (META_DEBUG_EDGE_RESISTANCE, + "Xinerama edges for resistance: %s\n", big_buffer); + + meta_rectangle_edge_list_to_string (screen_edges, ", ", big_buffer); + meta_topic (META_DEBUG_EDGE_RESISTANCE, + "Screen edges for resistance : %s\n", big_buffer); + } +#endif + + /* + * 1st: Get the total number of each kind of edge + */ + num_left = num_right = num_top = num_bottom = 0; + for (i = 0; i < 3; i++) + { + tmp = NULL; + switch (i) + { + case 0: + tmp = window_edges; + break; + case 1: + tmp = xinerama_edges; + break; + case 2: + tmp = screen_edges; + break; + default: + g_assert_not_reached (); + } + + while (tmp) + { + MetaEdge *edge = tmp->data; + switch (edge->side_type) + { + case META_SIDE_LEFT: + num_left++; + break; + case META_SIDE_RIGHT: + num_right++; + break; + case META_SIDE_TOP: + num_top++; + break; + case META_SIDE_BOTTOM: + num_bottom++; + break; + default: + g_assert_not_reached (); + } + tmp = tmp->next; + } + } + + /* + * 2nd: Allocate the edges + */ + g_assert (display->grab_edge_resistance_data == NULL); + display->grab_edge_resistance_data = g_new (MetaEdgeResistanceData, 1); + edge_data = display->grab_edge_resistance_data; + edge_data->left_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_left + num_right); + edge_data->right_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_left + num_right); + edge_data->top_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_top + num_bottom); + edge_data->bottom_edges = g_array_sized_new (FALSE, + FALSE, + sizeof(MetaEdge*), + num_top + num_bottom); + + /* + * 3rd: Add the edges to the arrays + */ + for (i = 0; i < 3; i++) + { + tmp = NULL; + switch (i) + { + case 0: + tmp = window_edges; + break; + case 1: + tmp = xinerama_edges; + break; + case 2: + tmp = screen_edges; + break; + default: + g_assert_not_reached (); + } + + while (tmp) + { + MetaEdge *edge = tmp->data; + switch (edge->side_type) + { + case META_SIDE_LEFT: + case META_SIDE_RIGHT: + g_array_append_val (edge_data->left_edges, edge); + g_array_append_val (edge_data->right_edges, edge); + break; + case META_SIDE_TOP: + case META_SIDE_BOTTOM: + g_array_append_val (edge_data->top_edges, edge); + g_array_append_val (edge_data->bottom_edges, edge); + break; + default: + g_assert_not_reached (); + } + tmp = tmp->next; + } + } + + /* + * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were + * individually sorted earlier and we could have done this faster and + * avoided this sort by sticking them into the array with some simple + * merging of the lists). + */ + g_array_sort (display->grab_edge_resistance_data->left_edges, + stupid_sort_requiring_extra_pointer_dereference); + g_array_sort (display->grab_edge_resistance_data->right_edges, + stupid_sort_requiring_extra_pointer_dereference); + g_array_sort (display->grab_edge_resistance_data->top_edges, + stupid_sort_requiring_extra_pointer_dereference); + g_array_sort (display->grab_edge_resistance_data->bottom_edges, + stupid_sort_requiring_extra_pointer_dereference); +} + +static void +initialize_grab_edge_resistance_data (MetaDisplay *display) +{ + MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data; + + edge_data->left_data.timeout_setup = FALSE; + edge_data->right_data.timeout_setup = FALSE; + edge_data->top_data.timeout_setup = FALSE; + edge_data->bottom_data.timeout_setup = FALSE; + + edge_data->left_data.keyboard_buildup = 0; + edge_data->right_data.keyboard_buildup = 0; + edge_data->top_data.keyboard_buildup = 0; + edge_data->bottom_data.keyboard_buildup = 0; +} + +void +meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display) +{ + GList *stacked_windows; + GList *cur_window_iter; + GList *edges; + /* Lists of window positions (rects) and their relative stacking positions */ + int stack_position; + GSList *obscuring_windows, *window_stacking; + /* The portions of the above lists that still remain at the stacking position + * in the layer that we are working on + */ + GSList *rem_windows, *rem_win_stacking; + + /* + * 1st: Get the list of relevant windows, from bottom to top + */ + stacked_windows = + meta_stack_list_windows (display->grab_screen->stack, + display->grab_screen->active_workspace); + + /* + * 2nd: we need to separate that stacked list into a list of windows that + * can obscure other edges. To make sure we only have windows obscuring + * those below it instead of going both ways, we also need to keep a + * counter list. Messy, I know. + */ + obscuring_windows = window_stacking = NULL; + cur_window_iter = stacked_windows; + stack_position = 0; + while (cur_window_iter != NULL) + { + MetaWindow *cur_window = cur_window_iter->data; + if (WINDOW_EDGES_RELEVANT (cur_window, display)) + { + MetaRectangle *new_rect; + new_rect = g_new (MetaRectangle, 1); + meta_window_get_outer_rect (cur_window, new_rect); + obscuring_windows = g_slist_prepend (obscuring_windows, new_rect); + window_stacking = + g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position)); + } + + stack_position++; + cur_window_iter = cur_window_iter->next; + } + /* Put 'em in bottom to top order */ + rem_windows = obscuring_windows = g_slist_reverse (obscuring_windows); + rem_win_stacking = window_stacking = g_slist_reverse (window_stacking); + + /* + * 3rd: loop over the windows again, this time getting the edges from + * them and removing intersections with the relevant obscuring_windows & + * obscuring_docks. + */ + edges = NULL; + stack_position = 0; + cur_window_iter = stacked_windows; + while (cur_window_iter != NULL) + { + MetaRectangle cur_rect; + MetaWindow *cur_window = cur_window_iter->data; + meta_window_get_outer_rect (cur_window, &cur_rect); + + /* Check if we want to use this window's edges for edge + * resistance (note that dock edges are considered screen edges + * which are handled separately + */ + if (WINDOW_EDGES_RELEVANT (cur_window, display) && + cur_window->type != META_WINDOW_DOCK) + { + GList *new_edges; + MetaEdge *new_edge; + MetaRectangle reduced; + + /* We don't care about snapping to any portion of the window that + * is offscreen (we also don't care about parts of edges covered + * by other windows or DOCKS, but that's handled below). + */ + meta_rectangle_intersect (&cur_rect, + &display->grab_screen->rect, + &reduced); + + new_edges = NULL; + + /* Left side of this window is resistance for the right edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.width = 0; + new_edge->side_type = META_SIDE_RIGHT; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Right side of this window is resistance for the left edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.x += new_edge->rect.width; + new_edge->rect.width = 0; + new_edge->side_type = META_SIDE_LEFT; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Top side of this window is resistance for the bottom edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.height = 0; + new_edge->side_type = META_SIDE_BOTTOM; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Top side of this window is resistance for the bottom edge of + * the window being moved. + */ + new_edge = g_new (MetaEdge, 1); + new_edge->rect = reduced; + new_edge->rect.y += new_edge->rect.height; + new_edge->rect.height = 0; + new_edge->side_type = META_SIDE_TOP; + new_edge->edge_type = META_EDGE_WINDOW; + new_edges = g_list_prepend (new_edges, new_edge); + + /* Update the remaining windows to only those at a higher + * stacking position than this one. + */ + while (rem_win_stacking && + stack_position >= GPOINTER_TO_INT (rem_win_stacking->data)) + { + rem_windows = rem_windows->next; + rem_win_stacking = rem_win_stacking->next; + } + + /* Remove edge portions overlapped by rem_windows and rem_docks */ + new_edges = + meta_rectangle_remove_intersections_with_boxes_from_edges ( + new_edges, + rem_windows); + + /* Save the new edges */ + edges = g_list_concat (new_edges, edges); + } + + stack_position++; + cur_window_iter = cur_window_iter->next; + } + + /* + * 4th: Free the extra memory not needed and sort the list + */ + g_list_free (stacked_windows); + /* Free the memory used by the obscuring windows/docks lists */ + g_slist_free (window_stacking); + /* FIXME: Shouldn't there be a helper function to make this one line of code + * to free a list instead of four ugly ones? + */ + g_slist_foreach (obscuring_windows, + (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */ + NULL); + g_slist_free (obscuring_windows); + + /* Sort the list. FIXME: Should I bother with this sorting? I just + * sort again later in cache_edges() anyway... + */ + edges = g_list_sort (edges, meta_rectangle_edge_cmp); + + /* + * 5th: Cache the combination of these edges with the onscreen and + * xinerama edges in an array for quick access. Free the edges since + * they've been cached elsewhere. + */ + cache_edges (display, + edges, + display->grab_screen->active_workspace->xinerama_edges, + display->grab_screen->active_workspace->screen_edges); + g_list_free (edges); + + /* + * 6th: Initialize the resistance timeouts and buildups + */ + initialize_grab_edge_resistance_data (display); +} + +/* Note that old_[xy] and new_[xy] are with respect to inner positions of + * the window. + */ +void +meta_window_edge_resistance_for_move (MetaWindow *window, + int old_x, + int old_y, + int *new_x, + int *new_y, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op) +{ + MetaRectangle old_outer, proposed_outer, new_outer; + gboolean is_resize; + + if (window == window->display->grab_window && + window->display->grab_wireframe_active) + { + meta_window_get_xor_rect (window, + &window->display->grab_wireframe_rect, + &old_outer); + } + else + { + meta_window_get_outer_rect (window, &old_outer); + } + proposed_outer = old_outer; + proposed_outer.x += (*new_x - old_x); + proposed_outer.y += (*new_y - old_y); + new_outer = proposed_outer; + + window->display->grab_last_user_action_was_snap = snap; + is_resize = FALSE; + if (apply_edge_resistance_to_each_side (window->display, + window, + &old_outer, + &new_outer, + timeout_func, + snap, + is_keyboard_op, + is_resize)) + { + /* apply_edge_resistance_to_each_side independently applies + * resistance to both the right and left edges of new_outer as both + * could meet areas of resistance. But we don't want a resize, so we + * just have both edges move according to the stricter of the + * resistances. Same thing goes for top & bottom edges. + */ + MetaRectangle *reference; + int left_change, right_change, smaller_x_change; + int top_change, bottom_change, smaller_y_change; + + if (snap && !is_keyboard_op) + reference = &proposed_outer; + else + reference = &old_outer; + + left_change = BOX_LEFT (new_outer) - BOX_LEFT (*reference); + right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference); + if ( snap && is_keyboard_op && left_change == 0) + smaller_x_change = right_change; + else if (snap && is_keyboard_op && right_change == 0) + smaller_x_change = left_change; + else if (ABS (left_change) < ABS (right_change)) + smaller_x_change = left_change; + else + smaller_x_change = right_change; + + top_change = BOX_TOP (new_outer) - BOX_TOP (*reference); + bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference); + if ( snap && is_keyboard_op && top_change == 0) + smaller_y_change = bottom_change; + else if (snap && is_keyboard_op && bottom_change == 0) + smaller_y_change = top_change; + else if (ABS (top_change) < ABS (bottom_change)) + smaller_y_change = top_change; + else + smaller_y_change = bottom_change; + + *new_x = old_x + smaller_x_change + + (BOX_LEFT (*reference) - BOX_LEFT (old_outer)); + *new_y = old_y + smaller_y_change + + (BOX_TOP (*reference) - BOX_TOP (old_outer)); + + meta_topic (META_DEBUG_EDGE_RESISTANCE, + "outer x & y move-to coordinate changed from %d,%d to %d,%d\n", + proposed_outer.x, proposed_outer.y, + old_outer.x + (*new_x - old_x), + old_outer.y + (*new_y - old_y)); + } +} + +/* Note that old_(width|height) and new_(width|height) are with respect to + * sizes of the inner window. + */ +void +meta_window_edge_resistance_for_resize (MetaWindow *window, + int old_width, + int old_height, + int *new_width, + int *new_height, + int gravity, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op) +{ + MetaRectangle old_outer, new_outer; + int proposed_outer_width, proposed_outer_height; + gboolean is_resize; + + if (window == window->display->grab_window && + window->display->grab_wireframe_active) + { + meta_window_get_xor_rect (window, + &window->display->grab_wireframe_rect, + &old_outer); + } + else + { + meta_window_get_outer_rect (window, &old_outer); + } + proposed_outer_width = old_outer.width + (*new_width - old_width); + proposed_outer_height = old_outer.height + (*new_height - old_height); + meta_rectangle_resize_with_gravity (&old_outer, + &new_outer, + gravity, + proposed_outer_width, + proposed_outer_height); + + window->display->grab_last_user_action_was_snap = snap; + is_resize = TRUE; + if (apply_edge_resistance_to_each_side (window->display, + window, + &old_outer, + &new_outer, + timeout_func, + snap, + is_keyboard_op, + is_resize)) + { + *new_width = old_width + (new_outer.width - old_outer.width); + *new_height = old_height + (new_outer.height - old_outer.height); + + meta_topic (META_DEBUG_EDGE_RESISTANCE, + "outer width & height got changed from %d,%d to %d,%d\n", + proposed_outer_width, proposed_outer_height, + new_outer.width, new_outer.height); + } +} diff --git a/src/core/edge-resistance.h b/src/core/edge-resistance.h new file mode 100644 index 00000000..14ba17a0 --- /dev/null +++ b/src/core/edge-resistance.h @@ -0,0 +1,48 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Edge resistance for move/resize operations */ + +/* + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_EDGE_RESISTANCE_H +#define META_EDGE_RESISTANCE_H + +#include "window-private.h" + +void meta_window_edge_resistance_for_move (MetaWindow *window, + int old_x, + int old_y, + int *new_x, + int *new_y, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op); +void meta_window_edge_resistance_for_resize (MetaWindow *window, + int old_width, + int old_height, + int *new_width, + int *new_height, + int gravity, + GSourceFunc timeout_func, + gboolean snap, + gboolean is_keyboard_op); + +#endif /* META_EDGE_RESISTANCE_H */ + diff --git a/src/core/effects.c b/src/core/effects.c new file mode 100644 index 00000000..1392b7c2 --- /dev/null +++ b/src/core/effects.c @@ -0,0 +1,735 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file effects.c "Special effects" other than compositor effects. + * + * Before we had a serious compositor, we supported swooping + * rectangles for minimising and so on. These are still supported + * today, even when the compositor is enabled. The file contains two + * parts: + * + * 1) A set of functions, each of which implements a special effect. + * (Only the minimize function does anything interesting; we should + * probably get rid of the rest.) + * + * 2) A set of functions for moving a highlighted wireframe box around + * the screen, optionally with height and width shown in the middle. + * This is used for moving and resizing when reduced_resources is set. + * + * There was formerly a system which allowed callers to drop in their + * own handlers for various things; it was never used (people who want + * their own handlers can just modify this file, after all) and it added + * a good deal of extra complexity, so it has been removed. If you want it, + * it can be found in svn r3769. + * + * Once upon a time there were three different ways of drawing the box + * animation: window wireframe, window opaque, and root. People who had + * the shape extension theoretically had the choice of all three, and + * people who didn't weren't given the choice of the wireframe option. + * In practice, though, the opaque animation was never perfect, so it came + * down to the wireframe option for those who had the extension and + * the root option for those who didn't; there was actually no way of choosing + * any other option anyway. Work on the opaque animation stopped in 2002; + * anyone who wants something like that these days will be using the + * compositor anyway. + * + * In svn r3769 this was made explicit. + */ + +/* + * Copyright (C) 2001 Anders Carlsson, Havoc Pennington + * + * 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 "effects.h" +#include "display-private.h" +#include "ui.h" +#include "window-private.h" +#include "prefs.h" + +#ifdef HAVE_SHAPE +#include <X11/extensions/shape.h> +#endif + +#define META_MINIMIZE_ANIMATION_LENGTH 0.25 +#define META_SHADE_ANIMATION_LENGTH 0.2 + +#include <string.h> + +typedef struct MetaEffect MetaEffect; +typedef struct MetaEffectPriv MetaEffectPriv; + +typedef struct +{ + MetaScreen *screen; + + double millisecs_duration; + GTimeVal start_time; + +#ifdef HAVE_SHAPE + /** For wireframe window */ + Window wireframe_xwindow; +#else + /** Rectangle to erase */ + MetaRectangle last_rect; + + /** First time we've plotted anything in this animation? */ + gboolean first_time; + + /** For wireframe drawn on root window */ + GC gc; +#endif + + MetaRectangle start_rect; + MetaRectangle end_rect; + +} BoxAnimationContext; + +/** + * Information we need to know during a maximise or minimise effect. + */ +typedef struct +{ + /** This is the normal-size window. */ + MetaRectangle window_rect; + /** This is the size of the window when it's an icon. */ + MetaRectangle icon_rect; +} MetaMinimizeEffect, MetaUnminimizeEffect; + +struct MetaEffectPriv +{ + MetaEffectFinished finished; + gpointer finished_data; +}; + +struct MetaEffect +{ + /** The window the effect is applied to. */ + MetaWindow *window; + /** Which effect is happening here. */ + MetaEffectType type; + /** The effect handler can hang data here. */ + gpointer info; + + union + { + MetaMinimizeEffect minimize; + /* ... and theoretically anything else */ + } u; + + MetaEffectPriv *priv; +}; + +static void run_default_effect_handler (MetaEffect *effect); +static void run_handler (MetaEffect *effect); +static void effect_free (MetaEffect *effect); + +static MetaEffect * +create_effect (MetaEffectType type, + MetaWindow *window, + MetaEffectFinished finished, + gpointer finished_data); + +static void +draw_box_animation (MetaScreen *screen, + MetaRectangle *initial_rect, + MetaRectangle *destination_rect, + double seconds_duration); + +/** + * Creates an effect. + * + */ +static MetaEffect* +create_effect (MetaEffectType type, + MetaWindow *window, + MetaEffectFinished finished, + gpointer finished_data) +{ + MetaEffect *effect = g_new (MetaEffect, 1); + + effect->type = type; + effect->window = window; + effect->priv = g_new (MetaEffectPriv, 1); + effect->priv->finished = finished; + effect->priv->finished_data = finished_data; + + return effect; +} + +/** + * Destroys an effect. If the effect has a "finished" hook, it will be + * called before cleanup. + * + * \param effect The effect. + */ +static void +effect_free (MetaEffect *effect) +{ + if (effect->priv->finished) + effect->priv->finished (effect->priv->finished_data); + + g_free (effect->priv); + g_free (effect); +} + +void +meta_effect_run_focus (MetaWindow *window, + MetaEffectFinished finished, + gpointer data) +{ + MetaEffect *effect; + + g_return_if_fail (window != NULL); + + effect = create_effect (META_EFFECT_FOCUS, window, finished, data); + + run_handler (effect); +} + +void +meta_effect_run_minimize (MetaWindow *window, + MetaRectangle *window_rect, + MetaRectangle *icon_rect, + MetaEffectFinished finished, + gpointer data) +{ + MetaEffect *effect; + + g_return_if_fail (window != NULL); + g_return_if_fail (icon_rect != NULL); + + effect = create_effect (META_EFFECT_MINIMIZE, window, finished, data); + + effect->u.minimize.window_rect = *window_rect; + effect->u.minimize.icon_rect = *icon_rect; + + run_handler (effect); +} + +void +meta_effect_run_unminimize (MetaWindow *window, + MetaRectangle *window_rect, + MetaRectangle *icon_rect, + MetaEffectFinished finished, + gpointer data) +{ + MetaEffect *effect; + + g_return_if_fail (window != NULL); + g_return_if_fail (icon_rect != NULL); + + effect = create_effect (META_EFFECT_UNMINIMIZE, window, finished, data); + + effect->u.minimize.window_rect = *window_rect; + effect->u.minimize.icon_rect = *icon_rect; + + run_handler (effect); +} + +void +meta_effect_run_close (MetaWindow *window, + MetaEffectFinished finished, + gpointer data) +{ + MetaEffect *effect; + + g_return_if_fail (window != NULL); + + effect = create_effect (META_EFFECT_CLOSE, window, + finished, data); + + run_handler (effect); +} + + +/* old ugly minimization effect */ + +#ifdef HAVE_SHAPE +static void +update_wireframe_window (MetaDisplay *display, + Window xwindow, + const MetaRectangle *rect) +{ + XMoveResizeWindow (display->xdisplay, + xwindow, + rect->x, rect->y, + rect->width, rect->height); + +#define OUTLINE_WIDTH 3 + + if (rect->width > OUTLINE_WIDTH * 2 && + rect->height > OUTLINE_WIDTH * 2) + { + XRectangle xrect; + Region inner_xregion; + Region outer_xregion; + + inner_xregion = XCreateRegion (); + outer_xregion = XCreateRegion (); + + xrect.x = 0; + xrect.y = 0; + xrect.width = rect->width; + xrect.height = rect->height; + + XUnionRectWithRegion (&xrect, outer_xregion, outer_xregion); + + xrect.x += OUTLINE_WIDTH; + xrect.y += OUTLINE_WIDTH; + xrect.width -= OUTLINE_WIDTH * 2; + xrect.height -= OUTLINE_WIDTH * 2; + + XUnionRectWithRegion (&xrect, inner_xregion, inner_xregion); + + XSubtractRegion (outer_xregion, inner_xregion, outer_xregion); + + XShapeCombineRegion (display->xdisplay, xwindow, + ShapeBounding, 0, 0, outer_xregion, ShapeSet); + + XDestroyRegion (outer_xregion); + XDestroyRegion (inner_xregion); + } + else + { + /* Unset the shape */ + XShapeCombineMask (display->xdisplay, xwindow, + ShapeBounding, 0, 0, None, ShapeSet); + } +} +#endif + +/** + * A hack to force the X server to synchronize with the + * graphics hardware. + */ +static void +graphics_sync (BoxAnimationContext *context) +{ + XImage *image; + + image = XGetImage (context->screen->display->xdisplay, + context->screen->xroot, + 0, 0, 1, 1, + AllPlanes, ZPixmap); + + XDestroyImage (image); +} + +static gboolean +effects_draw_box_animation_timeout (BoxAnimationContext *context) +{ + double elapsed; + GTimeVal current_time; + MetaRectangle draw_rect; + double fraction; + +#ifndef HAVE_SHAPE + if (!context->first_time) + { + /* Restore the previously drawn background */ + XDrawRectangle (context->screen->display->xdisplay, + context->screen->xroot, + context->gc, + context->last_rect.x, context->last_rect.y, + context->last_rect.width, context->last_rect.height); + } + else + context->first_time = FALSE; + +#endif /* !HAVE_SHAPE */ + + g_get_current_time (¤t_time); + + /* We use milliseconds for all times */ + elapsed = + ((((double)current_time.tv_sec - context->start_time.tv_sec) * G_USEC_PER_SEC + + (current_time.tv_usec - context->start_time.tv_usec))) / 1000.0; + + if (elapsed < 0) + { + /* Probably the system clock was set backwards? */ + meta_warning ("System clock seemed to go backwards?\n"); + elapsed = G_MAXDOUBLE; /* definitely done. */ + } + + if (elapsed > context->millisecs_duration) + { + /* All done */ +#ifdef HAVE_SHAPE + XDestroyWindow (context->screen->display->xdisplay, + context->wireframe_xwindow); +#else + meta_display_ungrab (context->screen->display); + meta_ui_pop_delay_exposes (context->screen->ui); + XFreeGC (context->screen->display->xdisplay, + context->gc); +#endif /* !HAVE_SHAPE */ + + graphics_sync (context); + + g_free (context); + return FALSE; + } + + g_assert (context->millisecs_duration > 0.0); + fraction = elapsed / context->millisecs_duration; + + draw_rect = context->start_rect; + + /* Now add a delta proportional to elapsed time. */ + draw_rect.x += (context->end_rect.x - context->start_rect.x) * fraction; + draw_rect.y += (context->end_rect.y - context->start_rect.y) * fraction; + draw_rect.width += (context->end_rect.width - context->start_rect.width) * fraction; + draw_rect.height += (context->end_rect.height - context->start_rect.height) * fraction; + + /* don't confuse X or gdk-pixbuf with bogus rectangles */ + if (draw_rect.width < 1) + draw_rect.width = 1; + if (draw_rect.height < 1) + draw_rect.height = 1; + +#ifdef HAVE_SHAPE + update_wireframe_window (context->screen->display, + context->wireframe_xwindow, + &draw_rect); +#else + context->last_rect = draw_rect; + + /* Draw the rectangle */ + XDrawRectangle (context->screen->display->xdisplay, + context->screen->xroot, + context->gc, + draw_rect.x, draw_rect.y, + draw_rect.width, draw_rect.height); + +#endif /* !HAVE_SHAPE */ + + /* kick changes onto the server */ + graphics_sync (context); + + return TRUE; +} + +void +draw_box_animation (MetaScreen *screen, + MetaRectangle *initial_rect, + MetaRectangle *destination_rect, + double seconds_duration) +{ + BoxAnimationContext *context; + +#ifdef HAVE_SHAPE + XSetWindowAttributes attrs; +#else + XGCValues gc_values; +#endif + + g_return_if_fail (seconds_duration > 0.0); + + if (g_getenv ("MARCO_DEBUG_EFFECTS")) + seconds_duration *= 10; /* slow things down */ + + /* Create the animation context */ + context = g_new0 (BoxAnimationContext, 1); + + context->screen = screen; + + context->millisecs_duration = seconds_duration * 1000.0; + + context->start_rect = *initial_rect; + context->end_rect = *destination_rect; + +#ifdef HAVE_SHAPE + + attrs.override_redirect = True; + attrs.background_pixel = BlackPixel (screen->display->xdisplay, + screen->number); + + context->wireframe_xwindow = XCreateWindow (screen->display->xdisplay, + screen->xroot, + initial_rect->x, + initial_rect->y, + initial_rect->width, + initial_rect->height, + 0, + CopyFromParent, + CopyFromParent, + (Visual *)CopyFromParent, + CWOverrideRedirect | CWBackPixel, + &attrs); + + update_wireframe_window (screen->display, + context->wireframe_xwindow, + initial_rect); + + XMapWindow (screen->display->xdisplay, + context->wireframe_xwindow); + +#else /* !HAVE_SHAPE */ + + context->first_time = TRUE; + gc_values.subwindow_mode = IncludeInferiors; + gc_values.function = GXinvert; + + context->gc = XCreateGC (screen->display->xdisplay, + screen->xroot, + GCSubwindowMode | GCFunction, + &gc_values); + + /* Grab the X server to avoid screen dirt */ + meta_display_grab (context->screen->display); + meta_ui_push_delay_exposes (context->screen->ui); +#endif + + /* Do this only after we get the pixbuf from the server, + * so that the animation doesn't get truncated. + */ + g_get_current_time (&context->start_time); + + /* Add the timeout - a short one, could even use an idle, + * but this is maybe more CPU-friendly. + */ + g_timeout_add (15, + (GSourceFunc)effects_draw_box_animation_timeout, + context); + + /* kick changes onto the server */ + XFlush (context->screen->display->xdisplay); +} + +void +meta_effects_begin_wireframe (MetaScreen *screen, + const MetaRectangle *rect, + int width, + int height) +{ + /* Grab the X server to avoid screen dirt */ + meta_display_grab (screen->display); + meta_ui_push_delay_exposes (screen->ui); + + meta_effects_update_wireframe (screen, + NULL, -1, -1, + rect, width, height); +} + +static void +draw_xor_rect (MetaScreen *screen, + const MetaRectangle *rect, + int width, + int height) +{ + /* The lines in the center can't overlap the rectangle or each + * other, or the XOR gets reversed. So we have to draw things + * a bit oddly. + */ + XSegment segments[8]; + MetaRectangle shrunk_rect; + int i; + +#define LINE_WIDTH META_WIREFRAME_XOR_LINE_WIDTH + + /* We don't want the wireframe going outside the window area. + * It makes it harder for the user to position windows and it exposes other + * annoying bugs. + */ + shrunk_rect = *rect; + + shrunk_rect.x += LINE_WIDTH / 2 + LINE_WIDTH % 2; + shrunk_rect.y += LINE_WIDTH / 2 + LINE_WIDTH % 2; + shrunk_rect.width -= LINE_WIDTH + 2 * (LINE_WIDTH % 2); + shrunk_rect.height -= LINE_WIDTH + 2 * (LINE_WIDTH % 2); + + XDrawRectangle (screen->display->xdisplay, + screen->xroot, + screen->root_xor_gc, + shrunk_rect.x, shrunk_rect.y, + shrunk_rect.width, shrunk_rect.height); + + /* Don't put lines inside small rectangles where they won't fit */ + if (shrunk_rect.width < (LINE_WIDTH * 4) || + shrunk_rect.height < (LINE_WIDTH * 4)) + return; + + if ((width >= 0) && (height >= 0)) + { + XGCValues gc_values = { 0 }; + + if (XGetGCValues (screen->display->xdisplay, + screen->root_xor_gc, + GCFont, &gc_values)) + { + char *text; + int text_length; + + XFontStruct *font_struct; + int text_width, text_height; + int box_x, box_y; + int box_width, box_height; + + font_struct = XQueryFont (screen->display->xdisplay, + gc_values.font); + + if (font_struct != NULL) + { + text = g_strdup_printf ("%d x %d", width, height); + text_length = strlen (text); + + text_width = text_length * font_struct->max_bounds.width; + text_height = font_struct->max_bounds.descent + + font_struct->max_bounds.ascent; + + box_width = text_width + 2 * LINE_WIDTH; + box_height = text_height + 2 * LINE_WIDTH; + + + box_x = shrunk_rect.x + (shrunk_rect.width - box_width) / 2; + box_y = shrunk_rect.y + (shrunk_rect.height - box_height) / 2; + + if ((box_width < shrunk_rect.width) && + (box_height < shrunk_rect.height)) + { + XFillRectangle (screen->display->xdisplay, + screen->xroot, + screen->root_xor_gc, + box_x, box_y, + box_width, box_height); + XDrawString (screen->display->xdisplay, + screen->xroot, + screen->root_xor_gc, + box_x + LINE_WIDTH, + box_y + LINE_WIDTH + font_struct->max_bounds.ascent, + text, text_length); + } + + g_free (text); + + XFreeFontInfo (NULL, font_struct, 1); + + if ((box_width + LINE_WIDTH) >= (shrunk_rect.width / 3)) + return; + + if ((box_height + LINE_WIDTH) >= (shrunk_rect.height / 3)) + return; + } + } + } + + /* Two vertical lines at 1/3 and 2/3 */ + segments[0].x1 = shrunk_rect.x + shrunk_rect.width / 3; + segments[0].y1 = shrunk_rect.y + LINE_WIDTH / 2 + LINE_WIDTH % 2; + segments[0].x2 = segments[0].x1; + segments[0].y2 = shrunk_rect.y + shrunk_rect.height - LINE_WIDTH / 2; + + segments[1] = segments[0]; + segments[1].x1 = shrunk_rect.x + (shrunk_rect.width / 3) * 2; + segments[1].x2 = segments[1].x1; + + /* Now make two horizontal lines at 1/3 and 2/3, but not + * overlapping the verticals + */ + + segments[2].x1 = shrunk_rect.x + LINE_WIDTH / 2 + LINE_WIDTH % 2; + segments[2].x2 = segments[0].x1 - LINE_WIDTH / 2; + segments[2].y1 = shrunk_rect.y + shrunk_rect.height / 3; + segments[2].y2 = segments[2].y1; + + segments[3] = segments[2]; + segments[3].x1 = segments[2].x2 + LINE_WIDTH; + segments[3].x2 = segments[1].x1 - LINE_WIDTH / 2; + + segments[4] = segments[3]; + segments[4].x1 = segments[3].x2 + LINE_WIDTH; + segments[4].x2 = shrunk_rect.x + shrunk_rect.width - LINE_WIDTH / 2; + + /* Second horizontal line is just like the first, but + * shifted down + */ + i = 5; + while (i < 8) + { + segments[i] = segments[i - 3]; + segments[i].y1 = shrunk_rect.y + (shrunk_rect.height / 3) * 2; + segments[i].y2 = segments[i].y1; + ++i; + } + + XDrawSegments (screen->display->xdisplay, + screen->xroot, + screen->root_xor_gc, + segments, + G_N_ELEMENTS (segments)); +} + +void +meta_effects_update_wireframe (MetaScreen *screen, + const MetaRectangle *old_rect, + int old_width, + int old_height, + const MetaRectangle *new_rect, + int new_width, + int new_height) +{ + if (old_rect) + draw_xor_rect (screen, old_rect, old_width, old_height); + + if (new_rect) + draw_xor_rect (screen, new_rect, new_width, new_height); + + XFlush (screen->display->xdisplay); +} + +void +meta_effects_end_wireframe (MetaScreen *screen, + const MetaRectangle *old_rect, + int old_width, + int old_height) +{ + meta_effects_update_wireframe (screen, + old_rect, old_width, old_height, + NULL, -1, -1); + + meta_display_ungrab (screen->display); + meta_ui_pop_delay_exposes (screen->ui); +} + +static void +run_default_effect_handler (MetaEffect *effect) +{ + switch (effect->type) + { + case META_EFFECT_MINIMIZE: + draw_box_animation (effect->window->screen, + &(effect->u.minimize.window_rect), + &(effect->u.minimize.icon_rect), + META_MINIMIZE_ANIMATION_LENGTH); + break; + + default: + break; + } +} + +static void +run_handler (MetaEffect *effect) +{ + if (meta_prefs_get_mate_animations ()) + run_default_effect_handler (effect); + + effect_free (effect); +} diff --git a/src/core/effects.h b/src/core/effects.h new file mode 100644 index 00000000..91d09e15 --- /dev/null +++ b/src/core/effects.h @@ -0,0 +1,170 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file effects.h "Special effects" other than compositor effects. + * + * Before we had a serious compositor, we supported swooping + * rectangles for minimising and so on. These are still supported + * today, even when the compositor is enabled. The file contains two + * parts: + * + * 1) A set of functions, each of which implements a special effect. + * (Only the minimize function does anything interesting; we should + * probably get rid of the rest.) + * + * 2) A set of functions for moving a highlighted wireframe box around + * the screen, optionally with height and width shown in the middle. + * This is used for moving and resizing when reduced_resources is set. + * + * There was formerly a system which allowed callers to drop in their + * own handlers for various things; it was never used (people who want + * their own handlers can just modify this file, after all) and it added + * a good deal of extra complexity, so it has been removed. If you want it, + * it can be found in svn r3769. + */ + +/* + * Copyright (C) 2001 Anders Carlsson, Havoc Pennington + * + * 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. + */ + +#ifndef META_EFFECTS_H +#define META_EFFECTS_H + +#include "util.h" +#include "screen-private.h" + +typedef enum +{ + META_EFFECT_MINIMIZE, + META_EFFECT_UNMINIMIZE, + META_EFFECT_FOCUS, + META_EFFECT_CLOSE, + META_NUM_EFFECTS +} MetaEffectType; + +/** + * A callback which will be called when the effect has finished. + */ +typedef void (* MetaEffectFinished) (gpointer data); + +/** + * Performs the minimize effect. + * + * \param window The window we're moving + * \param window_rect Its current state + * \param target Where it should end up + * \param finished Callback for when it's finished + * \param data Data for callback + */ +void meta_effect_run_minimize (MetaWindow *window, + MetaRectangle *window_rect, + MetaRectangle *target, + MetaEffectFinished finished, + gpointer data); + +/** + * Performs the unminimize effect. There is no such effect. + * FIXME: delete this. + * + * \param window The window we're moving + * \param icon_rect Its current state + * \param window_rect Where it should end up + * \param finished Callback for when it's finished + * \param data Data for callback + */ +void meta_effect_run_unminimize (MetaWindow *window, + MetaRectangle *window_rect, + MetaRectangle *icon_rect, + MetaEffectFinished finished, + gpointer data); + +/** + * Performs the close effect. There is no such effect. + * FIXME: delete this. + * + * \param window The window we're moving + * \param finished Callback for when it's finished + * \param data Data for callback + */ +void meta_effect_run_close (MetaWindow *window, + MetaEffectFinished finished, + gpointer data); + +/** + * Performs the focus effect. There is no such effect. + * FIXME: delete this. + * + * \param window The window we're moving + * \param finished Callback for when it's finished + * \param data Data for callback + */ +void meta_effect_run_focus (MetaWindow *window, + MetaEffectFinished finished, + gpointer data); + +/** + * Grabs the server and paints a wireframe rectangle on the screen. + * Since this involves starting a grab, please be considerate of other + * users and don't keep the grab for long. You may move the wireframe + * around using meta_effects_update_wireframe() and remove it, and undo + * the grab, using meta_effects_end_wireframe(). + * + * \param screen The screen to draw the rectangle on. + * \param rect The size of the rectangle to draw. + * \param width The width to display in the middle (or 0 not to) + * \param height The width to display in the middle (or 0 not to) + */ +void meta_effects_begin_wireframe (MetaScreen *screen, + const MetaRectangle *rect, + int width, + int height); + +/** + * Moves a wireframe rectangle around after its creation by + * meta_effects_begin_wireframe(). (Perhaps we ought to remember the old + * positions and not require people to pass them in?) + * + * \param old_rect Where the rectangle is now + * \param old_width The width that was displayed on it (or 0 if there wasn't) + * \param old_height The height that was displayed on it (or 0 if there wasn't) + * \param new_rect Where the rectangle is going + * \param new_width The width that will be displayed on it (or 0 not to) + * \param new_height The height that will be displayed on it (or 0 not to) + */ +void meta_effects_update_wireframe (MetaScreen *screen, + const MetaRectangle *old_rect, + int old_width, + int old_height, + const MetaRectangle *new_rect, + int new_width, + int new_height); + +/** + * Removes a wireframe rectangle from the screen and ends the grab started by + * meta_effects_begin_wireframe(). + * + * \param old_rect Where the rectangle is now + * \param old_width The width that was displayed on it (or 0 if there wasn't) + * \param old_height The height that was displayed on it (or 0 if there wasn't) + */ +void meta_effects_end_wireframe (MetaScreen *screen, + const MetaRectangle *old_rect, + int width, + int height); + +#endif /* META_EFFECTS_H */ diff --git a/src/core/errors.c b/src/core/errors.c new file mode 100644 index 00000000..3b2a6334 --- /dev/null +++ b/src/core/errors.c @@ -0,0 +1,288 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X error handling */ + +/* + * Copyright (C) 2001 Havoc Pennington, error trapping inspired by GDK + * code copyrighted by the GTK team. + * + * 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 "errors.h" +#include "display-private.h" +#include <errno.h> +#include <stdlib.h> +#include <gdk/gdk.h> + +static int x_error_handler (Display *display, + XErrorEvent *error); +static int x_io_error_handler (Display *display); + +void +meta_errors_init (void) +{ + XSetErrorHandler (x_error_handler); + XSetIOErrorHandler (x_io_error_handler); +} + +typedef struct ForeignDisplay ForeignDisplay; + +struct ForeignDisplay +{ + Display *dpy; + ErrorHandler handler; + gpointer data; + ForeignDisplay *next; +}; + +static ForeignDisplay *foreign_displays; + +void +meta_errors_register_foreign_display (Display *foreign_dpy, + ErrorHandler handler, + gpointer data) +{ + ForeignDisplay *info = g_new0 (ForeignDisplay, 1); + info->dpy = foreign_dpy; + info->handler = handler; + info->data = data; + info->next = foreign_displays; + foreign_displays = info; +} + +static void +meta_error_trap_push_internal (MetaDisplay *display, + gboolean need_sync) +{ + /* GDK resets the error handler on each push */ + int (* old_error_handler) (Display *, + XErrorEvent *); + + if (need_sync) + { + XSync (display->xdisplay, False); + } + + gdk_error_trap_push (); + + /* old_error_handler will just be equal to x_error_handler + * for nested traps + */ + old_error_handler = XSetErrorHandler (x_error_handler); + + /* Replace GDK handler, but save it so we can chain up */ + if (display->error_trap_handler == NULL) + { + g_assert (display->error_traps == 0); + display->error_trap_handler = old_error_handler; + g_assert (display->error_trap_handler != x_error_handler); + } + + display->error_traps += 1; + + meta_topic (META_DEBUG_ERRORS, "%d traps remain\n", display->error_traps); +} + +static int +meta_error_trap_pop_internal (MetaDisplay *display, + gboolean need_sync) +{ + int result; + + g_assert (display->error_traps > 0); + + if (need_sync) + { + XSync (display->xdisplay, False); + } + + result = gdk_error_trap_pop (); + + display->error_traps -= 1; + + if (display->error_traps == 0) + { + /* check that GDK put our handler back; this + * assumes that there are no pending GDK traps from GDK itself + */ + + int (* restored_error_handler) (Display *, + XErrorEvent *); + + restored_error_handler = XSetErrorHandler (x_error_handler); + + /* remove this */ + display->error_trap_handler = NULL; + } + + meta_topic (META_DEBUG_ERRORS, "%d traps\n", display->error_traps); + + return result; +} + +void +meta_error_trap_push (MetaDisplay *display) +{ + meta_error_trap_push_internal (display, FALSE); +} + +void +meta_error_trap_pop (MetaDisplay *display, + gboolean last_request_was_roundtrip) +{ + gboolean need_sync; + + /* we only have to sync when popping the outermost trap */ + need_sync = (display->error_traps == 1 && !last_request_was_roundtrip); + + if (need_sync) + meta_topic (META_DEBUG_SYNC, "Syncing on error_trap_pop, traps = %d, roundtrip = %d\n", + display->error_traps, last_request_was_roundtrip); + + display->error_trap_synced_at_last_pop = need_sync || last_request_was_roundtrip; + + meta_error_trap_pop_internal (display, need_sync); +} + +void +meta_error_trap_push_with_return (MetaDisplay *display) +{ + gboolean need_sync; + + /* We don't sync on push_with_return if there are no traps + * currently, because we assume that any errors were either covered + * by a previous pop, or were fatal. + * + * More generally, we don't sync if we were synchronized last time + * we popped. This is known to be the case if there are no traps, + * but we also keep a flag so we know whether it's the case otherwise. + */ + + if (!display->error_trap_synced_at_last_pop) + need_sync = TRUE; + else + need_sync = FALSE; + + if (need_sync) + meta_topic (META_DEBUG_SYNC, "Syncing on error_trap_push_with_return, traps = %d\n", + display->error_traps); + + meta_error_trap_push_internal (display, FALSE); +} + +int +meta_error_trap_pop_with_return (MetaDisplay *display, + gboolean last_request_was_roundtrip) +{ + if (!last_request_was_roundtrip) + meta_topic (META_DEBUG_SYNC, "Syncing on error_trap_pop_with_return, traps = %d, roundtrip = %d\n", + display->error_traps, last_request_was_roundtrip); + + display->error_trap_synced_at_last_pop = TRUE; + + return meta_error_trap_pop_internal (display, + !last_request_was_roundtrip); +} + +static int +x_error_handler (Display *xdisplay, + XErrorEvent *error) +{ + int retval; + gchar buf[64]; + MetaDisplay *display; + ForeignDisplay *foreign; + + for (foreign = foreign_displays; foreign != NULL; foreign = foreign->next) + { + if (foreign->dpy == xdisplay) + { + foreign->handler (xdisplay, error, foreign->data); + + return 0; + } + } + + XGetErrorText (xdisplay, error->error_code, buf, 63); + + display = meta_display_for_x_display (xdisplay); + + /* Display can be NULL here because the compositing manager + * has its own Display, but Xlib only has one global error handler + */ + if (display->error_traps > 0) + { + /* we're in an error trap, chain to the trap handler + * saved from GDK + */ + meta_verbose ("X error: %s serial %ld error_code %d request_code %d minor_code %d)\n", + buf, + error->serial, + error->error_code, + error->request_code, + error->minor_code); + + g_assert (display->error_trap_handler != NULL); + g_assert (display->error_trap_handler != x_error_handler); + + retval = (* display->error_trap_handler) (xdisplay, error); + } + else + { + meta_bug ("Unexpected X error: %s serial %ld error_code %d request_code %d minor_code %d)\n", + buf, + error->serial, + error->error_code, + error->request_code, + error->minor_code); + + retval = 1; /* compiler warning */ + } + + return retval; +} + +static int +x_io_error_handler (Display *xdisplay) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (xdisplay); + + if (display == NULL) + meta_bug ("IO error received for unknown display?\n"); + + if (errno == EPIPE) + { + meta_warning (_("Lost connection to the display '%s';\n" + "most likely the X server was shut down or you killed/destroyed\n" + "the window manager.\n"), + display->name); + } + else + { + meta_warning (_("Fatal IO error %d (%s) on display '%s'.\n"), + errno, g_strerror (errno), + display->name); + } + + /* Xlib would force an exit anyhow */ + exit (1); + + return 0; +} diff --git a/src/core/eventqueue.c b/src/core/eventqueue.c new file mode 100644 index 00000000..2799cae9 --- /dev/null +++ b/src/core/eventqueue.c @@ -0,0 +1,184 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X event source for main loop */ + +/* + * Copyright (C) 2001 Havoc Pennington (based on GDK code (C) Owen + * Taylor, Red Hat Inc.) + * + * 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 "eventqueue.h" +#include <X11/Xlib.h> + +static gboolean eq_prepare (GSource *source, + gint *timeout); +static gboolean eq_check (GSource *source); +static gboolean eq_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); +static void eq_destroy (GSource *source); + +static GSourceFuncs eq_funcs = { + eq_prepare, + eq_check, + eq_dispatch, + eq_destroy +}; + +struct _MetaEventQueue +{ + GSource source; + + Display *display; + GPollFD poll_fd; + int connection_fd; + GQueue *events; +}; + +MetaEventQueue* +meta_event_queue_new (Display *display, MetaEventQueueFunc func, gpointer data) +{ + GSource *source; + MetaEventQueue *eq; + + source = g_source_new (&eq_funcs, sizeof (MetaEventQueue)); + eq = (MetaEventQueue*) source; + + eq->connection_fd = ConnectionNumber (display); + eq->poll_fd.fd = eq->connection_fd; + eq->poll_fd.events = G_IO_IN; + + eq->events = g_queue_new (); + + eq->display = display; + + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_add_poll (source, &eq->poll_fd); + g_source_set_can_recurse (source, TRUE); + + g_source_set_callback (source, (GSourceFunc) func, data, NULL); + + g_source_attach (source, NULL); + g_source_unref (source); + + return eq; +} + +void +meta_event_queue_free (MetaEventQueue *eq) +{ + GSource *source; + + source = (GSource*) eq; + + g_source_destroy (source); +} + +static gboolean +eq_events_pending (MetaEventQueue *eq) +{ + return eq->events->length > 0 || XPending (eq->display); +} + +static void +eq_queue_events (MetaEventQueue *eq) +{ + XEvent xevent; + + while (XPending (eq->display)) + { + XEvent *copy; + + XNextEvent (eq->display, &xevent); + + copy = g_new (XEvent, 1); + *copy = xevent; + + g_queue_push_tail (eq->events, copy); + } +} + +static gboolean +eq_prepare (GSource *source, gint *timeout) +{ + MetaEventQueue *eq; + + eq = (MetaEventQueue*) source; + + *timeout = -1; + + return eq_events_pending (eq); +} + +static gboolean +eq_check (GSource *source) +{ + MetaEventQueue *eq; + + eq = (MetaEventQueue*) source; + + if (eq->poll_fd.revents & G_IO_IN) + return eq_events_pending (eq); + else + return FALSE; +} + +static gboolean +eq_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) +{ + MetaEventQueue *eq; + + eq = (MetaEventQueue*) source; + + eq_queue_events (eq); + + if (eq->events->length > 0) + { + XEvent *event; + MetaEventQueueFunc func; + + event = g_queue_pop_head (eq->events); + func = (MetaEventQueueFunc) callback; + + (* func) (event, user_data); + + g_free (event); + } + + return TRUE; +} + +static void +eq_destroy (GSource *source) +{ + MetaEventQueue *eq; + + eq = (MetaEventQueue*) source; + + while (eq->events->length > 0) + { + XEvent *event; + + event = g_queue_pop_head (eq->events); + + g_free (event); + } + + g_queue_free (eq->events); + + /* source itself is freed by glib */ +} diff --git a/src/core/eventqueue.h b/src/core/eventqueue.h new file mode 100644 index 00000000..8f3596af --- /dev/null +++ b/src/core/eventqueue.h @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X event source for main loop */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * 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. + */ + +#ifndef META_EVENT_QUEUE_H +#define META_EVENT_QUEUE_H + +#include <glib.h> +#include <X11/Xlib.h> + +typedef struct _MetaEventQueue MetaEventQueue; + +typedef void (* MetaEventQueueFunc) (XEvent *event, + gpointer data); + +MetaEventQueue* meta_event_queue_new (Display *display, + MetaEventQueueFunc func, + gpointer data); +void meta_event_queue_free (MetaEventQueue *eq); + +#endif diff --git a/src/core/frame-private.h b/src/core/frame-private.h new file mode 100644 index 00000000..bde352dc --- /dev/null +++ b/src/core/frame-private.h @@ -0,0 +1,88 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X window decorations */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * 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. + */ + +#ifndef META_FRAME_PRIVATE_H +#define META_FRAME_PRIVATE_H + +#include "frame.h" +#include "window-private.h" + +typedef struct _MetaFrameGeometry MetaFrameGeometry; + +struct _MetaFrameGeometry +{ + /* border sizes (space between frame and child) */ + int left_width; + int right_width; + int top_height; + int bottom_height; +}; + +struct _MetaFrame +{ + /* window we frame */ + MetaWindow *window; + + /* reparent window */ + Window xwindow; + + MetaCursor current_cursor; + + /* This rect is trusted info from where we put the + * frame, not the result of ConfigureNotify + */ + MetaRectangle rect; + + /* position of client, size of frame */ + int child_x; + int child_y; + int right_width; + int bottom_height; + + guint mapped : 1; + guint need_reapply_frame_shape : 1; + guint is_flashing : 1; /* used by the visual bell flash */ +}; + +void meta_window_ensure_frame (MetaWindow *window); +void meta_window_destroy_frame (MetaWindow *window); +void meta_frame_queue_draw (MetaFrame *frame); + +MetaFrameFlags meta_frame_get_flags (MetaFrame *frame); + +/* These should ONLY be called from meta_window_move_resize_internal */ +void meta_frame_calc_geometry (MetaFrame *frame, + MetaFrameGeometry *geomp); +void meta_frame_sync_to_window (MetaFrame *frame, + int gravity, + gboolean need_move, + gboolean need_resize); + +void meta_frame_set_screen_cursor (MetaFrame *frame, + MetaCursor cursor); + +#endif + + + + diff --git a/src/core/frame.c b/src/core/frame.c new file mode 100644 index 00000000..2d0bbb51 --- /dev/null +++ b/src/core/frame.c @@ -0,0 +1,421 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "frame-private.h" +#include "bell.h" +#include "errors.h" +#include "keybindings.h" + +#ifdef HAVE_RENDER +#include <X11/extensions/Xrender.h> +#endif + +#define EVENT_MASK (SubstructureRedirectMask | \ + StructureNotifyMask | SubstructureNotifyMask | \ + ExposureMask | \ + ButtonPressMask | ButtonReleaseMask | \ + PointerMotionMask | PointerMotionHintMask | \ + EnterWindowMask | LeaveWindowMask | \ + FocusChangeMask | \ + ColormapChangeMask) + +void +meta_window_ensure_frame (MetaWindow *window) +{ + MetaFrame *frame; + XSetWindowAttributes attrs; + Visual *visual; + + if (window->frame) + return; + + /* See comment below for why this is required. */ + meta_display_grab (window->display); + + frame = g_new (MetaFrame, 1); + + frame->window = window; + frame->xwindow = None; + + frame->rect = window->rect; + frame->child_x = 0; + frame->child_y = 0; + frame->bottom_height = 0; + frame->right_width = 0; + frame->current_cursor = 0; + + frame->mapped = FALSE; + frame->need_reapply_frame_shape = TRUE; + frame->is_flashing = FALSE; + + meta_verbose ("Framing window %s: visual %s default, depth %d default depth %d\n", + window->desc, + XVisualIDFromVisual (window->xvisual) == + XVisualIDFromVisual (window->screen->default_xvisual) ? + "is" : "is not", + window->depth, window->screen->default_depth); + meta_verbose ("Frame geometry %d,%d %dx%d\n", + frame->rect.x, frame->rect.y, + frame->rect.width, frame->rect.height); + + /* Default depth/visual handles clients with weird visuals; they can + * always be children of the root depth/visual obviously, but + * e.g. DRI games can't be children of a parent that has the same + * visual as the client. NULL means default visual. + * + * We look for an ARGB visual if we can find one, otherwise use + * the default of NULL. + */ + + /* Special case for depth 32 windows (assumed to be ARGB), + * we use the window's visual. Otherwise we just use the system visual. + */ + if (window->depth == 32) + visual = window->xvisual; + else + visual = NULL; + + frame->xwindow = meta_ui_create_frame_window (window->screen->ui, + window->display->xdisplay, + visual, + frame->rect.x, + frame->rect.y, + frame->rect.width, + frame->rect.height, + frame->window->screen->number); + + meta_verbose ("Frame for %s is 0x%lx\n", frame->window->desc, frame->xwindow); + attrs.event_mask = EVENT_MASK; + XChangeWindowAttributes (window->display->xdisplay, + frame->xwindow, CWEventMask, &attrs); + + meta_display_register_x_window (window->display, &frame->xwindow, window); + + /* Now that frame->xwindow is registered with window, we can set its + * background. + */ + meta_ui_reset_frame_bg (window->screen->ui, frame->xwindow); + + /* Reparent the client window; it may be destroyed, + * thus the error trap. We'll get a destroy notify later + * and free everything. Comment in FVWM source code says + * we need a server grab or the child can get its MapNotify + * before we've finished reparenting and getting the decoration + * window onscreen, so ensure_frame must be called with + * a grab. + */ + meta_error_trap_push (window->display); + 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\n", window->desc); + window->unmaps_pending += 1; + } + /* window was reparented to this position */ + window->rect.x = 0; + window->rect.y = 0; + + XReparentWindow (window->display->xdisplay, + window->xwindow, + frame->xwindow, + window->rect.x, + window->rect.y); + /* FIXME handle this error */ + meta_error_trap_pop (window->display, FALSE); + + /* stick frame to the window */ + window->frame = frame; + + if (window->title) + meta_ui_set_frame_title (window->screen->ui, + window->frame->xwindow, + window->title); + + /* Move keybindings to frame instead of window */ + meta_window_grab_keys (window); + + /* Shape mask */ + meta_ui_apply_frame_shape (frame->window->screen->ui, + frame->xwindow, + frame->rect.width, + frame->rect.height, + frame->window->has_shape); + frame->need_reapply_frame_shape = FALSE; + + meta_display_ungrab (window->display); +} + +void +meta_window_destroy_frame (MetaWindow *window) +{ + MetaFrame *frame; + + if (window->frame == NULL) + return; + + meta_verbose ("Unframing window %s\n", window->desc); + + frame = window->frame; + + meta_bell_notify_frame_destroy (frame); + + /* Unparent the client window; it may be destroyed, + * thus the error trap. + */ + meta_error_trap_push (window->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\n", window->desc); + window->unmaps_pending += 1; + } + XReparentWindow (window->display->xdisplay, + window->xwindow, + window->screen->xroot, + /* Using anything other than meta_window_get_position() + * coordinates here means we'll need to ensure a configure + * notify event is sent; see bug 399552. + */ + window->frame->rect.x, + window->frame->rect.y); + meta_error_trap_pop (window->display, FALSE); + + meta_ui_destroy_frame_window (window->screen->ui, frame->xwindow); + + meta_display_unregister_x_window (window->display, + frame->xwindow); + + window->frame = NULL; + + /* Move keybindings to window instead of frame */ + meta_window_grab_keys (window); + + g_free (frame); + + /* Put our state back where it should be */ + meta_window_queue (window, META_QUEUE_CALC_SHOWING); + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + + +MetaFrameFlags +meta_frame_get_flags (MetaFrame *frame) +{ + MetaFrameFlags flags; + + flags = 0; + + if (frame->window->border_only) + { + ; /* FIXME this may disable the _function_ as well as decor + * in some cases, which is sort of wrong. + */ + } + else + { + flags |= META_FRAME_ALLOWS_MENU; + + if (frame->window->has_close_func) + flags |= META_FRAME_ALLOWS_DELETE; + + if (frame->window->has_maximize_func) + flags |= META_FRAME_ALLOWS_MAXIMIZE; + + if (frame->window->has_minimize_func) + flags |= META_FRAME_ALLOWS_MINIMIZE; + + if (frame->window->has_shade_func) + flags |= META_FRAME_ALLOWS_SHADE; + } + + if (META_WINDOW_ALLOWS_MOVE (frame->window)) + flags |= META_FRAME_ALLOWS_MOVE; + + if (META_WINDOW_ALLOWS_HORIZONTAL_RESIZE (frame->window)) + flags |= META_FRAME_ALLOWS_HORIZONTAL_RESIZE; + + if (META_WINDOW_ALLOWS_VERTICAL_RESIZE (frame->window)) + flags |= META_FRAME_ALLOWS_VERTICAL_RESIZE; + + if (frame->window->has_focus) + flags |= META_FRAME_HAS_FOCUS; + + if (frame->window->shaded) + flags |= META_FRAME_SHADED; + + if (frame->window->on_all_workspaces) + flags |= META_FRAME_STUCK; + + /* FIXME: Should we have some kind of UI for windows that are just vertically + * maximized or just horizontally maximized? + */ + if (META_WINDOW_MAXIMIZED (frame->window)) + flags |= META_FRAME_MAXIMIZED; + + if (frame->window->fullscreen) + flags |= META_FRAME_FULLSCREEN; + + if (frame->is_flashing) + flags |= META_FRAME_IS_FLASHING; + + if (frame->window->wm_state_above) + flags |= META_FRAME_ABOVE; + + return flags; +} + +void +meta_frame_calc_geometry (MetaFrame *frame, + MetaFrameGeometry *geomp) +{ + MetaFrameGeometry geom; + MetaWindow *window; + + window = frame->window; + + meta_ui_get_frame_geometry (window->screen->ui, + frame->xwindow, + &geom.top_height, + &geom.bottom_height, + &geom.left_width, + &geom.right_width); + + *geomp = geom; +} + +static void +update_shape (MetaFrame *frame) +{ + if (frame->need_reapply_frame_shape) + { + meta_ui_apply_frame_shape (frame->window->screen->ui, + frame->xwindow, + frame->rect.width, + frame->rect.height, + frame->window->has_shape); + frame->need_reapply_frame_shape = FALSE; + } +} + +void +meta_frame_sync_to_window (MetaFrame *frame, + int resize_gravity, + gboolean need_move, + gboolean need_resize) +{ + if (!(need_move || need_resize)) + { + update_shape (frame); + return; + } + + meta_topic (META_DEBUG_GEOMETRY, + "Syncing frame geometry %d,%d %dx%d (SE: %d,%d)\n", + frame->rect.x, frame->rect.y, + frame->rect.width, frame->rect.height, + frame->rect.x + frame->rect.width, + frame->rect.y + frame->rect.height); + + /* set bg to none to avoid flicker */ + if (need_resize) + { + meta_ui_unflicker_frame_bg (frame->window->screen->ui, + frame->xwindow, + frame->rect.width, + frame->rect.height); + + /* we need new shape if we're resized */ + frame->need_reapply_frame_shape = TRUE; + } + + /* Done before the window resize, because doing it before means + * part of the window being resized becomes unshaped, which may + * be sort of hard to see with bg = None. If we did it after + * window resize, part of the window being resized would become + * shaped, which might be more visible. + */ + update_shape (frame); + + meta_ui_move_resize_frame (frame->window->screen->ui, + frame->xwindow, + frame->rect.x, + frame->rect.y, + frame->rect.width, + frame->rect.height); + + if (need_resize) + { + meta_ui_reset_frame_bg (frame->window->screen->ui, + frame->xwindow); + + /* If we're interactively resizing the frame, repaint + * it immediately so we don't start to lag. + */ + if (frame->window->display->grab_window == + frame->window) + meta_ui_repaint_frame (frame->window->screen->ui, + frame->xwindow); + } +} + +void +meta_frame_queue_draw (MetaFrame *frame) +{ + meta_ui_queue_frame_draw (frame->window->screen->ui, + frame->xwindow); +} + +void +meta_frame_set_screen_cursor (MetaFrame *frame, + MetaCursor cursor) +{ + Cursor xcursor; + if (cursor == frame->current_cursor) + return; + frame->current_cursor = cursor; + if (cursor == META_CURSOR_DEFAULT) + XUndefineCursor (frame->window->display->xdisplay, frame->xwindow); + else + { + xcursor = meta_display_create_x_cursor (frame->window->display, cursor); + XDefineCursor (frame->window->display->xdisplay, frame->xwindow, xcursor); + XFlush (frame->window->display->xdisplay); + XFreeCursor (frame->window->display->xdisplay, xcursor); + } +} + +Window +meta_frame_get_xwindow (MetaFrame *frame) +{ + return frame->xwindow; +} diff --git a/src/core/group-private.h b/src/core/group-private.h new file mode 100644 index 00000000..bf8dd5a3 --- /dev/null +++ b/src/core/group-private.h @@ -0,0 +1,43 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window group private header */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * + * 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. + */ + +#ifndef META_GROUP_PRIVATE_H +#define META_GROUP_PRIVATE_H + +#include "group.h" + +struct _MetaGroup +{ + int refcount; + MetaDisplay *display; + GSList *windows; + Window group_leader; + char *startup_id; + char *wm_client_machine; +}; + +#endif + + + + diff --git a/src/core/group-props.c b/src/core/group-props.c new file mode 100644 index 00000000..05e82900 --- /dev/null +++ b/src/core/group-props.c @@ -0,0 +1,234 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* MetaGroup property handling */ + +/* + * Copyright (C) 2002 Red Hat, Inc. + * + * 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 "group-props.h" +#include "group-private.h" +#include "xprops.h" +#include <X11/Xatom.h> + +typedef void (* InitValueFunc) (MetaDisplay *display, + Atom property, + MetaPropValue *value); +typedef void (* ReloadValueFunc) (MetaGroup *group, + MetaPropValue *value); + +struct _MetaGroupPropHooks +{ + Atom property; + InitValueFunc init_func; + ReloadValueFunc reload_func; +}; + +static void init_prop_value (MetaDisplay *display, + Atom property, + MetaPropValue *value); +static void reload_prop_value (MetaGroup *group, + MetaPropValue *value); +static MetaGroupPropHooks* find_hooks (MetaDisplay *display, + Atom property); + + + +void +meta_group_reload_property (MetaGroup *group, + Atom property) +{ + meta_group_reload_properties (group, &property, 1); +} + +void +meta_group_reload_properties (MetaGroup *group, + const Atom *properties, + int n_properties) +{ + int i; + MetaPropValue *values; + + g_return_if_fail (properties != NULL); + g_return_if_fail (n_properties > 0); + + values = g_new0 (MetaPropValue, n_properties); + + i = 0; + while (i < n_properties) + { + init_prop_value (group->display, properties[i], &values[i]); + ++i; + } + + meta_prop_get_values (group->display, group->group_leader, + values, n_properties); + + i = 0; + while (i < n_properties) + { + reload_prop_value (group, &values[i]); + + ++i; + } + + meta_prop_free_values (values, n_properties); + + g_free (values); +} + +/* Fill in the MetaPropValue used to get the value of "property" */ +static void +init_prop_value (MetaDisplay *display, + Atom property, + MetaPropValue *value) +{ + MetaGroupPropHooks *hooks; + + value->type = META_PROP_VALUE_INVALID; + value->atom = None; + + hooks = find_hooks (display, property); + if (hooks && hooks->init_func != NULL) + (* hooks->init_func) (display, property, value); +} + +static void +reload_prop_value (MetaGroup *group, + MetaPropValue *value) +{ + MetaGroupPropHooks *hooks; + + hooks = find_hooks (group->display, value->atom); + if (hooks && hooks->reload_func != NULL) + (* hooks->reload_func) (group, value); +} + +static void +init_wm_client_machine (MetaDisplay *display, + Atom property, + MetaPropValue *value) +{ + value->type = META_PROP_VALUE_STRING; + value->atom = display->atom_WM_CLIENT_MACHINE; +} + +static void +reload_wm_client_machine (MetaGroup *group, + MetaPropValue *value) +{ + g_free (group->wm_client_machine); + group->wm_client_machine = NULL; + + if (value->type != META_PROP_VALUE_INVALID) + group->wm_client_machine = g_strdup (value->v.str); + + meta_verbose ("Group has client machine \"%s\"\n", + group->wm_client_machine ? group->wm_client_machine : "unset"); +} + +static void +init_net_startup_id (MetaDisplay *display, + Atom property, + MetaPropValue *value) +{ + value->type = META_PROP_VALUE_UTF8; + value->atom = display->atom__NET_STARTUP_ID; +} + +static void +reload_net_startup_id (MetaGroup *group, + MetaPropValue *value) +{ + g_free (group->startup_id); + group->startup_id = NULL; + + if (value->type != META_PROP_VALUE_INVALID) + group->startup_id = g_strdup (value->v.str); + + meta_verbose ("Group has startup id \"%s\"\n", + group->startup_id ? group->startup_id : "unset"); +} + +#define N_HOOKS 3 + +void +meta_display_init_group_prop_hooks (MetaDisplay *display) +{ + int i; + MetaGroupPropHooks *hooks; + + g_assert (display->group_prop_hooks == NULL); + + display->group_prop_hooks = g_new0 (MetaGroupPropHooks, N_HOOKS); + hooks = display->group_prop_hooks; + + i = 0; + + hooks[i].property = display->atom_WM_CLIENT_MACHINE; + hooks[i].init_func = init_wm_client_machine; + hooks[i].reload_func = reload_wm_client_machine; + ++i; + + hooks[i].property = display->atom__NET_WM_PID; + hooks[i].init_func = NULL; + hooks[i].reload_func = NULL; + ++i; + + hooks[i].property = display->atom__NET_STARTUP_ID; + hooks[i].init_func = init_net_startup_id; + hooks[i].reload_func = reload_net_startup_id; + ++i; + + if (i != N_HOOKS) + { + g_error ("Initialized %d group hooks should have been %d\n", i, N_HOOKS); + } +} + +void +meta_display_free_group_prop_hooks (MetaDisplay *display) +{ + g_assert (display->group_prop_hooks != NULL); + + g_free (display->group_prop_hooks); + display->group_prop_hooks = NULL; +} + +static MetaGroupPropHooks* +find_hooks (MetaDisplay *display, + Atom property) +{ + int i; + + /* FIXME we could sort the array and do binary search or + * something + */ + + i = 0; + while (i < N_HOOKS) + { + if (display->group_prop_hooks[i].property == property) + return &display->group_prop_hooks[i]; + + ++i; + } + + return NULL; +} diff --git a/src/core/group-props.h b/src/core/group-props.h new file mode 100644 index 00000000..ffde0901 --- /dev/null +++ b/src/core/group-props.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* MetaGroup property handling */ + +/* + * Copyright (C) 2002 Red Hat, Inc. + * + * 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. + */ + +#ifndef META_GROUP_PROPS_H +#define META_GROUP_PROPS_H + +#include "group.h" + +void meta_group_reload_property (MetaGroup *group, + Atom property); +void meta_group_reload_properties (MetaGroup *group, + const Atom *properties, + int n_properties); +void meta_display_init_group_prop_hooks (MetaDisplay *display); +void meta_display_free_group_prop_hooks (MetaDisplay *display); + +#endif /* META_GROUP_PROPS_H */ diff --git a/src/core/group.c b/src/core/group.c new file mode 100644 index 00000000..35db53c7 --- /dev/null +++ b/src/core/group.c @@ -0,0 +1,274 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window groups */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * Copyright (C) 2003 Rob Adams + * + * 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 "util.h" +#include "group-private.h" +#include "group-props.h" +#include "window.h" + +static MetaGroup* +meta_group_new (MetaDisplay *display, + Window group_leader) +{ + MetaGroup *group; +#define N_INITIAL_PROPS 3 + Atom initial_props[N_INITIAL_PROPS]; + int i; + + g_assert (N_INITIAL_PROPS == (int) G_N_ELEMENTS (initial_props)); + + group = g_new0 (MetaGroup, 1); + + group->display = display; + group->windows = NULL; + group->group_leader = group_leader; + group->refcount = 1; /* owned by caller, hash table has only weak ref */ + + if (display->groups_by_leader == NULL) + display->groups_by_leader = g_hash_table_new (meta_unsigned_long_hash, + meta_unsigned_long_equal); + + g_assert (g_hash_table_lookup (display->groups_by_leader, &group_leader) == NULL); + + g_hash_table_insert (display->groups_by_leader, + &group->group_leader, + group); + + /* Fill these in the order we want them to be gotten */ + i = 0; + initial_props[i++] = display->atom_WM_CLIENT_MACHINE; + initial_props[i++] = display->atom__NET_WM_PID; + initial_props[i++] = display->atom__NET_STARTUP_ID; + g_assert (N_INITIAL_PROPS == i); + + meta_group_reload_properties (group, initial_props, N_INITIAL_PROPS); + + meta_topic (META_DEBUG_GROUPS, + "Created new group with leader 0x%lx\n", + group->group_leader); + + return group; +} + +static void +meta_group_unref (MetaGroup *group) +{ + g_return_if_fail (group->refcount > 0); + + group->refcount -= 1; + if (group->refcount == 0) + { + meta_topic (META_DEBUG_GROUPS, + "Destroying group with leader 0x%lx\n", + group->group_leader); + + g_assert (group->display->groups_by_leader != NULL); + + g_hash_table_remove (group->display->groups_by_leader, + &group->group_leader); + + /* mop up hash table, this is how it gets freed on display close */ + if (g_hash_table_size (group->display->groups_by_leader) == 0) + { + g_hash_table_destroy (group->display->groups_by_leader); + group->display->groups_by_leader = NULL; + } + + g_free (group->wm_client_machine); + g_free (group->startup_id); + + g_free (group); + } +} + +MetaGroup* +meta_window_get_group (MetaWindow *window) +{ + if (window->unmanaging) + return NULL; + + return window->group; +} + +void +meta_window_compute_group (MetaWindow* window) +{ + MetaGroup *group; + MetaWindow *ancestor; + + /* use window->xwindow if no window->xgroup_leader */ + + group = NULL; + + /* Determine the ancestor of the window; its group setting will override the + * normal grouping rules; see bug 328211. + */ + ancestor = meta_window_find_root_ancestor (window); + + if (window->display->groups_by_leader) + { + if (ancestor != window) + group = ancestor->group; + else if (window->xgroup_leader != None) + group = g_hash_table_lookup (window->display->groups_by_leader, + &window->xgroup_leader); + else + group = g_hash_table_lookup (window->display->groups_by_leader, + &window->xwindow); + } + + if (group != NULL) + { + window->group = group; + group->refcount += 1; + } + else + { + if (ancestor != window && ancestor->xgroup_leader != None) + group = meta_group_new (window->display, + ancestor->xgroup_leader); + else if (window->xgroup_leader != None) + group = meta_group_new (window->display, + window->xgroup_leader); + else + group = meta_group_new (window->display, + window->xwindow); + + window->group = group; + } + + window->group->windows = g_slist_prepend (window->group->windows, window); + + meta_topic (META_DEBUG_GROUPS, + "Adding %s to group with leader 0x%lx\n", + window->desc, group->group_leader); + +} + +static void +remove_window_from_group (MetaWindow *window) +{ + if (window->group != NULL) + { + meta_topic (META_DEBUG_GROUPS, + "Removing %s from group with leader 0x%lx\n", + window->desc, window->group->group_leader); + + window->group->windows = + g_slist_remove (window->group->windows, + window); + meta_group_unref (window->group); + window->group = NULL; + } +} + +void +meta_window_group_leader_changed (MetaWindow *window) +{ + remove_window_from_group (window); + meta_window_compute_group (window); +} + +void +meta_window_shutdown_group (MetaWindow *window) +{ + remove_window_from_group (window); +} + +MetaGroup* +meta_display_lookup_group (MetaDisplay *display, + Window group_leader) +{ + MetaGroup *group; + + group = NULL; + + if (display->groups_by_leader) + group = g_hash_table_lookup (display->groups_by_leader, + &group_leader); + + return group; +} + +GSList* +meta_group_list_windows (MetaGroup *group) +{ + return g_slist_copy (group->windows); +} + +void +meta_group_update_layers (MetaGroup *group) +{ + GSList *tmp; + GSList *frozen_stacks; + + if (group->windows == NULL) + return; + + frozen_stacks = NULL; + tmp = group->windows; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + /* we end up freezing the same stack a lot of times, + * but doesn't hurt anything. have to handle + * groups that span 2 screens. + */ + meta_stack_freeze (window->screen->stack); + frozen_stacks = g_slist_prepend (frozen_stacks, window->screen->stack); + + meta_stack_update_layer (window->screen->stack, + window); + + tmp = tmp->next; + } + + tmp = frozen_stacks; + while (tmp != NULL) + { + meta_stack_thaw (tmp->data); + tmp = tmp->next; + } + + g_slist_free (frozen_stacks); +} + +const char* +meta_group_get_startup_id (MetaGroup *group) +{ + return group->startup_id; +} + +gboolean +meta_group_property_notify (MetaGroup *group, + XEvent *event) +{ + meta_group_reload_property (group, + event->xproperty.atom); + + return TRUE; + +} diff --git a/src/core/group.h b/src/core/group.h new file mode 100644 index 00000000..f71c2c47 --- /dev/null +++ b/src/core/group.h @@ -0,0 +1,53 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window groups */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * + * 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. + */ + +#ifndef META_GROUP_H +#define META_GROUP_H + +#include "window-private.h" + +/* note, can return NULL */ +MetaGroup* meta_window_get_group (MetaWindow *window); +void meta_window_compute_group (MetaWindow* window); +void meta_window_shutdown_group (MetaWindow *window); + +void meta_window_group_leader_changed (MetaWindow *window); + +/* note, can return NULL */ +MetaGroup* meta_display_lookup_group (MetaDisplay *display, + Window group_leader); + +GSList* meta_group_list_windows (MetaGroup *group); + +void meta_group_update_layers (MetaGroup *group); + +const char* meta_group_get_startup_id (MetaGroup *group); + +gboolean meta_group_property_notify (MetaGroup *group, + XEvent *event); + +#endif + + + + diff --git a/src/core/iconcache.c b/src/core/iconcache.c new file mode 100644 index 00000000..af9411ba --- /dev/null +++ b/src/core/iconcache.c @@ -0,0 +1,849 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window icons */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * 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 "iconcache.h" +#include "ui.h" +#include "errors.h" + +#include <X11/Xatom.h> + +/* The icon-reading code is also in libwnck, please sync bugfixes */ + +static void +get_fallback_icons (MetaScreen *screen, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height) +{ + /* we don't scale, should be fixed if we ever un-hardcode the icon + * size + */ + *iconp = meta_ui_get_default_window_icon (screen->ui); + *mini_iconp = meta_ui_get_default_mini_icon (screen->ui); +} + +static gboolean +find_largest_sizes (gulong *data, + gulong nitems, + int *width, + int *height) +{ + *width = 0; + *height = 0; + + while (nitems > 0) + { + int w, h; + + if (nitems < 3) + return FALSE; /* no space for w, h */ + + w = data[0]; + h = data[1]; + + if (nitems < ((gulong)(w * h) + 2)) + return FALSE; /* not enough data */ + + *width = MAX (w, *width); + *height = MAX (h, *height); + + data += (w * h) + 2; + nitems -= (w * h) + 2; + } + + return TRUE; +} + +static gboolean +find_best_size (gulong *data, + gulong nitems, + int ideal_width, + int ideal_height, + int *width, + int *height, + gulong **start) +{ + int best_w; + int best_h; + gulong *best_start; + int max_width, max_height; + + *width = 0; + *height = 0; + *start = NULL; + + if (!find_largest_sizes (data, nitems, &max_width, &max_height)) + return FALSE; + + if (ideal_width < 0) + ideal_width = max_width; + if (ideal_height < 0) + ideal_height = max_height; + + best_w = 0; + best_h = 0; + best_start = NULL; + + while (nitems > 0) + { + int w, h; + gboolean replace; + + replace = FALSE; + + if (nitems < 3) + return FALSE; /* no space for w, h */ + + w = data[0]; + h = data[1]; + + if (nitems < ((gulong)(w * h) + 2)) + break; /* not enough data */ + + if (best_start == NULL) + { + replace = TRUE; + } + else + { + /* work with averages */ + const int ideal_size = (ideal_width + ideal_height) / 2; + int best_size = (best_w + best_h) / 2; + int this_size = (w + h) / 2; + + /* larger than desired is always better than smaller */ + if (best_size < ideal_size && + this_size >= ideal_size) + replace = TRUE; + /* if we have too small, pick anything bigger */ + else if (best_size < ideal_size && + this_size > best_size) + replace = TRUE; + /* if we have too large, pick anything smaller + * but still >= the ideal + */ + else if (best_size > ideal_size && + this_size >= ideal_size && + this_size < best_size) + replace = TRUE; + } + + if (replace) + { + best_start = data + 2; + best_w = w; + best_h = h; + } + + data += (w * h) + 2; + nitems -= (w * h) + 2; + } + + if (best_start) + { + *start = best_start; + *width = best_w; + *height = best_h; + return TRUE; + } + else + return FALSE; +} + +static void +argbdata_to_pixdata (gulong *argb_data, int len, guchar **pixdata) +{ + guchar *p; + int i; + + *pixdata = g_new (guchar, len * 4); + p = *pixdata; + + /* One could speed this up a lot. */ + i = 0; + while (i < len) + { + guint argb; + guint rgba; + + argb = argb_data[i]; + rgba = (argb << 8) | (argb >> 24); + + *p = rgba >> 24; + ++p; + *p = (rgba >> 16) & 0xff; + ++p; + *p = (rgba >> 8) & 0xff; + ++p; + *p = rgba & 0xff; + ++p; + + ++i; + } +} + +static gboolean +read_rgb_icon (MetaDisplay *display, + Window xwindow, + int ideal_width, + int ideal_height, + int ideal_mini_width, + int ideal_mini_height, + int *width, + int *height, + guchar **pixdata, + int *mini_width, + int *mini_height, + guchar **mini_pixdata) +{ + Atom type; + int format; + gulong nitems; + gulong bytes_after; + int result, err; + guchar *data; + gulong *best; + int w, h; + gulong *best_mini; + int mini_w, mini_h; + gulong *data_as_long; + + meta_error_trap_push_with_return (display); + type = None; + data = NULL; + result = XGetWindowProperty (display->xdisplay, + xwindow, + display->atom__NET_WM_ICON, + 0, G_MAXLONG, + False, XA_CARDINAL, &type, &format, &nitems, + &bytes_after, &data); + err = meta_error_trap_pop_with_return (display, TRUE); + + if (err != Success || + result != Success) + return FALSE; + + if (type != XA_CARDINAL) + { + XFree (data); + return FALSE; + } + + data_as_long = (gulong *)data; + + if (!find_best_size (data_as_long, nitems, + ideal_width, ideal_height, + &w, &h, &best)) + { + XFree (data); + return FALSE; + } + + if (!find_best_size (data_as_long, nitems, + ideal_mini_width, ideal_mini_height, + &mini_w, &mini_h, &best_mini)) + { + XFree (data); + return FALSE; + } + + *width = w; + *height = h; + + *mini_width = mini_w; + *mini_height = mini_h; + + argbdata_to_pixdata (best, w * h, pixdata); + argbdata_to_pixdata (best_mini, mini_w * mini_h, mini_pixdata); + + XFree (data); + + return TRUE; +} + +static void +free_pixels (guchar *pixels, gpointer data) +{ + g_free (pixels); +} + +static void +get_pixmap_geometry (MetaDisplay *display, + Pixmap pixmap, + int *w, + int *h, + int *d) +{ + Window root_ignored; + int x_ignored, y_ignored; + guint width, height; + guint border_width_ignored; + guint depth; + + if (w) + *w = 1; + if (h) + *h = 1; + if (d) + *d = 1; + + XGetGeometry (display->xdisplay, + pixmap, &root_ignored, &x_ignored, &y_ignored, + &width, &height, &border_width_ignored, &depth); + + if (w) + *w = width; + if (h) + *h = height; + if (d) + *d = depth; +} + +static GdkPixbuf* +apply_mask (GdkPixbuf *pixbuf, + GdkPixbuf *mask) +{ + int w, h; + int i, j; + GdkPixbuf *with_alpha; + guchar *src; + guchar *dest; + int src_stride; + int dest_stride; + + w = MIN (gdk_pixbuf_get_width (mask), gdk_pixbuf_get_width (pixbuf)); + h = MIN (gdk_pixbuf_get_height (mask), gdk_pixbuf_get_height (pixbuf)); + + with_alpha = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + + dest = gdk_pixbuf_get_pixels (with_alpha); + src = gdk_pixbuf_get_pixels (mask); + + dest_stride = gdk_pixbuf_get_rowstride (with_alpha); + src_stride = gdk_pixbuf_get_rowstride (mask); + + i = 0; + while (i < h) + { + j = 0; + while (j < w) + { + guchar *s = src + i * src_stride + j * 3; + guchar *d = dest + i * dest_stride + j * 4; + + /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 + * otherwise + */ + if (s[0] == 0) + d[3] = 0; /* transparent */ + else + d[3] = 255; /* opaque */ + + ++j; + } + + ++i; + } + + return with_alpha; +} + +static gboolean +try_pixmap_and_mask (MetaDisplay *display, + Pixmap src_pixmap, + Pixmap src_mask, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height) +{ + GdkPixbuf *unscaled = NULL; + GdkPixbuf *mask = NULL; + int w, h; + + if (src_pixmap == None) + return FALSE; + + meta_error_trap_push (display); + + get_pixmap_geometry (display, src_pixmap, &w, &h, NULL); + + unscaled = meta_gdk_pixbuf_get_from_pixmap (NULL, + src_pixmap, + 0, 0, 0, 0, + w, h); + + if (unscaled && src_mask != None) + { + get_pixmap_geometry (display, src_mask, &w, &h, NULL); + mask = meta_gdk_pixbuf_get_from_pixmap (NULL, + src_mask, + 0, 0, 0, 0, + w, h); + } + + meta_error_trap_pop (display, FALSE); + + if (mask) + { + GdkPixbuf *masked; + + masked = apply_mask (unscaled, mask); + g_object_unref (G_OBJECT (unscaled)); + unscaled = masked; + + g_object_unref (G_OBJECT (mask)); + mask = NULL; + } + + if (unscaled) + { + *iconp = + gdk_pixbuf_scale_simple (unscaled, + ideal_width > 0 ? ideal_width : + gdk_pixbuf_get_width (unscaled), + ideal_height > 0 ? ideal_height : + gdk_pixbuf_get_height (unscaled), + GDK_INTERP_BILINEAR); + *mini_iconp = + gdk_pixbuf_scale_simple (unscaled, + ideal_mini_width > 0 ? ideal_mini_width : + gdk_pixbuf_get_width (unscaled), + ideal_mini_height > 0 ? ideal_mini_height : + gdk_pixbuf_get_height (unscaled), + GDK_INTERP_BILINEAR); + + g_object_unref (G_OBJECT (unscaled)); + + if (*iconp && *mini_iconp) + return TRUE; + else + { + if (*iconp) + g_object_unref (G_OBJECT (*iconp)); + if (*mini_iconp) + g_object_unref (G_OBJECT (*mini_iconp)); + return FALSE; + } + } + else + return FALSE; +} + +static void +get_kwm_win_icon (MetaDisplay *display, + Window xwindow, + Pixmap *pixmap, + Pixmap *mask) +{ + Atom type; + int format; + gulong nitems; + gulong bytes_after; + guchar *data; + Pixmap *icons; + int err, result; + + *pixmap = None; + *mask = None; + + meta_error_trap_push_with_return (display); + icons = NULL; + result = XGetWindowProperty (display->xdisplay, xwindow, + display->atom__KWM_WIN_ICON, + 0, G_MAXLONG, + False, + display->atom__KWM_WIN_ICON, + &type, &format, &nitems, + &bytes_after, &data); + icons = (Pixmap *)data; + + err = meta_error_trap_pop_with_return (display, TRUE); + if (err != Success || + result != Success) + return; + + if (type != display->atom__KWM_WIN_ICON) + { + XFree (icons); + return; + } + + *pixmap = icons[0]; + *mask = icons[1]; + + XFree (icons); + + return; +} + +void +meta_icon_cache_init (MetaIconCache *icon_cache) +{ + g_return_if_fail (icon_cache != NULL); + + icon_cache->origin = USING_NO_ICON; + icon_cache->prev_pixmap = None; + icon_cache->prev_mask = None; +#if 0 + icon_cache->icon = NULL; + icon_cache->mini_icon = NULL; + icon_cache->ideal_width = -1; /* won't be a legit width */ + icon_cache->ideal_height = -1; + icon_cache->ideal_mini_width = -1; + icon_cache->ideal_mini_height = -1; +#endif + icon_cache->want_fallback = TRUE; + icon_cache->wm_hints_dirty = TRUE; + icon_cache->kwm_win_icon_dirty = TRUE; + icon_cache->net_wm_icon_dirty = TRUE; +} + +static void +clear_icon_cache (MetaIconCache *icon_cache, + gboolean dirty_all) +{ +#if 0 + if (icon_cache->icon) + g_object_unref (G_OBJECT (icon_cache->icon)); + icon_cache->icon = NULL; + + if (icon_cache->mini_icon) + g_object_unref (G_OBJECT (icon_cache->mini_icon)); + icon_cache->mini_icon = NULL; +#endif + + icon_cache->origin = USING_NO_ICON; + + if (dirty_all) + { + icon_cache->wm_hints_dirty = TRUE; + icon_cache->kwm_win_icon_dirty = TRUE; + icon_cache->net_wm_icon_dirty = TRUE; + } +} + +void +meta_icon_cache_free (MetaIconCache *icon_cache) +{ + clear_icon_cache (icon_cache, FALSE); +} + +void +meta_icon_cache_property_changed (MetaIconCache *icon_cache, + MetaDisplay *display, + Atom atom) +{ + if (atom == display->atom__NET_WM_ICON) + icon_cache->net_wm_icon_dirty = TRUE; + else if (atom == display->atom__KWM_WIN_ICON) + icon_cache->kwm_win_icon_dirty = TRUE; + else if (atom == XA_WM_HINTS) + icon_cache->wm_hints_dirty = TRUE; +} + +gboolean +meta_icon_cache_get_icon_invalidated (MetaIconCache *icon_cache) +{ + if (icon_cache->origin <= USING_KWM_WIN_ICON && + icon_cache->kwm_win_icon_dirty) + return TRUE; + else if (icon_cache->origin <= USING_WM_HINTS && + icon_cache->wm_hints_dirty) + return TRUE; + else if (icon_cache->origin <= USING_NET_WM_ICON && + icon_cache->net_wm_icon_dirty) + return TRUE; + else if (icon_cache->origin < USING_FALLBACK_ICON && + icon_cache->want_fallback) + return TRUE; + else if (icon_cache->origin == USING_NO_ICON) + return TRUE; + else if (icon_cache->origin == USING_FALLBACK_ICON && + !icon_cache->want_fallback) + return TRUE; + else + return FALSE; +} + +static void +replace_cache (MetaIconCache *icon_cache, + IconOrigin origin, + GdkPixbuf *new_icon, + GdkPixbuf *new_mini_icon) +{ + clear_icon_cache (icon_cache, FALSE); + + icon_cache->origin = origin; + +#if 0 + if (new_icon) + g_object_ref (G_OBJECT (new_icon)); + + icon_cache->icon = new_icon; + + if (new_mini_icon) + g_object_ref (G_OBJECT (new_mini_icon)); + + icon_cache->mini_icon = new_mini_icon; +#endif +} + +static GdkPixbuf* +scaled_from_pixdata (guchar *pixdata, + int w, + int h, + int new_w, + int new_h) +{ + GdkPixbuf *src; + GdkPixbuf *dest; + + src = gdk_pixbuf_new_from_data (pixdata, + GDK_COLORSPACE_RGB, + TRUE, + 8, + w, h, w * 4, + free_pixels, + NULL); + + if (src == NULL) + return NULL; + + if (w != h) + { + GdkPixbuf *tmp; + int size; + + size = MAX (w, h); + + tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size, size); + + if (tmp) + { + gdk_pixbuf_fill (tmp, 0); + gdk_pixbuf_copy_area (src, 0, 0, w, h, + tmp, + (size - w) / 2, (size - h) / 2); + + g_object_unref (src); + src = tmp; + } + } + + if (w != new_w || h != new_h) + { + dest = gdk_pixbuf_scale_simple (src, new_w, new_h, GDK_INTERP_BILINEAR); + + g_object_unref (G_OBJECT (src)); + } + else + { + dest = src; + } + + return dest; +} + +gboolean +meta_read_icons (MetaScreen *screen, + Window xwindow, + MetaIconCache *icon_cache, + Pixmap wm_hints_pixmap, + Pixmap wm_hints_mask, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height) +{ + guchar *pixdata; + int w, h; + guchar *mini_pixdata; + int mini_w, mini_h; + Pixmap pixmap; + Pixmap mask; + + /* Return value is whether the icon changed */ + + g_return_val_if_fail (icon_cache != NULL, FALSE); + + *iconp = NULL; + *mini_iconp = NULL; + +#if 0 + if (ideal_width != icon_cache->ideal_width || + ideal_height != icon_cache->ideal_height || + ideal_mini_width != icon_cache->ideal_mini_width || + ideal_mini_height != icon_cache->ideal_mini_height) + clear_icon_cache (icon_cache, TRUE); + + icon_cache->ideal_width = ideal_width; + icon_cache->ideal_height = ideal_height; + icon_cache->ideal_mini_width = ideal_mini_width; + icon_cache->ideal_mini_height = ideal_mini_height; +#endif + + if (!meta_icon_cache_get_icon_invalidated (icon_cache)) + return FALSE; /* we have no new info to use */ + + pixdata = NULL; + + /* Our algorithm here assumes that we can't have for example origin + * < USING_NET_WM_ICON and icon_cache->net_wm_icon_dirty == FALSE + * unless we have tried to read NET_WM_ICON. + * + * Put another way, if an icon origin is not dirty, then we have + * tried to read it at the current size. If it is dirty, then + * we haven't done that since the last change. + */ + + if (icon_cache->origin <= USING_NET_WM_ICON && + icon_cache->net_wm_icon_dirty) + + { + icon_cache->net_wm_icon_dirty = FALSE; + + if (read_rgb_icon (screen->display, xwindow, + ideal_width, ideal_height, + ideal_mini_width, ideal_mini_height, + &w, &h, &pixdata, + &mini_w, &mini_h, &mini_pixdata)) + { + *iconp = scaled_from_pixdata (pixdata, w, h, + ideal_width, ideal_height); + + *mini_iconp = scaled_from_pixdata (mini_pixdata, mini_w, mini_h, + ideal_mini_width, ideal_mini_height); + + if (*iconp && *mini_iconp) + { + replace_cache (icon_cache, USING_NET_WM_ICON, + *iconp, *mini_iconp); + + return TRUE; + } + else + { + if (*iconp) + g_object_unref (G_OBJECT (*iconp)); + if (*mini_iconp) + g_object_unref (G_OBJECT (*mini_iconp)); + } + } + } + + if (icon_cache->origin <= USING_WM_HINTS && + icon_cache->wm_hints_dirty) + { + icon_cache->wm_hints_dirty = FALSE; + + pixmap = wm_hints_pixmap; + mask = wm_hints_mask; + + /* We won't update if pixmap is unchanged; + * avoids a get_from_drawable() on every geometry + * hints change + */ + if ((pixmap != icon_cache->prev_pixmap || + mask != icon_cache->prev_mask) && + pixmap != None) + { + if (try_pixmap_and_mask (screen->display, + pixmap, mask, + iconp, ideal_width, ideal_height, + mini_iconp, ideal_mini_width, ideal_mini_height)) + { + icon_cache->prev_pixmap = pixmap; + icon_cache->prev_mask = mask; + + replace_cache (icon_cache, USING_WM_HINTS, + *iconp, *mini_iconp); + + return TRUE; + } + } + } + + if (icon_cache->origin <= USING_KWM_WIN_ICON && + icon_cache->kwm_win_icon_dirty) + { + icon_cache->kwm_win_icon_dirty = FALSE; + + get_kwm_win_icon (screen->display, xwindow, &pixmap, &mask); + + if ((pixmap != icon_cache->prev_pixmap || + mask != icon_cache->prev_mask) && + pixmap != None) + { + if (try_pixmap_and_mask (screen->display, pixmap, mask, + iconp, ideal_width, ideal_height, + mini_iconp, ideal_mini_width, ideal_mini_height)) + { + icon_cache->prev_pixmap = pixmap; + icon_cache->prev_mask = mask; + + replace_cache (icon_cache, USING_KWM_WIN_ICON, + *iconp, *mini_iconp); + + return TRUE; + } + } + } + + if (icon_cache->want_fallback && + icon_cache->origin < USING_FALLBACK_ICON) + { + get_fallback_icons (screen, + iconp, + ideal_width, + ideal_height, + mini_iconp, + ideal_mini_width, + ideal_mini_height); + + replace_cache (icon_cache, USING_FALLBACK_ICON, + *iconp, *mini_iconp); + + return TRUE; + } + + if (!icon_cache->want_fallback && + icon_cache->origin == USING_FALLBACK_ICON) + { + /* Get rid of current icon */ + clear_icon_cache (icon_cache, FALSE); + + return TRUE; + } + + /* found nothing new */ + return FALSE; +} diff --git a/src/core/iconcache.h b/src/core/iconcache.h new file mode 100644 index 00000000..85ea9c66 --- /dev/null +++ b/src/core/iconcache.h @@ -0,0 +1,79 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window icons */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * 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. + */ + +#ifndef META_ICON_CACHE_H +#define META_ICON_CACHE_H + +#include "screen-private.h" + +typedef struct _MetaIconCache MetaIconCache; + +typedef enum +{ + /* These MUST be in ascending order of preference; + * i.e. if we get _NET_WM_ICON and already have + * WM_HINTS, we prefer _NET_WM_ICON + */ + USING_NO_ICON, + USING_FALLBACK_ICON, + USING_KWM_WIN_ICON, + USING_WM_HINTS, + USING_NET_WM_ICON +} IconOrigin; + +struct _MetaIconCache +{ + int origin; + Pixmap prev_pixmap; + Pixmap prev_mask; + guint want_fallback : 1; + /* TRUE if these props have changed */ + guint wm_hints_dirty : 1; + guint kwm_win_icon_dirty : 1; + guint net_wm_icon_dirty : 1; +}; + +void meta_icon_cache_init (MetaIconCache *icon_cache); +void meta_icon_cache_free (MetaIconCache *icon_cache); +void meta_icon_cache_property_changed (MetaIconCache *icon_cache, + MetaDisplay *display, + Atom atom); +gboolean meta_icon_cache_get_icon_invalidated (MetaIconCache *icon_cache); + +gboolean meta_read_icons (MetaScreen *screen, + Window xwindow, + MetaIconCache *icon_cache, + Pixmap wm_hints_pixmap, + Pixmap wm_hints_mask, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height); + +#endif + + + + diff --git a/src/core/keybindings.c b/src/core/keybindings.c new file mode 100644 index 00000000..b9371c85 --- /dev/null +++ b/src/core/keybindings.c @@ -0,0 +1,3352 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco Keybindings */ +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 Red Hat Inc. + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#define _GNU_SOURCE +#define _SVID_SOURCE /* for putenv() */ + +#include <config.h> +#include "keybindings.h" +#include "workspace.h" +#include "errors.h" +#include "edge-resistance.h" +#include "ui.h" +#include "frame-private.h" +#include "place.h" +#include "prefs.h" +#include "effects.h" +#include "util.h" + +#include <X11/keysym.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#ifdef HAVE_XKB +#include <X11/XKBlib.h> +#endif + +static gboolean all_bindings_disabled = FALSE; + +typedef void (* MetaKeyHandlerFunc) (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding); + +/* Prototypes for handlers */ +#define keybind(name, handler, param, flags, stroke, description) \ +static void \ +handler (MetaDisplay *display,\ + MetaScreen *screen,\ + MetaWindow *window,\ + XEvent *event,\ + MetaKeyBinding *binding); +#include "all-keybindings.h" +#undef keybind + +/* These can't be bound to anything, but they are used to handle + * various other events. TODO: Possibly we should include them as event + * handler functions and have some kind of flag to say they're unbindable. + */ + +static void handle_workspace_switch (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding); + +static gboolean process_mouse_move_resize_grab (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym); + +static gboolean process_keyboard_move_grab (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym); + +static gboolean process_keyboard_resize_grab (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym); + +static gboolean process_tab_grab (MetaDisplay *display, + MetaScreen *screen, + XEvent *event, + KeySym keysym); + +static gboolean process_workspace_switch_grab (MetaDisplay *display, + MetaScreen *screen, + XEvent *event, + KeySym keysym); + +static void regrab_key_bindings (MetaDisplay *display); + +typedef struct +{ + const char *name; + MetaKeyHandlerFunc func; + gint data, flags; +} MetaKeyHandler; + +struct _MetaKeyBinding +{ + const char *name; + KeySym keysym; + KeyCode keycode; + unsigned int mask; + MetaVirtualModifier modifiers; + const MetaKeyHandler *handler; +}; + +#define keybind(name, handler, param, flags, stroke, description) \ + { #name, handler, param, flags }, +static const MetaKeyHandler key_handlers[] = { +#include "all-keybindings.h" + { NULL, NULL, 0, 0 } +}; +#undef keybind + +static void +reload_keymap (MetaDisplay *display) +{ + if (display->keymap) + meta_XFree (display->keymap); + + display->keymap = XGetKeyboardMapping (display->xdisplay, + display->min_keycode, + display->max_keycode - + display->min_keycode + 1, + &display->keysyms_per_keycode); +} + +static void +reload_modmap (MetaDisplay *display) +{ + XModifierKeymap *modmap; + int map_size; + int i; + + if (display->modmap) + XFreeModifiermap (display->modmap); + + modmap = XGetModifierMapping (display->xdisplay); + display->modmap = modmap; + + display->ignored_modifier_mask = 0; + + /* Multiple bits may get set in each of these */ + display->num_lock_mask = 0; + display->scroll_lock_mask = 0; + display->meta_mask = 0; + display->hyper_mask = 0; + display->super_mask = 0; + + /* there are 8 modifiers, and the first 3 are shift, shift lock, + * and control + */ + map_size = 8 * modmap->max_keypermod; + i = 3 * modmap->max_keypermod; + while (i < map_size) + { + /* get the key code at this point in the map, + * see if its keysym is one we're interested in + */ + int keycode = modmap->modifiermap[i]; + + if (keycode >= display->min_keycode && + keycode <= display->max_keycode) + { + int j = 0; + KeySym *syms = display->keymap + + (keycode - display->min_keycode) * display->keysyms_per_keycode; + + while (j < display->keysyms_per_keycode) + { + if (syms[j] != 0) + { + const char *str; + + str = XKeysymToString (syms[j]); + meta_topic (META_DEBUG_KEYBINDINGS, + "Keysym %s bound to modifier 0x%x\n", + str ? str : "none", + (1 << ( i / modmap->max_keypermod))); + } + + if (syms[j] == XK_Num_Lock) + { + /* Mod1Mask is 1 << 3 for example, i.e. the + * fourth modifier, i / keyspermod is the modifier + * index + */ + + display->num_lock_mask |= (1 << ( i / modmap->max_keypermod)); + } + else if (syms[j] == XK_Scroll_Lock) + { + display->scroll_lock_mask |= (1 << ( i / modmap->max_keypermod)); + } + else if (syms[j] == XK_Super_L || + syms[j] == XK_Super_R) + { + display->super_mask |= (1 << ( i / modmap->max_keypermod)); + } + else if (syms[j] == XK_Hyper_L || + syms[j] == XK_Hyper_R) + { + display->hyper_mask |= (1 << ( i / modmap->max_keypermod)); + } + else if (syms[j] == XK_Meta_L || + syms[j] == XK_Meta_R) + { + display->meta_mask |= (1 << ( i / modmap->max_keypermod)); + } + + ++j; + } + } + + ++i; + } + + display->ignored_modifier_mask = (display->num_lock_mask | + display->scroll_lock_mask | + LockMask); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Ignoring modmask 0x%x num lock 0x%x scroll lock 0x%x hyper 0x%x super 0x%x meta 0x%x\n", + display->ignored_modifier_mask, + display->num_lock_mask, + display->scroll_lock_mask, + display->hyper_mask, + display->super_mask, + display->meta_mask); +} + +static void +reload_keycodes (MetaDisplay *display) +{ + meta_topic (META_DEBUG_KEYBINDINGS, + "Reloading keycodes for binding tables\n"); + + if (display->key_bindings) + { + int i; + + i = 0; + while (i < display->n_key_bindings) + { + if (display->key_bindings[i].keycode == 0) + display->key_bindings[i].keycode = XKeysymToKeycode ( + display->xdisplay, display->key_bindings[i].keysym); + + ++i; + } + } +} + +static void +reload_modifiers (MetaDisplay *display) +{ + meta_topic (META_DEBUG_KEYBINDINGS, + "Reloading keycodes for binding tables\n"); + + if (display->key_bindings) + { + int i; + + i = 0; + while (i < display->n_key_bindings) + { + meta_display_devirtualize_modifiers (display, + display->key_bindings[i].modifiers, + &display->key_bindings[i].mask); + + meta_topic (META_DEBUG_KEYBINDINGS, + " Devirtualized mods 0x%x -> 0x%x (%s)\n", + display->key_bindings[i].modifiers, + display->key_bindings[i].mask, + display->key_bindings[i].name); + + ++i; + } + } +} + + +static int +count_bindings (const MetaKeyPref *prefs, + int n_prefs) +{ + int i; + int count; + + count = 0; + i = 0; + while (i < n_prefs) + { + GSList *tmp = prefs[i].bindings; + + while (tmp) + { + MetaKeyCombo *combo = tmp->data; + + if (combo && (combo->keysym != None || combo->keycode != 0)) + { + count += 1; + + if (prefs[i].add_shift && + (combo->modifiers & META_VIRTUAL_SHIFT_MASK) == 0) + count += 1; + } + + tmp = tmp->next; + } + + ++i; + } + + return count; +} + +/* FIXME: replace this with a temporary hash */ +static const MetaKeyHandler* +find_handler (const MetaKeyHandler *handlers, + const char *name) +{ + const MetaKeyHandler *iter; + + iter = handlers; + while (iter->name) + { + if (strcmp (iter->name, name) == 0) + return iter; + + ++iter; + } + + return NULL; +} + +static void +rebuild_binding_table (MetaDisplay *display, + MetaKeyBinding **bindings_p, + int *n_bindings_p, + const MetaKeyPref *prefs, + int n_prefs) +{ + int n_bindings; + int src, dest; + + n_bindings = count_bindings (prefs, n_prefs); + g_free (*bindings_p); + *bindings_p = g_new0 (MetaKeyBinding, n_bindings); + + src = 0; + dest = 0; + while (src < n_prefs) + { + GSList *tmp = prefs[src].bindings; + + while (tmp) + { + MetaKeyCombo *combo = tmp->data; + + if (combo && (combo->keysym != None || combo->keycode != 0)) + { + const MetaKeyHandler *handler = find_handler (key_handlers, prefs[src].name); + + (*bindings_p)[dest].name = prefs[src].name; + (*bindings_p)[dest].handler = handler; + (*bindings_p)[dest].keysym = combo->keysym; + (*bindings_p)[dest].keycode = combo->keycode; + (*bindings_p)[dest].modifiers = combo->modifiers; + (*bindings_p)[dest].mask = 0; + + ++dest; + + if (prefs[src].add_shift && + (combo->modifiers & META_VIRTUAL_SHIFT_MASK) == 0) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Binding %s also needs Shift grabbed\n", + prefs[src].name); + + (*bindings_p)[dest].name = prefs[src].name; + (*bindings_p)[dest].handler = handler; + (*bindings_p)[dest].keysym = combo->keysym; + (*bindings_p)[dest].keycode = combo->keycode; + (*bindings_p)[dest].modifiers = combo->modifiers | + META_VIRTUAL_SHIFT_MASK; + (*bindings_p)[dest].mask = 0; + + ++dest; + } + } + + tmp = tmp->next; + } + + ++src; + } + + g_assert (dest == n_bindings); + + *n_bindings_p = dest; + + meta_topic (META_DEBUG_KEYBINDINGS, + " %d bindings in table\n", + *n_bindings_p); +} + +static void +rebuild_key_binding_table (MetaDisplay *display) +{ + const MetaKeyPref *prefs; + int n_prefs; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Rebuilding key binding table from preferences\n"); + + meta_prefs_get_key_bindings (&prefs, &n_prefs); + rebuild_binding_table (display, + &display->key_bindings, + &display->n_key_bindings, + prefs, n_prefs); +} + +static void +regrab_key_bindings (MetaDisplay *display) +{ + GSList *tmp; + GSList *windows; + + meta_error_trap_push (display); /* for efficiency push outer trap */ + + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *screen = tmp->data; + + meta_screen_ungrab_keys (screen); + meta_screen_grab_keys (screen); + + tmp = tmp->next; + } + + windows = meta_display_list_windows (display); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + meta_window_ungrab_keys (w); + meta_window_grab_keys (w); + + tmp = tmp->next; + } + meta_error_trap_pop (display, FALSE); + + g_slist_free (windows); +} + +static MetaKeyBindingAction +display_get_keybinding_action (MetaDisplay *display, + unsigned int keysym, + unsigned int keycode, + unsigned long mask) +{ + int i; + + i = display->n_key_bindings - 1; + while (i >= 0) + { + if (display->key_bindings[i].keysym == keysym && + display->key_bindings[i].keycode == keycode && + display->key_bindings[i].mask == mask) + { + return meta_prefs_get_keybinding_action (display->key_bindings[i].name); + } + + --i; + } + + return META_KEYBINDING_ACTION_NONE; +} + +void +meta_display_process_mapping_event (MetaDisplay *display, + XEvent *event) +{ + if (event->xmapping.request == MappingModifier) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Received MappingModifier event, will reload modmap and redo keybindings\n"); + + reload_modmap (display); + + reload_modifiers (display); + + regrab_key_bindings (display); + } + else if (event->xmapping.request == MappingKeyboard) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Received MappingKeyboard event, will reload keycodes and redo keybindings\n"); + + reload_keymap (display); + reload_modmap (display); + + reload_keycodes (display); + + regrab_key_bindings (display); + } +} + +static void +bindings_changed_callback (MetaPreference pref, + void *data) +{ + MetaDisplay *display; + + display = data; + + switch (pref) + { + case META_PREF_KEYBINDINGS: + rebuild_key_binding_table (display); + reload_keycodes (display); + reload_modifiers (display); + regrab_key_bindings (display); + break; + default: + break; + } +} + + +void +meta_display_init_keys (MetaDisplay *display) +{ + /* Keybindings */ + display->keymap = NULL; + display->keysyms_per_keycode = 0; + display->modmap = NULL; + display->min_keycode = 0; + display->max_keycode = 0; + display->ignored_modifier_mask = 0; + display->num_lock_mask = 0; + display->scroll_lock_mask = 0; + display->hyper_mask = 0; + display->super_mask = 0; + display->meta_mask = 0; + display->key_bindings = NULL; + display->n_key_bindings = 0; + + XDisplayKeycodes (display->xdisplay, + &display->min_keycode, + &display->max_keycode); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Display has keycode range %d to %d\n", + display->min_keycode, + display->max_keycode); + + reload_keymap (display); + reload_modmap (display); + + rebuild_key_binding_table (display); + + reload_keycodes (display); + reload_modifiers (display); + + /* Keys are actually grabbed in meta_screen_grab_keys() */ + + meta_prefs_add_listener (bindings_changed_callback, display); +} + +void +meta_display_shutdown_keys (MetaDisplay *display) +{ + /* Note that display->xdisplay is invalid in this function */ + + meta_prefs_remove_listener (bindings_changed_callback, display); + + if (display->keymap) + meta_XFree (display->keymap); + + if (display->modmap) + XFreeModifiermap (display->modmap); + g_free (display->key_bindings); +} + +static const char* +keysym_name (int keysym) +{ + const char *name; + + name = XKeysymToString (keysym); + if (name == NULL) + name = "(unknown)"; + + return name; +} + +/* Grab/ungrab, ignoring all annoying modifiers like NumLock etc. */ +static void +meta_change_keygrab (MetaDisplay *display, + Window xwindow, + gboolean grab, + int keysym, + unsigned int keycode, + int modmask) +{ + unsigned int ignored_mask; + + /* Grab keycode/modmask, together with + * all combinations of ignored modifiers. + * X provides no better way to do this. + */ + + meta_topic (META_DEBUG_KEYBINDINGS, + "%s keybinding %s keycode %d mask 0x%x on 0x%lx\n", + grab ? "Grabbing" : "Ungrabbing", + keysym_name (keysym), keycode, + modmask, xwindow); + + /* efficiency, avoid so many XSync() */ + meta_error_trap_push (display); + + ignored_mask = 0; + while (ignored_mask <= display->ignored_modifier_mask) + { + if (ignored_mask & ~(display->ignored_modifier_mask)) + { + /* Not a combination of ignored modifiers + * (it contains some non-ignored modifiers) + */ + ++ignored_mask; + continue; + } + + if (meta_is_debugging ()) + meta_error_trap_push_with_return (display); + if (grab) + XGrabKey (display->xdisplay, keycode, + modmask | ignored_mask, + xwindow, + True, + GrabModeAsync, GrabModeSync); + else + XUngrabKey (display->xdisplay, keycode, + modmask | ignored_mask, + xwindow); + + if (meta_is_debugging ()) + { + int result; + + result = meta_error_trap_pop_with_return (display, FALSE); + + if (grab && result != Success) + { + if (result == BadAccess) + meta_warning (_("Some other program is already using the key %s with modifiers %x as a binding\n"), keysym_name (keysym), modmask | ignored_mask); + else + meta_topic (META_DEBUG_KEYBINDINGS, + "Failed to grab key %s with modifiers %x\n", + keysym_name (keysym), modmask | ignored_mask); + } + } + + ++ignored_mask; + } + + meta_error_trap_pop (display, FALSE); +} + +static void +meta_grab_key (MetaDisplay *display, + Window xwindow, + int keysym, + unsigned int keycode, + int modmask) +{ + meta_change_keygrab (display, xwindow, TRUE, keysym, keycode, modmask); +} + +static void +grab_keys (MetaKeyBinding *bindings, + int n_bindings, + MetaDisplay *display, + Window xwindow, + gboolean binding_per_window) +{ + int i; + + g_assert (n_bindings == 0 || bindings != NULL); + + meta_error_trap_push (display); + + i = 0; + while (i < n_bindings) + { + if (!!binding_per_window == + !!(bindings[i].handler->flags & BINDING_PER_WINDOW) && + bindings[i].keycode != 0) + { + meta_grab_key (display, xwindow, + bindings[i].keysym, + bindings[i].keycode, + bindings[i].mask); + } + + ++i; + } + + meta_error_trap_pop (display, FALSE); +} + +static void +ungrab_all_keys (MetaDisplay *display, + Window xwindow) +{ + if (meta_is_debugging ()) + meta_error_trap_push_with_return (display); + else + meta_error_trap_push (display); + + XUngrabKey (display->xdisplay, AnyKey, AnyModifier, + xwindow); + + if (meta_is_debugging ()) + { + int result; + + result = meta_error_trap_pop_with_return (display, FALSE); + + if (result != Success) + meta_topic (META_DEBUG_KEYBINDINGS, + "Ungrabbing all keys on 0x%lx failed\n", xwindow); + } + else + meta_error_trap_pop (display, FALSE); +} + +void +meta_screen_grab_keys (MetaScreen *screen) +{ + if (screen->all_keys_grabbed) + return; + + if (screen->keys_grabbed) + return; + + grab_keys (screen->display->key_bindings, + screen->display->n_key_bindings, + screen->display, screen->xroot, + FALSE); + + screen->keys_grabbed = TRUE; +} + +void +meta_screen_ungrab_keys (MetaScreen *screen) +{ + if (screen->keys_grabbed) + { + ungrab_all_keys (screen->display, screen->xroot); + screen->keys_grabbed = FALSE; + } +} + +void +meta_window_grab_keys (MetaWindow *window) +{ + if (window->all_keys_grabbed) + return; + + if (window->type == META_WINDOW_DOCK) + { + if (window->keys_grabbed) + ungrab_all_keys (window->display, window->xwindow); + window->keys_grabbed = FALSE; + return; + } + + if (window->keys_grabbed) + { + if (window->frame && !window->grab_on_frame) + ungrab_all_keys (window->display, window->xwindow); + else if (window->frame == NULL && + window->grab_on_frame) + ; /* continue to regrab on client window */ + else + return; /* already all good */ + } + + grab_keys (window->display->key_bindings, + window->display->n_key_bindings, + window->display, + window->frame ? window->frame->xwindow : window->xwindow, + TRUE); + + window->keys_grabbed = TRUE; + window->grab_on_frame = window->frame != NULL; +} + +void +meta_window_ungrab_keys (MetaWindow *window) +{ + if (window->keys_grabbed) + { + if (window->grab_on_frame && + window->frame != NULL) + ungrab_all_keys (window->display, + window->frame->xwindow); + else if (!window->grab_on_frame) + ungrab_all_keys (window->display, + window->xwindow); + + window->keys_grabbed = FALSE; + } +} + +#ifdef WITH_VERBOSE_MODE +static const char* +grab_status_to_string (int status) +{ + switch (status) + { + case AlreadyGrabbed: + return "AlreadyGrabbed"; + case GrabSuccess: + return "GrabSuccess"; + case GrabNotViewable: + return "GrabNotViewable"; + case GrabFrozen: + return "GrabFrozen"; + case GrabInvalidTime: + return "GrabInvalidTime"; + default: + return "(unknown)"; + } +} +#endif /* WITH_VERBOSE_MODE */ + +static gboolean +grab_keyboard (MetaDisplay *display, + Window xwindow, + guint32 timestamp) +{ + int result; + int grab_status; + + /* Grab the keyboard, so we get key releases and all key + * presses + */ + meta_error_trap_push_with_return (display); + + grab_status = XGrabKeyboard (display->xdisplay, + xwindow, True, + GrabModeAsync, GrabModeAsync, + timestamp); + + if (grab_status != GrabSuccess) + { + meta_error_trap_pop_with_return (display, TRUE); + meta_topic (META_DEBUG_KEYBINDINGS, + "XGrabKeyboard() returned failure status %s time %u\n", + grab_status_to_string (grab_status), + timestamp); + return FALSE; + } + else + { + result = meta_error_trap_pop_with_return (display, TRUE); + if (result != Success) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "XGrabKeyboard() resulted in an error\n"); + return FALSE; + } + } + + meta_topic (META_DEBUG_KEYBINDINGS, "Grabbed all keys\n"); + + return TRUE; +} + +static void +ungrab_keyboard (MetaDisplay *display, guint32 timestamp) +{ + meta_error_trap_push (display); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Ungrabbing keyboard with timestamp %u\n", + timestamp); + XUngrabKeyboard (display->xdisplay, timestamp); + meta_error_trap_pop (display, FALSE); +} + +gboolean +meta_screen_grab_all_keys (MetaScreen *screen, guint32 timestamp) +{ + gboolean retval; + + if (screen->all_keys_grabbed) + return FALSE; + + if (screen->keys_grabbed) + meta_screen_ungrab_keys (screen); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Grabbing all keys on RootWindow\n"); + retval = grab_keyboard (screen->display, screen->xroot, timestamp); + if (retval) + screen->all_keys_grabbed = TRUE; + else + meta_screen_grab_keys (screen); + + return retval; +} + +void +meta_screen_ungrab_all_keys (MetaScreen *screen, guint32 timestamp) +{ + if (screen->all_keys_grabbed) + { + ungrab_keyboard (screen->display, timestamp); + + screen->all_keys_grabbed = FALSE; + screen->keys_grabbed = FALSE; + + /* Re-establish our standard bindings */ + meta_screen_grab_keys (screen); + } +} + +gboolean +meta_window_grab_all_keys (MetaWindow *window, + guint32 timestamp) +{ + Window grabwindow; + gboolean retval; + + if (window->all_keys_grabbed) + return FALSE; + + if (window->keys_grabbed) + meta_window_ungrab_keys (window); + + /* Make sure the window is focused, otherwise the grab + * won't do a lot of good. + */ + meta_topic (META_DEBUG_FOCUS, + "Focusing %s because we're grabbing all its keys\n", + window->desc); + meta_window_focus (window, timestamp); + + grabwindow = window->frame ? window->frame->xwindow : window->xwindow; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Grabbing all keys on window %s\n", window->desc); + retval = grab_keyboard (window->display, grabwindow, timestamp); + if (retval) + { + window->keys_grabbed = FALSE; + window->all_keys_grabbed = TRUE; + window->grab_on_frame = window->frame != NULL; + } + + return retval; +} + +void +meta_window_ungrab_all_keys (MetaWindow *window, guint32 timestamp) +{ + if (window->all_keys_grabbed) + { + ungrab_keyboard (window->display, timestamp); + + window->grab_on_frame = FALSE; + window->all_keys_grabbed = FALSE; + window->keys_grabbed = FALSE; + + /* Re-establish our standard bindings */ + meta_window_grab_keys (window); + } +} + +static gboolean +is_modifier (MetaDisplay *display, + unsigned int keycode) +{ + int i; + int map_size; + gboolean retval = FALSE; + + g_assert (display->modmap); + + map_size = 8 * display->modmap->max_keypermod; + i = 0; + while (i < map_size) + { + if (keycode == display->modmap->modifiermap[i]) + { + retval = TRUE; + break; + } + ++i; + } + + return retval; +} + +/* Indexes: + * shift = 0 + * lock = 1 + * control = 2 + * mod1 = 3 + * mod2 = 4 + * mod3 = 5 + * mod4 = 6 + * mod5 = 7 + */ + +static gboolean +is_specific_modifier (MetaDisplay *display, + unsigned int keycode, + unsigned int mask) +{ + int i; + int end; + gboolean retval = FALSE; + int mod_index; + + g_assert (display->modmap); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Checking whether code 0x%x is bound to modifier 0x%x\n", + keycode, mask); + + mod_index = 0; + mask = mask >> 1; + while (mask != 0) + { + mod_index += 1; + mask = mask >> 1; + } + + meta_topic (META_DEBUG_KEYBINDINGS, + "Modifier has index %d\n", mod_index); + + end = (mod_index + 1) * display->modmap->max_keypermod; + i = mod_index * display->modmap->max_keypermod; + while (i < end) + { + if (keycode == display->modmap->modifiermap[i]) + { + retval = TRUE; + break; + } + ++i; + } + + return retval; +} + +static unsigned int +get_primary_modifier (MetaDisplay *display, + unsigned int entire_binding_mask) +{ + /* The idea here is to see if the "main" modifier + * for Alt+Tab has been pressed/released. So if the binding + * is Alt+Shift+Tab then releasing Alt is the thing that + * ends the operation. It's pretty random how we order + * these. + */ + unsigned int masks[] = { Mod5Mask, Mod4Mask, Mod3Mask, + Mod2Mask, Mod1Mask, ControlMask, + ShiftMask, LockMask }; + + int i; + + i = 0; + while (i < (int) G_N_ELEMENTS (masks)) + { + if (entire_binding_mask & masks[i]) + return masks[i]; + ++i; + } + + return 0; +} + +static gboolean +keycode_is_primary_modifier (MetaDisplay *display, + unsigned int keycode, + unsigned int entire_binding_mask) +{ + unsigned int primary_modifier; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Checking whether code 0x%x is the primary modifier of mask 0x%x\n", + keycode, entire_binding_mask); + + primary_modifier = get_primary_modifier (display, entire_binding_mask); + if (primary_modifier != 0) + return is_specific_modifier (display, keycode, primary_modifier); + else + return FALSE; +} + +static gboolean +primary_modifier_still_pressed (MetaDisplay *display, + unsigned int entire_binding_mask) +{ + unsigned int primary_modifier; + int x, y, root_x, root_y; + Window root, child; + guint mask; + MetaScreen *random_screen; + Window random_xwindow; + + primary_modifier = get_primary_modifier (display, entire_binding_mask); + + random_screen = display->screens->data; + random_xwindow = random_screen->no_focus_window; + XQueryPointer (display->xdisplay, + random_xwindow, /* some random window */ + &root, &child, + &root_x, &root_y, + &x, &y, + &mask); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Primary modifier 0x%x full grab mask 0x%x current state 0x%x\n", + primary_modifier, entire_binding_mask, mask); + + if ((mask & primary_modifier) == 0) + return FALSE; + else + return TRUE; +} + +/* now called from only one place, may be worth merging */ +static gboolean +process_event (MetaKeyBinding *bindings, + int n_bindings, + MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym, + gboolean on_window) +{ + int i; + + /* we used to have release-based bindings but no longer. */ + if (event->type == KeyRelease) + return FALSE; + + /* + * TODO: This would be better done with a hash table; + * it doesn't suit to use O(n) for such a common operation. + */ + for (i=0; i<n_bindings; i++) + { + const MetaKeyHandler *handler = bindings[i].handler; + + if ((!on_window && handler->flags & BINDING_PER_WINDOW) || + event->type != KeyPress || + bindings[i].keycode != event->xkey.keycode || + ((event->xkey.state & 0xff & ~(display->ignored_modifier_mask)) != + bindings[i].mask)) + continue; + + /* + * window must be non-NULL for on_window to be true, + * and so also window must be non-NULL if we get here and + * this is a BINDING_PER_WINDOW binding. + */ + + meta_topic (META_DEBUG_KEYBINDINGS, + "Binding keycode 0x%x mask 0x%x matches event 0x%x state 0x%x\n", + bindings[i].keycode, bindings[i].mask, + event->xkey.keycode, event->xkey.state); + + if (handler == NULL) + meta_bug ("Binding %s has no handler\n", bindings[i].name); + else + meta_topic (META_DEBUG_KEYBINDINGS, + "Running handler for %s\n", + bindings[i].name); + + /* Global keybindings count as a let-the-terminal-lose-focus + * due to new window mapping until the user starts + * interacting with the terminal again. + */ + display->allow_terminal_deactivation = TRUE; + + (* handler->func) (display, screen, + bindings[i].handler->flags & BINDING_PER_WINDOW? window: NULL, + event, + &bindings[i]); + return TRUE; + } + + meta_topic (META_DEBUG_KEYBINDINGS, + "No handler found for this event in this binding table\n"); + return FALSE; +} + +/* Handle a key event. May be called recursively: some key events cause + * grabs to be ended and then need to be processed again in their own + * right. This cannot cause infinite recursion because we never call + * ourselves when there wasn't a grab, and we always clear the grab + * first; the invariant is enforced using an assertion. See #112560. + * FIXME: We need to prove there are no race conditions here. + * FIXME: Does it correctly handle alt-Tab being followed by another + * grabbing keypress without letting go of alt? + * FIXME: An iterative solution would probably be simpler to understand + * (and help us solve the other fixmes). + */ +void +meta_display_process_key_event (MetaDisplay *display, + MetaWindow *window, + XEvent *event) +{ + KeySym keysym; + gboolean keep_grab; + gboolean all_keys_grabbed; + const char *str; + MetaScreen *screen; + + XAllowEvents (display->xdisplay, + all_bindings_disabled ? ReplayKeyboard : AsyncKeyboard, + event->xkey.time); + if (all_bindings_disabled) + return; + + /* if key event was on root window, we have a shortcut */ + screen = meta_display_screen_for_root (display, event->xkey.window); + + /* else round-trip to server */ + if (screen == NULL) + screen = meta_display_screen_for_xwindow (display, + event->xany.window); + + if (screen == NULL) + return; /* event window is destroyed */ + + /* ignore key events on popup menus and such. */ + if (window == NULL && + meta_ui_window_is_widget (screen->ui, event->xany.window)) + return; + + /* window may be NULL */ + + keysym = XKeycodeToKeysym (display->xdisplay, event->xkey.keycode, 0); + + str = XKeysymToString (keysym); + + /* was topic */ + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing key %s event, keysym: %s state: 0x%x window: %s\n", + event->type == KeyPress ? "press" : "release", + str ? str : "none", event->xkey.state, + window ? window->desc : "(no window)"); + + keep_grab = TRUE; + all_keys_grabbed = window ? window->all_keys_grabbed : screen->all_keys_grabbed; + if (all_keys_grabbed) + { + if (display->grab_op == META_GRAB_OP_NONE) + return; + /* If we get here we have a global grab, because + * we're in some special keyboard mode such as window move + * mode. + */ + if (window ? (window == display->grab_window) : + (screen == display->grab_screen)) + { + switch (display->grab_op) + { + case META_GRAB_OP_MOVING: + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_E: + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for mouse-only move/resize\n"); + g_assert (window != NULL); + keep_grab = process_mouse_move_resize_grab (display, screen, + window, event, keysym); + break; + + case META_GRAB_OP_KEYBOARD_MOVING: + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for keyboard move\n"); + g_assert (window != NULL); + keep_grab = process_keyboard_move_grab (display, screen, + window, event, keysym); + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for keyboard resize\n"); + g_assert (window != NULL); + keep_grab = process_keyboard_resize_grab (display, screen, + window, event, keysym); + break; + + case META_GRAB_OP_KEYBOARD_TABBING_NORMAL: + case META_GRAB_OP_KEYBOARD_TABBING_DOCK: + case META_GRAB_OP_KEYBOARD_TABBING_GROUP: + case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL: + case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK: + case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP: + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for keyboard tabbing/cycling\n"); + keep_grab = process_tab_grab (display, screen, event, keysym); + break; + + case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING: + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for keyboard workspace switching\n"); + keep_grab = process_workspace_switch_grab (display, screen, event, keysym); + break; + + default: + break; + } + } + if (!keep_grab) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending grab op %u on key event sym %s\n", + display->grab_op, XKeysymToString (keysym)); + meta_display_end_grab_op (display, event->xkey.time); + return; + } + } + /* Do the normal keybindings */ + process_event (display->key_bindings, + display->n_key_bindings, + display, screen, window, event, keysym, + !all_keys_grabbed && window); +} + +static gboolean +process_mouse_move_resize_grab (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym) +{ + /* don't care about releases, but eat them, don't end grab */ + if (event->type == KeyRelease) + return TRUE; + + if (keysym == XK_Escape) + { + /* End move or resize and restore to original state. If the + * window was a maximized window that had been "shaken loose" we + * need to remaximize it. In normal cases, we need to do a + * moveresize now to get the position back to the original. In + * wireframe mode, we just need to set grab_was_cancelled to tru + * to avoid avoid moveresizing to the position of the wireframe. + */ + if (window->shaken_loose) + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + else if (!display->grab_wireframe_active) + meta_window_move_resize (display->grab_window, + TRUE, + display->grab_initial_window_pos.x, + display->grab_initial_window_pos.y, + display->grab_initial_window_pos.width, + display->grab_initial_window_pos.height); + else + display->grab_was_cancelled = TRUE; + + /* End grab */ + return FALSE; + } + + return TRUE; +} + +static gboolean +process_keyboard_move_grab (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym) +{ + gboolean handled; + int x, y; + int incr; + gboolean smart_snap; + + handled = FALSE; + + /* don't care about releases, but eat them, don't end grab */ + if (event->type == KeyRelease) + return TRUE; + + /* don't end grab on modifier key presses */ + if (is_modifier (display, event->xkey.keycode)) + return TRUE; + + if (display->grab_wireframe_active) + { + x = display->grab_wireframe_rect.x; + y = display->grab_wireframe_rect.y; + } + else + { + meta_window_get_position (window, &x, &y); + } + + smart_snap = (event->xkey.state & ShiftMask) != 0; + +#define SMALL_INCREMENT 1 +#define NORMAL_INCREMENT 10 + + if (smart_snap) + incr = 1; + else if (event->xkey.state & ControlMask) + incr = SMALL_INCREMENT; + else + incr = NORMAL_INCREMENT; + + if (keysym == XK_Escape) + { + /* End move and restore to original state. If the window was a + * maximized window that had been "shaken loose" we need to + * remaximize it. In normal cases, we need to do a moveresize + * now to get the position back to the original. In wireframe + * mode, we just need to set grab_was_cancelled to tru to avoid + * avoid moveresizing to the position of the wireframe. + */ + if (window->shaken_loose) + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + else if (!display->grab_wireframe_active) + meta_window_move_resize (display->grab_window, + TRUE, + display->grab_initial_window_pos.x, + display->grab_initial_window_pos.y, + display->grab_initial_window_pos.width, + display->grab_initial_window_pos.height); + else + display->grab_was_cancelled = TRUE; + } + + /* When moving by increments, we still snap to edges if the move + * to the edge is smaller than the increment. This is because + * Shift + arrow to snap is sort of a hidden feature. This way + * people using just arrows shouldn't get too frustrated. + */ + switch (keysym) + { + case XK_KP_Home: + case XK_KP_Prior: + case XK_Up: + case XK_KP_Up: + y -= incr; + handled = TRUE; + break; + case XK_KP_End: + case XK_KP_Next: + case XK_Down: + case XK_KP_Down: + y += incr; + handled = TRUE; + break; + } + + switch (keysym) + { + case XK_KP_Home: + case XK_KP_End: + case XK_Left: + case XK_KP_Left: + x -= incr; + handled = TRUE; + break; + case XK_KP_Prior: + case XK_KP_Next: + case XK_Right: + case XK_KP_Right: + x += incr; + handled = TRUE; + break; + } + + if (handled) + { + MetaRectangle old_rect; + meta_topic (META_DEBUG_KEYBINDINGS, + "Computed new window location %d,%d due to keypress\n", + x, y); + + if (display->grab_wireframe_active) + old_rect = display->grab_wireframe_rect; + else + meta_window_get_client_root_coords (window, &old_rect); + + meta_window_edge_resistance_for_move (window, + old_rect.x, + old_rect.y, + &x, + &y, + NULL, + smart_snap, + TRUE); + + if (display->grab_wireframe_active) + { + meta_window_update_wireframe (window, x, y, + display->grab_wireframe_rect.width, + display->grab_wireframe_rect.height); + } + else + { + meta_window_move (window, TRUE, x, y); + } + + meta_window_update_keyboard_move (window); + } + + return handled; +} + +static gboolean +process_keyboard_resize_grab_op_change (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym) +{ + gboolean handled; + + handled = FALSE; + switch (display->grab_op) + { + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + switch (keysym) + { + case XK_Up: + case XK_KP_Up: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + handled = TRUE; + break; + case XK_Down: + case XK_KP_Down: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + handled = TRUE; + break; + case XK_Left: + case XK_KP_Left: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + handled = TRUE; + break; + case XK_Right: + case XK_KP_Right: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_S: + switch (keysym) + { + case XK_Left: + case XK_KP_Left: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + handled = TRUE; + break; + case XK_Right: + case XK_KP_Right: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_N: + switch (keysym) + { + case XK_Left: + case XK_KP_Left: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + handled = TRUE; + break; + case XK_Right: + case XK_KP_Right: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_W: + switch (keysym) + { + case XK_Up: + case XK_KP_Up: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + handled = TRUE; + break; + case XK_Down: + case XK_KP_Down: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_E: + switch (keysym) + { + case XK_Up: + case XK_KP_Up: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + handled = TRUE; + break; + case XK_Down: + case XK_KP_Down: + display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + break; + + default: + g_assert_not_reached (); + break; + } + + if (handled) + { + meta_window_update_keyboard_resize (window, TRUE); + return TRUE; + } + + return FALSE; +} + +static gboolean +process_keyboard_resize_grab (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + KeySym keysym) +{ + gboolean handled; + int height_inc; + int width_inc; + int width, height; + gboolean smart_snap; + int gravity; + + handled = FALSE; + + /* don't care about releases, but eat them, don't end grab */ + if (event->type == KeyRelease) + return TRUE; + + /* don't end grab on modifier key presses */ + if (is_modifier (display, event->xkey.keycode)) + return TRUE; + + if (keysym == XK_Escape) + { + /* End resize and restore to original state. If not in + * wireframe mode, we need to do a moveresize now to get the + * position back to the original. If we are in wireframe mode, + * we need to avoid moveresizing to the position of the + * wireframe. + */ + if (!display->grab_wireframe_active) + meta_window_move_resize (display->grab_window, + TRUE, + display->grab_initial_window_pos.x, + display->grab_initial_window_pos.y, + display->grab_initial_window_pos.width, + display->grab_initial_window_pos.height); + else + display->grab_was_cancelled = TRUE; + + return FALSE; + } + + if (process_keyboard_resize_grab_op_change (display, screen, window, + event, keysym)) + return TRUE; + + if (display->grab_wireframe_active) + { + width = display->grab_wireframe_rect.width; + height = display->grab_wireframe_rect.height; + } + else + { + width = window->rect.width; + height = window->rect.height; + } + + gravity = meta_resize_gravity_from_grab_op (display->grab_op); + + smart_snap = (event->xkey.state & ShiftMask) != 0; + +#define SMALL_INCREMENT 1 +#define NORMAL_INCREMENT 10 + + if (smart_snap) + { + height_inc = 1; + width_inc = 1; + } + else if (event->xkey.state & ControlMask) + { + width_inc = SMALL_INCREMENT; + height_inc = SMALL_INCREMENT; + } + else + { + width_inc = NORMAL_INCREMENT; + height_inc = NORMAL_INCREMENT; + } + + /* If this is a resize increment window, make the amount we resize + * the window by match that amount (well, unless snap resizing...) + */ + if (window->size_hints.width_inc > 1) + width_inc = window->size_hints.width_inc; + if (window->size_hints.height_inc > 1) + height_inc = window->size_hints.height_inc; + + switch (keysym) + { + case XK_Up: + case XK_KP_Up: + switch (gravity) + { + case NorthGravity: + case NorthWestGravity: + case NorthEastGravity: + /* Move bottom edge up */ + height -= height_inc; + break; + + case SouthGravity: + case SouthWestGravity: + case SouthEastGravity: + /* Move top edge up */ + height += height_inc; + break; + + case EastGravity: + case WestGravity: + case CenterGravity: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + case XK_Down: + case XK_KP_Down: + switch (gravity) + { + case NorthGravity: + case NorthWestGravity: + case NorthEastGravity: + /* Move bottom edge down */ + height += height_inc; + break; + + case SouthGravity: + case SouthWestGravity: + case SouthEastGravity: + /* Move top edge down */ + height -= height_inc; + break; + + case EastGravity: + case WestGravity: + case CenterGravity: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + case XK_Left: + case XK_KP_Left: + switch (gravity) + { + case EastGravity: + case SouthEastGravity: + case NorthEastGravity: + /* Move left edge left */ + width += width_inc; + break; + + case WestGravity: + case SouthWestGravity: + case NorthWestGravity: + /* Move right edge left */ + width -= width_inc; + break; + + case NorthGravity: + case SouthGravity: + case CenterGravity: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + case XK_Right: + case XK_KP_Right: + switch (gravity) + { + case EastGravity: + case SouthEastGravity: + case NorthEastGravity: + /* Move left edge right */ + width -= width_inc; + break; + + case WestGravity: + case SouthWestGravity: + case NorthWestGravity: + /* Move right edge right */ + width += width_inc; + break; + + case NorthGravity: + case SouthGravity: + case CenterGravity: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + default: + break; + } + + /* fixup hack (just paranoia, not sure it's required) */ + if (height < 1) + height = 1; + if (width < 1) + width = 1; + + if (handled) + { + MetaRectangle old_rect; + meta_topic (META_DEBUG_KEYBINDINGS, + "Computed new window size due to keypress: " + "%dx%d, gravity %s\n", + width, height, meta_gravity_to_string (gravity)); + + if (display->grab_wireframe_active) + old_rect = display->grab_wireframe_rect; + else + old_rect = window->rect; /* Don't actually care about x,y */ + + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_resize (window, + old_rect.width, + old_rect.height, + &width, + &height, + gravity, + NULL, + smart_snap, + TRUE); + + if (display->grab_wireframe_active) + { + MetaRectangle new_position; + meta_rectangle_resize_with_gravity (&display->grab_wireframe_rect, + &new_position, + gravity, + width, + height); + meta_window_update_wireframe (window, + new_position.x, + new_position.y, + new_position.width, + new_position.height); + } + else + { + /* We don't need to update unless the specified width and height + * are actually different from what we had before. + */ + if (window->rect.width != width || window->rect.height != height) + meta_window_resize_with_gravity (window, + TRUE, + width, + height, + gravity); + } + meta_window_update_keyboard_resize (window, FALSE); + } + + return handled; +} + +static gboolean +end_keyboard_grab (MetaDisplay *display, + unsigned int keycode) +{ +#ifdef HAVE_XKB + if (display->xkb_base_event_type > 0) + { + unsigned int primary_modifier; + XkbStateRec state; + + primary_modifier = get_primary_modifier (display, display->grab_mask); + + XkbGetState (display->xdisplay, XkbUseCoreKbd, &state); + + if (!(primary_modifier & state.mods)) + return TRUE; + } + else +#endif + { + if (keycode_is_primary_modifier (display, keycode, display->grab_mask)) + return TRUE; + } + + return FALSE; +} + +static gboolean +process_tab_grab (MetaDisplay *display, + MetaScreen *screen, + XEvent *event, + KeySym keysym) +{ + MetaKeyBindingAction action; + gboolean popup_not_showing; + gboolean backward; + gboolean key_used; + Window prev_xwindow; + MetaWindow *prev_window; + + if (screen != display->grab_screen) + return FALSE; + + g_return_val_if_fail (screen->tab_popup != NULL, FALSE); + + if (event->type == KeyRelease && + end_keyboard_grab (display, event->xkey.keycode)) + { + /* We're done, move to the new window. */ + Window target_xwindow; + MetaWindow *target_window; + + target_xwindow = + (Window) meta_ui_tab_popup_get_selected (screen->tab_popup); + target_window = + meta_display_lookup_x_window (display, target_xwindow); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending tab operation, primary modifier released\n"); + + if (target_window) + { + target_window->tab_unminimized = FALSE; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Activating target window\n"); + + meta_topic (META_DEBUG_FOCUS, "Activating %s due to tab popup " + "selection and turning mouse_mode off\n", + target_window->desc); + display->mouse_mode = FALSE; + meta_window_activate (target_window, event->xkey.time); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending grab early so we can focus the target window\n"); + meta_display_end_grab_op (display, event->xkey.time); + + return TRUE; /* we already ended the grab */ + } + + return FALSE; /* end grab */ + } + + /* don't care about other releases, but eat them, don't end grab */ + if (event->type == KeyRelease) + return TRUE; + + /* don't end grab on modifier key presses */ + if (is_modifier (display, event->xkey.keycode)) + return TRUE; + + prev_xwindow = (Window) meta_ui_tab_popup_get_selected (screen->tab_popup); + prev_window = meta_display_lookup_x_window (display, prev_xwindow); + action = display_get_keybinding_action (display, + keysym, + event->xkey.keycode, + display->grab_mask); + + /* Cancel when alt-Escape is pressed during using alt-Tab, and vice + * versa. + */ + switch (action) + { + case META_KEYBINDING_ACTION_CYCLE_PANELS: + case META_KEYBINDING_ACTION_CYCLE_WINDOWS: + case META_KEYBINDING_ACTION_CYCLE_PANELS_BACKWARD: + case META_KEYBINDING_ACTION_CYCLE_WINDOWS_BACKWARD: + /* CYCLE_* are traditionally Escape-based actions, + * and should cancel traditionally Tab-based ones. + */ + switch (display->grab_op) + { + case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL: + case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK: + /* carry on */ + break; + default: + return FALSE; + } + break; + case META_KEYBINDING_ACTION_SWITCH_PANELS: + case META_KEYBINDING_ACTION_SWITCH_WINDOWS: + case META_KEYBINDING_ACTION_SWITCH_PANELS_BACKWARD: + case META_KEYBINDING_ACTION_SWITCH_WINDOWS_BACKWARD: + /* SWITCH_* are traditionally Tab-based actions, + * and should cancel traditionally Escape-based ones. + */ + switch (display->grab_op) + { + case META_GRAB_OP_KEYBOARD_TABBING_NORMAL: + case META_GRAB_OP_KEYBOARD_TABBING_DOCK: + /* carry on */ + break; + default: + /* Also, we must re-lower and re-minimize whatever window + * we'd previously raised and unminimized. + */ + meta_stack_set_positions (screen->stack, + screen->display->grab_old_window_stacking); + if (prev_window && prev_window->tab_unminimized) + { + meta_window_minimize (prev_window); + prev_window->tab_unminimized = FALSE; + } + return FALSE; + } + break; + case META_KEYBINDING_ACTION_CYCLE_GROUP: + case META_KEYBINDING_ACTION_CYCLE_GROUP_BACKWARD: + case META_KEYBINDING_ACTION_SWITCH_GROUP: + case META_KEYBINDING_ACTION_SWITCH_GROUP_BACKWARD: + switch (display->grab_op) + { + case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP: + case META_GRAB_OP_KEYBOARD_TABBING_GROUP: + /* carry on */ + break; + default: + return FALSE; + } + + break; + default: + break; + } + + /* !! TO HERE !! + * alt-f6 during alt-{Tab,Escape} does not end the grab + * but does change the grab op (and redraws the window, + * of course). + * See _{SWITCH,CYCLE}_GROUP.@@@ + */ + + popup_not_showing = FALSE; + key_used = FALSE; + backward = FALSE; + + switch (action) + { + case META_KEYBINDING_ACTION_CYCLE_PANELS: + case META_KEYBINDING_ACTION_CYCLE_WINDOWS: + case META_KEYBINDING_ACTION_CYCLE_GROUP: + popup_not_showing = TRUE; + key_used = TRUE; + break; + case META_KEYBINDING_ACTION_CYCLE_PANELS_BACKWARD: + case META_KEYBINDING_ACTION_CYCLE_WINDOWS_BACKWARD: + case META_KEYBINDING_ACTION_CYCLE_GROUP_BACKWARD: + popup_not_showing = TRUE; + key_used = TRUE; + backward = TRUE; + break; + case META_KEYBINDING_ACTION_SWITCH_PANELS: + case META_KEYBINDING_ACTION_SWITCH_WINDOWS: + case META_KEYBINDING_ACTION_SWITCH_GROUP: + key_used = TRUE; + break; + case META_KEYBINDING_ACTION_SWITCH_PANELS_BACKWARD: + case META_KEYBINDING_ACTION_SWITCH_WINDOWS_BACKWARD: + case META_KEYBINDING_ACTION_SWITCH_GROUP_BACKWARD: + key_used = TRUE; + backward = TRUE; + break; + default: + break; + } + + if (key_used) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Key pressed, moving tab focus in popup\n"); + + if (event->xkey.state & ShiftMask) + backward = !backward; + + if (backward) + meta_ui_tab_popup_backward (screen->tab_popup); + else + meta_ui_tab_popup_forward (screen->tab_popup); + + if (popup_not_showing) + { + /* We can't actually change window focus, due to the grab. + * but raise the window. + */ + Window target_xwindow; + MetaWindow *target_window; + + meta_stack_set_positions (screen->stack, + display->grab_old_window_stacking); + + target_xwindow = + (Window) meta_ui_tab_popup_get_selected (screen->tab_popup); + target_window = + meta_display_lookup_x_window (display, target_xwindow); + + if (prev_window && prev_window->tab_unminimized) + { + prev_window->tab_unminimized = FALSE; + meta_window_minimize (prev_window); + } + + if (target_window) + { + meta_window_raise (target_window); + target_window->tab_unminimized = target_window->minimized; + meta_window_unminimize (target_window); + } + } + } + else + { + /* end grab */ + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending tabbing/cycling, uninteresting key pressed\n"); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Syncing to old stack positions.\n"); + meta_stack_set_positions (screen->stack, + screen->display->grab_old_window_stacking); + + if (prev_window && prev_window->tab_unminimized) + { + meta_window_minimize (prev_window); + prev_window->tab_unminimized = FALSE; + } + } + + return key_used; +} + +static void +handle_switch_to_workspace (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *event_window, + XEvent *event, + MetaKeyBinding *binding) +{ + gint which = binding->handler->data; + MetaWorkspace *workspace; + + if (which < 0) + { + /* Negative workspace numbers are directions with respect to the + * current workspace. While we could insta-switch here by setting + * workspace to the result of meta_workspace_get_neighbor(), when + * people request a workspace switch to the left or right via + * the keyboard, they actually want a tab popup. So we should + * go there instead. + * + * Note that we're the only caller of that function, so perhaps + * we should merge with it. + */ + handle_workspace_switch (display, screen, event_window, event, binding); + return; + } + + workspace = meta_screen_get_workspace_by_index (screen, which); + + if (workspace) + { + meta_workspace_activate (workspace, event->xkey.time); + } + else + { + /* We could offer to create it I suppose */ + } +} + +static void +error_on_command (int command_index, + const char *command, + const char *message, + int screen_number, + guint32 timestamp) +{ + if (command_index < 0) + meta_warning ("Error on terminal command \"%s\": %s\n", command, message); + else + meta_warning ("Error on command %d \"%s\": %s\n", + command_index, command, message); + + /* + marco-dialog said: + + FIXME offer to change the value of the command's mateconf key + */ + + if (command && strcmp(command, "")!=0) + { + char *text = g_strdup_printf ( + /* Displayed when a keybinding which is + * supposed to launch a program fails. + */ + _("There was an error running " + "<tt>%s</tt>:\n\n%s"), + command, + message); + + meta_show_dialog ("--error", + text, + NULL, + screen_number, + NULL, NULL, 0, + NULL, NULL); + + g_free (text); + + } + else + { + meta_show_dialog ("--error", + message, + NULL, + screen_number, + NULL, NULL, 0, + NULL, NULL); + } +} + +static void +set_display_setup_func (void *data) +{ + const char *screen_name = data; + char *full; + + full = g_strdup_printf ("DISPLAY=%s", screen_name); + + putenv (full); + + /* do not free full, because putenv is lame */ +} + +static gboolean +meta_spawn_command_line_async_on_screen (const gchar *command_line, + MetaScreen *screen, + GError **error) +{ + gboolean retval; + gchar **argv = NULL; + + g_return_val_if_fail (command_line != NULL, FALSE); + + if (!g_shell_parse_argv (command_line, + NULL, &argv, + error)) + return FALSE; + + retval = g_spawn_async (NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + set_display_setup_func, + screen->screen_name, + NULL, + error); + g_strfreev (argv); + + return retval; +} + + +static void +handle_run_command (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + gint which = binding->handler->data; + const char *command; + GError *err; + + command = meta_prefs_get_command (which); + + if (command == NULL) + { + char *s; + + meta_topic (META_DEBUG_KEYBINDINGS, + "No command %d to run in response to keybinding press\n", + which); + + s = g_strdup_printf (_("No command %d has been defined.\n"), + which + 1); + error_on_command (which, NULL, s, screen->number, event->xkey.time); + g_free (s); + + return; + } + + err = NULL; + if (!meta_spawn_command_line_async_on_screen (command, screen, &err)) + { + error_on_command (which, command, err->message, screen->number, event->xkey.time); + + g_error_free (err); + } +} + + +static void +handle_maximize_vertically (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_resize_func) + { + if (window->maximized_vertically) + meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL); + else + meta_window_maximize (window, META_MAXIMIZE_VERTICAL); + } +} + +static void +handle_maximize_horizontally (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_resize_func) + { + if (window->maximized_horizontally) + meta_window_unmaximize (window, META_MAXIMIZE_HORIZONTAL); + else + meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL); + } +} + +/* Move a window to a corner; to_bottom/to_right are FALSE for the + * top or left edge, or TRUE for the bottom/right edge. xchange/ychange + * are FALSE if that dimension is not to be changed, TRUE otherwise. + * Together they describe which of the four corners, or four sides, + * is desired. + */ +static void +handle_move_to_corner_backend (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + gboolean xchange, + gboolean ychange, + gboolean to_right, + gboolean to_bottom) +{ + MetaRectangle work_area; + MetaRectangle outer; + int orig_x, orig_y; + int new_x, new_y; + int frame_width, frame_height; + + meta_window_get_work_area_all_xineramas (window, &work_area); + meta_window_get_outer_rect (window, &outer); + meta_window_get_position (window, &orig_x, &orig_y); + + frame_width = (window->frame ? window->frame->child_x : 0); + frame_height = (window->frame ? window->frame->child_y : 0); + + if (xchange) { + new_x = work_area.x + (to_right ? + (work_area.width + frame_width) - outer.width : + 0); + } else { + new_x = orig_x; + } + + if (ychange) { + new_y = work_area.y + (to_bottom ? + (work_area.height + frame_height) - outer.height : + 0); + } else { + new_y = orig_y; + } + + meta_window_move_resize (window, + FALSE, + new_x, + new_y, + window->rect.width, + window->rect.height); +} + +static void +handle_move_to_corner_nw (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, FALSE, FALSE); +} + +static void +handle_move_to_corner_ne (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, TRUE, FALSE); +} + +static void +handle_move_to_corner_sw (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, FALSE, TRUE); +} + +static void +handle_move_to_corner_se (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, TRUE, TRUE); +} + +static void +handle_move_to_side_n (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, FALSE, TRUE, FALSE, FALSE); +} + +static void +handle_move_to_side_s (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, FALSE, TRUE, FALSE, TRUE); +} + +static void +handle_move_to_side_e (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, TRUE, FALSE, TRUE, FALSE); +} + +static void +handle_move_to_side_w (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + handle_move_to_corner_backend (display, screen, window, TRUE, FALSE, FALSE, FALSE); +} + +static void +handle_move_to_center (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + MetaRectangle work_area; + MetaRectangle outer; + int orig_x, orig_y; + int frame_width, frame_height; + + meta_window_get_work_area_all_xineramas (window, &work_area); + meta_window_get_outer_rect (window, &outer); + meta_window_get_position (window, &orig_x, &orig_y); + + frame_width = (window->frame ? window->frame->child_x : 0); + frame_height = (window->frame ? window->frame->child_y : 0); + + meta_window_move_resize (window, + TRUE, + work_area.x + (work_area.width +frame_width -outer.width )/2, + work_area.y + (work_area.height+frame_height-outer.height)/2, + window->rect.width, + window->rect.height); +} + +static gboolean +process_workspace_switch_grab (MetaDisplay *display, + MetaScreen *screen, + XEvent *event, + KeySym keysym) +{ + MetaWorkspace *workspace; + + if (screen != display->grab_screen) + return FALSE; + + g_return_val_if_fail (screen->tab_popup != NULL, FALSE); + + if (event->type == KeyRelease && + end_keyboard_grab (display, event->xkey.keycode)) + { + /* We're done, move to the new workspace. */ + MetaWorkspace *target_workspace; + + target_workspace = + (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending workspace tab operation, primary modifier released\n"); + + if (target_workspace == screen->active_workspace) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending grab so we can focus on the target workspace\n"); + meta_display_end_grab_op (display, event->xkey.time); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Focusing default window on target workspace\n"); + + meta_workspace_focus_default_window (target_workspace, + NULL, + event->xkey.time); + + return TRUE; /* we already ended the grab */ + } + + /* Workspace switching should have already occurred on KeyPress */ + meta_warning ("target_workspace != active_workspace. Some other event must have occurred.\n"); + + return FALSE; /* end grab */ + } + + /* don't care about other releases, but eat them, don't end grab */ + if (event->type == KeyRelease) + return TRUE; + + /* don't end grab on modifier key presses */ + if (is_modifier (display, event->xkey.keycode)) + return TRUE; + + /* select the next workspace in the tabpopup */ + workspace = + (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup); + + if (workspace) + { + MetaWorkspace *target_workspace; + MetaKeyBindingAction action; + + action = display_get_keybinding_action (display, + keysym, + event->xkey.keycode, + display->grab_mask); + + switch (action) + { + case META_KEYBINDING_ACTION_WORKSPACE_UP: + target_workspace = meta_workspace_get_neighbor (workspace, + META_MOTION_UP); + break; + + case META_KEYBINDING_ACTION_WORKSPACE_DOWN: + target_workspace = meta_workspace_get_neighbor (workspace, + META_MOTION_DOWN); + break; + + case META_KEYBINDING_ACTION_WORKSPACE_LEFT: + target_workspace = meta_workspace_get_neighbor (workspace, + META_MOTION_LEFT); + break; + + case META_KEYBINDING_ACTION_WORKSPACE_RIGHT: + target_workspace = meta_workspace_get_neighbor (workspace, + META_MOTION_RIGHT); + break; + + default: + target_workspace = NULL; + break; + } + + if (target_workspace) + { + meta_ui_tab_popup_select (screen->tab_popup, + (MetaTabEntryKey) target_workspace); + meta_topic (META_DEBUG_KEYBINDINGS, + "Tab key pressed, moving tab focus in popup\n"); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Activating target workspace\n"); + + meta_workspace_activate (target_workspace, event->xkey.time); + + return TRUE; /* we already ended the grab */ + } + } + + /* end grab */ + meta_topic (META_DEBUG_KEYBINDINGS, + "Ending workspace tabbing & focusing default window; uninteresting key pressed\n"); + workspace = + (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup); + meta_workspace_focus_default_window (workspace, NULL, event->xkey.time); + return FALSE; +} + +static void +handle_show_desktop (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (screen->active_workspace->showing_desktop) + { + meta_screen_unshow_desktop (screen); + meta_workspace_focus_default_window (screen->active_workspace, + NULL, + event->xkey.time); + } + else + meta_screen_show_desktop (screen, event->xkey.time); +} + +static void +handle_panel (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + MetaKeyBindingAction action = binding->handler->data; + Atom action_atom; + XClientMessageEvent ev; + + action_atom = None; + switch (action) + { + /* FIXME: The numbers are wrong */ + case META_KEYBINDING_ACTION_PANEL_MAIN_MENU: + action_atom = display->atom__MATE_PANEL_ACTION_MAIN_MENU; + break; + case META_KEYBINDING_ACTION_PANEL_RUN_DIALOG: + action_atom = display->atom__MATE_PANEL_ACTION_RUN_DIALOG; + break; + default: + return; + } + + ev.type = ClientMessage; + ev.window = screen->xroot; + ev.message_type = display->atom__MATE_PANEL_ACTION; + ev.format = 32; + ev.data.l[0] = action_atom; + ev.data.l[1] = event->xkey.time; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Sending panel message with timestamp %lu, and turning mouse_mode " + "off due to keybinding press\n", event->xkey.time); + display->mouse_mode = FALSE; + + meta_error_trap_push (display); + + /* Release the grab for the panel before sending the event */ + XUngrabKeyboard (display->xdisplay, event->xkey.time); + + XSendEvent (display->xdisplay, + screen->xroot, + False, + StructureNotifyMask, + (XEvent*) &ev); + + meta_error_trap_pop (display, FALSE); +} + +static void +handle_activate_window_menu (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *event_window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (display->focus_window) + { + int x, y; + + meta_window_get_position (display->focus_window, + &x, &y); + + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + x += display->focus_window->rect.width; + + meta_window_show_menu (display->focus_window, + x, y, + 0, + event->xkey.time); + } +} + +static MetaGrabOp +tab_op_from_tab_type (MetaTabList type) +{ + switch (type) + { + case META_TAB_LIST_NORMAL: + return META_GRAB_OP_KEYBOARD_TABBING_NORMAL; + case META_TAB_LIST_DOCKS: + return META_GRAB_OP_KEYBOARD_TABBING_DOCK; + case META_TAB_LIST_GROUP: + return META_GRAB_OP_KEYBOARD_TABBING_GROUP; + } + + g_assert_not_reached (); + + return 0; +} + +static MetaGrabOp +cycle_op_from_tab_type (MetaTabList type) +{ + switch (type) + { + case META_TAB_LIST_NORMAL: + return META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL; + case META_TAB_LIST_DOCKS: + return META_GRAB_OP_KEYBOARD_ESCAPING_DOCK; + case META_TAB_LIST_GROUP: + return META_GRAB_OP_KEYBOARD_ESCAPING_GROUP; + } + + g_assert_not_reached (); + + return 0; +} + +static void +do_choose_window (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *event_window, + XEvent *event, + MetaKeyBinding *binding, + gboolean backward, + gboolean show_popup) +{ + MetaTabList type = binding->handler->data; + MetaWindow *initial_selection; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Tab list = %u show_popup = %d\n", type, show_popup); + + /* reverse direction if shift is down */ + if (event->xkey.state & ShiftMask) + backward = !backward; + + initial_selection = meta_display_get_tab_next (display, + type, + screen, + screen->active_workspace, + NULL, + backward); + + /* Note that focus_window may not be in the tab chain, but it's OK */ + if (initial_selection == NULL) + initial_selection = meta_display_get_tab_current (display, + type, screen, + screen->active_workspace); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Initially selecting window %s\n", + initial_selection ? initial_selection->desc : "(none)"); + + if (initial_selection != NULL) + { + if (binding->mask == 0) + { + /* If no modifiers, we can't do the "hold down modifier to keep + * moving" thing, so we just instaswitch by one window. + */ + meta_topic (META_DEBUG_FOCUS, + "Activating %s and turning off mouse_mode due to " + "switch/cycle windows with no modifiers\n", + initial_selection->desc); + display->mouse_mode = FALSE; + meta_window_activate (initial_selection, event->xkey.time); + } + else if (meta_display_begin_grab_op (display, + screen, + NULL, + show_popup ? + tab_op_from_tab_type (type) : + cycle_op_from_tab_type (type), + FALSE, + FALSE, + 0, + binding->mask, + event->xkey.time, + 0, 0)) + { + if (!primary_modifier_still_pressed (display, + binding->mask)) + { + /* This handles a race where modifier might be released + * before we establish the grab. must end grab + * prior to trying to focus a window. + */ + meta_topic (META_DEBUG_FOCUS, + "Ending grab, activating %s, and turning off " + "mouse_mode due to switch/cycle windows where " + "modifier was released prior to grab\n", + initial_selection->desc); + meta_display_end_grab_op (display, event->xkey.time); + display->mouse_mode = FALSE; + meta_window_activate (initial_selection, event->xkey.time); + } + else + { + meta_ui_tab_popup_select (screen->tab_popup, + (MetaTabEntryKey) initial_selection->xwindow); + + if (show_popup) + meta_ui_tab_popup_set_showing (screen->tab_popup, + TRUE); + else + { + meta_window_raise (initial_selection); + initial_selection->tab_unminimized = + initial_selection->minimized; + meta_window_unminimize (initial_selection); + } + } + } + } +} + +static void +handle_switch (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *event_window, + XEvent *event, + MetaKeyBinding *binding) +{ + gint backwards = binding->handler->flags & BINDING_IS_REVERSED; + + do_choose_window (display, screen, event_window, event, binding, + backwards, TRUE); +} + +static void +handle_cycle (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *event_window, + XEvent *event, + MetaKeyBinding *binding) +{ + gint backwards = binding->handler->flags & BINDING_IS_REVERSED; + + do_choose_window (display, screen, event_window, event, binding, + backwards, FALSE); +} + + +static void +handle_toggle_fullscreen (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->fullscreen) + meta_window_unmake_fullscreen (window); + else if (window->has_fullscreen_func) + meta_window_make_fullscreen (window); +} + +static void +handle_toggle_above (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->wm_state_above) + meta_window_unmake_above (window); + else + meta_window_make_above (window); +} + +static void +handle_toggle_maximized (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (META_WINDOW_MAXIMIZED (window)) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + else if (window->has_maximize_func) + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); +} + +static void +handle_maximize (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_maximize_func) + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); +} + +static void +handle_unmaximize (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->maximized_vertically || window->maximized_horizontally) + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); +} + +static void +handle_toggle_shaded (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->shaded) + meta_window_unshade (window, event->xkey.time); + else if (window->has_shade_func) + meta_window_shade (window, event->xkey.time); +} + +static void +handle_close (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_close_func) + meta_window_delete (window, event->xkey.time); +} + +static void +handle_minimize (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_minimize_func) + meta_window_minimize (window); +} + +static void +handle_begin_move (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_move_func) + { + meta_window_begin_grab_op (window, + META_GRAB_OP_KEYBOARD_MOVING, + FALSE, + event->xkey.time); + } +} + +static void +handle_begin_resize (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->has_resize_func) + { + meta_window_begin_grab_op (window, + META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN, + FALSE, + event->xkey.time); + } +} + +static void +handle_toggle_on_all_workspaces (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + if (window->on_all_workspaces) + meta_window_unstick (window); + else + meta_window_stick (window); +} + +static void +handle_move_to_workspace (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + gint which = binding->handler->data; + gboolean flip = (which < 0); + MetaWorkspace *workspace; + + /* If which is zero or positive, it's a workspace number, and the window + * should move to the workspace with that number. + * + * However, if it's negative, it's a direction with respect to the current + * position; it's expressed as a member of the MetaMotionDirection enum, + * all of whose members are negative. Such a change is called a flip. + */ + + if (window->always_sticky) + return; + + workspace = NULL; + if (flip) + { + workspace = meta_workspace_get_neighbor (screen->active_workspace, + which); + } + else + { + workspace = meta_screen_get_workspace_by_index (screen, which); + } + + if (workspace) + { + /* Activate second, so the window is never unmapped */ + meta_window_change_workspace (window, workspace); + if (flip) + { + meta_topic (META_DEBUG_FOCUS, + "Resetting mouse_mode to FALSE due to " + "handle_move_to_workspace() call with flip set.\n"); + workspace->screen->display->mouse_mode = FALSE; + meta_workspace_activate_with_focus (workspace, + window, + event->xkey.time); + } + } + else + { + /* We could offer to create it I suppose */ + } +} + +static void +handle_raise_or_lower (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + /* Get window at pointer */ + + MetaWindow *above = NULL; + + /* Check if top */ + if (meta_stack_get_top (window->screen->stack) == window) + { + meta_window_lower (window); + return; + } + + /* else check if windows in same layer are intersecting it */ + + above = meta_stack_get_above (window->screen->stack, window, TRUE); + + while (above) + { + MetaRectangle tmp, win_rect, above_rect; + + if (above->mapped) + { + meta_window_get_outer_rect (window, &win_rect); + meta_window_get_outer_rect (above, &above_rect); + + /* Check if obscured */ + if (meta_rectangle_intersect (&win_rect, &above_rect, &tmp)) + { + meta_window_raise (window); + return; + } + } + + above = meta_stack_get_above (window->screen->stack, above, TRUE); + } + + /* window is not obscured */ + meta_window_lower (window); +} + +static void +handle_raise (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + meta_window_raise (window); +} + +static void +handle_lower (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + meta_window_lower (window); +} + +static void +handle_workspace_switch (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + gint motion = binding->handler->data; + unsigned int grab_mask; + + g_assert (motion < 0); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Starting tab between workspaces, showing popup\n"); + + /* FIXME should we use binding->mask ? */ + grab_mask = event->xkey.state & ~(display->ignored_modifier_mask); + + if (meta_display_begin_grab_op (display, + screen, + NULL, + META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING, + FALSE, + FALSE, + 0, + grab_mask, + event->xkey.time, + 0, 0)) + { + MetaWorkspace *next; + gboolean grabbed_before_release; + + next = meta_workspace_get_neighbor (screen->active_workspace, motion); + g_assert (next); + + grabbed_before_release = primary_modifier_still_pressed (display, grab_mask); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Activating target workspace\n"); + + if (!grabbed_before_release) + { + /* end the grab right away, modifier possibly released + * before we could establish the grab and receive the + * release event. Must end grab before we can switch + * spaces. + */ + meta_display_end_grab_op (display, event->xkey.time); + } + + meta_workspace_activate (next, event->xkey.time); + + if (grabbed_before_release) + { + meta_ui_tab_popup_select (screen->tab_popup, (MetaTabEntryKey) next); + + /* only after selecting proper space */ + meta_ui_tab_popup_set_showing (screen->tab_popup, TRUE); + } + } +} + +static void +handle_set_spew_mark (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + meta_verbose ("-- MARK MARK MARK MARK --\n"); +} + +void +meta_set_keybindings_disabled (gboolean setting) +{ + all_bindings_disabled = setting; + meta_topic (META_DEBUG_KEYBINDINGS, + "Keybindings %s\n", all_bindings_disabled ? "disabled" : "enabled"); +} + +static void +handle_run_terminal (MetaDisplay *display, + MetaScreen *screen, + MetaWindow *window, + XEvent *event, + MetaKeyBinding *binding) +{ + const char *command; + GError *err; + + command = meta_prefs_get_terminal_command (); + + if (command == NULL) + { + char *s; + + meta_topic (META_DEBUG_KEYBINDINGS, + "No terminal command to run in response to " + "keybinding press\n"); + + s = g_strdup_printf (_("No terminal command has been defined.\n")); + error_on_command (-1, NULL, s, screen->number, event->xkey.time); + g_free (s); + + return; + } + + err = NULL; + if (!meta_spawn_command_line_async_on_screen (command, screen, &err)) + { + error_on_command (-1, command, err->message, screen->number, + event->xkey.time); + + g_error_free (err); + } +} diff --git a/src/core/keybindings.h b/src/core/keybindings.h new file mode 100644 index 00000000..618520b4 --- /dev/null +++ b/src/core/keybindings.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file keybindings.h Grab and ungrab keys, and process the key events + * + * Performs global X grabs on the keys we need to be told about, like + * the one to close a window. It also deals with incoming key events. + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * 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. + */ + +#ifndef META_KEYBINDINGS_H +#define META_KEYBINDINGS_H + +#include "display-private.h" +#include "window.h" + +void meta_display_init_keys (MetaDisplay *display); +void meta_display_shutdown_keys (MetaDisplay *display); +void meta_screen_grab_keys (MetaScreen *screen); +void meta_screen_ungrab_keys (MetaScreen *screen); +gboolean meta_screen_grab_all_keys (MetaScreen *screen, + guint32 timestamp); +void meta_screen_ungrab_all_keys (MetaScreen *screen, + guint32 timestamp); +void meta_window_grab_keys (MetaWindow *window); +void meta_window_ungrab_keys (MetaWindow *window); +gboolean meta_window_grab_all_keys (MetaWindow *window, + guint32 timestamp); +void meta_window_ungrab_all_keys (MetaWindow *window, + guint32 timestamp); +void meta_display_process_key_event (MetaDisplay *display, + MetaWindow *window, + XEvent *event); +void meta_set_keybindings_disabled (gboolean setting); +void meta_display_process_mapping_event (MetaDisplay *display, + XEvent *event); + +#endif + + + + diff --git a/src/core/main.c b/src/core/main.c new file mode 100644 index 00000000..38c69b0a --- /dev/null +++ b/src/core/main.c @@ -0,0 +1,673 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco main() */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file + * Program startup. + * Functions which parse the command-line arguments, create the display, + * kick everything off and then close down Marco when it's time to go. + */ + +/** + * \mainpage + * Marco - a boring window manager for the adult in you + * + * Many window managers are like Marshmallow Froot Loops; Marco + * is like Cheerios. + * + * The best way to get a handle on how the whole system fits together + * is discussed in doc/code-overview.txt; if you're looking for functions + * to investigate, read main(), meta_display_open(), and event_callback(). + */ + +#define _GNU_SOURCE +#define _SVID_SOURCE /* for putenv() and some signal-related functions */ + +#include <config.h> +#include "main.h" +#include "util.h" +#include "display-private.h" +#include "errors.h" +#include "ui.h" +#include "session.h" +#include "prefs.h" + +#include <glib-object.h> +#include <glib/gprintf.h> + +#include <stdlib.h> +#include <sys/types.h> +#include <wait.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <time.h> +#include <unistd.h> + +/** + * The exit code we'll return to our parent process when we eventually die. + */ +static MetaExitCode meta_exit_code = META_EXIT_SUCCESS; + +/** + * Handle on the main loop, so that we have an easy way of shutting Marco + * down. + */ +static GMainLoop *meta_main_loop = NULL; + +/** + * If set, Marco will spawn an identical copy of itself immediately + * before quitting. + */ +static gboolean meta_restart_after_quit = FALSE; + +static void prefs_changed_callback (MetaPreference pref, + gpointer data); + +/** + * Prints log messages. If Marco was compiled with backtrace support, + * also prints a backtrace (see meta_print_backtrace()). + * + * \param log_domain the domain the error occurred in (we ignore this) + * \param log_level the log level so that we can filter out less + * important messages + * \param message the message to log + * \param user_data arbitrary data (we ignore this) + */ +static void +log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + meta_warning ("Log level %d: %s\n", log_level, message); + meta_print_backtrace (); +} + +/** + * Prints the version notice. This is shown when Marco is called + * with the --version switch. + */ +static void +version (void) +{ + const int latest_year = 2009; + char yearbuffer[256]; + GDate date; + + /* this is all so the string to translate stays constant. + * see how much we love the translators. + */ + g_date_set_dmy (&date, 1, G_DATE_JANUARY, latest_year); + if (g_date_strftime (yearbuffer, sizeof (yearbuffer), "%Y", &date)==0) + /* didn't work? fall back to decimal representation */ + g_sprintf (yearbuffer, "%d", latest_year); + + g_print (_("marco %s\n" + "Copyright (C) 2001-%s Havoc Pennington, Red Hat, Inc., and others\n" + "This is free software; see the source for copying conditions.\n" + "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"), + VERSION, yearbuffer); + exit (0); +} + +/** + * Prints a list of which configure script options were used to + * build this copy of Marco. This is actually always called + * on startup, but it's all no-op unless we're in verbose mode + * (see meta_set_verbose). + */ +static void +meta_print_compilation_info (void) +{ +#ifdef HAVE_SHAPE + meta_verbose ("Compiled with shape extension\n"); +#else + meta_verbose ("Compiled without shape extension\n"); +#endif +#ifdef HAVE_XINERAMA + meta_topic (META_DEBUG_XINERAMA, "Compiled with Xinerama extension\n"); +#else + meta_topic (META_DEBUG_XINERAMA, "Compiled without Xinerama extension\n"); +#endif +#ifdef HAVE_XFREE_XINERAMA + meta_topic (META_DEBUG_XINERAMA, " (using XFree86 Xinerama)\n"); +#else + meta_topic (META_DEBUG_XINERAMA, " (not using XFree86 Xinerama)\n"); +#endif +#ifdef HAVE_SOLARIS_XINERAMA + meta_topic (META_DEBUG_XINERAMA, " (using Solaris Xinerama)\n"); +#else + meta_topic (META_DEBUG_XINERAMA, " (not using Solaris Xinerama)\n"); +#endif +#ifdef HAVE_XSYNC + meta_verbose ("Compiled with sync extension\n"); +#else + meta_verbose ("Compiled without sync extension\n"); +#endif +#ifdef HAVE_RANDR + meta_verbose ("Compiled with randr extension\n"); +#else + meta_verbose ("Compiled without randr extension\n"); +#endif +#ifdef HAVE_STARTUP_NOTIFICATION + meta_verbose ("Compiled with startup notification\n"); +#else + meta_verbose ("Compiled without startup notification\n"); +#endif +#ifdef HAVE_COMPOSITE_EXTENSIONS + meta_verbose ("Compiled with composite extensions\n"); +#else + meta_verbose ("Compiled without composite extensions\n"); +#endif +} + +/** + * Prints the version number, the current timestamp (not the + * build date), the locale, the character encoding, and a list + * of configure script options that were used to build this + * copy of Marco. This is actually always called + * on startup, but it's all no-op unless we're in verbose mode + * (see meta_set_verbose). + */ +static void +meta_print_self_identity (void) +{ + char buf[256]; + GDate d; + const char *charset; + + /* Version and current date. */ + g_date_clear (&d, 1); + g_date_set_time_t (&d, time (NULL)); + g_date_strftime (buf, sizeof (buf), "%x", &d); + meta_verbose ("Marco version %s running on %s\n", + VERSION, buf); + + /* Locale and encoding. */ + g_get_charset (&charset); + meta_verbose ("Running in locale \"%s\" with encoding \"%s\"\n", + setlocale (LC_ALL, NULL), charset); + + /* Compilation settings. */ + meta_print_compilation_info (); +} + +/** + * The set of possible options that can be set on Marco's + * command line. This type exists so that meta_parse_options() can + * write to an instance of it. + */ +typedef struct +{ + gchar *save_file; + gchar *display_name; + gchar *client_id; + gboolean replace_wm; + gboolean disable_sm; + gboolean print_version; + gboolean sync; + gboolean composite; + gboolean no_composite; + gboolean no_force_fullscreen; +} MetaArguments; + +#ifdef HAVE_COMPOSITE_EXTENSIONS +#define COMPOSITE_OPTS_FLAGS 0 +#else /* HAVE_COMPOSITE_EXTENSIONS */ +/* No compositor, so don't show the arguments in --help */ +#define COMPOSITE_OPTS_FLAGS G_OPTION_FLAG_HIDDEN +#endif /* HAVE_COMPOSITE_EXTENSIONS */ + +/** + * Parses argc and argv and returns the + * arguments that Marco understands in meta_args. + * + * The strange call signature has to be written like it is so + * that g_option_context_parse() gets a chance to modify argc and + * argv. + * + * \param argc Pointer to the number of arguments Marco was given + * \param argv Pointer to the array of arguments Marco was given + * \param meta_args The result of parsing the arguments. + **/ +static void +meta_parse_options (int *argc, char ***argv, + MetaArguments *meta_args) +{ + MetaArguments my_args = {NULL, NULL, NULL, + FALSE, FALSE, FALSE, FALSE, FALSE}; + GOptionEntry options[] = { + { + "sm-disable", 0, 0, G_OPTION_ARG_NONE, + &my_args.disable_sm, + N_("Disable connection to session manager"), + NULL + }, + { + "replace", 0, 0, G_OPTION_ARG_NONE, + &my_args.replace_wm, + N_("Replace the running window manager with Marco"), + NULL + }, + { + "sm-client-id", 0, 0, G_OPTION_ARG_STRING, + &my_args.client_id, + N_("Specify session management ID"), + "ID" + }, + { + "display", 'd', 0, G_OPTION_ARG_STRING, + &my_args.display_name, N_("X Display to use"), + "DISPLAY" + }, + { + "sm-save-file", 0, 0, G_OPTION_ARG_FILENAME, + &my_args.save_file, + N_("Initialize session from savefile"), + "FILE" + }, + { + "version", 0, 0, G_OPTION_ARG_NONE, + &my_args.print_version, + N_("Print version"), + NULL + }, + { + "sync", 0, 0, G_OPTION_ARG_NONE, + &my_args.sync, + N_("Make X calls synchronous"), + NULL + }, + { + "composite", 'c', COMPOSITE_OPTS_FLAGS, G_OPTION_ARG_NONE, + &my_args.composite, + N_("Turn compositing on"), + NULL + }, + { + "no-composite", 0, COMPOSITE_OPTS_FLAGS, G_OPTION_ARG_NONE, + &my_args.no_composite, + N_("Turn compositing off"), + NULL + }, + { + "no-force-fullscreen", 0, COMPOSITE_OPTS_FLAGS, G_OPTION_ARG_NONE, + &my_args.no_force_fullscreen, + N_("Don't make fullscreen windows that are maximized and have no decorations"), + NULL + }, + {NULL} + }; + GOptionContext *ctx; + GError *error = NULL; + + ctx = g_option_context_new (NULL); + g_option_context_add_main_entries (ctx, options, "marco"); + if (!g_option_context_parse (ctx, argc, argv, &error)) + { + g_print ("marco: %s\n", error->message); + exit(1); + } + g_option_context_free (ctx); + /* Return the parsed options through the meta_args param. */ + *meta_args = my_args; +} + +/** + * Selects which display Marco should use. It first tries to use + * display_name as the display. If display_name is NULL then + * try to use the environment variable MARCO_DISPLAY. If that + * also is NULL, use the default - :0.0 + */ +static void +meta_select_display (gchar *display_name) +{ + gchar *envVar = ""; + if (display_name) + envVar = g_strconcat ("DISPLAY=", display_name, NULL); + else if (g_getenv ("MARCO_DISPLAY")) + envVar = g_strconcat ("DISPLAY=", + g_getenv ("MARCO_DISPLAY"), NULL); + /* DO NOT FREE envVar, putenv() sucks */ + putenv (envVar); +} + +static void +meta_finalize (void) +{ + MetaDisplay *display = meta_get_display(); + + meta_session_shutdown (); + + if (display) + meta_display_close (display, + CurrentTime); /* I doubt correct timestamps matter here */ +} + +static int sigterm_pipe_fds[2] = { -1, -1 }; + +static void +sigterm_handler (int signum) +{ + if (sigterm_pipe_fds[1] >= 0) + { + int dummy; + + dummy = write (sigterm_pipe_fds[1], "", 1); + close (sigterm_pipe_fds[1]); + sigterm_pipe_fds[1] = -1; + } +} + +static gboolean +on_sigterm (void) +{ + meta_quit (META_EXIT_SUCCESS); + return FALSE; +} + +/** + * This is where the story begins. It parses commandline options and + * environment variables, sets up the screen, hands control off to + * GTK, and cleans up afterwards. + * + * \param argc Number of arguments (as usual) + * \param argv Array of arguments (as usual) + * + * \bug It's a bit long. It would be good to split it out into separate + * functions. + */ +int +main (int argc, char **argv) +{ + struct sigaction act; + sigset_t empty_mask; + MetaArguments meta_args; + const gchar *log_domains[] = { + NULL, G_LOG_DOMAIN, "Gtk", "Gdk", "GLib", + "Pango", "GLib-GObject", "GThread" + }; + guint i; + GIOChannel *channel; + + if (!g_thread_supported ()) + g_thread_init (NULL); + + if (setlocale (LC_ALL, "") == NULL) + meta_warning ("Locale not understood by C library, internationalization will not work\n"); + + g_type_init (); + + sigemptyset (&empty_mask); + act.sa_handler = SIG_IGN; + act.sa_mask = empty_mask; + act.sa_flags = 0; + if (sigaction (SIGPIPE, &act, NULL) < 0) + g_printerr ("Failed to register SIGPIPE handler: %s\n", + g_strerror (errno)); +#ifdef SIGXFSZ + if (sigaction (SIGXFSZ, &act, NULL) < 0) + g_printerr ("Failed to register SIGXFSZ handler: %s\n", + g_strerror (errno)); +#endif + + if (pipe (sigterm_pipe_fds) != 0) + g_printerr ("Failed to create SIGTERM pipe: %s\n", + g_strerror (errno)); + + channel = g_io_channel_unix_new (sigterm_pipe_fds[0]); + g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_add_watch (channel, G_IO_IN, (GIOFunc) on_sigterm, NULL); + g_io_channel_set_close_on_unref (channel, TRUE); + g_io_channel_unref (channel); + + act.sa_handler = &sigterm_handler; + if (sigaction (SIGTERM, &act, NULL) < 0) + g_printerr ("Failed to register SIGTERM handler: %s\n", + g_strerror (errno)); + + if (g_getenv ("MARCO_VERBOSE")) + meta_set_verbose (TRUE); + if (g_getenv ("MARCO_DEBUG")) + meta_set_debugging (TRUE); + + if (g_get_home_dir ()) + if (chdir (g_get_home_dir ()) < 0) + meta_warning ("Could not change to home directory %s.\n", + g_get_home_dir ()); + + meta_print_self_identity (); + + bindtextdomain (GETTEXT_PACKAGE, MARCO_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + /* Parse command line arguments.*/ + meta_parse_options (&argc, &argv, &meta_args); + + meta_set_syncing (meta_args.sync || (g_getenv ("MARCO_SYNC") != NULL)); + + if (meta_args.print_version) + version (); + + meta_select_display (meta_args.display_name); + + if (meta_args.replace_wm) + meta_set_replace_current_wm (TRUE); + + if (meta_args.save_file && meta_args.client_id) + meta_fatal ("Can't specify both SM save file and SM client id\n"); + + meta_main_loop = g_main_loop_new (NULL, FALSE); + + meta_ui_init (&argc, &argv); + + /* must be after UI init so we can override GDK handlers */ + meta_errors_init (); + + /* Load prefs */ + meta_prefs_init (); + meta_prefs_add_listener (prefs_changed_callback, NULL); + + +#if 1 + + for (i=0; i<G_N_ELEMENTS(log_domains); i++) + g_log_set_handler (log_domains[i], + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + log_handler, NULL); + +#endif + + if (g_getenv ("MARCO_G_FATAL_WARNINGS") != NULL) + g_log_set_always_fatal (G_LOG_LEVEL_MASK); + + meta_ui_set_current_theme (meta_prefs_get_theme (), FALSE); + + /* Try to find some theme that'll work if the theme preference + * doesn't exist. First try Simple (the default theme) then just + * try anything in the themes directory. + */ + if (!meta_ui_have_a_theme ()) + meta_ui_set_current_theme ("Simple", FALSE); + + if (!meta_ui_have_a_theme ()) + { + const char *dir_entry = NULL; + GError *err = NULL; + GDir *themes_dir = NULL; + + if (!(themes_dir = g_dir_open (MARCO_DATADIR"/themes", 0, &err))) + { + meta_fatal (_("Failed to scan themes directory: %s\n"), err->message); + g_error_free (err); + } + else + { + while (((dir_entry = g_dir_read_name (themes_dir)) != NULL) && + (!meta_ui_have_a_theme ())) + { + meta_ui_set_current_theme (dir_entry, FALSE); + } + + g_dir_close (themes_dir); + } + } + + if (!meta_ui_have_a_theme ()) + meta_fatal (_("Could not find a theme! Be sure %s exists and contains the usual themes.\n"), + MARCO_DATADIR"/themes"); + + /* Connect to SM as late as possible - but before managing display, + * or we might try to manage a window before we have the session + * info + */ + if (!meta_args.disable_sm) + { + if (meta_args.client_id == NULL) + { + const gchar *desktop_autostart_id; + + desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + + if (desktop_autostart_id != NULL) + meta_args.client_id = g_strdup (desktop_autostart_id); + } + + /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to + * use the same client id. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + + meta_session_init (meta_args.client_id, meta_args.save_file); + } + /* Free memory possibly allocated by the argument parsing which are + * no longer needed. + */ + g_free (meta_args.save_file); + g_free (meta_args.display_name); + g_free (meta_args.client_id); + + if (meta_args.composite || meta_args.no_composite) + meta_prefs_set_compositing_manager (meta_args.composite); + + if (meta_args.no_force_fullscreen) + meta_prefs_set_force_fullscreen (FALSE); + + if (!meta_display_open ()) + meta_exit (META_EXIT_ERROR); + + g_main_loop_run (meta_main_loop); + + meta_finalize (); + + if (meta_restart_after_quit) + { + GError *err; + + err = NULL; + if (!g_spawn_async (NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + NULL, + &err)) + { + meta_fatal (_("Failed to restart: %s\n"), + err->message); + g_error_free (err); /* not reached anyhow */ + meta_exit_code = META_EXIT_ERROR; + } + } + + return meta_exit_code; +} + +/** + * Stops Marco. This tells the event loop to stop processing; it is rather + * dangerous to use this rather than meta_restart() because this will leave + * the user with no window manager. We generally do this only if, for example, + * the session manager asks us to; we assume the session manager knows what + * it's talking about. + * + * \param code The success or failure code to return to the calling process. + */ +void +meta_quit (MetaExitCode code) +{ + meta_exit_code = code; + + if (g_main_loop_is_running (meta_main_loop)) + g_main_loop_quit (meta_main_loop); +} + +/** + * Restarts Marco. In practice, this tells the event loop to stop + * processing, having first set the meta_restart_after_quit flag which + * tells Marco to spawn an identical copy of itself before quitting. + * This happens on receipt of a _MARCO_RESTART_MESSAGE client event. + */ +void +meta_restart (void) +{ + meta_restart_after_quit = TRUE; + meta_quit (META_EXIT_SUCCESS); +} + +/** + * Called on pref changes. (One of several functions of its kind and purpose.) + * + * \bug Why are these particular prefs handled in main.c and not others? + * Should they be? + * + * \param pref Which preference has changed + * \param data Arbitrary data (which we ignore) + */ +static void +prefs_changed_callback (MetaPreference pref, + gpointer data) +{ + switch (pref) + { + case META_PREF_THEME: + meta_ui_set_current_theme (meta_prefs_get_theme (), FALSE); + meta_display_retheme_all (); + break; + + case META_PREF_CURSOR_THEME: + case META_PREF_CURSOR_SIZE: + meta_display_set_cursor_theme (meta_prefs_get_cursor_theme (), + meta_prefs_get_cursor_size ()); + break; + default: + /* handled elsewhere or otherwise */ + break; + } +} diff --git a/src/core/marco-Xatomtype.h b/src/core/marco-Xatomtype.h new file mode 100644 index 00000000..5698ed31 --- /dev/null +++ b/src/core/marco-Xatomtype.h @@ -0,0 +1,136 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file marco-Xatomtype.h Types for communicating with X about properties + * + * This files defines crock C structures for calling XGetWindowProperty and + * XChangeProperty. All fields must be longs as the semantics of property + * routines will handle conversion to and from actual 32 bit objects. If your + * compiler doesn't treat &structoflongs the same as &arrayoflongs[0], you + * will have some work to do. + */ + +/*********************************************************** + +Copyright 1987, 1998 The Open Group + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of The Open Group shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from The Open Group. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +******************************************************************/ + +#ifndef _XATOMTYPE_H_ +#define _XATOMTYPE_H_ + +#define BOOL long +#define SIGNEDINT long +#define UNSIGNEDINT unsigned long +#define RESOURCEID unsigned long + + +/* this structure may be extended, but do not change the order */ +typedef struct { + UNSIGNEDINT flags; + SIGNEDINT x, y, width, height; /* need to cvt; only for pre-ICCCM */ + SIGNEDINT minWidth, minHeight; /* need to cvt */ + SIGNEDINT maxWidth, maxHeight; /* need to cvt */ + SIGNEDINT widthInc, heightInc; /* need to cvt */ + SIGNEDINT minAspectX, minAspectY; /* need to cvt */ + SIGNEDINT maxAspectX, maxAspectY; /* need to cvt */ + SIGNEDINT baseWidth,baseHeight; /* need to cvt; ICCCM version 1 */ + SIGNEDINT winGravity; /* need to cvt; ICCCM version 1 */ +} xPropSizeHints; +#define OldNumPropSizeElements 15 /* pre-ICCCM */ +#define NumPropSizeElements 18 /* ICCCM version 1 */ + +/* this structure may be extended, but do not change the order */ +/* RGB properties */ +typedef struct { + RESOURCEID colormap; + UNSIGNEDINT red_max; + UNSIGNEDINT red_mult; + UNSIGNEDINT green_max; + UNSIGNEDINT green_mult; + UNSIGNEDINT blue_max; + UNSIGNEDINT blue_mult; + UNSIGNEDINT base_pixel; + RESOURCEID visualid; /* ICCCM version 1 */ + RESOURCEID killid; /* ICCCM version 1 */ +} xPropStandardColormap; +#define OldNumPropStandardColormapElements 8 /* pre-ICCCM */ +#define NumPropStandardColormapElements 10 /* ICCCM version 1 */ + + +/* this structure may be extended, but do not change the order */ +typedef struct { + UNSIGNEDINT flags; + BOOL input; /* need to convert */ + SIGNEDINT initialState; /* need to cvt */ + RESOURCEID iconPixmap; + RESOURCEID iconWindow; + SIGNEDINT iconX; /* need to cvt */ + SIGNEDINT iconY; /* need to cvt */ + RESOURCEID iconMask; + UNSIGNEDINT windowGroup; + } xPropWMHints; +#define NumPropWMHintsElements 9 /* number of elements in this structure */ + +/* this structure defines the icon size hints information */ +typedef struct { + SIGNEDINT minWidth, minHeight; /* need to cvt */ + SIGNEDINT maxWidth, maxHeight; /* need to cvt */ + SIGNEDINT widthInc, heightInc; /* need to cvt */ + } xPropIconSize; +#define NumPropIconSizeElements 6 /* number of elements in this structure */ + +/* this structure defines the window manager state information */ +typedef struct { + SIGNEDINT state; /* need to cvt */ + RESOURCEID iconWindow; +} xPropWMState; +#define NumPropWMStateElements 2 /* number of elements in struct */ + +#undef BOOL +#undef SIGNEDINT +#undef UNSIGNEDINT +#undef RESOURCEID + +#endif /* _XATOMTYPE_H_ */ diff --git a/src/core/place.c b/src/core/place.c new file mode 100644 index 00000000..cb1458f0 --- /dev/null +++ b/src/core/place.c @@ -0,0 +1,932 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window placement */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 Rob Adams + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> + +#include "place.h" +#include "workspace.h" +#include "prefs.h" +#include <gdk/gdk.h> +#include <math.h> +#include <stdlib.h> + +typedef enum +{ + META_LEFT, + META_RIGHT, + META_TOP, + META_BOTTOM +} MetaWindowDirection; + +static gint +northwestcmp (gconstpointer a, gconstpointer b) +{ + MetaWindow *aw = (gpointer) a; + MetaWindow *bw = (gpointer) b; + int from_origin_a; + int from_origin_b; + int ax, ay, bx, by; + + /* we're interested in the frame position for cascading, + * not meta_window_get_position() + */ + if (aw->frame) + { + ax = aw->frame->rect.x; + ay = aw->frame->rect.y; + } + else + { + ax = aw->rect.x; + ay = aw->rect.y; + } + + if (bw->frame) + { + bx = bw->frame->rect.x; + by = bw->frame->rect.y; + } + else + { + bx = bw->rect.x; + by = bw->rect.y; + } + + /* probably there's a fast good-enough-guess we could use here. */ + from_origin_a = sqrt (ax * ax + ay * ay); + from_origin_b = sqrt (bx * bx + by * by); + + if (from_origin_a < from_origin_b) + return -1; + else if (from_origin_a > from_origin_b) + return 1; + else + return 0; +} + +static void +find_next_cascade (MetaWindow *window, + MetaFrameGeometry *fgeom, + /* visible windows on relevant workspaces */ + GList *windows, + int x, + int y, + int *new_x, + int *new_y) +{ + GList *tmp; + GList *sorted; + int cascade_x, cascade_y; + int x_threshold, y_threshold; + int window_width, window_height; + int cascade_stage; + MetaRectangle work_area; + const MetaXineramaScreenInfo* current; + + sorted = g_list_copy (windows); + sorted = g_list_sort (sorted, northwestcmp); + + /* This is a "fuzzy" cascade algorithm. + * For each window in the list, we find where we'd cascade a + * new window after it. If a window is already nearly at that + * position, we move on. + */ + + /* arbitrary-ish threshold, honors user attempts to + * manually cascade. + */ +#define CASCADE_FUZZ 15 + if (fgeom) + { + x_threshold = MAX (fgeom->left_width, CASCADE_FUZZ); + y_threshold = MAX (fgeom->top_height, CASCADE_FUZZ); + } + else + { + x_threshold = CASCADE_FUZZ; + y_threshold = CASCADE_FUZZ; + } + + /* Find furthest-SE origin of all workspaces. + * cascade_x, cascade_y are the target position + * of NW corner of window frame. + */ + + current = meta_screen_get_current_xinerama (window->screen); + meta_window_get_work_area_for_xinerama (window, current->number, &work_area); + + cascade_x = MAX (0, work_area.x); + cascade_y = MAX (0, work_area.y); + + /* Find first cascade position that's not used. */ + + window_width = window->frame ? window->frame->rect.width : window->rect.width; + window_height = window->frame ? window->frame->rect.height : window->rect.height; + + cascade_stage = 0; + tmp = sorted; + while (tmp != NULL) + { + MetaWindow *w; + int wx, wy; + + w = tmp->data; + + /* we want frame position, not window position */ + if (w->frame) + { + wx = w->frame->rect.x; + wy = w->frame->rect.y; + } + else + { + wx = w->rect.x; + wy = w->rect.y; + } + + if (ABS (wx - cascade_x) < x_threshold && + ABS (wy - cascade_y) < y_threshold) + { + /* This window is "in the way", move to next cascade + * point. The new window frame should go at the origin + * of the client window we're stacking above. + */ + meta_window_get_position (w, &wx, &wy); + cascade_x = wx; + cascade_y = wy; + + /* If we go off the screen, start over with a new cascade */ + if (((cascade_x + window_width) > + (work_area.x + work_area.width)) || + ((cascade_y + window_height) > + (work_area.y + work_area.height))) + { + cascade_x = MAX (0, work_area.x); + cascade_y = MAX (0, work_area.y); + +#define CASCADE_INTERVAL 50 /* space between top-left corners of cascades */ + cascade_stage += 1; + cascade_x += CASCADE_INTERVAL * cascade_stage; + + /* start over with a new cascade translated to the right, unless + * we are out of space + */ + if ((cascade_x + window_width) < + (work_area.x + work_area.width)) + { + tmp = sorted; + continue; + } + else + { + /* All out of space, this cascade_x won't work */ + cascade_x = MAX (0, work_area.x); + break; + } + } + } + else + { + /* Keep searching for a further-down-the-diagonal window. */ + } + + tmp = tmp->next; + } + + /* cascade_x and cascade_y will match the last window in the list + * that was "in the way" (in the approximate cascade diagonal) + */ + + g_list_free (sorted); + + /* Convert coords to position of window, not position of frame. */ + if (fgeom == NULL) + { + *new_x = cascade_x; + *new_y = cascade_y; + } + else + { + *new_x = cascade_x + fgeom->left_width; + *new_y = cascade_y + fgeom->top_height; + } +} + +static void +find_most_freespace (MetaWindow *window, + MetaFrameGeometry *fgeom, + /* visible windows on relevant workspaces */ + MetaWindow *focus_window, + int x, + int y, + int *new_x, + int *new_y) +{ + MetaWindowDirection side; + int max_area; + int max_width, max_height, left, right, top, bottom; + int left_space, right_space, top_space, bottom_space; + int frame_size_left, frame_size_top; + MetaRectangle work_area; + MetaRectangle avoid; + MetaRectangle outer; + + frame_size_left = fgeom ? fgeom->left_width : 0; + frame_size_top = fgeom ? fgeom->top_height : 0; + + meta_window_get_work_area_current_xinerama (focus_window, &work_area); + meta_window_get_outer_rect (focus_window, &avoid); + meta_window_get_outer_rect (window, &outer); + + /* Find the areas of choosing the various sides of the focus window */ + max_width = MIN (avoid.width, outer.width); + max_height = MIN (avoid.height, outer.height); + left_space = avoid.x - work_area.x; + right_space = work_area.width - (avoid.x + avoid.width - work_area.x); + top_space = avoid.y - work_area.y; + bottom_space = work_area.height - (avoid.y + avoid.height - work_area.y); + left = MIN (left_space, outer.width); + right = MIN (right_space, outer.width); + top = MIN (top_space, outer.height); + bottom = MIN (bottom_space, outer.height); + + /* Find out which side of the focus_window can show the most of the window */ + side = META_LEFT; + max_area = left*max_height; + if (right*max_height > max_area) + { + side = META_RIGHT; + max_area = right*max_height; + } + if (top*max_width > max_area) + { + side = META_TOP; + max_area = top*max_width; + } + if (bottom*max_width > max_area) + { + side = META_BOTTOM; + max_area = bottom*max_width; + } + + /* Give up if there's no where to put it (i.e. focus window is maximized) */ + if (max_area == 0) + return; + + /* Place the window on the relevant side; if the whole window fits, + * make it adjacent to the focus window; if not, make sure the + * window doesn't go off the edge of the screen. + */ + switch (side) + { + case META_LEFT: + *new_y = avoid.y + frame_size_top; + if (left_space > outer.width) + *new_x = avoid.x - outer.width + frame_size_left; + else + *new_x = work_area.x + frame_size_left; + break; + case META_RIGHT: + *new_y = avoid.y + frame_size_top; + if (right_space > outer.width) + *new_x = avoid.x + avoid.width + frame_size_left; + else + *new_x = work_area.x + work_area.width - outer.width + frame_size_left; + break; + case META_TOP: + *new_x = avoid.x + frame_size_left; + if (top_space > outer.height) + *new_y = avoid.y - outer.height + frame_size_top; + else + *new_y = work_area.y + frame_size_top; + break; + case META_BOTTOM: + *new_x = avoid.x + frame_size_left; + if (bottom_space > outer.height) + *new_y = avoid.y + avoid.height + frame_size_top; + else + *new_y = work_area.y + work_area.height - outer.height + frame_size_top; + break; + } +} + +static void +avoid_being_obscured_as_second_modal_dialog (MetaWindow *window, + MetaFrameGeometry *fgeom, + int *x, + int *y) +{ + /* We can't center this dialog if it was denied focus and it + * overlaps with the focus window and this dialog is modal and this + * dialog is in the same app as the focus window (*phew*...please + * don't make me say that ten times fast). See bug 307875 comment 11 + * and 12 for details, but basically it means this is probably a + * second modal dialog for some app while the focus window is the + * first modal dialog. We should probably make them simultaneously + * visible in general, but it becomes mandatory to do so due to + * buggy apps (e.g. those using gtk+ *sigh*) because in those cases + * this second modal dialog also happens to be modal to the first + * dialog in addition to the main window, while it has only let us + * know about the modal-to-the-main-window part. + */ + + MetaWindow *focus_window; + MetaRectangle overlap; + + focus_window = window->display->focus_window; + + if (window->denied_focus_and_not_transient && + window->wm_state_modal && /* FIXME: Maybe do this for all transients? */ + meta_window_same_application (window, focus_window) && + meta_rectangle_intersect (&window->rect, + &focus_window->rect, + &overlap)) + { + find_most_freespace (window, fgeom, focus_window, *x, *y, x, y); + meta_topic (META_DEBUG_PLACEMENT, + "Dialog window %s was denied focus but may be modal " + "to the focus window; had to move it to avoid the " + "focus window\n", + window->desc); + } +} + +static gboolean +rectangle_overlaps_some_window (MetaRectangle *rect, + GList *windows) +{ + GList *tmp; + MetaRectangle dest; + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *other = tmp->data; + MetaRectangle other_rect; + + switch (other->type) + { + case META_WINDOW_DOCK: + case META_WINDOW_SPLASHSCREEN: + case META_WINDOW_DESKTOP: + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + break; + + case META_WINDOW_NORMAL: + case META_WINDOW_UTILITY: + case META_WINDOW_TOOLBAR: + case META_WINDOW_MENU: + meta_window_get_outer_rect (other, &other_rect); + + if (meta_rectangle_intersect (rect, &other_rect, &dest)) + return TRUE; + break; + } + + tmp = tmp->next; + } + + return FALSE; +} + +static gint +leftmost_cmp (gconstpointer a, gconstpointer b) +{ + MetaWindow *aw = (gpointer) a; + MetaWindow *bw = (gpointer) b; + int ax, bx; + + /* we're interested in the frame position for cascading, + * not meta_window_get_position() + */ + if (aw->frame) + ax = aw->frame->rect.x; + else + ax = aw->rect.x; + + if (bw->frame) + bx = bw->frame->rect.x; + else + bx = bw->rect.x; + + if (ax < bx) + return -1; + else if (ax > bx) + return 1; + else + return 0; +} + +static gint +topmost_cmp (gconstpointer a, gconstpointer b) +{ + MetaWindow *aw = (gpointer) a; + MetaWindow *bw = (gpointer) b; + int ay, by; + + /* we're interested in the frame position for cascading, + * not meta_window_get_position() + */ + if (aw->frame) + ay = aw->frame->rect.y; + else + ay = aw->rect.y; + + if (bw->frame) + by = bw->frame->rect.y; + else + by = bw->rect.y; + + if (ay < by) + return -1; + else if (ay > by) + return 1; + else + return 0; +} + +static void +center_tile_rect_in_area (MetaRectangle *rect, + MetaRectangle *work_area) +{ + int fluff; + + /* The point here is to tile a window such that "extra" + * space is equal on either side (i.e. so a full screen + * of windows tiled this way would center the windows + * as a group) + */ + + fluff = (work_area->width % (rect->width+1)) / 2; + rect->x = work_area->x + fluff; + fluff = (work_area->height % (rect->height+1)) / 3; + rect->y = work_area->y + fluff; +} + +/* Find the leftmost, then topmost, empty area on the workspace + * that can contain the new window. + * + * Cool feature to have: if we can't fit the current window size, + * try shrinking the window (within geometry constraints). But + * beware windows such as Emacs with no sane minimum size, we + * don't want to create a 1x1 Emacs. + */ +static gboolean +find_first_fit (MetaWindow *window, + MetaFrameGeometry *fgeom, + /* visible windows on relevant workspaces */ + GList *windows, + int xinerama, + int x, + int y, + int *new_x, + int *new_y) +{ + /* This algorithm is limited - it just brute-force tries + * to fit the window in a small number of locations that are aligned + * with existing windows. It tries to place the window on + * the bottom of each existing window, and then to the right + * of each existing window, aligned with the left/top of the + * existing window in each of those cases. + */ + int retval; + GList *below_sorted; + GList *right_sorted; + GList *tmp; + MetaRectangle rect; + MetaRectangle work_area; + + retval = FALSE; + + /* Below each window */ + below_sorted = g_list_copy (windows); + below_sorted = g_list_sort (below_sorted, leftmost_cmp); + below_sorted = g_list_sort (below_sorted, topmost_cmp); + + /* To the right of each window */ + right_sorted = g_list_copy (windows); + right_sorted = g_list_sort (right_sorted, topmost_cmp); + right_sorted = g_list_sort (right_sorted, leftmost_cmp); + + rect.width = window->rect.width; + rect.height = window->rect.height; + + if (fgeom) + { + rect.width += fgeom->left_width + fgeom->right_width; + rect.height += fgeom->top_height + fgeom->bottom_height; + } + +#ifdef WITH_VERBOSE_MODE + { + char xinerama_location_string[RECT_LENGTH]; + meta_rectangle_to_string (&window->screen->xinerama_infos[xinerama].rect, + xinerama_location_string); + meta_topic (META_DEBUG_XINERAMA, + "Natural xinerama is %s\n", + xinerama_location_string); + } +#endif + + meta_window_get_work_area_for_xinerama (window, xinerama, &work_area); + + center_tile_rect_in_area (&rect, &work_area); + + if (meta_rectangle_contains_rect (&work_area, &rect) && + !rectangle_overlaps_some_window (&rect, windows)) + { + *new_x = rect.x; + *new_y = rect.y; + if (fgeom) + { + *new_x += fgeom->left_width; + *new_y += fgeom->top_height; + } + + retval = TRUE; + + goto out; + } + + /* try below each window */ + tmp = below_sorted; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + MetaRectangle outer_rect; + + meta_window_get_outer_rect (w, &outer_rect); + + rect.x = outer_rect.x; + rect.y = outer_rect.y + outer_rect.height; + + if (meta_rectangle_contains_rect (&work_area, &rect) && + !rectangle_overlaps_some_window (&rect, below_sorted)) + { + *new_x = rect.x; + *new_y = rect.y; + if (fgeom) + { + *new_x += fgeom->left_width; + *new_y += fgeom->top_height; + } + + retval = TRUE; + + goto out; + } + + tmp = tmp->next; + } + + /* try to the right of each window */ + tmp = right_sorted; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + MetaRectangle outer_rect; + + meta_window_get_outer_rect (w, &outer_rect); + + rect.x = outer_rect.x + outer_rect.width; + rect.y = outer_rect.y; + + if (meta_rectangle_contains_rect (&work_area, &rect) && + !rectangle_overlaps_some_window (&rect, right_sorted)) + { + *new_x = rect.x; + *new_y = rect.y; + if (fgeom) + { + *new_x += fgeom->left_width; + *new_y += fgeom->top_height; + } + + retval = TRUE; + + goto out; + } + + tmp = tmp->next; + } + + out: + + g_list_free (below_sorted); + g_list_free (right_sorted); + return retval; +} + +void +meta_window_place (MetaWindow *window, + MetaFrameGeometry *fgeom, + int x, + int y, + int *new_x, + int *new_y) +{ + GList *windows; + const MetaXineramaScreenInfo *xi; + + /* frame member variables should NEVER be used in here, only + * MetaFrameGeometry. But remember fgeom == NULL + * for undecorated windows. Also, this function should + * NEVER have side effects other than computing the + * placement coordinates. + */ + + meta_topic (META_DEBUG_PLACEMENT, "Placing window %s\n", window->desc); + + windows = NULL; + + switch (window->type) + { + /* Run placement algorithm on these. */ + case META_WINDOW_NORMAL: + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + case META_WINDOW_SPLASHSCREEN: + break; + + /* Assume the app knows best how to place these, no placement + * algorithm ever (other than "leave them as-is") + */ + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + case META_WINDOW_MENU: + case META_WINDOW_UTILITY: + goto done_no_constraints; + } + + if (meta_prefs_get_disable_workarounds ()) + { + switch (window->type) + { + /* Only accept USPosition on normal windows because the app is full + * of shit claiming the user set -geometry for a dialog or dock + */ + case META_WINDOW_NORMAL: + if (window->size_hints.flags & USPosition) + { + /* don't constrain with placement algorithm */ + meta_topic (META_DEBUG_PLACEMENT, + "Honoring USPosition for %s instead of using placement algorithm\n", window->desc); + + goto done; + } + break; + + /* Ignore even USPosition on dialogs, splashscreen */ + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + case META_WINDOW_SPLASHSCREEN: + break; + + /* Assume the app knows best how to place these. */ + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + case META_WINDOW_MENU: + case META_WINDOW_UTILITY: + if (window->size_hints.flags & PPosition) + { + meta_topic (META_DEBUG_PLACEMENT, + "Not placing non-normal non-dialog window with PPosition set\n"); + goto done_no_constraints; + } + break; + } + } + else + { + /* workarounds enabled */ + + if ((window->size_hints.flags & PPosition) || + (window->size_hints.flags & USPosition)) + { + meta_topic (META_DEBUG_PLACEMENT, + "Not placing window with PPosition or USPosition set\n"); + avoid_being_obscured_as_second_modal_dialog (window, fgeom, &x, &y); + goto done_no_constraints; + } + } + + if ((window->type == META_WINDOW_DIALOG || + window->type == META_WINDOW_MODAL_DIALOG) && + window->xtransient_for != None) + { + /* Center horizontally, at top of parent vertically */ + + MetaWindow *parent; + + parent = + meta_display_lookup_x_window (window->display, + window->xtransient_for); + + if (parent) + { + int w; + + meta_window_get_position (parent, &x, &y); + w = parent->rect.width; + + /* center of parent */ + x = x + w / 2; + /* center of child over center of parent */ + x -= window->rect.width / 2; + + /* "visually" center window over parent, leaving twice as + * much space below as on top. + */ + y += (parent->rect.height - window->rect.height)/3; + + /* put top of child's frame, not top of child's client */ + if (fgeom) + y += fgeom->top_height; + + meta_topic (META_DEBUG_PLACEMENT, "Centered window %s over transient parent\n", + window->desc); + + avoid_being_obscured_as_second_modal_dialog (window, fgeom, &x, &y); + + goto done; + } + } + + /* FIXME UTILITY with transient set should be stacked up + * on the sides of the parent window or something. + */ + + if (window->type == META_WINDOW_DIALOG || + window->type == META_WINDOW_MODAL_DIALOG || + window->type == META_WINDOW_SPLASHSCREEN) + { + /* Center on current xinerama (i.e. on current monitor) */ + int w, h; + + /* Warning, this function is a round trip! */ + xi = meta_screen_get_current_xinerama (window->screen); + + w = xi->rect.width; + h = xi->rect.height; + + x = (w - window->rect.width) / 2; + y = (h - window->rect.height) / 2; + + x += xi->rect.x; + y += xi->rect.y; + + meta_topic (META_DEBUG_PLACEMENT, "Centered window %s on screen %d xinerama %d\n", + window->desc, window->screen->number, xi->number); + + goto done_check_denied_focus; + } + + /* Find windows that matter (not minimized, on same workspace + * as placed window, may be shaded - if shaded we pretend it isn't + * for placement purposes) + */ + { + GSList *all_windows; + GSList *tmp; + + all_windows = meta_display_list_windows (window->display); + + tmp = all_windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (meta_window_showing_on_its_workspace (w) && + w != window && + (window->workspace == w->workspace || + window->on_all_workspaces || w->on_all_workspaces)) + windows = g_list_prepend (windows, w); + + tmp = tmp->next; + } + + g_slist_free (all_windows); + } + + /* Warning, this is a round trip! */ + xi = meta_screen_get_current_xinerama (window->screen); + + /* "Origin" placement algorithm */ + x = xi->rect.x; + y = xi->rect.y; + + if (find_first_fit (window, fgeom, windows, + xi->number, + x, y, &x, &y)) + goto done_check_denied_focus; + + /* Maximize windows if they are too big for their work area (bit of + * a hack here). Assume undecorated windows probably don't intend to + * be maximized. + */ + if (window->has_maximize_func && window->decorated && + !window->fullscreen) + { + MetaRectangle workarea; + MetaRectangle outer; + + meta_window_get_work_area_for_xinerama (window, + xi->number, + &workarea); + meta_window_get_outer_rect (window, &outer); + + /* If the window is bigger than the screen, then automaximize. Do NOT + * auto-maximize the directions independently. See #419810. + */ + if (outer.width >= workarea.width && outer.height >= workarea.height) + { + window->maximize_horizontally_after_placement = TRUE; + window->maximize_vertically_after_placement = TRUE; + } + } + + /* If no placement has been done, revert to cascade to avoid + * fully overlapping window (e.g. starting multiple terminals) + * */ + if (x == xi->rect.x && y == xi->rect.y) + find_next_cascade (window, fgeom, windows, x, y, &x, &y); + + done_check_denied_focus: + /* If the window is being denied focus and isn't a transient of the + * focus window, we do NOT want it to overlap with the focus window + * if at all possible. This is guaranteed to only be called if the + * focus_window is non-NULL, and we try to avoid that window. + */ + if (window->denied_focus_and_not_transient) + { + gboolean found_fit; + MetaWindow *focus_window; + MetaRectangle overlap; + + focus_window = window->display->focus_window; + g_assert (focus_window != NULL); + + /* No need to do anything if the window doesn't overlap at all */ + found_fit = !meta_rectangle_intersect (&window->rect, + &focus_window->rect, + &overlap); + + /* Try to do a first fit again, this time only taking into account the + * focus window. + */ + if (!found_fit) + { + GList *focus_window_list; + focus_window_list = g_list_prepend (NULL, focus_window); + + /* Reset x and y ("origin" placement algorithm) */ + x = xi->rect.x; + y = xi->rect.y; + + found_fit = find_first_fit (window, fgeom, focus_window_list, + xi->number, + x, y, &x, &y); + g_list_free (focus_window_list); + } + + /* If that still didn't work, just place it where we can see as much + * as possible. + */ + if (!found_fit) + find_most_freespace (window, fgeom, focus_window, x, y, &x, &y); + } + + done: + g_list_free (windows); + + done_no_constraints: + + *new_x = x; + *new_y = y; +} diff --git a/src/core/place.h b/src/core/place.h new file mode 100644 index 00000000..85b7cd3f --- /dev/null +++ b/src/core/place.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window placement */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * 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. + */ + +#ifndef META_PLACE_H +#define META_PLACE_H + +#include "window-private.h" +#include "frame-private.h" + +void meta_window_place (MetaWindow *window, + MetaFrameGeometry *fgeom, + int x, + int y, + int *new_x, + int *new_y); + +#endif diff --git a/src/core/prefs.c b/src/core/prefs.c new file mode 100644 index 00000000..494d3da1 --- /dev/null +++ b/src/core/prefs.c @@ -0,0 +1,2794 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco preferences */ + +/* + * Copyright (C) 2001 Havoc Pennington, Copyright (C) 2002 Red Hat Inc. + * Copyright (C) 2006 Elijah Newren + * Copyright (C) 2008 Thomas Thurman + * + * 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 "prefs.h" +#include "ui.h" +#include "util.h" +#ifdef HAVE_MATECONF +#include <mateconf/mateconf-client.h> +#endif +#include <string.h> +#include <stdlib.h> + +#define MAX_REASONABLE_WORKSPACES 36 + +#define MAX_COMMANDS (32 + NUM_EXTRA_COMMANDS) +#define NUM_EXTRA_COMMANDS 2 +#define SCREENSHOT_COMMAND_IDX (MAX_COMMANDS - 2) +#define WIN_SCREENSHOT_COMMAND_IDX (MAX_COMMANDS - 1) + +/* If you add a key, it needs updating in init() and in the mateconf + * notify listener and of course in the .schemas file. + * + * Keys which are handled by one of the unified handlers below are + * not given a name here, because the purpose of the unified handlers + * is that keys should be referred to exactly once. + */ +#define KEY_TITLEBAR_FONT "/apps/marco/general/titlebar_font" +#define KEY_NUM_WORKSPACES "/apps/marco/general/num_workspaces" +#define KEY_COMPOSITOR "/apps/marco/general/compositing_manager" +#define KEY_MATE_ACCESSIBILITY "/desktop/mate/interface/accessibility" + +#define KEY_COMMAND_DIRECTORY "/apps/marco/keybinding_commands" +#define KEY_COMMAND_PREFIX "/apps/marco/keybinding_commands/command_" + +#define KEY_TERMINAL_DIR "/desktop/mate/applications/terminal" +#define KEY_TERMINAL_COMMAND KEY_TERMINAL_DIR "/exec" + +#define KEY_SCREEN_BINDINGS_PREFIX "/apps/marco/global_keybindings" +#define KEY_WINDOW_BINDINGS_PREFIX "/apps/marco/window_keybindings" +#define KEY_LIST_BINDINGS_SUFFIX "_list" + +#define KEY_WORKSPACE_NAME_DIRECTORY "/apps/marco/workspace_names" +#define KEY_WORKSPACE_NAME_PREFIX "/apps/marco/workspace_names/name_" + + +#ifdef HAVE_MATECONF +static MateConfClient *default_client = NULL; +static GList *changes = NULL; +static guint changed_idle; +static GList *listeners = NULL; +#endif + +static gboolean use_system_font = FALSE; +static PangoFontDescription *titlebar_font = NULL; +static MetaVirtualModifier mouse_button_mods = Mod1Mask; +static MetaFocusMode focus_mode = META_FOCUS_MODE_CLICK; +static MetaFocusNewWindows focus_new_windows = META_FOCUS_NEW_WINDOWS_SMART; +static gboolean raise_on_click = TRUE; +static char* current_theme = NULL; +static int num_workspaces = 4; +static MetaActionTitlebar action_double_click_titlebar = META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE; +static MetaActionTitlebar action_middle_click_titlebar = META_ACTION_TITLEBAR_LOWER; +static MetaActionTitlebar action_right_click_titlebar = META_ACTION_TITLEBAR_MENU; +static gboolean application_based = FALSE; +static gboolean disable_workarounds = FALSE; +static gboolean auto_raise = FALSE; +static gboolean auto_raise_delay = 500; +static gboolean provide_visual_bell = FALSE; +static gboolean bell_is_audible = TRUE; +static gboolean reduced_resources = FALSE; +static gboolean mate_accessibility = FALSE; +static gboolean mate_animations = TRUE; +static char *cursor_theme = NULL; +static int cursor_size = 24; +static gboolean compositing_manager = FALSE; +static gboolean resize_with_right_button = FALSE; +static gboolean force_fullscreen = TRUE; + +static MetaVisualBellType visual_bell_type = META_VISUAL_BELL_FULLSCREEN_FLASH; +static MetaButtonLayout button_layout; + +/* The screenshot commands are at the end */ +static char *commands[MAX_COMMANDS] = { NULL, }; + +static char *terminal_command = NULL; + +static char *workspace_names[MAX_REASONABLE_WORKSPACES] = { NULL, }; + +#ifdef HAVE_MATECONF +static gboolean handle_preference_update_enum (const gchar *key, MateConfValue *value); + +static gboolean update_key_binding (const char *name, + const char *value); +static gboolean find_and_update_list_binding (MetaKeyPref *bindings, + const char *name, + GSList *value); +static gboolean update_key_list_binding (const char *name, + GSList *value); +static gboolean update_command (const char *name, + const char *value); +static gboolean update_workspace_name (const char *name, + const char *value); + +static void change_notify (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + +static char* mateconf_key_for_workspace_name (int i); + +static void queue_changed (MetaPreference pref); + +typedef enum + { + META_LIST_OF_STRINGS, + META_LIST_OF_MATECONFVALUE_STRINGS + } MetaStringListType; + +static gboolean update_list_binding (MetaKeyPref *binding, + GSList *value, + MetaStringListType type_of_value); + +static void cleanup_error (GError **error); +static gboolean get_bool (const char *key, gboolean *val); +static void maybe_give_disable_workarounds_warning (void); + +static void titlebar_handler (MetaPreference, const gchar*, gboolean*); +static void theme_name_handler (MetaPreference, const gchar*, gboolean*); +static void mouse_button_mods_handler (MetaPreference, const gchar*, gboolean*); +static void button_layout_handler (MetaPreference, const gchar*, gboolean*); + +#endif /* HAVE_MATECONF */ + +static gboolean update_binding (MetaKeyPref *binding, + const char *value); + +static void init_bindings (void); +static void init_commands (void); +static void init_workspace_names (void); + +#ifndef HAVE_MATECONF +static void init_button_layout (void); +#endif /* !HAVE_MATECONF */ + +#ifdef HAVE_MATECONF + +typedef struct +{ + MetaPrefsChangedFunc func; + gpointer data; +} MetaPrefsListener; + +static MateConfEnumStringPair symtab_focus_mode[] = + { + { META_FOCUS_MODE_CLICK, "click" }, + { META_FOCUS_MODE_SLOPPY, "sloppy" }, + { META_FOCUS_MODE_MOUSE, "mouse" }, + { 0, NULL }, + }; + +static MateConfEnumStringPair symtab_focus_new_windows[] = + { + { META_FOCUS_NEW_WINDOWS_SMART, "smart" }, + { META_FOCUS_NEW_WINDOWS_STRICT, "strict" }, + { 0, NULL }, + }; + +static MateConfEnumStringPair symtab_visual_bell_type[] = + { + /* Note to the reader: 0 is an invalid value; these start at 1. */ + { META_VISUAL_BELL_FULLSCREEN_FLASH, "fullscreen" }, + { META_VISUAL_BELL_FRAME_FLASH, "frame_flash" }, + { 0, NULL }, + }; + +static MateConfEnumStringPair symtab_titlebar_action[] = + { + { META_ACTION_TITLEBAR_TOGGLE_SHADE, "toggle_shade" }, + { META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE, "toggle_maximize" }, + { META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_HORIZONTALLY, + "toggle_maximize_horizontally" }, + { META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_VERTICALLY, + "toggle_maximize_vertically" }, + { META_ACTION_TITLEBAR_MINIMIZE, "minimize" }, + { META_ACTION_TITLEBAR_NONE, "none" }, + { META_ACTION_TITLEBAR_LOWER, "lower" }, + { META_ACTION_TITLEBAR_MENU, "menu" }, + { META_ACTION_TITLEBAR_TOGGLE_SHADE, "toggle_shade" }, + { 0, NULL }, + }; + +/** + * The details of one preference which is constrained to be + * one of a small number of string values-- in other words, + * an enumeration. + * + * We could have done this other ways. One particularly attractive + * possibility would have been to represent the entire symbol table + * as a space-separated string literal in the list of symtabs, so + * the focus mode enums could have been represented simply by + * "click sloppy mouse". However, the simplicity gained would have + * been outweighed by the bugs caused when the ordering of the enum + * strings got out of sync with the actual enum statement. Also, + * there is existing library code to use this kind of symbol tables. + * + * Other things we might consider doing to clean this up in the + * future include: + * + * - most of the keys begin with the same prefix, and perhaps we + * could assume it if they don't start with a slash + * + * - there are several cases where a single identifier could be used + * to generate an entire entry, and perhaps this could be done + * with a macro. (This would reduce clarity, however, and is + * probably a bad thing.) + * + * - these types all begin with a gchar* (and contain a MetaPreference) + * and we can factor out the repeated code in the handlers by taking + * advantage of this using some kind of union arrangement. + */ +typedef struct +{ + gchar *key; + MetaPreference pref; + MateConfEnumStringPair *symtab; + gpointer target; +} MetaEnumPreference; + +typedef struct +{ + gchar *key; + MetaPreference pref; + gboolean *target; + gboolean becomes_true_on_destruction; +} MetaBoolPreference; + +typedef struct +{ + gchar *key; + MetaPreference pref; + + /** + * A handler. Many of the string preferences aren't stored as + * strings and need parsing; others of them have default values + * which can't be solved in the general case. If you include a + * function pointer here, it will be called before the string + * value is written out to the target variable. + * + * The function is passed two arguments: the preference, and + * the new string as a gchar*. It returns a gboolean; + * only if this is true, the listeners will be informed that + * the preference has changed. + * + * This may be NULL. If it is, see "target", below. + */ + void (*handler) (MetaPreference pref, + const gchar *string_value, + gboolean *inform_listeners); + + /** + * Where to write the incoming string. + * + * This must be NULL if the handler is non-NULL. + * If the incoming string is NULL, no change will be made. + */ + gchar **target; + +} MetaStringPreference; + +#define METAINTPREFERENCE_NO_CHANGE_ON_DESTROY G_MININT + +typedef struct +{ + gchar *key; + MetaPreference pref; + gint *target; + /** + * Minimum and maximum values of the integer. + * If the new value is out of bounds, it will be discarded with a warning. + */ + gint minimum, maximum; + /** + * Value to use if the key is destroyed. + * If this is METAINTPREFERENCE_NO_CHANGE_ON_DESTROY, it will + * not be changed when the key is destroyed. + */ + gint value_if_destroyed; +} MetaIntPreference; + +/* FIXMEs: */ +/* @@@ Don't use NULL lines at the end; glib can tell you how big it is */ +/* @@@ /apps/marco/general should be assumed if first char is not / */ +/* @@@ Will it ever be possible to merge init and update? If not, why not? */ + +static MetaEnumPreference preferences_enum[] = + { + { "/apps/marco/general/focus_new_windows", + META_PREF_FOCUS_NEW_WINDOWS, + symtab_focus_new_windows, + &focus_new_windows, + }, + { "/apps/marco/general/focus_mode", + META_PREF_FOCUS_MODE, + symtab_focus_mode, + &focus_mode, + }, + { "/apps/marco/general/visual_bell_type", + META_PREF_VISUAL_BELL_TYPE, + symtab_visual_bell_type, + &visual_bell_type, + }, + { "/apps/marco/general/action_double_click_titlebar", + META_PREF_ACTION_DOUBLE_CLICK_TITLEBAR, + symtab_titlebar_action, + &action_double_click_titlebar, + }, + { "/apps/marco/general/action_middle_click_titlebar", + META_PREF_ACTION_MIDDLE_CLICK_TITLEBAR, + symtab_titlebar_action, + &action_middle_click_titlebar, + }, + { "/apps/marco/general/action_right_click_titlebar", + META_PREF_ACTION_RIGHT_CLICK_TITLEBAR, + symtab_titlebar_action, + &action_right_click_titlebar, + }, + { NULL, 0, NULL, NULL }, + }; + +static MetaBoolPreference preferences_bool[] = + { + { "/apps/marco/general/raise_on_click", + META_PREF_RAISE_ON_CLICK, + &raise_on_click, + TRUE, + }, + { "/apps/marco/general/titlebar_uses_system_font", + META_PREF_TITLEBAR_FONT, /* note! shares a pref */ + &use_system_font, + TRUE, + }, + { "/apps/marco/general/application_based", + META_PREF_APPLICATION_BASED, + NULL, /* feature is known but disabled */ + FALSE, + }, + { "/apps/marco/general/disable_workarounds", + META_PREF_DISABLE_WORKAROUNDS, + &disable_workarounds, + FALSE, + }, + { "/apps/marco/general/auto_raise", + META_PREF_AUTO_RAISE, + &auto_raise, + FALSE, + }, + { "/apps/marco/general/visual_bell", + META_PREF_VISUAL_BELL, + &provide_visual_bell, /* FIXME: change the name: it's confusing */ + FALSE, + }, + { "/apps/marco/general/audible_bell", + META_PREF_AUDIBLE_BELL, + &bell_is_audible, /* FIXME: change the name: it's confusing */ + FALSE, + }, + { "/apps/marco/general/reduced_resources", + META_PREF_REDUCED_RESOURCES, + &reduced_resources, + FALSE, + }, + { "/desktop/mate/interface/accessibility", + META_PREF_MATE_ACCESSIBILITY, + &mate_accessibility, + FALSE, + }, + { "/desktop/mate/interface/enable_animations", + META_PREF_MATE_ANIMATIONS, + &mate_animations, + TRUE, + }, + { "/apps/marco/general/compositing_manager", + META_PREF_COMPOSITING_MANAGER, + &compositing_manager, + FALSE, + }, + { "/apps/marco/general/resize_with_right_button", + META_PREF_RESIZE_WITH_RIGHT_BUTTON, + &resize_with_right_button, + FALSE, + }, + { NULL, 0, NULL, FALSE }, + }; + +static MetaStringPreference preferences_string[] = + { + { "/apps/marco/general/mouse_button_modifier", + META_PREF_MOUSE_BUTTON_MODS, + mouse_button_mods_handler, + NULL, + }, + { "/apps/marco/general/theme", + META_PREF_THEME, + theme_name_handler, + NULL, + }, + { KEY_TITLEBAR_FONT, + META_PREF_TITLEBAR_FONT, + titlebar_handler, + NULL, + }, + { KEY_TERMINAL_COMMAND, + META_PREF_TERMINAL_COMMAND, + NULL, + &terminal_command, + }, + { "/apps/marco/general/button_layout", + META_PREF_BUTTON_LAYOUT, + button_layout_handler, + NULL, + }, + { "/desktop/mate/peripherals/mouse/cursor_theme", + META_PREF_CURSOR_THEME, + NULL, + &cursor_theme, + }, + { NULL, 0, NULL, NULL }, + }; + +static MetaIntPreference preferences_int[] = + { + { "/apps/marco/general/num_workspaces", + META_PREF_NUM_WORKSPACES, + &num_workspaces, + /* I would actually recommend we change the destroy value to 4 + * and get rid of METAINTPREFERENCE_NO_CHANGE_ON_DESTROY entirely. + * -- tthurman + */ + 1, MAX_REASONABLE_WORKSPACES, METAINTPREFERENCE_NO_CHANGE_ON_DESTROY, + }, + { "/apps/marco/general/auto_raise_delay", + META_PREF_AUTO_RAISE_DELAY, + &auto_raise_delay, + 0, 10000, 0, + /* @@@ Get rid of MAX_REASONABLE_AUTO_RAISE_DELAY */ + }, + { "/desktop/mate/peripherals/mouse/cursor_size", + META_PREF_CURSOR_SIZE, + &cursor_size, + 1, 128, 24, + }, + { NULL, 0, NULL, 0, 0, 0, }, + }; + +static void +handle_preference_init_enum (void) +{ + MetaEnumPreference *cursor = preferences_enum; + + while (cursor->key!=NULL) + { + char *value; + GError *error = NULL; + + if (cursor->target==NULL) + { + ++cursor; + continue; + } + + value = mateconf_client_get_string (default_client, + cursor->key, + &error); + cleanup_error (&error); + + if (value==NULL) + { + ++cursor; + continue; + } + + if (!mateconf_string_to_enum (cursor->symtab, + value, + (gint *) cursor->target)) + meta_warning (_("MateConf key '%s' is set to an invalid value\n"), + cursor->key); + + g_free (value); + + ++cursor; + } +} + +static void +handle_preference_init_bool (void) +{ + MetaBoolPreference *cursor = preferences_bool; + + while (cursor->key!=NULL) + { + if (cursor->target!=NULL) + get_bool (cursor->key, cursor->target); + + ++cursor; + } + + maybe_give_disable_workarounds_warning (); +} + +static void +handle_preference_init_string (void) +{ + MetaStringPreference *cursor = preferences_string; + + while (cursor->key!=NULL) + { + char *value; + GError *error = NULL; + gboolean dummy = TRUE; + + /* the string "value" will be newly allocated */ + value = mateconf_client_get_string (default_client, + cursor->key, + &error); + cleanup_error (&error); + + if (cursor->handler) + { + if (cursor->target) + meta_bug ("%s has both a target and a handler\n", cursor->key); + + cursor->handler (cursor->pref, value, &dummy); + + g_free (value); + } + else if (cursor->target) + { + if (*(cursor->target)) + g_free (*(cursor->target)); + + *(cursor->target) = value; + } + + ++cursor; + } +} + +static void +handle_preference_init_int (void) +{ + MetaIntPreference *cursor = preferences_int; + + + while (cursor->key!=NULL) + { + gint value; + GError *error = NULL; + + value = mateconf_client_get_int (default_client, + cursor->key, + &error); + cleanup_error (&error); + + if (value < cursor->minimum || value > cursor->maximum) + { + meta_warning (_("%d stored in MateConf key %s is out of range %d to %d\n"), + value, cursor->key, cursor->minimum, cursor->maximum); + /* Former behaviour for out-of-range values was: + * - number of workspaces was clamped; + * - auto raise delay was always reset to zero even if too high!; + * - cursor size was ignored. + * + * These seem to be meaningless variations. If they did + * have meaning we could have put them into MetaIntPreference. + * The last of these is the closest to how we behave for + * other types, so I think we should standardise on that. + */ + } + else if (cursor->target) + *cursor->target = value; + + ++cursor; + } +} + +static gboolean +handle_preference_update_enum (const gchar *key, MateConfValue *value) +{ + MetaEnumPreference *cursor = preferences_enum; + gint old_value; + + while (cursor->key!=NULL && strcmp (key, cursor->key)!=0) + ++cursor; + + if (cursor->key==NULL) + /* Didn't recognise that key. */ + return FALSE; + + /* Setting it to null (that is, removing it) always means + * "don't change". + */ + + if (value==NULL) + return TRUE; + + /* Check the type. Enums are always strings. */ + + if (value->type != MATECONF_VALUE_STRING) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + /* But we did recognise it. */ + return TRUE; + } + + /* We need to know whether the value changes, so + * store the current value away. + */ + + old_value = * ((gint *) cursor->target); + + /* Now look it up... */ + + if (!mateconf_string_to_enum (cursor->symtab, + mateconf_value_get_string (value), + (gint *) cursor->target)) + { + /* + * We found it, but it was invalid. Complain. + * + * FIXME: This replicates the original behaviour, but in the future + * we might consider reverting invalid keys to their original values. + * (We know the old value, so we can look up a suitable string in + * the symtab.) + * + * (Empty comment follows so the translators don't see this.) + */ + + /* */ + meta_warning (_("MateConf key '%s' is set to an invalid value\n"), + key); + return TRUE; + } + + /* Did it change? If so, tell the listeners about it. */ + + if (old_value != *((gint *) cursor->target)) + queue_changed (cursor->pref); + + return TRUE; +} + +static gboolean +handle_preference_update_bool (const gchar *key, MateConfValue *value) +{ + MetaBoolPreference *cursor = preferences_bool; + gboolean old_value; + + while (cursor->key!=NULL && strcmp (key, cursor->key)!=0) + ++cursor; + + if (cursor->key==NULL) + /* Didn't recognise that key. */ + return FALSE; + + if (cursor->target==NULL) + /* No work for us to do. */ + return TRUE; + + if (value==NULL) + { + /* Value was destroyed; let's get out of here. */ + + if (cursor->becomes_true_on_destruction) + /* This preserves the behaviour of the old system, but + * for all I know that might have been an oversight. + */ + *((gboolean *)cursor->target) = TRUE; + + return TRUE; + } + + /* Check the type. */ + + if (value->type != MATECONF_VALUE_BOOL) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + /* But we did recognise it. */ + return TRUE; + } + + /* We need to know whether the value changes, so + * store the current value away. + */ + + old_value = * ((gboolean *) cursor->target); + + /* Now look it up... */ + + *((gboolean *) cursor->target) = mateconf_value_get_bool (value); + + /* Did it change? If so, tell the listeners about it. */ + + if (old_value != *((gboolean *) cursor->target)) + queue_changed (cursor->pref); + + if (cursor->pref==META_PREF_DISABLE_WORKAROUNDS) + maybe_give_disable_workarounds_warning (); + + return TRUE; +} + +static gboolean +handle_preference_update_string (const gchar *key, MateConfValue *value) +{ + MetaStringPreference *cursor = preferences_string; + const gchar *value_as_string; + gboolean inform_listeners = TRUE; + + while (cursor->key!=NULL && strcmp (key, cursor->key)!=0) + ++cursor; + + if (cursor->key==NULL) + /* Didn't recognise that key. */ + return FALSE; + + if (value==NULL) + return TRUE; + + /* Check the type. */ + + if (value->type != MATECONF_VALUE_STRING) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + /* But we did recognise it. */ + return TRUE; + } + + /* Docs: "The returned string is not a copy, don't try to free it." */ + value_as_string = mateconf_value_get_string (value); + + if (cursor->handler) + cursor->handler (cursor->pref, value_as_string, &inform_listeners); + else if (cursor->target) + { + if (*(cursor->target)) + g_free(*(cursor->target)); + + if (value_as_string!=NULL) + *(cursor->target) = g_strdup (value_as_string); + else + *(cursor->target) = NULL; + + inform_listeners = + (value_as_string==NULL && *(cursor->target)==NULL) || + (value_as_string!=NULL && *(cursor->target)!=NULL && + strcmp (value_as_string, *(cursor->target))==0); + } + + if (inform_listeners) + queue_changed (cursor->pref); + + return TRUE; +} + +static gboolean +handle_preference_update_int (const gchar *key, MateConfValue *value) +{ + MetaIntPreference *cursor = preferences_int; + gint new_value; + + while (cursor->key!=NULL && strcmp (key, cursor->key)!=0) + ++cursor; + + if (cursor->key==NULL) + /* Didn't recognise that key. */ + return FALSE; + + if (cursor->target==NULL) + /* No work for us to do. */ + return TRUE; + + if (value==NULL) + { + /* Value was destroyed. */ + + if (cursor->value_if_destroyed != METAINTPREFERENCE_NO_CHANGE_ON_DESTROY) + *((gint *)cursor->target) = cursor->value_if_destroyed; + + return TRUE; + } + + /* Check the type. */ + + if (value->type != MATECONF_VALUE_INT) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + /* But we did recognise it. */ + return TRUE; + } + + new_value = mateconf_value_get_int (value); + + if (new_value < cursor->minimum || new_value > cursor->maximum) + { + meta_warning (_("%d stored in MateConf key %s is out of range %d to %d\n"), + new_value, cursor->key, + cursor->minimum, cursor->maximum); + return TRUE; + } + + /* Did it change? If so, tell the listeners about it. */ + + if (*cursor->target != new_value) + { + *cursor->target = new_value; + queue_changed (cursor->pref); + } + + return TRUE; + +} + + +/****************************************************************************/ +/* Listeners. */ +/****************************************************************************/ + +void +meta_prefs_add_listener (MetaPrefsChangedFunc func, + gpointer data) +{ + MetaPrefsListener *l; + + l = g_new (MetaPrefsListener, 1); + l->func = func; + l->data = data; + + listeners = g_list_prepend (listeners, l); +} + +void +meta_prefs_remove_listener (MetaPrefsChangedFunc func, + gpointer data) +{ + GList *tmp; + + tmp = listeners; + while (tmp != NULL) + { + MetaPrefsListener *l = tmp->data; + + if (l->func == func && + l->data == data) + { + g_free (l); + listeners = g_list_delete_link (listeners, tmp); + + return; + } + + tmp = tmp->next; + } + + meta_bug ("Did not find listener to remove\n"); +} + +static void +emit_changed (MetaPreference pref) +{ + GList *tmp; + GList *copy; + + meta_topic (META_DEBUG_PREFS, "Notifying listeners that pref %s changed\n", + meta_preference_to_string (pref)); + + copy = g_list_copy (listeners); + + tmp = copy; + + while (tmp != NULL) + { + MetaPrefsListener *l = tmp->data; + + (* l->func) (pref, l->data); + + tmp = tmp->next; + } + + g_list_free (copy); +} + +static gboolean +changed_idle_handler (gpointer data) +{ + GList *tmp; + GList *copy; + + changed_idle = 0; + + copy = g_list_copy (changes); /* reentrancy paranoia */ + + g_list_free (changes); + changes = NULL; + + tmp = copy; + while (tmp != NULL) + { + MetaPreference pref = GPOINTER_TO_INT (tmp->data); + + emit_changed (pref); + + tmp = tmp->next; + } + + g_list_free (copy); + + return FALSE; +} + +static void +queue_changed (MetaPreference pref) +{ + meta_topic (META_DEBUG_PREFS, "Queueing change of pref %s\n", + meta_preference_to_string (pref)); + + if (g_list_find (changes, GINT_TO_POINTER (pref)) == NULL) + changes = g_list_prepend (changes, GINT_TO_POINTER (pref)); + else + meta_topic (META_DEBUG_PREFS, "Change of pref %s was already pending\n", + meta_preference_to_string (pref)); + + /* add idle at priority below the mateconf notify idle */ + if (changed_idle == 0) + changed_idle = g_idle_add_full (META_PRIORITY_PREFS_NOTIFY, + changed_idle_handler, NULL, NULL); +} + +#else /* HAVE_MATECONF */ + +void +meta_prefs_add_listener (MetaPrefsChangedFunc func, + gpointer data) +{ + /* Nothing, because they have mateconf turned off */ +} + +void +meta_prefs_remove_listener (MetaPrefsChangedFunc func, + gpointer data) +{ + /* Nothing, because they have mateconf turned off */ +} + +#endif /* HAVE_MATECONF */ + + +/****************************************************************************/ +/* Initialisation. */ +/****************************************************************************/ + +#ifdef HAVE_MATECONF +/* @@@ again, use glib's ability to tell you the size of the array */ +static gchar *mateconf_dirs_we_are_interested_in[] = { + "/apps/marco", + KEY_TERMINAL_DIR, + KEY_MATE_ACCESSIBILITY, + "/desktop/mate/peripherals/mouse", + "/desktop/mate/interface", + NULL, +}; +#endif + +void +meta_prefs_init (void) +{ +#ifdef HAVE_MATECONF + GError *err = NULL; + gchar **mateconf_dir_cursor; + + if (default_client != NULL) + return; + + /* returns a reference which we hold forever */ + default_client = mateconf_client_get_default (); + + for (mateconf_dir_cursor=mateconf_dirs_we_are_interested_in; + *mateconf_dir_cursor!=NULL; + mateconf_dir_cursor++) + { + mateconf_client_add_dir (default_client, + *mateconf_dir_cursor, + MATECONF_CLIENT_PRELOAD_RECURSIVE, + &err); + cleanup_error (&err); + } + + /* Pick up initial values. */ + + handle_preference_init_enum (); + handle_preference_init_bool (); + handle_preference_init_string (); + handle_preference_init_int (); + + /* @@@ Is there any reason we don't do the add_dir here? */ + for (mateconf_dir_cursor=mateconf_dirs_we_are_interested_in; + *mateconf_dir_cursor!=NULL; + mateconf_dir_cursor++) + { + mateconf_client_notify_add (default_client, + *mateconf_dir_cursor, + change_notify, + NULL, + NULL, + &err); + cleanup_error (&err); + } + +#else /* HAVE_MATECONF */ + + /* Set defaults for some values that can't be set at initialization time of + * the static globals. In the case of the theme, note that there is code + * elsewhere that will do everything possible to fallback to an existing theme + * if the one here does not exist. + */ + titlebar_font = pango_font_description_from_string ("Sans Bold 10"); + current_theme = g_strdup ("ClearlooksRe"); + + init_button_layout(); +#endif /* HAVE_MATECONF */ + + init_bindings (); + init_commands (); + init_workspace_names (); +} + + +/****************************************************************************/ +/* Updates. */ +/****************************************************************************/ + +#ifdef HAVE_MATECONF + +gboolean (*preference_update_handler[]) (const gchar*, MateConfValue*) = { + handle_preference_update_enum, + handle_preference_update_bool, + handle_preference_update_string, + handle_preference_update_int, + NULL +}; + +static void +change_notify (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + const char *key; + MateConfValue *value; + gint i=0; + + key = mateconf_entry_get_key (entry); + value = mateconf_entry_get_value (entry); + + /* First, search for a handler that might know what to do. */ + + /* FIXME: When this is all working, since the first item in every + * array is the gchar* of the key, there's no reason we can't + * find the correct record for that key here and save code duplication. + */ + + while (preference_update_handler[i]!=NULL) + { + if (preference_update_handler[i] (key, value)) + goto out; /* Get rid of this eventually */ + + i++; + } + + if (g_str_has_prefix (key, KEY_WINDOW_BINDINGS_PREFIX) || + g_str_has_prefix (key, KEY_SCREEN_BINDINGS_PREFIX)) + { + if (g_str_has_suffix (key, KEY_LIST_BINDINGS_SUFFIX)) + { + GSList *list; + + if (value && value->type != MATECONF_VALUE_LIST) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + goto out; + } + + list = value ? mateconf_value_get_list (value) : NULL; + + if (update_key_list_binding (key, list)) + queue_changed (META_PREF_KEYBINDINGS); + } + else + { + const char *str; + + if (value && value->type != MATECONF_VALUE_STRING) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + goto out; + } + + str = value ? mateconf_value_get_string (value) : NULL; + + if (update_key_binding (key, str)) + queue_changed (META_PREF_KEYBINDINGS); + } + } + else if (g_str_has_prefix (key, KEY_COMMAND_PREFIX)) + { + const char *str; + + if (value && value->type != MATECONF_VALUE_STRING) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + goto out; + } + + str = value ? mateconf_value_get_string (value) : NULL; + + if (update_command (key, str)) + queue_changed (META_PREF_COMMANDS); + } + else if (g_str_has_prefix (key, KEY_WORKSPACE_NAME_PREFIX)) + { + const char *str; + + if (value && value->type != MATECONF_VALUE_STRING) + { + meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"), + key); + goto out; + } + + str = value ? mateconf_value_get_string (value) : NULL; + + if (update_workspace_name (key, str)) + queue_changed (META_PREF_WORKSPACE_NAMES); + } + else + { + meta_topic (META_DEBUG_PREFS, "Key %s doesn't mean anything to Marco\n", + key); + } + + out: + /* nothing */ + return; /* AIX compiler wants something after a label like out: */ +} + +static void +cleanup_error (GError **error) +{ + if (*error) + { + meta_warning ("%s\n", (*error)->message); + + g_error_free (*error); + *error = NULL; + } +} + +/* get_bool returns TRUE if *val is filled in, FALSE otherwise */ +/* @@@ probably worth moving this inline; only used once */ +static gboolean +get_bool (const char *key, gboolean *val) +{ + GError *err = NULL; + MateConfValue *value; + gboolean filled_in = FALSE; + + value = mateconf_client_get (default_client, key, &err); + cleanup_error (&err); + if (value) + { + if (value->type == MATECONF_VALUE_BOOL) + { + *val = mateconf_value_get_bool (value); + filled_in = TRUE; + } + mateconf_value_free (value); + } + + return filled_in; +} + +/** + * Special case: give a warning the first time disable_workarounds + * is turned on. + */ +static void +maybe_give_disable_workarounds_warning (void) +{ + static gboolean first_disable = TRUE; + + if (first_disable && disable_workarounds) + { + first_disable = FALSE; + + meta_warning (_("Workarounds for broken applications disabled. " + "Some applications may not behave properly.\n")); + } +} + +#endif /* HAVE_MATECONF */ + +MetaVirtualModifier +meta_prefs_get_mouse_button_mods (void) +{ + return mouse_button_mods; +} + +MetaFocusMode +meta_prefs_get_focus_mode (void) +{ + return focus_mode; +} + +MetaFocusNewWindows +meta_prefs_get_focus_new_windows (void) +{ + return focus_new_windows; +} + +gboolean +meta_prefs_get_raise_on_click (void) +{ + /* Force raise_on_click on for click-to-focus, as requested by Havoc + * in #326156. + */ + return raise_on_click || focus_mode == META_FOCUS_MODE_CLICK; +} + +const char* +meta_prefs_get_theme (void) +{ + return current_theme; +} + +const char* +meta_prefs_get_cursor_theme (void) +{ + return cursor_theme; +} + +int +meta_prefs_get_cursor_size (void) +{ + return cursor_size; +} + + +/****************************************************************************/ +/* Handlers for string preferences. */ +/****************************************************************************/ + +#ifdef HAVE_MATECONF + +static void +titlebar_handler (MetaPreference pref, + const gchar *string_value, + gboolean *inform_listeners) +{ + PangoFontDescription *new_desc = NULL; + + if (string_value) + new_desc = pango_font_description_from_string (string_value); + + if (new_desc == NULL) + { + meta_warning (_("Could not parse font description " + "\"%s\" from MateConf key %s\n"), + string_value ? string_value : "(null)", + KEY_TITLEBAR_FONT); + + *inform_listeners = FALSE; + + return; + } + + /* Is the new description the same as the old? */ + + if (titlebar_font && + pango_font_description_equal (new_desc, titlebar_font)) + { + pango_font_description_free (new_desc); + *inform_listeners = FALSE; + return; + } + + /* No, so free the old one and put ours in instead. */ + + if (titlebar_font) + pango_font_description_free (titlebar_font); + + titlebar_font = new_desc; + +} + +static void +theme_name_handler (MetaPreference pref, + const gchar *string_value, + gboolean *inform_listeners) +{ + g_free (current_theme); + + /* Fallback crackrock */ + if (string_value == NULL) + current_theme = g_strdup ("ClearlooksRe"); + else + current_theme = g_strdup (string_value); +} + +static void +mouse_button_mods_handler (MetaPreference pref, + const gchar *string_value, + gboolean *inform_listeners) +{ + MetaVirtualModifier mods; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Mouse button modifier has new mateconf value \"%s\"\n", + string_value); + if (string_value && meta_ui_parse_modifier (string_value, &mods)) + { + mouse_button_mods = mods; + } + else + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Failed to parse new mateconf value\n"); + + meta_warning (_("\"%s\" found in configuration database is " + "not a valid value for mouse button modifier\n"), + string_value); + + *inform_listeners = FALSE; + } +} + +static gboolean +button_layout_equal (const MetaButtonLayout *a, + const MetaButtonLayout *b) +{ + int i; + + i = 0; + while (i < MAX_BUTTONS_PER_CORNER) + { + if (a->left_buttons[i] != b->left_buttons[i]) + return FALSE; + if (a->right_buttons[i] != b->right_buttons[i]) + return FALSE; + if (a->left_buttons_has_spacer[i] != b->left_buttons_has_spacer[i]) + return FALSE; + if (a->right_buttons_has_spacer[i] != b->right_buttons_has_spacer[i]) + return FALSE; + ++i; + } + + return TRUE; +} + +static MetaButtonFunction +button_function_from_string (const char *str) +{ + /* FIXME: mateconf_string_to_enum is the obvious way to do this */ + + if (strcmp (str, "menu") == 0) + return META_BUTTON_FUNCTION_MENU; + else if (strcmp (str, "minimize") == 0) + return META_BUTTON_FUNCTION_MINIMIZE; + else if (strcmp (str, "maximize") == 0) + return META_BUTTON_FUNCTION_MAXIMIZE; + else if (strcmp (str, "close") == 0) + return META_BUTTON_FUNCTION_CLOSE; + else if (strcmp (str, "shade") == 0) + return META_BUTTON_FUNCTION_SHADE; + else if (strcmp (str, "above") == 0) + return META_BUTTON_FUNCTION_ABOVE; + else if (strcmp (str, "stick") == 0) + return META_BUTTON_FUNCTION_STICK; + else + /* don't know; give up */ + return META_BUTTON_FUNCTION_LAST; +} + +static MetaButtonFunction +button_opposite_function (MetaButtonFunction ofwhat) +{ + switch (ofwhat) + { + case META_BUTTON_FUNCTION_SHADE: + return META_BUTTON_FUNCTION_UNSHADE; + case META_BUTTON_FUNCTION_UNSHADE: + return META_BUTTON_FUNCTION_SHADE; + + case META_BUTTON_FUNCTION_ABOVE: + return META_BUTTON_FUNCTION_UNABOVE; + case META_BUTTON_FUNCTION_UNABOVE: + return META_BUTTON_FUNCTION_ABOVE; + + case META_BUTTON_FUNCTION_STICK: + return META_BUTTON_FUNCTION_UNSTICK; + case META_BUTTON_FUNCTION_UNSTICK: + return META_BUTTON_FUNCTION_STICK; + + default: + return META_BUTTON_FUNCTION_LAST; + } +} + +static void +button_layout_handler (MetaPreference pref, + const gchar *string_value, + gboolean *inform_listeners) +{ + MetaButtonLayout new_layout; + char **sides = NULL; + int i; + + /* We need to ignore unknown button functions, for + * compat with future versions + */ + + if (string_value) + sides = g_strsplit (string_value, ":", 2); + + if (sides != NULL && sides[0] != NULL) + { + char **buttons; + int b; + gboolean used[META_BUTTON_FUNCTION_LAST]; + + i = 0; + while (i < META_BUTTON_FUNCTION_LAST) + { + used[i] = FALSE; + new_layout.left_buttons_has_spacer[i] = FALSE; + ++i; + } + + buttons = g_strsplit (sides[0], ",", -1); + i = 0; + b = 0; + while (buttons[b] != NULL) + { + MetaButtonFunction f = button_function_from_string (buttons[b]); + if (i > 0 && strcmp("spacer", buttons[b]) == 0) + { + new_layout.left_buttons_has_spacer[i-1] = TRUE; + f = button_opposite_function (f); + + if (f != META_BUTTON_FUNCTION_LAST) + { + new_layout.left_buttons_has_spacer[i-2] = TRUE; + } + } + else + { + if (f != META_BUTTON_FUNCTION_LAST && !used[f]) + { + new_layout.left_buttons[i] = f; + used[f] = TRUE; + ++i; + + f = button_opposite_function (f); + + if (f != META_BUTTON_FUNCTION_LAST) + new_layout.left_buttons[i++] = f; + + } + else + { + meta_topic (META_DEBUG_PREFS, "Ignoring unknown or already-used button name \"%s\"\n", + buttons[b]); + } + } + + ++b; + } + + new_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST; + new_layout.left_buttons_has_spacer[i] = FALSE; + + g_strfreev (buttons); + } + + if (sides != NULL && sides[0] != NULL && sides[1] != NULL) + { + char **buttons; + int b; + gboolean used[META_BUTTON_FUNCTION_LAST]; + + i = 0; + while (i < META_BUTTON_FUNCTION_LAST) + { + used[i] = FALSE; + new_layout.right_buttons_has_spacer[i] = FALSE; + ++i; + } + + buttons = g_strsplit (sides[1], ",", -1); + i = 0; + b = 0; + while (buttons[b] != NULL) + { + MetaButtonFunction f = button_function_from_string (buttons[b]); + if (i > 0 && strcmp("spacer", buttons[b]) == 0) + { + new_layout.right_buttons_has_spacer[i-1] = TRUE; + f = button_opposite_function (f); + if (f != META_BUTTON_FUNCTION_LAST) + { + new_layout.right_buttons_has_spacer[i-2] = TRUE; + } + } + else + { + if (f != META_BUTTON_FUNCTION_LAST && !used[f]) + { + new_layout.right_buttons[i] = f; + used[f] = TRUE; + ++i; + + f = button_opposite_function (f); + + if (f != META_BUTTON_FUNCTION_LAST) + new_layout.right_buttons[i++] = f; + + } + else + { + meta_topic (META_DEBUG_PREFS, "Ignoring unknown or already-used button name \"%s\"\n", + buttons[b]); + } + } + + ++b; + } + + new_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST; + new_layout.right_buttons_has_spacer[i] = FALSE; + + g_strfreev (buttons); + } + + g_strfreev (sides); + + /* Invert the button layout for RTL languages */ + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + { + MetaButtonLayout rtl_layout; + int j; + + for (i = 0; new_layout.left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++); + for (j = 0; j < i; j++) + { + rtl_layout.right_buttons[j] = new_layout.left_buttons[i - j - 1]; + if (j == 0) + rtl_layout.right_buttons_has_spacer[i - 1] = new_layout.left_buttons_has_spacer[i - j - 1]; + else + rtl_layout.right_buttons_has_spacer[j - 1] = new_layout.left_buttons_has_spacer[i - j - 1]; + } + rtl_layout.right_buttons[j] = META_BUTTON_FUNCTION_LAST; + rtl_layout.right_buttons_has_spacer[j] = FALSE; + + for (i = 0; new_layout.right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++); + for (j = 0; j < i; j++) + { + rtl_layout.left_buttons[j] = new_layout.right_buttons[i - j - 1]; + if (j == 0) + rtl_layout.left_buttons_has_spacer[i - 1] = new_layout.right_buttons_has_spacer[i - j - 1]; + else + rtl_layout.left_buttons_has_spacer[j - 1] = new_layout.right_buttons_has_spacer[i - j - 1]; + } + rtl_layout.left_buttons[j] = META_BUTTON_FUNCTION_LAST; + rtl_layout.left_buttons_has_spacer[j] = FALSE; + + new_layout = rtl_layout; + } + + if (button_layout_equal (&button_layout, &new_layout)) + { + /* Same as before, so duck out */ + *inform_listeners = FALSE; + } + else + { + button_layout = new_layout; + } +} + +#endif /* HAVE_MATECONF */ + +const PangoFontDescription* +meta_prefs_get_titlebar_font (void) +{ + if (use_system_font) + return NULL; + else + return titlebar_font; +} + +int +meta_prefs_get_num_workspaces (void) +{ + return num_workspaces; +} + +gboolean +meta_prefs_get_application_based (void) +{ + return FALSE; /* For now, we never want this to do anything */ + + return application_based; +} + +gboolean +meta_prefs_get_disable_workarounds (void) +{ + return disable_workarounds; +} + +#ifdef HAVE_MATECONF +#define MAX_REASONABLE_AUTO_RAISE_DELAY 10000 + +#endif /* HAVE_MATECONF */ + +#ifdef WITH_VERBOSE_MODE +const char* +meta_preference_to_string (MetaPreference pref) +{ + /* FIXME: another case for mateconf_string_to_enum */ + switch (pref) + { + case META_PREF_MOUSE_BUTTON_MODS: + return "MOUSE_BUTTON_MODS"; + + case META_PREF_FOCUS_MODE: + return "FOCUS_MODE"; + + case META_PREF_FOCUS_NEW_WINDOWS: + return "FOCUS_NEW_WINDOWS"; + + case META_PREF_RAISE_ON_CLICK: + return "RAISE_ON_CLICK"; + + case META_PREF_THEME: + return "THEME"; + + case META_PREF_TITLEBAR_FONT: + return "TITLEBAR_FONT"; + + case META_PREF_NUM_WORKSPACES: + return "NUM_WORKSPACES"; + + case META_PREF_APPLICATION_BASED: + return "APPLICATION_BASED"; + + case META_PREF_KEYBINDINGS: + return "KEYBINDINGS"; + + case META_PREF_DISABLE_WORKAROUNDS: + return "DISABLE_WORKAROUNDS"; + + case META_PREF_ACTION_DOUBLE_CLICK_TITLEBAR: + return "ACTION_DOUBLE_CLICK_TITLEBAR"; + + case META_PREF_ACTION_MIDDLE_CLICK_TITLEBAR: + return "ACTION_MIDDLE_CLICK_TITLEBAR"; + + case META_PREF_ACTION_RIGHT_CLICK_TITLEBAR: + return "ACTION_RIGHT_CLICK_TITLEBAR"; + + case META_PREF_AUTO_RAISE: + return "AUTO_RAISE"; + + case META_PREF_AUTO_RAISE_DELAY: + return "AUTO_RAISE_DELAY"; + + case META_PREF_COMMANDS: + return "COMMANDS"; + + case META_PREF_TERMINAL_COMMAND: + return "TERMINAL_COMMAND"; + + case META_PREF_BUTTON_LAYOUT: + return "BUTTON_LAYOUT"; + + case META_PREF_WORKSPACE_NAMES: + return "WORKSPACE_NAMES"; + + case META_PREF_VISUAL_BELL: + return "VISUAL_BELL"; + + case META_PREF_AUDIBLE_BELL: + return "AUDIBLE_BELL"; + + case META_PREF_VISUAL_BELL_TYPE: + return "VISUAL_BELL_TYPE"; + + case META_PREF_REDUCED_RESOURCES: + return "REDUCED_RESOURCES"; + + case META_PREF_MATE_ACCESSIBILITY: + return "MATE_ACCESSIBILTY"; + + case META_PREF_MATE_ANIMATIONS: + return "MATE_ANIMATIONS"; + + case META_PREF_CURSOR_THEME: + return "CURSOR_THEME"; + + case META_PREF_CURSOR_SIZE: + return "CURSOR_SIZE"; + + case META_PREF_COMPOSITING_MANAGER: + return "COMPOSITING_MANAGER"; + + case META_PREF_RESIZE_WITH_RIGHT_BUTTON: + return "RESIZE_WITH_RIGHT_BUTTON"; + + case META_PREF_FORCE_FULLSCREEN: + return "FORCE_FULLSCREEN"; + } + + return "(unknown)"; +} +#endif /* WITH_VERBOSE_MODE */ + +void +meta_prefs_set_num_workspaces (int n_workspaces) +{ +#ifdef HAVE_MATECONF + GError *err; + + if (default_client == NULL) + return; + + if (n_workspaces < 1) + n_workspaces = 1; + if (n_workspaces > MAX_REASONABLE_WORKSPACES) + n_workspaces = MAX_REASONABLE_WORKSPACES; + + err = NULL; + mateconf_client_set_int (default_client, + KEY_NUM_WORKSPACES, + n_workspaces, + &err); + + if (err) + { + meta_warning (_("Error setting number of workspaces to %d: %s\n"), + num_workspaces, + err->message); + g_error_free (err); + } +#endif /* HAVE_MATECONF */ +} + +#define keybind(name, handler, param, flags, stroke, description) \ + { #name, NULL, !!(flags & BINDING_REVERSES), !!(flags & BINDING_PER_WINDOW) }, +static MetaKeyPref key_bindings[] = { +#include "all-keybindings.h" + { NULL, NULL, FALSE } +}; +#undef keybind + +#ifndef HAVE_MATECONF + +/** + * A type to map names of keybindings (such as "switch_windows") + * to the binding strings themselves (such as "<Alt>Tab"). + * It exists only when MateConf is turned off in ./configure and + * functions as a sort of ersatz MateConf. + */ +typedef struct +{ + const char *name; + const char *keybinding; +} MetaSimpleKeyMapping; + +/* FIXME: This would be neater if the array only contained entries whose + * default keystroke was non-null. You COULD do this by defining + * ONLY_BOUND_BY_DEFAULT around various blocks at the cost of making + * the bindings file way more complicated. However, we could stop this being + * data and move it into code. Then the compiler would optimise away + * the problem lines. + */ + +#define keybind(name, handler, param, flags, stroke, description) \ + { #name, stroke }, + +static MetaSimpleKeyMapping key_string_bindings[] = { +#include "all-keybindings.h" + { NULL, NULL } +}; +#undef keybind + +#endif /* NOT HAVE_MATECONF */ + +static void +init_bindings (void) +{ +#ifdef HAVE_MATECONF + const char *prefix[] = { + KEY_WINDOW_BINDINGS_PREFIX, + KEY_SCREEN_BINDINGS_PREFIX, + NULL + }; + int i; + GSList *list, *l, *list_val; + const char *str_val; + const char *key; + MateConfEntry *entry; + MateConfValue *value; + + for (i = 0; prefix[i]; i++) + { + list = mateconf_client_all_entries (default_client, prefix[i], NULL); + for (l = list; l; l = l->next) + { + entry = l->data; + key = mateconf_entry_get_key (entry); + value = mateconf_entry_get_value (entry); + if (g_str_has_suffix (key, KEY_LIST_BINDINGS_SUFFIX)) + { + list_val = mateconf_client_get_list (default_client, key, MATECONF_VALUE_STRING, NULL); + + update_key_list_binding (key, list_val); + g_slist_foreach (list_val, (GFunc)g_free, NULL); + g_slist_free (list_val); + } + else + { + str_val = mateconf_value_get_string (value); + update_key_binding (key, str_val); + } + mateconf_entry_free (entry); + } + g_slist_free (list); + } +#else /* HAVE_MATECONF */ + int i = 0; + int which = 0; + while (key_string_bindings[i].name) + { + if (key_string_bindings[i].keybinding == NULL) { + ++i; + continue; + } + + while (strcmp(key_bindings[which].name, + key_string_bindings[i].name) != 0) + which++; + + /* Set the binding */ + update_binding (&key_bindings[which], + key_string_bindings[i].keybinding); + + ++i; + } +#endif /* HAVE_MATECONF */ +} + +static void +init_commands (void) +{ +#ifdef HAVE_MATECONF + GSList *list, *l; + const char *str_val; + const char *key; + MateConfEntry *entry; + MateConfValue *value; + + list = mateconf_client_all_entries (default_client, KEY_COMMAND_DIRECTORY, NULL); + for (l = list; l; l = l->next) + { + entry = l->data; + key = mateconf_entry_get_key (entry); + value = mateconf_entry_get_value (entry); + str_val = mateconf_value_get_string (value); + update_command (key, str_val); + mateconf_entry_free (entry); + } + g_slist_free (list); +#else + int i; + for (i = 0; i < MAX_COMMANDS; i++) + commands[i] = NULL; +#endif /* HAVE_MATECONF */ +} + +static void +init_workspace_names (void) +{ +#ifdef HAVE_MATECONF + GSList *list, *l; + const char *str_val; + const char *key; + MateConfEntry *entry; + MateConfValue *value; + + list = mateconf_client_all_entries (default_client, KEY_WORKSPACE_NAME_DIRECTORY, NULL); + for (l = list; l; l = l->next) + { + entry = l->data; + key = mateconf_entry_get_key (entry); + value = mateconf_entry_get_value (entry); + str_val = mateconf_value_get_string (value); + update_workspace_name (key, str_val); + mateconf_entry_free (entry); + } + g_slist_free (list); +#else + int i; + for (i = 0; i < MAX_REASONABLE_WORKSPACES; i++) + workspace_names[i] = g_strdup_printf (_("Workspace %d"), i + 1); + + meta_topic (META_DEBUG_PREFS, + "Initialized workspace names\n"); +#endif /* HAVE_MATECONF */ +} + +static gboolean +update_binding (MetaKeyPref *binding, + const char *value) +{ + unsigned int keysym; + unsigned int keycode; + MetaVirtualModifier mods; + MetaKeyCombo *combo; + gboolean changed; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Binding \"%s\" has new mateconf value \"%s\"\n", + binding->name, value ? value : "none"); + + keysym = 0; + keycode = 0; + mods = 0; + if (value) + { + if (!meta_ui_parse_accelerator (value, &keysym, &keycode, &mods)) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Failed to parse new mateconf value\n"); + meta_warning (_("\"%s\" found in configuration database is not a valid value for keybinding \"%s\"\n"), + value, binding->name); + + return FALSE; + } + } + + /* If there isn't already a first element, make one. */ + if (!binding->bindings) + { + MetaKeyCombo *blank = g_malloc0 (sizeof (MetaKeyCombo)); + binding->bindings = g_slist_alloc(); + binding->bindings->data = blank; + } + + combo = binding->bindings->data; + +#ifdef HAVE_MATECONF + /* Bug 329676: Bindings which can be shifted must not have no modifiers, + * nor only SHIFT as a modifier. + */ + + if (binding->add_shift && + 0 != keysym && + (META_VIRTUAL_SHIFT_MASK == mods || 0 == mods)) + { + gchar *old_setting; + gchar *key; + GError *err = NULL; + + meta_warning ("Cannot bind \"%s\" to %s: it needs a modifier " + "such as Ctrl or Alt.\n", + binding->name, + value); + + old_setting = meta_ui_accelerator_name (combo->keysym, + combo->modifiers); + + if (!strcmp(old_setting, value)) + { + /* We were about to set it to the same value + * that it had originally! This must be caused + * by getting an invalid string back from + * meta_ui_accelerator_name. Bail out now + * so we don't get into an infinite loop. + */ + g_free (old_setting); + return TRUE; + } + + meta_warning ("Reverting \"%s\" to %s.\n", + binding->name, + old_setting); + + /* FIXME: add_shift is currently screen_bindings only, but + * there's no really good reason it should always be. + * So we shouldn't blindly add KEY_SCREEN_BINDINGS_PREFIX + * onto here. + */ + key = g_strconcat (KEY_SCREEN_BINDINGS_PREFIX, "/", + binding->name, NULL); + + mateconf_client_set_string (mateconf_client_get_default (), + key, old_setting, &err); + + if (err) + { + meta_warning ("Error while reverting keybinding: %s\n", + err->message); + g_error_free (err); + err = NULL; + } + + g_free (old_setting); + g_free (key); + + /* The call to mateconf_client_set_string() will cause this function + * to be called again with the new value, so there's no need to + * carry on. + */ + return TRUE; + } +#endif + + changed = FALSE; + if (keysym != combo->keysym || + keycode != combo->keycode || + mods != combo->modifiers) + { + changed = TRUE; + + combo->keysym = keysym; + combo->keycode = keycode; + combo->modifiers = mods; + + meta_topic (META_DEBUG_KEYBINDINGS, + "New keybinding for \"%s\" is keysym = 0x%x keycode = 0x%x mods = 0x%x\n", + binding->name, combo->keysym, combo->keycode, + combo->modifiers); + } + else + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Keybinding for \"%s\" is unchanged\n", binding->name); + } + + return changed; +} + +#ifdef HAVE_MATECONF +static gboolean +update_list_binding (MetaKeyPref *binding, + GSList *value, + MetaStringListType type_of_value) +{ + unsigned int keysym; + unsigned int keycode; + MetaVirtualModifier mods; + gboolean changed = FALSE; + const gchar *pref_string; + GSList *pref_iterator = value, *tmp; + MetaKeyCombo *combo; + + meta_topic (META_DEBUG_KEYBINDINGS, + "Binding \"%s\" has new mateconf value\n", + binding->name); + + if (binding->bindings == NULL) + { + /* We need to insert a dummy element into the list, because the first + * element is the one governed by update_binding. We only handle the + * subsequent elements. + */ + MetaKeyCombo *blank = g_malloc0 (sizeof (MetaKeyCombo)); + binding->bindings = g_slist_alloc(); + binding->bindings->data = blank; + } + + /* Okay, so, we're about to provide a new list of key combos for this + * action. Delete any pre-existing list. + */ + tmp = binding->bindings->next; + while (tmp) + { + g_free (tmp->data); + tmp = tmp->next; + } + g_slist_free (binding->bindings->next); + binding->bindings->next = NULL; + + while (pref_iterator) + { + keysym = 0; + keycode = 0; + mods = 0; + + if (!pref_iterator->data) + { + pref_iterator = pref_iterator->next; + continue; + } + + switch (type_of_value) + { + case META_LIST_OF_STRINGS: + pref_string = pref_iterator->data; + break; + case META_LIST_OF_MATECONFVALUE_STRINGS: + pref_string = mateconf_value_get_string (pref_iterator->data); + break; + default: + g_assert_not_reached (); + } + + if (!meta_ui_parse_accelerator (pref_string, &keysym, &keycode, &mods)) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Failed to parse new mateconf value\n"); + meta_warning (_("\"%s\" found in configuration database is not a valid value for keybinding \"%s\"\n"), + pref_string, binding->name); + + /* Should we remove this value from the list in mateconf? */ + pref_iterator = pref_iterator->next; + continue; + } + + /* Bug 329676: Bindings which can be shifted must not have no modifiers, + * nor only SHIFT as a modifier. + */ + + if (binding->add_shift && + 0 != keysym && + (META_VIRTUAL_SHIFT_MASK == mods || 0 == mods)) + { + meta_warning ("Cannot bind \"%s\" to %s: it needs a modifier " + "such as Ctrl or Alt.\n", + binding->name, + pref_string); + + /* Should we remove this value from the list in mateconf? */ + + pref_iterator = pref_iterator->next; + continue; + } + + changed = TRUE; + + combo = g_malloc0 (sizeof (MetaKeyCombo)); + combo->keysym = keysym; + combo->keycode = keycode; + combo->modifiers = mods; + binding->bindings->next = g_slist_prepend (binding->bindings->next, combo); + + meta_topic (META_DEBUG_KEYBINDINGS, + "New keybinding for \"%s\" is keysym = 0x%x keycode = 0x%x mods = 0x%x\n", + binding->name, keysym, keycode, mods); + + pref_iterator = pref_iterator->next; + } + return changed; +} + +static const gchar* +relative_key (const gchar* key) +{ + const gchar* end; + + end = strrchr (key, '/'); + + ++end; + + return end; +} + +/* Return value is TRUE if a preference changed and we need to + * notify + */ +static gboolean +find_and_update_binding (MetaKeyPref *bindings, + const char *name, + const char *value) +{ + const char *key; + int i; + + if (*name == '/') + key = relative_key (name); + else + key = name; + + i = 0; + while (bindings[i].name && + strcmp (key, bindings[i].name) != 0) + ++i; + + if (bindings[i].name) + return update_binding (&bindings[i], value); + else + return FALSE; +} + +static gboolean +update_key_binding (const char *name, + const char *value) +{ + return find_and_update_binding (key_bindings, name, value); +} + +static gboolean +find_and_update_list_binding (MetaKeyPref *bindings, + const char *name, + GSList *value) +{ + const char *key; + int i; + gchar *name_without_suffix = g_strdup(name); + + name_without_suffix[strlen(name_without_suffix) - strlen(KEY_LIST_BINDINGS_SUFFIX)] = 0; + + if (*name_without_suffix == '/') + key = relative_key (name_without_suffix); + else + key = name_without_suffix; + + i = 0; + while (bindings[i].name && + strcmp (key, bindings[i].name) != 0) + ++i; + + g_free (name_without_suffix); + + if (bindings[i].name) + return update_list_binding (&bindings[i], value, META_LIST_OF_MATECONFVALUE_STRINGS); + else + return FALSE; +} + +static gboolean +update_key_list_binding (const char *name, + GSList *value) +{ + return find_and_update_list_binding (key_bindings, name, value); +} + +static gboolean +update_command (const char *name, + const char *value) +{ + char *p; + int i; + + p = strrchr (name, '_'); + if (p == NULL) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Command %s has no underscore?\n", name); + return FALSE; + } + + ++p; + + if (g_ascii_isdigit (*p)) + { + i = atoi (p); + i -= 1; /* count from 0 not 1 */ + } + else + { + p = strrchr (name, '/'); + ++p; + + if (strcmp (p, "command_screenshot") == 0) + { + i = SCREENSHOT_COMMAND_IDX; + } + else if (strcmp (p, "command_window_screenshot") == 0) + { + i = WIN_SCREENSHOT_COMMAND_IDX; + } + else + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Command %s doesn't end in number?\n", name); + return FALSE; + } + } + + if (i >= MAX_COMMANDS) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Command %d is too highly numbered, ignoring\n", i); + return FALSE; + } + + if ((commands[i] == NULL && value == NULL) || + (commands[i] && value && strcmp (commands[i], value) == 0)) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Command %d is unchanged\n", i); + return FALSE; + } + + g_free (commands[i]); + commands[i] = g_strdup (value); + + meta_topic (META_DEBUG_KEYBINDINGS, + "Updated command %d to \"%s\"\n", + i, commands[i] ? commands[i] : "none"); + + return TRUE; +} + +#endif /* HAVE_MATECONF */ + +const char* +meta_prefs_get_command (int i) +{ + g_return_val_if_fail (i >= 0 && i < MAX_COMMANDS, NULL); + + return commands[i]; +} + +char* +meta_prefs_get_mateconf_key_for_command (int i) +{ + char *key; + + switch (i) + { + case SCREENSHOT_COMMAND_IDX: + key = g_strdup (KEY_COMMAND_PREFIX "screenshot"); + break; + case WIN_SCREENSHOT_COMMAND_IDX: + key = g_strdup (KEY_COMMAND_PREFIX "window_screenshot"); + break; + default: + key = g_strdup_printf (KEY_COMMAND_PREFIX"%d", i + 1); + break; + } + + return key; +} + +const char* +meta_prefs_get_terminal_command (void) +{ + return terminal_command; +} + +const char* +meta_prefs_get_mateconf_key_for_terminal_command (void) +{ + return KEY_TERMINAL_COMMAND; +} + +#ifdef HAVE_MATECONF +static gboolean +update_workspace_name (const char *name, + const char *value) +{ + char *p; + int i; + + p = strrchr (name, '_'); + if (p == NULL) + { + meta_topic (META_DEBUG_PREFS, + "Workspace name %s has no underscore?\n", name); + return FALSE; + } + + ++p; + + if (!g_ascii_isdigit (*p)) + { + meta_topic (META_DEBUG_PREFS, + "Workspace name %s doesn't end in number?\n", name); + return FALSE; + } + + i = atoi (p); + i -= 1; /* count from 0 not 1 */ + + if (i >= MAX_REASONABLE_WORKSPACES) + { + meta_topic (META_DEBUG_PREFS, + "Workspace name %d is too highly numbered, ignoring\n", i); + return FALSE; + } + + if (workspace_names[i] && value && strcmp (workspace_names[i], value) == 0) + { + meta_topic (META_DEBUG_PREFS, + "Workspace name %d is unchanged\n", i); + return FALSE; + } + + /* This is a bad hack. We have to treat empty string as + * "unset" because the root window property can't contain + * null. So it gets empty string instead and we don't want + * that to result in setting the empty string as a value that + * overrides "unset". + */ + if (value != NULL && *value != '\0') + { + g_free (workspace_names[i]); + workspace_names[i] = g_strdup (value); + } + else + { + /* use a default name */ + char *d; + + d = g_strdup_printf (_("Workspace %d"), i + 1); + if (workspace_names[i] && strcmp (workspace_names[i], d) == 0) + { + g_free (d); + return FALSE; + } + else + { + g_free (workspace_names[i]); + workspace_names[i] = d; + } + } + + meta_topic (META_DEBUG_PREFS, + "Updated workspace name %d to \"%s\"\n", + i, workspace_names[i] ? workspace_names[i] : "none"); + + return TRUE; +} +#endif /* HAVE_MATECONF */ + +const char* +meta_prefs_get_workspace_name (int i) +{ + g_return_val_if_fail (i >= 0 && i < MAX_REASONABLE_WORKSPACES, NULL); + + g_assert (workspace_names[i] != NULL); + + meta_topic (META_DEBUG_PREFS, + "Getting workspace name for %d: \"%s\"\n", + i, workspace_names[i]); + + return workspace_names[i]; +} + +void +meta_prefs_change_workspace_name (int i, + const char *name) +{ +#ifdef HAVE_MATECONF + char *key; + GError *err; + + g_return_if_fail (i >= 0 && i < MAX_REASONABLE_WORKSPACES); + + meta_topic (META_DEBUG_PREFS, + "Changing name of workspace %d to %s\n", + i, name ? name : "none"); + + /* This is a bad hack. We have to treat empty string as + * "unset" because the root window property can't contain + * null. So it gets empty string instead and we don't want + * that to result in setting the empty string as a value that + * overrides "unset". + */ + if (name && *name == '\0') + name = NULL; + + if ((name == NULL && workspace_names[i] == NULL) || + (name && workspace_names[i] && strcmp (name, workspace_names[i]) == 0)) + { + meta_topic (META_DEBUG_PREFS, + "Workspace %d already has name %s\n", + i, name ? name : "none"); + return; + } + + key = mateconf_key_for_workspace_name (i); + + err = NULL; + if (name != NULL) + mateconf_client_set_string (default_client, + key, name, + &err); + else + mateconf_client_unset (default_client, + key, &err); + + + if (err) + { + meta_warning (_("Error setting name for workspace %d to \"%s\": %s\n"), + i, name ? name : "none", + err->message); + g_error_free (err); + } + + g_free (key); +#else + g_free (workspace_names[i]); + workspace_names[i] = g_strdup (name); +#endif /* HAVE_MATECONF */ +} + +#ifdef HAVE_MATECONF +static char* +mateconf_key_for_workspace_name (int i) +{ + char *key; + + key = g_strdup_printf (KEY_WORKSPACE_NAME_PREFIX"%d", i + 1); + + return key; +} +#endif /* HAVE_MATECONF */ + +void +meta_prefs_get_button_layout (MetaButtonLayout *button_layout_p) +{ + *button_layout_p = button_layout; +} + +gboolean +meta_prefs_get_visual_bell (void) +{ + return provide_visual_bell; +} + +gboolean +meta_prefs_bell_is_audible (void) +{ + return bell_is_audible; +} + +MetaVisualBellType +meta_prefs_get_visual_bell_type (void) +{ + return visual_bell_type; +} + +void +meta_prefs_get_key_bindings (const MetaKeyPref **bindings, + int *n_bindings) +{ + + *bindings = key_bindings; + *n_bindings = (int) G_N_ELEMENTS (key_bindings) - 1; +} + +MetaActionTitlebar +meta_prefs_get_action_double_click_titlebar (void) +{ + return action_double_click_titlebar; +} + +MetaActionTitlebar +meta_prefs_get_action_middle_click_titlebar (void) +{ + return action_middle_click_titlebar; +} + +MetaActionTitlebar +meta_prefs_get_action_right_click_titlebar (void) +{ + return action_right_click_titlebar; +} + +gboolean +meta_prefs_get_auto_raise (void) +{ + return auto_raise; +} + +int +meta_prefs_get_auto_raise_delay (void) +{ + return auto_raise_delay; +} + +gboolean +meta_prefs_get_reduced_resources (void) +{ + return reduced_resources; +} + +gboolean +meta_prefs_get_mate_accessibility () +{ + return mate_accessibility; +} + +gboolean +meta_prefs_get_mate_animations () +{ + return mate_animations; +} + +MetaKeyBindingAction +meta_prefs_get_keybinding_action (const char *name) +{ + int i; + + i = G_N_ELEMENTS (key_bindings) - 2; /* -2 for dummy entry at end */ + while (i >= 0) + { + if (strcmp (key_bindings[i].name, name) == 0) + return (MetaKeyBindingAction) i; + + --i; + } + + return META_KEYBINDING_ACTION_NONE; +} + +/* This is used by the menu system to decide what key binding + * to display next to an option. We return the first non-disabled + * binding, if any. + */ +void +meta_prefs_get_window_binding (const char *name, + unsigned int *keysym, + MetaVirtualModifier *modifiers) +{ + int i; + + i = G_N_ELEMENTS (key_bindings) - 2; /* -2 for dummy entry at end */ + while (i >= 0) + { + if (key_bindings[i].per_window && + strcmp (key_bindings[i].name, name) == 0) + { + GSList *s = key_bindings[i].bindings; + + while (s) + { + MetaKeyCombo *c = s->data; + + if (c->keysym!=0 || c->modifiers!=0) + { + *keysym = c->keysym; + *modifiers = c->modifiers; + return; + } + + s = s->next; + } + + /* Not found; return the disabled value */ + *keysym = *modifiers = 0; + return; + } + + --i; + } + + g_assert_not_reached (); +} + +gboolean +meta_prefs_get_compositing_manager (void) +{ + return compositing_manager; +} + +guint +meta_prefs_get_mouse_button_resize (void) +{ + return resize_with_right_button ? 3: 2; +} + +guint +meta_prefs_get_mouse_button_menu (void) +{ + return resize_with_right_button ? 2: 3; +} + +gboolean +meta_prefs_get_force_fullscreen (void) +{ + return force_fullscreen; +} + +void +meta_prefs_set_compositing_manager (gboolean whether) +{ +#ifdef HAVE_MATECONF + GError *err = NULL; + + mateconf_client_set_bool (default_client, + KEY_COMPOSITOR, + whether, + &err); + + if (err) + { + meta_warning (_("Error setting compositor status: %s\n"), + err->message); + g_error_free (err); + } +#else + compositing_manager = whether; +#endif +} + +#ifndef HAVE_MATECONF +static void +init_button_layout(void) +{ + MetaButtonLayout button_layout_ltr = { + { + /* buttons in the group on the left side */ + META_BUTTON_FUNCTION_MENU, + META_BUTTON_FUNCTION_LAST + }, + { + /* buttons in the group on the right side */ + META_BUTTON_FUNCTION_MINIMIZE, + META_BUTTON_FUNCTION_MAXIMIZE, + META_BUTTON_FUNCTION_CLOSE, + META_BUTTON_FUNCTION_LAST + } + }; + MetaButtonLayout button_layout_rtl = { + { + /* buttons in the group on the left side */ + META_BUTTON_FUNCTION_CLOSE, + META_BUTTON_FUNCTION_MAXIMIZE, + META_BUTTON_FUNCTION_MINIMIZE, + META_BUTTON_FUNCTION_LAST + }, + { + /* buttons in the group on the right side */ + META_BUTTON_FUNCTION_MENU, + META_BUTTON_FUNCTION_LAST + } + }; + + button_layout = meta_ui_get_direction() == META_UI_DIRECTION_LTR ? + button_layout_ltr : button_layout_rtl; +}; + +#endif + +void +meta_prefs_set_force_fullscreen (gboolean whether) +{ + force_fullscreen = whether; +} + diff --git a/src/core/schema-bindings.c b/src/core/schema-bindings.c new file mode 100644 index 00000000..2f1da821 --- /dev/null +++ b/src/core/schema-bindings.c @@ -0,0 +1,195 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2008 Thomas Thurman + * + * 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. + */ + +/** \file Schema bindings generator. + * + * This program simply takes the items given in the binding list in + * all-keybindings.h and turns them into a portion of + * the MateConf .schemas file. + * + * FIXME: also need to make 50-marco-desktop-key.xml + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <glib.h> +#include "config.h" + +#define _(x) x + +static void single_stanza (gboolean is_window, const char *name, + const char *default_value, + gboolean can_reverse, + const char *description); + +char *about_keybindings, *about_reversible_keybindings; + +char *source_filename, *target_filename; +FILE *source_file, *target_file; + +static void +single_stanza (gboolean is_window, const char *name, + const char *default_value, + gboolean can_reverse, + const char *description) +{ + char *keybinding_type = is_window? "window": "global"; + char *escaped_default_value, *escaped_description; + + if (description==NULL) + return; /* it must be undocumented, so it can't go in this table */ + + escaped_description = g_markup_escape_text (description, -1); + escaped_default_value = default_value==NULL? "disabled": + g_markup_escape_text (default_value, -1); + + fprintf (target_file, " <schema>\n"); + fprintf (target_file, " <key>/schemas/apps/marco/%s_keybindings/%s</key>\n", + keybinding_type, name); + fprintf (target_file, " <applyto>/apps/marco/%s_keybindings/%s</applyto>\n", + keybinding_type, name); + fprintf (target_file, " <owner>marco</owner>\n"); + fprintf (target_file, " <type>string</type>\n"); + fprintf (target_file, " <default>%s</default>\n", escaped_default_value); + + fprintf (target_file, " <locale name=\"C\">\n"); + fprintf (target_file, " <short>%s</short>\n", description); + fprintf (target_file, " <long>%s</long>\n", + can_reverse? about_reversible_keybindings: + about_keybindings); + fprintf (target_file, " </locale>\n"); + fprintf (target_file, " </schema>\n\n"); + + g_free (escaped_description); + + if (default_value!=NULL) + g_free (escaped_default_value); +} + +static void produce_bindings (); + +static void +produce_bindings () +{ + /* 10240 is ridiculous overkill; we're writing the input file and + * the lines are always 80 chars or less. + */ + char buffer[10240]; + + source_file = fopen(source_filename, "r"); + + if (!source_file) + { + g_error ("Cannot compile without %s: %s\n", + source_filename, strerror (errno)); + } + + target_file = fopen(target_filename, "w"); + + if (!target_file) + { + g_error ("Cannot create %s: %s\n", + target_filename, strerror (errno)); + } + + while (fgets (buffer, sizeof (buffer), source_file)) + { + if (strstr (buffer, "<!-- GENERATED -->")) + break; + + fprintf (target_file, "%s", buffer); + } + + if (!feof (source_file)) + { +#define keybind(name, handler, param, flags, stroke, description) \ + single_stanza ( \ + flags & BINDING_PER_WINDOW, \ + #name, \ + stroke, \ + flags & BINDING_REVERSES, \ + description); +#include "all-keybindings.h" +#undef keybind + } + + while (fgets (buffer, sizeof (buffer), source_file)) + fprintf (target_file, "%s", buffer); + + if (fclose (source_file)!=0) + { + g_error ("Cannot close %s: %s\n", + source_filename, strerror (errno)); + } + + if (fclose (target_file)!=0) + { + g_error ("Cannot close %s: %s\n", + target_filename, strerror (errno)); + } +} + +int +main (int argc, char **argv) +{ + if (argc!=3) + { + g_error ("Syntax: %s <source.in.in> <target.in>\n", argv[0]); + } + + source_filename = argv[1]; + target_filename = argv[2]; + + /* Translators: Please don't translate "Control", "Shift", etc, since these + * are hardcoded (in gtk/gtkaccelgroup.c; it's not marco's fault). + * "disabled" must also stay as it is. + */ + about_keybindings = g_markup_escape_text(_( \ + "The format looks like \"<Control>a\" or \"<Shift><Alt>F1\".\n\n"\ + "The parser is fairly liberal and allows "\ + "lower or upper case, and also abbreviations such as \"<Ctl>\" and " \ + "\"<Ctrl>\". If you set the option to the special string " \ + "\"disabled\", then there will be no keybinding for this action."), + -1); + + about_reversible_keybindings = g_markup_escape_text(_( \ + "The format looks like \"<Control>a\" or \"<Shift><Alt>F1\".\n\n"\ + "The parser is fairly liberal and allows "\ + "lower or upper case, and also abbreviations such as \"<Ctl>\" and " \ + "\"<Ctrl>\". If you set the option to the special string " \ + "\"disabled\", then there will be no keybinding for this action.\n\n"\ + "This keybinding may be reversed by holding down the \"shift\" key; " + "therefore, \"shift\" cannot be one of the keys it uses."), + -1); + + produce_bindings (); + + g_free (about_keybindings); + g_free (about_reversible_keybindings); + + return 0; +} + +/* eof schema-bindings.c */ + diff --git a/src/core/screen-private.h b/src/core/screen-private.h new file mode 100644 index 00000000..30941eab --- /dev/null +++ b/src/core/screen-private.h @@ -0,0 +1,226 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file screen-private.h Screens which Marco manages + * + * Managing X screens. + * This file contains methods on this class which are available to + * routines in core but not outside it. (See screen.h for the routines + * which the rest of the world is allowed to use.) + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_SCREEN_PRIVATE_H +#define META_SCREEN_PRIVATE_H + +#include "display-private.h" +#include "screen.h" +#include <X11/Xutil.h> +#include "ui.h" + +typedef struct _MetaXineramaScreenInfo MetaXineramaScreenInfo; + +struct _MetaXineramaScreenInfo +{ + int number; + MetaRectangle rect; +}; + +typedef void (* MetaScreenWindowFunc) (MetaScreen *screen, MetaWindow *window, + gpointer user_data); + +typedef enum +{ + META_SCREEN_TOPLEFT, + META_SCREEN_TOPRIGHT, + META_SCREEN_BOTTOMLEFT, + META_SCREEN_BOTTOMRIGHT +} MetaScreenCorner; + +typedef enum +{ + META_SCREEN_UP, + META_SCREEN_DOWN, + META_SCREEN_LEFT, + META_SCREEN_RIGHT +} MetaScreenDirection; + +#define META_WIREFRAME_XOR_LINE_WIDTH 2 + +struct _MetaScreen +{ + MetaDisplay *display; + int number; + char *screen_name; + Screen *xscreen; + Window xroot; + int default_depth; + Visual *default_xvisual; + MetaRectangle rect; /* Size of screen; rect.x & rect.y are always 0 */ + MetaUI *ui; + MetaTabPopup *tab_popup; + + MetaWorkspace *active_workspace; + + /* This window holds the focus when we don't want to focus + * any actual clients + */ + Window no_focus_window; + + GList *workspaces; + + MetaStack *stack; + + MetaCursor current_cursor; + + Window flash_window; + + Window wm_sn_selection_window; + Atom wm_sn_atom; + guint32 wm_sn_timestamp; + + MetaXineramaScreenInfo *xinerama_infos; + int n_xinerama_infos; + + /* Cache the current Xinerama */ + int last_xinerama_index; + +#ifdef HAVE_STARTUP_NOTIFICATION + SnMonitorContext *sn_context; + GSList *startup_sequences; + guint startup_sequence_timeout; +#endif + +#ifdef HAVE_COMPOSITE_EXTENSIONS + Window wm_cm_selection_window; + guint32 wm_cm_timestamp; +#endif + + guint work_area_idle; + + int rows_of_workspaces; + int columns_of_workspaces; + MetaScreenCorner starting_corner; + guint vertical_workspaces : 1; + + guint keys_grabbed : 1; + guint all_keys_grabbed : 1; + + int closing; + + /* gc for XOR on root window */ + GC root_xor_gc; + + /* Managed by compositor.c */ + gpointer compositor_data; +}; + +MetaScreen* meta_screen_new (MetaDisplay *display, + int number, + guint32 timestamp); +void meta_screen_free (MetaScreen *screen, + guint32 timestamp); +void meta_screen_manage_all_windows (MetaScreen *screen); +void meta_screen_foreach_window (MetaScreen *screen, + MetaScreenWindowFunc func, + gpointer data); +void meta_screen_queue_frame_redraws (MetaScreen *screen); +void meta_screen_queue_window_resizes (MetaScreen *screen); + +int meta_screen_get_n_workspaces (MetaScreen *screen); + +MetaWorkspace* meta_screen_get_workspace_by_index (MetaScreen *screen, + int index); + +void meta_screen_set_cursor (MetaScreen *screen, + MetaCursor cursor); +void meta_screen_update_cursor (MetaScreen *screen); + +void meta_screen_ensure_tab_popup (MetaScreen *screen, + MetaTabList list_type, + MetaTabShowType show_type); +void meta_screen_ensure_workspace_popup (MetaScreen *screen); + +MetaWindow* meta_screen_get_mouse_window (MetaScreen *screen, + MetaWindow *not_this_one); + +const MetaXineramaScreenInfo* meta_screen_get_current_xinerama (MetaScreen *screen); +const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_rect (MetaScreen *screen, + MetaRectangle *rect); +const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_window (MetaScreen *screen, + MetaWindow *window); + + +const MetaXineramaScreenInfo* meta_screen_get_xinerama_neighbor (MetaScreen *screen, + int which_xinerama, + MetaScreenDirection dir); +void meta_screen_get_natural_xinerama_list (MetaScreen *screen, + int** xineramas_list, + int* n_xineramas); + +void meta_screen_update_workspace_layout (MetaScreen *screen); +void meta_screen_update_workspace_names (MetaScreen *screen); +void meta_screen_queue_workarea_recalc (MetaScreen *screen); + +Window meta_create_offscreen_window (Display *xdisplay, + Window parent, + long valuemask); + +typedef struct MetaWorkspaceLayout MetaWorkspaceLayout; + +struct MetaWorkspaceLayout +{ + int rows; + int cols; + int *grid; + int grid_area; + int current_row; + int current_col; +}; + +void meta_screen_calc_workspace_layout (MetaScreen *screen, + int num_workspaces, + int current_space, + MetaWorkspaceLayout *layout); +void meta_screen_free_workspace_layout (MetaWorkspaceLayout *layout); + +void meta_screen_resize (MetaScreen *screen, + int width, + int height); + +void meta_screen_minimize_all_on_active_workspace_except (MetaScreen *screen, + MetaWindow *keep); + +/* Show/hide the desktop (temporarily hide all windows) */ +void meta_screen_show_desktop (MetaScreen *screen, + guint32 timestamp); +void meta_screen_unshow_desktop (MetaScreen *screen); + +/* Update whether the destkop is being shown for the current active_workspace */ +void meta_screen_update_showing_desktop_hint (MetaScreen *screen); + +gboolean meta_screen_apply_startup_properties (MetaScreen *screen, + MetaWindow *window); +void meta_screen_composite_all_windows (MetaScreen *screen); + +#endif diff --git a/src/core/screen.c b/src/core/screen.c new file mode 100644 index 00000000..f9db9c71 --- /dev/null +++ b/src/core/screen.c @@ -0,0 +1,2815 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X screen handler */ + +/* + * Copyright (C) 2001, 2002 Havoc Pennington + * Copyright (C) 2002, 2003 Red Hat Inc. + * Some ICCCM manager selection code derived from fvwm2, + * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "screen-private.h" +#include "util.h" +#include "errors.h" +#include "window-private.h" +#include "frame-private.h" +#include "prefs.h" +#include "workspace.h" +#include "keybindings.h" +#include "stack.h" +#include "xprops.h" +#include "compositor.h" + +#ifdef HAVE_SOLARIS_XINERAMA +#include <X11/extensions/xinerama.h> +#endif +#ifdef HAVE_XFREE_XINERAMA +#include <X11/extensions/Xinerama.h> +#endif + +#include <X11/Xatom.h> +#include <locale.h> +#include <string.h> +#include <stdio.h> + +static char* get_screen_name (MetaDisplay *display, + int number); + +static void update_num_workspaces (MetaScreen *screen, + guint32 timestamp); +static void update_focus_mode (MetaScreen *screen); +static void set_workspace_names (MetaScreen *screen); +static void prefs_changed_callback (MetaPreference pref, + gpointer data); + +static void set_desktop_geometry_hint (MetaScreen *screen); +static void set_desktop_viewport_hint (MetaScreen *screen); + +#ifdef HAVE_STARTUP_NOTIFICATION +static void meta_screen_sn_event (SnMonitorEvent *event, + void *user_data); +#endif + +static int +set_wm_check_hint (MetaScreen *screen) +{ + unsigned long data[1]; + + g_return_val_if_fail (screen->display->leader_window != None, 0); + + data[0] = screen->display->leader_window; + + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_SUPPORTING_WM_CHECK, + XA_WINDOW, + 32, PropModeReplace, (guchar*) data, 1); + + return Success; +} + +static void +unset_wm_check_hint (MetaScreen *screen) +{ + XDeleteProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_SUPPORTING_WM_CHECK); +} + +static int +set_supported_hint (MetaScreen *screen) +{ + Atom atoms[] = { +#define EWMH_ATOMS_ONLY +#define item(x) screen->display->atom_##x, +#include "atomnames.h" +#undef item +#undef EWMH_ATOMS_ONLY + }; + + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_SUPPORTED, + XA_ATOM, + 32, PropModeReplace, + (guchar*) atoms, G_N_ELEMENTS(atoms)); + + return Success; +} + +static int +set_wm_icon_size_hint (MetaScreen *screen) +{ +#define N_VALS 6 + gulong vals[N_VALS]; + + /* min width, min height, max w, max h, width inc, height inc */ + vals[0] = META_ICON_WIDTH; + vals[1] = META_ICON_HEIGHT; + vals[2] = META_ICON_WIDTH; + vals[3] = META_ICON_HEIGHT; + vals[4] = 0; + vals[5] = 0; + + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom_WM_ICON_SIZE, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) vals, N_VALS); + + return Success; +#undef N_VALS +} + +static void +reload_xinerama_infos (MetaScreen *screen) +{ + MetaDisplay *display; + + { + GList *tmp; + + tmp = screen->workspaces; + while (tmp != NULL) + { + MetaWorkspace *space = tmp->data; + + meta_workspace_invalidate_work_area (space); + + tmp = tmp->next; + } + } + + display = screen->display; + + if (screen->xinerama_infos) + g_free (screen->xinerama_infos); + + screen->xinerama_infos = NULL; + screen->n_xinerama_infos = 0; + screen->last_xinerama_index = 0; + + screen->display->xinerama_cache_invalidated = TRUE; + +#ifdef HAVE_XFREE_XINERAMA + if (XineramaIsActive (display->xdisplay)) + { + XineramaScreenInfo *infos; + int n_infos; + int i; + + n_infos = 0; + infos = XineramaQueryScreens (display->xdisplay, &n_infos); + + meta_topic (META_DEBUG_XINERAMA, + "Found %d Xinerama screens on display %s\n", + n_infos, display->name); + + if (n_infos > 0) + { + screen->xinerama_infos = g_new (MetaXineramaScreenInfo, n_infos); + screen->n_xinerama_infos = n_infos; + + i = 0; + while (i < n_infos) + { + screen->xinerama_infos[i].number = infos[i].screen_number; + screen->xinerama_infos[i].rect.x = infos[i].x_org; + screen->xinerama_infos[i].rect.y = infos[i].y_org; + screen->xinerama_infos[i].rect.width = infos[i].width; + screen->xinerama_infos[i].rect.height = infos[i].height; + + meta_topic (META_DEBUG_XINERAMA, + "Xinerama %d is %d,%d %d x %d\n", + screen->xinerama_infos[i].number, + screen->xinerama_infos[i].rect.x, + screen->xinerama_infos[i].rect.y, + screen->xinerama_infos[i].rect.width, + screen->xinerama_infos[i].rect.height); + + ++i; + } + } + + meta_XFree (infos); + } + else + { + meta_topic (META_DEBUG_XINERAMA, + "No XFree86 Xinerama extension or XFree86 Xinerama inactive on display %s\n", + display->name); + } +#else + meta_topic (META_DEBUG_XINERAMA, + "Marco compiled without XFree86 Xinerama support\n"); +#endif /* HAVE_XFREE_XINERAMA */ + +#ifdef HAVE_SOLARIS_XINERAMA + /* This code from GDK, Copyright (C) 2002 Sun Microsystems */ + if (screen->n_xinerama_infos == 0 && + XineramaGetState (screen->display->xdisplay, + screen->number)) + { + XRectangle monitors[MAXFRAMEBUFFERS]; + unsigned char hints[16]; + int result; + int n_monitors; + int i; + + n_monitors = 0; + result = XineramaGetInfo (screen->display->xdisplay, + screen->number, + monitors, hints, + &n_monitors); + /* Yes I know it should be Success but the current implementation + * returns the num of monitor + */ + if (result > 0) + { + g_assert (n_monitors > 0); + + screen->xinerama_infos = g_new (MetaXineramaScreenInfo, n_monitors); + screen->n_xinerama_infos = n_monitors; + + i = 0; + while (i < n_monitors) + { + screen->xinerama_infos[i].number = i; + screen->xinerama_infos[i].rect.x = monitors[i].x; + screen->xinerama_infos[i].rect.y = monitors[i].y; + screen->xinerama_infos[i].rect.width = monitors[i].width; + screen->xinerama_infos[i].rect.height = monitors[i].height; + + meta_topic (META_DEBUG_XINERAMA, + "Xinerama %d is %d,%d %d x %d\n", + screen->xinerama_infos[i].number, + screen->xinerama_infos[i].rect.x, + screen->xinerama_infos[i].rect.y, + screen->xinerama_infos[i].rect.width, + screen->xinerama_infos[i].rect.height); + + ++i; + } + } + } + else if (screen->n_xinerama_infos == 0) + { + meta_topic (META_DEBUG_XINERAMA, + "No Solaris Xinerama extension or Solaris Xinerama inactive on display %s\n", + display->name); + } +#else + meta_topic (META_DEBUG_XINERAMA, + "Marco compiled without Solaris Xinerama support\n"); +#endif /* HAVE_SOLARIS_XINERAMA */ + + + /* If no Xinerama, fill in the single screen info so + * we can use the field unconditionally + */ + if (screen->n_xinerama_infos == 0) + { + if (g_getenv ("MARCO_DEBUG_XINERAMA")) + { + meta_topic (META_DEBUG_XINERAMA, + "Pretending a single monitor has two Xinerama screens\n"); + + screen->xinerama_infos = g_new (MetaXineramaScreenInfo, 2); + screen->n_xinerama_infos = 2; + + screen->xinerama_infos[0].number = 0; + screen->xinerama_infos[0].rect = screen->rect; + screen->xinerama_infos[0].rect.width = screen->rect.width / 2; + + screen->xinerama_infos[1].number = 1; + screen->xinerama_infos[1].rect = screen->rect; + screen->xinerama_infos[1].rect.x = screen->rect.width / 2; + screen->xinerama_infos[1].rect.width = screen->rect.width / 2; + } + else + { + meta_topic (META_DEBUG_XINERAMA, + "No Xinerama screens, using default screen info\n"); + + screen->xinerama_infos = g_new (MetaXineramaScreenInfo, 1); + screen->n_xinerama_infos = 1; + + screen->xinerama_infos[0].number = 0; + screen->xinerama_infos[0].rect = screen->rect; + } + } + + g_assert (screen->n_xinerama_infos > 0); + g_assert (screen->xinerama_infos != NULL); +} + +MetaScreen* +meta_screen_new (MetaDisplay *display, + int number, + guint32 timestamp) +{ + MetaScreen *screen; + Window xroot; + Display *xdisplay; + XWindowAttributes attr; + Window new_wm_sn_owner; + Window current_wm_sn_owner; + gboolean replace_current_wm; + Atom wm_sn_atom; + char buf[128]; + guint32 manager_timestamp; + gulong current_workspace; + + replace_current_wm = meta_get_replace_current_wm (); + + /* Only display->name, display->xdisplay, and display->error_traps + * can really be used in this function, since normally screens are + * created from the MetaDisplay constructor + */ + + xdisplay = display->xdisplay; + + meta_verbose ("Trying screen %d on display '%s'\n", + number, display->name); + + xroot = RootWindow (xdisplay, number); + + /* FVWM checks for None here, I don't know if this + * ever actually happens + */ + if (xroot == None) + { + meta_warning (_("Screen %d on display '%s' is invalid\n"), + number, display->name); + return NULL; + } + + sprintf (buf, "WM_S%d", number); + wm_sn_atom = XInternAtom (xdisplay, buf, False); + + current_wm_sn_owner = XGetSelectionOwner (xdisplay, wm_sn_atom); + + if (current_wm_sn_owner != None) + { + XSetWindowAttributes attrs; + + if (!replace_current_wm) + { + meta_warning (_("Screen %d on display \"%s\" already has a window manager; try using the --replace option to replace the current window manager.\n"), + number, display->name); + + return NULL; + } + + /* We want to find out when the current selection owner dies */ + meta_error_trap_push_with_return (display); + attrs.event_mask = StructureNotifyMask; + XChangeWindowAttributes (xdisplay, + current_wm_sn_owner, CWEventMask, &attrs); + if (meta_error_trap_pop_with_return (display, FALSE) != Success) + current_wm_sn_owner = None; /* don't wait for it to die later on */ + } + + /* We need SelectionClear and SelectionRequest events on the new_wm_sn_owner, + * but those cannot be masked, so we only need NoEventMask. + */ + new_wm_sn_owner = meta_create_offscreen_window (xdisplay, xroot, NoEventMask); + + manager_timestamp = timestamp; + + XSetSelectionOwner (xdisplay, wm_sn_atom, new_wm_sn_owner, + manager_timestamp); + + if (XGetSelectionOwner (xdisplay, wm_sn_atom) != new_wm_sn_owner) + { + meta_warning (_("Could not acquire window manager selection on screen %d display \"%s\"\n"), + number, display->name); + + XDestroyWindow (xdisplay, new_wm_sn_owner); + + return NULL; + } + + { + /* Send client message indicating that we are now the WM */ + XClientMessageEvent ev; + + ev.type = ClientMessage; + ev.window = xroot; + ev.message_type = display->atom_MANAGER; + ev.format = 32; + ev.data.l[0] = manager_timestamp; + ev.data.l[1] = wm_sn_atom; + + XSendEvent (xdisplay, xroot, False, StructureNotifyMask, (XEvent*)&ev); + } + + /* Wait for old window manager to go away */ + if (current_wm_sn_owner != None) + { + XEvent event; + + /* We sort of block infinitely here which is probably lame. */ + + meta_verbose ("Waiting for old window manager to exit\n"); + do + { + XWindowEvent (xdisplay, current_wm_sn_owner, + StructureNotifyMask, &event); + } + while (event.type != DestroyNotify); + } + + /* select our root window events */ + meta_error_trap_push_with_return (display); + + /* We need to or with the existing event mask since + * gtk+ may be interested in other events. + */ + XGetWindowAttributes (xdisplay, xroot, &attr); + XSelectInput (xdisplay, + xroot, + SubstructureRedirectMask | SubstructureNotifyMask | + ColormapChangeMask | PropertyChangeMask | + LeaveWindowMask | EnterWindowMask | + KeyPressMask | KeyReleaseMask | + FocusChangeMask | StructureNotifyMask | +#ifdef HAVE_COMPOSITE_EXTENSIONS + ExposureMask | +#endif + attr.your_event_mask); + if (meta_error_trap_pop_with_return (display, FALSE) != Success) + { + meta_warning (_("Screen %d on display \"%s\" already has a window manager\n"), + number, display->name); + + XDestroyWindow (xdisplay, new_wm_sn_owner); + + return NULL; + } + + screen = g_new (MetaScreen, 1); + screen->closing = 0; + + screen->display = display; + screen->number = number; + screen->screen_name = get_screen_name (display, number); + screen->xscreen = ScreenOfDisplay (xdisplay, number); + screen->xroot = xroot; + screen->rect.x = screen->rect.y = 0; + screen->rect.width = WidthOfScreen (screen->xscreen); + screen->rect.height = HeightOfScreen (screen->xscreen); + screen->current_cursor = -1; /* invalid/unset */ + screen->default_xvisual = DefaultVisualOfScreen (screen->xscreen); + screen->default_depth = DefaultDepthOfScreen (screen->xscreen); + screen->flash_window = None; + + screen->wm_sn_selection_window = new_wm_sn_owner; + screen->wm_sn_atom = wm_sn_atom; + screen->wm_sn_timestamp = manager_timestamp; + +#ifdef HAVE_COMPOSITE_EXTENSIONS + screen->wm_cm_selection_window = meta_create_offscreen_window (xdisplay, + xroot, + NoEventMask); +#endif + screen->work_area_idle = 0; + + screen->active_workspace = NULL; + screen->workspaces = NULL; + screen->rows_of_workspaces = 1; + screen->columns_of_workspaces = -1; + screen->vertical_workspaces = FALSE; + screen->starting_corner = META_SCREEN_TOPLEFT; + screen->compositor_data = NULL; + + { + XFontStruct *font_info; + XGCValues gc_values; + gulong value_mask = 0; + + gc_values.subwindow_mode = IncludeInferiors; + value_mask |= GCSubwindowMode; + gc_values.function = GXinvert; + value_mask |= GCFunction; + gc_values.line_width = META_WIREFRAME_XOR_LINE_WIDTH; + value_mask |= GCLineWidth; + + font_info = XLoadQueryFont (screen->display->xdisplay, "fixed"); + + if (font_info != NULL) + { + gc_values.font = font_info->fid; + value_mask |= GCFont; + XFreeFontInfo (NULL, font_info, 1); + } + else + meta_warning ("xserver doesn't have 'fixed' font.\n"); + + screen->root_xor_gc = XCreateGC (screen->display->xdisplay, + screen->xroot, + value_mask, + &gc_values); + } + + screen->xinerama_infos = NULL; + screen->n_xinerama_infos = 0; + screen->last_xinerama_index = 0; + + reload_xinerama_infos (screen); + + meta_screen_set_cursor (screen, META_CURSOR_DEFAULT); + + /* Handle creating a no_focus_window for this screen */ + screen->no_focus_window = + meta_create_offscreen_window (display->xdisplay, + screen->xroot, + FocusChangeMask|KeyPressMask|KeyReleaseMask); + XMapWindow (display->xdisplay, screen->no_focus_window); + /* Done with no_focus_window stuff */ + + set_wm_icon_size_hint (screen); + + set_supported_hint (screen); + + set_wm_check_hint (screen); + + set_desktop_viewport_hint (screen); + + set_desktop_geometry_hint (screen); + + meta_screen_update_workspace_layout (screen); + + /* Get current workspace */ + current_workspace = 0; + if (meta_prop_get_cardinal (screen->display, + screen->xroot, + screen->display->atom__NET_CURRENT_DESKTOP, + ¤t_workspace)) + meta_verbose ("Read existing _NET_CURRENT_DESKTOP = %d\n", + (int) current_workspace); + else + meta_verbose ("No _NET_CURRENT_DESKTOP present\n"); + + /* Screens must have at least one workspace at all times, + * so create that required workspace. + */ + meta_workspace_activate (meta_workspace_new (screen), timestamp); + update_num_workspaces (screen, timestamp); + + set_workspace_names (screen); + + screen->all_keys_grabbed = FALSE; + screen->keys_grabbed = FALSE; + meta_screen_grab_keys (screen); + + screen->ui = meta_ui_new (screen->display->xdisplay, + screen->xscreen); + + screen->tab_popup = NULL; + + screen->stack = meta_stack_new (screen); + + meta_prefs_add_listener (prefs_changed_callback, screen); + +#ifdef HAVE_STARTUP_NOTIFICATION + screen->sn_context = + sn_monitor_context_new (screen->display->sn_display, + screen->number, + meta_screen_sn_event, + screen, + NULL); + screen->startup_sequences = NULL; + screen->startup_sequence_timeout = 0; +#endif + + /* Switch to the _NET_CURRENT_DESKTOP workspace */ + { + MetaWorkspace *space; + + space = meta_screen_get_workspace_by_index (screen, + current_workspace); + + if (space != NULL) + meta_workspace_activate (space, timestamp); + } + + meta_verbose ("Added screen %d ('%s') root 0x%lx\n", + screen->number, screen->screen_name, screen->xroot); + + return screen; +} + +void +meta_screen_free (MetaScreen *screen, + guint32 timestamp) +{ + MetaDisplay *display; + XGCValues gc_values = { 0 }; + + display = screen->display; + + screen->closing += 1; + + meta_display_grab (display); + + if (screen->display->compositor) + { + meta_compositor_unmanage_screen (screen->display->compositor, + screen); + } + + meta_display_unmanage_windows_for_screen (display, screen, timestamp); + + meta_prefs_remove_listener (prefs_changed_callback, screen); + + meta_screen_ungrab_keys (screen); + +#ifdef HAVE_STARTUP_NOTIFICATION + g_slist_foreach (screen->startup_sequences, + (GFunc) sn_startup_sequence_unref, NULL); + g_slist_free (screen->startup_sequences); + screen->startup_sequences = NULL; + + if (screen->startup_sequence_timeout != 0) + { + g_source_remove (screen->startup_sequence_timeout); + screen->startup_sequence_timeout = 0; + } + if (screen->sn_context) + { + sn_monitor_context_unref (screen->sn_context); + screen->sn_context = NULL; + } +#endif + + meta_ui_free (screen->ui); + + meta_stack_free (screen->stack); + + meta_error_trap_push_with_return (screen->display); + XSelectInput (screen->display->xdisplay, screen->xroot, 0); + if (meta_error_trap_pop_with_return (screen->display, FALSE) != Success) + meta_warning (_("Could not release screen %d on display \"%s\"\n"), + screen->number, screen->display->name); + + unset_wm_check_hint (screen); + + XDestroyWindow (screen->display->xdisplay, + screen->wm_sn_selection_window); + + if (screen->work_area_idle != 0) + g_source_remove (screen->work_area_idle); + + + if (XGetGCValues (screen->display->xdisplay, + screen->root_xor_gc, + GCFont, + &gc_values)) + { + XUnloadFont (screen->display->xdisplay, + gc_values.font); + } + + XFreeGC (screen->display->xdisplay, + screen->root_xor_gc); + + if (screen->xinerama_infos) + g_free (screen->xinerama_infos); + + g_free (screen->screen_name); + g_free (screen); + + XFlush (display->xdisplay); + meta_display_ungrab (display); +} + +typedef struct +{ + Window xwindow; + XWindowAttributes attrs; +} WindowInfo; + +static GList * +list_windows (MetaScreen *screen) +{ + Window ignored1, ignored2; + Window *children; + guint n_children, i; + GList *result; + + XQueryTree (screen->display->xdisplay, + screen->xroot, + &ignored1, &ignored2, &children, &n_children); + + result = NULL; + for (i = 0; i < n_children; ++i) + { + WindowInfo *info = g_new0 (WindowInfo, 1); + + meta_error_trap_push_with_return (screen->display); + + XGetWindowAttributes (screen->display->xdisplay, + children[i], &info->attrs); + + if (meta_error_trap_pop_with_return (screen->display, TRUE)) + { + meta_verbose ("Failed to get attributes for window 0x%lx\n", + children[i]); + g_free (info); + } + else + { + info->xwindow = children[i]; + } + + result = g_list_prepend (result, info); + } + + if (children) + XFree (children); + + return g_list_reverse (result); +} + +void +meta_screen_manage_all_windows (MetaScreen *screen) +{ + GList *windows; + GList *list; + + meta_display_grab (screen->display); + + windows = list_windows (screen); + + meta_stack_freeze (screen->stack); + for (list = windows; list != NULL; list = list->next) + { + WindowInfo *info = list->data; + MetaWindow *window; + + window = meta_window_new_with_attrs (screen->display, info->xwindow, TRUE, + &info->attrs); + if (info->xwindow == screen->no_focus_window || + info->xwindow == screen->flash_window || +#ifdef HAVE_COMPOSITE_EXTENSIONS + info->xwindow == screen->wm_cm_selection_window || +#endif + info->xwindow == screen->wm_sn_selection_window) { + meta_verbose ("Not managing our own windows\n"); + continue; + } + + if (screen->display->compositor) + meta_compositor_add_window (screen->display->compositor, window, + info->xwindow, &info->attrs); + } + meta_stack_thaw (screen->stack); + + g_list_foreach (windows, (GFunc)g_free, NULL); + g_list_free (windows); + + meta_display_ungrab (screen->display); +} + +void +meta_screen_composite_all_windows (MetaScreen *screen) +{ +#ifdef HAVE_COMPOSITE_EXTENSIONS + MetaDisplay *display; + GList *windows, *list; + + display = screen->display; + if (!display->compositor) + return; + + windows = list_windows (screen); + + meta_stack_freeze (screen->stack); + + for (list = windows; list != NULL; list = list->next) + { + WindowInfo *info = list->data; + + if (info->xwindow == screen->no_focus_window || + info->xwindow == screen->flash_window || + info->xwindow == screen->wm_sn_selection_window || + info->xwindow == screen->wm_cm_selection_window) { + meta_verbose ("Not managing our own windows\n"); + continue; + } + + meta_compositor_add_window (display->compositor, + meta_display_lookup_x_window (display, + info->xwindow), + info->xwindow, &info->attrs); + } + + meta_stack_thaw (screen->stack); + + g_list_foreach (windows, (GFunc)g_free, NULL); + g_list_free (windows); +#endif +} + +MetaScreen* +meta_screen_for_x_screen (Screen *xscreen) +{ + MetaDisplay *display; + + display = meta_display_for_x_display (DisplayOfScreen (xscreen)); + + if (display == NULL) + return NULL; + + return meta_display_screen_for_x_screen (display, xscreen); +} + +static void +prefs_changed_callback (MetaPreference pref, + gpointer data) +{ + MetaScreen *screen = data; + + if (pref == META_PREF_NUM_WORKSPACES) + { + /* MateConf doesn't provide timestamps, but luckily update_num_workspaces + * often doesn't need it... + */ + guint32 timestamp = + meta_display_get_current_time_roundtrip (screen->display); + update_num_workspaces (screen, timestamp); + } + else if (pref == META_PREF_FOCUS_MODE) + { + update_focus_mode (screen); + } + else if (pref == META_PREF_WORKSPACE_NAMES) + { + set_workspace_names (screen); + } +} + + +static char* +get_screen_name (MetaDisplay *display, + int number) +{ + char *p; + char *dname; + char *scr; + + /* DisplayString gives us a sort of canonical display, + * vs. the user-entered name from XDisplayName() + */ + dname = g_strdup (DisplayString (display->xdisplay)); + + /* Change display name to specify this screen. + */ + p = strrchr (dname, ':'); + if (p) + { + p = strchr (p, '.'); + if (p) + *p = '\0'; + } + + scr = g_strdup_printf ("%s.%d", dname, number); + + g_free (dname); + + return scr; +} + +static gint +ptrcmp (gconstpointer a, gconstpointer b) +{ + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; +} + +static void +listify_func (gpointer key, gpointer value, gpointer data) +{ + GSList **listp; + + listp = data; + + *listp = g_slist_prepend (*listp, value); +} + +void +meta_screen_foreach_window (MetaScreen *screen, + MetaScreenWindowFunc func, + gpointer data) +{ + GSList *winlist; + GSList *tmp; + + /* If we end up doing this often, just keeping a list + * of windows might be sensible. + */ + + winlist = NULL; + g_hash_table_foreach (screen->display->window_ids, + listify_func, + &winlist); + + winlist = g_slist_sort (winlist, ptrcmp); + + tmp = winlist; + while (tmp != NULL) + { + /* If the next node doesn't contain this window + * a second time, delete the window. + */ + if (tmp->next == NULL || + (tmp->next && tmp->next->data != tmp->data)) + { + MetaWindow *window = tmp->data; + + if (window->screen == screen) + (* func) (screen, window, data); + } + + tmp = tmp->next; + } + g_slist_free (winlist); +} + +static void +queue_draw (MetaScreen *screen, MetaWindow *window, gpointer data) +{ + if (window->frame) + meta_frame_queue_draw (window->frame); +} + +void +meta_screen_queue_frame_redraws (MetaScreen *screen) +{ + meta_screen_foreach_window (screen, queue_draw, NULL); +} + +static void +queue_resize (MetaScreen *screen, MetaWindow *window, gpointer data) +{ + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + +void +meta_screen_queue_window_resizes (MetaScreen *screen) +{ + meta_screen_foreach_window (screen, queue_resize, NULL); +} + +int +meta_screen_get_n_workspaces (MetaScreen *screen) +{ + return g_list_length (screen->workspaces); +} + +MetaWorkspace* +meta_screen_get_workspace_by_index (MetaScreen *screen, + int idx) +{ + GList *tmp; + int i; + + /* should be robust, idx is maybe from an app */ + if (idx < 0) + return NULL; + + i = 0; + tmp = screen->workspaces; + while (tmp != NULL) + { + MetaWorkspace *w = tmp->data; + + if (i == idx) + return w; + + ++i; + tmp = tmp->next; + } + + return NULL; +} + +static void +set_number_of_spaces_hint (MetaScreen *screen, + int n_spaces) +{ + unsigned long data[1]; + + if (screen->closing > 0) + return; + + data[0] = n_spaces; + + meta_verbose ("Setting _NET_NUMBER_OF_DESKTOPS to %lu\n", data[0]); + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_NUMBER_OF_DESKTOPS, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 1); + meta_error_trap_pop (screen->display, FALSE); +} + +static void +set_desktop_geometry_hint (MetaScreen *screen) +{ + unsigned long data[2]; + + if (screen->closing > 0) + return; + + data[0] = screen->rect.width; + data[1] = screen->rect.height; + + meta_verbose ("Setting _NET_DESKTOP_GEOMETRY to %lu, %lu\n", data[0], data[1]); + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_DESKTOP_GEOMETRY, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 2); + meta_error_trap_pop (screen->display, FALSE); +} + +static void +set_desktop_viewport_hint (MetaScreen *screen) +{ + unsigned long data[2]; + + if (screen->closing > 0) + return; + + /* + * Marco does not implement viewports, so this is a fixed 0,0 + */ + data[0] = 0; + data[1] = 0; + + meta_verbose ("Setting _NET_DESKTOP_VIEWPORT to 0, 0\n"); + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_DESKTOP_VIEWPORT, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 2); + meta_error_trap_pop (screen->display, FALSE); +} + +static void +update_num_workspaces (MetaScreen *screen, + guint32 timestamp) +{ + int new_num; + GList *tmp; + int i; + GList *extras; + MetaWorkspace *last_remaining; + gboolean need_change_space; + + new_num = meta_prefs_get_num_workspaces (); + + g_assert (new_num > 0); + + last_remaining = NULL; + extras = NULL; + i = 0; + tmp = screen->workspaces; + while (tmp != NULL) + { + MetaWorkspace *w = tmp->data; + + if (i >= new_num) + extras = g_list_prepend (extras, w); + else + last_remaining = w; + + ++i; + tmp = tmp->next; + } + + g_assert (last_remaining); + + /* Get rid of the extra workspaces by moving all their windows + * to last_remaining, then activating last_remaining if + * one of the removed workspaces was active. This will be a bit + * wacky if the config tool for changing number of workspaces + * is on a removed workspace ;-) + */ + need_change_space = FALSE; + tmp = extras; + while (tmp != NULL) + { + MetaWorkspace *w = tmp->data; + + meta_workspace_relocate_windows (w, last_remaining); + + if (w == screen->active_workspace) + need_change_space = TRUE; + + tmp = tmp->next; + } + + if (need_change_space) + meta_workspace_activate (last_remaining, timestamp); + + /* Should now be safe to free the workspaces */ + tmp = extras; + while (tmp != NULL) + { + MetaWorkspace *w = tmp->data; + + g_assert (w->windows == NULL); + meta_workspace_free (w); + + tmp = tmp->next; + } + + g_list_free (extras); + + while (i < new_num) + { + meta_workspace_new (screen); + ++i; + } + + set_number_of_spaces_hint (screen, new_num); + + meta_screen_queue_workarea_recalc (screen); +} + +static void +update_focus_mode (MetaScreen *screen) +{ + /* nothing to do anymore */ ; +} + +void +meta_screen_set_cursor (MetaScreen *screen, + MetaCursor cursor) +{ + Cursor xcursor; + + if (cursor == screen->current_cursor) + return; + + screen->current_cursor = cursor; + + xcursor = meta_display_create_x_cursor (screen->display, cursor); + XDefineCursor (screen->display->xdisplay, screen->xroot, xcursor); + XFlush (screen->display->xdisplay); + XFreeCursor (screen->display->xdisplay, xcursor); +} + +void +meta_screen_update_cursor (MetaScreen *screen) +{ + Cursor xcursor; + + xcursor = meta_display_create_x_cursor (screen->display, + screen->current_cursor); + XDefineCursor (screen->display->xdisplay, screen->xroot, xcursor); + XFlush (screen->display->xdisplay); + XFreeCursor (screen->display->xdisplay, xcursor); +} + +#define MAX_PREVIEW_SIZE 150.0 + +static GdkPixbuf * +get_window_pixbuf (MetaWindow *window, + int *width, + int *height) +{ + Pixmap pmap; + GdkPixbuf *pixbuf, *scaled; + double ratio; + + pmap = meta_compositor_get_window_pixmap (window->display->compositor, + window); + if (pmap == None) + return NULL; + + pixbuf = meta_ui_get_pixbuf_from_pixmap (pmap); + if (pixbuf == NULL) + return NULL; + + *width = gdk_pixbuf_get_width (pixbuf); + *height = gdk_pixbuf_get_height (pixbuf); + + /* Scale pixbuf to max dimension MAX_PREVIEW_SIZE */ + if (*width > *height) + { + ratio = ((double) *width) / MAX_PREVIEW_SIZE; + *width = (int) MAX_PREVIEW_SIZE; + *height = (int) (((double) *height) / ratio); + } + else + { + ratio = ((double) *height) / MAX_PREVIEW_SIZE; + *height = (int) MAX_PREVIEW_SIZE; + *width = (int) (((double) *width) / ratio); + } + + scaled = gdk_pixbuf_scale_simple (pixbuf, *width, *height, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + return scaled; +} + +void +meta_screen_ensure_tab_popup (MetaScreen *screen, + MetaTabList list_type, + MetaTabShowType show_type) +{ + MetaTabEntry *entries; + GList *tab_list; + GList *tmp; + int len; + int i; + + if (screen->tab_popup) + return; + + tab_list = meta_display_get_tab_list (screen->display, + list_type, + screen, + screen->active_workspace); + + len = g_list_length (tab_list); + + entries = g_new (MetaTabEntry, len + 1); + entries[len].key = NULL; + entries[len].title = NULL; + entries[len].icon = NULL; + + i = 0; + tmp = tab_list; + while (i < len) + { + MetaWindow *window; + MetaRectangle r; + GdkPixbuf *win_pixbuf; + int width, height; + + window = tmp->data; + + entries[i].key = (MetaTabEntryKey) window->xwindow; + entries[i].title = window->title; + + win_pixbuf = get_window_pixbuf (window, &width, &height); + if (win_pixbuf == NULL) + entries[i].icon = g_object_ref (window->icon); + else + { + int icon_width, icon_height, t_width, t_height; +#define ICON_OFFSET 6 + + icon_width = gdk_pixbuf_get_width (window->icon); + icon_height = gdk_pixbuf_get_height (window->icon); + + t_width = width + ICON_OFFSET; + t_height = height + ICON_OFFSET; + + entries[i].icon = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + t_width, t_height); + gdk_pixbuf_fill (entries[i].icon, 0x00000000); + gdk_pixbuf_copy_area (win_pixbuf, 0, 0, width, height, + entries[i].icon, 0, 0); + g_object_unref (win_pixbuf); + gdk_pixbuf_composite (window->icon, entries[i].icon, + t_width - icon_width, t_height - icon_height, + icon_width, icon_height, + t_width - icon_width, t_height - icon_height, + 1.0, 1.0, GDK_INTERP_BILINEAR, 255); + } + + entries[i].blank = FALSE; + entries[i].hidden = !meta_window_showing_on_its_workspace (window); + entries[i].demands_attention = window->wm_state_demands_attention; + + if (show_type == META_TAB_SHOW_INSTANTLY || + !entries[i].hidden || + !meta_window_get_icon_geometry (window, &r)) + meta_window_get_outer_rect (window, &r); + + entries[i].rect = r; + + /* Find inside of highlight rectangle to be used when window is + * outlined for tabbing. This should be the size of the + * east/west frame, and the size of the south frame, on those + * sides. On the top it should be the size of the south frame + * edge. + */ +#define OUTLINE_WIDTH 5 + /* Top side */ + if (!entries[i].hidden && + window->frame && window->frame->bottom_height > 0 && + window->frame->child_y >= window->frame->bottom_height) + entries[i].inner_rect.y = window->frame->bottom_height; + else + entries[i].inner_rect.y = OUTLINE_WIDTH; + + /* Bottom side */ + if (!entries[i].hidden && + window->frame && window->frame->bottom_height != 0) + entries[i].inner_rect.height = r.height + - entries[i].inner_rect.y - window->frame->bottom_height; + else + entries[i].inner_rect.height = r.height + - entries[i].inner_rect.y - OUTLINE_WIDTH; + + /* Left side */ + if (!entries[i].hidden && window->frame && window->frame->child_x != 0) + entries[i].inner_rect.x = window->frame->child_x; + else + entries[i].inner_rect.x = OUTLINE_WIDTH; + + /* Right side */ + if (!entries[i].hidden && + window->frame && window->frame->right_width != 0) + entries[i].inner_rect.width = r.width + - entries[i].inner_rect.x - window->frame->right_width; + else + entries[i].inner_rect.width = r.width + - entries[i].inner_rect.x - OUTLINE_WIDTH; + + ++i; + tmp = tmp->next; + } + + screen->tab_popup = meta_ui_tab_popup_new (entries, + screen->number, + len, + 5, /* FIXME */ + TRUE); + + for (i = 0; i < len; i++) + g_object_unref (entries[i].icon); + + g_free (entries); + + g_list_free (tab_list); + + /* don't show tab popup, since proper window isn't selected yet */ +} + +void +meta_screen_ensure_workspace_popup (MetaScreen *screen) +{ + MetaTabEntry *entries; + int len; + int i; + MetaWorkspaceLayout layout; + int n_workspaces; + int current_workspace; + + if (screen->tab_popup) + return; + + current_workspace = meta_workspace_index (screen->active_workspace); + n_workspaces = meta_screen_get_n_workspaces (screen); + + meta_screen_calc_workspace_layout (screen, n_workspaces, + current_workspace, &layout); + + len = layout.grid_area; + + entries = g_new (MetaTabEntry, len + 1); + entries[len].key = NULL; + entries[len].title = NULL; + entries[len].icon = NULL; + + i = 0; + while (i < len) + { + if (layout.grid[i] >= 0) + { + MetaWorkspace *workspace; + + workspace = meta_screen_get_workspace_by_index (screen, + layout.grid[i]); + + entries[i].key = (MetaTabEntryKey) workspace; + entries[i].title = meta_workspace_get_name (workspace); + entries[i].icon = NULL; + entries[i].blank = FALSE; + + g_assert (entries[i].title != NULL); + } + else + { + entries[i].key = NULL; + entries[i].title = NULL; + entries[i].icon = NULL; + entries[i].blank = TRUE; + } + entries[i].hidden = FALSE; + entries[i].demands_attention = FALSE; + + ++i; + } + + screen->tab_popup = meta_ui_tab_popup_new (entries, + screen->number, + len, + layout.cols, + FALSE); + + g_free (entries); + meta_screen_free_workspace_layout (&layout); + + /* don't show tab popup, since proper space isn't selected yet */ +} + +MetaWindow* +meta_screen_get_mouse_window (MetaScreen *screen, + MetaWindow *not_this_one) +{ + MetaWindow *window; + Window root_return, child_return; + int root_x_return, root_y_return; + int win_x_return, win_y_return; + unsigned int mask_return; + + if (not_this_one) + meta_topic (META_DEBUG_FOCUS, + "Focusing mouse window excluding %s\n", not_this_one->desc); + + meta_error_trap_push (screen->display); + XQueryPointer (screen->display->xdisplay, + screen->xroot, + &root_return, + &child_return, + &root_x_return, + &root_y_return, + &win_x_return, + &win_y_return, + &mask_return); + meta_error_trap_pop (screen->display, TRUE); + + window = meta_stack_get_default_focus_window_at_point (screen->stack, + screen->active_workspace, + not_this_one, + root_x_return, + root_y_return); + + return window; +} + +const MetaXineramaScreenInfo* +meta_screen_get_xinerama_for_rect (MetaScreen *screen, + MetaRectangle *rect) +{ + int i; + int best_xinerama, xinerama_score; + + if (screen->n_xinerama_infos == 1) + return &screen->xinerama_infos[0]; + + best_xinerama = 0; + xinerama_score = 0; + + for (i = 0; i < screen->n_xinerama_infos; i++) + { + MetaRectangle dest; + if (meta_rectangle_intersect (&screen->xinerama_infos[i].rect, + rect, + &dest)) + { + int cur = meta_rectangle_area (&dest); + if (cur > xinerama_score) + { + xinerama_score = cur; + best_xinerama = i; + } + } + } + + return &screen->xinerama_infos[best_xinerama]; +} + +const MetaXineramaScreenInfo* +meta_screen_get_xinerama_for_window (MetaScreen *screen, + MetaWindow *window) +{ + MetaRectangle window_rect; + + meta_window_get_outer_rect (window, &window_rect); + + return meta_screen_get_xinerama_for_rect (screen, &window_rect); +} + +const MetaXineramaScreenInfo* +meta_screen_get_xinerama_neighbor (MetaScreen *screen, + int which_xinerama, + MetaScreenDirection direction) +{ + MetaXineramaScreenInfo* input = screen->xinerama_infos + which_xinerama; + MetaXineramaScreenInfo* current; + int i; + + for (i = 0; i < screen->n_xinerama_infos; i++) + { + current = screen->xinerama_infos + i; + + if ((direction == META_SCREEN_RIGHT && + current->rect.x == input->rect.x + input->rect.width && + meta_rectangle_vert_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_LEFT && + input->rect.x == current->rect.x + current->rect.width && + meta_rectangle_vert_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_UP && + input->rect.y == current->rect.y + current->rect.height && + meta_rectangle_horiz_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_DOWN && + current->rect.y == input->rect.y + input->rect.height && + meta_rectangle_horiz_overlap(¤t->rect, &input->rect))) + { + return current; + } + } + + return NULL; +} + +void +meta_screen_get_natural_xinerama_list (MetaScreen *screen, + int** xineramas_list, + int* n_xineramas) +{ + const MetaXineramaScreenInfo* current; + const MetaXineramaScreenInfo* tmp; + GQueue* xinerama_queue; + int* visited; + int cur = 0; + int i; + + *n_xineramas = screen->n_xinerama_infos; + *xineramas_list = g_new (int, screen->n_xinerama_infos); + + /* we calculate a natural ordering by which to choose xineramas for + * window placement. We start at the current xinerama, and perform + * a breadth-first search of the xineramas starting from that + * xinerama. We choose preferentially left, then right, then down, + * then up. The visitation order produced by this traversal is the + * natural xinerama ordering. + */ + + visited = g_new (int, screen->n_xinerama_infos); + for (i = 0; i < screen->n_xinerama_infos; i++) + { + visited[i] = FALSE; + } + + current = meta_screen_get_current_xinerama (screen); + xinerama_queue = g_queue_new (); + g_queue_push_tail (xinerama_queue, (gpointer) current); + visited[current->number] = TRUE; + + while (!g_queue_is_empty (xinerama_queue)) + { + current = (const MetaXineramaScreenInfo*) + g_queue_pop_head (xinerama_queue); + + (*xineramas_list)[cur++] = current->number; + + /* enqueue each of the directions */ + tmp = meta_screen_get_xinerama_neighbor (screen, + current->number, + META_SCREEN_LEFT); + if (tmp && !visited[tmp->number]) + { + g_queue_push_tail (xinerama_queue, + (MetaXineramaScreenInfo*) tmp); + visited[tmp->number] = TRUE; + } + tmp = meta_screen_get_xinerama_neighbor (screen, + current->number, + META_SCREEN_RIGHT); + if (tmp && !visited[tmp->number]) + { + g_queue_push_tail (xinerama_queue, + (MetaXineramaScreenInfo*) tmp); + visited[tmp->number] = TRUE; + } + tmp = meta_screen_get_xinerama_neighbor (screen, + current->number, + META_SCREEN_UP); + if (tmp && !visited[tmp->number]) + { + g_queue_push_tail (xinerama_queue, + (MetaXineramaScreenInfo*) tmp); + visited[tmp->number] = TRUE; + } + tmp = meta_screen_get_xinerama_neighbor (screen, + current->number, + META_SCREEN_DOWN); + if (tmp && !visited[tmp->number]) + { + g_queue_push_tail (xinerama_queue, + (MetaXineramaScreenInfo*) tmp); + visited[tmp->number] = TRUE; + } + } + + /* in case we somehow missed some set of xineramas, go through the + * visited list and add in any xineramas that were missed + */ + for (i = 0; i < screen->n_xinerama_infos; i++) + { + if (visited[i] == FALSE) + { + (*xineramas_list)[cur++] = i; + } + } + + g_free (visited); + g_queue_free (xinerama_queue); +} + +const MetaXineramaScreenInfo* +meta_screen_get_current_xinerama (MetaScreen *screen) +{ + if (screen->n_xinerama_infos == 1) + return &screen->xinerama_infos[0]; + + /* Sadly, we have to do it this way. Yuck. + */ + + if (screen->display->xinerama_cache_invalidated) + { + Window root_return, child_return; + int win_x_return, win_y_return; + unsigned int mask_return; + int i; + MetaRectangle pointer_position; + + screen->display->xinerama_cache_invalidated = FALSE; + + pointer_position.width = pointer_position.height = 1; + XQueryPointer (screen->display->xdisplay, + screen->xroot, + &root_return, + &child_return, + &pointer_position.x, + &pointer_position.y, + &win_x_return, + &win_y_return, + &mask_return); + + screen->last_xinerama_index = 0; + for (i = 0; i < screen->n_xinerama_infos; i++) + { + if (meta_rectangle_contains_rect (&screen->xinerama_infos[i].rect, + &pointer_position)) + { + screen->last_xinerama_index = i; + break; + } + } + + meta_topic (META_DEBUG_XINERAMA, + "Rechecked current Xinerama, now %d\n", + screen->last_xinerama_index); + } + + return &screen->xinerama_infos[screen->last_xinerama_index]; +} + +#define _NET_WM_ORIENTATION_HORZ 0 +#define _NET_WM_ORIENTATION_VERT 1 + +#define _NET_WM_TOPLEFT 0 +#define _NET_WM_TOPRIGHT 1 +#define _NET_WM_BOTTOMRIGHT 2 +#define _NET_WM_BOTTOMLEFT 3 + +void +meta_screen_update_workspace_layout (MetaScreen *screen) +{ + gulong *list; + int n_items; + + list = NULL; + n_items = 0; + + if (meta_prop_get_cardinal_list (screen->display, + screen->xroot, + screen->display->atom__NET_DESKTOP_LAYOUT, + &list, &n_items)) + { + if (n_items == 3 || n_items == 4) + { + int cols, rows; + + switch (list[0]) + { + case _NET_WM_ORIENTATION_HORZ: + screen->vertical_workspaces = FALSE; + break; + case _NET_WM_ORIENTATION_VERT: + screen->vertical_workspaces = TRUE; + break; + default: + meta_warning ("Someone set a weird orientation in _NET_DESKTOP_LAYOUT\n"); + break; + } + + cols = list[1]; + rows = list[2]; + + if (rows <= 0 && cols <= 0) + { + meta_warning ("Columns = %d rows = %d in _NET_DESKTOP_LAYOUT makes no sense\n", rows, cols); + } + else + { + if (rows > 0) + screen->rows_of_workspaces = rows; + else + screen->rows_of_workspaces = -1; + + if (cols > 0) + screen->columns_of_workspaces = cols; + else + screen->columns_of_workspaces = -1; + } + + if (n_items == 4) + { + switch (list[3]) + { + case _NET_WM_TOPLEFT: + screen->starting_corner = META_SCREEN_TOPLEFT; + break; + case _NET_WM_TOPRIGHT: + screen->starting_corner = META_SCREEN_TOPRIGHT; + break; + case _NET_WM_BOTTOMRIGHT: + screen->starting_corner = META_SCREEN_BOTTOMRIGHT; + break; + case _NET_WM_BOTTOMLEFT: + screen->starting_corner = META_SCREEN_BOTTOMLEFT; + break; + default: + meta_warning ("Someone set a weird starting corner in _NET_DESKTOP_LAYOUT\n"); + break; + } + } + else + screen->starting_corner = META_SCREEN_TOPLEFT; + } + else + { + meta_warning ("Someone set _NET_DESKTOP_LAYOUT to %d integers instead of 4 " + "(3 is accepted for backwards compat)\n", n_items); + } + + meta_XFree (list); + } + + meta_verbose ("Workspace layout rows = %d cols = %d orientation = %d starting corner = %u\n", + screen->rows_of_workspaces, + screen->columns_of_workspaces, + screen->vertical_workspaces, + screen->starting_corner); +} + +static void +set_workspace_names (MetaScreen *screen) +{ + /* This updates names on root window when the pref changes, + * note we only get prefs change notify if things have + * really changed. + */ + GString *flattened; + int i; + int n_spaces; + + /* flatten to nul-separated list */ + n_spaces = meta_screen_get_n_workspaces (screen); + flattened = g_string_new (""); + i = 0; + while (i < n_spaces) + { + const char *name; + + name = meta_prefs_get_workspace_name (i); + + if (name) + g_string_append_len (flattened, name, + strlen (name) + 1); + else + g_string_append_len (flattened, "", 1); + + ++i; + } + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, + screen->xroot, + screen->display->atom__NET_DESKTOP_NAMES, + screen->display->atom_UTF8_STRING, + 8, PropModeReplace, + (unsigned char *)flattened->str, flattened->len); + meta_error_trap_pop (screen->display, FALSE); + + g_string_free (flattened, TRUE); +} + +void +meta_screen_update_workspace_names (MetaScreen *screen) +{ + char **names; + int n_names; + int i; + + /* this updates names in prefs when the root window property changes, + * iff the new property contents don't match what's already in prefs + */ + + names = NULL; + n_names = 0; + if (!meta_prop_get_utf8_list (screen->display, + screen->xroot, + screen->display->atom__NET_DESKTOP_NAMES, + &names, &n_names)) + { + meta_verbose ("Failed to get workspace names from root window %d\n", + screen->number); + return; + } + + i = 0; + while (i < n_names) + { + meta_topic (META_DEBUG_PREFS, + "Setting workspace %d name to \"%s\" due to _NET_DESKTOP_NAMES change\n", + i, names[i] ? names[i] : "null"); + meta_prefs_change_workspace_name (i, names[i]); + + ++i; + } + + g_strfreev (names); +} + +Window +meta_create_offscreen_window (Display *xdisplay, + Window parent, + long valuemask) +{ + XSetWindowAttributes attrs; + + /* we want to be override redirect because sometimes we + * create a window on a screen we aren't managing. + * (but on a display we are managing at least one screen for) + */ + attrs.override_redirect = True; + attrs.event_mask = valuemask; + + return XCreateWindow (xdisplay, + parent, + -100, -100, 1, 1, + 0, + CopyFromParent, + CopyFromParent, + (Visual *)CopyFromParent, + CWOverrideRedirect | CWEventMask, + &attrs); +} + +static void +set_work_area_hint (MetaScreen *screen) +{ + int num_workspaces; + GList *tmp_list; + unsigned long *data, *tmp; + MetaRectangle area; + + num_workspaces = meta_screen_get_n_workspaces (screen); + data = g_new (unsigned long, num_workspaces * 4); + tmp_list = screen->workspaces; + tmp = data; + + while (tmp_list != NULL) + { + MetaWorkspace *workspace = tmp_list->data; + + if (workspace->screen == screen) + { + meta_workspace_get_work_area_all_xineramas (workspace, &area); + tmp[0] = area.x; + tmp[1] = area.y; + tmp[2] = area.width; + tmp[3] = area.height; + + tmp += 4; + } + + tmp_list = tmp_list->next; + } + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_WORKAREA, + XA_CARDINAL, 32, PropModeReplace, + (guchar*) data, num_workspaces*4); + g_free (data); + meta_error_trap_pop (screen->display, FALSE); +} + +static gboolean +set_work_area_idle_func (MetaScreen *screen) +{ + meta_topic (META_DEBUG_WORKAREA, + "Running work area idle function\n"); + + screen->work_area_idle = 0; + + set_work_area_hint (screen); + + return FALSE; +} + +void +meta_screen_queue_workarea_recalc (MetaScreen *screen) +{ + /* Recompute work area in an idle */ + if (screen->work_area_idle == 0) + { + meta_topic (META_DEBUG_WORKAREA, + "Adding work area hint idle function\n"); + screen->work_area_idle = + g_idle_add_full (META_PRIORITY_WORK_AREA_HINT, + (GSourceFunc) set_work_area_idle_func, + screen, + NULL); + } +} + + +#ifdef WITH_VERBOSE_MODE +static char * +meta_screen_corner_to_string (MetaScreenCorner corner) +{ + switch (corner) + { + case META_SCREEN_TOPLEFT: + return "TopLeft"; + case META_SCREEN_TOPRIGHT: + return "TopRight"; + case META_SCREEN_BOTTOMLEFT: + return "BottomLeft"; + case META_SCREEN_BOTTOMRIGHT: + return "BottomRight"; + } + + return "Unknown"; +} +#endif /* WITH_VERBOSE_MODE */ + +void +meta_screen_calc_workspace_layout (MetaScreen *screen, + int num_workspaces, + int current_space, + MetaWorkspaceLayout *layout) +{ + int rows, cols; + int grid_area; + int *grid; + int i, r, c; + int current_row, current_col; + + rows = screen->rows_of_workspaces; + cols = screen->columns_of_workspaces; + if (rows <= 0 && cols <= 0) + cols = num_workspaces; + + if (rows <= 0) + rows = num_workspaces / cols + ((num_workspaces % cols) > 0 ? 1 : 0); + if (cols <= 0) + cols = num_workspaces / rows + ((num_workspaces % rows) > 0 ? 1 : 0); + + /* paranoia */ + if (rows < 1) + rows = 1; + if (cols < 1) + cols = 1; + + g_assert (rows != 0 && cols != 0); + + grid_area = rows * cols; + + meta_verbose ("Getting layout rows = %d cols = %d current = %d " + "num_spaces = %d vertical = %s corner = %s\n", + rows, cols, current_space, num_workspaces, + screen->vertical_workspaces ? "(true)" : "(false)", + meta_screen_corner_to_string (screen->starting_corner)); + + /* ok, we want to setup the distances in the workspace array to go + * in each direction. Remember, there are many ways that a workspace + * array can be setup. + * see http://www.freedesktop.org/standards/wm-spec/1.2/html/x109.html + * and look at the _NET_DESKTOP_LAYOUT section for details. + * For instance: + */ + /* starting_corner = META_SCREEN_TOPLEFT + * vertical_workspaces = 0 vertical_workspaces=1 + * 1234 1357 + * 5678 2468 + * + * starting_corner = META_SCREEN_TOPRIGHT + * vertical_workspaces = 0 vertical_workspaces=1 + * 4321 7531 + * 8765 8642 + * + * starting_corner = META_SCREEN_BOTTOMLEFT + * vertical_workspaces = 0 vertical_workspaces=1 + * 5678 2468 + * 1234 1357 + * + * starting_corner = META_SCREEN_BOTTOMRIGHT + * vertical_workspaces = 0 vertical_workspaces=1 + * 8765 8642 + * 4321 7531 + * + */ + /* keep in mind that we could have a ragged layout, e.g. the "8" + * in the above grids could be missing + */ + + + grid = g_new (int, grid_area); + + current_row = -1; + current_col = -1; + i = 0; + + switch (screen->starting_corner) + { + case META_SCREEN_TOPLEFT: + if (screen->vertical_workspaces) + { + c = 0; + while (c < cols) + { + r = 0; + while (r < rows) + { + grid[r*cols+c] = i; + ++i; + ++r; + } + ++c; + } + } + else + { + r = 0; + while (r < rows) + { + c = 0; + while (c < cols) + { + grid[r*cols+c] = i; + ++i; + ++c; + } + ++r; + } + } + break; + case META_SCREEN_TOPRIGHT: + if (screen->vertical_workspaces) + { + c = cols - 1; + while (c >= 0) + { + r = 0; + while (r < rows) + { + grid[r*cols+c] = i; + ++i; + ++r; + } + --c; + } + } + else + { + r = 0; + while (r < rows) + { + c = cols - 1; + while (c >= 0) + { + grid[r*cols+c] = i; + ++i; + --c; + } + ++r; + } + } + break; + case META_SCREEN_BOTTOMLEFT: + if (screen->vertical_workspaces) + { + c = 0; + while (c < cols) + { + r = rows - 1; + while (r >= 0) + { + grid[r*cols+c] = i; + ++i; + --r; + } + ++c; + } + } + else + { + r = rows - 1; + while (r >= 0) + { + c = 0; + while (c < cols) + { + grid[r*cols+c] = i; + ++i; + ++c; + } + --r; + } + } + break; + case META_SCREEN_BOTTOMRIGHT: + if (screen->vertical_workspaces) + { + c = cols - 1; + while (c >= 0) + { + r = rows - 1; + while (r >= 0) + { + grid[r*cols+c] = i; + ++i; + --r; + } + --c; + } + } + else + { + r = rows - 1; + while (r >= 0) + { + c = cols - 1; + while (c >= 0) + { + grid[r*cols+c] = i; + ++i; + --c; + } + --r; + } + } + break; + } + + if (i != grid_area) + meta_bug ("did not fill in the whole workspace grid in %s (%d filled)\n", + G_STRFUNC, i); + + current_row = 0; + current_col = 0; + r = 0; + while (r < rows) + { + c = 0; + while (c < cols) + { + if (grid[r*cols+c] == current_space) + { + current_row = r; + current_col = c; + } + else if (grid[r*cols+c] >= num_workspaces) + { + /* flag nonexistent spaces with -1 */ + grid[r*cols+c] = -1; + } + ++c; + } + ++r; + } + + layout->rows = rows; + layout->cols = cols; + layout->grid = grid; + layout->grid_area = grid_area; + layout->current_row = current_row; + layout->current_col = current_col; + +#ifdef WITH_VERBOSE_MODE + if (meta_is_verbose ()) + { + r = 0; + while (r < layout->rows) + { + meta_verbose (" "); + meta_push_no_msg_prefix (); + c = 0; + while (c < layout->cols) + { + if (r == layout->current_row && + c == layout->current_col) + meta_verbose ("*%2d ", layout->grid[r*layout->cols+c]); + else + meta_verbose ("%3d ", layout->grid[r*layout->cols+c]); + ++c; + } + meta_verbose ("\n"); + meta_pop_no_msg_prefix (); + ++r; + } + } +#endif /* WITH_VERBOSE_MODE */ +} + +void +meta_screen_free_workspace_layout (MetaWorkspaceLayout *layout) +{ + g_free (layout->grid); +} + +static void +meta_screen_resize_func (MetaScreen *screen, + MetaWindow *window, + void *user_data) +{ + if (window->struts) + { + meta_window_update_struts (window); + } + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); + + meta_window_recalc_features (window); +} + +void +meta_screen_resize (MetaScreen *screen, + int width, + int height) +{ + screen->rect.width = width; + screen->rect.height = height; + + reload_xinerama_infos (screen); + set_desktop_geometry_hint (screen); + + /* Queue a resize on all the windows */ + meta_screen_foreach_window (screen, meta_screen_resize_func, 0); +} + +void +meta_screen_update_showing_desktop_hint (MetaScreen *screen) +{ + unsigned long data[1]; + + data[0] = screen->active_workspace->showing_desktop ? 1 : 0; + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_SHOWING_DESKTOP, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 1); + meta_error_trap_pop (screen->display, FALSE); +} + +static void +queue_windows_showing (MetaScreen *screen) +{ + GSList *windows; + GSList *tmp; + + /* Must operate on all windows on display instead of just on the + * active_workspace's window list, because the active_workspace's + * window list may not contain the on_all_workspace windows. + */ + windows = meta_display_list_windows (screen->display); + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->screen == screen) + meta_window_queue (w, META_QUEUE_CALC_SHOWING); + + tmp = tmp->next; + } + + g_slist_free (windows); +} + +void +meta_screen_minimize_all_on_active_workspace_except (MetaScreen *screen, + MetaWindow *keep) +{ + GList *windows; + GList *tmp; + + windows = screen->active_workspace->windows; + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->screen == screen && + w->has_minimize_func && + w != keep) + meta_window_minimize (w); + + tmp = tmp->next; + } +} + +void +meta_screen_show_desktop (MetaScreen *screen, + guint32 timestamp) +{ + GList *windows; + + if (screen->active_workspace->showing_desktop) + return; + + screen->active_workspace->showing_desktop = TRUE; + + queue_windows_showing (screen); + + /* Focus the most recently used META_WINDOW_DESKTOP window, if there is one; + * see bug 159257. + */ + windows = screen->active_workspace->mru_list; + while (windows != NULL) + { + MetaWindow *w = windows->data; + + if (w->screen == screen && + w->type == META_WINDOW_DESKTOP) + { + meta_window_focus (w, timestamp); + break; + } + + windows = windows->next; + } + + + meta_screen_update_showing_desktop_hint (screen); +} + +void +meta_screen_unshow_desktop (MetaScreen *screen) +{ + if (!screen->active_workspace->showing_desktop) + return; + + screen->active_workspace->showing_desktop = FALSE; + + queue_windows_showing (screen); + + meta_screen_update_showing_desktop_hint (screen); +} + + +#ifdef HAVE_STARTUP_NOTIFICATION +static gboolean startup_sequence_timeout (void *data); + +static void +update_startup_feedback (MetaScreen *screen) +{ + if (screen->startup_sequences != NULL) + { + meta_topic (META_DEBUG_STARTUP, + "Setting busy cursor\n"); + meta_screen_set_cursor (screen, META_CURSOR_BUSY); + } + else + { + meta_topic (META_DEBUG_STARTUP, + "Setting default cursor\n"); + meta_screen_set_cursor (screen, META_CURSOR_DEFAULT); + } +} + +static void +add_sequence (MetaScreen *screen, + SnStartupSequence *sequence) +{ + meta_topic (META_DEBUG_STARTUP, + "Adding sequence %s\n", + sn_startup_sequence_get_id (sequence)); + sn_startup_sequence_ref (sequence); + screen->startup_sequences = g_slist_prepend (screen->startup_sequences, + sequence); + + /* our timeout just polls every second, instead of bothering + * to compute exactly when we may next time out + */ + if (screen->startup_sequence_timeout == 0) + screen->startup_sequence_timeout = g_timeout_add (1000, + startup_sequence_timeout, + screen); + + update_startup_feedback (screen); +} + +static void +remove_sequence (MetaScreen *screen, + SnStartupSequence *sequence) +{ + meta_topic (META_DEBUG_STARTUP, + "Removing sequence %s\n", + sn_startup_sequence_get_id (sequence)); + + screen->startup_sequences = g_slist_remove (screen->startup_sequences, + sequence); + sn_startup_sequence_unref (sequence); + + if (screen->startup_sequences == NULL && + screen->startup_sequence_timeout != 0) + { + g_source_remove (screen->startup_sequence_timeout); + screen->startup_sequence_timeout = 0; + } + + update_startup_feedback (screen); +} + +typedef struct +{ + GSList *list; + GTimeVal now; +} CollectTimedOutData; + +/* This should be fairly long, as it should never be required unless + * apps or .desktop files are buggy, and it's confusing if + * OpenOffice or whatever seems to stop launching - people + * might decide they need to launch it again. + */ +#define STARTUP_TIMEOUT 15000 + +static void +collect_timed_out_foreach (void *element, + void *data) +{ + CollectTimedOutData *ctod = data; + SnStartupSequence *sequence = element; + long tv_sec, tv_usec; + double elapsed; + + sn_startup_sequence_get_last_active_time (sequence, &tv_sec, &tv_usec); + + elapsed = + ((((double)ctod->now.tv_sec - tv_sec) * G_USEC_PER_SEC + + (ctod->now.tv_usec - tv_usec))) / 1000.0; + + meta_topic (META_DEBUG_STARTUP, + "Sequence used %g seconds vs. %g max: %s\n", + elapsed, (double) STARTUP_TIMEOUT, + sn_startup_sequence_get_id (sequence)); + + if (elapsed > STARTUP_TIMEOUT) + ctod->list = g_slist_prepend (ctod->list, sequence); +} + +static gboolean +startup_sequence_timeout (void *data) +{ + MetaScreen *screen = data; + CollectTimedOutData ctod; + GSList *tmp; + + ctod.list = NULL; + g_get_current_time (&ctod.now); + g_slist_foreach (screen->startup_sequences, + collect_timed_out_foreach, + &ctod); + + tmp = ctod.list; + while (tmp != NULL) + { + SnStartupSequence *sequence = tmp->data; + + meta_topic (META_DEBUG_STARTUP, + "Timed out sequence %s\n", + sn_startup_sequence_get_id (sequence)); + + sn_startup_sequence_complete (sequence); + + tmp = tmp->next; + } + + g_slist_free (ctod.list); + + if (screen->startup_sequences != NULL) + { + return TRUE; + } + else + { + /* remove */ + screen->startup_sequence_timeout = 0; + return FALSE; + } +} + +static void +meta_screen_sn_event (SnMonitorEvent *event, + void *user_data) +{ + MetaScreen *screen; + SnStartupSequence *sequence; + + screen = user_data; + + sequence = sn_monitor_event_get_startup_sequence (event); + + switch (sn_monitor_event_get_type (event)) + { + case SN_MONITOR_EVENT_INITIATED: + { + const char *wmclass; + + wmclass = sn_startup_sequence_get_wmclass (sequence); + + meta_topic (META_DEBUG_STARTUP, + "Received startup initiated for %s wmclass %s\n", + sn_startup_sequence_get_id (sequence), + wmclass ? wmclass : "(unset)"); + add_sequence (screen, sequence); + } + break; + + case SN_MONITOR_EVENT_COMPLETED: + { + meta_topic (META_DEBUG_STARTUP, + "Received startup completed for %s\n", + sn_startup_sequence_get_id (sequence)); + remove_sequence (screen, + sn_monitor_event_get_startup_sequence (event)); + } + break; + + case SN_MONITOR_EVENT_CHANGED: + meta_topic (META_DEBUG_STARTUP, + "Received startup changed for %s\n", + sn_startup_sequence_get_id (sequence)); + break; + + case SN_MONITOR_EVENT_CANCELED: + meta_topic (META_DEBUG_STARTUP, + "Received startup canceled for %s\n", + sn_startup_sequence_get_id (sequence)); + break; + } +} +#endif + +/* Sets the initial_timestamp and initial_workspace properties + * of a window according to information given us by the + * startup-notification library. + * + * Returns TRUE if startup properties have been applied, and + * FALSE if they have not (for example, if they had already + * been applied.) + */ +gboolean +meta_screen_apply_startup_properties (MetaScreen *screen, + MetaWindow *window) +{ +#ifdef HAVE_STARTUP_NOTIFICATION + const char *startup_id; + GSList *tmp; + SnStartupSequence *sequence; + + /* Does the window have a startup ID stored? */ + startup_id = meta_window_get_startup_id (window); + + meta_topic (META_DEBUG_STARTUP, + "Applying startup props to %s id \"%s\"\n", + window->desc, + startup_id ? startup_id : "(none)"); + + sequence = NULL; + if (startup_id == NULL) + { + /* No startup ID stored for the window. Let's ask the + * startup-notification library whether there's anything + * stored for the resource name or resource class hints. + */ + tmp = screen->startup_sequences; + while (tmp != NULL) + { + const char *wmclass; + + wmclass = sn_startup_sequence_get_wmclass (tmp->data); + + if (wmclass != NULL && + ((window->res_class && + strcmp (wmclass, window->res_class) == 0) || + (window->res_name && + strcmp (wmclass, window->res_name) == 0))) + { + sequence = tmp->data; + + g_assert (window->startup_id == NULL); + window->startup_id = g_strdup (sn_startup_sequence_get_id (sequence)); + startup_id = window->startup_id; + + meta_topic (META_DEBUG_STARTUP, + "Ending legacy sequence %s due to window %s\n", + sn_startup_sequence_get_id (sequence), + window->desc); + + sn_startup_sequence_complete (sequence); + break; + } + + tmp = tmp->next; + } + } + + /* Still no startup ID? Bail. */ + if (startup_id == NULL) + return FALSE; + + /* We might get this far and not know the sequence ID (if the window + * already had a startup ID stored), so let's look for one if we don't + * already know it. + */ + if (sequence == NULL) + { + tmp = screen->startup_sequences; + while (tmp != NULL) + { + const char *id; + + id = sn_startup_sequence_get_id (tmp->data); + + if (strcmp (id, startup_id) == 0) + { + sequence = tmp->data; + break; + } + + tmp = tmp->next; + } + } + + if (sequence != NULL) + { + gboolean changed_something = FALSE; + + meta_topic (META_DEBUG_STARTUP, + "Found startup sequence for window %s ID \"%s\"\n", + window->desc, startup_id); + + if (!window->initial_workspace_set) + { + int space = sn_startup_sequence_get_workspace (sequence); + if (space >= 0) + { + meta_topic (META_DEBUG_STARTUP, + "Setting initial window workspace to %d based on startup info\n", + space); + + window->initial_workspace_set = TRUE; + window->initial_workspace = space; + changed_something = TRUE; + } + } + + if (!window->initial_timestamp_set) + { + guint32 timestamp = sn_startup_sequence_get_timestamp (sequence); + meta_topic (META_DEBUG_STARTUP, + "Setting initial window timestamp to %u based on startup info\n", + timestamp); + + window->initial_timestamp_set = TRUE; + window->initial_timestamp = timestamp; + changed_something = TRUE; + } + + return changed_something; + } + else + { + meta_topic (META_DEBUG_STARTUP, + "Did not find startup sequence for window %s ID \"%s\"\n", + window->desc, startup_id); + } + +#endif /* HAVE_STARTUP_NOTIFICATION */ + + return FALSE; +} + +int +meta_screen_get_screen_number (MetaScreen *screen) +{ + return screen->number; +} + +MetaDisplay * +meta_screen_get_display (MetaScreen *screen) +{ + return screen->display; +} + +Window +meta_screen_get_xroot (MetaScreen *screen) +{ + return screen->xroot; +} + +void +meta_screen_get_size (MetaScreen *screen, + int *width, + int *height) +{ + *width = screen->rect.width; + *height = screen->rect.height; +} + +gpointer +meta_screen_get_compositor_data (MetaScreen *screen) +{ + return screen->compositor_data; +} + +void +meta_screen_set_compositor_data (MetaScreen *screen, + gpointer compositor) +{ + screen->compositor_data = compositor; +} + +#ifdef HAVE_COMPOSITE_EXTENSIONS +void +meta_screen_set_cm_selection (MetaScreen *screen) +{ + char selection[32]; + Atom a; + + screen->wm_cm_timestamp = meta_display_get_current_time_roundtrip ( + screen->display); + + g_snprintf (selection, sizeof(selection), "_NET_WM_CM_S%d", screen->number); + meta_verbose ("Setting selection: %s\n", selection); + a = XInternAtom (screen->display->xdisplay, selection, FALSE); + XSetSelectionOwner (screen->display->xdisplay, a, + screen->wm_cm_selection_window, screen->wm_cm_timestamp); +} + +void +meta_screen_unset_cm_selection (MetaScreen *screen) +{ + char selection[32]; + Atom a; + + g_snprintf (selection, sizeof(selection), "_NET_WM_CM_S%d", screen->number); + a = XInternAtom (screen->display->xdisplay, selection, FALSE); + XSetSelectionOwner (screen->display->xdisplay, a, + None, screen->wm_cm_timestamp); +} +#endif /* HAVE_COMPOSITE_EXTENSIONS */ diff --git a/src/core/session.c b/src/core/session.c new file mode 100644 index 00000000..80d22365 --- /dev/null +++ b/src/core/session.c @@ -0,0 +1,1831 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco Session Management */ + +/* + * Copyright (C) 2001 Havoc Pennington (some code in here from + * libmateui, (C) Tom Tromey, Carsten Schaar) + * Copyright (C) 2004, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> + +#include "session.h" +#include <X11/Xatom.h> + +#include <time.h> +#include <sys/wait.h> + +#ifndef HAVE_SM +void +meta_session_init (const char *client_id, + const char *save_file) +{ + meta_topic (META_DEBUG_SM, "Compiled without session management support\n"); +} + +void +meta_session_shutdown (void) +{ + /* nothing */ +} + +const MetaWindowSessionInfo* +meta_window_lookup_saved_state (MetaWindow *window) +{ + return NULL; +} + +void +meta_window_release_saved_state (const MetaWindowSessionInfo *info) +{ + ; +} +#else /* HAVE_SM */ + +#include <X11/ICE/ICElib.h> +#include <X11/SM/SMlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <errno.h> +#include <glib.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include "main.h" +#include "util.h" +#include "display-private.h" +#include "workspace.h" + +static void ice_io_error_handler (IceConn connection); + +static void new_ice_connection (IceConn connection, IcePointer client_data, + Bool opening, IcePointer *watch_data); + +static void save_state (void); +static char* load_state (const char *previous_save_file); +static void regenerate_save_file (void); +static const char* full_save_file (void); +static void warn_about_lame_clients_and_finish_interact (gboolean shutdown); +static void disconnect (void); + +/* This is called when data is available on an ICE connection. */ +static gboolean +process_ice_messages (GIOChannel *channel, + GIOCondition condition, + gpointer client_data) +{ + IceConn connection = (IceConn) client_data; + IceProcessMessagesStatus status; + + /* This blocks infinitely sometimes. I don't know what + * to do about it. Checking "condition" just breaks + * session management. + */ + status = IceProcessMessages (connection, NULL, NULL); + + if (status == IceProcessMessagesIOError) + { +#if 0 + IcePointer context = IceGetConnectionContext (connection); +#endif + + /* We were disconnected; close our connection to the + * session manager, this will result in the ICE connection + * being cleaned up, since it is owned by libSM. + */ + disconnect (); + meta_quit (META_EXIT_SUCCESS); + + return FALSE; + } + + return TRUE; +} + +/* This is called when a new ICE connection is made. It arranges for + the ICE connection to be handled via the event loop. */ +static void +new_ice_connection (IceConn connection, IcePointer client_data, Bool opening, + IcePointer *watch_data) +{ + guint input_id; + + if (opening) + { + /* Make sure we don't pass on these file descriptors to any + * exec'ed children + */ + GIOChannel *channel; + + fcntl (IceConnectionNumber (connection), F_SETFD, + fcntl (IceConnectionNumber (connection), F_GETFD, 0) | FD_CLOEXEC); + + channel = g_io_channel_unix_new (IceConnectionNumber (connection)); + + input_id = g_io_add_watch (channel, + G_IO_IN | G_IO_ERR, + process_ice_messages, + connection); + + g_io_channel_unref (channel); + + *watch_data = (IcePointer) GUINT_TO_POINTER (input_id); + } + else + { + input_id = GPOINTER_TO_UINT ((gpointer) *watch_data); + + g_source_remove (input_id); + } +} + +static IceIOErrorHandler ice_installed_handler; + +/* We call any handler installed before (or after) mate_ice_init but + avoid calling the default libICE handler which does an exit() */ +static void +ice_io_error_handler (IceConn connection) +{ + if (ice_installed_handler) + (*ice_installed_handler) (connection); +} + +static void +ice_init (void) +{ + static gboolean ice_initted = FALSE; + + if (! ice_initted) + { + IceIOErrorHandler default_handler; + + ice_installed_handler = IceSetIOErrorHandler (NULL); + default_handler = IceSetIOErrorHandler (ice_io_error_handler); + + if (ice_installed_handler == default_handler) + ice_installed_handler = NULL; + + IceAddConnectionWatch (new_ice_connection, NULL); + + ice_initted = TRUE; + } +} + +typedef enum +{ + STATE_DISCONNECTED, + STATE_IDLE, + STATE_SAVING_PHASE_1, + STATE_WAITING_FOR_PHASE_2, + STATE_SAVING_PHASE_2, + STATE_WAITING_FOR_INTERACT, + STATE_DONE_WITH_INTERACT, + STATE_SKIPPING_GLOBAL_SAVE, + STATE_FROZEN, + STATE_REGISTERING +} ClientState; + +static void save_phase_2_callback (SmcConn smc_conn, + SmPointer client_data); +static void interact_callback (SmcConn smc_conn, + SmPointer client_data); +static void shutdown_cancelled_callback (SmcConn smc_conn, + SmPointer client_data); +static void save_complete_callback (SmcConn smc_conn, + SmPointer client_data); +static void die_callback (SmcConn smc_conn, + SmPointer client_data); +static void save_yourself_callback (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast); +static void set_clone_restart_commands (void); + +static char *client_id = NULL; +static gpointer session_connection = NULL; +static ClientState current_state = STATE_DISCONNECTED; +static gboolean interaction_allowed = FALSE; + +void +meta_session_init (const char *previous_client_id, + const char *previous_save_file) +{ + /* Some code here from twm */ + char buf[256]; + unsigned long mask; + SmcCallbacks callbacks; + char *saved_client_id; + + meta_topic (META_DEBUG_SM, "Initializing session with save file '%s'\n", + previous_save_file ? previous_save_file : "(none)"); + + if (previous_save_file) + { + saved_client_id = load_state (previous_save_file); + previous_client_id = saved_client_id; + } + else if (previous_client_id) + { + char *save_file = g_strconcat (previous_client_id, ".ms", NULL); + saved_client_id = load_state (save_file); + g_free (save_file); + } + else + { + saved_client_id = NULL; + } + + ice_init (); + + mask = SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask; + + callbacks.save_yourself.callback = save_yourself_callback; + callbacks.save_yourself.client_data = NULL; + + callbacks.die.callback = die_callback; + callbacks.die.client_data = NULL; + + callbacks.save_complete.callback = save_complete_callback; + callbacks.save_complete.client_data = NULL; + + callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback; + callbacks.shutdown_cancelled.client_data = NULL; + + session_connection = + SmcOpenConnection (NULL, /* use SESSION_MANAGER env */ + NULL, /* means use existing ICE connection */ + SmProtoMajor, + SmProtoMinor, + mask, + &callbacks, + (char*) previous_client_id, + &client_id, + 255, buf); + + if (session_connection == NULL) + { + meta_topic (META_DEBUG_SM, + "Failed to a open connection to a session manager, so window positions will not be saved: %s\n", + buf); + + goto out; + } + else + { + if (client_id == NULL) + meta_bug ("Session manager gave us a NULL client ID?"); + meta_topic (META_DEBUG_SM, "Obtained session ID '%s'\n", client_id); + } + + if (previous_client_id && strcmp (previous_client_id, client_id) == 0) + current_state = STATE_IDLE; + else + current_state = STATE_REGISTERING; + + { + SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6]; + SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val, prop6val; + char pid[32]; + char hint = SmRestartImmediately; + char priority = 20; /* low to run before other apps */ + + prop1.name = SmProgram; + prop1.type = SmARRAY8; + prop1.num_vals = 1; + prop1.vals = &prop1val; + prop1val.value = "marco"; + prop1val.length = strlen ("marco"); + + /* twm sets getuid() for this, but the SM spec plainly + * says pw_name, twm is on crack + */ + prop2.name = SmUserID; + prop2.type = SmARRAY8; + prop2.num_vals = 1; + prop2.vals = &prop2val; + prop2val.value = (char*) g_get_user_name (); + prop2val.length = strlen (prop2val.value); + + prop3.name = SmRestartStyleHint; + prop3.type = SmCARD8; + prop3.num_vals = 1; + prop3.vals = &prop3val; + prop3val.value = &hint; + prop3val.length = 1; + + sprintf (pid, "%d", getpid ()); + prop4.name = SmProcessID; + prop4.type = SmARRAY8; + prop4.num_vals = 1; + prop4.vals = &prop4val; + prop4val.value = pid; + prop4val.length = strlen (prop4val.value); + + /* Always start in home directory */ + prop5.name = SmCurrentDirectory; + prop5.type = SmARRAY8; + prop5.num_vals = 1; + prop5.vals = &prop5val; + prop5val.value = (char*) g_get_home_dir (); + prop5val.length = strlen (prop5val.value); + + prop6.name = "_GSM_Priority"; + prop6.type = SmCARD8; + prop6.num_vals = 1; + prop6.vals = &prop6val; + prop6val.value = &priority; + prop6val.length = 1; + + props[0] = &prop1; + props[1] = &prop2; + props[2] = &prop3; + props[3] = &prop4; + props[4] = &prop5; + props[5] = &prop6; + + SmcSetProperties (session_connection, 6, props); + } + + out: + g_free (saved_client_id); +} + +void +meta_session_shutdown (void) +{ + /* Change our restart mode to IfRunning */ + + SmProp prop1; + SmPropValue prop1val; + SmProp *props[1]; + char hint = SmRestartIfRunning; + + if (!meta_get_display ()) + { + meta_verbose ("Cannot close session because there is no display"); + return; + } + + warn_about_lame_clients_and_finish_interact (FALSE); + + if (session_connection == NULL) + return; + + prop1.name = SmRestartStyleHint; + prop1.type = SmCARD8; + prop1.num_vals = 1; + prop1.vals = &prop1val; + prop1val.value = &hint; + prop1val.length = 1; + + props[0] = &prop1; + + SmcSetProperties (session_connection, 1, props); +} + +static void +disconnect (void) +{ + SmcCloseConnection (session_connection, 0, NULL); + session_connection = NULL; + current_state = STATE_DISCONNECTED; +} + +static void +save_yourself_possibly_done (gboolean shutdown, + gboolean successful) +{ + meta_topic (META_DEBUG_SM, + "save possibly done shutdown = %d success = %d\n", + shutdown, successful); + + if (current_state == STATE_SAVING_PHASE_1) + { + Status status; + + status = SmcRequestSaveYourselfPhase2 (session_connection, + save_phase_2_callback, + GINT_TO_POINTER (shutdown)); + + if (status) + current_state = STATE_WAITING_FOR_PHASE_2; + + meta_topic (META_DEBUG_SM, + "Requested phase 2, status = %d\n", status); + } + + if (current_state == STATE_SAVING_PHASE_2 && + interaction_allowed) + { + Status status; + + status = SmcInteractRequest (session_connection, + /* ignore this feature of the protocol by always + * claiming normal + */ + SmDialogNormal, + interact_callback, + GINT_TO_POINTER (shutdown)); + + if (status) + current_state = STATE_WAITING_FOR_INTERACT; + + meta_topic (META_DEBUG_SM, + "Requested interact, status = %d\n", status); + } + + if (current_state == STATE_SAVING_PHASE_1 || + current_state == STATE_SAVING_PHASE_2 || + current_state == STATE_DONE_WITH_INTERACT || + current_state == STATE_SKIPPING_GLOBAL_SAVE) + { + meta_topic (META_DEBUG_SM, "Sending SaveYourselfDone\n"); + + SmcSaveYourselfDone (session_connection, + successful); + + if (shutdown) + current_state = STATE_FROZEN; + else + current_state = STATE_IDLE; + } +} + +static void +save_phase_2_callback (SmcConn smc_conn, SmPointer client_data) +{ + gboolean shutdown; + + meta_topic (META_DEBUG_SM, "Phase 2 save"); + + shutdown = GPOINTER_TO_INT (client_data); + + current_state = STATE_SAVING_PHASE_2; + + save_state (); + + save_yourself_possibly_done (shutdown, TRUE); +} + +static void +save_yourself_callback (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast) +{ + gboolean successful; + + meta_topic (META_DEBUG_SM, "SaveYourself received"); + + successful = TRUE; + + /* The first SaveYourself after registering for the first time + * is a special case (SM specs 7.2). + */ + +#if 0 /* I think the MateClient rationale for this doesn't apply */ + if (current_state == STATE_REGISTERING) + { + current_state = STATE_IDLE; + /* Double check that this is a section 7.2 SaveYourself: */ + + if (save_style == SmSaveLocal && + interact_style == SmInteractStyleNone && + !shutdown && !fast) + { + /* The protocol requires this even if xsm ignores it. */ + SmcSaveYourselfDone (session_connection, successful); + return; + } + } +#endif + + /* ignore Global style saves + * + * This interpretaion of the Local/Global/Both styles + * was discussed extensively on the xdg-list. See: + * + * https://listman.redhat.com/pipermail/xdg-list/2002-July/000615.html + */ + if (save_style == SmSaveGlobal) + { + current_state = STATE_SKIPPING_GLOBAL_SAVE; + save_yourself_possibly_done (shutdown, successful); + return; + } + + interaction_allowed = interact_style != SmInteractStyleNone; + + current_state = STATE_SAVING_PHASE_1; + + regenerate_save_file (); + + set_clone_restart_commands (); + + save_yourself_possibly_done (shutdown, successful); +} + + +static void +die_callback (SmcConn smc_conn, SmPointer client_data) +{ + meta_topic (META_DEBUG_SM, "Exiting at request of session manager\n"); + disconnect (); + meta_quit (META_EXIT_SUCCESS); +} + +static void +save_complete_callback (SmcConn smc_conn, SmPointer client_data) +{ + /* nothing */ + meta_topic (META_DEBUG_SM, "SaveComplete received\n"); +} + +static void +shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data) +{ + meta_topic (META_DEBUG_SM, "Shutdown cancelled received\n"); + + if (session_connection != NULL && + (current_state != STATE_IDLE && current_state != STATE_FROZEN)) + { + SmcSaveYourselfDone (session_connection, True); + current_state = STATE_IDLE; + } +} + +static void +interact_callback (SmcConn smc_conn, SmPointer client_data) +{ + /* nothing */ + gboolean shutdown; + + meta_topic (META_DEBUG_SM, "Interaction permission received\n"); + + shutdown = GPOINTER_TO_INT (client_data); + + current_state = STATE_DONE_WITH_INTERACT; + + warn_about_lame_clients_and_finish_interact (shutdown); +} + +static void +set_clone_restart_commands (void) +{ + char *restartv[10]; + char *clonev[10]; + char *discardv[10]; + int i; + SmProp prop1, prop2, prop3, *props[3]; + + /* Restart (use same client ID) */ + + prop1.name = SmRestartCommand; + prop1.type = SmLISTofARRAY8; + + g_return_if_fail (client_id); + + i = 0; + restartv[i] = "marco"; + ++i; + restartv[i] = "--sm-client-id"; + ++i; + restartv[i] = client_id; + ++i; + restartv[i] = NULL; + + prop1.vals = g_new (SmPropValue, i); + i = 0; + while (restartv[i]) + { + prop1.vals[i].value = restartv[i]; + prop1.vals[i].length = strlen (restartv[i]); + ++i; + } + prop1.num_vals = i; + + /* Clone (no client ID) */ + + i = 0; + clonev[i] = "marco"; + ++i; + clonev[i] = NULL; + + prop2.name = SmCloneCommand; + prop2.type = SmLISTofARRAY8; + + prop2.vals = g_new (SmPropValue, i); + i = 0; + while (clonev[i]) + { + prop2.vals[i].value = clonev[i]; + prop2.vals[i].length = strlen (clonev[i]); + ++i; + } + prop2.num_vals = i; + + /* Discard */ + + i = 0; + discardv[i] = "rm"; + ++i; + discardv[i] = "-f"; + ++i; + discardv[i] = (char*) full_save_file (); + ++i; + discardv[i] = NULL; + + prop3.name = SmDiscardCommand; + prop3.type = SmLISTofARRAY8; + + prop3.vals = g_new (SmPropValue, i); + i = 0; + while (discardv[i]) + { + prop3.vals[i].value = discardv[i]; + prop3.vals[i].length = strlen (discardv[i]); + ++i; + } + prop3.num_vals = i; + + + props[0] = &prop1; + props[1] = &prop2; + props[2] = &prop3; + + SmcSetProperties (session_connection, 3, props); + + g_free (prop1.vals); + g_free (prop2.vals); + g_free (prop3.vals); +} + +/* The remaining code in this file actually loads/saves the session, + * while the code above this comment handles chatting with the + * session manager. + */ + +static const char* +window_type_to_string (MetaWindowType type) +{ + switch (type) + { + case META_WINDOW_NORMAL: + return "normal"; + case META_WINDOW_DESKTOP: + return "desktop"; + case META_WINDOW_DOCK: + return "dock"; + case META_WINDOW_DIALOG: + return "dialog"; + case META_WINDOW_MODAL_DIALOG: + return "modal_dialog"; + case META_WINDOW_TOOLBAR: + return "toolbar"; + case META_WINDOW_MENU: + return "menu"; + case META_WINDOW_SPLASHSCREEN: + return "splashscreen"; + case META_WINDOW_UTILITY: + return "utility"; + } + + return ""; +} + +static MetaWindowType +window_type_from_string (const char *str) +{ + if (strcmp (str, "normal") == 0) + return META_WINDOW_NORMAL; + else if (strcmp (str, "desktop") == 0) + return META_WINDOW_DESKTOP; + else if (strcmp (str, "dock") == 0) + return META_WINDOW_DOCK; + else if (strcmp (str, "dialog") == 0) + return META_WINDOW_DIALOG; + else if (strcmp (str, "modal_dialog") == 0) + return META_WINDOW_MODAL_DIALOG; + else if (strcmp (str, "toolbar") == 0) + return META_WINDOW_TOOLBAR; + else if (strcmp (str, "menu") == 0) + return META_WINDOW_MENU; + else if (strcmp (str, "utility") == 0) + return META_WINDOW_UTILITY; + else if (strcmp (str, "splashscreen") == 0) + return META_WINDOW_SPLASHSCREEN; + else + return META_WINDOW_NORMAL; +} + +static int +window_gravity_from_string (const char *str) +{ + if (strcmp (str, "NorthWestGravity") == 0) + return NorthWestGravity; + else if (strcmp (str, "NorthGravity") == 0) + return NorthGravity; + else if (strcmp (str, "NorthEastGravity") == 0) + return NorthEastGravity; + else if (strcmp (str, "WestGravity") == 0) + return WestGravity; + else if (strcmp (str, "CenterGravity") == 0) + return CenterGravity; + else if (strcmp (str, "EastGravity") == 0) + return EastGravity; + else if (strcmp (str, "SouthWestGravity") == 0) + return SouthWestGravity; + else if (strcmp (str, "SouthGravity") == 0) + return SouthGravity; + else if (strcmp (str, "SouthEastGravity") == 0) + return SouthEastGravity; + else if (strcmp (str, "StaticGravity") == 0) + return StaticGravity; + else + return NorthWestGravity; +} + +static char* +encode_text_as_utf8_markup (const char *text) +{ + /* text can be any encoding, and is nul-terminated. + * we pretend it's Latin-1 and encode as UTF-8 + */ + GString *str; + const char *p; + char *escaped; + + str = g_string_new (""); + + p = text; + while (*p) + { + g_string_append_unichar (str, *p); + ++p; + } + + escaped = g_markup_escape_text (str->str, str->len); + g_string_free (str, TRUE); + + return escaped; +} + +static char* +decode_text_from_utf8 (const char *text) +{ + /* Convert back from the encoded (but not escaped) UTF-8 */ + GString *str; + const char *p; + + str = g_string_new (""); + + p = text; + while (*p) + { + /* obviously this barfs if the UTF-8 contains chars > 255 */ + g_string_append_c (str, g_utf8_get_char (p)); + + p = g_utf8_next_char (p); + } + + return g_string_free (str, FALSE); +} + +static void +save_state (void) +{ + char *marco_dir; + char *session_dir; + FILE *outfile; + GSList *windows; + GSList *tmp; + int stack_position; + + g_assert (client_id); + + outfile = NULL; + + /* + * g_get_user_config_dir() is guaranteed to return an existing directory. + * Eventually, if SM stays with the WM, I'd like to make this + * something like <config>/window_placement in a standard format. + * Future optimisers should note also that by the time we get here + * we probably already have full_save_path figured out and therefore + * can just use the directory name from that. + */ + marco_dir = g_strconcat (g_get_user_config_dir (), + G_DIR_SEPARATOR_S "marco", + NULL); + + session_dir = g_strconcat (marco_dir, + G_DIR_SEPARATOR_S "sessions", + NULL); + + if (mkdir (marco_dir, 0700) < 0 && + errno != EEXIST) + { + meta_warning (_("Could not create directory '%s': %s\n"), + marco_dir, g_strerror (errno)); + } + + if (mkdir (session_dir, 0700) < 0 && + errno != EEXIST) + { + meta_warning (_("Could not create directory '%s': %s\n"), + session_dir, g_strerror (errno)); + } + + meta_topic (META_DEBUG_SM, "Saving session to '%s'\n", full_save_file ()); + + outfile = fopen (full_save_file (), "w"); + + if (outfile == NULL) + { + meta_warning (_("Could not open session file '%s' for writing: %s\n"), + full_save_file (), g_strerror (errno)); + goto out; + } + + /* The file format is: + * <marco_session id="foo"> + * <window id="bar" class="XTerm" name="xterm" title="/foo/bar" role="blah" type="normal" stacking="5"> + * <workspace index="2"/> + * <workspace index="4"/> + * <sticky/> <minimized/> <maximized/> + * <geometry x="100" y="100" width="200" height="200" gravity="northwest"/> + * </window> + * </marco_session> + * + * Note that attributes on <window> are the match info we use to + * see if the saved state applies to a restored window, and + * child elements are the saved state to be applied. + * + */ + + fprintf (outfile, "<marco_session id=\"%s\">\n", + client_id); + + windows = meta_display_list_windows (meta_get_display ()); + stack_position = 0; + + windows = g_slist_sort (windows, meta_display_stack_cmp); + tmp = windows; + stack_position = 0; + + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + if (window->sm_client_id) + { + char *sm_client_id; + char *res_class; + char *res_name; + char *role; + char *title; + + /* client id, class, name, role are not expected to be + * in UTF-8 (I think they are in XPCS which is Latin-1? + * in practice they are always ascii though.) + */ + + sm_client_id = encode_text_as_utf8_markup (window->sm_client_id); + res_class = window->res_class ? + encode_text_as_utf8_markup (window->res_class) : NULL; + res_name = window->res_name ? + encode_text_as_utf8_markup (window->res_name) : NULL; + role = window->role ? + encode_text_as_utf8_markup (window->role) : NULL; + if (window->title) + title = g_markup_escape_text (window->title, -1); + else + title = NULL; + + meta_topic (META_DEBUG_SM, "Saving session managed window %s, client ID '%s'\n", + window->desc, window->sm_client_id); + + fprintf (outfile, + " <window id=\"%s\" class=\"%s\" name=\"%s\" title=\"%s\" role=\"%s\" type=\"%s\" stacking=\"%d\">\n", + sm_client_id, + res_class ? res_class : "", + res_name ? res_name : "", + title ? title : "", + role ? role : "", + window_type_to_string (window->type), + stack_position); + + g_free (sm_client_id); + g_free (res_class); + g_free (res_name); + g_free (role); + g_free (title); + + /* Sticky */ + if (window->on_all_workspaces) + fputs (" <sticky/>\n", outfile); + + /* Minimized */ + if (window->minimized) + fputs (" <minimized/>\n", outfile); + + /* Maximized */ + if (META_WINDOW_MAXIMIZED (window)) + { + fprintf (outfile, + " <maximized saved_x=\"%d\" saved_y=\"%d\" saved_width=\"%d\" saved_height=\"%d\"/>\n", + window->saved_rect.x, + window->saved_rect.y, + window->saved_rect.width, + window->saved_rect.height); + } + + /* Workspaces we're on */ + { + int n; + n = meta_workspace_index (window->workspace); + fprintf (outfile, + " <workspace index=\"%d\"/>\n", n); + } + + /* Gravity */ + { + int x, y, w, h; + meta_window_get_geometry (window, &x, &y, &w, &h); + + fprintf (outfile, + " <geometry x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" gravity=\"%s\"/>\n", + x, y, w, h, + meta_gravity_to_string (window->size_hints.win_gravity)); + } + + fputs (" </window>\n", outfile); + } + else + { + meta_topic (META_DEBUG_SM, "Not saving window '%s', not session managed\n", + window->desc); + } + + tmp = tmp->next; + ++stack_position; + } + + g_slist_free (windows); + + fputs ("</marco_session>\n", outfile); + + out: + if (outfile) + { + /* FIXME need a dialog for this */ + if (ferror (outfile)) + { + meta_warning (_("Error writing session file '%s': %s\n"), + full_save_file (), g_strerror (errno)); + } + if (fclose (outfile)) + { + meta_warning (_("Error closing session file '%s': %s\n"), + full_save_file (), g_strerror (errno)); + } + } + + g_free (marco_dir); + g_free (session_dir); +} + +typedef enum +{ + WINDOW_TAG_NONE, + WINDOW_TAG_DESKTOP, + WINDOW_TAG_STICKY, + WINDOW_TAG_MINIMIZED, + WINDOW_TAG_MAXIMIZED, + WINDOW_TAG_GEOMETRY +} WindowTag; + +typedef struct +{ + MetaWindowSessionInfo *info; + char *previous_id; +} ParseData; + +static void session_info_free (MetaWindowSessionInfo *info); +static MetaWindowSessionInfo* session_info_new (void); + +static void start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); +static void end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); +static void text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error); + +static GMarkupParser marco_session_parser = { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +static GSList *window_info_list = NULL; + +static char* +load_state (const char *previous_save_file) +{ + GMarkupParseContext *context; + GError *error; + ParseData parse_data; + char *text; + gsize length; + char *session_file; + + session_file = g_strconcat (g_get_user_config_dir (), + G_DIR_SEPARATOR_S "marco" + G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S, + previous_save_file, + NULL); + + error = NULL; + if (!g_file_get_contents (session_file, + &text, + &length, + &error)) + { + char *canonical_session_file = session_file; + + /* Maybe they were doing it the old way, with ~/.marco */ + session_file = g_strconcat (g_get_home_dir (), + G_DIR_SEPARATOR_S ".marco" + G_DIR_SEPARATOR_S "sessions" + G_DIR_SEPARATOR_S, + previous_save_file, + NULL); + + if (!g_file_get_contents (session_file, + &text, + &length, + NULL)) + { + /* oh, just give up */ + + g_error_free (error); + g_free (session_file); + g_free (canonical_session_file); + return NULL; + } + + g_free (canonical_session_file); + } + + meta_topic (META_DEBUG_SM, "Parsing saved session file %s\n", session_file); + g_free (session_file); + session_file = NULL; + + parse_data.info = NULL; + parse_data.previous_id = NULL; + + context = g_markup_parse_context_new (&marco_session_parser, + 0, &parse_data, NULL); + + error = NULL; + if (!g_markup_parse_context_parse (context, + text, + length, + &error)) + goto error; + + + error = NULL; + if (!g_markup_parse_context_end_parse (context, &error)) + goto error; + + g_markup_parse_context_free (context); + + goto out; + + error: + + meta_warning (_("Failed to parse saved session file: %s\n"), + error->message); + g_error_free (error); + + if (parse_data.info) + session_info_free (parse_data.info); + + g_free (parse_data.previous_id); + parse_data.previous_id = NULL; + + out: + + g_free (text); + + return parse_data.previous_id; +} + +/* FIXME this isn't very robust against bogus session files */ +static void +start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseData *pd; + + pd = user_data; + + if (strcmp (element_name, "marco_session") == 0) + { + /* Get previous ID */ + int i; + + i = 0; + while (attribute_names[i]) + { + const char *name; + const char *val; + + name = attribute_names[i]; + val = attribute_values[i]; + + if (pd->previous_id) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<marco_session> attribute seen but we already have the session ID")); + return; + } + + if (strcmp (name, "id") == 0) + { + pd->previous_id = decode_text_from_utf8 (val); + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unknown attribute %s on <%s> element"), + name, "marco_session"); + return; + } + + ++i; + } + } + else if (strcmp (element_name, "window") == 0) + { + int i; + + if (pd->info) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("nested <window> tag")); + return; + } + + pd->info = session_info_new (); + + i = 0; + while (attribute_names[i]) + { + const char *name; + const char *val; + + name = attribute_names[i]; + val = attribute_values[i]; + + if (strcmp (name, "id") == 0) + { + if (*val) + pd->info->id = decode_text_from_utf8 (val); + } + else if (strcmp (name, "class") == 0) + { + if (*val) + pd->info->res_class = decode_text_from_utf8 (val); + } + else if (strcmp (name, "name") == 0) + { + if (*val) + pd->info->res_name = decode_text_from_utf8 (val); + } + else if (strcmp (name, "title") == 0) + { + if (*val) + pd->info->title = g_strdup (val); + } + else if (strcmp (name, "role") == 0) + { + if (*val) + pd->info->role = decode_text_from_utf8 (val); + } + else if (strcmp (name, "type") == 0) + { + if (*val) + pd->info->type = window_type_from_string (val); + } + else if (strcmp (name, "stacking") == 0) + { + if (*val) + { + pd->info->stack_position = atoi (val); + pd->info->stack_position_set = TRUE; + } + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unknown attribute %s on <%s> element"), + name, "window"); + session_info_free (pd->info); + pd->info = NULL; + return; + } + + ++i; + } + } + else if (strcmp (element_name, "workspace") == 0) + { + int i; + + i = 0; + while (attribute_names[i]) + { + const char *name; + + name = attribute_names[i]; + + if (strcmp (name, "index") == 0) + { + pd->info->workspace_indices = + g_slist_prepend (pd->info->workspace_indices, + GINT_TO_POINTER (atoi (attribute_values[i]))); + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unknown attribute %s on <%s> element"), + name, "window"); + session_info_free (pd->info); + pd->info = NULL; + return; + } + + ++i; + } + } + else if (strcmp (element_name, "sticky") == 0) + { + pd->info->on_all_workspaces = TRUE; + pd->info->on_all_workspaces_set = TRUE; + } + else if (strcmp (element_name, "minimized") == 0) + { + pd->info->minimized = TRUE; + pd->info->minimized_set = TRUE; + } + else if (strcmp (element_name, "maximized") == 0) + { + int i; + + i = 0; + pd->info->maximized = TRUE; + pd->info->maximized_set = TRUE; + while (attribute_names[i]) + { + const char *name; + const char *val; + + name = attribute_names[i]; + val = attribute_values[i]; + + if (strcmp (name, "saved_x") == 0) + { + if (*val) + { + pd->info->saved_rect.x = atoi (val); + pd->info->saved_rect_set = TRUE; + } + } + else if (strcmp (name, "saved_y") == 0) + { + if (*val) + { + pd->info->saved_rect.y = atoi (val); + pd->info->saved_rect_set = TRUE; + } + } + else if (strcmp (name, "saved_width") == 0) + { + if (*val) + { + pd->info->saved_rect.width = atoi (val); + pd->info->saved_rect_set = TRUE; + } + } + else if (strcmp (name, "saved_height") == 0) + { + if (*val) + { + pd->info->saved_rect.height = atoi (val); + pd->info->saved_rect_set = TRUE; + } + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unknown attribute %s on <%s> element"), + name, "maximized"); + return; + } + + ++i; + } + + if (pd->info->saved_rect_set) + meta_topic (META_DEBUG_SM, "Saved unmaximized size %d,%d %dx%d \n", + pd->info->saved_rect.x, + pd->info->saved_rect.y, + pd->info->saved_rect.width, + pd->info->saved_rect.height); + } + else if (strcmp (element_name, "geometry") == 0) + { + int i; + + pd->info->geometry_set = TRUE; + + i = 0; + while (attribute_names[i]) + { + const char *name; + const char *val; + + name = attribute_names[i]; + val = attribute_values[i]; + + if (strcmp (name, "x") == 0) + { + if (*val) + pd->info->rect.x = atoi (val); + } + else if (strcmp (name, "y") == 0) + { + if (*val) + pd->info->rect.y = atoi (val); + } + else if (strcmp (name, "width") == 0) + { + if (*val) + pd->info->rect.width = atoi (val); + } + else if (strcmp (name, "height") == 0) + { + if (*val) + pd->info->rect.height = atoi (val); + } + else if (strcmp (name, "gravity") == 0) + { + if (*val) + pd->info->gravity = window_gravity_from_string (val); + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unknown attribute %s on <%s> element"), + name, "geometry"); + return; + } + + ++i; + } + + meta_topic (META_DEBUG_SM, "Loaded geometry %d,%d %dx%d gravity %s\n", + pd->info->rect.x, + pd->info->rect.y, + pd->info->rect.width, + pd->info->rect.height, + meta_gravity_to_string (pd->info->gravity)); + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + _("Unknown element %s"), + element_name); + return; + } +} + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseData *pd; + + pd = user_data; + + if (strcmp (element_name, "window") == 0) + { + g_assert (pd->info); + + window_info_list = g_slist_prepend (window_info_list, + pd->info); + + meta_topic (META_DEBUG_SM, "Loaded window info from session with class: %s name: %s role: %s\n", + pd->info->res_class ? pd->info->res_class : "(none)", + pd->info->res_name ? pd->info->res_name : "(none)", + pd->info->role ? pd->info->role : "(none)"); + + pd->info = NULL; + } +} + +static void +text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + /* Right now we don't have any elements where we care about their + * content + */ +} + +static gboolean +both_null_or_matching (const char *a, + const char *b) +{ + if (a == NULL && b == NULL) + return TRUE; + else if (a && b && strcmp (a, b) == 0) + return TRUE; + else + return FALSE; +} + +static GSList* +get_possible_matches (MetaWindow *window) +{ + /* Get all windows with this client ID */ + GSList *retval; + GSList *tmp; + gboolean ignore_client_id; + + retval = NULL; + + ignore_client_id = g_getenv ("MARCO_DEBUG_SM") != NULL; + + tmp = window_info_list; + while (tmp != NULL) + { + MetaWindowSessionInfo *info; + + info = tmp->data; + + if ((ignore_client_id || + both_null_or_matching (info->id, window->sm_client_id)) && + both_null_or_matching (info->res_class, window->res_class) && + both_null_or_matching (info->res_name, window->res_name) && + both_null_or_matching (info->role, window->role)) + { + meta_topic (META_DEBUG_SM, "Window %s may match saved window with class: %s name: %s role: %s\n", + window->desc, + info->res_class ? info->res_class : "(none)", + info->res_name ? info->res_name : "(none)", + info->role ? info->role : "(none)"); + + retval = g_slist_prepend (retval, info); + } + else + { + if (meta_is_verbose ()) + { + if (!both_null_or_matching (info->id, window->sm_client_id)) + meta_topic (META_DEBUG_SM, "Window %s has SM client ID %s, saved state has %s, no match\n", + window->desc, + window->sm_client_id ? window->sm_client_id : "(none)", + info->id ? info->id : "(none)"); + else if (!both_null_or_matching (info->res_class, window->res_class)) + meta_topic (META_DEBUG_SM, "Window %s has class %s doesn't match saved class %s, no match\n", + window->desc, + window->res_class ? window->res_class : "(none)", + info->res_class ? info->res_class : "(none)"); + + else if (!both_null_or_matching (info->res_name, window->res_name)) + meta_topic (META_DEBUG_SM, "Window %s has name %s doesn't match saved name %s, no match\n", + window->desc, + window->res_name ? window->res_name : "(none)", + info->res_name ? info->res_name : "(none)"); + else if (!both_null_or_matching (info->role, window->role)) + meta_topic (META_DEBUG_SM, "Window %s has role %s doesn't match saved role %s, no match\n", + window->desc, + window->role ? window->role : "(none)", + info->role ? info->role : "(none)"); + else + meta_topic (META_DEBUG_SM, "???? should not happen - window %s doesn't match saved state %s for no good reason\n", + window->desc, info->id); + } + } + + tmp = tmp->next; + } + + return retval; +} + +static const MetaWindowSessionInfo* +find_best_match (GSList *infos, + MetaWindow *window) +{ + GSList *tmp; + const MetaWindowSessionInfo *matching_title; + const MetaWindowSessionInfo *matching_type; + + matching_title = NULL; + matching_type = NULL; + + tmp = infos; + while (tmp != NULL) + { + MetaWindowSessionInfo *info; + + info = tmp->data; + + if (matching_title == NULL && + both_null_or_matching (info->title, window->title)) + matching_title = info; + + if (matching_type == NULL && + info->type == window->type) + matching_type = info; + + tmp = tmp->next; + } + + /* Prefer same title, then same type of window, then + * just pick something. Eventually we could enhance this + * to e.g. break ties by geometry hint similarity, + * or other window features. + */ + + if (matching_title) + return matching_title; + else if (matching_type) + return matching_type; + else + return infos->data; +} + +const MetaWindowSessionInfo* +meta_window_lookup_saved_state (MetaWindow *window) +{ + GSList *possibles; + const MetaWindowSessionInfo *info; + + /* Window is not session managed. + * I haven't yet figured out how to deal with these + * in a way that doesn't cause broken side effects in + * situations other than on session restore. + */ + if (window->sm_client_id == NULL) + { + meta_topic (META_DEBUG_SM, + "Window %s is not session managed, not checking for saved state\n", + window->desc); + return NULL; + } + + possibles = get_possible_matches (window); + + if (possibles == NULL) + { + meta_topic (META_DEBUG_SM, "Window %s has no possible matches in the list of saved window states\n", + window->desc); + return NULL; + } + + info = find_best_match (possibles, window); + + g_slist_free (possibles); + + return info; +} + +void +meta_window_release_saved_state (const MetaWindowSessionInfo *info) +{ + /* We don't want to use the same saved state again for another + * window. + */ + window_info_list = g_slist_remove (window_info_list, info); + + session_info_free ((MetaWindowSessionInfo*) info); +} + +static void +session_info_free (MetaWindowSessionInfo *info) +{ + g_free (info->id); + g_free (info->res_class); + g_free (info->res_name); + g_free (info->title); + g_free (info->role); + + g_slist_free (info->workspace_indices); + + g_free (info); +} + +static MetaWindowSessionInfo* +session_info_new (void) +{ + MetaWindowSessionInfo *info; + + info = g_new0 (MetaWindowSessionInfo, 1); + + info->type = META_WINDOW_NORMAL; + info->gravity = NorthWestGravity; + + return info; +} + +static char* full_save_path = NULL; + +static void +regenerate_save_file (void) +{ + g_free (full_save_path); + + if (client_id) + full_save_path = g_strconcat (g_get_user_config_dir (), + G_DIR_SEPARATOR_S "marco" + G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S, + client_id, + ".ms", + NULL); + else + full_save_path = NULL; +} + +static const char* +full_save_file (void) +{ + return full_save_path; +} + +static int +windows_cmp_by_title (MetaWindow *a, + MetaWindow *b) +{ + return g_utf8_collate (a->title, b->title); +} + +static void +finish_interact (gboolean shutdown) +{ + if (current_state == STATE_DONE_WITH_INTERACT) /* paranoia */ + { + SmcInteractDone (session_connection, False /* don't cancel logout */); + + save_yourself_possibly_done (shutdown, TRUE); + } +} + +static void +dialog_closed (GPid pid, int status, gpointer user_data) +{ + gboolean shutdown = GPOINTER_TO_INT (user_data); + + if (WIFEXITED (status) && WEXITSTATUS (status) == 0) /* pressed "OK" */ + { + finish_interact (shutdown); + } +} + +static void +warn_about_lame_clients_and_finish_interact (gboolean shutdown) +{ + GSList *lame = NULL; + GSList *windows; + GSList *lame_details = NULL; + GSList *tmp; + GSList *columns = NULL; + GPid pid; + + windows = meta_display_list_windows (meta_get_display ()); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + /* only complain about normal windows, the others + * are kind of dumb to worry about + */ + if (window->sm_client_id == NULL && + window->type == META_WINDOW_NORMAL) + lame = g_slist_prepend (lame, window); + + tmp = tmp->next; + } + + g_slist_free (windows); + + if (lame == NULL) + { + /* No lame apps. */ + finish_interact (shutdown); + return; + } + + columns = g_slist_prepend (columns, "Window"); + columns = g_slist_prepend (columns, "Class"); + + lame = g_slist_sort (lame, (GCompareFunc) windows_cmp_by_title); + + tmp = lame; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + lame_details = g_slist_prepend (lame_details, + w->res_class ? w->res_class : ""); + lame_details = g_slist_prepend (lame_details, + w->title); + + tmp = tmp->next; + } + g_slist_free (lame); + + pid = meta_show_dialog("--list", + _("These windows do not support "save current setup" " + "and will have to be restarted manually next time " + "you log in."), + "240", + meta_screen_get_screen_number (meta_get_display()->active_screen), + NULL, NULL, + None, + columns, + lame_details); + + g_slist_free (lame_details); + + g_child_watch_add (pid, dialog_closed, GINT_TO_POINTER (shutdown)); +} + +#endif /* HAVE_SM */ diff --git a/src/core/session.h b/src/core/session.h new file mode 100644 index 00000000..62a9370d --- /dev/null +++ b/src/core/session.h @@ -0,0 +1,91 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file session.h Session management + * + * Maps windows to information about their placing and state on startup. + * This is window matching, which we have a policy of leaving in general + * to programs such as Devil's Pie, but the session manager specification + * requires us to do it here. + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * 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. + */ + +#ifndef META_SESSION_H +#define META_SESSION_H + +#include "window-private.h" + +typedef struct _MetaWindowSessionInfo MetaWindowSessionInfo; + +struct _MetaWindowSessionInfo +{ + /* Fields we use to match against */ + + char *id; + char *res_class; + char *res_name; + char *title; + char *role; + MetaWindowType type; + + /* Information we restore */ + + GSList *workspace_indices; + + int stack_position; + + /* width/height should be multiplied by resize inc and + * added to base size; position should be interpreted in + * light of gravity. This preserves semantics of the + * window size/pos, even if fonts/themes change, etc. + */ + int gravity; + MetaRectangle rect; + MetaRectangle saved_rect; + guint on_all_workspaces : 1; + guint minimized : 1; + guint maximized : 1; + + guint stack_position_set : 1; + guint geometry_set : 1; + guint on_all_workspaces_set : 1; + guint minimized_set : 1; + guint maximized_set : 1; + guint saved_rect_set : 1; +}; + +/* If lookup_saved_state returns something, it should be used, + * and then released when you're done with it. + */ +const MetaWindowSessionInfo* meta_window_lookup_saved_state (MetaWindow *window); +void meta_window_release_saved_state (const MetaWindowSessionInfo *info); + +void meta_session_init (const char *client_id, + const char *save_file); + + +void meta_session_shutdown (void); + +#endif + + + + diff --git a/src/core/stack.c b/src/core/stack.c new file mode 100644 index 00000000..2e108d20 --- /dev/null +++ b/src/core/stack.c @@ -0,0 +1,1661 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file stack.c Which windows cover which other windows + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2004 Rob Adams + * Copyright (C) 2004, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "stack.h" +#include "window-private.h" +#include "errors.h" +#include "frame-private.h" +#include "group.h" +#include "prefs.h" +#include "workspace.h" + +#include <X11/Xatom.h> + +#define WINDOW_HAS_TRANSIENT_TYPE(w) \ + (w->type == META_WINDOW_DIALOG || \ + w->type == META_WINDOW_MODAL_DIALOG || \ + w->type == META_WINDOW_TOOLBAR || \ + w->type == META_WINDOW_MENU || \ + w->type == META_WINDOW_UTILITY) + +#define WINDOW_TRANSIENT_FOR_WHOLE_GROUP(w) \ + ((w->xtransient_for == None || \ + w->transient_parent_is_root_window) && \ + WINDOW_HAS_TRANSIENT_TYPE (w)) + +#define WINDOW_IN_STACK(w) (w->stack_position >= 0) + +static void stack_sync_to_server (MetaStack *stack); +static void meta_window_set_stack_position_no_sync (MetaWindow *window, + int position); +static void stack_do_window_deletions (MetaStack *stack); +static void stack_do_window_additions (MetaStack *stack); +static void stack_do_relayer (MetaStack *stack); +static void stack_do_constrain (MetaStack *stack); +static void stack_do_resort (MetaStack *stack); + +static void stack_ensure_sorted (MetaStack *stack); + +MetaStack* +meta_stack_new (MetaScreen *screen) +{ + MetaStack *stack; + + stack = g_new (MetaStack, 1); + + stack->screen = screen; + stack->windows = g_array_new (FALSE, FALSE, sizeof (Window)); + + stack->sorted = NULL; + stack->added = NULL; + stack->removed = NULL; + + stack->freeze_count = 0; + stack->last_root_children_stacked = NULL; + + stack->n_positions = 0; + + stack->need_resort = FALSE; + stack->need_relayer = FALSE; + stack->need_constrain = FALSE; + + return stack; +} + +void +meta_stack_free (MetaStack *stack) +{ + g_array_free (stack->windows, TRUE); + + g_list_free (stack->sorted); + g_list_free (stack->added); + g_list_free (stack->removed); + + if (stack->last_root_children_stacked) + g_array_free (stack->last_root_children_stacked, TRUE); + + g_free (stack); +} + +void +meta_stack_add (MetaStack *stack, + MetaWindow *window) +{ + meta_topic (META_DEBUG_STACK, "Adding window %s to the stack\n", window->desc); + + if (window->stack_position >= 0) + meta_bug ("Window %s had stack position already\n", window->desc); + + stack->added = g_list_prepend (stack->added, window); + + window->stack_position = stack->n_positions; + stack->n_positions += 1; + meta_topic (META_DEBUG_STACK, + "Window %s has stack_position initialized to %d\n", + window->desc, window->stack_position); + + stack_sync_to_server (stack); +} + +void +meta_stack_remove (MetaStack *stack, + MetaWindow *window) +{ + meta_topic (META_DEBUG_STACK, "Removing window %s from the stack\n", window->desc); + + if (window->stack_position < 0) + meta_bug ("Window %s removed from stack but had no stack position\n", + window->desc); + + /* Set window to top position, so removing it will not leave gaps + * in the set of positions + */ + meta_window_set_stack_position_no_sync (window, + stack->n_positions - 1); + window->stack_position = -1; + stack->n_positions -= 1; + + /* We don't know if it's been moved from "added" to "stack" yet */ + stack->added = g_list_remove (stack->added, window); + stack->sorted = g_list_remove (stack->sorted, window); + + /* Remember the window ID to remove it from the stack array. + * The macro is safe to use: Window is guaranteed to be 32 bits, and + * GUINT_TO_POINTER says it only works on 32 bits. + */ + stack->removed = g_list_prepend (stack->removed, + GUINT_TO_POINTER (window->xwindow)); + if (window->frame) + stack->removed = g_list_prepend (stack->removed, + GUINT_TO_POINTER (window->frame->xwindow)); + + stack_sync_to_server (stack); +} + +void +meta_stack_update_layer (MetaStack *stack, + MetaWindow *window) +{ + stack->need_relayer = TRUE; + + stack_sync_to_server (stack); +} + +void +meta_stack_update_transient (MetaStack *stack, + MetaWindow *window) +{ + stack->need_constrain = TRUE; + + stack_sync_to_server (stack); +} + +/* raise/lower within a layer */ +void +meta_stack_raise (MetaStack *stack, + MetaWindow *window) +{ + meta_window_set_stack_position_no_sync (window, + stack->n_positions - 1); + + stack_sync_to_server (stack); +} + +void +meta_stack_lower (MetaStack *stack, + MetaWindow *window) +{ + meta_window_set_stack_position_no_sync (window, 0); + + stack_sync_to_server (stack); +} + +void +meta_stack_freeze (MetaStack *stack) +{ + stack->freeze_count += 1; +} + +void +meta_stack_thaw (MetaStack *stack) +{ + g_return_if_fail (stack->freeze_count > 0); + + stack->freeze_count -= 1; + stack_sync_to_server (stack); +} + +static gboolean +is_focused_foreach (MetaWindow *window, + void *data) +{ + if (window == window->display->expected_focus_window) + { + *((gboolean*) data) = TRUE; + return FALSE; + } + return TRUE; +} + +static gboolean +windows_on_different_xinerama (MetaWindow *a, + MetaWindow *b) +{ + if (a->screen != b->screen) + return TRUE; + + return meta_screen_get_xinerama_for_window (a->screen, a) != + meta_screen_get_xinerama_for_window (b->screen, b); +} + +/* Get layer ignoring any transient or group relationships */ +static MetaStackLayer +get_standalone_layer (MetaWindow *window) +{ + MetaStackLayer layer; + gboolean focused_transient = FALSE; + + switch (window->type) + { + case META_WINDOW_DESKTOP: + layer = META_LAYER_DESKTOP; + break; + + case META_WINDOW_DOCK: + /* still experimenting here */ + if (window->wm_state_below) + layer = META_LAYER_BOTTOM; + else + layer = META_LAYER_DOCK; + break; + + default: + meta_window_foreach_transient (window, + is_focused_foreach, + &focused_transient); + + if (window->wm_state_below) + layer = META_LAYER_BOTTOM; + else if (window->fullscreen && + (focused_transient || + window == window->display->expected_focus_window || + window->display->expected_focus_window == NULL || + (window->display->expected_focus_window != NULL && + windows_on_different_xinerama (window, + window->display->expected_focus_window)))) + layer = META_LAYER_FULLSCREEN; + else if (window->wm_state_above) + layer = META_LAYER_TOP; + else + layer = META_LAYER_NORMAL; + break; + } + + return layer; +} + +/* Note that this function can never use window->layer only + * get_standalone_layer, or we'd have issues. + */ +static MetaStackLayer +get_maximum_layer_in_group (MetaWindow *window) +{ + GSList *members; + MetaGroup *group; + GSList *tmp; + MetaStackLayer max; + MetaStackLayer layer; + + max = META_LAYER_DESKTOP; + + group = meta_window_get_group (window); + + if (group != NULL) + members = meta_group_list_windows (group); + else + members = NULL; + + tmp = members; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + layer = get_standalone_layer (w); + if (layer > max) + max = layer; + + tmp = tmp->next; + } + + g_slist_free (members); + + return max; +} + +static void +compute_layer (MetaWindow *window) +{ + window->layer = get_standalone_layer (window); + + /* We can only do promotion-due-to-group for dialogs and other + * transients, or weird stuff happens like the desktop window and + * caja windows getting in the same layer, or all mate-terminal + * windows getting in fullscreen layer if any terminal is + * fullscreen. + */ + if (WINDOW_HAS_TRANSIENT_TYPE(window) && + (window->xtransient_for == None || + window->transient_parent_is_root_window)) + { + /* We only do the group thing if the dialog is NOT transient for + * a particular window. Imagine a group with a normal window, a dock, + * and a dialog transient for the normal window; you don't want the dialog + * above the dock if it wouldn't normally be. + */ + + MetaStackLayer group_max; + + group_max = get_maximum_layer_in_group (window); + + if (group_max > window->layer) + { + meta_topic (META_DEBUG_STACK, + "Promoting window %s from layer %u to %u due to group membership\n", + window->desc, window->layer, group_max); + window->layer = group_max; + } + } + + meta_topic (META_DEBUG_STACK, "Window %s on layer %u type = %u has_focus = %d\n", + window->desc, window->layer, + window->type, window->has_focus); +} + +/* Front of the layer list is the topmost window, + * so the lower stack position is later in the list + */ +static int +compare_window_position (void *a, + void *b) +{ + MetaWindow *window_a = a; + MetaWindow *window_b = b; + + /* Go by layer, then stack_position */ + if (window_a->layer < window_b->layer) + return 1; /* move window_a later in list */ + else if (window_a->layer > window_b->layer) + return -1; + else if (window_a->stack_position < window_b->stack_position) + return 1; /* move window_a later in list */ + else if (window_a->stack_position > window_b->stack_position) + return -1; + else + return 0; /* not reached */ +} + +/* + * Stacking constraints + * + * Assume constraints of the form "AB" meaning "window A must be + * below window B" + * + * If we have windows stacked from bottom to top + * "ABC" then raise A we get "BCA". Say C is + * transient for B is transient for A. So + * we have constraints AB and BC. + * + * After raising A, we need to reapply the constraints. + * If we do this by raising one window at a time - + * + * start: BCA + * apply AB: CAB + * apply BC: ABC + * + * but apply constraints in the wrong order and it breaks: + * + * start: BCA + * apply BC: BCA + * apply AB: CAB + * + * We make a directed graph of the constraints by linking + * from "above windows" to "below windows as follows: + * + * AB -> BC -> CD + * \ + * CE + * + * If we then walk that graph and apply the constraints in the order + * that they appear, we will apply them correctly. Note that the + * graph MAY have cycles, so we have to guard against that. + * + */ + +typedef struct Constraint Constraint; + +struct Constraint +{ + MetaWindow *above; + MetaWindow *below; + + /* used to keep the constraint in the + * list of constraints for window "below" + */ + Constraint *next; + + /* used to create the graph. */ + GSList *next_nodes; + + /* constraint has been applied, used + * to detect cycles. + */ + unsigned int applied : 1; + + /* constraint has a previous node in the graph, + * used to find places to start in the graph. + * (I think this also has the side effect + * of preventing cycles, since cycles will + * have no starting point - so maybe + * the "applied" flag isn't needed.) + */ + unsigned int has_prev : 1; +}; + +/* We index the array of constraints by window + * stack positions, just because the stack + * positions are a convenient index. + */ +static void +add_constraint (Constraint **constraints, + MetaWindow *above, + MetaWindow *below) +{ + Constraint *c; + + g_assert (above->screen == below->screen); + + /* check if constraint is a duplicate */ + c = constraints[below->stack_position]; + while (c != NULL) + { + if (c->above == above) + return; + c = c->next; + } + + /* if not, add the constraint */ + c = g_new (Constraint, 1); + c->above = above; + c->below = below; + c->next = constraints[below->stack_position]; + c->next_nodes = NULL; + c->applied = FALSE; + c->has_prev = FALSE; + + constraints[below->stack_position] = c; +} + +static void +create_constraints (Constraint **constraints, + GList *windows) +{ + GList *tmp; + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (!WINDOW_IN_STACK (w)) + { + meta_topic (META_DEBUG_STACK, "Window %s not in the stack, not constraining it\n", + w->desc); + tmp = tmp->next; + continue; + } + + if (WINDOW_TRANSIENT_FOR_WHOLE_GROUP (w)) + { + GSList *group_windows; + GSList *tmp2; + MetaGroup *group; + + group = meta_window_get_group (w); + + if (group != NULL) + group_windows = meta_group_list_windows (group); + else + group_windows = NULL; + + tmp2 = group_windows; + + while (tmp2 != NULL) + { + MetaWindow *group_window = tmp2->data; + + if (!WINDOW_IN_STACK (group_window) || + w->screen != group_window->screen) + { + tmp2 = tmp2->next; + continue; + } + +#if 0 + /* old way of doing it */ + if (!(meta_window_is_ancestor_of_transient (w, group_window)) && + !WINDOW_TRANSIENT_FOR_WHOLE_GROUP (group_window)) /* note */;/*note*/ +#else + /* better way I think, so transient-for-group are constrained + * only above non-transient-type windows in their group + */ + if (!WINDOW_HAS_TRANSIENT_TYPE (group_window)) +#endif + { + meta_topic (META_DEBUG_STACK, "Constraining %s above %s as it's transient for its group\n", + w->desc, group_window->desc); + add_constraint (constraints, w, group_window); + } + + tmp2 = tmp2->next; + } + + g_slist_free (group_windows); + } + else if (w->xtransient_for != None && + !w->transient_parent_is_root_window) + { + MetaWindow *parent; + + parent = + meta_display_lookup_x_window (w->display, w->xtransient_for); + + if (parent && WINDOW_IN_STACK (parent) && + parent->screen == w->screen) + { + meta_topic (META_DEBUG_STACK, "Constraining %s above %s due to transiency\n", + w->desc, parent->desc); + add_constraint (constraints, w, parent); + } + } + + tmp = tmp->next; + } +} + +static void +graph_constraints (Constraint **constraints, + int n_constraints) +{ + int i; + + i = 0; + while (i < n_constraints) + { + Constraint *c; + + /* If we have "A below B" and "B below C" then AB -> BC so we + * add BC to next_nodes in AB. + */ + + c = constraints[i]; + while (c != NULL) + { + Constraint *n; + + g_assert (c->below->stack_position == i); + + /* Constraints where ->above is below are our + * next_nodes and we are their previous + */ + n = constraints[c->above->stack_position]; + while (n != NULL) + { + c->next_nodes = g_slist_prepend (c->next_nodes, + n); + /* c is a previous node of n */ + n->has_prev = TRUE; + + n = n->next; + } + + c = c->next; + } + + ++i; + } +} + +static void +free_constraints (Constraint **constraints, + int n_constraints) +{ + int i; + + i = 0; + while (i < n_constraints) + { + Constraint *c; + + c = constraints[i]; + while (c != NULL) + { + Constraint *next = c->next; + + g_slist_free (c->next_nodes); + + g_free (c); + + c = next; + } + + ++i; + } +} + +static void +ensure_above (MetaWindow *above, + MetaWindow *below) +{ + if (WINDOW_HAS_TRANSIENT_TYPE(above) && + above->layer < below->layer) + { + meta_topic (META_DEBUG_STACK, + "Promoting window %s from layer %u to %u due to contraint\n", + above->desc, above->layer, below->layer); + above->layer = below->layer; + } + + if (above->stack_position < below->stack_position) + { + /* move above to below->stack_position bumping below down the stack */ + meta_window_set_stack_position_no_sync (above, below->stack_position); + g_assert (below->stack_position + 1 == above->stack_position); + } + meta_topic (META_DEBUG_STACK, "%s above at %d > %s below at %d\n", + above->desc, above->stack_position, + below->desc, below->stack_position); +} + +static void +traverse_constraint (Constraint *c) +{ + GSList *tmp; + + if (c->applied) + return; + + ensure_above (c->above, c->below); + c->applied = TRUE; + + tmp = c->next_nodes; + while (tmp != NULL) + { + traverse_constraint (tmp->data); + + tmp = tmp->next; + } +} + +static void +apply_constraints (Constraint **constraints, + int n_constraints) +{ + GSList *heads; + GSList *tmp; + int i; + + /* List all heads in an ordered constraint chain */ + heads = NULL; + i = 0; + while (i < n_constraints) + { + Constraint *c; + + c = constraints[i]; + while (c != NULL) + { + if (!c->has_prev) + heads = g_slist_prepend (heads, c); + + c = c->next; + } + + ++i; + } + + /* Now traverse the chain and apply constraints */ + tmp = heads; + while (tmp != NULL) + { + Constraint *c = tmp->data; + + traverse_constraint (c); + + tmp = tmp->next; + } + + g_slist_free (heads); +} + +/** + * Go through "deleted" and take the matching windows + * out of "windows". + */ +static void +stack_do_window_deletions (MetaStack *stack) +{ + /* Do removals before adds, with paranoid idea that we might re-add + * the same window IDs. + */ + GList *tmp; + int i; + + tmp = stack->removed; + while (tmp != NULL) + { + Window xwindow; + xwindow = GPOINTER_TO_UINT (tmp->data); + + /* We go from the end figuring removals are more + * likely to be recent. + */ + i = stack->windows->len; + while (i > 0) + { + --i; + + /* there's no guarantee we'll actually find windows to + * remove, e.g. the same xwindow could have been + * added/removed before we ever synced, and we put + * both the window->xwindow and window->frame->xwindow + * in the removal list. + */ + if (xwindow == g_array_index (stack->windows, Window, i)) + { + g_array_remove_index (stack->windows, i); + goto next; + } + } + + next: + tmp = tmp->next; + } + + g_list_free (stack->removed); + stack->removed = NULL; +} + +static void +stack_do_window_additions (MetaStack *stack) +{ + GList *tmp; + gint i, n_added; + + n_added = g_list_length (stack->added); + if (n_added > 0) + { + Window *end; + int old_size; + + meta_topic (META_DEBUG_STACK, + "Adding %d windows to sorted list\n", + n_added); + + old_size = stack->windows->len; + g_array_set_size (stack->windows, old_size + n_added); + + end = &g_array_index (stack->windows, Window, old_size); + + /* stack->added has the most recent additions at the + * front of the list, so we need to reverse it + */ + stack->added = g_list_reverse (stack->added); + + i = 0; + tmp = stack->added; + while (tmp != NULL) + { + MetaWindow *w; + + w = tmp->data; + + end[i] = w->xwindow; + + /* add to the main list */ + stack->sorted = g_list_prepend (stack->sorted, w); + + ++i; + tmp = tmp->next; + } + + stack->need_resort = TRUE; /* may not be needed as we add to top */ + stack->need_constrain = TRUE; + stack->need_relayer = TRUE; + } + + g_list_free (stack->added); + stack->added = NULL; +} + +/** + * Update the layers that windows are in + */ +static void +stack_do_relayer (MetaStack *stack) +{ + GList *tmp; + + if (!stack->need_relayer) + return; + + meta_topic (META_DEBUG_STACK, + "Recomputing layers\n"); + + tmp = stack->sorted; + + while (tmp != NULL) + { + MetaWindow *w; + MetaStackLayer old_layer; + + w = tmp->data; + old_layer = w->layer; + + compute_layer (w); + + if (w->layer != old_layer) + { + meta_topic (META_DEBUG_STACK, + "Window %s moved from layer %u to %u\n", + w->desc, old_layer, w->layer); + + stack->need_resort = TRUE; + stack->need_constrain = TRUE; + /* don't need to constrain as constraining + * purely operates in terms of stack_position + * not layer + */ + } + + tmp = tmp->next; + } + + stack->need_relayer = FALSE; +} + +/** + * Update stack_position and layer to reflect transiency + * constraints + */ +static void +stack_do_constrain (MetaStack *stack) +{ + Constraint **constraints; + + /* It'd be nice if this were all faster, probably */ + + if (!stack->need_constrain) + return; + + meta_topic (META_DEBUG_STACK, + "Reapplying constraints\n"); + + constraints = g_new0 (Constraint*, + stack->n_positions); + + create_constraints (constraints, stack->sorted); + + graph_constraints (constraints, stack->n_positions); + + apply_constraints (constraints, stack->n_positions); + + free_constraints (constraints, stack->n_positions); + g_free (constraints); + + stack->need_constrain = FALSE; +} + +/** + * Sort stack->sorted with layers having priority over stack_position. + */ +static void +stack_do_resort (MetaStack *stack) +{ + if (!stack->need_resort) + return; + + meta_topic (META_DEBUG_STACK, + "Sorting stack list\n"); + + stack->sorted = g_list_sort (stack->sorted, + (GCompareFunc) compare_window_position); + + stack->need_resort = FALSE; +} + +/** + * Puts the stack into canonical form. + * + * Honour the removed and added lists of the stack, and then recalculate + * all the layers (if the flag is set), re-run all the constraint calculations + * (if the flag is set), and finally re-sort the stack (if the flag is set, + * and if it wasn't already it might have become so during all the previous + * activity). + */ +static void +stack_ensure_sorted (MetaStack *stack) +{ + stack_do_window_deletions (stack); + stack_do_window_additions (stack); + stack_do_relayer (stack); + stack_do_constrain (stack); + stack_do_resort (stack); +} + +/** + * This function is used to avoid raising a window above popup + * menus and other such things. + * + * FIXME This is sort of an expensive function, should probably + * do something to avoid it. One approach would be to reverse + * the stacking algorithm to work by placing each window above + * the others, and start by lowering a window to the bottom + * (instead of the current way, which works by placing each + * window below another and starting with a raise) + */ +static void +raise_window_relative_to_managed_windows (MetaScreen *screen, + Window xwindow) +{ + + Window ignored1, ignored2; + Window *children; + unsigned int n_children; + int i; + + /* Normally XQueryTree() means "must grab server" but here + * we don't, since we know we won't manage any new windows + * or restack any windows before using the XQueryTree results. + */ + + meta_error_trap_push_with_return (screen->display); + + XQueryTree (screen->display->xdisplay, + screen->xroot, + &ignored1, &ignored2, &children, &n_children); + + if (meta_error_trap_pop_with_return (screen->display, TRUE) != Success) + { + meta_topic (META_DEBUG_STACK, + "Error querying root children to raise window 0x%lx\n", + xwindow); + return; + } + + /* Children are in order from bottom to top. We want to + * find the topmost managed child, then configure + * our window to be above it. + */ + i = n_children - 1; + while (i >= 0) + { + if (children[i] == xwindow) + { + /* Do nothing. This means we're already the topmost managed + * window, but it DOES NOT mean we are already just above + * the topmost managed window. This is important because if + * an override redirect window is up, and we map a new + * managed window, the new window is probably above the old + * popup by default, and we want to push it below that + * popup. So keep looking for a sibling managed window + * to be moved below. + */ + } + else if (meta_display_lookup_x_window (screen->display, + children[i]) != NULL) + { + XWindowChanges changes; + + /* children[i] is the topmost managed child */ + meta_topic (META_DEBUG_STACK, + "Moving 0x%lx above topmost managed child window 0x%lx\n", + xwindow, children[i]); + + changes.sibling = children[i]; + changes.stack_mode = Above; + + meta_error_trap_push (screen->display); + XConfigureWindow (screen->display->xdisplay, + xwindow, + CWSibling | CWStackMode, + &changes); + meta_error_trap_pop (screen->display, FALSE); + + break; + } + + --i; + } + + if (i < 0) + { + /* No sibling to use, just lower ourselves to the bottom + * to be sure we're below any override redirect windows. + */ + meta_error_trap_push (screen->display); + XLowerWindow (screen->display->xdisplay, + xwindow); + meta_error_trap_pop (screen->display, FALSE); + } + + if (children) + XFree (children); +} + +/** + * Order the windows on the X server to be the same as in our structure. + * We do this using XRestackWindows if we don't know the previous order, + * or XConfigureWindow on a few particular windows if we do and can figure + * out the minimum set of changes. After that, we set __NET_CLIENT_LIST + * and __NET_CLIENT_LIST_STACKING. + */ +static void +stack_sync_to_server (MetaStack *stack) +{ + GArray *stacked; + GArray *root_children_stacked; + GList *tmp; + + /* Bail out if frozen */ + if (stack->freeze_count > 0) + return; + + meta_topic (META_DEBUG_STACK, "Syncing window stack to server\n"); + + stack_ensure_sorted (stack); + + /* Create stacked xwindow arrays. + * Painfully, "stacked" is in bottom-to-top order for the + * _NET hints, and "root_children_stacked" is in top-to-bottom + * order for XRestackWindows() + */ + stacked = g_array_new (FALSE, FALSE, sizeof (Window)); + root_children_stacked = g_array_new (FALSE, FALSE, sizeof (Window)); + + meta_topic (META_DEBUG_STACK, "Top to bottom: "); + meta_push_no_msg_prefix (); + + tmp = stack->sorted; + while (tmp != NULL) + { + MetaWindow *w; + + w = tmp->data; + + /* remember, stacked is in reverse order (bottom to top) */ + g_array_prepend_val (stacked, w->xwindow); + + /* build XRestackWindows() array from top to bottom */ + if (w->frame) + g_array_append_val (root_children_stacked, w->frame->xwindow); + else + g_array_append_val (root_children_stacked, w->xwindow); + + meta_topic (META_DEBUG_STACK, "%u:%d - %s ", w->layer, w->stack_position, w->desc); + + tmp = tmp->next; + } + + meta_topic (META_DEBUG_STACK, "\n"); + meta_pop_no_msg_prefix (); + + /* All windows should be in some stacking order */ + if (stacked->len != stack->windows->len) + meta_bug ("%u windows stacked, %u windows exist in stack\n", + stacked->len, stack->windows->len); + + /* Sync to server */ + + meta_topic (META_DEBUG_STACK, "Restacking %u windows\n", + root_children_stacked->len); + + meta_error_trap_push (stack->screen->display); + + if (stack->last_root_children_stacked == NULL) + { + /* Just impose our stack, we don't know the previous state. + * This involves a ton of circulate requests and may flicker. + */ + meta_topic (META_DEBUG_STACK, "Don't know last stack state, restacking everything\n"); + + if (root_children_stacked->len > 0) + XRestackWindows (stack->screen->display->xdisplay, + (Window *) root_children_stacked->data, + root_children_stacked->len); + } + else if (root_children_stacked->len > 0) + { + /* Try to do minimal window moves to get the stack in order */ + /* A point of note: these arrays include frames not client windows, + * so if a client window has changed frame since last_root_children_stacked + * was saved, then we may have inefficiency, but I don't think things + * break... + */ + const Window *old_stack = (Window *) stack->last_root_children_stacked->data; + const Window *new_stack = (Window *) root_children_stacked->data; + const int old_len = stack->last_root_children_stacked->len; + const int new_len = root_children_stacked->len; + const Window *oldp = old_stack; + const Window *newp = new_stack; + const Window *old_end = old_stack + old_len; + const Window *new_end = new_stack + new_len; + Window last_window = None; + + while (oldp != old_end && + newp != new_end) + { + if (*oldp == *newp) + { + /* Stacks are the same here, move on */ + ++oldp; + last_window = *newp; + ++newp; + } + else if (meta_display_lookup_x_window (stack->screen->display, + *oldp) == NULL) + { + /* *oldp is no longer known to us (probably destroyed), + * so we can just skip it + */ + ++oldp; + } + else + { + /* Move *newp below last_window */ + if (last_window == None) + { + meta_topic (META_DEBUG_STACK, "Using window 0x%lx as topmost (but leaving it in-place)\n", *newp); + + raise_window_relative_to_managed_windows (stack->screen, + *newp); + } + else + { + /* This means that if last_window is dead, but not + * *newp, then we fail to restack *newp; but on + * unmanaging last_window, we'll fix it up. + */ + + XWindowChanges changes; + + changes.sibling = last_window; + changes.stack_mode = Below; + + meta_topic (META_DEBUG_STACK, "Placing window 0x%lx below 0x%lx\n", + *newp, last_window); + + XConfigureWindow (stack->screen->display->xdisplay, + *newp, + CWSibling | CWStackMode, + &changes); + } + + last_window = *newp; + ++newp; + } + } + + if (newp != new_end) + { + /* Restack remaining windows */ + meta_topic (META_DEBUG_STACK, "Restacking remaining %d windows\n", + (int) (new_end - newp)); + /* We need to include an already-stacked window + * in the restack call, so we get in the proper position + * with respect to it. + */ + if (newp != new_stack) + --newp; + XRestackWindows (stack->screen->display->xdisplay, + (Window *) newp, new_end - newp); + } + } + + meta_error_trap_pop (stack->screen->display, FALSE); + /* on error, a window was destroyed; it should eventually + * get removed from the stacking list when we unmanage it + * and we'll fix stacking at that time. + */ + + /* Sync _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING */ + + XChangeProperty (stack->screen->display->xdisplay, + stack->screen->xroot, + stack->screen->display->atom__NET_CLIENT_LIST, + XA_WINDOW, + 32, PropModeReplace, + (unsigned char *)stack->windows->data, + stack->windows->len); + XChangeProperty (stack->screen->display->xdisplay, + stack->screen->xroot, + stack->screen->display->atom__NET_CLIENT_LIST_STACKING, + XA_WINDOW, + 32, PropModeReplace, + (unsigned char *)stacked->data, + stacked->len); + + g_array_free (stacked, TRUE); + + if (stack->last_root_children_stacked) + g_array_free (stack->last_root_children_stacked, TRUE); + stack->last_root_children_stacked = root_children_stacked; + + /* That was scary... */ +} + +MetaWindow* +meta_stack_get_top (MetaStack *stack) +{ + stack_ensure_sorted (stack); + + if (stack->sorted) + return stack->sorted->data; + else + return NULL; +} + +MetaWindow* +meta_stack_get_bottom (MetaStack *stack) +{ + GList *link; + + stack_ensure_sorted (stack); + + link = g_list_last (stack->sorted); + if (link != NULL) + return link->data; + else + return NULL; +} + +MetaWindow* +meta_stack_get_above (MetaStack *stack, + MetaWindow *window, + gboolean only_within_layer) +{ + GList *link; + MetaWindow *above; + + stack_ensure_sorted (stack); + + link = g_list_find (stack->sorted, window); + if (link == NULL) + return NULL; + if (link->prev == NULL) + return NULL; + + above = link->prev->data; + + if (only_within_layer && + above->layer != window->layer) + return NULL; + else + return above; +} + +MetaWindow* +meta_stack_get_below (MetaStack *stack, + MetaWindow *window, + gboolean only_within_layer) +{ + GList *link; + MetaWindow *below; + + stack_ensure_sorted (stack); + + link = g_list_find (stack->sorted, window); + + if (link == NULL) + return NULL; + if (link->next == NULL) + return NULL; + + below = link->next->data; + + if (only_within_layer && + below->layer != window->layer) + return NULL; + else + return below; +} + +static gboolean +window_contains_point (MetaWindow *window, + int root_x, + int root_y) +{ + MetaRectangle rect; + + meta_window_get_outer_rect (window, &rect); + + return POINT_IN_RECT (root_x, root_y, rect); +} + +static MetaWindow* +get_default_focus_window (MetaStack *stack, + MetaWorkspace *workspace, + MetaWindow *not_this_one, + gboolean must_be_at_point, + int root_x, + int root_y) +{ + /* Find the topmost, focusable, mapped, window. + * not_this_one is being unfocused or going away, so exclude it. + * Also, prefer to focus transient parent of not_this_one, + * or top window in same group as not_this_one. + */ + + MetaWindow *topmost_dock; + MetaWindow *transient_parent; + MetaWindow *topmost_in_group; + MetaWindow *topmost_overall; + MetaGroup *not_this_one_group; + GList *link; + + topmost_dock = NULL; + transient_parent = NULL; + topmost_in_group = NULL; + topmost_overall = NULL; + if (not_this_one) + not_this_one_group = meta_window_get_group (not_this_one); + else + not_this_one_group = NULL; + + stack_ensure_sorted (stack); + + /* top of this layer is at the front of the list */ + link = stack->sorted; + + while (link) + { + MetaWindow *window = link->data; + + if (window && + window != not_this_one && + (window->unmaps_pending == 0) && + !window->minimized && + (window->input || window->take_focus) && + (workspace == NULL || + meta_window_located_on_workspace (window, workspace))) + { + if (topmost_dock == NULL && + window->type == META_WINDOW_DOCK) + topmost_dock = window; + + if (not_this_one != NULL) + { + if (transient_parent == NULL && + not_this_one->xtransient_for != None && + not_this_one->xtransient_for == window->xwindow && + (!must_be_at_point || + window_contains_point (window, root_x, root_y))) + transient_parent = window; + + if (topmost_in_group == NULL && + not_this_one_group != NULL && + not_this_one_group == meta_window_get_group (window) && + (!must_be_at_point || + window_contains_point (window, root_x, root_y))) + topmost_in_group = window; + } + + /* Note that DESKTOP windows can be topmost_overall so + * we prefer focusing desktop or other windows over + * focusing dock, even though docks are stacked higher. + */ + if (topmost_overall == NULL && + window->type != META_WINDOW_DOCK && + (!must_be_at_point || + window_contains_point (window, root_x, root_y))) + topmost_overall = window; + + /* We could try to bail out early here for efficiency in + * some cases, but it's just not worth the code. + */ + } + + link = link->next; + } + + if (transient_parent) + return transient_parent; + else if (topmost_in_group) + return topmost_in_group; + else if (topmost_overall) + return topmost_overall; + else + return topmost_dock; +} + +MetaWindow* +meta_stack_get_default_focus_window_at_point (MetaStack *stack, + MetaWorkspace *workspace, + MetaWindow *not_this_one, + int root_x, + int root_y) +{ + return get_default_focus_window (stack, workspace, not_this_one, + TRUE, root_x, root_y); +} + +MetaWindow* +meta_stack_get_default_focus_window (MetaStack *stack, + MetaWorkspace *workspace, + MetaWindow *not_this_one) +{ + return get_default_focus_window (stack, workspace, not_this_one, + FALSE, 0, 0); +} + +GList* +meta_stack_list_windows (MetaStack *stack, + MetaWorkspace *workspace) +{ + GList *workspace_windows = NULL; + GList *link; + + stack_ensure_sorted (stack); /* do adds/removes */ + + link = stack->sorted; + + while (link) + { + MetaWindow *window = link->data; + + if (window && + (workspace == NULL || meta_window_located_on_workspace (window, workspace))) + { + workspace_windows = g_list_prepend (workspace_windows, + window); + } + + link = link->next; + } + + return workspace_windows; +} + +int +meta_stack_windows_cmp (MetaStack *stack, + MetaWindow *window_a, + MetaWindow *window_b) +{ + g_return_val_if_fail (window_a->screen == window_b->screen, 0); + + /* -1 means a below b */ + + stack_ensure_sorted (stack); /* update constraints, layers */ + + if (window_a->layer < window_b->layer) + return -1; + else if (window_a->layer > window_b->layer) + return 1; + else if (window_a->stack_position < window_b->stack_position) + return -1; + else if (window_a->stack_position > window_b->stack_position) + return 1; + else + return 0; /* not reached */ +} + +static int +compare_just_window_stack_position (void *a, + void *b) +{ + MetaWindow *window_a = a; + MetaWindow *window_b = b; + + if (window_a->stack_position < window_b->stack_position) + return -1; /* move window_a earlier in list */ + else if (window_a->stack_position > window_b->stack_position) + return 1; + else + return 0; /* not reached */ +} + +GList* +meta_stack_get_positions (MetaStack *stack) +{ + GList *tmp; + + /* Make sure to handle any adds or removes */ + stack_ensure_sorted (stack); + + tmp = g_list_copy (stack->sorted); + tmp = g_list_sort (tmp, (GCompareFunc) compare_just_window_stack_position); + + return tmp; +} + +static gint +compare_pointers (gconstpointer a, + gconstpointer b) +{ + if (a > b) + return 1; + else if (a < b) + return -1; + else + return 0; +} + +static gboolean +lists_contain_same_windows (GList *a, + GList *b) +{ + GList *copy1, *copy2; + GList *tmp1, *tmp2; + + if (g_list_length (a) != g_list_length (b)) + return FALSE; + + tmp1 = copy1 = g_list_sort (g_list_copy (a), compare_pointers); + tmp2 = copy2 = g_list_sort (g_list_copy (b), compare_pointers); + + while (tmp1 && tmp1->data == tmp2->data) /* tmp2 is non-NULL if tmp1 is */ + { + tmp1 = tmp1->next; + tmp2 = tmp2->next; + } + + g_list_free (copy1); + g_list_free (copy2); + + return (tmp1 == NULL); /* tmp2 is non-NULL if tmp1 is */ +} + +void +meta_stack_set_positions (MetaStack *stack, + GList *windows) +{ + int i; + GList *tmp; + + /* Make sure any adds or removes aren't in limbo -- is this needed? */ + stack_ensure_sorted (stack); + + if (!lists_contain_same_windows (windows, stack->sorted)) + { + meta_warning ("This list of windows has somehow changed; not resetting " + "positions of the windows.\n"); + return; + } + + g_list_free (stack->sorted); + stack->sorted = g_list_copy (windows); + + stack->need_resort = TRUE; + stack->need_constrain = TRUE; + + i = 0; + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + w->stack_position = i++; + tmp = tmp->next; + } + + meta_topic (META_DEBUG_STACK, + "Reset the stack positions of (nearly) all windows\n"); + + stack_sync_to_server (stack); +} + +void +meta_window_set_stack_position_no_sync (MetaWindow *window, + int position) +{ + int low, high, delta; + GList *tmp; + + g_return_if_fail (window->screen->stack != NULL); + g_return_if_fail (window->stack_position >= 0); + g_return_if_fail (position >= 0); + g_return_if_fail (position < window->screen->stack->n_positions); + + if (position == window->stack_position) + { + meta_topic (META_DEBUG_STACK, "Window %s already has position %d\n", + window->desc, position); + return; + } + + window->screen->stack->need_resort = TRUE; + window->screen->stack->need_constrain = TRUE; + + if (position < window->stack_position) + { + low = position; + high = window->stack_position - 1; + delta = 1; + } + else + { + low = window->stack_position + 1; + high = position; + delta = -1; + } + + tmp = window->screen->stack->sorted; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->stack_position >= low && + w->stack_position <= high) + w->stack_position += delta; + + tmp = tmp->next; + } + + window->stack_position = position; + + meta_topic (META_DEBUG_STACK, + "Window %s had stack_position set to %d\n", + window->desc, window->stack_position); +} + +void +meta_window_set_stack_position (MetaWindow *window, + int position) +{ + meta_window_set_stack_position_no_sync (window, position); + stack_sync_to_server (window->screen->stack); +} diff --git a/src/core/stack.h b/src/core/stack.h new file mode 100644 index 00000000..f9693897 --- /dev/null +++ b/src/core/stack.h @@ -0,0 +1,402 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file stack.h Which windows cover which other windows + * + * There are two factors that determine window position. + * + * One is window->stack_position, which is a unique integer + * indicating how windows are ordered with respect to one + * another. The ordering here transcends layers; it isn't changed + * as the window is moved among layers. This allows us to move several + * windows from one layer to another, while preserving the relative + * order of the moved windows. Also, it allows us to restore + * the stacking order from a saved session. + * + * However when actually stacking windows on the screen, the + * layer overrides the stack_position; windows are first sorted + * by layer, then by stack_position within each layer. + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_STACK_H +#define META_STACK_H + +#include "screen-private.h" + +/** + * Layers a window can be in. + * These MUST be in the order of stacking. + */ +typedef enum +{ + META_LAYER_DESKTOP = 0, + META_LAYER_BOTTOM = 1, + META_LAYER_NORMAL = 2, + META_LAYER_TOP = 4, /* Same as DOCK; see EWMH and bug 330717 */ + META_LAYER_DOCK = 4, + META_LAYER_FULLSCREEN = 5, + META_LAYER_FOCUSED_WINDOW = 6, + META_LAYER_LAST = 7 +} MetaStackLayer; + +/** + * A sorted list of windows bearing some level of resemblance to the stack of + * windows on the X server. + * + * (This is only used as a field within a MetaScreen; we treat it as a separate + * class for simplicity.) + */ +struct _MetaStack +{ + /** The MetaScreen containing this stack. */ + MetaScreen *screen; + + /** + * A sequence of all the Windows (X handles, not MetaWindows) of the windows + * we manage, sorted in order. Suitable to be passed into _NET_CLIENT_LIST. + */ + GArray *windows; + + /** The MetaWindows of the windows we manage, sorted in order. */ + GList *sorted; + + /** + * MetaWindows waiting to be added to the "sorted" and "windows" list, after + * being added by meta_stack_add() and before being assimilated by + * stack_ensure_sorted(). + * + * The order of the elements in this list is not important; what is important + * is the stack_position element of each window. + */ + GList *added; + + /** + * Windows (X handles, not MetaWindows) waiting to be removed from the + * "windows" list, after being removed by meta_stack_remove() and before + * being assimilated by stack_ensure_sorted(). (We already removed them + * from the "sorted" list.) + * + * The order of the elements in this list is not important. + */ + GList *removed; + + /** + * If this is zero, the local stack oughtn't to be brought up to date with + * the X server's stack, because it is in the middle of being updated. + * If it is positive, the local stack is said to be "frozen", and will need + * to be thawed that many times before the stack can be brought up to date + * again. You may freeze the stack with meta_stack_freeze() and thaw it + * with meta_stack_thaw(). + */ + int freeze_count; + + /** + * The last-known stack of all windows, bottom to top. We cache it here + * so that subsequent times we'll be able to do incremental moves. + */ + GArray *last_root_children_stacked; + + /** + * Number of stack positions; same as the length of added, but + * kept for quick reference. + */ + gint n_positions; + + /** Is the stack in need of re-sorting? */ + unsigned int need_resort : 1; + + /** + * Are the windows in the stack in need of having their + * layers recalculated? + */ + unsigned int need_relayer : 1; + + /** + * Are the windows in the stack in need of having their positions + * recalculated with respect to transiency (parent and child windows)? + */ + unsigned int need_constrain : 1; +}; + +/** + * Creates and initialises a MetaStack. + * + * \param screen The MetaScreen which will be the parent of this stack. + * \return The new screen. + */ +MetaStack *meta_stack_new (MetaScreen *screen); + +/** + * Destroys and frees a MetaStack. + * + * \param stack The stack to destroy. + */ +void meta_stack_free (MetaStack *stack); + +/** + * Adds a window to the local stack. It is a fatal error to call this + * function on a window which already exists on the stack of any screen. + * + * \param window The window to add + * \param stack The stack to add it to + */ +void meta_stack_add (MetaStack *stack, + MetaWindow *window); + +/** + * Removes a window from the local stack. It is a fatal error to call this + * function on a window which exists on the stack of any screen. + * + * \param window The window to remove + * \param stack The stack to remove it from + */ +void meta_stack_remove (MetaStack *stack, + MetaWindow *window); +/** + * Recalculates the correct layer for all windows in the stack, + * and moves them about accordingly. + * + * \param window Dummy parameter + * \param stack The stack to recalculate + * \bug What's with the dummy parameter? + */ +void meta_stack_update_layer (MetaStack *stack, + MetaWindow *window); + +/** + * Recalculates the correct stacking order for all windows in the stack + * according to their transience, and moves them about accordingly. + * + * \param window Dummy parameter + * \param stack The stack to recalculate + * \bug What's with the dummy parameter? + */ +void meta_stack_update_transient (MetaStack *stack, + MetaWindow *window); + +/** + * Move a window to the top of its layer. + * + * \param stack The stack to modify. + * \param window The window that's making an ascension. + * (Amulet of Yendor not required.) + */ +void meta_stack_raise (MetaStack *stack, + MetaWindow *window); +/** + * Move a window to the bottom of its layer. + * + * \param stack The stack to modify. + * \param window The window that's on the way downwards. + */ +void meta_stack_lower (MetaStack *stack, + MetaWindow *window); + +/** + * Prevent syncing to server until the next call of meta_stack_thaw(), + * so that we can carry out multiple operations in one go without having + * everything halfway reflected on the X server. + * + * (Calls to meta_stack_freeze() nest, so that multiple calls to + * meta_stack_freeze will require multiple calls to meta_stack_thaw().) + * + * \param stack The stack to freeze. + */ +void meta_stack_freeze (MetaStack *stack); + +/** + * Undoes a meta_stack_freeze(), and processes anything which has become + * necessary during the freeze. It is an error to call this function if + * the stack has not been frozen. + * + * \param stack The stack to thaw. + */ +void meta_stack_thaw (MetaStack *stack); + +/** + * Finds the top window on the stack. + * + * \param stack The stack to examine. + * \return The top window on the stack, or NULL in the vanishingly unlikely + * event that you have no windows on your screen whatsoever. + */ +MetaWindow* meta_stack_get_top (MetaStack *stack); + +/** + * Finds the window at the bottom of the stack. Since that's pretty much + * always the desktop, this isn't the most useful of functions, and nobody + * actually calls it. We should probably get rid of it. + * + * \param stack The stack to search + */ +MetaWindow* meta_stack_get_bottom (MetaStack *stack); + +/** + * Finds the window above a given window in the stack. + * It is not an error to pass in a window which does not exist in + * the stack; the function will merely return NULL. + * + * \param stack The stack to search. + * \param window The window to look above. + * \param only_within_layer If true, will return NULL if "window" is the + * top window in its layer. + * \return NULL if there is no such window; + * the window above "window" otherwise. + */ +MetaWindow* meta_stack_get_above (MetaStack *stack, + MetaWindow *window, + gboolean only_within_layer); + +/** + * Finds the window below a given window in the stack. + * It is not an error to pass in a window which does not exist in + * the stack; the function will merely return NULL. + * + * \param stack The stack to search. + * \param window The window to look below. + * \param only_within_layer If true, will return NULL if "window" is the + * bottom window in its layer. + * \return NULL if there is no such window; + * the window below "window" otherwise. + */ +MetaWindow* meta_stack_get_below (MetaStack *stack, + MetaWindow *window, + gboolean only_within_layer); + +/** + * Find the topmost, focusable, mapped, window in a stack. If you supply + * a window as "not_this_one", we won't return that one (presumably + * because it's going to be going away). But if you do supply "not_this_one" + * and we find its parent, we'll return that; and if "not_this_one" is in + * a group, we'll return the top window of that group. + * + * Also, we are prejudiced against dock windows. Every kind of window, even + * the desktop, will be returned in preference to a dock window. + * + * \param stack The stack to search. + * \param workspace NULL to search all workspaces; otherwise only windows + * from that workspace will be returned. + * \param not_this_one Window to ignore because it's being unfocussed or + * going away. + * \return The window matching all these constraints or NULL if none does. + * + * \bug Never called! + */ +MetaWindow* meta_stack_get_default_focus_window (MetaStack *stack, + MetaWorkspace *workspace, + MetaWindow *not_this_one); + +/** + * Find the topmost, focusable, mapped, window in a stack. If you supply + * a window as "not_this_one", we won't return that one (presumably + * because it's going to be going away). But if you do supply "not_this_one" + * and we find its parent, we'll return that; and if "not_this_one" is in + * a group, we'll return the top window of that group. + * + * Also, we are prejudiced against dock windows. Every kind of window, even + * the desktop, will be returned in preference to a dock window. + * + * \param stack The stack to search. + * \param workspace NULL to search all workspaces; otherwise only windows + * from that workspace will be returned. + * \param not_this_one Window to ignore because it's being unfocussed or + * going away. + * \param root_x The returned window must contain this point, + * unless it's a dock. + * \param root_y See root_x. + * \return The window matching all these constraints or NULL if none does. + */ +MetaWindow* meta_stack_get_default_focus_window_at_point (MetaStack *stack, + MetaWorkspace *workspace, + MetaWindow *not_this_one, + int root_x, + int root_y); + +/** + * Finds all the windows in the stack, in order. + * + * \param stack The stack to examine. + * \param workspace If non-NULL, only windows on this workspace will be + * returned; otherwise all windows in the stack will be + * returned. + * \return A list of windows, in stacking order, honouring layers. + */ +GList* meta_stack_list_windows (MetaStack *stack, + MetaWorkspace *workspace); + +/** + * Comparison function for windows within a stack. This is not directly + * suitable for use within a standard comparison routine, because it takes + * an extra parameter; you will need to wrap it. + * + * (FIXME: We could remove the stack parameter and use the stack of + * the screen of window A, and complain if the stack of the screen of + * window B differed; then this would be a usable general comparison function.) + * + * (FIXME: Apparently identical to compare_window_position(). Merge them.) + * + * \param stack A stack containing both window_a and window_b + * \param window_a A window + * \param window_b Another window + * \return -1 if window_a is below window_b, honouring layers; 1 if it's + * above it; 0 if you passed in the same window twice! + */ +int meta_stack_windows_cmp (MetaStack *stack, + MetaWindow *window_a, + MetaWindow *window_b); + +/** + * Sets the position of a window within the stack. This will only move it + * up or down within its layer. It is an error to attempt to move this + * below position zero or above the last position in the stack (however, since + * we don't provide a simple way to tell the number of windows in the stack, + * this requirement may not be easy to fulfil). + * + * \param window The window which is moving. + * \param position Where it should move to (0 is the bottom). + */ +void meta_window_set_stack_position (MetaWindow *window, + int position); + +/** + * Returns the current stack state, allowing rudimentary transactions. + * + * \param stack The stack to examine. + * \return An opaque GList representing the current stack sort order; + * it is the caller's responsibility to free it. + * Pass this to meta_stack_set_positions() later if you want to restore + * the state to where it was when you called this function. + */ +GList* meta_stack_get_positions (MetaStack *stack); + +/** + * Rolls back a transaction, given the list returned from + * meta_stack_get_positions(). + * + * \param stack The stack to roll back. + * \param windows The list returned from meta_stack_get_positions(). + */ +void meta_stack_set_positions (MetaStack *stack, + GList *windows); + +#endif diff --git a/src/core/testasyncgetprop.c b/src/core/testasyncgetprop.c new file mode 100644 index 00000000..ed044009 --- /dev/null +++ b/src/core/testasyncgetprop.c @@ -0,0 +1,497 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation. + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of The Open Group shall not be + * used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization from The Open Group. + */ + +#include "async-getprop.h" + +#include <time.h> +#include <sys/time.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <assert.h> + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#define NULL ((void*) 0) +#endif + +#ifdef HAVE_BACKTRACE +#include <execinfo.h> +static void +print_backtrace (void) +{ + void *bt[500]; + int bt_size; + int i; + char **syms; + + bt_size = backtrace (bt, 500); + + syms = backtrace_symbols (bt, bt_size); + + i = 0; + while (i < bt_size) + { + fprintf (stderr, " %s\n", syms[i]); + ++i; + } + + free (syms); +} +#else +static void +print_backtrace (void) +{ + fprintf (stderr, "Not compiled with backtrace support\n"); +} +#endif + +static int error_trap_depth = 0; + +static int +x_error_handler (Display *xdisplay, + XErrorEvent *error) +{ + char buf[64]; + + XGetErrorText (xdisplay, error->error_code, buf, 63); + + if (error_trap_depth == 0) + { + print_backtrace (); + + fprintf (stderr, "Unexpected X error: %s serial %ld error_code %d request_code %d minor_code %d)\n", + buf, + error->serial, + error->error_code, + error->request_code, + error->minor_code); + + exit (1); + } + + return 1; /* return value is meaningless */ +} + +static void +error_trap_push (Display *xdisplay) +{ + ++error_trap_depth; +} + +static void +error_trap_pop (Display *xdisplay) +{ + if (error_trap_depth == 0) + { + fprintf (stderr, "Error trap underflow!\n"); + exit (1); + } + + XSync (xdisplay, False); /* get all errors out of the queue */ + --error_trap_depth; +} + +static char* +my_strdup (const char *str) +{ + char *s; + + s = malloc (strlen (str) + 1); + if (s == NULL) + { + fprintf (stderr, "malloc failed\n"); + exit (1); + } + strcpy (s, str); + + return s; +} + +static char* +atom_name (Display *display, + Atom atom) +{ + if (atom == None) + { + return my_strdup ("None"); + } + else + { + char *xname; + char *ret; + + error_trap_push (display); + xname = XGetAtomName (display, atom); + error_trap_pop (display); + if (xname == NULL) + return my_strdup ("[unknown atom]"); + + ret = my_strdup (xname); + XFree (xname); + + return ret; + } +} + + +#define ELAPSED(start_time, current_time) \ + (((((double)current_time.tv_sec - start_time.tv_sec) * 1000000 + \ + (current_time.tv_usec - start_time.tv_usec))) / 1000.0) + +static struct timeval program_start_time; + +static Bool +try_get_reply (Display *xdisplay, + AgGetPropertyTask *task) +{ + if (ag_task_have_reply (task)) + { + int result; + Atom actual_type; + int actual_format; + unsigned long n_items; + unsigned long bytes_after; + unsigned char *data; + char *name; + struct timeval current_time; + + gettimeofday (¤t_time, NULL); + + printf (" %gms (we have a reply for property %ld)\n", + ELAPSED (program_start_time, current_time), + ag_task_get_property (task)); + + data = NULL; + + name = atom_name (xdisplay, + ag_task_get_property (task)); + printf (" %s on 0x%lx:\n", name, + ag_task_get_window (task)); + free (name); + + result = ag_task_get_reply_and_free (task, + &actual_type, + &actual_format, + &n_items, + &bytes_after, + &data); + task = NULL; + + if (result != Success) + { + fprintf (stderr, " error code %d getting reply\n", result); + } + else + { + name = atom_name (xdisplay, actual_type); + printf (" actual_type = %s\n", name); + free (name); + + printf (" actual_format = %d\n", actual_format); + + printf (" n_items = %lu\n", n_items); + printf (" bytes_after = %lu\n", bytes_after); + + printf (" data = \"%s\"\n", data ? (char*) data : "NULL"); + } + + return True; + } + + return False; +} + +static void run_speed_comparison (Display *xdisplay, + Window window); + +int +main (int argc, char **argv) +{ + Display *xdisplay; + int i; + int n_left; + int n_props; + Window window; + const char *window_str; + char *end; + Atom *props; + struct timeval current_time; + + if (argc < 2) + { + fprintf (stderr, "specify window ID\n"); + return 1; + } + + window_str = argv[1]; + + end = NULL; + window = strtoul (window_str, &end, 0); + if (end == NULL || *end != '\0') + { + fprintf (stderr, "\"%s\" does not parse as a window ID\n", window_str); + return 1; + } + + xdisplay = XOpenDisplay (NULL); + if (xdisplay == NULL) + { + fprintf (stderr, "Could not open display\n"); + return 1; + } + + if (getenv ("MARCO_SYNC") != NULL) + XSynchronize (xdisplay, True); + + XSetErrorHandler (x_error_handler); + + n_props = 0; + props = XListProperties (xdisplay, window, &n_props); + if (n_props == 0 || props == NULL) + { + fprintf (stderr, "Window has no properties\n"); + return 1; + } + + gettimeofday (&program_start_time, NULL); + + i = 0; + while (i < n_props) + { + gettimeofday (¤t_time, NULL); + printf (" %gms (sending request for property %ld)\n", + ELAPSED (program_start_time, current_time), + props[i]); + if (ag_task_create (xdisplay, + window, props[i], + 0, 0xffffffff, + False, + AnyPropertyType) == NULL) + { + fprintf (stderr, "Failed to send request\n"); + return 1; + } + + ++i; + } + + XFree (props); + props = NULL; + + n_left = n_props; + + while (TRUE) + { + XEvent xevent; + int connection; + fd_set set; + AgGetPropertyTask *task; + + /* Mop up event queue */ + while (XPending (xdisplay) > 0) + { + XNextEvent (xdisplay, &xevent); + gettimeofday (¤t_time, NULL); + printf (" %gms (processing event type %d)\n", + ELAPSED (program_start_time, current_time), + xevent.xany.type); + } + + while ((task = ag_get_next_completed_task (xdisplay))) + { + try_get_reply (xdisplay, task); + n_left -= 1; + } + + if (n_left == 0) + { + printf ("All %d replies received.\n", n_props); + break; + } + + /* Wake up if we may have a reply */ + connection = ConnectionNumber (xdisplay); + + FD_ZERO (&set); + FD_SET (connection, &set); + + gettimeofday (¤t_time, NULL); + printf (" %gms (blocking for data %d left)\n", + ELAPSED (program_start_time, current_time), n_left); + select (connection + 1, &set, NULL, NULL, NULL); + } + + run_speed_comparison (xdisplay, window); + + return 0; +} + +/* This function doesn't have all the printf's + * and other noise, it just compares async to sync + */ +static void +run_speed_comparison (Display *xdisplay, + Window window) +{ + int i; + int n_props; + struct timeval start, end; + int n_left; + + /* We just use atom values (0 to n_props) % 200, many are probably + * BadAtom, that's fine, but the %200 keeps most of them valid. The + * async case is about twice as advantageous when using valid atoms + * (or the issue may be that it's more advantageous when the + * properties are present and data is transmitted). + */ + n_props = 4000; + printf ("Timing with %d property requests\n", n_props); + + gettimeofday (&start, NULL); + + i = 0; + while (i < n_props) + { + if (ag_task_create (xdisplay, + window, (Atom) i % 200, + 0, 0xffffffff, + False, + AnyPropertyType) == NULL) + { + fprintf (stderr, "Failed to send request\n"); + exit (1); + } + + ++i; + } + + n_left = n_props; + + while (TRUE) + { + int connection; + fd_set set; + XEvent xevent; + AgGetPropertyTask *task; + + /* Mop up event queue */ + while (XPending (xdisplay) > 0) + XNextEvent (xdisplay, &xevent); + + while ((task = ag_get_next_completed_task (xdisplay))) + { + int result; + Atom actual_type; + int actual_format; + unsigned long n_items; + unsigned long bytes_after; + unsigned char *data; + + assert (ag_task_have_reply (task)); + + data = NULL; + result = ag_task_get_reply_and_free (task, + &actual_type, + &actual_format, + &n_items, + &bytes_after, + &data); + + if (data) + XFree (data); + + n_left -= 1; + } + + if (n_left == 0) + break; + + /* Wake up if we may have a reply */ + connection = ConnectionNumber (xdisplay); + + FD_ZERO (&set); + FD_SET (connection, &set); + + select (connection + 1, &set, NULL, NULL, NULL); + } + + gettimeofday (&end, NULL); + + printf ("Async time: %gms\n", + ELAPSED (start, end)); + + gettimeofday (&start, NULL); + + error_trap_push (xdisplay); + + i = 0; + while (i < n_props) + { + Atom actual_type; + int actual_format; + unsigned long n_items; + unsigned long bytes_after; + unsigned char *data; + + data = NULL; + if (XGetWindowProperty (xdisplay, window, + (Atom) i % 200, + 0, 0xffffffff, + False, + AnyPropertyType, + &actual_type, + &actual_format, + &n_items, + &bytes_after, + &data) == Success) + { + if (data) + XFree (data); + } + + ++i; + } + + error_trap_pop (xdisplay); + + gettimeofday (&end, NULL); + + printf ("Sync time: %gms\n", + ELAPSED (start, end)); +} diff --git a/src/core/testboxes.c b/src/core/testboxes.c new file mode 100644 index 00000000..fc81f064 --- /dev/null +++ b/src/core/testboxes.c @@ -0,0 +1,1416 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco box operation testing program */ + +/* + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "boxes.h" +#include <glib.h> +#include <stdlib.h> +#include <stdio.h> +#include <X11/Xutil.h> /* Just for the definition of the various gravities */ +#include <time.h> /* To initialize random seed */ + +#define NUM_RANDOM_RUNS 10000 + +static void +init_random_ness () +{ + srand(time(NULL)); +} + +static void +get_random_rect (MetaRectangle *rect) +{ + rect->x = rand () % 1600; + rect->y = rand () % 1200; + rect->width = rand () % 1600 + 1; + rect->height = rand () % 1200 + 1; +} + +static MetaRectangle* +new_meta_rect (int x, int y, int width, int height) +{ + MetaRectangle* temporary; + temporary = g_new (MetaRectangle, 1); + temporary->x = x; + temporary->y = y; + temporary->width = width; + temporary->height = height; + + return temporary; +} + +static MetaStrut* +new_meta_strut (int x, int y, int width, int height, int side) +{ + MetaStrut* temporary; + temporary = g_new (MetaStrut, 1); + temporary->rect = meta_rect(x, y, width, height); + temporary->side = side; + + return temporary; +} + +static MetaEdge* +new_screen_edge (int x, int y, int width, int height, int side_type) +{ + MetaEdge* temporary; + temporary = g_new (MetaEdge, 1); + temporary->rect.x = x; + temporary->rect.y = y; + temporary->rect.width = width; + temporary->rect.height = height; + temporary->side_type = side_type; + temporary->edge_type = META_EDGE_SCREEN; + + return temporary; +} + +static MetaEdge* +new_xinerama_edge (int x, int y, int width, int height, int side_type) +{ + MetaEdge* temporary; + temporary = g_new (MetaEdge, 1); + temporary->rect.x = x; + temporary->rect.y = y; + temporary->rect.width = width; + temporary->rect.height = height; + temporary->side_type = side_type; + temporary->edge_type = META_EDGE_XINERAMA; + + return temporary; +} + +static void +test_area () +{ + MetaRectangle temp; + int i; + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&temp); + g_assert (meta_rectangle_area (&temp) == temp.width * temp.height); + } + + temp = meta_rect (0, 0, 5, 7); + g_assert (meta_rectangle_area (&temp) == 35); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_intersect () +{ + MetaRectangle a = {100, 200, 50, 40}; + MetaRectangle b = { 0, 50, 110, 152}; + MetaRectangle c = { 0, 0, 10, 10}; + MetaRectangle d = {100, 100, 50, 50}; + MetaRectangle b_intersect_d = {100, 100, 10, 50}; + MetaRectangle temp; + MetaRectangle temp2; + + meta_rectangle_intersect (&a, &b, &temp); + temp2 = meta_rect (100, 200, 10, 2); + g_assert (meta_rectangle_equal (&temp, &temp2)); + g_assert (meta_rectangle_area (&temp) == 20); + + meta_rectangle_intersect (&a, &c, &temp); + g_assert (meta_rectangle_area (&temp) == 0); + + meta_rectangle_intersect (&a, &d, &temp); + g_assert (meta_rectangle_area (&temp) == 0); + + meta_rectangle_intersect (&b, &d, &b); + g_assert (meta_rectangle_equal (&b, &b_intersect_d)); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_equal () +{ + MetaRectangle a = {10, 12, 4, 18}; + MetaRectangle b = a; + MetaRectangle c = {10, 12, 4, 19}; + MetaRectangle d = {10, 12, 7, 18}; + MetaRectangle e = {10, 62, 4, 18}; + MetaRectangle f = {27, 12, 4, 18}; + + g_assert ( meta_rectangle_equal (&a, &b)); + g_assert (!meta_rectangle_equal (&a, &c)); + g_assert (!meta_rectangle_equal (&a, &d)); + g_assert (!meta_rectangle_equal (&a, &e)); + g_assert (!meta_rectangle_equal (&a, &f)); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_overlap_funcs () +{ + MetaRectangle temp1, temp2; + int i; + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&temp1); + get_random_rect (&temp2); + g_assert (meta_rectangle_overlap (&temp1, &temp2) == + (meta_rectangle_horiz_overlap (&temp1, &temp2) && + meta_rectangle_vert_overlap (&temp1, &temp2))); + } + + temp1 = meta_rect ( 0, 0, 10, 10); + temp2 = meta_rect (20, 0, 10, 5); + g_assert (!meta_rectangle_overlap (&temp1, &temp2)); + g_assert (!meta_rectangle_horiz_overlap (&temp1, &temp2)); + g_assert ( meta_rectangle_vert_overlap (&temp1, &temp2)); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_basic_fitting () +{ + MetaRectangle temp1, temp2, temp3; + int i; + /* Four cases: + * case temp1 fits temp2 temp1 could fit temp2 + * 1 Y Y + * 2 N Y + * 3 Y N + * 4 N N + * Of the four cases, case 3 is impossible. An alternate way of looking + * at this table is that either the middle column must be no, or the last + * column must be yes. So we test that. Also, we can repeat the test + * reversing temp1 and temp2. + */ + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&temp1); + get_random_rect (&temp2); + g_assert (meta_rectangle_contains_rect (&temp1, &temp2) == FALSE || + meta_rectangle_could_fit_rect (&temp1, &temp2) == TRUE); + g_assert (meta_rectangle_contains_rect (&temp2, &temp1) == FALSE || + meta_rectangle_could_fit_rect (&temp2, &temp1) == TRUE); + } + + temp1 = meta_rect ( 0, 0, 10, 10); + temp2 = meta_rect ( 5, 5, 5, 5); + temp3 = meta_rect ( 8, 2, 3, 7); + g_assert ( meta_rectangle_contains_rect (&temp1, &temp2)); + g_assert (!meta_rectangle_contains_rect (&temp2, &temp1)); + g_assert (!meta_rectangle_contains_rect (&temp1, &temp3)); + g_assert ( meta_rectangle_could_fit_rect (&temp1, &temp3)); + g_assert (!meta_rectangle_could_fit_rect (&temp3, &temp2)); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +free_strut_list (GSList *struts) +{ + GSList *tmp = struts; + while (tmp) + { + g_free (tmp->data); + tmp = tmp->next; + } + g_slist_free (struts); +} + +static GSList* +get_strut_list (int which) +{ + GSList *ans; + MetaDirection wc = 0; /* wc == who cares? ;-) */ + + ans = NULL; + + g_assert (which >=0 && which <= 6); + switch (which) + { + case 0: + break; + case 1: + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 400, 1160, 1600, 40, wc)); + break; + case 2: + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 800, 1100, 400, 100, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 300, 1150, 150, 50, wc)); + break; + case 3: + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 800, 1100, 400, 100, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 300, 1150, 80, 50, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 700, 525, 200, 150, wc)); + break; + case 4: + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 800, 1200, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 800, 0, 1600, 20, wc)); + break; + case 5: + ans = g_slist_prepend (ans, new_meta_strut ( 800, 0, 1600, 20, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 800, 1200, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 800, 10, 800, 1200, wc)); + break; + case 6: + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 40, wc)); + ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc)); + break; + } + + return ans; +} + +static GList* +get_screen_region (int which) +{ + GList *ret; + GSList *struts; + MetaRectangle basic_rect; + + basic_rect = meta_rect (0, 0, 1600, 1200); + ret = NULL; + + struts = get_strut_list (which); + ret = meta_rectangle_get_minimal_spanning_set_for_region (&basic_rect, struts); + free_strut_list (struts); + + return ret; +} + +static GList* +get_screen_edges (int which) +{ + GList *ret; + GSList *struts; + MetaRectangle basic_rect; + + basic_rect = meta_rect (0, 0, 1600, 1200); + ret = NULL; + + struts = get_strut_list (which); + ret = meta_rectangle_find_onscreen_edges (&basic_rect, struts); + free_strut_list (struts); + + return ret; +} + +static GList* +get_xinerama_edges (int which_xinerama_set, int which_strut_set) +{ + GList *ret; + GSList *struts; + GList *xins; + + xins = NULL; + g_assert (which_xinerama_set >=0 && which_xinerama_set <= 3); + switch (which_xinerama_set) + { + case 0: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 1200)); + break; + case 1: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 800, 1200)); + xins = g_list_prepend (xins, new_meta_rect (800, 0, 800, 1200)); + break; + case 2: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 600)); + xins = g_list_prepend (xins, new_meta_rect ( 0, 600, 1600, 600)); + break; + case 3: + xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 600)); + xins = g_list_prepend (xins, new_meta_rect ( 0, 600, 800, 600)); + xins = g_list_prepend (xins, new_meta_rect (800, 600, 800, 600)); + break; + } + + ret = NULL; + + struts = get_strut_list (which_strut_set); + ret = meta_rectangle_find_nonintersected_xinerama_edges (xins, struts); + + free_strut_list (struts); + meta_rectangle_free_list_and_elements (xins); + + return ret; +} + +#if 0 +static void +test_merge_regions () +{ + /* logarithmically distributed random number of struts (range?) + * logarithmically distributed random size of struts (up to screen size???) + * uniformly distributed location of center of struts (within screen) + * merge all regions that are possible + * print stats on problem setup + * number of (non-completely-occluded?) struts + * percentage of screen covered + * length of resulting non-minimal spanning set + * length of resulting minimal spanning set + * print stats on merged regions: + * number boxes merged + * number of those merges that were of the form A contains B + * number of those merges that were of the form A partially contains B + * number of those merges that were of the form A is adjacent to B + */ + + GList* region; + GList* compare; + int num_contains, num_merged, num_part_contains, num_adjacent; + + num_contains = num_merged = num_part_contains = num_adjacent = 0; + compare = region = get_screen_region (2); + g_assert (region); + + printf ("Merging stats:\n"); + printf (" Length of initial list: %d\n", g_list_length (region)); +#ifdef PRINT_DEBUG + char rect1[RECT_LENGTH], rect2[RECT_LENGTH]; + char region_list[(RECT_LENGTH + 2) * g_list_length (region)]; + meta_rectangle_region_to_string (region, ", ", region_list); + printf (" Initial rectangles: %s\n", region_list); +#endif + + while (compare && compare->next) + { + MetaRectangle *a = compare->data; + GList *other = compare->next; + + g_assert (a->width > 0 && a->height > 0); + + while (other) + { + MetaRectangle *b = other->data; + GList *delete_me = NULL; + + g_assert (b->width > 0 && b->height > 0); + +#ifdef PRINT_DEBUG + printf (" -- Comparing %s to %s --\n", + meta_rectangle_to_string (a, rect1), + meta_rectangle_to_string (b, rect2)); +#endif + + /* If a contains b, just remove b */ + if (meta_rectangle_contains_rect (a, b)) + { + delete_me = other; + num_contains++; + num_merged++; + } + /* If b contains a, just remove a */ + else if (meta_rectangle_contains_rect (a, b)) + { + delete_me = compare; + num_contains++; + num_merged++; + } + /* If a and b might be mergeable horizontally */ + else if (a->y == b->y && a->height == b->height) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + num_part_contains++; + num_merged++; + } + /* If a and b are adjacent */ + else if (a->x + a->width == b->x || a->x == b->x + b->width) + { + int new_x = MIN (a->x, b->x); + a->width = MAX (a->x + a->width, b->x + b->width) - new_x; + a->x = new_x; + delete_me = other; + num_adjacent++; + num_merged++; + } + } + /* If a and b might be mergeable vertically */ + else if (a->x == b->x && a->width == b->width) + { + /* If a and b overlap */ + if (meta_rectangle_overlap (a, b)) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + num_part_contains++; + num_merged++; + } + /* If a and b are adjacent */ + else if (a->y + a->height == b->y || a->y == b->y + b->height) + { + int new_y = MIN (a->y, b->y); + a->height = MAX (a->y + a->height, b->y + b->height) - new_y; + a->y = new_y; + delete_me = other; + num_adjacent++; + num_merged++; + } + } + + other = other->next; + + /* Delete any rectangle in the list that is no longer wanted */ + if (delete_me != NULL) + { +#ifdef PRINT_DEBUG + MetaRectangle *bla = delete_me->data; + printf (" Deleting rect %s\n", + meta_rectangle_to_string (bla, rect1)); +#endif + + /* Deleting the rect we're compare others to is a little tricker */ + if (compare == delete_me) + { + compare = compare->next; + other = compare->next; + a = compare->data; + } + + /* Okay, we can free it now */ + g_free (delete_me->data); + region = g_list_delete_link (region, delete_me); + } + +#ifdef PRINT_DEBUG + char region_list[(RECT_LENGTH + 2) * g_list_length (region)]; + meta_rectangle_region_to_string (region, ", ", region_list); + printf (" After comparison, new list is: %s\n", region_list); +#endif + } + + compare = compare->next; + } + + printf (" Num rectangles contained in others : %d\n", + num_contains); + printf (" Num rectangles partially contained in others: %d\n", + num_part_contains); + printf (" Num rectangles adjacent to others : %d\n", + num_adjacent); + printf (" Num rectangles merged with others : %d\n", + num_merged); +#ifdef PRINT_DEBUG + char region_list2[(RECT_LENGTH + 2) * g_list_length (region)]; + meta_rectangle_region_to_string (region, ", ", region_list2); + printf (" Final rectangles: %s\n", region_list2); +#endif + + meta_rectangle_free_spanning_set (region); + region = NULL; + + printf ("%s passed.\n", G_STRFUNC); +} +#endif + +static void +verify_lists_are_equal (GList *code, GList *answer) +{ + int which = 0; + + while (code && answer) + { + MetaRectangle *a = code->data; + MetaRectangle *b = answer->data; + + if (a->x != b->x || + a->y != b->y || + a->width != b->width || + a->height != b->height) + { + g_error ("%dth item in code answer answer lists do not match; " + "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n", + which, + a->x, a->y, a->width, a->height, + b->x, b->y, b->width, b->height); + } + + code = code->next; + answer = answer->next; + + which++; + } + + /* Ought to be at the end of both lists; check if we aren't */ + if (code) + { + MetaRectangle *tmp = code->data; + g_error ("code list longer than answer list by %d items; " + "first extra item: %d,%d +%d,%d\n", + g_list_length (code), + tmp->x, tmp->y, tmp->width, tmp->height); + } + + if (answer) + { + MetaRectangle *tmp = answer->data; + g_error ("answer list longer than code list by %d items; " + "first extra item: %d,%d +%d,%d\n", + g_list_length (answer), + tmp->x, tmp->y, tmp->width, tmp->height); + } +} + +static void +test_regions_okay () +{ + GList* region; + GList* tmp; + + /*************************************************************/ + /* Make sure test region 0 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (0); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect (0, 0, 1600, 1200)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 1 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (1); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 400, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 1600, 1140)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 2 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (2); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 300, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect ( 450, 20, 350, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect (1200, 20, 400, 1180)); + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 800, 1130)); + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 1600, 1080)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 3 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (3); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect ( 380, 675, 420, 525)); /* 220500 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 300, 1180)); /* 354000 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 380, 20, 320, 1180)); /* 377600 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 675, 800, 475)); /* 380000 */ + tmp = g_list_prepend (tmp, new_meta_rect (1200, 20, 400, 1180)); /* 472000 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 675, 1600, 425)); /* 680000 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 900, 20, 700, 1080)); /* 756000 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 700, 1130)); /* 791000 */ + tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 1600, 505)); /* 808000 */ +#if 0 + printf ("Got to here...\n"); + char region_list[(RECT_LENGTH+2) * g_list_length (region)]; + char tmp_list[ (RECT_LENGTH+2) * g_list_length (tmp)]; + meta_rectangle_region_to_string (region, ", ", region_list); + meta_rectangle_region_to_string (region, ", ", tmp_list); + printf ("%s vs. %s\n", region_list, tmp_list); +#endif + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 4 has the right spanning rectangles */ + /*************************************************************/ + region = get_screen_region (4); + tmp = NULL; + tmp = g_list_prepend (tmp, new_meta_rect ( 800, 20, 800, 1180)); + verify_lists_are_equal (region, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (region); + + /*************************************************************/ + /* Make sure test region 5 has the right spanning rectangles */ + /*************************************************************/ + printf ("The next test intentionally causes a warning, " + "but it can be ignored.\n"); + region = get_screen_region (5); + verify_lists_are_equal (region, NULL); + + /* FIXME: Still to do: + * - Create random struts and check the regions somehow + */ + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_region_fitting () +{ + GList* region; + MetaRectangle rect; + + /* See test_basic_fitting() for how/why these automated random tests work */ + int i; + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&rect); + g_assert (meta_rectangle_contained_in_region (region, &rect) == FALSE || + meta_rectangle_could_fit_in_region (region, &rect) == TRUE); + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (1); + + rect = meta_rect (50, 50, 400, 400); + g_assert (meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (meta_rectangle_contained_in_region (region, &rect)); + + rect = meta_rect (250, 0, 500, 1150); + g_assert (!meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (!meta_rectangle_contained_in_region (region, &rect)); + + rect = meta_rect (250, 0, 400, 400); + g_assert (meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (!meta_rectangle_contained_in_region (region, &rect)); + + meta_rectangle_free_list_and_elements (region); + + region = get_screen_region (2); + rect = meta_rect (1000, 50, 600, 1100); + g_assert (meta_rectangle_could_fit_in_region (region, &rect)); + g_assert (!meta_rectangle_contained_in_region (region, &rect)); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_clamping_to_region () +{ + GList* region; + MetaRectangle rect; + MetaRectangle min_size; + FixedDirections fixed_directions; + int i; + + min_size.height = min_size.width = 1; + fixed_directions = 0; + + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + MetaRectangle temp; + get_random_rect (&rect); + temp = rect; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (meta_rectangle_could_fit_in_region (region, &rect) == TRUE); + g_assert (rect.x == temp.x && rect.y == temp.y); + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (1); + + rect = meta_rect (50, 50, 10000, 10000); + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 1600 && rect.height == 1140); + + rect = meta_rect (275, -50, 410, 10000); + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 400 && rect.height == 1180); + + rect = meta_rect (50, 50, 10000, 10000); + min_size.height = 1170; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 400 && rect.height == 1180); + + printf ("The next test intentionally causes a warning, " + "but it can be ignored.\n"); + rect = meta_rect (50, 50, 10000, 10000); + min_size.width = 600; min_size.height = 1170; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 600 && rect.height == 1170); + + rect = meta_rect (350, 50, 100, 1100); + min_size.width = 1; min_size.height = 1; + fixed_directions = FIXED_DIRECTION_X; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 100 && rect.height == 1100); + + rect = meta_rect (300, 70, 500, 1100); + min_size.width = 1; min_size.height = 1; + fixed_directions = FIXED_DIRECTION_Y; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 400 && rect.height == 1100); + + printf ("The next test intentionally causes a warning, " + "but it can be ignored.\n"); + rect = meta_rect (300, 70, 999999, 999999); + min_size.width = 100; min_size.height = 200; + fixed_directions = FIXED_DIRECTION_Y; + meta_rectangle_clamp_to_fit_into_region (region, + fixed_directions, + &rect, + &min_size); + g_assert (rect.width == 100 && rect.height == 999999); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", G_STRFUNC); +} + +static gboolean +rect_overlaps_region (const GList *spanning_rects, + const MetaRectangle *rect) +{ + /* FIXME: Should I move this to boxes.[ch]? */ + const GList *temp; + gboolean overlaps; + + temp = spanning_rects; + overlaps = FALSE; + while (!overlaps && temp != NULL) + { + overlaps = overlaps || meta_rectangle_overlap (temp->data, rect); + temp = temp->next; + } + + return overlaps; +} + +gboolean time_to_print = FALSE; + +static void +test_clipping_to_region () +{ + GList* region; + MetaRectangle rect, temp; + FixedDirections fixed_directions = 0; + int i; + + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&rect); + if (rect_overlaps_region (region, &rect)) + { + meta_rectangle_clip_to_region (region, 0, &rect); + g_assert (meta_rectangle_contained_in_region (region, &rect) == TRUE); + } + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (2); + + rect = meta_rect (-50, -10, 10000, 10000); + meta_rectangle_clip_to_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (region->data, &rect)); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (300, 1000, 400, 150); + meta_rectangle_clip_to_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (400, 1000, 300, 200); + temp = meta_rect (450, 1000, 250, 200); + meta_rectangle_clip_to_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (400, 1000, 300, 200); + temp = meta_rect (400, 1000, 300, 150); + meta_rectangle_clip_to_region (region, + FIXED_DIRECTION_X, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (400, 1000, 300, 200); + temp = meta_rect (400, 1000, 300, 150); + meta_rectangle_clip_to_region (region, + FIXED_DIRECTION_X, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_shoving_into_region () +{ + GList* region; + MetaRectangle rect, temp; + FixedDirections fixed_directions = 0; + int i; + + region = get_screen_region (3); + for (i = 0; i < NUM_RANDOM_RUNS; i++) + { + get_random_rect (&rect); + if (meta_rectangle_could_fit_in_region (region, &rect)) + { + meta_rectangle_shove_into_region (region, 0, &rect); + g_assert (meta_rectangle_contained_in_region (region, &rect)); + } + } + meta_rectangle_free_list_and_elements (region); + + /* Do some manual tests too */ + region = get_screen_region (2); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (300, 950, 400, 200); + meta_rectangle_shove_into_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (425, 1000, 300, 200); + temp = meta_rect (450, 1000, 300, 200); + meta_rectangle_shove_into_region (region, + fixed_directions, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (425, 1000, 300, 200); + temp = meta_rect (425, 950, 300, 200); + meta_rectangle_shove_into_region (region, + FIXED_DIRECTION_X, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 300, 1000, 400, 200); + temp = meta_rect (1200, 1000, 400, 200); + meta_rectangle_shove_into_region (region, + FIXED_DIRECTION_Y, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 800, 1150, 400, 50); /* Completely "offscreen" :) */ + temp = meta_rect ( 800, 1050, 400, 50); + meta_rectangle_shove_into_region (region, + 0, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (-1000, 0, 400, 150); /* Offscreen in 2 directions */ + temp = meta_rect ( 0, 20, 400, 150); + meta_rectangle_shove_into_region (region, + 0, + &rect); + g_assert (meta_rectangle_equal (&rect, &temp)); + + meta_rectangle_free_list_and_elements (region); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +verify_edge_lists_are_equal (GList *code, GList *answer) +{ + int which = 0; + + while (code && answer) + { + MetaEdge *a = code->data; + MetaEdge *b = answer->data; + + if (!meta_rectangle_equal (&a->rect, &b->rect) || + a->side_type != b->side_type || + a->edge_type != b->edge_type) + { + g_error ("%dth item in code answer answer lists do not match; " + "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n", + which, + a->rect.x, a->rect.y, a->rect.width, a->rect.height, + b->rect.x, b->rect.y, b->rect.width, b->rect.height); + } + + code = code->next; + answer = answer->next; + + which++; + } + + /* Ought to be at the end of both lists; check if we aren't */ + if (code) + { + MetaEdge *tmp = code->data; + g_error ("code list longer than answer list by %d items; " + "first extra item rect: %d,%d +%d,%d\n", + g_list_length (code), + tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height); + } + + if (answer) + { + MetaEdge *tmp = answer->data; + g_error ("answer list longer than code list by %d items; " + "first extra item rect: %d,%d +%d,%d\n", + g_list_length (answer), + tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height); + } +} + +static void +test_find_onscreen_edges () +{ + GList* edges; + GList* tmp; + + int left = META_DIRECTION_LEFT; + int right = META_DIRECTION_RIGHT; + int top = META_DIRECTION_TOP; + int bottom = META_DIRECTION_BOTTOM; + + /*************************************************/ + /* Make sure test region 0 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (0); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 1600, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 0, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 0, 0, 1200, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 0, 0, 1200, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 1 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (1); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 1200, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1140, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 0, 40, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 2 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (2); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1200, 350, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 300, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 150, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0, 100, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0, 50, right)); + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0, 100, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1150, 0, 50, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 3 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (3); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1200, 420, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 300, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 80, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 400, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 700, 525, 200, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 700, 675, 200, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0, 100, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 700, 525, 0, 150, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0, 50, right)); + tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0, 100, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 900, 525, 0, 150, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1150, 0, 50, left)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left)); + +#if 0 + #define FUDGE 50 /* number of edges */ + char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE]; + meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1); + meta_rectangle_edge_list_to_string (tmp, "\n ", big_buffer2); + printf("Generated edge list:\n %s\nComparison edges list:\n %s\n", + big_buffer1, big_buffer2); +#endif + + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 4 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (4); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1200, 800, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 20, 800, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 800, 20, 0, 1180, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 5 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (5); + tmp = NULL; + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************/ + /* Make sure test region 6 has the correct edges */ + /*************************************************/ + edges = get_screen_edges (6); + tmp = NULL; + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 1600, 0, bottom)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 40, 1600, 0, top)); + tmp = g_list_prepend (tmp, new_screen_edge (1600, 40, 0, 1160, right)); + tmp = g_list_prepend (tmp, new_screen_edge ( 0, 40, 0, 1160, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_find_nonintersected_xinerama_edges () +{ + GList* edges; + GList* tmp; + + int left = META_DIRECTION_LEFT; + int right = META_DIRECTION_RIGHT; + int top = META_DIRECTION_TOP; + int bottom = META_DIRECTION_BOTTOM; + + /*************************************************************************/ + /* Make sure test xinerama set 0 for with region 0 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (0, 0); + tmp = NULL; + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 2 for with region 1 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (2, 1); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 1600, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 1600, 0, top)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 1 for with region 2 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (1, 2); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 20, 0, 1080, right)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 20, 0, 1180, left)); +#if 0 + #define FUDGE 50 + char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE]; + meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1); + meta_rectangle_edge_list_to_string (tmp, "\n ", big_buffer2); + printf("Generated edge list:\n %s\nComparison edges list:\n %s\n", + big_buffer1, big_buffer2); +#endif + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 3 for with region 3 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (3, 3); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 900, 600, 700, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 700, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 900, 600, 700, 0, top)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 700, 0, top)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 675, 0, 425, right)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 675, 0, 525, left)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 3 for with region 4 has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (3, 4); + tmp = NULL; + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 800, 0, bottom)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 800, 0, top)); + tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 0, 600, right)); + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + /*************************************************************************/ + /* Make sure test xinerama set 3 for with region 5has the correct edges */ + /*************************************************************************/ + edges = get_xinerama_edges (3, 5); + tmp = NULL; + verify_edge_lists_are_equal (edges, tmp); + meta_rectangle_free_list_and_elements (tmp); + meta_rectangle_free_list_and_elements (edges); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_gravity_resize () +{ + MetaRectangle oldrect, rect, temp; + + rect.x = -500; /* Some random amount not equal to oldrect.x to ensure that + * the resize is done with respect to oldrect instead of rect + */ + oldrect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect ( 50, 300, 20, 5); + meta_rectangle_resize_with_gravity (&oldrect, + &rect, + NorthWestGravity, + 20, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (165, 300, 20, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + NorthGravity, + 20, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (280, 300, 20, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + NorthEastGravity, + 20, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect ( 50, 695, 50, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + SouthWestGravity, + 50, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (150, 695, 50, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + SouthGravity, + 50, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 50, 300, 250, 400); + temp = meta_rect (250, 695, 50, 5); + meta_rectangle_resize_with_gravity (&rect, + &rect, + SouthEastGravity, + 50, + 5); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (167, 738, 237, 843); + temp = meta_rect (167, 1113, 832, 93); + meta_rectangle_resize_with_gravity (&rect, + &rect, + WestGravity, + 832, + 93); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect ( 167, 738, 237, 843); + temp = meta_rect (-131, 1113, 833, 93); + meta_rectangle_resize_with_gravity (&rect, + &rect, + CenterGravity, + 832, + 93); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (270, 994, 430, 212); + meta_rectangle_resize_with_gravity (&rect, + &rect, + EastGravity, + 430, + 211); + g_assert (meta_rectangle_equal (&rect, &temp)); + + rect = meta_rect (300, 1000, 400, 200); + temp = meta_rect (300, 1000, 430, 211); + meta_rectangle_resize_with_gravity (&rect, + &rect, + StaticGravity, + 430, + 211); + g_assert (meta_rectangle_equal (&rect, &temp)); + + printf ("%s passed.\n", G_STRFUNC); +} + +static void +test_find_closest_point_to_line () +{ + double x1, y1, x2, y2, px, py, rx, ry; + double answer_x, answer_y; + + x1 = 3.0; y1 = 49.0; + x2 = 2.0; y2 = - 1.0; + px = -2.6; py = 19.1; + answer_x = 2.4; answer_y = 19; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + /* Special test for x1 == x2, so that slop of line is infinite */ + x1 = 3.0; y1 = 49.0; + x2 = 3.0; y2 = - 1.0; + px = -2.6; py = 19.1; + answer_x = 3.0; answer_y = 19.1; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + /* Special test for y1 == y2, so perp line has slope of infinity */ + x1 = 3.14; y1 = 7.0; + x2 = 2.718; y2 = 7.0; + px = -2.6; py = 19.1; + answer_x = -2.6; answer_y = 7; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + /* Test when we the point we want to be closest to is actually on the line */ + x1 = 3.0; y1 = 49.0; + x2 = 2.0; y2 = - 1.0; + px = 2.4; py = 19.0; + answer_x = 2.4; answer_y = 19; + meta_rectangle_find_linepoint_closest_to_point (x1, y1, + x2, y2, + px, py, + &rx, &ry); + g_assert (rx == answer_x && ry == answer_y); + + printf ("%s passed.\n", G_STRFUNC); +} + +int +main() +{ + init_random_ness (); + test_area (); + test_intersect (); + test_equal (); + test_overlap_funcs (); + test_basic_fitting (); + + test_regions_okay (); + test_region_fitting (); + + test_clamping_to_region (); + test_clipping_to_region (); + test_shoving_into_region (); + + /* And now the functions dealing with edges more than boxes */ + test_find_onscreen_edges (); + test_find_nonintersected_xinerama_edges (); + + /* And now the misfit functions that don't quite fit in anywhere else... */ + test_gravity_resize (); + test_find_closest_point_to_line (); + + printf ("All tests passed.\n"); + return 0; +} diff --git a/src/core/util.c b/src/core/util.c new file mode 100644 index 00000000..6dd27f8e --- /dev/null +++ b/src/core/util.c @@ -0,0 +1,641 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco utilities */ + +/* + * Copyright (C) 2001 Havoc Pennington + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200112L /* for fdopen() */ + +#include <config.h> +#include "util.h" +#include "main.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <X11/Xlib.h> /* must explicitly be included for Solaris; #326746 */ +#include <X11/Xutil.h> /* Just for the definition of the various gravities */ + +#ifdef HAVE_BACKTRACE +#include <execinfo.h> +void +meta_print_backtrace (void) +{ + void *bt[500]; + int bt_size; + int i; + char **syms; + + bt_size = backtrace (bt, 500); + + syms = backtrace_symbols (bt, bt_size); + + i = 0; + while (i < bt_size) + { + meta_verbose (" %s\n", syms[i]); + ++i; + } + + free (syms); +} +#else +void +meta_print_backtrace (void) +{ + meta_verbose ("Not compiled with backtrace support\n"); +} +#endif + +static gboolean is_verbose = FALSE; +static gboolean is_debugging = FALSE; +static gboolean replace_current = FALSE; +static int no_prefix = 0; + +#ifdef WITH_VERBOSE_MODE +static FILE* logfile = NULL; + +static void +ensure_logfile (void) +{ + if (logfile == NULL && g_getenv ("MARCO_USE_LOGFILE")) + { + char *filename = NULL; + char *tmpl; + int fd; + GError *err; + + tmpl = g_strdup_printf ("marco-%d-debug-log-XXXXXX", + (int) getpid ()); + + err = NULL; + fd = g_file_open_tmp (tmpl, + &filename, + &err); + + g_free (tmpl); + + if (err != NULL) + { + meta_warning (_("Failed to open debug log: %s\n"), + err->message); + g_error_free (err); + return; + } + + logfile = fdopen (fd, "w"); + + if (logfile == NULL) + { + meta_warning (_("Failed to fdopen() log file %s: %s\n"), + filename, strerror (errno)); + close (fd); + } + else + { + g_printerr (_("Opened log file %s\n"), filename); + } + + g_free (filename); + } +} +#endif + +gboolean +meta_is_verbose (void) +{ + return is_verbose; +} + +void +meta_set_verbose (gboolean setting) +{ +#ifndef WITH_VERBOSE_MODE + if (setting) + meta_fatal (_("Marco was compiled without support for verbose mode\n")); +#else + if (setting) + ensure_logfile (); +#endif + + is_verbose = setting; +} + +gboolean +meta_is_debugging (void) +{ + return is_debugging; +} + +void +meta_set_debugging (gboolean setting) +{ +#ifdef WITH_VERBOSE_MODE + if (setting) + ensure_logfile (); +#endif + + is_debugging = setting; +} + +gboolean +meta_get_replace_current_wm (void) +{ + return replace_current; +} + +void +meta_set_replace_current_wm (gboolean setting) +{ + replace_current = setting; +} + +char * +meta_g_utf8_strndup (const gchar *src, + gsize n) +{ + const gchar *s = src; + while (n && *s) + { + s = g_utf8_next_char (s); + n--; + } + + return g_strndup (src, s - src); +} + +static int +utf8_fputs (const char *str, + FILE *f) +{ + char *l; + int retval; + + l = g_locale_from_utf8 (str, -1, NULL, NULL, NULL); + + if (l == NULL) + retval = fputs (str, f); /* just print it anyway, better than nothing */ + else + retval = fputs (l, f); + + g_free (l); + + return retval; +} + +void +meta_free_gslist_and_elements (GSList *list_to_deep_free) +{ + g_slist_foreach (list_to_deep_free, + (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */ + NULL); + g_slist_free (list_to_deep_free); +} + +#ifdef WITH_VERBOSE_MODE +void +meta_debug_spew_real (const char *format, ...) +{ + va_list args; + gchar *str; + FILE *out; + + g_return_if_fail (format != NULL); + + if (!is_debugging) + return; + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + out = logfile ? logfile : stderr; + + if (no_prefix == 0) + utf8_fputs (_("Window manager: "), out); + utf8_fputs (str, out); + + fflush (out); + + g_free (str); +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef WITH_VERBOSE_MODE +void +meta_verbose_real (const char *format, ...) +{ + va_list args; + gchar *str; + FILE *out; + + g_return_if_fail (format != NULL); + + if (!is_verbose) + return; + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + out = logfile ? logfile : stderr; + + if (no_prefix == 0) + utf8_fputs ("Window manager: ", out); + utf8_fputs (str, out); + + fflush (out); + + g_free (str); +} +#endif /* WITH_VERBOSE_MODE */ + +#ifdef WITH_VERBOSE_MODE +static const char* +topic_name (MetaDebugTopic topic) +{ + switch (topic) + { + case META_DEBUG_FOCUS: + return "FOCUS"; + case META_DEBUG_WORKAREA: + return "WORKAREA"; + case META_DEBUG_STACK: + return "STACK"; + case META_DEBUG_THEMES: + return "THEMES"; + case META_DEBUG_SM: + return "SM"; + case META_DEBUG_EVENTS: + return "EVENTS"; + case META_DEBUG_WINDOW_STATE: + return "WINDOW_STATE"; + case META_DEBUG_WINDOW_OPS: + return "WINDOW_OPS"; + case META_DEBUG_PLACEMENT: + return "PLACEMENT"; + case META_DEBUG_GEOMETRY: + return "GEOMETRY"; + case META_DEBUG_PING: + return "PING"; + case META_DEBUG_XINERAMA: + return "XINERAMA"; + case META_DEBUG_KEYBINDINGS: + return "KEYBINDINGS"; + case META_DEBUG_SYNC: + return "SYNC"; + case META_DEBUG_ERRORS: + return "ERRORS"; + case META_DEBUG_STARTUP: + return "STARTUP"; + case META_DEBUG_PREFS: + return "PREFS"; + case META_DEBUG_GROUPS: + return "GROUPS"; + case META_DEBUG_RESIZING: + return "RESIZING"; + case META_DEBUG_SHAPES: + return "SHAPES"; + case META_DEBUG_COMPOSITOR: + return "COMPOSITOR"; + case META_DEBUG_EDGE_RESISTANCE: + return "EDGE_RESISTANCE"; + } + + return "WM"; +} + +static int sync_count = 0; + +void +meta_topic_real (MetaDebugTopic topic, + const char *format, + ...) +{ + va_list args; + gchar *str; + FILE *out; + + g_return_if_fail (format != NULL); + + if (!is_verbose) + return; + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + out = logfile ? logfile : stderr; + + if (no_prefix == 0) + fprintf (out, "%s: ", topic_name (topic)); + + if (topic == META_DEBUG_SYNC) + { + ++sync_count; + fprintf (out, "%d: ", sync_count); + } + + utf8_fputs (str, out); + + fflush (out); + + g_free (str); +} +#endif /* WITH_VERBOSE_MODE */ + +void +meta_bug (const char *format, ...) +{ + va_list args; + gchar *str; + FILE *out; + + g_return_if_fail (format != NULL); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + +#ifdef WITH_VERBOSE_MODE + out = logfile ? logfile : stderr; +#else + out = stderr; +#endif + + if (no_prefix == 0) + utf8_fputs (_("Bug in window manager: "), out); + utf8_fputs (str, out); + + fflush (out); + + g_free (str); + + meta_print_backtrace (); + + /* stop us in a debugger */ + abort (); +} + +void +meta_warning (const char *format, ...) +{ + va_list args; + gchar *str; + FILE *out; + + g_return_if_fail (format != NULL); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + +#ifdef WITH_VERBOSE_MODE + out = logfile ? logfile : stderr; +#else + out = stderr; +#endif + + if (no_prefix == 0) + utf8_fputs (_("Window manager warning: "), out); + utf8_fputs (str, out); + + fflush (out); + + g_free (str); +} + +void +meta_fatal (const char *format, ...) +{ + va_list args; + gchar *str; + FILE *out; + + g_return_if_fail (format != NULL); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + +#ifdef WITH_VERBOSE_MODE + out = logfile ? logfile : stderr; +#else + out = stderr; +#endif + + if (no_prefix == 0) + utf8_fputs (_("Window manager error: "), out); + utf8_fputs (str, out); + + fflush (out); + + g_free (str); + + meta_exit (META_EXIT_ERROR); +} + +void +meta_push_no_msg_prefix (void) +{ + ++no_prefix; +} + +void +meta_pop_no_msg_prefix (void) +{ + g_return_if_fail (no_prefix > 0); + + --no_prefix; +} + +void +meta_exit (MetaExitCode code) +{ + + exit (code); +} + +gint +meta_unsigned_long_equal (gconstpointer v1, + gconstpointer v2) +{ + return *((const gulong*) v1) == *((const gulong*) v2); +} + +guint +meta_unsigned_long_hash (gconstpointer v) +{ + gulong val = * (const gulong *) v; + + /* I'm not sure this works so well. */ +#if GLIB_SIZEOF_LONG > 4 + return (guint) (val ^ (val >> 32)); +#else + return val; +#endif +} + +const char* +meta_gravity_to_string (int gravity) +{ + switch (gravity) + { + case NorthWestGravity: + return "NorthWestGravity"; + break; + case NorthGravity: + return "NorthGravity"; + break; + case NorthEastGravity: + return "NorthEastGravity"; + break; + case WestGravity: + return "WestGravity"; + break; + case CenterGravity: + return "CenterGravity"; + break; + case EastGravity: + return "EastGravity"; + break; + case SouthWestGravity: + return "SouthWestGravity"; + break; + case SouthGravity: + return "SouthGravity"; + break; + case SouthEastGravity: + return "SouthEastGravity"; + break; + case StaticGravity: + return "StaticGravity"; + break; + default: + return "NorthWestGravity"; + break; + } +} + +GPid +meta_show_dialog (const char *type, + const char *message, + const char *timeout, + const gint screen_number, + const char *ok_text, + const char *cancel_text, + const int transient_for, + GSList *columns, + GSList *entries) +{ + GError *error = NULL; + char *screen_number_text = g_strdup_printf("%d", screen_number); + GSList *tmp; + int i=0; + GPid child_pid; + const char **argvl = g_malloc(sizeof (char*) * + (17 + + g_slist_length (columns)*2 + + g_slist_length (entries))); + + argvl[i++] = "matedialog"; + argvl[i++] = type; + argvl[i++] = "--screen"; + argvl[i++] = screen_number_text; + argvl[i++] = "--class"; + argvl[i++] = "marco-dialog"; + argvl[i++] = "--title"; + /* Translators: This is the title used on dialog boxes */ + argvl[i++] = _("Marco"); + argvl[i++] = "--text"; + argvl[i++] = message; + + if (timeout) + { + argvl[i++] = "--timeout"; + argvl[i++] = timeout; + } + + if (ok_text) + { + argvl[i++] = "--ok-label"; + argvl[i++] = ok_text; + } + + if (cancel_text) + { + argvl[i++] = "--cancel-label"; + argvl[i++] = cancel_text; + } + + tmp = columns; + while (tmp) + { + argvl[i++] = "--column"; + argvl[i++] = tmp->data; + tmp = tmp->next; + } + + tmp = entries; + while (tmp) + { + argvl[i++] = tmp->data; + tmp = tmp->next; + } + + argvl[i] = NULL; + + if (transient_for) + { + gchar *env = g_strdup_printf("%d", transient_for); + setenv ("WINDOWID", env, 1); + g_free (env); + } + + g_spawn_async ( + "/", + (gchar**) argvl, /* ugh */ + NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, + &child_pid, + &error + ); + + if (transient_for) + unsetenv ("WINDOWID"); + + g_free (argvl); + g_free (screen_number_text); + + if (error) + { + meta_warning ("%s\n", error->message); + g_error_free (error); + } + + return child_pid; +} +/* eof util.c */ + diff --git a/src/core/window-private.h b/src/core/window-private.h new file mode 100644 index 00000000..1a966275 --- /dev/null +++ b/src/core/window-private.h @@ -0,0 +1,640 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file window-private.h Windows which Marco manages + * + * Managing X windows. + * This file contains methods on this class which are available to + * routines in core but not outside it. (See window.h for the routines + * which the rest of the world is allowed to use.) + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2003, 2004 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_WINDOW_PRIVATE_H +#define META_WINDOW_PRIVATE_H + +#include <config.h> +#include "window.h" +#include "screen-private.h" +#include "util.h" +#include "stack.h" +#include "iconcache.h" +#include <X11/Xutil.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +typedef struct _MetaGroup MetaGroup; +typedef struct _MetaWindowQueue MetaWindowQueue; + +typedef gboolean (*MetaWindowForeachFunc) (MetaWindow *window, + void *data); + +typedef enum +{ + META_WINDOW_NORMAL, + META_WINDOW_DESKTOP, + META_WINDOW_DOCK, + META_WINDOW_DIALOG, + META_WINDOW_MODAL_DIALOG, + META_WINDOW_TOOLBAR, + META_WINDOW_MENU, + META_WINDOW_UTILITY, + META_WINDOW_SPLASHSCREEN +} MetaWindowType; + +typedef enum +{ + META_MAXIMIZE_HORIZONTAL = 1 << 0, + META_MAXIMIZE_VERTICAL = 1 << 1 +} MetaMaximizeFlags; + +typedef enum { + META_CLIENT_TYPE_UNKNOWN = 0, + META_CLIENT_TYPE_APPLICATION = 1, + META_CLIENT_TYPE_PAGER = 2, + META_CLIENT_TYPE_MAX_RECOGNIZED = 2 +} MetaClientType; + +typedef enum { + META_QUEUE_CALC_SHOWING = 1 << 0, + META_QUEUE_MOVE_RESIZE = 1 << 1, + META_QUEUE_UPDATE_ICON = 1 << 2, +} MetaQueueType; + +#define NUMBER_OF_QUEUES 3 + +struct _MetaWindow +{ + MetaDisplay *display; + MetaScreen *screen; + MetaWorkspace *workspace; + Window xwindow; + /* may be NULL! not all windows get decorated */ + MetaFrame *frame; + int depth; + Visual *xvisual; + Colormap colormap; + char *desc; /* used in debug spew */ + char *title; + + char *icon_name; + GdkPixbuf *icon; + GdkPixbuf *mini_icon; + MetaIconCache icon_cache; + Pixmap wm_hints_pixmap; + Pixmap wm_hints_mask; + + MetaWindowType type; + Atom type_atom; + + /* NOTE these five are not in UTF-8, we just treat them as random + * binary data + */ + char *res_class; + char *res_name; + char *role; + char *sm_client_id; + char *wm_client_machine; + char *startup_id; + + int net_wm_pid; + + Window xtransient_for; + Window xgroup_leader; + Window xclient_leader; + + /* Initial workspace property */ + int initial_workspace; + + /* Initial timestamp property */ + guint32 initial_timestamp; + + /* Whether we're maximized */ + guint maximized_horizontally : 1; + guint maximized_vertically : 1; + + /* Whether we have to maximize/minimize after placement */ + guint maximize_horizontally_after_placement : 1; + guint maximize_vertically_after_placement : 1; + guint minimize_after_placement : 1; + + /* Whether we're shaded */ + guint shaded : 1; + + /* Whether we're fullscreen */ + guint fullscreen : 1; + + /* Whether we have to fullscreen after placement */ + guint fullscreen_after_placement : 1; + + /* Area to cover when in fullscreen mode. If _NET_WM_FULLSCREEN_MONITORS has + * been overridden (via a client message), the window will cover the union of + * these monitors. If not, this is the single monitor which the window's + * origin is on. */ + long fullscreen_monitors[4]; + + /* Whether we're trying to constrain the window to be fully onscreen */ + guint require_fully_onscreen : 1; + + /* Whether we're trying to constrain the window to be on a single xinerama */ + guint require_on_single_xinerama : 1; + + /* Whether we're trying to constrain the window's titlebar to be onscreen */ + guint require_titlebar_visible : 1; + + /* Whether we're sticky in the multi-workspace sense + * (vs. the not-scroll-with-viewport sense, we don't + * have no stupid viewports) + */ + guint on_all_workspaces : 1; + + /* Minimize is the state controlled by the minimize button */ + guint minimized : 1; + guint was_minimized : 1; + guint tab_unminimized : 1; + + /* Whether the window is mapped; actual server-side state + * see also unmaps_pending + */ + guint mapped : 1; + + /* Iconic is the state in WM_STATE; happens for workspaces/shading + * in addition to minimize + */ + guint iconic : 1; + /* initially_iconic is the WM_HINTS setting when we first manage + * the window. It's taken to mean initially minimized. + */ + guint initially_iconic : 1; + + /* whether an initial workspace was explicitly set */ + guint initial_workspace_set : 1; + + /* whether an initial timestamp was explicitly set */ + guint initial_timestamp_set : 1; + + /* whether net_wm_user_time has been set yet */ + guint net_wm_user_time_set : 1; + + /* These are the flags from WM_PROTOCOLS */ + guint take_focus : 1; + guint delete_window : 1; + guint net_wm_ping : 1; + /* Globally active / No input */ + guint input : 1; + + /* MWM hints about features of window */ + guint mwm_decorated : 1; + guint mwm_border_only : 1; + guint mwm_has_close_func : 1; + guint mwm_has_minimize_func : 1; + guint mwm_has_maximize_func : 1; + guint mwm_has_move_func : 1; + guint mwm_has_resize_func : 1; + + /* Computed features of window */ + guint decorated : 1; + guint border_only : 1; + guint always_sticky : 1; + guint has_close_func : 1; + guint has_minimize_func : 1; + guint has_maximize_func : 1; + guint has_shade_func : 1; + guint has_move_func : 1; + guint has_resize_func : 1; + guint has_fullscreen_func : 1; + + /* Weird "_NET_WM_STATE_MODAL" flag */ + guint wm_state_modal : 1; + + /* TRUE if the client forced these on */ + guint wm_state_skip_taskbar : 1; + guint wm_state_skip_pager : 1; + + /* Computed whether to skip taskbar or not */ + guint skip_taskbar : 1; + guint skip_pager : 1; + + /* TRUE if client set these */ + guint wm_state_above : 1; + guint wm_state_below : 1; + + /* EWHH demands attention flag */ + guint wm_state_demands_attention : 1; + + /* this flag tracks receipt of focus_in focus_out and + * determines whether we draw the focus + */ + guint has_focus : 1; + + /* Have we placed this window? */ + guint placed : 1; + + /* Must we force_save_user_window_placement? */ + guint force_save_user_rect : 1; + + /* Is this not a transient of the focus window which is being denied focus? */ + guint denied_focus_and_not_transient : 1; + + /* Has this window not ever been shown yet? */ + guint showing_for_first_time : 1; + + /* Are we in meta_window_free()? */ + guint unmanaging : 1; + + /* Are we in meta_window_new()? */ + guint constructing : 1; + + /* Are we in the various queues? (Bitfield: see META_WINDOW_IS_IN_QUEUE) */ + guint is_in_queues : NUMBER_OF_QUEUES; + + /* Used by keybindings.c */ + guint keys_grabbed : 1; /* normal keybindings grabbed */ + guint grab_on_frame : 1; /* grabs are on the frame */ + guint all_keys_grabbed : 1; /* AnyKey grabbed */ + + /* Set if the reason for unmanaging the window is that + * it was withdrawn + */ + guint withdrawn : 1; + + /* TRUE if constrain_position should calc placement. + * only relevant if !window->placed + */ + guint calc_placement : 1; + + /* Transient parent is a root window */ + guint transient_parent_is_root_window : 1; + + /* Info on which props we got our attributes from */ + guint using_net_wm_name : 1; /* vs. plain wm_name */ + guint using_net_wm_visible_name : 1; /* tracked so we can clear it */ + guint using_net_wm_icon_name : 1; /* vs. plain wm_icon_name */ + guint using_net_wm_visible_icon_name : 1; /* tracked so we can clear it */ + + /* has a shape mask */ + guint has_shape : 1; + + /* icon props have changed */ + guint need_reread_icon : 1; + + /* if TRUE, window was maximized at start of current grab op */ + guint shaken_loose : 1; + + /* if TRUE we have a grab on the focus click buttons */ + guint have_focus_click_grab : 1; + + /* if TRUE, application is buggy and SYNC resizing is turned off */ + guint disable_sync : 1; + + /* Note: can be NULL */ + GSList *struts; + +#ifdef HAVE_XSYNC + /* XSync update counter */ + XSyncCounter sync_request_counter; + guint sync_request_serial; + GTimeVal sync_request_time; +#endif + + /* Number of UnmapNotify that are caused by us, if + * we get UnmapNotify with none pending then the client + * is withdrawing the window. + */ + int unmaps_pending; + + /* set to the most recent user-interaction event timestamp that we + know about for this window */ + guint32 net_wm_user_time; + + /* window that gets updated net_wm_user_time values */ + Window user_time_window; + + /* The size we set the window to last (i.e. what we believe + * to be its actual size on the server). The x, y are + * the actual server-side x,y so are relative to the frame + * (meaning that they just hold the frame width and height) + * or the root window (meaning they specify the location + * of the top left of the inner window) as appropriate. + */ + MetaRectangle rect; + + /* The geometry to restore when we unmaximize. The position is in + * root window coords, even if there's a frame, which contrasts with + * window->rect above. Note that this gives the position and size + * of the client window (i.e. ignoring the frame). + */ + MetaRectangle saved_rect; + + /* This is the geometry the window had after the last user-initiated + * move/resize operations. We use this whenever we are moving the + * implicitly (for example, if we move to avoid a panel, we can snap + * back to this position if the panel moves again). Note that this + * gives the position and size of the client window (i.e. ignoring + * the frame). + * + * Position valid if user_has_moved, size valid if user_has_resized + * + * Position always in root coords, unlike window->rect. + */ + MetaRectangle user_rect; + + /* Requested geometry */ + int border_width; + /* x/y/w/h here get filled with ConfigureRequest values */ + XSizeHints size_hints; + + /* Managed by stack.c */ + MetaStackLayer layer; + int stack_position; /* see comment in stack.h */ + + /* Current dialog open for this window */ + int dialog_pid; + + /* maintained by group.c */ + MetaGroup *group; +}; + +/* These differ from window->has_foo_func in that they consider + * the dynamic window state such as "maximized", not just the + * window's type + */ +#define META_WINDOW_MAXIMIZED(w) ((w)->maximized_horizontally && \ + (w)->maximized_vertically) +#define META_WINDOW_MAXIMIZED_VERTICALLY(w) ((w)->maximized_vertically) +#define META_WINDOW_MAXIMIZED_HORIZONTALLY(w) ((w)->maximized_horizontally) +#define META_WINDOW_ALLOWS_MOVE(w) ((w)->has_move_func && !(w)->fullscreen) +#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !(w)->fullscreen && !(w)->shaded) +#define META_WINDOW_ALLOWS_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && \ + (((w)->size_hints.min_width < (w)->size_hints.max_width) || \ + ((w)->size_hints.min_height < (w)->size_hints.max_height))) +#define META_WINDOW_ALLOWS_HORIZONTAL_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && (w)->size_hints.min_width < (w)->size_hints.max_width) +#define META_WINDOW_ALLOWS_VERTICAL_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && (w)->size_hints.min_height < (w)->size_hints.max_height) + +MetaWindow* meta_window_new (MetaDisplay *display, + Window xwindow, + gboolean must_be_viewable); +MetaWindow* meta_window_new_with_attrs (MetaDisplay *display, + Window xwindow, + gboolean must_be_viewable, + XWindowAttributes *attrs); +void meta_window_free (MetaWindow *window, + guint32 timestamp); +void meta_window_calc_showing (MetaWindow *window); +void meta_window_queue (MetaWindow *window, + guint queuebits); +void meta_window_minimize (MetaWindow *window); +void meta_window_unminimize (MetaWindow *window); +void meta_window_maximize (MetaWindow *window, + MetaMaximizeFlags directions); +void meta_window_maximize_internal (MetaWindow *window, + MetaMaximizeFlags directions, + MetaRectangle *saved_rect); +void meta_window_unmaximize (MetaWindow *window, + MetaMaximizeFlags directions); +void meta_window_make_above (MetaWindow *window); +void meta_window_unmake_above (MetaWindow *window); +void meta_window_shade (MetaWindow *window, + guint32 timestamp); +void meta_window_unshade (MetaWindow *window, + guint32 timestamp); +void meta_window_change_workspace (MetaWindow *window, + MetaWorkspace *workspace); +void meta_window_stick (MetaWindow *window); +void meta_window_unstick (MetaWindow *window); + +void meta_window_activate (MetaWindow *window, + guint32 current_time); +void meta_window_activate_with_workspace (MetaWindow *window, + guint32 current_time, + MetaWorkspace *workspace); +void meta_window_make_fullscreen_internal (MetaWindow *window); +void meta_window_make_fullscreen (MetaWindow *window); +void meta_window_unmake_fullscreen (MetaWindow *window); +void meta_window_update_fullscreen_monitors (MetaWindow *window, + unsigned long top, + unsigned long bottom, + unsigned long left, + unsigned long right); + +/* args to move are window pos, not frame pos */ +void meta_window_move (MetaWindow *window, + gboolean user_op, + int root_x_nw, + int root_y_nw); +void meta_window_resize (MetaWindow *window, + gboolean user_op, + int w, + int h); +void meta_window_move_resize (MetaWindow *window, + gboolean user_op, + int root_x_nw, + int root_y_nw, + int w, + int h); +void meta_window_resize_with_gravity (MetaWindow *window, + gboolean user_op, + int w, + int h, + int gravity); + + +/* Return whether the window would be showing if we were on its workspace */ +gboolean meta_window_showing_on_its_workspace (MetaWindow *window); + +/* Return whether the window should be currently mapped */ +gboolean meta_window_should_be_showing (MetaWindow *window); + +/* See warning in window.c about this function */ +gboolean __window_is_terminal (MetaWindow *window); + +void meta_window_update_struts (MetaWindow *window); + +/* this gets root coords */ +void meta_window_get_position (MetaWindow *window, + int *x, + int *y); + +/* Gets root coords for x, y, width & height of client window; uses + * meta_window_get_position for x & y. + */ +void meta_window_get_client_root_coords (MetaWindow *window, + MetaRectangle *rect); + +/* gets position we need to set to stay in current position, + * assuming position will be gravity-compensated. i.e. + * this is the position a client would send in a configure + * request. + */ +void meta_window_get_gravity_position (MetaWindow *window, + int gravity, + int *x, + int *y); +/* Get geometry for saving in the session; x/y are gravity + * position, and w/h are in resize inc above the base size. + */ +void meta_window_get_geometry (MetaWindow *window, + int *x, + int *y, + int *width, + int *height); +void meta_window_get_outer_rect (const MetaWindow *window, + MetaRectangle *rect); +void meta_window_get_xor_rect (MetaWindow *window, + const MetaRectangle *grab_wireframe_rect, + MetaRectangle *xor_rect); +void meta_window_begin_wireframe (MetaWindow *window); +void meta_window_update_wireframe (MetaWindow *window, + int x, + int y, + int width, + int height); +void meta_window_end_wireframe (MetaWindow *window); + +void meta_window_delete (MetaWindow *window, + guint32 timestamp); +void meta_window_kill (MetaWindow *window); +void meta_window_focus (MetaWindow *window, + guint32 timestamp); +void meta_window_raise (MetaWindow *window); +void meta_window_lower (MetaWindow *window); + +void meta_window_update_unfocused_button_grabs (MetaWindow *window); + +/* Sends a client message */ +void meta_window_send_icccm_message (MetaWindow *window, + Atom atom, + guint32 timestamp); + + +void meta_window_move_resize_request(MetaWindow *window, + guint value_mask, + int gravity, + int x, + int y, + int width, + int height); +gboolean meta_window_configure_request (MetaWindow *window, + XEvent *event); +gboolean meta_window_property_notify (MetaWindow *window, + XEvent *event); +gboolean meta_window_client_message (MetaWindow *window, + XEvent *event); +gboolean meta_window_notify_focus (MetaWindow *window, + XEvent *event); + +void meta_window_set_current_workspace_hint (MetaWindow *window); + +unsigned long meta_window_get_net_wm_desktop (MetaWindow *window); + +void meta_window_show_menu (MetaWindow *window, + int root_x, + int root_y, + int button, + guint32 timestamp); + +gboolean meta_window_titlebar_is_onscreen (MetaWindow *window); +void meta_window_shove_titlebar_onscreen (MetaWindow *window); + +void meta_window_set_gravity (MetaWindow *window, + int gravity); + +void meta_window_handle_mouse_grab_op_event (MetaWindow *window, + XEvent *event); + +GList* meta_window_get_workspaces (MetaWindow *window); + +gboolean meta_window_located_on_workspace (MetaWindow *window, + MetaWorkspace *workspace); + +void meta_window_get_work_area_current_xinerama (MetaWindow *window, + MetaRectangle *area); +void meta_window_get_work_area_for_xinerama (MetaWindow *window, + int which_xinerama, + MetaRectangle *area); +void meta_window_get_work_area_all_xineramas (MetaWindow *window, + MetaRectangle *area); + + +gboolean meta_window_same_application (MetaWindow *window, + MetaWindow *other_window); + +#define META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE(w) \ + ((w)->type != META_WINDOW_DOCK && (w)->type != META_WINDOW_DESKTOP) +#define META_WINDOW_IN_NORMAL_TAB_CHAIN(w) \ + (((w)->input || (w)->take_focus ) && META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w) && (!(w)->skip_taskbar)) +#define META_WINDOW_IN_DOCK_TAB_CHAIN(w) \ + (((w)->input || (w)->take_focus) && (! META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w) || (w)->skip_taskbar)) +#define META_WINDOW_IN_GROUP_TAB_CHAIN(w, g) \ + (((w)->input || (w)->take_focus) && (!g || meta_window_get_group(w)==g)) + +void meta_window_refresh_resize_popup (MetaWindow *window); + +void meta_window_free_delete_dialog (MetaWindow *window); + +void meta_window_foreach_transient (MetaWindow *window, + MetaWindowForeachFunc func, + void *data); +gboolean meta_window_is_ancestor_of_transient (MetaWindow *window, + MetaWindow *transient); +void meta_window_foreach_ancestor (MetaWindow *window, + MetaWindowForeachFunc func, + void *data); +MetaWindow* meta_window_find_root_ancestor (MetaWindow *window); + + +void meta_window_begin_grab_op (MetaWindow *window, + MetaGrabOp op, + gboolean frame_action, + guint32 timestamp); + +void meta_window_update_keyboard_resize (MetaWindow *window, + gboolean update_cursor); +void meta_window_update_keyboard_move (MetaWindow *window); + +void meta_window_update_layer (MetaWindow *window); + +gboolean meta_window_get_icon_geometry (MetaWindow *window, + MetaRectangle *rect); + +const char* meta_window_get_startup_id (MetaWindow *window); + +void meta_window_recalc_features (MetaWindow *window); +void meta_window_recalc_window_type (MetaWindow *window); + +void meta_window_stack_just_below (MetaWindow *window, + MetaWindow *below_this_one); + +void meta_window_set_user_time (MetaWindow *window, + guint32 timestamp); + +void meta_window_set_demands_attention (MetaWindow *window); + +void meta_window_unset_demands_attention (MetaWindow *window); + +void meta_window_update_icon_now (MetaWindow *window); + +void meta_window_update_role (MetaWindow *window); +void meta_window_update_net_wm_type (MetaWindow *window); + +#endif diff --git a/src/core/window-props.c b/src/core/window-props.c new file mode 100644 index 00000000..b7b3e12d --- /dev/null +++ b/src/core/window-props.c @@ -0,0 +1,1553 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file window-props.c MetaWindow property handling + * + * A system which can inspect sets of properties of given windows + * and take appropriate action given their values. + * + * Note that all the meta_window_reload_propert* functions require a + * round trip to the server. + * + * The guts of this system are in meta_display_init_window_prop_hooks(). + * Reading this function will give you insight into how this all fits + * together. + */ + +/* + * Copyright (C) 2001, 2002, 2003 Red Hat, Inc. + * Copyright (C) 2004, 2005 Elijah Newren + * Copyright (C) 2009 Thomas Thurman + * + * 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. + */ + +#define _GNU_SOURCE +#define _SVID_SOURCE /* for gethostname() */ + +#include <config.h> +#include "window-props.h" +#include "errors.h" +#include "xprops.h" +#include "frame-private.h" +#include "group.h" +#include <X11/Xatom.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <pwd.h> + +#ifdef HAVE_GTOP +#include <glibtop/procuid.h> +#include <errno.h> +#include <pwd.h> +#endif /* HAVE_GTOP */ + +#ifndef HOST_NAME_MAX +/* Solaris headers apparently don't define this so do so manually; #326745 */ +#define HOST_NAME_MAX 255 +#endif + +typedef void (* ReloadValueFunc) (MetaWindow *window, + MetaPropValue *value, + gboolean initial); + +typedef struct MetaWindowPropHooks +{ + Atom property; + MetaPropValueType type; + ReloadValueFunc reload_func; +} MetaWindowPropHooks; + +static MetaWindowPropHooks* find_hooks (MetaDisplay *display, + Atom property); + + +void +meta_window_reload_property (MetaWindow *window, + Atom property, + gboolean initial) +{ + meta_window_reload_properties (window, &property, 1, initial); +} + +void +meta_window_reload_properties (MetaWindow *window, + const Atom *properties, + int n_properties, + gboolean initial) +{ + meta_window_reload_properties_from_xwindow (window, + window->xwindow, + properties, + n_properties, + initial); +} + +void +meta_window_reload_property_from_xwindow (MetaWindow *window, + Window xwindow, + Atom property, + gboolean initial) +{ + meta_window_reload_properties_from_xwindow (window, xwindow, &property, 1, + initial); +} + +void +meta_window_reload_properties_from_xwindow (MetaWindow *window, + Window xwindow, + const Atom *properties, + int n_properties, + gboolean initial) +{ + int i; + MetaPropValue *values; + + g_return_if_fail (properties != NULL); + g_return_if_fail (n_properties > 0); + + values = g_new0 (MetaPropValue, n_properties); + + for (i=0; i<n_properties; i++) + { + MetaWindowPropHooks *hooks = find_hooks (window->display, + properties[i]); + + if (!hooks || hooks->type == META_PROP_VALUE_INVALID) + { + values[i].type = META_PROP_VALUE_INVALID; + values[i].atom = None; + } + else + { + values[i].type = hooks->type; + values[i].atom = properties[i]; + } + } + + meta_prop_get_values (window->display, xwindow, + values, n_properties); + + for (i=0; i<n_properties; i++) + { + MetaWindowPropHooks *hooks = find_hooks (window->display, + properties[i]); + + if (hooks && hooks->reload_func != NULL) + (* hooks->reload_func) (window, &values[i], initial); + } + + meta_prop_free_values (values, n_properties); + + g_free (values); +} + +static void +reload_wm_client_machine (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + g_free (window->wm_client_machine); + window->wm_client_machine = NULL; + + if (value->type != META_PROP_VALUE_INVALID) + window->wm_client_machine = g_strdup (value->v.str); + + meta_verbose ("Window has client machine \"%s\"\n", + window->wm_client_machine ? window->wm_client_machine : "unset"); +} + +static void +complain_about_broken_client (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + meta_warning ("Broken client! Window %s changed client leader window or SM client ID\n", + window->desc); +} + +static void +reload_net_wm_window_type (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + meta_window_update_net_wm_type (window); +} + +static void +reload_icon (MetaWindow *window, + Atom atom) +{ + meta_icon_cache_property_changed (&window->icon_cache, + window->display, + atom); + meta_window_queue(window, META_QUEUE_UPDATE_ICON); +} + +static void +reload_net_wm_icon (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + reload_icon (window, window->display->atom__NET_WM_ICON); +} + +static void +reload_kwm_win_icon (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + reload_icon (window, window->display->atom__KWM_WIN_ICON); +} + +static void +reload_struts (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + meta_window_update_struts (window); +} + +static void +reload_wm_window_role (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + meta_window_update_role (window); +} + +static void +reload_net_wm_pid (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + gulong cardinal = (int) value->v.cardinal; + + if (cardinal <= 0) + meta_warning (_("Application set a bogus _NET_WM_PID %lu\n"), + cardinal); + else + { + window->net_wm_pid = cardinal; + meta_verbose ("Window has _NET_WM_PID %d\n", + window->net_wm_pid); + } + } +} + +static void +reload_net_wm_user_time (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + gulong cardinal = value->v.cardinal; + meta_window_set_user_time (window, cardinal); + } +} + +static void +reload_net_wm_user_time_window (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + /* Unregister old NET_WM_USER_TIME_WINDOW */ + if (window->user_time_window != None) + { + /* See the comment to the meta_display_register_x_window call below. */ + meta_display_unregister_x_window (window->display, + window->user_time_window); + /* Don't get events on not-managed windows */ + XSelectInput (window->display->xdisplay, + window->user_time_window, + NoEventMask); + } + + + /* Obtain the new NET_WM_USER_TIME_WINDOW and register it */ + window->user_time_window = value->v.xwindow; + if (window->user_time_window != None) + { + /* Kind of a hack; display.c:event_callback() ignores events + * for unknown windows. We make window->user_time_window + * known by registering it with window (despite the fact + * that window->xwindow is already registered with window). + * This basically means that property notifies to either the + * window->user_time_window or window->xwindow will be + * treated identically and will result in functions for + * window being called to update it. Maybe we should ignore + * any property notifies to window->user_time_window other + * than atom__NET_WM_USER_TIME ones, but I just don't care + * and it's not specified in the spec anyway. + */ + meta_display_register_x_window (window->display, + &window->user_time_window, + window); + /* Just listen for property notify events */ + XSelectInput (window->display->xdisplay, + window->user_time_window, + PropertyChangeMask); + + /* Manually load the _NET_WM_USER_TIME field from the given window + * at this time as well. If the user_time_window ever broadens in + * scope, we'll probably want to load all relevant properties here. + */ + meta_window_reload_property_from_xwindow ( + window, + window->user_time_window, + window->display->atom__NET_WM_USER_TIME, + initial); + } + } +} + +/** + * Finds who owns a particular process, if we can. + * + * \param process The process's ID. + * \result Set to the ID of the user, if we returned true. + * + * \result True if we could tell. + */ +static gboolean +owner_of_process (pid_t process, uid_t *result) +{ +#ifdef HAVE_GTOP + glibtop_proc_uid process_details; + + glibtop_get_proc_uid (&process_details, process); + + *result = process_details.uid; + return TRUE; +#else + /* I don't know, maybe we could do something hairy like see whether + * /proc/$PID exists and who owns it, in case they have procfs. + */ + return FALSE; +#endif /* HAVE_GTOP */ +} + +#define MAX_TITLE_LENGTH 512 + +/** + * Called by set_window_title and set_icon_title to set the value of + * *target to title. It required and atom is set, it will update the + * appropriate property. + * + * Returns TRUE if a new title was set. + */ +static gboolean +set_title_text (MetaWindow *window, + gboolean previous_was_modified, + const char *title, + Atom atom, + char **target) +{ + char hostname[HOST_NAME_MAX + 1]; + gboolean modified = FALSE; + + if (!target) + return FALSE; + + g_free (*target); + + if (!title) + *target = g_strdup (""); + else if (g_utf8_strlen (title, MAX_TITLE_LENGTH + 1) > MAX_TITLE_LENGTH) + { + *target = meta_g_utf8_strndup (title, MAX_TITLE_LENGTH); + modified = TRUE; + } + /* if WM_CLIENT_MACHINE indicates this machine is on a remote host + * let's place that hostname in the title */ + else if (window->wm_client_machine && + !gethostname (hostname, HOST_NAME_MAX + 1) && + strcmp (hostname, window->wm_client_machine)) + { + /* Translators: the title of a window from another machine */ + *target = g_strdup_printf (_("%s (on %s)"), + title, window->wm_client_machine); + modified = TRUE; + } + else if (window->net_wm_pid != -1) + { + /* We know the process which owns this window; perhaps we can + * find out the name of its owner (if it's not us). + */ + + char *found_name = NULL; + + uid_t window_owner = 0; + gboolean window_owner_known = + owner_of_process (window->net_wm_pid, &window_owner); + + /* Assume a window with unknown ownership is ours (call it usufruct!) */ + gboolean window_owner_is_us = + !window_owner_known || window_owner==getuid (); + + if (window_owner_is_us) + { + /* we own it, so fall back to the simple case */ + *target = g_strdup (title); + } + else + { + /* it belongs to window_owner. So what's their name? */ + + if (window_owner==0) + { + /* Simple case-- don't bother to look it up. It's root. */ + *target = g_strdup_printf (_("%s (as superuser)"), + title); + } + else + { + /* Okay, let's look up the name. */ + struct passwd *pwd; + + errno = 0; + pwd = getpwuid (window_owner); + if (errno==0 && pwd!=NULL) + { + found_name = pwd->pw_name; + } + + if (found_name) + /* Translators: the title of a window owned by another user + * on this machine */ + *target = g_strdup_printf (_("%s (as %s)"), + title, + found_name); + else + /* Translators: the title of a window owned by another user + * on this machine, whose name we don't know */ + *target = g_strdup_printf (_("%s (as another user)"), + title); + } + /* either way we changed it */ + modified = TRUE; + + } + } + else + *target = g_strdup (title); + + if (modified && atom != None) + meta_prop_set_utf8_string_hint (window->display, + window->xwindow, + atom, *target); + + /* Bug 330671 -- Don't forget to clear _NET_WM_VISIBLE_(ICON_)NAME */ + if (!modified && previous_was_modified) + { + meta_error_trap_push (window->display); + XDeleteProperty (window->display->xdisplay, + window->xwindow, + atom); + meta_error_trap_pop (window->display, FALSE); + } + + return modified; +} + +static void +set_window_title (MetaWindow *window, + const char *title) +{ + char *str; + + gboolean modified = + set_title_text (window, + window->using_net_wm_visible_name, + title, + window->display->atom__NET_WM_VISIBLE_NAME, + &window->title); + window->using_net_wm_visible_name = modified; + + /* strndup is a hack since GNU libc has broken %.10s */ + str = g_strndup (window->title, 10); + g_free (window->desc); + window->desc = g_strdup_printf ("0x%lx (%s)", window->xwindow, str); + g_free (str); + + if (window->frame) + meta_ui_set_frame_title (window->screen->ui, + window->frame->xwindow, + window->title); +} + +static void +reload_net_wm_name (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + set_window_title (window, value->v.str); + window->using_net_wm_name = TRUE; + + meta_verbose ("Using _NET_WM_NAME for new title of %s: \"%s\"\n", + window->desc, window->title); + } + else + { + set_window_title (window, NULL); + window->using_net_wm_name = FALSE; + if (!initial) + meta_window_reload_property (window, XA_WM_NAME, FALSE); + } +} + +static void +reload_wm_name (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (window->using_net_wm_name) + { + meta_verbose ("Ignoring WM_NAME \"%s\" as _NET_WM_NAME is set\n", + value->v.str); + return; + } + + if (value->type != META_PROP_VALUE_INVALID) + { + set_window_title (window, value->v.str); + + meta_verbose ("Using WM_NAME for new title of %s: \"%s\"\n", + window->desc, window->title); + } + else + { + set_window_title (window, NULL); + } +} + +static void +set_icon_title (MetaWindow *window, + const char *title) +{ + gboolean modified = + set_title_text (window, + window->using_net_wm_visible_icon_name, + title, + window->display->atom__NET_WM_VISIBLE_ICON_NAME, + &window->icon_name); + window->using_net_wm_visible_icon_name = modified; +} + +static void +reload_net_wm_icon_name (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + set_icon_title (window, value->v.str); + window->using_net_wm_icon_name = TRUE; + + meta_verbose ("Using _NET_WM_ICON_NAME for new title of %s: \"%s\"\n", + window->desc, window->title); + } + else + { + set_icon_title (window, NULL); + window->using_net_wm_icon_name = FALSE; + if (!initial) + meta_window_reload_property (window, XA_WM_ICON_NAME, FALSE); + } +} + +static void +reload_wm_icon_name (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (window->using_net_wm_icon_name) + { + meta_verbose ("Ignoring WM_ICON_NAME \"%s\" as _NET_WM_ICON_NAME is set\n", + value->v.str); + return; + } + + if (value->type != META_PROP_VALUE_INVALID) + { + set_icon_title (window, value->v.str); + + meta_verbose ("Using WM_ICON_NAME for new title of %s: \"%s\"\n", + window->desc, window->title); + } + else + { + set_icon_title (window, NULL); + } +} + +static void +reload_net_wm_state (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + int i; + + /* We know this is only an initial window creation, + * clients don't change the property. + */ + + if (!initial) { + /* no, they DON'T change the property */ + meta_verbose ("Ignoring _NET_WM_STATE: we should be the one who set " + "the property in the first place\n"); + return; + } + + window->shaded = FALSE; + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; + window->fullscreen = FALSE; + window->wm_state_modal = FALSE; + window->wm_state_skip_taskbar = FALSE; + window->wm_state_skip_pager = FALSE; + window->wm_state_above = FALSE; + window->wm_state_below = FALSE; + window->wm_state_demands_attention = FALSE; + + if (value->type == META_PROP_VALUE_INVALID) + return; + + i = 0; + while (i < value->v.atom_list.n_atoms) + { + if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SHADED) + window->shaded = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MAXIMIZED_HORZ) + window->maximize_horizontally_after_placement = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MAXIMIZED_VERT) + window->maximize_vertically_after_placement = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_HIDDEN) + window->minimize_after_placement = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MODAL) + window->wm_state_modal = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SKIP_TASKBAR) + window->wm_state_skip_taskbar = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SKIP_PAGER) + window->wm_state_skip_pager = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_FULLSCREEN) + window->fullscreen_after_placement = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_ABOVE) + window->wm_state_above = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_BELOW) + window->wm_state_below = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_DEMANDS_ATTENTION) + window->wm_state_demands_attention = TRUE; + else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_STICKY) + window->on_all_workspaces = TRUE; + + ++i; + } + + meta_verbose ("Reloaded _NET_WM_STATE for %s\n", + window->desc); + + meta_window_recalc_window_type (window); +} + +static void +reload_mwm_hints (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + MotifWmHints *hints; + + window->mwm_decorated = TRUE; + window->mwm_border_only = FALSE; + 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; + + if (value->type == META_PROP_VALUE_INVALID) + { + meta_verbose ("Window %s has no MWM hints\n", window->desc); + meta_window_recalc_features (window); + return; + } + + hints = value->v.motif_hints; + + /* We support those MWM hints deemed non-stupid */ + + meta_verbose ("Window %s has MWM hints\n", + window->desc); + + if (hints->flags & MWM_HINTS_DECORATIONS) + { + meta_verbose ("Window %s sets MWM_HINTS_DECORATIONS 0x%lx\n", + window->desc, hints->decorations); + + if (hints->decorations == 0) + window->mwm_decorated = FALSE; + /* some input methods use this */ + else if (hints->decorations == MWM_DECOR_BORDER) + window->mwm_border_only = TRUE; + } + else + meta_verbose ("Decorations flag unset\n"); + + if (hints->flags & MWM_HINTS_FUNCTIONS) + { + gboolean toggle_value; + + meta_verbose ("Window %s sets MWM_HINTS_FUNCTIONS 0x%lx\n", + window->desc, hints->functions); + + /* If _ALL is specified, then other flags indicate what to turn off; + * if ALL is not specified, flags are what to turn on. + * at least, I think so + */ + + if ((hints->functions & MWM_FUNC_ALL) == 0) + { + toggle_value = TRUE; + + meta_verbose ("Window %s disables all funcs then reenables some\n", + window->desc); + window->mwm_has_close_func = FALSE; + window->mwm_has_minimize_func = FALSE; + window->mwm_has_maximize_func = FALSE; + window->mwm_has_move_func = FALSE; + window->mwm_has_resize_func = FALSE; + } + else + { + meta_verbose ("Window %s enables all funcs then disables some\n", + window->desc); + toggle_value = FALSE; + } + + if ((hints->functions & MWM_FUNC_CLOSE) != 0) + { + meta_verbose ("Window %s toggles close via MWM hints\n", + window->desc); + window->mwm_has_close_func = toggle_value; + } + if ((hints->functions & MWM_FUNC_MINIMIZE) != 0) + { + meta_verbose ("Window %s toggles minimize via MWM hints\n", + window->desc); + window->mwm_has_minimize_func = toggle_value; + } + if ((hints->functions & MWM_FUNC_MAXIMIZE) != 0) + { + meta_verbose ("Window %s toggles maximize via MWM hints\n", + window->desc); + window->mwm_has_maximize_func = toggle_value; + } + if ((hints->functions & MWM_FUNC_MOVE) != 0) + { + meta_verbose ("Window %s toggles move via MWM hints\n", + window->desc); + window->mwm_has_move_func = toggle_value; + } + if ((hints->functions & MWM_FUNC_RESIZE) != 0) + { + meta_verbose ("Window %s toggles resize via MWM hints\n", + window->desc); + window->mwm_has_resize_func = toggle_value; + } + } + else + meta_verbose ("Functions flag unset\n"); + + meta_window_recalc_features (window); + + /* We do all this anyhow at the end of meta_window_new() */ + if (!window->constructing) + { + if (window->decorated) + meta_window_ensure_frame (window); + else + meta_window_destroy_frame (window); + + meta_window_queue (window, + META_QUEUE_MOVE_RESIZE | + /* because ensure/destroy frame may unmap: */ + META_QUEUE_CALC_SHOWING); + } +} + +static void +reload_wm_class (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (window->res_class) + g_free (window->res_class); + if (window->res_name) + g_free (window->res_name); + + window->res_class = NULL; + window->res_name = NULL; + + if (value->type != META_PROP_VALUE_INVALID) + { + if (value->v.class_hint.res_name) + window->res_name = g_strdup (value->v.class_hint.res_name); + + if (value->v.class_hint.res_class) + window->res_class = g_strdup (value->v.class_hint.res_class); + } + + meta_verbose ("Window %s class: '%s' name: '%s'\n", + window->desc, + window->res_class ? window->res_class : "none", + window->res_name ? window->res_name : "none"); +} + +static void +reload_net_wm_desktop (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + window->initial_workspace_set = TRUE; + window->initial_workspace = value->v.cardinal; + meta_topic (META_DEBUG_PLACEMENT, + "Read initial workspace prop %d for %s\n", + window->initial_workspace, window->desc); + } +} + +static void +reload_net_startup_id (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + guint32 timestamp = window->net_wm_user_time; + MetaWorkspace *workspace = NULL; + + g_free (window->startup_id); + + if (value->type != META_PROP_VALUE_INVALID) + window->startup_id = g_strdup (value->v.str); + else + window->startup_id = NULL; + + /* Update timestamp and workspace on a running window */ + if (!window->constructing) + { + window->initial_timestamp_set = 0; + window->initial_workspace_set = 0; + + if (meta_screen_apply_startup_properties (window->screen, window)) + { + + if (window->initial_timestamp_set) + timestamp = window->initial_timestamp; + if (window->initial_workspace_set) + workspace = meta_screen_get_workspace_by_index (window->screen, window->initial_workspace); + + meta_window_activate_with_workspace (window, timestamp, workspace); + } + } + + meta_verbose ("New _NET_STARTUP_ID \"%s\" for %s\n", + window->startup_id ? window->startup_id : "unset", + window->desc); +} + +static void +reload_update_counter (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { +#ifdef HAVE_XSYNC + XSyncCounter counter = value->v.xcounter; + + window->sync_request_counter = counter; + meta_verbose ("Window has _NET_WM_SYNC_REQUEST_COUNTER 0x%lx\n", + window->sync_request_counter); +#endif + } +} + +#define FLAG_TOGGLED_ON(old,new,flag) \ + (((old)->flags & (flag)) == 0 && \ + ((new)->flags & (flag)) != 0) + +#define FLAG_TOGGLED_OFF(old,new,flag) \ + (((old)->flags & (flag)) != 0 && \ + ((new)->flags & (flag)) == 0) + +#define FLAG_CHANGED(old,new,flag) \ + (FLAG_TOGGLED_ON(old,new,flag) || FLAG_TOGGLED_OFF(old,new,flag)) + +static void +spew_size_hints_differences (const XSizeHints *old, + const XSizeHints *new) +{ + if (FLAG_CHANGED (old, new, USPosition)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: USPosition now %s\n", + FLAG_TOGGLED_ON (old, new, USPosition) ? "set" : "unset"); + if (FLAG_CHANGED (old, new, USSize)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: USSize now %s\n", + FLAG_TOGGLED_ON (old, new, USSize) ? "set" : "unset"); + if (FLAG_CHANGED (old, new, PPosition)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PPosition now %s\n", + FLAG_TOGGLED_ON (old, new, PPosition) ? "set" : "unset"); + if (FLAG_CHANGED (old, new, PSize)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PSize now %s\n", + FLAG_TOGGLED_ON (old, new, PSize) ? "set" : "unset"); + if (FLAG_CHANGED (old, new, PMinSize)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PMinSize now %s (%d x %d -> %d x %d)\n", + FLAG_TOGGLED_ON (old, new, PMinSize) ? "set" : "unset", + old->min_width, old->min_height, + new->min_width, new->min_height); + if (FLAG_CHANGED (old, new, PMaxSize)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PMaxSize now %s (%d x %d -> %d x %d)\n", + FLAG_TOGGLED_ON (old, new, PMaxSize) ? "set" : "unset", + old->max_width, old->max_height, + new->max_width, new->max_height); + if (FLAG_CHANGED (old, new, PResizeInc)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PResizeInc now %s (width_inc %d -> %d height_inc %d -> %d)\n", + FLAG_TOGGLED_ON (old, new, PResizeInc) ? "set" : "unset", + old->width_inc, new->width_inc, + old->height_inc, new->height_inc); + if (FLAG_CHANGED (old, new, PAspect)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PAspect now %s (min %d/%d -> %d/%d max %d/%d -> %d/%d)\n", + FLAG_TOGGLED_ON (old, new, PAspect) ? "set" : "unset", + old->min_aspect.x, old->min_aspect.y, + new->min_aspect.x, new->min_aspect.y, + old->max_aspect.x, old->max_aspect.y, + new->max_aspect.x, new->max_aspect.y); + if (FLAG_CHANGED (old, new, PBaseSize)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PBaseSize now %s (%d x %d -> %d x %d)\n", + FLAG_TOGGLED_ON (old, new, PBaseSize) ? "set" : "unset", + old->base_width, old->base_height, + new->base_width, new->base_height); + if (FLAG_CHANGED (old, new, PWinGravity)) + meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PWinGravity now %s (%d -> %d)\n", + FLAG_TOGGLED_ON (old, new, PWinGravity) ? "set" : "unset", + old->win_gravity, new->win_gravity); +} + +void +meta_set_normal_hints (MetaWindow *window, + XSizeHints *hints) +{ + int x, y, w, h; + double minr, maxr; + /* Some convenience vars */ + int minw, minh, maxw, maxh; /* min/max width/height */ + int basew, baseh, winc, hinc; /* base width/height, width/height increment */ + + /* Save the last ConfigureRequest, which we put here. + * Values here set in the hints are supposed to + * be ignored. + */ + x = window->size_hints.x; + y = window->size_hints.y; + w = window->size_hints.width; + h = window->size_hints.height; + + /* as far as I can tell, value->v.size_hints.flags is just to + * check whether we had old-style normal hints without gravity, + * base size as returned by XGetNormalHints(), so we don't + * really use it as we fixup window->size_hints to have those + * fields if they're missing. + */ + + /* + * When the window is first created, NULL hints will + * be passed in which will initialize all of the fields + * as if flags were zero + */ + if (hints) + window->size_hints = *hints; + else + window->size_hints.flags = 0; + + /* Put back saved ConfigureRequest. */ + window->size_hints.x = x; + window->size_hints.y = y; + window->size_hints.width = w; + window->size_hints.height = h; + + /* Get base size hints */ + if (window->size_hints.flags & PBaseSize) + { + meta_topic (META_DEBUG_GEOMETRY, "Window %s sets base size %d x %d\n", + window->desc, + window->size_hints.base_width, + window->size_hints.base_height); + } + else if (window->size_hints.flags & PMinSize) + { + window->size_hints.base_width = window->size_hints.min_width; + window->size_hints.base_height = window->size_hints.min_height; + } + else + { + window->size_hints.base_width = 0; + window->size_hints.base_height = 0; + } + window->size_hints.flags |= PBaseSize; + + /* Get min size hints */ + if (window->size_hints.flags & PMinSize) + { + meta_topic (META_DEBUG_GEOMETRY, "Window %s sets min size %d x %d\n", + window->desc, + window->size_hints.min_width, + window->size_hints.min_height); + } + else if (window->size_hints.flags & PBaseSize) + { + window->size_hints.min_width = window->size_hints.base_width; + window->size_hints.min_height = window->size_hints.base_height; + } + else + { + window->size_hints.min_width = 0; + window->size_hints.min_height = 0; + } + window->size_hints.flags |= PMinSize; + + /* Get max size hints */ + if (window->size_hints.flags & PMaxSize) + { + meta_topic (META_DEBUG_GEOMETRY, "Window %s sets max size %d x %d\n", + window->desc, + window->size_hints.max_width, + window->size_hints.max_height); + } + else + { + window->size_hints.max_width = G_MAXINT; + window->size_hints.max_height = G_MAXINT; + window->size_hints.flags |= PMaxSize; + } + + /* Get resize increment hints */ + if (window->size_hints.flags & PResizeInc) + { + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets resize width inc: %d height inc: %d\n", + window->desc, + window->size_hints.width_inc, + window->size_hints.height_inc); + } + else + { + window->size_hints.width_inc = 1; + window->size_hints.height_inc = 1; + window->size_hints.flags |= PResizeInc; + } + + /* Get aspect ratio hints */ + if (window->size_hints.flags & PAspect) + { + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets min_aspect: %d/%d max_aspect: %d/%d\n", + window->desc, + window->size_hints.min_aspect.x, + window->size_hints.min_aspect.y, + window->size_hints.max_aspect.x, + window->size_hints.max_aspect.y); + } + else + { + window->size_hints.min_aspect.x = 1; + window->size_hints.min_aspect.y = G_MAXINT; + window->size_hints.max_aspect.x = G_MAXINT; + window->size_hints.max_aspect.y = 1; + window->size_hints.flags |= PAspect; + } + + /* Get gravity hint */ + if (window->size_hints.flags & PWinGravity) + { + meta_topic (META_DEBUG_GEOMETRY, "Window %s sets gravity %d\n", + window->desc, + window->size_hints.win_gravity); + } + else + { + meta_topic (META_DEBUG_GEOMETRY, + "Window %s doesn't set gravity, using NW\n", + window->desc); + window->size_hints.win_gravity = NorthWestGravity; + window->size_hints.flags |= PWinGravity; + } + + /*** Lots of sanity checking ***/ + + /* Verify all min & max hints are at least 1 pixel */ + if (window->size_hints.min_width < 1) + { + /* someone is on crack */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets min width to 0, which makes no sense\n", + window->desc); + window->size_hints.min_width = 1; + } + if (window->size_hints.max_width < 1) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets max width to 0, which makes no sense\n", + window->desc); + window->size_hints.max_width = 1; + } + if (window->size_hints.min_height < 1) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets min height to 0, which makes no sense\n", + window->desc); + window->size_hints.min_height = 1; + } + if (window->size_hints.max_height < 1) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets max height to 0, which makes no sense\n", + window->desc); + window->size_hints.max_height = 1; + } + + /* Verify size increment hints are at least 1 pixel */ + if (window->size_hints.width_inc < 1) + { + /* app authors find so many ways to smoke crack */ + window->size_hints.width_inc = 1; + meta_topic (META_DEBUG_GEOMETRY, "Corrected 0 width_inc to 1\n"); + } + if (window->size_hints.height_inc < 1) + { + /* another cracksmoker */ + window->size_hints.height_inc = 1; + meta_topic (META_DEBUG_GEOMETRY, "Corrected 0 height_inc to 1\n"); + } + /* divide by 0 cracksmokers; note that x & y in (min|max)_aspect are + * numerator & denominator + */ + if (window->size_hints.min_aspect.y < 1) + window->size_hints.min_aspect.y = 1; + if (window->size_hints.max_aspect.y < 1) + window->size_hints.max_aspect.y = 1; + + minw = window->size_hints.min_width; minh = window->size_hints.min_height; + maxw = window->size_hints.max_width; maxh = window->size_hints.max_height; + basew = window->size_hints.base_width; baseh = window->size_hints.base_height; + winc = window->size_hints.width_inc; hinc = window->size_hints.height_inc; + + /* Make sure min and max size hints are consistent with the base + increment + * size hints. If they're not, it's not a real big deal, but it means the + * effective min and max size are more restrictive than the application + * specified values. + */ + if ((minw - basew) % winc != 0) + { + /* Take advantage of integer division throwing away the remainder... */ + window->size_hints.min_width = basew + ((minw - basew)/winc + 1)*winc; + + meta_topic (META_DEBUG_GEOMETRY, + "Window %s has width_inc (%d) that does not evenly divide " + "min_width - base_width (%d - %d); thus effective " + "min_width is really %d\n", + window->desc, + winc, minw, basew, window->size_hints.min_width); + minw = window->size_hints.min_width; + } + if (maxw != G_MAXINT && (maxw - basew) % winc != 0) + { + /* Take advantage of integer division throwing away the remainder... */ + window->size_hints.max_width = basew + ((maxw - basew)/winc)*winc; + + meta_topic (META_DEBUG_GEOMETRY, + "Window %s has width_inc (%d) that does not evenly divide " + "max_width - base_width (%d - %d); thus effective " + "max_width is really %d\n", + window->desc, + winc, maxw, basew, window->size_hints.max_width); + maxw = window->size_hints.max_width; + } + if ((minh - baseh) % hinc != 0) + { + /* Take advantage of integer division throwing away the remainder... */ + window->size_hints.min_height = baseh + ((minh - baseh)/hinc + 1)*hinc; + + meta_topic (META_DEBUG_GEOMETRY, + "Window %s has height_inc (%d) that does not evenly divide " + "min_height - base_height (%d - %d); thus effective " + "min_height is really %d\n", + window->desc, + hinc, minh, baseh, window->size_hints.min_height); + minh = window->size_hints.min_height; + } + if (maxh != G_MAXINT && (maxh - baseh) % hinc != 0) + { + /* Take advantage of integer division throwing away the remainder... */ + window->size_hints.max_height = baseh + ((maxh - baseh)/hinc)*hinc; + + meta_topic (META_DEBUG_GEOMETRY, + "Window %s has height_inc (%d) that does not evenly divide " + "max_height - base_height (%d - %d); thus effective " + "max_height is really %d\n", + window->desc, + hinc, maxh, baseh, window->size_hints.max_height); + maxh = window->size_hints.max_height; + } + + /* make sure maximum size hints are compatible with minimum size hints; min + * size hints take precedence. + */ + if (window->size_hints.max_width < window->size_hints.min_width) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets max width %d less than min width %d, " + "disabling resize\n", + window->desc, + window->size_hints.max_width, + window->size_hints.min_width); + maxw = window->size_hints.max_width = window->size_hints.min_width; + } + if (window->size_hints.max_height < window->size_hints.min_height) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets max height %d less than min height %d, " + "disabling resize\n", + window->desc, + window->size_hints.max_height, + window->size_hints.min_height); + maxh = window->size_hints.max_height = window->size_hints.min_height; + } + + /* Make sure the aspect ratio hints are sane. */ + minr = window->size_hints.min_aspect.x / + (double)window->size_hints.min_aspect.y; + maxr = window->size_hints.max_aspect.x / + (double)window->size_hints.max_aspect.y; + if (minr > maxr) + { + /* another cracksmoker; not even minimally (self) consistent */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets min aspect ratio larger than max aspect " + "ratio; disabling aspect ratio constraints.\n", + window->desc); + window->size_hints.min_aspect.x = 1; + window->size_hints.min_aspect.y = G_MAXINT; + window->size_hints.max_aspect.x = G_MAXINT; + window->size_hints.max_aspect.y = 1; + } + else /* check consistency of aspect ratio hints with other hints */ + { + if (minh > 0 && minr > (maxw / (double)minh)) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets min aspect ratio larger than largest " + "aspect ratio possible given min/max size constraints; " + "disabling min aspect ratio constraint.\n", + window->desc); + window->size_hints.min_aspect.x = 1; + window->size_hints.min_aspect.y = G_MAXINT; + } + if (maxr < (minw / (double)maxh)) + { + /* another cracksmoker */ + meta_topic (META_DEBUG_GEOMETRY, + "Window %s sets max aspect ratio smaller than smallest " + "aspect ratio possible given min/max size constraints; " + "disabling max aspect ratio constraint.\n", + window->desc); + window->size_hints.max_aspect.x = G_MAXINT; + window->size_hints.max_aspect.y = 1; + } + /* FIXME: Would be nice to check that aspect ratios are + * consistent with base and size increment constraints. + */ + } +} + +static void +reload_normal_hints (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + if (value->type != META_PROP_VALUE_INVALID) + { + XSizeHints old_hints; + + meta_topic (META_DEBUG_GEOMETRY, "Updating WM_NORMAL_HINTS for %s\n", window->desc); + + old_hints = window->size_hints; + + meta_set_normal_hints (window, value->v.size_hints.hints); + + spew_size_hints_differences (&old_hints, &window->size_hints); + + meta_window_recalc_features (window); + + if (!initial) + meta_window_queue(window, META_QUEUE_MOVE_RESIZE); + } +} + +static void +reload_wm_protocols (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + int i; + + window->take_focus = FALSE; + window->delete_window = FALSE; + window->net_wm_ping = FALSE; + + if (value->type == META_PROP_VALUE_INVALID) + return; + + i = 0; + while (i < value->v.atom_list.n_atoms) + { + if (value->v.atom_list.atoms[i] == + window->display->atom_WM_TAKE_FOCUS) + window->take_focus = TRUE; + else if (value->v.atom_list.atoms[i] == + window->display->atom_WM_DELETE_WINDOW) + window->delete_window = TRUE; + else if (value->v.atom_list.atoms[i] == + window->display->atom__NET_WM_PING) + window->net_wm_ping = TRUE; + ++i; + } + + meta_verbose ("New _NET_STARTUP_ID \"%s\" for %s\n", + window->startup_id ? window->startup_id : "unset", + window->desc); +} + +static void +reload_wm_hints (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + Window old_group_leader; + + old_group_leader = window->xgroup_leader; + + /* Fill in defaults */ + window->input = TRUE; + window->initially_iconic = FALSE; + window->xgroup_leader = None; + window->wm_hints_pixmap = None; + window->wm_hints_mask = None; + + if (value->type != META_PROP_VALUE_INVALID) + { + const XWMHints *hints = value->v.wm_hints; + + if (hints->flags & InputHint) + window->input = hints->input; + + if (hints->flags & StateHint) + window->initially_iconic = (hints->initial_state == IconicState); + + if (hints->flags & WindowGroupHint) + window->xgroup_leader = hints->window_group; + + if (hints->flags & IconPixmapHint) + window->wm_hints_pixmap = hints->icon_pixmap; + + if (hints->flags & IconMaskHint) + window->wm_hints_mask = hints->icon_mask; + + meta_verbose ("Read WM_HINTS input: %d iconic: %d group leader: 0x%lx pixmap: 0x%lx mask: 0x%lx\n", + window->input, window->initially_iconic, + window->xgroup_leader, + window->wm_hints_pixmap, + window->wm_hints_mask); + } + + if (window->xgroup_leader != old_group_leader) + { + meta_verbose ("Window %s changed its group leader to 0x%lx\n", + window->desc, window->xgroup_leader); + + meta_window_group_leader_changed (window); + } + + meta_icon_cache_property_changed (&window->icon_cache, + window->display, + XA_WM_HINTS); + + meta_window_queue (window, META_QUEUE_UPDATE_ICON | META_QUEUE_MOVE_RESIZE); +} + +static void +reload_transient_for (MetaWindow *window, + MetaPropValue *value, + gboolean initial) +{ + window->xtransient_for = None; + + if (value->type != META_PROP_VALUE_INVALID) + window->xtransient_for = value->v.xwindow; + + /* Make sure transient_for is valid */ + if (window->xtransient_for != None && + meta_display_lookup_x_window (window->display, + window->xtransient_for) == NULL) + { + meta_warning (_("Invalid WM_TRANSIENT_FOR window 0x%lx specified " + "for %s.\n"), + window->xtransient_for, window->desc); + window->xtransient_for = None; + } + + window->transient_parent_is_root_window = + window->xtransient_for == window->screen->xroot; + + if (window->xtransient_for != None) + meta_verbose ("Window %s transient for 0x%lx (root = %d)\n", window->desc, + window->xtransient_for, window->transient_parent_is_root_window); + else + meta_verbose ("Window %s is not transient\n", window->desc); + + /* may now be a dialog */ + meta_window_recalc_window_type (window); + + /* update stacking constraints */ + meta_stack_update_transient (window->screen->stack, window); + + /* possibly change its group. We treat being a window's transient as + * equivalent to making it your group leader, to work around shortcomings + * in programs such as xmms-- see #328211. + */ + if (window->xtransient_for != None && + window->xgroup_leader != None && + window->xtransient_for != window->xgroup_leader) + meta_window_group_leader_changed (window); + + if (!window->constructing) + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + +/** + * Initialises the property hooks system. Each row in the table named "hooks" + * represents an action to take when a property is found on a newly-created + * window, or when a property changes its value. + * + * The first column shows which atom the row concerns. + * The second gives the type of the property data. The property will be + * queried for its new value, unless the type is given as + * META_PROP_VALUE_INVALID, in which case nothing will be queried. + * The third column gives the name of a callback which gets called with the + * new value. (If the new value was not retrieved because the second column + * was META_PROP_VALUE_INVALID, the callback still gets called anyway.) + * This value may be NULL, in which case no callback will be called. + */ +void +meta_display_init_window_prop_hooks (MetaDisplay *display) +{ + MetaWindowPropHooks hooks[] = { + { display->atom_WM_STATE, META_PROP_VALUE_INVALID, NULL }, + { display->atom_WM_CLIENT_MACHINE, META_PROP_VALUE_STRING, reload_wm_client_machine }, + { display->atom__NET_WM_PID, META_PROP_VALUE_CARDINAL, reload_net_wm_pid }, + { display->atom__NET_WM_USER_TIME, META_PROP_VALUE_CARDINAL, reload_net_wm_user_time }, + { display->atom__NET_WM_NAME, META_PROP_VALUE_UTF8, reload_net_wm_name }, + { XA_WM_NAME, META_PROP_VALUE_TEXT_PROPERTY, reload_wm_name }, + { display->atom__NET_WM_ICON, META_PROP_VALUE_INVALID, reload_net_wm_icon }, + { display->atom__KWM_WIN_ICON, META_PROP_VALUE_INVALID, reload_kwm_win_icon }, + { display->atom__NET_WM_ICON_NAME, META_PROP_VALUE_UTF8, reload_net_wm_icon_name }, + { XA_WM_ICON_NAME, META_PROP_VALUE_TEXT_PROPERTY, reload_wm_icon_name }, + { display->atom__NET_WM_STATE, META_PROP_VALUE_ATOM_LIST, reload_net_wm_state }, + { display->atom__MOTIF_WM_HINTS, META_PROP_VALUE_MOTIF_HINTS, reload_mwm_hints }, + { display->atom__NET_WM_ICON_GEOMETRY, META_PROP_VALUE_INVALID, NULL }, + { XA_WM_CLASS, META_PROP_VALUE_CLASS_HINT, reload_wm_class }, + { display->atom_WM_CLIENT_LEADER, META_PROP_VALUE_INVALID, complain_about_broken_client }, + { display->atom_SM_CLIENT_ID, META_PROP_VALUE_INVALID, complain_about_broken_client }, + { display->atom_WM_WINDOW_ROLE, META_PROP_VALUE_INVALID, reload_wm_window_role }, + { display->atom__NET_WM_WINDOW_TYPE, META_PROP_VALUE_INVALID, reload_net_wm_window_type }, + { display->atom__NET_WM_DESKTOP, META_PROP_VALUE_CARDINAL, reload_net_wm_desktop }, + { display->atom__NET_WM_STRUT, META_PROP_VALUE_INVALID, reload_struts }, + { display->atom__NET_WM_STRUT_PARTIAL, META_PROP_VALUE_INVALID, reload_struts }, + { display->atom__NET_STARTUP_ID, META_PROP_VALUE_UTF8, reload_net_startup_id }, + { display->atom__NET_WM_SYNC_REQUEST_COUNTER, META_PROP_VALUE_SYNC_COUNTER, reload_update_counter }, + { XA_WM_NORMAL_HINTS, META_PROP_VALUE_SIZE_HINTS, reload_normal_hints }, + { display->atom_WM_PROTOCOLS, META_PROP_VALUE_ATOM_LIST, reload_wm_protocols }, + { XA_WM_HINTS, META_PROP_VALUE_WM_HINTS, reload_wm_hints }, + { XA_WM_TRANSIENT_FOR, META_PROP_VALUE_WINDOW, reload_transient_for }, + { display->atom__NET_WM_USER_TIME_WINDOW, META_PROP_VALUE_WINDOW, reload_net_wm_user_time_window }, + { 0 }, + }; + + MetaWindowPropHooks *table = g_memdup (hooks, sizeof (hooks)), + *cursor = table; + + g_assert (display->prop_hooks == NULL); + + display->prop_hooks_table = (gpointer) table; + display->prop_hooks = g_hash_table_new (NULL, NULL); + + while (cursor->property) + { + /* Atoms are safe to use with GINT_TO_POINTER because it's safe with + * anything 32 bits or less, and atoms are 32 bits with the top three + * bits clear. (Scheifler & Gettys, 2e, p372) + */ + g_hash_table_insert (display->prop_hooks, + GINT_TO_POINTER (cursor->property), + cursor); + cursor++; + } +} + +void +meta_display_free_window_prop_hooks (MetaDisplay *display) +{ + g_hash_table_unref (display->prop_hooks); + display->prop_hooks = NULL; + + g_free (display->prop_hooks_table); + display->prop_hooks_table = NULL; +} + +/** + * Finds the hooks for a particular property. + */ +static MetaWindowPropHooks* +find_hooks (MetaDisplay *display, + Atom property) +{ + return g_hash_table_lookup (display->prop_hooks, + GINT_TO_POINTER (property)); +} diff --git a/src/core/window-props.h b/src/core/window-props.h new file mode 100644 index 00000000..1e60eff0 --- /dev/null +++ b/src/core/window-props.h @@ -0,0 +1,129 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file window-props.h MetaWindow property handling + * + * A system which can inspect sets of properties of given windows + * and take appropriate action given their values. + * + * Note that all the meta_window_reload_propert* functions require a + * round trip to the server. + */ + +/* + * Copyright (C) 2001, 2002 Red Hat, Inc. + * + * 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. + */ + +#ifndef META_WINDOW_PROPS_H +#define META_WINDOW_PROPS_H + +#include "window-private.h" + +/** + * Requests the current values of a single property for a given + * window from the server, and deals with it appropriately. + * Does not return it to the caller (it's been dealt with!) + * + * \param window The window. + * \param property A single X atom. + */ +void meta_window_reload_property (MetaWindow *window, + Atom property, + gboolean initial); + + +/** + * Requests the current values of a set of properties for a given + * window from the server, and deals with them appropriately. + * Does not return them to the caller (they've been dealt with!) + * + * \param window The window. + * \param properties A pointer to a list of X atoms, "n_properties" long. + * \param n_properties The length of the properties list. + */ +void meta_window_reload_properties (MetaWindow *window, + const Atom *properties, + int n_properties, + gboolean initial); + +/** + * Requests the current values of a single property for a given + * window from the server, and deals with it appropriately. + * Does not return it to the caller (it's been dealt with!) + * + * \param window A window on the same display as the one we're + * investigating (only used to find the display) + * \param xwindow The X handle for the window. + * \param property A single X atom. + */ +void meta_window_reload_property_from_xwindow + (MetaWindow *window, + Window xwindow, + Atom property, + gboolean initial); + +/** + * Requests the current values of a set of properties for a given + * window from the server, and deals with them appropriately. + * Does not return them to the caller (they've been dealt with!) + * + * \param window A window on the same display as the one we're + * investigating (only used to find the display) + * \param xwindow The X handle for the window. + * \param properties A pointer to a list of X atoms, "n_properties" long. + * \param n_properties The length of the properties list. + */ +void meta_window_reload_properties_from_xwindow + (MetaWindow *window, + Window xwindow, + const Atom *properties, + int n_properties, + gboolean initial); + +/** + * Initialises the hooks used for the reload_propert* functions + * on a particular display, and stores a pointer to them in the + * display. + * + * \param display The display. + */ +void meta_display_init_window_prop_hooks (MetaDisplay *display); + +/** + * Frees the hooks used for the reload_propert* functions + * for a particular display. + * + * \param display The display. + */ +void meta_display_free_window_prop_hooks (MetaDisplay *display); + +/** + * Sets the size hints for a window. This happens when a + * WM_NORMAL_HINTS property is set on a window, but it is public + * because the size hints are set to defaults when a window is + * created. See + * http://tronche.com/gui/x/icccm/sec-4.html#WM_NORMAL_HINTS + * for the X details. + * + * \param window The window to set the size hints on. + * \param hints Either some X size hints, or NULL for default. + */ +void meta_set_normal_hints (MetaWindow *window, + XSizeHints *hints); + +#endif /* META_WINDOW_PROPS_H */ diff --git a/src/core/window.c b/src/core/window.c new file mode 100644 index 00000000..08ab9ec9 --- /dev/null +++ b/src/core/window.c @@ -0,0 +1,8178 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X managed windows */ + +/* + * Copyright (C) 2001 Havoc Pennington, Anders Carlsson + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "window-private.h" +#include "edge-resistance.h" +#include "util.h" +#include "frame-private.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 "group.h" +#include "window-props.h" +#include "constraints.h" +#include "compositor.h" +#include "effects.h" + +#include <X11/Xatom.h> +#include <string.h> + +#ifdef HAVE_SHAPE +#include <X11/extensions/shape.h> +#endif + +static int destroying_windows_disallowed = 0; + + +static void update_sm_hints (MetaWindow *window); +static void update_net_frame_extents (MetaWindow *window); +static void recalc_window_type (MetaWindow *window); +static void recalc_window_features (MetaWindow *window); +static void invalidate_work_areas (MetaWindow *window); +static void recalc_window_type (MetaWindow *window); +static void set_wm_state (MetaWindow *window, + int state); +static void 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 void meta_window_save_rect (MetaWindow *window); +static void save_user_window_placement (MetaWindow *window); +static void force_save_user_window_placement (MetaWindow *window); + +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); + +static void ensure_mru_position_after (MetaWindow *window, + MetaWindow *after_this_one); + + +static void meta_window_move_resize_now (MetaWindow *window); + +static void meta_window_unqueue (MetaWindow *window, guint queuebits); + +static void update_move (MetaWindow *window, + gboolean snap, + int x, + int y); +static gboolean update_move_timeout (gpointer data); +static void update_resize (MetaWindow *window, + gboolean snap, + int x, + int y, + gboolean force); +static gboolean update_resize_timeout (gpointer data); + + +static void meta_window_flush_calc_showing (MetaWindow *window); + +static gboolean queue_calc_showing_func (MetaWindow *window, + void *data); + +static void meta_window_apply_session_info (MetaWindow *window, + const MetaWindowSessionInfo *info); + +static void unmaximize_window_before_freeing (MetaWindow *window); +static void unminimize_window_and_all_transient_parents (MetaWindow *window); + +/* Idle handlers for the three queues. The "data" parameter in each case + * will be a GINT_TO_POINTER of the index into the queue arrays to use. + * + * TODO: Possibly there is still some code duplication among these, which we + * need to sort out at some point. + */ +static gboolean idle_calc_showing (gpointer data); +static gboolean idle_move_resize (gpointer data); +static gboolean idle_update_icon (gpointer data); + +#ifdef WITH_VERBOSE_MODE +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"; +} +#endif + +static gboolean +is_desktop_or_dock_foreach (MetaWindow *window, + void *data) +{ + gboolean *result = data; + + *result = + window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK; + if (*result) + return FALSE; /* stop as soon as we find one */ + else + return TRUE; +} + +/* window is the window that's newly mapped provoking + * the possible change + */ +static void +maybe_leave_show_desktop_mode (MetaWindow *window) +{ + gboolean is_desktop_or_dock; + + if (!window->screen->active_workspace->showing_desktop) + return; + + /* If the window is a transient for the dock or desktop, don't + * leave show desktop mode when the window opens. That's + * so you can e.g. hide all windows, manipulate a file on + * the desktop via a dialog, then unshow windows again. + */ + is_desktop_or_dock = FALSE; + is_desktop_or_dock_foreach (window, + &is_desktop_or_dock); + + meta_window_foreach_ancestor (window, is_desktop_or_dock_foreach, + &is_desktop_or_dock); + + if (!is_desktop_or_dock) + { + meta_screen_minimize_all_on_active_workspace_except (window->screen, + window); + meta_screen_unshow_desktop (window->screen); + } +} + +MetaWindow* +meta_window_new (MetaDisplay *display, + Window xwindow, + gboolean must_be_viewable) +{ + XWindowAttributes attrs; + MetaWindow *window; + + meta_display_grab (display); + meta_error_trap_push (display); /* Push a trap over all of window + * creation, to reduce XSync() calls + */ + + meta_error_trap_push_with_return (display); + + if (XGetWindowAttributes (display->xdisplay,xwindow, &attrs)) + { + if(meta_error_trap_pop_with_return (display, TRUE) != Success) + { + meta_verbose ("Failed to get attributes for window 0x%lx\n", + xwindow); + meta_error_trap_pop (display, TRUE); + meta_display_ungrab (display); + return NULL; + } + window = meta_window_new_with_attrs (display, xwindow, + must_be_viewable, &attrs); + } + else + { + meta_error_trap_pop_with_return (display, TRUE); + meta_verbose ("Failed to get attributes for window 0x%lx\n", + xwindow); + meta_error_trap_pop (display, TRUE); + meta_display_ungrab (display); + return NULL; + } + + + meta_error_trap_pop (display, FALSE); + meta_display_ungrab (display); + + return window; +} + +MetaWindow* +meta_window_new_with_attrs (MetaDisplay *display, + Window xwindow, + gboolean must_be_viewable, + XWindowAttributes *attrs) +{ + MetaWindow *window; + GSList *tmp; + MetaWorkspace *space; + gulong existing_wm_state; + gulong event_mask; + MetaMoveResizeFlags flags; +#define N_INITIAL_PROPS 19 + Atom initial_props[N_INITIAL_PROPS]; + int i; + gboolean has_shape; + + g_assert (attrs != NULL); + g_assert (N_INITIAL_PROPS == (int) G_N_ELEMENTS (initial_props)); + + meta_verbose ("Attempting to manage 0x%lx\n", xwindow); + + if (meta_display_xwindow_is_a_no_focus_window (display, xwindow)) + { + meta_verbose ("Not managing no_focus_window 0x%lx\n", + xwindow); + return NULL; + } + + if (attrs->override_redirect) + { + meta_verbose ("Deciding not to manage override_redirect window 0x%lx\n", xwindow); + return NULL; + } + + /* Grab server */ + meta_display_grab (display); + meta_error_trap_push (display); /* Push a trap over all of window + * creation, to reduce XSync() calls + */ + + 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_error_trap_pop (display, TRUE); + 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_with_return (display); + + XAddToSaveSet (display->xdisplay, xwindow); + + event_mask = + PropertyChangeMask | EnterWindowMask | LeaveWindowMask | + FocusChangeMask | ColormapChangeMask; + + XSelectInput (display->xdisplay, xwindow, event_mask); + + has_shape = FALSE; +#ifdef HAVE_SHAPE + if (META_DISPLAY_HAS_SHAPE (display)) + { + int x_bounding, y_bounding, x_clip, y_clip; + unsigned w_bounding, h_bounding, w_clip, h_clip; + int bounding_shaped, clip_shaped; + + XShapeSelectInput (display->xdisplay, xwindow, ShapeNotifyMask); + + XShapeQueryExtents (display->xdisplay, xwindow, + &bounding_shaped, &x_bounding, &y_bounding, + &w_bounding, &h_bounding, + &clip_shaped, &x_clip, &y_clip, + &w_clip, &h_clip); + + has_shape = bounding_shaped != FALSE; + + meta_topic (META_DEBUG_SHAPES, + "Window has_shape = %d extents %d,%d %u x %u\n", + has_shape, x_bounding, y_bounding, + w_bounding, h_bounding); + } +#endif + + /* 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_with_return (display, FALSE) != Success) + { + meta_verbose ("Window 0x%lx disappeared just as we tried to manage it\n", + xwindow); + meta_error_trap_pop (display, FALSE); + meta_display_ungrab (display); + return NULL; + } + + g_assert (!attrs->override_redirect); + + window = g_new (MetaWindow, 1); + + window->constructing = TRUE; + + window->dialog_pid = -1; + + window->xwindow = xwindow; + + /* this is in window->screen->display, but that's too annoying to + * type + */ + window->display = display; + window->workspace = NULL; + +#ifdef HAVE_XSYNC + window->sync_request_counter = None; + window->sync_request_serial = 0; + window->sync_request_time.tv_sec = 0; + window->sync_request_time.tv_usec = 0; +#endif + + window->screen = NULL; + tmp = display->screens; + while (tmp != NULL) + { + MetaScreen *scr = tmp->data; + + if (scr->xroot == attrs->root) + { + window->screen = tmp->data; + break; + } + + tmp = tmp->next; + } + + g_assert (window->screen); + + window->desc = g_strdup_printf ("0x%lx", window->xwindow); + + /* avoid tons of stack updates */ + meta_stack_freeze (window->screen->stack); + + window->has_shape = has_shape; + + window->rect.x = attrs->x; + window->rect.y = attrs->y; + window->rect.width = attrs->width; + window->rect.height = attrs->height; + + /* 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; + /* initialize the remaining size_hints as if size_hints.flags were zero */ + meta_set_normal_hints (window, NULL); + + /* 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->colormap = attrs->colormap; + + 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->frame = NULL; + window->has_focus = FALSE; + + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; + window->maximize_horizontally_after_placement = FALSE; + window->maximize_vertically_after_placement = FALSE; + window->minimize_after_placement = FALSE; + window->fullscreen_after_placement = FALSE; + window->fullscreen_monitors[0] = -1; + window->require_fully_onscreen = TRUE; + window->require_on_single_xinerama = TRUE; + window->require_titlebar_visible = TRUE; + window->on_all_workspaces = FALSE; + window->shaded = FALSE; + window->initially_iconic = FALSE; + window->minimized = FALSE; + window->was_minimized = FALSE; + window->tab_unminimized = FALSE; + window->iconic = FALSE; + window->mapped = attrs->map_state != IsUnmapped; + /* if already mapped, no need to worry about focus-on-first-time-showing */ + window->showing_for_first_time = !window->mapped; + /* 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->force_save_user_rect = TRUE; + window->denied_focus_and_not_transient = FALSE; + window->unmanaging = FALSE; + window->is_in_queues = 0; + window->keys_grabbed = FALSE; + window->grab_on_frame = FALSE; + window->all_keys_grabbed = FALSE; + window->withdrawn = FALSE; + window->initial_workspace_set = FALSE; + window->initial_timestamp_set = FALSE; + window->net_wm_user_time_set = FALSE; + window->user_time_window = None; + window->calc_placement = FALSE; + window->shaken_loose = FALSE; + window->have_focus_click_grab = FALSE; + window->disable_sync = FALSE; + + window->unmaps_pending = 0; + + window->mwm_decorated = TRUE; + window->mwm_border_only = FALSE; + 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->always_sticky = FALSE; + + window->wm_state_modal = FALSE; + window->skip_taskbar = FALSE; + window->skip_pager = FALSE; + window->wm_state_skip_taskbar = FALSE; + window->wm_state_skip_pager = FALSE; + window->wm_state_above = FALSE; + window->wm_state_below = FALSE; + window->wm_state_demands_attention = FALSE; + + window->res_class = NULL; + window->res_name = NULL; + window->role = NULL; + window->sm_client_id = NULL; + window->wm_client_machine = NULL; + window->startup_id = NULL; + + window->net_wm_pid = -1; + + window->xtransient_for = None; + window->xclient_leader = None; + window->transient_parent_is_root_window = FALSE; + + window->type = META_WINDOW_NORMAL; + window->type_atom = None; + + window->struts = NULL; + + window->using_net_wm_name = FALSE; + window->using_net_wm_visible_name = FALSE; + window->using_net_wm_icon_name = FALSE; + window->using_net_wm_visible_icon_name = FALSE; + + window->need_reread_icon = TRUE; + + window->layer = META_LAYER_LAST; /* invalid value */ + window->stack_position = -1; + window->initial_workspace = 0; /* not used */ + window->initial_timestamp = 0; /* not used */ + + meta_display_register_x_window (display, &window->xwindow, window); + + + /* assign the window to its group, or create a new group if needed + */ + window->group = NULL; + window->xgroup_leader = None; + meta_window_compute_group (window); + + /* Fill these in the order we want them to be gotten. we want to + * get window name and class first so we can use them in error + * messages and such. However, name is modified depending on + * wm_client_machine, so push it slightly sooner. + */ + i = 0; + initial_props[i++] = display->atom_WM_CLIENT_MACHINE; + initial_props[i++] = display->atom__NET_WM_PID; + initial_props[i++] = display->atom__NET_WM_NAME; + initial_props[i++] = XA_WM_CLASS; + initial_props[i++] = XA_WM_NAME; + initial_props[i++] = display->atom__NET_WM_ICON_NAME; + initial_props[i++] = XA_WM_ICON_NAME; + initial_props[i++] = display->atom__NET_WM_DESKTOP; + initial_props[i++] = display->atom__NET_STARTUP_ID; + initial_props[i++] = display->atom__NET_WM_SYNC_REQUEST_COUNTER; + initial_props[i++] = XA_WM_NORMAL_HINTS; + initial_props[i++] = display->atom_WM_PROTOCOLS; + initial_props[i++] = XA_WM_HINTS; + initial_props[i++] = display->atom__NET_WM_USER_TIME; + initial_props[i++] = display->atom__NET_WM_STATE; + initial_props[i++] = display->atom__MOTIF_WM_HINTS; + initial_props[i++] = XA_WM_TRANSIENT_FOR; + initial_props[i++] = display->atom__NET_WM_USER_TIME_WINDOW; + initial_props[i++] = display->atom__NET_WM_FULLSCREEN_MONITORS; + g_assert (N_INITIAL_PROPS == i); + + meta_window_reload_properties (window, initial_props, N_INITIAL_PROPS, TRUE); + + update_sm_hints (window); /* must come after transient_for */ + meta_window_update_role (window); + meta_window_update_net_wm_type (window); + meta_window_update_icon_now (window); + + 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; + } + + /* Apply any window attributes such as initial workspace + * based on startup notification + */ + meta_screen_apply_startup_properties (window->screen, window); + + /* Try to get a "launch timestamp" for the window. If the window is + * a transient, we'd like to be able to get a last-usage timestamp + * from the parent window. If the window has no parent, there isn't + * much we can do...except record the current time so that any children + * can use this time as a fallback. + */ + if (!window->net_wm_user_time_set) { + MetaWindow *parent = NULL; + if (window->xtransient_for) + parent = meta_display_lookup_x_window (window->display, + window->xtransient_for); + + /* First, maybe the app was launched with startup notification using an + * obsolete version of the spec; use that timestamp if it exists. + */ + if (window->initial_timestamp_set) + /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just + * being recorded as a fallback for potential transients + */ + window->net_wm_user_time = window->initial_timestamp; + else if (parent != NULL) + meta_window_set_user_time(window, parent->net_wm_user_time); + else + /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just + * being recorded as a fallback for potential transients + */ + window->net_wm_user_time = + meta_display_get_current_time_roundtrip (window->display); + } + + if (window->decorated) + meta_window_ensure_frame (window); + + meta_window_grab_keys (window); + if (window->type != META_WINDOW_DOCK) + { + meta_display_grab_window_buttons (window->display, window->xwindow); + meta_display_grab_focus_window_button (window->display, window); + } + + 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. + * Need to set this before adding to the workspaces so the MRU + * lists will be updated. + */ + window->on_all_workspaces = TRUE; + } + + /* 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); + + /* need to set on_all_workspaces first so that it will be + * added to all the MRU lists + */ + window->on_all_workspaces = TRUE; + meta_workspace_add_window (window->screen->active_workspace, window); + } + else + { + meta_topic (META_DEBUG_PLACEMENT, + "Window %s is initially on space %d\n", + window->desc, window->initial_workspace); + + space = + meta_screen_get_workspace_by_index (window->screen, + window->initial_workspace); + + if (space) + meta_workspace_add_window (space, window); + } + } + + if (window->workspace == 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 && parent->workspace) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on same workspace as parent %s\n", + window->desc, parent->desc); + + if (parent->on_all_workspaces) + window->on_all_workspaces = TRUE; + + /* this will implicitly add to the appropriate MRU lists + */ + meta_workspace_add_window (parent->workspace, window); + } + } + + if (window->workspace == 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); + } + + /* for the various on_all_workspaces = TRUE possible above */ + meta_window_set_current_workspace_hint (window); + + meta_window_update_struts (window); + + /* Must add window to stack before doing move/resize, since the + * window might have fullscreen size (i.e. should have been + * fullscreen'd; acrobat is one such braindead case; it withdraws + * and remaps its window whenever trying to become fullscreen...) + * and thus constraints may try to auto-fullscreen it which also + * means restacking it. + */ + meta_stack_add (window->screen->stack, + window); + + /* Put our state back where it should be, + * passing TRUE for is_configure_request, ICCCM says + * initial map is handled same as configure request + */ + flags = + META_IS_CONFIGURE_REQUEST | META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION; + meta_window_move_resize_internal (window, + flags, + window->size_hints.win_gravity, + window->size_hints.x, + window->size_hints.y, + window->size_hints.width, + window->size_hints.height); + + /* Now try applying saved stuff from the session */ + { + const MetaWindowSessionInfo *info; + + info = meta_window_lookup_saved_state (window); + + if (info) + { + meta_window_apply_session_info (window, info); + meta_window_release_saved_state (info); + } + } + + /* 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); + + /* Sync stack changes */ + meta_stack_thaw (window->screen->stack); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + + meta_window_queue (window, META_QUEUE_CALC_SHOWING); + /* See bug 303284; a transient of the given window can already exist, in which + * case we think it should probably be shown. + */ + meta_window_foreach_transient (window, + queue_calc_showing_func, + NULL); + /* See bug 334899; the window may have minimized ancestors + * which need to be shown. + * + * However, we shouldn't unminimize windows here when opening + * a new display because that breaks passing _NET_WM_STATE_HIDDEN + * between window managers when replacing them; see bug 358042. + * + * And we shouldn't unminimize windows if they were initially + * iconic. + */ + if (!display->display_opening && !window->initially_iconic) + unminimize_window_and_all_transient_parents (window); + + meta_error_trap_pop (display, FALSE); /* pop the XSync()-reducing trap */ + meta_display_ungrab (display); + + window->constructing = FALSE; + + return window; +} + +/* This function should only be called from the end of meta_window_new_with_attrs () */ +static void +meta_window_apply_session_info (MetaWindow *window, + const MetaWindowSessionInfo *info) +{ + if (info->stack_position_set) + { + meta_topic (META_DEBUG_SM, + "Restoring stack position %d for window %s\n", + info->stack_position, window->desc); + + /* FIXME well, I'm not sure how to do this. */ + } + + if (info->minimized_set) + { + meta_topic (META_DEBUG_SM, + "Restoring minimized state %d for window %s\n", + info->minimized, window->desc); + + if (window->has_minimize_func && info->minimized) + meta_window_minimize (window); + } + + if (info->maximized_set) + { + meta_topic (META_DEBUG_SM, + "Restoring maximized state %d for window %s\n", + info->maximized, window->desc); + + if (window->has_maximize_func && info->maximized) + { + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + + if (info->saved_rect_set) + { + meta_topic (META_DEBUG_SM, + "Restoring saved rect %d,%d %dx%d for window %s\n", + info->saved_rect.x, + info->saved_rect.y, + info->saved_rect.width, + info->saved_rect.height, + window->desc); + + window->saved_rect.x = info->saved_rect.x; + window->saved_rect.y = info->saved_rect.y; + window->saved_rect.width = info->saved_rect.width; + window->saved_rect.height = info->saved_rect.height; + } + } + } + + if (info->on_all_workspaces_set) + { + window->on_all_workspaces = info->on_all_workspaces; + meta_topic (META_DEBUG_SM, + "Restoring sticky state %d for window %s\n", + window->on_all_workspaces, window->desc); + } + + if (info->workspace_indices) + { + GSList *tmp; + GSList *spaces; + + spaces = NULL; + + tmp = info->workspace_indices; + while (tmp != NULL) + { + MetaWorkspace *space; + + space = + meta_screen_get_workspace_by_index (window->screen, + GPOINTER_TO_INT (tmp->data)); + + if (space) + spaces = g_slist_prepend (spaces, space); + + tmp = tmp->next; + } + + if (spaces) + { + /* This briefly breaks the invariant that we are supposed + * to always be on some workspace. But we paranoically + * ensured that one of the workspaces from the session was + * indeed valid, so we know we'll go right back to one. + */ + if (window->workspace) + meta_workspace_remove_window (window->workspace, window); + + /* Only restore to the first workspace if the window + * happened to be on more than one, since we have replaces + * window->workspaces with window->workspace + */ + meta_workspace_add_window (spaces->data, window); + + meta_topic (META_DEBUG_SM, + "Restoring saved window %s to workspace %d\n", + window->desc, + meta_workspace_index (spaces->data)); + + g_slist_free (spaces); + } + } + + if (info->geometry_set) + { + int x, y, w, h; + MetaMoveResizeFlags flags; + + window->placed = TRUE; /* don't do placement algorithms later */ + + x = info->rect.x; + y = info->rect.y; + + w = window->size_hints.base_width + + info->rect.width * window->size_hints.width_inc; + h = window->size_hints.base_height + + info->rect.height * window->size_hints.height_inc; + + /* Force old gravity, ignoring anything now set */ + window->size_hints.win_gravity = info->gravity; + + meta_topic (META_DEBUG_SM, + "Restoring pos %d,%d size %d x %d for %s\n", + x, y, w, h, window->desc); + + flags = META_DO_GRAVITY_ADJUST | META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION; + meta_window_move_resize_internal (window, + flags, + window->size_hints.win_gravity, + x, y, w, h); + } +} + +void +meta_window_free (MetaWindow *window, + guint32 timestamp) +{ + GList *tmp; + + meta_verbose ("Unmanaging 0x%lx\n", window->xwindow); + + if (window->display->compositor) + meta_compositor_free_window (window->display->compositor, window); + + if (window->display->window_with_menu == window) + { + meta_ui_window_menu_free (window->display->window_menu); + window->display->window_menu = NULL; + window->display->window_with_menu = NULL; + } + + if (destroying_windows_disallowed > 0) + meta_bug ("Tried to destroy window %s while destruction was not allowed\n", + window->desc); + + window->unmanaging = TRUE; + + if (window->fullscreen) + { + MetaGroup *group; + + /* If the window is fullscreen, it may be forcing + * other windows in its group to a higher layer + */ + + meta_stack_freeze (window->screen->stack); + group = meta_window_get_group (window); + if (group) + meta_group_update_layers (group); + meta_stack_thaw (window->screen->stack); + } + + meta_window_shutdown_group (window); /* safe to do this early as + * group.c won't re-add to the + * group if window->unmanaging + */ + + /* If we have the focus, focus some other window. + * This is done first, so that if the unmap causes + * an EnterNotify the EnterNotify will have final say + * on what gets focused, maintaining sloppy focus + * invariants. + */ + if (window->has_focus) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing default window since we're unmanaging %s\n", + window->desc); + meta_workspace_focus_default_window (window->screen->active_workspace, + window, + timestamp); + } + else if (window->display->expected_focus_window == window) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing default window since expected focus window freed %s\n", + window->desc); + window->display->expected_focus_window = NULL; + meta_workspace_focus_default_window (window->screen->active_workspace, + window, + timestamp); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Unmanaging window %s which doesn't currently have focus\n", + window->desc); + } + + if (window->struts) + { + meta_free_gslist_and_elements (window->struts); + window->struts = NULL; + + meta_topic (META_DEBUG_WORKAREA, + "Unmanaging window %s which has struts, so invalidating work areas\n", + window->desc); + invalidate_work_areas (window); + } + + if (window->display->grab_window == window) + meta_display_end_grab_op (window->display, timestamp); + + g_assert (window->display->grab_window != window); + + if (window->display->focus_window == window) + { + window->display->focus_window = NULL; + meta_compositor_set_active_window (window->display->compositor, + window->screen, NULL); + } + + if (window->maximized_horizontally || window->maximized_vertically) + unmaximize_window_before_freeing (window); + + /* The XReparentWindow call in meta_window_destroy_frame() moves the + * window so we need to send a configure notify; see bug 399552. (We + * also do this just in case a window got unmaximized.) + */ + send_configure_notify (window); + + meta_window_unqueue (window, META_QUEUE_CALC_SHOWING | + META_QUEUE_MOVE_RESIZE | + META_QUEUE_UPDATE_ICON); + meta_window_free_delete_dialog (window); + + if (window->workspace) + meta_workspace_remove_window (window->workspace, window); + + g_assert (window->workspace == NULL); + +#ifndef G_DISABLE_CHECKS + tmp = window->screen->workspaces; + while (tmp != NULL) + { + MetaWorkspace *workspace = tmp->data; + + g_assert (g_list_find (workspace->windows, window) == NULL); + g_assert (g_list_find (workspace->mru_list, window) == NULL); + + tmp = tmp->next; + } +#endif + + meta_stack_remove (window->screen->stack, window); + + if (window->frame) + meta_window_destroy_frame (window); + + if (window->withdrawn) + { + /* We need to clean off the window's state so it + * won't be restored if the app maps it again. + */ + meta_error_trap_push (window->display); + meta_verbose ("Cleaning state from window %s\n", window->desc); + XDeleteProperty (window->display->xdisplay, + window->xwindow, + window->display->atom__NET_WM_DESKTOP); + XDeleteProperty (window->display->xdisplay, + window->xwindow, + window->display->atom__NET_WM_STATE); + XDeleteProperty (window->display->xdisplay, + window->xwindow, + window->display->atom__NET_WM_FULLSCREEN_MONITORS); + set_wm_state (window, WithdrawnState); + meta_error_trap_pop (window->display, FALSE); + } + else + { + /* We need to put WM_STATE so that others will understand it on + * restart. + */ + if (!window->minimized) + { + meta_error_trap_push (window->display); + set_wm_state (window, NormalState); + meta_error_trap_pop (window->display, FALSE); + } + + /* And we need to be sure the window is mapped so other WMs + * know that it isn't Withdrawn + */ + meta_error_trap_push (window->display); + XMapWindow (window->display->xdisplay, + window->xwindow); + meta_error_trap_pop (window->display, FALSE); + } + + meta_window_ungrab_keys (window); + meta_display_ungrab_window_buttons (window->display, window->xwindow); + meta_display_ungrab_focus_window_button (window->display, window); + + meta_display_unregister_x_window (window->display, window->xwindow); + + + meta_error_trap_push (window->display); + + /* Put back anything we messed up */ + if (window->border_width != 0) + XSetWindowBorderWidth (window->display->xdisplay, + window->xwindow, + window->border_width); + + /* No save set */ + XRemoveFromSaveSet (window->display->xdisplay, + window->xwindow); + + /* Don't get events on not-managed windows */ + XSelectInput (window->display->xdisplay, + window->xwindow, + NoEventMask); + + /* Stop getting events for the window's _NET_WM_USER_TIME_WINDOW too */ + if (window->user_time_window != None) + { + meta_display_unregister_x_window (window->display, + window->user_time_window); + XSelectInput (window->display->xdisplay, + window->user_time_window, + NoEventMask); + window->user_time_window = None; + } + +#ifdef HAVE_SHAPE + if (META_DISPLAY_HAS_SHAPE (window->display)) + XShapeSelectInput (window->display->xdisplay, window->xwindow, NoEventMask); +#endif + + meta_error_trap_pop (window->display, FALSE); + + if (window->icon) + g_object_unref (G_OBJECT (window->icon)); + + if (window->mini_icon) + g_object_unref (G_OBJECT (window->mini_icon)); + + meta_icon_cache_free (&window->icon_cache); + + g_free (window->sm_client_id); + g_free (window->wm_client_machine); + g_free (window->startup_id); + g_free (window->role); + g_free (window->res_class); + g_free (window->res_name); + g_free (window->title); + g_free (window->icon_name); + g_free (window->desc); + g_free (window); +} + +static void +set_wm_state (MetaWindow *window, + int state) +{ + unsigned long data[2]; + + meta_verbose ("Setting wm state %s on %s\n", + wm_state_to_string (state), window->desc); + + /* Marco doesn't use icon windows, so data[1] should be None + * according to the ICCCM 2.0 Section 4.1.3.1. + */ + data[0] = state; + data[1] = None; + + meta_error_trap_push (window->display); + XChangeProperty (window->display->xdisplay, window->xwindow, + window->display->atom_WM_STATE, + window->display->atom_WM_STATE, + 32, PropModeReplace, (guchar*) data, 2); + meta_error_trap_pop (window->display, FALSE); +} + +static void +set_net_wm_state (MetaWindow *window) +{ + int i; + unsigned long data[12]; + + i = 0; + if (window->shaded) + { + data[i] = window->display->atom__NET_WM_STATE_SHADED; + ++i; + } + if (window->wm_state_modal) + { + data[i] = window->display->atom__NET_WM_STATE_MODAL; + ++i; + } + if (window->skip_pager) + { + data[i] = window->display->atom__NET_WM_STATE_SKIP_PAGER; + ++i; + } + if (window->skip_taskbar) + { + data[i] = window->display->atom__NET_WM_STATE_SKIP_TASKBAR; + ++i; + } + if (window->maximized_horizontally) + { + data[i] = window->display->atom__NET_WM_STATE_MAXIMIZED_HORZ; + ++i; + } + if (window->maximized_vertically) + { + data[i] = window->display->atom__NET_WM_STATE_MAXIMIZED_VERT; + ++i; + } + if (window->fullscreen) + { + data[i] = window->display->atom__NET_WM_STATE_FULLSCREEN; + ++i; + } + if (!meta_window_showing_on_its_workspace (window) || window->shaded) + { + data[i] = window->display->atom__NET_WM_STATE_HIDDEN; + ++i; + } + if (window->wm_state_above) + { + data[i] = window->display->atom__NET_WM_STATE_ABOVE; + ++i; + } + if (window->wm_state_below) + { + data[i] = window->display->atom__NET_WM_STATE_BELOW; + ++i; + } + if (window->wm_state_demands_attention) + { + data[i] = window->display->atom__NET_WM_STATE_DEMANDS_ATTENTION; + ++i; + } + if (window->on_all_workspaces) + { + data[i] = window->display->atom__NET_WM_STATE_STICKY; + ++i; + } + + meta_verbose ("Setting _NET_WM_STATE with %d atoms\n", i); + + meta_error_trap_push (window->display); + XChangeProperty (window->display->xdisplay, window->xwindow, + window->display->atom__NET_WM_STATE, + XA_ATOM, + 32, PropModeReplace, (guchar*) data, i); + meta_error_trap_pop (window->display, FALSE); + + if (window->fullscreen) + { + data[0] = window->fullscreen_monitors[0]; + data[1] = window->fullscreen_monitors[1]; + data[2] = window->fullscreen_monitors[2]; + data[3] = window->fullscreen_monitors[3]; + + meta_verbose ("Setting _NET_WM_FULLSCREEN_MONITORS\n"); + meta_error_trap_push (window->display); + XChangeProperty (window->display->xdisplay, + window->xwindow, + window->display->atom__NET_WM_FULLSCREEN_MONITORS, + XA_CARDINAL, 32, PropModeReplace, + (guchar*) data, 4); + meta_error_trap_pop (window->display, FALSE); + } +} + +gboolean +meta_window_located_on_workspace (MetaWindow *window, + MetaWorkspace *workspace) +{ + return (window->on_all_workspaces && window->screen == workspace->screen) || + (window->workspace == workspace); +} + +static gboolean +is_minimized_foreach (MetaWindow *window, + void *data) +{ + gboolean *result = data; + + *result = window->minimized; + if (*result) + return FALSE; /* stop as soon as we find one */ + else + return TRUE; +} + +static gboolean +ancestor_is_minimized (MetaWindow *window) +{ + gboolean is_minimized; + + is_minimized = FALSE; + + meta_window_foreach_ancestor (window, is_minimized_foreach, &is_minimized); + + return is_minimized; +} + +gboolean +meta_window_showing_on_its_workspace (MetaWindow *window) +{ + gboolean showing; + gboolean is_desktop_or_dock; + MetaWorkspace* workspace_of_window; + + showing = TRUE; + + /* 1. See if we're minimized */ + if (window->minimized) + showing = FALSE; + + /* 2. See if we're in "show desktop" mode */ + is_desktop_or_dock = FALSE; + is_desktop_or_dock_foreach (window, + &is_desktop_or_dock); + + meta_window_foreach_ancestor (window, is_desktop_or_dock_foreach, + &is_desktop_or_dock); + + if (window->on_all_workspaces) + workspace_of_window = window->screen->active_workspace; + else if (window->workspace) + workspace_of_window = window->workspace; + else /* This only seems to be needed for startup */ + workspace_of_window = NULL; + + if (showing && + workspace_of_window && workspace_of_window->showing_desktop && + !is_desktop_or_dock) + { + meta_verbose ("We're showing the desktop on the workspace(s) that window %s is on\n", + window->desc); + showing = FALSE; + } + + /* 3. See if an ancestor is minimized (note that + * ancestor's "mapped" field may not be up to date + * since it's being computed in this same idle queue) + */ + + if (showing) + { + if (ancestor_is_minimized (window)) + showing = FALSE; + } + +#if 0 + /* 4. See if we're drawing wireframe + */ + if (window->display->grab_window == window && + window->display->grab_wireframe_active) + showing = FALSE; +#endif + + return showing; +} + +gboolean +meta_window_should_be_showing (MetaWindow *window) +{ + gboolean on_workspace; + + meta_verbose ("Should be showing for window %s\n", window->desc); + + /* See if we're on the workspace */ + on_workspace = meta_window_located_on_workspace (window, + window->screen->active_workspace); + + if (!on_workspace) + meta_verbose ("Window %s is not on workspace %d\n", + window->desc, + meta_workspace_index (window->screen->active_workspace)); + else + meta_verbose ("Window %s is on the active workspace %d\n", + window->desc, + meta_workspace_index (window->screen->active_workspace)); + + if (window->on_all_workspaces) + meta_verbose ("Window %s is on all workspaces\n", window->desc); + + return on_workspace && meta_window_showing_on_its_workspace (window); +} + +static void +finish_minimize (gpointer data) +{ + MetaWindow *window = data; + /* FIXME: It really sucks to put timestamp pinging here; it'd + * probably make more sense in implement_showing() so that it's at + * least not duplicated in meta_window_show; but since + * finish_minimize is a callback making things just slightly icky, I + * haven't done that yet. + */ + guint32 timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_window_hide (window); + if (window->has_focus) + { + meta_workspace_focus_default_window (window->screen->active_workspace, + window, + timestamp); + } +} + +static void +implement_showing (MetaWindow *window, + gboolean showing) +{ + /* Actually show/hide the window */ + meta_verbose ("Implement showing = %d for window %s\n", + showing, window->desc); + + if (!showing) + { + gboolean on_workspace; + + on_workspace = meta_window_located_on_workspace (window, + window->screen->active_workspace); + + /* Really this effects code should probably + * be in meta_window_hide so the window->mapped + * test isn't duplicated here. Anyhow, we animate + * if we are mapped now, we are supposed to + * be minimized, and we are on the current workspace. + */ + if (on_workspace && window->minimized && window->mapped && + !meta_prefs_get_reduced_resources ()) + { + MetaRectangle icon_rect, window_rect; + gboolean result; + + /* Check if the window has an icon geometry */ + result = meta_window_get_icon_geometry (window, &icon_rect); + + if (!result) + { + /* just animate into the corner somehow - maybe + * not a good idea... + */ + icon_rect.x = window->screen->rect.width; + icon_rect.y = window->screen->rect.height; + icon_rect.width = 1; + icon_rect.height = 1; + } + + meta_window_get_outer_rect (window, &window_rect); + + meta_effect_run_minimize (window, + &window_rect, + &icon_rect, + finish_minimize, + window); + } + else + { + finish_minimize (window); + } + } + else + { + meta_window_show (window); + } +} + +void +meta_window_calc_showing (MetaWindow *window) +{ + implement_showing (window, meta_window_should_be_showing (window)); +} + +static guint queue_idle[NUMBER_OF_QUEUES] = {0, 0, 0}; +static GSList *queue_pending[NUMBER_OF_QUEUES] = {NULL, NULL, NULL}; + +static int +stackcmp (gconstpointer a, gconstpointer b) +{ + MetaWindow *aw = (gpointer) a; + MetaWindow *bw = (gpointer) b; + + if (aw->screen != bw->screen) + return 0; /* don't care how they sort with respect to each other */ + else + return meta_stack_windows_cmp (aw->screen->stack, + aw, bw); +} + +static gboolean +idle_calc_showing (gpointer data) +{ + GSList *tmp; + GSList *copy; + GSList *should_show; + GSList *should_hide; + GSList *unplaced; + GSList *displays; + MetaWindow *first_window; + guint queue_index = GPOINTER_TO_INT (data); + + meta_topic (META_DEBUG_WINDOW_STATE, + "Clearing the calc_showing queue\n"); + + /* Work with a copy, for reentrancy. The allowed reentrancy isn't + * complete; destroying a window while we're in here would result in + * badness. But it's OK to queue/unqueue calc_showings. + */ + copy = g_slist_copy (queue_pending[queue_index]); + g_slist_free (queue_pending[queue_index]); + queue_pending[queue_index] = NULL; + queue_idle[queue_index] = 0; + + destroying_windows_disallowed += 1; + + /* We map windows from top to bottom and unmap from bottom to + * top, to avoid extra expose events. The exception is + * for unplaced windows, which have to be mapped from bottom to + * top so placement works. + */ + should_show = NULL; + should_hide = NULL; + unplaced = NULL; + displays = NULL; + + tmp = copy; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + if (!window->placed) + unplaced = g_slist_prepend (unplaced, window); + else if (meta_window_should_be_showing (window)) + should_show = g_slist_prepend (should_show, window); + else + should_hide = g_slist_prepend (should_hide, window); + + tmp = tmp->next; + } + + /* bottom to top */ + unplaced = g_slist_sort (unplaced, stackcmp); + should_hide = g_slist_sort (should_hide, stackcmp); + /* top to bottom */ + should_show = g_slist_sort (should_show, stackcmp); + should_show = g_slist_reverse (should_show); + + first_window = copy->data; + + meta_display_grab (first_window->display); + + tmp = unplaced; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + meta_window_calc_showing (window); + + tmp = tmp->next; + } + + tmp = should_show; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + implement_showing (window, TRUE); + + tmp = tmp->next; + } + + tmp = should_hide; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + implement_showing (window, FALSE); + + tmp = tmp->next; + } + + tmp = copy; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + /* important to set this here for reentrancy - + * if we queue a window again while it's in "copy", + * then queue_calc_showing will just return since + * we are still in the calc_showing queue + */ + window->is_in_queues &= ~META_QUEUE_CALC_SHOWING; + + tmp = tmp->next; + } + + if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK) + { + /* When display->mouse_mode is false, we want to ignore + * EnterNotify events unless they come from mouse motion. To do + * that, we set a sentinel property on the root window if we're + * not in mouse_mode. + */ + tmp = should_show; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (!window->display->mouse_mode) + meta_display_increment_focus_sentinel (window->display); + + tmp = tmp->next; + } + } + + meta_display_ungrab (first_window->display); + + g_slist_free (copy); + + g_slist_free (unplaced); + g_slist_free (should_show); + g_slist_free (should_hide); + g_slist_free (displays); + + destroying_windows_disallowed -= 1; + + return FALSE; +} + +#ifdef WITH_VERBOSE_MODE +static const gchar* meta_window_queue_names[NUMBER_OF_QUEUES] = + {"calc_showing", "move_resize", "update_icon"}; +#endif + +static void +meta_window_unqueue (MetaWindow *window, guint queuebits) +{ + gint queuenum; + + for (queuenum=0; queuenum<NUMBER_OF_QUEUES; queuenum++) + { + if ((queuebits & 1<<queuenum) /* they have asked to unqueue */ + && + (window->is_in_queues & 1<<queuenum)) /* it's in the queue */ + { + + meta_topic (META_DEBUG_WINDOW_STATE, + "Removing %s from the %s queue\n", + window->desc, + meta_window_queue_names[queuenum]); + + /* Note that window may not actually be in the queue + * because it may have been in "copy" inside the idle handler + */ + queue_pending[queuenum] = g_slist_remove (queue_pending[queuenum], window); + window->is_in_queues &= ~(1<<queuenum); + + /* Okay, so maybe we've used up all the entries in the queue. + * In that case, we should kill the function that deals with + * the queue, because there's nothing left for it to do. + */ + if (queue_pending[queuenum] == NULL && queue_idle[queuenum] != 0) + { + g_source_remove (queue_idle[queuenum]); + queue_idle[queuenum] = 0; + } + } + } +} + +static void +meta_window_flush_calc_showing (MetaWindow *window) +{ + if (window->is_in_queues & META_QUEUE_CALC_SHOWING) + { + meta_window_unqueue (window, META_QUEUE_CALC_SHOWING); + meta_window_calc_showing (window); + } +} + +void +meta_window_queue (MetaWindow *window, guint queuebits) +{ + guint queuenum; + + for (queuenum=0; queuenum<NUMBER_OF_QUEUES; queuenum++) + { + if (queuebits & 1<<queuenum) + { + /* Data which varies between queues. + * Yes, these do look a lot like associative arrays: + * I seem to be turning into a Perl programmer. + */ + + const gint window_queue_idle_priority[NUMBER_OF_QUEUES] = + { + G_PRIORITY_DEFAULT_IDLE, /* CALC_SHOWING */ + META_PRIORITY_RESIZE, /* MOVE_RESIZE */ + G_PRIORITY_DEFAULT_IDLE /* UPDATE_ICON */ + }; + + const GSourceFunc window_queue_idle_handler[NUMBER_OF_QUEUES] = + { + idle_calc_showing, + idle_move_resize, + idle_update_icon, + }; + + /* If we're about to drop the window, there's no point in putting + * it on a queue. + */ + if (window->unmanaging) + break; + + /* If the window already claims to be in that queue, there's no + * point putting it in the queue. + */ + if (window->is_in_queues & 1<<queuenum) + break; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Putting %s in the %s queue\n", + window->desc, + meta_window_queue_names[queuenum]); + + /* So, mark it as being in this queue. */ + window->is_in_queues |= 1<<queuenum; + + /* There's not a lot of point putting things into a queue if + * nobody's on the other end pulling them out. Therefore, + * let's check to see whether an idle handler exists to do + * that. If not, we'll create one. + */ + + if (queue_idle[queuenum] == 0) + queue_idle[queuenum] = g_idle_add_full + ( + window_queue_idle_priority[queuenum], + window_queue_idle_handler[queuenum], + GUINT_TO_POINTER(queuenum), + NULL + ); + + /* And now we actually put it on the queue. */ + queue_pending[queuenum] = g_slist_prepend (queue_pending[queuenum], + window); + } + } +} + +static gboolean +intervening_user_event_occurred (MetaWindow *window) +{ + guint32 compare; + MetaWindow *focus_window; + + focus_window = window->display->focus_window; + + meta_topic (META_DEBUG_STARTUP, + "COMPARISON:\n" + " net_wm_user_time_set : %d\n" + " net_wm_user_time : %u\n" + " initial_timestamp_set: %d\n" + " initial_timestamp : %u\n", + window->net_wm_user_time_set, + window->net_wm_user_time, + window->initial_timestamp_set, + window->initial_timestamp); + if (focus_window != NULL) + { + meta_topic (META_DEBUG_STARTUP, + "COMPARISON (continued):\n" + " focus_window : %s\n" + " fw->net_wm_user_time_set : %d\n" + " fw->net_wm_user_time : %u\n", + focus_window->desc, + focus_window->net_wm_user_time_set, + focus_window->net_wm_user_time); + } + + /* We expect the most common case for not focusing a new window + * to be when a hint to not focus it has been set. Since we can + * deal with that case rapidly, we use special case it--this is + * merely a preliminary optimization. :) + */ + if ( ((window->net_wm_user_time_set == TRUE) && + (window->net_wm_user_time == 0)) + || + ((window->initial_timestamp_set == TRUE) && + (window->initial_timestamp == 0))) + { + meta_topic (META_DEBUG_STARTUP, + "window %s explicitly requested no focus\n", + window->desc); + return TRUE; + } + + if (!(window->net_wm_user_time_set) && !(window->initial_timestamp_set)) + { + meta_topic (META_DEBUG_STARTUP, + "no information about window %s found\n", + window->desc); + return FALSE; + } + + if (focus_window != NULL && + !focus_window->net_wm_user_time_set) + { + meta_topic (META_DEBUG_STARTUP, + "focus window, %s, doesn't have a user time set yet!\n", + window->desc); + return FALSE; + } + + /* To determine the "launch" time of an application, + * startup-notification can set the TIMESTAMP and the + * application (usually via its toolkit such as gtk or qt) can + * set the _NET_WM_USER_TIME. If both are set, we need to be + * using the newer of the two values. + * + * See http://bugzilla.gnome.org/show_bug.cgi?id=573922 + */ + compare = 0; + if (window->net_wm_user_time_set && + window->initial_timestamp_set) + compare = + XSERVER_TIME_IS_BEFORE (window->net_wm_user_time, + window->initial_timestamp) ? + window->initial_timestamp : window->net_wm_user_time; + else if (window->net_wm_user_time_set) + compare = window->net_wm_user_time; + else if (window->initial_timestamp_set) + compare = window->initial_timestamp; + + if ((focus_window != NULL) && + XSERVER_TIME_IS_BEFORE (compare, focus_window->net_wm_user_time)) + { + meta_topic (META_DEBUG_STARTUP, + "window %s focus prevented by other activity; %u < %u\n", + window->desc, + compare, + focus_window->net_wm_user_time); + return TRUE; + } + else + { + meta_topic (META_DEBUG_STARTUP, + "new window %s with no intervening events\n", + window->desc); + return FALSE; + } +} + +/* This function is an ugly hack. It's experimental in nature and ought to be + * replaced by a real hint from the app to the WM if we decide the experimental + * behavior is worthwhile. The basic idea is to get more feedback about how + * usage scenarios of "strict" focus users and what they expect. See #326159. + */ +gboolean +__window_is_terminal (MetaWindow *window) +{ + if (window == NULL || window->res_class == NULL) + return FALSE; + + /* + * Compare res_class, which is not user-settable, and thus theoretically + * a more-reliable indication of term-ness. + */ + + /* mate-terminal -- if you couldn't guess */ + if (strcmp (window->res_class, "Mate-terminal") == 0) + return TRUE; + /* xterm, rxvt, aterm */ + else if (strcmp (window->res_class, "XTerm") == 0) + return TRUE; + /* konsole, KDE's terminal program */ + else if (strcmp (window->res_class, "Konsole") == 0) + return TRUE; + /* rxvt-unicode */ + else if (strcmp (window->res_class, "URxvt") == 0) + return TRUE; + /* eterm */ + else if (strcmp (window->res_class, "Eterm") == 0) + return TRUE; + /* KTerm -- some terminal not KDE based; so not like Konsole */ + else if (strcmp (window->res_class, "KTerm") == 0) + return TRUE; + /* Multi-mate-terminal */ + else if (strcmp (window->res_class, "Multi-mate-terminal") == 0) + return TRUE; + /* mlterm ("multi lingual terminal emulator on X") */ + else if (strcmp (window->res_class, "mlterm") == 0) + return TRUE; + /* Terminal -- XFCE Terminal */ + else if (strcmp (window->res_class, "Terminal") == 0) + return TRUE; + + return FALSE; +} + +/* This function determines what state the window should have assuming that it + * and the focus_window have no relation + */ +static void +window_state_on_map (MetaWindow *window, + gboolean *takes_focus, + gboolean *places_on_top) +{ + gboolean intervening_events; + + intervening_events = intervening_user_event_occurred (window); + + *takes_focus = !intervening_events; + *places_on_top = *takes_focus; + + /* don't initially focus windows that are intended to not accept + * focus + */ + if (!(window->input || window->take_focus)) + { + *takes_focus = FALSE; + return; + } + + /* Terminal usage may be different; some users intend to launch + * many apps in quick succession or to just view things in the new + * window while still interacting with the terminal. In that case, + * apps launched from the terminal should not take focus. This + * isn't quite the same as not allowing focus to transfer from + * terminals due to new window map, but the latter is a much easier + * approximation to enforce so we do that. + */ + if (*takes_focus && + meta_prefs_get_focus_new_windows () == META_FOCUS_NEW_WINDOWS_STRICT && + !window->display->allow_terminal_deactivation && + __window_is_terminal (window->display->focus_window) && + !meta_window_is_ancestor_of_transient (window->display->focus_window, + window)) + { + meta_topic (META_DEBUG_FOCUS, + "focus_window is terminal; not focusing new window.\n"); + *takes_focus = FALSE; + *places_on_top = FALSE; + } + + switch (window->type) + { + case META_WINDOW_UTILITY: + case META_WINDOW_TOOLBAR: + *takes_focus = FALSE; + *places_on_top = FALSE; + break; + case META_WINDOW_DOCK: + case META_WINDOW_DESKTOP: + case META_WINDOW_SPLASHSCREEN: + case META_WINDOW_MENU: + /* don't focus any of these; places_on_top may be irrelevant for some of + * these (e.g. dock)--but you never know--the focus window might also be + * of the same type in some weird situation... + */ + *takes_focus = FALSE; + break; + case META_WINDOW_NORMAL: + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + /* The default is correct for these */ + break; + } +} + +static gboolean +windows_overlap (const MetaWindow *w1, const MetaWindow *w2) +{ + MetaRectangle w1rect, w2rect; + meta_window_get_outer_rect (w1, &w1rect); + meta_window_get_outer_rect (w2, &w2rect); + return meta_rectangle_overlap (&w1rect, &w2rect); +} + +/* Returns whether a new window would be covered by any + * existing window on the same workspace that is set + * to be "above" ("always on top"). A window that is not + * set "above" would be underneath the new window anyway. + * + * We take "covered" to mean even partially covered, but + * some people might prefer entirely covered. I think it + * is more useful to behave this way if any part of the + * window is covered, because a partial coverage could be + * (say) ninety per cent and almost indistinguishable from total. + */ +static gboolean +window_would_be_covered (const MetaWindow *newbie) +{ + MetaWorkspace *workspace = newbie->workspace; + GList *tmp, *windows; + + windows = meta_workspace_list_windows (workspace); + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->wm_state_above && w != newbie) + { + /* We have found a window that is "above". Perhaps it overlaps. */ + if (windows_overlap (w, newbie)) + { + g_list_free (windows); /* clean up... */ + return TRUE; /* yes, it does */ + } + } + + tmp = tmp->next; + } + + g_list_free (windows); + return FALSE; /* none found */ +} + +/* XXX META_EFFECT_*_MAP */ +void +meta_window_show (MetaWindow *window) +{ + gboolean did_show; + gboolean takes_focus_on_map; + gboolean place_on_top_on_map; + gboolean needs_stacking_adjustment; + MetaWindow *focus_window; + guint32 timestamp; + + /* FIXME: It really sucks to put timestamp pinging here; it'd + * probably make more sense in implement_showing() so that it's at + * least not duplicated in finish_minimize. *shrug* + */ + timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_topic (META_DEBUG_WINDOW_STATE, + "Showing window %s, shaded: %d iconic: %d placed: %d\n", + window->desc, window->shaded, window->iconic, window->placed); + + focus_window = window->display->focus_window; /* May be NULL! */ + did_show = FALSE; + window_state_on_map (window, &takes_focus_on_map, &place_on_top_on_map); + needs_stacking_adjustment = FALSE; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Window %s %s focus on map, and %s place on top on map.\n", + window->desc, + takes_focus_on_map ? "does" : "does not", + place_on_top_on_map ? "does" : "does not"); + + /* Now, in some rare cases we should *not* put a new window on top. + * These cases include certain types of windows showing for the first + * time, and any window which would be covered because of another window + * being set "above" ("always on top"). + * + * FIXME: Although "place_on_top_on_map" and "takes_focus_on_map" are + * generally based on the window type, there is a special case when the + * focus window is a terminal for them both to be false; this should + * probably rather be a term in the "if" condition below. + */ + + if ( focus_window != NULL && window->showing_for_first_time && + ( (!place_on_top_on_map && !takes_focus_on_map) || + window_would_be_covered (window) ) + ) { + if (meta_window_is_ancestor_of_transient (focus_window, window)) + { + /* This happens for error dialogs or alerts; these need to remain on + * top, but it would be confusing to have its ancestor remain + * focused. + */ + meta_topic (META_DEBUG_STARTUP, + "The focus window %s is an ancestor of the newly mapped " + "window %s which isn't being focused. Unfocusing the " + "ancestor.\n", + focus_window->desc, window->desc); + + meta_display_focus_the_no_focus_window (window->display, + window->screen, + timestamp); + } + else + { + needs_stacking_adjustment = TRUE; + if (!window->placed) + window->denied_focus_and_not_transient = TRUE; + } + } + + if (!window->placed) + { + /* We have to recalc the placement here since other windows may + * have been mapped/placed since we last did constrain_position + */ + + /* calc_placement is an efficiency hack to avoid + * multiple placement calculations before we finally + * show the window. + */ + window->calc_placement = TRUE; + meta_window_move_resize_now (window); + window->calc_placement = FALSE; + + /* don't ever do the initial position constraint thing again. + * This is toggled here so that initially-iconified windows + * still get placed when they are ultimately shown. + */ + window->placed = TRUE; + + /* Don't want to accidentally reuse the fact that we had been denied + * focus in any future constraints unless we're denied focus again. + */ + window->denied_focus_and_not_transient = FALSE; + } + + if (needs_stacking_adjustment) + { + gboolean overlap; + + /* This window isn't getting focus on map. We may need to do some + * special handing with it in regards to + * - the stacking of the window + * - the MRU position of the window + * - the demands attention setting of the window + * + * Firstly, set the flag so we don't give the window focus anyway + * and confuse people. + */ + + takes_focus_on_map = FALSE; + + overlap = windows_overlap (window, focus_window); + + /* We want alt tab to go to the denied-focus window */ + ensure_mru_position_after (window, focus_window); + + /* We don't want the denied-focus window to obscure the focus + * window, and if we're in both click-to-focus mode and + * raise-on-click mode then we want to maintain the invariant + * that MRU order == stacking order. The need for this if + * comes from the fact that in sloppy/mouse focus the focus + * window may not overlap other windows and also can be + * considered "below" them; this combination means that + * placing the denied-focus window "below" the focus window + * in the stack when it doesn't overlap it confusingly places + * that new window below a lot of other windows. + */ + if (overlap || + (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK && + meta_prefs_get_raise_on_click ())) + meta_window_stack_just_below (window, focus_window); + + /* If the window will be obscured by the focus window, then the + * user might not notice the window appearing so set the + * demands attention hint. + * + * We set the hint ourselves rather than calling + * meta_window_set_demands_attention() because that would cause + * a recalculation of overlap, and a call to set_net_wm_state() + * which we are going to call ourselves here a few lines down. + */ + if (overlap) + window->wm_state_demands_attention = TRUE; + } + + /* Shaded means the frame is mapped but the window is not */ + + if (window->frame && !window->frame->mapped) + { + meta_topic (META_DEBUG_WINDOW_STATE, + "Frame actually needs map\n"); + window->frame->mapped = TRUE; + meta_ui_map_frame (window->screen->ui, window->frame->xwindow); + did_show = TRUE; + } + + if (window->shaded) + { + if (window->mapped) + { + meta_topic (META_DEBUG_WINDOW_STATE, + "%s actually needs unmap (shaded)\n", window->desc); + meta_topic (META_DEBUG_WINDOW_STATE, + "Incrementing unmaps_pending on %s for shade\n", + window->desc); + window->mapped = FALSE; + window->unmaps_pending += 1; + meta_error_trap_push (window->display); + XUnmapWindow (window->display->xdisplay, window->xwindow); + meta_error_trap_pop (window->display, FALSE); + } + + if (!window->iconic) + { + window->iconic = TRUE; + set_wm_state (window, IconicState); + } + } + else + { + if (!window->mapped) + { + meta_topic (META_DEBUG_WINDOW_STATE, + "%s actually needs map\n", window->desc); + window->mapped = TRUE; + meta_error_trap_push (window->display); + XMapWindow (window->display->xdisplay, window->xwindow); + meta_error_trap_pop (window->display, FALSE); + did_show = TRUE; + + if (window->was_minimized) + { + MetaRectangle window_rect; + MetaRectangle icon_rect; + + window->was_minimized = FALSE; + + if (meta_window_get_icon_geometry (window, &icon_rect)) + { + meta_window_get_outer_rect (window, &window_rect); + + meta_effect_run_unminimize (window, + &window_rect, + &icon_rect, + NULL, NULL); + } + } + } + + if (window->iconic) + { + window->iconic = FALSE; + set_wm_state (window, NormalState); + } + } + + /* We don't want to worry about all cases from inside + * implement_showing(); we only want to worry about focus if this + * window has not been shown before. + */ + if (window->showing_for_first_time) + { + window->showing_for_first_time = FALSE; + if (takes_focus_on_map) + { + meta_window_focus (window, timestamp); + } + else + { + /* Prevent EnterNotify events in sloppy/mouse focus from + * erroneously focusing the window that had been denied + * focus. FIXME: This introduces a race; I have a couple + * ideas for a better way to accomplish the same thing, but + * they're more involved so do it this way for now. + */ + meta_display_increment_focus_sentinel (window->display); + } + } + + set_net_wm_state (window); + + if (did_show && window->struts) + { + meta_topic (META_DEBUG_WORKAREA, + "Mapped window %s with struts, so invalidating work areas\n", + window->desc); + invalidate_work_areas (window); + } + + /* + * Now that we have shown the window, we no longer want to consider the + * initial timestamp in any subsequent deliberations whether to focus this + * window or not, so clear the flag. + * + * See http://bugzilla.gnome.org/show_bug.cgi?id=573922 + */ + window->initial_timestamp_set = FALSE; +} + +/* XXX META_EFFECT_*_UNMAP */ +static void +meta_window_hide (MetaWindow *window) +{ + gboolean did_hide; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Hiding window %s\n", window->desc); + + did_hide = FALSE; + + if (window->frame && window->frame->mapped) + { + meta_topic (META_DEBUG_WINDOW_STATE, "Frame actually needs unmap\n"); + window->frame->mapped = FALSE; + meta_ui_unmap_frame (window->screen->ui, window->frame->xwindow); + did_hide = TRUE; + } + + if (window->mapped) + { + meta_topic (META_DEBUG_WINDOW_STATE, + "%s actually needs unmap\n", window->desc); + meta_topic (META_DEBUG_WINDOW_STATE, + "Incrementing unmaps_pending on %s for hide\n", + window->desc); + window->mapped = FALSE; + window->unmaps_pending += 1; + meta_error_trap_push (window->display); + XUnmapWindow (window->display->xdisplay, window->xwindow); + meta_error_trap_pop (window->display, FALSE); + did_hide = TRUE; + } + + if (!window->iconic) + { + window->iconic = TRUE; + set_wm_state (window, IconicState); + } + + set_net_wm_state (window); + + if (did_hide && window->struts) + { + meta_topic (META_DEBUG_WORKAREA, + "Unmapped window %s with struts, so invalidating work areas\n", + window->desc); + invalidate_work_areas (window); + } +} + +static gboolean +queue_calc_showing_func (MetaWindow *window, + void *data) +{ + meta_window_queue(window, META_QUEUE_CALC_SHOWING); + return TRUE; +} + +void +meta_window_minimize (MetaWindow *window) +{ + if (!window->minimized) + { + window->minimized = TRUE; + meta_window_queue(window, META_QUEUE_CALC_SHOWING); + + meta_window_foreach_transient (window, + queue_calc_showing_func, + NULL); + + if (window->has_focus) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing default window due to minimization of focus window %s\n", + window->desc); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Minimizing window %s which doesn't have the focus\n", + window->desc); + } + } +} + +void +meta_window_unminimize (MetaWindow *window) +{ + if (window->minimized) + { + window->minimized = FALSE; + window->was_minimized = TRUE; + meta_window_queue(window, META_QUEUE_CALC_SHOWING); + + meta_window_foreach_transient (window, + queue_calc_showing_func, + NULL); + } +} + +static void +ensure_size_hints_satisfied (MetaRectangle *rect, + const XSizeHints *size_hints) +{ + int minw, minh, maxw, maxh; /* min/max width/height */ + int basew, baseh, winc, hinc; /* base width/height, width/height increment */ + int extra_width, extra_height; + + minw = size_hints->min_width; minh = size_hints->min_height; + maxw = size_hints->max_width; maxh = size_hints->max_height; + basew = size_hints->base_width; baseh = size_hints->base_height; + winc = size_hints->width_inc; hinc = size_hints->height_inc; + + /* First, enforce min/max size constraints */ + rect->width = CLAMP (rect->width, minw, maxw); + rect->height = CLAMP (rect->height, minh, maxh); + + /* Now, verify size increment constraints are satisfied, or make them be */ + extra_width = (rect->width - basew) % winc; + extra_height = (rect->height - baseh) % hinc; + + rect->width -= extra_width; + rect->height -= extra_height; + + /* Adjusting width/height down, as done above, may violate minimum size + * constraints, so one last fix. + */ + if (rect->width < minw) + rect->width += ((minw - rect->width)/winc + 1)*winc; + if (rect->height < minh) + rect->height += ((minh - rect->height)/hinc + 1)*hinc; +} + +static void +meta_window_save_rect (MetaWindow *window) +{ + if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen)) + { + /* save size/pos as appropriate args for move_resize */ + if (!window->maximized_horizontally) + { + window->saved_rect.x = window->rect.x; + window->saved_rect.width = window->rect.width; + if (window->frame) + window->saved_rect.x += window->frame->rect.x; + } + if (!window->maximized_vertically) + { + window->saved_rect.y = window->rect.y; + window->saved_rect.height = window->rect.height; + if (window->frame) + window->saved_rect.y += window->frame->rect.y; + } + } +} + +/** + * Save the user_rect regardless of whether the window is maximized or + * fullscreen. See save_user_window_placement() for most uses. + * + * \param window Store current position of this window for future reference + */ +static void +force_save_user_window_placement (MetaWindow *window) +{ + meta_window_get_client_root_coords (window, &window->user_rect); +} + +/** + * Save the user_rect, but only if the window is neither maximized nor + * fullscreen, otherwise the window may snap back to those dimensions + * (bug #461927). + * + * \param window Store current position of this window for future reference + */ +static void +save_user_window_placement (MetaWindow *window) +{ + if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen)) + { + MetaRectangle user_rect; + + meta_window_get_client_root_coords (window, &user_rect); + + if (!window->maximized_horizontally) + { + window->user_rect.x = user_rect.x; + window->user_rect.width = user_rect.width; + } + if (!window->maximized_vertically) + { + window->user_rect.y = user_rect.y; + window->user_rect.height = user_rect.height; + } + } +} + +void +meta_window_maximize_internal (MetaWindow *window, + MetaMaximizeFlags directions, + MetaRectangle *saved_rect) +{ + /* At least one of the two directions ought to be set */ + gboolean maximize_horizontally, maximize_vertically; + maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + maximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (maximize_horizontally || maximize_vertically); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Maximizing %s%s\n", + window->desc, + maximize_horizontally && maximize_vertically ? "" : + maximize_horizontally ? " horizontally" : + maximize_vertically ? " vertically" : "BUGGGGG"); + + if (saved_rect != NULL) + window->saved_rect = *saved_rect; + else + meta_window_save_rect (window); + + window->maximized_horizontally = + window->maximized_horizontally || maximize_horizontally; + window->maximized_vertically = + window->maximized_vertically || maximize_vertically; + if (maximize_horizontally || maximize_vertically) + window->force_save_user_rect = FALSE; + + /* Fix for #336850: If the frame shape isn't reapplied, it is + * possible that the frame will retains its rounded corners. That + * happens if the client's size when maximized equals the unmaximized + * size. + */ + if (window->frame) + window->frame->need_reapply_frame_shape = TRUE; + + recalc_window_features (window); + set_net_wm_state (window); +} + +void +meta_window_maximize (MetaWindow *window, + MetaMaximizeFlags directions) +{ + /* At least one of the two directions ought to be set */ + gboolean maximize_horizontally, maximize_vertically; + maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + maximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (maximize_horizontally || maximize_vertically); + + /* Only do something if the window isn't already maximized in the + * given direction(s). + */ + if ((maximize_horizontally && !window->maximized_horizontally) || + (maximize_vertically && !window->maximized_vertically)) + { + if (window->shaded && maximize_vertically) + { + /* Shading sucks anyway; I'm not adding a timestamp argument + * to this function just for this niche usage & corner case. + */ + guint32 timestamp = + meta_display_get_current_time_roundtrip (window->display); + meta_window_unshade (window, timestamp); + } + + /* if the window hasn't been placed yet, we'll maximize it then + */ + if (!window->placed) + { + window->maximize_horizontally_after_placement = + window->maximize_horizontally_after_placement || + maximize_horizontally; + window->maximize_vertically_after_placement = + window->maximize_vertically_after_placement || + maximize_vertically; + return; + } + + meta_window_maximize_internal (window, + directions, + NULL); + + /* move_resize with new maximization constraints + */ + meta_window_queue(window, META_QUEUE_MOVE_RESIZE); + } +} + +static void +unmaximize_window_before_freeing (MetaWindow *window) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Unmaximizing %s just before freeing\n", + window->desc); + + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; + + if (window->withdrawn) /* See bug #137185 */ + { + window->rect = window->saved_rect; + set_net_wm_state (window); + } + else if (window->screen->closing) /* See bug #358042 */ + { + /* Do NOT update net_wm_state: this screen is closing, + * it likely will be managed by another window manager + * that will need the current _NET_WM_STATE atoms. + * Moreover, it will need to know the unmaximized geometry, + * therefore move_resize the window to saved_rect here + * before closing it. */ + meta_window_move_resize (window, + FALSE, + window->saved_rect.x, + window->saved_rect.y, + window->saved_rect.width, + window->saved_rect.height); + } +} + +void +meta_window_unmaximize (MetaWindow *window, + MetaMaximizeFlags directions) +{ + /* At least one of the two directions ought to be set */ + gboolean unmaximize_horizontally, unmaximize_vertically; + unmaximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + unmaximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (unmaximize_horizontally || unmaximize_vertically); + + /* Only do something if the window isn't already maximized in the + * given direction(s). + */ + if ((unmaximize_horizontally && window->maximized_horizontally) || + (unmaximize_vertically && window->maximized_vertically)) + { + MetaRectangle target_rect; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Unmaximizing %s%s\n", + window->desc, + unmaximize_horizontally && unmaximize_vertically ? "" : + unmaximize_horizontally ? " horizontally" : + unmaximize_vertically ? " vertically" : "BUGGGGG"); + + window->maximized_horizontally = + window->maximized_horizontally && !unmaximize_horizontally; + window->maximized_vertically = + window->maximized_vertically && !unmaximize_vertically; + + /* Unmaximize to the saved_rect position in the direction(s) + * being unmaximized. + */ + meta_window_get_client_root_coords (window, &target_rect); + if (unmaximize_horizontally) + { + target_rect.x = window->saved_rect.x; + target_rect.width = window->saved_rect.width; + } + if (unmaximize_vertically) + { + target_rect.y = window->saved_rect.y; + target_rect.height = window->saved_rect.height; + } + + /* Window's size hints may have changed while maximized, making + * saved_rect invalid. #329152 + */ + ensure_size_hints_satisfied (&target_rect, &window->size_hints); + + /* When we unmaximize, if we're doing a mouse move also we could + * get the window suddenly jumping to the upper left corner of + * the workspace, since that's where it was when the grab op + * started. So we need to update the grab state. + */ + if (meta_grab_op_is_moving (window->display->grab_op) && + window->display->grab_window == window) + { + window->display->grab_anchor_window_pos = target_rect; + } + + meta_window_move_resize (window, + FALSE, + target_rect.x, + target_rect.y, + target_rect.width, + target_rect.height); + + /* Make sure user_rect is current. + */ + force_save_user_window_placement (window); + + if (window->display->grab_wireframe_active) + { + window->display->grab_wireframe_rect = target_rect; + } + + recalc_window_features (window); + set_net_wm_state (window); + } +} + +void +meta_window_make_above (MetaWindow *window) +{ + window->wm_state_above = TRUE; + meta_window_update_layer (window); + meta_window_raise (window); + set_net_wm_state (window); +} + +void +meta_window_unmake_above (MetaWindow *window) +{ + window->wm_state_above = FALSE; + meta_window_raise (window); + meta_window_update_layer (window); + set_net_wm_state (window); +} + +void +meta_window_make_fullscreen_internal (MetaWindow *window) +{ + if (!window->fullscreen) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Fullscreening %s\n", window->desc); + + if (window->shaded) + { + /* Shading sucks anyway; I'm not adding a timestamp argument + * to this function just for this niche usage & corner case. + */ + guint32 timestamp = + meta_display_get_current_time_roundtrip (window->display); + meta_window_unshade (window, timestamp); + } + + meta_window_save_rect (window); + + window->fullscreen = TRUE; + window->force_save_user_rect = FALSE; + + meta_stack_freeze (window->screen->stack); + meta_window_update_layer (window); + + meta_window_raise (window); + meta_stack_thaw (window->screen->stack); + + recalc_window_features (window); + set_net_wm_state (window); + } +} + +void +meta_window_make_fullscreen (MetaWindow *window) +{ + if (!window->fullscreen) + { + meta_window_make_fullscreen_internal (window); + /* move_resize with new constraints + */ + meta_window_queue(window, META_QUEUE_MOVE_RESIZE); + } +} + +void +meta_window_unmake_fullscreen (MetaWindow *window) +{ + if (window->fullscreen) + { + MetaRectangle target_rect; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Unfullscreening %s\n", window->desc); + + window->fullscreen = FALSE; + target_rect = window->saved_rect; + + /* Window's size hints may have changed while maximized, making + * saved_rect invalid. #329152 + */ + ensure_size_hints_satisfied (&target_rect, &window->size_hints); + + meta_window_move_resize (window, + FALSE, + target_rect.x, + target_rect.y, + target_rect.width, + target_rect.height); + + /* Make sure user_rect is current. + */ + force_save_user_window_placement (window); + + meta_window_update_layer (window); + + recalc_window_features (window); + set_net_wm_state (window); + } +} + +void +meta_window_update_fullscreen_monitors (MetaWindow *window, + unsigned long top, + unsigned long bottom, + unsigned long left, + unsigned long right) +{ + if ((int)top < window->screen->n_xinerama_infos && + (int)bottom < window->screen->n_xinerama_infos && + (int)left < window->screen->n_xinerama_infos && + (int)right < window->screen->n_xinerama_infos) + { + window->fullscreen_monitors[0] = top; + window->fullscreen_monitors[1] = bottom; + window->fullscreen_monitors[2] = left; + window->fullscreen_monitors[3] = right; + } + else + { + window->fullscreen_monitors[0] = -1; + } + + if (window->fullscreen) + { + meta_window_queue(window, META_QUEUE_MOVE_RESIZE); + } +} + +void +meta_window_shade (MetaWindow *window, + guint32 timestamp) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Shading %s\n", window->desc); + if (!window->shaded) + { + window->shaded = TRUE; + + meta_window_queue(window, META_QUEUE_MOVE_RESIZE | META_QUEUE_CALC_SHOWING); + + /* After queuing the calc showing, since _focus flushes it, + * and we need to focus the frame + */ + meta_topic (META_DEBUG_FOCUS, + "Re-focusing window %s after shading it\n", + window->desc); + meta_window_focus (window, timestamp); + + set_net_wm_state (window); + } +} + +void +meta_window_unshade (MetaWindow *window, + guint32 timestamp) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Unshading %s\n", window->desc); + if (window->shaded) + { + window->shaded = FALSE; + meta_window_queue(window, META_QUEUE_MOVE_RESIZE | META_QUEUE_CALC_SHOWING); + + /* focus the window */ + meta_topic (META_DEBUG_FOCUS, + "Focusing window %s after unshading it\n", + window->desc); + meta_window_focus (window, timestamp); + + set_net_wm_state (window); + } +} + +static gboolean +unminimize_func (MetaWindow *window, + void *data) +{ + meta_window_unminimize (window); + return TRUE; +} + +static void +unminimize_window_and_all_transient_parents (MetaWindow *window) +{ + meta_window_unminimize (window); + meta_window_foreach_ancestor (window, unminimize_func, NULL); +} + +static void +window_activate (MetaWindow *window, + guint32 timestamp, + MetaClientType source_indication, + MetaWorkspace *workspace) +{ + gboolean can_ignore_outdated_timestamps; + meta_topic (META_DEBUG_FOCUS, + "_NET_ACTIVE_WINDOW message sent for %s at time %u " + "by client type %u.\n", + window->desc, timestamp, source_indication); + + /* Older EWMH spec didn't specify a timestamp; we decide to honor these only + * if the app specifies that it is a pager. + * + * Update: Unconditionally honor 0 timestamps for now; we'll fight + * that battle later. Just remove the "FALSE &&" in order to only + * honor 0 timestamps for pagers. + */ + can_ignore_outdated_timestamps = + (timestamp != 0 || (FALSE && source_indication != META_CLIENT_TYPE_PAGER)); + if (XSERVER_TIME_IS_BEFORE (timestamp, window->display->last_user_time) && + can_ignore_outdated_timestamps) + { + meta_topic (META_DEBUG_FOCUS, + "last_user_time (%u) is more recent; ignoring " + " _NET_ACTIVE_WINDOW message.\n", + window->display->last_user_time); + meta_window_set_demands_attention(window); + return; + } + + /* For those stupid pagers, get a valid timestamp and show a warning */ + if (timestamp == 0) + { + meta_warning ("meta_window_activate called by a pager with a 0 timestamp; " + "the pager needs to be fixed.\n"); + timestamp = meta_display_get_current_time_roundtrip (window->display); + } + + meta_window_set_user_time (window, timestamp); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + + /* Get window on current or given workspace */ + if (workspace == NULL) + workspace = window->screen->active_workspace; + + /* For non-transient windows, we just set up a pulsing indicator, + rather than move windows or workspaces. + See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */ + if (window->xtransient_for == None && + !meta_window_located_on_workspace (window, workspace)) + { + meta_window_set_demands_attention (window); + /* We've marked it as demanding, don't need to do anything else. */ + return; + } + else if (window->xtransient_for != None) + { + /* Move transients to current workspace - preference dialogs should appear over + the source window. */ + meta_window_change_workspace (window, workspace); + } + + if (window->shaded) + meta_window_unshade (window, timestamp); + + unminimize_window_and_all_transient_parents (window); + + if (meta_prefs_get_raise_on_click () || + source_indication == META_CLIENT_TYPE_PAGER) + meta_window_raise (window); + + meta_topic (META_DEBUG_FOCUS, + "Focusing window %s due to activation\n", + window->desc); + meta_window_focus (window, timestamp); +} + +/* This function exists since most of the functionality in window_activate + * is useful for Marco, but Marco shouldn't need to specify a client + * type for itself. ;-) + */ +void +meta_window_activate (MetaWindow *window, + guint32 timestamp) +{ + /* We're not really a pager, but the behavior we want is the same as if + * we were such. If we change the pager behavior later, we could revisit + * this and just add extra flags to window_activate. + */ + window_activate (window, timestamp, META_CLIENT_TYPE_PAGER, NULL); +} + +void +meta_window_activate_with_workspace (MetaWindow *window, + guint32 timestamp, + MetaWorkspace *workspace) +{ + /* We're not really a pager, but the behavior we want is the same as if + * we were such. If we change the pager behavior later, we could revisit + * this and just add extra flags to window_activate. + */ + window_activate (window, timestamp, META_CLIENT_TYPE_APPLICATION, workspace); +} + +/* Manually fix all the weirdness explained in the big comment at the + * beginning of meta_window_move_resize_internal() giving positions + * expected by meta_window_constrain (i.e. positions & sizes of the + * internal or client window). + */ +static void +adjust_for_gravity (MetaWindow *window, + MetaFrameGeometry *fgeom, + gboolean coords_assume_border, + int gravity, + MetaRectangle *rect) +{ + int ref_x, ref_y; + int bw; + int child_x, child_y; + int frame_width, frame_height; + + if (coords_assume_border) + bw = window->border_width; + else + bw = 0; + + if (fgeom) + { + child_x = fgeom->left_width; + child_y = fgeom->top_height; + frame_width = child_x + rect->width + fgeom->right_width; + frame_height = child_y + rect->height + fgeom->bottom_height; + } + else + { + child_x = 0; + child_y = 0; + frame_width = rect->width; + frame_height = rect->height; + } + + /* We're computing position to pass to window_move, which is + * the position of the client window (StaticGravity basically) + * + * (see WM spec description of gravity computation, but note that + * their formulas assume we're honoring the border width, rather + * than compensating for having turned it off) + */ + switch (gravity) + { + case NorthWestGravity: + ref_x = rect->x; + ref_y = rect->y; + break; + case NorthGravity: + ref_x = rect->x + rect->width / 2 + bw; + ref_y = rect->y; + break; + case NorthEastGravity: + ref_x = rect->x + rect->width + bw * 2; + ref_y = rect->y; + break; + case WestGravity: + ref_x = rect->x; + ref_y = rect->y + rect->height / 2 + bw; + break; + case CenterGravity: + ref_x = rect->x + rect->width / 2 + bw; + ref_y = rect->y + rect->height / 2 + bw; + break; + case EastGravity: + ref_x = rect->x + rect->width + bw * 2; + ref_y = rect->y + rect->height / 2 + bw; + break; + case SouthWestGravity: + ref_x = rect->x; + ref_y = rect->y + rect->height + bw * 2; + break; + case SouthGravity: + ref_x = rect->x + rect->width / 2 + bw; + ref_y = rect->y + rect->height + bw * 2; + break; + case SouthEastGravity: + ref_x = rect->x + rect->width + bw * 2; + ref_y = rect->y + rect->height + bw * 2; + break; + case StaticGravity: + default: + ref_x = rect->x; + ref_y = rect->y; + break; + } + + switch (gravity) + { + case NorthWestGravity: + rect->x = ref_x + child_x; + rect->y = ref_y + child_y; + break; + case NorthGravity: + rect->x = ref_x - frame_width / 2 + child_x; + rect->y = ref_y + child_y; + break; + case NorthEastGravity: + rect->x = ref_x - frame_width + child_x; + rect->y = ref_y + child_y; + break; + case WestGravity: + rect->x = ref_x + child_x; + rect->y = ref_y - frame_height / 2 + child_y; + break; + case CenterGravity: + rect->x = ref_x - frame_width / 2 + child_x; + rect->y = ref_y - frame_height / 2 + child_y; + break; + case EastGravity: + rect->x = ref_x - frame_width + child_x; + rect->y = ref_y - frame_height / 2 + child_y; + break; + case SouthWestGravity: + rect->x = ref_x + child_x; + rect->y = ref_y - frame_height + child_y; + break; + case SouthGravity: + rect->x = ref_x - frame_width / 2 + child_x; + rect->y = ref_y - frame_height + child_y; + break; + case SouthEastGravity: + rect->x = ref_x - frame_width + child_x; + rect->y = ref_y - frame_height + child_y; + break; + case StaticGravity: + default: + rect->x = ref_x; + rect->y = ref_y; + break; + } +} + +static gboolean +static_gravity_works (MetaDisplay *display) +{ + return display->static_gravity_works; +} + +#ifdef HAVE_XSYNC +static void +send_sync_request (MetaWindow *window) +{ + XSyncValue value; + XClientMessageEvent ev; + + window->sync_request_serial++; + + XSyncIntToValue (&value, window->sync_request_serial); + + ev.type = ClientMessage; + ev.window = window->xwindow; + ev.message_type = window->display->atom_WM_PROTOCOLS; + ev.format = 32; + ev.data.l[0] = window->display->atom__NET_WM_SYNC_REQUEST; + /* FIXME: meta_display_get_current_time() is bad, but since calls + * come from meta_window_move_resize_internal (which in turn come + * from all over), I'm not sure what we can do to fix it. Do we + * want to use _roundtrip, though? + */ + ev.data.l[1] = meta_display_get_current_time (window->display); + ev.data.l[2] = XSyncValueLow32 (value); + ev.data.l[3] = XSyncValueHigh32 (value); + + /* We don't need to trap errors here as we are already + * inside an error_trap_push()/pop() pair. + */ + XSendEvent (window->display->xdisplay, + window->xwindow, False, 0, (XEvent*) &ev); + + g_get_current_time (&window->sync_request_time); +} +#endif + +static void +meta_window_move_resize_internal (MetaWindow *window, + MetaMoveResizeFlags flags, + int gravity, + int root_x_nw, + int root_y_nw, + int w, + int h) +{ + /* meta_window_move_resize_internal gets called with very different + * meanings for root_x_nw and root_y_nw. w & h are always the area + * of the inner or client window (i.e. excluding the frame) and + * gravity is the relevant gravity associated with the request (note + * that gravity is ignored for move-only operations unless its + * e.g. a configure request). The location is different for + * different cases because of how this function gets called; note + * that in all cases what we want to find out is the upper left + * corner of the position of the inner window: + * + * Case | Called from (flags; gravity) + * -----+----------------------------------------------- + * 1 | A resize only ConfigureRequest + * 1 | meta_window_resize + * 1 | meta_window_resize_with_gravity + * 2 | New window + * 2 | Session restore + * 2 | A not-resize-only ConfigureRequest/net_moveresize_window request + * 3 | meta_window_move + * 3 | meta_window_move_resize + * + * For each of the cases, root_x_nw and root_y_nw must be treated as follows: + * + * (1) They should be entirely ignored; instead the previous position + * and size of the window should be resized according to the given + * gravity in order to determine the new position of the window. + * (2) Needs to be fixed up by adjust_for_gravity() as these + * coordinates are relative to some corner or side of the outer + * window (except for the case of StaticGravity) and we want to + * know the location of the upper left corner of the inner window. + * (3) These values are already the desired positon of the NW corner + * of the inner window + */ + XWindowChanges values; + unsigned int mask; + gboolean need_configure_notify; + MetaFrameGeometry fgeom; + gboolean need_move_client = FALSE; + gboolean need_move_frame = FALSE; + gboolean need_resize_client = FALSE; + gboolean need_resize_frame = FALSE; + int frame_size_dx; + int frame_size_dy; + int size_dx; + int size_dy; + gboolean is_configure_request; + gboolean do_gravity_adjust; + gboolean is_user_action; + gboolean configure_frame_first; + gboolean use_static_gravity; + /* used for the configure request, but may not be final + * destination due to StaticGravity etc. + */ + int client_move_x; + int client_move_y; + MetaRectangle new_rect; + MetaRectangle old_rect; + + is_configure_request = (flags & META_IS_CONFIGURE_REQUEST) != 0; + do_gravity_adjust = (flags & META_DO_GRAVITY_ADJUST) != 0; + is_user_action = (flags & META_IS_USER_ACTION) != 0; + + /* The action has to be a move or a resize or both... */ + g_assert (flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)); + + /* We don't need it in the idle queue anymore. */ + meta_window_unqueue (window, META_QUEUE_MOVE_RESIZE); + + meta_window_get_client_root_coords (window, &old_rect); + + meta_topic (META_DEBUG_GEOMETRY, + "Move/resize %s to %d,%d %dx%d%s%s from %d,%d %dx%d\n", + window->desc, root_x_nw, root_y_nw, w, h, + is_configure_request ? " (configure request)" : "", + is_user_action ? " (user move/resize)" : "", + old_rect.x, old_rect.y, old_rect.width, old_rect.height); + + if (window->frame) + meta_frame_calc_geometry (window->frame, + &fgeom); + + new_rect.x = root_x_nw; + new_rect.y = root_y_nw; + new_rect.width = w; + new_rect.height = h; + + /* If this is a resize only, the position should be ignored and + * instead obtained by resizing the old rectangle according to the + * relevant gravity. + */ + if ((flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)) == + META_IS_RESIZE_ACTION) + { + meta_rectangle_resize_with_gravity (&old_rect, + &new_rect, + gravity, + new_rect.width, + new_rect.height); + + meta_topic (META_DEBUG_GEOMETRY, + "Compensated for gravity in resize action; new pos %d,%d\n", + new_rect.x, new_rect.y); + } + else if (is_configure_request || do_gravity_adjust) + { + adjust_for_gravity (window, + window->frame ? &fgeom : NULL, + /* configure request coords assume + * the border width existed + */ + is_configure_request, + gravity, + &new_rect); + + meta_topic (META_DEBUG_GEOMETRY, + "Compensated for configure_request/do_gravity_adjust needing " + "weird positioning; new pos %d,%d\n", + new_rect.x, new_rect.y); + } + + meta_window_constrain (window, + window->frame ? &fgeom : NULL, + flags, + gravity, + &old_rect, + &new_rect); + + w = new_rect.width; + h = new_rect.height; + root_x_nw = new_rect.x; + root_y_nw = new_rect.y; + + if (w != window->rect.width || + h != window->rect.height) + need_resize_client = TRUE; + + window->rect.width = w; + window->rect.height = h; + + if (window->frame) + { + int new_w, new_h; + + new_w = window->rect.width + fgeom.left_width + fgeom.right_width; + + if (window->shaded) + new_h = fgeom.top_height; + else + new_h = window->rect.height + fgeom.top_height + fgeom.bottom_height; + + frame_size_dx = new_w - window->frame->rect.width; + frame_size_dy = new_h - window->frame->rect.height; + + need_resize_frame = (frame_size_dx != 0 || frame_size_dy != 0); + + window->frame->rect.width = new_w; + window->frame->rect.height = new_h; + + meta_topic (META_DEBUG_GEOMETRY, + "Calculated frame size %dx%d\n", + window->frame->rect.width, + window->frame->rect.height); + } + else + { + frame_size_dx = 0; + frame_size_dy = 0; + } + + /* For nice effect, when growing the window we want to move/resize + * the frame first, when shrinking the window we want to move/resize + * the client first. If we grow one way and shrink the other, + * see which way we're moving "more" + * + * Mail from Owen subject "Suggestion: Gravity and resizing from the left" + * http://mail.gnome.org/archives/wm-spec-list/1999-November/msg00088.html + * + * An annoying fact you need to know in this code is that StaticGravity + * does nothing if you _only_ resize or _only_ move the frame; + * it must move _and_ resize, otherwise you get NorthWestGravity + * behavior. The move and resize must actually occur, it is not + * enough to set CWX | CWWidth but pass in the current size/pos. + */ + + if (window->frame) + { + int new_x, new_y; + int frame_pos_dx, frame_pos_dy; + + /* Compute new frame coords */ + new_x = root_x_nw - fgeom.left_width; + new_y = root_y_nw - fgeom.top_height; + + frame_pos_dx = new_x - window->frame->rect.x; + frame_pos_dy = new_y - window->frame->rect.y; + + need_move_frame = (frame_pos_dx != 0 || frame_pos_dy != 0); + + window->frame->rect.x = new_x; + window->frame->rect.y = new_y; + + /* If frame will both move and resize, then StaticGravity + * on the child window will kick in and implicitly move + * the child with respect to the frame. The implicit + * move will keep the child in the same place with + * respect to the root window. If frame only moves + * or only resizes, then the child will just move along + * with the frame. + */ + + /* window->rect.x, window->rect.y are relative to frame, + * remember they are the server coords + */ + + new_x = fgeom.left_width; + new_y = fgeom.top_height; + + if (need_resize_frame && need_move_frame && + static_gravity_works (window->display)) + { + /* static gravity kicks in because frame + * is both moved and resized + */ + /* when we move the frame by frame_pos_dx, frame_pos_dy the + * client will implicitly move relative to frame by the + * inverse delta. + * + * When moving client then frame, we move the client by the + * frame delta, to be canceled out by the implicit move by + * the inverse frame delta, resulting in a client at new_x, + * new_y. + * + * When moving frame then client, we move the client + * by the same delta as the frame, because the client + * was "left behind" by the frame - resulting in a client + * at new_x, new_y. + * + * In both cases we need to move the client window + * in all cases where we had to move the frame window. + */ + + client_move_x = new_x + frame_pos_dx; + client_move_y = new_y + frame_pos_dy; + + if (need_move_frame) + need_move_client = TRUE; + + use_static_gravity = TRUE; + } + else + { + client_move_x = new_x; + client_move_y = new_y; + + if (client_move_x != window->rect.x || + client_move_y != window->rect.y) + need_move_client = TRUE; + + use_static_gravity = FALSE; + } + + /* This is the final target position, but not necessarily what + * we pass to XConfigureWindow, due to StaticGravity implicit + * movement. + */ + window->rect.x = new_x; + window->rect.y = new_y; + } + else + { + if (root_x_nw != window->rect.x || + root_y_nw != window->rect.y) + need_move_client = TRUE; + + window->rect.x = root_x_nw; + window->rect.y = root_y_nw; + + client_move_x = window->rect.x; + client_move_y = window->rect.y; + + use_static_gravity = FALSE; + } + + /* If frame extents have changed, fill in other frame fields and + change frame's extents property. */ + if (window->frame && + (window->frame->child_x != fgeom.left_width || + window->frame->child_y != fgeom.top_height || + window->frame->right_width != fgeom.right_width || + window->frame->bottom_height != fgeom.bottom_height)) + { + window->frame->child_x = fgeom.left_width; + window->frame->child_y = fgeom.top_height; + window->frame->right_width = fgeom.right_width; + window->frame->bottom_height = fgeom.bottom_height; + + update_net_frame_extents (window); + } + + /* See ICCCM 4.1.5 for when to send ConfigureNotify */ + + need_configure_notify = FALSE; + + /* If this is a configure request and we change nothing, then we + * must send configure notify. + */ + if (is_configure_request && + !(need_move_client || need_move_frame || + need_resize_client || need_resize_frame || + window->border_width != 0)) + need_configure_notify = TRUE; + + /* We must send configure notify if we move but don't resize, since + * the client window may not get a real event + */ + if ((need_move_client || need_move_frame) && + !(need_resize_client || need_resize_frame)) + need_configure_notify = TRUE; + + /* MapRequest events with a PPosition or UPosition hint with a frame + * are moved by marco without resizing; send a configure notify + * in such cases. See #322840. (Note that window->constructing is + * only true iff this call is due to a MapRequest, and when + * PPosition/UPosition hints aren't set, marco seems to send a + * ConfigureNotify anyway due to the above code.) + */ + if (window->constructing && window->frame && + ((window->size_hints.flags & PPosition) || + (window->size_hints.flags & USPosition))) + need_configure_notify = TRUE; + + /* The rest of this function syncs our new size/pos with X as + * efficiently as possible + */ + + /* configure frame first if we grow more than we shrink + */ + size_dx = w - window->rect.width; + size_dy = h - window->rect.height; + + configure_frame_first = (size_dx + size_dy >= 0); + + if (use_static_gravity) + meta_window_set_gravity (window, StaticGravity); + + if (configure_frame_first && window->frame) + meta_frame_sync_to_window (window->frame, + gravity, + need_move_frame, need_resize_frame); + + values.border_width = 0; + values.x = client_move_x; + values.y = client_move_y; + values.width = window->rect.width; + values.height = window->rect.height; + + mask = 0; + if (is_configure_request && window->border_width != 0) + mask |= CWBorderWidth; /* must force to 0 */ + if (need_move_client) + mask |= (CWX | CWY); + if (need_resize_client) + mask |= (CWWidth | CWHeight); + + if (mask != 0) + { + { + int newx, newy; + meta_window_get_position (window, &newx, &newy); + meta_topic (META_DEBUG_GEOMETRY, + "Syncing new client geometry %d,%d %dx%d, border: %s pos: %s size: %s\n", + newx, newy, + window->rect.width, window->rect.height, + mask & CWBorderWidth ? "true" : "false", + need_move_client ? "true" : "false", + need_resize_client ? "true" : "false"); + } + + meta_error_trap_push (window->display); + +#ifdef HAVE_XSYNC + if (window->sync_request_counter != None && + window->display->grab_sync_request_alarm != None && + window->sync_request_time.tv_usec == 0 && + window->sync_request_time.tv_sec == 0) + { + /* turn off updating */ + if (window->display->compositor) + meta_compositor_set_updates (window->display->compositor, window, FALSE); + + send_sync_request (window); + } +#endif + + XConfigureWindow (window->display->xdisplay, + window->xwindow, + mask, + &values); + + meta_error_trap_pop (window->display, FALSE); + } + + if (!configure_frame_first && window->frame) + meta_frame_sync_to_window (window->frame, + gravity, + need_move_frame, need_resize_frame); + + /* Put gravity back to be nice to lesser window managers */ + if (use_static_gravity) + meta_window_set_gravity (window, NorthWestGravity); + + if (need_configure_notify) + send_configure_notify (window); + + if (!window->placed && window->force_save_user_rect && !window->fullscreen) + force_save_user_window_placement (window); + else if (is_user_action) + save_user_window_placement (window); + + if (need_move_frame || need_resize_frame || + need_move_client || need_resize_client) + { + int newx, newy; + meta_window_get_position (window, &newx, &newy); + meta_topic (META_DEBUG_GEOMETRY, + "New size/position %d,%d %dx%d (user %d,%d %dx%d)\n", + newx, newy, window->rect.width, window->rect.height, + window->user_rect.x, window->user_rect.y, + window->user_rect.width, window->user_rect.height); + } + else + { + meta_topic (META_DEBUG_GEOMETRY, "Size/position not modified\n"); + } + + if (window->display->grab_wireframe_active) + meta_window_update_wireframe (window, root_x_nw, root_y_nw, w, h); + else + meta_window_refresh_resize_popup (window); + + /* Invariants leaving this function are: + * a) window->rect and frame->rect reflect the actual + * server-side size/pos of window->xwindow and frame->xwindow + * b) all constraints are obeyed by window->rect and frame->rect + */ +} + +void +meta_window_resize (MetaWindow *window, + gboolean user_op, + int w, + int h) +{ + int x, y; + MetaMoveResizeFlags flags; + + meta_window_get_position (window, &x, &y); + + flags = (user_op ? META_IS_USER_ACTION : 0) | META_IS_RESIZE_ACTION; + meta_window_move_resize_internal (window, + flags, + NorthWestGravity, + x, y, w, h); +} + +void +meta_window_move (MetaWindow *window, + gboolean user_op, + int root_x_nw, + int root_y_nw) +{ + MetaMoveResizeFlags flags = + (user_op ? META_IS_USER_ACTION : 0) | META_IS_MOVE_ACTION; + meta_window_move_resize_internal (window, + flags, + NorthWestGravity, + root_x_nw, root_y_nw, + window->rect.width, + window->rect.height); +} + +void +meta_window_move_resize (MetaWindow *window, + gboolean user_op, + int root_x_nw, + int root_y_nw, + int w, + int h) +{ + MetaMoveResizeFlags flags = + (user_op ? META_IS_USER_ACTION : 0) | + META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION; + meta_window_move_resize_internal (window, + flags, + NorthWestGravity, + root_x_nw, root_y_nw, + w, h); +} + +void +meta_window_resize_with_gravity (MetaWindow *window, + gboolean user_op, + int w, + int h, + int gravity) +{ + int x, y; + MetaMoveResizeFlags flags; + + meta_window_get_position (window, &x, &y); + + flags = (user_op ? META_IS_USER_ACTION : 0) | META_IS_RESIZE_ACTION; + meta_window_move_resize_internal (window, + flags, + gravity, + x, y, w, h); +} + +static void +meta_window_move_resize_now (MetaWindow *window) +{ + /* If constraints have changed then we want to snap back to wherever + * the user had the window. We use user_rect for this reason. See + * also bug 426519 comment 3. + */ + meta_window_move_resize (window, FALSE, + window->user_rect.x, + window->user_rect.y, + window->user_rect.width, + window->user_rect.height); +} + +static gboolean +idle_move_resize (gpointer data) +{ + GSList *tmp; + GSList *copy; + guint queue_index = GPOINTER_TO_INT (data); + + meta_topic (META_DEBUG_GEOMETRY, "Clearing the move_resize queue\n"); + + /* Work with a copy, for reentrancy. The allowed reentrancy isn't + * complete; destroying a window while we're in here would result in + * badness. But it's OK to queue/unqueue move_resizes. + */ + copy = g_slist_copy (queue_pending[queue_index]); + g_slist_free (queue_pending[queue_index]); + queue_pending[queue_index] = NULL; + queue_idle[queue_index] = 0; + + destroying_windows_disallowed += 1; + + tmp = copy; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + /* As a side effect, sets window->move_resize_queued = FALSE */ + meta_window_move_resize_now (window); + + tmp = tmp->next; + } + + g_slist_free (copy); + + destroying_windows_disallowed -= 1; + + return FALSE; +} + +void +meta_window_get_position (MetaWindow *window, + int *x, + int *y) +{ + if (window->frame) + { + if (x) + *x = window->frame->rect.x + window->frame->child_x; + if (y) + *y = window->frame->rect.y + window->frame->child_y; + } + else + { + if (x) + *x = window->rect.x; + if (y) + *y = window->rect.y; + } +} + +void +meta_window_get_client_root_coords (MetaWindow *window, + MetaRectangle *rect) +{ + meta_window_get_position (window, &rect->x, &rect->y); + rect->width = window->rect.width; + rect->height = window->rect.height; +} + +void +meta_window_get_gravity_position (MetaWindow *window, + int gravity, + int *root_x, + int *root_y) +{ + MetaRectangle frame_extents; + int w, h; + int x, y; + + w = window->rect.width; + h = window->rect.height; + + if (gravity == StaticGravity) + { + frame_extents = window->rect; + if (window->frame) + { + frame_extents.x = window->frame->rect.x + window->frame->child_x; + frame_extents.y = window->frame->rect.y + window->frame->child_y; + } + } + else + { + if (window->frame == NULL) + frame_extents = window->rect; + else + frame_extents = window->frame->rect; + } + + x = frame_extents.x; + y = frame_extents.y; + + switch (gravity) + { + case NorthGravity: + case CenterGravity: + case SouthGravity: + /* Find center of frame. */ + x += frame_extents.width / 2; + /* Center client window on that point. */ + x -= w / 2; + break; + + case SouthEastGravity: + case EastGravity: + case NorthEastGravity: + /* Find right edge of frame */ + x += frame_extents.width; + /* Align left edge of client at that point. */ + x -= w; + break; + default: + break; + } + + switch (gravity) + { + case WestGravity: + case CenterGravity: + case EastGravity: + /* Find center of frame. */ + y += frame_extents.height / 2; + /* Center client window there. */ + y -= h / 2; + break; + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + /* Find south edge of frame */ + y += frame_extents.height; + /* Place bottom edge of client there */ + y -= h; + break; + default: + break; + } + + if (root_x) + *root_x = x; + if (root_y) + *root_y = y; +} + +void +meta_window_get_geometry (MetaWindow *window, + int *x, + int *y, + int *width, + int *height) +{ + meta_window_get_gravity_position (window, + window->size_hints.win_gravity, + x, y); + + *width = (window->rect.width - window->size_hints.base_width) / + window->size_hints.width_inc; + *height = (window->rect.height - window->size_hints.base_height) / + window->size_hints.height_inc; +} + +void +meta_window_get_outer_rect (const MetaWindow *window, + MetaRectangle *rect) +{ + if (window->frame) + *rect = window->frame->rect; + else + *rect = window->rect; +} + +void +meta_window_get_xor_rect (MetaWindow *window, + const MetaRectangle *grab_wireframe_rect, + MetaRectangle *xor_rect) +{ + if (window->frame) + { + xor_rect->x = grab_wireframe_rect->x - window->frame->child_x; + xor_rect->y = grab_wireframe_rect->y - window->frame->child_y; + xor_rect->width = grab_wireframe_rect->width + window->frame->child_x + window->frame->right_width; + + if (window->shaded) + xor_rect->height = window->frame->child_y; + else + xor_rect->height = grab_wireframe_rect->height + window->frame->child_y + window->frame->bottom_height; + } + else + *xor_rect = *grab_wireframe_rect; +} + +/* Figure out the numbers that show up in the + * resize popup when in reduced resources mode. + */ +static void +meta_window_get_wireframe_geometry (MetaWindow *window, + int *width, + int *height) +{ + if (!window->display->grab_wireframe_active) + return; + + if ((width == NULL) || (height == NULL)) + return; + + if ((window->display->grab_window->size_hints.width_inc <= 1) || + (window->display->grab_window->size_hints.height_inc <= 1)) + { + *width = -1; + *height = -1; + return; + } + + *width = window->display->grab_wireframe_rect.width - + window->display->grab_window->size_hints.base_width; + *width /= window->display->grab_window->size_hints.width_inc; + + *height = window->display->grab_wireframe_rect.height - + window->display->grab_window->size_hints.base_height; + *height /= window->display->grab_window->size_hints.height_inc; +} + +/* XXX META_EFFECT_ALT_TAB, well, this and others */ +void +meta_window_begin_wireframe (MetaWindow *window) +{ + + MetaRectangle new_xor; + int display_width, display_height; + + meta_window_get_client_root_coords (window, + &window->display->grab_wireframe_rect); + + meta_window_get_xor_rect (window, &window->display->grab_wireframe_rect, + &new_xor); + meta_window_get_wireframe_geometry (window, &display_width, &display_height); + + meta_effects_begin_wireframe (window->screen, + &new_xor, display_width, display_height); + + window->display->grab_wireframe_last_xor_rect = new_xor; + window->display->grab_wireframe_last_display_width = display_width; + window->display->grab_wireframe_last_display_height = display_height; +} + +void +meta_window_update_wireframe (MetaWindow *window, + int x, + int y, + int width, + int height) +{ + + MetaRectangle new_xor; + int display_width, display_height; + + window->display->grab_wireframe_rect.x = x; + window->display->grab_wireframe_rect.y = y; + window->display->grab_wireframe_rect.width = width; + window->display->grab_wireframe_rect.height = height; + + meta_window_get_xor_rect (window, &window->display->grab_wireframe_rect, + &new_xor); + meta_window_get_wireframe_geometry (window, &display_width, &display_height); + + meta_effects_update_wireframe (window->screen, + &window->display->grab_wireframe_last_xor_rect, + window->display->grab_wireframe_last_display_width, + window->display->grab_wireframe_last_display_height, + &new_xor, display_width, display_height); + + window->display->grab_wireframe_last_xor_rect = new_xor; + window->display->grab_wireframe_last_display_width = display_width; + window->display->grab_wireframe_last_display_height = display_height; +} + +void +meta_window_end_wireframe (MetaWindow *window) +{ + meta_effects_end_wireframe (window->display->grab_window->screen, + &window->display->grab_wireframe_last_xor_rect, + window->display->grab_wireframe_last_display_width, + window->display->grab_wireframe_last_display_height); +} + +const char* +meta_window_get_startup_id (MetaWindow *window) +{ + if (window->startup_id == NULL) + { + MetaGroup *group; + + group = meta_window_get_group (window); + + if (group != NULL) + return meta_group_get_startup_id (group); + } + + return window->startup_id; +} + +static MetaWindow* +get_modal_transient (MetaWindow *window) +{ + GSList *windows; + GSList *tmp; + MetaWindow *modal_transient; + + /* A window can't be the transient of itself, but this is just for + * convenience in the loop below; we manually fix things up at the + * end if no real modal transient was found. + */ + modal_transient = window; + + windows = meta_display_list_windows (window->display); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *transient = tmp->data; + + if (transient->xtransient_for == modal_transient->xwindow && + transient->wm_state_modal) + { + modal_transient = transient; + tmp = windows; + continue; + } + + tmp = tmp->next; + } + + g_slist_free (windows); + + if (window == modal_transient) + modal_transient = NULL; + + return modal_transient; +} + +/* XXX META_EFFECT_FOCUS */ +void +meta_window_focus (MetaWindow *window, + guint32 timestamp) +{ + MetaWindow *modal_transient; + + meta_topic (META_DEBUG_FOCUS, + "Setting input focus to window %s, input: %d take_focus: %d\n", + window->desc, window->input, window->take_focus); + + if (window->display->grab_window && + window->display->grab_window->all_keys_grabbed) + { + meta_topic (META_DEBUG_FOCUS, + "Current focus window %s has global keygrab, not focusing window %s after all\n", + window->display->grab_window->desc, window->desc); + return; + } + + modal_transient = get_modal_transient (window); + if (modal_transient != NULL && + !modal_transient->unmanaging) + { + meta_topic (META_DEBUG_FOCUS, + "%s has %s as a modal transient, so focusing it instead.\n", + window->desc, modal_transient->desc); + if (!modal_transient->on_all_workspaces && + modal_transient->workspace != window->screen->active_workspace) + meta_window_change_workspace (modal_transient, + window->screen->active_workspace); + window = modal_transient; + } + + meta_window_flush_calc_showing (window); + + if (!window->mapped && !window->shaded) + { + meta_topic (META_DEBUG_FOCUS, + "Window %s is not showing, not focusing after all\n", + window->desc); + return; + } + + /* For output-only or shaded windows, focus the frame. + * This seems to result in the client window getting key events + * though, so I don't know if it's icccm-compliant. + * + * Still, we have to do this or keynav breaks for these windows. + */ + if (window->frame && + (window->shaded || + !(window->input || window->take_focus))) + { + if (window->frame) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing frame of %s\n", window->desc); + meta_display_set_input_focus_window (window->display, + window, + TRUE, + timestamp); + } + } + else + { + if (window->input) + { + meta_topic (META_DEBUG_FOCUS, + "Setting input focus on %s since input = true\n", + window->desc); + meta_display_set_input_focus_window (window->display, + window, + FALSE, + timestamp); + } + + if (window->take_focus) + { + meta_topic (META_DEBUG_FOCUS, + "Sending WM_TAKE_FOCUS to %s since take_focus = true\n", + window->desc); + meta_window_send_icccm_message (window, + window->display->atom_WM_TAKE_FOCUS, + timestamp); + window->display->expected_focus_window = window; + } + } + + if (window->wm_state_demands_attention) + meta_window_unset_demands_attention(window); + + meta_effect_run_focus(window, NULL, NULL); +} + +static void +meta_window_change_workspace_without_transients (MetaWindow *window, + MetaWorkspace *workspace) +{ + meta_verbose ("Changing window %s to workspace %d\n", + window->desc, meta_workspace_index (workspace)); + + /* unstick if stuck. meta_window_unstick would call + * meta_window_change_workspace recursively if the window + * is not in the active workspace. + */ + if (window->on_all_workspaces) + meta_window_unstick (window); + + /* See if we're already on this space. If not, make sure we are */ + if (window->workspace != workspace) + { + meta_workspace_remove_window (window->workspace, window); + meta_workspace_add_window (workspace, window); + } +} + +static gboolean +change_workspace_foreach (MetaWindow *window, + void *data) +{ + meta_window_change_workspace_without_transients (window, data); + return TRUE; +} + +void +meta_window_change_workspace (MetaWindow *window, + MetaWorkspace *workspace) +{ + meta_window_change_workspace_without_transients (window, workspace); + + meta_window_foreach_transient (window, change_workspace_foreach, + workspace); + meta_window_foreach_ancestor (window, change_workspace_foreach, + workspace); +} + +static void +window_stick_impl (MetaWindow *window) +{ + GList *tmp; + MetaWorkspace *workspace; + + meta_verbose ("Sticking window %s current on_all_workspaces = %d\n", + window->desc, window->on_all_workspaces); + + if (window->on_all_workspaces) + return; + + /* We don't change window->workspaces, because we revert + * to that original workspace list if on_all_workspaces is + * toggled back off. + */ + window->on_all_workspaces = TRUE; + + /* We do, however, change the MRU lists of all the workspaces + */ + tmp = window->screen->workspaces; + while (tmp) + { + workspace = (MetaWorkspace *) tmp->data; + if (!g_list_find (workspace->mru_list, window)) + workspace->mru_list = g_list_prepend (workspace->mru_list, window); + + tmp = tmp->next; + } + + meta_window_set_current_workspace_hint (window); + + meta_window_queue(window, META_QUEUE_CALC_SHOWING); +} + +static void +window_unstick_impl (MetaWindow *window) +{ + GList *tmp; + MetaWorkspace *workspace; + + if (!window->on_all_workspaces) + return; + + /* Revert to window->workspaces */ + + window->on_all_workspaces = FALSE; + + /* Remove window from MRU lists that it doesn't belong in */ + tmp = window->screen->workspaces; + while (tmp) + { + workspace = (MetaWorkspace *) tmp->data; + if (window->workspace != workspace) + workspace->mru_list = g_list_remove (workspace->mru_list, window); + tmp = tmp->next; + } + + /* We change ourselves to the active workspace, since otherwise you'd get + * a weird window-vaporization effect. Once we have UI for being + * on more than one workspace this should probably be add_workspace + * not change_workspace. + */ + if (window->screen->active_workspace != window->workspace) + meta_window_change_workspace (window, window->screen->active_workspace); + + meta_window_set_current_workspace_hint (window); + + meta_window_queue(window, META_QUEUE_CALC_SHOWING); +} + +static gboolean +stick_foreach_func (MetaWindow *window, + void *data) +{ + gboolean stick; + + stick = *(gboolean*)data; + if (stick) + window_stick_impl (window); + else + window_unstick_impl (window); + return TRUE; +} + +void +meta_window_stick (MetaWindow *window) +{ + gboolean stick = TRUE; + window_stick_impl (window); + meta_window_foreach_transient (window, + stick_foreach_func, + &stick); +} + +void +meta_window_unstick (MetaWindow *window) +{ + gboolean stick = FALSE; + window_unstick_impl (window); + meta_window_foreach_transient (window, + stick_foreach_func, + &stick); +} + +unsigned long +meta_window_get_net_wm_desktop (MetaWindow *window) +{ + if (window->on_all_workspaces) + return 0xFFFFFFFF; + else + return meta_workspace_index (window->workspace); +} + +static void +update_net_frame_extents (MetaWindow *window) +{ + unsigned long data[4] = { 0, 0, 0, 0 }; + + if (window->frame) + { + /* Left */ + data[0] = window->frame->child_x; + /* Right */ + data[1] = window->frame->right_width; + /* Top */ + data[2] = window->frame->child_y; + /* Bottom */ + data[3] = window->frame->bottom_height; + } + + meta_topic (META_DEBUG_GEOMETRY, + "Setting _NET_FRAME_EXTENTS on managed window 0x%lx " + "to left = %lu, right = %lu, top = %lu, bottom = %lu\n", + window->xwindow, data[0], data[1], data[2], data[3]); + + meta_error_trap_push (window->display); + XChangeProperty (window->display->xdisplay, window->xwindow, + window->display->atom__NET_FRAME_EXTENTS, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 4); + meta_error_trap_pop (window->display, FALSE); +} + +void +meta_window_set_current_workspace_hint (MetaWindow *window) +{ + /* FIXME if on more than one workspace, we claim to be "sticky", + * the WM spec doesn't say what to do here. + */ + unsigned long data[1]; + + if (window->workspace == NULL) + { + /* this happens when unmanaging windows */ + return; + } + + data[0] = meta_window_get_net_wm_desktop (window); + + meta_verbose ("Setting _NET_WM_DESKTOP of %s to %lu\n", + window->desc, data[0]); + + meta_error_trap_push (window->display); + XChangeProperty (window->display->xdisplay, window->xwindow, + window->display->atom__NET_WM_DESKTOP, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 1); + meta_error_trap_pop (window->display, FALSE); +} + +static gboolean +find_root_ancestor (MetaWindow *window, + void *data) +{ + MetaWindow **ancestor = data; + + /* Overwrite the previously "most-root" ancestor with the new one found */ + *ancestor = window; + + /* We want this to continue until meta_window_foreach_ancestor quits because + * there are no more valid ancestors. + */ + return TRUE; +} + +MetaWindow * +meta_window_find_root_ancestor (MetaWindow *window) +{ + MetaWindow *ancestor; + ancestor = window; + meta_window_foreach_ancestor (window, find_root_ancestor, &ancestor); + return ancestor; +} + +void +meta_window_raise (MetaWindow *window) +{ + MetaWindow *ancestor; + ancestor = meta_window_find_root_ancestor (window); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Raising window %s, ancestor of %s\n", + ancestor->desc, window->desc); + + /* Raise the ancestor of the window (if the window has no ancestor, + * then ancestor will be set to the window itself); do this because + * it's weird to see windows from other apps stacked between a child + * and parent window of the currently active app. The stacking + * constraints in stack.c then magically take care of raising all + * the child windows appropriately. + */ + if (window->screen->stack == ancestor->screen->stack) + meta_stack_raise (window->screen->stack, ancestor); + else + { + meta_warning ( + "Either stacks aren't per screen or some window has a weird " + "transient_for hint; window->screen->stack != " + "ancestor->screen->stack. window = %s, ancestor = %s.\n", + window->desc, ancestor->desc); + /* We could raise the window here, but don't want to do that twice and + * so we let the case below handle that. + */ + } + + /* Okay, so stacking constraints misses one case: If a window has + * two children and we want to raise one of those children, then + * raising the ancestor isn't enough; we need to also raise the + * correct child. See bug 307875. + */ + if (window != ancestor) + meta_stack_raise (window->screen->stack, window); +} + +void +meta_window_lower (MetaWindow *window) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Lowering window %s\n", window->desc); + + meta_stack_lower (window->screen->stack, window); +} + +void +meta_window_send_icccm_message (MetaWindow *window, + Atom atom, + guint32 timestamp) +{ + /* This comment and code are from twm, copyright + * Open Group, Evans & Sutherland, etc. + */ + + /* + * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all + * client messages will have the following form: + * + * event type ClientMessage + * message type _XA_WM_PROTOCOLS + * window tmp->w + * format 32 + * data[0] message atom + * data[1] time stamp + */ + + XClientMessageEvent ev; + + ev.type = ClientMessage; + ev.window = window->xwindow; + ev.message_type = window->display->atom_WM_PROTOCOLS; + ev.format = 32; + ev.data.l[0] = atom; + ev.data.l[1] = timestamp; + + meta_error_trap_push (window->display); + XSendEvent (window->display->xdisplay, + window->xwindow, False, 0, (XEvent*) &ev); + meta_error_trap_pop (window->display, FALSE); +} + +void +meta_window_move_resize_request (MetaWindow *window, + guint value_mask, + int gravity, + int new_x, + int new_y, + int new_width, + int new_height) +{ + int x, y, width, height; + gboolean allow_position_change; + gboolean in_grab_op; + MetaMoveResizeFlags flags; + + /* We ignore configure requests while the user is moving/resizing + * the window, since these represent the app sucking and fighting + * the user, most likely due to a bug in the app (e.g. pfaedit + * seemed to do this) + * + * Still have to do the ConfigureNotify and all, but pretend the + * app asked for the current size/position instead of the new one. + */ + in_grab_op = FALSE; + if (window->display->grab_op != META_GRAB_OP_NONE && + window == window->display->grab_window) + { + switch (window->display->grab_op) + { + case META_GRAB_OP_MOVING: + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_E: + in_grab_op = TRUE; + break; + default: + break; + } + } + + /* it's essential to use only the explicitly-set fields, + * and otherwise use our current up-to-date position. + * + * Otherwise you get spurious position changes when the app changes + * size, for example, if window->rect is not in sync with the + * server-side position in effect when the configure request was + * generated. + */ + meta_window_get_gravity_position (window, + gravity, + &x, &y); + + allow_position_change = FALSE; + + if (meta_prefs_get_disable_workarounds ()) + { + if (window->type == META_WINDOW_DIALOG || + window->type == META_WINDOW_MODAL_DIALOG || + window->type == META_WINDOW_SPLASHSCREEN) + ; /* No position change for these */ + else if ((window->size_hints.flags & PPosition) || + /* USPosition is just stale if window is placed; + * no --geometry involved here. + */ + ((window->size_hints.flags & USPosition) && + !window->placed)) + allow_position_change = TRUE; + } + else + { + allow_position_change = TRUE; + } + + if (in_grab_op) + allow_position_change = FALSE; + + if (allow_position_change) + { + if (value_mask & CWX) + x = new_x; + if (value_mask & CWY) + y = new_y; + if (value_mask & (CWX | CWY)) + { + /* Once manually positioned, windows shouldn't be placed + * by the window manager. + */ + window->placed = TRUE; + } + } + else + { + meta_topic (META_DEBUG_GEOMETRY, + "Not allowing position change for window %s PPosition 0x%lx USPosition 0x%lx type %u\n", + window->desc, window->size_hints.flags & PPosition, + window->size_hints.flags & USPosition, + window->type); + } + + width = window->rect.width; + height = window->rect.height; + if (!in_grab_op) + { + if (value_mask & CWWidth) + width = new_width; + + if (value_mask & CWHeight) + height = new_height; + } + + /* ICCCM 4.1.5 */ + + /* We're ignoring the value_mask here, since sizes + * not in the mask will be the current window geometry. + */ + window->size_hints.x = x; + window->size_hints.y = y; + window->size_hints.width = width; + window->size_hints.height = height; + + /* NOTE: We consider ConfigureRequests to be "user" actions in one + * way, but not in another. Explanation of the two cases are in the + * next two big comments. + */ + + /* The constraints code allows user actions to move windows + * offscreen, etc., and configure request actions would often send + * windows offscreen when users don't want it if not constrained + * (e.g. hitting a dropdown triangle in a fileselector to show more + * options, which makes the window bigger). Thus we do not set + * META_IS_USER_ACTION in flags to the + * meta_window_move_resize_internal() call. + */ + flags = META_IS_CONFIGURE_REQUEST; + if (value_mask & (CWX | CWY)) + flags |= META_IS_MOVE_ACTION; + if (value_mask & (CWWidth | CWHeight)) + flags |= META_IS_RESIZE_ACTION; + + if (flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)) + meta_window_move_resize_internal (window, + flags, + gravity, + x, + y, + width, + height); + + /* window->user_rect exists to allow "snapping-back" the window if a + * new strut is set (causing the window to move) and then the strut + * is later removed without the user moving the window in the + * interim. We'd like to "snap-back" to the position specified by + * ConfigureRequest events (at least the constrained version of the + * ConfigureRequest, since that is guaranteed to be onscreen) so we + * set user_rect here. + * + * See also bug 426519. + */ + save_user_window_placement (window); +} + +gboolean +meta_window_configure_request (MetaWindow *window, + XEvent *event) +{ + /* Note that x, y is the corner of the window border, + * and width, height is the size of the window inside + * its border, but that we always deny border requests + * and give windows a border of 0. But we save the + * requested border here. + */ + if (event->xconfigurerequest.value_mask & CWBorderWidth) + window->border_width = event->xconfigurerequest.border_width; + + meta_window_move_resize_request(window, + event->xconfigurerequest.value_mask, + window->size_hints.win_gravity, + event->xconfigurerequest.x, + event->xconfigurerequest.y, + event->xconfigurerequest.width, + event->xconfigurerequest.height); + + /* Handle stacking. We only handle raises/lowers, mostly because + * stack.c really can't deal with anything else. I guess we'll fix + * that if a client turns up that really requires it. Only a very + * few clients even require the raise/lower (and in fact all client + * attempts to deal with stacking order are essentially broken, + * since they have no idea what other clients are involved or how + * the stack looks). + * + * I'm pretty sure no interesting client uses TopIf, BottomIf, or + * Opposite anyway, so the only possible missing thing is + * Above/Below with a sibling set. For now we just pretend there's + * never a sibling set and always do the full raise/lower instead of + * the raise-just-above/below-sibling. + */ + if (event->xconfigurerequest.value_mask & CWStackMode) + { + MetaWindow *active_window; + active_window = window->display->expected_focus_window; + if (meta_prefs_get_disable_workarounds () || + !meta_prefs_get_raise_on_click ()) + { + meta_topic (META_DEBUG_STACK, + "%s sent an xconfigure stacking request; this is " + "broken behavior and the request is being ignored.\n", + window->desc); + } + else if (active_window && + !meta_window_same_application (window, active_window) && + XSERVER_TIME_IS_BEFORE (window->net_wm_user_time, + active_window->net_wm_user_time)) + { + meta_topic (META_DEBUG_STACK, + "Ignoring xconfigure stacking request from %s (with " + "user_time %u); currently active application is %s (with " + "user_time %u).\n", + window->desc, + window->net_wm_user_time, + active_window->desc, + active_window->net_wm_user_time); + if (event->xconfigurerequest.detail == Above) + meta_window_set_demands_attention(window); + } + else + { + switch (event->xconfigurerequest.detail) + { + case Above: + meta_window_raise (window); + break; + case Below: + meta_window_lower (window); + break; + case TopIf: + case BottomIf: + case Opposite: + break; + } + } + } + + return TRUE; +} + +gboolean +meta_window_property_notify (MetaWindow *window, + XEvent *event) +{ + return process_property_notify (window, &event->xproperty); +} + +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 +#define _NET_WM_MOVERESIZE_CANCEL 11 + +gboolean +meta_window_client_message (MetaWindow *window, + XEvent *event) +{ + MetaDisplay *display; + + display = window->display; + + if (event->xclient.message_type == + display->atom__NET_CLOSE_WINDOW) + { + guint32 timestamp; + + if (event->xclient.data.l[0] != 0) + timestamp = event->xclient.data.l[0]; + else + { + meta_warning ("Receiving a NET_CLOSE_WINDOW message for %s without " + "a timestamp! This means some buggy (outdated) " + "application is on the loose!\n", + window->desc); + timestamp = meta_display_get_current_time (window->display); + } + + meta_window_delete (window, timestamp); + + return TRUE; + } + else if (event->xclient.message_type == + display->atom__NET_WM_DESKTOP) + { + int space; + MetaWorkspace *workspace; + + space = event->xclient.data.l[0]; + + meta_verbose ("Request to move %s to workspace %d\n", + window->desc, space); + + workspace = + meta_screen_get_workspace_by_index (window->screen, + space); + + if (workspace) + { + if (window->on_all_workspaces) + meta_window_unstick (window); + meta_window_change_workspace (window, workspace); + } + else if (space == (int) 0xFFFFFFFF) + { + meta_window_stick (window); + } + else + { + meta_verbose ("No such workspace %d for screen\n", space); + } + + meta_verbose ("Window %s now on_all_workspaces = %d\n", + window->desc, window->on_all_workspaces); + + return TRUE; + } + else if (event->xclient.message_type == + display->atom__NET_WM_STATE) + { + gulong action; + Atom first; + Atom second; + + action = event->xclient.data.l[0]; + first = event->xclient.data.l[1]; + second = event->xclient.data.l[2]; + + if (meta_is_verbose ()) + { + char *str1; + char *str2; + + meta_error_trap_push_with_return (display); + str1 = XGetAtomName (display->xdisplay, first); + if (meta_error_trap_pop_with_return (display, TRUE) != Success) + str1 = NULL; + + meta_error_trap_push_with_return (display); + str2 = XGetAtomName (display->xdisplay, second); + if (meta_error_trap_pop_with_return (display, TRUE) != Success) + str2 = NULL; + + meta_verbose ("Request to change _NET_WM_STATE action %lu atom1: %s atom2: %s\n", + action, + str1 ? str1 : "(unknown)", + str2 ? str2 : "(unknown)"); + + meta_XFree (str1); + meta_XFree (str2); + } + + if (first == display->atom__NET_WM_STATE_SHADED || + second == display->atom__NET_WM_STATE_SHADED) + { + gboolean shade; + guint32 timestamp; + + /* Stupid protocol has no timestamp; of course, shading + * sucks anyway so who really cares that we're forced to do + * a roundtrip here? + */ + timestamp = meta_display_get_current_time_roundtrip (window->display); + + shade = (action == _NET_WM_STATE_ADD || + (action == _NET_WM_STATE_TOGGLE && !window->shaded)); + if (shade && window->has_shade_func) + meta_window_shade (window, timestamp); + else + meta_window_unshade (window, timestamp); + } + + if (first == display->atom__NET_WM_STATE_FULLSCREEN || + second == display->atom__NET_WM_STATE_FULLSCREEN) + { + gboolean make_fullscreen; + + make_fullscreen = (action == _NET_WM_STATE_ADD || + (action == _NET_WM_STATE_TOGGLE && !window->fullscreen)); + if (make_fullscreen && window->has_fullscreen_func) + meta_window_make_fullscreen (window); + else + meta_window_unmake_fullscreen (window); + } + + if (first == display->atom__NET_WM_STATE_MAXIMIZED_HORZ || + second == display->atom__NET_WM_STATE_MAXIMIZED_HORZ) + { + gboolean max; + + max = (action == _NET_WM_STATE_ADD || + (action == _NET_WM_STATE_TOGGLE && + !window->maximized_horizontally)); + if (max && window->has_maximize_func) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL); + } + else + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_unmaximize (window, META_MAXIMIZE_HORIZONTAL); + } + } + + if (first == display->atom__NET_WM_STATE_MAXIMIZED_VERT || + second == display->atom__NET_WM_STATE_MAXIMIZED_VERT) + { + gboolean max; + + max = (action == _NET_WM_STATE_ADD || + (action == _NET_WM_STATE_TOGGLE && + !window->maximized_vertically)); + if (max && window->has_maximize_func) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_maximize (window, META_MAXIMIZE_VERTICAL); + } + else + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL); + } + } + + if (first == display->atom__NET_WM_STATE_MODAL || + second == display->atom__NET_WM_STATE_MODAL) + { + window->wm_state_modal = + (action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->wm_state_modal); + + recalc_window_type (window); + meta_window_queue(window, META_QUEUE_MOVE_RESIZE); + } + + if (first == display->atom__NET_WM_STATE_SKIP_PAGER || + second == display->atom__NET_WM_STATE_SKIP_PAGER) + { + window->wm_state_skip_pager = + (action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->skip_pager); + + recalc_window_features (window); + set_net_wm_state (window); + } + + if (first == display->atom__NET_WM_STATE_SKIP_TASKBAR || + second == display->atom__NET_WM_STATE_SKIP_TASKBAR) + { + window->wm_state_skip_taskbar = + (action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->skip_taskbar); + + recalc_window_features (window); + set_net_wm_state (window); + } + + if (first == display->atom__NET_WM_STATE_ABOVE || + second == display->atom__NET_WM_STATE_ABOVE) + { + window->wm_state_above = + (action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->wm_state_above); + + meta_window_update_layer (window); + set_net_wm_state (window); + } + + if (first == display->atom__NET_WM_STATE_BELOW || + second == display->atom__NET_WM_STATE_BELOW) + { + window->wm_state_below = + (action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->wm_state_below); + + meta_window_update_layer (window); + set_net_wm_state (window); + } + + if (first == display->atom__NET_WM_STATE_DEMANDS_ATTENTION || + second == display->atom__NET_WM_STATE_DEMANDS_ATTENTION) + { + if ((action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->wm_state_demands_attention)) + meta_window_set_demands_attention (window); + else + meta_window_unset_demands_attention (window); + } + + if (first == display->atom__NET_WM_STATE_STICKY || + second == display->atom__NET_WM_STATE_STICKY) + { + if ((action == _NET_WM_STATE_ADD) || + (action == _NET_WM_STATE_TOGGLE && !window->on_all_workspaces)) + meta_window_stick (window); + else + meta_window_unstick (window); + } + + return TRUE; + } + else if (event->xclient.message_type == + display->atom_WM_CHANGE_STATE) + { + meta_verbose ("WM_CHANGE_STATE client message, state: %ld\n", + event->xclient.data.l[0]); + if (event->xclient.data.l[0] == IconicState && + window->has_minimize_func) + meta_window_minimize (window); + + return TRUE; + } + else if (event->xclient.message_type == + display->atom__NET_WM_MOVERESIZE) + { + int x_root; + int y_root; + int action; + MetaGrabOp op; + int button; + guint32 timestamp; + + /* _NET_WM_MOVERESIZE messages are almost certainly going to come from + * clients when users click on the fake "frame" that the client has, + * thus we should also treat such messages as though it were a + * "frame action". + */ + gboolean const frame_action = TRUE; + + x_root = event->xclient.data.l[0]; + y_root = event->xclient.data.l[1]; + action = event->xclient.data.l[2]; + button = event->xclient.data.l[3]; + + /* FIXME: What a braindead protocol; no timestamp?!? */ + timestamp = meta_display_get_current_time_roundtrip (display); + meta_warning ("Received a _NET_WM_MOVERESIZE message for %s; these " + "messages lack timestamps and therefore suck.\n", + window->desc); + meta_topic (META_DEBUG_WINDOW_OPS, + "Received _NET_WM_MOVERESIZE message on %s, %d,%d action = %d, button %d\n", + window->desc, + x_root, y_root, action, button); + + op = META_GRAB_OP_NONE; + switch (action) + { + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: + op = META_GRAB_OP_RESIZING_NW; + break; + case _NET_WM_MOVERESIZE_SIZE_TOP: + op = META_GRAB_OP_RESIZING_N; + break; + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + op = META_GRAB_OP_RESIZING_NE; + break; + case _NET_WM_MOVERESIZE_SIZE_RIGHT: + op = META_GRAB_OP_RESIZING_E; + break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + op = META_GRAB_OP_RESIZING_SE; + break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOM: + op = META_GRAB_OP_RESIZING_S; + break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + op = META_GRAB_OP_RESIZING_SW; + break; + case _NET_WM_MOVERESIZE_SIZE_LEFT: + op = META_GRAB_OP_RESIZING_W; + break; + case _NET_WM_MOVERESIZE_MOVE: + op = META_GRAB_OP_MOVING; + break; + case _NET_WM_MOVERESIZE_SIZE_KEYBOARD: + op = META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN; + break; + case _NET_WM_MOVERESIZE_MOVE_KEYBOARD: + op = META_GRAB_OP_KEYBOARD_MOVING; + break; + case _NET_WM_MOVERESIZE_CANCEL: + /* handled below */ + break; + default: + break; + } + + if (action == _NET_WM_MOVERESIZE_CANCEL) + { + meta_display_end_grab_op (window->display, timestamp); + } + else if (op != META_GRAB_OP_NONE && + ((window->has_move_func && op == META_GRAB_OP_KEYBOARD_MOVING) || + (window->has_resize_func && op == META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN))) + { + meta_window_begin_grab_op (window, op, frame_action, timestamp); + } + else if (op != META_GRAB_OP_NONE && + ((window->has_move_func && op == META_GRAB_OP_MOVING) || + (window->has_resize_func && + (op != META_GRAB_OP_MOVING && + op != META_GRAB_OP_KEYBOARD_MOVING)))) + { + /* + * the button SHOULD already be included in the message + */ + if (button == 0) + { + int x, y, query_root_x, query_root_y; + Window root, child; + guint mask; + + /* The race conditions in this _NET_WM_MOVERESIZE thing + * are mind-boggling + */ + mask = 0; + meta_error_trap_push (window->display); + XQueryPointer (window->display->xdisplay, + window->xwindow, + &root, &child, + &query_root_x, &query_root_y, + &x, &y, + &mask); + meta_error_trap_pop (window->display, TRUE); + + if (mask & Button1Mask) + button = 1; + else if (mask & Button2Mask) + button = 2; + else if (mask & Button3Mask) + button = 3; + else + button = 0; + } + + if (button != 0) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Beginning move/resize with button = %d\n", button); + meta_display_begin_grab_op (window->display, + window->screen, + window, + op, + FALSE, + frame_action, + button, 0, + timestamp, + x_root, + y_root); + } + } + + return TRUE; + } + else if (event->xclient.message_type == + display->atom__NET_MOVERESIZE_WINDOW) + { + int gravity, source; + guint value_mask; + + gravity = (event->xclient.data.l[0] & 0xff); + value_mask = (event->xclient.data.l[0] & 0xf00) >> 8; + source = (event->xclient.data.l[0] & 0xf000) >> 12; + + if (gravity == 0) + gravity = window->size_hints.win_gravity; + + meta_window_move_resize_request(window, + value_mask, + gravity, + event->xclient.data.l[1], /* x */ + event->xclient.data.l[2], /* y */ + event->xclient.data.l[3], /* width */ + event->xclient.data.l[4]); /* height */ + } + else if (event->xclient.message_type == + display->atom__NET_ACTIVE_WINDOW) + { + MetaClientType source_indication; + guint32 timestamp; + + meta_verbose ("_NET_ACTIVE_WINDOW request for window '%s', activating\n", + window->desc); + + source_indication = event->xclient.data.l[0]; + timestamp = event->xclient.data.l[1]; + + if (source_indication > META_CLIENT_TYPE_MAX_RECOGNIZED) + source_indication = META_CLIENT_TYPE_UNKNOWN; + + if (timestamp == 0) + { + /* Client using older EWMH _NET_ACTIVE_WINDOW without a timestamp */ + meta_warning ("Buggy client sent a _NET_ACTIVE_WINDOW message with a " + "timestamp of 0 for %s\n", + window->desc); + timestamp = meta_display_get_current_time (display); + } + + window_activate (window, timestamp, source_indication, NULL); + return TRUE; + } + else if (event->xclient.message_type == + display->atom__NET_WM_FULLSCREEN_MONITORS) + { + MetaClientType source_indication; + gulong top, bottom, left, right; + + meta_verbose ("_NET_WM_FULLSCREEN_MONITORS request for window '%s'\n", + window->desc); + + top = event->xclient.data.l[0]; + bottom = event->xclient.data.l[1]; + left = event->xclient.data.l[2]; + right = event->xclient.data.l[3]; + source_indication = event->xclient.data.l[4]; + + meta_window_update_fullscreen_monitors (window, top, bottom, left, right); + } + + return FALSE; +} + +gboolean +meta_window_notify_focus (MetaWindow *window, + XEvent *event) +{ + /* note the event can be on either the window or the frame, + * we focus the frame for shaded windows + */ + + /* The event can be FocusIn, FocusOut, or UnmapNotify. + * On UnmapNotify we have to pretend it's focus out, + * because we won't get a focus out if it occurs, apparently. + */ + + /* We ignore grabs, though this is questionable. + * It may be better to increase the intelligence of + * the focus window tracking. + * + * The problem is that keybindings for windows are done with + * XGrabKey, which means focus_window disappears and the front of + * the MRU list gets confused from what the user expects once a + * keybinding is used. + */ + meta_topic (META_DEBUG_FOCUS, + "Focus %s event received on %s 0x%lx (%s) " + "mode %s detail %s\n", + event->type == FocusIn ? "in" : + event->type == FocusOut ? "out" : + event->type == UnmapNotify ? "unmap" : + "???", + window->desc, event->xany.window, + event->xany.window == window->xwindow ? + "client window" : + (window->frame && event->xany.window == window->frame->xwindow) ? + "frame window" : + "unknown window", + event->type != UnmapNotify ? + meta_event_mode_to_string (event->xfocus.mode) : "n/a", + event->type != UnmapNotify ? + meta_event_detail_to_string (event->xfocus.detail) : "n/a"); + + /* FIXME our pointer tracking is broken; see how + * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c + * handle it for the correct way. In brief you need to track + * pointer focus and regular focus, and handle EnterNotify in + * PointerRoot mode with no window manager. However as noted above, + * accurate focus tracking will break things because we want to keep + * windows "focused" when using keybindings on them, and also we + * sometimes "focus" a window by focusing its frame or + * no_focus_window; so this all needs rethinking massively. + * + * My suggestion is to change it so that we clearly separate + * actual keyboard focus tracking using the xterm algorithm, + * and marco's "pretend" focus window, and go through all + * the code and decide which one should be used in each place; + * a hard bit is deciding on a policy for that. + * + * http://bugzilla.gnome.org/show_bug.cgi?id=90382 + */ + + if ((event->type == FocusIn || + event->type == FocusOut) && + (event->xfocus.mode == NotifyGrab || + event->xfocus.mode == NotifyUngrab || + /* From WindowMaker, ignore all funky pointer root events */ + event->xfocus.detail > NotifyNonlinearVirtual)) + { + meta_topic (META_DEBUG_FOCUS, + "Ignoring focus event generated by a grab or other weirdness\n"); + return TRUE; + } + + if (event->type == FocusIn) + { + if (window != window->display->focus_window) + { + meta_topic (META_DEBUG_FOCUS, + "* Focus --> %s\n", window->desc); + window->display->focus_window = window; + window->has_focus = TRUE; + meta_compositor_set_active_window (window->display->compositor, + window->screen, window); + + /* Move to the front of the focusing workspace's MRU list. + * We should only be "removing" it from the MRU list if it's + * not already there. Note that it's possible that we might + * be processing this FocusIn after we've changed to a + * different workspace; we should therefore update the MRU + * list only if the window is actually on the active + * workspace. + */ + if (window->screen->active_workspace && + meta_window_located_on_workspace (window, + window->screen->active_workspace)) + { + GList* link; + link = g_list_find (window->screen->active_workspace->mru_list, + window); + g_assert (link); + + window->screen->active_workspace->mru_list = + g_list_remove_link (window->screen->active_workspace->mru_list, + link); + g_list_free (link); + + window->screen->active_workspace->mru_list = + g_list_prepend (window->screen->active_workspace->mru_list, + window); + } + + if (window->frame) + meta_frame_queue_draw (window->frame); + + meta_error_trap_push (window->display); + XInstallColormap (window->display->xdisplay, + window->colormap); + meta_error_trap_pop (window->display, FALSE); + + /* move into FOCUSED_WINDOW layer */ + meta_window_update_layer (window); + + /* Ungrab click to focus button since the sync grab can interfere + * with some things you might do inside the focused window, by + * causing the client to get funky enter/leave events. + * + * The reason we usually have a passive grab on the window is + * so that we can intercept clicks and raise the window in + * response. For click-to-focus we don't need that since the + * focused window is already raised. When raise_on_click is + * FALSE we also don't need that since we don't do anything + * when the window is clicked. + * + * There is dicussion in bugs 102209, 115072, and 461577 + */ + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK || + !meta_prefs_get_raise_on_click()) + meta_display_ungrab_focus_window_button (window->display, window); + } + } + else if (event->type == FocusOut || + event->type == UnmapNotify) + { + if (event->type == FocusOut && + event->xfocus.detail == NotifyInferior) + { + /* This event means the client moved focus to a subwindow */ + meta_topic (META_DEBUG_FOCUS, + "Ignoring focus out on %s with NotifyInferior\n", + window->desc); + return TRUE; + } + + if (window == window->display->focus_window) + { + meta_topic (META_DEBUG_FOCUS, + "%s is now the previous focus window due to being focused out or unmapped\n", + window->desc); + + meta_topic (META_DEBUG_FOCUS, + "* Focus --> NULL (was %s)\n", window->desc); + + window->display->focus_window = NULL; + window->has_focus = FALSE; + if (window->frame) + meta_frame_queue_draw (window->frame); + + meta_compositor_set_active_window (window->display->compositor, + window->screen, NULL); + + meta_error_trap_push (window->display); + XUninstallColormap (window->display->xdisplay, + window->colormap); + meta_error_trap_pop (window->display, FALSE); + + /* move out of FOCUSED_WINDOW layer */ + meta_window_update_layer (window); + + /* Re-grab for click to focus and raise-on-click, if necessary */ + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK || + !meta_prefs_get_raise_on_click ()) + meta_display_grab_focus_window_button (window->display, window); + } + } + + /* Now set _NET_ACTIVE_WINDOW hint */ + meta_display_update_active_window_hint (window->display); + + return FALSE; +} + +static gboolean +process_property_notify (MetaWindow *window, + XPropertyEvent *event) +{ + Window xid = window->xwindow; + + if (meta_is_verbose ()) /* avoid looking up the name if we don't have to */ + { + char *property_name = XGetAtomName (window->display->xdisplay, + event->atom); + + meta_verbose ("Property notify on %s for %s\n", + window->desc, property_name); + XFree (property_name); + } + + if (event->atom == window->display->atom__NET_WM_USER_TIME && + window->user_time_window) + { + xid = window->user_time_window; + } + + meta_window_reload_property (window, event->atom, FALSE); + + return TRUE; +} + +static void +send_configure_notify (MetaWindow *window) +{ + XEvent event; + + /* from twm */ + + event.type = ConfigureNotify; + event.xconfigure.display = window->display->xdisplay; + event.xconfigure.event = window->xwindow; + event.xconfigure.window = window->xwindow; + event.xconfigure.x = window->rect.x - window->border_width; + event.xconfigure.y = window->rect.y - window->border_width; + if (window->frame) + { + if (window->withdrawn) + { + /* WARNING: x & y need to be set to whatever the XReparentWindow + * call in meta_window_destroy_frame will use so that the window + * has the right coordinates. Currently, that means no change to + * x & y. + */ + } + else + { + /* Need to be in root window coordinates */ + event.xconfigure.x += window->frame->rect.x; + event.xconfigure.y += window->frame->rect.y; + } + } + event.xconfigure.width = window->rect.width; + event.xconfigure.height = window->rect.height; + event.xconfigure.border_width = window->border_width; /* requested not actual */ + event.xconfigure.above = None; /* FIXME */ + event.xconfigure.override_redirect = False; + + meta_topic (META_DEBUG_GEOMETRY, + "Sending synthetic configure notify to %s with x: %d y: %d w: %d h: %d\n", + window->desc, + event.xconfigure.x, event.xconfigure.y, + event.xconfigure.width, event.xconfigure.height); + + meta_error_trap_push (window->display); + XSendEvent (window->display->xdisplay, + window->xwindow, + False, StructureNotifyMask, &event); + meta_error_trap_pop (window->display, FALSE); +} + +gboolean +meta_window_get_icon_geometry (MetaWindow *window, + MetaRectangle *rect) +{ + gulong *geometry = NULL; + int nitems; + + if (meta_prop_get_cardinal_list (window->display, + window->xwindow, + window->display->atom__NET_WM_ICON_GEOMETRY, + &geometry, &nitems)) + { + if (nitems != 4) + { + meta_verbose ("_NET_WM_ICON_GEOMETRY on %s has %d values instead of 4\n", + window->desc, nitems); + meta_XFree (geometry); + return FALSE; + } + + if (rect) + { + rect->x = geometry[0]; + rect->y = geometry[1]; + rect->width = geometry[2]; + rect->height = geometry[3]; + } + + meta_XFree (geometry); + + return TRUE; + } + + return FALSE; +} + +static Window +read_client_leader (MetaDisplay *display, + Window xwindow) +{ + Window retval = None; + + meta_prop_get_window (display, xwindow, + display->atom_WM_CLIENT_LEADER, + &retval); + + return retval; +} + +typedef struct +{ + Window leader; +} ClientLeaderData; + +static gboolean +find_client_leader_func (MetaWindow *ancestor, + void *data) +{ + ClientLeaderData *d; + + d = data; + + d->leader = read_client_leader (ancestor->display, + ancestor->xwindow); + + /* keep going if no client leader found */ + return d->leader == None; +} + +static void +update_sm_hints (MetaWindow *window) +{ + Window leader; + + window->xclient_leader = None; + window->sm_client_id = NULL; + + /* If not on the current window, we can get the client + * leader from transient parents. If we find a client + * leader, we read the SM_CLIENT_ID from it. + */ + leader = read_client_leader (window->display, window->xwindow); + if (leader == None) + { + ClientLeaderData d; + d.leader = None; + meta_window_foreach_ancestor (window, find_client_leader_func, + &d); + leader = d.leader; + } + + if (leader != None) + { + char *str; + + window->xclient_leader = leader; + + if (meta_prop_get_latin1_string (window->display, leader, + window->display->atom_SM_CLIENT_ID, + &str)) + { + window->sm_client_id = g_strdup (str); + meta_XFree (str); + } + } + else + { + meta_verbose ("Didn't find a client leader for %s\n", window->desc); + + if (!meta_prefs_get_disable_workarounds ()) + { + /* Some broken apps (kdelibs fault?) set SM_CLIENT_ID on the app + * instead of the client leader + */ + char *str; + + str = NULL; + if (meta_prop_get_latin1_string (window->display, window->xwindow, + window->display->atom_SM_CLIENT_ID, + &str)) + { + if (window->sm_client_id == NULL) /* first time through */ + meta_warning (_("Window %s sets SM_CLIENT_ID on itself, instead of on the WM_CLIENT_LEADER window as specified in the ICCCM.\n"), + window->desc); + + window->sm_client_id = g_strdup (str); + meta_XFree (str); + } + } + } + + meta_verbose ("Window %s client leader: 0x%lx SM_CLIENT_ID: '%s'\n", + window->desc, window->xclient_leader, + window->sm_client_id ? window->sm_client_id : "none"); +} + +void +meta_window_update_role (MetaWindow *window) +{ + char *str; + + if (window->role) + g_free (window->role); + window->role = NULL; + + if (meta_prop_get_latin1_string (window->display, window->xwindow, + window->display->atom_WM_WINDOW_ROLE, + &str)) + { + window->role = g_strdup (str); + meta_XFree (str); + } + + meta_verbose ("Updated role of %s to '%s'\n", + window->desc, window->role ? window->role : "null"); +} + +void +meta_window_update_net_wm_type (MetaWindow *window) +{ + int n_atoms; + Atom *atoms; + int i; + + window->type_atom = None; + n_atoms = 0; + atoms = NULL; + + meta_prop_get_atom_list (window->display, window->xwindow, + window->display->atom__NET_WM_WINDOW_TYPE, + &atoms, &n_atoms); + + i = 0; + while (i < n_atoms) + { + /* We break as soon as we find one we recognize, + * supposed to prefer those near the front of the list + */ + if (atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_DESKTOP || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_DOCK || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_TOOLBAR || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_MENU || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_DIALOG || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_NORMAL || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_UTILITY || + atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_SPLASH) + { + window->type_atom = atoms[i]; + break; + } + + ++i; + } + + meta_XFree (atoms); + + if (meta_is_verbose ()) + { + char *str; + + str = NULL; + if (window->type_atom != None) + { + meta_error_trap_push (window->display); + str = XGetAtomName (window->display->xdisplay, window->type_atom); + meta_error_trap_pop (window->display, TRUE); + } + + meta_verbose ("Window %s type atom %s\n", window->desc, + str ? str : "(none)"); + + if (str) + meta_XFree (str); + } + + meta_window_recalc_window_type (window); +} + +static void +redraw_icon (MetaWindow *window) +{ + /* We could probably be smart and just redraw the icon here, + * instead of the whole frame. + */ + if (window->frame && (window->mapped || window->frame->mapped)) + meta_ui_queue_frame_draw (window->screen->ui, window->frame->xwindow); +} + +void +meta_window_update_icon_now (MetaWindow *window) +{ + GdkPixbuf *icon; + GdkPixbuf *mini_icon; + + icon = NULL; + mini_icon = NULL; + + if (meta_read_icons (window->screen, + window->xwindow, + &window->icon_cache, + window->wm_hints_pixmap, + window->wm_hints_mask, + &icon, + META_ICON_WIDTH, META_ICON_HEIGHT, + &mini_icon, + META_MINI_ICON_WIDTH, + META_MINI_ICON_HEIGHT)) + { + if (window->icon) + g_object_unref (G_OBJECT (window->icon)); + + if (window->mini_icon) + g_object_unref (G_OBJECT (window->mini_icon)); + + window->icon = icon; + window->mini_icon = mini_icon; + + redraw_icon (window); + } + + g_assert (window->icon); + g_assert (window->mini_icon); +} + +static gboolean +idle_update_icon (gpointer data) +{ + GSList *tmp; + GSList *copy; + guint queue_index = GPOINTER_TO_INT (data); + + meta_topic (META_DEBUG_GEOMETRY, "Clearing the update_icon queue\n"); + + /* Work with a copy, for reentrancy. The allowed reentrancy isn't + * complete; destroying a window while we're in here would result in + * badness. But it's OK to queue/unqueue update_icons. + */ + copy = g_slist_copy (queue_pending[queue_index]); + g_slist_free (queue_pending[queue_index]); + queue_pending[queue_index] = NULL; + queue_idle[queue_index] = 0; + + destroying_windows_disallowed += 1; + + tmp = copy; + while (tmp != NULL) + { + MetaWindow *window; + + window = tmp->data; + + meta_window_update_icon_now (window); + window->is_in_queues &= ~META_QUEUE_UPDATE_ICON; + + tmp = tmp->next; + } + + g_slist_free (copy); + + destroying_windows_disallowed -= 1; + + return FALSE; +} + +GList* +meta_window_get_workspaces (MetaWindow *window) +{ + if (window->on_all_workspaces) + return window->screen->workspaces; + else + return window->workspace->list_containing_self; +} + +static void +invalidate_work_areas (MetaWindow *window) +{ + GList *tmp; + + tmp = meta_window_get_workspaces (window); + + while (tmp != NULL) + { + meta_workspace_invalidate_work_area (tmp->data); + tmp = tmp->next; + } +} + +void +meta_window_update_struts (MetaWindow *window) +{ + GSList *old_struts; + GSList *new_struts; + GSList *old_iter, *new_iter; + gulong *struts = NULL; + int nitems; + gboolean changed; + + meta_verbose ("Updating struts for %s\n", window->desc); + + old_struts = window->struts; + new_struts = NULL; + + if (meta_prop_get_cardinal_list (window->display, + window->xwindow, + window->display->atom__NET_WM_STRUT_PARTIAL, + &struts, &nitems)) + { + if (nitems != 12) + meta_verbose ("_NET_WM_STRUT_PARTIAL on %s has %d values instead " + "of 12\n", + window->desc, nitems); + else + { + /* Pull out the strut info for each side in the hint */ + int i; + for (i=0; i<4; i++) + { + MetaStrut *temp; + int thickness, strut_begin, strut_end; + + thickness = struts[i]; + if (thickness == 0) + continue; + strut_begin = struts[4+(i*2)]; + strut_end = struts[4+(i*2)+1]; + + temp = g_new (MetaStrut, 1); + temp->side = 1 << i; /* See MetaSide def. Matches nicely, eh? */ + temp->rect = window->screen->rect; + switch (temp->side) + { + case META_SIDE_RIGHT: + temp->rect.x = BOX_RIGHT(temp->rect) - thickness; + /* Intentionally fall through without breaking */ + case META_SIDE_LEFT: + temp->rect.width = thickness; + temp->rect.y = strut_begin; + temp->rect.height = strut_end - strut_begin + 1; + break; + case META_SIDE_BOTTOM: + temp->rect.y = BOX_BOTTOM(temp->rect) - thickness; + /* Intentionally fall through without breaking */ + case META_SIDE_TOP: + temp->rect.height = thickness; + temp->rect.x = strut_begin; + temp->rect.width = strut_end - strut_begin + 1; + break; + default: + g_assert_not_reached (); + } + + new_struts = g_slist_prepend (new_struts, temp); + } + + meta_verbose ("_NET_WM_STRUT_PARTIAL struts %lu %lu %lu %lu for " + "window %s\n", + struts[0], struts[1], struts[2], struts[3], + window->desc); + } + meta_XFree (struts); + } + else + { + meta_verbose ("No _NET_WM_STRUT property for %s\n", + window->desc); + } + + if (!new_struts && + meta_prop_get_cardinal_list (window->display, + window->xwindow, + window->display->atom__NET_WM_STRUT, + &struts, &nitems)) + { + if (nitems != 4) + meta_verbose ("_NET_WM_STRUT on %s has %d values instead of 4\n", + window->desc, nitems); + else + { + /* Pull out the strut info for each side in the hint */ + int i; + for (i=0; i<4; i++) + { + MetaStrut *temp; + int thickness; + + thickness = struts[i]; + if (thickness == 0) + continue; + + temp = g_new (MetaStrut, 1); + temp->side = 1 << i; + temp->rect = window->screen->rect; + switch (temp->side) + { + case META_SIDE_RIGHT: + temp->rect.x = BOX_RIGHT(temp->rect) - thickness; + /* Intentionally fall through without breaking */ + case META_SIDE_LEFT: + temp->rect.width = thickness; + break; + case META_SIDE_BOTTOM: + temp->rect.y = BOX_BOTTOM(temp->rect) - thickness; + /* Intentionally fall through without breaking */ + case META_SIDE_TOP: + temp->rect.height = thickness; + break; + default: + g_assert_not_reached (); + } + + new_struts = g_slist_prepend (new_struts, temp); + } + + meta_verbose ("_NET_WM_STRUT struts %lu %lu %lu %lu for window %s\n", + struts[0], struts[1], struts[2], struts[3], + window->desc); + } + meta_XFree (struts); + } + else if (!new_struts) + { + meta_verbose ("No _NET_WM_STRUT property for %s\n", + window->desc); + } + + /* Determine whether old_struts and new_struts are the same */ + old_iter = old_struts; + new_iter = new_struts; + while (old_iter && new_iter) + { + MetaStrut *old_strut = (MetaStrut*) old_iter->data; + MetaStrut *new_strut = (MetaStrut*) new_iter->data; + + if (old_strut->side != new_strut->side || + !meta_rectangle_equal (&old_strut->rect, &new_strut->rect)) + break; + + old_iter = old_iter->next; + new_iter = new_iter->next; + } + changed = (old_iter != NULL || new_iter != NULL); + + /* Update appropriately */ + meta_free_gslist_and_elements (old_struts); + window->struts = new_struts; + if (changed) + { + meta_topic (META_DEBUG_WORKAREA, + "Invalidating work areas of window %s due to struts update\n", + window->desc); + invalidate_work_areas (window); + } + else + { + meta_topic (META_DEBUG_WORKAREA, + "Struts on %s were unchanged\n", window->desc); + } +} + +void +meta_window_recalc_window_type (MetaWindow *window) +{ + recalc_window_type (window); +} + +static void +recalc_window_type (MetaWindow *window) +{ + MetaWindowType old_type; + + old_type = window->type; + + if (window->type_atom != None) + { + if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_DESKTOP) + window->type = META_WINDOW_DESKTOP; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_DOCK) + window->type = META_WINDOW_DOCK; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_TOOLBAR) + window->type = META_WINDOW_TOOLBAR; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_MENU) + window->type = META_WINDOW_MENU; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_DIALOG) + window->type = META_WINDOW_DIALOG; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_NORMAL) + window->type = META_WINDOW_NORMAL; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_UTILITY) + window->type = META_WINDOW_UTILITY; + else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_SPLASH) + window->type = META_WINDOW_SPLASHSCREEN; + else + meta_bug ("Set a type atom for %s that wasn't handled in recalc_window_type\n", + window->desc); + } + else if (window->xtransient_for != None) + { + window->type = META_WINDOW_DIALOG; + } + else + { + window->type = META_WINDOW_NORMAL; + } + + if (window->type == META_WINDOW_DIALOG && + window->wm_state_modal) + window->type = META_WINDOW_MODAL_DIALOG; + + meta_verbose ("Calculated type %u for %s, old type %u\n", + window->type, window->desc, old_type); + + if (old_type != window->type) + { + recalc_window_features (window); + + set_net_wm_state (window); + + /* Update frame */ + if (window->decorated) + meta_window_ensure_frame (window); + else + meta_window_destroy_frame (window); + + /* update stacking constraints */ + meta_window_update_layer (window); + + meta_window_grab_keys (window); + } +} + +static void +set_allowed_actions_hint (MetaWindow *window) +{ +#define MAX_N_ACTIONS 12 + unsigned long data[MAX_N_ACTIONS]; + int i; + + i = 0; + if (window->has_move_func) + { + data[i] = window->display->atom__NET_WM_ACTION_MOVE; + ++i; + } + if (window->has_resize_func) + { + data[i] = window->display->atom__NET_WM_ACTION_RESIZE; + ++i; + } + if (window->has_fullscreen_func) + { + data[i] = window->display->atom__NET_WM_ACTION_FULLSCREEN; + ++i; + } + if (window->has_minimize_func) + { + data[i] = window->display->atom__NET_WM_ACTION_MINIMIZE; + ++i; + } + if (window->has_shade_func) + { + data[i] = window->display->atom__NET_WM_ACTION_SHADE; + ++i; + } + /* sticky according to EWMH is different from marco's sticky; + * marco doesn't support EWMH sticky + */ + if (window->has_maximize_func) + { + data[i] = window->display->atom__NET_WM_ACTION_MAXIMIZE_HORZ; + ++i; + data[i] = window->display->atom__NET_WM_ACTION_MAXIMIZE_VERT; + ++i; + } + /* We always allow this */ + data[i] = window->display->atom__NET_WM_ACTION_CHANGE_DESKTOP; + ++i; + if (window->has_close_func) + { + data[i] = window->display->atom__NET_WM_ACTION_CLOSE; + ++i; + } + + /* I guess we always allow above/below operations */ + data[i] = window->display->atom__NET_WM_ACTION_ABOVE; + ++i; + data[i] = window->display->atom__NET_WM_ACTION_BELOW; + ++i; + + g_assert (i <= MAX_N_ACTIONS); + + meta_verbose ("Setting _NET_WM_ALLOWED_ACTIONS with %d atoms\n", i); + + meta_error_trap_push (window->display); + XChangeProperty (window->display->xdisplay, window->xwindow, + window->display->atom__NET_WM_ALLOWED_ACTIONS, + XA_ATOM, + 32, PropModeReplace, (guchar*) data, i); + meta_error_trap_pop (window->display, FALSE); +#undef MAX_N_ACTIONS +} + +void +meta_window_recalc_features (MetaWindow *window) +{ + recalc_window_features (window); +} + +static void +recalc_window_features (MetaWindow *window) +{ + gboolean old_has_close_func; + gboolean old_has_minimize_func; + gboolean old_has_move_func; + gboolean old_has_resize_func; + gboolean old_has_shade_func; + gboolean old_always_sticky; + + old_has_close_func = window->has_close_func; + old_has_minimize_func = window->has_minimize_func; + old_has_move_func = window->has_move_func; + old_has_resize_func = window->has_resize_func; + old_has_shade_func = window->has_shade_func; + old_always_sticky = window->always_sticky; + + /* Use MWM hints initially */ + window->decorated = window->mwm_decorated; + window->border_only = window->mwm_border_only; + window->has_close_func = window->mwm_has_close_func; + window->has_minimize_func = window->mwm_has_minimize_func; + window->has_maximize_func = window->mwm_has_maximize_func; + window->has_move_func = window->mwm_has_move_func; + + window->has_resize_func = TRUE; + + /* If min_size == max_size, then don't allow resize */ + if (window->size_hints.min_width == window->size_hints.max_width && + window->size_hints.min_height == window->size_hints.max_height) + window->has_resize_func = FALSE; + else if (!window->mwm_has_resize_func) + { + /* We ignore mwm_has_resize_func because WM_NORMAL_HINTS is the + * authoritative source for that info. Some apps such as mplayer or + * xine disable resize via MWM but not WM_NORMAL_HINTS, but that + * leads to e.g. us not fullscreening their windows. Apps that set + * MWM but not WM_NORMAL_HINTS are basically broken. We complain + * about these apps but make them work. + */ + + meta_warning (_("Window %s sets an MWM hint indicating it isn't resizable, but sets min size %d x %d and max size %d x %d; this doesn't make much sense.\n"), + window->desc, + window->size_hints.min_width, + window->size_hints.min_height, + window->size_hints.max_width, + window->size_hints.max_height); + } + + window->has_shade_func = TRUE; + window->has_fullscreen_func = TRUE; + + window->always_sticky = FALSE; + + /* Semantic category overrides the MWM hints */ + if (window->type == META_WINDOW_TOOLBAR) + window->decorated = FALSE; + + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + window->always_sticky = TRUE; + + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->type == META_WINDOW_SPLASHSCREEN) + { + window->decorated = FALSE; + window->has_close_func = FALSE; + window->has_shade_func = FALSE; + + /* FIXME this keeps panels and things from using + * NET_WM_MOVERESIZE; the problem is that some + * panels (edge panels) have fixed possible locations, + * and others ("floating panels") do not. + * + * Perhaps we should require edge panels to explicitly + * disable movement? + */ + window->has_move_func = FALSE; + window->has_resize_func = FALSE; + } + + if (window->type != META_WINDOW_NORMAL) + { + window->has_minimize_func = FALSE; + window->has_maximize_func = FALSE; + window->has_fullscreen_func = FALSE; + } + + if (!window->has_resize_func) + { + window->has_maximize_func = FALSE; + + /* don't allow fullscreen if we can't resize, unless the size + * is entire screen size (kind of broken, because we + * actually fullscreen to xinerama head size not screen size) + */ + if (window->size_hints.min_width == window->screen->rect.width && + window->size_hints.min_height == window->screen->rect.height) + ; /* leave fullscreen available */ + else + window->has_fullscreen_func = FALSE; + } + + /* We leave fullscreen windows decorated, just push the frame outside + * the screen. This avoids flickering to unparent them. + * + * Note that setting has_resize_func = FALSE here must come after the + * above code that may disable fullscreen, because if the window + * is not resizable purely due to fullscreen, we don't want to + * disable fullscreen mode. + */ + if (window->fullscreen) + { + window->has_shade_func = FALSE; + window->has_move_func = FALSE; + window->has_resize_func = FALSE; + window->has_maximize_func = FALSE; + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Window %s fullscreen = %d not resizable, maximizable = %d fullscreenable = %d min size %dx%d max size %dx%d\n", + window->desc, + window->fullscreen, + window->has_maximize_func, window->has_fullscreen_func, + window->size_hints.min_width, + window->size_hints.min_height, + window->size_hints.max_width, + window->size_hints.max_height); + + /* no shading if not decorated */ + if (!window->decorated || window->border_only) + window->has_shade_func = FALSE; + + window->skip_taskbar = FALSE; + window->skip_pager = FALSE; + + if (window->wm_state_skip_taskbar) + window->skip_taskbar = TRUE; + + if (window->wm_state_skip_pager) + window->skip_pager = TRUE; + + switch (window->type) + { + /* Force skip taskbar/pager on these window types */ + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + case META_WINDOW_MENU: + case META_WINDOW_UTILITY: + case META_WINDOW_SPLASHSCREEN: + window->skip_taskbar = TRUE; + window->skip_pager = TRUE; + break; + + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + /* only skip taskbar if we have a real transient parent */ + if (window->xtransient_for != None && + window->xtransient_for != window->screen->xroot) + window->skip_taskbar = TRUE; + break; + + case META_WINDOW_NORMAL: + break; + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Window %s decorated = %d border_only = %d has_close = %d has_minimize = %d has_maximize = %d has_move = %d has_shade = %d skip_taskbar = %d skip_pager = %d\n", + window->desc, + window->decorated, + window->border_only, + window->has_close_func, + window->has_minimize_func, + window->has_maximize_func, + window->has_move_func, + window->has_shade_func, + window->skip_taskbar, + window->skip_pager); + + /* FIXME: + * Lame workaround for recalc_window_features + * being used overzealously. The fix is to + * only recalc_window_features when something + * has actually changed. + */ + if (window->constructing || + old_has_close_func != window->has_close_func || + old_has_minimize_func != window->has_minimize_func || + old_has_move_func != window->has_move_func || + old_has_resize_func != window->has_resize_func || + old_has_shade_func != window->has_shade_func || + old_always_sticky != window->always_sticky) + set_allowed_actions_hint (window); + + /* FIXME perhaps should ensure if we don't have a shade func, + * we aren't shaded, etc. + */ +} + +static void +menu_callback (MetaWindowMenu *menu, + Display *xdisplay, + Window client_xwindow, + guint32 timestamp, + MetaMenuOp op, + int workspace_index, + gpointer data) +{ + MetaDisplay *display; + MetaWindow *window; + MetaWorkspace *workspace; + + display = meta_display_for_x_display (xdisplay); + window = meta_display_lookup_x_window (display, client_xwindow); + workspace = NULL; + + if (window != NULL) /* window can be NULL */ + { + meta_verbose ("Menu op %u on %s\n", op, window->desc); + + switch (op) + { + case META_MENU_OP_NONE: + /* nothing */ + break; + + case META_MENU_OP_DELETE: + meta_window_delete (window, timestamp); + break; + + case META_MENU_OP_MINIMIZE: + meta_window_minimize (window); + break; + + case META_MENU_OP_UNMAXIMIZE: + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + break; + + case META_MENU_OP_MAXIMIZE: + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + break; + + case META_MENU_OP_UNSHADE: + meta_window_unshade (window, timestamp); + break; + + case META_MENU_OP_SHADE: + meta_window_shade (window, timestamp); + break; + + case META_MENU_OP_MOVE_LEFT: + workspace = meta_workspace_get_neighbor (window->screen->active_workspace, + META_MOTION_LEFT); + break; + + case META_MENU_OP_MOVE_RIGHT: + workspace = meta_workspace_get_neighbor (window->screen->active_workspace, + META_MOTION_RIGHT); + break; + + case META_MENU_OP_MOVE_UP: + workspace = meta_workspace_get_neighbor (window->screen->active_workspace, + META_MOTION_UP); + break; + + case META_MENU_OP_MOVE_DOWN: + workspace = meta_workspace_get_neighbor (window->screen->active_workspace, + META_MOTION_DOWN); + break; + + case META_MENU_OP_WORKSPACES: + workspace = meta_screen_get_workspace_by_index (window->screen, + workspace_index); + break; + + case META_MENU_OP_STICK: + meta_window_stick (window); + break; + + case META_MENU_OP_UNSTICK: + meta_window_unstick (window); + break; + + case META_MENU_OP_ABOVE: + case META_MENU_OP_UNABOVE: + if (window->wm_state_above == FALSE) + meta_window_make_above (window); + else + meta_window_unmake_above (window); + break; + + case META_MENU_OP_MOVE: + meta_window_begin_grab_op (window, + META_GRAB_OP_KEYBOARD_MOVING, + TRUE, + timestamp); + break; + + case META_MENU_OP_RESIZE: + meta_window_begin_grab_op (window, + META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN, + TRUE, + timestamp); + break; + + case META_MENU_OP_RECOVER: + meta_window_shove_titlebar_onscreen (window); + break; + + default: + meta_warning (G_STRLOC": Unknown window op\n"); + break; + } + + if (workspace) + { + meta_window_change_workspace (window, + workspace); +#if 0 + meta_workspace_activate (workspace); + meta_window_raise (window); +#endif + } + } + else + { + meta_verbose ("Menu callback on nonexistent window\n"); + } + + if (display->window_menu == menu) + { + display->window_menu = NULL; + display->window_with_menu = NULL; + } + + meta_ui_window_menu_free (menu); +} + +void +meta_window_show_menu (MetaWindow *window, + int root_x, + int root_y, + int button, + guint32 timestamp) +{ + MetaMenuOp ops; + MetaMenuOp insensitive; + MetaWindowMenu *menu; + MetaWorkspaceLayout layout; + int n_workspaces; + gboolean ltr; + + if (window->display->window_menu) + { + meta_ui_window_menu_free (window->display->window_menu); + window->display->window_menu = NULL; + window->display->window_with_menu = NULL; + } + + ops = META_MENU_OP_NONE; + insensitive = META_MENU_OP_NONE; + + ops |= (META_MENU_OP_DELETE | META_MENU_OP_MINIMIZE | META_MENU_OP_MOVE | META_MENU_OP_RESIZE); + + if (!meta_window_titlebar_is_onscreen (window) && + window->type != META_WINDOW_DOCK && + window->type != META_WINDOW_DESKTOP) + ops |= META_MENU_OP_RECOVER; + + n_workspaces = meta_screen_get_n_workspaces (window->screen); + + if (n_workspaces > 1) + ops |= META_MENU_OP_WORKSPACES; + + meta_screen_calc_workspace_layout (window->screen, + n_workspaces, + meta_workspace_index ( window->screen->active_workspace), + &layout); + + if (!window->on_all_workspaces) + { + ltr = meta_ui_get_direction() == META_UI_DIRECTION_LTR; + + if (layout.current_col > 0) + ops |= ltr ? META_MENU_OP_MOVE_LEFT : META_MENU_OP_MOVE_RIGHT; + if ((layout.current_col < layout.cols - 1) && + (layout.current_row * layout.cols + (layout.current_col + 1) < n_workspaces)) + ops |= ltr ? META_MENU_OP_MOVE_RIGHT : META_MENU_OP_MOVE_LEFT; + if (layout.current_row > 0) + ops |= META_MENU_OP_MOVE_UP; + if ((layout.current_row < layout.rows - 1) && + ((layout.current_row + 1) * layout.cols + layout.current_col < n_workspaces)) + ops |= META_MENU_OP_MOVE_DOWN; + } + + meta_screen_free_workspace_layout (&layout); + + if (META_WINDOW_MAXIMIZED (window)) + ops |= META_MENU_OP_UNMAXIMIZE; + else + ops |= META_MENU_OP_MAXIMIZE; + +#if 0 + if (window->shaded) + ops |= META_MENU_OP_UNSHADE; + else + ops |= META_MENU_OP_SHADE; +#endif + + ops |= META_MENU_OP_UNSTICK; + ops |= META_MENU_OP_STICK; + + if (window->wm_state_above) + ops |= META_MENU_OP_UNABOVE; + else + ops |= META_MENU_OP_ABOVE; + + if (!window->has_maximize_func) + insensitive |= META_MENU_OP_UNMAXIMIZE | META_MENU_OP_MAXIMIZE; + + /*if (!window->has_minimize_func) + insensitive |= META_MENU_OP_MINIMIZE;*/ + + /*if (!window->has_close_func) + insensitive |= META_MENU_OP_DELETE;*/ + + if (!window->has_shade_func) + insensitive |= META_MENU_OP_SHADE | META_MENU_OP_UNSHADE; + + if (!META_WINDOW_ALLOWS_MOVE (window)) + insensitive |= META_MENU_OP_MOVE; + + if (!META_WINDOW_ALLOWS_RESIZE (window)) + insensitive |= META_MENU_OP_RESIZE; + + if (window->always_sticky) + insensitive |= META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES; + + if ((window->type == META_WINDOW_DESKTOP) || + (window->type == META_WINDOW_DOCK) || + (window->type == META_WINDOW_SPLASHSCREEN)) + insensitive |= META_MENU_OP_ABOVE | META_MENU_OP_UNABOVE; + + /* If all operations are disabled, just quit without showing the menu. + * This is the case, for example, with META_WINDOW_DESKTOP windows. + */ + if ((ops & ~insensitive) == 0) + return; + + menu = + meta_ui_window_menu_new (window->screen->ui, + window->xwindow, + ops, + insensitive, + meta_window_get_net_wm_desktop (window), + meta_screen_get_n_workspaces (window->screen), + menu_callback, + NULL); + + window->display->window_menu = menu; + window->display->window_with_menu = window; + + meta_verbose ("Popping up window menu for %s\n", window->desc); + + meta_ui_window_menu_popup (menu, root_x, root_y, button, timestamp); +} + +void +meta_window_shove_titlebar_onscreen (MetaWindow *window) +{ + MetaRectangle outer_rect; + GList *onscreen_region; + int horiz_amount, vert_amount; + int newx, newy; + + /* If there's no titlebar, don't bother */ + if (!window->frame) + return; + + /* Get the basic info we need */ + meta_window_get_outer_rect (window, &outer_rect); + onscreen_region = window->screen->active_workspace->screen_region; + + /* Extend the region (just in case the window is too big to fit on the + * screen), then shove the window on screen, then return the region to + * normal. + */ + horiz_amount = outer_rect.width; + vert_amount = outer_rect.height; + meta_rectangle_expand_region (onscreen_region, + horiz_amount, + horiz_amount, + 0, + vert_amount); + meta_rectangle_shove_into_region(onscreen_region, + FIXED_DIRECTION_X, + &outer_rect); + meta_rectangle_expand_region (onscreen_region, + -horiz_amount, + -horiz_amount, + 0, + -vert_amount); + + newx = outer_rect.x + window->frame->child_x; + newy = outer_rect.y + window->frame->child_y; + meta_window_move_resize (window, + FALSE, + newx, + newy, + window->rect.width, + window->rect.height); +} + +gboolean +meta_window_titlebar_is_onscreen (MetaWindow *window) +{ + MetaRectangle titlebar_rect; + GList *onscreen_region; + int titlebar_size; + gboolean is_onscreen; + + const int min_height_needed = 8; + const int min_width_percent = 0.5; + const int min_width_absolute = 50; + + /* Titlebar can't be offscreen if there is no titlebar... */ + if (!window->frame) + return FALSE; + + /* Get the rectangle corresponding to the titlebar */ + meta_window_get_outer_rect (window, &titlebar_rect); + titlebar_rect.height = window->frame->child_y; + titlebar_size = meta_rectangle_area (&titlebar_rect); + + /* Run through the spanning rectangles for the screen and see if one of + * them overlaps with the titlebar sufficiently to consider it onscreen. + */ + is_onscreen = FALSE; + onscreen_region = window->screen->active_workspace->screen_region; + while (onscreen_region) + { + MetaRectangle *spanning_rect = onscreen_region->data; + MetaRectangle overlap; + + meta_rectangle_intersect (&titlebar_rect, spanning_rect, &overlap); + if (overlap.height > MIN (titlebar_rect.height, min_height_needed) && + overlap.width > MIN (titlebar_rect.width * min_width_percent, + min_width_absolute)) + { + is_onscreen = TRUE; + break; + } + + onscreen_region = onscreen_region->next; + } + + return is_onscreen; +} + +static double +timeval_to_ms (const GTimeVal *timeval) +{ + return (timeval->tv_sec * G_USEC_PER_SEC + timeval->tv_usec) / 1000.0; +} + +static double +time_diff (const GTimeVal *first, + const GTimeVal *second) +{ + double first_ms = timeval_to_ms (first); + double second_ms = timeval_to_ms (second); + + return first_ms - second_ms; +} + +static gboolean +check_moveresize_frequency (MetaWindow *window, + gdouble *remaining) +{ + GTimeVal current_time; + + g_get_current_time (¤t_time); + +#ifdef HAVE_XSYNC + if (!window->disable_sync && + window->display->grab_sync_request_alarm != None) + { + if (window->sync_request_time.tv_sec != 0 || + window->sync_request_time.tv_usec != 0) + { + double elapsed = + time_diff (¤t_time, &window->sync_request_time); + + if (elapsed < 1000.0) + { + /* We want to be sure that the timeout happens at + * a time where elapsed will definitely be + * greater than 1000, so we can disable sync + */ + if (remaining) + *remaining = 1000.0 - elapsed + 100; + + return FALSE; + } + else + { + /* We have now waited for more than a second for the + * application to respond to the sync request + */ + window->disable_sync = TRUE; + return TRUE; + } + } + else + { + /* No outstanding sync requests. Go ahead and resize + */ + return TRUE; + } + } + else +#endif /* HAVE_XSYNC */ + { + const double max_resizes_per_second = 25.0; + const double ms_between_resizes = 1000.0 / max_resizes_per_second; + double elapsed; + + elapsed = time_diff (¤t_time, &window->display->grab_last_moveresize_time); + + if (elapsed >= 0.0 && elapsed < ms_between_resizes) + { + meta_topic (META_DEBUG_RESIZING, + "Delaying move/resize as only %g of %g ms elapsed\n", + elapsed, ms_between_resizes); + + if (remaining) + *remaining = (ms_between_resizes - elapsed); + + return FALSE; + } + + meta_topic (META_DEBUG_RESIZING, + " Checked moveresize freq, allowing move/resize now (%g of %g seconds elapsed)\n", + elapsed / 1000.0, 1.0 / max_resizes_per_second); + + return TRUE; + } +} + +static gboolean +update_move_timeout (gpointer data) +{ + MetaWindow *window = data; + + update_move (window, + window->display->grab_last_user_action_was_snap, + window->display->grab_latest_motion_x, + window->display->grab_latest_motion_y); + + return FALSE; +} + +static void +update_move (MetaWindow *window, + gboolean snap, + int x, + int y) +{ + int dx, dy; + int new_x, new_y; + MetaRectangle old; + int shake_threshold; + MetaDisplay *display = window->display; + + display->grab_latest_motion_x = x; + display->grab_latest_motion_y = y; + + dx = x - display->grab_anchor_root_x; + dy = y - display->grab_anchor_root_y; + + new_x = display->grab_anchor_window_pos.x + dx; + new_y = display->grab_anchor_window_pos.y + dy; + + meta_verbose ("x,y = %d,%d anchor ptr %d,%d anchor pos %d,%d dx,dy %d,%d\n", + x, y, + display->grab_anchor_root_x, + display->grab_anchor_root_y, + display->grab_anchor_window_pos.x, + display->grab_anchor_window_pos.y, + dx, dy); + + /* Don't bother doing anything if no move has been specified. (This + * happens often, even in keyboard moving, due to the warping of the + * pointer. + */ + if (dx == 0 && dy == 0) + return; + + /* shake loose (unmaximize) maximized window if dragged beyond the threshold + * in the Y direction. You can't pull a window loose via X motion. + */ + +#define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6 + shake_threshold = meta_ui_get_drag_threshold (window->screen->ui) * + DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR; + + if (META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) + { + double prop; + + /* Shake loose */ + window->shaken_loose = TRUE; + + /* move the unmaximized window to the cursor */ + prop = + ((double)(x - display->grab_initial_window_pos.x)) / + ((double)display->grab_initial_window_pos.width); + + display->grab_initial_window_pos.x = + x - window->saved_rect.width * prop; + display->grab_initial_window_pos.y = y; + + if (window->frame) + { + display->grab_initial_window_pos.y += window->frame->child_y / 2; + } + + window->saved_rect.x = display->grab_initial_window_pos.x; + window->saved_rect.y = display->grab_initial_window_pos.y; + display->grab_anchor_root_x = x; + display->grab_anchor_root_y = y; + + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + + return; + } + /* remaximize window on an other xinerama monitor if window has + * been shaken loose or it is still maximized (then move straight) + */ + else if (window->shaken_loose || META_WINDOW_MAXIMIZED (window)) + { + const MetaXineramaScreenInfo *wxinerama; + MetaRectangle work_area; + int monitor; + + wxinerama = meta_screen_get_xinerama_for_window (window->screen, window); + + for (monitor = 0; monitor < window->screen->n_xinerama_infos; monitor++) + { + meta_window_get_work_area_for_xinerama (window, monitor, &work_area); + + /* check if cursor is near the top of a xinerama work area */ + if (x >= work_area.x && + x < (work_area.x + work_area.width) && + y >= work_area.y && + y < (work_area.y + shake_threshold)) + { + /* move the saved rect if window will become maximized on an + * other monitor so user isn't surprised on a later unmaximize + */ + if (wxinerama->number != monitor) + { + window->saved_rect.x = work_area.x; + window->saved_rect.y = work_area.y; + + if (window->frame) + { + window->saved_rect.x += window->frame->child_x; + window->saved_rect.y += window->frame->child_y; + } + + window->user_rect.x = window->saved_rect.x; + window->user_rect.y = window->saved_rect.y; + + meta_window_unmaximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + } + + display->grab_initial_window_pos = work_area; + display->grab_anchor_root_x = x; + display->grab_anchor_root_y = y; + window->shaken_loose = FALSE; + + meta_window_maximize (window, + META_MAXIMIZE_HORIZONTAL | + META_MAXIMIZE_VERTICAL); + + return; + } + } + } + + if (display->grab_wireframe_active) + old = display->grab_wireframe_rect; + else + meta_window_get_client_root_coords (window, &old); + + /* Don't allow movement in the maximized directions */ + if (window->maximized_horizontally) + new_x = old.x; + if (window->maximized_vertically) + new_y = old.y; + + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_move (window, + old.x, + old.y, + &new_x, + &new_y, + update_move_timeout, + snap, + FALSE); + + if (display->compositor) + { + int root_x = new_x - display->grab_anchor_window_pos.x + display->grab_anchor_root_x; + int root_y = new_y - display->grab_anchor_window_pos.y + display->grab_anchor_root_y; + + meta_compositor_update_move (display->compositor, + window, root_x, root_y); + } + + if (display->grab_wireframe_active) + meta_window_update_wireframe (window, new_x, new_y, + display->grab_wireframe_rect.width, + display->grab_wireframe_rect.height); + else + meta_window_move (window, TRUE, new_x, new_y); +} + +static gboolean +update_resize_timeout (gpointer data) +{ + MetaWindow *window = data; + + update_resize (window, + window->display->grab_last_user_action_was_snap, + window->display->grab_latest_motion_x, + window->display->grab_latest_motion_y, + TRUE); + return FALSE; +} + +static void +update_resize (MetaWindow *window, + gboolean snap, + int x, int y, + gboolean force) +{ + int dx, dy; + int new_w, new_h; + int gravity; + MetaRectangle old; + int new_x, new_y; + double remaining; + + window->display->grab_latest_motion_x = x; + window->display->grab_latest_motion_y = y; + + dx = x - window->display->grab_anchor_root_x; + dy = y - window->display->grab_anchor_root_y; + + new_w = window->display->grab_anchor_window_pos.width; + new_h = window->display->grab_anchor_window_pos.height; + + /* Don't bother doing anything if no move has been specified. (This + * happens often, even in keyboard resizing, due to the warping of the + * pointer. + */ + if (dx == 0 && dy == 0) + return; + + /* FIXME this is only used in wireframe mode */ + new_x = window->display->grab_anchor_window_pos.x; + new_y = window->display->grab_anchor_window_pos.y; + + if (window->display->grab_op == META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN) + { + if ((dx > 0) && (dy > 0)) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_SE; + meta_window_update_keyboard_resize (window, TRUE); + } + else if ((dx < 0) && (dy > 0)) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_SW; + meta_window_update_keyboard_resize (window, TRUE); + } + else if ((dx > 0) && (dy < 0)) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_NE; + meta_window_update_keyboard_resize (window, TRUE); + } + else if ((dx < 0) && (dy < 0)) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_NW; + meta_window_update_keyboard_resize (window, TRUE); + } + else if (dx < 0) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + meta_window_update_keyboard_resize (window, TRUE); + } + else if (dx > 0) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + meta_window_update_keyboard_resize (window, TRUE); + } + else if (dy > 0) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + meta_window_update_keyboard_resize (window, TRUE); + } + else if (dy < 0) + { + window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + meta_window_update_keyboard_resize (window, TRUE); + } + } + + /* FIXME: This stupidity only needed because of wireframe mode and + * the fact that wireframe isn't making use of + * meta_rectangle_resize_with_gravity(). If we were to use that, we + * could just increment new_w and new_h by dx and dy in all cases. + */ + switch (window->display->grab_op) + { + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + new_w += dx; + break; + + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + new_w -= dx; + new_x += dx; + break; + + default: + break; + } + + switch (window->display->grab_op) + { + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + new_h += dy; + break; + + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + new_h -= dy; + new_y += dy; + break; + default: + break; + } + + if (!check_moveresize_frequency (window, &remaining) && !force) + { + /* we are ignoring an event here, so we schedule a + * compensation event when we would otherwise not ignore + * an event. Otherwise we can become stuck if the user never + * generates another event. + */ + if (!window->display->grab_resize_timeout_id) + { + window->display->grab_resize_timeout_id = + g_timeout_add ((int)remaining, update_resize_timeout, window); + } + + return; + } + + /* If we get here, it means the client should have redrawn itself */ + if (window->display->compositor) + meta_compositor_set_updates (window->display->compositor, window, TRUE); + + /* Remove any scheduled compensation events */ + if (window->display->grab_resize_timeout_id) + { + g_source_remove (window->display->grab_resize_timeout_id); + window->display->grab_resize_timeout_id = 0; + } + + if (window->display->grab_wireframe_active) + old = window->display->grab_wireframe_rect; + else + old = window->rect; /* Don't actually care about x,y */ + + /* One sided resizing ought to actually be one-sided, despite the fact that + * aspect ratio windows don't interact nicely with the above stuff. So, + * to avoid some nasty flicker, we enforce that. + */ + switch (window->display->grab_op) + { + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_N: + new_w = old.width; + break; + + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_RESIZING_W: + new_h = old.height; + break; + + default: + break; + } + + /* compute gravity of client during operation */ + gravity = meta_resize_gravity_from_grab_op (window->display->grab_op); + g_assert (gravity >= 0); + + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_resize (window, + old.width, + old.height, + &new_w, + &new_h, + gravity, + update_resize_timeout, + snap, + FALSE); + + if (window->display->grab_wireframe_active) + { + if ((new_x + new_w <= new_x) || (new_y + new_h <= new_y)) + return; + + /* FIXME This is crap. For example, the wireframe isn't + * constrained in the way that a real resize would be. An + * obvious elegant solution is to unmap the window during + * wireframe, but still resize it; however, that probably + * confuses broken clients that have problems with opaque + * resize, they probably don't track their visibility. + */ + meta_window_update_wireframe (window, new_x, new_y, new_w, new_h); + } + else + { + /* We don't need to update unless the specified width and height + * are actually different from what we had before. + */ + if (old.width != new_w || old.height != new_h) + meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity); + } + + /* Store the latest resize time, if we actually resized. */ + if (window->rect.width != old.width || window->rect.height != old.height) + g_get_current_time (&window->display->grab_last_moveresize_time); +} + +typedef struct +{ + const XEvent *current_event; + int count; + guint32 last_time; +} EventScannerData; + +static Bool +find_last_time_predicate (Display *display, + XEvent *xevent, + XPointer arg) +{ + EventScannerData *esd = (void*) arg; + + if (esd->current_event->type == xevent->type && + esd->current_event->xany.window == xevent->xany.window) + { + esd->count += 1; + esd->last_time = xevent->xmotion.time; + } + + return False; +} + +static gboolean +check_use_this_motion_notify (MetaWindow *window, + XEvent *event) +{ + EventScannerData esd; + XEvent useless; + + /* This code is copied from Owen's GDK code. */ + + if (window->display->grab_motion_notify_time != 0) + { + /* == is really the right test, but I'm all for paranoia */ + if (window->display->grab_motion_notify_time <= + event->xmotion.time) + { + meta_topic (META_DEBUG_RESIZING, + "Arrived at event with time %u (waiting for %u), using it\n", + (unsigned int)event->xmotion.time, + window->display->grab_motion_notify_time); + window->display->grab_motion_notify_time = 0; + return TRUE; + } + else + return FALSE; /* haven't reached the saved timestamp yet */ + } + + esd.current_event = event; + esd.count = 0; + esd.last_time = 0; + + /* "useless" isn't filled in because the predicate never returns True */ + XCheckIfEvent (window->display->xdisplay, + &useless, + find_last_time_predicate, + (XPointer) &esd); + + if (esd.count > 0) + meta_topic (META_DEBUG_RESIZING, + "Will skip %d motion events and use the event with time %u\n", + esd.count, (unsigned int) esd.last_time); + + if (esd.last_time == 0) + return TRUE; + else + { + /* Save this timestamp, and ignore all motion notify + * until we get to the one with this stamp. + */ + window->display->grab_motion_notify_time = esd.last_time; + return FALSE; + } +} + +void +meta_window_handle_mouse_grab_op_event (MetaWindow *window, + XEvent *event) +{ +#ifdef HAVE_XSYNC + if (event->type == (window->display->xsync_event_base + XSyncAlarmNotify)) + { + meta_topic (META_DEBUG_RESIZING, + "Alarm event received last motion x = %d y = %d\n", + window->display->grab_latest_motion_x, + window->display->grab_latest_motion_y); + + /* If sync was previously disabled, turn it back on and hope + * the application has come to its senses (maybe it was just + * busy with a pagefault or a long computation). + */ + window->disable_sync = FALSE; + window->sync_request_time.tv_sec = 0; + window->sync_request_time.tv_usec = 0; + + /* This means we are ready for another configure. */ + switch (window->display->grab_op) + { + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + /* no pointer round trip here, to keep in sync */ + update_resize (window, + window->display->grab_last_user_action_was_snap, + window->display->grab_latest_motion_x, + window->display->grab_latest_motion_y, + TRUE); + break; + + default: + break; + } + } +#endif /* HAVE_XSYNC */ + + switch (event->type) + { + case ButtonRelease: + meta_display_check_threshold_reached (window->display, + event->xbutton.x_root, + event->xbutton.y_root); + /* If the user was snap moving then ignore the button release + * because they may have let go of shift before releasing the + * mouse button and they almost certainly do not want a + * non-snapped movement to occur from the button release. + */ + if (!window->display->grab_last_user_action_was_snap) + { + if (meta_grab_op_is_moving (window->display->grab_op)) + { + if (event->xbutton.root == window->screen->xroot) + update_move (window, event->xbutton.state & ShiftMask, + event->xbutton.x_root, event->xbutton.y_root); + } + else if (meta_grab_op_is_resizing (window->display->grab_op)) + { + if (event->xbutton.root == window->screen->xroot) + update_resize (window, + event->xbutton.state & ShiftMask, + event->xbutton.x_root, + event->xbutton.y_root, + TRUE); + if (window->display->compositor) + meta_compositor_set_updates (window->display->compositor, window, TRUE); + } + } + + meta_display_end_grab_op (window->display, event->xbutton.time); + break; + + case MotionNotify: + meta_display_check_threshold_reached (window->display, + event->xmotion.x_root, + event->xmotion.y_root); + if (meta_grab_op_is_moving (window->display->grab_op)) + { + if (event->xmotion.root == window->screen->xroot) + { + if (check_use_this_motion_notify (window, + event)) + update_move (window, + event->xmotion.state & ShiftMask, + event->xmotion.x_root, + event->xmotion.y_root); + } + } + else if (meta_grab_op_is_resizing (window->display->grab_op)) + { + if (event->xmotion.root == window->screen->xroot) + { + if (check_use_this_motion_notify (window, + event)) + update_resize (window, + event->xmotion.state & ShiftMask, + event->xmotion.x_root, + event->xmotion.y_root, + FALSE); + } + } + break; + + default: + break; + } +} + +void +meta_window_set_gravity (MetaWindow *window, + int gravity) +{ + XSetWindowAttributes attrs; + + meta_verbose ("Setting gravity of %s to %d\n", window->desc, gravity); + + attrs.win_gravity = gravity; + + meta_error_trap_push (window->display); + + XChangeWindowAttributes (window->display->xdisplay, + window->xwindow, + CWWinGravity, + &attrs); + + meta_error_trap_pop (window->display, FALSE); +} + +static void +get_work_area_xinerama (MetaWindow *window, + MetaRectangle *area, + int which_xinerama) +{ + GList *tmp; + + g_assert (which_xinerama >= 0); + + /* Initialize to the whole xinerama */ + *area = window->screen->xinerama_infos[which_xinerama].rect; + + tmp = meta_window_get_workspaces (window); + while (tmp != NULL) + { + MetaRectangle workspace_work_area; + meta_workspace_get_work_area_for_xinerama (tmp->data, + which_xinerama, + &workspace_work_area); + meta_rectangle_intersect (area, + &workspace_work_area, + area); + tmp = tmp->next; + } + + meta_topic (META_DEBUG_WORKAREA, + "Window %s xinerama %d has work area %d,%d %d x %d\n", + window->desc, which_xinerama, + area->x, area->y, area->width, area->height); +} + +void +meta_window_get_work_area_current_xinerama (MetaWindow *window, + MetaRectangle *area) +{ + const MetaXineramaScreenInfo *xinerama = NULL; + xinerama = meta_screen_get_xinerama_for_window (window->screen, + window); + + meta_window_get_work_area_for_xinerama (window, + xinerama->number, + area); +} + +void +meta_window_get_work_area_for_xinerama (MetaWindow *window, + int which_xinerama, + MetaRectangle *area) +{ + g_return_if_fail (which_xinerama >= 0); + + get_work_area_xinerama (window, + area, + which_xinerama); +} + +void +meta_window_get_work_area_all_xineramas (MetaWindow *window, + MetaRectangle *area) +{ + GList *tmp; + + /* Initialize to the whole screen */ + *area = window->screen->rect; + + tmp = meta_window_get_workspaces (window); + while (tmp != NULL) + { + MetaRectangle workspace_work_area; + meta_workspace_get_work_area_all_xineramas (tmp->data, + &workspace_work_area); + meta_rectangle_intersect (area, + &workspace_work_area, + area); + tmp = tmp->next; + } + + meta_topic (META_DEBUG_WORKAREA, + "Window %s has whole-screen work area %d,%d %d x %d\n", + window->desc, area->x, area->y, area->width, area->height); +} + + +gboolean +meta_window_same_application (MetaWindow *window, + MetaWindow *other_window) +{ + MetaGroup *group = meta_window_get_group (window); + MetaGroup *other_group = meta_window_get_group (other_window); + + return + group!=NULL && + other_group!=NULL && + group==other_group; +} + +void +meta_window_refresh_resize_popup (MetaWindow *window) +{ + if (window->display->grab_op == META_GRAB_OP_NONE) + return; + + if (window->display->grab_window != window) + return; + + /* We shouldn't ever get called when the wireframe is active + * because that's handled by a different code path in effects.c + */ + if (window->display->grab_wireframe_active) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "refresh_resize_popup called when wireframe active\n"); + return; + } + + switch (window->display->grab_op) + { + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + break; + + default: + /* Not resizing */ + return; + } + + if (window->display->grab_resize_popup == NULL) + { + if (window->size_hints.width_inc > 1 || + window->size_hints.height_inc > 1) + window->display->grab_resize_popup = + meta_ui_resize_popup_new (window->display->xdisplay, + window->screen->number); + } + + if (window->display->grab_resize_popup != NULL) + { + MetaRectangle rect; + + if (window->display->grab_wireframe_active) + rect = window->display->grab_wireframe_rect; + else + meta_window_get_client_root_coords (window, &rect); + + meta_ui_resize_popup_set (window->display->grab_resize_popup, + rect, + window->size_hints.base_width, + window->size_hints.base_height, + window->size_hints.width_inc, + window->size_hints.height_inc); + + meta_ui_resize_popup_set_showing (window->display->grab_resize_popup, + TRUE); + } +} + +void +meta_window_foreach_transient (MetaWindow *window, + MetaWindowForeachFunc func, + void *data) +{ + GSList *windows; + GSList *tmp; + + windows = meta_display_list_windows (window->display); + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *transient = tmp->data; + + if (meta_window_is_ancestor_of_transient (window, transient)) + { + if (!(* func) (transient, data)) + break; + } + + tmp = tmp->next; + } + + g_slist_free (windows); +} + +void +meta_window_foreach_ancestor (MetaWindow *window, + MetaWindowForeachFunc func, + void *data) +{ + MetaWindow *w; + MetaWindow *tortoise; + + w = window; + tortoise = window; + while (TRUE) + { + if (w->xtransient_for == None || + w->transient_parent_is_root_window) + break; + + w = meta_display_lookup_x_window (w->display, w->xtransient_for); + + if (w == NULL || w == tortoise) + break; + + if (!(* func) (w, data)) + break; + + if (w->xtransient_for == None || + w->transient_parent_is_root_window) + break; + + w = meta_display_lookup_x_window (w->display, w->xtransient_for); + + if (w == NULL || w == tortoise) + break; + + if (!(* func) (w, data)) + break; + + tortoise = meta_display_lookup_x_window (tortoise->display, + tortoise->xtransient_for); + + /* "w" should have already covered all ground covered by the + * tortoise, so the following must hold. + */ + g_assert (tortoise != NULL); + g_assert (tortoise->xtransient_for != None); + g_assert (!tortoise->transient_parent_is_root_window); + } +} + +typedef struct +{ + MetaWindow *ancestor; + gboolean found; +} FindAncestorData; + +static gboolean +find_ancestor_func (MetaWindow *window, + void *data) +{ + FindAncestorData *d = data; + + if (window == d->ancestor) + { + d->found = TRUE; + return FALSE; + } + + return TRUE; +} + +gboolean +meta_window_is_ancestor_of_transient (MetaWindow *window, + MetaWindow *transient) +{ + FindAncestorData d; + + d.ancestor = window; + d.found = FALSE; + + meta_window_foreach_ancestor (transient, find_ancestor_func, &d); + + return d.found; +} + +/* Warp pointer to location appropriate for grab, + * return root coordinates where pointer ended up. + */ +static gboolean +warp_grab_pointer (MetaWindow *window, + MetaGrabOp grab_op, + int *x, + int *y) +{ + MetaRectangle rect; + MetaDisplay *display; + + display = window->display; + + /* We may not have done begin_grab_op yet, i.e. may not be in a grab + */ + + if (window == display->grab_window && display->grab_wireframe_active) + { + meta_window_get_xor_rect (window, &display->grab_wireframe_rect, &rect); + } + else + { + meta_window_get_outer_rect (window, &rect); + } + + switch (grab_op) + { + case META_GRAB_OP_KEYBOARD_MOVING: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + *x = rect.width / 2; + *y = rect.height / 2; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_S: + *x = rect.width / 2; + *y = rect.height - 1; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_N: + *x = rect.width / 2; + *y = 0; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_W: + *x = 0; + *y = rect.height / 2; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_E: + *x = rect.width - 1; + *y = rect.height / 2; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + *x = rect.width - 1; + *y = rect.height - 1; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + *x = rect.width - 1; + *y = 0; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + *x = 0; + *y = rect.height - 1; + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + *x = 0; + *y = 0; + break; + + default: + return FALSE; + } + + *x += rect.x; + *y += rect.y; + + /* Avoid weird bouncing at the screen edge; see bug 154706 */ + *x = CLAMP (*x, 0, window->screen->rect.width-1); + *y = CLAMP (*y, 0, window->screen->rect.height-1); + + meta_error_trap_push_with_return (display); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Warping pointer to %d,%d with window at %d,%d\n", + *x, *y, rect.x, rect.y); + + /* Need to update the grab positions so that the MotionNotify and other + * events generated by the XWarpPointer() call below don't cause complete + * funkiness. See bug 124582 and bug 122670. + */ + display->grab_anchor_root_x = *x; + display->grab_anchor_root_y = *y; + display->grab_latest_motion_x = *x; + display->grab_latest_motion_y = *y; + if (display->grab_wireframe_active) + display->grab_anchor_window_pos = display->grab_wireframe_rect; + else + meta_window_get_client_root_coords (window, + &display->grab_anchor_window_pos); + + XWarpPointer (display->xdisplay, + None, + window->screen->xroot, + 0, 0, 0, 0, + *x, *y); + + if (meta_error_trap_pop_with_return (display, FALSE) != Success) + { + meta_verbose ("Failed to warp pointer for window %s\n", + window->desc); + return FALSE; + } + + return TRUE; +} + +void +meta_window_begin_grab_op (MetaWindow *window, + MetaGrabOp op, + gboolean frame_action, + guint32 timestamp) +{ + int x, y; + + warp_grab_pointer (window, + op, &x, &y); + + meta_display_begin_grab_op (window->display, + window->screen, + window, + op, + FALSE, + frame_action, + 0 /* button */, + 0, + timestamp, + x, y); +} + +void +meta_window_update_keyboard_resize (MetaWindow *window, + gboolean update_cursor) +{ + int x, y; + + warp_grab_pointer (window, + window->display->grab_op, + &x, &y); + + if (update_cursor) + { + guint32 timestamp; + /* FIXME: Using CurrentTime is really bad mojo */ + timestamp = CurrentTime; + meta_display_set_grab_op_cursor (window->display, + NULL, + window->display->grab_op, + TRUE, + window->display->grab_xwindow, + timestamp); + } +} + +void +meta_window_update_keyboard_move (MetaWindow *window) +{ + int x, y; + + warp_grab_pointer (window, + window->display->grab_op, + &x, &y); +} + +void +meta_window_update_layer (MetaWindow *window) +{ + MetaGroup *group; + + meta_stack_freeze (window->screen->stack); + group = meta_window_get_group (window); + if (group) + meta_group_update_layers (group); + else + meta_stack_update_layer (window->screen->stack, window); + meta_stack_thaw (window->screen->stack); +} + +/* ensure_mru_position_after ensures that window appears after + * below_this_one in the active_workspace's mru_list (i.e. it treats + * window as having been less recently used than below_this_one) + */ +static void +ensure_mru_position_after (MetaWindow *window, + MetaWindow *after_this_one) +{ + /* This is sort of slow since it runs through the entire list more + * than once (especially considering the fact that we expect the + * windows of interest to be the first two elements in the list), + * but it doesn't matter while we're only using it on new window + * map. + */ + + GList* active_mru_list; + GList* window_position; + GList* after_this_one_position; + + active_mru_list = window->screen->active_workspace->mru_list; + window_position = g_list_find (active_mru_list, window); + after_this_one_position = g_list_find (active_mru_list, after_this_one); + + /* after_this_one_position is NULL when we switch workspaces, but in + * that case we don't need to do any MRU shuffling so we can simply + * return. + */ + if (after_this_one_position == NULL) + return; + + if (g_list_length (window_position) > g_list_length (after_this_one_position)) + { + window->screen->active_workspace->mru_list = + g_list_delete_link (window->screen->active_workspace->mru_list, + window_position); + + window->screen->active_workspace->mru_list = + g_list_insert_before (window->screen->active_workspace->mru_list, + after_this_one_position->next, + window); + } +} + +void +meta_window_stack_just_below (MetaWindow *window, + MetaWindow *below_this_one) +{ + g_return_if_fail (window != NULL); + g_return_if_fail (below_this_one != NULL); + + if (window->stack_position > below_this_one->stack_position) + { + meta_topic (META_DEBUG_STACK, + "Setting stack position of window %s to %d (making it below window %s).\n", + window->desc, + below_this_one->stack_position, + below_this_one->desc); + meta_window_set_stack_position (window, below_this_one->stack_position); + } + else + { + meta_topic (META_DEBUG_STACK, + "Window %s was already below window %s.\n", + window->desc, below_this_one->desc); + } +} + +void +meta_window_set_user_time (MetaWindow *window, + guint32 timestamp) +{ + /* FIXME: If Soeren's suggestion in bug 151984 is implemented, it will allow + * us to sanity check the timestamp here and ensure it doesn't correspond to + * a future time. + */ + + /* Only update the time if this timestamp is newer... */ + if (window->net_wm_user_time_set && + XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time)) + { + meta_topic (META_DEBUG_STARTUP, + "Window %s _NET_WM_USER_TIME not updated to %u, because it " + "is less than %u\n", + window->desc, timestamp, window->net_wm_user_time); + } + else + { + meta_topic (META_DEBUG_STARTUP, + "Window %s has _NET_WM_USER_TIME of %u\n", + window->desc, timestamp); + window->net_wm_user_time_set = TRUE; + window->net_wm_user_time = timestamp; + if (XSERVER_TIME_IS_BEFORE (window->display->last_user_time, timestamp)) + window->display->last_user_time = timestamp; + + /* If this is a terminal, user interaction with it means the user likely + * doesn't want to have focus transferred for now due to new windows. + */ + if (meta_prefs_get_focus_new_windows () == + META_FOCUS_NEW_WINDOWS_STRICT && + __window_is_terminal (window)) + window->display->allow_terminal_deactivation = FALSE; + } +} + +/* Sets the demands_attention hint on a window, but only + * if it's at least partially obscured (see #305882). + */ +void +meta_window_set_demands_attention (MetaWindow *window) +{ + MetaRectangle candidate_rect, other_rect; + GList *stack = window->screen->stack->sorted; + MetaWindow *other_window; + gboolean obscured = FALSE; + + MetaWorkspace *workspace = window->screen->active_workspace; + if (workspace!=window->workspace) + { + /* windows on other workspaces are necessarily obscured */ + obscured = TRUE; + } + else if (window->minimized) + { + obscured = TRUE; + } + else + { + meta_window_get_outer_rect (window, &candidate_rect); + + /* The stack is sorted with the top windows first. */ + + while (stack != NULL && stack->data != window) + { + other_window = stack->data; + stack = stack->next; + + if (other_window->on_all_workspaces || + window->on_all_workspaces || + other_window->workspace == window->workspace) + { + meta_window_get_outer_rect (other_window, &other_rect); + + if (meta_rectangle_overlap (&candidate_rect, &other_rect)) + { + obscured = TRUE; + break; + } + } + } + } + + if (obscured) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Marking %s as needing attention\n", + window->desc); + + window->wm_state_demands_attention = TRUE; + set_net_wm_state (window); + } + else + { + /* If the window's in full view, there's no point setting the flag. */ + + meta_topic (META_DEBUG_WINDOW_OPS, + "Not marking %s as needing attention because " + "it's in full view\n", + window->desc); + } +} + +void +meta_window_unset_demands_attention (MetaWindow *window) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Marking %s as not needing attention\n", window->desc); + + window->wm_state_demands_attention = FALSE; + set_net_wm_state (window); +} + +MetaFrame * +meta_window_get_frame (MetaWindow *window) +{ + return window->frame; +} + +gboolean +meta_window_has_focus (MetaWindow *window) +{ + return window->has_focus; +} + +gboolean +meta_window_is_shaded (MetaWindow *window) +{ + return window->shaded; +} + +MetaRectangle * +meta_window_get_rect (MetaWindow *window) +{ + return &window->rect; +} + +MetaScreen * +meta_window_get_screen (MetaWindow *window) +{ + return window->screen; +} + +MetaDisplay * +meta_window_get_display (MetaWindow *window) +{ + return window->display; +} + +Window +meta_window_get_xwindow (MetaWindow *window) +{ + return window->xwindow; +} diff --git a/src/core/workspace.c b/src/core/workspace.c new file mode 100644 index 00000000..2b28fe0b --- /dev/null +++ b/src/core/workspace.c @@ -0,0 +1,1038 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco Workspaces */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "workspace.h" +#include "errors.h" +#include "prefs.h" +#include <X11/Xatom.h> +#include <string.h> +#include <canberra-gtk.h> + +void meta_workspace_queue_calc_showing (MetaWorkspace *workspace); +static void set_active_space_hint (MetaScreen *screen); +static void focus_ancestor_or_mru_window (MetaWorkspace *workspace, + MetaWindow *not_this_one, + guint32 timestamp); +static void free_this (gpointer candidate, + gpointer dummy); +static void workspace_free_struts (MetaWorkspace *workspace); + +static void +maybe_add_to_list (MetaScreen *screen, MetaWindow *window, gpointer data) +{ + GList **mru_list = data; + + if (window->on_all_workspaces) + *mru_list = g_list_prepend (*mru_list, window); +} + +MetaWorkspace* +meta_workspace_new (MetaScreen *screen) +{ + MetaWorkspace *workspace; + + workspace = g_new (MetaWorkspace, 1); + + workspace->screen = screen; + workspace->screen->workspaces = + g_list_append (workspace->screen->workspaces, workspace); + workspace->windows = NULL; + workspace->mru_list = NULL; + meta_screen_foreach_window (screen, maybe_add_to_list, &workspace->mru_list); + + workspace->work_areas_invalid = TRUE; + workspace->work_area_xinerama = NULL; + workspace->work_area_screen.x = 0; + workspace->work_area_screen.y = 0; + workspace->work_area_screen.width = 0; + workspace->work_area_screen.height = 0; + + workspace->screen_region = NULL; + workspace->xinerama_region = NULL; + workspace->screen_edges = NULL; + workspace->xinerama_edges = NULL; + workspace->list_containing_self = g_list_prepend (NULL, workspace); + + workspace->all_struts = NULL; + + workspace->showing_desktop = FALSE; + + return workspace; +} + +/** Foreach function for workspace_free_struts() */ +static void +free_this (gpointer candidate, gpointer dummy) +{ + g_free (candidate); +} + +/** + * Frees the struts list of a workspace. + * + * \param workspace The workspace. + */ +static void +workspace_free_struts (MetaWorkspace *workspace) +{ + if (workspace->all_struts == NULL) + return; + + g_slist_foreach (workspace->all_struts, free_this, NULL); + g_slist_free (workspace->all_struts); + workspace->all_struts = NULL; +} + +void +meta_workspace_free (MetaWorkspace *workspace) +{ + GList *tmp; + MetaScreen *screen; + int i; + + g_return_if_fail (workspace != workspace->screen->active_workspace); + + /* Here we assume all the windows are already on another workspace + * as well, so they won't be "orphaned" + */ + + tmp = workspace->windows; + while (tmp != NULL) + { + GList *next; + MetaWindow *window = tmp->data; + next = tmp->next; + + /* pop front of list we're iterating over */ + meta_workspace_remove_window (workspace, window); + g_assert (window->workspace != NULL); + + tmp = next; + } + + g_assert (workspace->windows == NULL); + + screen = workspace->screen; + + workspace->screen->workspaces = + g_list_remove (workspace->screen->workspaces, workspace); + + g_free (workspace->work_area_xinerama); + + g_list_free (workspace->mru_list); + g_list_free (workspace->list_containing_self); + + /* screen.c:update_num_workspaces(), which calls us, removes windows from + * workspaces first, which can cause the workareas on the workspace to be + * invalidated (and hence for struts/regions/edges to be freed). + * So, no point trying to double free it; that causes a crash + * anyway. #361804. + */ + + if (!workspace->work_areas_invalid) + { + workspace_free_struts (workspace); + for (i = 0; i < screen->n_xinerama_infos; i++) + meta_rectangle_free_list_and_elements (workspace->xinerama_region[i]); + g_free (workspace->xinerama_region); + meta_rectangle_free_list_and_elements (workspace->screen_region); + meta_rectangle_free_list_and_elements (workspace->screen_edges); + meta_rectangle_free_list_and_elements (workspace->xinerama_edges); + } + + g_free (workspace); + + /* don't bother to reset names, pagers can just ignore + * extra ones + */ +} + +void +meta_workspace_add_window (MetaWorkspace *workspace, + MetaWindow *window) +{ + g_return_if_fail (window->workspace == NULL); + + /* If the window is on all workspaces, we want to add it to all mru + * lists, otherwise just add it to this workspaces mru list + */ + if (window->on_all_workspaces) + { + if (window->workspace == NULL) + { + GList* tmp = window->screen->workspaces; + while (tmp) + { + MetaWorkspace* work = (MetaWorkspace*) tmp->data; + if (!g_list_find (work->mru_list, window)) + work->mru_list = g_list_prepend (work->mru_list, window); + + tmp = tmp->next; + } + } + } + else + { + g_assert (g_list_find (workspace->mru_list, window) == NULL); + workspace->mru_list = g_list_prepend (workspace->mru_list, window); + } + + workspace->windows = g_list_prepend (workspace->windows, window); + window->workspace = workspace; + + meta_window_set_current_workspace_hint (window); + + if (window->struts) + { + meta_topic (META_DEBUG_WORKAREA, + "Invalidating work area of workspace %d since we're adding window %s to it\n", + meta_workspace_index (workspace), window->desc); + meta_workspace_invalidate_work_area (workspace); + } + + /* queue a move_resize since changing workspaces may change + * the relevant struts + */ + meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE); +} + +void +meta_workspace_remove_window (MetaWorkspace *workspace, + MetaWindow *window) +{ + g_return_if_fail (window->workspace == workspace); + + workspace->windows = g_list_remove (workspace->windows, window); + window->workspace = NULL; + + /* If the window is on all workspaces, we don't want to remove it + * from the MRU list unless this causes it to be removed from all + * workspaces + */ + if (window->on_all_workspaces) + { + GList* tmp = window->screen->workspaces; + while (tmp) + { + MetaWorkspace* work = (MetaWorkspace*) tmp->data; + work->mru_list = g_list_remove (work->mru_list, window); + + tmp = tmp->next; + } + } + else + { + workspace->mru_list = g_list_remove (workspace->mru_list, window); + g_assert (g_list_find (workspace->mru_list, window) == NULL); + } + + meta_window_set_current_workspace_hint (window); + + if (window->struts) + { + meta_topic (META_DEBUG_WORKAREA, + "Invalidating work area of workspace %d since we're removing window %s from it\n", + meta_workspace_index (workspace), window->desc); + meta_workspace_invalidate_work_area (workspace); + } + + /* queue a move_resize since changing workspaces may change + * the relevant struts + */ + meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE); +} + +void +meta_workspace_relocate_windows (MetaWorkspace *workspace, + MetaWorkspace *new_home) +{ + GList *tmp; + GList *copy; + + g_return_if_fail (workspace != new_home); + + /* can't modify list we're iterating over */ + copy = g_list_copy (workspace->windows); + + tmp = copy; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + meta_workspace_remove_window (workspace, window); + meta_workspace_add_window (new_home, window); + + tmp = tmp->next; + } + + g_list_free (copy); + + g_assert (workspace->windows == NULL); +} + +void +meta_workspace_queue_calc_showing (MetaWorkspace *workspace) +{ + GList *tmp; + + tmp = workspace->windows; + while (tmp != NULL) + { + meta_window_queue (tmp->data, META_QUEUE_CALC_SHOWING); + + tmp = tmp->next; + } +} + +static void workspace_switch_sound(MetaWorkspace *from, + MetaWorkspace *to) { + + MetaWorkspaceLayout layout; + int i, nw, x, y, fi, ti; + const char *e; + + nw = meta_screen_get_n_workspaces(from->screen); + fi = meta_workspace_index(from); + ti = meta_workspace_index(to); + + meta_screen_calc_workspace_layout(from->screen, + nw, + fi, + &layout); + + for (i = 0; i < nw; i++) + if (layout.grid[i] == ti) + break; + + if (i >= nw) { + meta_bug("Failed to find destination workspace in layout\n"); + goto finish; + } + + y = i / layout.cols; + x = i % layout.cols; + + /* We priorize horizontal over vertical movements here. The + rationale for this is that horizontal movements are probably more + interesting for sound effects because speakers are usually + positioned on a horizontal and not a vertical axis. i.e. your + spatial "Woosh!" effects will easily be able to encode horizontal + movement but not such much vertical movement. */ + + if (x < layout.current_col) + e = "desktop-switch-left"; + else if (x > layout.current_col) + e = "desktop-switch-right"; + else if (y < layout.current_row) + e = "desktop-switch-up"; + else if (y > layout.current_row) + e = "desktop-switch-down"; + else { + meta_bug("Uh, origin and destination workspace at same logic position!\n"); + goto finish; + } + + ca_context_play(ca_gtk_context_get(), 1, + CA_PROP_EVENT_ID, e, + CA_PROP_EVENT_DESCRIPTION, "Desktop switched", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + + finish: + meta_screen_free_workspace_layout (&layout); +} + +void +meta_workspace_activate_with_focus (MetaWorkspace *workspace, + MetaWindow *focus_this, + guint32 timestamp) +{ + MetaWorkspace *old; + MetaWindow *move_window; + + meta_verbose ("Activating workspace %d\n", + meta_workspace_index (workspace)); + + if (workspace->screen->active_workspace == workspace) + return; + + if (workspace->screen->active_workspace) + workspace_switch_sound(workspace->screen->active_workspace, workspace); + + /* Note that old can be NULL; e.g. when starting up */ + old = workspace->screen->active_workspace; + + workspace->screen->active_workspace = workspace; + + set_active_space_hint (workspace->screen); + + /* If the "show desktop" mode is active for either the old workspace + * or the new one *but not both*, then update the + * _net_showing_desktop hint + */ + if (old && (old->showing_desktop ^ workspace->showing_desktop)) + meta_screen_update_showing_desktop_hint (workspace->screen); + + if (old == NULL) + return; + + move_window = NULL; + if (workspace->screen->display->grab_op == META_GRAB_OP_MOVING || + workspace->screen->display->grab_op == META_GRAB_OP_KEYBOARD_MOVING) + move_window = workspace->screen->display->grab_window; + + if (move_window != NULL) + { + if (move_window->on_all_workspaces) + move_window = NULL; /* don't move it after all */ + + /* We put the window on the new workspace, flip spaces, + * then remove from old workspace, so the window + * never gets unmapped and we maintain the button grab + * on it. + * + * \bug This comment appears to be the reverse of what happens + */ + if (move_window && (move_window->workspace != workspace)) + { + meta_workspace_remove_window (old, move_window); + meta_workspace_add_window (workspace, move_window); + } + } + + meta_workspace_queue_calc_showing (old); + meta_workspace_queue_calc_showing (workspace); + + /* FIXME: Why do we need this?!? Isn't it handled in the lines above? */ + if (move_window) + /* Removes window from other spaces */ + meta_window_change_workspace (move_window, workspace); + + if (focus_this) + { + meta_window_focus (focus_this, timestamp); + meta_window_raise (focus_this); + } + else if (move_window) + { + meta_window_raise (move_window); + } + else + { + meta_topic (META_DEBUG_FOCUS, "Focusing default window on new workspace\n"); + meta_workspace_focus_default_window (workspace, NULL, timestamp); + } +} + +void +meta_workspace_activate (MetaWorkspace *workspace, + guint32 timestamp) +{ + meta_workspace_activate_with_focus (workspace, NULL, timestamp); +} + +int +meta_workspace_index (MetaWorkspace *workspace) +{ + int ret; + + ret = g_list_index (workspace->screen->workspaces, workspace); + + if (ret < 0) + meta_bug ("Workspace does not exist to index!\n"); + + return ret; +} + +/* get windows contained on workspace, including workspace->windows + * and also sticky windows. + */ +GList* +meta_workspace_list_windows (MetaWorkspace *workspace) +{ + GSList *display_windows; + GSList *tmp; + GList *workspace_windows; + + display_windows = meta_display_list_windows (workspace->screen->display); + + workspace_windows = NULL; + tmp = display_windows; + while (tmp != NULL) + { + MetaWindow *window = tmp->data; + + if (meta_window_located_on_workspace (window, workspace)) + workspace_windows = g_list_prepend (workspace_windows, + window); + + tmp = tmp->next; + } + + g_slist_free (display_windows); + + return workspace_windows; +} + +static void +set_active_space_hint (MetaScreen *screen) +{ + unsigned long data[1]; + + /* this is because we destroy the spaces in order, + * so we always end up setting a current desktop of + * 0 when closing a screen, so lose the current desktop + * on restart. By doing this we keep the current + * desktop on restart. + */ + if (screen->closing > 0) + return; + + data[0] = meta_workspace_index (screen->active_workspace); + + meta_verbose ("Setting _NET_CURRENT_DESKTOP to %lu\n", data[0]); + + meta_error_trap_push (screen->display); + XChangeProperty (screen->display->xdisplay, screen->xroot, + screen->display->atom__NET_CURRENT_DESKTOP, + XA_CARDINAL, + 32, PropModeReplace, (guchar*) data, 1); + meta_error_trap_pop (screen->display, FALSE); +} + +void +meta_workspace_invalidate_work_area (MetaWorkspace *workspace) +{ + GList *tmp; + GList *windows; + int i; + + if (workspace->work_areas_invalid) + { + meta_topic (META_DEBUG_WORKAREA, + "Work area for workspace %d is already invalid\n", + meta_workspace_index (workspace)); + return; + } + + meta_topic (META_DEBUG_WORKAREA, + "Invalidating work area for workspace %d\n", + meta_workspace_index (workspace)); + + g_free (workspace->work_area_xinerama); + workspace->work_area_xinerama = NULL; + + workspace_free_struts (workspace); + + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + meta_rectangle_free_list_and_elements (workspace->xinerama_region[i]); + g_free (workspace->xinerama_region); + meta_rectangle_free_list_and_elements (workspace->screen_region); + meta_rectangle_free_list_and_elements (workspace->screen_edges); + meta_rectangle_free_list_and_elements (workspace->xinerama_edges); + workspace->xinerama_region = NULL; + workspace->screen_region = NULL; + workspace->screen_edges = NULL; + workspace->xinerama_edges = NULL; + + workspace->work_areas_invalid = TRUE; + + /* redo the size/position constraints on all windows */ + windows = meta_workspace_list_windows (workspace); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + meta_window_queue (w, META_QUEUE_MOVE_RESIZE); + + tmp = tmp->next; + } + + g_list_free (windows); + + meta_screen_queue_workarea_recalc (workspace->screen); +} + +static void +ensure_work_areas_validated (MetaWorkspace *workspace) +{ + GList *windows; + GList *tmp; + MetaRectangle work_area; + int i; /* C89 absolutely sucks... */ + + if (!workspace->work_areas_invalid) + return; + + g_assert (workspace->all_struts == NULL); + g_assert (workspace->xinerama_region == NULL); + g_assert (workspace->screen_region == NULL); + g_assert (workspace->screen_edges == NULL); + g_assert (workspace->xinerama_edges == NULL); + + /* STEP 1: Get the list of struts */ + windows = meta_workspace_list_windows (workspace); + for (tmp = windows; tmp != NULL; tmp = tmp->next) + { + MetaWindow *win = tmp->data; + GSList *s_iter; + + for (s_iter = win->struts; s_iter != NULL; s_iter = s_iter->next) { + MetaStrut *cpy = g_new (MetaStrut, 1); + *cpy = *((MetaStrut *)s_iter->data); + workspace->all_struts = g_slist_prepend (workspace->all_struts, + cpy); + } + } + g_list_free (windows); + + /* STEP 2: Get the maximal/spanning rects for the onscreen and + * on-single-xinerama regions + */ + g_assert (workspace->xinerama_region == NULL); + g_assert (workspace->screen_region == NULL); + + workspace->xinerama_region = g_new (GList*, + workspace->screen->n_xinerama_infos); + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + { + workspace->xinerama_region[i] = + meta_rectangle_get_minimal_spanning_set_for_region ( + &workspace->screen->xinerama_infos[i].rect, + workspace->all_struts); + } + workspace->screen_region = + meta_rectangle_get_minimal_spanning_set_for_region ( + &workspace->screen->rect, + workspace->all_struts); + + /* STEP 3: Get the work areas (region-to-maximize-to) for the screen and + * xineramas. + */ + work_area = workspace->screen->rect; /* start with the screen */ + if (workspace->screen_region == NULL) + work_area = meta_rect (0, 0, -1, -1); + else + meta_rectangle_clip_to_region (workspace->screen_region, + FIXED_DIRECTION_NONE, + &work_area); + + /* Lots of paranoia checks, forcing work_area_screen to be sane */ +#define MIN_SANE_AREA 100 + if (work_area.width < MIN_SANE_AREA) + { + meta_warning ("struts occupy an unusually large percentage of the screen; " + "available remaining width = %d < %d", + work_area.width, MIN_SANE_AREA); + if (work_area.width < 1) + { + work_area.x = (workspace->screen->rect.width - MIN_SANE_AREA)/2; + work_area.width = MIN_SANE_AREA; + } + else + { + int amount = (MIN_SANE_AREA - work_area.width)/2; + work_area.x -= amount; + work_area.width += 2*amount; + } + } + if (work_area.height < MIN_SANE_AREA) + { + meta_warning ("struts occupy an unusually large percentage of the screen; " + "available remaining height = %d < %d", + work_area.height, MIN_SANE_AREA); + if (work_area.height < 1) + { + work_area.y = (workspace->screen->rect.height - MIN_SANE_AREA)/2; + work_area.height = MIN_SANE_AREA; + } + else + { + int amount = (MIN_SANE_AREA - work_area.height)/2; + work_area.y -= amount; + work_area.height += 2*amount; + } + } + workspace->work_area_screen = work_area; + meta_topic (META_DEBUG_WORKAREA, + "Computed work area for workspace %d: %d,%d %d x %d\n", + meta_workspace_index (workspace), + workspace->work_area_screen.x, + workspace->work_area_screen.y, + workspace->work_area_screen.width, + workspace->work_area_screen.height); + + /* Now find the work areas for each xinerama */ + g_free (workspace->work_area_xinerama); + workspace->work_area_xinerama = g_new (MetaRectangle, + workspace->screen->n_xinerama_infos); + + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + { + work_area = workspace->screen->xinerama_infos[i].rect; + + if (workspace->xinerama_region[i] == NULL) + /* FIXME: constraints.c untested with this, but it might be nice for + * a screen reader or magnifier. + */ + work_area = meta_rect (work_area.x, work_area.y, -1, -1); + else + meta_rectangle_clip_to_region (workspace->xinerama_region[i], + FIXED_DIRECTION_NONE, + &work_area); + + workspace->work_area_xinerama[i] = work_area; + meta_topic (META_DEBUG_WORKAREA, + "Computed work area for workspace %d " + "xinerama %d: %d,%d %d x %d\n", + meta_workspace_index (workspace), + i, + workspace->work_area_xinerama[i].x, + workspace->work_area_xinerama[i].y, + workspace->work_area_xinerama[i].width, + workspace->work_area_xinerama[i].height); + } + + /* STEP 4: Make sure the screen_region is nonempty (separate from step 2 + * since it relies on step 3). + */ + if (workspace->screen_region == NULL) + { + MetaRectangle *nonempty_region; + nonempty_region = g_new (MetaRectangle, 1); + *nonempty_region = workspace->work_area_screen; + workspace->screen_region = g_list_prepend (NULL, nonempty_region); + } + + /* STEP 5: Cache screen and xinerama edges for edge resistance and snapping */ + g_assert (workspace->screen_edges == NULL); + g_assert (workspace->xinerama_edges == NULL); + workspace->screen_edges = + meta_rectangle_find_onscreen_edges (&workspace->screen->rect, + workspace->all_struts); + tmp = NULL; + for (i = 0; i < workspace->screen->n_xinerama_infos; i++) + tmp = g_list_prepend (tmp, &workspace->screen->xinerama_infos[i].rect); + workspace->xinerama_edges = + meta_rectangle_find_nonintersected_xinerama_edges (tmp, + workspace->all_struts); + g_list_free (tmp); + + /* We're all done, YAAY! Record that everything has been validated. */ + workspace->work_areas_invalid = FALSE; +} + +void +meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace, + int which_xinerama, + MetaRectangle *area) +{ + g_assert (which_xinerama >= 0); + + ensure_work_areas_validated (workspace); + g_assert (which_xinerama < workspace->screen->n_xinerama_infos); + + *area = workspace->work_area_xinerama[which_xinerama]; +} + +void +meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace, + MetaRectangle *area) +{ + ensure_work_areas_validated (workspace); + + *area = workspace->work_area_screen; +} + +GList* +meta_workspace_get_onscreen_region (MetaWorkspace *workspace) +{ + ensure_work_areas_validated (workspace); + + return workspace->screen_region; +} + +GList* +meta_workspace_get_onxinerama_region (MetaWorkspace *workspace, + int which_xinerama) +{ + ensure_work_areas_validated (workspace); + + return workspace->xinerama_region[which_xinerama]; +} + +#ifdef WITH_VERBOSE_MODE +static char * +meta_motion_direction_to_string (MetaMotionDirection direction) +{ + switch (direction) + { + case META_MOTION_UP: + return "Up"; + case META_MOTION_DOWN: + return "Down"; + case META_MOTION_LEFT: + return "Left"; + case META_MOTION_RIGHT: + return "Right"; + } + + return "Unknown"; +} +#endif /* WITH_VERBOSE_MODE */ + +MetaWorkspace* +meta_workspace_get_neighbor (MetaWorkspace *workspace, + MetaMotionDirection direction) +{ + MetaWorkspaceLayout layout; + int i, current_space, num_workspaces; + gboolean ltr; + + current_space = meta_workspace_index (workspace); + num_workspaces = meta_screen_get_n_workspaces (workspace->screen); + meta_screen_calc_workspace_layout (workspace->screen, num_workspaces, + current_space, &layout); + + meta_verbose ("Getting neighbor of %d in direction %s\n", + current_space, meta_motion_direction_to_string (direction)); + + ltr = meta_ui_get_direction() == META_UI_DIRECTION_LTR; + + switch (direction) + { + case META_MOTION_LEFT: + layout.current_col -= ltr ? 1 : -1; + break; + case META_MOTION_RIGHT: + layout.current_col += ltr ? 1 : -1; + break; + case META_MOTION_UP: + layout.current_row -= 1; + break; + case META_MOTION_DOWN: + layout.current_row += 1; + break; + } + + if (layout.current_col < 0) + layout.current_col = 0; + if (layout.current_col >= layout.cols) + layout.current_col = layout.cols - 1; + if (layout.current_row < 0) + layout.current_row = 0; + if (layout.current_row >= layout.rows) + layout.current_row = layout.rows - 1; + + i = layout.grid[layout.current_row * layout.cols + layout.current_col]; + + if (i < 0) + i = current_space; + + if (i >= num_workspaces) + meta_bug ("calc_workspace_layout left an invalid (too-high) workspace number %d in the grid\n", + i); + + meta_verbose ("Neighbor workspace is %d at row %d col %d\n", + i, layout.current_row, layout.current_col); + + meta_screen_free_workspace_layout (&layout); + + return meta_screen_get_workspace_by_index (workspace->screen, i); +} + +const char* +meta_workspace_get_name (MetaWorkspace *workspace) +{ + return meta_prefs_get_workspace_name (meta_workspace_index (workspace)); +} + +void +meta_workspace_focus_default_window (MetaWorkspace *workspace, + MetaWindow *not_this_one, + guint32 timestamp) +{ + if (timestamp == CurrentTime) + { + meta_warning ("CurrentTime used to choose focus window; " + "focus window may not be correct.\n"); + } + + + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK || + !workspace->screen->display->mouse_mode) + focus_ancestor_or_mru_window (workspace, not_this_one, timestamp); + else + { + MetaWindow * window; + window = meta_screen_get_mouse_window (workspace->screen, not_this_one); + if (window && + window->type != META_WINDOW_DOCK && + window->type != META_WINDOW_DESKTOP) + { + if (timestamp == CurrentTime) + { + + /* We would like for this to never happen. However, if + * it does happen then we kludge since using CurrentTime + * can mean ugly race conditions--and we can avoid these + * by allowing EnterNotify events (which come with + * timestamps) to handle focus. + */ + + meta_topic (META_DEBUG_FOCUS, + "Not focusing mouse window %s because EnterNotify events should handle that\n", window->desc); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Focusing mouse window %s\n", window->desc); + meta_window_focus (window, timestamp); + } + + if (workspace->screen->display->autoraise_window != window && + meta_prefs_get_auto_raise ()) + { + meta_display_queue_autoraise_callback (workspace->screen->display, + window); + } + } + else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_SLOPPY) + focus_ancestor_or_mru_window (workspace, not_this_one, timestamp); + else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_MOUSE) + { + meta_topic (META_DEBUG_FOCUS, + "Setting focus to no_focus_window, since no valid " + "window to focus found.\n"); + meta_display_focus_the_no_focus_window (workspace->screen->display, + workspace->screen, + timestamp); + } + } +} + +static gboolean +record_ancestor (MetaWindow *window, + void *data) +{ + MetaWindow **result = data; + + *result = window; + return FALSE; /* quit with the first ancestor we find */ +} + +/* Focus ancestor of not_this_one if there is one, otherwise focus the MRU + * window on active workspace + */ +static void +focus_ancestor_or_mru_window (MetaWorkspace *workspace, + MetaWindow *not_this_one, + guint32 timestamp) +{ + MetaWindow *window = NULL; + MetaWindow *desktop_window = NULL; + GList *tmp; + + if (not_this_one) + meta_topic (META_DEBUG_FOCUS, + "Focusing MRU window excluding %s\n", not_this_one->desc); + else + meta_topic (META_DEBUG_FOCUS, + "Focusing MRU window\n"); + + /* First, check to see if we need to focus an ancestor of a window */ + if (not_this_one) + { + MetaWindow *ancestor; + ancestor = NULL; + meta_window_foreach_ancestor (not_this_one, record_ancestor, &ancestor); + if (ancestor != NULL) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s, ancestor of %s\n", + ancestor->desc, not_this_one->desc); + + meta_window_focus (ancestor, timestamp); + + /* Also raise the window if in click-to-focus */ + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK) + meta_window_raise (ancestor); + + return; + } + } + + /* No ancestor, look for the MRU window */ + tmp = workspace->mru_list; + + while (tmp) + { + MetaWindow* tmp_window; + tmp_window = ((MetaWindow*) tmp->data); + if (tmp_window != not_this_one && + meta_window_showing_on_its_workspace (tmp_window) && + tmp_window->type != META_WINDOW_DOCK && + tmp_window->type != META_WINDOW_DESKTOP) + { + window = tmp->data; + break; + } + else if (tmp_window != not_this_one && + desktop_window == NULL && + meta_window_showing_on_its_workspace (tmp_window) && + tmp_window->type == META_WINDOW_DESKTOP) + { + /* Found the most recently used desktop window */ + desktop_window = tmp_window; + } + + tmp = tmp->next; + } + + /* If no window was found, default to the MRU desktop-window */ + if (window == NULL) + window = desktop_window; + + if (window) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing workspace MRU window %s\n", window->desc); + + meta_window_focus (window, timestamp); + + /* Also raise the window if in click-to-focus */ + if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK) + meta_window_raise (window); + } + else + { + meta_topic (META_DEBUG_FOCUS, "No MRU window to focus found; focusing no_focus_window.\n"); + meta_display_focus_the_no_focus_window (workspace->screen->display, + workspace->screen, + timestamp); + } +} diff --git a/src/core/workspace.h b/src/core/workspace.h new file mode 100644 index 00000000..f4079ebb --- /dev/null +++ b/src/core/workspace.h @@ -0,0 +1,113 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file workspace.h Workspaces + * + * A workspace is a set of windows which all live on the same + * screen. (You may also see the name "desktop" around the place, + * which is the EWMH's name for the same thing.) Only one workspace + * of a screen may be active at once; all windows on all other workspaces + * are unmapped. + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2004, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_WORKSPACE_H +#define META_WORKSPACE_H + +#include "window-private.h" + +/* Negative to avoid conflicting with real workspace + * numbers + */ +typedef enum +{ + META_MOTION_UP = -1, + META_MOTION_DOWN = -2, + META_MOTION_LEFT = -3, + META_MOTION_RIGHT = -4 +} MetaMotionDirection; + +struct _MetaWorkspace +{ + MetaScreen *screen; + + GList *windows; + GList *mru_list; + + GList *list_containing_self; + + MetaRectangle work_area_screen; + MetaRectangle *work_area_xinerama; + GList *screen_region; + GList **xinerama_region; + GList *screen_edges; + GList *xinerama_edges; + GSList *all_struts; + guint work_areas_invalid : 1; + + guint showing_desktop : 1; +}; + +MetaWorkspace* meta_workspace_new (MetaScreen *screen); +void meta_workspace_free (MetaWorkspace *workspace); +void meta_workspace_add_window (MetaWorkspace *workspace, + MetaWindow *window); +void meta_workspace_remove_window (MetaWorkspace *workspace, + MetaWindow *window); +void meta_workspace_relocate_windows (MetaWorkspace *workspace, + MetaWorkspace *new_home); +void meta_workspace_activate_with_focus (MetaWorkspace *workspace, + MetaWindow *focus_this, + guint32 timestamp); +void meta_workspace_activate (MetaWorkspace *workspace, + guint32 timestamp); +int meta_workspace_index (MetaWorkspace *workspace); +GList* meta_workspace_list_windows (MetaWorkspace *workspace); + +void meta_workspace_invalidate_work_area (MetaWorkspace *workspace); + + +void meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace, + int which_xinerama, + MetaRectangle *area); +void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace, + MetaRectangle *area); +GList* meta_workspace_get_onscreen_region (MetaWorkspace *workspace); +GList* meta_workspace_get_onxinerama_region (MetaWorkspace *workspace, + int which_xinerama); +void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace, + MetaRectangle *area); + +void meta_workspace_focus_default_window (MetaWorkspace *workspace, + MetaWindow *not_this_one, + guint32 timestamp); + +MetaWorkspace* meta_workspace_get_neighbor (MetaWorkspace *workspace, + MetaMotionDirection direction); + +const char* meta_workspace_get_name (MetaWorkspace *workspace); + +#endif + + + + diff --git a/src/core/xprops.c b/src/core/xprops.c new file mode 100644 index 00000000..71e9eeae --- /dev/null +++ b/src/core/xprops.c @@ -0,0 +1,1238 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco X property convenience routines */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 Red Hat Inc. + * + * Some trivial property-unpacking code from Xlib: + * Copyright 1987, 1988, 1998 The Open Group + * Copyright 1988 by Wyse Technology, Inc., San Jose, Ca, + * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts, + * + * 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. + */ + +/*********************************************************** +Copyright 1988 by Wyse Technology, Inc., San Jose, Ca, +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts, + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL AND WYSE DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +EVENT SHALL DIGITAL OR WYSE BE LIABLE FOR ANY SPECIAL, INDIRECT OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF +USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +******************************************************************/ + +/* + +Copyright 1987, 1988, 1998 The Open Group + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of The Open Group shall +not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization +from The Open Group. + +*/ + + +#include <config.h> +#include "xprops.h" +#include "errors.h" +#include "util.h" +#include "async-getprop.h" +#include "ui.h" +#include "marco-Xatomtype.h" +#include <X11/Xatom.h> +#include <string.h> +#include "window-private.h" + +typedef struct +{ + MetaDisplay *display; + Window xwindow; + Atom xatom; + Atom type; + int format; + unsigned long n_items; + unsigned long bytes_after; + unsigned char *prop; +} GetPropertyResults; + +static gboolean +validate_or_free_results (GetPropertyResults *results, + int expected_format, + Atom expected_type, + gboolean must_have_items) +{ + char *type_name; + char *expected_name; + char *prop_name; + const char *title; + const char *res_class; + const char *res_name; + MetaWindow *w; + + if (expected_format == results->format && + expected_type == results->type && + (!must_have_items || results->n_items > 0)) + return TRUE; + + meta_error_trap_push (results->display); + type_name = XGetAtomName (results->display->xdisplay, results->type); + expected_name = XGetAtomName (results->display->xdisplay, expected_type); + prop_name = XGetAtomName (results->display->xdisplay, results->xatom); + meta_error_trap_pop (results->display, TRUE); + + w = meta_display_lookup_x_window (results->display, results->xwindow); + + if (w != NULL) + { + title = w->title; + res_class = w->res_class; + res_name = w->res_name; + } + else + { + title = NULL; + res_class = NULL; + res_name = NULL; + } + + if (title == NULL) + title = "unknown"; + + if (res_class == NULL) + res_class = "unknown"; + + if (res_name == NULL) + res_name = "unknown"; + + meta_warning (_("Window 0x%lx has property %s\nthat was expected to have type %s format %d\nand actually has type %s format %d n_items %d.\nThis is most likely an application bug, not a window manager bug.\nThe window has title=\"%s\" class=\"%s\" name=\"%s\"\n"), + results->xwindow, + prop_name ? prop_name : "(bad atom)", + expected_name ? expected_name : "(bad atom)", + expected_format, + type_name ? type_name : "(bad atom)", + results->format, (int) results->n_items, + title, res_class, res_name); + + if (type_name) + XFree (type_name); + if (expected_name) + XFree (expected_name); + if (prop_name) + XFree (prop_name); + + if (results->prop) + { + XFree (results->prop); + results->prop = NULL; + } + + return FALSE; +} + +static gboolean +get_property (MetaDisplay *display, + Window xwindow, + Atom xatom, + Atom req_type, + GetPropertyResults *results) +{ + results->display = display; + results->xwindow = xwindow; + results->xatom = xatom; + results->prop = NULL; + results->n_items = 0; + results->type = None; + results->bytes_after = 0; + results->format = 0; + + meta_error_trap_push_with_return (display); + if (XGetWindowProperty (display->xdisplay, xwindow, xatom, + 0, G_MAXLONG, + False, req_type, &results->type, &results->format, + &results->n_items, + &results->bytes_after, + &results->prop) != Success || + results->type == None) + { + if (results->prop) + XFree (results->prop); + meta_error_trap_pop_with_return (display, TRUE); + return FALSE; + } + + if (meta_error_trap_pop_with_return (display, TRUE) != Success) + { + if (results->prop) + XFree (results->prop); + return FALSE; + } + + return TRUE; +} + +static gboolean +atom_list_from_results (GetPropertyResults *results, + Atom **atoms_p, + int *n_atoms_p) +{ + if (!validate_or_free_results (results, 32, XA_ATOM, FALSE)) + return FALSE; + + *atoms_p = (Atom*) results->prop; + *n_atoms_p = results->n_items; + results->prop = NULL; + + return TRUE; +} + +gboolean +meta_prop_get_atom_list (MetaDisplay *display, + Window xwindow, + Atom xatom, + Atom **atoms_p, + int *n_atoms_p) +{ + GetPropertyResults results; + + *atoms_p = NULL; + *n_atoms_p = 0; + + if (!get_property (display, xwindow, xatom, XA_ATOM, + &results)) + return FALSE; + + return atom_list_from_results (&results, atoms_p, n_atoms_p); +} + +static gboolean +cardinal_list_from_results (GetPropertyResults *results, + gulong **cardinals_p, + int *n_cardinals_p) +{ + if (!validate_or_free_results (results, 32, XA_CARDINAL, FALSE)) + return FALSE; + + *cardinals_p = (gulong*) results->prop; + *n_cardinals_p = results->n_items; + results->prop = NULL; + + return TRUE; +} + +gboolean +meta_prop_get_cardinal_list (MetaDisplay *display, + Window xwindow, + Atom xatom, + gulong **cardinals_p, + int *n_cardinals_p) +{ + GetPropertyResults results; + + *cardinals_p = NULL; + *n_cardinals_p = 0; + + if (!get_property (display, xwindow, xatom, XA_CARDINAL, + &results)) + return FALSE; + + return cardinal_list_from_results (&results, cardinals_p, n_cardinals_p); +} + +static gboolean +motif_hints_from_results (GetPropertyResults *results, + MotifWmHints **hints_p) +{ + int real_size, max_size; +#define MAX_ITEMS sizeof (MotifWmHints)/sizeof (gulong) + + *hints_p = NULL; + + if (results->type == None || results->n_items <= 0) + { + meta_verbose ("Motif hints had unexpected type or n_items\n"); + if (results->prop) + { + XFree (results->prop); + results->prop = NULL; + } + return FALSE; + } + + /* The issue here is that some old crufty code will set a smaller + * MotifWmHints than the one we expect, apparently. I'm not sure of + * the history behind it. See bug #89841 for example. + */ + *hints_p = ag_Xmalloc (sizeof (MotifWmHints)); + if (*hints_p == NULL) + { + if (results->prop) + { + XFree (results->prop); + results->prop = NULL; + } + return FALSE; + } + real_size = results->n_items * sizeof (gulong); + max_size = MAX_ITEMS * sizeof (gulong); + memcpy (*hints_p, results->prop, MIN (real_size, max_size)); + + if (results->prop) + { + XFree (results->prop); + results->prop = NULL; + } + + return TRUE; +} + +gboolean +meta_prop_get_motif_hints (MetaDisplay *display, + Window xwindow, + Atom xatom, + MotifWmHints **hints_p) +{ + GetPropertyResults results; + + *hints_p = NULL; + + if (!get_property (display, xwindow, xatom, AnyPropertyType, + &results)) + return FALSE; + + return motif_hints_from_results (&results, hints_p); +} + +static gboolean +latin1_string_from_results (GetPropertyResults *results, + char **str_p) +{ + *str_p = NULL; + + if (!validate_or_free_results (results, 8, XA_STRING, FALSE)) + return FALSE; + + *str_p = (char*) results->prop; + results->prop = NULL; + + return TRUE; +} + +gboolean +meta_prop_get_latin1_string (MetaDisplay *display, + Window xwindow, + Atom xatom, + char **str_p) +{ + GetPropertyResults results; + + *str_p = NULL; + + if (!get_property (display, xwindow, xatom, XA_STRING, + &results)) + return FALSE; + + return latin1_string_from_results (&results, str_p); +} + +static gboolean +utf8_string_from_results (GetPropertyResults *results, + char **str_p) +{ + *str_p = NULL; + + if (!validate_or_free_results (results, 8, + results->display->atom_UTF8_STRING, FALSE)) + return FALSE; + + if (results->n_items > 0 && + !g_utf8_validate ((gchar *)results->prop, results->n_items, NULL)) + { + char *name; + + name = XGetAtomName (results->display->xdisplay, results->xatom); + meta_warning (_("Property %s on window 0x%lx contained invalid UTF-8\n"), + name, results->xwindow); + meta_XFree (name); + XFree (results->prop); + results->prop = NULL; + + return FALSE; + } + + *str_p = (char*) results->prop; + results->prop = NULL; + + return TRUE; +} + +gboolean +meta_prop_get_utf8_string (MetaDisplay *display, + Window xwindow, + Atom xatom, + char **str_p) +{ + GetPropertyResults results; + + *str_p = NULL; + + if (!get_property (display, xwindow, xatom, + display->atom_UTF8_STRING, + &results)) + return FALSE; + + return utf8_string_from_results (&results, str_p); +} + +/* this one freakishly returns g_malloc memory */ +static gboolean +utf8_list_from_results (GetPropertyResults *results, + char ***str_p, + int *n_str_p) +{ + int i; + int n_strings; + char **retval; + const char *p; + + *str_p = NULL; + *n_str_p = 0; + + if (!validate_or_free_results (results, 8, + results->display->atom_UTF8_STRING, FALSE)) + return FALSE; + + /* I'm not sure this is right, but I'm guessing the + * property is nul-separated + */ + i = 0; + n_strings = 0; + while (i < (int) results->n_items) + { + if (results->prop[i] == '\0') + ++n_strings; + ++i; + } + + if (results->prop[results->n_items - 1] != '\0') + ++n_strings; + + /* we're guaranteed that results->prop has a nul on the end + * by XGetWindowProperty + */ + + retval = g_new0 (char*, n_strings + 1); + + p = (char *)results->prop; + i = 0; + while (i < n_strings) + { + if (!g_utf8_validate (p, -1, NULL)) + { + char *name; + + meta_error_trap_push (results->display); + name = XGetAtomName (results->display->xdisplay, results->xatom); + meta_error_trap_pop (results->display, TRUE); + meta_warning (_("Property %s on window 0x%lx contained invalid UTF-8 for item %d in the list\n"), + name, results->xwindow, i); + meta_XFree (name); + meta_XFree (results->prop); + results->prop = NULL; + + g_strfreev (retval); + return FALSE; + } + + retval[i] = g_strdup (p); + + p = p + strlen (p) + 1; + ++i; + } + + *str_p = retval; + *n_str_p = i; + + meta_XFree (results->prop); + results->prop = NULL; + + return TRUE; +} + +/* returns g_malloc not Xmalloc memory */ +gboolean +meta_prop_get_utf8_list (MetaDisplay *display, + Window xwindow, + Atom xatom, + char ***str_p, + int *n_str_p) +{ + GetPropertyResults results; + + *str_p = NULL; + + if (!get_property (display, xwindow, xatom, + display->atom_UTF8_STRING, + &results)) + return FALSE; + + return utf8_list_from_results (&results, str_p, n_str_p); +} + +void +meta_prop_set_utf8_string_hint (MetaDisplay *display, + Window xwindow, + Atom atom, + const char *val) +{ + meta_error_trap_push (display); + XChangeProperty (display->xdisplay, + xwindow, atom, + display->atom_UTF8_STRING, + 8, PropModeReplace, (guchar*) val, strlen (val)); + meta_error_trap_pop (display, FALSE); +} + +static gboolean +window_from_results (GetPropertyResults *results, + Window *window_p) +{ + if (!validate_or_free_results (results, 32, XA_WINDOW, TRUE)) + return FALSE; + + *window_p = *(Window*) results->prop; + XFree (results->prop); + results->prop = NULL; + + return TRUE; +} + +#ifdef HAVE_XSYNC +static gboolean +counter_from_results (GetPropertyResults *results, + XSyncCounter *counter_p) +{ + if (!validate_or_free_results (results, 32, + XA_CARDINAL, + TRUE)) + return FALSE; + + *counter_p = *(XSyncCounter*) results->prop; + XFree (results->prop); + results->prop = NULL; + + return TRUE; +} +#endif + +gboolean +meta_prop_get_window (MetaDisplay *display, + Window xwindow, + Atom xatom, + Window *window_p) +{ + GetPropertyResults results; + + *window_p = None; + + if (!get_property (display, xwindow, xatom, XA_WINDOW, + &results)) + return FALSE; + + return window_from_results (&results, window_p); +} + +gboolean +meta_prop_get_cardinal (MetaDisplay *display, + Window xwindow, + Atom xatom, + gulong *cardinal_p) +{ + return meta_prop_get_cardinal_with_atom_type (display, xwindow, xatom, + XA_CARDINAL, cardinal_p); +} + +static gboolean +cardinal_with_atom_type_from_results (GetPropertyResults *results, + Atom prop_type, + gulong *cardinal_p) +{ + if (!validate_or_free_results (results, 32, prop_type, TRUE)) + return FALSE; + + *cardinal_p = *(gulong*) results->prop; + XFree (results->prop); + results->prop = NULL; + + return TRUE; +} + +gboolean +meta_prop_get_cardinal_with_atom_type (MetaDisplay *display, + Window xwindow, + Atom xatom, + Atom prop_type, + gulong *cardinal_p) +{ + GetPropertyResults results; + + *cardinal_p = 0; + + if (!get_property (display, xwindow, xatom, prop_type, + &results)) + return FALSE; + + return cardinal_with_atom_type_from_results (&results, prop_type, cardinal_p); +} + +static gboolean +text_property_from_results (GetPropertyResults *results, + char **utf8_str_p) +{ + XTextProperty tp; + + *utf8_str_p = NULL; + + tp.value = results->prop; + results->prop = NULL; + tp.encoding = results->type; + tp.format = results->format; + tp.nitems = results->n_items; + + *utf8_str_p = meta_text_property_to_utf8 (results->display->xdisplay, + &tp); + + if (tp.value != NULL) + XFree (tp.value); + + return *utf8_str_p != NULL; +} + +gboolean +meta_prop_get_text_property (MetaDisplay *display, + Window xwindow, + Atom xatom, + char **utf8_str_p) +{ + GetPropertyResults results; + + if (!get_property (display, xwindow, xatom, AnyPropertyType, + &results)) + return FALSE; + + return text_property_from_results (&results, utf8_str_p); +} + +/* From Xmd.h */ +#ifndef cvtINT32toInt +#if SIZEOF_VOID_P == 8 +#define cvtINT8toInt(val) ((((unsigned int)val) & 0x00000080) ? (((unsigned int)val) | 0xffffffffffffff00) : ((unsigned int)val)) +#define cvtINT16toInt(val) ((((unsigned int)val) & 0x00008000) ? (((unsigned int)val) | 0xffffffffffff0000) : ((unsigned int)val)) +#define cvtINT32toInt(val) ((((unsigned int)val) & 0x80000000) ? (((unsigned int)val) | 0xffffffff00000000) : ((unsigned int)val)) +#define cvtINT8toShort(val) cvtINT8toInt(val) +#define cvtINT16toShort(val) cvtINT16toInt(val) +#define cvtINT32toShort(val) cvtINT32toInt(val) +#define cvtINT8toLong(val) cvtINT8toInt(val) +#define cvtINT16toLong(val) cvtINT16toInt(val) +#define cvtINT32toLong(val) cvtINT32toInt(val) +#else +#define cvtINT8toInt(val) (val) +#define cvtINT16toInt(val) (val) +#define cvtINT32toInt(val) (val) +#define cvtINT8toShort(val) (val) +#define cvtINT16toShort(val) (val) +#define cvtINT32toShort(val) (val) +#define cvtINT8toLong(val) (val) +#define cvtINT16toLong(val) (val) +#define cvtINT32toLong(val) (val) +#endif /* SIZEOF_VOID_P == 8 */ +#endif /* cvtINT32toInt() */ + +static gboolean +wm_hints_from_results (GetPropertyResults *results, + XWMHints **hints_p) +{ + XWMHints *hints; + xPropWMHints *raw; + + *hints_p = NULL; + + if (!validate_or_free_results (results, 32, XA_WM_HINTS, TRUE)) + return FALSE; + + /* pre-R3 bogusly truncated window_group, don't fail on them */ + if (results->n_items < (NumPropWMHintsElements - 1)) + { + meta_verbose ("WM_HINTS property too short: %d should be %d\n", + (int) results->n_items, NumPropWMHintsElements - 1); + if (results->prop) + { + XFree (results->prop); + results->prop = NULL; + } + return FALSE; + } + + hints = ag_Xmalloc0 (sizeof (XWMHints)); + + raw = (xPropWMHints*) results->prop; + + hints->flags = raw->flags; + hints->input = (raw->input ? True : False); + hints->initial_state = cvtINT32toInt (raw->initialState); + hints->icon_pixmap = raw->iconPixmap; + hints->icon_window = raw->iconWindow; + hints->icon_x = cvtINT32toInt (raw->iconX); + hints->icon_y = cvtINT32toInt (raw->iconY); + hints->icon_mask = raw->iconMask; + if (results->n_items >= NumPropWMHintsElements) + hints->window_group = raw->windowGroup; + else + hints->window_group = 0; + + if (results->prop) + { + XFree (results->prop); + results->prop = NULL; + } + + *hints_p = hints; + + return TRUE; +} + +gboolean +meta_prop_get_wm_hints (MetaDisplay *display, + Window xwindow, + Atom xatom, + XWMHints **hints_p) +{ + GetPropertyResults results; + + *hints_p = NULL; + + if (!get_property (display, xwindow, xatom, XA_WM_HINTS, + &results)) + return FALSE; + + return wm_hints_from_results (&results, hints_p); +} + +static gboolean +class_hint_from_results (GetPropertyResults *results, + XClassHint *class_hint) +{ + int len_name, len_class; + + class_hint->res_class = NULL; + class_hint->res_name = NULL; + + if (!validate_or_free_results (results, 8, XA_STRING, FALSE)) + return FALSE; + + len_name = strlen ((char *) results->prop); + if (! (class_hint->res_name = ag_Xmalloc (len_name+1))) + { + XFree (results->prop); + results->prop = NULL; + return FALSE; + } + + strcpy (class_hint->res_name, (char *)results->prop); + + if (len_name == (int) results->n_items) + len_name--; + + len_class = strlen ((char *)results->prop + len_name + 1); + + if (! (class_hint->res_class = ag_Xmalloc(len_class+1))) + { + XFree(class_hint->res_name); + class_hint->res_name = NULL; + XFree (results->prop); + results->prop = NULL; + return FALSE; + } + + strcpy (class_hint->res_class, (char *)results->prop + len_name + 1); + + XFree (results->prop); + results->prop = NULL; + + return TRUE; +} + +gboolean +meta_prop_get_class_hint (MetaDisplay *display, + Window xwindow, + Atom xatom, + XClassHint *class_hint) +{ + GetPropertyResults results; + + class_hint->res_class = NULL; + class_hint->res_name = NULL; + + if (!get_property (display, xwindow, xatom, XA_STRING, + &results)) + return FALSE; + + return class_hint_from_results (&results, class_hint); +} + +static gboolean +size_hints_from_results (GetPropertyResults *results, + XSizeHints **hints_p, + gulong *flags_p) +{ + xPropSizeHints *raw; + XSizeHints *hints; + + *hints_p = NULL; + *flags_p = 0; + + if (!validate_or_free_results (results, 32, XA_WM_SIZE_HINTS, FALSE)) + return FALSE; + + if (results->n_items < OldNumPropSizeElements) + return FALSE; + + raw = (xPropSizeHints*) results->prop; + + hints = ag_Xmalloc (sizeof (XSizeHints)); + + /* XSizeHints misdeclares these as int instead of long */ + hints->flags = raw->flags; + hints->x = cvtINT32toInt (raw->x); + hints->y = cvtINT32toInt (raw->y); + hints->width = cvtINT32toInt (raw->width); + hints->height = cvtINT32toInt (raw->height); + hints->min_width = cvtINT32toInt (raw->minWidth); + hints->min_height = cvtINT32toInt (raw->minHeight); + hints->max_width = cvtINT32toInt (raw->maxWidth); + hints->max_height = cvtINT32toInt (raw->maxHeight); + hints->width_inc = cvtINT32toInt (raw->widthInc); + hints->height_inc = cvtINT32toInt (raw->heightInc); + hints->min_aspect.x = cvtINT32toInt (raw->minAspectX); + hints->min_aspect.y = cvtINT32toInt (raw->minAspectY); + hints->max_aspect.x = cvtINT32toInt (raw->maxAspectX); + hints->max_aspect.y = cvtINT32toInt (raw->maxAspectY); + + *flags_p = (USPosition | USSize | PAllHints); + if (results->n_items >= NumPropSizeElements) + { + hints->base_width= cvtINT32toInt (raw->baseWidth); + hints->base_height= cvtINT32toInt (raw->baseHeight); + hints->win_gravity= cvtINT32toInt (raw->winGravity); + *flags_p |= (PBaseSize | PWinGravity); + } + + hints->flags &= (*flags_p); /* get rid of unwanted bits */ + + XFree (results->prop); + results->prop = NULL; + + *hints_p = hints; + + return TRUE; +} + +gboolean +meta_prop_get_size_hints (MetaDisplay *display, + Window xwindow, + Atom xatom, + XSizeHints **hints_p, + gulong *flags_p) +{ + GetPropertyResults results; + + *hints_p = NULL; + *flags_p = 0; + + if (!get_property (display, xwindow, xatom, XA_WM_SIZE_HINTS, + &results)) + return FALSE; + + return size_hints_from_results (&results, hints_p, flags_p); +} + +static AgGetPropertyTask* +get_task (MetaDisplay *display, + Window xwindow, + Atom xatom, + Atom req_type) +{ + return ag_task_create (display->xdisplay, + xwindow, + xatom, 0, G_MAXLONG, + False, req_type); +} + +static char* +latin1_to_utf8 (const char *text) +{ + GString *str; + const char *p; + + str = g_string_new (""); + + p = text; + while (*p) + { + g_string_append_unichar (str, *p); + ++p; + } + + return g_string_free (str, FALSE); +} + +void +meta_prop_get_values (MetaDisplay *display, + Window xwindow, + MetaPropValue *values, + int n_values) +{ + int i; + AgGetPropertyTask **tasks; + + meta_verbose ("Requesting %d properties of 0x%lx at once\n", + n_values, xwindow); + + if (n_values == 0) + return; + + tasks = g_new0 (AgGetPropertyTask*, n_values); + + /* Start up tasks. The "values" array can have values + * with atom == None, which means to ignore that element. + */ + i = 0; + while (i < n_values) + { + if (values[i].required_type == None) + { + switch (values[i].type) + { + case META_PROP_VALUE_INVALID: + /* This means we don't really want a value, e.g. got + * property notify on an atom we don't care about. + */ + if (values[i].atom != None) + meta_bug ("META_PROP_VALUE_INVALID requested in %s\n", G_STRFUNC); + break; + case META_PROP_VALUE_UTF8_LIST: + case META_PROP_VALUE_UTF8: + values[i].required_type = display->atom_UTF8_STRING; + break; + case META_PROP_VALUE_STRING: + case META_PROP_VALUE_STRING_AS_UTF8: + values[i].required_type = XA_STRING; + break; + case META_PROP_VALUE_MOTIF_HINTS: + values[i].required_type = AnyPropertyType; + break; + case META_PROP_VALUE_CARDINAL_LIST: + case META_PROP_VALUE_CARDINAL: + values[i].required_type = XA_CARDINAL; + break; + case META_PROP_VALUE_WINDOW: + values[i].required_type = XA_WINDOW; + break; + case META_PROP_VALUE_ATOM_LIST: + values[i].required_type = XA_ATOM; + break; + case META_PROP_VALUE_TEXT_PROPERTY: + values[i].required_type = AnyPropertyType; + break; + case META_PROP_VALUE_WM_HINTS: + values[i].required_type = XA_WM_HINTS; + break; + case META_PROP_VALUE_CLASS_HINT: + values[i].required_type = XA_STRING; + break; + case META_PROP_VALUE_SIZE_HINTS: + values[i].required_type = XA_WM_SIZE_HINTS; + break; + case META_PROP_VALUE_SYNC_COUNTER: + values[i].required_type = XA_CARDINAL; + break; + } + } + + if (values[i].atom != None) + tasks[i] = get_task (display, xwindow, + values[i].atom, values[i].required_type); + + ++i; + } + + /* Get replies for all our tasks */ + meta_topic (META_DEBUG_SYNC, "Syncing to get %d GetProperty replies in %s\n", + n_values, G_STRFUNC); + XSync (display->xdisplay, False); + + /* Collect results, should arrive in order requested */ + i = 0; + while (i < n_values) + { + AgGetPropertyTask *task; + GetPropertyResults results; + + if (tasks[i] == NULL) + { + /* Probably values[i].type was None, or ag_task_create() + * returned NULL. + */ + values[i].type = META_PROP_VALUE_INVALID; + goto next; + } + + task = ag_get_next_completed_task (display->xdisplay); + g_assert (task != NULL); + g_assert (ag_task_have_reply (task)); + + results.display = display; + results.xwindow = xwindow; + results.xatom = values[i].atom; + results.prop = NULL; + results.n_items = 0; + results.type = None; + results.bytes_after = 0; + results.format = 0; + + if (ag_task_get_reply_and_free (task, + &results.type, &results.format, + &results.n_items, + &results.bytes_after, + &results.prop) != Success || + results.type == None) + { + values[i].type = META_PROP_VALUE_INVALID; + if (results.prop) + { + XFree (results.prop); + results.prop = NULL; + } + goto next; + } + + switch (values[i].type) + { + case META_PROP_VALUE_INVALID: + g_assert_not_reached (); + break; + case META_PROP_VALUE_UTF8_LIST: + if (!utf8_list_from_results (&results, + &values[i].v.string_list.strings, + &values[i].v.string_list.n_strings)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_UTF8: + if (!utf8_string_from_results (&results, + &values[i].v.str)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_STRING: + if (!latin1_string_from_results (&results, + &values[i].v.str)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_STRING_AS_UTF8: + if (!latin1_string_from_results (&results, + &values[i].v.str)) + values[i].type = META_PROP_VALUE_INVALID; + else + { + char *new_str; + char *xmalloc_new_str; + + new_str = latin1_to_utf8 (values[i].v.str); + xmalloc_new_str = ag_Xmalloc (strlen (new_str) + 1); + if (xmalloc_new_str != NULL) + { + strcpy (xmalloc_new_str, new_str); + meta_XFree (values[i].v.str); + values[i].v.str = xmalloc_new_str; + } + + g_free (new_str); + } + break; + case META_PROP_VALUE_MOTIF_HINTS: + if (!motif_hints_from_results (&results, + &values[i].v.motif_hints)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_CARDINAL_LIST: + if (!cardinal_list_from_results (&results, + &values[i].v.cardinal_list.cardinals, + &values[i].v.cardinal_list.n_cardinals)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_CARDINAL: + if (!cardinal_with_atom_type_from_results (&results, + values[i].required_type, + &values[i].v.cardinal)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_WINDOW: + if (!window_from_results (&results, + &values[i].v.xwindow)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_ATOM_LIST: + if (!atom_list_from_results (&results, + &values[i].v.atom_list.atoms, + &values[i].v.atom_list.n_atoms)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_TEXT_PROPERTY: + if (!text_property_from_results (&results, &values[i].v.str)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_WM_HINTS: + if (!wm_hints_from_results (&results, &values[i].v.wm_hints)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_CLASS_HINT: + if (!class_hint_from_results (&results, &values[i].v.class_hint)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_SIZE_HINTS: + if (!size_hints_from_results (&results, + &values[i].v.size_hints.hints, + &values[i].v.size_hints.flags)) + values[i].type = META_PROP_VALUE_INVALID; + break; + case META_PROP_VALUE_SYNC_COUNTER: +#ifdef HAVE_XSYNC + if (!counter_from_results (&results, + &values[i].v.xcounter)) + values[i].type = META_PROP_VALUE_INVALID; +#else + values[i].type = META_PROP_VALUE_INVALID; + if (results.prop) + { + XFree (results.prop); + results.prop = NULL; + } +#endif + break; + } + + next: + ++i; + } + + g_free (tasks); +} + +static void +free_value (MetaPropValue *value) +{ + switch (value->type) + { + case META_PROP_VALUE_INVALID: + break; + case META_PROP_VALUE_UTF8: + case META_PROP_VALUE_STRING: + case META_PROP_VALUE_STRING_AS_UTF8: + meta_XFree (value->v.str); + break; + case META_PROP_VALUE_MOTIF_HINTS: + meta_XFree (value->v.motif_hints); + break; + case META_PROP_VALUE_CARDINAL: + break; + case META_PROP_VALUE_WINDOW: + break; + case META_PROP_VALUE_ATOM_LIST: + meta_XFree (value->v.atom_list.atoms); + break; + case META_PROP_VALUE_TEXT_PROPERTY: + meta_XFree (value->v.str); + break; + case META_PROP_VALUE_WM_HINTS: + meta_XFree (value->v.wm_hints); + break; + case META_PROP_VALUE_CLASS_HINT: + meta_XFree (value->v.class_hint.res_class); + meta_XFree (value->v.class_hint.res_name); + break; + case META_PROP_VALUE_SIZE_HINTS: + meta_XFree (value->v.size_hints.hints); + break; + case META_PROP_VALUE_UTF8_LIST: + g_strfreev (value->v.string_list.strings); + break; + case META_PROP_VALUE_CARDINAL_LIST: + meta_XFree (value->v.cardinal_list.cardinals); + break; + case META_PROP_VALUE_SYNC_COUNTER: + break; + } +} + +void +meta_prop_free_values (MetaPropValue *values, + int n_values) +{ + int i; + + i = 0; + while (i < n_values) + { + free_value (&values[i]); + ++i; + } + + /* Zero the whole thing to quickly detect breakage */ + memset (values, '\0', sizeof (MetaPropValue) * n_values); +} |