mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 17:28:49 -10:00
Screen Resolution under GNOME through Mutter's DisplayConfig
On GNOME's Wayland session RetroArch's X11 driver runs on XWayland. XWayland lists every mode at the desktop's current rate - a 240 Hz panel shows no 60 Hz mode - and a "switch" only scales the fullscreen window; the real modes belong to the compositor. The Wayland driver had no resolution list at all. Under Mutter no X11 or Wayland protocol reaches the real modes; its org.gnome.Mutter.DisplayConfig D-Bus interface (what GNOME Settings uses) does. gfx/common/mutter_displayconfig.c (HAVE_DBUS only) reads GetCurrentState and applies ApplyMonitorsConfig: - The list is the target head's real modes, one entry per size and rate (a fixed and a variable rate twin are merged), sorted, the scanned-out mode current. - A switch picks the listed mode of that size nearest the rate, the same rule as the XRandR path; a zero size keeps the current one (the refresh rate autoswitch). It is applied as a temporary configuration: not saved to monitors.xml and no "keep these settings?" prompt. Every other monitor keeps its mode, position, scale and transform; every monitor keeps its colour mode (HDR), RGB range and underscanning; a mirror switches all its monitors or none; heads to the right of or below the switched one move with its new size; the layout mode goes along only where Mutter allows choosing it. - The target head is the connector (wl_output v4 name), else the head under the RetroArch window (XWayland root coordinates are Mutter's logical layout), else the monitor index, else the primary. - Each call opens and closes its own private session bus connection, bounded by a 2 s timeout. With no bus address and no $XDG_RUNTIME_DIR/bus it does nothing, so no bus is ever autolaunched. Backwards compatibility - every existing path behaves as before: - Builds without HAVE_DBUS compile exactly the previous code. - dispserv_x11 only asks Mutter when the output is XWayland's (XWAYLAND<n>); a real X server never talks to D-Bus. On XWayland without Mutter (KDE, sway, ...) XWayland's own list and switch are used as before. Only when Mutter answers is its answer final. - dispserv_wl gets set_resolution, get_resolution_list and get_flags in HAVE_DBUS builds. A new display server flag, DISPSERV_CTX_NO_RESOLUTION_LIST, lets it report "nothing to list right now": set when Mutter was not on the bus at init, and video_display_server_has_resolution_list() then answers false, so the Screen Resolution entry and the refresh rate autoswitch stay off on non-GNOME Wayland exactly as before. No existing server sets the flag, and its bit is not DISPSERV_CTX_MODELINE's. Checked against a real headless Mutter 46: GetCurrentState parses and ApplyMonitorsConfig is accepted. Regression test samples/gfx/mutter_displayconfig (ASan + UBSan, new Linux-samples-gfx step): mock_mutter.py carries Mutter's signatures and its ApplyMonitorsConfig checks (serial, connectors, modes, scales, every lit monitor configured) on two monitors - a 240/120/60 Hz HDR panel and an underscanned one beside it. Cases: no session bus and a bus without Mutter (unavailable, nothing autolaunched); the list, a 60 Hz switch with every other setting carried over, rate only, whole hertz, a smaller mode with the neighbour moved, another head by connector, unlisted size and rate refused without an apply; dispserv_x11 on a real Xorg dummy server with Mutter on the bus (XRandR list, Mutter never asked); dispserv_x11 on Xwayland under a headless Weston with Mutter (Mutter's list and switch) and without (XWayland's list as before); dispserv_wl with Mutter (list, switch, not flagged) and without (flagged, no list, as before).
This commit is contained in:
1 parent
8cee4866b1
commit
17c7c6a393
13 files changed
+2006
-7
No files matched your search
@@ -766,6 +766,33 @@ jobs:
|
||||
pkill openbox || true
|
||||
sudo kill "$XORG_PID" || true
|
||||
|
||||
- name: Screen Resolution through Mutter's DisplayConfig (ASan + UBSan)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Under GNOME's Wayland session the X11 driver runs on
|
||||
# XWayland, which lists every mode at the desktop's current
|
||||
# rate and only scales the window when "switched", and the
|
||||
# Wayland driver had no resolution list at all; Mutter's
|
||||
# org.gnome.Mutter.DisplayConfig D-Bus interface is the only
|
||||
# way to the monitor's real modes. mock_mutter.py carries
|
||||
# Mutter's signatures and its ApplyMonitorsConfig checks; the
|
||||
# test drives gfx/common/mutter_displayconfig.c directly and
|
||||
# through dispserv_x11 (real Xorg dummy server, and Xwayland
|
||||
# under a headless Weston) and dispserv_wl, with and without
|
||||
# Mutter on the bus. Without it every path must behave
|
||||
# exactly as before: no session bus, a bus without Mutter, a
|
||||
# real X server (Mutter is never asked), XWayland's own list,
|
||||
# and no Wayland resolution list.
|
||||
sudo apt-get install -y -qq libdbus-1-dev dbus python3-dbus \
|
||||
python3-gi weston xwayland > /dev/null
|
||||
(cd samples/gfx/mutter_displayconfig \
|
||||
&& make clean all SANITIZER=address,undefined \
|
||||
&& ASAN_OPTIONS=detect_leaks=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
XORG="sudo Xorg" timeout 300 ./run.sh)
|
||||
echo "[pass] mutter_displayconfig_test"
|
||||
|
||||
- name: D3D11 hardware ring under threaded video (mingw + Wine, headless)
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -1444,6 +1444,7 @@ ifeq ($(HAVE_DBUS), 1)
|
||||
LIBS += $(DBUS_LIBS)
|
||||
DEF_FLAGS += $(DBUS_CFLAGS)
|
||||
OBJ += gfx/common/dbus_common.o
|
||||
OBJ += gfx/common/mutter_displayconfig.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_UDEV), 1)
|
||||
|
||||
@@ -0,0 +1,969 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2010-2026 - The RetroArch team
|
||||
*
|
||||
* RetroArch 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch 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 RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
#include <compat/strl.h>
|
||||
|
||||
#include "mutter_displayconfig.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
#define MDC_BUS_NAME "org.gnome.Mutter.DisplayConfig"
|
||||
#define MDC_PATH "/org/gnome/Mutter/DisplayConfig"
|
||||
#define MDC_IFACE "org.gnome.Mutter.DisplayConfig"
|
||||
#define MDC_TIMEOUT_MS 2000
|
||||
|
||||
/* ApplyMonitorsConfig methods */
|
||||
#define MDC_METHOD_TEMPORARY 1
|
||||
|
||||
/* Layout modes: logical monitors sized by mode / scale, or by mode */
|
||||
#define MDC_LAYOUT_LOGICAL 1
|
||||
#define MDC_LAYOUT_PHYSICAL 2
|
||||
|
||||
#define MDC_MAX_SCALES 16
|
||||
#define MDC_MAX_LM_MON 8
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char id[128];
|
||||
int w, h;
|
||||
double refresh;
|
||||
double scales[MDC_MAX_SCALES];
|
||||
int nscales;
|
||||
bool current;
|
||||
bool interlaced;
|
||||
} mdc_mode_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char connector[64];
|
||||
mdc_mode_t *modes;
|
||||
int nmodes;
|
||||
unsigned color_mode;
|
||||
unsigned rgb_range;
|
||||
bool underscanning;
|
||||
bool has_underscanning;
|
||||
bool has_color_mode;
|
||||
bool has_rgb_range;
|
||||
} mdc_monitor_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x, y;
|
||||
double scale;
|
||||
unsigned transform;
|
||||
int mon[MDC_MAX_LM_MON]; /* indices into state->mon */
|
||||
int nmon;
|
||||
bool primary;
|
||||
} mdc_logical_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned serial;
|
||||
unsigned layout_mode;
|
||||
mdc_monitor_t *mon;
|
||||
int nmon;
|
||||
mdc_logical_t *lm;
|
||||
int nlm;
|
||||
bool has_layout_mode;
|
||||
bool supports_layout_change;
|
||||
} mdc_state_t;
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Connection
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
/* The session bus, never an autolaunched one: with no address in the
|
||||
* environment and no $XDG_RUNTIME_DIR/bus socket, libdbus would spawn
|
||||
* a bus of its own, which is nothing Mutter is on. */
|
||||
static DBusConnection *mdc_connect(void)
|
||||
{
|
||||
DBusError err;
|
||||
DBusConnection *conn;
|
||||
const char *addr = getenv("DBUS_SESSION_BUS_ADDRESS");
|
||||
|
||||
if (!addr || !*addr)
|
||||
{
|
||||
char path[512];
|
||||
const char *rt = getenv("XDG_RUNTIME_DIR");
|
||||
if (!rt || !*rt)
|
||||
return NULL;
|
||||
snprintf(path, sizeof(path), "%s/bus", rt);
|
||||
if (access(path, F_OK) != 0)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dbus_error_init(&err);
|
||||
conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
|
||||
if (dbus_error_is_set(&err))
|
||||
dbus_error_free(&err);
|
||||
if (conn)
|
||||
dbus_connection_set_exit_on_disconnect(conn, false);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void mdc_disconnect(DBusConnection *conn)
|
||||
{
|
||||
if (!conn)
|
||||
return;
|
||||
dbus_connection_close(conn);
|
||||
dbus_connection_unref(conn);
|
||||
}
|
||||
|
||||
static bool mdc_has_owner(DBusConnection *conn)
|
||||
{
|
||||
DBusError err;
|
||||
bool has;
|
||||
dbus_error_init(&err);
|
||||
has = dbus_bus_name_has_owner(conn, MDC_BUS_NAME, &err) ? true : false;
|
||||
if (dbus_error_is_set(&err))
|
||||
{
|
||||
dbus_error_free(&err);
|
||||
return false;
|
||||
}
|
||||
return has;
|
||||
}
|
||||
|
||||
bool mutter_displayconfig_available(void)
|
||||
{
|
||||
bool has;
|
||||
DBusConnection *conn = mdc_connect();
|
||||
if (!conn)
|
||||
return false;
|
||||
has = mdc_has_owner(conn);
|
||||
mdc_disconnect(conn);
|
||||
return has;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* GetCurrentState
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static void mdc_state_free(mdc_state_t *st)
|
||||
{
|
||||
int i;
|
||||
if (!st)
|
||||
return;
|
||||
for (i = 0; i < st->nmon; i++)
|
||||
free(st->mon[i].modes);
|
||||
free(st->mon);
|
||||
free(st->lm);
|
||||
free(st);
|
||||
}
|
||||
|
||||
static int mdc_count(DBusMessageIter *array)
|
||||
{
|
||||
int n = 0;
|
||||
DBusMessageIter it;
|
||||
dbus_message_iter_recurse(array, &it);
|
||||
while (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_INVALID)
|
||||
{
|
||||
n++;
|
||||
dbus_message_iter_next(&it);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* One a{sv} entry's variant, if it holds a basic value of type t */
|
||||
static bool mdc_variant_basic(DBusMessageIter *variant, int t, void *out)
|
||||
{
|
||||
DBusMessageIter v;
|
||||
dbus_message_iter_recurse(variant, &v);
|
||||
if (dbus_message_iter_get_arg_type(&v) != t)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&v, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Walks an a{sv}; cb is handed each key and its variant */
|
||||
typedef void (*mdc_prop_cb)(void *ud, const char *key,
|
||||
DBusMessageIter *variant);
|
||||
|
||||
static bool mdc_props(DBusMessageIter *array, mdc_prop_cb cb, void *ud)
|
||||
{
|
||||
DBusMessageIter it;
|
||||
if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY)
|
||||
return false;
|
||||
dbus_message_iter_recurse(array, &it);
|
||||
while (dbus_message_iter_get_arg_type(&it) == DBUS_TYPE_DICT_ENTRY)
|
||||
{
|
||||
DBusMessageIter e;
|
||||
const char *key = NULL;
|
||||
dbus_message_iter_recurse(&it, &e);
|
||||
if (dbus_message_iter_get_arg_type(&e) == DBUS_TYPE_STRING)
|
||||
{
|
||||
dbus_message_iter_get_basic(&e, &key);
|
||||
dbus_message_iter_next(&e);
|
||||
if (dbus_message_iter_get_arg_type(&e) == DBUS_TYPE_VARIANT)
|
||||
cb(ud, key, &e);
|
||||
}
|
||||
dbus_message_iter_next(&it);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mdc_mode_prop(void *ud, const char *key, DBusMessageIter *v)
|
||||
{
|
||||
mdc_mode_t *m = (mdc_mode_t*)ud;
|
||||
dbus_bool_t b = FALSE;
|
||||
if (!strcmp(key, "is-current") && mdc_variant_basic(v, DBUS_TYPE_BOOLEAN, &b))
|
||||
m->current = b ? true : false;
|
||||
else if (!strcmp(key, "is-interlaced") && mdc_variant_basic(v, DBUS_TYPE_BOOLEAN, &b))
|
||||
m->interlaced = b ? true : false;
|
||||
}
|
||||
|
||||
static void mdc_monitor_prop(void *ud, const char *key, DBusMessageIter *v)
|
||||
{
|
||||
mdc_monitor_t *mon = (mdc_monitor_t*)ud;
|
||||
dbus_bool_t b = FALSE;
|
||||
dbus_uint32_t u = 0;
|
||||
if (!strcmp(key, "is-underscanning") && mdc_variant_basic(v, DBUS_TYPE_BOOLEAN, &b))
|
||||
{
|
||||
mon->underscanning = b ? true : false;
|
||||
mon->has_underscanning = true;
|
||||
}
|
||||
else if (!strcmp(key, "color-mode") && mdc_variant_basic(v, DBUS_TYPE_UINT32, &u))
|
||||
{
|
||||
mon->color_mode = u;
|
||||
mon->has_color_mode = true;
|
||||
}
|
||||
else if (!strcmp(key, "rgb-range") && mdc_variant_basic(v, DBUS_TYPE_UINT32, &u))
|
||||
{
|
||||
mon->rgb_range = u;
|
||||
mon->has_rgb_range = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void mdc_global_prop(void *ud, const char *key, DBusMessageIter *v)
|
||||
{
|
||||
mdc_state_t *st = (mdc_state_t*)ud;
|
||||
dbus_bool_t b = FALSE;
|
||||
dbus_uint32_t u = 0;
|
||||
if (!strcmp(key, "layout-mode") && mdc_variant_basic(v, DBUS_TYPE_UINT32, &u))
|
||||
{
|
||||
st->layout_mode = u;
|
||||
st->has_layout_mode = true;
|
||||
}
|
||||
else if (!strcmp(key, "supports-changing-layout-mode")
|
||||
&& mdc_variant_basic(v, DBUS_TYPE_BOOLEAN, &b))
|
||||
st->supports_layout_change = b ? true : false;
|
||||
}
|
||||
|
||||
/* (ssss): the connector is the first string */
|
||||
static bool mdc_spec_connector(DBusMessageIter *spec, char *s, size_t len)
|
||||
{
|
||||
DBusMessageIter f;
|
||||
const char *c = NULL;
|
||||
if (dbus_message_iter_get_arg_type(spec) != DBUS_TYPE_STRUCT)
|
||||
return false;
|
||||
dbus_message_iter_recurse(spec, &f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_STRING)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &c);
|
||||
strlcpy(s, c ? c : "", len);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (siiddada{sv}) */
|
||||
static bool mdc_parse_mode(DBusMessageIter *s, mdc_mode_t *m)
|
||||
{
|
||||
DBusMessageIter f, sc;
|
||||
const char *id = NULL;
|
||||
dbus_int32_t w = 0, h = 0;
|
||||
double refresh = 0.0, pref = 0.0;
|
||||
|
||||
dbus_message_iter_recurse(s, &f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_STRING)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &id);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_INT32)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &w);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_INT32)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &h);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_DOUBLE)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &refresh);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_DOUBLE)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &pref);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_ARRAY)
|
||||
return false;
|
||||
|
||||
memset(m, 0, sizeof(*m));
|
||||
strlcpy(m->id, id ? id : "", sizeof(m->id));
|
||||
m->w = (int)w;
|
||||
m->h = (int)h;
|
||||
m->refresh = refresh;
|
||||
|
||||
dbus_message_iter_recurse(&f, &sc);
|
||||
while (dbus_message_iter_get_arg_type(&sc) == DBUS_TYPE_DOUBLE
|
||||
&& m->nscales < MDC_MAX_SCALES)
|
||||
{
|
||||
dbus_message_iter_get_basic(&sc, &m->scales[m->nscales++]);
|
||||
dbus_message_iter_next(&sc);
|
||||
}
|
||||
dbus_message_iter_next(&f);
|
||||
mdc_props(&f, mdc_mode_prop, m);
|
||||
return m->id[0] && m->w > 0 && m->h > 0;
|
||||
}
|
||||
|
||||
/* ((ssss)a(siiddada{sv})a{sv}) */
|
||||
static bool mdc_parse_monitor(DBusMessageIter *s, mdc_monitor_t *mon)
|
||||
{
|
||||
DBusMessageIter f, modes;
|
||||
int n;
|
||||
|
||||
memset(mon, 0, sizeof(*mon));
|
||||
dbus_message_iter_recurse(s, &f);
|
||||
if (!mdc_spec_connector(&f, mon->connector, sizeof(mon->connector)))
|
||||
return false;
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_ARRAY)
|
||||
return false;
|
||||
|
||||
if ((n = mdc_count(&f)) > 0
|
||||
&& !(mon->modes = (mdc_mode_t*)calloc(n, sizeof(*mon->modes))))
|
||||
return false;
|
||||
dbus_message_iter_recurse(&f, &modes);
|
||||
while (dbus_message_iter_get_arg_type(&modes) == DBUS_TYPE_STRUCT
|
||||
&& mon->nmodes < n)
|
||||
{
|
||||
if (mdc_parse_mode(&modes, &mon->modes[mon->nmodes]))
|
||||
mon->nmodes++;
|
||||
dbus_message_iter_next(&modes);
|
||||
}
|
||||
dbus_message_iter_next(&f);
|
||||
mdc_props(&f, mdc_monitor_prop, mon);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (iiduba(ssss)a{sv}) */
|
||||
static bool mdc_parse_logical(DBusMessageIter *s, const mdc_state_t *st,
|
||||
mdc_logical_t *lm)
|
||||
{
|
||||
DBusMessageIter f, specs;
|
||||
dbus_int32_t x = 0, y = 0;
|
||||
dbus_uint32_t transform = 0;
|
||||
dbus_bool_t primary = FALSE;
|
||||
double scale = 1.0;
|
||||
|
||||
memset(lm, 0, sizeof(*lm));
|
||||
dbus_message_iter_recurse(s, &f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_INT32)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &x);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_INT32)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &y);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_DOUBLE)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &scale);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_UINT32)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &transform);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_BOOLEAN)
|
||||
return false;
|
||||
dbus_message_iter_get_basic(&f, &primary);
|
||||
dbus_message_iter_next(&f);
|
||||
if (dbus_message_iter_get_arg_type(&f) != DBUS_TYPE_ARRAY)
|
||||
return false;
|
||||
|
||||
lm->x = (int)x;
|
||||
lm->y = (int)y;
|
||||
lm->scale = scale > 0.0 ? scale : 1.0;
|
||||
lm->transform = transform;
|
||||
lm->primary = primary ? true : false;
|
||||
|
||||
dbus_message_iter_recurse(&f, &specs);
|
||||
while (dbus_message_iter_get_arg_type(&specs) == DBUS_TYPE_STRUCT)
|
||||
{
|
||||
char c[64];
|
||||
if (mdc_spec_connector(&specs, c, sizeof(c)))
|
||||
{
|
||||
int m;
|
||||
for (m = 0; m < st->nmon && lm->nmon < MDC_MAX_LM_MON; m++)
|
||||
if (!strcmp(st->mon[m].connector, c))
|
||||
{
|
||||
lm->mon[lm->nmon++] = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dbus_message_iter_next(&specs);
|
||||
}
|
||||
return lm->nmon > 0;
|
||||
}
|
||||
|
||||
static mdc_state_t *mdc_get_state(DBusConnection *conn)
|
||||
{
|
||||
DBusMessageIter it, arr;
|
||||
DBusError err;
|
||||
int n;
|
||||
dbus_uint32_t serial = 0;
|
||||
DBusMessage *reply = NULL;
|
||||
mdc_state_t *st = NULL;
|
||||
DBusMessage *msg = dbus_message_new_method_call(MDC_BUS_NAME,
|
||||
MDC_PATH, MDC_IFACE, "GetCurrentState");
|
||||
|
||||
if (!msg)
|
||||
return NULL;
|
||||
dbus_error_init(&err);
|
||||
reply = dbus_connection_send_with_reply_and_block(conn, msg,
|
||||
MDC_TIMEOUT_MS, &err);
|
||||
dbus_message_unref(msg);
|
||||
if (dbus_error_is_set(&err))
|
||||
{
|
||||
RARCH_WARN("[Mutter] GetCurrentState: %s.\n", err.message);
|
||||
dbus_error_free(&err);
|
||||
return NULL;
|
||||
}
|
||||
if (!reply)
|
||||
return NULL;
|
||||
|
||||
if ( !dbus_message_iter_init(reply, &it)
|
||||
|| dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32
|
||||
|| !(st = (mdc_state_t*)calloc(1, sizeof(*st))))
|
||||
goto fail;
|
||||
dbus_message_iter_get_basic(&it, &serial);
|
||||
st->serial = serial;
|
||||
st->layout_mode = MDC_LAYOUT_LOGICAL;
|
||||
|
||||
/* monitors */
|
||||
dbus_message_iter_next(&it);
|
||||
if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_ARRAY)
|
||||
goto fail;
|
||||
if ((n = mdc_count(&it)) > 0
|
||||
&& !(st->mon = (mdc_monitor_t*)calloc(n, sizeof(*st->mon))))
|
||||
goto fail;
|
||||
dbus_message_iter_recurse(&it, &arr);
|
||||
while (dbus_message_iter_get_arg_type(&arr) == DBUS_TYPE_STRUCT
|
||||
&& st->nmon < n)
|
||||
{
|
||||
if (mdc_parse_monitor(&arr, &st->mon[st->nmon]))
|
||||
st->nmon++;
|
||||
else
|
||||
free(st->mon[st->nmon].modes);
|
||||
dbus_message_iter_next(&arr);
|
||||
}
|
||||
|
||||
/* logical monitors */
|
||||
dbus_message_iter_next(&it);
|
||||
if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_ARRAY)
|
||||
goto fail;
|
||||
if ((n = mdc_count(&it)) > 0
|
||||
&& !(st->lm = (mdc_logical_t*)calloc(n, sizeof(*st->lm))))
|
||||
goto fail;
|
||||
dbus_message_iter_recurse(&it, &arr);
|
||||
while (dbus_message_iter_get_arg_type(&arr) == DBUS_TYPE_STRUCT
|
||||
&& st->nlm < n)
|
||||
{
|
||||
if (mdc_parse_logical(&arr, st, &st->lm[st->nlm]))
|
||||
st->nlm++;
|
||||
dbus_message_iter_next(&arr);
|
||||
}
|
||||
|
||||
/* properties */
|
||||
dbus_message_iter_next(&it);
|
||||
mdc_props(&it, mdc_global_prop, st);
|
||||
|
||||
dbus_message_unref(reply);
|
||||
if (!st->nlm)
|
||||
{
|
||||
mdc_state_free(st);
|
||||
return NULL;
|
||||
}
|
||||
return st;
|
||||
|
||||
fail:
|
||||
RARCH_WARN("[Mutter] GetCurrentState reply not understood.\n");
|
||||
dbus_message_unref(reply);
|
||||
mdc_state_free(st);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Head selection and geometry
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static const mdc_mode_t *mdc_current_mode(const mdc_monitor_t *mon)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < mon->nmodes; i++)
|
||||
if (mon->modes[i].current)
|
||||
return &mon->modes[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int mdc_round(double v)
|
||||
{
|
||||
return (int)(v + 0.5);
|
||||
}
|
||||
|
||||
/* A logical monitor's extent in the layout on the given mode */
|
||||
static void mdc_logical_size(const mdc_state_t *st, const mdc_logical_t *lm,
|
||||
const mdc_mode_t *mode, double scale, int *w, int *h)
|
||||
{
|
||||
int mw = mode ? mode->w : 0;
|
||||
int mh = mode ? mode->h : 0;
|
||||
if (lm->transform & 1)
|
||||
{
|
||||
int t = mw;
|
||||
mw = mh;
|
||||
mh = t;
|
||||
}
|
||||
if (st->layout_mode != MDC_LAYOUT_PHYSICAL && scale > 0.0)
|
||||
{
|
||||
mw = mdc_round(mw / scale);
|
||||
mh = mdc_round(mh / scale);
|
||||
}
|
||||
*w = mw;
|
||||
*h = mh;
|
||||
}
|
||||
|
||||
static int mdc_pick_logical(const mdc_state_t *st,
|
||||
const mutter_dc_target_t *t)
|
||||
{
|
||||
int i, j;
|
||||
if (t && t->connector && *t->connector)
|
||||
for (i = 0; i < st->nlm; i++)
|
||||
for (j = 0; j < st->lm[i].nmon; j++)
|
||||
if (!strcmp(st->mon[st->lm[i].mon[j]].connector, t->connector))
|
||||
return i;
|
||||
if (t && t->have_point)
|
||||
for (i = 0; i < st->nlm; i++)
|
||||
{
|
||||
int w, h;
|
||||
const mdc_logical_t *lm = &st->lm[i];
|
||||
mdc_logical_size(st, lm, mdc_current_mode(&st->mon[lm->mon[0]]),
|
||||
lm->scale, &w, &h);
|
||||
if ( t->x >= lm->x && t->x < lm->x + w
|
||||
&& t->y >= lm->y && t->y < lm->y + h)
|
||||
return i;
|
||||
}
|
||||
if (t && t->monitor_index > 0 && t->monitor_index <= st->nlm)
|
||||
return t->monitor_index - 1;
|
||||
for (i = 0; i < st->nlm; i++)
|
||||
if (st->lm[i].primary)
|
||||
return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The listed mode of that size nearest the rate: within half a hertz
|
||||
* or matching the whole-hertz label, progressive first, the current
|
||||
* one on a tie */
|
||||
static const mdc_mode_t *mdc_find_mode(const mdc_monitor_t *mon,
|
||||
int w, int h, int int_hz, double hz)
|
||||
{
|
||||
int i;
|
||||
double best_diff = 0.0;
|
||||
const mdc_mode_t *best = NULL;
|
||||
for (i = 0; i < mon->nmodes; i++)
|
||||
{
|
||||
double diff;
|
||||
const mdc_mode_t *m = &mon->modes[i];
|
||||
if (m->w != w || m->h != h)
|
||||
continue;
|
||||
diff = m->refresh - hz;
|
||||
if (diff < 0.0)
|
||||
diff = -diff;
|
||||
if (diff >= 0.5 && !(int_hz > 0 && (int)(m->refresh + 0.001) == int_hz))
|
||||
continue;
|
||||
if (m->interlaced)
|
||||
diff += 1000.0;
|
||||
if (!best || diff < best_diff || (diff == best_diff && m->current))
|
||||
{
|
||||
best = m;
|
||||
best_diff = diff;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
static bool mdc_mode_has_scale(const mdc_mode_t *m, double scale)
|
||||
{
|
||||
int i;
|
||||
if (!m->nscales)
|
||||
return true;
|
||||
for (i = 0; i < m->nscales; i++)
|
||||
if (m->scales[i] - scale < 0.0001 && scale - m->scales[i] < 0.0001)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The supported scale nearest the one in use */
|
||||
static double mdc_nearest_scale(const mdc_mode_t *m, double scale)
|
||||
{
|
||||
int i;
|
||||
double best = scale, best_diff = -1.0;
|
||||
for (i = 0; i < m->nscales; i++)
|
||||
{
|
||||
double d = m->scales[i] - scale;
|
||||
if (d < 0.0)
|
||||
d = -d;
|
||||
if (best_diff < 0.0 || d < best_diff)
|
||||
{
|
||||
best = m->scales[i];
|
||||
best_diff = d;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Resolution list
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static int mdc_list_qsort(const void *pa, const void *pb)
|
||||
{
|
||||
const video_display_config_t *a = (const video_display_config_t*)pa;
|
||||
const video_display_config_t *b = (const video_display_config_t*)pb;
|
||||
if (a->dims != b->dims)
|
||||
return a->dims < b->dims ? -1 : 1;
|
||||
if (a->interlaced != b->interlaced)
|
||||
return a->interlaced ? 1 : -1;
|
||||
if (a->refreshrate_float != b->refreshrate_float)
|
||||
return a->refreshrate_float < b->refreshrate_float ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum mutter_dc_result mutter_displayconfig_get_resolution_list(
|
||||
const mutter_dc_target_t *target,
|
||||
video_display_config_t **list, unsigned *len)
|
||||
{
|
||||
int i;
|
||||
unsigned j, n = 0;
|
||||
const mdc_monitor_t *mon = NULL;
|
||||
video_display_config_t *conf = NULL;
|
||||
mdc_state_t *st = NULL;
|
||||
DBusConnection *conn = mdc_connect();
|
||||
|
||||
*list = NULL;
|
||||
*len = 0;
|
||||
if (!conn)
|
||||
return MUTTER_DC_UNAVAILABLE;
|
||||
if (!mdc_has_owner(conn) || !(st = mdc_get_state(conn)))
|
||||
{
|
||||
mdc_disconnect(conn);
|
||||
return MUTTER_DC_UNAVAILABLE;
|
||||
}
|
||||
mdc_disconnect(conn);
|
||||
|
||||
mon = &st->mon[st->lm[mdc_pick_logical(st, target)].mon[0]];
|
||||
if (mon->nmodes > 0
|
||||
&& (conf = (video_display_config_t*)calloc(mon->nmodes, sizeof(*conf))))
|
||||
{
|
||||
for (i = 0; i < mon->nmodes; i++)
|
||||
{
|
||||
video_display_config_t e;
|
||||
const mdc_mode_t *m = &mon->modes[i];
|
||||
bool dup = false;
|
||||
|
||||
memset(&e, 0, sizeof(e));
|
||||
e.dims = VIDEO_SCALE_PACK(m->w, m->h);
|
||||
e.bpp = 32;
|
||||
e.refreshrate_float = (float)m->refresh;
|
||||
e.refreshrate = (unsigned)(m->refresh + 0.001);
|
||||
e.interlaced = m->interlaced;
|
||||
e.current = m->current;
|
||||
|
||||
/* The same size and rate twice (a fixed and a variable rate
|
||||
* mode, say) is one choice in the menu */
|
||||
for (j = 0; j < n; j++)
|
||||
{
|
||||
float d = conf[j].refreshrate_float - e.refreshrate_float;
|
||||
if ( conf[j].dims == e.dims
|
||||
&& conf[j].interlaced == e.interlaced
|
||||
&& d < 0.005f && d > -0.005f)
|
||||
{
|
||||
conf[j].current = conf[j].current || e.current;
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup)
|
||||
conf[n++] = e;
|
||||
}
|
||||
qsort(conf, n, sizeof(*conf), mdc_list_qsort);
|
||||
for (j = 0; j < n; j++)
|
||||
conf[j].idx = j;
|
||||
}
|
||||
mdc_state_free(st);
|
||||
|
||||
if (!n)
|
||||
{
|
||||
free(conf);
|
||||
return MUTTER_DC_FAILED;
|
||||
}
|
||||
*list = conf;
|
||||
*len = n;
|
||||
return MUTTER_DC_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* ApplyMonitorsConfig
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static bool mdc_append_dict_bool(DBusMessageIter *dict, const char *key,
|
||||
bool value)
|
||||
{
|
||||
DBusMessageIter e, v;
|
||||
dbus_bool_t b = value ? TRUE : FALSE;
|
||||
return dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &e)
|
||||
&& dbus_message_iter_append_basic(&e, DBUS_TYPE_STRING, &key)
|
||||
&& dbus_message_iter_open_container(&e, DBUS_TYPE_VARIANT, "b", &v)
|
||||
&& dbus_message_iter_append_basic(&v, DBUS_TYPE_BOOLEAN, &b)
|
||||
&& dbus_message_iter_close_container(&e, &v)
|
||||
&& dbus_message_iter_close_container(dict, &e);
|
||||
}
|
||||
|
||||
static bool mdc_append_dict_u32(DBusMessageIter *dict, const char *key,
|
||||
unsigned value)
|
||||
{
|
||||
DBusMessageIter e, v;
|
||||
dbus_uint32_t u = value;
|
||||
return dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &e)
|
||||
&& dbus_message_iter_append_basic(&e, DBUS_TYPE_STRING, &key)
|
||||
&& dbus_message_iter_open_container(&e, DBUS_TYPE_VARIANT, "u", &v)
|
||||
&& dbus_message_iter_append_basic(&v, DBUS_TYPE_UINT32, &u)
|
||||
&& dbus_message_iter_close_container(&e, &v)
|
||||
&& dbus_message_iter_close_container(dict, &e);
|
||||
}
|
||||
|
||||
/* (ssa{sv}): connector, mode id, and the monitor's own settings as it
|
||||
* reported them, so colour mode, RGB range and underscanning carry
|
||||
* over instead of falling back to their defaults */
|
||||
static bool mdc_append_monitor(DBusMessageIter *arr, const mdc_monitor_t *mon,
|
||||
const char *mode_id)
|
||||
{
|
||||
DBusMessageIter s, props;
|
||||
const char *c = mon->connector;
|
||||
if ( !dbus_message_iter_open_container(arr, DBUS_TYPE_STRUCT, NULL, &s)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_STRING, &c)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_STRING, &mode_id)
|
||||
|| !dbus_message_iter_open_container(&s, DBUS_TYPE_ARRAY, "{sv}", &props))
|
||||
return false;
|
||||
if (mon->has_underscanning
|
||||
&& !mdc_append_dict_bool(&props, "enable_underscanning", mon->underscanning))
|
||||
return false;
|
||||
if (mon->has_color_mode
|
||||
&& !mdc_append_dict_u32(&props, "color-mode", mon->color_mode))
|
||||
return false;
|
||||
if (mon->has_rgb_range
|
||||
&& !mdc_append_dict_u32(&props, "rgb-range", mon->rgb_range))
|
||||
return false;
|
||||
return dbus_message_iter_close_container(&s, &props)
|
||||
&& dbus_message_iter_close_container(arr, &s);
|
||||
}
|
||||
|
||||
enum mutter_dc_result mutter_displayconfig_set_resolution(
|
||||
const mutter_dc_target_t *target,
|
||||
unsigned dims, int int_hz, float hz)
|
||||
{
|
||||
int i, j, t;
|
||||
int want_w, want_h, old_w = 0, old_h = 0, new_w = 0, new_h = 0;
|
||||
int *x = NULL, *y = NULL;
|
||||
double new_scale;
|
||||
double want_hz = hz;
|
||||
dbus_uint32_t serial, method = MDC_METHOD_TEMPORARY;
|
||||
enum mutter_dc_result result = MUTTER_DC_FAILED;
|
||||
const mdc_mode_t **chosen = NULL;
|
||||
const mdc_mode_t *cur, *best;
|
||||
const mdc_monitor_t *mon;
|
||||
mdc_logical_t *lm;
|
||||
DBusMessageIter it, lms, props;
|
||||
DBusMessage *msg = NULL;
|
||||
DBusMessage *reply = NULL;
|
||||
mdc_state_t *st = NULL;
|
||||
DBusConnection *conn = mdc_connect();
|
||||
DBusError err;
|
||||
|
||||
dbus_error_init(&err);
|
||||
if (!conn)
|
||||
return MUTTER_DC_UNAVAILABLE;
|
||||
if (!mdc_has_owner(conn) || !(st = mdc_get_state(conn)))
|
||||
{
|
||||
mdc_disconnect(conn);
|
||||
return MUTTER_DC_UNAVAILABLE;
|
||||
}
|
||||
|
||||
t = mdc_pick_logical(st, target);
|
||||
lm = &st->lm[t];
|
||||
mon = &st->mon[lm->mon[0]];
|
||||
cur = mdc_current_mode(mon);
|
||||
want_w = (int)VIDEO_SCALE_W(dims);
|
||||
want_h = (int)VIDEO_SCALE_H(dims);
|
||||
if (!want_w)
|
||||
want_w = cur ? cur->w : 0;
|
||||
if (!want_h)
|
||||
want_h = cur ? cur->h : 0;
|
||||
if (want_hz <= 0.0 && int_hz > 0)
|
||||
want_hz = int_hz;
|
||||
if (want_hz <= 0.0 && cur)
|
||||
want_hz = cur->refresh;
|
||||
|
||||
if (!(best = mdc_find_mode(mon, want_w, want_h, int_hz, want_hz)))
|
||||
{
|
||||
RARCH_WARN("[Mutter] No listed mode %dx%d at %.3f Hz on %s.\n",
|
||||
want_w, want_h, want_hz, mon->connector);
|
||||
goto end;
|
||||
}
|
||||
if (cur && best == cur && lm->nmon == 1)
|
||||
{
|
||||
result = MUTTER_DC_OK;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Mode per monitor of the head: a mirror takes the same size and
|
||||
* rate on each of its monitors, or nothing */
|
||||
if (!(chosen = (const mdc_mode_t**)calloc(st->nmon, sizeof(*chosen)))
|
||||
|| !(x = (int*)calloc(st->nlm, sizeof(*x)))
|
||||
|| !(y = (int*)calloc(st->nlm, sizeof(*y))))
|
||||
goto end;
|
||||
for (i = 0; i < st->nmon; i++)
|
||||
chosen[i] = mdc_current_mode(&st->mon[i]);
|
||||
chosen[lm->mon[0]] = best;
|
||||
for (j = 1; j < lm->nmon; j++)
|
||||
{
|
||||
const mdc_mode_t *mm = mdc_find_mode(&st->mon[lm->mon[j]],
|
||||
best->w, best->h, (int)(best->refresh + 0.001), best->refresh);
|
||||
if (!mm)
|
||||
{
|
||||
RARCH_WARN("[Mutter] Mirror %s has no %dx%d at %.3f Hz.\n",
|
||||
st->mon[lm->mon[j]].connector, best->w, best->h, best->refresh);
|
||||
goto end;
|
||||
}
|
||||
chosen[lm->mon[j]] = mm;
|
||||
}
|
||||
for (i = 0; i < st->nmon; i++)
|
||||
{
|
||||
bool lit = false;
|
||||
int k;
|
||||
for (j = 0; j < st->nlm && !lit; j++)
|
||||
for (k = 0; k < st->lm[j].nmon; k++)
|
||||
if (st->lm[j].mon[k] == i)
|
||||
lit = true;
|
||||
if (lit && !chosen[i])
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Keep the scale unless the new mode cannot take it */
|
||||
new_scale = lm->scale;
|
||||
if (!mdc_mode_has_scale(best, new_scale))
|
||||
new_scale = mdc_nearest_scale(best, new_scale);
|
||||
|
||||
/* Heads to the right of and below this one move with its new
|
||||
* extent, so the layout stays adjacent without overlaps */
|
||||
mdc_logical_size(st, lm, cur, lm->scale, &old_w, &old_h);
|
||||
mdc_logical_size(st, lm, best, new_scale, &new_w, &new_h);
|
||||
for (i = 0; i < st->nlm; i++)
|
||||
{
|
||||
x[i] = st->lm[i].x;
|
||||
y[i] = st->lm[i].y;
|
||||
if (i == t)
|
||||
continue;
|
||||
if (st->lm[i].x >= lm->x + old_w)
|
||||
x[i] += new_w - old_w;
|
||||
if (st->lm[i].y >= lm->y + old_h)
|
||||
y[i] += new_h - old_h;
|
||||
}
|
||||
|
||||
if (!(msg = dbus_message_new_method_call(MDC_BUS_NAME, MDC_PATH,
|
||||
MDC_IFACE, "ApplyMonitorsConfig")))
|
||||
goto end;
|
||||
serial = st->serial;
|
||||
dbus_message_iter_init_append(msg, &it);
|
||||
if ( !dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &serial)
|
||||
|| !dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &method)
|
||||
|| !dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY,
|
||||
"(iiduba(ssa{sv}))", &lms))
|
||||
goto end;
|
||||
for (i = 0; i < st->nlm; i++)
|
||||
{
|
||||
DBusMessageIter s, mons;
|
||||
const mdc_logical_t *l = &st->lm[i];
|
||||
dbus_int32_t lx = x[i];
|
||||
dbus_int32_t ly = y[i];
|
||||
double sc = (i == t) ? new_scale : l->scale;
|
||||
dbus_uint32_t tr = l->transform;
|
||||
dbus_bool_t pr = l->primary ? TRUE : FALSE;
|
||||
if ( !dbus_message_iter_open_container(&lms, DBUS_TYPE_STRUCT, NULL, &s)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_INT32, &lx)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_INT32, &ly)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_DOUBLE, &sc)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_UINT32, &tr)
|
||||
|| !dbus_message_iter_append_basic(&s, DBUS_TYPE_BOOLEAN, &pr)
|
||||
|| !dbus_message_iter_open_container(&s, DBUS_TYPE_ARRAY,
|
||||
"(ssa{sv})", &mons))
|
||||
goto end;
|
||||
for (j = 0; j < l->nmon; j++)
|
||||
if (!mdc_append_monitor(&mons, &st->mon[l->mon[j]],
|
||||
chosen[l->mon[j]]->id))
|
||||
goto end;
|
||||
if ( !dbus_message_iter_close_container(&s, &mons)
|
||||
|| !dbus_message_iter_close_container(&lms, &s))
|
||||
goto end;
|
||||
}
|
||||
if ( !dbus_message_iter_close_container(&it, &lms)
|
||||
|| !dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &props))
|
||||
goto end;
|
||||
/* The layout mode goes along only where Mutter lets it be chosen;
|
||||
* elsewhere naming it is an error even at its current value */
|
||||
if (st->has_layout_mode && st->supports_layout_change
|
||||
&& !mdc_append_dict_u32(&props, "layout-mode", st->layout_mode))
|
||||
goto end;
|
||||
if (!dbus_message_iter_close_container(&it, &props))
|
||||
goto end;
|
||||
|
||||
reply = dbus_connection_send_with_reply_and_block(conn, msg,
|
||||
MDC_TIMEOUT_MS, &err);
|
||||
if (dbus_error_is_set(&err))
|
||||
{
|
||||
RARCH_ERR("[Mutter] Switching %s to %dx%d %.3f Hz failed: %s.\n",
|
||||
mon->connector, best->w, best->h, best->refresh, err.message);
|
||||
goto end;
|
||||
}
|
||||
RARCH_LOG("[Mutter] %s switched to %dx%d %.3f Hz.\n",
|
||||
mon->connector, best->w, best->h, best->refresh);
|
||||
result = MUTTER_DC_OK;
|
||||
|
||||
end:
|
||||
if (dbus_error_is_set(&err))
|
||||
dbus_error_free(&err);
|
||||
if (reply)
|
||||
dbus_message_unref(reply);
|
||||
if (msg)
|
||||
dbus_message_unref(msg);
|
||||
free(chosen);
|
||||
free(x);
|
||||
free(y);
|
||||
mdc_state_free(st);
|
||||
mdc_disconnect(conn);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2010-2026 - The RetroArch team
|
||||
*
|
||||
* RetroArch 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 Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch 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 RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* GNOME's display modes through Mutter's org.gnome.Mutter.DisplayConfig
|
||||
* D-Bus interface - the one GNOME Settings uses. Under GNOME neither
|
||||
* XWayland's RandR (every mode at the desktop's current rate, and a
|
||||
* "switch" that only scales the window) nor any Wayland protocol can
|
||||
* list or change the monitor's real modes; this can.
|
||||
*
|
||||
* Only built with HAVE_DBUS. Every call opens its own private session
|
||||
* bus connection and closes it again, so nothing here outlives a call,
|
||||
* and each call is bounded by a D-Bus timeout. */
|
||||
|
||||
#ifndef __MUTTER_DISPLAYCONFIG_H
|
||||
#define __MUTTER_DISPLAYCONFIG_H
|
||||
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#include "../video_display_server.h"
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* Which head a call is for, most specific first: a connector name
|
||||
* ("DP-1", from wl_output v4), a point in the logical layout (the
|
||||
* centre of the RetroArch window on XWayland), a 1-based logical
|
||||
* monitor index. Anything unset or unmatched falls through to the
|
||||
* primary logical monitor. */
|
||||
typedef struct mutter_dc_target
|
||||
{
|
||||
const char *connector;
|
||||
int x, y;
|
||||
int monitor_index;
|
||||
bool have_point;
|
||||
} mutter_dc_target_t;
|
||||
|
||||
enum mutter_dc_result
|
||||
{
|
||||
/* Mutter is not on the session bus, or no session bus: the caller
|
||||
* does what it did before */
|
||||
MUTTER_DC_UNAVAILABLE = -1,
|
||||
/* Mutter answered and refused (no such mode, invalid layout) */
|
||||
MUTTER_DC_FAILED = 0,
|
||||
MUTTER_DC_OK = 1
|
||||
};
|
||||
|
||||
/* true when org.gnome.Mutter.DisplayConfig has an owner on the
|
||||
* session bus. Never autolaunches a bus. */
|
||||
bool mutter_displayconfig_available(void);
|
||||
|
||||
/* The modes of the target head, as the menu's resolution list: sorted
|
||||
* by size then rate, one entry per size and rate, the scanned-out one
|
||||
* current. *list is malloc'd; the caller frees it. */
|
||||
enum mutter_dc_result mutter_displayconfig_get_resolution_list(
|
||||
const mutter_dc_target_t *target,
|
||||
video_display_config_t **list, unsigned *len);
|
||||
|
||||
/* Switch the target head to the listed mode of that size nearest the
|
||||
* rate; a zero width or height keeps the current one. Applied as a
|
||||
* temporary configuration: not saved, and no "keep these settings?"
|
||||
* prompt. Every other monitor keeps its mode, position, scale,
|
||||
* transform and colour settings. */
|
||||
enum mutter_dc_result mutter_displayconfig_set_resolution(
|
||||
const mutter_dc_target_t *target,
|
||||
unsigned dims, int int_hz, float hz);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <string.h>
|
||||
#include <boolean.h>
|
||||
#include <compat/strl.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
@@ -27,6 +28,10 @@
|
||||
|
||||
#include "../../verbosity.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include "../common/mutter_displayconfig.h"
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct wl_display *dpy;
|
||||
@@ -43,6 +48,11 @@ typedef struct
|
||||
* sysfs node the EDID lives under; Wayland itself has no
|
||||
* protocol for the EDID */
|
||||
char name[32];
|
||||
#ifdef HAVE_DBUS
|
||||
/* GNOME: modes listed and switched through Mutter's D-Bus
|
||||
* interface; no Wayland protocol does this under Mutter */
|
||||
bool mutter;
|
||||
#endif
|
||||
} dispserv_wl_t;
|
||||
|
||||
/* wl_output listener callbacks */
|
||||
@@ -157,6 +167,10 @@ static void *wl_display_server_init(void)
|
||||
wl_display_roundtrip(serv->dpy);
|
||||
wayland_drm_lease_report(serv->dpy);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
serv->mutter = mutter_displayconfig_available();
|
||||
#endif
|
||||
|
||||
return serv;
|
||||
}
|
||||
|
||||
@@ -174,6 +188,66 @@ static void wl_display_server_destroy(void *data)
|
||||
free(serv);
|
||||
}
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
/* The head this client's wl_output names (wl_output v4), else
|
||||
* Mutter's primary */
|
||||
static void wl_display_server_mutter_target(dispserv_wl_t *serv,
|
||||
int monitor_index, mutter_dc_target_t *t)
|
||||
{
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->connector = serv->name[0] ? serv->name : NULL;
|
||||
t->monitor_index = monitor_index;
|
||||
}
|
||||
|
||||
static void *wl_display_server_get_resolution_list(void *data,
|
||||
unsigned *len)
|
||||
{
|
||||
mutter_dc_target_t t;
|
||||
video_display_config_t *list = NULL;
|
||||
dispserv_wl_t *serv = (dispserv_wl_t*)data;
|
||||
|
||||
*len = 0;
|
||||
if (!serv || !serv->mutter)
|
||||
return NULL;
|
||||
wl_display_server_mutter_target(serv, 0, &t);
|
||||
if (mutter_displayconfig_get_resolution_list(&t, &list, len)
|
||||
!= MUTTER_DC_OK)
|
||||
return NULL;
|
||||
return list;
|
||||
}
|
||||
|
||||
static bool wl_display_server_set_resolution(void *data,
|
||||
unsigned dims, int int_hz, float hz, int center,
|
||||
int monitor_index, int xoffset, int padjust)
|
||||
{
|
||||
mutter_dc_target_t t;
|
||||
dispserv_wl_t *serv = (dispserv_wl_t*)data;
|
||||
|
||||
if (!serv || !serv->mutter)
|
||||
return false;
|
||||
wl_display_server_mutter_target(serv, monitor_index, &t);
|
||||
if (mutter_displayconfig_set_resolution(&t, dims, int_hz, hz)
|
||||
!= MUTTER_DC_OK)
|
||||
return false;
|
||||
/* Mutter has applied it by the time it replies; the wl_output
|
||||
* mode events for the new mode are on the socket */
|
||||
if (serv->dpy)
|
||||
wl_display_roundtrip(serv->dpy);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Without Mutter there is nothing to list, and the menu entry and the
|
||||
* refresh rate autoswitch stay off as they always were */
|
||||
static uint32_t wl_display_server_get_flags(void *data)
|
||||
{
|
||||
uint32_t flags = 0;
|
||||
dispserv_wl_t *serv = (dispserv_wl_t*)data;
|
||||
if (!serv || !serv->mutter)
|
||||
BIT32_SET(flags, DISPSERV_CTX_NO_RESOLUTION_LIST);
|
||||
return flags;
|
||||
}
|
||||
#endif
|
||||
|
||||
static float wl_display_server_get_refresh_rate(void *data)
|
||||
{
|
||||
dispserv_wl_t *serv = (dispserv_wl_t*)data;
|
||||
@@ -267,8 +341,13 @@ const video_display_server_t dispserv_wl = {
|
||||
NULL, /* set_window_opacity */
|
||||
NULL, /* set_window_progress */
|
||||
NULL, /* set_window_decorations */
|
||||
#ifdef HAVE_DBUS
|
||||
wl_display_server_set_resolution,
|
||||
wl_display_server_get_resolution_list,
|
||||
#else
|
||||
NULL, /* set_resolution */
|
||||
NULL, /* get_resolution_list */
|
||||
#endif
|
||||
NULL, /* get_output_options */
|
||||
NULL, /* set_screen_orientation */
|
||||
NULL, /* get_screen_orientation */
|
||||
@@ -277,7 +356,11 @@ const video_display_server_t dispserv_wl = {
|
||||
NULL, /* get_video_output_prev */
|
||||
NULL, /* get_video_output_next */
|
||||
wl_display_server_get_metrics,
|
||||
#ifdef HAVE_DBUS
|
||||
wl_display_server_get_flags,
|
||||
#else
|
||||
NULL, /* get_flags */
|
||||
#endif
|
||||
NULL, /* get_scanline */
|
||||
NULL, /* wait_vblank */
|
||||
NULL, /* modeline_list_outputs */
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
#include "../../retroarch.h"
|
||||
#include "../../verbosity.h"
|
||||
#include "edid_sysfs.h"
|
||||
#if defined(HAVE_XRANDR) && defined(HAVE_DBUS)
|
||||
#include "../common/mutter_displayconfig.h"
|
||||
#endif
|
||||
|
||||
enum dispserv_x11_flags
|
||||
{
|
||||
@@ -1438,6 +1441,30 @@ static int x11_res_list_qsort(const void *pa, const void *pb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
/* XWayland lists every mode at the desktop's current rate and only
|
||||
* scales a fullscreen window when "switched"; the real modes belong to
|
||||
* the compositor. Under GNOME, Mutter's D-Bus interface has them. Its
|
||||
* outputs are all named XWAYLAND<n>; a real X server never is, so on
|
||||
* one nothing below talks to D-Bus at all. */
|
||||
static bool x11_res_is_xwayland(const XRROutputInfo *oi)
|
||||
{
|
||||
return oi && oi->name && !strncmp(oi->name, "XWAYLAND", 8);
|
||||
}
|
||||
|
||||
/* The Mutter head to ask about: the one under the RetroArch window
|
||||
* (XWayland's root coordinates are Mutter's logical layout), else the
|
||||
* monitor index, else the primary */
|
||||
static void x11_res_mutter_target(Display *dpy, int monitor_index,
|
||||
mutter_dc_target_t *t)
|
||||
{
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->monitor_index = monitor_index;
|
||||
t->have_point = x11_ml_target_point(dpy,
|
||||
monitor_index > 0 ? monitor_index - 1 : -1, &t->x, &t->y);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *x11_display_server_get_resolution_list(void *data,
|
||||
unsigned *len)
|
||||
{
|
||||
@@ -1457,10 +1484,25 @@ static void *x11_display_server_get_resolution_list(void *data,
|
||||
if ( (res = XRRGetScreenResourcesCurrent(dpy,
|
||||
RootWindow(dpy, DefaultScreen(dpy))))
|
||||
&& x11_res_pick_output(dpy, res,
|
||||
RootWindow(dpy, DefaultScreen(dpy)), 0, &oi, &ci)
|
||||
&& oi->nmode > 0
|
||||
&& (conf = (video_display_config_t*)
|
||||
calloc(oi->nmode, sizeof(*conf))))
|
||||
RootWindow(dpy, DefaultScreen(dpy)), 0, &oi, &ci))
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
if (x11_res_is_xwayland(oi))
|
||||
{
|
||||
mutter_dc_target_t t;
|
||||
x11_res_mutter_target(dpy, 0, &t);
|
||||
/* Mutter's answer stands, list or none; without Mutter the
|
||||
* XWayland list below is what there is */
|
||||
if (mutter_displayconfig_get_resolution_list(&t, &conf, &n)
|
||||
!= MUTTER_DC_UNAVAILABLE)
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
if (oi->nmode > 0)
|
||||
conf = (video_display_config_t*)calloc(oi->nmode, sizeof(*conf));
|
||||
}
|
||||
|
||||
if (conf)
|
||||
{
|
||||
for (i = 0; i < oi->nmode; i++)
|
||||
{
|
||||
@@ -1504,6 +1546,9 @@ static void *x11_display_server_get_resolution_list(void *data,
|
||||
conf[j].idx = j;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
done:
|
||||
#endif
|
||||
if (ci)
|
||||
XRRFreeCrtcInfo(ci);
|
||||
if (oi)
|
||||
@@ -1561,6 +1606,24 @@ static bool x11_display_server_set_resolution(void *data,
|
||||
|| !x11_res_pick_output(dpy, res, root, monitor_index, &oi, &ci))
|
||||
goto end;
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
if (x11_res_is_xwayland(oi))
|
||||
{
|
||||
mutter_dc_target_t t;
|
||||
enum mutter_dc_result r;
|
||||
x11_res_mutter_target(dpy, monitor_index, &t);
|
||||
r = mutter_displayconfig_set_resolution(&t, dims, int_hz, hz);
|
||||
if (r != MUTTER_DC_UNAVAILABLE)
|
||||
{
|
||||
ok = (r == MUTTER_DC_OK);
|
||||
/* XWayland follows the compositor a moment later; the kept
|
||||
* rate must not outlive the mode it was read from */
|
||||
x11_refresh_invalidate();
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
cur_mi = x11_res_find_mode(res, ci->mode);
|
||||
want_w = VIDEO_SCALE_W(dims);
|
||||
want_h = VIDEO_SCALE_H(dims);
|
||||
|
||||
@@ -34,7 +34,11 @@ enum display_server_flags
|
||||
DISPSERV_CTX_FLAGS_NONE = 0,
|
||||
/* The server can apply a video_modeline_t through its modeline_*
|
||||
* ops (the bit the CRT consumer and the menu look for). */
|
||||
DISPSERV_CTX_MODELINE
|
||||
DISPSERV_CTX_MODELINE,
|
||||
/* The server has resolution list callbacks but nothing to list them
|
||||
* from right now (the Wayland server away from GNOME): the menu and
|
||||
* the refresh rate autoswitch treat it as having none. */
|
||||
DISPSERV_CTX_NO_RESOLUTION_LIST
|
||||
};
|
||||
|
||||
/* One-cycle alias for the bit's previous name. */
|
||||
|
||||
+10
-2
@@ -1858,8 +1858,16 @@ int video_display_server_get_edid(uint8_t *out, size_t max)
|
||||
|
||||
bool video_display_server_has_resolution_list(void)
|
||||
{
|
||||
return (current_display_server
|
||||
&& current_display_server->get_resolution_list);
|
||||
video_driver_state_t *video_st = &video_driver_st;
|
||||
if ( !current_display_server
|
||||
|| !current_display_server->get_resolution_list)
|
||||
return false;
|
||||
if ( current_display_server->get_flags
|
||||
&& BIT32_GET(current_display_server->get_flags(
|
||||
video_st->current_display_server_data),
|
||||
DISPSERV_CTX_NO_RESOLUTION_LIST))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void *video_display_server_get_resolution_list(unsigned *size)
|
||||
|
||||
@@ -388,6 +388,7 @@ VIDEO CONTEXT
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include "../gfx/common/dbus_common.c"
|
||||
#include "../gfx/common/mutter_displayconfig.c"
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_OPENGLES
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
TARGET := mutter_displayconfig_test
|
||||
|
||||
# Links the real Mutter DisplayConfig client and the real X11 and
|
||||
# Wayland display servers against libdbus, libX11/libXrandr and
|
||||
# libwayland-client. run.sh supplies the session bus, the mock Mutter
|
||||
# and the X/Wayland servers each case needs.
|
||||
REPO_ROOT := ../../..
|
||||
LRC := $(REPO_ROOT)/libretro-common
|
||||
|
||||
SOURCES := mutter_displayconfig_test.c \
|
||||
$(REPO_ROOT)/gfx/common/mutter_displayconfig.c \
|
||||
$(REPO_ROOT)/gfx/display_servers/dispserv_x11.c \
|
||||
$(REPO_ROOT)/gfx/display_servers/dispserv_wl.c \
|
||||
$(REPO_ROOT)/gfx/common/xinerama_common.c \
|
||||
$(LRC)/compat/compat_strl.c
|
||||
|
||||
CFLAGS += -Wall -std=gnu99 -g -O0 \
|
||||
-DHAVE_DBUS -DHAVE_XRANDR -DHAVE_XINERAMA -DHAVE_WAYLAND \
|
||||
-DTEST_X11 -DTEST_WL \
|
||||
-I$(REPO_ROOT) -I$(LRC)/include \
|
||||
$(shell pkg-config --cflags dbus-1 wayland-client)
|
||||
|
||||
LDLIBS += $(shell pkg-config --libs dbus-1 wayland-client) \
|
||||
-lXrandr -lXinerama -lX11 -lm
|
||||
|
||||
# Objects under obj/, never next to the sources: other samples build
|
||||
# some of the same files with other flags.
|
||||
OBJS := $(addprefix obj/,$(notdir $(SOURCES:.c=.o)))
|
||||
VPATH := $(sort $(dir $(SOURCES)))
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
obj/%.o: %.c
|
||||
@mkdir -p obj
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -rf $(TARGET) obj
|
||||
|
||||
.PHONY: clean
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
# A stand-in for GNOME's org.gnome.Mutter.DisplayConfig, with the
|
||||
# signatures of Mutter's own interface XML and the checks
|
||||
# ApplyMonitorsConfig makes before it touches a monitor: the serial
|
||||
# must be the current one, the method known, every connector known,
|
||||
# every mode one of that connector's, every scale one of that mode's,
|
||||
# and every lit monitor configured. A config that passes becomes the
|
||||
# current state (serial bumped), so a second call sees the first.
|
||||
#
|
||||
# Two monitors side by side: DP-1, primary, a 2560x1440 panel at
|
||||
# 240/120/60 Hz (plus 1920x1080) in HDR colour mode, and HDMI-1 to its
|
||||
# right with underscanning on. DP-1 lists its 240 Hz mode twice, fixed
|
||||
# and variable rate, the way Mutter 47+ does.
|
||||
#
|
||||
# Test-only methods on the same interface: LastApply (what the last
|
||||
# accepted ApplyMonitorsConfig asked for, as one line), Calls (how many
|
||||
# GetCurrentState / ApplyMonitorsConfig calls came in) and Reset.
|
||||
|
||||
import sys
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.mainloop.glib
|
||||
from gi.repository import GLib
|
||||
|
||||
NAME = "org.gnome.Mutter.DisplayConfig"
|
||||
PATH = "/org/gnome/Mutter/DisplayConfig"
|
||||
|
||||
def mode(mid, w, h, hz, scales, current=False):
|
||||
return {"id": mid, "w": w, "h": h, "hz": hz, "scales": scales,
|
||||
"current": current}
|
||||
|
||||
def initial():
|
||||
return {
|
||||
"serial": 7,
|
||||
"monitors": [
|
||||
{"connector": "DP-1", "props": {"color-mode": dbus.UInt32(1),
|
||||
"supported-color-modes":
|
||||
dbus.Array([dbus.UInt32(0), dbus.UInt32(1)], signature="u")},
|
||||
"modes": [
|
||||
mode("[email protected]", 2560, 1440, 239.913, [1.0, 1.25, 1.5, 2.0], True),
|
||||
mode("[email protected]+vrr", 2560, 1440, 239.913, [1.0, 1.25, 1.5, 2.0]),
|
||||
mode("[email protected]", 2560, 1440, 119.877, [1.0, 1.25, 1.5, 2.0]),
|
||||
mode("[email protected]", 2560, 1440, 59.951, [1.0, 1.25, 1.5, 2.0]),
|
||||
mode("[email protected]", 1920, 1080, 60.0, [1.0, 1.25]),
|
||||
]},
|
||||
{"connector": "HDMI-1", "props": {"is-underscanning": True},
|
||||
"modes": [
|
||||
mode("[email protected]", 1920, 1080, 60.0, [1.0], True),
|
||||
mode("[email protected]", 1280, 720, 60.0, [1.0]),
|
||||
]},
|
||||
],
|
||||
# x, y, scale, transform, primary, connectors
|
||||
"logical": [
|
||||
[0, 0, 1.0, 0, True, ["DP-1"]],
|
||||
[2560, 0, 1.0, 0, False, ["HDMI-1"]],
|
||||
],
|
||||
}
|
||||
|
||||
class DisplayConfig(dbus.service.Object):
|
||||
def __init__(self, bus):
|
||||
super().__init__(bus, PATH)
|
||||
self.st = initial()
|
||||
self.last = ""
|
||||
self.n_get = 0
|
||||
self.n_apply = 0
|
||||
|
||||
@dbus.service.method(NAME, in_signature="",
|
||||
out_signature="ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv}")
|
||||
def GetCurrentState(self):
|
||||
self.n_get += 1
|
||||
mons = []
|
||||
for m in self.st["monitors"]:
|
||||
modes = []
|
||||
for md in m["modes"]:
|
||||
props = {"is-current": dbus.Boolean(md["current"])}
|
||||
if md["current"]:
|
||||
props["is-preferred"] = dbus.Boolean(True)
|
||||
modes.append((md["id"], dbus.Int32(md["w"]), dbus.Int32(md["h"]),
|
||||
dbus.Double(md["hz"]), dbus.Double(1.0),
|
||||
dbus.Array([dbus.Double(s) for s in md["scales"]], signature="d"),
|
||||
dbus.Dictionary(props, signature="sv")))
|
||||
spec = (m["connector"], "VND", "Panel", "0001")
|
||||
mprops = dict(m["props"])
|
||||
mprops["display-name"] = m["connector"]
|
||||
mons.append((spec, dbus.Array(modes, signature="(siiddada{sv})"),
|
||||
dbus.Dictionary(mprops, signature="sv")))
|
||||
lms = []
|
||||
for x, y, sc, tr, pr, cons in self.st["logical"]:
|
||||
specs = [(c, "VND", "Panel", "0001") for c in cons]
|
||||
lms.append((dbus.Int32(x), dbus.Int32(y), dbus.Double(sc), dbus.UInt32(tr),
|
||||
dbus.Boolean(pr), dbus.Array(specs, signature="(ssss)"),
|
||||
dbus.Dictionary({}, signature="sv")))
|
||||
props = {"layout-mode": dbus.UInt32(1),
|
||||
"supports-changing-layout-mode": dbus.Boolean(True)}
|
||||
return (dbus.UInt32(self.st["serial"]),
|
||||
dbus.Array(mons, signature="((ssss)a(siiddada{sv})a{sv})"),
|
||||
dbus.Array(lms, signature="(iiduba(ssss)a{sv})"),
|
||||
dbus.Dictionary(props, signature="sv"))
|
||||
|
||||
@dbus.service.method(NAME, in_signature="uua(iiduba(ssa{sv}))a{sv}",
|
||||
out_signature="")
|
||||
def ApplyMonitorsConfig(self, serial, method, logical, props):
|
||||
self.n_apply += 1
|
||||
err = "org.freedesktop.DBus.Error.AccessDenied"
|
||||
if serial != self.st["serial"]:
|
||||
raise dbus.exceptions.DBusException("The requested configuration is based on stale information", name=err)
|
||||
if method not in (0, 1, 2):
|
||||
raise dbus.exceptions.DBusException("Invalid method", name="org.freedesktop.DBus.Error.InvalidArgs")
|
||||
mons = {m["connector"]: m for m in self.st["monitors"]}
|
||||
seen = set()
|
||||
new_logical = []
|
||||
chosen = {}
|
||||
parts = []
|
||||
for x, y, sc, tr, pr, monspecs in logical:
|
||||
cons = []
|
||||
mparts = []
|
||||
for con, mid, mprops in monspecs:
|
||||
if con not in mons:
|
||||
raise dbus.exceptions.DBusException("Invalid connector " + con, name="org.freedesktop.DBus.Error.InvalidArgs")
|
||||
md = [md for md in mons[con]["modes"] if md["id"] == mid]
|
||||
if not md:
|
||||
raise dbus.exceptions.DBusException("Invalid mode " + mid, name="org.freedesktop.DBus.Error.InvalidArgs")
|
||||
if not any(abs(s - sc) < 0.0001 for s in md[0]["scales"]):
|
||||
raise dbus.exceptions.DBusException("Scale not supported", name="org.freedesktop.DBus.Error.InvalidArgs")
|
||||
seen.add(con)
|
||||
cons.append(con)
|
||||
chosen[con] = mid
|
||||
mparts.append("%s=%s{%s}" % (con, mid, ",".join(
|
||||
"%s:%s" % (k, (str(bool(v)).lower() if isinstance(v, dbus.Boolean) else int(v)))
|
||||
for k, v in sorted(mprops.items()))))
|
||||
new_logical.append([int(x), int(y), float(sc), int(tr), bool(pr), cons])
|
||||
parts.append("%d,%d,%.2f,%d,%s[%s]" % (x, y, sc, tr, "p" if pr else "-", ";".join(mparts)))
|
||||
for m in self.st["monitors"]:
|
||||
if m["connector"] not in seen:
|
||||
raise dbus.exceptions.DBusException("Monitor " + m["connector"] + " not configured", name="org.freedesktop.DBus.Error.InvalidArgs")
|
||||
gp = ",".join("%s:%d" % (k, int(v)) for k, v in sorted(props.items()))
|
||||
self.last = "method=%d serial=%d lm=%s props={%s}" % (method, serial, " ".join(parts), gp)
|
||||
if method == 0:
|
||||
return
|
||||
for con, mid in chosen.items():
|
||||
for md in mons[con]["modes"]:
|
||||
md["current"] = (md["id"] == mid)
|
||||
self.st["logical"] = new_logical
|
||||
self.st["serial"] += 1
|
||||
|
||||
@dbus.service.method(NAME, in_signature="", out_signature="s")
|
||||
def LastApply(self):
|
||||
return self.last
|
||||
|
||||
@dbus.service.method(NAME, in_signature="", out_signature="uu")
|
||||
def Calls(self):
|
||||
return (dbus.UInt32(self.n_get), dbus.UInt32(self.n_apply))
|
||||
|
||||
@dbus.service.method(NAME, in_signature="", out_signature="")
|
||||
def Reset(self):
|
||||
self.st = initial()
|
||||
self.last = ""
|
||||
self.n_get = 0
|
||||
self.n_apply = 0
|
||||
|
||||
def main():
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
bus = dbus.SessionBus()
|
||||
obj = DisplayConfig(bus)
|
||||
# The BusName object owns the name; dropped, it releases it
|
||||
name = dbus.service.BusName(NAME, bus)
|
||||
print("mock mutter ready", flush=True)
|
||||
GLib.MainLoop().run()
|
||||
del obj, name
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,458 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (mutter_displayconfig_test.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* Screen Resolution under GNOME, through Mutter's DisplayConfig D-Bus
|
||||
* interface, and proof that nothing changes anywhere else.
|
||||
*
|
||||
* On GNOME's Wayland session RetroArch's X11 driver runs on XWayland,
|
||||
* which lists every mode at the desktop's current rate (a 240 Hz panel
|
||||
* shows no 60 Hz mode) and "switches" by scaling the window; the
|
||||
* Wayland driver had no resolution list at all. Mutter's D-Bus
|
||||
* interface is the only way to the real modes, and gfx/common/
|
||||
* mutter_displayconfig.c now uses it from both drivers.
|
||||
*
|
||||
* Run by run.sh, which starts a private session bus and, per case,
|
||||
* mock_mutter.py (Mutter's signatures and its ApplyMonitorsConfig
|
||||
* checks), a real Xorg dummy server, or Xwayland under a headless
|
||||
* Weston. One case per invocation:
|
||||
*
|
||||
* nobus no session bus: everything unavailable, nothing
|
||||
* autolaunched
|
||||
* nomutter a bus without Mutter: unavailable
|
||||
* mutter the list, switches (rate, size with the neighbour
|
||||
* moved, rate only, whole hertz, another head by
|
||||
* connector), refusals, and what went on the wire
|
||||
* x11-xorg dispserv_x11 on a real X server with Mutter on the
|
||||
* bus: the XRandR list, and Mutter never asked
|
||||
* x11-xwayland dispserv_x11 on XWayland with Mutter: Mutter's list
|
||||
* and Mutter's switch
|
||||
* x11-xwayland-nomutter XWayland without Mutter: XWayland's list,
|
||||
* exactly as before
|
||||
* wl dispserv_wl with Mutter: the list, the switch, and
|
||||
* the resolution list not flagged away
|
||||
* wl-nomutter dispserv_wl without Mutter: flagged away, as before
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
#include "../../../gfx/video_display_server.h"
|
||||
#include "../../../gfx/common/mutter_displayconfig.h"
|
||||
|
||||
#ifdef TEST_X11
|
||||
#include <X11/Xlib.h>
|
||||
Display *g_x11_dpy = NULL;
|
||||
Window g_x11_win = 0;
|
||||
int g_x11_screen = 0;
|
||||
extern const video_display_server_t dispserv_x11;
|
||||
#endif
|
||||
|
||||
#ifdef TEST_WL
|
||||
struct wl_display;
|
||||
void wayland_drm_lease_report(struct wl_display *dpy) { (void)dpy; }
|
||||
extern const video_display_server_t dispserv_wl;
|
||||
#endif
|
||||
|
||||
static int s_verbose;
|
||||
|
||||
static void vlog(const char *fmt, va_list ap)
|
||||
{
|
||||
if (s_verbose)
|
||||
vfprintf(stderr, fmt, ap);
|
||||
}
|
||||
void RARCH_DBG(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(fmt, ap); va_end(ap); }
|
||||
void RARCH_LOG(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(fmt, ap); va_end(ap); }
|
||||
void RARCH_WARN(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(fmt, ap); va_end(ap); }
|
||||
void RARCH_ERR(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(fmt, ap); va_end(ap); }
|
||||
void video_monitor_set_refresh_rate(float hz) { (void)hz; }
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* The mock's test-only methods
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static DBusMessage *mock_call(const char *method)
|
||||
{
|
||||
DBusError err;
|
||||
DBusMessage *msg, *reply;
|
||||
DBusConnection *c;
|
||||
|
||||
dbus_error_init(&err);
|
||||
if (!(c = dbus_bus_get_private(DBUS_BUS_SESSION, &err)))
|
||||
{
|
||||
dbus_error_free(&err);
|
||||
return NULL;
|
||||
}
|
||||
msg = dbus_message_new_method_call("org.gnome.Mutter.DisplayConfig",
|
||||
"/org/gnome/Mutter/DisplayConfig", "org.gnome.Mutter.DisplayConfig",
|
||||
method);
|
||||
reply = dbus_connection_send_with_reply_and_block(c, msg, 2000, &err);
|
||||
dbus_message_unref(msg);
|
||||
if (dbus_error_is_set(&err))
|
||||
dbus_error_free(&err);
|
||||
dbus_connection_close(c);
|
||||
dbus_connection_unref(c);
|
||||
return reply;
|
||||
}
|
||||
|
||||
static void mock_last(char *s, size_t len)
|
||||
{
|
||||
const char *v = "";
|
||||
DBusMessage *r = mock_call("LastApply");
|
||||
if (r)
|
||||
dbus_message_get_args(r, NULL, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID);
|
||||
snprintf(s, len, "%s", v);
|
||||
if (r)
|
||||
dbus_message_unref(r);
|
||||
}
|
||||
|
||||
static void mock_calls(unsigned *get, unsigned *apply)
|
||||
{
|
||||
dbus_uint32_t g = 0, a = 0;
|
||||
DBusMessage *r = mock_call("Calls");
|
||||
if (r)
|
||||
{
|
||||
dbus_message_get_args(r, NULL, DBUS_TYPE_UINT32, &g,
|
||||
DBUS_TYPE_UINT32, &a, DBUS_TYPE_INVALID);
|
||||
dbus_message_unref(r);
|
||||
}
|
||||
*get = g;
|
||||
*apply = a;
|
||||
}
|
||||
|
||||
static void mock_reset(void)
|
||||
{
|
||||
DBusMessage *r = mock_call("Reset");
|
||||
if (r)
|
||||
dbus_message_unref(r);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Checks
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
#define FAIL(...) do { fprintf(stderr, "FAIL: " __VA_ARGS__); fputc('\n', stderr); return 1; } while (0)
|
||||
|
||||
static int expect_last(const char *want, const char *what)
|
||||
{
|
||||
char got[2048];
|
||||
mock_last(got, sizeof(got));
|
||||
if (strcmp(got, want))
|
||||
FAIL("%s: Mutter was asked\n %s\nwanted\n %s", what, got, want);
|
||||
printf("[pass] %s\n", what);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find(const video_display_config_t *l, unsigned n,
|
||||
unsigned w, unsigned h, float hz)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
float d = l[i].refreshrate_float - hz;
|
||||
if (VIDEO_SCALE_W(l[i].dims) == w && VIDEO_SCALE_H(l[i].dims) == h
|
||||
&& d < 0.01f && d > -0.01f)
|
||||
return (int)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* DP-1's modes, as the mock lists them: the 240 Hz mode twice (fixed
|
||||
* and variable rate) must come out once */
|
||||
static int check_dp1_list(const video_display_config_t *l, unsigned n,
|
||||
float current_hz, const char *what)
|
||||
{
|
||||
unsigned i;
|
||||
int c;
|
||||
if (n != 4)
|
||||
FAIL("%s: %u entries, want 4", what, n);
|
||||
if (find(l, n, 1920, 1080, 60.0f) != 0
|
||||
|| find(l, n, 2560, 1440, 59.951f) != 1
|
||||
|| find(l, n, 2560, 1440, 119.877f) != 2
|
||||
|| find(l, n, 2560, 1440, 239.913f) != 3)
|
||||
FAIL("%s: entries missing or out of order", what);
|
||||
c = find(l, n, 2560, 1440, current_hz);
|
||||
for (i = 0; i < n; i++)
|
||||
if (l[i].current != ((int)i == c) || l[i].idx != i
|
||||
|| l[i].refreshrate != (unsigned)(l[i].refreshrate_float + 0.001f))
|
||||
FAIL("%s: entry %u mislabelled (current %d idx %u rate %u)", what, i,
|
||||
l[i].current, l[i].idx, l[i].refreshrate);
|
||||
printf("[pass] %s: 4 modes, 60/120/240 Hz, current %.3f Hz\n", what, current_hz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int case_unavailable(const char *what)
|
||||
{
|
||||
video_display_config_t *l = (video_display_config_t*)1;
|
||||
unsigned n = 99;
|
||||
if (mutter_displayconfig_available())
|
||||
FAIL("%s: reported available", what);
|
||||
if (mutter_displayconfig_get_resolution_list(NULL, &l, &n) != MUTTER_DC_UNAVAILABLE
|
||||
|| l || n)
|
||||
FAIL("%s: list not unavailable", what);
|
||||
if (mutter_displayconfig_set_resolution(NULL, 0, 60, 60.0f) != MUTTER_DC_UNAVAILABLE)
|
||||
FAIL("%s: set not unavailable", what);
|
||||
printf("[pass] %s: unavailable, caller keeps its old behaviour\n", what);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define LAYOUT "props={layout-mode:1}"
|
||||
|
||||
static int case_mutter(void)
|
||||
{
|
||||
mutter_dc_target_t hdmi;
|
||||
video_display_config_t *l = NULL;
|
||||
unsigned n = 0, g, a;
|
||||
|
||||
if (!mutter_displayconfig_available())
|
||||
FAIL("mock Mutter not seen on the bus");
|
||||
if (mutter_displayconfig_get_resolution_list(NULL, &l, &n) != MUTTER_DC_OK)
|
||||
FAIL("list failed");
|
||||
if (check_dp1_list(l, n, 239.913f, "primary head list"))
|
||||
return 1;
|
||||
free(l);
|
||||
|
||||
/* 240 -> 60 Hz: HDR colour mode kept on DP-1, underscanning kept on
|
||||
* HDMI-1, HDMI-1 where it was, temporary, current serial */
|
||||
if (mutter_displayconfig_set_resolution(NULL,
|
||||
VIDEO_SCALE_PACK(2560, 1440), 59, 59.951f) != MUTTER_DC_OK)
|
||||
FAIL("switch to 60 Hz refused");
|
||||
if (expect_last("method=1 serial=7 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 2560,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"60 Hz: one mode changed, every other setting carried over"))
|
||||
return 1;
|
||||
if (mutter_displayconfig_get_resolution_list(NULL, &l, &n) != MUTTER_DC_OK
|
||||
|| check_dp1_list(l, n, 59.951f, "list after the switch"))
|
||||
return 1;
|
||||
free(l);
|
||||
|
||||
/* Rate only (size 0), as the refresh rate autoswitch asks */
|
||||
if (mutter_displayconfig_set_resolution(NULL, 0, 119, 119.877f) != MUTTER_DC_OK)
|
||||
FAIL("rate-only switch refused");
|
||||
if (expect_last("method=1 serial=8 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 2560,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"rate only: the size stays"))
|
||||
return 1;
|
||||
|
||||
/* Whole hertz from the menu label alone */
|
||||
if (mutter_displayconfig_set_resolution(NULL,
|
||||
VIDEO_SCALE_PACK(2560, 1440), 239, 0.0f) != MUTTER_DC_OK)
|
||||
FAIL("whole-hertz switch refused");
|
||||
if (expect_last("method=1 serial=9 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 2560,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"whole hertz: 239 finds 239.913, not the variable-rate twin"))
|
||||
return 1;
|
||||
|
||||
/* Smaller: the head to the right moves in to stay adjacent */
|
||||
if (mutter_displayconfig_set_resolution(NULL,
|
||||
VIDEO_SCALE_PACK(1920, 1080), 60, 60.0f) != MUTTER_DC_OK)
|
||||
FAIL("switch to 1920x1080 refused");
|
||||
if (expect_last("method=1 serial=10 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 1920,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"smaller mode: the neighbour follows"))
|
||||
return 1;
|
||||
|
||||
/* The other head, by connector: its own list and switch */
|
||||
memset(&hdmi, 0, sizeof(hdmi));
|
||||
hdmi.connector = "HDMI-1";
|
||||
if (mutter_displayconfig_get_resolution_list(&hdmi, &l, &n) != MUTTER_DC_OK
|
||||
|| n != 2 || find(l, n, 1280, 720, 60.0f) != 0 || !l[1].current)
|
||||
FAIL("HDMI-1 list wrong (%u entries)", n);
|
||||
free(l);
|
||||
if (mutter_displayconfig_set_resolution(&hdmi,
|
||||
VIDEO_SCALE_PACK(1280, 720), 60, 60.0f) != MUTTER_DC_OK)
|
||||
FAIL("HDMI-1 switch refused");
|
||||
if (expect_last("method=1 serial=11 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 1920,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"head chosen by connector"))
|
||||
return 1;
|
||||
|
||||
/* Refusals: a size no head lists, and a rate that one does not */
|
||||
mock_calls(&g, &a);
|
||||
if (mutter_displayconfig_set_resolution(NULL,
|
||||
VIDEO_SCALE_PACK(1234, 567), 60, 60.0f) != MUTTER_DC_FAILED)
|
||||
FAIL("an unlisted size was not refused");
|
||||
if (mutter_displayconfig_set_resolution(NULL,
|
||||
VIDEO_SCALE_PACK(1920, 1080), 144, 144.0f) != MUTTER_DC_FAILED)
|
||||
FAIL("an unlisted rate was not refused");
|
||||
{
|
||||
unsigned g2, a2;
|
||||
mock_calls(&g2, &a2);
|
||||
if (a2 != a)
|
||||
FAIL("a refused switch still sent ApplyMonitorsConfig");
|
||||
}
|
||||
printf("[pass] unlisted size and rate refused without asking Mutter\n");
|
||||
|
||||
mock_reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef TEST_X11
|
||||
static int x11_open(void)
|
||||
{
|
||||
if (!(g_x11_dpy = XOpenDisplay(NULL)))
|
||||
FAIL("no X display");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int case_x11_xorg(void)
|
||||
{
|
||||
video_display_config_t *l;
|
||||
unsigned n = 0, g, a;
|
||||
void *data;
|
||||
if (x11_open())
|
||||
return 1;
|
||||
data = dispserv_x11.init();
|
||||
l = (video_display_config_t*)dispserv_x11.get_resolution_list(data, &n);
|
||||
if (!l || n < 10)
|
||||
FAIL("real X server: %u entries", n);
|
||||
free(l);
|
||||
dispserv_x11.destroy(data);
|
||||
mock_calls(&g, &a);
|
||||
if (g || a)
|
||||
FAIL("real X server: Mutter was asked (%u state, %u apply)", g, a);
|
||||
printf("[pass] real X server: %u XRandR modes, Mutter never asked\n", n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int case_x11_xwayland(bool mutter)
|
||||
{
|
||||
video_display_config_t *l;
|
||||
unsigned n = 0;
|
||||
void *data;
|
||||
if (x11_open())
|
||||
return 1;
|
||||
data = dispserv_x11.init();
|
||||
l = (video_display_config_t*)dispserv_x11.get_resolution_list(data, &n);
|
||||
if (mutter)
|
||||
{
|
||||
if (!l || check_dp1_list(l, n, 239.913f, "XWayland + Mutter list"))
|
||||
return 1;
|
||||
free(l);
|
||||
if (!dispserv_x11.set_resolution(data, VIDEO_SCALE_PACK(2560, 1440),
|
||||
59, 59.951f, 0, 0, 0, 0))
|
||||
FAIL("XWayland + Mutter switch refused");
|
||||
if (expect_last("method=1 serial=7 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 2560,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"XWayland + Mutter switch goes to Mutter"))
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* XWayland's own list, as before: its emulated sizes, each a
|
||||
* CVT timing at the one rate the compositor runs */
|
||||
unsigned i;
|
||||
float lo, hi;
|
||||
if (!l || n < 2)
|
||||
FAIL("XWayland without Mutter: %u entries", n);
|
||||
lo = hi = l[0].refreshrate_float;
|
||||
for (i = 1; i < n; i++)
|
||||
{
|
||||
if (l[i].refreshrate_float < lo)
|
||||
lo = l[i].refreshrate_float;
|
||||
if (l[i].refreshrate_float > hi)
|
||||
hi = l[i].refreshrate_float;
|
||||
}
|
||||
if (hi - lo > 2.0f)
|
||||
FAIL("XWayland without Mutter: %.3f..%.3f Hz, not XWayland's list", lo, hi);
|
||||
free(l);
|
||||
printf("[pass] XWayland without Mutter: XWayland's %u modes, as before\n", n);
|
||||
}
|
||||
dispserv_x11.destroy(data);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TEST_WL
|
||||
static int case_wl(bool mutter)
|
||||
{
|
||||
video_display_config_t *l;
|
||||
unsigned n = 0;
|
||||
uint32_t flags;
|
||||
void *data = dispserv_wl.init();
|
||||
if (!data)
|
||||
FAIL("no Wayland display");
|
||||
if (!dispserv_wl.get_resolution_list || !dispserv_wl.set_resolution
|
||||
|| !dispserv_wl.get_flags)
|
||||
FAIL("dispserv_wl has no resolution callbacks in a HAVE_DBUS build");
|
||||
flags = dispserv_wl.get_flags(data);
|
||||
l = (video_display_config_t*)dispserv_wl.get_resolution_list(data, &n);
|
||||
if (mutter)
|
||||
{
|
||||
if (BIT32_GET(flags, DISPSERV_CTX_NO_RESOLUTION_LIST))
|
||||
FAIL("Wayland + Mutter: resolution list flagged away");
|
||||
if (!l || check_dp1_list(l, n, 239.913f, "Wayland + Mutter list"))
|
||||
return 1;
|
||||
free(l);
|
||||
if (!dispserv_wl.set_resolution(data, VIDEO_SCALE_PACK(2560, 1440),
|
||||
119, 119.877f, 0, 0, 0, 0))
|
||||
FAIL("Wayland + Mutter switch refused");
|
||||
if (expect_last("method=1 serial=7 lm=0,0,1.00,0,p[[email protected]{color-mode:1}]"
|
||||
" 2560,0,1.00,0,-[[email protected]{enable_underscanning:true}] " LAYOUT,
|
||||
"Wayland + Mutter switch"))
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* What video_display_server_has_resolution_list() reads: the
|
||||
* menu entry and the autoswitch stay off, as before */
|
||||
if (!BIT32_GET(flags, DISPSERV_CTX_NO_RESOLUTION_LIST) || l || n
|
||||
|| dispserv_wl.set_resolution(data, 0, 60, 60.0f, 0, 0, 0, 0))
|
||||
FAIL("Wayland without Mutter: resolution list offered");
|
||||
printf("[pass] Wayland without Mutter: no resolution list, as before\n");
|
||||
}
|
||||
dispserv_wl.destroy(data);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *c = argc > 1 ? argv[1] : "";
|
||||
s_verbose = getenv("MDC_VERBOSE") != NULL;
|
||||
|
||||
if (!strcmp(c, "nobus"))
|
||||
return case_unavailable("no session bus");
|
||||
if (!strcmp(c, "nomutter"))
|
||||
return case_unavailable("session bus without Mutter");
|
||||
if (!strcmp(c, "mutter"))
|
||||
return case_mutter();
|
||||
#ifdef TEST_X11
|
||||
if (!strcmp(c, "x11-xorg"))
|
||||
return case_x11_xorg();
|
||||
if (!strcmp(c, "x11-xwayland"))
|
||||
return case_x11_xwayland(true);
|
||||
if (!strcmp(c, "x11-xwayland-nomutter"))
|
||||
return case_x11_xwayland(false);
|
||||
#endif
|
||||
#ifdef TEST_WL
|
||||
if (!strcmp(c, "wl"))
|
||||
return case_wl(true);
|
||||
if (!strcmp(c, "wl-nomutter"))
|
||||
return case_wl(false);
|
||||
#endif
|
||||
fprintf(stderr, "unknown case '%s'\n", c);
|
||||
return 2;
|
||||
}
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
# Every case of mutter_displayconfig_test, each with exactly the servers
|
||||
# it needs. Needs dbus-daemon, python3-dbus + python3-gi, Xorg with the
|
||||
# dummy driver, weston and Xwayland. Run from this directory after
|
||||
# make; exits non-zero on the first failure. XORG="sudo Xorg" where
|
||||
# Xorg cannot run as the user (the GitHub runners).
|
||||
set -eu
|
||||
HERE=$(pwd)
|
||||
T=$HERE/mutter_displayconfig_test
|
||||
WORK=$(mktemp -d)
|
||||
PIDS=""
|
||||
|
||||
cleanup()
|
||||
{
|
||||
for p in $PIDS; do kill "$p" 2>/dev/null || sudo -n kill "$p" 2>/dev/null || true; done
|
||||
rm -rf "$WORK"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
start_mock()
|
||||
{
|
||||
python3 "$HERE/mock_mutter.py" > "$WORK/mock.log" 2>&1 &
|
||||
MOCK=$!
|
||||
PIDS="$PIDS $MOCK"
|
||||
for i in $(seq 1 50); do
|
||||
grep -q ready "$WORK/mock.log" 2>/dev/null && return 0
|
||||
sleep 0.1
|
||||
done
|
||||
cat "$WORK/mock.log"; exit 1
|
||||
}
|
||||
|
||||
stop_mock()
|
||||
{
|
||||
kill "$MOCK" 2>/dev/null || true
|
||||
wait "$MOCK" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# No session bus at all: nothing must be autolaunched
|
||||
env -u DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR="$WORK/nort" "$T" nobus
|
||||
|
||||
# A private session bus for everything else
|
||||
export XDG_RUNTIME_DIR="$WORK/rt"
|
||||
mkdir -p -m 700 "$XDG_RUNTIME_DIR"
|
||||
dbus-daemon --session --fork --print-address=3 --print-pid=4 \
|
||||
3> "$WORK/bus.addr" 4> "$WORK/bus.pid"
|
||||
DBUS_SESSION_BUS_ADDRESS=$(cat "$WORK/bus.addr")
|
||||
export DBUS_SESSION_BUS_ADDRESS
|
||||
PIDS="$PIDS $(cat "$WORK/bus.pid")"
|
||||
|
||||
"$T" nomutter
|
||||
|
||||
start_mock
|
||||
"$T" mutter
|
||||
stop_mock
|
||||
|
||||
# A real X server with Mutter on the bus: Mutter must not be asked
|
||||
${XORG:-Xorg} :91 -config "$HERE/../display_servers_x11_live/xorg-dummy.conf" \
|
||||
-logfile "$WORK/xorg.log" -noreset -nolisten tcp -ac > "$WORK/xorg.out" 2>&1 &
|
||||
PIDS="$PIDS $!"
|
||||
for i in $(seq 1 60); do DISPLAY=:91 xrandr > /dev/null 2>&1 && break; sleep 0.25; done
|
||||
start_mock
|
||||
DISPLAY=:91 "$T" x11-xorg
|
||||
stop_mock
|
||||
|
||||
# Headless Weston with Xwayland: GNOME's shape without GNOME
|
||||
weston --backend=headless --socket=mdc-wl --idle-time=0 \
|
||||
> "$WORK/weston.log" 2>&1 &
|
||||
PIDS="$PIDS $!"
|
||||
for i in $(seq 1 60); do [ -S "$XDG_RUNTIME_DIR/mdc-wl" ] && break; sleep 0.25; done
|
||||
WAYLAND_DISPLAY=mdc-wl Xwayland :92 -noreset > "$WORK/xwayland.log" 2>&1 &
|
||||
PIDS="$PIDS $!"
|
||||
for i in $(seq 1 60); do DISPLAY=:92 xrandr > /dev/null 2>&1 && break; sleep 0.25; done
|
||||
|
||||
DISPLAY=:92 "$T" x11-xwayland-nomutter
|
||||
WAYLAND_DISPLAY=mdc-wl "$T" wl-nomutter
|
||||
|
||||
start_mock
|
||||
DISPLAY=:92 "$T" x11-xwayland
|
||||
stop_mock
|
||||
start_mock
|
||||
WAYLAND_DISPLAY=mdc-wl "$T" wl
|
||||
stop_mock
|
||||
|
||||
echo "ALL OK"
|
||||
Reference in new issue
Block a user