Skip to content

Commit

Permalink
Add support for parent relations between windows.
Browse files Browse the repository at this point in the history
see swaywm/wlr-protocols#52

Windows that have a parent are ignored (not shown in the taskbar, since these are usually dialogs).
Also re-wrote the logic for identifying and deciding when an icon should be
shown for a foreign-toplevel. Signals are now always emitted by the done event
handler.

Additionally, improvements to parse app ID under Wayland as well

Moved the functionality for this from cairo-dock-X-utilities.c to
cairo-dock-windows-manager.c (as a new function: gldi_window_parse_class()),
this is now used by both the X and Wayland backends. This fixes issues by
not recognizing some apps (e.g. Firefox and gnome-terminal if using XWayland)
due to the app ID not matching the expected case.
  • Loading branch information
dkondor committed Nov 6, 2020
1 parent 0618222 commit 02bc14b
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 92 deletions.
65 changes: 65 additions & 0 deletions src/gldit/cairo-dock-windows-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "cairo-dock-log.h"
#include "cairo-dock-desktop-manager.h" // g_desktopGeometry
#include "cairo-dock-utils.h" // cairo_dock_remove_version_from_string
#define _MANAGER_DEF_
#include "cairo-dock-windows-manager.h"

Expand Down Expand Up @@ -341,6 +342,70 @@ void gldi_window_move_to_current_desktop (GldiWindowActor *pAppli)
}


gchar* gldi_window_parse_class(const gchar* res_class, const gchar* res_name) {
gchar *cClass = NULL, *cWmClass = NULL;
if (res_class)
{
cd_debug (" res_name : %s(%x); res_class : %s(%x)", res_name, res_name, res_class, res_class);
if (strcmp (res_class, "Wine") == 0 && res_name && (g_str_has_suffix (res_name, ".exe") || g_str_has_suffix (res_name, ".EXE"))) // wine application: use the name instead, because we don't want to group all wine apps togather
{
cd_debug (" wine application detected, changing the class '%s' to '%s'", res_class, res_name);
cClass = g_ascii_strdown (res_name, -1);
}
// chromium web apps (not the browser): same remark as for wine apps
else if (res_name && res_name[0] != '\0' && res_class[0] != '\0'
&& (
((res_class[0] == 'c' || res_class[0] == 'C') && (strcmp(res_class+1, "hromium-browser") == 0 || strcmp(res_class+1, "hromium") == 0))
|| strcmp (res_class, "Google-chrome") == 0 // from Google
|| strcmp (res_class, "Google-chrome-beta") == 0
|| strcmp (res_class, "Google-chrome-unstable") == 0)
&& strcmp (res_class+1, res_name+1) != 0) // skip first letter (upper/lowercase)
{
cClass = g_ascii_strdown (res_name, -1);

/* Remove spaces. Why do they add spaces here?
* (e.g.: Google-chrome-unstable (/home/$USER/.config/google-chrome-unstable))
*/
gchar *str = strchr (cClass, ' ');
if (str != NULL)
*str = '\0';

/* Replace '.' to '_' (e.g.: www.google.com__calendar). It's to not
* just have 'www' as class (we will drop the rest just here after)
*/
for (int i = 0; cClass[i] != '\0'; i++)
{
if (cClass[i] == '.')
cClass[i] = '_';
}
cd_debug (" chromium application detected, changing the class '%s' to '%s'", res_class, cClass);
}
else if (*res_class == '/' && (g_str_has_suffix (res_class, ".exe") || g_str_has_suffix (res_name, ".EXE"))) // case of Mono applications like tomboy ...
{
gchar *str = strrchr (res_class, '/');
if (str)
str ++;
else
str = res_class;
cClass = g_ascii_strdown (str, -1);
cClass[strlen (cClass) - 4] = '\0';
}
else
{
cClass = g_ascii_strdown (res_class, -1); // down case because some apps change the case depending of their windows...
}

cairo_dock_remove_version_from_string (cClass); // we remore number of version (e.g. Openoffice.org-3.1)

gchar *str = strchr (cClass, '.'); // we remove all .xxx otherwise we can't detect the lack of extension when looking for an icon (openoffice.org) or it's a problem when looking for an icon (jbrout.py).
if (str != NULL)
*str = '\0';
cd_debug ("got an application with class '%s'", cClass);
}
return cClass;
}


///////////////
/// MANAGER ///
///////////////
Expand Down
10 changes: 10 additions & 0 deletions src/gldit/cairo-dock-windows-manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ guint gldi_window_get_id (GldiWindowActor *pAppli);
GldiWindowActor *gldi_window_pick (void);


/* utility for parsing special cases in the window class / app ID;
* used by both the X and Wayland backends
*
* res_class and res_name should be the window class / app ID and window
* name / title as reported by the windowing system; the return value is
* a parsed class with some special cases handled; the caller should
* free it when it is done using it
* */
gchar* gldi_window_parse_class(const gchar* res_class, const gchar* res_name);

