Skip to content

Commit

Permalink
Merge pull request Cairo-Dock#47 from dkondor/plasma_desktop
Browse files Browse the repository at this point in the history
Improve virtual desktops support for KDE Plasma
  • Loading branch information
dkondor authored Oct 23, 2024
2 parents bdf4565 + 77611fe commit 9f9421e
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 24 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ include ("${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/GNUInstallDirs.cmake")
########### project ###############

project ("cairo-dock")
set (VERSION "3.5.99.beta2") # no dash, only numbers, dots and maybe alpha/beta/rc, e.g.: 3.3.1 or 3.3.99.alpha1
set (VERSION "3.5.99.beta3") # no dash, only numbers, dots and maybe alpha/beta/rc, e.g.: 3.3.1 or 3.3.99.alpha1

add_compile_options ($<$<COMPILE_LANGUAGE:C>:-std=c99> -Wall -Wextra $<$<COMPILE_LANGUAGE:C>:-Werror-implicit-function-declaration>) # -Wextra -Wwrite-strings -Wuninitialized -Werror-implicit-function-declaration -Wstrict-prototypes -Wreturn-type -Wparentheses -Warray-bounds)
if (NOT DEFINED CMAKE_BUILD_TYPE)
Expand Down
70 changes: 70 additions & 0 deletions src/gldit/cairo-dock-desktop-manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,76 @@ typedef enum {
NB_NOTIFICATIONS_DESKTOP
} CairoDesktopNotifications;


/*
Old behavior: separate iNbDesktops + iNbViewportX / iNbViewportY:
- iNbDesktops: Independent "desktops", no overlap (i.e. each window is on one desktop at maximum). Desktops
are arranged in one dimension.
- iNbViewportX / iNbViewportY: each desktop can have multiple viewports, strictly arranged into a rectangle
that form a continuous space, thus windows can overlap among viewports.
- These map to distinct APIs in X11: desktops to _NET_NUMBER_OF_DESKTOPS / _NET_CURRENT_DESKTOP and
viewports to _NET_DESKTOP_GEOMETRY / _NET_DESKTOP_VIEWPORT:
https://specifications.freedesktop.org/wm-spec/latest/ar01s03.html
Typcial behavior:
- Compiz (+ also Gnome-shell?): iNbDesktops == 1, "workspaces" correspond to viewports only
- other WMs: iNbDesktops >= 1, iNbViewportX == iNbViewportY == 1
Questions / issues:
- Unsure if for any WM / DE, we can have both iNbDesktops > 1 and multiple viewports. In theory, on Wayland
this will be possible with the ext-workspace protocol if there are multiple workspace groups. On X11,
this is explicitly supported by the standards, but likely not implemented by WMs.
- Are there X11 WM that provides multipe desktops and arranges them in 2D? In theory, this is possible using
_NET_DESKTOP_LAYOUT, however Cairo-Dock currently does not handle this. Note: according to the
specification, this is set by the "pager", which is possibly a separate entity from the WM:
https://specifications.freedesktop.org/wm-spec/latest/ar01s03.html
So possibly Cairo-Dock could act as a pager in this case and arrange the desktops as it is convenient.
(TODO: tesk maybe with Openbox or similar)
However, when not using viewports, this is only relevant if the WM / compositor provides an animation
when switching (as it can be confusing if that does not match CD's UI).
Examples:
- Cinnamon: https://github.com/linuxmint/muffin/commit/4a0a270c260f2272b97473b1897c5d71c746ecc6
(set through their config?)
- Openbox: https://forums.freebsd.org/threads/how-to-start-openbox-with-2x2-grid-workplaces.84749/
https://openbox.org/help/Openbox-session
(set by changing _NET_DESKTOP_LAYOUT, e.g. by us)
- KWin (X11): https://invent.kde.org/plasma/kwin/-/merge_requests/5065
(set in config or DBus)
- How should things work with multiple monitors? On X11, they are assumed to have the same desktops and
viewports, but Wayland allows separate workspace groups
Minimal changes for Wayland:
- workspace groups correspond to desktops, and workspaces to viewports; make sure things work for multiple
workspace groups
- allow viewports to be non-overlapping (GLDI_WM_NO_VIEWPORT_OVERLAP -- this is how desktops behave)
- workspaces are arranged into 1 or 2 dimensions, based on the coordinates (higher dimensions are not
supported, converted to 1D)
- update the switcher applet to display desktops / workspace groups more independently
- change APIs to add / remove individual workspaces, take care to handle to case of X11 rectangular
viewports though
- move GldiWMBackendFlags and the related capabilities here?
- X11: special case WMs where desktops can be arranged in 2D? (but has to be no viewports)
-> "desktops" would be interpreted as independent viewports in this case
Plan for a new API:
- two-level structure with "desktops" and "vieports"
- on X11: desktop <-> _NET_NUMBER_OF_DESKTOPS / _NET_CURRENT_DESKTOP / _NET_DESKTOP_LAYOUT
workspace <-> _NET_DESKTOP_GEOMETRY / _NET_DESKTOP_VIEWPORT
- on Wayland: desktop <-> workspace-group, viewport <-> workspace (on KWin, only one desktop)
- both desktops and viewports can be arranged into a 2D grid (for desktops, add this)
- number of viewports per desktop can be different -> dynamic storage of a new _GldiViewportGeometry for
each desktop
- desktops are always "independent": a window can only span one (or all for sticky windows)
- vieports can be overlapping (a window can span multiple), flag / setting for this that is set by the backend
Next steps:
- change API for adding removing "workspaces", these are handled by a backend-specific way (move some of the
logic from the switcher plugin to core)
- make the number of viewports per desktop independent, use this to implement the ext-workspace /
cosmic-workspace protocol
- implement support for _NET_DESKTOP_LAYOUT for arranging desktops in a 2D grid on X11
*/

