summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-12-01 23:52:01 -0300
committerPerberos <[email protected]>2011-12-01 23:52:01 -0300
commit28a029a4990d2a84f9d6a0b890eba812ea503998 (patch)
tree7a69477d0dd6bf351801fa9698d95224e4fe47b6 /src/core
downloadmarco-28a029a4990d2a84f9d6a0b890eba812ea503998.tar.bz2
marco-28a029a4990d2a84f9d6a0b890eba812ea503998.tar.xz
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'src/core')
-rw-r--r--src/core/async-getprop.c680
-rw-r--r--src/core/async-getprop.h67
-rw-r--r--src/core/atomnames.h166
-rw-r--r--src/core/bell.c397
-rw-r--r--src/core/bell.h108
-rw-r--r--src/core/boxes.c1926
-rw-r--r--src/core/constraints.c1382
-rw-r--r--src/core/constraints.h48
-rw-r--r--src/core/core.c779
-rw-r--r--src/core/delete.c266
-rw-r--r--src/core/display-private.h513
-rw-r--r--src/core/display.c5355
-rw-r--r--src/core/edge-resistance.c1277
-rw-r--r--src/core/edge-resistance.h48
-rw-r--r--src/core/effects.c735
-rw-r--r--src/core/effects.h170
-rw-r--r--src/core/errors.c288
-rw-r--r--src/core/eventqueue.c184
-rw-r--r--src/core/eventqueue.h40
-rw-r--r--src/core/frame-private.h88
-rw-r--r--src/core/frame.c421
-rw-r--r--src/core/group-private.h43
-rw-r--r--src/core/group-props.c234
-rw-r--r--src/core/group-props.h37
-rw-r--r--src/core/group.c274
-rw-r--r--src/core/group.h53
-rw-r--r--src/core/iconcache.c849
-rw-r--r--src/core/iconcache.h79
-rw-r--r--src/core/keybindings.c3352
-rw-r--r--src/core/keybindings.h60
-rw-r--r--src/core/main.c673
-rw-r--r--src/core/marco-Xatomtype.h136
-rw-r--r--src/core/place.c932
-rw-r--r--src/core/place.h37
-rw-r--r--src/core/prefs.c2794
-rw-r--r--src/core/schema-bindings.c195
-rw-r--r--src/core/screen-private.h226
-rw-r--r--src/core/screen.c2815
-rw-r--r--src/core/session.c1831
-rw-r--r--src/core/session.h91
-rw-r--r--src/core/stack.c1661
-rw-r--r--src/core/stack.h402
-rw-r--r--src/core/testasyncgetprop.c497
-rw-r--r--src/core/testboxes.c1416
-rw-r--r--src/core/util.c641
-rw-r--r--src/core/window-private.h640
-rw-r--r--src/core/window-props.c1553
-rw-r--r--src/core/window-props.h129
-rw-r--r--src/core/window.c8178
-rw-r--r--src/core/workspace.c1038
-rw-r--r--src/core/workspace.h113
-rw-r--r--src/core/xprops.c1238
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, &timestamp))
+ 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, &timestamp))
+ 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 (&current_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,
+ &current_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(&current->rect, &input->rect)) ||
+ (direction == META_SCREEN_LEFT &&
+ input->rect.x == current->rect.x + current->rect.width &&
+ meta_rectangle_vert_overlap(&current->rect, &input->rect)) ||
+ (direction == META_SCREEN_UP &&
+ input->rect.y == current->rect.y + current->rect.height &&
+ meta_rectangle_horiz_overlap(&current->rect, &input->rect)) ||
+ (direction == META_SCREEN_DOWN &&
+ current->rect.y == input->rect.y + input->rect.height &&
+ meta_rectangle_horiz_overlap(&current->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 &quot;save current setup&quot; "
+ "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 (&current_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 (&current_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 (&current_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 (&current_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 (&current_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 (&current_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 (&current_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);
+}