From 31f26c2915caa6320af74ba73ebf71491ab80fa3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 21 Oct 2007 17:54:45 +0200 Subject: Basic autoconfization --- src/lassi-avahi.c | 255 ++++++++ src/lassi-avahi.h | 31 + src/lassi-clipboard.c | 174 ++++++ src/lassi-clipboard.h | 24 + src/lassi-grab.c | 407 +++++++++++++ src/lassi-grab.h | 40 ++ src/lassi-order.c | 147 +++++ src/lassi-order.h | 13 + src/lassi-osd.c | 144 +++++ src/lassi-osd.h | 18 + src/lassi-prefs.c | 212 +++++++ src/lassi-prefs.h | 29 + src/lassi-server.c | 1554 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lassi-server.h | 84 +++ src/lassi-tray.c | 100 ++++ src/lassi-tray.h | 32 + src/mango-lassi.glade | 300 ++++++++++ src/x | 16 + src/xinput.c | 102 ++++ 19 files changed, 3682 insertions(+) create mode 100644 src/lassi-avahi.c create mode 100644 src/lassi-avahi.h create mode 100644 src/lassi-clipboard.c create mode 100644 src/lassi-clipboard.h create mode 100644 src/lassi-grab.c create mode 100644 src/lassi-grab.h create mode 100644 src/lassi-order.c create mode 100644 src/lassi-order.h create mode 100644 src/lassi-osd.c create mode 100644 src/lassi-osd.h create mode 100644 src/lassi-prefs.c create mode 100644 src/lassi-prefs.h create mode 100644 src/lassi-server.c create mode 100644 src/lassi-server.h create mode 100644 src/lassi-tray.c create mode 100644 src/lassi-tray.h create mode 100644 src/mango-lassi.glade create mode 100755 src/x create mode 100644 src/xinput.c (limited to 'src') 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 + +#include +#include +#include +#include + +#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 +#include +#include +#include + +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 +#include + +#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 + +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 +#include + +#include +#include + +#include +#include + +#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 + +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 + +#include + +#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 + +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 +#include + +#include + +#include + +#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("%s", 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 + +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 + +#include + +#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 +#include + +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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 %s, which is located to the %s 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 %s which is located to the %s.", 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 %s which was located to the %s.", 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 +#include + +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 + +#include + +#include + +#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 +#include + +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 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 8 + Input Sharing + GTK_WIN_POS_CENTER_ON_PARENT + user-desktop + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 12 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 12 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + False + False + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 6 + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 6 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Add + True + + + GTK_PACK_END + 1 + + + + + + + False + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 6 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-up + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Up + True + + + GTK_PACK_END + 1 + + + + + + + False + 1 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-go-down + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Down + True + + + GTK_PACK_END + 1 + + + + + + + False + 2 + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 + 6 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + + + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Remove + True + + + False + GTK_PACK_END + 1 + + + + + + + False + 3 + + + + + False + 1 + + + + + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 4 + 6 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 1 + gtk-dialog-info + 6 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <i>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.</i> + True + GTK_JUSTIFY_CENTER + True + True + 50 + + + 1 + + + + + False + GTK_PACK_END + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-close + True + 0 + + + + + + False + GTK_PACK_END + + + + + + 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 +#include +#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; iinputclassinfo); + for (j=0; jnum_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; jnum_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