void gldi_register_windows_manager (void);

G_END_DECLS
Expand Down
67 changes: 4 additions & 63 deletions src/implementations/cairo-dock-X-utilities.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "cairo-dock-desktop-manager.h"
#include "cairo-dock-opengl.h" // for texture_from_pixmap
#include "cairo-dock-X-utilities.h"
#include "cairo-dock-windows-manager.h" // gldi_window_parse_class

#include <cairo/cairo-xlib.h> // needed for cairo_xlib_surface_create

Expand Down Expand Up @@ -1111,75 +1112,15 @@ gchar *cairo_dock_get_xwindow_name (Window Xid, gboolean bSearchWmName)
gchar *cairo_dock_get_xwindow_class (Window Xid, gchar **cWMClass)
{
XClassHint *pClassHint = XAllocClassHint ();
gchar *cClass = NULL, *cWmClass = NULL;
gchar *cClass = NULL;
if (XGetClassHint (s_XDisplay, Xid, pClassHint) != 0 && pClassHint->res_class)
{
cWmClass = g_strdup (pClassHint->res_class);

cd_debug (" res_name : %s(%x); res_class : %s(%x)", pClassHint->res_name, pClassHint->res_name, pClassHint->res_class, pClassHint->res_class);
if (strcmp (pClassHint->res_class, "Wine") == 0 && pClassHint->res_name && (g_str_has_suffix (pClassHint->res_name, ".exe") || g_str_has_suffix (pClassHint->res_name, ".EXE"))) // wine application: use the name instead, because we don't want to group all wine apps togather
{
cd_debug (" wine application detected, changing the class '%s' to '%s'", pClassHint->res_class, pClassHint->res_name);
cClass = g_ascii_strdown (pClassHint->res_name, -1);
}
// chromium web apps (not the browser): same remark as for wine apps
else if (pClassHint->res_name && pClassHint->res_name[0] != '\0' && pClassHint->res_class[0] != '\0'
&& (
((pClassHint->res_class[0] == 'c' || pClassHint->res_class[0] == 'C') && (strcmp(pClassHint->res_class+1, "hromium-browser") == 0 || strcmp(pClassHint->res_class+1, "hromium") == 0))
|| strcmp (pClassHint->res_class, "Google-chrome") == 0 // from Google
|| strcmp (pClassHint->res_class, "Google-chrome-beta") == 0
|| strcmp (pClassHint->res_class, "Google-chrome-unstable") == 0)
&& strcmp (pClassHint->res_class+1, pClassHint->res_name+1) != 0) // skip first letter (upper/lowercase)
{
cClass = g_ascii_strdown (pClassHint->res_name, -1);

/* Remove spaces. Why do they add spaces here?
* (e.g.: Google-chrome-unstable (/home/$USER/.config/google-chrome-unstable))
*/
gchar *str = strchr (cClass, ' ');
if (str != NULL)
*str = '\0';

/* Replace '.' to '_' (e.g.: www.google.com__calendar). It's to not
* just have 'www' as class (we will drop the rest just here after)
*/
for (int i = 0; cClass[i] != '\0'; i++)
{
if (cClass[i] == '.')
cClass[i] = '_';
}
cd_debug (" chromium application detected, changing the class '%s' to '%s'", pClassHint->res_class, cClass);
}
else if (*pClassHint->res_class == '/' && (g_str_has_suffix (pClassHint->res_class, ".exe") || g_str_has_suffix (pClassHint->res_name, ".EXE"))) // case of Mono applications like tomboy ...
{
gchar *str = strrchr (pClassHint->res_class, '/');
if (str)
str ++;
else
str = pClassHint->res_class;
cClass = g_ascii_strdown (str, -1);
cClass[strlen (cClass) - 4] = '\0';
}
else
{
cClass = g_ascii_strdown (pClassHint->res_class, -1); // down case because some apps change the case depending of their windows...
}

cairo_dock_remove_version_from_string (cClass); // we remore number of version (e.g. Openoffice.org-3.1)

gchar *str = strchr (cClass, '.'); // we remove all .xxx otherwise we can't detect the lack of extension when looking for an icon (openoffice.org) or it's a problem when looking for an icon (jbrout.py).
if (str != NULL)
*str = '\0';
cd_debug ("got an application with class '%s'", cClass);

cClass = gldi_window_parse_class(pClassHint->res_class, pClassHint->res_name);
if (cWMClass) *cWMClass = g_strdup (pClassHint->res_class);
XFree (pClassHint->res_name);
XFree (pClassHint->res_class);
XFree (pClassHint);
}
if (cWMClass)
*cWMClass = cWmClass;
else
g_free (cWmClass);
return cClass;
}

