summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2007-08-22 03:34:09 +0200
committerLennart Poettering <lennart@poettering.net>2007-08-22 03:34:09 +0200
commita4084a94e8c612b33b24e55086304978a392ab0e (patch)
tree54435057c6503eb446dad3cd5319a7359eaaf72b
Initial commit
-rw-r--r--Makefile8
-rw-r--r--gnome-input-share.glade370
-rw-r--r--lassi-clipboard.c174
-rw-r--r--lassi-clipboard.h24
-rw-r--r--lassi-grab.c407
-rw-r--r--lassi-grab.h40
-rw-r--r--lassi-order.c147
-rw-r--r--lassi-order.h13
-rw-r--r--lassi-osd.c143
-rw-r--r--lassi-osd.h19
-rw-r--r--lassi-server.c1437
-rw-r--r--lassi-server.h70
-rw-r--r--lassi-xtest.c23
-rw-r--r--lassi-xtest.h6
-rw-r--r--xinput.c102
15 files changed, 2983 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c21ca57
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+CFLAGS=-Wall -Wextra -W -O0 -g -pipe -Wno-unused-parameter `pkg-config --cflags dbus-glib-1 glib-2.0 gtk+-2.0 xtst`
+LIBS=`pkg-config --libs dbus-glib-1 glib-2.0 gtk+-2.0 xtst`
+
+mango-lassi: lassi-server.o lassi-grab.o lassi-osd.o lassi-order.o lassi-clipboard.o *.h
+ $(CC) $^ -o $@ $(LIBS) $(CFLAGS)
+
+clean:
+ rm -f *.o mango-lassi
diff --git a/gnome-input-share.glade b/gnome-input-share.glade
new file mode 100644
index 0000000..b696493
--- /dev/null
+++ b/gnome-input-share.glade
@@ -0,0 +1,370 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.2.2 on Sat Aug 11 01:33:05 2007 by lennart@ecstasy-->
+<glade-interface>
+ <widget class="GtkDialog" id="dialog1">
+ <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">5</property>
+ <property name="title" translatable="yes">Gnome Input Share</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</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">3</property>
+ <child>
+ <widget class="GtkHPaned" id="hpaned1">
+ <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="border_width">5</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <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="right_padding">4</property>
+ <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">8</property>
+ <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="xalign">0</property>
+ <property name="label" translatable="yes">_Selected desktops:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <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="spacing">8</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <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="treeview2">
+ <property name="width_request">150</property>
+ <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="headers_clickable">True</property>
+ <property name="reorderable">True</property>
+ <property name="fixed_height_mode">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox3">
+ <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">8</property>
+ <child>
+ <widget class="GtkButton" id="button2">
+ <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>
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <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>
+ <child>
+ <widget class="GtkImage" id="image1">
+ <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-back</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAccelLabel" id="accellabel1">
+ <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>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button3">
+ <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>
+ <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>
+ <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-forward</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAccelLabel" id="accellabel3">
+ <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="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button4">
+ <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>
+ <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>
+ <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>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAccelLabel" id="accellabel4">
+ <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">Move _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="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button5">
+ <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>
+ <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>
+ <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-down</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAccelLabel" id="accellabel4">
+ <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">Move _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">3</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <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="left_padding">4</property>
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <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">8</property>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <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">_Available desktops:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <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="treeview1">
+ <property name="width_request">150</property>
+ <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="headers_clickable">True</property>
+ <property name="fixed_height_mode">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</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">&lt;i&gt;Please start Gnome Input Share on all desktops you want to share your mouse and keyboard with and then add them to the list of desktops on the left. Order them according their physical layout, from left to right.&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">0</property>
+ </widget>
+ <packing>
+ <property name="padding">8</property>
+ <property name="position">2</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="button1">
+ <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>
+ </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/lassi-clipboard.c b/lassi-clipboard.c
new file mode 100644
index 0000000..bdfeff4
--- /dev/null
+++ b/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/lassi-clipboard.h b/lassi-clipboard.h
new file mode 100644
index 0000000..f5574e3
--- /dev/null
+++ b/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/lassi-grab.c b/lassi-grab.c
new file mode 100644
index 0000000..4a2be87
--- /dev/null
+++ b/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/lassi-grab.h b/lassi-grab.h
new file mode 100644
index 0000000..a9366e0
--- /dev/null
+++ b/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/lassi-order.c b/lassi-order.c
new file mode 100644
index 0000000..ceeb3f1
--- /dev/null
+++ b/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/lassi-order.h b/lassi-order.h
new file mode 100644
index 0000000..e41a0d7
--- /dev/null
+++ b/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/lassi-osd.c b/lassi-osd.c
new file mode 100644
index 0000000..f5824a6
--- /dev/null
+++ b/lassi-osd.c
@@ -0,0 +1,143 @@
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <gdk/gdkx.h>
+
+#include <string.h>
+
+#include "lassi-osd.h"
+
+void 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);
+
+}
+
+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/lassi-osd.h b/lassi-osd.h
new file mode 100644
index 0000000..4f3fc1b
--- /dev/null
+++ b/lassi-osd.h
@@ -0,0 +1,19 @@
+#ifndef foolassiosdhfoo
+#define foolassiosdhfoo
+
+#include <gtk/gtk.h>
+
+typedef struct LassiOsdInfo LassiOsdInfo;
+
+struct LassiOsdInfo {
+ GtkWidget *window, *label, *left_icon, *right_icon;
+};
+
+void 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/lassi-server.c b/lassi-server.c
new file mode 100644
index 0000000..7a6f56c
--- /dev/null
+++ b/lassi-server.c
@@ -0,0 +1,1437 @@
+#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"
+
+#define LASSI_INTERFACE "org.gnome.MangoLassi"
+
+#define PORT_MIN 4000
+#define PORT_MAX 4050
+
+#define CONNECTIONS_MAX 16
+
+static void server_disconnect_all(LassiServer *ls);
+static LassiConnection* server_connect(LassiServer *ls, const char *a);
+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.", 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);
+}
+
+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_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 connection_unlink(LassiConnection *lc) {
+ DBusMessage *n;
+ dbus_bool_t b;
+ LassiServer *ls;
+ g_assert(lc);
+
+ g_debug("Unlinked %s (%s)", lc->id, lc->address);
+
+ ls = lc->server;
+
+ ls->connections = g_list_remove(ls->connections, lc);
+ ls->n_connections --;
+
+ if (lc->id) {
+ 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);
+
+ /* 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_INVALID);
+ g_assert(b);
+
+ server_broadcast(ls, n, NULL);
+ dbus_message_unref(n);
+
+ 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);
+ }
+
+ server_layout_changed(ls, -1);
+ server_dump(ls);
+ }
+
+ 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);
+ }
+
+ server_layout_changed(lc->server, -1);
+
+ 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 (!(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;
+ LassiServer *ls;
+
+ 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) {
+ g_debug("We've been kicked ourselves.");
+
+ server_disconnect_all(lc->server);
+ return 0;
+ }
+
+ if (!(k = g_hash_table_lookup(lc->server->connections_by_id, id)))
+ return 0;
+
+ ls = lc->server;
+ connection_unlink(k);
+
+ 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);
+
+ 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);
+
+ 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;
+ 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);
+
+ 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)
+ 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("%u", getpid());
+ ls->address = dbus_server_get_address(ls->dbus_server);
+ ls->order = g_list_prepend(NULL, g_strdup(ls->id));
+
+ lassi_grab_init(&ls->grab_info, ls);
+ lassi_osd_init(&ls->osd_info);
+ lassi_clipboard_init(&ls->clipboard_info, ls);
+
+ r = 0;
+
+finish:
+ dbus_error_free(&e);
+ return r;
+}
+
+static void server_disconnect_all(LassiServer *ls) {
+
+ while (ls->connections)
+ connection_unlink(ls->connections->data);
+}
+
+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);
+
+ 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);
+
+ memset(ls, 0, sizeof(*ls));
+}
+
+static LassiConnection* 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;
+
+ server_connect(&ls, "tcp:port=4000,host=lambda.local");
+ server_connect(&ls, "tcp:port=4001,host=lambda.local");
+ server_connect(&ls, "tcp:port=4002,host=lambda.local");
+ server_connect(&ls, "tcp:port=4003,host=lambda.local");
+ server_connect(&ls, "tcp:port=4000,host=ecstasy.local");
+ server_connect(&ls, "tcp:port=4001,host=ecstasy.local");
+ server_connect(&ls, "tcp:port=4002,host=ecstasy.local");
+ server_connect(&ls, "tcp:port=4003,host=ecstasy.local");
+ server_connect(&ls, "tcp:port=4000,host=127.0.0.1");
+ server_connect(&ls, "tcp:port=4001,host=127.0.0.1");
+ server_connect(&ls, "tcp:port=4002,host=127.0.0.1");
+ server_connect(&ls, "tcp:port=4003,host=127.0.0.1");
+
+ gtk_main();
+
+fail:
+
+ server_done(&ls);
+
+ return 0;
+}
diff --git a/lassi-server.h b/lassi-server.h
new file mode 100644
index 0000000..b8f7dc4
--- /dev/null
+++ b/lassi-server.h
@@ -0,0 +1,70 @@
+#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"
+
+struct LassiServer {
+ DBusServer *dbus_server;
+
+ char *id, *address;
+
+ /* 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;
+};
+
+struct LassiConnection {
+ LassiServer *server;
+
+ DBusConnection *dbus_connection;
+ char *id, *address;
+
+ gboolean we_are_client;
+};
+
+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);
+
+#endif
diff --git a/lassi-xtest.c b/lassi-xtest.c
new file mode 100644
index 0000000..680666c
--- /dev/null
+++ b/lassi-xtest.c
@@ -0,0 +1,23 @@
+#include <gdk/gdkx.h>
+#include <X11/extensions/XTest.h>
+#include <X11/extensions/xf86dga.h>
+
+
+int xtest_init(void) {
+ int event_base;
+ int error_base;
+ int major_version;
+ int minor_version;
+
+ if (!XTestQueryExtension(GDK_DISPLAY(), &event_base, &error_base, &major_version, &minor_version)) {
+ g_warning("XTest extension not supported.");
+ return -1;
+ }
+
+ g_debug("XTest %u.%u supported.", major_version, minor_version);
+
+ //if (!XDGAQueryExtension(GDK_DI
+
+
+ return 0;
+}
diff --git a/lassi-xtest.h b/lassi-xtest.h
new file mode 100644
index 0000000..f8d2bc0
--- /dev/null
+++ b/lassi-xtest.h
@@ -0,0 +1,6 @@
+#ifndef foolassixtesthfoo
+#define foolassixtesthfoo
+
+int xtest_init(void);
+
+#endif
diff --git a/xinput.c b/xinput.c
new file mode 100644
index 0000000..66f0e3e
--- /dev/null
+++ b/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);
+}