// data
struct _GldiDesktopGeometry {
int iNbScreens;
Expand Down
36 changes: 31 additions & 5 deletions src/gldit/cairo-dock-windows-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ void gldi_windows_foreach (gboolean bOrderedByZ, GFunc callback, gpointer data)
g_list_foreach (s_pWindowsList, callback, data);
}

void gldi_windows_foreach_unordered (GFunc callback, gpointer data)
{
g_list_foreach (s_pWindowsList, callback, data);
}

GldiWindowActor *gldi_windows_find (gboolean (*callback) (GldiWindowActor*, gpointer), gpointer data)
{
GldiWindowActor *actor;
Expand Down Expand Up @@ -128,6 +133,8 @@ void gldi_window_move_to_desktop (GldiWindowActor *actor, int iNumDesktop, int i
iNumDesktop,
(iNumViewportX - g_desktopGeometry.iCurrentViewportX) * gldi_desktop_get_width(),
(iNumViewportY - g_desktopGeometry.iCurrentViewportY) * gldi_desktop_get_height());
else if (s_backend.move_to_viewport_abs)
s_backend.move_to_viewport_abs (actor, iNumDesktop, iNumViewportX, iNumViewportY);
}

void gldi_window_show (GldiWindowActor *actor)
Expand Down Expand Up @@ -315,24 +322,39 @@ static inline gboolean _window_is_on_current_desktop (GtkAllocation *pWindowGeom
}
gboolean gldi_window_is_on_current_desktop (GldiWindowActor *actor)
{
///return (actor->iNumDesktop == -1 || actor->iNumDesktop == g_desktopGeometry.iCurrentDesktop) && actor->iViewPortX == g_desktopGeometry.iCurrentViewportX && actor->iViewPortY == g_desktopGeometry.iCurrentViewportY; /// TODO: check that it works
return actor->bIsSticky || _window_is_on_current_desktop (&actor->windowGeometry, actor->iNumDesktop);
if (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT
|| GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_NO_VIEWPORT_OVERLAP)
return gldi_window_is_on_desktop (actor, g_desktopGeometry.iCurrentDesktop,
g_desktopGeometry.iCurrentViewportX, g_desktopGeometry.iCurrentViewportY);

if (actor->bIsSticky || actor->iNumDesktop == -1) return TRUE;

return _window_is_on_current_desktop (&actor->windowGeometry, actor->iNumDesktop);
}


