Allow clients to place themselves up to 99% offscreen.
[mikachu/openbox.git] / openbox / client.c
index e4d8534..07ffbbf 100644 (file)
@@ -112,6 +112,8 @@ static gboolean client_can_steal_focus(ObClient *self,
                                        gboolean allow_other_desktop,
                                        gboolean request_from_user,
                                        Time steal_time, Time launch_time);
+static void client_setup_default_decor_and_functions(ObClient *self);
+static void client_setup_decor_undecorated(ObClient *self);
 
 void client_startup(gboolean reconfig)
 {
@@ -165,6 +167,21 @@ void client_remove_destroy_notify(ObClientCallback func)
     }
 }
 
+void client_remove_destroy_notify_data(ObClientCallback func, gpointer data)
+{
+    GSList *it;
+
+    for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
+        ClientCallback *d = it->data;
+        if (d->func == func && d->data == data) {
+            g_slice_free(ClientCallback, d);
+            client_destroy_notifies =
+                g_slist_delete_link(client_destroy_notifies, it);
+            break;
+        }
+    }
+}
+
 void client_set_list(void)
 {
     Window *windows, *win_it;
@@ -201,6 +218,7 @@ void client_manage(Window window, ObPrompt *prompt)
     Time launch_time;
     guint32 user_time;
     gboolean obplaced;
+    gulong ignore_start = FALSE;
 
     ob_debug("Managing window: 0x%lx", window);
 
@@ -232,12 +250,26 @@ void client_manage(Window window, ObPrompt *prompt)
     ob_debug("Window group: 0x%x", self->group?self->group->leader:0);
     ob_debug("Window name: %s class: %s role: %s title: %s",
              self->name, self->class, self->role, self->title);
+    ob_debug("Window group name: %s group class: %s",
+             self->group_name, self->group_class);
 
     /* per-app settings override stuff from client_get_all, and return the
        settings for other uses too. the returned settings is a shallow copy,
        that needs to be freed with g_free(). */
     settings = client_get_settings_state(self);
 
+    /* the session should get the last say though */
+    client_restore_session_state(self);
+
+    /* the per-app settings/session may have changed the decorations for
+       the window, so we setup decorations for that here.  this is a special
+       case because we want to place the window according to these decoration
+       changes.
+       we do this before setting up the frame so that it will reflect the
+       decorations of the window as it will be placed on screen.
+    */
+    client_setup_decor_undecorated(self);
+
     /* specify that if we exit, the window should not be destroyed and
        should be reparented back to root automatically, unless we are managing
        an internal ObPrompt window  */
@@ -253,8 +285,9 @@ void client_manage(Window window, ObPrompt *prompt)
        time now */
     grab_server(FALSE);
 
-    /* the session should get the last say though */
-    client_restore_session_state(self);
+    /* this needs to occur once we have a frame, since it sets a property on
+       the frame */
+    client_update_opacity(self);
 
     /* don't put helper/modal windows on a different desktop if they are
        related to the focused window.  */
@@ -337,8 +370,7 @@ void client_manage(Window window, ObPrompt *prompt)
                      "program + user specified" :
                      "BADNESS !?")))), place.width, place.height);
 
-        obplaced = place_client(self, do_activate, &place.x, &place.y,
-                                settings);
+        obplaced = place_client(self, do_activate, &place, settings);
 
         /* watch for buggy apps that ask to be placed at (0,0) when there is
            a strut there */
@@ -453,19 +485,13 @@ void client_manage(Window window, ObPrompt *prompt)
     /* grab mouse bindings before showing the window */
     mouse_grab_for_client(self, TRUE);
 
+    if (!config_focus_under_mouse)
+        ignore_start = event_start_ignore_all_enters();
+
     /* this has to happen before we try focus the window, but we want it to
        happen after the client's stacking has been determined or it looks bad
     */
-    {
-        gulong ignore_start;
-        if (!config_focus_under_mouse)
-            ignore_start = event_start_ignore_all_enters();
-
-        client_show(self);
-
-        if (!config_focus_under_mouse)
-            event_end_ignore_all_enters(ignore_start);
-    }
+    client_show(self);
 
     /* activate/hilight/raise the window */
     if (try_activate) {
@@ -493,6 +519,9 @@ void client_manage(Window window, ObPrompt *prompt)
             stacking_raise(CLIENT_AS_WINDOW(self));
     }
 
