#include "xerror.h"
#include "screen.h"
#include "moveresize.h"
+#include "ping.h"
#include "place.h"
#include "prop.h"
#include "extensions.h"
#include "session.h"
#include "event.h"
#include "grab.h"
+#include "prompt.h"
#include "focus.h"
#include "stacking.h"
#include "openbox.h"
#include "keyboard.h"
#include "mouse.h"
#include "render/render.h"
+#include "gettext.h"
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
+#ifdef HAVE_SIGNAL_H
+# include <signal.h> /* for kill() */
+#endif
+
#include <glib.h>
#include <X11/Xutil.h>
static void client_get_shaped(ObClient *self);
static void client_get_mwm_hints(ObClient *self);
static void client_get_colormap(ObClient *self);
+static void client_set_desktop_recursive(ObClient *self,
+ guint target,
+ gboolean donthide,
+ gboolean dontraise);
static void client_change_allowed_actions(ObClient *self);
static void client_change_state(ObClient *self);
static void client_change_wm_state(ObClient *self);
gboolean bylayer,
ObStackingLayer layer);
static void client_call_notifies(ObClient *self, GSList *list);
+static void client_ping_event(ObClient *self, gboolean dead);
+static void client_prompt_kill(ObClient *self);
void client_startup(gboolean reconfig)
}
}
-void client_set_list()
+void client_set_list(void)
{
Window *windows, *win_it;
GList *it;
stacking_set_list();
}
-void client_manage_all()
+void client_manage_all(void)
{
guint i, j, nchild;
Window w, *children;
}
}
+ /* manage windows in reverse order from how they were originally mapped.
+ this is an attempt to manage children windows before their parents, so
+ that when the parent is mapped, it can find the child */
for (i = 0; i < nchild; ++i) {
if (children[i] == None)
continue;
if (attrib.override_redirect) continue;
if (attrib.map_state != IsUnmapped)
- client_manage(children[i]);
+ client_manage(children[i], NULL);
}
}
XFree(children);
}
-void client_manage(Window window)
+void client_manage(Window window, ObPrompt *prompt)
{
ObClient *self;
XEvent e;
map_time = event_get_server_time();
- /* choose the events we want to receive on the CLIENT window */
- attrib_set.event_mask = CLIENT_EVENTMASK;
+ /* choose the events we want to receive on the CLIENT window
+ (ObPrompt windows can request events too) */
+ attrib_set.event_mask = CLIENT_EVENTMASK |
+ (prompt ? prompt->event_mask : 0);
attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
XChangeWindowAttributes(ob_display, window,
CWEventMask|CWDontPropagate, &attrib_set);
self = g_new0(ObClient, 1);
self->obwin.type = Window_Client;
self->window = window;
+ self->prompt = prompt;
/* non-zero defaults */
self->wmstate = WithdrawnState; /* make sure it gets updated first time */
ob_debug("Window type: %d\n", self->type);
ob_debug("Window group: 0x%x\n", self->group?self->group->leader:0);
+ /* now we have all of the window's information so we can set this up.
+ do this before creating the frame, so it can tell that we are still
+ mapping and doesn't go applying things right away */
+ client_setup_decor_and_functions(self, FALSE);
+
/* specify that if we exit, the window should not be destroyed and
- should be reparented back to root automatically */
- XChangeSaveSet(ob_display, window, SetModeInsert);
+ should be reparented back to root automatically, unless we are managing
+ an internal ObPrompt window */
+ if (!self->prompt)
+ XChangeSaveSet(ob_display, window, SetModeInsert);
/* create the decoration frame for the client window */
self->frame = frame_new(self);
/* the session should get the last say though */
client_restore_session_state(self);
- /* now we have all of the window's information so we can set this up */
- client_setup_decor_and_functions(self, FALSE);
-
/* tell startup notification that this app started */
- launch_time = sn_app_started(self->startup_id, self->class);
+ launch_time = sn_app_started(self->startup_id, self->class, self->name);
/* do this after we have a frame.. it uses the frame to help determine the
WM_STATE to apply. */
g_free(monitor);
monitor = NULL;
+ ob_debug_type(OB_DEBUG_FOCUS, "Going to try activate new window? %s\n",
+ activate ? "yes" : "no");
if (activate) {
gboolean raise = FALSE;
return self;
}
-void client_unmanage_all()
+void client_unmanage_all(void)
{
while (client_list != NULL)
client_unmanage(client_list->data);
mouse_grab_for_client(self, FALSE);
- /* remove the window from our save set */
- XChangeSaveSet(ob_display, self->window, SetModeDelete);
+ /* remove the window from our save set, unless we are managing an internal
+ ObPrompt window */
+ if (!self->prompt)
+ XChangeSaveSet(ob_display, self->window, SetModeDelete);
/* update the focus lists */
focus_order_remove(self);
focus_client = NULL;
}
+ /* if we're prompting to kill the client, close that */
+ prompt_unref(self->kill_prompt);
+ self->kill_prompt = NULL;
+
client_list = g_list_remove(client_list, self);
stacking_remove(self);
g_hash_table_remove(window_map, &self->window);
XMapWindow(ob_display, self->window);
}
+ /* these should not be left on the window ever. other window managers
+ don't necessarily use them and it will mess them up (like compiz) */
+ PROP_ERASE(self->window, net_wm_visible_name);
+ PROP_ERASE(self->window, net_wm_visible_icon_name);
+
/* update the list hints */
client_set_list();
g_free(self->icons[j].data);
if (self->nicons > 0)
g_free(self->icons);
+ g_free(self->startup_id);
g_free(self->wm_command);
g_free(self->title);
g_free(self->icon_title);
+ g_free(self->original_title);
g_free(self->name);
g_free(self->class);
g_free(self->role);
!g_pattern_match(app->name, strlen(self->name), self->name, NULL))
match = FALSE;
else if (app->class &&
- !g_pattern_match(app->class,
- strlen(self->class), self->class, NULL))
+ !g_pattern_match(app->class,
+ strlen(self->class), self->class, NULL))
match = FALSE;
else if (app->role &&
!g_pattern_match(app->role,
strlen(self->role), self->role, NULL))
match = FALSE;
+ else if ((signed)app->type >= 0 && app->type != self->type)
+ match = FALSE;
if (match) {
ob_debug("Window matching: %s\n", app->name);
gint fw, fh;
Rect desired;
guint i;
+ gboolean found_mon;
RECT_SET(desired, *x, *y, w, h);
frame_rect_to_frame(self->frame, &desired);
rudeb = TRUE;
}
+ /* we iterate through every monitor that the window is at least partially
+ on, to make sure it is obeying the rules on them all
+
+ if the window does not appear on any monitors, then use the first one
+ */
+ found_mon = FALSE;
for (i = 0; i < screen_num_monitors; ++i) {
Rect *a;
if (!screen_physical_area_monitor_contains(i, &desired)) {
- if (i < screen_num_monitors - 1)
+ if (i < screen_num_monitors - 1 || found_mon)
continue;
/* the window is not inside any monitor! so just use the first
one */
a = screen_area(self->desktop, 0, NULL);
- } else
+ } else {
+ found_mon = TRUE;
a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &desired);
+ }
/* This makes sure windows aren't entirely outside of the screen so you
can't see them at all.
/* if this protocol is requested, then the window will be
notified whenever we want it to receive focus */
self->focus_notify = TRUE;
+ else if (proto[i] == prop_atoms.net_wm_ping)
+ /* if this protocol is requested, then the window will allow
+ pings to determine if it is still alive */
+ self->ping = TRUE;
#ifdef SYNC
else if (proto[i] == prop_atoms.net_wm_sync_request)
/* if this protocol is requested, then resizing the
}
#endif
-void client_get_colormap(ObClient *self)
+static void client_get_colormap(ObClient *self)
{
XWindowAttributes wa;
switch (self->type) {
case OB_CLIENT_TYPE_NORMAL:
/* normal windows retain all of the possible decorations and
- functionality, and are the only windows that you can fullscreen */
+ functionality, and can be fullscreen */
self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
break;
case OB_CLIENT_TYPE_DIALOG:
+ /* sometimes apps make dialog windows fullscreen for some reason (for
+ e.g. kpdf does this..) */
+ self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
+ break;
+
case OB_CLIENT_TYPE_UTILITY:
/* these windows don't have anything added or removed by default */
break;
PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
- /* make sure the window isn't breaking any rules now */
+ /* make sure the window isn't breaking any rules now
+
+ don't check ICONIFY here. just cuz a window can't iconify doesnt mean
+ it can't be iconified with its parent
+ */
if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
if (self->frame) client_shade(self, FALSE);
else self->shaded = FALSE;
}
- if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
- if (self->frame) client_iconify(self, FALSE, TRUE, FALSE);
- else self->iconic = FALSE;
- }
if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
if (self->frame) client_fullscreen(self, FALSE);
else self->fullscreen = FALSE;
gchar *visible = NULL;
g_free(self->title);
+ g_free(self->original_title);
/* try netwm */
if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
data = g_strdup("Unnamed Window");
}
}
+ self->original_title = g_strdup(data);
if (self->client_machine) {
visible = g_strdup_printf("%s (%s)", data, self->client_machine);
} else
visible = data;
+ if (self->not_responding) {
+ data = visible;
+ if (self->kill_level > 0)
+ visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
+ else
+ visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
+ g_free(data);
+ }
+
PROP_SETS(self->window, net_wm_visible_name, visible);
self->title = visible;
} else
visible = data;
+ if (self->not_responding) {
+ data = visible;
+ if (self->kill_level > 0)
+ visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
+ else
+ visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
+ g_free(data);
+ }
+
PROP_SETS(self->window, net_wm_visible_icon_name, visible);
self->icon_title = visible;
}
}
}
+/* Avoid storing icons above this size if possible */
+#define AVOID_ABOVE 64
+
void client_update_icons(ObClient *self)
{
guint num;
guint32 *data;
guint w, h, i, j;
+ guint num_seen; /* number of icons present */
+ guint num_small_seen; /* number of icons small enough present */
+ guint smallest, smallest_area;
for (i = 0; i < self->nicons; ++i)
g_free(self->icons[i].data);
if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
/* figure out how many valid icons are in here */
i = 0;
- while (num - i > 2) {
- w = data[i++];
- h = data[i++];
- i += w * h;
- if (i > num || w*h == 0) break;
- ++self->nicons;
- }
+ num_seen = num_small_seen = 0;
+ smallest = smallest_area = 0;
+ if (num > 2)
+ while (i < num) {
+ w = data[i++];
+ h = data[i++];
+ i += w * h;
+ /* watch for it being too small for the specified size, or for
+ zero sized icons. */
+ if (i > num || w == 0 || h == 0) break;
+
+ if (!smallest_area || w*h < smallest_area) {
+ smallest = num_seen;
+ smallest_area = w*h;
+ }
+ ++num_seen;
+ if (w <= AVOID_ABOVE && h <= AVOID_ABOVE)
+ ++num_small_seen;
+ }
+ if (num_small_seen > 0)
+ self->nicons = num_small_seen;
+ else if (num_seen)
+ self->nicons = 1;
self->icons = g_new(ObClientIcon, self->nicons);
/* store the icons */
i = 0;
- for (j = 0; j < self->nicons; ++j) {
+ for (j = 0; j < self->nicons;) {
guint x, y, t;
w = self->icons[j].width = data[i++];
h = self->icons[j].height = data[i++];
- if (w*h == 0) continue;
+ /* if there are some icons smaller than the threshold, we're
+ skipping all the ones above */
+ if (num_small_seen > 0) {
+ if (w > AVOID_ABOVE || h > AVOID_ABOVE) {
+ i += w*h;
+ continue;
+ }
+ }
+ /* if there were no icons smaller than the threshold, then we are
+ only taking the smallest available one we saw */
+ else if (j != smallest) {
+ i += w*h;
+ continue;
+ }
self->icons[j].data = g_new(RrPixel32, w * h);
for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
(((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
}
g_assert(i <= num);
+
+ ++j;
}
g_free(data);
RECT_SET(self->icon_geometry, 0, 0, 0, 0);
- if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num)
- && num == 4)
+ if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num))
{
- /* don't let them set it with an area < 0 */
- RECT_SET(self->icon_geometry, data[0], data[1],
- MAX(data[2],0), MAX(data[3],0));
+ if (num == 4)
+ /* don't let them set it with an area < 0 */
+ RECT_SET(self->icon_geometry, data[0], data[1],
+ MAX(data[2],0), MAX(data[3],0));
+ g_free(data);
}
}
if (got) {
gchar localhost[128];
+ guint32 pid;
gethostname(localhost, 127);
localhost[127] = '\0';
self->client_machine = s;
else
g_free(s);
+
+ /* see if it has the PID set too (the PID requires that the
+ WM_CLIENT_MACHINE be set) */
+ if (PROP_GET32(self->window, net_wm_pid, cardinal, &pid))
+ self->pid = pid;
}
}
(self->decorations == 0 &&
!(self->max_horz && self->max_vert) &&
RECT_EQUAL(self->area, *monitor))) &&
- (client_focused(self) || client_search_focus_tree(self)))
+ /* you are fullscreen while you or your children are focused.. */
+ (client_focused(self) || client_search_focus_tree(self) ||
+ /* you can be fullscreen if you're on another desktop */
+ (self->desktop != screen_desktop &&
+ self->desktop != DESKTOP_ALL) ||
+ /* and you can also be fullscreen if the focused client is on
+ another monitor, or nothing else is focused */
+ (!focus_client ||
+ client_monitor(focus_client) != client_monitor(self))))
l = OB_STACKING_LAYER_FULLSCREEN;
else if (self->above) l = OB_STACKING_LAYER_ABOVE;
else if (self->below) l = OB_STACKING_LAYER_BELOW;
stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
}
+ /* we've been restacked */
+ self->visited = TRUE;
+
for (it = self->transients; it; it = g_slist_next(it))
client_calc_layer_recursive(it->data, orig,
self->layer);
}
+static void client_calc_layer_internal(ObClient *self)
+{
+ GSList *sit;
+
+ /* transients take on the layer of their parents */
+ sit = client_search_all_top_parents(self);
+
+ for (; sit; sit = g_slist_next(sit))
+ client_calc_layer_recursive(sit->data, self, 0);
+}
+
void client_calc_layer(ObClient *self)
{
- ObClient *orig;
- GSList *it;
+ GList *it;
- orig = self;
+ /* skip over stuff above fullscreen layer */
+ for (it = stacking_list; it; it = g_list_next(it))
+ if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
- /* transients take on the layer of their parents */
- it = client_search_all_top_parents(self);
+ /* find the windows in the fullscreen layer, and mark them not-visited */
+ for (; it; it = g_list_next(it)) {
+ if (window_layer(it->data) < OB_STACKING_LAYER_FULLSCREEN) break;
+ else if (WINDOW_IS_CLIENT(it->data))
+ WINDOW_AS_CLIENT(it->data)->visited = FALSE;
+ }
+
+ client_calc_layer_internal(self);
+
+ /* skip over stuff above fullscreen layer */
+ for (it = stacking_list; it; it = g_list_next(it))
+ if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
- for (; it; it = g_slist_next(it))
- client_calc_layer_recursive(it->data, orig, 0);
+ /* now recalc any windows in the fullscreen layer which have not
+ had their layer recalced already */
+ for (; it; it = g_list_next(it)) {
+ if (window_layer(it->data) < OB_STACKING_LAYER_FULLSCREEN) break;
+ else if (WINDOW_IS_CLIENT(it->data) &&
+ !WINDOW_AS_CLIENT(it->data)->visited)
+ client_calc_layer_internal(it->data);
+ }
}
gboolean client_should_show(ObClient *self)
gboolean show = FALSE;
if (client_should_show(self)) {
+ /* replay pending pointer event before showing the window, in case it
+ should be going to something under the window */
+ mouse_replay_pointer();
+
frame_show(self->frame);
show = TRUE;
so trying to ignore them is futile in case 3 anyways
*/
+ /* replay pending pointer event before hiding the window, in case it
+ should be going to the window */
+ mouse_replay_pointer();
+
frame_hide(self->frame);
hide = TRUE;
void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
gboolean user, gboolean final, gboolean force_reply)
{
+ Rect oldframe;
gint oldw, oldh;
gboolean send_resize_client;
gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
oldw = self->area.width;
oldh = self->area.height;
+ oldframe = self->frame->area;
RECT_SET(self->area, x, y, w, h);
/* for app-requested resizes, always resize if 'resized' is true.
if (!user)
ignore_start = event_start_ignore_all_enters();
+ /* replay pending pointer event before move the window, in case it
+ would change what window gets the event */
+ mouse_replay_pointer();
+
frame_adjust_area(self->frame, fmoved, fresized, FALSE);
if (!user)
}
XFlush(ob_display);
+
+ /* if it moved between monitors, then this can affect the stacking
+ layer of this window or others - for fullscreen windows */
+ if (screen_find_monitor(&self->frame->area) !=
+ screen_find_monitor(&oldframe))
+ {
+ client_calc_layer(self);
+ }
}
void client_fullscreen(ObClient *self, gboolean fs)
frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
}
-void client_close(ObClient *self)
+static void client_ping_event(ObClient *self, gboolean dead)
{
- XEvent ce;
+ if (self->not_responding != dead) {
+ self->not_responding = dead;
+ client_update_title(self);
+
+ if (dead)
+ /* the client isn't responding, so ask to kill it */
+ client_prompt_kill(self);
+ else {
+ /* it came back to life ! */
+
+ if (self->kill_prompt) {
+ prompt_unref(self->kill_prompt);
+ self->kill_prompt = NULL;
+ }
+ self->kill_level = 0;
+ }
+ }
+}
+
+void client_close(ObClient *self)
+{
if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
+ /* if closing an internal obprompt, that is just cancelling it */
+ if (self->prompt) {
+ prompt_cancel(self->prompt);
+ return;
+ }
+
/* in the case that the client provides no means to requesting that it
close, we just kill it */
if (!self->delete_window)
+ /* don't use client_kill(), we should only kill based on PID in
+ response to a lack of PING replies */
+ XKillClient(ob_display, self->window);
+ else {
+ /* request the client to close with WM_DELETE_WINDOW */
+ PROP_MSG_TO(self->window, self->window, wm_protocols,
+ prop_atoms.wm_delete_window, event_curtime, 0, 0, 0,
+ NoEventMask);
+
+ /* we're trying to close the window, so see if it is responding. if it
+ is not, then we will let them kill the window */
+ if (self->ping)
+ ping_start(self, client_ping_event);
+
+ /* if we already know the window isn't responding (maybe they clicked
+ no in the kill dialog but it hasn't come back to life), then show
+ the kill dialog */
+ if (self->not_responding)
+ client_prompt_kill(self);
+ }
+}
+
+#define OB_KILL_RESULT_NO 0
+#define OB_KILL_RESULT_YES 1
+
+static void client_kill_requested(ObPrompt *p, gint result, gpointer data)
+{
+ ObClient *self = data;
+
+ if (result == OB_KILL_RESULT_YES)
client_kill(self);
- /*
- XXX: itd be cool to do timeouts and shit here for killing the client's
- process off
- like... if the window is around after 5 seconds, then the close button
- turns a nice red, and if this function is called again, the client is
- explicitly killed.
- */
+ prompt_unref(self->kill_prompt);
+ self->kill_prompt = NULL;
+}
+
+static void client_prompt_kill(ObClient *self)
+{
+ /* check if we're already prompting */
+ if (!self->kill_prompt) {
+ ObPromptAnswer answers[] = {
+ { _("No"), OB_KILL_RESULT_NO },
+ { _("Yes"), OB_KILL_RESULT_YES }
+ };
+ gchar *m;
+
+ if (client_on_localhost(self)) {
+ const gchar *sig;
+
+ if (self->kill_level == 0)
+ sig = "terminate";
+ else
+ sig = "kill";
+
+ m = g_strdup_printf
+ (_("The window \"%s\" does not seem to be responding. Do you want to force it to exit by sending the %s signal?"), self->original_title, sig);
+ }
+ else
+ m = g_strdup_printf
+ (_("The window \"%s\" does not seem to be responding. Do you want to disconnect it from the X server?"), self->original_title);
- ce.xclient.type = ClientMessage;
- ce.xclient.message_type = prop_atoms.wm_protocols;
- ce.xclient.display = ob_display;
- ce.xclient.window = self->window;
- ce.xclient.format = 32;
- ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
- ce.xclient.data.l[1] = event_curtime;
- ce.xclient.data.l[2] = 0l;
- ce.xclient.data.l[3] = 0l;
- ce.xclient.data.l[4] = 0l;
- XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
+
+ self->kill_prompt = prompt_new(m, answers,
+ sizeof(answers)/sizeof(answers[0]),
+ OB_KILL_RESULT_NO, /* default = no */
+ OB_KILL_RESULT_NO, /* cancel = no */
+ client_kill_requested, self);
+ g_free(m);
+ }
+
+ prompt_show(self->kill_prompt, self);
}
void client_kill(ObClient *self)
{
- XKillClient(ob_display, self->window);
+ /* don't kill our own windows */
+ if (self->prompt) return;
+
+ if (client_on_localhost(self) && self->pid) {
+ /* running on the local host */
+ if (self->kill_level == 0) {
+ ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
+ self->window, self->pid);
+ kill(self->pid, SIGTERM);
+ ++self->kill_level;
+
+ /* show that we're trying to kill it */
+ client_update_title(self);
+ }
+ else {
+ ob_debug("killing window 0x%x with pid %lu, with SIGKILL\n",
+ self->window, self->pid);
+ kill(self->pid, SIGKILL); /* kill -9 */
+ }
+ }
+ else {
+ /* running on a remote host */
+ XKillClient(ob_display, self->window);
+ }
}
void client_hilite(ObClient *self, gboolean hilite)
}
}
-void client_set_desktop_recursive(ObClient *self,
- guint target,
- gboolean donthide,
- gboolean dontraise)
+static void client_set_desktop_recursive(ObClient *self,
+ guint target,
+ gboolean donthide,
+ gboolean dontraise)
{
guint old;
GSList *it;
*h -= self->frame->size.top + self->frame->size.bottom;
}
-ObClient* client_under_pointer()
+ObClient* client_under_pointer(void)
{
gint x, y;
GList *it;
{
return self->group && self->group->members->next;
}
+
+/*! Returns TRUE if the client is running on the same machine as Openbox */
+gboolean client_on_localhost(ObClient *self)
+{
+ return self->client_machine == NULL;
+}