summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2007-10-21 17:54:45 +0200
committerLennart Poettering <lennart@poettering.net>2007-10-21 17:54:45 +0200
commit31f26c2915caa6320af74ba73ebf71491ab80fa3 (patch)
tree08ad71473267f9a7d3cf24de044c92d2fef0992c /src
parent02ff678b9ec7f36c9c18fa2c0e6b1f388d0c106e (diff)
Basic autoconfization
Diffstat (limited to 'src')
-rw-r--r--src/lassi-avahi.c255
-rw-r--r--src/lassi-avahi.h31
-rw-r--r--src/lassi-clipboard.c174
-rw-r--r--src/lassi-clipboard.h24
-rw-r--r--src/lassi-grab.c407
-rw-r--r--src/lassi-grab.h40
-rw-r--r--src/lassi-order.c147
-rw-r--r--src/lassi-order.h13
-rw-r--r--src/lassi-osd.c144
-rw-r--r--src/lassi-osd.h18
-rw-r--r--src/lassi-prefs.c212
-rw-r--r--src/lassi-prefs.h29
-rw-r--r--src/lassi-server.c1554
-rw-r--r--src/lassi-server.h84
-rw-r--r--src/lassi-tray.c100
-rw-r--r--src/lassi-tray.h32
-rw-r--r--src/mango-lassi.glade300
-rwxr-xr-xsrc/x16
-rw-r--r--src/xinput.c102
19 files changed, 3682 insertions, 0 deletions
diff --git a/src/lassi-avahi.c b/src/lassi-avahi.c
new file mode 100644
index 0000000..258c199
--- /dev/null
+++ b/src/lassi-avahi.c
@@ -0,0 +1,255 @@
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <avahi-common/error.h>
+#include <avahi-common/alternative.h>
+#include <avahi-glib/glib-malloc.h>
+
+#include "lassi-avahi.h"
+
+/* FIXME: Error and collision handling is suboptimal */
+
+static void resolve_cb(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ const char *host_name,
+ const AvahiAddress *address,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void* userdata) {
+
+ LassiAvahiInfo *i = userdata;
+
+ g_assert(r);
+ g_assert(i);
+
+ /* Called whenever a service has been resolved successfully or timed out */
+
+ switch (event) {
+ case AVAHI_RESOLVER_FOUND: {
+ char a[AVAHI_ADDRESS_STR_MAX], *t;
+
+ avahi_address_snprint(a, sizeof(a), address);
+ t = g_strdup_printf("tcp:port=%u,host=%s", port, a);
+ lassi_server_connect(i->server, t);
+ g_free(t);
+ break;
+ }
+
+ case AVAHI_RESOLVER_FAILURE:
+ g_message("Failed to resolve service '%s' of type '%s' in domain '%s': %s", name, type, domain, avahi_strerror(avahi_client_errno(i->client)));
+ break;
+
+ }
+
+ avahi_service_resolver_free(r);
+ }
+
+static void browse_cb(
+ AvahiServiceBrowser *b,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ AvahiLookupResultFlags flags,
+ void* userdata) {
+
+ LassiAvahiInfo *i = userdata;
+
+ g_assert(b);
+ g_assert(i);
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW:
+
+ if (!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN) &&
+ !lassi_server_is_connected(i->server, name) &&
+ lassi_server_is_known(i->server, name))
+
+ if (!(avahi_service_resolver_new(i->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_cb, i)))
+ g_message("Failed to resolve service '%s': %s", name, avahi_strerror(avahi_client_errno(i->client)));
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ break;
+
+ case AVAHI_BROWSER_FAILURE:
+ g_message("Browsing failed: %s", avahi_strerror(avahi_client_errno(i->client)));
+ gtk_main_quit();
+ break;
+ }
+}
+
+
+static void create_service(LassiAvahiInfo *i);
+
+static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+ LassiAvahiInfo *i = userdata;
+
+ g_assert(g);
+ g_assert(i);
+
+ i->group = g;
+
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED :
+ g_message("Service '%s' successfully established.", i->service_name);
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION : {
+ char *n;
+
+ n = avahi_alternative_service_name(i->service_name);
+ avahi_free(i->service_name);
+ i->service_name = n;
+
+ g_message("Service name collision, renaming service to '%s'", n);
+
+ /* And recreate the services */
+ create_service(i);
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_FAILURE :
+ g_message("Entry group failure: %s", avahi_strerror(avahi_client_errno(i->client)));
+ gtk_main_quit();
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ ;
+ }
+}
+
+static void create_service(LassiAvahiInfo *i) {
+ g_assert(i);
+
+ if (!i->group)
+ if (!(i->group = avahi_entry_group_new(i->client, entry_group_callback, i))) {
+ g_message("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(i->client)));
+ gtk_main_quit();
+ return;
+ }
+
+ if (avahi_entry_group_is_empty(i->group)) {
+ int ret;
+
+ for (;;) {
+
+ if (!i->service_name)
+ i->service_name = g_strdup(i->server->id);
+
+ if ((ret = avahi_entry_group_add_service(i->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, i->service_name, LASSI_SERVICE_TYPE, NULL, NULL, i->server->port, NULL)) < 0) {
+
+ if (ret == AVAHI_ERR_COLLISION) {
+ char *n = avahi_alternative_service_name(i->service_name);
+ avahi_free(i->service_name);
+ i->service_name = n;
+ continue;
+ }
+
+ g_message("Failed to add service: %s", avahi_strerror(ret));
+ gtk_main_quit();
+ return;
+ }
+
+ break;
+ }
+
+ if (strcmp(i->service_name, i->server->id)) {
+ g_free(i->server->id);
+ i->server->id = g_strdup(i->service_name);
+ }
+
+ if ((ret = avahi_entry_group_commit(i->group)) < 0) {
+ g_message("Failed to commit entry group: %s", avahi_strerror(ret));
+ gtk_main_quit();
+ return;
+ }
+ }
+}
+
+static void client_cb(AvahiClient *client, AvahiClientState state, void *userdata) {
+ LassiAvahiInfo *i = userdata;
+
+ i->client = client;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ if (!i->group)
+ create_service(i);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ g_message("Client failure: %s", avahi_strerror(avahi_client_errno(client)));
+ gtk_main_quit();
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ case AVAHI_CLIENT_S_REGISTERING:
+ if (i->group)
+ avahi_entry_group_reset(i->group);
+
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ ;
+ }
+}
+
+int lassi_avahi_init(LassiAvahiInfo *i, LassiServer *server) {
+ int error;
+
+ g_assert(i);
+ g_assert(server);
+
+ memset(i, 0, sizeof(*i));
+ i->server = server;
+
+ avahi_set_allocator(avahi_glib_allocator());
+
+ if (!(i->poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT))) {
+ g_message("avahi_glib_poll_new() failed.");
+ goto fail;
+ }
+
+ if (!(i->client = avahi_client_new(avahi_glib_poll_get(i->poll), 0, client_cb, i, &error))) {
+ g_message("avahi_client_new() failed: %s", avahi_strerror(error));
+ goto fail;
+ }
+
+ if (!(i->browser = avahi_service_browser_new(i->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, LASSI_SERVICE_TYPE, NULL, 0, browse_cb, i))) {
+ g_message("avahi_service_browser_new(): %s", avahi_strerror(avahi_client_errno(i->client)));
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ lassi_avahi_done(i);
+ return -1;
+}
+
+void lassi_avahi_done(LassiAvahiInfo *i) {
+ g_assert(i);
+
+ if (i->client)
+ avahi_client_free(i->client);
+
+ if (i->poll)
+ avahi_glib_poll_free(i->poll);
+
+ g_free(i->service_name);
+
+ memset(i, 0, sizeof(*i));
+}
diff --git a/src/lassi-avahi.h b/src/lassi-avahi.h
new file mode 100644
index 0000000..04b634c
--- /dev/null
+++ b/src/lassi-avahi.h
@@ -0,0 +1,31 @@
+#ifndef foolassiavahihfoo
+#define foolassiavahihfoo
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-client/lookup.h>
+#include <avahi-glib/glib-watch.h>
+
+typedef struct LassiAvahiInfo LassiAvahiInfo;
+struct LassiServer;
+
+struct LassiAvahiInfo {
+ struct LassiServer *server;
+
+ AvahiGLibPoll *poll;
+ AvahiClient *client;
+
+ AvahiEntryGroup *group;
+ char *service_name;
+
+ AvahiServiceBrowser *browser;
+};
+
+#include "lassi-server.h"
+
+int lassi_avahi_init(LassiAvahiInfo *i, LassiServer *server);
+void lassi_avahi_done(LassiAvahiInfo *i);
+
+#define LASSI_SERVICE_TYPE "_mango-lassi._tcp"
+
+#endif
diff --git a/src/lassi-clipboard.c b/src/lassi-clipboard.c
new file mode 100644
index 0000000..bdfeff4
--- /dev/null
+++ b/src/lassi-clipboard.c
@@ -0,0 +1,174 @@
+#include <string.h>
+#include <gtk/gtk.h>
+
+#include "lassi-server.h"
+
+#define LASSI_MARKER "application/x-mango-lassi-marker"
+
+static void targets_received(GtkClipboard *clipboard, GdkAtom *atoms, int n_atoms, gpointer userdata) {
+ int j, k;
+ LassiClipboardInfo *i = userdata;
+ char **targets;
+
+ g_assert(clipboard);
+ g_assert(i);
+
+ g_debug("recvd targs %p, %i", atoms, n_atoms);
+
+ if (!atoms)
+ return;
+
+ targets = g_new0(char*, n_atoms+1);
+
+ for (j = 0, k = 0; j < n_atoms; j++) {
+ char *c = gdk_atom_name(atoms[j]);
+
+ /* Avoid loops */
+ if (strcmp(c, LASSI_MARKER) == 0) {
+ g_free(c);
+ goto fail;
+ }
+
+ if (strcmp(c, "TIMESTAMP") == 0 ||
+ strcmp(c, "TARGETS") == 0 ||
+ strcmp(c, "CLIPBOARD_MANAGER") == 0 ||
+ strcmp(c, "CLIENT_WINDOW") == 0 ||
+ strcmp(c, "DELETE") == 0 ||
+ strcmp(c, "INSERT_PROPERTY") == 0 ||
+ strcmp(c, "INSERT_SELECTION") == 0 ||
+ strcmp(c, "LENGTH") == 0 ||
+ strcmp(c, "TASK") == 0 ||
+ strcmp(c, "MULTIPLE") == 0 ||
+ strcmp(c, "DRAWABLE") == 0) {
+ g_free(c);
+ continue;
+ }
+
+ targets[k++] = c;
+ }
+
+ g_debug("%p %i", targets, n_atoms);
+ lassi_server_acquire_clipboard(i->server, clipboard == i->primary, targets);
+
+fail:
+ g_strfreev(targets);
+}
+
+static void owner_change(GtkClipboard *clipboard, GdkEventOwnerChange *event, gpointer userdata) {
+ LassiClipboardInfo *i = userdata;
+
+ g_assert(clipboard);
+ g_assert(i);
+
+ g_debug("owner change");
+
+ if (event->reason == GDK_OWNER_CHANGE_NEW_OWNER)
+ gtk_clipboard_request_targets(clipboard, targets_received, i);
+ else
+ lassi_server_return_clipboard(i->server, clipboard == i->primary);
+}
+
+static void get_func(GtkClipboard *clipboard, GtkSelectionData *sd, guint info, gpointer userdata) {
+ LassiClipboardInfo *i = userdata;
+ char *t;
+ int f = 0;
+ gpointer d = NULL;
+ gint l = 0;
+
+ g_assert(clipboard);
+ g_assert(i);
+
+ t = gdk_atom_name(sd->target);
+
+ g_debug("get(%s)", t);
+
+ if (lassi_server_get_clipboard(i->server, clipboard == i->primary, t, &f, &d, &l) >= 0) {
+ g_debug("successfully got data");
+ gtk_selection_data_set(sd, sd->target, f, d, l);
+ } else
+ g_debug("failed to get data");
+
+ g_free(d);
+ g_free(t);
+}
+
+static void clear_func(GtkClipboard *clipboard, gpointer userdata) {
+ LassiClipboardInfo *i = userdata;
+
+ g_assert(clipboard);
+ g_assert(i);
+
+ g_debug("clear");
+
+ gtk_clipboard_request_targets(clipboard, targets_received, i);
+}
+
+int lassi_clipboard_init(LassiClipboardInfo *i, LassiServer *s) {
+ g_assert(i);
+ g_assert(s);
+
+ memset(i, 0, sizeof(*i));
+ i->server = s;
+
+ i->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ i->primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+
+ g_signal_connect(i->clipboard, "owner_change", G_CALLBACK(owner_change), i);
+ g_signal_connect(i->primary, "owner_change", G_CALLBACK(owner_change), i);
+ return 0;
+}
+
+void lassi_clipboard_done(LassiClipboardInfo *i) {
+ g_assert(i);
+
+ memset(i, 0, sizeof(*i));
+}
+
+void lassi_clipboard_set(LassiClipboardInfo *i, gboolean primary, char *targets[]) {
+ int n = 0, j;
+ gboolean b;
+ char **t;
+ GtkTargetEntry *e;
+
+ for (t = targets; *t; t++)
+ n++;
+
+ e = g_new0(GtkTargetEntry, n+1);
+
+ for (t = targets, j = 0; *t; t++, j++) {
+ e[j].target = *t;
+ e[j].info = j;
+ }
+
+ e[j].target = LASSI_MARKER;
+ e[j].info = j;
+
+ g_debug("setting %i targets", n+1);
+
+ b = gtk_clipboard_set_with_data(primary ? i->primary : i->clipboard, e, n+1, get_func, clear_func, i);
+ g_assert(b);
+}
+
+void lassi_clipboard_clear(LassiClipboardInfo *i, gboolean primary) {
+ g_assert(i);
+
+ gtk_clipboard_clear(primary ? i->primary : i->clipboard);
+}
+
+int lassi_clipboard_get(LassiClipboardInfo *i, gboolean primary, const char *target, int *f, gpointer *p, int *l) {
+ GtkSelectionData*sd;
+ g_assert(i);
+
+ if (!(sd = gtk_clipboard_wait_for_contents(primary ? i->primary : i->clipboard, gdk_atom_intern(target, TRUE))))
+ return -1;
+
+ g_assert(sd->length > 0);
+
+ *f = sd->format;
+ *p = g_memdup(sd->data, sd->length);
+ *l = sd->length;
+
+ gtk_selection_data_free(sd);
+
+ return 0;
+}
diff --git a/src/lassi-clipboard.h b/src/lassi-clipboard.h
new file mode 100644
index 0000000..f5574e3
--- /dev/null
+++ b/src/lassi-clipboard.h
@@ -0,0 +1,24 @@
+#ifndef foolassiclipboardhfoo
+#define foolassiclipboardhfoo
+
+#include <gtk/gtk.h>
+
+typedef struct LassiClipboardInfo LassiClipboardInfo;
+struct LassiServer;
+
+struct LassiClipboardInfo {
+ struct LassiServer *server;
+
+ GtkClipboard *clipboard, *primary;
+};
+
+#include "lassi-server.h"
+
+int lassi_clipboard_init(LassiClipboardInfo *i, LassiServer *server);
+void lassi_clipboard_done(LassiClipboardInfo *i);
+
+void lassi_clipboard_set(LassiClipboardInfo *i, gboolean primary, char *targets[]);
+void lassi_clipboard_clear(LassiClipboardInfo *i, gboolean primary);
+int lassi_clipboard_get(LassiClipboardInfo *i, gboolean primary, const char *target, int *format, gpointer *p, int *l);
+
+#endif
diff --git a/src/lassi-grab.c b/src/lassi-grab.c
new file mode 100644
index 0000000..e2bda77
--- /dev/null
+++ b/src/lassi-grab.c
@@ -0,0 +1,407 @@
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XTest.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include "lassi-server.h"
+#include "lassi-grab.h"
+
+#define TRIGGER_WIDTH 1
+
+static int local2global(LassiGrabInfo *i, int y) {
+ g_assert(i);
+ g_assert(y >= 0 && y <= gdk_screen_get_height(i->screen)-1);
+
+ /* Convert local screen coordinates (0 .. height) into global ones (0 . 65535) */
+ return (y * 0xFFFF) / (gdk_screen_get_height(i->screen)-1);
+}
+
+static int global2local(LassiGrabInfo *i, int y) {
+ g_assert(i);
+ g_assert(y >= 0 && y <= 0xFFFF);
+
+ /* Convert global screen coordinates (0 . 65535) into local ones (0 .. height) */
+ return (y * (gdk_screen_get_height(i->screen)-1)) / 0xFFFF;
+}
+
+static void move_pointer(LassiGrabInfo *i, int x, int y) {
+ g_assert(i);
+
+ /* Move the pointer ... */
+ gdk_display_warp_pointer(i->display, i->screen, x, y);
+
+ i->last_x = x;
+ i->last_y = y;
+}
+
+static void drop_motion_events(LassiGrabInfo *i) {
+ XEvent txe;
+
+ g_assert(i);
+
+ /* Drop all queued motion events */
+ while (XCheckTypedEvent(GDK_DISPLAY_XDISPLAY(i->display), MotionNotify, &txe))
+ ;
+}
+
+static int grab_input(LassiGrabInfo *i, GdkWindow *w) {
+ g_assert(i);
+ g_assert(w);
+
+ if (gdk_pointer_grab(w, TRUE,
+ GDK_POINTER_MOTION_MASK|
+ GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK,
+ NULL, i->empty_cursor, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) {
+ g_debug("pointer grab failed");
+ return -1;
+ }
+
+
+ if (gdk_keyboard_grab(w, TRUE, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) {
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+ g_debug("keyboard grab failed");
+ return -1;
+ }
+
+ XTestGrabControl(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ if (i->grab_window != w) {
+ /* Now, rebase the pointer, so that we can easily calculate
+ * relative movements */
+ move_pointer(i, i->base_x, i->base_y);
+
+ i->grab_window = w;
+
+ i->left_shift = i->right_shift = i->double_shift = FALSE;
+
+ g_debug("Input now grabbed");
+ }
+
+ return 0;
+}
+
+int lassi_grab_start(LassiGrabInfo *i, gboolean to_left) {
+ g_assert(i);
+
+ return grab_input(i, to_left ? i->left_window : i->right_window);
+}
+
+void lassi_grab_stop(LassiGrabInfo *i, int y) {
+ int x;
+
+ g_assert(i);
+
+ if (!i->grab_window)
+ return;
+
+ /* Move the pointer back into our screen */
+ if (y >= 0 && y < 0xFFFF) {
+
+ /* We received a valid y coordinate, so let's use it */
+ y = global2local(i, y);
+
+ if (i->grab_window == i->left_window)
+ x = TRIGGER_WIDTH;
+ else
+ x = gdk_screen_get_width(i->screen)-TRIGGER_WIDTH-1;
+
+ } else {
+
+ /* We received an invlid y coordinate, so let's center the
+ * pointer */
+ x = i->base_x;
+ y = i->base_y;
+ }
+
+ move_pointer(i, x, y);
+
+ gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+
+ drop_motion_events(i);
+
+ i->grab_window = NULL;
+
+ g_debug("Input now ungrabbed");
+
+ XTestGrabControl(GDK_DISPLAY_XDISPLAY(i->display), True);
+}
+
+static void handle_motion(LassiGrabInfo *i, int x, int y) {
+ int dx, dy;
+ int r;
+ int w, h;
+
+ dx = x - i->last_x;
+ dy = y - i->last_y;
+
+ i->last_x = x;
+ i->last_y = y;
+
+/* g_debug("rel motion %i %i", dx, dy); */
+
+ w = gdk_screen_get_width(i->screen);
+ h = gdk_screen_get_height(i->screen);
+
+ if (x <= w/10 || y <= h/10 ||
+ x >= (w*9)/10 || y >= (h*9)/10) {
+
+ XEvent txe;
+
+ /* Pointer is too near to the edges, move cursor
+ * back to center, so that further movements are
+ * not clipped */
+
+ g_debug("centering");
+
+ /* First, make sure there is no further motion event in the queue */
+ while (XCheckTypedEvent(GDK_DISPLAY_XDISPLAY(i->display), MotionNotify, &txe)) {
+ dx += txe.xmotion.x - i->last_x;
+ dy += txe.xmotion.y - i->last_y;
+
+ i->last_x = txe.xmotion.x;
+ i->last_y = txe.xmotion.y;
+ }
+
+ move_pointer(i, i->base_x, i->base_y);
+ }
+
+ /* Filter out non-existant or too large motions */
+ if ((dx != 0 || dy != 0) &&
+ ((abs(dx) <= (w*9)/20) && (abs(dy) <= (h*9)/20))) {
+
+/* g_debug("sending motion"); */
+
+ /* Send the event */
+ r = lassi_server_motion_event(i->server, dx, dy);
+ g_assert(r >= 0);
+ }
+}
+
+static GdkFilterReturn filter_func(GdkXEvent *gxe, GdkEvent *event, gpointer data) {
+ LassiGrabInfo *i = data;
+ XEvent *xe = (XEvent*) gxe;
+ GdkWindow *w = ((GdkEventAny*) event)->window;
+
+ g_assert(i);
+ g_assert(xe);
+ g_assert(event);
+
+ switch (xe->type){
+
+ case EnterNotify: {
+ XEnterWindowEvent *ewe = (XEnterWindowEvent*) xe;
+
+ if (ewe->mode == NotifyNormal && ewe->state == 0 && !i->grab_window) {
+/* g_debug("enter %u %u", ewe->x_root, ewe->y_root); */
+
+ /* Only honour this when no button/key is pressed */
+
+ if (lassi_server_change_grab(i->server, w == i->left_window, local2global(i, ewe->y_root)) >= 0)
+ grab_input(i, w);
+
+ } else if (i->grab_window)
+ handle_motion(i, ewe->x_root, ewe->y_root);
+
+ break;
+ }
+
+ case MotionNotify:
+
+ if (i->grab_window) {
+ XMotionEvent *me = (XMotionEvent*) xe;
+
+/* g_debug("motion %u %u", me->x_root, me->y_root); */
+ handle_motion(i, me->x_root, me->y_root);
+ }
+
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+
+ if (i->grab_window) {
+ int r;
+ XButtonEvent *be = (XButtonEvent*) xe;
+
+/* g_debug("button press/release"); */
+ handle_motion(i, be->x_root, be->y_root);
+
+ /* Send the event */
+ r = lassi_server_button_event(i->server, be->button, xe->type == ButtonPress);
+ g_assert(r >= 0);
+ }
+ break;
+
+ case KeyPress:
+ case KeyRelease:
+
+/* g_debug("raw key"); */
+
+ if (i->grab_window) {
+ int r;
+ XKeyEvent *ke = (XKeyEvent *) xe;
+ KeySym keysym;
+
+ keysym = XKeycodeToKeysym(GDK_DISPLAY_XDISPLAY(i->display), ke->keycode, 0);
+
+ if (keysym == XK_Shift_L)
+ i->left_shift = ke->type == KeyPress;
+ if (keysym == XK_Shift_R)
+ i->right_shift = xe->type == KeyPress;
+
+ if (i->left_shift && i->right_shift)
+ i->double_shift = TRUE;
+
+/* g_debug("left_shift=%i right_shift=%i 0x04%x", i->left_shift, i->right_shift, (unsigned) keysym); */
+
+/* g_debug("key press/release"); */
+ handle_motion(i, ke->x_root, ke->y_root);
+
+ /* Send the event */
+ r = lassi_server_key_event(i->server, keysym, xe->type == KeyPress);
+ g_assert(r >= 0);
+
+ if (!i->left_shift && !i->right_shift && i->double_shift) {
+/* g_debug("Got double shift"); */
+ lassi_server_acquire_grab(i->server);
+ lassi_grab_stop(i, -1);
+ }
+ }
+ break;
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+int lassi_grab_init(LassiGrabInfo *i, LassiServer *s) {
+ GdkWindowAttr wa;
+ GdkColor black = { 0, 0, 0, 0 };
+ const gchar cursor_data[1] = { 0 };
+ GdkBitmap *bitmap;
+ int xtest_event_base, xtest_error_base;
+ int major_version, minor_version;
+
+ memset(i, 0, sizeof(*i));
+ i->server = s;
+
+ i->screen = gdk_screen_get_default();
+ i->display = gdk_screen_get_display(i->screen);
+ i->root = gdk_screen_get_root_window(i->screen);
+
+ if (!XTestQueryExtension(GDK_DISPLAY_XDISPLAY(i->display), &xtest_event_base, &xtest_error_base, &major_version, &minor_version)) {
+ g_warning("XTest extension not supported.");
+ return -1;
+ }
+
+ g_debug("XTest %u.%u supported.", major_version, minor_version);
+
+ /* Create empty cursor */
+ bitmap = gdk_bitmap_create_from_data(NULL, cursor_data, 1, 1);
+ i->empty_cursor = gdk_cursor_new_from_pixmap(bitmap, bitmap, &black, &black, 0, 0);
+ gdk_pixmap_unref(bitmap);
+
+ /* Create trigger windows */
+ memset(&wa, 0, sizeof(wa));
+
+ wa.title = "Mango Lassi Left";
+ wa.event_mask = GDK_POINTER_MOTION_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_KEY_PRESS_MASK|GDK_KEY_RELEASE_MASK|GDK_ENTER_NOTIFY_MASK;
+ wa.x = 0;
+ wa.y = gdk_screen_get_height(i->screen)/20;
+ wa.width = TRIGGER_WIDTH;
+ wa.height = (gdk_screen_get_height(i->screen)*18)/20;
+ wa.wclass = GDK_INPUT_ONLY;
+ wa.window_type = GDK_WINDOW_FOREIGN;
+ wa.override_redirect = TRUE;
+ wa.type_hint = GDK_WINDOW_TYPE_HINT_DOCK;
+ wa.cursor = i->empty_cursor;
+
+ i->left_window = gdk_window_new(i->root, &wa, GDK_WA_TITLE|GDK_WA_X|GDK_WA_Y|GDK_WA_NOREDIR|GDK_WA_TYPE_HINT|GDK_WA_CURSOR);
+ gdk_window_set_keep_above(i->left_window, TRUE);
+ gdk_window_add_filter(i->left_window, filter_func, i);
+
+ wa.title = "Mango Lassi Right";
+ wa.x = gdk_screen_get_width(i->screen) - TRIGGER_WIDTH;
+
+ i->right_window = gdk_window_new(i->root, &wa, GDK_WA_TITLE|GDK_WA_X|GDK_WA_Y|GDK_WA_NOREDIR|GDK_WA_TYPE_HINT);
+ gdk_window_set_keep_above(i->right_window, TRUE);
+ gdk_window_add_filter(i->right_window, filter_func, i);
+
+ i->base_x = gdk_screen_get_width(i->screen)/2;
+ i->base_y = gdk_screen_get_height(i->screen)/2;
+
+ XTestGrabControl(GDK_DISPLAY_XDISPLAY(i->display), True);
+
+ return 0;
+}
+
+void lassi_grab_done(LassiGrabInfo *i) {
+ g_assert(i);
+
+ lassi_grab_stop(i, -1);
+
+ if (i->left_window)
+ gdk_window_destroy(i->left_window);
+
+ if (i->right_window)
+ gdk_window_destroy(i->right_window);
+
+ if (i->empty_cursor)
+ gdk_cursor_unref(i->empty_cursor);
+}
+
+void lassi_grab_enable_triggers(LassiGrabInfo *i, gboolean left, gboolean right) {
+ g_assert(i);
+
+ g_debug("Showing windows: left=%s, right=%s", left ? "yes" : "no", right ? "yes" : "no");
+
+ if (left)
+ gdk_window_show(i->left_window);
+ else
+ gdk_window_hide(i->left_window);
+
+ if (right)
+ gdk_window_show(i->right_window);
+ else
+ gdk_window_hide(i->right_window);
+}
+
+int lassi_grab_move_pointer_relative(LassiGrabInfo *i, int dx, int dy) {
+ g_assert(i);
+
+ if (i->grab_window)
+ return -1;
+
+ XTestFakeRelativeMotionEvent(GDK_DISPLAY_XDISPLAY(i->display), dx, dy, 0);
+ XSync(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ return 0;
+}
+
+int lassi_grab_press_button(LassiGrabInfo *i, unsigned button, gboolean is_press) {
+ g_assert(i);
+
+ if (i->grab_window)
+ return -1;
+
+ XTestFakeButtonEvent(GDK_DISPLAY_XDISPLAY(i->display), button, is_press, 0);
+ XSync(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ return 0;
+}
+
+int lassi_grab_press_key(LassiGrabInfo *i, unsigned key, gboolean is_press) {
+ g_assert(i);
+ if (i->grab_window)
+ return -1;
+
+ XTestFakeKeyEvent(GDK_DISPLAY_XDISPLAY(i->display), XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(i->display), key), is_press, 0);
+ XSync(GDK_DISPLAY_XDISPLAY(i->display), False);
+
+ return 0;
+}
diff --git a/src/lassi-grab.h b/src/lassi-grab.h
new file mode 100644
index 0000000..a9366e0
--- /dev/null
+++ b/src/lassi-grab.h
@@ -0,0 +1,40 @@
+#ifndef foolassigrabhfoo
+#define foolassigrabhfoo
+
+#include <gdk/gdk.h>
+
+typedef struct LassiGrabInfo LassiGrabInfo;
+struct LassiServer;
+
+struct LassiGrabInfo {
+ struct LassiServer *server;
+
+ GdkDisplay *display;
+ GdkScreen *screen;
+ GdkWindow *root;
+
+ GdkWindow *left_window, *right_window;
+ GdkCursor *empty_cursor;
+ GdkWindow *grab_window;
+
+ int base_x, base_y;
+ int last_x, last_y;
+
+ gboolean left_shift, right_shift, double_shift;
+};
+
+#include "lassi-server.h"
+
+int lassi_grab_init(LassiGrabInfo *i, LassiServer *server);
+void lassi_grab_done(LassiGrabInfo *i);
+
+int lassi_grab_start(LassiGrabInfo *i, gboolean to_left);
+void lassi_grab_stop(LassiGrabInfo *i, int y);
+
+void lassi_grab_enable_triggers(LassiGrabInfo *i, gboolean left, gboolean right);
+
+int lassi_grab_move_pointer_relative(LassiGrabInfo *i, int dx, int dy);
+int lassi_grab_press_button(LassiGrabInfo *i, unsigned button, gboolean is_press);
+int lassi_grab_press_key(LassiGrabInfo *i, unsigned key, gboolean is_press);
+
+#endif
diff --git a/src/lassi-order.c b/src/lassi-order.c
new file mode 100644
index 0000000..ceeb3f1
--- /dev/null
+++ b/src/lassi-order.c
@@ -0,0 +1,147 @@
+#include <string.h>
+
+#include <glib.h>
+
+#include "lassi-order.h"
+
+int lassi_list_compare(GList *i, GList *j) {
+
+ for (; i && j; i = i->next, j = j->next) {
+ int c;
+
+ c = strcmp(i->data, j->data);
+
+ if (c)
+ return c;
+
+ }
+
+ if (i)
+ return 1;
+
+ if (j)
+ return -1;
+
+ return 0;
+}
+
+gboolean lassi_list_nodups(GList *l) {
+ GList *i, *j;
+
+ for (i = l; i; i = i->next)
+ for (j = i->next; j; j = j->next)
+ if (strcmp(i->data, j->data) == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+GList *lassi_list_merge(GList *a, GList *b) {
+ GList *ia, *ib, *p, *c, *d;
+
+ g_assert(lassi_list_nodups(a));
+ g_assert(lassi_list_nodups(b));
+
+ p = b;
+
+ for (ia = a; ia; ia = ia->next) {
+
+ for (ib = p; ib; ib = ib->next) {
+
+ if (strcmp(ia->data, ib->data) == 0) {
+
+ /* Found a common entry, hence copy everything since
+ * the last one we found from b to a */
+
+ for (c = p; c != ib; c = c->next) {
+
+ /* Before we copy, make sure this entry is not yet
+ * in a */
+
+ for (d = a; d; d = d->next)
+ if (strcmp(c->data, d->data) == 0)
+ break;
+
+ if (!d)
+ /* OK, This one is new, let's copy it */
+
+ a = g_list_insert_before(a, ia, g_strdup(c->data));
+ }
+
+ p = ib->next;
+ }
+ }
+ }
+
+ /* Copy the tail */
+ for (c = p; c; c = c->next) {
+
+ for (d = a; d; d = d->next)
+ if (strcmp(c->data, d->data) == 0)
+ break;
+
+ if (!d)
+ a = g_list_append(a, g_strdup(c->data));
+ }
+
+ g_assert(lassi_list_nodups(a));
+
+ return a;
+}
+
+GList* lassi_list_copy(GList *l) {
+ GList *r = NULL;
+
+ for (; l; l = l->next)
+ r = g_list_prepend(r, g_strdup(l->data));
+
+ return g_list_reverse(r);
+}
+
+void lassi_list_free(GList *i) {
+ while (i) {
+ g_free(i->data);
+ i = i->next;
+ }
+}
+
+
+#if 0
+
+int main(int argc, char *argv[]) {
+ GList *a = NULL, *b = NULL, *c = NULL, *d = NULL, *i;
+
+
+ a = g_list_append(a, "eins");
+ a = g_list_append(a, "zwei");
+ a = g_list_append(a, "vier");
+ a = g_list_append(a, "fünf");
+ a = g_list_append(a, "sechs");
+ a = g_list_append(a, "acht");
+
+ b = g_list_append(b, "eins");
+ b = g_list_append(b, "zwei");
+ b = g_list_append(b, "drei");
+ b = g_list_append(b, "vier");
+ b = g_list_append(b, "sechs");
+ b = g_list_append(b, "acht");
+
+ c = g_list_append(c, "eins");
+ c = g_list_append(c, "sieben");
+ c = g_list_append(c, "acht");
+
+ d = g_list_append(d, "drei");
+ d = g_list_append(d, "neun");
+ d = g_list_append(d, "zwei");
+
+ a = lassi_list_merge(a, b);
+ a = lassi_list_merge(a, c);
+ a = lassi_list_merge(a, d);
+
+ for (i = a; i; i = i->next)
+ g_debug("%s", (char*) i->data);
+
+ return 0;
+}
+
+#endif
diff --git a/src/lassi-order.h b/src/lassi-order.h
new file mode 100644
index 0000000..e41a0d7
--- /dev/null
+++ b/src/lassi-order.h
@@ -0,0 +1,13 @@
+#ifndef foolassiorderhfoo
+#define foolassiorderhfoo
+
+#include <glib.h>
+
+int lassi_list_compare(GList *i, GList *j);
+gboolean lassi_list_nodups(GList *l);
+GList *lassi_list_merge(GList *a, GList *b);
+GList *lassi_list_copy(GList *l);
+void lassi_list_free(GList *i);
+
+#endif
+
diff --git a/src/lassi-osd.c b/src/lassi-osd.c
new file mode 100644
index 0000000..ae9a21c
--- /dev/null
+++ b/src/lassi-osd.c
@@ -0,0 +1,144 @@
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <gdk/gdkx.h>
+
+#include <string.h>
+
+#include "lassi-osd.h"
+
+int lassi_osd_init(LassiOsdInfo *osd) {
+ GtkWidget *hbox;
+ GdkColor color;
+ guint32 cardinal;
+ GdkDisplay *display;
+
+ g_assert(osd);
+
+ memset(osd, 0, sizeof(*osd));
+
+ osd->window = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_window_set_title(GTK_WINDOW(osd->window), "Mango Lassi OSD");
+ gtk_window_stick(GTK_WINDOW(osd->window));
+ gtk_window_set_keep_above(GTK_WINDOW(osd->window), TRUE);
+ gtk_window_set_decorated(GTK_WINDOW(osd->window), FALSE);
+ gtk_window_set_deletable(GTK_WINDOW(osd->window), FALSE);
+ gtk_window_set_type_hint(GTK_WINDOW(osd->window), GDK_WINDOW_TYPE_HINT_NOTIFICATION);
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(osd->window), TRUE);
+ gtk_window_set_skip_pager_hint(GTK_WINDOW(osd->window), TRUE);
+ gtk_window_set_accept_focus(GTK_WINDOW(osd->window), FALSE);
+ gtk_window_set_focus_on_map(GTK_WINDOW(osd->window), FALSE);
+ gtk_window_set_gravity(GTK_WINDOW(osd->window), GDK_GRAVITY_SOUTH_WEST);
+
+ osd->label = gtk_label_new("Test");
+ gtk_misc_set_padding(GTK_MISC(osd->label), 16, 0);
+/* gtk_label_set_line_wrap(GTK_LABEL(osd->label), TRUE); */
+ osd->left_icon = gtk_image_new();
+ osd->right_icon = gtk_image_new();
+
+ hbox = gtk_hbox_new(0, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
+
+ gtk_box_pack_start(GTK_BOX(hbox), osd->left_icon, FALSE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), osd->label, TRUE, TRUE, 0);
+ gtk_box_pack_end(GTK_BOX(hbox), osd->right_icon, FALSE, TRUE, 0);
+
+ gtk_container_add(GTK_CONTAINER(osd->window), hbox);
+
+ gtk_widget_show(hbox);
+ gtk_widget_show(osd->label);
+
+ gdk_color_parse("#262624", &color);
+ if (!gdk_colormap_alloc_color(gtk_widget_get_colormap(osd->window), &color, FALSE, FALSE))
+ gdk_color_black(gtk_widget_get_colormap(osd->window), &color);
+ gtk_widget_modify_bg(osd->window, GTK_STATE_NORMAL, &color);
+
+ gtk_widget_realize(GTK_WIDGET(osd->window));
+
+ cardinal = 0xbfffffff;
+ display = gdk_drawable_get_display(osd->window->window);
+ XChangeProperty(GDK_DISPLAY_XDISPLAY(display),
+ GDK_WINDOW_XID(osd->window->window),
+ gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_WINDOW_OPACITY"),
+ XA_CARDINAL, 32,
+ PropModeReplace,
+ (guchar *) &cardinal, 1);
+
+ g_debug("WINDOW=%p", osd->window);
+
+ return 0;
+}
+
+void lassi_osd_done(LassiOsdInfo *osd) {
+ g_assert(osd);
+
+ gtk_widget_destroy(osd->window);
+
+ memset(osd, 0, sizeof(*osd));
+}
+
+void lassi_osd_set_text(LassiOsdInfo *osd, const char *text, const char *icon_name_left, const char *icon_name_right) {
+ char *t;
+ int w, h, max_width;
+
+ g_assert(osd);
+ g_assert(osd->window);
+
+ g_debug("WINDOW=%p", osd->window);
+
+ g_debug("Showing text '%s'", text);
+
+ t = g_strdup_printf("<span size=\"large\" color=\"#F5F5F5\">%s</span>", text);
+ gtk_label_set_markup(GTK_LABEL(osd->label), t);
+ g_free(t);
+
+ if (icon_name_left) {
+ gtk_image_set_from_icon_name(GTK_IMAGE(osd->left_icon), icon_name_left, GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show(osd->left_icon);
+ } else
+ gtk_widget_hide(osd->left_icon);
+
+ if (icon_name_right) {
+ gtk_image_set_from_icon_name(GTK_IMAGE(osd->right_icon), icon_name_right, GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show(osd->right_icon);
+ } else
+ gtk_widget_hide(osd->right_icon);
+
+ max_width = (gdk_screen_width()*18)/20;
+
+ g_debug("WINDOW=%p", osd->window);
+
+ gtk_widget_set_size_request(osd->window, -1, -1);
+
+ gtk_window_get_size(GTK_WINDOW(osd->window), &w, &h);
+
+ g_debug("WINDOW=%p", osd->window);
+
+ if (w > max_width) {
+ gtk_widget_set_size_request(osd->window, max_width, -1);
+ w = max_width;
+ }
+
+ if (!icon_name_left == !icon_name_right) {
+ gtk_label_set_justify(GTK_LABEL(osd->label), GTK_JUSTIFY_CENTER);
+ gtk_window_move(GTK_WINDOW(osd->window), (gdk_screen_width() - w)/2, (gdk_screen_height()*9)/10 - h);
+ } else if (icon_name_left) {
+ gtk_label_set_justify(GTK_LABEL(osd->label), GTK_JUSTIFY_LEFT);
+ gtk_window_move(GTK_WINDOW(osd->window), gdk_screen_width()/20, (gdk_screen_height()*9)/10 - h);
+ } else {
+ gtk_label_set_justify(GTK_LABEL(osd->label), GTK_JUSTIFY_RIGHT);
+ gtk_window_move(GTK_WINDOW(osd->window), (gdk_screen_width()*19)/20 - w, (gdk_screen_height()*9)/10 - h);
+ }
+
+ gtk_widget_show(osd->window);
+
+ g_debug("osd shown");
+}
+
+void lassi_osd_hide(LassiOsdInfo *osd) {
+ g_assert(osd);
+
+ gtk_widget_hide(osd->window);
+
+ g_debug("osd hidden");
+}
diff --git a/src/lassi-osd.h b/src/lassi-osd.h
new file mode 100644
index 0000000..2a792eb
--- /dev/null
+++ b/src/lassi-osd.h
@@ -0,0 +1,18 @@
+#ifndef foolassiosdhfoo
+#define foolassiosdhfoo
+
+#include <gtk/gtk.h>
+
+typedef struct LassiOsdInfo LassiOsdInfo;
+
+struct LassiOsdInfo {
+ GtkWidget *window, *label, *left_icon, *right_icon;
+};
+
+int lassi_osd_init(LassiOsdInfo *osd);
+void lassi_osd_done(LassiOsdInfo *osd);
+
+void lassi_osd_set_text(LassiOsdInfo *osd, const char *text, const char *icon_name_left, const char *icon_name_right);
+void lassi_osd_hide(LassiOsdInfo *osd);
+
+#endif
diff --git a/src/lassi-prefs.c b/src/lassi-prefs.c
new file mode 100644
index 0000000..c3adf68
--- /dev/null
+++ b/src/lassi-prefs.c
@@ -0,0 +1,212 @@
+#include <string.h>
+
+#include <avahi-ui/avahi-ui.h>
+
+#include "lassi-prefs.h"
+#include "lassi-server.h"
+
+enum {
+ COLUMN_ICON,
+ COLUMN_NAME,
+ COLUMN_GLIST,
+ N_COLUMNS
+};
+
+
+static void on_add_button_clicked(GtkButton *widget, LassiPrefsInfo *i) {
+ GtkWidget *d;
+
+ d = aui_service_dialog_new("Choose Desktop to add", GTK_WINDOW(i->dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT, NULL);
+ aui_service_dialog_set_browse_service_types(AUI_SERVICE_DIALOG(d), LASSI_SERVICE_TYPE, NULL);
+
+ if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
+ char a[AVAHI_ADDRESS_STR_MAX], *t;
+
+ avahi_address_snprint(a, sizeof(a), aui_service_dialog_get_address(AUI_SERVICE_DIALOG(d)));
+ t = g_strdup_printf("tcp:port=%u,host=%s", aui_service_dialog_get_port(AUI_SERVICE_DIALOG(d)), a);
+ lassi_server_connect(i->server, t);
+ g_free(t);
+ }
+
+ gtk_widget_destroy(d);
+}
+
+static void on_remove_button_clicked(GtkButton *widget, LassiPrefsInfo *i) {
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ char *id;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(i->tree_view));
+
+ if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
+ return;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(i->list_store), &iter, COLUMN_NAME, &id, -1);
+ if (id) {
+ lassi_server_disconnect(i->server, id, TRUE);
+ g_free(id);
+ }
+}
+
+static void on_up_button_clicked(GtkButton *widget, LassiPrefsInfo *i) {
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ char *id;
+
+/* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(i->tree_view));
+
+ if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
+ return;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(i->list_store), &iter, COLUMN_NAME, &id, -1);
+
+ if (id) {
+ GList *o = lassi_list_copy(i->server->order);
+ lissi_list_move_up(o, id);
+ lassi_server_set_order(i->server, o);
+ g_free(id);
+ }*/
+}
+
+static void on_down_button_clicked(GtkButton *widget, LassiPrefsInfo *i) {
+}
+
+static void on_close_button_clicked(GtkButton *widget, LassiPrefsInfo *i) {
+ gtk_widget_hide(GTK_WIDGET(i->dialog));
+}
+
+static void update_sensitive(LassiPrefsInfo *i) {
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gboolean is_first;
+ char *id;
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(i->tree_view));
+
+ if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+ gtk_widget_set_sensitive(i->up_button, FALSE);
+ gtk_widget_set_sensitive(i->down_button, FALSE);
+ gtk_widget_set_sensitive(i->remove_button, FALSE);
+ return;
+ }
+
+ gtk_tree_model_get(GTK_TREE_MODEL(i->list_store), &iter, COLUMN_NAME, &id, -1);
+ gtk_widget_set_sensitive(i->remove_button, strcmp(id, i->server->id) != 0);
+ g_free(id);
+
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(i->list_store), &iter);
+
+ is_first = gtk_tree_path_prev(path);
+ gtk_widget_set_sensitive(i->up_button, is_first);
+ if (is_first)
+ gtk_tree_path_next(path);
+
+ gtk_tree_path_next(path);
+ gtk_widget_set_sensitive(i->down_button, gtk_tree_model_get_iter(GTK_TREE_MODEL(i->list_store), &iter, path));
+
+ gtk_tree_path_free(path);
+}
+
+static void on_selection_changed(GtkTreeSelection *selection, LassiPrefsInfo *i) {
+ update_sensitive(i);
+}
+
+int lassi_prefs_init(LassiPrefsInfo *i, LassiServer *server) {
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+
+ g_assert(i);
+ g_assert(server);
+
+ memset(i, 0, sizeof(*i));
+ i->server = server;
+
+ i->xml = glade_xml_new("mango-lassi.glade", NULL, NULL);
+
+ i->dialog = glade_xml_get_widget(i->xml, "preferences_dialog");
+ i->up_button = glade_xml_get_widget(i->xml, "up_button");
+ i->down_button = glade_xml_get_widget(i->xml, "down_button");
+ i->add_button = glade_xml_get_widget(i->xml, "add_button");
+ i->remove_button = glade_xml_get_widget(i->xml, "remove_button");
+ i->tree_view = glade_xml_get_widget(i->xml, "tree_view");
+
+ glade_xml_signal_connect_data(i->xml, "on_add_button_clicked", (GCallback) on_add_button_clicked, i);
+ glade_xml_signal_connect_data(i->xml, "on_remove_button_clicked", (GCallback) on_remove_button_clicked, i);
+ glade_xml_signal_connect_data(i->xml, "on_up_button_clicked", (GCallback) on_up_button_clicked, i);
+ glade_xml_signal_connect_data(i->xml, "on_down_button_clicked", (GCallback) on_down_button_clicked, i);
+
+ glade_xml_signal_connect_data(i->xml, "on_close_button_clicked", (GCallback) on_close_button_clicked, i);
+
+ g_signal_connect(G_OBJECT(i->dialog), "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
+
+ i->list_store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(i->tree_view), GTK_TREE_MODEL(i->list_store));
+
+ column = gtk_tree_view_column_new_with_attributes("Icon", gtk_cell_renderer_pixbuf_new(), "icon-name", COLUMN_ICON, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(i->tree_view), column);
+
+ column = gtk_tree_view_column_new_with_attributes("Name", gtk_cell_renderer_text_new(), "text", COLUMN_NAME, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(i->tree_view), column);
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(i->tree_view));
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+ g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(on_selection_changed), i);
+
+ lassi_prefs_update(i);
+
+ return 0;
+}
+
+void lassi_prefs_update(LassiPrefsInfo *i) {
+ GList *l;
+ char *selected_item = NULL;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ g_assert(i);
+
+ g_message("prefs update");
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(i->tree_view));
+
+ if (gtk_tree_selection_get_selected(selection, NULL, &iter))
+ gtk_tree_model_get(GTK_TREE_MODEL(i->list_store), &iter, COLUMN_NAME, &selected_item, -1);
+
+ gtk_list_store_clear(GTK_LIST_STORE(i->list_store));
+
+ for (l = i->server->order; l; l = l->next) {
+
+ if (!lassi_server_is_connected(i->server, l->data))
+ continue;
+
+ gtk_list_store_append(GTK_LIST_STORE(i->list_store), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(i->list_store), &iter,
+ COLUMN_ICON, strcmp(i->server->id, l->data) ? "network-wired" : "user-desktop",
+ COLUMN_NAME, l->data,
+ COLUMN_GLIST, l, -1);
+
+ if (selected_item)
+ if (strcmp(selected_item, l->data) == 0)
+ gtk_tree_selection_select_iter(selection, &iter);
+ }
+
+ g_free(selected_item);
+
+ update_sensitive(i);
+}
+
+void lassi_prefs_show(LassiPrefsInfo *i) {
+ g_assert(i);
+
+ gtk_window_present(GTK_WINDOW(i->dialog));
+}
+
+void lassi_prefs_done(LassiPrefsInfo *i) {
+ g_assert(i);
+
+ g_object_unref(G_OBJECT(i->xml));
+ g_object_unref(G_OBJECT(i->list_store));
+
+ memset(i, 0, sizeof(*i));
+}
diff --git a/src/lassi-prefs.h b/src/lassi-prefs.h
new file mode 100644
index 0000000..a948709
--- /dev/null
+++ b/src/lassi-prefs.h
@@ -0,0 +1,29 @@
+#ifndef foolassiprefshfoo
+#define foolassiprefshfoo
+
+#include <gtk/gtk.h>
+#include <glade/glade-xml.h>
+
+typedef struct LassiPrefsInfo LassiPrefsInfo;
+struct LassiServer;
+
+struct LassiPrefsInfo {
+ struct LassiServer *server;
+
+ GtkWidget *dialog;
+ GtkWidget *up_button, *down_button, *add_button, *remove_button;
+ GtkWidget *tree_view;
+
+ GtkListStore *list_store;
+
+ GladeXML *xml;
+};
+
+#include "lassi-server.h"
+
+int lassi_prefs_init(LassiPrefsInfo *i, LassiServer *server);
+void lassi_prefs_show(LassiPrefsInfo *i);
+void lassi_prefs_update(LassiPrefsInfo *i);
+void lassi_prefs_done(LassiPrefsInfo *i);
+
+#endif
diff --git a/src/lassi-server.c b/src/lassi-server.c
new file mode 100644
index 0000000..f160279
--- /dev/null
+++ b/src/lassi-server.c
@@ -0,0 +1,1554 @@
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus.h>
+
+#include <gtk/gtk.h>
+
+#include "lassi-server.h"
+#include "lassi-grab.h"
+#include "lassi-order.h"
+#include "lassi-clipboard.h"
+#include "lassi-avahi.h"
+#include "lassi-tray.h"
+
+#define LASSI_INTERFACE "org.gnome.MangoLassi"
+
+#define PORT_MIN 7421
+#define PORT_MAX (PORT_MIN + 50)
+
+#define CONNECTIONS_MAX 16
+
+static void server_disconnect_all(LassiServer *ls, gboolean clear_order);
+static void server_send_update_grab(LassiServer *ls, int y);
+
+static void server_broadcast(LassiServer *ls, DBusMessage *m, LassiConnection *except) {
+ GList *i;
+
+ g_assert(ls);
+ g_assert(m);
+
+ for (i = ls->connections; i; i = i->next) {
+ dbus_bool_t b;
+ LassiConnection *lc = i->data;
+ DBusMessage *n;
+
+ if (lc == except || !lc->id)
+ continue;
+
+ n = dbus_message_copy(m);
+ g_assert(n);
+ b = dbus_connection_send(lc->dbus_connection, n, NULL);
+ g_assert(b);
+ dbus_message_unref(n);
+ }
+}
+
+static void server_layout_changed(LassiServer *ls, int y) {
+ g_assert(ls);
+
+ g_debug("updating layout");
+
+ lassi_grab_enable_triggers(&ls->grab_info, !!ls->connections_left, !!ls->connections_right);
+
+ if (ls->active_connection) {
+ char *t;
+ gboolean to_left = !!g_list_find(ls->connections_left, ls->active_connection);
+
+ t = g_strdup_printf("Mouse and keyboard are being redirected to <b>%s</b>, which is located to the <b>%s</b> of this screen.\n"
+ "To redirect input back to this screen, press and release both shift keys simultaneously.",
+ ls->active_connection->id, to_left ? "left" : "right");
+
+ if (to_left)
+ lassi_osd_set_text(&ls->osd_info, t, "go-previous", NULL);
+ else
+ lassi_osd_set_text(&ls->osd_info, t, NULL, "go-next");
+
+ lassi_grab_start(&ls->grab_info, to_left);
+
+ } else {
+ lassi_grab_stop(&ls->grab_info, y);
+ lassi_osd_hide(&ls->osd_info);
+ }
+}
+
+static void server_set_order(LassiServer *ls, GList *order) {
+ GList *l;
+ gboolean on_left = TRUE;
+ g_assert(ls);
+
+ lassi_list_free(ls->order);
+ ls->order = order;
+
+ g_list_free(ls->connections_left);
+ g_list_free(ls->connections_right);
+
+ ls->connections_left = ls->connections_right = NULL;
+
+ for (l = ls->order; l; l = l->next) {
+ LassiConnection *lc;
+
+ if (!(lc = g_hash_table_lookup(ls->connections_by_id, l->data))) {
+
+ if (strcmp(ls->id, l->data))
+ continue;
+ }
+
+ g_assert(lc || on_left);
+
+ if (!lc)
+ on_left = FALSE;
+ else if (on_left)
+ ls->connections_left = g_list_prepend(ls->connections_left, lc);
+ else
+ ls->connections_right = g_list_prepend(ls->connections_right, lc);
+ }
+
+ for (l = ls->connections; l; l = l->next) {
+ LassiConnection *lc = l->data;
+
+ if (!lc->id)
+ continue;
+
+ if (g_list_find(ls->connections_left, lc))
+ continue;
+
+ if (g_list_find(ls->connections_right, lc))
+ continue;
+
+ ls->order = g_list_append(ls->order, lc->id);
+ ls->connections_right = g_list_prepend(ls->connections_right, lc);
+ }
+
+ ls->connections_right = g_list_reverse(ls->connections_right);
+ server_layout_changed(ls, -1);
+
+ lassi_prefs_update(&ls->prefs_info);
+}
+
+static void server_dump(LassiServer *ls) {
+ GList *l;
+ int n = 0;
+
+ g_assert(ls);
+
+ g_debug("BEGIN Current connections:");
+
+ g_debug("Displays left of us:");
+ for (l = ls->connections_left; l; l = l->next) {
+ LassiConnection *lc = l->data;
+ if (!lc->id)
+ continue;
+ g_debug("%2i) %s %s %s", n++, ls->active_connection == lc ? "ACTIVE" : " ", lc->id, lc->address);
+ }
+
+ g_debug("Displays right of us:");
+ for (l = ls->connections_right; l; l = l->next) {
+ LassiConnection *lc = l->data;
+ if (!lc->id)
+ continue;
+ g_debug("%2i) %s %s %s", n++, ls->active_connection == lc ? "ACTIVE" : " ", lc->id, lc->address);
+ }
+
+ if (!ls->active_connection)
+ g_debug("We're the active connection");
+
+ g_debug("END");
+}
+
+static void connection_destroy(LassiConnection *lc) {
+ g_assert(lc);
+
+ dbus_connection_flush(lc->dbus_connection);
+ dbus_connection_close(lc->dbus_connection);
+ dbus_connection_unref(lc->dbus_connection);
+ g_free(lc->id);
+ g_free(lc->address);
+ g_free(lc);
+}
+
+static void server_pick_active_connection(LassiServer *ls) {
+ LassiConnection *pick;
+ GList *l;
+ char *id;
+
+ pick = NULL;
+ id = ls->id;
+
+ for (l = ls->connections; l; l = l->next) {
+ LassiConnection *lc = l->data;
+
+ if (!lc->id)
+ continue;
+
+ if (strcmp(lc->id, id) > 0) {
+ id = lc->id;
+ pick = lc;
+ }
+ }
+
+ ls->active_connection = pick;
+
+ server_send_update_grab(ls, -1);
+ server_layout_changed(ls, -1);
+}
+
+static void server_send_update_grab(LassiServer *ls, int y) {
+ char *active;
+ DBusMessage *n;
+ dbus_bool_t b;
+ gint32 g;
+
+ g_assert(ls);
+
+ active = ls->active_connection ? ls->active_connection->id : ls->id;
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "UpdateGrab");
+ g_assert(n);
+
+ g = ++ ls->active_generation;
+ b = dbus_message_append_args(
+ n,
+ DBUS_TYPE_INT32, &g,
+ DBUS_TYPE_STRING, &active,
+ DBUS_TYPE_INT32, &y,
+ DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ server_broadcast(ls, n, NULL);
+ dbus_message_unref(n);
+}
+
+static void server_send_update_order(LassiServer *ls, LassiConnection *except) {
+ DBusMessage *n;
+ dbus_bool_t b;
+ gint32 g;
+ DBusMessageIter iter, sub;
+ GList *l;
+
+ g_assert(ls);
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "UpdateOrder");
+ g_assert(n);
+
+ g = ++ ls->order_generation;
+ b = dbus_message_append_args(
+ n,
+ DBUS_TYPE_INT32, &g,
+ DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ dbus_message_iter_init_append(n, &iter);
+
+ b = dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &sub);
+ g_assert(b);
+
+ for (l = ls->order; l; l = l->next) {
+ b = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &l->data);
+ g_assert(b);
+ }
+
+ b = dbus_message_iter_close_container(&iter, &sub);
+ g_assert(b);
+
+ server_broadcast(ls, n, except);
+ dbus_message_unref(n);
+}
+
+int lassi_server_change_grab(LassiServer *ls, gboolean to_left, int y) {
+ LassiConnection *lc;
+ GList *l;
+
+ g_assert(ls);
+
+ l = to_left ? ls->connections_left : ls->connections_right;
+ lc = l ? l->data : NULL;
+
+ if (!lc)
+ return -1;
+
+ ls->active_connection = lc;
+
+ server_send_update_grab(ls, y);
+ server_layout_changed(ls, y);
+ return 0;
+}
+
+int lassi_server_acquire_grab(LassiServer *ls) {
+ g_assert(ls);
+
+ ls->active_connection = NULL;
+
+ server_send_update_grab(ls, -1);
+ server_layout_changed(ls, -1);
+ return 0;
+}
+
+int lassi_server_motion_event(LassiServer *ls, int dx, int dy) {
+ DBusMessage *n;
+ dbus_bool_t b;
+
+ g_assert(ls);
+
+ if (!ls->active_connection)
+ return -1;
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "MotionEvent");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_INT32, &dx, DBUS_TYPE_INT32, &dy, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ b = dbus_connection_send(ls->active_connection->dbus_connection, n, NULL);
+ g_assert(b);
+
+ dbus_message_unref(n);
+
+ dbus_connection_flush(ls->active_connection->dbus_connection);
+
+ return 0;
+}
+
+int lassi_server_button_event(LassiServer *ls, unsigned button, gboolean is_press) {
+ DBusMessage *n;
+ dbus_bool_t b;
+
+ if (!ls->active_connection)
+ return -1;
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "ButtonEvent");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_UINT32, &button, DBUS_TYPE_BOOLEAN, &is_press, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ b = dbus_connection_send(ls->active_connection->dbus_connection, n, NULL);
+ g_assert(b);
+
+ dbus_message_unref(n);
+
+ dbus_connection_flush(ls->active_connection->dbus_connection);
+
+ return 0;
+}
+
+int lassi_server_key_event(LassiServer *ls, unsigned key, gboolean is_press) {
+ DBusMessage *n;
+ dbus_bool_t b;
+
+ if (!ls->active_connection)
+ return -1;
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "KeyEvent");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_UINT32, &key, DBUS_TYPE_BOOLEAN, &is_press, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ b = dbus_connection_send(ls->active_connection->dbus_connection, n, NULL);
+ g_assert(b);
+
+ dbus_message_unref(n);
+
+ dbus_connection_flush(ls->active_connection->dbus_connection);
+
+ return 0;
+}
+
+static void show_welcome(LassiConnection *lc, gboolean connect) {
+ gboolean to_left;
+ LassiServer *ls;
+ char *summary, *body;
+
+ g_assert(lc);
+
+ ls = lc->server;
+ to_left = !!g_list_find(ls->connections_left, lc);
+
+ if (connect) {
+ summary = g_strdup_printf("%s now shares input with this desktop", lc->id);
+ body = g_strdup_printf("You're now sharing keyboard and mouse with <b>%s</b> which is located to the <b>%s</b>.", lc->id, to_left ? "left" : "right");
+ } else {
+ summary = g_strdup_printf("%s no longer shares input with this desktop", lc->id);
+ body = g_strdup_printf("You're no longer sharing keyboard and mouse with <b>%s</b> which was located to the <b>%s</b>.", lc->id, to_left ? "left" : "right");
+ }
+
+ lassi_tray_show_notification(&ls->tray_info, summary, body, to_left ? LASSI_TRAY_NOTIFICATION_LEFT : LASSI_TRAY_NOTIFICATION_RIGHT);
+
+ g_free(summary);
+ g_free(body);
+}
+
+static void connection_unlink(LassiConnection *lc, gboolean remove_from_order) {
+ LassiServer *ls;
+ g_assert(lc);
+
+ g_debug("Unlinking %s (%s)", lc->id, lc->address);
+
+ ls = lc->server;
+
+ if (lc->id) {
+ DBusMessage *n;
+ dbus_bool_t b;
+
+ /* Tell everyone */
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "NodeRemoved");
+ g_assert(n);
+
+ b = dbus_message_append_args(n,
+ DBUS_TYPE_STRING, &lc->id,
+ DBUS_TYPE_STRING, &lc->address,
+ DBUS_TYPE_BOOLEAN, &remove_from_order,
+ DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ server_broadcast(ls, n, NULL);
+ dbus_message_unref(n);
+ }
+
+ ls->connections = g_list_remove(ls->connections, lc);
+ ls->n_connections --;
+
+ if (lc->id) {
+ show_welcome(lc, FALSE);
+
+ g_hash_table_remove(ls->connections_by_id, lc->id);
+ ls->connections_left = g_list_remove(ls->connections_left, lc);
+ ls->connections_right = g_list_remove(ls->connections_right, lc);
+
+ if (ls->active_connection == lc)
+ server_pick_active_connection(ls);
+
+ if (ls->clipboard_connection == lc) {
+ ls->clipboard_connection = NULL;
+ ls->clipboard_empty = TRUE;
+ lassi_clipboard_clear(&lc->server->clipboard_info, FALSE);
+ }
+
+ if (ls->primary_connection == lc) {
+ ls->primary_connection = NULL;
+ ls->primary_empty = TRUE;
+ lassi_clipboard_clear(&lc->server->clipboard_info, TRUE);
+ }
+
+ if (remove_from_order) {
+ GList *i = g_list_find_custom(ls->order, lc->id, (GCompareFunc) strcmp);
+
+ if (i)
+ ls->order = g_list_delete_link(ls->order, i);
+ }
+
+ server_layout_changed(ls, -1);
+ lassi_prefs_update(&ls->prefs_info);
+ server_dump(ls);
+ }
+
+ lassi_tray_update(&ls->tray_info, ls->n_connections);
+
+ connection_destroy(lc);
+}
+
+static void server_position_connection(LassiServer *ls, LassiConnection *lc) {
+ GList *l;
+ LassiConnection *last = NULL;
+
+ g_assert(ls);
+ g_assert(lc);
+
+ g_assert(!g_list_find(ls->connections_left, lc));
+ g_assert(!g_list_find(ls->connections_right, lc));
+
+ for (l = ls->order; l; l = l->next) {
+ LassiConnection *k;
+
+ if (strcmp(l->data, lc->id) == 0)
+ break;
+
+ if ((k = g_hash_table_lookup(ls->connections_by_id, l->data)))
+ last = k;
+ }
+
+ if (l) {
+ /* OK, We found a spot to add this */
+
+ if (last) {
+ GList *j;
+
+ /*Ok, this one belongs to the right of 'last' */
+
+ if ((j = g_list_find(ls->connections_left, last)))
+ /* This one belongs in the left list */
+ ls->connections_left = g_list_insert_before(ls->connections_left, j, lc);
+ else {
+ /* This one belongs in the rightlist */
+ ls->connections_right = g_list_reverse(ls->connections_right);
+ j = g_list_find(ls->connections_right, last);
+ g_assert(j);
+ ls->connections_right = g_list_insert_before(ls->connections_right, j, lc);
+ ls->connections_right = g_list_reverse(ls->connections_right);
+ }
+ } else
+ /* Hmm, this is before the left end */
+ ls->connections_left = g_list_append(ls->connections_left, lc);
+ } else {
+ ls->order = g_list_append(ls->order, g_strdup(lc->id));
+ /* No spot found, let's add it to the right end */
+ ls->connections_right = g_list_append(ls->connections_right, lc);
+ }
+}
+
+int lassi_server_acquire_clipboard(LassiServer *ls, gboolean primary, char**targets) {
+ DBusMessageIter iter, sub;
+ DBusMessage *n;
+ gint32 g;
+ gboolean b;
+
+ g_assert(ls);
+ g_assert(targets);
+
+ if (primary) {
+ ls->primary_empty = FALSE;
+ ls->primary_connection = NULL;
+ } else {
+ ls->clipboard_empty = FALSE;
+ ls->clipboard_connection = NULL;
+ }
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "AcquireClipboard");
+ g_assert(n);
+
+ if (primary)
+ g = ++ ls->primary_generation;
+ else
+ g = ++ ls->clipboard_generation;
+
+ b = dbus_message_append_args(n, DBUS_TYPE_INT32, &g, DBUS_TYPE_BOOLEAN, &primary, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ dbus_message_iter_init_append(n, &iter);
+
+ b = dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &sub);
+ g_assert(b);
+
+ for (; *targets; targets++) {
+ g_debug("Exporting target %s", *targets);
+ b = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, targets);
+ g_assert(b);
+ }
+
+ b = dbus_message_iter_close_container(&iter, &sub);
+ g_assert(b);
+
+ server_broadcast(ls, n, NULL);
+ g_assert(b);
+
+ dbus_message_unref(n);
+ return 0;
+}
+
+int lassi_server_return_clipboard(LassiServer *ls, gboolean primary) {
+ DBusMessage *n;
+ guint32 g;
+ gboolean b;
+
+ g_assert(ls);
+
+ if (primary) {
+
+ if (ls->primary_empty || ls->primary_connection != NULL)
+ return -1;
+
+ ls->primary_empty = TRUE;
+ ls->primary_connection = NULL;
+
+ } else {
+
+ if (ls->clipboard_empty || ls->clipboard_connection != NULL)
+ return -1;
+
+ ls->clipboard_empty = TRUE;
+ ls->clipboard_connection = NULL;
+ }
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "ReturnClipboard");
+ g_assert(n);
+
+ if (primary)
+ g = ++ ls->primary_generation;
+ else
+ g = ++ ls->clipboard_generation;
+
+ b = dbus_message_append_args(n, DBUS_TYPE_UINT32, &g, DBUS_TYPE_BOOLEAN, &primary, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ server_broadcast(ls, n, NULL);
+
+ dbus_message_unref(n);
+ return 0;
+}
+
+int lassi_server_get_clipboard(LassiServer *ls, gboolean primary, const char *t, int *f, gpointer *p, int *l) {
+ DBusMessage *n, *reply;
+ DBusConnection *c;
+ DBusError e;
+ int ret = -1;
+ DBusMessageIter iter, sub;
+ gboolean b;
+
+ g_assert(ls);
+
+ dbus_error_init(&e);
+
+ if (primary) {
+
+ if (ls->primary_empty || !ls->primary_connection)
+ return -1;
+
+ c = ls->primary_connection->dbus_connection;
+
+ } else {
+
+ if (ls->clipboard_empty || !ls->clipboard_connection)
+ return -1;
+
+ c = ls->clipboard_connection->dbus_connection;
+ }
+
+ n = dbus_message_new_method_call(NULL, "/", LASSI_INTERFACE, "GetClipboard");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_BOOLEAN, &primary, DBUS_TYPE_STRING, &t, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ if (!(reply = dbus_connection_send_with_reply_and_block(c, n, -1, &e))) {
+ g_debug("Getting clipboard failed: %s", e.message);
+ goto finish;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+ dbus_message_iter_get_basic(&iter, f);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE) {
+ g_debug("Invalid clipboard data");
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+ dbus_message_iter_get_fixed_array(&sub, p, l);
+
+ *p = g_memdup(*p, *l);
+
+ ret = 0;
+
+finish:
+
+ dbus_message_unref(n);
+
+ if (reply)
+ dbus_message_unref(reply);
+
+ dbus_error_free(&e);
+
+ return ret;
+}
+
+static int signal_hello(LassiConnection *lc, DBusMessage *m) {
+ const char *id, *address;
+ DBusError e;
+ GList *i;
+ dbus_bool_t b;
+ DBusMessage *n;
+ gint32 active_generation, order_generation, clipboard_generation;
+
+ dbus_error_init(&e);
+
+ if (lc->id) {
+ g_debug("Received duplicate HelloNode.");
+ return -1;
+ }
+
+ if (!(dbus_message_get_args(
+ m, &e,
+ DBUS_TYPE_STRING, &id,
+ DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_INT32, &active_generation,
+ DBUS_TYPE_INT32, &order_generation,
+ DBUS_TYPE_INT32, &clipboard_generation,
+ DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ if (strcmp(id, lc->server->id) == 0) {
+ g_debug("Dropping looped back connection.");
+ return -1;
+ }
+
+ if (g_hash_table_lookup(lc->server->connections_by_id, id)) {
+ g_debug("Dropping duplicate connection.");
+ return -1;
+ }
+
+ lc->server->active_generation = MAX(lc->server->active_generation, active_generation);
+ lc->server->order_generation = MAX(lc->server->order_generation, order_generation);
+ lc->server->clipboard_generation = MAX(lc->server->clipboard_generation, clipboard_generation);
+
+ g_debug("Got welcome from %s (%s)", id, address);
+
+ lc->id = g_strdup(id);
+ lc->address = g_strdup(address);
+ g_hash_table_insert(lc->server->connections_by_id, lc->id, lc);
+ server_position_connection(lc->server, lc);
+
+ /* Notify all old nodes of the new one */
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "NodeAdded");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_STRING, &id, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ server_broadcast(lc->server, n, lc);
+ dbus_message_unref(n);
+
+ /* Notify new node about old nodes */
+ for (i = lc->server->connections; i; i = i->next) {
+ LassiConnection *k = i->data;
+ dbus_bool_t b;
+
+ if (k == lc || !k->id)
+ continue;
+
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "NodeAdded");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_STRING, &id, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ b = dbus_connection_send(lc->dbus_connection, n, NULL);
+ g_assert(b);
+
+ dbus_message_unref(n);
+ }
+
+ if (lc->we_are_client) {
+ server_send_update_grab(lc->server, -1);
+ server_send_update_order(lc->server, NULL);
+
+ lc->delayed_welcome = FALSE;
+ show_welcome(lc, TRUE);
+ } else
+ lc->delayed_welcome = TRUE;
+
+ server_layout_changed(lc->server, -1);
+ lassi_prefs_update(&lc->server->prefs_info);
+
+ server_dump(lc->server);
+
+ return 0;
+}
+
+static int signal_node_added(LassiConnection *lc, DBusMessage *m) {
+ const char *id, *address;
+ DBusError e;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &id, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ if (strcmp(id, lc->server->id) == 0)
+ return 0;
+
+ if (g_hash_table_lookup(lc->server->connections_by_id, id))
+ return 0;
+
+ if (!(lassi_server_connect(lc->server, address))) {
+ DBusMessage *n;
+ dbus_bool_t b;
+
+ /* Failed to connnect to this client, tell everyone */
+ n = dbus_message_new_signal("/", LASSI_INTERFACE, "NodeRemoved");
+ g_assert(n);
+
+ b = dbus_message_append_args(n, DBUS_TYPE_STRING, &id, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ server_broadcast(lc->server, n, NULL);
+ dbus_message_unref(n);
+ }
+
+ return 0;
+}
+
+static int signal_node_removed(LassiConnection *lc, DBusMessage *m) {
+ const char *id, *address;
+ DBusError e;
+ LassiConnection *k;
+ gboolean remove_from_order;
+ LassiServer *ls;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e,
+ DBUS_TYPE_STRING, &id,
+ DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_BOOLEAN, &remove_from_order,
+ DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ if (strcmp(id, lc->server->id) == 0) {
+ g_debug("We've been kicked ourselves.");
+
+ server_disconnect_all(lc->server, TRUE);
+ return 0;
+ }
+
+ if (remove_from_order) {
+ GList *i = g_list_find_custom(ls->order, id, (GCompareFunc) strcmp);
+
+ if (i)
+ ls->order = g_list_delete_link(ls->order, i);
+ }
+
+ ls = lc->server;
+
+ if ((k = g_hash_table_lookup(lc->server->connections_by_id, id)))
+ connection_unlink(k, remove_from_order);
+
+ server_broadcast(ls, m, lc == k ? NULL : lc);
+
+ return 0;
+}
+
+static int signal_update_grab(LassiConnection *lc, DBusMessage *m) {
+ const char*id, *current_id;
+ gint32 generation;
+ LassiConnection *k = NULL;
+ DBusError e;
+ int y;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(
+ m, &e,
+ DBUS_TYPE_INT32, &generation,
+ DBUS_TYPE_STRING, &id,
+ DBUS_TYPE_INT32, &y,
+ DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ g_debug("received grab request for %s (%i vs %i)", id, lc->server->active_generation, generation);
+
+ if (strcmp(id, lc->server->id) && !(k = g_hash_table_lookup(lc->server->connections_by_id, id))) {
+ g_debug("Unknown connection");
+ return -1;
+ }
+
+ if (k == lc->server->active_connection) {
+ g_debug("Connection already active");
+ return 0;
+ }
+
+ current_id = lc->server->active_connection ? lc->server->active_connection->id : lc->server->id;
+
+ if ((lc->server->active_generation > generation || (lc->server->active_generation == generation && strcmp(current_id, id) > 0))) {
+ g_debug("Ignoring request for active connection");
+ return 0;
+ }
+
+ lc->server->active_connection = k;
+ lc->server->active_generation = generation;
+
+ if (!k)
+ g_debug("We're now the active server.");
+ else
+ g_debug("Connection '%s' activated.", k->id);
+
+ server_broadcast(lc->server, m, lc);
+ server_layout_changed(lc->server, y);
+
+ return 0;
+}
+
+static int signal_update_order(LassiConnection *lc, DBusMessage *m) {
+ gint32 generation;
+ DBusError e;
+ DBusMessageIter iter, sub;
+ GList *new_order = NULL, *merged_order = NULL;
+ int r = 0;
+ int c = 0;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(
+ m, &e,
+ DBUS_TYPE_INT32, &generation,
+ DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ dbus_message_iter_init(m, &iter);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) {
+ g_debug("Bad connection list fo the left");
+ return -1;
+ }
+
+ if (lc->server->order_generation > generation) {
+ g_debug("Ignoring request for layout");
+ return 0;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ const char *id;
+ dbus_message_iter_get_basic(&sub, &id);
+ new_order = g_list_prepend(new_order, g_strdup(id));
+ dbus_message_iter_next(&sub);
+ }
+
+ new_order = g_list_reverse(new_order);
+
+ if (!lassi_list_nodups(new_order)) {
+ g_debug("Received invalid list.");
+ r = -1;
+ goto finish;
+ }
+
+ c = lassi_list_compare(lc->server->order, new_order);
+
+ if (c == 0) {
+ g_debug("Requested order identical to ours.");
+ goto finish;
+ }
+
+ if (lc->server->order_generation == generation && c > 0) {
+ g_debug("Ignoring request for layout 2");
+ goto finish;
+ }
+
+ merged_order = lassi_list_merge(lassi_list_copy(new_order), lc->server->order);
+
+ if (lassi_list_compare(lc->server->order, merged_order)) {
+ server_set_order(lc->server, merged_order);
+ merged_order = NULL;
+ }
+
+ server_send_update_order(lc->server, lassi_list_compare(lc->server->order, new_order) ? NULL : lc);
+
+ lc->server->order_generation = generation;
+
+finish:
+
+ lassi_list_free(new_order);
+ lassi_list_free(merged_order);
+
+ if (lc->delayed_welcome) {
+ lc->delayed_welcome = FALSE;
+ show_welcome(lc, TRUE);
+ }
+
+ return r;
+}
+
+static int signal_key_event(LassiConnection *lc, DBusMessage *m) {
+ DBusError e;
+ guint32 key;
+ gboolean is_press;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_UINT32, &key, DBUS_TYPE_BOOLEAN, &is_press, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+/* g_debug("got dbus key %i %i", key, !!is_press); */
+ lassi_grab_press_key(&lc->server->grab_info, key, is_press);
+
+ return 0;
+}
+
+static int signal_motion_event(LassiConnection *lc, DBusMessage *m) {
+ DBusError e;
+ int dx, dy;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_INT32, &dx, DBUS_TYPE_INT32, &dy, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+/* g_debug("got dbus motion %i %i", dx, dy); */
+ lassi_grab_move_pointer_relative(&lc->server->grab_info, dx, dy);
+
+ return 0;
+}
+
+static int signal_button_event(LassiConnection *lc, DBusMessage *m) {
+ DBusError e;
+ guint32 button;
+ gboolean is_press;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_UINT32, &button, DBUS_TYPE_BOOLEAN, &is_press, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+/* g_debug("got dbus button %i %i", button, !!is_press); */
+ lassi_grab_press_button(&lc->server->grab_info, button, is_press);
+
+ return 0;
+}
+
+static int signal_acquire_clipboard(LassiConnection *lc, DBusMessage *m) {
+ DBusError e;
+ gint32 g;
+ gboolean primary;
+ DBusMessageIter iter, sub;
+ char **targets;
+ unsigned alloc_targets, j;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_INT32, &g, DBUS_TYPE_BOOLEAN, &primary, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ if ((primary ? lc->server->primary_generation : lc->server->clipboard_generation) > g) {
+ g_debug("Ignoring request for clipboard.");
+ return 0;
+ }
+
+ /* FIXME, tie break missing */
+
+ dbus_message_iter_init(m, &iter);
+ dbus_message_iter_next(&iter);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) {
+ g_debug("Bad target list");
+ return -1;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ targets = g_new(char*, alloc_targets = 20);
+ j = 0;
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ const char *t;
+ dbus_message_iter_get_basic(&sub, &t);
+
+ if (j >= alloc_targets) {
+ alloc_targets *= 2;
+ targets = g_realloc(targets, sizeof(char*) * (alloc_targets+1));
+ }
+
+ g_assert(j < alloc_targets);
+
+ targets[j++] = (char*) t;
+ dbus_message_iter_next(&sub);
+
+ g_debug("Received target %s on %s", t, lc->id);
+ }
+
+ targets[j] = NULL;
+
+ lassi_clipboard_set(&lc->server->clipboard_info, primary, targets);
+
+ g_free(targets);
+
+ if (primary) {
+ lc->server->primary_connection = lc;
+ lc->server->primary_empty = FALSE;
+ lc->server->primary_generation = g;
+ } else {
+ lc->server->clipboard_connection = lc;
+ lc->server->clipboard_empty = FALSE;
+ lc->server->clipboard_generation = g;
+ }
+
+ return 0;
+}
+
+static int signal_return_clipboard(LassiConnection *lc, DBusMessage *m) {
+ DBusError e;
+ gint32 g;
+ gboolean primary;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_INT32, &g, DBUS_TYPE_BOOLEAN, &primary, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ if ((primary ? lc->server->primary_generation : lc->server->clipboard_generation) > g) {
+ g_debug("Ignoring request for clipboard empty.");
+ return 0;
+ }
+
+ /* FIXME, tie break missing */
+
+ lassi_clipboard_clear(&lc->server->clipboard_info, primary);
+
+ if (primary) {
+ lc->server->primary_connection = NULL;
+ lc->server->primary_empty = TRUE;
+ lc->server->primary_generation = g;
+ } else {
+ lc->server->clipboard_connection = NULL;
+ lc->server->clipboard_empty = TRUE;
+ lc->server->clipboard_generation = g;
+ }
+
+ return 0;
+}
+
+static int method_get_clipboard(LassiConnection *lc, DBusMessage *m) {
+ DBusError e;
+ char *type;
+ gboolean primary;
+ DBusMessage *n = NULL;
+ gint32 f;
+ gpointer p = NULL;
+ int l = 0;
+ DBusMessageIter iter, sub;
+ gboolean b;
+
+ dbus_error_init(&e);
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_BOOLEAN, &primary, DBUS_TYPE_STRING, &type, DBUS_TYPE_INVALID))) {
+ g_debug("Received invalid message: %s", e.message);
+ dbus_error_free(&e);
+ return -1;
+ }
+
+ if ((primary && (lc->server->primary_connection || lc->server->primary_empty)) ||
+ (!primary && (lc->server->clipboard_connection || lc->server->clipboard_empty))) {
+ n = dbus_message_new_error(m, LASSI_INTERFACE ".NotOwner", "We're not the clipboard owner");
+ goto finish;
+ }
+
+ if (lassi_clipboard_get(&lc->server->clipboard_info, primary, type, &f, &p, &l) < 0) {
+ n = dbus_message_new_error(m, LASSI_INTERFACE ".ClipboardFailure", "Failed to read clipboard data");
+ goto finish;
+ }
+
+ if (l > dbus_connection_get_max_message_size(lc->dbus_connection)*9/10) {
+ n = dbus_message_new_error(m, LASSI_INTERFACE ".TooLarge", "Clipboard data too large");
+ goto finish;
+ }
+
+ n = dbus_message_new_method_return(m);
+ g_assert(n);
+
+ dbus_message_iter_init_append(n, &iter);
+ b = dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &f);
+ g_assert(b);
+
+ b = dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &sub);
+ g_assert(b);
+
+ b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &p, l);
+ g_assert(b);
+
+ b = dbus_message_iter_close_container(&iter, &sub);
+ g_assert(b);
+
+finish:
+ g_assert(n);
+
+ dbus_connection_send(lc->dbus_connection, n, NULL);
+ dbus_message_unref(n);
+
+ g_free(p);
+
+ return 0;
+}
+
+DBusHandlerResult message_function(DBusConnection *c, DBusMessage *m, void *userdata) {
+ DBusError e;
+ LassiConnection *lc = userdata;
+
+ g_assert(c);
+ g_assert(m);
+ g_assert(lc);
+
+ dbus_error_init(&e);
+
+/* g_debug("[%s] interface=%s, path=%s, member=%s serial=%u", */
+/* lc->id, */
+/* dbus_message_get_interface(m), */
+/* dbus_message_get_path(m), */
+/* dbus_message_get_member(m), */
+/* dbus_message_get_serial(m)); */
+
+ if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected"))
+ goto fail;
+
+ else if (dbus_message_is_signal(m, LASSI_INTERFACE, "Hello")) {
+ if (signal_hello(lc, m) < 0)
+ goto fail;
+
+ } else if (lc->id) {
+
+ if (dbus_message_is_signal(m, LASSI_INTERFACE, "NodeAdded")) {
+
+ if (signal_node_added(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "NodeRemoved")) {
+
+ if (signal_node_removed(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "UpdateGrab")) {
+
+ if (signal_update_grab(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "UpdateOrder")) {
+
+ if (signal_update_order(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "KeyEvent")) {
+
+ if (signal_key_event(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "MotionEvent")) {
+
+ if (signal_motion_event(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "ButtonEvent")) {
+
+ if (signal_button_event(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "AcquireClipboard")) {
+
+ if (signal_acquire_clipboard(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_signal(m, LASSI_INTERFACE, "ReturnClipboard")) {
+
+ if (signal_return_clipboard(lc, m) < 0)
+ goto fail;
+
+ } else if (dbus_message_is_method_call(m, LASSI_INTERFACE, "GetClipboard")) {
+
+ if (method_get_clipboard(lc, m) < 0)
+ goto fail;
+
+ } else
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ } else
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+fail:
+
+ dbus_error_free(&e);
+
+ connection_unlink(lc, TRUE);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static LassiConnection* connection_add(LassiServer *ls, DBusConnection *c, gboolean we_are_client) {
+ LassiConnection *lc;
+ dbus_bool_t b;
+ DBusMessage *m;
+ gint32 ag, og, cg;
+ int fd, one = 1;
+
+ g_assert(ls);
+ g_assert(c);
+
+ lc = g_new(LassiConnection, 1);
+ lc->dbus_connection = dbus_connection_ref(c);
+ lc->server = ls;
+ lc->id = lc->address = NULL;
+ lc->we_are_client = we_are_client;
+ lc->delayed_welcome = FALSE;
+ ls->connections = g_list_prepend(ls->connections, lc);
+ ls->n_connections++;
+
+ dbus_connection_setup_with_g_main(c, NULL);
+
+ b = dbus_connection_add_filter(c, message_function, lc, NULL);
+ g_assert(b);
+
+ m = dbus_message_new_signal("/", LASSI_INTERFACE, "Hello");
+ g_assert(m);
+
+ ag = ls->active_generation;
+ og = ls->order_generation;
+ cg = ls->clipboard_generation;
+
+ b = dbus_message_append_args(
+ m,
+ DBUS_TYPE_STRING, &ls->id,
+ DBUS_TYPE_STRING, &ls->address,
+ DBUS_TYPE_INT32, &ag,
+ DBUS_TYPE_INT32, &og,
+ DBUS_TYPE_INT32, &cg,
+ DBUS_TYPE_INVALID);
+ g_assert(b);
+
+ fd = -1;
+ dbus_connection_get_socket(c, &fd);
+ g_assert(fd >= 0);
+
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0)
+ g_warning("Failed to enable TCP_NODELAY");
+
+ b = dbus_connection_send(c, m, NULL);
+ g_assert(b);
+
+ dbus_message_unref(m);
+
+ lassi_tray_update(&ls->tray_info, ls->n_connections);
+ return lc;
+}
+
+static void new_connection(DBusServer *s, DBusConnection *c, void *userdata) {
+ LassiServer *ls = userdata;
+
+ g_assert(s);
+ g_assert(c);
+
+ if (ls->n_connections >= CONNECTIONS_MAX)
+ return;
+
+ dbus_connection_set_allow_anonymous(c, TRUE);
+ connection_add(ls, c, FALSE);
+}
+
+static int server_init(LassiServer *ls) {
+ DBusError e;
+ int r = -1;
+ guint16 port;
+
+ g_assert(ls);
+
+ memset(ls, 0, sizeof(*ls));
+
+ dbus_error_init(&e);
+
+ for (port = PORT_MIN; port < PORT_MAX; port++) {
+ char *t;
+
+ t = g_strdup_printf("tcp:port=%u,host=0.0.0.0", port);
+ ls->dbus_server = dbus_server_listen(t, &e);
+ g_free(t);
+
+ if (ls->dbus_server) {
+ ls->port = port;
+ break;
+ }
+
+ if (!dbus_error_has_name(&e, DBUS_ERROR_ADDRESS_IN_USE)) {
+ g_warning("Failed to create D-Bus server: %s %s", e.message, e.name);
+ goto finish;
+ }
+
+ dbus_error_free(&e);
+ }
+
+ if (!ls->dbus_server) {
+ g_warning("All ports blocked.");
+ goto finish;
+ }
+
+ g_debug("Listening on port %u", port);
+
+ dbus_server_setup_with_g_main(ls->dbus_server, NULL);
+ dbus_server_set_new_connection_function(ls->dbus_server, new_connection, ls, NULL);
+
+ ls->connections_by_id = g_hash_table_new(g_str_hash, g_str_equal);
+
+ ls->id = g_strdup_printf("%s's desktop on %s", g_get_user_name(), g_get_host_name());
+
+ if (lassi_avahi_init(&ls->avahi_info, ls) < 0)
+ goto finish;
+
+ /* The initialization of Avahi might have changed ls->id! */
+
+ ls->address = dbus_server_get_address(ls->dbus_server);
+ ls->order = g_list_prepend(NULL, g_strdup(ls->id));
+
+ if (lassi_grab_init(&ls->grab_info, ls) < 0)
+ goto finish;
+
+ if (lassi_osd_init(&ls->osd_info) < 0)
+ goto finish;
+
+ if (lassi_clipboard_init(&ls->clipboard_info, ls) < 0)
+ goto finish;
+
+
+ if (lassi_tray_init(&ls->tray_info, ls) < 0)
+ goto finish;
+
+ if (lassi_prefs_init(&ls->prefs_info, ls) < 0)
+ goto finish;
+
+ r = 0;
+
+finish:
+ dbus_error_free(&e);
+ return r;
+}
+
+void lassi_server_disconnect(LassiServer *ls, const char *id, gboolean remove_from_order) {
+ LassiConnection *lc;
+
+ g_assert(ls);
+ g_assert(id);
+
+ if ((lc = g_hash_table_lookup(ls->connections_by_id, id)))
+ connection_unlink(lc, remove_from_order);
+ else if (remove_from_order) {
+ GList *i = g_list_find_custom(ls->order, id, (GCompareFunc) strcmp);
+
+ if (i)
+ ls->order = g_list_delete_link(ls->order, i);
+ }
+}
+
+static void server_disconnect_all(LassiServer *ls, gboolean clear_order) {
+
+ while (ls->connections)
+ connection_unlink(ls->connections->data, clear_order);
+
+ if (clear_order) {
+ lassi_list_free(ls->order);
+ ls->order = NULL;
+ }
+}
+
+static void server_done(LassiServer *ls) {
+
+ g_assert(ls);
+
+ if (ls->dbus_server) {
+ dbus_server_disconnect(ls->dbus_server);
+ dbus_server_unref(ls->dbus_server);
+ }
+
+ server_disconnect_all(ls, FALSE);
+
+ if (ls->connections_by_id)
+ g_hash_table_destroy(ls->connections_by_id);
+
+ g_free(ls->id);
+ g_free(ls->address);
+
+ lassi_list_free(ls->order);
+
+ lassi_grab_done(&ls->grab_info);
+ lassi_osd_done(&ls->osd_info);
+ lassi_clipboard_done(&ls->clipboard_info);
+ lassi_avahi_done(&ls->avahi_info);
+ lassi_tray_done(&ls->tray_info);
+ lassi_prefs_done(&ls->prefs_info);
+
+ memset(ls, 0, sizeof(*ls));
+}
+
+gboolean lassi_server_is_connected(LassiServer *ls, const char *id) {
+ g_assert(ls);
+ g_assert(id);
+
+ return strcmp(id, ls->id) == 0 || g_hash_table_lookup(ls->connections_by_id, id);
+}
+
+gboolean lassi_server_is_known(LassiServer *ls, const char *id) {
+ g_assert(ls);
+ g_assert(id);
+
+ return !!g_list_find_custom(ls->order, id, (GCompareFunc) strcmp);
+}
+
+LassiConnection* lassi_server_connect(LassiServer *ls, const char *a) {
+ DBusError e;
+ DBusConnection *c;
+ LassiConnection *lc = NULL;
+
+ dbus_error_init(&e);
+
+ if (ls->n_connections >= CONNECTIONS_MAX)
+ goto finish;
+
+ if (!(c = dbus_connection_open_private(a, &e))) {
+ g_warning("Failed to connect to client: %s", e.message);
+ goto finish;
+ }
+
+ lc = connection_add(ls, c, TRUE);
+
+finish:
+
+ if (c)
+ dbus_connection_unref(c);
+
+ dbus_error_free(&e);
+ return lc;
+}
+
+int main(int argc, char *argv[]) {
+ LassiServer ls;
+
+ gtk_init(&argc, &argv);
+
+ memset(&ls, 0, sizeof(ls));
+
+ if (server_init(&ls) < 0)
+ goto fail;
+
+ gtk_main();
+
+fail:
+
+ server_done(&ls);
+
+ return 0;
+}
diff --git a/src/lassi-server.h b/src/lassi-server.h
new file mode 100644
index 0000000..55849d4
--- /dev/null
+++ b/src/lassi-server.h
@@ -0,0 +1,84 @@
+#ifndef foolassiserverhfoo
+#define foolassiserverhfoo
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+typedef struct LassiServer LassiServer;
+typedef struct LassiConnection LassiConnection;
+
+#include "lassi-grab.h"
+#include "lassi-osd.h"
+#include "lassi-clipboard.h"
+#include "lassi-avahi.h"
+#include "lassi-tray.h"
+#include "lassi-prefs.h"
+
+struct LassiServer {
+ DBusServer *dbus_server;
+
+ char *id, *address;
+ uint16_t port;
+
+ /* All connections */
+ GList *connections;
+ int n_connections;
+
+ /* Configured connections */
+ GHashTable *connections_by_id;
+ GList *connections_left, *connections_right; /* stored from right to left, resp, left to right */
+
+ /* Active display management */
+ int active_generation;
+ LassiConnection *active_connection;
+
+ /* Layout management */
+ int order_generation;
+ GList *order;
+
+ /* Clipboard CLIPBOARD management */
+ int clipboard_generation;
+ LassiConnection *clipboard_connection;
+ gboolean clipboard_empty;
+
+ /* Clipboard PRIMARY management */
+ int primary_generation;
+ LassiConnection *primary_connection;
+ gboolean primary_empty;
+
+ LassiGrabInfo grab_info;
+ LassiOsdInfo osd_info;
+ LassiClipboardInfo clipboard_info;
+ LassiAvahiInfo avahi_info;
+ LassiTrayInfo tray_info;
+ LassiPrefsInfo prefs_info;
+};
+
+struct LassiConnection {
+ LassiServer *server;
+
+ DBusConnection *dbus_connection;
+ char *id, *address;
+
+ gboolean we_are_client;
+ gboolean delayed_welcome;
+};
+
+int lassi_server_change_grab(LassiServer *s, gboolean to_left, int y);
+int lassi_server_acquire_grab(LassiServer *s);
+
+int lassi_server_motion_event(LassiServer *s, int dx, int dy);
+int lassi_server_button_event(LassiServer *ls, unsigned button, gboolean is_press);
+int lassi_server_key_event(LassiServer *ls, unsigned key, gboolean is_press);
+
+int lassi_server_acquire_clipboard(LassiServer *ls, gboolean primary, char**targets);
+int lassi_server_return_clipboard(LassiServer *ls, gboolean primary);
+int lassi_server_get_clipboard(LassiServer *ls, gboolean primary, const char *t, int *f, gpointer *p, int *l);
+
+LassiConnection* lassi_server_connect(LassiServer *ls, const char *a);
+void lassi_server_disconnect(LassiServer *ls, const char *id, gboolean remove_from_order);
+
+gboolean lassi_server_is_connected(LassiServer *ls, const char *id);
+gboolean lassi_server_is_known(LassiServer *ls, const char *id);
+
+#endif
diff --git a/src/lassi-tray.c b/src/lassi-tray.c
new file mode 100644
index 0000000..72fc310
--- /dev/null
+++ b/src/lassi-tray.c
@@ -0,0 +1,100 @@
+#include <gtk/gtk.h>
+
+#include <libnotify/notify.h>
+
+#include <string.h>
+
+#include "lassi-tray.h"
+#include "lassi-server.h"
+
+#define ICON_IDLE "network-wired"
+#define ICON_BUSY "network-workgroup"
+
+static void on_prefs_activate(GtkMenuItem *menuitem, LassiTrayInfo *i) {
+ lassi_prefs_show(&i->server->prefs_info);
+}
+
+static void on_tray_activate(GtkStatusIcon *status_icon, LassiTrayInfo *i) {
+ gtk_menu_popup(GTK_MENU(i->menu), NULL, NULL, gtk_status_icon_position_menu, i->status_icon, 0, gtk_get_current_event_time());
+}
+
+static void on_tray_popup_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, LassiTrayInfo *i) {
+ on_tray_activate(status_icon, i);
+}
+
+int lassi_tray_init(LassiTrayInfo *i, LassiServer *server) {
+ GtkWidget *item;
+ g_assert(i);
+ g_assert(server);
+
+ memset(i, 0, sizeof(*i));
+ i->server = server;
+
+ notify_init("Mango Lassi");
+
+ i->status_icon = gtk_status_icon_new_from_icon_name(ICON_IDLE);
+
+ i->menu = gtk_menu_new();
+ item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(on_prefs_activate), i);
+ gtk_menu_shell_append(GTK_MENU_SHELL(i->menu), item);
+ item = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(i->menu), item);
+ item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_main_quit), NULL);
+ gtk_menu_shell_append(GTK_MENU_SHELL(i->menu), item);
+ gtk_widget_show_all(i->menu);
+
+ g_signal_connect(G_OBJECT(i->status_icon), "popup_menu", G_CALLBACK(on_tray_popup_menu), i);
+ g_signal_connect(G_OBJECT(i->status_icon), "activate", G_CALLBACK(on_tray_activate), i);
+
+ lassi_tray_update(i, 0);
+
+ return 0;
+}
+
+void lassi_tray_update(LassiTrayInfo *i, int n_connected) {
+ char *t;
+ g_assert(i);
+
+ gtk_status_icon_set_from_icon_name(i->status_icon, n_connected > 0 ? ICON_BUSY : ICON_IDLE);
+
+ if (n_connected == 0)
+ t = g_strdup("No desktops connected.");
+ else if (n_connected == 1)
+ t = g_strdup("1 desktop connected.");
+ else
+ t = g_strdup_printf("%i desktops connected.", n_connected);
+
+ gtk_status_icon_set_tooltip(i->status_icon, t);
+
+ g_free(t);
+}
+
+void lassi_tray_show_notification(LassiTrayInfo *i, char *summary, char *body, LassiTrayNotificationIcon icon) {
+
+ static const char * const icon_name[] = {
+ [LASSI_TRAY_NOTIFICATION_WELCOME] = "user-desktop",
+ [LASSI_TRAY_NOTIFICATION_LEFT] = "go-previous",
+ [LASSI_TRAY_NOTIFICATION_RIGHT] = "go-next"
+ };
+
+ NotifyNotification *n;
+
+ n = notify_notification_new_with_status_icon(summary, body, icon_name[icon], i->status_icon);
+ notify_notification_set_timeout(n, 10000);
+ notify_notification_set_urgency(n, NOTIFY_URGENCY_LOW);
+ notify_notification_set_category(n, "network");
+ notify_notification_show(n, NULL);
+
+}
+
+void lassi_tray_done(LassiTrayInfo *i) {
+ g_assert(i);
+
+ g_object_unref(G_OBJECT(i->status_icon));
+
+ notify_uninit();
+
+ memset(i, 0, sizeof(*i));
+}
diff --git a/src/lassi-tray.h b/src/lassi-tray.h
new file mode 100644
index 0000000..d316092
--- /dev/null
+++ b/src/lassi-tray.h
@@ -0,0 +1,32 @@
+#ifndef foolassitrayhfoo
+#define foolassitrayhfoo
+
+#include <gtk/gtk.h>
+#include <libnotify/notification.h>
+
+typedef struct LassiTrayInfo LassiTrayInfo;
+struct LassiServer;
+
+typedef enum LassiTrayNotificationIcon {
+ LASSI_TRAY_NOTIFICATION_WELCOME,
+ LASSI_TRAY_NOTIFICATION_LEFT,
+ LASSI_TRAY_NOTIFICATION_RIGHT
+} LassiTrayNotificationIcon;
+
+struct LassiTrayInfo {
+ struct LassiServer *server;
+
+ GtkStatusIcon *status_icon;
+ GtkWidget *menu;
+};
+
+#include "lassi-server.h"
+
+int lassi_tray_init(LassiTrayInfo *i, LassiServer *server);
+void lassi_tray_done(LassiTrayInfo *i);
+void lassi_tray_update(LassiTrayInfo *i, int n_connected);
+
+void lassi_tray_show_notification(LassiTrayInfo *i, char *summary, char *body, LassiTrayNotificationIcon icon);
+
+
+#endif
diff --git a/src/mango-lassi.glade b/src/mango-lassi.glade
new file mode 100644
index 0000000..2be70a0
--- /dev/null
+++ b/src/mango-lassi.glade
@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.2.2 on Sun Sep 9 23:32:09 2007 by lennart@ecstasy-->
+<glade-interface>
+ <widget class="GtkDialog" id="preferences_dialog">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">8</property>
+ <property name="title" translatable="yes">Input Sharing</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="icon_name">user-desktop</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">4</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="GtkTreeView" id="tree_view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="headers_visible">False</property>
+ <property name="show_expanders">False</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkButton" id="add_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_add_button_clicked"/>
+ <child>
+ <widget class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">3</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock">gtk-add</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">_Add</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="up_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_up_button_clicked"/>
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">3</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock">gtk-go-up</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">_Up</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="down_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_down_button_clicked"/>
+ <child>
+ <widget class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">3</property>
+ <child>
+ <widget class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock">gtk-go-down</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">_Down</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="remove_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_remove_button_clicked"/>
+ <child>
+ <widget class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">3</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock">gtk-remove</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">4</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="xalign">1</property>
+ <property name="stock">gtk-dialog-info</property>
+ <property name="icon_size">6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;i&gt;Reorder the desktops you're already sharing mouse and keyboard with, or add new desktops to your session.
+Order the desktops in the list above from left to right how they are positioned on your desk.
+Please make sure to run Mango Lassi Input Sharing on all computers you want so share input with.&lt;/i&gt;</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="width_chars">50</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_close_button_clicked"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/src/x b/src/x
new file mode 100755
index 0000000..c3b6d95
--- /dev/null
+++ b/src/x
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+exec &> /dev/null
+
+Xephyr -ac :1 &
+disown
+
+DISPLAY=:1 fluxbox &
+disown
+
+Xephyr -ac :2 &
+disown
+
+DISPLAY=:2 fluxbox &
+disown
+
diff --git a/src/xinput.c b/src/xinput.c
new file mode 100644
index 0000000..66f0e3e
--- /dev/null
+++ b/src/xinput.c
@@ -0,0 +1,102 @@
+/************************************************************************
+ *
+ * File: xinput.c
+ *
+ * Sample program to access input devices other than the X pointer and
+ * keyboard using the Input Device extension to X.
+ * This program creates a window and selects input from it.
+ * To terminate this program, press button 1 on any device being accessed
+ * through the extension when the X pointer is in the test window.
+ *
+ * To compile this program, use
+ * "cc xinput.c -I/usr/include/X11R5 -L/usr/lib/X11R5
+ * -lXi -lXext -lX11 -o xinput
+ */
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XInput.h>
+#include "stdio.h"
+
+main()
+{
+ int i, j, count, ndevices, devcnt=0, devkeyp, devbutp;
+ Display *display;
+ Window my;
+ XEvent event;
+ XDeviceInfoPtr list, slist;
+ XInputClassInfo *ip;
+ XDeviceButtonEvent *b;
+ XEventClass class[128];
+ XDevice *dev, *opendevs[9];
+ XAnyClassPtr any;
+ XKeyInfoPtr K;
+
+ if ((display = XOpenDisplay ("")) == NULL)
+ {
+ printf ("No connection to server - Terminating.\n");
+ exit(1);
+ }
+ my = XCreateSimpleWindow (display, RootWindow(display,0), 100, 100,
+ 100, 100, 1, BlackPixel(display,0), WhitePixel(display,0));
+ XMapWindow (display, my);
+ XSync(display,0);
+
+ slist=list=(XDeviceInfoPtr) XListInputDevices (display, &ndevices);
+ for (i=0; i<ndevices; i++, list++)
+ {
+ any = (XAnyClassPtr) (list->inputclassinfo);
+ for (j=0; j<list->num_classes; j++)
+ {
+ if (any->class == KeyClass)
+ {
+ K = (XKeyInfoPtr) any;
+ printf ("device %s:\n",list->name);
+ printf ("num_keys=%d min_keycode=%d max_keycode=%d\n\n",
+ K->num_keys,K->min_keycode,K->max_keycode);
+ }
+ else if (any->class == ButtonClass)
+ printf ("device %s num_buttons=%d\n\n",list->name,
+ ((XButtonInfoPtr) any)->num_buttons);
+ /*
+ * Increment 'any' to point to the next item in the linked
+ * list. The length is in bytes, so 'any' must be cast to
+ * a character pointer before being incremented.
+ */
+ any = (XAnyClassPtr) ((char *) any + any->length);
+ }
+ if (1) //list->use != IsXKeyboard &&list->use != IsXPointer)
+ {
+ dev = XOpenDevice (display, list->id);
+ for (ip= dev->classes, j=0; j<dev->num_classes; j++, ip++)
+ if (ip->input_class == KeyClass)
+ {
+ /* This is a macro, the braces are necessary */
+ DeviceKeyPress (dev, devkeyp, class[count++]);
+ }
+ else if (ip->input_class == ButtonClass)
+ {
+ DeviceButtonPress (dev, devbutp,class[count++]);
+ }
+ opendevs[devcnt++]=dev;
+ }
+ }
+ XSelectExtensionEvent (display, my, class, count);
+ for (;;)
+ {
+ XNextEvent (display, &event);
+ if (event.type == devkeyp)
+ printf ("Device key press event device=%d\n",
+ ((XDeviceKeyEvent *) &event)->deviceid);
+ else if (event.type == devbutp)
+ {
+ b = (XDeviceButtonEvent * ) &event;
+ printf ("Device button press event device=%d button=%d\n",
+ b->deviceid, b->button);
+ if (b->button==1)
+ break;
+ }
+ }
+ for (i=0; i<devcnt; i++)
+ XCloseDevice (display, opendevs[i]);
+ XFreeDeviceList (slist);
+}