Expand Down
134 changes: 111 additions & 23 deletions src/implementations/cairo-dock-foreign-toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ struct _GldiWFTWindowActor {
// foreign-toplevel specific
// handle to toplevel object
wfthandle *handle;
// store parent handle, if supplied
// NOTE: windows with parent are ignored (not shown in the taskbar)
wfthandle *parent;
gchar* cOldClass; // save the old window class in case it is changed
gboolean init_done; // set to true after the first done event
gboolean class_changed; // true if the app ID was changed
};
typedef struct _GldiWFTWindowActor GldiWFTWindowActor;

Expand Down Expand Up @@ -77,7 +83,22 @@ static void _maximize (GldiWindowActor *actor, gboolean bMaximize)
if (bMaximize) zwlr_foreign_toplevel_handle_v1_set_maximized (wactor->handle);
else zwlr_foreign_toplevel_handle_v1_unset_maximized (wactor->handle);
}

static GldiWindowActor* _get_transient_for(GldiWindowActor* actor)
{
GldiWFTWindowActor *wactor = (GldiWFTWindowActor *)actor;
wfthandle* parent = wactor->parent;
if (!parent) return NULL;
GldiWFTWindowActor *pactor = zwlr_foreign_toplevel_handle_v1_get_user_data (parent);
return (GldiWindowActor*)pactor;
}

/* current active window -- last one to receive activated signal */
static GldiWindowActor* s_pActiveWindow = NULL;
/* maybe new active window -- this is set if the activated signal is
* received for a window that is not "created" yet, i.e. has not done
* the initial init yet or has a parent */
static GldiWindowActor* s_pMaybeActiveWindow = NULL;
static GldiWindowActor* _get_active_window (void)
{
return s_pActiveWindow;
Expand Down Expand Up @@ -127,30 +148,30 @@ static void _gldi_toplevel_title_cb (void *data, G_GNUC_UNUSED wfthandle *handle
GldiWindowActor* actor = (GldiWindowActor*)wactor;
g_free (actor->cName);
actor->cName = g_strdup ((gchar *)title);
if (actor->cClass) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_NAME_CHANGED, actor);
if (wactor->init_done && !wactor->parent) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_NAME_CHANGED, actor);
}

static void _gldi_toplevel_appid_cb (void *data, G_GNUC_UNUSED wfthandle *handle, const char *app_id)
{
GldiWFTWindowActor* wactor = (GldiWFTWindowActor*)data;
GldiWindowActor* actor = (GldiWindowActor*)wactor;

if (!app_id) return;
else
if (!wactor->cOldClass)
{
char* cOldClass = actor->cClass;
actor->cClass = g_strdup ((gchar *)app_id);
if (!cOldClass)
{
actor->bDisplayed = TRUE;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_CREATED, actor);
}
else
{
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_CLASS_CHANGED, actor, cOldClass, NULL);
g_free (cOldClass);
}
wactor->cOldClass = actor->cClass;
}
else {
g_free (actor->cClass);
}
actor->cClass = g_strdup ((gchar *)app_id);
// if (actor->cClass && !wactor->parent) actor->bDisplayed = TRUE;
// else actor->bDisplayed = FALSE;
// we note that this actor's window class (app ID) has changed
// we don't send the notification about it here, since this
// change could be grouped with other changes that should be
// applied atomically
wactor->class_changed = TRUE;
// if (wactor->init_done && !wactor->parent) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_CLASS_CHANGED, actor, cOldClass, NULL);
}

static void _gldi_toplevel_output_enter_cb ( G_GNUC_UNUSED void *data, G_GNUC_UNUSED wfthandle *handle, G_GNUC_UNUSED struct wl_output *output)
Expand Down Expand Up @@ -181,8 +202,26 @@ static void _gldi_toplevel_state_cb (void *data, G_GNUC_UNUSED wfthandle *handle
}
if (activated)
{
s_pActiveWindow = actor;
if (actor->cClass) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_ACTIVATED, actor);
s_pMaybeActiveWindow = actor;
if (actor->bDisplayed && s_pActiveWindow != actor)
{
s_pActiveWindow = actor;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_ACTIVATED, actor);
}
else if (wactor->init_done && wactor->parent)
{
GldiWFTWindowActor* pwactor = wactor;
do {
pwactor = zwlr_foreign_toplevel_handle_v1_get_user_data(pwactor->parent);
} while (pwactor->parent);
GldiWindowActor* pactor = (GldiWindowActor*)pwactor;
if (!pwactor->init_done) s_pMaybeActiveWindow = pactor;
else if (pactor->bDisplayed && s_pActiveWindow != pactor)
{
s_pActiveWindow = pactor;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_ACTIVATED, pactor);
}
}
}
gboolean bHiddenChanged = (minimized != actor->bIsHidden);
gboolean bMaximizedChanged = (maximized != actor->bIsMaximized);
Expand All @@ -191,30 +230,76 @@ static void _gldi_toplevel_state_cb (void *data, G_GNUC_UNUSED wfthandle *handle
actor->bIsMaximized = maximized;

if (bHiddenChanged || bMaximizedChanged)
if (actor->cClass) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_STATE_CHANGED, actor, bHiddenChanged, bMaximizedChanged, bFullScreenChanged);
if (actor->bDisplayed) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_STATE_CHANGED, actor, bHiddenChanged, bMaximizedChanged, bFullScreenChanged);
}

