/* Xoo - a graphical wrapper around xnest * * Copyright 2004,2005 Matthew Allum, Openedhand Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include "fakedev.h" #include "callbacks.h" #include "prefs.h" #define XNEST_BIN "/usr/bin/Xephyr" /* Used by the signal handler to detect which child has died */ pid_t xnest_pid; static gboolean key_event (GtkWidget * widget, GdkEventKey * event, FakeApp * app); static void fakeapp_catch_sigchild (int sign); static char * find_spare_dpy (void) { int i; char buf[100]; for (i = 0; i < 256; i++) { g_snprintf (buf, sizeof(buf), "/tmp/.X%d-lock", i); if (access (buf, F_OK) != 0) { return g_strdup_printf (":%i", i); } } return NULL; } FakeApp * fakeapp_new (void) { GladeXML *glade; FakeApp *app = g_new0 (FakeApp, 1); app->xnest_dpy_name = find_spare_dpy (); app->xnest_bin_path = XNEST_BIN; app->xnest_bin_options = strdup ("-ac"); /* freed if changed */ app->key_rep_init_timeout.tv_usec = 0; app->key_rep_init_timeout.tv_sec = 1; app->key_rep_timeout.tv_usec = 40000; app->key_rep_timeout.tv_sec = 0; app->win_title = "Xoo"; glade = glade_xml_new (PKGDATADIR "/Xoo.glade", NULL, NULL); g_assert (glade != NULL); glade_xml_signal_connect_data (glade, "on_send_signal_activate", (GCallback) on_send_signal_activate, app); glade_xml_signal_connect_data (glade, "on_quit_activate", (GCallback) on_quit_activate, app); glade_xml_signal_connect_data (glade, "on_about_activate", (GCallback) on_about_activate, app); glade_xml_signal_connect_data (glade, "on_window_destroy", (GCallback) on_window_destroy, app); glade_xml_signal_connect_data (glade, "on_popup_menu_show", (GCallback) on_popup_menu_show, app); glade_xml_signal_connect_data (glade, "on_show_decorations_toggle", (GCallback) on_show_decorations_toggle, app); glade_xml_signal_connect_data (glade, "on_delete_event_hide", (GCallback) on_delete_event_hide, app); glade_xml_signal_connect_data (glade, "on_select_device_activate", (GCallback) on_select_device, app); #if HAVE_GCONF glade_xml_signal_connect_data (glade, "on_preferences_activate", (GCallback) on_preferences_activate, app); glade_xml_signal_connect_data (glade, "on_prefs_apply_clicked", (GCallback) on_prefs_apply_clicked, app); glade_xml_signal_connect_data (glade, "on_prefs_cancel_clicked", (GCallback) on_prefs_cancel_clicked, app); #else gtk_widget_hide (glade_xml_get_widget (glade, "preferences")); #endif app->window = glade_xml_get_widget (glade, "window"); app->menubar = glade_xml_get_widget (glade, "menubar"); app->fixed = glade_xml_get_widget (glade, "fixed"); app->back = glade_xml_get_widget (glade, "back"); app->winnest = glade_xml_get_widget (glade, "winnest"); g_signal_connect (app->window, "key-press-event", (GCallback) key_event, app); g_signal_connect (app->window, "key-release-event", (GCallback) key_event, app); app->prefs_window = glade_xml_get_widget (glade, "prefswindow"); gtk_window_set_transient_for (GTK_WINDOW (app->prefs_window), GTK_WINDOW (app->window)); app->entry_display = glade_xml_get_widget (glade, "entry_display"); app->entry_server = glade_xml_get_widget (glade, "entry_server"); app->entry_options = glade_xml_get_widget (glade, "entry_options"); app->entry_start = glade_xml_get_widget (glade, "entry_start"); app->debug_menu = glade_xml_get_widget (glade, "send_signal"); app->popupmenu = glade_xml_get_widget (glade, "popupmenu_menu"); app->about_window = glade_xml_get_widget (glade, "aboutwindow"); gtk_window_set_transient_for (GTK_WINDOW (app->about_window), GTK_WINDOW (app->window)); g_signal_connect_swapped (glade_xml_get_widget (glade, "button_about_close"), "clicked", G_CALLBACK (gtk_widget_hide), app->about_window); return app; } void fakeapp_create_gui (FakeApp * app) { GdkColor color; GdkGC *gc; FakeButton *button; /* Configure the main window title and size */ gtk_window_set_title (GTK_WINDOW (app->window), app->win_title); gtk_widget_set_size_request (app->fixed, app->device_width, app->device_height); /* Move and set the size of the window for the Xnest. */ gtk_widget_set_size_request (app->winnest, app->device_display_width, app->device_display_height); gtk_fixed_move (GTK_FIXED (app->fixed), app->winnest, app->device_display_x, app->device_display_y); /* Set the device image */ gtk_widget_set_size_request (app->back, app->device_width, app->device_height); gtk_fixed_move (GTK_FIXED (app->fixed), app->back, 0, 0); gtk_image_set_from_pixbuf (GTK_IMAGE (app->back), app->device_img); /* Now setup the buttons. Should do this in realize but we can hack that */ gtk_widget_realize (app->fixed); for (button = app->button_head; button; button = button->next) { GtkWidget *eventbox; button->normal_img = gdk_pixmap_new (app->fixed->window, button->width, button->height, -1); gdk_draw_pixbuf (GDK_DRAWABLE (button->normal_img), NULL, app->device_img, button->x, button->y, 0, 0, button->width, button->height, GDK_RGB_DITHER_NONE, 0, 0); button->image = gtk_image_new_from_pixmap (button->normal_img, NULL); gtk_widget_show (button->image); eventbox = gtk_event_box_new (); gtk_widget_show (eventbox); gtk_container_add (GTK_CONTAINER (eventbox), button->image); g_signal_connect (eventbox, "button-press-event", G_CALLBACK (button_press), button); g_signal_connect (eventbox, "button-release-event", G_CALLBACK (button_release), button); gtk_fixed_put (GTK_FIXED (app->fixed), eventbox, button->x, button->y); if (button->overlay) { button->active_img = gdk_pixmap_new (app->fixed->window, button->width, button->height, -1); g_assert (button->active_img); gc = gdk_gc_new (GDK_DRAWABLE (button->active_img)); gdk_draw_drawable (button->active_img, gc, button->normal_img, 0, 0, 0, 0, -1, -1); gdk_draw_pixbuf (button->active_img, gc, button->overlay, 0, 0, 0, 0, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); } else { button->active_img = gdk_pixmap_new (app->fixed->window, button->width, button->height, -1); gdk_draw_pixbuf (button->active_img, NULL, app->device_img, button->x, button->y, 0, 0, button->width, button->height, GDK_RGB_DITHER_NONE, 0, 0); gc = gdk_gc_new (GDK_DRAWABLE (button->active_img)); gdk_color_parse ("yellow", &color); gdk_gc_set_rgb_fg_color (gc, &color); gdk_draw_rectangle (GDK_DRAWABLE (button->active_img), gc, FALSE, 0, 0, button->width - 1, button->height - 1); g_object_unref (gc); } } gtk_widget_show (app->window); } static gboolean key_event (GtkWidget * widget, GdkEventKey * event, FakeApp * app) { if (app->xnest_window == 0) { g_warning ("Skipping event send, no window to send to"); } else { XEvent xevent; xevent.xkey.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease; xevent.xkey.window = GDK_WINDOW_XWINDOW (app->winnest->window); xevent.xkey.root = GDK_WINDOW_XWINDOW (gdk_screen_get_root_window (gdk_drawable_get_screen (app->winnest->window))); xevent.xkey.time = event->time; /* FIXME, the following might cause problems for non-GTK apps */ xevent.xkey.x = 0; xevent.xkey.y = 0; xevent.xkey.x_root = 0; xevent.xkey.y_root = 0; xevent.xkey.state = event->state; xevent.xkey.keycode = event->hardware_keycode; xevent.xkey.same_screen = TRUE; gdk_error_trap_push (); XSendEvent (GDK_WINDOW_XDISPLAY (app->winnest->window), app->xnest_window, False, NoEventMask, &xevent); gdk_display_sync (gtk_widget_get_display (widget)); if (gdk_error_trap_pop ()) { g_warning ("X error on XSendEvent"); } } return TRUE; } gboolean fakeapp_start_server (FakeApp * app) { int pid; gchar winid[32]; gchar exec_buf[2048]; gchar **exec_vector = NULL; g_snprintf (winid, 32, "%li", gdk_x11_drawable_get_xid (app->winnest->window)); g_snprintf (exec_buf, 2048, "%s %s %s -parent %s", "Xnest", app->xnest_dpy_name, app->xnest_bin_options, winid); /* Split the above up into something execv can digest */ exec_vector = g_strsplit (exec_buf, " ", 0); signal (SIGCHLD, fakeapp_catch_sigchild); pid = fork (); switch (pid) { case 0: execv (app->xnest_bin_path, exec_vector); g_warning ("Failed to Launch %s\n", app->xnest_bin_path); exit (1); case -1: g_warning ("Failed to Launch %s\n", app->xnest_bin_path); break; default: g_strfreev (exec_vector); app->xnest_pid = pid; xnest_pid = pid; } if (pid == -1 || !keys_init (app)) { g_warning ("'%s' Did not start correctly.", exec_buf); g_warning ("Please restart with working --xnest-bin-options, --xnest-bin, options."); exit (1); } /* Disable the debug signal if we are not running Xephyr */ if (strstr (app->xnest_bin_path, "Xephyr") == NULL) { gtk_widget_set_sensitive (app->debug_menu, FALSE); } else { gtk_widget_set_sensitive (app->debug_menu, TRUE); } if (app->start_cmd) { pid = fork (); switch (pid) { case 0: setenv ("DISPLAY", app->xnest_dpy_name, 1); execl ("/bin/sh", "sh", "-c", app->start_cmd, NULL); g_warning ("Failed to Launch %s\n", app->start_cmd); exit (1); case -1: g_warning ("Failed to Launch %s\n", app->start_cmd); break; default: break; } } return FALSE; } static void fakeapp_catch_sigchild (int sign) { pid_t this_pid; this_pid = waitpid (-1, 0, WNOHANG); if (this_pid != xnest_pid) return; gtk_main_quit (); } gboolean fakeapp_restart_server (FakeApp * app) { if (app->xnest_pid > 0) { XCloseDisplay (app->xnest_dpy); signal (SIGCHLD, SIG_DFL); /* start_server() will reset this */ kill (app->xnest_pid, SIGTERM); } sleep (1); /* give server a chance to quit */ return fakeapp_start_server (app); } void usage (char *progname) { fprintf (stderr, "%s " VERSION " usage:\n" "--xnest-dpy, -xd Display String for Xnest to use. ( default ':1')\n" "--xnest-bin, -xn Location of Xnest binary ( default " XNEST_BIN ")\n" "--xnest-bin-options -xo Command line opts to pass to server ( Default '-ac' )\n" "--title, -t Set the window title\n" "--device, -d Device config file to use\n" "--help, -h Show this help\n", progname); exit (1); } int main (int argc, char **argv) { FakeApp *app; char *device = PKGDATADIR "/ipaq3800.xml"; int i; gtk_init (&argc, &argv); /* TODO: use popt */ app = fakeapp_new (); app->argv = argv; app->argc = argc; #ifdef HAVE_GCONF /* Do this here so that command line argument override the GConf prefs */ gconf_prefs_init (app); #endif for (i = 1; i < argc; i++) { if (!strcmp ("--xnest-dpy", argv[i]) || !strcmp ("-xd", argv[i])) { if (++i >= argc) usage (argv[0]); app->xnest_dpy_name = argv[i]; continue; } if (!strcmp ("--xnest-bin", argv[i]) || !strcmp ("-xn", argv[i])) { if (++i >= argc) usage (argv[0]); app->xnest_bin_path = argv[i]; continue; } if (!strcmp ("--xnest-bin-options", argv[i]) || !strcmp ("-xo", argv[i])) { if (++i >= argc) usage (argv[0]); app->xnest_bin_options = argv[i]; continue; } if (!strcmp ("--device", argv[i]) || !strcmp ("-d", argv[i])) { if (++i >= argc) usage (argv[0]); device = argv[i]; continue; } if (!strcmp ("--title", argv[i]) || !strcmp ("-t", argv[i])) { if (++i >= argc) usage (argv[0]); app->win_title = argv[i]; continue; } if (!strcmp ("--help", argv[i]) || !strcmp ("-h", argv[i])) { usage (argv[0]); } usage (argv[0]); } config_init (app, device); fakeapp_create_gui (app); g_idle_add ((GSourceFunc) fakeapp_start_server, app); gtk_main (); return 0; }