gboolean gldi_window_is_on_desktop (GldiWindowActor *pAppli, int iNumDesktop, int iNumViewportX, int iNumViewportY)
{
if (pAppli->bIsSticky || pAppli->iNumDesktop == -1) // a sticky window is by definition on all desktops/viewports
return TRUE;

if (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_NO_VIEWPORT_OVERLAP)
return (pAppli->iNumDesktop == iNumDesktop && pAppli->iViewPortX == iNumViewportX
&& pAppli->iViewPortY == iNumViewportY);

// On calcule les coordonnees en repere absolu.
int x = pAppli->windowGeometry.x; // par rapport au viewport courant.
x += g_desktopGeometry.iCurrentViewportX * gldi_desktop_get_width(); // repere absolu
int x = pAppli->windowGeometry.x; // par rapport au viewport courant (or self, depending on the backend)
if (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT) x += pAppli->iViewPortX * gldi_desktop_get_width(); // repere absolu
else x += g_desktopGeometry.iCurrentViewportX * gldi_desktop_get_width();
if (x < 0)
x += g_desktopGeometry.iNbViewportX * gldi_desktop_get_width();

int y = pAppli->windowGeometry.y;
y += g_desktopGeometry.iCurrentViewportY * gldi_desktop_get_height();
if (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT) y += pAppli->iViewPortY * gldi_desktop_get_height();
else y += g_desktopGeometry.iCurrentViewportY * gldi_desktop_get_height();
if (y < 0)
y += g_desktopGeometry.iNbViewportY * gldi_desktop_get_height();

int w = pAppli->windowGeometry.width, h = pAppli->windowGeometry.height;

// test d'intersection avec le viewport donne.
Expand All @@ -351,6 +373,10 @@ void gldi_window_move_to_current_desktop (GldiWindowActor *pAppli)
g_desktopGeometry.iCurrentViewportY); // on ne veut pas decaler son viewport par rapport a nous.
}

gboolean gldi_window_manager_is_position_relative_to_current_viewport (void)
{
return !(GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT);
}

