diff --git a/packages/libical/build.sh b/packages/libical/build.sh index 373a7ef6017..7c27a2d8351 100644 --- a/packages/libical/build.sh +++ b/packages/libical/build.sh @@ -3,19 +3,45 @@ TERMUX_PKG_DESCRIPTION="Libical is an Open Source implementation of the iCalenda TERMUX_PKG_LICENSE="LGPL-2.0" TERMUX_PKG_MAINTAINER="@termux" TERMUX_PKG_VERSION="4.0.4" +TERMUX_PKG_REVISION=1 TERMUX_PKG_SRCURL=https://github.com/libical/libical/releases/download/v$TERMUX_PKG_VERSION/libical-$TERMUX_PKG_VERSION.tar.gz TERMUX_PKG_SHA256=c851cdb46da5e6397881dafaa592c5516fb49da05dd1bb095f711a6d20eac422 TERMUX_PKG_AUTO_UPDATE=true -TERMUX_PKG_DEPENDS="libc++, libicu" +TERMUX_PKG_DEPENDS="glib, gobject-introspection, libc++, libicu, libxml2" +TERMUX_PKG_BUILD_DEPENDS="g-ir-scanner" TERMUX_PKG_BREAKS="libical-dev" TERMUX_PKG_REPLACES="libical-dev" TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" -DLIBICAL_BUILD_DOCS=false --DLIBICAL_GLIB=false --DLIBICAL_GOBJECT_INTROSPECTION=false +-DLIBICAL_GLIB_BUILD_DOCS=false -DUSE_BUILTIN_TZDATA=true -DPERL_EXECUTABLE=/usr/bin/perl +-DICAL_GLIB=true +-DIMPORT_ICAL_GLIB_SRC_GENERATOR=$TERMUX_PKG_HOSTBUILD_DIR/host-prefix/lib/cmake/LibIcal/IcalGlibSrcGenerator.cmake +-DENABLE_GTK_DOC=false " +TERMUX_PKG_HOSTBUILD=true + +termux_step_pre_configure() { + termux_setup_gir +} + +termux_step_host_build() { + termux_setup_cmake + termux_setup_ninja + + mkdir -p "$TERMUX_PKG_HOSTBUILD_DIR"/host-prefix + + cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$TERMUX_PKG_HOSTBUILD_DIR"/host-prefix \ + -DENABLE_GTK_DOC=OFF \ + -DLIBICAL_GLIB_BUILD_DOCS=OFF \ + -G Ninja \ + "$TERMUX_PKG_SRCDIR" + cmake --build . + cmake --install . --prefix "$TERMUX_PKG_HOSTBUILD_DIR"/host-prefix +} termux_step_post_get_source() { # Do not forget to bump revision of reverse dependencies and rebuild them diff --git a/packages/libical/gir/4.0.4/ICal-4.0.xml b/packages/libical/gir/4.0.4/ICal-4.0.xml new file mode 100644 index 00000000000..87ec2e15747 --- /dev/null +++ b/packages/libical/gir/4.0.4/ICal-4.0.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/libical/gir/4.0.4/ICalGLib-4.0.xml b/packages/libical/gir/4.0.4/ICalGLib-4.0.xml new file mode 100644 index 00000000000..d093d42cf22 --- /dev/null +++ b/packages/libical/gir/4.0.4/ICalGLib-4.0.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/spidermonkey/0008-fix-marcos.patch b/packages/spidermonkey/0007-fix-marcos.patch similarity index 80% rename from packages/spidermonkey/0008-fix-marcos.patch rename to packages/spidermonkey/0007-fix-marcos.patch index 256c711927a..b89a4ea14f9 100644 --- a/packages/spidermonkey/0008-fix-marcos.patch +++ b/packages/spidermonkey/0007-fix-marcos.patch @@ -53,17 +53,6 @@ # define MOZ_CUBEB_REMOTING #endif ---- a/mozglue/misc/ConditionVariable_posix.cpp -+++ b/mozglue/misc/ConditionVariable_posix.cpp -@@ -23,7 +23,7 @@ - // Android 4.4 or earlier & macOS 10.12 has the clock functions, but not - // pthread_condattr_setclock. - #if defined(HAVE_CLOCK_MONOTONIC) && \ -- !(defined(__ANDROID__) && __ANDROID_API__ < 21) && !defined(__APPLE__) -+ defined(__TERMUX__) - # define CV_USE_CLOCK_API - #endif - --- a/mozglue/misc/StackWalk.cpp +++ b/mozglue/misc/StackWalk.cpp @@ -692,7 +692,7 @@ @@ -77,12 +66,13 @@ pthread_getattr_np(pthread_self(), &sattr); --- a/nsprpub/pr/src/pthreads/ptsynch.c +++ b/nsprpub/pr/src/pthreads/ptsynch.c -@@ -953,7 +953,7 @@ - #if (defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)) \ - || (defined(FREEBSD) && __FreeBSD_version < 1200059) \ - || defined(OPENBSD) || defined(BSDI) \ -- || defined(DARWIN) -+ || defined(DARWIN) || defined(__TERMUX__) +@@ -864,7 +864,7 @@ + */ + # if (defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)) || \ + (defined(FREEBSD) && __FreeBSD_version < 1200059) || \ +- defined(OPENBSD) || defined(DARWIN) ++ defined(OPENBSD) || defined(DARWIN) || defined(__TERMUX__) /* union semun is defined by including */ - #else + # else /* according to X/OPEN we have to define it ourselves */ + diff --git a/packages/spidermonkey/0009-impl-posix-shmem.patch b/packages/spidermonkey/0008-impl-posix-shmem.patch similarity index 80% rename from packages/spidermonkey/0009-impl-posix-shmem.patch rename to packages/spidermonkey/0008-impl-posix-shmem.patch index e736e9e633b..d2fda0d1623 100644 --- a/packages/spidermonkey/0009-impl-posix-shmem.patch +++ b/packages/spidermonkey/0008-impl-posix-shmem.patch @@ -1,8 +1,8 @@ ---- a/ipc/chromium/src/base/shared_memory_posix.cc -+++ b/ipc/chromium/src/base/shared_memory_posix.cc -@@ -40,6 +40,65 @@ - #include "mozilla/UniquePtrExtensions.h" +--- a/ipc/glue/SharedMemoryPlatform_posix.cpp ++++ b/ipc/glue/SharedMemoryPlatform_posix.cpp +@@ -39,6 +39,65 @@ #include "prenv.h" + #include "nsXULAppAPI.h" // for XRE_IsParentProcess +static int shm_unlink(const char *name) { + size_t namelen; @@ -63,15 +63,16 @@ + return fd; +} + - namespace base { + namespace mozilla::ipc::shared_memory { - void SharedMemory::MappingDeleter::operator()(void* ptr) { -@@ -153,7 +212,7 @@ + // memfd_create is a nonstandard interface for creating anonymous +@@ -101,7 +160,7 @@ // FreeBSD in version 13. - # if !defined(HAVE_MEMFD_CREATE) && defined(XP_LINUX) && \ -- defined(SYS_memfd_create) -+ defined(SYS_memfd_create) && !defined(__TERMUX__) + #if !defined(HAVE_MEMFD_CREATE) && defined(XP_LINUX) && \ +- defined(SYS_memfd_create) ++ defined(SYS_memfd_create) && !defined(__TERMUX__) // Older libc versions (e.g., glibc before 2.27) don't have the // wrapper, but we can supply our own; see `linux_memfd_defs.h`. + diff --git a/packages/spidermonkey/0010-do-not-check-time_t-for-system-libevent.patch b/packages/spidermonkey/0009-do-not-check-time_t-for-system-libevent.patch similarity index 100% rename from packages/spidermonkey/0010-do-not-check-time_t-for-system-libevent.patch rename to packages/spidermonkey/0009-do-not-check-time_t-for-system-libevent.patch diff --git a/packages/spidermonkey/0011-use-UnderrunHandlerNoop.patch b/packages/spidermonkey/0010-use-UnderrunHandlerNoop.patch similarity index 100% rename from packages/spidermonkey/0011-use-UnderrunHandlerNoop.patch rename to packages/spidermonkey/0010-use-UnderrunHandlerNoop.patch diff --git a/packages/spidermonkey/0012-do-not-use-adaptive-mutex.patch b/packages/spidermonkey/0011-do-not-use-adaptive-mutex.patch similarity index 100% rename from packages/spidermonkey/0012-do-not-use-adaptive-mutex.patch rename to packages/spidermonkey/0011-do-not-use-adaptive-mutex.patch diff --git a/packages/spidermonkey/0012-fix-rust-compile.patch b/packages/spidermonkey/0012-fix-rust-compile.patch new file mode 100644 index 00000000000..8442d21449b --- /dev/null +++ b/packages/spidermonkey/0012-fix-rust-compile.patch @@ -0,0 +1,47 @@ +--- a/toolkit/components/extensions/storage/moz.build ++++ b/toolkit/components/extensions/storage/moz.build +@@ -11,7 +11,7 @@ + # a delegate for consumers to use instead. Android Components can then provide + # an implementation of the delegate that's backed by the Rust component. For + # details, please see bug 1626506, comment 4. +-if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": ++if False: + EXTRA_JS_MODULES += [ + "ExtensionStorageComponents.sys.mjs", + ] +--- a/toolkit/library/rust/shared/Cargo.toml ++++ b/toolkit/library/rust/shared/Cargo.toml +@@ -112,7 +112,7 @@ + [target.'cfg(any(target_os = "macos",target_os = "ios", all(target_os = "windows", not(target_arch = "aarch64"))))'.dependencies] + osclientcerts = { path = "../../../../security/manager/ssl/osclientcerts" } + +-[target.'cfg(not(target_os = "android"))'.dependencies] ++[target.'cfg(target_os = "android")'.dependencies] + gkrust-uniffi-components = { path = "../../../components/uniffi-bindgen-gecko-js/components/", features = ["xpcom"] } + gkrust-uniffi-fixtures = { path = "../../../components/uniffi-bindgen-gecko-js/fixtures/", features = ["xpcom"], optional = true } + viaduct = "0.1" +--- a/toolkit/library/rust/shared/lib.rs ++++ b/toolkit/library/rust/shared/lib.rs +@@ -55,7 +55,6 @@ + + extern crate audio_thread_priority; + +-#[cfg(not(target_os = "android"))] + extern crate webext_storage; + + #[cfg(feature = "webrtc")] +@@ -91,13 +90,11 @@ + all(target_os = "windows", not(target_arch = "aarch64"))))] + extern crate osclientcerts; + +-#[cfg(not(target_os = "android"))] + extern crate gkrust_uniffi_components; + + #[cfg(feature = "uniffi_fixtures")] + extern crate gkrust_uniffi_fixtures; + +-#[cfg(not(target_os = "android"))] + extern crate viaduct; + + extern crate gecko_logger; + diff --git a/packages/spidermonkey/0013-fix-rust-compile.patch b/packages/spidermonkey/0013-fix-rust-compile.patch deleted file mode 100644 index c90e443dc1f..00000000000 --- a/packages/spidermonkey/0013-fix-rust-compile.patch +++ /dev/null @@ -1,52 +0,0 @@ ---- a/toolkit/components/extensions/storage/moz.build -+++ b/toolkit/components/extensions/storage/moz.build -@@ -17,7 +17,7 @@ - # a delegate for consumers to use instead. Android Components can then provide - # an implementation of the delegate that's backed by the Rust component. For - # details, please see bug 1626506, comment 4. --if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": -+if False: - EXPORTS.mozilla.extensions.storage += [ - "ExtensionStorageComponents.h", - ] ---- a/toolkit/library/rust/shared/Cargo.toml -+++ b/toolkit/library/rust/shared/Cargo.toml -@@ -104,7 +104,6 @@ - # Since we're building with at least rustc 1.63, enable rust 1.57 features (use of try_reserve methods). - fallible_collections = { version = "0.4", features = ["rust_1_57"] } - --[target.'cfg(not(target_os = "android"))'.dependencies] - viaduct = "0.1" - webext_storage_bridge = { path = "../../../components/extensions/storage/webext_storage_bridge" } - tabs = { version = "0.1" } ---- a/toolkit/library/rust/shared/lib.rs -+++ b/toolkit/library/rust/shared/lib.rs -@@ -51,20 +51,16 @@ - - extern crate audio_thread_priority; - --#[cfg(not(target_os = "android"))] - extern crate webext_storage_bridge; - --#[cfg(not(target_os = "android"))] - extern crate tabs; - --#[cfg(not(target_os = "android"))] - mod reexport_appservices_uniffi_scaffolding { - tabs::uniffi_reexport_scaffolding!(); - relevancy::uniffi_reexport_scaffolding!(); - suggest::uniffi_reexport_scaffolding!(); - } - --#[cfg(not(target_os = "android"))] - extern crate suggest; - - #[cfg(feature = "webrtc")] -@@ -93,7 +88,6 @@ - extern crate l10nregistry_ffi; - extern crate localization_ffi; - --#[cfg(not(target_os = "android"))] - extern crate viaduct; - - extern crate gecko_logger; diff --git a/packages/spidermonkey/0014-unset-thread-priority-scheduling-macro.patch b/packages/spidermonkey/0013-unset-thread-priority-scheduling-macro.patch similarity index 100% rename from packages/spidermonkey/0014-unset-thread-priority-scheduling-macro.patch rename to packages/spidermonkey/0013-unset-thread-priority-scheduling-macro.patch diff --git a/packages/spidermonkey/0015-skia-termux.patch b/packages/spidermonkey/0014-skia-termux.patch similarity index 100% rename from packages/spidermonkey/0015-skia-termux.patch rename to packages/spidermonkey/0014-skia-termux.patch diff --git a/packages/spidermonkey/0016-protobuf-no-android-log.patch b/packages/spidermonkey/0015-protobuf-no-android-log.patch similarity index 100% rename from packages/spidermonkey/0016-protobuf-no-android-log.patch rename to packages/spidermonkey/0015-protobuf-no-android-log.patch diff --git a/packages/spidermonkey/0018-fix-include-dir-for-cpufeatures.patch b/packages/spidermonkey/0016-fix-include-dir-for-cpufeatures.patch similarity index 100% rename from packages/spidermonkey/0018-fix-include-dir-for-cpufeatures.patch rename to packages/spidermonkey/0016-fix-include-dir-for-cpufeatures.patch diff --git a/packages/spidermonkey/0019-fix-sigbus.patch b/packages/spidermonkey/0017-fix-sigbus.patch similarity index 100% rename from packages/spidermonkey/0019-fix-sigbus.patch rename to packages/spidermonkey/0017-fix-sigbus.patch diff --git a/packages/spidermonkey/0017-icu-74.patch b/packages/spidermonkey/0017-icu-74.patch deleted file mode 100644 index 5e2b2256776..00000000000 --- a/packages/spidermonkey/0017-icu-74.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/intl/lwbrk/LineBreaker.cpp -+++ b/intl/lwbrk/LineBreaker.cpp -@@ -434,7 +434,13 @@ - /* REGIONAL_INDICATOR = 39, [RI] */ CLASS_CHARACTER, - /* E_BASE = 40, [EB] */ CLASS_BREAKABLE, - /* E_MODIFIER = 41, [EM] */ CLASS_CHARACTER, -- /* ZWJ = 42, [ZWJ]*/ CLASS_CHARACTER}; -+ /* ZWJ = 42, [ZWJ]*/ CLASS_CHARACTER, -+ /* AKSARA = 43, [AK] */ CLASS_CHARACTER, -+ /* AKSARA_PREBASE = 44, [AP] */ CLASS_CHARACTER, -+ /* AKSARA_START = 45, [AS] */ CLASS_CHARACTER, -+ /* VIRAMA_FINAL = 46, [VF] */ CLASS_CHARACTER, -+ /* VIRAMA = 47, [VI] */ CLASS_CHARACTER, -+ }; - - static_assert(U_LB_COUNT == mozilla::ArrayLength(sUnicodeLineBreakToClass), - "Gecko vs ICU LineBreak class mismatch"); diff --git a/packages/spidermonkey/0020-do-not-call-get-pci-status.patch b/packages/spidermonkey/0018-do-not-call-get-pci-status.patch similarity index 100% rename from packages/spidermonkey/0020-do-not-call-get-pci-status.patch rename to packages/spidermonkey/0018-do-not-call-get-pci-status.patch diff --git a/packages/spidermonkey/0021-fix-missing-include-for-zlib-module.patch b/packages/spidermonkey/0019-fix-missing-include-for-zlib-module.patch similarity index 100% rename from packages/spidermonkey/0021-fix-missing-include-for-zlib-module.patch rename to packages/spidermonkey/0019-fix-missing-include-for-zlib-module.patch diff --git a/packages/spidermonkey/0022-configure-no-pack-relative-relocs.patch b/packages/spidermonkey/0020-configure-no-pack-relative-relocs.patch similarity index 100% rename from packages/spidermonkey/0022-configure-no-pack-relative-relocs.patch rename to packages/spidermonkey/0020-configure-no-pack-relative-relocs.patch diff --git a/packages/spidermonkey/0023-check-if-dlpi_name-is-nullptr-before-using.patch b/packages/spidermonkey/0022-check-if-dlpi_name-is-nullptr-before-using.patch similarity index 100% rename from packages/spidermonkey/0023-check-if-dlpi_name-is-nullptr-before-using.patch rename to packages/spidermonkey/0022-check-if-dlpi_name-is-nullptr-before-using.patch diff --git a/packages/spidermonkey/0024-fix-missing-include-for-macro-MOZ_TRY-in-TimeZone.cpp.patch b/packages/spidermonkey/0023-fix-missing-include-for-macro-MOZ_TRY-in-TimeZone.cpp.patch similarity index 100% rename from packages/spidermonkey/0024-fix-missing-include-for-macro-MOZ_TRY-in-TimeZone.cpp.patch rename to packages/spidermonkey/0023-fix-missing-include-for-macro-MOZ_TRY-in-TimeZone.cpp.patch diff --git a/packages/spidermonkey/0025-disable-native-https-dns-resolve.patch b/packages/spidermonkey/0024-disable-native-https-dns-resolve.patch similarity index 86% rename from packages/spidermonkey/0025-disable-native-https-dns-resolve.patch rename to packages/spidermonkey/0024-disable-native-https-dns-resolve.patch index 166198d1a7a..11216900569 100644 --- a/packages/spidermonkey/0025-disable-native-https-dns-resolve.patch +++ b/packages/spidermonkey/0024-disable-native-https-dns-resolve.patch @@ -2,7 +2,7 @@ It uses `android_res_nquery`, but this function only got added in API level 29 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp -@@ -237,6 +237,8 @@ +@@ -210,6 +210,8 @@ // native HTTPS records on Win 11 for now. sNativeHTTPSSupported = StaticPrefs::network_dns_native_https_query_win10() || mozilla::IsWin11OrLater(); @@ -13,15 +13,17 @@ It uses `android_res_nquery`, but this function only got added in API level 29 sNativeHTTPSSupported = jni::GetAPIVersion() >= 29; --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build -@@ -59,15 +59,7 @@ +@@ -53,17 +53,7 @@ "nsEffectiveTLDService.cpp", # Excluded from UNIFIED_SOURCES due to special build flags. ] -if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": - SOURCES += ["PlatformDNSWin.cpp"] --elif CONFIG["OS_TARGET"] == "Linux" or CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": +-elif CONFIG["OS_TARGET"] == "Linux": - SOURCES += ["PlatformDNSUnix.cpp"] - OS_LIBS += ["resolv"] +-elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": +- SOURCES += ["PlatformDNSMac.cpp"] -elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": - SOURCES += ["PlatformDNSAndroid.cpp"] -else: @@ -30,3 +32,4 @@ It uses `android_res_nquery`, but this function only got added in API level 29 UNIFIED_SOURCES += [ "ChildDNSService.cpp", + diff --git a/packages/spidermonkey/0026-partially-disable-unified-builds.patch b/packages/spidermonkey/0025-partially-disable-unified-builds.patch similarity index 100% rename from packages/spidermonkey/0026-partially-disable-unified-builds.patch rename to packages/spidermonkey/0025-partially-disable-unified-builds.patch diff --git a/packages/spidermonkey/build.sh b/packages/spidermonkey/build.sh index 4bb8236e535..7915ed7cb39 100644 --- a/packages/spidermonkey/build.sh +++ b/packages/spidermonkey/build.sh @@ -2,14 +2,18 @@ TERMUX_PKG_HOMEPAGE=https://spidermonkey.dev TERMUX_PKG_DESCRIPTION="Mozilla's JavaScript engine" TERMUX_PKG_LICENSE="MPL-2.0" TERMUX_PKG_MAINTAINER="@termux" -TERMUX_PKG_VERSION="128.10.0" -TERMUX_PKG_REVISION=2 -_REAL_VERSION=${TERMUX_PKG_VERSION}esr -TERMUX_PKG_SRCURL=https://archive.mozilla.org/pub/firefox/releases/$_REAL_VERSION/source/firefox-$_REAL_VERSION.source.tar.xz -TERMUX_PKG_SHA256=2ed83e26e41a8b3e2c7c0d13448a84dbb9b7ed65ed46bc162d629b0c6b071caf +TERMUX_PKG_VERSION="140.7.0" +TERMUX_PKG_REVISION=1 +TERMUX_PKG_SRCURL="https://archive.mozilla.org/pub/firefox/releases/${TERMUX_PKG_VERSION}esr/source/firefox-${TERMUX_PKG_VERSION}esr.source.tar.xz" +TERMUX_PKG_SHA256=608a739071726f30236f7100ec5e30e1b8ec342d4e91e715948c287909cb1529 TERMUX_PKG_DEPENDS="libicu, libnspr, libnss, zlib" TERMUX_PKG_BUILD_IN_SRC=true +# Firefox 140's mach only supports Python 3.12 or lower. +_MOZILLA_PYTHON_VERSION=3.12.12 +_MOZILLA_PYTHON_SRCURL=https://www.python.org/ftp/python/${_MOZILLA_PYTHON_VERSION}/Python-${_MOZILLA_PYTHON_VERSION}.tar.xz +_MOZILLA_PYTHON_SHA256=fb85a13414b028c49ba18bbd523c2d055a30b56b18b92ce454ea2c51edc656c4 + termux_step_post_get_source() { local f="media/ffvpx/config_unix_aarch64.h" echo "Applying sed substitution to ${f}" @@ -17,6 +21,10 @@ termux_step_post_get_source() { } termux_step_pre_configure() { + termux_setup_build_python \ + "$_MOZILLA_PYTHON_VERSION" \ + "$_MOZILLA_PYTHON_SRCURL" \ + "$_MOZILLA_PYTHON_SHA256" termux_setup_nodejs termux_setup_rust diff --git a/scripts/build/setup/termux_setup_bpc.sh b/scripts/build/setup/termux_setup_bpc.sh index c73b6abdb71..4ececd510f4 100644 --- a/scripts/build/setup/termux_setup_bpc.sh +++ b/scripts/build/setup/termux_setup_bpc.sh @@ -52,5 +52,11 @@ termux_setup_bpc() { fi export PATH="$_BPC_FOLDER/bin:$PATH" + local _BPC_PYTHON_SITE_PACKAGES + _BPC_PYTHON_SITE_PACKAGES="$(find "$_BPC_FOLDER" -type d -name blueprintcompiler -print -quit)" + if [[ -n "$_BPC_PYTHON_SITE_PACKAGES" ]]; then + _BPC_PYTHON_SITE_PACKAGES="${_BPC_PYTHON_SITE_PACKAGES%/blueprintcompiler}" + export PYTHONPATH="$_BPC_PYTHON_SITE_PACKAGES${PYTHONPATH:+:$PYTHONPATH}" + fi export GI_TYPELIB_PATH="$TERMUX_PREFIX/lib/girepository-1.0" } diff --git a/scripts/build/setup/termux_setup_build_python.sh b/scripts/build/setup/termux_setup_build_python.sh index d762c7362d4..beb0c522b39 100644 --- a/scripts/build/setup/termux_setup_build_python.sh +++ b/scripts/build/setup/termux_setup_build_python.sh @@ -27,9 +27,21 @@ termux_setup_build_python() { local _PYTHON_SRCURL local _PYTHON_SHA256 local _PYTHON_FOLDER - _PYTHON_VERSION="$(. "$TERMUX_SCRIPTDIR/packages/python/build.sh"; echo "$TERMUX_PKG_VERSION")" - _PYTHON_SRCURL="$(. "$TERMUX_SCRIPTDIR/packages/python/build.sh"; echo "$TERMUX_PKG_SRCURL")" - _PYTHON_SHA256="$(. "$TERMUX_SCRIPTDIR/packages/python/build.sh"; echo "$TERMUX_PKG_SHA256")" + local _APPLY_TERMUX_PYTHON_PATCH=true + case "$#" in + 0) + _PYTHON_VERSION="$(. "$TERMUX_SCRIPTDIR/packages/python/build.sh"; echo "$TERMUX_PKG_VERSION")" + _PYTHON_SRCURL="$(. "$TERMUX_SCRIPTDIR/packages/python/build.sh"; echo "$TERMUX_PKG_SRCURL")" + _PYTHON_SHA256="$(. "$TERMUX_SCRIPTDIR/packages/python/build.sh"; echo "$TERMUX_PKG_SHA256")" + ;; + 3) + _PYTHON_VERSION="$1" + _PYTHON_SRCURL="$2" + _PYTHON_SHA256="$3" + _APPLY_TERMUX_PYTHON_PATCH=false + ;; + *) termux_error_exit "termux_setup_build_python expects either zero or three arguments.";; + esac if [[ "${TERMUX_PACKAGES_OFFLINE-false}" = "true" ]]; then _PYTHON_FOLDER=${TERMUX_SCRIPTDIR}/build-tools/python-${_PYTHON_VERSION} else @@ -50,10 +62,12 @@ termux_setup_build_python() { -f "python-$_PYTHON_VERSION.tar.xz" cd "$_PYTHON_FOLDER" - for f in "$TERMUX_SCRIPTDIR"/packages/python/0008-fix-ctypes-util-find_library.patch; do - echo "[${FUNCNAME[0]}]: Applying $(basename "$f")" - cat "$f" | sed -e "s|@@TERMUX_PKG_API_LEVEL@@|${TERMUX_PKG_API_LEVEL}|g" | patch --silent -p1 - done + if [[ "$_APPLY_TERMUX_PYTHON_PATCH" == "true" ]]; then + for f in "$TERMUX_SCRIPTDIR"/packages/python/0008-fix-ctypes-util-find_library.patch; do + echo "[${FUNCNAME[0]}]: Applying $(basename "$f")" + cat "$f" | sed -e "s|@@TERMUX_PKG_API_LEVEL@@|${TERMUX_PKG_API_LEVEL}|g" | patch --silent -p1 + done + fi # Perform a hostbuild of python. We are kind of doing a minimal build, which # may break some stuff that rely on an extended python release. diff --git a/scripts/setup-termux.sh b/scripts/setup-termux.sh index aa0598d2800..7f74215ec1f 100755 --- a/scripts/setup-termux.sh +++ b/scripts/setup-termux.sh @@ -52,11 +52,12 @@ PACKAGES+=" zip" PACKAGES+=" file" # Needed by nchat PYTHON_PACKAGES="" -PYTHON_PACKAGES+=" itstool" # necessary to build orca and some other packages -PYTHON_PACKAGES+=" pygments" # necessary to build mesa (dependency of mako that _must_ be kept `--upgrade`d) -PYTHON_PACKAGES+=" mako" # necessary to build mesa -PYTHON_PACKAGES+=" pyyaml" # necessary to build mesa -PYTHON_PACKAGES+=" setuptools" # necessary to build mesa (explicitly 'system'-wide unlike the setuptools in termux_setup_python_pip) +PYTHON_PACKAGES+=" itstool" # necessary to build orca and some other packages +PYTHON_PACKAGES+=" pygments" # necessary to build mesa (dependency of mako that _must_ be kept `--upgrade`d) +PYTHON_PACKAGES+=" mako" # necessary to build mesa +PYTHON_PACKAGES+=" pyyaml" # necessary to build mesa +PYTHON_PACKAGES+=" setuptools" # necessary to build mesa (explicitly 'system'-wide unlike the setuptools in termux_setup_python_pip) +PYTHON_PACKAGES+=" docutils" # necesary to build gnome-shell # More 'system-wide' python packages should be added here if working towards the goal # of setup-termux.sh for on-device building having closer behavior # to setup-ubuntu.sh for cross-compilation. If adding packages here, please add a comment diff --git a/x11-packages/evolution-data-server/0001-use-pregenerated-iconv-detect.patch b/x11-packages/evolution-data-server/0001-use-pregenerated-iconv-detect.patch new file mode 100644 index 00000000000..1868258dd18 --- /dev/null +++ b/x11-packages/evolution-data-server/0001-use-pregenerated-iconv-detect.patch @@ -0,0 +1,20 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -569,12 +569,17 @@ + message(FATAL_ERROR "You need to install a working iconv implementation, such as ftp://ftp.gnu.org/pub/gnu/libiconv") + endif(NOT HAVE_ICONV) + ++if(CMAKE_CROSSCOMPILING) ++ file(COPY "$ENV{ICONV_DETECT_H}" DESTINATION "${CMAKE_BINARY_DIR}/") ++ set(_correct_iconv 1) ++else() + set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBS}) + file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/" _binary_dir_with_separator) + CHECK_C_SOURCE_RUNS("#define ICONV_DETECT_BUILD_DIR \"${_binary_dir_with_separator}\" + #include \"${CMAKE_SOURCE_DIR}/iconv-detect.c\"" _correct_iconv) + unset(_binary_dir_with_separator) + unset(CMAKE_REQUIRED_LIBRARIES) ++endif() + + if(NOT _correct_iconv) + message(FATAL_ERROR "You need to install a working iconv implementation, such as ftp://ftp.gnu.org/pub/gnu/libiconv") diff --git a/x11-packages/evolution-data-server/0002-do-not-run-configure-test.patch b/x11-packages/evolution-data-server/0002-do-not-run-configure-test.patch new file mode 100644 index 00000000000..991ae8050b9 --- /dev/null +++ b/x11-packages/evolution-data-server/0002-do-not-run-configure-test.patch @@ -0,0 +1,21 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -639,6 +639,10 @@ + # Check to see if strftime supports the use of %l and %k + # ******************************************************* + ++if(CMAKE_CROSSCOMPILING) ++ message(INFO "Cross compiling, assume HAVE_LKSTRFTIME is 0") ++ set(HAVE_LKSTRFTIME 0) ++else() + CHECK_C_SOURCE_RUNS(" + #include + #include +@@ -657,6 +661,7 @@ + return 1; + return 0; + }" HAVE_LKSTRFTIME) ++endif() + + # ****************************** + # system mail stuff diff --git a/x11-packages/evolution-data-server/0003-decode-scheme-use-script.patch b/x11-packages/evolution-data-server/0003-decode-scheme-use-script.patch new file mode 100644 index 00000000000..30d1fbf40f5 --- /dev/null +++ b/x11-packages/evolution-data-server/0003-decode-scheme-use-script.patch @@ -0,0 +1,20 @@ +--- a/data/CMakeLists.txt ++++ b/data/CMakeLists.txt +@@ -35,10 +35,17 @@ + set(CMAKE_REQUIRED_LIBRARIES ${DATA_SERVER_LDFLAGS}) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DG_LOG_DOMAIN=\"oauth2-value-helper\" -DBUILDING_VALUE_HELPER=1) + file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/oauth2-google-client-id" _binary_dir_to_file) ++if(CMAKE_CROSSCOMPILING) ++ execute_process( ++ COMMAND "$ENV{GOOGLE_OAUTH2_SCHEME_SCRIPT}" "${WITH_GOOGLE_CLIENT_ID}" "${_binary_dir_to_file}" ++ ) ++ set(_decoded 1) ++else() + CHECK_C_SOURCE_RUNS("#define DECODE_KEY \"${WITH_GOOGLE_CLIENT_ID}\" + #define DECODE_TO_FILE \"${_binary_dir_to_file}\" + #define DECODE_REVERSED 1 + #include \"${CMAKE_SOURCE_DIR}/src/libedataserver/e-oauth2-service.c\"" _decoded) ++endif() + file(READ ${_binary_dir_to_file} _google_oauth2_scheme) + unset(_binary_dir_to_file) + unset(_decoded) diff --git a/x11-packages/evolution-data-server/0004-use-host-tools.patch b/x11-packages/evolution-data-server/0004-use-host-tools.patch new file mode 100644 index 00000000000..212d3703907 --- /dev/null +++ b/x11-packages/evolution-data-server/0004-use-host-tools.patch @@ -0,0 +1,22 @@ +--- a/src/camel/CMakeLists.txt ++++ b/src/camel/CMakeLists.txt +@@ -20,7 +20,7 @@ + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/camel-mime-tables.c +- COMMAND ${CMAKE_CURRENT_BINARY_DIR}/camel-gen-tables >${CMAKE_CURRENT_BINARY_DIR}/camel-mime-tables.c ++ COMMAND camel-gen-tables >${CMAKE_CURRENT_BINARY_DIR}/camel-mime-tables.c + DEPENDS camel-gen-tables + ) + +--- a/src/addressbook/libebook-contacts/CMakeLists.txt ++++ b/src/addressbook/libebook-contacts/CMakeLists.txt +@@ -25,7 +25,7 @@ + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/e-name-western-tables.h +- COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gen-western-table "${CMAKE_CURRENT_SOURCE_DIR}/e-name-western-tables.h.in" >${CMAKE_CURRENT_BINARY_DIR}/e-name-western-tables.h ++ COMMAND gen-western-table "${CMAKE_CURRENT_SOURCE_DIR}/e-name-western-tables.h.in" >${CMAKE_CURRENT_BINARY_DIR}/e-name-western-tables.h + DEPENDS gen-western-table e-name-western-tables.h.in + ) + diff --git a/x11-packages/evolution-data-server/0005-do-not-use-db_load.patch b/x11-packages/evolution-data-server/0005-do-not-use-db_load.patch new file mode 100644 index 00000000000..7a4291b4534 --- /dev/null +++ b/x11-packages/evolution-data-server/0005-do-not-use-db_load.patch @@ -0,0 +1,14 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -335,7 +335,11 @@ + # db_load checking, it's optional + # ****************************** + ++if(ENABLE_TESTS) + find_program(DB_LOAD NAMES db_load db4_load db_load-4 db5_load db_load-5) ++else() ++set(DB_LOAD 0) ++endif() + if(DB_LOAD) + set(HAVE_DB_LOAD ON) + else(DB_LOAD) diff --git a/x11-packages/evolution-data-server/build.sh b/x11-packages/evolution-data-server/build.sh new file mode 100644 index 00000000000..fcc602c9fc7 --- /dev/null +++ b/x11-packages/evolution-data-server/build.sh @@ -0,0 +1,48 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/evolution/-/wikis/home +TERMUX_PKG_DESCRIPTION="Unified contacts, tasks and calendar backend" +TERMUX_PKG_LICENSE="LGPL-2.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="3.59.2" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/evolution-data-server/${TERMUX_PKG_VERSION%.*}/evolution-data-server-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=15bd798c85dee923462bebe880761b4668b6c5065584e840900988389add83bf +TERMUX_PKG_DEPENDS="glib, gtk3, gtk4, json-glib, krb5, libcanberra, libical, libicu, libnspr, libnss, libsecret, libsoup3, libsqlite, libuuid, libxml2, openldap" +TERMUX_PKG_BUILD_DEPENDS="glib-cross" +TERMUX_PKG_HOSTBUILD=true +TERMUX_PKG_VERSIONED_GIR=false + +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-DCMAKE_SYSTEM_NAME=Linux +-DENABLE_TESTS=OFF +-DWITH_NSPR_INCLUDES=$TERMUX_PREFIX/include +-DWITH_NSS_INCLUDES=$TERMUX_PREFIX/include/nss +-DENABLE_OAUTH2_WEBKITGTK=OFF +-DENABLE_OAUTH2_WEBKITGTK4=OFF +-DENABLE_GOA=OFF +-DENABLE_WEATHER=OFF +-DWITH_LIBDB=OFF +-DENABLE_GTK_DOC=OFF +" + +termux_step_host_build() { + touch evolution-data-server-config.h + gcc -I. \ + "$TERMUX_PKG_SRCDIR"/src/camel/camel-gen-tables.c \ + -o camel-gen-tables + chmod +x camel-gen-tables + gcc -I. \ + $(pkg-config --cflags glib-2.0) \ + "$TERMUX_PKG_SRCDIR"/src/addressbook/libebook-contacts/gen-western-table.c \ + $(pkg-config --libs glib-2.0) \ + -o gen-western-table + chmod +x gen-western-table +} + +termux_step_pre_configure() { + export SENDMAIL_PATH="$TERMUX_PREFIX/bin/sendmail" + export GOOGLE_OAUTH2_SCHEME_SCRIPT="$TERMUX_PKG_BUILDER_DIR/google-scheme-decode.py" + export ICONV_DETECT_H="$TERMUX_PKG_BUILDER_DIR/iconv-detect.h" + + export PATH="$TERMUX_PKG_HOSTBUILD_DIR:$PATH" + + LDFLAGS="-Wl,-rpath=$TERMUX_PREFIX/lib/evolution-data-server $LDFLAGS" +} diff --git a/x11-packages/evolution-data-server/google-scheme-decode.py b/x11-packages/evolution-data-server/google-scheme-decode.py new file mode 100755 index 00000000000..983434e7e01 --- /dev/null +++ b/x11-packages/evolution-data-server/google-scheme-decode.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +import base64 +import sys + +if __name__ == '__main__': + enc = sys.argv[1] + out = sys.argv[2] + + t = base64.b64decode(enc) + t = bytes(map(lambda x: x^t[-1], t[:-1])) + t = b".".join(t.split(b".")[::-1]) + + with open(out, "wb") as fp: + fp.write(t + b"\n") diff --git a/x11-packages/evolution-data-server/iconv-detect.h b/x11-packages/evolution-data-server/iconv-detect.h new file mode 100644 index 00000000000..6694b341c5f --- /dev/null +++ b/x11-packages/evolution-data-server/iconv-detect.h @@ -0,0 +1,5 @@ +/* This is an auto-generated header, DO NOT EDIT! */ + +#define ICONV_ISO_D_FORMAT "iso-%d-%d" +#define ICONV_ISO_S_FORMAT "iso-%d-%s" +#define ICONV_10646 "UCS-4BE" diff --git a/x11-packages/extension-manager/build.sh b/x11-packages/extension-manager/build.sh new file mode 100644 index 00000000000..1c99115f3f7 --- /dev/null +++ b/x11-packages/extension-manager/build.sh @@ -0,0 +1,21 @@ +TERMUX_PKG_HOMEPAGE=https://mattjakeman.com/apps/extension-manager +TERMUX_PKG_DESCRIPTION="Native tool for browsing, installing, and managing GNOME Shell Extensions" +TERMUX_PKG_LICENSE="GPL-3.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="0.6.5" +TERMUX_PKG_SRCURL="https://github.com/mjakeman/extension-manager/archive/refs/tags/v$TERMUX_PKG_VERSION.tar.gz" +TERMUX_PKG_SHA256=5a43c7b9570775948a35f402b1c1d5f0f0401e339ab2144d466f75eae26ef9de +TERMUX_PKG_AUTO_UPDATE=true +TERMUX_PKG_DEPENDS="gtk4, json-glib, libadwaita, libsoup3, libxml2" +TERMUX_PKG_BUILD_DEPENDS="blueprint-compiler, glib, glib-cross, g-ir-scanner, gobject-introspection" +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dpackage=Termux +-Dbacktrace=false +" + +termux_step_pre_configure() { + termux_setup_meson + termux_setup_glib_cross_pkg_config_wrapper + termux_setup_bpc + export TERMUX_MESON_ENABLE_SOVERSION=1 +} diff --git a/x11-packages/gjs/0001-cross-build.patch b/x11-packages/gjs/0001-cross-build.patch new file mode 100644 index 00000000000..3dbbd15d239 --- /dev/null +++ b/x11-packages/gjs/0001-cross-build.patch @@ -0,0 +1,41 @@ +--- a/meson.build ++++ b/meson.build +@@ -255,6 +255,14 @@ + # Check if a minimal SpiderMonkey program compiles, links, and runs. If not, + # it's most likely the case that SpiderMonkey was configured incorrectly, for + # example by building mozglue as a shared library. ++recommended_configuration = ''' Check the recommended configuration: ++https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr91/docs/Building%20SpiderMonkey.md''' ++if meson.is_cross_build() ++ warning('''This is a cross build. A check that a minimal ++SpiderMonkey program executes will not be performed. Before shipping GJS, you ++should check that it does not crash on startup, since building SpiderMonkey with ++the wrong configuration may cause that.''' + recommended_configuration) ++else + minimal_program = cxx.run(''' + #include + int main(void) { +@@ -266,22 +274,16 @@ + args: debug_arg, dependencies: spidermonkey, + name: 'SpiderMonkey sanity check') + +-recommended_configuration = ''' Check the recommended configuration: +-https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr91/docs/Building%20SpiderMonkey.md''' + if not minimal_program.compiled() + error('''A minimal SpiderMonkey program + could not be compiled or linked. Most likely you should build it with a + different configuration.''' + recommended_configuration) +-elif meson.is_cross_build() +- warning('''This is a cross build. A check that a minimal +-SpiderMonkey program executes will not be performed. Before shipping GJS, you +-should check that it does not crash on startup, since building SpiderMonkey with +-the wrong configuration may cause that.''' + recommended_configuration) + elif minimal_program.returncode() != 0 + error('''A minimal SpiderMonkey program + failed to execute. Most likely you should build it with a different + configuration.''' + recommended_configuration) + endif ++endif + + ### Check for external programs ################################################ + diff --git a/x11-packages/gjs/0002-skip-tests.patch b/x11-packages/gjs/0002-skip-tests.patch new file mode 100644 index 00000000000..ad436941f28 --- /dev/null +++ b/x11-packages/gjs/0002-skip-tests.patch @@ -0,0 +1,27 @@ +--- a/meson.build ++++ b/meson.build +@@ -644,6 +644,7 @@ + + ### Tests and test setups ###################################################### + ++if not get_option('skip_tests') + # External code should not error out even when building with -Werror + gi_tests = subproject('gobject-introspection-tests', + default_options: ['werror=false', 'cairo=true', +@@ -695,6 +696,8 @@ + zeal11_environment.set('JS_GC_ZEAL', '11') + add_test_setup('post_verify', timeout_multiplier: 2, env: zeal11_environment) + ++endif ++ + ### Warn about conditions that may affect runtime ############################## + + if tests_locale == 'C' or tests_locale == 'N/A' +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -24,3 +24,5 @@ + description: 'Skip tests that need a display connection') + option('verbose_logs', type: 'boolean', value: false, + description: 'Enable extra log messages that may decrease performance (not allowed in release builds)') ++option('skip_tests', type: 'boolean', value: false, ++ description: 'Skip all the tests') diff --git a/x11-packages/gjs/build.sh b/x11-packages/gjs/build.sh new file mode 100644 index 00000000000..cf793aa870f --- /dev/null +++ b/x11-packages/gjs/build.sh @@ -0,0 +1,31 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gjs +TERMUX_PKG_DESCRIPTION="JavaScript Bindings for GNOME" +TERMUX_PKG_LICENSE="MIT, LGPL-2.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="1.87.1" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gjs/${TERMUX_PKG_VERSION%.*}/gjs-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=95f7a73acc5b2dbe643a6851ba5b4001230a75e1e859afc9a81fbf4861b899cf +TERMUX_PKG_DEPENDS="glib, gobject-introspection, libcairo, libffi, libx11, readline, spidermonkey" +TERMUX_PKG_BUILD_DEPENDS="g-ir-scanner, glib-cross" +TERMUX_PKG_VERSIONED_GIR=false +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Db_pch=false +-Dinstalled_tests=false +-Dskip_dbus_tests=true +-Dskip_gtk_tests=true +-Dskip_tests=true +" + +termux_step_post_get_source() { + # Do not use meson wrap projects + rm -f subprojects/*.wrap +} + +termux_step_pre_configure() { + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper + + CPPFLAGS+=" -DXP_UNIX=1" + + export TERMUX_MESON_ENABLE_SOVERSION=1 +} diff --git a/x11-packages/gjs/gir/GjsPrivate-1.0.xml b/x11-packages/gjs/gir/GjsPrivate-1.0.xml new file mode 100644 index 00000000000..6a6516ea128 --- /dev/null +++ b/x11-packages/gjs/gir/GjsPrivate-1.0.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/gnome-autoar/build.sh b/x11-packages/gnome-autoar/build.sh new file mode 100644 index 00000000000..cb8420ac872 --- /dev/null +++ b/x11-packages/gnome-autoar/build.sh @@ -0,0 +1,20 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-autoar +TERMUX_PKG_DESCRIPTION="gnome-autoar provides functions and widgets for GNOME applications" +TERMUX_PKG_LICENSE="GPL-3.0" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="0.4.5" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-autoar/${TERMUX_PKG_VERSION%.*}/gnome-autoar-${TERMUX_PKG_VERSION}.tar.xz" +TERMUX_PKG_SHA256=838c5306fc38bfaa2f23abe24262f4bf15771e3303fb5dcb74f5b9c7a615dabe +TERMUX_PKG_BUILD_DEPENDS="pkg-config, xorgproto, libxml2, libffi, libarchive" +TERMUX_PKG_DEPENDS="python, pygobject, gtk4, desktop-file-utils" +TERMUX_PKG_PYTHON_COMMON_DEPS="docutils" +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dintrospection=disabled +-Dgtk_doc=false +-Dtests=false +" + +termux_step_pre_configure() { + termux_setup_cmake + termux_setup_meson +} diff --git a/x11-packages/gnome-browser-connector/build.sh b/x11-packages/gnome-browser-connector/build.sh new file mode 100644 index 00000000000..8e768bf4790 --- /dev/null +++ b/x11-packages/gnome-browser-connector/build.sh @@ -0,0 +1,53 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-browser-connector +TERMUX_PKG_DESCRIPTION="GNOME Shell browser connector" +TERMUX_PKG_LICENSE="GPL-3.0" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="42.1" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-browser-connector/${TERMUX_PKG_VERSION%%.*}/gnome-browser-connector-${TERMUX_PKG_VERSION}.tar.xz" +TERMUX_PKG_SHA256=bd9702ce1c163606ca32b8c13d1f3ba6e82b247cf87aac60610b411df1556212 +TERMUX_PKG_DEPENDS="python, pygobject" +TERMUX_PKG_SUGGESTS="firefox" +TERMUX_PKG_AUTO_UPDATE=true + +termux_step_pre_configure() { + termux_setup_meson + + # Fix incorrect install path by overriding py.get_install_dir() + sed -i \ + "s|py.get_install_dir()|join_paths(get_option('prefix'), 'lib', 'python${TERMUX_PYTHON_VERSION}', 'site-packages')|" \ + "$TERMUX_PKG_SRCDIR/meson.build" +} + +termux_step_create_debscripts() { + # Post-install script + cat <<-EOF >./postinst + #!$TERMUX_PREFIX/bin/sh + set -e + + # Set up Firefox native messaging host + mkdir -p "\$HOME/.mozilla/native-messaging-hosts" + cp "$TERMUX_PREFIX/lib/mozilla/native-messaging-hosts/org.gnome.chrome_gnome_shell.json" "\$HOME/.mozilla/native-messaging-hosts/" + + # Set up Chromium native messaging host + mkdir -p "\$HOME/.config/chromium/NativeMessagingHosts" + cp "$TERMUX_PREFIX/etc/chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json" "\$HOME/.config/chromium/NativeMessagingHosts/" + + exit 0 + EOF + + # Pre-remove script + cat <<-EOF >./prerm + #!$TERMUX_PREFIX/bin/sh + set -e + + # Clean up Firefox native messaging host + rm -f "\$HOME/.mozilla/native-messaging-hosts/org.gnome.chrome_gnome_shell.json" + + # Clean up Chromium native messaging host + rm -f "\$HOME/.config/chromium/NativeMessagingHosts/org.gnome.chrome_gnome_shell.json" + + exit 0 + EOF + + chmod 0755 ./postinst ./prerm +} diff --git a/x11-packages/gnome-console/build.sh b/x11-packages/gnome-console/build.sh new file mode 100644 index 00000000000..0754b7513c0 --- /dev/null +++ b/x11-packages/gnome-console/build.sh @@ -0,0 +1,18 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/console +TERMUX_PKG_DESCRIPTION="Simple, user-friendly terminal emulator for the GNOME desktop" +TERMUX_PKG_LICENSE="GPL-3.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.2" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-console/${TERMUX_PKG_VERSION%%.*}/gnome-console-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=27b02ce8e890ff8f53ca85db786b1ae35903f0b6327812f0224dc0b54b1088db +TERMUX_PKG_DEPENDS="dconf, gtk4, glib, gsettings-desktop-schemas, hicolor-icon-theme, libadwaita, libnotify, libgtop, libvte, pango" +TERMUX_PKG_BUILD_DEPENDS="glib-cross" +TERMUX_PKG_AUTO_UPDATE=true +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dtests=false +" + +termux_step_pre_configure() { + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper +} diff --git a/x11-packages/gnome-console/default-shell.patch b/x11-packages/gnome-console/default-shell.patch new file mode 100644 index 00000000000..870ebccc115 --- /dev/null +++ b/x11-packages/gnome-console/default-shell.patch @@ -0,0 +1,33 @@ +--- a/data/org.gnome.Console.gschema.xml.in ++++ b/data/org.gnome.Console.gschema.xml.in +@@ -23,7 +23,7 @@ + true + + +- [] ++ ['@TERMUX_PREFIX@/bin/bash'] + + + false +--- a/src/kgx-settings.c ++++ b/src/kgx-settings.c +@@ -638,7 +638,7 @@ kgx_settings_get_shell (KgxSettings *self) + + /* We could probably do something other than /bin/sh */ + shell = g_new0 (char *, 2); +- shell[0] = g_strdup ("/bin/sh"); ++ shell[0] = g_strdup ("@TERMUX_PREFIX@/bin/bash"); + shell[1] = NULL; + g_warning ("No Shell! Defaulting to “%s”", shell[0]); + +--- a/src/kgx-utils.c ++++ b/src/kgx-utils.c +@@ -54,7 +54,7 @@ process_single_arg (const char *const raw_arg) + g_set_str (&argv[0], bin); + } else { + argv = g_new0 (char *, 4); +- g_set_str (&argv[0], "/bin/sh"); ++ g_set_str (&argv[0], "@TERMUX_PREFIX@/bin/sh"); + g_set_str (&argv[1], "-c"); + g_set_str (&argv[2], bin); + } diff --git a/x11-packages/gnome-console/no-format-nonliteral.patch b/x11-packages/gnome-console/no-format-nonliteral.patch new file mode 100644 index 00000000000..5e0a0a0c07a --- /dev/null +++ b/x11-packages/gnome-console/no-format-nonliteral.patch @@ -0,0 +1,14 @@ +--- a/meson.build ++++ b/meson.build +@@ -121,11 +121,7 @@ cc = meson.get_compiler('c') + + foreach arg : [ + '-Wdeclaration-after-statement', +- ['-Werror=format-security', '-Werror=format=2'], + '-Werror=missing-declarations', +- '-Wformat-nonliteral', +- '-Wformat-security', +- '-Werror=format', + '-Winit-self', + '-Wmaybe-uninitialized', + '-Wno-strict-aliasing', diff --git a/x11-packages/gnome-control-center/0001-patch-meson-build.patch b/x11-packages/gnome-control-center/0001-patch-meson-build.patch new file mode 100644 index 00000000000..1f90f25109e --- /dev/null +++ b/x11-packages/gnome-control-center/0001-patch-meson-build.patch @@ -0,0 +1,46 @@ +diff --git a/meson.build b/meson.build +index a7ca06b..8bb074f 100644 +--- a/meson.build ++++ b/meson.build +@@ -20,7 +20,7 @@ control_center_schemadir = join_paths (control_center_datadir, 'glib-2.0', 'sche + + control_center_gettext = meson.project_name() + '-2.0' + +-host_is_linux = host_machine.system().contains('linux') ++host_is_linux = host_machine.system().contains('linux') and not host_machine.system().contains('android') + host_is_linux_not_s390 = host_is_linux and not host_machine.cpu().contains('s390') + + cc = meson.get_compiler('c') +@@ -134,7 +134,6 @@ libgvc = subproject( + libgvc_dep = libgvc.get_variable('libgvc_dep') + + libadwaita_version = '>= 1.8.alpha' +-goa_req_version = '>= 3.51.0' + pulse_req_version = '>= 2.0' + + gtk_dep = dependency( +@@ -191,7 +190,6 @@ gnome_desktop_dep = dependency('gnome-desktop-4') + gnome_bg_dep = dependency('gnome-bg-4') + gnome_rr_dep = dependency('gnome-rr-4') + gnome_settings_dep = dependency('gnome-settings-daemon', version: '>= 48.rc', fallback: 'gsd') +-goa_dep = dependency('goa-1.0', version: goa_req_version, fallback: 'goa') + gsettings_desktop_dep = dependency('gsettings-desktop-schemas', version: '>= 48.alpha') # actually requires commit 9d55553, just after alpha + libxml_dep = dependency('libxml-2.0') + pulse_dep = dependency('libpulse', version: pulse_req_version) +diff --git a/panels/meson.build b/panels/meson.build +index 0877009..7813ae2 100644 +--- a/panels/meson.build ++++ b/panels/meson.build +@@ -18,11 +18,10 @@ panels = [ + 'system', + 'universal-access', + 'wellbeing', +- 'wwan', + ] + + if host_is_linux +- panels += ['network'] ++ panels += ['network', 'wwan'] + endif + + if host_is_linux_not_s390 diff --git a/x11-packages/gnome-control-center/0002-remove-online-accounts-power-printers-from-panel.patch b/x11-packages/gnome-control-center/0002-remove-online-accounts-power-printers-from-panel.patch new file mode 100644 index 00000000000..f88ee176de5 --- /dev/null +++ b/x11-packages/gnome-control-center/0002-remove-online-accounts-power-printers-from-panel.patch @@ -0,0 +1,65 @@ +diff --git a/panels/keyboard/meson.build b/panels/keyboard/meson.build +index cd1b54e..d742787 100644 +--- a/panels/keyboard/meson.build ++++ b/panels/keyboard/meson.build +@@ -47,8 +47,8 @@ endforeach + libkeyboard_shortcuts = static_library( + 'keyboard-shortcuts', + sources: 'keyboard-shortcuts.c', +- include_directories: [top_inc], +- dependencies: deps, ++ include_directories: [top_inc, common_inc], ++ dependencies: deps + [generates_sources_dep], + ) + + keyboard_shortcuts_dep = declare_dependency( +diff --git a/panels/meson.build b/panels/meson.build +index 07c7733..fc37f02 100644 +--- a/panels/meson.build ++++ b/panels/meson.build +@@ -12,9 +12,6 @@ panels = [ + 'mouse', + 'multitasking', + 'notifications', +- 'online-accounts', +- 'power', +- 'printers', + 'privacy', + 'search', + 'sharing', +diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c +index 28a1671..85482e5 100644 +--- a/shell/cc-panel-loader.c ++++ b/shell/cc-panel-loader.c +@@ -47,9 +47,15 @@ extern GType cc_network_panel_get_type (void); + extern GType cc_wifi_panel_get_type (void); + #endif /* BUILD_NETWORK */ + extern GType cc_notifications_panel_get_type (void); ++#ifdef BUILD_ONLINE_ACCOUNTS + extern GType cc_online_accounts_panel_get_type (void); ++#endif /* BUILD_ONLINE_ACCOUNTS */ ++#ifdef BUILD_POWER + extern GType cc_power_panel_get_type (void); ++#endif /* BUILD_POWER */ ++#ifdef BUILD_PRINTERS + extern GType cc_printers_panel_get_type (void); ++#endif /* BUILD_PRINTERS */ + extern GType cc_privacy_panel_get_type (void); + extern GType cc_search_panel_get_type (void); + extern GType cc_sharing_panel_get_type (void); +@@ -101,9 +107,15 @@ static CcPanelLoaderVtable default_panels[] = + PANEL_TYPE("wifi", cc_wifi_panel_get_type, cc_wifi_panel_static_init_func), + #endif + PANEL_TYPE("notifications", cc_notifications_panel_get_type, NULL), ++#ifdef BUILD_ONLINE_ACCOUNTS + PANEL_TYPE("online-accounts", cc_online_accounts_panel_get_type, NULL), ++#endif ++#ifdef BUILD_POWER + PANEL_TYPE("power", cc_power_panel_get_type, NULL), ++#endif ++#ifdef BUILD_PRINTERS + PANEL_TYPE("printers", cc_printers_panel_get_type, NULL), ++#endif + PANEL_TYPE("privacy", cc_privacy_panel_get_type, NULL), + PANEL_TYPE("search", cc_search_panel_get_type, NULL), + PANEL_TYPE("sharing", cc_sharing_panel_get_type, cc_sharing_panel_static_init_func), diff --git a/x11-packages/gnome-control-center/0003-remove-color-management-from-panel.patch b/x11-packages/gnome-control-center/0003-remove-color-management-from-panel.patch new file mode 100644 index 00000000000..fcebb3ccc6d --- /dev/null +++ b/x11-packages/gnome-control-center/0003-remove-color-management-from-panel.patch @@ -0,0 +1,58 @@ +diff --git a/meson.build b/meson.build +index 684fccd..6e4a052 100644 +--- a/meson.build ++++ b/meson.build +@@ -182,7 +182,7 @@ subproject('libgxdp', + ) + + accounts_dep = dependency('accountsservice', version: '>= 23.11.69') +-colord_dep = dependency('colord', version: '>= 0.1.34') ++colord_dep = dependency('colord', version: '>= 0.1.34', required: false) + gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: '>= 2.23.0') + gio_dep = dependency('gio-2.0') + glib_dep = dependency('glib-2.0', version: '>= 2.76.6') +@@ -334,6 +334,8 @@ config_h.set('HAVE_WACOM', host_is_linux_not_s390, + description: 'Define to 1 if Wacom is supportted') + config_h.set('BUILD_THUNDERBOLT', host_is_linux_not_s390, + description: 'Define to 1 to build the Thunderbolt panel') ++config_h.set('HAVE__COLORD', colord_dep.found(), ++ description: 'Build with color management in panel') + + gnome = import('gnome') + i18n = import('i18n') +diff --git a/panels/meson.build b/panels/meson.build +index fc37f02..0877009 100644 +--- a/panels/meson.build ++++ b/panels/meson.build +@@ -7,7 +7,6 @@ dependent_panels = [ + panels = [ + 'applications', + 'background', +- 'color', + 'display', + 'mouse', + 'multitasking', +diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c +index 85482e5..ffeda92 100644 +--- a/shell/cc-panel-loader.c ++++ b/shell/cc-panel-loader.c +@@ -36,7 +36,9 @@ extern GType cc_background_panel_get_type (void); + #ifdef BUILD_BLUETOOTH + extern GType cc_bluetooth_panel_get_type (void); + #endif /* BUILD_BLUETOOTH */ ++#ifdef HAVE__COLORD + extern GType cc_color_panel_get_type (void); ++#endif + extern GType cc_date_time_panel_get_type (void); + extern GType cc_display_panel_get_type (void); + extern GType cc_keyboard_panel_get_type (void); +@@ -97,7 +99,9 @@ static CcPanelLoaderVtable default_panels[] = + #ifdef BUILD_BLUETOOTH + PANEL_TYPE("bluetooth", cc_bluetooth_panel_get_type, NULL), + #endif ++#ifdef HAVE__COLORD + PANEL_TYPE("color", cc_color_panel_get_type, NULL), ++#endif + PANEL_TYPE("display", cc_display_panel_get_type, NULL), + PANEL_TYPE("keyboard", cc_keyboard_panel_get_type, NULL), + PANEL_TYPE("mouse", cc_mouse_panel_get_type, NULL), diff --git a/x11-packages/gnome-control-center/0004-fix-paths.patch b/x11-packages/gnome-control-center/0004-fix-paths.patch new file mode 100644 index 00000000000..e3e4f376d5c --- /dev/null +++ b/x11-packages/gnome-control-center/0004-fix-paths.patch @@ -0,0 +1,70 @@ +diff --git a/build-aux/meson/find_xdg_file.py b/build-aux/meson/find_xdg_file.py +index 8e12823..1704a72 100644 +--- a/build-aux/meson/find_xdg_file.py ++++ b/build-aux/meson/find_xdg_file.py +@@ -21,7 +21,7 @@ if not data_home or data_home == '': + + data_dirs_str = os.getenv('XDG_DATA_DIRS') + if not data_dirs_str or data_dirs_str == '': +- data_dirs_str = '/usr/local/share/:/usr/share/' ++ data_dirs_str = '@TERMUX_PREFIX@/local/share/:@TERMUX_PREFIX@/share/' + + dirs = [] + dirs += [ data_home ] +diff --git a/panels/power/power-panel-scenario-tester.py b/panels/power/power-panel-scenario-tester.py +index 75adc7d..ae34ae6 100755 +--- a/panels/power/power-panel-scenario-tester.py ++++ b/panels/power/power-panel-scenario-tester.py +@@ -62,7 +62,7 @@ class GccDBusTestCase(DBusTestCase): + + if not os.environ.get('UNDER_JHBUILD', False) or klass.upowerd_path == None: + klass.upowerd_path = None +- with open('/usr/share/dbus-1/system-services/org.freedesktop.UPower.service') as f: ++ with open('@TERMUX_PREFIX@/share/dbus-1/system-services/org.freedesktop.UPower.service') as f: + for line in f: + if line.startswith('Exec='): + klass.upowerd_path = line.split('=', 1)[1].strip() +diff --git a/panels/printers/pp-host.c b/panels/printers/pp-host.c +index a31a606..424c935 100644 +--- a/panels/printers/pp-host.c ++++ b/panels/printers/pp-host.c +@@ -256,7 +256,7 @@ _pp_host_get_snmp_devices_thread (GTask *task, + devices = g_ptr_array_new_with_free_func (g_object_unref); + + argv = g_new0 (gchar *, 3); +- argv[0] = g_strdup ("/usr/lib/cups/backend/snmp"); ++ argv[0] = g_strdup ("@TERMUX_PREFIX@/lib/cups/backend/snmp"); + argv[1] = g_strdup (priv->hostname); + + /* Use SNMP to get printer's informations */ +diff --git a/panels/printers/pp-options-dialog.c b/panels/printers/pp-options-dialog.c +index e7416f3..89dccf4 100644 +--- a/panels/printers/pp-options-dialog.c ++++ b/panels/printers/pp-options-dialog.c +@@ -805,8 +805,8 @@ test_page_cb (PpOptionsDialog *self) + + if (self->printer_name) + { +- const gchar *const dirs[] = { "/usr/share/cups", +- "/usr/local/share/cups", ++ const gchar *const dirs[] = { "@TERMUX_PREFIX@/share/cups", ++ "@TERMUX_PREFIX@/local/share/cups", + NULL }; + const gchar *datadir = NULL; + g_autofree gchar *filename = NULL; +diff --git a/panels/system/datetime/tz.h b/panels/system/datetime/tz.h +index feef165..c50fd4d 100644 +--- a/panels/system/datetime/tz.h ++++ b/panels/system/datetime/tz.h +@@ -28,9 +28,9 @@ + G_BEGIN_DECLS + + #ifndef __sun +-# define TZ_DATA_FILE "/usr/share/zoneinfo/zone.tab" ++# define TZ_DATA_FILE "@TERMUX_PREFIX@/share/zoneinfo/zone.tab" + #else +-# define TZ_DATA_FILE "/usr/share/lib/zoneinfo/tab/zone_sun.tab" ++# define TZ_DATA_FILE "@TERMUX_PREFIX@/share/lib/zoneinfo/tab/zone_sun.tab" + #endif + + typedef struct _TzDB TzDB; diff --git a/x11-packages/gnome-control-center/0005-remove-user-and-secure-shell-section.patch b/x11-packages/gnome-control-center/0005-remove-user-and-secure-shell-section.patch new file mode 100644 index 00000000000..a5a91ee5928 --- /dev/null +++ b/x11-packages/gnome-control-center/0005-remove-user-and-secure-shell-section.patch @@ -0,0 +1,177 @@ +diff --git a/panels/system/cc-system-panel.c b/panels/system/cc-system-panel.c +index a81e9a4..bf58e46 100644 +--- a/panels/system/cc-system-panel.c ++++ b/panels/system/cc-system-panel.c +@@ -32,8 +32,10 @@ + #include "datetime/cc-datetime-page.h" + #include "region/cc-region-page.h" + #include "remote-desktop/cc-remote-desktop-page.h" ++#ifndef __ANDROID__ + #include "secure-shell/cc-secure-shell-page.h" + #include "users/cc-users-page.h" ++#endif + + struct _CcSystemPanel + { +@@ -43,9 +45,11 @@ struct _CcSystemPanel + AdwActionRow *datetime_row; + AdwActionRow *region_row; + AdwActionRow *remote_desktop_row; ++#ifndef __ANDROID__ + AdwActionRow *users_row; + + CcSecureShellPage *secure_shell_dialog; ++#endif + AdwNavigationPage *software_updates_group; + }; + +@@ -118,6 +122,7 @@ cc_system_page_open_software_update (CcSystemPanel *self) + g_warning ("Failed to spawn %s: %s", argv[0], error->message); + } + ++#ifndef __ANDROID__ + static void + on_secure_shell_row_clicked (CcSystemPanel *self) + { +@@ -130,7 +135,7 @@ on_secure_shell_row_clicked (CcSystemPanel *self) + + adw_dialog_present (ADW_DIALOG (self->secure_shell_dialog), GTK_WIDGET (self)); + } +- ++#endif + static void + cc_system_panel_class_init (CcSystemPanelClass *klass) + { +@@ -142,19 +147,25 @@ cc_system_panel_class_init (CcSystemPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, datetime_row); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, region_row); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, remote_desktop_row); ++#ifndef __ANDROID__ + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, users_row); ++#endif + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, software_updates_group); + + gtk_widget_class_bind_template_callback (widget_class, cc_system_page_open_software_update); ++#ifndef __ANDROID__ + gtk_widget_class_bind_template_callback (widget_class, on_secure_shell_row_clicked); ++#endif + + g_type_ensure (CC_TYPE_ABOUT_PAGE); + g_type_ensure (CC_TYPE_DATE_TIME_PAGE); + g_type_ensure (CC_TYPE_LIST_ROW); + g_type_ensure (CC_TYPE_REGION_PAGE); + g_type_ensure (CC_TYPE_REMOTE_DESKTOP_PAGE); ++#ifndef __ANDROID__ + g_type_ensure (CC_TYPE_SECURE_SHELL_PAGE); + g_type_ensure (CC_TYPE_USERS_PAGE); ++#endif + } + + static void +@@ -175,5 +186,7 @@ cc_system_panel_init (CcSystemPanel *self) + cc_panel_add_static_subpage (CC_PANEL (self), "datetime", CC_TYPE_DATE_TIME_PAGE); + cc_panel_add_static_subpage (CC_PANEL (self), "region", CC_TYPE_REGION_PAGE); + cc_panel_add_static_subpage (CC_PANEL (self), "remote-desktop", CC_TYPE_REMOTE_DESKTOP_PAGE); ++#ifndef __ANDROID__ + cc_panel_add_static_subpage (CC_PANEL (self), "users", CC_TYPE_USERS_PAGE); ++#endif + } +--- a/panels/system/cc-system-panel.blp ++++ b/panels/system/cc-system-panel.blp +@@ -30,16 +30,6 @@ template $CcSystemPanel: $CcPanel { + action-target: "'datetime'"; + } + +- $CcListRow users_row { +- title: _("_Users"); +- subtitle: _("Add and remove accounts, change password"); +- icon-name: "org.gnome.Settings-users-symbolic"; +- use-underline: true; +- show-arrow: true; +- action-name: "navigation.push"; +- action-target: "'users'"; +- } +- + $CcListRow remote_desktop_row { + title: _("R_emote Desktop"); + subtitle: _("Allow this device to be used remotely"); +@@ -49,14 +39,6 @@ template $CcSystemPanel: $CcPanel { + action-target: "'remote-desktop'"; + } + +- $CcListRow { +- title: _("Se_cure Shell"); +- subtitle: _("SSH network access"); +- icon-name: "utilities-terminal-symbolic"; +- show-arrow: true; +- activated => $on_secure_shell_row_clicked(template); +- } +- + $CcListRow about_row { + title: _("_About"); + subtitle: _("Hardware details and software versions"); +diff --git a/panels/system/meson.build b/panels/system/meson.build +index a9ac171..b8b06b9 100644 +--- a/panels/system/meson.build ++++ b/panels/system/meson.build +@@ -41,23 +41,7 @@ sources = files( + 'remote-desktop/cc-remote-desktop-page.c', + 'remote-desktop/cc-remote-login-page.c', + 'remote-desktop/cc-tls-certificate.c', +- 'secure-shell/cc-secure-shell.c', +- 'secure-shell/cc-secure-shell-page.c', +- 'users/cc-add-user-dialog.c', +- 'users/cc-avatar-chooser.c', +- 'users/cc-crop-area.c', +- 'users/cc-entry-feedback.c', +- 'users/cc-enterprise-login-dialog.c', +- 'users/cc-fingerprint-dialog.c', +- 'users/cc-fingerprint-manager.c', +- 'users/cc-password-dialog.c', +- 'users/cc-realm-manager.c', +- 'users/cc-user-page.c', +- 'users/cc-users-page.c', +- 'users/pw-utils.c', +- 'users/run-passwd.c', +- 'users/user-utils.c', +-) ++ ) + + sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', +@@ -132,8 +116,10 @@ subdir('datetime') + subdir('icons') + subdir('region') + subdir('remote-desktop') ++if host_is_linux + subdir('secure-shell') + subdir('users') ++endif + + panels_libs += static_library( + cappletname, +diff --git a/panels/system/system.gresource.xml b/panels/system/system.gresource.xml +index 6df8843..bc90942 100644 +--- a/panels/system/system.gresource.xml ++++ b/panels/system/system.gresource.xml +@@ -14,20 +14,6 @@ + region/cc-format-chooser.ui + region/cc-format-preview.ui + region/region.css +- remote-desktop/cc-encryption-fingerprint-dialog.ui +- remote-desktop/cc-remote-desktop-page.ui +- remote-desktop/cc-remote-login-page.ui +- remote-desktop/cc-desktop-sharing-page.ui +- remote-desktop/remote-desktop.css +- secure-shell/cc-secure-shell-page.ui +- users/cc-add-user-dialog.ui +- users/cc-avatar-chooser.ui +- users/cc-entry-feedback.ui +- users/cc-enterprise-login-dialog.ui +- users/cc-fingerprint-dialog.ui +- users/cc-password-dialog.ui +- users/cc-users-page.ui +- users/cc-user-page.ui + users/users.css + eff_short_wordlist_2_0.txt + diff --git a/x11-packages/gnome-control-center/0006-don-t-relay-on-pwquality.patch b/x11-packages/gnome-control-center/0006-don-t-relay-on-pwquality.patch new file mode 100644 index 00000000000..6651af4f9b0 --- /dev/null +++ b/x11-packages/gnome-control-center/0006-don-t-relay-on-pwquality.patch @@ -0,0 +1,63 @@ +diff --git a/meson.build b/meson.build +index 22da3cf..ceb222c 100644 +--- a/meson.build ++++ b/meson.build +@@ -208,7 +208,6 @@ x11_dep = dependency('x11', version: '>= 1.8') + xi_dep = dependency('xi', version: '>= 1.2') + epoxy_dep = dependency('epoxy') + gcr_dep = dependency('gcr-4', version: '>= 4.1.0') +-pwquality_dep = dependency('pwquality', version: '>= 1.2.2') + + m_dep = cc.find_library('m') + +diff --git a/panels/system/meson.build b/panels/system/meson.build +index b8b06b9..00a5105 100644 +--- a/panels/system/meson.build ++++ b/panels/system/meson.build +@@ -82,7 +82,6 @@ deps = common_deps + [ + libsecret_dep, + krb_dep, + polkit_gobject_dep, +- pwquality_dep, + udisks2, + ] + +diff --git a/panels/system/remote-desktop/cc-desktop-sharing-page.c b/panels/system/remote-desktop/cc-desktop-sharing-page.c +index 58178d8..9d53d9e 100644 +--- a/panels/system/remote-desktop/cc-desktop-sharing-page.c ++++ b/panels/system/remote-desktop/cc-desktop-sharing-page.c +@@ -44,7 +44,6 @@ + #endif + + #include +-#include + #include + + #include "org.gnome.RemoteDesktop.h" +@@ -418,10 +417,23 @@ setup_desktop_sharing_page (CcDesktopSharingPage *self) + if (username == NULL) + { + struct passwd *pw = getpwuid (getuid ()); +- if (pw != NULL) +- username = g_strdup (pw->pw_name); ++ if (pw != NULL && pw->pw_name != NULL) ++ { ++ username = g_strdup (pw->pw_name); ++ } + else +- g_warning ("Failed to get username: %s", g_strerror (errno)); ++ { ++ const char *env_user = g_getenv ("USER"); ++ if (env_user != NULL) ++ { ++ username = g_strdup (env_user); ++ } ++ else ++ { ++ username = g_strdup ("termux"); ++ g_warning ("Failed to get username, using fallback 'termux': %s", g_strerror (errno)); ++ } ++ } + } + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username); + diff --git a/x11-packages/gnome-control-center/0007-disable-polkit.patch b/x11-packages/gnome-control-center/0007-disable-polkit.patch new file mode 100644 index 00000000000..4e39b083b94 --- /dev/null +++ b/x11-packages/gnome-control-center/0007-disable-polkit.patch @@ -0,0 +1,401 @@ +diff --git a/meson.build b/meson.build +index 052babb..5b2a3af 100644 +--- a/meson.build ++++ b/meson.build +@@ -214,14 +214,22 @@ common_deps = [ + gtk_dep, + ] + +-polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.103') +-# Also verify that polkit ITS files exist: +-# https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/491 +-polkit_files = [ 'gettext/its/polkit.its', 'gettext/its/polkit.loc' ] +-foreach polkit_file: polkit_files +- r = run_command('build-aux/meson/find_xdg_file.py', polkit_file, check: true) +- assert(r.returncode() == 0, 'ITS support missing from polkit, please upgrade or contact your distribution') +-endforeach ++build_with_polkit = get_option('polkit') ++if build_with_polkit ++ polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.103') ++ # Also verify that polkit ITS files exist: ++ # https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/491 ++ polkit_files = [ 'gettext/its/polkit.its', 'gettext/its/polkit.loc' ] ++ foreach polkit_file: polkit_files ++ r = run_command('build-aux/meson/find_xdg_file.py', polkit_file, check: true) ++ assert(r.returncode() == 0, 'ITS support missing from polkit, please upgrade or contact your distribution') ++ endforeach ++else ++ polkit_gobject_dep = dependency('', required: false) ++endif ++ ++config_h.set('HAVE_POLKIT_SUPPORT', build_with_polkit, ++ description: 'Defined if build with polkit') + + # Localisation API + if cc.has_header_symbol('langinfo.h', '_NL_TIME_FIRST_WEEKDAY') +diff --git a/meson_options.txt b/meson_options.txt +index b2302bf..8ea5c57 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -10,3 +10,4 @@ option('profile', type: 'combo', choices: ['default','development'], value: 'def + option('malcontent', type: 'boolean', value: false, description: 'build with malcontent support') + option('distributor_logo', type: 'string', description: 'absolute path to distributor logo for the About panel') + option('dark_mode_distributor_logo', type: 'string', description: 'absolute path to distributor logo dark mode variant') ++option('polkit', type: 'boolean', value: true, description: 'Enable polkit support') +diff --git a/panels/common/cc-hostname-entry.c b/panels/common/cc-hostname-entry.c +index 29e3b98..9838aed 100644 +--- a/panels/common/cc-hostname-entry.c ++++ b/panels/common/cc-hostname-entry.c +@@ -23,8 +23,9 @@ + #include "cc-hostname.h" + #include "cc-hostname-entry.h" + #include "hostname-helper.h" +- ++#ifdef HAVE_POLKIT_SUPPORT + #include ++#endif + + struct _CcHostnameEntry + { +@@ -87,7 +88,7 @@ cc_hostname_entry_constructed (GObject *object) + GPermission *permission; + g_autoptr(GError) error = NULL; + g_autofree gchar *str = NULL; +- ++#ifdef HAVE_POLKIT_SUPPORT + permission = polkit_permission_new_sync ("org.freedesktop.hostname1.set-static-hostname", + NULL, NULL, NULL); + +@@ -108,7 +109,7 @@ cc_hostname_entry_constructed (GObject *object) + g_debug ("Not allowed to change the hostname"); + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + } +- ++#endif + str = cc_hostname_get_display_hostname (cc_hostname_get_default ()); + if (str != NULL) + gtk_editable_set_text (GTK_EDITABLE (self), str); +diff --git a/panels/common/meson.build b/panels/common/meson.build +index 7314235..88420e5 100644 +--- a/panels/common/meson.build ++++ b/panels/common/meson.build +@@ -107,6 +107,7 @@ libdevice_dep = declare_dependency( + link_with: libdevice + ) + ++if build_with_polkit + polkit_conf = configuration_data() + polkit_conf.set('PRIVILEGED_GROUP', get_option('privileged_group')) + configure_file( +@@ -115,3 +116,4 @@ configure_file( + configuration: polkit_conf, + install_dir: join_paths(control_center_datadir, 'polkit-1', 'rules.d') + ) ++endif +diff --git a/panels/system/datetime/cc-datetime-page.c b/panels/system/datetime/cc-datetime-page.c +index 0c51dc3..6060d02 100644 +--- a/panels/system/datetime/cc-datetime-page.c ++++ b/panels/system/datetime/cc-datetime-page.c +@@ -42,7 +42,9 @@ + #include + #include + #include ++#ifdef HAVE_POLKIT_SUPPORT + #include ++#endif + + /* FIXME: This should be "Etc/GMT" instead */ + #define DEFAULT_TZ "Europe/London" +@@ -870,6 +872,7 @@ cc_date_time_page_init (CcDateTimePage *self) + gtk_list_box_invalidate_sort (self->date_box); + + /* add the lock button */ ++#ifdef HAVE_POLKIT_SUPPORT + self->permission = polkit_permission_new_sync (DATETIME_PERMISSION, NULL, NULL, NULL); + self->tz_permission = polkit_permission_new_sync (DATETIME_TZ_PERMISSION, NULL, NULL, NULL); + if (self->permission != NULL) +@@ -882,6 +885,7 @@ cc_date_time_page_init (CcDateTimePage *self) + g_warning ("Your system does not have the '%s' PolicyKit files installed. Please check your installation", + DATETIME_PERMISSION); + } ++#endif + cc_permission_infobar_set_permission (self->permission_infobar, self->permission); + + self->location_settings = g_settings_new (LOCATION_SETTINGS); +diff --git a/panels/system/datetime/meson.build b/panels/system/datetime/meson.build +index cd94085..5202509 100644 +--- a/panels/system/datetime/meson.build ++++ b/panels/system/datetime/meson.build +@@ -7,7 +7,7 @@ i18n.merge_file( + install: true, + install_dir: control_center_desktopdir + ) +- ++if build_with_polkit + polkit = 'org.gnome.controlcenter.@0@.policy'.format(cappletname) + + i18n.merge_file( +@@ -17,7 +17,7 @@ i18n.merge_file( + install: true, + install_dir: join_paths(control_center_datadir, 'polkit-1', 'actions') + ) +- ++endif + gdbus = 'timedated' + gdbus_iface_xml = gdbus + '1-interface.xml' + +diff --git a/panels/system/region/cc-region-page.c b/panels/system/region/cc-region-page.c +index d723018..aa821fe 100644 +--- a/panels/system/region/cc-region-page.c ++++ b/panels/system/region/cc-region-page.c +@@ -46,7 +46,9 @@ + #include + + #include ++#ifdef HAVE_POLKIT_SUPPORT + #include ++#endif + + #define GNOME_SYSTEM_LOCALE_DIR "org.gnome.system.locale" + #define KEY_REGION "region" +@@ -70,8 +72,9 @@ struct _CcRegionPage { + CcListRow *language_row; + CcLanguageChooser *language_chooser; + CcFormatChooser *format_chooser; +- ++#ifdef HAVE_POLKIT_SUPPORT + gboolean login_auto_apply; ++#endif + GPermission *permission; + GDBusProxy *localed; + GDBusProxy *session; +@@ -314,8 +317,10 @@ update_language (CcRegionPage *self, + g_set_str (&self->language, language); + set_accountservice_languages (self); + ++#ifdef HAVE_POLKIT_SUPPORT + if (self->login_auto_apply) + set_system_language (self, language); ++#endif + maybe_notify (self, LC_MESSAGES, language); + + update_user_language_row (self); +@@ -357,8 +362,10 @@ update_region (CcRegionPage *self, + + set_accountservice_languages (self); + ++#ifdef HAVE_POLKIT_SUPPORT + if (self->login_auto_apply) + set_system_region (self, region); ++#endif + + if (region == NULL || region[0] == '\0') { + // Formats (region) are being reset as part of changing the language, +@@ -615,8 +622,12 @@ set_login_button_visibility (CcRegionPage *self) + + g_object_get (self->user_manager, "has-multiple-users", &has_multiple_users, NULL); + ++#ifdef HAVE_POLKIT_SUPPORT + self->login_auto_apply = !has_multiple_users && g_permission_get_allowed (self->permission); + gtk_widget_set_visible (self->login_group, !self->login_auto_apply); ++#else ++ gtk_widget_set_visible (self->login_group, TRUE); ++#endif + + g_signal_handlers_disconnect_by_func (self->user_manager, set_login_button_visibility, self); + } +@@ -696,6 +707,7 @@ localed_proxy_ready (GObject *source, + static void + setup_login_permission (CcRegionPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GError) error = NULL; + gboolean can_acquire; +@@ -730,6 +742,7 @@ setup_login_permission (CcRegionPage *self) + (g_permission_get_allowed (self->permission) || + g_permission_get_can_acquire (self->permission)); + gtk_widget_set_sensitive (self->login_group, can_acquire); ++#endif + } + + static void +@@ -755,6 +768,7 @@ session_proxy_ready (GObject *source, + static void + on_login_formats_row_activated_cb (CcRegionPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + if (g_permission_get_allowed (self->permission)) { + show_format_chooser (self, SYSTEM); + } else if (g_permission_get_can_acquire (self->permission)) { +@@ -763,11 +777,15 @@ on_login_formats_row_activated_cb (CcRegionPage *self) + choose_region_permission_cb, + self); + } ++#else ++ show_format_chooser (self, SYSTEM); ++#endif + } + + static void + on_login_language_row_activated_cb (CcRegionPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + if (g_permission_get_allowed (self->permission)) { + show_language_chooser (self, SYSTEM); + } else if (g_permission_get_can_acquire (self->permission)) { +@@ -776,6 +794,9 @@ on_login_language_row_activated_cb (CcRegionPage *self) + choose_language_permission_cb, + self); + } ++#else ++ show_language_chooser(self, SYSTEM); ++#endif + } + + static void +diff --git a/panels/system/remote-desktop/cc-remote-login-page.c b/panels/system/remote-desktop/cc-remote-login-page.c +index 0e9ba99..a5bc09f 100644 +--- a/panels/system/remote-desktop/cc-remote-login-page.c ++++ b/panels/system/remote-desktop/cc-remote-login-page.c +@@ -22,7 +22,9 @@ + + #include + #include ++#ifdef HAVE_POLKIT_SUPPORT + #include ++#endif + #include + #include + +@@ -43,7 +45,9 @@ + #define REMOTE_DESKTOP_STORE_CREDENTIALS_TIMEOUT_S 2 + #define REMOTE_LOGIN_DBUS_SERVICE "org.gnome.RemoteDesktop.Configuration" + #define REMOTE_LOGIN_OBJECT_PATH "/org/gnome/RemoteDesktop/Configuration" ++#ifdef HAVE_POLKIT_SUPPORT + #define REMOTE_LOGIN_PERMISSION "org.gnome.controlcenter.remote-session-helper" ++#endif + + struct _CcRemoteLoginPage { + AdwBin parent_instance; +@@ -52,7 +56,9 @@ struct _CcRemoteLoginPage { + + AdwSwitchRow *remote_login_row; + GtkWidget *toast_overlay; ++#ifdef HAVE_POLKIT_SUPPORT + CcPermissionInfobar *permission_infobar; ++#endif + AdwActionRow *hostname_row; + AdwActionRow *port_row; + GtkWidget *credentials_group; +@@ -64,7 +70,9 @@ struct _CcRemoteLoginPage { + GTlsCertificate *certificate; + + GCancellable *cancellable; ++#ifdef HAVE_POLKIT_SUPPORT + GPermission *permission; ++#endif + + char *temp_cert_dir; + char *cert_path; +@@ -157,7 +165,9 @@ block_remote_login_row_activation (CcRemoteLoginPage *self) + static void + unblock_remote_login_row_activation (CcRemoteLoginPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + if (g_permission_get_allowed (self->permission)) ++#endif + gtk_widget_set_sensitive (GTK_WIDGET (self->remote_login_row), TRUE); + self->activating_blocked = FALSE; + } +@@ -364,10 +374,12 @@ disable_remote_login_service (CcRemoteLoginPage *self) + static void + on_remote_login_active_changed (CcRemoteLoginPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + if (adw_switch_row_get_active (self->remote_login_row)) + enable_remote_login (self); + else + disable_remote_login_service (self); ++#endif + } + + static void +@@ -451,6 +463,7 @@ hide_password (CcRemoteLoginPage *self) + static void + sync_permissions (CcRemoteLoginPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + if (!g_permission_get_allowed (self->permission)) + { + hide_password (self); +@@ -467,6 +480,7 @@ sync_permissions (CcRemoteLoginPage *self) + if (self->have_credentials) + gtk_widget_set_sensitive (self->credentials_group, TRUE); + } ++#endif + } + + static void +@@ -476,7 +490,9 @@ cc_remote_login_page_dispose (GObject *object) + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); ++#ifdef HAVE_POLKIT_SUPPORT + g_clear_object (&self->permission); ++#endif + + g_clear_pointer (&self->temp_cert_dir, g_free); + g_clear_pointer (&self->cert_path, g_free); +@@ -570,6 +586,7 @@ on_got_rdp_credentials (GObject *source_object, + static void + fetch_credentials (CcRemoteLoginPage *self) + { ++#ifdef HAVE_POLKIT_SUPPORT + g_autoptr(GError) error = NULL; + g_autofree gchar *username = NULL; + g_autofree gchar *password = NULL; +@@ -582,6 +599,7 @@ fetch_credentials (CcRemoteLoginPage *self) + (GAsyncReadyCallback) + on_got_rdp_credentials, + self); ++#endif + } + + static void +@@ -706,6 +724,7 @@ cc_remote_login_page_init (CcRemoteLoginPage *self) + G_CALLBACK (on_remote_login_active_changed), self, + G_CONNECT_SWAPPED); + ++#ifdef HAVE_POLKIT_SUPPORT + self->permission = (GPermission*) polkit_permission_new_sync (REMOTE_LOGIN_PERMISSION, NULL, self->cancellable, &error); + + if (error != NULL) +@@ -718,7 +737,9 @@ cc_remote_login_page_init (CcRemoteLoginPage *self) + g_signal_connect_swapped (self->permission, "notify::allowed", + G_CALLBACK (sync_permissions), + self); +- ++#else ++ self->permission = NULL; ++#endif + g_object_bind_property (self->password_entry, "sensitive", + self->generate_password_button_row, "sensitive", + G_BINDING_SYNC_CREATE); +diff --git a/panels/system/remote-desktop/meson.build b/panels/system/remote-desktop/meson.build +index 16dfc40..579eea2 100644 +--- a/panels/system/remote-desktop/meson.build ++++ b/panels/system/remote-desktop/meson.build +@@ -6,6 +6,7 @@ sources += gnome.gdbus_codegen( + namespace: 'Gsd' + ) + ++if build_with_polkit + polkit_conf = configuration_data() + polkit_conf.set('libexecdir', control_center_libexecdir) + polkit = 'org.gnome.controlcenter.remote-session-helper.policy' +@@ -23,3 +24,4 @@ i18n.merge_file( + install: true, + install_dir: join_paths(control_center_datadir, 'polkit-1', 'actions') + ) ++endif diff --git a/x11-packages/gnome-control-center/0008-remove-udev.patch b/x11-packages/gnome-control-center/0008-remove-udev.patch new file mode 100644 index 00000000000..7d3ebf0c5ff --- /dev/null +++ b/x11-packages/gnome-control-center/0008-remove-udev.patch @@ -0,0 +1,182 @@ +--- a/meson.build ++++ b/meson.build +@@ -208,7 +208,14 @@ libxml_dep = dependency('libxml-2.0') + pulse_dep = dependency('libpulse', version: pulse_req_version) + pulse_mainloop_dep = dependency('libpulse-mainloop-glib', version: pulse_req_version) + upower_glib_dep = dependency('upower-glib', version: '>= 1.90.6') +-gudev_dep = dependency('gudev-1.0', version: '>= 232') ++enable_udev = get_option('udev') ++if enable_udev.enabled() ++ gudev_dep = dependency('gudev-1.0', version: '>= 232') ++else ++ gudev_dep = dependency('', required: false) ++endif ++config_h.set('HAVE_UDEV', enable_udev.enabled(), ++ description: 'Defined if to build with udev support') + epoxy_dep = dependency('epoxy') + gcr_dep = dependency('gcr-4', version: '>= 4.1.0') + +diff --git a/meson_options.txt b/meson_options.txt +index 8ea5c57..17f03ea 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -11,3 +11,4 @@ option('malcontent', type: 'boolean', value: false, description: 'build with mal + option('distributor_logo', type: 'string', description: 'absolute path to distributor logo for the About panel') + option('dark_mode_distributor_logo', type: 'string', description: 'absolute path to distributor logo dark mode variant') + option('polkit', type: 'boolean', value: true, description: 'Enable polkit support') ++option('udev', type: 'feature', value: 'enabled', description: 'Enable udev support') +diff --git a/panels/common/gsd-device-manager.c b/panels/common/gsd-device-manager.c +index 5a5002e..f0d893a 100644 +--- a/panels/common/gsd-device-manager.c ++++ b/panels/common/gsd-device-manager.c +@@ -22,7 +22,9 @@ + #include "config.h" + + #include ++#ifdef HAVE_UDEV + #include ++#endif + + #include "gsd-device-manager.h" + #include "gsd-common-enums.h" +@@ -53,8 +55,10 @@ G_DEFINE_TYPE_WITH_PRIVATE (GsdDevice, gsd_device, G_TYPE_OBJECT) + typedef struct + { + GObject parent_instance; ++#ifdef HAVE_UDEV + GHashTable *devices; + GUdevClient *udev_client; ++#endif + } GsdDeviceManagerPrivate; + + enum { +@@ -75,6 +79,7 @@ enum { + N_SIGNALS + }; + ++#ifdef HAVE_UDEV + /* Index matches GsdDeviceType */ + const gchar *udev_ids[] = { + "ID_INPUT_MOUSE", +@@ -84,6 +89,7 @@ const gchar *udev_ids[] = { + "ID_INPUT_TOUCHSCREEN", + "ID_INPUT_TABLET_PAD", + }; ++#endif + + static guint signals[N_SIGNALS] = { 0 }; + +@@ -272,10 +278,10 @@ gsd_device_manager_finalize (GObject *object) + { + GsdDeviceManager *manager = GSD_DEVICE_MANAGER (object); + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); +- ++#ifdef HAVE_UDEV + g_hash_table_destroy (priv->devices); + g_object_unref (priv->udev_client); +- ++#endif + G_OBJECT_CLASS (gsd_device_manager_parent_class)->finalize (object); + } + +@@ -283,6 +289,7 @@ static GList * + gsd_device_manager_real_list_devices (GsdDeviceManager *manager, + GsdDeviceType type) + { ++#ifdef HAVE_UDEV + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + GsdDeviceType device_type; + GList *devices = NULL; +@@ -299,12 +306,16 @@ gsd_device_manager_real_list_devices (GsdDeviceManager *manager, + } + + return devices; ++#else ++ return NULL; ++#endif + } + + static GsdDevice * + gsd_device_manager_real_lookup_device (GsdDeviceManager *manager, + GdkDevice *gdk_device) + { ++#ifdef HAVE_UDEV + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + GdkDisplay *display = gdk_device_get_display (gdk_device); + const gchar *node_path = NULL; +@@ -351,6 +362,9 @@ gsd_device_manager_real_lookup_device (GsdDeviceManager *manager, + } + + return NULL; ++#else ++ return NULL; ++#endif + } + + static void +@@ -391,6 +405,7 @@ gsd_device_manager_class_init (GsdDeviceManagerClass *klass) + GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE); + } + ++#ifdef HAVE_UDEV + static GsdDeviceType + udev_device_get_device_type (GUdevDevice *device) + { +@@ -505,10 +520,12 @@ udev_event_cb (GUdevClient *client, + remove_device (manager, device); + } + } ++#endif + + static void + gsd_device_manager_init (GsdDeviceManager *manager) + { ++#ifdef HAVE_UDEV + GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager); + const gchar *subsystems[] = { "input", NULL }; + g_autoptr(GList) devices = NULL; +@@ -531,6 +548,7 @@ gsd_device_manager_init (GsdDeviceManager *manager) + if (device_is_evdev (device)) + add_device (manager, device); + } ++#endif + } + + GsdDeviceManager * +diff --git a/panels/system/about/cc-system-details-window.c b/panels/system/about/cc-system-details-window.c +index 028ed1d..05c87f5 100644 +--- a/panels/system/about/cc-system-details-window.c ++++ b/panels/system/about/cc-system-details-window.c +@@ -34,7 +34,9 @@ + #include + #include + #include ++#ifdef HAVE_UDEV + #include ++#endif + + #include + +@@ -623,6 +625,7 @@ get_ram_size_libgtop (void) + return mem.total; + } + ++#ifdef HAVE_UDEV + guint64 + get_ram_size_dmi (void) + { +@@ -646,6 +649,14 @@ get_ram_size_dmi (void) + } + return ram_total; + } ++#else ++guint64 ++get_ram_size_dmi (void) ++{ ++ /* udev not available, return 0 to fall back to libgtop */ ++ return 0; ++} ++#endif + + static void + system_details_window_title_print_padding (const gchar *title, GString *dst_string, gsize maxlen) diff --git a/x11-packages/gnome-control-center/0009-remove-udisk2.patch b/x11-packages/gnome-control-center/0009-remove-udisk2.patch new file mode 100644 index 00000000000..6bfefd8699a --- /dev/null +++ b/x11-packages/gnome-control-center/0009-remove-udisk2.patch @@ -0,0 +1,64 @@ +diff --git a/panels/system/about/cc-system-details-window.c b/panels/system/about/cc-system-details-window.c +index 05c87f5..f8a3f96 100644 +--- a/panels/system/about/cc-system-details-window.c ++++ b/panels/system/about/cc-system-details-window.c +@@ -33,7 +33,11 @@ + #include + #include + #include ++ ++#ifdef HAVE__UDISK2 + #include ++#endif ++ + #ifdef HAVE_UDEV + #include + #endif +@@ -351,12 +355,13 @@ get_os_type (void) + { + if (GLIB_SIZEOF_VOID_P == 8) + /* translators: This is the type of architecture for the OS */ +- return g_strdup_printf (_("64-bit")); ++ return g_strdup_printf ("%s", _("64-bit")); + else + /* translators: This is the type of architecture for the OS */ +- return g_strdup_printf (_("32-bit")); ++ return g_strdup_printf ("%s", _("32-bit")); + } + ++#ifdef HAVE__UDISK2 + char * + get_primary_disk_info (void) + { +@@ -401,6 +406,15 @@ get_primary_disk_info (void) + + return NULL; + } ++#else ++char * ++get_primary_disk_info (void) ++{ ++ /* UDisks2 not available, return NULL or some default value */ ++ g_warning("UDisks2 is not available. Disk information will not be available."); ++ return NULL; ++} ++#endif + + char * + get_hardware_model_string (void) +diff --git a/panels/system/meson.build b/panels/system/meson.build +index b8b06b9..bbc7b8b 100644 +--- a/panels/system/meson.build ++++ b/panels/system/meson.build +@@ -64,8 +64,9 @@ sources += gnome.mkenums_simple( + libsecret_dep = dependency('libsecret-1') + gnutls_dep = dependency('gnutls') + libgtop2 = dependency('libgtop-2.0') +-udisks2 = dependency('udisks2', version: '>= 2.8.2') +- ++udisks2 = dependency('udisks2', version: '>= 2.8.2', required: false) ++config_h.set('HAVE__UDISK2', udisks2.found(), ++ description: 'Build with udisk2') + # Kerberos support + krb_dep = dependency('krb5', required: false) + assert(krb_dep.found(), 'kerberos libraries not found in your path') diff --git a/x11-packages/gnome-control-center/0010-fix-locale-issue.patch b/x11-packages/gnome-control-center/0010-fix-locale-issue.patch new file mode 100644 index 00000000000..83e045428fa --- /dev/null +++ b/x11-packages/gnome-control-center/0010-fix-locale-issue.patch @@ -0,0 +1,16 @@ +diff --git a/panels/system/region/cc-format-preview.c b/panels/system/region/cc-format-preview.c +index 895f063..a79e3ba 100644 +--- a/panels/system/region/cc-format-preview.c ++++ b/panels/system/region/cc-format-preview.c +@@ -133,7 +133,11 @@ update_format_examples (CcFormatPreview *self) + else + old_locale = uselocale (locale); + ++#if defined(_NL_MEASUREMENT_MEASUREMENT) && defined(__GLIBC__) + fmt = nl_langinfo (_NL_MEASUREMENT_MEASUREMENT); ++#else ++ fmt = "1"; ++#endif + /* The returned pointer of nl_langinfo could be invalid after switching + locale, so we must use it here. */ + is_imperial = fmt && *fmt == 2; diff --git a/x11-packages/gnome-control-center/0011-remove-remote-desktop.patch b/x11-packages/gnome-control-center/0011-remove-remote-desktop.patch new file mode 100644 index 00000000000..d6294684b28 --- /dev/null +++ b/x11-packages/gnome-control-center/0011-remove-remote-desktop.patch @@ -0,0 +1,109 @@ +diff --git a/meson.build b/meson.build +index 5b2a3af..9dea0a9 100644 +--- a/meson.build ++++ b/meson.build +@@ -342,8 +342,6 @@ config_h.set('HAVE_WACOM', host_is_linux_not_s390, + description: 'Define to 1 if Wacom is supportted') + config_h.set('BUILD_THUNDERBOLT', host_is_linux_not_s390, + description: 'Define to 1 to build the Thunderbolt panel') +-config_h.set('HAVE__COLORD', colord_dep.found(), +- description: 'Build with color management in panel') + + gnome = import('gnome') + i18n = import('i18n') +diff --git a/panels/system/cc-system-panel.c b/panels/system/cc-system-panel.c +index bf58e46..58b33a1 100644 +--- a/panels/system/cc-system-panel.c ++++ b/panels/system/cc-system-panel.c +@@ -31,7 +31,6 @@ + #include "about/cc-about-page.h" + #include "datetime/cc-datetime-page.h" + #include "region/cc-region-page.h" +-#include "remote-desktop/cc-remote-desktop-page.h" + #ifndef __ANDROID__ + #include "secure-shell/cc-secure-shell-page.h" + #include "users/cc-users-page.h" +@@ -44,7 +43,6 @@ struct _CcSystemPanel + AdwActionRow *about_row; + AdwActionRow *datetime_row; + AdwActionRow *region_row; +- AdwActionRow *remote_desktop_row; + #ifndef __ANDROID__ + AdwActionRow *users_row; + +@@ -146,7 +144,6 @@ cc_system_panel_class_init (CcSystemPanelClass *klass) + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, about_row); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, datetime_row); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, region_row); +- gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, remote_desktop_row); + #ifndef __ANDROID__ + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, users_row); + #endif +@@ -161,7 +158,6 @@ cc_system_panel_class_init (CcSystemPanelClass *klass) + g_type_ensure (CC_TYPE_DATE_TIME_PAGE); + g_type_ensure (CC_TYPE_LIST_ROW); + g_type_ensure (CC_TYPE_REGION_PAGE); +- g_type_ensure (CC_TYPE_REMOTE_DESKTOP_PAGE); + #ifndef __ANDROID__ + g_type_ensure (CC_TYPE_SECURE_SHELL_PAGE); + g_type_ensure (CC_TYPE_USERS_PAGE); +@@ -176,16 +172,11 @@ cc_system_panel_init (CcSystemPanel *self) + g_resources_register (cc_system_get_resource ()); + gtk_widget_init_template (GTK_WIDGET (self)); + +- service_state = cc_get_service_state (REMOTE_DESKTOP_SERVICE, G_BUS_TYPE_SYSTEM); +- /* Hide the remote-desktop page if the g-r-d service is either "masked", "static", or "not-found". */ +- gtk_widget_set_visible (GTK_WIDGET (self->remote_desktop_row), service_state == CC_SERVICE_STATE_ENABLED || +- service_state == CC_SERVICE_STATE_DISABLED); + gtk_widget_set_visible (GTK_WIDGET (self->software_updates_group), show_software_updates_group (self)); + + cc_panel_add_static_subpage (CC_PANEL (self), "about", CC_TYPE_ABOUT_PAGE); + cc_panel_add_static_subpage (CC_PANEL (self), "datetime", CC_TYPE_DATE_TIME_PAGE); + cc_panel_add_static_subpage (CC_PANEL (self), "region", CC_TYPE_REGION_PAGE); +- cc_panel_add_static_subpage (CC_PANEL (self), "remote-desktop", CC_TYPE_REMOTE_DESKTOP_PAGE); + #ifndef __ANDROID__ + cc_panel_add_static_subpage (CC_PANEL (self), "users", CC_TYPE_USERS_PAGE); + #endif +diff --git a/panels/system/meson.build b/panels/system/meson.build +index b8b06b9..d63007e 100644 +--- a/panels/system/meson.build ++++ b/panels/system/meson.build +@@ -1,6 +1,7 @@ + panels_list += cappletname + desktop = 'gnome-@0@-panel.desktop'.format(cappletname) + ++if build_with_polkit + i18n.merge_file( + type: 'desktop', + input: desktop + '.in', +@@ -18,6 +19,7 @@ i18n.merge_file( + install: true, + install_dir: join_paths(control_center_datadir, 'polkit-1', 'actions') + ) ++endif + + sources = files( + 'cc-password-utils.c', +@@ -35,13 +37,7 @@ sources = files( + 'region/cc-region-page.c', + 'region/cc-format-chooser.c', + 'region/cc-format-preview.c', +- 'remote-desktop/cc-desktop-sharing-page.c', +- 'remote-desktop/cc-encryption-fingerprint-dialog.c', +- 'remote-desktop/cc-gnome-remote-desktop.c', +- 'remote-desktop/cc-remote-desktop-page.c', +- 'remote-desktop/cc-remote-login-page.c', +- 'remote-desktop/cc-tls-certificate.c', +- ) ++ ) + + sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', +@@ -115,7 +111,6 @@ subdir('about') + subdir('datetime') + subdir('icons') + subdir('region') +-subdir('remote-desktop') + if host_is_linux + subdir('secure-shell') + subdir('users') diff --git a/x11-packages/gnome-control-center/0012-gnomevolumecontrol.patch b/x11-packages/gnome-control-center/0012-gnomevolumecontrol.patch new file mode 100644 index 00000000000..e2b62fe7dac --- /dev/null +++ b/x11-packages/gnome-control-center/0012-gnomevolumecontrol.patch @@ -0,0 +1,13 @@ +Fixes link-time conflict with graphviz + +--- a/subprojects/gvc/meson.build ++++ b/subprojects/gvc/meson.build +@@ -90,7 +90,7 @@ else + error('Installing shared library, but pkglibdir is unset!') + endif + +- libgvc_shared = shared_library('gvc', ++ libgvc_shared = shared_library('gnomevolumecontrol', + sources: libgvc_gir_sources + libgvc_no_gir_sources + libgvc_enums, + dependencies: libgvc_deps, + c_args: c_args, diff --git a/x11-packages/gnome-control-center/0013-disable-accountsservice.patch b/x11-packages/gnome-control-center/0013-disable-accountsservice.patch new file mode 100644 index 00000000000..ddaebaa3cdc --- /dev/null +++ b/x11-packages/gnome-control-center/0013-disable-accountsservice.patch @@ -0,0 +1,123 @@ +diff --git a/meson.build b/meson.build +index 7841741..779aa81 100644 +--- a/meson.build ++++ b/meson.build +@@ -181,7 +181,6 @@ subproject('libgxdp', + default_options: 'tests=false', + ) + +-accounts_dep = dependency('accountsservice', version: '>= 23.11.69') + colord_dep = dependency('colord', version: '>= 0.1.34', required: false) + gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: '>= 2.23.0') + gio_dep = dependency('gio-2.0') +diff --git a/panels/system/meson.build b/panels/system/meson.build +index db0897f..4ba9b20 100644 +--- a/panels/system/meson.build ++++ b/panels/system/meson.build +@@ -68,7 +68,6 @@ krb_dep = dependency('krb5', required: false) + assert(krb_dep.found(), 'kerberos libraries not found in your path') + + deps = common_deps + [ +- accounts_dep, + gcr_dep, + gdk_pixbuf_dep, + gnome_desktop_dep, +diff --git a/panels/system/region/cc-region-page.c b/panels/system/region/cc-region-page.c +index f32c2ad..e63dc93 100644 +--- a/panels/system/region/cc-region-page.c ++++ b/panels/system/region/cc-region-page.c +@@ -34,7 +34,6 @@ + #include "cc-list-row.h" + #include "cc-region-page.h" + +-#include + #include + #include + #include +@@ -78,8 +78,6 @@ struct _CcRegionPage { + GDBusProxy *localed; + GDBusProxy *session; + +- ActUserManager *user_manager; +- ActUser *user; + GSettings *locale_settings; + + gchar *language; +@@ -303,7 +302,6 @@ set_accountservice_languages (CcRegionPage *self) + if (g_strcmp0 (self->language, self->region) != 0 && self->region[0] != '\0') + languages[1] = self->region; + +- act_user_set_languages (self->user, languages); + } + + static void update_user_language_row (CcRegionPage *self); +@@ -547,8 +545,6 @@ update_language_from_user (CcRegionPage *self) + { + const gchar *language = NULL; + +- if (act_user_is_loaded (self->user)) +- language = act_user_get_language (self->user); + + if (language == NULL || *language == '\0') + language = setlocale (LC_MESSAGES, NULL); +@@ -569,12 +565,6 @@ update_region_from_setting (CcRegionPage *self) + static void + setup_language_section (CcRegionPage *self) + { +- self->user = act_user_manager_get_user_by_id (self->user_manager, getuid ()); +- g_signal_connect_object (self->user, "notify::language", +- G_CALLBACK (update_language_from_user), self, G_CONNECT_SWAPPED); +- g_signal_connect_object (self->user, "notify::is-loaded", +- G_CALLBACK (update_language_from_user), self, G_CONNECT_SWAPPED); +- + self->locale_settings = g_settings_new (GNOME_SYSTEM_LOCALE_DIR); + g_signal_connect_object (self->locale_settings, "changed::" KEY_REGION, + G_CALLBACK (update_region_from_setting), self, G_CONNECT_SWAPPED); +@@ -619,11 +609,7 @@ set_login_button_visibility (CcRegionPage *self) + gboolean has_multiple_users; + gboolean loaded; + +- g_object_get (self->user_manager, "is-loaded", &loaded, NULL); +- if (!loaded) +- return; +- +- g_object_get (self->user_manager, "has-multiple-users", &has_multiple_users, NULL); ++ return; + + #ifdef HAVE_POLKIT_SUPPORT + self->login_auto_apply = !has_multiple_users && g_permission_get_allowed (self->permission); +@@ -631,8 +617,6 @@ set_login_button_visibility (CcRegionPage *self) + #else + gtk_widget_set_visible (self->login_group, TRUE); + #endif +- +- g_signal_handlers_disconnect_by_func (self->user_manager, set_login_button_visibility, self); + } + + /* Callbacks */ +@@ -821,16 +805,6 @@ cc_region_page_finalize (GObject *object) + { + CcRegionPage *self = CC_REGION_PAGE (object); + +- if (self->user_manager) { +- g_signal_handlers_disconnect_by_data (self->user_manager, self); +- self->user_manager = NULL; +- } +- +- if (self->user) { +- g_signal_handlers_disconnect_by_data (self->user, self); +- self->user = NULL; +- } +- + g_clear_object (&self->permission); + g_clear_object (&self->localed); + g_clear_object (&self->session); +@@ -881,8 +855,6 @@ cc_region_page_init (CcRegionPage *self) + + self->cancellable = g_cancellable_new (); + +- self->user_manager = act_user_manager_get_default (); +- + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, diff --git a/x11-packages/gnome-control-center/build.sh b/x11-packages/gnome-control-center/build.sh new file mode 100644 index 00000000000..895a399583b --- /dev/null +++ b/x11-packages/gnome-control-center/build.sh @@ -0,0 +1,26 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-control-center +TERMUX_PKG_DESCRIPTION="GNOME's main interface to configure various aspects of the desktop" +TERMUX_PKG_LICENSE="GPL-2.0" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.4" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-control-center/${TERMUX_PKG_VERSION%%.*}/gnome-control-center-${TERMUX_PKG_VERSION}.tar.xz" +TERMUX_PKG_SHA256=131975e5fddd208f3a30f766a1c667f8cb3f15b689a868d285a641a234204b02 +TERMUX_PKG_AUTO_UPDATE=true +TERMUX_PKG_DEPENDS="glib, libglibmm-2.68, libcanberra, gtk4, libadwaita, gobject-introspection, pulseaudio, gettext, gdk-pixbuf, gnome-desktop4, gsettings-desktop-schemas, gnome-settings-daemon, libxml2, upower, gcr4, fontconfig, cups, krb5, ibus, gsound, samba, libsecret" +TERMUX_PKG_BUILD_DEPENDS="blueprint-compiler, glib-cross, glib, libwayland, libepoxy, libwayland-protocols, mutter, libwayland-cross-scanner, libx11, libxi, libgtop, libcairo, libandroid-glob" +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dlocation-services=disabled +-Dsnap=false +-Dtests=false +-Ddocumentation=false +-Dpolkit=false +-Dudev=disabled +" + +termux_step_pre_configure() { + termux_setup_bpc + termux_setup_gir + termux_setup_meson + termux_setup_pkg_config_wrapper "${TERMUX_PREFIX}/opt/glib/cross/lib/x86_64-linux-gnu/pkgconfig:${TERMUX_PREFIX}/opt/libwayland/cross/lib/x86_64-linux-gnu/pkgconfig" + LDFLAGS+=" -landroid-glob" +} diff --git a/x11-packages/gnome-session/0001-no-logind.patch b/x11-packages/gnome-session/0001-no-logind.patch new file mode 100644 index 00000000000..e9d03a8cfb2 --- /dev/null +++ b/x11-packages/gnome-session/0001-no-logind.patch @@ -0,0 +1,149 @@ +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -4,3 +4,4 @@ + option('docbook', type: 'boolean', value: true, description: 'build documentation') + option('man', type: 'boolean', value: true, description: 'build documentation (requires xmlto)') + option('x11', type: 'boolean', value: true, description: 'enable x11 session support') ++option('systemd', type: 'boolean', value: true, description: 'Use systemd') +--- a/meson.build ++++ b/meson.build +@@ -125,7 +125,12 @@ + pkgconfig_define: ['prefix', session_prefix]) + endif + ++if not get_option('systemd') ++libsystemd_dep = dependency('', required : false) ++else + libsystemd_dep = dependency('libsystemd', version: '>= 209', required: true) ++config_h.set(HAVE_SYSTEMD, true) ++endif + session_bin_deps += libsystemd_dep + + configure_file( +--- a/gnome-session/gsm-autostart-app.c ++++ b/gnome-session/gsm-autostart-app.c +@@ -31,8 +31,10 @@ + #define GNOME_DESKTOP_USE_UNSTABLE_API + #include + ++#ifdef HAVE_SYSTEMD + #include + #include ++#endif + + #include "gsm-autostart-app.h" + #include "gsm-util.h" +@@ -966,12 +968,16 @@ on_child_setup (GsmAutostartApp *app) + /* The FALSE means programs aren't expected to prefix each + * line with prefix to specify priority. + */ ++#ifdef HAVE_SYSTEMD + standard_output = sd_journal_stream_fd (priv->desktop_id, + LOG_INFO, + FALSE); + standard_error = sd_journal_stream_fd (priv->desktop_id, + LOG_WARNING, + FALSE); ++#else ++ standard_output = standard_error = -1; ++#endif + + if (standard_output >= 0) { + dup2 (standard_output, STDOUT_FILENO); +@@ -1033,10 +1040,12 @@ autostart_app_start_spawn (GsmAutostartApp *app, + g_app_launch_context_setenv (ctx, "DESKTOP_AUTOSTART_ID", startup_id); + } + ++#ifdef HAVE_SYSTEMD + if (sd_booted () > 0) { + child_setup_func = (GSpawnChildSetupFunc) on_child_setup; + child_setup_data = app; + } ++#endif + + handler = g_signal_connect (ctx, "launched", G_CALLBACK (app_launched), app); + success = g_desktop_app_info_launch_uris_as_manager (priv->app_info, +--- a/gnome-session/gsm-manager.c ++++ b/gnome-session/gsm-manager.c +@@ -40,9 +40,15 @@ + #include "gsm-manager.h" + #include "org.gnome.SessionManager.h" + ++#ifdef HAVE_SYSTEMD + #include + + #include ++#else ++#define sd_notify(...) ++#define sd_notifyf(...) ++#define sd_journal_send(...) ++#endif + + #include "gsm-store.h" + #include "gsm-inhibitor.h" +--- a/gnome-session/gsm-systemd.c ++++ b/gnome-session/gsm-systemd.c +@@ -28,6 +28,7 @@ + #include + #include + ++#ifdef HAVE_SYSTEMD + #include + + #include +@@ -1194,4 +1195,10 @@ sd_proxy_signal_cb (GDBusProxy *proxy, + } + } + +- ++#else ++GsmSystemd * ++gsm_systemd_new (void) ++{ ++ return NULL; ++} ++#endif +--- a/gnome-session/main.c ++++ b/gnome-session/main.c +@@ -43,7 +43,9 @@ + #include "gsm-system.h" + #include "gsm-fail-whale.h" + ++#ifdef HAVE_SYSTEMD + #include ++#endif + + #define GSM_DBUS_NAME "org.gnome.SessionManager" + +@@ -558,6 +560,7 @@ main (int argc, char **argv) + * journald picks ups the nicer "gnome-session" as the program + * name instead of whatever shell script GDM happened to use. + */ ++#ifdef HAVE_SYSTEMD + if (!debug) { + int journalfd; + +@@ -567,6 +570,7 @@ main (int argc, char **argv) + dup2(journalfd, 2); + } + } ++#endif + + gdm_log_init (); + gdm_log_set_debug (debug); +--- a/tools/gnome-session-ctl.c ++++ b/tools/gnome-session-ctl.c +@@ -28,7 +28,12 @@ + #include + #include + #include ++#ifdef HAVE_SYSTEMD + #include ++#else ++#define sd_notify(...) ++#define sd_notifyf(...) ++#endif + + #include + #include + diff --git a/x11-packages/gnome-session/0002-Werror.patch b/x11-packages/gnome-session/0002-Werror.patch new file mode 100644 index 00000000000..e06b3ff3d88 --- /dev/null +++ b/x11-packages/gnome-session/0002-Werror.patch @@ -0,0 +1,13 @@ +--- a/meson.build ++++ b/meson.build +@@ -76,6 +76,10 @@ + + compiler_flags += cc.get_supported_arguments(test_cflags) + endif ++compiler_flags += [ ++ '-Wno-format-nonliteral', ++ '-Wno-format-security' ++] + + add_project_arguments(common_flags + compiler_flags, language: 'c') + diff --git a/x11-packages/gnome-session/0003-force-gnome-settings-daemon-49.patch b/x11-packages/gnome-session/0003-force-gnome-settings-daemon-49.patch new file mode 100644 index 00000000000..085495e586b --- /dev/null +++ b/x11-packages/gnome-session/0003-force-gnome-settings-daemon-49.patch @@ -0,0 +1,13 @@ +Set to FALSE for testing with gnome-settings-daemon 49+ + +--- a/gnome-session/gsm-session-fill.c ++++ b/gnome-session/gsm-session-fill.c +@@ -79,7 +79,7 @@ check_required_components_helper (const char *component, + + if (app_path == NULL) { + g_warning ("Unable to find required component '%s'", component); +- *error = TRUE; ++ *error = TRUE; + } + } + diff --git a/x11-packages/gnome-session/0004-manage-system-dbus.patch b/x11-packages/gnome-session/0004-manage-system-dbus.patch new file mode 100644 index 00000000000..c443420a38d --- /dev/null +++ b/x11-packages/gnome-session/0004-manage-system-dbus.patch @@ -0,0 +1,26 @@ +--- a/gnome-session/main.c ++++ b/gnome-session/main.c +@@ -518,6 +518,23 @@ main (int argc, char **argv) + gsm_util_init_error (TRUE, "%s", error->message); + } + ++ /* Termux gnome-session: Start dbus-daemon --system first before anything */ ++ { ++ const char *pid_file = "@TERMUX_PREFIX@/var/run/dbus/pid"; ++ FILE *f = fopen (pid_file, "r"); ++ if (f) { ++ int pid = 0; ++ if (fscanf (f, "%d", &pid) == 1 && pid > 0) { ++ g_message ("Termux gnome-session: Killing stale system dbus-daemon (pid %d)", pid); ++ kill (pid, SIGTERM); ++ } ++ fclose (f); ++ } ++ remove (pid_file); ++ g_message ("Termux gnome-session: starting system dbus-daemon..."); ++ system ("dbus-daemon --system --fork"); ++ } ++ + /* From 3.14 GDM sets XDG_CURRENT_DESKTOP. For compatibility with + * older versions of GDM, other display managers, and startx, + * set a fallback value if we don't find it set. diff --git a/x11-packages/gnome-session/build.sh b/x11-packages/gnome-session/build.sh new file mode 100644 index 00000000000..cb4141b4be0 --- /dev/null +++ b/x11-packages/gnome-session/build.sh @@ -0,0 +1,26 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-session/ +TERMUX_PKG_DESCRIPTION="The GNOME session manager" +TERMUX_PKG_LICENSE="GPL-2.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="48.0" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-session/${TERMUX_PKG_VERSION%%.*}/gnome-session-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=dd909fbc5b22cdbdb2fc4df1a47d78d1b5943ccc5e61e6a20a1846246347c417 +TERMUX_PKG_DEPENDS="glib, gnome-desktop3, gtk3, json-glib, libice, libsm, libx11" +TERMUX_PKG_RECOMMENDS="gnome-shell" +TERMUX_PKG_BUILD_DEPENDS="glib-cross" +TERMUX_PKG_VERSIONED_GIR=false +# Should be bumped with gnome-shell +TERMUX_PKG_AUTO_UPDATE=false + +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dx11=true +-Dsystemd=false +-Dsystemduserunitdir=$TERMUX_PKG_TMPDIR +" + +termux_step_pre_configure() { + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper + + export TERMUX_MESON_ENABLE_SOVERSION=1 +} diff --git a/x11-packages/gnome-settings-daemon/0001-no-polkit.patch b/x11-packages/gnome-settings-daemon/0001-no-polkit.patch new file mode 100644 index 00000000000..4691cf1aa9b --- /dev/null +++ b/x11-packages/gnome-settings-daemon/0001-no-polkit.patch @@ -0,0 +1,67 @@ +diff --git a/meson.build b/meson.build +index d2900f6..d44251c 100644 +--- a/meson.build ++++ b/meson.build +@@ -111,7 +111,6 @@ libgeoclue_dep = dependency('libgeoclue-2.0', version: '>= 2.3.1') + libnotify_dep = dependency('libnotify', version: '>= 0.7.3') + libpulse_mainloop_glib_dep = dependency('libpulse-mainloop-glib', version: '>= 2.0') + pango_dep = dependency('pango', version: '>= 1.20.0') +-polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.114') + upower_glib_dep = dependency('upower-glib', version: '>= 0.99.12') + x11_dep = dependency('x11') + xfixes_dep = dependency('xfixes', version: '>= 6.0') +diff --git a/plugins/datetime/meson.build b/plugins/datetime/meson.build +index 0562a3e..4a5254f 100644 +--- a/plugins/datetime/meson.build ++++ b/plugins/datetime/meson.build +@@ -24,8 +24,7 @@ deps = plugins_deps + [ + gweather_dep, + libgeoclue_dep, + libnotify_dep, +- m_dep, +- polkit_gobject_dep ++ m_dep + ] + + cflags += ['-DGNOMECC_DATA_DIR="@0@"'.format(gsd_pkgdatadir)] +diff --git a/plugins/wwan/meson.build b/plugins/wwan/meson.build +index a81ce41..7e149d0 100644 +--- a/plugins/wwan/meson.build ++++ b/plugins/wwan/meson.build +@@ -5,7 +5,7 @@ sources = files( + ) + sources += main_helper_sources + +-deps = plugins_deps + [gio_dep, gcr_dep, mm_glib_dep, polkit_gobject_dep] ++deps = plugins_deps + [gio_dep, gcr_dep, mm_glib_dep] + + cflags += ['-DGNOMECC_DATA_DIR="@0@"'.format(gsd_pkgdatadir)] + +diff --git a/plugins/datetime/gsd-timezone-monitor.c b/plugins/datetime/gsd-timezone-monitor.c +index a6e8a48..464ccdf 100644 +--- a/plugins/datetime/gsd-timezone-monitor.c ++++ b/plugins/datetime/gsd-timezone-monitor.c +@@ -27,7 +27,6 @@ + + #include + #include +-#include + + #define DESKTOP_ID "gnome-datetime-panel" + #define SET_TIMEZONE_PERMISSION "org.freedesktop.timedate1.set-timezone" +@@ -434,6 +433,7 @@ gsd_timezone_monitor_init (GsdTimezoneMonitor *self) + + g_debug ("Starting timezone monitor"); + ++#ifndef __ANDROID__ + priv->permission = polkit_permission_new_sync (SET_TIMEZONE_PERMISSION, + NULL, NULL, + &error); +@@ -449,6 +449,7 @@ gsd_timezone_monitor_init (GsdTimezoneMonitor *self) + g_debug ("No permission to set timezone"); + return; + } ++#endif + + priv->cancellable = g_cancellable_new (); + priv->dtm = timedate1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, diff --git a/x11-packages/gnome-settings-daemon/0002-no-gudev.patch b/x11-packages/gnome-settings-daemon/0002-no-gudev.patch new file mode 100644 index 00000000000..d478c13cb27 --- /dev/null +++ b/x11-packages/gnome-settings-daemon/0002-no-gudev.patch @@ -0,0 +1,65 @@ +--- a/plugins/power/gsd-backlight.c ++++ b/plugins/power/gsd-backlight.c +@@ -27,7 +27,7 @@ + #include "gsd-power-constants.h" + #include "gsd-power-manager.h" + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + #include + #endif /* __linux__ */ + +@@ -53,7 +53,7 @@ + + BacklightBackend backend; + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + GDBusProxy *logind_proxy; + + GUdevClient *udev; +@@ -93,7 +93,7 @@ + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + gsd_backlight_initable_iface_init);) + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + static GUdevDevice* + gsd_backlight_udev_get_type (GList *devices, const gchar *type) + { +@@ -511,7 +511,7 @@ + return; + } + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + if (backlight->backend == BACKLIGHT_BACKEND_UDEV) { + BacklightHelperData *task_data; + +@@ -915,7 +915,7 @@ + G_CONNECT_SWAPPED); + maybe_update_mutter_backlight (backlight); + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + backlight->logind_proxy = + g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + 0, +@@ -987,7 +987,7 @@ + { + GsdBacklight *backlight = GSD_BACKLIGHT (object); + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + g_assert (backlight->active_task == NULL); + g_assert (g_queue_is_empty (&backlight->tasks)); + g_clear_object (&backlight->logind_proxy); +@@ -1033,7 +1033,7 @@ + backlight->brightness_val = -1; + backlight->brightness_step = 1; + +-#ifdef __linux__ ++#if defined(__linux__) && defined(HAVE_GUDEV) && HAVE_GUDEV + backlight->active_task = NULL; + g_queue_init (&backlight->tasks); + #endif /* __linux__ */ diff --git a/x11-packages/gnome-settings-daemon/build.sh b/x11-packages/gnome-settings-daemon/build.sh new file mode 100644 index 00000000000..f397c605705 --- /dev/null +++ b/x11-packages/gnome-settings-daemon/build.sh @@ -0,0 +1,46 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-settings-daemon +TERMUX_PKG_DESCRIPTION="GNOME Settings Daemon" +TERMUX_PKG_LICENSE="GPL-2.0-or-later, LGPL-2.1-only" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="48.1" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-settings-daemon/${TERMUX_PKG_VERSION%%.*}/gnome-settings-daemon-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=3860a2ea214dcbcb6600ae7a1e3358a5389215087bc3e4a47cee3f87baee062e +TERMUX_PKG_DEPENDS="alsa-lib, glib, geoclue, geocode-glib, gnome-desktop3, gtk3, libcanberra, libcolord, libgweather, libnotify, libx11, libxfixes, pango, pulseaudio-glib, upower" +TERMUX_PKG_BUILD_DEPENDS="glib-cross" + +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dsystemd=false +-Delogind=false +-Dalsa=true +-Dgudev=false +-Dsmartcard=false +-Dcups=false +-Drfkill=false +-Dwwan=false +-Dwayland=false +-Dnetwork_manager=false +" + +termux_step_pre_configure() { + termux_setup_glib_cross_pkg_config_wrapper + + if [ "$TERMUX_ON_DEVICE_BUILD" = false ]; then + # Create host pkg-config wrapper + mkdir -p "$TERMUX_PKG_TMPDIR"/host-pkg-config + cat > "$TERMUX_PKG_TMPDIR"/host-pkg-config/pkg-config <<-HERE + #!/bin/sh + unset PKG_CONFIG_DIR + unset PKG_CONFIG_LIBDIR + exec /usr/bin/pkg-config "\$@" + HERE + chmod +x "$TERMUX_PKG_TMPDIR"/host-pkg-config/pkg-config + + # Use it in meson + local _native_file="$TERMUX_PKG_TMPDIR"/native-file.ini + echo "[binaries]" > $_native_file + echo "pkg-config = '$TERMUX_PKG_TMPDIR/host-pkg-config/pkg-config'" >> $_native_file + TERMUX_PKG_EXTRA_CONFIGURE_ARGS="--native-file $_native_file $TERMUX_PKG_EXTRA_CONFIGURE_ARGS" + fi + + export TERMUX_MESON_ENABLE_SOVERSION=1 +} diff --git a/x11-packages/gnome-shell/0001-use-gjs-option.patch b/x11-packages/gnome-shell/0001-use-gjs-option.patch new file mode 100644 index 00000000000..6fdcc7434f6 --- /dev/null +++ b/x11-packages/gnome-shell/0001-use-gjs-option.patch @@ -0,0 +1,37 @@ +--- a/meson.build ++++ b/meson.build +@@ -142,7 +142,10 @@ endif + + mutter_typelibdir = mutter_dep.get_variable('typelibdir') + python = find_program('python3') +-gjs = find_program('gjs') ++gjs_path = get_option('gjs_path') ++if gjs_path == '' ++ gjs_path = find_program('gjs').full_path() ++endif + generate_app_list = find_program('build-aux/generate-app-list.py') + + cc = meson.get_compiler('c') +--- a/meson.options ++++ b/meson.options +@@ -51,3 +51,9 @@ + value: true, + description: 'Enable systemd integration' + ) ++ ++option('gjs_path', ++ type: 'string', ++ value: '', ++ description: 'The path to gjs' ++) +--- a/js/dbusServices/meson.build ++++ b/js/dbusServices/meson.build +@@ -22,7 +22,7 @@ + + serviceconf = configuration_data() + serviceconf.set('service', service) +- serviceconf.set('gjs', gjs.full_path()) ++ serviceconf.set('gjs', gjs_path) + serviceconf.set('pkgdatadir', pkgdatadir) + + configure_file( diff --git a/x11-packages/gnome-shell/0002-disable-polkit.patch b/x11-packages/gnome-shell/0002-disable-polkit.patch new file mode 100644 index 00000000000..b513b60e47d --- /dev/null +++ b/x11-packages/gnome-shell/0002-disable-polkit.patch @@ -0,0 +1,184 @@ +diff --git a/config.h.meson b/config.h.meson +index a357e1c..b686322 100644 +--- a/config.h.meson ++++ b/config.h.meson +@@ -45,3 +45,6 @@ + + /* Whether GNOME Shell is built with X11 clients support */ + #mesondefine HAVE_X11_CLIENT ++ ++/* Whether GNOME Shell is built with polkit support */ ++#mesondefine HAVE_POLKIT_SUPPORT +diff --git a/js/misc/dependencies.js b/js/misc/dependencies.js +index c0816f0..11398a8 100644 +--- a/js/misc/dependencies.js ++++ b/js/misc/dependencies.js +@@ -19,8 +19,6 @@ import 'gi://Graphene?version=1.0'; + import 'gi://GWeather?version=4.0'; + import 'gi://IBus?version=1.0'; + import 'gi://Pango?version=1.0'; +-import 'gi://Polkit?version=1.0'; +-import 'gi://PolkitAgent?version=1.0'; + import 'gi://Rsvg?version=2.0'; + import 'gi://Soup?version=3.0'; + import 'gi://UPowerGlib?version=1.0'; +diff --git a/js/ui/endSessionDialog.js b/js/ui/endSessionDialog.js +index 7455526..2e56da7 100644 +--- a/js/ui/endSessionDialog.js ++++ b/js/ui/endSessionDialog.js +@@ -20,7 +20,6 @@ import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; + import Pango from 'gi://Pango'; +-import Polkit from 'gi://Polkit'; + import Shell from 'gi://Shell'; + import St from 'gi://St'; + import UPower from 'gi://UPowerGlib'; +diff --git a/js/ui/environment.js b/js/ui/environment.js +index 64ee2b8..7f5dbc2 100644 +--- a/js/ui/environment.js ++++ b/js/ui/environment.js +@@ -11,7 +11,6 @@ import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; + import Meta from 'gi://Meta'; +-import Polkit from 'gi://Polkit'; + import Shell from 'gi://Shell'; + import St from 'gi://St'; + +@@ -29,7 +28,6 @@ Gio._promisify(Gio.DBusProxy, 'new'); + Gio._promisify(Gio.DBusProxy.prototype, 'init_async'); + Gio._promisify(Gio.DBusProxy.prototype, 'call_with_unix_fd_list'); + Gio._promisify(Gio.File.prototype, 'query_info_async'); +-Gio._promisify(Polkit.Permission, 'new'); + Gio._promisify(Shell.App.prototype, 'activate_action'); + + // We can't import shell JS modules yet, because they may have +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index b732a9b..a6347c7 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -4,7 +4,6 @@ import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; + import NM from 'gi://NM'; +-import Polkit from 'gi://Polkit'; + import Shell from 'gi://Shell'; + import St from 'gi://St'; + +diff --git a/js/ui/status/thunderbolt.js b/js/ui/status/thunderbolt.js +index 6c14003..068411a 100644 +--- a/js/ui/status/thunderbolt.js ++++ b/js/ui/status/thunderbolt.js +@@ -3,7 +3,6 @@ + import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; +-import Polkit from 'gi://Polkit'; + import Shell from 'gi://Shell'; + import * as Signals from '../../misc/signals.js'; + +@@ -238,7 +237,6 @@ class Indicator extends SystemIndicator { + this._sync(); + + this._perm = null; +- this._createPermission(); + } + + async _createPermission() { +diff --git a/meson.build b/meson.build +index 908cfb2..90107ed 100644 +--- a/meson.build ++++ b/meson.build +@@ -83,7 +83,11 @@ clutter_dep = dependency(clutter_pc, version: mutter_req) + mtk_dep = dependency(mtk_pc, version: mutter_req) + cogl_dep = dependency(cogl_pc, version: mutter_req) + mutter_dep = dependency(libmutter_pc, version: mutter_req) ++if get_option('polkit') + polkit_dep = dependency('polkit-agent-1', version: polkit_req) ++else ++polkit_dep = dependency('', required : false) ++endif + schemas_dep = dependency('gsettings-desktop-schemas', version: schemas_req) + gnome_desktop_dep = dependency('gnome-desktop-4', version: gnome_desktop_req) + pango_dep = dependency('pango', version: pango_req) +@@ -181,6 +185,7 @@ cdata.set('HAVE_FDWALK', + cc.has_function('fdwalk') + ) + ++cdata.set('HAVE_POLKIT_SUPPORT', get_option('polkit')) + polkit_has_autocleanup = cc.compiles( + ''' + #define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE +diff --git a/meson.options b/meson.options +index a2cf12f..2d23b91 100644 +--- a/meson.options ++++ b/meson.options +@@ -57,3 +57,9 @@ option('gjs_path', + value: '', + description: 'The path to gjs' + ) ++ ++option('polkit', ++ type: 'boolean', ++ value: true, ++ description: 'Enable polkit support' ++) +diff --git a/src/meson.build b/src/meson.build +index 9691ef8..62576f6 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -258,10 +258,13 @@ libshell_gir_includes = [ + 'Meta-@0@'.format(mutter_api_version), + 'Gcr-4', + 'GioUnix-2.0', +- 'PolkitAgent-1.0', + 'GdkPixbuf-2.0' + ] + ++if get_option('polkit') ++ libshell_gir_includes += ['PolkitAgent-1.0'] ++endif ++ + if have_networkmanager + libshell_gir_includes += ['NM-1.0'] + endif +diff --git a/src/shell-polkit-authentication-agent.c b/src/shell-polkit-authentication-agent.c +index 6815f01..73dc7ba 100644 +--- a/src/shell-polkit-authentication-agent.c ++++ b/src/shell-polkit-authentication-agent.c +@@ -8,6 +8,8 @@ + + #include "config.h" + ++#ifdef HAVE_POLKIT_SUPPORT ++ + #include + + #include "shell-polkit-authentication-agent.h" +@@ -427,3 +429,5 @@ shell_polkit_authentication_agent_complete (ShellPolkitAuthenticationAgent *agen + + auth_request_complete (agent->current_request, dismissed); + } ++ ++#endif +diff --git a/src/shell-polkit-authentication-agent.h b/src/shell-polkit-authentication-agent.h +index e4fccda..74af569 100644 +--- a/src/shell-polkit-authentication-agent.h ++++ b/src/shell-polkit-authentication-agent.h +@@ -8,6 +8,9 @@ + + #pragma once + ++#include "config.h" ++#ifdef HAVE_POLKIT_SUPPORT ++ + #define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE + #include + #include +@@ -32,3 +35,5 @@ void shell_polkit_authentication_agent_register (Shel + void shell_polkit_authentication_agent_unregister (ShellPolkitAuthenticationAgent *agent); + + G_END_DECLS ++ ++#endif diff --git a/x11-packages/gnome-shell/0003-tagged-pointer.patch b/x11-packages/gnome-shell/0003-tagged-pointer.patch new file mode 100644 index 00000000000..b7e0bda95f3 --- /dev/null +++ b/x11-packages/gnome-shell/0003-tagged-pointer.patch @@ -0,0 +1,44 @@ +--- a/src/main.c ++++ b/src/main.c +@@ -25,6 +25,32 @@ + #include + #endif + ++#ifdef __TERMUX__ ++#include ++#include ++static void android_disable_tags() { ++ void *lib_handle = dlopen("libc.so", RTLD_LAZY); ++ if (lib_handle) { ++ if (android_get_device_api_level() >= 31) { ++ int (*mallopt_func)(int, int) = dlsym(lib_handle, "mallopt"); ++ if (mallopt_func) { ++ mallopt_func(M_BIONIC_SET_HEAP_TAGGING_LEVEL, 0); ++ } ++ return; ++ } ++ /* android_get_device_api_level() < 31 */ ++ bool (*android_mallopt)(int opcode, void* arg, size_t arg_size) = dlsym(lib_handle, "android_mallopt"); ++ if (android_mallopt) { ++ int android_malloc_tag_level = 0; ++ android_mallopt(8, &android_malloc_tag_level, sizeof(android_malloc_tag_level)); ++ } ++ dlclose(lib_handle); ++ } ++} ++#else ++static void android_disable_tags(){} ++#endif ++ + #include "shell-global.h" + #include "shell-global-private.h" + #include "shell-perf-log.h" +@@ -598,6 +624,8 @@ + GError *error = NULL; + int ecode = EXIT_SUCCESS; + ++ android_disable_tags(); ++ + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); diff --git a/x11-packages/gnome-shell/0004-disable-accountsservice.patch b/x11-packages/gnome-shell/0004-disable-accountsservice.patch new file mode 100644 index 00000000000..2ed4552671d --- /dev/null +++ b/x11-packages/gnome-shell/0004-disable-accountsservice.patch @@ -0,0 +1,202 @@ +diff --git a/js/misc/dependencies.js b/js/misc/dependencies.js +index 88997a9..b820a39 100644 +--- a/js/misc/dependencies.js ++++ b/js/misc/dependencies.js +@@ -4,7 +4,6 @@ import gi from 'gi'; + * Required dependencies + */ + +-import 'gi://AccountsService?version=1.0'; + import 'gi://Atk?version=1.0'; + import 'gi://Atspi?version=2.0'; + import 'gi://Gcr?version=4'; +diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js +index cdf020d..13ee439 100644 +--- a/js/misc/systemActions.js ++++ b/js/misc/systemActions.js +@@ -1,4 +1,3 @@ +-import AccountsService from 'gi://AccountsService'; + import Clutter from 'gi://Clutter'; + import Gdm from 'gi://Gdm'; + import Gio from 'gi://Gio'; +@@ -159,17 +158,6 @@ const SystemActions = GObject.registerClass({ + this._loginManager = LoginManager.getLoginManager(); + this._monitorManager = global.backend.get_monitor_manager(); + +- this._userManager = AccountsService.UserManager.get_default(); +- +- this._userManager.connect('notify::is-loaded', +- () => this._updateMultiUser()); +- this._userManager.connect('notify::has-multiple-users', +- () => this._updateMultiUser()); +- this._userManager.connect('user-added', +- () => this._updateMultiUser()); +- this._userManager.connect('user-removed', +- () => this._updateMultiUser()); +- + this._lockdownSettings.connect(`changed::${DISABLE_USER_SWITCH_KEY}`, + () => this._updateSwitchUser()); + this._lockdownSettings.connect(`changed::${DISABLE_LOG_OUT_KEY}`, +@@ -381,7 +369,7 @@ const SystemActions = GObject.registerClass({ + + _updateSwitchUser() { + let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY); +- let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users; ++ let multiUser = false; + let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; + + let visible = allowSwitch && multiUser && shouldShowInMode; +@@ -392,13 +380,11 @@ const SystemActions = GObject.registerClass({ + } + + _updateLogout() { +- let user = this._userManager.get_user(GLib.get_user_name()); +- + let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY); + let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY); +- let systemAccount = user.system_account; +- let localAccount = user.local_account; +- let multiUser = this._userManager.has_multiple_users; ++ let systemAccount = true; ++ let localAccount = true; ++ let multiUser = false; + let multiSession = Gdm.get_session_ids().length > 1; + let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; + +diff --git a/js/ui/endSessionDialog.js b/js/ui/endSessionDialog.js +index 9369d99..2e56da7 100644 +--- a/js/ui/endSessionDialog.js ++++ b/js/ui/endSessionDialog.js +@@ -15,7 +15,6 @@ + * along with this program; if not, see . + */ + +-import AccountsService from 'gi://AccountsService'; + import Clutter from 'gi://Clutter'; + import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; +@@ -235,8 +234,6 @@ class EndSessionDialog extends ModalDialog.ModalDialog { + this._canRebootToBootLoaderMenu = false; + this._getCanRebootToBootLoaderMenu().catch(logError); + +- this._userManager = AccountsService.UserManager.get_default(); +- this._user = this._userManager.get_user(GLib.get_user_name()); + this._updatesPermission = null; + + try { +@@ -271,10 +268,6 @@ class EndSessionDialog extends ModalDialog.ModalDialog { + + this.connect('opened', this._onOpened.bind(this)); + +- this._user.connectObject( +- 'notify::is-loaded', this._sync.bind(this), +- 'changed', this._sync.bind(this), this); +- + this._messageDialogContent = new Dialog.MessageDialogContent(); + + this._checkBox = new CheckBox.CheckBox(); +@@ -654,20 +647,14 @@ class EndSessionDialog extends ModalDialog.ModalDialog { + continue; + + const session = { +- user: this._userManager.get_user(userName), + username: userName, + type: proxy.Type, + remote: proxy.Remote, + }; + const nSessions = this._sessions.push(session); + +- let userAvatar = new UserWidget.Avatar(session.user, { +- iconSize: _ITEM_ICON_SIZE, +- }); +- userAvatar.update(); +- + const displayUserName = +- session.user.get_real_name() ?? session.username; ++ session.username; + + let userLabelText; + if (session.remote) +@@ -680,7 +667,6 @@ class EndSessionDialog extends ModalDialog.ModalDialog { + userLabelText = userName; + + let listItem = new Dialog.ListSectionItem({ +- icon_actor: userAvatar, + title: userLabelText, + }); + this._sessionSection.list.add_child(listItem); +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 733346f..96a1cad 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1,4 +1,3 @@ +-import AccountsService from 'gi://AccountsService'; + import Clutter from 'gi://Clutter'; + import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; +@@ -672,14 +671,10 @@ export class ScreenShield extends Signals.EventEmitter { + St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, ''); + St.Clipboard.get_default().set_text(St.ClipboardType.PRIMARY, ''); + +- let userManager = AccountsService.UserManager.get_default(); +- let user = userManager.get_user(GLib.get_user_name()); + + this.activate(animate); + +- const lock = this._isGreeter +- ? true +- : user.password_mode !== AccountsService.UserPasswordMode.NONE; ++ const lock = false; + this._setLocked(lock); + } + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index f94f2ec..a162fa9 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -1,4 +1,3 @@ +-import AccountsService from 'gi://AccountsService'; + import Atk from 'gi://Atk'; + import Clutter from 'gi://Clutter'; + import Gdm from 'gi://Gdm'; +@@ -553,9 +552,7 @@ export const UnlockDialog = GObject.registerClass({ + Main.layoutManager.connectObject('monitors-changed', + this._updateBackgrounds.bind(this), this); + +- this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); +- this._user = this._userManager.get_user(this._userName); + + // Authentication & Clock stack + this._stack = new Shell.Stack(); +@@ -603,11 +600,6 @@ export const UnlockDialog = GObject.registerClass({ + this._lockdownSettings.connect('changed::disable-user-switching', + this._updateUserSwitchVisibility.bind(this)); + +- this._user.connectObject( +- 'notify::is-loaded', () => this._updateUserSwitchVisibility(), +- 'notify::has-multiple-users', () => this._updateUserSwitchVisibility(), +- this); +- + this._updateUserSwitchVisibility(); + + // Main Box +@@ -807,7 +799,6 @@ export const UnlockDialog = GObject.registerClass({ + _onReset(authPrompt, beginRequest) { + let userName; + if (beginRequest === AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { +- this._authPrompt.setUser(this._user); + userName = this._userName; + } else { + userName = null; +@@ -876,8 +867,8 @@ export const UnlockDialog = GObject.registerClass({ + } + + _updateUserSwitchVisibility() { +- this._otherUserButton.visible = this._userManager.can_switch() && +- this._userManager.has_multiple_users && ++ this._otherUserButton.visible = ++ false && + this._screenSaverSettings.get_boolean('user-switch-enabled') && + !this._lockdownSettings.get_boolean('disable-user-switching'); + } diff --git a/x11-packages/gnome-shell/0005-disable-gdm.patch b/x11-packages/gnome-shell/0005-disable-gdm.patch new file mode 100644 index 00000000000..78354fb16f4 --- /dev/null +++ b/x11-packages/gnome-shell/0005-disable-gdm.patch @@ -0,0 +1,238 @@ +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index df23e5e..67eff29 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -1,16 +1,6 @@ + + + +- gdm/authList.js +- gdm/authPrompt.js +- gdm/batch.js +- gdm/credentialManager.js +- gdm/loginDialog.js +- gdm/oVirt.js +- gdm/realmd.js +- gdm/util.js +- gdm/vmware.js +- + extensions/extension.js + extensions/sharedInternals.js + +diff --git a/js/misc/dependencies.js b/js/misc/dependencies.js +index b820a39..11398a8 100644 +--- a/js/misc/dependencies.js ++++ b/js/misc/dependencies.js +@@ -8,7 +8,6 @@ import 'gi://Atk?version=1.0'; + import 'gi://Atspi?version=2.0'; + import 'gi://Gcr?version=4'; + import 'gi://Gdk?version=4.0'; +-import 'gi://Gdm?version=1.0'; + import 'gi://Geoclue?version=2.0'; + import 'gi://Gio?version=2.0'; + import 'gi://GioUnix?version=2.0'; +diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js +index 13ee439..0193230 100644 +--- a/js/misc/systemActions.js ++++ b/js/misc/systemActions.js +@@ -1,5 +1,4 @@ + import Clutter from 'gi://Clutter'; +-import Gdm from 'gi://Gdm'; + import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; +@@ -10,7 +10,6 @@ import * as Main from '../ui/main.js'; + import * as Screenshot from '../ui/screenshot.js'; + + const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; +-const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; + const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; + const DISABLE_USER_SWITCH_KEY = 'disable-user-switching'; + const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; +@@ -150,7 +148,6 @@ const SystemActions = GObject.registerClass({ + available: true, + }); + +- this._loginScreenSettings = new Gio.Settings({schema_id: LOGIN_SCREEN_SCHEMA}); + this._lockdownSettings = new Gio.Settings({schema_id: LOCKDOWN_SCHEMA}); + this._orientationSettings = new Gio.Settings({schema_id: 'org.gnome.settings-daemon.peripherals.touchscreen'}); + +@@ -340,10 +340,7 @@ const SystemActions = GObject.registerClass({ + } + + _updatePowerOff() { +- let disabled = (Main.sessionMode.isLocked && +- !this._screenSaverSettings.get_boolean(RESTART_ENABLED_KEY)) || +- (Main.sessionMode.isGreeter && +- this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); ++ let disabled = true; + this._actions.get(POWER_OFF_ACTION_ID).available = this._canHavePowerOff && !disabled; + this.notify('can-power-off'); + +@@ -354,10 +349,7 @@ const SystemActions = GObject.registerClass({ + } + + _updateSuspend() { +- let disabled = (Main.sessionMode.isLocked && +- this._suspendNeedsAuth) || +- (Main.sessionMode.isGreeter && +- this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); ++ let disabled = true; + this._actions.get(SUSPEND_ACTION_ID).available = this._canHaveSuspend && !disabled; + this.notify('can-suspend'); + } +@@ -385,7 +377,7 @@ const SystemActions = GObject.registerClass({ + let systemAccount = true; + let localAccount = true; + let multiUser = false; +- let multiSession = Gdm.get_session_ids().length > 1; ++ let multiSession = false; + let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; + + let visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount) && shouldShowInMode; +@@ -418,7 +410,6 @@ const SystemActions = GObject.registerClass({ + Main.screenShield.lock(false); + + Clutter.threads_add_repaint_func(Clutter.RepaintFlags.POST_PAINT, () => { +- Gdm.goto_login_session_sync(null); + return false; + }); + } +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 96a1cad..f1e6fdc 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -9,7 +9,6 @@ import St from 'gi://St'; + import * as Signals from '../misc/signals.js'; + + import * as GnomeSession from '../misc/gnomeSession.js'; +-import * as OVirt from '../gdm/oVirt.js'; + import * as LoginManager from '../misc/loginManager.js'; + import * as Lightbox from './lightbox.js'; + import * as Main from './main.js'; +@@ -96,7 +95,6 @@ export class ScreenShield extends Signals.EventEmitter { + }); + + this._credentialManagers = {}; +- this.addCredentialManager(OVirt.SERVICE_NAME, OVirt.getOVirtCredentialsManager()); + + this._loginManager = LoginManager.getLoginManager(); + this._loginManager.connect('prepare-for-sleep', +diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js +index 6c2a15e..72e8195 100644 +--- a/js/ui/sessionMode.js ++++ b/js/ui/sessionMode.js +@@ -3,7 +3,6 @@ import * as Signals from '../misc/signals.js'; + + import * as FileUtils from '../misc/fileUtils.js'; + +-import {LoginDialog} from '../gdm/loginDialog.js'; + import {UnlockDialog} from '../ui/unlockDialog.js'; + + import * as Config from '../misc/config.js'; +@@ -52,7 +51,6 @@ const _modes = { + hasNotifications: true, + isGreeter: true, + isPrimary: true, +- unlockDialog: LoginDialog, + components: Config.HAVE_NETWORKMANAGER + ? ['networkAgent', 'polkitAgent'] + : ['polkitAgent'], +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index a162fa9..9962ef1 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -1,6 +1,5 @@ + import Atk from 'gi://Atk'; + import Clutter from 'gi://Clutter'; +-import Gdm from 'gi://Gdm'; + import Gio from 'gi://Gio'; + import GnomeDesktop from 'gi://GnomeDesktop'; + import GLib from 'gi://GLib'; +@@ -13,8 +13,6 @@ import * as Main from './main.js'; + import * as MessageTray from './messageTray.js'; + import * as SwipeTracker from './swipeTracker.js'; + import {formatDateWithCFormatString} from '../misc/dateUtils.js'; +-import * as AuthPrompt from '../gdm/authPrompt.js'; +-import {AuthPromptStatus} from '../gdm/authPrompt.js'; + import {MprisSource} from './mpris.js'; + import {MediaMessage} from './messageList.js'; + +@@ -493,15 +490,6 @@ export const UnlockDialog = GObject.registerClass({ + + parentActor.add_child(this); + +- this._gdmClient = new Gdm.Client(); +- +- try { +- this._gdmClient.set_enabled_extensions([ +- Gdm.UserVerifierChoiceList.interface_info().name, +- ]); +- } catch { +- } +- + this._adjustment = new St.Adjustment({ + actor: this, + lower: 0, +@@ -699,6 +687,7 @@ export const UnlockDialog = GObject.registerClass({ + } + + _ensureAuthPrompt() { ++ return; + if (!this._authPrompt) { + this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient, + AuthPrompt.AuthPromptMode.UNLOCK_ONLY); +@@ -746,8 +735,6 @@ export const UnlockDialog = GObject.registerClass({ + } + + _showPrompt() { +- this._ensureAuthPrompt(); +- + if (this._activePage === this._promptBox) + return; + +@@ -798,13 +785,12 @@ export const UnlockDialog = GObject.registerClass({ + + _onReset(authPrompt, beginRequest) { + let userName; +- if (beginRequest === AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { ++ if (true) { + userName = this._userName; + } else { + userName = null; + } + +- this._authPrompt.begin({userName}); + } + + _escape() { +@@ -818,8 +804,6 @@ export const UnlockDialog = GObject.registerClass({ + + this._adjustment.remove_transition('value'); + +- this._ensureAuthPrompt(); +- + let progress = this._adjustment.value; + tracker.confirmSwipe(this._stack.height, + [0, 1], +@@ -847,8 +831,6 @@ export const UnlockDialog = GObject.registerClass({ + } + + _otherUserClicked() { +- Gdm.goto_login_session_sync(null); +- + this._authPrompt.cancel(); + } + +@@ -859,11 +841,6 @@ export const UnlockDialog = GObject.registerClass({ + this._idleMonitor.remove_watch(this._idleWatchId); + this._idleWatchId = 0; + } +- +- if (this._gdmClient) { +- this._gdmClient = null; +- delete this._gdmClient; +- } + } + + _updateUserSwitchVisibility() { diff --git a/x11-packages/gnome-shell/0006-gnomevolumecontrol.patch b/x11-packages/gnome-shell/0006-gnomevolumecontrol.patch new file mode 100644 index 00000000000..e2b62fe7dac --- /dev/null +++ b/x11-packages/gnome-shell/0006-gnomevolumecontrol.patch @@ -0,0 +1,13 @@ +Fixes link-time conflict with graphviz + +--- a/subprojects/gvc/meson.build ++++ b/subprojects/gvc/meson.build +@@ -90,7 +90,7 @@ else + error('Installing shared library, but pkglibdir is unset!') + endif + +- libgvc_shared = shared_library('gvc', ++ libgvc_shared = shared_library('gnomevolumecontrol', + sources: libgvc_gir_sources + libgvc_no_gir_sources + libgvc_enums, + dependencies: libgvc_deps, + c_args: c_args, diff --git a/x11-packages/gnome-shell/0007-set-gjs.patch b/x11-packages/gnome-shell/0007-set-gjs.patch new file mode 100644 index 00000000000..4c6797e63b9 --- /dev/null +++ b/x11-packages/gnome-shell/0007-set-gjs.patch @@ -0,0 +1,11 @@ +--- a/subprojects/extensions-app/meson.build ++++ b/subprojects/extensions-app/meson.build +@@ -47,7 +47,7 @@ metainfodir = join_paths(datadir, 'metainfo') + schemadir = join_paths(datadir, 'glib-2.0', 'schemas') + servicedir = join_paths(datadir, 'dbus-1', 'services') + +-gjs = find_program('gjs') ++gjs = find_program('@TERMUX_PREFIX@/bin/gjs', native : false) + appstreamcli = find_program('appstreamcli', required: false) + desktop_file_validate = find_program('desktop-file-validate', required: false) + diff --git a/x11-packages/gnome-shell/0008-gnome-session-48-backcompat.patch b/x11-packages/gnome-shell/0008-gnome-session-48-backcompat.patch new file mode 100644 index 00000000000..5bf3aed6719 --- /dev/null +++ b/x11-packages/gnome-shell/0008-gnome-session-48-backcompat.patch @@ -0,0 +1,132 @@ +We don't ship gnome-session 49 yet. +--- +From 078fbcde4147147d47b9facafba5de4a9db4abd7 Mon Sep 17 00:00:00 2001 +From: Achill Gilgenast +Date: Tue, 16 Sep 2025 09:36:10 +0200 +Subject: [PATCH 1/2] Reapply "main: Notify gnome-session when we're ready" + +This reverts commit d697bdceaefeae49ad0e03df7352884899c3e14a. + +Signed-off-by: Achill Gilgenast +--- + js/ui/main.js | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index 40898e15c23d..ae7a80527361 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -326,6 +326,7 @@ async function _initializeUI() { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + Shell.util_sd_notify(); + global.context.notify_ready(); ++ _notifyGnomeSessionReady(); + return GLib.SOURCE_REMOVE; + }); + +@@ -390,6 +391,20 @@ async function _initializeUI() { + }); + } + ++async function _notifyGnomeSessionReady() { ++ try { ++ let params = GLib.Variant.new('(ss)', ['org.gnome.Shell.desktop', '']); ++ await Gio.DBus.session.call( ++ 'org.gnome.SessionManager', ++ '/org/gnome/SessionManager', ++ 'org.gnome.SessionManager', ++ 'RegisterClient', params, null, ++ Gio.DBusCallFlags.NONE, -1, null); ++ } catch (e) { ++ log(`Error notifying gnome-session that we're ready: ${e.message}`); ++ } ++} ++ + function _handleShowWelcomeScreen() { + const lastShownVersion = global.settings.get_string(WELCOME_DIALOG_LAST_SHOWN_VERSION); + if (Util.GNOMEversionCompare(WELCOME_DIALOG_LAST_TOUR_CHANGE, lastShownVersion) > 0) { +-- +2.51.0 + +From ca32ea422d2b075406a78058a14b0898ddb87698 Mon Sep 17 00:00:00 2001 +From: Achill Gilgenast +Date: Tue, 16 Sep 2025 09:36:18 +0200 +Subject: [PATCH 2/2] Revert "data: Drop org.gnome.Shell.desktop" + +This reverts commit f3911151da7b0e3c630011cb26bd85f719e531d2. + +Signed-off-by: Achill Gilgenast +--- + data/meson.build | 13 ++++++++++++- + ....in => org.gnome.Shell.Extensions.desktop.in.in} | 0 + ...n => org.gnome.Shell.PortalHelper.desktop.in.in} | 0 + data/org.gnome.Shell.desktop.in.in | 13 +++++++++++++ + 4 files changed, 25 insertions(+), 1 deletion(-) + rename data/{org.gnome.Shell.Extensions.desktop.in => org.gnome.Shell.Extensions.desktop.in.in} (100%) + rename data/{org.gnome.Shell.PortalHelper.desktop.in => org.gnome.Shell.PortalHelper.desktop.in.in} (100%) + create mode 100644 data/org.gnome.Shell.desktop.in.in + +diff --git a/data/meson.build b/data/meson.build +index cc7b5e2a1fe6..76a32ca59f1b 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -1,6 +1,7 @@ + data_builddir = meson.current_build_dir() + + desktop_files = [ ++ 'org.gnome.Shell.desktop', + 'org.gnome.Shell.Extensions.desktop', + ] + service_files = [] +@@ -10,9 +11,19 @@ if have_portal_helper + service_files += 'org.gnome.Shell.PortalHelper.service' + endif + ++desktopconf = configuration_data() ++# We substitute in bindir so it works as an autostart ++# file when built in a non-system prefix ++desktopconf.set('bindir', bindir) ++desktopconf.set('systemd_hidden', have_systemd ? 'true' : 'false') ++ + foreach desktop_file : desktop_files + i18n.merge_file( +- input: desktop_file + '.in', ++ input: configure_file( ++ input: desktop_file + '.in.in', ++ output: desktop_file + '.in', ++ configuration: desktopconf ++ ), + output: desktop_file, + po_dir: po_dir, + install: true, +diff --git a/data/org.gnome.Shell.Extensions.desktop.in b/data/org.gnome.Shell.Extensions.desktop.in.in +similarity index 100% +rename from data/org.gnome.Shell.Extensions.desktop.in +rename to data/org.gnome.Shell.Extensions.desktop.in.in +diff --git a/data/org.gnome.Shell.PortalHelper.desktop.in b/data/org.gnome.Shell.PortalHelper.desktop.in.in +similarity index 100% +rename from data/org.gnome.Shell.PortalHelper.desktop.in +rename to data/org.gnome.Shell.PortalHelper.desktop.in.in +diff --git a/data/org.gnome.Shell.desktop.in.in b/data/org.gnome.Shell.desktop.in.in +new file mode 100644 +index 000000000000..59d1cd2fe924 +--- /dev/null ++++ b/data/org.gnome.Shell.desktop.in.in +@@ -0,0 +1,13 @@ ++[Desktop Entry] ++Type=Application ++Name=GNOME Shell ++Comment=Window management and application launching ++Exec=@bindir@/gnome-shell ++Categories=GNOME;GTK;Core; ++OnlyShowIn=GNOME; ++NoDisplay=true ++X-GNOME-Autostart-Phase=DisplayServer ++X-GNOME-Provides=panel;windowmanager; ++X-GNOME-Autostart-Notify=true ++X-GNOME-AutoRestart=false ++X-GNOME-HiddenUnderSystemd=@systemd_hidden@ +-- +2.51.0 + + diff --git a/x11-packages/gnome-shell/0009-disable-brightness.patch b/x11-packages/gnome-shell/0009-disable-brightness.patch new file mode 100644 index 00000000000..f165cb0c8af --- /dev/null +++ b/x11-packages/gnome-shell/0009-disable-brightness.patch @@ -0,0 +1,11 @@ +--- a/js/ui/status/brightness.js ++++ b/js/ui/status/brightness.js +@@ -49,8 +49,5 @@ class BrightnessItem extends QuickSlider { + this.menu.addMenuItem(this._monitorBrightnessSection); + + this._manager = Main.brightnessManager; +- this._manager.connectObject('changed', +- this._sync.bind(this), this); +- this._sync(); + } + diff --git a/x11-packages/gnome-shell/0010-fix-libical-const-property.patch b/x11-packages/gnome-shell/0010-fix-libical-const-property.patch new file mode 100644 index 00000000000..1326541a682 --- /dev/null +++ b/x11-packages/gnome-shell/0010-fix-libical-const-property.patch @@ -0,0 +1,11 @@ +--- a/src/calendar-server/gnome-shell-calendar-server.c 2026-01-21 19:23:48.000000000 +0800 ++++ b/src/calendar-server/gnome-shell-calendar-server.c 2026-08-10 03:19:28.963452862 +0800 +@@ -116,7 +116,7 @@ + get_time_from_property (ECalClient *cal, + ICalComponent *icomp, + ICalPropertyKind prop_kind, +- ICalTime * (* get_prop_func) (ICalProperty *prop), ++ ICalTime * (* get_prop_func) (const ICalProperty *prop), + ICalTimezone *default_zone, + ICalTime **out_itt, + ICalTimezone **out_timezone) diff --git a/x11-packages/gnome-shell/0101-disable-packagekit.patch b/x11-packages/gnome-shell/0101-disable-packagekit.patch new file mode 100644 index 00000000000..929c834ff69 --- /dev/null +++ b/x11-packages/gnome-shell/0101-disable-packagekit.patch @@ -0,0 +1,23 @@ +It is much difficult to port packagekit to Termux + +--- a/js/ui/endSessionDialog.js ++++ b/js/ui/endSessionDialog.js +@@ -240,10 +240,14 @@ + this._user = this._userManager.get_user(GLib.get_user_name()); + this._updatesPermission = null; + +- this._pkOfflineProxy = new PkOfflineProxy(Gio.DBus.system, +- 'org.freedesktop.PackageKit', +- '/org/freedesktop/PackageKit', +- this._onPkOfflineProxyCreated.bind(this)); ++ try { ++ this._pkOfflineProxy = new PkOfflineProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ '/org/freedesktop/PackageKit', ++ this._onPkOfflineProxyCreated.bind(this)); ++ } catch (error) { ++ log(`Failed to connect to PackageKit: ${error.message}`); ++ } + + this._powerProxy = new UPowerProxy(Gio.DBus.system, + 'org.freedesktop.UPower', diff --git a/x11-packages/gnome-shell/build.sh b/x11-packages/gnome-shell/build.sh new file mode 100644 index 00000000000..31274eb2032 --- /dev/null +++ b/x11-packages/gnome-shell/build.sh @@ -0,0 +1,31 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-shell +TERMUX_PKG_DESCRIPTION="GNOME shell" +TERMUX_PKG_LICENSE="GPL-2.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.3" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-shell/${TERMUX_PKG_VERSION%%.*}/gnome-shell-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=28f0dbd64452f0057129f226753c95d2726de07f1f5f473addc7c3c507a6d31a +TERMUX_PKG_DEPENDS="atk, evolution-data-server, gcr4, gdk-pixbuf, gjs, glib, gnome-autoar, gnome-desktop4, gnome-session, gobject-introspection, graphene, gsettings-desktop-schemas, ibus, libgweather, libx11, libxfixes, libxml2, mutter, pango, pulseaudio" +TERMUX_PKG_BUILD_DEPENDS="g-ir-scanner, glib-cross" +TERMUX_PKG_VERSIONED_GIR=false + +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dpolkit=false +-Dnetworkmanager=false +-Dportal_helper=false +-Dcamera_monitor=false +-Dsystemd=false +-Dgjs_path=$TERMUX_PREFIX/bin/gjs +-Dextensions_tool=true +-Dextensions_app=true +-Dtests=false +" + +termux_step_pre_configure() { + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper + + export TERMUX_MESON_ENABLE_SOVERSION=1 + + rm -rf "$TERMUX_PKG_SRCDIR/js/gdm" +} diff --git a/x11-packages/gnome-shell/gir/Gvc-1.0.xml b/x11-packages/gnome-shell/gir/Gvc-1.0.xml new file mode 100644 index 00000000000..0699b6b05d9 --- /dev/null +++ b/x11-packages/gnome-shell/gir/Gvc-1.0.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/gnome-shell/gir/Shell-16.xml b/x11-packages/gnome-shell/gir/Shell-16.xml new file mode 100644 index 00000000000..312960a88f0 --- /dev/null +++ b/x11-packages/gnome-shell/gir/Shell-16.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/gnome-shell/gir/Shell-17.xml b/x11-packages/gnome-shell/gir/Shell-17.xml new file mode 100644 index 00000000000..e7842a82f6a --- /dev/null +++ b/x11-packages/gnome-shell/gir/Shell-17.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/gnome-shell/gir/Shew-0.xml b/x11-packages/gnome-shell/gir/Shew-0.xml new file mode 100644 index 00000000000..2a0e86a431a --- /dev/null +++ b/x11-packages/gnome-shell/gir/Shew-0.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/x11-packages/gnome-shell/gir/St-16.xml b/x11-packages/gnome-shell/gir/St-16.xml new file mode 100644 index 00000000000..43b2ab02440 --- /dev/null +++ b/x11-packages/gnome-shell/gir/St-16.xml @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/gnome-shell/gir/St-17.xml b/x11-packages/gnome-shell/gir/St-17.xml new file mode 100644 index 00000000000..9a3d8891f60 --- /dev/null +++ b/x11-packages/gnome-shell/gir/St-17.xml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/gnome-tweaks/build.sh b/x11-packages/gnome-tweaks/build.sh new file mode 100644 index 00000000000..d7b7cf28512 --- /dev/null +++ b/x11-packages/gnome-tweaks/build.sh @@ -0,0 +1,22 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/gnome-tweaks +TERMUX_PKG_DESCRIPTION="Graphical interface for advanced GNOME settings" +TERMUX_PKG_LICENSE="GPL-3.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.0" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/gnome-tweaks/${TERMUX_PKG_VERSION%%.*}/gnome-tweaks-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=b3909bdcb4905b68427d6ab581e01f436dff8e5c0a389b1e0b14500f18806ebb +TERMUX_PKG_DEPENDS="dconf, glib, gnome-desktop4, gsettings-desktop-schemas, gtk4, hicolor-icon-theme, libadwaita, libnotify, pango, python, pygobject" +TERMUX_PKG_BUILD_DEPENDS="rsync, glib-cross" +TERMUX_PKG_SETUP_PYTHON=true +TERMUX_PKG_AUTO_UPDATE=true +TERMUX_PKG_PLATFORM_INDEPENDENT=true + +termux_step_pre_configure() { + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper + + local p="${TERMUX_PKG_BUILDER_DIR}/gnome-tweaks.diff" + sed -e "s|@TERMUX_PYTHON_HOME@|${TERMUX_PYTHON_HOME}|g" \ + -e "s|@TERMUX_PREFIX@|${TERMUX_PREFIX}|g" \ + "${p}" | patch -p1 --silent -d "$TERMUX_PKG_SRCDIR" +} diff --git a/x11-packages/gnome-tweaks/gnome-tweaks.diff b/x11-packages/gnome-tweaks/gnome-tweaks.diff new file mode 100644 index 00000000000..77d8a1a2707 --- /dev/null +++ b/x11-packages/gnome-tweaks/gnome-tweaks.diff @@ -0,0 +1,40 @@ +--- a/gnome-tweaks ++++ b/gnome-tweaks +@@ -14,7 +14,6 @@ import gi + gi.require_version("Gtk", "4.0") + gi.require_version("Adw", "1") + gi.require_version("GDesktopEnums", "3.0") +-gi.require_version('GUdev', '1.0') + + import gtweak + from gtweak.defs import VERSION, APP_ID, GSETTINGS_SCHEMA_DIR, TWEAK_DIR, DATA_DIR, \ +@@ -28,7 +27,7 @@ def set_internationalization(domain, locale_dir): + try: + locale.bindtextdomain(domain, locale_dir) + locale.textdomain(domain) +- except AttributeError as e: ++ except (AttributeError,OSError) as e: + # Python built without gettext support does not have + # bindtextdomain() and textdomain(). + logging.error( +@@ -49,9 +49,8 @@ def setup_defs_for_prefix(prefix: str): + gtweak.LOCALE_DIR = os.path.join(prefix, "share", "locale") + gtweak.GSETTINGS_SCHEMA_DIR = os.path.join(prefix, "share", "glib-2.0", "schemas") + +- me = os.path.abspath(os.path.dirname(__file__)) +- gtweak.TWEAK_DIR = os.path.join(me, "gtweak", "tweaks") +- gtweak.PKG_DATA_DIR = os.path.join(me, "data") ++ gtweak.TWEAK_DIR = "@TERMUX_PYTHON_HOME@/site-packages/gtweak/tweaks" ++ gtweak.PKG_DATA_DIR = "@TERMUX_PREFIX@/share/gnome-tweaks" + + + def setup_defs(prefix: str | None = None): +@@ -84,7 +83,7 @@ if __name__ == '__main__': + parser.add_option("--gapplication-service", action="store_true") + options, args = parser.parse_args() + +- setup_defs(options.prefix or None) ++ setup_defs(options.prefix or "@TERMUX_PREFIX@") + + gtweak.VERBOSE = options.verbose + diff --git a/x11-packages/gnome-tweaks/no-gudev.patch b/x11-packages/gnome-tweaks/no-gudev.patch new file mode 100644 index 00000000000..9ace0dcf46c --- /dev/null +++ b/x11-packages/gnome-tweaks/no-gudev.patch @@ -0,0 +1,34 @@ +diff --git a/meson.build b/meson.build +index 2c7b6d0..31753ca 100644 +--- a/meson.build ++++ b/meson.build +@@ -23,7 +23,6 @@ dependency('gobject-introspection-1.0', version: '>= 1.78.0') + dependency('gtk4', version: '>= 4.10.0') + dependency('libadwaita-1', version: '>= 1.4.0') + dependency('pygobject-3.0', version: '>= 3.46.0') +-dependency('gudev-1.0', version: '>= 238') + dependency('gsettings-desktop-schemas', version: '>= 46.0') + + prefix = get_option('prefix') +--- a/gtweak/devicemanager.py ++++ b/gtweak/devicemanager.py +@@ -2,7 +2,6 @@ + # SPDX-License-Identifier: GPL-3.0+ + # License-Filename: LICENSES/GPL-3.0 + +-from gi.repository import GUdev + + # Inspired by panels/common/gsd-device-manager.c in GNOME Settings + # https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/6d2add0e30538692c151e5fa0bd94ae9bece7690/panels/common/gsd-device-manager.c +@@ -28,10 +27,7 @@ def udev_device_is_evdev(device): + + + def get_input_devices(): +- udev_client = GUdev.Client() +- devices = udev_client.query_by_subsystem ("input") +- +- return [device for device in devices if udev_device_is_evdev(device)] ++ return [] + + + def udev_device_get_device_types (device): diff --git a/x11-packages/gnome/build.sh b/x11-packages/gnome/build.sh new file mode 100644 index 00000000000..a8b1751da6e --- /dev/null +++ b/x11-packages/gnome/build.sh @@ -0,0 +1,10 @@ +TERMUX_PKG_HOMEPAGE=https://www.gnome.org/ +TERMUX_PKG_DESCRIPTION="A metapackage that installs all parts of the GNOME desktop environment" +TERMUX_PKG_LICENSE="Public Domain" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.0" +TERMUX_PKG_AUTO_UPDATE=false +TERMUX_PKG_SKIP_SRC_EXTRACT=true +TERMUX_PKG_DEPENDS="epiphany, gnome-backgrounds, gnome-calculator, gnome-clocks, gnome-console, gnome-control-center, gnome-keyring, gnome-menus, gnome-session, gnome-settings-daemon, gnome-shell, gvfs, nautilus, orca" +TERMUX_PKG_METAPACKAGE=true +TERMUX_PKG_PLATFORM_INDEPENDENT=true diff --git a/x11-packages/localsearch/0001-no-udev-3.9.0.patch b/x11-packages/localsearch/0001-no-udev-3.9.0.patch new file mode 100644 index 00000000000..dc5eed5b276 --- /dev/null +++ b/x11-packages/localsearch/0001-no-udev-3.9.0.patch @@ -0,0 +1,165 @@ +From 151cb704db837b1a5d59b6c146bf42e42c0c2dbc Mon Sep 17 00:00:00 2001 +From: Md Arif <111168803+sabamdarif@users.noreply.github.com> +Date: Wed, 9 Jul 2025 14:56:31 +0530 +Subject: [PATCH] no udev 3.9.0 + +--- + meson.build | 1 - + src/common/tracker-enums.h | 18 +++++++++--------- + src/indexer/meson.build | 1 - + src/indexer/tracker-miner-files-methods.c | 17 +++++------------ + src/indexer/tracker-miner-files.c | 8 -------- + src/indexer/tracker-miner-files.h | 2 -- + 6 files changed, 14 insertions(+), 33 deletions(-) + +diff --git a/meson.build b/meson.build +index a5820e3..2f38233 100644 +--- a/meson.build ++++ b/meson.build +@@ -23,7 +23,6 @@ gexiv2 = dependency('gexiv2', required: get_option('raw')) + gio = dependency('gio-2.0', version: '>=' + glib_required) + gio_unix = dependency('gio-unix-2.0', version: '>=' + glib_required) + glib = dependency('glib-2.0', version: '>=' + glib_required) +-gudev = dependency('gudev-1.0') + gmodule = dependency('gmodule-2.0', version: '>=' + glib_required) + gobject = dependency('gobject-2.0', version: '>=' + glib_required) + gobject_introspection = dependency('gobject-introspection-1.0') +diff --git a/src/common/tracker-enums.h b/src/common/tracker-enums.h +index acd6493..6f69244 100644 +--- a/src/common/tracker-enums.h ++++ b/src/common/tracker-enums.h +@@ -23,18 +23,18 @@ + G_BEGIN_DECLS + + typedef enum { +- TRACKER_SERIALIZATION_FORMAT_SPARQL, +- TRACKER_SERIALIZATION_FORMAT_TURTLE, +- /* JSON and JSON_LD are treated as the same thing right now, but we could +- * treat them differently if we wanted. also it's nice to be able to pass +- * both 'json' and 'json-ld' to `tracker extract --output-format=`. +- */ +- TRACKER_SERIALIZATION_FORMAT_JSON, +- TRACKER_SERIALIZATION_FORMAT_JSON_LD, ++ TRACKER_SERIALIZATION_FORMAT_SPARQL, ++ TRACKER_SERIALIZATION_FORMAT_TURTLE, ++ /* JSON and JSON_LD are treated as the same thing right now, but we could ++ * treat them differently if we wanted. also it's nice to be able to pass ++ * both 'json' and 'json-ld' to `tracker extract --output-format=`. ++ */ ++ TRACKER_SERIALIZATION_FORMAT_JSON, ++ TRACKER_SERIALIZATION_FORMAT_JSON_LD + } TrackerSerializationFormat; + + typedef enum { +- TRACKER_INDEX_LOCATION_FLAGS_NONE = 0, ++ TRACKER_INDEX_LOCATION_FLAGS_NONE = 0 + } TrackerIndexLocationFlags; + + G_END_DECLS +diff --git a/src/indexer/meson.build b/src/indexer/meson.build +index 95baf5b..21663e5 100644 +--- a/src/indexer/meson.build ++++ b/src/indexer/meson.build +@@ -56,7 +56,6 @@ sources = [ + tracker_miner_fs_deps = [ + tracker_miners_common_dep, + tracker_miner_dep, +- gudev, + ] + + executable('localsearch-@0@'.format(tracker_api_major), +diff --git a/src/indexer/tracker-miner-files-methods.c b/src/indexer/tracker-miner-files-methods.c +index 403c971..501e429 100644 +--- a/src/indexer/tracker-miner-files-methods.c ++++ b/src/indexer/tracker-miner-files-methods.c +@@ -412,9 +412,8 @@ static gchar * + lookup_filesystem_id (TrackerMinerFiles *files, + GFile *file) + { +- const gchar *id = NULL, *devname = NULL; +- GUdevClient *udev_client; +- g_autoptr (GUdevDevice) udev_device = NULL; ++ const gchar *id = NULL; ++ const char *devname = NULL; + MountData *mount_data = NULL; + const char *path; + int path_len; +@@ -455,15 +454,9 @@ lookup_filesystem_id (TrackerMinerFiles *files, + } + } + +- if (devname) { +- udev_client = tracker_miner_files_get_udev_client (files); +- udev_device = g_udev_client_query_by_device_file (udev_client, devname); +- if (udev_device) { +- id = g_udev_device_get_property (udev_device, "ID_FS_UUID_SUB"); +- if (!id) +- id = g_udev_device_get_property (udev_device, "ID_FS_UUID"); +- } +- } ++ // Without udev, just return the device path as a fallback or NULL ++ if (devname) ++ id = devname; + + return g_strdup (id); + } +diff --git a/src/indexer/tracker-miner-files.c b/src/indexer/tracker-miner-files.c +index 25e1d1a..b559ff4 100644 +--- a/src/indexer/tracker-miner-files.c ++++ b/src/indexer/tracker-miner-files.c +@@ -79,7 +79,6 @@ struct _TrackerMinerFilesPrivate { + gboolean low_battery_pause; + gboolean initial_index; + +- GUdevClient *udev_client; + #ifdef HAVE_POWER + TrackerPower *power; + #endif /* HAVE_POWER) */ +@@ -309,7 +308,6 @@ tracker_miner_files_init (TrackerMinerFiles *mf) + priv->finished_handler = g_signal_connect_after (mf, "finished", + G_CALLBACK (miner_finished_cb), + NULL); +- priv->udev_client = g_udev_client_new (NULL); + } + + static void +@@ -429,7 +427,6 @@ miner_files_finalize (GObject *object) + #endif /* HAVE_POWER */ + + tracker_domain_ontology_unref (priv->domain_ontology); +- g_clear_pointer (&priv->udev_client, g_object_unref); + + if (priv->storage) { + g_object_unref (priv->storage); +@@ -1221,8 +1218,3 @@ tracker_miner_files_check_allowed_text_file (TrackerMinerFiles *mf, + return FALSE; + } + +-GUdevClient * +-tracker_miner_files_get_udev_client (TrackerMinerFiles *mf) +-{ +- return mf->private->udev_client; +-} +diff --git a/src/indexer/tracker-miner-files.h b/src/indexer/tracker-miner-files.h +index 96acf01..e9ed27a 100644 +--- a/src/indexer/tracker-miner-files.h ++++ b/src/indexer/tracker-miner-files.h +@@ -21,7 +21,6 @@ + #define __TRACKER_MINER_FS_FILES_H__ + + #include +-#include + + #include "tracker-config.h" + +@@ -45,7 +44,6 @@ TrackerStorage * tracker_miner_files_get_storage (TrackerMinerFiles *mf); + gboolean tracker_miner_files_check_allowed_text_file (TrackerMinerFiles *mf, + GFile *file); + +-GUdevClient * tracker_miner_files_get_udev_client (TrackerMinerFiles *mf); + + G_END_DECLS + +-- +2.48.1 + diff --git a/x11-packages/localsearch/0002-fix-paths.patch b/x11-packages/localsearch/0002-fix-paths.patch new file mode 100644 index 00000000000..763b1bb2adc --- /dev/null +++ b/x11-packages/localsearch/0002-fix-paths.patch @@ -0,0 +1,372 @@ +From 705664ef5394beb4eb9ab948935a85e9b9e75527 Mon Sep 17 00:00:00 2001 +From: Md Arif <111168803+sabamdarif@users.noreply.github.com> +Date: Thu, 10 Jul 2025 12:00:00 +0530 +Subject: [PATCH] fix paths + +--- + docs/reference/build.py | 2 +- + examples/flatpak/example-app.py | 2 +- + examples/python/query-async.py | 2 +- + examples/python/query-sync.py | 2 +- + gitlog-to-changelog | 2 +- + meson_integration_commands.sh | 2 +- + run-uninstalled.in | 2 +- + src/cli/tracker-sandbox.in | 2 +- + src/create-miner-symlinks.sh | 2 +- + src/extractor/calculate-hash.sh | 2 +- + src/extractor/setup-extract-rules.sh | 2 +- + src/install-generated-header.sh | 2 +- + tests/common/tracker-file-utils-test.c | 46 ++++++++++++------------- + tests/estress/abstract_engine.py | 2 +- + tests/estress/abstract_text_engine.py | 2 +- + tests/estress/client.py | 2 +- + tests/estress/mock_engine.py | 2 +- + tests/estress/options.py | 2 +- + tests/estress/rss_engine.py | 2 +- + tests/estress/wh_engine.py | 2 +- + tests/functional-tests/configuration.py | 2 +- + utils/trackertestutils/__main__.py | 6 ++-- + 22 files changed, 46 insertions(+), 46 deletions(-) + +diff --git a/docs/reference/build.py b/docs/reference/build.py +index 62bdd4a..293ab14 100755 +--- a/docs/reference/build.py ++++ b/docs/reference/build.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # Tracker website build script. + # + # Copyright 2020, Sam Thursfield +diff --git a/examples/flatpak/example-app.py b/examples/flatpak/example-app.py +index ec6e1d8..bf4efce 100644 +--- a/examples/flatpak/example-app.py ++++ b/examples/flatpak/example-app.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + + """A simple search app that works inside Flatpak.""" + +diff --git a/examples/python/query-async.py b/examples/python/query-async.py +index e96933d..6737ca5 100755 +--- a/examples/python/query-async.py ++++ b/examples/python/query-async.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # Copyright (C) 2020, Sam Thursfield + # + # This program is free software; you can redistribute it and/or +diff --git a/examples/python/query-sync.py b/examples/python/query-sync.py +index f7b26e3..3af54f4 100755 +--- a/examples/python/query-sync.py ++++ b/examples/python/query-sync.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # Copyright (C) 2020, Sam Thursfield + # + # This program is free software; you can redistribute it and/or +diff --git a/gitlog-to-changelog b/gitlog-to-changelog +index 1f0216a..3a59aa0 100755 +--- a/gitlog-to-changelog ++++ b/gitlog-to-changelog +@@ -1,4 +1,4 @@ +-#!/usr/bin/env perl ++#!@TERMUX_PREFIX@/bin/perl + # Convert git log output to ChangeLog format. + + my $VERSION = '2023-10-19 07:20'; # UTC +diff --git a/meson_integration_commands.sh b/meson_integration_commands.sh +index 34a526f..3897df9 100644 +--- a/meson_integration_commands.sh ++++ b/meson_integration_commands.sh +@@ -1,4 +1,4 @@ +-#!/bin/sh ++#!@TERMUX_PREFIX@/bin/sh + + # Post install triggers. + # +diff --git a/run-uninstalled.in b/run-uninstalled.in +index 795ab3e..560ddfb 100755 +--- a/run-uninstalled.in ++++ b/run-uninstalled.in +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2019 Sam Thursfield + # +diff --git a/src/cli/tracker-sandbox.in b/src/cli/tracker-sandbox.in +index b75ceb6..5ef31a6 100755 +--- a/src/cli/tracker-sandbox.in ++++ b/src/cli/tracker-sandbox.in +@@ -1,4 +1,4 @@ +-#!/bin/sh ++#!@TERMUX_PREFIX@/bin/sh + + export GI_TYPELIB_PATH=@typelibdir@/:$GI_TYPELIB_PATH + export PYTHONPATH=@testutils_dir@:$PYTHONPATH +diff --git a/src/create-miner-symlinks.sh b/src/create-miner-symlinks.sh +index c46f9c0..20e4bb3 100644 +--- a/src/create-miner-symlinks.sh ++++ b/src/create-miner-symlinks.sh +@@ -1,4 +1,4 @@ +-#!/bin/sh ++#!@TERMUX_PREFIX@/bin/sh + # Post-install script for install stuff that Meson doesn't support directly. + # + # We can't pass the necessary variables directly to the script, so we +diff --git a/src/extractor/calculate-hash.sh b/src/extractor/calculate-hash.sh +index 223d9b0..79de190 100755 +--- a/src/extractor/calculate-hash.sh ++++ b/src/extractor/calculate-hash.sh +@@ -1,2 +1,2 @@ +-#!/bin/sh ++#!@TERMUX_PREFIX@/bin/sh + cat $@ | sha256sum | cut -f 1 -d ' ' +diff --git a/src/extractor/setup-extract-rules.sh b/src/extractor/setup-extract-rules.sh +index b681ab6..34736a3 100644 +--- a/src/extractor/setup-extract-rules.sh ++++ b/src/extractor/setup-extract-rules.sh +@@ -1,4 +1,4 @@ +-#!/bin/sh ++#!@TERMUX_PREFIX@/bin/sh + # Create a directory of enabled extract rules inside the build tree, for + # use when running tracker-extract from the build tree. + +diff --git a/src/install-generated-header.sh b/src/install-generated-header.sh +index 0078aaf..47cc625 100644 +--- a/src/install-generated-header.sh ++++ b/src/install-generated-header.sh +@@ -1,4 +1,4 @@ +-#!/bin/sh ++#!@TERMUX_PREFIX@/bin/sh + + # Script to install generated .h files. This only exists to workaround + # a Meson bug: https://github.com/mesonbuild/meson/issues/705 +diff --git a/tests/common/tracker-file-utils-test.c b/tests/common/tracker-file-utils-test.c +index 78fa6f2..dd4f3f4 100644 +--- a/tests/common/tracker-file-utils-test.c ++++ b/tests/common/tracker-file-utils-test.c +@@ -81,11 +81,11 @@ string_in_list (GSList *list, const gchar *string) + static void + test_path_list_filter_duplicates (void) + { +- const gchar *input_roots [] = {"/home/ivan", +- "/home", +- "/tmp", +- "/usr/", +- "/usr/share/local", NULL}; ++ const gchar *input_roots [] = {"@TERMUX_HOME@/ivan", ++ "@TERMUX_HOME@", ++ "@TERMUX_PREFIX@/tmp", ++ "@TERMUX_PREFIX@", ++ "@TERMUX_PREFIX@/share/local", NULL}; + + GSList *input_as_list = NULL; + GSList *result; +@@ -95,9 +95,9 @@ test_path_list_filter_duplicates (void) + result = tracker_path_list_filter_duplicates (input_as_list, ".", TRUE); + g_assert_cmpint (3, ==, g_slist_length (result)); + +- g_assert_true (string_in_list (result, "/home")); +- g_assert_true (string_in_list (result, "/tmp")); +- g_assert_true (string_in_list (result, "/usr")); ++ g_assert_true (string_in_list (result, "@TERMUX_HOME@")); ++ g_assert_true (string_in_list (result, "@TERMUX_PREFIX@/tmp")); ++ g_assert_true (string_in_list (result, "@TERMUX_PREFIX@")); + + g_slist_foreach (input_as_list, (GFunc) g_free, NULL); + g_slist_foreach (result, (GFunc) g_free, NULL); +@@ -106,25 +106,25 @@ test_path_list_filter_duplicates (void) + static void + test_path_list_filter_duplicates_with_exceptions () + { +- const gchar *input_roots [] = { "/home/user/MyDocs", +- "/home/user/MyDocs/.sounds", +- "/home/user/MyDocs/visible", ++ const gchar *input_roots [] = { "@TERMUX_HOME@/user/MyDocs", ++ "@TERMUX_HOME@/user/MyDocs/.sounds", ++ "@TERMUX_HOME@/user/MyDocs/visible", + NULL}; + GSList *input_as_list = NULL, *result = NULL; + + input_as_list = array_as_list (input_roots); + +- result = tracker_path_list_filter_duplicates (input_as_list, "/home/user/MyDocs", FALSE); ++ result = tracker_path_list_filter_duplicates (input_as_list, "@TERMUX_HOME@/user/MyDocs", FALSE); + g_assert_cmpint (g_slist_length (result), ==, 3); +- g_assert_true (string_in_list (result, "/home/user/MyDocs")); +- g_assert_true (string_in_list (result, "/home/user/MyDocs/.sounds")); +- g_assert_true (string_in_list (result, "/home/user/MyDocs/visible")); ++ g_assert_true (string_in_list (result, "@TERMUX_HOME@/user/MyDocs")); ++ g_assert_true (string_in_list (result, "@TERMUX_HOME@/user/MyDocs/.sounds")); ++ g_assert_true (string_in_list (result, "@TERMUX_HOME@/user/MyDocs/visible")); + g_slist_foreach (result, (GFunc) g_free, NULL); + + +- result = tracker_path_list_filter_duplicates (input_as_list, "/home/user/MyDocs", TRUE); ++ result = tracker_path_list_filter_duplicates (input_as_list, "@TERMUX_HOME@/user/MyDocs", TRUE); + g_assert_cmpint (g_slist_length (result), ==, 1); +- g_assert_true (string_in_list (result, "/home/user/MyDocs")); ++ g_assert_true (string_in_list (result, "@TERMUX_HOME@/user/MyDocs")); + g_slist_foreach (result, (GFunc) g_free, NULL); + + g_slist_foreach (input_as_list, (GFunc) g_free, NULL); +@@ -144,15 +144,15 @@ test_path_evaluate_name (void) + g_setenv ("TEST_TRACKER_DIR", test, TRUE); + + +- result = tracker_path_evaluate_name ("/home/user/all/ok"); +- g_assert_cmpstr (result, ==, "/home/user/all/ok"); ++ result = tracker_path_evaluate_name ("@TERMUX_HOME@/user/all/ok"); ++ g_assert_cmpstr (result, ==, "@TERMUX_HOME@/user/all/ok"); + g_free (result); + + /* The result of this test and the next one are not consistent! + * Must it remove the end '/' or not? + */ +- result = tracker_path_evaluate_name ("/home/user/all/dir/"); +- g_assert_cmpstr (result, ==, "/home/user/all/dir"); ++ result = tracker_path_evaluate_name ("@TERMUX_HOME@/user/all/dir/"); ++ g_assert_cmpstr (result, ==, "@TERMUX_HOME@/user/all/dir"); + g_free (result); + + +@@ -343,7 +343,7 @@ test_file_system_get_remaining_space () + { + guint64 space; + +- space = tracker_file_system_get_remaining_space ("/home"); ++ space = tracker_file_system_get_remaining_space ("@TERMUX_HOME@"); + g_assert_cmpint (space, >, 0); + + // This is a critical (aborts the process) +@@ -355,7 +355,7 @@ test_file_system_get_remaining_space_percentage () + { + gdouble space; + +- space = tracker_file_system_get_remaining_space_percentage ("/home"); ++ space = tracker_file_system_get_remaining_space_percentage ("@TERMUX_HOME@"); + g_assert_cmpfloat (space, >=, 0); + g_assert_cmpfloat (space, <=, 100); + +diff --git a/tests/estress/abstract_engine.py b/tests/estress/abstract_engine.py +index fb35baa..99663d8 100644 +--- a/tests/estress/abstract_engine.py ++++ b/tests/estress/abstract_engine.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/estress/abstract_text_engine.py b/tests/estress/abstract_text_engine.py +index a506457..72f2d6c 100644 +--- a/tests/estress/abstract_text_engine.py ++++ b/tests/estress/abstract_text_engine.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/estress/client.py b/tests/estress/client.py +index 19aabe7..ca12c35 100755 +--- a/tests/estress/client.py ++++ b/tests/estress/client.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/estress/mock_engine.py b/tests/estress/mock_engine.py +index 72976fe..f2859c8 100644 +--- a/tests/estress/mock_engine.py ++++ b/tests/estress/mock_engine.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/estress/options.py b/tests/estress/options.py +index 828f9a2..62a696b 100644 +--- a/tests/estress/options.py ++++ b/tests/estress/options.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/estress/rss_engine.py b/tests/estress/rss_engine.py +index ed40edd..43078aa 100755 +--- a/tests/estress/rss_engine.py ++++ b/tests/estress/rss_engine.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/estress/wh_engine.py b/tests/estress/wh_engine.py +index 1787e24..10e74e5 100755 +--- a/tests/estress/wh_engine.py ++++ b/tests/estress/wh_engine.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright (C) 2009, Nokia + # +diff --git a/tests/functional-tests/configuration.py b/tests/functional-tests/configuration.py +index 193dc0a..f180601 100644 +--- a/tests/functional-tests/configuration.py ++++ b/tests/functional-tests/configuration.py +@@ -74,7 +74,7 @@ def tap_protocol_enabled(): + # 'filter-hidden' property of TrackerIndexingTree is hardwired to be True at + # present :/ + _TEST_MONITORED_TMP_DIR = os.path.join(config["TEST_ROOT"], "tracker-tests") +-if _TEST_MONITORED_TMP_DIR.startswith("/tmp"): ++if _TEST_MONITORED_TMP_DIR.startswith("@TERMUX_PREFIX@/tmp"): + if "REAL_HOME" in os.environ: + _TEST_MONITORED_TMP_DIR = os.path.join(os.environ["REAL_HOME"], "tracker-tests") + else: +diff --git a/utils/trackertestutils/__main__.py b/utils/trackertestutils/__main__.py +index bec5b45..04337fa 100644 +--- a/utils/trackertestutils/__main__.py ++++ b/utils/trackertestutils/__main__.py +@@ -49,7 +49,7 @@ script_about = "Tracker Sandbox developer tool." + prog = os.path.basename(getattr(sys, 'orig_argv', ['python'])[0]) + script_name = "tracker-sandbox" if prog.startswith("python") else prog + +-default_store_location = '/tmp/tracker-sandbox' ++default_store_location = '@TERMUX_PREFIX@/tmp/tracker-sandbox' + + store_pid = -1 + store_proc = None +@@ -118,7 +118,7 @@ def create_sandbox(store_location, prefix=None, use_session_dirs=False, + extra_env['XDG_RUNTIME_DIR'] = '%s/run/' % store_location + + # Prefix - only set if non-standard +- if prefix and prefix != '/usr': ++ if prefix and prefix != '@TERMUX_PREFIX@': + environment_set_and_add_path(extra_env, 'PATH', prefix, 'bin') + environment_set_and_add_path(extra_env, 'LD_LIBRARY_PATH', prefix, 'lib') + environment_set_and_add_path(extra_env, 'LD_LIBRARY_PATH', prefix, 'lib64') +@@ -214,7 +214,7 @@ def argument_parser(): + parser.add_argument('-p', '--prefix', metavar='DIR', action=expand_path, + help="run Tracker from the given install prefix. You " + "can run the system version of Tracker by " +- "specifying --prefix=/usr") ++ "specifying --prefix=@TERMUX_PREFIX@") + parser.add_argument('--use-session-dirs', action='store_true', + help=f"update the real Tracker index (use with care!)") + store_group = parser.add_mutually_exclusive_group() +-- +2.48.1 + diff --git a/x11-packages/localsearch/build.sh b/x11-packages/localsearch/build.sh new file mode 100644 index 00000000000..e0ad18b3b0b --- /dev/null +++ b/x11-packages/localsearch/build.sh @@ -0,0 +1,24 @@ +TERMUX_PKG_HOMEPAGE=https://github.com/GNOME/localsearch +TERMUX_PKG_DESCRIPTION="Local search library that uses Tracker" +TERMUX_PKG_LICENSE="LGPL-2.1-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION=3.9.0 +TERMUX_PKG_SRCURL=https://github.com/GNOME/localsearch/archive/refs/tags/${TERMUX_PKG_VERSION}.tar.gz +TERMUX_PKG_SHA256=6d8d5ff2a2320632e2cd14ef5a9c6a57d67430ac71d36428a257404a81dcf943 +TERMUX_PKG_DEPENDS="asciidoc, dbus, glib, sqlite, gstreamer, upower, totem-pl-parser, tinysparql, libexif, gexiv2, libgxps, libseccomp, libpng, ffmpeg, libportal, libportal-gtk3, libportal-gtk4" +TERMUX_PKG_BUILD_DEPENDS="glib-cross" +TERMUX_PKG_PYTHON_COMMON_DEPS="jinja2, markupsafe, markdown, pygments, typogrify" +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Dsystemd_user_services=false +-Dlandlock=disabled +" +TERMUX_PKG_RM_AFTER_INSTALL=" +lib/python*/site-packages/asciidoc +" + +termux_step_pre_configure() { + termux_setup_cmake + termux_setup_meson + termux_setup_glib_cross_pkg_config_wrapper + export TERMUX_MESON_ENABLE_SOVERSION=1 +} diff --git a/x11-packages/mutter/0003-missing-guards.patch b/x11-packages/mutter/0003-missing-guards.patch index a4495ecf32b..096deb85f4a 100644 --- a/x11-packages/mutter/0003-missing-guards.patch +++ b/x11-packages/mutter/0003-missing-guards.patch @@ -18,6 +18,7 @@ +#ifdef HAVE_LIBGUDEV #include "backends/meta-udev.h" +#endif + #include "backends/native/meta-backend-native-types.h" #include "backends/native/meta-clutter-backend-native.h" #include "backends/native/meta-kms-types.h" - + diff --git a/x11-packages/mutter/0005-fill-memfd.patch b/x11-packages/mutter/0005-fill-memfd.patch index 9678f990d5a..4a08b859fc4 100644 --- a/x11-packages/mutter/0005-fill-memfd.patch +++ b/x11-packages/mutter/0005-fill-memfd.patch @@ -13,11 +13,11 @@ #define PRIVATE_OWNER_FROM_FIELD(TypeName, field_ptr, field_name) \ (TypeName *)((guint8 *)(field_ptr) - G_PRIVATE_OFFSET (TypeName, field_name)) ---- a/src/core/meta-anonymous-file.h -+++ b/src/core/meta-anonymous-file.h -@@ -22,6 +22,13 @@ - #include "meta/common.h" - #include "core/util-private.h" +--- a/mtk/mtk/mtk-anonymous-file.h ++++ b/mtk/mtk/mtk-anonymous-file.h +@@ -24,6 +24,13 @@ + + #include "mtk/mtk-macros.h" +#if defined(__ANDROID__) && __ANDROID_API__ < 30 +#include @@ -26,6 +26,6 @@ +#define HAVE_MEMFD_CREATE +#endif + - typedef struct _MetaAnonymousFile MetaAnonymousFile; + typedef struct _MtkAnonymousFile MtkAnonymousFile; - typedef enum _MetaAnonymousFileMapmode + typedef enum _MtkAnonymousFileMapmode diff --git a/x11-packages/mutter/0006-fix-anno-file.patch b/x11-packages/mutter/0006-fix-anno-file.patch index 96e366b7952..11eaa5c2501 100644 --- a/x11-packages/mutter/0006-fix-anno-file.patch +++ b/x11-packages/mutter/0006-fix-anno-file.patch @@ -1,11 +1,11 @@ Fallback to `@TERMUX_PREFIX@/tmp` if env `XDG_RUNTIME_DIR` is not set. ---- a/src/core/meta-anonymous-file.c -+++ b/src/core/meta-anonymous-file.c -@@ -115,6 +115,12 @@ - char *name; +--- a/mtk/mtk/mtk-anonymous-file.c ++++ b/mtk/mtk/mtk-anonymous-file.c +@@ -121,6 +121,12 @@ create_anonymous_file (const char *name, + g_autofree char *filename = NULL; - path = getenv ("XDG_RUNTIME_DIR"); + path = g_get_user_runtime_dir (); +#ifdef __TERMUX__ + if (!path) + { diff --git a/x11-packages/mutter/0007-revert-glycin.patch b/x11-packages/mutter/0007-revert-glycin.patch new file mode 100644 index 00000000000..7dad66b1acd --- /dev/null +++ b/x11-packages/mutter/0007-revert-glycin.patch @@ -0,0 +1,515 @@ +Partially revert https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4554 +glycin does not work on Android + +diff --git a/config.h.meson b/config.h.meson +index 655227c..fc1e518 100644 +--- a/config.h.meson ++++ b/config.h.meson +@@ -130,6 +130,9 @@ + /* Supports timerfd_create/timerfd_settime */ + #mesondefine HAVE_TIMERFD + ++/* Supports malloc_trim */ ++#mesondefine HAVE_MALLOC_TRIM ++ + /* Supports eventfd */ + #mesondefine HAVE_EVENTFD + +diff --git a/meson.build b/meson.build +index c9f85b1..5feca07 100644 +--- a/meson.build ++++ b/meson.build +@@ -21,7 +21,7 @@ gi_req = '>= 0.9.5' + graphene_req = '>= 1.10.2' + gtk3_req = '>= 3.19.8' + gtk4_req = '>= 4.14.0' +-glycin_req = '>= 2.0.beta.2' ++gdk_pixbuf_req = '>= 2.0' + pango_req = '>= 1.46.0' + cairo_req = '>= 1.10.0' + pangocairo_req = '>= 1.20' +@@ -117,7 +117,7 @@ subproject('gvdb') + + m_dep = cc.find_library('m', required: true) + graphene_dep = dependency('graphene-gobject-1.0', version: graphene_req) +-glycin_dep = dependency('glycin-2', version: glycin_req) ++gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req) + cairo_dep = dependency('cairo', version: cairo_req) + pixman_dep = dependency('pixman-1', version: pixman_req) + gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas', +@@ -394,6 +394,8 @@ int main (int argc, char ** argv) { + } + ''', name : 'timerfd_create(2) system call') + ++have_malloc_trim = cc.has_function('malloc_trim') ++ + have_documentation = get_option('docs') + have_tests = get_option('tests').enable_auto_if(have_native_backend).enabled() + have_mutter_tests = false +@@ -617,6 +619,7 @@ cdata.set('HAVE_STARTUP_NOTIFICATION', have_startup_notification) + cdata.set('HAVE_INTROSPECTION', have_introspection) + cdata.set('HAVE_PROFILER', have_profiler) + cdata.set('HAVE_TIMERFD', have_timerfd) ++cdata.set('HAVE_MALLOC_TRIM', have_malloc_trim) + cdata.set('HAVE_EVENTFD', have_eventfd) + cdata.set('HAVE_FONTS', have_fonts) + cdata.set('HAVE_DRM_PLANE_SIZE_HINT', have_drm_plane_size_hint) +diff --git a/src/compositor/meta-background-actor.c b/src/compositor/meta-background-actor.c +index cf3d6a0..71591a7 100644 +--- a/src/compositor/meta-background-actor.c ++++ b/src/compositor/meta-background-actor.c +@@ -22,7 +22,6 @@ + #include "config.h" + + #include "compositor/meta-background-content-private.h" +-#include "compositor/meta-background-private.h" + #include "compositor/meta-cullable.h" + #include "meta/meta-background-actor.h" + +@@ -47,26 +46,6 @@ static void cullable_iface_init (MetaCullableInterface *iface); + G_DEFINE_TYPE_WITH_CODE (MetaBackgroundActor, meta_background_actor, CLUTTER_TYPE_ACTOR, + G_IMPLEMENT_INTERFACE (META_TYPE_CULLABLE, cullable_iface_init)); + +-static void +-on_background_changed (MetaBackgroundContent *content, +- GParamSpec *pspec, +- gpointer data) +-{ +- MetaBackgroundActor *self = META_BACKGROUND_ACTOR (data); +- MetaBackground *background; +- ClutterColorState *color_state; +- +- background = meta_background_content_get_background (content); +- if (!background) +- return; +- +- color_state = meta_background_get_color_state (background); +- if (!color_state) +- return; +- +- clutter_actor_set_color_state (CLUTTER_ACTOR (self), color_state); +-} +- + static void + maybe_create_content (MetaBackgroundActor *self) + { +@@ -78,10 +57,6 @@ maybe_create_content (MetaBackgroundActor *self) + content = meta_background_content_new (self->display, self->monitor); + self->content = META_BACKGROUND_CONTENT (content); + clutter_actor_set_content (CLUTTER_ACTOR (self), content); +- g_signal_connect_object (content, "notify::background", +- G_CALLBACK (on_background_changed), self, +- G_CONNECT_DEFAULT); +- on_background_changed (self->content, NULL, self); + } + + static void +diff --git a/src/compositor/meta-background-content-private.h b/src/compositor/meta-background-content-private.h +index ac83784..36031a4 100644 +--- a/src/compositor/meta-background-content-private.h ++++ b/src/compositor/meta-background-content-private.h +@@ -11,5 +11,3 @@ void meta_background_content_cull_unobscured (MetaBackgroundContent *self, + + void meta_background_content_cull_redraw_clip (MetaBackgroundContent *self, + MtkRegion *clip_region); +- +-MetaBackground * meta_background_content_get_background (MetaBackgroundContent *self); +diff --git a/src/compositor/meta-background-content.c b/src/compositor/meta-background-content.c +index 918d5f8..481765c 100644 +--- a/src/compositor/meta-background-content.c ++++ b/src/compositor/meta-background-content.c +@@ -303,7 +303,6 @@ on_background_changed (MetaBackground *background, + { + invalidate_pipeline (self, CHANGED_BACKGROUND); + clutter_content_invalidate (CLUTTER_CONTENT (self)); +- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BACKGROUND]); + } + + static CoglPipeline * +@@ -970,9 +969,7 @@ meta_background_content_class_init (MetaBackgroundContentClass *klass) + properties[PROP_BACKGROUND] = + g_param_spec_object ("background", NULL, NULL, + META_TYPE_BACKGROUND, +- G_PARAM_READWRITE | +- G_PARAM_STATIC_STRINGS | +- G_PARAM_EXPLICIT_NOTIFY); ++ G_PARAM_READWRITE); + + properties[PROP_GRADIENT] = + g_param_spec_boolean ("gradient", NULL, NULL, +@@ -1075,7 +1072,6 @@ meta_background_content_set_background (MetaBackgroundContent *self, + + invalidate_pipeline (self, CHANGED_BACKGROUND); + clutter_content_invalidate (CLUTTER_CONTENT (self)); +- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BACKGROUND]); + } + + void +@@ -1233,9 +1229,3 @@ meta_background_content_cull_redraw_clip (MetaBackgroundContent *self, + { + set_clip_region (self, clip_region); + } +- +-MetaBackground * +-meta_background_content_get_background (MetaBackgroundContent *self) +-{ +- return self->background; +-} +diff --git a/src/compositor/meta-background-image-private.h b/src/compositor/meta-background-image-private.h +deleted file mode 100644 +index d29260d..0000000 +--- a/src/compositor/meta-background-image-private.h ++++ /dev/null +@@ -1,26 +0,0 @@ +-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +-/* +- * Copyright 2025 Red Hat, Inc. +- * +- * This program is free software; you can redistribute it and/or +- * modify it under the terms of the GNU General Public License as +- * published by the Free Software Foundation; either version 2 of the +- * License, or (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, but +- * WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You should have received a copy of the GNU General Public License +- * along with this program; if not, see . +- * +- * Author: Matthias Clasen +- */ +- +-#pragma once +- +-#include "clutter/clutter.h" +-#include "meta/meta-background-image.h" +- +-ClutterColorState * meta_background_image_get_color_state (MetaBackgroundImage *self); +diff --git a/src/compositor/meta-background-image.c b/src/compositor/meta-background-image.c +index 6cf9187..59e43bd 100644 +--- a/src/compositor/meta-background-image.c ++++ b/src/compositor/meta-background-image.c +@@ -18,13 +18,16 @@ + + #include "config.h" + +-#include "compositor/meta-background-image-private.h" ++#include "meta/meta-background-image.h" + +-#include ++#include + #include + ++#ifdef HAVE_MALLOC_TRIM ++#include ++#endif ++ + #include "clutter/clutter.h" +-#include "clutter/clutter-backend-private.h" + #include "compositor/cogl-utils.h" + + enum +@@ -63,7 +66,6 @@ struct _MetaBackgroundImage + gboolean in_cache; + gboolean loaded; + CoglTexture *texture; +- ClutterColorState *color_state; + }; + + G_DEFINE_TYPE (MetaBackgroundImageCache, meta_background_image_cache, G_TYPE_OBJECT); +@@ -117,111 +119,37 @@ meta_background_image_cache_get_default (void) + return cache; + } + +-static CoglPixelFormat +-gly_memory_format_to_cogl (GlyMemoryFormat format) +-{ +- switch ((guint) format) +- { +- case GLY_MEMORY_B8G8R8A8_PREMULTIPLIED: +- return COGL_PIXEL_FORMAT_BGRA_8888_PRE; +- case GLY_MEMORY_A8R8G8B8_PREMULTIPLIED: +- return COGL_PIXEL_FORMAT_ARGB_8888_PRE; +- case GLY_MEMORY_R8G8B8A8_PREMULTIPLIED: +- return COGL_PIXEL_FORMAT_RGBA_8888_PRE; +- case GLY_MEMORY_B8G8R8A8: +- return COGL_PIXEL_FORMAT_BGRA_8888; +- case GLY_MEMORY_A8R8G8B8: +- return COGL_PIXEL_FORMAT_ARGB_8888; +- case GLY_MEMORY_R8G8B8A8: +- return COGL_PIXEL_FORMAT_RGBA_8888; +- case GLY_MEMORY_A8B8G8R8: +- return COGL_PIXEL_FORMAT_ABGR_8888; +- case GLY_MEMORY_R8G8B8: +- return COGL_PIXEL_FORMAT_RGB_888; +- case GLY_MEMORY_B8G8R8: +- return COGL_PIXEL_FORMAT_BGR_888; +- case GLY_MEMORY_R16G16B16A16_PREMULTIPLIED: +- return COGL_PIXEL_FORMAT_RGBA_16161616_PRE; +- case GLY_MEMORY_R16G16B16A16: +- return COGL_PIXEL_FORMAT_RGBA_16161616; +- case GLY_MEMORY_R16G16B16A16_FLOAT: +- return COGL_PIXEL_FORMAT_RGBA_FP_16161616; +- case GLY_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: +- return COGL_PIXEL_FORMAT_RGBA_FP_32323232_PRE; +- case GLY_MEMORY_R32G32B32A32_FLOAT: +- return COGL_PIXEL_FORMAT_RGBA_FP_32323232; +- default: +- g_assert_not_reached (); +- } +-} +- +-static GlyMemoryFormatSelection +-glycin_supported_memory_formats (void) +-{ +- return GLY_MEMORY_SELECTION_B8G8R8A8_PREMULTIPLIED | +- GLY_MEMORY_SELECTION_A8R8G8B8_PREMULTIPLIED | +- GLY_MEMORY_SELECTION_R8G8B8A8_PREMULTIPLIED | +- GLY_MEMORY_SELECTION_B8G8R8A8 | +- GLY_MEMORY_SELECTION_A8R8G8B8 | +- GLY_MEMORY_SELECTION_R8G8B8A8 | +- GLY_MEMORY_SELECTION_A8B8G8R8 | +- GLY_MEMORY_SELECTION_R8G8B8 | +- GLY_MEMORY_SELECTION_B8G8R8 | +- GLY_MEMORY_SELECTION_R16G16B16A16_PREMULTIPLIED | +- GLY_MEMORY_SELECTION_R16G16B16A16 | +- GLY_MEMORY_SELECTION_R16G16B16A16_FLOAT | +- GLY_MEMORY_SELECTION_R32G32B32A32_FLOAT_PREMULTIPLIED | +- GLY_MEMORY_SELECTION_R32G32B32A32_FLOAT; +-} +- +-static void +-gly_cicp_to_clutter (const GlyCicp *gly_cicp, +- ClutterCicp *clutter_cicp) +-{ +- clutter_cicp->primaries = (ClutterCicpPrimaries) gly_cicp->color_primaries; +- clutter_cicp->transfer = (ClutterCicpTransfer) gly_cicp->transfer_characteristics; +- clutter_cicp->matrix_coefficients = gly_cicp->matrix_coefficients; +- clutter_cicp->video_full_range_flag = gly_cicp->video_full_range_flag; +-} +- + static void + load_file (GTask *task, +- MetaBackgroundImage *source, ++ MetaBackgroundImage *image, + gpointer task_data, + GCancellable *cancellable) + { +- g_autoptr (GFileInputStream) stream = NULL; +- g_autoptr (GlyLoader) loader = NULL; +- g_autoptr (GlyImage) image = NULL; +- GlyFrame *frame; + GError *error = NULL; ++ GdkPixbuf *pixbuf; ++ GFileInputStream *stream; + +- stream = g_file_read (source->file, NULL, &error); ++ stream = g_file_read (image->file, NULL, &error); + if (stream == NULL) + { + g_task_return_error (task, error); + return; + } + +- loader = gly_loader_new_for_stream (G_INPUT_STREAM (stream)); ++ pixbuf = gdk_pixbuf_new_from_stream (G_INPUT_STREAM (stream), NULL, &error); ++ g_object_unref (stream); + +- gly_loader_set_accepted_memory_formats (loader, glycin_supported_memory_formats ()); ++#ifdef HAVE_MALLOC_TRIM ++ malloc_trim (0); ++#endif + +- image = gly_loader_load (loader, &error); +- if (!image) ++ if (pixbuf == NULL) + { + g_task_return_error (task, error); + return; + } + +- frame = gly_image_next_frame (image, &error); +- if (!frame) +- { +- g_task_return_error (task, error); +- return; +- } +- +- g_task_return_pointer (task, frame, (GDestroyNotify) g_object_unref); ++ g_task_return_pointer (task, pixbuf, (GDestroyNotify) g_object_unref); + } + + static void +@@ -236,16 +164,15 @@ file_loaded (GObject *source_object, + g_autoptr (GError) local_error = NULL; + GTask *task; + CoglTexture *texture; +- g_autoptr (GlyFrame) frame = NULL; ++ GdkPixbuf *pixbuf, *rotated; + int width, height, row_stride; +- GlyMemoryFormat format; +- g_autoptr (GlyCicp) cicp = NULL; +- GBytes *bytes; ++ guchar *pixels; ++ gboolean has_alpha; + + task = G_TASK (result); +- frame = g_task_propagate_pointer (task, &error); ++ pixbuf = g_task_propagate_pointer (task, &error); + +- if (frame == NULL) ++ if (pixbuf == NULL) + { + char *uri = g_file_get_uri (image->file); + g_warning ("Failed to load background '%s': %s", +@@ -254,23 +181,27 @@ file_loaded (GObject *source_object, + goto out; + } + +- width = gly_frame_get_width (frame); +- height = gly_frame_get_height (frame); +- row_stride = gly_frame_get_stride (frame); +- bytes = gly_frame_get_buf_bytes (frame); +- format = gly_frame_get_memory_format (frame); +- cicp = gly_frame_get_color_cicp (frame); ++ rotated = gdk_pixbuf_apply_embedded_orientation (pixbuf); ++ if (rotated != NULL) ++ { ++ g_object_unref (pixbuf); ++ pixbuf = rotated; ++ } ++ ++ width = gdk_pixbuf_get_width (pixbuf); ++ height = gdk_pixbuf_get_height (pixbuf); ++ row_stride = gdk_pixbuf_get_rowstride (pixbuf); ++ pixels = gdk_pixbuf_get_pixels (pixbuf); ++ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + + texture = meta_create_texture (width, height, ctx, +- gly_memory_format_has_alpha (format) +- ? COGL_TEXTURE_COMPONENTS_RGBA +- : COGL_TEXTURE_COMPONENTS_RGB, ++ has_alpha ? COGL_TEXTURE_COMPONENTS_RGBA : COGL_TEXTURE_COMPONENTS_RGB, + META_TEXTURE_ALLOW_SLICING); + + if (!cogl_texture_set_data (texture, +- gly_memory_format_to_cogl (format), ++ has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, + row_stride, +- g_bytes_get_data (bytes, NULL), 0, ++ pixels, 0, + &local_error)) + { + g_warning ("Failed to create texture for background: %s", +@@ -280,28 +211,10 @@ file_loaded (GObject *source_object, + + image->texture = texture; + +- if (cicp) +- { +- ClutterCicp clutter_cicp; +- ClutterBackend *clutter_backend = clutter_get_default_backend (); +- ClutterContext *clutter_context = clutter_backend->context; +- g_autoptr (GError) local_error2 = NULL; +- +- gly_cicp_to_clutter (cicp, &clutter_cicp); +- +- image->color_state = +- clutter_color_state_params_new_from_cicp (clutter_context, +- &clutter_cicp, +- &local_error2); +- if (local_error2) +- g_warning ("%s", local_error2->message); +- } +- else +- { +- image->color_state = NULL; +- } +- + out: ++ if (pixbuf != NULL) ++ g_object_unref (pixbuf); ++ + image->loaded = TRUE; + g_signal_emit (image, signals[LOADED], 0); + } +@@ -395,8 +308,6 @@ meta_background_image_finalize (GObject *object) + if (image->file) + g_object_unref (image->file); + +- g_clear_object (&image->color_state); +- + G_OBJECT_CLASS (meta_background_image_parent_class)->finalize (object); + } + +@@ -462,11 +373,3 @@ meta_background_image_get_texture (MetaBackgroundImage *image) + + return image->texture; + } +- +-ClutterColorState * +-meta_background_image_get_color_state (MetaBackgroundImage *image) +-{ +- g_return_val_if_fail (META_IS_BACKGROUND_IMAGE (image), NULL); +- +- return image->color_state; +-} +diff --git a/src/compositor/meta-background-private.h b/src/compositor/meta-background-private.h +index 056e274..dca3a5d 100644 +--- a/src/compositor/meta-background-private.h ++++ b/src/compositor/meta-background-private.h +@@ -9,5 +9,3 @@ CoglTexture * meta_background_get_texture (MetaBackground *self, + int monitor_index, + MtkRectangle *texture_area, + CoglPipelineWrapMode *wrap_mode); +- +-ClutterColorState * meta_background_get_color_state (MetaBackground *self); +diff --git a/src/compositor/meta-background.c b/src/compositor/meta-background.c +index 18833ff..7e33bc1 100644 +--- a/src/compositor/meta-background.c ++++ b/src/compositor/meta-background.c +@@ -24,11 +24,9 @@ + #include + + #include "backends/meta-backend-private.h" +-#include "clutter/clutter-color-state.h" + #include "compositor/cogl-utils.h" +-#include "compositor/meta-background-image-private.h" +-#include "meta/compositor.h" + #include "meta/display.h" ++#include "meta/meta-background-image.h" + #include "meta/meta-background.h" + #include "meta/meta-monitor-manager.h" + #include "meta/util.h" +@@ -1034,16 +1032,3 @@ meta_background_refresh_all (void) + for (l = all_backgrounds; l; l = l->next) + mark_changed (l->data); + } +- +-ClutterColorState * +-meta_background_get_color_state (MetaBackground *self) +-{ +- g_return_val_if_fail (META_IS_BACKGROUND (self), NULL); +- +- if (self->background_image1) +- return meta_background_image_get_color_state (self->background_image1); +- else if (self->background_image2) +- return meta_background_image_get_color_state (self->background_image2); +- else +- return NULL; +-} +diff --git a/src/meson.build b/src/meson.build +index 508c341..c36efc3 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -25,7 +25,7 @@ mutter_pkg_private_deps = [ + gmodule_no_export_dep, + gnome_settings_daemon_dep, + xkbcommon_dep, +- glycin_dep, ++ gdk_pixbuf_dep, + libeis_dep, + libdisplay_info_dep, + ] diff --git a/x11-packages/mutter/0008-fix-build-cross-g-ir-scanner-1.80.patch b/x11-packages/mutter/0008-fix-build-cross-g-ir-scanner-1.80.patch new file mode 100644 index 00000000000..c10d99b0677 --- /dev/null +++ b/x11-packages/mutter/0008-fix-build-cross-g-ir-scanner-1.80.patch @@ -0,0 +1,11 @@ +--- a/meson.build ++++ b/meson.build +@@ -367,7 +367,7 @@ if have_introspection + '-U_GNU_SOURCE', + ] + +- if gobject_introspection_dep.version().version_compare('>=1.83.2') ++ if false + introspection_args += [ + '--doc-format=gi-docgen', + ] diff --git a/x11-packages/mutter/0009-fix-pango-autoptr.patch b/x11-packages/mutter/0009-fix-pango-autoptr.patch new file mode 100644 index 00000000000..1c4e0332b4f --- /dev/null +++ b/x11-packages/mutter/0009-fix-pango-autoptr.patch @@ -0,0 +1,12 @@ +--- a/clutter/clutter/pango/clutter-pango-private.h ++++ b/clutter/clutter/pango/clutter-pango-private.h +@@ -79,7 +79,9 @@ + ClutterColorState *color_state, + ClutterColorState *target_color_state); + ++#if !PANGO_VERSION_CHECK(1, 57, 2) + G_DEFINE_AUTOPTR_CLEANUP_FUNC (PangoRenderer, g_object_unref) ++#endif + + static inline PangoDirection + clutter_text_direction_to_pango_direction (ClutterTextDirection dir) diff --git a/x11-packages/mutter/0010-backends-anland-Add-current-Anland-transport.patch b/x11-packages/mutter/0010-backends-anland-Add-current-Anland-transport.patch new file mode 100644 index 00000000000..7f494b174c1 --- /dev/null +++ b/x11-packages/mutter/0010-backends-anland-Add-current-Anland-transport.patch @@ -0,0 +1,1694 @@ +From 93d1a7b64da000e54eda7c76d3397187bbe58510 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 08:50:33 +0800 +Subject: [PATCH 10/27] backends/anland: Add current Anland transport + +Add a standalone producer transport for the current Anland: Termux wire ABI. Keep the daemon control connection alive while no Android consumer is present, and report a lost control channel separately from malformed handoff data. + +Own the five handoff descriptors, dma-bufs, shared selected-buffer index, frame fence, input framing, output payloads, and fallback transition in one context. + +Add a mock daemon test covering the 5.13 ABI, no-consumer retry, dma-buf metadata, readiness and audio fd order, clipboard framing, native fence transfer, consumer loss, and reconnect. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0001-backends-anland-Add-current-Anland-transport.patch + +diff --git a/src/backends/anland/vendor/meta-anland-protocol.h b/src/backends/anland/vendor/meta-anland-protocol.h +new file mode 100644 +index 0000000000..804385f130 +--- /dev/null ++++ b/src/backends/anland/vendor/meta-anland-protocol.h +@@ -0,0 +1,183 @@ ++#ifndef META_ANLAND_PROTOCOL_H ++#define META_ANLAND_PROTOCOL_H ++ ++#include ++ ++#define CTRL_MSG_CONSUMER_HELLO 1 ++#define CTRL_MSG_PRODUCER_HELLO 2 ++#define CTRL_MSG_SCREEN_INFO 7 ++#define CTRL_MSG_REJECT 8 ++#define CTRL_MSG_PICKUP_FDS 9 ++#define CTRL_MSG_FDS_READY 10 ++ ++#define DATA_MSG_BUF_READY 100 ++#define DATA_MSG_REFRESH_DONE 101 ++#define DATA_MSG_INPUT_EVENT 102 ++#define DATA_MSG_OUTPUT_EVENT 103 ++#define DATA_MSG_INPUT_EXTEND_FDS 104 ++#define DATA_MSG_BUFS_READY 200 ++ ++#define MAX_BUFS 8 ++ ++struct ctrl_msg ++{ ++ uint32_t type; ++ uint32_t size; ++ uint8_t payload[]; ++} __attribute__ ((packed)); ++ ++struct data_msg ++{ ++ uint32_t type; ++ uint32_t size; ++ uint8_t payload[]; ++} __attribute__ ((packed)); ++ ++struct screen_info ++{ ++ uint32_t width; ++ uint32_t height; ++ uint32_t format; ++ uint32_t refresh; ++} __attribute__ ((packed)); ++ ++struct buf_info ++{ ++ uint32_t stride; ++ uint32_t width; ++ uint32_t height; ++ uint32_t format; ++ uint64_t modifier; ++ uint32_t offset; ++} __attribute__ ((packed)); ++ ++#define INPUT_TYPE_TOUCH 1 ++#define INPUT_TYPE_KEY 2 ++#define INPUT_TYPE_POINTER_MOTION 3 ++#define INPUT_TYPE_POINTER_BUTTON 4 ++#define INPUT_TYPE_POINTER_AXIS 5 ++#define INPUT_TYPE_TOUCH_FRAME 6 ++#define INPUT_TYPE_DISPLAY_REFRESH 7 ++#define INPUT_TYPE_CLIPBOARD 8 ++#define INPUT_TYPE_TEXT_INPUT 9 ++#define INPUT_TYPE_ACTION 10 ++#define INPUT_TYPE_RESOURCE 11 ++ ++#define SERVICE_TYPE_CAMERA 1 ++ ++#define INPUT_ACTION_DOWN 0 ++#define INPUT_ACTION_UP 1 ++#define INPUT_ACTION_MOVE 2 ++ ++#define OUTPUT_TYPE_CLIPBOARD 1 ++#define OUTPUT_TYPE_RESOURCES_REQUEST 2 ++ ++struct InputEvent ++{ ++ uint32_t type; ++ union ++ { ++ struct ++ { ++ int32_t action; ++ float x; ++ float y; ++ int32_t pointer_id; ++ } touch; ++ struct ++ { ++ int32_t action; ++ int32_t keycode; ++ } key; ++ struct ++ { ++ float x; ++ float y; ++ float dx; ++ float dy; ++ } pointer_motion; ++ struct ++ { ++ uint32_t button; ++ int32_t pressed; ++ } pointer_button; ++ struct ++ { ++ uint32_t axis; ++ float value; ++ int32_t discrete; ++ } pointer_axis; ++ struct ++ { ++ uint32_t refresh_mhz; ++ } display; ++ struct ++ { ++ uint32_t size; ++ } clipboard; ++ struct ++ { ++ uint32_t size; ++ } text_input; ++ struct ++ { ++ uint32_t action; ++ int32_t value; ++ } input_action; ++ struct ++ { ++ uint32_t type; ++ uint32_t fdnum; ++ } resource; ++ struct ++ { ++ uint32_t padding[4]; ++ }; ++ }; ++} __attribute__ ((packed)); ++ ++struct OutputEvent ++{ ++ uint32_t type; ++ union ++ { ++ struct ++ { ++ uint32_t size; ++ } clipboard; ++ struct ++ { ++ uint32_t type; ++ uint32_t args[3]; ++ } resources_request; ++ struct ++ { ++ uint32_t padding[4]; ++ }; ++ }; ++} __attribute__ ((packed)); ++ ++#define AUDIO_MSG_FORMAT 1 ++#define AUDIO_MSG_PCM 2 ++ ++#define AUDIO_FORMAT_S16LE 0 ++ ++#define AUDIO_ROLE_PLAYBACK 0 ++#define AUDIO_ROLE_CAPTURE 1 ++ ++struct audio_format ++{ ++ uint32_t rate; ++ uint32_t channels; ++ uint32_t format; ++ uint32_t role; ++ uint32_t quantum; ++} __attribute__ ((packed)); ++ ++struct audio_msg ++{ ++ uint32_t type; ++ uint32_t size; ++} __attribute__ ((packed)); ++ ++#endif /* META_ANLAND_PROTOCOL_H */ +diff --git a/src/backends/anland/vendor/meta-anland-socket-utils.c b/src/backends/anland/vendor/meta-anland-socket-utils.c +new file mode 100644 +index 0000000000..01beee104b +--- /dev/null ++++ b/src/backends/anland/vendor/meta-anland-socket-utils.c +@@ -0,0 +1,210 @@ ++#include "meta-anland-socket-utils.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++int ++meta_anland_connect_unix (const char *path) ++{ ++ struct sockaddr_un address = { 0 }; ++ int fd; ++ ++ if (!path || strlen (path) >= sizeof (address.sun_path)) ++ { ++ errno = ENAMETOOLONG; ++ return -1; ++ } ++ ++ fd = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); ++ if (fd < 0) ++ return -1; ++ ++ address.sun_family = AF_UNIX; ++ strcpy (address.sun_path, path); ++ if (connect (fd, (struct sockaddr *) &address, sizeof (address)) < 0) ++ { ++ close (fd); ++ return -1; ++ } ++ ++ return fd; ++} ++ ++int ++meta_anland_send_all (int fd, ++ const void *buffer, ++ size_t length) ++{ ++ const uint8_t *data = buffer; ++ size_t sent = 0; ++ ++ while (sent < length) ++ { ++ ssize_t n = send (fd, data + sent, length - sent, MSG_NOSIGNAL); ++ ++ if (n > 0) ++ { ++ sent += n; ++ continue; ++ } ++ if (n < 0 && errno == EINTR) ++ continue; ++ ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int ++meta_anland_recv_all (int fd, ++ void *buffer, ++ size_t length) ++{ ++ uint8_t *data = buffer; ++ size_t received = 0; ++ ++ while (received < length) ++ { ++ ssize_t n = recv (fd, data + received, length - received, 0); ++ ++ if (n > 0) ++ { ++ received += n; ++ continue; ++ } ++ if (n < 0 && errno == EINTR) ++ continue; ++ ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int ++meta_anland_send_fds (int fd, ++ const void *buffer, ++ size_t length, ++ const int *fds, ++ int n_fds) ++{ ++ struct iovec iov = { .iov_base = (void *) buffer, .iov_len = length }; ++ struct msghdr message = { .msg_iov = &iov, .msg_iovlen = 1 }; ++ struct cmsghdr *cmsg; ++ size_t control_length; ++ char *control; ++ ssize_t n; ++ ++ if (!buffer || length == 0 || !fds || n_fds <= 0) ++ { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ control_length = CMSG_SPACE (sizeof (int) * (size_t) n_fds); ++ control = calloc (1, control_length); ++ if (!control) ++ return -1; ++ ++ message.msg_control = control; ++ message.msg_controllen = control_length; ++ cmsg = CMSG_FIRSTHDR (&message); ++ cmsg->cmsg_level = SOL_SOCKET; ++ cmsg->cmsg_type = SCM_RIGHTS; ++ cmsg->cmsg_len = CMSG_LEN (sizeof (int) * (size_t) n_fds); ++ memcpy (CMSG_DATA (cmsg), fds, sizeof (int) * (size_t) n_fds); ++ ++ do ++ n = sendmsg (fd, &message, MSG_NOSIGNAL); ++ while (n < 0 && errno == EINTR); ++ ++ free (control); ++ return n == (ssize_t) length ? 0 : -1; ++} ++ ++int ++meta_anland_recv_fds (int fd, ++ void *buffer, ++ size_t length, ++ int *fds, ++ int max_fds, ++ int *n_fds) ++{ ++ struct iovec iov = { .iov_base = buffer, .iov_len = length }; ++ struct msghdr message = { .msg_iov = &iov, .msg_iovlen = 1 }; ++ size_t control_length; ++ char *control; ++ ssize_t n; ++ ++ if (!buffer || length == 0 || !fds || max_fds <= 0 || !n_fds) ++ { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ *n_fds = 0; ++ control_length = CMSG_SPACE (sizeof (int) * (size_t) max_fds); ++ control = calloc (1, control_length); ++ if (!control) ++ return -1; ++ ++ message.msg_control = control; ++ message.msg_controllen = control_length; ++ do ++ n = recvmsg (fd, &message, MSG_CMSG_CLOEXEC); ++ while (n < 0 && errno == EINTR); ++ ++ if (n <= 0) ++ goto fail; ++ ++ for (struct cmsghdr *cmsg = CMSG_FIRSTHDR (&message); ++ cmsg; ++ cmsg = CMSG_NXTHDR (&message, cmsg)) ++ { ++ int count; ++ int available; ++ int copy_count; ++ ++ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) ++ continue; ++ if (cmsg->cmsg_len < CMSG_LEN (0) || ++ (cmsg->cmsg_len - CMSG_LEN (0)) % sizeof (int) != 0) ++ goto fail; ++ ++ count = (int) ((cmsg->cmsg_len - CMSG_LEN (0)) / sizeof (int)); ++ available = max_fds - *n_fds; ++ copy_count = count < available ? count : available; ++ memcpy (fds + *n_fds, CMSG_DATA (cmsg), ++ sizeof (int) * (size_t) copy_count); ++ *n_fds += copy_count; ++ for (int i = copy_count; i < count; i++) ++ { ++ int received_fd; ++ ++ memcpy (&received_fd, (int *) CMSG_DATA (cmsg) + i, ++ sizeof (received_fd)); ++ close (received_fd); ++ } ++ } ++ ++ if (!(message.msg_flags & MSG_CTRUNC)) ++ { ++ free (control); ++ return (int) n; ++ } ++ ++ errno = EMSGSIZE; ++ ++fail: ++ for (int i = 0; i < *n_fds; i++) ++ close (fds[i]); ++ *n_fds = 0; ++ free (control); ++ return -1; ++} +diff --git a/src/backends/anland/vendor/meta-anland-socket-utils.h b/src/backends/anland/vendor/meta-anland-socket-utils.h +new file mode 100644 +index 0000000000..8daf5299e4 +--- /dev/null ++++ b/src/backends/anland/vendor/meta-anland-socket-utils.h +@@ -0,0 +1,27 @@ ++#ifndef META_ANLAND_SOCKET_UTILS_H ++#define META_ANLAND_SOCKET_UTILS_H ++ ++#include ++ ++int meta_anland_connect_unix (const char *path); ++ ++int meta_anland_send_all (int fd, ++ const void *buffer, ++ size_t length); ++int meta_anland_recv_all (int fd, ++ void *buffer, ++ size_t length); ++ ++int meta_anland_send_fds (int fd, ++ const void *buffer, ++ size_t length, ++ const int *fds, ++ int n_fds); ++int meta_anland_recv_fds (int fd, ++ void *buffer, ++ size_t length, ++ int *fds, ++ int max_fds, ++ int *n_fds); ++ ++#endif /* META_ANLAND_SOCKET_UTILS_H */ +diff --git a/src/backends/anland/vendor/meta-anland-transport.c b/src/backends/anland/vendor/meta-anland-transport.c +new file mode 100644 +index 0000000000..a5ef702dfc +--- /dev/null ++++ b/src/backends/anland/vendor/meta-anland-transport.c +@@ -0,0 +1,706 @@ ++#include "meta-anland-transport.h" ++ ++#include "meta-anland-socket-utils.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define HANDSHAKE_TIMEOUT_MS 100 ++#define INPUT_TIMEOUT_MS 100 ++ ++struct _MetaAnlandTransport ++{ ++ int control_fd; ++ int data_fd; ++ int buffer_ready_fd; ++ int fence_fd; ++ int shm_fd; ++ int audio_fd; ++ int pending_render_fence_fd; ++ volatile uint32_t *selected_buffer; ++ ++ struct screen_info screen_info; ++ int buffer_fds[MAX_BUFS]; ++ struct buf_info buffer_infos[MAX_BUFS]; ++ int n_buffers; ++ bool fallback; ++ ++ void (*fallback_callback) (void *user_data); ++ void *fallback_user_data; ++}; ++ ++static void ++close_fds (int *fds, ++ int n_fds) ++{ ++ for (int i = 0; i < n_fds; i++) ++ { ++ if (fds[i] >= 0) ++ close (fds[i]); ++ } ++} ++ ++static void ++release_consumer_resources (MetaAnlandTransport *transport) ++{ ++ for (int i = 0; i < transport->n_buffers; i++) ++ { ++ if (transport->buffer_fds[i] >= 0) ++ { ++ close (transport->buffer_fds[i]); ++ transport->buffer_fds[i] = -1; ++ } ++ } ++ transport->n_buffers = 0; ++ ++ if (transport->selected_buffer) ++ { ++ munmap ((void *) transport->selected_buffer, sizeof (uint32_t)); ++ transport->selected_buffer = NULL; ++ } ++ ++ if (transport->data_fd >= 0) ++ close (transport->data_fd); ++ if (transport->buffer_ready_fd >= 0) ++ close (transport->buffer_ready_fd); ++ if (transport->fence_fd >= 0) ++ close (transport->fence_fd); ++ if (transport->shm_fd >= 0) ++ close (transport->shm_fd); ++ if (transport->audio_fd >= 0) ++ close (transport->audio_fd); ++ if (transport->pending_render_fence_fd >= 0) ++ close (transport->pending_render_fence_fd); ++ ++ transport->data_fd = -1; ++ transport->buffer_ready_fd = -1; ++ transport->fence_fd = -1; ++ transport->shm_fd = -1; ++ transport->audio_fd = -1; ++ transport->pending_render_fence_fd = -1; ++} ++ ++static void ++enter_fallback (MetaAnlandTransport *transport) ++{ ++ if (transport->fallback) ++ return; ++ ++ transport->fallback = true; ++ release_consumer_resources (transport); ++ if (transport->fallback_callback) ++ transport->fallback_callback (transport->fallback_user_data); ++} ++ ++static int ++wait_for_fd (int fd, ++ short events, ++ int timeout_ms) ++{ ++ struct pollfd poll_fd = { .fd = fd, .events = events }; ++ int result; ++ ++ do ++ result = poll (&poll_fd, 1, timeout_ms); ++ while (result < 0 && errno == EINTR); ++ ++ if (result <= 0) ++ return result; ++ if (poll_fd.revents & (POLLERR | POLLHUP | POLLNVAL)) ++ return -1; ++ return poll_fd.revents & events ? 1 : -1; ++} ++ ++static int ++recv_all_timeout (int fd, ++ void *buffer, ++ size_t size, ++ int timeout_ms) ++{ ++ uint8_t *data = buffer; ++ size_t received = 0; ++ ++ while (received < size) ++ { ++ ssize_t n = recv (fd, data + received, size - received, MSG_DONTWAIT); ++ ++ if (n > 0) ++ { ++ received += n; ++ continue; ++ } ++ if (n == 0) ++ return -1; ++ if (errno == EINTR) ++ continue; ++ if (errno != EAGAIN) ++ return -1; ++ if (wait_for_fd (fd, POLLIN, timeout_ms) != 1) ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int ++send_all_timeout (int fd, ++ const void *buffer, ++ size_t size, ++ int timeout_ms) ++{ ++ const uint8_t *data = buffer; ++ size_t sent = 0; ++ ++ while (sent < size) ++ { ++ ssize_t n = send (fd, data + sent, size - sent, ++ MSG_NOSIGNAL | MSG_DONTWAIT); ++ ++ if (n > 0) ++ { ++ sent += n; ++ continue; ++ } ++ if (n < 0 && errno == EINTR) ++ continue; ++ if (n < 0 && errno == EAGAIN && ++ wait_for_fd (fd, POLLOUT, timeout_ms) == 1) ++ continue; ++ ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int ++recv_fds_exact (int fd, ++ void *buffer, ++ size_t size, ++ int *fds, ++ int max_fds, ++ int *n_fds) ++{ ++ int received; ++ ++ received = meta_anland_recv_fds (fd, buffer, size, fds, max_fds, n_fds); ++ if (received < 0) ++ return -1; ++ if ((size_t) received < size && ++ meta_anland_recv_all (fd, (uint8_t *) buffer + received, ++ size - (size_t) received) < 0) ++ return -1; ++ ++ return 0; ++} ++ ++static int ++discard_data_payload (MetaAnlandTransport *transport, ++ uint32_t size) ++{ ++ uint8_t buffer[4096]; ++ ++ if (size > META_ANLAND_MAX_PAYLOAD_SIZE) ++ return -1; ++ ++ while (size > 0) ++ { ++ size_t chunk = size < sizeof (buffer) ? size : sizeof (buffer); ++ ++ if (recv_all_timeout (transport->data_fd, buffer, chunk, ++ INPUT_TIMEOUT_MS) < 0) ++ return -1; ++ size -= (uint32_t) chunk; ++ } ++ ++ return 0; ++} ++ ++static MetaAnlandTransportPickupResult ++pickup_fds (MetaAnlandTransport *transport) ++{ ++ struct ctrl_msg request = { .type = CTRL_MSG_PICKUP_FDS, .size = 0 }; ++ struct ctrl_msg response; ++ int fds[5] = { -1, -1, -1, -1, -1 }; ++ int n_fds = 0; ++ int wait_result; ++ ++ if (meta_anland_send_all (transport->control_fd, &request, ++ sizeof (request)) < 0) ++ return META_ANLAND_TRANSPORT_PICKUP_DAEMON_LOST; ++ ++ wait_result = wait_for_fd (transport->control_fd, POLLIN, ++ HANDSHAKE_TIMEOUT_MS); ++ if (wait_result == 0) ++ return META_ANLAND_TRANSPORT_PICKUP_NO_CONSUMER; ++ if (wait_result < 0 || ++ recv_fds_exact (transport->control_fd, &response, sizeof (response), ++ fds, 5, &n_fds) < 0) ++ { ++ close_fds (fds, n_fds); ++ return META_ANLAND_TRANSPORT_PICKUP_DAEMON_LOST; ++ } ++ ++ if (response.type != CTRL_MSG_FDS_READY || response.size != 0 || n_fds != 5) ++ { ++ close_fds (fds, n_fds); ++ return META_ANLAND_TRANSPORT_PICKUP_PROTOCOL_ERROR; ++ } ++ ++ transport->buffer_ready_fd = fds[0]; ++ transport->fence_fd = fds[1]; ++ transport->data_fd = fds[2]; ++ transport->shm_fd = fds[3]; ++ transport->audio_fd = fds[4]; ++ transport->selected_buffer = mmap (NULL, sizeof (uint32_t), PROT_READ, ++ MAP_SHARED, transport->shm_fd, 0); ++ if (transport->selected_buffer == MAP_FAILED) ++ { ++ transport->selected_buffer = NULL; ++ return META_ANLAND_TRANSPORT_PICKUP_PROTOCOL_ERROR; ++ } ++ ++ return META_ANLAND_TRANSPORT_PICKUP_READY; ++} ++ ++static MetaAnlandTransportPickupResult ++receive_buffers (MetaAnlandTransport *transport) ++{ ++ struct data_msg header; ++ struct buf_info buffer_infos[MAX_BUFS]; ++ int fds[MAX_BUFS] = { -1, -1, -1, -1, -1, -1, -1, -1 }; ++ int n_fds = 0; ++ int n_buffers; ++ ++ if (wait_for_fd (transport->data_fd, POLLIN, HANDSHAKE_TIMEOUT_MS) != 1 || ++ recv_fds_exact (transport->data_fd, &header, sizeof (header), ++ fds, MAX_BUFS, &n_fds) < 0) ++ { ++ close_fds (fds, n_fds); ++ return META_ANLAND_TRANSPORT_PICKUP_NO_CONSUMER; ++ } ++ ++ if (header.type != DATA_MSG_BUFS_READY || header.size == 0 || ++ header.size % sizeof (struct buf_info) != 0) ++ goto protocol_error; ++ ++ n_buffers = (int) (header.size / sizeof (struct buf_info)); ++ if (n_buffers != n_fds || n_buffers < 1 || n_buffers > MAX_BUFS || ++ meta_anland_recv_all (transport->data_fd, buffer_infos, ++ header.size) < 0) ++ goto protocol_error; ++ ++ for (int i = 0; i < n_buffers; i++) ++ { ++ if (buffer_infos[i].width == 0 || buffer_infos[i].height == 0 || ++ buffer_infos[i].stride == 0 || ++ (i > 0 && (buffer_infos[i].width != buffer_infos[0].width || ++ buffer_infos[i].height != buffer_infos[0].height || ++ buffer_infos[i].format != buffer_infos[0].format))) ++ goto protocol_error; ++ } ++ ++ for (int i = 0; i < n_buffers; i++) ++ { ++ transport->buffer_fds[i] = fds[i]; ++ transport->buffer_infos[i] = buffer_infos[i]; ++ } ++ transport->n_buffers = n_buffers; ++ return META_ANLAND_TRANSPORT_PICKUP_READY; ++ ++protocol_error: ++ close_fds (fds, n_fds); ++ return META_ANLAND_TRANSPORT_PICKUP_PROTOCOL_ERROR; ++} ++ ++int ++meta_anland_transport_connect (MetaAnlandTransport **out_transport, ++ const char *socket_path) ++{ ++ MetaAnlandTransport *transport; ++ struct ctrl_msg request = { .type = CTRL_MSG_PRODUCER_HELLO, .size = 0 }; ++ struct ctrl_msg response; ++ struct screen_info screen_info; ++ ++ if (!out_transport || !socket_path) ++ return -1; ++ ++ *out_transport = NULL; ++ transport = calloc (1, sizeof (*transport)); ++ if (!transport) ++ return -1; ++ ++ transport->control_fd = -1; ++ transport->data_fd = -1; ++ transport->buffer_ready_fd = -1; ++ transport->fence_fd = -1; ++ transport->shm_fd = -1; ++ transport->audio_fd = -1; ++ transport->pending_render_fence_fd = -1; ++ transport->fallback = true; ++ for (int i = 0; i < MAX_BUFS; i++) ++ transport->buffer_fds[i] = -1; ++ ++ transport->control_fd = meta_anland_connect_unix (socket_path); ++ if (transport->control_fd < 0 || ++ meta_anland_send_all (transport->control_fd, &request, ++ sizeof (request)) < 0 || ++ meta_anland_recv_all (transport->control_fd, &response, ++ sizeof (response)) < 0 || ++ response.type != CTRL_MSG_SCREEN_INFO || ++ response.size != sizeof (screen_info) || ++ meta_anland_recv_all (transport->control_fd, &screen_info, ++ sizeof (screen_info)) < 0) ++ { ++ if (transport->control_fd >= 0) ++ close (transport->control_fd); ++ free (transport); ++ return -1; ++ } ++ ++ transport->screen_info = screen_info; ++ *out_transport = transport; ++ return 0; ++} ++ ++void ++meta_anland_transport_disconnect (MetaAnlandTransport *transport) ++{ ++ if (!transport) ++ return; ++ ++ release_consumer_resources (transport); ++ if (transport->control_fd >= 0) ++ close (transport->control_fd); ++ free (transport); ++} ++ ++bool ++meta_anland_transport_get_screen_info (MetaAnlandTransport *transport, ++ struct screen_info *screen_info) ++{ ++ if (!transport || !screen_info) ++ return false; ++ ++ *screen_info = transport->screen_info; ++ return true; ++} ++ ++MetaAnlandTransportPickupResult ++meta_anland_transport_try_pickup (MetaAnlandTransport *transport) ++{ ++ MetaAnlandTransportPickupResult result; ++ ++ if (!transport) ++ return META_ANLAND_TRANSPORT_PICKUP_PROTOCOL_ERROR; ++ if (!transport->fallback) ++ return META_ANLAND_TRANSPORT_PICKUP_READY; ++ ++ result = pickup_fds (transport); ++ if (result != META_ANLAND_TRANSPORT_PICKUP_READY) ++ { ++ release_consumer_resources (transport); ++ return result; ++ } ++ ++ result = receive_buffers (transport); ++ if (result != META_ANLAND_TRANSPORT_PICKUP_READY) ++ { ++ release_consumer_resources (transport); ++ return result; ++ } ++ ++ transport->fallback = false; ++ return META_ANLAND_TRANSPORT_PICKUP_READY; ++} ++ ++bool ++meta_anland_transport_is_fallback (MetaAnlandTransport *transport) ++{ ++ return !transport || transport->fallback; ++} ++ ++void ++meta_anland_transport_set_fallback_callback (MetaAnlandTransport *transport, ++ void (*callback) (void *user_data), ++ void *user_data) ++{ ++ transport->fallback_callback = callback; ++ transport->fallback_user_data = user_data; ++} ++ ++int ++meta_anland_transport_get_buffer_ready_fd (MetaAnlandTransport *transport) ++{ ++ return transport && !transport->fallback ? transport->buffer_ready_fd : -1; ++} ++ ++int ++meta_anland_transport_get_data_fd (MetaAnlandTransport *transport) ++{ ++ return transport && !transport->fallback ? transport->data_fd : -1; ++} ++ ++int ++meta_anland_transport_get_audio_fd (MetaAnlandTransport *transport) ++{ ++ return transport && !transport->fallback ? transport->audio_fd : -1; ++} ++ ++int ++meta_anland_transport_get_buffer_count (MetaAnlandTransport *transport) ++{ ++ return transport && !transport->fallback ? transport->n_buffers : 0; ++} ++ ++int ++meta_anland_transport_get_selected_buffer (MetaAnlandTransport *transport) ++{ ++ uint32_t selected; ++ ++ if (!transport || transport->fallback || !transport->selected_buffer) ++ return -1; ++ ++ selected = *transport->selected_buffer; ++ return selected < (uint32_t) transport->n_buffers ? (int) selected : -1; ++} ++ ++int ++meta_anland_transport_get_buffer_fd (MetaAnlandTransport *transport, ++ int index) ++{ ++ if (!transport || transport->fallback || index < 0 || ++ index >= transport->n_buffers) ++ return -1; ++ ++ return transport->buffer_fds[index]; ++} ++ ++bool ++meta_anland_transport_get_buffer_info (MetaAnlandTransport *transport, ++ int index, ++ struct buf_info *buffer_info) ++{ ++ if (!transport || !buffer_info || transport->fallback || index < 0 || ++ index >= transport->n_buffers) ++ return false; ++ ++ *buffer_info = transport->buffer_infos[index]; ++ return true; ++} ++ ++void ++meta_anland_transport_set_render_fence (MetaAnlandTransport *transport, ++ int fence_fd) ++{ ++ if (!transport) ++ { ++ if (fence_fd >= 0) ++ close (fence_fd); ++ return; ++ } ++ ++ if (transport->pending_render_fence_fd >= 0) ++ close (transport->pending_render_fence_fd); ++ transport->pending_render_fence_fd = fence_fd; ++} ++ ++bool ++meta_anland_transport_notify_frame_done (MetaAnlandTransport *transport) ++{ ++ struct iovec iov; ++ struct msghdr message = { 0 }; ++ char byte = 0; ++ union ++ { ++ char bytes[CMSG_SPACE (sizeof (int))]; ++ struct cmsghdr align; ++ } control = { 0 }; ++ ssize_t n; ++ ++ if (!transport) ++ return false; ++ if (transport->fallback) ++ { ++ if (transport->pending_render_fence_fd >= 0) ++ close (transport->pending_render_fence_fd); ++ transport->pending_render_fence_fd = -1; ++ return false; ++ } ++ ++ iov.iov_base = &byte; ++ iov.iov_len = sizeof (byte); ++ message.msg_iov = &iov; ++ message.msg_iovlen = 1; ++ if (transport->pending_render_fence_fd >= 0) ++ { ++ struct cmsghdr *cmsg; ++ ++ message.msg_control = control.bytes; ++ message.msg_controllen = sizeof (control.bytes); ++ cmsg = CMSG_FIRSTHDR (&message); ++ cmsg->cmsg_level = SOL_SOCKET; ++ cmsg->cmsg_type = SCM_RIGHTS; ++ cmsg->cmsg_len = CMSG_LEN (sizeof (int)); ++ memcpy (CMSG_DATA (cmsg), &transport->pending_render_fence_fd, ++ sizeof (transport->pending_render_fence_fd)); ++ } ++ ++ do ++ n = sendmsg (transport->fence_fd, &message, MSG_NOSIGNAL | MSG_DONTWAIT); ++ while (n < 0 && errno == EINTR); ++ ++ if (transport->pending_render_fence_fd >= 0) ++ { ++ close (transport->pending_render_fence_fd); ++ transport->pending_render_fence_fd = -1; ++ } ++ ++ if (n == 1) ++ return true; ++ ++ enter_fallback (transport); ++ return false; ++} ++ ++int ++meta_anland_transport_poll_input_event (MetaAnlandTransport *transport, ++ struct InputEvent *event, ++ int timeout_ms) ++{ ++ struct data_msg header; ++ ++ if (!transport || !event || transport->fallback) ++ return 0; ++ ++ for (;;) ++ { ++ int wait_result = wait_for_fd (transport->data_fd, POLLIN, timeout_ms); ++ ++ if (wait_result == 0) ++ return 0; ++ if (wait_result < 0 || ++ recv_all_timeout (transport->data_fd, &header, sizeof (header), ++ INPUT_TIMEOUT_MS) < 0) ++ { ++ enter_fallback (transport); ++ return -1; ++ } ++ ++ if (header.type != DATA_MSG_INPUT_EVENT) ++ { ++ if (discard_data_payload (transport, header.size) < 0) ++ { ++ enter_fallback (transport); ++ return -1; ++ } ++ timeout_ms = 0; ++ continue; ++ } ++ ++ if (header.size != sizeof (*event) || ++ recv_all_timeout (transport->data_fd, event, sizeof (*event), ++ INPUT_TIMEOUT_MS) < 0) ++ { ++ enter_fallback (transport); ++ return -1; ++ } ++ ++ return 1; ++ } ++} ++ ++int ++meta_anland_transport_read_input_payload (MetaAnlandTransport *transport, ++ void *payload, ++ size_t size, ++ int timeout_ms) ++{ ++ if (!transport || transport->fallback) ++ return 0; ++ if (size == 0) ++ return 1; ++ if (size > META_ANLAND_MAX_PAYLOAD_SIZE || !payload || ++ recv_all_timeout (transport->data_fd, payload, size, ++ timeout_ms) < 0) ++ { ++ enter_fallback (transport); ++ return -1; ++ } ++ ++ return 1; ++} ++ ++int ++meta_anland_transport_read_input_fds (MetaAnlandTransport *transport, ++ int *fds, ++ int max_fds, ++ int *n_fds, ++ int timeout_ms) ++{ ++ struct data_msg header; ++ ++ if (!n_fds) ++ return -1; ++ *n_fds = 0; ++ if (!transport || transport->fallback) ++ return 0; ++ if (wait_for_fd (transport->data_fd, POLLIN, timeout_ms) != 1 || ++ recv_fds_exact (transport->data_fd, &header, sizeof (header), fds, ++ max_fds, n_fds) < 0 || ++ header.type != DATA_MSG_INPUT_EXTEND_FDS || header.size != 0 || ++ *n_fds < 1) ++ { ++ close_fds (fds, *n_fds); ++ *n_fds = 0; ++ enter_fallback (transport); ++ return -1; ++ } ++ ++ return 1; ++} ++ ++bool ++meta_anland_transport_send_output_event (MetaAnlandTransport *transport, ++ const struct OutputEvent *event, ++ const void *payload, ++ size_t payload_size) ++{ ++ struct data_msg header = { .type = DATA_MSG_OUTPUT_EVENT, ++ .size = sizeof (*event) }; ++ size_t size; ++ uint8_t *message; ++ ++ if (!transport || !event || transport->fallback || ++ (payload_size > 0 && !payload) || ++ payload_size > META_ANLAND_MAX_PAYLOAD_SIZE || ++ payload_size > SIZE_MAX - sizeof (header) - sizeof (*event)) ++ return false; ++ ++ size = sizeof (header) + sizeof (*event) + payload_size; ++ message = malloc (size); ++ if (!message) ++ return false; ++ ++ memcpy (message, &header, sizeof (header)); ++ memcpy (message + sizeof (header), event, sizeof (*event)); ++ if (payload_size > 0) ++ memcpy (message + sizeof (header) + sizeof (*event), payload, payload_size); ++ ++ if (send_all_timeout (transport->data_fd, message, size, ++ INPUT_TIMEOUT_MS) < 0) ++ { ++ free (message); ++ enter_fallback (transport); ++ return false; ++ } ++ ++ free (message); ++ return true; ++} +diff --git a/src/backends/anland/vendor/meta-anland-transport.h b/src/backends/anland/vendor/meta-anland-transport.h +new file mode 100644 +index 0000000000..79802a8200 +--- /dev/null ++++ b/src/backends/anland/vendor/meta-anland-transport.h +@@ -0,0 +1,70 @@ ++#ifndef META_ANLAND_TRANSPORT_H ++#define META_ANLAND_TRANSPORT_H ++ ++#include ++#include ++#include ++ ++#include "meta-anland-protocol.h" ++ ++typedef struct _MetaAnlandTransport MetaAnlandTransport; ++ ++#define META_ANLAND_MAX_PAYLOAD_SIZE (1024 * 1024) ++ ++typedef enum ++{ ++ META_ANLAND_TRANSPORT_PICKUP_READY, ++ META_ANLAND_TRANSPORT_PICKUP_NO_CONSUMER, ++ META_ANLAND_TRANSPORT_PICKUP_DAEMON_LOST = -1, ++ META_ANLAND_TRANSPORT_PICKUP_PROTOCOL_ERROR = -2, ++} MetaAnlandTransportPickupResult; ++ ++int meta_anland_transport_connect (MetaAnlandTransport **transport, ++ const char *socket_path); ++void meta_anland_transport_disconnect (MetaAnlandTransport *transport); ++ ++bool meta_anland_transport_get_screen_info (MetaAnlandTransport *transport, ++ struct screen_info *screen_info); ++ ++MetaAnlandTransportPickupResult ++meta_anland_transport_try_pickup (MetaAnlandTransport *transport); ++ ++bool meta_anland_transport_is_fallback (MetaAnlandTransport *transport); ++void meta_anland_transport_set_fallback_callback (MetaAnlandTransport *transport, ++ void (*callback) (void *user_data), ++ void *user_data); ++ ++int meta_anland_transport_get_buffer_ready_fd (MetaAnlandTransport *transport); ++int meta_anland_transport_get_data_fd (MetaAnlandTransport *transport); ++int meta_anland_transport_get_audio_fd (MetaAnlandTransport *transport); ++int meta_anland_transport_get_buffer_count (MetaAnlandTransport *transport); ++int meta_anland_transport_get_selected_buffer (MetaAnlandTransport *transport); ++int meta_anland_transport_get_buffer_fd (MetaAnlandTransport *transport, ++ int index); ++bool meta_anland_transport_get_buffer_info (MetaAnlandTransport *transport, ++ int index, ++ struct buf_info *buffer_info); ++ ++void meta_anland_transport_set_render_fence (MetaAnlandTransport *transport, ++ int fence_fd); ++bool meta_anland_transport_notify_frame_done (MetaAnlandTransport *transport); ++ ++int meta_anland_transport_poll_input_event (MetaAnlandTransport *transport, ++ struct InputEvent *event, ++ int timeout_ms); ++int meta_anland_transport_read_input_payload (MetaAnlandTransport *transport, ++ void *payload, ++ size_t size, ++ int timeout_ms); ++int meta_anland_transport_read_input_fds (MetaAnlandTransport *transport, ++ int *fds, ++ int max_fds, ++ int *n_fds, ++ int timeout_ms); ++ ++bool meta_anland_transport_send_output_event (MetaAnlandTransport *transport, ++ const struct OutputEvent *event, ++ const void *payload, ++ size_t payload_size); ++ ++#endif /* META_ANLAND_TRANSPORT_H */ +diff --git a/src/tests/anland-transport-tests.c b/src/tests/anland-transport-tests.c +new file mode 100644 +index 0000000000..0e17cec208 +--- /dev/null ++++ b/src/tests/anland-transport-tests.c +@@ -0,0 +1,419 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "meta-anland-socket-utils.h" ++#include "meta-anland-transport.h" ++ ++_Static_assert (sizeof (struct ctrl_msg) == 8, ++ "Anland control header ABI changed"); ++_Static_assert (sizeof (struct data_msg) == 8, ++ "Anland data header ABI changed"); ++_Static_assert (sizeof (struct screen_info) == 16, ++ "Anland screen info ABI changed"); ++_Static_assert (sizeof (struct buf_info) == 28, ++ "Anland buffer metadata ABI changed"); ++_Static_assert (sizeof (struct InputEvent) == 20, ++ "Anland input event ABI changed"); ++_Static_assert (sizeof (struct OutputEvent) == 20, ++ "Anland output event ABI changed"); ++_Static_assert (sizeof (struct audio_format) == 20, ++ "Anland audio format ABI changed"); ++_Static_assert (sizeof (struct audio_msg) == 8, ++ "Anland audio header ABI changed"); ++ ++typedef struct ++{ ++ int ready; ++ int fence; ++ int data; ++ int audio; ++} MockPeers; ++ ++typedef struct ++{ ++ int listen_fd; ++ GMutex lock; ++ MockPeers peers; ++ gboolean delay_first_pickup; ++ gboolean failed; ++} MockDaemon; ++ ++static void ++close_fd (int *fd) ++{ ++ if (*fd >= 0) ++ { ++ close (*fd); ++ *fd = -1; ++ } ++} ++ ++static void ++close_peers (MockDaemon *daemon) ++{ ++ MockPeers peers; ++ ++ g_mutex_lock (&daemon->lock); ++ peers = daemon->peers; ++ daemon->peers = (MockPeers) { -1, -1, -1, -1 }; ++ g_mutex_unlock (&daemon->lock); ++ ++ close_fd (&peers.ready); ++ close_fd (&peers.fence); ++ close_fd (&peers.data); ++ close_fd (&peers.audio); ++} ++ ++static MockPeers ++get_peers (MockDaemon *daemon) ++{ ++ MockPeers peers; ++ ++ g_mutex_lock (&daemon->lock); ++ peers = daemon->peers; ++ g_mutex_unlock (&daemon->lock); ++ return peers; ++} ++ ++static int ++new_memfd (const char *name, ++ size_t size) ++{ ++ int fd = memfd_create (name, MFD_CLOEXEC); ++ ++ if (fd < 0 || ftruncate (fd, (off_t) size) < 0) ++ { ++ close_fd (&fd); ++ return -1; ++ } ++ ++ return fd; ++} ++ ++static gboolean ++send_screen_info (int control_fd) ++{ ++ struct ctrl_msg header = { ++ .type = CTRL_MSG_SCREEN_INFO, ++ .size = sizeof (struct screen_info), ++ }; ++ struct screen_info info = { ++ .width = 1920, ++ .height = 1080, ++ .format = 1, ++ .refresh = 120000, ++ }; ++ ++ return meta_anland_send_all (control_fd, &header, sizeof (header)) == 0 && ++ meta_anland_send_all (control_fd, &info, sizeof (info)) == 0; ++} ++ ++static gboolean ++send_buffers (int data_fd) ++{ ++ struct data_msg header = { ++ .type = DATA_MSG_BUFS_READY, ++ .size = 2 * sizeof (struct buf_info), ++ }; ++ struct buf_info infos[2] = { ++ { .stride = 5120, .width = 1280, .height = 720, ++ .format = 1, .modifier = 0, .offset = 0 }, ++ { .stride = 5120, .width = 1280, .height = 720, ++ .format = 1, .modifier = 0, .offset = 0 }, ++ }; ++ int fds[2] = { ++ new_memfd ("mutter-anland-buffer-0", 4096), ++ new_memfd ("mutter-anland-buffer-1", 4096), ++ }; ++ gboolean result; ++ ++ result = fds[0] >= 0 && fds[1] >= 0 && ++ meta_anland_send_fds (data_fd, &header, sizeof (header), fds, 2) == 0 && ++ meta_anland_send_all (data_fd, infos, sizeof (infos)) == 0; ++ close_fd (&fds[0]); ++ close_fd (&fds[1]); ++ return result; ++} ++ ++static gboolean ++publish_consumer (MockDaemon *daemon, ++ int control_fd) ++{ ++ struct ctrl_msg response = { .type = CTRL_MSG_FDS_READY, .size = 0 }; ++ int ready = -1; ++ int fence[2] = { -1, -1 }; ++ int data[2] = { -1, -1 }; ++ int audio[2] = { -1, -1 }; ++ int shm_fd = -1; ++ int producer_fds[5]; ++ uint32_t selected = 1; ++ gboolean result = FALSE; ++ ++ ready = eventfd (0, EFD_CLOEXEC); ++ if (ready < 0 || ++ socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fence) < 0 || ++ socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, data) < 0 || ++ socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, audio) < 0) ++ goto out; ++ shm_fd = new_memfd ("mutter-anland-index", sizeof (selected)); ++ if (shm_fd < 0 || pwrite (shm_fd, &selected, sizeof (selected), 0) != ++ (ssize_t) sizeof (selected)) ++ goto out; ++ ++ producer_fds[0] = ready; ++ producer_fds[1] = fence[0]; ++ producer_fds[2] = data[0]; ++ producer_fds[3] = shm_fd; ++ producer_fds[4] = audio[0]; ++ if (meta_anland_send_fds (control_fd, &response, sizeof (response), ++ producer_fds, G_N_ELEMENTS (producer_fds)) < 0) ++ goto out; ++ ++ g_mutex_lock (&daemon->lock); ++ daemon->peers = (MockPeers) { ++ .ready = ready, .fence = fence[1], .data = data[1], .audio = audio[1], ++ }; ++ g_mutex_unlock (&daemon->lock); ++ ready = fence[1] = data[1] = audio[1] = -1; ++ if (!send_buffers (get_peers (daemon).data)) ++ goto out; ++ result = TRUE; ++ ++out: ++ close_fd (&ready); ++ close_fd (&fence[0]); ++ close_fd (&fence[1]); ++ close_fd (&data[0]); ++ close_fd (&data[1]); ++ close_fd (&audio[0]); ++ close_fd (&audio[1]); ++ close_fd (&shm_fd); ++ if (!result) ++ close_peers (daemon); ++ return result; ++} ++ ++static gpointer ++mock_daemon_thread (gpointer user_data) ++{ ++ MockDaemon *daemon = user_data; ++ struct ctrl_msg request; ++ int control_fd = -1; ++ ++ control_fd = accept4 (daemon->listen_fd, NULL, NULL, SOCK_CLOEXEC); ++ if (control_fd < 0 || ++ meta_anland_recv_all (control_fd, &request, sizeof (request)) < 0 || ++ request.type != CTRL_MSG_PRODUCER_HELLO || request.size != 0 || ++ !send_screen_info (control_fd)) ++ goto failed; ++ ++ while (meta_anland_recv_all (control_fd, &request, sizeof (request)) == 0) ++ { ++ if (request.type != CTRL_MSG_PICKUP_FDS || request.size != 0) ++ goto failed; ++ if (daemon->delay_first_pickup) ++ { ++ daemon->delay_first_pickup = FALSE; ++ continue; ++ } ++ if (!publish_consumer (daemon, control_fd)) ++ goto failed; ++ } ++ ++ close_fd (&control_fd); ++ return NULL; ++ ++failed: ++ daemon->failed = TRUE; ++ close_fd (&control_fd); ++ return NULL; ++} ++ ++static int ++create_listener (const char *path) ++{ ++ struct sockaddr_un address = { 0 }; ++ int fd; ++ ++ fd = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); ++ if (fd < 0) ++ return -1; ++ address.sun_family = AF_UNIX; ++ if (strlen (path) >= sizeof (address.sun_path)) ++ goto failed; ++ strcpy (address.sun_path, path); ++ if (bind (fd, (struct sockaddr *) &address, sizeof (address)) < 0 || ++ listen (fd, 1) < 0) ++ goto failed; ++ ++ return fd; ++ ++failed: ++ close (fd); ++ return -1; ++} ++ ++static void ++send_data_message (int fd, ++ uint32_t type, ++ const void *payload, ++ size_t size) ++{ ++ struct data_msg header = { .type = type, .size = size }; ++ ++ g_assert_cmpint (meta_anland_send_all (fd, &header, sizeof (header)), ==, 0); ++ if (size > 0) ++ g_assert_cmpint (meta_anland_send_all (fd, payload, size), ==, 0); ++} ++ ++static void ++fallback_callback (void *user_data) ++{ ++ int *count = user_data; ++ ++ (*count)++; ++} ++ ++static void ++test_transport_and_reconnect (void) ++{ ++ g_autoptr (GError) error = NULL; ++ g_autofree char *directory = g_dir_make_tmp ("mutter-anland-XXXXXX", &error); ++ g_autofree char *socket_path = NULL; ++ MockDaemon daemon = { ++ .listen_fd = -1, ++ .peers = { -1, -1, -1, -1 }, ++ .delay_first_pickup = TRUE, ++ }; ++ MetaAnlandTransport *transport = NULL; ++ struct screen_info screen_info; ++ struct InputEvent event = { 0 }; ++ struct InputEvent received_event; ++ struct OutputEvent output = { 0 }; ++ struct data_msg output_header; ++ struct OutputEvent received_output; ++ MockPeers peers; ++ GThread *thread; ++ char text[] = "Anland text"; ++ char audio_byte = 'A'; ++ char received_audio = 0; ++ char received_text[sizeof (text)] = { 0 }; ++ char fence_byte = 1; ++ int fence_fds[1] = { -1 }; ++ int n_fence_fds = 0; ++ int pipe_fds[2] = { -1, -1 }; ++ int fallback_count = 0; ++ eventfd_t ready_value = 5; ++ eventfd_t received_ready = 0; ++ ++ g_assert_no_error (error); ++ socket_path = g_build_filename (directory, "display.sock", NULL); ++ daemon.listen_fd = create_listener (socket_path); ++ if (daemon.listen_fd < 0) ++ g_error ("failed to create mock Anland socket %s: %s", ++ socket_path, g_strerror (errno)); ++ g_assert_cmpint (daemon.listen_fd, >=, 0); ++ g_mutex_init (&daemon.lock); ++ thread = g_thread_new ("anland-mock-daemon", mock_daemon_thread, &daemon); ++ ++ g_assert_cmpint (meta_anland_transport_connect (&transport, socket_path), ==, 0); ++ g_assert_true (meta_anland_transport_get_screen_info (transport, &screen_info)); ++ g_assert_cmpuint (screen_info.refresh, ==, 120000); ++ g_assert_cmpint (meta_anland_transport_try_pickup (transport), ==, ++ META_ANLAND_TRANSPORT_PICKUP_NO_CONSUMER); ++ g_assert_cmpint (meta_anland_transport_try_pickup (transport), ==, ++ META_ANLAND_TRANSPORT_PICKUP_READY); ++ g_assert_false (meta_anland_transport_is_fallback (transport)); ++ g_assert_cmpint (meta_anland_transport_get_buffer_count (transport), ==, 2); ++ g_assert_cmpint (meta_anland_transport_get_selected_buffer (transport), ==, 1); ++ g_assert_cmpint (meta_anland_transport_get_buffer_fd (transport, 0), >=, 0); ++ ++ peers = get_peers (&daemon); ++ g_assert_cmpint (eventfd_write (peers.ready, ready_value), ==, 0); ++ g_assert_cmpint (eventfd_read (meta_anland_transport_get_buffer_ready_fd (transport), ++ &received_ready), ==, 0); ++ g_assert_cmpuint (received_ready, ==, ready_value); ++ g_assert_cmpint (send (peers.audio, &audio_byte, sizeof (audio_byte), 0), ==, 1); ++ g_assert_cmpint (recv (meta_anland_transport_get_audio_fd (transport), ++ &received_audio, sizeof (received_audio), 0), ==, 1); ++ g_assert_cmpint (received_audio, ==, audio_byte); ++ ++ send_data_message (peers.data, 0xfeed, text, sizeof (text)); ++ event.type = INPUT_TYPE_TEXT_INPUT; ++ event.text_input.size = sizeof (text); ++ send_data_message (peers.data, DATA_MSG_INPUT_EVENT, &event, sizeof (event)); ++ g_assert_cmpint (meta_anland_send_all (peers.data, text, sizeof (text)), ==, 0); ++ g_assert_cmpint (meta_anland_transport_poll_input_event (transport, &received_event, 100), ==, 1); ++ g_assert_cmpuint (received_event.type, ==, INPUT_TYPE_TEXT_INPUT); ++ g_assert_cmpint (meta_anland_transport_read_input_payload (transport, received_text, ++ sizeof (received_text), 100), ==, 1); ++ g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); ++ ++ output.type = OUTPUT_TYPE_CLIPBOARD; ++ output.clipboard.size = sizeof (text); ++ g_assert_true (meta_anland_transport_send_output_event (transport, &output, ++ text, sizeof (text))); ++ g_assert_cmpint (meta_anland_recv_all (peers.data, &output_header, ++ sizeof (output_header)), ==, 0); ++ g_assert_cmpuint (output_header.type, ==, DATA_MSG_OUTPUT_EVENT); ++ g_assert_cmpint (meta_anland_recv_all (peers.data, &received_output, ++ sizeof (received_output)), ==, 0); ++ g_assert_cmpuint (output_header.size, ==, sizeof (received_output)); ++ g_assert_cmpuint (received_output.type, ==, OUTPUT_TYPE_CLIPBOARD); ++ g_assert_cmpint (meta_anland_recv_all (peers.data, received_text, ++ sizeof (received_text)), ==, 0); ++ g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); ++ ++ g_assert_cmpint (pipe2 (pipe_fds, O_CLOEXEC), ==, 0); ++ meta_anland_transport_set_render_fence (transport, pipe_fds[0]); ++ pipe_fds[0] = -1; ++ g_assert_true (meta_anland_transport_notify_frame_done (transport)); ++ g_assert_cmpint (meta_anland_recv_fds (peers.fence, &fence_byte, ++ sizeof (fence_byte), fence_fds, 1, ++ &n_fence_fds), ==, 1); ++ g_assert_cmpint (fence_byte, ==, 0); ++ g_assert_cmpint (n_fence_fds, ==, 1); ++ close_fd (&fence_fds[0]); ++ close_fd (&pipe_fds[1]); ++ ++ meta_anland_transport_set_fallback_callback (transport, fallback_callback, ++ &fallback_count); ++ g_mutex_lock (&daemon.lock); ++ close_fd (&daemon.peers.data); ++ g_mutex_unlock (&daemon.lock); ++ g_assert_cmpint (meta_anland_transport_poll_input_event (transport, &event, 100), ==, -1); ++ g_assert_true (meta_anland_transport_is_fallback (transport)); ++ g_assert_cmpint (fallback_count, ==, 1); ++ close_peers (&daemon); ++ ++ g_assert_cmpint (meta_anland_transport_try_pickup (transport), ==, ++ META_ANLAND_TRANSPORT_PICKUP_READY); ++ g_assert_false (meta_anland_transport_is_fallback (transport)); ++ meta_anland_transport_disconnect (transport); ++ g_thread_join (thread); ++ g_assert_false (daemon.failed); ++ close_fd (&daemon.listen_fd); ++ close_peers (&daemon); ++ g_assert_cmpint (g_unlink (socket_path), ==, 0); ++ g_assert_cmpint (g_rmdir (directory), ==, 0); ++ g_mutex_clear (&daemon.lock); ++} ++ ++int ++main (int argc, ++ char *argv[]) ++{ ++ g_test_init (&argc, &argv, NULL); ++ g_test_add_func ("/backends/anland/transport-and-reconnect", ++ test_transport_and_reconnect); ++ return g_test_run (); ++} +diff --git a/src/tests/meson.build b/src/tests/meson.build +index 8c3773033f..20ce0cb6bc 100644 +--- a/src/tests/meson.build ++++ b/src/tests/meson.build +@@ -142,6 +142,24 @@ gnome.compile_schemas( + depend_files: 'org.gnome.mutter.test.gschema.xml', + ) + ++anland_transport_test = executable('mutter-anland-transport-tests', ++ sources: [ ++ 'anland-transport-tests.c', ++ '../backends/anland/vendor/meta-anland-socket-utils.c', ++ '../backends/anland/vendor/meta-anland-transport.c', ++ ], ++ include_directories: [ ++ tests_includes, ++ include_directories('../backends/anland/vendor'), ++ ], ++ dependencies: glib_dep, ++ install: false, ++) ++ ++test('anland-transport', anland_transport_test, ++ suite: ['core', 'mutter/anland'], ++) ++ + if have_cogl_tests + subdir('cogl') + endif diff --git a/x11-packages/mutter/0011-backends-anland-Add-headless-native-backend-selectio.patch b/x11-packages/mutter/0011-backends-anland-Add-headless-native-backend-selectio.patch new file mode 100644 index 00000000000..71fe38ba8bd --- /dev/null +++ b/x11-packages/mutter/0011-backends-anland-Add-headless-native-backend-selectio.patch @@ -0,0 +1,251 @@ +From 4bb2726d944dd592dd3fc365f66a8d7b6a0aa0df Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 08:57:33 +0800 +Subject: [PATCH 11/27] backends/anland: Add headless native backend selection + +Add an opt-in Meson feature and enable it from Debian's patch queue build. The feature requires Mutter's native and Wayland backends. + +Add --anland as a mutually exclusive Wayland compositor mode. It constructs MetaBackendAnland with the existing native headless mode, preserving the no-launcher and no-libinput behavior while later commits attach the Anland transport. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0002-backends-anland-Add-headless-native-backend-selection.patch + +diff --git a/config.h.meson b/config.h.meson +index fc1e5185ba..f7b0e36cd4 100644 +--- a/config.h.meson ++++ b/config.h.meson +@@ -57,6 +57,9 @@ + /* Define if you want to enable the native (KMS) backend based on systemd */ + #mesondefine HAVE_NATIVE_BACKEND + ++/* Define if the Anland display backend is enabled */ ++#mesondefine HAVE_ANLAND ++ + /* Define if you want to enable Wayland support */ + #mesondefine HAVE_WAYLAND + +diff --git a/meson.build b/meson.build +index 469ab7463e..e52fc3b863 100644 +--- a/meson.build ++++ b/meson.build +@@ -306,6 +306,16 @@ if have_native_backend + endif + endif + ++have_anland = get_option('anland').enabled() ++if have_anland ++ if not have_native_backend ++ error('The Anland backend requires the native backend') ++ endif ++ if not have_wayland ++ error('The Anland backend requires Wayland support') ++ endif ++endif ++ + if have_wayland or have_native_backend + libdrm_dep = dependency('libdrm', version: libdrm_req) + have_drm_plane_size_hint = cc.has_type('struct drm_plane_size_hint', +@@ -606,6 +616,7 @@ cdata.set('HAVE_X11', have_x11) + cdata.set('HAVE_X11_CLIENT', have_x11_client) + cdata.set('HAVE_LOGIND', have_logind) + cdata.set('HAVE_NATIVE_BACKEND', have_native_backend) ++cdata.set('HAVE_ANLAND', have_anland) + cdata.set('HAVE_REMOTE_DESKTOP', have_remote_desktop) + cdata.set('HAVE_DEVKIT', have_devkit) + cdata.set('HAVE_GNOME_DESKTOP', have_gnome_desktop) +diff --git a/meson.options b/meson.options +index 581aac2f0a..2ca02ab8ac 100644 +--- a/meson.options ++++ b/meson.options +@@ -63,6 +63,12 @@ option('native_backend', + description: 'Enable the native backend' + ) + ++option('anland', ++ type: 'feature', ++ value: 'disabled', ++ description: 'Enable the Anland display backend' ++) ++ + option('remote_desktop', + type: 'boolean', + value: true, +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +new file mode 100644 +index 0000000000..ed710d3304 +--- /dev/null ++++ b/src/backends/anland/meta-backend-anland.c +@@ -0,0 +1,22 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#include "config.h" ++ ++#include "backends/anland/meta-backend-anland.h" ++ ++struct _MetaBackendAnland ++{ ++ MetaBackendNative parent; ++}; ++ ++G_DEFINE_TYPE (MetaBackendAnland, meta_backend_anland, META_TYPE_BACKEND_NATIVE) ++ ++static void ++meta_backend_anland_class_init (MetaBackendAnlandClass *klass) ++{ ++} ++ ++static void ++meta_backend_anland_init (MetaBackendAnland *backend_anland) ++{ ++} +diff --git a/src/backends/anland/meta-backend-anland.h b/src/backends/anland/meta-backend-anland.h +new file mode 100644 +index 0000000000..746e550941 +--- /dev/null ++++ b/src/backends/anland/meta-backend-anland.h +@@ -0,0 +1,11 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#pragma once ++ ++#include "backends/native/meta-backend-native-private.h" ++ ++#define META_TYPE_BACKEND_ANLAND (meta_backend_anland_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaBackendAnland, ++ meta_backend_anland, ++ META, BACKEND_ANLAND, ++ MetaBackendNative) +diff --git a/src/core/meta-context-main.c b/src/core/meta-context-main.c +index 5656c7e333..4acb9bce97 100644 +--- a/src/core/meta-context-main.c ++++ b/src/core/meta-context-main.c +@@ -47,6 +47,9 @@ + #ifdef HAVE_DEVKIT + #include "core/meta-mdk.h" + #endif ++#ifdef HAVE_ANLAND ++#include "backends/anland/meta-backend-anland.h" ++#endif + + #if defined (HAVE_X11) && defined (HAVE_WAYLAND) + #include "backends/x11/nested/meta-backend-x11-nested.h" +@@ -75,6 +78,9 @@ typedef struct _MetaContextMainOptions + gboolean headless; + gboolean devkit; + GList *virtual_monitor_infos; ++#endif ++#ifdef HAVE_ANLAND ++ gboolean anland; + #endif + char *trace_file; + gboolean debug_control; +@@ -152,6 +158,36 @@ check_configuration (MetaContextMain *context_main, + } + #endif /* HAVE_NATIVE_BACKEND */ + ++#ifdef HAVE_ANLAND ++ if (context_main->options.anland && ++ (context_main->options.display_server || ++ context_main->options.headless ++#ifdef HAVE_DEVKIT ++ || context_main->options.devkit ++#endif ++ )) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "Can't run Anland with display server or headless mode"); ++ return FALSE; ++ } ++#ifdef HAVE_X11 ++ if (context_main->options.anland && context_main->options.x11.force) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "Can't run Anland with the X11 backend"); ++ return FALSE; ++ } ++#endif /* HAVE_X11 */ ++#ifdef HAVE_WAYLAND ++ if (context_main->options.anland && context_main->options.nested) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "Can't run Anland as a nested compositor"); ++ return FALSE; ++ } ++#endif /* HAVE_WAYLAND */ ++#endif /* HAVE_ANLAND */ + return TRUE; + } + +@@ -251,6 +287,9 @@ determine_compositor_type (MetaContextMain *context_main, + context_main->options.headless || + context_main->options.devkit || + #endif /* HAVE_NATIVE_BACKEND */ ++#ifdef HAVE_ANLAND ++ context_main->options.anland || ++#endif /* HAVE_ANLAND */ + context_main->options.nested) + return META_COMPOSITOR_TYPE_WAYLAND; + #endif /* HAVE_WAYLAND */ +@@ -498,6 +537,19 @@ create_headless_backend (MetaContext *context, + NULL); + } + ++#ifdef HAVE_ANLAND ++static MetaBackend * ++create_anland_backend (MetaContext *context, ++ GError **error) ++{ ++ return g_initable_new (META_TYPE_BACKEND_ANLAND, ++ NULL, error, ++ "context", context, ++ "mode", META_BACKEND_NATIVE_MODE_HEADLESS, ++ NULL); ++} ++#endif ++ + static MetaBackend * + create_native_backend (MetaContext *context, + GError **error) +@@ -533,6 +585,10 @@ meta_context_main_create_backend (MetaContext *context, + return create_nested_backend (context, error); + #endif + #ifdef HAVE_NATIVE_BACKEND ++#ifdef HAVE_ANLAND ++ if (context_main->options.anland) ++ return create_anland_backend (context, error); ++#endif + if (context_main->options.headless || + context_main->options.devkit) + return create_headless_backend (context, error); +@@ -684,6 +740,13 @@ meta_context_main_add_option_entries (MetaContextMain *context_main) + &context_main->options.headless, + N_("Run as a headless display server") + }, ++#ifdef HAVE_ANLAND ++ { ++ "anland", 0, 0, G_OPTION_ARG_NONE, ++ &context_main->options.anland, ++ N_("Run with the Anland display backend") ++ }, ++#endif + { + "virtual-monitor", 0, 0, G_OPTION_ARG_CALLBACK, + add_virtual_monitor_cb, +diff --git a/src/meson.build b/src/meson.build +index c36efc3a1e..65d6314b72 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -923,6 +923,13 @@ if have_devkit + ] + endif + ++if have_anland ++ mutter_sources += [ ++ 'backends/anland/meta-backend-anland.c', ++ 'backends/anland/meta-backend-anland.h', ++ ] ++endif ++ + if have_wayland or have_native_backend + mutter_sources += [ + 'common/meta-cogl-drm-formats.c', diff --git a/x11-packages/mutter/0012-backends-anland-Add-virtual-output-and-reconnect-sta.patch b/x11-packages/mutter/0012-backends-anland-Add-virtual-output-and-reconnect-sta.patch new file mode 100644 index 00000000000..f397aa690ff --- /dev/null +++ b/x11-packages/mutter/0012-backends-anland-Add-virtual-output-and-reconnect-sta.patch @@ -0,0 +1,483 @@ +From 1b68d61ed9339372a8e83bb585ded63943d8d04d Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 09:14:08 +0800 +Subject: [PATCH 12/27] backends/anland: Add virtual output and reconnect state + +Create the persistent Anland-1 virtual monitor from valid consumer screen +information and retain it through fallback. Reconnect consumer-only resources +with a bounded source while retaining a placeholder framebuffer. + +Use the consumer buffer dimensions as authoritative when applying a mode +change, including a surface recreation or runtime refresh transition. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0003-backends-anland-Add-virtual-output-and-reconnect-state.patch + +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index ed710d3304..1f7747ace5 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -4,19 +4,295 @@ + + #include "backends/anland/meta-backend-anland.h" + ++#include "backends/anland/vendor/meta-anland-transport.h" ++#include "backends/meta-monitor-manager-private.h" ++#include "backends/meta-virtual-monitor.h" ++#include "meta/meta-backend.h" ++ ++#define ANLAND_RECONNECT_INTERVAL_MS 200 ++ ++enum ++{ ++ PROP_0, ++ ++ PROP_ANLAND_SOCKET, ++}; ++ + struct _MetaBackendAnland + { + MetaBackendNative parent; ++ ++ char *socket_path; ++ MetaAnlandTransport *transport; ++ MetaVirtualMonitor *virtual_monitor; ++ struct screen_info screen_info; ++ guint reconnect_source_id; + }; + + G_DEFINE_TYPE (MetaBackendAnland, meta_backend_anland, META_TYPE_BACKEND_NATIVE) + ++static char * ++get_default_socket_path (void) ++{ ++ const char *socket_path = g_getenv ("ANLAND_SOCKET"); ++ const char *tmpdir; ++ const char *prefix; ++ ++ if (socket_path && *socket_path) ++ return g_strdup (socket_path); ++ ++ tmpdir = g_getenv ("TMPDIR"); ++ if (tmpdir && *tmpdir) ++ return g_build_filename (tmpdir, "anland", "display_daemon.sock", NULL); ++ ++ prefix = g_getenv ("PREFIX"); ++ if (prefix && g_str_has_prefix (prefix, "/data/data/com.termux/")) ++ return g_strdup ("/data/data/com.termux/files/usr/tmp/anland/display_daemon.sock"); ++ ++ return g_strdup ("/tmp/anland/display_daemon.sock"); ++} ++ ++static gboolean ++validate_screen_info (const struct screen_info *screen_info, ++ GError **error) ++{ ++ if (screen_info->width == 0 || screen_info->height == 0 || ++ screen_info->width > G_MAXINT || screen_info->height > G_MAXINT || ++ (screen_info->refresh != 0 && screen_info->refresh < 1000) || ++ screen_info->refresh > 1000000) ++ { ++ if (error) ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, ++ "Anland daemon provided invalid screen information"); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static void ++clear_transport (MetaBackendAnland *backend) ++{ ++ if (backend->transport) ++ meta_anland_transport_disconnect (backend->transport); ++ backend->transport = NULL; ++} ++ ++static gboolean ++connect_transport (MetaBackendAnland *backend, ++ GError **error) ++{ ++ struct screen_info screen_info; ++ ++ if (backend->transport) ++ return TRUE; ++ ++ if (meta_anland_transport_connect (&backend->transport, ++ backend->socket_path) < 0) ++ { ++ if (error) ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, ++ "Failed to connect to Anland daemon at %s", ++ backend->socket_path); ++ return FALSE; ++ } ++ ++ if (!meta_anland_transport_get_screen_info (backend->transport, &screen_info) || ++ !validate_screen_info (&screen_info, error)) ++ { ++ clear_transport (backend); ++ return FALSE; ++ } ++ ++ backend->screen_info = screen_info; ++ return TRUE; ++} ++ ++static gboolean ++update_virtual_monitor (MetaBackendAnland *backend, ++ GError **error) ++{ ++ MetaMonitorManager *monitor_manager; ++ float refresh_rate; ++ ++ if (!backend->transport) ++ return FALSE; ++ ++ monitor_manager = meta_backend_get_monitor_manager (META_BACKEND (backend)); ++ refresh_rate = backend->screen_info.refresh > 0 ? ++ backend->screen_info.refresh / 1000.0f : 60.0f; ++ ++ if (backend->virtual_monitor) ++ { ++ meta_virtual_monitor_set_mode (backend->virtual_monitor, ++ backend->screen_info.width, ++ backend->screen_info.height, ++ refresh_rate); ++ meta_monitor_manager_reload (monitor_manager); ++ return TRUE; ++ } ++ ++ g_autoptr (MetaVirtualMonitorInfo) info = ++ meta_virtual_monitor_info_new (backend->screen_info.width, ++ backend->screen_info.height, ++ refresh_rate, ++ "Anland", "Anland-1", "Anland-1"); ++ backend->virtual_monitor = ++ meta_monitor_manager_create_virtual_monitor (monitor_manager, info, error); ++ if (!backend->virtual_monitor) ++ return FALSE; ++ ++ meta_monitor_manager_reload (monitor_manager); ++ return TRUE; ++} ++ ++static gboolean reconnect_cb (gpointer user_data); ++ ++static void ++schedule_reconnect (MetaBackendAnland *backend) ++{ ++ if (backend->reconnect_source_id) ++ return; ++ ++ backend->reconnect_source_id = ++ g_timeout_add (ANLAND_RECONNECT_INTERVAL_MS, reconnect_cb, backend); ++} ++ ++static void ++on_transport_fallback (void *user_data) ++{ ++ MetaBackendAnland *backend = user_data; ++ ++ schedule_reconnect (backend); ++} ++ ++static gboolean ++reconnect_cb (gpointer user_data) ++{ ++ MetaBackendAnland *backend = META_BACKEND_ANLAND (user_data); ++ MetaAnlandTransportPickupResult pickup_result; ++ ++ if (!backend->transport) ++ { ++ if (!connect_transport (backend, NULL)) ++ return G_SOURCE_CONTINUE; ++ if (!update_virtual_monitor (backend, NULL)) ++ { ++ clear_transport (backend); ++ return G_SOURCE_CONTINUE; ++ } ++ meta_anland_transport_set_fallback_callback (backend->transport, ++ on_transport_fallback, ++ backend); ++ } ++ ++ pickup_result = meta_anland_transport_try_pickup (backend->transport); ++ switch (pickup_result) ++ { ++ case META_ANLAND_TRANSPORT_PICKUP_READY: ++ backend->reconnect_source_id = 0; ++ return G_SOURCE_REMOVE; ++ case META_ANLAND_TRANSPORT_PICKUP_NO_CONSUMER: ++ return G_SOURCE_CONTINUE; ++ case META_ANLAND_TRANSPORT_PICKUP_DAEMON_LOST: ++ case META_ANLAND_TRANSPORT_PICKUP_PROTOCOL_ERROR: ++ clear_transport (backend); ++ return G_SOURCE_CONTINUE; ++ } ++ ++ g_assert_not_reached (); ++} ++ ++static gboolean ++meta_backend_anland_init_post (MetaBackend *backend, ++ GError **error) ++{ ++ MetaBackendClass *parent_class = ++ META_BACKEND_CLASS (meta_backend_anland_parent_class); ++ MetaBackendAnland *backend_anland = META_BACKEND_ANLAND (backend); ++ ++ if (!parent_class->init_post (backend, error)) ++ return FALSE; ++ ++ if (!connect_transport (backend_anland, error)) ++ return FALSE; ++ ++ meta_anland_transport_set_fallback_callback (backend_anland->transport, ++ on_transport_fallback, ++ backend_anland); ++ return TRUE; ++} ++ ++static void ++meta_backend_anland_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ MetaBackendAnland *backend = META_BACKEND_ANLAND (object); ++ ++ switch (prop_id) ++ { ++ case PROP_ANLAND_SOCKET: ++ if (g_value_get_string (value)) ++ { ++ g_free (backend->socket_path); ++ backend->socket_path = g_value_dup_string (value); ++ } ++ break; ++ default: ++ G_OBJECT_CLASS (meta_backend_anland_parent_class)->set_property (object, ++ prop_id, ++ value, ++ pspec); ++ break; ++ } ++} ++ ++static void ++meta_backend_anland_dispose (GObject *object) ++{ ++ MetaBackendAnland *backend = META_BACKEND_ANLAND (object); ++ ++ g_clear_handle_id (&backend->reconnect_source_id, g_source_remove); ++ g_clear_object (&backend->virtual_monitor); ++ clear_transport (backend); ++ g_clear_pointer (&backend->socket_path, g_free); ++ ++ G_OBJECT_CLASS (meta_backend_anland_parent_class)->dispose (object); ++} ++ + static void + meta_backend_anland_class_init (MetaBackendAnlandClass *klass) + { ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ MetaBackendClass *backend_class = META_BACKEND_CLASS (klass); ++ ++ object_class->set_property = meta_backend_anland_set_property; ++ object_class->dispose = meta_backend_anland_dispose; ++ backend_class->init_post = meta_backend_anland_init_post; ++ ++ g_object_class_install_property ( ++ object_class, PROP_ANLAND_SOCKET, ++ g_param_spec_string ("anland-socket", NULL, NULL, NULL, ++ G_PARAM_WRITABLE | ++ G_PARAM_CONSTRUCT_ONLY | ++ G_PARAM_STATIC_STRINGS)); + } + + static void + meta_backend_anland_init (MetaBackendAnland *backend_anland) + { ++ backend_anland->socket_path = get_default_socket_path (); ++} ++ ++gboolean ++meta_backend_anland_setup (MetaBackendAnland *backend, ++ GError **error) ++{ ++ if (!update_virtual_monitor (backend, error)) ++ return FALSE; ++ ++ schedule_reconnect (backend); ++ return TRUE; + } +diff --git a/src/backends/anland/meta-backend-anland.h b/src/backends/anland/meta-backend-anland.h +index 746e550941..e5987ca74b 100644 +--- a/src/backends/anland/meta-backend-anland.h ++++ b/src/backends/anland/meta-backend-anland.h +@@ -9,3 +9,6 @@ G_DECLARE_FINAL_TYPE (MetaBackendAnland, + meta_backend_anland, + META, BACKEND_ANLAND, + MetaBackendNative) ++ ++gboolean meta_backend_anland_setup (MetaBackendAnland *backend, ++ GError **error); +diff --git a/src/backends/anland/vendor/meta-anland-transport.c b/src/backends/anland/vendor/meta-anland-transport.c +index a5ef702dfc..785047fb23 100644 +--- a/src/backends/anland/vendor/meta-anland-transport.c ++++ b/src/backends/anland/vendor/meta-anland-transport.c +@@ -12,6 +12,7 @@ + #include + + #define HANDSHAKE_TIMEOUT_MS 100 ++#define INITIAL_HANDSHAKE_TIMEOUT_MS 5000 + #define INPUT_TIMEOUT_MS 100 + + struct _MetaAnlandTransport +@@ -327,6 +328,7 @@ meta_anland_transport_connect (MetaAnlandTransport **out_transport, + struct ctrl_msg request = { .type = CTRL_MSG_PRODUCER_HELLO, .size = 0 }; + struct ctrl_msg response; + struct screen_info screen_info; ++ int wait_result; + + if (!out_transport || !socket_path) + return -1; +@@ -350,23 +352,29 @@ meta_anland_transport_connect (MetaAnlandTransport **out_transport, + transport->control_fd = meta_anland_connect_unix (socket_path); + if (transport->control_fd < 0 || + meta_anland_send_all (transport->control_fd, &request, +- sizeof (request)) < 0 || +- meta_anland_recv_all (transport->control_fd, &response, +- sizeof (response)) < 0 || ++ sizeof (request)) < 0) ++ goto failed; ++ ++ wait_result = wait_for_fd (transport->control_fd, POLLIN, ++ INITIAL_HANDSHAKE_TIMEOUT_MS); ++ if (wait_result != 1 || ++ recv_all_timeout (transport->control_fd, &response, sizeof (response), ++ INITIAL_HANDSHAKE_TIMEOUT_MS) < 0 || + response.type != CTRL_MSG_SCREEN_INFO || + response.size != sizeof (screen_info) || +- meta_anland_recv_all (transport->control_fd, &screen_info, +- sizeof (screen_info)) < 0) +- { +- if (transport->control_fd >= 0) +- close (transport->control_fd); +- free (transport); +- return -1; +- } ++ recv_all_timeout (transport->control_fd, &screen_info, ++ sizeof (screen_info), INITIAL_HANDSHAKE_TIMEOUT_MS) < 0) ++ goto failed; + + transport->screen_info = screen_info; + *out_transport = transport; + return 0; ++ ++failed: ++ if (transport->control_fd >= 0) ++ close (transport->control_fd); ++ free (transport); ++ return -1; + } + + void +diff --git a/src/core/meta-context-main.c b/src/core/meta-context-main.c +index 4acb9bce97..cbe9d17e3b 100644 +--- a/src/core/meta-context-main.c ++++ b/src/core/meta-context-main.c +@@ -81,6 +81,7 @@ typedef struct _MetaContextMainOptions + #endif + #ifdef HAVE_ANLAND + gboolean anland; ++ char *anland_socket; + #endif + char *trace_file; + gboolean debug_control; +@@ -159,6 +160,14 @@ check_configuration (MetaContextMain *context_main, + #endif /* HAVE_NATIVE_BACKEND */ + + #ifdef HAVE_ANLAND ++ if (context_main->options.anland_socket && ++ !context_main->options.anland) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "--anland-socket requires --anland"); ++ return FALSE; ++ } ++ + if (context_main->options.anland && + (context_main->options.display_server || + context_main->options.headless +@@ -489,6 +498,13 @@ meta_context_main_setup (MetaContext *context, + } + #endif + ++#ifdef HAVE_ANLAND ++ if (context_main->options.anland && ++ !meta_backend_anland_setup ( ++ META_BACKEND_ANLAND (meta_context_get_backend (context)), error)) ++ return FALSE; ++#endif ++ + return TRUE; + } + +@@ -542,6 +558,18 @@ static MetaBackend * + create_anland_backend (MetaContext *context, + GError **error) + { ++ MetaContextMain *context_main = META_CONTEXT_MAIN (context); ++ ++ if (context_main->options.anland_socket) ++ { ++ return g_initable_new (META_TYPE_BACKEND_ANLAND, ++ NULL, error, ++ "context", context, ++ "mode", META_BACKEND_NATIVE_MODE_HEADLESS, ++ "anland-socket", context_main->options.anland_socket, ++ NULL); ++ } ++ + return g_initable_new (META_TYPE_BACKEND_ANLAND, + NULL, error, + "context", context, +@@ -746,6 +774,12 @@ meta_context_main_add_option_entries (MetaContextMain *context_main) + &context_main->options.anland, + N_("Run with the Anland display backend") + }, ++ { ++ "anland-socket", 0, 0, G_OPTION_ARG_FILENAME, ++ &context_main->options.anland_socket, ++ N_("Specify Anland display daemon socket"), ++ "PATH" ++ }, + #endif + { + "virtual-monitor", 0, 0, G_OPTION_ARG_CALLBACK, +@@ -811,6 +845,7 @@ meta_context_main_finalize (GObject *object) + #ifdef HAVE_NATIVE_BACKEND + MetaContextMain *context_main = META_CONTEXT_MAIN (object); + ++ g_clear_pointer (&context_main->options.anland_socket, g_free); + g_list_free_full (context_main->persistent_virtual_monitors, g_object_unref); + context_main->persistent_virtual_monitors = NULL; + +diff --git a/src/meson.build b/src/meson.build +index 65d6314b72..2fc372b617 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -927,6 +927,10 @@ if have_anland + mutter_sources += [ + 'backends/anland/meta-backend-anland.c', + 'backends/anland/meta-backend-anland.h', ++ 'backends/anland/vendor/meta-anland-socket-utils.c', ++ 'backends/anland/vendor/meta-anland-socket-utils.h', ++ 'backends/anland/vendor/meta-anland-transport.c', ++ 'backends/anland/vendor/meta-anland-transport.h', + ] + endif + diff --git a/x11-packages/mutter/0013-backends-anland-Render-directly-into-consumer-buffer.patch b/x11-packages/mutter/0013-backends-anland-Render-directly-into-consumer-buffer.patch new file mode 100644 index 00000000000..7361012a865 --- /dev/null +++ b/x11-packages/mutter/0013-backends-anland-Render-directly-into-consumer-buffer.patch @@ -0,0 +1,696 @@ +From c00fe32609cb106ef308412d0623676815381d1c Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 09:37:58 +0800 +Subject: [PATCH 13/27] backends/anland: Render directly into consumer buffers + +Import the current Anland dma-buf handoff as linear ABGR8888 Cogl framebuffers and render directly into the consumer-selected buffer. Validate untrusted layouts and modifier support before importing them, and make unknown formats recoverable errors. + +Add a private StageView offscreen-present hook. It drives the consumer-paced frame clock, delivers native fence fds when available, falls back to synchronous rendering otherwise, and defers software presentation until the next buffer-ready signal. + +Tear down the GSource and external framebuffers before the transport closes consumer descriptors, keeping the existing placeholder framebuffer active during fallback. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0004-backends-anland-Render-directly-into-consumer-buffers.patch + +diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h +index 31ac7e144e..246bb5afc2 100644 +--- a/clutter/clutter/clutter-stage-view-private.h ++++ b/clutter/clutter/clutter-stage-view-private.h +@@ -21,6 +21,11 @@ + #include "clutter/clutter-types.h" + #include "mtk/mtk.h" + ++typedef gboolean (* MetaStageViewPresentFunc) (ClutterStageView *view, ++ ClutterFrame *frame, ++ int64_t global_frame_counter, ++ gpointer user_data); ++ + CLUTTER_EXPORT + void clutter_stage_view_after_paint (ClutterStageView *view, + MtkRegion *redraw_clip); +@@ -46,6 +51,20 @@ void clutter_stage_view_invalidate_projection (ClutterStageView *view); + void clutter_stage_view_set_projection (ClutterStageView *view, + const graphene_matrix_t *matrix); + ++CLUTTER_EXPORT ++void clutter_stage_view_replace_framebuffer (ClutterStageView *view, ++ CoglFramebuffer *framebuffer); ++ ++CLUTTER_EXPORT ++void clutter_stage_view_set_present_func (ClutterStageView *view, ++ MetaStageViewPresentFunc present_func, ++ gpointer user_data); ++ ++CLUTTER_EXPORT ++gboolean clutter_stage_view_present (ClutterStageView *view, ++ ClutterFrame *frame, ++ int64_t global_frame_counter); ++ + CLUTTER_EXPORT + void clutter_stage_view_add_redraw_clip (ClutterStageView *view, + const MtkRectangle *clip); +diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c +index 823e9fb63e..fe6ffd0cf1 100644 +--- a/clutter/clutter/clutter-stage-view.c ++++ b/clutter/clutter/clutter-stage-view.c +@@ -90,6 +90,9 @@ typedef struct _ClutterStageViewPrivate + gboolean has_accumulated_redraw_clip; + MtkRegion *accumulated_redraw_clip; + ++ MetaStageViewPresentFunc present_func; ++ gpointer present_user_data; ++ + float refresh_rate; + int64_t vblank_duration_us; + ClutterFrameClock *frame_clock; +@@ -1207,6 +1210,51 @@ clutter_stage_view_set_framebuffer (ClutterStageView *view, + } + } + ++void ++clutter_stage_view_replace_framebuffer (ClutterStageView *view, ++ CoglFramebuffer *framebuffer) ++{ ++ ClutterStageViewPrivate *priv = ++ clutter_stage_view_get_instance_private (view); ++ ++ g_return_if_fail (framebuffer != NULL); ++ g_return_if_fail (cogl_framebuffer_get_width (priv->framebuffer) == ++ cogl_framebuffer_get_width (framebuffer)); ++ g_return_if_fail (cogl_framebuffer_get_height (priv->framebuffer) == ++ cogl_framebuffer_get_height (framebuffer)); ++ ++ g_set_object (&priv->framebuffer, framebuffer); ++ clutter_stage_view_invalidate_viewport (view); ++ clutter_stage_view_invalidate_projection (view); ++} ++ ++void ++clutter_stage_view_set_present_func (ClutterStageView *view, ++ MetaStageViewPresentFunc present_func, ++ gpointer user_data) ++{ ++ ClutterStageViewPrivate *priv = ++ clutter_stage_view_get_instance_private (view); ++ ++ priv->present_func = present_func; ++ priv->present_user_data = user_data; ++} ++ ++gboolean ++clutter_stage_view_present (ClutterStageView *view, ++ ClutterFrame *frame, ++ int64_t global_frame_counter) ++{ ++ ClutterStageViewPrivate *priv = ++ clutter_stage_view_get_instance_private (view); ++ ++ if (!priv->present_func) ++ return FALSE; ++ ++ return priv->present_func (view, frame, global_frame_counter, ++ priv->present_user_data); ++} ++ + static void + clutter_stage_view_set_transform (ClutterStageView *view, + MtkMonitorTransform transform) +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index 1f7747ace5..3f491b1768 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -5,11 +5,24 @@ + #include "backends/anland/meta-backend-anland.h" + + #include "backends/anland/vendor/meta-anland-transport.h" ++#include "backends/meta-backend-private.h" ++#include "backends/meta-fd-source.h" + #include "backends/meta-monitor-manager-private.h" ++#include "backends/meta-renderer.h" ++#include "backends/meta-renderer-view.h" ++#include "backends/meta-stage-view-private.h" + #include "backends/meta-virtual-monitor.h" ++#include "backends/native/meta-renderer-native-private.h" + #include "meta/meta-backend.h" + ++#include ++#include ++#include ++#include ++#include ++ + #define ANLAND_RECONNECT_INTERVAL_MS 200 ++#define ANLAND_PROTOCOL_FORMAT_RGBA_8888 1 + + enum + { +@@ -27,10 +40,25 @@ struct _MetaBackendAnland + MetaVirtualMonitor *virtual_monitor; + struct screen_info screen_info; + guint reconnect_source_id; ++ ++ GSource *buffer_ready_source; ++ MetaStageView *stage_view; ++ CoglFramebuffer *placeholder_framebuffer; ++ CoglFramebuffer *buffer_framebuffers[MAX_BUFS]; ++ int current_buffer; ++ gboolean consumer_active; ++ gboolean frame_clock_inhibited; ++ gboolean frame_pending; ++ gboolean warned_sync_fallback; ++ int64_t pending_global_frame_counter; ++ int64_t pending_view_frame_counter; ++ unsigned int presentation_sequence; + }; + + G_DEFINE_TYPE (MetaBackendAnland, meta_backend_anland, META_TYPE_BACKEND_NATIVE) + ++static void deactivate_consumer (MetaBackendAnland *backend); ++ + static char * + get_default_socket_path (void) + { +@@ -73,6 +101,8 @@ validate_screen_info (const struct screen_info *screen_info, + static void + clear_transport (MetaBackendAnland *backend) + { ++ deactivate_consumer (backend); ++ + if (backend->transport) + meta_anland_transport_disconnect (backend->transport); + backend->transport = NULL; +@@ -146,6 +176,359 @@ update_virtual_monitor (MetaBackendAnland *backend, + return TRUE; + } + ++static void ++inhibit_frame_clock (MetaBackendAnland *backend) ++{ ++ if (!backend->stage_view || backend->frame_clock_inhibited) ++ return; ++ ++ clutter_frame_clock_inhibit ( ++ clutter_stage_view_get_frame_clock (CLUTTER_STAGE_VIEW (backend->stage_view))); ++ backend->frame_clock_inhibited = TRUE; ++} ++ ++static void ++uninhibit_frame_clock (MetaBackendAnland *backend) ++{ ++ if (!backend->stage_view || !backend->frame_clock_inhibited) ++ return; ++ ++ clutter_frame_clock_uninhibit ( ++ clutter_stage_view_get_frame_clock (CLUTTER_STAGE_VIEW (backend->stage_view))); ++ backend->frame_clock_inhibited = FALSE; ++} ++ ++static MetaStageView * ++find_stage_view (MetaBackendAnland *backend) ++{ ++ MetaRenderer *renderer; ++ MetaCrtc *crtc; ++ MetaRendererView *renderer_view; ++ ++ if (!backend->virtual_monitor) ++ return NULL; ++ ++ renderer = meta_backend_get_renderer (META_BACKEND (backend)); ++ crtc = meta_virtual_monitor_get_crtc (backend->virtual_monitor); ++ renderer_view = meta_renderer_get_view_for_crtc (renderer, crtc); ++ ++ return renderer_view ? META_STAGE_VIEW (renderer_view) : NULL; ++} ++ ++static gboolean ++ensure_stage_view (MetaBackendAnland *backend, ++ GError **error) ++{ ++ MetaStageView *stage_view = find_stage_view (backend); ++ ++ if (!stage_view) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Anland virtual monitor does not have a renderer view yet"); ++ return FALSE; ++ } ++ ++ if (backend->stage_view == stage_view) ++ return TRUE; ++ ++ if (backend->consumer_active || backend->frame_pending) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_BUSY, ++ "Anland renderer view changed while a consumer was active"); ++ return FALSE; ++ } ++ ++ if (backend->frame_clock_inhibited) ++ uninhibit_frame_clock (backend); ++ ++ g_set_object (&backend->stage_view, stage_view); ++ g_set_object (&backend->placeholder_framebuffer, ++ clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (stage_view))); ++ return TRUE; ++} ++ ++static gboolean ++validate_buffer_info (const struct buf_info *buffer_info, ++ int fd, ++ GError **error) ++{ ++ struct stat stat_buf; ++ uint64_t required_size; ++ ++ if (buffer_info->format != ANLAND_PROTOCOL_FORMAT_RGBA_8888 || ++ buffer_info->modifier != DRM_FORMAT_MOD_LINEAR || ++ buffer_info->width > G_MAXUINT32 / 4u || ++ buffer_info->stride < buffer_info->width * 4u || ++ buffer_info->height > G_MAXUINT64 / buffer_info->stride || ++ buffer_info->offset > G_MAXUINT64 - ++ (uint64_t) buffer_info->stride * buffer_info->height) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, ++ "Anland daemon provided an invalid dma-buf description"); ++ return FALSE; ++ } ++ ++ required_size = buffer_info->offset + ++ (uint64_t) buffer_info->stride * buffer_info->height; ++ if (fstat (fd, &stat_buf) == 0 && stat_buf.st_size > 0 && ++ required_size > (uint64_t) stat_buf.st_size) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, ++ "Anland dma-buf is smaller than its advertised layout"); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static void ++complete_pending_frame (MetaBackendAnland *backend) ++{ ++ ClutterFrameInfo frame_info; ++ ++ if (!backend->stage_view || !backend->frame_pending) ++ return; ++ ++ frame_info = (ClutterFrameInfo) { ++ .global_frame_counter = backend->pending_global_frame_counter, ++ .view_frame_counter = backend->pending_view_frame_counter, ++ .refresh_rate = clutter_stage_view_get_refresh_rate ( ++ CLUTTER_STAGE_VIEW (backend->stage_view)), ++ .presentation_time = g_get_monotonic_time (), ++ .flags = CLUTTER_FRAME_INFO_FLAG_NONE, ++ .sequence = ++backend->presentation_sequence, ++ }; ++ clutter_stage_view_notify_presented (CLUTTER_STAGE_VIEW (backend->stage_view), ++ &frame_info); ++ backend->frame_pending = FALSE; ++} ++ ++static void ++deactivate_consumer (MetaBackendAnland *backend) ++{ ++ if (backend->buffer_ready_source) ++ { ++ g_source_destroy (backend->buffer_ready_source); ++ g_clear_pointer (&backend->buffer_ready_source, g_source_unref); ++ } ++ ++ if (backend->stage_view) ++ { ++ clutter_stage_view_set_present_func ( ++ CLUTTER_STAGE_VIEW (backend->stage_view), NULL, NULL); ++ ++ if (backend->current_buffer >= 0 && ++ backend->placeholder_framebuffer && ++ clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (backend->stage_view)) == ++ backend->buffer_framebuffers[backend->current_buffer]) ++ clutter_stage_view_replace_framebuffer ( ++ CLUTTER_STAGE_VIEW (backend->stage_view), ++ backend->placeholder_framebuffer); ++ ++ if (backend->frame_pending) ++ clutter_stage_view_notify_ready (CLUTTER_STAGE_VIEW (backend->stage_view)); ++ } ++ ++ for (int i = 0; i < MAX_BUFS; i++) ++ g_clear_object (&backend->buffer_framebuffers[i]); ++ ++ backend->consumer_active = FALSE; ++ backend->current_buffer = -1; ++ backend->frame_pending = FALSE; ++ inhibit_frame_clock (backend); ++} ++ ++static gboolean ++on_stage_view_present (ClutterStageView *stage_view, ++ ClutterFrame *frame, ++ int64_t global_frame_counter, ++ gpointer user_data) ++{ ++ MetaBackendAnland *backend = user_data; ++ CoglFramebuffer *framebuffer; ++ CoglContext *cogl_context; ++ int fence_fd = -1; ++ ++ if (!backend->consumer_active || !backend->transport || ++ meta_anland_transport_is_fallback (backend->transport)) ++ return FALSE; ++ ++ framebuffer = clutter_stage_view_get_onscreen (stage_view); ++ cogl_context = cogl_framebuffer_get_context (framebuffer); ++ cogl_framebuffer_flush (framebuffer); ++ ++ if (cogl_context_has_feature (cogl_context, COGL_FEATURE_ID_SYNC_FD)) ++ fence_fd = cogl_context_get_latest_sync_fd (cogl_context); ++ else ++ { ++ if (!backend->warned_sync_fallback) ++ { ++ g_warning ("Anland backend has no native fence support; using synchronous rendering"); ++ backend->warned_sync_fallback = TRUE; ++ } ++ cogl_framebuffer_finish (framebuffer); ++ } ++ ++ meta_anland_transport_set_render_fence (backend->transport, fence_fd); ++ if (!meta_anland_transport_notify_frame_done (backend->transport)) ++ { ++ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); ++ return TRUE; ++ } ++ ++ backend->pending_global_frame_counter = global_frame_counter; ++ backend->pending_view_frame_counter = clutter_frame_get_count (frame); ++ backend->frame_pending = TRUE; ++ inhibit_frame_clock (backend); ++ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); ++ return TRUE; ++} ++ ++static gboolean ++buffer_ready_source_prepare (gpointer user_data) ++{ ++ return FALSE; ++} ++ ++static gboolean ++buffer_ready_source_dispatch (gpointer user_data) ++{ ++ MetaBackendAnland *backend = META_BACKEND_ANLAND (user_data); ++ eventfd_t count; ++ int buffer_ready_fd; ++ int selected_buffer; ++ ++ if (!backend->consumer_active || !backend->transport || ++ meta_anland_transport_is_fallback (backend->transport)) ++ return G_SOURCE_REMOVE; ++ ++ buffer_ready_fd = meta_anland_transport_get_buffer_ready_fd (backend->transport); ++ if (buffer_ready_fd < 0 || eventfd_read (buffer_ready_fd, &count) < 0 || ++ count != 1) ++ { ++ meta_anland_transport_enter_fallback (backend->transport); ++ return G_SOURCE_REMOVE; ++ } ++ ++ selected_buffer = meta_anland_transport_get_selected_buffer (backend->transport); ++ if (selected_buffer < 0 || selected_buffer >= MAX_BUFS || ++ !backend->buffer_framebuffers[selected_buffer]) ++ { ++ meta_anland_transport_enter_fallback (backend->transport); ++ return G_SOURCE_REMOVE; ++ } ++ ++ complete_pending_frame (backend); ++ ++ if (backend->current_buffer != selected_buffer) ++ { ++ clutter_stage_view_replace_framebuffer ( ++ CLUTTER_STAGE_VIEW (backend->stage_view), ++ backend->buffer_framebuffers[selected_buffer]); ++ backend->current_buffer = selected_buffer; ++ } ++ ++ clutter_stage_view_add_redraw_clip (CLUTTER_STAGE_VIEW (backend->stage_view), ++ NULL); ++ uninhibit_frame_clock (backend); ++ clutter_stage_view_schedule_update_now (CLUTTER_STAGE_VIEW (backend->stage_view)); ++ return G_SOURCE_CONTINUE; ++} ++ ++static gboolean ++activate_consumer (MetaBackendAnland *backend) ++{ ++ MetaRenderer *renderer = meta_backend_get_renderer (META_BACKEND (backend)); ++ MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer); ++ CoglFramebuffer *framebuffers[MAX_BUFS] = { NULL, }; ++ struct buf_info buffer_info; ++ int n_buffers; ++ int buffer_ready_fd; ++ int source_fd; ++ int i; ++ g_autoptr (GError) error = NULL; ++ ++ n_buffers = meta_anland_transport_get_buffer_count (backend->transport); ++ if (n_buffers < 1 || n_buffers > MAX_BUFS || ++ !meta_anland_transport_get_buffer_info (backend->transport, 0, ++ &buffer_info)) ++ goto failed; ++ ++ if (buffer_info.width != backend->screen_info.width || ++ buffer_info.height != backend->screen_info.height) ++ { ++ backend->screen_info.width = buffer_info.width; ++ backend->screen_info.height = buffer_info.height; ++ if (!update_virtual_monitor (backend, &error)) ++ g_warning ("Failed to update the Anland virtual monitor: %s", ++ error->message); ++ return FALSE; ++ } ++ ++ if (!ensure_stage_view (backend, &error)) ++ return FALSE; ++ ++ if (cogl_framebuffer_get_width (backend->placeholder_framebuffer) != ++ (int) buffer_info.width || ++ cogl_framebuffer_get_height (backend->placeholder_framebuffer) != ++ (int) buffer_info.height) ++ return FALSE; ++ ++ for (i = 0; i < n_buffers; i++) ++ { ++ int fd; ++ uint32_t stride; ++ uint32_t offset; ++ uint64_t modifier; ++ ++ if (!meta_anland_transport_get_buffer_info (backend->transport, i, ++ &buffer_info)) ++ goto failed; ++ ++ fd = meta_anland_transport_get_buffer_fd (backend->transport, i); ++ if (fd < 0 || !validate_buffer_info (&buffer_info, fd, &error)) ++ goto failed; ++ ++ stride = buffer_info.stride; ++ offset = buffer_info.offset; ++ modifier = buffer_info.modifier; ++ framebuffers[i] = meta_renderer_native_create_dma_buf_framebuffer ( ++ renderer_native, buffer_info.width, buffer_info.height, ++ DRM_FORMAT_ABGR8888, 1, &fd, &stride, &offset, &modifier, &error); ++ if (!framebuffers[i]) ++ goto failed; ++ } ++ ++ buffer_ready_fd = meta_anland_transport_get_buffer_ready_fd (backend->transport); ++ source_fd = fcntl (buffer_ready_fd, F_DUPFD_CLOEXEC, 3); ++ if (source_fd < 0) ++ { ++ g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (errno), ++ "Failed to duplicate Anland buffer-ready eventfd"); ++ goto failed; ++ } ++ ++ for (i = 0; i < n_buffers; i++) ++ backend->buffer_framebuffers[i] = framebuffers[i]; ++ backend->consumer_active = TRUE; ++ backend->current_buffer = -1; ++ inhibit_frame_clock (backend); ++ clutter_stage_view_set_present_func (CLUTTER_STAGE_VIEW (backend->stage_view), ++ on_stage_view_present, backend); ++ backend->buffer_ready_source = meta_create_fd_source ( ++ source_fd, "[mutter] Anland buffer ready", ++ buffer_ready_source_prepare, buffer_ready_source_dispatch, backend, NULL); ++ g_source_attach (backend->buffer_ready_source, NULL); ++ return TRUE; ++ ++failed: ++ g_warning ("Failed to activate Anland consumer buffers: %s", ++ error ? error->message : "invalid transport state"); ++ for (i = 0; i < MAX_BUFS; i++) ++ g_clear_object (&framebuffers[i]); ++ return FALSE; ++} ++ + static gboolean reconnect_cb (gpointer user_data); + + static void +@@ -163,6 +546,7 @@ on_transport_fallback (void *user_data) + { + MetaBackendAnland *backend = user_data; + ++ deactivate_consumer (backend); + schedule_reconnect (backend); + } + +@@ -190,6 +574,8 @@ reconnect_cb (gpointer user_data) + switch (pickup_result) + { + case META_ANLAND_TRANSPORT_PICKUP_READY: ++ if (!activate_consumer (backend)) ++ return G_SOURCE_CONTINUE; + backend->reconnect_source_id = 0; + return G_SOURCE_REMOVE; + case META_ANLAND_TRANSPORT_PICKUP_NO_CONSUMER: +@@ -255,8 +641,11 @@ meta_backend_anland_dispose (GObject *object) + MetaBackendAnland *backend = META_BACKEND_ANLAND (object); + + g_clear_handle_id (&backend->reconnect_source_id, g_source_remove); +- g_clear_object (&backend->virtual_monitor); + clear_transport (backend); ++ uninhibit_frame_clock (backend); ++ g_clear_object (&backend->placeholder_framebuffer); ++ g_clear_object (&backend->stage_view); ++ g_clear_object (&backend->virtual_monitor); + g_clear_pointer (&backend->socket_path, g_free); + + G_OBJECT_CLASS (meta_backend_anland_parent_class)->dispose (object); +@@ -284,6 +673,7 @@ static void + meta_backend_anland_init (MetaBackendAnland *backend_anland) + { + backend_anland->socket_path = get_default_socket_path (); ++ backend_anland->current_buffer = -1; + } + + gboolean +diff --git a/src/backends/anland/vendor/meta-anland-transport.c b/src/backends/anland/vendor/meta-anland-transport.c +index 785047fb23..b987eb2f74 100644 +--- a/src/backends/anland/vendor/meta-anland-transport.c ++++ b/src/backends/anland/vendor/meta-anland-transport.c +@@ -94,9 +94,9 @@ enter_fallback (MetaAnlandTransport *transport) + return; + + transport->fallback = true; +- release_consumer_resources (transport); + if (transport->fallback_callback) + transport->fallback_callback (transport->fallback_user_data); ++ release_consumer_resources (transport); + } + + static int +@@ -434,6 +434,13 @@ meta_anland_transport_is_fallback (MetaAnlandTransport *transport) + return !transport || transport->fallback; + } + ++void ++meta_anland_transport_enter_fallback (MetaAnlandTransport *transport) ++{ ++ if (transport) ++ enter_fallback (transport); ++} ++ + void + meta_anland_transport_set_fallback_callback (MetaAnlandTransport *transport, + void (*callback) (void *user_data), +diff --git a/src/backends/anland/vendor/meta-anland-transport.h b/src/backends/anland/vendor/meta-anland-transport.h +index 79802a8200..248da61a85 100644 +--- a/src/backends/anland/vendor/meta-anland-transport.h ++++ b/src/backends/anland/vendor/meta-anland-transport.h +@@ -30,6 +30,7 @@ MetaAnlandTransportPickupResult + meta_anland_transport_try_pickup (MetaAnlandTransport *transport); + + bool meta_anland_transport_is_fallback (MetaAnlandTransport *transport); ++void meta_anland_transport_enter_fallback (MetaAnlandTransport *transport); + void meta_anland_transport_set_fallback_callback (MetaAnlandTransport *transport, + void (*callback) (void *user_data), + void *user_data); +diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c +index 7936f3af40..1bd96112d0 100644 +--- a/src/backends/meta-stage-impl.c ++++ b/src/backends/meta-stage-impl.c +@@ -315,6 +315,13 @@ swap_framebuffer (ClutterStageWindow *stage_window, + { + MetaStageView *view = META_STAGE_VIEW (stage_view); + ++ if (clutter_stage_view_present (stage_view, frame, ++ priv->global_frame_counter)) ++ { ++ priv->global_frame_counter++; ++ return; ++ } ++ + meta_topic (META_DEBUG_BACKEND, + "fake offscreen swap (framebuffer: %p)", + framebuffer); +diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c +index 60b8d005c1..f65afc548d 100644 +--- a/src/backends/native/meta-renderer-native.c ++++ b/src/backends/native/meta-renderer-native.c +@@ -664,11 +664,43 @@ meta_renderer_native_create_dma_buf_framebuffer (MetaRendererNative *renderer_n + CoglTexture *cogl_tex; + CoglOffscreen *cogl_fbo; + const MetaFormatInfo *format_info; ++ const uint64_t *import_modifiers = modifiers; + + format_info = meta_format_info_from_drm_format (drm_format); +- g_assert (format_info); ++ if (!format_info) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "Unsupported dma-buf format 0x%08x", drm_format); ++ return NULL; ++ } + cogl_format = format_info->cogl_format; + ++ if (!meta_egl_has_extensions (egl, egl_display, NULL, ++ "EGL_EXT_image_dma_buf_import", NULL)) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "EGL does not support dma-buf image import"); ++ return NULL; ++ } ++ ++ if (modifiers && ++ !meta_egl_has_extensions (egl, egl_display, NULL, ++ "EGL_EXT_image_dma_buf_import_modifiers", ++ NULL)) ++ { ++ for (int i = 0; i < n_planes; i++) ++ { ++ if (modifiers[i] != DRM_FORMAT_MOD_LINEAR) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "EGL cannot import non-linear dma-buf modifiers"); ++ return NULL; ++ } ++ } ++ ++ import_modifiers = NULL; ++ } ++ + egl_image = meta_egl_create_dmabuf_image (egl, + egl_display, + width, +@@ -678,7 +710,7 @@ meta_renderer_native_create_dma_buf_framebuffer (MetaRendererNative *renderer_n + fds, + strides, + offsets, +- modifiers, ++ import_modifiers, + error); + if (egl_image == EGL_NO_IMAGE_KHR) + return NULL; diff --git a/x11-packages/mutter/0014-backends-anland-Handle-input-and-runtime-refresh.patch b/x11-packages/mutter/0014-backends-anland-Handle-input-and-runtime-refresh.patch new file mode 100644 index 00000000000..2dd505d1394 --- /dev/null +++ b/x11-packages/mutter/0014-backends-anland-Handle-input-and-runtime-refresh.patch @@ -0,0 +1,1190 @@ +From 646c02a6019e2497c9e7a40d953f5583fdca8c67 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 21:22:09 +0800 +Subject: [PATCH 14/27] backends/anland: Handle input and runtime refresh + +Create persistent pointer, keyboard, and touchscreen virtual devices for the Anland data channel. Convert protocol evdev keys and buttons, scroll axes, and consumer motion into native Clutter input while preserving absolute and relative motion in one event. + +Batch touch updates into one input-thread task per protocol touch frame, expire incomplete batches safely, and release all pressed input state on fallback. Drain unimplemented variable payloads and resource fds so later protocol frames remain aligned. + +Reconnect consumer rendering after a valid runtime refresh change and wait for the virtual StageView to adopt the new refresh rate before reimporting buffers. + +Extend the mock transport test with pointer-motion and display-refresh wire framing. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0005-backends-anland-Handle-input-and-runtime-refresh.patch + +diff --git a/src/backends/anland/meta-anland-input.c b/src/backends/anland/meta-anland-input.c +new file mode 100644 +index 0000000000..2d0c1b66d2 +--- /dev/null ++++ b/src/backends/anland/meta-anland-input.c +@@ -0,0 +1,516 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#include "config.h" ++ ++#include "backends/anland/meta-anland-input.h" ++ ++#include "backends/meta-backend-private.h" ++#include "backends/native/meta-virtual-input-device-native.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define ANLAND_MAX_RESOURCE_FDS 64 ++#define ANLAND_MAX_TOUCH_BATCH_EVENTS 128 ++#define ANLAND_TOUCH_BATCH_TIMEOUT_MS 100 ++ ++struct _MetaAnlandInput ++{ ++ ClutterVirtualInputDevice *pointer; ++ ClutterVirtualInputDevice *keyboard; ++ ClutterVirtualInputDevice *touchscreen; ++ gboolean pressed_keys[KEY_CNT]; ++ gboolean pressed_buttons[KEY_CNT]; ++ GHashTable *touch_slots; ++ gboolean active_touch_slots[CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS]; ++ GArray *touch_batch; ++ guint touch_batch_timeout_id; ++ ++ MetaAnlandInputRefreshFunc refresh_func; ++ gpointer refresh_user_data; ++}; ++ ++static gboolean ++is_finite_pair (float x, ++ float y) ++{ ++ return isfinite (x) && isfinite (y); ++} ++ ++static void ++clear_touch_slot (MetaAnlandInput *input, ++ int slot) ++{ ++ GHashTableIter iter; ++ gpointer key; ++ gpointer value; ++ ++ g_hash_table_iter_init (&iter, input->touch_slots); ++ while (g_hash_table_iter_next (&iter, &key, &value)) ++ { ++ if (GPOINTER_TO_UINT (value) == (guint) slot + 1) ++ { ++ g_hash_table_iter_remove (&iter); ++ break; ++ } ++ } ++ input->active_touch_slots[slot] = FALSE; ++} ++ ++static void ++cancel_touches (MetaAnlandInput *input) ++{ ++ MetaVirtualTouchEvent events[CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS]; ++ guint n_events = 0; ++ guint slot; ++ ++ if (!input->touchscreen) ++ return; ++ ++ for (slot = 0; slot < G_N_ELEMENTS (input->active_touch_slots); slot++) ++ { ++ if (!input->active_touch_slots[slot]) ++ continue; ++ ++ events[n_events++] = (MetaVirtualTouchEvent) { ++ .type = META_VIRTUAL_TOUCH_EVENT_CANCEL, ++ .slot = slot, ++ }; ++ } ++ ++ if (n_events > 0) ++ meta_virtual_input_device_native_notify_touch_events ( ++ META_VIRTUAL_INPUT_DEVICE_NATIVE (input->touchscreen), ++ g_get_monotonic_time (), events, n_events); ++ ++ if (input->touch_slots) ++ g_hash_table_remove_all (input->touch_slots); ++ memset (input->active_touch_slots, 0, sizeof (input->active_touch_slots)); ++} ++ ++static gboolean ++touch_batch_timeout_cb (gpointer user_data) ++{ ++ MetaAnlandInput *input = user_data; ++ ++ input->touch_batch_timeout_id = 0; ++ if (input->touch_batch->len > 0) ++ { ++ g_warning ("Anland input dropped an incomplete touch frame"); ++ cancel_touches (input); ++ g_array_set_size (input->touch_batch, 0); ++ } ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++schedule_touch_batch_timeout (MetaAnlandInput *input) ++{ ++ if (!input->touch_batch_timeout_id) ++ input->touch_batch_timeout_id = ++ g_timeout_add (ANLAND_TOUCH_BATCH_TIMEOUT_MS, ++ touch_batch_timeout_cb, input); ++} ++ ++static int ++allocate_touch_slot (MetaAnlandInput *input) ++{ ++ guint slot; ++ ++ for (slot = 0; slot < G_N_ELEMENTS (input->active_touch_slots); slot++) ++ { ++ if (!input->active_touch_slots[slot]) ++ return slot; ++ } ++ ++ return -1; ++} ++ ++static void ++flush_touch_batch (MetaAnlandInput *input) ++{ ++ guint i; ++ ++ if (input->touch_batch_timeout_id) ++ { ++ g_source_remove (input->touch_batch_timeout_id); ++ input->touch_batch_timeout_id = 0; ++ } ++ ++ if (input->touch_batch->len == 0) ++ return; ++ ++ meta_virtual_input_device_native_notify_touch_events ( ++ META_VIRTUAL_INPUT_DEVICE_NATIVE (input->touchscreen), ++ g_get_monotonic_time (), (const MetaVirtualTouchEvent *) input->touch_batch->data, ++ input->touch_batch->len); ++ ++ for (i = 0; i < input->touch_batch->len; i++) ++ { ++ const MetaVirtualTouchEvent *event = ++ &g_array_index (input->touch_batch, MetaVirtualTouchEvent, i); ++ ++ if (event->type == META_VIRTUAL_TOUCH_EVENT_UP) ++ clear_touch_slot (input, event->slot); ++ } ++ g_array_set_size (input->touch_batch, 0); ++} ++ ++static gboolean ++drain_payload (MetaAnlandTransport *transport, ++ uint32_t size) ++{ ++ uint8_t buffer[4096]; ++ ++ if (size > META_ANLAND_MAX_PAYLOAD_SIZE) ++ return FALSE; ++ ++ while (size > 0) ++ { ++ size_t chunk = MIN ((size_t) size, sizeof (buffer)); ++ ++ if (meta_anland_transport_read_input_payload (transport, buffer, chunk, ++ 100) != 1) ++ return FALSE; ++ size -= (uint32_t) chunk; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++discard_resource_fds (MetaAnlandTransport *transport, ++ uint32_t expected_fds) ++{ ++ int fds[ANLAND_MAX_RESOURCE_FDS]; ++ int n_fds = 0; ++ int i; ++ ++ if (expected_fds == 0) ++ return TRUE; ++ if (expected_fds > G_N_ELEMENTS (fds) || ++ meta_anland_transport_read_input_fds (transport, fds, ++ G_N_ELEMENTS (fds), &n_fds, 100) != 1) ++ return FALSE; ++ ++ for (i = 0; i < n_fds; i++) ++ close (fds[i]); ++ ++ return n_fds == (int) expected_fds; ++} ++ ++static void ++handle_pointer_motion (MetaAnlandInput *input, ++ const struct InputEvent *event) ++{ ++ if (!is_finite_pair (event->pointer_motion.x, event->pointer_motion.y) || ++ !is_finite_pair (event->pointer_motion.dx, event->pointer_motion.dy)) ++ return; ++ ++ meta_virtual_input_device_native_notify_absolute_relative_motion ( ++ META_VIRTUAL_INPUT_DEVICE_NATIVE (input->pointer), g_get_monotonic_time (), ++ event->pointer_motion.x, event->pointer_motion.y, ++ event->pointer_motion.dx, event->pointer_motion.dy); ++} ++ ++static void ++handle_pointer_button (MetaAnlandInput *input, ++ const struct InputEvent *event) ++{ ++ uint32_t button = event->pointer_button.button; ++ gboolean pressed = event->pointer_button.pressed != 0; ++ ++ if (button >= KEY_CNT || button < BTN_LEFT || button > BTN_GEAR_UP || ++ input->pressed_buttons[button] == pressed) ++ return; ++ ++ input->pressed_buttons[button] = pressed; ++ clutter_virtual_input_device_notify_button ( ++ input->pointer, g_get_monotonic_time (), meta_evdev_button_to_clutter (button), ++ pressed ? CLUTTER_BUTTON_STATE_PRESSED : CLUTTER_BUTTON_STATE_RELEASED); ++} ++ ++static void ++handle_pointer_axis (MetaAnlandInput *input, ++ const struct InputEvent *event) ++{ ++ ClutterScrollDirection direction; ++ double dx = 0.0; ++ double dy = 0.0; ++ int64_t discrete = event->pointer_axis.discrete; ++ int steps; ++ ++ if (!isfinite (event->pointer_axis.value)) ++ return; ++ ++ switch (event->pointer_axis.axis) ++ { ++ case 0: ++ dy = event->pointer_axis.value; ++ direction = discrete >= 0 ? CLUTTER_SCROLL_DOWN : CLUTTER_SCROLL_UP; ++ break; ++ case 1: ++ dx = event->pointer_axis.value; ++ direction = discrete >= 0 ? CLUTTER_SCROLL_RIGHT : CLUTTER_SCROLL_LEFT; ++ break; ++ default: ++ g_warning ("Anland input ignored invalid scroll axis %u", ++ event->pointer_axis.axis); ++ return; ++ } ++ ++ if (dx != 0.0 || dy != 0.0) ++ clutter_virtual_input_device_notify_scroll_continuous ( ++ input->pointer, g_get_monotonic_time (), dx, dy, ++ CLUTTER_SCROLL_SOURCE_WHEEL, CLUTTER_SCROLL_FINISHED_NONE); ++ ++ steps = MIN (llabs (discrete), 120); ++ while (steps-- > 0) ++ clutter_virtual_input_device_notify_discrete_scroll ( ++ input->pointer, g_get_monotonic_time (), direction, ++ CLUTTER_SCROLL_SOURCE_WHEEL); ++} ++ ++static void ++handle_key (MetaAnlandInput *input, ++ const struct InputEvent *event) ++{ ++ uint32_t keycode = event->key.keycode; ++ gboolean pressed; ++ ++ if (keycode >= KEY_CNT || ++ (event->key.action != INPUT_ACTION_DOWN && ++ event->key.action != INPUT_ACTION_UP)) ++ return; ++ ++ pressed = event->key.action == INPUT_ACTION_DOWN; ++ if (input->pressed_keys[keycode] == pressed) ++ return; ++ ++ input->pressed_keys[keycode] = pressed; ++ clutter_virtual_input_device_notify_key ( ++ input->keyboard, g_get_monotonic_time (), keycode, ++ pressed ? CLUTTER_KEY_STATE_PRESSED : CLUTTER_KEY_STATE_RELEASED); ++} ++ ++static void ++handle_touch (MetaAnlandInput *input, ++ const struct InputEvent *event) ++{ ++ gpointer value; ++ int slot; ++ MetaVirtualTouchEvent touch_event; ++ ++ if ((event->touch.action == INPUT_ACTION_DOWN || ++ event->touch.action == INPUT_ACTION_MOVE) && ++ !is_finite_pair (event->touch.x, event->touch.y)) ++ return; ++ ++ value = g_hash_table_lookup (input->touch_slots, ++ GINT_TO_POINTER (event->touch.pointer_id)); ++ switch (event->touch.action) ++ { ++ case INPUT_ACTION_DOWN: ++ if (value) ++ return; ++ ++ slot = allocate_touch_slot (input); ++ if (slot < 0) ++ { ++ g_warning ("Anland input ran out of touch slots"); ++ return; ++ } ++ input->active_touch_slots[slot] = TRUE; ++ g_hash_table_insert (input->touch_slots, ++ GINT_TO_POINTER (event->touch.pointer_id), ++ GUINT_TO_POINTER (slot + 1)); ++ touch_event = (MetaVirtualTouchEvent) { ++ .type = META_VIRTUAL_TOUCH_EVENT_DOWN, ++ .slot = slot, ++ .x = event->touch.x, ++ .y = event->touch.y, ++ }; ++ break; ++ case INPUT_ACTION_MOVE: ++ if (!value) ++ return; ++ touch_event = (MetaVirtualTouchEvent) { ++ .type = META_VIRTUAL_TOUCH_EVENT_MOTION, ++ .slot = GPOINTER_TO_UINT (value) - 1, ++ .x = event->touch.x, ++ .y = event->touch.y, ++ }; ++ break; ++ case INPUT_ACTION_UP: ++ if (!value) ++ return; ++ touch_event = (MetaVirtualTouchEvent) { ++ .type = META_VIRTUAL_TOUCH_EVENT_UP, ++ .slot = GPOINTER_TO_UINT (value) - 1, ++ }; ++ break; ++ default: ++ return; ++ } ++ ++ if (input->touch_batch->len >= ANLAND_MAX_TOUCH_BATCH_EVENTS) ++ { ++ g_warning ("Anland input dropped an oversized touch frame"); ++ cancel_touches (input); ++ g_array_set_size (input->touch_batch, 0); ++ return; ++ } ++ ++ g_array_append_val (input->touch_batch, touch_event); ++ schedule_touch_batch_timeout (input); ++} ++ ++MetaAnlandInput * ++meta_anland_input_new (MetaBackend *backend, ++ MetaAnlandInputRefreshFunc refresh_func, ++ gpointer user_data, ++ GError **error) ++{ ++ MetaAnlandInput *input; ++ ClutterSeat *seat; ++ ++ seat = meta_backend_get_default_seat (backend); ++ if (!seat) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Anland backend has no default input seat"); ++ return NULL; ++ } ++ ++ input = g_new0 (MetaAnlandInput, 1); ++ input->touch_slots = g_hash_table_new (g_direct_hash, g_direct_equal); ++ input->touch_batch = g_array_new (FALSE, FALSE, sizeof (MetaVirtualTouchEvent)); ++ input->pointer = clutter_seat_create_virtual_device (seat, ++ CLUTTER_POINTER_DEVICE); ++ input->keyboard = clutter_seat_create_virtual_device (seat, ++ CLUTTER_KEYBOARD_DEVICE); ++ input->touchscreen = clutter_seat_create_virtual_device (seat, ++ CLUTTER_TOUCHSCREEN_DEVICE); ++ if (!input->pointer || !input->keyboard || !input->touchscreen) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Failed to create Anland virtual input devices"); ++ meta_anland_input_free (input); ++ return NULL; ++ } ++ ++ input->refresh_func = refresh_func; ++ input->refresh_user_data = user_data; ++ return input; ++} ++ ++void ++meta_anland_input_free (MetaAnlandInput *input) ++{ ++ if (!input) ++ return; ++ ++ if (input->touch_batch_timeout_id) ++ { ++ g_source_remove (input->touch_batch_timeout_id); ++ input->touch_batch_timeout_id = 0; ++ } ++ meta_anland_input_reset (input); ++ g_clear_object (&input->pointer); ++ g_clear_object (&input->keyboard); ++ g_clear_object (&input->touchscreen); ++ g_clear_pointer (&input->touch_slots, g_hash_table_unref); ++ g_clear_pointer (&input->touch_batch, g_array_unref); ++ g_free (input); ++} ++ ++gboolean ++meta_anland_input_handle_event (MetaAnlandInput *input, ++ MetaAnlandTransport *transport, ++ const struct InputEvent *event) ++{ ++ switch (event->type) ++ { ++ case INPUT_TYPE_POINTER_MOTION: ++ handle_pointer_motion (input, event); ++ break; ++ case INPUT_TYPE_POINTER_BUTTON: ++ handle_pointer_button (input, event); ++ break; ++ case INPUT_TYPE_POINTER_AXIS: ++ handle_pointer_axis (input, event); ++ break; ++ case INPUT_TYPE_KEY: ++ handle_key (input, event); ++ break; ++ case INPUT_TYPE_TOUCH: ++ handle_touch (input, event); ++ break; ++ case INPUT_TYPE_TOUCH_FRAME: ++ flush_touch_batch (input); ++ break; ++ case INPUT_TYPE_DISPLAY_REFRESH: ++ if (event->display.refresh_mhz >= 1000 && ++ event->display.refresh_mhz <= 1000000 && input->refresh_func) ++ input->refresh_func (event->display.refresh_mhz, ++ input->refresh_user_data); ++ break; ++ case INPUT_TYPE_CLIPBOARD: ++ return drain_payload (transport, event->clipboard.size); ++ case INPUT_TYPE_TEXT_INPUT: ++ return drain_payload (transport, event->text_input.size); ++ case INPUT_TYPE_RESOURCE: ++ return discard_resource_fds (transport, event->resource.fdnum); ++ case INPUT_TYPE_ACTION: ++ break; ++ default: ++ g_warning ("Anland input ignored unknown event type %u", event->type); ++ break; ++ } ++ ++ return TRUE; ++} ++ ++void ++meta_anland_input_reset (MetaAnlandInput *input) ++{ ++ guint code; ++ ++ if (!input) ++ return; ++ ++ if (input->touch_batch_timeout_id) ++ { ++ g_source_remove (input->touch_batch_timeout_id); ++ input->touch_batch_timeout_id = 0; ++ } ++ ++ for (code = 0; code < G_N_ELEMENTS (input->pressed_keys); code++) ++ { ++ if (!input->pressed_keys[code]) ++ continue; ++ ++ clutter_virtual_input_device_notify_key ( ++ input->keyboard, g_get_monotonic_time (), code, ++ CLUTTER_KEY_STATE_RELEASED); ++ input->pressed_keys[code] = FALSE; ++ } ++ ++ for (code = BTN_LEFT; code < G_N_ELEMENTS (input->pressed_buttons); code++) ++ { ++ if (!input->pressed_buttons[code]) ++ continue; ++ ++ clutter_virtual_input_device_notify_button ( ++ input->pointer, g_get_monotonic_time (), ++ meta_evdev_button_to_clutter (code), CLUTTER_BUTTON_STATE_RELEASED); ++ input->pressed_buttons[code] = FALSE; ++ } ++ ++ cancel_touches (input); ++ g_array_set_size (input->touch_batch, 0); ++} +diff --git a/src/backends/anland/meta-anland-input.h b/src/backends/anland/meta-anland-input.h +new file mode 100644 +index 0000000000..783e54736e +--- /dev/null ++++ b/src/backends/anland/meta-anland-input.h +@@ -0,0 +1,24 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#pragma once ++ ++#include ++ ++#include "backends/anland/vendor/meta-anland-transport.h" ++#include "backends/meta-backend-types.h" ++ ++typedef struct _MetaAnlandInput MetaAnlandInput; ++ ++typedef void (* MetaAnlandInputRefreshFunc) (uint32_t refresh_mhz, ++ gpointer user_data); ++ ++MetaAnlandInput * meta_anland_input_new (MetaBackend *backend, ++ MetaAnlandInputRefreshFunc refresh_func, ++ gpointer user_data, ++ GError **error); ++void meta_anland_input_free (MetaAnlandInput *input); ++ ++gboolean meta_anland_input_handle_event (MetaAnlandInput *input, ++ MetaAnlandTransport *transport, ++ const struct InputEvent *event); ++void meta_anland_input_reset (MetaAnlandInput *input); +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index 3f491b1768..59b5b988a3 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -4,6 +4,7 @@ + + #include "backends/anland/meta-backend-anland.h" + ++#include "backends/anland/meta-anland-input.h" + #include "backends/anland/vendor/meta-anland-transport.h" + #include "backends/meta-backend-private.h" + #include "backends/meta-fd-source.h" +@@ -17,6 +18,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -40,8 +42,10 @@ struct _MetaBackendAnland + MetaVirtualMonitor *virtual_monitor; + struct screen_info screen_info; + guint reconnect_source_id; ++ MetaAnlandInput *input; + + GSource *buffer_ready_source; ++ GSource *input_source; + MetaStageView *stage_view; + CoglFramebuffer *placeholder_framebuffer; + CoglFramebuffer *buffer_framebuffers[MAX_BUFS]; +@@ -58,6 +62,7 @@ struct _MetaBackendAnland + G_DEFINE_TYPE (MetaBackendAnland, meta_backend_anland, META_TYPE_BACKEND_NATIVE) + + static void deactivate_consumer (MetaBackendAnland *backend); ++static void schedule_reconnect (MetaBackendAnland *backend); + + static char * + get_default_socket_path (void) +@@ -306,6 +311,15 @@ complete_pending_frame (MetaBackendAnland *backend) + static void + deactivate_consumer (MetaBackendAnland *backend) + { ++ if (backend->input_source) ++ { ++ g_source_destroy (backend->input_source); ++ g_clear_pointer (&backend->input_source, g_source_unref); ++ } ++ ++ if (backend->input) ++ meta_anland_input_reset (backend->input); ++ + if (backend->buffer_ready_source) + { + g_source_destroy (backend->buffer_ready_source); +@@ -384,6 +398,40 @@ on_stage_view_present (ClutterStageView *stage_view, + return TRUE; + } + ++static gboolean ++input_source_prepare (gpointer user_data) ++{ ++ return FALSE; ++} ++ ++static gboolean ++input_source_dispatch (gpointer user_data) ++{ ++ MetaBackendAnland *backend = META_BACKEND_ANLAND (user_data); ++ struct InputEvent event; ++ int result; ++ ++ if (!backend->consumer_active || !backend->transport || !backend->input || ++ meta_anland_transport_is_fallback (backend->transport)) ++ return G_SOURCE_REMOVE; ++ ++ while ((result = meta_anland_transport_poll_input_event ( ++ backend->transport, &event, 0)) > 0) ++ { ++ if (!meta_anland_input_handle_event (backend->input, ++ backend->transport, &event)) ++ { ++ meta_anland_transport_enter_fallback (backend->transport); ++ return G_SOURCE_REMOVE; ++ } ++ ++ if (!backend->consumer_active) ++ return G_SOURCE_REMOVE; ++ } ++ ++ return result < 0 ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE; ++} ++ + static gboolean + buffer_ready_source_prepare (gpointer user_data) + { +@@ -444,7 +492,9 @@ activate_consumer (MetaBackendAnland *backend) + struct buf_info buffer_info; + int n_buffers; + int buffer_ready_fd; +- int source_fd; ++ int buffer_ready_source_fd = -1; ++ int input_fd; ++ int input_source_fd = -1; + int i; + g_autoptr (GError) error = NULL; + +@@ -474,6 +524,12 @@ activate_consumer (MetaBackendAnland *backend) + (int) buffer_info.height) + return FALSE; + ++ if (backend->screen_info.refresh > 0 && ++ fabsf (clutter_stage_view_get_refresh_rate ( ++ CLUTTER_STAGE_VIEW (backend->stage_view)) - ++ backend->screen_info.refresh / 1000.0f) > 0.01f) ++ return FALSE; ++ + for (i = 0; i < n_buffers; i++) + { + int fd; +@@ -500,14 +556,23 @@ activate_consumer (MetaBackendAnland *backend) + } + + buffer_ready_fd = meta_anland_transport_get_buffer_ready_fd (backend->transport); +- source_fd = fcntl (buffer_ready_fd, F_DUPFD_CLOEXEC, 3); +- if (source_fd < 0) ++ buffer_ready_source_fd = fcntl (buffer_ready_fd, F_DUPFD_CLOEXEC, 3); ++ if (buffer_ready_source_fd < 0) + { + g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (errno), + "Failed to duplicate Anland buffer-ready eventfd"); + goto failed; + } + ++ input_fd = meta_anland_transport_get_data_fd (backend->transport); ++ input_source_fd = fcntl (input_fd, F_DUPFD_CLOEXEC, 3); ++ if (input_source_fd < 0) ++ { ++ g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (errno), ++ "Failed to duplicate Anland data fd"); ++ goto failed; ++ } ++ + for (i = 0; i < n_buffers; i++) + backend->buffer_framebuffers[i] = framebuffers[i]; + backend->consumer_active = TRUE; +@@ -516,9 +581,13 @@ activate_consumer (MetaBackendAnland *backend) + clutter_stage_view_set_present_func (CLUTTER_STAGE_VIEW (backend->stage_view), + on_stage_view_present, backend); + backend->buffer_ready_source = meta_create_fd_source ( +- source_fd, "[mutter] Anland buffer ready", ++ buffer_ready_source_fd, "[mutter] Anland buffer ready", + buffer_ready_source_prepare, buffer_ready_source_dispatch, backend, NULL); + g_source_attach (backend->buffer_ready_source, NULL); ++ backend->input_source = meta_create_fd_source ( ++ input_source_fd, "[mutter] Anland input", ++ input_source_prepare, input_source_dispatch, backend, NULL); ++ g_source_attach (backend->input_source, NULL); + return TRUE; + + failed: +@@ -526,6 +595,10 @@ failed: + error ? error->message : "invalid transport state"); + for (i = 0; i < MAX_BUFS; i++) + g_clear_object (&framebuffers[i]); ++ if (buffer_ready_source_fd >= 0) ++ close (buffer_ready_source_fd); ++ if (input_source_fd >= 0) ++ close (input_source_fd); + return FALSE; + } + +@@ -550,6 +623,26 @@ on_transport_fallback (void *user_data) + schedule_reconnect (backend); + } + ++static void ++on_input_display_refresh (uint32_t refresh_mhz, ++ gpointer user_data) ++{ ++ MetaBackendAnland *backend = META_BACKEND_ANLAND (user_data); ++ ++ if (backend->screen_info.refresh == refresh_mhz) ++ return; ++ ++ backend->screen_info.refresh = refresh_mhz; ++ deactivate_consumer (backend); ++ if (!update_virtual_monitor (backend, NULL)) ++ { ++ clear_transport (backend); ++ return; ++ } ++ ++ schedule_reconnect (backend); ++} ++ + static gboolean + reconnect_cb (gpointer user_data) + { +@@ -600,6 +693,11 @@ meta_backend_anland_init_post (MetaBackend *backend, + if (!parent_class->init_post (backend, error)) + return FALSE; + ++ backend_anland->input = meta_anland_input_new ( ++ backend, on_input_display_refresh, backend_anland, error); ++ if (!backend_anland->input) ++ return FALSE; ++ + if (!connect_transport (backend_anland, error)) + return FALSE; + +@@ -643,6 +741,7 @@ meta_backend_anland_dispose (GObject *object) + g_clear_handle_id (&backend->reconnect_source_id, g_source_remove); + clear_transport (backend); + uninhibit_frame_clock (backend); ++ g_clear_pointer (&backend->input, meta_anland_input_free); + g_clear_object (&backend->placeholder_framebuffer); + g_clear_object (&backend->stage_view); + g_clear_object (&backend->virtual_monitor); +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 0a6aae3cf0..68e2b0468f 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -976,6 +976,66 @@ meta_seat_impl_notify_absolute_motion_in_impl (MetaSeatImpl *seat_impl, + queue_event (seat_impl, event); + } + ++void ++meta_seat_impl_notify_absolute_relative_motion_in_impl (MetaSeatImpl *seat_impl, ++ ClutterInputDevice *input_device, ++ uint64_t time_us, ++ float x, ++ float y, ++ float dx, ++ float dy, ++ float dx_unaccel, ++ float dy_unaccel, ++ double *axes) ++{ ++ MetaInputDeviceNative *device_native = ++ META_INPUT_DEVICE_NATIVE (input_device); ++ ClutterModifierType modifiers; ++ ClutterEvent *event; ++ float cur_x, cur_y; ++ float dx_constrained, dy_constrained; ++ ++ if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE) ++ { ++ meta_input_device_native_get_coords_in_impl (device_native, &cur_x, &cur_y); ++ modifiers = device_native->button_state; ++ } ++ else ++ { ++ meta_input_device_native_get_coords_in_impl ( ++ META_INPUT_DEVICE_NATIVE (seat_impl->core_pointer), &cur_x, &cur_y); ++ modifiers = seat_impl->button_state; ++ } ++ ++ constrain_coordinates (seat_impl, input_device, time_us, x, y, &x, &y); ++ dx_constrained = x - cur_x; ++ dy_constrained = y - cur_y; ++ update_device_coords_in_impl (seat_impl, input_device, ++ GRAPHENE_POINT_INIT (x, y)); ++ ++ modifiers |= xkb_state_serialize_mods (seat_impl->xkb, XKB_STATE_MODS_EFFECTIVE); ++ ++ g_signal_emit (seat_impl, signals[POINTER_POSITION_CHANGED_IN_IMPL], 0, ++ &GRAPHENE_POINT_INIT (seat_impl->pointer_x, ++ seat_impl->pointer_y)); ++ ++ event = ++ clutter_event_motion_new (CLUTTER_EVENT_FLAG_RELATIVE_MOTION, ++ time_us, ++ input_device, ++ device_native->last_tool, ++ modifiers, ++ GRAPHENE_POINT_INIT (x, y), ++ GRAPHENE_POINT_INIT (dx, dy), ++ GRAPHENE_POINT_INIT (dx_unaccel, ++ dy_unaccel), ++ GRAPHENE_POINT_INIT (dx_constrained, ++ dy_constrained), ++ axes); ++ ++ queue_event (seat_impl, event); ++} ++ + void + meta_seat_impl_notify_button_in_impl (MetaSeatImpl *seat_impl, + ClutterInputDevice *input_device, +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index 24d0405062..a995244f31 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -147,6 +147,17 @@ void meta_seat_impl_notify_absolute_motion_in_impl (MetaSeatImpl *seat_imp + float y, + double *axes); + ++void meta_seat_impl_notify_absolute_relative_motion_in_impl (MetaSeatImpl *seat_impl, ++ ClutterInputDevice *input_device, ++ uint64_t time_us, ++ float x, ++ float y, ++ float dx, ++ float dy, ++ float dx_unaccel, ++ float dy_unaccel, ++ double *axes); ++ + void meta_seat_impl_notify_button_in_impl (MetaSeatImpl *seat_impl, + ClutterInputDevice *input_device, + uint64_t time_us, +diff --git a/src/backends/native/meta-virtual-input-device-native.c b/src/backends/native/meta-virtual-input-device-native.c +index e33925d7ba..20536dbb08 100644 +--- a/src/backends/native/meta-virtual-input-device-native.c ++++ b/src/backends/native/meta-virtual-input-device-native.c +@@ -64,6 +64,15 @@ typedef struct + double y; + } MetaVirtualEventMotion; + ++typedef struct ++{ ++ uint64_t time_us; ++ double x; ++ double y; ++ double dx; ++ double dy; ++} MetaVirtualEventAbsoluteRelativeMotion; ++ + typedef struct + { + uint64_t time_us; +@@ -96,6 +105,12 @@ typedef struct + double y; + } MetaVirtualEventTouch; + ++typedef struct ++{ ++ uint64_t time_us; ++ GArray *events; ++} MetaVirtualEventTouchBatch; ++ + G_DEFINE_TYPE (MetaVirtualInputDeviceNative, + meta_virtual_input_device_native, + CLUTTER_TYPE_VIRTUAL_INPUT_DEVICE) +@@ -312,6 +327,57 @@ meta_virtual_input_device_native_notify_absolute_motion (ClutterVirtualInputDevi + g_object_unref (task); + } + ++static gboolean ++notify_absolute_relative_motion_in_impl (GTask *task) ++{ ++ MetaVirtualInputDeviceNative *virtual_native = ++ g_task_get_source_object (task); ++ MetaSeatNative *seat_native = ++ meta_virtual_input_device_native_get_seat_native (virtual_native); ++ MetaVirtualEventAbsoluteRelativeMotion *event = g_task_get_task_data (task); ++ ++ if (event->time_us == CLUTTER_CURRENT_TIME) ++ event->time_us = g_get_monotonic_time (); ++ ++ meta_seat_impl_notify_absolute_relative_motion_in_impl ( ++ seat_native->impl, virtual_native->impl_state->device, event->time_us, ++ (float) event->x, (float) event->y, (float) event->dx, (float) event->dy, ++ (float) event->dx, (float) event->dy, NULL); ++ g_task_return_boolean (task, TRUE); ++ return G_SOURCE_REMOVE; ++} ++ ++void ++meta_virtual_input_device_native_notify_absolute_relative_motion ( ++ MetaVirtualInputDeviceNative *virtual_native, ++ uint64_t time_us, ++ double x, ++ double y, ++ double dx, ++ double dy) ++{ ++ MetaSeatNative *seat_native; ++ MetaVirtualEventAbsoluteRelativeMotion *event; ++ GTask *task; ++ ++ g_return_if_fail (META_IS_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_native)); ++ g_return_if_fail (virtual_native->impl_state != NULL); ++ ++ seat_native = meta_virtual_input_device_native_get_seat_native (virtual_native); ++ event = g_new0 (MetaVirtualEventAbsoluteRelativeMotion, 1); ++ event->time_us = time_us; ++ event->x = x; ++ event->y = y; ++ event->dx = dx; ++ event->dy = dy; ++ ++ task = g_task_new (virtual_native, NULL, NULL, NULL); ++ g_task_set_task_data (task, event, g_free); ++ meta_seat_impl_run_input_task (seat_native->impl, task, ++ (GSourceFunc) notify_absolute_relative_motion_in_impl); ++ g_object_unref (task); ++} ++ + static gboolean + notify_button_in_impl (GTask *task) + { +@@ -970,6 +1036,123 @@ meta_virtual_input_device_native_notify_touch_up (ClutterVirtualInputDevice *vir + g_object_unref (task); + } + ++static gboolean ++notify_touch_events_in_impl (GTask *task) ++{ ++ MetaVirtualInputDeviceNative *virtual_native = ++ g_task_get_source_object (task); ++ MetaSeatNative *seat_native = ++ meta_virtual_input_device_native_get_seat_native (virtual_native); ++ MetaSeatImpl *seat = seat_native->impl; ++ MetaVirtualEventTouchBatch *batch = g_task_get_task_data (task); ++ GArray *events = batch->events; ++ uint64_t time_us = batch->time_us; ++ guint i; ++ ++ for (i = 0; i < events->len; i++) ++ { ++ const MetaVirtualTouchEvent *event = ++ &g_array_index (events, MetaVirtualTouchEvent, i); ++ MetaTouchState *touch_state; ++ int device_slot = virtual_native->slot_base + event->slot; ++ ++ switch (event->type) ++ { ++ case META_VIRTUAL_TOUCH_EVENT_DOWN: ++ touch_state = meta_seat_impl_acquire_touch_state_in_impl (seat, ++ device_slot); ++ if (!touch_state) ++ continue; ++ ++ touch_state->coords.x = (float) event->x; ++ touch_state->coords.y = (float) event->y; ++ meta_seat_impl_notify_touch_event_in_impl ( ++ seat, virtual_native->impl_state->device, CLUTTER_TOUCH_BEGIN, ++ time_us, touch_state->seat_slot, touch_state->coords.x, ++ touch_state->coords.y); ++ break; ++ case META_VIRTUAL_TOUCH_EVENT_MOTION: ++ touch_state = meta_seat_impl_lookup_touch_state_in_impl (seat, ++ device_slot); ++ if (!touch_state) ++ continue; ++ ++ touch_state->coords.x = (float) event->x; ++ touch_state->coords.y = (float) event->y; ++ meta_seat_impl_notify_touch_event_in_impl ( ++ seat, virtual_native->impl_state->device, CLUTTER_TOUCH_UPDATE, ++ time_us, touch_state->seat_slot, touch_state->coords.x, ++ touch_state->coords.y); ++ break; ++ case META_VIRTUAL_TOUCH_EVENT_UP: ++ case META_VIRTUAL_TOUCH_EVENT_CANCEL: ++ touch_state = meta_seat_impl_lookup_touch_state_in_impl (seat, ++ device_slot); ++ if (!touch_state) ++ continue; ++ ++ meta_seat_impl_notify_touch_event_in_impl ( ++ seat, virtual_native->impl_state->device, ++ event->type == META_VIRTUAL_TOUCH_EVENT_UP ? CLUTTER_TOUCH_END : ++ CLUTTER_TOUCH_CANCEL, ++ time_us, touch_state->seat_slot, touch_state->coords.x, ++ touch_state->coords.y); ++ meta_seat_impl_release_touch_state_in_impl (seat, ++ touch_state->seat_slot); ++ break; ++ } ++ } ++ ++ g_task_return_boolean (task, TRUE); ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++meta_virtual_event_touch_batch_free (MetaVirtualEventTouchBatch *batch) ++{ ++ g_clear_pointer (&batch->events, g_array_unref); ++ g_free (batch); ++} ++ ++void ++meta_virtual_input_device_native_notify_touch_events ( ++ MetaVirtualInputDeviceNative *virtual_native, ++ uint64_t time_us, ++ const MetaVirtualTouchEvent *events, ++ gsize n_events) ++{ ++ MetaSeatNative *seat_native; ++ MetaVirtualEventTouchBatch *batch; ++ GArray *copied_events; ++ GTask *task; ++ gsize i; ++ ++ g_return_if_fail (META_IS_VIRTUAL_INPUT_DEVICE_NATIVE (virtual_native)); ++ g_return_if_fail (virtual_native->impl_state != NULL); ++ g_return_if_fail (n_events > 0); ++ g_return_if_fail (events != NULL); ++ ++ copied_events = g_array_sized_new (FALSE, FALSE, sizeof (*events), n_events); ++ for (i = 0; i < n_events; i++) ++ { ++ g_return_if_fail (events[i].slot >= 0 && ++ events[i].slot < CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS); ++ g_array_append_val (copied_events, events[i]); ++ } ++ ++ seat_native = meta_virtual_input_device_native_get_seat_native (virtual_native); ++ batch = g_new0 (MetaVirtualEventTouchBatch, 1); ++ batch->time_us = time_us == CLUTTER_CURRENT_TIME ? ++ g_get_monotonic_time () : time_us; ++ batch->events = copied_events; ++ task = g_task_new (virtual_native, NULL, NULL, NULL); ++ g_task_set_task_data (task, batch, ++ (GDestroyNotify) meta_virtual_event_touch_batch_free); ++ meta_seat_impl_run_input_task (seat_native->impl, task, ++ (GSourceFunc) notify_touch_events_in_impl); ++ g_object_unref (task); ++} ++ + static void + meta_virtual_input_device_native_get_property (GObject *object, + guint prop_id, +diff --git a/src/backends/native/meta-virtual-input-device-native.h b/src/backends/native/meta-virtual-input-device-native.h +index a41b5ed294..149c989282 100644 +--- a/src/backends/native/meta-virtual-input-device-native.h ++++ b/src/backends/native/meta-virtual-input-device-native.h +@@ -19,6 +19,8 @@ + + #pragma once + ++#include ++ + #include "clutter/clutter-virtual-input-device.h" + + #define META_TYPE_VIRTUAL_INPUT_DEVICE_NATIVE (meta_virtual_input_device_native_get_type ()) +@@ -26,3 +28,33 @@ G_DECLARE_FINAL_TYPE (MetaVirtualInputDeviceNative, + meta_virtual_input_device_native, + META, VIRTUAL_INPUT_DEVICE_NATIVE, + ClutterVirtualInputDevice) ++ ++typedef enum ++{ ++ META_VIRTUAL_TOUCH_EVENT_DOWN, ++ META_VIRTUAL_TOUCH_EVENT_MOTION, ++ META_VIRTUAL_TOUCH_EVENT_UP, ++ META_VIRTUAL_TOUCH_EVENT_CANCEL, ++} MetaVirtualTouchEventType; ++ ++typedef struct ++{ ++ MetaVirtualTouchEventType type; ++ int slot; ++ double x; ++ double y; ++} MetaVirtualTouchEvent; ++ ++void meta_virtual_input_device_native_notify_absolute_relative_motion ( ++ MetaVirtualInputDeviceNative *virtual_device, ++ uint64_t time_us, ++ double x, ++ double y, ++ double dx, ++ double dy); ++ ++void meta_virtual_input_device_native_notify_touch_events ( ++ MetaVirtualInputDeviceNative *virtual_device, ++ uint64_t time_us, ++ const MetaVirtualTouchEvent *events, ++ gsize n_events); +diff --git a/src/meson.build b/src/meson.build +index 2fc372b617..e61bea7cdc 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -927,6 +927,8 @@ if have_anland + mutter_sources += [ + 'backends/anland/meta-backend-anland.c', + 'backends/anland/meta-backend-anland.h', ++ 'backends/anland/meta-anland-input.c', ++ 'backends/anland/meta-anland-input.h', + 'backends/anland/vendor/meta-anland-socket-utils.c', + 'backends/anland/vendor/meta-anland-socket-utils.h', + 'backends/anland/vendor/meta-anland-transport.c', +diff --git a/src/tests/anland-transport-tests.c b/src/tests/anland-transport-tests.c +index 0e17cec208..77c39e376a 100644 +--- a/src/tests/anland-transport-tests.c ++++ b/src/tests/anland-transport-tests.c +@@ -358,6 +358,30 @@ test_transport_and_reconnect (void) + sizeof (received_text), 100), ==, 1); + g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); + ++ event = (struct InputEvent) { ++ .type = INPUT_TYPE_POINTER_MOTION, ++ .pointer_motion = { ++ .x = 640.0f, ++ .y = 360.0f, ++ .dx = -2.5f, ++ .dy = 1.5f, ++ }, ++ }; ++ send_data_message (peers.data, DATA_MSG_INPUT_EVENT, &event, sizeof (event)); ++ g_assert_cmpint (meta_anland_transport_poll_input_event (transport, &received_event, 100), ==, 1); ++ g_assert_cmpuint (received_event.type, ==, INPUT_TYPE_POINTER_MOTION); ++ g_assert_cmpfloat (received_event.pointer_motion.x, ==, 640.0f); ++ g_assert_cmpfloat (received_event.pointer_motion.dy, ==, 1.5f); ++ ++ event = (struct InputEvent) { ++ .type = INPUT_TYPE_DISPLAY_REFRESH, ++ .display = { .refresh_mhz = 90000 }, ++ }; ++ send_data_message (peers.data, DATA_MSG_INPUT_EVENT, &event, sizeof (event)); ++ g_assert_cmpint (meta_anland_transport_poll_input_event (transport, &received_event, 100), ==, 1); ++ g_assert_cmpuint (received_event.type, ==, INPUT_TYPE_DISPLAY_REFRESH); ++ g_assert_cmpuint (received_event.display.refresh_mhz, ==, 90000); ++ + output.type = OUTPUT_TYPE_CLIPBOARD; + output.clipboard.size = sizeof (text); + g_assert_true (meta_anland_transport_send_output_event (transport, &output, diff --git a/x11-packages/mutter/0015-backends-anland-Bridge-clipboard-and-text-input.patch b/x11-packages/mutter/0015-backends-anland-Bridge-clipboard-and-text-input.patch new file mode 100644 index 00000000000..9c7c4948632 --- /dev/null +++ b/x11-packages/mutter/0015-backends-anland-Bridge-clipboard-and-text-input.patch @@ -0,0 +1,787 @@ +From 55dcfaeb115c3d7c8cdecd55d768299a0af3f0bc Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 22:02:49 +0800 +Subject: [PATCH 15/27] backends/anland: Bridge clipboard and text input + +Bridge Anland clipboard payloads through MetaSelection and forward local +selection changes to the active consumer without echoing remote updates. + +Commit UTF-8 text to focused, enabled Wayland text-input clients, retain the +key-event fallback, and cancel pending selection work on fallback or reconnect. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0006-backends-anland-Bridge-clipboard-and-text-input.patch + +diff --git a/src/backends/anland/meta-anland-clipboard.c b/src/backends/anland/meta-anland-clipboard.c +new file mode 100644 +index 0000000000..a3bd5dd948 +--- /dev/null ++++ b/src/backends/anland/meta-anland-clipboard.c +@@ -0,0 +1,317 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#include "config.h" ++ ++#include "backends/anland/meta-anland-clipboard.h" ++ ++#include "backends/anland/vendor/meta-anland-transport.h" ++#include "meta/display.h" ++#include "meta/meta-backend.h" ++#include "meta/meta-context.h" ++#include "meta/meta-selection.h" ++#include "meta/meta-selection-source-memory.h" ++ ++#include ++ ++#define ANLAND_CLIPBOARD_LIMIT (1024 * 1024) ++ ++typedef struct ++{ ++ MetaAnlandClipboard *clipboard; ++ guint generation; ++ GOutputStream *output; ++} ClipboardTransfer; ++ ++struct _MetaAnlandClipboard ++{ ++ int ref_count; ++ MetaSelection *selection; ++ MetaSelectionSource *source; ++ MetaAnlandTransport *transport; ++ GCancellable *transfer_cancellable; ++ GBytes *cached_contents; ++ guint generation; ++ gulong owner_changed_id; ++ gboolean disposed; ++}; ++ ++static MetaAnlandClipboard * ++meta_anland_clipboard_ref (MetaAnlandClipboard *clipboard) ++{ ++ clipboard->ref_count++; ++ return clipboard; ++} ++ ++static void ++meta_anland_clipboard_unref (MetaAnlandClipboard *clipboard) ++{ ++ if (--clipboard->ref_count != 0) ++ return; ++ ++ g_clear_object (&clipboard->transfer_cancellable); ++ g_clear_object (&clipboard->source); ++ g_clear_pointer (&clipboard->cached_contents, g_bytes_unref); ++ g_clear_object (&clipboard->selection); ++ g_free (clipboard); ++} ++ ++static gboolean ++contents_are_valid (GBytes *contents) ++{ ++ const char *data; ++ gsize size; ++ ++ data = g_bytes_get_data (contents, &size); ++ if (size == 0) ++ return TRUE; ++ ++ return size <= ANLAND_CLIPBOARD_LIMIT && ++ !memchr (data, '\0', size) && ++ g_utf8_validate (data, size, NULL); ++} ++ ++static gboolean ++contents_equal (GBytes *a, ++ GBytes *b) ++{ ++ return a && b && g_bytes_equal (a, b); ++} ++ ++static void ++send_contents (MetaAnlandClipboard *clipboard, ++ GBytes *contents) ++{ ++ struct OutputEvent event = { ++ .type = OUTPUT_TYPE_CLIPBOARD, ++ }; ++ const void *data; ++ gsize size; ++ ++ if (!clipboard->transport || ++ meta_anland_transport_is_fallback (clipboard->transport)) ++ return; ++ ++ data = g_bytes_get_data (contents, &size); ++ event.clipboard.size = size; ++ meta_anland_transport_send_output_event (clipboard->transport, &event, ++ data, size); ++} ++ ++static void ++set_cached_contents (MetaAnlandClipboard *clipboard, ++ GBytes *contents) ++{ ++ if (contents_equal (clipboard->cached_contents, contents)) ++ return; ++ ++ g_clear_pointer (&clipboard->cached_contents, g_bytes_unref); ++ clipboard->cached_contents = g_bytes_ref (contents); ++} ++ ++static void ++clipboard_transfer_free (ClipboardTransfer *transfer) ++{ ++ g_clear_object (&transfer->output); ++ meta_anland_clipboard_unref (transfer->clipboard); ++ g_free (transfer); ++} ++ ++static void ++on_selection_transfer_complete (MetaSelection *selection, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ ClipboardTransfer *transfer = user_data; ++ MetaAnlandClipboard *clipboard = transfer->clipboard; ++ g_autoptr (GError) error = NULL; ++ g_autoptr (GBytes) contents = NULL; ++ ++ if (!meta_selection_transfer_finish (selection, result, &error)) ++ { ++ if (!error || !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ g_warning ("Anland failed to transfer clipboard: %s", ++ error ? error->message : "unknown error"); ++ goto out; ++ } ++ ++ if (clipboard->disposed || transfer->generation != clipboard->generation) ++ goto out; ++ ++ g_output_stream_close (transfer->output, NULL, NULL); ++ contents = g_memory_output_stream_steal_as_bytes ( ++ G_MEMORY_OUTPUT_STREAM (transfer->output)); ++ if (!contents_are_valid (contents)) ++ { ++ g_warning ("Anland ignored invalid or oversized clipboard contents"); ++ goto out; ++ } ++ ++ set_cached_contents (clipboard, contents); ++ send_contents (clipboard, contents); ++ ++out: ++ clipboard_transfer_free (transfer); ++} ++ ++static const char * ++choose_mimetype (MetaSelection *selection) ++{ ++ GList *mimetypes; ++ GList *l; ++ const char *chosen = NULL; ++ ++ mimetypes = meta_selection_get_mimetypes (selection, ++ META_SELECTION_CLIPBOARD); ++ for (l = mimetypes; l; l = l->next) ++ { ++ if (g_str_equal (l->data, "text/plain;charset=utf-8")) ++ { ++ chosen = "text/plain;charset=utf-8"; ++ break; ++ } ++ ++ if (!chosen && g_str_equal (l->data, "text/plain")) ++ chosen = "text/plain"; ++ } ++ g_list_free_full (mimetypes, g_free); ++ ++ return chosen; ++} ++ ++static void ++cancel_selection_transfer (MetaAnlandClipboard *clipboard) ++{ ++ if (clipboard->transfer_cancellable) ++ g_cancellable_cancel (clipboard->transfer_cancellable); ++ g_clear_object (&clipboard->transfer_cancellable); ++} ++ ++static void ++on_selection_owner_changed (MetaSelection *selection, ++ MetaSelectionType selection_type, ++ MetaSelectionSource *owner, ++ MetaAnlandClipboard *clipboard) ++{ ++ ClipboardTransfer *transfer; ++ const char *mimetype; ++ GOutputStream *output; ++ ++ if (selection_type != META_SELECTION_CLIPBOARD || clipboard->disposed) ++ return; ++ ++ cancel_selection_transfer (clipboard); ++ clipboard->generation++; ++ ++ if (owner == clipboard->source) ++ return; ++ ++ g_clear_object (&clipboard->source); ++ if (!owner) ++ { ++ g_autoptr (GBytes) empty = g_bytes_new_static ("", 0); ++ ++ set_cached_contents (clipboard, empty); ++ send_contents (clipboard, empty); ++ return; ++ } ++ ++ mimetype = choose_mimetype (selection); ++ if (!mimetype) ++ return; ++ ++ output = g_memory_output_stream_new_resizable (); ++ clipboard->transfer_cancellable = g_cancellable_new (); ++ transfer = g_new0 (ClipboardTransfer, 1); ++ transfer->clipboard = meta_anland_clipboard_ref (clipboard); ++ transfer->generation = clipboard->generation; ++ transfer->output = g_object_ref (output); ++ meta_selection_transfer_async (selection, META_SELECTION_CLIPBOARD, mimetype, ++ ANLAND_CLIPBOARD_LIMIT, output, ++ clipboard->transfer_cancellable, ++ (GAsyncReadyCallback) on_selection_transfer_complete, ++ transfer); ++ g_object_unref (output); ++} ++ ++MetaAnlandClipboard * ++meta_anland_clipboard_new (MetaBackend *backend) ++{ ++ MetaAnlandClipboard *clipboard; ++ MetaContext *context; ++ MetaDisplay *display; ++ ++ context = meta_backend_get_context (backend); ++ display = meta_context_get_display (context); ++ if (!display) ++ return NULL; ++ ++ clipboard = g_new0 (MetaAnlandClipboard, 1); ++ clipboard->ref_count = 1; ++ clipboard->selection = g_object_ref (meta_display_get_selection (display)); ++ clipboard->owner_changed_id = g_signal_connect_after ( ++ clipboard->selection, "owner-changed", ++ G_CALLBACK (on_selection_owner_changed), clipboard); ++ return clipboard; ++} ++ ++void ++meta_anland_clipboard_free (MetaAnlandClipboard *clipboard) ++{ ++ if (!clipboard) ++ return; ++ ++ clipboard->disposed = TRUE; ++ meta_anland_clipboard_set_transport (clipboard, NULL); ++ if (clipboard->owner_changed_id) ++ { ++ g_signal_handler_disconnect (clipboard->selection, ++ clipboard->owner_changed_id); ++ clipboard->owner_changed_id = 0; ++ } ++ meta_anland_clipboard_unref (clipboard); ++} ++ ++void ++meta_anland_clipboard_set_transport (MetaAnlandClipboard *clipboard, ++ MetaAnlandTransport *transport) ++{ ++ if (!clipboard || clipboard->transport == transport) ++ return; ++ ++ clipboard->generation++; ++ cancel_selection_transfer (clipboard); ++ clipboard->transport = transport; ++ ++ if (transport && clipboard->cached_contents) ++ send_contents (clipboard, clipboard->cached_contents); ++} ++ ++gboolean ++meta_anland_clipboard_set_from_consumer (MetaAnlandClipboard *clipboard, ++ GBytes *contents) ++{ ++ g_autoptr (GError) error = NULL; ++ g_autoptr (MetaSelectionSource) source = NULL; ++ ++ if (!clipboard || !contents_are_valid (contents)) ++ return FALSE; ++ ++ if (contents_equal (clipboard->cached_contents, contents)) ++ return TRUE; ++ ++ source = meta_selection_source_memory_new ("text/plain;charset=utf-8", ++ contents, &error); ++ if (!source) ++ { ++ g_warning ("Anland failed to create clipboard source: %s", error->message); ++ return FALSE; ++ } ++ ++ cancel_selection_transfer (clipboard); ++ clipboard->generation++; ++ set_cached_contents (clipboard, contents); ++ g_set_object (&clipboard->source, source); ++ meta_selection_set_owner (clipboard->selection, META_SELECTION_CLIPBOARD, ++ source); ++ return TRUE; ++} +diff --git a/src/backends/anland/meta-anland-clipboard.h b/src/backends/anland/meta-anland-clipboard.h +new file mode 100644 +index 0000000000..f642fb035b +--- /dev/null ++++ b/src/backends/anland/meta-anland-clipboard.h +@@ -0,0 +1,18 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#pragma once ++ ++#include ++ ++typedef struct _MetaAnlandClipboard MetaAnlandClipboard; ++typedef struct _MetaAnlandTransport MetaAnlandTransport; ++typedef struct _MetaBackend MetaBackend; ++ ++MetaAnlandClipboard * meta_anland_clipboard_new (MetaBackend *backend); ++void meta_anland_clipboard_free (MetaAnlandClipboard *clipboard); ++ ++void meta_anland_clipboard_set_transport (MetaAnlandClipboard *clipboard, ++ MetaAnlandTransport *transport); ++ ++gboolean meta_anland_clipboard_set_from_consumer (MetaAnlandClipboard *clipboard, ++ GBytes *contents); +diff --git a/src/backends/anland/meta-anland-input.c b/src/backends/anland/meta-anland-input.c +index 2d0c1b66d2..9e9b104b01 100644 +--- a/src/backends/anland/meta-anland-input.c ++++ b/src/backends/anland/meta-anland-input.c +@@ -160,28 +160,6 @@ flush_touch_batch (MetaAnlandInput *input) + g_array_set_size (input->touch_batch, 0); + } + +-static gboolean +-drain_payload (MetaAnlandTransport *transport, +- uint32_t size) +-{ +- uint8_t buffer[4096]; +- +- if (size > META_ANLAND_MAX_PAYLOAD_SIZE) +- return FALSE; +- +- while (size > 0) +- { +- size_t chunk = MIN ((size_t) size, sizeof (buffer)); +- +- if (meta_anland_transport_read_input_payload (transport, buffer, chunk, +- 100) != 1) +- return FALSE; +- size -= (uint32_t) chunk; +- } +- +- return TRUE; +-} +- + static gboolean + discard_resource_fds (MetaAnlandTransport *transport, + uint32_t expected_fds) +@@ -428,7 +406,7 @@ meta_anland_input_free (MetaAnlandInput *input) + g_free (input); + } + +-gboolean ++MetaAnlandInputEventResult + meta_anland_input_handle_event (MetaAnlandInput *input, + MetaAnlandTransport *transport, + const struct InputEvent *event) +@@ -460,11 +438,11 @@ meta_anland_input_handle_event (MetaAnlandInput *input, + input->refresh_user_data); + break; + case INPUT_TYPE_CLIPBOARD: +- return drain_payload (transport, event->clipboard.size); + case INPUT_TYPE_TEXT_INPUT: +- return drain_payload (transport, event->text_input.size); ++ return META_ANLAND_INPUT_EVENT_NEEDS_PAYLOAD; + case INPUT_TYPE_RESOURCE: +- return discard_resource_fds (transport, event->resource.fdnum); ++ return discard_resource_fds (transport, event->resource.fdnum) ? ++ META_ANLAND_INPUT_EVENT_HANDLED : META_ANLAND_INPUT_EVENT_ERROR; + case INPUT_TYPE_ACTION: + break; + default: +@@ -472,7 +450,54 @@ meta_anland_input_handle_event (MetaAnlandInput *input, + break; + } + +- return TRUE; ++ return META_ANLAND_INPUT_EVENT_HANDLED; ++} ++ ++gboolean ++meta_anland_input_inject_text (MetaAnlandInput *input, ++ const char *text) ++{ ++ const char *cursor = text; ++ gboolean injected = FALSE; ++ ++ if (!input || !input->keyboard || !text) ++ return FALSE; ++ ++ while (*cursor) ++ { ++ gunichar keyval; ++ ++ keyval = g_utf8_get_char_validated (cursor, -1); ++ if (keyval == (gunichar) -1 || keyval == (gunichar) -2) ++ return FALSE; ++ ++ switch (keyval) ++ { ++ case '\n': ++ case '\r': ++ keyval = CLUTTER_KEY_Return; ++ break; ++ case '\t': ++ keyval = CLUTTER_KEY_Tab; ++ break; ++ case '\b': ++ keyval = CLUTTER_KEY_BackSpace; ++ break; ++ default: ++ break; ++ } ++ ++ clutter_virtual_input_device_notify_keyval ( ++ input->keyboard, g_get_monotonic_time (), keyval, ++ CLUTTER_KEY_STATE_PRESSED); ++ clutter_virtual_input_device_notify_keyval ( ++ input->keyboard, g_get_monotonic_time (), keyval, ++ CLUTTER_KEY_STATE_RELEASED); ++ cursor = g_utf8_next_char (cursor); ++ injected = TRUE; ++ } ++ ++ return injected; + } + + void +diff --git a/src/backends/anland/meta-anland-input.h b/src/backends/anland/meta-anland-input.h +index 783e54736e..8b0731bdc8 100644 +--- a/src/backends/anland/meta-anland-input.h ++++ b/src/backends/anland/meta-anland-input.h +@@ -9,6 +9,13 @@ + + typedef struct _MetaAnlandInput MetaAnlandInput; + ++typedef enum ++{ ++ META_ANLAND_INPUT_EVENT_HANDLED, ++ META_ANLAND_INPUT_EVENT_NEEDS_PAYLOAD, ++ META_ANLAND_INPUT_EVENT_ERROR, ++} MetaAnlandInputEventResult; ++ + typedef void (* MetaAnlandInputRefreshFunc) (uint32_t refresh_mhz, + gpointer user_data); + +@@ -18,7 +25,10 @@ MetaAnlandInput * meta_anland_input_new (MetaBackend *backend, + GError **error); + void meta_anland_input_free (MetaAnlandInput *input); + +-gboolean meta_anland_input_handle_event (MetaAnlandInput *input, ++MetaAnlandInputEventResult ++ meta_anland_input_handle_event (MetaAnlandInput *input, + MetaAnlandTransport *transport, + const struct InputEvent *event); ++gboolean meta_anland_input_inject_text (MetaAnlandInput *input, ++ const char *text); + void meta_anland_input_reset (MetaAnlandInput *input); +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index 59b5b988a3..c8a769be2f 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -4,6 +4,7 @@ + + #include "backends/anland/meta-backend-anland.h" + ++#include "backends/anland/meta-anland-clipboard.h" + #include "backends/anland/meta-anland-input.h" + #include "backends/anland/vendor/meta-anland-transport.h" + #include "backends/meta-backend-private.h" +@@ -15,10 +16,14 @@ + #include "backends/meta-virtual-monitor.h" + #include "backends/native/meta-renderer-native-private.h" + #include "meta/meta-backend.h" ++#include "meta/meta-context.h" ++#include "meta/meta-wayland-compositor.h" ++#include "wayland/meta-wayland.h" + + #include + #include + #include ++#include + #include + #include + #include +@@ -43,6 +48,7 @@ struct _MetaBackendAnland + struct screen_info screen_info; + guint reconnect_source_id; + MetaAnlandInput *input; ++ MetaAnlandClipboard *clipboard; + + GSource *buffer_ready_source; + GSource *input_source; +@@ -64,6 +70,95 @@ G_DEFINE_TYPE (MetaBackendAnland, meta_backend_anland, META_TYPE_BACKEND_NATIVE) + static void deactivate_consumer (MetaBackendAnland *backend); + static void schedule_reconnect (MetaBackendAnland *backend); + ++static void ++on_context_started (MetaContext *context, ++ MetaBackendAnland *backend) ++{ ++ backend->clipboard = meta_anland_clipboard_new (META_BACKEND (backend)); ++ if (!backend->clipboard) ++ g_warning ("Failed to initialize Anland clipboard bridge"); ++} ++ ++static gboolean ++read_text_payload (MetaBackendAnland *backend, ++ uint32_t size, ++ const char *name, ++ GBytes **contents) ++{ ++ g_autofree char *text = NULL; ++ ++ *contents = NULL; ++ if (size > META_ANLAND_MAX_PAYLOAD_SIZE) ++ { ++ g_warning ("Anland rejected oversized %s payload", name); ++ return FALSE; ++ } ++ ++ text = g_malloc (size + 1); ++ if (meta_anland_transport_read_input_payload (backend->transport, text, ++ size, 100) != 1) ++ return FALSE; ++ text[size] = '\0'; ++ ++ if (memchr (text, '\0', size) || !g_utf8_validate (text, size, NULL)) ++ { ++ g_warning ("Anland ignored invalid UTF-8 %s payload", name); ++ return TRUE; ++ } ++ ++ *contents = g_bytes_new (text, size); ++ return TRUE; ++} ++ ++static gboolean ++handle_extended_input (MetaBackendAnland *backend, ++ const struct InputEvent *event) ++{ ++ g_autoptr (GBytes) contents = NULL; ++ const char *text; ++ guint size; ++ ++ switch (event->type) ++ { ++ case INPUT_TYPE_CLIPBOARD: ++ if (!read_text_payload (backend, event->clipboard.size, "clipboard", ++ &contents)) ++ return FALSE; ++ break; ++ case INPUT_TYPE_TEXT_INPUT: ++ if (!read_text_payload (backend, event->text_input.size, "text input", ++ &contents)) ++ return FALSE; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ if (!contents) ++ return TRUE; ++ ++ size = g_bytes_get_size (contents); ++ if (size == 0) ++ return TRUE; ++ ++ text = g_bytes_get_data (contents, NULL); ++ if (event->type == INPUT_TYPE_CLIPBOARD) ++ return meta_anland_clipboard_set_from_consumer (backend->clipboard, ++ contents); ++ ++ { ++ MetaContext *context = meta_backend_get_context (META_BACKEND (backend)); ++ MetaWaylandCompositor *compositor = ++ meta_context_get_wayland_compositor (context); ++ ++ if (compositor && meta_wayland_text_input_commit_string ( ++ meta_wayland_compositor_get_text_input (compositor), text)) ++ return TRUE; ++ } ++ ++ return meta_anland_input_inject_text (backend->input, text); ++} ++ + static char * + get_default_socket_path (void) + { +@@ -311,6 +406,8 @@ complete_pending_frame (MetaBackendAnland *backend) + static void + deactivate_consumer (MetaBackendAnland *backend) + { ++ meta_anland_clipboard_set_transport (backend->clipboard, NULL); ++ + if (backend->input_source) + { + g_source_destroy (backend->input_source); +@@ -418,8 +515,20 @@ input_source_dispatch (gpointer user_data) + while ((result = meta_anland_transport_poll_input_event ( + backend->transport, &event, 0)) > 0) + { +- if (!meta_anland_input_handle_event (backend->input, +- backend->transport, &event)) ++ MetaAnlandInputEventResult input_result; ++ ++ input_result = meta_anland_input_handle_event (backend->input, ++ backend->transport, ++ &event); ++ if (input_result == META_ANLAND_INPUT_EVENT_NEEDS_PAYLOAD) ++ { ++ if (!handle_extended_input (backend, &event)) ++ { ++ meta_anland_transport_enter_fallback (backend->transport); ++ return G_SOURCE_REMOVE; ++ } ++ } ++ else if (input_result == META_ANLAND_INPUT_EVENT_ERROR) + { + meta_anland_transport_enter_fallback (backend->transport); + return G_SOURCE_REMOVE; +@@ -518,6 +627,13 @@ activate_consumer (MetaBackendAnland *backend) + if (!ensure_stage_view (backend, &error)) + return FALSE; + ++ if (!backend->clipboard) ++ { ++ g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Anland clipboard bridge is unavailable"); ++ goto failed; ++ } ++ + if (cogl_framebuffer_get_width (backend->placeholder_framebuffer) != + (int) buffer_info.width || + cogl_framebuffer_get_height (backend->placeholder_framebuffer) != +@@ -576,6 +692,7 @@ activate_consumer (MetaBackendAnland *backend) + for (i = 0; i < n_buffers; i++) + backend->buffer_framebuffers[i] = framebuffers[i]; + backend->consumer_active = TRUE; ++ meta_anland_clipboard_set_transport (backend->clipboard, backend->transport); + backend->current_buffer = -1; + inhibit_frame_clock (backend); + clutter_stage_view_set_present_func (CLUTTER_STAGE_VIEW (backend->stage_view), +@@ -698,6 +815,9 @@ meta_backend_anland_init_post (MetaBackend *backend, + if (!backend_anland->input) + return FALSE; + ++ g_signal_connect_object (meta_backend_get_context (backend), "started", ++ G_CALLBACK (on_context_started), backend, 0); ++ + if (!connect_transport (backend_anland, error)) + return FALSE; + +@@ -741,6 +861,7 @@ meta_backend_anland_dispose (GObject *object) + g_clear_handle_id (&backend->reconnect_source_id, g_source_remove); + clear_transport (backend); + uninhibit_frame_clock (backend); ++ g_clear_pointer (&backend->clipboard, meta_anland_clipboard_free); + g_clear_pointer (&backend->input, meta_anland_input_free); + g_clear_object (&backend->placeholder_framebuffer); + g_clear_object (&backend->stage_view); +diff --git a/src/meson.build b/src/meson.build +index e61bea7cdc..cacb2a4eba 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -929,6 +929,8 @@ if have_anland + 'backends/anland/meta-backend-anland.h', + 'backends/anland/meta-anland-input.c', + 'backends/anland/meta-anland-input.h', ++ 'backends/anland/meta-anland-clipboard.c', ++ 'backends/anland/meta-anland-clipboard.h', + 'backends/anland/vendor/meta-anland-socket-utils.c', + 'backends/anland/vendor/meta-anland-socket-utils.h', + 'backends/anland/vendor/meta-anland-transport.c', +diff --git a/src/tests/anland-transport-tests.c b/src/tests/anland-transport-tests.c +index 77c39e376a..b49f9952ef 100644 +--- a/src/tests/anland-transport-tests.c ++++ b/src/tests/anland-transport-tests.c +@@ -358,6 +358,16 @@ test_transport_and_reconnect (void) + sizeof (received_text), 100), ==, 1); + g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); + ++ event.type = INPUT_TYPE_CLIPBOARD; ++ event.clipboard.size = sizeof (text); ++ send_data_message (peers.data, DATA_MSG_INPUT_EVENT, &event, sizeof (event)); ++ g_assert_cmpint (meta_anland_send_all (peers.data, text, sizeof (text)), ==, 0); ++ g_assert_cmpint (meta_anland_transport_poll_input_event (transport, &received_event, 100), ==, 1); ++ g_assert_cmpuint (received_event.type, ==, INPUT_TYPE_CLIPBOARD); ++ g_assert_cmpint (meta_anland_transport_read_input_payload (transport, received_text, ++ sizeof (received_text), 100), ==, 1); ++ g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); ++ + event = (struct InputEvent) { + .type = INPUT_TYPE_POINTER_MOTION, + .pointer_motion = { +diff --git a/src/wayland/meta-wayland-text-input.c b/src/wayland/meta-wayland-text-input.c +index aac1b88536..10271645d5 100644 +--- a/src/wayland/meta-wayland-text-input.c ++++ b/src/wayland/meta-wayland-text-input.c +@@ -23,6 +23,7 @@ + + #include + ++#include "clutter/clutter/clutter-input-focus.h" + #include "compositor/meta-surface-actor-wayland.h" + #include "wayland/meta-wayland-private.h" + #include "wayland/meta-wayland-seat.h" +@@ -809,6 +810,22 @@ meta_wayland_text_input_destroy (MetaWaylandTextInput *text_input) + g_free (text_input); + } + ++gboolean ++meta_wayland_text_input_commit_string (MetaWaylandTextInput *text_input, ++ const char *text) ++{ ++ if (!text_input || !text || !*text || !text_input->enabled || ++ !text_input->surface || ++ wl_list_empty (&text_input->focus_resource_list) || ++ !clutter_input_focus_is_focused (text_input->input_focus)) ++ return FALSE; ++ ++ CLUTTER_INPUT_FOCUS_GET_CLASS (text_input->input_focus)->commit_text ( ++ text_input->input_focus, text); ++ meta_wayland_text_input_focus_flush_done (text_input->input_focus); ++ return TRUE; ++} ++ + static void + meta_wayland_text_input_create_new_resource (MetaWaylandTextInput *text_input, + struct wl_client *client, +diff --git a/src/wayland/meta-wayland-text-input.h b/src/wayland/meta-wayland-text-input.h +index 458614c959..c28d0a0876 100644 +--- a/src/wayland/meta-wayland-text-input.h ++++ b/src/wayland/meta-wayland-text-input.h +@@ -43,3 +43,6 @@ gboolean meta_wayland_text_input_update (MetaWaylandTextInput *text_input, + + gboolean meta_wayland_text_input_handle_event (MetaWaylandTextInput *text_input, + const ClutterEvent *event); ++ ++gboolean meta_wayland_text_input_commit_string (MetaWaylandTextInput *text_input, ++ const char *text); diff --git a/x11-packages/mutter/0016-backends-anland-Add-PipeWire-audio-bridge.patch b/x11-packages/mutter/0016-backends-anland-Add-PipeWire-audio-bridge.patch new file mode 100644 index 00000000000..701eda91232 --- /dev/null +++ b/x11-packages/mutter/0016-backends-anland-Add-PipeWire-audio-bridge.patch @@ -0,0 +1,806 @@ +From f1c7da12942c652fd0a1aa9e676190ca39dabbd5 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 23:16:55 +0800 +Subject: [PATCH 16/27] backends/anland: Add PipeWire audio bridge + +Bridge the current AUDIO_MSG_FORMAT and AUDIO_MSG_PCM protocol to PipeWire +playback and capture streams without introducing private shared-memory messages. + +Duplicate the audio fd for the worker, detach it before transport teardown, and +keep PipeWire failures isolated from the display and input backend. + +Keep the speaker node processing while unlinked and send a short zero-filled +PCM message whenever PipeWire has no audio data. Disable PipeWire and session +idle suspension for that node so the Android playback stream stays active until +the desktop audio graph begins producing PCM. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0007-backends-anland-Add-PipeWire-audio-bridge.patch + +diff --git a/meson.build b/meson.build +index e52fc3b863..c795253988 100644 +--- a/meson.build ++++ b/meson.build +@@ -354,7 +354,7 @@ if have_startup_notification + endif + + have_remote_desktop = get_option('remote_desktop') +-if have_remote_desktop ++if have_remote_desktop or have_anland + libpipewire_dep = dependency('libpipewire-0.3', version: libpipewire_req) + endif + +diff --git a/src/backends/anland/meta-anland-audio.c b/src/backends/anland/meta-anland-audio.c +new file mode 100644 +index 0000000000..43862fa84f +--- /dev/null ++++ b/src/backends/anland/meta-anland-audio.c +@@ -0,0 +1,665 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#include "config.h" ++ ++#include "backends/anland/meta-anland-audio.h" ++ ++#include "backends/anland/vendor/meta-anland-protocol.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define DEFAULT_RATE 48000 ++#define DEFAULT_PLAY_CHANNELS 2 ++#define DEFAULT_CAP_CHANNELS 1 ++#define MIC_RING_BYTES (48000 * 2 * (int) sizeof (int16_t)) ++#define MAX_DGRAM (64 * 1024) ++#define RECONNECT_SECS 1 ++#define MIN_AUDIO_RATE 8000 ++#define MAX_AUDIO_RATE 384000 ++#define MAX_AUDIO_CHANNELS 2 ++#define MAX_AUDIO_QUANTUM 65536 ++#define KEEPALIVE_FRAMES 480 ++#define KEEPALIVE_BYTES (KEEPALIVE_FRAMES * DEFAULT_PLAY_CHANNELS * (int) sizeof (int16_t)) ++ ++struct _MetaAnlandAudio ++{ ++ struct pw_thread_loop *loop; ++ struct pw_context *context; ++ struct pw_core *core; ++ struct spa_hook core_listener; ++ struct spa_source *reconnect_timer; ++ bool pw_connected; ++ ++ struct pw_stream *capture; ++ struct spa_hook capture_listener; ++ struct pw_stream *source; ++ struct spa_hook source_listener; ++ ++ uint32_t play_rate; ++ uint32_t play_channels; ++ uint32_t cap_rate; ++ uint32_t cap_channels; ++ uint32_t play_quantum; ++ uint32_t cap_quantum; ++ ++ int audio_fd; ++ struct spa_source *io; ++ ++ uint8_t *ring; ++ size_t ring_size; ++ size_t ring_head; ++ size_t ring_tail; ++ size_t ring_fill; ++ ++ uint8_t silence[KEEPALIVE_BYTES]; ++ ++ uint8_t rx[MAX_DGRAM]; ++ bool loop_started; ++}; ++ ++static int connect_stream (struct pw_stream *stream, ++ enum spa_direction direction, ++ uint32_t rate, ++ uint32_t channels, ++ uint32_t quantum); ++static const struct spa_pod * build_format (struct spa_pod_builder *builder, ++ uint32_t rate, ++ uint32_t channels); ++static void set_latency (struct pw_stream *stream, ++ uint32_t quantum, ++ uint32_t rate); ++ ++static void ++ring_reset (MetaAnlandAudio *audio) ++{ ++ audio->ring_head = audio->ring_tail = audio->ring_fill = 0; ++} ++ ++static void ++ring_write (MetaAnlandAudio *audio, ++ const uint8_t *data, ++ size_t size) ++{ ++ size_t first; ++ ++ if (size > audio->ring_size) ++ { ++ data += size - audio->ring_size; ++ size = audio->ring_size; ++ } ++ ++ if (audio->ring_fill + size > audio->ring_size) ++ { ++ size_t drop = audio->ring_fill + size - audio->ring_size; ++ ++ audio->ring_tail = (audio->ring_tail + drop) % audio->ring_size; ++ audio->ring_fill -= drop; ++ } ++ ++ first = MIN (audio->ring_size - audio->ring_head, size); ++ memcpy (audio->ring + audio->ring_head, data, first); ++ memcpy (audio->ring, data + first, size - first); ++ audio->ring_head = (audio->ring_head + size) % audio->ring_size; ++ audio->ring_fill += size; ++} ++ ++static size_t ++ring_read (MetaAnlandAudio *audio, ++ uint8_t *data, ++ size_t size) ++{ ++ size_t available = MIN (size, audio->ring_fill); ++ size_t first = MIN (audio->ring_size - audio->ring_tail, available); ++ ++ memcpy (data, audio->ring + audio->ring_tail, first); ++ memcpy (data + first, audio->ring, available - first); ++ audio->ring_tail = (audio->ring_tail + available) % audio->ring_size; ++ audio->ring_fill -= available; ++ return available; ++} ++ ++static void ++on_capture_process (void *user_data) ++{ ++ MetaAnlandAudio *audio = user_data; ++ struct pw_buffer *buffer; ++ struct spa_data *data; ++ ++ buffer = pw_stream_dequeue_buffer (audio->capture); ++ if (!buffer) ++ return; ++ ++ data = &buffer->buffer->datas[0]; ++ if (audio->audio_fd >= 0) ++ { ++ const uint8_t *payload = audio->silence; ++ size_t payload_size = sizeof (audio->silence); ++ ++ if (data->data && data->chunk && data->chunk->size > 0) ++ { ++ payload = (uint8_t *) data->data + data->chunk->offset; ++ payload_size = data->chunk->size; ++ } ++ ++ struct audio_msg header = { ++ .type = AUDIO_MSG_PCM, ++ .size = payload_size, ++ }; ++ struct iovec iov[2] = { ++ { .iov_base = &header, .iov_len = sizeof (header) }, ++ { .iov_base = (void *) payload, .iov_len = payload_size }, ++ }; ++ struct msghdr message = { ++ .msg_iov = iov, ++ .msg_iovlen = G_N_ELEMENTS (iov), ++ }; ++ ++ /* The PipeWire loop must not stall on a slow or disconnected consumer. */ ++ sendmsg (audio->audio_fd, &message, MSG_DONTWAIT | MSG_NOSIGNAL); ++ } ++ ++ pw_stream_queue_buffer (audio->capture, buffer); ++} ++ ++static void ++on_source_process (void *user_data) ++{ ++ MetaAnlandAudio *audio = user_data; ++ struct pw_buffer *buffer; ++ struct spa_data *data; ++ uint32_t stride; ++ uint32_t frames; ++ uint32_t size; ++ size_t available; ++ ++ buffer = pw_stream_dequeue_buffer (audio->source); ++ if (!buffer) ++ return; ++ ++ data = &buffer->buffer->datas[0]; ++ if (!data->data || !data->chunk) ++ { ++ pw_stream_queue_buffer (audio->source, buffer); ++ return; ++ } ++ ++ stride = sizeof (int16_t) * audio->cap_channels; ++ frames = data->maxsize / stride; ++ if (buffer->requested && buffer->requested < frames) ++ frames = buffer->requested; ++ size = frames * stride; ++ ++ available = ring_read (audio, data->data, size); ++ if (available < size) ++ memset ((uint8_t *) data->data + available, 0, size - available); ++ ++ data->chunk->offset = 0; ++ data->chunk->stride = stride; ++ data->chunk->size = size; ++ pw_stream_queue_buffer (audio->source, buffer); ++} ++ ++static const struct pw_stream_events capture_events = { ++ PW_VERSION_STREAM_EVENTS, ++ .process = on_capture_process, ++}; ++ ++static const struct pw_stream_events source_events = { ++ PW_VERSION_STREAM_EVENTS, ++ .process = on_source_process, ++}; ++ ++static void ++apply_format (MetaAnlandAudio *audio, ++ const struct audio_format *format) ++{ ++ gboolean playback = format->role == AUDIO_ROLE_PLAYBACK; ++ uint32_t rate = format->rate ? format->rate : DEFAULT_RATE; ++ uint32_t channels = format->channels ? format->channels : ++ (playback ? DEFAULT_PLAY_CHANNELS : DEFAULT_CAP_CHANNELS); ++ uint32_t *current_rate = playback ? &audio->play_rate : &audio->cap_rate; ++ uint32_t *current_channels = playback ? &audio->play_channels : ++ &audio->cap_channels; ++ uint32_t *current_quantum = playback ? &audio->play_quantum : ++ &audio->cap_quantum; ++ struct pw_stream *stream = playback ? audio->capture : audio->source; ++ gboolean format_changed; ++ gboolean quantum_changed; ++ ++ format_changed = rate != *current_rate || channels != *current_channels; ++ quantum_changed = format->quantum != *current_quantum; ++ if (!format_changed && !quantum_changed) ++ return; ++ ++ *current_rate = rate; ++ *current_channels = channels; ++ *current_quantum = format->quantum; ++ ++ if (!audio->pw_connected || !stream) ++ return; ++ ++ if (format_changed) ++ { ++ uint8_t buffer[1024]; ++ struct spa_pod_builder builder = SPA_POD_BUILDER_INIT (buffer, ++ sizeof (buffer)); ++ const struct spa_pod *params[1] = { ++ build_format (&builder, rate, channels), ++ }; ++ ++ set_latency (stream, format->quantum, rate); ++ pw_stream_update_params (stream, params, G_N_ELEMENTS (params)); ++ } ++ else if (format->quantum > 0) ++ { ++ set_latency (stream, format->quantum, rate); ++ } ++} ++ ++static bool ++valid_format (const struct audio_format *format) ++{ ++ return (format->role == AUDIO_ROLE_PLAYBACK || ++ format->role == AUDIO_ROLE_CAPTURE) && ++ format->format == AUDIO_FORMAT_S16LE && ++ format->rate >= MIN_AUDIO_RATE && ++ format->rate <= MAX_AUDIO_RATE && ++ format->channels > 0 && ++ format->channels <= MAX_AUDIO_CHANNELS && ++ format->quantum <= MAX_AUDIO_QUANTUM; ++} ++ ++static void ++on_audio_readable (void *user_data, ++ int fd, ++ uint32_t mask) ++{ ++ MetaAnlandAudio *audio = user_data; ++ ++ if (mask & (SPA_IO_ERR | SPA_IO_HUP) || !(mask & SPA_IO_IN)) ++ return; ++ ++ for (;;) ++ { ++ struct audio_msg header; ++ size_t available; ++ ssize_t received = recv (fd, audio->rx, sizeof (audio->rx), ++ MSG_DONTWAIT | MSG_TRUNC); ++ ++ if (received <= 0) ++ break; ++ if ((size_t) received > sizeof (audio->rx) || ++ (size_t) received < sizeof (header)) ++ continue; ++ ++ memcpy (&header, audio->rx, sizeof (header)); ++ available = (size_t) received - sizeof (header); ++ if (header.type == AUDIO_MSG_FORMAT) ++ { ++ struct audio_format format; ++ ++ if (header.size != sizeof (format) || header.size != available) ++ continue; ++ ++ memcpy (&format, audio->rx + sizeof (header), sizeof (format)); ++ if (valid_format (&format)) ++ apply_format (audio, &format); ++ continue; ++ } ++ ++ if (header.type != AUDIO_MSG_PCM || header.size != available || ++ header.size % (sizeof (int16_t) * audio->cap_channels) != 0) ++ continue; ++ ++ ring_write (audio, audio->rx + sizeof (header), header.size); ++ } ++} ++ ++static void ++arm_reconnect (MetaAnlandAudio *audio) ++{ ++ struct timespec interval = { .tv_sec = RECONNECT_SECS, .tv_nsec = 0 }; ++ ++ pw_loop_update_timer (pw_thread_loop_get_loop (audio->loop), ++ audio->reconnect_timer, &interval, NULL, false); ++} ++ ++static void ++on_core_error (void *user_data, ++ uint32_t id, ++ int seq, ++ int result, ++ const char *message) ++{ ++ MetaAnlandAudio *audio = user_data; ++ ++ (void) seq; ++ (void) message; ++ ++ if (id != PW_ID_CORE || result != -EPIPE) ++ return; ++ ++ audio->pw_connected = false; ++ arm_reconnect (audio); ++} ++ ++static const struct pw_core_events core_events = { ++ PW_VERSION_CORE_EVENTS, ++ .error = on_core_error, ++}; ++ ++static const struct spa_pod * ++build_format (struct spa_pod_builder *builder, ++ uint32_t rate, ++ uint32_t channels) ++{ ++ struct spa_audio_info_raw info = { ++ .format = SPA_AUDIO_FORMAT_S16_LE, ++ .rate = rate, ++ .channels = channels, ++ }; ++ ++ if (channels >= 2) ++ { ++ info.position[0] = SPA_AUDIO_CHANNEL_FL; ++ info.position[1] = SPA_AUDIO_CHANNEL_FR; ++ } ++ else ++ { ++ info.position[0] = SPA_AUDIO_CHANNEL_MONO; ++ } ++ ++ return spa_format_audio_raw_build (builder, SPA_PARAM_EnumFormat, &info); ++} ++ ++static void ++set_latency (struct pw_stream *stream, ++ uint32_t quantum, ++ uint32_t rate) ++{ ++ char latency[32]; ++ struct spa_dict_item items[] = { ++ SPA_DICT_ITEM_INIT (PW_KEY_NODE_LATENCY, latency), ++ }; ++ struct spa_dict properties = SPA_DICT_INIT (items, G_N_ELEMENTS (items)); ++ ++ if (quantum == 0) ++ return; ++ ++ g_snprintf (latency, sizeof (latency), "%u/%u", quantum, rate); ++ pw_stream_update_properties (stream, &properties); ++} ++ ++static int ++connect_stream (struct pw_stream *stream, ++ enum spa_direction direction, ++ uint32_t rate, ++ uint32_t channels, ++ uint32_t quantum) ++{ ++ uint8_t buffer[1024]; ++ struct spa_pod_builder builder = SPA_POD_BUILDER_INIT (buffer, ++ sizeof (buffer)); ++ const struct spa_pod *params[1] = { ++ build_format (&builder, rate, channels), ++ }; ++ ++ set_latency (stream, quantum, rate); ++ return pw_stream_connect (stream, direction, PW_ID_ANY, ++ PW_STREAM_FLAG_AUTOCONNECT | ++ PW_STREAM_FLAG_MAP_BUFFERS | ++ PW_STREAM_FLAG_RT_PROCESS, ++ params, G_N_ELEMENTS (params)); ++} ++ ++static void ++teardown_pw (MetaAnlandAudio *audio) ++{ ++ if (audio->capture) ++ { ++ spa_hook_remove (&audio->capture_listener); ++ pw_stream_destroy (audio->capture); ++ audio->capture = NULL; ++ } ++ ++ if (audio->source) ++ { ++ spa_hook_remove (&audio->source_listener); ++ pw_stream_destroy (audio->source); ++ audio->source = NULL; ++ } ++ ++ if (audio->core) ++ { ++ spa_hook_remove (&audio->core_listener); ++ pw_core_disconnect (audio->core); ++ audio->core = NULL; ++ } ++} ++ ++static int ++build_pw (MetaAnlandAudio *audio) ++{ ++ audio->core = pw_context_connect (audio->context, NULL, 0); ++ if (!audio->core) ++ return -1; ++ ++ pw_core_add_listener (audio->core, &audio->core_listener, &core_events, audio); ++ ++ audio->capture = pw_stream_new ( ++ audio->core, "anland-speaker", ++ pw_properties_new (PW_KEY_MEDIA_TYPE, "Audio", ++ PW_KEY_MEDIA_CLASS, "Audio/Sink", ++ PW_KEY_NODE_NAME, "anland-speaker", ++ PW_KEY_NODE_DESCRIPTION, "Anland remote speaker", ++ PW_KEY_PRIORITY_SESSION, "1010", ++ PW_KEY_PRIORITY_DRIVER, "1010", ++ PW_KEY_NODE_ALWAYS_PROCESS, "true", ++ PW_KEY_NODE_PAUSE_ON_IDLE, "false", ++ PW_KEY_NODE_SUSPEND_ON_IDLE, "false", ++ "session.suspend-timeout-seconds", "0", ++ NULL)); ++ if (!audio->capture) ++ return -1; ++ ++ pw_stream_add_listener (audio->capture, &audio->capture_listener, ++ &capture_events, audio); ++ ++ audio->source = pw_stream_new ( ++ audio->core, "anland-mic", ++ pw_properties_new (PW_KEY_MEDIA_TYPE, "Audio", ++ PW_KEY_MEDIA_CLASS, "Audio/Source", ++ PW_KEY_NODE_NAME, "anland-mic", ++ PW_KEY_NODE_DESCRIPTION, "Anland remote microphone", ++ PW_KEY_PRIORITY_SESSION, "1010", ++ PW_KEY_PRIORITY_DRIVER, "1010", ++ NULL)); ++ if (!audio->source) ++ return -1; ++ ++ pw_stream_add_listener (audio->source, &audio->source_listener, ++ &source_events, audio); ++ ++ if (connect_stream (audio->capture, PW_DIRECTION_INPUT, audio->play_rate, ++ audio->play_channels, audio->play_quantum) < 0 || ++ connect_stream (audio->source, PW_DIRECTION_OUTPUT, audio->cap_rate, ++ audio->cap_channels, audio->cap_quantum) < 0) ++ return -1; ++ ++ return 0; ++} ++ ++static void ++on_reconnect_timer (void *user_data, ++ uint64_t expirations) ++{ ++ MetaAnlandAudio *audio = user_data; ++ ++ (void) expirations; ++ ++ if (audio->pw_connected) ++ return; ++ ++ teardown_pw (audio); ++ if (build_pw (audio) == 0) ++ { ++ audio->pw_connected = true; ++ } ++ else ++ { ++ teardown_pw (audio); ++ arm_reconnect (audio); ++ } ++} ++ ++void ++meta_anland_audio_set_fd (MetaAnlandAudio *audio, ++ int fd) ++{ ++ struct pw_loop *loop; ++ int owned_fd = -1; ++ ++ if (!audio) ++ return; ++ ++ if (fd >= 0) ++ { ++ owned_fd = fcntl (fd, F_DUPFD_CLOEXEC, 0); ++ if (owned_fd < 0) ++ g_warning ("Anland failed to duplicate audio fd: %s", strerror (errno)); ++ } ++ ++ pw_thread_loop_lock (audio->loop); ++ ++ loop = pw_thread_loop_get_loop (audio->loop); ++ if (audio->io) ++ { ++ pw_loop_destroy_source (loop, audio->io); ++ audio->io = NULL; ++ } ++ ++ if (audio->audio_fd >= 0) ++ close (audio->audio_fd); ++ audio->audio_fd = owned_fd; ++ ring_reset (audio); ++ ++ if (owned_fd >= 0) ++ { ++ audio->io = pw_loop_add_io (loop, owned_fd, SPA_IO_IN, false, ++ on_audio_readable, audio); ++ if (!audio->io) ++ { ++ close (audio->audio_fd); ++ audio->audio_fd = -1; ++ } ++ } ++ ++ pw_thread_loop_unlock (audio->loop); ++} ++ ++MetaAnlandAudio * ++meta_anland_audio_new (void) ++{ ++ MetaAnlandAudio *audio; ++ ++ pw_init (NULL, NULL); ++ ++ audio = g_new0 (MetaAnlandAudio, 1); ++ if (!audio) ++ goto fail_init; ++ ++ audio->audio_fd = -1; ++ audio->play_rate = DEFAULT_RATE; ++ audio->play_channels = DEFAULT_PLAY_CHANNELS; ++ audio->cap_rate = DEFAULT_RATE; ++ audio->cap_channels = DEFAULT_CAP_CHANNELS; ++ audio->ring_size = MIC_RING_BYTES; ++ audio->ring = g_malloc (audio->ring_size); ++ if (!audio->ring) ++ goto fail; ++ ++ audio->loop = pw_thread_loop_new ("anland-audio", NULL); ++ if (!audio->loop) ++ goto fail; ++ ++ audio->context = pw_context_new (pw_thread_loop_get_loop (audio->loop), ++ NULL, 0); ++ if (!audio->context) ++ goto fail; ++ ++ audio->reconnect_timer = pw_loop_add_timer ( ++ pw_thread_loop_get_loop (audio->loop), on_reconnect_timer, audio); ++ if (!audio->reconnect_timer) ++ goto fail; ++ ++ if (pw_thread_loop_start (audio->loop) < 0) ++ goto fail; ++ audio->loop_started = true; ++ ++ pw_thread_loop_lock (audio->loop); ++ if (build_pw (audio) == 0) ++ { ++ audio->pw_connected = true; ++ } ++ else ++ { ++ teardown_pw (audio); ++ arm_reconnect (audio); ++ } ++ pw_thread_loop_unlock (audio->loop); ++ ++ return audio; ++ ++fail: ++ if (audio->loop_started) ++ pw_thread_loop_stop (audio->loop); ++ if (audio->reconnect_timer) ++ pw_loop_destroy_source (pw_thread_loop_get_loop (audio->loop), ++ audio->reconnect_timer); ++ if (audio->context) ++ pw_context_destroy (audio->context); ++ if (audio->loop) ++ pw_thread_loop_destroy (audio->loop); ++ g_free (audio->ring); ++ g_free (audio); ++fail_init: ++ return NULL; ++} ++ ++void ++meta_anland_audio_free (MetaAnlandAudio *audio) ++{ ++ if (!audio) ++ return; ++ ++ if (audio->loop_started) ++ pw_thread_loop_stop (audio->loop); ++ teardown_pw (audio); ++ if (audio->io) ++ pw_loop_destroy_source (pw_thread_loop_get_loop (audio->loop), audio->io); ++ if (audio->reconnect_timer) ++ pw_loop_destroy_source (pw_thread_loop_get_loop (audio->loop), ++ audio->reconnect_timer); ++ if (audio->audio_fd >= 0) ++ close (audio->audio_fd); ++ if (audio->context) ++ pw_context_destroy (audio->context); ++ if (audio->loop) ++ pw_thread_loop_destroy (audio->loop); ++ g_free (audio->ring); ++ g_free (audio); ++} +diff --git a/src/backends/anland/meta-anland-audio.h b/src/backends/anland/meta-anland-audio.h +new file mode 100644 +index 0000000000..d00e2d36a6 +--- /dev/null ++++ b/src/backends/anland/meta-anland-audio.h +@@ -0,0 +1,11 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#pragma once ++ ++typedef struct _MetaAnlandAudio MetaAnlandAudio; ++ ++MetaAnlandAudio * meta_anland_audio_new (void); ++void meta_anland_audio_free (MetaAnlandAudio *audio); ++ ++void meta_anland_audio_set_fd (MetaAnlandAudio *audio, ++ int fd); +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index c8a769be2f..d81d396199 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -4,6 +4,7 @@ + + #include "backends/anland/meta-backend-anland.h" + ++#include "backends/anland/meta-anland-audio.h" + #include "backends/anland/meta-anland-clipboard.h" + #include "backends/anland/meta-anland-input.h" + #include "backends/anland/vendor/meta-anland-transport.h" +@@ -47,6 +48,7 @@ struct _MetaBackendAnland + MetaVirtualMonitor *virtual_monitor; + struct screen_info screen_info; + guint reconnect_source_id; ++ MetaAnlandAudio *audio; + MetaAnlandInput *input; + MetaAnlandClipboard *clipboard; + +@@ -406,6 +408,7 @@ complete_pending_frame (MetaBackendAnland *backend) + static void + deactivate_consumer (MetaBackendAnland *backend) + { ++ meta_anland_audio_set_fd (backend->audio, -1); + meta_anland_clipboard_set_transport (backend->clipboard, NULL); + + if (backend->input_source) +@@ -705,6 +708,9 @@ activate_consumer (MetaBackendAnland *backend) + input_source_fd, "[mutter] Anland input", + input_source_prepare, input_source_dispatch, backend, NULL); + g_source_attach (backend->input_source, NULL); ++ meta_anland_audio_set_fd (backend->audio, ++ meta_anland_transport_get_audio_fd ( ++ backend->transport)); + return TRUE; + + failed: +@@ -815,6 +821,10 @@ meta_backend_anland_init_post (MetaBackend *backend, + if (!backend_anland->input) + return FALSE; + ++ backend_anland->audio = meta_anland_audio_new (); ++ if (!backend_anland->audio) ++ g_warning ("Failed to initialize Anland audio bridge"); ++ + g_signal_connect_object (meta_backend_get_context (backend), "started", + G_CALLBACK (on_context_started), backend, 0); + +@@ -861,6 +871,7 @@ meta_backend_anland_dispose (GObject *object) + g_clear_handle_id (&backend->reconnect_source_id, g_source_remove); + clear_transport (backend); + uninhibit_frame_clock (backend); ++ g_clear_pointer (&backend->audio, meta_anland_audio_free); + g_clear_pointer (&backend->clipboard, meta_anland_clipboard_free); + g_clear_pointer (&backend->input, meta_anland_input_free); + g_clear_object (&backend->placeholder_framebuffer); +diff --git a/src/meson.build b/src/meson.build +index cacb2a4eba..379495d70a 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -79,9 +79,14 @@ if have_libwacom + ] + endif + +-if have_remote_desktop ++if have_remote_desktop or have_anland + mutter_pkg_private_deps += [ + libpipewire_dep, ++ ] ++endif ++ ++if have_remote_desktop ++ mutter_pkg_private_deps += [ + libeis_dep, + ] + endif +@@ -927,6 +932,8 @@ if have_anland + mutter_sources += [ + 'backends/anland/meta-backend-anland.c', + 'backends/anland/meta-backend-anland.h', ++ 'backends/anland/meta-anland-audio.c', ++ 'backends/anland/meta-anland-audio.h', + 'backends/anland/meta-anland-input.c', + 'backends/anland/meta-anland-input.h', + 'backends/anland/meta-anland-clipboard.c', diff --git a/x11-packages/mutter/0017-backends-anland-Add-camera-service-and-integration-t.patch b/x11-packages/mutter/0017-backends-anland-Add-camera-service-and-integration-t.patch new file mode 100644 index 00000000000..b7e25524c9a --- /dev/null +++ b/x11-packages/mutter/0017-backends-anland-Add-camera-service-and-integration-t.patch @@ -0,0 +1,1392 @@ +From c18c9ac789cfe096cfb4b0ef724487ecc4b35b4f Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sun, 2 Aug 2026 23:36:05 +0800 +Subject: [PATCH 17/27] backends/anland: Add camera service and integration + tests + +Request the Anland camera resource after consumer activation and adopt its +control and stream descriptors with explicit cleanup on fallback and reconnect. + +Expose persistent PipeWire video-source nodes backed by the camera shared-memory +READY/DONE protocol, validate asynchronous camera information, and extend the +mock transport coverage for resource descriptor and request framing. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0008-backends-anland-Add-camera-service-and-integration-tests.patch + +diff --git a/src/backends/anland/meta-anland-camera.c b/src/backends/anland/meta-anland-camera.c +new file mode 100644 +index 0000000000..3d2ce39dbf +--- /dev/null ++++ b/src/backends/anland/meta-anland-camera.c +@@ -0,0 +1,1036 @@ ++#include "config.h" ++ ++#include "backends/anland/meta-anland-camera.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * Control-channel protocol + stream/shm protocol. MUST stay in sync with the ++ * consumer's camera_service.h. ++ */ ++struct camera_ctrl_msg { ++ uint8_t type; ++ uint8_t reserved; ++ uint16_t len; ++ uint8_t payload[]; ++} __attribute__((packed)); ++ ++#define CAMERA_CTRL_GET_INFO 0x01 ++#define CAMERA_CTRL_START_RECORD 0x02 /* payload: id(1B) w(2B,LE) h(2B,LE) */ ++#define CAMERA_CTRL_STOP_RECORD 0x03 /* payload: id(1B) */ ++#define CAMERA_CTRL_INFO_REPLY 0x81 ++ ++/* stream_fd control protocol (SEQPACKET). Frame pixels travel through shared memory; ++ * this only carries the shm hand-off and the READY/DONE pacing. */ ++#define CAMERA_SLOTS 2 ++struct cam_stream_msg { ++ uint8_t type; ++ uint8_t slot; ++ uint16_t fmt; /* READY: CAM_FMT_*; else 0 */ ++ uint32_t a; /* SHM_OFFER: slot_bytes; READY: width */ ++ uint32_t b; /* READY: height */ ++} __attribute__((packed)); ++ ++_Static_assert(sizeof(struct camera_ctrl_msg) == 4, ++ "camera control header must match Anland 5.13"); ++_Static_assert(sizeof(struct cam_stream_msg) == 12, ++ "camera stream message must match Anland 5.13"); ++ ++#define CAM_STREAM_GET_SHM 1 ++#define CAM_STREAM_SHM_OFFER 2 ++#define CAM_STREAM_READY 3 ++#define CAM_STREAM_DONE 4 ++ ++/* Pixel layout in a slot (matches consumer camera_service.h). All are w*h*3/2 bytes, ++ * single block, Y-stride = w, so only the announced SPA format differs. */ ++#define CAM_FMT_I420 0 ++#define CAM_FMT_NV12 1 ++#define CAM_FMT_NV21 2 ++ ++/* Default resolution we advertise + request before anything is negotiated. The node ++ * format then FOLLOWS whatever the consumer actually delivers (every READY carries the ++ * real w/h): if the device's CameraX picks a different size, the first frame triggers a ++ * re-negotiation so the node always matches the real frames -- no garbage, no truncation. */ ++#define CAM_DEF_W 1280 ++#define CAM_DEF_H 720 ++#define CAM_FPS 30 ++ ++#define MAX_CAMERAS 8 ++#define RECONNECT_SECS 1 ++#define MAX_CAMERA_FRAME_BYTES (256U * 1024U * 1024U) ++ ++struct cam { ++ struct anland_camera *owner; ++ int index; /* camera id, for ctrl messages */ ++ ++ /* Persistent virtual webcam node -- created when a camera first appears, kept ++ * alive across consumer disconnects (blank frames keep flowing), destroyed only ++ * when the camera count shrinks or the engine stops. */ ++ struct pw_stream *node; ++ struct spa_hook listener; ++ bool streaming; /* an app is consuming this node */ ++ bool recording; /* we've told the consumer to START on ctrl */ ++ bool process_seen; /* one-shot debug: on_process was called */ ++ ++ /* Current negotiated node format. The node always advertises exactly this, and we ++ * re-negotiate it to match the resolution + pixel layout the consumer actually sends. */ ++ uint32_t fmt_w, fmt_h; ++ int fmt_code; /* CAM_FMT_* currently announced */ ++ ++ /* Hot-swapped per (re)connect. -1 while detached -> the node emits blank frames. */ ++ int stream_fd; ++ struct spa_source *io; ++ ++ /* Per-camera shared-memory double buffer, offered by the consumer over stream_fd. ++ * On READY we copy the named slot out into our own `frame` (so the consumer can ++ * reuse the slot immediately) and DONE it; on_process emits `frame` at app pace. */ ++ int shm_fd; /* owned dup of the consumer's ashmem, -1 none */ ++ uint8_t *shm; /* mmap (PROT_READ), CAMERA_SLOTS*slot_bytes */ ++ size_t shm_bytes; ++ size_t slot_bytes; ++ bool shm_requested; ++ ++ /* Producer-owned copy of the latest frame (loop thread only). */ ++ uint8_t *frame; ++ size_t frame_cap; ++ size_t frame_size; ++ bool have_frame; ++}; ++ ++struct anland_camera { ++ struct pw_thread_loop *loop; ++ struct pw_context *context; ++ struct pw_core *core; ++ struct spa_hook core_listener; ++ struct spa_source *reconnect_timer; ++ bool pw_connected; ++ bool loop_started; ++ ++ int ctrl_fd; /* owned; shared control socket, -1 when none */ ++ struct spa_source *ctrl_io; ++ uint8_t ctrl_buffer[sizeof(struct camera_ctrl_msg) + ++ 1 + MAX_CAMERAS * 4]; ++ size_t ctrl_buffer_len; ++ bool info_pending; ++ int num_cameras; /* number of live nodes */ ++ struct cam cams[MAX_CAMERAS]; ++}; ++ ++static int build_pw(struct anland_camera *c); ++static void teardown_pw(struct anland_camera *c); ++static void create_nodes(struct anland_camera *c); ++static void destroy_nodes(struct anland_camera *c); ++static void attach_fd(struct cam *cam, int fd); ++static void detach_fd(struct cam *cam); ++static void clear_resources_locked(struct anland_camera *c); ++static void renegotiate_format(struct cam *cam, uint32_t w, uint32_t h, ++ int fmt_code); ++static const struct spa_pod *build_video_format(struct spa_pod_builder *b, ++ uint32_t w, uint32_t h, int fmt_code); ++ ++static bool camera_frame_size(uint32_t width, uint32_t height, size_t *size) ++{ ++ size_t pixels; ++ ++ if (width == 0 || height == 0 || width > UINT16_MAX || ++ height > UINT16_MAX || width > SIZE_MAX / height) ++ return false; ++ pixels = (size_t)width * height; ++ if (pixels > SIZE_MAX - pixels / 2 || ++ pixels + pixels / 2 > MAX_CAMERA_FRAME_BYTES) ++ return false; ++ *size = pixels + pixels / 2; ++ return true; ++} ++ ++static bool valid_camera_format(int format) ++{ ++ return format == CAM_FMT_I420 || format == CAM_FMT_NV12 || ++ format == CAM_FMT_NV21; ++} ++ ++/* ---- control channel ---- */ ++ ++static void send_ctrl(struct anland_camera *c, uint8_t type, ++ const uint8_t *payload, uint16_t len) ++{ ++ if (c->ctrl_fd < 0) ++ return; ++ uint8_t buf[sizeof(struct camera_ctrl_msg) + 8]; ++ if (len > sizeof(buf) - sizeof(struct camera_ctrl_msg)) ++ return; ++ struct camera_ctrl_msg *h = (struct camera_ctrl_msg *)buf; ++ h->type = type; ++ h->reserved = 0; ++ h->len = GUINT16_TO_LE(len); ++ if (len) ++ memcpy(buf + sizeof(*h), payload, len); ++ send(c->ctrl_fd, buf, sizeof(*h) + len, MSG_NOSIGNAL | MSG_DONTWAIT); ++} ++ ++static bool ++handle_camera_info(struct anland_camera *c, ++ const uint8_t *payload, ++ size_t payload_size) ++{ ++ uint8_t n_cameras; ++ ++ if (payload_size < 1) ++ return false; ++ ++ n_cameras = payload[0]; ++ if (n_cameras != c->num_cameras || ++ payload_size != 1 + (size_t) n_cameras * 4) ++ return false; ++ ++ for (int i = 0; i < n_cameras; i++) { ++ struct cam *cam = &c->cams[i]; ++ uint16_t wire_width; ++ uint16_t wire_height; ++ uint32_t width; ++ uint32_t height; ++ size_t frame_size; ++ ++ memcpy(&wire_width, payload + 1 + (size_t) i * 4, ++ sizeof(wire_width)); ++ memcpy(&wire_height, payload + 1 + (size_t) i * 4 + 2, ++ sizeof(wire_height)); ++ width = GUINT16_FROM_LE(wire_width); ++ height = GUINT16_FROM_LE(wire_height); ++ if (!camera_frame_size(width, height, &frame_size)) ++ return false; ++ ++ if (!cam->node) { ++ cam->fmt_w = width; ++ cam->fmt_h = height; ++ } else if (!cam->have_frame && ++ (cam->fmt_w != width || cam->fmt_h != height)) { ++ renegotiate_format(cam, width, height, cam->fmt_code); ++ } ++ } ++ ++ return true; ++} ++ ++static bool ++consume_control_messages(struct anland_camera *c) ++{ ++ while (c->ctrl_buffer_len >= sizeof(struct camera_ctrl_msg)) { ++ struct camera_ctrl_msg header; ++ size_t message_size; ++ ++ memcpy(&header, c->ctrl_buffer, sizeof(header)); ++ header.len = GUINT16_FROM_LE(header.len); ++ if (header.len > sizeof(c->ctrl_buffer) - sizeof(header)) ++ return false; ++ ++ message_size = sizeof(header) + header.len; ++ if (c->ctrl_buffer_len < message_size) ++ return true; ++ ++ if (!c->info_pending || ++ header.type != CAMERA_CTRL_INFO_REPLY || header.reserved != 0 || ++ !handle_camera_info(c, c->ctrl_buffer + sizeof(header), header.len)) ++ return false; ++ ++ c->info_pending = false; ++ c->ctrl_buffer_len -= message_size; ++ memmove(c->ctrl_buffer, c->ctrl_buffer + message_size, ++ c->ctrl_buffer_len); ++ } ++ ++ return true; ++} ++ ++static void ++on_control_readable(void *data, ++ int fd, ++ uint32_t mask) ++{ ++ struct anland_camera *c = data; ++ ++ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { ++ clear_resources_locked(c); ++ return; ++ } ++ ++ while (mask & SPA_IO_IN) { ++ ssize_t n; ++ ++ if (c->ctrl_buffer_len == sizeof(c->ctrl_buffer) || fd != c->ctrl_fd) { ++ clear_resources_locked(c); ++ return; ++ } ++ ++ n = recv(fd, c->ctrl_buffer + c->ctrl_buffer_len, ++ sizeof(c->ctrl_buffer) - c->ctrl_buffer_len, MSG_DONTWAIT); ++ if (n > 0) { ++ c->ctrl_buffer_len += (size_t) n; ++ if (!consume_control_messages(c)) { ++ g_warning("Anland camera control channel sent an invalid reply"); ++ clear_resources_locked(c); ++ return; ++ } ++ continue; ++ } ++ if (n == 0) { ++ clear_resources_locked(c); ++ return; ++ } ++ if (errno == EINTR) ++ continue; ++ if (errno == EAGAIN) ++ return; ++#if EWOULDBLOCK != EAGAIN ++ if (errno == EWOULDBLOCK) ++ return; ++#endif ++ clear_resources_locked(c); ++ return; ++ } ++} ++ ++static void ++request_camera_info(struct anland_camera *c) ++{ ++ struct camera_ctrl_msg request = { ++ .type = CAMERA_CTRL_GET_INFO, ++ .reserved = 0, ++ .len = 0, ++ }; ++ ++ if (c->ctrl_fd < 0) ++ return; ++ ++ if (send(c->ctrl_fd, &request, sizeof(request), ++ MSG_NOSIGNAL | MSG_DONTWAIT) == (ssize_t) sizeof(request)) ++ c->info_pending = true; ++} ++ ++/* Send a fixed stream-control message (GET_SHM / DONE) on a camera's stream socket. */ ++static void send_stream(struct cam *cam, uint8_t type, uint8_t slot) ++{ ++ if (cam->stream_fd < 0) ++ return; ++ struct cam_stream_msg m = { .type = type, .slot = slot }; ++ send(cam->stream_fd, &m, sizeof(m), MSG_NOSIGNAL | MSG_DONTWAIT); ++} ++ ++/* Ask the consumer to hand over the shm fd (once per (re)attach, while streaming). */ ++static void request_shm(struct cam *cam) ++{ ++ if (!cam->streaming || cam->stream_fd < 0 || cam->shm || cam->shm_requested) ++ return; ++ cam->shm_requested = true; ++ send_stream(cam, CAM_STREAM_GET_SHM, 0); ++} ++ ++/* Reconcile the "should the consumer be recording?" state with what we've told it. ++ * Recording is wanted when an app is consuming the node AND we have a live control ++ * socket + stream fd. Requests the consumer at the node's current format. */ ++static void update_recording(struct cam *cam) ++{ ++ const bool want = cam->streaming && cam->stream_fd >= 0 && cam->owner->ctrl_fd >= 0; ++ if (want == cam->recording) ++ return; ++ if (want) { ++ uint8_t p[5]; ++ uint16_t w = GUINT16_TO_LE((uint16_t)cam->fmt_w); ++ uint16_t h = GUINT16_TO_LE((uint16_t)cam->fmt_h); ++ p[0] = (uint8_t)cam->index; ++ memcpy(p + 1, &w, sizeof(w)); ++ memcpy(p + 3, &h, sizeof(h)); ++ send_ctrl(cam->owner, CAMERA_CTRL_START_RECORD, p, sizeof(p)); ++ g_debug("anland-camera: start camera %d at %ux%u\n", ++ cam->index, cam->fmt_w, cam->fmt_h); ++ request_shm(cam); /* pull the shm fd so frames can flow */ ++ } else if (cam->owner->ctrl_fd >= 0) { ++ uint8_t id = (uint8_t)cam->index; ++ send_ctrl(cam->owner, CAMERA_CTRL_STOP_RECORD, &id, 1); ++ g_debug("anland-camera: stop camera %d\n", cam->index); ++ } ++ cam->recording = want; ++} ++ ++/* ---- frame emission ---- */ ++ ++/* The graph calls this whenever the consuming app needs a frame. Emit the latest ++ * received frame, or neutral gray while detached / before the first frame arrives, so ++ * a consuming app always gets a feed. Runs on the loop thread, same as the stream- ++ * socket reader, so cam->frame needs no lock. */ ++static void on_process(void *data) ++{ ++ struct cam *cam = data; ++ if (!cam->node) ++ return; ++ ++ struct pw_buffer *pwb = pw_stream_dequeue_buffer(cam->node); ++ if (!cam->process_seen) { ++ cam->process_seen = true; ++ } ++ if (!pwb) ++ return; ++ struct spa_data *d = &pwb->buffer->datas[0]; ++ if (d->data && d->chunk) { ++ const bool live = cam->have_frame && cam->stream_fd >= 0; ++ size_t fmt_bytes = 0; ++ size_t avail; ++ uint32_t copy; ++ ++ if (!camera_frame_size(cam->fmt_w, cam->fmt_h, &fmt_bytes)) { ++ pw_stream_queue_buffer(cam->node, pwb); ++ return; ++ } ++ avail = live ? cam->frame_size : fmt_bytes; ++ copy = avail < d->maxsize ? (uint32_t)avail : d->maxsize; ++ if (live) ++ memcpy(d->data, cam->frame, copy); ++ else ++ memset(d->data, 128, copy); /* neutral gray: valid I420 at any size */ ++ d->chunk->offset = 0; ++ d->chunk->stride = cam->fmt_w; /* I420 Y-plane stride */ ++ d->chunk->size = copy; ++ } ++ pw_stream_queue_buffer(cam->node, pwb); ++} ++ ++/* Re-announce the node's format to match what the consumer is actually sending (size ++ * AND pixel layout), so the advertised caps always equal the real frames. The app ++ * renegotiates and on_param_changed resizes the buffer pool. fmt_* are updated ++ * optimistically so this doesn't re-fire on every subsequent frame. */ ++static void renegotiate_format(struct cam *cam, uint32_t w, uint32_t h, int fmt_code) ++{ ++ size_t frame_size; ++ ++ if (!cam->node || !valid_camera_format(fmt_code) || ++ !camera_frame_size(w, h, &frame_size)) ++ return; ++ g_debug("anland-camera: camera %d format %ux%u/%d -> %ux%u/%d\n", ++ cam->index, cam->fmt_w, cam->fmt_h, cam->fmt_code, ++ w, h, fmt_code); ++ cam->fmt_w = w; ++ cam->fmt_h = h; ++ cam->fmt_code = fmt_code; ++ uint8_t buffer[1024]; ++ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); ++ const struct spa_pod *params[1] = { build_video_format(&b, w, h, fmt_code) }; ++ pw_stream_update_params(cam->node, params, 1); ++} ++ ++/* ---- stream socket: shm hand-off + READY/DONE pacing ---- */ ++ ++static void unmap_shm(struct cam *cam) ++{ ++ if (cam->shm) { ++ munmap(cam->shm, cam->shm_bytes); ++ cam->shm = NULL; ++ } ++ if (cam->shm_fd >= 0) { ++ close(cam->shm_fd); ++ cam->shm_fd = -1; ++ } ++ cam->shm_bytes = cam->slot_bytes = 0; ++ cam->shm_requested = false; ++} ++ ++static void handle_stream_msg(struct cam *cam, const struct cam_stream_msg *m, int rfd) ++{ ++ switch (m->type) { ++ case CAM_STREAM_SHM_OFFER: { ++ size_t slot_bytes = m->a; ++ size_t total; ++ ++ if (rfd < 0 || slot_bytes == 0 || ++ slot_bytes > MAX_CAMERA_FRAME_BYTES || ++ slot_bytes > SIZE_MAX / CAMERA_SLOTS) { ++ if (rfd >= 0) ++ close(rfd); ++ return; ++ } ++ total = (size_t)CAMERA_SLOTS * slot_bytes; ++ if (cam->shm) ++ munmap(cam->shm, cam->shm_bytes); ++ if (cam->shm_fd >= 0) ++ close(cam->shm_fd); ++ cam->shm_fd = rfd; ++ cam->slot_bytes = slot_bytes; ++ cam->shm_bytes = total; ++ cam->shm = mmap(NULL, total, PROT_READ, MAP_SHARED, rfd, 0); ++ if (cam->shm == MAP_FAILED) { ++ cam->shm = NULL; ++ close(rfd); ++ cam->shm_fd = -1; ++ cam->shm_bytes = cam->slot_bytes = 0; ++ return; ++ } ++ g_debug("anland-camera: camera %d mapped %zu bytes per slot\n", ++ cam->index, slot_bytes); ++ break; ++ } ++ case CAM_STREAM_READY: { ++ if (rfd >= 0) ++ close(rfd); /* READY carries no fd */ ++ uint8_t slot = m->slot; ++ uint32_t w = m->a, h = m->b; ++ int fmt = m->fmt; ++ size_t bytes; ++ bool valid = valid_camera_format(fmt) && ++ camera_frame_size(w, h, &bytes); ++ if (cam->shm && slot < CAMERA_SLOTS && valid && ++ bytes <= cam->slot_bytes) { ++ if (cam->frame_cap < bytes) { ++ uint8_t *nb = realloc(cam->frame, bytes); ++ if (nb) { ++ cam->frame = nb; ++ cam->frame_cap = bytes; ++ } ++ } ++ if (cam->frame_cap >= bytes && bytes > 0) { ++ /* Copy out of the slot promptly so the consumer can reuse it. */ ++ memcpy(cam->frame, cam->shm + (size_t)slot * cam->slot_bytes, bytes); ++ cam->frame_size = bytes; ++ cam->have_frame = true; ++ } ++ } ++ /* Release the slot (even if we couldn't read, so the consumer never wedges). */ ++ send_stream(cam, CAM_STREAM_DONE, slot); ++ if (valid && (w != cam->fmt_w || h != cam->fmt_h || ++ fmt != cam->fmt_code)) ++ renegotiate_format(cam, w, h, fmt); ++ break; ++ } ++ default: ++ if (rfd >= 0) ++ close(rfd); ++ break; ++ } ++} ++ ++static void on_stream_readable(void *data, int fd, uint32_t mask) ++{ ++ struct cam *cam = data; ++ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) ++ return; ++ if (!(mask & SPA_IO_IN)) ++ return; ++ ++ for (;;) { ++ struct cam_stream_msg m; ++ int rfd = -1; ++ struct iovec iov = { .iov_base = &m, .iov_len = sizeof(m) }; ++ union { ++ char buf[CMSG_SPACE(sizeof(int))]; ++ struct cmsghdr align; ++ } cmsg; ++ struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, ++ .msg_control = cmsg.buf, .msg_controllen = sizeof(cmsg.buf) }; ++ ssize_t n = recvmsg(fd, &msg, MSG_DONTWAIT); ++ if (n <= 0) ++ break; ++ if (msg.msg_flags & MSG_CTRUNC) ++ continue; ++ for (struct cmsghdr *c = CMSG_FIRSTHDR(&msg); c; ++ c = CMSG_NXTHDR(&msg, c)) { ++ if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_RIGHTS || ++ c->cmsg_len < CMSG_LEN(sizeof(int))) ++ continue; ++ memcpy(&rfd, CMSG_DATA(c), sizeof(rfd)); ++ break; ++ } ++ if (n == (ssize_t)sizeof(m)) ++ handle_stream_msg(cam, &m, rfd); ++ else if (rfd >= 0) ++ close(rfd); ++ } ++} ++ ++/* Point a camera at a fresh stream socket (or -1 to detach). Drops the old shm mapping ++ * (the new connection re-offers it) so a stale region never bleeds into the new one. */ ++static void attach_fd(struct cam *cam, int fd) ++{ ++ struct pw_loop *loop = pw_thread_loop_get_loop(cam->owner->loop); ++ if (cam->io) { ++ pw_loop_destroy_source(loop, cam->io); ++ cam->io = NULL; ++ } ++ if (cam->stream_fd >= 0) ++ close(cam->stream_fd); ++ cam->stream_fd = fd; ++ unmap_shm(cam); /* re-requested on the new connection */ ++ cam->have_frame = false; /* show blank until a fresh frame arrives */ ++ if (fd >= 0) ++ cam->io = pw_loop_add_io(loop, fd, SPA_IO_IN, false, on_stream_readable, cam); ++ if (fd >= 0 && !cam->io) { ++ close(cam->stream_fd); ++ cam->stream_fd = -1; ++ } ++ update_recording(cam); /* START + GET_SHM if an app is consuming */ ++ request_shm(cam); /* also (re)request if already streaming */ ++} ++ ++static void detach_fd(struct cam *cam) ++{ ++ attach_fd(cam, -1); ++} ++ ++/* ---- Video/Source node ---- */ ++ ++static uint32_t spa_video_fmt(int code) ++{ ++ switch (code) { ++ case CAM_FMT_NV12: return SPA_VIDEO_FORMAT_NV12; ++ case CAM_FMT_NV21: return SPA_VIDEO_FORMAT_NV21; ++ default: return SPA_VIDEO_FORMAT_I420; ++ } ++} ++ ++static const struct spa_pod *build_video_format(struct spa_pod_builder *b, ++ uint32_t w, uint32_t h, int fmt_code) ++{ ++ struct spa_video_info_raw info; ++ spa_zero(info); ++ info.format = spa_video_fmt(fmt_code); ++ info.size = SPA_RECTANGLE(w, h); ++ info.framerate = SPA_FRACTION(CAM_FPS, 1); ++ return spa_format_video_raw_build(b, SPA_PARAM_EnumFormat, &info); ++} ++ ++static void on_stream_state_changed(void *data, enum pw_stream_state old, ++ enum pw_stream_state state, const char *error) ++{ ++ struct cam *cam = data; ++ (void)error; ++ (void)old; ++ const bool streaming = (state == PW_STREAM_STATE_STREAMING); ++ if (streaming == cam->streaming) ++ return; ++ cam->streaming = streaming; ++ update_recording(cam); ++} ++ ++/* The format was negotiated: parse it and declare a buffer pool of the matching size. ++ * Without this PipeWire never allocates correctly-sized buffers and ++ * pw_stream_dequeue_buffer() returns NULL forever, so on_process can never emit. */ ++static void on_param_changed(void *data, uint32_t id, const struct spa_pod *param) ++{ ++ struct cam *cam = data; ++ if (!param || id != SPA_PARAM_Format) ++ return; ++ ++ uint32_t mtype, msubtype; ++ if (spa_format_parse(param, &mtype, &msubtype) < 0 || ++ mtype != SPA_MEDIA_TYPE_video || msubtype != SPA_MEDIA_SUBTYPE_raw) ++ return; ++ ++ struct spa_video_info_raw raw; ++ spa_zero(raw); ++ if (spa_format_video_raw_parse(param, &raw) < 0) ++ return; ++ if (raw.size.width) ++ cam->fmt_w = raw.size.width; ++ if (raw.size.height) ++ cam->fmt_h = raw.size.height; ++ ++ size_t frame_size; ++ if (!camera_frame_size(cam->fmt_w, cam->fmt_h, &frame_size) || ++ frame_size > INT32_MAX) ++ return; ++ ++ uint8_t buffer[512]; ++ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); ++ const struct spa_pod *params[1]; ++ params[0] = spa_pod_builder_add_object(&b, ++ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, ++ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 2, 8), ++ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), ++ SPA_PARAM_BUFFERS_size, SPA_POD_Int((int)frame_size), ++ SPA_PARAM_BUFFERS_stride, SPA_POD_Int((int)cam->fmt_w), ++ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int( ++ (1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd))); ++ pw_stream_update_params(cam->node, params, 1); ++} ++ ++static const struct pw_stream_events stream_events = { ++ PW_VERSION_STREAM_EVENTS, ++ .state_changed = on_stream_state_changed, ++ .param_changed = on_param_changed, ++ .process = on_process, ++}; ++ ++static int connect_node(struct cam *cam) ++{ ++ char name[32], desc[48]; ++ snprintf(name, sizeof(name), "anland-camera-%d", cam->index); ++ snprintf(desc, sizeof(desc), "Anland remote camera %d", cam->index); ++ ++ cam->node = pw_stream_new(cam->owner->core, name, ++ pw_properties_new( ++ PW_KEY_MEDIA_TYPE, "Video", ++ PW_KEY_MEDIA_CATEGORY, "Capture", ++ PW_KEY_MEDIA_CLASS, "Video/Source", ++ PW_KEY_MEDIA_ROLE, "Camera", ++ PW_KEY_NODE_NAME, name, ++ PW_KEY_NODE_DESCRIPTION, desc, ++ NULL)); ++ if (!cam->node) ++ return -1; ++ pw_stream_add_listener(cam->node, &cam->listener, &stream_events, cam); ++ ++ uint8_t buffer[1024]; ++ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); ++ const struct spa_pod *params[1] = { ++ build_video_format(&b, cam->fmt_w, cam->fmt_h, cam->fmt_code) }; ++ ++ /* No DRIVER flag: the consuming app (graph) drives, so PipeWire calls on_process ++ * on demand. No RT_PROCESS either, so on_process runs on the same loop thread as ++ * the stream-socket reader and shares cam->frame without a lock. */ ++ int ret = pw_stream_connect(cam->node, PW_DIRECTION_OUTPUT, PW_ID_ANY, ++ PW_STREAM_FLAG_MAP_BUFFERS, params, 1); ++ if (ret < 0) { ++ spa_hook_remove(&cam->listener); ++ pw_stream_destroy(cam->node); ++ cam->node = NULL; ++ } ++ return ret; ++} ++ ++/* Create the node for one camera slot (no fd attached yet). PipeWire drives its ++ * .process whenever an app consumes it. */ ++static int create_node(struct cam *cam) ++{ ++ if (cam->node) ++ return 0; ++ cam->streaming = false; ++ cam->recording = false; ++ cam->process_seen = false; ++ if (cam->fmt_w == 0 || cam->fmt_h == 0) { ++ cam->fmt_w = CAM_DEF_W; ++ cam->fmt_h = CAM_DEF_H; ++ } ++ /* The consumer always delivers NV21, so advertise it from the start -- no live ++ * format switch (which crashes downstream gst). Only resolution ever renegotiates. */ ++ cam->fmt_code = CAM_FMT_NV21; ++ return connect_node(cam); ++} ++ ++/* (Re)create nodes for the current camera count, e.g. after a PipeWire reconnect. ++ * The stream fds in cam->stream_fd are preserved, so readers are re-armed. */ ++static void create_nodes(struct anland_camera *c) ++{ ++ if (!c->pw_connected || !c->core) ++ return; ++ struct pw_loop *loop = pw_thread_loop_get_loop(c->loop); ++ for (int i = 0; i < c->num_cameras; i++) { ++ struct cam *cam = &c->cams[i]; ++ if (cam->node) ++ continue; ++ create_node(cam); ++ if (cam->stream_fd >= 0 && !cam->io) ++ cam->io = pw_loop_add_io(loop, cam->stream_fd, SPA_IO_IN, false, ++ on_stream_readable, cam); ++ } ++} ++ ++/* Destroy one camera's node and reader. Keeps stream_fd (caller decides). */ ++static void destroy_node(struct cam *cam) ++{ ++ struct pw_loop *loop = cam->owner && cam->owner->loop ++ ? pw_thread_loop_get_loop(cam->owner->loop) : NULL; ++ if (cam->node) { ++ spa_hook_remove(&cam->listener); ++ pw_stream_destroy(cam->node); ++ cam->node = NULL; ++ } ++ if (cam->io && loop) { ++ pw_loop_destroy_source(loop, cam->io); ++ cam->io = NULL; ++ } ++ cam->streaming = false; ++ cam->recording = false; ++} ++ ++static void destroy_nodes(struct anland_camera *c) ++{ ++ for (int i = 0; i < MAX_CAMERAS; i++) ++ destroy_node(&c->cams[i]); ++} ++ ++/* ---- PipeWire connection lifecycle (mirrors the audio engine) ---- */ ++ ++static void arm_reconnect(struct anland_camera *c) ++{ ++ struct timespec val = { .tv_sec = RECONNECT_SECS, .tv_nsec = 0 }; ++ pw_loop_update_timer(pw_thread_loop_get_loop(c->loop), c->reconnect_timer, ++ &val, NULL, false); ++} ++ ++static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) ++{ ++ struct anland_camera *c = data; ++ (void)seq; ++ (void)message; ++ if (id == PW_ID_CORE && res == -EPIPE) { ++ c->pw_connected = false; ++ arm_reconnect(c); ++ } ++} ++ ++static const struct pw_core_events core_events = { ++ PW_VERSION_CORE_EVENTS, ++ .error = on_core_error, ++}; ++ ++static int build_pw(struct anland_camera *c) ++{ ++ c->core = pw_context_connect(c->context, NULL, 0); ++ if (!c->core) ++ return -1; ++ pw_core_add_listener(c->core, &c->core_listener, &core_events, c); ++ return 0; ++} ++ ++static void teardown_pw(struct anland_camera *c) ++{ ++ destroy_nodes(c); ++ if (c->core) { ++ spa_hook_remove(&c->core_listener); ++ pw_core_disconnect(c->core); ++ c->core = NULL; ++ } ++} ++ ++static void on_reconnect_timer(void *data, uint64_t expirations) ++{ ++ struct anland_camera *c = data; ++ (void)expirations; ++ if (c->pw_connected) ++ return; ++ ++ teardown_pw(c); ++ if (build_pw(c) == 0) { ++ c->pw_connected = true; ++ create_nodes(c); /* rebuild nodes from the still-valid stream fds */ ++ } else { ++ teardown_pw(c); ++ arm_reconnect(c); ++ } ++} ++ ++/* ---- public API ---- */ ++ ++static void close_resource_set(int ctrl_fd, const int *stream_fds, int num_cameras) ++{ ++ if (ctrl_fd >= 0) ++ close(ctrl_fd); ++ if (!stream_fds) ++ return; ++ for (int i = 0; i < num_cameras; i++) ++ if (stream_fds[i] >= 0) ++ close(stream_fds[i]); ++} ++ ++void meta_anland_camera_set_resources(struct anland_camera *c, int ctrl_fd, ++ const int *stream_fds, int num_cameras) ++{ ++ if (!c || !stream_fds || num_cameras < 0) { ++ close_resource_set(ctrl_fd, stream_fds, ++ num_cameras > 0 ? num_cameras : 0); ++ return; ++ } ++ ++ int accepted = num_cameras < MAX_CAMERAS ? num_cameras : MAX_CAMERAS; ++ for (int i = accepted; i < num_cameras; i++) { ++ if (stream_fds[i] >= 0) ++ close(stream_fds[i]); ++ } ++ ++ pw_thread_loop_lock(c->loop); ++ ++ /* Swap in the new control socket. */ ++ if (c->ctrl_io) { ++ pw_loop_destroy_source(pw_thread_loop_get_loop(c->loop), c->ctrl_io); ++ c->ctrl_io = NULL; ++ } ++ if (c->ctrl_fd >= 0) ++ close(c->ctrl_fd); ++ c->ctrl_fd = ctrl_fd; ++ c->ctrl_buffer_len = 0; ++ c->info_pending = false; ++ if (c->ctrl_fd >= 0) { ++ c->ctrl_io = pw_loop_add_io(pw_thread_loop_get_loop(c->loop), c->ctrl_fd, ++ SPA_IO_IN, false, on_control_readable, c); ++ if (!c->ctrl_io) { ++ g_warning("Failed to monitor the Anland camera control channel"); ++ clear_resources_locked(c); ++ pw_thread_loop_unlock(c->loop); ++ return; ++ } ++ } ++ ++ /* Pop excess cameras if the new connection exposes fewer than before. */ ++ for (int i = accepted; i < c->num_cameras; i++) { ++ destroy_node(&c->cams[i]); ++ detach_fd(&c->cams[i]); /* closes the now-stale stream fd */ ++ } ++ ++ /* Create any newly-appeared cameras, then (re)attach every live one's stream fd. ++ * Existing nodes are kept (apply-in-place, like the audio engine hot-swaps its ++ * socket) so consuming apps never see the webcam disappear across a reconnect. */ ++ c->num_cameras = accepted; ++ for (int i = 0; i < accepted; i++) { ++ struct cam *cam = &c->cams[i]; ++ cam->owner = c; ++ cam->index = i; ++ if (c->pw_connected && c->core) ++ create_node(cam); ++ attach_fd(cam, stream_fds[i]); ++ } ++ ++ /* The reply is parsed by the PipeWire loop, so a missing consumer response cannot ++ * stall Mutter's main context. It also confirms that the resource fd count and ++ * advertised physical cameras agree. */ ++ request_camera_info(c); ++ ++ pw_thread_loop_unlock(c->loop); ++} ++ ++static void clear_resources_locked(struct anland_camera *c) ++{ ++ /* Consumer is gone: drop the control socket and detach every camera's stream fd. ++ * The nodes stay alive and keep emitting blank frames, so PipeWire and any ++ * capturing app never see the cameras disappear -- the audio engine's detach ++ * behaviour. */ ++ for (int i = 0; i < c->num_cameras; i++) { ++ struct cam *cam = &c->cams[i]; ++ if (cam->recording && c->ctrl_fd >= 0) { ++ uint8_t id = (uint8_t)cam->index; ++ send_ctrl(c, CAMERA_CTRL_STOP_RECORD, &id, 1); ++ } ++ cam->recording = false; ++ } ++ if (c->ctrl_io) { ++ pw_loop_destroy_source(pw_thread_loop_get_loop(c->loop), c->ctrl_io); ++ c->ctrl_io = NULL; ++ } ++ if (c->ctrl_fd >= 0) { ++ close(c->ctrl_fd); ++ c->ctrl_fd = -1; ++ } ++ c->ctrl_buffer_len = 0; ++ c->info_pending = false; ++ for (int i = 0; i < c->num_cameras; i++) ++ detach_fd(&c->cams[i]); ++} ++ ++void meta_anland_camera_clear(struct anland_camera *c) ++{ ++ if (!c) ++ return; ++ pw_thread_loop_lock(c->loop); ++ clear_resources_locked(c); ++ pw_thread_loop_unlock(c->loop); ++} ++ ++struct anland_camera *meta_anland_camera_new(void) ++{ ++ pw_init(NULL, NULL); ++ ++ struct anland_camera *c = calloc(1, sizeof(*c)); ++ if (!c) { ++ return NULL; ++ } ++ c->ctrl_fd = -1; ++ for (int i = 0; i < MAX_CAMERAS; i++) { ++ c->cams[i].owner = c; ++ c->cams[i].index = i; ++ c->cams[i].stream_fd = -1; ++ c->cams[i].shm_fd = -1; ++ } ++ ++ c->loop = pw_thread_loop_new("anland-camera", NULL); ++ if (!c->loop) ++ goto fail; ++ ++ c->context = pw_context_new(pw_thread_loop_get_loop(c->loop), NULL, 0); ++ if (!c->context) ++ goto fail; ++ ++ c->reconnect_timer = pw_loop_add_timer(pw_thread_loop_get_loop(c->loop), ++ on_reconnect_timer, c); ++ if (!c->reconnect_timer) ++ goto fail; ++ ++ if (pw_thread_loop_start(c->loop) < 0) ++ goto fail; ++ c->loop_started = true; ++ ++ pw_thread_loop_lock(c->loop); ++ if (build_pw(c) == 0) { ++ c->pw_connected = true; ++ } else { ++ teardown_pw(c); ++ arm_reconnect(c); ++ } ++ pw_thread_loop_unlock(c->loop); ++ ++ return c; ++ ++fail: ++ if (c->loop_started) ++ pw_thread_loop_stop(c->loop); ++ if (c->reconnect_timer) ++ pw_loop_destroy_source(pw_thread_loop_get_loop(c->loop), ++ c->reconnect_timer); ++ if (c->context) ++ pw_context_destroy(c->context); ++ if (c->loop) ++ pw_thread_loop_destroy(c->loop); ++ free(c); ++ return NULL; ++} ++ ++void meta_anland_camera_free(struct anland_camera *c) ++{ ++ if (!c) ++ return; ++ ++ if (c->loop_started) ++ pw_thread_loop_lock(c->loop); ++ clear_resources_locked(c); ++ teardown_pw(c); ++ for (int i = 0; i < MAX_CAMERAS; i++) { ++ struct cam *cam = &c->cams[i]; ++ unmap_shm(cam); ++ free(cam->frame); ++ cam->frame = NULL; ++ } ++ if (c->reconnect_timer) ++ pw_loop_destroy_source(pw_thread_loop_get_loop(c->loop), c->reconnect_timer); ++ if (c->loop_started) ++ pw_thread_loop_unlock(c->loop); ++ ++ if (c->loop_started) ++ pw_thread_loop_stop(c->loop); ++ if (c->context) ++ pw_context_destroy(c->context); ++ if (c->loop) ++ pw_thread_loop_destroy(c->loop); ++ free(c); ++} +diff --git a/src/backends/anland/meta-anland-camera.h b/src/backends/anland/meta-anland-camera.h +new file mode 100644 +index 0000000000..1c1ce31c45 +--- /dev/null ++++ b/src/backends/anland/meta-anland-camera.h +@@ -0,0 +1,14 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#pragma once ++ ++typedef struct anland_camera MetaAnlandCamera; ++ ++MetaAnlandCamera * meta_anland_camera_new (void); ++void meta_anland_camera_free (MetaAnlandCamera *camera); ++ ++void meta_anland_camera_set_resources (MetaAnlandCamera *camera, ++ int control_fd, ++ const int *stream_fds, ++ int n_stream_fds); ++void meta_anland_camera_clear (MetaAnlandCamera *camera); +diff --git a/src/backends/anland/meta-anland-input.c b/src/backends/anland/meta-anland-input.c +index 9e9b104b01..0b2bde734d 100644 +--- a/src/backends/anland/meta-anland-input.c ++++ b/src/backends/anland/meta-anland-input.c +@@ -13,7 +13,6 @@ + #include + #include + +-#define ANLAND_MAX_RESOURCE_FDS 64 + #define ANLAND_MAX_TOUCH_BATCH_EVENTS 128 + #define ANLAND_TOUCH_BATCH_TIMEOUT_MS 100 + +@@ -160,27 +159,6 @@ flush_touch_batch (MetaAnlandInput *input) + g_array_set_size (input->touch_batch, 0); + } + +-static gboolean +-discard_resource_fds (MetaAnlandTransport *transport, +- uint32_t expected_fds) +-{ +- int fds[ANLAND_MAX_RESOURCE_FDS]; +- int n_fds = 0; +- int i; +- +- if (expected_fds == 0) +- return TRUE; +- if (expected_fds > G_N_ELEMENTS (fds) || +- meta_anland_transport_read_input_fds (transport, fds, +- G_N_ELEMENTS (fds), &n_fds, 100) != 1) +- return FALSE; +- +- for (i = 0; i < n_fds; i++) +- close (fds[i]); +- +- return n_fds == (int) expected_fds; +-} +- + static void + handle_pointer_motion (MetaAnlandInput *input, + const struct InputEvent *event) +@@ -441,8 +419,7 @@ meta_anland_input_handle_event (MetaAnlandInput *input, + case INPUT_TYPE_TEXT_INPUT: + return META_ANLAND_INPUT_EVENT_NEEDS_PAYLOAD; + case INPUT_TYPE_RESOURCE: +- return discard_resource_fds (transport, event->resource.fdnum) ? +- META_ANLAND_INPUT_EVENT_HANDLED : META_ANLAND_INPUT_EVENT_ERROR; ++ return META_ANLAND_INPUT_EVENT_NEEDS_RESOURCE_FDS; + case INPUT_TYPE_ACTION: + break; + default: +diff --git a/src/backends/anland/meta-anland-input.h b/src/backends/anland/meta-anland-input.h +index 8b0731bdc8..46c48698a2 100644 +--- a/src/backends/anland/meta-anland-input.h ++++ b/src/backends/anland/meta-anland-input.h +@@ -13,6 +13,7 @@ typedef enum + { + META_ANLAND_INPUT_EVENT_HANDLED, + META_ANLAND_INPUT_EVENT_NEEDS_PAYLOAD, ++ META_ANLAND_INPUT_EVENT_NEEDS_RESOURCE_FDS, + META_ANLAND_INPUT_EVENT_ERROR, + } MetaAnlandInputEventResult; + +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index d81d396199..aa5db4ac21 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -5,6 +5,7 @@ + #include "backends/anland/meta-backend-anland.h" + + #include "backends/anland/meta-anland-audio.h" ++#include "backends/anland/meta-anland-camera.h" + #include "backends/anland/meta-anland-clipboard.h" + #include "backends/anland/meta-anland-input.h" + #include "backends/anland/vendor/meta-anland-transport.h" +@@ -31,6 +32,7 @@ + + #define ANLAND_RECONNECT_INTERVAL_MS 200 + #define ANLAND_PROTOCOL_FORMAT_RGBA_8888 1 ++#define ANLAND_MAX_RESOURCE_FDS 64 + + enum + { +@@ -49,6 +51,7 @@ struct _MetaBackendAnland + struct screen_info screen_info; + guint reconnect_source_id; + MetaAnlandAudio *audio; ++ MetaAnlandCamera *camera; + MetaAnlandInput *input; + MetaAnlandClipboard *clipboard; + +@@ -161,6 +164,85 @@ handle_extended_input (MetaBackendAnland *backend, + return meta_anland_input_inject_text (backend->input, text); + } + ++static void ++close_resource_fds (int *fds, ++ int n_fds) ++{ ++ int i; ++ ++ for (i = 0; i < n_fds; i++) ++ { ++ if (fds[i] >= 0) ++ close (fds[i]); ++ } ++} ++ ++static gboolean ++set_resource_fds_cloexec (int *fds, ++ int n_fds) ++{ ++ int i; ++ ++ for (i = 0; i < n_fds; i++) ++ { ++ int flags = fcntl (fds[i], F_GETFD); ++ ++ if (flags < 0 || fcntl (fds[i], F_SETFD, flags | FD_CLOEXEC) < 0) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++handle_resource_input (MetaBackendAnland *backend, ++ const struct InputEvent *event) ++{ ++ int fds[ANLAND_MAX_RESOURCE_FDS]; ++ int n_fds = 0; ++ ++ if (event->resource.fdnum == 0 || ++ event->resource.fdnum > G_N_ELEMENTS (fds) || ++ meta_anland_transport_read_input_fds (backend->transport, fds, ++ G_N_ELEMENTS (fds), &n_fds, 100) != 1 || ++ n_fds != (int) event->resource.fdnum) ++ { ++ close_resource_fds (fds, n_fds); ++ return FALSE; ++ } ++ ++ if (!set_resource_fds_cloexec (fds, n_fds)) ++ { ++ close_resource_fds (fds, n_fds); ++ return FALSE; ++ } ++ ++ if (event->resource.type == SERVICE_TYPE_CAMERA && backend->camera) ++ { ++ meta_anland_camera_set_resources (backend->camera, fds[0], &fds[1], ++ n_fds - 1); ++ return TRUE; ++ } ++ ++ close_resource_fds (fds, n_fds); ++ return TRUE; ++} ++ ++static void ++request_camera_resources (MetaBackendAnland *backend) ++{ ++ struct OutputEvent event = { ++ .type = OUTPUT_TYPE_RESOURCES_REQUEST, ++ .resources_request.type = SERVICE_TYPE_CAMERA, ++ }; ++ ++ if (!backend->camera) ++ return; ++ ++ meta_anland_transport_send_output_event (backend->transport, &event, ++ NULL, 0); ++} ++ + static char * + get_default_socket_path (void) + { +@@ -409,6 +491,7 @@ static void + deactivate_consumer (MetaBackendAnland *backend) + { + meta_anland_audio_set_fd (backend->audio, -1); ++ meta_anland_camera_clear (backend->camera); + meta_anland_clipboard_set_transport (backend->clipboard, NULL); + + if (backend->input_source) +@@ -531,6 +614,14 @@ input_source_dispatch (gpointer user_data) + return G_SOURCE_REMOVE; + } + } ++ else if (input_result == META_ANLAND_INPUT_EVENT_NEEDS_RESOURCE_FDS) ++ { ++ if (!handle_resource_input (backend, &event)) ++ { ++ meta_anland_transport_enter_fallback (backend->transport); ++ return G_SOURCE_REMOVE; ++ } ++ } + else if (input_result == META_ANLAND_INPUT_EVENT_ERROR) + { + meta_anland_transport_enter_fallback (backend->transport); +@@ -711,6 +802,7 @@ activate_consumer (MetaBackendAnland *backend) + meta_anland_audio_set_fd (backend->audio, + meta_anland_transport_get_audio_fd ( + backend->transport)); ++ request_camera_resources (backend); + return TRUE; + + failed: +@@ -825,6 +917,10 @@ meta_backend_anland_init_post (MetaBackend *backend, + if (!backend_anland->audio) + g_warning ("Failed to initialize Anland audio bridge"); + ++ backend_anland->camera = meta_anland_camera_new (); ++ if (!backend_anland->camera) ++ g_warning ("Failed to initialize Anland camera bridge"); ++ + g_signal_connect_object (meta_backend_get_context (backend), "started", + G_CALLBACK (on_context_started), backend, 0); + +@@ -872,6 +968,7 @@ meta_backend_anland_dispose (GObject *object) + clear_transport (backend); + uninhibit_frame_clock (backend); + g_clear_pointer (&backend->audio, meta_anland_audio_free); ++ g_clear_pointer (&backend->camera, meta_anland_camera_free); + g_clear_pointer (&backend->clipboard, meta_anland_clipboard_free); + g_clear_pointer (&backend->input, meta_anland_input_free); + g_clear_object (&backend->placeholder_framebuffer); +diff --git a/src/meson.build b/src/meson.build +index 379495d70a..0b38fe144e 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -934,6 +934,8 @@ if have_anland + 'backends/anland/meta-backend-anland.h', + 'backends/anland/meta-anland-audio.c', + 'backends/anland/meta-anland-audio.h', ++ 'backends/anland/meta-anland-camera.c', ++ 'backends/anland/meta-anland-camera.h', + 'backends/anland/meta-anland-input.c', + 'backends/anland/meta-anland-input.h', + 'backends/anland/meta-anland-clipboard.c', +diff --git a/src/tests/anland-transport-tests.c b/src/tests/anland-transport-tests.c +index b49f9952ef..6fde546f8e 100644 +--- a/src/tests/anland-transport-tests.c ++++ b/src/tests/anland-transport-tests.c +@@ -308,6 +308,9 @@ test_transport_and_reconnect (void) + char received_audio = 0; + char received_text[sizeof (text)] = { 0 }; + char fence_byte = 1; ++ int resource_fds[2] = { -1, -1 }; ++ int received_resource_fds[2] = { -1, -1 }; ++ int n_resource_fds = 0; + int fence_fds[1] = { -1 }; + int n_fence_fds = 0; + int pipe_fds[2] = { -1, -1 }; +@@ -358,6 +361,38 @@ test_transport_and_reconnect (void) + sizeof (received_text), 100), ==, 1); + g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); + ++ g_assert_cmpint (pipe2 (resource_fds, O_CLOEXEC), ==, 0); ++ event = (struct InputEvent) { ++ .type = INPUT_TYPE_RESOURCE, ++ .resource = { ++ .type = SERVICE_TYPE_CAMERA, ++ .fdnum = G_N_ELEMENTS (resource_fds), ++ }, ++ }; ++ send_data_message (peers.data, DATA_MSG_INPUT_EVENT, &event, sizeof (event)); ++ output_header = (struct data_msg) { ++ .type = DATA_MSG_INPUT_EXTEND_FDS, ++ .size = 0, ++ }; ++ g_assert_cmpint (meta_anland_send_fds (peers.data, &output_header, ++ sizeof (output_header), resource_fds, ++ G_N_ELEMENTS (resource_fds)), ==, 0); ++ close_fd (&resource_fds[0]); ++ close_fd (&resource_fds[1]); ++ g_assert_cmpint (meta_anland_transport_poll_input_event (transport, ++ &received_event, 100), ==, 1); ++ g_assert_cmpuint (received_event.type, ==, INPUT_TYPE_RESOURCE); ++ g_assert_cmpuint (received_event.resource.type, ==, SERVICE_TYPE_CAMERA); ++ g_assert_cmpuint (received_event.resource.fdnum, ==, ++ G_N_ELEMENTS (received_resource_fds)); ++ g_assert_cmpint (meta_anland_transport_read_input_fds (transport, ++ received_resource_fds, ++ G_N_ELEMENTS (received_resource_fds), ++ &n_resource_fds, 100), ==, 1); ++ g_assert_cmpint (n_resource_fds, ==, G_N_ELEMENTS (received_resource_fds)); ++ close_fd (&received_resource_fds[0]); ++ close_fd (&received_resource_fds[1]); ++ + event.type = INPUT_TYPE_CLIPBOARD; + event.clipboard.size = sizeof (text); + send_data_message (peers.data, DATA_MSG_INPUT_EVENT, &event, sizeof (event)); +@@ -368,6 +403,21 @@ test_transport_and_reconnect (void) + sizeof (received_text), 100), ==, 1); + g_assert_cmpmem (received_text, sizeof (received_text), text, sizeof (text)); + ++ output = (struct OutputEvent) { ++ .type = OUTPUT_TYPE_RESOURCES_REQUEST, ++ .resources_request = { .type = SERVICE_TYPE_CAMERA }, ++ }; ++ g_assert_true (meta_anland_transport_send_output_event (transport, &output, ++ NULL, 0)); ++ g_assert_cmpint (meta_anland_recv_all (peers.data, &output_header, ++ sizeof (output_header)), ==, 0); ++ g_assert_cmpuint (output_header.type, ==, DATA_MSG_OUTPUT_EVENT); ++ g_assert_cmpint (meta_anland_recv_all (peers.data, &received_output, ++ sizeof (received_output)), ==, 0); ++ g_assert_cmpuint (received_output.type, ==, OUTPUT_TYPE_RESOURCES_REQUEST); ++ g_assert_cmpuint (received_output.resources_request.type, ==, ++ SERVICE_TYPE_CAMERA); ++ + event = (struct InputEvent) { + .type = INPUT_TYPE_POINTER_MOTION, + .pointer_motion = { diff --git a/x11-packages/mutter/0018-backends-anland-Map-input-to-logical-output-coordina.patch b/x11-packages/mutter/0018-backends-anland-Map-input-to-logical-output-coordina.patch new file mode 100644 index 00000000000..f2e7d2f11be --- /dev/null +++ b/x11-packages/mutter/0018-backends-anland-Map-input-to-logical-output-coordina.patch @@ -0,0 +1,228 @@ +From efe964d65e05a71ddd9acd6def7d29d3e4e2e303 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Tue, 4 Aug 2026 00:23:01 +0800 +Subject: [PATCH 18/27] backends/anland: Map input to logical output + coordinates + +Anland reports pointer and touch positions in consumer buffer pixels, while Clutter constrains them in the logical monitor coordinate space. With GNOME output scaling, raw positions beyond the logical right and bottom edges were clamped and created a return dead zone. + +Map absolute pointer and touch positions plus relative pointer deltas from the current consumer extent into the primary logical monitor layout before native input injection. This keeps physical-layout and scale-one behavior unchanged. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0009-backends-anland-Map-input-to-logical-output-coordinates.patch + +diff --git a/src/backends/anland/meta-anland-input.c b/src/backends/anland/meta-anland-input.c +index 0b2bde734d..00fd4dfa5b 100644 +--- a/src/backends/anland/meta-anland-input.c ++++ b/src/backends/anland/meta-anland-input.c +@@ -5,6 +5,8 @@ + #include "backends/anland/meta-anland-input.h" + + #include "backends/meta-backend-private.h" ++#include "backends/meta-logical-monitor.h" ++#include "backends/meta-monitor-manager-private.h" + #include "backends/native/meta-virtual-input-device-native.h" + + #include +@@ -18,6 +20,7 @@ + + struct _MetaAnlandInput + { ++ MetaBackend *backend; + ClutterVirtualInputDevice *pointer; + ClutterVirtualInputDevice *keyboard; + ClutterVirtualInputDevice *touchscreen; +@@ -32,6 +35,45 @@ struct _MetaAnlandInput + gpointer refresh_user_data; + }; + ++static void ++map_input_coordinates (MetaAnlandInput *input, ++ uint32_t input_width, ++ uint32_t input_height, ++ float x, ++ float y, ++ gboolean is_delta, ++ float *mapped_x, ++ float *mapped_y) ++{ ++ MetaMonitorManager *monitor_manager; ++ MetaLogicalMonitor *logical_monitor; ++ MtkRectangle layout; ++ ++ *mapped_x = x; ++ *mapped_y = y; ++ ++ if (input_width == 0 || input_height == 0) ++ return; ++ ++ monitor_manager = meta_backend_get_monitor_manager (input->backend); ++ logical_monitor = ++ meta_monitor_manager_get_primary_logical_monitor (monitor_manager); ++ if (!logical_monitor) ++ return; ++ ++ layout = meta_logical_monitor_get_layout (logical_monitor); ++ ++ /* Anland reports buffer pixels, while Clutter uses the logical layout. */ ++ *mapped_x = x * layout.width / input_width; ++ *mapped_y = y * layout.height / input_height; ++ ++ if (!is_delta) ++ { ++ *mapped_x += layout.x; ++ *mapped_y += layout.y; ++ } ++} ++ + static gboolean + is_finite_pair (float x, + float y) +@@ -161,16 +203,26 @@ flush_touch_batch (MetaAnlandInput *input) + + static void + handle_pointer_motion (MetaAnlandInput *input, +- const struct InputEvent *event) ++ const struct InputEvent *event, ++ uint32_t input_width, ++ uint32_t input_height) + { ++ float x, y, dx, dy; ++ + if (!is_finite_pair (event->pointer_motion.x, event->pointer_motion.y) || + !is_finite_pair (event->pointer_motion.dx, event->pointer_motion.dy)) + return; + ++ map_input_coordinates (input, input_width, input_height, ++ event->pointer_motion.x, event->pointer_motion.y, ++ FALSE, &x, &y); ++ map_input_coordinates (input, input_width, input_height, ++ event->pointer_motion.dx, event->pointer_motion.dy, ++ TRUE, &dx, &dy); ++ + meta_virtual_input_device_native_notify_absolute_relative_motion ( + META_VIRTUAL_INPUT_DEVICE_NATIVE (input->pointer), g_get_monotonic_time (), +- event->pointer_motion.x, event->pointer_motion.y, +- event->pointer_motion.dx, event->pointer_motion.dy); ++ x, y, dx, dy); + } + + static void +@@ -255,17 +307,26 @@ handle_key (MetaAnlandInput *input, + + static void + handle_touch (MetaAnlandInput *input, +- const struct InputEvent *event) ++ const struct InputEvent *event, ++ uint32_t input_width, ++ uint32_t input_height) + { + gpointer value; + int slot; + MetaVirtualTouchEvent touch_event; ++ float x, y; + + if ((event->touch.action == INPUT_ACTION_DOWN || + event->touch.action == INPUT_ACTION_MOVE) && + !is_finite_pair (event->touch.x, event->touch.y)) + return; + ++ if (event->touch.action == INPUT_ACTION_DOWN || ++ event->touch.action == INPUT_ACTION_MOVE) ++ map_input_coordinates (input, input_width, input_height, ++ event->touch.x, event->touch.y, ++ FALSE, &x, &y); ++ + value = g_hash_table_lookup (input->touch_slots, + GINT_TO_POINTER (event->touch.pointer_id)); + switch (event->touch.action) +@@ -287,8 +348,8 @@ handle_touch (MetaAnlandInput *input, + touch_event = (MetaVirtualTouchEvent) { + .type = META_VIRTUAL_TOUCH_EVENT_DOWN, + .slot = slot, +- .x = event->touch.x, +- .y = event->touch.y, ++ .x = x, ++ .y = y, + }; + break; + case INPUT_ACTION_MOVE: +@@ -297,8 +358,8 @@ handle_touch (MetaAnlandInput *input, + touch_event = (MetaVirtualTouchEvent) { + .type = META_VIRTUAL_TOUCH_EVENT_MOTION, + .slot = GPOINTER_TO_UINT (value) - 1, +- .x = event->touch.x, +- .y = event->touch.y, ++ .x = x, ++ .y = y, + }; + break; + case INPUT_ACTION_UP: +@@ -343,6 +404,7 @@ meta_anland_input_new (MetaBackend *backend, + } + + input = g_new0 (MetaAnlandInput, 1); ++ input->backend = backend; + input->touch_slots = g_hash_table_new (g_direct_hash, g_direct_equal); + input->touch_batch = g_array_new (FALSE, FALSE, sizeof (MetaVirtualTouchEvent)); + input->pointer = clutter_seat_create_virtual_device (seat, +@@ -387,12 +449,14 @@ meta_anland_input_free (MetaAnlandInput *input) + MetaAnlandInputEventResult + meta_anland_input_handle_event (MetaAnlandInput *input, + MetaAnlandTransport *transport, +- const struct InputEvent *event) ++ const struct InputEvent *event, ++ uint32_t input_width, ++ uint32_t input_height) + { + switch (event->type) + { + case INPUT_TYPE_POINTER_MOTION: +- handle_pointer_motion (input, event); ++ handle_pointer_motion (input, event, input_width, input_height); + break; + case INPUT_TYPE_POINTER_BUTTON: + handle_pointer_button (input, event); +@@ -404,7 +468,7 @@ meta_anland_input_handle_event (MetaAnlandInput *input, + handle_key (input, event); + break; + case INPUT_TYPE_TOUCH: +- handle_touch (input, event); ++ handle_touch (input, event, input_width, input_height); + break; + case INPUT_TYPE_TOUCH_FRAME: + flush_touch_batch (input); +diff --git a/src/backends/anland/meta-anland-input.h b/src/backends/anland/meta-anland-input.h +index 46c48698a2..fe9eaa886f 100644 +--- a/src/backends/anland/meta-anland-input.h ++++ b/src/backends/anland/meta-anland-input.h +@@ -27,9 +27,11 @@ MetaAnlandInput * meta_anland_input_new (MetaBackend *backend, + void meta_anland_input_free (MetaAnlandInput *input); + + MetaAnlandInputEventResult +- meta_anland_input_handle_event (MetaAnlandInput *input, +- MetaAnlandTransport *transport, +- const struct InputEvent *event); ++meta_anland_input_handle_event (MetaAnlandInput *input, ++ MetaAnlandTransport *transport, ++ const struct InputEvent *event, ++ uint32_t input_width, ++ uint32_t input_height); + gboolean meta_anland_input_inject_text (MetaAnlandInput *input, + const char *text); + void meta_anland_input_reset (MetaAnlandInput *input); +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index aa5db4ac21..1b2b18cb70 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -605,7 +605,9 @@ input_source_dispatch (gpointer user_data) + + input_result = meta_anland_input_handle_event (backend->input, + backend->transport, +- &event); ++ &event, ++ backend->screen_info.width, ++ backend->screen_info.height); + if (input_result == META_ANLAND_INPUT_EVENT_NEEDS_PAYLOAD) + { + if (!handle_extended_input (backend, &event)) diff --git a/x11-packages/mutter/0019-backends-anland-Release-StageView-before-mode-change.patch b/x11-packages/mutter/0019-backends-anland-Release-StageView-before-mode-change.patch new file mode 100644 index 00000000000..e5bab811f29 --- /dev/null +++ b/x11-packages/mutter/0019-backends-anland-Release-StageView-before-mode-change.patch @@ -0,0 +1,62 @@ +From ee74e33cdbda4e9a364ad0c4f1a769d94d181db7 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Tue, 4 Aug 2026 02:59:29 +0800 +Subject: [PATCH 19/27] backends/anland: Release StageView before mode changes + +A virtual monitor mode change rebuilds renderer views and disposes the old +StageView and frame clock. Anland retained the disposed view and tried to +uninhibit its cleared frame clock while reconnecting, crashing GNOME Shell +after the consumer surface changed size. + +Release Anland's inactive consumer view before setting the new mode, then +reload the monitor manager so the current monitor state, logical monitor, and +renderer view adopt the new dimensions. The reconnect timer can then bind the +replacement consumer buffers after the view rebuild. + +This prevents stale frame-clock access without leaving the renderer view at +the old size and showing a black screen after a resize. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0010-backends-anland-Release-StageView-before-mode-changes.patch + +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index 1b2b18cb70..ebe09f55f1 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -73,6 +73,7 @@ struct _MetaBackendAnland + G_DEFINE_TYPE (MetaBackendAnland, meta_backend_anland, META_TYPE_BACKEND_NATIVE) + + static void deactivate_consumer (MetaBackendAnland *backend); ++static void release_stage_view (MetaBackendAnland *backend); + static void schedule_reconnect (MetaBackendAnland *backend); + + static void +@@ -338,6 +339,7 @@ update_virtual_monitor (MetaBackendAnland *backend, + + if (backend->virtual_monitor) + { ++ release_stage_view (backend); + meta_virtual_monitor_set_mode (backend->virtual_monitor, + backend->screen_info.width, + backend->screen_info.height, +@@ -382,6 +384,20 @@ uninhibit_frame_clock (MetaBackendAnland *backend) + backend->frame_clock_inhibited = FALSE; + } + ++static void ++release_stage_view (MetaBackendAnland *backend) ++{ ++ if (!backend->stage_view) ++ return; ++ ++ g_return_if_fail (!backend->consumer_active); ++ g_return_if_fail (!backend->frame_pending); ++ ++ uninhibit_frame_clock (backend); ++ g_clear_object (&backend->placeholder_framebuffer); ++ g_clear_object (&backend->stage_view); ++} ++ + static MetaStageView * + find_stage_view (MetaBackendAnland *backend) + { diff --git a/x11-packages/mutter/0020-backends-anland-Recover-from-consumer-activation-fai.patch b/x11-packages/mutter/0020-backends-anland-Recover-from-consumer-activation-fai.patch new file mode 100644 index 00000000000..262b4d4d345 --- /dev/null +++ b/x11-packages/mutter/0020-backends-anland-Recover-from-consumer-activation-fai.patch @@ -0,0 +1,52 @@ +From 853051888bc995563d7a7bcaa973d8b0987c1815 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Tue, 4 Aug 2026 15:16:32 +0800 +Subject: [PATCH 20/27] backends/anland: Recover from consumer activation + failures + +When the picture-in-picture replacement buffers failed EGL import, consumer +activation returned without putting the transport back into fallback. The +reconnect timer then retried the same descriptor set indefinitely, even after +the consumer re-deposited fresh resources with the daemon. + +Release any partially imported framebuffers and duplicated source descriptors, +then enter transport fallback on fatal activation errors. This closes the stale +data channel and lets the next reconnect collect the consumer's replacement +resources. + +Include the buffer index and layout in dma-buf import errors so persistent +driver-specific failures can be diagnosed from the compositor log. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0011-backends-anland-Recover-from-consumer-activation-failures.patch + +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index ebe09f55f1..ec15a2fe84 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -780,7 +780,16 @@ activate_consumer (MetaBackendAnland *backend) + renderer_native, buffer_info.width, buffer_info.height, + DRM_FORMAT_ABGR8888, 1, &fd, &stride, &offset, &modifier, &error); + if (!framebuffers[i]) +- goto failed; ++ { ++ g_prefix_error (&error, ++ "Failed to import Anland buffer %d " ++ "(%ux%u, stride %u, offset %u, " ++ "modifier %" G_GUINT64_FORMAT "): ", ++ i, buffer_info.width, buffer_info.height, ++ buffer_info.stride, buffer_info.offset, ++ buffer_info.modifier); ++ goto failed; ++ } + } + + buffer_ready_fd = meta_anland_transport_get_buffer_ready_fd (backend->transport); +@@ -832,6 +841,7 @@ failed: + close (buffer_ready_source_fd); + if (input_source_fd >= 0) + close (input_source_fd); ++ meta_anland_transport_enter_fallback (backend->transport); + return FALSE; + } + diff --git a/x11-packages/mutter/0021-backends-anland-Detach-consumer-before-stage-rebuild.patch b/x11-packages/mutter/0021-backends-anland-Detach-consumer-before-stage-rebuild.patch new file mode 100644 index 00000000000..fd1e4eab4a9 --- /dev/null +++ b/x11-packages/mutter/0021-backends-anland-Detach-consumer-before-stage-rebuild.patch @@ -0,0 +1,55 @@ +From 91454ba45d0f91d123d2d90d346cf1eef08c76fe Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Wed, 5 Aug 2026 02:25:21 +0800 +Subject: [PATCH 21/27] backends/anland: Detach consumer before stage rebuilds + +Display configuration changes such as monitor scaling rebuild the native +renderer views even when the virtual monitor mode remains unchanged. Anland +kept its imported consumer framebuffers, present callback, and inhibited frame +clock attached to the old StageView, allowing the compositor to access invalid +stage state during the rebuild. + +Override the stage update hook to deactivate the consumer and release the old +view before chaining to the native backend. Schedule the existing reconnect +path afterwards so rendering resumes against the replacement view. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0013-backends-anland-Detach-consumer-before-stage-rebuilds.patch + +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index ec15a2fe84..ab8cd0d16c 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -961,6 +961,24 @@ meta_backend_anland_init_post (MetaBackend *backend, + return TRUE; + } + ++static void ++meta_backend_anland_update_stage (MetaBackend *backend) ++{ ++ MetaBackendAnland *backend_anland = META_BACKEND_ANLAND (backend); ++ MetaBackendClass *parent_class = ++ META_BACKEND_CLASS (meta_backend_anland_parent_class); ++ gboolean had_stage_view = backend_anland->stage_view != NULL; ++ ++ if (backend_anland->consumer_active) ++ deactivate_consumer (backend_anland); ++ release_stage_view (backend_anland); ++ ++ parent_class->update_stage (backend); ++ ++ if (had_stage_view && backend_anland->transport) ++ schedule_reconnect (backend_anland); ++} ++ + static void + meta_backend_anland_set_property (GObject *object, + guint prop_id, +@@ -1016,6 +1034,7 @@ meta_backend_anland_class_init (MetaBackendAnlandClass *klass) + object_class->set_property = meta_backend_anland_set_property; + object_class->dispose = meta_backend_anland_dispose; + backend_class->init_post = meta_backend_anland_init_post; ++ backend_class->update_stage = meta_backend_anland_update_stage; + + g_object_class_install_property ( + object_class, PROP_ANLAND_SOCKET, diff --git a/x11-packages/mutter/0022-core-Select-Anland-backend-from-ANLAND_SOCKET.patch b/x11-packages/mutter/0022-core-Select-Anland-backend-from-ANLAND_SOCKET.patch new file mode 100644 index 00000000000..8a7abad0108 --- /dev/null +++ b/x11-packages/mutter/0022-core-Select-Anland-backend-from-ANLAND_SOCKET.patch @@ -0,0 +1,42 @@ +From 9f18b91cbfea4db3a53471ca4afe40d78f38732b Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Wed, 5 Aug 2026 09:48:57 +0800 +Subject: [PATCH 22/27] core: Select Anland backend from ANLAND_SOCKET + +ANLAND_SOCKET already selects the display daemon socket, but starting the +backend still requires the separate --anland option. Display managers launch +GNOME Shell without backend-specific command-line arguments, preventing an +Anland session from using their normal startup path. + +Treat a non-empty ANLAND_SOCKET as an opt-in to the Anland backend after +command-line parsing and before configuration validation. Preserve the +existing explicit options and their mutual-exclusion checks, while leaving an +empty environment value inactive. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0014-core-Select-Anland-backend-from-ANLAND_SOCKET.patch + +diff --git a/src/core/meta-context-main.c b/src/core/meta-context-main.c +index cbe9d17e3b..2628ba9b43 100644 +--- a/src/core/meta-context-main.c ++++ b/src/core/meta-context-main.c +@@ -329,10 +329,19 @@ meta_context_main_configure (MetaContext *context, + MetaContextMain *context_main = META_CONTEXT_MAIN (context); + MetaContextClass *context_class = + META_CONTEXT_CLASS (meta_context_main_parent_class); ++#ifdef HAVE_ANLAND ++ const char *anland_socket; ++#endif + + if (!context_class->configure (context, argc, argv, error)) + return FALSE; + ++#ifdef HAVE_ANLAND ++ anland_socket = g_getenv ("ANLAND_SOCKET"); ++ if (anland_socket && *anland_socket) ++ context_main->options.anland = TRUE; ++#endif ++ + if (!check_configuration (context_main, error)) + return FALSE; + diff --git a/x11-packages/mutter/0023-wayland-Relax-Xwayland-socket-ownership-for-Anland.patch b/x11-packages/mutter/0023-wayland-Relax-Xwayland-socket-ownership-for-Anland.patch new file mode 100644 index 00000000000..8b399e8fc8c --- /dev/null +++ b/x11-packages/mutter/0023-wayland-Relax-Xwayland-socket-ownership-for-Anland.patch @@ -0,0 +1,107 @@ +From 987dc095cb1f5cbf5db60c60dcd342d3700eaabd Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Wed, 5 Aug 2026 12:27:59 +0800 +Subject: [PATCH 23/27] wayland: Relax Xwayland socket ownership for Anland + +PRoot shared TMP directories can expose /tmp/.X11-unix with a UID that cannot be changed from the container. Mutter's ownership check then rejects Xwayland before it can start. + +Allow the Anland backend to use such a directory while retaining the writable and sticky-bit checks. Keep ownership enforcement for other backends. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0015-wayland-Relax-Xwayland-socket-ownership-for-Anland.patch + +diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c +index 9d7ca6a179..808f7fc17c 100644 +--- a/src/wayland/meta-xwayland.c ++++ b/src/wayland/meta-xwayland.c +@@ -50,6 +50,9 @@ + + #include "backends/meta-monitor-manager-private.h" + #include "backends/meta-settings-private.h" ++#ifdef HAVE_ANLAND ++#include "backends/anland/meta-backend-anland.h" ++#endif + #include "meta/main.h" + #include "meta/meta-backend.h" + #include "mtk/mtk-x11.h" +@@ -513,7 +516,8 @@ meta_xwayland_override_display_number (int number) + } + + static gboolean +-ensure_x11_unix_perms (GError **error) ++ensure_x11_unix_perms (GError **error, ++ gboolean allow_untrusted_owner) + { + /* Try to detect systems on which /tmp/.X11-unix is owned by neither root nor + * ourselves because in that case the owner can take over the socket we create +@@ -545,7 +549,8 @@ ensure_x11_unix_perms (GError **error) + /* If the directory already exists, it should belong to the same + * user as /tmp or belong to ourselves ... + * (if /tmp is not owned by root or ourselves we're in deep trouble) */ +- if (x11_tmp.st_uid != tmp.st_uid && x11_tmp.st_uid != getuid ()) ++ if (!allow_untrusted_owner && ++ x11_tmp.st_uid != tmp.st_uid && x11_tmp.st_uid != getuid ()) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + "Wrong ownership for directory \"%s\", owned by %d but " +@@ -577,12 +582,13 @@ ensure_x11_unix_perms (GError **error) + } + + static gboolean +-ensure_x11_unix_dir (GError **error) ++ensure_x11_unix_dir (GError **error, ++ gboolean allow_untrusted_owner) + { + if (mkdir (X11_TMP_UNIX_DIR, 01777) != 0) + { + if (errno == EEXIST) +- return ensure_x11_unix_perms (error); ++ return ensure_x11_unix_perms (error, allow_untrusted_owner); + + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Failed to create directory \"%s\": %s", +@@ -623,12 +629,13 @@ static gboolean + choose_xdisplay (MetaXWaylandManager *manager, + MetaXWaylandConnection *connection, + int *display, ++ gboolean allow_untrusted_owner, + GError **error) + { + int number_of_tries = 0; + char *lock_file = NULL; + +- if (!ensure_x11_unix_dir (error)) ++ if (!ensure_x11_unix_dir (error, allow_untrusted_owner)) + return FALSE; + + do +@@ -1089,8 +1096,13 @@ meta_xwayland_init (MetaXWaylandManager *manager, + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaX11DisplayPolicy policy; ++ gboolean allow_untrusted_owner = FALSE; + int display = 0; + ++#ifdef HAVE_ANLAND ++ allow_untrusted_owner = META_IS_BACKEND_ANLAND (backend); ++#endif ++ + if (display_number_override != -1) + display = display_number_override; + else if (g_getenv ("RUNNING_UNDER_GDM")) +@@ -1098,11 +1110,13 @@ meta_xwayland_init (MetaXWaylandManager *manager, + + if (!manager->public_connection.name) + { +- if (!choose_xdisplay (manager, &manager->public_connection, &display, error)) ++ if (!choose_xdisplay (manager, &manager->public_connection, &display, ++ allow_untrusted_owner, error)) + return FALSE; + + display++; +- if (!choose_xdisplay (manager, &manager->private_connection, &display, error)) ++ if (!choose_xdisplay (manager, &manager->private_connection, &display, ++ allow_untrusted_owner, error)) + return FALSE; + + if (!prepare_auth_file (manager, error)) diff --git a/x11-packages/mutter/0024-xwayland-Read-displayfd-readiness-data.patch b/x11-packages/mutter/0024-xwayland-Read-displayfd-readiness-data.patch new file mode 100644 index 00000000000..5065df4a07b --- /dev/null +++ b/x11-packages/mutter/0024-xwayland-Read-displayfd-readiness-data.patch @@ -0,0 +1,36 @@ +From 3c10cd671e92b451449b071cea850ba424441f8b Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Wed, 5 Aug 2026 14:18:01 +0800 +Subject: [PATCH 24/27] xwayland: Read displayfd readiness data + +PRoot can report only G_IO_HUP when Xwayland writes its display name and closes the displayfd socket. Treating the missing G_IO_IN flag as a startup failure leaves the private initialization connection unopened, which blocks all public X11 clients. + +Read the pending display name from the socket and consider Xwayland ready only when data is present. An Xwayland process that exits before announcing readiness still produces EOF and remains a failure. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0016-xwayland-Read-displayfd-readiness-data.patch + +diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c +index 808f7fc17c..ee7a894cf7 100644 +--- a/src/wayland/meta-xwayland.c ++++ b/src/wayland/meta-xwayland.c +@@ -769,12 +769,15 @@ on_displayfd_ready (int fd, + gpointer user_data) + { + GTask *task = user_data; ++ char display_name[16]; ++ ssize_t bytes_read; + + /* The server writes its display name to the displayfd +- * socket when it's ready. We don't care about the data +- * in the socket, just that it wrote something, since +- * that means it's ready. */ +- g_task_return_boolean (task, !!(condition & G_IO_IN)); ++ * socket when it's ready. PRoot may only report G_IO_HUP ++ * after Xwayland writes and closes the socket, so check ++ * the pending data instead of relying on the condition. */ ++ bytes_read = recv (fd, display_name, sizeof (display_name), MSG_DONTWAIT); ++ g_task_return_boolean (task, bytes_read > 0); + g_object_unref (task); + + return G_SOURCE_REMOVE; diff --git a/x11-packages/mutter/0025-core-Continue-X11-initialization-without-Anland-sess.patch b/x11-packages/mutter/0025-core-Continue-X11-initialization-without-Anland-sess.patch new file mode 100644 index 00000000000..83a256632ed --- /dev/null +++ b/x11-packages/mutter/0025-core-Continue-X11-initialization-without-Anland-sess.patch @@ -0,0 +1,59 @@ +From 4a18f6e07e40d43b097f1a70774177e91b674608 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Wed, 5 Aug 2026 15:09:14 +0800 +Subject: [PATCH 25/27] core: Continue X11 initialization without Anland + session services + +GNOME Shell can reject Mutter's init-xserver task when it cannot start optional X11 session services through systemd. This is expected in PRoot containers without a system bus, even though Xwayland has already announced readiness. Continue initializing the X11 display for the Anland backend when that callback returns FALSE without an error, while preserving failures with explicit errors and all other backends. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0017-core-Continue-X11-initialization-without-Anland-session-services.patch + +diff --git a/src/core/display.c b/src/core/display.c +index 3e25f8019b..649c770e63 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -35,6 +35,9 @@ + #include + + #include "backends/meta-backend-private.h" ++#ifdef HAVE_ANLAND ++#include "backends/anland/meta-backend-anland.h" ++#endif + #include "backends/meta-cursor-sprite-xcursor.h" + #include "backends/meta-cursor-tracker-private.h" + #include "backends/meta-input-capture.h" +@@ -831,15 +834,29 @@ meta_display_init_x11_finish (MetaDisplay *display, + GError **error) + { + MetaX11Display *x11_display; ++ gboolean x11_services_started; + + g_assert (g_task_get_source_tag (G_TASK (result)) == meta_display_init_x11); + +- if (!g_task_propagate_boolean (G_TASK (result), error)) ++ x11_services_started = g_task_propagate_boolean (G_TASK (result), error); ++ if (!x11_services_started) + { +- if (*error == NULL) +- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error"); ++#ifdef HAVE_ANLAND ++ MetaContext *context = meta_display_get_context (display); ++ MetaBackend *backend = meta_context_get_backend (context); + +- return FALSE; ++ if (*error == NULL && META_IS_BACKEND_ANLAND (backend)) ++ { ++ g_message ("Continuing without X11 session services on Anland"); ++ } ++ else ++#endif ++ { ++ if (*error == NULL) ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error"); ++ ++ return FALSE; ++ } + } + + if (display->x11_display) diff --git a/x11-packages/mutter/0026-backends-anland-Terminate-text-payloads-before-injec.patch b/x11-packages/mutter/0026-backends-anland-Terminate-text-payloads-before-injec.patch new file mode 100644 index 00000000000..f1f8313b412 --- /dev/null +++ b/x11-packages/mutter/0026-backends-anland-Terminate-text-payloads-before-injec.patch @@ -0,0 +1,47 @@ +From a3eafe482007cc4df1fd1a0c5bfc56d686ed7256 Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Fri, 7 Aug 2026 01:16:15 +0800 +Subject: [PATCH 26/27] backends/anland: Terminate text payloads before + injection + +Anland text input arrives as a length-delimited GBytes payload, which does not guarantee a trailing NUL byte. Passing its data directly to the text-input and virtual-key helpers makes them scan beyond the payload and inject adjacent heap bytes as spurious input. + +Copy text input into a NUL-terminated string before dispatching it, while keeping clipboard payloads length-delimited. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0018-backends-anland-Terminate-text-payloads-before-injection.patch + +diff --git a/src/backends/anland/meta-backend-anland.c b/src/backends/anland/meta-backend-anland.c +index ab8cd0d16c..e7262557e5 100644 +--- a/src/backends/anland/meta-backend-anland.c ++++ b/src/backends/anland/meta-backend-anland.c +@@ -121,6 +121,7 @@ handle_extended_input (MetaBackendAnland *backend, + const struct InputEvent *event) + { + g_autoptr (GBytes) contents = NULL; ++ g_autofree char *input_text = NULL; + const char *text; + guint size; + +@@ -152,17 +153,19 @@ handle_extended_input (MetaBackendAnland *backend, + return meta_anland_clipboard_set_from_consumer (backend->clipboard, + contents); + ++ input_text = g_strndup (text, size); ++ + { + MetaContext *context = meta_backend_get_context (META_BACKEND (backend)); + MetaWaylandCompositor *compositor = + meta_context_get_wayland_compositor (context); + + if (compositor && meta_wayland_text_input_commit_string ( +- meta_wayland_compositor_get_text_input (compositor), text)) ++ meta_wayland_compositor_get_text_input (compositor), input_text)) + return TRUE; + } + +- return meta_anland_input_inject_text (backend->input, text); ++ return meta_anland_input_inject_text (backend->input, input_text); + } + + static void diff --git a/x11-packages/mutter/0027-backends-anland-Prefer-consumer-clipboard-on-reconne.patch b/x11-packages/mutter/0027-backends-anland-Prefer-consumer-clipboard-on-reconne.patch new file mode 100644 index 00000000000..678b9d1282b --- /dev/null +++ b/x11-packages/mutter/0027-backends-anland-Prefer-consumer-clipboard-on-reconne.patch @@ -0,0 +1,31 @@ +From a1753391a9bad3f360e79d2848047894319cca6c Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Sat, 8 Aug 2026 17:25:42 +0800 +Subject: [PATCH 27/27] backends/anland: Prefer consumer clipboard on reconnect + +The Android consumer sends an initial clipboard update after a transport reconnect. Replaying the last compositor clipboard before that update can overwrite a newer system clipboard while the app is returning from the background, causing stale text to be pasted in the session. + +Keep cached content only for loop detection. Let the consumer's initial clipboard transfer establish the current value; subsequent owner changes continue to be forwarded normally. + +Gbp-Pq: Topic anland +Gbp-Pq: Name 0019-backends-anland-Prefer-consumer-clipboard-on-reconnect.patch + +diff --git a/src/backends/anland/meta-anland-clipboard.c b/src/backends/anland/meta-anland-clipboard.c +index a3bd5dd948..7bec4a13c6 100644 +--- a/src/backends/anland/meta-anland-clipboard.c ++++ b/src/backends/anland/meta-anland-clipboard.c +@@ -282,8 +282,12 @@ meta_anland_clipboard_set_transport (MetaAnlandClipboard *clipboard, + cancel_selection_transfer (clipboard); + clipboard->transport = transport; + +- if (transport && clipboard->cached_contents) +- send_contents (clipboard, clipboard->cached_contents); ++ /* ++ * The consumer reads the Android clipboard as part of its reconnect path. ++ * Do not race that initial sync by replaying cached compositor contents here: ++ * when the app was in the background, doing so can overwrite a newer Android ++ * clipboard before it has a chance to send it to Mutter. ++ */ + } + + gboolean diff --git a/x11-packages/mutter/0028-backends-anland-Build-headless-native-backend-withou.patch b/x11-packages/mutter/0028-backends-anland-Build-headless-native-backend-withou.patch new file mode 100644 index 00000000000..4305e549a78 --- /dev/null +++ b/x11-packages/mutter/0028-backends-anland-Build-headless-native-backend-withou.patch @@ -0,0 +1,1132 @@ +From c178be423b7ea71878fcb01cc2aa777bef10644e Mon Sep 17 00:00:00 2001 +From: lfdevs +Date: Mon, 10 Aug 2026 21:37:17 +0800 +Subject: [PATCH] backends/anland: Build headless native backend without system + input services + +Allow Anland to use the native renderer and virtual input path while the regular native backend remains disabled. In this mode, GPU discovery, logind launcher integration, UDev hotplug handling, and libinput physical-device handling are omitted. + +The Anland headless backend continues to create its core virtual devices and use the existing XKB, pointer, keyboard, and touch event paths, so Android-provided input remains available without requiring libinput on Termux. +--- + meson.build | 26 ++++--- + src/backends/anland/meta-anland-input.c | 2 +- + src/backends/native/meta-backend-native.c | 42 ++++++++++- + src/backends/native/meta-device-pool.c | 4 ++ + .../native/meta-input-device-native.c | 33 ++++++++- + .../native/meta-input-device-native.h | 10 ++- + .../native/meta-input-settings-native.c | 37 ++++++++++ + src/backends/native/meta-input-thread.h | 2 + + src/backends/native/meta-kms-impl-device.c | 6 ++ + src/backends/native/meta-kms-private.h | 2 + + src/backends/native/meta-kms.c | 12 ++++ + src/backends/native/meta-output-kms.c | 6 ++ + src/backends/native/meta-seat-impl.c | 72 +++++++++++++------ + src/backends/native/meta-seat-impl.h | 8 ++- + src/backends/native/meta-seat-native.h | 4 ++ + .../native/meta-virtual-input-device-native.c | 51 ++++--------- + src/core/meta-context-main.c | 4 +- + src/meson.build | 24 +++++-- + src/wayland/meta-wayland-drm-lease.c | 16 +++-- + 19 files changed, 274 insertions(+), 87 deletions(-) + +diff --git a/meson.build b/meson.build +index c795253988..823793776f 100644 +--- a/meson.build ++++ b/meson.build +@@ -285,13 +285,12 @@ if have_libgudev + endif + + have_native_backend = get_option('native_backend') +-if have_native_backend +- libgbm_dep = dependency('gbm', version: gbm_req) +- libinput_dep = dependency('libinput', version: libinput_req) ++have_anland = get_option('anland').enabled() ++have_anland_headless_native = have_anland and not have_native_backend ++have_libinput = have_native_backend + +- if not have_logind +- error('The native backend requires logind to be enabled') +- endif ++if have_native_backend or have_anland_headless_native ++ libgbm_dep = dependency('gbm', version: gbm_req) + + if not have_egl + error('The native backend requires EGL to be enabled') +@@ -300,17 +299,21 @@ if have_native_backend + if not have_gles2 + error('The native backend requires GLESv2 to be enabled') + endif ++endif ++ ++if have_native_backend ++ libinput_dep = dependency('libinput', version: libinput_req) ++ ++ if not have_logind ++ error('The native backend requires logind to be enabled') ++ endif + + if not have_libgudev + error('The native backend requires udev to be enabled') + endif + endif + +-have_anland = get_option('anland').enabled() + if have_anland +- if not have_native_backend +- error('The Anland backend requires the native backend') +- endif + if not have_wayland + error('The Anland backend requires Wayland support') + endif +@@ -615,7 +618,8 @@ cdata.set('HAVE_XWAYLAND', have_xwayland) + cdata.set('HAVE_X11', have_x11) + cdata.set('HAVE_X11_CLIENT', have_x11_client) + cdata.set('HAVE_LOGIND', have_logind) +-cdata.set('HAVE_NATIVE_BACKEND', have_native_backend) ++cdata.set('HAVE_NATIVE_BACKEND', have_native_backend or have_anland_headless_native) ++cdata.set('HAVE_LIBINPUT', have_libinput) + cdata.set('HAVE_ANLAND', have_anland) + cdata.set('HAVE_REMOTE_DESKTOP', have_remote_desktop) + cdata.set('HAVE_DEVKIT', have_devkit) +diff --git a/src/backends/anland/meta-anland-input.c b/src/backends/anland/meta-anland-input.c +index 00fd4dfa5b..e8554a08cc 100644 +--- a/src/backends/anland/meta-anland-input.c ++++ b/src/backends/anland/meta-anland-input.c +@@ -5,7 +5,7 @@ + #include "backends/anland/meta-anland-input.h" + + #include "backends/meta-backend-private.h" +-#include "backends/meta-logical-monitor.h" ++#include "backends/meta-logical-monitor-private.h" + #include "backends/meta-monitor-manager-private.h" + #include "backends/native/meta-virtual-input-device-native.h" + +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index baca057b34..34171cddb0 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -137,14 +137,20 @@ meta_backend_native_create_default_seat (MetaBackend *backend, + meta_backend_native_get_instance_private (backend_native); + ClutterContext *clutter_context = + meta_backend_get_clutter_context (backend); ++#ifdef HAVE_LOGIND + MetaLauncher *launcher = meta_backend_get_launcher (backend); ++#endif + const char *seat_id = NULL; + MetaSeatNativeFlag flags = META_SEAT_NATIVE_FLAG_NONE; + + switch (priv->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: ++#ifdef HAVE_LOGIND + seat_id = meta_launcher_get_seat_id (launcher); ++#else ++ g_assert_not_reached (); ++#endif + break; + case META_BACKEND_NATIVE_MODE_HEADLESS: + case META_BACKEND_NATIVE_MODE_TEST_HEADLESS: +@@ -571,6 +577,7 @@ create_render_device (MetaBackendNative *backend_native, + return NULL; + } + ++#ifdef HAVE_LIBGUDEV + static gboolean + add_drm_device (MetaBackendNative *backend_native, + GUdevDevice *device, +@@ -626,7 +633,6 @@ add_drm_device (MetaBackendNative *backend_native, + meta_backend_add_gpu (META_BACKEND (backend_native), META_GPU (gpu_kms)); + return TRUE; + } +- + static gboolean + should_ignore_device (MetaBackendNative *backend_native, + GUdevDevice *device) +@@ -789,6 +795,18 @@ init_gpus (MetaBackendNative *native, + + return TRUE; + } ++#else ++static gboolean ++init_gpus (MetaBackendNative *native, ++ GError **error) ++{ ++ MetaKms *kms = meta_backend_native_get_kms (native); ++ ++ (void) error; ++ meta_kms_notify_probed (kms); ++ return TRUE; ++} ++#endif + + static void + on_started (MetaContext *context, +@@ -809,6 +827,19 @@ meta_backend_native_create_launcher (MetaBackend *backend, + MetaBackendNative *native = META_BACKEND_NATIVE (backend); + MetaBackendNativePrivate *priv = + meta_backend_native_get_instance_private (native); ++ ++#ifndef HAVE_LOGIND ++ if (priv->mode == META_BACKEND_NATIVE_MODE_HEADLESS || ++ priv->mode == META_BACKEND_NATIVE_MODE_TEST_HEADLESS) ++ { ++ *launcher_out = NULL; ++ return TRUE; ++ } ++ ++ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "A native backend without logind must run headlessly"); ++ return FALSE; ++#else + g_autoptr (MetaLauncher) launcher = NULL; + g_autoptr (GError) local_error = NULL; + +@@ -856,6 +887,7 @@ meta_backend_native_create_launcher (MetaBackend *backend, + + *launcher_out = g_steal_pointer (&launcher); + return TRUE; ++#endif + } + + +@@ -1001,13 +1033,21 @@ meta_backend_native_activate_vt (MetaBackendNative *backend_native, + { + MetaBackendNativePrivate *priv = + meta_backend_native_get_instance_private (backend_native); ++#ifdef HAVE_LOGIND + MetaLauncher *launcher = + meta_backend_get_launcher (META_BACKEND (backend_native)); ++#endif + + switch (priv->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: ++#ifdef HAVE_LOGIND + return meta_launcher_activate_vt (launcher, vt, error); ++#else ++ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "VT switching requires logind support"); ++ return FALSE; ++#endif + case META_BACKEND_NATIVE_MODE_HEADLESS: + case META_BACKEND_NATIVE_MODE_TEST_HEADLESS: + case META_BACKEND_NATIVE_MODE_TEST_VKMS: +diff --git a/src/backends/native/meta-device-pool.c b/src/backends/native/meta-device-pool.c +index 81e3a27490..d5be433536 100644 +--- a/src/backends/native/meta-device-pool.c ++++ b/src/backends/native/meta-device-pool.c +@@ -356,15 +356,19 @@ MetaDevicePool * + meta_device_pool_new (MetaBackendNative *backend_native) + { + MetaDevicePool *pool; ++#ifdef HAVE_LOGIND + MetaLauncher *launcher; ++#endif + + pool = g_object_new (META_TYPE_DEVICE_POOL, NULL); + + pool->backend = META_BACKEND (backend_native); + ++#ifdef HAVE_LOGIND + launcher = meta_backend_get_launcher (pool->backend); + if (launcher) + pool->session_proxy = meta_launcher_get_session_proxy (launcher); ++#endif + + return pool; + } +diff --git a/src/backends/native/meta-input-device-native.c b/src/backends/native/meta-input-device-native.c +index 51b627f4a7..0b30623662 100644 +--- a/src/backends/native/meta-input-device-native.c ++++ b/src/backends/native/meta-input-device-native.c +@@ -1361,6 +1361,8 @@ meta_input_device_native_init (MetaInputDeviceNative *self) + self->height = -1; + } + ++#ifdef HAVE_LIBINPUT ++ + static void + update_pad_features (MetaInputDeviceNative *device_native) + { +@@ -1584,6 +1586,20 @@ meta_input_device_native_new_in_impl (MetaSeatImpl *seat_impl, + return CLUTTER_INPUT_DEVICE (device); + } + ++#else /* HAVE_LIBINPUT */ ++ ++ClutterInputDevice * ++meta_input_device_native_new_in_impl (MetaSeatImpl *seat_impl, ++ struct libinput_device *libinput_device) ++{ ++ (void) seat_impl; ++ (void) libinput_device; ++ ++ return NULL; ++} ++ ++#endif /* HAVE_LIBINPUT */ ++ + /* + * meta_input_device_native_new_virtual_in_impl: + * @seat: the seat the device will belong to +@@ -1628,12 +1644,17 @@ meta_input_device_native_new_virtual_in_impl (MetaSeatImpl *seat_impl, + + void + meta_input_device_native_update_leds_in_impl (MetaInputDeviceNative *device, +- enum libinput_led leds) ++ MetaInputDeviceNativeLed leds) + { ++#ifdef HAVE_LIBINPUT + if (!device->libinput_device) + return; + + libinput_device_led_update (device->libinput_device, leds); ++#else ++ (void) device; ++ (void) leds; ++#endif + } + + /** +@@ -1754,12 +1775,21 @@ meta_input_device_native_set_mapping_mode_in_impl (ClutterInputDevice *devic + void + meta_input_device_native_detach_libinput_in_impl (MetaInputDeviceNative *device_native) + { ++#ifdef HAVE_LIBINPUT + g_clear_pointer (&device_native->libinput_device, libinput_device_unref); ++#else ++ (void) device_native; ++#endif + } + + gboolean + meta_input_device_native_has_scroll_inverted (MetaInputDeviceNative *device_native) + { ++#ifndef HAVE_LIBINPUT ++ (void) device_native; ++ ++ return FALSE; ++#else + struct libinput_device *libinput_device = device_native->libinput_device; + + if (!libinput_device) +@@ -1769,4 +1799,5 @@ meta_input_device_native_has_scroll_inverted (MetaInputDeviceNative *device_nati + return FALSE; + + return !!libinput_device_config_scroll_get_natural_scroll_enabled (libinput_device); ++#endif + } +diff --git a/src/backends/native/meta-input-device-native.h b/src/backends/native/meta-input-device-native.h +index 3b0d3baa81..23122e891e 100644 +--- a/src/backends/native/meta-input-device-native.h ++++ b/src/backends/native/meta-input-device-native.h +@@ -36,6 +36,14 @@ + #include "backends/native/meta-seat-native.h" + #include "clutter/clutter-mutter.h" + ++#ifdef HAVE_LIBINPUT ++#include ++typedef enum libinput_led MetaInputDeviceNativeLed; ++#else ++struct libinput_device; ++typedef unsigned int MetaInputDeviceNativeLed; ++#endif ++ + #define META_TYPE_INPUT_DEVICE_NATIVE meta_input_device_native_get_type() + + #define META_INPUT_DEVICE_NATIVE(obj) \ +@@ -139,7 +147,7 @@ ClutterInputDevice * meta_input_device_native_new_virtual_in_impl (MetaSeat + ClutterInputMode mode); + + void meta_input_device_native_update_leds_in_impl (MetaInputDeviceNative *device, +- enum libinput_led leds); ++ MetaInputDeviceNativeLed leds); + + ClutterInputDeviceType meta_input_device_native_determine_type_in_impl (struct libinput_device *libinput_device); + +diff --git a/src/backends/native/meta-input-settings-native.c b/src/backends/native/meta-input-settings-native.c +index 09eddc3651..c8e499e706 100644 +--- a/src/backends/native/meta-input-settings-native.c ++++ b/src/backends/native/meta-input-settings-native.c +@@ -22,12 +22,47 @@ + #include "config.h" + + #include ++#ifdef HAVE_LIBINPUT + #include ++#endif + + #include "backends/native/meta-backend-native.h" + #include "backends/native/meta-input-thread.h" + #include "backends/native/meta-input-settings-native.h" + ++#ifndef HAVE_LIBINPUT ++ ++struct _MetaInputSettingsNative ++{ ++ MetaInputSettings parent_instance; ++}; ++ ++G_DEFINE_FINAL_TYPE (MetaInputSettingsNative, ++ meta_input_settings_native, ++ META_TYPE_INPUT_SETTINGS) ++ ++static void ++meta_input_settings_native_class_init (MetaInputSettingsNativeClass *klass) ++{ ++ (void) klass; ++} ++ ++static void ++meta_input_settings_native_init (MetaInputSettingsNative *settings) ++{ ++ (void) settings; ++} ++ ++MetaInputSettings * ++meta_input_settings_native_new_in_impl (MetaSeatImpl *seat_impl) ++{ ++ return g_object_new (META_TYPE_INPUT_SETTINGS_NATIVE, ++ "backend", meta_seat_impl_get_backend (seat_impl), ++ NULL); ++} ++ ++#else ++ + struct _MetaInputSettingsNative + { + MetaInputSettings parent_instance; +@@ -879,3 +914,5 @@ meta_input_settings_native_new_in_impl (MetaSeatImpl *seat_impl) + "seat-impl", seat_impl, + NULL); + } ++ ++#endif +diff --git a/src/backends/native/meta-input-thread.h b/src/backends/native/meta-input-thread.h +index 3694ed02cb..8c9845dee4 100644 +--- a/src/backends/native/meta-input-thread.h ++++ b/src/backends/native/meta-input-thread.h +@@ -22,7 +22,9 @@ + #define META_INPUT_THREAD_H_INSIDE + + #include "src/backends/native/meta-input-device-native.h" ++#ifdef HAVE_LIBINPUT + #include "src/backends/native/meta-input-device-tool-native.h" ++#endif + #include "src/backends/native/meta-input-settings-native.h" + #include "src/backends/native/meta-keymap-native-private.h" + #include "src/backends/native/meta-seat-impl.h" +diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c +index 4a53915dea..189f2bde5e 100644 +--- a/src/backends/native/meta-kms-impl-device.c ++++ b/src/backends/native/meta-kms-impl-device.c +@@ -44,7 +44,9 @@ + #include "backends/native/meta-kms-utils.h" + #include "backends/native/meta-thread-private.h" + ++#ifdef HAVE_LIBGUDEV + #include "meta-default-modes.h" ++#endif + #include "meta-private-enum-types.h" + + enum +@@ -1059,6 +1061,7 @@ init_planes (MetaKmsImplDevice *impl_device) + drmModeFreePlaneResources (drm_planes); + } + ++#ifdef HAVE_LIBGUDEV + static void + init_fallback_modes (MetaKmsImplDevice *impl_device) + { +@@ -1089,6 +1092,7 @@ init_fallback_modes (MetaKmsImplDevice *impl_device) + + priv->fallback_modes = g_list_reverse (modes); + } ++#endif + + static void + ensure_latched_fd_hold (MetaKmsImplDevice *impl_device) +@@ -2429,7 +2433,9 @@ meta_kms_impl_device_init_mode_setting (MetaKmsImplDevice *impl_device, + init_crtcs (impl_device, drm_resources); + init_planes (impl_device); + ++#ifdef HAVE_LIBGUDEV + init_fallback_modes (impl_device); ++#endif + + update_connectors (impl_device, drm_resources, 0); + +diff --git a/src/backends/native/meta-kms-private.h b/src/backends/native/meta-kms-private.h +index f92fa89ce3..843d940bdb 100644 +--- a/src/backends/native/meta-kms-private.h ++++ b/src/backends/native/meta-kms-private.h +@@ -19,7 +19,9 @@ + + #include "backends/native/meta-kms.h" + ++#ifdef HAVE_LIBGUDEV + #include ++#endif + + #include "backends/native/meta-kms-types.h" + #include "backends/native/meta-kms-types-private.h" +diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c +index f0fcc60d0b..cbd8c02f66 100644 +--- a/src/backends/native/meta-kms.c ++++ b/src/backends/native/meta-kms.c +@@ -20,7 +20,9 @@ + + #include "backends/native/meta-kms-private.h" + ++#ifdef HAVE_LIBGUDEV + #include "backends/meta-udev.h" ++#endif + #include "backends/native/meta-backend-native.h" + #include "backends/native/meta-kms-cursor-manager.h" + #include "backends/native/meta-kms-device-private.h" +@@ -286,6 +288,7 @@ meta_kms_resume (MetaKms *kms) + meta_kms_run_impl_task_sync (kms, resume_in_impl, NULL, NULL); + } + ++#ifdef HAVE_LIBGUDEV + static char * + hotplug_event_from_udev_device (GUdevDevice *udev_device) + { +@@ -338,6 +341,7 @@ on_udev_lease (MetaUdev *udev, + { + g_signal_emit (kms, signals[LEASE_CHANGED], 0); + } ++#endif + + MetaBackend * + meta_kms_get_backend (MetaKms *kms) +@@ -406,7 +410,9 @@ meta_kms_new (MetaBackend *backend, + MetaKmsFlags flags, + GError **error) + { ++#ifdef HAVE_LIBGUDEV + MetaUdev *udev = meta_backend_get_udev (backend); ++#endif + MetaKms *kms; + const char *thread_type_string; + const char *preferred_scheduling_priority_string; +@@ -459,6 +465,7 @@ meta_kms_new (MetaBackend *backend, + NULL); + kms->flags = flags; + ++#ifdef HAVE_LIBGUDEV + if (!(flags & META_KMS_FLAG_NO_MODE_SETTING)) + { + kms->hotplug_handler_id = +@@ -470,6 +477,7 @@ meta_kms_new (MetaBackend *backend, + kms->removed_handler_id = + g_signal_connect (udev, "device-removed", + G_CALLBACK (on_udev_device_removed), kms); ++#endif + + g_signal_connect (backend, "prepare-shutdown", + G_CALLBACK (on_prepare_shutdown), +@@ -499,14 +507,18 @@ static void + meta_kms_finalize (GObject *object) + { + MetaKms *kms = META_KMS (object); ++#ifdef HAVE_LIBGUDEV + MetaBackend *backend = meta_thread_get_backend (META_THREAD (kms)); + MetaUdev *udev = meta_backend_get_udev (backend); ++#endif + + g_list_free_full (kms->devices, g_object_unref); + ++#ifdef HAVE_LIBGUDEV + g_clear_signal_handler (&kms->hotplug_handler_id, udev); + g_clear_signal_handler (&kms->lease_handler_id, udev); + g_clear_signal_handler (&kms->removed_handler_id, udev); ++#endif + + G_OBJECT_CLASS (meta_kms_parent_class)->finalize (object); + } +diff --git a/src/backends/native/meta-output-kms.c b/src/backends/native/meta-output-kms.c +index be725f66ed..9d33be5ffa 100644 +--- a/src/backends/native/meta-output-kms.c ++++ b/src/backends/native/meta-output-kms.c +@@ -26,7 +26,9 @@ + #include + #include + ++#ifdef HAVE_LIBGUDEV + #include "backends/meta-backlight-sysfs-private.h" ++#endif + #include "backends/meta-backlight-ref-white-private.h" + #include "backends/meta-color-device.h" + #include "backends/meta-color-manager.h" +@@ -380,6 +382,7 @@ meta_kms_connector_type_from_drm (uint32_t drm_connector_type) + return (MetaConnectorType) drm_connector_type; + } + ++#ifdef HAVE_LIBGUDEV + static MetaBacklight * + meta_output_kms_create_backlight (MetaOutput *output, + GError **error) +@@ -434,6 +437,7 @@ meta_output_kms_create_backlight (MetaOutput *output, + + return META_BACKLIGHT (g_steal_pointer (&backlight_sysfs)); + } ++#endif + + MetaOutputKms * + meta_output_kms_new (MetaGpuKms *gpu_kms, +@@ -642,7 +646,9 @@ meta_output_kms_class_init (MetaOutputKmsClass *klass) + + output_class->get_privacy_screen_state = + meta_output_kms_get_privacy_screen_state; ++#ifdef HAVE_LIBGUDEV + output_class->create_backlight = meta_output_kms_create_backlight; ++#endif + + output_native_class->read_edid = meta_output_kms_read_edid; + +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 68e2b0468f..f5b3bebe9d 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -29,7 +29,9 @@ + + #include + #include ++#ifdef HAVE_LIBINPUT + #include ++#endif + #include + #include + +@@ -94,7 +96,7 @@ enum + + static guint signals[N_SIGNALS] = { 0 }; + +-/* Index matches libinput_led bit offsets */ ++/* Index matches native LED bit offsets. */ + typedef enum _MetaKeyboardLed MetaKeyboardLed; + enum _MetaKeyboardLed + { +@@ -141,7 +143,9 @@ G_DEFINE_TYPE_WITH_CODE (MetaSeatImpl, meta_seat_impl, G_TYPE_OBJECT, + meta_seat_impl_initable_iface_init) + G_ADD_PRIVATE (MetaSeatImpl)) + ++#ifdef HAVE_LIBINPUT + static void process_events (MetaSeatImpl *seat_impl); ++#endif + static void meta_seat_impl_constrain_pointer (MetaSeatImpl *seat_impl, + uint64_t time_us, + graphene_point_t prev, +@@ -178,7 +182,7 @@ meta_seat_impl_sync_leds_in_impl (MetaSeatImpl *seat_impl) + meta_seat_impl_get_instance_private (seat_impl); + GSList *iter; + MetaInputDeviceNative *device_native; +- enum libinput_led leds = 0; ++ MetaInputDeviceNativeLed leds = 0; + int i; + + for (i = 0; i < N_KEYBOARD_LEDS; i++) +@@ -368,6 +372,7 @@ meta_seat_impl_clear_repeat_source (MetaSeatImpl *seat_impl) + g_clear_object (&seat_impl->repeat_device); + } + ++#ifdef HAVE_LIBINPUT + static void + dispatch_libinput (MetaSeatImpl *seat_impl) + { +@@ -378,12 +383,14 @@ dispatch_libinput (MetaSeatImpl *seat_impl) + + process_events (seat_impl); + } ++#endif + + static gboolean + keyboard_repeat (gpointer data) + { + MetaSeatImpl *seat_impl = data; + ++#ifdef HAVE_LIBINPUT + /* There might be events queued in libinput that could cancel the + repeat timer. */ + if (seat_impl->libinput) +@@ -392,6 +399,7 @@ keyboard_repeat (gpointer data) + if (!seat_impl->repeat_source) + return G_SOURCE_REMOVE; + } ++#endif + + g_return_val_if_fail (seat_impl->repeat_device != NULL, G_SOURCE_REMOVE); + +@@ -992,32 +1000,28 @@ meta_seat_impl_notify_absolute_relative_motion_in_impl (MetaSeatImpl *seat + META_INPUT_DEVICE_NATIVE (input_device); + ClutterModifierType modifiers; + ClutterEvent *event; +- float cur_x, cur_y; ++ graphene_point_t coords, new_coords; + float dx_constrained, dy_constrained; + ++ meta_seat_impl_get_onscreen_coords_for_source_device (seat_impl, ++ input_device, ++ &coords); ++ new_coords = GRAPHENE_POINT_INIT (x, y); ++ constrain_coordinates (seat_impl, input_device, time_us, ++ coords, &new_coords); ++ dx_constrained = new_coords.x - coords.x; ++ dy_constrained = new_coords.y - coords.y; ++ update_device_coords_in_impl (seat_impl, input_device, new_coords); ++ + if (clutter_input_device_get_device_type (input_device) == CLUTTER_TABLET_DEVICE) +- { +- meta_input_device_native_get_coords_in_impl (device_native, &cur_x, &cur_y); +- modifiers = device_native->button_state; +- } ++ modifiers = device_native->button_state; + else +- { +- meta_input_device_native_get_coords_in_impl ( +- META_INPUT_DEVICE_NATIVE (seat_impl->core_pointer), &cur_x, &cur_y); +- modifiers = seat_impl->button_state; +- } +- +- constrain_coordinates (seat_impl, input_device, time_us, x, y, &x, &y); +- dx_constrained = x - cur_x; +- dy_constrained = y - cur_y; +- update_device_coords_in_impl (seat_impl, input_device, +- GRAPHENE_POINT_INIT (x, y)); ++ modifiers = seat_impl->button_state; + + modifiers |= xkb_state_serialize_mods (seat_impl->xkb, XKB_STATE_MODS_EFFECTIVE); + + g_signal_emit (seat_impl, signals[POINTER_POSITION_CHANGED_IN_IMPL], 0, +- &GRAPHENE_POINT_INIT (seat_impl->pointer_x, +- seat_impl->pointer_y)); ++ &new_coords); + + event = + clutter_event_motion_new (CLUTTER_EVENT_FLAG_RELATIVE_MOTION, +@@ -1025,7 +1029,7 @@ meta_seat_impl_notify_absolute_relative_motion_in_impl (MetaSeatImpl *seat + input_device, + device_native->last_tool, + modifiers, +- GRAPHENE_POINT_INIT (x, y), ++ new_coords, + GRAPHENE_POINT_INIT (dx, dy), + GRAPHENE_POINT_INIT (dx_unaccel, + dy_unaccel), +@@ -1066,6 +1070,7 @@ meta_seat_impl_notify_button_in_impl (MetaSeatImpl *seat_impl, + return; + } + ++#ifdef HAVE_LIBINPUT + if (device_native->last_tool) + { + GDesktopStylusButtonAction action; +@@ -1105,6 +1110,7 @@ meta_seat_impl_notify_button_in_impl (MetaSeatImpl *seat_impl, + } + } + else ++#endif + { + button_nr = meta_evdev_button_to_clutter (button); + if (button_nr < 1 || button_nr > 12) +@@ -1676,6 +1682,8 @@ notify_absolute_motion_in_impl (ClutterInputDevice *input_device, + axes); + } + ++#ifdef HAVE_LIBINPUT ++ + static void + notify_relative_tool_motion_in_impl (ClutterInputDevice *input_device, + uint64_t time_us, +@@ -1923,6 +1931,8 @@ notify_pad_dial (ClutterInputDevice *input_device, + queue_event (seat_impl, event); + } + ++#endif /* HAVE_LIBINPUT */ ++ + static gboolean + has_touchscreen (MetaSeatImpl *seat_impl) + { +@@ -1968,6 +1978,11 @@ has_pointer (MetaSeatImpl *seat_impl) + static gboolean + device_is_tablet_switch (MetaInputDeviceNative *device_native) + { ++#ifndef HAVE_LIBINPUT ++ (void) device_native; ++ ++ return FALSE; ++#else + if (!device_native->libinput_device) + return FALSE; + +@@ -1978,6 +1993,7 @@ device_is_tablet_switch (MetaInputDeviceNative *device_native) + return TRUE; + + return FALSE; ++#endif + } + + static gboolean +@@ -2105,6 +2121,7 @@ meta_seat_impl_remove_device (MetaSeatImpl *seat_impl, + g_object_unref (device); + } + ++#ifdef HAVE_LIBINPUT + static gboolean + process_base_event (MetaSeatImpl *seat_impl, + struct libinput_event *event) +@@ -3060,6 +3077,7 @@ static const struct libinput_interface libinput_interface = { + open_restricted, + close_restricted + }; ++#endif + + static void + kbd_a11y_changed_cb (MetaInputSettings *input_settings, +@@ -3116,6 +3134,7 @@ meta_seat_impl_set_keyboard_numlock_in_impl (MetaSeatImpl *seat_impl, + seat_impl->xkb); + } + ++#ifdef HAVE_LIBINPUT + static gboolean + meta_libinput_source_prepare (gpointer user_data) + { +@@ -3198,6 +3217,7 @@ init_libinput_source (MetaSeatImpl *seat_impl) + g_source_attach (source, seat_impl->input_context); + g_source_unref (source); + } ++#endif + + static void + init_core_devices (MetaSeatImpl *seat_impl) +@@ -3291,6 +3311,7 @@ input_thread (MetaSeatImpl *seat_impl) + if (meta_input_settings_maybe_restore_numlock_state (seat_impl->input_settings)) + meta_seat_impl_set_keyboard_numlock_in_impl (seat_impl, TRUE); + ++#ifdef HAVE_LIBINPUT + if (!(seat_impl->flags & META_SEAT_NATIVE_FLAG_NO_LIBINPUT)) + { + g_autoptr (GError) error = NULL; +@@ -3302,6 +3323,7 @@ input_thread (MetaSeatImpl *seat_impl) + return NULL; + } + } ++#endif + + seat_impl->has_touchscreen = has_touchscreen (seat_impl); + seat_impl->has_tablet_switch = has_tablet_switch (seat_impl); +@@ -3411,7 +3433,9 @@ destroy_in_impl (GTask *task) + g_slist_free_full (seat_impl->devices, g_object_unref); + seat_impl->devices = NULL; + ++#ifdef HAVE_LIBINPUT + g_clear_pointer (&seat_impl->libinput, libinput_unref); ++#endif + g_clear_pointer (&seat_impl->tools, g_hash_table_unref); + g_clear_pointer (&priv->touch_states, g_hash_table_destroy); + g_clear_pointer (&seat_impl->libinput_source, g_source_destroy); +@@ -3780,8 +3804,10 @@ release_devices (GTask *task) + } + else + { ++#ifdef HAVE_LIBINPUT + libinput_suspend (seat_impl->libinput); + process_events (seat_impl); ++#endif + + seat_impl->released = TRUE; + } +@@ -3821,9 +3847,11 @@ reclaim_devices (GTask *task) + + if (seat_impl->released) + { ++#ifdef HAVE_LIBINPUT + libinput_resume (seat_impl->libinput); + meta_seat_impl_update_xkb_state_in_impl (seat_impl); + process_events (seat_impl); ++#endif + + seat_impl->released = FALSE; + } +@@ -4251,8 +4279,10 @@ start_in_impl (GTask *task) + { + MetaSeatImpl *seat_impl = g_task_get_source_object (task); + ++#ifdef HAVE_LIBINPUT + if (seat_impl->libinput) + init_libinput_source (seat_impl); ++#endif + + g_task_return_boolean (task, TRUE); + +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index a995244f31..05529cb9c5 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -26,9 +26,15 @@ + #error "This header cannot be included directly. Use "backends/native/meta-input-thread.h"" + #endif /* META_INPUT_THREAD_H_INSIDE */ + ++#include ++ ++#ifdef HAVE_LIBINPUT + #include + #include +-#include ++#else ++struct libinput; ++struct libinput_device; ++#endif + + #include "backends/meta-input-settings-private.h" + #include "backends/meta-viewport-info.h" +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index 9f300bad79..47a53b9811 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -22,7 +22,11 @@ + + #pragma once + ++#ifdef HAVE_LIBINPUT + #include ++#else ++struct libinput_seat; ++#endif + #include + + #include "backends/meta-input-settings-private.h" +diff --git a/src/backends/native/meta-virtual-input-device-native.c b/src/backends/native/meta-virtual-input-device-native.c +index 20536dbb08..3d0606a0c5 100644 +--- a/src/backends/native/meta-virtual-input-device-native.c ++++ b/src/backends/native/meta-virtual-input-device-native.c +@@ -1053,54 +1053,33 @@ notify_touch_events_in_impl (GTask *task) + { + const MetaVirtualTouchEvent *event = + &g_array_index (events, MetaVirtualTouchEvent, i); +- MetaTouchState *touch_state; ++ ClutterEventType event_type; + int device_slot = virtual_native->slot_base + event->slot; + + switch (event->type) + { + case META_VIRTUAL_TOUCH_EVENT_DOWN: +- touch_state = meta_seat_impl_acquire_touch_state_in_impl (seat, +- device_slot); +- if (!touch_state) +- continue; +- +- touch_state->coords.x = (float) event->x; +- touch_state->coords.y = (float) event->y; +- meta_seat_impl_notify_touch_event_in_impl ( +- seat, virtual_native->impl_state->device, CLUTTER_TOUCH_BEGIN, +- time_us, touch_state->seat_slot, touch_state->coords.x, +- touch_state->coords.y); ++ event_type = CLUTTER_TOUCH_BEGIN; + break; + case META_VIRTUAL_TOUCH_EVENT_MOTION: +- touch_state = meta_seat_impl_lookup_touch_state_in_impl (seat, +- device_slot); +- if (!touch_state) +- continue; +- +- touch_state->coords.x = (float) event->x; +- touch_state->coords.y = (float) event->y; +- meta_seat_impl_notify_touch_event_in_impl ( +- seat, virtual_native->impl_state->device, CLUTTER_TOUCH_UPDATE, +- time_us, touch_state->seat_slot, touch_state->coords.x, +- touch_state->coords.y); ++ event_type = CLUTTER_TOUCH_UPDATE; + break; + case META_VIRTUAL_TOUCH_EVENT_UP: ++ event_type = CLUTTER_TOUCH_END; ++ break; + case META_VIRTUAL_TOUCH_EVENT_CANCEL: +- touch_state = meta_seat_impl_lookup_touch_state_in_impl (seat, +- device_slot); +- if (!touch_state) +- continue; +- +- meta_seat_impl_notify_touch_event_in_impl ( +- seat, virtual_native->impl_state->device, +- event->type == META_VIRTUAL_TOUCH_EVENT_UP ? CLUTTER_TOUCH_END : +- CLUTTER_TOUCH_CANCEL, +- time_us, touch_state->seat_slot, touch_state->coords.x, +- touch_state->coords.y); +- meta_seat_impl_release_touch_state_in_impl (seat, +- touch_state->seat_slot); ++ event_type = CLUTTER_TOUCH_CANCEL; + break; ++ default: ++ continue; + } ++ ++ meta_seat_impl_notify_touch_event_in_impl (seat, ++ virtual_native->impl_state->device, ++ event_type, time_us, ++ device_slot, ++ (float) event->x, ++ (float) event->y); + } + + g_task_return_boolean (task, TRUE); +diff --git a/src/core/meta-context-main.c b/src/core/meta-context-main.c +index 2628ba9b43..8e2d8b3e51 100644 +--- a/src/core/meta-context-main.c ++++ b/src/core/meta-context-main.c +@@ -200,7 +200,7 @@ check_configuration (MetaContextMain *context_main, + return TRUE; + } + +-#if defined(HAVE_WAYLAND) && defined(HAVE_NATIVE_BACKEND) ++#if defined(HAVE_WAYLAND) && defined(HAVE_NATIVE_BACKEND) && defined(HAVE_LOGIND) + static gboolean + session_type_is_supported (const char *session_type) + { +@@ -281,7 +281,7 @@ find_session_type (GError **error) + { + return g_strdup ("x11"); + } +-#endif /* defined(HAVE_WAYLAND) && defined(HAVE_NATIVE_BACKEND) */ ++#endif /* defined(HAVE_WAYLAND) && defined(HAVE_NATIVE_BACKEND) && defined(HAVE_LOGIND) */ + + static MetaCompositorType + determine_compositor_type (MetaContextMain *context_main, +diff --git a/src/meson.build b/src/meson.build +index 0b38fe144e..9602992acf 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -139,14 +139,19 @@ if have_logind + ] + endif + +-if have_native_backend ++if have_native_backend or have_anland_headless_native + mutter_pkg_private_deps += [ +- libinput_dep, + libgbm_dep, + xkbcommon_dep, + ] + endif + ++if have_libinput ++ mutter_pkg_private_deps += [ ++ libinput_dep, ++ ] ++endif ++ + if have_wayland or have_native_backend + mutter_pkg_private_deps += [ + libdrm_dep, +@@ -769,7 +774,7 @@ if have_wayland + ] + endif + +- if have_native_backend ++ if have_native_backend or have_anland_headless_native + mutter_sources += [ + 'wayland/meta-wayland-drm-lease.c', + 'wayland/meta-wayland-drm-lease.h', +@@ -777,7 +782,7 @@ if have_wayland + endif + endif + +-if have_native_backend ++if have_native_backend or have_anland_headless_native + mutter_sources += [ + 'backends/native/meta-backend-native.c', + 'backends/native/meta-backend-native.h', +@@ -821,8 +826,6 @@ if have_native_backend + 'backends/native/meta-frame-native.h', + 'backends/native/meta-input-device-native.c', + 'backends/native/meta-input-device-native.h', +- 'backends/native/meta-input-device-tool-native.c', +- 'backends/native/meta-input-device-tool-native.h', + 'backends/native/meta-input-settings-native.c', + 'backends/native/meta-input-settings-native.h', + 'backends/native/meta-keymap-native-private.h', +@@ -919,6 +922,13 @@ if have_native_backend + 'compositor/meta-compositor-view-native.c', + 'compositor/meta-compositor-view-native.h', + ] ++ ++ if have_libinput ++ mutter_sources += [ ++ 'backends/native/meta-input-device-tool-native.c', ++ 'backends/native/meta-input-device-tool-native.h', ++ ] ++ endif + endif + + if have_devkit +@@ -992,7 +1002,7 @@ if have_remote_desktop + ] + endif + +-if have_native_backend ++if have_native_backend or have_anland_headless_native + mutter_private_enum_sources += [ + 'backends/native/meta-backend-native-types.h', + 'backends/native/meta-drm-buffer.h', +diff --git a/src/wayland/meta-wayland-drm-lease.c b/src/wayland/meta-wayland-drm-lease.c +index 20ee4d979d..62b76e9003 100644 +--- a/src/wayland/meta-wayland-drm-lease.c ++++ b/src/wayland/meta-wayland-drm-lease.c +@@ -35,7 +35,9 @@ + #include "backends/native/meta-kms-impl-device.h" + #include "backends/native/meta-kms.h" + #include "backends/edid.h" ++#ifdef HAVE_LOGIND + #include "backends/meta-launcher.h" ++#endif + #include "wayland/meta-wayland-private.h" + + #include "drm-lease-v1-server-protocol.h" +@@ -666,6 +668,7 @@ on_device_removed (MetaDrmLeaseManager *drm_lease_manager, + g_hash_table_remove (lease_manager->devices, kms_device); + } + ++#ifdef HAVE_LOGIND + static void + send_pending_on_device_bind_events (MetaWaylandDrmLeaseManager *lease_manager, + MetaWaylandDrmLeaseDevice *lease_device) +@@ -708,6 +711,7 @@ on_active_session_changed (MetaLauncher *launcher, + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &lease_device)) + send_pending_on_device_bind_events (lease_manager, lease_device); + } ++#endif + + static void + on_connector_added (MetaDrmLeaseManager *drm_lease_manager, +@@ -779,7 +783,9 @@ meta_wayland_drm_lease_manager_new (MetaWaylandCompositor *compositor) + { + MetaContext *context = meta_wayland_compositor_get_context (compositor); + MetaBackend *backend = meta_context_get_backend (context); ++#ifdef HAVE_LOGIND + MetaLauncher *launcher = meta_backend_get_launcher (backend); ++#endif + MetaWaylandDrmLeaseManager *lease_manager; + MetaDrmLeaseManager *drm_lease_manager; + +@@ -817,12 +823,12 @@ meta_wayland_drm_lease_manager_new (MetaWaylandCompositor *compositor) + G_CALLBACK (on_connector_removed), + lease_manager); + ++#ifdef HAVE_LOGIND + if (launcher) +- { +- g_signal_connect (launcher, "notify::session-active", +- G_CALLBACK (on_active_session_changed), +- lease_manager); +- } ++ g_signal_connect (launcher, "notify::session-active", ++ G_CALLBACK (on_active_session_changed), ++ lease_manager); ++#endif + + return lease_manager; + } +-- +2.47.3 + diff --git a/x11-packages/mutter/build.sh b/x11-packages/mutter/build.sh index 2c5c05ec144..342dfb144d6 100644 --- a/x11-packages/mutter/build.sh +++ b/x11-packages/mutter/build.sh @@ -2,11 +2,12 @@ TERMUX_PKG_HOMEPAGE=https://mutter.gnome.org/ TERMUX_PKG_DESCRIPTION="A Wayland display server and X11 window manager and compositor library" TERMUX_PKG_LICENSE="GPL-2.0-or-later" TERMUX_PKG_MAINTAINER="@termux" -TERMUX_PKG_VERSION="48.1" +TERMUX_PKG_VERSION="49.3" TERMUX_PKG_REVISION=1 TERMUX_PKG_SRCURL="https://download.gnome.org/sources/mutter/${TERMUX_PKG_VERSION%%.*}/mutter-$TERMUX_PKG_VERSION.tar.xz" -TERMUX_PKG_SHA256=e9970b3d81c76aed1e91f3436a82e7b14c5e32cf55949b1c9a18d0f157bacafc +TERMUX_PKG_SHA256=9ef1f61d6fe401cca3fcbe6d9a3b2a4f9b42638cbf8443735666a61964a8f0c3 TERMUX_PKG_DEPENDS="atk, fribidi, gdk-pixbuf, glib, gnome-desktop4, gobject-introspection, graphene, gsettings-desktop-schemas, gtk4, harfbuzz, libandroid-shmem, libcairo, libcanberra, libcolord, libdisplay-info, libdrm, libei, libice, libpixman, libsm, libwayland, libx11, libxau, libxcb, libxcomposite, libxcursor, libxdamage, libxext, libxfixes, libxi, libxinerama, libxkbcommon, libxkbfile, libxrandr, libxtst, littlecms, opengl, pango, pipewire, startup-notification, xkeyboard-config, xwayland" +TERMUX_PKG_SUGGESTS="anland" TERMUX_PKG_BUILD_DEPENDS="glib-cross, libwayland-protocols" TERMUX_PKG_VERSIONED_GIR=false @@ -15,18 +16,21 @@ TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" -Dlogind=false -Dudev=false -Dnative_backend=false +-Danland=enabled -Dlibwacom=false -Dintrospection=true -Dtests=disabled -Dinstalled_tests=false -Dbash_completion=false -Dprofiler=false +-Dx11=true -Dxwayland_path=$TERMUX_PREFIX/bin/Xwayland " termux_step_pre_configure() { termux_setup_gir - termux_setup_glib_cross_pkg_config_wrapper + termux_setup_pkg_config_wrapper "${TERMUX_PREFIX}/opt/glib/cross/lib/x86_64-linux-gnu/pkgconfig:${TERMUX_PREFIX}/opt/libwayland/cross/lib/x86_64-linux-gnu/pkgconfig" + export TERMUX_MESON_ENABLE_SOVERSION=1 LDFLAGS+=" -landroid-shmem" diff --git a/x11-packages/mutter/gir/Clutter-17.xml b/x11-packages/mutter/gir/Clutter-17.xml new file mode 100644 index 00000000000..2f07a72ca88 --- /dev/null +++ b/x11-packages/mutter/gir/Clutter-17.xml @@ -0,0 +1,1142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/mutter/gir/Cogl-17.xml b/x11-packages/mutter/gir/Cogl-17.xml new file mode 100644 index 00000000000..d729c76328a --- /dev/null +++ b/x11-packages/mutter/gir/Cogl-17.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/mutter/gir/Meta-17.xml b/x11-packages/mutter/gir/Meta-17.xml new file mode 100644 index 00000000000..2de3f929d1b --- /dev/null +++ b/x11-packages/mutter/gir/Meta-17.xml @@ -0,0 +1,975 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/mutter/gir/Mtk-17.xml b/x11-packages/mutter/gir/Mtk-17.xml new file mode 100644 index 00000000000..bd62f252cb1 --- /dev/null +++ b/x11-packages/mutter/gir/Mtk-17.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/x11-packages/nautilus/0001-getpwent.patch b/x11-packages/nautilus/0001-getpwent.patch new file mode 100644 index 00000000000..e243aaba1ba --- /dev/null +++ b/x11-packages/nautilus/0001-getpwent.patch @@ -0,0 +1,35 @@ +From 4726c280cea4ef689119415aa96ccf98b9229aeb Mon Sep 17 00:00:00 2001 +From: Md Arif <111168803+sabamdarif@users.noreply.github.com> +Date: Fri, 11 Jul 2025 10:12:34 +0530 +Subject: [PATCH] getpwent + +--- + src/nautilus-file.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/nautilus-file.c b/src/nautilus-file.c +index 5a7629c..8270569 100644 +--- a/src/nautilus-file.c ++++ b/src/nautilus-file.c +@@ -5846,6 +5846,10 @@ nautilus_file_set_owner (NautilusFile *file, + GList * + nautilus_get_user_names (void) + { ++#ifdef __ANDROID__ ++ /* Not supported on Android. */ ++ return NULL; ++#else + GList *list; + char *real_name, *name; + struct passwd *user; +@@ -5872,6 +5876,7 @@ nautilus_get_user_names (void) + endpwent (); + + return g_list_sort (list, (GCompareFunc) g_utf8_collate); ++#endif + } + + /** +-- +2.48.1 + diff --git a/x11-packages/nautilus/0002-fix-paths.patch b/x11-packages/nautilus/0002-fix-paths.patch new file mode 100644 index 00000000000..5478ca13be9 --- /dev/null +++ b/x11-packages/nautilus/0002-fix-paths.patch @@ -0,0 +1,186 @@ +From 27516506261ce834974e6efa54fcfa5b42685a0b Mon Sep 17 00:00:00 2001 +From: Md Arif <111168803+sabamdarif@users.noreply.github.com> +Date: Thu, 10 Jul 2025 12:42:34 +0530 +Subject: [PATCH] fix paths + +--- + data/check-potfiles.sh | 2 +- + data/lineup-parameters | 2 +- + .../display/test-nautilus-directory-async.c | 2 +- + test/automated/displayless/test-directory.c | 14 +++++++------- + .../automated/displayless/test-file-metadata.c | 2 +- + ...file-utilities-get-common-filename-prefix.c | 2 +- + test/automated/displayless/test-file.c | 18 +++++++++--------- + 7 files changed, 21 insertions(+), 21 deletions(-) + +--- a/build-aux/check-potfiles.sh ++++ b/build-aux/check-potfiles.sh +@@ -1,4 +1,4 @@ +-#!/usr/bin/env bash ++#!@TERMUX_PREFIX@/bin/bash + + srcdirs="eel extensions src libnautilus-extension" + uidirs="src/resources/ui src/gtk" +--- a/build-aux/lineup-parameters ++++ b/build-aux/lineup-parameters +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python3 ++#!@TERMUX_PREFIX@/bin/python3 + # + # Copyright © 2019 Michael Catanzaro + # +diff --git a/test/automated/display/test-nautilus-directory-async.c b/test/automated/display/test-nautilus-directory-async.c +index a4dc24e..4f27c78 100644 +--- a/test/automated/display/test-nautilus-directory-async.c ++++ b/test/automated/display/test-nautilus-directory-async.c +@@ -68,7 +68,7 @@ main (int argc, + + if (argv[1] == NULL) + { +- uri = "file:///tmp"; ++ uri = "file://@TERMUX_PREFIX@/tmp"; + } + else + { +diff --git a/test/automated/displayless/test-directory.c b/test/automated/displayless/test-directory.c +index 886bad7..ef3b22d 100644 +--- a/test/automated/displayless/test-directory.c ++++ b/test/automated/displayless/test-directory.c +@@ -12,19 +12,19 @@ static int data_dummy; + static void + test_directory_duplicate_pointers (void) + { +- g_autoptr (NautilusDirectory) directory = nautilus_directory_get_by_uri ("file:///etc"); +- g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file:///etc/passwd"); ++ g_autoptr (NautilusDirectory) directory = nautilus_directory_get_by_uri ("file://@TERMUX_PREFIX@/etc"); ++ g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file://@TERMUX_PREFIX@/etc/passwd"); + + /* Assert that the NautilusFile reuses the existing NautilusDirectory instance.. */ + g_assert_cmpuint (nautilus_directory_number_outstanding (), ==, 1); + +- g_assert_true (nautilus_directory_get_by_uri ("file:///etc") == directory); ++ g_assert_true (nautilus_directory_get_by_uri ("file://@TERMUX_PREFIX@/etc") == directory); + nautilus_directory_unref (directory); + +- g_assert_true (nautilus_directory_get_by_uri ("file:///etc/") == directory); ++ g_assert_true (nautilus_directory_get_by_uri ("file://@TERMUX_PREFIX@/etc/") == directory); + nautilus_directory_unref (directory); + +- g_assert_true (nautilus_directory_get_by_uri ("file:///etc////") == directory); ++ g_assert_true (nautilus_directory_get_by_uri ("file://@TERMUX_PREFIX@/etc////") == directory); + nautilus_directory_unref (directory); + } + +@@ -32,7 +32,7 @@ test_directory_duplicate_pointers (void) + static void + test_directory_hash_table_cleanup (void) + { +- NautilusDirectory *directory = nautilus_directory_get_by_uri ("file:///etc"); ++ NautilusDirectory *directory = nautilus_directory_get_by_uri ("file://@TERMUX_PREFIX@/etc"); + + g_assert_cmpuint (nautilus_directory_number_outstanding (), ==, 1); + +@@ -68,7 +68,7 @@ got_files_callback (NautilusDirectory *directory, + static void + test_directory_call_when_ready (void) + { +- g_autoptr (NautilusDirectory) directory = nautilus_directory_get_by_uri ("file:///etc"); ++ g_autoptr (NautilusDirectory) directory = nautilus_directory_get_by_uri ("file://@TERMUX_PREFIX@/etc"); + g_assert_cmpuint (nautilus_directory_number_outstanding (), ==, 1); + + got_files_flag = FALSE; +diff --git a/test/automated/displayless/test-file-metadata.c b/test/automated/displayless/test-file-metadata.c +index 86aadc8..342cbe1 100644 +--- a/test/automated/displayless/test-file-metadata.c ++++ b/test/automated/displayless/test-file-metadata.c +@@ -4,7 +4,7 @@ + #include + #include + +-static const char *TEST_FILE = "file:///etc/passwd"; ++static const char *TEST_FILE = "file://@TERMUX_PREFIX@/etc/passwd"; + static const char *KEY_BOOL = NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED; + static const char *KEY_STR = NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY; + +diff --git a/test/automated/displayless/test-file-utilities-get-common-filename-prefix.c b/test/automated/displayless/test-file-utilities-get-common-filename-prefix.c +index 186ec91..4c15efa 100644 +--- a/test/automated/displayless/test-file-utilities-get-common-filename-prefix.c ++++ b/test/automated/displayless/test-file-utilities-get-common-filename-prefix.c +@@ -363,7 +363,7 @@ get_files_for_names (const GStrv files_names) + for (gsize i = 0; i < len; i++) + { + gchar *name = files_names[i]; +- g_autofree gchar *uri = g_strconcat ("file:///", name, NULL); ++ g_autofree gchar *uri = g_strconcat ("file://@TERMUX_PREFIX@/", name, NULL); + NautilusFile *file = nautilus_file_get_by_uri (uri); + + if (name[strlen (name) - 1] == G_DIR_SEPARATOR) +diff --git a/test/automated/displayless/test-file.c b/test/automated/displayless/test-file.c +index 1dfba7f..01c2516 100644 +--- a/test/automated/displayless/test-file.c ++++ b/test/automated/displayless/test-file.c +@@ -11,7 +11,7 @@ test_file_refcount_single_file (void) + { + g_assert_cmpint (nautilus_directory_number_outstanding (), ==, 0); + +- NautilusFile *file = nautilus_file_get_by_uri ("file:///home/"); ++ NautilusFile *file = nautilus_file_get_by_uri ("file://@TERMUX_HOME@/"); + + g_assert_cmpint (G_OBJECT (file)->ref_count, ==, 1); + g_assert_cmpint (G_OBJECT (file->details->directory)->ref_count, ==, 1); +@@ -25,7 +25,7 @@ test_file_refcount_single_file (void) + static void + test_file_check_name_bland (void) + { +- g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file:///home"); ++ g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file://@TERMUX_HOME@/"); + const char *name = nautilus_file_get_name (file); + g_assert_cmpstr (name, ==, "home"); + } +@@ -33,7 +33,7 @@ test_file_check_name_bland (void) + static void + test_file_check_name_trailing_slash (void) + { +- g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file:///home/"); ++ g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file://@TERMUX_HOME@/"); + const char *name = nautilus_file_get_name (file); + g_assert_cmpstr (name, ==, "home"); + } +@@ -41,20 +41,20 @@ test_file_check_name_trailing_slash (void) + static void + test_file_duplicate_pointers (void) + { +- g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file:///home/"); ++ g_autoptr (NautilusFile) file = nautilus_file_get_by_uri ("file://@TERMUX_HOME@/"); + +- g_assert_true (nautilus_file_get_by_uri ("file:///home/") == file); ++ g_assert_true (nautilus_file_get_by_uri ("file://@TERMUX_HOME@/") == file); + nautilus_file_unref (file); + +- g_assert_true (nautilus_file_get_by_uri ("file:///home") == file); ++ g_assert_true (nautilus_file_get_by_uri ("file://@TERMUX_HOME@") == file); + nautilus_file_unref (file); + } + + static void + test_file_sort_order (void) + { +- g_autoptr (NautilusFile) file_1 = nautilus_file_get_by_uri ("file:///etc"); +- g_autoptr (NautilusFile) file_2 = nautilus_file_get_by_uri ("file:///usr"); ++ g_autoptr (NautilusFile) file_1 = nautilus_file_get_by_uri ("file://@TERMUX_PREFIX@/etc"); ++ g_autoptr (NautilusFile) file_2 = nautilus_file_get_by_uri ("file://@TERMUX_PREFIX@"); + NautilusFileSortType sort_type = NAUTILUS_FILE_SORT_BY_DISPLAY_NAME; + + g_assert_cmpint (G_OBJECT (file_1)->ref_count, ==, 1); +@@ -70,7 +70,7 @@ test_file_sort_order (void) + static void + test_file_sort_with_self (void) + { +- g_autoptr (NautilusFile) file_1 = nautilus_file_get_by_uri ("file:///etc"); ++ g_autoptr (NautilusFile) file_1 = nautilus_file_get_by_uri ("file://@TERMUX_PREFIX@/etc"); + NautilusFileSortType sort_type = NAUTILUS_FILE_SORT_BY_DISPLAY_NAME; + int order; + +-- +2.48.1 + diff --git a/x11-packages/nautilus/0003-Werror.patch b/x11-packages/nautilus/0003-Werror.patch new file mode 100644 index 00000000000..f91172f87f6 --- /dev/null +++ b/x11-packages/nautilus/0003-Werror.patch @@ -0,0 +1,25 @@ +From 69074d38c40bff266e32671affcc6030b9705908 Mon Sep 17 00:00:00 2001 +From: Md Arif <111168803+sabamdarif@users.noreply.github.com> +Date: Thu, 10 Jul 2025 19:34:45 +0530 +Subject: [PATCH] Werror + +--- + meson.build | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/meson.build b/meson.build +index d12a545..0ebfdff 100644 +--- a/meson.build ++++ b/meson.build +@@ -70,6 +70,8 @@ add_project_arguments( + '-Wextra', + '-Wno-cast-function-type', + '-Wno-unused-parameter', ++ '-Wno-format-nonliteral', ++ '-Wno-format-security', + # Context: https://gitlab.com/freedesktop-sdk/freedesktop-sdk/commit/ce54a2527555e51e4ebf4cce9cbb6259cafa89a4 + # -O2 and -fexceptions unmask some cases of -Wmaybe-uninitialized, which + # become errors for some reason (I’m guessing because of the +-- +2.48.1 + diff --git a/x11-packages/nautilus/build.sh b/x11-packages/nautilus/build.sh new file mode 100644 index 00000000000..bf172ad6158 --- /dev/null +++ b/x11-packages/nautilus/build.sh @@ -0,0 +1,29 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/GNOME/nautilus +TERMUX_PKG_DESCRIPTION="File browser for GNOME" +TERMUX_PKG_LICENSE="GPL-3.0-or-later" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.3" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/nautilus/${TERMUX_PKG_VERSION%%.*}/nautilus-${TERMUX_PKG_VERSION}.tar.xz" +TERMUX_PKG_SHA256=aa6bf376f08992362805eae01890b1bf5ad148f356aa7ccfe1f664eda88d413e +TERMUX_PKG_DEPENDS="dbus, gexiv2, glib, gstreamer, tinysparql, totem-pl-parser, libportal, libportal-gtk4, localsearch, gdk-pixbuf, gnome-autoar, gnome-desktop4, libadwaita" +TERMUX_PKG_BUILD_DEPENDS="g-ir-scanner, gst-plugins-base, glib-cross" +TERMUX_PKG_PYTHON_COMMON_DEPS="setuptools, docutils" +TERMUX_PKG_EXTRA_CONFIGURE_ARGS=" +-Ddocs=false +-Dcloudproviders=false +-Dpackagekit=false +-Dselinux=false +-Dtests=none +" + +termux_step_pre_configure() { + termux_setup_cmake + termux_setup_meson + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper + export TERMUX_MESON_ENABLE_SOVERSION=1 + + # ERROR: Destination '/data/data/com.termux/files/usr/lib/libnautilus-extension.so.4' + # already exists and is not a symlink + rm -rf "$TERMUX__PREFIX__LIB_DIR"/libnautilus* +} diff --git a/x11-packages/nautilus/gir/48.5/Nautilus-4.0.xml b/x11-packages/nautilus/gir/48.5/Nautilus-4.0.xml new file mode 100644 index 00000000000..da909fd86ce --- /dev/null +++ b/x11-packages/nautilus/gir/48.5/Nautilus-4.0.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/nautilus/gir/49.3/Nautilus-4.1.xml b/x11-packages/nautilus/gir/49.3/Nautilus-4.1.xml new file mode 100644 index 00000000000..da909fd86ce --- /dev/null +++ b/x11-packages/nautilus/gir/49.3/Nautilus-4.1.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x11-packages/ptyxis/build.sh b/x11-packages/ptyxis/build.sh new file mode 100644 index 00000000000..17b49ba746f --- /dev/null +++ b/x11-packages/ptyxis/build.sh @@ -0,0 +1,17 @@ +TERMUX_PKG_HOMEPAGE=https://gitlab.gnome.org/chergert/ptyxis +TERMUX_PKG_DESCRIPTION="Advanced terminal for GNOME" +TERMUX_PKG_LICENSE="GPL-3.0-only" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="49.3" +TERMUX_PKG_SRCURL="https://download.gnome.org/sources/ptyxis/${TERMUX_PKG_VERSION%%.*}/ptyxis-$TERMUX_PKG_VERSION.tar.xz" +TERMUX_PKG_SHA256=d15b6ed8ebd19dfbe46be48ea4ab3ba4dc5048e277c9733a62ea19111f0bfc5b +TERMUX_PKG_DEPENDS="dconf, glib, gtk4, hicolor-icon-theme, json-glib, libadwaita, libandroid-wordexp, libc++, libcairo, libvte, pango" +TERMUX_PKG_BUILD_DEPENDS="glib-cross" +TERMUX_PKG_AUTO_UPDATE=true + +termux_step_pre_configure() { + termux_setup_gir + termux_setup_glib_cross_pkg_config_wrapper + CFLAGS+=" -Wno-format-nonliteral" + LDFLAGS+=" -landroid-wordexp" +} diff --git a/x11-packages/ptyxis/default-shell.patch b/x11-packages/ptyxis/default-shell.patch new file mode 100644 index 00000000000..e42b0abd261 --- /dev/null +++ b/x11-packages/ptyxis/default-shell.patch @@ -0,0 +1,40 @@ +--- a/agent/ptyxis-agent-impl.c ++++ b/agent/ptyxis-agent-impl.c +@@ -349,7 +349,7 @@ ptyxis_agent_impl_get_preferred_shell_cb (GObject *object, + g_autoptr(GError) error = NULL; + g_autofree char *stdout_buf = NULL; + PtyxisIpcAgent *agent; +- const char *default_shell = "/bin/sh"; ++ const char *default_shell = "@TERMUX_PREFIX@/bin/bash"; + + g_assert (G_IS_SUBPROCESS (subprocess)); + g_assert (G_IS_ASYNC_RESULT (result)); +@@ -381,7 +381,7 @@ static gboolean + ptyxis_agent_impl_handle_get_preferred_shell (PtyxisIpcAgent *agent, + GDBusMethodInvocation *invocation) + { +- const char *default_shell = "/bin/sh"; ++ const char *default_shell = "@TERMUX_PREFIX@/bin/bash"; + struct passwd *pw; + + g_assert (PTYXIS_IS_AGENT_IMPL (agent)); +--- a/src/org.gnome.Ptyxis.gschema.xml.in ++++ b/src/org.gnome.Ptyxis.gschema.xml.in +@@ -287,7 +287,7 @@ + + + +- false ++ true + Use Custom Command + If a custom command should be used + +@@ -299,7 +299,7 @@ + + + +- '' ++ '@TERMUX_PREFIX@/bin/bash' + Custom Command + A custom command for the shell + diff --git a/x11-packages/ptyxis/no-conflict-environ.patch b/x11-packages/ptyxis/no-conflict-environ.patch new file mode 100644 index 00000000000..0488a6c00f5 --- /dev/null +++ b/x11-packages/ptyxis/no-conflict-environ.patch @@ -0,0 +1,86 @@ +Prevents: +../src/agent/ptyxis-run-context.c:67:48: error: declaration shadows a variable in the global scope [-Werror,-Wshadow] + 67 | const char * const *environ, + +--- a/agent/ptyxis-run-context.c ++++ b/agent/ptyxis-run-context.c +@@ -64,13 +64,13 @@ ptyxis_run_context_new (void) + + static void + copy_envvar_with_fallback (PtyxisRunContext *run_context, +- const char * const *environ, ++ const char * const *ptyxis_environ, + const char *key, + const char *fallback) + { + const char *val; + +- if ((val = g_environ_getenv ((char **)environ, key))) ++ if ((val = g_environ_getenv ((char **)ptyxis_environ, key))) + ptyxis_run_context_setenv (run_context, key, val); + else if (fallback != NULL) + ptyxis_run_context_setenv (run_context, key, fallback); +@@ -215,7 +215,7 @@ ptyxis_run_context_class_init (PtyxisRunContextClass *klass) + static void + ptyxis_run_context_init (PtyxisRunContext *self) + { +- g_auto(GStrv) environ = g_get_environ (); ++ g_auto(GStrv) ptyxis_environ = g_get_environ (); + + self->setup_tty = TRUE; + +@@ -226,7 +226,7 @@ ptyxis_run_context_init (PtyxisRunContext *self) + /* Always start with full environment at the base layer, but allow + * removal by _set_environ(NULL) to clear it. + */ +- ptyxis_run_context_set_environ (self, (const char * const *)environ); ++ ptyxis_run_context_set_environ (self, (const char * const *)ptyxis_environ); + } + + void +@@ -461,7 +461,7 @@ ptyxis_run_context_get_environ (PtyxisRunContext *self) + + void + ptyxis_run_context_set_environ (PtyxisRunContext *self, +- const char * const *environ) ++ const char * const *ptyxis_environ) + { + PtyxisRunContextLayer *layer; + +@@ -471,9 +471,9 @@ ptyxis_run_context_set_environ (PtyxisRunContext *self, + + g_array_set_size (layer->env, 0); + +- if (environ != NULL && environ[0] != NULL) ++ if (ptyxis_environ != NULL && ptyxis_environ[0] != NULL) + { +- char **copy = g_strdupv ((char **)environ); ++ char **copy = g_strdupv ((char **)ptyxis_environ); + g_array_append_vals (layer->env, copy, g_strv_length (copy)); + g_free (copy); + } +@@ -481,20 +481,20 @@ ptyxis_run_context_set_environ (PtyxisRunContext *self, + + void + ptyxis_run_context_add_environ (PtyxisRunContext *self, +- const char * const *environ) ++ const char * const *ptyxis_environ) + { + PtyxisRunContextLayer *layer; + + g_return_if_fail (PTYXIS_IS_RUN_CONTEXT (self)); + +- if (environ == NULL || environ[0] == NULL) ++ if (ptyxis_environ == NULL || ptyxis_environ[0] == NULL) + return; + + layer = ptyxis_run_context_current_layer (self); + +- for (guint i = 0; environ[i]; i++) ++ for (guint i = 0; ptyxis_environ[i]; i++) + { +- const char *pair = environ[i]; ++ const char *pair = ptyxis_environ[i]; + const char *eq = strchr (pair, '='); + char **dest = NULL; + gsize keylen; diff --git a/x11-packages/ptyxis/no-xdg-portal.patch b/x11-packages/ptyxis/no-xdg-portal.patch new file mode 100644 index 00000000000..6374ebd905e --- /dev/null +++ b/x11-packages/ptyxis/no-xdg-portal.patch @@ -0,0 +1,40 @@ +--- a/src/ptyxis-tab.c ++++ b/src/ptyxis-tab.c +@@ -25,7 +25,7 @@ + + #include + +-#ifdef __linux__ ++#if defined(__linux__) && !defined(__ANDROID__) + # include + # include + #endif +@@ -115,7 +115,7 @@ static void ptyxis_tab_respawn (PtyxisTab *self); + + G_DEFINE_FINAL_TYPE (PtyxisTab, ptyxis_tab, GTK_TYPE_WIDGET) + +-#ifdef __linux__ ++#if defined(__linux__) && !defined(__ANDROID__) + static XdpPortal *portal; + #endif + +@@ -2005,7 +2005,7 @@ ptyxis_tab_get_command_line (PtyxisTab *self) + return self->command_line; + } + +-#ifdef __linux__ ++#if defined(__linux__) && !defined(__ANDROID__) + static void + ptyxis_tab_toast (PtyxisTab *self, + int timeout, +--- a/agent/meson.build ++++ b/agent/meson.build +@@ -43,7 +43,7 @@ ptyxis_agent_c_args = [ + ] + + libc_compat = true +-if target_machine.cpu_family() == 'x86_64' ++if false + ptyxis_agent_c_args += ['-include', 'x86_64/force_link_glibc_2.17.h'] + else + libc_compat = false