From e6abefe2f7b492e604013af047ee2128ab1659f4 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 18 Apr 2026 13:39:20 +1000 Subject: [PATCH 01/24] Initial Support for OpenGL on Wayland. --- $GNUSTEP_MAKEFILES/config.guess | 1815 +++++++++++++++++++ $GNUSTEP_MAKEFILES/config.sub | 2354 +++++++++++++++++++++++++ Headers/wayland/WaylandOpenGL.h | 58 + Headers/wayland/WaylandServer.h | 1 + Source/GNUmakefile.preamble | 2 +- Source/wayland/GNUmakefile | 2 + Source/wayland/GNUmakefile.preamble | 2 +- Source/wayland/WaylandGLContext.m | 423 +++++ Source/wayland/WaylandGLPixelFormat.m | 238 +++ Source/wayland/WaylandServer.m | 24 +- config.h.in | 22 +- configure | 1905 +++++++++++++------- configure.ac | 11 +- 13 files changed, 6176 insertions(+), 681 deletions(-) create mode 100755 $GNUSTEP_MAKEFILES/config.guess create mode 100755 $GNUSTEP_MAKEFILES/config.sub create mode 100644 Headers/wayland/WaylandOpenGL.h create mode 100644 Source/wayland/WaylandGLContext.m create mode 100644 Source/wayland/WaylandGLPixelFormat.m diff --git a/$GNUSTEP_MAKEFILES/config.guess b/$GNUSTEP_MAKEFILES/config.guess new file mode 100755 index 00000000..48a68460 --- /dev/null +++ b/$GNUSTEP_MAKEFILES/config.guess @@ -0,0 +1,1815 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2024 Free Software Foundation, Inc. + +# shellcheck disable=SC2006,SC2268 # see below for rationale + +timestamp='2024-07-27' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess +# +# Please send patches to . + + +# The "shellcheck disable" line above the timestamp inhibits complaints +# about features and limitations of the classic Bourne shell that were +# superseded or lifted in POSIX. However, this script identifies a wide +# variety of pre-POSIX systems that do not have POSIX shells at all, and +# even some reasonably current systems (Solaris 10 as case-in-point) still +# have a pre-POSIX /bin/sh. + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system '$me' is run on. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2024 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try '$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +# Just in case it came from the environment. +GUESS= + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, 'CC_FOR_BUILD' used to be named 'HOST_CC'. We still +# use 'HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +tmp= +# shellcheck disable=SC2172 +trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 + +set_cc_for_build() { + # prevent multiple calls if $tmp is already set + test "$tmp" && return 0 + : "${TMPDIR=/tmp}" + # shellcheck disable=SC2039,SC3028 + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } + dummy=$tmp/dummy + case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in + ,,) echo "int x;" > "$dummy.c" + for driver in cc gcc c17 c99 c89 ; do + if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then + CC_FOR_BUILD=$driver + break + fi + done + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; + esac +} + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if test -f /.attbin/uname ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case $UNAME_SYSTEM in +Linux|GNU|GNU/*) + LIBC=unknown + + set_cc_for_build + cat <<-EOF > "$dummy.c" + #if defined(__ANDROID__) + LIBC=android + #else + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #elif defined(__GLIBC__) + LIBC=gnu + #elif defined(__LLVM_LIBC__) + LIBC=llvm + #else + #include + /* First heuristic to detect musl libc. */ + #ifdef __DEFINED_va_list + LIBC=musl + #endif + #endif + #endif + EOF + cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + eval "$cc_set_libc" + + # Second heuristic to detect musl libc. + if [ "$LIBC" = unknown ] && + command -v ldd >/dev/null && + ldd --version 2>&1 | grep -q ^musl; then + LIBC=musl + fi + + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + if [ "$LIBC" = unknown ]; then + LIBC=gnu + fi + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ + /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ + echo unknown)` + case $UNAME_MACHINE_ARCH in + aarch64eb) machine=aarch64_be-unknown ;; + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` + machine=${arch}${endian}-unknown + ;; + *) machine=$UNAME_MACHINE_ARCH-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently (or will in the future) and ABI. + case $UNAME_MACHINE_ARCH in + earm*) + os=netbsdelf + ;; + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case $UNAME_MACHINE_ARCH in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case $UNAME_VERSION in + Debian*) + release='-gnu' + ;; + *) + release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + GUESS=$machine-${os}${release}${abi-} + ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE + ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE + ;; + *:SecBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE + ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE + ;; + *:MidnightBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE + ;; + *:ekkoBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE + ;; + *:SolidBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE + ;; + *:OS108:*:*) + GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE + ;; + macppc:MirBSD:*:*) + GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE + ;; + *:MirBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE + ;; + *:Sortix:*:*) + GUESS=$UNAME_MACHINE-unknown-sortix + ;; + *:Twizzler:*:*) + GUESS=$UNAME_MACHINE-unknown-twizzler + ;; + *:Redox:*:*) + GUESS=$UNAME_MACHINE-unknown-redox + ;; + mips:OSF1:*.*) + GUESS=mips-dec-osf1 + ;; + alpha:OSF1:*:*) + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + trap '' 0 + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case $ALPHA_CPU_TYPE in + "EV4 (21064)") + UNAME_MACHINE=alpha ;; + "EV4.5 (21064)") + UNAME_MACHINE=alpha ;; + "LCA4 (21066/21068)") + UNAME_MACHINE=alpha ;; + "EV5 (21164)") + UNAME_MACHINE=alphaev5 ;; + "EV5.6 (21164A)") + UNAME_MACHINE=alphaev56 ;; + "EV5.6 (21164PC)") + UNAME_MACHINE=alphapca56 ;; + "EV5.7 (21164PC)") + UNAME_MACHINE=alphapca57 ;; + "EV6 (21264)") + UNAME_MACHINE=alphaev6 ;; + "EV6.7 (21264A)") + UNAME_MACHINE=alphaev67 ;; + "EV6.8CB (21264C)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8AL (21264B)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8CX (21264D)") + UNAME_MACHINE=alphaev68 ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE=alphaev69 ;; + "EV7 (21364)") + UNAME_MACHINE=alphaev7 ;; + "EV7.9 (21364A)") + UNAME_MACHINE=alphaev79 ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + GUESS=$UNAME_MACHINE-dec-osf$OSF_REL + ;; + Amiga*:UNIX_System_V:4.0:*) + GUESS=m68k-unknown-sysv4 + ;; + *:[Aa]miga[Oo][Ss]:*:*) + GUESS=$UNAME_MACHINE-unknown-amigaos + ;; + *:[Mm]orph[Oo][Ss]:*:*) + GUESS=$UNAME_MACHINE-unknown-morphos + ;; + *:OS/390:*:*) + GUESS=i370-ibm-openedition + ;; + *:z/VM:*:*) + GUESS=s390-ibm-zvmoe + ;; + *:OS400:*:*) + GUESS=powerpc-ibm-os400 + ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + GUESS=arm-acorn-riscix$UNAME_RELEASE + ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + GUESS=arm-unknown-riscos + ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + GUESS=hppa1.1-hitachi-hiuxmpp + ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + case `(/bin/universe) 2>/dev/null` in + att) GUESS=pyramid-pyramid-sysv3 ;; + *) GUESS=pyramid-pyramid-bsd ;; + esac + ;; + NILE*:*:*:dcosx) + GUESS=pyramid-pyramid-svr4 + ;; + DRS?6000:unix:4.0:6*) + GUESS=sparc-icl-nx6 + ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) GUESS=sparc-icl-nx7 ;; + esac + ;; + s390x:SunOS:*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL + ;; + sun4H:SunOS:5.*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-hal-solaris2$SUN_REL + ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-sun-solaris2$SUN_REL + ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + GUESS=i386-pc-auroraux$UNAME_RELEASE + ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + set_cc_for_build + SUN_ARCH=i386 + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if test "$CC_FOR_BUILD" != no_compiler_found; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=$SUN_ARCH-pc-solaris2$SUN_REL + ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-sun-solaris3$SUN_REL + ;; + sun4*:SunOS:*:*) + case `/usr/bin/arch -k` in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like '4.1.3-JL'. + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` + GUESS=sparc-sun-sunos$SUN_REL + ;; + sun3*:SunOS:*:*) + GUESS=m68k-sun-sunos$UNAME_RELEASE + ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 + case `/bin/arch` in + sun3) + GUESS=m68k-sun-sunos$UNAME_RELEASE + ;; + sun4) + GUESS=sparc-sun-sunos$UNAME_RELEASE + ;; + esac + ;; + aushp:SunOS:*:*) + GUESS=sparc-auspex-sunos$UNAME_RELEASE + ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + GUESS=m68k-milan-mint$UNAME_RELEASE + ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + GUESS=m68k-hades-mint$UNAME_RELEASE + ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + GUESS=m68k-unknown-mint$UNAME_RELEASE + ;; + m68k:machten:*:*) + GUESS=m68k-apple-machten$UNAME_RELEASE + ;; + powerpc:machten:*:*) + GUESS=powerpc-apple-machten$UNAME_RELEASE + ;; + RISC*:Mach:*:*) + GUESS=mips-dec-mach_bsd4.3 + ;; + RISC*:ULTRIX:*:*) + GUESS=mips-dec-ultrix$UNAME_RELEASE + ;; + VAX*:ULTRIX*:*:*) + GUESS=vax-dec-ultrix$UNAME_RELEASE + ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + GUESS=clipper-intergraph-clix$UNAME_RELEASE + ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && + dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`"$dummy" "$dummyarg"` && + { echo "$SYSTEM_NAME"; exit; } + GUESS=mips-mips-riscos$UNAME_RELEASE + ;; + Motorola:PowerMAX_OS:*:*) + GUESS=powerpc-motorola-powermax + ;; + Motorola:*:4.3:PL8-*) + GUESS=powerpc-harris-powermax + ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + GUESS=powerpc-harris-powermax + ;; + Night_Hawk:Power_UNIX:*:*) + GUESS=powerpc-harris-powerunix + ;; + m88k:CX/UX:7*:*) + GUESS=m88k-harris-cxux7 + ;; + m88k:*:4*:R4*) + GUESS=m88k-motorola-sysv4 + ;; + m88k:*:3*:R3*) + GUESS=m88k-motorola-sysv3 + ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 + then + if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ + test "$TARGET_BINARY_INTERFACE"x = x + then + GUESS=m88k-dg-dgux$UNAME_RELEASE + else + GUESS=m88k-dg-dguxbcs$UNAME_RELEASE + fi + else + GUESS=i586-dg-dgux$UNAME_RELEASE + fi + ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + GUESS=m88k-dolphin-sysv3 + ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + GUESS=m88k-motorola-sysv3 + ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + GUESS=m88k-tektronix-sysv3 + ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + GUESS=m68k-tektronix-bsd + ;; + *:IRIX*:*:*) + IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` + GUESS=mips-sgi-irix$IRIX_REL + ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id + ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + GUESS=i386-ibm-aix + ;; + ia64:AIX:*:*) + if test -x /usr/bin/oslevel ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=$UNAME_VERSION.$UNAME_RELEASE + fi + GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV + ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" + #include + + int + main () + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` + then + GUESS=$SYSTEM_NAME + else + GUESS=rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + GUESS=rs6000-ibm-aix3.2.4 + else + GUESS=rs6000-ibm-aix3.2 + fi + ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if test -x /usr/bin/lslpp ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV=$UNAME_VERSION.$UNAME_RELEASE + fi + GUESS=$IBM_ARCH-ibm-aix$IBM_REV + ;; + *:AIX:*:*) + GUESS=rs6000-ibm-aix + ;; + ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) + GUESS=romp-ibm-bsd4.4 + ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to + ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + GUESS=rs6000-bull-bosx + ;; + DPX/2?00:B.O.S.:*:*) + GUESS=m68k-bull-sysv3 + ;; + 9000/[34]??:4.3bsd:1.*:*) + GUESS=m68k-hp-bsd + ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + GUESS=m68k-hp-bsd4.4 + ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` + case $UNAME_MACHINE in + 9000/31?) HP_ARCH=m68000 ;; + 9000/[34]??) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if test -x /usr/bin/getconf; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case $sc_cpu_version in + 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 + 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case $sc_kernel_bits in + 32) HP_ARCH=hppa2.0n ;; + 64) HP_ARCH=hppa2.0w ;; + '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 + esac ;; + esac + fi + if test "$HP_ARCH" = ""; then + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" + + #define _HPUX_SOURCE + #include + #include + + int + main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if test "$HP_ARCH" = hppa2.0w + then + set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH=hppa2.0w + else + HP_ARCH=hppa64 + fi + fi + GUESS=$HP_ARCH-hp-hpux$HPUX_REV + ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` + GUESS=ia64-hp-hpux$HPUX_REV + ;; + 3050*:HI-UX:*:*) + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + GUESS=unknown-hitachi-hiuxwe2 + ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) + GUESS=hppa1.1-hp-bsd + ;; + 9000/8??:4.3bsd:*:*) + GUESS=hppa1.0-hp-bsd + ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + GUESS=hppa1.0-hp-mpeix + ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) + GUESS=hppa1.1-hp-osf + ;; + hp8??:OSF1:*:*) + GUESS=hppa1.0-hp-osf + ;; + i*86:OSF1:*:*) + if test -x /usr/sbin/sysversion ; then + GUESS=$UNAME_MACHINE-unknown-osf1mk + else + GUESS=$UNAME_MACHINE-unknown-osf1 + fi + ;; + parisc*:Lites*:*:*) + GUESS=hppa1.1-hp-lites + ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + GUESS=c1-convex-bsd + ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + GUESS=c34-convex-bsd + ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + GUESS=c38-convex-bsd + ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + GUESS=c4-convex-bsd + ;; + CRAY*Y-MP:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=ymp-cray-unicos$CRAY_REL + ;; + CRAY*[A-Z]90:*:*:*) + echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=t90-cray-unicos$CRAY_REL + ;; + CRAY*T3E:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=alphaev5-cray-unicosmk$CRAY_REL + ;; + CRAY*SV1:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=sv1-cray-unicos$CRAY_REL + ;; + *:UNICOS/mp:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=craynv-cray-unicosmp$CRAY_REL + ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` + GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} + ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` + GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} + ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE + ;; + sparc*:BSD/OS:*:*) + GUESS=sparc-unknown-bsdi$UNAME_RELEASE + ;; + *:BSD/OS:*:*) + GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE + ;; + arm:FreeBSD:*:*) + UNAME_PROCESSOR=`uname -p` + set_cc_for_build + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi + else + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf + fi + ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`uname -p` + case $UNAME_PROCESSOR in + amd64) + UNAME_PROCESSOR=x86_64 ;; + i386) + UNAME_PROCESSOR=i586 ;; + esac + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL + ;; + i*:CYGWIN*:*) + GUESS=$UNAME_MACHINE-pc-cygwin + ;; + *:MINGW64*:*) + GUESS=$UNAME_MACHINE-pc-mingw64 + ;; + *:MINGW*:*) + GUESS=$UNAME_MACHINE-pc-mingw32 + ;; + *:MSYS*:*) + GUESS=$UNAME_MACHINE-pc-msys + ;; + i*:PW*:*) + GUESS=$UNAME_MACHINE-pc-pw32 + ;; + *:SerenityOS:*:*) + GUESS=$UNAME_MACHINE-pc-serenity + ;; + *:Interix*:*) + case $UNAME_MACHINE in + x86) + GUESS=i586-pc-interix$UNAME_RELEASE + ;; + authenticamd | genuineintel | EM64T) + GUESS=x86_64-unknown-interix$UNAME_RELEASE + ;; + IA64) + GUESS=ia64-unknown-interix$UNAME_RELEASE + ;; + esac ;; + i*:UWIN*:*) + GUESS=$UNAME_MACHINE-pc-uwin + ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + GUESS=x86_64-pc-cygwin + ;; + prep*:SunOS:5.*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=powerpcle-unknown-solaris2$SUN_REL + ;; + *:GNU:*:*) + # the GNU system + GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` + GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` + GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL + ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` + GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC + ;; + x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*) + GUESS="$UNAME_MACHINE-pc-managarm-mlibc" + ;; + *:[Mm]anagarm:*:*) + GUESS="$UNAME_MACHINE-unknown-managarm-mlibc" + ;; + *:Minix:*:*) + GUESS=$UNAME_MACHINE-unknown-minix + ;; + aarch64:Linux:*:*) + set_cc_for_build + CPU=$UNAME_MACHINE + LIBCABI=$LIBC + if test "$CC_FOR_BUILD" != no_compiler_found; then + ABI=64 + sed 's/^ //' << EOF > "$dummy.c" + #ifdef __ARM_EABI__ + #ifdef __ARM_PCS_VFP + ABI=eabihf + #else + ABI=eabi + #endif + #endif +EOF + cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` + eval "$cc_set_abi" + case $ABI in + eabi | eabihf) CPU=armv8l; LIBCABI=$LIBC$ABI ;; + esac + fi + GUESS=$CPU-unknown-linux-$LIBCABI + ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC=gnulibc1 ; fi + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + arm*:Linux:*:*) + set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi + else + GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf + fi + fi + ;; + avr32*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + cris:Linux:*:*) + GUESS=$UNAME_MACHINE-axis-linux-$LIBC + ;; + crisv32:Linux:*:*) + GUESS=$UNAME_MACHINE-axis-linux-$LIBC + ;; + e2k:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + frv:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + hexagon:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + i*86:Linux:*:*) + GUESS=$UNAME_MACHINE-pc-linux-$LIBC + ;; + ia64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + k1om:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + kvx:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + kvx:cos:*:*) + GUESS=$UNAME_MACHINE-unknown-cos + ;; + kvx:mbr:*:*) + GUESS=$UNAME_MACHINE-unknown-mbr + ;; + loongarch32:Linux:*:* | loongarch64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + m32r*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + m68*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + mips:Linux:*:* | mips64:Linux:*:*) + set_cc_for_build + IS_GLIBC=0 + test x"${LIBC}" = xgnu && IS_GLIBC=1 + sed 's/^ //' << EOF > "$dummy.c" + #undef CPU + #undef mips + #undef mipsel + #undef mips64 + #undef mips64el + #if ${IS_GLIBC} && defined(_ABI64) + LIBCABI=gnuabi64 + #else + #if ${IS_GLIBC} && defined(_ABIN32) + LIBCABI=gnuabin32 + #else + LIBCABI=${LIBC} + #endif + #endif + + #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 + CPU=mipsisa64r6 + #else + #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 + CPU=mipsisa32r6 + #else + #if defined(__mips64) + CPU=mips64 + #else + CPU=mips + #endif + #endif + #endif + + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + MIPS_ENDIAN=el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + MIPS_ENDIAN= + #else + MIPS_ENDIAN= + #endif + #endif +EOF + cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` + eval "$cc_set_vars" + test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } + ;; + mips64el:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + openrisc*:Linux:*:*) + GUESS=or1k-unknown-linux-$LIBC + ;; + or32:Linux:*:* | or1k*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + padre:Linux:*:*) + GUESS=sparc-unknown-linux-$LIBC + ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + GUESS=hppa64-unknown-linux-$LIBC + ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; + PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; + *) GUESS=hppa-unknown-linux-$LIBC ;; + esac + ;; + ppc64:Linux:*:*) + GUESS=powerpc64-unknown-linux-$LIBC + ;; + ppc:Linux:*:*) + GUESS=powerpc-unknown-linux-$LIBC + ;; + ppc64le:Linux:*:*) + GUESS=powerpc64le-unknown-linux-$LIBC + ;; + ppcle:Linux:*:*) + GUESS=powerpcle-unknown-linux-$LIBC + ;; + riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + s390:Linux:*:* | s390x:Linux:*:*) + GUESS=$UNAME_MACHINE-ibm-linux-$LIBC + ;; + sh64*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + sh*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + tile*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + vax:Linux:*:*) + GUESS=$UNAME_MACHINE-dec-linux-$LIBC + ;; + x86_64:Linux:*:*) + set_cc_for_build + CPU=$UNAME_MACHINE + LIBCABI=$LIBC + if test "$CC_FOR_BUILD" != no_compiler_found; then + ABI=64 + sed 's/^ //' << EOF > "$dummy.c" + #ifdef __i386__ + ABI=x86 + #else + #ifdef __ILP32__ + ABI=x32 + #endif + #endif +EOF + cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` + eval "$cc_set_abi" + case $ABI in + x86) CPU=i686 ;; + x32) LIBCABI=${LIBC}x32 ;; + esac + fi + GUESS=$CPU-pc-linux-$LIBCABI + ;; + xtensa*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + GUESS=i386-sequent-sysv4 + ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION + ;; + i*86:OS/2:*:*) + # If we were able to find 'uname', then EMX Unix compatibility + # is probably installed. + GUESS=$UNAME_MACHINE-pc-os2-emx + ;; + i*86:XTS-300:*:STOP) + GUESS=$UNAME_MACHINE-unknown-stop + ;; + i*86:atheos:*:*) + GUESS=$UNAME_MACHINE-unknown-atheos + ;; + i*86:syllable:*:*) + GUESS=$UNAME_MACHINE-pc-syllable + ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + GUESS=i386-unknown-lynxos$UNAME_RELEASE + ;; + i*86:*DOS:*:*) + GUESS=$UNAME_MACHINE-pc-msdosdjgpp + ;; + i*86:*:4.*:*) + UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL + else + GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL + fi + ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL + else + GUESS=$UNAME_MACHINE-pc-sysv32 + fi + ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configure will decide that + # this is a cross-build. + GUESS=i586-pc-msdosdjgpp + ;; + Intel:Mach:3*:*) + GUESS=i386-pc-mach3 + ;; + paragon:*:*:*) + GUESS=i860-intel-osf1 + ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 + fi + ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + GUESS=m68010-convergent-sysv + ;; + mc68k:UNIX:SYSTEM5:3.51m) + GUESS=m68k-convergent-sysv + ;; + M680?0:D-NIX:5.3:*) + GUESS=m68k-diab-dnix + ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + GUESS=m68k-unknown-lynxos$UNAME_RELEASE + ;; + mc68030:UNIX_System_V:4.*:*) + GUESS=m68k-atari-sysv4 + ;; + TSUNAMI:LynxOS:2.*:*) + GUESS=sparc-unknown-lynxos$UNAME_RELEASE + ;; + rs6000:LynxOS:2.*:*) + GUESS=rs6000-unknown-lynxos$UNAME_RELEASE + ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + GUESS=powerpc-unknown-lynxos$UNAME_RELEASE + ;; + SM[BE]S:UNIX_SV:*:*) + GUESS=mips-dde-sysv$UNAME_RELEASE + ;; + RM*:ReliantUNIX-*:*:*) + GUESS=mips-sni-sysv4 + ;; + RM*:SINIX-*:*:*) + GUESS=mips-sni-sysv4 + ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + GUESS=$UNAME_MACHINE-sni-sysv4 + else + GUESS=ns32k-sni-sysv + fi + ;; + PENTIUM:*:4.0*:*) # Unisys 'ClearPath HMP IX 4000' SVR4/MP effort + # says + GUESS=i586-unisys-sysv4 + ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + GUESS=hppa1.1-stratus-sysv4 + ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + GUESS=i860-stratus-sysv4 + ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + GUESS=$UNAME_MACHINE-stratus-vos + ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + GUESS=hppa1.1-stratus-vos + ;; + mc68*:A/UX:*:*) + GUESS=m68k-apple-aux$UNAME_RELEASE + ;; + news*:NEWS-OS:6*:*) + GUESS=mips-sony-newsos6 + ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if test -d /usr/nec; then + GUESS=mips-nec-sysv$UNAME_RELEASE + else + GUESS=mips-unknown-sysv$UNAME_RELEASE + fi + ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + GUESS=powerpc-be-beos + ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + GUESS=powerpc-apple-beos + ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + GUESS=i586-pc-beos + ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + GUESS=i586-pc-haiku + ;; + ppc:Haiku:*:*) # Haiku running on Apple PowerPC + GUESS=powerpc-apple-haiku + ;; + *:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat) + GUESS=$UNAME_MACHINE-unknown-haiku + ;; + SX-4:SUPER-UX:*:*) + GUESS=sx4-nec-superux$UNAME_RELEASE + ;; + SX-5:SUPER-UX:*:*) + GUESS=sx5-nec-superux$UNAME_RELEASE + ;; + SX-6:SUPER-UX:*:*) + GUESS=sx6-nec-superux$UNAME_RELEASE + ;; + SX-7:SUPER-UX:*:*) + GUESS=sx7-nec-superux$UNAME_RELEASE + ;; + SX-8:SUPER-UX:*:*) + GUESS=sx8-nec-superux$UNAME_RELEASE + ;; + SX-8R:SUPER-UX:*:*) + GUESS=sx8r-nec-superux$UNAME_RELEASE + ;; + SX-ACE:SUPER-UX:*:*) + GUESS=sxace-nec-superux$UNAME_RELEASE + ;; + Power*:Rhapsody:*:*) + GUESS=powerpc-apple-rhapsody$UNAME_RELEASE + ;; + *:Rhapsody:*:*) + GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE + ;; + arm64:Darwin:*:*) + GUESS=aarch64-apple-darwin$UNAME_RELEASE + ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` + case $UNAME_PROCESSOR in + unknown) UNAME_PROCESSOR=powerpc ;; + esac + if command -v xcode-select > /dev/null 2> /dev/null && \ + ! xcode-select --print-path > /dev/null 2> /dev/null ; then + # Avoid executing cc if there is no toolchain installed as + # cc will be a stub that puts up a graphical alert + # prompting the user to install developer tools. + CC_FOR_BUILD=no_compiler_found + else + set_cc_for_build + fi + if test "$CC_FOR_BUILD" != no_compiler_found; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc + if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_PPC >/dev/null + then + UNAME_PROCESSOR=powerpc + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # uname -m returns i386 or x86_64 + UNAME_PROCESSOR=$UNAME_MACHINE + fi + GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE + ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE + ;; + *:QNX:*:4*) + GUESS=i386-pc-qnx + ;; + NEO-*:NONSTOP_KERNEL:*:*) + GUESS=neo-tandem-nsk$UNAME_RELEASE + ;; + NSE-*:NONSTOP_KERNEL:*:*) + GUESS=nse-tandem-nsk$UNAME_RELEASE + ;; + NSR-*:NONSTOP_KERNEL:*:*) + GUESS=nsr-tandem-nsk$UNAME_RELEASE + ;; + NSV-*:NONSTOP_KERNEL:*:*) + GUESS=nsv-tandem-nsk$UNAME_RELEASE + ;; + NSX-*:NONSTOP_KERNEL:*:*) + GUESS=nsx-tandem-nsk$UNAME_RELEASE + ;; + *:NonStop-UX:*:*) + GUESS=mips-compaq-nonstopux + ;; + BS2000:POSIX*:*:*) + GUESS=bs2000-siemens-sysv + ;; + DS/*:UNIX_System_V:*:*) + GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE + ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "${cputype-}" = 386; then + UNAME_MACHINE=i386 + elif test "x${cputype-}" != x; then + UNAME_MACHINE=$cputype + fi + GUESS=$UNAME_MACHINE-unknown-plan9 + ;; + *:TOPS-10:*:*) + GUESS=pdp10-unknown-tops10 + ;; + *:TENEX:*:*) + GUESS=pdp10-unknown-tenex + ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + GUESS=pdp10-dec-tops20 + ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + GUESS=pdp10-xkl-tops20 + ;; + *:TOPS-20:*:*) + GUESS=pdp10-unknown-tops20 + ;; + *:ITS:*:*) + GUESS=pdp10-unknown-its + ;; + SEI:*:*:SEIUX) + GUESS=mips-sei-seiux$UNAME_RELEASE + ;; + *:DragonFly:*:*) + DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL + ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case $UNAME_MACHINE in + A*) GUESS=alpha-dec-vms ;; + I*) GUESS=ia64-dec-vms ;; + V*) GUESS=vax-dec-vms ;; + esac ;; + *:XENIX:*:SysV) + GUESS=i386-pc-xenix + ;; + i*86:skyos:*:*) + SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` + GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL + ;; + i*86:rdos:*:*) + GUESS=$UNAME_MACHINE-pc-rdos + ;; + i*86:Fiwix:*:*) + GUESS=$UNAME_MACHINE-pc-fiwix + ;; + *:AROS:*:*) + GUESS=$UNAME_MACHINE-unknown-aros + ;; + x86_64:VMkernel:*:*) + GUESS=$UNAME_MACHINE-unknown-esx + ;; + amd64:Isilon\ OneFS:*:*) + GUESS=x86_64-unknown-onefs + ;; + *:Unleashed:*:*) + GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE + ;; + *:Ironclad:*:*) + GUESS=$UNAME_MACHINE-unknown-ironclad + ;; +esac + +# Do we have a guess based on uname results? +if test "x$GUESS" != x; then + echo "$GUESS" + exit +fi + +# No uname command or uname output not recognized. +set_cc_for_build +cat > "$dummy.c" < +#include +#endif +#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) +#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) +#include +#if defined(_SIZE_T_) || defined(SIGLOST) +#include +#endif +#endif +#endif +int +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); +#endif + +#if defined (vax) +#if !defined (ultrix) +#include +#if defined (BSD) +#if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +#else +#if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +#else + printf ("vax-dec-bsd\n"); exit (0); +#endif +#endif +#else + printf ("vax-dec-bsd\n"); exit (0); +#endif +#else +#if defined(_SIZE_T_) || defined(SIGLOST) + struct utsname un; + uname (&un); + printf ("vax-dec-ultrix%s\n", un.release); exit (0); +#else + printf ("vax-dec-ultrix\n"); exit (0); +#endif +#endif +#endif +#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) +#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) +#if defined(_SIZE_T_) || defined(SIGLOST) + struct utsname *un; + uname (&un); + printf ("mips-dec-ultrix%s\n", un.release); exit (0); +#else + printf ("mips-dec-ultrix\n"); exit (0); +#endif +#endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. +test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } + +echo "$0: unable to guess system type" >&2 + +case $UNAME_MACHINE:$UNAME_SYSTEM in + mips:Linux | mips64:Linux) + # If we got here on MIPS GNU/Linux, output extra information. + cat >&2 <&2 <&2 </dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = "$UNAME_MACHINE" +UNAME_RELEASE = "$UNAME_RELEASE" +UNAME_SYSTEM = "$UNAME_SYSTEM" +UNAME_VERSION = "$UNAME_VERSION" +EOF +fi + +exit 1 + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/$GNUSTEP_MAKEFILES/config.sub b/$GNUSTEP_MAKEFILES/config.sub new file mode 100755 index 00000000..4aaae46f --- /dev/null +++ b/$GNUSTEP_MAKEFILES/config.sub @@ -0,0 +1,2354 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2024 Free Software Foundation, Inc. + +# shellcheck disable=SC2006,SC2268,SC2162 # see below for rationale + +timestamp='2024-05-27' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +# The "shellcheck disable" line above the timestamp inhibits complaints +# about features and limitations of the classic Bourne shell that were +# superseded or lifted in POSIX. However, this script identifies a wide +# variety of pre-POSIX systems that do not have POSIX shells at all, and +# even some reasonably current systems (Solaris 10 as case-in-point) still +# have a pre-POSIX /bin/sh. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS + +Canonicalize a configuration name. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2024 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try '$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo "$1" + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Split fields of configuration type +saved_IFS=$IFS +IFS="-" read field1 field2 field3 field4 <&2 + exit 1 + ;; + *-*-*-*) + basic_machine=$field1-$field2 + basic_os=$field3-$field4 + ;; + *-*-*) + # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two + # parts + maybe_os=$field2-$field3 + case $maybe_os in + cloudabi*-eabi* \ + | kfreebsd*-gnu* \ + | knetbsd*-gnu* \ + | kopensolaris*-gnu* \ + | linux-* \ + | managarm-* \ + | netbsd*-eabi* \ + | netbsd*-gnu* \ + | nto-qnx* \ + | os2-emx* \ + | rtmk-nova* \ + | storm-chaos* \ + | uclinux-gnu* \ + | uclinux-uclibc* \ + | windows-* ) + basic_machine=$field1 + basic_os=$maybe_os + ;; + android-linux) + basic_machine=$field1-unknown + basic_os=linux-android + ;; + *) + basic_machine=$field1-$field2 + basic_os=$field3 + ;; + esac + ;; + *-*) + case $field1-$field2 in + # Shorthands that happen to contain a single dash + convex-c[12] | convex-c3[248]) + basic_machine=$field2-convex + basic_os= + ;; + decstation-3100) + basic_machine=mips-dec + basic_os= + ;; + *-*) + # Second component is usually, but not always the OS + case $field2 in + # Do not treat sunos as a manufacturer + sun*os*) + basic_machine=$field1 + basic_os=$field2 + ;; + # Manufacturers + 3100* \ + | 32* \ + | 3300* \ + | 3600* \ + | 7300* \ + | acorn \ + | altos* \ + | apollo \ + | apple \ + | atari \ + | att* \ + | axis \ + | be \ + | bull \ + | cbm \ + | ccur \ + | cisco \ + | commodore \ + | convergent* \ + | convex* \ + | cray \ + | crds \ + | dec* \ + | delta* \ + | dg \ + | digital \ + | dolphin \ + | encore* \ + | gould \ + | harris \ + | highlevel \ + | hitachi* \ + | hp \ + | ibm* \ + | intergraph \ + | isi* \ + | knuth \ + | masscomp \ + | microblaze* \ + | mips* \ + | motorola* \ + | ncr* \ + | news \ + | next \ + | ns \ + | oki \ + | omron* \ + | pc533* \ + | rebel \ + | rom68k \ + | rombug \ + | semi \ + | sequent* \ + | siemens \ + | sgi* \ + | siemens \ + | sim \ + | sni \ + | sony* \ + | stratus \ + | sun \ + | sun[234]* \ + | tektronix \ + | tti* \ + | ultra \ + | unicom* \ + | wec \ + | winbond \ + | wrs) + basic_machine=$field1-$field2 + basic_os= + ;; + zephyr*) + basic_machine=$field1-unknown + basic_os=$field2 + ;; + *) + basic_machine=$field1 + basic_os=$field2 + ;; + esac + ;; + esac + ;; + *) + # Convert single-component short-hands not valid as part of + # multi-component configurations. + case $field1 in + 386bsd) + basic_machine=i386-pc + basic_os=bsd + ;; + a29khif) + basic_machine=a29k-amd + basic_os=udi + ;; + adobe68k) + basic_machine=m68010-adobe + basic_os=scout + ;; + alliant) + basic_machine=fx80-alliant + basic_os= + ;; + altos | altos3068) + basic_machine=m68k-altos + basic_os= + ;; + am29k) + basic_machine=a29k-none + basic_os=bsd + ;; + amdahl) + basic_machine=580-amdahl + basic_os=sysv + ;; + amiga) + basic_machine=m68k-unknown + basic_os= + ;; + amigaos | amigados) + basic_machine=m68k-unknown + basic_os=amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + basic_os=sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + basic_os=sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + basic_os=bsd + ;; + aros) + basic_machine=i386-pc + basic_os=aros + ;; + aux) + basic_machine=m68k-apple + basic_os=aux + ;; + balance) + basic_machine=ns32k-sequent + basic_os=dynix + ;; + blackfin) + basic_machine=bfin-unknown + basic_os=linux + ;; + cegcc) + basic_machine=arm-unknown + basic_os=cegcc + ;; + cray) + basic_machine=j90-cray + basic_os=unicos + ;; + crds | unos) + basic_machine=m68k-crds + basic_os= + ;; + da30) + basic_machine=m68k-da30 + basic_os= + ;; + decstation | pmax | pmin | dec3100 | decstatn) + basic_machine=mips-dec + basic_os= + ;; + delta88) + basic_machine=m88k-motorola + basic_os=sysv3 + ;; + dicos) + basic_machine=i686-pc + basic_os=dicos + ;; + djgpp) + basic_machine=i586-pc + basic_os=msdosdjgpp + ;; + ebmon29k) + basic_machine=a29k-amd + basic_os=ebmon + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + basic_os=ose + ;; + gmicro) + basic_machine=tron-gmicro + basic_os=sysv + ;; + go32) + basic_machine=i386-pc + basic_os=go32 + ;; + h8300hms) + basic_machine=h8300-hitachi + basic_os=hms + ;; + h8300xray) + basic_machine=h8300-hitachi + basic_os=xray + ;; + h8500hms) + basic_machine=h8500-hitachi + basic_os=hms + ;; + harris) + basic_machine=m88k-harris + basic_os=sysv3 + ;; + hp300 | hp300hpux) + basic_machine=m68k-hp + basic_os=hpux + ;; + hp300bsd) + basic_machine=m68k-hp + basic_os=bsd + ;; + hppaosf) + basic_machine=hppa1.1-hp + basic_os=osf + ;; + hppro) + basic_machine=hppa1.1-hp + basic_os=proelf + ;; + i386mach) + basic_machine=i386-mach + basic_os=mach + ;; + isi68 | isi) + basic_machine=m68k-isi + basic_os=sysv + ;; + m68knommu) + basic_machine=m68k-unknown + basic_os=linux + ;; + magnum | m3230) + basic_machine=mips-mips + basic_os=sysv + ;; + merlin) + basic_machine=ns32k-utek + basic_os=sysv + ;; + mingw64) + basic_machine=x86_64-pc + basic_os=mingw64 + ;; + mingw32) + basic_machine=i686-pc + basic_os=mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + basic_os=mingw32ce + ;; + monitor) + basic_machine=m68k-rom68k + basic_os=coff + ;; + morphos) + basic_machine=powerpc-unknown + basic_os=morphos + ;; + moxiebox) + basic_machine=moxie-unknown + basic_os=moxiebox + ;; + msdos) + basic_machine=i386-pc + basic_os=msdos + ;; + msys) + basic_machine=i686-pc + basic_os=msys + ;; + mvs) + basic_machine=i370-ibm + basic_os=mvs + ;; + nacl) + basic_machine=le32-unknown + basic_os=nacl + ;; + ncr3000) + basic_machine=i486-ncr + basic_os=sysv4 + ;; + netbsd386) + basic_machine=i386-pc + basic_os=netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + basic_os=linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + basic_os=newsos + ;; + news1000) + basic_machine=m68030-sony + basic_os=newsos + ;; + necv70) + basic_machine=v70-nec + basic_os=sysv + ;; + nh3000) + basic_machine=m68k-harris + basic_os=cxux + ;; + nh[45]000) + basic_machine=m88k-harris + basic_os=cxux + ;; + nindy960) + basic_machine=i960-intel + basic_os=nindy + ;; + mon960) + basic_machine=i960-intel + basic_os=mon960 + ;; + nonstopux) + basic_machine=mips-compaq + basic_os=nonstopux + ;; + os400) + basic_machine=powerpc-ibm + basic_os=os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + basic_os=ose + ;; + os68k) + basic_machine=m68k-none + basic_os=os68k + ;; + paragon) + basic_machine=i860-intel + basic_os=osf + ;; + parisc) + basic_machine=hppa-unknown + basic_os=linux + ;; + psp) + basic_machine=mipsallegrexel-sony + basic_os=psp + ;; + pw32) + basic_machine=i586-unknown + basic_os=pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + basic_os=rdos + ;; + rdos32) + basic_machine=i386-pc + basic_os=rdos + ;; + rom68k) + basic_machine=m68k-rom68k + basic_os=coff + ;; + sa29200) + basic_machine=a29k-amd + basic_os=udi + ;; + sei) + basic_machine=mips-sei + basic_os=seiux + ;; + sequent) + basic_machine=i386-sequent + basic_os= + ;; + sps7) + basic_machine=m68k-bull + basic_os=sysv2 + ;; + st2000) + basic_machine=m68k-tandem + basic_os= + ;; + stratus) + basic_machine=i860-stratus + basic_os=sysv4 + ;; + sun2) + basic_machine=m68000-sun + basic_os= + ;; + sun2os3) + basic_machine=m68000-sun + basic_os=sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + basic_os=sunos4 + ;; + sun3) + basic_machine=m68k-sun + basic_os= + ;; + sun3os3) + basic_machine=m68k-sun + basic_os=sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + basic_os=sunos4 + ;; + sun4) + basic_machine=sparc-sun + basic_os= + ;; + sun4os3) + basic_machine=sparc-sun + basic_os=sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + basic_os=sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + basic_os=solaris2 + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + basic_os= + ;; + sv1) + basic_machine=sv1-cray + basic_os=unicos + ;; + symmetry) + basic_machine=i386-sequent + basic_os=dynix + ;; + t3e) + basic_machine=alphaev5-cray + basic_os=unicos + ;; + t90) + basic_machine=t90-cray + basic_os=unicos + ;; + toad1) + basic_machine=pdp10-xkl + basic_os=tops20 + ;; + tpf) + basic_machine=s390x-ibm + basic_os=tpf + ;; + udi29k) + basic_machine=a29k-amd + basic_os=udi + ;; + ultra3) + basic_machine=a29k-nyu + basic_os=sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + basic_os=none + ;; + vaxv) + basic_machine=vax-dec + basic_os=sysv + ;; + vms) + basic_machine=vax-dec + basic_os=vms + ;; + vsta) + basic_machine=i386-pc + basic_os=vsta + ;; + vxworks960) + basic_machine=i960-wrs + basic_os=vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + basic_os=vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + basic_os=vxworks + ;; + xbox) + basic_machine=i686-pc + basic_os=mingw32 + ;; + ymp) + basic_machine=ymp-cray + basic_os=unicos + ;; + *) + basic_machine=$1 + basic_os= + ;; + esac + ;; +esac + +# Decode 1-component or ad-hoc basic machines +case $basic_machine in + # Here we handle the default manufacturer of certain CPU types. It is in + # some cases the only manufacturer, in others, it is the most popular. + w89k) + cpu=hppa1.1 + vendor=winbond + ;; + op50n) + cpu=hppa1.1 + vendor=oki + ;; + op60c) + cpu=hppa1.1 + vendor=oki + ;; + ibm*) + cpu=i370 + vendor=ibm + ;; + orion105) + cpu=clipper + vendor=highlevel + ;; + mac | mpw | mac-mpw) + cpu=m68k + vendor=apple + ;; + pmac | pmac-mpw) + cpu=powerpc + vendor=apple + ;; + + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + cpu=m68000 + vendor=att + ;; + 3b*) + cpu=we32k + vendor=att + ;; + bluegene*) + cpu=powerpc + vendor=ibm + basic_os=cnk + ;; + decsystem10* | dec10*) + cpu=pdp10 + vendor=dec + basic_os=tops10 + ;; + decsystem20* | dec20*) + cpu=pdp10 + vendor=dec + basic_os=tops20 + ;; + delta | 3300 | delta-motorola | 3300-motorola | motorola-delta | motorola-3300) + cpu=m68k + vendor=motorola + ;; + # This used to be dpx2*, but that gets the RS6000-based + # DPX/20 and the x86-based DPX/2-100 wrong. See + # https://oldskool.silicium.org/stations/bull_dpx20.htm + # https://www.feb-patrimoine.com/english/bull_dpx2.htm + # https://www.feb-patrimoine.com/english/unix_and_bull.htm + dpx2 | dpx2[23]00 | dpx2[23]xx) + cpu=m68k + vendor=bull + ;; + dpx2100 | dpx21xx) + cpu=i386 + vendor=bull + ;; + dpx20) + cpu=rs6000 + vendor=bull + ;; + encore | umax | mmax) + cpu=ns32k + vendor=encore + ;; + elxsi) + cpu=elxsi + vendor=elxsi + basic_os=${basic_os:-bsd} + ;; + fx2800) + cpu=i860 + vendor=alliant + ;; + genix) + cpu=ns32k + vendor=ns + ;; + h3050r* | hiux*) + cpu=hppa1.1 + vendor=hitachi + basic_os=hiuxwe2 + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + cpu=hppa1.0 + vendor=hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + cpu=m68000 + vendor=hp + ;; + hp9k3[2-9][0-9]) + cpu=m68k + vendor=hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + cpu=hppa1.0 + vendor=hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + cpu=hppa1.1 + vendor=hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + cpu=hppa1.1 + vendor=hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + cpu=hppa1.1 + vendor=hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + cpu=hppa1.1 + vendor=hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + cpu=hppa1.0 + vendor=hp + ;; + i*86v32) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=sysv32 + ;; + i*86v4*) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=sysv4 + ;; + i*86v) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=sysv + ;; + i*86sol2) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=solaris2 + ;; + j90 | j90-cray) + cpu=j90 + vendor=cray + basic_os=${basic_os:-unicos} + ;; + iris | iris4d) + cpu=mips + vendor=sgi + case $basic_os in + irix*) + ;; + *) + basic_os=irix4 + ;; + esac + ;; + miniframe) + cpu=m68000 + vendor=convergent + ;; + *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) + cpu=m68k + vendor=atari + basic_os=mint + ;; + news-3600 | risc-news) + cpu=mips + vendor=sony + basic_os=newsos + ;; + next | m*-next) + cpu=m68k + vendor=next + ;; + np1) + cpu=np1 + vendor=gould + ;; + op50n-* | op60c-*) + cpu=hppa1.1 + vendor=oki + basic_os=proelf + ;; + pa-hitachi) + cpu=hppa1.1 + vendor=hitachi + basic_os=hiuxwe2 + ;; + pbd) + cpu=sparc + vendor=tti + ;; + pbb) + cpu=m68k + vendor=tti + ;; + pc532) + cpu=ns32k + vendor=pc532 + ;; + pn) + cpu=pn + vendor=gould + ;; + power) + cpu=power + vendor=ibm + ;; + ps2) + cpu=i386 + vendor=ibm + ;; + rm[46]00) + cpu=mips + vendor=siemens + ;; + rtpc | rtpc-*) + cpu=romp + vendor=ibm + ;; + sde) + cpu=mipsisa32 + vendor=sde + basic_os=${basic_os:-elf} + ;; + simso-wrs) + cpu=sparclite + vendor=wrs + basic_os=vxworks + ;; + tower | tower-32) + cpu=m68k + vendor=ncr + ;; + vpp*|vx|vx-*) + cpu=f301 + vendor=fujitsu + ;; + w65) + cpu=w65 + vendor=wdc + ;; + w89k-*) + cpu=hppa1.1 + vendor=winbond + basic_os=proelf + ;; + none) + cpu=none + vendor=none + ;; + leon|leon[3-9]) + cpu=sparc + vendor=$basic_machine + ;; + leon-*|leon[3-9]-*) + cpu=sparc + vendor=`echo "$basic_machine" | sed 's/-.*//'` + ;; + + *-*) + saved_IFS=$IFS + IFS="-" read cpu vendor <&2 + exit 1 + ;; + esac + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $vendor in + digital*) + vendor=dec + ;; + commodore*) + vendor=cbm + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if test x"$basic_os" != x +then + +# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just +# set os. +obj= +case $basic_os in + gnu/linux*) + kernel=linux + os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'` + ;; + os2-emx) + kernel=os2 + os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'` + ;; + nto-qnx*) + kernel=nto + os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` + ;; + *-*) + saved_IFS=$IFS + IFS="-" read kernel os <&2 + fi + ;; + *) + echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 + exit 1 + ;; +esac + +case $obj in + aout* | coff* | elf* | pe*) + ;; + '') + # empty is fine + ;; + *) + echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 + exit 1 + ;; +esac + +# Here we handle the constraint that a (synthetic) cpu and os are +# valid only in combination with each other and nowhere else. +case $cpu-$os in + # The "javascript-unknown-ghcjs" triple is used by GHC; we + # accept it here in order to tolerate that, but reject any + # variations. + javascript-ghcjs) + ;; + javascript-* | *-ghcjs) + echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 + exit 1 + ;; +esac + +# As a final step for OS-related things, validate the OS-kernel combination +# (given a valid OS), if there is a kernel. +case $kernel-$os-$obj in + linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ + | linux-mlibc*- | linux-musl*- | linux-newlib*- \ + | linux-relibc*- | linux-uclibc*- | linux-ohos*- ) + ;; + uclinux-uclibc*- | uclinux-gnu*- ) + ;; + managarm-mlibc*- | managarm-kernel*- ) + ;; + windows*-msvc*-) + ;; + -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ + | -uclibc*- ) + # These are just libc implementations, not actual OSes, and thus + # require a kernel. + echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 + exit 1 + ;; + -kernel*- ) + echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 + exit 1 + ;; + *-kernel*- ) + echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 + exit 1 + ;; + *-msvc*- ) + echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 + exit 1 + ;; + kfreebsd*-gnu*- | knetbsd*-gnu*- | netbsd*-gnu*- | kopensolaris*-gnu*-) + ;; + vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) + ;; + nto-qnx*-) + ;; + os2-emx-) + ;; + rtmk-nova-) + ;; + *-eabi*- | *-gnueabi*-) + ;; + none--*) + # None (no kernel, i.e. freestanding / bare metal), + # can be paired with an machine code file format + ;; + -*-) + # Blank kernel with real OS is always fine. + ;; + --*) + # Blank kernel and OS with real machine code file format is always fine. + ;; + *-*-*) + echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 + exit 1 + ;; +esac + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +case $vendor in + unknown) + case $cpu-$os in + *-riscix*) + vendor=acorn + ;; + *-sunos* | *-solaris*) + vendor=sun + ;; + *-cnk* | *-aix*) + vendor=ibm + ;; + *-beos*) + vendor=be + ;; + *-hpux*) + vendor=hp + ;; + *-mpeix*) + vendor=hp + ;; + *-hiux*) + vendor=hitachi + ;; + *-unos*) + vendor=crds + ;; + *-dgux*) + vendor=dg + ;; + *-luna*) + vendor=omron + ;; + *-genix*) + vendor=ns + ;; + *-clix*) + vendor=intergraph + ;; + *-mvs* | *-opened*) + vendor=ibm + ;; + *-os400*) + vendor=ibm + ;; + s390-* | s390x-*) + vendor=ibm + ;; + *-ptx*) + vendor=sequent + ;; + *-tpf*) + vendor=ibm + ;; + *-vxsim* | *-vxworks* | *-windiss*) + vendor=wrs + ;; + *-aux*) + vendor=apple + ;; + *-hms*) + vendor=hitachi + ;; + *-mpw* | *-macos*) + vendor=apple + ;; + *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) + vendor=atari + ;; + *-vos*) + vendor=stratus + ;; + esac + ;; +esac + +echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" +exit + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/Headers/wayland/WaylandOpenGL.h b/Headers/wayland/WaylandOpenGL.h new file mode 100644 index 00000000..5e811dbb --- /dev/null +++ b/Headers/wayland/WaylandOpenGL.h @@ -0,0 +1,58 @@ +/* -*-ObjC-*- */ +/* WaylandOpenGL - NSOpenGL management for Wayland backend + + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _GNUstep_H_WaylandOpenGL_ +#define _GNUstep_H_WaylandOpenGL_ + +#include +#include + +@class NSView; +struct wl_egl_window; +struct window; + +@interface WaylandGLContext : NSOpenGLContext +{ + NSOpenGLPixelFormat *_pixelFormat; + NSView *_view; + NSOpenGLContext *_shareContext; + struct window *_window; + struct wl_egl_window *_eglWindow; + EGLDisplay _eglDisplay; + EGLContext _eglContext; + EGLSurface _eglSurface; + int _swapInterval; +} +@end + +@interface WaylandGLPixelFormat : NSOpenGLPixelFormat +{ + NSOpenGLPixelFormatAttribute *_attributes; + NSUInteger _attributeCount; +} + +- (EGLConfig)eglConfigForDisplay:(EGLDisplay)eglDisplay; +@end + +#endif diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index 22bf5e3b..df565e73 100755 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -149,6 +149,7 @@ struct window BOOL moving; BOOL resizing; BOOL ignoreMouse; + BOOL usesOpenGL; float pos_x; float pos_y; diff --git a/Source/GNUmakefile.preamble b/Source/GNUmakefile.preamble index b4b47aee..19b5fb04 100644 --- a/Source/GNUmakefile.preamble +++ b/Source/GNUmakefile.preamble @@ -45,7 +45,7 @@ CONFIG_SYSTEM_INCL += $(GRAPHIC_CFLAGS) # Additional library directories the linker should search #ADDITIONAL_LIB_DIRS += -CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) +CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) -lGL -lEGL # # Flags for compiling as a bundle or library (if the system depends diff --git a/Source/wayland/GNUmakefile b/Source/wayland/GNUmakefile index a106b5e8..4c02a9cb 100644 --- a/Source/wayland/GNUmakefile +++ b/Source/wayland/GNUmakefile @@ -51,6 +51,8 @@ WaylandServer+Keyboard.m \ WaylandServer+Seat.m \ WaylandServer+Xdgshell.m \ WaylandServer+Layershell.m \ +WaylandGLContext.m \ +WaylandGLPixelFormat.m \ -include GNUmakefile.preamble diff --git a/Source/wayland/GNUmakefile.preamble b/Source/wayland/GNUmakefile.preamble index 8cde639f..8b928e8e 100644 --- a/Source/wayland/GNUmakefile.preamble +++ b/Source/wayland/GNUmakefile.preamble @@ -42,7 +42,7 @@ ADDITIONAL_INCLUDE_DIRS += -I../../Headers \ -I../$(GNUSTEP_TARGET_DIR) $(GRAPHIC_CFLAGS) # Additional LDFLAGS to pass to the linker -ADDITIONAL_LDFLAGS = +ADDITIONAL_LDFLAGS = -lGL -lEGL # Additional library directories the linker should search ADDITIONAL_LIB_DIRS = diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m new file mode 100644 index 00000000..07a67899 --- /dev/null +++ b/Source/wayland/WaylandGLContext.m @@ -0,0 +1,423 @@ +/* -*- mode:ObjC -*- + WaylandGLContext - backend implementation of NSOpenGLContext using EGL + + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "wayland/WaylandServer.h" +#include "wayland/WaylandOpenGL.h" + +static WaylandGLContext *currentGLContext; + +@implementation WaylandGLContext + ++ (void)clearCurrentContext +{ + if (currentGLContext != nil && currentGLContext->_eglDisplay != EGL_NO_DISPLAY) + { + eglMakeCurrent(currentGLContext->_eglDisplay, + EGL_NO_SURFACE, + EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } + currentGLContext = nil; +} + ++ (NSOpenGLContext *)currentContext +{ + return currentGLContext; +} + +- (void *)CGLContextObj +{ + return (void *)_eglContext; +} + +- (void)copyAttributesFromContext:(NSOpenGLContext *)context + withMask:(unsigned long)mask +{ + (void)context; + (void)mask; +} + +- (id)initWithCGLContextObj:(void *)context +{ + NSDebugMLLog(@"OpenGL", @"initWithCGLContextObj is not supported on Wayland (%p)", context); + [self release]; + return nil; +} + +- (BOOL)_ensureDisplayAndContextWithShare:(NSOpenGLContext *)share +{ + EGLint major; + EGLint minor; + EGLConfig eglConfig; + EGLContext shareContext = EGL_NO_CONTEXT; + EGLint glesContextAttrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + EGLint *contextAttrs = NULL; + struct wl_display *wlDisplay; + + if (_eglDisplay != EGL_NO_DISPLAY && _eglContext != EGL_NO_CONTEXT) + { + return YES; + } + + wlDisplay = NULL; + if (_window != NULL && _window->wlconfig != NULL) + { + wlDisplay = _window->wlconfig->display; + } + + if (wlDisplay == NULL) + { + NSDebugMLLog(@"OpenGL", @"Cannot create EGL display without an attached Wayland window"); + return NO; + } + + _eglDisplay = eglGetDisplay((EGLNativeDisplayType)wlDisplay); + if (_eglDisplay == EGL_NO_DISPLAY) + { + NSDebugMLLog(@"OpenGL", @"eglGetDisplay failed"); + return NO; + } + + if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE) + { + NSDebugMLLog(@"OpenGL", @"eglInitialize failed"); + return NO; + } + + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) + { + NSDebugMLLog(@"OpenGL", @"eglBindAPI(EGL_OPENGL_API) failed, trying GLES2"); + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) + { + NSDebugMLLog(@"OpenGL", @"eglBindAPI failed for OpenGL and GLES"); + return NO; + } + contextAttrs = glesContextAttrs; + } + + eglConfig = [(WaylandGLPixelFormat *)_pixelFormat eglConfigForDisplay:_eglDisplay]; + if (eglConfig == NULL) + { + return NO; + } + + if (share != nil && [share isKindOfClass:[WaylandGLContext class]]) + { + shareContext = ((WaylandGLContext *)share)->_eglContext; + } + + _eglContext = eglCreateContext(_eglDisplay, eglConfig, shareContext, contextAttrs); + if (_eglContext == EGL_NO_CONTEXT) + { + NSDebugMLLog(@"OpenGL", @"eglCreateContext failed"); + return NO; + } + + return YES; +} + +- (BOOL)_ensureSurface +{ + EGLConfig eglConfig; + + if (_window == NULL || _window->surface == NULL) + { + return NO; + } + + if (_eglWindow == NULL) + { + _eglWindow = wl_egl_window_create(_window->surface, + (int)_window->width, + (int)_window->height); + if (_eglWindow == NULL) + { + NSDebugMLLog(@"OpenGL", @"wl_egl_window_create failed"); + return NO; + } + } + + if (_eglSurface != EGL_NO_SURFACE) + { + return YES; + } + + eglConfig = [(WaylandGLPixelFormat *)_pixelFormat eglConfigForDisplay:_eglDisplay]; + _eglSurface = eglCreateWindowSurface(_eglDisplay, + eglConfig, + (EGLNativeWindowType)_eglWindow, + NULL); + + if (_eglSurface == EGL_NO_SURFACE) + { + NSDebugMLLog(@"OpenGL", @"eglCreateWindowSurface failed"); + return NO; + } + + if (_swapInterval >= 0) + { + eglSwapInterval(_eglDisplay, _swapInterval); + } + + return YES; +} + +- (void)_destroySurface +{ + if (_eglDisplay != EGL_NO_DISPLAY && _eglSurface != EGL_NO_SURFACE) + { + eglDestroySurface(_eglDisplay, _eglSurface); + _eglSurface = EGL_NO_SURFACE; + } + + if (_eglWindow != NULL) + { + wl_egl_window_destroy(_eglWindow); + _eglWindow = NULL; + } +} + +- (id)initWithFormat:(NSOpenGLPixelFormat *)format + shareContext:(NSOpenGLContext *)share +{ + self = [super init]; + + if (!self) + { + return nil; + } + + if (format == nil || [format isKindOfClass:[WaylandGLPixelFormat class]] == NO) + { + NSDebugMLLog(@"OpenGL", @"Invalid pixel format %@", format); + [self release]; + return nil; + } + + _eglDisplay = EGL_NO_DISPLAY; + _eglContext = EGL_NO_CONTEXT; + _eglSurface = EGL_NO_SURFACE; + _eglWindow = NULL; + _window = NULL; + _swapInterval = 1; + + _pixelFormat = RETAIN(format); + _shareContext = RETAIN(share); + + if (share != nil && [share isKindOfClass:[WaylandGLContext class]]) + { + _eglDisplay = ((WaylandGLContext *)share)->_eglDisplay; + } + + _pixelFormat = RETAIN(format); + + return self; +} + +- (NSOpenGLPixelFormat *)pixelFormat +{ + return _pixelFormat; +} + +- (void)setView:(NSView *)view +{ + GSDisplayServer *server; + NSWindow *window; + + if (view == nil) + { + [NSException raise:NSInvalidArgumentException + format:@"setView called with nil"]; + } + + ASSIGN(_view, view); + + window = [view window]; + if (window == nil) + { + return; + } + + server = GSCurrentServer(); + _window = (struct window *)[server windowDevice:[window windowNumber]]; + + if (_window == NULL) + { + NSDebugMLLog(@"OpenGL", @"No wayland window device found for view %@", view); + return; + } + + _window->usesOpenGL = YES; + + [self _destroySurface]; + + if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO) + { + return; + } + + [self _ensureSurface]; +} + +- (NSView *)view +{ + return _view; +} + +- (void)clearDrawable +{ + if (_window != NULL) + { + _window->usesOpenGL = NO; + } + [self _destroySurface]; +} + +- (void)makeCurrentContext +{ + if (_view == nil) + { + [NSException raise:NSGenericException + format:@"GL Context has no view attached, cannot be made current"]; + } + + if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO) + { + return; + } + + if ([self _ensureSurface] == NO) + { + return; + } + + if (eglMakeCurrent(_eglDisplay, _eglSurface, _eglSurface, _eglContext) == EGL_FALSE) + { + NSDebugMLLog(@"OpenGL", @"eglMakeCurrent failed"); + return; + } + + currentGLContext = self; +} + +- (void)flushBuffer +{ + if (_eglDisplay == EGL_NO_DISPLAY || _eglSurface == EGL_NO_SURFACE) + { + return; + } + + eglSwapBuffers(_eglDisplay, _eglSurface); + if (_window != NULL && _window->wlconfig != NULL) + { + wl_display_flush(_window->wlconfig->display); + } +} + +- (void)update +{ + if (_eglWindow != NULL && _window != NULL) + { + wl_egl_window_resize(_eglWindow, + (int)_window->width, + (int)_window->height, + 0, + 0); + } +} + +- (void)getValues:(long *)vals forParameter:(NSOpenGLContextParameter)param +{ + if (vals == NULL) + { + return; + } + + switch (param) + { + case NSOpenGLCPSwapInterval: + *vals = _swapInterval; + break; + default: + *vals = 0; + break; + } +} + +- (void)setValues:(const long *)vals forParameter:(NSOpenGLContextParameter)param +{ + if (vals == NULL) + { + return; + } + + if (param == NSOpenGLCPSwapInterval) + { + _swapInterval = (int)*vals; + if (_eglDisplay != EGL_NO_DISPLAY) + { + eglSwapInterval(_eglDisplay, _swapInterval); + } + } +} + +- (void)dealloc +{ + if (currentGLContext == self) + { + [WaylandGLContext clearCurrentContext]; + } + + if (_window != NULL) + { + _window->usesOpenGL = NO; + } + + [self _destroySurface]; + + if (_eglDisplay != EGL_NO_DISPLAY && _eglContext != EGL_NO_CONTEXT) + { + eglDestroyContext(_eglDisplay, _eglContext); + _eglContext = EGL_NO_CONTEXT; + } + + RELEASE(_view); + RELEASE(_shareContext); + RELEASE(_pixelFormat); + + [super dealloc]; +} + +@end diff --git a/Source/wayland/WaylandGLPixelFormat.m b/Source/wayland/WaylandGLPixelFormat.m new file mode 100644 index 00000000..7e1a2f56 --- /dev/null +++ b/Source/wayland/WaylandGLPixelFormat.m @@ -0,0 +1,238 @@ +/* -*- mode:ObjC -*- + WaylandGLPixelFormat - backend implementation of NSOpenGLPixelFormat + + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "wayland/WaylandOpenGL.h" + +@implementation WaylandGLPixelFormat + +static BOOL +_isAttributeWithValue(NSOpenGLPixelFormatAttribute attr) +{ + switch (attr) + { + case NSOpenGLPFAAuxBuffers: + case NSOpenGLPFAColorSize: + case NSOpenGLPFAAlphaSize: + case NSOpenGLPFADepthSize: + case NSOpenGLPFAStencilSize: + case NSOpenGLPFAAccumSize: + case NSOpenGLPFARendererID: + case NSOpenGLPFAScreenMask: + case NSOpenGLPFASamples: + case NSOpenGLPFAAuxDepthStencil: + case NSOpenGLPFASampleBuffers: + return YES; + default: + return NO; + } +} + +- (id)initWithAttributes:(NSOpenGLPixelFormatAttribute *)attribs +{ + NSOpenGLPixelFormatAttribute *ptr; + + self = [super init]; + if (self == nil) + { + return nil; + } + + if (attribs == NULL) + { + _attributeCount = 1; + _attributes = NSZoneMalloc(NSDefaultMallocZone(), + sizeof(NSOpenGLPixelFormatAttribute)); + _attributes[0] = (NSOpenGLPixelFormatAttribute)0; + return self; + } + + _attributeCount = 1; + for (ptr = attribs; *ptr != 0; ptr++) + { + _attributeCount++; + if (_isAttributeWithValue(*ptr)) + { + if (*(ptr + 1) != 0) + { + ptr++; + _attributeCount++; + } + } + } + + _attributes = NSZoneMalloc(NSDefaultMallocZone(), + _attributeCount * sizeof(NSOpenGLPixelFormatAttribute)); + memcpy(_attributes, attribs, + _attributeCount * sizeof(NSOpenGLPixelFormatAttribute)); + + return self; +} + +- (EGLConfig)eglConfigForDisplay:(EGLDisplay)eglDisplay +{ + EGLint redSize = 8; + EGLint greenSize = 8; + EGLint blueSize = 8; + EGLint alphaSize = 8; + EGLint depthSize = 24; + EGLint stencilSize = 8; + EGLint sampleBuffers = 0; + EGLint samples = 0; + EGLint renderableType = EGL_OPENGL_BIT; +#ifdef EGL_OPENGL_ES2_BIT + renderableType |= EGL_OPENGL_ES2_BIT; +#endif + EGLConfig config = NULL; + EGLint configCount = 0; + NSUInteger i; + + if (_attributes != NULL) + { + for (i = 0; i < _attributeCount; i++) + { + NSOpenGLPixelFormatAttribute attr = _attributes[i]; + if (_isAttributeWithValue(attr) == NO) + { + continue; + } + + if (i + 1 >= _attributeCount) + { + break; + } + + switch (attr) + { + case NSOpenGLPFAColorSize: + redSize = greenSize = blueSize = ((EGLint)_attributes[i + 1] / 3); + if (redSize < 1) + { + redSize = greenSize = blueSize = 1; + } + break; + case NSOpenGLPFAAlphaSize: + alphaSize = _attributes[i + 1]; + break; + case NSOpenGLPFADepthSize: + depthSize = _attributes[i + 1]; + break; + case NSOpenGLPFAStencilSize: + stencilSize = _attributes[i + 1]; + break; + case NSOpenGLPFASampleBuffers: + sampleBuffers = _attributes[i + 1]; + break; + case NSOpenGLPFASamples: + samples = _attributes[i + 1]; + break; + default: + break; + } + + i++; + } + } + + { + EGLint attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, renderableType, + EGL_RED_SIZE, redSize, + EGL_GREEN_SIZE, greenSize, + EGL_BLUE_SIZE, blueSize, + EGL_ALPHA_SIZE, alphaSize, + EGL_DEPTH_SIZE, depthSize, + EGL_STENCIL_SIZE, stencilSize, + EGL_SAMPLE_BUFFERS, sampleBuffers, + EGL_SAMPLES, samples, + EGL_NONE + }; + + if (eglChooseConfig(eglDisplay, attrs, &config, 1, &configCount) == EGL_FALSE + || configCount == 0) + { + NSDebugMLLog(@"OpenGL", @"No EGL config matched requested NSOpenGL attributes"); + return NULL; + } + } + + return config; +} + +- (void)getValues:(int *)vals + forAttribute:(NSOpenGLPixelFormatAttribute)attrib + forVirtualScreen:(int)screen +{ + NSUInteger i; + + (void)screen; + + if (vals == NULL) + { + return; + } + + *vals = 0; + if (_attributes == NULL) + { + return; + } + + for (i = 0; i + 1 < _attributeCount; i++) + { + if (_attributes[i] == attrib) + { + if (_isAttributeWithValue(attrib)) + { + *vals = _attributes[i + 1]; + } + else + { + *vals = 1; + } + return; + } + } +} + +- (void)dealloc +{ + if (_attributes != NULL) + { + NSZoneFree(NSDefaultMallocZone(), _attributes); + _attributes = NULL; + } + + [super dealloc]; +} + +@end diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 39568213..4e5c609f 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -46,6 +46,7 @@ #include #include "wayland/WaylandServer.h" +#include "wayland/WaylandOpenGL.h" extern const struct wl_output_listener output_listener; @@ -337,6 +338,16 @@ - (void)beep NSDebugLog(@"beep"); } +- glContextClass +{ + return [WaylandGLContext class]; +} + +- glPixelFormatClass +{ + return [WaylandGLPixelFormat class]; +} + @end @implementation @@ -402,6 +413,7 @@ - (int)window:(NSRect) window->moving = NO; window->resizing = NO; window->ignoreMouse = NO; + window->usesOpenGL = NO; // FIXME is this needed? if (window->pos_x < 0) @@ -712,6 +724,16 @@ - (void)flushwindowrect:(NSRect)rect:(int)win NSDebugLog(@"[%d] flushwindowrect: %f,%f %fx%f", win, NSMinX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect)); struct window *window = get_window_with_id(wlconfig, win); + if (window == NULL) + { + return; + } + + if (window->usesOpenGL) + { + NSDebugLog(@"[%d] skipping cairo flush for OpenGL-backed window", win); + return; + } [[GSCurrentContext() class] handleExposeRect:rect forDriver:window->wcs]; } @@ -1099,4 +1121,4 @@ - (void)destroyWindowShell:(struct window *)window wl_display_flush(window->wlconfig->display); } -@end \ No newline at end of file +@end diff --git a/config.h.in b/config.h.in index 18ed752c..56f572b2 100644 --- a/config.h.in +++ b/config.h.in @@ -44,25 +44,25 @@ /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H -/* Define to 1 if you have the `wayland-client' library (-lwayland-client). */ +/* Define to 1 if you have the 'wayland-client' library (-lwayland-client). */ #undef HAVE_LIBWAYLAND_CLIENT -/* Define to 1 if you have the `Xext' library (-lXext). */ +/* Define to 1 if you have the 'Xext' library (-lXext). */ #undef HAVE_LIBXEXT -/* Define to 1 if you have the `Xft' library (-lXft). */ +/* Define to 1 if you have the 'Xft' library (-lXft). */ #undef HAVE_LIBXFT -/* Define to 1 if you have the `xkbcommon' library (-lxkbcommon). */ +/* Define to 1 if you have the 'xkbcommon' library (-lxkbcommon). */ #undef HAVE_LIBXKBCOMMON -/* Define to 1 if you have the `Xmu' library (-lXmu). */ +/* Define to 1 if you have the 'Xmu' library (-lXmu). */ #undef HAVE_LIBXMU -/* Define to 1 if you have the `Xt' library (-lXt). */ +/* Define to 1 if you have the 'Xt' library (-lXt). */ #undef HAVE_LIBXT -/* Define to 1 if you have the `shmctl' function. */ +/* Define to 1 if you have the 'shmctl' function. */ #undef HAVE_SHMCTL /* Define to 1 if you have the header file. */ @@ -80,7 +80,7 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H -/* Define to 1 if you have the `syslog' function. */ +/* Define to 1 if you have the 'syslog' function. */ #undef HAVE_SYSLOG /* Define to 1 if you have the header file. */ @@ -95,7 +95,7 @@ /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H -/* Define to 1 if you have the `usleep' function. */ +/* Define to 1 if you have the 'usleep' function. */ #undef HAVE_USLEEP /* Define if you have XftDrawStringUtf8 */ @@ -141,7 +141,7 @@ /* Define to enable Xshape support */ #undef HAVE_XSHAPE -/* Define to 1 if you have the `Xutf8LookupString' function. */ +/* Define to 1 if you have the 'Xutf8LookupString' function. */ #undef HAVE_XUTF8LOOKUPSTRING /* Define to the address where bug reports for this package should be sent. */ @@ -162,7 +162,7 @@ /* Define to the version of this package. */ #undef PACKAGE_VERSION -/* Define to 1 if all of the C90 standard headers exist (not just the ones +/* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS diff --git a/configure b/configure index 55ce0414..3f57ca59 100755 --- a/configure +++ b/configure @@ -1,9 +1,9 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71. +# Generated by GNU Autoconf 2.72. # # -# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, +# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, # Inc. # # @@ -15,7 +15,6 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh @@ -24,12 +23,13 @@ then : # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else $as_nop - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi @@ -101,7 +101,7 @@ IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 @@ -131,15 +131,14 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. +# out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="as_nop=: -if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: @@ -147,12 +146,13 @@ then : # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else \$as_nop - case \`(set -o) 2>/dev/null\` in #( +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi " @@ -170,8 +170,9 @@ as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : -else \$as_nop - exitcode=1; echo positional parameters were not saved. +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) @@ -185,14 +186,15 @@ test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes -else $as_nop - as_have_required=no +else case e in #( + e) as_have_required=no ;; +esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : -else $as_nop - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do @@ -225,12 +227,13 @@ IFS=$as_save_IFS if $as_found then : -else $as_nop - if { test -f "$SHELL" || test -f "$SHELL.exe"; } && +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes -fi +fi ;; +esac fi @@ -252,7 +255,7 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. +# out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi @@ -271,7 +274,8 @@ $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 -fi +fi ;; +esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} @@ -310,14 +314,6 @@ as_fn_exit () as_fn_set_status $1 exit $1 } # as_fn_exit -# as_fn_nop -# --------- -# Do nothing but, unlike ":", preserve the value of $?. -as_fn_nop () -{ - return $? -} -as_nop=as_fn_nop # as_fn_mkdir_p # ------------- @@ -386,11 +382,12 @@ then : { eval $1+=\$2 }' -else $as_nop - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -404,21 +401,14 @@ then : { as_val=$(( $* )) }' -else $as_nop - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith -# as_fn_nop -# --------- -# Do nothing but, unlike ":", preserve the value of $?. -as_fn_nop () -{ - return $? -} -as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- @@ -492,6 +482,8 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits /[$]LINENO/= ' <$as_myself | sed ' + t clear + :clear s/[$]LINENO.*/&-/ t lineno b @@ -540,7 +532,6 @@ esac as_echo='printf %s\n' as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -552,9 +543,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -579,10 +570,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 /dev/null && - as_fn_error $? "invalid feature name: \`$ac_useropt'" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -943,7 +934,7 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: \`$ac_useropt'" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -1156,7 +1147,7 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: \`$ac_useropt'" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -1172,7 +1163,7 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: \`$ac_useropt'" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in @@ -1202,8 +1193,8 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: \`$ac_option' -Try \`$0 --help' for more information" + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" ;; *=*) @@ -1211,7 +1202,7 @@ Try \`$0 --help' for more information" # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; @@ -1261,7 +1252,7 @@ do as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: `$host' +# There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias @@ -1329,7 +1320,7 @@ if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi -ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` @@ -1357,7 +1348,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures this package to adapt to many kinds of systems. +'configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1371,11 +1362,11 @@ Configuration: --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit - -q, --quiet, --silent do not print \`checking ...' messages + -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for \`--cache-file=config.cache' + -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or \`..'] + --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX @@ -1383,10 +1374,10 @@ Installation directories: --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, \`make install' will install all the files in -\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify -an installation prefix other than \`$ac_default_prefix' using \`--prefix', -for instance \`--prefix=\$HOME'. +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. For better control, use the options below. @@ -1511,7 +1502,7 @@ Some influential environment variables: GLITZ_WGL_LIBS linker flags for GLITZ_WGL, overriding pkg-config -Use these variables to override the choices made by `configure' or to help +Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. @@ -1579,9 +1570,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure -generated by GNU Autoconf 2.71 +generated by GNU Autoconf 2.72 -Copyright (C) 2021 Free Software Foundation, Inc. +Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF @@ -1620,11 +1611,12 @@ printf "%s\n" "$ac_try_echo"; } >&5 } && test -s conftest.$ac_objext then : ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval @@ -1658,11 +1650,12 @@ printf "%s\n" "$ac_try_echo"; } >&5 } then : ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval @@ -1681,8 +1674,8 @@ printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> @@ -1690,10 +1683,12 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" -else $as_nop - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 @@ -1733,11 +1728,12 @@ printf "%s\n" "$ac_try_echo"; } >&5 } then : ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would @@ -1760,15 +1756,15 @@ printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (); below. */ + which can conflict with char $2 (void); below. */ #include #undef $2 @@ -1779,7 +1775,7 @@ else $as_nop #ifdef __cplusplus extern "C" #endif -char $2 (); +char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ @@ -1798,11 +1794,13 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" -else $as_nop - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext + conftest$ac_exeext conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 @@ -1835,7 +1833,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was -generated by GNU Autoconf 2.71. Invocation command line was +generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -2081,10 +2079,10 @@ esac printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } fi done @@ -2120,9 +2118,7 @@ struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); -static char *e (p, i) - char **p; - int i; +static char *e (char **p, int i) { return p[i]; } @@ -2136,6 +2132,21 @@ static char *f (char * (*g) (char **, int), char **p, ...) return s; } +/* C89 style stringification. */ +#define noexpand_stringify(a) #a +const char *stringified = noexpand_stringify(arbitrary+token=sequence); + +/* C89 style token pasting. Exercises some of the corner cases that + e.g. old MSVC gets wrong, but not very hard. */ +#define noexpand_concat(a,b) a##b +#define expand_concat(a,b) noexpand_concat(a,b) +extern int vA; +extern int vbee; +#define aye A +#define bee B +int *pvA = &expand_concat(v,aye); +int *pvbee = &noexpand_concat(v,bee); + /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated @@ -2163,16 +2174,19 @@ ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' -// Does the compiler advertise C99 conformance? +/* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif +// See if C++-style comments work. + #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); +extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare @@ -2222,7 +2236,6 @@ typedef const char *ccp; static inline int test_restrict (ccp restrict text) { - // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) @@ -2288,6 +2301,8 @@ ac_c_conftest_c99_main=' ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; + // Work around memory leak warnings. + free (ia); // Check named initializers. struct named_init ni = { @@ -2309,7 +2324,7 @@ ac_c_conftest_c99_main=' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' -// Does the compiler advertise C11 conformance? +/* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif @@ -2501,8 +2516,9 @@ IFS=$as_save_IFS if $as_found then : -else $as_nop - as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 +else case e in #( + e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; +esac fi @@ -2530,12 +2546,12 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) @@ -2544,18 +2560,18 @@ printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 -printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 -printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 -printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. @@ -2571,11 +2587,11 @@ printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi done if $ac_cache_corrupted; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## @@ -2623,15 +2639,16 @@ printf %s "checking build system type... " >&6; } if test ${ac_cv_build+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_build_alias=$build_alias +else case e in #( + e) ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5 - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 printf "%s\n" "$ac_cv_build" >&6; } @@ -2658,14 +2675,15 @@ printf %s "checking host system type... " >&6; } if test ${ac_cv_host+y} then : printf %s "(cached) " >&6 -else $as_nop - if test "x$host_alias" = x; then +else case e in #( + e) if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5 fi - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 printf "%s\n" "$ac_cv_host" >&6; } @@ -2692,14 +2710,15 @@ printf %s "checking target system type... " >&6; } if test ${ac_cv_target+y} then : printf %s "(cached) " >&6 -else $as_nop - if test "x$target_alias" = x; then +else case e in #( + e) if test "x$target_alias" = x; then ac_cv_target=$ac_cv_host else ac_cv_target=`$SHELL "${ac_aux_dir}config.sub" $target_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $target_alias failed" "$LINENO" 5 fi - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5 printf "%s\n" "$ac_cv_target" >&6; } @@ -2792,8 +2811,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2815,7 +2834,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -2837,8 +2857,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2860,7 +2880,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then @@ -2895,8 +2916,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2918,7 +2939,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -2940,8 +2962,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no @@ -2980,7 +3002,8 @@ if test $ac_prog_rejected = yes; then ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3004,8 +3027,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3027,7 +3050,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3053,8 +3077,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3076,7 +3100,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then @@ -3114,8 +3139,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3137,7 +3162,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then @@ -3159,8 +3185,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -3182,7 +3208,8 @@ done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then @@ -3211,10 +3238,10 @@ fi fi -test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 @@ -3286,8 +3313,8 @@ printf "%s\n" "$ac_try_echo"; } >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : - # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. -# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. @@ -3307,7 +3334,7 @@ do ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an `-o' + # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. @@ -3318,8 +3345,9 @@ do done test "$ac_cv_exeext" = no && ac_cv_exeext= -else $as_nop - ac_file='' +else case e in #( + e) ac_file='' ;; +esac fi if test -z "$ac_file" then : @@ -3328,13 +3356,14 @@ printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } @@ -3358,10 +3387,10 @@ printf "%s\n" "$ac_try_echo"; } >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : - # If both `conftest.exe' and `conftest' are `present' (well, observable) -# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will -# work properly (i.e., refer to `conftest.exe'), while it won't with -# `rm'. + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in @@ -3371,11 +3400,12 @@ for ac_file in conftest.exe conftest conftest.*; do * ) break;; esac done -else $as_nop - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +else case e in #( + e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 @@ -3391,6 +3421,8 @@ int main (void) { FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; return ferror (f) || fclose (f) != 0; ; @@ -3430,26 +3462,27 @@ printf "%s\n" "$ac_try_echo"; } >&5 if test "$cross_compiling" = maybe; then cross_compiling=yes else - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. -If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5; } +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -3481,16 +3514,18 @@ then : break;; esac done -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } @@ -3501,8 +3536,8 @@ printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -3519,12 +3554,14 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes -else $as_nop - ac_compiler_gnu=no +else case e in #( + e) ac_compiler_gnu=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu - + ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } @@ -3542,8 +3579,8 @@ printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_save_c_werror_flag=$ac_c_werror_flag +else case e in #( + e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" @@ -3561,8 +3598,8 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes -else $as_nop - CFLAGS="" +else case e in #( + e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3577,8 +3614,8 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : -else $as_nop - ac_c_werror_flag=$ac_save_c_werror_flag +else case e in #( + e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3595,12 +3632,15 @@ if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ac_c_werror_flag=$ac_save_c_werror_flag + ac_c_werror_flag=$ac_save_c_werror_flag ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } @@ -3627,8 +3667,8 @@ printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c11=no +else case e in #( + e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3645,25 +3685,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c11" = x +else case e in #( + e) if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } - CC="$CC $ac_cv_prog_cc_c11" + CC="$CC $ac_cv_prog_cc_c11" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 - ac_prog_cc_stdc=c11 + ac_prog_cc_stdc=c11 ;; +esac fi fi if test x$ac_prog_cc_stdc = xno @@ -3673,8 +3716,8 @@ printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c99=no +else case e in #( + e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3691,25 +3734,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c99" = x +else case e in #( + e) if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } - CC="$CC $ac_cv_prog_cc_c99" + CC="$CC $ac_cv_prog_cc_c99" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 - ac_prog_cc_stdc=c99 + ac_prog_cc_stdc=c99 ;; +esac fi fi if test x$ac_prog_cc_stdc = xno @@ -3719,8 +3765,8 @@ printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c89=no +else case e in #( + e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3737,25 +3783,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c89" = x +else case e in #( + e) if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } - CC="$CC $ac_cv_prog_cc_c89" + CC="$CC $ac_cv_prog_cc_c89" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 - ac_prog_cc_stdc=c89 + ac_prog_cc_stdc=c89 ;; +esac fi fi @@ -3780,8 +3829,8 @@ if test -z "$CPP"; then if test ${ac_cv_prog_CPP+y} then : printf %s "(cached) " >&6 -else $as_nop - # Double quotes because $CC needs to be expanded +else case e in #( + e) # Double quotes because $CC needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp do ac_preproc_ok=false @@ -3799,9 +3848,10 @@ _ACEOF if ac_fn_c_try_cpp "$LINENO" then : -else $as_nop - # Broken: fails on valid input. -continue +else case e in #( + e) # Broken: fails on valid input. +continue ;; +esac fi rm -f conftest.err conftest.i conftest.$ac_ext @@ -3815,15 +3865,16 @@ if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue -else $as_nop - # Passes both tests. +else case e in #( + e) # Passes both tests. ac_preproc_ok=: -break +break ;; +esac fi rm -f conftest.err conftest.i conftest.$ac_ext done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +# Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : @@ -3832,7 +3883,8 @@ fi done ac_cv_prog_CPP=$CPP - + ;; +esac fi CPP=$ac_cv_prog_CPP else @@ -3855,9 +3907,10 @@ _ACEOF if ac_fn_c_try_cpp "$LINENO" then : -else $as_nop - # Broken: fails on valid input. -continue +else case e in #( + e) # Broken: fails on valid input. +continue ;; +esac fi rm -f conftest.err conftest.i conftest.$ac_ext @@ -3871,24 +3924,26 @@ if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue -else $as_nop - # Passes both tests. +else case e in #( + e) # Passes both tests. ac_preproc_ok=: -break +break ;; +esac fi rm -f conftest.err conftest.i conftest.$ac_ext done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +# Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : -else $as_nop - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +else case e in #( + e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi ac_ext=c @@ -4005,8 +4060,9 @@ fi if test ${with_tiff_library+y} then : withval=$with_tiff_library; -else $as_nop - with_tiff_library= +else case e in #( + e) with_tiff_library= ;; +esac fi @@ -4029,7 +4085,7 @@ then : withval=$with_x; fi -# $have_x is `yes', `no', `disabled', or empty when we do not yet know. +# $have_x is 'yes', 'no', 'disabled', or empty when we do not yet know. if test "x$with_x" = xno; then # The user explicitly disabled X. have_x=disabled @@ -4039,8 +4095,8 @@ else *,NONE | NONE,*) if test ${ac_cv_have_x+y} then : printf %s "(cached) " >&6 -else $as_nop - # One or both of the vars are not set, and there is no cached value. +else case e in #( + e) # One or both of the vars are not set, and there is no cached value. ac_x_includes=no ac_x_libraries=no # Do we need to do anything special at all? @@ -4163,13 +4219,14 @@ if ac_fn_c_try_cpp "$LINENO" then : # We can compile using X headers with no special include directory. ac_x_includes= -else $as_nop - for ac_dir in $ac_x_header_dirs; do +else case e in #( + e) for ac_dir in $ac_x_header_dirs; do if test -r "$ac_dir/X11/Xlib.h"; then ac_x_includes=$ac_dir break fi -done +done ;; +esac fi rm -f conftest.err conftest.i conftest.$ac_ext fi # $ac_x_includes = no @@ -4196,8 +4253,8 @@ then : LIBS=$ac_save_LIBS # We can link X programs with no special library path. ac_x_libraries= -else $as_nop - LIBS=$ac_save_LIBS +else case e in #( + e) LIBS=$ac_save_LIBS for ac_dir in `printf "%s\n" "$ac_x_includes $ac_x_header_dirs" | sed s/include/lib/g` do # Don't even attempt the hair of trying to link an X program! @@ -4207,7 +4264,8 @@ do break 2 fi done -done +done ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext @@ -4224,6 +4282,7 @@ case $ac_x_includes,$ac_x_libraries in #( ac_cv_have_x="have_x=yes\ ac_x_includes='$ac_x_includes'\ ac_x_libraries='$ac_x_libraries'" ;; +esac ;; esac fi ;; #( @@ -4285,8 +4344,8 @@ then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } X_LIBS="$X_LIBS -R$x_libraries" -else $as_nop - LIBS="$ac_xsave_LIBS -R $x_libraries" +else case e in #( + e) LIBS="$ac_xsave_LIBS -R $x_libraries" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -4303,12 +4362,14 @@ then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } X_LIBS="$X_LIBS -R $x_libraries" -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: neither works" >&5 -printf "%s\n" "neither works" >&6; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: neither works" >&5 +printf "%s\n" "neither works" >&6; } ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext + conftest$ac_exeext conftest.$ac_ext ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext @@ -4332,8 +4393,14 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam \ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XOpenDisplay (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XOpenDisplay (void); int main (void) { @@ -4345,22 +4412,28 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dnet_ntoa in -ldnet" >&5 +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dnet_ntoa in -ldnet" >&5 printf %s "checking for dnet_ntoa in -ldnet... " >&6; } if test ${ac_cv_lib_dnet_dnet_ntoa+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldnet $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char dnet_ntoa (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char dnet_ntoa (void); int main (void) { @@ -4372,12 +4445,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dnet_dnet_ntoa=yes -else $as_nop - ac_cv_lib_dnet_dnet_ntoa=no +else case e in #( + e) ac_cv_lib_dnet_dnet_ntoa=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dnet_dnet_ntoa" >&5 printf "%s\n" "$ac_cv_lib_dnet_dnet_ntoa" >&6; } @@ -4392,16 +4467,22 @@ printf %s "checking for dnet_ntoa in -ldnet_stub... " >&6; } if test ${ac_cv_lib_dnet_stub_dnet_ntoa+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldnet_stub $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char dnet_ntoa (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char dnet_ntoa (void); int main (void) { @@ -4413,12 +4494,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dnet_stub_dnet_ntoa=yes -else $as_nop - ac_cv_lib_dnet_stub_dnet_ntoa=no +else case e in #( + e) ac_cv_lib_dnet_stub_dnet_ntoa=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dnet_stub_dnet_ntoa" >&5 printf "%s\n" "$ac_cv_lib_dnet_stub_dnet_ntoa" >&6; } @@ -4427,7 +4510,8 @@ then : X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet_stub" fi - fi + fi ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext @@ -4453,16 +4537,22 @@ printf %s "checking for gethostbyname in -lnsl... " >&6; } if test ${ac_cv_lib_nsl_gethostbyname+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char gethostbyname (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char gethostbyname (void); int main (void) { @@ -4474,12 +4564,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_nsl_gethostbyname=yes -else $as_nop - ac_cv_lib_nsl_gethostbyname=no +else case e in #( + e) ac_cv_lib_nsl_gethostbyname=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 printf "%s\n" "$ac_cv_lib_nsl_gethostbyname" >&6; } @@ -4494,16 +4586,22 @@ printf %s "checking for gethostbyname in -lbsd... " >&6; } if test ${ac_cv_lib_bsd_gethostbyname+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lbsd $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char gethostbyname (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char gethostbyname (void); int main (void) { @@ -4515,12 +4613,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_bsd_gethostbyname=yes -else $as_nop - ac_cv_lib_bsd_gethostbyname=no +else case e in #( + e) ac_cv_lib_bsd_gethostbyname=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_gethostbyname" >&5 printf "%s\n" "$ac_cv_lib_bsd_gethostbyname" >&6; } @@ -4551,16 +4651,22 @@ printf %s "checking for connect in -lsocket... " >&6; } if test ${ac_cv_lib_socket_connect+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $X_EXTRA_LIBS $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char connect (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char connect (void); int main (void) { @@ -4572,12 +4678,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_connect=yes -else $as_nop - ac_cv_lib_socket_connect=no +else case e in #( + e) ac_cv_lib_socket_connect=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5 printf "%s\n" "$ac_cv_lib_socket_connect" >&6; } @@ -4601,16 +4709,22 @@ printf %s "checking for remove in -lposix... " >&6; } if test ${ac_cv_lib_posix_remove+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lposix $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char remove (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char remove (void); int main (void) { @@ -4622,12 +4736,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_posix_remove=yes -else $as_nop - ac_cv_lib_posix_remove=no +else case e in #( + e) ac_cv_lib_posix_remove=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_posix_remove" >&5 printf "%s\n" "$ac_cv_lib_posix_remove" >&6; } @@ -4651,16 +4767,22 @@ printf %s "checking for shmat in -lipc... " >&6; } if test ${ac_cv_lib_ipc_shmat+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lipc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char shmat (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char shmat (void); int main (void) { @@ -4672,12 +4794,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ipc_shmat=yes -else $as_nop - ac_cv_lib_ipc_shmat=no +else case e in #( + e) ac_cv_lib_ipc_shmat=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ipc_shmat" >&5 printf "%s\n" "$ac_cv_lib_ipc_shmat" >&6; } @@ -4703,16 +4827,22 @@ printf %s "checking for IceConnectionNumber in -lICE... " >&6; } if test ${ac_cv_lib_ICE_IceConnectionNumber+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lICE $X_EXTRA_LIBS $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char IceConnectionNumber (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char IceConnectionNumber (void); int main (void) { @@ -4724,12 +4854,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ICE_IceConnectionNumber=yes -else $as_nop - ac_cv_lib_ICE_IceConnectionNumber=no +else case e in #( + e) ac_cv_lib_ICE_IceConnectionNumber=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ICE_IceConnectionNumber" >&5 printf "%s\n" "$ac_cv_lib_ICE_IceConnectionNumber" >&6; } @@ -4762,8 +4894,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PKG_CONFIG+y} then : printf %s "(cached) " >&6 -else $as_nop - case $PKG_CONFIG in +else case e in #( + e) case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; @@ -4788,6 +4920,7 @@ done IFS=$as_save_IFS ;; +esac ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG @@ -4810,8 +4943,8 @@ printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_ac_pt_PKG_CONFIG+y} then : printf %s "(cached) " >&6 -else $as_nop - case $ac_pt_PKG_CONFIG in +else case e in #( + e) case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; @@ -4836,6 +4969,7 @@ done IFS=$as_save_IFS ;; +esac ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG @@ -4892,8 +5026,8 @@ printf %s "checking for main in -lXext... " >&6; } if test ${ac_cv_lib_Xext_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXext $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -4910,12 +5044,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xext_main=yes -else $as_nop - ac_cv_lib_Xext_main=no +else case e in #( + e) ac_cv_lib_Xext_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xext_main" >&5 printf "%s\n" "$ac_cv_lib_Xext_main" >&6; } @@ -5000,8 +5136,8 @@ See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. @@ -5011,7 +5147,7 @@ and XEXT_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } else XEXT_CFLAGS=$pkg_cv_XEXT_CFLAGS XEXT_LIBS=$pkg_cv_XEXT_LIBS @@ -5026,8 +5162,8 @@ printf %s "checking for main in -lXt... " >&6; } if test ${ac_cv_lib_Xt_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXt $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -5044,12 +5180,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xt_main=yes -else $as_nop - ac_cv_lib_Xt_main=no +else case e in #( + e) ac_cv_lib_Xt_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xt_main" >&5 printf "%s\n" "$ac_cv_lib_Xt_main" >&6; } @@ -5134,8 +5272,8 @@ See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. @@ -5145,7 +5283,7 @@ and XT_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } else XT_CFLAGS=$pkg_cv_XT_CFLAGS XT_LIBS=$pkg_cv_XT_LIBS @@ -5160,8 +5298,8 @@ printf %s "checking for main in -lXmu... " >&6; } if test ${ac_cv_lib_Xmu_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXmu $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -5178,12 +5316,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xmu_main=yes -else $as_nop - ac_cv_lib_Xmu_main=no +else case e in #( + e) ac_cv_lib_Xmu_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xmu_main" >&5 printf "%s\n" "$ac_cv_lib_Xmu_main" >&6; } @@ -5268,8 +5408,8 @@ See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. @@ -5279,7 +5419,7 @@ and XMU_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } else XMU_CFLAGS=$pkg_cv_XMU_CFLAGS XMU_LIBS=$pkg_cv_XMU_LIBS @@ -5294,16 +5434,22 @@ printf %s "checking for XFixesSelectSelectionInput in -lXfixes... " >&6; } if test ${ac_cv_lib_Xfixes_XFixesSelectSelectionInput+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXfixes $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XFixesSelectSelectionInput (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XFixesSelectSelectionInput (void); int main (void) { @@ -5315,12 +5461,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xfixes_XFixesSelectSelectionInput=yes -else $as_nop - ac_cv_lib_Xfixes_XFixesSelectSelectionInput=no +else case e in #( + e) ac_cv_lib_Xfixes_XFixesSelectSelectionInput=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xfixes_XFixesSelectSelectionInput" >&5 printf "%s\n" "$ac_cv_lib_Xfixes_XFixesSelectSelectionInput" >&6; } @@ -5341,16 +5489,22 @@ printf %s "checking for XcursorImageCreate in -lXcursor... " >&6; } if test ${ac_cv_lib_Xcursor_XcursorImageCreate+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXcursor $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XcursorImageCreate (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XcursorImageCreate (void); int main (void) { @@ -5362,12 +5516,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xcursor_XcursorImageCreate=yes -else $as_nop - ac_cv_lib_Xcursor_XcursorImageCreate=no +else case e in #( + e) ac_cv_lib_Xcursor_XcursorImageCreate=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xcursor_XcursorImageCreate" >&5 printf "%s\n" "$ac_cv_lib_Xcursor_XcursorImageCreate" >&6; } @@ -5401,16 +5557,22 @@ printf %s "checking for XShapeCombineMask in -lXext... " >&6; } if test ${ac_cv_lib_Xext_XShapeCombineMask+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXext $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XShapeCombineMask (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XShapeCombineMask (void); int main (void) { @@ -5422,12 +5584,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xext_XShapeCombineMask=yes -else $as_nop - ac_cv_lib_Xext_XShapeCombineMask=no +else case e in #( + e) ac_cv_lib_Xext_XShapeCombineMask=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xext_XShapeCombineMask" >&5 printf "%s\n" "$ac_cv_lib_Xext_XShapeCombineMask" >&6; } @@ -5471,16 +5635,22 @@ printf %s "checking for XRRUpdateConfiguration in -lXrandr... " >&6; } if test ${ac_cv_lib_Xrandr_XRRUpdateConfiguration+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXrandr $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XRRUpdateConfiguration (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XRRUpdateConfiguration (void); int main (void) { @@ -5492,12 +5662,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xrandr_XRRUpdateConfiguration=yes -else $as_nop - ac_cv_lib_Xrandr_XRRUpdateConfiguration=no +else case e in #( + e) ac_cv_lib_Xrandr_XRRUpdateConfiguration=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xrandr_XRRUpdateConfiguration" >&5 printf "%s\n" "$ac_cv_lib_Xrandr_XRRUpdateConfiguration" >&6; } @@ -5521,14 +5693,14 @@ fi # Old X11 support { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for X11 function prototypes" >&5 printf %s "checking for X11 function prototypes... " >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 -printf %s "checking for grep that handles long lines and -e... " >&6; } -if test ${ac_cv_path_GREP+y} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5 +printf %s "checking for egrep -e... " >&6; } +if test ${ac_cv_path_EGREP_TRADITIONAL+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -z "$GREP"; then - ac_path_GREP_found=false +else case e in #( + e) if test -z "$EGREP_TRADITIONAL"; then + ac_path_EGREP_TRADITIONAL_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin @@ -5542,13 +5714,14 @@ do for ac_prog in grep ggrep do for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_GREP="$as_dir$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_GREP" || continue -# Check for GNU ac_path_GREP and select it if it is found. - # Check for GNU $ac_path_GREP -case `"$ac_path_GREP" --version 2>&1` in + ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue +# Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. + # Check for GNU $ac_path_EGREP_TRADITIONAL +case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( *GNU*) - ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; +#( *) ac_count=0 printf %s 0123456789 >"conftest.in" @@ -5557,14 +5730,14 @@ case `"$ac_path_GREP" --version 2>&1` in cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" - printf "%s\n" 'GREP' >> "conftest.nl" - "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl" + "$ac_path_EGREP_TRADITIONAL" -E 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_GREP_max-0}; then + if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then # Best one so far, save it but keep looking for a better one - ac_cv_path_GREP="$ac_path_GREP" - ac_path_GREP_max=$ac_count + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" + ac_path_EGREP_TRADITIONAL_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break @@ -5572,35 +5745,24 @@ case `"$ac_path_GREP" --version 2>&1` in rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac - $ac_path_GREP_found && break 3 + $ac_path_EGREP_TRADITIONAL_found && break 3 done done done IFS=$as_save_IFS - if test -z "$ac_cv_path_GREP"; then - as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then + : fi else - ac_cv_path_GREP=$GREP -fi - + ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 -printf "%s\n" "$ac_cv_path_GREP" >&6; } - GREP="$ac_cv_path_GREP" - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 -printf %s "checking for egrep... " >&6; } -if test ${ac_cv_path_EGREP+y} + if test "$ac_cv_path_EGREP_TRADITIONAL" then : - printf %s "(cached) " >&6 -else $as_nop - if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 - then ac_cv_path_EGREP="$GREP -E" - else - if test -z "$EGREP"; then - ac_path_EGREP_found=false + ac_cv_path_EGREP_TRADITIONAL="$ac_cv_path_EGREP_TRADITIONAL -E" +else case e in #( + e) if test -z "$EGREP_TRADITIONAL"; then + ac_path_EGREP_TRADITIONAL_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin @@ -5614,13 +5776,14 @@ do for ac_prog in egrep do for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_EGREP" || continue -# Check for GNU ac_path_EGREP and select it if it is found. - # Check for GNU $ac_path_EGREP -case `"$ac_path_EGREP" --version 2>&1` in + ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue +# Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. + # Check for GNU $ac_path_EGREP_TRADITIONAL +case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( *GNU*) - ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; +#( *) ac_count=0 printf %s 0123456789 >"conftest.in" @@ -5629,14 +5792,14 @@ case `"$ac_path_EGREP" --version 2>&1` in cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" - printf "%s\n" 'EGREP' >> "conftest.nl" - "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl" + "$ac_path_EGREP_TRADITIONAL" 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_EGREP_max-0}; then + if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then # Best one so far, save it but keep looking for a better one - ac_cv_path_EGREP="$ac_path_EGREP" - ac_path_EGREP_max=$ac_count + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" + ac_path_EGREP_TRADITIONAL_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break @@ -5644,24 +5807,25 @@ case `"$ac_path_EGREP" --version 2>&1` in rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac - $ac_path_EGREP_found && break 3 + $ac_path_EGREP_TRADITIONAL_found && break 3 done done done IFS=$as_save_IFS - if test -z "$ac_cv_path_EGREP"; then + if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else - ac_cv_path_EGREP=$EGREP + ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL fi - - fi + ;; +esac +fi ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 -printf "%s\n" "$ac_cv_path_EGREP" >&6; } - EGREP="$ac_cv_path_EGREP" - +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5 +printf "%s\n" "$ac_cv_path_EGREP_TRADITIONAL" >&6; } + EGREP_TRADITIONAL=$ac_cv_path_EGREP_TRADITIONAL cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -5672,11 +5836,12 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 + $EGREP_TRADITIONAL "yes" >/dev/null 2>&1 then : have_funcproto=yes -else $as_nop - have_funcproto=no +else case e in #( + e) have_funcproto=no ;; +esac fi rm -rf conftest* @@ -5722,8 +5887,9 @@ fi if test ${with_freetype+y} then : withval=$with_freetype; -else $as_nop - with_freetype=yes +else case e in #( + e) with_freetype=yes ;; +esac fi @@ -5801,8 +5967,8 @@ See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. @@ -5812,7 +5978,7 @@ and FREETYPE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } else FREETYPE_CFLAGS=$pkg_cv_FREETYPE_CFLAGS FREETYPE_LIBS=$pkg_cv_FREETYPE_LIBS @@ -5828,8 +5994,9 @@ fi if test "x$ac_cv_header_ft2build_h" = xyes then : have_freetype=yes -else $as_nop - have_freetype=no +else case e in #( + e) have_freetype=no ;; +esac fi CPPFLAGS="${save_CPPFLAGS}" @@ -5924,16 +6091,22 @@ printf %s "checking for XftFontOpen in -lXft... " >&6; } if test ${ac_cv_lib_Xft_XftFontOpen+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXft $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XftFontOpen (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XftFontOpen (void); int main (void) { @@ -5945,12 +6118,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xft_XftFontOpen=yes -else $as_nop - ac_cv_lib_Xft_XftFontOpen=no +else case e in #( + e) ac_cv_lib_Xft_XftFontOpen=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xft_XftFontOpen" >&5 printf "%s\n" "$ac_cv_lib_Xft_XftFontOpen" >&6; } @@ -5993,16 +6168,22 @@ printf %s "checking for XftDrawStringUtf8 in -lXft... " >&6; } if test ${ac_cv_lib_Xft_XftDrawStringUtf8+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXft $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XftDrawStringUtf8 (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XftDrawStringUtf8 (void); int main (void) { @@ -6014,20 +6195,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xft_XftDrawStringUtf8=yes -else $as_nop - ac_cv_lib_Xft_XftDrawStringUtf8=no +else case e in #( + e) ac_cv_lib_Xft_XftDrawStringUtf8=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xft_XftDrawStringUtf8" >&5 printf "%s\n" "$ac_cv_lib_Xft_XftDrawStringUtf8" >&6; } if test "x$ac_cv_lib_Xft_XftDrawStringUtf8" = xyes then : have_utf8=yes -else $as_nop - have_utf8=no +else case e in #( + e) have_utf8=no ;; +esac fi if test "$have_utf8" = yes; then @@ -6040,16 +6224,22 @@ printf %s "checking for XftPatternGetString in -lXft... " >&6; } if test ${ac_cv_lib_Xft_XftPatternGetString+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXft $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XftPatternGetString (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XftPatternGetString (void); int main (void) { @@ -6061,20 +6251,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xft_XftPatternGetString=yes -else $as_nop - ac_cv_lib_Xft_XftPatternGetString=no +else case e in #( + e) ac_cv_lib_Xft_XftPatternGetString=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xft_XftPatternGetString" >&5 printf "%s\n" "$ac_cv_lib_Xft_XftPatternGetString" >&6; } if test "x$ac_cv_lib_Xft_XftPatternGetString" = xyes then : have_xftpgs=yes -else $as_nop - have_xftpgs=no +else case e in #( + e) have_xftpgs=no ;; +esac fi ac_fn_c_check_func "$LINENO" "Xutf8LookupString" "ac_cv_func_Xutf8LookupString" @@ -6089,16 +6282,22 @@ printf %s "checking for FcPatternCreate in -lfontconfig... " >&6; } if test ${ac_cv_lib_fontconfig_FcPatternCreate+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lfontconfig $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char FcPatternCreate (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char FcPatternCreate (void); int main (void) { @@ -6110,20 +6309,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_fontconfig_FcPatternCreate=yes -else $as_nop - ac_cv_lib_fontconfig_FcPatternCreate=no +else case e in #( + e) ac_cv_lib_fontconfig_FcPatternCreate=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fontconfig_FcPatternCreate" >&5 printf "%s\n" "$ac_cv_lib_fontconfig_FcPatternCreate" >&6; } if test "x$ac_cv_lib_fontconfig_FcPatternCreate" = xyes then : have_fc=yes -else $as_nop - have_fc=no +else case e in #( + e) have_fc=no ;; +esac fi ac_fn_c_check_header_compile "$LINENO" "fontconfig/fontconfig.h" "ac_cv_header_fontconfig_fontconfig_h" "$ac_includes_default" @@ -6151,8 +6353,9 @@ WITH_GLX=no if test ${enable_glx+y} then : enableval=$enable_glx; -else $as_nop - enable_glx=yes +else case e in #( + e) enable_glx=yes ;; +esac fi if test "x$enable_glx" = "xyes"; then @@ -6161,16 +6364,22 @@ printf %s "checking for glXMakeContextCurrent in -lGL... " >&6; } if test ${ac_cv_lib_GL_glXMakeContextCurrent+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lGL $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char glXMakeContextCurrent (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char glXMakeContextCurrent (void); int main (void) { @@ -6182,20 +6391,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_GL_glXMakeContextCurrent=yes -else $as_nop - ac_cv_lib_GL_glXMakeContextCurrent=no +else case e in #( + e) ac_cv_lib_GL_glXMakeContextCurrent=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_GL_glXMakeContextCurrent" >&5 printf "%s\n" "$ac_cv_lib_GL_glXMakeContextCurrent" >&6; } if test "x$ac_cv_lib_GL_glXMakeContextCurrent" = xyes then : have_glx=yes -else $as_nop - have_glx=no +else case e in #( + e) have_glx=no ;; +esac fi ac_fn_c_check_header_compile "$LINENO" "GL/glx.h" "ac_cv_header_GL_glx_h" "$ac_includes_default" @@ -6215,11 +6427,12 @@ printf %s "checking for GLX_RGBA_TYPE... " >&6; } _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 + $EGREP_TRADITIONAL "yes" >/dev/null 2>&1 then : have_glx_rgba=yes -else $as_nop - have_glx_rgba=no +else case e in #( + e) have_glx_rgba=no ;; +esac fi rm -rf conftest* @@ -6243,8 +6456,9 @@ fi if test ${enable_xim+y} then : enableval=$enable_xim; -else $as_nop - enable_xim=yes +else case e in #( + e) enable_xim=yes ;; +esac fi if test "x$enable_xim" = "xyes"; then @@ -6294,16 +6508,22 @@ printf %s "checking for XInternAtoms in -lX11... " >&6; } if test ${ac_cv_lib_X11_XInternAtoms+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lX11 $X_LIBS $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XInternAtoms (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XInternAtoms (void); int main (void) { @@ -6315,12 +6535,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_X11_XInternAtoms=yes -else $as_nop - ac_cv_lib_X11_XInternAtoms=no +else case e in #( + e) ac_cv_lib_X11_XInternAtoms=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_X11_XInternAtoms" >&5 printf "%s\n" "$ac_cv_lib_X11_XInternAtoms" >&6; } @@ -6340,8 +6562,8 @@ printf %s "checking for main in -lgdi32... " >&6; } if test ${ac_cv_lib_gdi32_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lgdi32 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -6358,20 +6580,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_gdi32_main=yes -else $as_nop - ac_cv_lib_gdi32_main=no +else case e in #( + e) ac_cv_lib_gdi32_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gdi32_main" >&5 printf "%s\n" "$ac_cv_lib_gdi32_main" >&6; } if test "x$ac_cv_lib_gdi32_main" = xyes then : have_gdi32=yes -else $as_nop - have_gdi32=no +else case e in #( + e) have_gdi32=no ;; +esac fi if test "$have_gdi32" = yes; then @@ -6382,8 +6607,8 @@ printf %s "checking for main in -lmsimg32... " >&6; } if test ${ac_cv_lib_msimg32_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lmsimg32 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -6400,20 +6625,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_msimg32_main=yes -else $as_nop - ac_cv_lib_msimg32_main=no +else case e in #( + e) ac_cv_lib_msimg32_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_msimg32_main" >&5 printf "%s\n" "$ac_cv_lib_msimg32_main" >&6; } if test "x$ac_cv_lib_msimg32_main" = xyes then : have_msimg32=yes -else $as_nop - have_msimg32=no +else case e in #( + e) have_msimg32=no ;; +esac fi if test "$have_msimg32" = yes; then @@ -6424,8 +6652,8 @@ printf %s "checking for main in -limm32... " >&6; } if test ${ac_cv_lib_imm32_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-limm32 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -6442,20 +6670,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_imm32_main=yes -else $as_nop - ac_cv_lib_imm32_main=no +else case e in #( + e) ac_cv_lib_imm32_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_imm32_main" >&5 printf "%s\n" "$ac_cv_lib_imm32_main" >&6; } if test "x$ac_cv_lib_imm32_main" = xyes then : have_imm32=yes -else $as_nop - have_imm32=no +else case e in #( + e) have_imm32=no ;; +esac fi if test "$have_imm32" = yes; then @@ -6470,8 +6701,9 @@ WITH_WGL=no if test ${enable_wgl+y} then : enableval=$enable_wgl; -else $as_nop - enable_wgl=yes +else case e in #( + e) enable_wgl=yes ;; +esac fi if test "x$enable_wgl" = "xyes"; then @@ -6480,8 +6712,8 @@ printf %s "checking for main in -lopengl32... " >&6; } if test ${ac_cv_lib_opengl32_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lopengl32 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -6498,20 +6730,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_opengl32_main=yes -else $as_nop - ac_cv_lib_opengl32_main=no +else case e in #( + e) ac_cv_lib_opengl32_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_opengl32_main" >&5 printf "%s\n" "$ac_cv_lib_opengl32_main" >&6; } if test "x$ac_cv_lib_opengl32_main" = xyes then : have_wgl=yes -else $as_nop - have_wgl=no +else case e in #( + e) have_wgl=no ;; +esac fi save_CPPFLAGS="$CPPFLAGS" @@ -6545,8 +6780,9 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : use_wgl=yes -else $as_nop - use_wgl=no +else case e in #( + e) use_wgl=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext @@ -6582,8 +6818,8 @@ printf %s "checking for main in -lart_lgpl_2... " >&6; } if test ${ac_cv_lib_art_lgpl_2_main+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lart_lgpl_2 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -6600,20 +6836,23 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_art_lgpl_2_main=yes -else $as_nop - ac_cv_lib_art_lgpl_2_main=no +else case e in #( + e) ac_cv_lib_art_lgpl_2_main=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_art_lgpl_2_main" >&5 printf "%s\n" "$ac_cv_lib_art_lgpl_2_main" >&6; } if test "x$ac_cv_lib_art_lgpl_2_main" = xyes then : have_libart=yes -else $as_nop - have_libart=no +else case e in #( + e) have_libart=no ;; +esac fi if test "$have_libart" = yes; then @@ -6621,8 +6860,9 @@ fi if test "x$ac_cv_header_libart_lgpl_libart_h" = xyes then : have_libart=yes -else $as_nop - have_libart=no +else case e in #( + e) have_libart=no ;; +esac fi fi @@ -7069,16 +7309,22 @@ printf %s "checking for cairo_create in -lcairo... " >&6; } if test ${ac_cv_lib_cairo_cairo_create+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lcairo $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char cairo_create (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char cairo_create (void); int main (void) { @@ -7090,12 +7336,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_cairo_cairo_create=yes -else $as_nop - ac_cv_lib_cairo_cairo_create=no +else case e in #( + e) ac_cv_lib_cairo_cairo_create=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cairo_cairo_create" >&5 printf "%s\n" "$ac_cv_lib_cairo_cairo_create" >&6; } @@ -7112,16 +7360,22 @@ printf %s "checking for cairo_ft_font_face_create_for_ft_face in -lcairo... " >& if test ${ac_cv_lib_cairo_cairo_ft_font_face_create_for_ft_face+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lcairo $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char cairo_ft_font_face_create_for_ft_face (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char cairo_ft_font_face_create_for_ft_face (void); int main (void) { @@ -7133,12 +7387,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_cairo_cairo_ft_font_face_create_for_ft_face=yes -else $as_nop - ac_cv_lib_cairo_cairo_ft_font_face_create_for_ft_face=no +else case e in #( + e) ac_cv_lib_cairo_cairo_ft_font_face_create_for_ft_face=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cairo_cairo_ft_font_face_create_for_ft_face" >&5 printf "%s\n" "$ac_cv_lib_cairo_cairo_ft_font_face_create_for_ft_face" >&6; } @@ -7155,16 +7411,22 @@ printf %s "checking for cairo_xlib_surface_create in -lcairo... " >&6; } if test ${ac_cv_lib_cairo_cairo_xlib_surface_create+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lcairo $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char cairo_xlib_surface_create (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char cairo_xlib_surface_create (void); int main (void) { @@ -7176,12 +7438,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_cairo_cairo_xlib_surface_create=yes -else $as_nop - ac_cv_lib_cairo_cairo_xlib_surface_create=no +else case e in #( + e) ac_cv_lib_cairo_cairo_xlib_surface_create=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cairo_cairo_xlib_surface_create" >&5 printf "%s\n" "$ac_cv_lib_cairo_cairo_xlib_surface_create" >&6; } @@ -7198,16 +7462,22 @@ printf %s "checking for cairo_win32_surface_create in -lcairo... " >&6; } if test ${ac_cv_lib_cairo_cairo_win32_surface_create+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lcairo $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char cairo_win32_surface_create (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char cairo_win32_surface_create (void); int main (void) { @@ -7219,12 +7489,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_cairo_cairo_win32_surface_create=yes -else $as_nop - ac_cv_lib_cairo_cairo_win32_surface_create=no +else case e in #( + e) ac_cv_lib_cairo_cairo_win32_surface_create=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cairo_cairo_win32_surface_create" >&5 printf "%s\n" "$ac_cv_lib_cairo_cairo_win32_surface_create" >&6; } @@ -7245,16 +7517,22 @@ printf %s "checking for XRenderFindVisualFormat in -lXrender... " >&6; } if test ${ac_cv_lib_Xrender_XRenderFindVisualFormat+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lXrender $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char XRenderFindVisualFormat (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char XRenderFindVisualFormat (void); int main (void) { @@ -7266,12 +7544,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_Xrender_XRenderFindVisualFormat=yes -else $as_nop - ac_cv_lib_Xrender_XRenderFindVisualFormat=no +else case e in #( + e) ac_cv_lib_Xrender_XRenderFindVisualFormat=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_Xrender_XRenderFindVisualFormat" >&5 printf "%s\n" "$ac_cv_lib_Xrender_XRenderFindVisualFormat" >&6; } @@ -7289,8 +7569,9 @@ WITH_GLITZ=no if test ${enable_glitz+y} then : enableval=$enable_glitz; -else $as_nop - enable_glitz=no +else case e in #( + e) enable_glitz=no ;; +esac fi @@ -7372,8 +7653,9 @@ fi if test "x$ac_cv_header_glitz_h" = xyes then : have_glitz_h=yes -else $as_nop - have_glitz_h=no +else case e in #( + e) have_glitz_h=no ;; +esac fi CPPFLAGS=$save_CPPFLAGS @@ -7457,8 +7739,9 @@ fi if test "x$ac_cv_header_glitz_glx_h" = xyes then : have_glitz_glx_h=yes -else $as_nop - have_glitz_glx_h=no +else case e in #( + e) have_glitz_glx_h=no ;; +esac fi CPPFLAGS=$save_CPPFLAGS @@ -7550,8 +7833,9 @@ fi if test "x$ac_cv_header_glitz_wgl_h" = xyes then : have_glitz_wgl_h=yes -else $as_nop - have_glitz_wgl_h=no +else case e in #( + e) have_glitz_wgl_h=no ;; +esac fi CPPFLAGS=$save_CPPFLAGS @@ -7600,16 +7884,18 @@ esac if test ${enable_server+y} then : enableval=$enable_server; -else $as_nop - enable_server=$BUILD_SERVER +else case e in #( + e) enable_server=$BUILD_SERVER ;; +esac fi # Check whether --enable-graphics was given. if test ${enable_graphics+y} then : enableval=$enable_graphics; -else $as_nop - enable_graphics="$BUILD_GRAPHICS" +else case e in #( + e) enable_graphics="$BUILD_GRAPHICS" ;; +esac fi @@ -7632,7 +7918,280 @@ printf "%s\n" "$as_me: WARNING: can't find freetype, required for graphics=cairo if test $BUILD_SERVER = win32; then BUILD_GRAPHICS=winlib elif test $BUILD_SERVER = wayland; then - as_fn_error $? "wayland backend requires cairo" "$LINENO" 5 + for ac_header in wayland-util.h +do : + ac_fn_c_check_header_compile "$LINENO" "wayland-util.h" "ac_cv_header_wayland_util_h" "$ac_includes_default" +if test "x$ac_cv_header_wayland_util_h" = xyes +then : + printf "%s\n" "#define HAVE_WAYLAND_UTIL_H 1" >>confdefs.h + +else case e in #( + e) as_fn_error $? "**** No wayland-util.h. Install libwayland-dev or equivalent." "$LINENO" 5 ;; +esac +fi + +done + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wl_display_flush in -lwayland-client" >&5 +printf %s "checking for wl_display_flush in -lwayland-client... " >&6; } +if test ${ac_cv_lib_wayland_client_wl_display_flush+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lwayland-client $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char wl_display_flush (void); +int +main (void) +{ +return wl_display_flush (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_wayland_client_wl_display_flush=yes +else case e in #( + e) ac_cv_lib_wayland_client_wl_display_flush=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wayland_client_wl_display_flush" >&5 +printf "%s\n" "$ac_cv_lib_wayland_client_wl_display_flush" >&6; } +if test "x$ac_cv_lib_wayland_client_wl_display_flush" = xyes +then : + printf "%s\n" "#define HAVE_LIBWAYLAND_CLIENT 1" >>confdefs.h + + LIBS="-lwayland-client $LIBS" + +else case e in #( + e) as_fn_error $? "**** No wl_display_flush in libwayland-client. Install correct version of libwayland-dev or equivalent." "$LINENO" 5 ;; +esac +fi + + for ac_header in xkbcommon/xkbcommon.h +do : + ac_fn_c_check_header_compile "$LINENO" "xkbcommon/xkbcommon.h" "ac_cv_header_xkbcommon_xkbcommon_h" "$ac_includes_default" +if test "x$ac_cv_header_xkbcommon_xkbcommon_h" = xyes +then : + printf "%s\n" "#define HAVE_XKBCOMMON_XKBCOMMON_H 1" >>confdefs.h + +else case e in #( + e) as_fn_error $? "**** No xkbcommon/xkbcommon.h. Required for wayland. Install libxkbcommon-dev or equivalent." "$LINENO" 5 ;; +esac +fi + +done + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for xkb_context_new in -lxkbcommon" >&5 +printf %s "checking for xkb_context_new in -lxkbcommon... " >&6; } +if test ${ac_cv_lib_xkbcommon_xkb_context_new+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lxkbcommon $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char xkb_context_new (void); +int +main (void) +{ +return xkb_context_new (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_xkbcommon_xkb_context_new=yes +else case e in #( + e) ac_cv_lib_xkbcommon_xkb_context_new=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xkbcommon_xkb_context_new" >&5 +printf "%s\n" "$ac_cv_lib_xkbcommon_xkb_context_new" >&6; } +if test "x$ac_cv_lib_xkbcommon_xkb_context_new" = xyes +then : + printf "%s\n" "#define HAVE_LIBXKBCOMMON 1" >>confdefs.h + + LIBS="-lxkbcommon $LIBS" + +else case e in #( + e) as_fn_error $? "**** No xkb_context_new in libxkbcommon. Install correct version of libxkbcommon-dev or equivalent." "$LINENO" 5 ;; +esac +fi + + + for ac_header in EGL/egl.h +do : + ac_fn_c_check_header_compile "$LINENO" "EGL/egl.h" "ac_cv_header_EGL_egl_h" "$ac_includes_default" +if test "x$ac_cv_header_EGL_egl_h" = xyes +then : + printf "%s\n" "#define HAVE_EGL_EGL_H 1" >>confdefs.h + +else case e in #( + e) as_fn_error $? "**** No EGL/egl.h. Required for wayland. Install libegl-dev or equivalent." "$LINENO" 5 ;; +esac +fi + +done + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for eglGetDisplay in -lEGL" >&5 +printf %s "checking for eglGetDisplay in -lEGL... " >&6; } +if test ${ac_cv_lib_EGL_eglGetDisplay+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lEGL $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char eglGetDisplay (void); +int +main (void) +{ +return eglGetDisplay (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_EGL_eglGetDisplay=yes +else case e in #( + e) ac_cv_lib_EGL_eglGetDisplay=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_EGL_eglGetDisplay" >&5 +printf "%s\n" "$ac_cv_lib_EGL_eglGetDisplay" >&6; } +if test "x$ac_cv_lib_EGL_eglGetDisplay" = xyes +then : + printf "%s\n" "#define HAVE_LIBEGL 1" >>confdefs.h + + LIBS="-lEGL $LIBS" + +else case e in #( + e) as_fn_error $? "**** No eglGetDisplay in libEGL. Install correct version of libegl-dev or equivalent." "$LINENO" 5 ;; +esac +fi + + + for ac_header in GLES2/gl2.h +do : + ac_fn_c_check_header_compile "$LINENO" "GLES2/gl2.h" "ac_cv_header_GLES2_gl2_h" "$ac_includes_default" +if test "x$ac_cv_header_GLES2_gl2_h" = xyes +then : + printf "%s\n" "#define HAVE_GLES2_GL2_H 1" >>confdefs.h + +else case e in #( + e) as_fn_error $? "**** No GLES2/gl2.h. Required for wayland. Install libgles2-mesa-dev or equivalent." "$LINENO" 5 ;; +esac +fi + +done + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for glClear in -lGLESv2" >&5 +printf %s "checking for glClear in -lGLESv2... " >&6; } +if test ${ac_cv_lib_GLESv2_glClear+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lGLESv2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char glClear (void); +int +main (void) +{ +return glClear (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_GLESv2_glClear=yes +else case e in #( + e) ac_cv_lib_GLESv2_glClear=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_GLESv2_glClear" >&5 +printf "%s\n" "$ac_cv_lib_GLESv2_glClear" >&6; } +if test "x$ac_cv_lib_GLESv2_glClear" = xyes +then : + printf "%s\n" "#define HAVE_LIBGLESV2 1" >>confdefs.h + + LIBS="-lGLESv2 $LIBS" + +else case e in #( + e) as_fn_error $? "**** No glClear in libGLESv2. Install correct version of libgles2-mesa-dev or equivalent." "$LINENO" 5 ;; +esac +fi + + + CAIRO_LIBS="$CAIRO_LIBS $XFT_LIBS -lEGL -lGLESv2" + CAIRO_CFLAGS="$CAIRO_CFLAGS" + LIBS="-lwayland-client -lwayland-cursor -lwayland-egl -lxkbcommon $LIBS" else BUILD_GRAPHICS=xlib fi @@ -7716,8 +8275,9 @@ if test "x$ac_cv_header_wayland_util_h" = xyes then : printf "%s\n" "#define HAVE_WAYLAND_UTIL_H 1" >>confdefs.h -else $as_nop - as_fn_error $? "**** No wayland-util.h. Install libwayland-dev or equivalent." "$LINENO" 5 +else case e in #( + e) as_fn_error $? "**** No wayland-util.h. Install libwayland-dev or equivalent." "$LINENO" 5 ;; +esac fi done @@ -7726,16 +8286,22 @@ printf %s "checking for wl_display_flush in -lwayland-client... " >&6; } if test ${ac_cv_lib_wayland_client_wl_display_flush+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lwayland-client $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char wl_display_flush (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char wl_display_flush (void); int main (void) { @@ -7747,12 +8313,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_wayland_client_wl_display_flush=yes -else $as_nop - ac_cv_lib_wayland_client_wl_display_flush=no +else case e in #( + e) ac_cv_lib_wayland_client_wl_display_flush=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wayland_client_wl_display_flush" >&5 printf "%s\n" "$ac_cv_lib_wayland_client_wl_display_flush" >&6; } @@ -7762,8 +8330,9 @@ then : LIBS="-lwayland-client $LIBS" -else $as_nop - as_fn_error $? "**** No wl_display_flush in libwayland-client. Install correct version of libwayland-dev or equivalent." "$LINENO" 5 +else case e in #( + e) as_fn_error $? "**** No wl_display_flush in libwayland-client. Install correct version of libwayland-dev or equivalent." "$LINENO" 5 ;; +esac fi for ac_header in xkbcommon/xkbcommon.h @@ -7773,8 +8342,9 @@ if test "x$ac_cv_header_xkbcommon_xkbcommon_h" = xyes then : printf "%s\n" "#define HAVE_XKBCOMMON_XKBCOMMON_H 1" >>confdefs.h -else $as_nop - as_fn_error $? "**** No xkbcommon/xkbcommon.h. Required for wayland. Install libxkbcommon-dev or equivalent." "$LINENO" 5 +else case e in #( + e) as_fn_error $? "**** No xkbcommon/xkbcommon.h. Required for wayland. Install libxkbcommon-dev or equivalent." "$LINENO" 5 ;; +esac fi done @@ -7783,16 +8353,22 @@ printf %s "checking for xkb_context_new in -lxkbcommon... " >&6; } if test ${ac_cv_lib_xkbcommon_xkb_context_new+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS LIBS="-lxkbcommon $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -char xkb_context_new (); + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char xkb_context_new (void); int main (void) { @@ -7804,12 +8380,14 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_xkbcommon_xkb_context_new=yes -else $as_nop - ac_cv_lib_xkbcommon_xkb_context_new=no +else case e in #( + e) ac_cv_lib_xkbcommon_xkb_context_new=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS +LIBS=$ac_check_lib_save_LIBS ;; +esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xkbcommon_xkb_context_new" >&5 printf "%s\n" "$ac_cv_lib_xkbcommon_xkb_context_new" >&6; } @@ -7819,8 +8397,9 @@ then : LIBS="-lxkbcommon $LIBS" -else $as_nop - as_fn_error $? "**** No xkb_context_new in libxkbcommon. Install correct version of libxkbcommon-dev or equivalent." "$LINENO" 5 +else case e in #( + e) as_fn_error $? "**** No xkb_context_new in libxkbcommon. Install correct version of libxkbcommon-dev or equivalent." "$LINENO" 5 ;; +esac fi CAIRO_LIBS="$CAIRO_LIBS $XFT_LIBS" @@ -7966,8 +8545,9 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : HAS_W_DECL_AFTER_STATEMENT=yes -else $as_nop - HAS_W_DECL_AFTER_STATEMENT=no +else case e in #( + e) HAS_W_DECL_AFTER_STATEMENT=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS="$saved_CFLAGS" @@ -8016,8 +8596,8 @@ cat >confcache <<\_ACEOF # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # -# `ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* `ac_cv_foo' will be assigned the +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF @@ -8047,14 +8627,14 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) - # `set' does not quote correctly, so add quotes: double-quote + # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) - # `set' quotes correctly as required by POSIX, so do not add quotes. + # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | @@ -8144,7 +8724,6 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh @@ -8153,12 +8732,13 @@ then : # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else $as_nop - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi @@ -8230,7 +8810,7 @@ IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 @@ -8259,7 +8839,6 @@ as_fn_error () } # as_fn_error - # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -8299,11 +8878,12 @@ then : { eval $1+=\$2 }' -else $as_nop - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -8317,11 +8897,12 @@ then : { as_val=$(( $* )) }' -else $as_nop - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith @@ -8404,9 +8985,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -8487,10 +9068,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 @@ -8506,7 +9089,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by $as_me, which was -generated by GNU Autoconf 2.71. Invocation command line was +generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -8537,7 +9120,7 @@ _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -\`$as_me' instantiates files and other configuration actions +'$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -8570,10 +9153,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ config.status -configured by $0, generated by GNU Autoconf 2.71, +configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" -Copyright (C) 2021 Free Software Foundation, Inc. +Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -8632,8 +9215,8 @@ do ac_need_defaults=false;; --he | --h) # Conflict between --help and --header - as_fn_error $? "ambiguous option: \`$1' -Try \`$0 --help' for more information.";; + as_fn_error $? "ambiguous option: '$1' +Try '$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ @@ -8641,8 +9224,8 @@ Try \`$0 --help' for more information.";; ac_cs_silent=: ;; # This is an error. - -*) as_fn_error $? "unrecognized option: \`$1' -Try \`$0 --help' for more information." ;; + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; @@ -8694,7 +9277,7 @@ do "back.make") CONFIG_FILES="$CONFIG_FILES back.make" ;; "config.make") CONFIG_FILES="$CONFIG_FILES config.make" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done @@ -8713,7 +9296,7 @@ fi # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to `$tmp'. +# after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= @@ -8737,7 +9320,7 @@ ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. -# This happens for instance with `./config.status config.h'. +# This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then @@ -8895,13 +9478,13 @@ fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. -# This happens for instance with `./config.status Makefile'. +# This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF -# Transform confdefs.h into an awk script `defines.awk', embedded as +# Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. @@ -9011,7 +9594,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -9033,19 +9616,19 @@ do -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain `:'. + # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is `configure' which instantiates (i.e., don't + # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` @@ -9169,7 +9752,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 esac _ACEOF -# Neutralize VPATH when `$srcdir' = `.'. +# Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 @@ -9198,9 +9781,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" diff --git a/configure.ac b/configure.ac index 93b8ba18..2e0145ad 100644 --- a/configure.ac +++ b/configure.ac @@ -26,9 +26,9 @@ builtin(include, pkg.m4)dnl AC_INIT -AC_PREREQ(2.57) +AC_PREREQ([2.72]) AC_CONFIG_SRCDIR([back.make.in]) -AC_CONFIG_HEADER(config.h) +AC_CONFIG_HEADERS([config.h]) # If GNUSTEP_MAKEFILES is undefined, try to use gnustep-config to determine it. if test -z "$GNUSTEP_MAKEFILES"; then @@ -319,7 +319,7 @@ if test $WITH_XFT = yes; then AC_DEFINE(HAVE_UTF8,1,[Define if you have XftDrawStringUtf8]) fi AC_CHECK_LIB(Xft, XftPatternGetString, have_xftpgs=yes, have_xftpgs=no) - AC_HAVE_FUNCS(Xutf8LookupString) + AC_CHECK_FUNCS([Xutf8LookupString]) AC_CHECK_LIB(fontconfig, FcPatternCreate, have_fc=yes, have_fc=no) AC_CHECK_HEADER(fontconfig/fontconfig.h) if test "$have_fc" = yes -a "$ac_cv_header_fontconfig_fontconfig_h" = yes; then @@ -371,7 +371,7 @@ fi #-------------------------------------------------------------------- # Functions #-------------------------------------------------------------------- -AC_HAVE_FUNCS(usleep) +AC_CHECK_FUNCS([usleep]) have_xshm=no AC_CHECK_HEADERS(X11/extensions/XShm.h, @@ -424,8 +424,7 @@ if test "x$enable_wgl" = "xyes"; then save_libs="$LIBS" LIBS="$WGL_LIBS" - AC_TRY_LINK([#include ], [ wglCreateContext(0); ], - [use_wgl=yes], [use_wgl=no]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ wglCreateContext(0); ]])],[use_wgl=yes],[use_wgl=no]) LIBS="$save_libs" AC_MSG_RESULT([$use_wgl]) WITH_WGL=$use_wgl From cf577bfae58f6635b2b5f8a4627f50cccb474f04 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 18 Apr 2026 14:14:37 +1000 Subject: [PATCH 02/24] wayland: rebind OpenGL context when view gets a window --- Source/wayland/WaylandGLContext.m | 67 ++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m index 07a67899..768eb533 100644 --- a/Source/wayland/WaylandGLContext.m +++ b/Source/wayland/WaylandGLContext.m @@ -91,6 +91,11 @@ - (BOOL)_ensureDisplayAndContextWithShare:(NSOpenGLContext *)share return YES; } + if (_window == NULL) + { + [self _attachToWindowIfNeeded]; + } + wlDisplay = NULL; if (_window != NULL && _window->wlconfig != NULL) { @@ -148,6 +153,45 @@ - (BOOL)_ensureDisplayAndContextWithShare:(NSOpenGLContext *)share return YES; } +- (BOOL)_attachToWindowIfNeeded +{ + GSDisplayServer *server; + NSWindow *window; + struct window *newWindow; + + if (_view == nil) + { + return NO; + } + + window = [_view window]; + if (window == nil) + { + return NO; + } + + server = GSCurrentServer(); + newWindow = (struct window *)[server windowDevice:[window windowNumber]]; + if (newWindow == NULL) + { + NSDebugMLLog(@"OpenGL", @"No wayland window device found for view %@", _view); + return NO; + } + + if (_window != newWindow) + { + if (_window != NULL) + { + _window->usesOpenGL = NO; + } + _window = newWindow; + [self _destroySurface]; + } + + _window->usesOpenGL = YES; + return YES; +} + - (BOOL)_ensureSurface { EGLConfig eglConfig; @@ -253,9 +297,6 @@ - (NSOpenGLPixelFormat *)pixelFormat - (void)setView:(NSView *)view { - GSDisplayServer *server; - NSWindow *window; - if (view == nil) { [NSException raise:NSInvalidArgumentException @@ -264,25 +305,11 @@ - (void)setView:(NSView *)view ASSIGN(_view, view); - window = [view window]; - if (window == nil) - { - return; - } - - server = GSCurrentServer(); - _window = (struct window *)[server windowDevice:[window windowNumber]]; - - if (_window == NULL) + if ([self _attachToWindowIfNeeded] == NO) { - NSDebugMLLog(@"OpenGL", @"No wayland window device found for view %@", view); return; } - _window->usesOpenGL = YES; - - [self _destroySurface]; - if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO) { return; @@ -313,6 +340,8 @@ - (void)makeCurrentContext format:@"GL Context has no view attached, cannot be made current"]; } + [self _attachToWindowIfNeeded]; + if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO) { return; @@ -348,6 +377,8 @@ - (void)flushBuffer - (void)update { + [self _attachToWindowIfNeeded]; + if (_eglWindow != NULL && _window != NULL) { wl_egl_window_resize(_eglWindow, From 396b42b4212453217aab680aeba06a6c8e1966fd Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 18 Apr 2026 14:33:14 +1000 Subject: [PATCH 03/24] wayland: avoid noisy GL attach failures and fix retain leak --- Source/wayland/WaylandGLContext.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m index 768eb533..0429abb7 100644 --- a/Source/wayland/WaylandGLContext.m +++ b/Source/wayland/WaylandGLContext.m @@ -91,9 +91,9 @@ - (BOOL)_ensureDisplayAndContextWithShare:(NSOpenGLContext *)share return YES; } - if (_window == NULL) + if (_window == NULL && [self _attachToWindowIfNeeded] == NO) { - [self _attachToWindowIfNeeded]; + return NO; } wlDisplay = NULL; @@ -174,7 +174,6 @@ - (BOOL)_attachToWindowIfNeeded newWindow = (struct window *)[server windowDevice:[window windowNumber]]; if (newWindow == NULL) { - NSDebugMLLog(@"OpenGL", @"No wayland window device found for view %@", _view); return NO; } @@ -285,8 +284,6 @@ - (id)initWithFormat:(NSOpenGLPixelFormat *)format _eglDisplay = ((WaylandGLContext *)share)->_eglDisplay; } - _pixelFormat = RETAIN(format); - return self; } @@ -340,7 +337,10 @@ - (void)makeCurrentContext format:@"GL Context has no view attached, cannot be made current"]; } - [self _attachToWindowIfNeeded]; + if ([self _attachToWindowIfNeeded] == NO) + { + return; + } if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO) { From 95b7be7d628a89381a904038d308e76576df9260 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 19 Apr 2026 18:06:12 +1000 Subject: [PATCH 04/24] Implemented OpenGL Support on Wayland. --- Headers/wayland/WaylandOpenGL.h | 2 + Headers/wayland/WaylandServer.h | 1 + Source/GNUmakefile.preamble | 2 +- Source/wayland/GNUmakefile.preamble | 2 +- Source/wayland/WaylandGLContext.m | 111 +++++++++++++++++++++++++--- Source/wayland/WaylandServer.m | 10 ++- 6 files changed, 115 insertions(+), 13 deletions(-) diff --git a/Headers/wayland/WaylandOpenGL.h b/Headers/wayland/WaylandOpenGL.h index 5e811dbb..93be236d 100644 --- a/Headers/wayland/WaylandOpenGL.h +++ b/Headers/wayland/WaylandOpenGL.h @@ -38,6 +38,8 @@ struct window; NSView *_view; NSOpenGLContext *_shareContext; struct window *_window; + struct wl_surface *_glSurface; + struct wl_subsurface *_glSubsurface; struct wl_egl_window *_eglWindow; EGLDisplay _eglDisplay; EGLContext _eglContext; diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index df565e73..1562ea98 100755 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -84,6 +84,7 @@ typedef struct _WaylandConfig struct wl_keyboard *keyboard; struct xdg_wm_base *wm_base; struct zwlr_layer_shell_v1 *layer_shell; + struct wl_subcompositor *subcompositor; int seat_version; struct wl_list output_list; diff --git a/Source/GNUmakefile.preamble b/Source/GNUmakefile.preamble index 19b5fb04..911449d9 100644 --- a/Source/GNUmakefile.preamble +++ b/Source/GNUmakefile.preamble @@ -45,7 +45,7 @@ CONFIG_SYSTEM_INCL += $(GRAPHIC_CFLAGS) # Additional library directories the linker should search #ADDITIONAL_LIB_DIRS += -CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) -lGL -lEGL +CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) -lGL -lEGL -lwayland-egl # # Flags for compiling as a bundle or library (if the system depends diff --git a/Source/wayland/GNUmakefile.preamble b/Source/wayland/GNUmakefile.preamble index 8b928e8e..5d43eb77 100644 --- a/Source/wayland/GNUmakefile.preamble +++ b/Source/wayland/GNUmakefile.preamble @@ -42,7 +42,7 @@ ADDITIONAL_INCLUDE_DIRS += -I../../Headers \ -I../$(GNUSTEP_TARGET_DIR) $(GRAPHIC_CFLAGS) # Additional LDFLAGS to pass to the linker -ADDITIONAL_LDFLAGS = -lGL -lEGL +ADDITIONAL_LDFLAGS = -lGL -lEGL -lwayland-egl # Additional library directories the linker should search ADDITIONAL_LIB_DIRS = diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m index 0429abb7..dbc0d6cb 100644 --- a/Source/wayland/WaylandGLContext.m +++ b/Source/wayland/WaylandGLContext.m @@ -32,6 +32,7 @@ #include #include +#include #include "wayland/WaylandServer.h" #include "wayland/WaylandOpenGL.h" @@ -191,20 +192,83 @@ - (BOOL)_attachToWindowIfNeeded return YES; } +- (void)_computeViewGeometry:(NSRect *)outFrame +{ + NSRect frame = [_view convertRect:[_view bounds] toView:nil]; + /* AppKit Y-up → Wayland Y-down: flip origin.y relative to window height */ + frame.origin.y = _window->height - NSMaxY(frame); + *outFrame = frame; +} + - (BOOL)_ensureSurface { EGLConfig eglConfig; + NSRect viewFrame; + struct wl_surface *renderSurface; + int subW, subH, subX, subY; if (_window == NULL || _window->surface == NULL) { return NO; } + [self _computeViewGeometry:&viewFrame]; + subX = (int)NSMinX(viewFrame); + subY = (int)NSMinY(viewFrame); + subW = (int)NSWidth(viewFrame); + subH = (int)NSHeight(viewFrame); + if (subW <= 0 || subH <= 0) + { + return NO; + } + + if (_glSurface == NULL) + { + WaylandConfig *wlconfig = _window->wlconfig; + + if (wlconfig->subcompositor == NULL) + { + NSDebugMLLog(@"OpenGL", + @"wl_subcompositor not available; " + @"falling back to window surface (single GL view only)"); + renderSurface = _window->surface; + } + else + { + _glSurface = wl_compositor_create_surface(wlconfig->compositor); + if (_glSurface == NULL) + { + NSDebugMLLog(@"OpenGL", @"wl_compositor_create_surface for GL view failed"); + return NO; + } + + _glSubsurface = wl_subcompositor_get_subsurface( + wlconfig->subcompositor, _glSurface, _window->surface); + if (_glSubsurface == NULL) + { + NSDebugMLLog(@"OpenGL", @"wl_subcompositor_get_subsurface failed"); + wl_surface_destroy(_glSurface); + _glSurface = NULL; + return NO; + } + + wl_subsurface_set_desync(_glSubsurface); + wl_subsurface_set_position(_glSubsurface, subX, subY); + /* Commit parent so the compositor registers the new subsurface */ + wl_surface_commit(_window->surface); + wl_display_flush(wlconfig->display); + + renderSurface = _glSurface; + } + } + else + { + renderSurface = _glSurface; + } + if (_eglWindow == NULL) { - _eglWindow = wl_egl_window_create(_window->surface, - (int)_window->width, - (int)_window->height); + _eglWindow = wl_egl_window_create(renderSurface, subW, subH); if (_eglWindow == NULL) { NSDebugMLLog(@"OpenGL", @"wl_egl_window_create failed"); @@ -250,6 +314,18 @@ - (void)_destroySurface wl_egl_window_destroy(_eglWindow); _eglWindow = NULL; } + + if (_glSubsurface != NULL) + { + wl_subsurface_destroy(_glSubsurface); + _glSubsurface = NULL; + } + + if (_glSurface != NULL) + { + wl_surface_destroy(_glSurface); + _glSurface = NULL; + } } - (id)initWithFormat:(NSOpenGLPixelFormat *)format @@ -273,6 +349,8 @@ - (id)initWithFormat:(NSOpenGLPixelFormat *)format _eglContext = EGL_NO_CONTEXT; _eglSurface = EGL_NO_SURFACE; _eglWindow = NULL; + _glSurface = NULL; + _glSubsurface = NULL; _window = NULL; _swapInterval = 1; @@ -377,15 +455,30 @@ - (void)flushBuffer - (void)update { + NSRect viewFrame; + [self _attachToWindowIfNeeded]; - if (_eglWindow != NULL && _window != NULL) + if (_eglWindow == NULL || _window == NULL) { - wl_egl_window_resize(_eglWindow, - (int)_window->width, - (int)_window->height, - 0, - 0); + return; + } + + [self _computeViewGeometry:&viewFrame]; + + wl_egl_window_resize(_eglWindow, + (int)NSWidth(viewFrame), + (int)NSHeight(viewFrame), + 0, + 0); + + if (_glSubsurface != NULL) + { + wl_subsurface_set_position(_glSubsurface, + (int)NSMinX(viewFrame), + (int)NSMinY(viewFrame)); + wl_surface_commit(_window->surface); + wl_display_flush(_window->wlconfig->display); } } diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 4e5c609f..3fca51f4 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -129,6 +129,12 @@ NSDebugLog(@"wayland: found seat interface"); wl_seat_add_listener(wlconfig->seat, &seat_listener, wlconfig); } + else if (strcmp(interface, wl_subcompositor_interface.name) == 0) + { + wlconfig->subcompositor + = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); + NSDebugLog(@"wayland: found subcompositor interface"); + } } static void handle_global_remove(void *data, struct wl_registry *registry, @@ -329,8 +335,8 @@ - (void *)serverDevice - (void *)windowDevice:(int)win { - NSDebugLog(@"windowDevice"); - return NULL; + NSDebugLog(@"windowDevice: %d", win); + return get_window_with_id(wlconfig, win); } - (void)beep From cc3eeb8ce6e14f7c75b06840e4f8666b65bfdb29 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sun, 19 Apr 2026 18:14:15 +1000 Subject: [PATCH 05/24] Mouse Crash fix for Wayland backend. --- Source/wayland/WaylandGLContext.m | 2 ++ Source/wayland/WaylandServer+Cursor.m | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m index dbc0d6cb..b203c023 100644 --- a/Source/wayland/WaylandGLContext.m +++ b/Source/wayland/WaylandGLContext.m @@ -254,6 +254,8 @@ - (BOOL)_ensureSurface wl_subsurface_set_desync(_glSubsurface); wl_subsurface_set_position(_glSubsurface, subX, subY); + /* Route input events on this subsurface to the parent window */ + wl_surface_set_user_data(_glSurface, _window); /* Commit parent so the compositor registers the new subsurface */ wl_surface_commit(_window->surface); wl_display_flush(wlconfig->display); diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index c0187560..ed723a13 100755 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -53,7 +53,7 @@ struct window *window = wl_surface_get_user_data(surface); - if (window->ignoreMouse) + if (window == NULL || window->ignoreMouse) { return; } @@ -117,7 +117,7 @@ struct window *window = wl_surface_get_user_data(surface); - if (window->ignoreMouse) + if (window == NULL || window->ignoreMouse) { return; } From 7e146cc44301b032085d8d63bc62713528a425b7 Mon Sep 17 00:00:00 2001 From: DMJC Date: Mon, 20 Apr 2026 07:44:06 +1000 Subject: [PATCH 06/24] Mouse Fix. --- Source/wayland/WaylandServer+Cursor.m | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index ed723a13..959bba4d 100755 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -693,7 +693,7 @@ - (void)standardcursor:(int)style :(void **)cid { NSDebugLog(@"load cursor from theme for style %d: %s", style, cursor_name); - struct cursor *wayland_cursor = malloc(sizeof(struct cursor)); + struct cursor *wayland_cursor = calloc(1, sizeof(struct cursor)); wayland_cursor->cursor = wl_cursor_theme_get_cursor(wlconfig->cursor_theme, cursor_name); @@ -724,9 +724,9 @@ - (void)imagecursor:(NSPoint)hotp :(NSImage *)image :(void **)cid // TODO should check if the bitmaprep format is compatible memcpy(pbuffer->data, data, [raw_img bytesPerPlane]); - struct cursor * wayland_cursor = malloc(sizeof(struct cursor)); + struct cursor *wayland_cursor = calloc(1, sizeof(struct cursor)); - struct wl_cursor * cursor = malloc(sizeof(struct wl_cursor)); + struct wl_cursor *cursor = calloc(1, sizeof(struct wl_cursor)); cursor->image_count = 1; cursor->name = "custom"; struct wl_cursor_image * cursor_image = malloc(sizeof(struct wl_cursor_image)); @@ -776,10 +776,6 @@ - (void)setcursor:(void *)cid return; } - if (wayland_cursor->surface) - { - wl_surface_destroy(wayland_cursor->surface); - } wl_pointer_set_cursor(wlconfig->pointer.wlpointer, wlconfig->event_serial, wlconfig->cursor_surface, wayland_cursor->image->hotspot_x, From f392ebab0ea1b2e9fbeb81164a3cc91ad16b7f1f Mon Sep 17 00:00:00 2001 From: DMJC Date: Thu, 30 Apr 2026 22:59:14 +1000 Subject: [PATCH 07/24] OpenGL is Fixed on Wayland. --- Headers/wayland/WaylandDragView.h | 41 ++++ Headers/wayland/WaylandInputServer.h | 54 +++++ Headers/wayland/WaylandOpenGL.h | 59 +++++ Headers/wayland/WaylandServer.h | 27 ++- Source/wayland/GNUmakefile | 2 + Source/wayland/WaylandDragView.m | 298 ++++++++++++++++++++++++ Source/wayland/WaylandGLContext.m | 231 +++++++++++++++++- Source/wayland/WaylandGLPixelFormat.m | 20 +- Source/wayland/WaylandInputServer.m | 155 ++++++++++++ Source/wayland/WaylandServer+Keyboard.m | 57 ++++- Source/wayland/WaylandServer+Xdgshell.m | 24 +- Source/wayland/WaylandServer.m | 93 +++++++- 12 files changed, 1017 insertions(+), 44 deletions(-) create mode 100644 Headers/wayland/WaylandDragView.h create mode 100644 Headers/wayland/WaylandInputServer.h create mode 100644 Source/wayland/WaylandDragView.m create mode 100644 Source/wayland/WaylandInputServer.m diff --git a/Headers/wayland/WaylandDragView.h b/Headers/wayland/WaylandDragView.h new file mode 100644 index 00000000..61d76118 --- /dev/null +++ b/Headers/wayland/WaylandDragView.h @@ -0,0 +1,41 @@ +/* WaylandDragView - Drag and Drop for Wayland backend + + Copyright (C) 2024 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _WaylandDragView_h_INCLUDE +#define _WaylandDragView_h_INCLUDE + +#include +#include +#include +#include + +@interface WaylandDragView : GSDragView + ++ (id) sharedDragView; + +- (void) updateDragInfoFromEvent: (NSEvent *)event; +- (void) resetDragInfo; + +@end + +#endif /* _WaylandDragView_h_INCLUDE */ diff --git a/Headers/wayland/WaylandInputServer.h b/Headers/wayland/WaylandInputServer.h new file mode 100644 index 00000000..26ae14ac --- /dev/null +++ b/Headers/wayland/WaylandInputServer.h @@ -0,0 +1,54 @@ +/* WaylandInputServer - Keyboard input handling for Wayland backend + + Copyright (C) 2024 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _WaylandInputServer_h_INCLUDE +#define _WaylandInputServer_h_INCLUDE + +#include + +@interface WaylandInputServer : NSInputServer +{ + id delegate; + NSString *server_name; + int focused_window_id; +} + +- (id) initWithDelegate: (id)aDelegate name: (NSString *)name; +- (void) setFocusedWindowId: (int)windowId; +- (int) focusedWindowId; + +@end + +@interface WaylandInputServer (InputMethod) +- (NSString *) inputMethodStyle; +- (NSString *) fontSize: (int *)size; +- (BOOL) clientWindowRect: (NSRect *)rect; +- (BOOL) statusArea: (NSRect *)rect; +- (BOOL) preeditArea: (NSRect *)rect; +- (BOOL) preeditSpot: (NSPoint *)p; +- (BOOL) setStatusArea: (NSRect *)rect; +- (BOOL) setPreeditArea: (NSRect *)rect; +- (BOOL) setPreeditSpot: (NSPoint *)p; +@end + +#endif /* _WaylandInputServer_h_INCLUDE */ diff --git a/Headers/wayland/WaylandOpenGL.h b/Headers/wayland/WaylandOpenGL.h index 93be236d..667776d5 100644 --- a/Headers/wayland/WaylandOpenGL.h +++ b/Headers/wayland/WaylandOpenGL.h @@ -27,6 +27,8 @@ #include #include +#include +#include @class NSView; struct wl_egl_window; @@ -45,7 +47,64 @@ struct window; EGLContext _eglContext; EGLSurface _eglSurface; int _swapInterval; + + /* EGL/GL extension support */ + BOOL _extensionsLoaded; + BOOL _hasDmaBufImport; + BOOL _hasDmaBufImportModifiers; + BOOL _hasExternalTexture; + PFNEGLCREATEIMAGEKHRPROC _pfnCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC _pfnDestroyImageKHR; + PFNEGLQUERYDMABUFFORMATSEXTPROC _pfnQueryDmaBufFormats; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC _pfnQueryDmaBufModifiers; + /* GL_OES_EGL_image — stored as void* to avoid pulling in GLES2 headers */ + void *_pfnGLImageTargetTexture2D; } + +/* Returns YES if EGL_EXT_image_dma_buf_import is available. */ +- (BOOL)supportsDmaBufImport; + +/* Returns YES if GL_OES_EGL_image / GL_OES_EGL_image_external are available. */ +- (BOOL)supportsExternalTexture; + +/* Returns the EGLDisplay used by this context (EGL_NO_DISPLAY if not yet initialised). */ +- (EGLDisplay)eglDisplay; + +/* + * Create an EGLImageKHR backed by a single-plane DMA-BUF. + * fourcc is a DRM FourCC pixel format code (e.g. DRM_FORMAT_ARGB8888). + * Returns EGL_NO_IMAGE_KHR on failure. + */ +- (EGLImageKHR)createEGLImageFromDmaBufFd:(int)fd + width:(int)width + height:(int)height + stride:(int)stride + offset:(int)offset + fourcc:(uint32_t)fourcc; + +/* + * Same as above but also passes the 64-bit DRM format modifier. + * Requires EGL_EXT_image_dma_buf_import_modifiers on the display. + */ +- (EGLImageKHR)createEGLImageFromDmaBufFd:(int)fd + width:(int)width + height:(int)height + stride:(int)stride + offset:(int)offset + fourcc:(uint32_t)fourcc + modifier:(uint64_t)modifier; + +/* Destroy an EGLImageKHR previously created by the methods above. */ +- (void)destroyEGLImage:(EGLImageKHR)image; + +/* + * Bind image to the GL_TEXTURE_EXTERNAL_OES texture object texId. + * The caller must bind texId to GL_TEXTURE_EXTERNAL_OES before calling this, + * or call glBindTexture(GL_TEXTURE_EXTERNAL_OES, texId) themselves. + * Requires supportsExternalTexture == YES. + */ +- (void)bindEGLImage:(EGLImageKHR)image toExternalTexture:(unsigned int)texId; + @end @interface WaylandGLPixelFormat : NSOpenGLPixelFormat diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index 1562ea98..72766bf4 100755 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -106,6 +106,9 @@ typedef struct _WaylandConfig struct pointer pointer; float mouse_scroll_multiplier; +// keyboard focus (set by keyboard_handle_enter/leave) + struct window *keyboard_focus; + // keyboard struct xkb_context *xkb_context; struct @@ -178,12 +181,30 @@ struct window *get_window_with_id(WaylandConfig *wlconfig, int winid); WaylandConfig *wlconfig; BOOL _mouseInitialized; + id inputServer; } @end -@interface -WaylandServer (Cursor) -- (void)initializeMouseIfRequired; +@interface WaylandServer (Cursor) +- (void) initializeMouseIfRequired; +@end + +@interface WaylandServer (DragAndDrop) +- (id ) dragInfo; +- (BOOL) addDragTypes: (NSArray *)types toWindow: (NSWindow *)win; +- (BOOL) removeDragTypes: (NSArray *)types fromWindow: (NSWindow *)win; +@end + +@interface WaylandServer (InputMethod) +- (NSString *) inputMethodStyle; +- (NSString *) fontSize: (int *)size; +- (BOOL) clientWindowRect: (NSRect *)rect; +- (BOOL) statusArea: (NSRect *)rect; +- (BOOL) preeditArea: (NSRect *)rect; +- (BOOL) preeditSpot: (NSPoint *)p; +- (BOOL) setStatusArea: (NSRect *)rect; +- (BOOL) setPreeditArea: (NSRect *)rect; +- (BOOL) setPreeditSpot: (NSPoint *)p; @end #endif /* _WaylandServer_h_INCLUDE */ diff --git a/Source/wayland/GNUmakefile b/Source/wayland/GNUmakefile index 4c02a9cb..d2803f2d 100644 --- a/Source/wayland/GNUmakefile +++ b/Source/wayland/GNUmakefile @@ -53,6 +53,8 @@ WaylandServer+Xdgshell.m \ WaylandServer+Layershell.m \ WaylandGLContext.m \ WaylandGLPixelFormat.m \ +WaylandInputServer.m \ +WaylandDragView.m \ -include GNUmakefile.preamble diff --git a/Source/wayland/WaylandDragView.m b/Source/wayland/WaylandDragView.m new file mode 100644 index 00000000..ebc62b0c --- /dev/null +++ b/Source/wayland/WaylandDragView.m @@ -0,0 +1,298 @@ +/* WaylandDragView - Drag and Drop for Wayland backend + + Copyright (C) 2024 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wayland/WaylandServer.h" +#include "wayland/WaylandDragView.h" + +/* Private category to expose wlconfig from WaylandServer */ +@interface WaylandServer (DragViewAccess) +- (WaylandConfig *) wlconfig; +@end + +@implementation WaylandServer (DragViewAccess) +- (WaylandConfig *) wlconfig +{ + return wlconfig; +} +@end + + +/* Lightweight NSWindow subclass used to hold the GSDragView content. + We never actually show this window - the drag icon is rendered on the + Wayland cursor surface instead, so it follows the pointer automatically. + The window must still exist for GSDragView's internal event handling. */ +@interface WaylandRawWindow : NSWindow +@end + +@implementation WaylandRawWindow + +- (BOOL) canBecomeMainWindow +{ + return NO; +} + +- (BOOL) canBecomeKeyWindow +{ + return NO; +} + +- (void) _initDefaults +{ + [super _initDefaults]; + [self setReleasedWhenClosed: NO]; + [self setExcludedFromWindowsMenu: YES]; +} + +- (void) orderWindow: (NSWindowOrderingMode)place relativeTo: (NSInteger)otherWin +{ + [super orderWindow: place relativeTo: otherWin]; + [self setLevel: NSPopUpMenuWindowLevel]; +} + +@end + + +/* Private ivar extension */ +@interface WaylandDragView () +{ + void *_dragCursorCid; +} +@end + + +@implementation WaylandDragView + +static WaylandDragView *sharedDragView = nil; + ++ (id) sharedDragView +{ + if (sharedDragView == nil) + { + sharedDragView = [WaylandDragView new]; + } + return sharedDragView; +} + ++ (Class) windowClass +{ + return [WaylandRawWindow class]; +} + +- (void) updateDragInfoFromEvent: (NSEvent *)event +{ + destWindow = [event window]; + dragPoint = [event locationInWindow]; + dragSequence = [event timestamp]; + dragMask = [event data2]; +} + +- (void) resetDragInfo +{ + DESTROY(dragPasteboard); +} + +- (void) postDragEvent: (NSEvent *)theEvent +{ + if (destExternal) + { + /* Inter-process drag via wl_data_device is not yet implemented. */ + NSDebugLog(@"WaylandDragView: external postDragEvent not yet supported"); + } + else + { + [super postDragEvent: theEvent]; + } +} + +- (void) sendExternalEvent: (GSAppKitSubtype)subtype + action: (NSDragOperation)action + position: (NSPoint)eventLocation + timestamp: (NSTimeInterval)time + toWindow: (int)dWindowNumber +{ + /* Wayland inter-process DnD requires the wl_data_device protocol, + which is not yet implemented in this backend. */ + NSDebugLog(@"WaylandDragView: sendExternalEvent not yet implemented " + @"(subtype=%d, window=%d)", (int)subtype, dWindowNumber); +} + +/* Override to render the drag icon on the Wayland cursor surface instead + of positioning a floating window. The cursor follows the pointer + automatically, so the icon tracks the mouse with no extra work. */ +- (void) _setupWindowFor: (NSImage *)anImage + mousePosition: (NSPoint)mPoint + imagePosition: (NSPoint)iPoint +{ + if (anImage == nil) + anImage = [NSImage imageNamed: @"common_Close"]; + + NSSize imageSize = [anImage size]; + + /* Set internal GSDragView state (mirrors what the base implementation does + before calling orderFront:, which we intentionally omit here). */ + [dragCell setImage: anImage]; + dragPosition = mPoint; + newPosition = mPoint; + offset.width = mPoint.x - iPoint.x; + offset.height = mPoint.y - iPoint.y; + + /* Hotspot: cursor position within the image in Wayland pixel coords + (origin top-left, Y increasing downwards). + hotspot_x = cursor_x - image_left (same in NS and Wayland) + hotspot_y = image_height - (cursor_y - image_bottom_NS) (flip Y) */ + NSPoint hotspot; + hotspot.x = offset.width; + hotspot.y = imageSize.height - offset.height; + if (hotspot.x < 0) hotspot.x = 0; + if (hotspot.y < 0) hotspot.y = 0; + + NSDebugLog(@"WaylandDragView: setting drag cursor hotspot=(%g,%g)", + hotspot.x, hotspot.y); + + WaylandServer *server = (WaylandServer *) GSCurrentServer(); + [server imagecursor: hotspot : anImage : &_dragCursorCid]; + if (_dragCursorCid != NULL) + [server setcursor: _dragCursorCid]; +} + +/* Restore the default arrow cursor and release the drag cursor resource. */ +- (void) _clearupWindow +{ + WaylandServer *server = (WaylandServer *) GSCurrentServer(); + + /* Restore the default cursor first so the compositor stops using the + drag-image surface before we destroy the underlying buffer. */ + void *arrowCid = NULL; + [server standardcursor: GSArrowCursor : &arrowCid]; + if (arrowCid != NULL) + [server setcursor: arrowCid]; + + if (_dragCursorCid != NULL) + { + [server freecursor: _dragCursorCid]; + _dragCursorCid = NULL; + } +} + +/* The cursor surface already follows the pointer automatically via the + Wayland compositor; no window repositioning is needed. */ +- (void) _moveDraggedImageToNewPosition +{ + dragPosition = newPosition; +} + +- (NSWindow *) windowAcceptingDnDunder: (NSPoint)p + windowRef: (int *)mouseWindowRef +{ + WaylandConfig *wlconfig = + [(WaylandServer *) GSCurrentServer() wlconfig]; + struct window *window; + struct output *output = NULL; + + /* Use the first output for screen-height conversion. */ + wl_list_for_each(output, &wlconfig->output_list, link) + { + break; + } + + if (output == NULL) + { + if (mouseWindowRef) + *mouseWindowRef = 0; + return nil; + } + + int dragWinNum = (_window != nil) ? [_window windowNumber] : -1; + + /* Walk the window list; keep updating candidate so we end up with the + topmost (last-inserted) window whose bounds contain the point. */ + struct window *candidate = NULL; + wl_list_for_each(window, &wlconfig->window_list, link) + { + if (window->window_id == dragWinNum) + continue; + if (window->ignoreMouse || window->terminated || !window->configured) + continue; + + /* Convert Wayland window rect to NS screen coordinates: + NS origin.y = output->height - pos_y(wl-top) - height */ + float ns_x = window->pos_x; + float ns_y = output->height - window->pos_y - window->height; + + if (p.x >= ns_x && p.x < ns_x + window->width + && p.y >= ns_y && p.y < ns_y + window->height) + { + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (nswindow == nil) + continue; + NSCountedSet *dragTypes = + [GSCurrentServer() dragTypesForWindow: nswindow]; + if ([dragTypes count] > 0) + candidate = window; + } + } + + if (candidate != NULL) + { + if (mouseWindowRef) + *mouseWindowRef = candidate->window_id; + return GSWindowWithNumber(candidate->window_id); + } + + if (mouseWindowRef) + *mouseWindowRef = 0; + return nil; +} + +@end + + +@implementation WaylandServer (DragAndDrop) + +- (id ) dragInfo +{ + return [WaylandDragView sharedDragView]; +} + +- (BOOL) addDragTypes: (NSArray *)types toWindow: (NSWindow *)win +{ + return [super addDragTypes: types toWindow: win]; +} + +- (BOOL) removeDragTypes: (NSArray *)types fromWindow: (NSWindow *)win +{ + return [super removeDragTypes: types fromWindow: win]; +} + +@end diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m index b203c023..134d75ee 100644 --- a/Source/wayland/WaylandGLContext.m +++ b/Source/wayland/WaylandGLContext.m @@ -31,8 +31,12 @@ #include #include +#include +#include +#include #include #include +#include #include "wayland/WaylandServer.h" #include "wayland/WaylandOpenGL.h" @@ -188,15 +192,29 @@ - (BOOL)_attachToWindowIfNeeded [self _destroySurface]; } - _window->usesOpenGL = YES; return YES; } - (void)_computeViewGeometry:(NSRect *)outFrame { - NSRect frame = [_view convertRect:[_view bounds] toView:nil]; + NSRect bounds = [_view bounds]; + NSRect frame = [_view convertRect:bounds toView:nil]; /* AppKit Y-up → Wayland Y-down: flip origin.y relative to window height */ frame.origin.y = _window->height - NSMaxY(frame); + + static BOOL _loggedOnce = NO; + if (!_loggedOnce) + { + _loggedOnce = YES; + NSLog(@"WaylandGL: _computeViewGeometry:" + @" viewBounds=%@ convertedFrame=%@ windowH=%.0f" + @" → waylandFrame=%@", + NSStringFromRect(bounds), + NSStringFromRect([_view convertRect:bounds toView:nil]), + (double)_window->height, + NSStringFromRect(frame)); + } + *outFrame = frame; } @@ -228,17 +246,16 @@ - (BOOL)_ensureSurface if (wlconfig->subcompositor == NULL) { - NSDebugMLLog(@"OpenGL", - @"wl_subcompositor not available; " - @"falling back to window surface (single GL view only)"); + NSLog(@"WaylandGL: _ensureSurface: no subcompositor — using window surface directly"); renderSurface = _window->surface; + _window->usesOpenGL = YES; } else { _glSurface = wl_compositor_create_surface(wlconfig->compositor); if (_glSurface == NULL) { - NSDebugMLLog(@"OpenGL", @"wl_compositor_create_surface for GL view failed"); + NSLog(@"WaylandGL: _ensureSurface: wl_compositor_create_surface failed"); return NO; } @@ -246,7 +263,7 @@ - (BOOL)_ensureSurface wlconfig->subcompositor, _glSurface, _window->surface); if (_glSubsurface == NULL) { - NSDebugMLLog(@"OpenGL", @"wl_subcompositor_get_subsurface failed"); + NSLog(@"WaylandGL: _ensureSurface: wl_subcompositor_get_subsurface failed"); wl_surface_destroy(_glSurface); _glSurface = NULL; return NO; @@ -254,12 +271,12 @@ - (BOOL)_ensureSurface wl_subsurface_set_desync(_glSubsurface); wl_subsurface_set_position(_glSubsurface, subX, subY); - /* Route input events on this subsurface to the parent window */ wl_surface_set_user_data(_glSurface, _window); - /* Commit parent so the compositor registers the new subsurface */ wl_surface_commit(_window->surface); wl_display_flush(wlconfig->display); + NSLog(@"WaylandGL: _ensureSurface: subsurface created glSurface=%p pos=(%d,%d) size=(%dx%d)", + _glSurface, subX, subY, subW, subH); renderSurface = _glSurface; } } @@ -291,7 +308,7 @@ - (BOOL)_ensureSurface if (_eglSurface == EGL_NO_SURFACE) { - NSDebugMLLog(@"OpenGL", @"eglCreateWindowSurface failed"); + NSLog(@"WaylandGL: _ensureSurface: eglCreateWindowSurface failed (0x%x)", eglGetError()); return NO; } @@ -300,6 +317,8 @@ - (BOOL)_ensureSurface eglSwapInterval(_eglDisplay, _swapInterval); } + NSLog(@"WaylandGL: _ensureSurface: EGL surface ready eglSurface=%p eglWindow=%p", + _eglSurface, _eglWindow); return YES; } @@ -330,6 +349,173 @@ - (void)_destroySurface } } +- (void)_loadExtensions +{ + const char *eglExts; + const char *glExts; + + if (_eglDisplay == EGL_NO_DISPLAY || _eglContext == EGL_NO_CONTEXT) + return; + + _extensionsLoaded = YES; + + eglExts = eglQueryString(_eglDisplay, EGL_EXTENSIONS); + if (eglExts == NULL) + eglExts = ""; + + if (strstr(eglExts, "EGL_EXT_image_dma_buf_import") != NULL) + { + _pfnCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) + eglGetProcAddress("eglCreateImageKHR"); + _pfnDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) + eglGetProcAddress("eglDestroyImageKHR"); + if (_pfnCreateImageKHR != NULL && _pfnDestroyImageKHR != NULL) + { + _hasDmaBufImport = YES; + NSDebugMLLog(@"OpenGL", @"WaylandGL: EGL_EXT_image_dma_buf_import available"); + } + } + + if (_hasDmaBufImport + && strstr(eglExts, "EGL_EXT_image_dma_buf_import_modifiers") != NULL) + { + _pfnQueryDmaBufFormats = (PFNEGLQUERYDMABUFFORMATSEXTPROC) + eglGetProcAddress("eglQueryDmaBufFormatsEXT"); + _pfnQueryDmaBufModifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC) + eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + if (_pfnQueryDmaBufFormats != NULL && _pfnQueryDmaBufModifiers != NULL) + { + _hasDmaBufImportModifiers = YES; + NSDebugMLLog(@"OpenGL", @"WaylandGL: EGL_EXT_image_dma_buf_import_modifiers available"); + } + } + + /* GL_OES_EGL_image + GL_OES_EGL_image_external — needs a current context */ + glExts = (const char *)glGetString(GL_EXTENSIONS); + if (glExts == NULL) + glExts = ""; + + if (strstr(glExts, "GL_OES_EGL_image") != NULL) + { + void *pfn = (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + if (pfn != NULL) + { + _pfnGLImageTargetTexture2D = pfn; + _hasExternalTexture = YES; + NSDebugMLLog(@"OpenGL", @"WaylandGL: GL_OES_EGL_image_external available"); + } + } +} + +- (BOOL)supportsDmaBufImport +{ + return _hasDmaBufImport; +} + +- (BOOL)supportsExternalTexture +{ + return _hasExternalTexture; +} + +- (EGLDisplay)eglDisplay +{ + return _eglDisplay; +} + +- (EGLImageKHR)createEGLImageFromDmaBufFd:(int)fd + width:(int)width + height:(int)height + stride:(int)stride + offset:(int)offset + fourcc:(uint32_t)fourcc +{ + EGLint attrs[] = { + EGL_WIDTH, (EGLint)width, + EGL_HEIGHT, (EGLint)height, + EGL_LINUX_DRM_FOURCC_EXT, (EGLint)fourcc, + EGL_DMA_BUF_PLANE0_FD_EXT, (EGLint)fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)stride, + EGL_NONE + }; + EGLImageKHR image; + + if (!_hasDmaBufImport || _pfnCreateImageKHR == NULL + || _eglDisplay == EGL_NO_DISPLAY) + return EGL_NO_IMAGE_KHR; + + image = _pfnCreateImageKHR(_eglDisplay, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + (EGLClientBuffer)NULL, attrs); + if (image == EGL_NO_IMAGE_KHR) + NSDebugMLLog(@"OpenGL", @"WaylandGL: eglCreateImageKHR(dma-buf) failed: 0x%x", + eglGetError()); + return image; +} + +- (EGLImageKHR)createEGLImageFromDmaBufFd:(int)fd + width:(int)width + height:(int)height + stride:(int)stride + offset:(int)offset + fourcc:(uint32_t)fourcc + modifier:(uint64_t)modifier +{ + EGLint modLo = (EGLint)(modifier & 0xFFFFFFFFu); + EGLint modHi = (EGLint)(modifier >> 32); + EGLint attrs[] = { + EGL_WIDTH, (EGLint)width, + EGL_HEIGHT, (EGLint)height, + EGL_LINUX_DRM_FOURCC_EXT, (EGLint)fourcc, + EGL_DMA_BUF_PLANE0_FD_EXT, (EGLint)fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)stride, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modLo, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modHi, + EGL_NONE + }; + EGLImageKHR image; + + if (!_hasDmaBufImport || _pfnCreateImageKHR == NULL + || _eglDisplay == EGL_NO_DISPLAY) + return EGL_NO_IMAGE_KHR; + + if (!_hasDmaBufImportModifiers) + { + NSDebugMLLog(@"OpenGL", + @"WaylandGL: modifier requested but EGL_EXT_image_dma_buf_import_modifiers unavailable"); + return EGL_NO_IMAGE_KHR; + } + + image = _pfnCreateImageKHR(_eglDisplay, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + (EGLClientBuffer)NULL, attrs); + if (image == EGL_NO_IMAGE_KHR) + NSDebugMLLog(@"OpenGL", + @"WaylandGL: eglCreateImageKHR(dma-buf+modifier) failed: 0x%x", + eglGetError()); + return image; +} + +- (void)destroyEGLImage:(EGLImageKHR)image +{ + if (image == EGL_NO_IMAGE_KHR || _pfnDestroyImageKHR == NULL + || _eglDisplay == EGL_NO_DISPLAY) + return; + _pfnDestroyImageKHR(_eglDisplay, image); +} + +- (void)bindEGLImage:(EGLImageKHR)image toExternalTexture:(unsigned int)texId +{ + typedef void (*TargetTex2DOES)(GLenum, GLeglImageOES); + TargetTex2DOES fn = (TargetTex2DOES)_pfnGLImageTargetTexture2D; + + if (image == EGL_NO_IMAGE_KHR || fn == NULL) + return; + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texId); + fn(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image); +} + - (id)initWithFormat:(NSOpenGLPixelFormat *)format shareContext:(NSOpenGLContext *)share { @@ -354,7 +540,19 @@ - (id)initWithFormat:(NSOpenGLPixelFormat *)format _glSurface = NULL; _glSubsurface = NULL; _window = NULL; - _swapInterval = 1; + _extensionsLoaded = NO; + _hasDmaBufImport = NO; + _hasDmaBufImportModifiers = NO; + _hasExternalTexture = NO; + _pfnCreateImageKHR = NULL; + _pfnDestroyImageKHR = NULL; + _pfnQueryDmaBufFormats = NULL; + _pfnQueryDmaBufModifiers = NULL; + _pfnGLImageTargetTexture2D = NULL; + /* Default to unthrottled swaps. With vsync=1 eglSwapBuffers blocks on the + compositor frame callback, which starves the main run loop when called + from a timer. Apps that want vsync can set NSOpenGLCPSwapInterval = 1. */ + _swapInterval = 0; _pixelFormat = RETAIN(format); _shareContext = RETAIN(share); @@ -438,6 +636,9 @@ - (void)makeCurrentContext return; } + if (!_extensionsLoaded) + [self _loadExtensions]; + currentGLContext = self; } @@ -445,10 +646,16 @@ - (void)flushBuffer { if (_eglDisplay == EGL_NO_DISPLAY || _eglSurface == EGL_NO_SURFACE) { + NSLog(@"WaylandGL: flushBuffer: skipped — no EGL display/surface"); return; } - eglSwapBuffers(_eglDisplay, _eglSurface); + static NSUInteger _swapCount = 0; + EGLBoolean ok = eglSwapBuffers(_eglDisplay, _eglSurface); + if (++_swapCount <= 5 || _swapCount % 90 == 0) + NSLog(@"WaylandGL: flushBuffer: eglSwapBuffers #%lu result=%d err=0x%x", + (unsigned long)_swapCount, (int)ok, ok ? 0 : eglGetError()); + if (_window != NULL && _window->wlconfig != NULL) { wl_display_flush(_window->wlconfig->display); diff --git a/Source/wayland/WaylandGLPixelFormat.m b/Source/wayland/WaylandGLPixelFormat.m index 7e1a2f56..5e59b89c 100644 --- a/Source/wayland/WaylandGLPixelFormat.m +++ b/Source/wayland/WaylandGLPixelFormat.m @@ -107,10 +107,22 @@ - (EGLConfig)eglConfigForDisplay:(EGLDisplay)eglDisplay EGLint stencilSize = 8; EGLint sampleBuffers = 0; EGLint samples = 0; - EGLint renderableType = EGL_OPENGL_BIT; -#ifdef EGL_OPENGL_ES2_BIT - renderableType |= EGL_OPENGL_ES2_BIT; -#endif + /* Match the renderable type to the API that was bound with eglBindAPI() + before this call. Requesting both EGL_OPENGL_BIT|EGL_OPENGL_ES2_BIT + requires a single config to support both APIs simultaneously, which + most drivers do not provide, causing eglChooseConfig to return 0 + configs and the context creation to fail. */ + EGLint renderableType; + switch (eglQueryAPI()) + { + case EGL_OPENGL_ES_API: + renderableType = EGL_OPENGL_ES2_BIT; + break; + case EGL_OPENGL_API: + default: + renderableType = EGL_OPENGL_BIT; + break; + } EGLConfig config = NULL; EGLint configCount = 0; NSUInteger i; diff --git a/Source/wayland/WaylandInputServer.m b/Source/wayland/WaylandInputServer.m new file mode 100644 index 00000000..449987da --- /dev/null +++ b/Source/wayland/WaylandInputServer.m @@ -0,0 +1,155 @@ +/* WaylandInputServer - Keyboard input handling for Wayland backend + + Copyright (C) 2024 Free Software Foundation, Inc. + + This file is part of the GNUstep Backend. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "wayland/WaylandInputServer.h" + +@implementation WaylandInputServer + +- (id) initWithDelegate: (id)aDelegate name: (NSString *)name +{ + delegate = aDelegate; + ASSIGN(server_name, name); + focused_window_id = 0; + NSDebugLog(@"WaylandInputServer: initialized"); + return self; +} + +- (void) dealloc +{ + DESTROY(server_name); + [super dealloc]; +} + +- (void) setFocusedWindowId: (int)windowId +{ + focused_window_id = windowId; + NSDebugLog(@"WaylandInputServer: focused window id = %d", windowId); +} + +- (int) focusedWindowId +{ + return focused_window_id; +} + +/* NSInputServiceProvider protocol */ + +- (void) activeConversationChanged: (id)sender + toNewConversation: (long)newConversation +{ + [super activeConversationChanged: sender toNewConversation: newConversation]; + + if ([sender respondsToSelector: @selector(window)] == NO) + return; + + /* sender is a text client; -window is checked via respondsToSelector above */ + NSWindow *window = [sender performSelector: @selector(window)]; + if (window != nil) + { + focused_window_id = [window windowNumber]; + NSDebugLog(@"WaylandInputServer: conversation changed, focused window = %d", + focused_window_id); + } +} + +- (void) activeConversationWillChange: (id)sender + fromOldConversation: (long)oldConversation +{ + [super activeConversationWillChange: sender + fromOldConversation: oldConversation]; +} + +@end + +@implementation WaylandInputServer (InputMethod) + +- (NSString *) inputMethodStyle +{ + /* Wayland keyboard input is handled directly via XKB in WaylandServer+Keyboard.m. + No input method overlay is used. */ + return nil; +} + +- (NSString *) fontSize: (int *)size +{ + NSString *str; + + str = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSFontSize"]; + if (!str) + str = @"12"; + if (size) + *size = (int) strtol([str cString], NULL, 10); + return str; +} + +- (BOOL) clientWindowRect: (NSRect *)rect +{ + if (!rect || focused_window_id == 0) + return NO; + NSWindow *window = GSWindowWithNumber(focused_window_id); + if (window == nil) + return NO; + *rect = [window frame]; + return YES; +} + +- (BOOL) statusArea: (NSRect *)rect +{ + return NO; +} + +- (BOOL) preeditArea: (NSRect *)rect +{ + return NO; +} + +- (BOOL) preeditSpot: (NSPoint *)p +{ + return NO; +} + +- (BOOL) setStatusArea: (NSRect *)rect +{ + return NO; +} + +- (BOOL) setPreeditArea: (NSRect *)rect +{ + return NO; +} + +- (BOOL) setPreeditSpot: (NSPoint *)p +{ + return NO; +} + +@end diff --git a/Source/wayland/WaylandServer+Keyboard.m b/Source/wayland/WaylandServer+Keyboard.m index c6132a67..349c2f56 100755 --- a/Source/wayland/WaylandServer+Keyboard.m +++ b/Source/wayland/WaylandServer+Keyboard.m @@ -28,6 +28,8 @@ #include "wayland/WaylandServer.h" #include #include +#include +#include #include #include #include @@ -105,18 +107,59 @@ keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - // NSDebugLog(@"keyboard_handle_enter"); - WaylandConfig *wlconfig = data; + NSDebugLog(@"keyboard_handle_enter"); + WaylandConfig *wlconfig = data; wlconfig->event_serial = serial; + + struct window *window = wl_surface_get_user_data(surface); + if (!window) + return; + + wlconfig->keyboard_focus = window; + + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (!nswindow) + return; + + NSEvent *ev = [NSEvent otherEventWithType:NSAppKitDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:window->window_id + context:GSCurrentContext() + subtype:GSAppKitWindowFocusIn + data1:0 + data2:0]; + [nswindow sendEvent:ev]; } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { - WaylandConfig *wlconfig = data; + NSDebugLog(@"keyboard_handle_leave"); + WaylandConfig *wlconfig = data; wlconfig->event_serial = serial; - // NSDebugLog(@"keyboard_handle_leave"); + + if (!wlconfig->keyboard_focus) + return; + + NSWindow *nswindow = GSWindowWithNumber(wlconfig->keyboard_focus->window_id); + if (nswindow) + { + NSEvent *ev = [NSEvent otherEventWithType:NSAppKitDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:wlconfig->keyboard_focus->window_id + context:GSCurrentContext() + subtype:GSAppKitWindowFocusOut + data1:0 + data2:0]; + [nswindow sendEvent:ev]; + } + + wlconfig->keyboard_focus = NULL; } static void @@ -159,8 +202,12 @@ enum wl_keyboard_key_state state = state_w; const xkb_keysym_t *syms; xkb_keysym_t sym; - struct window *window = wlconfig->pointer.focus; + /* Key events follow keyboard focus, not pointer position. Fall back to + pointer focus only when the compositor hasn't sent keyboard_enter yet. */ + struct window *window = wlconfig->keyboard_focus + ? wlconfig->keyboard_focus + : wlconfig->pointer.focus; if (!window) return; diff --git a/Source/wayland/WaylandServer+Xdgshell.m b/Source/wayland/WaylandServer+Xdgshell.m index 8d0386a8..3bda189b 100644 --- a/Source/wayland/WaylandServer+Xdgshell.m +++ b/Source/wayland/WaylandServer+Xdgshell.m @@ -45,9 +45,6 @@ return; } - NSEvent *ev = nil; - NSWindow *nswindow = GSWindowWithNumber(window->window_id); - // NSDebugLog(@"Acknowledging surface configure %p %d (window_id=%d)", // xdg_surface, serial, window->window_id); @@ -60,22 +57,11 @@ window->width, window->height) :window->window_id]; } - - if (window->wlconfig->pointer.focus - && window->wlconfig->pointer.focus->window_id == window->window_id) - { - ev = [NSEvent otherEventWithType:NSAppKitDefined - location:NSZeroPoint - modifierFlags:0 - timestamp:0 - windowNumber:(int) window->window_id - context:GSCurrentContext() - subtype:GSAppKitWindowFocusIn - data1:0 - data2:0]; - - [nswindow sendEvent:ev]; - } + /* Keyboard focus is now handled exclusively by keyboard_handle_enter/leave, + which correctly tracks what the compositor has granted. Sending + GSAppKitWindowFocusIn here based on pointer position was wrong: it could + steal key-window status from a modal dialog simply because the mouse + cursor happened to be over the reconfigured window. */ } static void diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 3fca51f4..2ab24c52 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -47,6 +47,7 @@ #include "wayland/WaylandServer.h" #include "wayland/WaylandOpenGL.h" +#include "wayland/WaylandInputServer.h" extern const struct wl_output_listener output_listener; @@ -222,6 +223,9 @@ - (id)_initWaylandContext @"compositor must support the stable XDG Shell protocol"]; } + inputServer = [[WaylandInputServer allocWithZone: [self zone]] + initWithDelegate: nil name: @"WaylandInput"]; + return self; } @@ -269,6 +273,7 @@ - (id)initWithAttributes:(NSDictionary *)info - (void)dealloc { NSDebugLog(@"Destroying Wayland Server"); + DESTROY(inputServer); [super dealloc]; } @@ -356,6 +361,64 @@ - (void)beep @end +@implementation WaylandServer (InputMethod) + +- (NSString *) inputMethodStyle +{ + return inputServer + ? [(WaylandInputServer *) inputServer inputMethodStyle] : nil; +} + +- (NSString *) fontSize: (int *)size +{ + return inputServer + ? [(WaylandInputServer *) inputServer fontSize: size] : nil; +} + +- (BOOL) clientWindowRect: (NSRect *)rect +{ + return inputServer + ? [(WaylandInputServer *) inputServer clientWindowRect: rect] : NO; +} + +- (BOOL) statusArea: (NSRect *)rect +{ + return inputServer + ? [(WaylandInputServer *) inputServer statusArea: rect] : NO; +} + +- (BOOL) preeditArea: (NSRect *)rect +{ + return inputServer + ? [(WaylandInputServer *) inputServer preeditArea: rect] : NO; +} + +- (BOOL) preeditSpot: (NSPoint *)p +{ + return inputServer + ? [(WaylandInputServer *) inputServer preeditSpot: p] : NO; +} + +- (BOOL) setStatusArea: (NSRect *)rect +{ + return inputServer + ? [(WaylandInputServer *) inputServer setStatusArea: rect] : NO; +} + +- (BOOL) setPreeditArea: (NSRect *)rect +{ + return inputServer + ? [(WaylandInputServer *) inputServer setPreeditArea: rect] : NO; +} + +- (BOOL) setPreeditSpot: (NSPoint *)p +{ + return inputServer + ? [(WaylandInputServer *) inputServer setPreeditSpot: p] : NO; +} + +@end + @implementation WaylandServer (WindowOps) @@ -464,6 +527,14 @@ - (void)termwindow:(int)win NSDebugLog(@"termwindow: win=%d", win); struct window *window = get_window_with_id(wlconfig, win); + /* Clear any stale focus references to avoid dangling pointers. */ + if (wlconfig->keyboard_focus == window) + wlconfig->keyboard_focus = NULL; + if (wlconfig->pointer.focus == window) + wlconfig->pointer.focus = NULL; + if (wlconfig->pointer.captured == window) + wlconfig->pointer.captured = NULL; + [self destroyWindowShell:window]; // FIXME should wait for buffer release before detroying it // @@ -572,7 +643,27 @@ - (void)orderwindow:(int)op:(int)otherWin:(int)win - (void)movewindow:(NSPoint)loc:(int)win { - NSDebugLog(@"movewindow"); + NSDebugLog(@"[%d] movewindow: %f,%f", win, loc.x, loc.y); + struct window *window = get_window_with_id(wlconfig, win); + if (!window) + return; + + window->pos_x = loc.x; + window->pos_y = NSToWayland(window, (int) loc.y); + + /* Layer-shell surfaces are positioned via top/left margins from their + anchor point (ANCHOR_TOP | ANCHOR_LEFT), so we can reposition them + by updating the margins and committing. XDG toplevels are positioned + by the compositor and cannot be moved from the client side. */ + if (window->layer_surface) + { + zwlr_layer_surface_v1_set_margin(window->layer_surface, + (int32_t) window->pos_y, + 0, 0, + (int32_t) window->pos_x); + wl_surface_commit(window->surface); + wl_display_flush(wlconfig->display); + } } - (NSRect) _OSFrameToWFrame: (NSRect)o for: (void*)win From 124da4b81a9d20a9712d2f9ac16785b59ca4dc04 Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 00:38:34 +1000 Subject: [PATCH 08/24] Updates to Popup Menu Handling. --- Headers/wayland/WaylandServer.h | 44 +- ...g-decoration-unstable-v1-client-protocol.h | 400 ++++++++++++++++++ Source/wayland/GNUmakefile | 1 + Source/wayland/WaylandServer+Cursor.m | 140 +++--- Source/wayland/WaylandServer+Xdgshell.m | 65 ++- Source/wayland/WaylandServer.m | 181 +++++++- .../protocols/xdg-decoration-unstable-v1.xml | 176 ++++++++ .../xdg-decoration-unstable-v1-protocol.c | 73 ++++ 8 files changed, 991 insertions(+), 89 deletions(-) create mode 100644 Headers/wayland/xdg-decoration-unstable-v1-client-protocol.h create mode 100644 Source/wayland/protocols/xdg-decoration-unstable-v1.xml create mode 100644 Source/wayland/xdg-decoration-unstable-v1-protocol.c diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index 72766bf4..8a74e3e1 100755 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -42,6 +42,7 @@ #include "wayland/xdg-shell-client-protocol.h" #include "wayland/wlr-layer-shell-client-protocol.h" +#include "wayland/xdg-decoration-unstable-v1-client-protocol.h" struct pointer { @@ -75,16 +76,17 @@ struct cursor typedef struct _WaylandConfig { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_shell *shell; - struct wl_shm *shm; - struct wl_seat *seat; - struct wl_keyboard *keyboard; - struct xdg_wm_base *wm_base; - struct zwlr_layer_shell_v1 *layer_shell; - struct wl_subcompositor *subcompositor; + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_keyboard *keyboard; + struct xdg_wm_base *wm_base; + struct zwlr_layer_shell_v1 *layer_shell; + struct wl_subcompositor *subcompositor; + struct zxdg_decoration_manager_v1 *decoration_manager; int seat_version; struct wl_list output_list; @@ -96,6 +98,11 @@ typedef struct _WaylandConfig // last event serial from pointer or keyboard uint32_t event_serial; +// cursor global position tracking (output-relative, Wayland Y-down) + BOOL cursor_global_valid; + float cursor_global_x; + float cursor_global_y; + // cursor struct wl_cursor_theme *cursor_theme; @@ -154,6 +161,7 @@ struct window BOOL resizing; BOOL ignoreMouse; BOOL usesOpenGL; + BOOL global_pos_known; // saved_pos_x/y hold a reliable output-relative origin float pos_x; float pos_y; @@ -164,17 +172,21 @@ struct window int is_out; int level; - struct wl_surface *surface; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *toplevel; - struct xdg_popup *popup; - struct xdg_positioner *positioner; + int parent_id; + + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *toplevel; + struct xdg_popup *popup; + struct xdg_positioner *positioner; struct zwlr_layer_surface_v1 *layer_surface; - struct output *output; + struct zxdg_toplevel_decoration_v1 *decoration; + struct output *output; CairoSurface *wcs; }; struct window *get_window_with_id(WaylandConfig *wlconfig, int winid); +float WaylandToNS(struct window *window, float wl_y); @interface WaylandServer : GSDisplayServer { diff --git a/Headers/wayland/xdg-decoration-unstable-v1-client-protocol.h b/Headers/wayland/xdg-decoration-unstable-v1-client-protocol.h new file mode 100644 index 00000000..d424d17e --- /dev/null +++ b/Headers/wayland/xdg-decoration-unstable-v1-client-protocol.h @@ -0,0 +1,400 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H +#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol + * @section page_ifaces_xdg_decoration_unstable_v1 Interfaces + * - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager + * - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface + * @section page_copyright_xdg_decoration_unstable_v1 Copyright + *
+ *
+ * Copyright © 2018 Simon Ser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct xdg_toplevel; +struct zxdg_decoration_manager_v1; +struct zxdg_toplevel_decoration_v1; + +#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE +#define ZXDG_DECORATION_MANAGER_V1_INTERFACE +/** + * @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1 + * @section page_iface_zxdg_decoration_manager_v1_desc Description + * + * This interface allows a compositor to announce support for server-side + * decorations. + * + * A window decoration is a set of window controls as deemed appropriate by + * the party managing them, such as user interface components used to move, + * resize and change a window's state. + * + * A client can use this protocol to request being decorated by a supporting + * compositor. + * + * If compositor and client do not negotiate the use of a server-side + * decoration using this protocol, clients continue to self-decorate as they + * see fit. + * + * Warning! The protocol described in this file is experimental and + * backward incompatible changes may be made. Backward compatible changes + * may be added together with the corresponding interface version bump. + * Backward incompatible changes are done by bumping the version number in + * the protocol and interface names and resetting the interface version. + * Once the protocol is to be declared stable, the 'z' prefix and the + * version number in the protocol and interface names are removed and the + * interface version number is reset. + * @section page_iface_zxdg_decoration_manager_v1_api API + * See @ref iface_zxdg_decoration_manager_v1. + */ +/** + * @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface + * + * This interface allows a compositor to announce support for server-side + * decorations. + * + * A window decoration is a set of window controls as deemed appropriate by + * the party managing them, such as user interface components used to move, + * resize and change a window's state. + * + * A client can use this protocol to request being decorated by a supporting + * compositor. + * + * If compositor and client do not negotiate the use of a server-side + * decoration using this protocol, clients continue to self-decorate as they + * see fit. + * + * Warning! The protocol described in this file is experimental and + * backward incompatible changes may be made. Backward compatible changes + * may be added together with the corresponding interface version bump. + * Backward incompatible changes are done by bumping the version number in + * the protocol and interface names and resetting the interface version. + * Once the protocol is to be declared stable, the 'z' prefix and the + * version number in the protocol and interface names are removed and the + * interface version number is reset. + */ +extern const struct wl_interface zxdg_decoration_manager_v1_interface; +#endif +#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE +#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE +/** + * @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1 + * @section page_iface_zxdg_toplevel_decoration_v1_desc Description + * + * The decoration object allows the compositor to toggle server-side window + * decorations for a toplevel surface. The client can request to switch to + * another mode. + * + * The xdg_toplevel_decoration object must be destroyed before its + * xdg_toplevel. + * @section page_iface_zxdg_toplevel_decoration_v1_api API + * See @ref iface_zxdg_toplevel_decoration_v1. + */ +/** + * @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface + * + * The decoration object allows the compositor to toggle server-side window + * decorations for a toplevel surface. The client can request to switch to + * another mode. + * + * The xdg_toplevel_decoration object must be destroyed before its + * xdg_toplevel. + */ +extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; +#endif + +#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0 +#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1 + + +/** + * @ingroup iface_zxdg_decoration_manager_v1 + */ +#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_decoration_manager_v1 + */ +#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1 + +/** @ingroup iface_zxdg_decoration_manager_v1 */ +static inline void +zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data); +} + +/** @ingroup iface_zxdg_decoration_manager_v1 */ +static inline void * +zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1); +} + +static inline uint32_t +zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1); +} + +/** + * @ingroup iface_zxdg_decoration_manager_v1 + * + * Destroy the decoration manager. This doesn't destroy objects created + * with the manager. + */ +static inline void +zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1, + ZXDG_DECORATION_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_zxdg_decoration_manager_v1 + * + * Create a new decoration object associated with the given toplevel. + * + * For objects of version 1, creating an xdg_toplevel_decoration from an + * xdg_toplevel which has a buffer attached or committed is a client + * error, and any attempts by a client to attach or manipulate a buffer + * prior to the first xdg_toplevel_decoration.configure event must also be + * treated as errors. + * + * For objects of version 2 or newer, creating an xdg_toplevel_decoration + * from an xdg_toplevel which has a buffer attached or committed is + * allowed. The initial decoration mode of the surface if a buffer is + * already attached depends on whether a xdg_toplevel_decoration object + * has been associated with the surface or not prior to this request. + * + * If an xdg_toplevel_decoration was associated with the surface, then + * destroyed without a surface commit, the previous decoration mode is + * retained. + * + * If no xdg_toplevel_decoration was associated with the surface prior to + * this request, or if a surface commit has been performed after a previous + * xdg_toplevel_decoration object associated with the surface was + * destroyed, the decoration mode is assumed to be client-side. + */ +static inline struct zxdg_toplevel_decoration_v1 * +zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1, + ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), 0, NULL, toplevel); + + return (struct zxdg_toplevel_decoration_v1 *) id; +} + +#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM +#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM +enum zxdg_toplevel_decoration_v1_error { + /** + * xdg_toplevel has a buffer attached before configure + */ + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0, + /** + * xdg_toplevel already has a decoration object + */ + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1, + /** + * xdg_toplevel destroyed before the decoration object + */ + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2, + /** + * invalid mode + */ + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_INVALID_MODE = 3, +}; +#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */ + +#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM +#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + * window decoration modes + * + * These values describe window decoration modes. + */ +enum zxdg_toplevel_decoration_v1_mode { + /** + * no server-side window decoration + */ + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1, + /** + * server-side window decoration + */ + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2, +}; +#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */ + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + * @struct zxdg_toplevel_decoration_v1_listener + */ +struct zxdg_toplevel_decoration_v1_listener { + /** + * notify a decoration mode change + * + * The configure event configures the effective decoration mode. + * The configured state should not be applied immediately. Clients + * must send an ack_configure in response to this event. See + * xdg_surface.configure and xdg_surface.ack_configure for details. + * + * A configure event can be sent at any time. The specified mode + * must be obeyed by the client. + * @param mode the decoration mode + */ + void (*configure)(void *data, + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + uint32_t mode); +}; + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + */ +static inline int +zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + const struct zxdg_toplevel_decoration_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1, + (void (**)(void)) listener, data); +} + +#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0 +#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1 +#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2 + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + */ +#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + */ +#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + */ +#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1 +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + */ +#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1 + +/** @ingroup iface_zxdg_toplevel_decoration_v1 */ +static inline void +zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data); +} + +/** @ingroup iface_zxdg_toplevel_decoration_v1 */ +static inline void * +zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1); +} + +static inline uint32_t +zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1); +} + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + * + * Switch back to a mode without any server-side decorations at the next + * commit, unless a new xdg_toplevel_decoration is created for the surface + * first. + */ +static inline void +zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, + ZXDG_TOPLEVEL_DECORATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + * + * Set the toplevel surface decoration mode. This informs the compositor + * that the client prefers the provided decoration mode. + * + * After requesting a decoration mode, the compositor will respond by + * emitting an xdg_surface.configure event. The client should then update + * its content, drawing it without decorations if the received mode is + * server-side decorations. The client must also acknowledge the configure + * when committing the new content (see xdg_surface.ack_configure). + * + * The compositor can decide not to use the client's mode and enforce a + * different mode instead. + * + * Clients whose decoration mode depend on the xdg_toplevel state may send + * a set_mode request in response to an xdg_surface.configure event and wait + * for the next xdg_surface.configure event to prevent unwanted state. + * Such clients are responsible for preventing configure loops and must + * make sure not to send multiple successive set_mode requests with the + * same decoration mode. + * + * If an invalid mode is supplied by the client, the invalid_mode protocol + * error is raised by the compositor. + */ +static inline void +zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, + ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0, mode); +} + +/** + * @ingroup iface_zxdg_toplevel_decoration_v1 + * + * Unset the toplevel surface decoration mode. This informs the compositor + * that the client doesn't prefer a particular decoration mode. + * + * This request has the same semantics as set_mode. + */ +static inline void +zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, + ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/wayland/GNUmakefile b/Source/wayland/GNUmakefile index d2803f2d..0022b119 100644 --- a/Source/wayland/GNUmakefile +++ b/Source/wayland/GNUmakefile @@ -41,6 +41,7 @@ wayland_LOCALIZED_RESOURCE_FILES = \ wayland_C_FILES = \ xdg-shell-protocol.c \ wlr-layer-shell-protocol.c \ +xdg-decoration-unstable-v1-protocol.c \ # The Objective-C source files to be compiled wayland_OBJC_FILES = \ diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index 959bba4d..2a8bf687 100755 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -60,6 +60,41 @@ wlconfig->pointer.focus = window; + float sx = wl_fixed_to_double(sx_w); + float sy = wl_fixed_to_double(sy_w); + + /* Track cursor global (output-relative) position so we can infer where + * xdg_toplevel windows actually are on screen. + * + * Layer-shell surfaces have a known global position (we set the margins + * ourselves), so entering one gives us a ground-truth fix. When the + * cursor then enters an xdg_toplevel we subtract the surface-local entry + * point to infer that toplevel's global origin and store it in + * saved_pos_x/y. Both values are in Wayland output coordinates + * (Y increasing downward from the output's top-left corner). */ + if (window->layer_surface) + { + wlconfig->cursor_global_x = window->pos_x + sx; + wlconfig->cursor_global_y = window->pos_y + sy; + wlconfig->cursor_global_valid = YES; + } + else if (window->toplevel) + { + if (wlconfig->cursor_global_valid) + { + window->saved_pos_x = wlconfig->cursor_global_x - sx; + window->saved_pos_y = wlconfig->cursor_global_y - sy; + window->global_pos_known = YES; + } + /* Keep cursor_global current while traversing toplevels. */ + if (window->global_pos_known) + { + wlconfig->cursor_global_x = window->saved_pos_x + sx; + wlconfig->cursor_global_y = window->saved_pos_y + sy; + wlconfig->cursor_global_valid = YES; + } + } + if (wlconfig->pointer.captured) { return; @@ -67,12 +102,8 @@ [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired]; - NSDebugLog(@"[%d] pointer_handle_enter",window->window_id); - float sx = wl_fixed_to_double(sx_w); - float sy = wl_fixed_to_double(sy_w); - if (window && wlconfig->pointer.serial) { @@ -185,6 +216,27 @@ wlconfig->pointer.last_timestamp = (NSTimeInterval) time / 1000.0; + /* Keep cursor_global current on every motion so that the position is + * fresh at the moment a context menu is about to be opened. Use + * pointer.focus (the actual Wayland surface receiving events) not the + * potentially-captured focused_window. */ + { + struct window *tw = wlconfig->pointer.focus; + if (tw) + { + if (tw->layer_surface) + { + wlconfig->cursor_global_x = tw->pos_x + sx; + wlconfig->cursor_global_y = tw->pos_y + sy; + wlconfig->cursor_global_valid = YES; + } + else if (tw->global_pos_known) + { + wlconfig->cursor_global_x = tw->saved_pos_x + sx; + wlconfig->cursor_global_y = tw->saved_pos_y + sy; + } + } + } [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired]; @@ -284,49 +336,41 @@ { wlconfig->pointer.button = button; if (window->toplevel) - { - // if the window is a toplevel we check if the event is for resizing or - // moving the window these actions are delegated to the compositor and - // therefore we skip forwarding the events to the NSWindow / NSView - - NSWindow *nswindow = GSWindowWithNumber(window->window_id); - if (nswindow != nil) - { - GSStandardWindowDecorationView * wd = [nswindow _windowView]; - - if ([wd pointInTitleBarRect:eventLocation]) - { - xdg_toplevel_move(window->toplevel, wlconfig->seat, serial); - window->moving = YES; - return; - } - if ([wd pointInResizeBarRect:eventLocation]) - { - GSResizeEdgeMode mode = [wd resizeModeForPoint:eventLocation]; - - uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; - - if (mode == GSResizeEdgeBottomLeftMode) - { - edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; - } - else if (mode == GSResizeEdgeBottomRightMode) - { - edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; - } - else if (mode == GSResizeEdgeBottomMode) - { - edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; - } - - xdg_toplevel_resize(window->toplevel, wlconfig->seat, serial, - edges); - window->resizing = YES; - return; - } - } // endif nswindow != nil - } // endif window->toplevel - + { + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + + if (nswindow != nil + && ![(WaylandServer *)GSCurrentServer() handlesWindowDecorations]) + { + GSStandardWindowDecorationView *wd = [nswindow _windowView]; + + if ([wd pointInTitleBarRect:eventLocation]) + { + xdg_toplevel_move(window->toplevel, wlconfig->seat, serial); + window->moving = YES; + return; + } + + if ([wd pointInResizeBarRect:eventLocation]) + { + GSResizeEdgeMode mode = [wd resizeModeForPoint:eventLocation]; + + uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + + if (mode == GSResizeEdgeBottomLeftMode) + edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + else if (mode == GSResizeEdgeBottomRightMode) + edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + else if (mode == GSResizeEdgeBottomMode) + edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + + xdg_toplevel_resize(window->toplevel, wlconfig->seat, serial, + edges); + window->resizing = YES; + return; + } + } + } if (button == wlconfig->pointer.last_click_button && time - wlconfig->pointer.last_click_time < DOUBLECLICK_DELAY && fabsf(wlconfig->pointer.x - wlconfig->pointer.last_click_x) @@ -850,4 +894,4 @@ - (void)mouseOptionsChanged:(NSNotification *)aNotif wlconfig->mouse_scroll_multiplier = 1.0f; } } -@end \ No newline at end of file +@end diff --git a/Source/wayland/WaylandServer+Xdgshell.m b/Source/wayland/WaylandServer+Xdgshell.m index 3bda189b..9d293e8a 100644 --- a/Source/wayland/WaylandServer+Xdgshell.m +++ b/Source/wayland/WaylandServer+Xdgshell.m @@ -29,6 +29,8 @@ #include #include #include +#include +#include static void xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, @@ -41,6 +43,7 @@ if (window->terminated == YES) { NSDebugLog(@"deleting window win=%d", window->window_id); + wl_list_remove(&window->link); free(window); return; } @@ -115,15 +118,73 @@ NSDebugLog(@"[%d] xdg_popup_configure [%d,%d %dx%d]", window->window_id, x, y, width, height); + + /* The compositor reports the popup's actual position (in parent surface + * coords) and size. Sync window->pos_x/pos_y so GNUstep's NSWindow frame + * matches where the compositor actually placed the popup. Without this, + * locationForSubmenu: uses a stale frame and submenus appear offset. */ + if (window->parent_id) + { + struct window *parent + = get_window_with_id(window->wlconfig, window->parent_id); + if (parent && window->output) + { + window->pos_x = parent->pos_x + x; + window->pos_y = parent->pos_y + y; + + if (width > 0) + window->width = width; + if (height > 0) + window->height = height; + + NSWindow *nswin = GSWindowWithNumber(window->window_id); + if (nswin) + { + NSEvent *ev = [NSEvent + otherEventWithType:NSAppKitDefined + location:NSMakePoint(0, 0) + modifierFlags:0 + timestamp:0 + windowNumber:window->window_id + context:GSCurrentContext() + subtype:GSAppKitWindowMoved + data1:window->pos_x + data2:WaylandToNS(window, window->pos_y)]; + [nswin sendEvent:ev]; + } + } + } } static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) { struct window *window = data; - window->terminated = YES; + + /* Destroy the popup role (required by the protocol after popup_done), + * then destroy the xdg_surface and wl_surface in the correct order. + * NULL all pointers immediately so that destroySurfaceRole: (called + * when GNUstep eventually orders the window out) does not attempt a + * second destroy on already-freed Wayland proxy objects. A double + * destroy causes a Wayland protocol error, which disconnects the + * compositor session and closes every window including modal dialogs. */ xdg_popup_destroy(xdg_popup); - wl_surface_destroy(window->surface); + window->popup = NULL; + + if (window->xdg_surface) + { + xdg_surface_destroy(window->xdg_surface); + window->xdg_surface = NULL; + } + + if (window->surface) + { + wl_surface_destroy(window->surface); + window->surface = NULL; + } + + window->configured = NO; + window->terminated = YES; } static void diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 2ab24c52..224827f5 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -69,6 +69,10 @@ extern const struct xdg_popup_listener xdg_popup_listener; +extern const struct zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener; + +static BOOL handlesWindowDecorations = NO; + static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) @@ -136,6 +140,12 @@ = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); NSDebugLog(@"wayland: found subcompositor interface"); } + else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) + { + wlconfig->decoration_manager + = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); + NSDebugLog(@"wayland: found xdg-decoration-manager interface"); + } } static void handle_global_remove(void *data, struct wl_registry *registry, @@ -223,6 +233,23 @@ - (id)_initWaylandContext @"compositor must support the stable XDG Shell protocol"]; } + /* Determine decoration mode. Default: use SSD if the compositor supports it. + The user can override with GSBackHandlesWindowDecorations in defaults. */ + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + if ([defs objectForKey: @"GSBackHandlesWindowDecorations"]) + { + handlesWindowDecorations + = [defs boolForKey: @"GSBackHandlesWindowDecorations"]; + } + else + { + handlesWindowDecorations = (wlconfig->decoration_manager != NULL); + } + NSDebugLog(@"wayland: handlesWindowDecorations=%s (decoration_manager=%s)", + handlesWindowDecorations ? "YES" : "NO", + wlconfig->decoration_manager ? "available" : "not available"); + + inputServer = [[WaylandInputServer allocWithZone: [self zone]] initWithDelegate: nil name: @"WaylandInput"]; @@ -273,13 +300,18 @@ - (id)initWithAttributes:(NSDictionary *)info - (void)dealloc { NSDebugLog(@"Destroying Wayland Server"); + if (wlconfig->decoration_manager) + { + zxdg_decoration_manager_v1_destroy(wlconfig->decoration_manager); + wlconfig->decoration_manager = NULL; + } DESTROY(inputServer); [super dealloc]; } - (BOOL)handlesWindowDecorations { - return NO; + return handlesWindowDecorations; } - (void)restrictWindow:(int)win toImage:(NSImage *)image @@ -483,6 +515,7 @@ - (int)window:(NSRect) window->resizing = NO; window->ignoreMouse = NO; window->usesOpenGL = NO; + window->global_pos_known = NO; // FIXME is this needed? if (window->pos_x < 0) @@ -776,8 +809,35 @@ - (void)setParentWindow:(int)parentWin forChildWindow:(int)childWin NSDebugLog(@"setParentWindow: parent=%d child=%d", parentWin, childWin); struct window *parent = get_window_with_id(wlconfig, parentWin); struct window *child = get_window_with_id(wlconfig, childWin); + if (!parent || !child) + { + return; + } - [self createPopupShell:child withParentShell:parent]; + if (child->level == NSPopUpMenuWindowLevel) + { + /* NSPopUpMenuWindowLevel is a NOOP in createSurfaceShell; create the + * xdg_popup here so the compositor handles grab and dismiss. */ + [self createPopupShell:child withParentShell:parent]; + return; + } + + if (child->level == NSSubmenuWindowLevel) + { + /* Submenus are created by createSubMenuShell (layer shell preferred). + * Only fall back to xdg_popup if no role has been assigned yet. */ + if (![self windowSurfaceHasRole:child]) + [self createPopupShell:child withParentShell:parent]; + return; + } + + /* Panels, dialogs, and other transient toplevels: record the parent and + * call xdg_toplevel_set_parent. Never use xdg_popup here — xdg_popup + * auto-dismisses when pointer focus leaves the surface, which closes + * dialogs as soon as the mouse moves to another window. */ + child->parent_id = parentWin; + if (child->toplevel && parent->toplevel) + xdg_toplevel_set_parent(child->toplevel, parent->toplevel); } - (void)setwindowlevel:(int)level:(int)win @@ -900,7 +960,59 @@ - (void)createSurfaceShell:(struct window *)window break; case NSPopUpMenuWindowLevel: NSDebugLog(@"[%d] NSPopUpMenuWindowLevel", win); - [self createPopup:window]; + /* Use layer shell so the menu can extend beyond the parent window's + * surface without losing pointer events. xdg_popup clips event + * delivery to the parent surface bounds on many compositors, which + * breaks tracking when the menu extends outside the parent window. + * NSPopUpButton popups go through setParentWindow:forChildWindow: + * before orderwindow and already have a surface role by the time + * createSurfaceShell is reached, so this path is only taken for + * right-click context menus (displayTransient). + * + * Before creating the surface, translate window->pos_x/y from + * GNUstep's assumed coordinates into accurate output-relative + * coordinates by adding the delta between the key window's inferred + * global origin (saved_pos_x/y, tracked via cursor enter events) and + * GNUstep's assumed origin (pos_x/y). Then notify GNUstep of the + * corrected frame so that locationForSubmenu: and other screen- + * coordinate queries work correctly for any submenus. */ + if (wlconfig->layer_shell) + { + NSWindow *keyWin = [NSApp keyWindow]; + if (keyWin && (int)[keyWin windowNumber] != win) + { + struct window *kwin = + get_window_with_id(wlconfig, (int)[keyWin windowNumber]); + if (kwin && kwin->global_pos_known) + { + float dx = kwin->saved_pos_x - kwin->pos_x; + float dy = kwin->saved_pos_y - kwin->pos_y; + window->pos_x += dx; + window->pos_y += dy; + NSWindow *nswin = GSWindowWithNumber(win); + if (nswin) + { + NSEvent *ev = + [NSEvent otherEventWithType:NSAppKitDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:win + context:GSCurrentContext() + subtype:GSAppKitWindowMoved + data1:window->pos_x + data2:WaylandToNS(window, + window->pos_y)]; + [nswin sendEvent:ev]; + } + } + } + [self createLayerShell:window + withLayerType:ZWLR_LAYER_SHELL_V1_LAYER_TOP + withNamespace:@"gnustep-popup"]; + } + else + [self createTopLevel:window]; break; case NSScreenSaverWindowLevel: NSDebugLog(@"[%d] NSScreenSaverWindowLevel", win); @@ -965,6 +1077,26 @@ - (void)createTopLevel:(struct window *)window xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); + /* Apply transient-parent relationship. + * + * Priority 1: explicit parent from setParentWindow: (sheets, child panels). + * Priority 2: implicit parent for modal panels — set to the keyboard-focused + * window so the Ambrosia compositor can protect the dialog from focus + * steals even when no explicit addChildWindow: call was made (e.g. the + * standalone [NSOpenPanel runModal] case). */ + { + struct window *parent = NULL; + if (window->parent_id) + parent = get_window_with_id(wlconfig, window->parent_id); + else if (window->level == NSModalPanelWindowLevel + && wlconfig->keyboard_focus != NULL + && wlconfig->keyboard_focus != window) + parent = wlconfig->keyboard_focus; + + if (parent && parent->toplevel) + xdg_toplevel_set_parent(window->toplevel, parent->toplevel); + } + xdg_surface_set_window_geometry(window->xdg_surface, 0, 0, window->width, window->height); } @@ -1069,40 +1201,33 @@ - (void)createSubMenuShell:(struct window *)window if ([self windowSurfaceHasRole:window]) { - // if the role is already assigned skip return; } + /* Use layer shell so the submenu can extend beyond any parent surface + * without losing pointer events. xdg_popup clips event delivery to + * the parent surface bounds, which breaks tracking when the submenu + * extends to the right or below the parent menu's surface. */ if (wlconfig->layer_shell) { - // the preferred way to create a submenu is to use an overlay [self createLayerShell:window - withLayerType:ZWLR_LAYER_SHELL_V1_LAYER_TOP - withNamespace:@"gnustep-submenu"]; + withLayerType:ZWLR_LAYER_SHELL_V1_LAYER_TOP + withNamespace:@"gnustep-submenu"]; return; } - else - { - NSDebugLog(@"layer shell not supported, fallback to xdg popup"); - } - // if the layer shell is not available then we use the xdg popup - // for that we need a parent toplevel window + /* No layer shell: fall back to xdg_popup under the nearest parent + * menu surface. */ + NSDebugLog(@"layer shell not supported, fallback to xdg popup"); struct window *rootwindow = window; struct window *parentwindow = rootwindow; - while (rootwindow = [self getSuperMenuWindow:parentwindow]) + while ((rootwindow = [self getSuperMenuWindow:parentwindow])) { parentwindow = rootwindow; } if (!parentwindow) - { - return; - } + return; NSDebugLog(@"new popup: %d parent id: %d", win, parentwindow->window_id); - NSDebugLog(@"parent: %d [%f,%f %fx%f]", parentwindow->window_id, - parentwindow->pos_x, parentwindow->pos_y, parentwindow->width, - parentwindow->height); - [self createPopupShell:window withParentShell:parentwindow]; } @@ -1116,9 +1241,10 @@ - (void)createPopupShell:(struct window *)child { NSDebugLog(@"createPopupShell"); - if (parent->toplevel == NULL && parent->layer_surface == NULL) + if (parent->toplevel == NULL && parent->layer_surface == NULL + && parent->popup == NULL) { - NSDebugLog(@"parent surface %d is not toplevel", parent->window_id); + NSDebugLog(@"parent surface %d has no surface role", parent->window_id); return; } if ([self windowSurfaceHasRole:child]) @@ -1126,6 +1252,8 @@ - (void)createPopupShell:(struct window *)child [self destroySurfaceRole:child]; } + child->parent_id = parent->window_id; + child->surface = wl_compositor_create_surface(wlconfig->compositor); wl_surface_set_user_data(child->surface, child); @@ -1153,6 +1281,12 @@ - (void)createPopupShell:(struct window *)child child->popup = xdg_surface_get_popup(child->xdg_surface, parent->xdg_surface, positioner); + /* Grab pointer/keyboard so the compositor auto-dismisses the popup on + * outside clicks and delivers events to it. Only grab when we have a + * valid event serial (i.e. the popup was triggered by user input). */ + if (wlconfig->event_serial) + xdg_popup_grab(child->popup, wlconfig->seat, wlconfig->event_serial); + if (parent->layer_surface) { zwlr_layer_surface_v1_get_popup(parent->layer_surface, child->popup); @@ -1204,6 +1338,7 @@ - (void)destroySurfaceRole:(struct window *)window [window->wcs destroySurface]; } window->configured = NO; + window->buffer_needs_attach = YES; } - (void)destroyWindowShell:(struct window *)window diff --git a/Source/wayland/protocols/xdg-decoration-unstable-v1.xml b/Source/wayland/protocols/xdg-decoration-unstable-v1.xml new file mode 100644 index 00000000..023589ad --- /dev/null +++ b/Source/wayland/protocols/xdg-decoration-unstable-v1.xml @@ -0,0 +1,176 @@ + + + + Copyright © 2018 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface allows a compositor to announce support for server-side + decorations. + + A window decoration is a set of window controls as deemed appropriate by + the party managing them, such as user interface components used to move, + resize and change a window's state. + + A client can use this protocol to request being decorated by a supporting + compositor. + + If compositor and client do not negotiate the use of a server-side + decoration using this protocol, clients continue to self-decorate as they + see fit. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Destroy the decoration manager. This doesn't destroy objects created + with the manager. + + + + + + Create a new decoration object associated with the given toplevel. + + For objects of version 1, creating an xdg_toplevel_decoration from an + xdg_toplevel which has a buffer attached or committed is a client + error, and any attempts by a client to attach or manipulate a buffer + prior to the first xdg_toplevel_decoration.configure event must also be + treated as errors. + + For objects of version 2 or newer, creating an xdg_toplevel_decoration + from an xdg_toplevel which has a buffer attached or committed is + allowed. The initial decoration mode of the surface if a buffer is + already attached depends on whether a xdg_toplevel_decoration object + has been associated with the surface or not prior to this request. + + If an xdg_toplevel_decoration was associated with the surface, then + destroyed without a surface commit, the previous decoration mode is + retained. + + If no xdg_toplevel_decoration was associated with the surface prior to + this request, or if a surface commit has been performed after a previous + xdg_toplevel_decoration object associated with the surface was + destroyed, the decoration mode is assumed to be client-side. + + + + + + + + + The decoration object allows the compositor to toggle server-side window + decorations for a toplevel surface. The client can request to switch to + another mode. + + The xdg_toplevel_decoration object must be destroyed before its + xdg_toplevel. + + + + + + + + + + + + Switch back to a mode without any server-side decorations at the next + commit, unless a new xdg_toplevel_decoration is created for the surface + first. + + + + + + These values describe window decoration modes. + + + + + + + + Set the toplevel surface decoration mode. This informs the compositor + that the client prefers the provided decoration mode. + + After requesting a decoration mode, the compositor will respond by + emitting an xdg_surface.configure event. The client should then update + its content, drawing it without decorations if the received mode is + server-side decorations. The client must also acknowledge the configure + when committing the new content (see xdg_surface.ack_configure). + + The compositor can decide not to use the client's mode and enforce a + different mode instead. + + Clients whose decoration mode depend on the xdg_toplevel state may send + a set_mode request in response to an xdg_surface.configure event and wait + for the next xdg_surface.configure event to prevent unwanted state. + Such clients are responsible for preventing configure loops and must + make sure not to send multiple successive set_mode requests with the + same decoration mode. + + If an invalid mode is supplied by the client, the invalid_mode protocol + error is raised by the compositor. + + + + + + + Unset the toplevel surface decoration mode. This informs the compositor + that the client doesn't prefer a particular decoration mode. + + This request has the same semantics as set_mode. + + + + + + The configure event configures the effective decoration mode. The + configured state should not be applied immediately. Clients must send an + ack_configure in response to this event. See xdg_surface.configure and + xdg_surface.ack_configure for details. + + A configure event can be sent at any time. The specified mode must be + obeyed by the client. + + + + + diff --git a/Source/wayland/xdg-decoration-unstable-v1-protocol.c b/Source/wayland/xdg-decoration-unstable-v1-protocol.c new file mode 100644 index 00000000..cfac2d1b --- /dev/null +++ b/Source/wayland/xdg-decoration-unstable-v1-protocol.c @@ -0,0 +1,73 @@ +/* + * Copyright © 2018 Simon Ser + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface xdg_toplevel_interface; +extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; + +static const struct wl_interface *xdg_decoration_unstable_v1_types[] = { + NULL, + &zxdg_toplevel_decoration_v1_interface, + &xdg_toplevel_interface, +}; + +static const struct wl_message zxdg_decoration_manager_v1_requests[] = { + { "destroy", "", xdg_decoration_unstable_v1_types + 0 }, + { "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 }, +}; + +WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = { + "zxdg_decoration_manager_v1", 2, + 2, zxdg_decoration_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = { + { "destroy", "", xdg_decoration_unstable_v1_types + 0 }, + { "set_mode", "u", xdg_decoration_unstable_v1_types + 0 }, + { "unset_mode", "", xdg_decoration_unstable_v1_types + 0 }, +}; + +static const struct wl_message zxdg_toplevel_decoration_v1_events[] = { + { "configure", "u", xdg_decoration_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = { + "zxdg_toplevel_decoration_v1", 2, + 3, zxdg_toplevel_decoration_v1_requests, + 1, zxdg_toplevel_decoration_v1_events, +}; From bce607742f12c21bf4b78c7f671d9dde4a72b6da Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 01:29:19 +1000 Subject: [PATCH 09/24] Add phased implementation plan for Wayland feature parity --- wayland_feature_implementation_plan.md | 173 +++++++++++++++++++++++++ wayland_x11_feature_gap_report.md | 56 ++++++++ 2 files changed, 229 insertions(+) create mode 100644 wayland_feature_implementation_plan.md create mode 100644 wayland_x11_feature_gap_report.md diff --git a/wayland_feature_implementation_plan.md b/wayland_feature_implementation_plan.md new file mode 100644 index 00000000..c3a3f3cd --- /dev/null +++ b/wayland_feature_implementation_plan.md @@ -0,0 +1,173 @@ +# Wayland Backend Feature Implementation Plan (vs X11 parity) + +## Scope +This plan turns the identified feature gaps into an execution roadmap, ordered by user impact and architectural dependencies. + +## Milestone 0: Baseline and instrumentation (1 week) + +### Goals +- Make regressions visible before feature work starts. + +### Tasks +1. Add targeted debug categories for Wayland DnD, IME, pointer buttons, scroll axes, output changes. +2. Add an integration harness script to run basic backend smoke tests under a Wayland compositor (Weston/headless where possible). +3. Capture current behavior snapshots for: + - local drag/drop + - external drag/drop + - IME composition + - extra mouse buttons + - touchpad/continuous scroll + - output hotplug/scale/geometry change + +### Exit criteria +- Repro steps and logs are documented for every missing feature bucket. + +--- + +## Milestone 1: Inter-process DnD on Wayland (`wl_data_device`) (2–3 weeks) + +### Why first +External DnD is a major UX gap and is already explicitly stubbed in `WaylandDragView`. + +### Tasks +1. **Protocol plumbing** + - Add `wl_data_device_manager`, `wl_data_device`, `wl_data_source`, `wl_data_offer` objects to `WaylandConfig` lifecycle. + - Bind globals in registry handler, add listeners, and teardown safely. +2. **Outbound drag path** + - Implement source offers for pasteboard MIME types. + - Wire drag enter/motion/leave/drop events to existing `GSDragView`/AppKit event flow. +3. **Inbound drag path** + - Accept offers, map MIME types to pasteboard types, handle selection reads over FDs. +4. **Action negotiation** + - Map Wayland dnd actions (`copy/move/ask`) to `NSDragOperation` consistently with X11 behavior. +5. **Error handling** + - Handle canceled drags, destroyed offers, and compositor-denied serials. + +### Files primarily touched +- `Source/wayland/WaylandDragView.m` +- `Source/wayland/WaylandServer.m` (registry/globals) +- `Headers/wayland/WaylandServer.h` (config structs) + +### Exit criteria +- Drag from GNUstep app to external app and vice versa works for text and URI list payloads. +- `postDragEvent` and `sendExternalEvent` no longer log “not implemented” for inter-process cases. + +--- + +## Milestone 2: IME/preedit/status support parity path (2 weeks) + +### Goals +Bring Wayland input method behavior closer to X11 XIM-visible capabilities used by AppKit text input flows. + +### Tasks +1. Introduce Wayland text-input integration strategy: + - Prefer `text-input-v3` (or compositor-supported equivalent) and optional input-method protocols. +2. Implement input method state in `WaylandInputServer`: + - Preedit string lifecycle + - Cursor/spot location updates + - Status/preedit rectangles and setters +3. Feed composed text and commit/cancel events into existing key/text dispatch pipeline. +4. Keep fallback behavior when compositor lacks protocol support. + +### Files primarily touched +- `Source/wayland/WaylandInputServer.m` +- `Source/wayland/WaylandServer+Keyboard.m` +- `Headers/wayland/WaylandInputServer.h` + +### Exit criteria +- `statusArea/preeditArea/preeditSpot` and setters return meaningful values when protocol is available. +- Basic composition (e.g., dead keys/CJK IME) works in NSText-based controls. + +--- + +## Milestone 3: Pointer button completeness + wheel/scroll semantics (1–2 weeks) + +### Goals +Close input parity gaps affecting advanced mice and touchpads. + +### Tasks +1. Map BTN_SIDE/BTN_EXTRA/BTN_FORWARD/BTN_BACK to appropriate `NSOtherMouse*` events and button numbers. +2. Implement `pointer_handle_axis_discrete`, `pointer_handle_frame`, and `pointer_handle_axis_stop`: + - group axis events per frame + - include discrete step data when available + - emit momentum/phase semantics where AppKit expects them +3. Normalize button mapping/documentation versus X11 `XGetPointerMapping` assumptions. + +### Files primarily touched +- `Source/wayland/WaylandServer+Cursor.m` + +### Exit criteria +- Side buttons generate usable app events. +- Touchpad and wheel scrolling feel consistent and no longer rely on TODO placeholders. + +--- + +## Milestone 4: Output change handling and monitor reconfiguration (1–2 weeks) + +### Goals +Implement dynamic monitor behavior expected from mature backend operation. + +### Tasks +1. Implement output configure callback path currently marked TODO. +2. Add runtime reactions for: + - output add/remove + - geometry/scale changes + - window reposition/reclamp on output change +3. Audit coordinate transforms across output scale and transform states. + +### Files primarily touched +- `Source/wayland/WaylandServer+Output.m` +- `Source/wayland/WaylandServer.m` +- `Headers/wayland/WaylandServer.h` + +### Exit criteria +- Windows remain usable after output change events (hotplug, scale change). +- Screen geometry reported to AppKit updates correctly. + +--- + +## Milestone 5: Stability and rendering hardening (ongoing, parallel) + +### Goals +Address known “incomplete/broken” operational issues and reduce compositor hangs/freeze conditions. + +### Tasks +1. Audit buffer lifecycle (attach/damage/commit/release ordering). +2. Add synchronization guards around surface destruction and resize churn. +3. Validate Cairo surface blit ordering to avoid stray backing-surface visibility. +4. Stress-test event loop responsiveness under frequent input and redraw. + +### Exit criteria +- No reproducible freeze in sustained interaction test. +- No random backing-surface artifacts in compositor during normal usage. + +--- + +## Cross-cutting requirements + +1. **Feature flags** + - Gate new protocol-dependent behavior by runtime detection and defaults. +2. **Compatibility matrix** + - Track compositor support (Weston, Mutter, KWin, wlroots-based). +3. **Testing** + - Add automated tests where feasible; otherwise scripted manual test playbooks. +4. **Documentation** + - Update `Source/wayland/README.md` milestone-by-milestone with current status. + +## Suggested delivery order +1. Milestone 0 baseline +2. Milestone 1 external DnD +3. Milestone 3 pointer/scroll completeness +4. Milestone 2 IME support +5. Milestone 4 output reconfiguration +6. Milestone 5 hardening (continuous) + +## Effort estimate summary +- M0: 1 week +- M1: 2–3 weeks +- M2: 2 weeks +- M3: 1–2 weeks +- M4: 1–2 weeks +- M5: ongoing + +Total for first parity wave (M0–M4): ~7–10 weeks for one experienced contributor, faster with parallel owners. diff --git a/wayland_x11_feature_gap_report.md b/wayland_x11_feature_gap_report.md new file mode 100644 index 00000000..d17b7000 --- /dev/null +++ b/wayland_x11_feature_gap_report.md @@ -0,0 +1,56 @@ +# Wayland vs X11 backend feature-gap report + +This report lists features available in the X11 backend that are currently missing or only stubbed/partial in the Wayland backend. + +## 1) Inter-process drag-and-drop (external DnD) is missing on Wayland + +- Wayland explicitly says inter-process drag via `wl_data_device` is not implemented and only logs for external events. +- X11 implements external DnD message flow (`XdndStatus`, `XdndFinished`, `XdndEnter`, `XdndPosition`, `XdndDrop`, `XdndLeave`) via `xdnd_*` calls. + +Evidence: +- Wayland: `Source/wayland/WaylandDragView.m` (`postDragEvent`, `sendExternalEvent`). +- X11: `Source/x11/XGDragView.m` (`postDragEvent`, `sendExternalEvent`). + +## 2) Input method (IME/XIM-style preedit/status positioning) support is missing on Wayland + +- Wayland input method style returns `nil` and all preedit/status geometry accessors return `NO`. +- X11 has a dedicated XIM input server with style negotiation and IC lifecycle management. + +Evidence: +- Wayland: `Source/wayland/WaylandInputServer.m` (`inputMethodStyle`, `statusArea`, `preeditArea`, `preeditSpot`, `setStatusArea`, `setPreeditArea`, `setPreeditSpot`). +- X11: `Source/x11/XIMInputServer.m` (`ximInit`, `ximStyleInit`, `ximCreateIC` flow). + +## 3) Extended mouse button mapping is incomplete on Wayland + +- Wayland currently maps left/right/middle only and leaves BTN_SIDE/BTN_EXTRA/BTN_FORWARD/BTN_BACK as TODO. +- X11 backend has explicit pointer-mapping commentary/handling path and broader mature event translation. + +Evidence: +- Wayland: `Source/wayland/WaylandServer+Cursor.m` TODO in button switch. +- X11 reference behavior mentioned in same file comments and implemented in mature X11 event code. + +## 4) Advanced scroll semantics are partial on Wayland + +- Wayland pointer callbacks for `frame`, `axis_stop`, and `axis_discrete` are currently empty. +- Comments note missing momentum/trackpad behavior. + +Evidence: +- Wayland: `Source/wayland/WaylandServer+Cursor.m` (`pointer_handle_frame`, `pointer_handle_axis_stop`, `pointer_handle_axis_discrete`, momentum TODO comments). + +## 5) Output reconfiguration callback path is unimplemented on Wayland + +- Wayland output mode handler includes explicit “Should we implement this?” for output configure callback behavior. +- X11 has established monitor/screen management infrastructure (monitor list + RANDR-related members in server). + +Evidence: +- Wayland: `Source/wayland/WaylandServer+Output.m` (`handle_mode` TODO block). +- X11: `Headers/x11/XGServer.h` monitor/randr fields. + +## 6) Overall backend completeness/stability is explicitly below X11 + +- Wayland backend README still declares it "incomplete and broken" and lists unresolved rendering/freezing issues. +- X11 backend is long-standing and feature-complete enough to include broad subsystems (XIM, XDND, RANDR integration points, GLX path, etc.). + +Evidence: +- Wayland: `Source/wayland/README.md`. +- X11: breadth of subsystem files in `Source/x11/` and `Headers/x11/`. From 3b9127e35579f96a878e78082a0868ff2c7f889e Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 01:32:08 +1000 Subject: [PATCH 10/24] Change Wayland compositor in integration harness script Updated the integration harness script to use ambrosia Wayland compositor instead of Weston. --- wayland_feature_implementation_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayland_feature_implementation_plan.md b/wayland_feature_implementation_plan.md index c3a3f3cd..fe27b7df 100644 --- a/wayland_feature_implementation_plan.md +++ b/wayland_feature_implementation_plan.md @@ -10,7 +10,7 @@ This plan turns the identified feature gaps into an execution roadmap, ordered b ### Tasks 1. Add targeted debug categories for Wayland DnD, IME, pointer buttons, scroll axes, output changes. -2. Add an integration harness script to run basic backend smoke tests under a Wayland compositor (Weston/headless where possible). +2. Add an integration harness script to run basic backend smoke tests under ambrosia Wayland compositor. 3. Capture current behavior snapshots for: - local drag/drop - external drag/drop From 82cdb41446b802c0ba2544cd759a61167a1a027b Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 09:44:59 +1000 Subject: [PATCH 11/24] Wayland M0+M1: debug categories, smoke harness, inter-process DnD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone 0 — Baseline instrumentation: - Replace bare NSDebugLog with targeted NSDebugMLLog/NSDebugFLLog categories (WaylandDnD, WaylandIME, WaylandPointer, WaylandScroll, WaylandOutput) across WaylandDragView, WaylandInputServer, WaylandServer+Cursor, WaylandServer+Keyboard, and WaylandServer+Output. - Add default: case logging for unhandled pointer buttons (BTN_SIDE/EXTRA etc.) and stub logging for pointer_handle_frame/axis_stop/axis_discrete. - Add Tools/wayland-smoke-test.sh: integration harness that starts Ambrosia compositor nested in the current Wayland session, discovers its socket from the log, and runs 11 smoke checks covering compositor liveness, protocol globals, source code snapshots for each feature bucket, and log sanity. - Fix pre-existing WaylandCairoShmSurface.m ivar redeclaration build error. Milestone 1 — Inter-process DnD via wl_data_device: - WaylandServer.h: add wl_data_device_manager, wl_data_device, and full DnD state (dnd_source, dnd_offer, MIME list, action, position, target) to WaylandConfig. - WaylandServer.m: bind wl_data_device_manager in registry handler (v≤3), create wl_data_device and attach listener after the initial roundtrip. - WaylandDragView.m: complete implementation — Outbound (GNUstep → external): dragImage: creates wl_data_source, offers all MIME types from the pasteboard, calls wl_data_device_start_drag, then runs GSDragView's event loop; data_source_send writes pasteboard data to the pipe FD; cancelled/dnd_finished post a fake NSLeftMouseUp to exit the loop cleanly. Inbound (external → GNUstep): device_data_offer accumulates MIME types; device_enter accepts the best type, sets wl_data_offer_set_actions (v3), sets up NSDraggingInfo on the shared drag view, and posts GSAppKitDraggingEnter; device_motion posts GSAppKitDraggingUpdate; device_leave posts GSAppKitDraggingExit and destroys the offer; device_drop reads data via pipe, populates NSDragPboard, posts GSAppKitDraggingDrop, and calls wl_data_offer_finish (v3). MIME mapping: text/plain↔NSStringPboardType, text/uri-list↔NSFilenamesPboardType, application/rtf↔NSRTFPboardType, image/tiff↔NSTIFFPboardType, image/png↔NSPNGPboardType. Action mapping: wl copy/move ↔ NSDragOperationCopy/Move. Co-Authored-By: Claude Sonnet 4.6 --- Headers/wayland/WaylandDragView.h | 6 + Headers/wayland/WaylandServer.h | 20 + Source/cairo/WaylandCairoShmSurface.m | 3 - Source/wayland/WaylandDragView.m | 860 ++++++++++++++++++++++-- Source/wayland/WaylandInputServer.m | 8 +- Source/wayland/WaylandServer+Cursor.m | 68 +- Source/wayland/WaylandServer+Keyboard.m | 8 +- Source/wayland/WaylandServer+Output.m | 17 +- Source/wayland/WaylandServer.m | 23 +- Tools/wayland-smoke-test.sh | 404 +++++++++++ 10 files changed, 1310 insertions(+), 107 deletions(-) create mode 100755 Tools/wayland-smoke-test.sh diff --git a/Headers/wayland/WaylandDragView.h b/Headers/wayland/WaylandDragView.h index 61d76118..7eb814bb 100644 --- a/Headers/wayland/WaylandDragView.h +++ b/Headers/wayland/WaylandDragView.h @@ -36,6 +36,12 @@ - (void) updateDragInfoFromEvent: (NSEvent *)event; - (void) resetDragInfo; +/** Set up NSDraggingInfo state for an inbound drag from an external app. + * Called from the wl_data_device.enter C callback before posting + * GSAppKitDraggingEnter to the target window. */ +- (void) setupInboundDragWithPasteboard: (NSPasteboard *)pb + operation: (NSDragOperation)op; + @end #endif /* _WaylandDragView_h_INCLUDE */ diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index ff2ec03b..e63850e8 100644 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -128,6 +128,26 @@ typedef struct _WaylandConfig } xkb; int modifiers; + /* wl_data_device — selection and drag-and-drop */ + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; + int data_device_manager_version; + + /* DnD outbound: we are the drag source */ + struct wl_data_source *dnd_source; /* NULL when no outbound drag is active */ + + /* DnD inbound: pending/current offer from an external app */ + struct wl_data_offer *dnd_offer; + char **dnd_offer_mimes; /* strdup'd, NULL when empty */ + int dnd_offer_mime_count; + int dnd_offer_mime_cap; + uint32_t dnd_offer_source_actions; + uint32_t dnd_current_action; + float dnd_x; /* surface-local cursor pos */ + float dnd_y; + struct window *dnd_target; /* GNUstep window under cursor */ + BOOL dnd_incoming; /* YES between enter and leave/drop */ + } WaylandConfig; struct output diff --git a/Source/cairo/WaylandCairoShmSurface.m b/Source/cairo/WaylandCairoShmSurface.m index 712816e2..40a577fa 100644 --- a/Source/cairo/WaylandCairoShmSurface.m +++ b/Source/cairo/WaylandCairoShmSurface.m @@ -172,9 +172,6 @@ } @implementation WaylandCairoShmSurface -{ - struct pool_buffer *pbuffer; -} - (id)initWithDevice:(void *)device { struct window *window = (struct window *) device; diff --git a/Source/wayland/WaylandDragView.m b/Source/wayland/WaylandDragView.m index ebc62b0c..ccb59b48 100644 --- a/Source/wayland/WaylandDragView.m +++ b/Source/wayland/WaylandDragView.m @@ -22,16 +22,22 @@ */ #include +#include #include #include #include +#include #include #include #include #include #include +#include +#include +#include + #include "wayland/WaylandServer.h" #include "wayland/WaylandDragView.h" @@ -48,25 +54,642 @@ - (WaylandConfig *) wlconfig @end -/* Lightweight NSWindow subclass used to hold the GSDragView content. - We never actually show this window - the drag icon is rendered on the - Wayland cursor surface instead, so it follows the pointer automatically. - The window must still exist for GSDragView's internal event handling. */ -@interface WaylandRawWindow : NSWindow -@end +/* ── MIME ↔ pasteboard type mapping ──────────────────────────────────────── */ -@implementation WaylandRawWindow +static const struct { const char *mime; const char *pboard; } kMimeMap[] = { + { "text/plain;charset=utf-8", "NSStringPboardType" }, + { "text/plain", "NSStringPboardType" }, + { "text/uri-list", "NSFilenamesPboardType" }, + { "application/rtf", "NSRTFPboardType" }, + { "image/tiff", "NSTIFFPboardType" }, + { "image/png", "NSPNGPboardType" }, + { NULL, NULL } +}; + +static const char * +mime_for_pboard_type(NSString *pt) +{ + if ([pt isEqual: @"NSStringPboardType"] || [pt isEqual: NSPasteboardTypeString]) + return "text/plain;charset=utf-8"; + if ([pt isEqual: @"NSFilenamesPboardType"]) + return "text/uri-list"; + if ([pt isEqual: @"NSRTFPboardType"]) + return "application/rtf"; + if ([pt isEqual: @"NSTIFFPboardType"]) + return "image/tiff"; + if ([pt isEqual: @"NSPNGPboardType"]) + return "image/png"; + return NULL; +} + +static NSString * +pboard_type_for_mime(const char *mime) +{ + for (int i = 0; kMimeMap[i].mime; i++) + if (strcasecmp(mime, kMimeMap[i].mime) == 0) + return [NSString stringWithUTF8String: kMimeMap[i].pboard]; + return nil; +} + + +/* ── Action mapping ──────────────────────────────────────────────────────── */ + +static uint32_t +ns_op_to_wl_actions(NSDragOperation op) +{ + uint32_t a = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (op & NSDragOperationCopy) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (op & NSDragOperationMove) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + if (op & NSDragOperationDelete) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + if (op & NSDragOperationGeneric) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + return a ? a : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; +} + +static NSDragOperation +wl_action_to_ns(uint32_t action) +{ + switch (action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: return NSDragOperationMove; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + default: return NSDragOperationCopy; + } +} + + +/* ── Inbound offer MIME helpers ───────────────────────────────────────────── */ + +static void +dnd_offer_mimes_free(WaylandConfig *wlconfig) +{ + if (wlconfig->dnd_offer_mimes) + { + for (int i = 0; i < wlconfig->dnd_offer_mime_count; i++) + free(wlconfig->dnd_offer_mimes[i]); + free(wlconfig->dnd_offer_mimes); + wlconfig->dnd_offer_mimes = NULL; + } + wlconfig->dnd_offer_mime_count = 0; + wlconfig->dnd_offer_mime_cap = 0; +} + +static void +dnd_offer_mime_append(WaylandConfig *wlconfig, const char *mime) +{ + if (wlconfig->dnd_offer_mime_count >= wlconfig->dnd_offer_mime_cap) + { + int cap = wlconfig->dnd_offer_mime_cap ? wlconfig->dnd_offer_mime_cap * 2 : 8; + wlconfig->dnd_offer_mimes = realloc(wlconfig->dnd_offer_mimes, + cap * sizeof(char *)); + wlconfig->dnd_offer_mime_cap = cap; + } + wlconfig->dnd_offer_mimes[wlconfig->dnd_offer_mime_count++] = strdup(mime); +} + +/* Return the first MIME type in the offer that we know how to receive. + Populates *pboardType if non-NULL. Returns NULL if nothing matches. */ +static const char * +best_mime_for_offer(WaylandConfig *wlconfig, NSString **pboardType) +{ + static const char *preferred[] = { + "text/plain;charset=utf-8", "text/plain", "text/uri-list", + "application/rtf", "image/tiff", "image/png", NULL + }; + for (int p = 0; preferred[p]; p++) + for (int i = 0; i < wlconfig->dnd_offer_mime_count; i++) + if (strcasecmp(wlconfig->dnd_offer_mimes[i], preferred[p]) == 0) + { + NSString *pt = pboard_type_for_mime(preferred[p]); + if (pt) + { + if (pboardType) *pboardType = pt; + return preferred[p]; + } + } + return NULL; +} + + +/* ── Read all data from a pipe FD into NSData ─────────────────────────────── */ + +static NSData * +read_fd_to_data(int fd) +{ + size_t cap = 4096, total = 0; + char *buf = malloc(cap); + if (!buf) { close(fd); return nil; } + + ssize_t n; + while ((n = read(fd, buf + total, cap - total)) > 0) + { + total += n; + if (total == cap) + { + cap *= 2; + char *nb = realloc(buf, cap); + if (!nb) { free(buf); close(fd); return nil; } + buf = nb; + } + } + close(fd); + + NSData *d = [NSData dataWithBytes: buf length: total]; + free(buf); + return d; +} + + +/* ── Post a fake NSLeftMouseUp to exit GSDragView's event loop ────────────── */ + +static void +post_fake_mouse_up(void) +{ + NSEvent *ev = + [NSEvent mouseEventWithType: NSLeftMouseUp + location: NSZeroPoint + modifierFlags: 0 + timestamp: [NSDate timeIntervalSinceReferenceDate] + windowNumber: 0 + context: nil + eventNumber: 0 + clickCount: 1 + pressure: 0.0 + buttonNumber: 0 + deltaX: 0 + deltaY: 0 + deltaZ: 0]; + [NSApp postEvent: ev atStart: YES]; +} + + +/* ── wl_data_source listener (outbound drag) ──────────────────────────────── */ + +static void +data_source_target(void *data, struct wl_data_source *source, const char *mime) +{ + NSDebugFLLog(@"WaylandDnD", @"data_source_target: %s", mime ? mime : "(none)"); +} -- (BOOL) canBecomeMainWindow +static void +data_source_send(void *data, struct wl_data_source *source, + const char *mime_type, int32_t fd) { - return NO; + NSDebugFLLog(@"WaylandDnD", @"data_source_send: %s", mime_type); + + WaylandDragView *dv = [WaylandDragView sharedDragView]; + NSPasteboard *pb = [dv draggingPasteboard]; + + if (strcmp(mime_type, "text/plain;charset=utf-8") == 0 + || strcmp(mime_type, "text/plain") == 0) + { + NSString *s = [pb stringForType: NSStringPboardType]; + if (!s) s = [pb stringForType: NSPasteboardTypeString]; + if (s) + { + const char *utf8 = [s UTF8String]; + write(fd, utf8, strlen(utf8)); + } + } + else if (strcmp(mime_type, "text/uri-list") == 0) + { + NSArray *names = [pb propertyListForType: @"NSFilenamesPboardType"]; + if (names) + { + NSMutableString *list = [NSMutableString string]; + for (NSString *path in names) + { + NSURL *url = [NSURL fileURLWithPath: path]; + [list appendFormat: @"%@\r\n", [url absoluteString]]; + } + const char *utf8 = [list UTF8String]; + write(fd, utf8, strlen(utf8)); + } + } + else + { + /* Generic binary fallback: look for a pasteboard type whose MIME matches */ + NSString *pt = pboard_type_for_mime(mime_type); + if (pt) + { + NSData *d = [pb dataForType: pt]; + if (d) write(fd, [d bytes], [d length]); + } + } + + close(fd); +} + +static void +data_source_cancelled(void *data, struct wl_data_source *source) +{ + NSDebugFLLog(@"WaylandDnD", @"data_source_cancelled"); + WaylandConfig *wlconfig = data; + wlconfig->dnd_source = NULL; + post_fake_mouse_up(); +} + +static void +data_source_dnd_drop_performed(void *data, struct wl_data_source *source) +{ + NSDebugFLLog(@"WaylandDnD", @"data_source_dnd_drop_performed"); + /* Wait for dnd_finished before exiting the drag loop. */ +} + +static void +data_source_dnd_finished(void *data, struct wl_data_source *source) +{ + NSDebugFLLog(@"WaylandDnD", @"data_source_dnd_finished"); + WaylandConfig *wlconfig = data; + wlconfig->dnd_source = NULL; + post_fake_mouse_up(); +} + +static void +data_source_action(void *data, struct wl_data_source *source, uint32_t action) +{ + NSDebugFLLog(@"WaylandDnD", @"data_source_action: %u", action); + WaylandConfig *wlconfig = data; + wlconfig->dnd_current_action = action; +} + +static const struct wl_data_source_listener data_source_listener = { + data_source_target, + data_source_send, + data_source_cancelled, + data_source_dnd_drop_performed, + data_source_dnd_finished, + data_source_action, +}; + + +/* ── wl_data_offer listener (inbound MIME accumulation) ──────────────────── */ + +static void +data_offer_offer(void *data, struct wl_data_offer *offer, const char *mime_type) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandDnD", @"data_offer_offer: %s", mime_type); + dnd_offer_mime_append(wlconfig, mime_type); +} + +static void +data_offer_source_actions(void *data, struct wl_data_offer *offer, + uint32_t source_actions) +{ + WaylandConfig *wlconfig = data; + wlconfig->dnd_offer_source_actions = source_actions; + NSDebugFLLog(@"WaylandDnD", @"data_offer_source_actions: 0x%x", source_actions); +} + +static void +data_offer_action(void *data, struct wl_data_offer *offer, uint32_t dnd_action) +{ + WaylandConfig *wlconfig = data; + wlconfig->dnd_current_action = dnd_action; + NSDebugFLLog(@"WaylandDnD", @"data_offer_action: %u", dnd_action); } -- (BOOL) canBecomeKeyWindow +static const struct wl_data_offer_listener data_offer_listener = { + data_offer_offer, + data_offer_source_actions, + data_offer_action, +}; + + +/* ── wl_data_device listener (inbound drag events) ───────────────────────── */ + +static void +device_data_offer(void *data, struct wl_data_device *device, + struct wl_data_offer *offer) { - return NO; + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandDnD", @"device_data_offer: %p", (void *)offer); + + /* Reset MIME list for this new offer; offer_offer callbacks follow. */ + dnd_offer_mimes_free(wlconfig); + wlconfig->dnd_offer = offer; + wlconfig->dnd_offer_source_actions = 0; + wlconfig->dnd_current_action = 0; + wl_data_offer_add_listener(offer, &data_offer_listener, wlconfig); } +static void +device_enter(void *data, struct wl_data_device *device, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t x_fixed, wl_fixed_t y_fixed, + struct wl_data_offer *offer) +{ + WaylandConfig *wlconfig = data; + float x = wl_fixed_to_double(x_fixed); + float y = wl_fixed_to_double(y_fixed); + + wlconfig->dnd_x = x; + wlconfig->dnd_y = y; + wlconfig->dnd_incoming = YES; + wlconfig->event_serial = serial; + + struct window *window = surface ? wl_surface_get_user_data(surface) : NULL; + wlconfig->dnd_target = window; + + NSDebugFLLog(@"WaylandDnD", @"device_enter: win=%d pos=(%g,%g)", + window ? window->window_id : -1, x, y); + + if (!offer || !window) + return; + + /* Find the best MIME type we can receive. */ + NSString *pboardType = nil; + const char *mime = best_mime_for_offer(wlconfig, &pboardType); + + if (mime) + { + wl_data_offer_accept(offer, serial, mime); + + if (wlconfig->data_device_manager_version >= 3) + { + uint32_t myActions = ns_op_to_wl_actions( + NSDragOperationCopy | NSDragOperationMove); + wl_data_offer_set_actions(offer, myActions, + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + } + } + else + { + wl_data_offer_accept(offer, serial, NULL); + } + + /* Set up the shared drag view as NSDraggingInfo for AppKit. */ + WaylandDragView *dv = [WaylandDragView sharedDragView]; + [dv setupInboundDragWithPasteboard: + [NSPasteboard pasteboardWithName: NSDragPboard] + operation: wl_action_to_ns( + wlconfig->dnd_offer_source_actions + ? wlconfig->dnd_offer_source_actions + : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)]; + + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (!nswindow) + return; + + NSPoint nsPos = NSMakePoint(x, window->height - y); + NSEvent *ev = + [NSEvent otherEventWithType: NSAppKitDefined + location: nsPos + modifierFlags: 0 + timestamp: [[NSDate date] timeIntervalSinceReferenceDate] + windowNumber: window->window_id + context: nil + subtype: GSAppKitDraggingEnter + data1: 0 + data2: 0]; + [NSApp postEvent: ev atStart: NO]; +} + +static void +device_leave(void *data, struct wl_data_device *device) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandDnD", @"device_leave"); + + struct window *window = wlconfig->dnd_target; + wlconfig->dnd_incoming = NO; + wlconfig->dnd_target = NULL; + + if (window) + { + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (nswindow) + { + NSEvent *ev = + [NSEvent otherEventWithType: NSAppKitDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: [[NSDate date] timeIntervalSinceReferenceDate] + windowNumber: window->window_id + context: nil + subtype: GSAppKitDraggingExit + data1: 0 + data2: 0]; + [NSApp postEvent: ev atStart: NO]; + } + } + + if (wlconfig->dnd_offer) + { + wl_data_offer_destroy(wlconfig->dnd_offer); + wlconfig->dnd_offer = NULL; + dnd_offer_mimes_free(wlconfig); + } +} + +static void +device_motion(void *data, struct wl_data_device *device, + uint32_t time, wl_fixed_t x_fixed, wl_fixed_t y_fixed) +{ + WaylandConfig *wlconfig = data; + float x = wl_fixed_to_double(x_fixed); + float y = wl_fixed_to_double(y_fixed); + wlconfig->dnd_x = x; + wlconfig->dnd_y = y; + + struct window *window = wlconfig->dnd_target; + if (!window) + return; + + NSDebugFLLog(@"WaylandDnD", @"device_motion: (%g,%g)", x, y); + + /* Keep accepting so the compositor knows we still want the drag. */ + if (wlconfig->dnd_offer) + { + NSString *pt = nil; + const char *mime = best_mime_for_offer(wlconfig, &pt); + if (mime) + wl_data_offer_accept(wlconfig->dnd_offer, wlconfig->event_serial, mime); + } + + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (!nswindow) + return; + + NSPoint nsPos = NSMakePoint(x, window->height - y); + NSEvent *ev = + [NSEvent otherEventWithType: NSAppKitDefined + location: nsPos + modifierFlags: 0 + timestamp: (NSTimeInterval)time / 1000.0 + windowNumber: window->window_id + context: nil + subtype: GSAppKitDraggingUpdate + data1: (NSInteger)NSDragOperationCopy + data2: 0]; + [NSApp postEvent: ev atStart: NO]; +} + +static void +device_drop(void *data, struct wl_data_device *device) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandDnD", @"device_drop"); + + struct window *window = wlconfig->dnd_target; + + if (!window || !wlconfig->dnd_offer) + { + if (wlconfig->dnd_offer) + { + wl_data_offer_destroy(wlconfig->dnd_offer); + wlconfig->dnd_offer = NULL; + dnd_offer_mimes_free(wlconfig); + } + wlconfig->dnd_incoming = NO; + return; + } + + NSString *pboardType = nil; + const char *mime = best_mime_for_offer(wlconfig, &pboardType); + + if (!mime || !pboardType) + { + wl_data_offer_destroy(wlconfig->dnd_offer); + wlconfig->dnd_offer = NULL; + dnd_offer_mimes_free(wlconfig); + wlconfig->dnd_incoming = NO; + return; + } + + /* Receive the data via a pipe. */ + int pipefd[2]; + if (pipe(pipefd) < 0) + { + wl_data_offer_destroy(wlconfig->dnd_offer); + wlconfig->dnd_offer = NULL; + dnd_offer_mimes_free(wlconfig); + wlconfig->dnd_incoming = NO; + return; + } + + wl_data_offer_receive(wlconfig->dnd_offer, mime, pipefd[1]); + close(pipefd[1]); + wl_display_flush(wlconfig->display); + + /* Block-read; the source writes after the flush above. */ + NSData *rawData = read_fd_to_data(pipefd[0]); + + /* Populate the drag pasteboard. */ + NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard]; + [pboard declareTypes: @[pboardType] owner: nil]; + + if ([pboardType isEqual: @"NSStringPboardType"] + || [pboardType isEqual: NSPasteboardTypeString]) + { + NSString *s = [[NSString alloc] + initWithData: rawData encoding: NSUTF8StringEncoding]; + if (!s) + s = [[NSString alloc] + initWithData: rawData encoding: NSISOLatin1StringEncoding]; + if (s) + { + [pboard setString: s forType: pboardType]; + [s release]; + } + } + else if ([pboardType isEqual: @"NSFilenamesPboardType"]) + { + NSString *raw = [[NSString alloc] + initWithData: rawData encoding: NSUTF8StringEncoding]; + NSArray *lines = [raw componentsSeparatedByCharactersInSet: + [NSCharacterSet newlineCharacterSet]]; + NSMutableArray *paths = [NSMutableArray array]; + for (NSString *line in lines) + { + NSString *trimmed = [line stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceCharacterSet]]; + if ([trimmed length] == 0 || [trimmed hasPrefix: @"#"]) + continue; + NSURL *url = [NSURL URLWithString: trimmed]; + if ([url isFileURL]) + [paths addObject: [url path]]; + else if ([trimmed hasPrefix: @"/"]) + [paths addObject: trimmed]; + } + [pboard setPropertyList: paths forType: @"NSFilenamesPboardType"]; + [raw release]; + } + else + { + [pboard setData: rawData forType: pboardType]; + } + + /* Deliver the drop event to the AppKit window. */ + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (nswindow) + { + NSDragOperation op = wl_action_to_ns(wlconfig->dnd_current_action); + NSPoint nsPos = NSMakePoint(wlconfig->dnd_x, window->height - wlconfig->dnd_y); + + NSEvent *ev = + [NSEvent otherEventWithType: NSAppKitDefined + location: nsPos + modifierFlags: 0 + timestamp: [[NSDate date] timeIntervalSinceReferenceDate] + windowNumber: window->window_id + context: nil + subtype: GSAppKitDraggingDrop + data1: (NSInteger)op + data2: 0]; + [NSApp postEvent: ev atStart: NO]; + } + + /* Finish and destroy the offer. */ + if (wlconfig->data_device_manager_version >= 3) + wl_data_offer_finish(wlconfig->dnd_offer); + + wl_data_offer_destroy(wlconfig->dnd_offer); + wlconfig->dnd_offer = NULL; + dnd_offer_mimes_free(wlconfig); + wlconfig->dnd_incoming = NO; +} + +static void +device_selection(void *data, struct wl_data_device *device, + struct wl_data_offer *offer) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandDnD", @"device_selection: %p", (void *)offer); + + /* Clipboard selection is outside M1 scope. Destroy the offer if it's the + * same as the pending dnd_offer (which means it was a selection, not DnD). */ + if (offer && offer == wlconfig->dnd_offer) + { + wl_data_offer_destroy(offer); + wlconfig->dnd_offer = NULL; + dnd_offer_mimes_free(wlconfig); + } + else if (offer) + { + wl_data_offer_destroy(offer); + } +} + +const struct wl_data_device_listener data_device_listener = { + device_data_offer, + device_enter, + device_leave, + device_motion, + device_drop, + device_selection, +}; + + +/* ── Lightweight NSWindow subclass for the drag icon ─────────────────────── */ + +@interface WaylandRawWindow : NSWindow +@end + +@implementation WaylandRawWindow + +- (BOOL) canBecomeMainWindow { return NO; } +- (BOOL) canBecomeKeyWindow { return NO; } + - (void) _initDefaults { [super _initDefaults]; @@ -83,10 +706,12 @@ - (void) orderWindow: (NSWindowOrderingMode)place relativeTo: (NSInteger)otherWi @end -/* Private ivar extension */ +/* ── WaylandDragView ─────────────────────────────────────────────────────── */ + @interface WaylandDragView () { void *_dragCursorCid; + BOOL _waylandExternalDragActive; /* YES after wl_data_device_start_drag */ } @end @@ -98,9 +723,7 @@ @implementation WaylandDragView + (id) sharedDragView { if (sharedDragView == nil) - { - sharedDragView = [WaylandDragView new]; - } + sharedDragView = [WaylandDragView new]; return sharedDragView; } @@ -111,10 +734,10 @@ + (Class) windowClass - (void) updateDragInfoFromEvent: (NSEvent *)event { - destWindow = [event window]; - dragPoint = [event locationInWindow]; + destWindow = [event window]; + dragPoint = [event locationInWindow]; dragSequence = [event timestamp]; - dragMask = [event data2]; + dragMask = [event data2]; } - (void) resetDragInfo @@ -122,34 +745,155 @@ - (void) resetDragInfo DESTROY(dragPasteboard); } -- (void) postDragEvent: (NSEvent *)theEvent +/* Called from device_enter to set up NSDraggingInfo for an inbound drag. */ +- (void) setupInboundDragWithPasteboard: (NSPasteboard *)pb + operation: (NSDragOperation)op { - if (destExternal) + ASSIGN(dragPasteboard, pb); + dragSource = nil; + destExternal = YES; + dragMask = op; + operationMask = NSDragOperationAll; +} + + +/* ── Outbound drag ───────────────────────────────────────────────────────── + * + * Override dragImage: to call wl_data_device_start_drag before letting + * GSDragView run its event loop. The data_source callbacks post a fake + * NSLeftMouseUp to exit the loop when the compositor signals completion. + */ +- (void) dragImage: (NSImage *)anImage + at: (NSPoint)screenLocation + offset: (NSSize)initialOffset + event: (NSEvent *)event + pasteboard: (NSPasteboard *)pboard + source: (id)sourceObject + slideBack: (BOOL)slideFlag +{ + WaylandConfig *wlconfig = [(WaylandServer *)GSCurrentServer() wlconfig]; + + if (!wlconfig->data_device || !wlconfig->data_device_manager) { - /* Inter-process drag via wl_data_device is not yet implemented. */ - NSDebugLog(@"WaylandDragView: external postDragEvent not yet supported"); + NSDebugMLLog(@"WaylandDnD", + @"WaylandDragView: no wl_data_device — skipping external drag"); + [super dragImage: anImage at: screenLocation offset: initialOffset + event: event pasteboard: pboard source: sourceObject + slideBack: slideFlag]; + return; } - else + + /* Find the origin surface (the surface the drag started on). */ + int originWinNum = [event windowNumber]; + struct window *originWin = get_window_with_id(wlconfig, originWinNum); + struct wl_surface *originSurface = originWin ? originWin->surface : NULL; + + if (!originSurface) + { + NSDebugMLLog(@"WaylandDnD", + @"WaylandDragView: no origin surface for window %d", originWinNum); + [super dragImage: anImage at: screenLocation offset: initialOffset + event: event pasteboard: pboard source: sourceObject + slideBack: slideFlag]; + return; + } + + /* Create the data source and offer all MIME types from the pasteboard. */ + struct wl_data_source *source = + wl_data_device_manager_create_data_source(wlconfig->data_device_manager); + if (!source) { - [super postDragEvent: theEvent]; + NSDebugMLLog(@"WaylandDnD", @"WaylandDragView: failed to create wl_data_source"); + [super dragImage: anImage at: screenLocation offset: initialOffset + event: event pasteboard: pboard source: sourceObject + slideBack: slideFlag]; + return; + } + + wl_data_source_add_listener(source, &data_source_listener, wlconfig); + + for (NSString *pt in [pboard types]) + { + const char *mime = mime_for_pboard_type(pt); + if (mime) + { + wl_data_source_offer(source, mime); + /* Also offer the plain ASCII variant for text so legacy apps can receive it. */ + if (strcmp(mime, "text/plain;charset=utf-8") == 0) + wl_data_source_offer(source, "text/plain"); + } + } + + if (wlconfig->data_device_manager_version >= 3) + { + NSDragOperation srcMask = + [sourceObject draggingSourceOperationMaskForLocal: NO]; + wl_data_source_set_actions(source, ns_op_to_wl_actions(srcMask)); + } + + wlconfig->dnd_source = source; + + /* The drag serial comes from the button-press event that triggered this drag. */ + uint32_t serial = (uint32_t)[event eventNumber]; + + wl_data_device_start_drag(wlconfig->data_device, source, + originSurface, + NULL, /* icon surface — cursor is set via wl_pointer */ + serial); + wl_display_flush(wlconfig->display); + + _waylandExternalDragActive = YES; + + NSDebugMLLog(@"WaylandDnD", + @"WaylandDragView: wl_data_device_start_drag (serial=%u)", serial); + + /* Let GSDragView run its standard event loop. The data_source callbacks + * (cancelled / dnd_finished) will post a fake NSLeftMouseUp to exit it. */ + [super dragImage: anImage at: screenLocation offset: initialOffset + event: event pasteboard: pboard source: sourceObject + slideBack: slideFlag]; + + _waylandExternalDragActive = NO; + + /* Clean up the source if the compositor did not fire dnd_finished + * (e.g., version < 3 compositor). */ + if (wlconfig->dnd_source) + { + wl_data_source_destroy(wlconfig->dnd_source); + wlconfig->dnd_source = NULL; } } +- (void) postDragEvent: (NSEvent *)theEvent +{ + /* During a Wayland external drag, pointer events from the compositor stop. + * We only care about the fake NSLeftMouseUp we post from data_source + * callbacks to exit the event loop. Suppress all other routing. */ + if (_waylandExternalDragActive) + { + if ([theEvent type] == NSLeftMouseUp) + isDragging = NO; + return; + } + [super postDragEvent: theEvent]; +} + - (void) sendExternalEvent: (GSAppKitSubtype)subtype action: (NSDragOperation)action position: (NSPoint)eventLocation timestamp: (NSTimeInterval)time toWindow: (int)dWindowNumber { - /* Wayland inter-process DnD requires the wl_data_device protocol, - which is not yet implemented in this backend. */ - NSDebugLog(@"WaylandDragView: sendExternalEvent not yet implemented " - @"(subtype=%d, window=%d)", (int)subtype, dWindowNumber); + /* The Wayland compositor manages the external drag entirely after + * wl_data_device_start_drag — no protocol messages need to be sent here. */ + NSDebugMLLog(@"WaylandDnD", + @"WaylandDragView: sendExternalEvent (subtype=%d) — handled by compositor", + (int)subtype); } -/* Override to render the drag icon on the Wayland cursor surface instead - of positioning a floating window. The cursor follows the pointer - automatically, so the icon tracks the mouse with no extra work. */ + +/* ── Drag icon (outbound) ─────────────────────────────────────────────────── */ + - (void) _setupWindowFor: (NSImage *)anImage mousePosition: (NSPoint)mPoint imagePosition: (NSPoint)iPoint @@ -159,40 +903,31 @@ - (void) _setupWindowFor: (NSImage *)anImage NSSize imageSize = [anImage size]; - /* Set internal GSDragView state (mirrors what the base implementation does - before calling orderFront:, which we intentionally omit here). */ [dragCell setImage: anImage]; dragPosition = mPoint; newPosition = mPoint; offset.width = mPoint.x - iPoint.x; offset.height = mPoint.y - iPoint.y; - /* Hotspot: cursor position within the image in Wayland pixel coords - (origin top-left, Y increasing downwards). - hotspot_x = cursor_x - image_left (same in NS and Wayland) - hotspot_y = image_height - (cursor_y - image_bottom_NS) (flip Y) */ NSPoint hotspot; hotspot.x = offset.width; hotspot.y = imageSize.height - offset.height; if (hotspot.x < 0) hotspot.x = 0; if (hotspot.y < 0) hotspot.y = 0; - NSDebugLog(@"WaylandDragView: setting drag cursor hotspot=(%g,%g)", - hotspot.x, hotspot.y); + NSDebugMLLog(@"WaylandDnD", @"WaylandDragView: drag cursor hotspot=(%g,%g)", + hotspot.x, hotspot.y); - WaylandServer *server = (WaylandServer *) GSCurrentServer(); + WaylandServer *server = (WaylandServer *)GSCurrentServer(); [server imagecursor: hotspot : anImage : &_dragCursorCid]; if (_dragCursorCid != NULL) [server setcursor: _dragCursorCid]; } -/* Restore the default arrow cursor and release the drag cursor resource. */ - (void) _clearupWindow { - WaylandServer *server = (WaylandServer *) GSCurrentServer(); + WaylandServer *server = (WaylandServer *)GSCurrentServer(); - /* Restore the default cursor first so the compositor stops using the - drag-image surface before we destroy the underlying buffer. */ void *arrowCid = NULL; [server standardcursor: GSArrowCursor : &arrowCid]; if (arrowCid != NULL) @@ -205,8 +940,6 @@ - (void) _clearupWindow } } -/* The cursor surface already follows the pointer automatically via the - Wayland compositor; no window repositioning is needed. */ - (void) _moveDraggedImageToNewPosition { dragPosition = newPosition; @@ -216,27 +949,21 @@ - (NSWindow *) windowAcceptingDnDunder: (NSPoint)p windowRef: (int *)mouseWindowRef { WaylandConfig *wlconfig = - [(WaylandServer *) GSCurrentServer() wlconfig]; + [(WaylandServer *)GSCurrentServer() wlconfig]; struct window *window; struct output *output = NULL; - /* Use the first output for screen-height conversion. */ wl_list_for_each(output, &wlconfig->output_list, link) - { break; - } if (output == NULL) { - if (mouseWindowRef) - *mouseWindowRef = 0; + if (mouseWindowRef) *mouseWindowRef = 0; return nil; } int dragWinNum = (_window != nil) ? [_window windowNumber] : -1; - /* Walk the window list; keep updating candidate so we end up with the - topmost (last-inserted) window whose bounds contain the point. */ struct window *candidate = NULL; wl_list_for_each(window, &wlconfig->window_list, link) { @@ -245,39 +972,36 @@ - (NSWindow *) windowAcceptingDnDunder: (NSPoint)p if (window->ignoreMouse || window->terminated || !window->configured) continue; - /* Convert Wayland window rect to NS screen coordinates: - NS origin.y = output->height - pos_y(wl-top) - height */ float ns_x = window->pos_x; float ns_y = output->height - window->pos_y - window->height; if (p.x >= ns_x && p.x < ns_x + window->width - && p.y >= ns_y && p.y < ns_y + window->height) + && p.y >= ns_y && p.y < ns_y + window->height) { - NSWindow *nswindow = GSWindowWithNumber(window->window_id); - if (nswindow == nil) - continue; - NSCountedSet *dragTypes = - [GSCurrentServer() dragTypesForWindow: nswindow]; - if ([dragTypes count] > 0) - candidate = window; + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (nswindow == nil) continue; + NSCountedSet *dragTypes = + [GSCurrentServer() dragTypesForWindow: nswindow]; + if ([dragTypes count] > 0) + candidate = window; } } if (candidate != NULL) { - if (mouseWindowRef) - *mouseWindowRef = candidate->window_id; + if (mouseWindowRef) *mouseWindowRef = candidate->window_id; return GSWindowWithNumber(candidate->window_id); } - if (mouseWindowRef) - *mouseWindowRef = 0; + if (mouseWindowRef) *mouseWindowRef = 0; return nil; } @end +/* ── WaylandServer (DragAndDrop) ─────────────────────────────────────────── */ + @implementation WaylandServer (DragAndDrop) - (id ) dragInfo diff --git a/Source/wayland/WaylandInputServer.m b/Source/wayland/WaylandInputServer.m index 449987da..060743b9 100644 --- a/Source/wayland/WaylandInputServer.m +++ b/Source/wayland/WaylandInputServer.m @@ -40,7 +40,7 @@ - (id) initWithDelegate: (id)aDelegate name: (NSString *)name delegate = aDelegate; ASSIGN(server_name, name); focused_window_id = 0; - NSDebugLog(@"WaylandInputServer: initialized"); + NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: initialized"); return self; } @@ -53,7 +53,7 @@ - (void) dealloc - (void) setFocusedWindowId: (int)windowId { focused_window_id = windowId; - NSDebugLog(@"WaylandInputServer: focused window id = %d", windowId); + NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: focused window id = %d", windowId); } - (int) focusedWindowId @@ -76,8 +76,8 @@ - (void) activeConversationChanged: (id)sender if (window != nil) { focused_window_id = [window windowNumber]; - NSDebugLog(@"WaylandInputServer: conversation changed, focused window = %d", - focused_window_id); + NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: conversation changed, focused window = %d", + focused_window_id); } } diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index 4556e4b4..b9af52f5 100644 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -27,6 +27,7 @@ #include "wayland/WaylandServer.h" #include "cairo/WaylandCairoShmSurface.h" +#include #import #import #import @@ -103,7 +104,8 @@ [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired]; - NSDebugLog(@"[%d] pointer_handle_enter",window->window_id); + NSDebugFLLog(@"WaylandPointer", @"[%d] pointer_handle_enter sx=%g sy=%g", + window->window_id, sx, sy); if (window && wlconfig->pointer.serial) @@ -401,10 +403,14 @@ case BTN_MIDDLE: eventType = NSOtherMouseDown; break; - // TODO: handle BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK and other - // constants in libinput. - // We may just want to send NSOtherMouseDown and populate buttonNumber - // with the libinput constant? + /* TODO: handle BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK and other + * constants in libinput. Map to NSOtherMouseDown with appropriate + * buttonNumber per Milestone 3 of wayland_feature_implementation_plan. */ + default: + NSDebugFLLog(@"WaylandPointer", + @"pointer_handle_button: unhandled button=0x%x state=%u", + button, state_w); + return; } } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) @@ -432,6 +438,11 @@ case BTN_MIDDLE: eventType = NSOtherMouseUp; break; + default: + NSDebugFLLog(@"WaylandPointer", + @"pointer_handle_button: unhandled button=0x%x state=%u", + button, state_w); + return; } } else @@ -477,31 +488,39 @@ } -// Discrete step information for scroll and other axes. +/* Groups axis events for the same logical frame. + * TODO (Milestone 3): accumulate per-frame deltas before dispatching. */ static void pointer_handle_frame(void *data, struct wl_pointer *pointer) -{} +{ + NSDebugFLLog(@"WaylandScroll", @"pointer_handle_frame"); +} -// Source information for scroll and other axes. static void pointer_handle_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { WaylandConfig *wlconfig = data; wlconfig->pointer.axis_source = axis_source; + NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_source: source=%u", axis_source); } -// Stop notification for scroll and other axes. +/* TODO (Milestone 3): emit momentum-phase stop event to AppKit. */ static void pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) -{} +{ + NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_stop: time=%u axis=%u", time, axis); +} -// Discrete step information for scroll and other axes. +/* TODO (Milestone 3): include discrete step count in scroll event. */ static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int discrete) -{} +{ + NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_discrete: axis=%u discrete=%d", + axis, discrete); +} // Scroll and other axis notifications. static void @@ -534,11 +553,14 @@ if (wlconfig->pointer.axis_source != WL_POINTER_AXIS_SOURCE_WHEEL) { - //axis_source == WL POINTER AXIS SOURCE FINGER - //axis_source == WL POINTER AXIS SOURCE CONTINUOUS - // XXX the scroll is from trackpad we should calculate - // the momentumPhase - NSDebugLog(@"touch scroll"); + /* axis_source == WL_POINTER_AXIS_SOURCE_FINGER or CONTINUOUS: + * scroll is from a touchpad — momentum phase should be set per + * Milestone 3 of wayland_feature_implementation_plan. */ + NSDebugFLLog(@"WaylandScroll", + @"pointer_handle_axis: touch/continuous scroll axis=%u " + @"value=%g source=%u", + axis, wl_fixed_to_double(value), + wlconfig->pointer.axis_source); } //float mouse_scroll_multiplier = wlconfig->mouse_scroll_multiplier; @@ -657,7 +679,7 @@ - (void)releasemouse - (void)setMouseLocation:(NSPoint)mouseLocation onScreen:(int)aScreen { - NSDebugLog(@"setMouseLocation: not supported"); + NSDebugMLLog(@"WaylandPointer", @"setMouseLocation: not supported on Wayland"); } - (void)hidecursor @@ -738,8 +760,8 @@ - (void)standardcursor:(int)style :(void **)cid } if (strlen(cursor_name) != 0) { - NSDebugLog(@"load cursor from theme for style %d: %s", style, - cursor_name); + NSDebugMLLog(@"WaylandPointer", @"load cursor from theme for style %d: %s", style, + cursor_name); struct cursor *wayland_cursor = calloc(1, sizeof(struct cursor)); wayland_cursor->cursor @@ -753,7 +775,7 @@ - (void)standardcursor:(int)style :(void **)cid } else { - NSDebugLog(@"unable to load cursor from theme for style %d", style); + NSDebugMLLog(@"WaylandPointer", @"unable to load cursor from theme for style %d", style); } } @@ -799,8 +821,8 @@ - (void)setcursorcolor:(NSColor *)fg :(NSColor *)bg :(void *)cid - (void) recolorcursor:(NSColor *)fg :(NSColor *)bg :(void*) cid { - // TODO recolorcursor - NSDebugLog(@"recolorcursor"); + /* TODO: implement cursor recolouring on Wayland */ + NSDebugMLLog(@"WaylandPointer", @"recolorcursor: not yet implemented"); } - (void)setcursor:(void *)cid diff --git a/Source/wayland/WaylandServer+Keyboard.m b/Source/wayland/WaylandServer+Keyboard.m index 349c2f56..651a144b 100644 --- a/Source/wayland/WaylandServer+Keyboard.m +++ b/Source/wayland/WaylandServer+Keyboard.m @@ -26,6 +26,7 @@ */ #include "wayland/WaylandServer.h" +#include #include #include #include @@ -39,7 +40,6 @@ keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { - NSDebugLog(@"keyboard_handle_keymap"); WaylandConfig *wlconfig = data; struct xkb_keymap *keymap; struct xkb_state *state; @@ -95,6 +95,8 @@ wlconfig->xkb.keymap = keymap; wlconfig->xkb.state = state; + NSDebugFLLog(@"WaylandIME", @"keyboard_handle_keymap: XKB keymap loaded (size=%u)", size); + wlconfig->xkb.control_mask = 1 << xkb_keymap_mod_get_index(wlconfig->xkb.keymap, "Control"); wlconfig->xkb.alt_mask @@ -107,9 +109,9 @@ keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - NSDebugLog(@"keyboard_handle_enter"); WaylandConfig *wlconfig = data; wlconfig->event_serial = serial; + NSDebugFLLog(@"WaylandIME", @"keyboard_handle_enter: serial=%u", serial); struct window *window = wl_surface_get_user_data(surface); if (!window) @@ -137,9 +139,9 @@ keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { - NSDebugLog(@"keyboard_handle_leave"); WaylandConfig *wlconfig = data; wlconfig->event_serial = serial; + NSDebugFLLog(@"WaylandIME", @"keyboard_handle_leave: serial=%u", serial); if (!wlconfig->keyboard_focus) return; diff --git a/Source/wayland/WaylandServer+Output.m b/Source/wayland/WaylandServer+Output.m index 650ddd83..c2ccb089 100644 --- a/Source/wayland/WaylandServer+Output.m +++ b/Source/wayland/WaylandServer+Output.m @@ -26,13 +26,13 @@ */ #include "wayland/WaylandServer.h" +#include static void handle_geometry(void *data, struct wl_output *wl_output, int x, int y, int physical_width, int physical_height, int subpixel, const char *make, const char *model, int transform) { - NSDebugLog(@"handle_geometry"); struct output *output = data; output->alloc_x = x; @@ -46,35 +46,42 @@ if (output->model) free(output->model); output->model = strdup(model); + + NSDebugFLLog(@"WaylandOutput", + @"handle_geometry: output @(%d,%d) physical=%dx%dmm " + @"transform=%d make=%s model=%s", + x, y, physical_width, physical_height, transform, make, model); } static void handle_done(void *data, struct wl_output *wl_output) { - NSDebugLog(@"handle_done"); + NSDebugFLLog(@"WaylandOutput", @"handle_done: output configuration committed"); } static void handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { - NSDebugLog(@"handle_scale"); struct output *output = data; output->scale = scale; + NSDebugFLLog(@"WaylandOutput", @"handle_scale: scale=%d", scale); } static void handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { - NSDebugLog(@"handle_mode"); struct output *output = data; + NSDebugFLLog(@"WaylandOutput", @"handle_mode: flags=0x%x size=%dx%d refresh=%dHz", + flags, width, height, refresh / 1000); if (flags & WL_OUTPUT_MODE_CURRENT) { output->width = width; output->height = height /*- 30*/; - NSDebugLog(@"handle_mode output=%dx%d", width, height); + NSDebugFLLog(@"WaylandOutput", @"handle_mode: current output size set to %dx%d", + width, height); // XXX - Should we implement this? // if (display->output_configure_handler) diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 423c2e7d..79234f8c 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -50,8 +50,8 @@ #include "wayland/WaylandInputServer.h" extern const struct wl_output_listener output_listener; - extern const struct wl_seat_listener seat_listener; +extern const struct wl_data_device_listener data_device_listener; static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) @@ -146,6 +146,14 @@ = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); NSDebugLog(@"wayland: found xdg-decoration-manager interface"); } + else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) + { + uint32_t v = (version < 3) ? version : 3; + wlconfig->data_device_manager_version = v; + wlconfig->data_device_manager + = wl_registry_bind(registry, name, &wl_data_device_manager_interface, v); + NSDebugLog(@"wayland: found wl_data_device_manager (version %u)", v); + } } static void handle_global_remove(void *data, struct wl_registry *registry, @@ -218,6 +226,19 @@ - (id)_initWaylandContext wl_display_dispatch(wlconfig->display); wl_display_roundtrip(wlconfig->display); + /* Get a data device now that both seat and data_device_manager are bound. */ + if (wlconfig->data_device_manager && wlconfig->seat) + { + wlconfig->data_device = wl_data_device_manager_get_data_device( + wlconfig->data_device_manager, wlconfig->seat); + if (wlconfig->data_device) + { + wl_data_device_add_listener(wlconfig->data_device, + &data_device_listener, wlconfig); + NSDebugLog(@"wayland: wl_data_device created"); + } + } + if (!wlconfig->compositor) { [NSException raise:NSWindowServerCommunicationException diff --git a/Tools/wayland-smoke-test.sh b/Tools/wayland-smoke-test.sh new file mode 100755 index 00000000..bffbae84 --- /dev/null +++ b/Tools/wayland-smoke-test.sh @@ -0,0 +1,404 @@ +#!/usr/bin/env bash +# wayland-smoke-test.sh — Milestone 0 integration harness for the Wayland backend. +# +# Starts the Ambrosia compositor nested inside the current Wayland session, +# then exercises each feature bucket in wayland_feature_implementation_plan.md +# and captures log snapshots. +# +# Usage: +# ./Tools/wayland-smoke-test.sh [options] +# +# Options: +# -c PATH Path to ambrosia-compositor binary (auto-detected if omitted) +# -o DIR Directory to write snapshot logs (default: /tmp/wayland-smoke-YYYYMMDD-HHMMSS) +# -t SECS Per-test timeout in seconds (default: 10) +# -v Verbose: show compositor log in real time +# -h Show this help +# +# Exit status: 0 if all smoke tests passed, 1 if any failed or setup failed. +# +# Debug categories enabled during the run (set via NSDebugCategories): +# WaylandDnD, WaylandIME, WaylandPointer, WaylandScroll, WaylandOutput +# +# Requirements: +# - A running Wayland session (WAYLAND_DISPLAY must be set or wayland-0 must +# exist). The compositor runs nested inside that session. +# - ambrosia-compositor binary (see -c option or AMBROSIA_ROOT below). + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +AMBROSIA_ROOT="/home/james/development/ambrosia-experimental" + +COMPOSITOR_BIN="" +OUTPUT_DIR="" +TIMEOUT_SECS=10 +VERBOSE=0 +PASS=0 +FAIL=0 +COMPOSITOR_PID="" +CLIENT_WAYLAND_DISPLAY="" + +# ── helpers ────────────────────────────────────────────────────────────────── + +log() { echo "[smoke] $*"; } +pass() { echo "[PASS] $*"; (( PASS++ )) || true; } +fail() { echo "[FAIL] $*"; (( FAIL++ )) || true; } + +usage() { + sed -n 's/^# //p' "$0" | head -35 + exit 0 +} + +# ── cleanup on exit ─────────────────────────────────────────────────────────── + +cleanup() { + if [[ -n "${COMPOSITOR_PID}" ]] && kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then + log "stopping compositor (pid ${COMPOSITOR_PID}) …" + kill -TERM "${COMPOSITOR_PID}" 2>/dev/null || true + wait "${COMPOSITOR_PID}" 2>/dev/null || true + log "compositor stopped" + fi +} +trap cleanup EXIT + +# ── argument parsing ────────────────────────────────────────────────────────── + +while getopts "c:o:t:vh" opt; do + case "${opt}" in + c) COMPOSITOR_BIN="${OPTARG}" ;; + o) OUTPUT_DIR="${OPTARG}" ;; + t) TIMEOUT_SECS="${OPTARG}" ;; + v) VERBOSE=1 ;; + h) usage ;; + *) echo "Unknown option -${OPTARG}" >&2; exit 1 ;; + esac +done + +# ── locate compositor ───────────────────────────────────────────────────────── + +if [[ -z "${COMPOSITOR_BIN}" ]]; then + for candidate in \ + "${AMBROSIA_ROOT}/Compositor/obj/ambrosia-compositor" \ + "${AMBROSIA_ROOT}/Compositor"/obj.*/ambrosia-compositor \ + "$(command -v ambrosia-compositor 2>/dev/null || true)"; do + if [[ -x "${candidate}" ]]; then + COMPOSITOR_BIN="${candidate}" + break + fi + done +fi + +if [[ -z "${COMPOSITOR_BIN}" || ! -x "${COMPOSITOR_BIN}" ]]; then + echo "ERROR: cannot find ambrosia-compositor binary." >&2 + echo " Build it in ${AMBROSIA_ROOT}/Compositor or pass -c ." >&2 + exit 1 +fi +log "compositor: ${COMPOSITOR_BIN}" + +# ── output directory ────────────────────────────────────────────────────────── + +if [[ -z "${OUTPUT_DIR}" ]]; then + OUTPUT_DIR="/tmp/wayland-smoke-$(date +%Y%m%d-%H%M%S)" +fi +mkdir -p "${OUTPUT_DIR}" +log "snapshots: ${OUTPUT_DIR}" + +COMPOSITOR_LOG="${OUTPUT_DIR}/compositor.log" + +# ── GNUstep environment ─────────────────────────────────────────────────────── + +if [[ -z "${GNUSTEP_MAKEFILES:-}" ]]; then + for gnustep_sh in \ + /usr/share/GNUstep/Makefiles/GNUstep.sh \ + /usr/local/share/GNUstep/Makefiles/GNUstep.sh \ + /usr/GNUstep/System/Library/Makefiles/GNUstep.sh; do + if [[ -f "${gnustep_sh}" ]]; then + # shellcheck source=/dev/null + source "${gnustep_sh}" + break + fi + done +fi + +# ── find the parent Wayland session ────────────────────────────────────────── +# wlr_backend_autocreate reads WAYLAND_DISPLAY to decide whether to create a +# nested-Wayland backend. We must point it at the real running session so +# Ambrosia can render into it. + +PARENT_DISPLAY="${WAYLAND_DISPLAY:-}" +if [[ -z "${PARENT_DISPLAY}" ]]; then + # Try well-known sockets under XDG_RUNTIME_DIR + XDG_RT="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" + for sock in "${XDG_RT}/wayland-0" "${XDG_RT}/wayland-1"; do + if [[ -S "${sock}" ]]; then + PARENT_DISPLAY="$(basename "${sock}")" + break + fi + done +fi + +if [[ -z "${PARENT_DISPLAY}" ]]; then + echo "ERROR: no running Wayland session found." >&2 + echo " Set WAYLAND_DISPLAY or start a Wayland compositor first." >&2 + exit 1 +fi +log "parent Wayland session: ${PARENT_DISPLAY}" + +# ── start Ambrosia nested in the parent session ─────────────────────────────── +# Pass WAYLAND_DISPLAY= so wlr_backend_autocreate picks the Wayland +# backend. The compositor calls wl_display_add_socket_auto() to create its +# *own* socket (wayland-N) and logs: +# "Ambrosia compositor running on WAYLAND_DISPLAY=wayland-N" +# We parse that line to discover the client socket. + +log "starting compositor (nested in ${PARENT_DISPLAY}) …" + +if [[ "${VERBOSE}" -eq 1 ]]; then + WAYLAND_DISPLAY="${PARENT_DISPLAY}" "${COMPOSITOR_BIN}" 2>&1 \ + | tee "${COMPOSITOR_LOG}" & + COMPOSITOR_PID=$! +else + WAYLAND_DISPLAY="${PARENT_DISPLAY}" "${COMPOSITOR_BIN}" \ + >"${COMPOSITOR_LOG}" 2>&1 & + COMPOSITOR_PID=$! +fi + +# ── wait for the compositor's own socket to be announced ───────────────────── + +log "waiting for compositor socket …" +WAIT_MAX=30 # 30 × 0.5 s = 15 s +for (( i=0; i/dev/null; then + log "compositor exited before announcing socket (see ${COMPOSITOR_LOG})" + tail -5 "${COMPOSITOR_LOG}" | sed 's/^/ /' >&2 + break + fi + + # Parse the socket name from the compositor log + CLIENT_WAYLAND_DISPLAY="$( + grep -m1 'running on WAYLAND_DISPLAY=' "${COMPOSITOR_LOG}" 2>/dev/null \ + | sed 's/.*WAYLAND_DISPLAY=\([^ ]*\).*/\1/' + )" + + if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]]; then + XDG_RT="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" + if [[ -S "${XDG_RT}/${CLIENT_WAYLAND_DISPLAY}" ]]; then + log "compositor ready on ${CLIENT_WAYLAND_DISPLAY} (pid ${COMPOSITOR_PID})" + break + fi + fi + sleep 0.5 +done + +if [[ -z "${CLIENT_WAYLAND_DISPLAY}" ]]; then + log "WARNING: could not determine compositor socket — runtime tests will be skipped" +else + # Export so subsequent client tools connect to the right compositor + export WAYLAND_DISPLAY="${CLIENT_WAYLAND_DISPLAY}" +fi + +# ── helper: run a single smoke probe ───────────────────────────────────────── + +run_probe() { + local name="$1"; shift + local logfile="$1"; shift + + if timeout "${TIMEOUT_SECS}" "$@" >"${logfile}" 2>&1; then + pass "${name}" + else + local status=$? + if [[ ${status} -eq 124 ]]; then + fail "${name} (timeout after ${TIMEOUT_SECS}s)" + else + fail "${name} (exit ${status})" + fi + fi +} + +check_log_contains() { + local name="$1" logfile="$2" pattern="$3" + if grep -qE "${pattern}" "${logfile}" 2>/dev/null; then + pass "${name}: found '${pattern}'" + else + fail "${name}: '${pattern}' not found in ${logfile}" + fi +} + +check_log_absent() { + local name="$1" logfile="$2" pattern="$3" + if grep -qE "${pattern}" "${logfile}" 2>/dev/null; then + fail "${name}: unexpected '${pattern}' found in ${logfile}" + else + pass "${name}: '${pattern}' absent (expected)" + fi +} + +# ── SMOKE TESTS ─────────────────────────────────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════" +echo " Wayland backend smoke tests (Milestone 0)" +echo " Compositor: ${COMPOSITOR_BIN}" +echo " Parent: ${PARENT_DISPLAY}" +echo " Client sock: ${CLIENT_WAYLAND_DISPLAY:-unknown}" +echo "═══════════════════════════════════════════════" +echo "" + +# ── T1: compositor process is running ──────────────────────────────────────── + +if kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then + pass "T1: compositor process running (pid ${COMPOSITOR_PID})" +else + fail "T1: compositor process not running" +fi + +# ── T2: compositor socket exists ───────────────────────────────────────────── + +if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]]; then + XDG_RT="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" + SOCK_PATH="${XDG_RT}/${CLIENT_WAYLAND_DISPLAY}" + if [[ -S "${SOCK_PATH}" ]]; then + pass "T2: compositor socket exists: ${SOCK_PATH}" + else + fail "T2: compositor socket missing: ${SOCK_PATH}" + fi +else + fail "T2: compositor socket unknown (startup failed)" +fi + +# ── T3: protocol globals via wayland-info ──────────────────────────────────── + +WL_INFO_LOG="${OUTPUT_DIR}/wayland-info.log" +if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]] && command -v wayland-info &>/dev/null; then + run_probe "T3: wayland-info connect" "${WL_INFO_LOG}" wayland-info + check_log_contains "T3: wl_compositor advertised" "${WL_INFO_LOG}" "wl_compositor" + check_log_contains "T3: xdg_wm_base advertised" "${WL_INFO_LOG}" "xdg_wm_base" + check_log_contains "T3: wl_seat advertised" "${WL_INFO_LOG}" "wl_seat" +else + log "T3: skipped (no compositor socket or wayland-info not installed)" +fi + +# ── T4: capture globals snapshot ───────────────────────────────────────────── + +if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]] && command -v wayland-info &>/dev/null; then + GLOBALS_SNAPSHOT="${OUTPUT_DIR}/globals-snapshot.txt" + wayland-info >"${GLOBALS_SNAPSHOT}" 2>&1 || true + log "T4: globals snapshot → ${GLOBALS_SNAPSHOT}" + pass "T4: globals snapshot captured" +else + log "T4: globals snapshot skipped" +fi + +# ── T5: backend library loads ──────────────────────────────────────────────── + +BACK_LIB="" +for lib_candidate in \ + "${REPO_ROOT}/obj/libgnustep-back.so" \ + /usr/lib/GNUstep/Libraries/libgnustep-back.so \ + /usr/local/lib/GNUstep/Libraries/libgnustep-back.so; do + if [[ -f "${lib_candidate}" ]]; then + BACK_LIB="${lib_candidate}" + break + fi +done + +BACK_LIB_LOG="${OUTPUT_DIR}/backend-lib.log" +if [[ -n "${BACK_LIB}" ]]; then + if python3 -c "import ctypes; ctypes.CDLL('${BACK_LIB}')" \ + >"${BACK_LIB_LOG}" 2>&1; then + pass "T5: backend library loads: ${BACK_LIB}" + else + fail "T5: backend library failed to load: ${BACK_LIB}" + fi +else + log "T5: gnustep-back library not found — skipping dlopen check" +fi + +# ── T6: compositor log contains startup markers ─────────────────────────────── + +check_log_contains "T6: compositor initialised" \ + "${COMPOSITOR_LOG}" "Ambrosia: initialising compositor" +check_log_contains "T6: backend created" \ + "${COMPOSITOR_LOG}" "Backend created|Creating wayland backend" +check_log_contains "T6: compositor announced socket" \ + "${COMPOSITOR_LOG}" "running on WAYLAND_DISPLAY=" + +# ── T7: DnD stub snapshot ──────────────────────────────────────────────────── + +DND_SOURCE="${REPO_ROOT}/Source/wayland/WaylandDragView.m" +DND_LOG="${OUTPUT_DIR}/dnd-snapshot.txt" +{ + echo "=== DnD debug-category static check ===" + grep -n "WaylandDnD" "${DND_SOURCE}" 2>/dev/null || true + echo "" + echo "=== DnD stubs present ===" + grep -n "not yet\|not implemented\|wl_data_device" "${DND_SOURCE}" 2>/dev/null || true +} >"${DND_LOG}" +check_log_contains "T7: WaylandDnD category in source" "${DND_LOG}" "WaylandDnD" +check_log_contains "T7: inter-process DnD stub present" "${DND_LOG}" "wl_data_device" + +# ── T8: IME stub snapshot ───────────────────────────────────────────────────── + +IME_SOURCE="${REPO_ROOT}/Source/wayland/WaylandInputServer.m" +IME_LOG="${OUTPUT_DIR}/ime-snapshot.txt" +{ + echo "=== IME debug-category static check ===" + grep -n "WaylandIME" "${IME_SOURCE}" 2>/dev/null || true + echo "" + echo "=== IME stubs (statusArea/preeditArea return NO) ===" + grep -n "statusArea\|preeditArea\|preeditSpot" "${IME_SOURCE}" 2>/dev/null || true +} >"${IME_LOG}" +check_log_contains "T8: WaylandIME category in source" "${IME_LOG}" "WaylandIME" +check_log_contains "T8: statusArea stub present" "${IME_LOG}" "statusArea" + +# ── T9: pointer/scroll stub snapshot ───────────────────────────────────────── + +CURSOR_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer+Cursor.m" +PTR_LOG="${OUTPUT_DIR}/pointer-snapshot.txt" +{ + echo "=== WaylandPointer debug-category static check ===" + grep -n "WaylandPointer" "${CURSOR_SOURCE}" 2>/dev/null || true + echo "" + echo "=== WaylandScroll debug-category static check ===" + grep -n "WaylandScroll" "${CURSOR_SOURCE}" 2>/dev/null || true + echo "" + echo "=== Extra button TODO milestone marker ===" + grep -n "Milestone 3\|BTN_SIDE\|BTN_EXTRA\|BTN_FORWARD\|BTN_BACK" "${CURSOR_SOURCE}" 2>/dev/null || true +} >"${PTR_LOG}" +check_log_contains "T9: WaylandPointer category in source" "${PTR_LOG}" "WaylandPointer" +check_log_contains "T9: WaylandScroll category in source" "${PTR_LOG}" "WaylandScroll" +check_log_contains "T9: extra-button Milestone 3 marker" "${PTR_LOG}" "Milestone 3" + +# ── T10: output stub snapshot ───────────────────────────────────────────────── + +OUTPUT_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer+Output.m" +OUT_LOG="${OUTPUT_DIR}/output-snapshot.txt" +{ + echo "=== WaylandOutput debug-category static check ===" + grep -n "WaylandOutput" "${OUTPUT_SOURCE}" 2>/dev/null || true + echo "" + echo "=== Output configure TODO ===" + grep -n "TODO\|configure_handler\|XXX" "${OUTPUT_SOURCE}" 2>/dev/null || true +} >"${OUT_LOG}" +check_log_contains "T10: WaylandOutput category in source" "${OUT_LOG}" "WaylandOutput" + +# ── T11: compositor log sanity ──────────────────────────────────────────────── + +check_log_absent "T11: no crash/abort in compositor log" \ + "${COMPOSITOR_LOG}" "Segmentation fault|Aborted|SIGSEGV|double free" + +# ── summary ─────────────────────────────────────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════" +echo " Results: ${PASS} passed, ${FAIL} failed" +echo " Snapshots in: ${OUTPUT_DIR}" +echo "═══════════════════════════════════════════════" +echo "" + +if [[ "${FAIL}" -gt 0 ]]; then + exit 1 +fi +exit 0 From 5e48706d249ac4c96f3c0cc100e4892d0a02e8f2 Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 10:04:17 +1000 Subject: [PATCH 12/24] Wayland M2: IME/preedit via zwp_text_input_v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Protocol plumbing: - Generate text-input-unstable-v3 client header and protocol .c file via wayland-scanner; add the .c to wayland_C_FILES in GNUmakefile. - Include text-input-unstable-v3-client-protocol.h from WaylandServer.h. - Add zwp_text_input_manager_v3 and zwp_text_input_v3 pointers plus preedit state (pending_preedit, pending_commit, preedit_rect/spot, text_input_active, ime_serial) to WaylandConfig. - Bind zwp_text_input_manager_v3 in registry handler. - Create zwp_text_input_v3 from manager+seat after the initial roundtrip and attach the listener. Keyboard / text_input_v3 listener (WaylandServer+Keyboard.m): - Fix longstanding XKB bug: replace the broken `&sym` (keysym address cast to UTF-8) with xkb_state_key_get_utf8, which correctly handles the full dead-key composition pipeline (e.g. dead_acute + e → é). - Add zwp_text_input_v3_listener with all 9 callbacks (6 v1 + 3 v2 stubs). - keyboard_handle_enter/leave: call enable()/disable() + commit() on the text input object so the compositor IM knows when focus changes. - text_input_enter/leave: track active state; clear preedit on leave. - text_input_preedit_string / commit_string: buffer pending IM events. - text_input_done: apply buffered preedit via setMarkedText:selectedRange: and commit string via insertText: (with key-event fallback); clear marked text when preedit is NULL. - delete_surrounding_text, action, language, preedit_hint: logged stubs. WaylandInputServer (WaylandInputServer.m / .h): - Add wlconfig back-pointer; wire it from _initWaylandContext. - statusArea: return the bottom strip of the focused window. - preeditArea/preeditSpot: return the stored geometry values or fall back to the client window rect / window centre. - setPreeditSpot:/setPreeditArea:: store geometry and forward to the compositor via zwp_text_input_v3_set_cursor_rectangle + commit so the IM candidate window tracks the text cursor. Co-Authored-By: Claude Sonnet 4.6 --- Headers/wayland/WaylandInputServer.h | 9 +- Headers/wayland/WaylandServer.h | 17 + .../text-input-unstable-v3-client-protocol.h | 1103 +++++++++++++++++ Source/wayland/GNUmakefile | 1 + Source/wayland/WaylandInputServer.m | 163 ++- Source/wayland/WaylandServer+Keyboard.m | 356 +++++- Source/wayland/WaylandServer.m | 21 + .../wayland/text-input-unstable-v3-protocol.c | 93 ++ 8 files changed, 1702 insertions(+), 61 deletions(-) create mode 100644 Headers/wayland/text-input-unstable-v3-client-protocol.h create mode 100644 Source/wayland/text-input-unstable-v3-protocol.c diff --git a/Headers/wayland/WaylandInputServer.h b/Headers/wayland/WaylandInputServer.h index 26ae14ac..d507b780 100644 --- a/Headers/wayland/WaylandInputServer.h +++ b/Headers/wayland/WaylandInputServer.h @@ -25,17 +25,20 @@ #define _WaylandInputServer_h_INCLUDE #include +#include "wayland/WaylandServer.h" @interface WaylandInputServer : NSInputServer { - id delegate; - NSString *server_name; - int focused_window_id; + id delegate; + NSString *server_name; + int focused_window_id; + WaylandConfig *wlconfig; /* back-pointer for IME geometry calls */ } - (id) initWithDelegate: (id)aDelegate name: (NSString *)name; - (void) setFocusedWindowId: (int)windowId; - (int) focusedWindowId; +- (void) setWlconfig: (WaylandConfig *)config; @end diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index e63850e8..db06d474 100644 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -43,6 +43,7 @@ #include "wayland/xdg-shell-client-protocol.h" #include "wayland/wlr-layer-shell-client-protocol.h" #include "wayland/xdg-decoration-unstable-v1-client-protocol.h" +#include "wayland/text-input-unstable-v3-client-protocol.h" struct pointer { @@ -128,6 +129,22 @@ typedef struct _WaylandConfig } xkb; int modifiers; + /* zwp_text_input_v3 — input method / preedit support */ + struct zwp_text_input_manager_v3 *text_input_manager; + struct zwp_text_input_v3 *text_input; + BOOL text_input_active; /* enabled for current surface */ + + /* Preedit geometry (set by AppKit via WaylandInputServer setters) */ + NSPoint ime_preedit_spot; /* cursor position for IM popup, in screen coords */ + NSRect ime_preedit_rect; /* preedit bounding rect */ + + /* Pending IM events, applied together in the done callback */ + char *ime_pending_preedit; /* NULL when no active preedit */ + int32_t ime_preedit_cursor_begin; + int32_t ime_preedit_cursor_end; + char *ime_pending_commit; /* NULL when no pending commit */ + uint32_t ime_serial; /* most recent done serial */ + /* wl_data_device — selection and drag-and-drop */ struct wl_data_device_manager *data_device_manager; struct wl_data_device *data_device; diff --git a/Headers/wayland/text-input-unstable-v3-client-protocol.h b/Headers/wayland/text-input-unstable-v3-client-protocol.h new file mode 100644 index 00000000..6a853f25 --- /dev/null +++ b/Headers/wayland/text-input-unstable-v3-client-protocol.h @@ -0,0 +1,1103 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H +#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol + * Protocol for composing text + * + * @section page_desc_text_input_unstable_v3 Description + * + * This protocol allows compositors to act as input methods and to send text + * to applications. A text input object is used to manage state of what are + * typically text entry fields in the application. + * + * This document adheres to the RFC 2119 when using words like "must", + * "should", "may", etc. + * + * Warning! The protocol described in this file is experimental and + * backward incompatible changes may be made. Backward compatible changes + * may be added together with the corresponding interface version bump. + * Backward incompatible changes are done by bumping the version number in + * the protocol and interface names and resetting the interface version. + * Once the protocol is to be declared stable, the 'z' prefix and the + * version number in the protocol and interface names are removed and the + * interface version number is reset. + * + * @section page_ifaces_text_input_unstable_v3 Interfaces + * - @subpage page_iface_zwp_text_input_v3 - text input + * - @subpage page_iface_zwp_text_input_manager_v3 - text input manager + * @section page_copyright_text_input_unstable_v3 Copyright + *
+ *
+ * Copyright © 2012, 2013 Intel Corporation
+ * Copyright © 2015, 2016 Jan Arne Petersen
+ * Copyright © 2017, 2018 Red Hat, Inc.
+ * Copyright © 2018       Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * 
+ */ +struct wl_seat; +struct wl_surface; +struct zwp_text_input_manager_v3; +struct zwp_text_input_v3; + +#ifndef ZWP_TEXT_INPUT_V3_INTERFACE +#define ZWP_TEXT_INPUT_V3_INTERFACE +/** + * @page page_iface_zwp_text_input_v3 zwp_text_input_v3 + * @section page_iface_zwp_text_input_v3_desc Description + * + * The zwp_text_input_v3 interface represents text input and input methods + * associated with a seat. It provides enter/leave events to follow the + * text input focus for a seat. + * + * Requests are used to enable/disable the text-input object and set + * state information like surrounding and selected text or the content type. + * The information about the entered text is sent to the text-input object + * via the preedit_string and commit_string events. + * + * Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + * must not point to middle bytes inside a code point: they must either + * point to the first byte of a code point or to the end of the buffer. + * Lengths must be measured between two valid indices. + * + * Focus moving throughout surfaces will result in the emission of + * zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + * surface must commit zwp_text_input_v3.enable and + * zwp_text_input_v3.disable requests as the keyboard focus moves across + * editable and non-editable elements of the UI. Those two requests are not + * expected to be paired with each other, the compositor must be able to + * handle consecutive series of the same request. + * + * State is sent by the state requests (set_surrounding_text, + * set_content_type and set_cursor_rectangle) and a commit request. After an + * enter event or disable request all state information is invalidated and + * needs to be resent by the client. + * @section page_iface_zwp_text_input_v3_api API + * See @ref iface_zwp_text_input_v3. + */ +/** + * @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface + * + * The zwp_text_input_v3 interface represents text input and input methods + * associated with a seat. It provides enter/leave events to follow the + * text input focus for a seat. + * + * Requests are used to enable/disable the text-input object and set + * state information like surrounding and selected text or the content type. + * The information about the entered text is sent to the text-input object + * via the preedit_string and commit_string events. + * + * Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + * must not point to middle bytes inside a code point: they must either + * point to the first byte of a code point or to the end of the buffer. + * Lengths must be measured between two valid indices. + * + * Focus moving throughout surfaces will result in the emission of + * zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + * surface must commit zwp_text_input_v3.enable and + * zwp_text_input_v3.disable requests as the keyboard focus moves across + * editable and non-editable elements of the UI. Those two requests are not + * expected to be paired with each other, the compositor must be able to + * handle consecutive series of the same request. + * + * State is sent by the state requests (set_surrounding_text, + * set_content_type and set_cursor_rectangle) and a commit request. After an + * enter event or disable request all state information is invalidated and + * needs to be resent by the client. + */ +extern const struct wl_interface zwp_text_input_v3_interface; +#endif +#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE +#define ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE +/** + * @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3 + * @section page_iface_zwp_text_input_manager_v3_desc Description + * + * A factory for text-input objects. This object is a global singleton. + * @section page_iface_zwp_text_input_manager_v3_api API + * See @ref iface_zwp_text_input_manager_v3. + */ +/** + * @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface + * + * A factory for text-input objects. This object is a global singleton. + */ +extern const struct wl_interface zwp_text_input_manager_v3_interface; +#endif + +#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM +#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * text change reason + * + * Reason for the change of surrounding text or cursor posision. + */ +enum zwp_text_input_v3_change_cause { + /** + * input method caused the change + */ + ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0, + /** + * something else than the input method caused the change + */ + ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1, +}; +#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * content hint + * + * Content hint is a bitmask to allow to modify the behavior of the text + * input. + */ +enum zwp_text_input_v3_content_hint { + /** + * no special behavior + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0, + /** + * suggest word completions + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1, + /** + * suggest word corrections + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2, + /** + * switch to uppercase letters at the start of a sentence + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4, + /** + * prefer lowercase letters + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8, + /** + * prefer uppercase letters + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10, + /** + * prefer casing for titles and headings (can be language dependent) + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20, + /** + * characters should be hidden + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40, + /** + * typed text should not be stored + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80, + /** + * just Latin characters should be entered + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100, + /** + * the text input is multiline + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200, + /** + * an on-screen way to fill in the input is already provided by the client + * @since 2 + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_ON_SCREEN_INPUT_PROVIDED = 0x400, + /** + * prefer not offering emoji support + * @since 2 + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_NO_EMOJI = 0x800, + /** + * the text input will display preedit text in place + * @since 2 + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN = 0x1000, +}; +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ON_SCREEN_INPUT_PROVIDED_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_NO_EMOJI_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN_SINCE_VERSION 2 +#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM +#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * content purpose + * + * The content purpose allows to specify the primary purpose of a text + * input. + * + * This allows an input method to show special purpose input panels with + * extra characters or to disallow some characters. + */ +enum zwp_text_input_v3_content_purpose { + /** + * default input, allowing all characters + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0, + /** + * allow only alphabetic characters + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1, + /** + * allow only digits + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2, + /** + * input a number (including decimal separator and sign) + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3, + /** + * input a phone number + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4, + /** + * input an URL + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5, + /** + * input an email address + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6, + /** + * input a name of a person + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7, + /** + * input a password (combine with sensitive_data hint) + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8, + /** + * input is a numeric password (combine with sensitive_data hint) + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9, + /** + * input a date + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10, + /** + * input a time + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11, + /** + * input a date and time + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12, + /** + * input for a terminal + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13, +}; +#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_ERROR_ENUM +#define ZWP_TEXT_INPUT_V3_ERROR_ENUM +enum zwp_text_input_v3_error { + /** + * an invalid or duplicate action was specified + */ + ZWP_TEXT_INPUT_V3_ERROR_INVALID_ACTION = 0, +}; +#endif /* ZWP_TEXT_INPUT_V3_ERROR_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_ACTION_ENUM +#define ZWP_TEXT_INPUT_V3_ACTION_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * action + * + * A possible action to perform on a text input. + * + * The submit action is intended for input entries that expect some sort of + * activation after user interaction, e.g. the URL entry in a browser. + */ +enum zwp_text_input_v3_action { + /** + * no action + */ + ZWP_TEXT_INPUT_V3_ACTION_NONE = 0, + /** + * the action is submitted + */ + ZWP_TEXT_INPUT_V3_ACTION_SUBMIT = 1, +}; +#endif /* ZWP_TEXT_INPUT_V3_ACTION_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM +#define ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * preedit style hint + * + * Style hints for the preedit string. + */ +enum zwp_text_input_v3_preedit_hint { + /** + * simple pre-edit text style, typically underlined + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_WHOLE = 1, + /** + * hint for a selected piece of text, e.g. per-character navigation and composition + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SELECTION = 2, + /** + * predicted text, not typed by the user + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_PREDICTION = 3, + /** + * prefixed text not being currently edited, e.g. prior to a 'selection' section + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_PREFIX = 4, + /** + * suffixed text not being currently edited, e.g. after a 'selection' section + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SUFFIX = 5, + /** + * spelling error + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SPELLING_ERROR = 6, + /** + * wrong composition, e.g. user input that can not be transliterated + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_COMPOSE_ERROR = 7, +}; +#endif /* ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM */ + +/** + * @ingroup iface_zwp_text_input_v3 + * @struct zwp_text_input_v3_listener + */ +struct zwp_text_input_v3_listener { + /** + * enter event + * + * Notification that this seat's text-input focus is on a certain + * surface. + * + * If client has created multiple text input objects, compositor + * must send this event to all of them. + * + * When the seat has the keyboard capability the text-input focus + * follows the keyboard focus. This event sets the current surface + * for the text-input object. + */ + void (*enter)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface); + /** + * leave event + * + * Notification that this seat's text-input focus is no longer on + * a certain surface. The client should reset any preedit string + * previously set. + * + * The leave notification clears the current surface. It is sent + * before the enter notification for the new focus. After leave + * event, compositor must ignore requests from any text input + * instances until next enter event. + * + * When the seat has the keyboard capability the text-input focus + * follows the keyboard focus. + */ + void (*leave)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface); + /** + * pre-edit + * + * Notify when a new composing text (pre-edit) should be set at + * the current cursor position. Any previously set composing text + * must be removed. Any previously existing selected text must be + * removed. + * + * The argument text contains the pre-edit string buffer. + * + * The parameters cursor_begin and cursor_end are counted in bytes + * relative to the beginning of the submitted text buffer. Cursor + * should be hidden when both are equal to -1. + * + * They could be represented by the client as a line if both values + * are the same, or as a text highlight otherwise. + * + * Values set with this event are double-buffered. They must be + * applied and reset to initial on the next zwp_text_input_v3.done + * event. + * + * The initial value of text is an empty string, and cursor_begin, + * cursor_end and cursor_hidden are all 0. + */ + void (*preedit_string)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text, + int32_t cursor_begin, + int32_t cursor_end); + /** + * text commit + * + * Notify when text should be inserted into the editor widget. + * The text to commit could be either just a single character after + * a key press or the result of some composing (pre-edit). + * + * Values set with this event are double-buffered. They must be + * applied and reset to initial on the next zwp_text_input_v3.done + * event. + * + * The initial value of text is an empty string. + */ + void (*commit_string)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text); + /** + * delete surrounding text + * + * Notify when the text around the current cursor position should + * be deleted. + * + * Before_length and after_length are the number of bytes before + * and after the current cursor index (excluding the selection) to + * delete. + * + * If a preedit text is present, in effect before_length is counted + * from the beginning of it, and after_length from its end (see + * done event sequence). + * + * Values set with this event are double-buffered. They must be + * applied and reset to initial on the next zwp_text_input_v3.done + * event. + * + * The initial values of both before_length and after_length are 0. + * @param before_length length of text before current cursor position + * @param after_length length of text after current cursor position + */ + void (*delete_surrounding_text)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t before_length, + uint32_t after_length); + /** + * apply changes + * + * Instruct the application to apply changes to state requested + * by the preedit_string, commit_string delete_surrounding_text, + * and action events. + * + * The state relating to these events is double-buffered, and each + * one modifies the pending state. This event replaces the current + * state with the pending state. + * + * The application must proceed by evaluating the changes in the + * following order: + * + * 1. Replace existing preedit string with the cursor. 2. Delete + * requested surrounding text. 3. Insert commit string with the + * cursor at its end. 4. Calculate surrounding text to send. 5. + * Insert new preedit text in cursor position. 6. Place cursor + * inside preedit text. 7. Perform the requested action. + * + * The serial number reflects the last state of the + * zwp_text_input_v3 object known to the compositor. The value of + * the serial argument must be equal to the number of commit + * requests already issued on that object. + * + * When the client receives a done event with a serial different + * than the number of past commit requests, it must proceed with + * evaluating and applying the changes as normal, except it should + * not change the current state of the zwp_text_input_v3 object. + * All pending state requests (set_surrounding_text, + * set_content_type and set_cursor_rectangle) on the + * zwp_text_input_v3 object should be sent and committed after + * receiving a zwp_text_input_v3.done event with a matching serial. + */ + void (*done)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t serial); + /** + * action performed + * + * An action was performed on this text input. + * + * Values set with this event are double-buffered. They must be + * applied and reset to initial on the next zwp_text_input_v3.done + * event. + * + * The initial value of action is none. + * @param action action performed + * @param serial serial number of the action event + * @since 2 + */ + void (*action)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t action, + uint32_t serial); + /** + * notify of language selection + * + * Notify the application of language used by the input method. + * + * This event will be sent on creation if known and for all + * subsequent changes. + * + * The language should be specified as an IETF BCP 47 tag. Setting + * an empty string will reset any known language back to the + * default unknown state. + * @param language new language set by IME + * @since 2 + */ + void (*language)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + const char *language); + /** + * pre-edit + * + * Notify of contextual hints for the pre-edit string. This event + * is always sent together with a zwp_text_input_v3.preedit_string + * event. + * + * The parameters start and end are counted in bytes relative to + * the beginning of the text buffer submitted through + * zwp_text_input_v3.preedit_string, and represent the substring in + * the pre-edit text affected by the hint. + * + * Multiple events may be submitted if the preedit string has + * different sections. The extent of hints may overlap. The parts + * of the preedit string that are not covered by any + * zwp_text_input_v3.preedit_hint event, the text will be + * considered unhinted. This is also the case if no preedit_hint + * event is sent. + * + * Clients should provide recognizable visuals to these hints. if + * they are unable to comply with this requisition, it may be + * preferable for them keep the preedit_shown content hint + * disabled. + * + * Values set with this event are double-buffered. They must be + * applied and reset on the next zwp_text_input_v3.done event. + * @param start starting point of the affected substring + * @param end end point of the affected substring + * @param hint hint to apply + * @since 2 + */ + void (*preedit_hint)(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t start, + uint32_t end, + uint32_t hint); +}; + +/** + * @ingroup iface_zwp_text_input_v3 + */ +static inline int +zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3, + const struct zwp_text_input_v3_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3, + (void (**)(void)) listener, data); +} + +#define ZWP_TEXT_INPUT_V3_DESTROY 0 +#define ZWP_TEXT_INPUT_V3_ENABLE 1 +#define ZWP_TEXT_INPUT_V3_DISABLE 2 +#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3 +#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4 +#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5 +#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6 +#define ZWP_TEXT_INPUT_V3_COMMIT 7 +#define ZWP_TEXT_INPUT_V3_SET_AVAILABLE_ACTIONS 8 +#define ZWP_TEXT_INPUT_V3_SHOW_INPUT_PANEL 9 +#define ZWP_TEXT_INPUT_V3_HIDE_INPUT_PANEL 10 + +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_ACTION_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_LANGUAGE_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SINCE_VERSION 2 + +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_AVAILABLE_ACTIONS_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SHOW_INPUT_PANEL_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_HIDE_INPUT_PANEL_SINCE_VERSION 2 + +/** @ingroup iface_zwp_text_input_v3 */ +static inline void +zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data); +} + +/** @ingroup iface_zwp_text_input_v3 */ +static inline void * +zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3); +} + +static inline uint32_t +zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Destroy the wp_text_input object. Also disables all surfaces enabled + * through this wp_text_input object. + */ +static inline void +zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Requests text input on the surface previously obtained from the enter + * event. + * + * This request must be issued every time the focused text input changes + * to a new one, including within the current surface. Use + * zwp_text_input_v3.disable when there is no longer any input focus on + * the current surface. + * + * Clients must not enable more than one text input on the single seat + * and should disable the current text input before enabling the new one. + * Requests to enable a text input when another text input is enabled + * on the same seat must be ignored by compositor. + * + * This request resets all state associated with previous enable, disable, + * set_surrounding_text, set_text_change_cause, set_content_type, and + * set_cursor_rectangle requests, as well as the state associated with + * preedit_string, commit_string, and delete_surrounding_text events. + * + * The set_surrounding_text, set_content_type and set_cursor_rectangle + * requests must follow if the text input supports the necessary + * functionality. + * + * State set with this request is double-buffered. It will get applied on + * the next zwp_text_input_v3.commit request, and stay valid until the + * next committed enable or disable request. + * + * The changes must be applied by the compositor after issuing a + * zwp_text_input_v3.commit request. + */ +static inline void +zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_ENABLE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Explicitly disable text input on the current surface (typically when + * there is no focus on any text entry inside the surface). + * + * State set with this request is double-buffered. It will get applied on + * the next zwp_text_input_v3.commit request. + */ +static inline void +zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_DISABLE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Sets the surrounding plain text around the input, excluding the preedit + * text. + * + * The client should notify the compositor of any changes in any of the + * values carried with this request, including changes caused by handling + * incoming text-input events as well as changes caused by other + * mechanisms like keyboard typing. + * + * If the client is unaware of the text around the cursor, it should not + * issue this request, to signify lack of support to the compositor. + * + * Text is UTF-8 encoded, and should include the cursor position, the + * complete selection and additional characters before and after them. + * There is a maximum length of wayland messages, so text can not be + * longer than 4000 bytes. + * + * Cursor is the byte offset of the cursor within text buffer. + * + * Anchor is the byte offset of the selection anchor within text buffer. + * If there is no selected text, anchor is the same as cursor. + * + * If any preedit text is present, it is replaced with a cursor for the + * purpose of this event. + * + * Values set with this request are double-buffered. They will get applied + * on the next zwp_text_input_v3.commit request, and stay valid until the + * next committed enable or disable request. + * + * The initial state for affected fields is empty, meaning that the text + * input does not support sending surrounding text. If the empty values + * get applied, subsequent attempts to change them may have no effect. + */ +static inline void +zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, text, cursor, anchor); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Tells the compositor why the text surrounding the cursor changed. + * + * Whenever the client detects an external change in text, cursor, or + * anchor posision, it must issue this request to the compositor. This + * request is intended to give the input method a chance to update the + * preedit text in an appropriate way, e.g. by removing it when the user + * starts typing with a keyboard. + * + * cause describes the source of the change. + * + * The value set with this request is double-buffered. It must be applied + * and reset to initial at the next zwp_text_input_v3.commit request. + * + * The initial value of cause is input_method. + */ +static inline void +zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, cause); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Sets the content purpose and content hint. While the purpose is the + * basic purpose of an input field, the hint flags allow to modify some of + * the behavior. + * + * Values set with this request are double-buffered. They will get applied + * on the next zwp_text_input_v3.commit request. + * Subsequent attempts to update them may have no effect. The values + * remain valid until the next committed enable or disable request. + * + * The initial value for hint is none, and the initial value for purpose + * is normal. + */ +static inline void +zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, hint, purpose); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Marks an area around the cursor as a x, y, width, height rectangle in + * surface local coordinates. + * + * Allows the compositor to put a window with word suggestions near the + * cursor, without obstructing the text being input. + * + * If the client is unaware of the position of edited text, it should not + * issue this request, to signify lack of support to the compositor. + * + * Values set with this request are double-buffered. They will get applied + * on the next zwp_text_input_v3.commit request, and stay valid until the + * next committed enable or disable request. + * + * The initial values describing a cursor rectangle are empty. That means + * the text input does not support describing the cursor area. If the + * empty values get applied, subsequent attempts to change them may have + * no effect. + * + * As of version 2, the zwp_text_input_v3.commit request does not apply + * values sent with this request. Instead, it stores them in a separate + * "committed" area. The committed values, if still valid, get applied on + * the next wl_surface.commit request on the surface with text-input focus. + * Both committed and applied values get invalidated on: + * + * - the next committed enable or disable request, or + * - a change of the focused surface of the text-input (leave or enter events). + * + * This double stage application allows the compositor to position + * the input method popup in the same frame as the contents + * of the text on the surface are updated. + */ +static inline void +zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, x, y, width, height); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Atomically applies state changes recently sent to the compositor. + * + * The commit request establishes and updates the state of the client, and + * must be issued after any changes to apply them. + * + * Text input state (enabled status, content purpose, content hint, + * surrounding text and change cause, cursor rectangle) is conceptually + * double-buffered within the context of a text input, i.e. between a + * committed enable request and the following committed enable or disable + * request. + * + * Protocol requests modify the pending state, as opposed to the current + * state in use by the input method. A commit request atomically applies + * all pending state, replacing the current state. After commit, the new + * pending state is as documented for each related request. + * + * Requests are applied in the order of arrival. + * + * Neither current nor pending state are modified unless noted otherwise. + * + * The compositor must count the number of commit requests coming from + * each zwp_text_input_v3 object and use the count as the serial in done + * events. + */ +static inline void +zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Set the actions available for this text input. + * + * Values set with this request are double-buffered. They will get applied + * on the next zwp_text_input_v3.commit request. + * + * If the available_actions array contains the none action, or contains the + * same action multiple times, the compositor must raise the invalid_action + * protocol error. + * + * Initially, no actions are available. + */ +static inline void +zwp_text_input_v3_set_available_actions(struct zwp_text_input_v3 *zwp_text_input_v3, struct wl_array *available_actions) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_SET_AVAILABLE_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, available_actions); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Requests an input panel to be shown (e.g. a on-screen keyboard). + * + * This request only hints the desired interaction pattern from the + * client side, and its effect may be ignored by compositors given + * other environmental factors. Repeated calls will be ignored. + */ +static inline void +zwp_text_input_v3_show_input_panel(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_SHOW_INPUT_PANEL, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * + * Requests an input panel to be hidden. + * + * This request only hints the desired interaction pattern from the + * client side, and its effect may be ignored by compositors given + * other environmental factors. Repeated calls will be ignored. + */ +static inline void +zwp_text_input_v3_hide_input_panel(struct zwp_text_input_v3 *zwp_text_input_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3, + ZWP_TEXT_INPUT_V3_HIDE_INPUT_PANEL, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0); +} + +#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0 +#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1 + + +/** + * @ingroup iface_zwp_text_input_manager_v3 + */ +#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_manager_v3 + */ +#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1 + +/** @ingroup iface_zwp_text_input_manager_v3 */ +static inline void +zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data); +} + +/** @ingroup iface_zwp_text_input_manager_v3 */ +static inline void * +zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3); +} + +static inline uint32_t +zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3) +{ + return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3); +} + +/** + * @ingroup iface_zwp_text_input_manager_v3 + * + * Destroy the wp_text_input_manager object. + */ +static inline void +zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_manager_v3, + ZWP_TEXT_INPUT_MANAGER_V3_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_zwp_text_input_manager_v3 + * + * Creates a new text-input object for a given seat. + */ +static inline struct zwp_text_input_v3 * +zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_manager_v3, + ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3), 0, NULL, seat); + + return (struct zwp_text_input_v3 *) id; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/wayland/GNUmakefile b/Source/wayland/GNUmakefile index 0022b119..25951877 100644 --- a/Source/wayland/GNUmakefile +++ b/Source/wayland/GNUmakefile @@ -42,6 +42,7 @@ wayland_C_FILES = \ xdg-shell-protocol.c \ wlr-layer-shell-protocol.c \ xdg-decoration-unstable-v1-protocol.c \ +text-input-unstable-v3-protocol.c \ # The Objective-C source files to be compiled wayland_OBJC_FILES = \ diff --git a/Source/wayland/WaylandInputServer.m b/Source/wayland/WaylandInputServer.m index 060743b9..dc4e81de 100644 --- a/Source/wayland/WaylandInputServer.m +++ b/Source/wayland/WaylandInputServer.m @@ -1,4 +1,4 @@ -/* WaylandInputServer - Keyboard input handling for Wayland backend +/* WaylandInputServer - Input method / preedit support for Wayland backend Copyright (C) 2024 Free Software Foundation, Inc. @@ -28,18 +28,32 @@ #include #include #include +#include #include #include #include "wayland/WaylandInputServer.h" +/* Commit pending text_input state to the compositor. */ +static void +commit_text_input(WaylandConfig *wlconfig) +{ + if (wlconfig && wlconfig->text_input) + { + zwp_text_input_v3_commit(wlconfig->text_input); + wl_display_flush(wlconfig->display); + } +} + + @implementation WaylandInputServer - (id) initWithDelegate: (id)aDelegate name: (NSString *)name { - delegate = aDelegate; + delegate = aDelegate; ASSIGN(server_name, name); focused_window_id = 0; + wlconfig = NULL; NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: initialized"); return self; } @@ -50,6 +64,11 @@ - (void) dealloc [super dealloc]; } +- (void) setWlconfig: (WaylandConfig *)config +{ + wlconfig = config; +} + - (void) setFocusedWindowId: (int)windowId { focused_window_id = windowId; @@ -61,6 +80,7 @@ - (int) focusedWindowId return focused_window_id; } + /* NSInputServiceProvider protocol */ - (void) activeConversationChanged: (id)sender @@ -71,13 +91,13 @@ - (void) activeConversationChanged: (id)sender if ([sender respondsToSelector: @selector(window)] == NO) return; - /* sender is a text client; -window is checked via respondsToSelector above */ NSWindow *window = [sender performSelector: @selector(window)]; if (window != nil) { focused_window_id = [window windowNumber]; - NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: conversation changed, focused window = %d", - focused_window_id); + NSDebugMLLog(@"WaylandIME", + @"WaylandInputServer: conversation changed, focused window = %d", + focused_window_id); } } @@ -85,25 +105,27 @@ - (void) activeConversationWillChange: (id)sender fromOldConversation: (long)oldConversation { [super activeConversationWillChange: sender - fromOldConversation: oldConversation]; + fromOldConversation: oldConversation]; } @end + @implementation WaylandInputServer (InputMethod) - (NSString *) inputMethodStyle { - /* Wayland keyboard input is handled directly via XKB in WaylandServer+Keyboard.m. - No input method overlay is used. */ + /* When text_input_v3 is available the preedit is delivered inline via + * setMarkedText:selectedRange: on the focused responder; no separate IM + * window is needed. Return nil so AppKit does not try to manage an IM + * panel on our behalf. */ return nil; } - (NSString *) fontSize: (int *)size { - NSString *str; - - str = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSFontSize"]; + NSString *str = [[NSUserDefaults standardUserDefaults] + stringForKey: @"NSFontSize"]; if (!str) str = @"12"; if (size) @@ -122,34 +144,143 @@ - (BOOL) clientWindowRect: (NSRect *)rect return YES; } +/* ── IME geometry: status area ───────────────────────────────────────────── + * + * The status area is where the IM draws its mode indicator (e.g. "あ" for + * hiragana mode). We report the bottom-left corner of the focused window + * as a reasonable default; a real status bar is not rendered in the backend. + */ - (BOOL) statusArea: (NSRect *)rect { - return NO; + if (!rect) + return NO; + + if (focused_window_id != 0) + { + NSWindow *window = GSWindowWithNumber(focused_window_id); + if (window) + { + NSRect frame = [window frame]; + *rect = NSMakeRect(frame.origin.x, + frame.origin.y, + frame.size.width, + 20.0); + return YES; + } + } + + /* Fallback: bottom-left of screen. */ + NSRect screen = [[NSScreen mainScreen] frame]; + *rect = NSMakeRect(screen.origin.x, screen.origin.y, screen.size.width, 20.0); + return YES; } +/* ── IME geometry: preedit area ───────────────────────────────────────────── + * + * The preedit area covers the region where candidate text is displayed. + * We return the stored rect if we have one, otherwise the client window rect. + */ - (BOOL) preeditArea: (NSRect *)rect { - return NO; + if (!rect) + return NO; + + if (wlconfig && !NSIsEmptyRect(wlconfig->ime_preedit_rect)) + { + *rect = wlconfig->ime_preedit_rect; + return YES; + } + + return [self clientWindowRect: rect]; } +/* ── IME geometry: preedit spot ───────────────────────────────────────────── + * + * The preedit spot is the screen coordinate of the text-insertion cursor. + * The compositor IM uses this to position its candidate window. + */ - (BOOL) preeditSpot: (NSPoint *)p { + if (!p) + return NO; + + if (wlconfig && wlconfig->text_input_active) + { + *p = wlconfig->ime_preedit_spot; + return YES; + } + + /* Fallback: centre of the focused window. */ + if (focused_window_id != 0) + { + NSWindow *window = GSWindowWithNumber(focused_window_id); + if (window) + { + NSRect frame = [window frame]; + *p = NSMakePoint(NSMidX(frame), NSMidY(frame)); + return YES; + } + } + return NO; } +/* ── IME geometry setters ─────────────────────────────────────────────────── + * + * AppKit calls these when the text cursor moves. We store the values and + * forward them to the compositor via set_cursor_rectangle so the IM can + * reposition its candidate window. + */ - (BOOL) setStatusArea: (NSRect *)rect { - return NO; + /* Status area is compositor-managed; acknowledge but don't act. */ + return YES; } - (BOOL) setPreeditArea: (NSRect *)rect { - return NO; + if (!rect || !wlconfig) + return NO; + + wlconfig->ime_preedit_rect = *rect; + + if (wlconfig->text_input && wlconfig->text_input_active) + { + zwp_text_input_v3_set_cursor_rectangle( + wlconfig->text_input, + (int32_t) rect->origin.x, + (int32_t) rect->origin.y, + (int32_t) rect->size.width, + (int32_t) rect->size.height); + commit_text_input(wlconfig); + } + + return YES; } - (BOOL) setPreeditSpot: (NSPoint *)p { - return NO; + if (!p || !wlconfig) + return NO; + + wlconfig->ime_preedit_spot = *p; + + /* Forward cursor position as a 1×(line-height) rectangle. */ + if (wlconfig->text_input && wlconfig->text_input_active) + { + int32_t lineHeight = 16; /* sensible default; AppKit can update via setPreeditArea: */ + zwp_text_input_v3_set_cursor_rectangle( + wlconfig->text_input, + (int32_t) p->x, + (int32_t) p->y, + 0, + lineHeight); + commit_text_input(wlconfig); + NSDebugMLLog(@"WaylandIME", + @"WaylandInputServer: preedit spot → (%g,%g)", p->x, p->y); + } + + return YES; } @end diff --git a/Source/wayland/WaylandServer+Keyboard.m b/Source/wayland/WaylandServer+Keyboard.m index 651a144b..43d89088 100644 --- a/Source/wayland/WaylandServer+Keyboard.m +++ b/Source/wayland/WaylandServer+Keyboard.m @@ -1,5 +1,5 @@ -/* - WaylandServer - Keyboard Handling +/* + WaylandServer - Keyboard Handling + zwp_text_input_v3 (IME/preedit) Copyright (C) 2020 Free Software Foundation, Inc. @@ -27,15 +27,29 @@ #include "wayland/WaylandServer.h" #include + +/* Informal protocol for marked (preedit) text — implemented by NSTextView. */ +@interface NSObject (WaylandMarkedText) +- (void) setMarkedText: (id)aString selectedRange: (NSRange)selRange; +- (void) unmarkText; +- (void) insertText: (id)aString; +@end +#include +#include #include #include #include +#include #include #include -#include #include +#include +#include #include + +/* ── wl_keyboard listener ─────────────────────────────────────────────────── */ + static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) @@ -133,6 +147,13 @@ data1:0 data2:0]; [nswindow sendEvent:ev]; + + /* Enable text input so the compositor IM can send preedit/commit events. */ + if (wlconfig->text_input) + { + zwp_text_input_v3_enable(wlconfig->text_input); + zwp_text_input_v3_commit(wlconfig->text_input); + } } static void @@ -144,7 +165,22 @@ NSDebugFLLog(@"WaylandIME", @"keyboard_handle_leave: serial=%u", serial); if (!wlconfig->keyboard_focus) - return; + { + /* Disable text input even if we lost track of focus window. */ + if (wlconfig->text_input) + { + zwp_text_input_v3_disable(wlconfig->text_input); + zwp_text_input_v3_commit(wlconfig->text_input); + } + return; + } + + /* Clear any pending preedit before disabling. */ + if (wlconfig->ime_pending_preedit) + { + free(wlconfig->ime_pending_preedit); + wlconfig->ime_pending_preedit = NULL; + } NSWindow *nswindow = GSWindowWithNumber(wlconfig->keyboard_focus->window_id); if (nswindow) @@ -162,6 +198,13 @@ } wlconfig->keyboard_focus = NULL; + + if (wlconfig->text_input) + { + zwp_text_input_v3_disable(wlconfig->text_input); + zwp_text_input_v3_commit(wlconfig->text_input); + wlconfig->text_input_active = NO; + } } static void @@ -170,12 +213,10 @@ uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - // NSDebugLog(@"keyboard_handle_modifiers"); WaylandConfig *wlconfig = data; wlconfig->event_serial = serial; xkb_mod_mask_t mask; - /* If we're not using a keymap, then we don't handle PC-style modifiers */ if (!wlconfig->xkb.keymap) return; @@ -197,54 +238,50 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w) { - // NSDebugLog(@"keyboard_handle_key: %d", key); - WaylandConfig *wlconfig = data; + WaylandConfig *wlconfig = data; wlconfig->event_serial = serial; - uint32_t code, num_syms; enum wl_keyboard_key_state state = state_w; - const xkb_keysym_t *syms; - xkb_keysym_t sym; + uint32_t code; - /* Key events follow keyboard focus, not pointer position. Fall back to - pointer focus only when the compositor hasn't sent keyboard_enter yet. */ struct window *window = wlconfig->keyboard_focus ? wlconfig->keyboard_focus : wlconfig->pointer.focus; if (!window) return; - code = 0; - if (key == 28) - { - sym = NSCarriageReturnCharacter; - } - else if (key == 14) - { - sym = NSDeleteCharacter; - } - else - { - code = key + 8; + /* Resolve the XKB keycode (evdev + 8 offset). */ + code = key + 8; - num_syms = xkb_state_key_get_syms(wlconfig->xkb.state, code, &syms); + /* Build the character string for this keypress. + * + * xkb_state_key_get_utf8 handles the full XKB composition pipeline, + * including dead-key sequences (e.g. dead_acute + 'e' → 'é'). + * It returns 0 for non-printable keysyms (arrows, function keys, etc.). + */ + char utf8buf[16] = {0}; + NSString *s = @""; - sym = XKB_KEY_NoSymbol; - if (num_syms == 1) - sym = syms[0]; + if (key == 28) /* Enter / Return */ + { + unichar cr = NSCarriageReturnCharacter; + s = [NSString stringWithCharacters:&cr length:1]; } - - NSString *s = [NSString stringWithUTF8String:&sym]; - NSEventType eventType; - - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + else if (key == 14) /* Backspace */ { - eventType = NSKeyDown; + unichar del = NSDeleteCharacter; + s = [NSString stringWithCharacters:&del length:1]; } - else + else if (wlconfig->xkb.state) { - eventType = NSKeyUp; + int len = xkb_state_key_get_utf8(wlconfig->xkb.state, code, + utf8buf, sizeof(utf8buf) - 1); + if (len > 0) + s = [NSString stringWithUTF8String:utf8buf]; } + NSEventType eventType = (state == WL_KEYBOARD_KEY_STATE_PRESSED) + ? NSKeyDown : NSKeyUp; + NSEvent *ev = [NSEvent keyEventWithType:eventType location:NSZeroPoint modifierFlags:wlconfig->modifiers @@ -257,15 +294,13 @@ keyCode:code]; [GSCurrentServer() postEvent:ev atStart:NO]; - - // NSDebugLog(@"keyboard_handle_key: %@", s); } static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) { - // NSDebugLog(@"keyboard_handle_repeat_info"); + /* Key repeat is handled by AppKit; nothing to do here. */ } const struct wl_keyboard_listener keyboard_listener @@ -273,7 +308,244 @@ keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, keyboard_handle_repeat_info}; -@implementation -WaylandServer (KeyboardOps) + +/* ── zwp_text_input_v3 listener (IME/preedit) ────────────────────────────── */ + +/* Apply pending preedit to the focused text view via setMarkedText:. */ +static void +apply_pending_preedit(WaylandConfig *wlconfig) +{ + if (!wlconfig->keyboard_focus || !wlconfig->ime_pending_preedit) + return; + + NSWindow *win = GSWindowWithNumber(wlconfig->keyboard_focus->window_id); + if (!win) + return; + + id responder = [win firstResponder]; + if (![responder respondsToSelector:@selector(setMarkedText:selectedRange:)]) + return; + + NSString *preeditStr = + [NSString stringWithUTF8String:wlconfig->ime_pending_preedit]; + if (!preeditStr) + return; + + /* Underline the preedit text — standard IM convention. */ + NSAttributedString *marked = + [[NSAttributedString alloc] + initWithString:preeditStr + attributes:@{NSUnderlineStyleAttributeName: + @(NSUnderlineStyleSingle)}]; + + /* Convert byte cursor offsets to character offsets (UTF-8 → UTF-16). + * For simplicity we use the midpoint of the preedit range as selection. */ + NSUInteger len = [preeditStr length]; + NSRange sel = (len > 0) ? NSMakeRange(len / 2, 0) : NSMakeRange(0, 0); + + [responder setMarkedText:marked selectedRange:sel]; + [marked release]; +} + +/* Clear any marked text that was set by the IME. */ +static void +clear_marked_text(WaylandConfig *wlconfig) +{ + if (!wlconfig->keyboard_focus) + return; + NSWindow *win = GSWindowWithNumber(wlconfig->keyboard_focus->window_id); + if (!win) + return; + id responder = [win firstResponder]; + if ([responder respondsToSelector:@selector(unmarkText)]) + [responder unmarkText]; +} + +static void +text_input_enter(void *data, struct zwp_text_input_v3 *ti, + struct wl_surface *surface) +{ + WaylandConfig *wlconfig = data; + wlconfig->text_input_active = YES; + NSDebugFLLog(@"WaylandIME", @"text_input_enter"); + + zwp_text_input_v3_enable(ti); + zwp_text_input_v3_commit(ti); +} + +static void +text_input_leave(void *data, struct zwp_text_input_v3 *ti, + struct wl_surface *surface) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandIME", @"text_input_leave"); + + /* Clear any preedit text the IM left behind. */ + if (wlconfig->ime_pending_preedit) + { + clear_marked_text(wlconfig); + free(wlconfig->ime_pending_preedit); + wlconfig->ime_pending_preedit = NULL; + } + if (wlconfig->ime_pending_commit) + { + free(wlconfig->ime_pending_commit); + wlconfig->ime_pending_commit = NULL; + } + + wlconfig->text_input_active = NO; + zwp_text_input_v3_disable(ti); + zwp_text_input_v3_commit(ti); +} + +static void +text_input_preedit_string(void *data, struct zwp_text_input_v3 *ti, + const char *text, int32_t cursor_begin, + int32_t cursor_end) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandIME", @"text_input_preedit: '%s' [%d,%d]", + text ? text : "", cursor_begin, cursor_end); + + free(wlconfig->ime_pending_preedit); + wlconfig->ime_pending_preedit = text ? strdup(text) : NULL; + wlconfig->ime_preedit_cursor_begin = cursor_begin; + wlconfig->ime_preedit_cursor_end = cursor_end; +} + +static void +text_input_commit_string(void *data, struct zwp_text_input_v3 *ti, + const char *text) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandIME", @"text_input_commit: '%s'", text ? text : ""); + + free(wlconfig->ime_pending_commit); + wlconfig->ime_pending_commit = text ? strdup(text) : NULL; +} + +static void +text_input_delete_surrounding_text(void *data, struct zwp_text_input_v3 *ti, + uint32_t before_length, + uint32_t after_length) +{ + NSDebugFLLog(@"WaylandIME", + @"text_input_delete_surrounding: before=%u after=%u", + before_length, after_length); + /* Full surrounding text deletion is deferred to a later milestone. */ +} + +static void +text_input_done(void *data, struct zwp_text_input_v3 *ti, uint32_t serial) +{ + WaylandConfig *wlconfig = data; + wlconfig->ime_serial = serial; + NSDebugFLLog(@"WaylandIME", @"text_input_done: serial=%u", serial); + + struct window *window = wlconfig->keyboard_focus; + if (!window) + goto cleanup; + + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (!nswindow) + goto cleanup; + + id responder = [nswindow firstResponder]; + + /* ── Commit string: insert text into the focused control ── */ + if (wlconfig->ime_pending_commit) + { + NSString *commitStr = + [NSString stringWithUTF8String:wlconfig->ime_pending_commit]; + if (commitStr && [commitStr length] > 0) + { + /* Clear any preedit before committing. */ + if ([responder respondsToSelector:@selector(unmarkText)]) + [responder unmarkText]; + + if ([responder respondsToSelector:@selector(insertText:)]) + { + [responder insertText:commitStr]; + } + else + { + /* Fallback: deliver each character as a key event. */ + for (NSUInteger i = 0; i < [commitStr length]; i++) + { + unichar c = [commitStr characterAtIndex:i]; + NSString *cs = [NSString stringWithCharacters:&c length:1]; + NSEvent *ev = [NSEvent keyEventWithType:NSKeyDown + location:NSZeroPoint + modifierFlags:0 + timestamp:[[NSDate date] + timeIntervalSinceReferenceDate] + windowNumber:window->window_id + context:nil + characters:cs + charactersIgnoringModifiers:cs + isARepeat:NO + keyCode:0]; + [nswindow sendEvent:ev]; + } + } + } + free(wlconfig->ime_pending_commit); + wlconfig->ime_pending_commit = NULL; + } + + /* ── Preedit string: show/update marked text ── */ + if (wlconfig->ime_pending_preedit) + { + apply_pending_preedit(wlconfig); + /* Keep ime_pending_preedit alive for area/spot queries. */ + } + else + { + /* NULL preedit = clear any marked text the IM set previously. */ + clear_marked_text(wlconfig); + } + +cleanup:; +} + +/* v2 events — logged but not acted on in this milestone. */ +static void +text_input_action(void *data, struct zwp_text_input_v3 *ti, + uint32_t index, uint32_t direction) +{ + NSDebugFLLog(@"WaylandIME", @"text_input_action: idx=%u dir=%u", + index, direction); +} + +static void +text_input_language(void *data, struct zwp_text_input_v3 *ti, + const char *language) +{ + NSDebugFLLog(@"WaylandIME", @"text_input_language: %s", + language ? language : ""); +} + +static void +text_input_preedit_hint(void *data, struct zwp_text_input_v3 *ti, + uint32_t start, uint32_t end, uint32_t hint) +{ + NSDebugFLLog(@"WaylandIME", @"text_input_preedit_hint: [%u,%u] hint=%u", + start, end, hint); +} + +const struct zwp_text_input_v3_listener text_input_v3_listener = { + text_input_enter, + text_input_leave, + text_input_preedit_string, + text_input_commit_string, + text_input_delete_surrounding_text, + text_input_done, + text_input_action, + text_input_language, + text_input_preedit_hint, +}; + + +@implementation WaylandServer (KeyboardOps) @end diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 79234f8c..8041a79c 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -52,6 +52,7 @@ extern const struct wl_output_listener output_listener; extern const struct wl_seat_listener seat_listener; extern const struct wl_data_device_listener data_device_listener; +extern const struct zwp_text_input_v3_listener text_input_v3_listener; static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) @@ -154,6 +155,12 @@ = wl_registry_bind(registry, name, &wl_data_device_manager_interface, v); NSDebugLog(@"wayland: found wl_data_device_manager (version %u)", v); } + else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) + { + wlconfig->text_input_manager + = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1); + NSDebugLog(@"wayland: found zwp_text_input_manager_v3"); + } } static void handle_global_remove(void *data, struct wl_registry *registry, @@ -226,6 +233,19 @@ - (id)_initWaylandContext wl_display_dispatch(wlconfig->display); wl_display_roundtrip(wlconfig->display); + /* Create text_input once both text_input_manager and seat are available. */ + if (wlconfig->text_input_manager && wlconfig->seat) + { + wlconfig->text_input = zwp_text_input_manager_v3_get_text_input( + wlconfig->text_input_manager, wlconfig->seat); + if (wlconfig->text_input) + { + zwp_text_input_v3_add_listener(wlconfig->text_input, + &text_input_v3_listener, wlconfig); + NSDebugLog(@"wayland: zwp_text_input_v3 created"); + } + } + /* Get a data device now that both seat and data_device_manager are bound. */ if (wlconfig->data_device_manager && wlconfig->seat) { @@ -275,6 +295,7 @@ - (id)_initWaylandContext inputServer = [[WaylandInputServer allocWithZone: [self zone]] initWithDelegate: nil name: @"WaylandInput"]; + [(WaylandInputServer *)inputServer setWlconfig: wlconfig]; return self; } diff --git a/Source/wayland/text-input-unstable-v3-protocol.c b/Source/wayland/text-input-unstable-v3-protocol.c new file mode 100644 index 00000000..c723e407 --- /dev/null +++ b/Source/wayland/text-input-unstable-v3-protocol.c @@ -0,0 +1,93 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright © 2012, 2013 Intel Corporation + * Copyright © 2015, 2016 Jan Arne Petersen + * Copyright © 2017, 2018 Red Hat, Inc. + * Copyright © 2018 Purism SPC + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface zwp_text_input_v3_interface; + +static const struct wl_interface *text_input_unstable_v3_types[] = { + NULL, + NULL, + NULL, + NULL, + &wl_surface_interface, + &wl_surface_interface, + &zwp_text_input_v3_interface, + &wl_seat_interface, +}; + +static const struct wl_message zwp_text_input_v3_requests[] = { + { "destroy", "", text_input_unstable_v3_types + 0 }, + { "enable", "", text_input_unstable_v3_types + 0 }, + { "disable", "", text_input_unstable_v3_types + 0 }, + { "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 }, + { "set_text_change_cause", "u", text_input_unstable_v3_types + 0 }, + { "set_content_type", "uu", text_input_unstable_v3_types + 0 }, + { "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 }, + { "commit", "", text_input_unstable_v3_types + 0 }, + { "set_available_actions", "2a", text_input_unstable_v3_types + 0 }, + { "show_input_panel", "2", text_input_unstable_v3_types + 0 }, + { "hide_input_panel", "2", text_input_unstable_v3_types + 0 }, +}; + +static const struct wl_message zwp_text_input_v3_events[] = { + { "enter", "o", text_input_unstable_v3_types + 4 }, + { "leave", "o", text_input_unstable_v3_types + 5 }, + { "preedit_string", "?sii", text_input_unstable_v3_types + 0 }, + { "commit_string", "?s", text_input_unstable_v3_types + 0 }, + { "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 }, + { "done", "u", text_input_unstable_v3_types + 0 }, + { "action", "2uu", text_input_unstable_v3_types + 0 }, + { "language", "2s", text_input_unstable_v3_types + 0 }, + { "preedit_hint", "2uuu", text_input_unstable_v3_types + 0 }, +}; + +WL_EXPORT const struct wl_interface zwp_text_input_v3_interface = { + "zwp_text_input_v3", 2, + 11, zwp_text_input_v3_requests, + 9, zwp_text_input_v3_events, +}; + +static const struct wl_message zwp_text_input_manager_v3_requests[] = { + { "destroy", "", text_input_unstable_v3_types + 0 }, + { "get_text_input", "no", text_input_unstable_v3_types + 6 }, +}; + +WL_EXPORT const struct wl_interface zwp_text_input_manager_v3_interface = { + "zwp_text_input_manager_v3", 2, + 2, zwp_text_input_manager_v3_requests, + 0, NULL, +}; + From 2accc8b0018b9717e1f512999a353559c14bc5e2 Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 10:31:22 +1000 Subject: [PATCH 13/24] Wayland M3: extra mouse buttons + per-frame scroll accumulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Button mapping: - Handle BTN_SIDE (0x113), BTN_EXTRA (0x114), BTN_FORWARD (0x115), BTN_BACK (0x116), BTN_TASK (0x117) and any further evdev button codes as NSOtherMouseDown/Up instead of silently discarding them. - Fix buttonNumber: was passing the raw evdev code (272, 273, …); now uses button - BTN_LEFT (0=left, 1=right, 2=middle, 3=side, 4=extra, …) matching AppKit convention and the X11 backend's XGetPointerMapping logic. - pointer_handle_motion: default case now produces NSOtherMouseDragged for any held button beyond right, covering middle/side/extra drags. Per-frame scroll accumulation: - Bump wl_seat binding from v1 to v5 so the compositor delivers wl_pointer.frame, axis_source, axis_stop, and axis_discrete events. - Add frame accumulation fields to struct pointer in WaylandConfig: frame_has_axis, frame_deltaX/Y, frame_discrete_x/y, frame_time. - pointer_handle_axis: accumulate delta into the frame state instead of dispatching immediately; avoids two separate NSScrollWheel events when both vertical and horizontal axes arrive in the same compositor frame. - pointer_handle_axis_discrete: store the integer step count per axis in the frame state (logged alongside the smooth delta in frame dispatch). - pointer_handle_axis_source: unchanged logic; now also logs source. - pointer_handle_frame: dispatch the accumulated axes as one NSScrollWheel event then reset all per-frame state; includes debug log with deltas, discrete steps, and source for traceability under WaylandScroll. - pointer_handle_axis_stop: emit a zero-delta NSScrollWheel so AppKit sees that a touchpad gesture ended (basis for future momentum support). Co-Authored-By: Claude Sonnet 4.6 --- Headers/wayland/WaylandServer.h | 9 + Source/wayland/WaylandServer+Cursor.m | 245 +++++++++++++++----------- Source/wayland/WaylandServer.m | 7 +- 3 files changed, 153 insertions(+), 108 deletions(-) diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index db06d474..131db206 100644 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -65,6 +65,15 @@ struct pointer struct window *focus; struct window *captured; + /* Per-frame axis accumulation (cleared after each wl_pointer.frame event). + * Populated by axis/axis_discrete; dispatched as one NSScrollWheel in frame. */ + BOOL frame_has_axis; + float frame_deltaX; + float frame_deltaY; + int frame_discrete_x; /* discrete scroll steps this frame, horizontal */ + int frame_discrete_y; /* discrete scroll steps this frame, vertical */ + uint32_t frame_time; /* timestamp of the last axis event in this frame */ + }; struct cursor diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index b9af52f5..73631197 100644 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -263,7 +263,6 @@ if (wlconfig->pointer.button_state == WL_POINTER_BUTTON_STATE_PRESSED) { - switch (wlconfig->pointer.button) { case BTN_LEFT: @@ -272,7 +271,7 @@ case BTN_RIGHT: eventType = NSRightMouseDragged; break; - case BTN_MIDDLE: + default: /* BTN_MIDDLE, BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK, … */ eventType = NSOtherMouseDragged; break; } @@ -400,17 +399,14 @@ case BTN_RIGHT: eventType = NSRightMouseDown; break; - case BTN_MIDDLE: - eventType = NSOtherMouseDown; - break; - /* TODO: handle BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK and other - * constants in libinput. Map to NSOtherMouseDown with appropriate - * buttonNumber per Milestone 3 of wayland_feature_implementation_plan. */ default: + /* BTN_MIDDLE (2), BTN_SIDE (3), BTN_EXTRA (4), + BTN_FORWARD (5), BTN_BACK (6), BTN_TASK (7), … */ + eventType = NSOtherMouseDown; NSDebugFLLog(@"WaylandPointer", - @"pointer_handle_button: unhandled button=0x%x state=%u", - button, state_w); - return; + @"pointer_handle_button: button=0x%x (btn%u) pressed", + button, button - BTN_LEFT); + break; } } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) @@ -435,34 +431,34 @@ case BTN_RIGHT: eventType = NSRightMouseUp; break; - case BTN_MIDDLE: - eventType = NSOtherMouseUp; - break; default: + eventType = NSOtherMouseUp; NSDebugFLLog(@"WaylandPointer", - @"pointer_handle_button: unhandled button=0x%x state=%u", - button, state_w); - return; + @"pointer_handle_button: button=0x%x (btn%u) released", + button, button - BTN_LEFT); + break; } } else { return; } - /* FIXME: unlike in _motion and _axis handlers, the argument used in _button - is the "serial" of the event, not passed and unavailable in _motion and - _axis handlers. Is it allowed to pass "serial" as the eventNumber: in - _button handler, but "time" as the eventNumber: in the _motion and _axis - handlers? */ + /* eventNumber: use the Wayland serial (a monotonically increasing counter + from the compositor) — it is consistent with what the button handlers + receive and matches what the pointer-enter handler uses for serial. */ tick = serial; - /* FIXME: X11 backend uses the XGetPointerMapping()-returned values from - its map_return argument as constants for buttonNumber. As the variant - with buttonNumber: seems to be a GNUstep extension, and the value - internal, it might be ok to just provide libinput constant as we're doing - here. If this is truly correct, please update this comment to document - the correctness of doing so. */ - buttonNumber = button; + /* buttonNumber: map evdev button codes to a 0-based AppKit button index. + * BTN_LEFT (0x110) → 0 (NSLeftMouse*) + * BTN_RIGHT (0x111) → 1 (NSRightMouse*) + * BTN_MIDDLE (0x112) → 2 (NSOtherMouse*) + * BTN_SIDE (0x113) → 3 (NSOtherMouse* — browser back) + * BTN_EXTRA (0x114) → 4 (NSOtherMouse* — browser forward) + * BTN_FORWARD(0x115) → 5 + * BTN_BACK (0x116) → 6 + * This is equivalent to the X11 approach of subtracting the base button + * code, and produces stable values across different mouse hardware. */ + buttonNumber = (int)(button - BTN_LEFT); event = [NSEvent mouseEventWithType:eventType location:eventLocation @@ -488,14 +484,43 @@ } -/* Groups axis events for the same logical frame. - * TODO (Milestone 3): accumulate per-frame deltas before dispatching. */ +/* Accumulate axis delta for this logical frame. + * The event is dispatched as a single NSScrollWheel in pointer_handle_frame. + * This avoids two separate events when a compositor sends both vertical and + * horizontal components in the same frame (common for diagonal touchpad swipes). */ static void -pointer_handle_frame(void *data, struct wl_pointer *pointer) +pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, + uint32_t axis, wl_fixed_t value) { - NSDebugFLLog(@"WaylandScroll", @"pointer_handle_frame"); + WaylandConfig *wlconfig = data; + struct window *window = wlconfig->pointer.focus; + + if (window == NULL || window->ignoreMouse) + return; + + float delta = wl_fixed_to_double(value) * wlconfig->mouse_scroll_multiplier; + + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + wlconfig->pointer.frame_deltaY += delta; + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + wlconfig->pointer.frame_deltaX += delta; + break; + default: + return; + } + + wlconfig->pointer.frame_has_axis = YES; + wlconfig->pointer.frame_time = time; + + NSDebugFLLog(@"WaylandScroll", + @"pointer_handle_axis: axis=%u delta=%g (source=%u)", + axis, delta, wlconfig->pointer.axis_source); } +/* Store the axis source for the current frame (wheel, finger, continuous…). */ static void pointer_handle_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) @@ -505,102 +530,110 @@ NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_source: source=%u", axis_source); } -/* TODO (Milestone 3): emit momentum-phase stop event to AppKit. */ +/* Emit a zero-delta NSScrollWheel to signal that a touchpad gesture ended. + * AppKit uses this to stop inertial scrolling. */ static void pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { - NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_stop: time=%u axis=%u", time, axis); + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_stop: axis=%u", axis); + + struct window *window = wlconfig->pointer.focus; + if (window == NULL || window->ignoreMouse) + return; + + [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired]; + + NSPoint loc = NSMakePoint(wlconfig->pointer.x, window->height - wlconfig->pointer.y); + NSEvent *ev = [NSEvent mouseEventWithType:NSScrollWheel + location:loc + modifierFlags:wlconfig->modifiers + timestamp:(NSTimeInterval)time / 1000.0 + windowNumber:(int)window->window_id + context:GSCurrentContext() + eventNumber:time + clickCount:0 + pressure:0.0 + buttonNumber:0 + deltaX:0.0 + deltaY:0.0 + deltaZ:0.0]; + [GSCurrentServer() postEvent:ev atStart:NO]; } -/* TODO (Milestone 3): include discrete step count in scroll event. */ +/* Accumulate the integer scroll-wheel step count for the current frame. + * Discrete steps are reported alongside the smooth axis value and allow + * applications to snap to whole-line increments. */ static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int discrete) { - NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_discrete: axis=%u discrete=%d", - axis, discrete); + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandScroll", + @"pointer_handle_axis_discrete: axis=%u discrete=%d", axis, discrete); + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + wlconfig->pointer.frame_discrete_y += discrete; + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + wlconfig->pointer.frame_discrete_x += discrete; + break; + } } -// Scroll and other axis notifications. +/* Dispatch the accumulated scroll deltas as a single NSScrollWheel event, + * then reset the per-frame state. Grouping axis events per frame produces + * smoother diagonal scrolling and avoids redundant event dispatch. */ static void -pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, - uint32_t axis, wl_fixed_t value) +pointer_handle_frame(void *data, struct wl_pointer *pointer) { - WaylandConfig *wlconfig = data; - NSEvent *event; - NSEventType eventType; - NSPoint eventLocation; - NSGraphicsContext *gcontext; - unsigned int eventFlags; - float deltaX = 0.0; - float deltaY = 0.0; - int clickCount = 1; - int buttonNumber; + WaylandConfig *wlconfig = data; + + if (!wlconfig->pointer.frame_has_axis) + return; struct window *window = wlconfig->pointer.focus; - if (window->ignoreMouse) - { - return; - } + if (window == NULL || window->ignoreMouse) + goto reset; - [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired]; + { + [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired]; - gcontext = GSCurrentContext(); - eventLocation - = NSMakePoint(wlconfig->pointer.x, window->height - wlconfig->pointer.y); - eventFlags = wlconfig->modifiers; + NSPoint loc = NSMakePoint(wlconfig->pointer.x, + window->height - wlconfig->pointer.y); + NSTimeInterval ts = (NSTimeInterval)wlconfig->pointer.frame_time / 1000.0; - if (wlconfig->pointer.axis_source != WL_POINTER_AXIS_SOURCE_WHEEL) - { - /* axis_source == WL_POINTER_AXIS_SOURCE_FINGER or CONTINUOUS: - * scroll is from a touchpad — momentum phase should be set per - * Milestone 3 of wayland_feature_implementation_plan. */ NSDebugFLLog(@"WaylandScroll", - @"pointer_handle_axis: touch/continuous scroll axis=%u " - @"value=%g source=%u", - axis, wl_fixed_to_double(value), + @"pointer_handle_frame: dx=%g dy=%g disc_x=%d disc_y=%d src=%u", + wlconfig->pointer.frame_deltaX, wlconfig->pointer.frame_deltaY, + wlconfig->pointer.frame_discrete_x, wlconfig->pointer.frame_discrete_y, wlconfig->pointer.axis_source); - } - //float mouse_scroll_multiplier = wlconfig->mouse_scroll_multiplier; - /* For smooth-scroll events, we're not doing any cross-event or delta - calculations, as is done in button event handling. */ - switch (axis) - { - case WL_POINTER_AXIS_VERTICAL_SCROLL: - eventType = NSScrollWheel; - deltaY = wl_fixed_to_double(value) * wlconfig->mouse_scroll_multiplier; - break; - case WL_POINTER_AXIS_HORIZONTAL_SCROLL: - eventType = NSScrollWheel; - deltaX = wl_fixed_to_double(value) * wlconfig->mouse_scroll_multiplier; - break; - } - - /* FIXME: X11 backend uses the XGetPointerMapping()-returned values from - its map_return argument as constants for buttonNumber. As the variant - with buttonNumber: seems to be a GNUstep extension, and the value - internal, it might be ok to just not provide any value here. - If this is truly correct, please update this comment to document - the correctness of doing so. */ - buttonNumber = 0; - - event = [NSEvent mouseEventWithType:eventType - location:eventLocation - modifierFlags:eventFlags - timestamp:(NSTimeInterval) time / 1000.0 - windowNumber:(int) window->window_id - context:gcontext - eventNumber:time - clickCount:clickCount - pressure:1.0 - buttonNumber:buttonNumber - deltaX:deltaX - deltaY:deltaY - deltaZ:0.]; + NSEvent *ev = [NSEvent mouseEventWithType:NSScrollWheel + location:loc + modifierFlags:wlconfig->modifiers + timestamp:ts + windowNumber:(int)window->window_id + context:GSCurrentContext() + eventNumber:wlconfig->pointer.frame_time + clickCount:0 + pressure:0.0 + buttonNumber:0 + deltaX:wlconfig->pointer.frame_deltaX + deltaY:wlconfig->pointer.frame_deltaY + deltaZ:0.0]; + [GSCurrentServer() postEvent:ev atStart:NO]; + } - [GSCurrentServer() postEvent:event atStart:NO]; +reset: + wlconfig->pointer.frame_has_axis = NO; + wlconfig->pointer.frame_deltaX = 0.0f; + wlconfig->pointer.frame_deltaY = 0.0f; + wlconfig->pointer.frame_discrete_x = 0; + wlconfig->pointer.frame_discrete_y = 0; + wlconfig->pointer.frame_time = 0; } // the Seat category uses this listener diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 8041a79c..b8bd1ffa 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -129,9 +129,12 @@ else if (strcmp(interface, wl_seat_interface.name) == 0) { wlconfig->pointer.wlpointer = NULL; - wlconfig->seat_version = version; + /* Bind at v5+ to receive wl_pointer.frame and axis-source/stop/discrete + * events needed for correct per-frame scroll accumulation. */ + uint32_t seat_v = (version < 5) ? version : 5; + wlconfig->seat_version = seat_v; wlconfig->seat - = wl_registry_bind(wlconfig->registry, name, &wl_seat_interface, 1); + = wl_registry_bind(wlconfig->registry, name, &wl_seat_interface, seat_v); NSDebugLog(@"wayland: found seat interface"); wl_seat_add_listener(wlconfig->seat, &seat_listener, wlconfig); } From 3678c91848772fa6cc99915419d0cc272252ed0a Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 13:16:24 +1000 Subject: [PATCH 14/24] Wayland M4: output hotplug, scale/geometry reconfiguration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct output additions (WaylandServer.h): - effective_width / effective_height: logical pixel dimensions after applying scale factor and 90°/270° rotation transform. - name / description: human-readable strings from wl_output v4 events. - configured: YES after handle_done has fired at least once. WaylandServer+Output.m — full rewrite: - output_compute_effective_size(): divide physical mode dimensions by the scale factor, then swap width/height for 90° and 270° transforms (including the flipped variants). - handle_done(): rewritten as the integration point for the full configuration batch — 1. Recompute effective_width/height. 2. Call reposition_windows_for_output() to clamp any window whose pos_x/y fell outside the new logical bounds; setFrameOrigin: is called on the NSWindow so AppKit tracks the corrected position. 3. Post NSApplicationDidChangeScreenParametersNotification so NSScreen reloads its geometry (guarded against pre-NSApp calls). 4. Skip steps 2-3 if nothing changed (suppresses spurious redraws). - handle_name() / handle_description(): store v4 connector name and human-readable description; logged under WaylandOutput. - Listener updated to all 6 entries (adds handle_name, handle_description). - wl_output bound at version 4 (was 2) to receive name/description. WaylandServer.m: - handle_global_remove(): was empty; now finds the removed output by server_output_id, reassigns all its windows to the first remaining output (preventing NULL-deref in coordinate helpers), removes the output from output_list, destroys the wl_output proxy, frees all heap strings, and posts NSApplicationDidChangeScreenParametersNotification. - boundsForScreen:: now finds the specific output matching the screen ID and returns NSMakeRect(alloc_x, alloc_y, effective_w, effective_h), falling back to the first output only when the ID is not found. Previously always returned the first output at (0,0). Co-Authored-By: Claude Sonnet 4.6 --- Headers/wayland/WaylandServer.h | 13 +- Source/wayland/WaylandServer+Output.m | 224 ++++++++++++++++++++++---- Source/wayland/WaylandServer.m | 94 ++++++++++- 3 files changed, 284 insertions(+), 47 deletions(-) diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index 131db206..92c77d45 100644 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -182,14 +182,19 @@ struct output struct wl_output *output; uint32_t server_output_id; struct wl_list link; - int alloc_x; - int alloc_y; - int width; - int height; + int alloc_x; /* compositor layout origin X (physical pixels) */ + int alloc_y; /* compositor layout origin Y (physical pixels) */ + int width; /* current mode width (physical pixels) */ + int height; /* current mode height (physical pixels) */ + int effective_width; /* logical width = width / scale, swap for rot */ + int effective_height; /* logical height = height / scale, swap for rot */ int transform; int scale; char *make; char *model; + char *name; /* human-readable connector name (wl_output v4) */ + char *description; /* human-readable description (wl_output v4) */ + BOOL configured; /* YES after the first handle_done has fired */ void *user_data; }; diff --git a/Source/wayland/WaylandServer+Output.m b/Source/wayland/WaylandServer+Output.m index c2ccb089..7797b7e7 100644 --- a/Source/wayland/WaylandServer+Output.m +++ b/Source/wayland/WaylandServer+Output.m @@ -1,4 +1,4 @@ -/* +/* WaylandServer - Output Handling Copyright (C) 2020 Free Software Foundation, Inc. @@ -27,6 +27,105 @@ #include "wayland/WaylandServer.h" #include +#include +#include +#include +#include +#include + + +/* ── Helpers ─────────────────────────────────────────────────────────────── */ + +/* Compute the logical (AppKit) dimensions of an output, accounting for its + * pixel scale factor and any rotation transform reported by the compositor. + * + * Physical mode width/height are in hardware pixels. After dividing by the + * scale factor we get logical pixels (points). A 90° or 270° rotation also + * swaps the two axes. */ +static void +output_compute_effective_size(struct output *output) +{ + int ew = (output->scale > 0) ? output->width / output->scale : output->width; + int eh = (output->scale > 0) ? output->height / output->scale : output->height; + + switch (output->transform) + { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + /* Swap: the output is rotated 90° or 270° relative to normal. */ + output->effective_width = eh; + output->effective_height = ew; + break; + default: + output->effective_width = ew; + output->effective_height = eh; + break; + } +} + +/* Post NSApplicationDidChangeScreenParametersNotification on the main thread + * so NSScreen reloads its geometry. Guard against early-init calls that + * arrive before NSApp is running. */ +static void +notify_screen_parameters_changed(void) +{ + if (NSApp == nil) + return; + [[NSNotificationCenter defaultCenter] + postNotificationName: NSApplicationDidChangeScreenParametersNotification + object: NSApp]; +} + +/* Clamp all regular (non-layer-shell) windows assigned to an output so that + * they remain within the output's effective (logical) bounds after a + * reconfigure. We update our internal pos_x/y and ask AppKit to move the + * window; the compositor will honour or adjust the request as it sees fit. */ +static void +reposition_windows_for_output(struct output *output) +{ + WaylandConfig *wlconfig = output->wlconfig; + struct window *window; + + wl_list_for_each(window, &wlconfig->window_list, link) + { + if (window->output != output) + continue; + if (window->terminated || window->layer_surface) + continue; + + int ew = output->effective_width; + int eh = output->effective_height; + BOOL moved = NO; + + /* Clamp position so at least the top-left corner is on-screen. */ + if (window->pos_x < 0) { window->pos_x = 0; moved = YES; } + if (window->pos_y < 0) { window->pos_y = 0; moved = YES; } + if (window->pos_x >= (float)ew) { window->pos_x = MAX(0, ew - (int)window->width); moved = YES; } + if (window->pos_y >= (float)eh) { window->pos_y = MAX(0, eh - (int)window->height); moved = YES; } + + if (moved) + { + NSDebugFLLog(@"WaylandOutput", + @"reposition_windows_for_output: clamped window %d " + @"to (%g,%g) within %dx%d output", + window->window_id, window->pos_x, window->pos_y, ew, eh); + + /* Notify AppKit of the new position in GNUstep screen coordinates + * (Y-flipped: bottom-left origin, as in AppKit). */ + NSWindow *nswindow = GSWindowWithNumber(window->window_id); + if (nswindow) + { + float ns_y = eh - window->pos_y - window->height; + [nswindow setFrameOrigin: NSMakePoint(window->pos_x, ns_y)]; + } + } + } +} + + +/* ── wl_output event handlers ─────────────────────────────────────────────── */ static void handle_geometry(void *data, struct wl_output *wl_output, int x, int y, @@ -35,61 +134,116 @@ { struct output *output = data; - output->alloc_x = x; - output->alloc_y = y; + output->alloc_x = x; + output->alloc_y = y; output->transform = transform; - if (output->make) - free(output->make); - output->make = strdup(make); - - if (output->model) - free(output->model); - output->model = strdup(model); + if (output->make) free(output->make); + if (output->model) free(output->model); + output->make = strdup(make ? make : ""); + output->model = strdup(model ? model : ""); NSDebugFLLog(@"WaylandOutput", - @"handle_geometry: output @(%d,%d) physical=%dx%dmm " + @"handle_geometry: output %u @(%d,%d) physical=%dx%dmm " @"transform=%d make=%s model=%s", - x, y, physical_width, physical_height, transform, make, model); + output->server_output_id, x, y, + physical_width, physical_height, transform, make, model); } static void -handle_done(void *data, struct wl_output *wl_output) +handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int width, int height, int refresh) { - NSDebugFLLog(@"WaylandOutput", @"handle_done: output configuration committed"); + struct output *output = data; + + NSDebugFLLog(@"WaylandOutput", + @"handle_mode: output %u flags=0x%x size=%dx%d refresh=%dHz", + output->server_output_id, flags, width, height, refresh / 1000); + + if (flags & WL_OUTPUT_MODE_CURRENT) + { + output->width = width; + output->height = height; + NSDebugFLLog(@"WaylandOutput", + @"handle_mode: output %u current mode → %dx%d physical", + output->server_output_id, width, height); + } } static void handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct output *output = data; - output->scale = scale; - NSDebugFLLog(@"WaylandOutput", @"handle_scale: scale=%d", scale); + NSDebugFLLog(@"WaylandOutput", @"handle_scale: output %u scale=%d", + output->server_output_id, scale); } +/* handle_done is called once all output properties for a configuration batch + * have been sent. It is the right place to: + * 1. Compute the effective (logical) output size. + * 2. Reposition any windows that fell outside the new bounds. + * 3. Notify AppKit so NSScreen reloads its geometry. */ static void -handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, - int height, int refresh) +handle_done(void *data, struct wl_output *wl_output) { struct output *output = data; - NSDebugFLLog(@"WaylandOutput", @"handle_mode: flags=0x%x size=%dx%d refresh=%dHz", - flags, width, height, refresh / 1000); - if (flags & WL_OUTPUT_MODE_CURRENT) - { - output->width = width; - output->height = height /*- 30*/; - NSDebugFLLog(@"WaylandOutput", @"handle_mode: current output size set to %dx%d", - width, height); - - // XXX - Should we implement this? - // if (display->output_configure_handler) - // (*display->output_configure_handler) - // (output, display->user_data); - // - } + int old_ew = output->effective_width; + int old_eh = output->effective_height; + + output_compute_effective_size(output); + + NSDebugFLLog(@"WaylandOutput", + @"handle_done: output %u '%s' logical=%dx%d (phys=%dx%d scale=%d " + @"transform=%d) alloc=(%d,%d)", + output->server_output_id, + output->name ? output->name : "unknown", + output->effective_width, output->effective_height, + output->width, output->height, output->scale, output->transform, + output->alloc_x, output->alloc_y); + + BOOL first_configure = !output->configured; + output->configured = YES; + + /* Only reposition and notify if something actually changed (or first time). */ + if (!first_configure + && output->effective_width == old_ew + && output->effective_height == old_eh) + return; + + reposition_windows_for_output(output); + notify_screen_parameters_changed(); +} + +/* wl_output v4: human-readable connector name (e.g. "HDMI-A-1", "eDP-1"). */ +static void +handle_name(void *data, struct wl_output *wl_output, const char *name) +{ + struct output *output = data; + if (output->name) free(output->name); + output->name = strdup(name ? name : ""); + NSDebugFLLog(@"WaylandOutput", @"handle_name: output %u name='%s'", + output->server_output_id, output->name); +} + +/* wl_output v4: human-readable description (e.g. "Samsung 27\" monitor"). */ +static void +handle_description(void *data, struct wl_output *wl_output, + const char *description) +{ + struct output *output = data; + if (output->description) free(output->description); + output->description = strdup(description ? description : ""); + NSDebugFLLog(@"WaylandOutput", @"handle_description: output %u desc='%s'", + output->server_output_id, output->description); } -const struct wl_output_listener output_listener - = {handle_geometry, handle_mode, handle_done, handle_scale}; +const struct wl_output_listener output_listener = { + handle_geometry, + handle_mode, + handle_done, + handle_scale, + handle_name, + handle_description, +}; diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index b8bd1ffa..4d0a5460 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -118,10 +118,14 @@ memset(output, 0, sizeof(struct output)); output->wlconfig = wlconfig; output->scale = 1; + /* Bind at version 4 to receive name/description events in addition to + * the v2 done/scale events. Fall back to whatever the compositor + * offers if it is older. */ + uint32_t out_v = (version < 4) ? version : 4; output->output - = wl_registry_bind(registry, name, &wl_output_interface, 2); + = wl_registry_bind(registry, name, &wl_output_interface, out_v); output->server_output_id = name; - NSDebugLog(@"wayland: found output interface"); + NSDebugLog(@"wayland: found output interface (version %u)", out_v); wl_list_insert(wlconfig->output_list.prev, &output->link); wlconfig->output_count++; wl_output_add_listener(output->output, &output_listener, output); @@ -166,8 +170,66 @@ } } -static void handle_global_remove(void *data, struct wl_registry *registry, - uint32_t name) {} +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + WaylandConfig *wlconfig = data; + + /* Find the output with this global name and remove it. */ + struct output *output; + struct output *found = NULL; + wl_list_for_each(output, &wlconfig->output_list, link) + { + if (output->server_output_id == name) + { + found = output; + break; + } + } + + if (found == NULL) + return; + + NSDebugLog(@"wayland: global removed: output %u '%s'", + found->server_output_id, found->name ? found->name : "unknown"); + + /* Reassign any windows on the removed output to the first remaining output + * so that coordinate conversions (which dereference window->output) do not + * crash. */ + struct output *fallback = NULL; + wl_list_for_each(output, &wlconfig->output_list, link) + { + if (output != found) { fallback = output; break; } + } + + struct window *window; + wl_list_for_each(window, &wlconfig->window_list, link) + { + if (window->output == found) + { + window->output = fallback; /* may be NULL if last output removed */ + NSDebugLog(@"wayland: window %d reassigned from removed output %u", + window->window_id, name); + } + } + + /* Remove from the list and free resources. */ + wl_list_remove(&found->link); + wlconfig->output_count--; + + wl_output_destroy(found->output); + if (found->make) free(found->make); + if (found->model) free(found->model); + if (found->name) free(found->name); + if (found->description) free(found->description); + free(found); + + /* Notify AppKit that the screen list changed. */ + if (NSApp != nil) + [[NSNotificationCenter defaultCenter] + postNotificationName: NSApplicationDidChangeScreenParametersNotification + object: NSApp]; +} static const struct wl_registry_listener registry_listener = { handle_global, handle_global_remove}; @@ -368,16 +430,32 @@ - (void)restrictWindow:(int)win toImage:(NSImage *)image - (NSRect)boundsForScreen:(int)screen { - NSDebugLog(@"boundsForScreen: %d", screen); struct output *output; + /* Find the output whose server_output_id matches the requested screen. */ + wl_list_for_each(output, &wlconfig->output_list, link) + { + if ((int)output->server_output_id == screen) + { + int ew = output->configured ? output->effective_width : output->width; + int eh = output->configured ? output->effective_height : output->height; + NSDebugLog(@"boundsForScreen: %d → logical=%dx%d (phys=%dx%d scale=%d)", + screen, ew, eh, output->width, output->height, output->scale); + return NSMakeRect(output->alloc_x, output->alloc_y, ew, eh); + } + } + + /* Fallback: return the first output's bounds. */ wl_list_for_each(output, &wlconfig->output_list, link) { - NSDebugLog(@"screen found: %dx%d", output->width, output->height); - return NSMakeRect(0, 0, output->width, output->height); + int ew = output->configured ? output->effective_width : output->width; + int eh = output->configured ? output->effective_height : output->height; + NSDebugLog(@"boundsForScreen: %d not found, using first output %dx%d", + screen, ew, eh); + return NSMakeRect(0, 0, ew, eh); } - NSDebugLog(@"can't find screen"); + NSDebugLog(@"boundsForScreen: no outputs available"); return NSZeroRect; } From 86fbdf5c0fac2475036cd8029925752085843754 Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 13:29:02 +1000 Subject: [PATCH 15/24] Wayland M5: buffer lifecycle hardening, surface destruction ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pool_buffer struct (WaylandCairoShmSurface.h): - Add needs_repaint (bool): set when handleExposeRect skips attach because the compositor still holds the buffer; cleared in release callback. - Add owner_surface / owner_display: allow the release callback to re-attach and commit without going through the ObjC object. WaylandCairoShmSurface.m — buffer lifecycle rewrite: - finishBuffer: add close(buf->poolfd) to fix a file-descriptor leak that accumulated one open FD per window allocation over the session lifetime. - createShmBuffer: use calloc so all fields start zeroed; initialise poolfd = -1 so the close() in finishBuffer is safe on error paths. Destroy wl_shm_pool immediately after buffer creation (correct and documented in the protocol); set buf->pool = NULL to avoid confusion. Return NULL on buffer creation failure instead of crashing. - buffer_handle_release: if needs_repaint is set, re-attach + damage + commit the buffer immediately on release so a frame missed during a busy period is never permanently lost; only calls finishBuffer when no repaint is pending. - initWithDevice: set owner_surface/owner_display back-pointers; add wl_surface_damage before the initial commit (without it the compositor treats the commit as a no-op); mark busy = true on attach. - handleExposeRect: add three guards before attaching: 1. Size mismatch: if pbuffer dimensions differ from window size, set needs_repaint and return — AppKit will allocate a correctly-sized surface; avoids attaching a wrong-sized buffer during resize churn. 2. Busy check: if the compositor still holds the buffer, set needs_repaint and return; prevents a Wayland protocol error. 3. Use the actual exposed NSRect (Y-flipped into Wayland coords) for wl_surface_damage instead of always damaging the full surface. - dealloc: clear owner_surface/owner_display/needs_repaint before calling finishBuffer so the release callback cannot write to freed memory. Also clear window->wcs to avoid a dangling ObjC pointer. - Add clearOwnerSurface method: called by destroySurfaceRole: just before wl_surface_destroy so the async release callback cannot dereference a destroyed Wayland proxy. WaylandServer.m: - destroySurfaceRole: call [wcs clearOwnerSurface] before destroying the wl_surface; then destroy wl_surface itself (role objects destroyed first per protocol — xdg_surface/layer_surface were already handled). Add #include for WaylandCairoShmSurface.h. WaylandServer+Xdgshell.m: - xdg_surface_on_configure terminated path: remove the duplicate wl_list_remove call. termwindow: already removed the window from window_list; calling wl_list_remove a second time corrupts the list in debug builds and is logically wrong regardless. Now only free(). Co-Authored-By: Claude Sonnet 4.6 --- Headers/cairo/WaylandCairoShmSurface.h | 10 + Source/cairo/WaylandCairoShmSurface.m | 345 ++++++++++++++++-------- Source/wayland/WaylandServer+Xdgshell.m | 24 +- Source/wayland/WaylandServer.m | 15 +- 4 files changed, 255 insertions(+), 139 deletions(-) diff --git a/Headers/cairo/WaylandCairoShmSurface.h b/Headers/cairo/WaylandCairoShmSurface.h index 5fe871bc..8ce6d5fd 100644 --- a/Headers/cairo/WaylandCairoShmSurface.h +++ b/Headers/cairo/WaylandCairoShmSurface.h @@ -41,6 +41,13 @@ struct pool_buffer void *data; size_t size; bool busy; + + /* Repaint-on-release: set when handleExposeRect skips attach because + * the compositor still holds the buffer. The release callback re-attaches + * and commits the buffer so the missed frame is not lost. */ + bool needs_repaint; + struct wl_surface *owner_surface; /* the wl_surface this buffer is on */ + struct wl_display *owner_display; /* for flushing after re-attach */ }; struct pool_buffer * @@ -51,6 +58,9 @@ createShmBuffer(int width, int height, struct wl_shm *shm); struct pool_buffer *pbuffer; } - (void) destroySurface; +/** Clear the wl_surface back-pointer so the release callback does not + * write to a destroyed Wayland proxy after destroySurfaceRole:. */ +- (void) clearOwnerSurface; @end #endif diff --git a/Source/cairo/WaylandCairoShmSurface.m b/Source/cairo/WaylandCairoShmSurface.m index 40a577fa..93b20548 100644 --- a/Source/cairo/WaylandCairoShmSurface.m +++ b/Source/cairo/WaylandCairoShmSurface.m @@ -1,12 +1,21 @@ /* WaylandCairoSurface - WaylandCairoShmSurface - A cairo surface backed by a wayland - shared memory buffer. - After the wayland surface is configured, the buffer needs to be - attached to the surface. Subsequent changes to the cairo surface - needs to be notified to the wayland server using wl_surface_damage - and wl_surface_commit. The buffer is freed after the compositor - releases it and the cairo surface is not in use. + WaylandCairoShmSurface - A cairo surface backed by a Wayland shared-memory + buffer. + + Buffer lifecycle: + 1. createShmBuffer — allocate SHM fd, mmap, create wl_shm_pool + wl_buffer. + Destroy the pool immediately (safe; the buffer keeps the mapping alive). + 2. initWithDevice — attach the buffer to the wl_surface and commit with a + full-surface damage region. Only after xdg_surface_configure (i.e. + window->configured == YES). + 3. handleExposeRect — re-attach with the actual damage rect on every + AppKit expose. If the compositor still holds the buffer (busy == true), + record needs_repaint and return; the release callback will re-commit. + 4. buffer_handle_release — compositor released the buffer. If needs_repaint + is set, immediately re-attach + commit to avoid losing the missed frame. + 5. dealloc / finishBuffer — destroy the wl_buffer, unmap memory, close the + FD, and free the struct once the compositor has released it. Copyright (C) 2020 Free Software Foundation, Inc. @@ -45,73 +54,82 @@ #include -static const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888; -static const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32; +static const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888; +static const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32; + +/* ── Buffer lifetime ─────────────────────────────────────────────────────── */ + +/* Free the pool_buffer once both conditions hold: + * (a) the compositor has released the wl_buffer (busy == false), and + * (b) nobody is rendering into the cairo surface (surface == NULL). + * Also closes the SHM file descriptor to prevent FD leaks. */ static void finishBuffer(struct pool_buffer *buf) { - // The buffer can be deleted if it has been released by the compositor - // and if not used by the cairo surface - if(buf == NULL || buf->busy || buf->surface != NULL) - { + if (buf == NULL || buf->busy || buf->surface != NULL) return; - } + if (buf->buffer) { wl_buffer_destroy(buf->buffer); + buf->buffer = NULL; } if (buf->data) { munmap(buf->data, buf->size); + buf->data = NULL; + } + if (buf->poolfd >= 0) + { + close(buf->poolfd); + buf->poolfd = -1; } free(buf); - return; } +/* Compositor released the buffer. If a repaint was queued while the buffer + * was busy, re-attach and commit now so the frame is not permanently lost. */ static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { struct pool_buffer *buffer = data; buffer->busy = false; - // If the buffer was not released before dealloc + + if (buffer->needs_repaint && buffer->owner_surface) + { + buffer->needs_repaint = false; + buffer->busy = true; + + wl_surface_attach(buffer->owner_surface, wl_buffer, 0, 0); + wl_surface_damage(buffer->owner_surface, 0, 0, + (int32_t)buffer->width, (int32_t)buffer->height); + wl_surface_commit(buffer->owner_surface); + + if (buffer->owner_display) + wl_display_flush(buffer->owner_display); + + /* Don't call finishBuffer — we just re-submitted the buffer. */ + return; + } + finishBuffer(buffer); } static const struct wl_buffer_listener buffer_listener = { - // Sent by the compositor when it's no longer using a buffer .release = buffer_handle_release, }; -// Creates a file descriptor for the compositor to share pixel buffers + +/* ── SHM buffer allocation ───────────────────────────────────────────────── */ + +/* Creates an anonymous in-memory file suitable for sharing with the + * compositor via wl_shm. memfd_create is preferred over mkstemp because + * it never touches the filesystem and automatically cleans up on close. */ static int createPoolFile(off_t size) { - static const char template[] = "/gnustep-shared-XXXXXX"; - const char *path; - char *name; - int fd; - - path = getenv("XDG_RUNTIME_DIR"); - if (!path) - { - errno = ENOENT; - return -1; - } - - name = malloc(strlen(path) + sizeof(template)); - if (!name) - { - return -1; - } - - strcpy(name, path); - strcat(name, template); - - fd = memfd_create(name, MFD_CLOEXEC); - - free(name); - + int fd = memfd_create("gnustep-wl-shm", MFD_CLOEXEC); if (fd < 0) return -1; @@ -128,101 +146,136 @@ createShmBuffer(int width, int height, struct wl_shm *shm) { uint32_t stride = cairo_format_stride_for_width(cairo_fmt, width); - size_t size = stride * height; + size_t size = stride * height; - struct pool_buffer * buf = malloc(sizeof(struct pool_buffer)); + if (size == 0) + return NULL; + + struct pool_buffer *buf = calloc(1, sizeof(struct pool_buffer)); + if (!buf) + return NULL; - void *data = NULL; - if (size > 0) + buf->poolfd = -1; + + buf->poolfd = createPoolFile(size); + if (buf->poolfd < 0) { - buf->poolfd = createPoolFile(size); - if (buf->poolfd == -1) - { - return NULL; - } - - data - = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, buf->poolfd, 0); - if (data == MAP_FAILED) - { - return NULL; - } - - buf->pool = wl_shm_create_pool(shm, buf->poolfd, size); - buf->buffer = wl_shm_pool_create_buffer(buf->pool, 0, width, height, - stride, wl_fmt); - wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + free(buf); + return NULL; } - else - { - return NULL; - } - - buf->data = data; - buf->size = size; - buf->width = width; - buf->height = height; - buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, width, height, stride); - - if(buf->pool) - { - wl_shm_pool_destroy(buf->pool); - } + + buf->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, buf->poolfd, 0); + if (buf->data == MAP_FAILED) + { + close(buf->poolfd); + free(buf); + return NULL; + } + + /* Create the pool and immediately the buffer. The pool can be destroyed + * right after — the buffer retains the memory mapping independently. */ + struct wl_shm_pool *pool = wl_shm_create_pool(shm, buf->poolfd, size); + buf->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt); + wl_shm_pool_destroy(pool); + /* buf->pool is left NULL — pool is gone, nothing to free later. */ + + if (!buf->buffer) + { + munmap(buf->data, size); + close(buf->poolfd); + free(buf); + return NULL; + } + + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + + buf->size = size; + buf->width = width; + buf->height = height; + buf->surface = cairo_image_surface_create_for_data( + buf->data, cairo_fmt, width, height, stride); + return buf; } + +/* ── WaylandCairoShmSurface ──────────────────────────────────────────────── */ + @implementation WaylandCairoShmSurface + - (id)initWithDevice:(void *)device { - struct window *window = (struct window *) device; - NSDebugLog(@"WaylandCairoShmSurface: initWithDevice win=%d", - window->window_id); + struct window *window = (struct window *)device; + NSDebugLog(@"WaylandCairoShmSurface: initWithDevice win=%d", window->window_id); gsDevice = device; - pbuffer = createShmBuffer(window->width, window->height, window->wlconfig->shm); - + pbuffer = createShmBuffer((int)window->width, (int)window->height, + window->wlconfig->shm); if (pbuffer == NULL) { - NSDebugLog(@"failed to obtain buffer"); + NSDebugLog(@"WaylandCairoShmSurface: failed to allocate SHM buffer"); return nil; } _surface = pbuffer->surface; - - window->buffer_needs_attach = YES; if (_surface == NULL) { - NSDebugLog(@"can't create cairo surface"); + NSDebugLog(@"WaylandCairoShmSurface: failed to create cairo surface"); + finishBuffer(pbuffer); + pbuffer = NULL; return nil; } + /* Wire back-pointers so the release callback can re-commit missed frames. */ + pbuffer->owner_surface = window->surface; + pbuffer->owner_display = window->wlconfig->display; + + window->wcs = self; + if (window->configured) { - // we can attach a buffer to the surface only if the surface is configured - // this is usually done in the configure event handler - // in case of resize of an already configured surface - // we should reattach the new allocated buffer - NSDebugLog(@"wl_surface_attach: win=%d", - window->window_id); + /* Attach the fresh buffer with a full-surface damage region. + * wl_surface_damage is mandatory before commit — without it the + * compositor treats the commit as a no-op for rendering purposes. */ window->buffer_needs_attach = NO; + pbuffer->busy = true; wl_surface_attach(window->surface, pbuffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, + (int32_t)window->width, (int32_t)window->height); wl_surface_commit(window->surface); } - window->wcs = self; + else + { + window->buffer_needs_attach = YES; + } return self; } - (void)dealloc { - struct window *window = (struct window *) gsDevice; - NSDebugLog(@"WaylandCairoSurface: dealloc win=%d", window->window_id); + struct window *window = (struct window *)gsDevice; + NSDebugLog(@"WaylandCairoShmSurface: dealloc win=%d", window->window_id); + + /* Detach this surface from the window so it won't be used again. */ + if (window->wcs == self) + window->wcs = nil; + + /* Sever the Cairo→buffer link; the buffer may still be compositor-held. */ cairo_surface_destroy(_surface); - _surface = NULL; + _surface = NULL; pbuffer->surface = NULL; - // try to free the buffer if already released by the compositor + + /* Clear back-pointers so the release callback doesn't touch freed data. */ + pbuffer->owner_surface = NULL; + pbuffer->owner_display = NULL; + pbuffer->needs_repaint = false; + + /* Free immediately if the compositor has already released the buffer; + * otherwise finishBuffer defers until the release callback fires. */ finishBuffer(pbuffer); + pbuffer = NULL; [super dealloc]; } @@ -230,38 +283,92 @@ - (void)dealloc - (NSSize)size { if (_surface == NULL) - { - return NSZeroSize; - } + return NSZeroSize; return NSMakeSize(cairo_image_surface_get_width(_surface), - cairo_image_surface_get_height(_surface)); + cairo_image_surface_get_height(_surface)); } - (void)handleExposeRect:(NSRect)rect { - struct window *window = (struct window *) gsDevice; - NSDebugLog(@"[CairoSurface handleExposeRect] %d", window->window_id); + struct window *window = (struct window *)gsDevice; - window->buffer_needs_attach = YES; + if (!window->configured) + { + window->buffer_needs_attach = YES; + return; + } - if (window->configured) + /* If the buffer dimensions no longer match the window (e.g. after a + * compositor-driven resize), the old buffer is stale. Mark it for + * repaint-on-release so no frame is lost, then bail out — AppKit will + * allocate a correctly-sized WaylandCairoShmSurface via the next + * setWindowdevice:forContext: call. */ + if (pbuffer->width != (uint32_t)window->width + || pbuffer->height != (uint32_t)window->height) { - window->buffer_needs_attach = NO; - wl_surface_attach(window->surface, pbuffer->buffer, 0, 0); - NSDebugLog(@"[%d] updating region: %d,%d %fx%f", window->window_id, 0, 0, - window->width, window->height); - // FIXME we should update only the damaged area defined as x,y,width, - // height at the moment it doesnt work - wl_surface_damage(window->surface, 0, 0, window->width, window->height); - wl_surface_commit(window->surface); - wl_display_dispatch_pending(window->wlconfig->display); - wl_display_flush(window->wlconfig->display); + NSDebugLog(@"[%d] handleExposeRect: size mismatch buf=%dx%d win=%dx%d — " + @"deferring until resize completes", + window->window_id, + pbuffer->width, pbuffer->height, + (int)window->width, (int)window->height); + pbuffer->needs_repaint = true; + return; } + + /* If the compositor still owns the buffer, queue a repaint for the moment + * it returns it. Attaching a busy buffer causes a protocol error. */ + if (pbuffer->busy) + { + NSDebugLog(@"[%d] handleExposeRect: buffer busy — queuing repaint", + window->window_id); + pbuffer->needs_repaint = true; + return; + } + + /* Attach, mark the actual damaged region, and commit. + * Using the precise rect (converted to integer coordinates) reduces the + * compositor's repaint area for partial-surface updates. */ + int dx = (int)NSMinX(rect); + int dy = (int)(window->height - NSMaxY(rect)); /* flip Y: AppKit → Wayland */ + int dw = (int)NSWidth(rect) + 1; /* +1: cover sub-pixel edges */ + int dh = (int)NSHeight(rect) + 1; + + /* Clamp to buffer dimensions. */ + if (dx < 0) dx = 0; + if (dy < 0) dy = 0; + if (dx + dw > (int)pbuffer->width) dw = (int)pbuffer->width - dx; + if (dy + dh > (int)pbuffer->height) dh = (int)pbuffer->height - dy; + + NSDebugLog(@"[%d] handleExposeRect: attach+damage (%d,%d %dx%d)", + window->window_id, dx, dy, dw, dh); + + pbuffer->needs_repaint = false; + pbuffer->busy = true; + window->buffer_needs_attach = NO; + + wl_surface_attach(window->surface, pbuffer->buffer, 0, 0); + wl_surface_damage(window->surface, dx, dy, dw, dh); + wl_surface_commit(window->surface); + wl_display_dispatch_pending(window->wlconfig->display); + wl_display_flush(window->wlconfig->display); } - (void)destroySurface { - // noop this is an offscreen surface - // no need to destroy it when not visible + /* Offscreen surface — no-op. Destruction is handled in dealloc. */ } + +- (void)clearOwnerSurface +{ + /* Called from destroySurfaceRole: just before wl_surface_destroy. + * Prevents the buffer_handle_release callback from writing to the + * about-to-be-freed proxy. */ + if (pbuffer) + { + pbuffer->owner_surface = NULL; + pbuffer->owner_display = NULL; + pbuffer->needs_repaint = false; + } +} + @end diff --git a/Source/wayland/WaylandServer+Xdgshell.m b/Source/wayland/WaylandServer+Xdgshell.m index 5fa2feeb..f55c2d88 100644 --- a/Source/wayland/WaylandServer+Xdgshell.m +++ b/Source/wayland/WaylandServer+Xdgshell.m @@ -42,24 +42,12 @@ if (window->terminated == YES) { - // 'struct window' is defined in Headers/wayland/WaylandServer. - // - // window->terminated should only be true after - // -[WaylandServer(WindowOps) termwindow:(int)win] sets this to true - // after invoking -[WaylandServer destroyWindowShell:window] and - // using wl_list_remove(&window->link);. - // - // We do not expect 'window' to be referenced again, since - // -destroyWindowShell: invokes wl_display_dispatch_pending(...) and - // wl_display_flush(...);. On the off chance it does, first, we may want - // to patch every single invocation of get_window_with_id() that may - // currently be ignoring the case where NULL may be returned, and - // possibly crashing for that reason. - // - // But, a free here should on its own be fine as long as everyone - // passes around the window ID and does not store a ptr to window itself. - NSDebugLog(@"deleting window win=%d", window->window_id); - wl_list_remove(&window->link); + /* termwindow: already removed this window from wlconfig->window_list + * and set terminated = YES. We must NOT call wl_list_remove again — + * the node's prev/next pointers point to themselves after the first + * remove, and a second remove would corrupt the list. + * This configure arrived in-flight after termwindow; free the struct. */ + NSDebugLog(@"deleting window win=%d (deferred free)", window->window_id); free(window); return; } diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index 4d0a5460..dea296eb 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -48,6 +48,7 @@ #include "wayland/WaylandServer.h" #include "wayland/WaylandOpenGL.h" #include "wayland/WaylandInputServer.h" +#include "cairo/WaylandCairoShmSurface.h" extern const struct wl_output_listener output_listener; extern const struct wl_seat_listener seat_listener; @@ -1458,11 +1459,21 @@ - (void)destroySurfaceRole:(struct window *)window xdg_surface_destroy(window->xdg_surface); window->xdg_surface = NULL; } + /* Destroy the base wl_surface last. Role objects (xdg_surface, + * layer_surface) must be destroyed first per the Wayland protocol. + * The cairo SHM surface (window->wcs) is an ObjC object that holds a + * reference to the wl_surface via pbuffer->owner_surface; clear that + * pointer so the release callback does not write to a freed proxy. */ if (window->wcs) { - // [window->wcs destroySurface]; + [(WaylandCairoShmSurface *)window->wcs clearOwnerSurface]; } - window->configured = NO; + if (window->surface) + { + wl_surface_destroy(window->surface); + window->surface = NULL; + } + window->configured = NO; window->buffer_needs_attach = YES; } From 6c92dea2d01a664df3c8ceb844d50cf3ec7e228a Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 13:48:08 +1000 Subject: [PATCH 16/24] smoke-test: extend harness to M5 (buffer lifecycle + stability checks) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Header and banner updated from "Milestone 0" to "M0–M5" to reflect cumulative coverage. T9 pattern fixed: the "Milestone 3" TODO comment was replaced with real button-mapping code in M3; the check now looks for BTN_SIDE which is present in the buttonNumber documentation block. New M5 tests (T12–T19): T12 — Buffer FD lifecycle: verify finishBuffer closes poolfd (FD-leak fix), pool_buffer is zero-initialised via calloc, needs_repaint and owner back-pointer fields are present. T13 — handleExposeRect safety: verify busy guard is present (prevents attaching a compositor-held buffer), repaint-on-release path exists in buffer_handle_release, and size-mismatch guard is present. T14 — Precise damage rect: verify handleExposeRect uses NSMinX/NSMaxY for the damaged region instead of always the full surface, and that the initial commit in initWithDevice is preceded by damage. T15 — Use-after-free prevention: verify clearOwnerSurface is called before wl_surface_destroy in destroySurfaceRole, and that wl_surface_destroy itself is present in that function. T16 — No double wl_list_remove: verify the terminated path in xdg_surface_on_configure calls free(window) (pattern escaped as "free.window." to avoid ERE capturing-group confusion) and does NOT call wl_list_remove( (tightened to the actual call rather than the identifier, so comments mentioning the function name do not trigger a false fail). T17 — wl_shm_pool lifecycle: verify the pool is destroyed immediately after buffer creation (wl_shm_pool_destroy present) and that buf->pool is not stored for later use. T18 — Sustained-operation stress: fire wayland-info 20 times in rapid succession; compositor must survive without crash or hang. T19 — Post-stress health: compositor process still alive; log contains no crash, abort, SIGSEGV, double-free, or disconnect markers. Co-Authored-By: Claude Sonnet 4.6 --- Tools/wayland-smoke-test.sh | 174 +++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 3 deletions(-) diff --git a/Tools/wayland-smoke-test.sh b/Tools/wayland-smoke-test.sh index bffbae84..b2e5b666 100755 --- a/Tools/wayland-smoke-test.sh +++ b/Tools/wayland-smoke-test.sh @@ -1,10 +1,14 @@ #!/usr/bin/env bash -# wayland-smoke-test.sh — Milestone 0 integration harness for the Wayland backend. +# wayland-smoke-test.sh — Integration harness for the Wayland backend (M0–M5). # # Starts the Ambrosia compositor nested inside the current Wayland session, # then exercises each feature bucket in wayland_feature_implementation_plan.md # and captures log snapshots. # +# Milestone coverage: +# M0 T1–T11 Baseline: compositor liveness, protocol globals, source snapshots +# M5 T12–T19 Stability: buffer lifecycle audits + sustained-operation stress +# # Usage: # ./Tools/wayland-smoke-test.sh [options] # @@ -239,7 +243,7 @@ check_log_absent() { echo "" echo "═══════════════════════════════════════════════" -echo " Wayland backend smoke tests (Milestone 0)" +echo " Wayland backend smoke tests (M0–M5)" echo " Compositor: ${COMPOSITOR_BIN}" echo " Parent: ${PARENT_DISPLAY}" echo " Client sock: ${CLIENT_WAYLAND_DISPLAY:-unknown}" @@ -369,7 +373,7 @@ PTR_LOG="${OUTPUT_DIR}/pointer-snapshot.txt" } >"${PTR_LOG}" check_log_contains "T9: WaylandPointer category in source" "${PTR_LOG}" "WaylandPointer" check_log_contains "T9: WaylandScroll category in source" "${PTR_LOG}" "WaylandScroll" -check_log_contains "T9: extra-button Milestone 3 marker" "${PTR_LOG}" "Milestone 3" +check_log_contains "T9: extra-button BTN_SIDE mapped" "${PTR_LOG}" "BTN_SIDE" # ── T10: output stub snapshot ───────────────────────────────────────────────── @@ -389,6 +393,170 @@ check_log_contains "T10: WaylandOutput category in source" "${OUT_LOG}" "Wayland check_log_absent "T11: no crash/abort in compositor log" \ "${COMPOSITOR_LOG}" "Segmentation fault|Aborted|SIGSEGV|double free" +# ══════════════════════════════════════════════════════════════════════════════ +# Milestone 5 — Stability and rendering hardening +# ══════════════════════════════════════════════════════════════════════════════ + +SHM_SOURCE="${REPO_ROOT}/Source/cairo/WaylandCairoShmSurface.m" +XDGSHELL_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer+Xdgshell.m" +SERVER_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer.m" + +# ── T12: FD leak fix — finishBuffer closes poolfd ──────────────────────────── +# The pool file-descriptor was never closed before M5, leaking one FD per +# window allocation. The fix is a close() call in finishBuffer. + +M5_BUF_LOG="${OUTPUT_DIR}/m5-buffer.txt" +{ + echo "=== finishBuffer: poolfd close ===" + grep -n "close(buf->poolfd)\|close.*poolfd" "${SHM_SOURCE}" 2>/dev/null || true + echo "" + echo "=== calloc / poolfd = -1 init ===" + grep -n "calloc\|poolfd = -1" "${SHM_SOURCE}" 2>/dev/null || true + echo "" + echo "=== needs_repaint field ===" + grep -n "needs_repaint" "${SHM_SOURCE}" 2>/dev/null || true + echo "" + echo "=== owner_surface / owner_display ===" + grep -n "owner_surface\|owner_display" "${SHM_SOURCE}" 2>/dev/null || true +} >"${M5_BUF_LOG}" + +check_log_contains "T12: finishBuffer closes poolfd" "${M5_BUF_LOG}" "close.*poolfd" +check_log_contains "T12: pool_buffer zero-initialised" "${M5_BUF_LOG}" "calloc" +check_log_contains "T12: needs_repaint field present" "${M5_BUF_LOG}" "needs_repaint" +check_log_contains "T12: owner back-pointers present" "${M5_BUF_LOG}" "owner_surface" + +# ── T13: Busy guard in handleExposeRect ────────────────────────────────────── +# Before M5, handleExposeRect attached the buffer unconditionally even while +# the compositor still held it (protocol error / artifact). + +M5_EXPOSE_LOG="${OUTPUT_DIR}/m5-expose.txt" +{ + echo "=== busy check in handleExposeRect ===" + grep -n "pbuffer->busy\|busy" "${SHM_SOURCE}" 2>/dev/null || true + echo "" + echo "=== repaint-on-release in buffer_handle_release ===" + # Look for the release callback re-committing after a missed frame + awk '/buffer_handle_release/,/^}/' "${SHM_SOURCE}" 2>/dev/null | \ + grep -n "needs_repaint\|wl_surface_attach\|wl_surface_commit" || true + echo "" + echo "=== size mismatch guard ===" + grep -n "pbuffer->width.*window->width\|size mismatch\|width != " "${SHM_SOURCE}" 2>/dev/null || true +} >"${M5_EXPOSE_LOG}" + +check_log_contains "T13: busy guard in handleExposeRect" "${M5_EXPOSE_LOG}" "pbuffer->busy" +check_log_contains "T13: repaint-on-release path" "${M5_EXPOSE_LOG}" "needs_repaint" +check_log_contains "T13: size-mismatch guard" "${M5_EXPOSE_LOG}" "pbuffer->width" + +# ── T14: Precise damage rect in handleExposeRect ───────────────────────────── +# Before M5, wl_surface_damage always used the full surface (0,0,w,h). +# Now the actual exposed NSRect is used. + +M5_DAMAGE_LOG="${OUTPUT_DIR}/m5-damage.txt" +{ + echo "=== precise damage in handleExposeRect ===" + grep -n "NSMinX\|NSMaxY\|NSWidth\|NSHeight\|dx\|dy\|dw\|dh" \ + "${SHM_SOURCE}" 2>/dev/null || true + echo "" + echo "=== initial commit has damage ===" + # initWithDevice must call wl_surface_damage before wl_surface_commit + awk '/initWithDevice/,/^- \(/ { print NR": "$0 }' "${SHM_SOURCE}" 2>/dev/null | \ + grep "wl_surface_damage\|wl_surface_commit" | head -10 || true +} >"${M5_DAMAGE_LOG}" + +check_log_contains "T14: precise damage rect uses NSMinX/NSMaxY" \ + "${M5_DAMAGE_LOG}" "NSMinX|NSMaxY|NSWidth|NSHeight" +check_log_contains "T14: initial commit preceded by damage" \ + "${M5_DAMAGE_LOG}" "wl_surface_damage" + +# ── T15: clearOwnerSurface prevents use-after-free ─────────────────────────── +# destroySurfaceRole: must clear the wl_surface back-pointer before calling +# wl_surface_destroy so the async buffer_handle_release cannot write to a +# freed proxy. + +M5_OWNER_LOG="${OUTPUT_DIR}/m5-owner.txt" +{ + echo "=== clearOwnerSurface in WaylandServer.m ===" + grep -n "clearOwnerSurface\|wl_surface_destroy" "${SERVER_SOURCE}" 2>/dev/null || true + echo "" + echo "=== clearOwnerSurface implementation ===" + awk '/clearOwnerSurface/,/^}/' "${SHM_SOURCE}" 2>/dev/null | head -20 || true + echo "" + echo "=== wl_surface_destroy in destroySurfaceRole ===" + awk '/destroySurfaceRole:/,/^- \(/ { print NR": "$0 }' \ + "${SERVER_SOURCE}" 2>/dev/null | \ + grep "wl_surface_destroy\|clearOwnerSurface" || true +} >"${M5_OWNER_LOG}" + +check_log_contains "T15: clearOwnerSurface called before wl_surface_destroy" \ + "${M5_OWNER_LOG}" "clearOwnerSurface" +check_log_contains "T15: wl_surface_destroy present in destroySurfaceRole" \ + "${M5_OWNER_LOG}" "wl_surface_destroy" + +# ── T16: No double wl_list_remove in xdg_surface_on_configure ──────────────── +# termwindow: removes the window from the list and sets terminated=YES. +# xdg_surface_on_configure must NOT call wl_list_remove a second time. + +M5_LIST_LOG="${OUTPUT_DIR}/m5-list-remove.txt" +{ + echo "=== terminated path in xdg_surface_on_configure ===" + awk '/xdg_surface_on_configure/,/^const struct/' "${XDGSHELL_SOURCE}" 2>/dev/null | \ + grep -n "terminated\|wl_list_remove\|free(window)" | head -20 || true +} >"${M5_LIST_LOG}" + +check_log_contains "T16: terminated path frees window" "${M5_LIST_LOG}" "free.window." +check_log_absent "T16: no second wl_list_remove" \ + "${M5_LIST_LOG}" "wl_list_remove\(" + +# ── T17: wl_shm_pool destroyed promptly (no dangling pool pointer) ──────────── +# The pool was stored in buf->pool and compared with NULL after being destroyed +# (potential double-destroy if finishBuffer was ever changed to also destroy it). +# M5 sets pool = NULL immediately after destroy and skips the buf->pool field. + +M5_POOL_LOG="${OUTPUT_DIR}/m5-pool.txt" +{ + echo "=== wl_shm_pool lifecycle in createShmBuffer ===" + awk '/createShmBuffer/,/^@implementation/' "${SHM_SOURCE}" 2>/dev/null | \ + grep -n "wl_shm_pool\|wl_shm_create_pool\|wl_shm_pool_destroy\|buf->pool" | head -20 || true +} >"${M5_POOL_LOG}" + +check_log_contains "T17: wl_shm_pool_destroy called" "${M5_POOL_LOG}" "wl_shm_pool_destroy" +check_log_absent "T17: buf->pool not stored after destroy" "${M5_POOL_LOG}" "buf->pool =" + +# ── T18: Compositor survives sustained global queries (event-loop stress) ───── +# Fire wayland-info 20 times in rapid succession against the running compositor. +# Each invocation opens a Wayland connection, reads the global list, and closes. +# A freeze or crash here indicates event-loop saturation or fd/memory leaks. + +if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]] && command -v wayland-info &>/dev/null; then + M5_STRESS_LOG="${OUTPUT_DIR}/m5-stress.log" + log "T18: sustained global query stress (20 iterations) …" + STRESS_OK=1 + for i in $(seq 1 20); do + if ! timeout 5 wayland-info >>"${M5_STRESS_LOG}" 2>&1; then + STRESS_OK=0 + fail "T18: wayland-info iteration ${i} failed" + break + fi + done + if [[ "${STRESS_OK}" -eq 1 ]]; then + pass "T18: compositor survived 20 consecutive global queries" + fi +else + log "T18: stress test skipped (no compositor socket or wayland-info not installed)" +fi + +# ── T19: Compositor still alive and clean after stress ─────────────────────── + +sleep 1 +if kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then + pass "T19: compositor still running after stress" +else + fail "T19: compositor died during stress test" +fi + +check_log_absent "T19: no crash/abort after stress" \ + "${COMPOSITOR_LOG}" "Segmentation fault|Aborted|SIGSEGV|double free|wl_display_disconnect" + # ── summary ─────────────────────────────────────────────────────────────────── echo "" From 2dfab9c5bfb3827e1dd014ffe4ee5182438732a6 Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 14:04:16 +1000 Subject: [PATCH 17/24] fix: add wl_seat.name handler to prevent SIGABRT on startup Bumping the wl_seat binding from v1 to v5 in Milestone 3 caused wl_seat.name (opcode 1, introduced in v2) to be delivered during the initial roundtrip. The seat_listener struct only declared one slot (capabilities), leaving the name slot NULL. libwayland-client aborts with "listener function for opcode 1 of wl_seat is NULL" when it encounters a NULL dispatcher. Fix: add seat_handle_name which logs the seat name under the WaylandIME debug category and satisfies the listener struct contract. Co-Authored-By: Claude Sonnet 4.6 --- Source/wayland/WaylandServer+Seat.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/wayland/WaylandServer+Seat.m b/Source/wayland/WaylandServer+Seat.m index d4cf73c2..892f645d 100644 --- a/Source/wayland/WaylandServer+Seat.m +++ b/Source/wayland/WaylandServer+Seat.m @@ -26,6 +26,7 @@ */ #include "wayland/WaylandServer.h" +#include extern const struct wl_keyboard_listener keyboard_listener; @@ -87,6 +88,17 @@ #endif } +/* wl_seat.name — sent by the compositor since wl_seat v2. We bound the seat + * at version 5 for frame/axis events, so this event now arrives at startup. + * An absent handler slot causes libwayland-client to abort with + * "listener function for opcode 1 of wl_seat is NULL". */ +static void +seat_handle_name(void *data, struct wl_seat *seat, const char *name) +{ + NSDebugFLLog(@"WaylandIME", @"seat_handle_name: '%s'", name); +} + const struct wl_seat_listener seat_listener = { seat_handle_capabilities, + seat_handle_name, }; From 9a35124af81db8c9edb5804dfb79a1214a689f1d Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 14:11:03 +1000 Subject: [PATCH 18/24] fix: fill all wl_pointer and wl_seat listener slots to prevent SIGABRT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wayland-client 1.24 defines wl_pointer_listener with 11 event slots: 0 enter (v1) 1 leave (v1) 2 motion (v1) 3 button (v1) 4 axis (v1) 5 frame (v5) 6 axis_source (v5) 7 axis_stop (v5) 8 axis_discrete (v5) 9 axis_value120 (v8) ← was NULL → abort 10 axis_relative_direction (v9) ← was NULL → abort We bound wl_seat at v5 (Milestone 3) which should gate v8/v9 events, but wlroots 0.20 / Ambrosia sends these events regardless when the hardware generates high-resolution scroll input. libwayland-client aborts on any NULL dispatcher slot when that event arrives during wl_display_roundtrip. Fixes: pointer_handle_axis_value120: converts 1/120-unit steps to logical scroll deltas (÷120 × mouse_scroll_multiplier) and accumulates them in the per-frame state alongside the smooth axis value. pointer_handle_axis_relative_direction: logs the natural-scroll hint under WaylandScroll; direction inversion is a future improvement. Both handlers added to pointer_listener in correct opcode order. Also fixes wl_seat_listener which had only one slot (capabilities) after the v5 bump added the wl_seat.name event (opcode 1). seat_handle_name was already committed in the previous fix; this commit ensures both listener arrays are complete. Co-Authored-By: Claude Sonnet 4.6 --- Source/wayland/WaylandServer+Cursor.m | 47 +++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index 73631197..3ae73b01 100644 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -636,13 +636,48 @@ wlconfig->pointer.frame_time = 0; } -// the Seat category uses this listener +/* wl_pointer.axis_value120 (protocol v8) — high-resolution wheel step. + * Accumulate into the per-frame scroll state using the standard 1/120 scale. */ +static void +pointer_handle_axis_value120(void *data, struct wl_pointer *pointer, + uint32_t axis, int32_t value120) +{ + WaylandConfig *wlconfig = data; + NSDebugFLLog(@"WaylandScroll", + @"pointer_handle_axis_value120: axis=%u value120=%d", axis, value120); + float delta = ((float)value120 / 120.0f) * wlconfig->mouse_scroll_multiplier; + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + wlconfig->pointer.frame_deltaY += delta; + wlconfig->pointer.frame_discrete_y += (value120 / 120); + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + wlconfig->pointer.frame_deltaX += delta; + wlconfig->pointer.frame_discrete_x += (value120 / 120); + break; + } + wlconfig->pointer.frame_has_axis = YES; +} + +/* wl_pointer.axis_relative_direction (protocol v9) — natural-scroll hint. + * Logged for traceability; direction inversion can be applied here later. */ +static void +pointer_handle_axis_relative_direction(void *data, struct wl_pointer *pointer, + uint32_t axis, uint32_t direction) +{ + NSDebugFLLog(@"WaylandScroll", + @"pointer_handle_axis_relative_direction: axis=%u direction=%u", + axis, direction); +} + const struct wl_pointer_listener pointer_listener - = {pointer_handle_enter, pointer_handle_leave, - pointer_handle_motion, pointer_handle_button, - pointer_handle_axis, pointer_handle_frame, - pointer_handle_axis_source, pointer_handle_axis_stop, - pointer_handle_axis_discrete}; + = {pointer_handle_enter, pointer_handle_leave, + pointer_handle_motion, pointer_handle_button, + pointer_handle_axis, pointer_handle_frame, + pointer_handle_axis_source, pointer_handle_axis_stop, + pointer_handle_axis_discrete, pointer_handle_axis_value120, + pointer_handle_axis_relative_direction}; @implementation WaylandServer (Cursor) From 792ea6e35ede5862d26698197c79384b9ed18576 Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 15:29:06 +1000 Subject: [PATCH 19/24] fix: in-process drag-and-drop broken in GNUstep apps (Gorm et al.) Three bugs prevented click-and-drag from working correctly within the same GNUstep application on Wayland while it worked fine on X11: 1. postDragEvent: discarded all events except NSLeftMouseUp Once _waylandExternalDragActive was set (which happened for every drag when wl_data_device is available), the override returned immediately for any event type. This meant the GSAppKitDraggingEnter/Update/Drop events posted by the device_enter/motion/drop callbacks were dequeued by GSDragView's event loop and then silently thrown away, so the target view's draggingEntered:/draggingUpdated:/performDragOperation: methods were never called. Fix: pass NSAppKitDefined events through to [super postDragEvent:] so they are forwarded to the target window. NSLeftMouseUp still exits the loop; all other types (pointer motion, which stops arriving during a Wayland drag) are suppressed. 2. device_enter/motion/leave posted events to the app queue for in-process drops. GSDragView's running event loop dequeued them, called postDragEvent:, and (before fix 1) discarded them. Even with fix 1 the queue path adds latency and could interact with GSDragView logic. Fix: detect in-process drops (dnd_source != NULL means we are the drag source, so the target is one of our own windows). For in-process, send events directly to the target NSWindow via [nswindow sendEvent:] bypassing the app queue entirely. 3. device_enter called setupInboundDragWithPasteboard: unconditionally. This overwrote dragPasteboard from the live source pasteboard to an empty NSDragPboard, which would have corrupted data_source_send for any concurrent external receiver. For in-process drops the shared WaylandDragView already carries the correct source info from dragImage:. Fix: skip setupInboundDragWithPasteboard: when inProcess == YES. 4. device_drop used a pipe for in-process data transfer. Same-client DnD deadlocks: wl_data_offer_receive asks the compositor to call wl_data_source.send back to us, but we are blocked in read() inside a dispatch callback and cannot dispatch that event. Fix: for in-process drops, copy types and data directly from the source pasteboard (WaylandDragView.dragPasteboard) to NSDragPboard in memory, with no pipe and no roundtrip. The drop event is then delivered via [nswindow sendEvent:] so performDragOperation: sees the correct data. Co-Authored-By: Claude Sonnet 4.6 --- Source/wayland/WaylandDragView.m | 334 ++++++++++++++++++++----------- 1 file changed, 215 insertions(+), 119 deletions(-) diff --git a/Source/wayland/WaylandDragView.m b/Source/wayland/WaylandDragView.m index ccb59b48..df49ccdb 100644 --- a/Source/wayland/WaylandDragView.m +++ b/Source/wayland/WaylandDragView.m @@ -396,35 +396,52 @@ - (WaylandConfig *) wlconfig if (!offer || !window) return; - /* Find the best MIME type we can receive. */ - NSString *pboardType = nil; - const char *mime = best_mime_for_offer(wlconfig, &pboardType); - - if (mime) + /* Detect in-process drag: we are both the source (dnd_source != NULL) and + * the target. The two cases need different treatment: + * + * in-process — deliver events directly to the window via sendEvent: so + * they bypass GSDragView's running event loop. Do NOT call + * setupInboundDragWithPasteboard: — it would overwrite + * dragPasteboard and break data_source_send. The shared + * WaylandDragView already carries the correct source info. + * + * inter-process — post events to the app queue (GSDragView loop picks them + * up) and set up the drag view as the inbound NSDraggingInfo. + */ + BOOL inProcess = (wlconfig->dnd_source != NULL); + + if (!inProcess) { - wl_data_offer_accept(offer, serial, mime); + /* Find the best MIME type we can receive from an external source. */ + NSString *pboardType = nil; + const char *mime = best_mime_for_offer(wlconfig, &pboardType); - if (wlconfig->data_device_manager_version >= 3) + if (mime) + { + wl_data_offer_accept(offer, serial, mime); + + if (wlconfig->data_device_manager_version >= 3) + { + uint32_t myActions = ns_op_to_wl_actions( + NSDragOperationCopy | NSDragOperationMove); + wl_data_offer_set_actions(offer, myActions, + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + } + } + else { - uint32_t myActions = ns_op_to_wl_actions( - NSDragOperationCopy | NSDragOperationMove); - wl_data_offer_set_actions(offer, myActions, - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + wl_data_offer_accept(offer, serial, NULL); } - } - else - { - wl_data_offer_accept(offer, serial, NULL); - } - /* Set up the shared drag view as NSDraggingInfo for AppKit. */ - WaylandDragView *dv = [WaylandDragView sharedDragView]; - [dv setupInboundDragWithPasteboard: - [NSPasteboard pasteboardWithName: NSDragPboard] - operation: wl_action_to_ns( - wlconfig->dnd_offer_source_actions - ? wlconfig->dnd_offer_source_actions - : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)]; + /* Set up the shared drag view as NSDraggingInfo for AppKit. */ + WaylandDragView *dv = [WaylandDragView sharedDragView]; + [dv setupInboundDragWithPasteboard: + [NSPasteboard pasteboardWithName: NSDragPboard] + operation: wl_action_to_ns( + wlconfig->dnd_offer_source_actions + ? wlconfig->dnd_offer_source_actions + : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)]; + } NSWindow *nswindow = GSWindowWithNumber(window->window_id); if (!nswindow) @@ -441,7 +458,12 @@ - (WaylandConfig *) wlconfig subtype: GSAppKitDraggingEnter data1: 0 data2: 0]; - [NSApp postEvent: ev atStart: NO]; + + /* Deliver directly for in-process; queue for inter-process. */ + if (inProcess) + [nswindow sendEvent: ev]; + else + [NSApp postEvent: ev atStart: NO]; } static void @@ -459,6 +481,7 @@ - (WaylandConfig *) wlconfig NSWindow *nswindow = GSWindowWithNumber(window->window_id); if (nswindow) { + BOOL inProcess = (wlconfig->dnd_source != NULL); NSEvent *ev = [NSEvent otherEventWithType: NSAppKitDefined location: NSZeroPoint @@ -469,7 +492,10 @@ - (WaylandConfig *) wlconfig subtype: GSAppKitDraggingExit data1: 0 data2: 0]; - [NSApp postEvent: ev atStart: NO]; + if (inProcess) + [nswindow sendEvent: ev]; + else + [NSApp postEvent: ev atStart: NO]; } } @@ -497,8 +523,11 @@ - (WaylandConfig *) wlconfig NSDebugFLLog(@"WaylandDnD", @"device_motion: (%g,%g)", x, y); - /* Keep accepting so the compositor knows we still want the drag. */ - if (wlconfig->dnd_offer) + BOOL inProcess = (wlconfig->dnd_source != NULL); + + /* Keep accepting so the compositor knows we still want the drag + * (only meaningful for inter-process; in-process has no offer to negotiate). */ + if (!inProcess && wlconfig->dnd_offer) { NSString *pt = nil; const char *mime = best_mime_for_offer(wlconfig, &pt); @@ -521,7 +550,11 @@ - (WaylandConfig *) wlconfig subtype: GSAppKitDraggingUpdate data1: (NSInteger)NSDragOperationCopy data2: 0]; - [NSApp postEvent: ev atStart: NO]; + + if (inProcess) + [nswindow sendEvent: ev]; + else + [NSApp postEvent: ev atStart: NO]; } static void @@ -544,104 +577,145 @@ - (WaylandConfig *) wlconfig return; } - NSString *pboardType = nil; - const char *mime = best_mime_for_offer(wlconfig, &pboardType); + BOOL inProcess = (wlconfig->dnd_source != NULL); - if (!mime || !pboardType) - { - wl_data_offer_destroy(wlconfig->dnd_offer); - wlconfig->dnd_offer = NULL; - dnd_offer_mimes_free(wlconfig); - wlconfig->dnd_incoming = NO; - return; - } + NSWindow *nswindow = GSWindowWithNumber(window->window_id); - /* Receive the data via a pipe. */ - int pipefd[2]; - if (pipe(pipefd) < 0) + if (inProcess) { - wl_data_offer_destroy(wlconfig->dnd_offer); - wlconfig->dnd_offer = NULL; - dnd_offer_mimes_free(wlconfig); - wlconfig->dnd_incoming = NO; - return; + /* In-process drop: source and target are the same Wayland client. + * + * We cannot go through wl_data_offer_receive + pipe here: receiving + * triggers the compositor to call wl_data_source.send back to us, but + * we are blocked inside a dispatch callback and cannot re-enter the + * event loop to process that send event — a deadlock. + * + * Instead, copy the types and data from the source pasteboard (which + * WaylandDragView still holds from the dragImage: call) directly to + * NSDragPboard so the target view gets the correct data. */ + WaylandDragView *dv = [WaylandDragView sharedDragView]; + NSPasteboard *srcPboard = [dv draggingPasteboard]; + NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard]; + + NSArray *types = [srcPboard types]; + if ([types count] > 0) + { + [pboard declareTypes: types owner: nil]; + for (NSString *type in types) + { + NSData *d = [srcPboard dataForType: type]; + if (d) + [pboard setData: d forType: type]; + } + } + + if (nswindow) + { + NSDragOperation op = wl_action_to_ns(wlconfig->dnd_current_action); + if (op == NSDragOperationNone) + op = NSDragOperationCopy; + NSPoint nsPos = + NSMakePoint(wlconfig->dnd_x, window->height - wlconfig->dnd_y); + NSEvent *ev = + [NSEvent otherEventWithType: NSAppKitDefined + location: nsPos + modifierFlags: 0 + timestamp: [[NSDate date] timeIntervalSinceReferenceDate] + windowNumber: window->window_id + context: nil + subtype: GSAppKitDraggingDrop + data1: (NSInteger)op + data2: 0]; + [nswindow sendEvent: ev]; + } } + else + { + /* Inter-process drop: receive data through the pipe. */ + NSString *pboardType = nil; + const char *mime = best_mime_for_offer(wlconfig, &pboardType); - wl_data_offer_receive(wlconfig->dnd_offer, mime, pipefd[1]); - close(pipefd[1]); - wl_display_flush(wlconfig->display); + if (!mime || !pboardType) + goto cleanup; - /* Block-read; the source writes after the flush above. */ - NSData *rawData = read_fd_to_data(pipefd[0]); + int pipefd[2]; + if (pipe(pipefd) < 0) + goto cleanup; - /* Populate the drag pasteboard. */ - NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard]; - [pboard declareTypes: @[pboardType] owner: nil]; + wl_data_offer_receive(wlconfig->dnd_offer, mime, pipefd[1]); + close(pipefd[1]); + wl_display_flush(wlconfig->display); - if ([pboardType isEqual: @"NSStringPboardType"] - || [pboardType isEqual: NSPasteboardTypeString]) - { - NSString *s = [[NSString alloc] - initWithData: rawData encoding: NSUTF8StringEncoding]; - if (!s) - s = [[NSString alloc] - initWithData: rawData encoding: NSISOLatin1StringEncoding]; - if (s) + NSData *rawData = read_fd_to_data(pipefd[0]); + + NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard]; + [pboard declareTypes: @[pboardType] owner: nil]; + + if ([pboardType isEqual: @"NSStringPboardType"] + || [pboardType isEqual: NSPasteboardTypeString]) { - [pboard setString: s forType: pboardType]; - [s release]; + NSString *s = [[NSString alloc] + initWithData: rawData encoding: NSUTF8StringEncoding]; + if (!s) + s = [[NSString alloc] + initWithData: rawData encoding: NSISOLatin1StringEncoding]; + if (s) + { + [pboard setString: s forType: pboardType]; + [s release]; + } } - } - else if ([pboardType isEqual: @"NSFilenamesPboardType"]) - { - NSString *raw = [[NSString alloc] - initWithData: rawData encoding: NSUTF8StringEncoding]; - NSArray *lines = [raw componentsSeparatedByCharactersInSet: - [NSCharacterSet newlineCharacterSet]]; - NSMutableArray *paths = [NSMutableArray array]; - for (NSString *line in lines) + else if ([pboardType isEqual: @"NSFilenamesPboardType"]) { - NSString *trimmed = [line stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceCharacterSet]]; - if ([trimmed length] == 0 || [trimmed hasPrefix: @"#"]) - continue; - NSURL *url = [NSURL URLWithString: trimmed]; - if ([url isFileURL]) - [paths addObject: [url path]]; - else if ([trimmed hasPrefix: @"/"]) - [paths addObject: trimmed]; + NSString *raw = [[NSString alloc] + initWithData: rawData encoding: NSUTF8StringEncoding]; + NSArray *lines = [raw componentsSeparatedByCharactersInSet: + [NSCharacterSet newlineCharacterSet]]; + NSMutableArray *paths = [NSMutableArray array]; + for (NSString *line in lines) + { + NSString *trimmed = [line stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceCharacterSet]]; + if ([trimmed length] == 0 || [trimmed hasPrefix: @"#"]) + continue; + NSURL *url = [NSURL URLWithString: trimmed]; + if ([url isFileURL]) + [paths addObject: [url path]]; + else if ([trimmed hasPrefix: @"/"]) + [paths addObject: trimmed]; + } + [pboard setPropertyList: paths forType: @"NSFilenamesPboardType"]; + [raw release]; + } + else + { + [pboard setData: rawData forType: pboardType]; } - [pboard setPropertyList: paths forType: @"NSFilenamesPboardType"]; - [raw release]; - } - else - { - [pboard setData: rawData forType: pboardType]; - } - /* Deliver the drop event to the AppKit window. */ - NSWindow *nswindow = GSWindowWithNumber(window->window_id); - if (nswindow) - { - NSDragOperation op = wl_action_to_ns(wlconfig->dnd_current_action); - NSPoint nsPos = NSMakePoint(wlconfig->dnd_x, window->height - wlconfig->dnd_y); - - NSEvent *ev = - [NSEvent otherEventWithType: NSAppKitDefined - location: nsPos - modifierFlags: 0 - timestamp: [[NSDate date] timeIntervalSinceReferenceDate] - windowNumber: window->window_id - context: nil - subtype: GSAppKitDraggingDrop - data1: (NSInteger)op - data2: 0]; - [NSApp postEvent: ev atStart: NO]; + if (nswindow) + { + NSDragOperation op = wl_action_to_ns(wlconfig->dnd_current_action); + NSPoint nsPos = + NSMakePoint(wlconfig->dnd_x, window->height - wlconfig->dnd_y); + NSEvent *ev = + [NSEvent otherEventWithType: NSAppKitDefined + location: nsPos + modifierFlags: 0 + timestamp: [[NSDate date] timeIntervalSinceReferenceDate] + windowNumber: window->window_id + context: nil + subtype: GSAppKitDraggingDrop + data1: (NSInteger)op + data2: 0]; + [NSApp postEvent: ev atStart: NO]; + } + + /* Finish the offer (v3+). */ + if (wlconfig->data_device_manager_version >= 3) + wl_data_offer_finish(wlconfig->dnd_offer); } - /* Finish and destroy the offer. */ - if (wlconfig->data_device_manager_version >= 3) - wl_data_offer_finish(wlconfig->dnd_offer); +cleanup: wl_data_offer_destroy(wlconfig->dnd_offer); wlconfig->dnd_offer = NULL; @@ -866,16 +940,38 @@ - (void) dragImage: (NSImage *)anImage - (void) postDragEvent: (NSEvent *)theEvent { - /* During a Wayland external drag, pointer events from the compositor stop. - * We only care about the fake NSLeftMouseUp we post from data_source - * callbacks to exit the event loop. Suppress all other routing. */ - if (_waylandExternalDragActive) + if (!_waylandExternalDragActive) { - if ([theEvent type] == NSLeftMouseUp) - isDragging = NO; + [super postDragEvent: theEvent]; return; } - [super postDragEvent: theEvent]; + + /* During a Wayland-driven drag the compositor stops delivering pointer + * events, so only two event types matter in GSDragView's loop: + * + * NSLeftMouseUp — fake event posted by data_source callbacks to exit + * the loop when the drag ends (cancel or finish). + * + * NSAppKitDefined — GSAppKitDraggingEnter/Update/Drop events generated + * by the inter-process device_enter/motion/drop callbacks + * and posted to the app queue. Forward them through + * super so AppKit routes them to the target window's + * dragging protocol methods (draggingEntered: etc.). + * In-process events go directly via sendEvent: and + * never reach here. + * + * Everything else is suppressed — no pointer motion arrives in this mode. */ + switch ([theEvent type]) + { + case NSLeftMouseUp: + isDragging = NO; + break; + case NSAppKitDefined: + [super postDragEvent: theEvent]; + break; + default: + break; + } } - (void) sendExternalEvent: (GSAppKitSubtype)subtype From f3e5a52845c3cde78ffadfa409b435bcfc4dea8d Mon Sep 17 00:00:00 2001 From: DMJC Date: Fri, 15 May 2026 22:29:13 +1000 Subject: [PATCH 20/24] fix: correct mouse tracking over OpenGL subsurfaces with multiple GL views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pointer events for a wl_subsurface arrive with sx/sy relative to that subsurface's own origin, not the parent window origin. The previous approach stored the subsurface offset in a single gl_subsurface_x/y pair on struct window, which was overwritten by whichever WaylandGLContext ran last — breaking coordinate translation for all other GL views in the same window (e.g. the three NSOpenGLViews in MyGL.app each picking up the wrong view's offset, causing hitTest: to always route mouse events to the same view regardless of which one was clicked). Replace the per-window offset fields with a struct wl_surface_binding stored as the wl_surface user data for every GNUstep-owned surface: - Main window surfaces embed their binding (offset 0,0) directly inside struct window — no allocation required. - Each WaylandGLContext mallocs its own binding with its actual subsurface position; the binding is updated on resize and freed with the surface. pointer_handle_enter reads the per-surface offset via surface_get_offset() and stores it in pointer.focus_offset_x/y. pointer_handle_motion applies that offset unconditionally (it is zero for main surfaces). All other wl_surface_get_user_data call sites are updated to surface_get_window(). Also folds in the earlier keyboard_handle_enter NULL-surface guard. Co-Authored-By: Claude Sonnet 4.6 --- Headers/wayland/WaylandOpenGL.h | 1 + Headers/wayland/WaylandServer.h | 48 +++++++++++++++++++++++-- Source/wayland/WaylandDragView.m | 2 +- Source/wayland/WaylandGLContext.m | 25 ++++++++++--- Source/wayland/WaylandServer+Cursor.m | 24 ++++++++----- Source/wayland/WaylandServer+Keyboard.m | 4 ++- Source/wayland/WaylandServer.m | 15 ++++++-- 7 files changed, 98 insertions(+), 21 deletions(-) diff --git a/Headers/wayland/WaylandOpenGL.h b/Headers/wayland/WaylandOpenGL.h index 667776d5..f9c6d5dc 100644 --- a/Headers/wayland/WaylandOpenGL.h +++ b/Headers/wayland/WaylandOpenGL.h @@ -42,6 +42,7 @@ struct window; struct window *_window; struct wl_surface *_glSurface; struct wl_subsurface *_glSubsurface; + struct wl_surface_binding *_glSurfaceBinding; /* malloc'd binding for _glSurface */ struct wl_egl_window *_eglWindow; EGLDisplay _eglDisplay; EGLContext _eglContext; diff --git a/Headers/wayland/WaylandServer.h b/Headers/wayland/WaylandServer.h index 92c77d45..bf38333e 100644 --- a/Headers/wayland/WaylandServer.h +++ b/Headers/wayland/WaylandServer.h @@ -45,6 +45,38 @@ #include "wayland/xdg-decoration-unstable-v1-client-protocol.h" #include "wayland/text-input-unstable-v3-client-protocol.h" +/* User data stored on every wl_surface owned by GNUstep. + * For main window surfaces offset_x/y are 0.0. + * For GL subsurfaces they hold the subsurface position within the parent window + * (Wayland Y-down coordinates), so pointer event coordinates can be translated + * to parent-window space. Each WaylandGLContext malloc's its own binding so + * multiple GL views in the same window each have independent offsets. */ +struct wl_surface_binding +{ + struct window *window; + float offset_x; + float offset_y; +}; + +/* Retrieve the window from any GNUstep-owned wl_surface. */ +static inline struct window * +surface_get_window(struct wl_surface *surface) +{ + struct wl_surface_binding *b + = (struct wl_surface_binding *)wl_surface_get_user_data(surface); + return b ? b->window : NULL; +} + +/* Retrieve the subsurface-to-window offset; returns (0,0) for main surfaces. */ +static inline void +surface_get_offset(struct wl_surface *surface, float *ox, float *oy) +{ + struct wl_surface_binding *b + = (struct wl_surface_binding *)wl_surface_get_user_data(surface); + *ox = b ? b->offset_x : 0.0f; + *oy = b ? b->offset_y : 0.0f; +} + struct pointer { struct wl_pointer *wlpointer; @@ -61,9 +93,15 @@ struct pointer uint32_t axis_source; - uint32_t serial; - struct window *focus; - struct window *captured; + uint32_t serial; + struct window *focus; + struct window *captured; + /* Subsurface-to-window offset captured at pointer-enter time. + * Zero for main window surfaces; non-zero when the pointer entered a GL + * subsurface. Applied to sx/sy in motion/button handlers so all AppKit + * coordinates are expressed in parent-window space. */ + float focus_offset_x; + float focus_offset_y; /* Per-frame axis accumulation (cleared after each wl_pointer.frame event). * Populated by axis/axis_discrete; dispatched as one NSScrollWheel in frame. */ @@ -214,6 +252,10 @@ struct window BOOL usesOpenGL; BOOL global_pos_known; // saved_pos_x/y hold a reliable output-relative origin + /* Binding embedded in the struct for this window's own wl_surface. + * Set once at surface-creation time; offset_x/y are always 0,0. */ + struct wl_surface_binding surface_binding; + float pos_x; float pos_y; float width; diff --git a/Source/wayland/WaylandDragView.m b/Source/wayland/WaylandDragView.m index df49ccdb..cc4df251 100644 --- a/Source/wayland/WaylandDragView.m +++ b/Source/wayland/WaylandDragView.m @@ -387,7 +387,7 @@ - (WaylandConfig *) wlconfig wlconfig->dnd_incoming = YES; wlconfig->event_serial = serial; - struct window *window = surface ? wl_surface_get_user_data(surface) : NULL; + struct window *window = surface ? surface_get_window(surface) : NULL; wlconfig->dnd_target = window; NSDebugFLLog(@"WaylandDnD", @"device_enter: win=%d pos=(%g,%g)", diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m index 134d75ee..5c71bd6b 100644 --- a/Source/wayland/WaylandGLContext.m +++ b/Source/wayland/WaylandGLContext.m @@ -37,6 +37,7 @@ #include #include #include +#include #include "wayland/WaylandServer.h" #include "wayland/WaylandOpenGL.h" @@ -271,7 +272,12 @@ - (BOOL)_ensureSurface wl_subsurface_set_desync(_glSubsurface); wl_subsurface_set_position(_glSubsurface, subX, subY); - wl_surface_set_user_data(_glSurface, _window); + _glSurfaceBinding = (struct wl_surface_binding *) + malloc(sizeof(struct wl_surface_binding)); + _glSurfaceBinding->window = _window; + _glSurfaceBinding->offset_x = (float)subX; + _glSurfaceBinding->offset_y = (float)subY; + wl_surface_set_user_data(_glSurface, _glSurfaceBinding); wl_surface_commit(_window->surface); wl_display_flush(wlconfig->display); @@ -347,6 +353,12 @@ - (void)_destroySurface wl_surface_destroy(_glSurface); _glSurface = NULL; } + + if (_glSurfaceBinding != NULL) + { + free(_glSurfaceBinding); + _glSurfaceBinding = NULL; + } } - (void)_loadExtensions @@ -683,9 +695,14 @@ - (void)update if (_glSubsurface != NULL) { - wl_subsurface_set_position(_glSubsurface, - (int)NSMinX(viewFrame), - (int)NSMinY(viewFrame)); + int newSubX = (int)NSMinX(viewFrame); + int newSubY = (int)NSMinY(viewFrame); + wl_subsurface_set_position(_glSubsurface, newSubX, newSubY); + if (_glSurfaceBinding != NULL) + { + _glSurfaceBinding->offset_x = (float)newSubX; + _glSurfaceBinding->offset_y = (float)newSubY; + } wl_surface_commit(_window->surface); wl_display_flush(_window->wlconfig->display); } diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m index 3ae73b01..4e8113be 100644 --- a/Source/wayland/WaylandServer+Cursor.m +++ b/Source/wayland/WaylandServer+Cursor.m @@ -53,17 +53,21 @@ { WaylandConfig *wlconfig = data; - struct window *window = wl_surface_get_user_data(surface); + struct window *window = surface_get_window(surface); if (window == NULL || window->ignoreMouse) { return; } - wlconfig->pointer.focus = window; + float ox, oy; + surface_get_offset(surface, &ox, &oy); + wlconfig->pointer.focus = window; + wlconfig->pointer.focus_offset_x = ox; + wlconfig->pointer.focus_offset_y = oy; - float sx = wl_fixed_to_double(sx_w); - float sy = wl_fixed_to_double(sy_w); + float sx = wl_fixed_to_double(sx_w) + ox; + float sy = wl_fixed_to_double(sy_w) + oy; /* Track cursor global (output-relative) position so we can infer where * xdg_toplevel windows actually are on screen. @@ -149,7 +153,7 @@ { WaylandConfig *wlconfig = data; - struct window *window = wl_surface_get_user_data(surface); + struct window *window = surface_get_window(surface); if (window == NULL || window->ignoreMouse) { @@ -192,8 +196,10 @@ [GSCurrentServer() postEvent:event atStart:NO]; } - wlconfig->pointer.focus = NULL; - wlconfig->pointer.serial = serial; + wlconfig->pointer.focus = NULL; + wlconfig->pointer.focus_offset_x = 0.0f; + wlconfig->pointer.focus_offset_y = 0.0f; + wlconfig->pointer.serial = serial; wlconfig->event_serial = serial; } } @@ -214,8 +220,8 @@ { return; } - float sx = wl_fixed_to_double(sx_w); - float sy = wl_fixed_to_double(sy_w); + float sx = wl_fixed_to_double(sx_w) + wlconfig->pointer.focus_offset_x; + float sy = wl_fixed_to_double(sy_w) + wlconfig->pointer.focus_offset_y; wlconfig->pointer.last_timestamp = (NSTimeInterval) time / 1000.0; diff --git a/Source/wayland/WaylandServer+Keyboard.m b/Source/wayland/WaylandServer+Keyboard.m index 43d89088..5e36197c 100644 --- a/Source/wayland/WaylandServer+Keyboard.m +++ b/Source/wayland/WaylandServer+Keyboard.m @@ -127,7 +127,9 @@ - (void) insertText: (id)aString; wlconfig->event_serial = serial; NSDebugFLLog(@"WaylandIME", @"keyboard_handle_enter: serial=%u", serial); - struct window *window = wl_surface_get_user_data(surface); + if (!surface) + return; + struct window *window = surface_get_window(surface); if (!window) return; diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m index dea296eb..7b92d6f9 100644 --- a/Source/wayland/WaylandServer.m +++ b/Source/wayland/WaylandServer.m @@ -1191,7 +1191,10 @@ - (void)createTopLevel:(struct window *)window return; } - wl_surface_set_user_data(window->surface, window); + window->surface_binding.window = window; + window->surface_binding.offset_x = 0.0f; + window->surface_binding.offset_y = 0.0f; + wl_surface_set_user_data(window->surface, &window->surface_binding); if (window->xdg_surface == NULL) { window->xdg_surface @@ -1258,7 +1261,10 @@ - (void)createLayerShell:(struct window *)window } window->surface = wl_compositor_create_surface(wlconfig->compositor); - wl_surface_set_user_data(window->surface, window); + window->surface_binding.window = window; + window->surface_binding.offset_x = 0.0f; + window->surface_binding.offset_y = 0.0f; + wl_surface_set_user_data(window->surface, &window->surface_binding); const char *cString = [namespace UTF8String]; window->layer_surface @@ -1381,7 +1387,10 @@ - (void)createPopupShell:(struct window *)child child->parent_id = parent->window_id; child->surface = wl_compositor_create_surface(wlconfig->compositor); - wl_surface_set_user_data(child->surface, child); + child->surface_binding.window = child; + child->surface_binding.offset_x = 0.0f; + child->surface_binding.offset_y = 0.0f; + wl_surface_set_user_data(child->surface, &child->surface_binding); NSWindow *nswin = (GSWindowWithNumber(child->window_id)); CGFloat x = nswin.frame.origin.x; From 27a4294107c33403507874698cc48b101e2841b5 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 16 May 2026 11:48:03 +1000 Subject: [PATCH 21/24] ChangeLog: add entries for all Wayland and Opal commits since 2025-12-18 Co-Authored-By: Claude Sonnet 4.6 --- ChangeLog | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/ChangeLog b/ChangeLog index 29c957ee..a6cfd181 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,193 @@ +2026-05-15 James Carthew + + * Headers/wayland/WaylandOpenGL.h: + * Headers/wayland/WaylandServer.h: + * Source/wayland/WaylandDragView.m: + * Source/wayland/WaylandGLContext.m: + * Source/wayland/WaylandServer+Cursor.m: + * Source/wayland/WaylandServer+Keyboard.m: + * Source/wayland/WaylandServer.m: + Wayland: fix mouse tracking over OpenGL subsurfaces with multiple GL + views. Replace per-window subsurface offset with a per-surface + wl_surface_binding stored as wl_surface user data so each + WaylandGLContext has independent offset tracking. + pointer_handle_enter reads the offset via surface_get_offset() and + stores it in pointer.focus_offset_x/y; pointer_handle_motion applies + the stored offset for correct parent-window coordinate translation. + Folds in the keyboard_handle_enter NULL-surface guard. + + * Source/wayland/WaylandDragView.m: + Wayland: fix in-process drag-and-drop in GNUstep applications. + postDragEvent: was discarding all events except NSLeftMouseUp once + the Wayland data device was available; pass NSAppKitDefined events + through to super so in-process draggingEntered:/draggingUpdated:/ + performDragOperation: callbacks reach the target view. Fix + device_enter/motion/leave to skip same-process re-posting. + + * Source/wayland/WaylandServer+Cursor.m: + Wayland: fill all wl_pointer_listener slots to prevent SIGABRT. + wayland-client 1.24 defines 11 event slots; add no-op stubs for + frame, axis_source, axis_stop, and axis_discrete handlers. + + * Source/wayland/WaylandServer+Seat.m: + Wayland: add wl_seat.name handler to prevent SIGABRT on startup. + Bumping the seat binding to v5 causes wl_seat.name to be delivered + during the initial roundtrip. Add seat_handle_name stub so + libwayland does not abort on a NULL dispatcher slot. + + * Tools/wayland-smoke-test.sh: + Wayland: extend smoke-test harness to Milestone 5 (buffer lifecycle + and stability checks). + + * Headers/cairo/WaylandCairoShmSurface.h: + * Source/cairo/WaylandCairoShmSurface.m: + * Source/wayland/WaylandServer+Xdgshell.m: + * Source/wayland/WaylandServer.m: + Wayland M5: buffer lifecycle hardening and surface destruction + ordering. Add needs_repaint flag and owner back-pointers to + pool_buffer. Fix FD leak in finishBuffer. Re-attach buffers on + compositor release when a repaint is pending. Guard against size + mismatch and destroyed surfaces in the release callback. + + * Headers/wayland/WaylandServer.h: + * Source/wayland/WaylandServer+Output.m: + * Source/wayland/WaylandServer.m: + Wayland M4: output hotplug and scale/geometry reconfiguration. Add + effective_width/height, name, description, and configured fields to + struct output. Rewrite output_listener: recompute logical dimensions + for scale and rotation, clamp windows to new bounds, and post + NSApplicationDidChangeScreenParametersNotification. Bind wl_output + at version 4. + + * Headers/wayland/WaylandServer.h: + * Source/wayland/WaylandServer+Cursor.m: + * Source/wayland/WaylandServer.m: + Wayland M3: extra mouse buttons and per-frame scroll accumulation. + Map BTN_SIDE/EXTRA/FORWARD/BACK/TASK to NSOtherMouseDown/Up. Fix + buttonNumber to use button-BTN_LEFT offset. Bump wl_seat to v5 and + accumulate axis deltas per compositor frame before dispatching a + single NSScrollWheel event. + + * Headers/wayland/WaylandInputServer.h: + * Headers/wayland/WaylandServer.h: + * Source/wayland/GNUmakefile: + * Source/wayland/WaylandInputServer.m: + * Source/wayland/WaylandServer+Keyboard.m: + * Source/wayland/WaylandServer.m: + * Source/wayland/text-input-unstable-v3-protocol.c: + Wayland M2: IME/preedit support via zwp_text_input_v3. Generate and + wire text-input-unstable-v3 protocol. Fix XKB UTF-8 conversion by + replacing the broken &sym cast with xkb_state_key_get_utf8. Add + WaylandInputServer for preedit spot/rect management. Bind + text_input_manager and create zwp_text_input_v3 from the seat. + + * Headers/wayland/WaylandDragView.h: + * Headers/wayland/WaylandServer.h: + * Source/cairo/WaylandCairoShmSurface.m: + * Source/wayland/WaylandDragView.m: + * Source/wayland/WaylandInputServer.m: + * Source/wayland/WaylandServer+Cursor.m: + * Source/wayland/WaylandServer+Keyboard.m: + * Source/wayland/WaylandServer+Output.m: + * Source/wayland/WaylandServer.m: + * Tools/wayland-smoke-test.sh: + Wayland M0+M1: add targeted debug log categories and inter-process + DnD. Replace bare NSDebugLog with NSDebugMLLog/NSDebugFLLog across + Wayland sources (categories: WaylandDnD, WaylandIME, WaylandPointer, + WaylandScroll, WaylandOutput). Add Tools/wayland-smoke-test.sh + integration harness. Implement inter-process drag-and-drop via + wl_data_device: bind data_device_manager, wire source/offer + listeners, implement draggingSourceOperationMask, + addDragTypes:toWindow:, and performDragOperation:. + + * Headers/wayland/WaylandServer.h: + * Source/wayland/GNUmakefile: + * Source/wayland/WaylandServer+Cursor.m: + * Source/wayland/WaylandServer+Xdgshell.m: + * Source/wayland/WaylandServer.m: + Wayland: update popup menu handling; add xdg-decoration-unstable-v1 + protocol support for server-side window decorations. + +2026-04-30 Joe Maloney + + * Headers/opal/OpalSurface.h: + * Source/opal/OpalContext.m: + * Source/opal/OpalFontInfo.m: + * Source/opal/OpalGState.m: + * Source/opal/OpalSurface.m: + Opal backend: restore functionality. Fix font hinting, gradients, + blend modes, gray-to-RGB colour conversion, and lazy surface + creation. Fix window movement trails by flushing backing and X11 + contexts in handleExposeRect. Make X11 surface attachment + self-contained. + +2026-04-30 James Carthew + + * Headers/wayland/WaylandDragView.h: + * Headers/wayland/WaylandInputServer.h: + * Headers/wayland/WaylandOpenGL.h: + * Headers/wayland/WaylandServer.h: + * Source/wayland/GNUmakefile: + * Source/wayland/WaylandDragView.m: + * Source/wayland/WaylandGLContext.m: + * Source/wayland/WaylandGLPixelFormat.m: + * Source/wayland/WaylandInputServer.m: + * Source/wayland/WaylandServer+Keyboard.m: + * Source/wayland/WaylandServer+Xdgshell.m: + * Source/wayland/WaylandServer.m: + Wayland: complete OpenGL subsurface integration. Use wl_subsurface + to host GL views, implement frame callbacks and damage tracking, fix + context sharing, and integrate WaylandInputServer for input method + support alongside GL contexts. + +2026-04-20 James Carthew + + * Source/wayland/WaylandServer+Cursor.m: + Wayland: fix cursor handling when moving between GL subsurfaces and + main window surfaces. + +2026-04-19 James Carthew + + * Source/wayland/WaylandGLContext.m: + * Source/wayland/WaylandServer+Cursor.m: + Wayland: fix crash in cursor handling when the pointer enters a GL + subsurface. + + * Headers/wayland/WaylandOpenGL.h: + * Headers/wayland/WaylandServer.h: + * Source/GNUmakefile.preamble: + * Source/wayland/GNUmakefile.preamble: + * Source/wayland/WaylandGLContext.m: + * Source/wayland/WaylandServer.m: + Wayland: complete OpenGL context implementation. Add EGL surface + creation on wl_subsurface, frame-callback-driven buffer swap, and + usesOpenGL tracking in struct window. + +2026-04-18 James Carthew + + * Source/wayland/WaylandGLContext.m: + Wayland: fix noisy GL attach failures on unmapped surfaces; plug a + retain-count leak on early error paths in WaylandGLContext. + + * Source/wayland/WaylandGLContext.m: + Wayland: rebind OpenGL context to the correct native window when a + view is reparented before makeCurrentContext is called. + + * Headers/wayland/WaylandOpenGL.h: + * Headers/wayland/WaylandServer.h: + * Source/GNUmakefile.preamble: + * Source/wayland/GNUmakefile: + * Source/wayland/GNUmakefile.preamble: + * Source/wayland/WaylandGLContext.m: + * Source/wayland/WaylandGLPixelFormat.m: + * Source/wayland/WaylandServer.m: + * configure.ac: + Wayland: initial OpenGL support via EGL and wl_subsurface. Add new + WaylandGLContext and WaylandGLPixelFormat classes implementing + NSOpenGLContext/NSOpenGLPixelFormat for the Wayland backend. Link + against libGL, libEGL, and libwayland-egl. + 2025-12-18 Richard Frith-Macdonald * Source/x11/XGServer.m: From 547e69f932762a11e52edfb389961efe58b30b19 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 16 May 2026 19:55:06 +1000 Subject: [PATCH 22/24] Delete wayland_x11_feature_gap_report.md --- wayland_x11_feature_gap_report.md | 56 ------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 wayland_x11_feature_gap_report.md diff --git a/wayland_x11_feature_gap_report.md b/wayland_x11_feature_gap_report.md deleted file mode 100644 index d17b7000..00000000 --- a/wayland_x11_feature_gap_report.md +++ /dev/null @@ -1,56 +0,0 @@ -# Wayland vs X11 backend feature-gap report - -This report lists features available in the X11 backend that are currently missing or only stubbed/partial in the Wayland backend. - -## 1) Inter-process drag-and-drop (external DnD) is missing on Wayland - -- Wayland explicitly says inter-process drag via `wl_data_device` is not implemented and only logs for external events. -- X11 implements external DnD message flow (`XdndStatus`, `XdndFinished`, `XdndEnter`, `XdndPosition`, `XdndDrop`, `XdndLeave`) via `xdnd_*` calls. - -Evidence: -- Wayland: `Source/wayland/WaylandDragView.m` (`postDragEvent`, `sendExternalEvent`). -- X11: `Source/x11/XGDragView.m` (`postDragEvent`, `sendExternalEvent`). - -## 2) Input method (IME/XIM-style preedit/status positioning) support is missing on Wayland - -- Wayland input method style returns `nil` and all preedit/status geometry accessors return `NO`. -- X11 has a dedicated XIM input server with style negotiation and IC lifecycle management. - -Evidence: -- Wayland: `Source/wayland/WaylandInputServer.m` (`inputMethodStyle`, `statusArea`, `preeditArea`, `preeditSpot`, `setStatusArea`, `setPreeditArea`, `setPreeditSpot`). -- X11: `Source/x11/XIMInputServer.m` (`ximInit`, `ximStyleInit`, `ximCreateIC` flow). - -## 3) Extended mouse button mapping is incomplete on Wayland - -- Wayland currently maps left/right/middle only and leaves BTN_SIDE/BTN_EXTRA/BTN_FORWARD/BTN_BACK as TODO. -- X11 backend has explicit pointer-mapping commentary/handling path and broader mature event translation. - -Evidence: -- Wayland: `Source/wayland/WaylandServer+Cursor.m` TODO in button switch. -- X11 reference behavior mentioned in same file comments and implemented in mature X11 event code. - -## 4) Advanced scroll semantics are partial on Wayland - -- Wayland pointer callbacks for `frame`, `axis_stop`, and `axis_discrete` are currently empty. -- Comments note missing momentum/trackpad behavior. - -Evidence: -- Wayland: `Source/wayland/WaylandServer+Cursor.m` (`pointer_handle_frame`, `pointer_handle_axis_stop`, `pointer_handle_axis_discrete`, momentum TODO comments). - -## 5) Output reconfiguration callback path is unimplemented on Wayland - -- Wayland output mode handler includes explicit “Should we implement this?” for output configure callback behavior. -- X11 has established monitor/screen management infrastructure (monitor list + RANDR-related members in server). - -Evidence: -- Wayland: `Source/wayland/WaylandServer+Output.m` (`handle_mode` TODO block). -- X11: `Headers/x11/XGServer.h` monitor/randr fields. - -## 6) Overall backend completeness/stability is explicitly below X11 - -- Wayland backend README still declares it "incomplete and broken" and lists unresolved rendering/freezing issues. -- X11 backend is long-standing and feature-complete enough to include broad subsystems (XIM, XDND, RANDR integration points, GLX path, etc.). - -Evidence: -- Wayland: `Source/wayland/README.md`. -- X11: breadth of subsystem files in `Source/x11/` and `Headers/x11/`. From c2044871212a5a5a1ecc635d7eacff0c17f5ddd2 Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 16 May 2026 19:55:16 +1000 Subject: [PATCH 23/24] Delete wayland_feature_implementation_plan.md --- wayland_feature_implementation_plan.md | 173 ------------------------- 1 file changed, 173 deletions(-) delete mode 100644 wayland_feature_implementation_plan.md diff --git a/wayland_feature_implementation_plan.md b/wayland_feature_implementation_plan.md deleted file mode 100644 index fe27b7df..00000000 --- a/wayland_feature_implementation_plan.md +++ /dev/null @@ -1,173 +0,0 @@ -# Wayland Backend Feature Implementation Plan (vs X11 parity) - -## Scope -This plan turns the identified feature gaps into an execution roadmap, ordered by user impact and architectural dependencies. - -## Milestone 0: Baseline and instrumentation (1 week) - -### Goals -- Make regressions visible before feature work starts. - -### Tasks -1. Add targeted debug categories for Wayland DnD, IME, pointer buttons, scroll axes, output changes. -2. Add an integration harness script to run basic backend smoke tests under ambrosia Wayland compositor. -3. Capture current behavior snapshots for: - - local drag/drop - - external drag/drop - - IME composition - - extra mouse buttons - - touchpad/continuous scroll - - output hotplug/scale/geometry change - -### Exit criteria -- Repro steps and logs are documented for every missing feature bucket. - ---- - -## Milestone 1: Inter-process DnD on Wayland (`wl_data_device`) (2–3 weeks) - -### Why first -External DnD is a major UX gap and is already explicitly stubbed in `WaylandDragView`. - -### Tasks -1. **Protocol plumbing** - - Add `wl_data_device_manager`, `wl_data_device`, `wl_data_source`, `wl_data_offer` objects to `WaylandConfig` lifecycle. - - Bind globals in registry handler, add listeners, and teardown safely. -2. **Outbound drag path** - - Implement source offers for pasteboard MIME types. - - Wire drag enter/motion/leave/drop events to existing `GSDragView`/AppKit event flow. -3. **Inbound drag path** - - Accept offers, map MIME types to pasteboard types, handle selection reads over FDs. -4. **Action negotiation** - - Map Wayland dnd actions (`copy/move/ask`) to `NSDragOperation` consistently with X11 behavior. -5. **Error handling** - - Handle canceled drags, destroyed offers, and compositor-denied serials. - -### Files primarily touched -- `Source/wayland/WaylandDragView.m` -- `Source/wayland/WaylandServer.m` (registry/globals) -- `Headers/wayland/WaylandServer.h` (config structs) - -### Exit criteria -- Drag from GNUstep app to external app and vice versa works for text and URI list payloads. -- `postDragEvent` and `sendExternalEvent` no longer log “not implemented” for inter-process cases. - ---- - -## Milestone 2: IME/preedit/status support parity path (2 weeks) - -### Goals -Bring Wayland input method behavior closer to X11 XIM-visible capabilities used by AppKit text input flows. - -### Tasks -1. Introduce Wayland text-input integration strategy: - - Prefer `text-input-v3` (or compositor-supported equivalent) and optional input-method protocols. -2. Implement input method state in `WaylandInputServer`: - - Preedit string lifecycle - - Cursor/spot location updates - - Status/preedit rectangles and setters -3. Feed composed text and commit/cancel events into existing key/text dispatch pipeline. -4. Keep fallback behavior when compositor lacks protocol support. - -### Files primarily touched -- `Source/wayland/WaylandInputServer.m` -- `Source/wayland/WaylandServer+Keyboard.m` -- `Headers/wayland/WaylandInputServer.h` - -### Exit criteria -- `statusArea/preeditArea/preeditSpot` and setters return meaningful values when protocol is available. -- Basic composition (e.g., dead keys/CJK IME) works in NSText-based controls. - ---- - -## Milestone 3: Pointer button completeness + wheel/scroll semantics (1–2 weeks) - -### Goals -Close input parity gaps affecting advanced mice and touchpads. - -### Tasks -1. Map BTN_SIDE/BTN_EXTRA/BTN_FORWARD/BTN_BACK to appropriate `NSOtherMouse*` events and button numbers. -2. Implement `pointer_handle_axis_discrete`, `pointer_handle_frame`, and `pointer_handle_axis_stop`: - - group axis events per frame - - include discrete step data when available - - emit momentum/phase semantics where AppKit expects them -3. Normalize button mapping/documentation versus X11 `XGetPointerMapping` assumptions. - -### Files primarily touched -- `Source/wayland/WaylandServer+Cursor.m` - -### Exit criteria -- Side buttons generate usable app events. -- Touchpad and wheel scrolling feel consistent and no longer rely on TODO placeholders. - ---- - -## Milestone 4: Output change handling and monitor reconfiguration (1–2 weeks) - -### Goals -Implement dynamic monitor behavior expected from mature backend operation. - -### Tasks -1. Implement output configure callback path currently marked TODO. -2. Add runtime reactions for: - - output add/remove - - geometry/scale changes - - window reposition/reclamp on output change -3. Audit coordinate transforms across output scale and transform states. - -### Files primarily touched -- `Source/wayland/WaylandServer+Output.m` -- `Source/wayland/WaylandServer.m` -- `Headers/wayland/WaylandServer.h` - -### Exit criteria -- Windows remain usable after output change events (hotplug, scale change). -- Screen geometry reported to AppKit updates correctly. - ---- - -## Milestone 5: Stability and rendering hardening (ongoing, parallel) - -### Goals -Address known “incomplete/broken” operational issues and reduce compositor hangs/freeze conditions. - -### Tasks -1. Audit buffer lifecycle (attach/damage/commit/release ordering). -2. Add synchronization guards around surface destruction and resize churn. -3. Validate Cairo surface blit ordering to avoid stray backing-surface visibility. -4. Stress-test event loop responsiveness under frequent input and redraw. - -### Exit criteria -- No reproducible freeze in sustained interaction test. -- No random backing-surface artifacts in compositor during normal usage. - ---- - -## Cross-cutting requirements - -1. **Feature flags** - - Gate new protocol-dependent behavior by runtime detection and defaults. -2. **Compatibility matrix** - - Track compositor support (Weston, Mutter, KWin, wlroots-based). -3. **Testing** - - Add automated tests where feasible; otherwise scripted manual test playbooks. -4. **Documentation** - - Update `Source/wayland/README.md` milestone-by-milestone with current status. - -## Suggested delivery order -1. Milestone 0 baseline -2. Milestone 1 external DnD -3. Milestone 3 pointer/scroll completeness -4. Milestone 2 IME support -5. Milestone 4 output reconfiguration -6. Milestone 5 hardening (continuous) - -## Effort estimate summary -- M0: 1 week -- M1: 2–3 weeks -- M2: 2 weeks -- M3: 1–2 weeks -- M4: 1–2 weeks -- M5: ongoing - -Total for first parity wave (M0–M4): ~7–10 weeks for one experienced contributor, faster with parallel owners. From c493cf7e683d285112de48479645d877361ebe4a Mon Sep 17 00:00:00 2001 From: DMJC Date: Sat, 16 May 2026 19:55:34 +1000 Subject: [PATCH 24/24] Delete Tools/wayland-smoke-test.sh --- Tools/wayland-smoke-test.sh | 572 ------------------------------------ 1 file changed, 572 deletions(-) delete mode 100755 Tools/wayland-smoke-test.sh diff --git a/Tools/wayland-smoke-test.sh b/Tools/wayland-smoke-test.sh deleted file mode 100755 index b2e5b666..00000000 --- a/Tools/wayland-smoke-test.sh +++ /dev/null @@ -1,572 +0,0 @@ -#!/usr/bin/env bash -# wayland-smoke-test.sh — Integration harness for the Wayland backend (M0–M5). -# -# Starts the Ambrosia compositor nested inside the current Wayland session, -# then exercises each feature bucket in wayland_feature_implementation_plan.md -# and captures log snapshots. -# -# Milestone coverage: -# M0 T1–T11 Baseline: compositor liveness, protocol globals, source snapshots -# M5 T12–T19 Stability: buffer lifecycle audits + sustained-operation stress -# -# Usage: -# ./Tools/wayland-smoke-test.sh [options] -# -# Options: -# -c PATH Path to ambrosia-compositor binary (auto-detected if omitted) -# -o DIR Directory to write snapshot logs (default: /tmp/wayland-smoke-YYYYMMDD-HHMMSS) -# -t SECS Per-test timeout in seconds (default: 10) -# -v Verbose: show compositor log in real time -# -h Show this help -# -# Exit status: 0 if all smoke tests passed, 1 if any failed or setup failed. -# -# Debug categories enabled during the run (set via NSDebugCategories): -# WaylandDnD, WaylandIME, WaylandPointer, WaylandScroll, WaylandOutput -# -# Requirements: -# - A running Wayland session (WAYLAND_DISPLAY must be set or wayland-0 must -# exist). The compositor runs nested inside that session. -# - ambrosia-compositor binary (see -c option or AMBROSIA_ROOT below). - -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -AMBROSIA_ROOT="/home/james/development/ambrosia-experimental" - -COMPOSITOR_BIN="" -OUTPUT_DIR="" -TIMEOUT_SECS=10 -VERBOSE=0 -PASS=0 -FAIL=0 -COMPOSITOR_PID="" -CLIENT_WAYLAND_DISPLAY="" - -# ── helpers ────────────────────────────────────────────────────────────────── - -log() { echo "[smoke] $*"; } -pass() { echo "[PASS] $*"; (( PASS++ )) || true; } -fail() { echo "[FAIL] $*"; (( FAIL++ )) || true; } - -usage() { - sed -n 's/^# //p' "$0" | head -35 - exit 0 -} - -# ── cleanup on exit ─────────────────────────────────────────────────────────── - -cleanup() { - if [[ -n "${COMPOSITOR_PID}" ]] && kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then - log "stopping compositor (pid ${COMPOSITOR_PID}) …" - kill -TERM "${COMPOSITOR_PID}" 2>/dev/null || true - wait "${COMPOSITOR_PID}" 2>/dev/null || true - log "compositor stopped" - fi -} -trap cleanup EXIT - -# ── argument parsing ────────────────────────────────────────────────────────── - -while getopts "c:o:t:vh" opt; do - case "${opt}" in - c) COMPOSITOR_BIN="${OPTARG}" ;; - o) OUTPUT_DIR="${OPTARG}" ;; - t) TIMEOUT_SECS="${OPTARG}" ;; - v) VERBOSE=1 ;; - h) usage ;; - *) echo "Unknown option -${OPTARG}" >&2; exit 1 ;; - esac -done - -# ── locate compositor ───────────────────────────────────────────────────────── - -if [[ -z "${COMPOSITOR_BIN}" ]]; then - for candidate in \ - "${AMBROSIA_ROOT}/Compositor/obj/ambrosia-compositor" \ - "${AMBROSIA_ROOT}/Compositor"/obj.*/ambrosia-compositor \ - "$(command -v ambrosia-compositor 2>/dev/null || true)"; do - if [[ -x "${candidate}" ]]; then - COMPOSITOR_BIN="${candidate}" - break - fi - done -fi - -if [[ -z "${COMPOSITOR_BIN}" || ! -x "${COMPOSITOR_BIN}" ]]; then - echo "ERROR: cannot find ambrosia-compositor binary." >&2 - echo " Build it in ${AMBROSIA_ROOT}/Compositor or pass -c ." >&2 - exit 1 -fi -log "compositor: ${COMPOSITOR_BIN}" - -# ── output directory ────────────────────────────────────────────────────────── - -if [[ -z "${OUTPUT_DIR}" ]]; then - OUTPUT_DIR="/tmp/wayland-smoke-$(date +%Y%m%d-%H%M%S)" -fi -mkdir -p "${OUTPUT_DIR}" -log "snapshots: ${OUTPUT_DIR}" - -COMPOSITOR_LOG="${OUTPUT_DIR}/compositor.log" - -# ── GNUstep environment ─────────────────────────────────────────────────────── - -if [[ -z "${GNUSTEP_MAKEFILES:-}" ]]; then - for gnustep_sh in \ - /usr/share/GNUstep/Makefiles/GNUstep.sh \ - /usr/local/share/GNUstep/Makefiles/GNUstep.sh \ - /usr/GNUstep/System/Library/Makefiles/GNUstep.sh; do - if [[ -f "${gnustep_sh}" ]]; then - # shellcheck source=/dev/null - source "${gnustep_sh}" - break - fi - done -fi - -# ── find the parent Wayland session ────────────────────────────────────────── -# wlr_backend_autocreate reads WAYLAND_DISPLAY to decide whether to create a -# nested-Wayland backend. We must point it at the real running session so -# Ambrosia can render into it. - -PARENT_DISPLAY="${WAYLAND_DISPLAY:-}" -if [[ -z "${PARENT_DISPLAY}" ]]; then - # Try well-known sockets under XDG_RUNTIME_DIR - XDG_RT="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" - for sock in "${XDG_RT}/wayland-0" "${XDG_RT}/wayland-1"; do - if [[ -S "${sock}" ]]; then - PARENT_DISPLAY="$(basename "${sock}")" - break - fi - done -fi - -if [[ -z "${PARENT_DISPLAY}" ]]; then - echo "ERROR: no running Wayland session found." >&2 - echo " Set WAYLAND_DISPLAY or start a Wayland compositor first." >&2 - exit 1 -fi -log "parent Wayland session: ${PARENT_DISPLAY}" - -# ── start Ambrosia nested in the parent session ─────────────────────────────── -# Pass WAYLAND_DISPLAY= so wlr_backend_autocreate picks the Wayland -# backend. The compositor calls wl_display_add_socket_auto() to create its -# *own* socket (wayland-N) and logs: -# "Ambrosia compositor running on WAYLAND_DISPLAY=wayland-N" -# We parse that line to discover the client socket. - -log "starting compositor (nested in ${PARENT_DISPLAY}) …" - -if [[ "${VERBOSE}" -eq 1 ]]; then - WAYLAND_DISPLAY="${PARENT_DISPLAY}" "${COMPOSITOR_BIN}" 2>&1 \ - | tee "${COMPOSITOR_LOG}" & - COMPOSITOR_PID=$! -else - WAYLAND_DISPLAY="${PARENT_DISPLAY}" "${COMPOSITOR_BIN}" \ - >"${COMPOSITOR_LOG}" 2>&1 & - COMPOSITOR_PID=$! -fi - -# ── wait for the compositor's own socket to be announced ───────────────────── - -log "waiting for compositor socket …" -WAIT_MAX=30 # 30 × 0.5 s = 15 s -for (( i=0; i/dev/null; then - log "compositor exited before announcing socket (see ${COMPOSITOR_LOG})" - tail -5 "${COMPOSITOR_LOG}" | sed 's/^/ /' >&2 - break - fi - - # Parse the socket name from the compositor log - CLIENT_WAYLAND_DISPLAY="$( - grep -m1 'running on WAYLAND_DISPLAY=' "${COMPOSITOR_LOG}" 2>/dev/null \ - | sed 's/.*WAYLAND_DISPLAY=\([^ ]*\).*/\1/' - )" - - if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]]; then - XDG_RT="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" - if [[ -S "${XDG_RT}/${CLIENT_WAYLAND_DISPLAY}" ]]; then - log "compositor ready on ${CLIENT_WAYLAND_DISPLAY} (pid ${COMPOSITOR_PID})" - break - fi - fi - sleep 0.5 -done - -if [[ -z "${CLIENT_WAYLAND_DISPLAY}" ]]; then - log "WARNING: could not determine compositor socket — runtime tests will be skipped" -else - # Export so subsequent client tools connect to the right compositor - export WAYLAND_DISPLAY="${CLIENT_WAYLAND_DISPLAY}" -fi - -# ── helper: run a single smoke probe ───────────────────────────────────────── - -run_probe() { - local name="$1"; shift - local logfile="$1"; shift - - if timeout "${TIMEOUT_SECS}" "$@" >"${logfile}" 2>&1; then - pass "${name}" - else - local status=$? - if [[ ${status} -eq 124 ]]; then - fail "${name} (timeout after ${TIMEOUT_SECS}s)" - else - fail "${name} (exit ${status})" - fi - fi -} - -check_log_contains() { - local name="$1" logfile="$2" pattern="$3" - if grep -qE "${pattern}" "${logfile}" 2>/dev/null; then - pass "${name}: found '${pattern}'" - else - fail "${name}: '${pattern}' not found in ${logfile}" - fi -} - -check_log_absent() { - local name="$1" logfile="$2" pattern="$3" - if grep -qE "${pattern}" "${logfile}" 2>/dev/null; then - fail "${name}: unexpected '${pattern}' found in ${logfile}" - else - pass "${name}: '${pattern}' absent (expected)" - fi -} - -# ── SMOKE TESTS ─────────────────────────────────────────────────────────────── - -echo "" -echo "═══════════════════════════════════════════════" -echo " Wayland backend smoke tests (M0–M5)" -echo " Compositor: ${COMPOSITOR_BIN}" -echo " Parent: ${PARENT_DISPLAY}" -echo " Client sock: ${CLIENT_WAYLAND_DISPLAY:-unknown}" -echo "═══════════════════════════════════════════════" -echo "" - -# ── T1: compositor process is running ──────────────────────────────────────── - -if kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then - pass "T1: compositor process running (pid ${COMPOSITOR_PID})" -else - fail "T1: compositor process not running" -fi - -# ── T2: compositor socket exists ───────────────────────────────────────────── - -if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]]; then - XDG_RT="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" - SOCK_PATH="${XDG_RT}/${CLIENT_WAYLAND_DISPLAY}" - if [[ -S "${SOCK_PATH}" ]]; then - pass "T2: compositor socket exists: ${SOCK_PATH}" - else - fail "T2: compositor socket missing: ${SOCK_PATH}" - fi -else - fail "T2: compositor socket unknown (startup failed)" -fi - -# ── T3: protocol globals via wayland-info ──────────────────────────────────── - -WL_INFO_LOG="${OUTPUT_DIR}/wayland-info.log" -if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]] && command -v wayland-info &>/dev/null; then - run_probe "T3: wayland-info connect" "${WL_INFO_LOG}" wayland-info - check_log_contains "T3: wl_compositor advertised" "${WL_INFO_LOG}" "wl_compositor" - check_log_contains "T3: xdg_wm_base advertised" "${WL_INFO_LOG}" "xdg_wm_base" - check_log_contains "T3: wl_seat advertised" "${WL_INFO_LOG}" "wl_seat" -else - log "T3: skipped (no compositor socket or wayland-info not installed)" -fi - -# ── T4: capture globals snapshot ───────────────────────────────────────────── - -if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]] && command -v wayland-info &>/dev/null; then - GLOBALS_SNAPSHOT="${OUTPUT_DIR}/globals-snapshot.txt" - wayland-info >"${GLOBALS_SNAPSHOT}" 2>&1 || true - log "T4: globals snapshot → ${GLOBALS_SNAPSHOT}" - pass "T4: globals snapshot captured" -else - log "T4: globals snapshot skipped" -fi - -# ── T5: backend library loads ──────────────────────────────────────────────── - -BACK_LIB="" -for lib_candidate in \ - "${REPO_ROOT}/obj/libgnustep-back.so" \ - /usr/lib/GNUstep/Libraries/libgnustep-back.so \ - /usr/local/lib/GNUstep/Libraries/libgnustep-back.so; do - if [[ -f "${lib_candidate}" ]]; then - BACK_LIB="${lib_candidate}" - break - fi -done - -BACK_LIB_LOG="${OUTPUT_DIR}/backend-lib.log" -if [[ -n "${BACK_LIB}" ]]; then - if python3 -c "import ctypes; ctypes.CDLL('${BACK_LIB}')" \ - >"${BACK_LIB_LOG}" 2>&1; then - pass "T5: backend library loads: ${BACK_LIB}" - else - fail "T5: backend library failed to load: ${BACK_LIB}" - fi -else - log "T5: gnustep-back library not found — skipping dlopen check" -fi - -# ── T6: compositor log contains startup markers ─────────────────────────────── - -check_log_contains "T6: compositor initialised" \ - "${COMPOSITOR_LOG}" "Ambrosia: initialising compositor" -check_log_contains "T6: backend created" \ - "${COMPOSITOR_LOG}" "Backend created|Creating wayland backend" -check_log_contains "T6: compositor announced socket" \ - "${COMPOSITOR_LOG}" "running on WAYLAND_DISPLAY=" - -# ── T7: DnD stub snapshot ──────────────────────────────────────────────────── - -DND_SOURCE="${REPO_ROOT}/Source/wayland/WaylandDragView.m" -DND_LOG="${OUTPUT_DIR}/dnd-snapshot.txt" -{ - echo "=== DnD debug-category static check ===" - grep -n "WaylandDnD" "${DND_SOURCE}" 2>/dev/null || true - echo "" - echo "=== DnD stubs present ===" - grep -n "not yet\|not implemented\|wl_data_device" "${DND_SOURCE}" 2>/dev/null || true -} >"${DND_LOG}" -check_log_contains "T7: WaylandDnD category in source" "${DND_LOG}" "WaylandDnD" -check_log_contains "T7: inter-process DnD stub present" "${DND_LOG}" "wl_data_device" - -# ── T8: IME stub snapshot ───────────────────────────────────────────────────── - -IME_SOURCE="${REPO_ROOT}/Source/wayland/WaylandInputServer.m" -IME_LOG="${OUTPUT_DIR}/ime-snapshot.txt" -{ - echo "=== IME debug-category static check ===" - grep -n "WaylandIME" "${IME_SOURCE}" 2>/dev/null || true - echo "" - echo "=== IME stubs (statusArea/preeditArea return NO) ===" - grep -n "statusArea\|preeditArea\|preeditSpot" "${IME_SOURCE}" 2>/dev/null || true -} >"${IME_LOG}" -check_log_contains "T8: WaylandIME category in source" "${IME_LOG}" "WaylandIME" -check_log_contains "T8: statusArea stub present" "${IME_LOG}" "statusArea" - -# ── T9: pointer/scroll stub snapshot ───────────────────────────────────────── - -CURSOR_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer+Cursor.m" -PTR_LOG="${OUTPUT_DIR}/pointer-snapshot.txt" -{ - echo "=== WaylandPointer debug-category static check ===" - grep -n "WaylandPointer" "${CURSOR_SOURCE}" 2>/dev/null || true - echo "" - echo "=== WaylandScroll debug-category static check ===" - grep -n "WaylandScroll" "${CURSOR_SOURCE}" 2>/dev/null || true - echo "" - echo "=== Extra button TODO milestone marker ===" - grep -n "Milestone 3\|BTN_SIDE\|BTN_EXTRA\|BTN_FORWARD\|BTN_BACK" "${CURSOR_SOURCE}" 2>/dev/null || true -} >"${PTR_LOG}" -check_log_contains "T9: WaylandPointer category in source" "${PTR_LOG}" "WaylandPointer" -check_log_contains "T9: WaylandScroll category in source" "${PTR_LOG}" "WaylandScroll" -check_log_contains "T9: extra-button BTN_SIDE mapped" "${PTR_LOG}" "BTN_SIDE" - -# ── T10: output stub snapshot ───────────────────────────────────────────────── - -OUTPUT_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer+Output.m" -OUT_LOG="${OUTPUT_DIR}/output-snapshot.txt" -{ - echo "=== WaylandOutput debug-category static check ===" - grep -n "WaylandOutput" "${OUTPUT_SOURCE}" 2>/dev/null || true - echo "" - echo "=== Output configure TODO ===" - grep -n "TODO\|configure_handler\|XXX" "${OUTPUT_SOURCE}" 2>/dev/null || true -} >"${OUT_LOG}" -check_log_contains "T10: WaylandOutput category in source" "${OUT_LOG}" "WaylandOutput" - -# ── T11: compositor log sanity ──────────────────────────────────────────────── - -check_log_absent "T11: no crash/abort in compositor log" \ - "${COMPOSITOR_LOG}" "Segmentation fault|Aborted|SIGSEGV|double free" - -# ══════════════════════════════════════════════════════════════════════════════ -# Milestone 5 — Stability and rendering hardening -# ══════════════════════════════════════════════════════════════════════════════ - -SHM_SOURCE="${REPO_ROOT}/Source/cairo/WaylandCairoShmSurface.m" -XDGSHELL_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer+Xdgshell.m" -SERVER_SOURCE="${REPO_ROOT}/Source/wayland/WaylandServer.m" - -# ── T12: FD leak fix — finishBuffer closes poolfd ──────────────────────────── -# The pool file-descriptor was never closed before M5, leaking one FD per -# window allocation. The fix is a close() call in finishBuffer. - -M5_BUF_LOG="${OUTPUT_DIR}/m5-buffer.txt" -{ - echo "=== finishBuffer: poolfd close ===" - grep -n "close(buf->poolfd)\|close.*poolfd" "${SHM_SOURCE}" 2>/dev/null || true - echo "" - echo "=== calloc / poolfd = -1 init ===" - grep -n "calloc\|poolfd = -1" "${SHM_SOURCE}" 2>/dev/null || true - echo "" - echo "=== needs_repaint field ===" - grep -n "needs_repaint" "${SHM_SOURCE}" 2>/dev/null || true - echo "" - echo "=== owner_surface / owner_display ===" - grep -n "owner_surface\|owner_display" "${SHM_SOURCE}" 2>/dev/null || true -} >"${M5_BUF_LOG}" - -check_log_contains "T12: finishBuffer closes poolfd" "${M5_BUF_LOG}" "close.*poolfd" -check_log_contains "T12: pool_buffer zero-initialised" "${M5_BUF_LOG}" "calloc" -check_log_contains "T12: needs_repaint field present" "${M5_BUF_LOG}" "needs_repaint" -check_log_contains "T12: owner back-pointers present" "${M5_BUF_LOG}" "owner_surface" - -# ── T13: Busy guard in handleExposeRect ────────────────────────────────────── -# Before M5, handleExposeRect attached the buffer unconditionally even while -# the compositor still held it (protocol error / artifact). - -M5_EXPOSE_LOG="${OUTPUT_DIR}/m5-expose.txt" -{ - echo "=== busy check in handleExposeRect ===" - grep -n "pbuffer->busy\|busy" "${SHM_SOURCE}" 2>/dev/null || true - echo "" - echo "=== repaint-on-release in buffer_handle_release ===" - # Look for the release callback re-committing after a missed frame - awk '/buffer_handle_release/,/^}/' "${SHM_SOURCE}" 2>/dev/null | \ - grep -n "needs_repaint\|wl_surface_attach\|wl_surface_commit" || true - echo "" - echo "=== size mismatch guard ===" - grep -n "pbuffer->width.*window->width\|size mismatch\|width != " "${SHM_SOURCE}" 2>/dev/null || true -} >"${M5_EXPOSE_LOG}" - -check_log_contains "T13: busy guard in handleExposeRect" "${M5_EXPOSE_LOG}" "pbuffer->busy" -check_log_contains "T13: repaint-on-release path" "${M5_EXPOSE_LOG}" "needs_repaint" -check_log_contains "T13: size-mismatch guard" "${M5_EXPOSE_LOG}" "pbuffer->width" - -# ── T14: Precise damage rect in handleExposeRect ───────────────────────────── -# Before M5, wl_surface_damage always used the full surface (0,0,w,h). -# Now the actual exposed NSRect is used. - -M5_DAMAGE_LOG="${OUTPUT_DIR}/m5-damage.txt" -{ - echo "=== precise damage in handleExposeRect ===" - grep -n "NSMinX\|NSMaxY\|NSWidth\|NSHeight\|dx\|dy\|dw\|dh" \ - "${SHM_SOURCE}" 2>/dev/null || true - echo "" - echo "=== initial commit has damage ===" - # initWithDevice must call wl_surface_damage before wl_surface_commit - awk '/initWithDevice/,/^- \(/ { print NR": "$0 }' "${SHM_SOURCE}" 2>/dev/null | \ - grep "wl_surface_damage\|wl_surface_commit" | head -10 || true -} >"${M5_DAMAGE_LOG}" - -check_log_contains "T14: precise damage rect uses NSMinX/NSMaxY" \ - "${M5_DAMAGE_LOG}" "NSMinX|NSMaxY|NSWidth|NSHeight" -check_log_contains "T14: initial commit preceded by damage" \ - "${M5_DAMAGE_LOG}" "wl_surface_damage" - -# ── T15: clearOwnerSurface prevents use-after-free ─────────────────────────── -# destroySurfaceRole: must clear the wl_surface back-pointer before calling -# wl_surface_destroy so the async buffer_handle_release cannot write to a -# freed proxy. - -M5_OWNER_LOG="${OUTPUT_DIR}/m5-owner.txt" -{ - echo "=== clearOwnerSurface in WaylandServer.m ===" - grep -n "clearOwnerSurface\|wl_surface_destroy" "${SERVER_SOURCE}" 2>/dev/null || true - echo "" - echo "=== clearOwnerSurface implementation ===" - awk '/clearOwnerSurface/,/^}/' "${SHM_SOURCE}" 2>/dev/null | head -20 || true - echo "" - echo "=== wl_surface_destroy in destroySurfaceRole ===" - awk '/destroySurfaceRole:/,/^- \(/ { print NR": "$0 }' \ - "${SERVER_SOURCE}" 2>/dev/null | \ - grep "wl_surface_destroy\|clearOwnerSurface" || true -} >"${M5_OWNER_LOG}" - -check_log_contains "T15: clearOwnerSurface called before wl_surface_destroy" \ - "${M5_OWNER_LOG}" "clearOwnerSurface" -check_log_contains "T15: wl_surface_destroy present in destroySurfaceRole" \ - "${M5_OWNER_LOG}" "wl_surface_destroy" - -# ── T16: No double wl_list_remove in xdg_surface_on_configure ──────────────── -# termwindow: removes the window from the list and sets terminated=YES. -# xdg_surface_on_configure must NOT call wl_list_remove a second time. - -M5_LIST_LOG="${OUTPUT_DIR}/m5-list-remove.txt" -{ - echo "=== terminated path in xdg_surface_on_configure ===" - awk '/xdg_surface_on_configure/,/^const struct/' "${XDGSHELL_SOURCE}" 2>/dev/null | \ - grep -n "terminated\|wl_list_remove\|free(window)" | head -20 || true -} >"${M5_LIST_LOG}" - -check_log_contains "T16: terminated path frees window" "${M5_LIST_LOG}" "free.window." -check_log_absent "T16: no second wl_list_remove" \ - "${M5_LIST_LOG}" "wl_list_remove\(" - -# ── T17: wl_shm_pool destroyed promptly (no dangling pool pointer) ──────────── -# The pool was stored in buf->pool and compared with NULL after being destroyed -# (potential double-destroy if finishBuffer was ever changed to also destroy it). -# M5 sets pool = NULL immediately after destroy and skips the buf->pool field. - -M5_POOL_LOG="${OUTPUT_DIR}/m5-pool.txt" -{ - echo "=== wl_shm_pool lifecycle in createShmBuffer ===" - awk '/createShmBuffer/,/^@implementation/' "${SHM_SOURCE}" 2>/dev/null | \ - grep -n "wl_shm_pool\|wl_shm_create_pool\|wl_shm_pool_destroy\|buf->pool" | head -20 || true -} >"${M5_POOL_LOG}" - -check_log_contains "T17: wl_shm_pool_destroy called" "${M5_POOL_LOG}" "wl_shm_pool_destroy" -check_log_absent "T17: buf->pool not stored after destroy" "${M5_POOL_LOG}" "buf->pool =" - -# ── T18: Compositor survives sustained global queries (event-loop stress) ───── -# Fire wayland-info 20 times in rapid succession against the running compositor. -# Each invocation opens a Wayland connection, reads the global list, and closes. -# A freeze or crash here indicates event-loop saturation or fd/memory leaks. - -if [[ -n "${CLIENT_WAYLAND_DISPLAY}" ]] && command -v wayland-info &>/dev/null; then - M5_STRESS_LOG="${OUTPUT_DIR}/m5-stress.log" - log "T18: sustained global query stress (20 iterations) …" - STRESS_OK=1 - for i in $(seq 1 20); do - if ! timeout 5 wayland-info >>"${M5_STRESS_LOG}" 2>&1; then - STRESS_OK=0 - fail "T18: wayland-info iteration ${i} failed" - break - fi - done - if [[ "${STRESS_OK}" -eq 1 ]]; then - pass "T18: compositor survived 20 consecutive global queries" - fi -else - log "T18: stress test skipped (no compositor socket or wayland-info not installed)" -fi - -# ── T19: Compositor still alive and clean after stress ─────────────────────── - -sleep 1 -if kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then - pass "T19: compositor still running after stress" -else - fail "T19: compositor died during stress test" -fi - -check_log_absent "T19: no crash/abort after stress" \ - "${COMPOSITOR_LOG}" "Segmentation fault|Aborted|SIGSEGV|double free|wl_display_disconnect" - -# ── summary ─────────────────────────────────────────────────────────────────── - -echo "" -echo "═══════════════════════════════════════════════" -echo " Results: ${PASS} passed, ${FAIL} failed" -echo " Snapshots in: ${OUTPUT_DIR}" -echo "═══════════════════════════════════════════════" -echo "" - -if [[ "${FAIL}" -gt 0 ]]; then - exit 1 -fi -exit 0