static void _gldi_toplevel_done_cb ( G_GNUC_UNUSED void *data, G_GNUC_UNUSED wfthandle *handle)
static void _gldi_toplevel_done_cb ( void *data, G_GNUC_UNUSED wfthandle *handle)
{

GldiWFTWindowActor* wactor = (GldiWFTWindowActor*)data;
GldiWindowActor* actor = (GldiWindowActor*)wactor;
gchar* cOldWmClass = NULL;
if (wactor->class_changed)
{
cOldWmClass = actor->cWmClass;
actor->cWmClass = actor->cClass;
actor->cClass = gldi_window_parse_class (actor->cWmClass, actor->cName);
}
gboolean new_displayed = FALSE;

if (!wactor->parent && actor->cClass) {
new_displayed = TRUE;
}

if (!actor->bDisplayed && new_displayed)
{
actor->bDisplayed = TRUE;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_CREATED, actor);
if (actor == s_pMaybeActiveWindow)
{
s_pActiveWindow = actor;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_ACTIVATED, actor);
}
}
else if (actor->bDisplayed && !new_displayed)
{
actor->bDisplayed = FALSE;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESTROYED, actor);
}
else if (actor->bDisplayed && new_displayed && wactor->class_changed) {
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_CLASS_CHANGED, actor, wactor->cOldClass, cOldWmClass);
}

g_free (cOldWmClass);
g_free (wactor->cOldClass);
wactor->cOldClass = NULL;
wactor->class_changed = FALSE;
wactor->init_done = TRUE;
}

static void _gldi_toplevel_closed_cb (void *data, G_GNUC_UNUSED wfthandle *handle)
{
GldiWFTWindowActor* wactor = (GldiWFTWindowActor*)data;
GldiWindowActor* actor = (GldiWindowActor*)wactor;
if (actor->cClass) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESTROYED, actor);
if (actor->bDisplayed) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESTROYED, actor);
gldi_object_unref (GLDI_OBJECT(actor));
}

static void _gldi_toplevel_parent_cb (void* data, G_GNUC_UNUSED wfthandle *handle, wfthandle *parent)
{
// fprintf(stderr,"Parent for toplevel: %p -> %p\n", handle, parent);
GldiWFTWindowActor* wactor = (GldiWFTWindowActor*)data;
wactor->parent = parent;
}

struct zwlr_foreign_toplevel_handle_v1_listener gldi_toplevel_handle_interface = {
.title = _gldi_toplevel_title_cb,
.app_id = _gldi_toplevel_appid_cb,
.output_enter = _gldi_toplevel_output_enter_cb,
.output_leave = _gldi_toplevel_output_leave_cb,
.state = _gldi_toplevel_state_cb,
.done = _gldi_toplevel_done_cb,
.closed = _gldi_toplevel_closed_cb
.closed = _gldi_toplevel_closed_cb,
.parent = _gldi_toplevel_parent_cb
};


Expand All @@ -226,11 +311,14 @@ static void init_object (GldiObject *obj, gpointer attr)
{
GldiWFTWindowActor* actor = (GldiWFTWindowActor*)obj;
actor->handle = (wfthandle*)attr;
actor->parent = NULL;
actor->init_done = FALSE;
}

static void reset_object (GldiObject* obj)
{
GldiWFTWindowActor* actor = (GldiWFTWindowActor*)obj;
g_free(actor->cOldClass);
if (obj && actor->handle) zwlr_foreign_toplevel_handle_v1_destroy(actor->handle);
}

Expand Down Expand Up @@ -290,7 +378,7 @@ static void gldi_zwlr_foreign_toplevel_manager_init ()
// wmb.get_icon_surface = _get_icon_surface;
// wmb.get_thumbnail_surface = _get_thumbnail_surface;
// wmb.get_texture = _get_texture;
// wmb.get_transient_for = _get_transient_for;
wmb.get_transient_for = _get_transient_for;
// wmb.is_above_or_below = _is_above_or_below;
// wmb.is_sticky = _is_sticky;
// wmb.set_sticky = _set_sticky;
Expand Down
Loading

0 comments on commit 02bc14b

Please sign in to comment.