+    if (!config_focus_under_mouse)
+        event_end_ignore_all_enters(ignore_start);
+
     /* add to client list/map */
     client_list = g_list_append(client_list, self);
     window_add(&self->window, CLIENT_AS_WINDOW(self));
@@ -695,6 +724,8 @@ void client_unmanage(ObClient *self)
     g_free(self->name);
     g_free(self->class);
     g_free(self->role);
+    g_free(self->group_name);
+    g_free(self->group_class);
     g_free(self->client_machine);
     g_free(self->sm_client_id);
     g_slice_free(ObClient, self);
@@ -887,15 +918,25 @@ static ObAppSettings *client_get_settings_state(ObClient *self)
 
         g_assert(app->name != NULL || app->class != NULL ||
                  app->role != NULL || app->title != NULL ||
+                 app->group_name != NULL || app->group_class != NULL ||
                  (signed)app->type >= 0);
 
         if (app->name &&
             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
             match = FALSE;
+        else if (app->group_name &&
+            !g_pattern_match(app->group_name,
+                             strlen(self->group_name), self->group_name, NULL))
+            match = FALSE;
         else if (app->class &&
                  !g_pattern_match(app->class,
                                   strlen(self->class), self->class, NULL))
             match = FALSE;
+        else if (app->group_class &&
+                 !g_pattern_match(app->group_class,
+                                  strlen(self->group_class), self->group_class,
+                                  NULL))
+            match = FALSE;
         else if (app->role &&
                  !g_pattern_match(app->role,
                                   strlen(self->role), self->role, NULL))
@@ -1128,14 +1169,14 @@ gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
            only limiting the application.
         */
         if (client_normal(self)) {
-            if (!self->strut.right && *x + fw/10 >= a->x + a->width - 1)
-                *x = a->x + a->width - fw/10;
-            if (!self->strut.bottom && *y + fh/10 >= a->y + a->height - 1)
-                *y = a->y + a->height - fh/10;
-            if (!self->strut.left && *x + fw*9/10 - 1 < a->x)
-                *x = a->x - fw*9/10;
-            if (!self->strut.top && *y + fh*9/10 - 1 < a->y)
-                *y = a->y - fh*9/10;
+            if (!self->strut.right && *x + fw/100 >= a->x + a->width - 1)
+                *x = a->x + a->width - fw/100;
+            if (!self->strut.bottom && *y + fh/100 >= a->y + a->height - 1)
+                *y = a->y + a->height - fh/100;
+            if (!self->strut.left && *x + fw*99/100 - 1 < a->x)
+                *x = a->x - fw*99/100;
+            if (!self->strut.top && *y + fh*99/100 - 1 < a->y)
+                *y = a->y - fh*99/100;
         }
 
         /* This here doesn't let windows even a pixel outside the
@@ -1173,11 +1214,8 @@ static void client_get_all(ObClient *self, gboolean real)
     client_get_type_and_transientness(self);
     client_update_normal_hints(self);
 
-    /* set up the decor/functions before getting the state.  the states may
-       affect which functions are available, but we want to know the maximum
-       decor/functions are available to this window, so we can then apply them
-       in client_apply_startup_state() */
-    client_setup_decor_and_functions(self, FALSE);
+    /* set up the maximum possible decor/functions */
+    client_setup_default_decor_and_functions(self);
 
     client_get_state(self);
 
@@ -1185,13 +1223,15 @@ static void client_get_all(ObClient *self, gboolean real)
        from per-app settings */
     client_get_session_ids(self);
 
-    /* now we got everything that can affect the decorations */
+    /* get this early so we have it for debugging, also this can be used
+     by app rule matching */
+    client_update_title(self);
+
+    /* now we got everything that can affect the decorations or app rule
+       matching */
     if (!real)
         return;
 
-    /* get this early so we have it for debugging */
-    client_update_title(self);
-
     /* save the values of the variables used for app rule matching */
     client_save_app_rule_values(self);
 
@@ -1665,6 +1705,16 @@ void client_update_colormap(ObClient *self, Colormap colormap)
         self->colormap = colormap;
 }
 
+void client_update_opacity(ObClient *self)
+{
+    guint32 o;
+
+    if (OBT_PROP_GET32(self->window, NET_WM_WINDOW_OPACITY, CARDINAL, &o))
+        OBT_PROP_SET32(self->frame->window, NET_WM_WINDOW_OPACITY, CARDINAL, o);
+    else
+        OBT_PROP_ERASE(self->frame->window, NET_WM_WINDOW_OPACITY);
+}
+
 void client_update_normal_hints(ObClient *self)
 {
     XSizeHints size;
@@ -1721,7 +1771,7 @@ void client_update_normal_hints(ObClient *self)
         ob_debug("Normal hints: not set");
 }
 
