diff --git a/ChangeLog b/ChangeLog index 1e21463d6..1115f84f1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2002-04-13 Havoc Pennington + + * src/delete.c: new file containing all the + wacky mess I just added to a simple "click the close button", + contains all the dealing-with-dead-application cruft. + Use metacity-window-demo to test by clicking the + toolbar button that locks it up. + 2002-04-12 Havoc Pennington * src/tools/metacity-window-demo.c (do_appwindow): make one of the diff --git a/src/Makefile.am b/src/Makefile.am index d78f57250..85c9c14de 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,6 +7,7 @@ metacity_SOURCES= \ common.h \ core.c \ core.h \ + delete.c \ display.c \ display.h \ effects.c \ diff --git a/src/delete.c b/src/delete.c new file mode 100644 index 000000000..c7416c4da --- /dev/null +++ b/src/delete.c @@ -0,0 +1,492 @@ +/* Metacity window deletion */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include "util.h" +#include "window.h" +#include "errors.h" + +#include +#include +#include +#include +#include +#include + +static void meta_window_present_delete_dialog (MetaWindow *window); + +static void +delete_ping_reply_func (MetaDisplay *display, + Window xwindow, + void *user_data) +{ + MetaWindow *window = user_data; + + meta_topic (META_DEBUG_PING, + "Got reply to delete ping for %s\n", + window->desc); + + /* we do nothing */ +} + +static Window +window_from_string (const char *str) +{ + char *end; + unsigned long l; + + end = NULL; + + l = strtoul (str, &end, 16); + + if (end == NULL || end == str) + { + meta_warning (_("Could not parse \"%s\" as an integer"), + str); + return None; + } + + if (*end != '\0') + { + meta_warning (_("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return None; + } + + return l; +} + +static int +pid_from_string (const char *str) +{ + char *end; + long l; + + end = NULL; + + l = strtol (str, &end, 10); + + if (end == NULL || end == str) + { + meta_warning (_("Could not parse \"%s\" as an integer"), + str); + return None; + } + + if (*end != '\0') + { + meta_warning (_("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return None; + } + + return l; +} + +static gboolean +parse_dialog_output (const char *str, + int *pid_out, + Window *win_out) +{ + char **split; + + split = g_strsplit (str, "\n", 2); + if (split && split[0] && split[1]) + { + g_strchomp (split[0]); + g_strchomp (split[1]); + + *pid_out = pid_from_string (split[0]); + *win_out = window_from_string (split[1]); + + g_strfreev (split); + + return TRUE; + } + else + { + g_strfreev (split); + meta_warning (_("Failed to parse message \"%s\" from dialog process\n"), + str); + return FALSE; + } +} + +static void +search_and_destroy_window (int pid, + Window xwindow) +{ + /* Find the window with the given dialog PID, + * double check that it matches "xwindow", then + * kill the window. + */ + GSList *tmp; + gboolean found; + + if (xwindow == None) + { + meta_topic (META_DEBUG_PING, + "Window to destroy is None, doing nothing\n"); + return; + } + + found = FALSE; + tmp = meta_displays_list (); + while (tmp != NULL) + { + GSList *windows = meta_display_list_windows (tmp->data); + GSList *tmp2; + + tmp2 = windows; + while (tmp2 != NULL) + { + MetaWindow *w = tmp2->data; + + if (w->dialog_pid == pid) + { + if (w->xwindow != xwindow) + meta_topic (META_DEBUG_PING, + "Dialog pid matches but not xwindow (0x%lx vs. 0x%lx)\n", + w->xwindow, xwindow); + else + { + meta_window_kill (w); + found = TRUE; + } + } + + tmp2 = tmp2->next; + } + + g_slist_free (windows); + + tmp = tmp->next; + } + + if (!found) + meta_topic (META_DEBUG_PING, + "Did not find a window with dialog pid %d xwindow 0x%lx\n", + pid, xwindow); +} + +static void +release_window_with_fd (int fd) +{ + /* Find the window with the given dialog PID, + * double check that it matches "xwindow", then + * kill the window. + */ + GSList *tmp; + gboolean found; + + found = FALSE; + + tmp = meta_displays_list (); + while (tmp != NULL) + { + GSList *windows = meta_display_list_windows (tmp->data); + GSList *tmp2; + + tmp2 = windows; + while (tmp2 != NULL) + { + MetaWindow *w = tmp2->data; + + if (w->dialog_pid >= 0 && + w->dialog_pipe == fd) + { + meta_topic (META_DEBUG_PING, + "Removing dialog with fd %d pid %d from window %s\n", + fd, w->dialog_pid, w->desc); + meta_window_free_delete_dialog (w); + found = TRUE; + } + + tmp2 = tmp2->next; + } + + g_slist_free (windows); + + tmp = tmp->next; + } + + if (!found) + meta_topic (META_DEBUG_PING, + "Did not find a window with a dialog pipe %d\n", + fd); +} + +static gboolean +io_from_ping_dialog (GIOChannel *channel, + GIOCondition condition, + gpointer data) +{ + meta_topic (META_DEBUG_PING, + "IO handler from ping dialog, condition = %x\n", + condition); + + if (condition & G_IO_IN) + { + char *str; + int len; + GError *err; + + /* Go ahead and block for all data from child */ + str = NULL; + len = 0; + err = NULL; + g_io_channel_read_to_end (channel, + &str, &len, + &err); + + if (err) + { + meta_warning (_("Error reading from dialog display process: %s\n"), + err->message); + g_error_free (err); + } + + meta_topic (META_DEBUG_PING, + "Read %d bytes strlen %d \"%s\" from child\n", + len, strlen (str), str); + + if (len > 0) + { + /* We're supposed to kill the given window */ + int pid; + Window xwindow; + + if (parse_dialog_output (str, &pid, &xwindow)) + search_and_destroy_window (pid, xwindow); + } + + g_free (str); + } + + release_window_with_fd (g_io_channel_unix_get_fd (channel)); + + /* Remove the callback */ + return FALSE; +} + +static void +delete_ping_timeout_func (MetaDisplay *display, + Window xwindow, + void *user_data) +{ + MetaWindow *window = user_data; + GError *err; + int child_pid; + int outpipe; + char *argv[5]; + char *window_id_str; + GIOChannel *channel; + + meta_topic (META_DEBUG_PING, + "Got delete ping timeout for %s\n", + window->desc); + + if (window->dialog_pid >= 0) + { + meta_window_present_delete_dialog (window); + return; + } + + window_id_str = g_strdup_printf ("0x%lx", window->xwindow); + + argv[0] = METACITY_LIBEXECDIR"/metacity-dialog"; + argv[1] = "--kill-window-question"; + argv[2] = window->title; + argv[3] = window_id_str; + argv[4] = NULL; + + err = NULL; + if (!g_spawn_async_with_pipes ("/", + argv, + NULL, + 0, + NULL, NULL, + &child_pid, + NULL, + &outpipe, + NULL, + &err)) + { + meta_warning (_("Error launching metacity-dialog to ask about killing an application: %s\n"), + err->message); + g_error_free (err); + goto out; + } + + window->dialog_pid = child_pid; + window->dialog_pipe = outpipe; + + channel = g_io_channel_unix_new (window->dialog_pipe); + g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR, + io_from_ping_dialog, + NULL, NULL); + g_io_channel_unref (channel); + + out: + g_free (window_id_str); +} + +void +meta_window_delete (MetaWindow *window, + Time timestamp) +{ + meta_error_trap_push (window->display); + if (window->delete_window) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Deleting %s with delete_window request\n", + window->desc); + meta_window_send_icccm_message (window, + window->display->atom_wm_delete_window, + timestamp); + } + else + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Deleting %s with explicit kill\n", + window->desc); + XKillClient (window->display->xdisplay, window->xwindow); + } + meta_error_trap_pop (window->display); + + meta_display_ping_window (window->display, + window, + timestamp, + delete_ping_reply_func, + delete_ping_timeout_func, + window); + + if (window->has_focus) + { + /* This is unfortunately going to result in weirdness + * if the window doesn't respond to the delete event. + * I don't know how to avoid that though. + */ + meta_topic (META_DEBUG_FOCUS, + "Focusing top window because focus window %s was deleted/killed\n", + window->desc); + meta_screen_focus_top_window (window->screen, window); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Window %s was deleted/killed but didn't have focus\n", + window->desc); + } +} + + +void +meta_window_kill (MetaWindow *window) +{ + char buf[257]; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Killing %s brutally\n", + window->desc); + + if (window->wm_client_machine != NULL && + window->net_wm_pid > 0) + { + if (gethostname (buf, sizeof(buf)-1) == 0) + { + if (strcmp (buf, window->wm_client_machine) == 0) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Killing %s with kill()\n", + window->desc); + + if (kill (window->net_wm_pid, 9) < 0) + meta_topic (META_DEBUG_WINDOW_OPS, + "Failed to signal %s: %s\n", + window->desc, strerror (errno)); + } + } + else + { + meta_warning (_("Failed to get hostname: %s\n"), + strerror (errno)); + } + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Disconnecting %s with XKillClient()\n", + window->desc); + meta_error_trap_push (window->display); + XKillClient (window->display->xdisplay, window->xwindow); + meta_error_trap_pop (window->display); +} + +void +meta_window_free_delete_dialog (MetaWindow *window) +{ + if (window->dialog_pid >= 0) + { + kill (window->dialog_pid, 9); + close (window->dialog_pipe); + window->dialog_pid = -1; + window->dialog_pipe = -1; + } +} + +static void +meta_window_present_delete_dialog (MetaWindow *window) +{ + meta_topic (META_DEBUG_PING, + "Presenting existing ping dialog for %s\n", + window->desc); + + if (window->dialog_pid >= 0) + { + GSList *windows; + GSList *tmp; + + /* Activate transient for window that belongs to + * metacity-dialog + */ + + windows = meta_display_list_windows (window->display); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->xtransient_for == window->xwindow && + w->res_class && + g_strcasecmp (w->res_class, "metacity-dialog") == 0) + { + meta_window_activate (w, + meta_display_get_current_time (w->display)); + break; + } + + tmp = tmp->next; + } + + g_slist_free (windows); + } +} diff --git a/src/display.c b/src/display.c index 8f167c337..f8400d9ae 100644 --- a/src/display.c +++ b/src/display.c @@ -2409,7 +2409,7 @@ meta_set_syncing (gboolean setting) } } -#define PING_TIMEOUT_DELAY 3000 +#define PING_TIMEOUT_DELAY 2250 static gboolean meta_display_ping_timeout (gpointer data) diff --git a/src/metacity-dialog.c b/src/metacity-dialog.c index 538b845d5..1e9b2f722 100644 --- a/src/metacity-dialog.c +++ b/src/metacity-dialog.c @@ -23,13 +23,60 @@ #include #include #include +#include #include #define _(x) dgettext (GETTEXT_PACKAGE, x) #define N_(x) x +#include + +static Window +window_from_string (const char *str) +{ + char *end; + unsigned long l; + + end = NULL; + + l = strtoul (str, &end, 16); + + if (end == NULL || end == str) + { + g_printerr (_("Could not parse \"%s\" as an integer"), + str); + return None; + } + + if (*end != '\0') + { + g_printerr (_("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return None; + } + + return l; +} + +static void +on_realize (GtkWidget *dialog, + void *data) +{ + const char *parent_str = data; + Window xwindow; + + xwindow = window_from_string (parent_str); + + gdk_error_trap_push (); + XSetTransientForHint (gdk_display, GDK_WINDOW_XID (dialog->window), + xwindow); + XSync (gdk_display, False); + gdk_error_trap_pop (); +} + static int -kill_window_question (const char *window_name) +kill_window_question (const char *window_name, + const char *parent_str) { GtkWidget *dialog; @@ -37,7 +84,8 @@ kill_window_question (const char *window_name) GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("The window \"%s\" is not responding.\n" - "Force this application to exit?"), + "Force this application to exit?\n" + "(Any open documents will be lost.)"), window_name); gtk_dialog_add_buttons (GTK_DIALOG (dialog), @@ -46,14 +94,17 @@ kill_window_question (const char *window_name) _("Kill application"), GTK_RESPONSE_ACCEPT, NULL); - + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT); - /* return 0 if we should kill the application */ + g_signal_connect (G_OBJECT (dialog), "realize", + G_CALLBACK (on_realize), (char*) parent_str); + + /* return our PID, then window ID that should be killed */ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) - g_print ("Y"); + g_print ("%d\n%s\n", (int) getpid (), parent_str); else - g_print ("N"); + g_print ("%d\n0x0\n", (int) getpid ()); return 0; } @@ -75,13 +126,13 @@ main (int argc, char **argv) if (strcmp (argv[1], "--kill-window-question") == 0) { - if (argc < 3) + if (argc < 4) { g_printerr ("bad args to metacity-dialog\n"); return 1; } - return kill_window_question (argv[2]); + return kill_window_question (argv[2], argv[3]); } g_printerr ("bad args to metacity-dialog\n"); diff --git a/src/window.c b/src/window.c index a40af438f..dc80b94bd 100644 --- a/src/window.c +++ b/src/window.c @@ -134,7 +134,6 @@ void meta_window_flush_move_resize (MetaWindow *window); static void meta_window_apply_session_info (MetaWindow *window, const MetaWindowSessionInfo *info); - static const char* wm_state_to_string (int state) { @@ -160,6 +159,8 @@ meta_window_new (MetaDisplay *display, Window xwindow, GSList *tmp; MetaWorkspace *space; gulong existing_wm_state; + char *str; + unsigned long cardinal; meta_verbose ("Attempting to manage 0x%lx\n", xwindow); @@ -265,6 +266,9 @@ meta_window_new (MetaDisplay *display, Window xwindow, window = g_new (MetaWindow, 1); + window->dialog_pid = -1; + window->dialog_pipe = -1; + window->xwindow = xwindow; /* this is in window->screen->display, but that's too annoying to @@ -380,6 +384,9 @@ meta_window_new (MetaDisplay *display, Window xwindow, window->res_name = NULL; window->role = NULL; window->sm_client_id = NULL; + window->wm_client_machine = NULL; + + window->net_wm_pid = -1; window->xtransient_for = None; window->xgroup_leader = None; @@ -399,6 +406,32 @@ meta_window_new (MetaDisplay *display, Window xwindow, window->initial_workspace = 0; /* not used */ meta_display_register_x_window (display, &window->xwindow, window); + if (meta_prop_get_latin1_string (window->display, window->xwindow, + window->display->atom_wm_client_machine, + &str)) + { + window->wm_client_machine = g_strdup (str); + meta_XFree (str); + + meta_verbose ("Window has client machine \"%s\"\n", + window->wm_client_machine); + } + + if (meta_prop_get_cardinal (window->display, window->xwindow, + window->display->atom_net_wm_pid, + &cardinal)) + { + if (cardinal <= 0) + meta_warning (_("Application set a bogus _NET_WM_PID %ld\n"), + cardinal); + else + { + window->net_wm_pid = cardinal; + meta_verbose ("Window has _NET_WM_PID %d\n", + window->net_wm_pid); + } + } + update_size_hints (window); update_title (window); update_protocols (window); @@ -771,6 +804,7 @@ meta_window_free (MetaWindow *window) meta_window_unqueue_calc_showing (window); meta_window_unqueue_move_resize (window); + meta_window_free_delete_dialog (window); tmp = window->workspaces; while (tmp != NULL) @@ -846,6 +880,7 @@ meta_window_free (MetaWindow *window) meta_icon_cache_free (&window->icon_cache); g_free (window->sm_client_id); + g_free (window->wm_client_machine); g_free (window->role); g_free (window->res_class); g_free (window->res_name); @@ -2483,110 +2518,6 @@ meta_window_get_outer_rect (MetaWindow *window, *rect = window->rect; } -static void -delete_ping_reply_func (MetaDisplay *display, - Window xwindow, - void *user_data) -{ - MetaWindow *window = user_data; - - meta_topic (META_DEBUG_PING, - "Got reply to delete ping for %s\n", - window->desc); - - /* we do nothing */ -} - -static void -delete_ping_timeout_func (MetaDisplay *display, - Window xwindow, - void *user_data) -{ - MetaWindow *window = user_data; - GError *err; - int child_pid; - int outpipe; - char *argv[4]; - - meta_topic (META_DEBUG_PING, - "Got delete ping timeout for %s\n", - window->desc); - -#if 0 - argv[0] = METACITY_LIBEXECDIR"/metacity-dialog"; - argv[1] = "--kill-window-question"; - argv[2] = window->title; - argv[3] = NULL; - - err = NULL; - if (!g_spawn_async_with_pipes ("/", - argv, - NULL, - 0, - NULL, NULL, - &child_pid, - NULL, - &outpipe, - NULL, - &err)) - { - meta_warning (_("Error launching metacity-dialog to ask about killing an application: %s\n"), - err->message); - g_error_free (err); - return; - } -#endif -} - -void -meta_window_delete (MetaWindow *window, - Time timestamp) -{ - meta_error_trap_push (window->display); - if (window->delete_window) - { - meta_topic (META_DEBUG_WINDOW_OPS, - "Deleting %s with delete_window request\n", - window->desc); - meta_window_send_icccm_message (window, - window->display->atom_wm_delete_window, - timestamp); - } - else - { - meta_topic (META_DEBUG_WINDOW_OPS, - "Deleting %s with explicit kill\n", - window->desc); - XKillClient (window->display->xdisplay, window->xwindow); - } - meta_error_trap_pop (window->display); - - meta_display_ping_window (window->display, - window, - timestamp, - delete_ping_reply_func, - delete_ping_timeout_func, - window); - - if (window->has_focus) - { - /* This is unfortunately going to result in weirdness - * if the window doesn't respond to the delete event. - * I don't know how to avoid that though. - */ - meta_topic (META_DEBUG_FOCUS, - "Focusing top window because focus window %s was deleted/killed\n", - window->desc); - meta_screen_focus_top_window (window->screen, window); - } - else - { - meta_topic (META_DEBUG_FOCUS, - "Window %s was deleted/killed but didn't have focus\n", - window->desc); - } -} - void meta_window_focus (MetaWindow *window, Time timestamp) diff --git a/src/window.h b/src/window.h index 7a53f998d..019a4af96 100644 --- a/src/window.h +++ b/src/window.h @@ -66,13 +66,16 @@ struct _MetaWindow MetaWindowType type; Atom type_atom; - /* NOTE these four are not in UTF-8, we just treat them as random + /* NOTE these five are not in UTF-8, we just treat them as random * binary data */ char *res_class; char *res_name; char *role; char *sm_client_id; + char *wm_client_machine; + + int net_wm_pid; Window xtransient_for; Window xgroup_leader; @@ -236,6 +239,10 @@ struct _MetaWindow /* Managed by stack.c */ MetaStackLayer layer; MetaStackOp *stack_op; + + /* Current dialog open for this window */ + int dialog_pid; + int dialog_pipe; }; MetaWindow* meta_window_new (MetaDisplay *display, @@ -313,6 +320,7 @@ void meta_window_get_outer_rect (MetaWindow *window, MetaRectangle *rect); void meta_window_delete (MetaWindow *window, Time timestamp); +void meta_window_kill (MetaWindow *window); void meta_window_focus (MetaWindow *window, Time timestamp); void meta_window_raise (MetaWindow *window); @@ -372,4 +380,6 @@ gboolean meta_window_same_application (MetaWindow *window, void meta_window_refresh_resize_popup (MetaWindow *window); +void meta_window_free_delete_dialog (MetaWindow *window); + #endif