Merge branch 'wayland-session-mgmt'

Add support for Wayland session management protocol and use it for
wxPersistentTLW if available.

See #26793.

Closes #26324.
This commit is contained in:
Vadim Zeitlin
2026-08-09 19:33:53 +02:00
15 changed files with 977 additions and 28 deletions
+35 -16
View File
@@ -471,36 +471,55 @@ if(wxUSE_GUI)
set(wx_protocols_temp_dir ${wxOUTPUT_DIR}/wx/protocols)
set(wx_protocols_output_dir ${wxSETUP_HEADER_PATH}/wx/protocols)
# Note that we need multiple execute_process()
# invocations as single one would run commands
# concurrently and not sequentially.
execute_process(
COMMAND
${CMAKE_COMMAND} -E make_directory ${wx_protocols_temp_dir}
${CMAKE_COMMAND} -E make_directory
${wx_protocols_temp_dir}
${wx_protocols_output_dir}
)
# This function takes the protocol name and the directory
# containing the corresponding XML file.
function(wx_generate_wayland_protocol protocol_dir protocol)
execute_process(
COMMAND
${WAYLAND_SCANNER} client-header
${wx_protocols_input_dir}/pointer-warp-v1.xml
${wx_protocols_temp_dir}/pointer-warp-v1-client-protocol.h
)
execute_process(
${protocol_dir}/${protocol}.xml
${wx_protocols_temp_dir}/${protocol}-client-protocol.h
COMMAND
${WAYLAND_SCANNER} private-code
${wx_protocols_input_dir}/pointer-warp-v1.xml
${wx_protocols_temp_dir}/pointer-warp-v1-client-protocol.c
${protocol_dir}/${protocol}.xml
${wx_protocols_temp_dir}/${protocol}-client-protocol.c
)
execute_process(
COMMAND
${CMAKE_COMMAND} -E make_directory ${wx_protocols_output_dir}
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${wx_protocols_temp_dir}/pointer-warp-v1-client-protocol.h
${wx_protocols_temp_dir}/pointer-warp-v1-client-protocol.c
${wx_protocols_temp_dir}/${protocol}-client-protocol.h
${wx_protocols_temp_dir}/${protocol}-client-protocol.c
${wx_protocols_output_dir}
)
endfunction()
wx_generate_wayland_protocol(${wx_protocols_input_dir} pointer-warp-v1)
# Check if we have GTK new enough to allow using XDG
# session management protocol: 3.24.53 is the earliest one
# with gdk_wayland_window_get_xdg_toplevel() that we need.
if(GTK3_VERSION VERSION_GREATER_EQUAL 3.24.53)
# We also need wayland-protocols as this protocol
# depends on xdg-shell one.
pkg_check_modules(WAYLAND_PROTOCOLS wayland-protocols)
if(WAYLAND_PROTOCOLS_FOUND)
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
wx_generate_wayland_protocol(${wx_protocols_input_dir} xdg-session-management-v1)
wx_generate_wayland_protocol(${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell xdg-shell)
set(wxHAVE_WAYLAND_SESSION_MANAGEMENT ON)
else()
message(WARNING "wayland-protocols not found, xdg-session-management protocol won't be used")
endif()
endif()
set(wxHAVE_WAYLAND_CLIENT ON)
list(APPEND wxTOOLKIT_INCLUDE_DIRS ${WAYLAND_CLIENT_INCLUDE_DIRS})
+7
View File
@@ -630,6 +630,13 @@
*/
#cmakedefine wxHAVE_WAYLAND_CLIENT 1
/*
Use Wayland session-management protocol.
This is defined if GTK is new enough to support it.
*/
#cmakedefine wxHAVE_WAYLAND_SESSION_MANAGEMENT 1
/*
Use XTest extension to implement wxUIActionSimulator?
+1 -1
View File
@@ -128,7 +128,7 @@ case $(uname -s) in
fi
if [ -f /etc/redhat-release ]; then
dnf install -y ${WX_EXTRA_PACKAGES} gawk expat-devel findutils g++ git-core gspell-devel gstreamer1-plugins-base-devel gtk3-devel make libcurl-devel libjpeg-devel libnotify-devel libpng-devel libSM-devel libsecret-devel libtiff-devel SDL-devel webkit2gtk4.1-devel zlib-devel
dnf install -y ${WX_EXTRA_PACKAGES} gawk expat-devel findutils g++ git-core gspell-devel gstreamer1-plugins-base-devel gtk3-devel make libcurl-devel libjpeg-devel libnotify-devel libpng-devel libSM-devel libsecret-devel libtiff-devel SDL-devel wayland-protocols-devel webkit2gtk4.1-devel zlib-devel
fi
;;
Vendored
+144 -1
View File
@@ -556,6 +556,66 @@ as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
# Sed expression to map a string onto a valid variable name.
as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
as_awk_strverscmp='
# Use only awk features that work with 7th edition Unix awk (1978).
# My, what an old awk you have, Mr. Solaris!
END {
while (length(v1) && length(v2)) {
# Set d1 to be the next thing to compare from v1, and likewise for d2.
# Normally this is a single character, but if v1 and v2 contain digits,
# compare them as integers and fractions as strverscmp does.
if (v1 ~ /^[0-9]/ && v2 ~ /^[0-9]/) {
# Split v1 and v2 into their leading digit string components d1 and d2,
# and advance v1 and v2 past the leading digit strings.
for (len1 = 1; substr(v1, len1 + 1) ~ /^[0-9]/; len1++) continue
for (len2 = 1; substr(v2, len2 + 1) ~ /^[0-9]/; len2++) continue
d1 = substr(v1, 1, len1); v1 = substr(v1, len1 + 1)
d2 = substr(v2, 1, len2); v2 = substr(v2, len2 + 1)
if (d1 ~ /^0/) {
if (d2 ~ /^0/) {
# Compare two fractions.
while (d1 ~ /^0/ && d2 ~ /^0/) {
d1 = substr(d1, 2); len1--
d2 = substr(d2, 2); len2--
}
if (len1 != len2 && ! (len1 && len2 && substr(d1, 1, 1) == substr(d2, 1, 1))) {
# The two components differ in length, and the common prefix
# contains only leading zeros. Consider the longer to be less.
d1 = -len1
d2 = -len2
} else {
# Otherwise, compare as strings.
d1 = "x" d1
d2 = "x" d2
}
} else {
# A fraction is less than an integer.
exit 1
}
} else {
if (d2 ~ /^0/) {
# An integer is greater than a fraction.
exit 2
} else {
# Compare two integers.
d1 += 0
d2 += 0
}
}
} else {
# The normal case, without worrying about digits.
d1 = substr(v1, 1, 1); v1 = substr(v1, 2)
d2 = substr(v2, 1, 1); v2 = substr(v2, 2)
}
if (d1 < d2) exit 1
if (d1 > d2) exit 2
}
# Beware Solaris /usr/xgp4/bin/awk (at least through Solaris 10),
# which mishandles some comparisons of empty strings to integers.
if (length(v2)) exit 1
if (length(v1)) exit 2
}
'
test -n "$DJDIR" || exec 7<&0 </dev/null
exec 6>&1
@@ -28324,6 +28384,84 @@ $as_echo "$wx_cv_wayland_scanner" >&6; }
fi
if test -n "$WAYLAND_SCANNER" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for support of the Wayland session management protocol" >&5
$as_echo_n "checking for support of the Wayland session management protocol... " >&6; }
if ${wx_cv_gtk_wayland_sm+:} false; then :
$as_echo_n "(cached) " >&6
else
GTK_VERSION=`$PKG_CONFIG --modversion gtk+-3.0`
as_arg_v1=$GTK_VERSION
as_arg_v2=3.24.52
awk "$as_awk_strverscmp" v1="$as_arg_v1" v2="$as_arg_v2" /dev/null
case $? in #(
1) :
wx_cv_gtk_wayland_sm=no ;; #(
0) :
wx_cv_gtk_wayland_sm=maybe ;; #(
2) :
wx_cv_gtk_wayland_sm=yes
;; #(
*) :
;;
esac
if test "$wx_cv_gtk_wayland_sm" = "maybe"; then
save_CFLAGS=$CFLAGS
save_LIBS=$LIBS
CFLAGS="$CFLAGS $TOOLKIT_INCLUDE"
LIBS="$LIBS $GUI_TK_LIBRARY"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <gdk/gdkwayland.h>
int
main ()
{
gdk_wayland_window_get_xdg_toplevel(0)
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
wx_cv_gtk_wayland_sm=yes
else
wx_cv_gtk_wayland_sm=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$save_LIBS
CFLAGS=$save_CFLAGS
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $wx_cv_gtk_wayland_sm" >&5
$as_echo "$wx_cv_gtk_wayland_sm" >&6; }
if test "$wx_cv_gtk_wayland_sm" = "yes"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for wayland-protocols data directory" >&5
$as_echo_n "checking for wayland-protocols data directory... " >&6; }
if ${wx_cv_wayland_protocols_dir+:} false; then :
$as_echo_n "(cached) " >&6
else
wx_cv_wayland_protocols_dir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $wx_cv_wayland_protocols_dir" >&5
$as_echo "$wx_cv_wayland_protocols_dir" >&6; }
WAYLAND_PROTOCOLS_DIR=$wx_cv_wayland_protocols_dir
$as_echo "#define wxHAVE_WAYLAND_SESSION_MANAGEMENT 1" >>confdefs.h
else
WAYLAND_SM_SUPPORT=':'
fi
$as_echo "#define wxHAVE_WAYLAND_CLIENT 1" >>confdefs.h
ac_config_commands="$ac_config_commands wayland-protocols"
@@ -45911,6 +46049,8 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
#
WAYLAND_SCANNER=$WAYLAND_SCANNER
WAYLAND_SM_SUPPORT=$WAYLAND_SM_SUPPORT
WAYLAND_PROTOCOLS_DIR=$WAYLAND_PROTOCOLS_DIR
@@ -46502,6 +46642,10 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
mkdir -p lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols
$WAYLAND_SCANNER client-header $srcdir/src/unix/protocols/pointer-warp-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/pointer-warp-v1-client-protocol.h
$WAYLAND_SCANNER private-code $srcdir/src/unix/protocols/pointer-warp-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/pointer-warp-v1-client-protocol.c
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER client-header $srcdir/src/unix/protocols/xdg-session-management-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-session-management-v1-client-protocol.h
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER private-code $srcdir/src/unix/protocols/xdg-session-management-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-session-management-v1-client-protocol.c
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER client-header $WAYLAND_PROTOCOLS_DIR/stable/xdg-shell/xdg-shell.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-shell-client-protocol.h
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER private-code $WAYLAND_PROTOCOLS_DIR/stable/xdg-shell/xdg-shell.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-shell-client-protocol.c
;;
"rcdefs.h":C)
mkdir -p $outdir &&
@@ -46613,4 +46757,3 @@ else
fi
echo ""
+56
View File
@@ -3100,15 +3100,71 @@ installed, where VER is 2, 3 or 4.
fi
if test -n "$WAYLAND_SCANNER" ; then
dnl Check if we can use the session management protocol which
dnl normally requires GTK >= 3.24.53 but allow using it with
dnl the versions of 3.24.52 that already have the support
dnl required for it, at least until 3.24.53 is released.
AC_CACHE_CHECK([for support of the Wayland session management protocol],
wx_cv_gtk_wayland_sm,
[
GTK_VERSION=`$PKG_CONFIG --modversion gtk+-3.0`
AS_VERSION_COMPARE([$GTK_VERSION], [3.24.52],
wx_cv_gtk_wayland_sm=no,
wx_cv_gtk_wayland_sm=maybe,
wx_cv_gtk_wayland_sm=yes
)
if test "$wx_cv_gtk_wayland_sm" = "maybe"; then
save_CFLAGS=$CFLAGS
save_LIBS=$LIBS
CFLAGS="$CFLAGS $TOOLKIT_INCLUDE"
LIBS="$LIBS $GUI_TK_LIBRARY"
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <gdk/gdkwayland.h>],
[
gdk_wayland_window_get_xdg_toplevel(0)
])],
wx_cv_gtk_wayland_sm=yes,
wx_cv_gtk_wayland_sm=no
)
LIBS=$save_LIBS
CFLAGS=$save_CFLAGS
fi
]
)
if test "$wx_cv_gtk_wayland_sm" = "yes"; then
dnl We don't check for wayland-protocols being installed as
dnl it's a dependency of libgtk-3-dev or similar, and so
dnl should be available.
AC_CACHE_CHECK([for wayland-protocols data directory],
wx_cv_wayland_protocols_dir,
[
wx_cv_wayland_protocols_dir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`
]
)
WAYLAND_PROTOCOLS_DIR=$wx_cv_wayland_protocols_dir
AC_DEFINE(wxHAVE_WAYLAND_SESSION_MANAGEMENT)
else
dnl Don't bother running wayland-scanner for this protocol.
WAYLAND_SM_SUPPORT=':'
fi
AC_DEFINE(wxHAVE_WAYLAND_CLIENT)
AC_CONFIG_COMMANDS([wayland-protocols],
[
mkdir -p lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols
$WAYLAND_SCANNER client-header $srcdir/src/unix/protocols/pointer-warp-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/pointer-warp-v1-client-protocol.h
$WAYLAND_SCANNER private-code $srcdir/src/unix/protocols/pointer-warp-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/pointer-warp-v1-client-protocol.c
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER client-header $srcdir/src/unix/protocols/xdg-session-management-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-session-management-v1-client-protocol.h
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER private-code $srcdir/src/unix/protocols/xdg-session-management-v1.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-session-management-v1-client-protocol.c
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER client-header $WAYLAND_PROTOCOLS_DIR/stable/xdg-shell/xdg-shell.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-shell-client-protocol.h
$WAYLAND_SM_SUPPORT $WAYLAND_SCANNER private-code $WAYLAND_PROTOCOLS_DIR/stable/xdg-shell/xdg-shell.xml lib/wx/include/${TOOLCHAIN_FULLNAME}/wx/protocols/xdg-shell-client-protocol.c
],
[
WAYLAND_SCANNER=$WAYLAND_SCANNER
WAYLAND_SM_SUPPORT=$WAYLAND_SM_SUPPORT
WAYLAND_PROTOCOLS_DIR=$WAYLAND_PROTOCOLS_DIR
]
)
else
+54
View File
@@ -10,12 +10,19 @@
#ifndef _WX_GTK_PRIVATE_TLWGEOM_H_
#define _WX_GTK_PRIVATE_TLWGEOM_H_
#define wxPERSIST_TLW_XDG_SESSION_ID "xdg_session_id"
class wxTLWGeometry : public wxTLWGeometryGeneric
{
typedef wxTLWGeometryGeneric BaseType;
public:
virtual bool Save(Store& store) const override
{
if ( wxTopLevelWindow::HasWaylandXDGSessionManagement() )
{
return store.SaveString(wxPERSIST_TLW_XDG_SESSION_ID, m_xdgSessionId);
}
if ( !wxTLWGeometryGeneric::Save(store) )
return false;
@@ -35,6 +42,16 @@ public:
virtual bool Restore(const Store& store) override
{
if ( wxTopLevelWindow::HasWaylandXDGSessionManagement() )
{
store.RestoreString(wxPERSIST_TLW_XDG_SESSION_ID, &m_xdgSessionId);
// When using Wayland XDG session management, we need our ApplyTo()
// to be called, even if we don't have any saved state yet, so
// always return true, even if the session ID is empty.
return true;
}
if ( !wxTLWGeometryGeneric::Restore(store) )
return false;
@@ -48,6 +65,19 @@ public:
virtual bool GetFrom(const wxTopLevelWindow* tlw) override
{
// If we are using Wayland XDG session management protocol, we just
// need to save the session ID.
if ( wxTopLevelWindow::HasWaylandXDGSessionManagement() )
{
m_xdgSessionId = tlw->GetWaylandXDGSessionId();
// Note that it shouldn't be empty here: when XDG session
// management protocol is supported, the compositor should have
// assigned a session ID for the associated TLW.
return true;
}
if ( !wxTLWGeometryGeneric::GetFrom(tlw) )
return false;
@@ -58,6 +88,28 @@ public:
virtual bool ApplyTo(wxTopLevelWindow* tlw) override
{
if ( wxTopLevelWindow::HasWaylandXDGSessionManagement() )
{
// As mentioned in Restore(), we need to call ApplyTo() even if we
// don't have any saved state yet in order to register this window
// with the session manager, so always do it, even if the session
// ID is empty.
tlw->SetWaylandXDGSessionId(m_xdgSessionId);
// However only return true if we did have a session ID to restore
// and return false for the first run to give the application a
// possibility to set the default size in this case.
//
// Note that returning true if we do have a session ID is
// optimistic, we don't know if the geometry will be really
// restored by the compositor (this will happen later, when the
// window is shown), but we have to assume that it will be, as
// always returning false from here would be definitely wrong as it
// would cause the application to always overwrite the possibly
// saved size with the default.
return !m_xdgSessionId.empty();
}
// Don't overwrite the current decoration size if we already have it.
if ( !tlw->m_decorSize.left && !tlw->m_decorSize.right &&
!tlw->m_decorSize.top && !tlw->m_decorSize.bottom )
@@ -70,6 +122,8 @@ public:
private:
wxTopLevelWindow::DecorSize m_decorSize;
wxString m_xdgSessionId;
};
#endif // _WX_GTK_PRIVATE_TLWGEOM_H_
+14
View File
@@ -31,6 +31,10 @@
#include "wx/protocols/pointer-warp-v1-client-protocol.h"
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
#include "wx/protocols/xdg-session-management-v1-client-protocol.h"
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
namespace wxWayland
{
@@ -51,6 +55,11 @@ wxDEFINE_WL_DELETER(wl_pointer, wl_pointer_release);
wxDEFINE_WL_DELETER(wl_registry, wl_registry_destroy);
wxDEFINE_WL_DELETER(wl_seat, wl_seat_release);
wxDEFINE_WL_DELETER(wp_pointer_warp_v1, wp_pointer_warp_v1_destroy);
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
wxDEFINE_WL_DELETER(xdg_session_manager_v1, xdg_session_manager_v1_destroy);
wxDEFINE_WL_DELETER(xdg_session_v1, xdg_session_v1_destroy);
wxDEFINE_WL_DELETER(xdg_toplevel_session_v1, xdg_toplevel_session_v1_destroy);
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
template <typename T>
using wl_unique_ptr = std::unique_ptr<T, wl_deleter<T>>;
@@ -115,6 +124,11 @@ struct Globals
// Optional pointer to the global pointer warp protocol object.
wl_unique_ptr<wp_pointer_warp_v1> pointer_warp;
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
// Optional pointer to the global session manager protocol object.
wl_unique_ptr<xdg_session_manager_v1> session_manager;
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
};
extern Globals WLGlobals;
+21
View File
@@ -86,11 +86,28 @@ public:
virtual void Refresh( bool eraseBackground = true,
const wxRect *rect = (const wxRect *) nullptr ) override;
// GTK-specific accessor returning true if Wayland XDG session management
// protocol is available.
//
// Returns false when not using Wayland at all.
static bool HasWaylandXDGSessionManagement();
// Set the XDG session ID to use: calling this even with an empty string
// registers this window as part of XDG toplevel session, meaning that its
// geometry will be saved/restored by the compositor.
bool SetWaylandXDGSessionId(const wxString& sessionId);
// Return the XDG session used by this object, if any.
wxString GetWaylandXDGSessionId() const;
// implementation from now on
// --------------------------
// GTK callbacks
virtual void GTKHandleRealized() override;
virtual void GTKHandleUnrealized() override;
void GTKHandleMapped();
void GTKConfigureEvent(int x, int y);
@@ -128,6 +145,10 @@ public:
void GTKDoAfterShow();
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
class wxXDGSessionData *m_xdgSessionData = nullptr;
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
#ifdef __WXGTK3__
void GTKUpdateClientSizeIfNecessary();
+10
View File
@@ -67,11 +67,21 @@ private:
return m_pers->SaveValue(name, value);
}
virtual bool SaveString(const wxString& name, const wxString& value) override
{
return m_pers->SaveValue(name, value);
}
virtual bool RestoreValue(const wxString& name, int* value) const override
{
return m_pers->RestoreValue(name, value);
}
virtual bool RestoreString(const wxString& name, wxString* value) const override
{
return m_pers->RestoreValue(name, value);
}
private:
wxPersistentTLW* const m_pers;
};
+20
View File
@@ -276,10 +276,30 @@ public:
// will return false.
virtual bool SaveValue(const wxString& name, int value) = 0;
// Same as above but for string values, not implemented by default
// because this only needs to be implemented when using wxGTK with
// Wayland XDG session management protocol and so can be ignored when
// not targeting this platform.
virtual bool SaveString(const wxString& name, const wxString& value)
{
wxUnusedVar(name);
wxUnusedVar(value);
return false;
}
// If restoring a value returns false, it just means that the value is
// not present and RestoreToGeometry() still continues with restoring
// the other values.
virtual bool RestoreValue(const wxString& name, int* value) const = 0;
// Same as for but for string values, not implemented by default, see
// comment for SaveString() above.
virtual bool RestoreString(const wxString& name, wxString* value) const
{
wxUnusedVar(name);
wxUnusedVar(value);
return false;
}
};
// Save the current window geometry using the provided serializer and
+36
View File
@@ -436,6 +436,24 @@ public:
*/
virtual bool SaveValue(const wxString& name, int value) = 0;
/**
Save a single field with the given string value.
This function is similar to SaveValue(), but is currently only used
when using wxGTK with Wayland XDG session management protocol and
so can be left unimplemented when not targeting this platform.
@param name uniquely identifies the field but is otherwise
arbitrary.
@param value value of the field, currently always non-empty.
@return @true if the field was saved or @false if saving it failed,
resulting in wxTopLevelWindow::SaveGeometry() failure.
@since 3.3.4
*/
virtual bool SaveString(const wxString& name, const wxString& value);
/**
Try to restore a single field.
@@ -452,6 +470,24 @@ public:
found or an error occurred.
*/
virtual bool RestoreValue(const wxString& name, int* value) const = 0;
/**
Try to restore a single string-valued field.
This function is similar to RestoreValue(), but is currently only
used when using wxGTK with Wayland XDG session management protocol
and so can be left unimplemented when not targeting this platform.
@param name uniquely identifies the field.
@param value non-null pointer to the value to be filled by this
function.
@return @true if the field was retrieved or @false if it wasn't
found or an error occurred,
@since 3.3.4
*/
virtual bool RestoreString(const wxString& name, wxString* value) const;
};
/**
+7
View File
@@ -629,6 +629,13 @@
*/
#undef wxHAVE_WAYLAND_CLIENT
/*
Use Wayland session-management protocol.
This is defined if GTK is new enough to support it.
*/
#undef wxHAVE_WAYLAND_SESSION_MANAGEMENT
/*
Use XTest extension to implement wxUIActionSimulator?
+228
View File
@@ -49,6 +49,12 @@
#include "wx/unix/private/x11ptr.h"
#endif
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
#include "wx/gtk/private/wayland.h"
static constexpr const char* TRACE_XDGSM = "xdgsm";
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
#define TRACE_TLWSIZE "tlwsize"
// ----------------------------------------------------------------------------
@@ -383,6 +389,205 @@ void wxTopLevelWindowGTK::GTKConfigureEvent(int x, int y)
// "realize" from m_widget
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// XDG session management support under Wayland
// ----------------------------------------------------------------------------
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
using wxWayland::wl_unique_ptr;
class wxXDGSessionData
{
public:
wxXDGSessionData() = default;
~wxXDGSessionData() = default;
wl_unique_ptr<xdg_session_v1> m_xdgSession;
wl_unique_ptr<xdg_toplevel_session_v1> m_xdgToplevelSession;
wxString m_sessionId;
wxDECLARE_NO_COPY_CLASS(wxXDGSessionData);
};
namespace
{
void
xdgSessionCreated(void* data,
xdg_session_v1* WXUNUSED(session),
const char *id)
{
auto* const win = static_cast<wxTopLevelWindowGTK*>(data);
wxLogTrace(TRACE_XDGSM, "New XDG session \"%s\" created for %s",
id, wxDumpWindow(win));
win->m_xdgSessionData->m_sessionId = wxString::FromUTF8(id);
}
void xdgSessionRestored(void* data, xdg_session_v1* WXUNUSED(session))
{
auto* const win = static_cast<wxTopLevelWindowGTK*>(data);
wxLogTrace(TRACE_XDGSM, "XDG session \"%s\" restored for %s",
win->m_xdgSessionData->m_sessionId, wxDumpWindow(win));
}
void xdgSessionReplaced(void* data, xdg_session_v1* WXUNUSED(session))
{
auto* const win = static_cast<wxTopLevelWindowGTK*>(data);
// It's not clear what can we possibly do here, so don't do anything.
wxLogTrace(TRACE_XDGSM, "XDG session \"%s\" replaced for %s",
win->m_xdgSessionData->m_sessionId, wxDumpWindow(win));
}
const xdg_session_v1_listener xdgSessionListener = {
xdgSessionCreated,
xdgSessionRestored,
xdgSessionReplaced,
};
void
xdgToplevelSessionRestored(void* data, xdg_toplevel_session_v1* WXUNUSED(ts))
{
auto* const win = static_cast<wxTopLevelWindowGTK*>(data);
wxLogTrace(TRACE_XDGSM, "XDG toplevel session \"%s\" restored for %s",
win->m_xdgSessionData->m_sessionId, wxDumpWindow(win));
}
const xdg_toplevel_session_v1_listener xdgToplevelSessionListener = {
xdgToplevelSessionRestored,
};
} // anonymous namespace
extern "C" {
static gboolean wxgtk_tlw_xdg_realized(GdkWindow* window, wxTopLevelWindow* win)
{
wxLogTrace(TRACE_XDGSM, "xdg_toplevel_realized for %s", wxDumpWindow(win));
auto& data = win->m_xdgSessionData;
wxCHECK_MSG( data, FALSE, "XDG session data should have been initialized" );
auto& xdgSession = data->m_xdgSession;
xdgSession.reset(xdg_session_manager_v1_get_session(
wxWayland::WLGlobals.session_manager.get(),
XDG_SESSION_MANAGER_V1_REASON_LAUNCH,
data->m_sessionId.empty() ? nullptr : data->m_sessionId.utf8_str()
));
wxCHECK_MSG( xdgSession, FALSE, "Failed to get xdg_session" );
xdg_session_v1_add_listener(xdgSession.get(), &xdgSessionListener, win);
xdg_toplevel* const xdgToplevel = gdk_wayland_window_get_xdg_toplevel(window);
if ( data->m_sessionId.empty() )
{
wxLogTrace(TRACE_XDGSM, "Adding %s to a new session",
wxDumpWindow(win));
data->m_xdgToplevelSession.reset(xdg_session_v1_add_toplevel(
xdgSession.get(),
xdgToplevel,
win->GetName().utf8_str()
));
}
else
{
wxLogTrace(TRACE_XDGSM, "Restoring session \"%s\" for %s",
data->m_sessionId, wxDumpWindow(win));
data->m_xdgToplevelSession.reset(xdg_session_v1_restore_toplevel(
data->m_xdgSession.get(),
xdgToplevel,
win->GetName().utf8_str()
));
}
xdg_toplevel_session_v1_add_listener(
data->m_xdgToplevelSession.get(), &xdgToplevelSessionListener, win
);
return FALSE;
}
}
/* static */
bool wxTopLevelWindowGTK::HasWaylandXDGSessionManagement()
{
// Cache it just to avoid giving the trace messages more than once.
static int s_cachedValue = -1;
if ( s_cachedValue == -1 )
{
s_cachedValue = 1;
if ( !g_signal_lookup("xdg-toplevel-realized", GDK_TYPE_WAYLAND_WINDOW) )
{
wxLogTrace(TRACE_XDGSM, "Runtime GTK version %d.%d.%d is too old.",
gtk_get_major_version(),
gtk_get_minor_version(),
gtk_get_micro_version());
s_cachedValue = 0;
}
if ( !wxWayland::WLGlobals.session_manager )
{
wxLogTrace(TRACE_XDGSM, "Compositor doesn't support the protocol.");
s_cachedValue = 0;
}
}
return s_cachedValue != 0;
}
bool wxTopLevelWindowGTK::SetWaylandXDGSessionId(const wxString& sessionId)
{
wxCHECK_MSG( !m_xdgSessionData, false, "XDG session ID already set?" );
wxCHECK_MSG( !gtk_widget_get_realized(m_widget), false,
"XDG session ID must be set before showing the window" );
wxCHECK_MSG( HasWaylandXDGSessionManagement(), false,
"Shouldn't be called if not supported" );
m_xdgSessionData = new wxXDGSessionData();
m_xdgSessionData->m_sessionId = sessionId;
return true;
}
wxString wxTopLevelWindowGTK::GetWaylandXDGSessionId() const
{
return m_xdgSessionData ? m_xdgSessionData->m_sessionId : wxString{};
}
#else // !wxHAVE_WAYLAND_SESSION_MANAGEMENT
/* static */
bool wxTopLevelWindowGTK::HasWaylandXDGSessionManagement()
{
return false;
}
bool wxTopLevelWindowGTK::SetWaylandXDGSessionId(const wxString& sessionId)
{
wxUnusedVar(sessionId);
return false;
}
wxString wxTopLevelWindowGTK::GetWaylandXDGSessionId() const
{
return {};
}
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT/!wxHAVE_WAYLAND_SESSION_MANAGEMENT
#if GTK_CHECK_VERSION(3,10,0)
extern "C" {
static void findTitlebar(GtkWidget* widget, void* data)
@@ -410,6 +615,16 @@ void wxTopLevelWindowGTK::GTKHandleRealized()
GdkWindow* window = gtk_widget_get_window(m_widget);
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
// We effectively check if SetWaylandXDGSessionId() has been called, which
// includes the check for the new enough GTK with support for this signal.
if ( m_xdgSessionData )
{
g_signal_connect (window, "xdg-toplevel-realized",
G_CALLBACK (wxgtk_tlw_xdg_realized), this);
}
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
#if GTK_CHECK_VERSION(3,10,0)
if (wx_is_at_least_gtk3(10))
{
@@ -473,6 +688,19 @@ void wxTopLevelWindowGTK::GTKHandleRealized()
#endif
}
void wxTopLevelWindowGTK::GTKHandleUnrealized()
{
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
if ( m_xdgSessionData )
{
delete m_xdgSessionData;
m_xdgSessionData = nullptr;
}
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
wxTopLevelWindowBase::GTKHandleUnrealized();
}
//-----------------------------------------------------------------------------
// "map_event" from m_widget
//-----------------------------------------------------------------------------
+16
View File
@@ -20,7 +20,14 @@
// Code generated by wayland-scanner uses visibility attribute incorrectly for
// C++, resulting in warnings, so disable them.
wxGCC_WARNING_SUPPRESS(attributes)
#include "wx/protocols/pointer-warp-v1-client-protocol.c"
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
#include "wx/protocols/xdg-session-management-v1-client-protocol.c"
#include "wx/protocols/xdg-shell-client-protocol.c"
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
wxGCC_WARNING_RESTORE(attributes)
constexpr const char* TRACE_WAYLAND = "wayland";
@@ -180,6 +187,15 @@ registry_handle_global(void* data,
wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, 1)
));
}
#ifdef wxHAVE_WAYLAND_SESSION_MANAGEMENT
if ( strcmp(interface, xdg_session_manager_v1_interface.name) == 0 )
{
WLGlobals.session_manager.reset(static_cast<xdg_session_manager_v1*>(
wl_registry_bind(registry, name, &xdg_session_manager_v1_interface, 1)
));
}
#endif // wxHAVE_WAYLAND_SESSION_MANAGEMENT
}
void
@@ -0,0 +1,318 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_session_management_v1">
<copyright>
Copyright 2018 Mike Blumenkrantz
Copyright 2018 Samsung Electronics Co., Ltd
Copyright 2018 Red Hat Inc.
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 (including the next
paragraph) 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.
</copyright>
<description summary="Protocol for managing application sessions">
This description provides a high-level overview of the interplay between
the interfaces defined this protocol. For details, see the protocol
specification.
The xdg_session_manager protocol declares interfaces necessary to
allow clients to restore toplevel state from previous executions. The
xdg_session_manager_v1.get_session request can be used to obtain a
xdg_session_v1 resource representing the state of a set of toplevels.
Clients may obtain the session string to use in future calls through
the xdg_session_v1.created event. Compositors will use this string
as an identifiable token for future runs, possibly storing data about
the related toplevels in persistent storage. Clients that wish to
track sessions in multiple environments may use the $XDG_CURRENT_DESKTOP
environment variable.
Toplevels are managed through the xdg_session_v1.add_toplevel and
xdg_session_v1.remove_toplevel pair of requests. Clients will explicitly
request a toplevel to be restored according to prior state through the
xdg_session_v1.restore_toplevel request before the toplevel is mapped.
Compositors may store session information up to any arbitrary level, and
apply any limits and policies to the amount of data stored and its lifetime.
Clients must account for missing sessions and partial session restoration.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<interface name="xdg_session_manager_v1" version="1">
<description summary="manage sessions for applications">
The xdg_session_manager_v1 interface defines base requests for creating and
managing a session for an application. Sessions persist across application
and compositor restarts unless explicitly destroyed. A session is created
for the purpose of maintaining an application's xdg_toplevel surfaces
across compositor or application restarts. The compositor should remember
as many states as possible for surfaces in a given session, but there is
no requirement for which states must be remembered.
Policies such as cache eviction are declared an implementation detail of
the compositor. Clients should account for no longer existing sessions.
</description>
<enum name="error">
<entry name="in_use" summary="a requested session is already in use"
value="1"/>
<entry name="invalid_session_id" summary="invalid session identifier"
value="2"/>
</enum>
<enum name="reason">
<description summary="reason for getting a session">
The reason may determine in what way a session restores the window
management state of associated toplevels.
For example newly launched applications might be launched on the active
workspace with restored size and position, while a recovered
application might restore additional state such as active workspace and
stacking order.
</description>
<entry name="launch" value="1">
<description summary="an app is newly launched">
A new app instance is launched, for example from an app launcher.
</description>
</entry>
<entry name="recover" value="2">
<description summary="an app recovered">
A app instance is recovering from for example a compositor or app crash.
</description>
</entry>
<entry name="session_restore" value="3">
<description summary="an app restored">
A app instance is restored, for example part of a restored session, or
restored from having been temporarily terminated due to resource
constraints.
</description>
</entry>
</enum>
<request name="destroy" type="destructor">
<description summary="Destroy this object">
Destroy the manager object. The existing session objects will be
unaffected.
</description>
</request>
<request name="get_session">
<description summary="create or restore a session">
Create a session object corresponding to either an existing session
identified by the given session identifier string or a new session.
While the session object exists, the session is considered to be "in
use".
If an identifier string represents a session that is currently actively
in use by the the same client, an 'in_use' error is raised. If some
other client is currently using the same session, the new session will
replace managing the associated state.
NULL is passed to initiate a new session. If a session_id is passed
which does not represent a valid session, the compositor treats it as if
NULL had been passed.
The session id string must be UTF-8 encoded. It is also limited by the
maximum length of wayland messages (around 4KB). The 'invalid_session_id'
protocol error will be raised if an invalid string is provided.
A client is allowed to have any number of in use sessions at the same
time.
</description>
<arg name="id" type="new_id" interface="xdg_session_v1"/>
<arg name="reason" type="uint" enum="reason"
summary="reason for session"/>
<arg name="session_id" type="string"
summary="the session to restore"
allow-null="true"/>
</request>
</interface>
<interface name="xdg_session_v1" version="1">
<description summary="A session for an application">
A xdg_session_v1 object represents a session for an application. While the
object exists, all surfaces which have been added to the session will
have states stored by the compositor which can be reapplied at a later
time. Two sessions cannot exist for the same identifier string.
States for surfaces added to a session are automatically updated by the
compositor when they are changed.
</description>
<enum name="error">
<entry name="name_in_use"
summary="toplevel name is already in use"
value="1"/>
<entry name="already_mapped"
summary="toplevel was already mapped when restored"
value="2"/>
<entry name="invalid_name"
summary="provided toplevel name is invalid"
value="3"/>
</enum>
<request name="destroy" type="destructor">
<description summary="Destroy the session">
Destroy a session object, preserving the current state but not continuing
to make further updates if state changes occur. This makes the associated
xdg_toplevel_session_v1 objects inert.
</description>
</request>
<request name="remove" type="destructor">
<description summary="Remove the session">
Remove the session, making it no longer available for restoration. A
compositor should in response to this request remove the data related to
this session from its storage.
</description>
</request>
<request name="add_toplevel">
<description summary="add a new surface to the session">
Attempt to add a given surface to the session. The passed name is used
to identify what window is being restored, and may be used to store
window specific state within the session.
The name given to the toplevel must not correspond to any previously
existing toplevel names in the session. If the name matches an already
known toplevel name in the session, a 'name_in_use' protocol error will
be raised.
This request will return a xdg_toplevel_session_v1 for later
manipulation. As this resource is created from an empty initial state,
compositors must not emit a xdg_toplevel_session_v1.restored event for
resources created through this request.
The name string must be UTF-8 encoded. It is also limited by the maximum
length of wayland messages (around 4KB). The 'invalid_name' protocol
error will be raised if an invalid string is provided.
</description>
<arg name="id" type="new_id" interface="xdg_toplevel_session_v1"/>
<arg name="toplevel" type="object" interface="xdg_toplevel"/>
<arg name="name" type="string" summary="name identifying the toplevel"/>
</request>
<request name="restore_toplevel">
<description summary="restore a surface state">
Inform the compositor that the toplevel associated with the passed name
should have its window management state restored.
If the toplevel name was previously granted to another xdg_toplevel,
the 'name_in_use' protocol error will be raised.
This request must be called prior to the first commit on the associated
wl_surface after creating the toplevel, otherwise an 'already_mapped'
error is raised.
As part of the initial configure sequence, if the toplevel was
successfully restored, a xdg_toplevel_session_v1.restored event is
emitted. If the toplevel name was not known in the session, this request
will be equivalent to the xdg_toplevel_session_v1.add_toplevel request,
and no such event will be emitted. See the xdg_toplevel_session_v1.restored
event for further details.
The name string must be UTF-8 encoded. It is also limited by the maximum
length of wayland messages (around 4KB). The 'invalid_name' protocol
error will be raised if an invalid string is provided.
</description>
<arg name="id" type="new_id" interface="xdg_toplevel_session_v1"/>
<arg name="toplevel" type="object" interface="xdg_toplevel"/>
<arg name="name" type="string" summary="name identifying the toplevel"/>
</request>
<request name="remove_toplevel">
<description summary="remove a surface from the session">
Remove a specified surface from the session and render any related
xdg_toplevel_session_v1 object inert. The compositor should remove any
data related to the toplevel in the corresponding session from its internal
storage.
The window is specified by its name in the session. The name string
must be encoded in UTF-8, and it is limited in size by the maximum
length of wayland messages (around 4KB).
</description>
<arg name="name" type="string" summary="name identifying the toplevel"/>
</request>
<event name="created">
<description summary="newly-created session id">
Emitted at most once some time after getting a new session object. It
means that no previous state was restored, and a new session was created.
The passed id can be persistently stored and used to restore previous
sessions.
</description>
<arg name="session_id" type="string"/>
</event>
<event name="restored">
<description summary="the session has been restored">
Emitted at most once some time after getting a new session object. It
means that previous state was at least partially restored. The same id
can again be used to restore previous sessions.
</description>
</event>
<event name="replaced">
<description summary="the session has been replaced">
Emitted at most once, if the session was taken over by some other
client. When this happens, the session and all its toplevel session
objects become inert, and should be destroyed.
</description>
</event>
</interface>
<interface name="xdg_toplevel_session_v1" version="1">
<description summary="A session for an application">
A xdg_toplevel_session_v1 resource acts as a handle for the given
toplevel in the session. It allows for receiving events after a
toplevel state was restored, and has the requests to manage them.
</description>
<request name="destroy" type="destructor">
<description summary="Destroy the object">
Destroy the object. This has no effect over window management of the
associated toplevel.
</description>
</request>
<request name="rename">
<description summary="change the name of toplevel session">
Renames the toplevel session. The new name can be used in subsequent requests
to identify this session object. The state associated with this toplevel
session will be preserved.
If the xdg_session_v1 already contains a toplevel with the specified name,
the 'name_in_use' protocol error will be raised.
</description>
<arg name="name" type="string" summary="new name to identify the toplevel"/>
</request>
<event name="restored">
<description summary="a toplevel's session has been restored">
The "restored" event is emitted prior to the first
xdg_toplevel.configure for the toplevel. It will only be emitted after
xdg_session_v1.restore_toplevel, and the initial empty surface state has
been applied, and it indicates that the surface's session is being
restored with this configure event.
</description>
</event>
</interface>
</protocol>