-void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
+static void client_setup_default_decor_and_functions(ObClient *self)
 {
     /* start with everything (cept fullscreen) */
     self->decorations =
@@ -1854,6 +1904,23 @@ void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
     }
+}
+
+/*! Set up decor for a client based on its undecorated state. */
+static void client_setup_decor_undecorated(ObClient *self)
+{
+    /* If the user requested no decorations, then remove all the decorations,
+       except the border.  But don't add a border if there wasn't one. */
+    if (self->undecorated)
+        self->decorations &= (config_theme_keepborder ?
+                              OB_FRAME_DECOR_BORDER : 0);
+}
+
+void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
+{
+    client_setup_default_decor_and_functions(self);
+
+    client_setup_decor_undecorated(self);
 
     if (self->max_horz && self->max_vert) {
         /* once upon a time you couldn't resize maximized windows, that is not
@@ -1863,12 +1930,6 @@ void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
         self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
     }
 
-    /* finally, the user can have requested no decorations, which overrides
-       everything (but doesnt give it a border if it doesnt have one) */
-    if (self->undecorated)
-        self->decorations &= (config_theme_keepborder ?
-                              OB_FRAME_DECOR_BORDER : 0);
-
     /* if we don't have a titlebar, then we cannot shade! */
     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
         self->functions &= ~OB_CLIENT_FUNC_SHADE;
@@ -2248,6 +2309,10 @@ void client_update_icons(ObClient *self)
     if (!self->icon_set && !self->parents) {
         RrPixel32 *icon = ob_rr_theme->def_win_icon;
         gulong *ldata; /* use a long here to satisfy OBT_PROP_SETA32 */
+        gint32 r,g,b;
+        r = g_random_int_range(0,255);
+        g = g_random_int_range(0,255);
+        b = g_random_int_range(0,255);
 
         w = ob_rr_theme->def_win_icon_w;
         h = ob_rr_theme->def_win_icon_h;
@@ -2256,9 +2321,9 @@ void client_update_icons(ObClient *self)
         ldata[1] = h;
         for (i = 0; i < w*h; ++i)
             ldata[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
-                (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
-                (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
-                (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
+                ((((icon[i] >> RrDefaultRedOffset) & 0xff)*r/255) << 16) +
+                ((((icon[i] >> RrDefaultGreenOffset) & 0xff)*g/255) << 8) +
+                ((((icon[i] >> RrDefaultBlueOffset) & 0xff)*b/255) << 0);
         OBT_PROP_SETA32(self->window, NET_WM_ICON, CARDINAL, ldata, w*h+2);
         g_free(ldata);
     } else if (self->frame)
@@ -2319,6 +2384,25 @@ static void client_get_session_ids(ObClient *self)
     if (self->name == NULL) self->name = g_strdup("");
     if (self->class == NULL) self->class = g_strdup("");
 
+    /* get the WM_CLASS (name and class) from the group leader. make them "" if
+       they are not provided */
+    if (leader)
+        got = OBT_PROP_GETSS_TYPE(leader, WM_CLASS, STRING_NO_CC, &ss);
+    else
+        got = FALSE;
+
+    if (got) {
+        if (ss[0]) {
+            self->group_name = g_strdup(ss[0]);
+            if (ss[1])
+                self->group_class = g_strdup(ss[1]);
+        }
+        g_strfreev(ss);
+    }
+
+    if (self->group_name == NULL) self->group_name = g_strdup("");
+    if (self->group_class == NULL) self->group_class = g_strdup("");
+
     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
     got = OBT_PROP_GETS_XPCS(self->window, WM_WINDOW_ROLE, &s);
 
@@ -2378,18 +2462,10 @@ static void client_get_session_ids(ObClient *self)
     }
 }
 
-/*! Save the properties used for app matching rules, as seen by Openbox when
-  the window mapped, so that users can still access them later if the app
-  changes them */
-static void client_save_app_rule_values(ObClient *self)
+const gchar *client_type_to_string(ObClient *self)
 {
     const gchar *type;
 
-    OBT_PROP_SETS(self->window, OB_APP_ROLE, self->role);
-    OBT_PROP_SETS(self->window, OB_APP_NAME, self->name);
-    OBT_PROP_SETS(self->window, OB_APP_CLASS, self->class);
-    OBT_PROP_SETS(self->window, OB_APP_TITLE, self->original_title);
-
     switch (self->type) {
     case OB_CLIENT_TYPE_NORMAL:
         type = "normal"; break;
@@ -2408,7 +2484,23 @@ static void client_save_app_rule_values(ObClient *self)
     case OB_CLIENT_TYPE_DOCK:
         type = "dock"; break;
     }
-    OBT_PROP_SETS(self->window, OB_APP_TYPE, type);
+
+    return type;
+}
+
+/*! Save the properties used for app matching rules, as seen by Openbox when
+  the window mapped, so that users can still access them later if the app
+  changes them */
+static void client_save_app_rule_values(ObClient *self)
+{
+    OBT_PROP_SETS(self->window, OB_APP_ROLE, self->role);
+    OBT_PROP_SETS(self->window, OB_APP_NAME, self->name);
+    OBT_PROP_SETS(self->window, OB_APP_CLASS, self->class);
+    OBT_PROP_SETS(self->window, OB_APP_GROUP_NAME, self->group_name);
+    OBT_PROP_SETS(self->window, OB_APP_GROUP_CLASS, self->group_class);
+    OBT_PROP_SETS(self->window, OB_APP_TITLE, self->original_title);
+
+    OBT_PROP_SETS(self->window, OB_APP_TYPE, client_type_to_string(self));
 }
 
 static void client_change_wm_state(ObClient *self)
@@ -2646,7 +2738,7 @@ gboolean client_should_show(ObClient *self)
 {
     if (self->iconic)
         return FALSE;
-    if (client_normal(self) && screen_showing_desktop)
+    if (client_normal(self) && screen_showing_desktop())
         return FALSE;
     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
         return TRUE;
@@ -2728,9 +2820,15 @@ gboolean client_helper(ObClient *self)
             self->type == OB_CLIENT_TYPE_TOOLBAR);
 }
 
+gboolean client_occupies_space(ObClient *self)
+{
+    return !(self->type == OB_CLIENT_TYPE_DESKTOP ||
+             self->type == OB_CLIENT_TYPE_SPLASH);
+}
+
 gboolean client_mouse_focusable(ObClient *self)
 {
-    return !(self->type == OB_CLIENT_TYPE_MENU ||
+    return !(/*self->type == OB_CLIENT_TYPE_MENU ||*/
              self->type == OB_CLIENT_TYPE_TOOLBAR ||
              self->type == OB_CLIENT_TYPE_SPLASH ||
              self->type == OB_CLIENT_TYPE_DOCK);
@@ -2800,6 +2898,9 @@ static void client_apply_startup_state(ObClient *self,
     if (fullscreen)
         client_fullscreen(self, TRUE);
 
+    /* make sure client_setup_decor_and_functions() is called at least once */
+    client_setup_decor_and_functions(self, FALSE);
+
     /* if the window hasn't been configured yet, then do so now, in fact the
        x,y,w,h may _not_ be the same as the area rect, which can end up
        meaning that the client isn't properly moved/resized by the fullscreen
@@ -2894,10 +2995,11 @@ void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
 
     /* cap any X windows at the size of an unsigned short */
     *w = MIN(*w,
-             G_MAXUSHORT - self->frame->size.left - self->frame->size.right);
+             (gint)G_MAXUSHORT
+             - self->frame->size.left - self->frame->size.right);
     *h = MIN(*h,
-             G_MAXUSHORT - self->frame->size.top - self->frame->size.bottom);
-
+             (gint)G_MAXUSHORT
+             - self->frame->size.top - self->frame->size.bottom);
 
     /* gets the frame's position */
     frame_client_gravity(self->frame, x, y);
@@ -3924,6 +4026,13 @@ gboolean client_focus(ObClient *self)
 {
     if (!client_validate(self)) return FALSE;
 
+    {
+        XkbStateRec state;
+        XkbGetState(obt_display, XkbUseCoreKbd, &state);
+        if (state.locked_mods & 128)
+            return FALSE;
+    }
+
     /* we might not focus this window, so if we have modal children which would
        be focused instead, bring them to this desktop */
     client_bring_modal_windows(self);
@@ -3982,7 +4091,7 @@ gboolean client_focus(ObClient *self)
 static void client_present(ObClient *self, gboolean here, gboolean raise,
                            gboolean unshade)
 {
-    if (client_normal(self) && screen_showing_desktop)
+    if (client_normal(self) && screen_showing_desktop())
         screen_show_desktop(FALSE, self);
     if (self->iconic)
         client_iconify(self, FALSE, here, FALSE);
@@ -4437,8 +4546,9 @@ void client_find_move_directional(ObClient *self, ObDirection dir,
     frame_frame_gravity(self->frame, x, y);
 }
 
-void client_find_resize_directional(ObClient *self, ObDirection side,
-                                    gboolean grow,
+void client_find_resize_directional(ObClient *self,
+                                    ObDirection side,
+                                    ObClientDirectionalResizeType resize_type,
                                     gint *x, gint *y, gint *w, gint *h)
 {
     gint head;
@@ -4446,31 +4556,84 @@ void client_find_resize_directional(ObClient *self, ObDirection side,
     gboolean near;
     ObDirection dir;
 
+    gboolean grow;
+    switch (resize_type) {
+    case CLIENT_RESIZE_GROW:
+        grow = TRUE;
+        break;
+    case CLIENT_RESIZE_GROW_IF_NOT_ON_EDGE:
+        grow = TRUE;
+        break;
+    case CLIENT_RESIZE_SHRINK:
+        grow = FALSE;
+        break;
+    }
+
     switch (side) {
     case OB_DIRECTION_EAST:
-        head = RECT_RIGHT(self->frame->area) +
-            (self->size_inc.width - 1) * (grow ? 1 : 0);
+        head = RECT_RIGHT(self->frame->area);
+        switch (resize_type) {
+        case CLIENT_RESIZE_GROW:
+            head += self->size_inc.width - 1;
+            break;
+        case CLIENT_RESIZE_GROW_IF_NOT_ON_EDGE:
+            head -= 1;
+            break;
+        case CLIENT_RESIZE_SHRINK:
+            break;
+        }
+
         e_start = RECT_TOP(self->frame->area);
         e_size = self->frame->area.height;
         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
         break;
     case OB_DIRECTION_WEST:
-        head = RECT_LEFT(self->frame->area) -
-            (self->size_inc.width - 1) * (grow ? 1 : 0);
+        head = RECT_LEFT(self->frame->area);
+        switch (resize_type) {
+        case CLIENT_RESIZE_GROW:
+            head -= self->size_inc.width - 1;
+            break;
+        case CLIENT_RESIZE_GROW_IF_NOT_ON_EDGE:
+            head += 1;
+            break;
+        case CLIENT_RESIZE_SHRINK:
+            break;
+        }
+
         e_start = RECT_TOP(self->frame->area);
         e_size = self->frame->area.height;
         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
         break;
     case OB_DIRECTION_NORTH:
-        head = RECT_TOP(self->frame->area) -
-            (self->size_inc.height - 1) * (grow ? 1 : 0);
+        head = RECT_TOP(self->frame->area);
+        switch (resize_type) {
+        case CLIENT_RESIZE_GROW:
+            head -= self->size_inc.height - 1;
+            break;
+        case CLIENT_RESIZE_GROW_IF_NOT_ON_EDGE:
+            head += 1;
+            break;
+        case CLIENT_RESIZE_SHRINK:
+            break;
+        }
+
         e_start = RECT_LEFT(self->frame->area);
         e_size = self->frame->area.width;
         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
         break;
     case OB_DIRECTION_SOUTH:
-        head = RECT_BOTTOM(self->frame->area) +
-            (self->size_inc.height - 1) * (grow ? 1 : 0);
+        head = RECT_BOTTOM(self->frame->area);
+        switch (resize_type) {
+        case CLIENT_RESIZE_GROW:
+            head += self->size_inc.height - 1;
+            break;
+        case CLIENT_RESIZE_GROW_IF_NOT_ON_EDGE:
+            head -= 1;
+            break;
+        case CLIENT_RESIZE_SHRINK:
+            break;
+        }
+
         e_start = RECT_LEFT(self->frame->area);
         e_size = self->frame->area.width;
         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
@@ -4509,7 +4672,7 @@ void client_find_resize_directional(ObClient *self, ObDirection side,
         if (grow == near) --e;
         delta = e - RECT_BOTTOM(self->frame->area);
         *h += delta;
-        break;
+       break;
     default:
         g_assert_not_reached();
     }