gchar* gldi_window_parse_class(const gchar* res_class, const gchar* res_name) {
gchar *cClass = NULL;
Expand Down
9 changes: 9 additions & 0 deletions src/gldit/cairo-dock-windows-manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ typedef enum {

// data

typedef enum {
GLDI_WM_GEOM_REL_TO_VIEWPORT = 1, // if present, windows' geometry is relative to the viewport they are present on (and not to the current viewport)
GLDI_WM_NO_VIEWPORT_OVERLAP = 2 // if present, windows cannot span multiple viewports
} GldiWMBackendFlags;

/// Definition of the Windows Manager backend.
struct _GldiWindowManagerBackend {
GldiWindowActor* (*get_active_window) (void);
Expand All @@ -78,6 +83,8 @@ struct _GldiWindowManagerBackend {
guint (*get_id) (GldiWindowActor *actor);
GldiWindowActor* (*pick_window) (GtkWindow *pParentWindow); // grab the mouse, wait for a click, then get the clicked window and returns its actor
const gchar *name; // name of the current backend
void (*move_to_viewport_abs) (GldiWindowActor *actor, int iNumDesktop, int iViewportX, int iViewportY); // like move_to_nth_desktop, but use absolute viewport coordinates
gpointer flags; // GldiWMBackendFlags, cast to pointer
} ;

/// Definition of a window actor.
Expand Down Expand Up @@ -115,6 +122,7 @@ const gchar *gldi_windows_manager_get_name ();
*@param data user data
*/
void gldi_windows_foreach (gboolean bOrderedByZ, GFunc callback, gpointer data);
void gldi_windows_foreach_unordered (GFunc callback, gpointer data);

/** Run a function on each window actor.
*@param callback the callback (takes the actor and the data, returns TRUE to stop)
Expand Down Expand Up @@ -177,6 +185,7 @@ guint gldi_window_get_id (GldiWindowActor *pAppli);

GldiWindowActor *gldi_window_pick (GtkWindow *pParentWindow);

gboolean gldi_window_manager_is_position_relative_to_current_viewport (void);

/* utility for parsing special cases in the window class / app ID;
* used by both the X and Wayland backends
Expand Down
101 changes: 89 additions & 12 deletions src/implementations/cairo-dock-plasma-virtual-desktop.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "wayland-plasma-virtual-desktop-client-protocol.h"
#include "cairo-dock-log.h"
#include "cairo-dock-desktop-manager.h"
#include "cairo-dock-windows-manager.h"
#include "cairo-dock-plasma-virtual-desktop.h"

typedef struct _PlasmaDesktop {
Expand All @@ -34,15 +35,70 @@ typedef struct _PlasmaDesktop {
static unsigned int s_iNumDesktops = 0;
static PlasmaDesktop **desktops = NULL; // allocated when the first desktop is added
static unsigned int s_iDesktopCap = 0; // capacity of the above array
static unsigned int s_iRows = 1; // number of rows the desktops are arranged into
static unsigned int s_iCurrent = 0; // index into desktops array with the currently active desktop

static void _update_desktop_layout ()
{
unsigned int rows = s_iRows;
if (!rows) rows = 1;
unsigned int cols = s_iNumDesktops / rows + ((s_iNumDesktops % rows) ? 1 : 0);
g_desktopGeometry.iNbDesktops = 1;
g_desktopGeometry.iNbViewportX = cols;
g_desktopGeometry.iNbViewportY = rows;
}

static void _update_current_desktop ()
{
g_desktopGeometry.iCurrentDesktop = 0;
g_desktopGeometry.iCurrentViewportY = s_iCurrent / g_desktopGeometry.iNbViewportX;
g_desktopGeometry.iCurrentViewportX = s_iCurrent % g_desktopGeometry.iNbViewportX;
}

typedef struct _update_windows_par
{
int cols_old;
int desktop_added;
int desktop_removed;
} update_windows_par;

static void _update_windows (gpointer ptr, gpointer data)
{
GldiWindowActor *actor = (GldiWindowActor*)ptr;
update_windows_par *par = (update_windows_par*)data;
int cols_old = par->cols_old;
int cols_new = g_desktopGeometry.iNbViewportX;

int ix = actor->iViewPortY * cols_old + actor->iViewPortX;
// handle the insertion or removal of desktops (which moves the index of desktops beyond it)
if (par->desktop_added >= 0 && ix >= par->desktop_added) ix++;
else if (par->desktop_removed >= 0 && ix >= par->desktop_removed && ix > 0) ix--;
int newY = ix / cols_new;
int newX = ix % cols_new;
if (newX != actor->iViewPortX || newY != actor->iViewPortY)
{
actor->iViewPortY = newY;
actor->iViewPortX = newX;
gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESKTOP_CHANGED, actor);
}
}


static void _remove_desktop (unsigned int i)
{
unsigned int j;
for (j = i + 1; j < s_iNumDesktops; j++) desktops[j - 1] = desktops[j];
s_iNumDesktops--;
desktops[s_iNumDesktops] = NULL;
g_desktopGeometry.iNbDesktops--;
int cols_old = g_desktopGeometry.iNbViewportX;
_update_desktop_layout ();
_update_current_desktop ();
gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_GEOMETRY_CHANGED, FALSE);
// note: this is not a "real" change, but the coordinates likely changed and we need to keep track of these
gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_CHANGED);
// update windows' position (x and y coordinates have likely changed)
update_windows_par par = {.cols_old = cols_old, .desktop_added = -1, .desktop_removed = i};
gldi_windows_foreach_unordered (_update_windows, &par);
}

int gldi_plasma_virtual_desktop_get_index (const char *desktop_id)
Expand Down Expand Up @@ -83,10 +139,11 @@ static void _activated (void *data, G_GNUC_UNUSED struct org_kde_plasma_virtual_
for (i = 0; i < s_iNumDesktops; i++) if (desktops[i] == desktop) break;
if (i < s_iNumDesktops)
{
g_desktopGeometry.iCurrentDesktop = i;
s_iCurrent = i;
_update_current_desktop ();
gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_CHANGED);
}
else cd_error ("plasma-virtual-desktop: could not find currently activated desktop!");
else cd_critical ("plasma-virtual-desktop: could not find currently activated desktop!");
}

static void _deactivated (G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop)
Expand Down Expand Up @@ -125,6 +182,8 @@ static const struct org_kde_plasma_virtual_desktop_listener desktop_listener = {
static void _desktop_created (G_GNUC_UNUSED void *data, struct org_kde_plasma_virtual_desktop_management *manager,
const char *desktop_id, uint32_t position)
{
int cols_old = g_desktopGeometry.iNbViewportX;

PlasmaDesktop *desktop = g_new0 (PlasmaDesktop, 1);
desktop->id = g_strdup (desktop_id);
if (s_iNumDesktops >= s_iDesktopCap)
Expand All @@ -140,12 +199,21 @@ static void _desktop_created (G_GNUC_UNUSED void *data, struct org_kde_plasma_vi
desktops[position] = desktop;
}
s_iNumDesktops++;
g_desktopGeometry.iNbDesktops = s_iNumDesktops;

gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_GEOMETRY_CHANGED, FALSE);
_update_desktop_layout ();
_update_current_desktop ();

desktop->handle = org_kde_plasma_virtual_desktop_management_get_virtual_desktop(manager, desktop_id);
org_kde_plasma_virtual_desktop_add_listener (desktop->handle, &desktop_listener, desktop);

gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_GEOMETRY_CHANGED, FALSE);
gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_CHANGED, FALSE);

if (s_iRows > 1)
{
// update windows' position (x and y coordinates have likely changed)
update_windows_par par = {.cols_old = cols_old, .desktop_added = position, .desktop_removed = -1};
gldi_windows_foreach_unordered (_update_windows, &par);
}
}

static void _desktop_removed (G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct org_kde_plasma_virtual_desktop_management *manager,
Expand All @@ -161,9 +229,17 @@ static void _desktop_done (G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct org_kd
}

static void _desktop_rows (G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct org_kde_plasma_virtual_desktop_management *manager,
G_GNUC_UNUSED uint32_t rows)
uint32_t rows)
{
// no-op
s_iRows = rows;
int cols_old = g_desktopGeometry.iNbViewportX;
_update_desktop_layout ();
gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_GEOMETRY_CHANGED, FALSE);
// note: this is not a "real" change, but the coordinates likely changed and we need to keep track of these
gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_CHANGED);
// update windows' position (x and y coordinates have likely changed)
update_windows_par par = {.cols_old = cols_old, .desktop_added = -1, .desktop_removed = -1};
gldi_windows_foreach_unordered (_update_windows, &par);
}


Expand All @@ -183,12 +259,13 @@ static gchar** _get_desktops_names (void)
return ret;
}

static gboolean _set_current_desktop (int iDesktopNumber, G_GNUC_UNUSED int iViewportNumberX, G_GNUC_UNUSED int iViewportNumberY)
static gboolean _set_current_desktop (G_GNUC_UNUSED int iDesktopNumber, int iViewportNumberX, int iViewportNumberY)
{
// viewport is ignored
if (iDesktopNumber >= 0 && (unsigned int)iDesktopNumber < s_iNumDesktops)
// desktop number is ignored (it should be 0)
unsigned int iReq = g_desktopGeometry.iNbViewportX * iViewportNumberY + iViewportNumberX;
if (iReq < s_iNumDesktops)
{
org_kde_plasma_virtual_desktop_request_activate (desktops[iDesktopNumber]->handle);
org_kde_plasma_virtual_desktop_request_activate (desktops[iReq]->handle);
return TRUE; // we don't know if we succeeded
}
return FALSE;
Expand Down
Loading

0 comments on commit 9f9421e

Please sign in to comment.