diff --git a/CMakeLists.txt b/CMakeLists.txt index eb43e7e3..c5741284 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ include ("${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/GNUInstallDirs.cmake") ########### project ############### project ("cairo-dock") -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 +set (VERSION "3.5.99.beta4") # no dash, only numbers, dots and maybe alpha/beta/rc, e.g.: 3.3.1 or 3.3.99.alpha1 add_compile_options ($<$:-std=c99> -Wall -Wextra $<$:-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) diff --git a/src/cairo-dock-user-menu.c b/src/cairo-dock-user-menu.c index 04173df6..af241ad0 100644 --- a/src/cairo-dock-user-menu.c +++ b/src/cairo-dock-user-menu.c @@ -1405,6 +1405,8 @@ static void _add_desktops_entry (GtkWidget *pMenu, gboolean bAll, gpointer *data static gpointer *s_pDesktopData = NULL; GtkWidget *pMenuItem; + if (!gldi_window_manager_can_move_to_desktop ()) return; + if (g_desktopGeometry.iNbDesktops > 1 || g_desktopGeometry.iNbViewportX > 1 || g_desktopGeometry.iNbViewportY > 1) { // add separator @@ -1900,7 +1902,8 @@ gboolean cairo_dock_notification_build_icon_menu (G_GNUC_UNUSED gpointer *pUserD // Move pMenuItem = _add_entry_in_menu (_("Move to this desktop"), GLDI_ICON_NAME_JUMP_TO, _cairo_dock_move_appli_to_current_desktop, pSubMenuOtherActions); - if (gldi_window_is_on_current_desktop (pAppli)) + // if it is not possible to move the window, we still keep this menu item, but make it inactive + if (!gldi_window_manager_can_move_to_desktop () || gldi_window_is_on_current_desktop (pAppli)) gtk_widget_set_sensitive (pMenuItem, FALSE); // Fullscreen @@ -1978,7 +1981,11 @@ gboolean cairo_dock_notification_build_icon_menu (G_GNUC_UNUSED gpointer *pUserD //\_________________________ Other actions GtkWidget *pSubMenuOtherActions = cairo_dock_create_sub_menu (_("Other actions"), menu, NULL); - _add_entry_in_menu (_("Move all to this desktop"), GLDI_ICON_NAME_JUMP_TO, _cairo_dock_move_class_to_current_desktop, pSubMenuOtherActions); + pMenuItem = _add_entry_in_menu (_("Move all to this desktop"), GLDI_ICON_NAME_JUMP_TO, _cairo_dock_move_class_to_current_desktop, pSubMenuOtherActions); + // if it is not possible to move the windows, we still keep this menu item, but make it inactive + if (!gldi_window_manager_can_move_to_desktop ()) + gtk_widget_set_sensitive (pMenuItem, FALSE); + _add_desktops_entry (pSubMenuOtherActions, TRUE, data); } diff --git a/src/gldit/cairo-dock-applications-manager.c b/src/gldit/cairo-dock-applications-manager.c index b7992a6a..050f7b69 100644 --- a/src/gldit/cairo-dock-applications-manager.c +++ b/src/gldit/cairo-dock-applications-manager.c @@ -62,7 +62,7 @@ extern gboolean g_bUseOpenGL; // private static GHashTable *s_hAppliIconsTable = NULL; // table des fenetres affichees dans le dock. -static int s_bAppliManagerIsRunning = FALSE; +static gboolean s_bAppliManagerIsRunning = FALSE; static GldiWindowActor *s_pCurrentActiveWindow = NULL; static void cairo_dock_unregister_appli (Icon *icon); @@ -88,6 +88,7 @@ static Icon *_get_appli_icon (GldiWindowActor *actor) static gboolean _on_window_created (G_GNUC_UNUSED gpointer data, GldiWindowActor *actor) { + if (!s_bAppliManagerIsRunning) return GLDI_NOTIFICATION_LET_PASS; Icon *pIcon = _get_appli_icon (actor); g_return_val_if_fail (pIcon == NULL, GLDI_NOTIFICATION_LET_PASS); @@ -746,7 +747,9 @@ static gboolean get_config (GKeyFile *pKeyFile, CairoTaskbarParam *pTaskBar) if (pTaskBar->bShowAppli) { - pTaskBar->bAppliOnCurrentDesktopOnly = cairo_dock_get_boolean_key_value (pKeyFile, "TaskBar", "current desktop only", &bFlushConfFileNeeded, FALSE, "Applications", NULL); + // current desktop only option only makes sense if we are actually able to track which desktop a window is on + pTaskBar->bAppliOnCurrentDesktopOnly = gldi_window_manager_can_track_workspaces () + && cairo_dock_get_boolean_key_value (pKeyFile, "TaskBar", "current desktop only", &bFlushConfFileNeeded, FALSE, "Applications", NULL); pTaskBar->bMixLauncherAppli = cairo_dock_get_boolean_key_value (pKeyFile, "TaskBar", "mix launcher appli", &bFlushConfFileNeeded, TRUE, NULL, NULL); diff --git a/src/gldit/cairo-dock-desktop-manager.c b/src/gldit/cairo-dock-desktop-manager.c index 92644043..5aa9a896 100644 --- a/src/gldit/cairo-dock-desktop-manager.c +++ b/src/gldit/cairo-dock-desktop-manager.c @@ -215,13 +215,18 @@ gboolean gldi_desktop_set_current (int iDesktopNumber, int iViewportNumberX, int return FALSE; } -gboolean gldi_desktop_set_nb_desktops (int iNbDesktops, int iNbViewportX, int iNbViewportY) +void gldi_desktop_add_workspace (void) { - if (s_backend.set_nb_desktops) - return s_backend.set_nb_desktops (iNbDesktops, iNbViewportX, iNbViewportY); - return FALSE; + if (s_backend.add_workspace) s_backend.add_workspace (); +} + +void gldi_desktop_remove_last_workspace (void) +{ + if (s_backend.remove_last_workspace) s_backend.remove_last_workspace (); } + + void gldi_desktop_refresh (void) { if (s_backend.refresh) diff --git a/src/gldit/cairo-dock-desktop-manager.h b/src/gldit/cairo-dock-desktop-manager.h index f2021799..01f0fc4a 100644 --- a/src/gldit/cairo-dock-desktop-manager.h +++ b/src/gldit/cairo-dock-desktop-manager.h @@ -80,7 +80,7 @@ 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 + - Are there X11 WMs that provide 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 @@ -107,8 +107,8 @@ Minimal changes for Wayland: 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? + viewports though -> partly done + - move GldiWMBackendFlags and the related capabilities here? -> no, better in window manager - 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 @@ -124,8 +124,8 @@ Plan for a new API: - 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) + - change API for adding and removing "workspaces", these are handled by a backend-specific way (move some of the + logic from the switcher plugin to core) -> done - 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 @@ -155,10 +155,11 @@ struct _GldiDesktopManagerBackend { gboolean (*set_desktops_names) (gchar **cNames); cairo_surface_t* (*get_desktop_bg_surface) (void); gboolean (*set_current_desktop) (int iDesktopNumber, int iViewportNumberX, int iViewportNumberY); - gboolean (*set_nb_desktops) (int iNbDesktops, int iNbViewportX, int iNbViewportY); void (*refresh) (void); void (*notify_startup) (const gchar *cClass); gboolean (*grab_shortkey) (guint keycode, guint modifiers, gboolean grab); + void (*add_workspace) (void); // gldi_desktop_add_workspace () + void (*remove_last_workspace) (void); // gldi_desktop_remove_last_workspace () }; /// Definition of a Desktop Background Buffer. It has a reference count so that it can be shared across all the lib. @@ -221,7 +222,25 @@ gboolean gldi_desktop_is_visible (void); gchar** gldi_desktop_get_names (void); gboolean gldi_desktop_set_names (gchar **cNames); gboolean gldi_desktop_set_current (int iDesktopNumber, int iViewportNumberX, int iViewportNumberY); -gboolean gldi_desktop_set_nb_desktops (int iNbDesktops, int iNbViewportX, int iNbViewportY); + +/** Adds a new workspace, desktop or viewport in an implementation-defined manner. + * Typically this can mean adding one more workspace / desktop as the "last" one. + * On X11, this will resize the desktop geometry, and could result in adding + * multiple viewports. + * Might not suceed, depending on the capabilities of the backend + * (NOTIFICATION_DESKTOP_GEOMETRY_CHANGED will be emitted if successful). + */ +void gldi_desktop_add_workspace (void); + +/** Remove the "last" workspace desktop or viewport, according to the + * internal ordering of workspaces. The actual number of workspaces can + * be > 1, depending on the backend (on X11, if viewports are arranged + * in a square). + * Might not suceed, depending on the capabilities of the backend + * (NOTIFICATION_DESKTOP_GEOMETRY_CHANGED will be emitted if successful). + */ +void gldi_desktop_remove_last_workspace (void); + void gldi_desktop_refresh (void); diff --git a/src/gldit/cairo-dock-dock-visibility.c b/src/gldit/cairo-dock-dock-visibility.c index a04fae1b..2966094e 100644 --- a/src/gldit/cairo-dock-dock-visibility.c +++ b/src/gldit/cairo-dock-dock-visibility.c @@ -327,6 +327,9 @@ void gldi_docks_visibility_start (void) { static gboolean first = TRUE; + if (! (gldi_window_manager_have_coordinates () && gldi_window_manager_can_track_workspaces ()) ) + return; + // register to events if (first) { diff --git a/src/gldit/cairo-dock-module-manager.h b/src/gldit/cairo-dock-module-manager.h index b174b353..eb121f85 100644 --- a/src/gldit/cairo-dock-module-manager.h +++ b/src/gldit/cairo-dock-module-manager.h @@ -44,7 +44,7 @@ G_BEGIN_DECLS * It is not required the change this when adding a function to the * public API (loading the module will fail if it refers to an * unresolved symbol anyway). */ -#define GLDI_ABI_VERSION 20240921 +#define GLDI_ABI_VERSION 20241026 // manager typedef struct _GldiModulesParam GldiModulesParam; diff --git a/src/gldit/cairo-dock-windows-manager.c b/src/gldit/cairo-dock-windows-manager.c index 014dbe88..690e38a8 100644 --- a/src/gldit/cairo-dock-windows-manager.c +++ b/src/gldit/cairo-dock-windows-manager.c @@ -255,7 +255,7 @@ GldiWindowActor *gldi_window_get_transient_for (GldiWindowActor *actor) void gldi_window_is_above_or_below (GldiWindowActor *actor, gboolean *bIsAbove, gboolean *bIsBelow) { - if (s_backend.set_window_border) + if (s_backend.is_above_or_below) s_backend.is_above_or_below (actor, bIsAbove, bIsBelow); else { @@ -322,35 +322,48 @@ static inline gboolean _window_is_on_current_desktop (GtkAllocation *pWindowGeom } gboolean gldi_window_is_on_current_desktop (GldiWindowActor *actor) { - if (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT - || GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_NO_VIEWPORT_OVERLAP) + const int flags = GPOINTER_TO_INT (s_backend.flags); + if (! (flags & GLDI_WM_HAVE_WORKSPACES)) + return FALSE; // if we don't track workspaces, there is no point + + if (actor->bIsSticky || actor->iNumDesktop == -1) // a sticky window is by definition on all desktops/viewports + return TRUE; + + if ( !(flags & GLDI_WM_HAVE_WINDOW_GEOMETRY) || (flags & GLDI_WM_NO_VIEWPORT_OVERLAP)) + return (actor->iNumDesktop == g_desktopGeometry.iCurrentDesktop + && actor->iViewPortX == g_desktopGeometry.iCurrentViewportX + && actor->iViewPortY == g_desktopGeometry.iCurrentViewportY); + + if (flags & GLDI_WM_GEOM_REL_TO_VIEWPORT) 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) { + const int flags = GPOINTER_TO_INT (s_backend.flags); + if (! (flags & GLDI_WM_HAVE_WORKSPACES)) + return FALSE; // if we don't track workspaces, there is no point + 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) + if ( !(flags & GLDI_WM_HAVE_WINDOW_GEOMETRY) || (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 (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 + if (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; - if (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT) y += pAppli->iViewPortY * gldi_desktop_get_height(); + if (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(); @@ -378,6 +391,22 @@ gboolean gldi_window_manager_is_position_relative_to_current_viewport (void) return !(GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_GEOM_REL_TO_VIEWPORT); } +gboolean gldi_window_manager_have_coordinates (void) +{ + return (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_HAVE_WINDOW_GEOMETRY); +} + +gboolean gldi_window_manager_can_track_workspaces (void) +{ + return (GPOINTER_TO_INT (s_backend.flags) & GLDI_WM_HAVE_WORKSPACES); +} + +gboolean gldi_window_manager_can_move_to_desktop (void) +{ + return (s_backend.move_to_nth_desktop || s_backend.move_to_viewport_abs); +} + + gchar* gldi_window_parse_class(const gchar* res_class, const gchar* res_name) { gchar *cClass = NULL; if (res_class) diff --git a/src/gldit/cairo-dock-windows-manager.h b/src/gldit/cairo-dock-windows-manager.h index 703c1226..9c6118c3 100644 --- a/src/gldit/cairo-dock-windows-manager.h +++ b/src/gldit/cairo-dock-windows-manager.h @@ -55,7 +55,9 @@ typedef enum { 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 + GLDI_WM_NO_VIEWPORT_OVERLAP = 2, // if present, windows cannot span multiple viewports + GLDI_WM_HAVE_WINDOW_GEOMETRY = 4, // the WM provides valid window geometry information (in _GldiWindowActor::windowGeometry) + GLDI_WM_HAVE_WORKSPACES = 8 // the WM tracks which workspace (desktop, viewport) a window is on (in iNumDesktop, iViewPortX, iViewPortY) } GldiWMBackendFlags; /// Definition of the Windows Manager backend. @@ -185,8 +187,38 @@ guint gldi_window_get_id (GldiWindowActor *pAppli); GldiWindowActor *gldi_window_pick (GtkWindow *pParentWindow); + +/* WM capabilities -- use cases outside of cairo-dock-windows-manager.c (especially plugins) + * should check these before using the corresponding fields */ + +/** Check whether we can track windows' position. + *@return whether GldiWindowActor::windowGeometry contains the actual + * window position; if FALSE, this should not be used + */ +gboolean gldi_window_manager_have_coordinates (void); + +/** Check whether we can track which workspace / viewport / desktop + * windows are present on. + *@return whether GldiWindowActor::iNumDesktop, iViewPortX and iViewPortY + * contains valid information; if FALSE, these should not be used + */ +gboolean gldi_window_manager_can_track_workspaces (void); + +/** Check how window position coordinates should be interpreted. Result is only + * valud if gldi_window_manager_have_coordinates () == TRUE as well. + *@return whether window coordinates should be interpreted relative to the + * currently active workspace / viewport; if false, coordinates are relative + * to the workspace / viewport that the window is currently on + */ gboolean gldi_window_manager_is_position_relative_to_current_viewport (void); +/** Check whether it is possible to move a window to another desktop / viewport. + *@return TRUE if it is possible to move a window; if FALSE, + * gldi_window_move_to_current_desktop () and gldi_window_move_to_desktop () + * will do nothing + */ +gboolean gldi_window_manager_can_move_to_desktop (void); + /* utility for parsing special cases in the window class / app ID; * used by both the X and Wayland backends * diff --git a/src/implementations/CMakeLists.txt b/src/implementations/CMakeLists.txt index ba6b0d19..303d410f 100644 --- a/src/implementations/CMakeLists.txt +++ b/src/implementations/CMakeLists.txt @@ -34,6 +34,7 @@ if(WaylandScanner_FOUND) cairo-dock-cosmic-toplevel.c cairo-dock-cosmic-toplevel.h cairo-dock-plasma-window-manager.c cairo-dock-plasma-window-manager.h cairo-dock-plasma-virtual-desktop.c cairo-dock-plasma-virtual-desktop.h + cairo-dock-cosmic-workspaces.c cairo-dock-cosmic-workspaces.h ) ecm_add_wayland_client_protocol( diff --git a/src/implementations/cairo-dock-X-manager.c b/src/implementations/cairo-dock-X-manager.c index 3b560544..ced3c7d4 100644 --- a/src/implementations/cairo-dock-X-manager.c +++ b/src/implementations/cairo-dock-X-manager.c @@ -789,13 +789,22 @@ static gboolean _set_current_desktop (int iDesktopNumber, int iViewportNumberX, return TRUE; } -static gboolean _set_nb_desktops (int iNbDesktops, int iNbViewportX, int iNbViewportY) +static void _add_workspace (void) { - if (iNbDesktops > 0) - cairo_dock_set_nb_desktops (iNbDesktops); - if (iNbViewportX > 0 && iNbViewportY > 0) - cairo_dock_set_nb_viewports (iNbViewportX, iNbViewportY); - return TRUE; + if (g_desktopGeometry.iNbViewportX == 1 && g_desktopGeometry.iNbViewportY == 1) + cairo_dock_set_nb_desktops (g_desktopGeometry.iNbDesktops + 1); + else cairo_dock_change_nb_viewports (1, cairo_dock_set_nb_viewports); +} + +static void _remove_workspace (void) +{ + if (g_desktopGeometry.iNbViewportX == 1 && g_desktopGeometry.iNbViewportY == 1) + { + // note: do not attempt to remove the last desktop + if (g_desktopGeometry.iNbDesktops > 1) + cairo_dock_set_nb_desktops (g_desktopGeometry.iNbDesktops - 1); + } + else cairo_dock_change_nb_viewports (-1, cairo_dock_set_nb_viewports); } static cairo_surface_t *_get_desktop_bg_surface (void) // attention : fonction lourde. @@ -1642,10 +1651,11 @@ static void init (void) dmb.set_desktops_names = _set_desktops_names; dmb.get_desktop_bg_surface = _get_desktop_bg_surface; dmb.set_current_desktop = _set_current_desktop; - dmb.set_nb_desktops = _set_nb_desktops; dmb.refresh = _refresh; dmb.notify_startup = _notify_startup; dmb.grab_shortkey = _grab_shortkey; + dmb.add_workspace = _add_workspace; + dmb.remove_last_workspace = _remove_workspace; gldi_desktop_manager_register_backend (&dmb, "X11"); GldiWindowManagerBackend wmb; @@ -1672,6 +1682,8 @@ static void init (void) wmb.can_minimize_maximize_close = _can_minimize_maximize_close; wmb.get_id = _get_id; wmb.pick_window = _pick_window; + //!! TODO: figure out GLDI_WM_NO_VIEWPORT_OVERLAP flag (depends on the WM, needs to be done in *-integration.c) !! + wmb.flags = GINT_TO_POINTER (GLDI_WM_HAVE_WINDOW_GEOMETRY | GLDI_WM_HAVE_WORKSPACES); wmb.name = "X11"; gldi_windows_manager_register_backend (&wmb); diff --git a/src/implementations/cairo-dock-X-utilities.c b/src/implementations/cairo-dock-X-utilities.c index 92888d98..07a514e9 100644 --- a/src/implementations/cairo-dock-X-utilities.c +++ b/src/implementations/cairo-dock-X-utilities.c @@ -719,6 +719,26 @@ void cairo_dock_set_nb_desktops (gulong iNbDesktops) XFlush (s_XDisplay); } +void cairo_dock_change_nb_viewports (int iDeltaNbDesktops, GldiChangeViewportFunc cb) +{ + // taken from the switcher applet + int iNewX, iNewY; + // Try to keep a square: (delta > 0 && X <= Y) || (delta < 0 && X > Y) + if ((iDeltaNbDesktops > 0) == (g_desktopGeometry.iNbViewportX <= g_desktopGeometry.iNbViewportY)) + { + iNewX = g_desktopGeometry.iNbViewportX + iDeltaNbDesktops; + if (iNewX <= 0) return; // cannot remove the last viewport + iNewY = g_desktopGeometry.iNbViewportY; + } + else + { + iNewX = g_desktopGeometry.iNbViewportX; + iNewY = g_desktopGeometry.iNbViewportY + iDeltaNbDesktops; + if (iNewY <= 0) return; // cannot remove the last viewport + } + cb (iNewX, iNewY); +} + static gboolean cairo_dock_support_X_extension (void) { diff --git a/src/implementations/cairo-dock-X-utilities.h b/src/implementations/cairo-dock-X-utilities.h index b8a68b07..58e2fc47 100644 --- a/src/implementations/cairo-dock-X-utilities.h +++ b/src/implementations/cairo-dock-X-utilities.h @@ -69,6 +69,17 @@ GdkPixbuf *cairo_dock_get_pixbuf_from_pixmap (int XPixmapID, gboolean bAddAlpha) void cairo_dock_set_nb_viewports (int iNbViewportX, int iNbViewportY); void cairo_dock_set_nb_desktops (gulong iNbDesktops); +typedef void (*GldiChangeViewportFunc) (int iNbViewportX, int iNbViewportY); + +/** Change the number of viewports while keeping the total workarea as a rectangle. + * The new dimensions are calculated to keep it close to a square. The actual change + * is carried out by the callback given as the second argument. + * @param iDeltaNbDesktops should be +1 or -1 to indicate whether to add or remove viewports + * @param cb a function carrying out the actual change, taking two integer parameters + * which are the new X and Y dimensions + */ +void cairo_dock_change_nb_viewports (int iDeltaNbDesktops, GldiChangeViewportFunc cb); + //////////// // WINDOW // diff --git a/src/implementations/cairo-dock-compiz-integration.c b/src/implementations/cairo-dock-compiz-integration.c index 8389e6b2..b25f0f21 100644 --- a/src/implementations/cairo-dock-compiz-integration.c +++ b/src/implementations/cairo-dock-compiz-integration.c @@ -31,7 +31,7 @@ #include "cairo-dock-container.h" // gldi_container_get_gdk_window #include "cairo-dock-class-manager.h" #include "cairo-dock-utils.h" // cairo_dock_launch_command_sync -#include "cairo-dock-X-utilities.h" // cairo_dock_get_X_display +#include "cairo-dock-X-utilities.h" // cairo_dock_get_X_display, cairo_dock_change_nb_viewports #include "cairo-dock-compiz-integration.h" static DBusGProxy *s_pScaleProxy = NULL; @@ -307,24 +307,11 @@ static gboolean set_on_widget_layer (GldiContainer *pContainer, gboolean bOnWidg /* Only add workspaces with Compiz: We shouldn't add desktops when using Compiz * and with this method, Compiz saves the new state */ -static gboolean set_nb_desktops (int iNbDesktops, int iNbViewportX, int iNbViewportY) +static gboolean _compiz_set_nb_viewports (int X, int Y) { gboolean bSuccess = FALSE; if (s_pHSizeProxy != NULL && s_pVSizeProxy != NULL) { - // We can receive (-1, >0, >0) or (2, -1, -1) - int X, Y; - if (iNbDesktops > 0) - { - X = iNbDesktops; - Y = 1; - } - else - { - X = iNbViewportX > 0 ? iNbViewportX : 1; - Y = iNbViewportY > 0 ? iNbViewportY : 1; - } - GError *error = NULL; bSuccess = dbus_g_proxy_call (s_pHSizeProxy, "set", &error, G_TYPE_INT, X, @@ -350,6 +337,16 @@ static gboolean set_nb_desktops (int iNbDesktops, int iNbViewportX, int iNbViewp return bSuccess; } +static void _add_workspace (void) +{ + cairo_dock_change_nb_viewports (1, _compiz_set_nb_viewports); +} + +static void _remove_workspace (void) +{ + cairo_dock_change_nb_viewports (-1, _compiz_set_nb_viewports); +} + static void _register_compiz_backend (void) { @@ -361,7 +358,9 @@ static void _register_compiz_backend (void) p.present_desktops = present_desktops; p.show_widget_layer = show_widget_layer; p.set_on_widget_layer = set_on_widget_layer; - p.set_nb_desktops = set_nb_desktops; + p.add_workspace = _add_workspace; + p.remove_last_workspace = _remove_workspace; + gldi_desktop_manager_register_backend (&p, "Compiz"); } diff --git a/src/implementations/cairo-dock-cosmic-toplevel.c b/src/implementations/cairo-dock-cosmic-toplevel.c index 731f19b3..d9033ed2 100644 --- a/src/implementations/cairo-dock-cosmic-toplevel.c +++ b/src/implementations/cairo-dock-cosmic-toplevel.c @@ -32,6 +32,7 @@ #include "cairo-dock-log.h" #include "cairo-dock-cosmic-toplevel.h" #include "cairo-dock-wayland-wm.h" +#include "cairo-dock-cosmic-workspaces.h" #include @@ -59,6 +60,16 @@ static uint32_t manager_id, info_id, manager_version, info_version; /********************************************************************** * window manager interface -- toplevel manager */ +static void _move_to_nth_desktop (GldiWindowActor *actor, G_GNUC_UNUSED int iNumDesktop, + int x, int y) +{ + if (!(s_ws_output && can_move_workspace)) return; + GldiWaylandWindowActor *wactor = (GldiWaylandWindowActor *)actor; + struct zcosmic_workspace_handle_v1 *ws = gldi_cosmic_workspaces_get_handle (x, y); + //!! TODO: we need a valid wl_output here !! + if (ws) zcosmic_toplevel_manager_v1_move_to_workspace (s_ptoplevel_manager, wactor->handle, ws, s_ws_output); +} + static void _show (GldiWindowActor *actor) { if (!can_activate) return; @@ -195,7 +206,10 @@ static void _gldi_toplevel_state_cb (void *data, G_GNUC_UNUSED wfthandle *handle for (i = 0; i*sizeof(uint32_t) < state->size; i++) { if (stdata[i] == ZCOSMIC_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) + { gldi_wayland_wm_activated (wactor, FALSE); + gldi_wayland_wm_stack_on_top ((GldiWindowActor*)wactor); + } else if (stdata[i] == ZCOSMIC_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) maximized_pending = TRUE; else if (stdata[i] == ZCOSMIC_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) @@ -227,11 +241,20 @@ static void _gldi_toplevel_parent_cb (void* data, G_GNUC_UNUSED wfthandle *handl } */ + +static void _workspace_entered (void *data, G_GNUC_UNUSED wfthandle *handle, struct zcosmic_workspace_handle_v1 *wshandle) +{ + gldi_cosmic_workspaces_update_window ((GldiWindowActor*)data, wshandle); + gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESKTOP_CHANGED, data); +} + static void _dummy (G_GNUC_UNUSED void *data, G_GNUC_UNUSED wfthandle *handle, G_GNUC_UNUSED void *workspace) { } + + /********************************************************************** * interface and object manager definitions */ @@ -248,7 +271,7 @@ static struct zcosmic_toplevel_handle_v1_listener gldi_toplevel_handle_interface .done = _gldi_toplevel_done_cb, .closed = _gldi_toplevel_closed_cb, // .parent = _gldi_toplevel_parent_cb, - .workspace_enter = (void (*)(void*, struct zcosmic_toplevel_handle_v1*, struct zcosmic_workspace_handle_v1*))_dummy, + .workspace_enter = _workspace_entered, .workspace_leave = (void (*)(void*, struct zcosmic_toplevel_handle_v1*, struct zcosmic_workspace_handle_v1*))_dummy }; @@ -318,6 +341,7 @@ gboolean gldi_cosmic_toplevel_try_init (struct wl_registry *registry) GldiWindowManagerBackend wmb; memset (&wmb, 0, sizeof (GldiWindowManagerBackend)); wmb.get_active_window = gldi_wayland_wm_get_active_window; + wmb.move_to_viewport_abs = _move_to_nth_desktop; // wmb.move_to_nth_desktop = _move_to_nth_desktop; wmb.show = _show; wmb.close = _close; @@ -340,6 +364,7 @@ gboolean gldi_cosmic_toplevel_try_init (struct wl_registry *registry) wmb.can_minimize_maximize_close = _can_minimize_maximize_close; // wmb.get_id = _get_id; wmb.pick_window = gldi_wayland_wm_pick_window; + wmb.flags = GINT_TO_POINTER (GLDI_WM_NO_VIEWPORT_OVERLAP | GLDI_WM_GEOM_REL_TO_VIEWPORT | GLDI_WM_HAVE_WORKSPACES); wmb.name = "Cosmic"; gldi_windows_manager_register_backend (&wmb); diff --git a/src/implementations/cairo-dock-cosmic-workspaces.c b/src/implementations/cairo-dock-cosmic-workspaces.c new file mode 100644 index 00000000..b51390af --- /dev/null +++ b/src/implementations/cairo-dock-cosmic-workspaces.c @@ -0,0 +1,558 @@ +/* + * cairo-dock-cosmic-workspaces.c -- desktop / workspace management + * facilities for Cosmic and compatible + * + * Copyright 2024 Daniel Kondor + * + * 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 3 + * 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, see . + * + */ + +#include "cairo-dock-log.h" +#include "cairo-dock-desktop-manager.h" +#include "cairo-dock-windows-manager.h" +#include "cairo-dock-cosmic-workspaces.h" + +struct wl_output *s_ws_output = NULL; + +/* + * notes: + * Labwc: does not send coordinates (but seems to send the workspaces in the correct order), + * non-active workspaces have the "hidden" flag, workspaces can be activated, but no other + * action is supported; multiple outputs share workspaces, always only one workspace group + * Cosmic: coordinates: x starts from 1, y all zero (default config / layout), workspaces + * can be activated, no other action + */ + + +typedef struct _CosmicWS { + struct zcosmic_workspace_handle_v1 *handle; // protocol object representing this desktop + char *name; // can be NULL if no name was delivered + guint x, y; // x and y coordinates; (guint)-1 means invalid + guint pending_x, pending_y; + char *pending_name; + gboolean bRemoved; // set to TRUE when receiving the removed event +} CosmicWS; + +#define INVALID_COORD (guint)-1 + +/// private variables -- track the current state of workspaces +static unsigned int s_iNumDesktops = 0; +static CosmicWS **desktops = NULL; // allocated when the first workspace is added +static unsigned int s_iDesktopCap = 0; // capacity of the above array +static unsigned int s_iCurrent = 0; // index into desktops array with the currently active desktop +static unsigned int s_iPending = 0; // workspace with pending activation + +static gboolean s_bPendingAdded = FALSE; // workspace was added or removed, need to recalculate layout + +static struct zcosmic_workspace_manager_v1 *s_pWSManager = NULL; +static struct zcosmic_workspace_group_handle_v1 *s_pWSGroup = NULL; // we support having only one workspace group for now + +static gboolean bValidX = FALSE; // if x coordinates are valid +static gboolean bValidY = FALSE; // if y coordinates are valid +static guint s_iXOffset = 0; // offset to add to x coordinates (if they don't start from 0) +static guint s_iYOffset = 0; // offset to add to x coordinates (if they don't start from 0) + +// we don't want arbitrarily large coordinates +#define MAX_DESKTOP_DIM 16 +#define MAX_DESKTOP_NUM 128 + +/* to be called after all workspaces have been delivered, from the workspace manager's done event */ +static void _update_desktop_layout () +{ + guint rows = 0, cols = 0; + g_desktopGeometry.iNbDesktops = 1; + + unsigned int i; + gboolean have_invalid_x = FALSE; + gboolean have_invalid_y = FALSE; + gboolean all_invalid_y = TRUE; + s_iXOffset = G_MAXUINT; + s_iYOffset = G_MAXUINT; + for (i = 0; i < s_iNumDesktops; i++) + { + guint x = desktops[i]->x; + guint y = desktops[i]->y; + + if (x == INVALID_COORD) have_invalid_x = TRUE; + else + { + if (x > cols) cols = x; + if (x < s_iXOffset) s_iXOffset = x; + } + + if (y == INVALID_COORD) have_invalid_y = TRUE; + else { + all_invalid_y = FALSE; + if (y > rows) rows = y; + if (y < s_iYOffset) s_iYOffset = y; + } + } + + if (s_iNumDesktops > 0 && !have_invalid_x) + { + if (have_invalid_y && all_invalid_y && cols < MAX_DESKTOP_NUM && cols/8 <= s_iNumDesktops) + { + bValidX = TRUE; + bValidY = FALSE; + g_desktopGeometry.iNbViewportX = cols + 1 - s_iXOffset; + g_desktopGeometry.iNbViewportY = 1; + return; + } + if (!have_invalid_y && cols < MAX_DESKTOP_DIM && rows < MAX_DESKTOP_DIM && rows*cols < MAX_DESKTOP_NUM && (rows*cols) / 8 <= s_iNumDesktops) + { + bValidX = TRUE; + bValidY = TRUE; + g_desktopGeometry.iNbViewportX = cols + 1 - s_iXOffset; + g_desktopGeometry.iNbViewportY = rows + 1 - s_iYOffset; + return; + } + } + + bValidX = FALSE; + bValidY = FALSE; + g_desktopGeometry.iNbViewportX = s_iNumDesktops ? s_iNumDesktops : 1; + g_desktopGeometry.iNbViewportY = 1; +} + +static void _update_current_desktop (void) +{ + g_desktopGeometry.iCurrentDesktop = 0; + if (bValidX && s_iCurrent < s_iNumDesktops) + { + CosmicWS *desktop = desktops[s_iCurrent]; + g_desktopGeometry.iCurrentViewportX = desktop->x - s_iXOffset; + if (bValidY) g_desktopGeometry.iCurrentViewportY = desktop->y - s_iYOffset; + else g_desktopGeometry.iCurrentViewportY = 0; + } + else + { + g_desktopGeometry.iCurrentViewportX = s_iCurrent; + g_desktopGeometry.iCurrentViewportY = 0; + } +} + +static void _name (void *data, struct zcosmic_workspace_handle_v1*, const char *name) +{ + CosmicWS *desktop = (CosmicWS*)data; + g_free (desktop->pending_name); + desktop->pending_name = g_strdup ((gchar *)name); +} + +static void _coordinates (void *data, struct zcosmic_workspace_handle_v1*, struct wl_array *coords) +{ + CosmicWS *desktop = (CosmicWS*)data; + uint32_t *cdata = (uint32_t*)coords->data; + size_t size = coords->size; + if (size > 2*sizeof(uint32_t) || size < sizeof(uint32_t)) + { + // too many or no coordinates, we cannot use them + desktop->pending_x = INVALID_COORD; + desktop->pending_y = INVALID_COORD; + } + else + { + // we have at least one coordinate + desktop->pending_x = cdata[0]; + if (size == 2*sizeof(uint32_t)) desktop->pending_y = cdata[1]; + else desktop->pending_y = INVALID_COORD; + } +} + +static void _state (void *data, struct zcosmic_workspace_handle_v1*, struct wl_array *state) +{ + gboolean bActivated = FALSE; +/* gboolean bUrgent = FALSE; -- we do not care about these + gboolean bHidden = FALSE; */ + int i; + uint32_t* stdata = (uint32_t*)state->data; + for (i = 0; i*sizeof(uint32_t) < state->size; i++) + { + if (stdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_STATE_ACTIVE) + bActivated = TRUE; +/* else if (stdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_STATE_URGENT) + bUrgent = TRUE; + else if (stdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_STATE_HIDDEN) + bHidden = TRUE; */ + } + + if (bActivated) + { + CosmicWS *desktop = (CosmicWS*)data; + unsigned int j; + for (j = 0; j < s_iNumDesktops; j++) + if (desktops[j] == desktop) + { + s_iPending = j; + return; + } + cd_critical ("cosmic-workspaces: could not find currently activated desktop!"); + } +} + +static void _capabilities (void*, G_GNUC_UNUSED struct zcosmic_workspace_handle_v1* handle, G_GNUC_UNUSED struct wl_array* cap) +{ +/* uint32_t* capdata = (uint32_t*)cap->data; + cd_warning ("workspace capabilities: %p", handle); + int i; + for (i = 0; i*sizeof(uint32_t) < cap->size; i++) + { + if (capdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_ACTIVATE) + g_print ("workspace can be activated\n"); + else if (capdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_DEACTIVATE) + g_print ("workspace can be deactivated\n"); + else if (capdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_REMOVE) + g_print ("workspace can be removed\n"); + else if (capdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_RENAME) + g_print ("workspace can be renamed\n"); + else if (capdata[i] == ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_SET_TILING_STATE) + g_print ("workspace can set tiling state\n"); + } */ +} + +static void _removed (void *data, struct zcosmic_workspace_handle_v1*) +{ + CosmicWS *desktop = (CosmicWS*)data; + desktop->bRemoved = TRUE; +} + + +static void _free_workspace (CosmicWS *desktop) +{ + g_free (desktop->name); + g_free (desktop->pending_name); + zcosmic_workspace_handle_v1_destroy (desktop->handle); + g_free (desktop); +} + + +static void _tiling_state (void*, struct zcosmic_workspace_handle_v1*, uint32_t) +{ + /* don't care */ +} + +static const struct zcosmic_workspace_handle_v1_listener desktop_listener = { + .name = _name, + .coordinates = _coordinates, + .state = _state, + .capabilities = _capabilities, + .remove = _removed, + .tiling_state = _tiling_state +}; + + +static void _group_capabilities (void*, G_GNUC_UNUSED struct zcosmic_workspace_group_handle_v1* handle, G_GNUC_UNUSED struct wl_array* cap) +{ + /* don't care */ +/* uint32_t* capdata = (uint32_t*)cap->data; + int i; + for (i = 0; i*sizeof(uint32_t) < cap->size; i++) + { + if (capdata[i] == ZCOSMIC_WORKSPACE_GROUP_HANDLE_V1_CREATE_WORKSPACE) + cd_warning ("workspace group can be add workspaces: %p\n", handle); + } */ +} + +static void _output_enter (void*, struct zcosmic_workspace_group_handle_v1* handle, struct wl_output* output) +{ + if (handle == s_pWSGroup) s_ws_output = output; +} + +static void _output_leave (void*, struct zcosmic_workspace_group_handle_v1* handle, struct wl_output* output) +{ + if (handle == s_pWSGroup && output == s_ws_output) s_ws_output = NULL; +} + +static void _desktop_created (void*, struct zcosmic_workspace_group_handle_v1 *manager, + struct zcosmic_workspace_handle_v1 *new_workspace) +{ + if (manager != s_pWSGroup) + { + // this is not "our" manager, we don't care + zcosmic_workspace_handle_v1_destroy (new_workspace); + return; + } + + CosmicWS *desktop = g_new0 (CosmicWS, 1); + desktop->x = INVALID_COORD; + desktop->y = INVALID_COORD; + desktop->pending_x = INVALID_COORD; + desktop->pending_y = INVALID_COORD; + if (s_iNumDesktops >= s_iDesktopCap) + { + desktops = g_renew (CosmicWS*, desktops, s_iNumDesktops + 16); + s_iDesktopCap = s_iNumDesktops + 16; + } + desktops[s_iNumDesktops] = desktop; + s_iNumDesktops++; + + desktop->handle = new_workspace; + zcosmic_workspace_handle_v1_add_listener (new_workspace, &desktop_listener, desktop); + s_bPendingAdded = TRUE; +} + +static void _group_removed (void*, struct zcosmic_workspace_group_handle_v1 *handle) +{ + if (handle == s_pWSGroup) + { + + if (s_iNumDesktops) + { + // the compositor should have deleted all desktop handles before + cd_critical ("cosmic-workspaces: non-empty workspace group removed!"); + do { + --s_iNumDesktops; + _free_workspace (desktops[s_iNumDesktops]); + } while(s_iNumDesktops); + } + + g_free (desktops); + desktops = NULL; + s_iDesktopCap = 0; + _update_desktop_layout (); + gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_GEOMETRY_CHANGED, FALSE); + s_pWSGroup = NULL; + } + zcosmic_workspace_group_handle_v1_destroy (handle); +} + + +static const struct zcosmic_workspace_group_handle_v1_listener group_listener = { + .capabilities = _group_capabilities, + .output_enter = _output_enter, + .output_leave = _output_leave, + .workspace = _desktop_created, + .remove = _group_removed +}; + + +static void _new_workspace_group (void*, struct zcosmic_workspace_manager_v1*, struct zcosmic_workspace_group_handle_v1 *new_group) +{ + if (s_pWSGroup) + { + cd_warning ("cosmic-workspaces: multiple workspace groups are not supported!\n"); + zcosmic_workspace_group_handle_v1_add_listener (new_group, &group_listener, NULL); + zcosmic_workspace_group_handle_v1_destroy (new_group); + } + else + { + s_pWSGroup = new_group; + zcosmic_workspace_group_handle_v1_add_listener (new_group, &group_listener, NULL); + } +} + +static void _done (void*, struct zcosmic_workspace_manager_v1*) +{ + gboolean bRemoved = FALSE; // if any workspace was removed + gboolean bCoords = FALSE; // any of the coordinates changed + gboolean bName = FALSE; // any of the names changed + // check all workspaces + unsigned int i, j = 0; + for (i = 0; i < s_iNumDesktops; i++) + { + if (desktops[i]->bRemoved) + { + _free_workspace (desktops[i]); + desktops[i] = NULL; + bRemoved = TRUE; + } + else { + if (desktops[i]->pending_x != desktops[i]->x) + { + desktops[i]->x = desktops[i]->pending_x; + bCoords = TRUE; + } + if (desktops[i]->pending_y != desktops[i]->y) + { + desktops[i]->y = desktops[i]->pending_y; + bCoords = TRUE; + } + if (desktops[i]->pending_name) + { + g_free (desktops[i]->name); + desktops[i]->name = desktops[i]->pending_name; + desktops[i]->pending_name = NULL; + bName = TRUE; + } + + if (i != j) desktops[j] = desktops[i]; + j++; + } + } + + s_iNumDesktops = j; // remaining number of desktops + + if (bRemoved || bCoords || s_bPendingAdded) + { + s_iCurrent = s_iPending; + if (s_iCurrent >= s_iNumDesktops) s_iCurrent = s_iNumDesktops ? (s_iNumDesktops - 1) : 0; + _update_desktop_layout (); + _update_current_desktop (); + gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_GEOMETRY_CHANGED, FALSE); + gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_CHANGED); + } + else if (s_iCurrent != s_iPending) + { + s_iCurrent = s_iPending; + _update_current_desktop (); + gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_CHANGED); + } + + if (bName) gldi_object_notify (&myDesktopMgr, NOTIFICATION_DESKTOP_NAMES_CHANGED); + s_bPendingAdded = FALSE; +} + +static void _finished (void*, struct zcosmic_workspace_manager_v1 *handle) +{ + zcosmic_workspace_manager_v1_destroy (handle); +} + +static const struct zcosmic_workspace_manager_v1_listener manager_listener = { + .workspace_group = _new_workspace_group, + .done = _done, + .finished = _finished +}; + + +static gchar** _get_desktops_names (void) +{ + gchar **ret = g_new0 (gchar*, s_iNumDesktops + 1); // + 1, so that it is a null-terminated list, as expected by the switcher plugin + unsigned int i; + for (i = 0; i < s_iNumDesktops; i++) ret[i] = g_strdup (desktops[i]->name); + return ret; +} + +static unsigned int _get_ix (guint x, guint y) +{ + unsigned int i = 0; + if (bValidX) + { + for (; i < s_iNumDesktops; i++) + { + if (!desktops[i]->bRemoved && (desktops[i]->x == x + s_iXOffset) + && (!bValidY || desktops[i]->y == y + s_iYOffset)) + break; + } + } + else i = x; + return i; +} + +static gboolean _set_current_desktop (G_GNUC_UNUSED int iDesktopNumber, int iViewportNumberX, int iViewportNumberY) +{ + // desktop number is ignored (it should be 0) + if (iViewportNumberX >= 0 && iViewportNumberY >= 0) + { + unsigned int iReq = _get_ix ((guint)iViewportNumberX, (guint)iViewportNumberY); + if (iReq < s_iNumDesktops) + { + zcosmic_workspace_handle_v1_activate (desktops[iReq]->handle); + zcosmic_workspace_manager_v1_commit (s_pWSManager); + return TRUE; // we don't know if we succeeded + } + } + cd_warning ("cosmic-workspaces: invalid workspace requested!\n"); + return FALSE; +} + +/* currently not supported on either Cosmic or Labwc, easier to disable +static void _add_workspace (void) +{ + if (s_pWSGroup && s_pWSManager) + { + char *name = g_strdup_printf ("Workspace %u", s_iNumDesktops + 1); + zcosmic_workspace_group_handle_v1_create_workspace (s_pWSGroup, name); + zcosmic_workspace_manager_v1_commit (s_pWSManager); + g_free (name); + } +} + +static void _remove_workspace (void) +{ + if (s_iNumDesktops <= 1 || !s_pWSManager) return; + zcosmic_workspace_handle_v1_remove (desktops[s_iNumDesktops - 1]->handle); + zcosmic_workspace_manager_v1_commit (s_pWSManager); +} +*/ + +static uint32_t protocol_id, protocol_version; +static gboolean protocol_found = FALSE; + +gboolean gldi_cosmic_workspaces_match_protocol (uint32_t id, const char *interface, uint32_t version) +{ + if (!strcmp(interface, zcosmic_workspace_manager_v1_interface.name)) + { + protocol_found = TRUE; + protocol_id = id; + protocol_version = version; + if ((uint32_t)zcosmic_workspace_manager_v1_interface.version < protocol_version) + protocol_version = zcosmic_workspace_manager_v1_interface.version; + return TRUE; + } + return FALSE; +} + +gboolean gldi_cosmic_workspaces_try_init (struct wl_registry *registry) +{ + if (!protocol_found) return FALSE; + s_pWSManager = wl_registry_bind (registry, protocol_id, &zcosmic_workspace_manager_v1_interface, protocol_version); + if (!s_pWSManager) return FALSE; + + GldiDesktopManagerBackend dmb; + memset (&dmb, 0, sizeof (GldiDesktopManagerBackend)); + dmb.set_current_desktop = _set_current_desktop; + dmb.get_desktops_names = _get_desktops_names; +// dmb.add_workspace = _add_workspace; +// dmb.remove_last_workspace = _remove_workspace; + gldi_desktop_manager_register_backend (&dmb, "cosmic-workspaces"); + + zcosmic_workspace_manager_v1_add_listener (s_pWSManager, &manager_listener, NULL); + return TRUE; +} + +struct zcosmic_workspace_handle_v1 *gldi_cosmic_workspaces_get_handle (int x, int y) +{ + unsigned int iReq = _get_ix ((guint)x, (guint)y); + if (iReq < s_iNumDesktops) + return desktops[iReq]->handle; + cd_warning ("cosmic-workspaces: invalid workspace requested!\n"); + return NULL; +} + +void gldi_cosmic_workspaces_update_window (GldiWindowActor *actor, struct zcosmic_workspace_handle_v1 *handle) +{ + unsigned int i; + for (i = 0; i < s_iNumDesktops; i++) + { + if (desktops[i]->handle == handle) + { + actor->iNumDesktop = 0; + if (bValidX) + { + actor->iViewPortX = desktops[i]->x - s_iXOffset; + if (bValidY) actor->iViewPortY = desktops[i]->y - s_iYOffset; + else actor->iViewPortY = 0; + } + else + { + actor->iViewPortX = i; + actor->iViewPortY = 0; + } + gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESKTOP_CHANGED, actor); + return; + } + } + cd_warning ("cosmic-workspaces: workspace not found!\n"); +} + + diff --git a/src/implementations/cairo-dock-cosmic-workspaces.h b/src/implementations/cairo-dock-cosmic-workspaces.h new file mode 100644 index 00000000..d207caef --- /dev/null +++ b/src/implementations/cairo-dock-cosmic-workspaces.h @@ -0,0 +1,36 @@ +/* + * cairo-dock-cosmic-workspaces.h -- desktop / workspace management + * facilities for Cosmic and compatible + * + * Copyright 2024 Daniel Kondor + * + * 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 3 + * 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, see . + * + */ + +#ifndef CAIRO_DOCK_COSMIC_WORKSPACES_H +#define CAIRO_DOCK_COSMIC_WORKSPACES_H + +#include +#include +#include "wayland-cosmic-workspace-client-protocol.h" + +extern struct wl_output *s_ws_output; + +gboolean gldi_cosmic_workspaces_match_protocol (uint32_t id, const char *interface, uint32_t version); +gboolean gldi_cosmic_workspaces_try_init (struct wl_registry *registry); +struct zcosmic_workspace_handle_v1 *gldi_cosmic_workspaces_get_handle (int x, int y); +void gldi_cosmic_workspaces_update_window (GldiWindowActor *actor, struct zcosmic_workspace_handle_v1 *handle); + +#endif + diff --git a/src/implementations/cairo-dock-plasma-virtual-desktop.c b/src/implementations/cairo-dock-plasma-virtual-desktop.c index bed9209e..c520cad3 100644 --- a/src/implementations/cairo-dock-plasma-virtual-desktop.c +++ b/src/implementations/cairo-dock-plasma-virtual-desktop.c @@ -271,10 +271,25 @@ static gboolean _set_current_desktop (G_GNUC_UNUSED int iDesktopNumber, int iVie return FALSE; } +static struct org_kde_plasma_virtual_desktop_management* s_pmanager = NULL; + +static void _add_workspace (void) +{ + if (!s_pmanager) return; + char *name = g_strdup_printf ("Workspace %u", s_iNumDesktops + 1); + org_kde_plasma_virtual_desktop_management_request_create_virtual_desktop (s_pmanager, name, s_iNumDesktops); + g_free (name); +} + +static void _remove_workspace (void) +{ + if (s_iNumDesktops <= 1 || !s_pmanager) return; + org_kde_plasma_virtual_desktop_management_request_remove_virtual_desktop (s_pmanager, desktops[s_iNumDesktops - 1]->id); +} + static uint32_t protocol_id, protocol_version; static gboolean protocol_found = FALSE; -static struct org_kde_plasma_virtual_desktop_management* s_pmanager = NULL; gboolean gldi_plasma_virtual_desktop_match_protocol (uint32_t id, const char *interface, uint32_t version) { @@ -298,8 +313,10 @@ gboolean gldi_plasma_virtual_desktop_try_init (struct wl_registry *registry) GldiDesktopManagerBackend dmb; memset (&dmb, 0, sizeof (GldiDesktopManagerBackend)); - dmb.set_current_desktop = _set_current_desktop; - dmb.get_desktops_names = _get_desktops_names; + dmb.set_current_desktop = _set_current_desktop; + dmb.get_desktops_names = _get_desktops_names; + dmb.add_workspace = _add_workspace; + dmb.remove_last_workspace = _remove_workspace; gldi_desktop_manager_register_backend (&dmb, "plasma-virtual-desktop"); org_kde_plasma_virtual_desktop_management_add_listener (s_pmanager, &manager_listener, NULL); diff --git a/src/implementations/cairo-dock-plasma-window-manager.c b/src/implementations/cairo-dock-plasma-window-manager.c index 11aed43d..5b365720 100644 --- a/src/implementations/cairo-dock-plasma-window-manager.c +++ b/src/implementations/cairo-dock-plasma-window-manager.c @@ -33,6 +33,8 @@ * (this probably needs support for the plasma-virtual-desktop protocol) */ +#define _GNU_SOURCE + #include #include "wayland-plasma-window-management-client-protocol.h" #include "cairo-dock-windows-manager.h" @@ -44,11 +46,33 @@ #include "cairo-dock-plasma-virtual-desktop.h" #include +#include typedef struct org_kde_plasma_window pwhandle; +static GldiObjectManager myPlasmaWindowObjectMgr; + +struct _GldiPlasmaWindowActor { + GldiWaylandWindowActor wactor; + + // uuid, can be used to identify windows, e.g. when updating the stacking order + char *uuid; +// char *themed_icon_name; -- supplied as a separate event, not sure if we need it + unsigned int pid; // pid of the process associated with this window + unsigned int sigkill_timeout; // set to an event source if the user requested to kill this process + unsigned int cap_and_state; // capabilites and state that is not stored elsewhere +}; +typedef struct _GldiPlasmaWindowActor GldiPlasmaWindowActor; + +typedef enum { + NB_NOTIFICATIONS_PLASMA_WINDOW_MANAGER = NB_NOTIFICATIONS_WINDOWS +} CairoPlasmaWMNotifications; + +static uint32_t protocol_version = 0; +static GHashTable *s_hIDTable = NULL; + // window manager interface static void _move_to_nth_desktop (GldiWindowActor *actor, G_GNUC_UNUSED int iNumDesktop, @@ -84,6 +108,17 @@ static void _minimize (GldiWindowActor *actor) ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MINIMIZED, ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MINIMIZED); } +/* does not work this way, would need to use timeout +static void _lower (GldiWindowActor *actor) +{ + GldiWaylandWindowActor *wactor = (GldiWaylandWindowActor *)actor; + org_kde_plasma_window_set_state (wactor->handle, + ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_BELOW, + ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_BELOW); + org_kde_plasma_window_set_state (wactor->handle, + ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_BELOW, 0); +} +*/ static void _maximize (GldiWindowActor *actor, gboolean bMaximize) { GldiWaylandWindowActor *wactor = (GldiWaylandWindowActor *)actor; @@ -98,6 +133,13 @@ static void _fullscreen (GldiWindowActor *actor, gboolean bFullScreen) ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_FULLSCREEN, bFullScreen ? ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_FULLSCREEN : 0); } +static void _set_above (GldiWindowActor *actor, gboolean bAbove) +{ + GldiWaylandWindowActor *wactor = (GldiWaylandWindowActor *)actor; + org_kde_plasma_window_set_state (wactor->handle, + ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_ABOVE, + bAbove ? ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_ABOVE : 0); +} static GldiWindowActor* _get_transient_for(GldiWindowActor* actor) { @@ -108,12 +150,28 @@ static GldiWindowActor* _get_transient_for(GldiWindowActor* actor) return (GldiWindowActor*)pactor; } +static void _is_above_or_below (GldiWindowActor *actor, gboolean *bIsAbove, gboolean *bIsBelow) +{ + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor *)actor; + *bIsAbove = !!(pactor->cap_and_state & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_ABOVE); + *bIsBelow = !!(pactor->cap_and_state & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_KEEP_BELOW); +} + +static void _set_sticky (GldiWindowActor *actor, gboolean bSticky) +{ + GldiWaylandWindowActor *wactor = (GldiWaylandWindowActor *)actor; + // note: this seems to do nothing + org_kde_plasma_window_set_state (wactor->handle, + ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ON_ALL_DESKTOPS, + bSticky ? ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ON_ALL_DESKTOPS : 0); +} + static void _can_minimize_maximize_close ( G_GNUC_UNUSED GldiWindowActor *actor, gboolean *bCanMinimize, gboolean *bCanMaximize, gboolean *bCanClose) { - // we don't know, set everything to true - *bCanMinimize = TRUE; - *bCanMaximize = TRUE; - *bCanClose = TRUE; + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor *)actor; + *bCanMinimize = !!(pactor->cap_and_state & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MINIMIZABLE); + *bCanMaximize = !!(pactor->cap_and_state & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MAXIMIZABLE); + *bCanClose = !!(pactor->cap_and_state & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_CLOSEABLE); } /// TODO: which one of these two are really used? In cairo-dock-X-manager.c, @@ -137,6 +195,29 @@ static void _set_minimize_position (GldiWindowActor *actor, GtkWidget* pContaine } +gboolean _send_sigkill (void *data) +{ + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor*)data; + if (pactor->pid) kill (pactor->pid, SIGKILL); + pactor->sigkill_timeout = 0; + return G_SOURCE_REMOVE; +} + +// kill the process corresponding to a window +// note that this depends on the pid provided by KWin and is thus +// inherently racy, if the pid is reused between the process exit and +// KWin sending the unmapped event +static void _kill (GldiWindowActor *actor) +{ + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor*)actor; + if (!pactor->pid) return; + // 1. we try sigterm, so that the app has a chance to shut down gracefully + kill (pactor->pid, SIGTERM); + // 2. after 2s, we send sigkill (if the window is still not closed) + pactor->sigkill_timeout = g_timeout_add (2000, _send_sigkill, pactor); +} + + /** callbacks **/ static void _gldi_toplevel_title_cb (void *data, G_GNUC_UNUSED pwhandle *handle, const char *title) { @@ -153,14 +234,27 @@ static void _gldi_toplevel_appid_cb (void *data, G_GNUC_UNUSED pwhandle *handle, static void _gldi_toplevel_state_cb (void *data, G_GNUC_UNUSED pwhandle *handle, uint32_t flags) { if (!data) return; - GldiWaylandWindowActor* wactor = (GldiWaylandWindowActor*)data; + GldiPlasmaWindowActor* pactor = (GldiPlasmaWindowActor*)data; + + // save the flags that contains other useful info (keep above / below, capabilities) + pactor->cap_and_state = flags; + GldiWaylandWindowActor* wactor = (GldiWaylandWindowActor*)data; gldi_wayland_wm_maximized_changed (wactor, !!(flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MAXIMIZED), FALSE); gldi_wayland_wm_minimized_changed (wactor, !!(flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MINIMIZED), FALSE); gldi_wayland_wm_fullscreen_changed (wactor, !!(flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_FULLSCREEN), FALSE); gldi_wayland_wm_attention_changed (wactor, !!(flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_DEMANDS_ATTENTION), FALSE); gldi_wayland_wm_skip_changed (wactor, !!(flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SKIPTASKBAR), FALSE); - if (flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ACTIVE) gldi_wayland_wm_activated (wactor, FALSE); + gldi_wayland_wm_sticky_changed (wactor, !!(flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ON_ALL_DESKTOPS), FALSE); + if (flags & ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ACTIVE) + { + gldi_wayland_wm_activated (wactor, FALSE); + // versions >= 12 and < 17 have stacking_order_uuid_changed which + // is handled separately below; for versions >= 17, we would need + // to support stacking_order_changed_2 + if (protocol_version < 12 || protocol_version >= 17) + gldi_wayland_wm_stack_on_top ((GldiWindowActor*)wactor); + } if (wactor->init_done) gldi_wayland_wm_done (wactor); } @@ -175,6 +269,12 @@ static void _gldi_toplevel_done_cb ( void *data, G_GNUC_UNUSED pwhandle *handle) static void _gldi_toplevel_closed_cb (void *data, G_GNUC_UNUSED pwhandle *handle) { + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor*)data; + if (pactor->sigkill_timeout) + { + g_source_remove (pactor->sigkill_timeout); + pactor->sigkill_timeout = 0; + } gldi_wayland_wm_closed (data, TRUE); } @@ -187,7 +287,6 @@ static void _gldi_toplevel_parent_cb (void* data, G_GNUC_UNUSED pwhandle *handle static void _gldi_toplevel_geometry_cb (void* data, G_GNUC_UNUSED pwhandle *handle, int32_t x, int32_t y, uint32_t w, uint32_t h) { - //!! TODO !! GldiWaylandWindowActor* wactor = (GldiWaylandWindowActor*)data; GldiWindowActor* actor = (GldiWindowActor*)wactor; actor->windowGeometry.width = w; @@ -227,9 +326,10 @@ static void _gldi_toplevel_icon_changed_cb (G_GNUC_UNUSED void* data, G_GNUC_UNU /* don't care */ } -static void _gldi_toplevel_pid_changed_cb (G_GNUC_UNUSED void* data, G_GNUC_UNUSED pwhandle *handle, G_GNUC_UNUSED uint32_t pid) +static void _gldi_toplevel_pid_changed_cb (void* data, G_GNUC_UNUSED pwhandle *handle, uint32_t pid) { - /* don't care */ + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor*)data; + if (pactor) pactor->pid = pid; } static void _gldi_toplevel_application_menu_cb (G_GNUC_UNUSED void* data, G_GNUC_UNUSED pwhandle *handle, @@ -258,19 +358,18 @@ static struct org_kde_plasma_window_listener gldi_toplevel_handle_interface = { }; -static void _destroy (gpointer obj) -{ - org_kde_plasma_window_destroy((pwhandle*)obj); -} - /* register new toplevel */ static void _new_toplevel ( G_GNUC_UNUSED void *data, struct org_kde_plasma_window_management *manager, G_GNUC_UNUSED uint32_t id, const char *uuid) { - // fprintf(stderr,"New toplevel: %p\n", handle); + if (!uuid) return; // sanity check + pwhandle* handle = org_kde_plasma_window_management_get_window_by_uuid (manager, uuid); - GldiWaylandWindowActor* wactor = gldi_wayland_wm_new_toplevel (handle); + GldiPlasmaWindowActor *pactor = (GldiPlasmaWindowActor*)gldi_object_new (&myPlasmaWindowObjectMgr, handle); + pactor->uuid = g_strdup (uuid); + g_hash_table_insert (s_hIDTable, pactor->uuid, pactor); + GldiWaylandWindowActor *wactor = (GldiWaylandWindowActor*)pactor; org_kde_plasma_window_set_user_data (handle, wactor); GldiWindowActor *actor = (GldiWindowActor*)wactor; // set initial position to something sensible in case we don't get it updated @@ -305,9 +404,26 @@ static void _stacking_order_changed_cb( G_GNUC_UNUSED void *data, } static void _stacking_order_uuid_changed_cb ( G_GNUC_UNUSED void *data, - G_GNUC_UNUSED struct org_kde_plasma_window_management *org_kde_plasma_window_management, G_GNUC_UNUSED const char *uuids) -{ - /* don't care */ + G_GNUC_UNUSED struct org_kde_plasma_window_management *org_kde_plasma_window_management, const char *uuids) +{ + int i = 0; + GString *str = NULL; + do { + const char *next = strchrnul (uuids, ';'); + if (!str) str = g_string_sized_new (next - uuids + 1); + g_string_overwrite_len (str, 0, uuids, next - uuids); + void *ptr = g_hash_table_lookup (s_hIDTable, str->str); + if (ptr) + { + GldiWindowActor *actor = (GldiWindowActor*)ptr; + actor->iStackOrder = i; + i++; + } + uuids = next; + if (*uuids) uuids++; // skip the trailing ';' + } while (*uuids); + if (str) g_string_free (str, TRUE); + gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_Z_ORDER_CHANGED, NULL); } static struct org_kde_plasma_window_management_listener gldi_toplevel_manager = { @@ -319,7 +435,7 @@ static struct org_kde_plasma_window_management_listener gldi_toplevel_manager = }; static struct org_kde_plasma_window_management* s_ptoplevel_manager = NULL; -static uint32_t protocol_id, protocol_version; +static uint32_t protocol_id; static gboolean protocol_found = FALSE; /// Desktop management functions @@ -330,8 +446,24 @@ static gboolean _show_hide_desktop (gboolean bShow) return TRUE; } +void _reset_object (GldiObject* obj) +{ + GldiPlasmaWindowActor* pactor = (GldiPlasmaWindowActor*)obj; + if (pactor) + { + g_hash_table_remove (s_hIDTable, pactor->uuid); + g_free (pactor->uuid); + org_kde_plasma_window_destroy((pwhandle*)pactor->wactor.handle); + } +} + static void gldi_plasma_window_manager_init () { + // hash table to map uuids to windows + // note: no free functions, the keys are the uuids stored in (and owned by) + // the GldiPlasmaWindowActor, they are freed together + s_hIDTable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + org_kde_plasma_window_management_add_listener(s_ptoplevel_manager, &gldi_toplevel_manager, NULL); // register window manager GldiWindowManagerBackend wmb; @@ -340,12 +472,12 @@ static void gldi_plasma_window_manager_init () wmb.move_to_viewport_abs = _move_to_nth_desktop; wmb.show = _show; wmb.close = _close; - // wmb.kill = _kill; + wmb.kill = _kill; wmb.minimize = _minimize; // wmb.lower = _lower; wmb.maximize = _maximize; wmb.set_fullscreen = _fullscreen; - // wmb.set_above = _set_above; + wmb.set_above = _set_above; wmb.set_minimize_position = _set_minimize_position; wmb.set_thumbnail_area = _set_thumbnail_area; // wmb.set_window_border = _set_window_border; @@ -353,13 +485,12 @@ static void gldi_plasma_window_manager_init () // wmb.get_thumbnail_surface = _get_thumbnail_surface; // wmb.get_texture = _get_texture; 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; + wmb.is_above_or_below = _is_above_or_below; + wmb.set_sticky = _set_sticky; wmb.can_minimize_maximize_close = _can_minimize_maximize_close; // wmb.get_id = _get_id; wmb.pick_window = gldi_wayland_wm_pick_window; - wmb.flags = GINT_TO_POINTER (GLDI_WM_NO_VIEWPORT_OVERLAP | GLDI_WM_GEOM_REL_TO_VIEWPORT); + wmb.flags = GINT_TO_POINTER (GLDI_WM_NO_VIEWPORT_OVERLAP | GLDI_WM_GEOM_REL_TO_VIEWPORT | GLDI_WM_HAVE_WINDOW_GEOMETRY | GLDI_WM_HAVE_WORKSPACES); wmb.name = "plasma"; gldi_windows_manager_register_backend (&wmb); @@ -369,7 +500,19 @@ static void gldi_plasma_window_manager_init () dmb.show_hide_desktop = _show_hide_desktop; gldi_desktop_manager_register_backend (&dmb, "plasma-window-management"); - gldi_wayland_wm_init (_destroy); + gldi_wayland_wm_init (NULL); + + // extend the generic Wayland toplevel object manager + memset (&myPlasmaWindowObjectMgr, 0, sizeof (GldiObjectManager)); + myPlasmaWindowObjectMgr.cName = "plasma-window-manager"; + myPlasmaWindowObjectMgr.iObjectSize = sizeof (GldiPlasmaWindowActor); + // interface + myPlasmaWindowObjectMgr.init_object = NULL; + myPlasmaWindowObjectMgr.reset_object = _reset_object; + // signals + gldi_object_install_notifications (&myPlasmaWindowObjectMgr, NB_NOTIFICATIONS_PLASMA_WINDOW_MANAGER); + // parent object + gldi_object_set_manager (GLDI_OBJECT (&myPlasmaWindowObjectMgr), &myWaylandWMObjectMgr); } diff --git a/src/implementations/cairo-dock-wayland-manager.c b/src/implementations/cairo-dock-wayland-manager.c index f380ba43..3a211ef5 100644 --- a/src/implementations/cairo-dock-wayland-manager.c +++ b/src/implementations/cairo-dock-wayland-manager.c @@ -49,6 +49,7 @@ #include "cairo-dock-plasma-window-manager.h" #include "cairo-dock-cosmic-toplevel.h" #include "cairo-dock-plasma-virtual-desktop.h" +#include "cairo-dock-cosmic-workspaces.h" #endif #include "cairo-dock-wayland-hotspots.h" #include "cairo-dock-egl.h" @@ -443,7 +444,12 @@ void gldi_wayland_grab_keyboard (GldiContainer *pContainer) void gldi_wayland_release_keyboard ( G_GNUC_UNUSED GldiContainer *pContainer) { GldiWindowActor *actor = gldi_windows_get_active (); - if (actor && !actor->bIsHidden) gldi_window_show (actor); + if (actor && !actor->bIsHidden) { + if (gldi_window_manager_can_track_workspaces () && !gldi_window_is_on_current_desktop (actor)) + return; + // TODO: avoid activating a window not on the current workspace in other cases! + gldi_window_show (actor); + } } static gboolean _dock_handle_leave (CairoDock *pDock, GdkEventCrossing *pEvent) @@ -532,7 +538,14 @@ static void _registry_global_cb ( G_GNUC_UNUSED void *data, struct wl_registry * { cd_debug("Found plasma-virtual-desktop-manager"); } - else gldi_cosmic_toplevel_match_protocol (id, interface, version); + else if (gldi_cosmic_toplevel_match_protocol (id, interface, version)) + { + cd_debug("Found cosmic-toplevel-manager"); + } + else if (gldi_cosmic_workspaces_match_protocol (id, interface, version)) + { + cd_debug("Found cosmic-workspace-manager"); + } #endif s_bInitializing = TRUE; } @@ -587,10 +600,19 @@ static void init (void) if (gldi_wayland_hotspots_try_init (registry)) cmb.update_polling_screen_edge = gldi_wayland_hotspots_update; #ifdef HAVE_WAYLAND_PROTOCOLS - if (!gldi_cosmic_toplevel_try_init (registry)) - if (!gldi_plasma_window_manager_try_init (registry)) + gboolean bCosmic = gldi_cosmic_toplevel_try_init (registry); + if (!bCosmic) if (!gldi_plasma_window_manager_try_init (registry)) gldi_wlr_foreign_toplevel_try_init (registry); - gldi_plasma_virtual_desktop_try_init (registry); + if (bCosmic) + { + if (!gldi_cosmic_workspaces_try_init (registry)) + gldi_plasma_virtual_desktop_try_init (registry); + } + else + { + if (!gldi_plasma_virtual_desktop_try_init (registry)) + gldi_cosmic_workspaces_try_init (registry); + } #endif cmb.set_input_shape = _set_input_shape; cmb.is_wayland = _is_wayland; diff --git a/src/implementations/cairo-dock-wayland-wm.c b/src/implementations/cairo-dock-wayland-wm.c index bdb33b49..d766aadf 100644 --- a/src/implementations/cairo-dock-wayland-wm.c +++ b/src/implementations/cairo-dock-wayland-wm.c @@ -30,7 +30,7 @@ extern CairoDock* g_pMainDock; -static GldiObjectManager myWaylandWMObjectMgr; +GldiObjectManager myWaylandWMObjectMgr; static void (*s_handle_destroy_cb)(gpointer handle) = NULL; @@ -43,6 +43,10 @@ static GldiWindowActor* s_pMaybeActiveWindow = NULL; /* our own config window -- will only work if the docks themselves are not * reported */ static GldiWindowActor* s_pSelf = NULL; +/* internal counter for updating windows' stacking order */ +static int s_iStackCounter = 0; +/* there was a change in windows' stacking order that needs to be signaled */ +static gboolean s_bStackChange = FALSE; // extra callback for when a new app is activated // this is useful for e.g. interactively selecting a window @@ -147,6 +151,12 @@ void gldi_wayland_wm_skip_changed (GldiWaylandWindowActor *wactor, gboolean skip if (notify) gldi_wayland_wm_done (wactor); } +void gldi_wayland_wm_sticky_changed (GldiWaylandWindowActor *wactor, gboolean sticky, gboolean notify) +{ + wactor->sticky_pending = sticky; + if (notify) gldi_wayland_wm_done (wactor); +} + void gldi_wayland_wm_closed (GldiWaylandWindowActor *wactor, gboolean notify) { wactor->close_pending = TRUE; @@ -207,6 +217,16 @@ static gboolean _update_attention (GldiWaylandWindowActor *wactor, gboolean noti return changed; } +static gboolean _update_sticky (GldiWaylandWindowActor *wactor, gboolean notify) +{ + GldiWindowActor* actor = (GldiWindowActor*)wactor; + gboolean changed = (wactor->sticky_pending != actor->bIsSticky); + if (changed) actor->bIsSticky = wactor->sticky_pending; + // a change in stickyness can be seen as a change in the desktop position + if (notify && changed) gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_DESKTOP_CHANGED, actor); + return changed; +} + static gboolean _idle_done (G_GNUC_UNUSED gpointer data) { GldiWaylandWindowActor *wactor = g_queue_pop_head(&s_pending_queue); @@ -349,8 +369,10 @@ void gldi_wayland_wm_done (GldiWaylandWindowActor *wactor) // check if other properties have changed (and send a notification) if (_update_state (wactor, TRUE)) continue; // update the needs-attention property - if (_update_attention(wactor, TRUE)) continue; - + if (_update_attention (wactor, TRUE)) continue; + // update the sticky property + if (_update_sticky (wactor, TRUE)) continue; + if (actor == s_pMaybeActiveWindow) { s_pActiveWindow = actor; @@ -370,6 +392,36 @@ void gldi_wayland_wm_done (GldiWaylandWindowActor *wactor) s_pCurrent = NULL; wactor = g_queue_pop_head(&s_pending_queue); // note: it is OK to call this on an empty queue } while (wactor); + + if (s_bStackChange) + { + gldi_object_notify (&myWindowObjectMgr, NOTIFICATION_WINDOW_Z_ORDER_CHANGED, NULL); + s_bStackChange = FALSE; + } +} + +static void _restack_windows (void* ptr, void*) +{ + GldiWindowActor *actor = (GldiWindowActor*)ptr; + actor->iStackOrder = s_iStackCounter; + s_iStackCounter++; +} + +void gldi_wayland_wm_stack_on_top (GldiWindowActor *actor) +{ + s_bStackChange = TRUE; + + if (s_iStackCounter < INT_MAX) + { + s_iStackCounter++; + actor->iStackOrder = s_iStackCounter; + return; + } + + // our counter would overflow, reset the order for all windows + s_iStackCounter = 0; + gldi_windows_foreach (TRUE, _restack_windows, NULL); + actor->iStackOrder = s_iStackCounter; // counter was incremented in the callback } GldiWindowActor* gldi_wayland_wm_get_active_window () diff --git a/src/implementations/cairo-dock-wayland-wm.h b/src/implementations/cairo-dock-wayland-wm.h index 4072ceef..5fc7673a 100644 --- a/src/implementations/cairo-dock-wayland-wm.h +++ b/src/implementations/cairo-dock-wayland-wm.h @@ -42,6 +42,7 @@ struct _GldiWaylandWindowActor { gboolean fullscreen_pending; // fullscreen state received gboolean attention_pending; // needs-attention state received (only KDE) gboolean skip_taskbar; // should not be shown in taskbar (only KDE) + gboolean sticky_pending; // sticky state received (only KDE) gboolean close_pending; // this window has been closed gboolean init_done; // initial state has been configured @@ -49,6 +50,9 @@ struct _GldiWaylandWindowActor { }; typedef struct _GldiWaylandWindowActor GldiWaylandWindowActor; +// manager for the above, can be extended by more specific implementations +extern GldiObjectManager myWaylandWMObjectMgr; + // functions to update the state of a toplevel and potentially signal // the taskbar of the changes @@ -63,6 +67,7 @@ void gldi_wayland_wm_minimized_changed (GldiWaylandWindowActor *wactor, gboolean void gldi_wayland_wm_fullscreen_changed (GldiWaylandWindowActor *wactor, gboolean fullscreen, gboolean notify); void gldi_wayland_wm_attention_changed (GldiWaylandWindowActor *wactor, gboolean attention, gboolean notify); void gldi_wayland_wm_skip_changed (GldiWaylandWindowActor *wactor, gboolean skip, gboolean notify); +void gldi_wayland_wm_sticky_changed (GldiWaylandWindowActor *wactor, gboolean sticky, gboolean notify); void gldi_wayland_wm_activated (GldiWaylandWindowActor *wactor, gboolean notify); void gldi_wayland_wm_closed (GldiWaylandWindowActor *wactor, gboolean notify); @@ -73,6 +78,12 @@ GldiWindowActor* gldi_wayland_wm_get_active_window (); GldiWindowActor* gldi_wayland_wm_pick_window (GtkWindow *pParentWindow); +/** Change the stacking order such that actor is on top. Does not send + * a notification; the user should do that manually, or call one of + * the above functions with notify == TRUE. + *@param actor The window to put on top. + */ +void gldi_wayland_wm_stack_on_top (GldiWindowActor *actor); typedef void (*GldiWaylandWMHandleDestroyFunc)(gpointer handle); void gldi_wayland_wm_init (GldiWaylandWMHandleDestroyFunc destroy_cb);