summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.github/workflows/archlinux.sh3
-rw-r--r--.github/workflows/builds.yml6
-rwxr-xr-x.github/workflows/debian.sh3
-rwxr-xr-x.github/workflows/fedora.sh3
-rw-r--r--.github/workflows/release.yml2
-rwxr-xr-x.github/workflows/ubuntu.sh3
-rw-r--r--NEWS39
-rw-r--r--configure.ac84
-rw-r--r--data/Makefile.am1
-rw-r--r--data/org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml.in29
-rw-r--r--data/org.mate.peripherals-mouse.gschema.xml.in5
-rw-r--r--mate-settings-daemon/main.c10
-rw-r--r--mate-settings-daemon/mate-settings-plugin-info.c59
-rw-r--r--mate-settings-daemon/mate-settings-plugin-info.h2
-rw-r--r--plugins/Makefile.am6
-rw-r--r--plugins/a11y-keyboard/a11y-keyboard.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/a11y-keyboard/msd-a11y-keyboard-manager.c21
-rw-r--r--plugins/background/Makefile.am21
-rw-r--r--plugins/background/msd-background-manager-wayland.c650
-rw-r--r--plugins/background/msd-background-manager-wayland.h38
-rw-r--r--plugins/background/msd-background-manager.c53
-rw-r--r--plugins/clipboard/Makefile.am35
-rw-r--r--plugins/clipboard/msd-clipboard-manager-wayland.c769
-rw-r--r--plugins/clipboard/msd-clipboard-manager-wayland.h39
-rw-r--r--plugins/clipboard/msd-clipboard-manager.c98
-rw-r--r--plugins/common/eggaccelerators.c10
-rw-r--r--plugins/common/msd-input-helper.c15
-rw-r--r--plugins/common/msd-keygrab.c19
-rw-r--r--plugins/common/msd-osd-window.c6
-rw-r--r--plugins/keybindings/keybindings.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/keybindings/msd-keybindings-manager.c24
-rw-r--r--plugins/keyboard/keyboard.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/keyboard/msd-keyboard-manager.c22
-rw-r--r--plugins/media-keys/media-keys.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/media-keys/msd-media-keys-manager.c21
-rw-r--r--plugins/mouse/mouse.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/mouse/msd-mouse-manager.c99
-rw-r--r--plugins/wlrandr/Makefile.am108
-rw-r--r--plugins/wlrandr/msd-wlrandr-16.pngbin0 -> 875 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-22.pngbin0 -> 1242 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-24.pngbin0 -> 1354 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-32.pngbin0 -> 1887 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-48.pngbin0 -> 3049 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-64.pngbin0 -> 4273 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-96.pngbin0 -> 6937 bytes
-rw-r--r--plugins/wlrandr/msd-wlrandr-manager.c2968
-rw-r--r--plugins/wlrandr/msd-wlrandr-manager.h57
-rw-r--r--plugins/wlrandr/msd-wlrandr-plugin.c105
-rw-r--r--plugins/wlrandr/msd-wlrandr-plugin.h63
-rw-r--r--plugins/wlrandr/msd-wlrandr.svg491
-rw-r--r--plugins/wlrandr/protocol/wlr-output-management-unstable-v1.xml601
-rw-r--r--plugins/wlrandr/wlrandr.mate-settings-plugin.desktop.in9
-rw-r--r--plugins/xrandr/msd-xrandr-manager.c55
-rw-r--r--plugins/xrandr/xrandr.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/xsettings/msd-xsettings-manager.c77
-rw-r--r--plugins/xsettings/wm-common.c23
-rw-r--r--plugins/xsettings/xsettings-manager.c10
-rw-r--r--po/POTFILES.in3
-rw-r--r--po/el.po22
-rw-r--r--po/ka.po78
-rw-r--r--po/kab.po71
-rw-r--r--po/ko.po20
-rw-r--r--po/oc.po28
-rw-r--r--po/ru.po34
64 files changed, 6851 insertions, 173 deletions
diff --git a/.github/workflows/archlinux.sh b/.github/workflows/archlinux.sh
index bb11578..3e38575 100755
--- a/.github/workflows/archlinux.sh
+++ b/.github/workflows/archlinux.sh
@@ -24,6 +24,8 @@ requires+=(
gettext
git
glib2-devel
+ gtk-layer-shell
+ libayatana-appindicator
libcanberra
libmatekbd
libmatemixer
@@ -35,6 +37,7 @@ requires+=(
polkit
python-packaging
which
+ wayland-protocols
)
infobegin "Update system"
diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml
index 01b3ba8..c6c9691 100644
--- a/.github/workflows/builds.yml
+++ b/.github/workflows/builds.yml
@@ -15,7 +15,7 @@ concurrency:
cancel-in-progress: true
env:
- MATE_DESKTOP_VERSION: 1.28.2
+ MATE_DESKTOP_VERSION: 1.29.0
CACHE_PATH: /tmp/.cache
jobs:
@@ -69,7 +69,7 @@ jobs:
pacman --noconfirm -Sy git || true
echo "::endgroup::"
- name: Repository checkout
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install dependency packages
run: .github/workflows/${{ env.DISTRO }}.sh
- name: Enable ccache to speed up builds
@@ -79,7 +79,7 @@ jobs:
# INFO: Depends on mate-desktop 1.27.1+, so we should install it from source.
- name: Cache mate-desktop binary packages
- uses: actions/cache@v5
+ uses: actions/cache@v6
id: cache-mate-desktop
with:
path: ${{ env.CACHE_PATH }}
diff --git a/.github/workflows/debian.sh b/.github/workflows/debian.sh
index 8750804..0534670 100755
--- a/.github/workflows/debian.sh
+++ b/.github/workflows/debian.sh
@@ -20,11 +20,13 @@ requires+=(
gcc
g++
git
+ libayatana-appindicator3-dev
libcanberra-gtk3-dev
libdconf-dev
libfontconfig1-dev
libglib2.0-dev
libgtk-3-dev
+ libgtk-layer-shell-dev
libmate-desktop-dev
libmatekbd-dev
libmatemixer-dev
@@ -44,6 +46,7 @@ requires+=(
mate-common
polkitd
pkexec
+ wayland-protocols
x11proto-kb-dev
)
diff --git a/.github/workflows/fedora.sh b/.github/workflows/fedora.sh
index 407d67d..dc35600 100755
--- a/.github/workflows/fedora.sh
+++ b/.github/workflows/fedora.sh
@@ -23,7 +23,9 @@ requires+=(
gcc-c++
git
gtk3-devel
+ gtk-layer-shell-devel
iso-codes-devel
+ libayatana-appindicator-gtk3-devel
libSM-devel
libcanberra-devel
libmatekbd-devel
@@ -36,6 +38,7 @@ requires+=(
polkit-devel
pulseaudio-libs-devel
redhat-rpm-config
+ wayland-protocols-devel
)
infobegin "Update system"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9a59377..fde2a49 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Repository checkout
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install dependency packages
run: sudo .github/workflows/ubuntu.sh
diff --git a/.github/workflows/ubuntu.sh b/.github/workflows/ubuntu.sh
index 3ab2e73..839e1e5 100755
--- a/.github/workflows/ubuntu.sh
+++ b/.github/workflows/ubuntu.sh
@@ -20,11 +20,13 @@ requires+=(
gcc
g++
git
+ libayatana-appindicator3-dev
libcanberra-gtk3-dev
libdconf-dev
libfontconfig1-dev
libglib2.0-dev
libgtk-3-dev
+ libgtk-layer-shell-dev
libmate-desktop-dev
libmatekbd-dev
libmatemixer-dev
@@ -42,6 +44,7 @@ requires+=(
libxt-dev
make
mate-common
+ wayland-protocols
x11proto-kb-dev
)
diff --git a/NEWS b/NEWS
index db159ea..aaf2c97 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,42 @@
+### mate-settings-daemon 1.29.1
+
+ * background: don't initialize alpha channel
+ * wayland/background: respect show-desktop-icons setting
+ * fix wayland-protocols path
+ * ci: add wayland related packages
+ * Remove dead code
+ * wlrandr: persist the applied configuration instead of a live snapshot
+ * clipboard: adjust offer timeout
+ * xsettings: flush Xlib output so settings reach X11/XWayland clients
+ * Remove dead code
+ * xsettings: flush Xlib output so settings reach X11/XWayland clients
+ * wlrandr: expose configuration over D-Bus for the display capplet
+ * Add wlrandr plugin
+ * Respect fade effect settings and fix redraw at resolution changes
+ * background: port to wayland via gtk-layer-shell
+ * clipboard-manager: add Wayland support
+ * Initial Wayland support
+ * Translated using Weblate (Greek)
+ * Translated using Weblate (Kabyle)
+ * Translated using Weblate (Russian)
+ * Translated using Weblate (Georgian)
+ * Translated using Weblate (Korean)
+ * Translated using Weblate (Occitan)
+ * build (deps) bump actions/checkout from 6 to 7
+ * build (deps) bump actions/cache from 5 to 76
+ * xrandr: Handle hotplug_mode_update for VM guest resizing
+ * background: simplify desktop detection to use EWMH standard
+ * correcting po with errors
+ * remove empty po file
+ * add translation coverage badge
+ * retrieve last transifex translations
+ * Revert "Retrieve last transifex translations"
+ * Retrieve last transifex translations
+ * build(deps): bump actions/checkout from 5 to 6
+ * ci: migrate from Travis CI to GitHub Actions
+ * fix memleak
+
+
### mate-settings-daemon 1.29.0
* xsettings: Remove HiDPI scale change workaround
diff --git a/configure.ac b/configure.ac
index 48c2865..911a8bd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ AC_PREREQ([2.60])
m4_define([msd_api_version_major],[1])
m4_define([msd_api_version_minor],[29])
-m4_define([msd_api_version_micro],[0])
+m4_define([msd_api_version_micro],[1])
m4_define([msd_api_version],
[msd_api_version_major.msd_api_version_minor.msd_api_version_micro])
@@ -55,7 +55,7 @@ dnl ---------------------------------------------------------------------------
GLIB_REQUIRED_VERSION=2.58.0
GIO_REQUIRED_VERSION=2.50.0
GTK_REQUIRED_VERSION=3.22.0
-MATE_DESKTOP_REQUIRED_VERSION=1.27.1
+MATE_DESKTOP_REQUIRED_VERSION=1.29.0
LIBMATEKBD_REQUIRED_VERSION=1.17.0
LIBNOTIFY_REQUIRED_VERSION=0.7.0
LIBATSPI_REQUIRED_VERSION=2.0
@@ -82,6 +82,81 @@ case $host_os in
esac
AC_SUBST([MSD_PLUGIN_LDFLAGS])
+dnl ---------------------------------------------------------------------------
+dnl - Ayatana AppIndicator
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE(appindicator,
+ AS_HELP_STRING([--enable-appindicator], [Enable ayatana appindicator support (default: auto)]),
+ [case "${enableval}" in
+ yes|no|auto) enable_appindicator=${enableval} ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-appindicator) ;;
+ esac],
+ enable_appindicator=auto)
+
+have_appindicator=no
+if test "x${enable_appindicator}" != "xno"; then
+ PKG_CHECK_MODULES(APPINDICATOR, ayatana-appindicator3-0.1,
+ [have_appindicator=yes
+ AC_DEFINE(HAVE_APP_INDICATOR, 1, [Define if ayatana appindicator support is enabled])],
+ [if test "x${enable_appindicator}" = "xyes"; then
+ AC_MSG_ERROR([Ayatana appindicator support requested but ayatana-appindicator3-0.1 not found])
+ fi])
+fi
+
+dnl ---------------------------------------------------------------------------
+dnl - Wayland
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE(wayland,
+ AS_HELP_STRING([--enable-wayland], [Enable Wayland support (default: auto)]),
+ [case "${enableval}" in
+ yes|no|auto) enable_wayland=${enableval} ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-wayland) ;;
+ esac],
+ enable_wayland=auto)
+
+have_wayland=no
+if test "x${enable_wayland}" != "xno"; then
+ save_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SETTINGS_DAEMON_CFLAGS"
+ AC_CHECK_HEADER([gdk/gdkwayland.h],
+ [have_wayland=yes],
+ [have_wayland=no])
+ CPPFLAGS="$save_CPPFLAGS"
+
+ if test "x${have_wayland}" = "xyes"; then
+ PKG_CHECK_MODULES(WAYLAND_CLIENT, wayland-client >= 1.18,
+ [have_wayland=yes],
+ [have_wayland=no])
+ fi
+
+ if test "x${have_wayland}" = "xyes"; then
+ WAYLAND_SCANNER=wayland-scanner
+ AC_SUBST(WAYLAND_SCANNER)
+ WAYLAND_PROTOCOLS_DATADIR=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols | sed 's|^//*|/|'`
+ AC_SUBST(WAYLAND_PROTOCOLS_DATADIR)
+ if test "x${WAYLAND_PROTOCOLS_DATADIR}" = "x"; then
+ have_wayland=no
+ fi
+ fi
+
+ if test "x${have_wayland}" = "xyes"; then
+ PKG_CHECK_MODULES(GTK_LAYER_SHELL, gtk-layer-shell-0 >= 0.6,
+ [have_wayland=yes],
+ [have_wayland=no])
+ fi
+
+ if test "x${have_wayland}" = "xyes"; then
+ AC_DEFINE(HAVE_WAYLAND, 1, [Define if Wayland support is enabled])
+ fi
+
+ if test "x${have_wayland}" = "xno" -a "x${enable_wayland}" = "xyes"; then
+ AC_MSG_ERROR([Wayland support requested but the required Wayland libraries (GTK+ Wayland backend, wayland-client, wayland-protocols or gtk-layer-shell) are missing])
+ fi
+fi
+AM_CONDITIONAL(BUILD_WAYLAND, test "x${have_wayland}" = "xyes")
+
PKG_CHECK_MODULES(DCONF, dconf >= 0.13.4)
AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
@@ -429,6 +504,7 @@ plugins/xrandr/Makefile
plugins/xrdb/Makefile
plugins/xrdb/data/Makefile
plugins/xsettings/Makefile
+plugins/wlrandr/Makefile
data/Makefile
data/mate-settings-daemon.desktop.in
data/mate-settings-daemon.pc
@@ -457,6 +533,7 @@ data/org.mate.SettingsDaemon.plugins.typing-break.gschema.xml
data/org.mate.SettingsDaemon.plugins.xrandr.gschema.xml
data/org.mate.SettingsDaemon.plugins.xrdb.gschema.xml
data/org.mate.SettingsDaemon.plugins.xsettings.gschema.xml
+data/org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml
data/org.mate.SettingsDaemon.plugins.gschema.xml
po/Makefile.in
])
@@ -498,6 +575,9 @@ Configure summary:
Libmatemixer support ....: ${have_libmatemixer}
Smartcard support .......: ${have_smartcard_support}
RFKill support ..........: ${enable_rfkill}
+ Appindicator support ....: ${have_appindicator}
+ Wayland support .........: ${have_wayland}
+ Wayland protocols dir ...: ${WAYLAND_PROTOCOLS_DATADIR}
${NSS_DATABASE:+\
System nssdb ............: ${NSS_DATABASE}
}\
diff --git a/data/Makefile.am b/data/Makefile.am
index 3745513..13442c1 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -25,6 +25,7 @@ msd_gschemas_in = \
org.mate.SettingsDaemon.plugins.xrandr.gschema.xml.in \
org.mate.SettingsDaemon.plugins.xrdb.gschema.xml.in \
org.mate.SettingsDaemon.plugins.xsettings.gschema.xml.in \
+ org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml.in \
org.mate.SettingsDaemon.plugins.gschema.xml.in \
$(NULL)
diff --git a/data/org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml.in b/data/org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml.in
new file mode 100644
index 0000000..40d8e26
--- /dev/null
+++ b/data/org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml.in
@@ -0,0 +1,29 @@
+<schemalist>
+ <schema gettext-domain="mate-settings-daemon" id="org.mate.SettingsDaemon.plugins.wlrandr" path="/org/mate/settings-daemon/plugins/wlrandr/">
+ <key name="active" type="b">
+ <default>true</default>
+ <summary>Activation of this plugin</summary>
+ <description>Whether this plugin would be activated by mate-settings-daemon or not</description>
+ </key>
+ <key name="priority" type="i">
+ <default>1</default>
+ <summary>Priority to use for this plugin</summary>
+ <description>Priority to use for this plugin in mate-settings-daemon startup queue</description>
+ </key>
+ <key name="show-notification-icon" type="b">
+ <default>false</default>
+ <summary>Show Displays in Notification Area</summary>
+ <description>Whether a notification icon with display-related things should be shown in the panel.</description>
+ </key>
+ <key name="turn-on-external-monitors-at-startup" type="b">
+ <default>false</default>
+ <summary>Turn on external monitor after system boot</summary>
+ <description>Turn on external monitor after system boot if user plugs in external monitor on system boot.</description>
+ </key>
+ <key name="default-configuration-file" type="s">
+ <default>'/etc/monitors-wayland.xml'</default>
+ <summary>File for default configuration for Wayland outputs</summary>
+ <description>The wlrandr plugin will look for a default configuration in the file specified by this key. This is similar to the ~/.config/monitors-wayland.xml that normally gets stored in users' home directories. If a user does not have such a file, or has one that does not match the user's setup of monitors, then the file specified by this key will be used instead.</description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/data/org.mate.peripherals-mouse.gschema.xml.in b/data/org.mate.peripherals-mouse.gschema.xml.in
index 87b4bc2..b36cbd8 100644
--- a/data/org.mate.peripherals-mouse.gschema.xml.in
+++ b/data/org.mate.peripherals-mouse.gschema.xml.in
@@ -10,6 +10,11 @@
<summary>Mouse button orientation</summary>
<description>Swap left and right mouse buttons for left-handed mice.</description>
</key>
+ <key name="natural-scroll" type="b">
+ <default>false</default>
+ <summary>Natural scrolling</summary>
+ <description>Set this to true to enable natural (reverse) scrolling for mice</description>
+ </key>
<key name="motion-acceleration" type="d">
<default>-1</default>
<summary>Motion Acceleration</summary>
diff --git a/mate-settings-daemon/main.c b/mate-settings-daemon/main.c
index 11d8687..ed9c2b3 100644
--- a/mate-settings-daemon/main.c
+++ b/mate-settings-daemon/main.c
@@ -39,6 +39,10 @@
#include <libnotify/notify.h>
#endif /* HAVE_LIBNOTIFY */
+#ifdef HAVE_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif /* HAVE_WAYLAND */
+
#include "mate-settings-manager.h"
#include "mate-settings-profile.h"
@@ -438,6 +442,12 @@ main (int argc, char *argv[])
}
mate_settings_profile_end ("opening gtk display");
+#ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ g_debug ("Running in a Wayland session; only the xsettings plugin (for XWayland) will be active");
+ }
+#endif /* HAVE_WAYLAND */
+
g_log_set_default_handler (msd_log_default_handler, NULL);
bus_register ();
diff --git a/mate-settings-daemon/mate-settings-plugin-info.c b/mate-settings-daemon/mate-settings-plugin-info.c
index 50508cb..282ffbf 100644
--- a/mate-settings-daemon/mate-settings-plugin-info.c
+++ b/mate-settings-daemon/mate-settings-plugin-info.c
@@ -27,6 +27,13 @@
#include <glib/gi18n.h>
#include <gmodule.h>
#include <gio/gio.h>
+#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif /* GDK_WINDOWING_WAYLAND */
#include "mate-settings-plugin-info.h"
#include "mate-settings-module.h"
@@ -56,6 +63,8 @@ struct MateSettingsPluginInfoPrivate
guint enabled : 1;
guint active : 1;
+ guint x11_only : 1;
+ guint wayland_only : 1;
/* A plugin is unavailable if it is not possible to activate it
due to an error loading the plugin module */
@@ -248,6 +257,14 @@ mate_settings_plugin_info_fill_from_file (MateSettingsPluginInfo *info,
info->priv->priority = PLUGIN_PRIORITY_DEFAULT;
}
+ /* Get X11Only */
+ info->priv->x11_only =
+ g_key_file_get_boolean (plugin_file, PLUGIN_GROUP, "X11Only", NULL);
+
+ /* Get WaylandOnly */
+ info->priv->wayland_only =
+ g_key_file_get_boolean (plugin_file, PLUGIN_GROUP, "WaylandOnly", NULL);
+
g_key_file_free (plugin_file);
debug_info (info);
@@ -405,6 +422,32 @@ mate_settings_plugin_info_activate (MateSettingsPluginInfo *info)
return TRUE;
}
+ if (info->priv->x11_only) {
+#ifdef GDK_WINDOWING_X11
+ gboolean x11 = GDK_IS_X11_DISPLAY (gdk_display_get_default ());
+#else
+ gboolean x11 = FALSE;
+#endif
+ if (!x11) {
+ g_debug ("Plugin '%s' requires X11; not loading on this display",
+ info->priv->name);
+ return FALSE;
+ }
+ }
+
+ if (info->priv->wayland_only) {
+#ifdef GDK_WINDOWING_WAYLAND
+ gboolean wayland = GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ());
+#else
+ gboolean wayland = FALSE;
+#endif
+ if (!wayland) {
+ g_debug ("Plugin '%s' requires Wayland; not loading on this display",
+ info->priv->name);
+ return FALSE;
+ }
+ }
+
if (_activate_plugin (info)) {
info->priv->active = TRUE;
return TRUE;
@@ -430,6 +473,22 @@ mate_settings_plugin_info_get_enabled (MateSettingsPluginInfo *info)
}
gboolean
+mate_settings_plugin_info_get_x11_only (MateSettingsPluginInfo *info)
+{
+ g_return_val_if_fail (MATE_IS_SETTINGS_PLUGIN_INFO (info), FALSE);
+
+ return (info->priv->x11_only);
+}
+
+gboolean
+mate_settings_plugin_info_get_wayland_only (MateSettingsPluginInfo *info)
+{
+ g_return_val_if_fail (MATE_IS_SETTINGS_PLUGIN_INFO (info), FALSE);
+
+ return (info->priv->wayland_only);
+}
+
+gboolean
mate_settings_plugin_info_is_available (MateSettingsPluginInfo *info)
{
g_return_val_if_fail (MATE_IS_SETTINGS_PLUGIN_INFO (info), FALSE);
diff --git a/mate-settings-daemon/mate-settings-plugin-info.h b/mate-settings-daemon/mate-settings-plugin-info.h
index d7d2c25..8678c6d 100644
--- a/mate-settings-daemon/mate-settings-plugin-info.h
+++ b/mate-settings-daemon/mate-settings-plugin-info.h
@@ -61,6 +61,8 @@ gboolean mate_settings_plugin_info_deactivate (MateSettingsPluginIn
gboolean mate_settings_plugin_info_is_active (MateSettingsPluginInfo *info);
gboolean mate_settings_plugin_info_get_enabled (MateSettingsPluginInfo *info);
gboolean mate_settings_plugin_info_is_available (MateSettingsPluginInfo *info);
+gboolean mate_settings_plugin_info_get_x11_only (MateSettingsPluginInfo *info);
+gboolean mate_settings_plugin_info_get_wayland_only (MateSettingsPluginInfo *info);
const char *mate_settings_plugin_info_get_name (MateSettingsPluginInfo *info);
const char *mate_settings_plugin_info_get_description (MateSettingsPluginInfo *info);
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index fe8bf9a..0118876 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -34,6 +34,12 @@ else
disabled_plugins += rfkill
endif
+if BUILD_WAYLAND
+enabled_plugins += wlrandr
+else
+disabled_plugins += wlrandr
+endif
+
SUBDIRS = common $(enabled_plugins)
DIST_SUBDIRS = $(SUBDIRS) $(disabled_plugins)
diff --git a/plugins/a11y-keyboard/a11y-keyboard.mate-settings-plugin.desktop.in b/plugins/a11y-keyboard/a11y-keyboard.mate-settings-plugin.desktop.in
index 51a59a2..9a1793e 100644
--- a/plugins/a11y-keyboard/a11y-keyboard.mate-settings-plugin.desktop.in
+++ b/plugins/a11y-keyboard/a11y-keyboard.mate-settings-plugin.desktop.in
@@ -1,6 +1,7 @@
[MATE Settings Plugin]
Module=a11y-keyboard
IAge=0
+X11Only=true
Name=Accessibility Keyboard
Description=Accessibility keyboard plugin
Authors=Jody Goldberg
diff --git a/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c b/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c
index d354db5..7ffe863 100644
--- a/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c
+++ b/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c
@@ -35,7 +35,9 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <gtk/gtk.h>
#include <gio/gio.h>
@@ -1149,6 +1151,20 @@ msd_a11y_keyboard_manager_start (MsdA11yKeyboardManager *manager,
{
mate_settings_profile_start (NULL);
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "X11 display is required for the a11y_keyboard plugin");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+#else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The a11y_keyboard plugin was built without X11 support");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
+
/* give our initialization a lower priority over msd-keyoboard so it
* restores the numlock state before we might start monitoring it */
g_idle_add_full (G_PRIORITY_LOW,
@@ -1195,6 +1211,11 @@ msd_a11y_keyboard_manager_stop (MsdA11yKeyboardManager *manager)
g_debug ("Stopping a11y_keyboard manager");
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
gdk_window_remove_filter (NULL, devicepresence_filter, manager);
if (p->status_icon)
diff --git a/plugins/background/Makefile.am b/plugins/background/Makefile.am
index b2ab685..ce8109f 100644
--- a/plugins/background/Makefile.am
+++ b/plugins/background/Makefile.am
@@ -1,5 +1,14 @@
NULL =
+if BUILD_WAYLAND
+WAYLAND_BACKGROUND_SOURCES = \
+ msd-background-manager-wayland.c \
+ msd-background-manager-wayland.h \
+ $(NULL)
+else
+WAYLAND_BACKGROUND_SOURCES =
+endif
+
noinst_PROGRAMS = \
test-background \
$(NULL)
@@ -8,6 +17,7 @@ test_background_SOURCES = \
test-background.c \
msd-background-manager.h \
msd-background-manager.c \
+ $(WAYLAND_BACKGROUND_SOURCES) \
$(NULL)
test_background_CPPFLAGS = \
@@ -18,6 +28,8 @@ test_background_CPPFLAGS = \
test_background_CFLAGS = \
$(SETTINGS_PLUGIN_CFLAGS) \
$(MATE_DESKTOP_CFLAGS) \
+ $(GTK_LAYER_SHELL_CFLAGS) \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CFLAGS) \
$(WARN_CFLAGS)
@@ -26,6 +38,8 @@ test_background_LDADD = \
$(SETTINGS_PLUGIN_LIBS) \
$(MATE_DESKTOP_LIBS) \
$(X11_LIBS) \
+ $(GTK_LAYER_SHELL_LIBS) \
+ $(WAYLAND_CLIENT_LIBS) \
$(NULL)
plugin_LTLIBRARIES = \
@@ -37,17 +51,22 @@ libbackground_la_SOURCES = \
msd-background-plugin.c \
msd-background-manager.h \
msd-background-manager.c \
+ $(WAYLAND_BACKGROUND_SOURCES) \
$(NULL)
libbackground_la_CPPFLAGS = \
-I$(top_srcdir)/mate-settings-daemon \
-I$(top_srcdir)/plugins/background/libbackground \
-DMATE_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \
+ $(GTK_LAYER_SHELL_CFLAGS) \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CPPFLAGS)
libbackground_la_CFLAGS = \
$(SETTINGS_PLUGIN_CFLAGS) \
$(MATE_DESKTOP_CFLAGS) \
+ $(GTK_LAYER_SHELL_CFLAGS) \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CFLAGS) \
$(WARN_CFLAGS)
@@ -59,6 +78,8 @@ libbackground_la_LIBADD = \
$(SETTINGS_PLUGIN_LIBS) \
$(MATE_DESKTOP_LIBS) \
$(X11_LIBS) \
+ $(GTK_LAYER_SHELL_LIBS) \
+ $(WAYLAND_CLIENT_LIBS) \
$(NULL)
plugin_in_files = \
diff --git a/plugins/background/msd-background-manager-wayland.c b/plugins/background/msd-background-manager-wayland.c
new file mode 100644
index 0000000..b4ab88d
--- /dev/null
+++ b/plugins/background/msd-background-manager-wayland.c
@@ -0,0 +1,650 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkwayland.h>
+#include <gtk/gtk.h>
+#include <wayland-client.h>
+
+#include <gtk-layer-shell.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <libmate-desktop/mate-bg.h>
+
+#include "msd-background-manager-wayland.h"
+
+struct _MsdBackgroundManagerWayland
+{
+ GdkDisplay *display;
+ GSettings *settings;
+ MateBG *bg;
+ GPtrArray *monitor_windows;
+ gboolean caja_desktop_active;
+};
+
+typedef struct
+{
+ GtkWidget *window;
+ GdkMonitor *monitor;
+ MsdBackgroundManagerWayland *manager;
+ cairo_surface_t *surface;
+ cairo_surface_t *fading_surface;
+ cairo_surface_t *end_surface;
+ guint fade_timeout_id;
+ gdouble fade_start_time;
+ gdouble fade_total_duration;
+ gboolean fade_is_first_frame;
+ int width;
+ int height;
+} MonitorWindow;
+
+static void
+free_monitor_window_surface (MonitorWindow *mw)
+{
+ if (mw->surface != NULL) {
+ cairo_surface_destroy (mw->surface);
+ mw->surface = NULL;
+ }
+}
+
+static gdouble
+get_current_time (void)
+{
+ return g_get_monotonic_time () / 1000000.0;
+}
+
+/* Check the GSettings key that indicates whether Caja is configured to
+ * draw desktop icons. When this is enabled Caja draws the desktop itself
+ * so we must not create our own layer-shell background windows.
+ */
+static gboolean
+caja_desktop_is_showing (MsdBackgroundManagerWayland *manager)
+{
+ return g_settings_get_boolean (manager->settings, MATE_BG_KEY_SHOW_DESKTOP);
+}
+
+static gboolean
+animations_are_disabled (MonitorWindow *mw G_GNUC_UNUSED)
+{
+ GtkSettings *settings;
+ gboolean enabled;
+
+ settings = gtk_settings_get_default ();
+ if (settings == NULL)
+ return TRUE;
+
+ g_object_get (settings, "gtk-enable-animations", &enabled, NULL);
+
+ return !enabled;
+}
+
+static void
+cancel_fade (MonitorWindow *mw)
+{
+ if (mw->fade_timeout_id == 0)
+ return;
+
+ g_source_remove (mw->fade_timeout_id);
+ mw->fade_timeout_id = 0;
+}
+
+static void
+on_fade_finished (MonitorWindow *mw)
+{
+ cairo_t *cr;
+
+ mw->fade_timeout_id = 0;
+
+ if (mw->fading_surface == NULL || mw->end_surface == NULL) {
+ if (mw->end_surface != NULL) {
+ cairo_surface_destroy (mw->end_surface);
+ mw->end_surface = NULL;
+ }
+ return;
+ }
+
+ cr = cairo_create (mw->fading_surface);
+ cairo_set_source_surface (cr, mw->end_surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (mw->end_surface);
+ mw->end_surface = NULL;
+
+ free_monitor_window_surface (mw);
+ mw->surface = mw->fading_surface;
+ mw->fading_surface = NULL;
+
+ gtk_widget_queue_draw (mw->window);
+}
+
+static gboolean
+on_fade_tick (MonitorWindow *mw)
+{
+ gdouble now, percent_done;
+ cairo_t *cr;
+ cairo_status_t status;
+
+ if (mw->fading_surface == NULL || mw->end_surface == NULL)
+ return FALSE;
+
+ now = get_current_time ();
+ percent_done = (now - mw->fade_start_time) / mw->fade_total_duration;
+ percent_done = CLAMP (percent_done, 0.0, 1.0);
+
+ /* If it's taking a long time to get to the first frame,
+ * then lengthen the duration, so the user will get to see
+ * the effect.
+ */
+ if (mw->fade_is_first_frame && percent_done > .33) {
+ mw->fade_is_first_frame = FALSE;
+ mw->fade_total_duration *= 1.5;
+ return TRUE;
+ }
+
+ if (animations_are_disabled (mw))
+ return FALSE;
+
+ /* Accumulate the end surface in place, so the fade is
+ * exponential rather than linear (looks good, matches X11).
+ */
+ cr = cairo_create (mw->fading_surface);
+ cairo_set_source_surface (cr, mw->end_surface, 0, 0);
+ cairo_paint_with_alpha (cr, percent_done);
+ status = cairo_status (cr);
+ cairo_destroy (cr);
+
+ if (status == CAIRO_STATUS_SUCCESS)
+ gtk_widget_queue_draw (mw->window);
+
+ return percent_done <= .99;
+}
+
+static void
+set_surface_instantly (MonitorWindow *mw, cairo_surface_t *new_surface)
+{
+ if (mw->fade_timeout_id != 0) {
+ if (mw->fading_surface != NULL) {
+ cairo_surface_destroy (mw->fading_surface);
+ mw->fading_surface = NULL;
+ }
+ if (mw->end_surface != NULL) {
+ cairo_surface_destroy (mw->end_surface);
+ mw->end_surface = NULL;
+ }
+ g_source_remove (mw->fade_timeout_id);
+ mw->fade_timeout_id = 0;
+ }
+ free_monitor_window_surface (mw);
+ mw->surface = new_surface;
+ gtk_widget_queue_draw (mw->window);
+}
+
+static void
+start_or_continue_fade (MsdBackgroundManagerWayland *manager,
+ MonitorWindow *mw,
+ cairo_surface_t *new_surface)
+{
+ gboolean fade_enabled;
+ gboolean animations_enabled;
+ cairo_t *cr;
+ GSource *source;
+
+ fade_enabled = g_settings_get_boolean (manager->settings,
+ MATE_BG_KEY_BACKGROUND_FADE);
+ animations_enabled = !animations_are_disabled (mw);
+
+ if (!fade_enabled || !animations_enabled || mw->surface == NULL) {
+ /* No fade: swap instantly. This also covers the first render
+ * after startup.
+ */
+ set_surface_instantly (mw, new_surface);
+ return;
+ }
+
+ /* If a fade is already running, restart from the current blended
+ * state (its completion commits the blend into mw->surface).
+ */
+ cancel_fade (mw);
+
+ mw->fading_surface = cairo_surface_create_similar (mw->surface,
+ cairo_surface_get_content (mw->surface),
+ mw->width, mw->height);
+ cr = cairo_create (mw->fading_surface);
+ cairo_set_source_surface (cr, mw->surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ mw->end_surface = new_surface;
+ mw->fade_start_time = get_current_time ();
+ mw->fade_total_duration = 0.75;
+ mw->fade_is_first_frame = TRUE;
+
+ source = g_timeout_source_new (1000 / 60.0);
+ g_source_set_callback (source, (GSourceFunc) on_fade_tick, mw,
+ (GDestroyNotify) on_fade_finished);
+ mw->fade_timeout_id = g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ gtk_widget_queue_draw (mw->window);
+}
+
+static void
+render_surface_at_size (MsdBackgroundManagerWayland *manager,
+ MonitorWindow *mw,
+ int width,
+ int height,
+ gboolean animate)
+{
+ GdkPixbuf *pixbuf;
+ cairo_surface_t *new_surface;
+ cairo_t *cr;
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+ width, height);
+ if (pixbuf == NULL) {
+ g_warning ("msd-background-wayland: unable to create %dx%d pixbuf",
+ width, height);
+ return;
+ }
+
+ mate_bg_draw (manager->bg, pixbuf, NULL, FALSE);
+
+ new_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ width, height);
+ cr = cairo_create (new_surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ g_object_unref (pixbuf);
+
+ mw->width = width;
+ mw->height = height;
+
+ if (animate)
+ start_or_continue_fade (manager, mw, new_surface);
+ else
+ set_surface_instantly (mw, new_surface);
+}
+
+static void
+render_surface_for_monitor (MsdBackgroundManagerWayland *manager,
+ MonitorWindow *mw,
+ gboolean animate)
+{
+ GdkRectangle rect;
+
+ gdk_monitor_get_geometry (mw->monitor, &rect);
+ render_surface_at_size (manager, mw, rect.width, rect.height, animate);
+}
+
+static void
+on_window_size_allocate (GtkWidget *widget G_GNUC_UNUSED,
+ GtkAllocation *alloc,
+ MonitorWindow *mw)
+{
+ if (alloc->width <= 0 || alloc->height <= 0)
+ return;
+
+ if (mw->surface == NULL)
+ return;
+
+ /* The layer surface is anchored to the output edges, so when the
+ * output resolution or scale changes the compositor resizes the
+ * window. Re-render at the new size so the wallpaper adapts even
+ * if no monitors-changed event arrives.
+ */
+ if (alloc->width == mw->width && alloc->height == mw->height)
+ return;
+
+ g_debug ("msd-background-wayland: window resized to %dx%d, re-rendering",
+ alloc->width, alloc->height);
+ render_surface_at_size (mw->manager, mw,
+ alloc->width, alloc->height, FALSE);
+}
+
+static gboolean
+on_window_draw (GtkWidget *widget,
+ cairo_t *cr,
+ MonitorWindow *mw)
+{
+ GtkAllocation alloc;
+ cairo_surface_t *src;
+ gdouble sx, sy;
+
+ if (mw->surface == NULL && mw->fading_surface == NULL)
+ return FALSE;
+
+ src = (mw->fading_surface != NULL) ? mw->fading_surface : mw->surface;
+
+ gtk_widget_get_allocation (widget, &alloc);
+ if (alloc.width <= 0 || alloc.height <= 0)
+ return FALSE;
+
+ sx = alloc.width / (gdouble) cairo_image_surface_get_width (src);
+ sy = alloc.height / (gdouble) cairo_image_surface_get_height (src);
+ cairo_scale (cr, sx, sy);
+ cairo_set_source_surface (cr, src, 0, 0);
+ cairo_paint (cr);
+
+ return FALSE;
+}
+
+static void
+redraw_all (MsdBackgroundManagerWayland *manager)
+{
+ guint i;
+
+ for (i = 0; i < manager->monitor_windows->len; i++) {
+ MonitorWindow *mw = g_ptr_array_index (manager->monitor_windows, i);
+ render_surface_for_monitor (manager, mw, TRUE);
+ gtk_widget_queue_draw (mw->window);
+ }
+}
+
+static void
+on_window_destroy (GtkWidget *widget G_GNUC_UNUSED,
+ MonitorWindow *mw)
+{
+ if (mw->fade_timeout_id != 0) {
+ /* Make on_fade_finished a no-op, then drop the source */
+ if (mw->fading_surface != NULL) {
+ cairo_surface_destroy (mw->fading_surface);
+ mw->fading_surface = NULL;
+ }
+ if (mw->end_surface != NULL) {
+ cairo_surface_destroy (mw->end_surface);
+ mw->end_surface = NULL;
+ }
+ g_source_remove (mw->fade_timeout_id);
+ mw->fade_timeout_id = 0;
+ }
+ free_monitor_window_surface (mw);
+ g_free (mw);
+}
+
+static void
+set_input_region_empty (GtkWidget *window)
+{
+ struct wl_surface *wl_surface;
+ GdkWindow *gdk_window;
+
+ gdk_window = gtk_widget_get_window (window);
+ if (gdk_window == NULL)
+ return;
+
+ wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);
+ if (wl_surface != NULL)
+ wl_surface_set_input_region (wl_surface, NULL);
+}
+
+static MonitorWindow *
+create_monitor_window (MsdBackgroundManagerWayland *manager G_GNUC_UNUSED,
+ GdkMonitor *monitor)
+{
+ MonitorWindow *mw;
+ GtkWidget *window;
+ GdkRectangle rect;
+ guint i;
+
+ gdk_monitor_get_geometry (monitor, &rect);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_app_paintable (window, TRUE);
+ gtk_widget_set_size_request (window, rect.width, rect.height);
+ gtk_window_set_default_size (GTK_WINDOW (window), rect.width, rect.height);
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+
+ g_debug ("msd-background-wayland: monitor %dx%d at %d,%d", rect.width, rect.height,
+ rect.x, rect.y);
+
+ gtk_layer_init_for_window (GTK_WINDOW (window));
+ gtk_layer_set_layer (GTK_WINDOW (window), GTK_LAYER_SHELL_LAYER_BACKGROUND);
+ gtk_layer_set_namespace (GTK_WINDOW (window), "mate-background");
+ gtk_layer_set_monitor (GTK_WINDOW (window), monitor);
+ for (i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
+ gtk_layer_set_anchor (GTK_WINDOW (window), i, TRUE);
+ gtk_layer_set_keyboard_mode (GTK_WINDOW (window), GTK_LAYER_SHELL_KEYBOARD_MODE_NONE);
+
+ mw = g_new0 (MonitorWindow, 1);
+ mw->window = window;
+ mw->monitor = monitor;
+ mw->manager = manager;
+ mw->width = rect.width;
+ mw->height = rect.height;
+
+ g_signal_connect (window, "draw", G_CALLBACK (on_window_draw), mw);
+ g_signal_connect (window, "size-allocate", G_CALLBACK (on_window_size_allocate), mw);
+ g_signal_connect (window, "destroy", G_CALLBACK (on_window_destroy), mw);
+
+ gtk_widget_show (window);
+
+ set_input_region_empty (window);
+
+ return mw;
+}
+
+static void
+destroy_all_monitor_windows (MsdBackgroundManagerWayland *manager)
+{
+ guint i;
+
+ for (i = 0; i < manager->monitor_windows->len; i++) {
+ MonitorWindow *mw = g_ptr_array_index (manager->monitor_windows, i);
+ gtk_widget_destroy (mw->window);
+ }
+ g_ptr_array_set_size (manager->monitor_windows, 0);
+}
+
+static void
+rebuild_monitor_windows (MsdBackgroundManagerWayland *manager)
+{
+ GdkDisplay *display = manager->display;
+ int n_monitors;
+ int i;
+
+ destroy_all_monitor_windows (manager);
+
+ /* Caja's desktop window (layer "desktop", BACKGROUND) owns the
+ * wallpaper and icons. When the "show-desktop-icons" setting is
+ * enabled we must not map our own layer windows, or we would
+ * cover it.
+ */
+ if (caja_desktop_is_showing (manager)) {
+ manager->caja_desktop_active = TRUE;
+ g_debug ("msd-background-wayland: caja desktop detected, background disabled");
+ return;
+ }
+ manager->caja_desktop_active = FALSE;
+
+ n_monitors = gdk_display_get_n_monitors (display);
+ for (i = 0; i < n_monitors; i++) {
+ GdkMonitor *monitor;
+ MonitorWindow *mw;
+
+ monitor = gdk_display_get_monitor (display, i);
+ mw = create_monitor_window (manager, monitor);
+ g_ptr_array_add (manager->monitor_windows, mw);
+ }
+}
+
+static void
+on_show_desktop_icons_changed (GSettings *settings G_GNUC_UNUSED,
+ const gchar *key G_GNUC_UNUSED,
+ MsdBackgroundManagerWayland *manager)
+{
+ gboolean active;
+
+ active = caja_desktop_is_showing (manager);
+ if (active == manager->caja_desktop_active)
+ return;
+
+ manager->caja_desktop_active = active;
+
+ if (active) {
+ g_debug ("msd-background-wayland: caja desktop icons enabled, hiding background");
+ destroy_all_monitor_windows (manager);
+ } else {
+ g_debug ("msd-background-wayland: caja desktop icons disabled, showing background");
+ rebuild_monitor_windows (manager);
+ redraw_all (manager);
+ }
+}
+
+static void
+on_monitors_changed (GdkScreen *screen G_GNUC_UNUSED,
+ MsdBackgroundManagerWayland *manager)
+{
+ guint n_monitors = gdk_display_get_n_monitors (manager->display);
+ guint i;
+
+ /* A monitor set change (added/removed) requires recreating the
+ * layer windows. A pure geometry/scale change (e.g. the compositor
+ * output is being resized) keeps the same window count, so just
+ * re-render in place at the new size - destroying and recreating
+ * windows mid-resize races with the compositor and leaves the
+ * wallpaper missing.
+ */
+ if (n_monitors != manager->monitor_windows->len) {
+ g_debug ("msd-background-wayland: monitors changed, rebuilding (%u monitors)",
+ n_monitors);
+ rebuild_monitor_windows (manager);
+ redraw_all (manager);
+ return;
+ }
+
+ g_debug ("msd-background-wayland: monitors changed, re-rendering in place (%u monitors)",
+ n_monitors);
+
+ for (i = 0; i < manager->monitor_windows->len; i++) {
+ MonitorWindow *mw = g_ptr_array_index (manager->monitor_windows, i);
+ render_surface_for_monitor (manager, mw, FALSE);
+ gtk_widget_queue_draw (mw->window);
+ }
+}
+
+static void
+on_bg_changed (MateBG *bg G_GNUC_UNUSED,
+ MsdBackgroundManagerWayland *manager)
+{
+ if (manager->caja_desktop_active)
+ return;
+
+ g_debug ("msd-background-wayland: background changed, redrawing");
+ redraw_all (manager);
+}
+
+static gboolean
+settings_change_event_idle_cb (MsdBackgroundManagerWayland *manager)
+{
+ g_debug ("msd-background-wayland: applying settings change");
+ mate_bg_load_from_preferences (manager->bg);
+ return FALSE;
+}
+
+static gboolean
+settings_change_event_cb (GSettings *settings G_GNUC_UNUSED,
+ gpointer keys G_GNUC_UNUSED,
+ gint n_keys G_GNUC_UNUSED,
+ MsdBackgroundManagerWayland *manager)
+{
+ g_idle_add ((GSourceFunc) settings_change_event_idle_cb, manager);
+ return FALSE;
+}
+
+MsdBackgroundManagerWayland *
+msd_background_manager_wayland_new (GError **error)
+{
+ MsdBackgroundManagerWayland *manager;
+ GdkDisplay *display;
+
+ display = gdk_display_get_default ();
+ if (display == NULL || !GDK_IS_WAYLAND_DISPLAY (display)) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "A Wayland display is required for the Wayland background backend");
+ return NULL;
+ }
+
+ manager = g_new0 (MsdBackgroundManagerWayland, 1);
+ manager->display = display;
+ manager->monitor_windows = g_ptr_array_new ();
+
+ manager->settings = g_settings_new (MATE_BG_SCHEMA);
+ manager->bg = mate_bg_new ();
+
+ g_signal_connect (manager->bg, "changed",
+ G_CALLBACK (on_bg_changed), manager);
+ g_signal_connect (manager->bg, "transitioned",
+ G_CALLBACK (on_bg_changed), manager);
+ g_signal_connect (manager->settings, "change-event",
+ G_CALLBACK (settings_change_event_cb), manager);
+ g_signal_connect (gdk_screen_get_default (), "monitors-changed",
+ G_CALLBACK (on_monitors_changed), manager);
+
+ g_debug ("msd-background-wayland: initializing on %d monitors",
+ gdk_display_get_n_monitors (display));
+
+ mate_bg_load_from_gsettings (manager->bg, manager->settings);
+
+ rebuild_monitor_windows (manager);
+ redraw_all (manager);
+
+ g_signal_connect (manager->settings, "changed::show-desktop-icons",
+ G_CALLBACK (on_show_desktop_icons_changed), manager);
+
+ return manager;
+}
+
+void
+msd_background_manager_wayland_destroy (MsdBackgroundManagerWayland *manager)
+{
+ if (manager == NULL)
+ return;
+
+ g_signal_handlers_disconnect_by_func (gdk_screen_get_default (),
+ G_CALLBACK (on_monitors_changed), manager);
+
+ destroy_all_monitor_windows (manager);
+ g_ptr_array_free (manager->monitor_windows, TRUE);
+
+ if (manager->settings != NULL) {
+ g_signal_handlers_disconnect_by_func (manager->settings,
+ G_CALLBACK (settings_change_event_cb), manager);
+ g_signal_handlers_disconnect_by_func (manager->settings,
+ G_CALLBACK (on_show_desktop_icons_changed), manager);
+ g_object_unref (manager->settings);
+ }
+
+ if (manager->bg != NULL) {
+ g_signal_handlers_disconnect_by_func (manager->bg,
+ G_CALLBACK (on_bg_changed), manager);
+ g_object_unref (manager->bg);
+ }
+
+ g_free (manager);
+}
diff --git a/plugins/background/msd-background-manager-wayland.h b/plugins/background/msd-background-manager-wayland.h
new file mode 100644
index 0000000..8125f58
--- /dev/null
+++ b/plugins/background/msd-background-manager-wayland.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef MSD_BACKGROUND_MANAGER_WAYLAND_H
+#define MSD_BACKGROUND_MANAGER_WAYLAND_H
+
+#include <glib-object.h>
+#include <glib.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _MsdBackgroundManagerWayland MsdBackgroundManagerWayland;
+
+MsdBackgroundManagerWayland *msd_background_manager_wayland_new (GError **error);
+
+void msd_background_manager_wayland_destroy (MsdBackgroundManagerWayland *manager);
+
+G_END_DECLS
+
+#endif /* MSD_BACKGROUND_MANAGER_WAYLAND_H */
diff --git a/plugins/background/msd-background-manager.c b/plugins/background/msd-background-manager.c
index 5264bfa..514f74c 100644
--- a/plugins/background/msd-background-manager.c
+++ b/plugins/background/msd-background-manager.c
@@ -37,7 +37,12 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
+#ifdef HAVE_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif /* HAVE_WAYLAND */
#include <gio/gio.h>
#define MATE_DESKTOP_USE_UNSTABLE_API
@@ -47,6 +52,10 @@
#include "mate-settings-profile.h"
#include "msd-background-manager.h"
+#ifdef HAVE_WAYLAND
+#include "msd-background-manager-wayland.h"
+#endif /* HAVE_WAYLAND */
+
struct _MsdBackgroundManager {
GObject parent;
@@ -55,6 +64,10 @@ struct _MsdBackgroundManager {
cairo_surface_t *surface;
gboolean draw_in_progress;
+
+#ifdef HAVE_WAYLAND
+ MsdBackgroundManagerWayland *wayland;
+#endif /* HAVE_WAYLAND */
};
G_DEFINE_TYPE (MsdBackgroundManager, msd_background_manager, G_TYPE_OBJECT)
@@ -241,6 +254,33 @@ msd_background_manager_start (MsdBackgroundManager *manager,
g_debug ("Starting background manager");
mate_settings_profile_start (NULL);
+#ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ manager->wayland = msd_background_manager_wayland_new (error);
+ if (manager->wayland == NULL) {
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+
+ mate_settings_profile_end (NULL);
+ return TRUE;
+ }
+#endif /* HAVE_WAYLAND */
+
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "X11 display is required for the background plugin");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+#else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The background plugin was built without X11 support");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
+
manager->settings = g_settings_new (MATE_BG_SCHEMA);
manager->bg = mate_bg_new();
@@ -265,6 +305,19 @@ msd_background_manager_stop (MsdBackgroundManager *manager)
{
g_debug ("Stopping background manager");
+#ifdef HAVE_WAYLAND
+ if (manager->wayland != NULL) {
+ msd_background_manager_wayland_destroy (manager->wayland);
+ manager->wayland = NULL;
+ return;
+ }
+#endif /* HAVE_WAYLAND */
+
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
disconnect_screen_signals (manager);
if (manager->settings != NULL) {
diff --git a/plugins/clipboard/Makefile.am b/plugins/clipboard/Makefile.am
index 414fff1..19c9c24 100644
--- a/plugins/clipboard/Makefile.am
+++ b/plugins/clipboard/Makefile.am
@@ -1,5 +1,25 @@
NULL =
+if BUILD_WAYLAND
+WAYLAND_PROTOCOL_XML = $(WAYLAND_PROTOCOLS_DATADIR)/staging/ext-data-control/ext-data-control-v1.xml
+WAYLAND_PROTOCOL_GENERATED = \
+ ext-data-control-v1-client.c \
+ ext-data-control-v1-client.h \
+ $(NULL)
+WAYLAND_CLIPBOARD_SOURCES = \
+ msd-clipboard-manager-wayland.c \
+ msd-clipboard-manager-wayland.h \
+ $(NULL)
+else
+WAYLAND_PROTOCOL_XML =
+WAYLAND_PROTOCOL_GENERATED =
+WAYLAND_CLIPBOARD_SOURCES =
+endif
+
+BUILT_SOURCES = \
+ $(WAYLAND_PROTOCOL_GENERATED) \
+ $(NULL)
+
plugin_LTLIBRARIES = \
libclipboard.la \
$(NULL)
@@ -9,19 +29,23 @@ libclipboard_la_SOURCES = \
msd-clipboard-plugin.c \
msd-clipboard-manager.h \
msd-clipboard-manager.c \
+ $(WAYLAND_CLIPBOARD_SOURCES) \
xutils.h \
xutils.c \
list.h \
list.c \
+ $(WAYLAND_PROTOCOL_GENERATED) \
$(NULL)
libclipboard_la_CPPFLAGS = \
-I$(top_srcdir)/mate-settings-daemon \
-DMATE_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CPPFLAGS)
libclipboard_la_CFLAGS = \
$(SETTINGS_PLUGIN_CFLAGS) \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CFLAGS) \
$(WARN_CFLAGS)
@@ -33,8 +57,17 @@ libclipboard_la_LIBADD = \
$(SETTINGS_PLUGIN_LIBS) \
$(X11_LIBS) \
$(XINPUT_LIBS) \
+ $(WAYLAND_CLIENT_LIBS) \
$(NULL)
+$(WAYLAND_PROTOCOL_GENERATED): $(WAYLAND_PROTOCOL_XML)
+
+ext-data-control-v1-client.h: $(WAYLAND_PROTOCOL_XML)
+ $(AM_V_GEN)$(WAYLAND_SCANNER) client-header $(WAYLAND_PROTOCOL_XML) $@
+
+ext-data-control-v1-client.c: $(WAYLAND_PROTOCOL_XML)
+ $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $(WAYLAND_PROTOCOL_XML) $@
+
plugin_in_files = \
clipboard.mate-settings-plugin.desktop.in \
$(NULL)
@@ -47,10 +80,12 @@ EXTRA_DIST = \
CLEANFILES = \
$(plugin_DATA) \
+ $(WAYLAND_PROTOCOL_GENERATED) \
$(NULL)
DISTCLEANFILES = \
$(plugin_DATA) \
+ $(WAYLAND_PROTOCOL_GENERATED) \
$(NULL)
$(plugin_DATA): $(plugin_in_files)
diff --git a/plugins/clipboard/msd-clipboard-manager-wayland.c b/plugins/clipboard/msd-clipboard-manager-wayland.c
new file mode 100644
index 0000000..a8701c8
--- /dev/null
+++ b/plugins/clipboard/msd-clipboard-manager-wayland.c
@@ -0,0 +1,769 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <wayland-client.h>
+
+#include "ext-data-control-v1-client.h"
+#include "msd-clipboard-manager-wayland.h"
+
+typedef enum
+{
+ MSD_WAYLAND_SELECTION_CLIPBOARD,
+ MSD_WAYLAND_SELECTION_PRIMARY
+} SelectionKind;
+
+typedef struct
+{
+ gchar *mime;
+ GByteArray *data;
+} ClipEntry;
+
+typedef struct _MsdClipboardManagerWayland MsdClipboardManagerWayland;
+
+typedef struct
+{
+ MsdClipboardManagerWayland *wl;
+ SelectionKind kind;
+ gint fd;
+ guint source_id;
+ gchar *mime;
+ GByteArray *data;
+ gboolean done;
+} ClipReceive;
+
+typedef struct
+{
+ MsdClipboardManagerWayland *wl;
+ SelectionKind kind;
+ struct ext_data_control_offer_v1 *offer;
+ GPtrArray *cache;
+ GPtrArray *receives;
+ struct ext_data_control_source_v1 *source;
+ gboolean reoffer_pending;
+ guint reoffer_timeout;
+ gint64 last_activity_ms;
+} SelectionState;
+
+struct _MsdClipboardManagerWayland
+{
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct ext_data_control_manager_v1 *dc_manager;
+ struct wl_seat *seat;
+ struct ext_data_control_device_v1 *device;
+ guint display_source;
+
+ struct ext_data_control_offer_v1 *pending_offer;
+ GPtrArray *pending_mimes;
+
+ SelectionState clipboard;
+ SelectionState primary;
+};
+
+static void selection_state_clear (SelectionState *state);
+
+static void
+clip_entry_free (gpointer data)
+{
+ ClipEntry *entry = data;
+
+ if (entry == NULL)
+ return;
+
+ g_free (entry->mime);
+ if (entry->data != NULL)
+ g_byte_array_free (entry->data, TRUE);
+ g_free (entry);
+}
+
+static SelectionState *
+state_from_kind (MsdClipboardManagerWayland *wl,
+ SelectionKind kind)
+{
+ if (kind == MSD_WAYLAND_SELECTION_PRIMARY)
+ return &wl->primary;
+
+ return &wl->clipboard;
+}
+
+static void
+start_reoffer (SelectionState *state);
+
+#define REOFFER_WINDOW_MS 2000
+#define REOFFER_QUIET_MS 1000
+
+static void
+cancel_reoffer (SelectionState *state)
+{
+ if (state->reoffer_timeout != 0) {
+ g_source_remove (state->reoffer_timeout);
+ state->reoffer_timeout = 0;
+ }
+}
+
+static gboolean
+reoffer_timeout_cb (gpointer data)
+{
+ SelectionState *state = data;
+
+ state->reoffer_timeout = 0;
+ start_reoffer (state);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_reoffer (SelectionState *state)
+{
+ if (state->cache == NULL || state->cache->len == 0)
+ return;
+ if (state->wl->dc_manager == NULL || state->wl->device == NULL)
+ return;
+ if (state->source != NULL)
+ return;
+
+ if (state->reoffer_timeout == 0) {
+ gint64 now;
+ guint delay;
+
+ now = g_get_monotonic_time () / 1000;
+
+ /* Keep a longer delay right after clipboard activity: an app
+ * may be about to re-assert its selection (e.g. GTK re-serving
+ * a torn copy if we steal ownership mid-store). Once the
+ * selection has been quiet, fall back to a shorter delay so
+ * pasting works soon after the source app is closed. */
+ delay = REOFFER_WINDOW_MS - (now - state->last_activity_ms);
+ if (delay > REOFFER_WINDOW_MS)
+ delay = REOFFER_WINDOW_MS;
+ if (delay < REOFFER_QUIET_MS)
+ delay = REOFFER_QUIET_MS;
+
+ state->reoffer_timeout = g_timeout_add (delay,
+ reoffer_timeout_cb,
+ state);
+ }
+}
+
+static void
+finalize_cache (SelectionState *state)
+{
+ GPtrArray *new_cache;
+ guint i;
+
+ if (state->receives == NULL)
+ return;
+
+ new_cache = g_ptr_array_new_with_free_func (clip_entry_free);
+
+ for (i = 0; i < state->receives->len; i++) {
+ ClipReceive *recv;
+ ClipEntry *entry;
+
+ recv = state->receives->pdata[i];
+
+ entry = g_new0 (ClipEntry, 1);
+ entry->mime = recv->mime;
+ entry->data = recv->data;
+ recv->mime = NULL;
+ recv->data = NULL;
+
+ g_ptr_array_add (new_cache, entry);
+ }
+
+ if (state->cache != NULL)
+ g_ptr_array_free (state->cache, TRUE);
+ state->cache = new_cache;
+
+ for (i = 0; i < state->receives->len; i++) {
+ ClipReceive *recv;
+
+ recv = state->receives->pdata[i];
+ if (recv->fd != -1)
+ close (recv->fd);
+ g_free (recv);
+ }
+ g_ptr_array_free (state->receives, FALSE);
+ state->receives = NULL;
+}
+
+static void
+receive_finish (ClipReceive *recv)
+{
+ SelectionState *state;
+ guint i;
+
+ if (recv->done)
+ return;
+ recv->done = TRUE;
+
+ if (recv->fd != -1) {
+ close (recv->fd);
+ recv->fd = -1;
+ }
+
+ state = state_from_kind (recv->wl, recv->kind);
+
+ if (state->receives == NULL)
+ return;
+
+ for (i = 0; i < state->receives->len; i++) {
+ ClipReceive *other;
+
+ other = state->receives->pdata[i];
+ if (!other->done)
+ return;
+ }
+
+ finalize_cache (state);
+
+ if (state->reoffer_pending) {
+ state->reoffer_pending = FALSE;
+ schedule_reoffer (state);
+ }
+}
+
+static void
+receive_cancel (ClipReceive *recv)
+{
+ if (recv->source_id != 0)
+ g_source_remove (recv->source_id);
+ if (recv->fd != -1)
+ close (recv->fd);
+ g_free (recv->mime);
+ if (recv->data != NULL)
+ g_byte_array_free (recv->data, TRUE);
+ g_free (recv);
+}
+
+static gboolean
+receive_dispatch_cb (gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ ClipReceive *recv = user_data;
+ guchar buf[4096];
+ gssize n;
+
+ n = read (fd, buf, sizeof (buf));
+ if (n > 0) {
+ g_byte_array_append (recv->data, buf, n);
+ return G_SOURCE_CONTINUE;
+ }
+ if (n == 0) {
+ receive_finish (recv);
+ return G_SOURCE_REMOVE;
+ }
+ if (n < 0 && errno == EINTR)
+ return G_SOURCE_CONTINUE;
+ if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ receive_finish (recv);
+ return G_SOURCE_REMOVE;
+ }
+ return G_SOURCE_CONTINUE;
+ }
+
+ receive_finish (recv);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+start_receives (SelectionState *state,
+ struct ext_data_control_offer_v1 *offer,
+ GPtrArray *mimes)
+{
+ guint i;
+
+ if (state->receives != NULL) {
+ for (i = 0; i < state->receives->len; i++)
+ receive_cancel (state->receives->pdata[i]);
+ g_ptr_array_free (state->receives, FALSE);
+ state->receives = NULL;
+ }
+
+ state->receives = g_ptr_array_new ();
+
+ if (mimes == NULL)
+ return;
+
+ state->last_activity_ms = g_get_monotonic_time () / 1000;
+
+ for (i = 0; i < mimes->len; i++) {
+ ClipReceive *recv;
+ int fds[2];
+
+ if (pipe (fds) < 0)
+ continue;
+
+ ext_data_control_offer_v1_receive (offer, mimes->pdata[i], fds[1]);
+ close (fds[1]);
+
+ recv = g_new0 (ClipReceive, 1);
+ recv->wl = state->wl;
+ recv->kind = state->kind;
+ recv->fd = fds[0];
+ recv->mime = g_strdup (mimes->pdata[i]);
+ recv->data = g_byte_array_new ();
+ fcntl (recv->fd, F_SETFL, O_NONBLOCK);
+ recv->source_id = g_unix_fd_add (recv->fd,
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ receive_dispatch_cb,
+ recv);
+ g_ptr_array_add (state->receives, recv);
+ }
+}
+
+static void
+source_write_all (gint fd,
+ const guint8 *data,
+ gsize len)
+{
+ gsize total = 0;
+ gint flags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags != -1)
+ fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+
+ while (total < len) {
+ gssize n;
+
+ n = write (fd, data + total, len - total);
+ if (n > 0) {
+ total += n;
+ continue;
+ }
+ if (n < 0 && errno == EINTR)
+ continue;
+ if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ GPollFD pfd;
+
+ pfd.fd = fd;
+ pfd.events = G_IO_OUT;
+ if (g_poll (&pfd, 1, 10000) <= 0)
+ break;
+ continue;
+ }
+ break;
+ }
+
+ close (fd);
+}
+
+static void
+handle_source_send (void *data,
+ struct ext_data_control_source_v1 *source,
+ const char *mime_type,
+ int32_t fd)
+{
+ SelectionState *state = data;
+ guint i;
+
+ if (state->cache == NULL) {
+ close (fd);
+ return;
+ }
+
+ for (i = 0; i < state->cache->len; i++) {
+ ClipEntry *entry;
+
+ entry = state->cache->pdata[i];
+ if (g_strcmp0 (entry->mime, mime_type) == 0) {
+ source_write_all (fd, entry->data->data, entry->data->len);
+ return;
+ }
+ }
+
+ close (fd);
+}
+
+static void
+handle_source_cancelled (void *data,
+ struct ext_data_control_source_v1 *source)
+{
+ SelectionState *state = data;
+
+ if (state->source == source) {
+ ext_data_control_source_v1_destroy (source);
+ state->source = NULL;
+ }
+
+ state->last_activity_ms = g_get_monotonic_time () / 1000;
+}
+
+static const struct ext_data_control_source_v1_listener source_listener = {
+ .send = handle_source_send,
+ .cancelled = handle_source_cancelled,
+};
+
+static void
+start_reoffer (SelectionState *state)
+{
+ struct ext_data_control_source_v1 *source;
+ guint i;
+
+ if (state->cache == NULL || state->cache->len == 0)
+ return;
+ if (state->wl->dc_manager == NULL || state->wl->device == NULL)
+ return;
+ if (state->source != NULL)
+ return;
+
+ source = ext_data_control_manager_v1_create_data_source (state->wl->dc_manager);
+ ext_data_control_source_v1_add_listener (source, &source_listener, state);
+
+ for (i = 0; i < state->cache->len; i++) {
+ ClipEntry *entry;
+
+ entry = state->cache->pdata[i];
+ ext_data_control_source_v1_offer (source, entry->mime);
+ }
+
+ state->source = source;
+
+ if (state->kind == MSD_WAYLAND_SELECTION_PRIMARY)
+ ext_data_control_device_v1_set_primary_selection (state->wl->device, source);
+ else
+ ext_data_control_device_v1_set_selection (state->wl->device, source);
+
+ wl_display_flush (state->wl->display);
+}
+
+static void
+handle_offer (void *data,
+ struct ext_data_control_offer_v1 *offer,
+ const char *mime_type)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ if (wl->pending_offer == offer && wl->pending_mimes != NULL)
+ g_ptr_array_add (wl->pending_mimes, g_strdup (mime_type));
+}
+
+static const struct ext_data_control_offer_v1_listener offer_listener = {
+ .offer = handle_offer,
+};
+
+static void
+handle_selection_for (MsdClipboardManagerWayland *wl,
+ SelectionState *state,
+ struct ext_data_control_offer_v1 *offer)
+{
+ if (offer != NULL && state->source != NULL) {
+ /* The compositor is echoing our own set_selection back to
+ * us: state->source is still the current selection (it was
+ * not cancelled), so this offer describes our own data.
+ * Keep the source alive and ignore the echoed offer.
+ */
+ if (state->offer != NULL && state->offer != offer)
+ ext_data_control_offer_v1_destroy (state->offer);
+ state->offer = offer;
+
+ if (wl->pending_offer != NULL)
+ wl->pending_offer = NULL;
+ if (wl->pending_mimes != NULL) {
+ g_ptr_array_free (wl->pending_mimes, TRUE);
+ wl->pending_mimes = NULL;
+ }
+ return;
+ }
+
+ if (state->source != NULL) {
+ ext_data_control_source_v1_destroy (state->source);
+ state->source = NULL;
+ }
+
+ if (state->offer != NULL && state->offer != offer) {
+ ext_data_control_offer_v1_destroy (state->offer);
+ state->offer = NULL;
+ }
+
+ if (offer == NULL) {
+ state->offer = NULL;
+
+ if (state->receives != NULL && state->receives->len > 0) {
+ state->reoffer_pending = TRUE;
+ } else {
+ schedule_reoffer (state);
+ }
+
+ return;
+ }
+
+ state->offer = offer;
+ state->reoffer_pending = FALSE;
+ cancel_reoffer (state);
+
+ if (wl->pending_offer == offer && wl->pending_mimes != NULL) {
+ GPtrArray *mimes;
+
+ mimes = wl->pending_mimes;
+ wl->pending_mimes = NULL;
+ wl->pending_offer = NULL;
+
+ start_receives (state, offer, mimes);
+ g_ptr_array_free (mimes, TRUE);
+ } else {
+ start_receives (state, offer, NULL);
+ }
+}
+
+static void
+handle_selection (void *data,
+ struct ext_data_control_device_v1 *device,
+ struct ext_data_control_offer_v1 *offer)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ handle_selection_for (wl, &wl->clipboard, offer);
+}
+
+static void
+handle_primary_selection (void *data,
+ struct ext_data_control_device_v1 *device,
+ struct ext_data_control_offer_v1 *offer)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ handle_selection_for (wl, &wl->primary, offer);
+}
+
+static void
+handle_data_offer (void *data,
+ struct ext_data_control_device_v1 *device,
+ struct ext_data_control_offer_v1 *offer)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ if (wl->pending_offer != NULL)
+ ext_data_control_offer_v1_destroy (wl->pending_offer);
+ if (wl->pending_mimes != NULL)
+ g_ptr_array_free (wl->pending_mimes, TRUE);
+
+ wl->pending_offer = offer;
+ wl->pending_mimes = g_ptr_array_new_with_free_func (g_free);
+ ext_data_control_offer_v1_add_listener (offer, &offer_listener, wl);
+}
+
+static void
+handle_finished (void *data,
+ struct ext_data_control_device_v1 *device)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ ext_data_control_device_v1_destroy (device);
+ wl->device = NULL;
+}
+
+static const struct ext_data_control_device_v1_listener device_listener = {
+ .data_offer = handle_data_offer,
+ .selection = handle_selection,
+ .finished = handle_finished,
+ .primary_selection = handle_primary_selection,
+};
+
+static void
+registry_handle_global (void *data,
+ struct wl_registry *registry,
+ uint32_t name,
+ const char *interface,
+ uint32_t version)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ if (g_strcmp0 (interface, "ext_data_control_manager_v1") == 0) {
+ wl->dc_manager = wl_registry_bind (registry,
+ name,
+ &ext_data_control_manager_v1_interface,
+ MIN (version, 1u));
+ } else if (g_strcmp0 (interface, "wl_seat") == 0) {
+ wl->seat = wl_registry_bind (registry,
+ name,
+ &wl_seat_interface,
+ MIN (version, 5u));
+ }
+}
+
+static void
+registry_handle_global_remove (void *data,
+ struct wl_registry *registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_global,
+ .global_remove = registry_handle_global_remove,
+};
+
+static gboolean
+display_dispatch_cb (gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ MsdClipboardManagerWayland *wl = user_data;
+
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ g_warning ("Clipboard manager: Wayland connection lost");
+ return G_SOURCE_REMOVE;
+ }
+
+ if (wl_display_dispatch (wl->display) == -1) {
+ g_warning ("Clipboard manager: Failed to dispatch Wayland events");
+ return G_SOURCE_REMOVE;
+ }
+
+ wl_display_flush (wl->display);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+selection_state_clear (SelectionState *state)
+{
+ guint i;
+
+ cancel_reoffer (state);
+
+ if (state->source != NULL) {
+ ext_data_control_source_v1_destroy (state->source);
+ state->source = NULL;
+ }
+
+ if (state->offer != NULL) {
+ ext_data_control_offer_v1_destroy (state->offer);
+ state->offer = NULL;
+ }
+
+ if (state->receives != NULL) {
+ for (i = 0; i < state->receives->len; i++)
+ receive_cancel (state->receives->pdata[i]);
+ g_ptr_array_free (state->receives, FALSE);
+ state->receives = NULL;
+ }
+
+ if (state->cache != NULL) {
+ g_ptr_array_free (state->cache, TRUE);
+ state->cache = NULL;
+ }
+}
+
+MsdClipboardManagerWayland *
+msd_clipboard_manager_wayland_new (GError **error)
+{
+ MsdClipboardManagerWayland *wl;
+
+ wl = g_new0 (MsdClipboardManagerWayland, 1);
+
+ wl->display = wl_display_connect (NULL);
+ if (wl->display == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not connect to the Wayland compositor");
+ g_free (wl);
+ return NULL;
+ }
+
+ wl->clipboard.wl = wl;
+ wl->clipboard.kind = MSD_WAYLAND_SELECTION_CLIPBOARD;
+ wl->primary.wl = wl;
+ wl->primary.kind = MSD_WAYLAND_SELECTION_PRIMARY;
+
+ wl->registry = wl_display_get_registry (wl->display);
+ wl_registry_add_listener (wl->registry, &registry_listener, wl);
+
+ wl_display_roundtrip (wl->display);
+
+ if (wl->dc_manager == NULL || wl->seat == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The compositor does not support the ext-data-control protocol");
+ msd_clipboard_manager_wayland_destroy (wl);
+ return NULL;
+ }
+
+ wl->device = ext_data_control_manager_v1_get_data_device (wl->dc_manager, wl->seat);
+ ext_data_control_device_v1_add_listener (wl->device, &device_listener, wl);
+
+ wl->display_source = g_unix_fd_add (wl_display_get_fd (wl->display),
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ display_dispatch_cb,
+ wl);
+
+ wl_display_roundtrip (wl->display);
+
+ return wl;
+}
+
+void
+msd_clipboard_manager_wayland_destroy (MsdClipboardManagerWayland *wl)
+{
+ if (wl == NULL)
+ return;
+
+ if (wl->display_source != 0) {
+ g_source_remove (wl->display_source);
+ wl->display_source = 0;
+ }
+
+ selection_state_clear (&wl->clipboard);
+ selection_state_clear (&wl->primary);
+
+ if (wl->pending_offer != NULL) {
+ ext_data_control_offer_v1_destroy (wl->pending_offer);
+ wl->pending_offer = NULL;
+ }
+ if (wl->pending_mimes != NULL) {
+ g_ptr_array_free (wl->pending_mimes, TRUE);
+ wl->pending_mimes = NULL;
+ }
+
+ if (wl->device != NULL) {
+ ext_data_control_device_v1_destroy (wl->device);
+ wl->device = NULL;
+ }
+ if (wl->dc_manager != NULL) {
+ ext_data_control_manager_v1_destroy (wl->dc_manager);
+ wl->dc_manager = NULL;
+ }
+ if (wl->seat != NULL) {
+ wl_seat_destroy (wl->seat);
+ wl->seat = NULL;
+ }
+ if (wl->registry != NULL) {
+ wl_registry_destroy (wl->registry);
+ wl->registry = NULL;
+ }
+ if (wl->display != NULL) {
+ wl_display_disconnect (wl->display);
+ wl->display = NULL;
+ }
+
+ g_free (wl);
+}
diff --git a/plugins/clipboard/msd-clipboard-manager-wayland.h b/plugins/clipboard/msd-clipboard-manager-wayland.h
new file mode 100644
index 0000000..886ed4d
--- /dev/null
+++ b/plugins/clipboard/msd-clipboard-manager-wayland.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __MSD_CLIPBOARD_MANAGER_WAYLAND_H
+#define __MSD_CLIPBOARD_MANAGER_WAYLAND_H
+
+#include <glib-object.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _MsdClipboardManagerWayland MsdClipboardManagerWayland;
+
+MsdClipboardManagerWayland *msd_clipboard_manager_wayland_new (GError **error);
+void msd_clipboard_manager_wayland_destroy (MsdClipboardManagerWayland *manager);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MSD_CLIPBOARD_MANAGER_WAYLAND_H */
diff --git a/plugins/clipboard/msd-clipboard-manager.c b/plugins/clipboard/msd-clipboard-manager.c
index ef4007a..b885b89 100644
--- a/plugins/clipboard/msd-clipboard-manager.c
+++ b/plugins/clipboard/msd-clipboard-manager.c
@@ -36,8 +36,14 @@
#include <glib.h>
#include <glib/gi18n.h>
+#include <gio/gio.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif /* GDK_WINDOWING_WAYLAND */
#include <X11/Xlib.h>
#include <X11/Xatom.h>
@@ -46,6 +52,9 @@
#include "mate-settings-profile.h"
#include "msd-clipboard-manager.h"
+#ifdef HAVE_WAYLAND
+#include "msd-clipboard-manager-wayland.h"
+#endif /* HAVE_WAYLAND */
struct MsdClipboardManagerPrivate
{
@@ -59,6 +68,10 @@ struct MsdClipboardManagerPrivate
Window requestor;
Atom property;
Time time;
+
+#ifdef HAVE_WAYLAND
+ MsdClipboardManagerWayland *wayland;
+#endif /* HAVE_WAYLAND */
};
typedef struct
@@ -930,16 +943,40 @@ start_clipboard_idle_cb (MsdClipboardManager *manager)
}
gboolean
-msd_clipboard_manager_start (MsdClipboardManager *manager,
+ msd_clipboard_manager_start (MsdClipboardManager *manager,
GError **error)
{
mate_settings_profile_start (NULL);
- g_idle_add ((GSourceFunc) start_clipboard_idle_cb, manager);
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_idle_add ((GSourceFunc) start_clipboard_idle_cb, manager);
+
+ mate_settings_profile_end (NULL);
+
+ return TRUE;
+ }
+#endif /* GDK_WINDOWING_X11 */
+
+#if defined (HAVE_WAYLAND) && defined (GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ manager->priv->wayland = msd_clipboard_manager_wayland_new (error);
+ if (manager->priv->wayland == NULL) {
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+
+ mate_settings_profile_end (NULL);
+ return TRUE;
+ }
+#endif /* HAVE_WAYLAND && GDK_WINDOWING_WAYLAND */
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The clipboard plugin requires an X11 or a Wayland display with the ext-data-control protocol");
mate_settings_profile_end (NULL);
- return TRUE;
+ return FALSE;
}
void
@@ -947,18 +984,32 @@ msd_clipboard_manager_stop (MsdClipboardManager *manager)
{
g_debug ("Stopping clipboard manager");
- clipboard_manager_watch_cb (manager,
- manager->priv->window,
- FALSE,
- 0,
- NULL);
- XDestroyWindow (manager->priv->display, manager->priv->window);
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ clipboard_manager_watch_cb (manager,
+ manager->priv->window,
+ FALSE,
+ 0,
+ NULL);
+ XDestroyWindow (manager->priv->display, manager->priv->window);
+
+ list_foreach (manager->priv->conversions, (Callback) conversion_free, NULL);
+ list_free (manager->priv->conversions);
- list_foreach (manager->priv->conversions, (Callback) conversion_free, NULL);
- list_free (manager->priv->conversions);
+ list_foreach (manager->priv->contents, (Callback) target_data_unref, NULL);
+ list_free (manager->priv->contents);
- list_foreach (manager->priv->contents, (Callback) target_data_unref, NULL);
- list_free (manager->priv->contents);
+ return;
+ }
+#endif /* GDK_WINDOWING_X11 */
+
+#if defined (HAVE_WAYLAND) && defined (GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ msd_clipboard_manager_wayland_destroy (manager->priv->wayland);
+ manager->priv->wayland = NULL;
+ return;
+ }
+#endif /* HAVE_WAYLAND && GDK_WINDOWING_WAYLAND */
}
static void
@@ -974,8 +1025,18 @@ msd_clipboard_manager_init (MsdClipboardManager *manager)
{
manager->priv = msd_clipboard_manager_get_instance_private (manager);
- manager->priv->display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
-
+#ifdef HAVE_WAYLAND
+ manager->priv->wayland = NULL;
+#endif /* HAVE_WAYLAND */
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ manager->priv->display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ else
+ manager->priv->display = NULL;
+#else
+ manager->priv->display = NULL;
+#endif /* GDK_WINDOWING_X11 */
}
static void
@@ -990,6 +1051,13 @@ msd_clipboard_manager_finalize (GObject *object)
g_return_if_fail (clipboard_manager->priv != NULL);
+#if defined (HAVE_WAYLAND) && defined (GDK_WINDOWING_WAYLAND)
+ if (clipboard_manager->priv->wayland != NULL) {
+ msd_clipboard_manager_wayland_destroy (clipboard_manager->priv->wayland);
+ clipboard_manager->priv->wayland = NULL;
+ }
+#endif /* HAVE_WAYLAND && GDK_WINDOWING_WAYLAND */
+
G_OBJECT_CLASS (msd_clipboard_manager_parent_class)->finalize (object);
}
diff --git a/plugins/common/eggaccelerators.c b/plugins/common/eggaccelerators.c
index 0f924d3..77f320b 100644
--- a/plugins/common/eggaccelerators.c
+++ b/plugins/common/eggaccelerators.c
@@ -23,7 +23,9 @@
#include <stdlib.h>
#include <string.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
@@ -566,6 +568,7 @@ static void
reload_modmap (GdkKeymap *keymap,
EggModmap *modmap)
{
+#ifdef GDK_WINDOWING_X11
XModifierKeymap *xmodmap;
int map_size;
int i;
@@ -629,6 +632,11 @@ reload_modmap (GdkKeymap *keymap,
g_free (keys);
}
+ XFreeModifiermap (xmodmap);
+#else
+ memset (modmap->mapping, 0, sizeof (modmap->mapping));
+#endif /* GDK_WINDOWING_X11 */
+
/* Add in the not-really-virtual fixed entries */
modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_CONTROL] |= EGG_VIRTUAL_CONTROL_MASK;
@@ -638,8 +646,6 @@ reload_modmap (GdkKeymap *keymap,
modmap->mapping[EGG_MODMAP_ENTRY_MOD3] |= EGG_VIRTUAL_MOD3_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK;
-
- XFreeModifiermap (xmodmap);
}
const EggModmap*
diff --git a/plugins/common/msd-input-helper.c b/plugins/common/msd-input-helper.c
index 05fad76..b6ca524 100644
--- a/plugins/common/msd-input-helper.c
+++ b/plugins/common/msd-input-helper.c
@@ -22,7 +22,9 @@
#include "config.h"
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <sys/types.h>
#include <X11/Xatom.h>
@@ -32,13 +34,20 @@
gboolean
supports_xinput_devices (void)
{
+#ifdef GDK_WINDOWING_X11
gint op_code, event, error;
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return FALSE;
+
return XQueryExtension (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
"XInputExtension",
&op_code,
&event,
&error);
+#else
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
}
static gboolean
@@ -78,6 +87,9 @@ device_is_touchpad (XDeviceInfo *deviceinfo)
display = gdk_display_get_default ();
+ if (!GDK_IS_X11_DISPLAY (display))
+ return NULL;
+
if (deviceinfo->type != XInternAtom (GDK_DISPLAY_XDISPLAY (display), XI_TOUCHPAD, True))
return NULL;
@@ -103,6 +115,9 @@ touchpad_is_present (void)
guint i;
gboolean retval;
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return FALSE;
+
if (supports_xinput_devices () == FALSE)
return TRUE;
diff --git a/plugins/common/msd-keygrab.c b/plugins/common/msd-keygrab.c
index 0ea0cc1..184aa21 100644
--- a/plugins/common/msd-keygrab.c
+++ b/plugins/common/msd-keygrab.c
@@ -24,7 +24,9 @@
#include "config.h"
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#ifdef HAVE_X11_EXTENSIONS_XKB_H
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
@@ -74,6 +76,10 @@ grab_key_real (guint keycode,
gboolean grab,
int mask)
{
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+
if (grab) {
XGrabKey (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
keycode,
@@ -88,6 +94,9 @@ grab_key_real (guint keycode,
mask,
GDK_WINDOW_XID (root));
}
+#else
+ g_warning ("Key grabbing is not supported without X11");
+#endif /* GDK_WINDOWING_X11 */
}
/* Grab the key. In order to ignore MSD_IGNORED_MODS we need to grab
@@ -203,6 +212,7 @@ key_uses_keycode (const Key *key, guint keycode)
return FALSE;
}
+#ifdef GDK_WINDOWING_X11
gboolean
match_key (Key *key, XEvent *event)
{
@@ -215,11 +225,9 @@ match_key (Key *key, XEvent *event)
setup_modifiers ();
-#ifdef HAVE_X11_EXTENSIONS_XKB_H
if (have_xkb (event->xkey.display))
group = XkbGroupForCoreState (event->xkey.state);
else
-#endif
group = (event->xkey.state & GDK_KEY_Mode_switch) ? 1 : 0;
/* Check if we find a keysym that matches our current state */
@@ -244,3 +252,10 @@ match_key (Key *key, XEvent *event)
return (key->state == (event->xkey.state & msd_used_mods)
&& key_uses_keycode (key, event->xkey.keycode));
}
+#else /* !GDK_WINDOWING_X11 */
+gboolean
+match_key (Key *key, XEvent *event)
+{
+ return FALSE;
+}
+#endif /* GDK_WINDOWING_X11 */
diff --git a/plugins/common/msd-osd-window.c b/plugins/common/msd-osd-window.c
index d241cfb..5ac99a4 100644
--- a/plugins/common/msd-osd-window.c
+++ b/plugins/common/msd-osd-window.c
@@ -36,7 +36,7 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
-#include <gdk/gdkx.h>
+#include <gdk/gdk.h>
#include "msd-osd-window.h"
@@ -455,8 +455,8 @@ msd_osd_window_init (MsdOsdWindow *window)
gtk_style_context_add_class (style, "window-frame");
/* assume 110x110 on a 640x480 display and scale from there */
- scalew = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / (640.0 * window->priv->scale_factor);
- scaleh = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / (480.0 * window->priv->scale_factor);
+ scalew = gdk_screen_get_width (screen) / (640.0 * window->priv->scale_factor);
+ scaleh = gdk_screen_get_height (screen) / (480.0 * window->priv->scale_factor);
scale = MIN (scalew, scaleh);
size = 110 * MAX (1, scale);
diff --git a/plugins/keybindings/keybindings.mate-settings-plugin.desktop.in b/plugins/keybindings/keybindings.mate-settings-plugin.desktop.in
index c668e3f..dbed8b9 100644
--- a/plugins/keybindings/keybindings.mate-settings-plugin.desktop.in
+++ b/plugins/keybindings/keybindings.mate-settings-plugin.desktop.in
@@ -1,6 +1,7 @@
[MATE Settings Plugin]
Module=keybindings
IAge=0
+X11Only=true
Name=Keybindings
Description=Keybindings plugin
Authors=AUTHOR
diff --git a/plugins/keybindings/msd-keybindings-manager.c b/plugins/keybindings/msd-keybindings-manager.c
index 5ce7f0d..324a976 100644
--- a/plugins/keybindings/msd-keybindings-manager.c
+++ b/plugins/keybindings/msd-keybindings-manager.c
@@ -34,7 +34,9 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <gtk/gtk.h>
#include <X11/keysym.h>
#include <gio/gio.h>
@@ -519,6 +521,7 @@ gboolean
msd_keybindings_manager_start (MsdKeybindingsManager *manager,
GError **error)
{
+#ifdef GDK_WINDOWING_X11
GdkDisplay *dpy;
GdkScreen *screen;
GdkWindow *window;
@@ -530,6 +533,15 @@ msd_keybindings_manager_start (MsdKeybindingsManager *manager,
mate_settings_profile_start (NULL);
dpy = gdk_display_get_default ();
+
+ if (!GDK_IS_X11_DISPLAY (dpy)) {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Keybindings are only supported on X11");
+ return FALSE;
+ }
+
xdpy = GDK_DISPLAY_XDISPLAY (dpy);
screen = gdk_display_get_default_screen (dpy);
@@ -559,6 +571,13 @@ msd_keybindings_manager_start (MsdKeybindingsManager *manager,
mate_settings_profile_end (NULL);
return TRUE;
+#else /* !GDK_WINDOWING_X11 */
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Keybindings are only supported on X11");
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
}
void
@@ -569,6 +588,11 @@ msd_keybindings_manager_stop (MsdKeybindingsManager *manager)
g_debug ("Stopping keybindings manager");
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
if (p->client != NULL) {
g_object_unref (p->client);
p->client = NULL;
diff --git a/plugins/keyboard/keyboard.mate-settings-plugin.desktop.in b/plugins/keyboard/keyboard.mate-settings-plugin.desktop.in
index 00bd1d9..562f21d 100644
--- a/plugins/keyboard/keyboard.mate-settings-plugin.desktop.in
+++ b/plugins/keyboard/keyboard.mate-settings-plugin.desktop.in
@@ -1,6 +1,7 @@
[MATE Settings Plugin]
Module=keyboard
IAge=0
+X11Only=true
Name=Keyboard
Description=Keyboard plugin
Authors=
diff --git a/plugins/keyboard/msd-keyboard-manager.c b/plugins/keyboard/msd-keyboard-manager.c
index 06bc322..7e7d3ae 100644
--- a/plugins/keyboard/msd-keyboard-manager.c
+++ b/plugins/keyboard/msd-keyboard-manager.c
@@ -34,8 +34,11 @@
#include <glib.h>
#include <glib/gi18n.h>
+#include <gio/gio.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#ifdef HAVE_X11_EXTENSIONS_XKB_H
#include <X11/XKBlib.h>
@@ -310,6 +313,20 @@ msd_keyboard_manager_start (MsdKeyboardManager *manager,
{
mate_settings_profile_start (NULL);
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "X11 display is required for the keyboard plugin");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+#else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The keyboard plugin was built without X11 support");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
+
g_idle_add ((GSourceFunc) start_keyboard_idle_cb, manager);
mate_settings_profile_end (NULL);
@@ -324,6 +341,11 @@ msd_keyboard_manager_stop (MsdKeyboardManager *manager)
g_debug ("Stopping keyboard manager");
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
if (p->settings != NULL) {
g_object_unref (p->settings);
p->settings = NULL;
diff --git a/plugins/media-keys/media-keys.mate-settings-plugin.desktop.in b/plugins/media-keys/media-keys.mate-settings-plugin.desktop.in
index 733bd10..05d2456 100644
--- a/plugins/media-keys/media-keys.mate-settings-plugin.desktop.in
+++ b/plugins/media-keys/media-keys.mate-settings-plugin.desktop.in
@@ -1,6 +1,7 @@
[MATE Settings Plugin]
Module=media-keys
IAge=0
+X11Only=true
Name=Media keys
Description=Media keys plugin
Authors=
diff --git a/plugins/media-keys/msd-media-keys-manager.c b/plugins/media-keys/msd-media-keys-manager.c
index 5ab8946..2d5428e 100644
--- a/plugins/media-keys/msd-media-keys-manager.c
+++ b/plugins/media-keys/msd-media-keys-manager.c
@@ -26,7 +26,9 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <gtk/gtk.h>
#include <gio/gio.h>
@@ -1605,6 +1607,20 @@ msd_media_keys_manager_start (MsdMediaKeysManager *manager, GError **error)
{
mate_settings_profile_start (NULL);
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "X11 display is required for the media_keys plugin");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+#else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The media_keys plugin was built without X11 support");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
+
#ifdef HAVE_LIBMATEMIXER
if (G_LIKELY (mate_mixer_is_initialized ())) {
mate_settings_profile_start ("mate_mixer_context_new");
@@ -1653,6 +1669,11 @@ msd_media_keys_manager_stop (MsdMediaKeysManager *manager)
g_debug ("Stopping media_keys manager");
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
for (ls = priv->screens; ls != NULL; ls = ls->next) {
gdk_window_remove_filter (gdk_screen_get_root_window (ls->data),
(GdkFilterFunc) acme_filter_events,
diff --git a/plugins/mouse/mouse.mate-settings-plugin.desktop.in b/plugins/mouse/mouse.mate-settings-plugin.desktop.in
index ae66d36..8b688f5 100644
--- a/plugins/mouse/mouse.mate-settings-plugin.desktop.in
+++ b/plugins/mouse/mouse.mate-settings-plugin.desktop.in
@@ -1,6 +1,7 @@
[MATE Settings Plugin]
Module=mouse
IAge=0
+X11Only=true
Name=Mouse
Description=Mouse plugin
Authors=
diff --git a/plugins/mouse/msd-mouse-manager.c b/plugins/mouse/msd-mouse-manager.c
index 92a5f0a..a79fd09 100644
--- a/plugins/mouse/msd-mouse-manager.c
+++ b/plugins/mouse/msd-mouse-manager.c
@@ -36,7 +36,9 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <gdk/gdkkeysyms.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
@@ -58,6 +60,7 @@
/* Mouse settings */
#define MATE_MOUSE_SCHEMA "org.mate.peripherals-mouse"
#define KEY_MOUSE_LOCATE_POINTER "locate-pointer"
+#define KEY_MOUSE_NATURAL_SCROLL "natural-scroll"
#define KEY_MIDDLE_BUTTON_EMULATION "middle-button-enabled"
/* Touchpad settings */
@@ -1389,8 +1392,8 @@ set_natural_scroll_libinput (XDeviceInfo *device_info,
}
static void
-set_natural_scroll (XDeviceInfo *device_info,
- gboolean natural_scroll)
+set_touchpad_natural_scroll (XDeviceInfo *device_info,
+ gboolean natural_scroll)
{
if (property_from_name ("Synaptics Scrolling Distance"))
set_natural_scroll_synaptics (device_info, natural_scroll);
@@ -1400,7 +1403,7 @@ set_natural_scroll (XDeviceInfo *device_info,
}
static void
-set_natural_scroll_all (MsdMouseManager *manager)
+set_touchpad_natural_scroll_all (MsdMouseManager *manager)
{
int numdevices, i;
XDeviceInfo *devicelist = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &numdevices);
@@ -1411,7 +1414,69 @@ set_natural_scroll_all (MsdMouseManager *manager)
gboolean natural_scroll = g_settings_get_boolean (manager->priv->settings_touchpad, KEY_TOUCHPAD_NATURAL_SCROLL);
for (i = 0; i < numdevices; i++) {
- set_natural_scroll (&devicelist[i], natural_scroll);
+ set_touchpad_natural_scroll (&devicelist[i], natural_scroll);
+ }
+
+ XFreeDeviceList (devicelist);
+}
+
+static void
+mouse_set_bool (XDeviceInfo *device_info,
+ const char *property_name,
+ int property_index,
+ gboolean enabled)
+{
+ XDevice *device;
+ GdkDisplay *display;
+
+ display = gdk_display_get_default ();
+
+ device = device_is_touchpad (device_info);
+ if (device != NULL) {
+ XCloseDevice (GDK_DISPLAY_XDISPLAY (display), device);
+ return;
+ }
+
+ if ((device_info->use == IsXPointer) ||
+ (device_info->use == IsXKeyboard) ||
+ (!xinput_device_has_buttons (device_info)))
+ return;
+
+ gdk_x11_display_error_trap_push (display);
+ device = XOpenDevice (GDK_DISPLAY_XDISPLAY (display), device_info->id);
+ if ((gdk_x11_display_error_trap_pop (display) != 0) || (device == NULL))
+ return;
+
+ property_set_bool (device_info, device, property_name, property_index, enabled);
+
+ gdk_x11_display_error_trap_push (display);
+ XCloseDevice (GDK_DISPLAY_XDISPLAY (display), device);
+ gdk_x11_display_error_trap_pop_ignored (display);
+}
+
+static void
+set_mouse_natural_scroll (XDeviceInfo *device_info,
+ gboolean natural_scroll)
+{
+ g_debug ("Trying to set %s for \"%s\"", natural_scroll ? "natural (reverse) scroll" : "normal scroll", device_info->name);
+
+ mouse_set_bool (device_info, "libinput Natural Scrolling Enabled", 0, natural_scroll);
+ mouse_set_bool (device_info, "Evdev Wheel Inversion", 0, natural_scroll);
+}
+
+static void
+set_mouse_natural_scroll_all (MsdMouseManager *manager)
+{
+ int numdevices, i;
+ XDeviceInfo *devicelist = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &numdevices);
+
+ if (devicelist == NULL)
+ return;
+
+ gboolean natural_scroll = g_settings_get_boolean (manager->priv->settings_mouse, KEY_MOUSE_NATURAL_SCROLL);
+
+ for (i = 0; i < numdevices; i++) {
+ set_mouse_natural_scroll (&devicelist[i], natural_scroll);
}
XFreeDeviceList (devicelist);
@@ -1677,7 +1742,8 @@ set_mouse_settings (MsdMouseManager *manager)
set_tap_to_click_all (manager);
set_click_actions_all (manager);
set_scrolling_all (manager->priv->settings_touchpad);
- set_natural_scroll_all (manager);
+ set_touchpad_natural_scroll_all (manager);
+ set_mouse_natural_scroll_all (manager);
set_touchpad_enabled_all (g_settings_get_boolean (manager->priv->settings_touchpad, KEY_TOUCHPAD_ENABLED));
set_accel_profile_all (manager);
}
@@ -1698,6 +1764,8 @@ mouse_callback (GSettings *settings,
set_accel_profile_all (manager);
} else if (g_strcmp0 (key, KEY_MIDDLE_BUTTON_EMULATION) == 0) {
set_middle_button_all (g_settings_get_boolean (settings, key));
+ } else if (g_strcmp0 (key, KEY_MOUSE_NATURAL_SCROLL) == 0) {
+ set_mouse_natural_scroll_all (manager);
} else if (g_strcmp0 (key, KEY_MOUSE_LOCATE_POINTER) == 0) {
set_locate_pointer (manager, g_settings_get_boolean (settings, key));
#if 0 /* FIXME need to fork (?) mousetweaks for this to work */
@@ -1739,7 +1807,7 @@ touchpad_callback (GSettings *settings,
|| (g_strcmp0 (key, KEY_HORIZ_TWO_FINGER_SCROLL) == 0)) {
set_scrolling_all (manager->priv->settings_touchpad);
} else if (g_strcmp0 (key, KEY_TOUCHPAD_NATURAL_SCROLL) == 0) {
- set_natural_scroll_all (manager);
+ set_touchpad_natural_scroll_all (manager);
} else if (g_strcmp0 (key, KEY_TOUCHPAD_ENABLED) == 0) {
set_touchpad_enabled_all (g_settings_get_boolean (settings, key));
} else if ((g_strcmp0 (key, KEY_MOTION_ACCELERATION) == 0)
@@ -1795,6 +1863,20 @@ msd_mouse_manager_start (MsdMouseManager *manager,
{
mate_settings_profile_start (NULL);
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "X11 display is required for the mouse plugin");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+#else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The mouse plugin was built without X11 support");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
+
if (!supports_xinput_devices ()) {
g_debug ("XInput is not supported, not applying any settings");
return TRUE;
@@ -1814,6 +1896,11 @@ msd_mouse_manager_stop (MsdMouseManager *manager)
g_debug ("Stopping mouse manager");
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
if (p->settings_mouse != NULL) {
g_object_unref(p->settings_mouse);
p->settings_mouse = NULL;
diff --git a/plugins/wlrandr/Makefile.am b/plugins/wlrandr/Makefile.am
new file mode 100644
index 0000000..cfcabd4
--- /dev/null
+++ b/plugins/wlrandr/Makefile.am
@@ -0,0 +1,108 @@
+icondir = $(datadir)/icons/hicolor
+context = apps
+
+
+ICON_FILES = \
+ msd-wlrandr-16.png \
+ msd-wlrandr-22.png \
+ msd-wlrandr-24.png \
+ msd-wlrandr-32.png \
+ msd-wlrandr-48.png \
+ msd-wlrandr-64.png \
+ msd-wlrandr-96.png \
+ msd-wlrandr.svg
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/16x16/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/22x22/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/24x24/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/32x32/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/48x48/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/64x64/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/96x96/$(context)
+ $(mkinstalldirs) $(DESTDIR)$(icondir)/scalable/$(context)
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-16.png $(DESTDIR)$(icondir)/16x16/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-22.png $(DESTDIR)$(icondir)/22x22/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-24.png $(DESTDIR)$(icondir)/24x24/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-32.png $(DESTDIR)$(icondir)/32x32/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-48.png $(DESTDIR)$(icondir)/48x48/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-64.png $(DESTDIR)$(icondir)/64x64/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr-96.png $(DESTDIR)$(icondir)/96x96/$(context)/msd-wlrandr.png
+ $(INSTALL_DATA) $(srcdir)/msd-wlrandr.svg $(DESTDIR)$(icondir)/scalable/$(context)/msd-wlrandr.svg
+
+uninstall-local:
+ rm -f $(DESTDIR)$(icondir)/16x16/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/22x22/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/24x24/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/32x32/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/48x48/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/64x64/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/96x96/$(context)/msd-wlrandr.png
+ rm -f $(DESTDIR)$(icondir)/scalable/$(context)/msd-wlrandr.svg
+
+plugin_LTLIBRARIES = \
+ libwlrandr.la
+
+protocol_src_dir = protocol
+protocol_src = $(protocol_src_dir)/wlr-output-management-unstable-v1.xml
+
+generated_protocol_sources = \
+ wlr-output-management-unstable-v1-protocol.c \
+ wlr-output-management-unstable-v1-client-protocol.h
+
+libwlrandr_la_SOURCES = \
+ msd-wlrandr-plugin.h \
+ msd-wlrandr-plugin.c \
+ msd-wlrandr-manager.h \
+ msd-wlrandr-manager.c \
+ $(generated_protocol_sources)
+
+libwlrandr_la_CPPFLAGS = \
+ -I$(top_srcdir)/mate-settings-daemon \
+ -I$(builddir) \
+ -DBINDIR=\"$(bindir)\" \
+ -DMATE_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \
+ $(AM_CPPFLAGS)
+
+libwlrandr_la_CFLAGS = \
+ $(SETTINGS_PLUGIN_CFLAGS) \
+ $(WAYLAND_CLIENT_CFLAGS) \
+ $(APPINDICATOR_CFLAGS) \
+ $(LIBNOTIFY_CFLAGS) \
+ $(MATE_DESKTOP_CFLAGS) \
+ $(AM_CFLAGS) \
+ $(WARN_CFLAGS)
+
+libwlrandr_la_LDFLAGS = \
+ $(MSD_PLUGIN_LDFLAGS)
+
+libwlrandr_la_LIBADD = \
+ $(SETTINGS_PLUGIN_LIBS) \
+ $(WAYLAND_CLIENT_LIBS) \
+ $(APPINDICATOR_LIBS) \
+ $(LIBNOTIFY_LIBS) \
+ $(MATE_DESKTOP_LIBS) \
+ $(NULL)
+
+BUILT_SOURCES = $(generated_protocol_sources)
+
+CLEANFILES = $(generated_protocol_sources)
+
+wlr-output-management-unstable-v1-client-protocol.h: $(protocol_src)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) client-header $< $@
+
+wlr-output-management-unstable-v1-protocol.c: $(protocol_src)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) private-code $< $@
+
+plugin_in_files = \
+ wlrandr.mate-settings-plugin.desktop.in
+
+plugin_DATA = $(plugin_in_files:.mate-settings-plugin.desktop.in=.mate-settings-plugin)
+
+EXTRA_DIST = $(plugin_in_files) $(ICON_FILES) $(protocol_src)
+DISTCLEANFILES = $(plugin_DATA)
+
+$(plugin_DATA): $(plugin_in_files)
+ $(AM_V_GEN) $(MSGFMT) --desktop --template $< -d $(top_srcdir)/po -o $@
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/wlrandr/msd-wlrandr-16.png b/plugins/wlrandr/msd-wlrandr-16.png
new file mode 100644
index 0000000..adb9e36
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-16.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-22.png b/plugins/wlrandr/msd-wlrandr-22.png
new file mode 100644
index 0000000..a90f71b
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-22.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-24.png b/plugins/wlrandr/msd-wlrandr-24.png
new file mode 100644
index 0000000..6d66034
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-24.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-32.png b/plugins/wlrandr/msd-wlrandr-32.png
new file mode 100644
index 0000000..d0fd058
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-32.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-48.png b/plugins/wlrandr/msd-wlrandr-48.png
new file mode 100644
index 0000000..ff2e1c7
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-48.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-64.png b/plugins/wlrandr/msd-wlrandr-64.png
new file mode 100644
index 0000000..ebde0b6
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-64.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-96.png b/plugins/wlrandr/msd-wlrandr-96.png
new file mode 100644
index 0000000..fab9dcd
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-96.png
Binary files differ
diff --git a/plugins/wlrandr/msd-wlrandr-manager.c b/plugins/wlrandr/msd-wlrandr-manager.c
new file mode 100644
index 0000000..7240ae8
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-manager.c
@@ -0,0 +1,2968 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkwayland.h>
+#include <libnotify/notify.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <libmate-desktop/mate-desktop-utils.h>
+
+#ifdef HAVE_APP_INDICATOR
+#include <libayatana-appindicator/app-indicator.h>
+#endif
+
+#include <wayland-client.h>
+#include "wlr-output-management-unstable-v1-client-protocol.h"
+
+#include "msd-wlrandr-manager.h"
+
+#define CONF_SCHEMA "org.mate.SettingsDaemon.plugins.wlrandr"
+#define CONF_KEY_SHOW_NOTIFICATION_ICON "show-notification-icon"
+#define CONF_KEY_TURN_ON_EXTERNAL_MONITORS_AT_STARTUP "turn-on-external-monitors-at-startup"
+#define CONF_KEY_DEFAULT_CONFIGURATION_FILE "default-configuration-file"
+
+/* Number of seconds that the confirmation dialog will last before it resets the
+ * RANDR configuration to its old state.
+ */
+#define CONFIRMATION_DIALOG_SECONDS 30
+
+/* name of the icon files (msd-wlrandr.svg, etc.) */
+#define MSD_WLRANDR_ICON_NAME "msd-wlrandr"
+
+/* executable of the control center's display configuration capplet */
+#define MSD_WLRANDR_DISPLAY_CAPPLET "mate-display-properties"
+
+#define CONFIG_INTENDED_BASENAME "monitors-wayland.xml"
+#define CONFIG_BACKUP_BASENAME "monitors-wayland.xml.backup"
+
+#define WLR_OUTPUT_MANAGER_VERSION 4
+
+#define MSD_DBUS_PATH "/org/mate/SettingsDaemon"
+#define MSD_DBUS_NAME "org.mate.SettingsDaemon"
+
+#define MSD_WLRANDR_DBUS_NAME MSD_DBUS_NAME ".WLRANDR"
+#define MSD_WLRANDR_DBUS_NAME_2 MSD_WLRANDR_DBUS_NAME "_2"
+#define MSD_WLRANDR_DBUS_PATH MSD_DBUS_PATH "/WLRANDR"
+
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='org.mate.SettingsDaemon.WLRANDR'>"
+" <method name='GetConfiguration'>"
+" <arg name='configuration' type='a(ssbiiidbb(iiii)a(iiii))' direction='out'/>"
+" </method>"
+" <method name='SetConfiguration'>"
+" <arg name='configuration' type='a(sbiiiiidib)' direction='in'/>"
+" </method>"
+" <method name='ApplyConfiguration'>"
+" </method>"
+" <signal name='ConfigurationChanged'>"
+" </signal>"
+" </interface>"
+""
+" <interface name='org.mate.SettingsDaemon.WLRANDR_2'>"
+" <method name='GetConfiguration'>"
+" <arg name='configuration' type='a(ssbiiidbb(iiii)a(iiii))' direction='out'/>"
+" </method>"
+" <method name='SetConfiguration'>"
+" <arg name='configuration' type='a(sbiiiiidib)' direction='in'/>"
+" </method>"
+" <method name='ApplyConfiguration'>"
+" <arg name='parent_window_id' type='x' direction='in'/>"
+" <arg name='timestamp' type='x' direction='in'/>"
+" </method>"
+" <signal name='ConfigurationChanged'>"
+" </signal>"
+" </interface>"
+"</node>";
+
+typedef struct {
+ char *name;
+ char *vendor;
+ char *product;
+ char *serial;
+ gboolean enabled;
+ int width;
+ int height;
+ int rate;
+ int x;
+ int y;
+ double scale;
+ int transform;
+ gboolean adaptive_sync;
+} WlrOutputInfo;
+
+typedef struct {
+ WlrOutputInfo **outputs;
+} WlrConfig;
+
+typedef struct _WlOutputHead WlOutputHead;
+typedef struct _WlOutputMode WlOutputMode;
+
+typedef struct _WlOutputMode {
+ int width;
+ int height;
+ int refresh;
+ gboolean preferred;
+ struct zwlr_output_mode_v1 *proxy;
+ WlOutputHead *head;
+} WlOutputMode;
+
+typedef struct _WlOutputHead {
+ char *name;
+ char *description;
+ char *make;
+ char *model;
+ char *serial_number;
+ gboolean enabled;
+ int x;
+ int y;
+ double scale;
+ int transform;
+ gboolean adaptive_sync;
+ int adaptive_sync_supported; /* -1 unknown, 0 no, 1 yes */
+ GList *modes;
+ WlOutputMode *current_mode;
+ struct zwlr_output_head_v1 *proxy;
+ MsdWlrandrManager *manager;
+} WlOutputHead;
+
+struct MsdWlrandrManagerPrivate {
+ GSettings *settings;
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct zwlr_output_manager_v1 *manager;
+ uint32_t manager_version;
+ uint32_t last_serial;
+ GList *heads;
+ gboolean started;
+ gboolean initial_config_applied;
+ struct zwlr_output_configuration_v1 *pending_config;
+ gboolean probing;
+#ifdef HAVE_APP_INDICATOR
+ AppIndicator *indicator;
+#endif
+ GtkWidget *menu;
+ GtkWidget *confirm_dialog;
+ guint icon_changed_id;
+
+ GDBusConnection *connection;
+ GDBusNodeInfo *introspection_data;
+ GCancellable *bus_cancellable;
+ guint owner_id;
+ guint registration_id;
+ guint registration_id_2;
+};
+
+static void
+rebuild_menu (MsdWlrandrManager *manager);
+
+static void
+emit_configuration_changed (MsdWlrandrManager *manager);
+
+static void
+save_current_configuration (MsdWlrandrManager *manager, WlrConfig *config);
+
+static void
+queue_rollback_confirmation (MsdWlrandrManager *manager, WlrConfig *previous);
+
+static void
+probe_adaptive_sync_support (MsdWlrandrManager *manager);
+
+static gboolean
+probe_idle_cb (gpointer data)
+{
+ probe_adaptive_sync_support (data);
+ return G_SOURCE_REMOVE;
+}
+
+static WlrConfig *
+snapshot_config (MsdWlrandrManager *manager);
+
+static WlrOutputInfo *
+find_output_in_config (WlrConfig *config, const char *name);
+
+static struct zwlr_output_mode_v1 *
+find_mode_for_output (WlOutputHead *head, WlrOutputInfo *info);
+
+static WlrOutputInfo *
+wlr_output_info_new (const char *name)
+{
+ WlrOutputInfo *info = g_new0 (WlrOutputInfo, 1);
+ info->name = g_strdup (name);
+ info->scale = 1.0;
+ return info;
+}
+
+static void
+wlr_output_info_free (WlrOutputInfo *info)
+{
+ if (info == NULL)
+ return;
+ g_free (info->name);
+ g_free (info->vendor);
+ g_free (info->product);
+ g_free (info->serial);
+ g_free (info);
+}
+
+static void
+wlr_config_free (WlrConfig *config)
+{
+ int i;
+
+ if (config == NULL)
+ return;
+
+ for (i = 0; config->outputs != NULL && config->outputs[i] != NULL; i++)
+ wlr_output_info_free (config->outputs[i]);
+ g_free (config->outputs);
+ g_free (config);
+}
+
+static WlOutputMode *
+wlr_output_mode_new (struct zwlr_output_mode_v1 *proxy)
+{
+ WlOutputMode *mode = g_new0 (WlOutputMode, 1);
+ mode->proxy = proxy;
+ return mode;
+}
+
+static void
+wlr_output_mode_free (WlOutputMode *mode)
+{
+ g_free (mode);
+}
+
+static WlOutputHead *
+wlr_output_head_new (struct zwlr_output_head_v1 *proxy)
+{
+ WlOutputHead *head = g_new0 (WlOutputHead, 1);
+ head->proxy = proxy;
+ head->scale = 1.0;
+ head->adaptive_sync_supported = -1;
+ return head;
+}
+
+static void
+wlr_output_head_free (WlOutputHead *head)
+{
+ GList *l;
+
+ if (head == NULL)
+ return;
+
+ for (l = head->modes; l != NULL; l = l->next)
+ wlr_output_mode_free (l->data);
+ g_list_free (head->modes);
+
+ g_free (head->name);
+ g_free (head->description);
+ g_free (head->make);
+ g_free (head->model);
+ g_free (head->serial_number);
+ g_free (head);
+}
+
+static WlOutputMode *
+find_mode_by_proxy (WlOutputHead *head, struct zwlr_output_mode_v1 *proxy)
+{
+ GList *l;
+
+ for (l = head->modes; l != NULL; l = l->next) {
+ WlOutputMode *mode = l->data;
+ if (mode->proxy == proxy)
+ return mode;
+ }
+
+ return NULL;
+}
+
+/* ------------------------------------------------------------------ *
+ * wlr-output-management protocol listeners *
+ * ------------------------------------------------------------------ */
+
+static void
+head_name_handler (void *data, struct zwlr_output_head_v1 *proxy, const char *name)
+{
+ WlOutputHead *head = data;
+ g_free (head->name);
+ head->name = g_strdup (name);
+}
+
+static void
+head_description_handler (void *data, struct zwlr_output_head_v1 *proxy, const char *description)
+{
+ WlOutputHead *head = data;
+ g_free (head->description);
+ head->description = g_strdup (description);
+}
+
+static void
+head_make_handler (void *data, struct zwlr_output_head_v1 *proxy, const char *make)
+{
+ WlOutputHead *head = data;
+ g_free (head->make);
+ head->make = g_strdup (make);
+}
+
+static void
+head_model_handler (void *data, struct zwlr_output_head_v1 *proxy, const char *model)
+{
+ WlOutputHead *head = data;
+ g_free (head->model);
+ head->model = g_strdup (model);
+}
+
+static void
+head_serial_number_handler (void *data, struct zwlr_output_head_v1 *proxy, const char *serial_number)
+{
+ WlOutputHead *head = data;
+ g_free (head->serial_number);
+ head->serial_number = g_strdup (serial_number);
+}
+
+static void
+head_physical_size_handler (void *data, struct zwlr_output_head_v1 *proxy, int32_t width, int32_t height)
+{
+}
+
+static void
+mode_size_handler (void *data, struct zwlr_output_mode_v1 *mode, int32_t width, int32_t height)
+{
+ WlOutputMode *wm = data;
+ wm->width = width;
+ wm->height = height;
+}
+
+static void
+mode_refresh_handler (void *data, struct zwlr_output_mode_v1 *mode, int32_t refresh)
+{
+ WlOutputMode *wm = data;
+ wm->refresh = refresh;
+}
+
+static void
+mode_preferred_handler (void *data, struct zwlr_output_mode_v1 *mode)
+{
+ WlOutputMode *wm = data;
+ wm->preferred = TRUE;
+}
+
+static void
+mode_finished_handler (void *data, struct zwlr_output_mode_v1 *mode)
+{
+ WlOutputMode *wm = data;
+ WlOutputHead *head = wm->head;
+ GList *l;
+
+ for (l = head->modes; l != NULL; l = l->next) {
+ if (l->data == wm) {
+ head->modes = g_list_delete_link (head->modes, l);
+ break;
+ }
+ }
+
+ if (head->current_mode == wm)
+ head->current_mode = NULL;
+
+ wlr_output_mode_free (wm);
+}
+
+static const struct zwlr_output_mode_v1_listener mode_listener = {
+ .size = mode_size_handler,
+ .refresh = mode_refresh_handler,
+ .preferred = mode_preferred_handler,
+ .finished = mode_finished_handler,
+};
+
+static void
+head_mode_handler (void *data, struct zwlr_output_head_v1 *proxy, struct zwlr_output_mode_v1 *mode)
+{
+ WlOutputHead *head = data;
+ WlOutputMode *wm;
+
+ wm = wlr_output_mode_new (mode);
+ wm->head = head;
+ head->modes = g_list_append (head->modes, wm);
+ zwlr_output_mode_v1_add_listener (mode, &mode_listener, wm);
+}
+
+static void
+head_enabled_handler (void *data, struct zwlr_output_head_v1 *proxy, int32_t enabled)
+{
+ WlOutputHead *head = data;
+ head->enabled = enabled != 0;
+}
+
+static void
+head_current_mode_handler (void *data, struct zwlr_output_head_v1 *proxy, struct zwlr_output_mode_v1 *mode)
+{
+ WlOutputHead *head = data;
+ head->current_mode = find_mode_by_proxy (head, mode);
+}
+
+static void
+head_position_handler (void *data, struct zwlr_output_head_v1 *proxy, int32_t x, int32_t y)
+{
+ WlOutputHead *head = data;
+ head->x = x;
+ head->y = y;
+}
+
+static void
+head_transform_handler (void *data, struct zwlr_output_head_v1 *proxy, int32_t transform)
+{
+ WlOutputHead *head = data;
+ head->transform = transform;
+}
+
+static void
+head_scale_handler (void *data, struct zwlr_output_head_v1 *proxy, wl_fixed_t scale)
+{
+ WlOutputHead *head = data;
+ head->scale = wl_fixed_to_double (scale);
+}
+
+static void
+head_adaptive_sync_handler (void *data, struct zwlr_output_head_v1 *proxy, uint32_t state)
+{
+ WlOutputHead *head = data;
+ head->adaptive_sync = state != 0;
+}
+
+static void
+head_finished_handler (void *data, struct zwlr_output_head_v1 *proxy)
+{
+ MsdWlrandrManager *manager = ((WlOutputHead *) data)->manager;
+ WlOutputHead *head = data;
+
+ manager->priv->heads = g_list_remove (manager->priv->heads, head);
+ wlr_output_head_free (head);
+ rebuild_menu (manager);
+}
+
+static const struct zwlr_output_head_v1_listener head_listener = {
+ .name = head_name_handler,
+ .description = head_description_handler,
+ .physical_size = head_physical_size_handler,
+ .mode = head_mode_handler,
+ .enabled = head_enabled_handler,
+ .current_mode = head_current_mode_handler,
+ .position = head_position_handler,
+ .transform = head_transform_handler,
+ .scale = head_scale_handler,
+ .finished = head_finished_handler,
+ .make = head_make_handler,
+ .model = head_model_handler,
+ .serial_number = head_serial_number_handler,
+ .adaptive_sync = head_adaptive_sync_handler,
+};
+
+static void
+manager_head_handler (void *data, struct zwlr_output_manager_v1 *manager_obj, struct zwlr_output_head_v1 *proxy)
+{
+ MsdWlrandrManager *manager = data;
+ WlOutputHead *head;
+
+ head = wlr_output_head_new (proxy);
+ head->manager = manager;
+ manager->priv->heads = g_list_append (manager->priv->heads, head);
+
+ zwlr_output_head_v1_add_listener (proxy, &head_listener, head);
+}
+
+static void
+apply_initial_config (MsdWlrandrManager *manager);
+
+static void
+manager_done_handler (void *data, struct zwlr_output_manager_v1 *manager_obj, uint32_t serial)
+{
+ MsdWlrandrManager *manager = data;
+
+ manager->priv->last_serial = serial;
+ g_debug ("msd-wlrandr: output manager done, serial %u", serial);
+
+ rebuild_menu (manager);
+ emit_configuration_changed (manager);
+
+ if (!manager->priv->initial_config_applied) {
+ manager->priv->initial_config_applied = TRUE;
+ apply_initial_config (manager);
+ }
+
+ probe_adaptive_sync_support (manager);
+}
+
+static void
+manager_finished_handler (void *data, struct zwlr_output_manager_v1 *manager_obj)
+{
+ MsdWlrandrManager *manager = data;
+
+ g_debug ("msd-wlrandr: output manager finished");
+
+ if (manager->priv->manager != NULL) {
+ zwlr_output_manager_v1_destroy (manager->priv->manager);
+ manager->priv->manager = NULL;
+ }
+}
+
+static const struct zwlr_output_manager_v1_listener manager_listener = {
+ .head = manager_head_handler,
+ .done = manager_done_handler,
+ .finished = manager_finished_handler,
+};
+
+static void
+registry_handle_global (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
+{
+ MsdWlrandrManager *manager = data;
+
+ if (strcmp (interface, zwlr_output_manager_v1_interface.name) == 0) {
+ manager->priv->manager_version = MIN (version, WLR_OUTPUT_MANAGER_VERSION);
+ manager->priv->manager = wl_registry_bind (registry, name,
+ &zwlr_output_manager_v1_interface,
+ manager->priv->manager_version);
+ zwlr_output_manager_v1_add_listener (manager->priv->manager, &manager_listener, manager);
+ g_debug ("msd-wlrandr: bound zwlr_output_manager_v1 version %u", manager->priv->manager_version);
+ }
+}
+
+static void
+registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_global,
+ .global_remove = registry_handle_global_remove,
+};
+
+/* ------------------------------------------------------------------ *
+ * Configuration application *
+ * ------------------------------------------------------------------ */
+
+typedef struct {
+ MsdWlrandrManager *manager;
+ gboolean save;
+ gboolean confirm_rollback;
+ WlrConfig *previous;
+ WlrConfig *config;
+ WlOutputHead *adaptive_sync_head;
+} ApplyData;
+
+static void
+notify_config_error (const char *message)
+{
+ NotifyNotification *n;
+
+ g_warning ("msd-wlrandr: %s", message);
+
+ if (notify_is_initted ()) {
+ n = notify_notification_new (_("Display Settings"), message, MSD_WLRANDR_ICON_NAME);
+ notify_notification_set_timeout (n, 3000);
+ notify_notification_show (n, NULL);
+ g_object_unref (n);
+ }
+}
+
+static void
+config_succeeded_handler (void *data, struct zwlr_output_configuration_v1 *config)
+{
+ ApplyData *ad = data;
+
+ g_debug ("msd-wlrandr: output configuration applied");
+
+ if (ad->save)
+ save_current_configuration (ad->manager, ad->config);
+
+ if (ad->confirm_rollback)
+ queue_rollback_confirmation (ad->manager, ad->previous);
+ else
+ wlr_config_free (ad->previous);
+
+ ad->manager->priv->pending_config = NULL;
+ g_idle_add (probe_idle_cb, ad->manager);
+ zwlr_output_configuration_v1_destroy (config);
+ wlr_config_free (ad->config);
+ g_free (ad);
+}
+
+static void
+config_failed_handler (void *data, struct zwlr_output_configuration_v1 *config)
+{
+ ApplyData *ad = data;
+
+ if (ad->adaptive_sync_head != NULL &&
+ g_list_find (ad->manager->priv->heads, ad->adaptive_sync_head) != NULL) {
+ WlOutputHead *head = ad->adaptive_sync_head;
+
+ g_debug ("msd-wlrandr: adaptive sync rejected by the compositor on %s", head->name);
+ head->adaptive_sync_supported = 0;
+ head->adaptive_sync = 0;
+
+ /* The configuration was rejected, so nothing was applied and
+ * the live head state is unchanged; persist it as is.
+ */
+ save_current_configuration (ad->manager, NULL);
+ rebuild_menu (ad->manager);
+ } else {
+ notify_config_error (_("Could not apply the display configuration"));
+ }
+
+ wlr_config_free (ad->previous);
+ ad->manager->priv->pending_config = NULL;
+ g_idle_add (probe_idle_cb, ad->manager);
+ zwlr_output_configuration_v1_destroy (config);
+ wlr_config_free (ad->config);
+ g_free (ad);
+}
+
+static void
+config_cancelled_handler (void *data, struct zwlr_output_configuration_v1 *config)
+{
+ ApplyData *ad = data;
+
+ g_debug ("msd-wlrandr: output configuration cancelled");
+
+ wlr_config_free (ad->previous);
+ ad->manager->priv->pending_config = NULL;
+ g_idle_add (probe_idle_cb, ad->manager);
+ zwlr_output_configuration_v1_destroy (config);
+ wlr_config_free (ad->config);
+ g_free (ad);
+}
+
+static const struct zwlr_output_configuration_v1_listener config_listener = {
+ .succeeded = config_succeeded_handler,
+ .failed = config_failed_handler,
+ .cancelled = config_cancelled_handler,
+};
+
+typedef struct {
+ MsdWlrandrManager *manager;
+ WlOutputHead *head;
+ guint stage;
+ gboolean original;
+} ProbeData;
+
+static const struct zwlr_output_configuration_v1_listener probe_listener;
+
+static void
+probe_confirm (ProbeData *pd, gboolean adaptive_sync)
+{
+ struct MsdWlrandrManagerPrivate *priv = pd->manager->priv;
+ struct zwlr_output_configuration_v1 *config_obj;
+ struct zwlr_output_configuration_head_v1 *head_obj;
+ WlrConfig *config;
+ GList *l;
+
+ if (priv->manager == NULL || g_list_find (priv->heads, pd->head) == NULL) {
+ g_free (pd);
+ return;
+ }
+
+ config = snapshot_config (pd->manager);
+ config_obj = zwlr_output_manager_v1_create_configuration (priv->manager, priv->last_serial);
+
+ for (l = priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ WlrOutputInfo *info = find_output_in_config (config, head->name);
+
+ if (info != NULL && info->enabled) {
+ struct zwlr_output_mode_v1 *mode = find_mode_for_output (head, info);
+
+ head_obj = zwlr_output_configuration_v1_enable_head (config_obj, head->proxy);
+
+ if (mode != NULL)
+ zwlr_output_configuration_head_v1_set_mode (head_obj, mode);
+ else
+ zwlr_output_configuration_head_v1_set_custom_mode (head_obj,
+ info->width,
+ info->height,
+ info->rate);
+
+ zwlr_output_configuration_head_v1_set_position (head_obj, info->x, info->y);
+ zwlr_output_configuration_head_v1_set_transform (head_obj, info->transform);
+ zwlr_output_configuration_head_v1_set_scale (head_obj, wl_fixed_from_double (info->scale));
+
+ if (priv->manager_version >= 4 && head == pd->head)
+ zwlr_output_configuration_head_v1_set_adaptive_sync (head_obj,
+ adaptive_sync ? 1 : 0);
+ } else {
+ zwlr_output_configuration_v1_disable_head (config_obj, head->proxy);
+ }
+ }
+
+ wlr_config_free (config);
+
+ zwlr_output_configuration_v1_add_listener (config_obj, &probe_listener, pd);
+ zwlr_output_configuration_v1_apply (config_obj);
+ wl_display_flush (priv->display);
+}
+
+static void
+probe_succeeded_handler (void *data, struct zwlr_output_configuration_v1 *config)
+{
+ ProbeData *pd = data;
+ struct MsdWlrandrManagerPrivate *priv = pd->manager->priv;
+
+ if (pd->stage == 0) {
+ if (g_list_find (priv->heads, pd->head) != NULL) {
+ g_debug ("msd-wlrandr: adaptive sync toggle accepted on %s", pd->head->name);
+ pd->stage = 1;
+ probe_confirm (pd, pd->original);
+ zwlr_output_configuration_v1_destroy (config);
+ return;
+ }
+ } else if (g_list_find (priv->heads, pd->head) != NULL) {
+ g_debug ("msd-wlrandr: adaptive sync confirmed on %s", pd->head->name);
+ pd->head->adaptive_sync_supported = 1;
+ pd->head->adaptive_sync = pd->original;
+ rebuild_menu (pd->manager);
+ }
+
+ priv->probing = FALSE;
+ probe_adaptive_sync_support (pd->manager);
+ zwlr_output_configuration_v1_destroy (config);
+ g_free (pd);
+}
+
+static void
+probe_failed_handler (void *data, struct zwlr_output_configuration_v1 *config)
+{
+ ProbeData *pd = data;
+ struct MsdWlrandrManagerPrivate *priv = pd->manager->priv;
+
+ if (pd->stage == 0 && g_list_find (priv->heads, pd->head) != NULL) {
+ g_debug ("msd-wlrandr: adaptive sync not supported on %s", pd->head->name);
+ pd->head->adaptive_sync_supported = 0;
+ rebuild_menu (pd->manager);
+ } else if (g_list_find (priv->heads, pd->head) != NULL) {
+ g_debug ("msd-wlrandr: adaptive sync restore rejected on %s", pd->head->name);
+ pd->head->adaptive_sync_supported = 0;
+ pd->head->adaptive_sync = pd->original;
+ rebuild_menu (pd->manager);
+ }
+
+ priv->probing = FALSE;
+ probe_adaptive_sync_support (pd->manager);
+ zwlr_output_configuration_v1_destroy (config);
+ g_free (pd);
+}
+
+static void
+probe_cancelled_handler (void *data, struct zwlr_output_configuration_v1 *config)
+{
+ ProbeData *pd = data;
+
+ g_debug ("msd-wlrandr: adaptive sync probe cancelled on %s", pd->head->name);
+
+ pd->manager->priv->probing = FALSE;
+ probe_adaptive_sync_support (pd->manager);
+ zwlr_output_configuration_v1_destroy (config);
+ g_free (pd);
+}
+
+static const struct zwlr_output_configuration_v1_listener probe_listener = {
+ .succeeded = probe_succeeded_handler,
+ .failed = probe_failed_handler,
+ .cancelled = probe_cancelled_handler,
+};
+
+static gboolean
+head_is_wayland_backend (WlOutputHead *head)
+{
+ return g_strcmp0 (head->make, "wayland") == 0 &&
+ g_strcmp0 (head->model, "wayland") == 0;
+}
+
+static void
+probe_adaptive_sync_support (MsdWlrandrManager *manager)
+{
+ struct MsdWlrandrManagerPrivate *priv = manager->priv;
+ ProbeData *pd;
+ GList *l;
+
+ if (priv->manager == NULL || priv->manager_version < 4)
+ return;
+
+ if (priv->pending_config != NULL || priv->probing)
+ return;
+
+ for (l = priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+
+ if (!head->enabled || head->adaptive_sync_supported != -1)
+ continue;
+
+ /* The wlroots Wayland backend cannot enable VRR on its
+ * nested output, and the compositor's reply is not reliable
+ * for it.
+ */
+ if (head_is_wayland_backend (head)) {
+ g_debug ("msd-wlrandr: adaptive sync not supported on nested output %s", head->name);
+ head->adaptive_sync_supported = 0;
+ continue;
+ }
+
+ if (head->current_mode == NULL)
+ continue;
+
+ pd = g_new0 (ProbeData, 1);
+ pd->manager = manager;
+ pd->head = head;
+ pd->stage = 0;
+ pd->original = head->adaptive_sync;
+ priv->probing = TRUE;
+
+ probe_confirm (pd, !head->adaptive_sync);
+
+ return;
+ }
+}
+
+static WlrOutputInfo *
+find_output_in_config (WlrConfig *config, const char *name)
+{
+ int i;
+
+ if (config == NULL || config->outputs == NULL)
+ return NULL;
+
+ for (i = 0; config->outputs[i] != NULL; i++) {
+ if (g_strcmp0 (config->outputs[i]->name, name) == 0)
+ return config->outputs[i];
+ }
+
+ return NULL;
+}
+
+static struct zwlr_output_mode_v1 *
+find_mode_for_output (WlOutputHead *head, WlrOutputInfo *info)
+{
+ GList *l;
+ struct zwlr_output_mode_v1 *fallback = NULL;
+
+ for (l = head->modes; l != NULL; l = l->next) {
+ WlOutputMode *wm = l->data;
+
+ if (wm->width == info->width && wm->height == info->height) {
+ if (fallback == NULL)
+ fallback = wm->proxy;
+ if (info->rate <= 0 || wm->refresh == info->rate)
+ return wm->proxy;
+ }
+ }
+
+ return fallback;
+}
+
+static void
+fill_output_mode (WlOutputHead *head, WlrOutputInfo *info)
+{
+ GList *l;
+ WlOutputMode *wm;
+
+ if (info->width > 0 && info->height > 0)
+ return;
+
+ if (head->current_mode != NULL) {
+ info->width = head->current_mode->width;
+ info->height = head->current_mode->height;
+ info->rate = head->current_mode->refresh;
+ return;
+ }
+
+ for (l = head->modes; l != NULL; l = l->next) {
+ wm = l->data;
+ if (wm->preferred || info->width <= 0) {
+ info->width = wm->width;
+ info->height = wm->height;
+ info->rate = wm->refresh;
+ if (wm->preferred)
+ return;
+ }
+ }
+}
+
+/* Deep copy of a configuration. Used to keep a reference to the
+ * configuration that was actually sent to the compositor, so that the file
+ * is saved with the intended values rather than whatever the compositor
+ * last reported (which may not have been updated yet when the
+ * "succeeded" event arrives).
+ */
+static WlrConfig *
+wlr_config_copy (const WlrConfig *config)
+{
+ WlrConfig *copy;
+ GPtrArray *outputs;
+ int i;
+
+ if (config == NULL)
+ return NULL;
+
+ outputs = g_ptr_array_new ();
+ for (i = 0; config->outputs != NULL && config->outputs[i] != NULL; i++) {
+ WlrOutputInfo *src = config->outputs[i];
+ WlrOutputInfo *dst = wlr_output_info_new (src->name);
+
+ dst->vendor = g_strdup (src->vendor);
+ dst->product = g_strdup (src->product);
+ dst->serial = g_strdup (src->serial);
+ dst->enabled = src->enabled;
+ dst->width = src->width;
+ dst->height = src->height;
+ dst->rate = src->rate;
+ dst->x = src->x;
+ dst->y = src->y;
+ dst->scale = src->scale;
+ dst->transform = src->transform;
+ dst->adaptive_sync = src->adaptive_sync;
+
+ g_ptr_array_add (outputs, dst);
+ }
+ g_ptr_array_add (outputs, NULL);
+
+ copy = g_new0 (WlrConfig, 1);
+ copy->outputs = (WlrOutputInfo **) g_ptr_array_free (outputs, FALSE);
+
+ return copy;
+}
+
+static gboolean
+apply_configuration (MsdWlrandrManager *manager,
+ WlrConfig *config,
+ gboolean save,
+ gboolean confirm_rollback,
+ WlrConfig *previous,
+ WlOutputHead *adaptive_sync_head)
+{
+ struct MsdWlrandrManagerPrivate *priv = manager->priv;
+ struct zwlr_output_configuration_v1 *config_obj;
+ GList *l;
+ ApplyData *ad;
+
+ if (priv->manager == NULL || priv->pending_config != NULL)
+ return FALSE;
+
+ g_debug ("msd-wlrandr: applying output configuration");
+
+ config_obj = zwlr_output_manager_v1_create_configuration (priv->manager, priv->last_serial);
+
+ for (l = priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ WlrOutputInfo *info = find_output_in_config (config, head->name);
+ struct zwlr_output_configuration_head_v1 *head_obj;
+
+ if (info != NULL && info->enabled) {
+ fill_output_mode (head, info);
+
+ head_obj = zwlr_output_configuration_v1_enable_head (config_obj, head->proxy);
+
+ {
+ struct zwlr_output_mode_v1 *mode = find_mode_for_output (head, info);
+
+ if (mode != NULL)
+ zwlr_output_configuration_head_v1_set_mode (head_obj, mode);
+ else
+ zwlr_output_configuration_head_v1_set_custom_mode (head_obj,
+ info->width,
+ info->height,
+ info->rate);
+ }
+
+ zwlr_output_configuration_head_v1_set_position (head_obj, info->x, info->y);
+ zwlr_output_configuration_head_v1_set_transform (head_obj, info->transform);
+ zwlr_output_configuration_head_v1_set_scale (head_obj, wl_fixed_from_double (info->scale));
+
+ if (priv->manager_version >= 4 && head == adaptive_sync_head)
+ zwlr_output_configuration_head_v1_set_adaptive_sync (head_obj,
+ info->adaptive_sync ? 1 : 0);
+ } else {
+ zwlr_output_configuration_v1_disable_head (config_obj, head->proxy);
+ }
+ }
+
+ ad = g_new0 (ApplyData, 1);
+ ad->manager = manager;
+ ad->save = save;
+ ad->confirm_rollback = confirm_rollback;
+ ad->previous = previous;
+ ad->adaptive_sync_head = adaptive_sync_head;
+
+ ad->config = wlr_config_copy (config);
+
+ /* Enrich the copy with the head identity fields; configurations that
+ * arrive over D-Bus do not carry them and they are useful when the
+ * file is inspected by hand.
+ */
+ for (l = priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ WlrOutputInfo *info = find_output_in_config (ad->config, head->name);
+
+ if (info == NULL)
+ continue;
+ if (info->vendor == NULL || *info->vendor == '\0')
+ info->vendor = g_strdup (head->make);
+ if (info->product == NULL || *info->product == '\0')
+ info->product = g_strdup (head->model);
+ if (info->serial == NULL || *info->serial == '\0')
+ info->serial = g_strdup (head->serial_number);
+ }
+
+ zwlr_output_configuration_v1_add_listener (config_obj, &config_listener, ad);
+ priv->pending_config = config_obj;
+ zwlr_output_configuration_v1_apply (config_obj);
+ wl_display_flush (priv->display);
+
+ return TRUE;
+}
+
+static int
+enabled_head_count (MsdWlrandrManager *manager)
+{
+ GList *l;
+ int count = 0;
+
+ for (l = manager->priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ if (head->enabled)
+ count++;
+ }
+
+ return count;
+}
+
+/* ------------------------------------------------------------------ *
+ * Confirmation / rollback of risky changes *
+ * ------------------------------------------------------------------ */
+
+typedef struct {
+ MsdWlrandrManager *manager;
+ WlrConfig *previous;
+ GtkWidget *dialog;
+ GtkLabel *countdown_label;
+ guint countdown;
+ guint timeout_id;
+} ConfirmationData;
+
+static void
+restore_previous_configuration (MsdWlrandrManager *manager, WlrConfig *previous)
+{
+ g_debug ("msd-wlrandr: restoring previous configuration");
+
+ if (!apply_configuration (manager, previous, TRUE, FALSE, NULL, NULL))
+ g_warning ("msd-wlrandr: could not restore previous configuration");
+}
+
+static gboolean
+confirmation_timeout_cb (gpointer data)
+{
+ ConfirmationData *cd = data;
+ char *markup;
+
+ if (cd->countdown > 1) {
+ cd->countdown--;
+ markup = g_markup_printf_escaped (_("Reverting in %u second%s…"),
+ cd->countdown,
+ cd->countdown == 1 ? "" : "s");
+ gtk_label_set_markup (cd->countdown_label, markup);
+ g_free (markup);
+ return G_SOURCE_CONTINUE;
+ }
+
+ cd->timeout_id = 0;
+ gtk_dialog_response (GTK_DIALOG (cd->dialog), GTK_RESPONSE_CANCEL);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+confirmation_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer data)
+{
+ ConfirmationData *cd = data;
+
+ if (response_id != GTK_RESPONSE_ACCEPT)
+ restore_previous_configuration (cd->manager, cd->previous);
+
+ gtk_widget_destroy (cd->dialog);
+}
+
+static void
+confirmation_dialog_destroy_cb (GtkWidget *widget, gpointer data)
+{
+ ConfirmationData *cd = data;
+
+ cd->manager->priv->confirm_dialog = NULL;
+
+ if (cd->timeout_id > 0) {
+ g_source_remove (cd->timeout_id);
+ cd->timeout_id = 0;
+ }
+
+ wlr_config_free (cd->previous);
+ g_free (cd);
+}
+
+static void
+queue_rollback_confirmation (MsdWlrandrManager *manager, WlrConfig *previous)
+{
+ ConfirmationData *cd;
+ GtkWidget *dialog;
+ GtkWidget *content;
+ GtkWidget *countdown_label;
+ char *markup;
+
+ if (manager->priv->confirm_dialog != NULL) {
+ wlr_config_free (previous);
+ return;
+ }
+
+ cd = g_new0 (ConfirmationData, 1);
+ cd->manager = manager;
+ cd->previous = previous;
+ cd->countdown = CONFIRMATION_DIALOG_SECONDS;
+
+ dialog = gtk_message_dialog_new (NULL,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Does the display look OK?"));
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "preferences-desktop-display");
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Restore Previous Configuration"), GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Keep This Configuration"), GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+
+ countdown_label = gtk_label_new (NULL);
+ markup = g_markup_printf_escaped (_("Reverting in %u seconds…"), cd->countdown);
+ gtk_label_set_markup (GTK_LABEL (countdown_label), markup);
+ g_free (markup);
+ gtk_widget_show (countdown_label);
+
+ content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_container_add (GTK_CONTAINER (content), countdown_label);
+
+ cd->dialog = dialog;
+ cd->countdown_label = GTK_LABEL (countdown_label);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (confirmation_dialog_response_cb), cd);
+ g_signal_connect (dialog, "destroy", G_CALLBACK (confirmation_dialog_destroy_cb), cd);
+
+ manager->priv->confirm_dialog = dialog;
+ gtk_widget_show_all (dialog);
+
+ cd->timeout_id = g_timeout_add (1000, confirmation_timeout_cb, cd);
+}
+
+/* ------------------------------------------------------------------ *
+ * Configuration snapshots *
+ * ------------------------------------------------------------------ */
+
+static WlrOutputInfo *
+snapshot_head (WlOutputHead *head)
+{
+ WlrOutputInfo *info = wlr_output_info_new (head->name);
+
+ info->vendor = g_strdup (head->make);
+ info->product = g_strdup (head->model);
+ info->serial = g_strdup (head->serial_number);
+ info->enabled = head->enabled;
+ info->x = head->x;
+ info->y = head->y;
+ info->scale = head->scale;
+ info->transform = head->transform;
+ info->adaptive_sync = head->adaptive_sync;
+
+ if (head->enabled && head->current_mode != NULL) {
+ info->width = head->current_mode->width;
+ info->height = head->current_mode->height;
+ info->rate = head->current_mode->refresh;
+ } else if (head->enabled) {
+ fill_output_mode (head, info);
+ }
+
+ return info;
+}
+
+static WlrConfig *
+snapshot_config (MsdWlrandrManager *manager)
+{
+ WlrConfig *config;
+ GList *l;
+ GPtrArray *outputs;
+
+ outputs = g_ptr_array_new ();
+ for (l = manager->priv->heads; l != NULL; l = l->next)
+ g_ptr_array_add (outputs, snapshot_head (l->data));
+ g_ptr_array_add (outputs, NULL);
+
+ config = g_new0 (WlrConfig, 1);
+ config->outputs = (WlrOutputInfo **) g_ptr_array_free (outputs, FALSE);
+
+ return config;
+}
+
+/* ------------------------------------------------------------------ *
+ * Config file storage (mirrors monitors.xml) *
+ * ------------------------------------------------------------------ */
+
+typedef struct {
+ WlrOutputInfo *output;
+ GPtrArray *outputs;
+ GPtrArray *configs;
+ GString *text;
+ char *current_element;
+} WlrConfigParser;
+
+static gboolean
+yes_no_to_boolean (const char *text)
+{
+ if (text == NULL)
+ return FALSE;
+
+ if (g_ascii_strcasecmp (text, "yes") == 0 ||
+ g_ascii_strcasecmp (text, "true") == 0 ||
+ strcmp (text, "1") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Read a scale value from the configuration file. g_ascii_strtod() is
+ * locale-independent and only accepts '.' as the decimal separator, so
+ * normalize any ',' left in the file by older versions of the plugin that
+ * wrote it with a locale-dependent "%f". Without this, e.g. "1,500000"
+ * would silently come back as 1.0.
+ */
+static double
+parse_scale (const char *text)
+{
+ char *normalized;
+ char *p;
+ double value;
+
+ if (text == NULL)
+ return 1.0;
+
+ normalized = g_strdup (text);
+ for (p = normalized; *p != '\0'; p++) {
+ if (*p == ',')
+ *p = '.';
+ }
+
+ value = g_ascii_strtod (normalized, NULL);
+ g_free (normalized);
+
+ return value;
+}
+
+static const char *
+transform_to_name (int transform)
+{
+ switch (transform) {
+ case WL_OUTPUT_TRANSFORM_NORMAL:
+ return "normal";
+ case WL_OUTPUT_TRANSFORM_90:
+ return "90";
+ case WL_OUTPUT_TRANSFORM_180:
+ return "180";
+ case WL_OUTPUT_TRANSFORM_270:
+ return "270";
+ case WL_OUTPUT_TRANSFORM_FLIPPED:
+ return "flipped";
+ case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+ return "flipped-90";
+ case WL_OUTPUT_TRANSFORM_FLIPPED_180:
+ return "flipped-180";
+ case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+ return "flipped-270";
+ default:
+ return "normal";
+ }
+}
+
+static int
+transform_from_name (const char *name)
+{
+ if (name == NULL)
+ return WL_OUTPUT_TRANSFORM_NORMAL;
+
+ if (strcmp (name, "normal") == 0)
+ return WL_OUTPUT_TRANSFORM_NORMAL;
+ if (strcmp (name, "90") == 0)
+ return WL_OUTPUT_TRANSFORM_90;
+ if (strcmp (name, "180") == 0)
+ return WL_OUTPUT_TRANSFORM_180;
+ if (strcmp (name, "270") == 0)
+ return WL_OUTPUT_TRANSFORM_270;
+ if (strcmp (name, "flipped") == 0)
+ return WL_OUTPUT_TRANSFORM_FLIPPED;
+ if (strcmp (name, "flipped-90") == 0)
+ return WL_OUTPUT_TRANSFORM_FLIPPED_90;
+ if (strcmp (name, "flipped-180") == 0)
+ return WL_OUTPUT_TRANSFORM_FLIPPED_180;
+ if (strcmp (name, "flipped-270") == 0)
+ return WL_OUTPUT_TRANSFORM_FLIPPED_270;
+
+ return WL_OUTPUT_TRANSFORM_NORMAL;
+}
+
+static void
+parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ WlrConfigParser *parser = user_data;
+ const char *name = NULL;
+ int i;
+
+ if (strcmp (element_name, "configuration") == 0) {
+ parser->outputs = g_ptr_array_new ();
+ return;
+ }
+
+ if (strcmp (element_name, "head") == 0) {
+ for (i = 0; attribute_names[i] != NULL; i++) {
+ if (strcmp (attribute_names[i], "name") == 0) {
+ name = attribute_values[i];
+ break;
+ }
+ }
+ parser->output = wlr_output_info_new (name);
+ if (parser->outputs != NULL)
+ g_ptr_array_add (parser->outputs, parser->output);
+ return;
+ }
+
+ g_free (parser->current_element);
+ parser->current_element = g_ascii_strdown (element_name, -1);
+ if (parser->text != NULL)
+ g_string_truncate (parser->text, 0);
+ else
+ parser->text = g_string_new (NULL);
+}
+
+static void
+parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ WlrConfigParser *parser = user_data;
+ WlrOutputInfo *output = parser->output;
+ const char *text = parser->text != NULL ? parser->text->str : "";
+
+ if (strcmp (element_name, "configuration") == 0) {
+ WlrConfig *config;
+
+ if (parser->outputs != NULL) {
+ g_ptr_array_add (parser->outputs, NULL);
+ config = g_new0 (WlrConfig, 1);
+ config->outputs = (WlrOutputInfo **) g_ptr_array_free (parser->outputs, FALSE);
+ parser->outputs = NULL;
+ g_ptr_array_add (parser->configs, config);
+ }
+ return;
+ }
+
+ if (strcmp (element_name, "head") == 0) {
+ parser->output = NULL;
+ return;
+ }
+
+ if (parser->current_element == NULL || output == NULL)
+ return;
+
+ if (strcmp (parser->current_element, "vendor") == 0) {
+ g_free (output->vendor);
+ output->vendor = g_strdup (text);
+ } else if (strcmp (parser->current_element, "product") == 0) {
+ g_free (output->product);
+ output->product = g_strdup (text);
+ } else if (strcmp (parser->current_element, "serial") == 0) {
+ g_free (output->serial);
+ output->serial = g_strdup (text);
+ } else if (strcmp (parser->current_element, "enabled") == 0) {
+ output->enabled = yes_no_to_boolean (text);
+ } else if (strcmp (parser->current_element, "width") == 0) {
+ output->width = atoi (text);
+ } else if (strcmp (parser->current_element, "height") == 0) {
+ output->height = atoi (text);
+ } else if (strcmp (parser->current_element, "rate") == 0) {
+ output->rate = atoi (text);
+ } else if (strcmp (parser->current_element, "x") == 0) {
+ output->x = atoi (text);
+ } else if (strcmp (parser->current_element, "y") == 0) {
+ output->y = atoi (text);
+ } else if (strcmp (parser->current_element, "scale") == 0) {
+ output->scale = parse_scale (text);
+ } else if (strcmp (parser->current_element, "transform") == 0) {
+ output->transform = transform_from_name (text);
+ } else if (strcmp (parser->current_element, "adaptive_sync") == 0) {
+ output->adaptive_sync = yes_no_to_boolean (text);
+ }
+}
+
+static void
+parser_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ WlrConfigParser *parser = user_data;
+
+ if (parser->text != NULL)
+ g_string_append_len (parser->text, text, text_len);
+}
+
+static WlrConfigParser *
+parser_new (void)
+{
+ WlrConfigParser *parser = g_new0 (WlrConfigParser, 1);
+ parser->configs = g_ptr_array_new ();
+ return parser;
+}
+
+static WlrConfig **
+configs_load_from_file (const char *filename)
+{
+ GMarkupParseContext *context;
+ WlrConfigParser *parser;
+ gchar *contents = NULL;
+ GError *error = NULL;
+ WlrConfig **result = NULL;
+
+ if (!g_file_get_contents (filename, &contents, NULL, &error)) {
+ g_debug ("msd-wlrandr: cannot read config file %s: %s", filename, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ parser = parser_new ();
+
+ context = g_markup_parse_context_new (&(GMarkupParser) {
+ .start_element = parser_start_element,
+ .end_element = parser_end_element,
+ .text = parser_text,
+ }, 0, parser, NULL);
+
+ if (g_markup_parse_context_parse (context, contents, -1, &error) &&
+ g_markup_parse_context_end_parse (context, &error)) {
+ g_ptr_array_add (parser->configs, NULL);
+ result = (WlrConfig **) g_ptr_array_free (parser->configs, FALSE);
+ } else {
+ g_warning ("msd-wlrandr: failed to parse config file %s: %s", filename, error->message);
+ g_error_free (error);
+ }
+
+ g_markup_parse_context_free (context);
+ g_free (contents);
+ g_free (parser);
+
+ return result;
+}
+
+static void
+free_config_list (WlrConfig **configs)
+{
+ int i;
+
+ if (configs == NULL)
+ return;
+
+ for (i = 0; configs[i] != NULL; i++)
+ wlr_config_free (configs[i]);
+ g_free (configs);
+}
+
+static gboolean
+config_matches_heads (WlrConfig *config, GList *heads)
+{
+ GPtrArray *config_names;
+ GPtrArray *head_names;
+ GList *l;
+ int i, j;
+ gboolean match;
+
+ config_names = g_ptr_array_new ();
+ for (i = 0; config->outputs != NULL && config->outputs[i] != NULL; i++)
+ g_ptr_array_add (config_names, config->outputs[i]->name);
+
+ head_names = g_ptr_array_new ();
+ for (l = heads; l != NULL; l = l->next)
+ g_ptr_array_add (head_names, ((WlOutputHead *) l->data)->name);
+
+ match = config_names->len == head_names->len;
+ if (match) {
+ for (i = 0; i < config_names->len && match; i++) {
+ gboolean found = FALSE;
+ for (j = 0; j < head_names->len; j++) {
+ if (g_strcmp0 (g_ptr_array_index (config_names, i), g_ptr_array_index (head_names, j)) == 0) {
+ found = TRUE;
+ break;
+ }
+ }
+ match = found;
+ }
+ }
+
+ g_ptr_array_free (config_names, FALSE);
+ g_ptr_array_free (head_names, FALSE);
+
+ return match;
+}
+
+static int
+config_enabled_head_count (WlrConfig *config)
+{
+ int i;
+ int count = 0;
+
+ for (i = 0; config->outputs != NULL && config->outputs[i] != NULL; i++) {
+ if (config->outputs[i]->enabled)
+ count++;
+ }
+
+ return count;
+}
+
+static void
+emit_configuration (WlrConfig *config, GString *string)
+{
+ int i;
+
+ g_string_append (string, " <configuration>\n");
+
+ for (i = 0; config->outputs != NULL && config->outputs[i] != NULL; i++) {
+ WlrOutputInfo *output = config->outputs[i];
+ char *escaped;
+
+ g_string_append_printf (string, " <head name=\"%s\">\n", output->name);
+
+ if (output->vendor != NULL && *output->vendor != '\0') {
+ escaped = g_markup_escape_text (output->vendor, -1);
+ g_string_append_printf (string, " <vendor>%s</vendor>\n", escaped);
+ g_free (escaped);
+ }
+ if (output->product != NULL && *output->product != '\0') {
+ escaped = g_markup_escape_text (output->product, -1);
+ g_string_append_printf (string, " <product>%s</product>\n", escaped);
+ g_free (escaped);
+ }
+ if (output->serial != NULL && *output->serial != '\0') {
+ escaped = g_markup_escape_text (output->serial, -1);
+ g_string_append_printf (string, " <serial>%s</serial>\n", escaped);
+ g_free (escaped);
+ }
+
+ if (output->enabled && output->width > 0) {
+ g_string_append (string, " <enabled>yes</enabled>\n");
+ g_string_append_printf (string, " <width>%d</width>\n", output->width);
+ g_string_append_printf (string, " <height>%d</height>\n", output->height);
+ g_string_append_printf (string, " <rate>%d</rate>\n", output->rate);
+ g_string_append_printf (string, " <x>%d</x>\n", output->x);
+ g_string_append_printf (string, " <y>%d</y>\n", output->y);
+
+ {
+ /* "%f" is locale-dependent and would write
+ * e.g. "1,500000" in comma-decimal locales,
+ * while the parser always reads with the
+ * locale-independent g_ascii_strtod(). Use
+ * g_ascii_formatd() so the file is always
+ * written with a '.' decimal separator.
+ */
+ char scale_buf[G_ASCII_DTOSTR_BUF_SIZE];
+ g_ascii_formatd (scale_buf, sizeof (scale_buf), "%f", output->scale);
+ g_string_append_printf (string, " <scale>%s</scale>\n", scale_buf);
+ }
+ g_string_append_printf (string, " <transform>%s</transform>\n", transform_to_name (output->transform));
+ } else {
+ g_string_append (string, " <enabled>no</enabled>\n");
+ }
+
+ g_string_append_printf (string, " <adaptive_sync>%s</adaptive_sync>\n",
+ output->adaptive_sync ? "yes" : "no");
+
+ g_string_append (string, " </head>\n");
+ }
+
+ g_string_append (string, " </configuration>\n");
+}
+
+/* Persist the current output configuration to monitors-wayland.xml. If
+ * `config` is non-NULL it is used as-is; otherwise the live head state is
+ * snapshotted (only used when nothing was applied and the state is known to
+ * be accurate, e.g. after a rejected adaptive sync toggle).
+ */
+static void
+save_current_configuration (MsdWlrandrManager *manager, WlrConfig *config)
+{
+ struct MsdWlrandrManagerPrivate *priv = manager->priv;
+ WlrConfig *snapshot;
+ WlrConfig **existing;
+ GString *string;
+ char *intended;
+ char *backup;
+ int i;
+ GError *error = NULL;
+
+ if (config == NULL)
+ config = snapshot = snapshot_config (manager);
+ else
+ snapshot = NULL;
+
+ intended = g_build_filename (g_get_user_config_dir (), CONFIG_INTENDED_BASENAME, NULL);
+ backup = g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
+
+ string = g_string_new ("<monitors version=\"1\">\n");
+
+ existing = configs_load_from_file (intended);
+ if (existing != NULL) {
+ for (i = 0; existing[i] != NULL; i++) {
+ if (config_matches_heads (existing[i], priv->heads))
+ continue;
+ emit_configuration (existing[i], string);
+ }
+ free_config_list (existing);
+ }
+
+ emit_configuration (config, string);
+ g_string_append (string, "</monitors>\n");
+
+ g_rename (intended, backup);
+
+ if (!g_file_set_contents (intended, string->str, -1, &error)) {
+ g_warning ("msd-wlrandr: could not save config to %s: %s", intended, error->message);
+ g_error_free (error);
+ } else {
+ g_debug ("msd-wlrandr: saved configuration to %s", intended);
+ }
+
+ g_string_free (string, TRUE);
+ g_free (intended);
+ g_free (backup);
+ if (snapshot != NULL)
+ wlr_config_free (snapshot);
+}
+
+static gboolean
+apply_configurations_from_list (MsdWlrandrManager *manager, WlrConfig **configs)
+{
+ int i;
+
+ if (configs == NULL)
+ return FALSE;
+
+ for (i = 0; configs[i] != NULL; i++) {
+ if (config_matches_heads (configs[i], manager->priv->heads)) {
+ if (config_enabled_head_count (configs[i]) == 0) {
+ g_debug ("msd-wlrandr: skipping configuration that would disable all heads");
+ return FALSE;
+ }
+ g_debug ("msd-wlrandr: found matching configuration, applying");
+ apply_configuration (manager, configs[i], FALSE, FALSE, NULL, NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+apply_stored_configuration_at_startup (MsdWlrandrManager *manager)
+{
+ gboolean res;
+ char *backup;
+ char *intended;
+ WlrConfig **configs;
+
+ backup = g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
+ intended = g_build_filename (g_get_user_config_dir (), CONFIG_INTENDED_BASENAME, NULL);
+
+ res = FALSE;
+
+ configs = configs_load_from_file (intended);
+ if (apply_configurations_from_list (manager, configs))
+ res = TRUE;
+ free_config_list (configs);
+
+ if (!res) {
+ configs = configs_load_from_file (backup);
+ if (apply_configurations_from_list (manager, configs))
+ res = TRUE;
+ free_config_list (configs);
+ }
+
+ g_free (backup);
+ g_free (intended);
+
+ return res;
+}
+
+static gboolean
+apply_default_configuration_from_file (MsdWlrandrManager *manager)
+{
+ char *filename;
+ WlrConfig **configs;
+ gboolean res;
+
+ filename = g_settings_get_string (manager->priv->settings, CONF_KEY_DEFAULT_CONFIGURATION_FILE);
+ configs = configs_load_from_file (filename);
+ res = apply_configurations_from_list (manager, configs);
+ free_config_list (configs);
+ g_free (filename);
+
+ return res;
+}
+
+static void
+auto_enable_external_monitors (MsdWlrandrManager *manager)
+{
+ WlrConfig *config;
+ GList *l;
+ gboolean changed = FALSE;
+
+ if (!g_settings_get_boolean (manager->priv->settings, CONF_KEY_TURN_ON_EXTERNAL_MONITORS_AT_STARTUP))
+ return;
+
+ config = snapshot_config (manager);
+
+ for (l = manager->priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ WlrOutputInfo *info = find_output_in_config (config, head->name);
+
+ if (info != NULL && !info->enabled) {
+ info->enabled = TRUE;
+ fill_output_mode (head, info);
+ changed = TRUE;
+ }
+ }
+
+ if (changed)
+ apply_configuration (manager, config, FALSE, FALSE, NULL, NULL);
+
+ wlr_config_free (config);
+}
+
+static void
+apply_initial_config (MsdWlrandrManager *manager)
+{
+ g_debug ("msd-wlrandr: applying initial configuration");
+
+ if (apply_stored_configuration_at_startup (manager))
+ return;
+
+ if (apply_default_configuration_from_file (manager))
+ return;
+
+ auto_enable_external_monitors (manager);
+}
+
+/* ------------------------------------------------------------------ *
+ * DBus interface *
+ * ------------------------------------------------------------------ */
+
+#define HEAD_INFO_TYPE "a(ssbiiidbb(iiii)a(iiii))"
+#define HEAD_SET_TYPE "a(sbiiiiidib)"
+#define HEAD_INFO_FORMAT "(ssbiiidbb(iiii)a(iiii))"
+#define HEAD_SET_FORMAT "(sbiiiiidib)"
+#define MODE_FORMAT "(iiii)"
+
+/* GVariant type strings cannot contain whitespace, so the compact forms
+ * above are required on the wire. These enums document what each field
+ * means so the code below never has to be decoded from the string. */
+
+/* One head as reported by GetConfiguration:
+ * (name, description, enabled, x, y, transform, scale,
+ * adaptive_sync_supported, adaptive_sync,
+ * current_mode (width, height, refresh, preferred),
+ * modes (width, height, refresh, preferred) ...) */
+typedef enum {
+ HEAD_INFO_FIELD_NAME, /* s output name */
+ HEAD_INFO_FIELD_DESCRIPTION, /* s human-readable description */
+ HEAD_INFO_FIELD_ENABLED, /* b output enabled */
+ HEAD_INFO_FIELD_X, /* i x position on the logical screen */
+ HEAD_INFO_FIELD_Y, /* i y position on the logical screen */
+ HEAD_INFO_FIELD_TRANSFORM, /* i rotation (wl_output_transform) */
+ HEAD_INFO_FIELD_SCALE, /* d fractional scale factor */
+ HEAD_INFO_FIELD_ADAPTIVE_SYNC_SUPPORTED, /* b hardware supports adaptive sync */
+ HEAD_INFO_FIELD_ADAPTIVE_SYNC, /* b adaptive sync currently active */
+ HEAD_INFO_FIELD_CURRENT_MODE, /* (iiii) the mode currently in use */
+ HEAD_INFO_FIELD_MODES, /* a(iiii) all modes the output supports */
+ HEAD_INFO_N_FIELDS
+} HeadInfoField;
+
+/* One entry of the modes array. */
+typedef enum {
+ MODE_FIELD_WIDTH, /* i width in pixels */
+ MODE_FIELD_HEIGHT, /* i height in pixels */
+ MODE_FIELD_REFRESH, /* i refresh rate in mHz */
+ MODE_FIELD_PREFERRED, /* i 1 if this is the preferred mode */
+ MODE_N_FIELDS
+} ModeField;
+
+/* One head as sent to SetConfiguration:
+ * (name, enabled, width, height, refresh, x, y, scale, transform,
+ * adaptive_sync) */
+typedef enum {
+ HEAD_SET_FIELD_NAME, /* s output name */
+ HEAD_SET_FIELD_ENABLED, /* b output enabled */
+ HEAD_SET_FIELD_WIDTH, /* i desired width in pixels */
+ HEAD_SET_FIELD_HEIGHT, /* i desired height in pixels */
+ HEAD_SET_FIELD_RATE, /* i desired refresh rate in mHz */
+ HEAD_SET_FIELD_X, /* i desired x position */
+ HEAD_SET_FIELD_Y, /* i desired y position */
+ HEAD_SET_FIELD_SCALE, /* d desired fractional scale */
+ HEAD_SET_FIELD_TRANSFORM, /* i desired rotation */
+ HEAD_SET_FIELD_ADAPTIVE_SYNC, /* b desired adaptive sync state */
+ HEAD_SET_N_FIELDS
+} HeadSetField;
+
+/* Notify interested parties (e.g. the display capplet) that the current
+ * output configuration may have changed. The same object path is
+ * registered twice, once per interface, so the signal is emitted on both.
+ */
+static void
+emit_configuration_changed (MsdWlrandrManager *manager)
+{
+ GDBusConnection *connection;
+
+ if (manager->priv->connection == NULL)
+ return;
+
+ connection = manager->priv->connection;
+
+ g_dbus_connection_emit_signal (connection,
+ NULL,
+ MSD_WLRANDR_DBUS_PATH,
+ MSD_WLRANDR_DBUS_NAME,
+ "ConfigurationChanged",
+ NULL, NULL);
+ g_dbus_connection_emit_signal (connection,
+ NULL,
+ MSD_WLRANDR_DBUS_PATH,
+ MSD_WLRANDR_DBUS_NAME_2,
+ "ConfigurationChanged",
+ NULL, NULL);
+}
+
+/* Serialize the current state of the outputs so the display capplet can
+ * render and edit it. Field order and meaning are documented by the
+ * HeadInfoField and ModeField enums above.
+ */
+static GVariant *
+build_configuration_variant (MsdWlrandrManager *manager)
+{
+ GVariantBuilder builder;
+ GList *l;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE (HEAD_INFO_TYPE));
+
+ for (l = manager->priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ GVariantBuilder modes_builder;
+ GList *m;
+ int cur_w = 0, cur_h = 0, cur_rate = 0, cur_preferred = 0;
+
+ g_variant_builder_init (&modes_builder, G_VARIANT_TYPE ("a" MODE_FORMAT));
+
+ for (m = head->modes; m != NULL; m = m->next) {
+ WlOutputMode *mode = m->data;
+
+ g_variant_builder_add (&modes_builder, MODE_FORMAT,
+ mode->width,
+ mode->height,
+ mode->refresh,
+ mode->preferred ? 1 : 0);
+ }
+
+ if (head->enabled && head->current_mode != NULL) {
+ cur_w = head->current_mode->width;
+ cur_h = head->current_mode->height;
+ cur_rate = head->current_mode->refresh;
+ cur_preferred = head->current_mode->preferred ? 1 : 0;
+ }
+
+ g_variant_builder_add (&builder, HEAD_INFO_FORMAT,
+ head->name != NULL ? head->name : "",
+ head->description != NULL ? head->description : "",
+ head->enabled,
+ head->x,
+ head->y,
+ head->transform,
+ head->scale,
+ head->adaptive_sync_supported == 1,
+ head->adaptive_sync,
+ cur_w, cur_h, cur_rate, cur_preferred,
+ &modes_builder);
+ }
+
+ return g_variant_builder_end (&builder);
+}
+
+/* Deserialize a configuration sent by the display capplet. Field order
+ * and meaning are documented by the HeadSetField enum above.
+ */
+static WlrConfig *
+parse_configuration_from_variant (GVariant *configuration)
+{
+ WlrConfig *config;
+ GVariantIter iter;
+ GPtrArray *outputs;
+
+ if (configuration == NULL ||
+ !g_variant_is_of_type (configuration, G_VARIANT_TYPE (HEAD_SET_TYPE)))
+ return NULL;
+
+ outputs = g_ptr_array_new ();
+
+ g_variant_iter_init (&iter, configuration);
+ while (TRUE) {
+ GVariant *head_var;
+ const char *name;
+ gboolean enabled;
+ gboolean adaptive_sync;
+ int width, height, rate, x, y, transform;
+ double scale;
+ WlrOutputInfo *info;
+
+ head_var = g_variant_iter_next_value (&iter);
+ if (head_var == NULL)
+ break;
+
+ g_variant_get_child (head_var, HEAD_SET_FIELD_NAME,
+ "&s", &name);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_ENABLED,
+ "b", &enabled);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_WIDTH,
+ "i", &width);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_HEIGHT,
+ "i", &height);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_RATE,
+ "i", &rate);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_X,
+ "i", &x);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_Y,
+ "i", &y);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_SCALE,
+ "d", &scale);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_TRANSFORM,
+ "i", &transform);
+ g_variant_get_child (head_var, HEAD_SET_FIELD_ADAPTIVE_SYNC,
+ "b", &adaptive_sync);
+
+ /* wlr_output_info_new () duplicates the name, so it is
+ * safe to release the tuple afterwards. */
+ info = wlr_output_info_new (name);
+ info->enabled = enabled;
+ info->width = width;
+ info->height = height;
+ info->rate = rate;
+ info->x = x;
+ info->y = y;
+ info->scale = scale;
+ info->transform = transform;
+ info->adaptive_sync = adaptive_sync;
+
+ g_variant_unref (head_var);
+
+ g_ptr_array_add (outputs, info);
+ }
+
+ if (outputs->len == 0) {
+ g_ptr_array_free (outputs, TRUE);
+ return NULL;
+ }
+
+ g_ptr_array_add (outputs, NULL);
+
+ config = g_new0 (WlrConfig, 1);
+ config->outputs = (WlrOutputInfo **) g_ptr_array_free (outputs, FALSE);
+
+ return config;
+}
+
+/* Pick the head whose adaptive sync state the caller wants to change, if
+ * any. The zwlr-output-management protocol only lets the plugin set
+ * adaptive sync on the heads it explicitly marks, so only the head whose
+ * requested value differs from the current one is applied.
+ */
+static WlOutputHead *
+find_adaptive_sync_head_to_set (MsdWlrandrManager *manager,
+ WlrConfig *config)
+{
+ GList *l;
+
+ for (l = manager->priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+ WlrOutputInfo *info = find_output_in_config (config, head->name);
+
+ if (info != NULL && info->enabled &&
+ info->adaptive_sync != head->adaptive_sync)
+ return head;
+ }
+
+ return NULL;
+}
+
+static gboolean
+msd_wlrandr_manager_set_configuration (MsdWlrandrManager *manager,
+ GVariant *configuration,
+ GError **error)
+{
+ WlrConfig *config;
+ WlrConfig *previous;
+ WlOutputHead *adaptive_sync_head;
+ gboolean res;
+
+ config = parse_configuration_from_variant (configuration);
+ if (config == NULL) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "The supplied configuration is invalid");
+ return FALSE;
+ }
+
+ if (config_enabled_head_count (config) == 0) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "The last active output cannot be turned off");
+ wlr_config_free (config);
+ return FALSE;
+ }
+
+ previous = snapshot_config (manager);
+
+ adaptive_sync_head = find_adaptive_sync_head_to_set (manager, config);
+
+ res = apply_configuration (manager, config, TRUE, TRUE, previous, adaptive_sync_head);
+ if (!res) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Could not apply the display configuration");
+ wlr_config_free (config);
+ wlr_config_free (previous);
+ return FALSE;
+ }
+
+ wlr_config_free (config);
+
+ return TRUE;
+}
+
+static gboolean
+msd_wlrandr_manager_apply_configuration (MsdWlrandrManager *manager,
+ GError **error)
+{
+ if (!apply_stored_configuration_at_startup (manager)) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No stored configuration applies to the current outputs");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ MsdWlrandrManager *manager = (MsdWlrandrManager *) user_data;
+ g_autoptr (GError) error = NULL;
+
+ g_debug ("Calling method '%s' for wlrandr", method_name);
+
+ if (g_strcmp0 (method_name, "GetConfiguration") == 0) {
+ GVariant *configuration;
+
+ configuration = build_configuration_variant (manager);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(@a(ssbiiidbb(iiii)a(iiii)))",
+ configuration));
+ } else if (g_strcmp0 (method_name, "SetConfiguration") == 0) {
+ g_autoptr (GVariant) configuration = NULL;
+
+ g_variant_get (parameters, "(@a(sbiiiiidib))", &configuration);
+ if (!msd_wlrandr_manager_set_configuration (manager, configuration, &error))
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ else
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ } else if (g_strcmp0 (method_name, "ApplyConfiguration") == 0) {
+ if (!msd_wlrandr_manager_apply_configuration (manager, &error))
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ else
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+}
+
+static void
+handle_method_call2 (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ MsdWlrandrManager *manager = (MsdWlrandrManager *) user_data;
+ g_autoptr (GError) error = NULL;
+
+ g_debug ("Calling method '%s' for wlrandr", method_name);
+
+ if (g_strcmp0 (method_name, "GetConfiguration") == 0) {
+ GVariant *configuration;
+
+ configuration = build_configuration_variant (manager);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(@a(ssbiiidbb(iiii)a(iiii)))",
+ configuration));
+ } else if (g_strcmp0 (method_name, "SetConfiguration") == 0) {
+ g_autoptr (GVariant) configuration = NULL;
+
+ g_variant_get (parameters, "(@a(sbiiiiidib))", &configuration);
+ if (!msd_wlrandr_manager_set_configuration (manager, configuration, &error))
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ else
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ } else if (g_strcmp0 (method_name, "ApplyConfiguration") == 0) {
+ gint64 parent_window_id;
+ gint64 timestamp;
+
+ g_variant_get (parameters, "(xx)", &parent_window_id, &timestamp);
+ /* Wayland has no X window ids or X timestamps; the arguments
+ * are accepted only for API compatibility with xrandr.
+ */
+ (void) parent_window_id;
+ (void) timestamp;
+
+ if (!msd_wlrandr_manager_apply_configuration (manager, &error))
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ else
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ .method_call = handle_method_call,
+};
+
+static const GDBusInterfaceVTable interface_vtable2 =
+{
+ .method_call = handle_method_call2,
+};
+
+static void
+on_bus_gotten (GObject *source_object,
+ GAsyncResult *res,
+ MsdWlrandrManager *manager)
+{
+ GDBusConnection *connection;
+ GError *error = NULL;
+
+ connection = g_bus_get_finish (res, &error);
+ if (connection == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get session bus: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ manager->priv->connection = connection;
+
+ if (manager->priv->introspection_data == NULL) {
+ g_warning ("Could not parse wlrandr D-Bus introspection XML");
+ return;
+ }
+
+ manager->priv->registration_id =
+ g_dbus_connection_register_object (connection,
+ MSD_WLRANDR_DBUS_PATH,
+ manager->priv->introspection_data->interfaces[0],
+ &interface_vtable,
+ manager,
+ NULL,
+ NULL);
+ manager->priv->registration_id_2 =
+ g_dbus_connection_register_object (connection,
+ MSD_WLRANDR_DBUS_PATH,
+ manager->priv->introspection_data->interfaces[1],
+ &interface_vtable2,
+ manager,
+ NULL,
+ NULL);
+
+ manager->priv->owner_id = g_bus_own_name_on_connection (manager->priv->connection,
+ MSD_DBUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
+ NULL, NULL, NULL, NULL);
+}
+
+static void
+register_manager_dbus (MsdWlrandrManager *manager)
+{
+ manager->priv->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ if (manager->priv->introspection_data == NULL) {
+ g_warning ("Could not parse wlrandr D-Bus introspection XML");
+ return;
+ }
+ manager->priv->bus_cancellable = g_cancellable_new ();
+ g_bus_get (G_BUS_TYPE_SESSION,
+ manager->priv->bus_cancellable,
+ (GAsyncReadyCallback) on_bus_gotten,
+ manager);
+}
+
+/* ------------------------------------------------------------------ *
+ * Menu actions *
+ * ------------------------------------------------------------------ */
+
+static void
+monitor_activate_cb (GtkCheckMenuItem *item, MsdWlrandrManager *manager)
+{
+ WlOutputHead *head = g_object_get_data (G_OBJECT (item), "head");
+ gboolean on = gtk_check_menu_item_get_active (item);
+ WlrConfig *config;
+ WlrOutputInfo *info;
+
+ if (head == NULL)
+ return;
+
+ if (head->enabled == on)
+ return;
+
+ if (!on && enabled_head_count (manager) <= 1) {
+ gulong activate_id;
+
+ g_warning ("msd-wlrandr: refusing to disable the last output");
+ notify_config_error (_("The last active output cannot be turned off"));
+
+ activate_id = (gulong) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (item), "activate-id"));
+ if (activate_id > 0) {
+ g_signal_handler_block (item, activate_id);
+ gtk_check_menu_item_set_active (item, TRUE);
+ g_signal_handler_unblock (item, activate_id);
+ }
+ return;
+ }
+
+ config = snapshot_config (manager);
+ info = find_output_in_config (config, head->name);
+ if (info == NULL) {
+ wlr_config_free (config);
+ return;
+ }
+
+ info->enabled = on;
+ if (on) {
+ fill_output_mode (head, info);
+ if (info->x == 0 && info->y == 0) {
+ GList *l;
+ int max_x = 0;
+
+ for (l = manager->priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *h = l->data;
+ if (h->enabled && h->current_mode != NULL) {
+ int right = h->x + h->current_mode->width;
+ if (right > max_x)
+ max_x = right;
+ }
+ }
+ info->x = max_x;
+ info->y = 0;
+ }
+ }
+
+ apply_configuration (manager, config, TRUE, FALSE, NULL, NULL);
+ wlr_config_free (config);
+}
+
+static void
+mode_activate_cb (GtkCheckMenuItem *item, MsdWlrandrManager *manager)
+{
+ WlOutputHead *head = g_object_get_data (G_OBJECT (item), "head");
+ WlOutputMode *mode = g_object_get_data (G_OBJECT (item), "mode");
+ WlrConfig *config;
+ WlrOutputInfo *info;
+
+ if (head == NULL || mode == NULL)
+ return;
+
+ if (!gtk_check_menu_item_get_active (item))
+ return;
+
+ config = snapshot_config (manager);
+ info = find_output_in_config (config, head->name);
+ if (info == NULL) {
+ wlr_config_free (config);
+ return;
+ }
+
+ info->width = mode->width;
+ info->height = mode->height;
+ info->rate = mode->refresh;
+
+ apply_configuration (manager, config, TRUE, FALSE, NULL, NULL);
+ wlr_config_free (config);
+}
+
+static void
+rotation_activate_cb (GtkCheckMenuItem *item, MsdWlrandrManager *manager)
+{
+ WlOutputHead *head = g_object_get_data (G_OBJECT (item), "head");
+ int transform = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "transform"));
+ WlrConfig *config;
+ WlrOutputInfo *info;
+
+ if (head == NULL)
+ return;
+
+ if (!gtk_check_menu_item_get_active (item))
+ return;
+
+ config = snapshot_config (manager);
+ info = find_output_in_config (config, head->name);
+ if (info == NULL) {
+ wlr_config_free (config);
+ return;
+ }
+
+ info->transform = transform;
+
+ apply_configuration (manager, config, TRUE, FALSE, NULL, NULL);
+ wlr_config_free (config);
+}
+
+static void
+adaptive_sync_activate_cb (GtkCheckMenuItem *item, MsdWlrandrManager *manager)
+{
+ WlOutputHead *head = g_object_get_data (G_OBJECT (item), "head");
+ gboolean on = gtk_check_menu_item_get_active (item);
+ WlrConfig *config;
+ WlrOutputInfo *info;
+
+ if (head == NULL)
+ return;
+
+ if (head->adaptive_sync_supported != 1) {
+ gulong activate_id;
+
+ g_debug ("msd-wlrandr: adaptive sync support not confirmed on %s", head->name);
+
+ activate_id = (gulong) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (item), "activate-id"));
+ if (activate_id > 0) {
+ g_signal_handler_block (item, activate_id);
+ gtk_check_menu_item_set_active (item, head->adaptive_sync);
+ g_signal_handler_unblock (item, activate_id);
+ }
+ return;
+ }
+
+ config = snapshot_config (manager);
+ info = find_output_in_config (config, head->name);
+ if (info == NULL) {
+ wlr_config_free (config);
+ return;
+ }
+
+ info->adaptive_sync = on;
+
+ apply_configuration (manager, config, TRUE, FALSE, NULL, head);
+ wlr_config_free (config);
+}
+
+static void
+run_display_capplet (MsdWlrandrManager *manager)
+{
+ GError *error = NULL;
+
+ if (!mate_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
+ MSD_WLRANDR_DISPLAY_CAPPLET, &error)) {
+ g_warning ("Could not spawn %s: %s", MSD_WLRANDR_DISPLAY_CAPPLET, error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+configure_display_cb (GtkMenuItem *item, MsdWlrandrManager *manager)
+{
+ run_display_capplet (manager);
+}
+
+/* ------------------------------------------------------------------ *
+ * SNI menu *
+ * ------------------------------------------------------------------ */
+
+static void
+append_output_title_item (GtkMenu *menu, WlOutputHead *head)
+{
+ GtkWidget *item;
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ char *markup;
+ char *display_name;
+
+ item = gtk_menu_item_new ();
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ image = gtk_image_new_from_icon_name ("computer", GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (box), image);
+
+ display_name = head->description != NULL && *head->description != '\0'
+ ? g_strdup (head->description) : g_strdup (head->name);
+
+ markup = g_markup_printf_escaped ("<b>%s</b>", display_name);
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ gtk_widget_set_margin_start (label, 6);
+ gtk_widget_set_margin_end (label, 6);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ gtk_container_add (GTK_CONTAINER (item), box);
+ gtk_widget_set_sensitive (item, FALSE);
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ g_free (markup);
+ g_free (display_name);
+}
+
+static void
+append_enable_item (GtkMenu *menu, MsdWlrandrManager *manager, WlOutputHead *head)
+{
+ GtkWidget *item;
+ gulong activate_id;
+
+ item = gtk_check_menu_item_new ();
+
+ if (head->enabled) {
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item), _("ON"));
+ gtk_widget_set_tooltip_text (item, _("Turn this monitor off"));
+ } else {
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item), _("OFF"));
+ gtk_widget_set_tooltip_text (item, _("Turn this monitor on"));
+ }
+
+ g_object_set_data (G_OBJECT (item), "head", head);
+
+ activate_id = g_signal_connect (item, "activate", G_CALLBACK (monitor_activate_cb), manager);
+ g_object_set_data (G_OBJECT (item), "activate-id", GUINT_TO_POINTER ((guint) activate_id));
+
+ g_signal_handler_block (item, activate_id);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), head->enabled);
+ g_signal_handler_unblock (item, activate_id);
+
+ if (head->enabled && enabled_head_count (manager) <= 1)
+ gtk_widget_set_sensitive (item, FALSE);
+
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+}
+
+static void
+append_resolution_submenu (GtkMenu *menu, MsdWlrandrManager *manager, WlOutputHead *head)
+{
+ GtkWidget *submenu;
+ GtkWidget *item;
+ GList *l;
+ GSList *group = NULL;
+
+ submenu = gtk_menu_new ();
+
+ for (l = head->modes; l != NULL; l = l->next) {
+ WlOutputMode *mode = l->data;
+ gboolean swapped = head->transform == WL_OUTPUT_TRANSFORM_90 ||
+ head->transform == WL_OUTPUT_TRANSFORM_270;
+ int width = swapped ? mode->height : mode->width;
+ int height = swapped ? mode->width : mode->height;
+ char *label;
+ gulong activate_id;
+
+ if (mode->refresh > 0)
+ label = g_strdup_printf ("%dx%d @ %g Hz", width, height, mode->refresh / 1000.0);
+ else
+ label = g_strdup_printf ("%dx%d", width, height);
+
+ item = gtk_radio_menu_item_new_with_label (group, label);
+ group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+ g_free (label);
+
+ g_object_set_data (G_OBJECT (item), "head", head);
+ g_object_set_data (G_OBJECT (item), "mode", mode);
+
+ if (mode->preferred)
+ gtk_widget_set_tooltip_text (item, _("Preferred resolution"));
+
+ if (head->current_mode == mode) {
+ activate_id = g_signal_connect (item, "toggled", G_CALLBACK (mode_activate_cb), manager);
+ g_signal_handler_block (item, activate_id);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
+ g_signal_handler_unblock (item, activate_id);
+ } else {
+ g_signal_connect (item, "toggled", G_CALLBACK (mode_activate_cb), manager);
+ }
+
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+ }
+
+ item = gtk_menu_item_new_with_label (_("Resolution"));
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+}
+
+static void
+append_rotation_submenu (GtkMenu *menu, MsdWlrandrManager *manager, WlOutputHead *head)
+{
+ typedef struct {
+ int transform;
+ const char *name;
+ } RotationInfo;
+
+ static const RotationInfo rotations[] = {
+ { WL_OUTPUT_TRANSFORM_NORMAL, N_("Normal") },
+ { WL_OUTPUT_TRANSFORM_90, N_("Left") },
+ { WL_OUTPUT_TRANSFORM_270, N_("Right") },
+ { WL_OUTPUT_TRANSFORM_180, N_("Upside Down") },
+ };
+
+ GtkWidget *submenu;
+ GtkWidget *item;
+ GSList *group = NULL;
+ int i;
+
+ submenu = gtk_menu_new ();
+
+ for (i = 0; i < G_N_ELEMENTS (rotations); i++) {
+ gulong activate_id;
+
+ item = gtk_radio_menu_item_new_with_label (group, _(rotations[i].name));
+ group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+
+ if (!head->enabled)
+ gtk_widget_set_sensitive (item, FALSE);
+
+ g_object_set_data (G_OBJECT (item), "head", head);
+ g_object_set_data (G_OBJECT (item), "transform", GINT_TO_POINTER (rotations[i].transform));
+
+ if (head->transform == rotations[i].transform) {
+ activate_id = g_signal_connect (item, "toggled", G_CALLBACK (rotation_activate_cb), manager);
+ g_signal_handler_block (item, activate_id);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
+ g_signal_handler_unblock (item, activate_id);
+ } else {
+ g_signal_connect (item, "toggled", G_CALLBACK (rotation_activate_cb), manager);
+ }
+
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+ }
+
+ item = gtk_menu_item_new_with_label (_("Rotation"));
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+}
+
+static void
+append_adaptive_sync_item (GtkMenu *menu, MsdWlrandrManager *manager, WlOutputHead *head)
+{
+ GtkWidget *item;
+ gulong activate_id;
+ gboolean unavailable;
+
+ if (head_is_wayland_backend (head) && head->adaptive_sync_supported == -1)
+ head->adaptive_sync_supported = 0;
+
+ unavailable = !head->enabled || head_is_wayland_backend (head) || head->adaptive_sync_supported != 1;
+
+ item = gtk_check_menu_item_new_with_label (_("Adaptive Sync (VRR)"));
+ g_object_set_data (G_OBJECT (item), "head", head);
+
+ activate_id = g_signal_connect (item, "activate", G_CALLBACK (adaptive_sync_activate_cb), manager);
+ g_object_set_data (G_OBJECT (item), "activate-id", GUINT_TO_POINTER ((guint) activate_id));
+
+ g_signal_handler_block (item, activate_id);
+ if (unavailable)
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), FALSE);
+ else
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), head->adaptive_sync);
+ g_signal_handler_unblock (item, activate_id);
+
+ if (unavailable)
+ gtk_widget_set_sensitive (item, FALSE);
+
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+}
+
+static void
+append_configure_item (GtkMenu *menu, MsdWlrandrManager *manager)
+{
+ GtkWidget *item;
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ GSettings *icon_settings;
+
+ item = gtk_menu_item_new ();
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
+ image = gtk_image_new_from_icon_name ("preferences-system", GTK_ICON_SIZE_MENU);
+ label = gtk_label_new_with_mnemonic (_("_Configure Display Settings…"));
+
+ icon_settings = g_settings_new ("org.mate.interface");
+ if (g_settings_get_boolean (icon_settings, "menus-have-icons"))
+ gtk_container_add (GTK_CONTAINER (box), image);
+ g_object_unref (icon_settings);
+
+ gtk_container_add (GTK_CONTAINER (box), label);
+ gtk_container_add (GTK_CONTAINER (item), box);
+
+ g_signal_connect (item, "activate", G_CALLBACK (configure_display_cb), manager);
+
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+}
+
+static void
+rebuild_menu (MsdWlrandrManager *manager)
+{
+ struct MsdWlrandrManagerPrivate *priv = manager->priv;
+ GtkWidget *menu;
+ GList *l;
+
+#ifndef HAVE_APP_INDICATOR
+ /* there is no tray icon to attach the menu to */
+ return;
+#else
+ if (priv->indicator == NULL)
+ return;
+#endif
+
+ menu = gtk_menu_new ();
+
+ for (l = priv->heads; l != NULL; l = l->next) {
+ WlOutputHead *head = l->data;
+
+ append_output_title_item (GTK_MENU (menu), head);
+ append_enable_item (GTK_MENU (menu), manager, head);
+ append_resolution_submenu (GTK_MENU (menu), manager, head);
+ append_rotation_submenu (GTK_MENU (menu), manager, head);
+ append_adaptive_sync_item (GTK_MENU (menu), manager, head);
+ }
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), gtk_separator_menu_item_new ());
+
+ append_configure_item (GTK_MENU (menu), manager);
+
+ gtk_widget_show_all (menu);
+
+ if (priv->menu != NULL)
+ gtk_widget_destroy (priv->menu);
+ priv->menu = menu;
+
+#ifdef HAVE_APP_INDICATOR
+ app_indicator_set_menu (priv->indicator, GTK_MENU (menu));
+#endif
+}
+
+/* ------------------------------------------------------------------ *
+ * GSettings callbacks *
+ * ------------------------------------------------------------------ */
+
+static void
+start_or_stop_icon (MsdWlrandrManager *manager)
+{
+ gboolean show = g_settings_get_boolean (manager->priv->settings, CONF_KEY_SHOW_NOTIFICATION_ICON);
+
+ if (show) {
+#ifdef HAVE_APP_INDICATOR
+ app_indicator_set_status (manager->priv->indicator, APP_INDICATOR_STATUS_ACTIVE);
+#endif
+ rebuild_menu (manager);
+ } else {
+#ifdef HAVE_APP_INDICATOR
+ app_indicator_set_status (manager->priv->indicator, APP_INDICATOR_STATUS_PASSIVE);
+#endif
+ }
+}
+
+static void
+on_config_changed (GSettings *settings, gchar *key, MsdWlrandrManager *manager)
+{
+ if (strcmp (key, CONF_KEY_SHOW_NOTIFICATION_ICON) == 0)
+ start_or_stop_icon (manager);
+}
+
+/* ------------------------------------------------------------------ *
+ * Object lifecycle *
+ * ------------------------------------------------------------------ */
+
+G_DEFINE_TYPE_WITH_PRIVATE (MsdWlrandrManager, msd_wlrandr_manager, G_TYPE_OBJECT)
+
+static void
+msd_wlrandr_manager_finalize (GObject *object)
+{
+ MsdWlrandrManager *manager = MSD_WLRANDR_MANAGER (object);
+ struct MsdWlrandrManagerPrivate *priv = manager->priv;
+ GList *l;
+
+ if (priv->owner_id > 0) {
+ g_bus_unown_name (priv->owner_id);
+ priv->owner_id = 0;
+ }
+
+ if (priv->connection != NULL) {
+ if (priv->registration_id > 0)
+ g_dbus_connection_unregister_object (priv->connection, priv->registration_id);
+ if (priv->registration_id_2 > 0)
+ g_dbus_connection_unregister_object (priv->connection, priv->registration_id_2);
+ g_object_unref (priv->connection);
+ priv->connection = NULL;
+ }
+
+ if (priv->introspection_data != NULL) {
+ g_dbus_node_info_unref (priv->introspection_data);
+ priv->introspection_data = NULL;
+ }
+
+ if (priv->bus_cancellable != NULL) {
+ g_cancellable_cancel (priv->bus_cancellable);
+ g_object_unref (priv->bus_cancellable);
+ priv->bus_cancellable = NULL;
+ }
+
+ if (priv->settings != NULL) {
+ if (priv->icon_changed_id > 0)
+ g_signal_handler_disconnect (priv->settings, priv->icon_changed_id);
+ g_object_unref (priv->settings);
+ }
+
+#ifdef HAVE_APP_INDICATOR
+ if (priv->indicator != NULL)
+ g_object_unref (priv->indicator);
+#endif
+ if (priv->menu != NULL)
+ gtk_widget_destroy (priv->menu);
+ if (priv->confirm_dialog != NULL)
+ gtk_widget_destroy (priv->confirm_dialog);
+
+ for (l = priv->heads; l != NULL; l = l->next)
+ wlr_output_head_free (l->data);
+ g_list_free (priv->heads);
+
+ if (priv->manager != NULL)
+ zwlr_output_manager_v1_destroy (priv->manager);
+ if (priv->registry != NULL)
+ wl_registry_destroy (priv->registry);
+
+ if (notify_is_initted ())
+ notify_uninit ();
+
+ G_OBJECT_CLASS (msd_wlrandr_manager_parent_class)->finalize (object);
+}
+
+static void
+msd_wlrandr_manager_init (MsdWlrandrManager *manager)
+{
+ manager->priv = msd_wlrandr_manager_get_instance_private (manager);
+}
+
+static void
+msd_wlrandr_manager_class_init (MsdWlrandrManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = msd_wlrandr_manager_finalize;
+}
+
+MsdWlrandrManager *
+msd_wlrandr_manager_new (void)
+{
+ return g_object_new (MSD_TYPE_WLRANDR_MANAGER, NULL);
+}
+
+gboolean
+msd_wlrandr_manager_start (MsdWlrandrManager *manager, GError **error)
+{
+ struct MsdWlrandrManagerPrivate *priv = manager->priv;
+ GdkDisplay *gdk_display;
+ GSettings *icon_settings;
+
+ g_return_val_if_fail (MSD_IS_WLRANDR_MANAGER (manager), FALSE);
+
+ if (priv->started)
+ return TRUE;
+
+ if (!GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The wlrandr plugin requires a Wayland display");
+ return FALSE;
+ }
+
+ gdk_display = gdk_display_get_default ();
+ priv->display = gdk_wayland_display_get_wl_display (gdk_display);
+ if (priv->display == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unable to get the Wayland display");
+ return FALSE;
+ }
+
+ g_debug ("Starting wlrandr manager");
+
+ priv->settings = g_settings_new (CONF_SCHEMA);
+
+ notify_init ("mate-settings-daemon");
+
+ priv->registry = wl_display_get_registry (priv->display);
+ wl_registry_add_listener (priv->registry, &registry_listener, manager);
+ wl_display_flush (priv->display);
+
+#ifdef HAVE_APP_INDICATOR
+ priv->indicator = app_indicator_new ("org.mate.settingsdaemon.wlrandr",
+ MSD_WLRANDR_ICON_NAME,
+ APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
+ app_indicator_set_title (priv->indicator, _("Configure display settings"));
+#endif
+
+ icon_settings = g_settings_new ("org.mate.interface");
+#ifdef HAVE_APP_INDICATOR
+ if (g_settings_get_boolean (icon_settings, "menus-have-icons"))
+ app_indicator_set_icon_theme_path (priv->indicator, NULL);
+#endif
+ g_object_unref (icon_settings);
+
+ priv->icon_changed_id = g_signal_connect (priv->settings, "changed::" CONF_KEY_SHOW_NOTIFICATION_ICON,
+ G_CALLBACK (on_config_changed), manager);
+
+ start_or_stop_icon (manager);
+
+ register_manager_dbus (manager);
+
+ priv->started = TRUE;
+
+ return TRUE;
+}
+
+void
+msd_wlrandr_manager_stop (MsdWlrandrManager *manager)
+{
+ struct MsdWlrandrManagerPrivate *priv;
+ GList *l;
+
+ g_return_if_fail (MSD_IS_WLRANDR_MANAGER (manager));
+
+ if (!manager->priv->started)
+ return;
+
+ g_debug ("Stopping wlrandr manager");
+
+ priv = manager->priv;
+
+ if (priv->owner_id > 0) {
+ g_bus_unown_name (priv->owner_id);
+ priv->owner_id = 0;
+ }
+
+ if (priv->connection != NULL) {
+ if (priv->registration_id > 0) {
+ g_dbus_connection_unregister_object (priv->connection, priv->registration_id);
+ priv->registration_id = 0;
+ }
+ if (priv->registration_id_2 > 0) {
+ g_dbus_connection_unregister_object (priv->connection, priv->registration_id_2);
+ priv->registration_id_2 = 0;
+ }
+ g_object_unref (priv->connection);
+ priv->connection = NULL;
+ }
+
+ if (priv->introspection_data != NULL) {
+ g_dbus_node_info_unref (priv->introspection_data);
+ priv->introspection_data = NULL;
+ }
+
+ if (priv->bus_cancellable != NULL) {
+ g_cancellable_cancel (priv->bus_cancellable);
+ g_object_unref (priv->bus_cancellable);
+ priv->bus_cancellable = NULL;
+ }
+
+ if (priv->settings != NULL) {
+ if (priv->icon_changed_id > 0)
+ g_signal_handler_disconnect (priv->settings, priv->icon_changed_id);
+ g_object_unref (priv->settings);
+ priv->settings = NULL;
+ }
+
+#ifdef HAVE_APP_INDICATOR
+ if (priv->indicator != NULL) {
+ app_indicator_set_status (priv->indicator, APP_INDICATOR_STATUS_PASSIVE);
+ g_object_unref (priv->indicator);
+ priv->indicator = NULL;
+ }
+#endif
+
+ if (priv->menu != NULL) {
+ gtk_widget_destroy (priv->menu);
+ priv->menu = NULL;
+ }
+
+ if (priv->confirm_dialog != NULL) {
+ gtk_widget_destroy (priv->confirm_dialog);
+ priv->confirm_dialog = NULL;
+ }
+
+ if (priv->manager != NULL) {
+ zwlr_output_manager_v1_stop (priv->manager);
+ zwlr_output_manager_v1_destroy (priv->manager);
+ priv->manager = NULL;
+ }
+
+ if (priv->registry != NULL) {
+ wl_registry_destroy (priv->registry);
+ priv->registry = NULL;
+ }
+
+ for (l = priv->heads; l != NULL; l = l->next)
+ wlr_output_head_free (l->data);
+ g_list_free (priv->heads);
+ priv->heads = NULL;
+
+ if (notify_is_initted ())
+ notify_uninit ();
+
+ priv->started = FALSE;
+}
diff --git a/plugins/wlrandr/msd-wlrandr-manager.h b/plugins/wlrandr/msd-wlrandr-manager.h
new file mode 100644
index 0000000..2763c09
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-manager.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __MSD_WLRANDR_MANAGER_H
+#define __MSD_WLRANDR_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MSD_TYPE_WLRANDR_MANAGER (msd_wlrandr_manager_get_type ())
+#define MSD_WLRANDR_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MSD_TYPE_WLRANDR_MANAGER, MsdWlrandrManager))
+#define MSD_WLRANDR_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), MSD_TYPE_WLRANDR_MANAGER, MsdWlrandrManagerClass))
+#define MSD_IS_WLRANDR_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MSD_TYPE_WLRANDR_MANAGER))
+#define MSD_IS_WLRANDR_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MSD_TYPE_WLRANDR_MANAGER))
+#define MSD_WLRANDR_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MSD_TYPE_WLRANDR_MANAGER, MsdWlrandrManagerClass))
+
+typedef struct MsdWlrandrManagerPrivate MsdWlrandrManagerPrivate;
+
+typedef struct
+{
+ GObject parent;
+ MsdWlrandrManagerPrivate *priv;
+} MsdWlrandrManager;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} MsdWlrandrManagerClass;
+
+GType msd_wlrandr_manager_get_type (void);
+
+MsdWlrandrManager * msd_wlrandr_manager_new (void);
+gboolean msd_wlrandr_manager_start (MsdWlrandrManager *manager,
+ GError **error);
+void msd_wlrandr_manager_stop (MsdWlrandrManager *manager);
+
+G_END_DECLS
+
+#endif /* __MSD_WLRANDR_MANAGER_H */
diff --git a/plugins/wlrandr/msd-wlrandr-plugin.c b/plugins/wlrandr/msd-wlrandr-plugin.c
new file mode 100644
index 0000000..522e122
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-plugin.c
@@ -0,0 +1,105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include "mate-settings-plugin.h"
+#include "msd-wlrandr-plugin.h"
+#include "msd-wlrandr-manager.h"
+
+struct MsdWlrandrPluginPrivate {
+ MsdWlrandrManager *manager;
+};
+
+MATE_SETTINGS_PLUGIN_REGISTER_WITH_PRIVATE (MsdWlrandrPlugin, msd_wlrandr_plugin)
+
+static void
+msd_wlrandr_plugin_init (MsdWlrandrPlugin *plugin)
+{
+ plugin->priv = msd_wlrandr_plugin_get_instance_private (plugin);
+
+ g_debug ("MsdWlrandrPlugin initializing");
+
+ plugin->priv->manager = msd_wlrandr_manager_new ();
+}
+
+static void
+msd_wlrandr_plugin_finalize (GObject *object)
+{
+ MsdWlrandrPlugin *plugin;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (MSD_IS_WLRANDR_PLUGIN (object));
+
+ g_debug ("MsdWlrandrPlugin finalizing");
+
+ plugin = MSD_WLRANDR_PLUGIN (object);
+
+ g_return_if_fail (plugin->priv != NULL);
+
+ if (plugin->priv->manager != NULL) {
+ g_object_unref (plugin->priv->manager);
+ }
+
+ G_OBJECT_CLASS (msd_wlrandr_plugin_parent_class)->finalize (object);
+}
+
+static void
+impl_activate (MateSettingsPlugin *plugin)
+{
+ gboolean res;
+ GError *error;
+
+ g_debug ("Activating wlrandr plugin");
+
+ error = NULL;
+ res = msd_wlrandr_manager_start (MSD_WLRANDR_PLUGIN (plugin)->priv->manager, &error);
+ if (! res) {
+ g_warning ("Unable to start wlrandr manager: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+impl_deactivate (MateSettingsPlugin *plugin)
+{
+ g_debug ("Deactivating wlrandr plugin");
+ msd_wlrandr_manager_stop (MSD_WLRANDR_PLUGIN (plugin)->priv->manager);
+}
+
+static void
+msd_wlrandr_plugin_class_init (MsdWlrandrPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MateSettingsPluginClass *plugin_class = MATE_SETTINGS_PLUGIN_CLASS (klass);
+
+ object_class->finalize = msd_wlrandr_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+}
+
+static void
+msd_wlrandr_plugin_class_finalize (MsdWlrandrPluginClass *klass)
+{
+}
diff --git a/plugins/wlrandr/msd-wlrandr-plugin.h b/plugins/wlrandr/msd-wlrandr-plugin.h
new file mode 100644
index 0000000..3adda68
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr-plugin.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __MSD_WLRANDR_PLUGIN_H__
+#define __MSD_WLRANDR_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gmodule.h>
+
+#include "mate-settings-plugin.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MSD_TYPE_WLRANDR_PLUGIN (msd_wlrandr_plugin_get_type ())
+#define MSD_WLRANDR_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MSD_TYPE_WLRANDR_PLUGIN, MsdWlrandrPlugin))
+#define MSD_WLRANDR_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), MSD_TYPE_WLRANDR_PLUGIN, MsdWlrandrPluginClass))
+#define MSD_IS_WLRANDR_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MSD_TYPE_WLRANDR_PLUGIN))
+#define MSD_IS_WLRANDR_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MSD_TYPE_WLRANDR_PLUGIN))
+#define MSD_WLRANDR_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MSD_TYPE_WLRANDR_PLUGIN, MsdWlrandrPluginClass))
+
+typedef struct MsdWlrandrPluginPrivate MsdWlrandrPluginPrivate;
+
+typedef struct
+{
+ MateSettingsPlugin parent;
+ MsdWlrandrPluginPrivate *priv;
+} MsdWlrandrPlugin;
+
+typedef struct
+{
+ MateSettingsPluginClass parent_class;
+} MsdWlrandrPluginClass;
+
+GType msd_wlrandr_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_mate_settings_plugin (GTypeModule *module);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MSD_WLRANDR_PLUGIN_H__ */
diff --git a/plugins/wlrandr/msd-wlrandr.svg b/plugins/wlrandr/msd-wlrandr.svg
new file mode 100644
index 0000000..87ce536
--- /dev/null
+++ b/plugins/wlrandr/msd-wlrandr.svg
@@ -0,0 +1,491 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="128"
+ height="128"
+ id="svg3304"
+ sodipodi:version="0.32"
+ inkscape:version="0.92.4 (unknown)"
+ sodipodi:docname="msd-xrandr.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="true"
+ version="1.1">
+ <defs
+ id="defs3306">
+ <linearGradient
+ id="linearGradient2804">
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="0"
+ id="stop2806" />
+ <stop
+ id="stop2812"
+ offset="0.5"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop2808" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2804"
+ id="linearGradient2831"
+ gradientUnits="userSpaceOnUse"
+ x1="21.875"
+ y1="48.000977"
+ x2="21.875"
+ y2="40" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2781"
+ id="radialGradient2829"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2,0,0,0.8,36,8.8)"
+ cx="1"
+ cy="44"
+ fx="1"
+ fy="44"
+ r="5" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2781">
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0"
+ id="stop2783" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop2785" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2781"
+ id="radialGradient2827"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2,0,0,0.8,-13,-79.2)"
+ cx="1"
+ cy="44"
+ fx="1"
+ fy="44"
+ r="5" />
+ <linearGradient
+ id="linearGradient5137">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop5139" />
+ <stop
+ style="stop-color:#e6e6e3;stop-opacity:1;"
+ offset="1"
+ id="stop5141" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient6240">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6242" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop6244" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient11400">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop11402" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop11404" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient11400"
+ id="linearGradient11406"
+ x1="23.154902"
+ y1="34.572548"
+ x2="23.529411"
+ y2="40.219608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,0.7954955)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5137"
+ id="linearGradient5147"
+ gradientUnits="userSpaceOnUse"
+ x1="17.247635"
+ y1="6.3760414"
+ x2="39.904388"
+ y2="38.876041"
+ gradientTransform="translate(0,0.7954955)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5137"
+ id="linearGradient5223"
+ gradientUnits="userSpaceOnUse"
+ x1="31.743324"
+ y1="37.842293"
+ x2="31.86105"
+ y2="43.82579"
+ gradientTransform="translate(0,0.7954955)" />
+ <linearGradient
+ id="linearGradient7025"
+ inkscape:collect="always">
+ <stop
+ id="stop7027"
+ offset="0"
+ style="stop-color:#e6ce46;stop-opacity:1" />
+ <stop
+ id="stop7029"
+ offset="1"
+ style="stop-color:#d6ba1c;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient22122"
+ inkscape:collect="always">
+ <stop
+ id="stop22124"
+ offset="0"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ id="stop22126"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient22140">
+ <stop
+ id="stop22142"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" />
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop22148" />
+ <stop
+ id="stop22144"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient22122"
+ id="radialGradient4770"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-1.000001,1.142856,0,-41.10259,45.50001)"
+ cx="7"
+ cy="39.464806"
+ fx="7"
+ fy="39.464806"
+ r="3.5" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient22122"
+ id="radialGradient4772"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-1.000001,1.142856,0,-89.10259,-31.49999)"
+ cx="7"
+ cy="39.464806"
+ fx="7"
+ fy="39.464806"
+ r="3.5" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient22140"
+ id="linearGradient4774"
+ gradientUnits="userSpaceOnUse"
+ x1="18.142136"
+ y1="35"
+ x2="18.142136"
+ y2="42.040661" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient7025"
+ id="linearGradient4776"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(53,1.428571)"
+ x1="13.630114"
+ y1="28.5"
+ x2="25.208096"
+ y2="41.180992" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6240"
+ id="linearGradient4778"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.007843,0.7954955)"
+ x1="20.156862"
+ y1="5.0996137"
+ x2="20.156862"
+ y2="26.039215" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#849193"
+ borderopacity="1.0000000"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568542"
+ inkscape:cx="77.858376"
+ inkscape:cy="41.598514"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:showpageshadow="false"
+ showborder="true"
+ inkscape:window-width="2509"
+ inkscape:window-height="1738"
+ inkscape:window-x="556"
+ inkscape:window-y="140"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:grid-points="true"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata3309">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Change Resolution</dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>display</rdf:li>
+ <rdf:li>resolution</rdf:li>
+ <rdf:li>video</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson
+Luca Ferretti &lt;[email protected]&gt;</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <dc:date />
+ <dc:source>http://www.gnome.org</dc:source>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/SourceCode" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ transform="translate(0,80)">
+ <g
+ id="g100"
+ transform="matrix(2.6666587,0,0,2.6666587,0.12519204,-80.521133)">
+ <g
+ transform="matrix(0.9308511,0,0,1.037397,1.6941489,-1.795056)"
+ style="opacity:0.3"
+ id="g2822">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient2827);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ id="rect1892"
+ width="10"
+ height="8"
+ x="-11"
+ y="-48"
+ transform="scale(-1)" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient2829);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ id="rect2789"
+ width="10"
+ height="8"
+ x="38"
+ y="40" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient2831);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ id="rect2793"
+ width="27"
+ height="8"
+ x="11"
+ y="40" />
+ </g>
+ <rect
+ y="0.79549509"
+ x="0"
+ height="48"
+ width="48"
+ id="rect4784"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccscc"
+ id="path9222"
+ d="m 14.375479,36.328843 c 0,0 1.216876,4.9346 -3.856329,4.980924 -2.4302757,0.0219 -1.9324777,4.035151 -1.9324777,4.035151 l 30.8464667,-0.03137 c 0,0 0.418438,-3.895363 -2.022217,-3.941032 -4.987467,-0.09214 -3.810529,-5.106415 -3.810529,-5.106415 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient5223);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccc"
+ id="rect5040"
+ d="M 4.8886262,4.2739318 H 43.01746 c 2.025119,0 3.500978,1.4276545 3.500978,3.6261408 l 0.01095,26.2038354 c 0,1.692067 -0.528347,2.215229 -2.034656,2.215229 L 3.5320635,36.300202 C 2.3527922,36.271409 1.513468,35.805541 1.4976345,34.280899 L 1.5128113,7.7123281 c 0,-1.7738259 1.5394074,-3.4383963 3.3758149,-3.4383963 z"
+ style="fill:url(#linearGradient5147);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
+ <rect
+ y="8.2973185"
+ x="5.5059438"
+ height="22.996691"
+ width="37.00032"
+ id="rect9208"
+ style="fill:#555753;fill-opacity:1;fill-rule:evenodd;stroke:#2e3436;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 4.8886719,5.1484375 c -1.3073224,0 -2.5,1.2966026 -2.5,2.5644531 L 2.3730469,34.271484 c 0.00626,0.602943 0.1382004,0.797516 0.2773437,0.917969 0.1391434,0.120454 0.4228271,0.22462 0.9023438,0.236328 l 40.9414066,0.01758 c 0.651929,0 0.862707,-0.109199 0.943359,-0.195312 0.08065,-0.08611 0.216797,-0.389657 0.216797,-1.144531 L 45.642578,7.9003906 c 0,-1.8071501 -1.011349,-2.7519531 -2.625,-2.7519531 z"
+ inkscape:href="#rect5040"
+ id="path5145"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ xlink:href="#rect5040"
+ inkscape:original="M 4.8886719 4.2734375 C 3.0522644 4.2734375 1.5136719 5.9390647 1.5136719 7.7128906 L 1.4980469 34.28125 C 1.5138804 35.805892 2.3519787 36.271988 3.53125 36.300781 L 44.494141 36.318359 C 46.00045 36.318359 46.529297 35.795583 46.529297 34.103516 L 46.517578 7.9003906 C 46.517578 5.7019043 45.042697 4.2734375 43.017578 4.2734375 L 4.8886719 4.2734375 z "
+ inkscape:radius="-0.875"
+ sodipodi:type="inkscape:offset" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc"
+ id="path10672"
+ d="m 13.992156,36.834708 c 0.08366,2.172549 -0.283353,2.911443 -1.633624,3.675231 l 23.615402,1.297318 c -0.949019,-1.168628 -2.329411,-2.933334 -1.952941,-4.988235 z"
+ style="opacity:0.6;fill:url(#linearGradient11406);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path6575"
+ d="m 9.9921262,42.291555 c 6.7066928,0.002 27.7967838,0 27.7967838,0"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#8d8d8f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:0.43902438" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 9.647928,43.299429 c 6.706693,0.002 28.719861,0 28.719861,0"
+ id="path8029"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc"
+ id="path4073"
+ d="M 6.031372,8.8268676 V 26.834708 C 22.475817,25.480459 28.630065,16.722289 41.999999,15.807256 L 42,8.7954956 Z"
+ style="opacity:0.4;fill:url(#linearGradient4778);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(-46.77135,-7.3370294)"
+ id="g4754">
+ <g
+ id="g22150"
+ style="opacity:0.3"
+ transform="matrix(0.916667,0,0,0.714282,49.771334,11.132681)">
+ <rect
+ y="35"
+ x="0"
+ height="7"
+ width="4"
+ id="rect22120"
+ style="opacity:1;fill:url(#radialGradient4770);fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1" />
+ <rect
+ transform="scale(-1)"
+ y="-42"
+ x="-48"
+ height="7"
+ width="4"
+ id="rect22134"
+ style="opacity:1;fill:url(#radialGradient4772);fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1" />
+ <rect
+ y="35"
+ x="4"
+ height="7"
+ width="40"
+ id="rect22138"
+ style="opacity:1;fill:url(#linearGradient4774);fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1" />
+ </g>
+ <g
+ id="g4743">
+ <g
+ transform="translate(-5.228666,-6.29601)"
+ id="g10824">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:url(#linearGradient4776);fill-opacity:1;fill-rule:evenodd;stroke:#a38503;stroke-width:1.00000024px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 57.5,14.928571 v 30 h 39 z m 6,13 15,11 h -15 z"
+ id="path4319" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 61.5,44.928571 v -3"
+ id="path4326"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 67.5,44.928571 v -3"
+ id="path4328"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 73.5,44.928571 V 41.964285"
+ id="path4330"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 79.5,44.928571 v -3"
+ id="path4332"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 85.5,44.928571 v -3"
+ id="path4334"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:0.4;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.0000006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 58.500002,16.928575 V 43.928586 H 93.500014 Z"
+ id="path6126" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ transform="translate(47.771334,-4.867439)"
+ sodipodi:nodetypes="cccc"
+ id="path12221"
+ d="M 9.5017474,38.542446 9.4575532,24.532894 28.541592,38.563262 Z"
+ style="opacity:0.4;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/plugins/wlrandr/protocol/wlr-output-management-unstable-v1.xml b/plugins/wlrandr/protocol/wlr-output-management-unstable-v1.xml
new file mode 100644
index 0000000..411e2f0
--- /dev/null
+++ b/plugins/wlrandr/protocol/wlr-output-management-unstable-v1.xml
@@ -0,0 +1,601 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_output_management_unstable_v1">
+ <copyright>
+ Copyright © 2019 Purism SPC
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <description summary="protocol to configure output devices">
+ This protocol exposes interfaces to obtain and modify output device
+ configuration.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <interface name="zwlr_output_manager_v1" version="4">
+ <description summary="output device configuration manager">
+ This interface is a manager that allows reading and writing the current
+ output device configuration.
+
+ Output devices that display pixels (e.g. a physical monitor or a virtual
+ output in a window) are represented as heads. Heads cannot be created nor
+ destroyed by the client, but they can be enabled or disabled and their
+ properties can be changed. Each head may have one or more available modes.
+
+ Whenever a head appears (e.g. a monitor is plugged in), it will be
+ advertised via the head event. Immediately after the output manager is
+ bound, all current heads are advertised.
+
+ Whenever a head's properties change, the relevant wlr_output_head events
+ will be sent. Not all head properties will be sent: only properties that
+ have changed need to.
+
+ Whenever a head disappears (e.g. a monitor is unplugged), a
+ wlr_output_head.finished event will be sent.
+
+ After one or more heads appear, change or disappear, the done event will
+ be sent. It carries a serial which can be used in a create_configuration
+ request to update heads properties.
+
+ The information obtained from this protocol should only be used for output
+ configuration purposes. This protocol is not designed to be a generic
+ output property advertisement protocol for regular clients. Instead,
+ protocols such as xdg-output should be used.
+ </description>
+
+ <event name="head">
+ <description summary="introduce a new head">
+ This event introduces a new head. This happens whenever a new head
+ appears (e.g. a monitor is plugged in) or after the output manager is
+ bound.
+ </description>
+ <arg name="head" type="new_id" interface="zwlr_output_head_v1"/>
+ </event>
+
+ <event name="done">
+ <description summary="sent all information about current configuration">
+ This event is sent after all information has been sent after binding to
+ the output manager object and after any subsequent changes. This applies
+ to child head and mode objects as well. In other words, this event is
+ sent whenever a head or mode is created or destroyed and whenever one of
+ their properties has been changed. Not all state is re-sent each time
+ the current configuration changes: only the actual changes are sent.
+
+ This allows changes to the output configuration to be seen as atomic,
+ even if they happen via multiple events.
+
+ A serial is sent to be used in a future create_configuration request.
+ </description>
+ <arg name="serial" type="uint" summary="current configuration serial"/>
+ </event>
+
+ <request name="create_configuration">
+ <description summary="create a new output configuration object">
+ Create a new output configuration object. This allows to update head
+ properties.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/>
+ <arg name="serial" type="uint"/>
+ </request>
+
+ <request name="stop">
+ <description summary="stop sending events">
+ Indicates the client no longer wishes to receive events for output
+ configuration changes. However the compositor may emit further events,
+ until the finished event is emitted.
+
+ The client must not send any more requests after this one.
+ </description>
+ </request>
+
+ <event name="finished" type="destructor">
+ <description summary="the compositor has finished with the manager">
+ This event indicates that the compositor is done sending manager events.
+ The compositor will destroy the object immediately after sending this
+ event, so it will become invalid and the client should release any
+ resources associated with it.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwlr_output_head_v1" version="4">
+ <description summary="output device">
+ A head is an output device. The difference between a wl_output object and
+ a head is that heads are advertised even if they are turned off. A head
+ object only advertises properties and cannot be used directly to change
+ them.
+
+ A head has some read-only properties: modes, name, description and
+ physical_size. These cannot be changed by clients.
+
+ Other properties can be updated via a wlr_output_configuration object.
+
+ Properties sent via this interface are applied atomically via the
+ wlr_output_manager.done event. No guarantees are made regarding the order
+ in which properties are sent.
+ </description>
+
+ <event name="name">
+ <description summary="head name">
+ This event describes the head name.
+
+ The naming convention is compositor defined, but limited to alphanumeric
+ characters and dashes (-). Each name is unique among all wlr_output_head
+ objects, but if a wlr_output_head object is destroyed the same name may
+ be reused later. The names will also remain consistent across sessions
+ with the same hardware and software configuration.
+
+ Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
+ not assume that the name is a reflection of an underlying DRM
+ connector, X11 connection, etc.
+
+ If the compositor implements the xdg-output protocol and this head is
+ enabled, the xdg_output.name event must report the same name.
+
+ The name event is sent after a wlr_output_head object is created. This
+ event is only sent once per object, and the name does not change over
+ the lifetime of the wlr_output_head object.
+ </description>
+ <arg name="name" type="string"/>
+ </event>
+
+ <event name="description">
+ <description summary="head description">
+ This event describes a human-readable description of the head.
+
+ The description is a UTF-8 string with no convention defined for its
+ contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
+ output via :1'. However, do not assume that the name is a reflection of
+ the make, model, serial of the underlying DRM connector or the display
+ name of the underlying X11 connection, etc.
+
+ If the compositor implements xdg-output and this head is enabled,
+ the xdg_output.description must report the same description.
+
+ The description event is sent after a wlr_output_head object is created.
+ This event is only sent once per object, and the description does not
+ change over the lifetime of the wlr_output_head object.
+ </description>
+ <arg name="description" type="string"/>
+ </event>
+
+ <event name="physical_size">
+ <description summary="head physical size">
+ This event describes the physical size of the head. This event is only
+ sent if the head has a physical size (e.g. is not a projector or a
+ virtual device).
+ </description>
+ <arg name="width" type="int" summary="width in millimeters of the output"/>
+ <arg name="height" type="int" summary="height in millimeters of the output"/>
+ </event>
+
+ <event name="mode">
+ <description summary="introduce a mode">
+ This event introduces a mode for this head. It is sent once per
+ supported mode.
+ </description>
+ <arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/>
+ </event>
+
+ <event name="enabled">
+ <description summary="head is enabled or disabled">
+ This event describes whether the head is enabled. A disabled head is not
+ mapped to a region of the global compositor space.
+
+ When a head is disabled, some properties (current_mode, position,
+ transform and scale) are irrelevant.
+ </description>
+ <arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/>
+ </event>
+
+ <event name="current_mode">
+ <description summary="current mode">
+ This event describes the mode currently in use for this head. It is only
+ sent if the output is enabled.
+ </description>
+ <arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
+ </event>
+
+ <event name="position">
+ <description summary="current position">
+ This events describes the position of the head in the global compositor
+ space. It is only sent if the output is enabled.
+ </description>
+ <arg name="x" type="int"
+ summary="x position within the global compositor space"/>
+ <arg name="y" type="int"
+ summary="y position within the global compositor space"/>
+ </event>
+
+ <event name="transform">
+ <description summary="current transformation">
+ This event describes the transformation currently applied to the head.
+ It is only sent if the output is enabled.
+ </description>
+ <arg name="transform" type="int" enum="wl_output.transform"/>
+ </event>
+
+ <event name="scale">
+ <description summary="current scale">
+ This events describes the scale of the head in the global compositor
+ space. It is only sent if the output is enabled.
+ </description>
+ <arg name="scale" type="fixed"/>
+ </event>
+
+ <event name="finished">
+ <description summary="the head has disappeared">
+ This event indicates that the head is no longer available. The head
+ object becomes inert. Clients should send a destroy request and release
+ any resources associated with it.
+ </description>
+ </event>
+
+ <!-- Version 2 additions -->
+
+ <event name="make" since="2">
+ <description summary="head manufacturer">
+ This event describes the manufacturer of the head.
+
+ This must report the same make as the wl_output interface does in its
+ geometry event.
+
+ Together with the model and serial_number events the purpose is to
+ allow clients to recognize heads from previous sessions and for example
+ load head-specific configurations back.
+
+ It is not guaranteed this event will be ever sent. A reason for that
+ can be that the compositor does not have information about the make of
+ the head or the definition of a make is not sensible in the current
+ setup, for example in a virtual session. Clients can still try to
+ identify the head by available information from other events but should
+ be aware that there is an increased risk of false positives.
+
+ It is not recommended to display the make string in UI to users. For
+ that the string provided by the description event should be preferred.
+ </description>
+ <arg name="make" type="string"/>
+ </event>
+
+ <event name="model" since="2">
+ <description summary="head model">
+ This event describes the model of the head.
+
+ This must report the same model as the wl_output interface does in its
+ geometry event.
+
+ Together with the make and serial_number events the purpose is to
+ allow clients to recognize heads from previous sessions and for example
+ load head-specific configurations back.
+
+ It is not guaranteed this event will be ever sent. A reason for that
+ can be that the compositor does not have information about the model of
+ the head or the definition of a model is not sensible in the current
+ setup, for example in a virtual session. Clients can still try to
+ identify the head by available information from other events but should
+ be aware that there is an increased risk of false positives.
+
+ It is not recommended to display the model string in UI to users. For
+ that the string provided by the description event should be preferred.
+ </description>
+ <arg name="model" type="string"/>
+ </event>
+
+ <event name="serial_number" since="2">
+ <description summary="head serial number">
+ This event describes the serial number of the head.
+
+ Together with the make and model events the purpose is to allow clients
+ to recognize heads from previous sessions and for example load head-
+ specific configurations back.
+
+ It is not guaranteed this event will be ever sent. A reason for that
+ can be that the compositor does not have information about the serial
+ number of the head or the definition of a serial number is not sensible
+ in the current setup. Clients can still try to identify the head by
+ available information from other events but should be aware that there
+ is an increased risk of false positives.
+
+ It is not recommended to display the serial_number string in UI to
+ users. For that the string provided by the description event should be
+ preferred.
+ </description>
+ <arg name="serial_number" type="string"/>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="release" type="destructor" since="3">
+ <description summary="destroy the head object">
+ This request indicates that the client will no longer use this head
+ object.
+ </description>
+ </request>
+
+ <!-- Version 4 additions -->
+
+ <enum name="adaptive_sync_state" since="4">
+ <entry name="disabled" value="0" summary="adaptive sync is disabled"/>
+ <entry name="enabled" value="1" summary="adaptive sync is enabled"/>
+ </enum>
+
+ <event name="adaptive_sync" since="4">
+ <description summary="current adaptive sync state">
+ This event describes whether adaptive sync is currently enabled for
+ the head or not. Adaptive sync is also known as Variable Refresh
+ Rate or VRR.
+ </description>
+ <arg name="state" type="uint" enum="adaptive_sync_state"/>
+ </event>
+ </interface>
+
+ <interface name="zwlr_output_mode_v1" version="3">
+ <description summary="output mode">
+ This object describes an output mode.
+
+ Some heads don't support output modes, in which case modes won't be
+ advertised.
+
+ Properties sent via this interface are applied atomically via the
+ wlr_output_manager.done event. No guarantees are made regarding the order
+ in which properties are sent.
+ </description>
+
+ <event name="size">
+ <description summary="mode size">
+ This event describes the mode size. The size is given in physical
+ hardware units of the output device. This is not necessarily the same as
+ the output size in the global compositor space. For instance, the output
+ may be scaled or transformed.
+ </description>
+ <arg name="width" type="int" summary="width of the mode in hardware units"/>
+ <arg name="height" type="int" summary="height of the mode in hardware units"/>
+ </event>
+
+ <event name="refresh">
+ <description summary="mode refresh rate">
+ This event describes the mode's fixed vertical refresh rate. It is only
+ sent if the mode has a fixed refresh rate.
+ </description>
+ <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
+ </event>
+
+ <event name="preferred">
+ <description summary="mode is preferred">
+ This event advertises this mode as preferred.
+ </description>
+ </event>
+
+ <event name="finished">
+ <description summary="the mode has disappeared">
+ This event indicates that the mode is no longer available. The mode
+ object becomes inert. Clients should send a destroy request and release
+ any resources associated with it.
+ </description>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="release" type="destructor" since="3">
+ <description summary="destroy the mode object">
+ This request indicates that the client will no longer use this mode
+ object.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_output_configuration_v1" version="4">
+ <description summary="output configuration">
+ This object is used by the client to describe a full output configuration.
+
+ First, the client needs to setup the output configuration. Each head can
+ be either enabled (and configured) or disabled. It is a protocol error to
+ send two enable_head or disable_head requests with the same head. It is a
+ protocol error to omit a head in a configuration.
+
+ Then, the client can apply or test the configuration. The compositor will
+ then reply with a succeeded, failed or cancelled event. Finally the client
+ should destroy the configuration object.
+ </description>
+
+ <enum name="error">
+ <entry name="already_configured_head" value="1"
+ summary="head has been configured twice"/>
+ <entry name="unconfigured_head" value="2"
+ summary="head has not been configured"/>
+ <entry name="already_used" value="3"
+ summary="request sent after configuration has been applied or tested"/>
+ </enum>
+
+ <request name="enable_head">
+ <description summary="enable and configure a head">
+ Enable a head. This request creates a head configuration object that can
+ be used to change the head's properties.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1"
+ summary="a new object to configure the head"/>
+ <arg name="head" type="object" interface="zwlr_output_head_v1"
+ summary="the head to be enabled"/>
+ </request>
+
+ <request name="disable_head">
+ <description summary="disable a head">
+ Disable a head.
+ </description>
+ <arg name="head" type="object" interface="zwlr_output_head_v1"
+ summary="the head to be disabled"/>
+ </request>
+
+ <request name="apply">
+ <description summary="apply the configuration">
+ Apply the new output configuration.
+
+ In case the configuration is successfully applied, there is no guarantee
+ that the new output state matches completely the requested
+ configuration. For instance, a compositor might round the scale if it
+ doesn't support fractional scaling.
+
+ After this request has been sent, the compositor must respond with an
+ succeeded, failed or cancelled event. Sending a request that isn't the
+ destructor is a protocol error.
+ </description>
+ </request>
+
+ <request name="test">
+ <description summary="test the configuration">
+ Test the new output configuration. The configuration won't be applied,
+ but will only be validated.
+
+ Even if the compositor succeeds to test a configuration, applying it may
+ fail.
+
+ After this request has been sent, the compositor must respond with an
+ succeeded, failed or cancelled event. Sending a request that isn't the
+ destructor is a protocol error.
+ </description>
+ </request>
+
+ <event name="succeeded">
+ <description summary="configuration changes succeeded">
+ Sent after the compositor has successfully applied the changes or
+ tested them.
+
+ Upon receiving this event, the client should destroy this object.
+
+ If the current configuration has changed, events to describe the changes
+ will be sent followed by a wlr_output_manager.done event.
+ </description>
+ </event>
+
+ <event name="failed">
+ <description summary="configuration changes failed">
+ Sent if the compositor rejects the changes or failed to apply them. The
+ compositor should revert any changes made by the apply request that
+ triggered this event.
+
+ Upon receiving this event, the client should destroy this object.
+ </description>
+ </event>
+
+ <event name="cancelled">
+ <description summary="configuration has been cancelled">
+ Sent if the compositor cancels the configuration because the state of an
+ output changed and the client has outdated information (e.g. after an
+ output has been hotplugged).
+
+ The client can create a new configuration with a newer serial and try
+ again.
+
+ Upon receiving this event, the client should destroy this object.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the output configuration">
+ Using this request a client can tell the compositor that it is not going
+ to use the configuration object anymore. Any changes to the outputs
+ that have not been applied will be discarded.
+
+ This request also destroys wlr_output_configuration_head objects created
+ via this object.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_output_configuration_head_v1" version="4">
+ <description summary="head configuration">
+ This object is used by the client to update a single head's configuration.
+
+ It is a protocol error to set the same property twice.
+ </description>
+
+ <enum name="error">
+ <entry name="already_set" value="1" summary="property has already been set"/>
+ <entry name="invalid_mode" value="2" summary="mode doesn't belong to head"/>
+ <entry name="invalid_custom_mode" value="3" summary="mode is invalid"/>
+ <entry name="invalid_transform" value="4" summary="transform value outside enum"/>
+ <entry name="invalid_scale" value="5" summary="scale negative or zero"/>
+ <entry name="invalid_adaptive_sync_state" value="6" since="4"
+ summary="invalid enum value used in the set_adaptive_sync request"/>
+ </enum>
+
+ <request name="set_mode">
+ <description summary="set the mode">
+ This request sets the head's mode.
+ </description>
+ <arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
+ </request>
+
+ <request name="set_custom_mode">
+ <description summary="set a custom mode">
+ This request assigns a custom mode to the head. The size is given in
+ physical hardware units of the output device. If set to zero, the
+ refresh rate is unspecified.
+
+ It is a protocol error to set both a mode and a custom mode.
+ </description>
+ <arg name="width" type="int" summary="width of the mode in hardware units"/>
+ <arg name="height" type="int" summary="height of the mode in hardware units"/>
+ <arg name="refresh" type="int" summary="vertical refresh rate in mHz or zero"/>
+ </request>
+
+ <request name="set_position">
+ <description summary="set the position">
+ This request sets the head's position in the global compositor space.
+ </description>
+ <arg name="x" type="int" summary="x position in the global compositor space"/>
+ <arg name="y" type="int" summary="y position in the global compositor space"/>
+ </request>
+
+ <request name="set_transform">
+ <description summary="set the transform">
+ This request sets the head's transform.
+ </description>
+ <arg name="transform" type="int" enum="wl_output.transform"/>
+ </request>
+
+ <request name="set_scale">
+ <description summary="set the scale">
+ This request sets the head's scale.
+ </description>
+ <arg name="scale" type="fixed"/>
+ </request>
+
+ <!-- Version 4 additions -->
+
+ <request name="set_adaptive_sync" since="4">
+ <description summary="enable/disable adaptive sync">
+ This request enables/disables adaptive sync. Adaptive sync is also
+ known as Variable Refresh Rate or VRR.
+ </description>
+ <arg name="state" type="uint" enum="zwlr_output_head_v1.adaptive_sync_state"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/plugins/wlrandr/wlrandr.mate-settings-plugin.desktop.in b/plugins/wlrandr/wlrandr.mate-settings-plugin.desktop.in
new file mode 100644
index 0000000..b44192c
--- /dev/null
+++ b/plugins/wlrandr/wlrandr.mate-settings-plugin.desktop.in
@@ -0,0 +1,9 @@
+[MATE Settings Plugin]
+Module=wlrandr
+IAge=0
+WaylandOnly=true
+Name=WLrandr
+Description=Configure Wayland outputs
+Authors=Various
+Copyright=Copyright © 2026 MATE Developers
+Website=
diff --git a/plugins/xrandr/msd-xrandr-manager.c b/plugins/xrandr/msd-xrandr-manager.c
index 78e538f..f49db50 100644
--- a/plugins/xrandr/msd-xrandr-manager.c
+++ b/plugins/xrandr/msd-xrandr-manager.c
@@ -36,7 +36,9 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
#include <gtk/gtk.h>
#include <gio/gio.h>
@@ -1572,6 +1574,22 @@ apply_color_profiles (void)
}
}
+static gboolean
+has_hotplug_mode_update (MateRRScreen *screen)
+{
+ MateRROutput **outputs;
+ int i;
+
+ outputs = mate_rr_screen_list_outputs (screen);
+
+ for (i = 0; outputs[i] != NULL; i++) {
+ if (mate_rr_output_get_hotplug_mode_update (outputs[i]))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static void
on_randr_event (MateRRScreen *screen, gpointer data)
{
@@ -1600,6 +1618,24 @@ on_randr_event (MateRRScreen *screen, gpointer data)
*/
show_timestamps_dialog (manager, "ignoring since change > config");
log_msg (" Ignoring event since change >= config\n");
+ } else if (has_hotplug_mode_update (screen)) {
+ /* An output has the hotplug_mode_update property, which
+ * means that the driver is requesting that the desktop
+ * environment apply a new preferred mode on hotplug events
+ * to handle dynamic guest resizing (e.g. SPICE/QXL in VMs).
+ *
+ * In this case, always auto-configure to use the new
+ * preferred mode rather than applying stored configurations.
+ */
+
+ show_timestamps_dialog (manager, "hotplug_mode_update detected, auto-configuring");
+
+ if (config_timestamp != priv->last_config_timestamp) {
+ priv->last_config_timestamp = config_timestamp;
+ auto_configure_outputs (manager, config_timestamp);
+ log_msg (" Automatically configured outputs for hotplug_mode_update\n");
+ } else
+ log_msg (" Ignored hotplug_mode_update event as timestamps are the same\n");
} else {
/* Here, config_timestamp > change_timestamp. This means that
* the screen got reconfigured because of hotplug/unplug; the X
@@ -2548,6 +2584,20 @@ msd_xrandr_manager_start (MsdXrandrManager *manager,
g_debug ("Starting xrandr manager");
mate_settings_profile_start (NULL);
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "X11 display is required for the xrandr plugin");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+#else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The xrandr plugin was built without X11 support");
+ mate_settings_profile_end (NULL);
+ return FALSE;
+#endif /* GDK_WINDOWING_X11 */
+
log_open ();
log_msg ("------------------------------------------------------------\nSTARTING XRANDR PLUGIN\n");
@@ -2629,6 +2679,11 @@ msd_xrandr_manager_stop (MsdXrandrManager *manager)
g_debug ("Stopping xrandr manager");
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+#endif /* GDK_WINDOWING_X11 */
+
manager->priv->running = FALSE;
display = gdk_display_get_default ();
diff --git a/plugins/xrandr/xrandr.mate-settings-plugin.desktop.in b/plugins/xrandr/xrandr.mate-settings-plugin.desktop.in
index 1c49e42..411489a 100644
--- a/plugins/xrandr/xrandr.mate-settings-plugin.desktop.in
+++ b/plugins/xrandr/xrandr.mate-settings-plugin.desktop.in
@@ -1,6 +1,7 @@
[MATE Settings Plugin]
Module=xrandr
IAge=0
+X11Only=true
Name=XRandR
Description=Set up screen size and rotation settings
Authors=Various
diff --git a/plugins/xsettings/msd-xsettings-manager.c b/plugins/xsettings/msd-xsettings-manager.c
index 86ff376..879aa81 100644
--- a/plugins/xsettings/msd-xsettings-manager.c
+++ b/plugins/xsettings/msd-xsettings-manager.c
@@ -36,7 +36,12 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif /* GDK_WINDOWING_WAYLAND */
#include <gtk/gtk.h>
#include <gio/gio.h>
@@ -108,6 +113,7 @@ struct _TranslationEntry {
struct MateXSettingsManagerPrivate
{
XSettingsManager **managers;
+ Display *xdisplay;
GHashTable *gsettings;
GSettings *gsettings_font;
GSettings *plugin_settings;
@@ -249,6 +255,10 @@ get_window_scale_auto (void)
display = gdk_display_get_default ();
monitor = gdk_display_get_primary_monitor (display);
+ /* If no monitor is available, use the current value as the default */
+ if (monitor == NULL)
+ return 1;
+
/* Use current value as the default */
window_scale = 1;
@@ -322,10 +332,10 @@ get_dpi_from_x_server (void)
if (screen != NULL) {
double width_dpi, height_dpi;
- Screen *xscreen = gdk_x11_screen_get_xscreen (screen);
-
- width_dpi = dpi_from_pixels_and_mm (WidthOfScreen (xscreen), WidthMMOfScreen (xscreen));
- height_dpi = dpi_from_pixels_and_mm (HeightOfScreen (xscreen), HeightMMOfScreen (xscreen));
+ width_dpi = dpi_from_pixels_and_mm (gdk_screen_get_width (screen),
+ gdk_screen_get_width_mm (screen));
+ height_dpi = dpi_from_pixels_and_mm (gdk_screen_get_height (screen),
+ gdk_screen_get_height_mm (screen));
if (width_dpi < DPI_LOW_REASONABLE_VALUE || width_dpi > DPI_HIGH_REASONABLE_VALUE
|| height_dpi < DPI_LOW_REASONABLE_VALUE || height_dpi > DPI_HIGH_REASONABLE_VALUE) {
@@ -628,7 +638,11 @@ xft_settings_set_xresources (MateXftSettings *settings)
/* get existing properties */
dpy = XOpenDisplay (NULL);
- g_return_if_fail (dpy != NULL);
+ if (dpy == NULL) {
+ g_warning ("Unable to open X display for X resources");
+ mate_settings_profile_end (NULL);
+ return;
+ }
add_string = g_string_new (XResourceManagerString (dpy));
g_debug("xft_settings_set_xresources: orig res '%s'", add_string->str);
@@ -868,32 +882,56 @@ terminate_cb (void *data)
gtk_main_quit ();
}
+#ifdef GDK_WINDOWING_WAYLAND
+/* No-op Xlib IO error handler used when running on Wayland: the X connection
+ * only serves XSettings on XWayland, so the daemon should survive an
+ * XWayland restart instead of exiting. */
+static int
+wayland_x_io_error_handler (Display *display)
+{
+ g_warning ("Connection to the XWayland display lost; XSettings for "
+ "X11 applications disabled");
+ return 0;
+}
+#endif /* GDK_WINDOWING_WAYLAND */
+
static gboolean
setup_xsettings_managers (MateXSettingsManager *manager)
{
- GdkDisplay *display;
- gboolean res;
- gboolean terminated;
+ Display *xdisplay;
+ gboolean res;
+ gboolean terminated;
- display = gdk_display_get_default ();
+ xdisplay = XOpenDisplay (NULL);
+ if (xdisplay == NULL) {
+ g_warning ("Unable to open X display; xsettings manager disabled");
+ return FALSE;
+ }
- res = xsettings_manager_check_running (gdk_x11_display_get_xdisplay (display),
- gdk_x11_screen_get_screen_number (gdk_screen_get_default ()));
+#ifdef GDK_WINDOWING_WAYLAND
+ /* When running on Wayland the XSETTINGS manager uses its own Xlib
+ * connection to XWayland, which nothing else watches. Now that we
+ * explicitly flush it, a disappearing XWayland would otherwise kill
+ * the whole daemon through the default Xlib IO error handler. */
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ XSetIOErrorHandler (wayland_x_io_error_handler);
+ }
+#endif /* GDK_WINDOWING_WAYLAND */
+
+ res = xsettings_manager_check_running (xdisplay, DefaultScreen (xdisplay));
if (res) {
g_warning ("You can only run one xsettings manager at a time; exiting");
+ XCloseDisplay (xdisplay);
return FALSE;
}
+ manager->priv->xdisplay = xdisplay;
manager->priv->managers = g_new0 (XSettingsManager *, 2);
terminated = FALSE;
- GdkScreen *screen;
-
- screen = gdk_display_get_default_screen (display);
-
- manager->priv->managers [0] = xsettings_manager_new (gdk_x11_display_get_xdisplay (display),
- gdk_x11_screen_get_screen_number (screen),
+ manager->priv->managers [0] = xsettings_manager_new (xdisplay,
+ DefaultScreen (xdisplay),
terminate_cb,
&terminated);
if (! manager->priv->managers [0]) {
@@ -1011,6 +1049,11 @@ mate_xsettings_manager_stop (MateXSettingsManager *manager)
p->managers = NULL;
}
+ if (p->xdisplay != NULL) {
+ XCloseDisplay (p->xdisplay);
+ p->xdisplay = NULL;
+ }
+
if (p->gsettings != NULL) {
g_hash_table_destroy (p->gsettings);
p->gsettings = NULL;
diff --git a/plugins/xsettings/wm-common.c b/plugins/xsettings/wm-common.c
index 8b5be13..804a727 100644
--- a/plugins/xsettings/wm-common.c
+++ b/plugins/xsettings/wm-common.c
@@ -1,11 +1,13 @@
- #include <X11/Xatom.h>
-#include <gdk/gdkx.h>
-#include <gdk/gdk.h>
#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include "wm-common.h"
+#ifdef GDK_WINDOWING_X11
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdk.h>
+
/* Our WM Window */
static Window wm_window = None;
@@ -112,3 +114,18 @@ wm_common_update_window ()
{
update_wm_window();
}
+
+#else /* !GDK_WINDOWING_X11 */
+
+gchar*
+wm_common_get_current_window_manager (void)
+{
+ return g_strdup (WM_COMMON_UNKNOWN);
+}
+
+void
+wm_common_update_window ()
+{
+}
+
+#endif /* GDK_WINDOWING_X11 */
diff --git a/plugins/xsettings/xsettings-manager.c b/plugins/xsettings/xsettings-manager.c
index d8f5dd5..3aeffa6 100644
--- a/plugins/xsettings/xsettings-manager.c
+++ b/plugins/xsettings/xsettings-manager.c
@@ -188,6 +188,11 @@ xsettings_manager_new (Display *display,
manager->terminate (manager->cb_data);
}
+ /* The display may not be watched by the main loop (e.g. when running on
+ * Wayland the XSettings manager uses its own connection to XWayland), so
+ * make sure the MANAGER message actually reaches the server. */
+ XFlush (manager->display);
+
return manager;
}
@@ -428,6 +433,11 @@ xsettings_manager_notify (XSettingsManager *manager)
free (buffer.data);
+ /* See the note in xsettings_manager_new(): on Wayland nothing else
+ * flushes this connection, so without this clients never see the
+ * property and none of the settings are applied. */
+ XFlush (manager->display);
+
return XSETTINGS_SUCCESS;
}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3a965df..2a1c106 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -25,6 +25,7 @@ data/org.mate.SettingsDaemon.plugins.typing-break.gschema.xml.in
data/org.mate.SettingsDaemon.plugins.xrandr.gschema.xml.in
data/org.mate.SettingsDaemon.plugins.xrdb.gschema.xml.in
data/org.mate.SettingsDaemon.plugins.xsettings.gschema.xml.in
+data/org.mate.SettingsDaemon.plugins.wlrandr.gschema.xml.in
data/mate-settings-daemon.desktop.in.in
mate-settings-daemon/main.c
plugins/a11y-keyboard/a11y-keyboard.mate-settings-plugin.desktop.in
@@ -62,3 +63,5 @@ plugins/xrdb/msd-xrdb-manager.c
plugins/xrdb/xrdb.mate-settings-plugin.desktop.in
plugins/xsettings/msd-xsettings-manager.c
plugins/xsettings/xsettings.mate-settings-plugin.desktop.in
+plugins/wlrandr/msd-wlrandr-manager.c
+plugins/wlrandr/wlrandr.mate-settings-plugin.desktop.in
diff --git a/po/el.po b/po/el.po
index a86b631..9fe14e5 100644
--- a/po/el.po
+++ b/po/el.po
@@ -1,9 +1,6 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR MATE Desktop Environment team
# This file is distributed under the same license as the mate-settings-daemon package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-# Translators:
# alexandros_ <[email protected]>, 2018
# Petros Freris (Πέτρος Φρέρης) <[email protected]>, 2018
# 437c9d6e19936ed69f57bed9e0fe4716, 2018
@@ -11,24 +8,29 @@
# Alexandros Kapetanios <[email protected]>, 2019
# Stefano Karapetsas <[email protected]>, 2019
# Transifex Bot <>, 2026
-#
+# Jim Spentzos <[email protected]>, 2026.
+#
+# Translators:
+#
msgid ""
msgstr ""
"Project-Id-Version: mate-settings-daemon 1.26.0\n"
"Report-Msgid-Bugs-To: https://mate-desktop.org/\n"
"POT-Creation-Date: 2022-10-29 15:35+0200\n"
-"PO-Revision-Date: 2018-03-12 08:16+0000\n"
-"Last-Translator: Transifex Bot <>, 2026\n"
-"Language-Team: Greek (https://app.transifex.com/mate/teams/13566/el/)\n"
+"PO-Revision-Date: 2026-07-29 00:18+0000\n"
+"Last-Translator: Jim Spentzos <[email protected]>\n"
+"Language-Team: Greek <https://translate.fedoraproject.org/projects/mate-"
+"desktop/applications/mate-settings-daemon/el/>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: el\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2026.7.1\n"
#: data/50-accessibility.xml.in:2
msgid "Accessibility"
-msgstr "Προσιτότητα"
+msgstr "Προσβασιμότητα"
#: data/50-accessibility.xml.in:4
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:155
@@ -1600,7 +1602,7 @@ msgstr "Κανονική"
#: plugins/xrandr/msd-xrandr-manager.c:2087
msgid "Left"
-msgstr "Αριστερά"
+msgstr "Αριστερό βέλος"
#: plugins/xrandr/msd-xrandr-manager.c:2088
msgid "Right"
diff --git a/po/ka.po b/po/ka.po
index bdb55ce..36b3339 100644
--- a/po/ka.po
+++ b/po/ka.po
@@ -1,28 +1,30 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR MATE Desktop Environment team
# This file is distributed under the same license as the mate-settings-daemon package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-# Translators:
# Stefano Karapetsas <[email protected]>, 2019
-#
+# Temuri Doghonadze <[email protected]>, 2026.
+#
+# Translators:
+#
msgid ""
msgstr ""
"Project-Id-Version: mate-settings-daemon 1.26.0\n"
"Report-Msgid-Bugs-To: https://mate-desktop.org/\n"
"POT-Creation-Date: 2022-10-29 15:35+0200\n"
-"PO-Revision-Date: 2018-03-12 08:16+0000\n"
-"Last-Translator: Stefano Karapetsas <[email protected]>, 2019\n"
-"Language-Team: Georgian (https://app.transifex.com/mate/teams/13566/ka/)\n"
+"PO-Revision-Date: 2026-07-10 05:17+0000\n"
+"Last-Translator: Temuri Doghonadze <[email protected]>\n"
+"Language-Team: Georgian <https://translate.fedoraproject.org/projects/mate-"
+"desktop/applications/mate-settings-daemon/ka/>\n"
+"Language: ka\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: ka\n"
-"Plural-Forms: nplurals=2; plural=(n!=1);\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2026.7.1.dev0\n"
#: data/50-accessibility.xml.in:2
msgid "Accessibility"
-msgstr "_დამხმარე საშუალებები"
+msgstr "წვდომადობა"
#: data/50-accessibility.xml.in:4
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:155
@@ -65,7 +67,7 @@ msgstr ""
#: data/org.mate.font-rendering.gschema.xml.in:22
msgid "DPI"
-msgstr ""
+msgstr "DPI"
#: data/org.mate.font-rendering.gschema.xml.in:23
msgid ""
@@ -75,7 +77,7 @@ msgstr ""
#: data/org.mate.font-rendering.gschema.xml.in:27
msgid "Antialiasing"
-msgstr ""
+msgstr "მოგლუვება"
#: data/org.mate.font-rendering.gschema.xml.in:28
msgid ""
@@ -86,7 +88,7 @@ msgstr ""
#: data/org.mate.font-rendering.gschema.xml.in:32
msgid "Hinting"
-msgstr ""
+msgstr "ჰინტინგი"
#: data/org.mate.font-rendering.gschema.xml.in:33
msgid ""
@@ -622,7 +624,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:75
msgid "Eject"
-msgstr "მოხსნა"
+msgstr "გამოღება"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:76
msgid "Binding to eject an optical disc."
@@ -882,12 +884,12 @@ msgstr ""
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:619
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:755
msgid "Activate"
-msgstr ""
+msgstr "გააქტიურება"
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:619
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:755
msgid "Deactivate"
-msgstr ""
+msgstr "დეაქტივაცია"
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:674
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:813
@@ -902,7 +904,7 @@ msgstr "ა_რ გამორთო"
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:677
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:816
msgid "_Activate"
-msgstr "გააქ_ტიურება"
+msgstr "გა_აქტიურება"
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:677
#: plugins/a11y-keyboard/msd-a11y-keyboard-manager.c:816
@@ -1003,11 +1005,11 @@ msgstr "ფონი"
#: plugins/background/background.mate-settings-plugin.desktop.in:6
msgid "Background plugin"
-msgstr ""
+msgstr "ფონის დამატება"
#: plugins/clipboard/clipboard.mate-settings-plugin.desktop.in:5
msgid "Clipboard"
-msgstr ""
+msgstr "ბუფერი"
#: plugins/clipboard/clipboard.mate-settings-plugin.desktop.in:6
msgid "Clipboard plugin"
@@ -1039,7 +1041,7 @@ msgstr ""
#: plugins/dummy/dummy.mate-settings-plugin.desktop.in:5
msgid "Dummy"
-msgstr ""
+msgstr "საცობი"
#: plugins/dummy/dummy.mate-settings-plugin.desktop.in:6
msgid "Dummy plugin"
@@ -1047,7 +1049,7 @@ msgstr ""
#: plugins/housekeeping/housekeeping.mate-settings-plugin.desktop.in:5
msgid "Housekeeping"
-msgstr ""
+msgstr "სახლის დალაგება"
#: plugins/housekeeping/housekeeping.mate-settings-plugin.desktop.in:6
msgid ""
@@ -1108,11 +1110,11 @@ msgstr "სანაგვე ყუთის დაცარიელება"
#: plugins/housekeeping/msd-ldsm-dialog.c:444
msgid "Examine…"
-msgstr ""
+msgstr "გამოცდა…"
#: plugins/housekeeping/msd-ldsm-dialog.c:451
msgid "Ignore"
-msgstr ""
+msgstr "იგნორი"
#: plugins/housekeeping/msd-ldsm-trash-empty.c:98
#, c-format
@@ -1136,7 +1138,7 @@ msgstr ""
#. Translators: "Emptying trash from <device>"
#: plugins/housekeeping/msd-ldsm-trash-empty.c:304
msgid "From: "
-msgstr ""
+msgstr "მოწყობილობიდან: "
#: plugins/housekeeping/msd-ldsm-trash-empty.c:356
msgid "Empty all of the items from the trash?"
@@ -1156,7 +1158,7 @@ msgstr "_სანაგვე ყუთის დაცარიელება
#: plugins/keybindings/keybindings.mate-settings-plugin.desktop.in:5
msgid "Keybindings"
-msgstr ""
+msgstr "კლავიატურის მალსახმობები"
#: plugins/keybindings/keybindings.mate-settings-plugin.desktop.in:6
msgid "Keybindings plugin"
@@ -1208,7 +1210,7 @@ msgstr ""
#: plugins/keyboard/msd-keyboard-xkb.c:285
msgid "_Layouts"
-msgstr ""
+msgstr "გან_ლაგებები"
#: plugins/keyboard/msd-keyboard-xkb.c:292
msgid "Keyboard _Preferences"
@@ -1289,7 +1291,7 @@ msgstr ""
#: plugins/mouse/mouse.mate-settings-plugin.desktop.in:5
msgid "Mouse"
-msgstr "თაგვი"
+msgstr "თაგუნა"
#: plugins/mouse/mouse.mate-settings-plugin.desktop.in:6
msgid "Mouse plugin"
@@ -1310,7 +1312,7 @@ msgstr "თაგვის პარამეტრები"
#: plugins/mpris/mpris.mate-settings-plugin.desktop.in:5
msgid "Mpris"
-msgstr ""
+msgstr "Mpris"
#: plugins/mpris/mpris.mate-settings-plugin.desktop.in:6
msgid "Mpris plugin"
@@ -1320,7 +1322,7 @@ msgstr ""
#. Priority=100
#: plugins/rfkill/rfkill.mate-settings-plugin.desktop.in:7
msgid "Rfkill"
-msgstr ""
+msgstr "Rfkill"
#: plugins/rfkill/rfkill.mate-settings-plugin.desktop.in:8
msgid "Rfkill plugin"
@@ -1378,11 +1380,11 @@ msgstr ""
#: plugins/smartcard/msd-smartcard.c:163 plugins/smartcard/msd-smartcard.c:164
msgid "name"
-msgstr ""
+msgstr "სახელი"
#: plugins/smartcard/msd-smartcard.c:168
msgid "Module"
-msgstr ""
+msgstr "მოდული"
#: plugins/smartcard/msd-smartcard.c:169
msgid "smartcard driver"
@@ -1390,7 +1392,7 @@ msgstr ""
#: plugins/smartcard/smartcard.mate-settings-plugin.desktop.in:5
msgid "Smartcard"
-msgstr ""
+msgstr "ჭკვიანი ბარათი"
#: plugins/smartcard/smartcard.mate-settings-plugin.desktop.in:6
msgid "Smartcard plugin"
@@ -1472,15 +1474,15 @@ msgstr ""
#: plugins/xrandr/msd-xrandr-manager.c:2086
msgid "Normal"
-msgstr "ჩვეულებრივი"
+msgstr "ნორმალური"
#: plugins/xrandr/msd-xrandr-manager.c:2087
msgid "Left"
-msgstr "მარცხნივ"
+msgstr "მარცხენა"
#: plugins/xrandr/msd-xrandr-manager.c:2088
msgid "Right"
-msgstr "მარჯვნივ"
+msgstr "მარჯვენა"
#: plugins/xrandr/msd-xrandr-manager.c:2089
msgid "Upside Down"
@@ -1488,7 +1490,7 @@ msgstr "ქვემოდან ზემოთ"
#: plugins/xrandr/msd-xrandr-manager.c:2178
msgid "ON"
-msgstr ""
+msgstr "ჩართ"
#: plugins/xrandr/msd-xrandr-manager.c:2179
msgid "Turn this monitor off"
@@ -1496,7 +1498,7 @@ msgstr ""
#: plugins/xrandr/msd-xrandr-manager.c:2182
msgid "OFF"
-msgstr ""
+msgstr "გამორთ"
#: plugins/xrandr/msd-xrandr-manager.c:2183
msgid "Turn this monitor on"
@@ -1528,7 +1530,7 @@ msgstr ""
#: plugins/xrandr/xrandr.mate-settings-plugin.desktop.in:5
msgid "XRandR"
-msgstr ""
+msgstr "XRandR"
#: plugins/xrandr/xrandr.mate-settings-plugin.desktop.in:6
msgid "Set up screen size and rotation settings"
diff --git a/po/kab.po b/po/kab.po
index a575511..0b75b8c 100644
--- a/po/kab.po
+++ b/po/kab.po
@@ -1,30 +1,33 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR MATE Desktop Environment team
# This file is distributed under the same license as the mate-settings-daemon package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-# Translators:
# Azwaw <[email protected]>, 2020
# ButterflyOfFire, 2025
# Transifex Bot <>, 2026
-#
+# ButterflyOfFire <[email protected]>, 2026.
+#
+# Translators:
+#
msgid ""
msgstr ""
"Project-Id-Version: mate-settings-daemon 1.26.0\n"
"Report-Msgid-Bugs-To: https://mate-desktop.org/\n"
"POT-Creation-Date: 2022-10-29 15:35+0200\n"
-"PO-Revision-Date: 2018-03-12 08:16+0000\n"
-"Last-Translator: Transifex Bot <>, 2026\n"
-"Language-Team: Kabyle (https://app.transifex.com/mate/teams/13566/kab/)\n"
+"PO-Revision-Date: 2026-06-24 19:18+0000\n"
+"Last-Translator: ButterflyOfFire "
+"Language-Team: Kabyle <https://translate.fedoraproject.org/projects/mate-"
+"desktop/applications/mate-settings-daemon/kab/>\n"
+"Language: kab\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: kab\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Weblate 2026.6.1\n"
#: data/50-accessibility.xml.in:2
msgid "Accessibility"
-msgstr ""
+msgstr "Tuffart"
#: data/50-accessibility.xml.in:4
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:155
@@ -88,7 +91,7 @@ msgstr ""
#: data/org.mate.font-rendering.gschema.xml.in:32
msgid "Hinting"
-msgstr ""
+msgstr "Areqqeɛ"
#: data/org.mate.font-rendering.gschema.xml.in:33
msgid ""
@@ -548,7 +551,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:30
msgid "Volume mute"
-msgstr ""
+msgstr "Gzem imesli"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:31
msgid "Binding to mute the system volume."
@@ -556,7 +559,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:35
msgid "Volume down"
-msgstr ""
+msgstr "Ssider-as i ubleɣ"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:36
msgid "Binding to lower the system volume."
@@ -564,7 +567,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:40
msgid "Volume up"
-msgstr ""
+msgstr "Rnu-yas i ubleɣ"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:41
msgid "Binding to raise the system volume."
@@ -604,7 +607,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:65
msgid "Shut down"
-msgstr ""
+msgstr "Sexsi"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:66
msgid "Binding to shut down."
@@ -612,7 +615,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:70
msgid "Log out"
-msgstr ""
+msgstr "Senser tuqqna"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:71
msgid "Binding to log out."
@@ -620,7 +623,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:75
msgid "Eject"
-msgstr ""
+msgstr "Ḍeqqer"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:76
msgid "Binding to eject an optical disc."
@@ -628,7 +631,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:80
msgid "Home folder"
-msgstr ""
+msgstr "Akaram umager"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:81
msgid "Binding to open the Home folder."
@@ -636,7 +639,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:85
msgid "Search"
-msgstr "Rechercher"
+msgstr "Nadi"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:86
msgid "Binding to launch the search tool."
@@ -644,7 +647,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:90
msgid "Launch email client"
-msgstr ""
+msgstr "Sekker amsaɣ n tirawt"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:91
msgid "Binding to launch the email client."
@@ -660,7 +663,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:100
msgid "Lock screen"
-msgstr ""
+msgstr "Lock screen"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:101
msgid "Binding to lock the screen."
@@ -668,7 +671,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:105
msgid "Launch help browser"
-msgstr ""
+msgstr "Serreḥ i iminig n uɛiwen"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:106
msgid "Binding to launch the help browser."
@@ -676,7 +679,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:110
msgid "Launch calculator"
-msgstr ""
+msgstr "Sekker taselkimt"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:111
msgid "Binding to launch the calculator."
@@ -692,7 +695,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:120
msgid "Launch web browser"
-msgstr ""
+msgstr "Sekker iminig n web"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:121
msgid "Binding to launch the web browser."
@@ -700,7 +703,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:125
msgid "Launch media player"
-msgstr ""
+msgstr "Sekker ameɣri n umidya"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:126
msgid "Binding to launch the media player."
@@ -716,7 +719,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:135
msgid "Pause playback"
-msgstr ""
+msgstr "Sesganfi taɣuri"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:136
msgid "Binding to pause playback."
@@ -724,7 +727,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:140
msgid "Stop playback"
-msgstr ""
+msgstr "Ḥbes taɣuri"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:141
msgid "Binding to stop playback."
@@ -732,7 +735,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:145
msgid "Previous track"
-msgstr ""
+msgstr "Tafuɣalt tuzwirt"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:146
msgid "Binding to skip to previous track."
@@ -740,7 +743,7 @@ msgstr ""
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:150
msgid "Next track"
-msgstr ""
+msgstr "Tafuɣalt tuḍfirt"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:151
msgid "Binding to skip to next track."
@@ -821,7 +824,7 @@ msgstr ""
#: mate-settings-daemon/main.c:66
msgid "Enable debugging code"
-msgstr ""
+msgstr "Ulamek aseɣti n tengalt"
#: mate-settings-daemon/main.c:67
msgid "Replace the current daemon"
@@ -989,7 +992,7 @@ msgstr ""
#: plugins/background/background.mate-settings-plugin.desktop.in:5
msgid "Background"
-msgstr "Arrière-plan"
+msgstr "Agilal"
#: plugins/background/background.mate-settings-plugin.desktop.in:6
msgid "Background plugin"
@@ -1360,7 +1363,7 @@ msgstr ""
#: plugins/smartcard/msd-smartcard.c:163 plugins/smartcard/msd-smartcard.c:164
msgid "name"
-msgstr ""
+msgstr "isem"
#: plugins/smartcard/msd-smartcard.c:168
msgid "Module"
@@ -1454,15 +1457,15 @@ msgstr ""
#: plugins/xrandr/msd-xrandr-manager.c:2086
msgid "Normal"
-msgstr ""
+msgstr "Amagnu"
#: plugins/xrandr/msd-xrandr-manager.c:2087
msgid "Left"
-msgstr "Aẓelmaḍ"
+msgstr "Ẓelmaḍ"
#: plugins/xrandr/msd-xrandr-manager.c:2088
msgid "Right"
-msgstr "Ayfus"
+msgstr "Yeffus"
#: plugins/xrandr/msd-xrandr-manager.c:2089
msgid "Upside Down"
diff --git a/po/ko.po b/po/ko.po
index c497913..17f295f 100644
--- a/po/ko.po
+++ b/po/ko.po
@@ -1,29 +1,31 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR MATE Desktop Environment team
# This file is distributed under the same license as the mate-settings-daemon package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-# Translators:
# Alan Lee <[email protected]>, 2018
# D.K Kim <[email protected]>, 2018
# Seong-ho Cho <[email protected]>, 2019
# 1763f4a4329a2376c933c5e919a36cbc_341ca53 <1f851310383599d03339229d772e1290_119292>, 2019
# Stefano Karapetsas <[email protected]>, 2019
# Junghee Lee <[email protected]>, 2021
-#
+# 김인수 <[email protected]>, 2026.
+#
+# Translators:
+#
msgid ""
msgstr ""
"Project-Id-Version: mate-settings-daemon 1.26.0\n"
"Report-Msgid-Bugs-To: https://mate-desktop.org/\n"
"POT-Creation-Date: 2022-10-29 15:35+0200\n"
-"PO-Revision-Date: 2018-03-12 08:16+0000\n"
-"Last-Translator: Junghee Lee <[email protected]>, 2021\n"
-"Language-Team: Korean (https://app.transifex.com/mate/teams/13566/ko/)\n"
+"PO-Revision-Date: 2026-06-16 10:10+0000\n"
+"Last-Translator: 김인수 <[email protected]>\n"
+"Language-Team: Korean <https://translate.fedoraproject.org/projects/mate-"
+"desktop/mate-settings-daemon/ko/>\n"
+"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: ko\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 2026.6.1\n"
#: data/50-accessibility.xml.in:2
msgid "Accessibility"
@@ -1429,7 +1431,7 @@ msgstr ""
#: plugins/sound/sound.mate-settings-plugin.desktop.in:5
msgid "Sound"
-msgstr "사운드"
+msgstr "음향"
#: plugins/sound/sound.mate-settings-plugin.desktop.in:6
msgid "Sound Sample Cache plugin"
diff --git a/po/oc.po b/po/oc.po
index e32b5ca..8bc0e76 100644
--- a/po/oc.po
+++ b/po/oc.po
@@ -1,28 +1,30 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR MATE Desktop Environment team
# This file is distributed under the same license as the mate-settings-daemon package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-# Translators:
-# 48c2de07903ce5a77a8e90265f95c4c2_8909374 <837c8d0ab97b27e737893c19f79bd4de_348476>, 2018
# Stefano Karapetsas <[email protected]>, 2019
# Cédric Valmary <[email protected]>, 2019
# Quentin PAGÈS, 2025
# Transifex Bot <>, 2026
-#
+# Quentin PAGÈS <[email protected]>, 2026.
+#
+# Translators:
+# 48c2de07903ce5a77a8e90265f95c4c2_8909374 <837c8d0ab97b27e737893c19f79bd4de_348476>, 2018
+#
msgid ""
msgstr ""
"Project-Id-Version: mate-settings-daemon 1.26.0\n"
"Report-Msgid-Bugs-To: https://mate-desktop.org/\n"
"POT-Creation-Date: 2022-10-29 15:35+0200\n"
-"PO-Revision-Date: 2018-03-12 08:16+0000\n"
-"Last-Translator: Transifex Bot <>, 2026\n"
-"Language-Team: Occitan (post 1500) (https://app.transifex.com/mate/teams/13566/oc/)\n"
+"PO-Revision-Date: 2026-06-18 15:10+0000\n"
+"Last-Translator: Quentin PAGÈS <[email protected]>\n"
+"Language-Team: Occitan <https://translate.fedoraproject.org/projects/mate-"
+"desktop/mate-settings-daemon/oc/>\n"
+"Language: oc\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: oc\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 2026.6.1\n"
#: data/50-accessibility.xml.in:2
msgid "Accessibility"
@@ -1101,8 +1103,8 @@ msgid ""
"Automatically prunes thumbnail caches and other transient files, and warns "
"about low disk space"
msgstr ""
-"Purga automaticament los caches de las vinhetas e autres fichièrs temporaris"
-" e avertís en cas d'espaci de disc bas"
+"Purga automaticament los caches de las miniaturas e autres fichièrs "
+"temporaris e avertís en cas d'espaci de disc bas"
#: plugins/housekeeping/msd-ldsm-dialog.c:61
msgid "Don't show any warnings again for this file system"
@@ -1447,7 +1449,7 @@ msgstr "pilòt smartcard"
#: plugins/smartcard/smartcard.mate-settings-plugin.desktop.in:5
msgid "Smartcard"
-msgstr "Smartcard"
+msgstr "Carta de piuse"
#: plugins/smartcard/smartcard.mate-settings-plugin.desktop.in:6
msgid "Smartcard plugin"
diff --git a/po/ru.po b/po/ru.po
index ccb0467..b8acfa5 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -1,10 +1,6 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR MATE Desktop Environment team
# This file is distributed under the same license as the mate-settings-daemon package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-# Translators:
-# XRevan86, 2018
# Dmitriy Kulikov <[email protected]>, 2018
# Alex Puts, 2018
# Дмитрий Михирев, 2019
@@ -16,20 +12,28 @@
# Norbert X, 2020
# Павел Коваленко, 2021
# Olesya Gerasimenko <[email protected]>, 2021
-#
+# "Sergey A." <[email protected]>, 2026.
+#
+# Translators:
+# XRevan86, 2018
+#
msgid ""
msgstr ""
"Project-Id-Version: mate-settings-daemon 1.26.0\n"
"Report-Msgid-Bugs-To: https://mate-desktop.org/\n"
"POT-Creation-Date: 2022-10-29 15:35+0200\n"
-"PO-Revision-Date: 2018-03-12 08:16+0000\n"
-"Last-Translator: Olesya Gerasimenko <[email protected]>, 2021\n"
-"Language-Team: Russian (https://app.transifex.com/mate/teams/13566/ru/)\n"
+"PO-Revision-Date: 2026-06-20 17:10+0000\n"
+"Last-Translator: \"Sergey A.\" <[email protected]>\n"
+"Language-Team: Russian <https://translate.fedoraproject.org/projects/mate-"
+"desktop/applications/mate-settings-daemon/ru/>\n"
+"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: ru\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
+"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"X-Generator: Weblate 2026.6.1\n"
#: data/50-accessibility.xml.in:2
msgid "Accessibility"
@@ -501,7 +505,7 @@ msgstr "Активация этого модуля"
#: data/org.mate.SettingsDaemon.plugins.xrdb.gschema.xml.in:6
#: data/org.mate.SettingsDaemon.plugins.xsettings.gschema.xml.in:6
msgid "Whether this plugin would be activated by mate-settings-daemon or not"
-msgstr "Будет ли активирован этот модуль mate-settings-daemon или нет."
+msgstr "Будет ли активирован этот модуль mate-settings-daemon или нет"
#: data/org.mate.SettingsDaemon.plugins.a11y-keyboard.gschema.xml.in:10
#: data/org.mate.SettingsDaemon.plugins.a11y-settings.gschema.xml.in:10
@@ -570,7 +574,7 @@ msgid ""
"issuing a subsequent warning."
msgstr ""
"Укажите процент свободного места, при достижении ниже которого нужно "
-"отобразить повторное предупреждение"
+"отобразить повторное предупреждение."
#: data/org.mate.SettingsDaemon.plugins.housekeeping.gschema.xml.in:25
msgid "Free space no notify threshold"
@@ -660,7 +664,7 @@ msgstr "Бесшумно отключить звук"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:46
msgid "Binding to mute the system volume quietly."
-msgstr "Комбинация клавиш для бесшумного приглушения системной громкости. "
+msgstr "Комбинация клавиш для бесшумного приглушения системной громкости."
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:50
msgid "Turn the volume down quietly"
@@ -692,7 +696,7 @@ msgstr "Выключить"
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:66
msgid "Binding to shut down."
-msgstr "Комбинация клавиш для выключения"
+msgstr "Комбинация клавиш для выключения."
#: data/org.mate.SettingsDaemon.plugins.media-keys.gschema.xml.in:70
msgid "Log out"
@@ -1647,7 +1651,7 @@ msgstr "Открыть диалог конфигурации монитора (�
#: plugins/xrandr/msd-xrandr-manager.c:2358
msgid "Configure display settings"
-msgstr "Настроить параметры "
+msgstr "Настроить параметры"
#: plugins/xrandr/msd-xrandr-manager.c:2418
msgid "Could not apply the stored configuration for monitors"