unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* emacs for pure Gtk3
@ 2020-04-26  7:56 Yuuki Harano
  2020-04-26  8:52 ` 조성빈
                   ` (7 more replies)
  0 siblings, 8 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-26  7:56 UTC (permalink / raw)
  To: emacs-devel

Hi,

You may know, I ported emacs for pure Gtk3, especially for wayland native.

https://github.com/masm11/emacs

I created a new window-system, pgtk, which doesn't use libX11 directly.

What do you think? I want to merge to mainline.
-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
@ 2020-04-26  8:52 ` 조성빈
  2020-04-26  9:35   ` Yuuki Harano
  2020-04-26  9:52   ` Yuuki Harano
  2020-04-26 14:01 ` Eli Zaretskii
                   ` (6 subsequent siblings)
  7 siblings, 2 replies; 90+ messages in thread
From: 조성빈 @ 2020-04-26  8:52 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: Emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1240 bytes --]

From https://github.com/masm11/emacs/pull/12:

> Yes, I want to merge this fork to mainline, but I have one more work to do before that.
> However, it is not essential for English users, so I'll publish to emacs-devel soon.

If you’re thinking about this,

> However, when you type e.g. C-x o, C-x goes through input methods and is handled by Emacs, and o is handled by input methods, so お appears as a preedit text. I have no idea. You can turn off input method before typing C-x o. I do, so no problem.

The Emacs port on macOS does the same thing (albeit in Korean) — I don’t think that it’ll be a problem.
How does the original port work? Does Emacs catch all of the keys and process it itself? I’ve always thought the keys go through the input system before Emacs can handle it — how did Emacs go around it before?

나의 iPhone에서 보냄
2020. 4. 26. 오후 4:57, Yuuki Harano <masm+emacs@masm11.me> 작성:

> Hi,
> 
> You may know, I ported emacs for pure Gtk3, especially for wayland native.
> 
> https://github.com/masm11/emacs
> 
> I created a new window-system, pgtk, which doesn't use libX11 directly.
> 
> What do you think? I want to merge to mainline.
> -- 
> Yuuki Harano
> 

[-- Attachment #2: Type: text/html, Size: 1915 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  8:52 ` 조성빈
@ 2020-04-26  9:35   ` Yuuki Harano
  2020-04-26  9:52   ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-26  9:35 UTC (permalink / raw)
  To: pcr910303; +Cc: Emacs-devel


On Sun, 26 Apr 2020 17:52:25 +0900,
	조성빈 <pcr910303@icloud.com> wrote:
> If you’re thinking about this,

Yes.

> How does the original port work? Does Emacs catch all of the keys and process it itself?

Yes, it does. It catches all the key events, and passes them to emacs lisp.
Emacs lisp, like mozc.el, processes them, and the IM status is shown on the
mode line.

On pgtk emacs, they can be handled by emacs lisp in the same way if
GtkIMContext is disabled. But that is not what it should be.
I think the keyboard events should be handled by Gtk library, but if so,
the IM status isn't shown. I want for it to be shown at least.

On mac emacs, it knows the IM status somehow and shows it on the mode line,
so users less confused.

-- 
Yuuki Harano

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  8:52 ` 조성빈
  2020-04-26  9:35   ` Yuuki Harano
@ 2020-04-26  9:52   ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-26  9:52 UTC (permalink / raw)
  To: pcr910303; +Cc: Emacs-devel

Thank you.

I'm sorry. I mistook the From: field, so resend this email.

On Sun, 26 Apr 2020 17:52:25 +0900,
	조성빈 <pcr910303@icloud.com> wrote:
> If you’re thinking about this,

Yes.

> How does the original port work? Does Emacs catch all of the keys and process it itself?

Yes, it does. It catches all the key events, and passes them to emacs lisp.
Emacs lisp, like mozc.el, processes them, and the IM status is shown on the
mode line.

On pgtk emacs, they can be handled by emacs lisp in the same way if
GtkIMContext is disabled. But that is not what it should be.
I think the keyboard events should be handled by Gtk library, but if so,
the IM status isn't shown. I want for it to be shown at least.

On mac emacs, it knows the IM status somehow and shows it on the mode line,
so users less confused.

-- 
Yuuki Harano

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
  2020-04-26  8:52 ` 조성빈
@ 2020-04-26 14:01 ` Eli Zaretskii
  2020-04-27 12:37   ` Yuuki Harano
  2020-11-17 14:50   ` Yuuki Harano
  2020-04-26 18:00 ` martin rudalics
                   ` (5 subsequent siblings)
  7 siblings, 2 replies; 90+ messages in thread
From: Eli Zaretskii @ 2020-04-26 14:01 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

> Date: Sun, 26 Apr 2020 16:56:04 +0900 (JST)
> From: Yuuki Harano <masm+emacs@masm11.me>
> 
> You may know, I ported emacs for pure Gtk3, especially for wayland native.
> 
> https://github.com/masm11/emacs
> 
> I created a new window-system, pgtk, which doesn't use libX11 directly.
> 
> What do you think? I want to merge to mainline.

Thank you for your interest in Emacs, and in particular for working on
this.

I think this should be pushed to a feature branch first, and we should
then let people use it and report any problems, with the purpose of
making it stable enough before we merge to master.

But before we create such a feature branch, there are a few
prerequisites:

  . You don't seem to have a copyright assignment on file.  This would
    be a significant contribution to Emacs, for which we must have
    such an assignment from you before bringing this code into the
    Emacs repository.  Would you be willing to start the legal
    paperwork now?  If so, I will send you the form to fill.

  . The code seem to be based on an relatively old version of our
    master branch, which makes it hard to review (there are many
    spurious changes unrelated to your work).  Please rebase on the
    latest HEAD of the master branch.

  . Would it be possible for you to describe the design of this
    feature, and how that affects the various Emacs features, so that
    understanding the changes would be facilitated?  In particular,
    can this new window-system live together with X and TTY frames in
    the same session? does it support Lisp threads? etc.  Also, what
    are the requirements from the platforms where this could be built
    and used?

Thanks again for your work on Emacs.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
  2020-04-26  8:52 ` 조성빈
  2020-04-26 14:01 ` Eli Zaretskii
@ 2020-04-26 18:00 ` martin rudalics
  2020-04-26 18:43   ` Stefan Monnier
  2020-04-27 15:43   ` Yuuki Harano
  2020-04-27  2:33 ` 황병희
                   ` (4 subsequent siblings)
  7 siblings, 2 replies; 90+ messages in thread
From: martin rudalics @ 2020-04-26 18:00 UTC (permalink / raw)
  To: Yuuki Harano, emacs-devel

 > You may know, I ported emacs for pure Gtk3, especially for wayland native.
 >
 > https://github.com/masm11/emacs
 >
 > I created a new window-system, pgtk, which doesn't use libX11 directly.
 >
 > What do you think? I want to merge to mainline.

Thank you!

Building on Debian with GTK+ Version 3.24.5, cairo version 1.16.0, and
gcc (Debian 8.3.0-6) 8.3.0 configured as

CFLAGS='-O0 -g3 -no-pie' ../configure --without-x --with-cairo
--with-modules --with-gif=ifavailable --with-tiff=ifavailable
--with-gnutls=no --without-pop --enable-gcc-warnings=warn-only
--enable-checking=yes --enable-check-lisp-object-type=yes

fails here thusly:

   CC       pgtkterm.o
../../src/pgtkterm.c: In function ‘mark_pgtkterm’:
../../src/pgtkterm.c:168:25: error: incompatible type for argument 1 of ‘mark_object’
      mark_object (dpyinfo->rdb);
                   ~~~~~~~^~~~~
In file included from ../../src/pgtkterm.c:39:
../../src/lisp.h:3785:26: note: expected ‘Lisp_Object’ {aka ‘struct Lisp_Object’} but argument is of type ‘XrmDatabase’ {aka ‘void *’}
  extern void mark_object (Lisp_Object);
                           ^~~~~~~~~~~
make[1]: *** [Makefile:405: pgtkterm.o] Fehler 1
make[1]: *** Es wird auf noch nicht beendete Prozesse gewartet....
make[1]: Verzeichnis „/home/martin/emacs-git/masm/obj-pure-gtk/src“ wird verlassen
make: *** [Makefile:431: src] Fehler 2

Any ideas?

martin




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26 18:00 ` martin rudalics
@ 2020-04-26 18:43   ` Stefan Monnier
  2020-04-27 15:43   ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: Stefan Monnier @ 2020-04-26 18:43 UTC (permalink / raw)
  To: martin rudalics; +Cc: Yuuki Harano, emacs-devel

martin rudalics [2020-04-26 20:00:23] wrote:
> CFLAGS='-O0 -g3 -no-pie' ../configure --without-x --with-cairo
> --with-modules --with-gif=ifavailable --with-tiff=ifavailable
> --with-gnutls=no --without-pop --enable-gcc-warnings=warn-only
> --enable-checking=yes --enable-check-lisp-object-type=yes

During development, `--enable-checking=yes --enable-check-lisp-object-type=yes`
are really your friends, indeed.  I strongly recommend people enable
those flags as often as possible.


        Stefan




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
                   ` (2 preceding siblings ...)
  2020-04-26 18:00 ` martin rudalics
@ 2020-04-27  2:33 ` 황병희
  2020-04-27  8:37 ` Po Lu via Emacs development discussions.
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 90+ messages in thread
From: 황병희 @ 2020-04-27  2:33 UTC (permalink / raw)
  To: emacs-devel

Hellow Yuuki^^^

Yuuki Harano <masm+emacs@masm11.me> writes:

> You may know, I ported emacs for pure Gtk3, especially for wayland native.
> https://github.com/masm11/emacs

THANK YOU VERY SO MUCH!!!

Sincerely, Wayland fan Byung-Hee

-- 
^고맙습니다 _地平天成_ 감사합니다_^))//




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
                   ` (3 preceding siblings ...)
  2020-04-27  2:33 ` 황병희
@ 2020-04-27  8:37 ` Po Lu via Emacs development discussions.
  2020-04-27 16:08   ` Yuuki Harano
  2020-04-29  6:28   ` Po Lu
  2020-04-28  0:51 ` Daniele Nicolodi
                   ` (2 subsequent siblings)
  7 siblings, 2 replies; 90+ messages in thread
From: Po Lu via Emacs development discussions. @ 2020-04-27  8:37 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 731 bytes --]

Yuuki Harano <masm+emacs@masm11.me> writes:

> Hi,
>
> You may know, I ported emacs for pure Gtk3, especially for wayland native.
>
> https://github.com/masm11/emacs
>
> I created a new window-system, pgtk, which doesn't use libX11 directly.
>
> What do you think? I want to merge to mainline.

I was maintaining some separate work a while ago, but I moved some of my
work on top of yours some time ago.  It would be nice if we could work
together.  A few noticable differences:
- Support for GTK 2, 3, and the latest GTK 4 snapshots
- Some visual features similar to other GTK applications
- The GTK foreign rendering that I was mentioning earlier

It currently includes some fairly ugly code, but I'm working on cleaning
it up.


[-- Attachment #2: GTK 2, 3 and 4 patch --]
[-- Type: text/x-patch, Size: 1137331 bytes --]

diff --git a/.gitignore b/.gitignore
index d4be6bb23e..4cc3f2ac0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,6 +132,7 @@ src/gl-stamp
 *.dll
 *.core
 *.elc
+*.eln
 *.o
 *.res
 *.so
@@ -290,3 +291,6 @@ nt/emacs.rc
 nt/emacsclient.rc
 src/gdb.ini
 /var/
+
+# gsettings schema
+/etc/*.gschema.valid
diff --git a/Makefile.in b/Makefile.in
index 67e15cfecd..3c5a2fb97c 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -216,6 +216,9 @@ icondir=
 # The source directory for the icon files.
 iconsrcdir=$(srcdir)/etc/images/icons
 
+# Where to install the gsettings schema file.
+gsettingsschemadir = @gsettingsschemadir@
+
 # ==================== Emacs-specific directories ====================
 
 # These variables hold the values Emacs will actually use.  They are
@@ -300,6 +303,8 @@ LN_S_FILEONLY =
 # We use gzip to compress installed .el and some .txt files.
 GZIP_PROG = @GZIP_PROG@
 
+GLIB_COMPILE_SCHEMAS = glib-compile-schemas
+
 # ============================= Targets ==============================
 
 # Program name transformation.
@@ -330,7 +335,9 @@ CONFIG_STATUS_FILES_IN =
 COPYDIR = ${srcdir}/etc ${srcdir}/lisp
 COPYDESTS = "$(DESTDIR)${etcdir}" "$(DESTDIR)${lispdir}"
 
-all: ${SUBDIR} info
+gsettings_SCHEMAS = etc/org.gnu.emacs.defaults.gschema.xml
+
+all: ${SUBDIR} info $(gsettings_SCHEMAS:.xml=.valid)
 
 .PHONY: all ${SUBDIR} blessmail epaths-force epaths-force-w32 etc-emacsver
 
@@ -468,7 +475,7 @@ .PHONY:
 ## don't have to duplicate the list of utilities to install in
 ## this Makefile as well.
 
-install: all install-arch-indep install-etcdoc install-arch-dep install-$(NTDIR) blessmail
+install: all install-arch-indep install-etcdoc install-arch-dep install-$(NTDIR) blessmail install-gsettings-schemas
 	@true
 
 ## Ensure that $subdir contains a subdirs.el file.
@@ -753,7 +760,7 @@ install-strip:
 ### create (but not the noninstalled files such as 'make all' would create).
 ###
 ### Don't delete the lisp and etc directories if they're in the source tree.
-uninstall: uninstall-$(NTDIR) uninstall-doc
+uninstall: uninstall-$(NTDIR) uninstall-doc uninstall-gsettings-schemas
 	rm -f "$(DESTDIR)$(includedir)/emacs-module.h"
 	$(MAKE) -C lib-src uninstall
 	-unset CDPATH; \
@@ -850,7 +857,7 @@ clean_dirs =
 
 $(foreach dir,$(clean_dirs),$(eval $(call submake_template,$(dir),clean)))
 
-clean: $(clean_dirs:=_clean)
+clean: $(clean_dirs:=_clean) clean-gsettings-schemas
 	$(MAKE) -C admin/charsets $@
 	[ ! -d test ] || $(MAKE) -C test $@
 	-rm -f ./*.tmp etc/*.tmp*
@@ -1187,3 +1194,10 @@ gitmerge:
 	${GITMERGE_EMACS} -batch --no-site-file --no-site-lisp \
 	  -l ${srcdir}/admin/gitmerge.el \
 	  --eval '(setq gitmerge-minimum-missing ${GITMERGE_NMIN})' -f gitmerge
+
+@GSETTINGS_RULES@
+
+install-gsettings-schemas:
+uninstall-gsettings-schemas:
+clean-gsettings-schemas:
+$(gsettings_SCHEMAS:.xml=.valid):
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 0000000000..c3a0295225
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,311 @@
+#######################################################################
+# CAVEAT LECTOR: This PKGBUILD is highly opinionated. I give you
+#                enough rope to hang yourself, but by default it
+#                only enables the features I use.
+#
+#        TLDR: yaourt users, cry me a river.
+#
+#        Everyone else: do not update blindly this PKGBUILD. At least
+#        make sure to compare and understand the changes.
+#
+#######################################################################
+
+#######################################################################
+# Track a maintenance branch or, by default, track master.
+#
+# Pick a branch from the output of "git branch" ran on your local copy
+# of the emacs repository.
+#
+# E.g.:
+#
+# BRANCH=master
+# BRANCH=emacs-26
+#
+# Take note that I don't expect you to track anything different to master
+# or emacs-26 for obvious reasons. (See below).
+#
+BRANCH=pgtk
+
+#######################################################################
+# Assign "YES" to the variable you want enabled; empty or other value
+# for NO.
+#
+# Where you read experimental, replace with foobar.
+# =================================================
+#
+#######################################################################
+CHECK=            # Run tests.
+CLANG=            # Use clang.
+LTO=              # Enable link-time optimization. Experimental.
+ATHENA=           # Use Athena widgets. (83 1337, 83 001d sk00l).
+GTK2=             # Leave empty to compile with GTK+ 3 support.
+                  # No, GTK+ 2 ain't kool, dawg!
+GPM="YES"         # Enable gpm support.
+M17N="YES"        # Enable m17n international table input support.
+                  # You are far better off using UTF-8 and an input
+                  # method under X/Wayland. But this gives independence
+                  # if you need it.
+OTF=              # OTF font support. Also a secondary dependency
+                  # by pulling m17n-lib. Not needed in that case.
+CAIRO="YES"       # Highly experimental. Maintaner dissapeared.
+XWIDGETS="YES"    # Use GTK+ widgets pulled from webkit2gtk. Usable.
+DOCS_HTML=        # Generate and install html documentation.
+DOCS_PDF=         # Generate and install pdf documentation.
+MAGICK="YES"      # Imagemagick, like flash, is bug ridden and won't die.
+                  # Yet useful... Broken with the transition to IM7.
+NOGZ=             # Don't compress el files.
+#######################################################################
+
+#######################################################################
+if [[ BRANCH = "emacs-26" ]]; then
+  pkgname=emacs26-git
+else
+  pkgname=emacs-pgtk
+fi
+pkgver=27.0.50.131872
+pkgrel=1
+pkgdesc="GNU Emacs. PGTK Development."
+arch=('x86_64') # Arch Linux only. Users of derivatives are on their own.
+url="https://github.com/masm11/emacs/"
+license=('GPL3')
+depends=( 'alsa-lib' )
+makedepends=( 'git' )
+#######################################################################
+
+#######################################################################
+if [[ $CLANG = "YES" ]]; then
+  export CC=/usr/bin/clang ;
+  export CXX=/usr/bin/clang++ ;
+  export CPP="/usr/bin/clang -E" ;
+  export LDFLAGS+=' -fuse-ld=lld' ;
+  makedepends+=( 'clang' 'lld') ;
+fi
+
+if [[ $LTO = "yes" ]]; then
+  export CFLAGS+=" -flto"
+  export tXXFLAGS+=" -flto"
+fi
+
+if [[ $ATHENA = "YES" ]]; then
+  depends+=( 'libxaw' );
+elif [[ $GTK2 = "YES" ]]; then
+  depends+=( 'gtk2' );
+else
+  depends+=( 'gtk3' );
+fi
+
+if [[ $GPM = "YES" ]]; then
+  depends+=( 'gpm');
+fi
+
+if [[ $M17N = "YES" ]]; then
+  depends+=( 'm17n-lib' );
+fi
+
+if [[ $OTF = "YES" ]] && [[ ! $M17N = "YES" ]]; then
+  depends+=( 'libotf' );
+fi
+
+if [[ $MAGICK = "YES" ]]; then
+  depends+=( 'imagemagick' );
+  depends+=( 'libjpeg-turbo' 'giflib' );
+elif [[ ! $NOX = "YES" ]]; then
+  depends+=( 'libjpeg-turbo' 'giflib' );
+else
+  depends+=();
+fi
+
+if [[ $CAIRO = "YES" ]]; then
+  depends+=( 'cairo' );
+fi
+
+if [[ $XWIDGETS = "YES" ]]; then
+  if [[ $GTK2 = "YES" ]] || [[ $ATHENA = "YES" ]]; then
+    echo "";
+    echo "";
+    echo "Xwidgets support *requires* gtk+3!!!";
+    echo "";
+    echo "";
+    exit 1;
+  else
+    depends+=( 'webkit2gtk' );
+  fi
+fi
+
+if [[ $DOCS_PDF = "YES" ]]; then
+  makedepends+=( 'texlive-core' );
+fi
+#######################################################################
+
+#######################################################################
+conflicts=('emacs')
+provides=('emacs')
+source=("emacs-pgtk::git+https://github.com/masm11/emacs#branch=$BRANCH")
+md5sums=('SKIP')
+
+pkgver() {
+  cd "$srcdir/emacs-pgtk"
+
+  printf "%s.%s" \
+    "$(grep AC_INIT configure.ac | \
+    sed -e 's/^.\+\ \([0-9]\+\.[0-9]\+\.[0-9]\+\?\).\+$/\1/')" \
+    "$(git rev-list --count HEAD)"
+}
+
+
+# There is no need to run autogen.sh after first checkout.
+# Doing so, breaks incremental compilation.
+prepare() {
+  cd "$srcdir/emacs-pgtk"
+  [[ -x configure ]] || ( ./autogen.sh git && ./autogen.sh autoconf )
+}
+
+build() {
+  cd "$srcdir/emacs-pgtk"
+
+  local _conf=(
+    --prefix=/usr
+    --sysconfdir=/etc
+    --libexecdir=/usr/lib
+    --localstatedir=/var
+    --mandir=/usr/share/man
+    --without-x
+    --with-gameuser=:games
+    --with-sound=alsa
+    --with-xft
+    --with-modules
+  )
+
+#######################################################################
+
+#######################################################################
+if [[ $CLANG = "YES" ]]; then
+  _conf+=(
+    '--enable-autodepend'
+ );
+fi
+if [[ $LTO = "YES" ]]; then
+  _conf+=(
+    '--enable-link-time-optimization'
+  );
+fi
+
+# Beware https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25228
+# dconf and gconf break font settings you set in ~/.emacs.
+# If you insist you'll need to play gymnastics with
+# set-frame-font and set-menu-font. Good luck!
+if [[ $ATHENA = "YES" ]]; then
+  _conf+=( '--with-x-toolkit=athena' '--without-gconf' '--without-gsettings' );
+elif [[ $GTK2 = "YES" ]]; then
+  _conf+=( '--with-x-toolkit=gtk2' '--without-gconf' '--without-gsettings' );
+else
+  # _conf+=( '--with-x-toolkit=gtk3' '--without-gconf' '--without-gsettings' );
+  _conf+=( '--with-x-toolkit=gtk3' );
+fi
+
+if [[ ! $GPM = "YES" ]]; then
+  _conf+=( '--without-gpm' );
+fi
+
+if [[ ! $M17N = "YES" ]]; then
+  _conf+=( '--without-m17n-flt' );
+fi
+
+if [[ $MAGICK = "YES" ]]; then
+  _conf+=(
+    '--with-imagemagick'
+ );
+else
+  _conf+=( '--without-imagemagick' );
+fi
+
+if [[ $CAIRO = "YES" ]]; then
+  _conf+=( '--with-cairo' );
+fi
+
+if [[ $XWIDGETS = "YES" ]]; then
+  _conf+=( '--with-xwidgets' );
+fi
+
+if [[ $NOGZ = "YES" ]]; then
+  _conf+=( '--without-compress-install' );
+fi
+#######################################################################
+
+#######################################################################
+
+  # Use gold with gcc, unconditionally.
+  #
+  if [[ ! $CLANG = "YES" ]]; then
+    export LD=/usr/bin/ld.gold
+    export LDFLAGS+=" -fuse-ld=gold";
+  fi
+
+  ./configure "${_conf[@]}"
+
+  # Using "make" instead of "make bootstrap" enables incremental
+  # compiling. Less time recompiling. Yay! But you may
+  # need to use bootstrap sometimes to unbreak the build.
+  # Just add it to the command line.
+  #
+  # Please note that incremental compilation implies that you
+  # are reusing your src directory!
+  #
+  make
+
+  # You may need to run this if 'loaddefs.el' files corrupt.
+  #cd "$srcdir/emacs-pgtk/lisp"
+  #make autoloads
+  #cd ../
+
+  # Optional documentation formats.
+  if [[ $DOCS_HTML = "YES" ]]; then
+    make html;
+  fi
+  if [[ $DOCS_PDF = "YES" ]]; then
+    make pdf;
+  fi
+  if [[ $CHECK = "YES" ]]; then
+    make check;
+  fi
+
+}
+
+package() {
+  cd "$srcdir/emacs-pgtk"
+
+  make DESTDIR="$pkgdir/" install
+
+  # Install optional documentation formats
+  if [[ $DOCS_HTML = "YES" ]]; then make DESTDIR="$pkgdir/" install-html; fi
+  if [[ $DOCS_PDF = "YES" ]]; then make DESTDIR="$pkgdir/" install-pdf; fi
+
+  # remove conflict with ctags package
+  mv "$pkgdir"/usr/bin/{ctags,ctags.emacs}
+
+  if [[ $NOGZ = "YES" ]]; then
+    mv "$pkgdir"/usr/share/man/man1/{ctags.1,ctags.emacs.1};
+  else
+    mv "$pkgdir"/usr/share/man/man1/{ctags.1.gz,ctags.emacs.1.gz}
+  fi
+
+  # fix user/root permissions on usr/share files
+  find "$pkgdir"/usr/share/emacs/ | xargs chown root:root
+
+  # fix permssions on /var/games
+  mkdir -p "$pkgdir"/var/games/emacs
+  chmod 775 "$pkgdir"/var/games
+  chmod 775 "$pkgdir"/var/games/emacs
+  chown -R root:games "$pkgdir"/var/games
+
+  # The logic used to install systemd's user service is partially broken
+  # under Arch Linux model, because it adds $DESTDIR as prefix to the
+  # final Exec targets. The fix is to hack it with an axe.
+  install -Dm644 etc/emacs.service \
+    "$pkgdir"/usr/lib/systemd/user/emacs.service
+  sed -i -e 's#\(ExecStart\=\)#\1\/usr\/bin\/#' \
+    -e 's#\(ExecStop\=\)#\1\/usr\/bin\/#' \
+    "$pkgdir"/usr/lib/systemd/user/emacs.service
+}
+
+# vim:set ft=sh ts=2 sw=2 et:
diff --git a/README-GTK.org b/README-GTK.org
new file mode 100644
index 0000000000..4ad8669b2e
--- /dev/null
+++ b/README-GTK.org
@@ -0,0 +1,30 @@
+* Emacs on pure GTK
+  This directory tree holds a modified copy of version 28.0.50
+  of GNU Emacs, the extensible, customizable and
+  self-documenting real-time display editor, modified to run
+  on the GTK toolkit without platform-specific support.
+
+  This is a modified copy of https://github.com/masm11/emacs,
+  with the following changes:
+    - Support was added for both GTK+ 2 and GTK 4 ::
+      Emacs now builds on GTK 2, 3 and 4.
+    - Child frames have been implemented ::
+      Packages that make use of child frames play an
+      important part in my work-flow, so I implemented them.
+    - Broadway is now supported on GTK 3 and GTK 4 ::
+    - Emacs now uses a GtkHeaderBar ::
+      To better comply with GNOME design guidelines,
+      tool-bar items and menu items can be optionally
+      displayed in the header bar.
+    - Input methods will no longer block key-presses from being read ::
+      This makes permanently enabled input methods usable.
+    - ~~read-color~~ now uses a GTK colour picker if applicable ::
+      This makes for better integration with GTK.
+    - GTK message dialogues are used for ~~x-popup-dialog~~ ::
+      This improves the appearance quite a bit.
+    - GTK widgets can be embedded in frames ::
+      This is much more powerful in theory than xwidgets, but in
+      it's current state requires some work.
+    - Improved touchscreen support ::
+      Touchscreen support has been vastly improved, with support
+      for scrolling, long-click selections, and so on.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..e4b9f42c3e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,152 @@
+# Emacs supporting pure-gtk3
+
+In this fork, I'm working to make Emacs support pure-gtk3, in order to support Wayland.
+
+## Building
+
+You need cairo.
+
+```sh
+git clone https://github.com/masm11/emacs.git
+cd emacs
+./autogen.sh
+./configure --without-x --with-cairo --with-modules
+make
+```
+
+Ignore warnings.
+
+#### For archlinux users
+
+This repository contains PKGBUILD, so you can install by:
+
+```
+mkdir /tmp/emacs
+cd /tmp/emacs
+wget https://raw.githubusercontent.com/masm11/emacs/pgtk/PKGBUILD
+makepkg -s
+sudo pacman -U emacs-pgtk*.tar.xz
+```
+
+## Running
+
+```sh
+cd src
+GDK_BACKEND=wayland ./emacs  (or try ./emacs -Q if you have problems.)
+```
+
+## X11 and Wayland
+
+Of course, PGTK supports X11 and Wayland connections.
+
+You can use `GDK_BACKEND` environment variable and `--display` option,
+and you can do `(make-frame-on-display display-name)` with display-name of
+different backend from the first frame.
+
+You can know which backend is used for a frame:
+
+```elisp
+(pgtk-backend-display-class)
+```
+
+This returns `"GdkWaylandDisplay"` for Wayland, or `"GdkX11Display"` for X11.
+
+Note: Segmentation fault may occur on multiple display environment.
+
+## Instead of xrdb
+
+X has the resource database, and you could store initial default values into it.
+
+Gtk/Gdk can't handle it even if on X11, so I implemented similar feature using gsettings.
+
+Saving:
+```elisp
+(pgtk-set-resource "background" "gray")
+```
+
+Getting:
+```elisp
+(x-get-resource "background" "Background")
+```
+
+If your emacs got failing to start, then edit your settings with `dconf-editor`.
+Your settings are saved under `/org/gnu/emacs/defaults-by-name/<instance-name>/` and
+`/org/gnu/emacs/defaults-by-class/`. All are of string type.
+Correct your mistakes.
+
+## TODO
+
+Known problems:
+- Segmentation fault while multiple-display.
+- Exits when a connection to display server is closed by peer. (I may not be able to resolve.)
+
+Not implemented:
+- GTK on wayland does not implement functions for these features:
+  - x_set_offset
+  - x_set_no_focus_on_map
+  - x_set_no_accept_focus
+  - x_set_z_group
+  - auto-raise/lower
+- GTK does not implement functions for these features:
+  - vendor_specific_keysyms
+- Some other features. Keywords:
+  - popup_activated
+  - gtk_plug (not exists on wayland)
+  - x_set_parent_frame
+  - frame_x_embedded_p
+
+I may not develop them because I don't use them.
+
+## Debugging
+
+Edit src/pgtkterm.h to uncomment:
+
+```c
+#define PGTK_DEBUG 1
+```
+
+It enables so much debugging outputs.
+
+On gdb, you may want to do:
+
+```
+(gdb) handle SIGPIPE nostop noprint
+```
+
+## Input Methods
+
+```elisp
+(when (eq window-system 'pgtk)
+  (pgtk-use-im-context t))
+```
+
+This enables Gtk's `GtkIMContext`.
+
+However, when you type e.g. `C-x o`,
+`C-x` goes through input methods and is handled by Emacs,
+and `o` is handled by input methods, so `お` appears as a preedit text.
+I have no idea. You can turn off input method before typing `C-x o`.
+I do, so no problem.
+
+## My Environment
+
+- archlinux
+- gtk+ 3.24.13
+- glib2 2.62.4
+- gcc 9.2.0
+- wayland 1.17.0
+- wayland-protocols 1.18
+- cairo 1.17.2
+- freetype2 2.10.1
+- imagemagick 7.0.9.16
+- ibus 1.5.21
+- mozc 2.23.2815.102
+- wayfire 57a585d
+
+## Notice
+
+- Commit messages are in Japanese.
+
+## About me
+
+masm11.
diff --git a/configure.ac b/configure.ac
index 719eb747ae..bee761f14d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -414,6 +414,22 @@ AC_DEFUN
 	  with_x_toolkit=$val
 ])
 
+AC_ARG_WITH([pgtk-toolkit],[AS_HELP_STRING([--with-pgtk-toolkit=KIT],
+ [use toolkit (KIT one of: gtk2, gtk3)])],
+[	  case "${withval}" in
+	    y | ye | yes )	val=gtk3 ;;
+	    n | no )		val=no  ;;
+	    gtk2  )	val=gtk2 ;;
+	    gtk3  )	val=gtk3 ;;
+	    gtk4  )     val=gtk4 ;;
+	    * )
+AC_MSG_ERROR(['--with-pgtk-toolkit=$withval' is invalid;
+this option's value should be 'yes', 'no', 'gtk2', 'gtk3' or 'gtk4'.])
+	    ;;
+	  esac
+	  with_pgtk_toolkit=$val
+])
+
 OPTION_DEFAULT_OFF([wide-int], [prefer wide Emacs integers (typically 62-bit); allows buffer and string size up to 2GB on 32-bit hosts, at the cost of 10% to 30% slowdown of Lisp interpreter and larger memory footprint])
 if test "$with_wide_int" = yes; then
   AC_DEFINE([WIDE_EMACS_INT], 1, [Use long long for EMACS_INT if available.])
@@ -464,6 +480,7 @@ AC_DEFUN
 OPTION_DEFAULT_ON([zlib],[don't compile with zlib decompression support])
 OPTION_DEFAULT_ON([modules],[don't compile with dynamic modules support])
 OPTION_DEFAULT_ON([threads],[don't compile with elisp threading support])
+OPTION_DEFAULT_OFF([nativecomp],[don't compile with emacs lisp native compiler support])
 
 AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
  [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])],
@@ -1857,6 +1874,8 @@ AC_DEFUN
 AC_PATH_X
 if test "$no_x" != yes; then
   window_system=x11
+else
+  window_system=pgtk
 fi
 
 LD_SWITCH_X_SITE_RPATH=
@@ -2237,6 +2256,9 @@ AC_DEFUN
       gtk3 ) with_gtk3=yes
              term_header=gtkutil.h
              USE_X_TOOLKIT=none ;;
+      gtk4 ) with_gtk4=yes
+             term_header=pgtksubr.h
+	     USE_X_TOOLKIT=none ;;
       no ) USE_X_TOOLKIT=none ;;
 dnl If user did not say whether to use a toolkit, make this decision later:
 dnl use the toolkit if we have gtk, or X11R5 or newer.
@@ -2249,6 +2271,22 @@ AC_DEFUN
   w32 )
     term_header=w32term.h
   ;;
+  pgtk )
+    term_header=pgtkterm.h
+    USE_X_TOOLKIT=none
+    case "${with_pgtk_toolkit}" in
+    gtk3 ) with_gtk3=yes
+           term_header=gtkutil.h ;;
+    gtk2 ) with_gtk3=no
+           with_gtk2=yes
+           term_header=gtkutil.h ;;
+    gtk4 ) with_gtk4=yes
+           term_header=pgtksubr.h
+           USE_X_TOOLKIT=none ;;
+    none ) with_gtk3=yes
+           term_header=gtkutil.h ;;
+    esac
+  ;;
 esac
 
 if test "$window_system" = none && test "X$with_x" != "Xno"; then
@@ -2577,7 +2615,7 @@ AC_DEFUN
 
 ### Use -lrsvg-2 if available, unless '--with-rsvg=no' is specified.
 HAVE_RSVG=no
-if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${opsys}" = "mingw32"; then
+if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${window_system}" = "pgtk" || test "${opsys}" = "mingw32"; then
   if test "${with_rsvg}" != "no"; then
     RSVG_REQUIRED=2.14.0
     RSVG_MODULE="librsvg-2.0 >= $RSVG_REQUIRED"
@@ -2598,7 +2636,7 @@ AC_DEFUN
 fi
 
 HAVE_IMAGEMAGICK=no
-if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes"; then
+if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${window_system}" = "pgtk" || test "${HAVE_W32}" = "yes"; then
   if test "${with_imagemagick}" != "no"; then
     if test -n "$BREW"; then
       # Homebrew doesn't link ImageMagick 6 by default, so make sure
@@ -2615,6 +2653,11 @@ AC_DEFUN
        EMACS_CHECK_MODULES([IMAGEMAGICK], [Wand >= 6.3.5 Wand != 6.8.2])
     fi
 
+    if test $HAVE_IMAGEMAGICK != yes; then
+      IMAGEMAGICK_MODULE="MagickWand-6.Q16HDRI >= 6.3.5 MagickWand-6.Q16HDRI != 6.8.2 MagickWand-6.Q16HDRI < 7 MagickCore-6.Q16HDRI >= 6.9.9 MagickCore-6.Q16HDRI < 7"
+      EMACS_CHECK_MODULES([IMAGEMAGICK], [$IMAGEMAGICK_MODULE])
+    fi
+
     if test $HAVE_IMAGEMAGICK = yes; then
       OLD_CFLAGS=$CFLAGS
       OLD_LIBS=$LIBS
@@ -2659,6 +2702,32 @@ AC_DEFUN
 check_gtk2=no
 gtk3_pkg_errors=
 if test "${opsys}" != "mingw32"; then
+  if test "${with_gtk4}" = "yes"; then
+    GLIB_REQUIRED=2.37.5
+    GTK_REQUIRED=3.98.3
+    GTK_MODULES="gtk4 >= $GTK_REQUIRED glib-2.0 >= $GLIB_REQUIRED"
+    EMACS_CHECK_MODULES([GTK], [$GTK_MODULES],
+      [pkg_check_gtk=yes], [pkg_check_gtk=no])
+    if test "$pkg_check_gtk" = "no" && test "$with_gtk4" = "yes"; then
+       AC_MSG_ERROR($GTK_PKG_ERRORS)
+    fi
+    if test "$pkg_check_gtk" = "yes"; then
+       AC_DEFINE(HAVE_GTK3, 1, [Define to 1 if using GTK 3 or later.])
+       AC_DEFINE(HAVE_GTK4, 1, [Define to 1 if using GTK 4 or later.])
+       GTK_OBJ="pgtksubr.o $GTK_OBJ"
+       gtk_term_header=pgtksubr.h
+       USE_GTK_TOOLKIT="GTK4"
+       if test "x$ac_enable_gtk_deprecation_warnings" = x; then
+	 AC_DEFINE([GDK_DISABLE_DEPRECATION_WARNINGS], [1],
+	   [Define to 1 to disable GTK+/GDK deprecation warnings.])
+	 AC_DEFINE([GLIB_DISABLE_DEPRECATION_WARNINGS], [1],
+	   [Define to 1 to disable Glib deprecation warnings.])
+       fi
+       if test "$window_system" = pgtk; then
+	  GLIB_GSETTINGS
+       fi
+    fi
+  fi
   if test "${with_gtk3}" = "yes" || test "${with_gtk}" = "yes" || test "$USE_X_TOOLKIT" = "maybe"; then
     GLIB_REQUIRED=2.37.5
     GTK_REQUIRED=3.10
@@ -2672,7 +2741,7 @@ AC_DEFUN
     fi
     if test "$pkg_check_gtk" = "yes"; then
        AC_DEFINE(HAVE_GTK3, 1, [Define to 1 if using GTK 3 or later.])
-       GTK_OBJ=emacsgtkfixed.o
+       GTK_OBJ="emacsgtkfixed.o"
        gtk_term_header=gtkutil.h
        USE_GTK_TOOLKIT="GTK3"
        if test "x$ac_enable_gtk_deprecation_warnings" = x; then
@@ -2681,12 +2750,14 @@ AC_DEFUN
 	 AC_DEFINE([GLIB_DISABLE_DEPRECATION_WARNINGS], [1],
 	   [Define to 1 to disable Glib deprecation warnings.])
        fi
+       if test "$window_system" = pgtk; then
+	  GLIB_GSETTINGS
+       fi
     else
-       check_gtk2=yes
+       check_gtk2yes
        gtk3_pkg_errors="$GTK_PKG_ERRORS "
     fi
   fi
-
   if test "${with_gtk2}" = "yes" || test "$check_gtk2" = "yes"; then
     GLIB_REQUIRED=2.28
     GTK_REQUIRED=2.24
@@ -2730,7 +2801,11 @@ AC_DEFUN
 	    libraries are there.  */
 	 if (g_signal_handler_find (G_OBJECT (gs), G_SIGNAL_MATCH_FUNC,
 				    0, 0, 0, G_CALLBACK (callback), 0))
+	 #ifdef HAVE_GTK4
+	   g_main_context_iteration (NULL, false);
+	 #else
 	   gtk_main_iteration ();
+	 #endif
        ]])],
     [emacs_cv_gtk_compiles=yes], [emacs_cv_gtk_compiles=no])])
   if test "${emacs_cv_gtk_compiles}" != "yes"; then
@@ -2742,7 +2817,7 @@ AC_DEFUN
     C_SWITCH_X_SITE="$C_SWITCH_X_SITE $GTK_CFLAGS"
     HAVE_GTK=yes
     AC_DEFINE(USE_GTK, 1, [Define to 1 if using GTK.])
-    GTK_OBJ="gtkutil.o $GTK_OBJ"
+    GTK_OBJ="gtkutil.o gtkinter.o $GTK_OBJ"
     term_header=$gtk_term_header
     USE_X_TOOLKIT=none
     AC_MSG_WARN([[Your version of Gtk+ will have problems with
@@ -2764,7 +2839,11 @@ AC_DEFUN
     with_toolkit_scroll_bars=yes
   fi
 
-  term_header=gtkutil.h
+  if test "$with_gtk4" = "yes"; then
+    term_header=pgtksubr.h
+  else
+    term_header=gtkutil.h
+  fi
 
   if test "${USE_GTK_TOOLKIT}" = GTK2; then
 
@@ -2808,6 +2887,49 @@ AC_DEFUN
 CFLAGS=$OLD_CFLAGS
 LIBS=$OLD_LIBS
 
+PGTK_OBJ=
+PGTK_LIBS=
+if test "$window_system" = "pgtk"; then
+  if test "$USE_GTK_TOOLKIT" = "GTK4"; then
+      PGTK_OBJ="gtkimage.o pgtkfns.o pgtkterm.o pgtkselect.o pgtkmenu.o pgtkim.o xsettings.o pgtkevent.o pgtkembed.o pgtkdnd.o"
+  else
+  if test "$USE_GTK_TOOLKIT" = "GTK3"; then
+      PGTK_OBJ="pgtkfns.o pgtkterm.o pgtkselect.o pgtkmenu.o pgtkim.o xsettings.o pgtkevent.o"
+  else
+      PGTK_OBJ="pgtkfns.o pgtkterm.o pgtkselect.o pgtkmenu.o pgtkim.o xsettings.o"
+  fi
+  fi
+  UNWIND_OLD_CFLAGS=$CFLAGS
+  CFLAGS="$CFLAGS $GTK_CFLAGS"
+  if test "$USE_GTK_TOOLKIT" = "GTK4"; then
+      AC_CHECK_HEADER([gdk/x11/gdkx.h],
+        [AC_CHECK_LIB([gtk-4], [gdk_x11_surface_get_xid], [HAVE_GDK_X11=yes])])
+  else
+  if test "$USE_GTK_TOOLKIT" = "GTK3"; then
+      AC_CHECK_HEADER([gdk/gdkx.h],
+        [AC_CHECK_LIB([gdk-3], [gdk_x11_window_get_xid], [HAVE_GDK_X11=yes])])
+  else
+  if test "$USE_GTK_TOOLKIT" = "GTK2"; then
+      AC_CHECK_HEADER([gdk/gdkx.h],
+        [AC_CHECK_LIB([gtk-x11], [gdk_x11_window_get_xid], [HAVE_GDK_X11=yes])])
+  fi
+  fi
+  fi
+
+  CFLAGS=$UNWIND_OLD_CFLAGS
+  if test "$HAVE_GDK_X11" = "yes"; then
+      PGTK_LIBS="$GTK_LIBS -lX11 -ldl"
+  else
+      PGTK_LIBS="$GTK_LIBS -ldl"
+  fi
+  if test "$HAVE_GDK_X11" = "yes"; then
+     AC_DEFINE([HAVE_GDK_X11], 1, [Define to 1 if GDK X11 libraries are available.])
+  fi
+  AC_DEFINE([HAVE_PGTK], 1, [Define to 1 if you have pure Gtk+-3.])
+fi
+AC_SUBST(PGTK_OBJ)
+AC_SUBST(PGTK_LIBS)
+
 dnl D-Bus has been tested under GNU/Linux only.  Must be adapted for
 dnl other platforms.
 HAVE_DBUS=no
@@ -2837,7 +2959,7 @@ AC_DEFUN
 
 dnl GSettings has been tested under GNU/Linux only.
 HAVE_GSETTINGS=no
-if test "${HAVE_X11}" = "yes" && test "${with_gsettings}" = "yes"; then
+if test "${HAVE_X11}" = "yes" -o "${window_system}" = "pgtk" && test "${with_gsettings}" = "yes"; then
    EMACS_CHECK_MODULES([GSETTINGS], [gio-2.0 >= 2.26])
    if test "$HAVE_GSETTINGS" = "yes"; then
       old_CFLAGS=$CFLAGS
@@ -2871,7 +2993,7 @@ AC_DEFUN
 dnl GConf has been tested under GNU/Linux only.
 dnl The version is really arbitrary, it is about the same age as Gtk+ 2.6.
 HAVE_GCONF=no
-if test "${HAVE_X11}" = "yes" && test "${with_gconf}" != "no"; then
+if test "${HAVE_X11}" = "yes" -o "${window_system}" = "pgtk" && test "${with_gconf}" != "no"; then
    EMACS_CHECK_MODULES([GCONF], [gconf-2.0 >= 2.13])
    if test "$HAVE_GCONF" = yes; then
       AC_DEFINE(HAVE_GCONF, 1, [Define to 1 if using GConf.])
@@ -3433,14 +3555,39 @@ AC_DEFUN
     fi
   fi
 else # "${HAVE_X11}" != "yes"
-  HAVE_XFT=no
-  HAVE_FREETYPE=no
-  HAVE_LIBOTF=no
-  HAVE_M17N_FLT=no
+  if test $window_system = pgtk; then
+    EMACS_CHECK_MODULES([FONTCONFIG], [fontconfig >= 2.2.0])
+    EMACS_CHECK_MODULES([FREETYPE], [freetype2])
+    if test "$HAVE_FONTCONFIG" != yes -o "$HAVE_FREETYPE" != yes; then
+      AC_MSG_ERROR(fontconfig and freetype is required.)
+    fi
+    HAVE_LIBOTF=no
+    AC_DEFINE(HAVE_FREETYPE, 1,
+	      [Define to 1 if using the freetype and fontconfig libraries.])
+    if test "${with_libotf}" != "no"; then
+      EMACS_CHECK_MODULES([LIBOTF], [libotf])
+      if test "$HAVE_LIBOTF" = "yes"; then
+	AC_DEFINE(HAVE_LIBOTF, 1, [Define to 1 if using libotf.])
+	AC_CHECK_LIB(otf, OTF_get_variation_glyphs,
+		     HAVE_OTF_GET_VARIATION_GLYPHS=yes,
+		     HAVE_OTF_GET_VARIATION_GLYPHS=no)
+	if test "${HAVE_OTF_GET_VARIATION_GLYPHS}" = "yes"; then
+	  AC_DEFINE(HAVE_OTF_GET_VARIATION_GLYPHS, 1,
+		    [Define to 1 if libotf has OTF_get_variation_glyphs.])
+	fi
+      fi
+    fi
+  else
+    HAVE_XFT=no
+    HAVE_FREETYPE=no
+    HAVE_LIBOTF=no
+    HAVE_M17N_FLT=no
+  fi
 fi   # "${HAVE_X11}" != "yes"
 
 HAVE_HARFBUZZ=no
 if test "${HAVE_X11}" = "yes" && test "${HAVE_FREETYPE}" = "yes" \
+        || test "$window_system" = "pgtk" \
         || test "${HAVE_W32}" = "yes"; then
   if test "${with_harfbuzz}" != "no"; then
     ### On MS-Windows we use hb_font_get_nominal_glyph, which appeared
@@ -3474,6 +3621,25 @@ AC_DEFUN
 AC_SUBST(M17N_FLT_CFLAGS)
 AC_SUBST(M17N_FLT_LIBS)
 
+HAVE_CAIRO=no
+if test "${HAVE_X11}" = "yes" -o "$window_system" = pgtk; then
+  if test "${with_cairo}" != "no"; then
+    CAIRO_REQUIRED=1.12.0
+    CAIRO_MODULE="cairo >= $CAIRO_REQUIRED"
+    EMACS_CHECK_MODULES(CAIRO, $CAIRO_MODULE)
+    if test $HAVE_CAIRO = yes; then
+      AC_DEFINE(USE_CAIRO, 1, [Define to 1 if using cairo.])
+    else
+      AC_MSG_ERROR([cairo requested but not found.])
+    fi
+
+    CFLAGS="$CFLAGS $CAIRO_CFLAGS"
+    LIBS="$LIBS $CAIRO_LIBS"
+    AC_SUBST(CAIRO_CFLAGS)
+    AC_SUBST(CAIRO_LIBS)
+  fi
+fi
+
 if test "${HAVE_X11}" = "yes"; then
   AC_CHECK_HEADER(X11/Xlib-xcb.h,
     AC_CHECK_LIB(xcb, xcb_translate_coordinates, HAVE_XCB=yes))
@@ -3581,8 +3747,10 @@ AC_DEFUN
 ### Use -ljpeg if available, unless '--with-jpeg=no'.
 HAVE_JPEG=no
 LIBJPEG=
-if test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \
-   || test "${HAVE_NS}" = "yes"; then
+if test "${NS_IMPL_COCOA}" = yes; then
+  : # Cocoa provides its own jpeg support, so do nothing.
+elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" || test "$window_system" = "pgtk" \
+     || test "${HAVE_NS}" = "yes"; then
   if test "${with_jpeg}" != "no"; then
     AC_CACHE_CHECK([for jpeglib 6b or later],
       [emacs_cv_jpeglib],
@@ -3736,7 +3904,7 @@ AC_DEFUN
   if test "$opsys" = mingw32; then
     AC_CHECK_HEADER([png.h], [HAVE_PNG=yes])
   elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \
-       || test "${HAVE_NS}" = "yes"; then
+       || test "${HAVE_NS}" = "yes" || test "$window_system" = "pgtk"; then
     EMACS_CHECK_MODULES([PNG], [libpng >= 1.0.0])
     if test $HAVE_PNG = yes; then
       LIBPNG=$PNG_LIBS
@@ -3811,7 +3979,7 @@ AC_DEFUN
     AC_DEFINE(HAVE_TIFF, 1, [Define to 1 if you have the tiff library (-ltiff).])
   fi
 elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \
-     || test "${HAVE_NS}" = "yes"; then
+     || test "${HAVE_NS}" = "yes" || test "$window_system" = "pgtk"; then
   if test "${with_tiff}" != "no"; then
     AC_CHECK_HEADER(tiffio.h,
       [tifflibs="-lz -lm"
@@ -3840,7 +4008,8 @@ AC_DEFUN
     AC_DEFINE(HAVE_GIF, 1, [Define to 1 if you have a gif (or ungif) library.])
   fi
 elif test "${HAVE_X11}" = "yes" && test "${with_gif}" != "no" \
-        || test "${HAVE_W32}" = "yes" || test "${HAVE_NS}" = "yes"; then
+        || test "${HAVE_W32}" = "yes" || test "${HAVE_NS}" = "yes" \
+	|| test "$window_system" = "pgtk"; then
   AC_CHECK_HEADER(gif_lib.h,
 # EGifPutExtensionLast only exists from version libungif-4.1.0b1.
 # Earlier versions can crash Emacs, but version 5.0 removes EGifPutExtensionLast.
@@ -5242,6 +5411,7 @@ AC_DEFUN
 AC_SUBST(exec_prefix)
 AC_SUBST(bindir)
 AC_SUBST(datadir)
+AC_SUBST(gsettingsschemadir)
 AC_SUBST(sharedstatedir)
 AC_SUBST(libexecdir)
 AC_SUBST(mandir)
@@ -5301,6 +5471,9 @@ AC_DEFUN
     FONT_OBJ="$FONT_OBJ ftfont.o"
   fi
 fi
+if test "${window_system}" = "pgtk"; then
+   FONT_OBJ="ftfont.o ftcrfont.o"
+fi
 if test "${HAVE_HARFBUZZ}" = "yes" ; then
   FONT_OBJ="$FONT_OBJ hbfont.o"
 fi
diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi
index 905e5c2e6c..053f697d40 100644
--- a/doc/lispref/frames.texi
+++ b/doc/lispref/frames.texi
@@ -60,6 +60,8 @@ Frames
 terminal.
 @item pc
 The frame is displayed on an MS-DOS terminal.
+@item pgtk
+The frame is displayed on a graphical terminal with the GTK toolkit.
 @end table
 @end defun
 
diff --git a/etc/NEWS b/etc/NEWS
index 025d5c14a7..a8fca897cd 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -58,6 +58,16 @@ shaping, so 'configure' now recommends that combination.
 ** The ftx font backend driver has been removed.
 It was declared obsolete in Emacs 27.1.
 
+---
+** Emacs can now run under GTK without X11 dependencies
+Emacs can now use pure GTK with cairo drawing. It supports GTK 2,
+3, and GTK 4's development branches. To build Emacs with this, use
+`--with-window-system=pgtk --with-pgtk-toolkit=gtk(2 3 or 4)'.
+
+--
+** Emacs now has enhanced touchscreen support on GTK 4 terminals
+Swipe, drag, and long-press gestures are now implemented.
+
 \f
 * Startup Changes in Emacs 28.1
 
@@ -72,6 +82,9 @@ dimension.
 
 \f
 * Editing Changes in Emacs 28.1
++++
+** GTK smooth scrolling is now used on GTK terminals.
+Scrolling now uses GTK's own natural scrolling on GTK terminals.
 
 +++
 ** New command 'undo-redo'.
diff --git a/etc/org.gnu.emacs.defaults.gschema.xml b/etc/org.gnu.emacs.defaults.gschema.xml
new file mode 100644
index 0000000000..2d206e63a0
--- /dev/null
+++ b/etc/org.gnu.emacs.defaults.gschema.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<schemalist>
+
+  <schema id="org.gnu.emacs.defaults">
+
+    <key name='alpha'                    type='s'><default>''</default></key>
+    <key name='auto-raise-lower'         type='s'><default>''</default></key>
+    <key name='auto-lower'               type='s'><default>''</default></key>
+    <key name='auto-raise'               type='s'><default>''</default></key>
+    <key name='background'               type='s'><default>''</default></key>
+    <key name='background-mode'          type='s'><default>''</default></key>
+    <key name='bitmap-icon'              type='s'><default>''</default></key>
+    <key name='border-color'             type='s'><default>''</default></key>
+    <key name='border-width'             type='s'><default>''</default></key>
+    <key name='buffer-predicate'         type='s'><default>''</default></key>
+    <key name='cursor-blink'             type='s'><default>''</default></key>
+    <key name='cursor-type'              type='s'><default>''</default></key>
+    <key name='cursor-color'             type='s'><default>''</default></key>
+    <key name='font'                     type='s'><default>''</default></key>
+    <key name='font-backend'             type='s'><default>''</default></key>
+    <key name='foreground'               type='s'><default>''</default></key>
+    <key name='fullscreen'               type='s'><default>''</default></key>
+    <key name='horizontal-scroll-bars'   type='s'><default>''</default></key>
+    <key name='icon-name'                type='s'><default>''</default></key>
+    <key name='inhibit-double-buffering' type='s'><default>''</default></key>
+    <key name='internal-border'          type='s'><default>''</default></key>
+    <key name='internal-border-width'    type='s'><default>''</default></key>
+    <key name='left-fringe'              type='s'><default>''</default></key>
+    <key name='line-spacing'             type='s'><default>''</default></key>
+    <key name='menu-bar'                 type='s'><default>''</default></key>
+    <key name='minibuffer'               type='s'><default>''</default></key>
+    <key name='name'                     type='s'><default>''</default></key>
+    <key name='pointer-color'            type='s'><default>''</default></key>
+    <key name='reverse-video'            type='s'><default>''</default></key>
+    <key name='right-fringe'             type='s'><default>''</default></key>
+    <key name='screen-gamma'             type='s'><default>''</default></key>
+    <key name='scroll-bar'               type='s'><default>''</default></key>
+    <key name='scroll-bar-background'    type='s'><default>''</default></key>
+    <key name='scroll-bar-foreground'    type='s'><default>''</default></key>
+    <key name='scroll-bar-height'        type='s'><default>''</default></key>
+    <key name='scroll-bar-width'         type='s'><default>''</default></key>
+    <key name='scroll-bars'              type='s'><default>''</default></key>
+    <key name='title'                    type='s'><default>''</default></key>
+    <key name='tool-bar'                 type='s'><default>''</default></key>
+    <key name='vertical-scroll-bars'     type='s'><default>''</default></key>
+    <key name='wait-for-w-m'             type='s'><default>''</default></key>
+
+  </schema>
+
+</schemalist>
diff --git a/etc/themes/gtk-theme.el b/etc/themes/gtk-theme.el
new file mode 100644
index 0000000000..c4d158af16
--- /dev/null
+++ b/etc/themes/gtk-theme.el
@@ -0,0 +1,90 @@
+;;; pgtk-theme.el --- GTK theme support for GNU Emacs. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2020 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides some crude GTK theme integration for GNU Emacs.
+
+;;; Code:
+
+(unless (and (or (string-prefix-p "3.98" gtk-version-string)
+                 (string-prefix-p "4." gtk-version-string))
+             (window-system))
+  (error "Trying to load gtk-theme.el on an incompatible terminal"))
+
+(require 'subr-x)
+
+(deftheme gtk
+  "The system GTK theme.")
+(let ((class '((class color) (min-colors 89))))
+  (custom-theme-set-faces
+   'gtk
+   `(default ((,class (:foreground
+                       "gtk-style-foreground"
+                       :background
+                       "gtk-style-background"
+                       :family
+                       ,(pgtk-default-font-family t)))))
+   `(variable-pitch ((,class (:family ,(pgtk-default-font-family nil)))))
+   `(mode-line ((,class (:foreground
+                         "gtk-style-modeline-foreground"
+                         :background
+                         "gtk-style-modeline-background"))))
+   `(region ((,class (:foreground
+                      "gtk_selection_fg_color"
+                      :background
+                      "gtk_selection_bg_color"))))
+   `(link ((,class (:foreground "gtk-style-link-foreground"
+                                :underline t))))
+   `(custom-button ((,class (:foreground "gtk-style-foreground"
+                                         :box (:line-width 5 :style gtk-released-button)))))
+   `(eww-form-submit ((,class (:foreground "gtk-style-foreground"
+                                           :box (:line-width 5 :style gtk-released-button)))))
+   `(custom-button-mouse ((,class (:foreground "gtk-style-foreground"
+                                               :box (:line-width 5 :style gtk-prelight-button)))))
+   `(custom-button-pressed ((,class (:foreground "gtk-style-foreground"
+                                                 :box (:line-width 5 :style gtk-pressed-button)))))
+   `(widget-field ((,class (:foreground "gtk-style-foreground"
+                                        :box (:line-width 5 :style gtk-selected-entry)))))
+   `(widget-button ((,class (:foreground "gtk-style-foreground"
+                                         :box (:line-width 5 :style gtk-released-button)))))
+   `(link-visited ((,class (:foreground "gtk-style-slink-foreground"
+                                        :underline t))))
+   `(tab-bar ((,class (:foreground "gtk-style-modeline-foreground"
+                                   :background "gtk-style-tb-background"))))
+   `(org-checkbox ((,class (:foreground "gtk-style-foreground"
+                                        :box (:line-width 5 :style gtk-unchecked-checkbox)))))))
+
+(custom-theme-set-variables
+ 'gtk '(cursor-type `(bar . ,(pgtk-get-text-caret-width)))
+ '(pgtk-flat-underwave-style t)
+ '(window-divider-default-right-width (pgtk-get-separator-width))
+ '(window-divider-default-bottom-width (pgtk-get-separator-width)))
+
+;;;###autoload
+(when (and (boundp 'custom-theme-load-path)
+           load-file-name)
+  ;; add theme folder to `custom-theme-load-path' when installing over MELPA
+  (add-to-list 'custom-theme-load-path
+               (file-name-as-directory (file-name-directory load-file-name))))
+
+(provide-theme 'gtk)
+
+;;; gtk-theme.el ends here
+(provide 'gtk-theme)
diff --git a/gtk4.org b/gtk4.org
new file mode 100644
index 0000000000..c85a51e1f9
--- /dev/null
+++ b/gtk4.org
@@ -0,0 +1,105 @@
+* Porting Emacs to GTK 4
+  GTK 4 is a new major release of the GTK toolkit, which brings
+  faster and better rendering, improved broadway support, and
+  massive restructuring and modernization to GTK.
+
+  This documents the effort being undertaken to port Emacs to
+  GTK 4.
+** Important issues that should be addressed
+*** gtk_main_iteration () has been removed
+    ~gtk_main_iteration ()~ has been removed. This means all
+    occurrences of ~gtk_main_iteration ()~ must be replaced
+    with ~g_main_context_iteration ()~, like:
+    #+NAME: GTK event loop iteration
+    #+BEGIN_SRC C
+#include <gtk/gtk.h>
+
+int main (void)
+  {
+    g_main_context_iteration (NULL, false);
+  }
+
+    #+END_SRC
+
+*** Menus need to be implemented from scratch.
+    GTK 4 removes ~~GtkMenuShell~~, and associated classes.
+    GtkPopovers are a far more modern replacement, but require
+    a rewrite of the menu handling system.
+
+*** GdkScreen is gone.
+    Emacs makes heavy usage of the deprecated obsolete APIs in
+    GdkScreen, which need to be replaced.
+
+** What has been done
+*** gtkutil.c has been removed
+    ~~gtkutil.c~~ is obsolete and will no longer be used for GTK 4
+    builds. GTK-related procedures for GTK 4 have been moved or
+    rewritten, and are now placed in ~~pgtksubr.c~~.
+
+*** All object files now compile without errors
+    Several functions are not yet implemented, and cause the
+    executable to fail to link.
+*** Emacs now compiles without errors
+*** The configure scripts now detect and use GTK 4.
+** What will be done
+   Everything in this section will be completed sooner or later
+*** DONE Make it build
+    Emacs now compiles without errors.
+*** DONE Make basic functionality, such as window creation and drawing work
+    Emacs successfully draws to the GtkDrawingArea widget.
+*** DONE Make event handling work
+    So far this hasn't happened yet.
+*** DONE Make input method handling work
+    Input methods are now fully functional on GTK 4 builds.
+*** DONE Port tooltips to GTK 4
+    +We rely on adding a GtkFixed to the frame overlay widget,+
+    +as you can no longer intercept tooltip windows as they+
+    +are being displayed.+
+    +We now rely on setting the drawing area tool-tip, and manually+
+    +positioning it once it is displayed.+
+    We now create a fake tooltip-styled popover.
+*** DONE Make kill ring <-> GTK integration work again
+    Clipboard management in GTK has been moved to GDK,
+    and the behaviour has been changed to better
+    reflect modern display technologies. The concepts
+    of "ownership", secondary selections and cut
+    buffers have been abolished. This requires a
+    rewrite of the selection handling code.
+*** DONE Port the frame HeaderBar to GTK 4
+**** DONE Implement HeaderBar tool items
+     Tool items are now implemented.
+**** DONE Implement header bar menus.
+     Header bar (and popup menus) are not yet implemented.
+*** DONE Port window pop-up menus to GTK 4
+    +This will probably involve some tricks, as the new GTK popover widget+
+    +does not play very well with manual event handling.+
+    Popup menus have been implemented.
+*** DONE Implement child frame handling for GTK 4
+    ~~gtk_window_move~~ has been removed, and changes to subsurface
+    handling have occurred. This means child frames will have to be
+    re-implemented from scratch.
+    Child frames have now been implemented. They can only be
+    displayed within the bounds of the parent frame.
+*** DONE Use only public APIs
+    Emacs currently relies on many unstable and undocumented private
+    GTK 4 interfaces in order to function. This should be eliminated.
+**** DONE Extending ~~GtkEventController~~
+     +Emacs does manual event handling, and reimplements much of the+
+     +logic in ~~GtkGesture~~. One cannot manually handle events+
+     +this way on GTK 4 without relying on private APIs.+
+     Emacs now uses a ~~GtkEventControllerLegacy~~.
+**** DONE Accessing ~~GdkEvent~~ fields
+     Emacs relies on accessing certain private undocumented GdkEvent
+     fields in order to work around certain GTK bugs. This should be
+     eliminated.
+**** DONE Accessing private GtkScrollbar fields
+     We rely on manually retrieving the GtkRange
+     associated to the GtkScrollbar.
+*** DONE Implement toolkit scroll bars
+    Toolkit scroll bars are now implemented. They are displayed in
+    the frame overlay.
+*** DONE Implement toolkit tool-bars
+    Toolkit tool-bars are now implemented.
+** What *won't* be done
+   Functionality in this category is either made redundant by
+   work done to port Emacs to GTK 4, or is obsolete anyways.
diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el
index d3d17fda7a..bd91edc0fa 100644
--- a/lisp/cus-edit.el
+++ b/lisp/cus-edit.el
@@ -2111,7 +2111,7 @@ custom-magic-reset
 ;;; The `custom' Widget.
 
 (defface custom-button
-  '((((type x w32 ns) (class color))	; Like default mode line
+  '((((type x w32 ns pgtk) (class color))	; Like default mode line
      :box (:line-width 2 :style released-button)
      :background "lightgrey" :foreground "black"))
   "Face for custom buffer buttons if `custom-raised-buttons' is non-nil."
@@ -2119,7 +2119,7 @@ custom-button
   :group 'custom-faces)
 
 (defface custom-button-mouse
-  '((((type x w32 ns) (class color))
+  '((((type x w32 ns pgtk) (class color))
      :box (:line-width 2 :style released-button)
      :background "grey90" :foreground "black")
     (t
@@ -2144,7 +2144,7 @@ custom-button-unraised
       (if custom-raised-buttons 'custom-button-mouse 'highlight))
 
 (defface custom-button-pressed
-  '((((type x w32 ns) (class color))
+  '((((type x w32 ns pgtk) (class color))
      :box (:line-width 2 :style pressed-button)
      :background "lightgrey" :foreground "black")
     (t :inverse-video t))
@@ -3322,6 +3322,10 @@ 'custom-display
 					   :sibling-args (:help-echo "\
 GNUstep or Macintosh OS Cocoa interface.")
 					   ns)
+				    (const :format "PGTK "
+					   :sibling-args (:help-echo "\
+Pure-GTK interface.")
+					   ns)
 				    (const :format "DOS "
 					   :sibling-args (:help-echo "\
 Plain MS-DOS.")
diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el
index ede4edcd57..5e4205a71a 100644
--- a/lisp/emacs-lisp/autoload.el
+++ b/lisp/emacs-lisp/autoload.el
@@ -1045,7 +1045,7 @@ update-directory-autoloads
                        ;; we don't want to depend on whether Emacs was
                        ;; built with or without modules support, nor
                        ;; what is the suffix for the underlying OS.
-		       (unless (string-match "\\.\\(elc\\|so\\|dll\\)" suf)
+		       (unless (string-match "\\.\\(elc\\|eln\\|so\\|dll\\)" suf)
                          (push suf tmp)))
                      (concat "\\`[^=.].*" (regexp-opt tmp t) "\\'")))
 	 (files (apply #'nconc
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 13b7219656..78b2e0a10a 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -2018,7 +2018,7 @@ byte-compile-file
 		  ;; emacs-lisp files in the build tree are
 		  ;; recompiled).  Previously this was accomplished by
 		  ;; deleting target-file before writing it.
-		  (rename-file tempfile target-file t))
+                  (rename-file tempfile target-file t))
 		(or noninteractive (message "Wrote %s" target-file)))
 	    ;; This is just to give a better error message than write-region
 	    (let ((exists (file-exists-p target-file)))
@@ -3071,7 +3071,7 @@ byte-compile-out-toplevel
 	      (setq rest (cdr rest)))
 	    rest))
       (let ((byte-compile-vector (byte-compile-constants-vector)))
-	(list 'byte-code (byte-compile-lapcode byte-compile-output)
+        (list 'byte-code (byte-compile-lapcode byte-compile-output)
 	      byte-compile-vector byte-compile-maxdepth)))
      ;; it's a trivial function
      ((cdr body) (cons 'progn (nreverse body)))
diff --git a/lisp/faces.el b/lisp/faces.el
index e707f6f4b6..acbb1001db 100644
--- a/lisp/faces.el
+++ b/lisp/faces.el
@@ -1151,13 +1151,14 @@ face-valid-attribute-values
            (:height
             'integerp)
            (:stipple
-            (and (memq (window-system frame) '(x ns)) ; No stipple on w32
-                 (mapcar #'list
+            (and (memq (window-system frame) '(x ns pgtk)) ; No stipple on w32
+                 (mapcar (lambda (f)
+                           (cons (file-name-base f) f))
                          (apply #'nconc
                                 (mapcar (lambda (dir)
                                           (and (file-readable-p dir)
                                                (file-directory-p dir)
-                                               (directory-files dir)))
+                                               (directory-files dir 'full)))
                                         x-bitmap-file-path)))))
            (:inherit
             (cons '("none" . nil)
@@ -1490,7 +1491,7 @@ face-spec-set-match-display
 	    match (cond ((eq req 'type)
 			 (or (memq (window-system frame) options)
 			     (and (memq 'graphic options)
-				  (memq (window-system frame) '(x w32 ns)))
+				  (memq (window-system frame) '(x w32 ns pgtk)))
 			     ;; FIXME: This should be revisited to use
 			     ;; display-graphic-p, provided that the
 			     ;; color selection depends on the number
@@ -1835,6 +1836,7 @@ color-values
   (cond
    ((member color '(unspecified "unspecified-fg" "unspecified-bg"))
     nil)
+
    ((display-graphic-p frame)
     (xw-color-values color frame))
    (t
@@ -1893,48 +1895,53 @@ read-color
 Interactively, or with optional arg MSG non-nil, print the
 resulting color name in the echo area."
   (interactive "i\np\ni\np")    ; Always convert to RGB interactively.
-  (let* ((completion-ignore-case t)
-	 (colors (or facemenu-color-alist
-		     (append '("foreground at point" "background at point")
-			     (if allow-empty-name '(""))
-                             (if (display-color-p)
-                                 (defined-colors-with-face-attributes)
-                               (defined-colors)))))
-	 (color (completing-read
-		 (or prompt "Color (name or #RGB triplet): ")
-		 ;; Completing function for reading colors, accepting
-		 ;; both color names and RGB triplets.
-		 (lambda (string pred flag)
-		   (cond
-		    ((null flag)        ; Try completion.
-		     (or (try-completion string colors pred)
-			 (if (color-defined-p string)
-			     string)))
-		    ((eq flag t)        ; List all completions.
-		     (or (all-completions string colors pred)
-			 (if (color-defined-p string)
-			     (list string))))
-		    ((eq flag 'lambda)  ; Test completion.
-		     (or (member string colors)
-			 (color-defined-p string)))))
-		 nil t)))
-
-    ;; Process named colors.
-    (when (member color colors)
-      (cond ((string-equal color "foreground at point")
-	     (setq color (foreground-color-at-point)))
-	    ((string-equal color "background at point")
-	     (setq color (background-color-at-point))))
-      (when (and convert-to-RGB
-		 (not (string-equal color "")))
-	(let ((components (x-color-values color)))
-	  (unless (string-match-p "^#\\(?:[[:xdigit:]][[:xdigit:]][[:xdigit:]]\\)+$" color)
-	    (setq color (format "#%04X%04X%04X"
-				(logand 65535 (nth 0 components))
-				(logand 65535 (nth 1 components))
-				(logand 65535 (nth 2 components))))))))
-    (when msg (message "Color: `%s'" color))
-    color))
+  (if (and (fboundp 'pgtk-read-color)
+           x-gtk-use-color-dialogs
+           (window-system))
+      (pgtk-read-color prompt msg)
+    (progn
+      (let* ((completion-ignore-case t)
+	     (colors (or facemenu-color-alist
+		         (append '("foreground at point" "background at point")
+			         (if allow-empty-name '(""))
+                                 (if (display-color-p)
+                                     (defined-colors-with-face-attributes)
+                                   (defined-colors)))))
+	     (color (completing-read
+		     (or prompt "Color (name or #RGB triplet): ")
+		     ;; Completing function for reading colors, accepting
+		     ;; both color names and RGB triplets.
+		     (lambda (string pred flag)
+		       (cond
+		        ((null flag)    ; Try completion.
+		         (or (try-completion string colors pred)
+			     (if (color-defined-p string)
+			         string)))
+		        ((eq flag t)    ; List all completions.
+		         (or (all-completions string colors pred)
+			     (if (color-defined-p string)
+			         (list string))))
+		        ((eq flag 'lambda) ; Test completion.
+		         (or (member string colors)
+			     (color-defined-p string)))))
+		     nil t)))
+
+        ;; Process named colors.
+        (when (member color colors)
+          (cond ((string-equal color "foreground at point")
+	         (setq color (foreground-color-at-point)))
+	        ((string-equal color "background at point")
+	         (setq color (background-color-at-point))))
+          (when (and convert-to-RGB
+		     (not (string-equal color "")))
+	    (let ((components (x-color-values color)))
+	      (unless (string-match-p "^#\\(?:[[:xdigit:]][[:xdigit:]][[:xdigit:]]\\)+$" color)
+	        (setq color (format "#%04X%04X%04X"
+				    (logand 65535 (nth 0 components))
+				    (logand 65535 (nth 1 components))
+				    (logand 65535 (nth 2 components))))))))
+        (when msg (message "Color: `%s'" color))
+        color))))
 
 (defun face-at-point (&optional thing multiple)
   "Return the face of the character after point.
@@ -2730,7 +2737,7 @@ tool-bar
   '((default
      :box (:line-width 1 :style released-button)
      :foreground "black")
-    (((type x w32 ns) (class color))
+    (((type x w32 ns pgtk) (class color))
      :background "grey75")
     (((type x) (class mono))
      :background "grey"))
diff --git a/lisp/frame.el b/lisp/frame.el
index 6c2f774709..bec305b6e0 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -1586,6 +1586,7 @@ frame-current-scroll-bars
 (declare-function x-frame-geometry "xfns.c" (&optional frame))
 (declare-function w32-frame-geometry "w32fns.c" (&optional frame))
 (declare-function ns-frame-geometry "nsfns.m" (&optional frame))
+(declare-function pgtk-frame-geometry "pgtkfns.c" (&optional frame))
 
 (defun frame-geometry (&optional frame)
   "Return geometric attributes of FRAME.
@@ -1635,6 +1636,8 @@ frame-geometry
       (w32-frame-geometry frame))
      ((eq frame-type 'ns)
       (ns-frame-geometry frame))
+     ((eq frame-type 'pgtk)
+      (pgtk-frame-geometry frame))
      (t
       (list
        '(outer-position 0 . 0)
@@ -1681,6 +1684,7 @@ frame--size-history
 (declare-function x-frame-edges "xfns.c" (&optional frame type))
 (declare-function w32-frame-edges "w32fns.c" (&optional frame type))
 (declare-function ns-frame-edges "nsfns.m" (&optional frame type))
+(declare-function pgtk-frame-edges "pgtkfns.c" (&optional frame type))
 
 (defun frame-edges (&optional frame type)
   "Return coordinates of FRAME's edges.
@@ -1704,12 +1708,15 @@ frame-edges
       (w32-frame-edges frame type))
      ((eq frame-type 'ns)
       (ns-frame-edges frame type))
+     ((eq frame-type 'pgtk)
+      (pgtk-frame-edges frame type))
      (t
       (list 0 0 (frame-width frame) (frame-height frame))))))
 
 (declare-function w32-mouse-absolute-pixel-position "w32fns.c")
 (declare-function x-mouse-absolute-pixel-position "xfns.c")
 (declare-function ns-mouse-absolute-pixel-position "nsfns.m")
+(declare-function pgtk-mouse-absolute-pixel-position "pgtkfns.c")
 
 (defun mouse-absolute-pixel-position ()
   "Return absolute position of mouse cursor in pixels.
@@ -1724,9 +1731,12 @@ mouse-absolute-pixel-position
       (w32-mouse-absolute-pixel-position))
      ((eq frame-type 'ns)
       (ns-mouse-absolute-pixel-position))
+     ((eq frame-type 'pgtk)
+      (pgtk-mouse-absolute-pixel-position))
      (t
       (cons 0 0)))))
 
+(declare-function pgtk-set-mouse-absolute-pixel-position "pgtkfns.c" (x y))
 (declare-function ns-set-mouse-absolute-pixel-position "nsfns.m" (x y))
 (declare-function w32-set-mouse-absolute-pixel-position "w32fns.c" (x y))
 (declare-function x-set-mouse-absolute-pixel-position "xfns.c" (x y))
@@ -1737,6 +1747,8 @@ set-mouse-absolute-pixel-position
 position (0, 0) of the selected frame's terminal."
   (let ((frame-type (framep-on-display)))
     (cond
+     ((eq frame-type 'pgtk)
+      (pgtk-set-mouse-absolute-pixel-position x y))
      ((eq frame-type 'ns)
       (ns-set-mouse-absolute-pixel-position x y))
      ((eq frame-type 'x)
@@ -1835,6 +1847,7 @@ frame-monitor-workarea
 (declare-function x-frame-list-z-order "xfns.c" (&optional display))
 (declare-function w32-frame-list-z-order "w32fns.c" (&optional display))
 (declare-function ns-frame-list-z-order "nsfns.m" (&optional display))
+(declare-function pgtk-frame-list-z-order "pgtkfns.c" (&optional display))
 
 (defun frame-list-z-order (&optional display)
   "Return list of Emacs' frames, in Z (stacking) order.
@@ -1854,11 +1867,14 @@ frame-list-z-order
      ((eq frame-type 'w32)
       (w32-frame-list-z-order display))
      ((eq frame-type 'ns)
-      (ns-frame-list-z-order display)))))
+      (ns-frame-list-z-order display))
+     ((eq frame-type 'pgtk)
+      (pgtk-frame-list-z-order display)))))
 
 (declare-function x-frame-restack "xfns.c" (frame1 frame2 &optional above))
 (declare-function w32-frame-restack "w32fns.c" (frame1 frame2 &optional above))
 (declare-function ns-frame-restack "nsfns.m" (frame1 frame2 &optional above))
+(declare-function pgtk-frame-restack "pgtkfns.c" (frame1 frame2 &optional above))
 
 (defun frame-restack (frame1 frame2 &optional above)
   "Restack FRAME1 below FRAME2.
@@ -1888,7 +1904,9 @@ frame-restack
          ((eq frame-type 'w32)
           (w32-frame-restack frame1 frame2 above))
          ((eq frame-type 'ns)
-          (ns-frame-restack frame1 frame2 above))))
+          (ns-frame-restack frame1 frame2 above))
+         ((eq frame-type 'pgtk)
+          (pgtk-frame-restack frame1 frame2 above))))
     (error "Cannot restack frames")))
 
 (defun frame-size-changed-p (&optional frame)
@@ -1935,7 +1953,7 @@ display-mouse-p
      ((eq frame-type 'w32)
       (with-no-warnings
        (> w32-num-mouse-buttons 0)))
-     ((memq frame-type '(x ns))
+     ((memq frame-type '(x ns pgtk))
       t)    ;; We assume X and NeXTstep *always* have a pointing device
      (t
       (or (and (featurep 'xt-mouse)
@@ -1961,7 +1979,7 @@ display-graphic-p
 that use a window system such as X, and false for text-only terminals.
 DISPLAY can be a display name, a frame, or nil (meaning the selected
 frame's display)."
-  (not (null (memq (framep-on-display display) '(x w32 ns)))))
+  (not (null (memq (framep-on-display display) '(x w32 ns pgtk)))))
 
 (defun display-images-p (&optional display)
   "Return non-nil if DISPLAY can display images.
@@ -1989,7 +2007,7 @@ display-selections-p
       ;; a Windows DOS Box.
       (with-no-warnings
        (not (null dos-windows-version))))
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       t)
      (t
       nil))))
@@ -1999,7 +2017,7 @@ display-symbol-keys-p
 This means that, for example, DISPLAY can differentiate between
 the keybinding RET and [return]."
   (let ((frame-type (framep-on-display display)))
-    (or (memq frame-type '(x w32 ns pc))
+    (or (memq frame-type '(x w32 ns pc pgtk))
         ;; MS-DOS and MS-Windows terminals have built-in support for
         ;; function (symbol) keys
         (memq system-type '(ms-dos windows-nt)))))
@@ -2012,7 +2030,7 @@ display-screens
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-screens display))
      (t
       1))))
@@ -2032,7 +2050,7 @@ display-pixel-height
 `display-monitor-attributes-list'."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-pixel-height display))
      (t
       (frame-height (if (framep display) display (selected-frame)))))))
@@ -2052,7 +2070,7 @@ display-pixel-width
 `display-monitor-attributes-list'."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-pixel-width display))
      (t
       (frame-width (if (framep display) display (selected-frame)))))))
@@ -2090,7 +2108,7 @@ display-mm-height
 refers to the height in millimeters for all physical monitors
 associated with DISPLAY.  To get information for each physical
 monitor, use `display-monitor-attributes-list'."
-  (and (memq (framep-on-display display) '(x w32 ns))
+  (and (memq (framep-on-display display) '(x w32 ns pgtk))
        (or (cddr (assoc (or display (frame-parameter nil 'display))
 			display-mm-dimensions-alist))
 	   (cddr (assoc t display-mm-dimensions-alist))
@@ -2111,7 +2129,7 @@ display-mm-width
 refers to the width in millimeters for all physical monitors
 associated with DISPLAY.  To get information for each physical
 monitor, use `display-monitor-attributes-list'."
-  (and (memq (framep-on-display display) '(x w32 ns))
+  (and (memq (framep-on-display display) '(x w32 ns pgtk))
        (or (cadr (assoc (or display (frame-parameter nil 'display))
 			display-mm-dimensions-alist))
 	   (cadr (assoc t display-mm-dimensions-alist))
@@ -2129,7 +2147,7 @@ display-backing-store
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-backing-store display))
      (t
       'not-useful))))
@@ -2142,7 +2160,7 @@ display-save-under
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-save-under display))
      (t
       'not-useful))))
@@ -2155,7 +2173,7 @@ display-planes
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-planes display))
      ((eq frame-type 'pc)
       4)
@@ -2170,7 +2188,7 @@ display-color-cells
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-color-cells display))
      ((eq frame-type 'pc)
       16)
@@ -2187,7 +2205,7 @@ display-visual-class
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns))
+     ((memq frame-type '(x w32 ns pgtk))
       (x-display-visual-class display))
      ((and (memq frame-type '(pc t))
 	   (tty-display-color-p display))
@@ -2201,6 +2219,8 @@ display-visual-class
 		  (&optional display))
 (declare-function ns-display-monitor-attributes-list "nsfns.m"
 		  (&optional terminal))
+(declare-function pgtk-display-monitor-attributes-list "pgtkfns.c"
+		  (&optional terminal))
 
 (defun display-monitor-attributes-list (&optional display)
   "Return a list of physical monitor attributes on DISPLAY.
@@ -2249,6 +2269,8 @@ display-monitor-attributes-list
       (w32-display-monitor-attributes-list display))
      ((eq frame-type 'ns)
       (ns-display-monitor-attributes-list display))
+     ((eq frame-type 'pgtk)
+      (pgtk-display-monitor-attributes-list display))
      (t
       (let ((geometry (list 0 0 (display-pixel-width display)
 			    (display-pixel-height display))))
diff --git a/lisp/international/mule-cmds.el b/lisp/international/mule-cmds.el
index 7714a778fc..36aad2d576 100644
--- a/lisp/international/mule-cmds.el
+++ b/lisp/international/mule-cmds.el
@@ -87,7 +87,7 @@ set-coding-system-map
     (bindings--define-key map [separator-3] menu-bar-separator)
     (bindings--define-key map [set-terminal-coding-system]
       '(menu-item "For Terminal" set-terminal-coding-system
-        :enable (null (memq initial-window-system '(x w32 ns)))
+        :enable (null (memq initial-window-system '(x w32 ns pgtk)))
         :help "How to encode terminal output"))
     (bindings--define-key map [set-keyboard-coding-system]
       '(menu-item "For Keyboard" set-keyboard-coding-system
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 97525b2708..a800f57c44 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -336,6 +336,17 @@
         (load "international/mule-util")
         (load "international/ucs-normalize")
         (load "term/ns-win"))))
+(if (featurep 'pgtk)
+    (progn
+      (load "term/common-win")
+      ;; Don't load ucs-normalize.el unless uni-*.el files were
+      ;; already produced, because it needs uni-*.el files that might
+      ;; not be built early enough during bootstrap.
+      (when (or (string-prefix-p "3.98" gtk-version-string)
+                (string-prefix-p "4." gtk-version-string))
+        (load "touch-screen"))
+      (load "emacs-lisp/subr-x")
+      (load "term/pgtk-win")))
 (if (fboundp 'x-create-frame)
     ;; Do it after loading term/foo-win.el since the value of the
     ;; mouse-wheel-*-event vars depends on those files being loaded or not.
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 731da193ef..ca1eb3e18c 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -2450,6 +2450,7 @@ toggle-menu-bar-mode-from-frame
 
 (declare-function x-menu-bar-open "term/x-win" (&optional frame))
 (declare-function w32-menu-bar-open "term/w32-win" (&optional frame))
+(declare-function pgtk-menu-bar-open "term/pgtk-win" (&optional frame))
 
 (defun lookup-key-ignore-too-long (map key)
   "Call `lookup-key' and convert numeric values to nil."
@@ -2584,6 +2585,7 @@ menu-bar-open
    (list nil (prefix-numeric-value current-prefix-arg)))
   (let ((type (framep (or frame (selected-frame)))))
     (cond
+     ((eq type 'pgtk) (display-gtk-header-menu))
      ((eq type 'x) (x-menu-bar-open frame))
      ((eq type 'w32) (w32-menu-bar-open frame))
      ((and (null tty-menu-open-use-tmm)
diff --git a/lisp/mwheel.el b/lisp/mwheel.el
index 317f2cd8ed..2fbd1e907c 100644
--- a/lisp/mwheel.el
+++ b/lisp/mwheel.el
@@ -52,7 +52,7 @@ mouse-wheel-change-button
   (when (bound-and-true-p mouse-wheel-mode) (mouse-wheel-mode 1)))
 
 (defcustom mouse-wheel-down-event
-  (if (or (featurep 'w32-win) (featurep 'ns-win))
+  (if (or (featurep 'w32-win) (featurep 'ns-win) (featurep 'pgtk))
       'wheel-up
     'mouse-4)
   "Event used for scrolling down."
@@ -61,7 +61,7 @@ mouse-wheel-down-event
   :set 'mouse-wheel-change-button)
 
 (defcustom mouse-wheel-up-event
-  (if (or (featurep 'w32-win) (featurep 'ns-win))
+  (if (or (featurep 'w32-win) (featurep 'ns-win) (featurep 'pgtk))
       'wheel-down
     'mouse-5)
   "Event used for scrolling up."
@@ -206,13 +206,13 @@ mwheel-scroll-right-function
   "Function that does the job of scrolling right.")
 
 (defvar mouse-wheel-left-event
-  (if (or (featurep 'w32-win) (featurep 'ns-win))
+  (if (or (featurep 'w32-win) (featurep 'ns-win) (featurep 'pgtk))
       'wheel-left
     (intern "mouse-6"))
   "Event used for scrolling left.")
 
 (defvar mouse-wheel-right-event
-  (if (or (featurep 'w32-win) (featurep 'ns-win))
+  (if (or (featurep 'w32-win) (featurep 'ns-win) (featurep 'pgtk))
       'wheel-right
     (intern "mouse-7"))
   "Event used for scrolling right.")
@@ -272,57 +272,76 @@ mwheel-scroll
       ;; So by adding things up we get a squaring up (1, 3, 6, 10, 15, ...).
       (setq amt (* amt (event-click-count event))))
     (when (numberp amt) (setq amt (* amt (event-line-count event))))
-    (condition-case nil
-        (unwind-protect
-	    (let ((button (mwheel-event-button event)))
-	      (cond ((eq button mouse-wheel-down-event)
-                     (condition-case nil (funcall mwheel-scroll-down-function amt)
-                       ;; Make sure we do indeed scroll to the beginning of
-                       ;; the buffer.
-                       (beginning-of-buffer
-                        (unwind-protect
-                            (funcall mwheel-scroll-down-function)
-                          ;; If the first scroll succeeded, then some scrolling
-                          ;; is possible: keep scrolling til the beginning but
-                          ;; do not signal an error.  For some reason, we have
-                          ;; to do it even if the first scroll signaled an
-                          ;; error, because otherwise the window is recentered
-                          ;; for a reason that escapes me.  This problem seems
-                          ;; to only affect scroll-down.  --Stef
-                          (set-window-start (selected-window) (point-min))))))
-		    ((eq button mouse-wheel-up-event)
-                     (condition-case nil (funcall mwheel-scroll-up-function amt)
-                       ;; Make sure we do indeed scroll to the end of the buffer.
-                       (end-of-buffer (while t (funcall mwheel-scroll-up-function)))))
-                    ((eq button mouse-wheel-left-event) ; for tilt scroll
-                     (when mouse-wheel-tilt-scroll
-                       (funcall (if mouse-wheel-flip-direction
-                                    mwheel-scroll-right-function
-                                  mwheel-scroll-left-function) amt)))
-                    ((eq button mouse-wheel-right-event) ; for tilt scroll
-                     (when mouse-wheel-tilt-scroll
-                       (funcall (if mouse-wheel-flip-direction
-                                    mwheel-scroll-left-function
-                                  mwheel-scroll-right-function) amt)))
-		    (t (error "Bad binding in mwheel-scroll"))))
-          (if (eq scroll-window selected-window)
-              ;; If there is a temporarily active region, deactivate it if
-              ;; scrolling moved point.
-	      (when (and old-point (/= old-point (window-point)))
-                ;; Call `deactivate-mark' at the original position, so that
-                ;; the original region is saved to the X selection.
-	        (let ((new-point (window-point)))
-	          (goto-char old-point)
-	          (deactivate-mark)
-	          (goto-char new-point)))
-	    (select-window selected-window t)))
-      ;; Do not ding at buffer limits.  Show a message instead.
-      (beginning-of-buffer
-       (message (error-message-string '(beginning-of-buffer)))
-       (setq saw-error t))
-      (end-of-buffer
-       (message (error-message-string '(end-of-buffer)))
-       (setq saw-error t)))
+    (when (eq (window-system) 'pgtk)
+      (setq amt (* pgtk-scroll-step-count (event-line-count event))))
+    (let ((button (mwheel-event-button event)))
+      (if (and (featurep 'pgtk)
+               (or (eq button mouse-wheel-down-event)
+                   (eq button mouse-wheel-up-event))
+               pgtk-use-pixel-scroll)
+          (pgtk-pixel-scroll-up
+           (cond
+            ((eq button mouse-wheel-down-event)
+             (pgtk-pixel-scroll-up -4))
+            ((eq button mouse-wheel-up-event)
+             (pgtk-pixel-scroll-up 2))))
+        (condition-case nil
+            (unwind-protect
+	        (let ((button (mwheel-event-button event)))
+	          (cond ((eq button mouse-wheel-down-event)
+                         (condition-case nil (funcall mwheel-scroll-down-function amt)
+                           ;; Make sure we do indeed scroll to the beginning of
+                           ;; the buffer.
+                           (beginning-of-buffer
+                            (unwind-protect
+                                (funcall mwheel-scroll-down-function)
+                              ;; If the first scroll succeeded, then some scrolling
+                              ;; is possible: keep scrolling til the beginning but
+                              ;; do not signal an error.  For some reason, we have
+                              ;; to do it even if the first scroll signaled an
+                              ;; error, because otherwise the window is recentered
+                              ;; for a reason that escapes me.  This problem seems
+                              ;; to only affect scroll-down.  --Stef
+                              (set-window-start (selected-window) (point-min))))))
+		        ((eq button mouse-wheel-up-event)
+                         (condition-case nil (funcall mwheel-scroll-up-function amt)
+                           ;; Make sure we do indeed scroll to the end of the buffer.
+                           (end-of-buffer (while t (funcall mwheel-scroll-up-function)))))
+                        ((eq button mouse-wheel-left-event) ; for tilt scroll
+                         (when mouse-wheel-tilt-scroll
+                           (funcall (if (or mouse-wheel-flip-direction
+                                            (and (featurep 'pgtk)
+                                                 (window-system)
+                                                 pgtk-last-event-from-touchscreen))
+                                        mwheel-scroll-right-function
+                                      mwheel-scroll-left-function) amt)))
+                        ((eq button mouse-wheel-right-event) ; for tilt scroll
+                         (when mouse-wheel-tilt-scroll
+                           (funcall (if (or mouse-wheel-flip-direction
+                                            (and (featurep 'pgtk)
+                                                 (window-system)
+                                                 pgtk-last-event-from-touchscreen))
+                                        mwheel-scroll-left-function
+                                      mwheel-scroll-right-function) amt)))
+		        (t (error "Bad binding in mwheel-scroll"))))
+              (if (eq scroll-window selected-window)
+                  ;; If there is a temporarily active region, deactivate it if
+                  ;; scrolling moved point.
+	          (when (and old-point (/= old-point (window-point)))
+                    ;; Call `deactivate-mark' at the original position, so that
+                    ;; the original region is saved to the X selection.
+	            (let ((new-point (window-point)))
+	              (goto-char old-point)
+	              (deactivate-mark)
+	              (goto-char new-point)))
+	        (select-window selected-window t)))
+          ;; Do not ding at buffer limits.  Show a message instead.
+          (beginning-of-buffer
+           (message (error-message-string '(beginning-of-buffer)))
+           (setq saw-error t))
+          (end-of-buffer
+           (message (error-message-string '(end-of-buffer)))
+           (setq saw-error t)))))
 
     (when (and (not saw-error)
                mouse-wheel-click-event mouse-wheel-inhibit-click-time)
diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el
index 7aad44b287..a745eeb6c0 100644
--- a/lisp/net/browse-url.el
+++ b/lisp/net/browse-url.el
@@ -797,8 +797,17 @@ browse-url
     ;; When connected to various displays, be careful to use the display of
     ;; the currently selected frame, rather than the original start display,
     ;; which may not even exist any more.
-    (if (stringp (frame-parameter nil 'display))
-        (setenv "DISPLAY" (frame-parameter nil 'display)))
+    (let ((dpy (frame-parameter nil 'display))
+          classname)
+      (cond
+       ((featurep 'pgtk)
+        (setq classname (pgtk-backend-display-class))
+        (if (equal classname "GdkWaylandDisplay")
+            (setenv "WAYLAND_DISPLAY" dpy)
+          (setenv "DISPLAY" dpy)))
+       (t
+        (setenv "DISPLAY" dpy))))
+
     (if (and (consp function)
 	     (not (functionp function)))
 	;; The `function' can be an alist; look down it for first match
diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index c83884fd25..9c88e1f0b1 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -166,7 +166,7 @@ eww-form-checkbox-symbol
                  string))
 
 (defface eww-form-submit
-  '((((type x w32 ns) (class color))	; Like default mode line
+  '((((type x w32 ns pgtk) (class color))	; Like default mode line
      :box (:line-width 2 :style released-button)
      :background "#808080" :foreground "black"))
   "Face for eww buffer buttons."
@@ -174,7 +174,7 @@ eww-form-submit
   :group 'eww)
 
 (defface eww-form-file
-  '((((type x w32 ns) (class color))	; Like default mode line
+  '((((type x w32 ns pgtk) (class color))	; Like default mode line
      :box (:line-width 2 :style released-button)
      :background "#808080" :foreground "black"))
   "Face for eww buffer buttons."
@@ -182,7 +182,7 @@ eww-form-file
   :group 'eww)
 
 (defface eww-form-checkbox
-  '((((type x w32 ns) (class color))	; Like default mode line
+  '((((type x w32 ns pgtk) (class color))	; Like default mode line
      :box (:line-width 2 :style released-button)
      :background "lightgrey" :foreground "black"))
   "Face for eww buffer buttons."
@@ -190,7 +190,7 @@ eww-form-checkbox
   :group 'eww)
 
 (defface eww-form-select
-  '((((type x w32 ns) (class color))	; Like default mode line
+  '((((type x w32 ns pgtk) (class color))	; Like default mode line
      :box (:line-width 2 :style released-button)
      :background "lightgrey" :foreground "black"))
   "Face for eww buffer buttons."
diff --git a/lisp/scroll-bar.el b/lisp/scroll-bar.el
index 5b82934579..ef7dfcb61d 100644
--- a/lisp/scroll-bar.el
+++ b/lisp/scroll-bar.el
@@ -427,7 +427,8 @@ scroll-bar-toolkit-scroll
 	  (recenter))
 	 ((eq part 'handle)
 	  (scroll-bar-drag-1 event))))
-      (sit-for 0)
+      (when (not (featurep 'pgtk))
+        (sit-for 0))
       (with-current-buffer (window-buffer window)
 	(setq point-before-scroll before-scroll))))))
 
diff --git a/lisp/startup.el b/lisp/startup.el
index bff10003f8..30d29febef 100644
--- a/lisp/startup.el
+++ b/lisp/startup.el
@@ -411,6 +411,12 @@ auto-save-list-file-prefix
 		 string)
   :group 'auto-save)
 
+(when (featurep 'gtk)
+  (defcustom display-gtk-about-screen t
+    "Whether or not to use a native GTK about dialog."
+    :type 'boolean
+    :group 'initialization))
+
 (defvar emacs-basic-display nil)
 
 (defvar init-file-debug nil)
@@ -1317,7 +1323,7 @@ command-line
   ;; only because all other settings of no-blinking-cursor are here.
   (unless (or noninteractive
 	      emacs-basic-display
-	      (and (memq window-system '(x w32 ns))
+	      (and (memq window-system '(x w32 ns pgtk))
 		   (not (member (x-get-resource "cursorBlink" "CursorBlink")
 				'("no" "off" "false" "0")))))
     (setq no-blinking-cursor t))
@@ -1967,6 +1973,8 @@ fancy-splash-frame
     ;; frame visible.
     (if (eq (window-system) 'w32)
 	(sit-for 0 t))
+    (if (eq (window-system) 'pgtk)
+	(sit-for 0.1 t))
     (dolist (frame (append (frame-list) (list (selected-frame))))
       (if (and (frame-visible-p frame)
 	       (not (window-minibuffer-p (frame-selected-window frame))))
@@ -2305,9 +2313,12 @@ display-about-screen
   "Display the *About GNU Emacs* buffer.
 A fancy display is used on graphic displays, normal otherwise."
   (interactive)
-  (if (use-fancy-splash-screens-p)
-      (fancy-about-screen)
-    (normal-splash-screen nil)))
+  (if (and window-system
+           (featurep 'pgtk))
+      (display-gtk-about-screen (selected-frame))
+    (if (use-fancy-splash-screens-p)
+        (fancy-about-screen)
+      (normal-splash-screen nil))))
 
 (defalias 'about-emacs 'display-about-screen)
 (defalias 'display-splash-screen 'display-startup-screen)
diff --git a/lisp/term/pgtk-win.el b/lisp/term/pgtk-win.el
new file mode 100644
index 0000000000..9a5aae5720
--- /dev/null
+++ b/lisp/term/pgtk-win.el
@@ -0,0 +1,628 @@
+;;; pgtk-win.el --- GTK terminal setup  -*- lexical-binding:t -*-
+
+;; Copyright (C) 1993-1994, 2001-2020 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; GTK windowing setup
+
+;;; Code:
+\f
+;; These are the standard X switches from the Xt Initialize.c file of
+;; Release 4.
+
+;; Command line		Resource Manager string
+
+;; +rv			*reverseVideo
+;; +synchronous		*synchronous
+;; -background		*background
+;; -bd			*borderColor
+;; -bg			*background
+;; -bordercolor		*borderColor
+;; -borderwidth		.borderWidth
+;; -bw			.borderWidth
+;; -display		.display
+;; -fg			*foreground
+;; -fn			*font
+;; -font		*font
+;; -foreground		*foreground
+;; -geometry		.geometry
+;; -iconic		.iconic
+;; -name		.name
+;; -reverse		*reverseVideo
+;; -rv			*reverseVideo
+;; -selectionTimeout    .selectionTimeout
+;; -synchronous		*synchronous
+;; -xrm
+
+;; An alist of X options and the function which handles them.  See
+;; ../startup.el.
+
+;;; Code:
+(eval-when-compile (require 'cl-lib))
+(or (featurep 'pgtk)
+    (error "%s: Loading pgtk-win.el but not compiled for pure Gtk+-3" invocation-name))
+
+;; Documentation-purposes only: actually loaded in loadup.el.
+(require 'term/common-win)
+(require 'frame)
+(require 'mouse)
+(require 'scroll-bar)
+(require 'faces)
+(require 'menu-bar)
+(require 'fontset)
+(require 'dnd)
+(require 'subr-x)
+
+(defgroup pgtk nil
+  "Pure-GTK specific features."
+  :group 'environment)
+
+;;;; Command line argument handling.
+
+(defvar x-invocation-args)
+;; Set in term/common-win.el; currently unused by Gtk's x-open-connection.
+(defvar x-command-line-resources)
+
+;; pgtkterm.c.
+(defvar pgtk-input-file)
+
+(defun pgtk-handle-nxopen (_switch &optional temp)
+  "Handle command open with _SWITCH and TEMP."
+  (setq unread-command-events (append unread-command-events
+                                      (if temp '(pgtk-open-temp-file)
+                                        '(pgtk-open-file)))
+        pgtk-input-file (append pgtk-input-file (list (pop x-invocation-args)))))
+
+(defun pgtk-handle-nxopentemp (switch)
+  "Handle command open with SWITCH and as a temporary file."
+  (pgtk-handle-nxopen switch t))
+
+(defun pgtk-ignore-1-arg (_switch)
+  "Ignore the first argument in `x-invocation-args'."
+  (setq x-invocation-args (cdr x-invocation-args)))
+
+;;;; Keyboard mapping.
+
+(define-obsolete-variable-alias 'pgtk-alternatives-map 'x-alternatives-map "24.1")
+
+(define-key global-map [home] 'beginning-of-buffer)
+(define-key global-map [end] 'end-of-buffer)
+(define-key global-map [kp-home] 'beginning-of-buffer)
+(define-key global-map [kp-end] 'end-of-buffer)
+(define-key global-map [kp-prior] 'scroll-down-command)
+(define-key global-map [kp-next] 'scroll-up-command)
+
+;;;; File handling.
+
+(defcustom pgtk-pop-up-frames 'fresh
+  "Non-nil means open files upon request from the Workspace in a new frame.
+If t, always do so.  Any other non-nil value means open a new frame
+unless the current buffer is a scratch buffer."
+  :type '(choice (const :tag "Never" nil)
+                 (const :tag "Always" t)
+                 (other :tag "Except for scratch buffer" fresh))
+  :version "23.1"
+  :group 'pgtk)
+
+(declare-function pgtk-hide-emacs "pgtkfns.c" (on))
+
+
+(defun pgtk-drag-n-drop (event &optional new-frame force-text)
+  "Edit the files listed in the drag-n-drop EVENT.
+Creates a new frame if NEW-FRAME is set.
+Forces text if FORCE-TEXT is set.
+Switch to a buffer editing the last file dropped."
+  (interactive "e")
+  (let* ((window (posn-window (event-start event)))
+         (arg (car (cdr (cdr event))))
+         (type (car arg))
+         (data (car (cdr arg)))
+         (url-or-string (cond ((eq type 'file)
+                               (concat "file:" data))
+                              (t data))))
+    (set-frame-selected-window nil window)
+    (when new-frame
+      (select-frame (make-frame)))
+    (raise-frame)
+    (setq window (selected-window))
+    (if force-text
+        (dnd-insert-text window 'private data)
+      (dnd-handle-one-url window 'private (string-trim url-or-string)))))
+
+
+(defun pgtk-drag-n-drop-other-frame (event)
+  "Edit the files listed in the drag-n-drop EVENT, in other frames.
+May create new frames, or reuse existing ones.  The frame editing
+the last file dropped is selected."
+  (interactive "e")
+  (pgtk-drag-n-drop event t))
+
+(defun pgtk-drag-n-drop-as-text (event)
+  "Drop the data in EVENT as text."
+  (interactive "e")
+  (pgtk-drag-n-drop event nil t))
+
+(defun pgtk-drag-n-drop-as-text-other-frame (event)
+  "Drop the data in EVENT as text in a new frame."
+  (interactive "e")
+  (pgtk-drag-n-drop event t t))
+
+(global-set-key [drag-n-drop] 'pgtk-drag-n-drop)
+(global-set-key [C-drag-n-drop] 'pgtk-drag-n-drop-other-frame)
+(global-set-key [M-drag-n-drop] 'pgtk-drag-n-drop-as-text)
+(global-set-key [C-M-drag-n-drop] 'pgtk-drag-n-drop-as-text-other-frame)
+
+;; Frame will be focused anyway, so select it
+;; (if this is not done, mode line is dimmed until first interaction)
+;; FIXME: Sounds like we're working around a bug in the underlying code.
+(add-hook 'after-make-frame-functions 'select-frame)
+
+(defvar tool-bar-mode)
+(declare-function tool-bar-mode "tool-bar" (&optional arg))
+
+;; Based on a function by David Reitter <dreitter@inf.ed.ac.uk> ;
+;; see https://lists.gnu.org/archive/html/emacs-devel/2005-09/msg00681.html .
+(defun pgtk-toggle-toolbar (&optional frame)
+  "Switch the tool bar on and off in frame FRAME.
+If FRAME is nil, the change applies to the selected frame."
+  (interactive)
+  (modify-frame-parameters
+   frame (list (cons 'tool-bar-lines
+		       (if (> (or (frame-parameter frame 'tool-bar-lines) 0) 0)
+				   0 1)) ))
+  (if (not tool-bar-mode) (tool-bar-mode t)))
+
+
+;;;; Dialog-related functions.
+
+;; Ask user for confirm before printing.  Due to Kevin Rodgers.
+(defun pgtk-print-buffer ()
+  "Interactive front-end to `print-buffer': asks for user confirmation first."
+  (interactive)
+  (if (and (called-interactively-p 'interactive)
+           (or (listp last-nonmenu-event)
+               (and (char-or-string-p (event-basic-type last-command-event))
+                    (memq 'super (event-modifiers last-command-event)))))
+      (let ((last-nonmenu-event (if (listp last-nonmenu-event)
+                                    last-nonmenu-event
+                                  ;; Fake it:
+                                  `(mouse-1 POSITION 1))))
+        (if (y-or-n-p (format "Print buffer %s? " (buffer-name)))
+            (print-buffer)
+	  (error "Canceled")))
+    (print-buffer)))
+
+;;;; Font support.
+
+;; Needed for font listing functions under both backend and normal
+(setq scalable-fonts-allowed t)
+
+
+;; pgtkterm.c
+(defvar pgtk-input-font)
+(defvar pgtk-input-fontsize)
+
+(defun pgtk-respond-to-change-font ()
+  "Respond to changeFont: event, expecting `pgtk-input-font' and\n\
+`pgtk-input-fontsize' of new font."
+  (interactive)
+  (modify-frame-parameters (selected-frame)
+                           (list (cons 'fontsize pgtk-input-fontsize)))
+  (modify-frame-parameters (selected-frame)
+                           (list (cons 'font pgtk-input-font)))
+  (set-frame-font pgtk-input-font))
+
+
+;; Default fontset.  This is mainly here to show how a fontset
+;; can be set up manually.  Ordinarily, fontsets are auto-created whenever
+;; a font is chosen by
+(defvar pgtk-standard-fontset-spec
+  ;; Only some code supports this so far, so use uglier XLFD version
+  ;; "-pgtk-*-*-*-*-*-10-*-*-*-*-*-fontset-standard,latin:Courier,han:Kai"
+  (mapconcat 'identity
+             '("-*-Monospace-*-*-*-*-10-*-*-*-*-*-fontset-standard"
+               "latin:-*-Courier-*-*-*-*-10-*-*-*-*-*-iso10646-1"
+               "han:-*-Kai-*-*-*-*-10-*-*-*-*-*-iso10646-1"
+               "cyrillic:-*-Trebuchet$MS-*-*-*-*-10-*-*-*-*-*-iso10646-1")
+             ",")
+  "String of fontset spec of the standard fontset.
+This defines a fontset consisting of the Courier and other fonts.
+See the documentation of `create-fontset-from-fontset-spec' for the format.")
+
+
+;;;; Pasteboard support.
+
+(define-obsolete-function-alias 'pgtk-store-cut-buffer-internal
+  'gui-set-selection "24.1")
+
+
+(defun pgtk-copy-including-secondary ()
+  (interactive)
+  (call-interactively 'kill-ring-save)
+  (gui-set-selection 'SECONDARY (buffer-substring (point) (mark t))))
+
+(defun pgtk-paste-secondary ()
+  (interactive)
+  (insert (gui-get-selection 'SECONDARY)))
+
+
+;;;; Color support.
+
+;; Functions for color panel + drag
+(defun pgtk-face-at-pos (pos)
+  (let* ((frame (car pos))
+         (frame-pos (cons (cadr pos) (cddr pos)))
+         (window (window-at (car frame-pos) (cdr frame-pos) frame))
+         (window-pos (coordinates-in-window-p frame-pos window))
+         (buffer (window-buffer window))
+         (edges (window-edges window)))
+    (cond
+     ((not window-pos)
+      nil)
+     ((eq window-pos 'mode-line)
+      'mode-line)
+     ((eq window-pos 'vertical-line)
+      'default)
+     ((consp window-pos)
+      (with-current-buffer buffer
+        (let ((p (car (compute-motion (window-start window)
+                                      (cons (nth 0 edges) (nth 1 edges))
+                                      (window-end window)
+                                      frame-pos
+                                      (- (window-width window) 1)
+                                      nil
+                                      window))))
+          (cond
+           ((eq p (window-point window))
+            'cursor)
+           ((and mark-active (< (region-beginning) p) (< p (region-end)))
+            'region)
+           (t
+	    (let ((faces (get-char-property p 'face window)))
+	      (if (consp faces) (car faces) faces)))))))
+     (t
+      nil))))
+
+(defun pgtk-suspend-error ()
+  ;; Don't allow suspending if any of the frames are PGTK frames.
+  (if (memq 'pgtk (mapcar 'window-system (frame-list)))
+      (error "Cannot suspend Emacs while a PGTK GUI frame exists")))
+
+
+;; Set some options to be as Nextstep-like as possible.
+(setq frame-title-format t
+      icon-title-format t)
+
+
+(defvar pgtk-initialized nil
+  "Non-nil if pure-GTK windowing has been initialized.")
+
+(declare-function x-handle-args "common-win" (args))
+(declare-function x-open-connection "pgtkfns.c"
+                  (display &optional xrm-string must-succeed))
+(declare-function pgtk-set-resource "pgtkfns.c" (owner name value))
+
+;; Do the actual pure-GTK Windows setup here; the above code just
+;; defines functions and variables that we use now.
+(cl-defmethod window-system-initialization (&context (window-system pgtk)
+                                            &optional display)
+  "Initialize Emacs for pure-GTK windowing."
+  (cl-assert (not pgtk-initialized))
+
+  ;; PENDING: not needed?
+  (setq command-line-args (x-handle-args command-line-args))
+
+  ;; Make sure we have a valid resource name.
+  (or (stringp x-resource-name)
+      (let (i)
+	(setq x-resource-name invocation-name)
+
+	;; Change any . or * characters in x-resource-name to hyphens,
+	;; so as not to choke when we use it in X resource queries.
+	(while (setq i (string-match "[.*]" x-resource-name))
+	  (aset x-resource-name i ?-))))
+
+  ;; Setup the default fontset.
+  (create-default-fontset)
+  ;; Create the standard fontset.
+  (condition-case err
+      (create-fontset-from-fontset-spec pgtk-standard-fontset-spec t)
+    (error (display-warning
+            'initialization
+            (format "Creation of the standard fontset failed: %s" err)
+            :error)))
+
+  (x-open-connection (or display
+                         x-display-name)
+		     x-command-line-resources
+		     ;; Exit Emacs with fatal error if this fails and we
+		     ;; are the initial display.
+                     (= (length (frame-list)) 0))
+
+  ;; FIXME: This will surely lead to "MODIFIED OUTSIDE CUSTOM" warnings.
+  ; (menu-bar-mode (if (get-lisp-resource nil "Menus") 1 -1))
+
+  ;; Mac OS X Lion introduces PressAndHold, which is unsupported by this port.
+  ;; See this thread for more details:
+  ;; https://lists.gnu.org/archive/html/emacs-devel/2011-06/msg00505.html
+  ; (pgtk-set-resource nil "ApplePressAndHoldEnabled" "NO")
+
+  (x-apply-session-resources)
+
+  ;; Don't let Emacs suspend under PGTK.
+  (add-hook 'suspend-hook 'pgtk-suspend-error)
+
+  (setq pgtk-initialized t))
+
+;; Any display name is OK.
+(add-to-list 'display-format-alist '(".*" . pgtk))
+(cl-defmethod handle-args-function (args &context (window-system pgtk))
+  (x-handle-args args))
+
+(cl-defmethod frame-creation-function (params &context (window-system pgtk))
+  (x-create-frame-with-faces params))
+
+(declare-function pgtk-own-selection-internal "pgtkselect.c" (selection value &optional frame))
+(declare-function pgtk-disown-selection-internal "pgtkselect.c" (selection &optional time_object terminal))
+(declare-function pgtk-selection-owner-p "pgtkselect.c" (&optional selection terminal))
+(declare-function pgtk-selection-exists-p "pgtkselect.c" (&optional selection terminal))
+(declare-function pgtk-get-selection-internal "pgtkselect.c" (selection-symbol target-type &optional time_stamp terminal))
+
+(cl-defmethod gui-backend-set-selection (selection value
+                                         &context (window-system pgtk))
+  (if value (pgtk-own-selection-internal selection value)
+    (pgtk-disown-selection-internal selection)))
+
+(cl-defmethod gui-backend-selection-owner-p (selection
+                                             &context (window-system pgtk))
+  (pgtk-selection-owner-p selection))
+
+(cl-defmethod gui-backend-selection-exists-p (selection
+                                              &context (window-system pgtk))
+  (pgtk-selection-exists-p selection))
+
+(cl-defmethod gui-backend-get-selection (selection-symbol target-type
+                                         &context (window-system pgtk))
+  (pgtk-get-selection-internal selection-symbol target-type))
+
+
+(defvar pgtk-preedit-overlay nil)
+
+(defun pgtk-preedit-text (e)
+  (interactive "e")
+  (when pgtk-preedit-overlay
+    (delete-overlay pgtk-preedit-overlay))
+  (setq pgtk-preedit-overlay nil)
+
+  (let ((ovstr "")
+        (idx 0)
+        atts ov str color face-name)
+    (dolist (part (nth 1 e))
+      (setq str (car part))
+      (setq face-name (intern (format "pgtk-im-%d" idx)))
+      (eval
+       `(defface ,face-name nil "face of input method preedit"))
+      (setq atts nil)
+      (when (setq color (cdr-safe (assq 'fg (cdr part))))
+        (setq atts (append atts `(:foreground ,color))))
+      (when (setq color (cdr-safe (assq 'bg (cdr part))))
+        (setq atts (append atts `(:background ,color))))
+      (when (setq color (cdr-safe (assq 'ul (cdr part))))
+        (setq atts (append atts `(:underline ,color))))
+      (face-spec-set face-name `((t . ,atts)))
+      (add-text-properties 0 (length str) `(face ,face-name) str)
+      (setq ovstr (concat ovstr str))
+      (setq idx (1+ idx)))
+
+    (setq ov (make-overlay (point) (point)))
+    (overlay-put ov 'before-string ovstr)
+    (setq pgtk-preedit-overlay ov)))
+
+(defconst x-gtk-stock-cache (make-hash-table :weakness t :test 'equal))
+
+(defcustom x-gtk-stock-map
+  (mapcar (lambda (arg)
+	    (cons (purecopy (car arg)) (purecopy (cdr arg))))
+  '(
+    ("etc/images/new" . ("document-new" "gtk-new"))
+    ("etc/images/open" . ("document-open" "gtk-open"))
+    ("etc/images/diropen" . "n:system-file-manager")
+    ("etc/images/close" . ("window-close" "gtk-close"))
+    ("etc/images/save" . ("document-save" "gtk-save"))
+    ("etc/images/saveas" . ("document-save-as" "gtk-save-as"))
+    ("etc/images/undo" . ("edit-undo" "gtk-undo"))
+    ("etc/images/cut" . ("edit-cut" "gtk-cut"))
+    ("etc/images/copy" . ("edit-copy" "gtk-copy"))
+    ("etc/images/paste" . ("edit-paste" "gtk-paste"))
+    ("etc/images/search" . ("edit-find" "gtk-find"))
+    ("etc/images/search-replace" . ("edit-find-replace" "gtk-find-and-replace"))
+    ("etc/images/print" . ("document-print" "gtk-print"))
+    ("etc/images/preferences" . ("preferences-system" "gtk-preferences"))
+    ("etc/images/help" . ("help-browser" "gtk-help"))
+    ("etc/images/left-arrow" . ("go-previous" "gtk-go-back"))
+    ("etc/images/right-arrow" . ("go-next" "gtk-go-forward"))
+    ("etc/images/home" . ("go-home" "gtk-home"))
+    ("etc/images/jump-to" . ("go-jump" "gtk-jump-to"))
+    ("etc/images/index" . "gtk-index")
+    ("etc/images/exit" . ("application-exit" "gtk-quit"))
+    ("etc/images/cancel" . "gtk-cancel")
+    ("etc/images/info" . ("dialog-information" "gtk-info"))
+    ("etc/images/bookmark_add" . "n:bookmark_add")
+    ;; Used in Gnus and/or MH-E:
+    ("etc/images/attach" . "gtk-attach")
+    ("etc/images/connect" . "gtk-connect")
+    ("etc/images/contact" . "gtk-contact")
+    ("etc/images/delete" . ("edit-delete" "gtk-delete"))
+    ("etc/images/describe" . ("document-properties" "gtk-properties"))
+    ("etc/images/disconnect" . "gtk-disconnect")
+    ;; ("etc/images/exit" . "gtk-exit")
+    ("etc/images/lock-broken" . "gtk-lock_broken")
+    ("etc/images/lock-ok" . "gtk-lock_ok")
+    ("etc/images/lock" . "gtk-lock")
+    ("etc/images/next-page" . "gtk-next-page")
+    ("etc/images/refresh" . ("view-refresh" "gtk-refresh"))
+    ("etc/images/sort-ascending" . ("view-sort-ascending" "gtk-sort-ascending"))
+    ("etc/images/sort-column-ascending" . "gtk-sort-column-ascending")
+    ("etc/images/sort-criteria" . "gtk-sort-criteria")
+    ("etc/images/sort-descending" . ("view-sort-descending"
+				     "gtk-sort-descending"))
+    ("etc/images/sort-row-ascending" . "gtk-sort-row-ascending")
+    ("images/gnus/toggle-subscription" . "gtk-task-recurring")
+    ("images/mail/compose" . "gtk-mail-compose")
+    ("images/mail/copy" . "gtk-mail-copy")
+    ("images/mail/forward" . "gtk-mail-forward")
+    ("images/mail/inbox" . "gtk-inbox")
+    ("images/mail/move" . "gtk-mail-move")
+    ("images/mail/not-spam" . "gtk-not-spam")
+    ("images/mail/outbox" . "gtk-outbox")
+    ("images/mail/reply-all" . "gtk-mail-reply-to-all")
+    ("images/mail/reply" . "gtk-mail-reply")
+    ("images/mail/save-draft" . "gtk-mail-handling")
+    ("images/mail/send" . "gtk-mail-send")
+    ("images/mail/spam" . "gtk-spam")
+    ;; Used for GDB Graphical Interface
+    ("images/gud/break" . "gtk-no")
+    ("images/gud/recstart" . ("media-record" "gtk-media-record"))
+    ("images/gud/recstop" . ("media-playback-stop" "gtk-media-stop"))
+    ;; No themed versions available:
+    ;; mail/preview (combining stock_mail and stock_zoom)
+    ;; mail/save    (combining stock_mail, stock_save and stock_convert)
+    ))
+  "How icons for tool bars are mapped to Gtk+ stock items.
+Emacs must be compiled with the Gtk+ toolkit for this to have any effect.
+A value that begins with n: denotes a named icon instead of a stock icon."
+  :version "22.2"
+  :type '(choice (repeat
+		  (choice symbol
+			  (cons (string :tag "Emacs icon")
+				(choice (group (string :tag "Named")
+					       (string :tag "Stock"))
+					(string :tag "Stock/named"))))))
+  :group 'x)
+
+(defcustom icon-map-list '(x-gtk-stock-map)
+  "A list of alists that map icon file names to stock/named icons.
+The alists are searched in the order they appear.  The first match is used.
+The keys in the alists are file names without extension and with two directory
+components.  For example, to map /usr/share/emacs/22.1.1/etc/images/open.xpm
+to stock item gtk-open, use:
+
+  (\"etc/images/open\" . \"gtk-open\")
+
+Themes also have named icons.  To map to one of those, use n: before the name:
+
+  (\"etc/images/diropen\" . \"n:system-file-manager\")
+
+The list elements are either the symbol name for the alist or the
+alist itself.
+
+If you don't want stock icons, set the variable to nil."
+  :version "22.2"
+  :type '(choice (const :tag "Don't use stock icons" nil)
+		 (repeat (choice symbol
+				 (cons (string :tag "Emacs icon")
+				       (string :tag "Stock/named")))))
+  :group 'x)
+
+(defun x-gtk-map-stock (file)
+  "Map icon with file name FILE to a Gtk+ stock name.
+This uses `icon-map-list' to map icon file names to stock icon names."
+  (when (stringp file)
+    (or (gethash file x-gtk-stock-cache)
+	(puthash
+	 file
+	 (save-match-data
+	   (let* ((file-sans (file-name-sans-extension file))
+		  (key (and (string-match "/\\([^/]+/[^/]+/[^/]+$\\)"
+					  file-sans)
+			    (match-string 1 file-sans)))
+		  (icon-map icon-map-list)
+		  elem value)
+	     (while (and (null value) icon-map)
+	       (setq elem (car icon-map)
+		     value (assoc-string (or key file-sans)
+					 (if (symbolp elem)
+					     (symbol-value elem)
+					   elem))
+		     icon-map (cdr icon-map)))
+	     (and value (cdr value))))
+	 x-gtk-stock-cache))))
+
+(when (fboundp 'pgtk-color-dialog)
+  (defun pgtk-read-color (&optional prompt msg)
+    "Read a color name or RGB triplet.
+Completion is available for color names, but not for RGB triplets.
+
+RGB triplets have the form \"#RRGGBB\".  Each of the R, G, and B
+components can have one to four digits, but all three components
+must have the same number of digits.  Each digit is a hex value
+between 0 and F; either upper case or lower case for A through F
+are acceptable.
+
+In addition to standard color names and RGB hex values, the
+following are available as color candidates.  In each case, the
+corresponding color is used.
+
+ * `foreground at point'   - foreground under the cursor
+ * `background at point'   - background under the cursor
+
+Optional arg PROMPT is the prompt; if nil, use a default prompt.
+
+Interactively, or with optional arg MSG non-nil, print the
+resulting color name in the echo area."
+    (interactive "i\np\ni\np")
+    (let* ((resulting-color
+            (pgtk-color-dialog (selected-frame)
+                               prompt
+                               nil
+                               (mapcar
+                                (lambda (the-color-item)
+                                  (cons 'color (color-name-to-rgb the-color-item)))
+                                (defined-colors)))))
+      (when resulting-color
+        (when msg
+          (message "Color: `%s'" resulting-color))
+        resulting-color))))
+
+(when (fboundp 'make-gtk-widget)
+  (cl-defun gtk-widget-clip-to-window (widget &optional (window (selected-window)))
+    "Clip the boundaries of WIDGET to WINDOW, or the selected window
+if WINDOW is nil."
+    (let ((top (window-pixel-top window))
+          (height (- (window-pixel-height window)
+                     (window-mode-line-height window)))
+          (left (window-pixel-left window))
+          (width (window-pixel-width window)))
+      (set-gtk-widget-size widget left top width height))))
+
+(defvar pgtk-scroll-step-count 1
+  "Mwheel will scroll this many steps at a time.")
+
+
+(when (or (string-prefix-p "3.98" gtk-version-string)
+          (string-prefix-p "4." gtk-version-string))
+  (require 'touch-screen)
+  (touch-screen-mode))
+
+(global-set-key (kbd "C-S-d") #'pgtk-show-inspector)
+
+(provide 'pgtk-win)
+(provide 'term/pgtk-win)
+
+;;; pgtk-win.el ends here
diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el
new file mode 100644
index 0000000000..0927f1c4c1
--- /dev/null
+++ b/lisp/touch-screen.el
@@ -0,0 +1,80 @@
+;;; touch-screen.el --- Touchscreen support for GNU Emacs. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2020 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides touch-screen support for GNU Emacs on GTK 4 terminals.
+;; This relies on several GTK 4-specific event handling changes, and is unlikely
+;; to work well on other platforms.
+
+;;; Code:
+
+(unless (and (or (string-prefix-p "3.98" gtk-version-string)
+                 (string-prefix-p "4." gtk-version-string)))
+  (error "Trying to load touch-screen.el on an incompatible terminal"))
+
+(require 'widget)
+
+(defvar touch-screen--idle-timer nil
+  "The timer that will be used for action popovers.")
+
+(defun touch-screen-pop-up-actions-frame ()
+  "Display the selection items popover."
+  (interactive)
+  (pgtk-show-selection-options (selected-frame)))
+
+(define-minor-mode touch-screen-mode
+  "A mode for touchscreen device that displays
+several graphical elements suitable for use on touchscreens."
+  :global t
+  :lighter nil)
+
+(defun touch-screen--after-mark-set ()
+  "Internal function for `touch-screen-after-mark-set'."
+  (when (and pgtk-last-event-from-touchscreen
+             touch-screen-mode
+             (region-active-p)
+             (window-system)
+             (> (abs (- (mark) (point))) 5))
+    (touch-screen-pop-up-actions-frame)))
+
+(defun touch-screen-after-mark-set ()
+  "This function will be run after the mark is set.
+It will display the pop-up frame if `pgtk-last-event-from-touchscreen'
+is non-nil."
+  (when (window-system)
+    (when touch-screen--idle-timer
+      (cancel-timer touch-screen--idle-timer))
+    (setq touch-screen--idle-timer
+          (run-with-idle-timer 1 nil #'touch-screen--after-mark-set))))
+
+(defun touch-screen--safe-kill ()
+  "Kill the region, without raising errors if it is not possible."
+  (when (not (and buffer-read-only
+                  (not inhibit-read-only)
+                  (not (get-text-property (point) 'inhibit-read-only))))
+    (unwind-protect
+        (kill-region (mark) (point))
+      nil)))
+
+(add-hook 'activate-mark-hook #'touch-screen-after-mark-set)
+(add-hook 'post-command-hook #'touch-screen-after-mark-set)
+
+(provide 'touch-screen)
+;;; touch-screen.el ends here
diff --git a/lisp/url/url-privacy.el b/lisp/url/url-privacy.el
index 716e310742..6cda0a38e7 100644
--- a/lisp/url/url-privacy.el
+++ b/lisp/url/url-privacy.el
@@ -46,6 +46,7 @@ url-setup-privacy-info
 	  (pcase (or window-system 'tty)
 	    ('x "X11")
 	    ('ns "OpenStep")
+	    ('pgtk "PureGTK")
 	    ('tty "TTY")
 	    (_ nil)))))
 
diff --git a/m4/gsettings.m4 b/m4/gsettings.m4
new file mode 100644
index 0000000000..882e6a83e7
--- /dev/null
+++ b/m4/gsettings.m4
@@ -0,0 +1,88 @@
+# Increment this whenever this file is changed.
+#serial 2
+
+dnl GLIB_GSETTINGS
+dnl Defines GSETTINGS_SCHEMAS_INSTALL which controls whether
+dnl the schema should be compiled
+dnl
+
+AC_DEFUN([GLIB_GSETTINGS],
+[
+  dnl We can't use PKG_PREREQ because that needs 0.29.
+  m4_ifndef([PKG_PROG_PKG_CONFIG],
+            [pkg.m4 version 0.28 or later is required])
+
+  m4_pattern_allow([AM_V_GEN])
+  AC_ARG_ENABLE(schemas-compile,
+                AS_HELP_STRING([--disable-schemas-compile],
+                               [Disable regeneration of gschemas.compiled on install]),
+                [case ${enableval} in
+                  yes) GSETTINGS_DISABLE_SCHEMAS_COMPILE=""  ;;
+                  no)  GSETTINGS_DISABLE_SCHEMAS_COMPILE="1" ;;
+                  *) AC_MSG_ERROR([bad value ${enableval} for --enable-schemas-compile]) ;;
+                 esac])
+  AC_SUBST([GSETTINGS_DISABLE_SCHEMAS_COMPILE])
+  PKG_PROG_PKG_CONFIG([0.16])
+  AC_SUBST(gsettingsschemadir, [${datadir}/glib-2.0/schemas])
+  AS_IF([test x$cross_compiling != xyes],
+        [PKG_CHECK_VAR([GLIB_COMPILE_SCHEMAS], [gio-2.0], [glib_compile_schemas])],
+        [AC_PATH_PROG([GLIB_COMPILE_SCHEMAS], [glib-compile-schemas])])
+  AC_SUBST(GLIB_COMPILE_SCHEMAS)
+  if test "x$GLIB_COMPILE_SCHEMAS" = "x"; then
+    ifelse([$2],,[AC_MSG_ERROR([glib-compile-schemas not found.])],[$2])
+  else
+    ifelse([$1],,[:],[$1])
+  fi
+
+  GSETTINGS_RULES='
+.PHONY : uninstall-gsettings-schemas install-gsettings-schemas clean-gsettings-schemas
+
+mostlyclean-am: clean-gsettings-schemas
+
+gsettings__enum_file = $(addsuffix .enums.xml,$(gsettings_ENUM_NAMESPACE))
+
+%.gschema.valid: %.gschema.xml $(gsettings__enum_file)
+	$(AM_V_GEN) $(GLIB_COMPILE_SCHEMAS) --strict --dry-run $(addprefix --schema-file=,$(gsettings__enum_file)) --schema-file=$< && mkdir -p [$](@D) && touch [$]@
+
+all-am: $(gsettings_SCHEMAS:.xml=.valid)
+uninstall-am: uninstall-gsettings-schemas
+install-data-am: install-gsettings-schemas
+
+.SECONDARY: $(gsettings_SCHEMAS)
+
+install-gsettings-schemas: $(gsettings_SCHEMAS) $(gsettings__enum_file)
+	@$(NORMAL_INSTALL)
+	if test -n "$^"; then \
+		test -z "$(gsettingsschemadir)" || $(MKDIR_P) "$(DESTDIR)$(gsettingsschemadir)"; \
+		$(INSTALL_DATA) $^ "$(DESTDIR)$(gsettingsschemadir)"; \
+		test -n "$(GSETTINGS_DISABLE_SCHEMAS_COMPILE)$(DESTDIR)" || $(GLIB_COMPILE_SCHEMAS) $(gsettingsschemadir); \
+	fi
+
+uninstall-gsettings-schemas:
+	@$(NORMAL_UNINSTALL)
+	@list='\''$(gsettings_SCHEMAS) $(gsettings__enum_file)'\''; test -n "$(gsettingsschemadir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e '\''s|^.*/||'\''`; \
+	test -n "$$files" || exit 0; \
+	echo " ( cd '\''$(DESTDIR)$(gsettingsschemadir)'\'' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(gsettingsschemadir)" && rm -f $$files
+	test -n "$(GSETTINGS_DISABLE_SCHEMAS_COMPILE)$(DESTDIR)" || $(GLIB_COMPILE_SCHEMAS) $(gsettingsschemadir)
+
+clean-gsettings-schemas:
+	rm -f $(gsettings_SCHEMAS:.xml=.valid) $(gsettings__enum_file)
+
+ifdef gsettings_ENUM_NAMESPACE
+$(gsettings__enum_file): $(gsettings_ENUM_FILES)
+	$(AM_V_GEN) glib-mkenums --comments '\''<!-- @comment@ -->'\'' --fhead "<schemalist>" --vhead "  <@type@ id='\''$(gsettings_ENUM_NAMESPACE).@EnumName@'\''>" --vprod "    <value nick='\''@valuenick@'\'' value='\''@valuenum@'\''/>" --vtail "  </@type@>" --ftail "</schemalist>" [$]^ > [$]@.tmp && mv [$]@.tmp [$]@
+endif
+'
+  _GSETTINGS_SUBST(GSETTINGS_RULES)
+])
+
+dnl _GSETTINGS_SUBST(VARIABLE)
+dnl Abstract macro to do either _AM_SUBST_NOTMAKE or AC_SUBST
+AC_DEFUN([_GSETTINGS_SUBST],
+[
+AC_SUBST([$1])
+m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([$1])])
+]
+)
diff --git a/src/.gdbinit b/src/.gdbinit
index 78536fc01f..1efde9f195 100644
--- a/src/.gdbinit
+++ b/src/.gdbinit
@@ -41,6 +41,9 @@ handle SIGUSR2 noprint pass
 # debugging.
 handle SIGALRM ignore
 
+# On selection send failed.
+handle SIGPIPE nostop noprint
+
 # Use $bugfix so that the value isn't a constant.
 # Using a constant runs into GDB bugs sometimes.
 define xgetptr
@@ -1224,6 +1227,7 @@ set print pretty on
 set print sevenbit-strings
 
 show environment DISPLAY
+show environment WAYLAND_DISPLAY
 show environment TERM
 
 # When debugging, it is handy to be able to "return" from
diff --git a/src/Makefile.in b/src/Makefile.in
index 552dd2e50a..3e6c32e8c9 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -286,6 +286,9 @@ W32_OBJ=
 ## -lkernel32 if CYGWIN but not HAVE_W32, else empty.
 W32_LIBS=@W32_LIBS@
 
+PGTK_OBJ=@PGTK_OBJ@
+PGTK_LIBS=@PGTK_LIBS@
+
 ## emacs.res if HAVE_W32
 EMACSRES = @EMACSRES@
 ## If HAVE_W32, compiler arguments for including
@@ -422,7 +425,7 @@ base_obj =
 	profiler.o decompress.o \
 	thread.o systhread.o \
 	$(if $(HYBRID_MALLOC),sheap.o) \
-	$(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \
+	$(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(PGTK_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \
 	$(W32_OBJ) $(WINDOW_SYSTEM_OBJ) $(XGSELOBJ) $(JSON_OBJ) $(GMP_OBJ)
 obj = $(base_obj) $(NS_OBJC_OBJ)
 
@@ -519,7 +522,7 @@ shortlisp :=
 lisp = $(addprefix ${lispsource}/,${shortlisp})
 
 ## Construct full set of libraries to be linked.
-LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \
+LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(PGTK_LIBS) $(LIBX_BASE) $(LIBIMAGE) \
    $(LIBX_OTHER) $(LIBSOUND) \
    $(RSVG_LIBS) $(IMAGEMAGICK_LIBS) $(LIB_ACL) $(LIB_CLOCK_GETTIME) \
    $(WEBKIT_LIBS) \
@@ -784,6 +787,10 @@ .PHONY:
 	@$(MAKE) $(AM_V_NO_PD) -C ../lisp EMACS="$(bootstrap_exe)"\
 		THEFILE=$< $<c
 
+%.eln: %.el | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
+	@$(MAKE)  $(AM_V_NO_PD) -C ../lisp EMACS="$(bootstrap_exe)"\
+		THEFILE=$< $<n
+
 ## VCSWITNESS points to the file that holds info about the current checkout.
 ## We use it as a heuristic to decide when to rebuild loaddefs.el.
 ## If empty it is ignored; the parent makefile can set it to some other value.
diff --git a/src/alloc.c b/src/alloc.c
index cc9ba8dbf5..4769a54e70 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -76,6 +76,11 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 #include <valgrind/memcheck.h>
 #endif
 
+#if HAVE_GTK4
+#include <gtk/gtk.h>
+#include <pgtkembed.h>
+#endif
+
 /* GC_CHECK_MARKED_OBJECTS means do sanity checks on allocated objects.
    We turn that on by default when ENABLE_CHECKING is defined;
    define GC_CHECK_MARKED_OBJECTS to zero to disable.  */
@@ -96,8 +101,10 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 #include <unistd.h>
 #include <fcntl.h>
 
-#ifdef USE_GTK
+#if defined(USE_GTK) && !defined (HAVE_GTK4)
 # include "gtkutil.h"
+#elif defined (HAVE_GTK4)
+# include "pgtksubr.h"
 #endif
 #ifdef WINDOWSNT
 #include "w32.h"
@@ -3114,6 +3121,15 @@ cleanup_vector (struct Lisp_Vector *vector)
       module_finalize_function (function);
     }
 #endif
+#ifdef HAVE_GTK4
+  else if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_EMBEDDED_WIDGET))
+    {
+      struct embedded_widget *widget = (struct embedded_widget *) vector;
+      g_object_unref (G_OBJECT (widget->actual_widget));
+      g_object_unref (G_OBJECT (widget->controller));
+      g_object_unref (G_OBJECT (widget->controller_2));
+    }
+#endif
 }
 
 /* Reclaim space used by unmarked vectors.  */
@@ -6019,9 +6035,14 @@ garbage_collect (void)
   mark_terminals ();
   mark_kboards ();
   mark_threads ();
+#ifdef HAVE_PGTK
+  mark_pgtkterm ();
+#endif
 
-#ifdef USE_GTK
+#if defined (USE_GTK) && !defined (HAVE_GTK4)
   xg_mark_data ();
+#elif defined (HAVE_GTK4)
+  egtk_mark_data ();
 #endif
 
 #ifdef HAVE_WINDOW_SYSTEM
@@ -6769,7 +6790,9 @@ survives_gc_p (Lisp_Object obj)
       break;
 
     case Lisp_Vectorlike:
-      survives_p = SUBRP (obj) || vector_marked_p (XVECTOR (obj));
+      survives_p =
+	(SUBRP (obj)) ||
+	vector_marked_p (XVECTOR (obj));
       break;
 
     case Lisp_Cons:
diff --git a/src/atimer.c b/src/atimer.c
index a7daf9dcf5..e67d319d9a 100644
--- a/src/atimer.c
+++ b/src/atimer.c
@@ -309,11 +309,12 @@ set_alarm (void)
 	  ispec.it_value = atimers->expiration;
 	  ispec.it_interval.tv_sec = ispec.it_interval.tv_nsec = 0;
 # ifdef HAVE_TIMERFD
-	  if (timerfd_settime (timerfd, TFD_TIMER_ABSTIME, &ispec, 0) == 0)
-	    {
-	      add_timer_wait_descriptor (timerfd);
-	      return;
-	    }
+	  if (timerfd >= 0)
+	    if (timerfd_settime (timerfd, TFD_TIMER_ABSTIME, &ispec, 0) == 0)
+	      {
+		add_timer_wait_descriptor (timerfd);
+		return;
+	      }
 # endif
 	  if (alarm_timer_ok
 	      && timer_settime (alarm_timer, TIMER_ABSTIME, &ispec, 0) == 0)
@@ -461,7 +462,8 @@ turn_on_atimers (bool on)
       if (alarm_timer_ok)
 	timer_settime (alarm_timer, TIMER_ABSTIME, &ispec, 0);
 # ifdef HAVE_TIMERFD
-      timerfd_settime (timerfd, TFD_TIMER_ABSTIME, &ispec, 0);
+      if (timerfd >= 0)
+	timerfd_settime (timerfd, TFD_TIMER_ABSTIME, &ispec, 0);
 # endif
 #endif
       alarm (0);
@@ -568,6 +570,9 @@ have_buggy_timerfd (void)
 # ifdef CYGWIN
   struct utsname name;
   return uname (&name) < 0 || strverscmp (name.release, "3.0.2") < 0;
+# elif defined HAVE_PGTK
+  /* pgtk emacs does not want timerfd. */
+  return true;
 # else
   return false;
 # endif
diff --git a/src/data.c b/src/data.c
index bce2e53cfb..802643a1a9 100644
--- a/src/data.c
+++ b/src/data.c
@@ -263,6 +263,8 @@ DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0,
           return Qxwidget;
         case PVEC_XWIDGET_VIEW:
           return Qxwidget_view;
+	case PVEC_EMBEDDED_WIDGET:
+	  return Qembedded_widget;
         /* "Impossible" cases.  */
 	case PVEC_MISC_PTR:
         case PVEC_OTHER:
@@ -3868,6 +3870,7 @@ #define PUT_ERROR(sym, tail, msg)			\
   DEFSYM (Qterminal, "terminal");
   DEFSYM (Qxwidget, "xwidget");
   DEFSYM (Qxwidget_view, "xwidget-view");
+  DEFSYM (Qembedded_widget, "embedded-widget");
 
   DEFSYM (Qdefun, "defun");
 
diff --git a/src/dispextern.h b/src/dispextern.h
index 0b1f3d14ae..212a6638c7 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -134,6 +134,14 @@ #define FACE_COLOR_TO_PIXEL(face_color, frame) (FRAME_NS_P (frame) \
 #define FACE_COLOR_TO_PIXEL(face_color, frame) face_color
 #endif
 
+#ifdef HAVE_PGTK
+#include "pgtkgui.h"
+/* Following typedef needed to accommodate the MSDOS port, believe it or not.  */
+typedef struct pgtk_display_info Display_Info;
+typedef Emacs_Pixmap XImagePtr;
+typedef XImagePtr XImagePtr_or_DC;
+#endif
+
 #ifdef HAVE_WINDOW_SYSTEM
 # include <time.h>
 # include "fontset.h"
@@ -1072,6 +1080,15 @@ #define CHECK_MATRIX(MATRIX) ((void) 0)
   /* Non-NULL means the current clipping area.  This is temporarily
      set while exposing a region.  Coordinates are frame-relative.  */
   const Emacs_Rectangle *clip;
+
+#ifdef HAVE_GTK4
+  /* Non-NULL means that all glyphs with a GTK depressed
+     button box in the text area have already been drawn. */
+  bool_bf button_unselected_marked_p : 1;
+  /* Non-NULL means that all glyphs with a GTK unpressed
+     button box in the text area have already been drawn. */
+  bool_bf button_selected_marked_p : 1;
+#endif
 #endif
 };
 
@@ -1392,6 +1409,12 @@ #define OVERLAPS_ERASED_CURSOR 	(1 << 2)
 #if defined (HAVE_NTGUI)
   Emacs_GC *gc;
   HDC hdc;
+#endif
+#if defined (HAVE_PGTK)
+  Emacs_GC xgcv;
+#ifdef HAVE_GTK4
+  bool_bf gtk_bi_composite_start;
+#endif
 #endif
 
   /* A pointer to the first glyph in the string.  This glyph
@@ -1628,7 +1651,17 @@ #define FONT_TOO_HIGH(ft)						\
   /* Boxes with 3D shadows.  Color equals the background color of the
      face.  Width is specified.  */
   FACE_RAISED_BOX,
-  FACE_SUNKEN_BOX
+  FACE_SUNKEN_BOX,
+
+  /* These options only take effect on GTK 4 terminals. */
+  FACE_RAISED_GTK_BUTTON_BOX,
+  FACE_PRELIGHT_GTK_BUTTON_BOX,
+  FACE_FOCUSED_GTK_ENTRY_BOX,
+  FACE_UNFOCUSED_GTK_ENTRY_BOX,
+  FACE_SUNKEN_GTK_BUTTON_BOX,
+  /* This should always be the last button box. */
+  FACE_UNCHECKED_GTK_CHECKBOX,
+  FACE_CHECKED_GTK_CHECKBOX
 };
 
 /* Underline type. */
@@ -1710,7 +1743,7 @@ #define FONT_TOO_HIGH(ft)						\
      of width box_line_width is drawn in color box_color.  A value of
      FACE_RAISED_BOX or FACE_SUNKEN_BOX means a 3D box is drawn with
      shadow colors derived from the background color of the face.  */
-  ENUM_BF (face_box_type) box : 2;
+  ENUM_BF (face_box_type) box : 7;
 
   /* Style of underlining. */
   ENUM_BF (face_underline_type) underline : 2;
diff --git a/src/dispnew.c b/src/dispnew.c
index 5b6fa51a56..d9fef34521 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -408,7 +408,7 @@ adjust_glyph_matrix (struct window *w, struct glyph_matrix *matrix, int x, int y
 	  && matrix->window_pixel_left == WINDOW_LEFT_PIXEL_EDGE (w)
 	  && matrix->window_pixel_top == WINDOW_TOP_PIXEL_EDGE (w)
 	  && matrix->window_height == window_height
-	  && matrix->window_vscroll == w->vscroll
+	  && matrix->window_vscroll == (w->vscroll + w->vpixoffset)
 	  && matrix->window_width == window_width)
 	return;
     }
@@ -625,7 +625,7 @@ adjust_glyph_matrix (struct window *w, struct glyph_matrix *matrix, int x, int y
       matrix->window_pixel_top = WINDOW_TOP_PIXEL_EDGE (w);
       matrix->window_height = window_height;
       matrix->window_width = window_width;
-      matrix->window_vscroll = w->vscroll;
+      matrix->window_vscroll = w->vscroll + w->vpixoffset;
     }
 }
 
@@ -1737,7 +1737,8 @@ required_matrix_height (struct window *w)
     {
       /* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html  */
       int ch_height = max (FRAME_SMALLEST_FONT_HEIGHT (f), 1);
-      int window_pixel_height = window_box_height (w) + eabs (w->vscroll);
+      int window_pixel_height = window_box_height (w) + eabs (w->vscroll)
+	+ eabs (w->vpixoffset);
 
       return (((window_pixel_height + ch_height - 1)
 	       / ch_height) * w->nrows_scale_factor
@@ -6348,6 +6349,19 @@ init_display_interactive (void)
     }
 #endif
 
+#ifdef HAVE_PGTK
+  if (!inhibit_window_system
+#ifndef CANNOT_DUMP
+     && initialized
+#endif
+      )
+    {
+      Vinitial_window_system = Qpgtk;
+      Vwindow_system_version = make_fixnum (1);
+      return;
+    }
+#endif
+
   /* If no window system has been specified, try to use the terminal.  */
   if (! isatty (STDIN_FILENO))
     fatal ("standard input is not a tty");
diff --git a/src/doc.c b/src/doc.c
index 285c0dbbbe..bf7a797459 100644
--- a/src/doc.c
+++ b/src/doc.c
@@ -505,7 +505,6 @@ store_function_docstring (Lisp_Object obj, EMACS_INT offset)
 	    XSETCAR (tem, make_fixnum (offset));
 	}
     }
-
   /* Lisp_Subrs have a slot for it.  */
   else if (SUBRP (fun))
     XSUBR (fun)->doc = offset;
diff --git a/src/emacs.c b/src/emacs.c
index ea9c4cd79d..0e3bf03ca9 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -120,6 +120,14 @@ #define MAIN_PROGRAM
 #include "pdumper.h"
 #include "epaths.h"
 
+#ifdef USE_GTK
+#include "gtkinter.h"
+#endif
+
+#ifdef HAVE_GTK4
+#include "pgtkembed.h"
+#endif
+
 static const char emacs_version[] = PACKAGE_VERSION;
 static const char emacs_copyright[] = COPYRIGHT;
 static const char emacs_bugreport[] = PACKAGE_BUGREPORT;
@@ -1578,6 +1586,9 @@ main (int argc, char **argv)
   init_bignum ();
   init_threads ();
   init_eval ();
+#ifdef HAVE_PGTK
+  init_pgtkterm ();   /* before init_atimer(). */
+#endif
   init_atimer ();
   running_asynch_code = 0;
   init_random ();
@@ -1591,6 +1602,15 @@ main (int argc, char **argv)
   init_json ();
 #endif
 
+#ifdef HAVE_NATIVE_COMP
+  if (!initialized)
+    syms_of_comp ();
+#endif
+
+#ifdef HAVE_GTK4
+  syms_of_pgtkembed ();
+#endif
+
   no_loadup
     = argmatch (argv, argc, "-nl", "--no-loadup", 6, NULL, &skip_args);
 
@@ -1903,6 +1923,16 @@ main (int argc, char **argv)
       syms_of_nsselect ();
       syms_of_fontset ();
 #endif /* HAVE_NS */
+#ifdef HAVE_PGTK
+      syms_of_pgtkterm();
+      syms_of_pgtkfns();
+      syms_of_pgtkselect ();
+      syms_of_pgtkmenu ();
+      syms_of_pgtkim ();
+      syms_of_fontset ();
+      syms_of_xsettings ();
+      syms_of_xwidget ();
+#endif
 
       syms_of_gnutls ();
 
@@ -1937,6 +1967,10 @@ main (int argc, char **argv)
       syms_of_json ();
 #endif
 
+#ifdef USE_GTK
+      syms_of_gtkinter ();
+#endif
+
       keys_of_casefiddle ();
       keys_of_cmds ();
       keys_of_buffer ();
@@ -1970,8 +2004,10 @@ main (int argc, char **argv)
 #ifdef HAVE_DBUS
   init_dbusbind ();
 #endif
-#ifdef USE_GTK
+#if defined(USE_GTK)
+#ifndef HAVE_PGTK
   init_xterm ();
+#endif
 #endif
 
   /* This can create a thread that may call getenv, so it must follow
@@ -2862,6 +2898,8 @@ DEFUN ("daemon-initialized", Fdaemon_initialized, Sdaemon_initialized, 0, 0, 0,
   return Qt;
 }
 
+
+
 void
 syms_of_emacs (void)
 {
diff --git a/src/emacsgtkfixed.c b/src/emacsgtkfixed.c
index ea9465d553..4172e8c380 100644
--- a/src/emacsgtkfixed.c
+++ b/src/emacsgtkfixed.c
@@ -20,9 +20,14 @@ Copyright (C) 2011-2020 Free Software Foundation, Inc.
 
 #include <config.h>
 
+#ifndef HAVE_GTK4
 #include "lisp.h"
 #include "frame.h"
+#ifdef HAVE_PGTK
+#include "pgtkterm.h"
+#else
 #include "xterm.h"
+#endif
 #include "xwidget.h"
 #include "emacsgtkfixed.h"
 
@@ -182,9 +187,15 @@ emacs_fixed_get_preferred_width (GtkWidget *widget,
 {
   EmacsFixed *fixed = EMACS_FIXED (widget);
   EmacsFixedPrivate *priv = fixed->priv;
+#ifdef HAVE_PGTK
+  int w = priv->f->output_data.pgtk->size_hints.min_width;
+  if (minimum) *minimum = w;
+  if (natural) *natural = priv->f->output_data.pgtk->preferred_width;
+#else
   int w = priv->f->output_data.x->size_hints.min_width;
   if (minimum) *minimum = w;
   if (natural) *natural = w;
+#endif
 }
 
 static void
@@ -194,12 +205,20 @@ emacs_fixed_get_preferred_height (GtkWidget *widget,
 {
   EmacsFixed *fixed = EMACS_FIXED (widget);
   EmacsFixedPrivate *priv = fixed->priv;
+#ifdef HAVE_PGTK
+  int h = priv->f->output_data.pgtk->size_hints.min_height;
+  if (minimum) *minimum = h;
+  if (natural) *natural = priv->f->output_data.pgtk->preferred_height;
+#else
   int h = priv->f->output_data.x->size_hints.min_height;
   if (minimum) *minimum = h;
   if (natural) *natural = h;
+#endif
 }
 
 
+#ifndef HAVE_PGTK
+
 /* Override the X function so we can intercept Gtk+ 3 calls.
    Use our values for min_width/height so that KDE don't freak out
    (Bug#8919), and so users can resize our frames as they wish.  */
@@ -234,8 +253,13 @@ XSetWMSizeHints (Display *d,
 
   if ((hints->flags & PMinSize) && f)
     {
+#ifdef HAVE_PGTK
+      int w = f->output_data.pgtk->size_hints.min_width;
+      int h = f->output_data.pgtk->size_hints.min_height;
+#else
       int w = f->output_data.x->size_hints.min_width;
       int h = f->output_data.x->size_hints.min_height;
+#endif
       data[5] = w;
       data[6] = h;
     }
@@ -253,3 +277,6 @@ XSetWMNormalHints (Display *d, Window w, XSizeHints *hints)
 {
   XSetWMSizeHints (d, w, hints, XA_WM_NORMAL_HINTS);
 }
+
+#endif
+#endif
diff --git a/src/fns.c b/src/fns.c
index ec0004d252..523c59c93d 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -20,6 +20,7 @@ Copyright (C) 1985-1987, 1993-1995, 1997-2020 Free Software Foundation,
 
 #include <config.h>
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <filevercmp.h>
diff --git a/src/font.c b/src/font.c
index ab00402b40..c7b2e353bb 100644
--- a/src/font.c
+++ b/src/font.c
@@ -5545,7 +5545,11 @@ syms_of_font (void)
   syms_of_xftfont ();
 #endif  /* HAVE_XFT */
 #endif  /* not USE_CAIRO */
-#endif	/* HAVE_X_WINDOWS */
+#else	/* not HAVE_X_WINDOWS */
+#ifdef USE_CAIRO
+  syms_of_ftcrfont ();
+#endif
+#endif	/* not HAVE_X_WINDOWS */
 #else	/* not HAVE_FREETYPE */
 #ifdef HAVE_X_WINDOWS
   syms_of_xfont ();
diff --git a/src/font.h b/src/font.h
index 8614e7fa10..89c1a7a091 100644
--- a/src/font.h
+++ b/src/font.h
@@ -822,7 +822,7 @@ #define FONT_INVALID_CODE 0xFFFFFFFF
 
 extern Lisp_Object font_make_entity (void);
 extern Lisp_Object font_make_object (int, Lisp_Object, int);
-#if defined (HAVE_XFT) || defined (HAVE_FREETYPE) || defined (HAVE_NS)
+#if defined (HAVE_XFT) || defined (HAVE_FREETYPE) || defined (HAVE_NS) || defined(HAVE_PGTK)
 extern Lisp_Object font_build_object (int, Lisp_Object, Lisp_Object, double);
 #endif
 
diff --git a/src/frame.c b/src/frame.c
index c871e4fd99..c7484e8aa4 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -55,6 +55,14 @@ Copyright (C) 1993-1995, 1997, 1999-2020 Free Software Foundation, Inc.
 #endif
 #include "pdumper.h"
 
+#ifdef HAVE_GTK3
+#include "gtkinter.h"
+#endif
+
+#ifdef HAVE_GTK4
+#include "pgtksubr.h"
+#endif
+
 /* The currently selected frame.  */
 Lisp_Object selected_frame;
 
@@ -292,6 +300,8 @@ DEFUN ("framep", Fframep, Sframep, 1, 1, 0,
       return Qpc;
     case output_ns:
       return Qns;
+    case output_pgtk:
+      return Qpgtk;
     default:
       emacs_abort ();
     }
@@ -1899,6 +1909,9 @@ check_minibuf_window (Lisp_Object frame, int select)
 Lisp_Object
 delete_frame (Lisp_Object frame, Lisp_Object force)
 {
+#ifdef HAVE_GTK4
+  block_input ();
+#endif
   struct frame *f = decode_any_frame (frame);
   struct frame *sf;
   struct kboard *kb;
@@ -2149,7 +2162,8 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
     /* Since a similar behavior was observed on the Lucid and Motif
        builds (see Bug#5802, Bug#21509, Bug#23499, Bug#27816), we now
        don't delete the terminal for these builds either.  */
-    if (terminal->reference_count == 0 && terminal->type == output_x_window)
+    if (terminal->reference_count == 0 &&
+	(terminal->type == output_x_window || terminal->type == output_pgtk))
       terminal->reference_count = 1;
 #endif /* USE_X_TOOLKIT || USE_GTK */
     if (terminal->reference_count == 0)
@@ -2305,7 +2319,9 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 	    delete_frame (minibuffer_child_frame, Qnoelisp);
 	}
     }
-
+#ifdef HAVE_GTK4
+  unblock_input ();
+#endif
   return Qnil;
 }
 
@@ -3095,7 +3111,6 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val)
 
       val = old_val;
     }
-
   /* The tty color needed to be set before the frame's parameter
      alist was updated with the new value.  This is not true any more,
      but we still do this test early on.  */
@@ -3116,6 +3131,12 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val)
 
   if (EQ (prop, Qbuffer_predicate))
     fset_buffer_predicate (f, val);
+#ifdef HAVE_GTK4
+  if (EQ (prop, Qheader_bar))
+    {
+      eg_notify_frame_header_bar_state_changed (f);
+    }
+#endif
 
   if (! FRAME_WINDOW_P (f))
     {
@@ -4637,10 +4658,17 @@ gui_set_border_width (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
   if (border_width == f->border_width)
     return;
 
+#ifndef HAVE_PGTK
   if (FRAME_NATIVE_WINDOW (f) != 0)
     error ("Cannot change the border width of a frame");
+#endif
 
   f->border_width = border_width;
+
+#ifdef HAVE_PGTK
+  if (FRAME_TERMINAL (f)->frame_rehighlight_hook)
+    (*FRAME_TERMINAL (f)->frame_rehighlight_hook) (f);
+#endif
 }
 
 void
@@ -5751,7 +5779,7 @@ DEFUN ("frame-pointer-visible-p", Fframe_pointer_visible_p,
 
 #ifdef HAVE_WINDOW_SYSTEM
 
-# if (defined USE_GTK || defined HAVE_NS || defined HAVE_XINERAMA \
+# if (defined USE_GTK || defined HAVE_PGTK || defined HAVE_NS || defined HAVE_XINERAMA \
       || defined HAVE_XRANDR)
 void
 free_monitors (struct MonitorInfo *monitors, int n_monitors)
@@ -5878,6 +5906,7 @@ syms_of_frame (void)
   DEFSYM (Qw32, "w32");
   DEFSYM (Qpc, "pc");
   DEFSYM (Qns, "ns");
+  DEFSYM (Qpgtk, "pgtk");
   DEFSYM (Qvisible, "visible");
   DEFSYM (Qbuffer_predicate, "buffer-predicate");
   DEFSYM (Qbuffer_list, "buffer-list");
@@ -6011,7 +6040,10 @@ syms_of_frame (void)
   DEFSYM (Qtop_only, "top-only");
   DEFSYM (Qiconify_top_level, "iconify-top-level");
   DEFSYM (Qmake_invisible, "make-invisible");
-
+  DEFSYM (Qheader_bar_icons, "header-bar-icons");
+#ifdef HAVE_GTK4
+  DEFSYM (Qheader_bar, "header-bar");
+#endif
   {
     int i;
 
diff --git a/src/frame.h b/src/frame.h
index 476bac67fa..22ddae9d68 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -97,7 +97,7 @@ #define EMACS_FRAME_H
      Usually it is nil.  */
   Lisp_Object title;
 
-#if defined (HAVE_WINDOW_SYSTEM)
+#if defined(HAVE_WINDOW_SYSTEM)
   /* This frame's parent frame, if it has one.  */
   Lisp_Object parent_frame;
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -574,6 +574,7 @@ #define EMACS_FRAME_H
     struct x_output *x;         /* From xterm.h.  */
     struct w32_output *w32;     /* From w32term.h.  */
     struct ns_output *ns;       /* From nsterm.h.  */
+    struct pgtk_output *pgtk;   /* From pgtkterm.h. */
   }
   output_data;
 
@@ -665,11 +666,21 @@ fset_face_alist (struct frame *f, Lisp_Object val)
 {
   f->face_alist = val;
 }
+
 #if defined (HAVE_WINDOW_SYSTEM)
 INLINE void
 fset_parent_frame (struct frame *f, Lisp_Object val)
 {
   f->parent_frame = val;
+  eassert (NILP (val) || FRAMEP (val));
+  if (NILP (val))
+    return;
+  if (FRAME_TERMINAL (f) &&
+      FRAME_TERMINAL (f)->set_parent_frame_hook)
+    FRAME_TERMINAL (f)->set_parent_frame_hook (f,
+					       XUNTAG (val,
+						       Lisp_Vectorlike,
+						       struct frame));
 }
 #endif
 INLINE void
@@ -761,6 +772,14 @@ fset_tool_bar_items (struct frame *f, Lisp_Object val)
 {
   f->tool_bar_items = val;
 }
+
+INLINE void
+fset_use_header_bar_icons (struct frame *f, Lisp_Object val)
+{
+  Lisp_Object alist = f->param_alist;
+  store_in_alist (&alist, Qheader_bar_icons, val);
+  f->param_alist = alist;
+}
 #ifdef USE_GTK
 INLINE void
 fset_tool_bar_position (struct frame *f, Lisp_Object val)
@@ -841,6 +860,11 @@ #define FRAME_NS_P(f) false
 #else
 #define FRAME_NS_P(f) ((f)->output_method == output_ns)
 #endif
+#ifndef HAVE_PGTK
+#define FRAME_PGTK_P(f) false
+#else
+#define FRAME_PGTK_P(f) ((f)->output_method == output_pgtk)
+#endif
 
 /* FRAME_WINDOW_P tests whether the frame is a graphical window system
    frame.  */
@@ -854,6 +878,9 @@ #define FRAME_WINDOW_P(f) FRAME_W32_P (f)
 #ifdef HAVE_NS
 #define FRAME_WINDOW_P(f) FRAME_NS_P(f)
 #endif
+#ifdef HAVE_PGTK
+#define FRAME_WINDOW_P(f) FRAME_PGTK_P(f)
+#endif
 #ifndef FRAME_WINDOW_P
 #define FRAME_WINDOW_P(f) ((void) (f), false)
 #endif
@@ -974,7 +1001,7 @@ #define FRAME_TOP_MARGIN_HEIGHT(F)				\
 #ifdef HAVE_EXT_MENU_BAR
 #define FRAME_EXTERNAL_MENU_BAR(f) (f)->external_menu_bar
 #else
-#define FRAME_EXTERNAL_MENU_BAR(f) false
+#define FRAME_EXTERNAL_MENU_BAR(f) 0
 #endif
 
 /* True if frame F is currently visible.  */
@@ -1642,6 +1669,31 @@ #define FRAME_SMALLEST_CHAR_WIDTH(f)		\
 #define FRAME_SMALLEST_FONT_HEIGHT(f)		\
   FRAME_DISPLAY_INFO (f)->smallest_font_height
 
+#ifdef HAVE_GTK3
+#define FRAME_DISPLAY_HEADER_BAR_ICONS(f) 	\
+  (!NILP (get_frame_param (f, Qheader_bar_icons)))
+#else
+#define FRAME_DISPLAY_HEADER_BAR_ICONS(f) 0
+#endif
+
+#ifdef HAVE_GTK3
+#ifndef HAVE_GTK4
+#define FRAME_DISPLAY_HEADER_BAR_MENU(f)	\
+  !FRAME_EXTERNAL_MENU_BAR (f)
+#else
+#define FRAME_DISPLAY_HEADER_BAR_MENU(f) 1
+#endif
+#else
+#define FRAME_DISPLAY_HEADER_BAR_MENU(f) 0
+#endif
+
+#ifdef HAVE_GTK4
+#define FRAME_DISPLAY_HEADER_BAR(f)	\
+  (!NILP (get_frame_param(f, Qheader_bar)))
+#else
+#define FRAME_DISPLAY_HEADER_BAR(f) 0
+#endif
+
 /***********************************************************************
 				Frame Parameters
  ***********************************************************************/
@@ -1700,7 +1752,7 @@ #define EMACS_CLASS "Emacs"
 extern void x_sync (struct frame *);
 #endif /* HAVE_X_WINDOWS */
 
-#ifndef HAVE_NS
+#if !defined(HAVE_NS) && !defined(HAVE_PGTK)
 
 /* Set F's bitmap icon, if specified among F's parameters.  */
 
diff --git a/src/fringe.c b/src/fringe.c
index fc4c738dc2..8bbc2c6a22 100644
--- a/src/fringe.c
+++ b/src/fringe.c
@@ -31,6 +31,8 @@
 #include "termhooks.h"
 #include "pdumper.h"
 
+#include "pgtkterm.h"
+
 /* Fringe bitmaps are represented in three different ways:
 
    Logical bitmaps are used internally to denote things like
@@ -1399,7 +1401,7 @@ DEFUN ("destroy-fringe-bitmap", Fdestroy_fringe_bitmap, Sdestroy_fringe_bitmap,
    On W32 and MAC (little endian), there's no need to do this.
 */
 
-#if defined (HAVE_X_WINDOWS)
+#if defined (HAVE_X_WINDOWS) || defined(HAVE_PGTK)
 static const unsigned char swap_nibble[16] = {
   0x0, 0x8, 0x4, 0xc,           /* 0000 1000 0100 1100 */
   0x2, 0xa, 0x6, 0xe,           /* 0010 1010 0110 1110 */
@@ -1462,6 +1464,25 @@ init_fringe_bitmap (int which, struct fringe_bitmap *fb, int once_p)
 #endif /* not USE_CAIRO */
 #endif /* HAVE_X_WINDOWS */
 
+#if !defined(HAVE_X_WINDOWS) && defined (HAVE_PGTK)
+      unsigned short *bits = fb->bits;
+      int j;
+
+      for (j = 0; j < fb->height; j++)
+	{
+	  unsigned short b = *bits;
+#ifdef WORDS_BIGENDIAN
+	  *bits++ = (b << (16 - fb->width));
+#else
+	  b = (unsigned short)((swap_nibble[b & 0xf] << 12)
+			       | (swap_nibble[(b>>4) & 0xf] << 8)
+			       | (swap_nibble[(b>>8) & 0xf] << 4)
+			       | (swap_nibble[(b>>12) & 0xf]));
+	  *bits++ = (b >> (16 - fb->width));
+#endif
+	}
+#endif /* !HAVE_X_WINDOWS && HAVE_PGTK */
+
 #ifdef HAVE_NTGUI
       unsigned short *bits = fb->bits;
       int j;
diff --git a/src/ftcrfont.c b/src/ftcrfont.c
index 7832d4f5ce..29e1d8b57f 100644
--- a/src/ftcrfont.c
+++ b/src/ftcrfont.c
@@ -22,7 +22,11 @@
 #include <cairo-ft.h>
 
 #include "lisp.h"
+#ifndef HAVE_PGTK
 #include "xterm.h"
+#else
+#include "pgtkterm.h"
+#endif
 #include "blockinput.h"
 #include "charset.h"
 #include "composite.h"
@@ -492,11 +496,19 @@ ftcrfont_draw (struct glyph_string *s,
 
   block_input ();
 
+#ifndef HAVE_PGTK
   cr = x_begin_cr_clip (f, s->gc);
+#else
+  cr = pgtk_begin_cr_clip (f);
+#endif
 
   if (with_background)
     {
+#ifndef HAVE_PGTK
       x_set_cr_source_with_gc_background (f, s->gc);
+#else
+      pgtk_set_cr_source_with_color (f, s->xgcv.background);
+#endif
       cairo_rectangle (cr, x, y - FONT_BASE (face->font),
 		       s->width, FONT_HEIGHT (face->font));
       cairo_fill (cr);
@@ -513,11 +525,19 @@ ftcrfont_draw (struct glyph_string *s,
                                                        NULL));
     }
 
+#ifndef HAVE_PGTK
   x_set_cr_source_with_gc_foreground (f, s->gc);
+#else
+  pgtk_set_cr_source_with_color (f, s->xgcv.foreground);
+#endif
   cairo_set_scaled_font (cr, ftcrfont_info->cr_scaled_font);
   cairo_show_glyphs (cr, glyphs, len);
 
+#ifndef HAVE_PGTK
   x_end_cr_clip (f);
+#else
+  pgtk_end_cr_clip (f);
+#endif
 
   unblock_input ();
 
diff --git a/src/ftfont.h b/src/ftfont.h
index f771dc159b..94050ddc7b 100644
--- a/src/ftfont.h
+++ b/src/ftfont.h
@@ -25,6 +25,7 @@ #define EMACS_FTFONT_H
 #include <ft2build.h>
 #include FT_FREETYPE_H
 #include FT_SIZES_H
+#include FT_TRUETYPE_TABLES_H
 #ifdef FT_BDF_H
 # include FT_BDF_H
 #endif
diff --git a/src/gtkconfig.h b/src/gtkconfig.h
new file mode 100644
index 0000000000..d6f5ecf4ae
--- /dev/null
+++ b/src/gtkconfig.h
@@ -0,0 +1,28 @@
+/* Basic interoperability configuration for GTK
+
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+
+#ifndef HAVE_GTK3
+#define gdk_event_get_event_type(ev) \
+  ((ev)->type)
+#endif
+
+// #define FIX_GTK_LEGACY_HANDLER_BUG
+// #define FIX_GTK_LEGACY_CONTROLLER_BUG
diff --git a/src/gtkimage.c b/src/gtkimage.c
new file mode 100644
index 0000000000..0101c03722
--- /dev/null
+++ b/src/gtkimage.c
@@ -0,0 +1,217 @@
+/* gtkimage.c -- Load images with GDK.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "gtkimage.h"
+#include "coding.h"
+#include "blockinput.h"
+#include "pgtkterm.h"
+
+#include <gtk/gtk.h>
+#include <cairo/cairo.h>
+
+
+#ifdef USE_CAIRO
+#define PUT_PIXEL image_pix_container_put_pixel
+#define NO_PIXMAP 0
+
+#define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b))
+#endif
+
+static Lisp_Object
+image_spec_value (Lisp_Object spec, Lisp_Object key, bool *found)
+{
+  Lisp_Object tail;
+
+  eassert (valid_image_p (spec));
+
+  for (tail = XCDR (spec);
+       CONSP (tail) && CONSP (XCDR (tail));
+       tail = XCDR (XCDR (tail)))
+    {
+      if (EQ (XCAR (tail), key))
+	{
+	  if (found)
+	    *found = 1;
+	  return XCAR (XCDR (tail));
+	}
+    }
+
+  if (found)
+    *found = 0;
+  return Qnil;
+}
+
+
+static Emacs_Pix_Container
+image_create_pix_container (struct frame *f, unsigned int width,
+			    unsigned int height, unsigned int depth)
+{
+  Emacs_Pix_Container pimg;
+
+  pimg = xmalloc (sizeof (*pimg));
+  pimg->width = width;
+  pimg->height = height;
+  pimg->bits_per_pixel = depth == 1 ? 8 : 32;
+  pimg->bytes_per_line = cairo_format_stride_for_width ((depth == 1
+							 ? CAIRO_FORMAT_A8
+							 : CAIRO_FORMAT_RGB24),
+							width);
+  pimg->data = xmalloc (pimg->bytes_per_line * height);
+
+  return pimg;
+}
+
+typedef void Picture;
+
+static bool
+image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int depth,
+				   Emacs_Pix_Container *pimg,
+				   Emacs_Pixmap *pixmap, Picture *picture)
+{
+  eassert (input_blocked_p ());
+
+  /* Allocate a pixmap of the same size.  */
+  *pixmap = image_create_pix_container (f, width, height, depth);
+  *pimg = *pixmap;
+  return 1;
+}
+
+static bool
+image_create_x_image_and_pixmap (struct frame *f, struct image *img,
+				 int width, int height, int depth,
+				 Emacs_Pix_Container *ximg, bool mask_p)
+{
+  eassert ((!mask_p ? img->pixmap : img->mask) == NO_PIXMAP);
+
+  Picture *picture = NULL;
+  return image_create_x_image_and_pixmap_1 (f, width, height, depth, ximg,
+					    !mask_p ? &img->pixmap : &img->mask,
+					    picture);
+}
+
+static void
+gimg_set_cr_source_with_color (cairo_t *cr, struct frame *f, unsigned long color)
+{
+  block_input ();
+  Emacs_Color col;
+  col.pixel = color;
+  pgtk_query_color (f, &col);
+  cairo_set_source_rgba (cr, col.red / 65535.0,
+			 col.green / 65535.0,
+			 col.blue / 65535.0, 1.0);
+  unblock_input ();
+}
+
+static void
+image_pix_container_put_pixel (Emacs_Pix_Container image,
+			       int x, int y, unsigned long pixel)
+{
+  if (image->bits_per_pixel == 32)
+    ((uint32_t *)(image->data + y * image->bytes_per_line))[x] = pixel;
+  else
+    ((uint8_t *)(image->data + y * image->bytes_per_line))[x] = pixel;
+}
+
+static unsigned long
+lookup_rgb_color (struct frame *f, int r, int g, int b)
+{
+  return RGB_TO_ULONG (r >> 8, g >> 8, b >> 8);
+}
+
+bool
+gdk_load (struct frame *f, struct image *img)
+{
+  block_input ();
+  Lisp_Object specified_file;
+  Lisp_Object specified_data;
+  specified_file = image_spec_value (img->spec, QCfile, NULL);
+  specified_data = image_spec_value (img->spec, QCdata, NULL);
+  if (!STRINGP (specified_file))
+    return false;
+  specified_file = image_find_image_file (specified_file);
+  if (!STRINGP (specified_file))
+    return false;
+  GError *error = NULL;
+  GdkTexture *text;
+  if (STRINGP (specified_file))
+    text = gdk_texture_new_from_file (g_file_new_for_path (SSDATA (ENCODE_FILE (specified_file))),
+				      &error);
+  else if (STRINGP (specified_data))
+    return false; // TODO!
+  else
+    return false;
+  if (error)
+    {
+      g_error_free (error);
+      return false;
+    }
+  Emacs_Pix_Context xpx = NULL;
+  ptrdiff_t h = gdk_texture_get_height (text), w = gdk_texture_get_width (text);
+  if (!image_create_x_image_and_pixmap (f, img, w, h, 0, &xpx, 0))
+    {
+      g_object_unref (text);
+      return false;
+    }
+  cairo_surface_t *surface
+    = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                  gdk_texture_get_width (text),
+                                  gdk_texture_get_height (text));
+  cairo_t *cr = cairo_create (surface);
+  gimg_set_cr_source_with_color (cr, f, FRAME_BACKGROUND_COLOR (f));
+  cairo_rectangle (cr, 0, 0, gdk_texture_get_width (text),
+		   gdk_texture_get_height (text));
+  cairo_paint (cr);
+  cairo_surface_flush (surface);
+  cairo_surface_t *vsuf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+						      gdk_texture_get_width (text),
+						      gdk_texture_get_height (text));
+  gdk_texture_download (text, cairo_image_surface_get_data (vsuf),
+                        cairo_image_surface_get_stride (vsuf));
+  cairo_surface_mark_dirty (vsuf);
+  cairo_set_source_surface (cr, vsuf, 0, 0);
+  cairo_paint (cr);
+  cairo_surface_destroy (vsuf);
+  cairo_destroy (cr);
+  uint8_t *data = cairo_image_surface_get_data (surface);
+  for (ptrdiff_t y = 0; y < h; ++y)
+    {
+      ptrdiff_t idx = w * y;
+      for (ptrdiff_t x = 0; x < w; ++x)
+        {
+          struct
+          {
+#ifdef WORDS_BIGENDIAN
+            uint8_t a, r, g, b;
+#else
+            uint8_t b, g, r, a;
+#endif
+          } cvals = *((typeof (cvals) *) data + idx + x);
+          PUT_PIXEL (xpx, x, y,
+                     lookup_rgb_color (f, 256 * cvals.r, 256 * cvals.g,
+                                       256 * cvals.b));
+        }
+    }
+  img->height = h;
+  img->width = w;
+  img->mask = NULL;
+  IMAGE_BACKGROUND (img, f, (Emacs_Pix_Context) xpx);
+  g_object_unref (text);
+  cairo_surface_destroy (surface);
+  unblock_input ();
+  return true;
+}
diff --git a/src/gtkimage.h b/src/gtkimage.h
new file mode 100644
index 0000000000..c8a4ecdc67
--- /dev/null
+++ b/src/gtkimage.h
@@ -0,0 +1,25 @@
+/* gtkimage.c -- Load images with GDK.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "lisp.h"
+#include "frame.h"
+#include "dispextern.h"
+
+extern bool
+gdk_load (struct frame *f, struct image *i);
diff --git a/src/gtkinter.c b/src/gtkinter.c
new file mode 100644
index 0000000000..c73f6b3eda
--- /dev/null
+++ b/src/gtkinter.c
@@ -0,0 +1,1093 @@
+/* GTK related routines
+
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "gtkinter.h"
+#include "blockinput.h"
+
+#ifdef HAVE_GTK4
+#include "pgtksubr.h"
+#else
+#include "gtkutil.h"
+#define XG_TOOL_BAR_LAST_MODIFIER "emacs-tool-bar-modifier"
+#endif
+#include "keyboard.h"
+
+#ifdef HAVE_GTK3
+
+#ifdef HAVE_GTK4
+#define xg_list_node egtk_list_node
+#define xg_menu_cb_data egtk_menu_cb_data
+#define xg_list_insert egtk_list_insert
+#endif
+
+/* Linked list of all allocated struct xg_menu_cb_data.  Used for marking
+   during GC.  The next member points to the items.  */
+static xg_list_node xg_menu_cb_list;
+
+#pragma GCC diagnostic ignored "-Wunused-macros"
+#pragma GCC diagnostic ignored "-Wunused-function"
+static xg_menu_cb_data *
+g_make_cl_data (xg_menu_cb_data *cl_data, struct frame *f,
+                GCallback highlight_cb)
+{
+  if (!cl_data)
+    {
+      cl_data = xmalloc (sizeof *cl_data);
+      cl_data->f = f;
+      cl_data->menu_bar_vector = f->menu_bar_vector;
+      cl_data->menu_bar_items_used = f->menu_bar_items_used;
+      cl_data->highlight_cb = highlight_cb;
+      cl_data->ref_count = 0;
+
+      xg_list_insert (&xg_menu_cb_list, &cl_data->ptrs);
+    }
+
+  cl_data->ref_count++;
+
+  return cl_data;
+}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic push
+#endif
+
+#ifndef HAVE_GTK4
+static void
+find_dialog_buttons (GtkWidget *w, gpointer l)
+{
+  if (GTK_IS_BUTTON (w))
+    *(GList **) l = g_list_append (*(GList **) l, w);
+}
+#endif
+
+GtkAboutDialog *
+show_about_dialog (gpointer thing, struct frame *f)
+{
+  GtkAboutDialog *a = GTK_ABOUT_DIALOG (gtk_about_dialog_new ());
+  GtkWindow *frame_window = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f));
+  gtk_about_dialog_set_program_name (a, PACKAGE_NAME);
+  Lisp_Object ev = call0 (intern ("emacs-version"));
+  CHECK_STRING (ev);
+  gtk_about_dialog_set_version (a, SSDATA (ev));
+  gtk_about_dialog_set_website (a, PACKAGE_URL);
+#ifdef HAVE_GTK3
+  gtk_about_dialog_set_license_type (a, GTK_LICENSE_GPL_3_0);
+#endif
+  gtk_about_dialog_set_copyright (a, COPYRIGHT);
+  gtk_about_dialog_set_wrap_license (a, true);
+  gtk_window_set_transient_for (GTK_WINDOW (a), frame_window);
+  gtk_dialog_run (GTK_DIALOG (a));
+  gtk_widget_destroy (GTK_WIDGET (a));
+  return a;
+}
+
+GtkDialog *
+#ifndef HAVE_GTK4
+build_dialog_n_items (char *fmt, widget_value *wv, GCallback select_cb,
+                      GCallback deactivate_cb, GCallback dialog_delete_callback)
+#else
+build_dialog_n_items (char *fmt, widget_value *wv, GCallback select_cb,
+                      GCallback deactivate_cb)
+#endif
+{
+  GtkDialog *dialog = GTK_DIALOG (
+    gtk_message_dialog_new (NULL,
+                            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                            GTK_MESSAGE_INFO, GTK_BUTTONS_NONE, fmt, NULL));
+#ifndef HAVE_GTK4
+  g_signal_connect (G_OBJECT (dialog), "delete-event",
+                    G_CALLBACK (dialog_delete_callback), 0);
+#endif
+  if (deactivate_cb)
+    {
+      g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0);
+      g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0);
+    }
+  int id = 0;
+  do
+    {
+      g_signal_connect (G_OBJECT (
+                          gtk_dialog_add_button (dialog, wv->value, id)),
+                        "clicked", select_cb, wv->call_data);
+      wv = wv->next;
+      ++id;
+    }
+  while (wv);
+  return dialog;
+}
+
+#ifndef HAVE_GTK4
+GtkDialog *
+build_dialog_3_items (Lisp_Object fmt, Lisp_Object name_a, Lisp_Object name_b,
+                      Lisp_Object name_c, GCallback select_cb,
+                      GCallback deactivate_cb, widget_value *wv_a,
+                      widget_value *wv_b, widget_value *wv_c,
+                      GCallback dialog_delete_callback)
+{
+  GtkDialog *dialog = GTK_DIALOG (
+    gtk_message_dialog_new (NULL,
+                            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                            GTK_MESSAGE_INFO, GTK_BUTTONS_NONE, SSDATA (fmt),
+                            NULL));
+  GtkButton *a
+    = GTK_BUTTON (gtk_dialog_add_button (dialog, SSDATA (name_a), 0));
+  GtkButton *b
+    = GTK_BUTTON (gtk_dialog_add_button (dialog, SSDATA (name_b), 1));
+  GtkButton *c
+    = GTK_BUTTON (gtk_dialog_add_button (dialog, SSDATA (name_c), 2));
+
+  g_signal_connect (G_OBJECT (dialog), "delete-event",
+                    G_CALLBACK (dialog_delete_callback), 0);
+  if (deactivate_cb)
+    {
+      g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0);
+      g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0);
+    }
+  g_signal_connect (G_OBJECT (a), "clicked", select_cb, wv_a->call_data);
+  g_signal_connect (G_OBJECT (b), "clicked", select_cb, wv_b->call_data);
+  g_signal_connect (G_OBJECT (c), "clicked", select_cb, wv_c->call_data);
+
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  return dialog;
+}
+
+GtkMessageDialog *
+build_dialog_2_items (Lisp_Object fmt, Lisp_Object name_a, Lisp_Object name_b,
+                      GCallback select_cb, GCallback deactivate_cb,
+                      widget_value *wv_a, widget_value *wv_b,
+                      GCallback dialog_delete_callback)
+{
+  GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG (
+    gtk_message_dialog_new (NULL,
+                            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                            GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+                            SSDATA (fmt), NULL));
+  GList *buttons = g_list_alloc ();
+  GtkContainer *action_area
+    = GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog)));
+  gtk_container_forall (action_area, find_dialog_buttons, &buttons);
+  GtkButton *a = buttons->next->data;
+  GtkButton *b = buttons->next->next->data;
+
+  gtk_button_set_label (a, SSDATA (name_a));
+  gtk_button_set_label (b, SSDATA (name_b));
+
+  g_signal_connect (G_OBJECT (dialog), "delete-event",
+                    G_CALLBACK (dialog_delete_callback), 0);
+  if (deactivate_cb)
+    {
+      g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0);
+      g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0);
+    }
+  g_signal_connect (G_OBJECT (a), "clicked", select_cb, wv_a->call_data);
+  g_signal_connect (G_OBJECT (b), "clicked", select_cb, wv_b->call_data);
+  g_list_free (buttons);
+  return dialog;
+}
+
+extern GtkMessageDialog *
+build_dialog_1_item (Lisp_Object fmt, Lisp_Object name_a, GCallback select_cb,
+                     GCallback deactivate_cb, widget_value *wv_a,
+                     GCallback dialog_delete_callback)
+{
+  GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG (
+    gtk_message_dialog_new (NULL,
+                            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                            GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK, SSDATA (fmt),
+                            NULL));
+  GList *buttons = g_list_alloc ();
+  GtkContainer *action_area
+    = GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog)));
+  gtk_container_forall (action_area, find_dialog_buttons, &buttons);
+  GtkButton *a = buttons->next->data;
+
+  gtk_button_set_label (a, SSDATA (name_a));
+
+  g_signal_connect (G_OBJECT (dialog), "delete-event",
+                    G_CALLBACK (dialog_delete_callback), 0);
+  if (deactivate_cb)
+    {
+      g_signal_connect (G_OBJECT (dialog), "close", deactivate_cb, 0);
+      g_signal_connect (G_OBJECT (dialog), "response", deactivate_cb, 0);
+    }
+  g_signal_connect (G_OBJECT (a), "clicked", select_cb, wv_a->call_data);
+  g_list_free (buttons);
+  return dialog;
+}
+#endif
+
+#ifdef HAVE_GTK3
+static void
+gtk_find_gtk_menu_button (GtkWidget *w, GtkMenuButton **user_data)
+{
+  if (GTK_IS_MENU_BUTTON (w))
+    (*user_data) = GTK_MENU_BUTTON (w);
+}
+#endif
+
+/* When the GTK widget W is to be created on a display for F that
+   is not the default display, set the display for W.
+   W can be a GtkMenu or a GtkWindow widget.  */
+#ifndef HAVE_GTK4
+static void
+gtk_set_screen (GtkWidget *w, struct frame *f)
+{
+#ifndef HAVE_PGTK
+  if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ())
+    {
+      GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
+      GdkScreen *gscreen = gdk_display_get_default_screen (gdpy);
+
+      if (GTK_IS_MENU (w))
+        gtk_menu_set_screen (GTK_MENU (w), gscreen);
+      else
+        gtk_window_set_screen (GTK_WINDOW (w), gscreen);
+    }
+#else
+  if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ())
+    {
+      GdkScreen *gscreen = gdk_display_get_default_screen (FRAME_X_DISPLAY (f));
+
+      if (GTK_IS_MENU (w))
+        gtk_menu_set_screen (GTK_MENU (w), gscreen);
+      else
+        gtk_window_set_screen (GTK_WINDOW (w), gscreen);
+    }
+#endif
+}
+#endif
+#ifdef HAVE_GTK3
+static bool
+xg_get_widget_pack_flag (GtkWidget *w)
+{
+  /*
+   * nil = start
+   * t = end
+   */
+  return g_object_get_data (G_OBJECT (w), "xg-widget-pack-flag") != 0;
+}
+
+static void
+xg_set_widget_pack_flag (GtkWidget *w, bool end)
+{
+  g_object_set_data (G_OBJECT (w), "xg-widget-pack-flag",
+                     (gpointer) (intptr_t) ((gpointer) ((gpointer) end) != 0));
+}
+
+static void
+xg_find_gtk_menu_button (GtkWidget *w, GtkMenuButton **user_data)
+{
+  if (GTK_IS_MENU_BUTTON (w))
+    (*user_data) = GTK_MENU_BUTTON (w);
+}
+
+void
+xg_notify_header_bar_menu_state_changed (struct frame *f)
+{
+  if (FRAME_PARENT_FRAME (f))
+    return;
+  GtkMenuButton *btn = NULL;
+  GtkHeaderBar *hbawr = FRAME_GTK_HEADER_BAR (f);
+  gtk_container_foreach (GTK_CONTAINER (hbawr),
+                         (GtkCallback) gtk_find_gtk_menu_button, &btn);
+  if (!FRAME_DISPLAY_HEADER_BAR_MENU (f))
+    {
+      if (btn)
+        gtk_widget_destroy (GTK_WIDGET (btn));
+      return;
+    }
+  else if (!btn)
+    {
+      GtkMenuButton *btn = GTK_MENU_BUTTON (gtk_menu_button_new ());
+#ifdef HAVE_GTK4
+      gtk_menu_button_set_icon_name (btn, "open-menu-symbolic");
+#else
+      gtk_button_set_image (
+        GTK_BUTTON (btn), gtk_image_new_from_icon_name ("open-menu-symbolic",
+                                                        GTK_ICON_SIZE_BUTTON));
+#endif
+      gtk_header_bar_pack_end (FRAME_GTK_HEADER_BAR (f), GTK_WIDGET (btn));
+    }
+#ifndef HAVE_GTK4
+  if (!btn)
+    {
+      GtkWidget *menubar = gtk_menu_button_new ();
+      btn = GTK_MENU_BUTTON (menubar);
+      gtk_button_set_image (
+        GTK_BUTTON (btn), gtk_image_new_from_icon_name ("open-menu-symbolic",
+                                                        GTK_ICON_SIZE_BUTTON));
+      gtk_menu_button_set_popup (btn, gtk_menu_new ());
+      gtk_header_bar_pack_end (hbawr, GTK_WIDGET (btn));
+      gtk_widget_set_visible (GTK_WIDGET (btn), true);
+    }
+#endif
+}
+
+#ifndef HAVE_GTK4
+static void
+xg_destroy_menu_item_if_empty (GtkWidget *it, gpointer _)
+{
+  if (GTK_IS_MENU_ITEM (it)
+      && !strlen (gtk_menu_item_get_label (GTK_MENU_ITEM (it))))
+    gtk_widget_destroy (it);
+}
+#endif
+
+#ifdef HAVE_GTK4
+
+struct ppw_data
+{
+  widget_value *popup;
+  GCallback select_cb;
+  GCallback deactivate_cb;
+  GCallback highlight_cb;
+  egtk_menu_cb_data *cl_data;
+  struct frame *f;
+  bool m;
+};
+
+static void
+popup_create_func (GtkMenuButton *menu_button,
+                   gpointer user_data)
+{
+  struct ppw_data *data = user_data;
+#define popup data->popup
+#define select_cb data->select_cb
+#define deactivate_cb data->deactivate_cb
+#define highlight_cb data->highlight_cb
+  if (popup && select_cb && !data->m)
+    {
+      if (gtk_menu_button_get_popover (menu_button))
+	gtk_menu_button_set_popover (menu_button, NULL);
+      GtkPopover *po = build_mm_from_wv (popup, data->f, select_cb,
+					 deactivate_cb, highlight_cb,
+					 true, true, data->cl_data, NULL,
+					 NULL, NULL, NULL);
+      gtk_menu_button_set_popover (menu_button, GTK_WIDGET (po));
+      data->m = true;
+    }
+#undef popup
+#undef select_cb
+#undef deactivate_cb
+#undef highlight_cb
+}
+
+static void
+free_data (gpointer data)
+{
+  struct ppw_data *d = data;
+  if (d->popup)
+    {
+      free_menubar_widget_value_tree (d->popup);
+      d->popup = NULL;
+    }
+  unref_cl_data (d->cl_data);
+  xfree (d);
+}
+#endif
+
+void
+xg_modify_header_bar_widgets (GtkHeaderBar *hbawr, struct frame *f,
+                              widget_value *val, bool deep_p,
+                              GCallback select_cb, GCallback deactivate_cb,
+                              GCallback highlight_cb)
+{
+#ifndef HAVE_GTK4
+  GtkMenuButton *btn = NULL;
+  GtkMenu *menubar = NULL;
+  if (!gtk_window_has_toplevel_focus (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f))))
+    return;
+  gtk_container_foreach (GTK_CONTAINER (hbawr),
+                         (GtkCallback) gtk_find_gtk_menu_button, &btn);
+
+  if (!FRAME_DISPLAY_HEADER_BAR_MENU (f))
+    {
+      if (btn)
+        gtk_widget_destroy (GTK_WIDGET (btn));
+      return;
+    }
+
+  if (!btn)
+    {
+      btn = GTK_MENU_BUTTON (gtk_menu_button_new ());
+      gtk_button_set_image (
+        GTK_BUTTON (btn), gtk_image_new_from_icon_name ("open-menu-symbolic",
+                                                        GTK_ICON_SIZE_BUTTON));
+      menubar = GTK_MENU (gtk_menu_new ());
+      gtk_menu_button_set_popup (btn, GTK_WIDGET (menubar));
+      gtk_header_bar_pack_end (hbawr, GTK_WIDGET (btn));
+      gtk_widget_set_visible (GTK_WIDGET (btn), true);
+    }
+  else
+    {
+      menubar = gtk_menu_button_get_popup (btn);
+    }
+  xg_menu_cb_data *cl_data;
+  GList *list = gtk_container_get_children (GTK_CONTAINER (menubar));
+
+  cl_data
+    = g_make_cl_data (g_object_get_data (G_OBJECT (menubar), XG_FRAME_DATA), f,
+                      highlight_cb);
+
+  update_cl_data (cl_data, f, highlight_cb);
+
+  g_object_set_data_full (G_OBJECT (menubar), XG_FRAME_DATA, cl_data, NULL);
+
+  xg_update_menubar (GTK_WIDGET (menubar), f, &list, list, 0, val->contents,
+                     select_cb, deactivate_cb, highlight_cb, cl_data);
+
+  widget_value *cur;
+
+  /* Update all sub menus.
+     We must keep the submenus (GTK menu item widgets) since the
+     X Window in the XEvent that activates the menu are those widgets.  */
+
+  /* Update cl_data, menu_item things in F may have changed.  */
+  update_cl_data (cl_data, f, highlight_cb);
+
+  if (deep_p)
+    for (cur = val->contents; cur; cur = cur->next)
+      {
+        GList *iter;
+        GtkWidget *sub = 0;
+        GtkWidget *newsub;
+        GtkMenuItem *witem = 0;
+
+        /* Find sub menu that corresponds to val and update it.  */
+        for (iter = list; iter; iter = g_list_next (iter))
+          {
+            witem = GTK_MENU_ITEM (iter->data);
+            if (xg_item_label_same_p (witem, cur->name))
+              {
+                sub = gtk_menu_item_get_submenu (witem);
+                break;
+              }
+          }
+
+        newsub = xg_update_submenu (sub, f, cur->contents, select_cb,
+                                    deactivate_cb, highlight_cb, cl_data);
+        /* sub may still be NULL.  If we just updated non deep and added
+         a new menu bar item, it has no sub menu yet.  So we set the
+         newly created sub menu under witem.  */
+        if (newsub != sub && witem != 0)
+          {
+            gtk_set_screen (newsub, f);
+            gtk_menu_item_set_submenu (witem, newsub);
+          }
+      }
+  g_list_free (list);
+  gtk_container_forall (GTK_CONTAINER (menubar), xg_destroy_menu_item_if_empty,
+                        NULL);
+  gtk_widget_show_all (GTK_WIDGET (menubar));
+#else
+  if (!FRAME_DISPLAY_HEADER_BAR (f) ||
+      !gtk_widget_has_visible_focus (FRAME_GTK_WIDGET (f)))
+      return;
+  xg_notify_header_bar_menu_state_changed (f);
+  if (!hbawr)
+    return;
+  GtkMenuButton *btn = NULL;
+  gtk_container_foreach (GTK_CONTAINER (hbawr),
+                         (GtkCallback) gtk_find_gtk_menu_button, &btn);
+  if (btn)
+    {
+      if (gtk_menu_button_get_popover (btn))
+	gtk_menu_button_set_popover (btn, NULL);
+    }
+  else
+    return;
+  struct ppw_data *d = xmalloc (sizeof *d);
+  *d = (struct ppw_data) { val, select_cb, deactivate_cb, highlight_cb,
+                           make_cl_data (NULL, f, highlight_cb), f, false };
+  gtk_menu_button_set_create_popup_func (btn, popup_create_func, d, free_data);
+#endif
+}
+
+static void
+process_frame_action_button (GtkWidget *w, gpointer l)
+{
+  if (!GTK_IS_WIDGET (w))
+    emacs_abort ();
+  if (GTK_IS_BUTTON (w) && !GTK_IS_MENU_BUTTON (w)
+      && !xg_get_widget_pack_flag (w))
+    *(GList **) l = g_list_append (*(GList **) l, w);
+}
+
+static void
+process_frame_action_button_end (GtkWidget *w, gpointer l)
+{
+  if (!GTK_IS_WIDGET (w))
+    emacs_abort ();
+  if (GTK_IS_BUTTON (w) && !GTK_IS_MENU_BUTTON (w)
+      && xg_get_widget_pack_flag (w))
+    *(GList **) l = g_list_append (*(GList **) l, w);
+}
+
+static char *
+find_icon_from_name (char *name, GtkIconTheme *icon_theme, char **icon_name)
+{
+  if (name[0] == 'n' && name[1] == ':')
+    {
+      *icon_name = name + 2;
+      name = NULL;
+
+      if (!gtk_icon_theme_has_icon (icon_theme, *icon_name))
+        *icon_name = NULL;
+    }
+  else if (gtk_icon_theme_has_icon (icon_theme, name))
+    {
+      *icon_name = name;
+      name = NULL;
+    }
+  else
+    {
+      name = NULL;
+      *icon_name = NULL;
+    }
+
+  return name;
+}
+
+/* Callback function invoked when a tool bar item is pressed.
+   W is the button widget in the tool bar that got pressed,
+   CLIENT_DATA is an integer that is the index of the button in the
+   tool bar.  0 is the first button.  */
+
+static void
+xg_tool_bar_callback (GtkWidget *w, gpointer client_data)
+{
+  intptr_t idx = (intptr_t) client_data;
+  gpointer gmod = g_object_get_data (G_OBJECT (w), XG_TOOL_BAR_LAST_MODIFIER);
+  intptr_t mod = (intptr_t) gmod;
+
+  struct frame *f = g_object_get_data (G_OBJECT (w), XG_FRAME_DATA);
+  Lisp_Object key, frame;
+  struct input_event event;
+  EVENT_INIT (event);
+
+  if (!f || !f->n_tool_bar_items || NILP (f->tool_bar_items))
+    return;
+
+  idx *= TOOL_BAR_ITEM_NSLOTS;
+
+  key = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_KEY);
+  XSETFRAME (frame, f);
+
+  event.kind = TOOL_BAR_EVENT;
+  event.frame_or_window = frame;
+  event.arg = key;
+  /* Convert between the modifier bits GDK uses and the modifier bits
+     Emacs uses.  This assumes GDK and X masks are the same, which they are when
+     this is written.  */
+#ifndef HAVE_PGTK
+  event.modifiers = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod);
+#else
+  event.modifiers = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod);
+#endif
+  kbd_buffer_store_event (&event);
+
+  /* Return focus to the frame after we have clicked on a detached
+     tool bar button. */
+  FRAME_TERMINAL (f)->focus_frame_hook (f, false);
+}
+
+static gboolean
+xg_tool_bar_button_cb (GtkWidget *widget,
+#ifdef HAVE_GTK4
+                       GdkEvent *event,
+#else
+                       GdkEventButton *event,
+#endif
+                       gpointer user_data)
+{
+#ifdef HAVE_GTK4
+  GdkModifierType
+#else
+  intptr_t
+#endif
+    state
+#ifndef HAVE_GTK4
+    = event->state;
+#else
+    = gdk_event_get_modifier_state (event);
+#endif
+  gpointer ptr = (gpointer) state;
+  g_object_set_data (G_OBJECT (widget), XG_TOOL_BAR_LAST_MODIFIER, ptr);
+  return FALSE;
+}
+
+static Lisp_Object
+file_for_image (Lisp_Object image)
+{
+  Lisp_Object specified_file = Qnil;
+  Lisp_Object tail;
+
+  for (tail = XCDR (image);
+       NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail));
+       tail = XCDR (XCDR (tail)))
+    if (EQ (XCAR (tail), QCfile))
+      specified_file = XCAR (XCDR (tail));
+
+  return specified_file;
+}
+#ifdef HAVE_GTK3
+void
+update_frame_header_bar (struct frame *f)
+{
+  GtkHeaderBar *header_bar = FRAME_GTK_HEADER_BAR (f);
+  if (!header_bar)
+    return;
+  if (FRAME_PARENT_FRAME (f))
+    return;
+#ifndef HAVE_GTK4
+  if (!gtk_window_has_toplevel_focus (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f))))
+    return;
+#else
+  if (!gtk_widget_is_focus (FRAME_GTK_WIDGET (f)))
+    return;
+#endif
+  block_input ();
+  int n_tool_bar_items
+    = FRAME_DISPLAY_HEADER_BAR_ICONS (f) ? f->n_tool_bar_items : 0;
+  int n_start_tool_bar_items = n_tool_bar_items;
+  int n_end_tool_bar_items = 0;
+
+  GList *start_items = NULL;
+  GList *end_items = NULL;
+
+  if (!f->n_tool_bar_items || NILP (f->tool_bar_items)
+      || f->n_tool_bar_items < 0 || !n_tool_bar_items)
+    return;
+
+  for (int i = 0; i < f->n_tool_bar_items; ++i)
+    {
+#define PROP(IDX) AREF (f->tool_bar_items, i *TOOL_BAR_ITEM_NSLOTS + (IDX))
+      if (!Fequal (PROP (TOOL_BAR_ITEM_TYPE), Qt))
+        {
+          struct xg_tool_bar_entry *tbe
+            = xmalloc (sizeof (struct xg_tool_bar_entry));
+          tbe->icon = PROP (TOOL_BAR_ITEM_IMAGES);
+          tbe->idx = i;
+          tbe->title = PROP (TOOL_BAR_ITEM_LABEL);
+          tbe->help = PROP (TOOL_BAR_ITEM_HELP);
+          tbe->selected = !NILP (PROP (TOOL_BAR_ITEM_SELECTED_P));
+          tbe->enabled = !NILP (PROP (TOOL_BAR_ITEM_ENABLED_P));
+          tbe->type = PROP (TOOL_BAR_ITEM_TYPE);
+#undef PROP
+          if (!NILP (Fequal (tbe->title, build_string ("Save")))
+              || !NILP (Fequal (tbe->title, build_string ("Load init files"))))
+            {
+              end_items = g_list_append (end_items, tbe);
+            }
+          else
+            {
+              start_items = g_list_append (start_items, tbe);
+            }
+        }
+    }
+  n_start_tool_bar_items = g_list_length (start_items);
+  n_end_tool_bar_items = g_list_length (end_items);
+
+  GList *header_buttons_start = NULL;
+  GList *header_buttons_end = NULL;
+
+  gtk_container_foreach (GTK_CONTAINER (header_bar),
+                         process_frame_action_button, &header_buttons_start);
+  gtk_container_foreach (GTK_CONTAINER (header_bar),
+                         process_frame_action_button_end, &header_buttons_end);
+
+  if (!n_start_tool_bar_items)
+    for (; header_buttons_start;
+         header_buttons_start = header_buttons_start->next)
+      gtk_widget_destroy (header_buttons_start->data);
+  if (!n_end_tool_bar_items)
+    for (; header_buttons_end; header_buttons_end = header_buttons_end->next)
+      gtk_widget_destroy (header_buttons_end->data);
+  if (!n_start_tool_bar_items)
+    {
+      if (header_buttons_end)
+        g_list_free (header_buttons_end);
+      if (header_buttons_start)
+        g_list_free (header_buttons_start);
+      if (end_items)
+        g_list_free (end_items);
+      return;
+    }
+
+  while (g_list_length (header_buttons_start) > n_start_tool_bar_items)
+    {
+      gpointer d = g_list_last (header_buttons_start)->data;
+
+      if (!GTK_IS_WIDGET (d))
+        emacs_abort ();
+
+      gtk_widget_destroy (GTK_WIDGET (d));
+      header_buttons_start = g_list_remove (header_buttons_start, d);
+    }
+
+  while (g_list_length (header_buttons_start) < n_start_tool_bar_items)
+    {
+      GtkWidget *tb = gtk_button_new ();
+#ifndef HAVE_GTK4
+      g_signal_connect (tb, "button-release-event",
+                        G_CALLBACK (xg_tool_bar_button_cb), NULL);
+#endif
+      g_object_set_data (G_OBJECT (tb), XG_FRAME_DATA, f);
+      header_buttons_start = g_list_append (header_buttons_start, tb);
+      gtk_header_bar_pack_start (header_bar, tb);
+      xg_set_widget_pack_flag (GTK_WIDGET (tb), 0);
+#ifndef HAVE_GTK4
+      gtk_widget_set_visible (GTK_WIDGET (tb), true);
+#endif
+    }
+
+  while (g_list_length (header_buttons_end) > n_end_tool_bar_items)
+    {
+      gpointer d = g_list_last (header_buttons_end)->data;
+
+      if (!GTK_IS_WIDGET (d))
+        emacs_abort ();
+
+      gtk_widget_destroy (GTK_WIDGET (d));
+      header_buttons_end = g_list_remove (header_buttons_end, d);
+    }
+
+  while (g_list_length (header_buttons_end) < n_end_tool_bar_items)
+    {
+      GtkWidget *tb = gtk_button_new ();
+#ifndef HAVE_GTK4
+      g_signal_connect (tb, "button-release-event",
+                        G_CALLBACK (xg_tool_bar_button_cb), NULL);
+#endif
+      g_object_set_data (G_OBJECT (tb), XG_FRAME_DATA, f);
+      header_buttons_end = g_list_append (header_buttons_end, tb);
+      gtk_header_bar_pack_end (header_bar, tb);
+      xg_set_widget_pack_flag (GTK_WIDGET (tb), 1);
+      gtk_widget_set_visible (GTK_WIDGET (tb), true);
+    }
+
+  bool flag = 0;
+  GList *l = header_buttons_start;
+  GList *r = start_items;
+
+loop:;
+#pragma GCC diagnostic ignored "-Wunused-value"
+  for (; (l && r); (l = l->next) && (r = r->next))
+    {
+#pragma GCC diagnostic push
+      GtkButton *btn = l->data;
+      struct xg_tool_bar_entry *e = r->data;
+      bool enabled_p = e->enabled;
+      bool selected_p = e->selected;
+      if (!btn)
+        continue;
+      if (EQ (e->type, Qt))
+        {
+          gtk_widget_set_visible (GTK_WIDGET (btn), false);
+        }
+      else
+        {
+          gtk_widget_set_visible (GTK_WIDGET (btn), true);
+          gtk_widget_set_sensitive (GTK_WIDGET (btn), enabled_p);
+          Lisp_Object image = e->icon;
+          Lisp_Object stock = Qnil;
+          char *icon_name = NULL;
+          char *stock_name = NULL;
+          int idx;
+          ptrdiff_t img_id;
+          g_object_ref (btn);
+          struct image *img;
+          if (!CONSP (image) && !valid_image_p (image))
+            {
+#ifndef HAVE_GTK4
+              gtk_button_set_image (btn, NULL);
+#else
+              gtk_container_remove (GTK_CONTAINER (btn),
+                                    gtk_bin_get_child (GTK_BIN (btn)));
+#endif
+              continue;
+            }
+          Lisp_Object specified_file = file_for_image (image);
+          if (!NILP (specified_file) && !NILP (Ffboundp (Qx_gtk_map_stock)))
+            stock = call1 (Qx_gtk_map_stock, specified_file);
+
+          if (CONSP (stock))
+            {
+              Lisp_Object itr;
+              for (itr = stock; CONSP (itr); itr = XCDR (itr))
+                {
+                  stock_name
+                    = find_icon_from_name (SSDATA (XCAR (itr)),
+#ifdef HAVE_GTK4
+                                           gtk_icon_theme_get_for_display (
+                                             gtk_widget_get_display (
+                                               GTK_WIDGET (header_bar))),
+#else
+                                           gtk_icon_theme_get_default (),
+#endif
+                                           &icon_name);
+                  if (stock_name || icon_name)
+                    break;
+                }
+            }
+          else if (STRINGP (stock))
+            {
+              stock_name = find_icon_from_name (SSDATA (stock),
+#ifdef HAVE_GTK4
+                                                gtk_icon_theme_get_for_display (
+                                                  gtk_widget_get_display (
+                                                    GTK_WIDGET (header_bar))),
+#else
+                                                gtk_icon_theme_get_default (),
+#endif
+                                                &icon_name);
+            }
+
+          if (stock_name || icon_name)
+            {
+              if (stock_name)
+                stock_name = g_strconcat (stock_name, "-symbolic", NULL);
+              if (icon_name)
+                icon_name = g_strconcat (icon_name, "-symbolic", NULL);
+            }
+
+          if (stock_name == NULL && icon_name == NULL)
+            {
+              if (VECTORP (image))
+                {
+                  if (enabled_p)
+                    idx = (selected_p ? TOOL_BAR_IMAGE_ENABLED_SELECTED
+                                      : TOOL_BAR_IMAGE_ENABLED_DESELECTED);
+                  else
+                    idx = (selected_p ? TOOL_BAR_IMAGE_DISABLED_SELECTED
+                                      : TOOL_BAR_IMAGE_DISABLED_DESELECTED);
+
+                  eassert (ASIZE (image) >= idx);
+                  image = AREF (image, idx);
+                }
+              else
+                idx = -1;
+
+              img_id = lookup_image (f, image);
+              img = IMAGE_FROM_ID (f, img_id);
+              prepare_image_for_display (f, img);
+            }
+
+          GtkImage *w = NULL;
+
+          if (stock_name || icon_name)
+            {
+              if (stock_name)
+                {
+#ifndef HAVE_GTK4
+                  w = GTK_IMAGE (
+                    gtk_image_new_from_stock (stock_name,
+                                              GTK_ICON_SIZE_BUTTON));
+#endif
+                }
+              else
+                {
+                  w = GTK_IMAGE (
+#ifndef HAVE_GTK4
+                    gtk_image_new_from_icon_name (icon_name,
+                                                  GTK_ICON_SIZE_BUTTON));
+#else
+                    gtk_image_new_from_icon_name (icon_name));
+#endif
+                }
+            }
+          else
+            {
+              if (!img->load_failed_p)
+                {
+                  w = GTK_IMAGE (
+#ifndef HAVE_GTK4
+                    xg_get_image_for_pixmap (f, img, GTK_WIDGET (header_bar),
+                                             NULL)
+#else
+                    egtk_get_image_for_pixmap (f, img, GTK_WIDGET (header_bar),
+                                               NULL)
+#endif
+                  );
+                }
+            }
+          if (w)
+	    gtk_widget_set_visible (GTK_WIDGET (w), true);
+#ifndef HAVE_GTK4
+          gtk_button_set_image (btn, NULL);
+#else
+          if (GTK_IS_LABEL (gtk_bin_get_child (GTK_BIN (btn))))
+            gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (btn)));
+#endif
+
+          Lisp_Object name = e->title;
+
+          if (name
+              && (!NILP (Fequal (name, build_string ("Save")))
+                  || !NILP (Fequal (name, build_string ("Help")))
+                  || !NILP (Fequal (name, build_string ("Load init files")))))
+            {
+              gtk_button_set_label (GTK_BUTTON (btn), SSDATA (name));
+            }
+          else
+            {
+              gtk_button_set_label (btn, NULL);
+#ifdef HAVE_GTK4
+              gtk_widget_remove_css_class (GTK_WIDGET (btn), "text-button");
+              gtk_widget_add_css_class (GTK_WIDGET (btn), "image-button");
+	      if (w)
+		gtk_container_add (GTK_CONTAINER (btn), GTK_WIDGET (w));
+#else
+              gtk_button_set_image (btn, GTK_WIDGET (w));
+#endif
+            }
+
+          gulong ptr;
+          if ((ptr
+               = (gulong) g_object_get_data (G_OBJECT (btn), "click-handler")))
+            {
+              g_signal_handler_disconnect (G_OBJECT (btn), ptr);
+            }
+          gulong l = g_signal_connect (G_OBJECT (btn), "clicked",
+                                       G_CALLBACK (xg_tool_bar_callback),
+                                       (gpointer) (intptr_t) e->idx);
+          g_object_set_data (G_OBJECT (btn), "click-handler", (gpointer) l);
+
+          if (e->help && !NILP (e->help))
+            gtk_widget_set_tooltip_text (GTK_WIDGET (btn), SSDATA (e->help));
+          else
+            gtk_widget_set_tooltip_text (GTK_WIDGET (btn), NULL);
+          if (stock_name)
+            g_free (stock_name);
+          if (icon_name)
+            g_free (icon_name);
+          g_object_unref (btn);
+        }
+    }
+  if (!flag)
+    {
+      l = header_buttons_end;
+      r = end_items;
+      flag = true;
+      goto loop;
+    }
+  g_list_free (header_buttons_start);
+  g_list_free (header_buttons_end);
+  g_list_free_full (end_items, xfree);
+  g_list_free_full (start_items, xfree);
+  unblock_input ();
+}
+#endif
+#endif
+DEFUN ("display-gtk-about-screen", Fdisplay_gtk_about_screen,
+       Sdisplay_gtk_about_screen, 0, 1, "",
+       doc: /* Display a GTK about screen for FRAME */)
+(Lisp_Object frame)
+{
+  if (!frame || NILP (frame))
+    frame = Fselected_frame ();
+  if (NILP (frame))
+    error ("No valid frame could be found");
+
+  CHECK_LIVE_FRAME (frame);
+  show_about_dialog (0, XFRAME (frame));
+  return Qnil;
+}
+
+DEFUN ("display-gtk-header-menu", Fdisplay_gtk_header_menu,
+       Sdisplay_gtk_header_menu, 0, 1, "",
+       doc: /* Display the header bar menu */)
+(Lisp_Object frame)
+{
+  if (!frame || NILP (frame))
+    frame = Fselected_frame ();
+  if (NILP (frame))
+    error ("No valid frame could be found");
+
+  CHECK_LIVE_FRAME (frame);
+
+  struct frame *f = XFRAME (frame);
+  check_pgtk_display_info (frame);
+#ifndef HAVE_GTK4
+
+#ifdef HAVE_GTK3
+  if (!FRAME_DISPLAY_HEADER_BAR_MENU (f))
+    error ("There is no header bar menu for frame %s", SSDATA (f->name));
+#endif
+
+  if (!FRAME_PGTK_P (f) || FRAME_X_P (f))
+    error ("Must be a pgtk or X frame");
+
+#ifdef HAVE_GTK3
+  GtkHeaderBar *hbar = FRAME_GTK_HEADER_BAR (f);
+  update_frame_header_bar (f);
+  GtkMenuButton *btn = NULL;
+  gtk_container_foreach (GTK_CONTAINER (hbar),
+                         (GtkCallback) gtk_find_gtk_menu_button, &btn);
+
+  if (!btn)
+    return Qnil;
+#ifndef HAVE_GTK4
+  gtk_button_clicked (GTK_BUTTON (btn));
+#else
+  gtk_menu_button_popup (btn);
+#endif
+#else
+  {
+    if (FRAME_EXTERNAL_MENU_BAR (f))
+      {
+        GList *children = gtk_container_get_children (
+          GTK_CONTAINER (FRAME_X_OUTPUT (f)->menubar_widget));
+        GtkWidget *w = children->data;
+        g_list_free (children);
+        gtk_menu_shell_activate_item (FRAME_X_OUTPUT (f)->menubar_widget, w,
+                                      true);
+      }
+  }
+#endif
+#endif
+#ifdef HAVE_GTK4
+  if (FRAME_MENU_BAR (f))
+    frame_activate_menu_bar (f);
+  else if (FRAME_DISPLAY_HEADER_BAR (f) &&
+	   FRAME_GTK_HEADER_BAR (f) &&
+	   gtk_widget_is_visible (GTK_WIDGET (FRAME_GTK_HEADER_BAR (f))))
+    {
+      if (!FRAME_GTK_HEADER_BAR (f))
+	return Qnil;
+      GtkMenuButton *btn = NULL;
+      gtk_container_foreach (GTK_CONTAINER (FRAME_GTK_HEADER_BAR (f)),
+			     (GtkCallback) gtk_find_gtk_menu_button, &btn);
+
+      if (!btn)
+	return Qnil;
+      gtk_menu_button_popup (btn);
+      return Qnil;
+    }
+  else
+#endif
+    call2 (intern_c_string ("mouse-popup-menubar"), Qnil, Qnil);
+  return Qnil;
+}
+
+void
+syms_of_gtkinter (void)
+{
+  defsubr (&Sdisplay_gtk_about_screen);
+  defsubr (&Sdisplay_gtk_header_menu);
+}
diff --git a/src/gtkinter.h b/src/gtkinter.h
new file mode 100644
index 0000000000..7b779d1713
--- /dev/null
+++ b/src/gtkinter.h
@@ -0,0 +1,109 @@
+/* GTK related routines
+
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef GTK_INTER_H
+#define GTK_INTER_H
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include "lisp.h"
+#ifdef HAVE_GTK4
+#include "pgtksubr.h"
+#else
+#include "gtkutil.h"
+#endif
+#include "frame.h"
+
+#ifdef HAVE_GTK3
+
+typedef struct
+{
+  struct frame *f;
+  int index;
+} xg_headerbar_item_callback_info;
+
+#endif
+
+extern GtkAboutDialog *
+show_about_dialog (gpointer thing, struct frame *f);
+
+extern void syms_of_gtkinter (void);
+
+extern GtkDialog *
+#ifndef HAVE_GTK4
+build_dialog_n_items (char *fmt,
+		      widget_value *wv,
+		      GCallback select_cb,
+                      GCallback deactivate_cb,
+		      GCallback dialog_delete_callback);
+#else
+build_dialog_n_items (char *fmt,
+		      widget_value *wv,
+		      GCallback select_cb,
+                      GCallback deactivate_cb);
+#endif
+#ifndef HAVE_GTK4
+extern GtkDialog *
+build_dialog_3_items (Lisp_Object fmt, Lisp_Object name_a, Lisp_Object name_b,
+                      Lisp_Object name_c, GCallback select_cb,
+                      GCallback deactivate_cb, widget_value *wv_a,
+                      widget_value *wv_b, widget_value *wv_c,
+                      GCallback dialog_delete_callback);
+
+extern GtkMessageDialog *
+build_dialog_2_items (Lisp_Object fmt, Lisp_Object name_a,
+                      Lisp_Object name_b, GCallback select_cb,
+                      GCallback deactivate_cb, widget_value *wv_a,
+		      widget_value *wv_b, GCallback dialog_delete_callback);
+
+extern GtkMessageDialog *
+build_dialog_1_item (Lisp_Object fmt, Lisp_Object name_a,
+		     GCallback select_cb,
+		     GCallback deactivate_cb, widget_value *wv_a,
+		     GCallback dialog_delete_callback);
+#endif
+#ifdef HAVE_GTK3
+
+extern void update_frame_header_bar (struct frame *f);
+
+extern void
+xg_notify_header_bar_menu_state_changed (struct frame *f);
+
+extern void
+xg_modify_header_bar_widgets (GtkHeaderBar *w, struct frame *f,
+			      widget_value *val, bool deep_p,
+			      GCallback select_cb, GCallback deactivate_cb,
+			      GCallback highlight_cb);
+
+struct xg_tool_bar_entry
+{
+  Lisp_Object title;
+  Lisp_Object help;
+  Lisp_Object icon;
+  bool enabled;
+  bool selected;
+  Lisp_Object type;
+  int idx;
+};
+
+#endif
+
+#endif
diff --git a/src/gtkutil.c b/src/gtkutil.c
index 681f86f51b..4f49e4e7c2 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -19,10 +19,11 @@ Copyright (C) 2003-2020 Free Software Foundation, Inc.
 
 #include <config.h>
 
-#ifdef USE_GTK
+#if defined(USE_GTK) && !defined (HAVE_GTK4)
 #include <float.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <math.h>
 
 #include <c-ctype.h>
 
@@ -30,13 +31,24 @@ Copyright (C) 2003-2020 Free Software Foundation, Inc.
 #include "dispextern.h"
 #include "frame.h"
 #include "systime.h"
+#ifndef HAVE_PGTK
 #include "xterm.h"
+#define xp x
+typedef struct x_output xp_output;
+#else
+#define xp pgtk
+typedef struct pgtk_output xp_output;
+#endif
 #include "blockinput.h"
 #include "window.h"
 #include "gtkutil.h"
 #include "termhooks.h"
 #include "keyboard.h"
 #include "coding.h"
+#ifndef PGTK_TRACE
+#define PGTK_TRACE(fmt, ...) ((void) 0)
+#define PGTK_BACKTRACE() ((void) 0)
+#endif
 
 #include <gdk/gdkkeysyms.h>
 
@@ -45,10 +57,14 @@ Copyright (C) 2003-2020 Free Software Foundation, Inc.
 #endif
 
 #ifdef HAVE_GTK3
+#ifndef HAVE_PGTK
 #include <gtk/gtkx.h>
+#endif
 #include "emacsgtkfixed.h"
 #endif
 
+#include "gtkinter.h"
+
 #ifdef HAVE_XDBE
 #include <X11/extensions/Xdbe.h>
 #endif
@@ -107,7 +123,7 @@ #define TB_INFO_KEY "xg_frame_tb_info"
 
 \f
 /***********************************************************************
-                      Display handling functions
+		      Display handling functions
  ***********************************************************************/
 
 /* Keep track of the default display, or NULL if there is none.  Emacs
@@ -122,16 +138,28 @@ #define TB_INFO_KEY "xg_frame_tb_info"
 static void
 xg_set_screen (GtkWidget *w, struct frame *f)
 {
+#ifndef HAVE_PGTK
   if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ())
     {
       GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
       GdkScreen *gscreen = gdk_display_get_default_screen (gdpy);
 
       if (GTK_IS_MENU (w))
-        gtk_menu_set_screen (GTK_MENU (w), gscreen);
+	gtk_menu_set_screen (GTK_MENU (w), gscreen);
+      else
+	gtk_window_set_screen (GTK_WINDOW (w), gscreen);
+    }
+#else
+  if (FRAME_X_DISPLAY(f) != DEFAULT_GDK_DISPLAY ())
+    {
+      GdkScreen *gscreen = gdk_display_get_default_screen (FRAME_X_DISPLAY(f));
+
+      if (GTK_IS_MENU (w))
+	gtk_menu_set_screen (GTK_MENU (w), gscreen);
       else
-        gtk_window_set_screen (GTK_WINDOW (w), gscreen);
+	gtk_window_set_screen (GTK_WINDOW (w), gscreen);
     }
+#endif
 }
 
 
@@ -143,12 +171,20 @@ xg_set_screen (GtkWidget *w, struct frame *f)
    multiple displays.  */
 
 void
+#ifndef HAVE_PGTK
 xg_display_open (char *display_name, Display **dpy)
+#else
+xg_display_open (char *display_name, GdkDisplay **dpy)
+#endif
 {
   GdkDisplay *gdpy;
 
   unrequest_sigio ();  /* See comment in x_display_ok, xterm.c.  */
+#ifndef HAVE_PGTK
   gdpy = gdk_display_open (display_name);
+#else
+  gdpy = gdk_display_open (strlen(display_name) == 0 ? NULL : display_name);
+#endif
   request_sigio ();
   if (!gdpy_def && gdpy)
     {
@@ -157,7 +193,11 @@ xg_display_open (char *display_name, Display **dpy)
 					       gdpy);
     }
 
+#ifndef HAVE_PGTK
   *dpy = gdpy ? GDK_DISPLAY_XDISPLAY (gdpy) : NULL;
+#else
+  *dpy = gdpy;
+#endif
 }
 
 /* Scaling/HiDPI functions. */
@@ -189,8 +229,13 @@ xg_get_scale (struct frame *f)
 /* Close display DPY.  */
 
 void
+#ifndef HAVE_PGTK
 xg_display_close (Display *dpy)
+#else
+xg_display_close (GdkDisplay *gdpy)
+#endif
 {
+#ifndef HAVE_PGTK
   GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);
 
   /* If this is the default display, try to change it before closing.
@@ -203,50 +248,84 @@ xg_display_close (Display *dpy)
 
       /* Find another display.  */
       for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
-        if (dpyinfo->display != dpy)
-          {
+	if (dpyinfo->display != dpy)
+	  {
 	    gdpy_new = gdk_x11_lookup_xdisplay (dpyinfo->display);
 	    gdk_display_manager_set_default_display (gdk_display_manager_get (),
 						     gdpy_new);
-            break;
-          }
+	    break;
+	  }
       gdpy_def = gdpy_new;
     }
 
   gdk_display_close (gdpy);
+
+#else
+
+  /* If this is the default display, try to change it before closing.
+     If there is no other display to use, gdpy_def is set to NULL, and
+     the next call to xg_display_open resets the default display.  */
+  if (gdk_display_get_default () == gdpy)
+    {
+      struct pgtk_display_info *dpyinfo;
+      GdkDisplay *gdpy_new = NULL;
+
+      /* Find another display.  */
+      for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
+	if (dpyinfo->gdpy != gdpy)
+	  {
+	    gdpy_new = dpyinfo->gdpy;
+	    gdk_display_manager_set_default_display (gdk_display_manager_get (),
+						     gdpy_new);
+	    break;
+	  }
+      gdpy_def = gdpy_new;
+    }
+
+  gdk_display_close (gdpy);
+#endif
 }
 
 \f
 /***********************************************************************
-                      Utility functions
+		      Utility functions
  ***********************************************************************/
 
 /* Create and return the cursor to be used for popup menus and
    scroll bars on display DPY.  */
 
 GdkCursor *
+#ifndef HAVE_PGTK
 xg_create_default_cursor (Display *dpy)
+#else
+xg_create_default_cursor (GdkDisplay *gdpy)
+#endif
 {
+#ifndef HAVE_PGTK
   GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);
+#endif
   return gdk_cursor_new_for_display (gdpy, GDK_LEFT_PTR);
 }
 
+#ifndef HAVE_PGTK
 /* Apply GMASK to GPIX and return a GdkPixbuf with an alpha channel.  */
 
 static GdkPixbuf *
 xg_get_pixbuf_from_pix_and_mask (struct frame *f,
-                                 Pixmap pix,
-                                 Pixmap mask)
+				 Pixmap pix,
+				 Pixmap mask)
 {
   GdkPixbuf *icon_buf = 0;
   int iunused;
   Window wunused;
   unsigned int width, height, depth, uunused;
 
+#ifndef HAVE_PGTK
   if (FRAME_DISPLAY_INFO (f)->red_bits != 8)
     return 0;
+
   XGetGeometry (FRAME_X_DISPLAY (f), pix, &wunused, &iunused, &iunused,
-                &width, &height, &uunused, &depth);
+		&width, &height, &uunused, &depth);
   if (depth != 24)
     return 0;
   XImage *xim = XGetImage (FRAME_X_DISPLAY (f), pix, 0, 0, width, height,
@@ -276,10 +355,21 @@ xg_get_pixbuf_from_pix_and_mask (struct frame *f,
 	XDestroyImage (xmm);
       XDestroyImage (xim);
     }
+#else
+  width = pix->width;
+  height = pix->height;
+  depth = pix->bits_per_pixel;
+
+  icon_buf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
+
+
+#endif
 
   return icon_buf;
 }
 
+#endif
+
 #if defined USE_CAIRO && !defined HAVE_GTK3
 static GdkPixbuf *
 xg_get_pixbuf_from_surface (cairo_surface_t *surface)
@@ -329,7 +419,6 @@ xg_get_pixbuf_from_surface (cairo_surface_t *surface)
   return icon_buf;
 }
 #endif	/* USE_CAIRO && !HAVE_GTK3 */
-
 static Lisp_Object
 file_for_image (Lisp_Object image)
 {
@@ -356,11 +445,11 @@ file_for_image (Lisp_Object image)
    If OLD_WIDGET is NULL, a new widget is constructed and returned.
    If OLD_WIDGET is not NULL, that widget is modified.  */
 
-static GtkWidget *
+GtkWidget *
 xg_get_image_for_pixmap (struct frame *f,
-                         struct image *img,
-                         GtkWidget *widget,
-                         GtkImage *old_widget)
+			 struct image *img,
+			 GtkWidget *widget,
+			 GtkImage *old_widget)
 {
 #ifdef USE_CAIRO
   cairo_surface_t *surface;
@@ -383,13 +472,14 @@ xg_get_image_for_pixmap (struct frame *f,
     {
       char *encoded_file = SSDATA (ENCODE_FILE (file));
       if (! old_widget)
-        old_widget = GTK_IMAGE (gtk_image_new_from_file (encoded_file));
+	old_widget = GTK_IMAGE (gtk_image_new_from_file (encoded_file));
       else
-        gtk_image_set_from_file (old_widget, encoded_file);
+	gtk_image_set_from_file (old_widget, encoded_file);
 
       return GTK_WIDGET (old_widget);
     }
 
+
   /* No file, do the image handling ourselves.  This will look very bad
      on a monochrome display, and sometimes bad on all displays with
      certain themes.  */
@@ -404,9 +494,9 @@ xg_get_image_for_pixmap (struct frame *f,
     {
 #ifdef HAVE_GTK3
       if (! old_widget)
-        old_widget = GTK_IMAGE (gtk_image_new_from_surface (surface));
+	old_widget = GTK_IMAGE (gtk_image_new_from_surface (surface));
       else
-        gtk_image_set_from_surface (old_widget, surface);
+	gtk_image_set_from_surface (old_widget, surface);
 #else  /* !HAVE_GTK3 */
       GdkPixbuf *icon_buf = xg_get_pixbuf_from_surface (surface);
 
@@ -436,13 +526,14 @@ xg_get_image_for_pixmap (struct frame *f,
   if (icon_buf)
     {
       if (! old_widget)
-        old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf));
+	old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf));
       else
-        gtk_image_set_from_pixbuf (old_widget, icon_buf);
+	gtk_image_set_from_pixbuf (old_widget, icon_buf);
 
       g_object_unref (G_OBJECT (icon_buf));
     }
 #endif
+  PGTK_TRACE("test########");
 
   return GTK_WIDGET (old_widget);
 }
@@ -471,7 +562,7 @@ xg_set_cursor (GtkWidget *w, GdkCursor *cursor)
 
 /* Insert NODE into linked LIST.  */
 
-static void
+void
 xg_list_insert (xg_list_node *list, xg_list_node *node)
 {
   xg_list_node *list_start = list->next;
@@ -530,20 +621,20 @@ get_utf8_string (const char *str)
       GError *err = NULL;
 
       while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
-                                       &bytes_written, &err))
-             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
-        {
-          ++nr_bad;
-          p += bytes_written+1;
-          g_error_free (err);
-          err = NULL;
-        }
+				       &bytes_written, &err))
+	     && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+	{
+	  ++nr_bad;
+	  p += bytes_written+1;
+	  g_error_free (err);
+	  err = NULL;
+	}
 
       if (err)
-        {
-          g_error_free (err);
-          err = NULL;
-        }
+	{
+	  g_error_free (err);
+	  err = NULL;
+	}
       if (cp) g_free (cp);
 
       len = strlen (str);
@@ -556,27 +647,27 @@ get_utf8_string (const char *str)
       p = (unsigned char *)str;
 
       while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
-                                       &bytes_written, &err))
-             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
-        {
-          memcpy (up, p, bytes_written);
-          up += bytes_written;
-          up += sprintf (up, "\\%03o", p[bytes_written]);
-          p += bytes_written + 1;
-          g_error_free (err);
-          err = NULL;
-        }
+				       &bytes_written, &err))
+	     && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+	{
+	  memcpy (up, p, bytes_written);
+	  up += bytes_written;
+	  up += sprintf (up, "\\%03o", p[bytes_written]);
+	  p += bytes_written + 1;
+	  g_error_free (err);
+	  err = NULL;
+	}
 
       if (cp)
-        {
-          strcpy (up, cp);
-          g_free (cp);
-        }
+	{
+	  strcpy (up, cp);
+	  g_free (cp);
+	}
       if (err)
-        {
-          g_error_free (err);
-          err = NULL;
-        }
+	{
+	  g_error_free (err);
+	  err = NULL;
+	}
     }
   return utf8_str;
 }
@@ -587,8 +678,8 @@ get_utf8_string (const char *str)
 
 bool
 xg_check_special_colors (struct frame *f,
-                         const char *color_name,
-                         Emacs_Color *color)
+			 const char *color_name,
+			 Emacs_Color *color)
 {
   bool success_p = 0;
   bool get_bg = strcmp ("gtk_selection_bg_color", color_name) == 0;
@@ -603,30 +694,51 @@ xg_check_special_colors (struct frame *f,
     GtkStyleContext *gsty
       = gtk_widget_get_style_context (FRAME_GTK_OUTER_WIDGET (f));
     GdkRGBA col;
+#ifndef HAVE_PGTK
     char buf[sizeof "rgb://rrrr/gggg/bbbb"];
+#else
+    char buf[sizeof "rgb(255,255,255)"];
+#endif
     int state = GTK_STATE_FLAG_SELECTED|GTK_STATE_FLAG_FOCUSED;
     if (get_fg)
       gtk_style_context_get_color (gsty, state, &col);
     else
       {
-        GdkRGBA *c;
-        /* FIXME: Retrieving the background color is deprecated in
-           GTK+ 3.16.  New versions of GTK+ don't use the concept of a
-           single background color any more, so we shouldn't query for
-           it.  */
-        gtk_style_context_get (gsty, state,
-                               GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &c,
-                               NULL);
-        col = *c;
-        gdk_rgba_free (c);
+	GdkRGBA *c;
+	/* FIXME: Retrieving the background color is deprecated in
+	   GTK+ 3.16.  New versions of GTK+ don't use the concept of a
+	   single background color any more, so we shouldn't query for
+	   it.  */
+	gtk_style_context_get (gsty, state,
+			       GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &c,
+			       NULL);
+	col = *c;
+	gdk_rgba_free (c);
       }
 
+#ifndef HAVE_PGTK
     unsigned short
       r = col.red * 65535,
       g = col.green * 65535,
       b = col.blue * 65535;
+#else
+    unsigned short
+      r = round (col.red * 255.0),
+      g = round (col.green * 255.0),
+      b = round (col.blue * 255.0);
+#endif
+#ifndef HAVE_PGTK
     sprintf (buf, "rgb:%04x/%04x/%04x", r, g, b);
+#else
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+    sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+#endif
+#ifndef HAVE_PGTK
     success_p = x_parse_color (f, buf, color) != 0;
+#else
+    success_p = pgtk_parse_color (buf, color) != 0;
+#endif
 #else
     GtkStyle *gsty = gtk_widget_get_style (FRAME_GTK_WIDGET (f));
     GdkColor *grgb = get_bg
@@ -648,7 +760,7 @@ xg_check_special_colors (struct frame *f,
 
 \f
 /***********************************************************************
-                              Tooltips
+			      Tooltips
  ***********************************************************************/
 /* Gtk+ calls this callback when the parent of our tooltip dummy changes.
    We use that to pop down the tooltip.  This happens if Gtk+ for some
@@ -656,11 +768,11 @@ xg_check_special_colors (struct frame *f,
 
 static void
 hierarchy_ch_cb (GtkWidget *widget,
-                 GtkWidget *previous_toplevel,
-                 gpointer   user_data)
+		 GtkWidget *previous_toplevel,
+		 gpointer   user_data)
 {
   struct frame *f = user_data;
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
 
   if (! top || ! GTK_IS_WINDOW (top))
@@ -675,14 +787,14 @@ hierarchy_ch_cb (GtkWidget *widget,
 
 static gboolean
 qttip_cb (GtkWidget  *widget,
-          gint        xpos,
-          gint        ypos,
-          gboolean    keyboard_mode,
-          GtkTooltip *tooltip,
-          gpointer    user_data)
+	  gint        xpos,
+	  gint        ypos,
+	  gboolean    keyboard_mode,
+	  GtkTooltip *tooltip,
+	  gpointer    user_data)
 {
   struct frame *f = user_data;
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   if (x->ttip_widget == NULL)
     {
       GtkWidget *p;
@@ -700,11 +812,11 @@ qttip_cb (GtkWidget  *widget,
       p = gtk_widget_get_parent (x->ttip_lbl);
       list = gtk_container_get_children (GTK_CONTAINER (p));
       for (iter = list; iter; iter = g_list_next (iter))
-        {
-          GtkWidget *w = GTK_WIDGET (iter->data);
-          if (GTK_IS_LABEL (w))
-            gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
-        }
+	{
+	  GtkWidget *w = GTK_WIDGET (iter->data);
+	  if (GTK_IS_LABEL (w))
+	    gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
+	}
       g_list_free (list);
 
       /* ATK needs an empty title for some reason.  */
@@ -714,7 +826,7 @@ qttip_cb (GtkWidget  *widget,
       gtk_widget_realize (x->ttip_lbl);
 
       g_signal_connect (x->ttip_lbl, "hierarchy-changed",
-                        G_CALLBACK (hierarchy_ch_cb), f);
+			G_CALLBACK (hierarchy_ch_cb), f);
     }
 
   return FALSE;
@@ -725,11 +837,14 @@ qttip_cb (GtkWidget  *widget,
 
 bool
 xg_prepare_tooltip (struct frame *f,
-                    Lisp_Object string,
-                    int *width,
-                    int *height)
+		    Lisp_Object string,
+		    int *width,
+		    int *height)
 {
-  struct x_output *x = f->output_data.x;
+#ifndef USE_GTK_TOOLTIP
+  return 0;
+#else
+  xp_output *x = f->output_data.xp;
   GtkWidget *widget;
   GdkWindow *gwin;
   GdkScreen *screen;
@@ -753,25 +868,30 @@ xg_prepare_tooltip (struct frame *f,
       g_object_set (settings, "gtk-enable-tooltips", FALSE, NULL);
       /* Record that we disabled it so it can be enabled again.  */
       g_object_set_data (G_OBJECT (x->ttip_window), "restore-tt",
-                         (gpointer)f);
+			 (gpointer)f);
     }
 
   /* Prevent Gtk+ from hiding tooltip on mouse move and such.  */
   g_object_set_data (G_OBJECT
-                     (gtk_widget_get_display (GTK_WIDGET (x->ttip_window))),
-                     "gdk-display-current-tooltip", NULL);
+		     (gtk_widget_get_display (GTK_WIDGET (x->ttip_window))),
+		     "gdk-display-current-tooltip", NULL);
 
   /* Put our dummy widget in so we can get callbacks for unrealize and
      hierarchy-changed.  */
   gtk_tooltip_set_custom (x->ttip_widget, widget);
   gtk_tooltip_set_text (x->ttip_widget, SSDATA (encoded_string));
+#ifdef HAVE_GTK3
   gtk_widget_get_preferred_size (GTK_WIDGET (x->ttip_window), NULL, &req);
+#else
+  gtk_widget_get_requisition (GTK_WIDGET (x->ttip_window), &req);
+#endif
   if (width) *width = req.width;
   if (height) *height = req.height;
 
   unblock_input ();
 
   return TRUE;
+#endif
 }
 
 /* Show the tooltip at ROOT_X and ROOT_Y.
@@ -780,15 +900,23 @@ xg_prepare_tooltip (struct frame *f,
 void
 xg_show_tooltip (struct frame *f, int root_x, int root_y)
 {
-  struct x_output *x = f->output_data.x;
+#ifdef USE_GTK_TOOLTIP
+  xp_output *x = f->output_data.xp;
   if (x->ttip_window)
     {
       block_input ();
+#ifndef HAVE_PGTK
       gtk_window_move (x->ttip_window, root_x / xg_get_scale (f),
 		       root_y / xg_get_scale (f));
       gtk_widget_show (GTK_WIDGET (x->ttip_window));
+#else
+      gtk_widget_show (GTK_WIDGET (x->ttip_window));
+      gtk_window_move (x->ttip_window, root_x / xg_get_scale (f),
+		       root_y / xg_get_scale (f));
+#endif
       unblock_input ();
     }
+#endif
 }
 
 
@@ -798,25 +926,27 @@ xg_show_tooltip (struct frame *f, int root_x, int root_y)
 bool
 xg_hide_tooltip (struct frame *f)
 {
-  if (f->output_data.x->ttip_window)
+#ifdef USE_GTK_TOOLTIP
+  if (f->output_data.xp->ttip_window)
     {
-      GtkWindow *win = f->output_data.x->ttip_window;
+      GtkWindow *win = f->output_data.xp->ttip_window;
 
       block_input ();
       gtk_widget_hide (GTK_WIDGET (win));
 
       if (g_object_get_data (G_OBJECT (win), "restore-tt"))
-        {
-          GdkWindow *gwin = gtk_widget_get_window (GTK_WIDGET (win));
-          GdkScreen *screen = gdk_window_get_screen (gwin);
-          GtkSettings *settings = gtk_settings_get_for_screen (screen);
-          g_object_set (settings, "gtk-enable-tooltips", TRUE, NULL);
-        }
+	{
+	  GdkWindow *gwin = gtk_widget_get_window (GTK_WIDGET (win));
+	  GdkScreen *screen = gdk_window_get_screen (gwin);
+	  GtkSettings *settings = gtk_settings_get_for_screen (screen);
+	  g_object_set (settings, "gtk-enable-tooltips", TRUE, NULL);
+	}
       unblock_input ();
 
       return TRUE;
     }
   return FALSE;
+#endif
 }
 
 \f
@@ -870,7 +1000,7 @@ xg_set_geometry (struct frame *f)
 	}
       else
 	{
-          /* GTK works in scaled pixels, so convert from X pixels.  */
+	  /* GTK works in scaled pixels, so convert from X pixels.  */
 	  int left = f->left_pos / scale;
 	  int xneg = f->size_hint_flags & XNegative;
 	  int top = f->top_pos / scale;
@@ -925,10 +1055,16 @@ xg_frame_resized (struct frame *f, int pixelwidth, int pixelheight)
 
   width = FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth);
   height = FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight);
+  PGTK_TRACE("xg_frame_resized: pixel: %dx%d, text: %dx%d", pixelwidth, pixelheight, width, height);
 
   frame_size_history_add
     (f, Qxg_frame_resized, width, height, Qnil);
 
+  PGTK_TRACE("width: %d -> %d.", FRAME_TEXT_WIDTH(f), width);
+  PGTK_TRACE("height: %d -> %d.", FRAME_TEXT_HEIGHT(f), height);
+  PGTK_TRACE("pixelwidth: %d -> %d.", FRAME_PIXEL_WIDTH(f), pixelwidth);
+  PGTK_TRACE("pixelheight: %d -> %d.", FRAME_PIXEL_HEIGHT(f), pixelheight);
+
   if (width != FRAME_TEXT_WIDTH (f)
       || height != FRAME_TEXT_HEIGHT (f)
       || pixelwidth != FRAME_PIXEL_WIDTH (f)
@@ -954,8 +1090,9 @@ xg_frame_set_char_size (struct frame *f, int width, int height)
     = pixelheight + FRAME_TOOLBAR_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f);
   int totalwidth = pixelwidth + FRAME_TOOLBAR_WIDTH (f);
   bool was_visible = false;
+#ifndef HAVE_PGTK
   bool hide_child_frame;
-
+#endif
   if (FRAME_PIXEL_HEIGHT (f) == 0)
     return;
 
@@ -997,6 +1134,7 @@ xg_frame_set_char_size (struct frame *f, int width, int height)
       gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
 			 totalwidth, gheight);
     }
+#ifndef HAVE_PGTK
   else if (FRAME_PARENT_FRAME (f) && FRAME_VISIBLE_P (f))
     {
       was_visible = true;
@@ -1028,6 +1166,7 @@ xg_frame_set_char_size (struct frame *f, int width, int height)
 	  fullscreen = Qnil;
 	}
     }
+#endif
   else
     {
       frame_size_history_add
@@ -1053,7 +1192,9 @@ xg_frame_set_char_size (struct frame *f, int width, int height)
       /* Must call this to flush out events */
       (void)gtk_events_pending ();
       gdk_flush ();
+#ifndef HAVE_PGTK
       x_wait_for_event (f, ConfigureNotify);
+#endif
 
       if (!NILP (fullscreen))
 	/* Try to restore fullscreen state.  */
@@ -1064,24 +1205,24 @@ xg_frame_set_char_size (struct frame *f, int width, int height)
     }
   else
     adjust_frame_size (f, width, height, 5, 0, Qxg_frame_set_char_size);
-
 }
 
+#ifndef HAVE_PGTK
 /* Handle height/width changes (i.e. add/remove/move menu/toolbar).
    The policy is to keep the number of editable lines.  */
 
-#if 0
 static void
 xg_height_or_width_changed (struct frame *f)
 {
   gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
-                     FRAME_TOTAL_PIXEL_WIDTH (f),
-                     FRAME_TOTAL_PIXEL_HEIGHT (f));
-  f->output_data.x->hint_flags = 0;
+		     FRAME_TOTAL_PIXEL_WIDTH (f),
+		     FRAME_TOTAL_PIXEL_HEIGHT (f));
+  f->output_data.xp->hint_flags = 0;
   x_wm_set_size_hint (f, 0, 0);
 }
 #endif
 
+#ifndef HAVE_PGTK
 /* Convert an X Window WSESC on display DPY to its corresponding GtkWidget.
    Must be done like this, because GtkWidget:s can have "hidden"
    X Window that aren't accessible.
@@ -1097,7 +1238,7 @@ xg_win_to_widget (Display *dpy, Window wdesc)
   block_input ();
 
   gdkwin = gdk_x11_window_lookup_for_display (gdk_x11_lookup_xdisplay (dpy),
-                                              wdesc);
+					      wdesc);
   if (gdkwin)
     {
       GdkEvent event;
@@ -1109,6 +1250,7 @@ xg_win_to_widget (Display *dpy, Window wdesc)
   unblock_input ();
   return gwdesc;
 }
+#endif
 
 /* Set the background of widget W to PIXEL.  */
 
@@ -1116,22 +1258,31 @@ xg_win_to_widget (Display *dpy, Window wdesc)
 xg_set_widget_bg (struct frame *f, GtkWidget *w, unsigned long pixel)
 {
 #ifdef HAVE_GTK3
-  XColor xbg;
+  Emacs_Color xbg;
   xbg.pixel = pixel;
+#ifndef HAVE_PGTK
   if (XQueryColor (FRAME_X_DISPLAY (f), FRAME_X_COLORMAP (f), &xbg))
+#else
+  xbg.red = (pixel >> 16) & 0xff;
+  xbg.green = (pixel >> 8) & 0xff;
+  xbg.blue = (pixel >> 0) & 0xff;
+  xbg.red |= xbg.red << 8;
+  xbg.green |= xbg.green << 8;
+  xbg.blue |= xbg.blue << 8;
+#endif
     {
       const char format[] = "* { background-color: #%02x%02x%02x; }";
       /* The format is always longer than the resulting string.  */
       char buffer[sizeof format];
       int n = snprintf(buffer, sizeof buffer, format,
-                       xbg.red >> 8, xbg.green >> 8, xbg.blue >> 8);
+		       xbg.red >> 8, xbg.green >> 8, xbg.blue >> 8);
       eassert (n > 0);
       eassert (n < sizeof buffer);
       GtkCssProvider *provider = gtk_css_provider_new ();
       gtk_css_provider_load_from_data (provider, buffer, -1, NULL);
       gtk_style_context_add_provider (gtk_widget_get_style_context(w),
-                                      GTK_STYLE_PROVIDER (provider),
-                                      GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+				      GTK_STYLE_PROVIDER (provider),
+				      GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
       g_clear_object (&provider);
     }
 #else
@@ -1147,13 +1298,22 @@ xg_set_widget_bg (struct frame *f, GtkWidget *w, unsigned long pixel)
 
 static void
 style_changed_cb (GObject *go,
-                  GParamSpec *spec,
-                  gpointer user_data)
+		  GParamSpec *spec,
+		  gpointer user_data)
 {
   struct input_event event;
   GdkDisplay *gdpy = user_data;
   const char *display_name = gdk_display_get_name (gdpy);
+#ifndef HAVE_PGTK
   Display *dpy = GDK_DISPLAY_XDISPLAY (gdpy);
+#else
+  GdkDisplay *dpy = gdpy;
+#endif
+
+#ifndef HAVE_PGTK
+  if (display_name == NULL)
+    display_name = "";
+#endif
 
   EVENT_INIT (event);
   event.kind = CONFIG_CHANGED_EVENT;
@@ -1171,29 +1331,35 @@ style_changed_cb (GObject *go,
     {
       Lisp_Object rest, frame;
       FOR_EACH_FRAME (rest, frame)
-        {
-          struct frame *f = XFRAME (frame);
-          if (FRAME_LIVE_P (f)
-              && FRAME_X_P (f)
-              && FRAME_X_DISPLAY (f) == dpy)
-            {
-              FRAME_TERMINAL (f)->set_scroll_bar_default_width_hook (f);
-              FRAME_TERMINAL (f)->set_scroll_bar_default_height_hook (f);
-              xg_frame_set_char_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f));
-            }
-        }
+	{
+	  struct frame *f = XFRAME (frame);
+	  if (FRAME_LIVE_P (f)
+#ifndef HAVE_PGTK
+	      && FRAME_X_P (f)
+#else
+	      && FRAME_PGTK_P (f)
+#endif
+	      && FRAME_X_DISPLAY (f) == dpy)
+	    {
+	      FRAME_TERMINAL (f)->set_scroll_bar_default_width_hook (f);
+	      FRAME_TERMINAL (f)->set_scroll_bar_default_height_hook (f);
+	      xg_frame_set_char_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f));
+	    }
+	}
     }
 }
 
 /* Called when a delete-event occurs on WIDGET.  */
 
+#ifndef HAVE_PGTK
 static gboolean
 delete_cb (GtkWidget *widget,
-           GdkEvent  *event,
-           gpointer user_data)
+	   GdkEvent  *event,
+	   gpointer user_data)
 {
   return TRUE;
 }
+#endif
 
 /* Create and set up the GTK widgets for frame F.
    Return true if creation succeeded.  */
@@ -1209,15 +1375,33 @@ xg_create_frame_widgets (struct frame *f)
 #endif
   char *title = 0;
 
+  PGTK_TRACE("xg_create_frame_widgets.");
   block_input ();
 
+#ifndef HAVE_PGTK  // gtk_plug not found.
   if (FRAME_X_EMBEDDED_P (f))
     {
       GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
-      wtop = gtk_plug_new_for_display (gdpy, f->output_data.x->parent_desc);
+      wtop = gtk_plug_new_for_display (gdpy, f->output_data.xp->parent_desc);
     }
   else
+#endif
     wtop = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+#ifdef HAVE_PGTK
+  gtk_widget_add_events (wtop, GDK_ALL_EVENTS_MASK);
+  gtk_widget_add_events (wtop, GDK_CONFIGURE);
+#endif
+  gtk_window_set_decorated (GTK_WINDOW (wtop), true);
+#ifdef HAVE_GTK3
+  GtkHeaderBar *hb = GTK_HEADER_BAR (gtk_header_bar_new ());
+#ifdef GDK_VERSION_3_12
+  gtk_header_bar_set_has_subtitle (hb, true);
+  gtk_header_bar_set_subtitle (hb, !NILP (f->name) ? SSDATA (f->name) : NULL);
+#endif
+  gtk_window_set_titlebar (GTK_WINDOW (wtop), GTK_WIDGET (hb));
+  gtk_header_bar_set_show_close_button (hb, true);
+  gtk_widget_set_can_focus (GTK_WIDGET (hb), false);
+#endif
 
   /* gtk_window_set_has_resize_grip is a Gtk+ 3.0 function but Ubuntu
      has backported it to Gtk+ 2.0 and they add the resize grip for
@@ -1274,8 +1458,11 @@ xg_create_frame_widgets (struct frame *f)
 
   FRAME_GTK_OUTER_WIDGET (f) = wtop;
   FRAME_GTK_WIDGET (f) = wfixed;
-  f->output_data.x->vbox_widget = wvbox;
-  f->output_data.x->hbox_widget = whbox;
+#ifdef HAVE_GTK3
+  FRAME_X_OUTPUT (f)->header_bar = hb;
+#endif
+  f->output_data.xp->vbox_widget = wvbox;
+  f->output_data.xp->hbox_widget = whbox;
 
   gtk_widget_set_has_window (wfixed, TRUE);
 
@@ -1294,18 +1481,23 @@ xg_create_frame_widgets (struct frame *f)
      FIXME: gtk_widget_set_double_buffered is deprecated and might stop
      working in the future.  We need to migrate away from combining
      X and GTK+ drawing to a pure GTK+ build.  */
+
+#ifndef HAVE_PGTK
   gtk_widget_set_double_buffered (wfixed, FALSE);
+#endif
 
-#if ! GTK_CHECK_VERSION (3, 22, 0)
+#if ! GTK_CHECK_VERSION (3, 22, 0) || HAVE_PGTK
   gtk_window_set_wmclass (GTK_WINDOW (wtop),
-                          SSDATA (Vx_resource_name),
-                          SSDATA (Vx_resource_class));
+			  SSDATA (Vx_resource_name),
+			  SSDATA (Vx_resource_class));
 #endif
 
+#ifndef HAVE_PGTK
   /* Add callback to do nothing on WM_DELETE_WINDOW.  The default in
      GTK is to destroy the widget.  We want Emacs to do that instead.  */
   g_signal_connect (G_OBJECT (wtop), "delete-event",
-                    G_CALLBACK (delete_cb), f);
+		    G_CALLBACK (delete_cb), f);
+#endif
 
   /* Convert our geometry parameters into a geometry string
      and specify it.
@@ -1315,22 +1507,38 @@ xg_create_frame_widgets (struct frame *f)
     = gtk_window_get_gravity (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
 
   gtk_widget_add_events (wfixed,
-                         GDK_POINTER_MOTION_MASK
-                         | GDK_EXPOSURE_MASK
-                         | GDK_BUTTON_PRESS_MASK
-                         | GDK_BUTTON_RELEASE_MASK
-                         | GDK_KEY_PRESS_MASK
-                         | GDK_ENTER_NOTIFY_MASK
-                         | GDK_LEAVE_NOTIFY_MASK
-                         | GDK_FOCUS_CHANGE_MASK
-                         | GDK_STRUCTURE_MASK
-                         | GDK_VISIBILITY_NOTIFY_MASK);
+			 GDK_POINTER_MOTION_MASK
+#ifndef HAVE_PGTK
+			 | GDK_EXPOSURE_MASK
+#endif
+			 | GDK_BUTTON_PRESS_MASK
+			 | GDK_BUTTON_RELEASE_MASK
+			 | GDK_KEY_PRESS_MASK
+			 | GDK_ENTER_NOTIFY_MASK
+			 | GDK_LEAVE_NOTIFY_MASK
+			 | GDK_FOCUS_CHANGE_MASK
+			 | GDK_STRUCTURE_MASK
+#ifdef HAVE_PGTK
+			 | GDK_SCROLL_MASK
+#ifdef HAVE_GTK3
+			 | GDK_SMOOTH_SCROLL_MASK
+#endif
+#endif
+			 | GDK_VISIBILITY_NOTIFY_MASK);
 
   /* Must realize the windows so the X window gets created.  It is used
      by callers of this function.  */
+#ifndef HAVE_PGTK
   gtk_widget_realize (wfixed);
+#else
+  gtk_widget_show_all(wtop);
+#endif
+#ifndef HAVE_PGTK
   FRAME_X_WINDOW (f) = GTK_WIDGET_TO_X_WIN (wfixed);
+#endif
+#ifndef HAVE_PGTK
   initial_set_up_x_back_buffer (f);
+#endif
 
   /* Since GTK clears its window by filling with the background color,
      we must keep X and GTK background in sync.  */
@@ -1347,6 +1555,7 @@ xg_create_frame_widgets (struct frame *f)
   gtk_widget_modify_style (wfixed, style);
 #else
   gtk_widget_set_can_focus (wfixed, TRUE);
+  gtk_widget_grab_focus(wfixed);
   gtk_window_set_resizable (GTK_WINDOW (wtop), TRUE);
 #endif
 
@@ -1359,9 +1568,9 @@ xg_create_frame_widgets (struct frame *f)
     }
 
   /* Steal a tool tip window we can move ourselves.  */
-  f->output_data.x->ttip_widget = 0;
-  f->output_data.x->ttip_lbl = 0;
-  f->output_data.x->ttip_window = 0;
+  f->output_data.xp->ttip_widget = 0;
+  f->output_data.xp->ttip_lbl = 0;
+  f->output_data.xp->ttip_window = 0;
   gtk_widget_set_tooltip_text (wtop, "Dummy text");
   g_signal_connect (wtop, "query-tooltip", G_CALLBACK (qttip_cb), f);
 
@@ -1370,14 +1579,14 @@ xg_create_frame_widgets (struct frame *f)
     GtkSettings *gs = gtk_settings_get_for_screen (screen);
     /* Only connect this signal once per screen.  */
     if (! g_signal_handler_find (G_OBJECT (gs),
-                                 G_SIGNAL_MATCH_FUNC,
-                                 0, 0, 0,
-                                 (gpointer) G_CALLBACK (style_changed_cb),
-                                 0))
+				 G_SIGNAL_MATCH_FUNC,
+				 0, 0, 0,
+				 (gpointer) G_CALLBACK (style_changed_cb),
+				 0))
       {
-        g_signal_connect (G_OBJECT (gs), "notify::gtk-theme-name",
-                          G_CALLBACK (style_changed_cb),
-                          gdk_screen_get_display (screen));
+	g_signal_connect (G_OBJECT (gs), "notify::gtk-theme-name",
+			  G_CALLBACK (style_changed_cb),
+			  gdk_screen_get_display (screen));
       }
   }
 
@@ -1391,23 +1600,31 @@ xg_free_frame_widgets (struct frame *f)
 {
   if (FRAME_GTK_OUTER_WIDGET (f))
     {
-      struct x_output *x = f->output_data.x;
+#ifdef USE_GTK_TOOLTIP
+      xp_output *x = f->output_data.xp;
+#endif
       struct xg_frame_tb_info *tbinfo
-        = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                             TB_INFO_KEY);
+	= g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+			     TB_INFO_KEY);
       if (tbinfo)
-        xfree (tbinfo);
+	xfree (tbinfo);
 
       /* x_free_frame_resources should have taken care of it */
+#ifndef HAVE_PGTK
       eassert (!FRAME_X_DOUBLE_BUFFERED_P (f));
+#endif
       gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f));
       FRAME_X_WINDOW (f) = 0; /* Set to avoid XDestroyWindow in xterm.c */
+#ifndef HAVE_PGTK
       FRAME_X_RAW_DRAWABLE (f) = 0;
+#endif
       FRAME_GTK_OUTER_WIDGET (f) = 0;
+#ifdef USE_GTK_TOOLTIP
       if (x->ttip_lbl)
-        gtk_widget_destroy (x->ttip_lbl);
+	gtk_widget_destroy (x->ttip_lbl);
       if (x->ttip_widget)
-        g_object_unref (G_OBJECT (x->ttip_widget));
+	g_object_unref (G_OBJECT (x->ttip_widget));
+#endif
     }
 }
 
@@ -1439,12 +1656,15 @@ x_wm_set_size_hint (struct frame *f, long int flags, bool user_position)
 
   XSETFRAME (frame, f);
   fs_state = Fframe_parameter (frame, Qfullscreen);
-  if ((EQ (fs_state, Qmaximized) || EQ (fs_state, Qfullboth)) &&
-      (x_wm_supports (f, FRAME_DISPLAY_INFO (f)->Xatom_net_wm_state) ||
-       x_wm_supports (f, FRAME_DISPLAY_INFO (f)->Xatom_net_wm_state_fullscreen)))
+  if ((EQ (fs_state, Qmaximized) || EQ (fs_state, Qfullboth))
+#ifndef HAVE_PGTK
+      && (x_wm_supports (f, FRAME_DISPLAY_INFO (f)->Xatom_net_wm_state) ||
+	  x_wm_supports (f, FRAME_DISPLAY_INFO (f)->Xatom_net_wm_state_fullscreen))
+#endif
+      )
     {
       /* Don't set hints when maximized or fullscreen.  Apparently KWin and
-         Gtk3 don't get along and the frame shrinks (!).
+	 Gtk3 don't get along and the frame shrinks (!).
       */
       return;
     }
@@ -1452,14 +1672,14 @@ x_wm_set_size_hint (struct frame *f, long int flags, bool user_position)
   if (flags)
     {
       memset (&size_hints, 0, sizeof (size_hints));
-      f->output_data.x->size_hints = size_hints;
-      f->output_data.x->hint_flags = hint_flags;
+      f->output_data.xp->size_hints = size_hints;
+      f->output_data.xp->hint_flags = hint_flags;
     }
   else
     flags = f->size_hint_flags;
 
-  size_hints = f->output_data.x->size_hints;
-  hint_flags = f->output_data.x->hint_flags;
+  size_hints = f->output_data.xp->size_hints;
+  hint_flags = f->output_data.xp->hint_flags;
 
   hint_flags |= GDK_HINT_RESIZE_INC | GDK_HINT_MIN_SIZE;
   size_hints.width_inc = frame_resize_pixelwise ? 1 : FRAME_COLUMN_WIDTH (f);
@@ -1472,6 +1692,7 @@ x_wm_set_size_hint (struct frame *f, long int flags, bool user_position)
   base_width = FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, 1) + FRAME_TOOLBAR_WIDTH (f);
   base_height = FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, 1)
     + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f);
+  PGTK_TRACE("base: %dx%d\n", base_width, base_height);
 
   size_hints.base_width = base_width;
   size_hints.base_height = base_height;
@@ -1521,16 +1742,18 @@ x_wm_set_size_hint (struct frame *f, long int flags, bool user_position)
   size_hints.width_inc /= scale;
   size_hints.height_inc /= scale;
 
-  if (hint_flags != f->output_data.x->hint_flags
+  if (hint_flags != f->output_data.xp->hint_flags
       || memcmp (&size_hints,
-		 &f->output_data.x->size_hints,
+		 &f->output_data.xp->size_hints,
 		 sizeof (size_hints)) != 0)
     {
       block_input ();
+#ifndef HAVE_PGTK
       gtk_window_set_geometry_hints (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
-                                     NULL, &size_hints, hint_flags);
-      f->output_data.x->size_hints = size_hints;
-      f->output_data.x->hint_flags = hint_flags;
+				     NULL, &size_hints, hint_flags);
+#endif
+      f->output_data.xp->size_hints = size_hints;
+      f->output_data.xp->hint_flags = hint_flags;
       unblock_input ();
     }
 }
@@ -1552,14 +1775,14 @@ xg_set_background_color (struct frame *f, unsigned long bg)
 #ifdef USE_TOOLKIT_SCROLL_BARS
       Lisp_Object bar;
       for (bar = FRAME_SCROLL_BARS (f);
-           !NILP (bar);
-           bar = XSCROLL_BAR (bar)->next)
-        {
-          GtkWidget *scrollbar =
-            xg_get_widget_from_map (XSCROLL_BAR (bar)->x_window);
-          GtkWidget *webox = gtk_widget_get_parent (scrollbar);
-          xg_set_widget_bg (f, webox, FRAME_BACKGROUND_PIXEL (f));
-        }
+	   !NILP (bar);
+	   bar = XSCROLL_BAR (bar)->next)
+	{
+	  GtkWidget *scrollbar =
+	    xg_get_widget_from_map (XSCROLL_BAR (bar)->x_window);
+	  GtkWidget *webox = gtk_widget_get_parent (scrollbar);
+	  xg_set_widget_bg (f, webox, FRAME_BACKGROUND_PIXEL (f));
+	}
 #endif
       unblock_input ();
     }
@@ -1596,7 +1819,11 @@ xg_frame_restack (struct frame *f1, struct frame *f2, bool above_flag)
       XSETFRAME (frame2, f2);
 
       gdk_window_restack (gwin1, gwin2, above_flag);
+#ifndef HAVE_PGTK
       x_sync (f1);
+#else
+      gdk_flush();
+#endif
     }
   unblock_input ();
 }
@@ -1660,6 +1887,7 @@ xg_set_override_redirect (struct frame *f, Lisp_Object override_redirect)
   unblock_input ();
 }
 
+#ifndef HAVE_PGTK
 /* Set the frame icon to ICON_PIXMAP/MASK.  This must be done with GTK
    functions so GTK does not overwrite the icon.  */
 
@@ -1667,16 +1895,17 @@ xg_set_override_redirect (struct frame *f, Lisp_Object override_redirect)
 xg_set_frame_icon (struct frame *f, Pixmap icon_pixmap, Pixmap icon_mask)
 {
   GdkPixbuf *gp = xg_get_pixbuf_from_pix_and_mask (f,
-                                                   icon_pixmap,
-                                                   icon_mask);
+						   icon_pixmap,
+						   icon_mask);
   if (gp)
     gtk_window_set_icon (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), gp);
 }
+#endif
 
 
 \f
 /***********************************************************************
-                      Dialog functions
+		      Dialog functions
  ***********************************************************************/
 /* Return the dialog title to use for a dialog of type KEY.
    This is the encoding used by lwlib.  We use the same for GTK.  */
@@ -1738,8 +1967,8 @@ dialog_delete_callback (GtkWidget *w, GdkEvent *event, gpointer user_data)
 
 static GtkWidget *
 create_dialog (widget_value *wv,
-               GCallback select_cb,
-               GCallback deactivate_cb)
+	       GCallback select_cb,
+	       GCallback deactivate_cb)
 {
   const char *title = get_dialog_title (wv->name[0]);
   int total_buttons = wv->name[1] - '0';
@@ -1747,11 +1976,32 @@ create_dialog (widget_value *wv,
   int left_buttons;
   int button_nr = 0;
   int button_spacing = 10;
+
+#ifdef GTK_INTER_H
+  if (total_buttons < 8)
+    {
+      GtkDialog *d = build_dialog_n_items (wv->contents->value,
+					   wv->contents->next,
+					   select_cb,
+					   deactivate_cb,
+					   G_CALLBACK (dialog_delete_callback));
+      if (wv->name[0] != 'Q')
+	{
+	  gtk_window_set_title (GTK_WINDOW (d), title);
+	}
+      return GTK_WIDGET (d);
+    }
+#endif
+
   GtkWidget *wdialog = gtk_dialog_new ();
   GtkDialog *wd = GTK_DIALOG (wdialog);
   widget_value *item;
   GtkWidget *whbox_down;
 
+#ifdef HAVE_GTK3
+  gtk_window_set_titlebar (GTK_WINDOW (wdialog), gtk_header_bar_new ());
+#endif
+
   /* If the number of buttons is greater than 4, make two rows of buttons
      instead.  This looks better.  */
   bool make_two_rows = total_buttons > 4;
@@ -1759,8 +2009,8 @@ create_dialog (widget_value *wv,
 #if GTK_CHECK_VERSION (3, 12, 0)
   GtkBuilder *gbld = gtk_builder_new ();
   GObject *go = gtk_buildable_get_internal_child (GTK_BUILDABLE (wd),
-                                                  gbld,
-                                                  "action_area");
+						  gbld,
+						  "action_area");
   GtkBox *cur_box = GTK_BOX (go);
   g_object_unref (G_OBJECT (gbld));
 #else
@@ -1791,7 +2041,7 @@ create_dialog (widget_value *wv,
     }
 
   g_signal_connect (G_OBJECT (wdialog), "delete-event",
-                    G_CALLBACK (dialog_delete_callback), 0);
+		    G_CALLBACK (dialog_delete_callback), 0);
 
   if (deactivate_cb)
     {
@@ -1806,44 +2056,48 @@ create_dialog (widget_value *wv,
       GtkRequisition req;
 
       if (item->name && strcmp (item->name, "message") == 0)
-        {
-          GtkBox *wvbox = GTK_BOX (gtk_dialog_get_content_area (wd));
-          /* This is the text part of the dialog.  */
-          w = gtk_label_new (utf8_label);
-          gtk_box_pack_start (wvbox, gtk_label_new (""), FALSE, FALSE, 0);
-          gtk_box_pack_start (wvbox, w, TRUE, TRUE, 0);
+	{
+	  GtkBox *wvbox = GTK_BOX (gtk_dialog_get_content_area (wd));
+	  /* This is the text part of the dialog.  */
+	  w = gtk_label_new (utf8_label);
+	  gtk_box_pack_start (wvbox, gtk_label_new (""), FALSE, FALSE, 0);
+	  gtk_box_pack_start (wvbox, w, TRUE, TRUE, 0);
 #if GTK_CHECK_VERSION (3, 14, 0)
-          gtk_widget_set_halign (w, GTK_ALIGN_START);
-          gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+	  gtk_widget_set_halign (w, GTK_ALIGN_START);
+	  gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
+#else
+	  gtk_misc_set_alignment (GTK_MISC (w), 0.1, 0.5);
+#endif
+	  /* Try to make dialog look better.  Must realize first so
+	     the widget can calculate the size it needs.  */
+	  gtk_widget_realize (w);
+#ifdef HAVE_GTK3
+	  gtk_widget_get_preferred_size (w, NULL, &req);
 #else
-          gtk_misc_set_alignment (GTK_MISC (w), 0.1, 0.5);
+	  gtk_widget_get_requisition (w, &req);
 #endif
-          /* Try to make dialog look better.  Must realize first so
-             the widget can calculate the size it needs.  */
-          gtk_widget_realize (w);
-          gtk_widget_get_preferred_size (w, NULL, &req);
-          gtk_box_set_spacing (wvbox, req.height);
+	  gtk_box_set_spacing (wvbox, req.height);
 	  if (item->value && strlen (item->value) > 0)
-            button_spacing = 2*req.width/strlen (item->value);
-          if (button_spacing < 10) button_spacing = 10;
-        }
+	    button_spacing = 2*req.width/strlen (item->value);
+	  if (button_spacing < 10) button_spacing = 10;
+	}
       else
-        {
-          /* This is one button to add to the dialog.  */
-          w = gtk_button_new_with_label (utf8_label);
-          if (! item->enabled)
-            gtk_widget_set_sensitive (w, FALSE);
-          if (select_cb)
-            g_signal_connect (G_OBJECT (w), "clicked",
-                              select_cb, item->call_data);
-
-          gtk_box_pack_start (cur_box, w, TRUE, TRUE, button_spacing);
-          if (++button_nr == left_buttons)
-            {
-              if (make_two_rows)
-                cur_box = GTK_BOX (whbox_down);
-            }
-        }
+	{
+	  /* This is one button to add to the dialog.  */
+	  w = gtk_button_new_with_label (utf8_label);
+	  if (! item->enabled)
+	    gtk_widget_set_sensitive (w, FALSE);
+	  if (select_cb)
+	    g_signal_connect (G_OBJECT (w), "clicked",
+			      select_cb, item->call_data);
+
+	  gtk_box_pack_start (cur_box, w, TRUE, TRUE, button_spacing);
+	  if (++button_nr == left_buttons)
+	    {
+	      if (make_two_rows)
+		cur_box = GTK_BOX (whbox_down);
+	    }
+	}
 
      if (utf8_label)
        g_free (utf8_label);
@@ -1927,7 +2181,7 @@ xg_dialog_run (struct frame *f, GtkWidget *w)
 
   xg_set_screen (w, f);
   gtk_window_set_transient_for (GTK_WINDOW (w),
-                                GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+				GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
   gtk_window_set_destroy_with_parent (GTK_WINDOW (w), TRUE);
   gtk_window_set_modal (GTK_WINDOW (w), TRUE);
 
@@ -1937,9 +2191,9 @@ xg_dialog_run (struct frame *f, GtkWidget *w)
   dd.timerid = 0;
 
   g_signal_connect (G_OBJECT (w),
-                    "response",
-                    G_CALLBACK (xg_dialog_response_cb),
-                    &dd);
+		    "response",
+		    G_CALLBACK (xg_dialog_response_cb),
+		    &dd);
   /* Don't destroy the widget if closed by the window manager close button.  */
   g_signal_connect (G_OBJECT (w), "delete-event", G_CALLBACK (gtk_true), NULL);
   gtk_widget_show (w);
@@ -1957,7 +2211,7 @@ xg_dialog_run (struct frame *f, GtkWidget *w)
 
 \f
 /***********************************************************************
-                      File dialog functions
+		      File dialog functions
  ***********************************************************************/
 /* Return true if the old file selection dialog is being used.  */
 
@@ -2014,12 +2268,12 @@ xg_toggle_notify_cb (GObject *gobject, GParamSpec *arg1, gpointer user_data)
       toggle_on = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wtoggle));
 
       if (!!visible != !!toggle_on)
-        {
-          gpointer cb = (gpointer) G_CALLBACK (xg_toggle_visibility_cb);
-          g_signal_handlers_block_by_func (G_OBJECT (wtoggle), cb, gobject);
-          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wtoggle), visible);
-          g_signal_handlers_unblock_by_func (G_OBJECT (wtoggle), cb, gobject);
-        }
+	{
+	  gpointer cb = (gpointer) G_CALLBACK (xg_toggle_visibility_cb);
+	  g_signal_handlers_block_by_func (G_OBJECT (wtoggle), cb, gobject);
+	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wtoggle), visible);
+	  g_signal_handlers_unblock_by_func (G_OBJECT (wtoggle), cb, gobject);
+	}
       x_gtk_show_hidden_files = visible;
     }
 }
@@ -2048,18 +2302,18 @@ xg_get_file_with_chooser (struct frame *f,
   GtkWidget *wmessage UNINIT;
   GtkWindow *gwin = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f));
   GtkFileChooserAction action = (mustmatch_p ?
-                                 GTK_FILE_CHOOSER_ACTION_OPEN :
-                                 GTK_FILE_CHOOSER_ACTION_SAVE);
+				 GTK_FILE_CHOOSER_ACTION_OPEN :
+				 GTK_FILE_CHOOSER_ACTION_SAVE);
 
   if (only_dir_p)
     action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
 
   filewin = gtk_file_chooser_dialog_new (prompt, gwin, action,
-                                         XG_TEXT_CANCEL, GTK_RESPONSE_CANCEL,
-                                         (mustmatch_p || only_dir_p ?
-                                          XG_TEXT_OPEN : XG_TEXT_OK),
-                                         GTK_RESPONSE_OK,
-                                         NULL);
+					 XG_TEXT_CANCEL, GTK_RESPONSE_CANCEL,
+					 (mustmatch_p || only_dir_p ?
+					  XG_TEXT_OPEN : XG_TEXT_OK),
+					 GTK_RESPONSE_OK,
+					 NULL);
   gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (filewin), TRUE);
 
   wbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
@@ -2074,20 +2328,20 @@ xg_get_file_with_chooser (struct frame *f,
 
   gtk_widget_show (wtoggle);
   g_signal_connect (G_OBJECT (wtoggle), "clicked",
-                    G_CALLBACK (xg_toggle_visibility_cb), filewin);
+		    G_CALLBACK (xg_toggle_visibility_cb), filewin);
   g_signal_connect (G_OBJECT (filewin), "notify",
-                    G_CALLBACK (xg_toggle_notify_cb), wtoggle);
+		    G_CALLBACK (xg_toggle_notify_cb), wtoggle);
 
   if (x_gtk_file_dialog_help_text)
     {
       char *z = msgbuf;
       /* Gtk+ 2.10 has the file name text entry box integrated in the dialog.
-         Show the C-l help text only for versions < 2.10.  */
+	 Show the C-l help text only for versions < 2.10.  */
       if (gtk_check_version (2, 10, 0) && action != GTK_FILE_CHOOSER_ACTION_SAVE)
-        z = stpcpy (z, "\nType C-l to display a file name text entry box.\n");
+	z = stpcpy (z, "\nType C-l to display a file name text entry box.\n");
       strcpy (z, "\nIf you don't like this file selector, use the "
-              "corresponding\nkey binding or customize "
-              "use-file-dialog to turn it off.");
+	      "corresponding\nkey binding or customize "
+	      "use-file-dialog to turn it off.");
 
       wmessage = gtk_label_new (msgbuf);
       gtk_widget_show (wmessage);
@@ -2106,26 +2360,26 @@ xg_get_file_with_chooser (struct frame *f,
       file = build_string (default_filename);
 
       /* File chooser does not understand ~/... in the file name.  It must be
-         an absolute name starting with /.  */
+	 an absolute name starting with /.  */
       if (default_filename[0] != '/')
-        file = Fexpand_file_name (file, Qnil);
+	file = Fexpand_file_name (file, Qnil);
 
       utf8_filename = SSDATA (ENCODE_UTF_8 (file));
       if (! NILP (Ffile_directory_p (file)))
-        gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filewin),
-                                             utf8_filename);
+	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filewin),
+					     utf8_filename);
       else
-        {
-          gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filewin),
-                                         utf8_filename);
-          if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
-            {
-              char *cp = strrchr (utf8_filename, '/');
-              if (cp) ++cp;
-              else cp = utf8_filename;
-              gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filewin), cp);
-            }
-        }
+	{
+	  gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filewin),
+					 utf8_filename);
+	  if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
+	    {
+	      char *cp = strrchr (utf8_filename, '/');
+	      if (cp) ++cp;
+	      else cp = utf8_filename;
+	      gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filewin), cp);
+	    }
+	}
     }
 
   *func = xg_get_file_name_from_chooser;
@@ -2156,10 +2410,10 @@ xg_get_file_name_from_selector (GtkWidget *w)
 
 static GtkWidget *
 xg_get_file_with_selection (struct frame *f,
-                            char *prompt,
-                            char *default_filename,
-                            bool mustmatch_p, bool only_dir_p,
-                            xg_get_file_func *func)
+			    char *prompt,
+			    char *default_filename,
+			    bool mustmatch_p, bool only_dir_p,
+			    xg_get_file_func *func)
 {
   GtkWidget *filewin;
   GtkFileSelection *filesel;
@@ -2198,10 +2452,10 @@ xg_get_file_with_selection (struct frame *f,
 
 char *
 xg_get_file_name (struct frame *f,
-                  char *prompt,
-                  char *default_filename,
-                  bool mustmatch_p,
-                  bool only_dir_p)
+		  char *prompt,
+		  char *default_filename,
+		  bool mustmatch_p,
+		  bool only_dir_p)
 {
   GtkWidget *w = 0;
   char *fn = 0;
@@ -2212,14 +2466,14 @@ xg_get_file_name (struct frame *f,
 
   if (xg_uses_old_file_dialog ())
     w = xg_get_file_with_selection (f, prompt, default_filename,
-                                    mustmatch_p, only_dir_p, &func);
+				    mustmatch_p, only_dir_p, &func);
   else
     w = xg_get_file_with_chooser (f, prompt, default_filename,
-                                  mustmatch_p, only_dir_p, &func);
+				  mustmatch_p, only_dir_p, &func);
 
 #else /* not HAVE_GTK_FILE_SELECTION_NEW */
   w = xg_get_file_with_chooser (f, prompt, default_filename,
-                                mustmatch_p, only_dir_p, &func);
+				mustmatch_p, only_dir_p, &func);
 #endif /* not HAVE_GTK_FILE_SELECTION_NEW */
 
   gtk_widget_set_name (w, "emacs-filedialog");
@@ -2233,7 +2487,7 @@ xg_get_file_name (struct frame *f,
 }
 
 /***********************************************************************
-                      GTK font chooser
+		      GTK font chooser
  ***********************************************************************/
 
 #ifdef HAVE_FREETYPE
@@ -2303,12 +2557,12 @@ xg_get_font (struct frame *f, const char *default_name)
 	 number */
       char *p = strrchr (default_name, '-');
       if (p)
-        {
-          char *ep = p+1;
-          while (c_isdigit (*ep))
-            ++ep;
-          if (*ep == '\0') *p = ' ';
-        }
+	{
+	  char *ep = p+1;
+	  while (c_isdigit (*ep))
+	    ++ep;
+	  if (*ep == '\0') *p = ' ';
+	}
     }
   else if (x_last_font_name)
     default_name = x_last_font_name;
@@ -2375,7 +2629,7 @@ xg_get_font (struct frame *f, const char *default_name)
 
 \f
 /***********************************************************************
-	                Menu functions.
+			Menu functions.
  ***********************************************************************/
 
 /* The name of menu items that can be used for customization.  Since GTK
@@ -2384,7 +2638,6 @@ xg_get_font (struct frame *f, const char *default_name)
 
 #define MENU_ITEM_NAME "emacs-menuitem"
 
-
 /* Linked list of all allocated struct xg_menu_cb_data.  Used for marking
    during GC.  The next member points to the items.  */
 static xg_list_node xg_menu_cb_list;
@@ -2403,7 +2656,7 @@ #define MENU_ITEM_NAME "emacs-menuitem"
    Returns CL_DATA if CL_DATA is not NULL,  or a pointer to a newly
    allocated xg_menu_cb_data if CL_DATA is NULL.  */
 
-static xg_menu_cb_data *
+xg_menu_cb_data *
 make_cl_data (xg_menu_cb_data *cl_data, struct frame *f, GCallback highlight_cb)
 {
   if (! cl_data)
@@ -2434,10 +2687,10 @@ make_cl_data (xg_menu_cb_data *cl_data, struct frame *f, GCallback highlight_cb)
    function is given when modifying a menu bar as was given when
    creating the menu bar.  */
 
-static void
+void
 update_cl_data (xg_menu_cb_data *cl_data,
-                struct frame *f,
-                GCallback highlight_cb)
+		struct frame *f,
+		GCallback highlight_cb)
 {
   if (cl_data)
     {
@@ -2458,10 +2711,10 @@ unref_cl_data (xg_menu_cb_data *cl_data)
     {
       cl_data->ref_count--;
       if (cl_data->ref_count == 0)
-        {
-          xg_list_remove (&xg_menu_cb_list, &cl_data->ptrs);
-          xfree (cl_data);
-        }
+	{
+	  xg_list_remove (&xg_menu_cb_list, &cl_data->ptrs);
+	  xfree (cl_data);
+	}
     }
 }
 
@@ -2481,24 +2734,24 @@ xg_mark_data (void)
       xg_menu_item_cb_data *cb_data = (xg_menu_item_cb_data *) iter;
 
       if (! NILP (cb_data->help))
-        mark_object (cb_data->help);
+	mark_object (cb_data->help);
     }
 
   FOR_EACH_FRAME (rest, frame)
     {
       struct frame *f = XFRAME (frame);
 
-      if (FRAME_X_P (f) && FRAME_GTK_OUTER_WIDGET (f))
-        {
-          struct xg_frame_tb_info *tbinfo
-            = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                                 TB_INFO_KEY);
-          if (tbinfo)
-            {
-              mark_object (tbinfo->last_tool_bar);
-              mark_object (tbinfo->style);
-            }
-        }
+      if ((FRAME_X_P (f) || FRAME_PGTK_P (f)) && FRAME_GTK_OUTER_WIDGET (f))
+	{
+	  struct xg_frame_tb_info *tbinfo
+	    = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+				 TB_INFO_KEY);
+	  if (tbinfo)
+	    {
+	      mark_object (tbinfo->last_tool_bar);
+	      mark_object (tbinfo->style);
+	    }
+	}
     }
 }
 
@@ -2526,8 +2779,8 @@ menuitem_destroy_callback (GtkWidget *w, gpointer client_data)
 
 static gboolean
 menuitem_highlight_callback (GtkWidget *w,
-                             GdkEventCrossing *event,
-                             gpointer client_data)
+			     GdkEventCrossing *event,
+			     gpointer client_data)
 {
   GdkEvent ev;
   GtkWidget *subwidget;
@@ -2539,11 +2792,11 @@ menuitem_highlight_callback (GtkWidget *w,
   if (data)
     {
       if (! NILP (data->help) && data->cl_data->highlight_cb)
-        {
-          gpointer call_data = event->type == GDK_LEAVE_NOTIFY ? 0 : data;
-          GtkCallback func = (GtkCallback) data->cl_data->highlight_cb;
-          (*func) (subwidget, call_data);
-        }
+	{
+	  gpointer call_data = event->type == GDK_LEAVE_NOTIFY ? 0 : data;
+	  GtkCallback func = (GtkCallback) data->cl_data->highlight_cb;
+	  (*func) (subwidget, call_data);
+	}
     }
 
   return FALSE;
@@ -2611,9 +2864,9 @@ make_widget_for_menu_item (const char *utf8_label, const char *utf8_key)
 
 static GtkWidget *
 make_menu_item (const char *utf8_label,
-                const char *utf8_key,
-                widget_value *item,
-                GSList **group)
+		const char *utf8_key,
+		widget_value *item,
+		GSList **group)
 {
   GtkWidget *w;
   GtkWidget *wtoadd = 0;
@@ -2640,7 +2893,7 @@ make_menu_item (const char *utf8_label,
       else w = gtk_radio_menu_item_new_with_label (*group, utf8_label);
       *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (w));
       if (item->selected)
-        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
+	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
     }
   else
     {
@@ -2671,11 +2924,11 @@ make_menu_item (const char *utf8_label,
 
 static GtkWidget *
 xg_create_one_menuitem (widget_value *item,
-                        struct frame *f,
-                        GCallback select_cb,
-                        GCallback highlight_cb,
-                        xg_menu_cb_data *cl_data,
-                        GSList **group)
+			struct frame *f,
+			GCallback select_cb,
+			GCallback highlight_cb,
+			xg_menu_cb_data *cl_data,
+			GSList **group)
 {
   char *utf8_label;
   char *utf8_key;
@@ -2694,15 +2947,18 @@ xg_create_one_menuitem (widget_value *item,
 
   xg_list_insert (&xg_menu_item_cb_list, &cb_data->ptrs);
 
+  if (!cl_data)
+    emacs_abort ();
+
   cb_data->select_id = 0;
   cb_data->help = item->help;
   cb_data->cl_data = cl_data;
   cb_data->call_data = item->call_data;
 
   g_signal_connect (G_OBJECT (w),
-                    "destroy",
-                    G_CALLBACK (menuitem_destroy_callback),
-                    cb_data);
+		    "destroy",
+		    G_CALLBACK (menuitem_destroy_callback),
+		    cb_data);
 
   /* Put cb_data in widget, so we can get at it when modifying menubar  */
   g_object_set_data (G_OBJECT (w), XG_ITEM_DATA, cb_data);
@@ -2711,8 +2967,8 @@ xg_create_one_menuitem (widget_value *item,
   if (item->call_data && ! item->contents)
     {
       if (select_cb)
-        cb_data->select_id
-          = g_signal_connect (G_OBJECT (w), "activate", select_cb, cb_data);
+	cb_data->select_id
+	  = g_signal_connect (G_OBJECT (w), "activate", select_cb, cb_data);
     }
 
   return w;
@@ -2740,15 +2996,15 @@ xg_create_one_menuitem (widget_value *item,
 
 static GtkWidget *
 create_menus (widget_value *data,
-              struct frame *f,
-              GCallback select_cb,
-              GCallback deactivate_cb,
-              GCallback highlight_cb,
-              bool pop_up_p,
-              bool menu_bar_p,
-              GtkWidget *topmenu,
-              xg_menu_cb_data *cl_data,
-              const char *name)
+	      struct frame *f,
+	      GCallback select_cb,
+	      GCallback deactivate_cb,
+	      GCallback highlight_cb,
+	      bool pop_up_p,
+	      bool menu_bar_p,
+	      GtkWidget *topmenu,
+	      xg_menu_cb_data *cl_data,
+	      const char *name)
 {
   widget_value *item;
   GtkWidget *wmenu = topmenu;
@@ -2758,42 +3014,42 @@ create_menus (widget_value *data,
     {
       if (! menu_bar_p)
       {
-        wmenu = gtk_menu_new ();
-        xg_set_screen (wmenu, f);
-        /* Connect this to the menu instead of items so we get enter/leave for
-           disabled items also.  TODO:  Still does not get enter/leave for
-           disabled items in detached menus.  */
-        g_signal_connect (G_OBJECT (wmenu),
-                          "enter-notify-event",
-                          G_CALLBACK (menuitem_highlight_callback),
-                          NULL);
-        g_signal_connect (G_OBJECT (wmenu),
-                          "leave-notify-event",
-                          G_CALLBACK (menuitem_highlight_callback),
-                          NULL);
+	wmenu = gtk_menu_new ();
+	xg_set_screen (wmenu, f);
+	/* Connect this to the menu instead of items so we get enter/leave for
+	   disabled items also.  TODO:  Still does not get enter/leave for
+	   disabled items in detached menus.  */
+	g_signal_connect (G_OBJECT (wmenu),
+			  "enter-notify-event",
+			  G_CALLBACK (menuitem_highlight_callback),
+			  NULL);
+	g_signal_connect (G_OBJECT (wmenu),
+			  "leave-notify-event",
+			  G_CALLBACK (menuitem_highlight_callback),
+			  NULL);
       }
       else
-        {
-          wmenu = gtk_menu_bar_new ();
-          /* Set width of menu bar to a small value so it doesn't enlarge
-             a small initial frame size.  The width will be set to the
-             width of the frame later on when it is added to a container.
-             height -1: Natural height.  */
-          gtk_widget_set_size_request (wmenu, 1, -1);
-        }
+	{
+	  wmenu = gtk_menu_bar_new ();
+	  /* Set width of menu bar to a small value so it doesn't enlarge
+	     a small initial frame size.  The width will be set to the
+	     width of the frame later on when it is added to a container.
+	     height -1: Natural height.  */
+	  gtk_widget_set_size_request (wmenu, 1, -1);
+	}
 
       /* Put cl_data on the top menu for easier access.  */
       cl_data = make_cl_data (cl_data, f, highlight_cb);
       g_object_set_data (G_OBJECT (wmenu), XG_FRAME_DATA, (gpointer)cl_data);
       g_signal_connect (G_OBJECT (wmenu), "destroy",
-                        G_CALLBACK (menu_destroy_callback), cl_data);
+			G_CALLBACK (menu_destroy_callback), cl_data);
 
       if (name)
-        gtk_widget_set_name (wmenu, name);
+	gtk_widget_set_name (wmenu, name);
 
       if (deactivate_cb)
-        g_signal_connect (G_OBJECT (wmenu),
-                          "selection-done", deactivate_cb, 0);
+	g_signal_connect (G_OBJECT (wmenu),
+			  "selection-done", deactivate_cb, 0);
     }
 
   for (item = data; item; item = item->next)
@@ -2801,50 +3057,50 @@ create_menus (widget_value *data,
       GtkWidget *w;
 
       if (pop_up_p && !item->contents && !item->call_data
-          && !menu_separator_name_p (item->name))
-        {
-          char *utf8_label;
-          /* A title for a popup.  We do the same as GTK does when
-             creating titles, but it does not look good.  */
-          group = NULL;
-          utf8_label = get_utf8_string (item->name);
-
-          w = gtk_menu_item_new_with_label (utf8_label);
-          gtk_widget_set_sensitive (w, FALSE);
-          if (utf8_label) g_free (utf8_label);
-        }
+	  && !menu_separator_name_p (item->name))
+	{
+	  char *utf8_label;
+	  /* A title for a popup.  We do the same as GTK does when
+	     creating titles, but it does not look good.  */
+	  group = NULL;
+	  utf8_label = get_utf8_string (item->name);
+
+	  w = gtk_menu_item_new_with_label (utf8_label);
+	  gtk_widget_set_sensitive (w, FALSE);
+	  if (utf8_label) g_free (utf8_label);
+	}
       else if (menu_separator_name_p (item->name))
-        {
-          group = NULL;
-          /* GTK only have one separator type.  */
-          w = gtk_separator_menu_item_new ();
-        }
+	{
+	  group = NULL;
+	  /* GTK only have one separator type.  */
+	  w = gtk_separator_menu_item_new ();
+	}
       else
-        {
-          w = xg_create_one_menuitem (item,
-                                      f,
-                                      item->contents ? 0 : select_cb,
-                                      highlight_cb,
-                                      cl_data,
-                                      &group);
-
-          /* Create a possibly empty submenu for menu bar items, since some
-             themes don't highlight items correctly without it. */
-          if (item->contents || menu_bar_p)
-            {
-              GtkWidget *submenu = create_menus (item->contents,
-                                                 f,
-                                                 select_cb,
-                                                 deactivate_cb,
-                                                 highlight_cb,
-                                                 0,
-                                                 0,
-                                                 0,
-                                                 cl_data,
-                                                 0);
-              gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), submenu);
-            }
-        }
+	{
+	  w = xg_create_one_menuitem (item,
+				      f,
+				      item->contents ? 0 : select_cb,
+				      highlight_cb,
+				      cl_data,
+				      &group);
+
+	  /* Create a possibly empty submenu for menu bar items, since some
+	     themes don't highlight items correctly without it. */
+	  if (item->contents || menu_bar_p)
+	    {
+	      GtkWidget *submenu = create_menus (item->contents,
+						 f,
+						 select_cb,
+						 deactivate_cb,
+						 highlight_cb,
+						 0,
+						 0,
+						 0,
+						 cl_data,
+						 0);
+	      gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), submenu);
+	    }
+	}
 
       gtk_menu_shell_append (GTK_MENU_SHELL (wmenu), w);
       gtk_widget_set_name (w, MENU_ITEM_NAME);
@@ -2882,7 +3138,7 @@ xg_create_widget (const char *type, const char *name, struct frame *f,
       w = create_dialog (val, select_cb, deactivate_cb);
       xg_set_screen (w, f);
       gtk_window_set_transient_for (GTK_WINDOW (w),
-                                    GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+				    GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
       gtk_window_set_destroy_with_parent (GTK_WINDOW (w), TRUE);
       gtk_widget_set_name (w, "emacs-dialog");
       gtk_window_set_modal (GTK_WINDOW (w), TRUE);
@@ -2890,29 +3146,29 @@ xg_create_widget (const char *type, const char *name, struct frame *f,
   else if (menu_bar_p || pop_up_p)
     {
       w = create_menus (val->contents,
-                        f,
-                        select_cb,
-                        deactivate_cb,
-                        highlight_cb,
-                        pop_up_p,
-                        menu_bar_p,
-                        0,
-                        0,
-                        name);
+			f,
+			select_cb,
+			deactivate_cb,
+			highlight_cb,
+			pop_up_p,
+			menu_bar_p,
+			0,
+			0,
+			name);
 
       /* Set the cursor to an arrow for popup menus when they are mapped.
-         This is done by default for menu bar menus.  */
+	 This is done by default for menu bar menus.  */
       if (pop_up_p)
-        {
-          /* Must realize so the GdkWindow inside the widget is created.  */
-          gtk_widget_realize (w);
-          xg_set_cursor (w, FRAME_DISPLAY_INFO (f)->xg_cursor);
-        }
+	{
+	  /* Must realize so the GdkWindow inside the widget is created.  */
+	  gtk_widget_realize (w);
+	  xg_set_cursor (w, FRAME_DISPLAY_INFO (f)->xg_cursor);
+	}
     }
   else
     {
       fprintf (stderr, "bad type in xg_create_widget: %s, doing nothing\n",
-               type);
+	       type);
     }
 
   return w;
@@ -2929,7 +3185,7 @@ xg_get_menu_item_label (GtkMenuItem *witem)
 
 /* Return true if the menu item WITEM has the text LABEL.  */
 
-static bool
+bool
 xg_item_label_same_p (GtkMenuItem *witem, const char *label)
 {
   bool is_same = 0;
@@ -2974,7 +3230,7 @@ xg_destroy_widgets (GList *list)
 
    This function calls itself to walk through the menu bar names.  */
 
-static void
+void
 xg_update_menubar (GtkWidget *menubar,
 		   struct frame *f,
 		   GList **list,
@@ -2995,8 +3251,8 @@ xg_update_menubar (GtkWidget *menubar,
 
       /* Add a blank entry so the menubar doesn't collapse to nothing. */
       gtk_menu_shell_insert (GTK_MENU_SHELL (menubar),
-                             gtk_menu_item_new_with_label (""),
-                             0);
+			     gtk_menu_item_new_with_label (""),
+			     0);
       /* All updated.  */
       val = 0;
       iter = 0;
@@ -3005,7 +3261,7 @@ xg_update_menubar (GtkWidget *menubar,
     {
       /* Item(s) added.  Add all new items in one call.  */
       create_menus (val, f, select_cb, deactivate_cb, highlight_cb,
-                    0, 1, menubar, cl_data, 0);
+		    0, 1, menubar, cl_data, 0);
 
       /* All updated.  */
       val = 0;
@@ -3030,127 +3286,130 @@ xg_update_menubar (GtkWidget *menubar,
 
       /* See if the changed entry (val) is present later in the menu bar  */
       for (iter2 = iter;
-           iter2 && ! val_in_menubar;
-           iter2 = g_list_next (iter2))
-        {
-          witem2 = GTK_MENU_ITEM (iter2->data);
-          val_in_menubar = xg_item_label_same_p (witem2, val->name);
-        }
+	   iter2 && ! val_in_menubar;
+	   iter2 = g_list_next (iter2))
+	{
+	  witem2 = GTK_MENU_ITEM (iter2->data);
+	  val_in_menubar = xg_item_label_same_p (witem2, val->name);
+	}
 
       /* See if the current entry (iter) is present later in the
-         specification for the new menu bar.  */
+	 specification for the new menu bar.  */
       for (cur = val; cur && ! iter_in_new_menubar; cur = cur->next)
-        iter_in_new_menubar = xg_item_label_same_p (witem, cur->name);
+	iter_in_new_menubar = xg_item_label_same_p (witem, cur->name);
 
       if (val_in_menubar && ! iter_in_new_menubar)
-        {
-          int nr = pos;
-
-          /*  This corresponds to:
-                Current:  A B C
-                New:      A C
-              Remove B.  */
-
-          g_object_ref (G_OBJECT (witem));
-          gtk_container_remove (GTK_CONTAINER (menubar), GTK_WIDGET (witem));
-          gtk_widget_destroy (GTK_WIDGET (witem));
-
-          /* Must get new list since the old changed.  */
-          g_list_free (*list);
-          *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar));
-          while (nr-- > 0) iter = g_list_next (iter);
-        }
+	{
+	  int nr = pos;
+
+	  /*  This corresponds to:
+		Current:  A B C
+		New:      A C
+	      Remove B.  */
+
+	  g_object_ref (G_OBJECT (witem));
+	  gtk_container_remove (GTK_CONTAINER (menubar), GTK_WIDGET (witem));
+	  gtk_widget_destroy (GTK_WIDGET (witem));
+
+	  /* Must get new list since the old changed.  */
+	  g_list_free (*list);
+	  *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar));
+	  while (nr-- > 0) iter = g_list_next (iter);
+	}
       else if (! val_in_menubar && ! iter_in_new_menubar)
-        {
-          /*  This corresponds to:
-                Current:  A B C
-                New:      A X C
-              Rename B to X.  This might seem to be a strange thing to do,
-              since if there is a menu under B it will be totally wrong for X.
-              But consider editing a C file.  Then there is a C-mode menu
-              (corresponds to B above).
-              If then doing C-x C-f the minibuf menu (X above) replaces the
-              C-mode menu.  When returning from the minibuffer, we get
-              back the C-mode menu.  Thus we do:
-                Rename B to X (C-mode to minibuf menu)
-                Rename X to B (minibuf to C-mode menu).
-              If the X menu hasn't been invoked, the menu under B
-              is up to date when leaving the minibuffer.  */
-          GtkLabel *wlabel = GTK_LABEL (XG_BIN_CHILD (witem));
-          char *utf8_label = get_utf8_string (val->name);
-
-          /* GTK menu items don't notice when their labels have been
-             changed from underneath them, so we have to explicitly
-             use g_object_notify to tell listeners (e.g., a GMenuModel
-             bridge that might be loaded) that the item's label has
-             changed.  */
-          gtk_label_set_text (wlabel, utf8_label);
-          g_object_notify (G_OBJECT (witem), "label");
-          if (utf8_label) g_free (utf8_label);
-          iter = g_list_next (iter);
-          val = val->next;
-          ++pos;
-        }
+	{
+	  /*  This corresponds to:
+		Current:  A B C
+		New:      A X C
+	      Rename B to X.  This might seem to be a strange thing to do,
+	      since if there is a menu under B it will be totally wrong for X.
+	      But consider editing a C file.  Then there is a C-mode menu
+	      (corresponds to B above).
+	      If then doing C-x C-f the minibuf menu (X above) replaces the
+	      C-mode menu.  When returning from the minibuffer, we get
+	      back the C-mode menu.  Thus we do:
+		Rename B to X (C-mode to minibuf menu)
+		Rename X to B (minibuf to C-mode menu).
+	      If the X menu hasn't been invoked, the menu under B
+	      is up to date when leaving the minibuffer.  */
+	  if (witem && GTK_IS_BIN (witem))
+	    {
+	      GtkLabel *wlabel = GTK_LABEL (XG_BIN_CHILD (witem));
+	      char *utf8_label = get_utf8_string (val->name);
+
+	      /* GTK menu items don't notice when their labels have been
+		 changed from underneath them, so we have to explicitly
+		 use g_object_notify to tell listeners (e.g., a GMenuModel
+		 bridge that might be loaded) that the item's label has
+		 changed.  */
+	      gtk_label_set_text (wlabel, utf8_label);
+	      g_object_notify (G_OBJECT (witem), "label");
+	      if (utf8_label) g_free (utf8_label);
+	    }
+	  iter = g_list_next (iter);
+	  val = val->next;
+	  ++pos;
+	}
       else if (! val_in_menubar && iter_in_new_menubar)
-        {
-          /*  This corresponds to:
-                Current:  A B C
-                New:      A X B C
-              Insert X.  */
-
-          int nr = pos;
-          GSList *group = 0;
-          GtkWidget *w = xg_create_one_menuitem (val,
-                                                 f,
-                                                 select_cb,
-                                                 highlight_cb,
-                                                 cl_data,
-                                                 &group);
-
-          /* Create a possibly empty submenu for menu bar items, since some
-             themes don't highlight items correctly without it. */
-          GtkWidget *submenu = create_menus (NULL, f,
-                                             select_cb, deactivate_cb,
-                                             highlight_cb,
-                                             0, 0, 0, cl_data, 0);
-
-          gtk_widget_set_name (w, MENU_ITEM_NAME);
-          gtk_menu_shell_insert (GTK_MENU_SHELL (menubar), w, pos);
-          gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), submenu);
-
-          g_list_free (*list);
-          *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar));
-          while (nr-- > 0) iter = g_list_next (iter);
-          iter = g_list_next (iter);
-          val = val->next;
-          ++pos;
-        }
+	{
+	  /*  This corresponds to:
+		Current:  A B C
+		New:      A X B C
+	      Insert X.  */
+
+	  int nr = pos;
+	  GSList *group = 0;
+	  GtkWidget *w = xg_create_one_menuitem (val,
+						 f,
+						 select_cb,
+						 highlight_cb,
+						 cl_data,
+						 &group);
+
+	  /* Create a possibly empty submenu for menu bar items, since some
+	     themes don't highlight items correctly without it. */
+	  GtkWidget *submenu = create_menus (NULL, f,
+					     select_cb, deactivate_cb,
+					     highlight_cb,
+					     0, 0, 0, cl_data, 0);
+
+	  gtk_widget_set_name (w, MENU_ITEM_NAME);
+	  gtk_menu_shell_insert (GTK_MENU_SHELL (menubar), w, pos);
+	  gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), submenu);
+
+	  g_list_free (*list);
+	  *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar));
+	  while (nr-- > 0) iter = g_list_next (iter);
+	  iter = g_list_next (iter);
+	  val = val->next;
+	  ++pos;
+	}
       else /* if (val_in_menubar && iter_in_new_menubar) */
-        {
-          int nr = pos;
-          /*  This corresponds to:
-                Current:  A B C
-                New:      A C B
-              Move C before B  */
-
-          g_object_ref (G_OBJECT (witem2));
-          gtk_container_remove (GTK_CONTAINER (menubar), GTK_WIDGET (witem2));
-          gtk_menu_shell_insert (GTK_MENU_SHELL (menubar),
-                                 GTK_WIDGET (witem2), pos);
-          g_object_unref (G_OBJECT (witem2));
-
-          g_list_free (*list);
-          *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar));
-          while (nr-- > 0) iter = g_list_next (iter);
-          if (iter) iter = g_list_next (iter);
-          val = val->next;
-          ++pos;
+	{
+	  int nr = pos;
+	  /*  This corresponds to:
+		Current:  A B C
+		New:      A C B
+	      Move C before B  */
+
+	  g_object_ref (G_OBJECT (witem2));
+	  gtk_container_remove (GTK_CONTAINER (menubar), GTK_WIDGET (witem2));
+	  gtk_menu_shell_insert (GTK_MENU_SHELL (menubar),
+				 GTK_WIDGET (witem2), pos);
+	  g_object_unref (G_OBJECT (witem2));
+
+	  g_list_free (*list);
+	  *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar));
+	  while (nr-- > 0) iter = g_list_next (iter);
+	  if (iter) iter = g_list_next (iter);
+	  val = val->next;
+	  ++pos;
       }
     }
 
   /* Update the rest of the menu bar.  */
   xg_update_menubar (menubar, f, list, iter, pos, val,
-                     select_cb, deactivate_cb, highlight_cb, cl_data);
+		     select_cb, deactivate_cb, highlight_cb, cl_data);
 }
 
 /* Update the menu item W so it corresponds to VAL.
@@ -3160,10 +3419,10 @@ xg_update_menubar (GtkWidget *menubar,
 
 static void
 xg_update_menu_item (widget_value *val,
-                     GtkWidget *w,
-                     GCallback select_cb,
-                     GCallback highlight_cb,
-                     xg_menu_cb_data *cl_data)
+		     GtkWidget *w,
+		     GCallback select_cb,
+		     GCallback highlight_cb,
+		     xg_menu_cb_data *cl_data)
 {
   GtkWidget *wchild;
   GtkLabel *wlbl = 0;
@@ -3189,14 +3448,14 @@ xg_update_menu_item (widget_value *val,
       g_list_free (list);
 
       if (! utf8_key)
-        {
-          /* Remove the key and keep just the label.  */
-          g_object_ref (G_OBJECT (wlbl));
-          gtk_container_remove (GTK_CONTAINER (w), wchild);
-          gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (wlbl));
-          g_object_unref (G_OBJECT (wlbl));
-          wkey = 0;
-        }
+	{
+	  /* Remove the key and keep just the label.  */
+	  g_object_ref (G_OBJECT (wlbl));
+	  gtk_container_remove (GTK_CONTAINER (w), wchild);
+	  gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (wlbl));
+	  g_object_unref (G_OBJECT (wlbl));
+	  wkey = 0;
+	}
 
     }
   else /* Just a label.  */
@@ -3205,17 +3464,17 @@ xg_update_menu_item (widget_value *val,
 
       /* Check if there is now a key.  */
       if (utf8_key)
-        {
-          GtkWidget *wtoadd = make_widget_for_menu_item (utf8_label, utf8_key);
-          GList *list = gtk_container_get_children (GTK_CONTAINER (wtoadd));
+	{
+	  GtkWidget *wtoadd = make_widget_for_menu_item (utf8_label, utf8_key);
+	  GList *list = gtk_container_get_children (GTK_CONTAINER (wtoadd));
 
-          wlbl = GTK_LABEL (list->data);
-          wkey = GTK_LABEL (list->next->data);
-          g_list_free (list);
+	  wlbl = GTK_LABEL (list->data);
+	  wkey = GTK_LABEL (list->next->data);
+	  g_list_free (list);
 
-          gtk_container_remove (GTK_CONTAINER (w), wchild);
-          gtk_container_add (GTK_CONTAINER (w), wtoadd);
-        }
+	  gtk_container_remove (GTK_CONTAINER (w), wchild);
+	  gtk_container_add (GTK_CONTAINER (w), wtoadd);
+	}
     }
 
   if (wkey) old_key = gtk_label_get_label (wkey);
@@ -3250,18 +3509,18 @@ xg_update_menu_item (widget_value *val,
 
       /* We assume the callback functions don't change.  */
       if (val->call_data && ! val->contents)
-        {
-          /* This item shall have a select callback.  */
-          if (! cb_data->select_id)
-            cb_data->select_id
-              = g_signal_connect (G_OBJECT (w), "activate",
-                                  select_cb, cb_data);
-        }
+	{
+	  /* This item shall have a select callback.  */
+	  if (! cb_data->select_id)
+	    cb_data->select_id
+	      = g_signal_connect (G_OBJECT (w), "activate",
+				  select_cb, cb_data);
+	}
       else if (cb_data->select_id)
-        {
-          g_signal_handler_disconnect (w, cb_data->select_id);
-          cb_data->select_id = 0;
-        }
+	{
+	  g_signal_handler_disconnect (w, cb_data->select_id);
+	  cb_data->select_id = 0;
+	}
     }
 
   if (label_changed) /* See comment in xg_update_menubar.  */
@@ -3296,7 +3555,7 @@ xg_update_radio_item (widget_value *val, GtkWidget *w)
    Returns the updated submenu widget, that is SUBMENU unless SUBMENU
    was NULL.  */
 
-static GtkWidget *
+GtkWidget *
 xg_update_submenu (GtkWidget *submenu,
 		   struct frame *f,
 		   widget_value *val,
@@ -3326,65 +3585,65 @@ xg_update_submenu (GtkWidget *submenu,
     if (cur->button_type == BUTTON_TYPE_RADIO && ! first_radio)
       first_radio = iter;
     else if (cur->button_type != BUTTON_TYPE_RADIO
-             && ! GTK_IS_RADIO_MENU_ITEM (w))
+	     && ! GTK_IS_RADIO_MENU_ITEM (w))
       first_radio = 0;
 
     if (GTK_IS_SEPARATOR_MENU_ITEM (w))
       {
-        if (! menu_separator_name_p (cur->name))
-          break;
+	if (! menu_separator_name_p (cur->name))
+	  break;
       }
     else if (GTK_IS_CHECK_MENU_ITEM (w))
       {
-        if (cur->button_type != BUTTON_TYPE_TOGGLE)
-          break;
-        xg_update_toggle_item (cur, w);
-        xg_update_menu_item (cur, w, select_cb, highlight_cb, cl_data);
+	if (cur->button_type != BUTTON_TYPE_TOGGLE)
+	  break;
+	xg_update_toggle_item (cur, w);
+	xg_update_menu_item (cur, w, select_cb, highlight_cb, cl_data);
       }
     else if (GTK_IS_RADIO_MENU_ITEM (w))
       {
-        if (cur->button_type != BUTTON_TYPE_RADIO)
-          break;
-        xg_update_radio_item (cur, w);
-        xg_update_menu_item (cur, w, select_cb, highlight_cb, cl_data);
+	if (cur->button_type != BUTTON_TYPE_RADIO)
+	  break;
+	xg_update_radio_item (cur, w);
+	xg_update_menu_item (cur, w, select_cb, highlight_cb, cl_data);
       }
     else if (GTK_IS_MENU_ITEM (w))
       {
-        GtkMenuItem *witem = GTK_MENU_ITEM (w);
-        GtkWidget *sub;
-
-        if (cur->button_type != BUTTON_TYPE_NONE ||
-            menu_separator_name_p (cur->name))
-          break;
-
-        xg_update_menu_item (cur, w, select_cb, highlight_cb, cl_data);
-
-        sub = gtk_menu_item_get_submenu (witem);
-        if (sub && ! cur->contents)
-          {
-            /* Not a submenu anymore.  */
-            g_object_ref (G_OBJECT (sub));
-            gtk_menu_item_set_submenu (witem, NULL);
-            gtk_widget_destroy (sub);
-          }
-        else if (cur->contents)
-          {
-            GtkWidget *nsub;
-
-            nsub = xg_update_submenu (sub, f, cur->contents,
-                                      select_cb, deactivate_cb,
-                                      highlight_cb, cl_data);
-
-            /* If this item just became a submenu, we must set it.  */
-            if (nsub != sub)
-              gtk_menu_item_set_submenu (witem, nsub);
-          }
+	GtkMenuItem *witem = GTK_MENU_ITEM (w);
+	GtkWidget *sub;
+
+	if (cur->button_type != BUTTON_TYPE_NONE ||
+	    menu_separator_name_p (cur->name))
+	  break;
+
+	xg_update_menu_item (cur, w, select_cb, highlight_cb, cl_data);
+
+	sub = gtk_menu_item_get_submenu (witem);
+	if (sub && ! cur->contents)
+	  {
+	    /* Not a submenu anymore.  */
+	    g_object_ref (G_OBJECT (sub));
+	    gtk_menu_item_set_submenu (witem, NULL);
+	    gtk_widget_destroy (sub);
+	  }
+	else if (cur->contents)
+	  {
+	    GtkWidget *nsub;
+
+	    nsub = xg_update_submenu (sub, f, cur->contents,
+				      select_cb, deactivate_cb,
+				      highlight_cb, cl_data);
+
+	    /* If this item just became a submenu, we must set it.  */
+	    if (nsub != sub)
+	      gtk_menu_item_set_submenu (witem, nsub);
+	  }
       }
     else
       {
-        /* Structural difference.  Remove everything from here and down
-           in SUBMENU.  */
-        break;
+	/* Structural difference.  Remove everything from here and down
+	   in SUBMENU.  */
+	break;
       }
   }
 
@@ -3392,7 +3651,7 @@ xg_update_submenu (GtkWidget *submenu,
   if (iter)
     {
       /* If we are adding new menu items below, we must remove from
-         first radio button so that radio groups become correct.  */
+	 first radio button so that radio groups become correct.  */
       if (cur && first_radio) xg_destroy_widgets (first_radio);
       else xg_destroy_widgets (iter);
     }
@@ -3401,15 +3660,15 @@ xg_update_submenu (GtkWidget *submenu,
     {
       /* More items added.  Create them.  */
       newsub = create_menus (cur,
-                             f,
-                             select_cb,
-                             deactivate_cb,
-                             highlight_cb,
-                             0,
-                             0,
-                             submenu,
-                             cl_data,
-                             0);
+			     f,
+			     select_cb,
+			     deactivate_cb,
+			     highlight_cb,
+			     0,
+			     0,
+			     submenu,
+			     cl_data,
+			     0);
     }
 
   if (list) g_list_free (list);
@@ -3429,7 +3688,7 @@ xg_update_submenu (GtkWidget *submenu,
 void
 xg_modify_menubar_widgets (GtkWidget *menubar, struct frame *f,
 			   widget_value *val, bool deep_p,
-                           GCallback select_cb, GCallback deactivate_cb,
+			   GCallback select_cb, GCallback deactivate_cb,
 			   GCallback highlight_cb)
 {
   xg_menu_cb_data *cl_data;
@@ -3440,59 +3699,60 @@ xg_modify_menubar_widgets (GtkWidget *menubar, struct frame *f,
   cl_data = g_object_get_data (G_OBJECT (menubar), XG_FRAME_DATA);
 
   xg_update_menubar (menubar, f, &list, list, 0, val->contents,
-                     select_cb, deactivate_cb, highlight_cb, cl_data);
+		     select_cb, deactivate_cb, highlight_cb, cl_data);
 
   if (deep_p)
     {
       widget_value *cur;
 
       /* Update all sub menus.
-         We must keep the submenus (GTK menu item widgets) since the
-         X Window in the XEvent that activates the menu are those widgets.  */
+	 We must keep the submenus (GTK menu item widgets) since the
+	 X Window in the XEvent that activates the menu are those widgets.  */
 
       /* Update cl_data, menu_item things in F may have changed.  */
       update_cl_data (cl_data, f, highlight_cb);
 
       for (cur = val->contents; cur; cur = cur->next)
-        {
-          GList *iter;
-          GtkWidget *sub = 0;
-          GtkWidget *newsub;
-          GtkMenuItem *witem = 0;
-
-          /* Find sub menu that corresponds to val and update it.  */
-          for (iter = list ; iter; iter = g_list_next (iter))
-            {
-              witem = GTK_MENU_ITEM (iter->data);
-              if (xg_item_label_same_p (witem, cur->name))
-                {
-                  sub = gtk_menu_item_get_submenu (witem);
-                  break;
-                }
-            }
+	{
+	  GList *iter;
+	  GtkWidget *sub = 0;
+	  GtkWidget *newsub;
+	  GtkMenuItem *witem = 0;
 
-          newsub = xg_update_submenu (sub,
-                                      f,
-                                      cur->contents,
-                                      select_cb,
-                                      deactivate_cb,
-                                      highlight_cb,
-                                      cl_data);
-          /* sub may still be NULL.  If we just updated non deep and added
-             a new menu bar item, it has no sub menu yet.  So we set the
-             newly created sub menu under witem.  */
-          if (newsub != sub && witem != 0)
-            {
-              xg_set_screen (newsub, f);
-              gtk_menu_item_set_submenu (witem, newsub);
-            }
-        }
+	  /* Find sub menu that corresponds to val and update it.  */
+	  for (iter = list ; iter; iter = g_list_next (iter))
+	    {
+	      witem = GTK_MENU_ITEM (iter->data);
+	      if (xg_item_label_same_p (witem, cur->name))
+		{
+		  sub = gtk_menu_item_get_submenu (witem);
+		  break;
+		}
+	    }
+
+	  newsub = xg_update_submenu (sub,
+				      f,
+				      cur->contents,
+				      select_cb,
+				      deactivate_cb,
+				      highlight_cb,
+				      cl_data);
+	  /* sub may still be NULL.  If we just updated non deep and added
+	     a new menu bar item, it has no sub menu yet.  So we set the
+	     newly created sub menu under witem.  */
+	  if (newsub != sub && witem != 0)
+	    {
+	      xg_set_screen (newsub, f);
+	      gtk_menu_item_set_submenu (witem, newsub);
+	    }
+	}
     }
 
   g_list_free (list);
   gtk_widget_show_all (menubar);
 }
 
+
 /* Callback called when the menu bar W is mapped.
    Used to find the height of the menu bar if we didn't get it
    after showing the widget.  */
@@ -3502,7 +3762,11 @@ menubar_map_cb (GtkWidget *w, gpointer user_data)
 {
   GtkRequisition req;
   struct frame *f = user_data;
+#ifdef HAVE_GTK3
   gtk_widget_get_preferred_size (w, NULL, &req);
+#else
+  gtk_widget_get_requisition (w, &req);
+#endif
   req.height *= xg_get_scale (f);
   if (FRAME_MENUBAR_HEIGHT (f) != req.height)
     {
@@ -3517,7 +3781,7 @@ menubar_map_cb (GtkWidget *w, gpointer user_data)
 void
 xg_update_frame_menubar (struct frame *f)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   GtkRequisition req;
 
   if (!x->menubar_widget || gtk_widget_get_mapped (x->menubar_widget))
@@ -3529,12 +3793,16 @@ xg_update_frame_menubar (struct frame *f)
   block_input ();
 
   gtk_box_pack_start (GTK_BOX (x->vbox_widget), x->menubar_widget,
-                      FALSE, FALSE, 0);
+		      FALSE, FALSE, 0);
   gtk_box_reorder_child (GTK_BOX (x->vbox_widget), x->menubar_widget, 0);
 
   g_signal_connect (x->menubar_widget, "map", G_CALLBACK (menubar_map_cb), f);
   gtk_widget_show_all (x->menubar_widget);
+#ifdef HAVE_GTK3
   gtk_widget_get_preferred_size (x->menubar_widget, NULL, &req);
+#else
+  gtk_widget_get_requisition (x->menubar_widget, &req);
+#endif
   req.height *= xg_get_scale (f);
   if (FRAME_MENUBAR_HEIGHT (f) != req.height)
     {
@@ -3550,7 +3818,7 @@ xg_update_frame_menubar (struct frame *f)
 void
 free_frame_menubar (struct frame *f)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
 
   if (x->menubar_widget)
     {
@@ -3558,7 +3826,7 @@ free_frame_menubar (struct frame *f)
 
       gtk_container_remove (GTK_CONTAINER (x->vbox_widget), x->menubar_widget);
        /* The menubar and its children shall be deleted when removed from
-          the container.  */
+	  the container.  */
       x->menubar_widget = 0;
       FRAME_MENUBAR_HEIGHT (f) = 0;
       adjust_frame_size (f, -1, -1, 2, 0, Qmenu_bar_lines);
@@ -3566,10 +3834,11 @@ free_frame_menubar (struct frame *f)
     }
 }
 
+#ifndef HAVE_PGTK
 bool
-xg_event_is_for_menubar (struct frame *f, const XEvent *event)
+xg_event_is_for_menubar (struct frame *f, const EVENT *event)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   GList *iter;
   GdkRectangle rec;
   GList *list;
@@ -3581,10 +3850,10 @@ xg_event_is_for_menubar (struct frame *f, const XEvent *event)
   if (! x->menubar_widget) return 0;
 
   if (! (event->xbutton.x >= 0
-         && event->xbutton.x < FRAME_PIXEL_WIDTH (f)
-         && event->xbutton.y >= 0
-         && event->xbutton.y < FRAME_MENUBAR_HEIGHT (f)
-         && event->xbutton.same_screen))
+	 && event->xbutton.x < FRAME_PIXEL_WIDTH (f)
+	 && event->xbutton.y >= 0
+	 && event->xbutton.y < FRAME_MENUBAR_HEIGHT (f)
+	 && event->xbutton.same_screen))
     return 0;
 
   gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
@@ -3611,16 +3880,17 @@ xg_event_is_for_menubar (struct frame *f, const XEvent *event)
     {
       GtkWidget *w = GTK_WIDGET (iter->data);
       if (gtk_widget_get_mapped (w) && gtk_widget_intersect (w, &rec, NULL))
-        break;
+	break;
     }
   g_list_free (list);
   return iter != 0;
 }
+#endif
 
 
 \f
 /***********************************************************************
-                      Scroll bar functions
+		      Scroll bar functions
  ***********************************************************************/
 
 
@@ -3658,7 +3928,11 @@ xg_store_widget_in_map (GtkWidget *w)
   if (id_to_widget.max_size == id_to_widget.used)
     {
       ptrdiff_t new_size;
+#ifndef HAVE_PGTK
       if (TYPE_MAXIMUM (Window) - ID_TO_WIDGET_INCR < id_to_widget.max_size)
+#else
+      if (TYPE_MAXIMUM (GWindow) - ID_TO_WIDGET_INCR < id_to_widget.max_size)
+#endif
 	memory_full (SIZE_MAX);
 
       new_size = id_to_widget.max_size + ID_TO_WIDGET_INCR;
@@ -3666,7 +3940,7 @@ xg_store_widget_in_map (GtkWidget *w)
 					new_size, sizeof (GtkWidget *));
 
       for (i = id_to_widget.max_size; i < new_size; ++i)
-        id_to_widget.widgets[i] = 0;
+	id_to_widget.widgets[i] = 0;
       id_to_widget.max_size = new_size;
     }
 
@@ -3676,12 +3950,12 @@ xg_store_widget_in_map (GtkWidget *w)
   for (i = 0; i < id_to_widget.max_size; ++i)
     {
       if (! id_to_widget.widgets[i])
-        {
-          id_to_widget.widgets[i] = w;
-          ++id_to_widget.used;
+	{
+	  id_to_widget.widgets[i] = w;
+	  ++id_to_widget.used;
 
-          return i;
-        }
+	  return i;
+	}
     }
 
   /* Should never end up here  */
@@ -3771,6 +4045,7 @@ xg_get_default_scrollbar_height (struct frame *f)
   return scroll_bar_width_for_theme * xg_get_scale (f);
 }
 
+#ifndef HAVE_PGTK
 /* Return the scrollbar id for X Window WID on display DPY.
    Return -1 if WID not in id_to_widget.  */
 
@@ -3785,12 +4060,13 @@ xg_get_scroll_id_for_window (Display *dpy, Window wid)
   if (w)
     {
       for (idx = 0; idx < id_to_widget.max_size; ++idx)
-        if (id_to_widget.widgets[idx] == w)
-          return idx;
+	if (id_to_widget.widgets[idx] == w)
+	  return idx;
     }
 
   return -1;
 }
+#endif
 
 /* Callback invoked when scroll bar WIDGET is destroyed.
    DATA is the index into id_to_widget for WIDGET.
@@ -3805,11 +4081,11 @@ xg_gtk_scroll_destroy (GtkWidget *widget, gpointer data)
 
 static void
 xg_finish_scroll_bar_creation (struct frame *f,
-                               GtkWidget *wscroll,
-                               struct scroll_bar *bar,
-                               GCallback scroll_callback,
-                               GCallback end_callback,
-                               const char *scroll_bar_name)
+			       GtkWidget *wscroll,
+			       struct scroll_bar *bar,
+			       GCallback scroll_callback,
+			       GCallback end_callback,
+			       const char *scroll_bar_name)
 {
   GtkWidget *webox = gtk_event_box_new ();
 
@@ -3822,17 +4098,17 @@ xg_finish_scroll_bar_creation (struct frame *f,
   ptrdiff_t scroll_id = xg_store_widget_in_map (wscroll);
 
   g_signal_connect (G_OBJECT (wscroll),
-                    "destroy",
-                    G_CALLBACK (xg_gtk_scroll_destroy),
-                    (gpointer) scroll_id);
+		    "destroy",
+		    G_CALLBACK (xg_gtk_scroll_destroy),
+		    (gpointer) scroll_id);
   g_signal_connect (G_OBJECT (wscroll),
-                    "change-value",
-                    scroll_callback,
-                    (gpointer) bar);
+		    "change-value",
+		    scroll_callback,
+		    (gpointer) bar);
   g_signal_connect (G_OBJECT (wscroll),
-                    "button-release-event",
-                    end_callback,
-                    (gpointer) bar);
+		    "button-release-event",
+		    end_callback,
+		    (gpointer) bar);
 
   /* The scroll bar widget does not draw on a window of its own.  Instead
      it draws on the parent window, in this case the edit widget.  So
@@ -3840,7 +4116,7 @@ xg_finish_scroll_bar_creation (struct frame *f,
      also, which causes flicker.  Put an event box between the edit widget
      and the scroll bar, so the scroll bar instead draws itself on the
      event box window.  */
-  gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget), webox, -1, -1);
+  gtk_fixed_put (GTK_FIXED (f->output_data.xp->edit_widget), webox, -1, -1);
   gtk_container_add (GTK_CONTAINER (webox), wscroll);
 
   xg_set_widget_bg (f, webox, FRAME_BACKGROUND_PIXEL (f));
@@ -3850,11 +4126,26 @@ xg_finish_scroll_bar_creation (struct frame *f,
      real X window, it and its scroll-bar child try to draw on the
      Emacs main window, which we draw over using Xlib.  */
   gtk_widget_realize (webox);
+#ifdef HAVE_PGTK
+  gtk_widget_show_all(webox);
+#endif
+#ifndef HAVE_PGTK
   GTK_WIDGET_TO_X_WIN (webox);
+#endif
 
   /* Set the cursor to an arrow.  */
   xg_set_cursor (webox, FRAME_DISPLAY_INFO (f)->xg_cursor);
 
+#ifdef HAVE_PGTK
+#ifdef HAVE_GTK3
+  GtkStyleContext *ctxt = gtk_widget_get_style_context(wscroll);
+  gtk_style_context_add_provider(ctxt, GTK_STYLE_PROVIDER(FRAME_OUTPUT_DATA (f)->scrollbar_foreground_css_provider),
+				 GTK_STYLE_PROVIDER_PRIORITY_USER);
+  gtk_style_context_add_provider(ctxt, GTK_STYLE_PROVIDER(FRAME_OUTPUT_DATA (f)->scrollbar_background_css_provider),
+				 GTK_STYLE_PROVIDER_PRIORITY_USER);
+#endif
+#endif
+
   bar->x_window = scroll_id;
 }
 
@@ -3868,10 +4159,10 @@ xg_finish_scroll_bar_creation (struct frame *f,
 
 void
 xg_create_scroll_bar (struct frame *f,
-                      struct scroll_bar *bar,
-                      GCallback scroll_callback,
-                      GCallback end_callback,
-                      const char *scroll_bar_name)
+		      struct scroll_bar *bar,
+		      GCallback scroll_callback,
+		      GCallback end_callback,
+		      const char *scroll_bar_name)
 {
   GtkWidget *wscroll;
 #ifdef HAVE_GTK3
@@ -3883,13 +4174,19 @@ xg_create_scroll_bar (struct frame *f,
   /* Page, step increment values are not so important here, they
      will be corrected in x_set_toolkit_scroll_bar_thumb. */
   vadj = gtk_adjustment_new (XG_SB_MIN, XG_SB_MIN, XG_SB_MAX,
-                             0.1, 0.1, 0.1);
+			     0.1, 0.1, 0.1);
 
   wscroll = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT (vadj));
 
   xg_finish_scroll_bar_creation (f, wscroll, bar, scroll_callback,
-                                 end_callback, scroll_bar_name);
+				 end_callback, scroll_bar_name);
   bar->horizontal = 0;
+
+#ifdef HAVE_GTK3
+  gtk_widget_override_background_color (wscroll, GTK_STATE_FLAG_NORMAL |
+					GTK_STATE_FLAG_ACTIVE,
+					0);
+#endif
 }
 
 /* Create a horizontal scroll bar widget for frame F.  Store the scroll
@@ -3915,12 +4212,12 @@ xg_create_horizontal_scroll_bar (struct frame *f,
   /* Page, step increment values are not so important here, they
      will be corrected in x_set_toolkit_scroll_bar_thumb. */
   hadj = gtk_adjustment_new (YG_SB_MIN, YG_SB_MIN, YG_SB_MAX,
-                             0.1, 0.1, 0.1);
+			     0.1, 0.1, 0.1);
 
   wscroll = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT (hadj));
 
   xg_finish_scroll_bar_creation (f, wscroll, bar, scroll_callback,
-                                 end_callback, scroll_bar_name);
+				 end_callback, scroll_bar_name);
   bar->horizontal = 1;
 }
 
@@ -3946,16 +4243,16 @@ xg_remove_scroll_bar (struct frame *f, ptrdiff_t scrollbar_id)
 
 void
 xg_update_scrollbar_pos (struct frame *f,
-                         ptrdiff_t scrollbar_id,
-                         int top,
-                         int left,
-                         int width,
-                         int height)
+			 ptrdiff_t scrollbar_id,
+			 int top,
+			 int left,
+			 int width,
+			 int height)
 {
   GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id);
   if (wscroll)
     {
-      GtkWidget *wfixed = f->output_data.x->edit_widget;
+      GtkWidget *wfixed = f->output_data.xp->edit_widget;
       GtkWidget *wparent = gtk_widget_get_parent (wscroll);
       gint msl;
       int scale = xg_get_scale (f);
@@ -3968,50 +4265,62 @@ xg_update_scrollbar_pos (struct frame *f,
       /* Clear out old position.  */
       int oldx = -1, oldy = -1, oldw, oldh;
       if (gtk_widget_get_parent (wparent) == wfixed)
-        {
-          gtk_container_child_get (GTK_CONTAINER (wfixed), wparent,
-                                   "x", &oldx, "y", &oldy, NULL);
-          gtk_widget_get_size_request (wscroll, &oldw, &oldh);
-        }
+	{
+	  gtk_container_child_get (GTK_CONTAINER (wfixed), wparent,
+				   "x", &oldx, "y", &oldy, NULL);
+	  gtk_widget_get_size_request (wscroll, &oldw, &oldh);
+	}
 
       /* Move and resize to new values.  */
       gtk_fixed_move (GTK_FIXED (wfixed), wparent, left, top);
       gtk_widget_style_get (wscroll, "min-slider-length", &msl, NULL);
       bool hidden = height < msl;
       if (hidden)
-        {
-          /* No room.  Hide scroll bar as some themes output a warning if
-             the height is less than the min size.  */
-          gtk_widget_hide (wparent);
-          gtk_widget_hide (wscroll);
-        }
+	{
+	  /* No room.  Hide scroll bar as some themes output a warning if
+	     the height is less than the min size.  */
+	  gtk_widget_hide (wparent);
+	  gtk_widget_hide (wscroll);
+	}
       else
-        {
-          gtk_widget_show_all (wparent);
-          gtk_widget_set_size_request (wscroll, width, height);
-        }
+	{
+	  gtk_widget_show_all (wparent);
+	  gtk_widget_set_size_request (wscroll, width, height);
+	}
       if (oldx != -1 && oldw > 0 && oldh > 0)
-        {
-          /* Clear under old scroll bar position.  */
-          oldw += (scale - 1) * oldw;
+	{
+	  /* Clear under old scroll bar position.  */
+	  oldw += (scale - 1) * oldw;
 	  oldx -= (scale - 1) * oldw;
-          x_clear_area (f, oldx, oldy, oldw, oldh);
-        }
+#ifndef HAVE_PGTK
+	  x_clear_area (f, oldx, oldy, oldw, oldh);
+#else
+	  pgtk_clear_area (f, oldx, oldy, oldw, oldh);
+#endif
+	}
 
       if (!hidden)
 	{
 	  GtkWidget *scrollbar = xg_get_widget_from_map (scrollbar_id);
 	  GtkWidget *webox = gtk_widget_get_parent (scrollbar);
 
+#ifndef HAVE_PGTK
 	  /* Don't obscure any child frames.  */
 	  XLowerWindow (FRAME_X_DISPLAY (f), GTK_WIDGET_TO_X_WIN (webox));
+#else
+	  gdk_window_lower(gtk_widget_get_window(webox));
+#endif
 	}
 
       /* GTK does not redraw until the main loop is entered again, but
-         if there are no X events pending we will not enter it.  So we sync
-         here to get some events.  */
+	 if there are no X events pending we will not enter it.  So we sync
+	 here to get some events.  */
 
+#ifndef HAVE_PGTK
       x_sync (f);
+#else
+      gdk_flush();
+#endif
       SET_FRAME_GARBAGED (f);
       cancel_mouse_face (f);
     }
@@ -4036,7 +4345,7 @@ xg_update_horizontal_scrollbar_pos (struct frame *f,
 
   if (wscroll)
     {
-      GtkWidget *wfixed = f->output_data.x->edit_widget;
+      GtkWidget *wfixed = f->output_data.xp->edit_widget;
       GtkWidget *wparent = gtk_widget_get_parent (wscroll);
       gint msl;
       int scale = xg_get_scale (f);
@@ -4049,45 +4358,57 @@ xg_update_horizontal_scrollbar_pos (struct frame *f,
       /* Clear out old position.  */
       int oldx = -1, oldy = -1, oldw, oldh;
       if (gtk_widget_get_parent (wparent) == wfixed)
-        {
-          gtk_container_child_get (GTK_CONTAINER (wfixed), wparent,
-                                   "x", &oldx, "y", &oldy, NULL);
-          gtk_widget_get_size_request (wscroll, &oldw, &oldh);
-        }
+	{
+	  gtk_container_child_get (GTK_CONTAINER (wfixed), wparent,
+				   "x", &oldx, "y", &oldy, NULL);
+	  gtk_widget_get_size_request (wscroll, &oldw, &oldh);
+	}
 
       /* Move and resize to new values.  */
       gtk_fixed_move (GTK_FIXED (wfixed), wparent, left, top);
       gtk_widget_style_get (wscroll, "min-slider-length", &msl, NULL);
       if (msl > width)
-        {
-          /* No room.  Hide scroll bar as some themes output a warning if
-             the width is less than the min size.  */
-          gtk_widget_hide (wparent);
-          gtk_widget_hide (wscroll);
-        }
+	{
+	  /* No room.  Hide scroll bar as some themes output a warning if
+	     the width is less than the min size.  */
+	  gtk_widget_hide (wparent);
+	  gtk_widget_hide (wscroll);
+	}
       else
-        {
-          gtk_widget_show_all (wparent);
-          gtk_widget_set_size_request (wscroll, width, height);
-        }
+	{
+	  gtk_widget_show_all (wparent);
+	  gtk_widget_set_size_request (wscroll, width, height);
+	}
       if (oldx != -1 && oldw > 0 && oldh > 0)
-        /* Clear under old scroll bar position.  */
-        x_clear_area (f, oldx, oldy, oldw, oldh);
+	/* Clear under old scroll bar position.  */
+#ifndef HAVE_PGTK
+	x_clear_area (f, oldx, oldy, oldw, oldh);
+#else
+	pgtk_clear_area (f, oldx, oldy, oldw, oldh);
+#endif
 
       /* GTK does not redraw until the main loop is entered again, but
-         if there are no X events pending we will not enter it.  So we sync
-         here to get some events.  */
+	 if there are no X events pending we will not enter it.  So we sync
+	 here to get some events.  */
 
       {
 	GtkWidget *scrollbar =
 	  xg_get_widget_from_map (scrollbar_id);
 	GtkWidget *webox = gtk_widget_get_parent (scrollbar);
 
+#ifndef HAVE_PGTK
 	/* Don't obscure any child frames.  */
 	XLowerWindow (FRAME_X_DISPLAY (f), GTK_WIDGET_TO_X_WIN (webox));
+#else
+	gdk_window_lower(gtk_widget_get_window(webox));
+#endif
       }
 
+#ifndef HAVE_PGTK
       x_sync (f);
+#else
+      gdk_flush();
+#endif
       SET_FRAME_GARBAGED (f);
       cancel_mouse_face (f);
     }
@@ -4108,14 +4429,13 @@ int_gtk_range_get_value (GtkRange *range)
 
 void
 xg_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar,
-                                 int portion,
-                                 int position,
-                                 int whole)
+				 int portion,
+				 int position,
+				 int whole)
 {
   GtkWidget *wscroll = xg_get_widget_from_map (bar->x_window);
 
   struct frame *f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window)));
-
   if (wscroll && bar->dragging == -1)
     {
       GtkAdjustment *adj;
@@ -4129,25 +4449,24 @@ xg_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar,
       adj = gtk_range_get_adjustment (GTK_RANGE (wscroll));
 
       if (scroll_bar_adjust_thumb_portion_p)
-        {
-          /* We do the same as for MOTIF in xterm.c, use 30 chars per
-             line rather than the real portion value.  This makes the
-             thumb less likely to resize and that looks better.  */
-          portion = WINDOW_TOTAL_LINES (XWINDOW (bar->window)) * 30;
-
-          /* When the thumb is at the bottom, position == whole.
-             So we need to increase `whole' to make space for the thumb.  */
-          whole += portion;
-        }
+	{
+	  /* We do the same as for MOTIF in xterm.c, use 30 chars per
+	     line rather than the real portion value.  This makes the
+	     thumb less likely to resize and that looks better.  */
+	  portion = WINDOW_TOTAL_LINES (XWINDOW (bar->window)) * 30;
+
+	  /* When the thumb is at the bottom, position == whole.
+	     So we need to increase `whole' to make space for the thumb.  */
+	  whole += portion;
+	}
 
       if (whole <= 0)
-        top = 0, shown = 1;
+	top = 0, shown = 1;
       else
-        {
-          top = (gdouble) position / whole;
-          shown = (gdouble) portion / whole;
-        }
-
+	{
+	  top = (gdouble) position / whole;
+	  shown = (gdouble) portion / whole;
+	}
       size = clip_to_bounds (1, shown * XG_SB_RANGE, XG_SB_RANGE);
       value = clip_to_bounds (XG_SB_MIN, top * XG_SB_RANGE, XG_SB_MAX - size);
 
@@ -4158,6 +4477,7 @@ xg_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar,
       if (old_size != size)
 	{
 	  int old_step = gtk_adjustment_get_step_increment (adj);
+	  PGTK_TRACE("xg_set_toolkit_scroll_bar_thumb: old_step=%d, new_step=%d", old_step, new_step);
 	  if (old_step != new_step)
 	    {
 	      gtk_adjustment_set_page_size (adj, size);
@@ -4170,22 +4490,22 @@ xg_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar,
 
       if (changed || int_gtk_range_get_value (GTK_RANGE (wscroll)) != value)
       {
-        block_input ();
+	block_input ();
 
-        /* gtk_range_set_value invokes the callback.  Set
-           ignore_gtk_scrollbar to make the callback do nothing  */
-        xg_ignore_gtk_scrollbar = 1;
+	/* gtk_range_set_value invokes the callback.  Set
+	   ignore_gtk_scrollbar to make the callback do nothing  */
+	xg_ignore_gtk_scrollbar = 1;
 
-        if (int_gtk_range_get_value (GTK_RANGE (wscroll)) != value)
-          gtk_range_set_value (GTK_RANGE (wscroll), (gdouble)value);
+	if (int_gtk_range_get_value (GTK_RANGE (wscroll)) != value)
+	  gtk_range_set_value (GTK_RANGE (wscroll), (gdouble)value);
 #if ! GTK_CHECK_VERSION (3, 18, 0)
-        else if (changed)
-          gtk_adjustment_changed (adj);
+	else if (changed)
+	  gtk_adjustment_changed (adj);
 #endif
 
-        xg_ignore_gtk_scrollbar = 0;
+	xg_ignore_gtk_scrollbar = 0;
 
-        unblock_input ();
+	unblock_input ();
       }
     }
 }
@@ -4231,32 +4551,48 @@ xg_set_toolkit_horizontal_scroll_bar_thumb (struct scroll_bar *bar,
    frame.  This function does additional checks.  */
 
 bool
-xg_event_is_for_scrollbar (struct frame *f, const XEvent *event)
+xg_event_is_for_scrollbar (struct frame *f, const EVENT *event)
 {
   bool retval = 0;
 
-  if (f && event->type == ButtonPress && event->xbutton.button < 4)
+  if (f
+#ifndef HAVE_PGTK
+      && event->type == ButtonPress && event->xbutton.button < 4
+#else
+      && event->type == GDK_BUTTON_PRESS && event->button.button < 4
+#endif
+      )
     {
       /* Check if press occurred outside the edit widget.  */
+#ifndef HAVE_PGTK
       GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
+#else
+      GdkDisplay *gdpy = FRAME_X_DISPLAY(f);
+#endif
       GdkWindow *gwin;
 #ifdef HAVE_GTK3
 #if GTK_CHECK_VERSION (3, 20, 0)
       GdkDevice *gdev
-        = gdk_seat_get_pointer (gdk_display_get_default_seat (gdpy));
+	= gdk_seat_get_pointer (gdk_display_get_default_seat (gdpy));
 #else
       GdkDevice *gdev = gdk_device_manager_get_client_pointer
-        (gdk_display_get_device_manager (gdpy));
+	(gdk_display_get_device_manager (gdpy));
 #endif
       gwin = gdk_device_get_window_at_position (gdev, NULL, NULL);
 #else
       gwin = gdk_display_get_window_at_pointer (gdpy, NULL, NULL);
 #endif
-      retval = gwin != gtk_widget_get_window (f->output_data.x->edit_widget);
+      retval = gwin != gtk_widget_get_window (f->output_data.xp->edit_widget);
     }
   else if (f
-           && ((event->type == ButtonRelease && event->xbutton.button < 4)
-               || event->type == MotionNotify))
+#ifndef HAVE_PGTK
+	   && ((event->type == ButtonRelease && event->xbutton.button < 4)
+	       || event->type == MotionNotify)
+#else
+	   && ((event->type == GDK_BUTTON_RELEASE && event->button.button < 4)
+	       || event->type == GDK_MOTION_NOTIFY)
+#endif
+	   )
     {
       /* If we are releasing or moving the scroll bar, it has the grab.  */
       GtkWidget *w = gtk_grab_get_current ();
@@ -4334,7 +4670,11 @@ draw_page (GtkPrintOperation *operation, GtkPrintContext *context,
   struct frame *f = XFRAME (Fnth (make_fixnum (page_nr), frames));
   cairo_t *cr = gtk_print_context_get_cairo_context (context);
 
+#ifndef HAVE_PGTK
   x_cr_draw_frame (cr, f);
+#else
+  pgtk_cr_draw_frame (cr, f);
+#endif
 }
 
 void
@@ -4351,11 +4691,11 @@ xg_print_frames_dialog (Lisp_Object frames)
   gtk_print_operation_set_n_pages (print, list_length (frames));
   g_signal_connect (print, "draw-page", G_CALLBACK (draw_page), &frames);
   res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
-                                 NULL, NULL);
+				 NULL, NULL);
   if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
     {
       if (print_settings != NULL)
-        g_object_unref (print_settings);
+	g_object_unref (print_settings);
       print_settings =
 	g_object_ref (gtk_print_operation_get_print_settings (print));
     }
@@ -4367,7 +4707,7 @@ xg_print_frames_dialog (Lisp_Object frames)
 
 \f
 /***********************************************************************
-                      Tool bar functions
+		      Tool bar functions
  ***********************************************************************/
 /* The key for the data we put in the GtkImage widgets.  The data is
    the image used by Emacs.  We use this to see if we need to update
@@ -4394,8 +4734,8 @@ #define XG_TOOL_BAR_ICON_NAME "emacs-tool-bar-icon-name"
 
 static gboolean
 xg_tool_bar_button_cb (GtkWidget *widget,
-                       GdkEventButton *event,
-                       gpointer user_data)
+		       GdkEventButton *event,
+		       gpointer user_data)
 {
   intptr_t state = event->state;
   gpointer ptr = (gpointer) state;
@@ -4435,7 +4775,11 @@ xg_tool_bar_callback (GtkWidget *w, gpointer client_data)
   /* Convert between the modifier bits GDK uses and the modifier bits
      Emacs uses.  This assumes GDK and X masks are the same, which they are when
      this is written.  */
+#ifndef HAVE_PGTK
   event.modifiers = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod);
+#else
+  event.modifiers = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod);
+#endif
   kbd_buffer_store_event (&event);
 
   /* Return focus to the frame after we have clicked on a detached
@@ -4467,8 +4811,8 @@ xg_get_tool_bar_widgets (GtkWidget *vb, GtkWidget **wimage)
 
 static gboolean
 xg_tool_bar_help_callback (GtkWidget *w,
-                           GdkEventCrossing *event,
-                           gpointer client_data)
+			   GdkEventCrossing *event,
+			   gpointer client_data)
 {
   intptr_t idx = (intptr_t) client_data;
   struct frame *f = g_object_get_data (G_OBJECT (w), XG_FRAME_DATA);
@@ -4483,7 +4827,7 @@ xg_tool_bar_help_callback (GtkWidget *w,
       help = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_HELP);
 
       if (NILP (help))
-        help = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_CAPTION);
+	help = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_CAPTION);
     }
   else
     help = Qnil;
@@ -4495,6 +4839,7 @@ xg_tool_bar_help_callback (GtkWidget *w,
 }
 
 
+#ifndef HAVE_GTK3
 /* This callback is called when a tool bar item shall be redrawn.
    It modifies the expose event so that the GtkImage widget redraws the
    whole image.  This to overcome a bug that makes GtkImage draw the image
@@ -4505,11 +4850,10 @@ xg_tool_bar_help_callback (GtkWidget *w,
 
    Returns FALSE to tell GTK to keep processing this event.  */
 
-#ifndef HAVE_GTK3
 static gboolean
 xg_tool_bar_item_expose_callback (GtkWidget *w,
-                                  GdkEventExpose *event,
-                                  gpointer client_data)
+				  GdkEventExpose *event,
+				  gpointer client_data)
 {
   gint width, height;
 
@@ -4532,36 +4876,36 @@ xg_tool_bar_item_expose_callback (GtkWidget *w,
 static void
 xg_pack_tool_bar (struct frame *f, Lisp_Object pos)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   bool into_hbox = EQ (pos, Qleft) || EQ (pos, Qright);
   GtkWidget *top_widget = x->toolbar_widget;
 
   gtk_orientable_set_orientation (GTK_ORIENTABLE (x->toolbar_widget),
-                                  into_hbox
-                                  ? GTK_ORIENTATION_VERTICAL
-                                  : GTK_ORIENTATION_HORIZONTAL);
+				  into_hbox
+				  ? GTK_ORIENTATION_VERTICAL
+				  : GTK_ORIENTATION_HORIZONTAL);
 
   if (into_hbox)
     {
       gtk_box_pack_start (GTK_BOX (x->hbox_widget), top_widget,
-                          FALSE, FALSE, 0);
+			  FALSE, FALSE, 0);
 
       if (EQ (pos, Qleft))
-        gtk_box_reorder_child (GTK_BOX (x->hbox_widget),
-                               top_widget,
-                               0);
+	gtk_box_reorder_child (GTK_BOX (x->hbox_widget),
+			       top_widget,
+			       0);
       x->toolbar_in_hbox = true;
     }
   else
     {
       bool vbox_pos = x->menubar_widget != 0;
       gtk_box_pack_start (GTK_BOX (x->vbox_widget), top_widget,
-                          FALSE, FALSE, 0);
+			  FALSE, FALSE, 0);
 
       if (EQ (pos, Qtop))
-        gtk_box_reorder_child (GTK_BOX (x->vbox_widget),
-                               top_widget,
-                               vbox_pos);
+	gtk_box_reorder_child (GTK_BOX (x->vbox_widget),
+			       top_widget,
+			       vbox_pos);
       x->toolbar_in_hbox = false;
     }
   x->toolbar_is_packed = true;
@@ -4571,8 +4915,8 @@ xg_pack_tool_bar (struct frame *f, Lisp_Object pos)
 
 static void
 tb_size_cb (GtkWidget    *widget,
-            GdkRectangle *allocation,
-            gpointer      user_data)
+	    GdkRectangle *allocation,
+	    gpointer      user_data)
 {
   /* When tool bar is created it has one preferred size.  But when size is
      allocated between widgets, it may get another.  So we must update
@@ -4591,13 +4935,13 @@ tb_size_cb (GtkWidget    *widget,
 static void
 xg_create_tool_bar (struct frame *f)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
 #ifdef HAVE_GTK3
   GtkStyleContext *gsty;
 #endif
   struct xg_frame_tb_info *tbinfo
     = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                         TB_INFO_KEY);
+			 TB_INFO_KEY);
   if (! tbinfo)
     {
       tbinfo = xmalloc (sizeof (*tbinfo));
@@ -4607,8 +4951,8 @@ xg_create_tool_bar (struct frame *f)
       tbinfo->dir = GTK_TEXT_DIR_NONE;
       tbinfo->n_last_items = 0;
       g_object_set_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                         TB_INFO_KEY,
-                         tbinfo);
+			 TB_INFO_KEY,
+			 tbinfo);
     }
 
   x->toolbar_widget = gtk_toolbar_new ();
@@ -4617,9 +4961,9 @@ xg_create_tool_bar (struct frame *f)
 
   gtk_toolbar_set_style (GTK_TOOLBAR (x->toolbar_widget), GTK_TOOLBAR_ICONS);
   gtk_orientable_set_orientation (GTK_ORIENTABLE (x->toolbar_widget),
-                                  GTK_ORIENTATION_HORIZONTAL);
+				  GTK_ORIENTATION_HORIZONTAL);
   g_signal_connect (x->toolbar_widget, "size-allocate",
-                    G_CALLBACK (tb_size_cb), f);
+		    G_CALLBACK (tb_size_cb), f);
 #ifdef HAVE_GTK3
   gsty = gtk_widget_get_style_context (x->toolbar_widget);
   gtk_style_context_add_class (gsty, "primary-toolbar");
@@ -4629,7 +4973,7 @@ xg_create_tool_bar (struct frame *f)
 
 #define PROP(IDX) AREF (f->tool_bar_items, i * TOOL_BAR_ITEM_NSLOTS + (IDX))
 
-/* Find the right-to-left image named by RTL in the tool bar images for F.
+/* find the right-to-left image named by RTL in the tool bar images for F.
    Returns IMAGE if RTL is not found.  */
 
 static Lisp_Object
@@ -4644,15 +4988,15 @@ find_rtl_image (struct frame *f, Lisp_Object image, Lisp_Object rtl)
     {
       Lisp_Object rtl_image = PROP (TOOL_BAR_ITEM_IMAGES);
       if (!NILP (file = file_for_image (rtl_image)))
-        {
-          file = call1 (intern ("file-name-sans-extension"),
-                       Ffile_name_nondirectory (file));
-          if (! NILP (Fequal (file, rtl_name)))
-            {
-              image = rtl_image;
-              break;
-            }
-        }
+	{
+	  file = call1 (intern ("file-name-sans-extension"),
+		       Ffile_name_nondirectory (file));
+	  if (! NILP (Fequal (file, rtl_name)))
+	    {
+	      image = rtl_image;
+	      break;
+	    }
+	}
     }
 
   return image;
@@ -4660,16 +5004,16 @@ find_rtl_image (struct frame *f, Lisp_Object image, Lisp_Object rtl)
 
 static GtkToolItem *
 xg_make_tool_item (struct frame *f,
-                   GtkWidget *wimage,
-                   GtkWidget **wbutton,
-                   const char *label,
-                   int i, bool horiz, bool text_image)
+		   GtkWidget *wimage,
+		   GtkWidget **wbutton,
+		   const char *label,
+		   int i, bool horiz, bool text_image)
 {
   GtkToolItem *ti = gtk_tool_item_new ();
   GtkWidget *vb = gtk_box_new (horiz
-                               ? GTK_ORIENTATION_HORIZONTAL
-                               : GTK_ORIENTATION_VERTICAL,
-                               0);
+			       ? GTK_ORIENTATION_HORIZONTAL
+			       : GTK_ORIENTATION_VERTICAL,
+			       0);
   GtkWidget *wb = gtk_button_new ();
   /* The eventbox is here so we can have tooltips on disabled items.  */
   GtkWidget *weventbox = gtk_event_box_new ();
@@ -4715,42 +5059,42 @@ xg_make_tool_item (struct frame *f,
       gpointer gi = (gpointer) ii;
 
       g_signal_connect (G_OBJECT (wb), "clicked",
-                        G_CALLBACK (xg_tool_bar_callback),
-                        gi);
+			G_CALLBACK (xg_tool_bar_callback),
+			gi);
 
       g_object_set_data (G_OBJECT (weventbox), XG_FRAME_DATA, (gpointer)f);
 
 #ifndef HAVE_GTK3
       /* Catch expose events to overcome an annoying redraw bug, see
-         comment for xg_tool_bar_item_expose_callback.  */
+	 comment for xg_tool_bar_item_expose_callback.  */
       g_signal_connect (G_OBJECT (ti),
-                        "expose-event",
-                        G_CALLBACK (xg_tool_bar_item_expose_callback),
-                        0);
+			"expose-event",
+			G_CALLBACK (xg_tool_bar_item_expose_callback),
+			0);
 #endif
       gtk_tool_item_set_homogeneous (ti, FALSE);
 
       /* Callback to save modifier mask (Shift/Control, etc).  GTK makes
-         no distinction based on modifiers in the activate callback,
-         so we have to do it ourselves.  */
+	 no distinction based on modifiers in the activate callback,
+	 so we have to do it ourselves.  */
       g_signal_connect (wb, "button-release-event",
-                        G_CALLBACK (xg_tool_bar_button_cb),
-                        NULL);
+			G_CALLBACK (xg_tool_bar_button_cb),
+			NULL);
 
       g_object_set_data (G_OBJECT (wb), XG_FRAME_DATA, (gpointer)f);
 
       /* Use enter/leave notify to show help.  We use the events
-         rather than the GtkButton specific signals "enter" and
-         "leave", so we can have only one callback.  The event
-         will tell us what kind of event it is.  */
+	 rather than the GtkButton specific signals "enter" and
+	 "leave", so we can have only one callback.  The event
+	 will tell us what kind of event it is.  */
       g_signal_connect (G_OBJECT (weventbox),
-                        "enter-notify-event",
-                        G_CALLBACK (xg_tool_bar_help_callback),
-                        gi);
+			"enter-notify-event",
+			G_CALLBACK (xg_tool_bar_help_callback),
+			gi);
       g_signal_connect (G_OBJECT (weventbox),
-                        "leave-notify-event",
-                        G_CALLBACK (xg_tool_bar_help_callback),
-                        gi);
+			"leave-notify-event",
+			G_CALLBACK (xg_tool_bar_help_callback),
+			gi);
     }
 
   if (wbutton) *wbutton = wb;
@@ -4767,7 +5111,7 @@ is_box_type (GtkWidget *vb, bool is_horizontal)
     {
       GtkOrientation ori = gtk_orientable_get_orientation (GTK_ORIENTABLE (vb));
       ret = (ori == GTK_ORIENTATION_HORIZONTAL && is_horizontal)
-        || (ori == GTK_ORIENTATION_VERTICAL && ! is_horizontal);
+	|| (ori == GTK_ORIENTATION_VERTICAL && ! is_horizontal);
     }
   return ret;
 #else
@@ -4804,7 +5148,7 @@ xg_tool_item_stale_p (GtkWidget *wbutton, const char *stock_name,
   else if (wimage)
     {
       gpointer gold_img = g_object_get_data (G_OBJECT (wimage),
-                                             XG_TOOL_BAR_IMAGE_DATA);
+					     XG_TOOL_BAR_IMAGE_DATA);
 #ifdef USE_CAIRO
       void *old_img = (void *) gold_img;
       if (old_img != img->cr_data)
@@ -4830,18 +5174,21 @@ xg_tool_item_stale_p (GtkWidget *wbutton, const char *stock_name,
 static bool
 xg_update_tool_bar_sizes (struct frame *f)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   GtkRequisition req;
   int nl = 0, nr = 0, nt = 0, nb = 0;
   GtkWidget *top_widget = x->toolbar_widget;
-
+#ifdef HAVE_GTK3
   gtk_widget_get_preferred_size (GTK_WIDGET (top_widget), NULL, &req);
+#else
+  gtk_widget_get_requisition (GTK_WIDGET (top_widget), &req);
+#endif
   if (x->toolbar_in_hbox)
     {
       int pos;
       gtk_container_child_get (GTK_CONTAINER (x->hbox_widget),
-                               top_widget,
-                               "position", &pos, NULL);
+			       top_widget,
+			       "position", &pos, NULL);
       if (pos == 0) nl = req.width;
       else nr = req.width;
     }
@@ -4849,8 +5196,8 @@ xg_update_tool_bar_sizes (struct frame *f)
     {
       int pos;
       gtk_container_child_get (GTK_CONTAINER (x->vbox_widget),
-                               top_widget,
-                               "position", &pos, NULL);
+			       top_widget,
+			       "position", &pos, NULL);
       if (pos == 0 || (pos == 1 && x->menubar_widget)) nt = req.height;
       else nb = req.height;
     }
@@ -4861,7 +5208,7 @@ xg_update_tool_bar_sizes (struct frame *f)
       || nb != FRAME_TOOLBAR_BOTTOM_HEIGHT (f))
     {
       FRAME_TOOLBAR_RIGHT_WIDTH (f) = FRAME_TOOLBAR_LEFT_WIDTH (f)
-        = FRAME_TOOLBAR_TOP_HEIGHT (f) = FRAME_TOOLBAR_BOTTOM_HEIGHT (f) = 0;
+	= FRAME_TOOLBAR_TOP_HEIGHT (f) = FRAME_TOOLBAR_BOTTOM_HEIGHT (f) = 0;
       FRAME_TOOLBAR_LEFT_WIDTH (f) = nl;
       FRAME_TOOLBAR_RIGHT_WIDTH (f) = nr;
       FRAME_TOOLBAR_TOP_HEIGHT (f) = nt;
@@ -4875,8 +5222,8 @@ xg_update_tool_bar_sizes (struct frame *f)
 
 static char *
 find_icon_from_name (char *name,
-                     GtkIconTheme *icon_theme,
-                     char **icon_name)
+		     GtkIconTheme *icon_theme,
+		     char **icon_name)
 {
 #ifndef HAVE_GTK3
   GtkStockItem stock_item;
@@ -4888,7 +5235,7 @@ find_icon_from_name (char *name,
       name = NULL;
 
       if (! gtk_icon_theme_has_icon (icon_theme, *icon_name))
-        *icon_name = NULL;
+	*icon_name = NULL;
     }
 
 #ifndef HAVE_GTK3
@@ -4916,7 +5263,11 @@ find_icon_from_name (char *name,
 update_frame_tool_bar (struct frame *f)
 {
   int i, j;
-  struct x_output *x = f->output_data.x;
+#ifndef HAVE_PGTK
+  struct x_output *x = f->output_data.xp;
+#else
+  struct pgtk_output *x = f->output_data.pgtk;
+#endif
   int hmargin = 0, vmargin = 0;
   GtkToolbar *wtoolbar;
   GtkToolItem *ti;
@@ -4941,10 +5292,10 @@ update_frame_tool_bar (struct frame *f)
   else if (CONSP (Vtool_bar_button_margin))
     {
       if (RANGED_FIXNUMP (1, XCAR (Vtool_bar_button_margin), INT_MAX))
-        hmargin = XFIXNAT (XCAR (Vtool_bar_button_margin));
+	hmargin = XFIXNAT (XCAR (Vtool_bar_button_margin));
 
       if (RANGED_FIXNUMP (1, XCDR (Vtool_bar_button_margin), INT_MAX))
-        vmargin = XFIXNAT (XCDR (Vtool_bar_button_margin));
+	vmargin = XFIXNAT (XCDR (Vtool_bar_button_margin));
     }
 
   /* The natural size (i.e. when GTK uses 0 as margin) looks best,
@@ -4966,7 +5317,7 @@ update_frame_tool_bar (struct frame *f)
 
   /* Are we up to date? */
   tbinfo = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                              TB_INFO_KEY);
+			      TB_INFO_KEY);
 
   if (! NILP (tbinfo->last_tool_bar) && ! NILP (f->tool_bar_items)
       && tbinfo->n_last_items == f->n_tool_bar_items
@@ -5041,84 +5392,84 @@ update_frame_tool_bar (struct frame *f)
       /* Ignore invalid image specifications.  */
       image = PROP (TOOL_BAR_ITEM_IMAGES);
       if (!valid_image_p (image))
-        {
-          if (ti)
+	{
+	  if (ti)
 	    gtk_container_remove (GTK_CONTAINER (wtoolbar),
 				  GTK_WIDGET (ti));
-          continue;
-        }
+	  continue;
+	}
 
       specified_file = file_for_image (image);
       if (!NILP (specified_file) && !NILP (Ffboundp (Qx_gtk_map_stock)))
-        stock = call1 (Qx_gtk_map_stock, specified_file);
+	stock = call1 (Qx_gtk_map_stock, specified_file);
 
       if (CONSP (stock))
-        {
-          Lisp_Object tem;
-          for (tem = stock; CONSP (tem); tem = XCDR (tem))
-            if (! NILP (tem) && STRINGP (XCAR (tem)))
-              {
-                stock_name = find_icon_from_name (SSDATA (XCAR (tem)),
-                                                  icon_theme,
-                                                  &icon_name);
-                if (stock_name || icon_name) break;
-              }
-        }
+	{
+	  Lisp_Object tem;
+	  for (tem = stock; CONSP (tem); tem = XCDR (tem))
+	    if (! NILP (tem) && STRINGP (XCAR (tem)))
+	      {
+		stock_name = find_icon_from_name (SSDATA (XCAR (tem)),
+						  icon_theme,
+						  &icon_name);
+		if (stock_name || icon_name) break;
+	      }
+	}
       else if (STRINGP (stock))
-        {
-          stock_name = find_icon_from_name (SSDATA (stock),
-                                            icon_theme,
-                                            &icon_name);
-        }
+	{
+	  stock_name = find_icon_from_name (SSDATA (stock),
+					    icon_theme,
+					    &icon_name);
+	}
 
       if (stock_name || icon_name)
-        icon_size = gtk_toolbar_get_icon_size (wtoolbar);
+	icon_size = gtk_toolbar_get_icon_size (wtoolbar);
 
       if (stock_name == NULL && icon_name == NULL)
-        {
-          /* No stock image, or stock item not known.  Try regular
-             image.  If image is a vector, choose it according to the
-             button state.  */
-          if (dir == GTK_TEXT_DIR_RTL
-              && !NILP (rtl = PROP (TOOL_BAR_ITEM_RTL_IMAGE))
-              && STRINGP (rtl))
+	{
+	  /* No stock image, or stock item not known.  Try regular
+	     image.  If image is a vector, choose it according to the
+	     button state.  */
+	  if (dir == GTK_TEXT_DIR_RTL
+	      && !NILP (rtl = PROP (TOOL_BAR_ITEM_RTL_IMAGE))
+	      && STRINGP (rtl))
 	    image = find_rtl_image (f, image, rtl);
 
-          if (VECTORP (image))
-            {
-              if (enabled_p)
-                idx = (selected_p
-                       ? TOOL_BAR_IMAGE_ENABLED_SELECTED
-                       : TOOL_BAR_IMAGE_ENABLED_DESELECTED);
-              else
-                idx = (selected_p
-                       ? TOOL_BAR_IMAGE_DISABLED_SELECTED
-                       : TOOL_BAR_IMAGE_DISABLED_DESELECTED);
-
-              eassert (ASIZE (image) >= idx);
-              image = AREF (image, idx);
-            }
-          else
-            idx = -1;
+	  if (VECTORP (image))
+	    {
+	      if (enabled_p)
+		idx = (selected_p
+		       ? TOOL_BAR_IMAGE_ENABLED_SELECTED
+		       : TOOL_BAR_IMAGE_ENABLED_DESELECTED);
+	      else
+		idx = (selected_p
+		       ? TOOL_BAR_IMAGE_DISABLED_SELECTED
+		       : TOOL_BAR_IMAGE_DISABLED_DESELECTED);
 
-          img_id = lookup_image (f, image);
-          img = IMAGE_FROM_ID (f, img_id);
-          prepare_image_for_display (f, img);
+	      eassert (ASIZE (image) >= idx);
+	      image = AREF (image, idx);
+	    }
+	  else
+	    idx = -1;
+
+	  img_id = lookup_image (f, image);
+	  img = IMAGE_FROM_ID (f, img_id);
+	  prepare_image_for_display (f, img);
 
-          if (img->load_failed_p
+	  if (img->load_failed_p
 #ifdef USE_CAIRO
 	      || img->cr_data == NULL
 #else
 	      || img->pixmap == None
 #endif
 	      )
-            {
-              if (ti)
+	    {
+	      if (ti)
 		gtk_container_remove (GTK_CONTAINER (wtoolbar),
 				      GTK_WIDGET (ti));
-              continue;
-            }
-        }
+	      continue;
+	    }
+	}
 
       /* If there is an existing widget, check if it's stale; if so,
 	 remove it and make a new tool item from scratch.  */
@@ -5131,58 +5482,58 @@ update_frame_tool_bar (struct frame *f)
 	}
 
       if (ti == NULL)
-        {
-          GtkWidget *w;
+	{
+	  GtkWidget *w;
 
 	  /* Save the image so we can see if an update is needed the
 	     next time we call xg_tool_item_match_p.  */
 	  if (EQ (style, Qtext))
 	    w = NULL;
 	  else if (stock_name)
-            {
+	    {
 
 #ifdef HAVE_GTK3
-              w = gtk_image_new_from_icon_name (stock_name, icon_size);
+	      w = gtk_image_new_from_icon_name (stock_name, icon_size);
 #else
-              w = gtk_image_new_from_stock (stock_name, icon_size);
+	      w = gtk_image_new_from_stock (stock_name, icon_size);
 #endif
-              g_object_set_data_full (G_OBJECT (w), XG_TOOL_BAR_STOCK_NAME,
-                                      (gpointer) xstrdup (stock_name),
-                                      (GDestroyNotify) xfree);
-            }
-          else if (icon_name)
-            {
-              w = gtk_image_new_from_icon_name (icon_name, icon_size);
-              g_object_set_data_full (G_OBJECT (w), XG_TOOL_BAR_ICON_NAME,
-                                      (gpointer) xstrdup (icon_name),
-                                      (GDestroyNotify) xfree);
-            }
-          else
-            {
-              w = xg_get_image_for_pixmap (f, img, x->widget, NULL);
-              g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA,
+	      g_object_set_data_full (G_OBJECT (w), XG_TOOL_BAR_STOCK_NAME,
+				      (gpointer) xstrdup (stock_name),
+				      (GDestroyNotify) xfree);
+	    }
+	  else if (icon_name)
+	    {
+	      w = gtk_image_new_from_icon_name (icon_name, icon_size);
+	      g_object_set_data_full (G_OBJECT (w), XG_TOOL_BAR_ICON_NAME,
+				      (gpointer) xstrdup (icon_name),
+				      (GDestroyNotify) xfree);
+	    }
+	  else
+	    {
+	      w = xg_get_image_for_pixmap (f, img, x->widget, NULL);
+	      g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA,
 #ifdef USE_CAIRO
-                                 (gpointer)img->cr_data
+				 (gpointer)img->cr_data
 #else
-                                 (gpointer)img->pixmap
+				 (gpointer)img->pixmap
 #endif
 				 );
-            }
+	    }
 
 #if GTK_CHECK_VERSION (3, 14, 0)
-          if (w)
-            {
-              gtk_widget_set_margin_start (w, hmargin);
-              gtk_widget_set_margin_end (w, hmargin);
-              gtk_widget_set_margin_top (w, vmargin);
-              gtk_widget_set_margin_bottom (w, vmargin);
-            }
+	  if (w)
+	    {
+	      gtk_widget_set_margin_start (w, hmargin);
+	      gtk_widget_set_margin_end (w, hmargin);
+	      gtk_widget_set_margin_top (w, vmargin);
+	      gtk_widget_set_margin_bottom (w, vmargin);
+	    }
 #else
 	  if (w) gtk_misc_set_padding (GTK_MISC (w), hmargin, vmargin);
 #endif
-          ti = xg_make_tool_item (f, w, &wbutton, label, i, horiz, text_image);
-          gtk_toolbar_insert (GTK_TOOLBAR (wtoolbar), ti, j);
-        }
+	  ti = xg_make_tool_item (f, w, &wbutton, label, i, horiz, text_image);
+	  gtk_toolbar_insert (GTK_TOOLBAR (wtoolbar), ti, j);
+	}
 
 #undef PROP
 
@@ -5201,7 +5552,7 @@ update_frame_tool_bar (struct frame *f)
   if (f->n_tool_bar_items != 0)
     {
       if (! x->toolbar_is_packed)
-        xg_pack_tool_bar (f, FRAME_TOOL_BAR_POSITION (f));
+	xg_pack_tool_bar (f, FRAME_TOOL_BAR_POSITION (f));
       gtk_widget_show_all (x->toolbar_widget);
       if (xg_update_tool_bar_sizes (f))
 	{
@@ -5224,16 +5575,19 @@ update_frame_tool_bar (struct frame *f)
       f->tool_bar_resized = f->tool_bar_redisplayed;
     }
 
+#ifdef HAVE_GTK3
+  update_frame_header_bar (f);
+#endif
   unblock_input ();
 }
 
-/* Deallocate all resources for the tool bar on frame F.
+/* deallocate all resources for the tool bar on frame F.
    Remove the tool bar.  */
 
 void
 free_frame_tool_bar (struct frame *f)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
 
   if (x->toolbar_widget)
     {
@@ -5242,16 +5596,16 @@ free_frame_tool_bar (struct frame *f)
 
       block_input ();
       if (x->toolbar_is_packed)
-        {
-          if (x->toolbar_in_hbox)
-            gtk_container_remove (GTK_CONTAINER (x->hbox_widget),
-                                  top_widget);
-          else
-            gtk_container_remove (GTK_CONTAINER (x->vbox_widget),
-                                  top_widget);
-        }
+	{
+	  if (x->toolbar_in_hbox)
+	    gtk_container_remove (GTK_CONTAINER (x->hbox_widget),
+				  top_widget);
+	  else
+	    gtk_container_remove (GTK_CONTAINER (x->vbox_widget),
+				  top_widget);
+	}
       else
-        gtk_widget_destroy (x->toolbar_widget);
+	gtk_widget_destroy (x->toolbar_widget);
 
       x->toolbar_widget = 0;
       x->toolbar_widget = 0;
@@ -5260,14 +5614,14 @@ free_frame_tool_bar (struct frame *f)
       FRAME_TOOLBAR_LEFT_WIDTH (f) = FRAME_TOOLBAR_RIGHT_WIDTH (f) = 0;
 
       tbinfo = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                                  TB_INFO_KEY);
+				  TB_INFO_KEY);
       if (tbinfo)
-        {
-          xfree (tbinfo);
-          g_object_set_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
-                             TB_INFO_KEY,
-                             NULL);
-        }
+	{
+	  xfree (tbinfo);
+	  g_object_set_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+			     TB_INFO_KEY,
+			     NULL);
+	}
 
       frame_size_history_add (f, Qfree_frame_tool_bar, 0, 0, Qnil);
       adjust_frame_size (f, -1, -1, 2, 0, Qtool_bar_lines);
@@ -5279,7 +5633,7 @@ free_frame_tool_bar (struct frame *f)
 void
 xg_change_toolbar_position (struct frame *f, Lisp_Object pos)
 {
-  struct x_output *x = f->output_data.x;
+  xp_output *x = f->output_data.xp;
   GtkWidget *top_widget = x->toolbar_widget;
 
   if (! x->toolbar_widget || ! top_widget)
@@ -5290,11 +5644,11 @@ xg_change_toolbar_position (struct frame *f, Lisp_Object pos)
   if (x->toolbar_is_packed)
     {
       if (x->toolbar_in_hbox)
-        gtk_container_remove (GTK_CONTAINER (x->hbox_widget),
-                              top_widget);
+	gtk_container_remove (GTK_CONTAINER (x->hbox_widget),
+			      top_widget);
       else
-        gtk_container_remove (GTK_CONTAINER (x->vbox_widget),
-                              top_widget);
+	gtk_container_remove (GTK_CONTAINER (x->vbox_widget),
+			      top_widget);
     }
 
   xg_pack_tool_bar (f, pos);
@@ -5313,7 +5667,7 @@ xg_change_toolbar_position (struct frame *f, Lisp_Object pos)
 
 \f
 /***********************************************************************
-                      Initializing
+		      Initializing
 ***********************************************************************/
 void
 xg_initialize (void)
@@ -5336,15 +5690,15 @@ xg_initialize (void)
   id_to_widget.widgets = 0;
 
   settings = gtk_settings_get_for_screen (gdk_display_get_default_screen
-                                          (gdk_display_get_default ()));
+					  (gdk_display_get_default ()));
 #ifndef HAVE_GTK3
   /* Remove F10 as a menu accelerator, it does not mix well with Emacs key
      bindings.  It doesn't seem to be any way to remove properties,
      so we set it to "" which in means "no key".  */
   gtk_settings_set_string_property (settings,
-                                    "gtk-menu-bar-accel",
-                                    "",
-                                    EMACS_CLASS);
+				    "gtk-menu-bar-accel",
+				    "",
+				    EMACS_CLASS);
 #endif
 
   /* Make GTK text input widgets use Emacs style keybindings.  This is
@@ -5353,22 +5707,22 @@ xg_initialize (void)
   g_object_set (settings, "gtk-key-theme-name", "Emacs", NULL);
 #else
   gtk_settings_set_string_property (settings,
-                                    "gtk-key-theme-name",
-                                    "Emacs",
-                                    EMACS_CLASS);
+				    "gtk-key-theme-name",
+				    "Emacs",
+				    EMACS_CLASS);
 #endif
 
   /* Make dialogs close on C-g.  Since file dialog inherits from
      dialog, this works for them also.  */
   binding_set = gtk_binding_set_by_class (g_type_class_ref (GTK_TYPE_DIALOG));
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_g, GDK_CONTROL_MASK,
-                                "close", 0);
+				"close", 0);
 
   /* Make menus close on C-g.  */
   binding_set = gtk_binding_set_by_class (g_type_class_ref
-                                          (GTK_TYPE_MENU_SHELL));
+					  (GTK_TYPE_MENU_SHELL));
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_g, GDK_CONTROL_MASK,
-                                "cancel", 0);
+				"cancel", 0);
   update_theme_scrollbar_width ();
   update_theme_scrollbar_height ();
 
diff --git a/src/gtkutil.h b/src/gtkutil.h
index 5419167cd7..a3592b8d51 100644
--- a/src/gtkutil.h
+++ b/src/gtkutil.h
@@ -25,7 +25,15 @@ #define GTKUTIL_H
 
 #include <gtk/gtk.h>
 #include "../lwlib/lwlib-widget.h"
+#ifdef HAVE_PGTK
+#include "pgtkterm.h"
+#define EVENT GdkEvent
+#else
 #include "xterm.h"
+#define EVENT XEvent
+#endif
+
+#include "gtkconfig.h"
 
 /* Minimum and maximum values used for GTK scroll bars  */
 
@@ -105,9 +113,13 @@ #define XG_ITEM_DATA "emacs_menuitem"
 
 extern void xg_update_frame_menubar (struct frame *f);
 
-extern bool xg_event_is_for_menubar (struct frame *, const XEvent *);
+extern bool xg_event_is_for_menubar (struct frame *, const EVENT *);
 
+#ifdef HAVE_PGTK
+extern ptrdiff_t xg_get_scroll_id_for_window (GDisplay *dpy, GWindow wid);
+#else
 extern ptrdiff_t xg_get_scroll_id_for_window (Display *dpy, Window wid);
+#endif
 
 extern void xg_create_scroll_bar (struct frame *f,
                                   struct scroll_bar *bar,
@@ -142,7 +154,7 @@ #define XG_ITEM_DATA "emacs_menuitem"
 							int portion,
 							int position,
 							int whole);
-extern bool xg_event_is_for_scrollbar (struct frame *, const XEvent *);
+extern bool xg_event_is_for_scrollbar (struct frame *, const EVENT *);
 extern int xg_get_default_scrollbar_width (struct frame *f);
 extern int xg_get_default_scrollbar_height (struct frame *f);
 
@@ -154,12 +166,23 @@ #define XG_ITEM_DATA "emacs_menuitem"
                               int pixelwidth,
                               int pixelheight);
 extern void xg_frame_set_char_size (struct frame *f, int width, int height);
+
+#ifdef HAVE_PGTK
+extern GtkWidget * xg_win_to_widget (GDisplay *dpy, GWindow wdesc);
+#else
 extern GtkWidget * xg_win_to_widget (Display *dpy, Window wdesc);
+#endif
 
 extern int xg_get_scale (struct frame *f);
+#ifndef HAVE_PGTK
 extern void xg_display_open (char *display_name, Display **dpy);
 extern void xg_display_close (Display *dpy);
 extern GdkCursor * xg_create_default_cursor (Display *dpy);
+#else
+extern void xg_display_open (char *display_name, GdkDisplay **dpy);
+extern void xg_display_close (GdkDisplay *gdpy);
+extern GdkCursor * xg_create_default_cursor (GdkDisplay *gdpy);
+#endif
 
 extern bool xg_create_frame_widgets (struct frame *f);
 extern void xg_free_frame_widgets (struct frame *f);
@@ -168,10 +191,18 @@ #define XG_ITEM_DATA "emacs_menuitem"
 				     const char *color_name,
 				     Emacs_Color *color);
 
+#ifdef HAVE_PGTK
+
+extern void xg_set_frame_icon (struct frame *f,
+                               Emacs_Pixmap icon_pixmap,
+                               Emacs_Pixmap icon_mask);
+#else
+
 extern void xg_set_frame_icon (struct frame *f,
                                Pixmap icon_pixmap,
                                Pixmap icon_mask);
 
+#endif
 extern void xg_set_undecorated (struct frame *f, Lisp_Object undecorated);
 extern void xg_frame_restack (struct frame *f1, struct frame *f2, bool above);
 extern void xg_set_skip_taskbar (struct frame *f, Lisp_Object skip_taskbar);
@@ -204,5 +235,44 @@ #define XG_ITEM_DATA "emacs_menuitem"
 
 extern bool xg_gtk_initialized;
 
+extern GtkWidget *
+xg_get_image_for_pixmap (struct frame *f,
+			 struct image *img,
+			 GtkWidget *widget,
+			 GtkImage *old_widget);
+
+void
+xg_list_insert (xg_list_node *list, xg_list_node *node);
+
+xg_menu_cb_data *
+make_cl_data (xg_menu_cb_data *cl_data, struct frame *f, GCallback highlight_cb);
+
+void
+update_cl_data (xg_menu_cb_data *cl_data,
+		struct frame *f,
+		GCallback highlight_cb);
+
+void
+xg_update_menubar (GtkWidget *menubar,
+		   struct frame *f,
+		   GList **list,
+		   GList *iter,
+		   int pos,
+		   widget_value *val,
+		   GCallback select_cb,
+		   GCallback deactivate_cb,
+		   GCallback highlight_cb,
+		   xg_menu_cb_data *cl_data);
+bool
+xg_item_label_same_p (GtkMenuItem *witem, const char *label);
+
+GtkWidget *
+xg_update_submenu (GtkWidget *submenu,
+		   struct frame *f,
+		   widget_value *val,
+		   GCallback select_cb,
+		   GCallback deactivate_cb,
+		   GCallback highlight_cb,
+		   xg_menu_cb_data *cl_data);
 #endif /* USE_GTK */
 #endif /* GTKUTIL_H */
diff --git a/src/image.c b/src/image.c
index c8a192aaaf..56ee8b3fe2 100644
--- a/src/image.c
+++ b/src/image.c
@@ -34,6 +34,10 @@ Copyright (C) 1989, 1992-2020 Free Software Foundation, Inc.
 #include <c-ctype.h>
 #include <flexmember.h>
 
+#ifdef HAVE_PGTK
+#include <cairo.h>
+#endif
+
 #include "lisp.h"
 #include "frame.h"
 #include "process.h"
@@ -66,6 +70,10 @@ Copyright (C) 1989, 1992-2020 Free Software Foundation, Inc.
 # pragma GCC diagnostic ignored "-Wclobbered"
 #endif
 
+#ifdef HAVE_GTK4
+#include "gtkimage.h"
+#endif
+
 #ifdef HAVE_X_WINDOWS
 typedef struct x_bitmap_record Bitmap_Record;
 #ifndef USE_CAIRO
@@ -129,6 +137,10 @@ #define PIX_MASK_DRAW	1
 
 #endif /* HAVE_NS */
 
+#ifdef HAVE_PGTK
+typedef struct pgtk_bitmap_record Bitmap_Record;
+#endif /* HAVE_PGTK */
+
 #if (defined HAVE_X_WINDOWS \
      && ! (defined HAVE_NTGUI || defined USE_CAIRO || defined HAVE_NS))
 /* W32_TODO : Color tables on W32.  */
@@ -137,7 +149,7 @@ #define PIX_MASK_DRAW	1
 
 static void image_disable_image (struct frame *, struct image *);
 static void image_edge_detection (struct frame *, struct image *, Lisp_Object,
-                                  Lisp_Object);
+				  Lisp_Object);
 
 static void init_color_table (void);
 static unsigned long lookup_rgb_color (struct frame *f, int r, int g, int b);
@@ -394,11 +406,37 @@ image_reference_bitmap (struct frame *f, ptrdiff_t id)
   ++FRAME_DISPLAY_INFO (f)->bitmaps[id - 1].refcount;
 }
 
+#ifdef HAVE_PGTK
+static cairo_pattern_t *
+image_create_pattern_from_pixbuf (struct frame *f, GdkPixbuf *pixbuf)
+{
+  GdkPixbuf *pb = gdk_pixbuf_add_alpha (pixbuf, TRUE, 255, 255, 255);
+  cairo_surface_t *surface = cairo_surface_create_similar_image (f->output_data.pgtk->cr_surface,
+								 CAIRO_FORMAT_A1,
+								 gdk_pixbuf_get_width (pb),
+								 gdk_pixbuf_get_height (pb));
+
+  cairo_t *cr = cairo_create (surface);
+  gdk_cairo_set_source_pixbuf (cr, pb, 0, 0);
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_paint (cr);
+  cairo_destroy (cr);
+
+  cairo_pattern_t *pat = cairo_pattern_create_for_surface (surface);
+  cairo_pattern_set_extend (pat, CAIRO_EXTEND_REPEAT);
+
+  cairo_surface_destroy (surface);
+  g_object_unref (pb);
+
+  return pat;
+}
+#endif
+
 /* Create a bitmap for frame F from a HEIGHT x WIDTH array of bits at BITS.  */
 
 ptrdiff_t
 image_create_bitmap_from_data (struct frame *f, char *bits,
-                               unsigned int width, unsigned int height)
+			       unsigned int width, unsigned int height)
 {
   Display_Info *dpyinfo = FRAME_DISPLAY_INFO (f);
   ptrdiff_t id;
@@ -428,6 +466,42 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
       return -1;
 #endif
 
+#ifdef HAVE_PGTK
+  GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
+				     FALSE,
+				     8,
+				     width,
+				     height);
+  {
+    char *sp = bits;
+    int mask = 0x01;
+    unsigned char *buf = gdk_pixbuf_get_pixels (pixbuf);
+    int rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+    for (int y = 0; y < height; y++) {
+      unsigned char *dp = buf + rowstride * y;
+      for (int x = 0; x < width; x++) {
+	if (*sp & mask) {
+	  *dp++ = 0xff;
+	  *dp++ = 0xff;
+	  *dp++ = 0xff;
+	} else {
+	  *dp++ = 0x00;
+	  *dp++ = 0x00;
+	  *dp++ = 0x00;
+	}
+	if ((mask <<= 1) >= 0x100) {
+	  mask = 0x01;
+	  sp++;
+	}
+      }
+      if (mask != 0x01) {
+	mask = 0x01;
+	sp++;
+      }
+    }
+  }
+#endif
+
   id = image_allocate_bitmap_record (f);
 
 #ifdef HAVE_NS
@@ -435,6 +509,12 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
   dpyinfo->bitmaps[id - 1].depth = 1;
 #endif
 
+#ifdef HAVE_PGTK
+  dpyinfo->bitmaps[id - 1].img = pixbuf;
+  dpyinfo->bitmaps[id - 1].depth = 1;
+  dpyinfo->bitmaps[id - 1].pattern = image_create_pattern_from_pixbuf (f, pixbuf);
+#endif
+
   dpyinfo->bitmaps[id - 1].file = NULL;
   dpyinfo->bitmaps[id - 1].height = height;
   dpyinfo->bitmaps[id - 1].width = width;
@@ -487,6 +567,28 @@ image_create_bitmap_from_file (struct frame *f, Lisp_Object file)
   return id;
 #endif
 
+#ifdef HAVE_PGTK
+  GError *err = NULL;
+  ptrdiff_t id;
+  void * bitmap = gdk_pixbuf_new_from_file(SSDATA(file), &err);
+
+  if (!bitmap) {
+    g_error_free(err);
+    return -1;
+  }
+
+  id = image_allocate_bitmap_record(f);
+
+  dpyinfo->bitmaps[id - 1].img = bitmap;
+  dpyinfo->bitmaps[id - 1].refcount = 1;
+  dpyinfo->bitmaps[id - 1].file = xlispstrdup (file);
+  //dpyinfo->bitmaps[id - 1].depth = 1;
+  dpyinfo->bitmaps[id - 1].height = gdk_pixbuf_get_width (bitmap);
+  dpyinfo->bitmaps[id - 1].width = gdk_pixbuf_get_height (bitmap);
+  dpyinfo->bitmaps[id - 1].pattern = image_create_pattern_from_pixbuf (f, bitmap);
+  return id;
+#endif
+
 #ifdef HAVE_X_WINDOWS
   unsigned int width, height;
   Pixmap bitmap;
@@ -559,6 +661,11 @@ free_bitmap_record (Display_Info *dpyinfo, Bitmap_Record *bm)
   ns_release_object (bm->img);
 #endif
 
+#ifdef HAVE_PGTK
+  if (bm->pattern != NULL)
+    cairo_pattern_destroy (bm->pattern);
+#endif
+
   if (bm->file)
     {
       xfree (bm->file);
@@ -607,18 +714,18 @@ image_destroy_all_bitmaps (Display_Info *dpyinfo)
 #endif
 
 static bool image_create_x_image_and_pixmap_1 (struct frame *, int, int, int,
-                                               Emacs_Pix_Container *,
-                                               Emacs_Pixmap *, Picture *);
+					       Emacs_Pix_Container *,
+					       Emacs_Pixmap *, Picture *);
 static void image_destroy_x_image (Emacs_Pix_Container);
 
 #ifdef HAVE_NTGUI
 static HDC image_get_x_image_or_dc (struct frame *, struct image *,
-                                    bool, HGDIOBJ *);
+				    bool, HGDIOBJ *);
 static void image_unget_x_image_or_dc (struct image *, bool,
-                                       HDC, HGDIOBJ);
+				       HDC, HGDIOBJ);
 #else
 static Emacs_Pix_Container image_get_x_image (struct frame *, struct image *,
-                                              bool);
+					      bool);
 static void image_unget_x_image (struct image *, bool, Emacs_Pix_Container);
 #define image_get_x_image_or_dc(f, img, mask_p, dummy)	\
   image_get_x_image (f, img, mask_p)
@@ -778,7 +885,7 @@ x_create_bitmap_mask (struct frame *f, ptrdiff_t id)
 static void image_laplace (struct frame *, struct image *);
 static void image_emboss (struct frame *, struct image *);
 static void image_build_heuristic_mask (struct frame *, struct image *,
-                                    Lisp_Object);
+				    Lisp_Object);
 
 static void
 add_image_type (Lisp_Object type)
@@ -1177,18 +1284,19 @@ free_image (struct frame *f, struct image *img)
 
 #if !defined USE_CAIRO && defined HAVE_XRENDER
       if (img->picture)
-        XRenderFreePicture (FRAME_X_DISPLAY (f), img->picture);
+	XRenderFreePicture (FRAME_X_DISPLAY (f), img->picture);
       if (img->mask_picture)
-        XRenderFreePicture (FRAME_X_DISPLAY (f), img->mask_picture);
+	XRenderFreePicture (FRAME_X_DISPLAY (f), img->mask_picture);
 #endif
 
       /* Windows NT redefines 'free', but in this file, we need to
-         avoid the redefinition.  */
+	 avoid the redefinition.  */
 #ifdef WINDOWSNT
 #undef free
 #endif
       /* Free resources, then free IMG.  */
-      img->type->free (f, img);
+      if (img->type)
+	img->type->free (f, img);
       xfree (img);
     }
 }
@@ -1298,7 +1406,7 @@ image_ascent (struct image *img, struct face *face, struct glyph_slice *slice)
 	     uppercase letters), so the image placement should err towards
 	     being top-heavy too.  It also just generally looks better.  */
 	  ascent = (height + FONT_BASE (face->font)
-                    - FONT_DESCENT (face->font) + 1) / 2;
+		    - FONT_DESCENT (face->font) + 1) / 2;
 #endif /* HAVE_NTGUI */
 	}
       else
@@ -1406,7 +1514,7 @@ image_background (struct image *img, struct frame *f, Emacs_Pix_Context pimg)
 
 int
 image_background_transparent (struct image *img, struct frame *f,
-                              Emacs_Pix_Context mask)
+			      Emacs_Pix_Context mask)
 {
   if (! img->background_transparent_valid)
     /* IMG doesn't have a background yet, try to guess a reasonable value.  */
@@ -1530,7 +1638,7 @@ image_clear_image (struct frame *f, struct image *img)
 
 static unsigned long
 image_alloc_image_color (struct frame *f, struct image *img,
-                         Lisp_Object color_name, unsigned long dflt)
+			 Lisp_Object color_name, unsigned long dflt)
 {
   Emacs_Color color;
   unsigned long result;
@@ -1538,10 +1646,10 @@ image_alloc_image_color (struct frame *f, struct image *img,
   eassert (STRINGP (color_name));
 
   if (FRAME_TERMINAL (f)->defined_color_hook (f,
-                                              SSDATA (color_name),
-                                              &color,
-                                              true,
-                                              false)
+					      SSDATA (color_name),
+					      &color,
+					      true,
+					      false)
       && img->ncolors < min (min (PTRDIFF_MAX, SIZE_MAX) / sizeof *img->colors,
 			     INT_MAX))
     {
@@ -1870,8 +1978,8 @@ postprocess_image (struct frame *f, struct image *img)
 	  tem = XCDR (conversion);
 	  if (CONSP (tem))
 	    image_edge_detection (f, img,
-                                  Fplist_get (tem, QCmatrix),
-                                  Fplist_get (tem, QCcolor_adjustment));
+				  Fplist_get (tem, QCmatrix),
+				  Fplist_get (tem, QCcolor_adjustment));
 	}
     }
 }
@@ -2237,14 +2345,14 @@ image_set_transform (struct frame *f, struct image *img)
     {
       XTransform tmat
 	= {{{XDoubleToFixed (matrix[0][0]),
-             XDoubleToFixed (matrix[1][0]),
-             XDoubleToFixed (matrix[2][0])},
+	     XDoubleToFixed (matrix[1][0]),
+	     XDoubleToFixed (matrix[2][0])},
 	    {XDoubleToFixed (matrix[0][1]),
-             XDoubleToFixed (matrix[1][1]),
-             XDoubleToFixed (matrix[2][1])},
+	     XDoubleToFixed (matrix[1][1]),
+	     XDoubleToFixed (matrix[2][1])},
 	    {XDoubleToFixed (matrix[0][2]),
-             XDoubleToFixed (matrix[1][2]),
-             XDoubleToFixed (matrix[2][2])}}};
+	     XDoubleToFixed (matrix[1][2]),
+	     XDoubleToFixed (matrix[2][2])}}};
 
       XRenderSetPictureFilter (FRAME_X_DISPLAY (f), img->picture, FilterBest,
 			       0, 0);
@@ -2300,7 +2408,10 @@ lookup_image (struct frame *f, Lisp_Object spec)
       block_input ();
       img = make_image (spec, hash);
       cache_image (f, img);
-      img->load_failed_p = ! img->type->load (f, img);
+      if (!img->type)
+	img->load_failed_p = true;
+      else
+	img->load_failed_p = ! img->type->load (f, img);
       img->frame_foreground = FRAME_FOREGROUND_PIXEL (f);
       img->frame_background = FRAME_BACKGROUND_PIXEL (f);
 
@@ -2357,7 +2468,7 @@ lookup_image (struct frame *f, Lisp_Object spec)
 		{
 		  img->background
 		    = image_alloc_image_color (f, img, bg,
-                                               FRAME_BACKGROUND_PIXEL (f));
+					       FRAME_BACKGROUND_PIXEL (f));
 		  img->background_valid = 1;
 		}
 	    }
@@ -2629,8 +2740,8 @@ image_check_image_size (Emacs_Pix_Container ximg, int width, int height)
 
 static bool
 image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int depth,
-                                   Emacs_Pix_Container *pimg,
-                                   Emacs_Pixmap *pixmap, Picture *picture)
+				   Emacs_Pix_Container *pimg,
+				   Emacs_Pixmap *pixmap, Picture *picture)
 {
 #ifdef USE_CAIRO
   eassert (input_blocked_p ());
@@ -2795,7 +2906,7 @@ image_destroy_x_image (Emacs_Pix_Container pimg)
 
 static void
 gui_put_x_image (struct frame *f, Emacs_Pix_Container pimg,
-                 Emacs_Pixmap pixmap, int width, int height)
+		 Emacs_Pixmap pixmap, int width, int height)
 {
 #ifdef USE_CAIRO
   eassert (pimg == pixmap);
@@ -2805,7 +2916,7 @@ gui_put_x_image (struct frame *f, Emacs_Pix_Container pimg,
   eassert (input_blocked_p ());
   gc = XCreateGC (FRAME_X_DISPLAY (f), pixmap, 0, NULL);
   XPutImage (FRAME_X_DISPLAY (f), pixmap, gc, pimg, 0, 0, 0, 0,
-             pimg->width, pimg->height);
+	     pimg->width, pimg->height);
   XFreeGC (FRAME_X_DISPLAY (f), gc);
 #endif /* HAVE_X_WINDOWS */
 
@@ -2838,8 +2949,8 @@ image_create_x_image_and_pixmap (struct frame *f, struct image *img,
   picture = !mask_p ? &img->picture : &img->mask_picture;
 #endif
   return image_create_x_image_and_pixmap_1 (f, width, height, depth, ximg,
-                                            !mask_p ? &img->pixmap : &img->mask,
-                                            picture);
+					    !mask_p ? &img->pixmap : &img->mask,
+					    picture);
 }
 
 /* Put pixel image PIMG into image IMG on frame F, as a mask if and only
@@ -2865,7 +2976,7 @@ image_put_x_image (struct frame *f, struct image *img, Emacs_Pix_Container ximg,
     }
 #else
   gui_put_x_image (f, ximg, !mask_p ? img->pixmap : img->mask,
-                   img->width, img->height);
+		   img->width, img->height);
   image_destroy_x_image (ximg);
 #endif
 }
@@ -3478,7 +3589,7 @@ #define match() \
 #define expect(TOKEN)		\
   do				\
     {				\
-      if (LA1 != (TOKEN)) 	\
+      if (LA1 != (TOKEN))	\
 	goto failure;		\
       match ();			\
     }				\
@@ -3761,9 +3872,9 @@ xbm_load (struct frame *f, struct image *img)
 	  && STRINGP (fmt[XBM_FOREGROUND].value))
 	{
 	  foreground = image_alloc_image_color (f,
-                                                img,
-                                                fmt[XBM_FOREGROUND].value,
-                                                foreground);
+						img,
+						fmt[XBM_FOREGROUND].value,
+						foreground);
 	  non_default_colors = 1;
 	}
 
@@ -3771,9 +3882,9 @@ xbm_load (struct frame *f, struct image *img)
 	  && STRINGP (fmt[XBM_BACKGROUND].value))
 	{
 	  background = image_alloc_image_color (f,
-                                                img,
-                                                fmt[XBM_BACKGROUND].value,
-                                                background);
+						img,
+						fmt[XBM_BACKGROUND].value,
+						background);
 	  non_default_colors = 1;
 	}
 
@@ -3807,16 +3918,16 @@ xbm_load (struct frame *f, struct image *img)
 	    bits = (char *) bool_vector_data (data);
 
 #ifdef HAVE_NTGUI
-          {
-            char *invertedBits;
-            int nbytes, i;
-            /* Windows mono bitmaps are reversed compared with X.  */
-            invertedBits = bits;
-            nbytes = (img->width + CHAR_BIT - 1) / CHAR_BIT * img->height;
-            SAFE_NALLOCA (bits, 1, nbytes);
-            for (i = 0; i < nbytes; i++)
-              bits[i] = XBM_BIT_SHUFFLE (invertedBits[i]);
-          }
+	  {
+	    char *invertedBits;
+	    int nbytes, i;
+	    /* Windows mono bitmaps are reversed compared with X.  */
+	    invertedBits = bits;
+	    nbytes = (img->width + CHAR_BIT - 1) / CHAR_BIT * img->height;
+	    SAFE_NALLOCA (bits, 1, nbytes);
+	    for (i = 0; i < nbytes; i++)
+	      bits[i] = XBM_BIT_SHUFFLE (invertedBits[i]);
+	  }
 #endif
 	  /* Create the pixmap.  */
 
@@ -3849,6 +3960,13 @@ xbm_load (struct frame *f, struct image *img)
 			      XPM images
  ***********************************************************************/
 
+#if defined (HAVE_XPM) || defined (HAVE_NS) || defined (HAVE_PGTK)
+
+static bool xpm_image_p (Lisp_Object object);
+static bool xpm_load (struct frame *f, struct image *img);
+
+#endif /* HAVE_XPM || HAVE_NS */
+
 #ifdef HAVE_XPM
 #ifdef HAVE_NTGUI
 /* Indicate to xpm.h that we don't have Xlib.  */
@@ -3925,7 +4043,7 @@ #define ALLOC_XPM_COLORS
 #ifdef ALLOC_XPM_COLORS
 
 static struct xpm_cached_color *xpm_cache_color (struct frame *, char *,
-                                                 XColor *, int);
+						 XColor *, int);
 
 /* An entry in a hash table used to cache color definitions of named
    colors.  This cache is necessary to speed up XPM image loading in
@@ -4543,10 +4661,10 @@ xpm_load (struct frame *f, struct image *img)
    Only XPM version 3 (without any extensions) is supported.  */
 
 static void xpm_put_color_table_v (Lisp_Object, const char *,
-                                   int, Lisp_Object);
+				   int, Lisp_Object);
 static Lisp_Object xpm_get_color_table_v (Lisp_Object, const char *, int);
 static void xpm_put_color_table_h (Lisp_Object, const char *,
-                                   int, Lisp_Object);
+				   int, Lisp_Object);
 static Lisp_Object xpm_get_color_table_h (Lisp_Object, const char *, int);
 
 /* Tokens returned from xpm_scan.  */
@@ -4633,9 +4751,9 @@ xpm_scan (const char **s, const char *end, const char **beg, ptrdiff_t *len)
 
 static Lisp_Object
 xpm_make_color_table_v (void (**put_func) (Lisp_Object, const char *, int,
-                                           Lisp_Object),
-                        Lisp_Object (**get_func) (Lisp_Object, const char *,
-                                                  int))
+					   Lisp_Object),
+			Lisp_Object (**get_func) (Lisp_Object, const char *,
+						  int))
 {
   *put_func = xpm_put_color_table_v;
   *get_func = xpm_get_color_table_v;
@@ -4644,9 +4762,9 @@ xpm_make_color_table_v (void (**put_func) (Lisp_Object, const char *, int,
 
 static void
 xpm_put_color_table_v (Lisp_Object color_table,
-                       const char *chars_start,
-                       int chars_len,
-                       Lisp_Object color)
+		       const char *chars_start,
+		       int chars_len,
+		       Lisp_Object color)
 {
   unsigned char uc = *chars_start;
   ASET (color_table, uc, color);
@@ -4654,8 +4772,8 @@ xpm_put_color_table_v (Lisp_Object color_table,
 
 static Lisp_Object
 xpm_get_color_table_v (Lisp_Object color_table,
-                       const char *chars_start,
-                       int chars_len)
+		       const char *chars_start,
+		       int chars_len)
 {
   unsigned char uc = *chars_start;
   return AREF (color_table, uc);
@@ -4663,9 +4781,9 @@ xpm_get_color_table_v (Lisp_Object color_table,
 
 static Lisp_Object
 xpm_make_color_table_h (void (**put_func) (Lisp_Object, const char *, int,
-                                           Lisp_Object),
-                        Lisp_Object (**get_func) (Lisp_Object, const char *,
-                                                  int))
+					   Lisp_Object),
+			Lisp_Object (**get_func) (Lisp_Object, const char *,
+						  int))
 {
   *put_func = xpm_put_color_table_h;
   *get_func = xpm_get_color_table_h;
@@ -4676,9 +4794,9 @@ xpm_make_color_table_h (void (**put_func) (Lisp_Object, const char *, int,
 
 static void
 xpm_put_color_table_h (Lisp_Object color_table,
-                       const char *chars_start,
-                       int chars_len,
-                       Lisp_Object color)
+		       const char *chars_start,
+		       int chars_len,
+		       Lisp_Object color)
 {
   struct Lisp_Hash_Table *table = XHASH_TABLE (color_table);
   Lisp_Object chars = make_unibyte_string (chars_start, chars_len), hash_code;
@@ -4689,8 +4807,8 @@ xpm_put_color_table_h (Lisp_Object color_table,
 
 static Lisp_Object
 xpm_get_color_table_h (Lisp_Object color_table,
-                       const char *chars_start,
-                       int chars_len)
+		       const char *chars_start,
+		       int chars_len)
 {
   struct Lisp_Hash_Table *table = XHASH_TABLE (color_table);
   ptrdiff_t i =
@@ -4722,9 +4840,9 @@ xpm_str_to_color_key (const char *s)
 
 static bool
 xpm_load_image (struct frame *f,
-                struct image *img,
-                const char *contents,
-                const char *end)
+		struct image *img,
+		const char *contents,
+		const char *end)
 {
   const char *s = contents, *beg, *str;
   char buffer[BUFSIZ];
@@ -4736,7 +4854,7 @@ xpm_load_image (struct frame *f,
   Lisp_Object (*get_color_table) (Lisp_Object, const char *, int);
   Lisp_Object frame, color_symbols, color_table;
   int best_key;
-#ifndef HAVE_NS
+#if !defined(HAVE_NS)
   bool have_mask = false;
 #endif
   Emacs_Pix_Container ximg = NULL, mask_img = NULL;
@@ -4747,7 +4865,7 @@ #define match() \
 #define expect(TOKEN)		\
   do				\
     {				\
-      if (LA1 != (TOKEN)) 	\
+      if (LA1 != (TOKEN))	\
 	goto failure;		\
       match ();			\
     }				\
@@ -4755,7 +4873,7 @@ #define expect(TOKEN)		\
 
 #define expect_ident(IDENT)					\
      if (LA1 == XPM_TK_IDENT \
-         && strlen ((IDENT)) == len && memcmp ((IDENT), beg, len) == 0)	\
+	 && strlen ((IDENT)) == len && memcmp ((IDENT), beg, len) == 0)	\
        match ();						\
      else							\
        goto failure
@@ -4790,7 +4908,7 @@ #define expect_ident(IDENT)					\
     }
 
   if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, 0)
-#ifndef HAVE_NS
+#if !defined(HAVE_NS)
       || !image_create_x_image_and_pixmap (f, img, width, height, 1,
 					   &mask_img, 1)
 #endif
@@ -4876,7 +4994,7 @@ #define expect_ident(IDENT)					\
 	      if (xstrcasecmp (SSDATA (XCDR (specified_color)), "None") == 0)
 		color_val = Qt;
 	      else if (FRAME_TERMINAL (f)->defined_color_hook
-                       (f, SSDATA (XCDR (specified_color)), &cdef, false, false))
+		       (f, SSDATA (XCDR (specified_color)), &cdef, false, false))
 		color_val
 		  = make_fixnum (lookup_rgb_color (f, cdef.red, cdef.green,
 						   cdef.blue));
@@ -4887,7 +5005,7 @@ #define expect_ident(IDENT)					\
 	  if (xstrcasecmp (max_color, "None") == 0)
 	    color_val = Qt;
 	  else if (FRAME_TERMINAL (f)->defined_color_hook
-                   (f, max_color, &cdef, false, false))
+		   (f, max_color, &cdef, false, false))
 	    color_val = make_fixnum (lookup_rgb_color (f, cdef.red, cdef.green,
 						       cdef.blue));
 	}
@@ -4918,13 +5036,13 @@ #define expect_ident(IDENT)					\
 
 	  PUT_PIXEL (ximg, x, y,
 		     FIXNUMP (color_val) ? XFIXNUM (color_val) : frame_fg);
-#ifndef HAVE_NS
+#if !defined(HAVE_NS)
 	  PUT_PIXEL (mask_img, x, y,
 		     (!EQ (color_val, Qt) ? PIX_MASK_DRAW
 		      : (have_mask = true, PIX_MASK_RETAIN)));
 #else
-          if (EQ (color_val, Qt))
-            ns_set_alpha (ximg, x, y, 0);
+	  if (EQ (color_val, Qt))
+	    ns_set_alpha (ximg, x, y, 0);
 #endif
 	}
       if (y + 1 < height)
@@ -4939,7 +5057,7 @@ #define expect_ident(IDENT)					\
     IMAGE_BACKGROUND (img, f, ximg);
 
   image_put_x_image (f, img, ximg, 0);
-#ifndef HAVE_NS
+#if !defined(HAVE_NS)
   if (have_mask)
     {
       /* Fill in the background_transparent field while we have the
@@ -4970,7 +5088,7 @@ #define expect_ident(IDENT)					\
 
 static bool
 xpm_load (struct frame *f,
-          struct image *img)
+	  struct image *img)
 {
   bool success_p = 0;
   Lisp_Object file_name;
@@ -5300,16 +5418,16 @@ init_color_table (void)
 
 static int emboss_matrix[9] = {
    /* x - 1	x	x + 1  */
-        2,     -1,  	  0,		/* y - 1 */
+	2,     -1,	  0,		/* y - 1 */
        -1,      0,        1,		/* y     */
-        0,      1,       -2		/* y + 1 */
+	0,      1,       -2		/* y + 1 */
 };
 
 static int laplace_matrix[9] = {
    /* x - 1	x	x + 1  */
-        1,      0,  	  0,		/* y - 1 */
-        0,      0,        0,		/* y     */
-        0,      0,       -1		/* y + 1 */
+	1,      0,	  0,		/* y - 1 */
+	0,      0,        0,		/* y     */
+	0,      0,       -1		/* y + 1 */
 };
 
 /* Value is the intensity of the color whose red/green/blue values
@@ -5354,9 +5472,9 @@ image_to_emacs_colors (struct frame *f, struct image *img, bool rgb_p)
       for (x = 0; x < img->width; ++x, ++p)
 	p->pixel = GET_PIXEL (ximg, x, y);
       if (rgb_p)
-        {
-          FRAME_TERMINAL (f)->query_colors (f, row, img->width);
-        }
+	{
+	  FRAME_TERMINAL (f)->query_colors (f, row, img->width);
+	}
 #else  /* USE_CAIRO || HAVE_NS */
       for (x = 0; x < img->width; ++x, ++p)
 	{
@@ -5467,7 +5585,7 @@ image_from_emacs_colors (struct frame *f, struct image *img, Emacs_Color *colors
 
 static void
 image_detect_edges (struct frame *f, struct image *img,
-                    int *matrix, int color_adjust)
+		    int *matrix, int color_adjust)
 {
   Emacs_Color *colors = image_to_emacs_colors (f, img, 1);
   Emacs_Color *new, *p;
@@ -5512,8 +5630,8 @@ #define COLOR(A, X, Y) ((A) + (Y) * img->width + (X))
 	  for (yy = y - 1; yy < y + 2; ++yy)
 	    for (xx = x - 1; xx < x + 2; ++xx, ++i)
 	      if (matrix[i])
-	        {
-	          Emacs_Color *t = COLOR (colors, xx, yy);
+		{
+		  Emacs_Color *t = COLOR (colors, xx, yy);
 		  r += matrix[i] * t->red;
 		  g += matrix[i] * t->green;
 		  b += matrix[i] * t->blue;
@@ -5567,7 +5685,7 @@ image_laplace (struct frame *f, struct image *img)
 
 static void
 image_edge_detection (struct frame *f, struct image *img,
-                      Lisp_Object matrix, Lisp_Object color_adjust)
+		      Lisp_Object matrix, Lisp_Object color_adjust)
 {
   int i = 0;
   int trans[9];
@@ -5670,7 +5788,7 @@ image_disable_image (struct frame *f, struct image *img)
   if (n_planes < 2 || cross_disabled_images)
     {
 #ifndef HAVE_NTGUI
-#ifndef HAVE_NS  /* TODO: NS support, however this not needed for toolbars */
+#if !defined(HAVE_NS)  /* TODO: NS support, however this not needed for toolbars */
 
 #ifndef USE_CAIRO
 #define CrossForeground(f) BLACK_PIX_DEFAULT (f)
@@ -5730,7 +5848,7 @@ #define MaskForeground(f)  PIX_MASK_DRAW
 
 static void
 image_build_heuristic_mask (struct frame *f, struct image *img,
-                            Lisp_Object how)
+			    Lisp_Object how)
 {
   Emacs_Pix_Context ximg;
 #ifdef HAVE_NTGUI
@@ -5748,7 +5866,7 @@ image_build_heuristic_mask (struct frame *f, struct image *img,
     image_clear_image_1 (f, img, CLEAR_IMAGE_MASK);
 
 #ifndef HAVE_NTGUI
-#ifndef HAVE_NS
+#if !defined HAVE_NS
   /* Create an image and pixmap serving as mask.  */
   if (! image_create_x_image_and_pixmap (f, img, img->width, img->height, 1,
 					 &mask_img, 1))
@@ -5808,9 +5926,9 @@ image_build_heuristic_mask (struct frame *f, struct image *img,
 				  ? PIX_MASK_DRAW : PIX_MASK_RETAIN));
 #else
       if (XGetPixel (ximg, x, y) == bg)
-        ns_set_alpha (ximg, x, y, 0);
+	ns_set_alpha (ximg, x, y, 0);
 #endif /* HAVE_NS */
-#ifndef HAVE_NS
+#if !defined HAVE_NS
   /* Fill in the background_transparent field while we have the mask handy. */
   image_background_transparent (img, f, mask_img);
 
@@ -5939,7 +6057,7 @@ pbm_scan_number (char **s, char *end)
       /* Read decimal number.  */
       val = c - '0';
       while ((c = pbm_next_char (s, end)) != -1 && c_isdigit (c))
-        val = 10 * val + c - '0';
+	val = 10 * val + c - '0';
     }
 
   return val;
@@ -6521,6 +6639,8 @@ init_png_functions (void)
 
 # endif /* WINDOWSNT */
 
+#ifdef HAVE_PNG
+
 /* Fast implementations of setjmp and longjmp.  Although setjmp and longjmp
    will do, POSIX _setjmp and _longjmp (if available) are often faster.
    Do not use sys_setjmp, as PNG supports only jmp_buf.
@@ -6698,8 +6818,8 @@ png_load_body (struct frame *f, struct image *img, struct png_load_context *c)
 
   /* Initialize read and info structs for PNG lib.  */
   png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
-				       NULL, my_png_error,
-				       my_png_warning);
+				    NULL, my_png_error,
+				    my_png_warning);
   if (png_ptr)
     {
       info_ptr = png_create_info_struct (png_ptr);
@@ -6812,12 +6932,12 @@ png_load_body (struct frame *f, struct image *img, struct png_load_context *c)
 	 color set by the image.  */
       if (STRINGP (specified_bg)
 	  ? FRAME_TERMINAL (f)->defined_color_hook (f,
-                                                    SSDATA (specified_bg),
-                                                    &color,
-                                                    false,
-                                                    false)
+						    SSDATA (specified_bg),
+						    &color,
+						    false,
+						    false)
 	  : (FRAME_TERMINAL (f)->query_frame_background_color (f, &color),
-             true))
+	     true))
 	/* The user specified `:background', use that.  */
 	{
 	  int shift = bit_depth == 16 ? 0 : 8;
@@ -6971,11 +7091,27 @@ png_load_body (struct frame *f, struct image *img, struct png_load_context *c)
 static bool
 png_load (struct frame *f, struct image *img)
 {
+#ifdef HAVE_GTK4
+  if (gdk_load (f, img))
+    return true;
+#endif
   struct png_load_context c;
   return png_load_body (f, img, &c);
 }
 
-#endif /* HAVE_PNG */
+#endif
+
+#elif defined HAVE_NS
+
+static bool
+png_load (struct frame *f, struct image *img)
+{
+  return ns_load_image (f, img,
+			image_spec_value (img->spec, QCfile, NULL),
+			image_spec_value (img->spec, QCdata, NULL));
+}
+
+#endif /* HAVE_NS */
 
 
 \f
@@ -7276,15 +7412,15 @@ our_stdio_fill_input_buffer (j_decompress_ptr cinfo)
 
       bytes = fread (src->buffer, 1, JPEG_STDIO_BUFFER_SIZE, src->file);
       if (bytes > 0)
-        src->mgr.bytes_in_buffer = bytes;
+	src->mgr.bytes_in_buffer = bytes;
       else
-        {
-          WARNMS (cinfo, JWRN_JPEG_EOF);
-          src->finished = 1;
-          src->buffer[0] = (JOCTET) 0xFF;
-          src->buffer[1] = (JOCTET) JPEG_EOI;
-          src->mgr.bytes_in_buffer = 2;
-        }
+	{
+	  WARNMS (cinfo, JWRN_JPEG_EOF);
+	  src->finished = 1;
+	  src->buffer[0] = (JOCTET) 0xFF;
+	  src->buffer[1] = (JOCTET) JPEG_EOI;
+	  src->mgr.bytes_in_buffer = 2;
+	}
       src->mgr.next_input_byte = src->buffer;
     }
 
@@ -7304,19 +7440,19 @@ our_stdio_skip_input_data (j_decompress_ptr cinfo, long int num_bytes)
   while (num_bytes > 0 && !src->finished)
     {
       if (num_bytes <= src->mgr.bytes_in_buffer)
-        {
-          src->mgr.bytes_in_buffer -= num_bytes;
-          src->mgr.next_input_byte += num_bytes;
-          break;
-        }
+	{
+	  src->mgr.bytes_in_buffer -= num_bytes;
+	  src->mgr.next_input_byte += num_bytes;
+	  break;
+	}
       else
-        {
-          num_bytes -= src->mgr.bytes_in_buffer;
-          src->mgr.bytes_in_buffer = 0;
-          src->mgr.next_input_byte = NULL;
+	{
+	  num_bytes -= src->mgr.bytes_in_buffer;
+	  src->mgr.bytes_in_buffer = 0;
+	  src->mgr.next_input_byte = NULL;
 
-          our_stdio_fill_input_buffer (cinfo);
-        }
+	  our_stdio_fill_input_buffer (cinfo);
+	}
     }
 }
 
@@ -7533,6 +7669,10 @@ jpeg_load_body (struct frame *f, struct image *img,
 static bool
 jpeg_load (struct frame *f, struct image *img)
 {
+#ifdef HAVE_GTK4
+  if (gdk_load (f, img))
+    return true;
+#endif
   struct my_jpeg_error_mgr mgr;
   return jpeg_load_body (f, img, &mgr);
 }
@@ -7803,6 +7943,10 @@ tiff_warning_handler (const char *title, const char *format, va_list ap)
 static bool
 tiff_load (struct frame *f, struct image *img)
 {
+#ifdef HAVE_GTK4
+  if (gdk_load (f, img))
+    return true;
+#endif
   Lisp_Object specified_file;
   Lisp_Object specified_data;
   TIFF *tiff;
@@ -8467,24 +8611,24 @@ gif_load (struct frame *f, struct image *img)
 		{
 		  int c = raster[y * subimg_width + x];
 		  if (transparency_color_index != c || disposal != 1)
-                    {
-                      PUT_PIXEL (ximg, x + subimg_left, row + subimg_top,
-                                 pixel_colors[c]);
+		    {
+		      PUT_PIXEL (ximg, x + subimg_left, row + subimg_top,
+				 pixel_colors[c]);
 		    }
 		}
 	    }
 	}
       else
 	{
-          for (y = 0; y < subimg_height; ++y)
+	  for (y = 0; y < subimg_height; ++y)
 	    for (x = 0; x < subimg_width; ++x)
 	      {
 		int c = raster[y * subimg_width + x];
 		if (transparency_color_index != c || disposal != 1)
-                  {
-                    PUT_PIXEL (ximg, x + subimg_left, y + subimg_top,
-                               pixel_colors[c]);
-                  }
+		  {
+		    PUT_PIXEL (ximg, x + subimg_left, y + subimg_top,
+			       pixel_colors[c]);
+		  }
 	      }
 	}
     }
@@ -8606,7 +8750,7 @@ gif_load (struct frame *f, struct image *img)
     {":max-height",	IMAGE_INTEGER_VALUE,			0},
     {":max-width",	IMAGE_INTEGER_VALUE,			0},
     {":format",		IMAGE_SYMBOL_VALUE,			0},
-    {":rotation",	IMAGE_NUMBER_VALUE,     		0},
+    {":rotation",	IMAGE_NUMBER_VALUE,			0},
     {":crop",		IMAGE_DONT_CHECK_VALUE_TYPE,		0}
   };
 
@@ -8614,7 +8758,7 @@ gif_load (struct frame *f, struct image *img)
 
 static void
 imagemagick_clear_image (struct frame *f,
-                         struct image *img)
+			 struct image *img)
 {
   image_clear_image (f, img);
 }
@@ -8780,9 +8924,9 @@ imagemagick_get_animation_cache (MagickWand *wand)
       cache = *pcache;
       if (! cache)
 	{
-          *pcache = cache = imagemagick_create_cache (signature);
-          break;
-        }
+	  *pcache = cache = imagemagick_create_cache (signature);
+	  break;
+	}
       if (strcmp (signature, cache->signature) == 0)
 	break;
       pcache = &cache->next;
@@ -8981,9 +9125,9 @@ imagemagick_load_image (struct frame *f, struct image *img,
   if (NILP (image_spec_value (img->spec, QCrotation, NULL)))
     if (MagickAutoOrientImage (image_wand) == MagickFalse)
       {
-        image_error ("Error applying automatic orientation in image `%s'", img->spec);
-        DestroyMagickWand (image_wand);
-        return 0;
+	image_error ("Error applying automatic orientation in image `%s'", img->spec);
+	DestroyMagickWand (image_wand);
+	return 0;
       }
 #endif
 
@@ -8997,14 +9141,14 @@ imagemagick_load_image (struct frame *f, struct image *img,
   if (MagickGetImageDelay (image_wand) > 0)
     img->lisp_data =
       Fcons (Qdelay,
-             Fcons (make_float (MagickGetImageDelay (image_wand) / 100.0),
-                    img->lisp_data));
+	     Fcons (make_float (MagickGetImageDelay (image_wand) / 100.0),
+		    img->lisp_data));
 
   if (MagickGetNumberImages (image_wand) > 1)
     img->lisp_data =
       Fcons (Qcount,
-             Fcons (make_fixnum (MagickGetNumberImages (image_wand)),
-                    img->lisp_data));
+	     Fcons (make_fixnum (MagickGetNumberImages (image_wand)),
+		    img->lisp_data));
 
   /* If we have an animated image, get the new wand based on the
      "super-wand". */
@@ -9042,10 +9186,10 @@ imagemagick_load_image (struct frame *f, struct image *img,
     specified_bg = image_spec_value (img->spec, QCbackground, NULL);
     if (!STRINGP (specified_bg)
 	|| !FRAME_TERMINAL (f)->defined_color_hook (f,
-                                                    SSDATA (specified_bg),
-                                                    &bgcolor,
-                                                    false,
-                                                    false))
+						    SSDATA (specified_bg),
+						    &bgcolor,
+						    false,
+						    false))
       FRAME_TERMINAL (f)->query_frame_background_color (f, &bgcolor);
 
     bg_wand = NewPixelWand ();
@@ -9076,10 +9220,10 @@ imagemagick_load_image (struct frame *f, struct image *img,
   if (CONSP (crop) && TYPE_RANGED_FIXNUMP (size_t, XCAR (crop)))
     {
       /* After some testing, it seems MagickCropImage is the fastest crop
-         function in ImageMagick.  This crop function seems to do less copying
-         than the alternatives, but it still reads the entire image into memory
-         before cropping, which is apparently difficult to avoid when using
-         imagemagick.  */
+	 function in ImageMagick.  This crop function seems to do less copying
+	 than the alternatives, but it still reads the entire image into memory
+	 before cropping, which is apparently difficult to avoid when using
+	 imagemagick.  */
       size_t crop_width = XFIXNUM (XCAR (crop));
       crop = XCDR (crop);
       if (CONSP (crop) && TYPE_RANGED_FIXNUMP (size_t, XCAR (crop)))
@@ -9112,11 +9256,11 @@ imagemagick_load_image (struct frame *f, struct image *img,
       rotation = XFLOAT_DATA (value);
       status = MagickRotateImage (image_wand, bg_wand, rotation);
       if (status == MagickFalse)
-        {
-          image_error ("Imagemagick image rotate failed");
+	{
+	  image_error ("Imagemagick image rotate failed");
 	  imagemagick_error (image_wand);
-          goto imagemagick_error;
-        }
+	  goto imagemagick_error;
+	}
     }
 
   /* Set the canvas background color to the frame or specified
@@ -9159,8 +9303,8 @@ imagemagick_load_image (struct frame *f, struct image *img,
   if (imagemagick_render_type != 0)
     {
       /* Magicexportimage is normally faster than pixelpushing.  This
-         method is also well tested.  Some aspects of this method are
-         ad-hoc and needs to be more researched. */
+	 method is also well tested.  Some aspects of this method are
+	 ad-hoc and needs to be more researched. */
       void *dataptr;
       int imagedepth = 24; /*MagickGetImageDepth(image_wand);*/
       const char *exportdepth = imagedepth <= 8 ? "I" : "BGRP"; /*"RGBP";*/
@@ -9189,11 +9333,11 @@ imagemagick_load_image (struct frame *f, struct image *img,
       /*   break; */
       /* } */
       /*
-        Here im just guessing the format of the bitmap.
-        happens to work fine for:
-        - bw djvu images
-        on rgb display.
-        seems about 3 times as fast as pixel pushing(not carefully measured)
+	Here im just guessing the format of the bitmap.
+	happens to work fine for:
+	- bw djvu images
+	on rgb display.
+	seems about 3 times as fast as pixel pushing(not carefully measured)
       */
       int pixelwidth = CharPixel; /*??? TODO figure out*/
       MagickExportImagePixels (image_wand, 0, 0, width, height,
@@ -9208,48 +9352,48 @@ imagemagick_load_image (struct frame *f, struct image *img,
       /* Try to create a x pixmap to hold the imagemagick pixmap.  */
       if (!image_create_x_image_and_pixmap (f, img, width, height, 0,
 					    &ximg, 0))
-        {
+	{
 #ifdef COLOR_TABLE_SUPPORT
 	  free_color_table ();
 #endif
-          image_error ("Imagemagick X bitmap allocation failure");
-          goto imagemagick_error;
-        }
+	  image_error ("Imagemagick X bitmap allocation failure");
+	  goto imagemagick_error;
+	}
 
       /* Copy imagemagick image to x with primitive yet robust pixel
-         pusher loop.  This has been tested a lot with many different
-         images.  */
+	 pusher loop.  This has been tested a lot with many different
+	 images.  */
 
       /* Copy pixels from the imagemagick image structure to the x image map. */
       iterator = NewPixelIterator (image_wand);
       if (! iterator)
-        {
+	{
 #ifdef COLOR_TABLE_SUPPORT
 	  free_color_table ();
 #endif
 	  image_destroy_x_image (ximg);
-          image_error ("Imagemagick pixel iterator creation failed");
-          goto imagemagick_error;
-        }
+	  image_error ("Imagemagick pixel iterator creation failed");
+	  goto imagemagick_error;
+	}
 
       image_height = MagickGetImageHeight (image_wand);
       for (y = 0; y < image_height; y++)
-        {
+	{
 	  size_t row_width;
 	  pixels = PixelGetNextIteratorRow (iterator, &row_width);
-          if (! pixels)
-            break;
+	  if (! pixels)
+	    break;
 	  int xlim = min (row_width, width);
 	  for (x = 0; x < xlim; x++)
-            {
-              PixelGetMagickColor (pixels[x], &pixel);
-              PUT_PIXEL (ximg, x, y,
-                         lookup_rgb_color (f,
+	    {
+	      PixelGetMagickColor (pixels[x], &pixel);
+	      PUT_PIXEL (ximg, x, y,
+			 lookup_rgb_color (f,
 					   color_scale * pixel.red,
 					   color_scale * pixel.green,
 					   color_scale * pixel.blue));
-            }
-        }
+	    }
+	}
       DestroyPixelIterator (iterator);
     }
 
@@ -9325,7 +9469,7 @@ imagemagick_load (struct frame *f, struct image *img)
 	  return 0;
 	}
       success_p = imagemagick_load_image (f, img, SDATA (data),
-                                          SBYTES (data), NULL);
+					  SBYTES (data), NULL);
     }
 
   return success_p;
@@ -9649,7 +9793,7 @@ svg_load (struct frame *f, struct image *img)
 	}
       original_filename = BVAR (current_buffer, filename);
       success_p = svg_load_image (f, img, SSDATA (data), SBYTES (data),
-                                  (NILP (original_filename) ? NULL
+				  (NILP (original_filename) ? NULL
 				   : SSDATA (original_filename)));
     }
 
@@ -9758,10 +9902,10 @@ svg_load_image (struct frame *f, struct image *img, char *contents,
     Lisp_Object specified_bg = image_spec_value (img->spec, QCbackground, NULL);
     if (!STRINGP (specified_bg)
 	|| !FRAME_TERMINAL (f)->defined_color_hook (f,
-                                                    SSDATA (specified_bg),
-                                                    &background,
-                                                    false,
-                                                    false))
+						    SSDATA (specified_bg),
+						    &background,
+						    false,
+						    false))
       FRAME_TERMINAL (f)->query_frame_background_color (f, &background);
 
     /* SVG pixmaps specify transparency in the last byte, so right
@@ -10237,7 +10381,7 @@ initialize_image_type (struct image_type const *type)
  { SYMBOL_INDEX (Qjpeg), jpeg_image_p, jpeg_load, image_clear_image,
    IMAGE_TYPE_INIT (init_jpeg_functions) },
 #endif
-#if defined HAVE_XPM || defined HAVE_NS
+#if defined HAVE_XPM || defined HAVE_NS || defined USE_CAIRO
  { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image,
    IMAGE_TYPE_INIT (init_xpm_functions) },
 #endif
@@ -10361,7 +10505,7 @@ syms_of_image (void)
 #else
 	make_fixnum (-1)
 #endif
-        );
+	);
   DEFSYM (Qlibjpeg_version, "libjpeg-version");
   Fset (Qlibjpeg_version,
 #if HAVE_JPEG
@@ -10378,7 +10522,7 @@ syms_of_image (void)
   DEFSYM (Qxbm, "xbm");
   add_image_type (Qxbm);
 
-#if defined (HAVE_XPM) || defined (HAVE_NS)
+#if defined (HAVE_XPM) || defined (HAVE_NS) || defined (USE_CAIRO)
   DEFSYM (Qxpm, "xpm");
   add_image_type (Qxpm);
 #endif
diff --git a/src/keyboard.c b/src/keyboard.c
index c94d794b01..975a6f63dd 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -658,6 +658,41 @@ echo_truncate (ptrdiff_t nchars)
   truncate_echo_area (nchars);
 }
 
+#ifdef HAVE_PGTK
+static void
+ckey_update_pgtk_im (void)
+{
+  if (this_command_key_count > 0)
+    {
+      Lisp_Object frame;
+      Lisp_Object tail;
+      FOR_EACH_FRAME (tail, frame)
+	{
+	  if (FRAME_PGTK_P (XFRAME (frame)) &&
+	      FRAME_LIVE_P (XFRAME (frame)) &&
+	      FRAME_KBOARD (XFRAME (frame)) == current_kboard)
+	    {
+	      Fpgtk_use_im_context (Qnil, frame);
+	    }
+	}
+    }
+  else
+    {
+      Lisp_Object frame;
+      Lisp_Object tail;
+      FOR_EACH_FRAME (tail, frame)
+	{
+	  if (FRAME_PGTK_P (XFRAME (frame)) &&
+	      FRAME_LIVE_P (XFRAME (frame)) &&
+	      FRAME_KBOARD (XFRAME (frame)) == current_kboard)
+	    {
+	      Fpgtk_use_im_context (Qt, frame);
+	    }
+	}
+    }
+}
+#endif
+
 \f
 /* Functions for manipulating this_command_keys.  */
 static void
@@ -668,6 +703,10 @@ add_command_key (Lisp_Object key)
 
   ASET (this_command_keys, this_command_key_count, key);
   ++this_command_key_count;
+
+#ifdef HAVE_PGTK
+  ckey_update_pgtk_im();
+#endif
 }
 
 \f
@@ -1245,6 +1284,9 @@ command_loop_1 (void)
   cancel_echoing ();
 
   this_command_key_count = 0;
+#ifdef HAVE_PGTK
+  ckey_update_pgtk_im();
+#endif
   this_single_command_key_start = 0;
 
   if (NILP (Vmemory_full))
@@ -1499,6 +1541,10 @@ command_loop_1 (void)
       this_command_key_count = 0;
       this_single_command_key_start = 0;
 
+#ifdef HAVE_PGTK
+  ckey_update_pgtk_im();
+#endif
+
       if (current_kboard->immediate_echo
 	  && !NILP (call0 (Qinternal_echo_keystrokes_prefix)))
 	{
@@ -3072,6 +3118,10 @@ read_char (int commandflag, Lisp_Object map,
       if (key_count > 0)
 	this_command_keys = keys;
 
+#ifdef HAVE_PGTK
+      ckey_update_pgtk_im();
+#endif
+
       cancel_echoing ();
       ok_to_echo_at_next_pause = saved_ok_to_echo;
       kset_echo_string (current_kboard, saved_echo_string);
@@ -3938,6 +3988,9 @@ kbd_buffer_get_event (KBOARD **kbp,
 	  *used_mouse_menu = true;
 	FALLTHROUGH;
 #endif
+#ifdef HAVE_PGTK
+      case PGTK_PREEDIT_TEXT_EVENT:
+#endif
 #ifdef HAVE_NTGUI
       case END_SESSION_EVENT:
       case LANGUAGE_CHANGE_EVENT:
@@ -6051,7 +6104,10 @@ make_lispy_event (struct input_event *event)
     case CONFIG_CHANGED_EVENT:
 	return list3 (Qconfig_changed_event,
 		      event->arg, event->frame_or_window);
-
+#ifdef HAVE_PGTK
+    case PGTK_PREEDIT_TEXT_EVENT:
+      return list2 (intern("pgtk-preedit-text"), event->arg);
+#endif
       /* The 'kind' field of the event is something we don't recognize.  */
     default:
       emacs_abort ();
@@ -9442,6 +9498,10 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt,
   if (INTERACTIVE && t < mock_input)
     echo_truncate (echo_start);
 
+#ifdef HAVE_PGTK
+  ckey_update_pgtk_im();
+#endif
+
   /* If the best binding for the current key sequence is a keymap, or
      we may be looking at a function key's escape sequence, keep on
      reading.  */
@@ -9518,6 +9578,10 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt,
 	echo_truncate (echo_local_start);
       this_command_key_count = keys_local_start;
 
+#ifdef HAVE_PGTK
+      ckey_update_pgtk_im();
+#endif
+
       /* By default, assume each event is "real".  */
       last_real_key_start = t;
 
@@ -10239,6 +10303,9 @@ read_key_sequence_vs (Lisp_Object prompt, Lisp_Object continue_echo,
     {
       this_command_key_count = 0;
       this_single_command_key_start = 0;
+#ifdef HAVE_PGTK
+  ckey_update_pgtk_im();
+#endif
     }
 
 #ifdef HAVE_WINDOW_SYSTEM
@@ -10925,7 +10992,7 @@ handle_interrupt (bool in_signal_handler)
          to ns_select there (needed because otherwise events aren't picked up
          outside of polling since we don't get SIGIO like X and we don't have a
          separate event loop thread like W32.  */
-#ifndef HAVE_NS
+#if !defined(HAVE_NS)
 #ifdef THREADS_ENABLED
   /* If we were called from a signal handler, we must be in the main
      thread, see deliver_process_signal.  So we must make sure the
@@ -12404,6 +12471,8 @@ keys_of_keyboard (void)
 			    "ns-put-working-text");
   initial_define_lispy_key (Vspecial_event_map, "ns-unput-working-text",
 			    "ns-unput-working-text");
+  initial_define_lispy_key (Vspecial_event_map, "pgtk-preedit-text",
+			    "pgtk-preedit-text");
   /* Here we used to use `ignore-event' which would simple set prefix-arg to
      current-prefix-arg, as is done in `handle-switch-frame'.
      But `handle-switch-frame is not run from the special-map.
diff --git a/src/lisp.h b/src/lisp.h
index b4ac017dcf..33b0adf86a 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -1103,6 +1103,7 @@ DEFINE_GDB_SYMBOL_END (PSEUDOVECTOR_FLAG)
   PVEC_MUTEX,
   PVEC_CONDVAR,
   PVEC_MODULE_FUNCTION,
+  PVEC_EMBEDDED_WIDGET,
 
   /* These should be last, for internal_equal and sxhash_obj.  */
   PVEC_COMPILED,
@@ -1349,6 +1350,7 @@ #define XSETSUB_CHAR_TABLE(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_SUB_CHAR_TABLE))
 #define XSETTHREAD(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_THREAD))
 #define XSETMUTEX(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_MUTEX))
 #define XSETCONDVAR(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_CONDVAR))
+#define XSETGTKWIDGET(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_EMBEDDED_WIDGET))
 
 /* Efficiently convert a pointer to a Lisp object and back.  The
    pointer is represented as a fixnum, so the garbage collector
@@ -2068,6 +2070,7 @@ CHAR_TABLE_SET (Lisp_Object ct, int idx, Lisp_Object val)
     char_table_set (ct, idx, val);
 }
 
+
 /* This structure describes a built-in function.
    It is generated by the DEFUN macro only.
    defsubr makes it into a Lisp object.  */
@@ -3066,7 +3069,7 @@ #define DEFUN(lname, fnname, sname, minargs, maxargs, intspec, doc)	\
   static union Aligned_Lisp_Subr sname =                                \
      {{{ PVEC_SUBR << PSEUDOVECTOR_AREA_BITS },				\
        { .a ## maxargs = fnname },					\
-       minargs, maxargs, lname, intspec, 0}};				\
+       minargs, maxargs, lname, intspec, 0}};			\
    Lisp_Object fnname
 
 /* defsubr (Sname);
@@ -3340,8 +3343,8 @@ rarely_quit (unsigned short int count)
 struct frame;
 
 /* Define if the windowing system provides a menu bar.  */
-#if defined (USE_X_TOOLKIT) || defined (HAVE_NTGUI) \
-  || defined (HAVE_NS) || defined (USE_GTK)
+#if (defined (USE_X_TOOLKIT) || defined (HAVE_NTGUI)	\
+     || defined (HAVE_NS) || defined (USE_GTK))
 #define HAVE_EXT_MENU_BAR true
 #endif
 
@@ -4709,7 +4712,6 @@ maybe_disable_address_randomization (int argc, char **argv)
 extern void malloc_probe (size_t);
 extern void syms_of_profiler (void);
 
-
 #ifdef DOS_NT
 /* Defined in msdos.c, w32.c.  */
 extern char *emacs_root_dir (void);
diff --git a/src/lread.c b/src/lread.c
index 59bf529f45..f38962f2a6 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -19,6 +19,7 @@ Copyright (C) 1985-1989, 1993-1995, 1997-2020 Free Software Foundation,
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 /* Tell globals.h to define tables needed by init_obarray.  */
+#include <string.h>
 #define DEFINE_SYMBOLS
 
 #include <config.h>
@@ -1144,7 +1145,6 @@ DEFUN ("load", Fload, Sload, 1, 5, 0,
   int version;
 
   CHECK_STRING (file);
-
   /* If file name is magic, call the handler.  */
   /* This shouldn't be necessary any more now that `openp' handles it right.
     handler = Ffind_file_name_handler (file, Qload);
@@ -4124,10 +4124,14 @@ intern_c_string_1 (const char *str, ptrdiff_t len)
 
   if (!SYMBOLP (tem))
     {
-      /* Creating a non-pure string from a string literal not implemented yet.
-	 We could just use make_string here and live with the extra copy.  */
-      eassert (!NILP (Vpurify_flag));
-      tem = intern_driver (make_pure_c_string (str, len), obarray, tem);
+      Lisp_Object string;
+
+      if (NILP (Vpurify_flag))
+	string = make_string (str, len);
+      else
+	string = make_pure_c_string (str, len);
+
+      tem = intern_driver (string, obarray, tem);
     }
   return tem;
 }
@@ -4810,21 +4814,16 @@ syms_of_lread (void)
 This list should not include the empty string.
 `load' and related functions try to append these suffixes, in order,
 to the specified file name if a suffix is allowed or required.  */);
+  Vload_suffixes = list2 (build_pure_c_string (".elc"),
+			  build_pure_c_string (".el"));
 #ifdef HAVE_MODULES
+  Vload_suffixes = Fcons (build_pure_c_string (MODULES_SUFFIX), Vload_suffixes);
 #ifdef MODULES_SECONDARY_SUFFIX
-  Vload_suffixes = list4 (build_pure_c_string (".elc"),
-			  build_pure_c_string (".el"),
-			  build_pure_c_string (MODULES_SUFFIX),
-                          build_pure_c_string (MODULES_SECONDARY_SUFFIX));
-#else
-  Vload_suffixes = list3 (build_pure_c_string (".elc"),
-			  build_pure_c_string (".el"),
-			  build_pure_c_string (MODULES_SUFFIX));
+  Vload_suffixes =
+    Fcons (build_pure_c_string (MODULES_SECONDARY_SUFFIX), Vload_suffixes);
 #endif
-#else
-  Vload_suffixes = list2 (build_pure_c_string (".elc"),
-			  build_pure_c_string (".el"));
 #endif
+
   DEFVAR_LISP ("module-file-suffix", Vmodule_file_suffix,
 	       doc: /* Suffix of loadable module file, or nil if modules are not supported.  */);
 #ifdef HAVE_MODULES
diff --git a/src/menu.c b/src/menu.c
index e4fda572cd..f65b096a8e 100644
--- a/src/menu.c
+++ b/src/menu.c
@@ -422,7 +422,7 @@ single_menu_item (Lisp_Object key, Lisp_Object item, Lisp_Object dummy, void *sk
 		  AREF (item_properties, ITEM_PROPERTY_SELECTED),
 		  AREF (item_properties, ITEM_PROPERTY_HELP));
 
-#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS) || defined (HAVE_NTGUI)
+#ifdef HAVE_EXT_MENU_BAR
   /* Display a submenu using the toolkit.  */
   if (FRAME_WINDOW_P (XFRAME (Vmenu_updating_frame))
       && ! (NILP (map) || NILP (enabled)))
@@ -1245,6 +1245,8 @@ x_popup_menu_1 (Lisp_Object position, Lisp_Object menu)
 
 	xpos = WINDOW_LEFT_EDGE_X (win);
 	ypos = WINDOW_TOP_EDGE_Y (win);
+
+	xpos += WINDOW_LEFT_MARGIN_WIDTH (win);
       }
     else
       /* ??? Not really clean; should be CHECK_WINDOW_OR_FRAME,
diff --git a/src/menu.h b/src/menu.h
index 44749ade75..d6ea9f2d34 100644
--- a/src/menu.h
+++ b/src/menu.h
@@ -35,14 +35,14 @@ #define MENU_H
 extern void save_menu_items (void);
 extern bool parse_single_submenu (Lisp_Object, Lisp_Object, Lisp_Object);
 extern void list_of_panes (Lisp_Object);
-#ifdef HAVE_EXT_MENU_BAR
+
 extern void free_menubar_widget_value_tree (widget_value *);
 extern void update_submenu_strings (widget_value *);
 extern void find_and_call_menu_selection (struct frame *, int,
                                           Lisp_Object, void *);
 extern widget_value *make_widget_value (const char *, char *, bool, Lisp_Object);
 extern widget_value *digest_single_submenu (int, int, bool);
-#endif
+
 
 #if defined (HAVE_X_WINDOWS) || defined (MSDOS)
 extern Lisp_Object x_menu_show (struct frame *, int, int, int,
@@ -59,6 +59,12 @@ #define MENU_H
 				 Lisp_Object, const char **);
 extern void ns_activate_menubar (struct frame *);
 #endif
+#ifdef HAVE_PGTK
+extern Lisp_Object pgtk_menu_show (struct frame *, int, int, int,
+				 Lisp_Object, const char **);
+extern void pgtk_activate_menubar (struct frame *);
+#endif
+
 extern Lisp_Object tty_menu_show (struct frame *, int, int, int,
 				  Lisp_Object, const char **);
 extern ptrdiff_t menu_item_width (const unsigned char *);
diff --git a/src/pdumper.c b/src/pdumper.c
index 63424c5734..6793d4243f 100644
--- a/src/pdumper.c
+++ b/src/pdumper.c
@@ -197,6 +197,8 @@ #define dump_offsetof(type, member)                             \
     /* dump_ptr = dump_ptr + dump_base  */
     RELOC_DUMP_TO_DUMP_PTR_RAW,
     /* dump_mpz = [rebuild bignum]  */
+    RELOC_NATIVE_COMP_UNIT,
+    RELOC_NATIVE_SUBR,
     RELOC_BIGNUM,
     /* dump_lv = make_lisp_ptr (dump_lv + dump_base,
 				type - RELOC_DUMP_TO_DUMP_LV)
@@ -341,6 +343,20 @@ dump_fingerprint (char const *label,
   fprintf (stderr, "%s: %.*s\n", label, hexbuf_size, hexbuf);
 }
 
+/* To be used if some order in the relocation process has to be enforced. */
+enum reloc_phase
+  {
+    /* First to run.  Place here every relocation with no dependecy.  */
+    EARLY_RELOCS,
+    /* Late and very late relocs are relocated at the very last after
+       all hooks has been run.  All lisp machinery is at disposal
+       (memory allocation allowed too).  */
+    LATE_RELOCS,
+    VERY_LATE_RELOCS,
+    /* Fake, must be last.  */
+    RELOC_NUM_PHASES
+  };
+
 /* Format of an Emacs dump file.  All offsets are relative to
    the beginning of the file.  An Emacs dump file is coupled
    to exactly the Emacs binary that produced it, so details of
@@ -368,7 +384,7 @@ dump_fingerprint (char const *label,
 
   /* Relocation table for the dump file; each entry is a
      struct dump_reloc.  */
-  struct dump_table_locator dump_relocs;
+  struct dump_table_locator dump_relocs[RELOC_NUM_PHASES];
 
   /* "Relocation" table we abuse to hold information about the
      location and type of each lisp object in the dump.  We need for
@@ -545,7 +561,7 @@ dump_fingerprint (char const *label,
   Lisp_Object cold_queue;
 
   /* Relocations in the dump.  */
-  Lisp_Object dump_relocs;
+  Lisp_Object dump_relocs[RELOC_NUM_PHASES];
 
   /* Object starts.  */
   Lisp_Object object_starts;
@@ -1429,7 +1445,7 @@ dump_reloc_dump_to_emacs_ptr_raw (struct dump_context *ctx,
                                   dump_off dump_offset)
 {
   if (ctx->flags.dump_object_contents)
-    dump_push (&ctx->dump_relocs,
+    dump_push (&ctx->dump_relocs[EARLY_RELOCS],
                list2 (make_fixnum (RELOC_DUMP_TO_EMACS_PTR_RAW),
                       dump_off_to_lisp (dump_offset)));
 }
@@ -1462,7 +1478,7 @@ dump_reloc_dump_to_dump_lv (struct dump_context *ctx,
       emacs_abort ();
     }
 
-  dump_push (&ctx->dump_relocs,
+  dump_push (&ctx->dump_relocs[EARLY_RELOCS],
              list2 (make_fixnum (reloc_type),
                     dump_off_to_lisp (dump_offset)));
 }
@@ -1478,7 +1494,7 @@ dump_reloc_dump_to_dump_ptr_raw (struct dump_context *ctx,
                                  dump_off dump_offset)
 {
   if (ctx->flags.dump_object_contents)
-    dump_push (&ctx->dump_relocs,
+    dump_push (&ctx->dump_relocs[EARLY_RELOCS],
                list2 (make_fixnum (RELOC_DUMP_TO_DUMP_PTR_RAW),
                       dump_off_to_lisp (dump_offset)));
 }
@@ -1511,7 +1527,7 @@ dump_reloc_dump_to_emacs_lv (struct dump_context *ctx,
       emacs_abort ();
     }
 
-  dump_push (&ctx->dump_relocs,
+  dump_push (&ctx->dump_relocs[EARLY_RELOCS],
              list2 (make_fixnum (reloc_type),
                     dump_off_to_lisp (dump_offset)));
 }
@@ -2228,7 +2244,7 @@ dump_bignum (struct dump_context *ctx, Lisp_Object object)
          Lisp_Bignum instead of the actual mpz field so that the
          relocation offset is aligned.  The relocation-application
          code knows to actually advance past the header.  */
-      dump_push (&ctx->dump_relocs,
+      dump_push (&ctx->dump_relocs[EARLY_RELOCS],
                  list2 (make_fixnum (RELOC_BIGNUM),
                         dump_off_to_lisp (bignum_offset)));
     }
@@ -3036,6 +3052,8 @@ dump_vectorlike (struct dump_context *ctx,
       error_unsupported_dump_object (ctx, lv, "condvar");
     case PVEC_MODULE_FUNCTION:
       error_unsupported_dump_object (ctx, lv, "module function");
+    case PVEC_EMBEDDED_WIDGET:
+      error_unsupported_dump_object (ctx, lv, "embedded widget");
     default:
       error_unsupported_dump_object(ctx, lv, "weird pseudovector");
     }
@@ -3410,6 +3428,7 @@ dump_cold_bignum (struct dump_context *ctx, Lisp_Object object)
     }
 }
 
+
 static void
 dump_drain_cold_data (struct dump_context *ctx)
 {
@@ -4052,7 +4071,8 @@ DEFUN ("dump-emacs-portable",
   ctx->symbol_aux = Qnil;
   ctx->copied_queue = Qnil;
   ctx->cold_queue = Qnil;
-  ctx->dump_relocs = Qnil;
+  for (int i = 0; i < RELOC_NUM_PHASES; ++i)
+    ctx->dump_relocs[i] = Qnil;
   ctx->object_starts = Qnil;
   ctx->emacs_relocs = Qnil;
   ctx->bignum_data = make_eq_hash_table ();
@@ -4207,8 +4227,9 @@ DEFUN ("dump-emacs-portable",
   /* Emit instructions for Emacs to execute when loading the dump.
      Note that this relocation information ends up in the cold section
      of the dump.  */
-  drain_reloc_list (ctx, dump_emit_dump_reloc, emacs_reloc_merger,
-		    &ctx->dump_relocs, &ctx->header.dump_relocs);
+  for (int i = 0; i < RELOC_NUM_PHASES; ++i)
+    drain_reloc_list (ctx, dump_emit_dump_reloc, emacs_reloc_merger,
+		      &ctx->dump_relocs[i], &ctx->header.dump_relocs[i]);
   unsigned number_hot_relocations = ctx->number_hot_relocations;
   ctx->number_hot_relocations = 0;
   unsigned number_discardable_relocations = ctx->number_discardable_relocations;
@@ -4226,7 +4247,8 @@ DEFUN ("dump-emacs-portable",
   eassert (NILP (ctx->deferred_symbols));
   eassert (NILP (ctx->deferred_hash_tables));
   eassert (NILP (ctx->fixups));
-  eassert (NILP (ctx->dump_relocs));
+  for (int i = 0; i < RELOC_NUM_PHASES; ++i)
+    eassert (NILP (ctx->dump_relocs[i]));
   eassert (NILP (ctx->emacs_relocs));
 
   /* Dump is complete.  Go back to the header and write the magic
@@ -5224,11 +5246,12 @@ dump_do_dump_relocation (const uintptr_t dump_base,
 }
 
 static void
-dump_do_all_dump_relocations (const struct dump_header *const header,
-			      const uintptr_t dump_base)
+dump_do_all_dump_reloc_for_phase (const struct dump_header *const header,
+				  const uintptr_t dump_base,
+				  const enum reloc_phase phase)
 {
-  struct dump_reloc *r = dump_ptr (dump_base, header->dump_relocs.offset);
-  dump_off nr_entries = header->dump_relocs.nr_entries;
+  struct dump_reloc *r = dump_ptr (dump_base, header->dump_relocs[phase].offset);
+  dump_off nr_entries = header->dump_relocs[phase].nr_entries;
   for (dump_off i = 0; i < nr_entries; ++i)
     dump_do_dump_relocation (dump_base, r[i]);
 }
@@ -5440,7 +5463,7 @@ pdumper_load (const char *dump_filename)
   dump_public.start = dump_base;
   dump_public.end = dump_public.start + dump_size;
 
-  dump_do_all_dump_relocations (header, dump_base);
+  dump_do_all_dump_reloc_for_phase (header, dump_base, EARLY_RELOCS);
   dump_do_all_emacs_relocations (header, dump_base);
 
   dump_mmap_discard_contents (&sections[DS_DISCARDABLE]);
@@ -5451,6 +5474,9 @@ pdumper_load (const char *dump_filename)
      initialization.  */
   for (int i = 0; i < nr_dump_hooks; ++i)
     dump_hooks[i] ();
+
+  dump_do_all_dump_reloc_for_phase (header, dump_base, LATE_RELOCS);
+  dump_do_all_dump_reloc_for_phase (header, dump_base, VERY_LATE_RELOCS);
   initialized = true;
 
   struct timespec load_timespec =
diff --git a/src/pgtkdnd.c b/src/pgtkdnd.c
new file mode 100644
index 0000000000..6655a4be30
--- /dev/null
+++ b/src/pgtkdnd.c
@@ -0,0 +1,112 @@
+/* GTK drag'n'drop support for Emacs
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "pgtkdnd.h"
+#include "config.h"
+#include "pgtkterm.h"
+#include "buffer.h"
+
+static GdkContentFormats *formats;
+
+void
+pgtk_dnd_global_init (void)
+{
+  formats = gdk_content_formats_new ((const char *[])
+				     {"text/plain", NULL}, 1);
+}
+
+
+static void
+drag_callback (GtkDropTarget *dest,
+	       GValue        *drop,
+	       gdouble        x,
+	       gdouble        y,
+	       gpointer       userptr)
+{
+  struct frame *target_frame = userptr;
+  gchar *c = g_strdup (g_value_get_string (drop));
+  size_t idx;
+  size_t idz = 0;
+  for (idx = 0; idz < strlen (c);)
+    {
+      if (c[idz] != '\xd')
+	{
+	  c[idx] = c[idz];
+	  ++idx;
+	  ++idz;
+	}
+      else
+	{
+	  ++idz;
+	}
+    }
+  c[idx] = 0;
+  Lisp_Object str = build_string_from_utf8 (c);
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = DRAG_N_DROP_EVENT;
+  inev.ie.arg = list2 (Qstring, str);
+  inev.ie.modifiers = 0;
+  XSETINT (inev.ie.x, (int) round (x));
+  XSETINT (inev.ie.y, (int) round (y));
+  XSETFRAME (inev.ie.frame_or_window, target_frame);
+  inev.ie.timestamp = time (NULL);
+
+  evq_enqueue (&inev);
+}
+
+
+static GdkDragAction
+drag_move_cb (GtkDropTarget *dest,
+	      gdouble        x,
+	      gdouble        y,
+	      gpointer userptr)
+{
+  struct frame *f = userptr;
+  enum window_part part;
+  Lisp_Object window = window_from_coordinates (f, (int) x, (int) y, &part, 1, 1);
+  if (part != ON_TEXT)
+    return false;
+  if (NILP (window))
+    return false;
+  Fselect_window (window, Qnil);
+  Lisp_Object frame;
+  XSETFRAME (frame, f);
+  Lisp_Object posn = Fposn_at_x_y (make_fixnum (x),
+				   make_fixnum (y),
+				   frame,
+				   Qnil);
+  Lisp_Object posn_point = call1 (intern_c_string ("posn-point"), posn);
+  Fset_window_point (window, posn_point);
+  redisplay ();
+  return GDK_ACTION_COPY;
+}
+
+void
+pgtk_dnd_init (struct frame *f)
+{
+  FRAME_X_OUTPUT (f)->di.target = gtk_drop_target_new (G_TYPE_STRING,
+						       GDK_ACTION_COPY |
+						       GDK_ACTION_MOVE);
+  gtk_widget_add_controller (FRAME_GTK_WIDGET (f),
+			     GTK_EVENT_CONTROLLER (FRAME_X_OUTPUT (f)->di.target));
+  g_signal_connect (G_OBJECT (FRAME_X_OUTPUT (f)->di.target),
+		    "drop", G_CALLBACK (drag_callback), f);
+  g_signal_connect (G_OBJECT (FRAME_X_OUTPUT (f)->di.target),
+		    "motion", G_CALLBACK (drag_move_cb), f);
+}
diff --git a/src/pgtkdnd.h b/src/pgtkdnd.h
new file mode 100644
index 0000000000..c6eeff5c10
--- /dev/null
+++ b/src/pgtkdnd.h
@@ -0,0 +1,35 @@
+/* GTK drag'n'drop support for Emacs
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "frame.h"
+#include <gtk/gtk.h>
+
+#ifndef PGTKDND_H
+#define PGTKDND_H
+
+struct dnd_info
+{
+  GtkDropTarget *target;
+};
+
+extern void
+pgtk_dnd_init (struct frame *f);
+extern void
+pgtk_dnd_global_init (void);
+#endif /* PGTKDND_H */
diff --git a/src/pgtkembed.c b/src/pgtkembed.c
new file mode 100644
index 0000000000..3874fcc841
--- /dev/null
+++ b/src/pgtkembed.c
@@ -0,0 +1,646 @@
+/* Support for embedding GTK (4) widgets in a frame
+
+   Copyright (C) 2011-2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#define __GI_SCANNER__
+
+#include "pgtkembed.h"
+#include "frame.h"
+#include "pgtkterm.h"
+#include "pgtksubr.h"
+#include "coding.h"
+#include "keyboard.h"
+
+#undef __GI_SCANNER__
+
+static gboolean
+embedded_widget_click_cb (GtkButton *btn, gpointer user_data)
+{
+  Lisp_Object arf = ((struct embedded_widget*) user_data)->callbacks;
+  if (!NILP (arf))
+    for (; CONSP (arf); arf = XCDR (arf))
+      if (FUNCTIONP (XCAR (arf)))
+	call0 (XCAR (arf));
+  return true;
+}
+#ifndef FIX_GTK_LEGACY_HANDLER_BUG
+static gboolean
+embedded_grab_cb (GtkEventControllerLegacy *controller,
+		  GdkEvent                 *event,
+		  gpointer                  user_data)
+{
+  if (gdk_event_get_event_type (event) == GDK_BUTTON_PRESS)
+    {
+        struct frame *frame = user_data;
+	gdouble x, y;
+	gdk_event_get_position (event, &x, &y);
+	GtkAllocation alloc;
+	gtk_widget_get_allocation (gtk_event_controller_get_widget
+				   (GTK_EVENT_CONTROLLER (controller)),
+				   &alloc);
+	x += alloc.x;
+	y += alloc.y;
+	Lisp_Object window = window_from_coordinates (frame,
+						      (int) x, (int) y, 0,
+						      false, false);
+	if (!NILP (window))
+	  Fselect_window (window, Qnil);
+    }
+  return false;
+}
+#endif
+static gboolean
+embedded_key_cb (GtkEventControllerKey *controller,
+		 guint                  keyval,
+		 guint                  keycode,
+		 GdkModifierType        state,
+		 gpointer               user_data)
+{
+  static bool kmflag = 0;
+  static bool zflag = 0;
+  struct frame *f = user_data;
+  int keystate = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), state);
+  guint c = keyval;
+  if (c > 255 || keycode == GDK_KEY_Back)
+    return false;
+  gchar cc = (gchar) keyval;
+  Lisp_Object keyl = list1 (intern_c_string_1 (&cc, 1));
+  if (keystate & ctrl_modifier)
+    keyl = Fcons (Qcontrol, keyl);
+  if (keystate & meta_modifier)
+    keyl = Fcons (Qmeta, keyl);
+  if (keystate & hyper_modifier)
+    keyl = Fcons (Qhyper, keyl);
+  if (keystate & super_modifier)
+    keyl = Fcons (Qsuper, keyl);
+  if (keystate & shift_modifier)
+    keyl = Fcons (intern_c_string ("shift"), keyl);
+  if (keystate & alt_modifier)
+    keyl = Fcons (Qalt, keyl);
+  Lisp_Object keymap = call1 (intern_c_string ("key-binding"), make_vector (1, keyl));
+  if (!NILP (call1 (Qkeymapp, keymap)))
+    kmflag = 1;
+  else if (kmflag && !this_command_key_count)
+    {
+      kmflag = 0;
+      goto kfl;
+    }
+  if (kmflag)
+    {
+    kfl:;
+      union buffered_input_event inev;
+      EVENT_INIT (inev);
+      inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+      inev.ie.arg = Qnil;
+      inev.ie.code = XFIXNAT (make_fixnum ((int) c));
+      XSETFRAME (inev.ie.frame_or_window, f);
+      inev.ie.modifiers = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f),
+						       state);
+      inev.ie.timestamp = 0;
+      evq_enqueue ((void *) &inev);
+      if (!this_command_key_count)
+	zflag = 1;
+      else
+	zflag = 0;
+      if (!kmflag || zflag)
+	{
+	  zflag = 0;
+	  kmflag = 0;
+	  pgtk_focus_frame (f, false);
+	}
+    }
+  return kmflag;
+}
+
+static void
+embedded_entry_submit_cb (GtkEntry *entry,
+			  gpointer user_data)
+{
+  Lisp_Object arf = ((struct embedded_widget*) user_data)->callbacks;
+  if (Flistp (arf))
+    for (; CONSP (arf); arf = XCDR (arf))
+      if (FUNCTIONP (XCAR (arf)))
+	call1 (XCAR (arf), build_string_from_utf8
+	       (gtk_entry_buffer_get_text (gtk_entry_get_buffer (entry))));
+}
+
+Lisp_Object
+create_embedded_widget (Lisp_Object type, Lisp_Object frame)
+{
+  struct embedded_widget *widget = ALLOCATE_PSEUDOVECTOR (struct embedded_widget,
+							  callbacks,
+							  PVEC_EMBEDDED_WIDGET);
+  EMBEDDED_WIDGET_PVEC_INIT (widget);
+  if (EQ (type, intern_c_string ("label")))
+    {
+      widget->actual_widget = GTK_WIDGET (gtk_label_new (""));
+    }
+  else if (EQ (type, intern_c_string ("text")))
+    {
+      GtkTextView *entry = GTK_TEXT_VIEW (gtk_text_view_new ());
+      widget->actual_widget = GTK_WIDGET (entry);
+    }
+  else if (EQ (type, intern_c_string ("scrolled-window")))
+    {
+      GtkScrolledWindow *window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+      widget->actual_widget = GTK_WIDGET (window);
+    }
+  else if (EQ (type, intern_c_string ("video")))
+    {
+      GtkVideo *video = GTK_VIDEO (gtk_video_new ());
+      widget->actual_widget = GTK_WIDGET (video);
+    }
+  else if (EQ (type, intern_c_string ("button")))
+    {
+      GtkButton *btn = GTK_BUTTON (gtk_button_new ());
+      widget->actual_widget = GTK_WIDGET (btn);
+      g_signal_connect (G_OBJECT (btn), "clicked",
+			G_CALLBACK (embedded_widget_click_cb),
+			(gpointer) widget);
+    }
+  else if (EQ (type, intern_c_string ("vbox")))
+    {
+      widget->actual_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+    }
+  else if (EQ (type, intern_c_string ("hbox")))
+    {
+      widget->actual_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+    }
+  else if (EQ (type, intern_c_string ("entry")))
+    {
+      widget->actual_widget = gtk_entry_new ();
+      g_signal_connect (G_OBJECT (widget->actual_widget), "activate",
+			G_CALLBACK (embedded_entry_submit_cb),
+			(gpointer) widget);
+    }
+  else if (EQ (type, intern_c_string ("viewport")))
+    {
+      widget->actual_widget = gtk_viewport_new (NULL, NULL);
+    }
+  else if (EQ (type, intern_c_string ("frame")))
+    {
+      widget->actual_widget = gtk_frame_new ("");
+    }
+  else if (EQ (type, intern_c_string ("hseparator")))
+    {
+      widget->actual_widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+    }
+  else if (EQ (type, intern_c_string ("vseparator")))
+    {
+      widget->actual_widget = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+    }
+  else if (EQ (type, intern_c_string ("image")))
+    {
+      widget->actual_widget = gtk_image_new ();
+    }
+  else if (EQ (type, intern_c_string ("header-bar")))
+    {
+      widget->actual_widget = gtk_header_bar_new ();
+    }
+  else
+    {
+      widget->actual_widget = gtk_text_new ();
+      error ("Widget not supported: %s", SSDATA (SYMBOL_NAME (type)));
+    }
+  g_object_ref (widget->actual_widget);
+  widget->type = type;
+  widget->frame = frame;
+  widget->fx = make_fixnum (0);
+  widget->fy = make_fixnum (0);
+  widget->width = Qnil;
+  widget->height = Qnil;
+  widget->data_alist = Qnil;
+  widget->callbacks = Qnil;
+  widget->controller = gtk_event_controller_key_new ();
+  widget->controller_2 = GTK_EVENT_CONTROLLER (gtk_event_controller_legacy_new ());
+  gtk_event_controller_set_propagation_phase (widget->controller,
+					      GTK_PHASE_CAPTURE);
+  gtk_event_controller_set_propagation_phase (widget->controller_2,
+					      GTK_PHASE_CAPTURE);
+  gtk_widget_add_controller (widget->actual_widget, widget->controller);
+  gtk_widget_add_controller (widget->actual_widget, widget->controller_2);
+  g_signal_connect (G_OBJECT (widget->controller),
+		    "key-pressed",
+		    G_CALLBACK (embedded_key_cb),
+		    XFRAME (widget->frame));
+#ifndef FIX_GTK_LEGACY_HANDLER_BUG
+  g_signal_connect (G_OBJECT (widget->controller_2),
+		    "event",
+		    G_CALLBACK (embedded_grab_cb),
+		    XFRAME (widget->frame));
+#endif
+  g_object_set_data (G_OBJECT (widget->actual_widget),
+		     "embedded-widget-p", (gpointer) true);
+  g_object_ref (widget->controller);
+  g_object_ref (widget->controller_2);
+  Lisp_Object ret;
+  XSETGTKWIDGET (ret, widget);
+  return ret;
+}
+
+static void
+show_embedded_widget (struct frame *f, struct embedded_widget *widget)
+{
+  GtkOverlay *outer_overlay = FRAME_DECOR_OVERLAY (f);
+  gtk_overlay_add_overlay (outer_overlay, widget->actual_widget);
+}
+
+static void
+hide_embedded_widget (struct embedded_widget *widget)
+{
+  g_object_ref (widget->actual_widget);
+  gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget->actual_widget)),
+			widget->actual_widget);
+  g_object_unref (G_OBJECT (widget->actual_widget));
+}
+
+static void
+set_widget_parent (struct embedded_widget *parent,
+		   struct embedded_widget *child)
+{
+  if (parent->frame != child->frame)
+    error ("Cannot set embedded widget parents across frames");
+  if (!GTK_IS_CONTAINER (parent->actual_widget))
+    error ("Parent type is not a container");
+  gtk_container_add (GTK_CONTAINER (parent->actual_widget), child->actual_widget);
+}
+
+static void
+update_widget_params (struct embedded_widget *widget)
+{
+  GdkRectangle *rect = g_object_get_data (G_OBJECT (widget->actual_widget),
+					  EG_OVERLAY_ALLOC);
+  if (!rect)
+    {
+      rect = g_malloc (sizeof *rect);
+      g_object_set_data_full (G_OBJECT (widget->actual_widget),
+			      EG_OVERLAY_ALLOC, rect, g_free);
+    }
+  rect->x = XFIXNUM (widget->fx);
+  rect->y = XFIXNUM (widget->fy);
+  rect->height = !NILP (widget->height) ? XFIXNUM (widget->height) : -1;
+  rect->width = !NILP (widget->width) ? XFIXNUM (widget->width) : -1;
+}
+
+DEFUN ("gtk-text-set-text-buffer-contents",Fgtk_text_set_text_buffer_contents,
+       Sgtk_text_set_text_buffer_contents, 2, 2, 0,
+       doc: /* Set the contents of WIDGET to TEXT. */)
+  (Lisp_Object text, Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  CHECK_STRING (text);
+
+  if (!GTK_IS_TEXT_VIEW (XGTKWIDGET (widget)->actual_widget))
+    error ("Not an entry");
+
+  GtkTextBuffer *buf = gtk_text_buffer_new (NULL);
+  gtk_text_buffer_set_text (buf, SSDATA (ENCODE_UTF_8 (text)), XFIXNUM (Flength (text)));
+  gtk_text_view_set_buffer (GTK_TEXT_VIEW (XGTKWIDGET (widget)->actual_widget), buf);
+  return text;
+}
+
+DEFUN ("gtk-image-set-filename", Fgtk_image_set_filename, Sgtk_image_set_filename,
+       2, 2, 0, doc: /* Show the file FILE in the image widget WIDGET.  */)
+  (Lisp_Object file, Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  if (!GTK_IS_IMAGE (XGTKWIDGET (widget)->actual_widget))
+    error ("Not an image");
+  CHECK_STRING (file);
+  gtk_image_set_from_file (GTK_IMAGE (XGTKWIDGET (widget)->actual_widget),
+                           SSDATA (ENCODE_UTF_8 (file)));
+  return file;
+}
+
+DEFUN ("gtk-text-get-text-buffer-contents", Fgtk_text_get_text_buffer_contents,
+       Sgtk_text_get_text_bufer_contents, 1, 1, 0,
+       doc: /* Return the contents of the text widget WIDGET. */)
+  (Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  if (!GTK_IS_TEXT_VIEW (XGTKWIDGET (widget)->actual_widget))
+    error ("Not an text");
+  GtkTextIter iter;
+  GtkTextIter ter;
+  gtk_text_buffer_get_start_iter
+    (gtk_text_view_get_buffer
+     (GTK_TEXT_VIEW
+      (XGTKWIDGET (widget)->actual_widget)), &iter);
+  gtk_text_buffer_get_end_iter
+    (gtk_text_view_get_buffer
+     (GTK_TEXT_VIEW
+      (XGTKWIDGET (widget)->actual_widget)), &ter);
+  return build_string_from_utf8 (gtk_text_buffer_get_text
+				 (gtk_text_view_get_buffer
+				  (GTK_TEXT_VIEW (XGTKWIDGET (widget)->actual_widget)),
+				  &iter, &ter, false));
+}
+
+DEFUN ("set-gtk-widget-size", Fset_gtk_widget_size, Sset_gtk_widget_size,
+       5, 5, 0, doc: /* Set the size of WIDGET to X, Y by WIDTH, HEIGHT. */)
+  (Lisp_Object widget, Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height)
+{
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  if (!NILP (x))
+    CHECK_FIXNUM (x);
+  if (!NILP (y))
+    CHECK_FIXNUM (y);
+  if (!NILP (width))
+    CHECK_FIXNUM (width);
+  if (!NILP (height))
+    CHECK_FIXNUM (height);
+  struct embedded_widget *ewidget = XGTKWIDGET (widget);
+  ewidget->fx = x;
+  ewidget->fy = y;
+  ewidget->width = width;
+  ewidget->height = height;
+  update_widget_params (ewidget);
+  gtk_widget_queue_allocate (ewidget->actual_widget);
+  if (gtk_widget_get_parent (ewidget->actual_widget))
+    gtk_widget_queue_resize (gtk_widget_get_parent (ewidget->actual_widget));
+  return Qnil;
+}
+
+DEFUN ("set-gtk-widget-parent", Fset_gtk_widget_parent, Sset_gtk_widget_parent,
+       2, 2, 0, doc: /* Set WIDGET's parent to PARENT. */)
+  (Lisp_Object parent, Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (parent);
+  CHECK_GTKWIDGET (widget);
+  set_widget_parent (XGTKWIDGET (parent), XGTKWIDGET (widget));
+  return Qnil;
+}
+
+DEFUN ("show-gtk-widget", Fshow_gtk_widget, Sshow_gtk_widget, 1, 1, 0,
+       doc: /* Display WIDGET in WIDGET's frame. */)
+  (Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  update_widget_params (XGTKWIDGET (widget));
+  show_embedded_widget (XFRAME (XGTKWIDGET (widget)->frame),
+			XGTKWIDGET (widget));
+  return Qnil;
+}
+
+DEFUN ("hide-gtk-widget", Fhide_gtk_widget, Shide_gtk_widget, 1, 1, 0,
+       doc: /* Remove the widget WIDGET from it's frame. */)
+  (Lisp_Object widget)
+{
+  update_widget_params (XGTKWIDGET (widget));
+  hide_embedded_widget (XGTKWIDGET (widget));
+  return Qnil;
+}
+
+DEFUN ("make-gtk-widget", Fmake_gtk_widget, Smake_gtk_widget, 2, 2, 0,
+       doc: /* Create a GTK widget with the type TYPE, bound to
+the frame FRAME.  TYPE can be one of the following:
+  - label (A GtkLabel).
+  - vbox (A vertical GtkBox).
+  - text (A GtkTextView)
+  - video (A GtkVideo)
+  - button (A GtkButton)
+  - hbox (A horizontal GtkBox)
+  - entry (A GtkEntry)  */)
+  (Lisp_Object type, Lisp_Object frame)
+{
+  CHECK_LIVE_FRAME (frame);
+  return create_embedded_widget (type, frame);
+}
+
+DEFUN ("gtk-video-set-filename", Fgtk_video_set_filename, Sgtk_video_set_filename, 2, 2, 0,
+       doc: /* Open the file FILE in the media player widget WIDGET.  */)
+  (Lisp_Object file, Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  if (!GTK_IS_VIDEO (XGTKWIDGET (widget)->actual_widget))
+    error ("Not a video widget");
+  CHECK_STRING (file);
+  gtk_video_set_filename (GTK_VIDEO (XGTKWIDGET (widget)->actual_widget),
+			  SSDATA (ENCODE_UTF_8 (file)));
+  return file;
+}
+
+DEFUN ("gtk-label-set-text", Fgtk_label_set_text, Sgtk_label_set_text, 2, 3, 0,
+       doc: /* Set the contents of LABEL to the string TEXT, using markup if MARKUP is
+non-nil. */)
+  (Lisp_Object text, Lisp_Object label, Lisp_Object markup)
+{
+  CHECK_GTKWIDGET (label);
+  CHECK_LIVE_FRAME (XGTKWIDGET (label)->frame);
+  if (!GTK_IS_LABEL (XGTKWIDGET (label)->actual_widget))
+    error ("Not a label widegt");
+  CHECK_STRING (text);
+  if (NILP (markup))
+    gtk_label_set_text (GTK_LABEL (XGTKWIDGET (label)->actual_widget),
+			SSDATA (ENCODE_UTF_8 (text)));
+  else
+    gtk_label_set_markup (GTK_LABEL (XGTKWIDGET (label)->actual_widget),
+			  SSDATA (ENCODE_UTF_8 (text)));
+  return text;
+}
+
+DEFUN ("gtk-entry-set-text", Fgtk_entry_set_text, Sgtk_entry_set_text, 2, 2, 0,
+       doc: /* Set the contents of LABEL to the string TEXT */)
+  (Lisp_Object text, Lisp_Object entry)
+{
+  CHECK_GTKWIDGET (entry);
+  CHECK_LIVE_FRAME (XGTKWIDGET (entry)->frame);
+  if (!GTK_IS_ENTRY (XGTKWIDGET (entry)->actual_widget))
+    error ("Not an widegt");
+  CHECK_STRING (text);
+  gtk_entry_buffer_set_text (gtk_entry_get_buffer (GTK_ENTRY (XGTKWIDGET (entry)->actual_widget)),
+			     SSDATA (ENCODE_UTF_8 (text)), -1);
+  return text;
+}
+
+
+DEFUN ("gtk-entry-get-text", Fgtk_entry_get_text, Sgtk_entry_get_text, 1, 1, 0,
+       doc: /* Get the contents of the entry ENTRY; */)
+  (Lisp_Object entry)
+{
+  CHECK_GTKWIDGET (entry);
+  CHECK_LIVE_FRAME (XGTKWIDGET (entry)->frame);
+  if (!GTK_IS_ENTRY (XGTKWIDGET (entry)->actual_widget))
+    error ("Not an widegt");
+  return build_string_from_utf8
+    (gtk_entry_buffer_get_text
+     (gtk_entry_get_buffer
+      (GTK_ENTRY (XGTKWIDGET (entry)->actual_widget))));
+}
+
+DEFUN ("gtk-widget-put-callback", Fgtk_widget_put_callback, Sgtk_widget_put_callback,
+       2, 2, 0, doc: /* Attatch CALLBACK to WIDGET */)
+  (Lisp_Object callback, Lisp_Object widget)
+{
+  if (!FUNCTIONP (callback))
+    wrong_type_argument (intern_c_string ("functionp"), widget);
+  CHECK_GTKWIDGET (widget);
+  CHECK_LIVE_FRAME (XGTKWIDGET (widget)->frame);
+  XGTKWIDGET (widget)->callbacks =
+    Fcons (callback, XGTKWIDGET (widget)->callbacks);
+  return XGTKWIDGET (widget)->callbacks;
+}
+
+DEFUN ("gtk-button-set-text", Fgtk_button_set_text, Sgtk_button_set_text,
+       2, 2, 0, doc: /* Set BUTTON's text to TEXT. */)
+  (Lisp_Object text, Lisp_Object button)
+{
+  CHECK_GTKWIDGET (button);
+  CHECK_LIVE_FRAME (XGTKWIDGET (button)->frame);
+  CHECK_STRING (text);
+  if (!GTK_IS_BUTTON (XGTKWIDGET (button)->actual_widget))
+    error ("Not a button widget");
+  gtk_button_set_label (GTK_BUTTON (XGTKWIDGET (button)->actual_widget),
+			SSDATA (ENCODE_UTF_8 (text)));
+  return text;
+}
+
+DEFUN ("gtk-button-set-icon", Fgtk_button_set_icon, Sgtk_button_set_icon,
+       2, 2, 0, doc: /* Set BUTTON's icon name to ICON. */)
+  (Lisp_Object icon, Lisp_Object button)
+{
+  CHECK_GTKWIDGET (button);
+  CHECK_LIVE_FRAME (XGTKWIDGET (button)->frame);
+  CHECK_STRING (icon);
+  if (!GTK_IS_BUTTON (XGTKWIDGET (button)->actual_widget))
+    error ("Not a button widget");
+  gtk_button_set_icon_name (GTK_BUTTON (XGTKWIDGET (button)->actual_widget),
+			    SSDATA (ENCODE_UTF_8 (icon)));
+  return button;
+}
+
+DEFUN ("gtk-entry-set-hint", Fgtk_entry_set_hint, Sgtk_entry_set_hint, 2, 2, 0,
+       doc: /* Set ENTRY's hint to HINT.  */ attributes: const)
+  (Lisp_Object hint, Lisp_Object entry)
+{
+  CHECK_STRING (hint);
+  CHECK_GTKWIDGET (entry);
+  CHECK_LIVE_FRAME (XGTKWIDGET (entry)->frame);
+  if (!GTK_IS_ENTRY (XGTKWIDGET (entry)->actual_widget))
+    error ("Not an entry");
+  gtk_entry_set_placeholder_text (GTK_ENTRY (XGTKWIDGET (entry)->actual_widget),
+				  SSDATA (ENCODE_UTF_8 (hint)));
+  return hint;
+}
+
+DEFUN ("gtk-box-set-item-margin", Fgtk_box_set_item_margin, Sgtk_box_set_item_margin,
+       2, 2, 0, doc: /* Set the space between BOX's items to SPACE. */)
+  (Lisp_Object space, Lisp_Object box)
+{
+  CHECK_FIXNUM (space);
+  CHECK_GTKWIDGET (box);
+  struct embedded_widget *widget = XGTKWIDGET (box);
+  CHECK_LIVE_FRAME (widget->frame);
+  if (!GTK_IS_BOX (widget->actual_widget))
+    error ("Not a box");
+  gtk_box_set_spacing (GTK_BOX (widget->actual_widget), XFIXNUM (space));
+  return space;
+}
+
+DEFUN ("gtk-text-set-monospace", Fgtk_text_set_monospace, Sgtk_text_set_monospace,
+       2, 2, 0, doc: /* TEXT's text will be displayed in monospace if MONO is non-nil. */)
+  (Lisp_Object mono, Lisp_Object label)
+{
+  CHECK_GTKWIDGET (label);
+  struct embedded_widget *widget = XGTKWIDGET (label);
+  CHECK_LIVE_FRAME (widget->frame);
+  if (!GTK_IS_TEXT_VIEW (widget->actual_widget))
+    error ("Not a text");
+  gtk_text_view_set_monospace (GTK_TEXT_VIEW (widget->actual_widget), true);
+  return mono;
+}
+
+DEFUN ("gtk-frame-set-label", Fgtk_frame_set_label, Sgtk_frame_set_label,
+       2, 2, 0, doc: /* Set FRAME's label to LABEL. */)
+  (Lisp_Object label, Lisp_Object frame)
+{
+  CHECK_STRING (label);
+  CHECK_GTKWIDGET (frame);
+  struct embedded_widget *widget = XGTKWIDGET (frame);
+  if (!GTK_IS_FRAME (widget->actual_widget))
+    error ("Not a frame");
+  gtk_frame_set_label (GTK_FRAME (widget->actual_widget),
+		       SSDATA (ENCODE_UTF_8 (label)));
+  return label;
+}
+
+DEFUN ("gtkwidgetp", Fgtkwidgetp, Sgtkwidgetp, 1, 1, 0,
+       doc: /* Returns t if IT is a GTK widget, else nil. */)
+  (Lisp_Object it)
+{
+  return GTKWIDGETP (it) ? Qt : Qnil;
+}
+
+DEFUN ("gtk-widget-type", Fgtk_widget_type, Sgtk_widget_type,
+       1, 1, 0, doc: /* Return the GObject type name of the widget WIDGET. */)
+  (Lisp_Object widget)
+{
+  CHECK_GTKWIDGET (widget);
+  return
+    intern_c_string (G_OBJECT_TYPE_NAME (G_OBJECT (XGTKWIDGET (widget)->actual_widget)));
+}
+
+DEFUN ("gtk-header-bar-set-title", Fgtk_header_bar_set_title, Sgtk_header_bar_set_title,
+       2, 2, 0, doc: /* Set HEADER-BAR's title to TITLE. */)
+  (Lisp_Object title, Lisp_Object header_bar)
+{
+  CHECK_GTKWIDGET (header_bar);
+  CHECK_STRING (title);
+  if (!GTK_IS_HEADER_BAR (XGTKWIDGET (header_bar)->actual_widget))
+    error ("Not a header-bar widget");
+  gtk_header_bar_set_title (GTK_HEADER_BAR (XGTKWIDGET (header_bar)->actual_widget),
+			    SSDATA (ENCODE_UTF_8 (title)));
+  return title;
+}
+
+void
+syms_of_pgtkembed (void)
+{
+  DEFSYM (Qgtkwidgetp, "gtkwidgetp");
+  DEFSYM (Qembedded_widgets, "embedded-widgets");
+  defsubr (&Sgtkwidgetp);
+  defsubr (&Smake_gtk_widget);
+  defsubr (&Sshow_gtk_widget);
+  defsubr (&Shide_gtk_widget);
+  defsubr (&Sset_gtk_widget_size);
+  defsubr (&Sset_gtk_widget_parent);
+  defsubr (&Sgtk_video_set_filename);
+  defsubr (&Sgtk_text_get_text_bufer_contents);
+  defsubr (&Sgtk_text_set_text_buffer_contents);
+  defsubr (&Sgtk_label_set_text);
+  defsubr (&Sgtk_widget_put_callback);
+  defsubr (&Sgtk_button_set_text);
+  defsubr (&Sgtk_entry_set_text);
+  defsubr (&Sgtk_entry_set_hint);
+  defsubr (&Sgtk_box_set_item_margin);
+  defsubr (&Sgtk_button_set_icon);
+  defsubr (&Sgtk_text_set_monospace);
+  defsubr (&Sgtk_entry_get_text);
+  defsubr (&Sgtk_frame_set_label);
+  defsubr (&Sgtk_widget_type);
+  defsubr (&Sgtk_header_bar_set_title);
+  defsubr (&Sgtk_image_set_filename);
+  Fprovide (Qembedded_widgets, Qnil);
+}
diff --git a/src/pgtkembed.h b/src/pgtkembed.h
new file mode 100644
index 0000000000..05ae07ec66
--- /dev/null
+++ b/src/pgtkembed.h
@@ -0,0 +1,78 @@
+/* Support for embedding GTK (4) widgets in a frame
+
+   Copyright (C) 2011-2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "lisp.h"
+
+#include <gtk/gtk.h>
+
+struct embedded_widget
+{
+  union vectorlike_header header;
+
+  /* The frame this widget is attatched to */
+  Lisp_Object frame;
+  /* A symbol representing the type of this widget */
+  Lisp_Object type;
+  /* Auxiliary data */
+  Lisp_Object data_alist;
+  /* Frame-relative x */
+  Lisp_Object fx;
+  /* Frame-relative y */
+  Lisp_Object fy;
+  /* Width of this widget */
+  Lisp_Object width;
+  /* Height of this widget */
+  Lisp_Object height;
+  /* A list of functions that will be called on any event. */
+  Lisp_Object callbacks;
+  /* Here ends the Lisp section of this lisp_vectorlike. */
+  GtkWidget *actual_widget;
+  /* The motion event controller associated with this embedded widget. */
+  GtkEventController *controller;
+  /* Another motion event controller associated with this embedded widget */
+  GtkEventController *controller_2;
+} GCALIGNED_STRUCT;
+
+
+#define GTKWIDGETP(x) PSEUDOVECTORP (x, PVEC_EMBEDDED_WIDGET)
+#define XGTKWIDGET(x) (eassert (GTKWIDGETP (x)), \
+		       XUNTAG (x, Lisp_Vectorlike, struct embedded_widget))
+
+#define CHECK_GTKWIDGET(x) \
+  CHECK_TYPE (GTKWIDGETP (x), Qgtkwidgetp, x)
+
+
+enum { EMBEDDED_WIDGET_LISP_SIZE = PSEUDOVECSIZE (struct embedded_widget,
+						  callbacks) };
+enum { EMBEDDED_WIDGET_REST_SIZE = VECSIZE (struct embedded_widget)
+                                 - EMBEDDED_WIDGET_LISP_SIZE };
+
+INLINE void
+EMBEDDED_WIDGET_PVEC_INIT (struct embedded_widget *b)
+{
+  XSETPVECTYPESIZE (b,
+		    PVEC_EMBEDDED_WIDGET,
+		    EMBEDDED_WIDGET_LISP_SIZE,
+		    EMBEDDED_WIDGET_REST_SIZE);
+}
+
+extern void syms_of_pgtkembed (void);
+extern Lisp_Object
+create_embedded_widget (Lisp_Object type, Lisp_Object buffer);
diff --git a/src/pgtkevent.c b/src/pgtkevent.c
new file mode 100644
index 0000000000..8bad8c33b6
--- /dev/null
+++ b/src/pgtkevent.c
@@ -0,0 +1,517 @@
+/* Event handling for GTK 3 and GTK 4      -*- coding: utf-8 -*-
+
+Copyright (C) 2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "frame.h"
+#include "pgtkterm.h"
+
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+
+#include "pgtkevent.h"
+#include "blockinput.h"
+#include "dynlib.h"
+#include <math.h>
+
+typedef struct _EmacsGtkEventHandlerPrivate
+{
+  struct frame *f;
+} EmacsGtkEventHandlerPrivate;
+#if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG)
+G_DEFINE_TYPE_WITH_PRIVATE (EmacsGtkEventHandler, emacs_gtk_event_handler,
+                            GTK_TYPE_EVENT_CONTROLLER);
+#else
+G_DEFINE_TYPE_WITH_PRIVATE (EmacsGtkEventHandler, emacs_gtk_event_handler,
+			    G_TYPE_OBJECT);
+#endif
+
+#if defined (HAVE_GTK4) && !defined (FIX_GTK_LEGACY_HANDLER_BUG)
+static gboolean
+emacs_gtk_event_handler_handle_event (EmacsGtkEventHandler *handler,
+                                      GdkEvent *_ev, gdouble x, gdouble y);
+static gboolean
+emacs_gtk_event_handler_on_event (GtkEventControllerLegacy *controller,
+				  GdkEvent                 *event,
+				  gpointer                  user_data)
+{
+  return emacs_gtk_event_handler_handle_event (user_data, event, 0, 0);
+}
+#endif
+#ifdef HAVE_GTK4
+static gboolean
+scroll_cb (GtkEventControllerScroll *controller,
+	   gdouble                   dx,
+	   gdouble                   dy,
+	   gpointer                  user_data)
+{
+  EmacsGtkEventHandlerPrivate *priv
+    = emacs_gtk_event_handler_get_instance_private (((EmacsGtkEventHandler *) user_data));
+  if (((EmacsGtkEventHandler *) user_data)->scroll_event)
+    return ((EmacsGtkEventHandler *) user_data)
+      ->scroll_event (controller, dx, dy, priv->f);
+  return false;
+}
+
+static void
+drag_end_cb (EmacsGtkEventHandler *h,
+	     GdkEventSequence *sequence,
+             GtkGesture *gesture)
+{
+  Vpgtk_last_event_from_touchscreen = Qt;
+  h->s_flag = false;
+  h->hq_flag = false;
+}
+
+static void
+drag_update_cb (EmacsGtkEventHandler *h,
+		gdouble xo, gdouble yo,
+                GtkGesture *gesture)
+{
+  Vpgtk_last_event_from_touchscreen = Qt;
+  gdouble dt = fabs (xo) - fabs (yo);
+  if (dt < 1.0 && !h->se_blocked)
+    {
+      scroll_cb (NULL, 0, -yo * 0.5, h);
+      scroll_cb (NULL, -xo * 0.5, 0, h);
+    }
+  else if (!h->se_blocked)
+    scroll_cb (NULL, -xo * 0.5, -yo * 0.5, h);
+}
+
+static void
+drag_begin_cb (EmacsGtkEventHandler *h,
+	       gdouble start_x, gdouble start_y,
+               GtkGesture *gesture)
+{
+  Vpgtk_last_event_from_touchscreen = Qt;
+  EmacsGtkEventHandlerPrivate *priv =
+    emacs_gtk_event_handler_get_instance_private (h);
+  enum window_part w = 0;
+  window_from_coordinates (priv->f, (int) round (start_x),
+			   (int) round (start_y), &w, 0, 0);
+  if (w != ON_TEXT)
+    {
+      h->se_blocked = true;
+      emacs_gtk_event_handler_toggle_scroll_evs (h, 0);
+    }
+  if (h->se_blocked)
+    return;
+  h->dsy = start_y;
+  h->dsx = start_x;
+  h->hq_flag = true;
+}
+
+static void
+long_press_cb (GtkGestureLongPress *p, gdouble x, gdouble y,
+               EmacsGtkEventHandler *h)
+{
+  Vpgtk_last_event_from_touchscreen = Qt;
+  if (h->long_press_event)
+    h->long_press_event (((EmacsGtkEventHandlerPrivate *)
+                            emacs_gtk_event_handler_get_instance_private (h))->f, x, y);
+}
+#endif
+
+
+#ifdef HAVE_GTK4
+static void
+motion_enter (GtkEventControllerMotion *controller,
+	      gdouble                   x,
+	      gdouble                   y,
+	      GdkCrossingMode           mode,
+	      gpointer                  user_data)
+{
+  EmacsGtkEventHandler *h = user_data;
+  EmacsGtkEventHandlerPrivate *priv
+    = emacs_gtk_event_handler_get_instance_private (h);
+  h->enter (h->widget, mode, priv->f);
+}
+static void
+motion_leave (GtkEventControllerMotion *controller,
+	      GdkCrossingMode           mode,
+	      gpointer                  user_data)
+{
+  EmacsGtkEventHandler *h = user_data;
+  EmacsGtkEventHandlerPrivate *priv
+    = emacs_gtk_event_handler_get_instance_private (h);
+#ifdef HAVE_GTK4
+  if (FRAME_X_OUTPUT (priv->f)->ttip_popover)
+    return;
+#endif
+  h->leave (h->widget, mode, priv->f);
+}
+#endif
+
+static void
+emacs_gtk_event_handler_init (EmacsGtkEventHandler *handler)
+{
+#if defined (HAVE_GTK4) && !defined (FIX_GTK_LEGACY_HANDLER_BUG)
+  handler->legacy = GTK_EVENT_CONTROLLER_LEGACY (gtk_event_controller_legacy_new ());
+  g_signal_connect_after (G_OBJECT (handler->legacy),
+			  "event",
+			  G_CALLBACK (emacs_gtk_event_handler_on_event),
+			  handler);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->legacy),
+					      GTK_PHASE_BUBBLE);
+#endif
+#ifdef HAVE_GTK4
+  handler->s_flag = false;
+  handler->hq_flag = false;
+  handler->se_blocked = false;
+  handler->scroll = GTK_EVENT_CONTROLLER_SCROLL
+    (gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES
+				      | GTK_EVENT_CONTROLLER_SCROLL_KINETIC));
+  handler->drag = gtk_gesture_drag_new ();
+  handler->lpress = gtk_gesture_long_press_new ();
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (handler->drag), true);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (handler->lpress), true);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->lpress),
+					      GTK_PHASE_CAPTURE);
+
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->scroll),
+					      GTK_PHASE_CAPTURE);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->drag),
+					      GTK_PHASE_CAPTURE);
+  g_signal_connect (G_OBJECT (handler->lpress),
+		    "pressed", G_CALLBACK (long_press_cb),
+		    handler);
+  g_signal_connect (G_OBJECT (handler->scroll),
+		    "scroll", G_CALLBACK (scroll_cb), handler);
+  g_signal_connect_swapped (handler->drag, "drag-begin",
+                            G_CALLBACK (drag_begin_cb),
+                            handler);
+  g_signal_connect_swapped (handler->drag, "drag-update",
+                            G_CALLBACK (drag_update_cb),
+                            handler);
+  g_signal_connect_swapped (handler->drag, "end",
+                            G_CALLBACK (drag_end_cb),
+                            handler);
+  handler->focus = (GtkEventControllerMotion *) gtk_event_controller_motion_new ();
+  g_signal_connect (G_OBJECT (handler->focus), "enter",
+		    G_CALLBACK (motion_enter), handler);
+  g_signal_connect (G_OBJECT (handler->focus), "leave",
+		    G_CALLBACK (motion_leave), handler);
+
+  g_object_ref (handler->focus);
+  g_object_ref (handler->drag);
+  g_object_ref (handler->lpress);
+  g_object_ref (handler->scroll);
+#endif
+}
+
+#ifdef HAVE_GTK4
+static gboolean
+gtk_simulate_touchscreen (void)
+{
+  static dynlib_handle_ptr dyn = NULL;
+  if (!dyn)
+    if (!(dyn = dynlib_open ("libgtk-4")))
+      return false;
+  gboolean (* fn) (void) = dynlib_sym (dyn, "gtk_simulate_touchscreen");
+  if (!fn)
+    return gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN;
+  return fn ();
+}
+
+static void
+emacs_gtk_event_handler_dispose (GObject *o)
+{
+  EmacsGtkEventHandler *h = (gpointer) o;
+  gtk_widget_remove_controller (h->widget, (gpointer) h->focus);
+  gtk_widget_remove_controller (h->widget, (gpointer) h->scroll);
+  gtk_widget_remove_controller (h->widget, (gpointer) h->drag);
+  gtk_widget_remove_controller (h->widget, (gpointer) h->lpress);
+  g_clear_pointer (&h->focus, g_object_unref);
+  g_clear_pointer (&h->scroll, g_object_unref);
+  g_clear_pointer (&h->drag, g_object_unref);
+  g_clear_pointer (&h->lpress, g_object_unref);
+  G_OBJECT_CLASS (emacs_gtk_event_handler_parent_class)->dispose (o);
+}
+#endif
+
+void
+emacs_gtk_event_handler_set_widget (EmacsGtkEventHandler *handler, GtkWidget *w)
+{
+#ifdef HAVE_GTK4
+  if (handler ==
+      FRAME_GTK_EV_HANDLER
+      (((EmacsGtkEventHandlerPrivate *) emacs_gtk_event_handler_get_instance_private (handler))->f))
+    {
+      gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->scroll));
+      gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->drag));
+      gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->lpress));
+      gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->focus));
+    }
+#endif
+#if !defined (HAVE_GTK4)
+  GTK_EVENT_CONTROLLER_CLASS (G_OBJECT_GET_CLASS (G_OBJECT (handler)))
+    ->set_widget (GTK_EVENT_CONTROLLER (handler), w);
+#elif !defined (FIX_GTK_LEGACY_HANDLER_BUG)
+  gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler->legacy));
+#else
+  gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (handler));
+#endif
+  handler->widget = w;
+}
+
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#ifndef HAVE_GTK4
+static gboolean
+emacs_gtk_event_handler_handle_event (GtkEventController *_handler, GdkEvent *ev)
+#else
+static gboolean
+emacs_gtk_event_handler_handle_event (
+#ifdef FIX_GTK_LEGACY_HANDLER_BUG
+				      GtkEventController
+#else
+				      EmacsGtkEventHandler
+#endif
+				      *_handler, GdkEvent *ev,
+				      gdouble x, gdouble y)
+#endif
+{
+  block_input ();
+  EmacsGtkEventHandler *handler = (gpointer) _handler;
+  EmacsGtkEventHandlerPrivate *priv
+    = emacs_gtk_event_handler_get_instance_private (handler);
+#ifdef HAVE_GTK4
+  if (handler->s_flag)
+    return false;
+  if (handler->se_blocked || gdk_device_get_source (gdk_event_get_device (ev)) ==
+      GDK_SOURCE_TOUCHSCREEN || gtk_simulate_touchscreen ())
+    Vpgtk_last_event_from_touchscreen = Qt;
+  else
+    Vpgtk_last_event_from_touchscreen = Qnil;
+  if (!GTK_IS_ROOT (handler->widget))
+    gtk_window_set_focus (GTK_WINDOW (gtk_widget_get_root (handler->widget)),
+			  handler->widget);
+#endif
+  switch (gdk_event_get_event_type (ev))
+    {
+    case GDK_BUTTON_PRESS:
+      if (handler->button_down
+          && handler->button_down (handler->widget, ev, priv->f))
+	{
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+    case GDK_BUTTON_RELEASE:
+      if (handler->button_up
+          && handler->button_up (handler->widget, ev, priv->f))
+	  {
+	    unblock_input ();
+	    return true;
+	  }
+      else
+	goto def;
+    case GDK_KEY_PRESS:
+      if (handler->key_down && handler->key_down (handler->widget, ev, priv->f))
+        {
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+    case GDK_KEY_RELEASE:
+      if (handler->key_up && handler->key_up (handler->widget, ev, priv->f))
+        {
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+#ifndef HAVE_GTK4
+    case GDK_MAP:
+      if (handler->map_event
+          && handler->map_event (handler->widget, ev, priv->f))
+        {
+	  unblock_input ();
+	  return true;
+	}
+    case GDK_WINDOW_STATE:
+      if (handler->window_state_event
+          && handler->window_state_event (handler->widget, ev, priv->f))
+        {
+	  unblock_input ();
+	  return true;
+	}
+#endif
+    case GDK_DELETE:
+      if (handler->delete_event
+          && handler->delete_event (handler->widget, ev, priv->f))
+        {
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+#ifndef HAVE_GTK4
+    case GDK_SCROLL:
+      if (handler->scroll_event
+	  && handler->scroll_event (handler->widget, ev, priv->f))
+	{
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+#endif
+#ifndef HAVE_GTK4
+    case GDK_SELECTION_CLEAR:
+      if (handler->selection_lost
+          && handler->selection_lost (handler->widget, (GdkEventSelection *) ev,
+                                      priv->f))
+        {
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+#endif
+    case GDK_CONFIGURE:
+      if (handler->configure
+          && handler->configure (handler->widget, ev, priv->f))
+	{
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+#ifndef HAVE_GTK4
+    case GDK_EXPOSE:
+    case GDK_DAMAGE:
+      if (handler->expose && handler->expose (handler->widget, (GdkEventExpose *) ev, 0))
+        {
+	  unblock_input ();
+	  return true;
+	}
+#endif
+    case GDK_FOCUS_CHANGE:
+#ifdef HAVE_GTK4
+      {
+	if (gdk_focus_event_get_in (ev))
+#else
+	if (ev->focus_change.in)
+#endif
+	  if (handler->focus_in && handler->focus_in (handler->widget, ev, priv->f))
+	    {
+	      unblock_input ();
+	      return false;
+	    }
+	  else
+            goto def;
+        else
+#ifdef HAVE_GTK4
+	  {
+            if (handler->focus_out
+                && handler->focus_out (handler->widget, ev, priv->f))
+              {
+                unblock_input ();
+                return false;
+              }
+            else
+              goto def;
+          }
+#endif
+#ifdef HAVE_GTK4
+      }
+    case GDK_ENTER_NOTIFY:
+      if (handler->enter_notify &&
+	  handler->enter_notify (handler->widget, ev, priv->f))
+	{
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+    case GDK_LEAVE_NOTIFY:
+      if (handler->leave_notify &&
+	  handler->leave_notify (handler->widget, ev, priv->f))
+	{
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+    case GDK_MOTION_NOTIFY:
+      if (handler->motion_notify &&
+	  handler->motion_notify (handler->widget, ev, priv->f))
+	{
+	  unblock_input ();
+	  return true;
+	}
+      else
+	goto def;
+#endif
+    default:
+    def:
+      unblock_input ();
+      return false;
+    }
+}
+
+#pragma GCC diagnostic push
+
+static void
+emacs_gtk_event_handler_class_init (EmacsGtkEventHandlerClass *clazz)
+{
+#if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG)
+  GtkEventControllerClass *cc = GTK_EVENT_CONTROLLER_CLASS (clazz);
+  cc->handle_event = emacs_gtk_event_handler_handle_event;
+#endif
+#ifdef HAVE_GTK4
+  G_OBJECT_CLASS (clazz)->dispose = emacs_gtk_event_handler_dispose;
+#endif
+}
+
+
+EmacsGtkEventHandler *
+emacs_gtk_event_handler_new (struct frame *f)
+{
+  EmacsGtkEventHandler *handler
+    = (EmacsGtkEventHandler *) g_type_create_instance (EMACS_TYPE_GTK_EVENT_HANDLER);
+  EmacsGtkEventHandlerPrivate *priv
+    = emacs_gtk_event_handler_get_instance_private (handler);
+  priv->f = f;
+  return handler;
+}
+
+
+#ifdef HAVE_GTK4
+void
+emacs_gtk_event_handler_toggle_scroll_evs (EmacsGtkEventHandler *handler,
+					   bool enable)
+{
+  handler->se_blocked = !enable;
+  if (enable)
+    {
+      gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->scroll),
+						  GTK_PHASE_CAPTURE);
+    }
+  else
+    {
+      gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (handler->scroll),
+						  GTK_PHASE_NONE);
+    }
+}
+#endif
+G_END_DECLS
+
+#endif /* HAVE_GTK_EVENT_CONTROLLER */
diff --git a/src/pgtkevent.h b/src/pgtkevent.h
new file mode 100644
index 0000000000..45c8b34086
--- /dev/null
+++ b/src/pgtkevent.h
@@ -0,0 +1,232 @@
+/* Event handling for GTK 3 and (in the future) GTK 4      -*- coding: utf-8 -*-
+
+Copyright (C) 2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef __PGTK_EVENT_H
+#define __PGTK_EVENT_H
+
+#include "config.h"
+#include "gtkconfig.h"
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_CONTROLLER_BUG)
+/* ATTENTION *
+ * If Emacs fails to compile, or throws weird
+ * errors during runtime, make sure to update
+ * this section with the contents of gtkeventcontrollerprivate.h
+ * from the GTK version you're using
+ */
+#ifndef HAVE_GTK4
+struct _GtkEventController
+{
+  GObject parent_instance;
+};
+
+struct _GtkEventControllerClass
+{
+  GObjectClass parent_class;
+
+  void     (* set_widget)   (GtkEventController *controller,
+                             GtkWidget          *widget);
+  void     (* unset_widget) (GtkEventController *controller);
+  gboolean (* handle_event) (GtkEventController *controller,
+                             const GdkEvent     *event);
+  void     (* reset)        (GtkEventController *controller);
+
+  /*<private>*/
+
+  /* Tells whether the event is filtered out, %TRUE makes
+   * the event unseen by the handle_event vfunc.
+   */
+  gboolean (* filter_event) (GtkEventController *controller,
+                             const GdkEvent     *event);
+  gpointer padding[10];
+};
+#else
+typedef enum {
+  GTK_CROSSING_FOCUS,
+  GTK_CROSSING_POINTER,
+  GTK_CROSSING_DROP
+} GtkCrossingType;
+
+typedef enum {
+  GTK_CROSSING_IN,
+  GTK_CROSSING_OUT
+} GtkCrossingDirection;
+
+typedef struct _GtkCrossingData GtkCrossingData;
+
+/**
+ * GtkCrossingData:
+ * @type: the type of crossing event
+ * @direction: whether this is a focus-in or focus-out event
+ * @mode: the crossing mode
+ * @old_target: the old target
+ * @old_descendent: the direct child of the receiving widget that
+ *     is an ancestor of @old_target, or %NULL if @old_target is not
+ *     a descendent of the receiving widget
+ * @new_target: the new target
+ * @new_descendent: the direct child of the receiving widget that
+ *     is an ancestor of @new_target, or %NULL if @new_target is not
+ *     a descendent of the receiving widget
+ * @drop: the #GdkDrop if this is info for a drop operation
+ *
+ * The struct that is passed to gtk_event_controller_handle_crossing().
+ *
+ * The @old_target and @new_target fields are set to the old or new
+ * focus, drop or hover location.
+ */
+struct _GtkCrossingData {
+  GtkCrossingType type;
+  GtkCrossingDirection direction;
+  GdkCrossingMode mode;
+  GtkWidget *old_target;
+  GtkWidget *old_descendent;
+  GtkWidget *new_target;
+  GtkWidget *new_descendent;
+  GdkDrop *drop;
+};
+
+struct _GtkEventController
+{
+  GObject parent_instance;
+};
+
+struct _GtkEventControllerClass
+{
+  GObjectClass parent_class;
+
+  void     (* set_widget)   (GtkEventController *controller,
+                             GtkWidget          *widget);
+  void     (* unset_widget) (GtkEventController *controller);
+  gboolean (* handle_event) (GtkEventController *controller,
+                             GdkEvent            *event,
+                             double              x,
+                             double              y);
+  void     (* reset)        (GtkEventController *controller);
+
+  void     (* handle_crossing) (GtkEventController    *controller,
+                                const GtkCrossingData *crossing,
+                                double                 x,
+                                double                 y);
+
+  /*<private>*/
+
+  /* Tells whether the event is filtered out, %TRUE makes
+   * the event unseen by the handle_event vfunc.
+   */
+  gboolean (* filter_event) (GtkEventController *controller,
+                             GdkEvent           *event);
+
+  gpointer padding[10];
+};
+
+#endif
+#endif
+struct _EmacsGtkEventHandler
+{
+#if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG)
+  GtkEventController parent_instance;
+#else
+  GObject parent_instance;
+#endif
+#ifdef HAVE_GTK4
+  // FIXME stupid hack
+  gchar padding[1280];
+#endif
+  GtkWidget *widget;
+  gboolean (*key_down) (GtkWidget *w, GdkEvent *ev, struct frame *frame);
+  gboolean (*key_up) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+  gboolean (*button_down) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+  gboolean (*button_up) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+  gboolean (*window_state_event) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+  gboolean (*delete_event) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+  gboolean (*map_event) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+  gboolean (*size_allocate) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+#ifndef HAVE_GTK4
+  gboolean (*scroll_event) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+#else
+  gboolean (*scroll_event) (GtkEventControllerScroll *widget,
+			    gdouble dx,
+			    gdouble dy,
+			    struct frame *_);
+  gboolean (*long_press_event) (struct frame *f, gdouble ex, gdouble ey);
+#endif
+#ifndef HAVE_GTK4
+  gboolean (*selection_lost) (GtkWidget *w, GdkEventSelection *ev, struct frame *f);
+#endif
+  gboolean (*configure) (GtkWidget *w, GdkEvent *ev, struct frame *f);
+#ifndef HAVE_GTK4
+  gboolean (*expose) (GtkWidget *w, GdkEventExpose *ev, gpointer ignored);
+#endif
+  gboolean (*focus_in) (GtkWidget *w, GdkEvent *ev, gpointer frame_ptr);
+  gboolean (*focus_out) (GtkWidget *w, GdkEvent *ev, gpointer frame_ptr);
+#ifdef HAVE_GTK4
+  gboolean (*enter_notify) (GtkWidget *w, GdkEvent *ev, gpointer frame_ptr);
+  gboolean (*leave_notify) (GtkWidget *w, GdkEvent *ev, gpointer frame_ptr);
+  gboolean (*motion_notify) (GtkWidget *w, GdkEvent *ev, gpointer frame_ptr);
+  void (*enter) (GtkWidget *w, GdkCrossingMode cm, struct frame *f);
+  void (*leave) (GtkWidget *w, GdkCrossingMode cm, struct frame *f);
+#ifndef FIX_GTK_LEGACY_HANDLER_BUG
+  GtkEventControllerLegacy *legacy;
+#endif
+#ifdef HAVE_GTK4
+  GtkEventControllerScroll *scroll;
+  GtkEventControllerMotion *focus;
+  GtkGesture *drag;
+  GtkGesture *lpress;
+  gdouble dsy, dsx;
+  bool_bf s_flag;
+  bool_bf hq_flag;
+  bool_bf se_blocked;
+#endif
+#endif
+};
+
+#define EMACS_TYPE_GTK_EVENT_HANDLER (emacs_gtk_event_handler_get_type ())
+
+G_DECLARE_FINAL_TYPE (EmacsGtkEventHandler,
+		      emacs_gtk_event_handler,
+		      EMACS,
+#if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG)
+		      GTK_EVENT_HANDLER
+#else
+		      G_OBJECT
+#endif
+		      ,
+#if !defined (HAVE_GTK4) || defined (FIX_GTK_LEGACY_HANDLER_BUG)
+		      GtkEventController
+#else
+		      GObject
+#endif
+		      )
+
+EmacsGtkEventHandler *emacs_gtk_event_handler_new (struct frame *f);
+
+void
+emacs_gtk_event_handler_set_widget (EmacsGtkEventHandler *handler, GtkWidget *w);
+
+#ifdef HAVE_GTK4
+void
+emacs_gtk_event_handler_toggle_scroll_evs (EmacsGtkEventHandler *handler,
+					   bool enable);
+#endif
+G_END_DECLS
+
+#endif /* __PGTK_EVENT_H */
diff --git a/src/pgtkfns.c b/src/pgtkfns.c
new file mode 100644
index 0000000000..8d9297b42c
--- /dev/null
+++ b/src/pgtkfns.c
@@ -0,0 +1,3964 @@
+/* Functions for the pure Gtk+-3.
+
+Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2018 Free Software
+Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This should be the first include, as it may set up #defines affecting
+   interpretation of even the system includes. */
+#include <config.h>
+
+#include <c-strcase.h>
+#include <math.h>
+
+#include "lisp.h"
+#include "blockinput.h"
+#include "buffer.h"
+#include "character.h"
+#include "font.h"
+#include "fontset.h"
+#ifndef HAVE_GTK4
+#include "gtkutil.h"
+#else
+#include "pgtksubr.h"
+#endif
+#include "keyboard.h"
+#include "termhooks.h"
+#include "window.h"
+#include "xsettings.h"
+
+#ifdef HAVE_GTK3
+#include "pgtkevent.h"
+#endif
+#include "pgtkterm.h"
+
+#ifdef HAVE_PGTK
+
+// static EmacsTooltip *pgtk_tooltip = nil;
+
+/* Static variables to handle applescript execution.  */
+static Lisp_Object as_script, *as_result;
+static int as_status;
+
+static ptrdiff_t image_cache_refcount;
+
+static int x_decode_color (struct frame *f, Lisp_Object color_name,
+                           int mono_color);
+static struct pgtk_display_info *pgtk_display_info_for_name (Lisp_Object);
+static void pgtk_set_name_as_filename (struct frame *);
+
+static const char *pgtk_app_name = "Emacs";
+
+/* ==========================================================================
+
+    Internal utility functions
+
+   ========================================================================== */
+
+struct pgtk_display_info *
+check_pgtk_display_info (Lisp_Object object)
+{
+  struct pgtk_display_info *dpyinfo = NULL;
+
+  if (NILP (object))
+    {
+      struct frame *sf = XFRAME (selected_frame);
+
+      if (FRAME_PGTK_P (sf) && FRAME_LIVE_P (sf))
+        dpyinfo = FRAME_DISPLAY_INFO (sf);
+      else if (x_display_list != 0)
+        dpyinfo = x_display_list;
+      else
+        error ("Frames are not in use or not initialized");
+    }
+  else if (TERMINALP (object))
+    {
+      struct terminal *t = decode_live_terminal (object);
+
+      if (t->type != output_pgtk)
+        error ("Terminal %d is not a display", t->id);
+
+      dpyinfo = t->display_info.pgtk;
+    }
+  else if (STRINGP (object))
+    dpyinfo = pgtk_display_info_for_name (object);
+  else
+    {
+      struct frame *f = decode_window_system_frame (object);
+      dpyinfo = FRAME_DISPLAY_INFO (f);
+    }
+
+  return dpyinfo;
+}
+
+/* Return the X display structure for the display named NAME.
+   Open a new connection if necessary.  */
+static struct pgtk_display_info *
+pgtk_display_info_for_name (Lisp_Object name)
+{
+  struct pgtk_display_info *dpyinfo;
+
+  CHECK_STRING (name);
+
+  for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
+    if (!NILP (Fstring_equal (XCAR (dpyinfo->name_list_element), name)))
+      return dpyinfo;
+
+  /* Use this general default value to start with.  */
+  Vx_resource_name = Vinvocation_name;
+
+  validate_x_resource_name ();
+
+  dpyinfo = pgtk_term_init (name, SSDATA (Vx_resource_name));
+
+  if (dpyinfo == 0)
+    error ("Cannot connect to display server %s", SDATA (name));
+
+  XSETFASTINT (Vwindow_system_version, 11);
+
+  return dpyinfo;
+}
+
+/* ==========================================================================
+
+    Frame parameter setters
+
+   ========================================================================== */
+
+static void
+x_set_foreground_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  Emacs_Color col;
+
+  /* Must block_input, because pgtk_lisp_to_color does block/unblock_input
+     which means that col may be deallocated in its unblock_input if there
+     is user input, unless we also block_input.  */
+  block_input ();
+  if (pgtk_lisp_to_color_with_frame (arg, &col, f))
+    {
+      store_frame_param (f, Qforeground_color, oldval);
+      unblock_input ();
+      error ("Unknown color");
+    }
+
+  FRAME_X_OUTPUT (f)->foreground_color = col.pixel;
+
+  FRAME_FOREGROUND_PIXEL (f) = col.pixel;
+
+  if (FRAME_GTK_WIDGET (f))
+    {
+      update_face_from_frame_parameter (f, Qforeground_color, arg);
+      /*recompute_basic_faces (f); */
+      if (FRAME_VISIBLE_P (f))
+        SET_FRAME_GARBAGED (f);
+    }
+  unblock_input ();
+}
+
+static void
+x_set_background_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  Emacs_Color col;
+
+  block_input ();
+  if (pgtk_lisp_to_color_with_frame (arg, &col, f))
+    {
+      store_frame_param (f, Qbackground_color, oldval);
+      unblock_input ();
+      error ("Unknown color");
+    }
+
+  /* clear the frame */
+  if (FRAME_VISIBLE_P (f))
+    pgtk_clear_frame (f);
+
+  PGTK_TRACE ("x_set_background_color: col.pixel=%08lx.", col.pixel);
+  FRAME_X_OUTPUT (f)->background_color = col.pixel;
+  FRAME_BACKGROUND_PIXEL (f)
+    = ARGB_TO_ULONG ((unsigned int) (0xff), (unsigned int) (col.red >> 8),
+                     (unsigned int) (col.green >> 8),
+                     (unsigned int) (col.blue >> 8));
+#ifndef HAVE_GTK4
+  xg_set_background_color (f, col.pixel);
+#else
+  egtk_set_background_color (f, col.pixel);
+#endif
+
+  update_face_from_frame_parameter (f, Qbackground_color, arg);
+
+  PGTK_TRACE ("visible_p=%d.", FRAME_VISIBLE_P (f));
+  if (FRAME_VISIBLE_P (f))
+    SET_FRAME_GARBAGED (f);
+
+  unblock_input ();
+}
+
+static void
+x_set_border_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  int pix;
+#ifdef HAVE_GTK3
+  CHECK_STRING (arg);
+  pix = x_decode_color (f, arg, BLACK_PIX_DEFAULT (f));
+  FRAME_X_OUTPUT (f)->border_pixel = pix;
+  pgtk_frame_rehighlight (FRAME_DISPLAY_INFO (f));
+#endif
+}
+
+static void
+x_set_cursor_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  unsigned long fore_pixel, pixel;
+  struct pgtk_output *x = f->output_data.pgtk;
+  Emacs_Color col;
+
+  if (!NILP (Vx_cursor_fore_pixel))
+    {
+      if (pgtk_lisp_to_color_with_frame (Vx_cursor_fore_pixel, &col, f))
+        signal_error ("Undefined color", Vx_cursor_fore_pixel);
+      fore_pixel = col.pixel;
+    }
+  else
+    fore_pixel = FRAME_BACKGROUND_PIXEL (f);
+
+  if (pgtk_lisp_to_color_with_frame (arg, &col, f))
+    signal_error ("Undefined color", arg);
+  pixel = col.pixel;
+
+  /* Make sure that the cursor color differs from the background color.  */
+  if (pixel == FRAME_BACKGROUND_PIXEL (f))
+    {
+      pixel = x->mouse_color;
+      if (pixel == fore_pixel)
+        {
+          fore_pixel = FRAME_BACKGROUND_PIXEL (f);
+        }
+    }
+
+  x->cursor_foreground_color = fore_pixel;
+  x->cursor_color = pixel;
+
+  if (FRAME_X_WINDOW (f) != 0)
+    {
+      x->cursor_xgcv.background = x->cursor_color;
+      x->cursor_xgcv.foreground = fore_pixel;
+
+      if (FRAME_VISIBLE_P (f))
+        {
+          gui_update_cursor (f, false);
+          gui_update_cursor (f, true);
+        }
+    }
+
+  update_face_from_frame_parameter (f, Qcursor_color, arg);
+}
+
+static void
+x_set_icon_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (f);
+  PGTK_TRACE ("x_set_icon_name");
+  if (FRAME_PARENT_FRAME (f))
+    return;
+  /* see if it's changed */
+  if (STRINGP (arg))
+    {
+      if (STRINGP (oldval) && EQ (Fstring_equal (oldval, arg), Qt))
+        return;
+    }
+  else if (!STRINGP (oldval) && EQ (oldval, Qnil) == EQ (arg, Qnil))
+    return;
+
+  fset_icon_name (f, arg);
+
+  if (NILP (arg))
+    {
+      if (!NILP (f->title))
+        arg = f->title;
+      else
+        /* Explicit name and no icon-name -> explicit_name.  */
+        if (f->explicit_name)
+        arg = f->name;
+      else
+        {
+          /* No explicit name and no icon-name ->
+             name has to be rebuild from icon_title_format.  */
+          windows_or_buffers_changed = 67;
+          return;
+        }
+    }
+
+  gtk_window_set_icon_name (GTK_WINDOW (widget), SSDATA (arg));
+}
+
+static void
+pgtk_set_name_internal (struct frame *f, Lisp_Object name)
+{
+  Lisp_Object encoded_name, encoded_icon_name;
+  GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (f);
+#ifdef HAVE_GTK4
+  if (!GTK_IS_WINDOW (widget))
+    {
+      gtk_widget_set_name (widget, SSDATA (ENCODE_UTF_8 (name)));
+      return;
+    }
+#endif
+  encoded_name = ENCODE_UTF_8 (name);
+  gtk_window_set_title (GTK_WINDOW (widget), SSDATA (encoded_name));
+
+  if (!STRINGP (f->icon_name))
+    encoded_icon_name = encoded_name;
+  else
+    encoded_icon_name = ENCODE_UTF_8 (f->icon_name);
+  gtk_window_set_icon_name (GTK_WINDOW (widget), SSDATA (encoded_icon_name));
+}
+
+static void
+pgtk_set_name (struct frame *f, Lisp_Object name, int explicit)
+{
+  PGTK_TRACE ("pgtk_set_name");
+
+  /* Make sure that requests from lisp code override requests from
+     Emacs redisplay code.  */
+  if (explicit)
+    {
+      /* If we're switching from explicit to implicit, we had better
+         update the mode lines and thereby update the title.  */
+      if (f->explicit_name && NILP (name))
+        update_mode_lines = 12;
+
+      f->explicit_name = !NILP (name);
+    }
+  else if (f->explicit_name)
+    return;
+
+  if (NILP (name))
+    name = build_string (pgtk_app_name);
+  else
+    CHECK_STRING (name);
+
+  /* Don't change the name if it's already NAME.  */
+  if (!NILP (Fstring_equal (name, f->name)))
+    return;
+
+  fset_name (f, name);
+
+  /* Title overrides explicit name.  */
+  if (!NILP (f->title))
+    name = f->title;
+
+  pgtk_set_name_internal (f, name);
+}
+
+/* This function should be called when the user's lisp code has
+   specified a name for the frame; the name will override any set by the
+   redisplay code.  */
+static void
+x_explicitly_set_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  PGTK_TRACE ("x_explicitly_set_name");
+  pgtk_set_name (f, arg, 1);
+}
+
+/* This function should be called by Emacs redisplay code to set the
+   name; names set this way will never override names set by the user's
+   lisp code.  */
+void
+pgtk_implicitly_set_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  PGTK_TRACE ("x_implicitly_set_name");
+
+  Lisp_Object frame_title
+    = buffer_local_value (Qframe_title_format,
+                          XWINDOW (f->selected_window)->contents);
+  Lisp_Object icon_title
+    = buffer_local_value (Qicon_title_format,
+                          XWINDOW (f->selected_window)->contents);
+
+  if (FRAME_PGTK_P (f)
+      && ((FRAME_ICONIFIED_P (f) && EQ (icon_title, Qt))
+          || EQ (frame_title, Qt)))
+    pgtk_set_name_as_filename (f);
+  else
+    pgtk_set_name (f, arg, 0);
+}
+
+/* Change the title of frame F to NAME.
+   If NAME is nil, use the frame name as the title.  */
+
+static void
+x_set_title (struct frame *f, Lisp_Object name, Lisp_Object old_name)
+{
+  PGTK_TRACE ("x_set_title");
+  /* Don't change the title if it's already NAME.  */
+  if (EQ (name, f->title))
+    return;
+
+  update_mode_lines = 22;
+
+  fset_title (f, name);
+
+  if (NILP (name))
+    name = f->name;
+  else
+    CHECK_STRING (name);
+
+  pgtk_set_name_internal (f, name);
+}
+
+static void
+pgtk_set_name_as_filename (struct frame *f)
+{
+  GtkWidget *widget;
+  Lisp_Object name, filename;
+  Lisp_Object buf = XWINDOW (f->selected_window)->contents;
+  const char *title;
+  Lisp_Object encoded_name, encoded_filename;
+  const char *str;
+  PGTK_TRACE ("pgtk_set_name_as_filename");
+
+  if (f->explicit_name || !NILP (f->title))
+    return;
+
+  block_input ();
+  filename = BVAR (XBUFFER (buf), filename);
+  name = BVAR (XBUFFER (buf), name);
+
+  if (NILP (name))
+    {
+      if (!NILP (filename))
+        name = Ffile_name_nondirectory (filename);
+      else
+        name = build_string (pgtk_app_name);
+    }
+
+  encoded_name = ENCODE_UTF_8 (name);
+
+  widget = FRAME_GTK_OUTER_WIDGET (f);
+
+
+  if (!widget)
+    return;
+
+  if (!FRAME_LIVE_P (f))
+    return;
+
+  if (FRAME_PARENT_FRAME (f) || !GTK_IS_WINDOW (widget))
+    return;
+
+  title = FRAME_ICONIFIED_P (f) ? gtk_window_get_icon_name (GTK_WINDOW (widget))
+                                : gtk_window_get_title (GTK_WINDOW (widget));
+
+  if (title && (!strcmp (title, SSDATA (encoded_name))))
+    {
+      unblock_input ();
+      return;
+    }
+
+  str = SSDATA (encoded_name);
+  if (str == NULL)
+    str = "Bad coding";
+
+  if (FRAME_ICONIFIED_P (f))
+    gtk_window_set_icon_name (GTK_WINDOW (widget), str);
+  else
+    {
+      const char *fstr;
+
+      if (!NILP (filename))
+        {
+          encoded_filename = ENCODE_UTF_8 (filename);
+
+          fstr = SSDATA (encoded_filename);
+          if (fstr == NULL)
+            fstr = "";
+        }
+      else
+        fstr = "";
+
+      if (!FRAME_PARENT_FRAME (f))
+	gtk_window_set_title (GTK_WINDOW (widget), str);
+      fset_name (f, name);
+    }
+
+  unblock_input ();
+}
+
+void
+pgtk_set_doc_edited (void)
+{
+}
+
+static void
+x_set_menu_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
+{
+#if defined (HAVE_EXT_MENU_BAR)
+  int nlines;
+  /* Right now, menu bars don't work properly in minibuf-only frames;
+     most of the commands try to apply themselves to the minibuffer
+     frame itself, and get an error because you can't switch buffers
+     in or split the minibuffer window.  */
+  if (FRAME_MINIBUF_ONLY_P (f) || FRAME_PARENT_FRAME (f))
+    return;
+
+  if (TYPE_RANGED_FIXNUMP (int, value))
+    nlines = XFIXNUM (value);
+  else
+    nlines = 0;
+
+  /* Make sure we redisplay all windows in this frame.  */
+  fset_redisplay (f);
+
+  FRAME_MENU_BAR_LINES (f) = 0;
+  FRAME_MENU_BAR_HEIGHT (f) = 0;
+  if (nlines)
+    {
+      FRAME_EXTERNAL_MENU_BAR (f) = 1;
+      if (FRAME_PGTK_P (f) &&
+#ifndef HAVE_GTK4
+	  f->output_data.pgtk->menubar_widget == 0
+#else
+	  FRAME_MENU_BAR (f)
+#endif
+	  )
+        /* Make sure next redisplay shows the menu bar.  */
+        XWINDOW (FRAME_SELECTED_WINDOW (f))->update_mode_line = true;
+    }
+  else
+    {
+#ifndef HAVE_GTK4
+      if (FRAME_EXTERNAL_MENU_BAR (f) == 1)
+#endif
+        free_frame_menubar (f);
+      FRAME_EXTERNAL_MENU_BAR (f) = 0;
+#ifndef HAVE_GTK4
+      if (FRAME_X_P (f))
+        f->output_data.pgtk->menubar_widget = 0;
+#endif
+    }
+
+  adjust_frame_glyphs (f);
+#endif
+}
+
+/* Set the number of lines used for the tab bar of frame F to VALUE.
+   VALUE not an integer, or < 0 means set the lines to zero.  OLDVAL
+   is the old number of tab bar lines.  This function changes the
+   height of all windows on frame F to match the new tab bar height.
+   The frame's height doesn't change.  */
+
+static void
+x_set_tab_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
+{
+  int nlines;
+
+  /* Treat tab bars like menu bars.  */
+  if (FRAME_MINIBUF_ONLY_P (f))
+    return;
+
+  /* Use VALUE only if an int >= 0.  */
+  if (RANGED_FIXNUMP (0, value, INT_MAX))
+    nlines = XFIXNAT (value);
+  else
+    nlines = 0;
+
+  x_change_tab_bar_height (f, nlines * FRAME_LINE_HEIGHT (f));
+}
+
+/* Set the pixel height of the tab bar of frame F to HEIGHT.  */
+void
+x_change_tab_bar_height (struct frame *f, int height)
+{
+  int unit = FRAME_LINE_HEIGHT (f);
+  int old_height = FRAME_TAB_BAR_HEIGHT (f);
+  int lines = (height + unit - 1) / unit;
+  Lisp_Object fullscreen;
+
+  /* Make sure we redisplay all windows in this frame.  */
+  fset_redisplay (f);
+
+  /* Recalculate tab bar and frame text sizes.  */
+  FRAME_TAB_BAR_HEIGHT (f) = height;
+  FRAME_TAB_BAR_LINES (f) = lines;
+  /* Store the `tab-bar-lines' and `height' frame parameters.  */
+  store_frame_param (f, Qtab_bar_lines, make_fixnum (lines));
+  store_frame_param (f, Qheight, make_fixnum (FRAME_LINES (f)));
+
+  /* We also have to make sure that the internal border at the top of
+     the frame, below the menu bar or tab bar, is redrawn when the
+     tab bar disappears.  This is so because the internal border is
+     below the tab bar if one is displayed, but is below the menu bar
+     if there isn't a tab bar.  The tab bar draws into the area
+     below the menu bar.  */
+  if (FRAME_X_WINDOW (f) && FRAME_TAB_BAR_HEIGHT (f) == 0)
+    {
+      clear_frame (f);
+      clear_current_matrices (f);
+    }
+
+  if ((height < old_height) && WINDOWP (f->tab_bar_window))
+    clear_glyph_matrix (XWINDOW (f->tab_bar_window)->current_matrix);
+
+  /* Recalculate tabbar height.  */
+  f->n_tab_bar_rows = 0;
+  if (old_height == 0
+      && (!f->after_make_frame || NILP (frame_inhibit_implied_resize)
+          || (CONSP (frame_inhibit_implied_resize)
+              && NILP (Fmemq (Qtab_bar_lines, frame_inhibit_implied_resize)))))
+    f->tab_bar_redisplayed = f->tab_bar_resized = false;
+
+  adjust_frame_size (f, -1, -1,
+                     ((!f->tab_bar_resized
+                       && (NILP (fullscreen = get_frame_param (f, Qfullscreen))
+                           || EQ (fullscreen, Qfullwidth)))
+                        ? 1
+                        : (old_height == 0 || height == 0) ? 2 : 4),
+                     false, Qtab_bar_lines);
+
+  f->tab_bar_resized = f->tab_bar_redisplayed;
+  block_input ();
+  /* adjust_frame_size might not have done anything, garbage frame
+     here.  */
+  adjust_frame_glyphs (f);
+  SET_FRAME_GARBAGED (f);
+  if (FRAME_X_WINDOW (f))
+    pgtk_clear_under_internal_border (f);
+  FRAME_X_OUTPUT (f)->want_flip = 0;
+  unblock_input ();
+}
+
+/* Set the pixel height of the tool bar of frame F to HEIGHT.  */
+static void
+x_change_tool_bar_height (struct frame *f, int height)
+{
+#if defined (HAVE_EXT_MENU_BAR)
+  FRAME_TOOL_BAR_LINES (f) = 0;
+  FRAME_TOOL_BAR_HEIGHT (f) = 0;
+  if (height)
+    {
+      FRAME_EXTERNAL_TOOL_BAR (f) = true;
+      if (FRAME_X_P (f) && f->output_data.pgtk->toolbar_widget == 0)
+        /* Make sure next redisplay shows the tool bar.  */
+        XWINDOW (FRAME_SELECTED_WINDOW (f))->update_mode_line = true;
+      update_frame_tool_bar (f);
+    }
+  else
+    {
+      free_frame_tool_bar (f);
+      FRAME_EXTERNAL_TOOL_BAR (f) = false;
+    }
+#endif
+}
+
+/* toolbar support */
+static void
+x_set_tool_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
+{
+  int nlines;
+
+  /* Treat tool bars like menu bars.  */
+  if (FRAME_MINIBUF_ONLY_P (f))
+    return;
+
+  /* Use VALUE only if an int >= 0.  */
+  if (RANGED_FIXNUMP (0, value, INT_MAX))
+    nlines = XFIXNAT (value);
+  else
+    nlines = 0;
+
+  x_change_tool_bar_height (f, nlines * FRAME_LINE_HEIGHT (f));
+}
+
+static void
+x_set_internal_border_width (struct frame *f, Lisp_Object arg,
+                             Lisp_Object oldval)
+{
+  int border = check_int_nonnegative (arg);
+  if (border != FRAME_INTERNAL_BORDER_WIDTH (f))
+    {
+      f->internal_border_width = border;
+      if (FRAME_PGTK_P (f))
+	{
+	  adjust_frame_size (f, -1, -1, 3, false, Qinternal_border_width);
+	  if (f->face_cache)
+	    pgtk_clear_under_internal_border (f);
+	}
+    }
+}
+
+static void
+x_set_icon_type (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+#ifndef HAVE_GTK4
+  GError *error_ = NULL;
+  if (NILP (arg) || EQ (arg, Qt))
+    {
+      gtk_window_set_icon (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), NULL);
+      return;
+    }
+  CHECK_STRING (arg);
+  gtk_window_set_icon_from_file (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                                 SSDATA (arg), &error_);
+  if (error_)
+    {
+      Lisp_Object error_string = build_string (error_->message);
+      g_error_free (error_);
+      error ("%s", SSDATA (error_string));
+    }
+#endif
+}
+
+/* This is the same as the xfns.c definition.  */
+static void
+x_set_cursor_type (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  set_frame_cursor_types (f, arg);
+}
+
+/* called to set mouse pointer color, but all other terms use it to
+   initialize pointer types (and don't set the color ;) */
+static void
+x_set_mouse_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+}
+
+static void
+x_icon (struct frame *f, Lisp_Object parms)
+/* --------------------------------------------------------------------------
+   Strangely-named function to set icon position parameters in frame.
+   This is irrelevant under macOS, but might be needed under GNUstep,
+   depending on the window manager used.  Note, this is not a standard
+   frame parameter-setter; it is called directly from x-create-frame.
+   -------------------------------------------------------------------------- */
+{
+#if 0
+  Lisp_Object icon_x, icon_y;
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (Qnil);
+
+  FRAME_X_OUTPUT(f)->icon_top = -1;
+  FRAME_X_OUTPUT(f)->icon_left = -1;
+
+  /* Set the position of the icon.  */
+  icon_x = gui_display_get_arg (dpyinfo, parms, Qicon_left, 0, 0, RES_TYPE_NUMBER);
+  icon_y = gui_display_get_arg (dpyinfo, parms, Qicon_top, 0, 0,  RES_TYPE_NUMBER);
+  if (!EQ (icon_x, Qunbound) && !EQ (icon_y, Qunbound))
+    {
+      CHECK_NUMBER (icon_x);
+      CHECK_NUMBER (icon_y);
+      FRAME_X_OUTPUT(f)->icon_top = XFIXNUM (icon_y);
+      FRAME_X_OUTPUT(f)->icon_left = XFIXNUM (icon_x);
+    }
+  else if (!EQ (icon_x, Qunbound) || !EQ (icon_y, Qunbound))
+    error ("Both left and top icon corners of icon must be specified");
+#endif
+}
+
+/**
+ * x_set_undecorated:
+ *
+ * Set frame F's `undecorated' parameter.  If non-nil, F's window-system
+ * window is drawn without decorations, title, minimize/maximize boxes
+ * and external borders.  This usually means that the window cannot be
+ * dragged, resized, iconified, maximized or deleted with the mouse.  If
+ * nil, draw the frame with all the elements listed above unless these
+ * have been suspended via window manager settings.
+ *
+ * Some window managers may not honor this parameter.
+ */
+static void
+x_set_undecorated (struct frame *f, Lisp_Object new_value,
+                   Lisp_Object old_value)
+{
+  if (!EQ (new_value, old_value))
+    {
+      FRAME_UNDECORATED (f) = NILP (new_value) ? false : true;
+#ifndef HAVE_GTK4
+      xg_set_undecorated (f, new_value);
+#else
+      egtk_set_undecorated (f, new_value);
+#endif
+    }
+}
+
+/**
+ * x_set_skip_taskbar:
+ *
+ * Set frame F's `skip-taskbar' parameter.  If non-nil, this should
+ * remove F's icon from the taskbar associated with the display of F's
+ * window-system window and inhibit switching to F's window via
+ * <Alt>-<TAB>.  If nil, lift these restrictions.
+ *
+ * Some window managers may not honor this parameter.
+ */
+static void
+x_set_skip_taskbar (struct frame *f, Lisp_Object new_value,
+                    Lisp_Object old_value)
+{
+  if (!EQ (new_value, old_value))
+    {
+#ifndef HAVE_GTK4
+      xg_set_skip_taskbar (f, new_value);
+#else
+      egtk_set_skip_taskbar (f, new_value);
+#endif
+      FRAME_SKIP_TASKBAR (f) = !NILP (new_value);
+    }
+}
+
+/**
+ * x_set_override_redirect:
+ *
+ * Set frame F's `override_redirect' parameter which, if non-nil, hints
+ * that the window manager doesn't want to deal with F.  Usually, such
+ * frames have no decorations and always appear on top of all frames.
+ *
+ * Some window managers may not honor this parameter.
+ */
+static void
+x_set_override_redirect (struct frame *f, Lisp_Object new_value,
+                         Lisp_Object old_value)
+{
+  if (!EQ (new_value, old_value))
+    {
+      /* Here (xfwm) override_redirect can be changed for invisible
+         frames only.  */
+      pgtk_make_frame_invisible (f);
+#ifndef HAVE_GTK4
+      xg_set_override_redirect (f, new_value);
+#else
+      egtk_set_override_redirect (f, new_value);
+#endif
+
+      pgtk_make_frame_visible (f);
+      FRAME_OVERRIDE_REDIRECT (f) = !NILP (new_value);
+    }
+}
+
+static void
+pgtk_set_sticky (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
+{
+#ifndef HAVE_GTK4
+  if (!NILP (new_value))
+    gtk_window_stick (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+  else
+    gtk_window_unstick (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+#endif
+}
+
+static void
+pgtk_set_tool_bar_position (struct frame *f, Lisp_Object new_value,
+                            Lisp_Object old_value)
+{
+  Lisp_Object choice = list4 (Qleft, Qright, Qtop, Qbottom);
+
+  if (!NILP (Fmemq (new_value, choice)))
+    {
+      if (!EQ (new_value, old_value))
+        {
+#ifndef HAVE_GTK4
+          xg_change_toolbar_position (f, new_value);
+#else
+	  egtk_change_toolbar_position (f, new_value);
+#endif
+          fset_tool_bar_position (f, new_value);
+        }
+    }
+  else
+    wrong_choice (choice, new_value);
+}
+
+static void
+pgtk_set_scroll_bar_foreground (struct frame *f, Lisp_Object new_value,
+                                Lisp_Object old_value)
+{
+#ifdef HAVE_GTK3
+  GtkCssProvider *css_provider
+    = FRAME_X_OUTPUT (f)->scrollbar_foreground_css_provider;
+
+  if (NILP (new_value))
+    {
+#ifndef HAVE_GTK4
+      gtk_css_provider_load_from_data (css_provider, "", -1, NULL);
+#else
+      gtk_css_provider_load_from_data (css_provider, "", -1);
+#endif
+    }
+  else if (STRINGP (new_value))
+    {
+      Emacs_Color rgb;
+
+      if (!pgtk_parse_color (SSDATA (new_value), &rgb))
+        error ("Unknown color.");
+
+      char css[64];
+      sprintf (css, "scrollbar slider { background-color: #%06x; }",
+               (unsigned int) rgb.pixel & 0xffffff);
+#ifdef HAVE_GTK4
+      gtk_css_provider_load_from_data (css_provider, css, -1);
+#else
+      gtk_css_provider_load_from_data (css_provider, css, -1, NULL);
+#endif
+    }
+  else
+    error ("Invalid scroll-bar-foreground.");
+#endif
+  // TODO for gtk 2
+}
+
+static void
+pgtk_set_scroll_bar_background (struct frame *f, Lisp_Object new_value,
+                                Lisp_Object old_value)
+{
+#ifdef HAVE_GTK3
+  GtkCssProvider *css_provider
+    = FRAME_X_OUTPUT (f)->scrollbar_background_css_provider;
+
+  if (NILP (new_value))
+    {
+#ifdef HAVE_GTK4
+      gtk_css_provider_load_from_data (css_provider, "", -1);
+#else
+      gtk_css_provider_load_from_data (css_provider, "", -1, NULL);
+#endif
+    }
+  else if (STRINGP (new_value))
+    {
+      Emacs_Color rgb;
+
+      if (!pgtk_parse_color (SSDATA (new_value), &rgb))
+        error ("Unknown color.");
+
+      char css[64];
+      sprintf (css, "scrollbar trough { background-color: #%06x; }",
+               (unsigned int) rgb.pixel & 0xffffff);
+#ifdef HAVE_GTK4
+      gtk_css_provider_load_from_data (css_provider, css, -1);
+#else
+      gtk_css_provider_load_from_data (css_provider, css, -1, NULL);
+#endif
+    }
+  else
+    error ("Invalid scroll-bar-background.");
+#endif
+ /* TODO implement for GTK 2 */
+}
+
+/* Note: see frame.c for template, also where generic functions are impl */
+frame_parm_handler pgtk_frame_parm_handlers[] = {
+  gui_set_autoraise, /* generic OK */
+  gui_set_autolower, /* generic OK */
+  x_set_background_color,
+  x_set_border_color,
+  gui_set_border_width,
+  x_set_cursor_color,
+  x_set_cursor_type,
+  gui_set_font, /* generic OK */
+  x_set_foreground_color,
+  x_set_icon_name,
+  x_set_icon_type,
+  x_set_internal_border_width, /* generic OK */
+  gui_set_right_divider_width,
+  gui_set_bottom_divider_width,
+  x_set_menu_bar_lines,
+  x_set_mouse_color,
+  x_explicitly_set_name,
+  gui_set_scroll_bar_width,  /* generic OK */
+  gui_set_scroll_bar_height, /* generic OK */
+  x_set_title,
+  gui_set_unsplittable,           /* generic OK */
+  gui_set_vertical_scroll_bars,   /* generic OK */
+  gui_set_horizontal_scroll_bars, /* generic OK */
+  gui_set_visibility,             /* generic OK */
+  x_set_tab_bar_lines,
+  x_set_tool_bar_lines,
+  pgtk_set_scroll_bar_foreground,
+  pgtk_set_scroll_bar_background,
+  gui_set_screen_gamma, /* generic OK */
+  gui_set_line_spacing, /* generic OK, sets f->extra_line_spacing to int */
+  gui_set_left_fringe,  /* generic OK */
+  gui_set_right_fringe, /* generic OK */
+  0,                    /* x_set_wait_for_wm */
+  gui_set_fullscreen,   /* generic OK */
+  gui_set_font_backend, /* generic OK */
+  gui_set_alpha,
+  pgtk_set_sticky,
+  pgtk_set_tool_bar_position,
+  pgtk_set_inhibit_double_buffering, /* x_set_inhibit_double_buffering */
+  x_set_undecorated,
+  x_set_parent_frame, /* x_set_parent_frame, */
+  x_set_skip_taskbar,
+  x_set_no_focus_on_map,
+  x_set_no_accept_focus,
+  x_set_z_group,
+  x_set_override_redirect,
+  gui_set_no_special_glyphs,
+};
+
+void
+pgtk_set_inhibit_double_buffering (struct frame *f, Lisp_Object new_value,
+                                   Lisp_Object old_value)
+{
+#ifndef HAVE_GTK4
+  if (FRAME_GTK_WIDGET (f))
+    gtk_widget_set_double_buffered (FRAME_GTK_WIDGET (f), !NILP (new_value));
+
+  if (FRAME_GTK_OUTER_WIDGET (f))
+    gtk_widget_set_double_buffered (FRAME_GTK_OUTER_WIDGET (f),
+                                    !NILP (new_value));
+#endif
+}
+
+/* Handler for signals raised during x_create_frame and
+   x_create_tip_frame.  FRAME is the frame which is partially
+   constructed.  */
+
+static Lisp_Object
+unwind_create_frame (Lisp_Object frame)
+{
+  struct frame *f = XFRAME (frame);
+
+  /* If frame is already dead, nothing to do.  This can happen if the
+     display is disconnected after the frame has become official, but
+     before x_create_frame removes the unwind protect.  */
+  if (!FRAME_LIVE_P (f))
+    return Qnil;
+
+  /* If frame is ``official'', nothing to do.  */
+  if (NILP (Fmemq (frame, Vframe_list)))
+    {
+      /* If the frame's image cache refcount is still the same as our
+         private shadow variable, it means we are unwinding a frame
+         for which we didn't yet call init_frame_faces, where the
+         refcount is incremented.  Therefore, we increment it here, so
+         that free_frame_faces, called in x_free_frame_resources
+         below, will not mistakenly decrement the counter that was not
+         incremented yet to account for this new frame.  */
+      if (FRAME_IMAGE_CACHE (f) != NULL
+          && FRAME_IMAGE_CACHE (f)->refcount == image_cache_refcount)
+        FRAME_IMAGE_CACHE (f)->refcount++;
+
+      x_free_frame_resources (f);
+      free_glyphs (f);
+      return Qt;
+    }
+
+  return Qnil;
+}
+
+static void
+do_unwind_create_frame (Lisp_Object frame)
+{
+  unwind_create_frame (frame);
+}
+
+/* Return the pixel color value for color COLOR_NAME on frame F.  If F
+   is a monochrome frame, return MONO_COLOR regardless of what ARG says.
+   Signal an error if color can't be allocated.  */
+
+static int
+x_decode_color (struct frame *f, Lisp_Object color_name, int mono_color)
+{
+  Emacs_Color cdef;
+
+  CHECK_STRING (color_name);
+
+  /* Return MONO_COLOR for monochrome frames.  */
+  if (FRAME_DISPLAY_INFO (f)->n_planes == 1)
+    return mono_color;
+
+  /* x_defined_color is responsible for coping with failures
+     by looking for a near-miss.  */
+  if (pgtk_defined_color (f, SSDATA (color_name), &cdef, true, 0))
+    return cdef.pixel;
+
+  signal_error ("Undefined color", color_name);
+}
+
+void
+x_set_parent_frame (struct frame *f, Lisp_Object new_value,
+                    Lisp_Object old_value)
+{
+  struct frame *p = NULL;
+  if (!NILP (new_value)
+      && (!FRAMEP (new_value) || !FRAME_LIVE_P (p = XFRAME (new_value))
+          || !FRAME_PGTK_P (p)))
+    {
+      store_frame_param (f, Qparent_frame, old_value);
+      error ("Invalid specification of `parent-frame'");
+    }
+#ifdef HAVE_GTK4
+  if (!EQ (old_value, new_value))
+    eg_unparent_frame (f);
+#endif
+  if (p != FRAME_PARENT_FRAME (f))
+    {
+      block_input ();
+      fset_parent_frame (f, new_value);
+      unblock_input ();
+    }
+}
+
+static void
+x_default_font_parameter (struct frame *f, Lisp_Object parms)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  Lisp_Object font_param
+    = gui_display_get_arg (dpyinfo, parms, Qfont, NULL, NULL, RES_TYPE_STRING);
+  Lisp_Object font = Qnil;
+  if (EQ (font_param, Qunbound))
+    font_param = Qnil;
+
+  if (NILP (font_param))
+    {
+      /* System font should take precedence over X resources.  We suggest this
+         regardless of font-use-system-font because .emacs may not have been
+         read yet.  */
+      const char *system_font = xsettings_get_system_font ();
+      if (system_font)
+        font = font_open_by_name (f, build_unibyte_string (system_font));
+    }
+
+  if (NILP (font))
+    font = !NILP (font_param)
+             ? font_param
+             : gui_display_get_arg (dpyinfo, parms, Qfont, "font", "Font",
+                                    RES_TYPE_STRING);
+
+  if (!FONTP (font) && !STRINGP (font))
+    {
+      const char *names[]
+        = {"monospace-10",
+           "-adobe-courier-medium-r-*-*-*-120-*-*-*-*-iso8859-1",
+           "-misc-fixed-medium-r-normal-*-*-140-*-*-c-*-iso8859-1",
+           "-*-*-medium-r-normal-*-*-140-*-*-c-*-iso8859-1",
+           /* This was formerly the first thing tried, but it finds
+              too many fonts and takes too long.  */
+           "-*-*-medium-r-*-*-*-*-*-*-c-*-iso8859-1",
+           /* If those didn't work, look for something which will
+              at least work.  */
+           "-*-fixed-*-*-*-*-*-140-*-*-c-*-iso8859-1", "fixed", NULL};
+      int i;
+
+      for (i = 0; names[i]; i++)
+        {
+          font = font_open_by_name (f, build_unibyte_string (names[i]));
+          if (!NILP (font))
+            break;
+        }
+      if (NILP (font))
+        error ("No suitable font was found");
+    }
+  else if (!NILP (font_param))
+    {
+      /* Remember the explicit font parameter, so we can re-apply it after
+         we've applied the `default' face settings.  */
+      AUTO_FRAME_ARG (arg, Qfont_parameter, font_param);
+      gui_set_frame_parameters (f, arg);
+    }
+
+  /* This call will make X resources override any system font setting.  */
+  gui_default_parameter (f, parms, Qfont, font, "font", "Font",
+                         RES_TYPE_STRING);
+}
+
+/* ==========================================================================
+
+    Lisp definitions
+
+   ========================================================================== */
+
+DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
+       1, 1, 0,
+       doc: /* Make a new X window, which is called a "frame" in Emacs terms.
+Return an Emacs frame object.  PARMS is an alist of frame parameters.
+If the parameters specify that the frame should not have a minibuffer,
+and do not specify a specific minibuffer window to use, then
+`default-minibuffer-frame' must be a frame whose minibuffer can be
+shared by the new frame.
+
+This function is an internal primitive--use `make-frame' instead.  */)
+(Lisp_Object parms)
+{
+  struct frame *f;
+  Lisp_Object frame, tem;
+  Lisp_Object name;
+  bool minibuffer_only = false;
+#ifndef HAVE_GTK4
+  long window_prompting = 0;
+#endif
+  bool undecorated = false, override_redirect = false;
+  ptrdiff_t count = SPECPDL_INDEX ();
+  Lisp_Object display;
+  struct pgtk_display_info *dpyinfo = NULL;
+  Lisp_Object parent, parent_frame;
+  struct kboard *kb;
+  int x_width = 0, x_height = 0;
+
+  parms = Fcopy_alist (parms);
+
+  /* Use this general default value to start with
+     until we know if this frame has a specified name.  */
+  Vx_resource_name = Vinvocation_name;
+
+  display
+    = gui_display_get_arg (dpyinfo, parms, Qterminal, 0, 0, RES_TYPE_NUMBER);
+  if (EQ (display, Qunbound))
+    display
+      = gui_display_get_arg (dpyinfo, parms, Qdisplay, 0, 0, RES_TYPE_STRING);
+  if (EQ (display, Qunbound))
+    display = Qnil;
+  dpyinfo = check_pgtk_display_info (display);
+  kb = dpyinfo->terminal->kboard;
+
+  if (!dpyinfo->terminal->name)
+    error ("Terminal is not live, can't create new frames on it");
+
+  name = gui_display_get_arg (dpyinfo, parms, Qname, "name", "Name",
+                              RES_TYPE_STRING);
+  if (!STRINGP (name) && !EQ (name, Qunbound) && !NILP (name))
+    error ("Invalid frame name--not a string or nil");
+
+  if (STRINGP (name))
+    Vx_resource_name = name;
+
+  /* See if parent window is specified.  */
+  parent = gui_display_get_arg (dpyinfo, parms, Qparent_id, NULL, NULL,
+                                RES_TYPE_NUMBER);
+  if (EQ (parent, Qunbound))
+    parent = Qnil;
+  if (!NILP (parent))
+    CHECK_NUMBER (parent);
+
+  frame = Qnil;
+  tem = gui_display_get_arg (dpyinfo, parms, Qminibuffer, "minibuffer",
+                             "Minibuffer", RES_TYPE_SYMBOL);
+  if (EQ (tem, Qnone) || NILP (tem))
+    f = make_frame_without_minibuffer (Qnil, kb, display);
+  else if (EQ (tem, Qonly))
+    {
+      f = make_minibuffer_frame ();
+      minibuffer_only = true;
+    }
+  else if (WINDOWP (tem))
+    f = make_frame_without_minibuffer (tem, kb, display);
+  else
+    f = make_frame (true);
+
+  parent_frame = gui_display_get_arg (dpyinfo, parms, Qparent_frame, NULL, NULL,
+                                      RES_TYPE_SYMBOL);
+  /* Accept parent-frame iff parent-id was not specified.  */
+  if (!NILP (parent) || EQ (parent_frame, Qunbound) || NILP (parent_frame)
+      || !FRAMEP (parent_frame) || !FRAME_LIVE_P (XFRAME (parent_frame))
+      || !FRAME_PGTK_P (XFRAME (parent_frame)))
+    parent_frame = Qnil;
+
+  f->terminal = dpyinfo->terminal;
+
+  store_frame_param (f, Qparent_frame, parent_frame);
+
+  if (!NILP (tem = (gui_display_get_arg (dpyinfo, parms, Qundecorated, NULL,
+                                         NULL, RES_TYPE_BOOLEAN)))
+      && !(EQ (tem, Qunbound)))
+    undecorated = true;
+
+  FRAME_UNDECORATED (f) = undecorated;
+  store_frame_param (f, Qundecorated, undecorated ? Qt : Qnil);
+
+  if (!NILP (tem = (gui_display_get_arg (dpyinfo, parms, Qoverride_redirect,
+                                         NULL, NULL, RES_TYPE_BOOLEAN)))
+      && !(EQ (tem, Qunbound)))
+    override_redirect = true;
+
+  FRAME_OVERRIDE_REDIRECT (f) = override_redirect;
+  store_frame_param (f, Qoverride_redirect, override_redirect ? Qt : Qnil);
+  store_frame_param (f, Qheader_bar_icons, Qt);
+#ifdef HAVE_GTK4
+  store_frame_param (f, Qheader_bar, Qt);
+#endif
+  XSETFRAME (frame, f);
+
+  f->output_method = output_pgtk;
+  FRAME_X_OUTPUT (f) = xzalloc (sizeof *FRAME_X_OUTPUT (f));
+#if 0
+  FRAME_X_OUTPUT(f)->icon_bitmap = -1;
+#endif
+  FRAME_FONTSET (f) = -1;
+  FRAME_X_OUTPUT (f)->white_relief.pixel = -1;
+  FRAME_X_OUTPUT (f)->black_relief.pixel = -1;
+
+  FRAME_X_OUTPUT (f)->clip_count = 0;
+#ifdef HAVE_GTK3
+  FRAME_X_OUTPUT (f)->scrollbar_foreground_css_provider
+    = gtk_css_provider_new ();
+  FRAME_X_OUTPUT (f)->scrollbar_background_css_provider
+    = gtk_css_provider_new ();
+#endif
+
+  FRAME_X_OUTPUT (f)->menubar_or_popup_count = 0;
+#ifdef HAVE_GTK4
+  FRAME_X_OUTPUT (f)->hyper_flag = 0;
+  FRAME_X_OUTPUT (f)->last_focus_child_frame = 0;
+#endif
+  fset_icon_name (f,
+                  gui_display_get_arg (dpyinfo, parms, Qicon_name, "iconName",
+                                       "Title", RES_TYPE_STRING));
+  if (!STRINGP (f->icon_name))
+    fset_icon_name (f, Qnil);
+
+  FRAME_DISPLAY_INFO (f) = dpyinfo;
+
+  /* With FRAME_DISPLAY_INFO set up, this unwind-protect is safe.  */
+  record_unwind_protect (do_unwind_create_frame, frame);
+
+  /* These colors will be set anyway later, but it's important
+     to get the color reference counts right, so initialize them!  */
+  {
+    Lisp_Object black;
+
+    /* Function x_decode_color can signal an error.  Make
+       sure to initialize color slots so that we won't try
+       to free colors we haven't allocated.  */
+    FRAME_FOREGROUND_PIXEL (f) = -1;
+    FRAME_BACKGROUND_PIXEL (f) = -1;
+    FRAME_X_OUTPUT (f)->cursor_color = -1;
+    FRAME_X_OUTPUT (f)->cursor_foreground_color = -1;
+#ifdef HAVE_GTK3
+    FRAME_X_OUTPUT (f)->border_pixel = -1;
+#endif
+    FRAME_X_OUTPUT (f)->mouse_color = -1;
+#ifdef HAVE_GTK4
+    FRAME_X_OUTPUT (f)->child_frame_order = NULL;
+#endif
+
+    black = build_string ("black");
+    FRAME_FOREGROUND_PIXEL (f)
+      = x_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    FRAME_BACKGROUND_PIXEL (f)
+      = x_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    FRAME_X_OUTPUT (f)->cursor_color
+      = x_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    FRAME_X_OUTPUT (f)->cursor_foreground_color
+      = x_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+#ifdef HAVE_GTK3
+    FRAME_X_OUTPUT (f)->border_pixel
+      = x_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+#endif
+    FRAME_X_OUTPUT (f)->mouse_color
+      = x_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+  }
+
+  /* Specify the parent under which to make this X window.  */
+  if (!NILP (parent))
+    {
+      FRAME_X_OUTPUT (f)->parent_desc = (GWindow) XFIXNAT (parent);
+      FRAME_X_OUTPUT (f)->explicit_parent = true;
+    }
+  else
+    {
+      FRAME_X_OUTPUT (f)->parent_desc = FRAME_DISPLAY_INFO (f)->root_window;
+      FRAME_X_OUTPUT (f)->explicit_parent = false;
+    }
+
+#ifdef HAVE_GTK4
+  FRAME_X_OUTPUT (f)->mbar_deep_atimer = NULL;
+#endif
+
+  /* Set the name; the functions to which we pass f expect the name to
+     be set.  */
+  if (EQ (name, Qunbound) || NILP (name))
+    {
+      fset_name (f, build_string (dpyinfo->x_id_name));
+      f->explicit_name = false;
+    }
+  else
+    {
+      fset_name (f, name);
+      f->explicit_name = true;
+      /* Use the frame's title when getting resources for this frame.  */
+      specbind (Qx_resource_name, name);
+    }
+
+  register_font_driver (&ftcrfont_driver, f);
+#ifdef HAVE_HARFBUZZ
+  register_font_driver (&ftcrhbfont_driver, f);
+#endif	/* HAVE_HARFBUZZ */
+  image_cache_refcount
+    = FRAME_IMAGE_CACHE (f) ? FRAME_IMAGE_CACHE (f)->refcount : 0;
+#if 0
+#ifdef GLYPH_DEBUG
+  dpyinfo_refcount = dpyinfo->reference_count;
+#endif /* GLYPH_DEBUG */
+#endif
+
+  gui_default_parameter (f, parms, Qfont_backend, Qnil, "fontBackend",
+                         "FontBackend", RES_TYPE_STRING);
+
+  /* Extract the window parameters from the supplied values
+     that are needed to determine window geometry.  */
+  x_default_font_parameter (f, parms);
+  if (!FRAME_FONT (f))
+    {
+      delete_frame (frame, Qnoelisp);
+      error ("Invalid frame font");
+    }
+
+    /* Frame contents get displaced if an embedded X window has a border.  */
+#if 0
+  if (! FRAME_X_EMBEDDED_P (f))
+#endif
+  gui_default_parameter (f, parms, Qborder_width, make_fixnum (0),
+                         "borderWidth", "BorderWidth", RES_TYPE_NUMBER);
+
+  /* This defaults to 1 in order to match xterm.  We recognize either
+     internalBorderWidth or internalBorder (which is what xterm calls
+     it).  */
+  if (NILP (Fassq (Qinternal_border_width, parms)))
+    {
+      Lisp_Object value;
+
+      value = gui_display_get_arg (dpyinfo, parms, Qinternal_border_width,
+                                   "internalBorder", "internalBorder",
+                                   RES_TYPE_NUMBER);
+      if (!EQ (value, Qunbound))
+        parms = Fcons (Fcons (Qinternal_border_width, value), parms);
+    }
+  gui_default_parameter (f, parms, Qinternal_border_width, make_fixnum (0),
+                         "internalBorderWidth", "internalBorderWidth",
+                         RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qright_divider_width, make_fixnum (0), NULL,
+                         NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0), NULL,
+                         NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qvertical_scroll_bars, Qright,
+                         "verticalScrollBars", "ScrollBars", RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil,
+                         "horizontalScrollBars", "ScrollBars", RES_TYPE_SYMBOL);
+  /* Also do the stuff which must be set before the window exists.  */
+  gui_default_parameter (f, parms, Qforeground_color, build_string ("black"),
+                         "foreground", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qbackground_color, build_string ("white"),
+                         "background", "Background", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qmouse_color, build_string ("black"),
+                         "pointerColor", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qborder_color, build_string ("black"),
+                         "borderColor", "BorderColor", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qscreen_gamma, Qnil, "screenGamma",
+                         "ScreenGamma", RES_TYPE_FLOAT);
+  gui_default_parameter (f, parms, Qline_spacing, Qnil, "lineSpacing",
+                         "LineSpacing", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qleft_fringe, Qnil, "leftFringe",
+                         "LeftFringe", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qright_fringe, Qnil, "rightFringe",
+                         "RightFringe", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qno_special_glyphs, Qnil, NULL, NULL,
+                         RES_TYPE_BOOLEAN);
+
+  gui_default_parameter (f, parms, Qscroll_bar_foreground, Qnil,
+                         "scrollBarForeground", "ScrollBarForeground",
+                         RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qscroll_bar_background, Qnil,
+                         "scrollBarBackground", "ScrollBarBackground",
+                         RES_TYPE_STRING);
+
+  /* Init faces before gui_default_parameter is called for the
+     scroll-bar-width parameter because otherwise we end up in
+     init_iterator with a null face cache, which should not happen.  */
+  init_frame_faces (f);
+
+  /* We have to call adjust_frame_size here since otherwise
+     x_set_tool_bar_lines will already work with the character sizes
+     installed by init_frame_faces while the frame's pixel size is still
+     calculated from a character size of 1 and we subsequently hit the
+     (height >= 0) assertion in window_box_height.
+
+     The non-pixelwise code apparently worked around this because it
+     had one frame line vs one toolbar line which left us with a zero
+     root window height which was obviously wrong as well ...
+
+     Also process `min-width' and `min-height' parameters right here
+     because `frame-windows-min-size' needs them.  */
+  tem = gui_display_get_arg (dpyinfo, parms, Qmin_width, NULL, NULL,
+                             RES_TYPE_NUMBER);
+  if (NUMBERP (tem))
+    store_frame_param (f, Qmin_width, tem);
+  tem = gui_display_get_arg (dpyinfo, parms, Qmin_height, NULL, NULL,
+                             RES_TYPE_NUMBER);
+  if (NUMBERP (tem))
+    store_frame_param (f, Qmin_height, tem);
+  adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f),
+                     FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), 5, true,
+                     Qx_create_frame_1);
+
+  /* Set the menu-bar-lines and tool-bar-lines parameters.  We don't
+     look up the X resources controlling the menu-bar and tool-bar
+     here; they are processed specially at startup, and reflected in
+     the values of the mode variables.  */
+
+  gui_default_parameter (f, parms, Qmenu_bar_lines,
+                         NILP (Vmenu_bar_mode) ? make_fixnum (0)
+                                               : make_fixnum (1),
+                         NULL, NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qtab_bar_lines,
+                         NILP (Vtab_bar_mode) ? make_fixnum (0)
+                                              : make_fixnum (1),
+                         NULL, NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qtool_bar_lines,
+                         NILP (Vtool_bar_mode) ? make_fixnum (0)
+                                               : make_fixnum (1),
+                         NULL, NULL, RES_TYPE_NUMBER);
+
+  gui_default_parameter (f, parms, Qbuffer_predicate, Qnil, "bufferPredicate",
+                         "BufferPredicate", RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qtitle, Qnil, "title", "Title",
+                         RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qwait_for_wm, Qt, "waitForWM", "WaitForWM",
+                         RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qtool_bar_position,
+                         FRAME_TOOL_BAR_POSITION (f), 0, 0, RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qinhibit_double_buffering, Qnil,
+                         "inhibitDoubleBuffering", "InhibitDoubleBuffering",
+                         RES_TYPE_BOOLEAN);
+#ifndef HAVE_GTK4
+  /* Compute the size of the X window.  */
+  window_prompting =
+#endif
+    gui_figure_window_size (f, parms, true, true, &x_width, &x_height);
+  tem = gui_display_get_arg (dpyinfo, parms, Qunsplittable, 0, 0,
+                             RES_TYPE_BOOLEAN);
+  f->no_split = minibuffer_only || EQ (tem, Qt);
+
+#if 0
+  x_icon_verify (f, parms);
+#endif
+
+#ifdef HAVE_GTK4
+  egtk_create_frame_widgets (f);
+  fset_parent_frame (f, parent_frame);
+#else
+  xg_create_frame_widgets (f);
+#endif
+#if defined(HAVE_GTK3) || defined (HAVE_GTK4)
+  FRAME_GTK_EV_HANDLER (f) = emacs_gtk_event_handler_new (f);
+  FRAME_GTK_ES_HANDLER (f) = emacs_gtk_event_handler_new (f);
+  g_object_ref (FRAME_GTK_EV_HANDLER (f));
+  g_object_ref (FRAME_GTK_ES_HANDLER (f));
+#endif
+  pgtk_set_event_handler (f);
+
+#ifndef HAVE_GTK4
+#define INSTALL_CURSOR(FIELD, NAME) \
+  FRAME_X_OUTPUT (f)->FIELD         \
+    = gdk_cursor_new_for_display (FRAME_X_DISPLAY (f), GDK_##NAME)
+
+  INSTALL_CURSOR (text_cursor, XTERM);
+  INSTALL_CURSOR (nontext_cursor, LEFT_PTR);
+  INSTALL_CURSOR (modeline_cursor, XTERM);
+  INSTALL_CURSOR (hand_cursor, HAND2);
+  INSTALL_CURSOR (hourglass_cursor, WATCH);
+  INSTALL_CURSOR (horizontal_drag_cursor, SB_H_DOUBLE_ARROW);
+  INSTALL_CURSOR (vertical_drag_cursor, SB_V_DOUBLE_ARROW);
+  INSTALL_CURSOR (left_edge_cursor, LEFT_SIDE);
+  INSTALL_CURSOR (right_edge_cursor, RIGHT_SIDE);
+  INSTALL_CURSOR (top_edge_cursor, TOP_SIDE);
+  INSTALL_CURSOR (bottom_edge_cursor, BOTTOM_SIDE);
+  INSTALL_CURSOR (top_left_corner_cursor, TOP_LEFT_CORNER);
+  INSTALL_CURSOR (top_right_corner_cursor, TOP_RIGHT_CORNER);
+  INSTALL_CURSOR (bottom_right_corner_cursor, BOTTOM_RIGHT_CORNER);
+  INSTALL_CURSOR (bottom_left_corner_cursor, BOTTOM_LEFT_CORNER);
+#undef INSTALL_CURSOR
+#else
+  FRAME_X_OUTPUT (f)->text_cursor = gdk_cursor_new_from_name ("text", NULL);
+  FRAME_X_OUTPUT (f)->nontext_cursor = gdk_cursor_new_from_name ("default", NULL);
+  FRAME_X_OUTPUT (f)->modeline_cursor = gdk_cursor_new_from_name ("default", NULL);
+  FRAME_X_OUTPUT (f)->hand_cursor = gdk_cursor_new_from_name ("pointer", NULL);
+  FRAME_X_OUTPUT (f)->hourglass_cursor = gdk_cursor_new_from_name ("wait", NULL);
+  FRAME_X_OUTPUT (f)->horizontal_drag_cursor = gdk_cursor_new_from_name ("col-resize", NULL);
+  FRAME_X_OUTPUT (f)->vertical_drag_cursor = gdk_cursor_new_from_name ("row-resize", NULL);
+  FRAME_X_OUTPUT (f)->bottom_right_corner_cursor = gdk_cursor_new_from_name ("se-resize", NULL);
+  FRAME_X_OUTPUT (f)->bottom_left_corner_cursor = gdk_cursor_new_from_name ("sw-resize", NULL);
+  FRAME_X_OUTPUT (f)->top_right_corner_cursor = gdk_cursor_new_from_name ("ne-resize", NULL);
+  FRAME_X_OUTPUT (f)->top_left_corner_cursor = gdk_cursor_new_from_name ("nw-resize", NULL);
+  FRAME_X_OUTPUT (f)->left_edge_cursor = gdk_cursor_new_from_name ("w-resize", NULL);
+  FRAME_X_OUTPUT (f)->right_edge_cursor = gdk_cursor_new_from_name ("e-resize", NULL);
+  FRAME_X_OUTPUT (f)->top_edge_cursor = gdk_cursor_new_from_name ("n-resize", NULL);
+  FRAME_X_OUTPUT (f)->bottom_edge_cursor = gdk_cursor_new_from_name ("s-resize", NULL);
+#endif
+
+  x_icon (f, parms);
+
+  /* Now consider the frame official.  */
+  f->terminal->reference_count++;
+  FRAME_DISPLAY_INFO (f)->reference_count++;
+  Vframe_list = Fcons (frame, Vframe_list);
+
+  /* We need to do this after creating the X window, so that the
+     icon-creation functions can say whose icon they're describing.  */
+  gui_default_parameter (f, parms, Qicon_type, Qt, "bitmapIcon", "BitmapIcon",
+                         RES_TYPE_BOOLEAN);
+
+  gui_default_parameter (f, parms, Qauto_raise, Qnil, "autoRaise",
+                         "AutoRaiseLower", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qauto_lower, Qnil, "autoLower",
+                         "AutoRaiseLower", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qcursor_type, Qbox, "cursorType",
+                         "CursorType", RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qscroll_bar_width, Qnil, "scrollBarWidth",
+                         "ScrollBarWidth", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qscroll_bar_height, Qnil, "scrollBarHeight",
+                         "ScrollBarHeight", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qalpha, Qnil, "alpha", "Alpha",
+                         RES_TYPE_NUMBER);
+#if 0
+  if (!NILP (parent_frame))
+    {
+      struct frame *p = XFRAME (parent_frame);
+
+      block_input ();
+      XReparentWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f),
+		       FRAME_X_WINDOW (p), f->left_pos, f->top_pos);
+      unblock_input ();
+    }
+#endif
+
+  gui_default_parameter (f, parms, Qno_focus_on_map, Qnil, NULL, NULL,
+                         RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qno_accept_focus, Qnil, NULL, NULL,
+                         RES_TYPE_BOOLEAN);
+
+  /* Consider frame official, now.  */
+  f->can_set_window_size = true;
+
+  if (x_width > 0)
+    SET_FRAME_WIDTH (f, x_width);
+  if (x_height > 0)
+    SET_FRAME_HEIGHT (f, x_height);
+
+  /* Tell the server what size and position, etc, we want, and how
+     badly we want them.  This should be done after we have the menu
+     bar so that its size can be taken into account.  */
+  block_input ();
+#ifndef HAVE_GTK4
+  x_wm_set_size_hint (f, window_prompting, false);
+#endif
+  unblock_input ();
+
+  adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f), 0, true,
+                     Qx_create_frame_2);
+
+  /* Process fullscreen parameter here in the hope that normalizing a
+     fullheight/fullwidth frame will produce the size set by the last
+     adjust_frame_size call.  */
+  gui_default_parameter (f, parms, Qfullscreen, Qnil, "fullscreen",
+                         "Fullscreen", RES_TYPE_SYMBOL);
+
+  /* Make the window appear on the frame and enable display, unless
+     the caller says not to.  However, with explicit parent, Emacs
+     cannot control visibility, so don't try.  */
+  if (!FRAME_X_OUTPUT (f)->explicit_parent)
+    {
+      Lisp_Object visibility = gui_display_get_arg (dpyinfo, parms, Qvisibility,
+                                                    0, 0, RES_TYPE_SYMBOL);
+
+      if (EQ (visibility, Qicon))
+        pgtk_iconify_frame (f);
+      else
+        {
+          if (EQ (visibility, Qunbound))
+            visibility = Qt;
+
+          if (!NILP (visibility))
+            pgtk_make_frame_visible (f);
+        }
+
+      store_frame_param (f, Qvisibility, visibility);
+    }
+
+  /* Works iff frame has been already mapped.  */
+  gui_default_parameter (f, parms, Qskip_taskbar, Qnil, NULL, NULL,
+                         RES_TYPE_BOOLEAN);
+  /* The `z-group' parameter works only for visible frames.  */
+  gui_default_parameter (f, parms, Qz_group, Qnil, NULL, NULL, RES_TYPE_SYMBOL);
+
+  /* Initialize `default-minibuffer-frame' in case this is the first
+     frame on this terminal.  */
+  if (FRAME_HAS_MINIBUF_P (f)
+      && (!FRAMEP (KVAR (kb, Vdefault_minibuffer_frame))
+          || !FRAME_LIVE_P (XFRAME (KVAR (kb, Vdefault_minibuffer_frame)))))
+    kset_default_minibuffer_frame (kb, frame);
+
+  /* Create the menu bar.  */
+  if (!minibuffer_only && FRAME_EXTERNAL_MENU_BAR (f))
+    {
+      /* If this signals an error, we haven't set size hints for the
+         frame and we didn't make it visible.  */
+      initialize_frame_menubar (f);
+    }
+
+  /* All remaining specified parameters, which have not been "used"
+     by gui_display_get_arg and friends, now go in the misc. alist of the frame.
+   */
+  for (tem = parms; CONSP (tem); tem = XCDR (tem))
+    if (CONSP (XCAR (tem)) && !NILP (XCAR (XCAR (tem))))
+      fset_param_alist (f, Fcons (XCAR (tem), f->param_alist));
+#ifdef HAVE_GTK4
+  FRAME_X_OUTPUT (f)->keep_above = NULL;
+#endif
+#ifdef HAVE_GTK3
+  FRAME_X_OUTPUT (f)->border_color_css_provider = NULL;
+#endif
+
+  FRAME_X_OUTPUT (f)->cr_surface_visible_bell = NULL;
+  FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
+
+  /* Make sure windows on this frame appear in calls to next-window
+     and similar functions.  */
+  Vwindow_list = Qnil;
+#ifndef HAVE_GTK4
+  fset_parent_frame (f, parent_frame);
+#endif
+#ifdef HAVE_GTK4
+  pgtk_dnd_init (f);
+  FRAME_X_OUTPUT (f)->ts_up_flag = false;
+  if (NILP (Vtool_bar_mode))
+    free_frame_tool_bar (f);
+#endif
+  return unbind_to (count, frame);
+}
+
+#if 0
+static int
+pgtk_window_is_ancestor (PGTKWindow *win, PGTKWindow *candidate)
+/* Test whether CANDIDATE is an ancestor window of WIN. */
+{
+  if (candidate == NULL)
+    return 0;
+  else if (win == candidate)
+    return 1;
+  else
+    return pgtk_window_is_ancestor(win, [candidate parentWindow]);
+}
+#endif
+
+/**
+ * x_frame_restack:
+ *
+ * Restack frame F1 below frame F2, above if ABOVE_FLAG is non-nil.  In
+ * practice this is a two-step action: The first step removes F1's
+ * window-system window from the display.  The second step reinserts
+ * F1's window below (above if ABOVE_FLAG is true) that of F2.
+ */
+static void
+pgtk_frame_restack (struct frame *f1, struct frame *f2, bool above_flag)
+{
+  block_input ();
+#ifndef HAVE_GTK4
+  xg_frame_restack (f1, f2, above_flag);
+#else
+  egtk_frame_restack (f1, f2, above_flag);
+#endif
+  unblock_input ();
+}
+
+DEFUN ("pgtk-frame-restack", Fpgtk_frame_restack, Spgtk_frame_restack, 2, 3, 0,
+       doc: /* Restack FRAME1 below FRAME2.
+This means that if both frames are visible and the display areas of
+these frames overlap, FRAME2 (partially) obscures FRAME1.  If optional
+third argument ABOVE is non-nil, restack FRAME1 above FRAME2.  This
+means that if both frames are visible and the display areas of these
+frames overlap, FRAME1 (partially) obscures FRAME2.
+
+This may be thought of as an atomic action performed in two steps: The
+first step removes FRAME1's window-step window from the display.  The
+second step reinserts FRAME1's window below (above if ABOVE is true)
+that of FRAME2.  Hence the position of FRAME2 in its display's Z
+\(stacking) order relative to all other frames excluding FRAME1 remains
+unaltered.
+
+Some window managers may refuse to restack windows.  */)
+(Lisp_Object frame1, Lisp_Object frame2, Lisp_Object above)
+{
+  struct frame *f1 = decode_live_frame (frame1);
+  struct frame *f2 = decode_live_frame (frame2);
+
+  if (!(FRAME_GTK_OUTER_WIDGET (f1) && FRAME_GTK_OUTER_WIDGET (f2)))
+    error ("Cannot restack frames");
+  pgtk_frame_restack (f1, f2, !NILP (above));
+  return Qt;
+}
+
+#ifdef HAVE_GSETTINGS
+
+#define RESOURCE_KEY_MAX_LEN 128
+#define SCHEMA_ID "org.gnu.emacs.defaults"
+#define PATH_FOR_CLASS_TYPE "/org/gnu/emacs/defaults-by-class/"
+#define PATH_PREFIX_FOR_NAME_TYPE "/org/gnu/emacs/defaults-by-name/"
+
+static inline int
+pgtk_is_lower_char (int c)
+{
+  return c >= 'a' && c <= 'z';
+}
+
+static inline int
+pgtk_is_upper_char (int c)
+{
+  return c >= 'A' && c <= 'Z';
+}
+
+static inline int
+pgtk_is_numeric_char (int c)
+{
+  return c >= '0' && c <= '9';
+}
+
+#ifndef GLIB_VERSION_2_40
+static gboolean
+g_settings_schema_has_key (GSettingsSchema *schema, const gchar *key)
+{
+  for (char **keys = g_settings_list_keys (schema); keys; ++keys)
+    {
+      if (strcmp (keys[0], key))
+        return true;
+    }
+  return false;
+}
+#endif
+
+static GSettings *
+parse_resource_key (const char *res_key, char *setting_key)
+{
+  char path[32 + RESOURCE_KEY_MAX_LEN];
+  const char *sp = res_key;
+  char *dp;
+
+  /*
+   * res_key="emacs.cursorBlink"
+   *   -> path="/org/gnu/emacs/defaults-by-name/emacs/"
+   *      setting_key="cursor-blink"
+   *
+   * res_key="Emacs.CursorBlink"
+   *   -> path="/org/gnu/emacs/defaults-by-class/"
+   *      setting_key="cursor-blink"
+   *
+   * Returns GSettings* if setting_key exists in schema, otherwise NULL.
+   */
+
+  /* generate path */
+  if (pgtk_is_upper_char (*sp))
+    {
+      /* First letter is upper case. It should be "Emacs",
+       * but don't care.
+       */
+      strcpy (path, PATH_FOR_CLASS_TYPE);
+      while (*sp != '\0')
+        {
+          if (*sp == '.')
+            break;
+          sp++;
+        }
+    }
+  else
+    {
+      strcpy (path, PATH_PREFIX_FOR_NAME_TYPE);
+      dp = path + strlen (path);
+      while (*sp != '\0')
+        {
+          int c = *sp;
+          if (c == '.')
+            break;
+          if (pgtk_is_lower_char (c))
+            (void) 0; /* lower -> NOP */
+          else if (pgtk_is_upper_char (c))
+            c = c - 'A' + 'a'; /* upper -> lower */
+          else if (pgtk_is_numeric_char (c))
+            (void) 0; /* numeric -> NOP */
+          else
+            return NULL; /* invalid */
+          *dp++ = c;
+          sp++;
+        }
+      *dp++ = '/'; /* must ends with '/' */
+      *dp = '\0';
+    }
+
+  if (*sp++ != '.')
+    return NULL;
+
+  /* generate setting_key */
+  dp = setting_key;
+  while (*sp != '\0')
+    {
+      int c = *sp;
+      if (pgtk_is_lower_char (c))
+        (void) 0; /* lower -> NOP */
+      else if (pgtk_is_upper_char (c))
+        {
+          c = c - 'A' + 'a'; /* upper -> lower */
+          if (dp != setting_key)
+            *dp++ = '-'; /* store '-' unless first char */
+        }
+      else if (pgtk_is_numeric_char (c))
+        (void) 0; /* numeric -> NOP */
+      else
+        return NULL; /* invalid */
+
+      *dp++ = c;
+      sp++;
+    }
+  *dp = '\0';
+
+  /* check existence of setting_key */
+  GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default ();
+  GSettingsSchema *scm
+    = g_settings_schema_source_lookup (ssrc, SCHEMA_ID, FALSE);
+  if (scm == NULL)
+    {
+      return NULL;
+    }
+
+  if (!g_settings_schema_has_key (scm, setting_key))
+    {
+      g_settings_schema_unref (scm);
+      return NULL;
+    }
+
+  /* create GSettings, and return it */
+  GSettings *gs = g_settings_new_full (scm, NULL, path);
+
+  g_settings_schema_unref (scm);
+  return gs;
+}
+
+const char *
+pgtk_get_defaults_value (const char *key)
+{
+  char skey[(RESOURCE_KEY_MAX_LEN + 1) * 2];
+
+  if (strlen (key) >= RESOURCE_KEY_MAX_LEN)
+    error ("resource key too long.");
+
+  GSettings *gs = parse_resource_key (key, skey);
+  if (gs == NULL)
+    {
+      return NULL;
+    }
+
+  gchar *str = g_settings_get_string (gs, skey);
+
+  /* There is no timing to free str.
+   * So, copy it here and free it.
+   *
+   * MEMO: Resource values for emacs shouldn't need such a long string value.
+   */
+  static char holder[128];
+  strncpy (holder, str, 128);
+  holder[127] = '\0';
+
+  g_object_unref (gs);
+  g_free (str);
+  return holder[0] != '\0' ? holder : NULL;
+}
+
+static void
+pgtk_set_defaults_value (const char *key, const char *value)
+{
+  char skey[(RESOURCE_KEY_MAX_LEN + 1) * 2];
+
+  if (strlen (key) >= RESOURCE_KEY_MAX_LEN)
+    error ("resource key too long.");
+
+  GSettings *gs = parse_resource_key (key, skey);
+  if (gs == NULL)
+    error ("unknown resource key.");
+
+  if (value != NULL)
+    {
+      g_settings_set_string (gs, skey, value);
+    }
+  else
+    {
+      g_settings_reset (gs, skey);
+    }
+
+  g_object_unref (gs);
+}
+
+#undef RESOURCE_KEY_MAX_LEN
+#undef SCHEMA_ID
+#undef PATH_FOR_CLASS_TYPE
+#undef PATH_PREFIX_FOR_NAME_TYPE
+
+#else /* not HAVE_GSETTINGS */
+
+const char *
+pgtk_get_defaults_value (const char *key)
+{
+  return NULL;
+}
+
+static void
+pgtk_set_defaults_value (const char *key, const char *value)
+{
+  error ("gsettings not supported.");
+}
+
+#endif
+
+DEFUN ("pgtk-set-resource", Fpgtk_set_resource, Spgtk_set_resource, 2, 2, 0,
+       doc: /* Set the value of ATTRIBUTE, of class CLASS, as VALUE, into defaults database. */)
+(Lisp_Object attribute, Lisp_Object value)
+{
+  check_window_system (NULL);
+
+  CHECK_STRING (attribute);
+  if (!NILP (value))
+    CHECK_STRING (value);
+
+  char *res = SSDATA (Vx_resource_name);
+  char *attr = SSDATA (attribute);
+  if (attr[0] >= 'A' && attr[0] <= 'Z')
+    res = SSDATA (Vx_resource_class);
+
+  char *key = g_strdup_printf ("%s.%s", res, attr);
+
+  pgtk_set_defaults_value (key, NILP (value) ? NULL : SSDATA (value));
+
+  return Qnil;
+}
+
+DEFUN ("x-server-max-request-size", Fx_server_max_request_size,
+       Sx_server_max_request_size, 0, 1, 0, doc
+       : /* This function is a no-op.  It is only present for completeness.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  /* This function has no real equivalent under PGTK.  Return nil to
+     indicate this. */
+  return Qnil;
+}
+
+DEFUN ("x-select-font", Fx_select_font, Sx_select_font, 0, 2, 0,
+       doc: /* Read a font using a GTK dialog.
+Return either a font spec (for GTK versions >= 3.2) or a string
+containing a GTK-style font name.
+
+FRAME is the frame on which to pop up the font chooser.  If omitted or
+nil, it defaults to the selected frame. */)
+  (Lisp_Object frame, Lisp_Object ignored)
+{
+  struct frame *f = decode_window_system_frame (frame);
+  Lisp_Object font;
+  Lisp_Object font_param;
+  char *default_name = NULL;
+  ptrdiff_t count = SPECPDL_INDEX ();
+
+  /* Prevent redisplay.  */
+  specbind (Qinhibit_redisplay, Qt);
+
+  block_input ();
+
+  XSETFONT (font, FRAME_FONT (f));
+  font_param = Ffont_get (font, QCname);
+  if (STRINGP (font_param))
+    default_name = xlispstrdup (font_param);
+  else
+    {
+      font_param = Fframe_parameter (frame, Qfont_parameter);
+      if (STRINGP (font_param))
+        default_name = xlispstrdup (font_param);
+    }
+#ifndef HAVE_GTK4
+  font = xg_get_font (f, default_name);
+#else
+  font = egtk_get_font (f, default_name);
+#endif
+  xfree (default_name);
+
+  unblock_input ();
+
+  if (NILP (font))
+    quit ();
+
+  return unbind_to (count, font);
+}
+
+DEFUN ("x-server-vendor", Fx_server_vendor, Sx_server_vendor, 0, 1, 0,
+       doc: /* Return the "vendor ID" string of the display server TERMINAL.
+\(Labeling every distributor as a "vendor" embodies the false assumption
+that operating systems cannot be developed and distributed noncommercially.)
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return build_string ("The GNU project's GTK toolkit.");
+}
+
+DEFUN ("x-server-version", Fx_server_version, Sx_server_version, 0, 1, 0,
+       doc: /* Return the version numbers of the server of display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return list3 (make_fixnum (GTK_MAJOR_VERSION),
+                make_fixnum (GTK_MINOR_VERSION),
+                make_fixnum (GTK_MICRO_VERSION));
+}
+
+DEFUN ("x-display-screens", Fx_display_screens, Sx_display_screens, 0, 1, 0,
+       doc: /* Return the number of screens on the display server TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+Note: "screen" here is not in X11's.  For the number of physical monitors,
+ use `(length \(display-monitor-attributes-list TERMINAL))' instead.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return make_fixnum (1);
+}
+
+DEFUN ("x-display-mm-height", Fx_display_mm_height, Sx_display_mm_height, 0, 1, 0,
+       doc: /* Return the height in millimeters of the the display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+On \"multi-monitor\" setups this refers to the height in millimeters for
+all physical monitors associated with TERMINAL.  To get information
+for each physical monitor, use `display-monitor-attributes-list'.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+#if defined(GDK_VERSION_3_22) || defined(HAVE_GTK4)
+#ifndef HAVE_GTK4
+  GdkMonitor *gmon = gdk_display_get_monitor_at_point (gdpy, 0, 0);
+#else
+  GdkMonitor *gmon = gdk_display_get_monitor (gdpy, 0);
+#endif
+  return make_fixnum (gdk_monitor_get_height_mm (gmon));
+#else
+  return make_fixnum (
+    gdk_screen_get_height_mm (gdk_display_get_default_screen (gdpy)));
+#endif
+}
+
+DEFUN ("x-display-mm-width", Fx_display_mm_width, Sx_display_mm_width, 0, 1, 0,
+       doc: /* Return the width in millimeters of the the display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+On \"multi-monitor\" setups this refers to the width in millimeters for
+all physical monitors associated with TERMINAL.  To get information
+for each physical monitor, use `display-monitor-attributes-list'.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+#if defined(GDK_VERSION_3_22) || defined(HAVE_GTK4)
+#ifndef HAVE_GTK4
+  GdkMonitor *gmon = gdk_display_get_monitor_at_point (gdpy, 0, 0);
+#else
+  GdkMonitor *gmon = gdk_display_get_monitor (gdpy, 0);
+#endif
+  return make_fixnum (gdk_monitor_get_width_mm (gmon));
+#else
+  return make_fixnum (
+    gdk_screen_get_width_mm (gdk_display_get_default_screen (gdpy)));
+#endif
+}
+
+DEFUN ("x-display-backing-store", Fx_display_backing_store,
+       Sx_display_backing_store, 0, 1, 0,
+       doc: /* Return an indication of whether the the display TERMINAL does backing store.
+The value may be `buffered', `retained', or `non-retained'.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return Qnil;
+}
+
+DEFUN ("x-display-visual-class", Fx_display_visual_class,
+       Sx_display_visual_class, 0, 1, 0,
+       doc: /* Return the visual class of the the display TERMINAL.
+The value is one of the symbols `static-gray', `gray-scale',
+`static-color', `pseudo-color', `true-color', or `direct-color'.
+
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+On PGTK, always return true-color.  */)
+(Lisp_Object terminal) { return intern ("true-color"); }
+
+DEFUN ("x-display-save-under", Fx_display_save_under,
+       Sx_display_save_under, 0, 1, 0,
+       doc: /* Return t if TERMINAL supports the save-under feature.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return Qnil;
+}
+
+DEFUN ("x-open-connection", Fx_open_connection, Sx_open_connection,
+       1, 3, 0,
+       doc: /* Open a connection to a display server.
+DISPLAY is the name of the display to connect to.
+Optional second arg XRM-STRING is a string of resources in xrdb format.
+If the optional third arg MUST-SUCCEED is non-nil,
+terminate Emacs if we can't open the connection.  */)
+(Lisp_Object display, Lisp_Object resource_string, Lisp_Object must_succeed)
+{
+  struct pgtk_display_info *dpyinfo;
+
+  if (NILP (display))
+    display = build_string ("");
+
+  CHECK_STRING (display);
+
+  nxatoms_of_pgtkselect ();
+  dpyinfo = pgtk_term_init (display, SSDATA (Vx_resource_name));
+  if (dpyinfo == 0)
+    {
+      if (!NILP (must_succeed))
+        fatal ("Display on %s not responding.\n", SSDATA (display));
+      else
+        error ("Display on %s not responding.\n", SSDATA (display));
+    }
+
+  return Qnil;
+}
+
+DEFUN ("x-close-connection", Fx_close_connection, Sx_close_connection,
+       1, 1, 0,
+       doc: /* Close the connection to TERMINAL's display server.
+For TERMINAL, specify a terminal object, a frame or a display name (a
+string).  If TERMINAL is nil, that stands for the selected frame's
+terminal.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+
+  if (dpyinfo->reference_count > 0)
+    error ("Display still has frames on it");
+
+  pgtk_delete_terminal (dpyinfo->terminal);
+
+  return Qnil;
+}
+
+DEFUN ("x-display-list", Fx_display_list, Sx_display_list, 0, 0, 0, doc
+       : /* Return the list of display names that Emacs has connections to.  */)
+(void)
+{
+  Lisp_Object result = Qnil;
+  struct pgtk_display_info *ndi;
+
+  for (ndi = x_display_list; ndi; ndi = ndi->next)
+    result = Fcons (XCAR (ndi->name_list_element), result);
+
+  return result;
+}
+
+DEFUN ("pgtk-hide-others", Fpgtk_hide_others, Spgtk_hide_others, 0, 0, 0, doc
+       : /* Hides all applications other than Emacs.  */)
+(void)
+{
+  check_window_system (NULL);
+  return Qnil;
+}
+
+DEFUN ("pgtk-hide-emacs", Fpgtk_hide_emacs, Spgtk_hide_emacs,
+       1, 1, 0,
+       doc: /* If ON is non-nil, the entire Emacs application is hidden.
+Otherwise if Emacs is hidden, it is unhidden.
+If ON is equal to `activate', Emacs is unhidden and becomes
+the active application.  */)
+(Lisp_Object on)
+{
+  check_window_system (NULL);
+  return Qnil;
+}
+
+DEFUN ("pgtk-font-name", Fpgtk_font_name, Spgtk_font_name, 1, 1, 0,
+       doc: /* Determine font PostScript or family name for font NAME.
+NAME should be a string containing either the font name or an XLFD
+font descriptor.  If string contains `fontset' and not
+`fontset-startup', it is left alone. */)
+(Lisp_Object name)
+{
+  char *nm;
+  CHECK_STRING (name);
+  nm = SSDATA (name);
+
+  if (nm[0] != '-')
+    return name;
+  if (strstr (nm, "fontset") && !strstr (nm, "fontset-startup"))
+    return name;
+
+  char *str = pgtk_xlfd_to_fontname (SSDATA (name));
+  name = build_string (str);
+  xfree (str);
+  return name;
+}
+
+/* ==========================================================================
+
+    Miscellaneous functions not called through hooks
+
+   ========================================================================== */
+
+/* called from frame.c */
+struct pgtk_display_info *
+check_x_display_info (Lisp_Object frame)
+{
+  return check_pgtk_display_info (frame);
+}
+
+void
+pgtk_set_scroll_bar_default_width (struct frame *f)
+{
+  int unit = FRAME_COLUMN_WIDTH (f);
+  int minw =
+#ifndef HAVE_GTK4
+    xg_get_default_scrollbar_width (f);
+#else
+    egtk_get_default_scrollbar_width (f);
+#endif
+  /* A minimum width of 14 doesn't look good for toolkit scroll bars.  */
+  FRAME_CONFIG_SCROLL_BAR_COLS (f) = (minw + unit - 1) / unit;
+  FRAME_CONFIG_SCROLL_BAR_WIDTH (f) = minw;
+}
+
+void
+pgtk_set_scroll_bar_default_height (struct frame *f)
+{
+  int height = FRAME_LINE_HEIGHT (f);
+  int min_height =
+#ifndef HAVE_GTK4
+    xg_get_default_scrollbar_height (f);
+#else
+    egtk_get_default_scrollbar_height (f);
+#endif
+  /* A minimum height of 14 doesn't look good for toolkit scroll bars.  */
+  FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) = min_height;
+  FRAME_CONFIG_SCROLL_BAR_LINES (f) = (min_height + height - 1) / height;
+}
+
+/* terms impl this instead of x-get-resource directly */
+const char *
+pgtk_get_string_resource (XrmDatabase rdb, const char *name, const char *class)
+{
+  check_window_system (NULL);
+
+  if (inhibit_x_resources)
+    /* --quick was passed, so this is a no-op.  */
+    return NULL;
+
+  const char *res = pgtk_get_defaults_value (name);
+  if (res == NULL)
+    res = pgtk_get_defaults_value (class);
+
+  if (res == NULL)
+    return NULL;
+
+  if (c_strncasecmp (res, "YES", 3) == 0)
+    return "true";
+
+  if (c_strncasecmp (res, "NO", 2) == 0)
+    return "false";
+
+  return res;
+}
+
+Lisp_Object
+x_get_focus_frame (struct frame *frame)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
+  Lisp_Object focus;
+
+  if (!dpyinfo->x_focus_frame)
+    return Qnil;
+
+  XSETFRAME (focus, dpyinfo->x_focus_frame);
+  return focus;
+}
+
+/* ==========================================================================
+
+    Lisp definitions that, for whatever reason, we can't alias as 'ns-XXX'.
+
+   ========================================================================== */
+
+DEFUN ("xw-color-defined-p", Fxw_color_defined_p, Sxw_color_defined_p, 1, 2, 0,
+       doc: /* Internal function called by `color-defined-p', which see.  */)
+(Lisp_Object color, Lisp_Object frame)
+{
+  if (!NILP (frame))
+    CHECK_FRAME (frame);
+  Emacs_Color col;
+  return (NILP (frame)
+            ? pgtk_lisp_to_color (color, &col)
+            : pgtk_lisp_to_color_with_frame (color, &col, XFRAME (frame)))
+           ? Qnil
+           : Qt;
+}
+
+DEFUN ("xw-color-values", Fxw_color_values, Sxw_color_values, 1, 2, 0,
+       doc: /* Internal function called by `color-values', which see.  */)
+(Lisp_Object color, Lisp_Object frame)
+{
+  Emacs_Color col;
+
+  CHECK_STRING (color);
+  if (!NILP (frame))
+    CHECK_FRAME (frame);
+
+  block_input ();
+
+  if (NILP (frame)
+      ? pgtk_lisp_to_color (color, &col)
+      : pgtk_lisp_to_color_with_frame (color, &col, XFRAME (frame)))
+    {
+      unblock_input ();
+      return Qnil;
+    }
+
+  unblock_input ();
+
+  return list3i (col.red, col.green, col.blue);
+}
+
+DEFUN ("xw-display-color-p", Fxw_display_color_p, Sxw_display_color_p, 0, 1, 0,
+       doc
+       : /* Internal function called by `display-color-p', which see.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return Qt;
+}
+
+DEFUN ("x-display-grayscale-p", Fx_display_grayscale_p, Sx_display_grayscale_p,
+       0, 1, 0,
+       doc: /* Return t if the display supports shades of gray.
+Note that color displays do support shades of gray.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal) { return Qnil; }
+
+DEFUN ("x-display-pixel-width", Fx_display_pixel_width, Sx_display_pixel_width,
+       0, 1, 0,
+       doc: /* Return the width in pixels of the display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+On \"multi-monitor\" setups this refers to the pixel width for all
+physical monitors associated with TERMINAL.  To get information for
+each physical monitor, use `display-monitor-attributes-list'.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+
+  return make_fixnum (x_display_pixel_width (dpyinfo));
+}
+
+DEFUN ("x-display-pixel-height", Fx_display_pixel_height,
+       Sx_display_pixel_height, 0, 1, 0,
+       doc: /* Return the height in pixels of the display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+On \"multi-monitor\" setups this refers to the pixel height for all
+physical monitors associated with TERMINAL.  To get information for
+each physical monitor, use `display-monitor-attributes-list'.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+
+  return make_fixnum (x_display_pixel_height (dpyinfo));
+}
+
+DEFUN ("pgtk-display-monitor-attributes-list",
+       Fpgtk_display_monitor_attributes_list,
+       Spgtk_display_monitor_attributes_list,
+       0, 1, 0,
+       doc: /* Return a list of physical monitor attributes on the X display TERMINAL.
+
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+Internal use only, use `display-monitor-attributes-list' instead.  */)
+(Lisp_Object terminal)
+{
+#if defined(GDK_VERSION_3_22) || HAVE_GTK4
+  struct terminal *term = decode_live_terminal (terminal);
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+  GdkMonitor **gmons;
+  int i, n_monitors, primary_index;
+  struct MonitorInfo *monitors;
+  Lisp_Object monitor_frames = Qnil;
+  Lisp_Object frame = Qnil, rest = Qnil;
+  Lisp_Object rv = Qnil;
+
+  if (term->type != output_pgtk)
+    return Qnil;
+
+  n_monitors = gdk_display_get_n_monitors (gdpy);
+  if (n_monitors == 0)
+    return Qnil;
+
+  gmons = xmalloc (sizeof *gmons * n_monitors);
+  for (i = 0; i < n_monitors; i++)
+    gmons[i] = gdk_display_get_monitor (gdpy, i);
+
+  monitors = xzalloc (n_monitors * sizeof *monitors);
+  for (i = 0; i < n_monitors; i++)
+    {
+      struct MonitorInfo *mon = &monitors[i];
+      GdkMonitor *gmon = gmons[i];
+      if (gmon != NULL)
+        {
+          GdkRectangle geom;
+          gdk_monitor_get_geometry (gmon, &geom);
+          mon->geom.x = geom.x;
+          mon->geom.y = geom.y;
+          mon->geom.width = geom.width;
+          mon->geom.height = geom.height;
+
+          gdk_monitor_get_workarea (gmon, &geom);
+          mon->work.x = geom.x;
+          mon->work.y = geom.y;
+          mon->work.width = geom.width;
+          mon->work.height = geom.height;
+
+          mon->mm_width = gdk_monitor_get_width_mm (gmon);
+          mon->mm_height = gdk_monitor_get_height_mm (gmon);
+
+          mon->name = xstrdup (gdk_monitor_get_model (gmon));
+        }
+    }
+
+  monitor_frames = Fmake_vector (make_fixnum (n_monitors), Qnil);
+  FOR_EACH_FRAME (rest, frame)
+  {
+    struct frame *f = XFRAME (frame);
+
+    if (FRAME_PGTK_P (f))
+      {
+        GtkWidget *widget = FRAME_GTK_WIDGET (f);
+        GdkMonitor *gmon =
+#ifdef HAVE_GTK4
+          gdk_display_get_monitor_at_surface
+#else
+          gdk_display_get_monitor_at_window
+#endif
+          (gdpy,
+#ifdef HAVE_GTK4
+           EGTK_WIDGET_GET_WINDOW (widget)
+#else
+           gtk_widget_get_window (widget)
+#endif
+          );
+
+        if (gmon != NULL)
+          {
+            for (i = 0; i < n_monitors; i++)
+              {
+                if (gmons[i] == gmon)
+                  {
+                    ASET (monitor_frames, i,
+                          Fcons (frame, AREF (monitor_frames, i)));
+                    break;
+                  }
+              }
+          }
+      }
+  }
+
+  primary_index = -1;
+  for (i = 0; i < n_monitors; i++)
+    {
+      if (gmons[i] != NULL &&
+#ifndef HAVE_GTK4
+          gdk_monitor_is_primary (gmons[i])
+#else
+          false
+#endif
+      )
+        {
+          primary_index = i;
+          break;
+        }
+    }
+
+  rv = make_monitor_attribute_list (monitors, n_monitors, primary_index,
+                                    monitor_frames, "Gdk");
+
+  free_monitors (monitors, n_monitors);
+  xfree (gmons);
+
+  return rv;
+#else
+  return Qnil;
+#endif
+}
+
+DEFUN ("x-display-planes", Fx_display_planes, Sx_display_planes,
+       0, 1, 0,
+       doc: /* Return the number of bitplanes of the display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  check_pgtk_display_info (terminal);
+  return make_fixnum (32);
+}
+
+DEFUN ("x-display-color-cells", Fx_display_color_cells, Sx_display_color_cells,
+       0, 1, 0,
+       doc: /* Returns the number of color cells of the display TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+  /* We force 24+ bit depths to 24-bit to prevent an overflow.  */
+  return make_fixnum (1 << min (dpyinfo->n_planes, 24));
+}
+
+/***********************************************************************
+                                Tool tips
+ ***********************************************************************/
+
+/* The frame of a currently visible tooltip.  */
+
+Lisp_Object tip_frame;
+
+/* If non-nil, a timer started that hides the last tooltip when it
+   fires.  */
+
+static Lisp_Object tip_timer;
+
+/* Compute where to display tip frame F.  PARMS is the list of frame
+   parameters for F.  DX and DY are specified offsets from the current
+   location of the mouse.  WIDTH and HEIGHT are the width and height
+   of the tooltip.  Return coordinates relative to the root window of
+   the display in *ROOT_X, and *ROOT_Y.  */
+
+static void
+compute_tip_xy (struct frame *f, Lisp_Object parms, Lisp_Object dx,
+                Lisp_Object dy, int width, int height,
+#ifndef HAVE_GTK4
+                int *root_x, int *root_y
+#else
+                double *root_x, double *root_y
+#endif
+)
+{
+  Lisp_Object left, top, right, bottom;
+  int min_x, min_y, max_x, max_y = -1;
+
+  /* User-specified position?  */
+  left = Fcdr (Fassq (Qleft, parms));
+  top = Fcdr (Fassq (Qtop, parms));
+  right = Fcdr (Fassq (Qright, parms));
+  bottom = Fcdr (Fassq (Qbottom, parms));
+
+  /* Move the tooltip window where the mouse pointer is.  Resize and
+     show it.  */
+  if ((!INTEGERP (left) && !INTEGERP (right))
+      || (!INTEGERP (top) && !INTEGERP (bottom)))
+    {
+      Lisp_Object frame, attributes, monitor, geometry;
+#ifdef HAVE_GTK3
+#if defined(GDK_VERSION_3_20) || defined(HAVE_GTK4)
+      GdkSeat *seat
+        = gdk_display_get_default_seat (FRAME_DISPLAY_INFO (f)->gdpy);
+      GdkDevice *dev = gdk_seat_get_pointer (seat);
+#else
+      GdkDeviceManager *man
+        = gdk_display_get_device_manager (FRAME_DISPLAY_INFO (f)->gdpy);
+      GdkDevice *dev = gdk_device_manager_get_client_pointer (man);
+#endif
+#endif /* HAVE_GTK3 */
+#ifndef HAVE_GTK4
+      GdkScreen *scr;
+#endif
+      block_input ();
+#ifdef HAVE_GTK3
+#ifndef HAVE_GTK4
+      gdk_device_get_position (dev, &scr, root_x, root_y);
+#else
+      gdk_device_get_surface_at_position (dev, root_x, root_y);
+#endif
+#else
+      gdk_display_get_pointer (FRAME_DISPLAY_INFO (f)->gdpy, &scr, root_x,
+                               root_y, NULL);
+#endif
+      unblock_input ();
+      XSETFRAME (frame, f);
+      attributes = Fpgtk_display_monitor_attributes_list (frame);
+
+      /* Try to determine the monitor where the mouse pointer is and
+         its geometry.  See bug#22549.  */
+      while (CONSP (attributes))
+        {
+          monitor = XCAR (attributes);
+          geometry = Fassq (Qgeometry, monitor);
+          if (CONSP (geometry))
+            {
+              min_x = XFIXNUM (Fnth (make_fixnum (1), geometry));
+              min_y = XFIXNUM (Fnth (make_fixnum (2), geometry));
+              max_x = min_x + XFIXNUM (Fnth (make_fixnum (3), geometry));
+              max_y = min_y + XFIXNUM (Fnth (make_fixnum (4), geometry));
+              if (min_x <= *root_x && *root_x < max_x && min_y <= *root_y
+                  && *root_y < max_y)
+                {
+                  break;
+                }
+              max_y = -1;
+            }
+
+          attributes = XCDR (attributes);
+        }
+    }
+
+  /* It was not possible to determine the monitor's geometry, so we
+     assign some sane defaults here: */
+  if (max_y < 0)
+    {
+      min_x = 0;
+      min_y = 0;
+      max_x = x_display_pixel_width (FRAME_DISPLAY_INFO (f));
+      max_y = x_display_pixel_height (FRAME_DISPLAY_INFO (f));
+    }
+  if (INTEGERP (top))
+    *root_y = XFIXNUM (top);
+  else if (INTEGERP (bottom))
+    *root_y = XFIXNUM (bottom) - height;
+  else if (*root_y + XFIXNUM (dy) <= min_y)
+    *root_y = min_y; /* Can happen for negative dy */
+  else if (*root_y + XFIXNUM (dy) + height <= max_y)
+    /* It fits below the pointer */
+    *root_y += XFIXNUM (dy);
+  else if (height + XFIXNUM (dy) + min_y <= *root_y)
+    /* It fits above the pointer.  */
+    *root_y -= height + XFIXNUM (dy);
+  else
+    /* Put it on the top.  */
+    *root_y = min_y;
+  if (INTEGERP (left))
+    *root_x = XFIXNUM (left);
+  else if (INTEGERP (right))
+    *root_x = XFIXNUM (right) - width;
+  else if (*root_x + XFIXNUM (dx) <= min_x)
+    *root_x = 0; /* Can happen for negative dx */
+  else if (*root_x + XFIXNUM (dx) + width <= max_x)
+    /* It fits to the right of the pointer.  */
+    *root_x += XFIXNUM (dx);
+  else if (width + XFIXNUM (dx) + min_x <= *root_x)
+    /* It fits to the left of the pointer.  */
+    *root_x -= width + XFIXNUM (dx);
+#ifndef HAVE_GTK4
+  else
+    /* Put it left justified on the screen -- it ought to fit that way.  */
+    *root_x = min_x;
+#endif
+}
+
+/* Hide tooltip.  Delete its frame if DELETE is true.  */
+static Lisp_Object
+x_hide_tip (bool delete)
+{
+  if (!NILP (tip_timer))
+    {
+      call1 (Qcancel_timer, tip_timer);
+      tip_timer = Qnil;
+    }
+
+  if (NILP (tip_frame)
+      || (!delete &&FRAMEP (tip_frame)
+          && !FRAME_VISIBLE_P (XFRAME (tip_frame))))
+    return Qnil;
+  else
+    {
+      ptrdiff_t count;
+      Lisp_Object was_open = Qnil;
+
+      count = SPECPDL_INDEX ();
+      specbind (Qinhibit_redisplay, Qt);
+      specbind (Qinhibit_quit, Qt);
+
+      {
+        /* When using system tooltip, tip_frame is the Emacs frame on
+           which the tip is shown.  */
+        struct frame *f = XFRAME (tip_frame);
+
+#ifndef HAVE_GTK4
+        if (FRAME_LIVE_P (f) && xg_hide_tooltip (f))
+#else
+        if (FRAME_LIVE_P (f) && egtk_hide_tooltip (f))
+#endif
+          {
+            tip_frame = Qnil;
+            was_open = Qt;
+          }
+      }
+
+      if (FRAMEP (tip_frame))
+        {
+          if (delete)
+            {
+              delete_frame (tip_frame, Qnil);
+              tip_frame = Qnil;
+            }
+          else
+            pgtk_make_frame_invisible (XFRAME (tip_frame));
+
+          was_open = Qt;
+        }
+      else
+        tip_frame = Qnil;
+
+      return unbind_to (count, was_open);
+    }
+}
+
+DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
+       doc: /* Show STRING in a "tooltip" window on frame FRAME.
+A tooltip window is a small X window displaying a string.
+
+This is an internal function; Lisp code should call `tooltip-show'.
+
+FRAME nil or omitted means use the selected frame.
+
+PARMS is an optional list of frame parameters which can be used to
+change the tooltip's appearance.
+
+Automatically hide the tooltip after TIMEOUT seconds.  TIMEOUT nil
+means use the default timeout of 5 seconds.
+
+If the list of frame parameters PARMS contains a `left' parameter,
+display the tooltip at that x-position.  If the list of frame parameters
+PARMS contains no `left' but a `right' parameter, display the tooltip
+right-adjusted at that x-position. Otherwise display it at the
+x-position of the mouse, with offset DX added (default is 5 if DX isn't
+specified).
+
+Likewise for the y-position: If a `top' frame parameter is specified, it
+determines the position of the upper edge of the tooltip window.  If a
+`bottom' parameter but no `top' frame parameter is specified, it
+determines the position of the lower edge of the tooltip window.
+Otherwise display the tooltip window at the y-position of the mouse,
+with offset DY added (default is -10).
+
+A tooltip's maximum size is specified by `x-max-tooltip-size'.
+Text larger than the specified size is clipped.  */)
+(Lisp_Object string, Lisp_Object frame, Lisp_Object parms, Lisp_Object timeout,
+ Lisp_Object dx, Lisp_Object dy)
+{
+  struct frame *f;
+#ifndef HAVE_GTK4
+  int
+#else
+  double
+#endif
+    root_x,
+    root_y;
+  int width, height;
+  ptrdiff_t count = SPECPDL_INDEX ();
+
+  specbind (Qinhibit_redisplay, Qt);
+
+  CHECK_STRING (string);
+  if (SCHARS (string) == 0)
+    string = make_unibyte_string (" ", 1);
+
+  f = decode_window_system_frame (frame);
+  if (NILP (timeout))
+    timeout = make_fixnum (5);
+  else
+    CHECK_FIXNAT (timeout);
+
+  if (NILP (dx))
+    dx = make_fixnum (5);
+  else
+    CHECK_NUMBER (dx);
+
+  if (NILP (dy))
+    dy = make_fixnum (-10);
+  else
+    CHECK_NUMBER (dy);
+
+  {
+    bool ok;
+
+    /* Hide a previous tip, if any.  */
+    Fx_hide_tip ();
+
+    block_input ();
+#ifdef HAVE_GTK4
+    ok = egtk_prepare_tooltip (f, string, &width, &height);
+#else
+    ok = xg_prepare_tooltip (f, string, &width, &height);
+#endif
+    if (ok)
+      {
+        compute_tip_xy (f, parms, dx, dy, width, height, &root_x, &root_y);
+#ifdef HAVE_GTK4
+        egtk_show_tooltip (f, round (root_x)
+                              - pgtk_get_window_decoration_width (f),
+                            round (root_y)
+			      - pgtk_get_window_inside_decor_height (f));
+#else
+        xg_show_tooltip (f, root_x, root_y);
+#endif
+        /* This is used in Fx_hide_tip.  */
+        XSETFRAME (tip_frame, f);
+      }
+    unblock_input ();
+    if (ok)
+      goto start_timer;
+  }
+
+start_timer:
+  /* Let the tip disappear after timeout seconds.  */
+  tip_timer
+    = call3 (intern ("run-at-time"), timeout, Qnil, intern ("x-hide-tip"));
+
+  return unbind_to (count, Qnil);
+}
+
+DEFUN ("x-hide-tip", Fx_hide_tip, Sx_hide_tip, 0, 0, 0,
+       doc: /* Hide the current tooltip window, if there is any.
+Value is t if tooltip was open, nil otherwise.  */)
+(void) { return x_hide_tip (!tooltip_reuse_hidden_frame); }
+
+/* Return geometric attributes of FRAME.  According to the value of
+   ATTRIBUTES return the outer edges of FRAME (Qouter_edges), the inner
+   edges of FRAME, the root window edges of frame (Qroot_edges).  Any
+   other value means to return the geometry as returned by
+   Fx_frame_geometry.  */
+static Lisp_Object
+frame_geometry (Lisp_Object frame, Lisp_Object attribute)
+{
+  struct frame *f = decode_live_frame (frame);
+  Lisp_Object fullscreen_symbol = Fframe_parameter (frame, Qfullscreen);
+  bool fullscreen = (EQ (fullscreen_symbol, Qfullboth)
+                     || EQ (fullscreen_symbol, Qfullscreen));
+  int border = fullscreen ? 0 : f->border_width;
+  int title_height = 0;
+  int native_width = FRAME_PIXEL_WIDTH (f);
+  int native_height = FRAME_PIXEL_HEIGHT (f);
+  int outer_width = native_width + 2 * border;
+  int outer_height = native_height + 2 * border + title_height;
+  int native_left = f->left_pos + border;
+  int native_top = f->top_pos + border + title_height;
+  int native_right = f->left_pos + outer_width - border;
+  int native_bottom = f->top_pos + outer_height - border;
+  int internal_border_width = FRAME_INTERNAL_BORDER_WIDTH (f);
+  int tab_bar_height = 0, tab_bar_width = 0;
+  int tool_bar_height = FRAME_TOOLBAR_HEIGHT (f);
+  int tool_bar_width
+    = (tool_bar_height ? outer_width - 2 * internal_border_width : 0);
+
+  tab_bar_height = FRAME_TAB_BAR_HEIGHT (f);
+  tab_bar_width
+    = (tab_bar_height ? native_width - 2 * internal_border_width : 0);
+  // inner_top += tab_bar_height;
+
+  /* Construct list.  */
+  if (EQ (attribute, Qouter_edges))
+    return list4 (make_fixnum (f->left_pos), make_fixnum (f->top_pos),
+                  make_fixnum (f->left_pos + outer_width),
+                  make_fixnum (f->top_pos + outer_height));
+  else if (EQ (attribute, Qnative_edges))
+    return list4 (make_fixnum (native_left), make_fixnum (native_top),
+                  make_fixnum (native_right), make_fixnum (native_bottom));
+  else if (EQ (attribute, Qinner_edges))
+    return list4 (make_fixnum (native_left + internal_border_width),
+                  make_fixnum (native_top + tool_bar_height
+                               + internal_border_width),
+                  make_fixnum (native_right - internal_border_width),
+                  make_fixnum (native_bottom - internal_border_width));
+  else
+    return list (Fcons (Qouter_position, Fcons (make_fixnum (f->left_pos),
+                                                make_fixnum (f->top_pos))),
+                 Fcons (Qouter_size, Fcons (make_fixnum (outer_width),
+                                            make_fixnum (outer_height))),
+                 Fcons (Qexternal_border_size,
+                        (fullscreen ? Fcons (make_fixnum (0), make_fixnum (0))
+                                    : Fcons (make_fixnum (border),
+                                             make_fixnum (border)))),
+                 Fcons (Qtitle_bar_size,
+                        Fcons (make_fixnum (0), make_fixnum (title_height))),
+                 Fcons (Qmenu_bar_external, Qnil),
+                 Fcons (Qmenu_bar_size,
+                        Fcons (make_fixnum (0), make_fixnum (0))),
+                 Fcons (Qtab_bar_size, Fcons (make_fixnum (tab_bar_width),
+                                              make_fixnum (tab_bar_height))),
+                 Fcons (Qtool_bar_external,
+                        FRAME_EXTERNAL_TOOL_BAR (f) ? Qt : Qnil),
+                 Fcons (Qtool_bar_position, FRAME_TOOL_BAR_POSITION (f)),
+                 Fcons (Qtool_bar_size, Fcons (make_fixnum (tool_bar_width),
+                                               make_fixnum (tool_bar_height))),
+                 Fcons (Qinternal_border_width,
+                        make_fixnum (internal_border_width)));
+}
+
+DEFUN ("pgtk-frame-geometry", Fpgtk_frame_geometry, Spgtk_frame_geometry, 0, 1, 0,
+       doc: /* Return geometric attributes of FRAME.
+FRAME must be a live frame and defaults to the selected one.  The return
+value is an association list of the attributes listed below.  All height
+and width values are in pixels.
+
+`outer-position' is a cons of the outer left and top edges of FRAME
+  relative to the origin - the position (0, 0) - of FRAME's display.
+
+`outer-size' is a cons of the outer width and height of FRAME.  The
+  outer size includes the title bar and the external borders as well as
+  any menu and/or tool bar of frame.
+
+`external-border-size' is a cons of the horizontal and vertical width of
+  FRAME's external borders as supplied by the window manager.
+
+`title-bar-size' is a cons of the width and height of the title bar of
+  FRAME as supplied by the window manager.  If both of them are zero,
+  FRAME has no title bar.  If only the width is zero, Emacs was not
+  able to retrieve the width information.
+
+`menu-bar-external', if non-nil, means the menu bar is external (never
+  included in the inner edges of FRAME).
+
+`menu-bar-size' is a cons of the width and height of the menu bar of
+  FRAME.
+
+`tool-bar-external', if non-nil, means the tool bar is external (never
+  included in the inner edges of FRAME).
+
+`tool-bar-position' tells on which side the tool bar on FRAME is and can
+  be one of `left', `top', `right' or `bottom'.  If this is nil, FRAME
+  has no tool bar.
+
+`tool-bar-size' is a cons of the width and height of the tool bar of
+  FRAME.
+
+`internal-border-width' is the width of the internal border of
+  FRAME.  */)
+(Lisp_Object frame) { return frame_geometry (frame, Qnil); }
+
+DEFUN ("pgtk-frame-edges", Fpgtk_frame_edges, Spgtk_frame_edges, 0, 2, 0,
+       doc: /* Return edge coordinates of FRAME.
+FRAME must be a live frame and defaults to the selected one.  The return
+value is a list of the form (LEFT, TOP, RIGHT, BOTTOM).  All values are
+in pixels relative to the origin - the position (0, 0) - of FRAME's
+display.
+
+If optional argument TYPE is the symbol `outer-edges', return the outer
+edges of FRAME.  The outer edges comprise the decorations of the window
+manager (like the title bar or external borders) as well as any external
+menu or tool bar of FRAME.  If optional argument TYPE is the symbol
+`native-edges' or nil, return the native edges of FRAME.  The native
+edges exclude the decorations of the window manager and any external
+menu or tool bar of FRAME.  If TYPE is the symbol `inner-edges', return
+the inner edges of FRAME.  These edges exclude title bar, any borders,
+menu bar or tool bar of FRAME.  */)
+(Lisp_Object frame, Lisp_Object type)
+{
+  return frame_geometry (frame,
+                         ((EQ (type, Qouter_edges) || EQ (type, Qinner_edges))
+                            ? type
+                            : Qnative_edges));
+}
+
+DEFUN ("pgtk-set-mouse-absolute-pixel-position",
+       Fpgtk_set_mouse_absolute_pixel_position,
+       Spgtk_set_mouse_absolute_pixel_position, 2, 2, 0,
+       doc: /* Move mouse pointer to absolute pixel position (X, Y).
+The coordinates X and Y are interpreted in pixels relative to a position
+\(0, 0) of the selected frame's display.  */)
+(Lisp_Object x, Lisp_Object y)
+{
+#ifndef HAVE_GTK4
+  struct frame *f = SELECTED_FRAME ();
+  GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (f);
+#ifdef HAVE_GTK4
+  GdkDisplay *gdpy = gtk_widget_get_display (widget);
+#else
+  GdkWindow *window = gtk_widget_get_window (widget);
+  GdkDisplay *gdpy = gdk_window_get_display (window);
+#endif
+#ifndef HAVE_GTK4
+  GdkScreen *gscr = gdk_window_get_screen (window);
+#endif
+#ifdef HAVE_GTK3
+#if defined(GDK_VERSION_3_22) || HAVE_GTK4
+  GdkSeat *seat = gdk_display_get_default_seat (gdpy);
+  GdkDevice *device = gdk_seat_get_pointer (seat);
+#else
+  GdkDeviceManager *mgr = gdk_display_get_device_manager (gdpy);
+  GdkDevice *device = gdk_device_manager_get_client_pointer (mgr);
+#endif
+
+  PGTK_TRACE ("pgtk-set-mouse-absolute-pixel-position:");
+  gdk_device_warp (device, gscr, XFIXNUM (x),
+                   XFIXNUM (y)); /* No effect on wayland. */
+#endif
+  gdk_display_warp_pointer (gdpy, gscr, XFIXNUM (x), XFIXNUM (y));
+#endif
+  return Qnil;
+}
+
+DEFUN ("pgtk-mouse-absolute-pixel-position",
+       Fpgtk_mouse_absolute_pixel_position,
+       Spgtk_mouse_absolute_pixel_position, 0, 0, 0,
+       doc: /* Return absolute position of mouse cursor in pixels.
+The position is returned as a cons cell (X . Y) of the
+coordinates of the mouse cursor position in pixels relative to a
+position (0, 0) of the selected frame's terminal. */)
+(void)
+{
+  struct frame *f = SELECTED_FRAME ();
+  GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (f);
+#ifndef HAVE_GTK4
+  GdkWindow *window = gtk_widget_get_window (widget);
+#else
+  GdkSurface *window = EGTK_WIDGET_GET_WINDOW (widget);
+#endif
+#ifndef HAVE_GTK4
+  GdkDisplay *gdpy = gdk_window_get_display (window);
+#else
+  GdkDisplay *gdpy = gdk_surface_get_display (window);
+#endif
+#ifndef HAVE_GTK4
+  GdkScreen *gscr;
+#endif
+#ifdef HAVE_GTK3
+#if defined(GDK_VERSION_3_20) || HAVE_GTK4
+  GdkSeat *seat = gdk_display_get_default_seat (gdpy);
+  GdkDevice *device = gdk_seat_get_pointer (seat);
+#else
+  GdkDeviceManager *mgr = gdk_display_get_device_manager (gdpy);
+  GdkDevice *device = gdk_device_manager_get_client_pointer (mgr);
+#endif
+#ifndef HAVE_GTK4
+  int
+#else
+  double
+#endif
+    x
+    = 0,
+    y = 0;
+#ifndef HAVE_GTK4
+  gdk_device_get_position (device, &gscr, &x, &y); /* can't get on wayland? */
+#else
+  gdk_device_get_surface_at_position (device, &x, &y);
+  x -= calculate_child_frame_distance_x (f);
+  y -= calculate_child_frame_distance_y (f);
+#endif
+#else
+  int x = 0, y = 0;
+  gdk_display_get_pointer (gdpy, &gscr, &x, &y, NULL);
+#endif
+#ifndef HAVE_GTK4
+  return Fcons (make_fixnum (x), make_fixnum (y));
+#else
+  return Fcons (make_fixnum (round (x)), make_fixnum (round (y)));
+#endif
+}
+
+void
+pgtk_m_px_pos (struct frame *f, double *_x, double *_y)
+{
+  GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (f);
+#ifndef HAVE_GTK4
+  GdkWindow *window = gtk_widget_get_window (widget);
+#else
+  GdkSurface *window = EGTK_WIDGET_GET_WINDOW (widget);
+#endif
+#ifndef HAVE_GTK4
+  GdkDisplay *gdpy = gdk_window_get_display (window);
+#else
+  GdkDisplay *gdpy = gdk_surface_get_display (window);
+#endif
+#ifndef HAVE_GTK4
+  GdkScreen *gscr;
+#endif
+#ifdef HAVE_GTK3
+#if defined(GDK_VERSION_3_20) || HAVE_GTK4
+  GdkSeat *seat = gdk_display_get_default_seat (gdpy);
+  GdkDevice *device = gdk_seat_get_pointer (seat);
+#else
+  GdkDeviceManager *mgr = gdk_display_get_device_manager (gdpy);
+  GdkDevice *device = gdk_device_manager_get_client_pointer (mgr);
+#endif
+#ifndef HAVE_GTK4
+  int
+#else
+  double
+#endif
+    x
+    = 0,
+    y = 0;
+#ifndef HAVE_GTK4
+  gdk_device_get_position (device, &gscr, &x, &y); /* can't get on wayland? */
+#else
+  gdk_device_get_surface_at_position (device, &x, &y);
+  x -= calculate_child_frame_distance_x (f);
+  y -= calculate_child_frame_distance_y (f);
+#endif
+#else
+  int x = 0, y = 0;
+  gdk_display_get_pointer (gdpy, &gscr, &x, &y, NULL);
+#endif
+  *_x = x;
+  *_y = y;
+}
+
+DEFUN ("pgtk-page-setup-dialog", Fpgtk_page_setup_dialog, Spgtk_page_setup_dialog, 0, 0, 0,
+       doc: /* Pop up a page setup dialog.
+The current page setup can be obtained using `x-get-page-setup'.  */)
+(void)
+{
+  block_input ();
+#ifdef HAVE_GTK4
+  egtk_page_setup_dialog ();
+#else
+  xg_page_setup_dialog ();
+#endif
+  unblock_input ();
+
+  return Qnil;
+}
+
+DEFUN ("pgtk-get-page-setup", Fpgtk_get_page_setup, Spgtk_get_page_setup, 0, 0, 0,
+       doc: /* Return the value of the current page setup.
+The return value is an alist containing the following keys:
+
+  orientation: page orientation (symbol `portrait', `landscape',
+	`reverse-portrait', or `reverse-landscape').
+  width, height: page width/height in points not including margins.
+  left-margin, right-margin, top-margin, bottom-margin: print margins,
+	which is the parts of the page that the printer cannot print
+	on, in points.
+
+The paper width can be obtained as the sum of width, left-margin, and
+right-margin values if the page orientation is `portrait' or
+`reverse-portrait'.  Otherwise, it is the sum of width, top-margin,
+and bottom-margin values.  Likewise, the paper height is the sum of
+height, top-margin, and bottom-margin values if the page orientation
+is `portrait' or `reverse-portrait'.  Otherwise, it is the sum of
+height, left-margin, and right-margin values.  */)
+(void)
+{
+  Lisp_Object result;
+
+  block_input ();
+#ifdef HAVE_GTK4
+  result = egtk_get_page_setup ();
+#else
+  result = xg_get_page_setup ();
+#endif
+  unblock_input ();
+
+  return result;
+}
+
+DEFUN ("pgtk-print-frames-dialog", Fpgtk_print_frames_dialog, Spgtk_print_frames_dialog, 0, 1, "",
+       doc: /* Pop up a print dialog to print the current contents of FRAMES.
+FRAMES should be nil (the selected frame), a frame, or a list of
+frames (each of which corresponds to one page).  Each frame should be
+visible.  */)
+(Lisp_Object frames)
+{
+  Lisp_Object rest, tmp;
+  int count;
+
+  if (!CONSP (frames))
+    frames = list1 (frames);
+
+  tmp = Qnil;
+  for (rest = frames; CONSP (rest); rest = XCDR (rest))
+    {
+      struct frame *f = decode_window_system_frame (XCAR (rest));
+      Lisp_Object frame;
+
+      XSETFRAME (frame, f);
+      if (!FRAME_VISIBLE_P (f))
+        error ("Frames to be printed must be visible.");
+      tmp = Fcons (frame, tmp);
+    }
+  frames = Fnreverse (tmp);
+
+  /* Make sure the current matrices are up-to-date.  */
+  count = SPECPDL_INDEX ();
+  specbind (Qredisplay_dont_pause, Qt);
+  redisplay_preserve_echo_area (32);
+  unbind_to (count, Qnil);
+
+  block_input ();
+#ifndef HAVE_GTK4
+  xg_print_frames_dialog (frames);
+#else
+  egtk_print_frames_dialog (frames);
+#endif
+
+  unblock_input ();
+
+  return Qnil;
+}
+
+#ifdef HAVE_GTK3
+static GtkWindow *
+locate_nonchild_window (struct frame *child)
+{
+  while (FRAME_PARENT_FRAME (child))
+    child = FRAME_PARENT_FRAME (child);
+  return GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child));
+}
+#endif
+
+
+#ifdef HAVE_GTK3
+DEFUN ("pgtk-color-dialog", Fpgtk_color_dialog, Spgtk_color_dialog, 0, 4, 0,
+       doc: /* Display a window-system color picker dialog on FRAME,
+or on the selected frame if FRAME is nil. A hex string containing the
+selected (A)RGB color will be returned, or nil if no color was selected.
+The title of the dialog will be set to PROMPT, or "Pick color" if
+PROMPT is nil. The color string is an hexedecimal RGB color, or an ARGB
+color if USE-ALPHA is not nil. PALETTE_ITEMS is an alist containing a pair of
+(symbol . (r g b a)). */
+       attributes: const)
+  (Lisp_Object frame, Lisp_Object prompt, Lisp_Object use_alpha, Lisp_Object palette_items)
+{
+  if (NILP (frame))
+    frame = Fselected_frame ();
+  CHECK_LIVE_FRAME (frame);
+  if (!FRAME_PGTK_P (XFRAME (frame)))
+    error ("Color picker dialogs can only be displayed on GTK frames.");
+  GtkColorChooserDialog *ccd = GTK_COLOR_CHOOSER_DIALOG
+    (gtk_color_chooser_dialog_new (NILP (prompt) ? "Pick color" :
+				   SSDATA (ENCODE_UTF_8 (prompt)),
+				   locate_nonchild_window (XFRAME (frame))));
+#if GTK_CHECK_VERSION (3, 22, 0)
+  GtkContainer *vbox_1 = GTK_CONTAINER (gtk_bin_get_child (GTK_BIN (ccd)));
+  GList *l = gtk_container_get_children (vbox_1);
+  GtkColorChooserWidget *widget = l->data;
+  g_object_ref (widget);
+  GtkScrolledWindow *window =
+    GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+  GtkViewport *vp = GTK_VIEWPORT (gtk_viewport_new (NULL, NULL));
+  gtk_scrolled_window_set_max_content_height (window, 800);
+  gtk_scrolled_window_set_propagate_natural_width (window, true);
+  gtk_scrolled_window_set_propagate_natural_height (window, true);
+  gtk_container_remove (vbox_1, GTK_WIDGET (widget));
+  gtk_container_add (GTK_CONTAINER (vp), GTK_WIDGET (widget));
+  gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (vp));
+  gtk_container_add (GTK_CONTAINER (vbox_1), GTK_WIDGET (window));
+#endif
+#ifndef HAVE_GTK4
+  gtk_widget_show_all (GTK_WIDGET (window));
+#endif
+  GdkRGBA *amtz = NULL;
+  if (!NILP (palette_items))
+    {
+      size_t amt = 0;
+      amtz = xmalloc (XFIXNUM (Flength (palette_items)) * sizeof (GdkRGBA));
+      GdkRGBA *cur = amtz;
+      for (; !NILP (palette_items); palette_items = XCDR (palette_items))
+	{
+	  eassert (amtz);
+	  ((char *) amtz)[amt * sizeof (GdkRGBA)] = '\0';
+	  CHECK_CONS (XCAR (palette_items));
+	  Lisp_Object symbol = XCAR (XCAR (palette_items));
+	  CHECK_SYMBOL (symbol);
+	  Lisp_Object r = Fcar (XCDR (XCAR (palette_items)));
+	  Lisp_Object g = Fcar (Fcdr (XCDR (XCAR (palette_items))));
+	  Lisp_Object b = Fcar (Fcdr (Fcdr (XCDR (XCAR (palette_items)))));
+	  Lisp_Object a = Fcar (Fcdr (Fcdr (Fcdr (XCDR (XCAR (palette_items))))));
+	  if (NILP (a))
+	    a = make_float (255.00);
+	  CHECK_NUMBER (r);
+	  CHECK_NUMBER (g);
+	  CHECK_NUMBER (b);
+	  CHECK_NUMBER (a);
+
+	  GdkRGBA *grgba = cur++;
+	  grgba->alpha = XFLOATINT (a);
+	  grgba->blue = XFLOATINT (b);
+	  grgba->green = XFLOATINT (g);
+	  grgba->red = XFLOATINT (r);
+
+	  ++amt;
+	}
+      gtk_color_chooser_add_palette (GTK_COLOR_CHOOSER (ccd),
+				     GTK_ORIENTATION_HORIZONTAL,
+				     9, amt, amtz);
+    }
+
+  gtk_widget_queue_allocate (GTK_WIDGET (vp));
+  if (gtk_dialog_run (GTK_DIALOG (ccd)) != GTK_RESPONSE_OK)
+    {
+      gtk_widget_destroy (GTK_WIDGET (ccd));
+      if (amtz)
+	xfree (amtz);
+      return Qnil;
+    }
+  GdkRGBA *rgba = g_malloc (sizeof (GdkRGBA));
+  gtk_color_chooser_set_use_alpha (GTK_COLOR_CHOOSER (ccd), !NILP (use_alpha));
+  gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (ccd), rgba);
+  gchar *str = g_malloc (NILP (use_alpha) ?
+			 sizeof ("#FFFFFF") :
+			 sizeof ("#FFFFFFFF"));
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+  if (NILP (use_alpha))
+    sprintf (str,
+	     "#%02x%02x%02x",
+	     (unsigned short) round (rgba->red * 255),
+	     (unsigned short) round (rgba->green * 255),
+	     (unsigned short) round (rgba->blue * 255));
+  else
+    sprintf (str,
+	     "#%02x%02x%02x%02x",
+	     (unsigned short) round (rgba->alpha * 255),
+	     (unsigned short) round (rgba->red * 255),
+	     (unsigned short) round (rgba->green * 255),
+	     (unsigned short) round (rgba->blue * 255));
+#pragma GCC diagnostic push
+  g_free (rgba);
+  Lisp_Object strz = build_string (str);
+  gtk_widget_destroy (GTK_WIDGET (ccd));
+  g_free (str);
+  if (amtz)
+    xfree (amtz);
+  return strz;
+}
+#endif
+
+#ifdef HAVE_GTK4
+DEFUN ("pgtk-show-selection-options", Fpgtk_show_selection_options, Spgtk_show_selection_options,
+       1, 1, 0, doc: /* Display the touchscreen selection options popover on FRAME. */)
+  (Lisp_Object frame)
+{
+  CHECK_LIVE_FRAME (frame);
+  if (!FRAME_PGTK_P (XFRAME (frame)))
+    error ("Not a pgtk frame.");
+  emacs_resizable_drawing_area_show_selection_options
+    ((gpointer) FRAME_GTK_WIDGET (XFRAME (frame)));
+  return Qnil;
+}
+#endif
+
+DEFUN ("x-file-dialog", Fx_file_dialog, Sx_file_dialog, 2, 5, 0,
+       doc: /* Read file name, prompting with PROMPT in directory DIR.
+Use a file selection dialog.  Select DEFAULT-FILENAME in the dialog's file
+selection box, if specified.  If MUSTMATCH is non-nil, the returned file
+or directory must exist.
+
+This function is defined only on PGTK, NS, MS Windows, and X Windows with the
+Motif or Gtk toolkits.  With the Motif toolkit, ONLY-DIR-P is ignored.
+Otherwise, if ONLY-DIR-P is non-nil, the user can select only directories.
+On MS Windows 7 and later, the file selection dialog "remembers" the last
+directory where the user selected a file, and will open that directory
+instead of DIR on subsequent invocations of this function with the same
+value of DIR as in previous invocations; this is standard MS Windows behavior.  */)
+(Lisp_Object prompt, Lisp_Object dir, Lisp_Object default_filename,
+ Lisp_Object mustmatch, Lisp_Object only_dir_p)
+{
+  struct frame *f = SELECTED_FRAME ();
+  char *fn;
+  Lisp_Object file = Qnil;
+  Lisp_Object decoded_file;
+  ptrdiff_t count = SPECPDL_INDEX ();
+  char *cdef_file;
+
+  check_window_system (f);
+
+  CHECK_STRING (prompt);
+  CHECK_STRING (dir);
+
+  /* Prevent redisplay.  */
+  specbind (Qinhibit_redisplay, Qt);
+#if 0
+  record_unwind_protect_void (clean_up_dialog);
+#endif
+
+  block_input ();
+
+  if (STRINGP (default_filename))
+    cdef_file = SSDATA (default_filename);
+  else
+    cdef_file = SSDATA (dir);
+#ifndef HAVE_GTK4
+  fn = xg_get_file_name (f, SSDATA (prompt), cdef_file, !NILP (mustmatch),
+                         !NILP (only_dir_p));
+#else
+  fn = egtk_get_file_name (f, SSDATA (prompt), cdef_file, !NILP (mustmatch),
+			   !NILP (only_dir_p));
+#endif
+
+  if (fn)
+    {
+      file = build_string (fn);
+#ifndef HAVE_GTK4
+      xfree (fn);
+#else
+      g_free (fn);
+#endif
+    }
+
+  unblock_input ();
+
+  /* Make "Cancel" equivalent to C-g.  */
+  if (NILP (file))
+    quit ();
+
+  decoded_file = DECODE_FILE (file);
+
+  return unbind_to (count, decoded_file);
+}
+
+#ifdef HAVE_GTK4
+/* We implement most of this in C code, so
+   GC doesn't go crazy when this is called. */
+void
+pgtk_vpixoffset_scroll (int pixels, struct window *window)
+{
+  pixels += 1;
+  int vs_begin = 0;
+  int vs_stop = pixels;
+  while (vs_begin != vs_stop)
+    {
+      int old_vs = window->vpixoffset;
+      if (window->vpixoffset < 0 && window->vpixoffset < old_vs)
+	adjust_frame_glyphs (XFRAME (window->frame));
+      if (vs_begin >= 0)
+	{
+	  window->vpixoffset -= vs_begin;
+	  Lisp_Object window_;
+	  XSETWINDOW (window_, window);
+	  Fset_window_point (window_,
+			     make_fixnum
+			     ((MATRIX_FIRST_TEXT_ROW
+			       (window->current_matrix) + 2)->start.pos.charpos));
+	}
+      else
+	{
+          redisplay ();
+          int specbind_ = SPECPDL_INDEX ();
+          record_unwind_protect_excursion ();
+          Fgoto_char (Fwindow_start (Qnil));
+          ptrdiff_t res;
+          Lisp_Object window_;
+          XSETWINDOW (window_, window);
+          if (!NILP (Fbobp ()))
+            {
+              res = BEGV;
+            }
+          else
+            {
+              if (MATRIX_FIRST_TEXT_ROW (window->current_matrix)->y >= 0)
+                {
+                  int specindex_b = SPECPDL_INDEX ();
+                  Lisp_Object symb = intern_c_string ("line-move-visual");
+                  Fset_window_point (window_, Fwindow_start (Qnil));
+                  Fvertical_motion (make_fixnum (-1), Qnil, Qnil);
+                  specbind (symb, Qt);
+                  Fbeginning_of_line (Qnil);
+                  res = PT;
+                  unbind_to (specindex_b, Qnil);
+                }
+              else
+                {
+                  res = XFIXNUM (window->start);
+                }
+            }
+
+          unbind_to (specbind_, Qnil);
+	  Lisp_Object tmp;
+	  XSETFASTINT (tmp, res);
+	  set_marker_restricted (window->start, tmp, window->contents);
+	  window->start_at_line_beg = false;
+	  window->force_start = true;
+	  window->update_mode_line = true;
+	  window->redisplay = true;
+	  window->window_end_valid = false;
+          Fset_window_point (window_, CALLN (Fplus, tmp, make_fixnum (100)));
+          struct glyph_row *mr = MATRIX_FIRST_TEXT_ROW (window->current_matrix);
+          while (mr->y < FRAME_LINE_HEIGHT (XFRAME (window->frame)))
+            ++mr;
+          int begin_row_height = mr->height;
+          if (!window->vpixoffset)
+            window->vpixoffset = -begin_row_height;
+          window->vpixoffset -= vs_begin;
+          if (window->vpixoffset >= 0)
+            {
+              window->vpixoffset = 0;
+              Fset_window_start (Qnil, tmp, Qt);
+            }
+
+	}
+      if (vs_begin > vs_stop)
+	--vs_begin;
+      else
+	++vs_begin;
+      XBUFFER (window->contents)->prevent_redisplay_optimizations_p = true;
+      redisplay ();
+    }
+}
+
+DEFUN ("pgtk-pixel-scroll-up", Fpgtk_pixel_scroll_up, Spgtk_pixel_scroll_up,
+       1, 1, 0, doc: /* Pixel-scroll the selected window by PIXELS. */)
+  (Lisp_Object pixels)
+{
+  if (NILP (Fwindow_system (Qnil)))
+    error ("No window system");
+  pgtk_vpixoffset_scroll (XFIXNUM (pixels),
+			  XWINDOW (selected_window));
+  return Qnil;
+}
+#endif
+
+DEFUN ("pgtk-backend-display-class", Fpgtk_backend_display_class, Spgtk_backend_display_class,
+       0, 1, "",
+       doc: /* Returns the name of the Gdk backend display class of the TERMINAL.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.  */)
+(Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+  const gchar *type_name = G_OBJECT_TYPE_NAME (G_OBJECT (gdpy));
+  return build_string (type_name);
+}
+#ifdef HAVE_GTK3
+DEFUN ("pgtk-show-inspector", Fpgtk_show_inspector, Spgtk_show_inspector,
+       0, 0, "", doc: /* Show the GTK inspector debug tool. */)
+  (void)
+{
+  if (NILP (Fwindow_system (Qnil)))
+    error ("No window system");
+#if defined (GDK_VERSION_3_14) || defined (HAVE_GTK4)
+  gtk_window_set_interactive_debugging (TRUE);
+#endif
+  return Qnil;
+}
+#endif
+
+#ifdef HAVE_GTK4
+DEFUN ("pgtk-default-font-family", Fpgtk_default_font_family,
+       Spgtk_default_font_family, 1, 1, 0,
+       doc: /* Returns the default GTK font family if MONO is nil,
+or the default monospace family if MONO is non-nil. */)
+  (Lisp_Object mono)
+{
+  return eg_get_font_family (!NILP (mono));
+}
+
+DEFUN ("pgtk-get-text-caret-width", Fpgtk_get_text_caret_width,
+       Spgtk_get_text_caret_width, 0, 0, 0,
+       doc: /* Return the width of GTK text carets in pixels. */)
+  (void)
+{
+  return eg_get_caret_px_width ();
+}
+
+DEFUN ("pgtk-get-separator-width", Fpgtk_get_separator_width,
+       Spgtk_get_separator_width, 0, 0, 0,
+       doc: /* Return the width of GTK separators, in pixels. */)
+  (void)
+{
+  return eg_get_divider_width ();
+}
+#endif
+
+/* ==========================================================================
+
+    Lisp interface declaration
+
+   ========================================================================== */
+
+void
+syms_of_pgtkfns (void)
+{
+  DEFSYM (Qfont_parameter, "font-parameter");
+  DEFSYM (Qfontsize, "fontsize");
+  DEFSYM (Qcancel_timer, "cancel-timer");
+  DEFSYM (Qframe_title_format, "frame-title-format");
+  DEFSYM (Qicon_title_format, "icon-title-format");
+  DEFSYM (Qdark, "dark");
+#ifdef HAVE_GTK4
+  DEFSYM (Qload_theme, "load-theme");
+  DEFSYM (Qgtk, "gtk");
+  DEFSYM (Qcustom_theme_enabled_p, "custom-theme-enabled-p");
+#endif
+  DEFVAR_LISP ("x-cursor-fore-pixel", Vx_cursor_fore_pixel, doc: /* A string indicating the foreground color of the cursor box.  */);
+  Vx_cursor_fore_pixel = Qnil;
+
+  DEFVAR_LISP ("pgtk-icon-type-alist", Vpgtk_icon_type_alist,
+               doc: /* Alist of elements (REGEXP . IMAGE) for images of icons associated to frames.
+If the title of a frame matches REGEXP, then IMAGE.tiff is
+selected as the image of the icon representing the frame when it's
+miniaturized.  If an element is t, then Emacs tries to select an icon
+based on the filetype of the visited file.
+
+The images have to be installed in a folder called English.lproj in the
+Emacs folder.  You have to restart Emacs after installing new icons.
+
+Example: Install an icon Gnus.tiff and execute the following code
+
+  (setq pgtk-icon-type-alist
+        (append pgtk-icon-type-alist
+                \\='((\"^\\\\*\\\\(Group\\\\*$\\\\|Summary \\\\|Article\\\\*$\\\\)\"
+                   . \"Gnus\"))))
+
+When you miniaturize a Group, Summary or Article frame, Gnus.tiff will
+be used as the image of the icon representing the frame.  */);
+  Vpgtk_icon_type_alist = list1 (Qt);
+
+  /* Provide x-toolkit also for GTK.  Internally GTK does not use Xt so it
+     is not an X toolkit in that sense (USE_X_TOOLKIT is not defined).
+     But for a user it is a toolkit for X, and indeed, configure
+     accepts --with-x-toolkit=gtk.  */
+  Fprovide (intern_c_string ("x-toolkit"), Qnil);
+  Fprovide (intern_c_string ("gtk"), Qnil);
+  Fprovide (intern_c_string ("move-toolbar"), Qnil);
+
+  DEFVAR_LISP ("gtk-version-string", Vgtk_version_string,
+	       doc: /* Version info for GTK+.  */);
+  {
+    char *ver = g_strdup_printf ("%d.%d.%d", GTK_MAJOR_VERSION,
+                                 GTK_MINOR_VERSION, GTK_MICRO_VERSION);
+    int len = strlen (ver);
+    Vgtk_version_string = make_pure_string (ver, len, len, false);
+    g_free (ver);
+  }
+
+  Fprovide (intern_c_string ("cairo"), Qnil);
+
+  DEFVAR_LISP ("cairo-version-string", Vcairo_version_string,
+	       doc: /* Version info for cairo.  */);
+  {
+    char *ver = g_strdup_printf ("%d.%d.%d", CAIRO_VERSION_MAJOR,
+                                 CAIRO_VERSION_MINOR, CAIRO_VERSION_MICRO);
+    int len = strlen (ver);
+    Vcairo_version_string = make_pure_string (ver, len, len, false);
+    g_free (ver);
+  }
+
+
+  defsubr (&Sx_select_font);
+
+  defsubr (&Spgtk_set_resource);
+  defsubr (&Sxw_display_color_p); /* this and next called directly by C code */
+  defsubr (&Sx_display_grayscale_p);
+  defsubr (&Spgtk_font_name);
+  defsubr (&Sxw_color_defined_p);
+  defsubr (&Sxw_color_values);
+  defsubr (&Sx_server_max_request_size);
+  defsubr (&Sx_server_vendor);
+  defsubr (&Sx_server_version);
+  defsubr (&Sx_display_pixel_width);
+  defsubr (&Sx_display_pixel_height);
+  defsubr (&Spgtk_display_monitor_attributes_list);
+  defsubr (&Spgtk_frame_geometry);
+  defsubr (&Spgtk_frame_edges);
+  defsubr (&Spgtk_frame_restack);
+  defsubr (&Spgtk_set_mouse_absolute_pixel_position);
+  defsubr (&Spgtk_mouse_absolute_pixel_position);
+  defsubr (&Sx_display_mm_width);
+  defsubr (&Sx_display_mm_height);
+  defsubr (&Sx_display_screens);
+  defsubr (&Sx_display_planes);
+  defsubr (&Sx_display_color_cells);
+  defsubr (&Sx_display_visual_class);
+  defsubr (&Sx_display_backing_store);
+  defsubr (&Sx_display_save_under);
+  defsubr (&Sx_create_frame);
+  defsubr (&Sx_open_connection);
+  defsubr (&Sx_close_connection);
+  defsubr (&Sx_display_list);
+
+#ifdef HAVE_GTK4
+  defsubr (&Spgtk_default_font_family);
+  defsubr (&Spgtk_get_text_caret_width);
+  defsubr (&Spgtk_get_separator_width);
+#endif
+
+#ifdef HAVE_GTK3
+  defsubr (&Spgtk_show_inspector);
+#endif
+
+  defsubr (&Spgtk_hide_others);
+  defsubr (&Spgtk_hide_emacs);
+
+  defsubr (&Sx_show_tip);
+  defsubr (&Sx_hide_tip);
+
+  // defsubr (&Spgtk_export_frames);
+  defsubr (&Spgtk_page_setup_dialog);
+  defsubr (&Spgtk_get_page_setup);
+  defsubr (&Spgtk_print_frames_dialog);
+  defsubr (&Spgtk_backend_display_class);
+
+#ifdef HAVE_GTK3
+  defsubr (&Spgtk_color_dialog);
+#endif
+
+#ifdef HAVE_GTK4
+  defsubr (&Spgtk_pixel_scroll_up);
+  defsubr (&Spgtk_show_selection_options);
+#endif
+
+  defsubr (&Sx_file_dialog);
+
+  as_status = 0;
+  as_script = Qnil;
+  as_result = 0;
+
+  tip_frame = Qnil;
+  staticpro (&tip_frame);
+  tip_timer = Qnil;
+  staticpro (&tip_timer);
+
+  /* This is not ifdef:ed, so other builds than GTK can customize it.  */
+  DEFVAR_BOOL ("x-gtk-use-old-file-dialog", x_gtk_use_old_file_dialog,
+    doc: /* Non-nil means prompt with the old GTK file selection dialog.
+If nil or if the file selection dialog is not available, the new GTK file
+chooser is used instead.  To turn off all file dialogs set the
+variable `use-file-dialog'.  */);
+  x_gtk_use_old_file_dialog = false;
+
+  DEFVAR_BOOL ("x-gtk-show-hidden-files", x_gtk_show_hidden_files,
+    doc: /* If non-nil, the GTK file chooser will by default show hidden files.
+Note that this is just the default, there is a toggle button on the file
+chooser to show or not show hidden files on a case by case basis.  */);
+  x_gtk_show_hidden_files = false;
+
+  DEFVAR_BOOL ("x-gtk-file-dialog-help-text", x_gtk_file_dialog_help_text,
+    doc: /* If non-nil, the GTK file chooser will show additional help text.
+If more space for files in the file chooser dialog is wanted, set this to nil
+to turn the additional text off.  */);
+  x_gtk_file_dialog_help_text = true;
+
+  DEFVAR_BOOL ("x-gtk-use-system-tooltips", x_gtk_use_system_tooltips,
+    doc: /* If non-nil with a Gtk+ built Emacs, the Gtk+ tooltip is used.
+Otherwise use Emacs own tooltip implementation.
+When using Gtk+ tooltips, the tooltip face is not used.  */);
+  x_gtk_use_system_tooltips = true;
+
+  DEFVAR_BOOL ("x-gtk-use-color-dialogs", x_gtk_use_color_dialogs,
+	       doc: /* If non-nil with a GTK+ built Emacs, GTK color dialogs
+will be used by read-color.  */);
+  x_gtk_use_color_dialogs = true;
+
+  DEFSYM (Qmono, "mono");
+
+  DEFSYM (Qpdf, "pdf");
+
+  DEFSYM (Qorientation, "orientation");
+  DEFSYM (Qtop_margin, "top-margin");
+  DEFSYM (Qbottom_margin, "bottom-margin");
+  DEFSYM (Qportrait, "portrait");
+  DEFSYM (Qlandscape, "landscape");
+  DEFSYM (Qreverse_portrait, "reverse-portrait");
+  DEFSYM (Qreverse_landscape, "reverse-landscape");
+}
+
+#ifdef PGTK_DEBUG
+
+#include <stdarg.h>
+#include <time.h>
+void
+pgtk_log (const char *file, int lineno, const char *fmt, ...)
+{
+  struct timespec ts;
+  struct tm tm;
+  char timestr[32];
+  va_list ap;
+
+  clock_gettime (CLOCK_REALTIME, &ts);
+
+  localtime_r (&ts.tv_sec, &tm);
+  strftime (timestr, sizeof timestr, "%H:%M:%S", &tm);
+
+  fprintf (stderr, "%s.%06ld %.10s:%04d ", timestr, ts.tv_nsec / 1000, file,
+           lineno);
+  va_start (ap, fmt);
+  vfprintf (stderr, fmt, ap);
+  va_end (ap);
+  fputc ('\n', stderr);
+}
+
+void
+pgtk_backtrace (const char *file, int lineno)
+{
+  Lisp_Object bt = make_uninit_vector (10);
+  for (int i = 0; i < 10; i++)
+    ASET (bt, i, Qnil);
+
+  struct timespec ts;
+  struct tm tm;
+  char timestr[32];
+
+  clock_gettime (CLOCK_REALTIME, &ts);
+
+  localtime_r (&ts.tv_sec, &tm);
+  strftime (timestr, sizeof timestr, "%H:%M:%S", &tm);
+
+  fprintf (stderr, "%s.%06ld %.10s:%04d ********\n", timestr, ts.tv_nsec / 1000,
+           file, lineno);
+
+  get_backtrace (bt);
+  for (int i = 0; i < 10; i++)
+    {
+      Lisp_Object stk = AREF (bt, i);
+      if (!NILP (stk))
+        {
+          Lisp_Object args[2] = {build_string ("%S"), stk};
+          Lisp_Object str = Fformat (2, args);
+          fprintf (stderr, "%s %.10s:%04d %s\n", timestr, file, lineno,
+                   SSDATA (str));
+        }
+    }
+
+  fprintf (stderr, "%s %.10s:%04d ********\n", timestr, file, lineno);
+}
+
+#endif
+
+#endif
diff --git a/src/pgtkgui.h b/src/pgtkgui.h
new file mode 100644
index 0000000000..bf76d20c14
--- /dev/null
+++ b/src/pgtkgui.h
@@ -0,0 +1,111 @@
+/* Definitions and headers for communication on the pure Gtk+3.
+   Copyright (C) 1995, 2005, 2008-2018 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef __PGTKGUI_H__
+#define __PGTKGUI_H__
+
+#define STORE_XCHAR2B(chp, b1, b2) \
+  (*(chp) = ((XChar2b)((((b1) & 0x00ff) << 8) | ((b2) & 0x00ff))))
+
+#define XCHAR2B_BYTE1(chp) \
+  ((*(chp) & 0xff00) >> 8)
+
+#define XCHAR2B_BYTE2(chp) \
+  (*(chp) & 0x00ff)
+
+
+typedef struct _GdkCursor *Emacs_Cursor;
+
+typedef void * Color;
+typedef int GWindow;
+typedef struct _GdkDisplay GDisplay;
+
+/* Xism */
+typedef void *XrmDatabase;
+
+
+/* some sort of attempt to normalize rectangle handling.. seems a bit much
+   for what is accomplished */
+typedef struct {
+      int x, y;
+      unsigned width, height;
+} GInternalRectangle;
+
+#define NativeRectangle GInternalRectangle
+
+typedef double CGFloat;
+
+#define STORE_NATIVE_RECT(nr, px, py, pwidth, pheight)	\
+  ((nr).x    = (px),			\
+   (nr).y    = (py),			\
+   (nr).width  = (pwidth),		\
+   (nr).height = (pheight))
+
+/* This stuff needed by frame.c. */
+#define ForgetGravity		0
+#define NorthWestGravity	1
+#define NorthGravity		2
+#define NorthEastGravity	3
+#define WestGravity		4
+#define CenterGravity		5
+#define EastGravity		6
+#define SouthWestGravity	7
+#define SouthGravity		8
+#define SouthEastGravity	9
+#define StaticGravity		10
+
+#define NoValue		0x0000
+#define XValue  	0x0001
+#define YValue		0x0002
+#define WidthValue  	0x0004
+#define HeightValue  	0x0008
+#define AllValues 	0x000F
+#define XNegative 	0x0010
+#define YNegative 	0x0020
+
+#define USPosition	(1L << 0) /* user specified x, y */
+#define USSize		(1L << 1) /* user specified width, height */
+
+#define PPosition	(1L << 2) /* program specified position */
+#define PSize		(1L << 3) /* program specified size */
+#define PMinSize	(1L << 4) /* program specified minimum size */
+#define PMaxSize	(1L << 5) /* program specified maximum size */
+#define PResizeInc	(1L << 6) /* program specified resize increments */
+#define PAspect		(1L << 7) /* program specified min, max aspect ratios */
+#define PBaseSize	(1L << 8) /* program specified base for incrementing */
+#define PWinGravity	(1L << 9) /* program specified window gravity */
+
+#define CONVERT_TO_EMACS_RECT(xr, nr)		\
+  ((xr).x     = (nr).x,				\
+   (xr).y     = (nr).y,				\
+   (xr).width = (nr).width,			\
+   (xr).height = (nr).height)
+
+#define CONVERT_FROM_EMACS_RECT(xr, nr)		\
+  ((nr).x      = (xr).x,			\
+   (nr).y      = (xr).y,			\
+   (nr).width  = (xr).width,			\
+   (nr).height = (xr).height)
+
+#define STORE_NATIVE_RECT(nr, px, py, pwidth, pheight)	\
+  ((nr).x      = (px),					\
+   (nr).y      = (py),					\
+   (nr).width  = (pwidth),				\
+   (nr).height = (pheight))
+
+#endif  /* __PGTKGUI_H__ */
diff --git a/src/pgtkim.c b/src/pgtkim.c
new file mode 100644
index 0000000000..07dba05c0b
--- /dev/null
+++ b/src/pgtkim.c
@@ -0,0 +1,348 @@
+/* Pure Gtk+-3 communication module.      -*- coding: utf-8 -*-
+
+Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2018 Free Software
+Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This should be the first include, as it may set up #defines affecting
+   interpretation of even the system includes. */
+#include <config.h>
+#include <stdio.h>
+
+#include "pgtkterm.h"
+#include "keyboard.h"
+#include "gtkconfig.h"
+#include "pgtkim.h"
+
+void
+im_context_commit_cb (GtkIMContext *imc, gchar *str, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+
+  if (dpyinfo->im.context == NULL)
+    return;
+  if (f == NULL)
+    return;
+
+  pgtk_enqueue_string (f, str);
+}
+
+gboolean
+im_context_retrieve_surrounding_cb (GtkIMContext *imc, gpointer user_data)
+{
+  gtk_im_context_set_surrounding (imc, "", -1, 0);
+  return TRUE;
+}
+
+gboolean
+im_context_delete_surrounding_cb (GtkIMContext *imc, int offset, int n_chars,
+                                  gpointer user_data)
+{
+  return TRUE;
+}
+
+static Lisp_Object
+make_color_string (PangoAttrColor *pac)
+{
+  char buf[256];
+  sprintf (buf, "#%02x%02x%02x", pac->color.red >> 8, pac->color.green >> 8,
+           pac->color.blue >> 8);
+  return build_string (buf);
+}
+
+void
+im_context_preedit_changed_cb (GtkIMContext *imc, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+  char *str;
+  PangoAttrList *attrs;
+  int pos;
+
+  if (dpyinfo->im.context == NULL)
+    return;
+  if (f == NULL)
+    return;
+
+  gtk_im_context_get_preedit_string (imc, &str, &attrs, &pos);
+
+  /*
+   * (
+   *   (TEXT (ul . COLOR) (bg . COLOR) (fg . COLOR))
+   *   ...
+   * )
+   */
+  Lisp_Object list = Qnil;
+
+  PangoAttrIterator *iter;
+  iter = pango_attr_list_get_iterator (attrs);
+  do
+    {
+      int st, ed;
+      int has_underline = 0;
+      Lisp_Object part = Qnil;
+
+      pango_attr_iterator_range (iter, &st, &ed);
+
+      if (ed > strlen (str))
+        ed = strlen (str);
+      if (st >= ed)
+        continue;
+
+      Lisp_Object text = make_string (str + st, ed - st);
+      part = Fcons (text, part);
+
+      PangoAttrInt *ul
+        = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
+      if (ul != NULL)
+        {
+          if (ul->value != PANGO_UNDERLINE_NONE)
+            has_underline = 1;
+        }
+
+      PangoAttrColor *pac;
+      if (has_underline)
+        {
+          pac = (PangoAttrColor *)
+            pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE_COLOR);
+          if (pac != NULL)
+            part = Fcons (Fcons (Qul, make_color_string (pac)), part);
+          else
+            part = Fcons (Fcons (Qul, Qt), part);
+        }
+
+      pac = (PangoAttrColor *) pango_attr_iterator_get (iter,
+                                                        PANGO_ATTR_FOREGROUND);
+      if (pac != NULL)
+        part = Fcons (Fcons (Qfg, make_color_string (pac)), part);
+
+      pac = (PangoAttrColor *) pango_attr_iterator_get (iter,
+                                                        PANGO_ATTR_BACKGROUND);
+      if (pac != NULL)
+        part = Fcons (Fcons (Qbg, make_color_string (pac)), part);
+
+      part = Fnreverse (part);
+      list = Fcons (part, list);
+    }
+  while (pango_attr_iterator_next (iter));
+
+  list = Fnreverse (list);
+  pgtk_enqueue_preedit (f, list);
+
+  g_free (str);
+  pango_attr_list_unref (attrs);
+}
+
+void
+im_context_preedit_end_cb (GtkIMContext *imc, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+
+  if (dpyinfo->im.context == NULL)
+    return;
+  if (f == NULL)
+    return;
+
+  pgtk_enqueue_preedit (f, Qnil);
+}
+
+void
+im_context_preedit_start_cb (GtkIMContext *imc, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+  if (dpyinfo->im.context != NULL)
+    {
+      gdouble x, y;
+      x = XWINDOW (f->selected_window)->pixel_left
+	+ XWINDOW (f->selected_window)->phys_cursor.x
+	+ FRAME_COLUMN_WIDTH (f)
+	+ FRAME_LEFT_FRINGE_WIDTH (f)
+	+ WINDOW_LEFT_MARGIN_WIDTH (XWINDOW (f->selected_window));
+      y = XWINDOW (f->selected_window)->pixel_top
+	+ XWINDOW (f->selected_window)->phys_cursor.y;
+
+      GdkRectangle rect;
+      rect.x = (int) x;
+      rect.y = (int) y;
+      rect.width = FRAME_CURSOR_WIDTH (f);
+      rect.height = FRAME_LINE_HEIGHT (f);
+      gtk_im_context_set_cursor_location (dpyinfo->im.context, &rect);
+    }
+}
+
+void
+pgtk_im_focus_in (struct frame *f)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  if (dpyinfo->im.context != NULL)
+    {
+      gtk_im_context_reset (dpyinfo->im.context);
+#ifndef HAVE_GTK4
+      gtk_im_context_set_client_window
+#else
+	gtk_im_context_set_client_widget
+#endif
+	(dpyinfo->im.context,
+#ifndef HAVE_GTK4
+	 gtk_widget_get_window (FRAME_GTK_WIDGET (f))
+#else
+	 FRAME_GTK_WIDGET (f)
+#endif
+	 );
+      gtk_im_context_focus_in (dpyinfo->im.context);
+      gdouble x, y;
+      x = XWINDOW (f->selected_window)->pixel_left
+	+ XWINDOW (f->selected_window)->phys_cursor.x
+	+ FRAME_LEFT_FRINGE_WIDTH (f)
+	+ WINDOW_LEFT_MARGIN_WIDTH (XWINDOW (f->selected_window));
+      y = XWINDOW (f->selected_window)->pixel_top
+	+ XWINDOW (f->selected_window)->phys_cursor.y;
+      GdkRectangle rect;
+      rect.x = (int) x;
+      rect.y = (int) y;
+      rect.width = FRAME_CURSOR_WIDTH (f);
+      rect.height = FRAME_LINE_HEIGHT (f);
+      gtk_im_context_set_cursor_location (dpyinfo->im.context, &rect);
+    }
+  dpyinfo->im.focused_frame = f;
+}
+
+void
+pgtk_im_focus_out (struct frame *f)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  if (dpyinfo->im.focused_frame == f)
+    {
+      if (dpyinfo->im.context != NULL)
+        {
+          gtk_im_context_reset (dpyinfo->im.context);
+          gtk_im_context_focus_out (dpyinfo->im.context);
+#ifndef HAVE_GTK4
+          gtk_im_context_set_client_window (dpyinfo->im.context, NULL);
+#else
+	  gtk_im_context_set_client_widget (dpyinfo->im.context, NULL);
+#endif
+        }
+      dpyinfo->im.focused_frame = NULL;
+    }
+}
+#ifndef HAVE_GTK4
+bool
+pgtk_im_filter_keypress (struct frame *f, GdkEventKey *ev)
+#else
+bool
+pgtk_im_filter_keypress (struct frame *f, GdkEvent *ev)
+#endif
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  if (dpyinfo->im.context != NULL)
+    {
+      gdouble x, y;
+      x = XWINDOW (f->selected_window)->pixel_left
+	+ XWINDOW (f->selected_window)->phys_cursor.x
+	+ FRAME_LEFT_FRINGE_WIDTH (f)
+	+ WINDOW_LEFT_MARGIN_WIDTH (XWINDOW (f->selected_window));
+      y = XWINDOW (f->selected_window)->pixel_top
+	+ XWINDOW (f->selected_window)->phys_cursor.y;
+      GdkRectangle rect;
+      rect.x = (int) x;
+      rect.y = (int) y;
+      rect.width = FRAME_CURSOR_WIDTH (f);
+      rect.height = FRAME_LINE_HEIGHT (f);
+      gtk_im_context_set_cursor_location (dpyinfo->im.context, &rect);
+      if (gtk_im_context_filter_keypress (dpyinfo->im.context, ev))
+        return true;
+    }
+  return false;
+}
+
+void
+pgtk_im_init (struct pgtk_display_info *dpyinfo)
+{
+  dpyinfo->im.context = NULL;
+}
+
+void
+pgtk_im_finish (struct pgtk_display_info *dpyinfo)
+{
+  if (dpyinfo->im.context != NULL)
+    g_object_unref (dpyinfo->im.context);
+  dpyinfo->im.context = NULL;
+}
+
+DEFUN ("pgtk-use-im-context", Fpgtk_use_im_context, Spgtk_use_im_context, 1, 2,
+       0, doc: /* Set whether GTK input methods will be used. */)
+(Lisp_Object use_p, Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+
+  if (NILP (use_p))
+    {
+      if (dpyinfo->im.context != NULL)
+        {
+          gtk_im_context_reset (dpyinfo->im.context);
+          gtk_im_context_focus_out (dpyinfo->im.context);
+#ifndef HAVE_GTK4
+          gtk_im_context_set_client_window (dpyinfo->im.context, NULL);
+#else
+	  gtk_im_context_set_client_widget (dpyinfo->im.context, NULL);
+#endif
+        }
+    }
+  else
+    {
+      if (dpyinfo->im.context == NULL)
+        {
+          dpyinfo->im.context = gtk_im_multicontext_new ();
+	  g_signal_connect (dpyinfo->im.context, "commit",
+			    G_CALLBACK (im_context_commit_cb), dpyinfo);
+	  g_signal_connect (dpyinfo->im.context, "retrieve-surrounding",
+			    G_CALLBACK (im_context_retrieve_surrounding_cb),
+			    dpyinfo);
+	  g_signal_connect (dpyinfo->im.context, "delete-surrounding",
+			    G_CALLBACK (im_context_delete_surrounding_cb),
+			    dpyinfo);
+	  g_signal_connect (dpyinfo->im.context, "preedit-changed",
+			    G_CALLBACK (im_context_preedit_changed_cb),
+			dpyinfo);
+	  g_signal_connect (dpyinfo->im.context, "preedit-end",
+			    G_CALLBACK (im_context_preedit_end_cb), dpyinfo);
+	  g_signal_connect (dpyinfo->im.context, "preedit-start",
+			    G_CALLBACK (im_context_preedit_start_cb), dpyinfo);
+	  gtk_im_context_set_use_preedit (dpyinfo->im.context, TRUE);
+        }
+
+      if (dpyinfo->im.focused_frame)
+	pgtk_im_focus_in (dpyinfo->im.focused_frame);
+    }
+
+  return Qnil;
+}
+
+void
+syms_of_pgtkim (void)
+{
+  defsubr (&Spgtk_use_im_context);
+
+  DEFSYM (Qpgtk_refresh_preedit, "pgtk-refresh-preedit");
+  DEFSYM (Qul, "ul");
+  DEFSYM (Qfg, "fg");
+  DEFSYM (Qbg, "bg");
+}
diff --git a/src/pgtkim.h b/src/pgtkim.h
new file mode 100644
index 0000000000..39078d1caf
--- /dev/null
+++ b/src/pgtkim.h
@@ -0,0 +1,50 @@
+/* Pure Gtk+-3 communication module.      -*- coding: utf-8 -*-
+
+Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2018 Free Software
+Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This should be the first include, as it may set up #defines affecting
+   interpretation of even the system includes. */
+#include <config.h>
+
+#include <gtk/gtk.h>
+
+#ifndef __PGTKIM_H
+#define __PGTKIM_H
+
+#include "pgtkterm.h"
+
+void pgtk_im_focus_in (struct frame *f);
+void pgtk_im_focus_out (struct frame *f);
+#ifndef HAVE_GTK4
+bool pgtk_im_filter_keypress (struct frame *f, GdkEventKey *ev);
+#else
+bool pgtk_im_filter_keypress (struct frame *f, GdkEvent *ev);
+#endif
+void im_context_preedit_changed_cb (GtkIMContext *imc, gpointer user_data);
+
+void im_context_preedit_end_cb (GtkIMContext *imc, gpointer user_data);
+void im_context_preedit_start_cb (GtkIMContext *imc, gpointer user_data);
+
+void im_context_commit_cb (GtkIMContext *imc, gchar *str, gpointer user_data);
+gboolean im_context_retrieve_surrounding_cb (GtkIMContext *imc,
+                                             gpointer user_data);
+gboolean im_context_delete_surrounding_cb (GtkIMContext *imc, int offset,
+                                           int n_chars, gpointer user_data);
+
+#endif  /* __PGTKIM_H */
diff --git a/src/pgtkmenu.c b/src/pgtkmenu.c
new file mode 100644
index 0000000000..b042574fdb
--- /dev/null
+++ b/src/pgtkmenu.c
@@ -0,0 +1,1192 @@
+/* Pure GTK3 menu and toolbar module.
+   Copyright (C) 2019-2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This should be the first include, as it may set up #defines affecting
+   interpretation of even the system includes.  */
+#include <config.h>
+
+#include "lisp.h"
+#include "frame.h"
+#include "window.h"
+#include "character.h"
+#include "buffer.h"
+#include "keymap.h"
+#include "coding.h"
+#include "commands.h"
+#include "blockinput.h"
+#include "termhooks.h"
+#include "keyboard.h"
+#include "menu.h"
+#include "pdumper.h"
+#include "atimer.h"
+
+#ifndef HAVE_GTK4
+#include "gtkutil.h"
+#else
+#include "pgtksubr.h"
+#endif
+
+#include "gtkinter.h"
+#include <gtk/gtk.h>
+
+/* Gtk calls callbacks just because we tell it what item should be
+   selected in a radio group.  If this variable is set to a non-zero
+   value, we are creating menus and don't want callbacks right now.
+*/
+static bool pgtk_crazy_callback_abort;
+static Lisp_Object *volatile menu_item_selection;
+
+static volatile int popup_activated_flag;
+
+Lisp_Object
+pgtk_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
+{
+  Lisp_Object title;
+  char *err_name = NULL;
+  Lisp_Object selection;
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  check_window_system (f);
+  title = Fcar (contents);
+  CHECK_STRING (title);
+
+  if (NILP (Fcar (Fcdr (contents))))
+    contents = list2 (title, Fcons (build_string ("OK"), Qt));
+
+  list_of_panes (Fcons (contents, Qnil));
+
+  record_unwind_protect_void (unuse_menu_items);
+  block_input ();
+  selection = pgtk_dialog_show (f, title, header, (const char **) &err_name);
+  unblock_input ();
+  unbind_to (specpdl_count, Qnil);
+
+  discard_menu_items();
+
+  if (err_name)
+    error ("%s", err_name);
+
+  return selection;
+}
+
+/* This callback is called from the menu bar pulldown menu
+   when the user makes a selection.
+   Figure out what the user chose
+   and put the appropriate events into the keyboard buffer.  */
+static void
+menubar_selection_callback (GtkWidget *widget, gpointer client_data)
+{
+#ifndef HAVE_GTK4
+  xg_menu_item_cb_data *cb_data = client_data;
+#else
+  egtk_menu_item_cb_data *cb_data = client_data;
+#endif
+  if (pgtk_crazy_callback_abort)
+    return;
+
+  if (! cb_data || ! cb_data->cl_data || ! cb_data->cl_data->f)
+    return;
+
+  /* For a group of radio buttons, GTK calls the selection callback first
+     for the item that was active before the selection and then for the one that
+     is active after the selection.  For C-h k this means we get the help on
+     the deselected item and then the selected item is executed.  Prevent that
+     by ignoring the non-active item.  */
+#ifndef HAVE_GTK4
+  if (GTK_IS_RADIO_MENU_ITEM (widget)
+      && ! gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
+    return;
+#endif
+
+  /* When a menu is popped down, X generates a focus event (i.e. focus
+     goes back to the frame below the menu).  Since GTK buffers events,
+     we force it out here before the menu selection event.  Otherwise
+     sit-for will exit at once if the focus event follows the menu selection
+     event.  */
+
+  block_input ();
+#ifndef HAVE_GTK4
+  while (gtk_events_pending ())
+    gtk_main_iteration ();
+#else
+  while (g_main_context_pending (NULL))
+    run_main_loop_iteration ();
+#endif
+  unblock_input ();
+
+  find_and_call_menu_selection (cb_data->cl_data->f,
+                                cb_data->cl_data->menu_bar_items_used,
+                                cb_data->cl_data->menu_bar_vector,
+                                cb_data->call_data);
+#ifdef HAVE_GTK4
+  GtkWidget *parent = (GTK_IS_POPOVER (widget) ? widget :
+		       gtk_widget_get_parent
+		       (gtk_widget_get_parent (gtk_widget_get_parent (widget))));
+  gtk_popover_popdown (GTK_POPOVER (parent));
+#endif
+}
+
+static void
+menu_highlight_callback (GtkWidget *widget, gpointer call_data)
+{
+#ifdef HAVE_GTK4
+  egtk_menu_item_cb_data *cb_data;
+#else
+  xg_menu_item_cb_data *cb_data;
+#endif
+
+  cb_data = g_object_get_data (G_OBJECT (widget), XG_ITEM_DATA);
+  if (! cb_data) return;
+}
+
+
+/* This callback is invoked when a dialog or menu is finished being
+   used and has been unposted.  */
+
+static void
+popup_deactivate_callback (GtkWidget *widget, gpointer client_data)
+{
+  popup_activated_flag = 0;
+}
+#ifdef HAVE_GTK4
+static void
+atimer_cb (struct atimer *at)
+{
+  set_frame_menubar (at->client_data, true, true);
+}
+#endif
+/* Set the contents of the menubar widgets of frame F.
+   The argument FIRST_TIME is currently ignored;
+   it is set the first time this is called, from initialize_frame_menubar.  */
+
+void
+set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
+{
+  block_input ();
+  GtkWidget *menubar_widget;
+#ifdef HAVE_GTK3
+  GtkHeaderBar *hdb = NULL;
+#endif
+  Lisp_Object items;
+  widget_value *wv, *first_wv, *prev_wv = 0;
+  int i;
+  int *submenu_start, *submenu_end;
+  bool *submenu_top_level_items;
+  int *submenu_n_panes;
+
+#ifndef HAVE_GTK4
+  menubar_widget = f->output_data.pgtk->menubar_widget;
+#else
+  menubar_widget = GTK_WIDGET (FRAME_MENU_BAR (f));
+#endif
+#ifdef HAVE_GTK3
+  if (FRAME_PGTK_P (f))
+    hdb = FRAME_GTK_HEADER_BAR (f);
+#endif
+
+#ifdef HAVE_GTK4
+#define again FRAME_X_OUTPUT (f)->mbar_deep_atimer
+  if (!deep_p)
+    {
+      save_menu_items ();
+      again = start_atimer (ATIMER_RELATIVE, make_timespec (0, 2000),
+			    atimer_cb, f);
+      finish_menu_items ();
+      unblock_input ();
+      return;
+    }
+#undef again
+#endif
+
+  XSETFRAME (Vmenu_updating_frame, f);
+#ifdef HAVE_GTK4
+  deep_p = true;
+#else
+  deep_p = FRAME_EXTERNAL_MENU_BAR (f);
+#endif
+
+  if (deep_p)
+    {
+      struct buffer *prev = current_buffer;
+      Lisp_Object buffer;
+      ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+      int previous_menu_items_used = f->menu_bar_items_used;
+      Lisp_Object *previous_items
+	= alloca (previous_menu_items_used * sizeof *previous_items);
+      int subitems;
+
+      /* If we are making a new widget, its contents are empty,
+	 do always reinitialize them.  */
+#ifndef HAVE_GTK4
+      if (! menubar_widget)
+#endif
+	previous_menu_items_used = 0;
+
+      buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->contents;
+      specbind (Qinhibit_quit, Qt);
+      /* Don't let the debugger step into this code
+	 because it is not reentrant.  */
+      specbind (Qdebug_on_next_call, Qnil);
+
+      record_unwind_save_match_data ();
+      if (NILP (Voverriding_local_map_menu_flag))
+	{
+	  specbind (Qoverriding_terminal_local_map, Qnil);
+	  specbind (Qoverriding_local_map, Qnil);
+	}
+
+      set_buffer_internal_1 (XBUFFER (buffer));
+
+      /* Run the Lucid hook.  */
+      safe_run_hooks (Qactivate_menubar_hook);
+
+      /* If it has changed current-menubar from previous value,
+	 really recompute the menubar from the value.  */
+      if (! NILP (Vlucid_menu_bar_dirty_flag))
+	call0 (Qrecompute_lucid_menubar);
+      safe_run_hooks (Qmenu_bar_update_hook);
+      fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
+
+      items = FRAME_MENU_BAR_ITEMS (f);
+
+      /* Save the frame's previous menu bar contents data.  */
+      if (previous_menu_items_used)
+	memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents,
+		previous_menu_items_used * word_size);
+
+      /* Fill in menu_items with the current menu bar contents.
+	 This can evaluate Lisp code.  */
+      save_menu_items ();
+
+      menu_items = f->menu_bar_vector;
+      menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
+      subitems = ASIZE (items) / 4;
+      submenu_start = alloca ((subitems + 1) * sizeof *submenu_start);
+      submenu_end = alloca (subitems * sizeof *submenu_end);
+      submenu_n_panes = alloca (subitems * sizeof *submenu_n_panes);
+      submenu_top_level_items = alloca (subitems
+					* sizeof *submenu_top_level_items);
+      init_menu_items ();
+      for (i = 0; i < subitems; i++)
+	{
+	  Lisp_Object key, string, maps;
+
+	  key = AREF (items, 4 * i);
+	  string = AREF (items, 4 * i + 1);
+	  maps = AREF (items, 4 * i + 2);
+	  if (NILP (string))
+	    break;
+
+	  submenu_start[i] = menu_items_used;
+
+	  menu_items_n_panes = 0;
+	  submenu_top_level_items[i]
+	    = parse_single_submenu (key, string, maps);
+	  submenu_n_panes[i] = menu_items_n_panes;
+
+	  submenu_end[i] = menu_items_used;
+	}
+
+      submenu_start[i] = -1;
+      finish_menu_items ();
+
+      /* Convert menu_items into widget_value trees
+	 to display the menu.  This cannot evaluate Lisp code.  */
+
+      wv = make_widget_value ("menubar", NULL, true, Qnil);
+      wv->button_type = BUTTON_TYPE_NONE;
+      first_wv = wv;
+
+      for (i = 0; submenu_start[i] >= 0; i++)
+	{
+	  menu_items_n_panes = submenu_n_panes[i];
+	  wv = digest_single_submenu (submenu_start[i], submenu_end[i],
+				      submenu_top_level_items[i]);
+	  if (prev_wv)
+	    prev_wv->next = wv;
+	  else
+	    first_wv->contents = wv;
+	  /* Don't set wv->name here; GC during the loop might relocate it.  */
+	  wv->enabled = true;
+	  wv->button_type = BUTTON_TYPE_NONE;
+	  prev_wv = wv;
+	}
+
+      set_buffer_internal_1 (prev);
+
+      /* If there has been no change in the Lisp-level contents
+	 of the menu bar, skip redisplaying it.  Just exit.  */
+
+      /* Compare the new menu items with the ones computed last time.  */
+      for (i = 0; i < previous_menu_items_used; i++)
+	if (menu_items_used == i
+	    || (!EQ (previous_items[i], AREF (menu_items, i))))
+	  break;
+      if (i == menu_items_used && i == previous_menu_items_used && i != 0)
+	{
+	  /* The menu items have not changed.  Don't bother updating
+	     the menus in any form, since it would be a no-op.  */
+	  free_menubar_widget_value_tree (first_wv);
+	  discard_menu_items ();
+	  unbind_to (specpdl_count, Qnil);
+	  return;
+	}
+
+      /* The menu items are different, so store them in the frame.  */
+      fset_menu_bar_vector (f, menu_items);
+      f->menu_bar_items_used = menu_items_used;
+
+      /* This undoes save_menu_items.  */
+      unbind_to (specpdl_count, Qnil);
+
+      /* Now GC cannot happen during the lifetime of the widget_value,
+	 so it's safe to store data from a Lisp_String.  */
+      wv = first_wv->contents;
+      for (i = 0; i < ASIZE (items); i += 4)
+	{
+	  Lisp_Object string;
+	  string = AREF (items, i + 1);
+	  if (NILP (string))
+            break;
+          wv->name = SSDATA (string);
+          update_submenu_strings (wv->contents);
+          wv = wv->next;
+	}
+
+    }
+  else
+    {
+      /* Make a widget-value tree containing
+	 just the top level menu bar strings.  */
+
+      wv = make_widget_value ("menubar", NULL, true, Qnil);
+      wv->button_type = BUTTON_TYPE_NONE;
+      first_wv = wv;
+
+      items = FRAME_MENU_BAR_ITEMS (f);
+      for (i = 0; i < ASIZE (items); i += 4)
+	{
+	  Lisp_Object string;
+
+	  string = AREF (items, i + 1);
+	  if (NILP (string))
+	    break;
+
+	  wv = make_widget_value (SSDATA (string), NULL, true, Qnil);
+	  wv->button_type = BUTTON_TYPE_NONE;
+	  /* This prevents lwlib from assuming this
+	     menu item is really supposed to be empty.  */
+	  /* The intptr_t cast avoids a warning.
+	     This value just has to be different from small integers.  */
+	  wv->call_data = (void *) (intptr_t) (-1);
+
+	  if (prev_wv)
+	    prev_wv->next = wv;
+	  else
+	    first_wv->contents = wv;
+	  prev_wv = wv;
+	}
+
+      /* Forget what we thought we knew about what is in the
+	 detailed contents of the menu bar menus.
+	 Changing the top level always destroys the contents.  */
+      f->menu_bar_items_used = 0;
+    }
+
+  pgtk_crazy_callback_abort = true;
+#ifdef HAVE_GTK3
+  if (hdb && FRAME_DISPLAY_HEADER_BAR_MENU (f))
+    {
+      xg_modify_header_bar_widgets (hdb,
+				    f,
+				    first_wv,
+				    true,
+				    G_CALLBACK (menubar_selection_callback),
+				    G_CALLBACK (popup_deactivate_callback),
+				    G_CALLBACK (menu_highlight_callback));
+    }
+#ifndef HAVE_GTK4
+  else if (hdb)
+    {
+      xg_notify_header_bar_menu_state_changed (f);
+    }
+#endif
+#endif
+#ifdef HAVE_GTK4
+  if (FRAME_EXTERNAL_MENU_BAR (f))
+    FRAME_MENU_BAR (f) = build_popover_menu_bar
+      (first_wv, make_cl_data (NULL, f, G_CALLBACK (menu_highlight_callback)),
+       G_CALLBACK (menubar_selection_callback), G_CALLBACK (popup_deactivate_callback),
+       G_CALLBACK (menu_highlight_callback), f, GTK_WIDGET (menubar_widget));
+#endif
+
+#ifndef HAVE_GTK4
+  if (menubar_widget)
+    {
+      /* The fourth arg is DEEP_P, which says to consider the entire
+	 menu trees we supply, rather than just the menu bar item names.  */
+      xg_modify_menubar_widgets (menubar_widget,
+                                 f,
+                                 first_wv,
+                                 deep_p,
+                                 G_CALLBACK (menubar_selection_callback),
+                                 G_CALLBACK (popup_deactivate_callback),
+                                 G_CALLBACK (menu_highlight_callback));
+    }
+  else if (FRAME_EXTERNAL_MENU_BAR (f))
+    {
+      menubar_widget
+        = xg_create_widget ("menubar", "menubar", f, first_wv,
+                            G_CALLBACK (menubar_selection_callback),
+                            G_CALLBACK (popup_deactivate_callback),
+                            G_CALLBACK (menu_highlight_callback));
+
+      f->output_data.pgtk->menubar_widget = menubar_widget;
+    }
+#endif
+#ifndef HAVE_GTK4
+  free_menubar_widget_value_tree (first_wv);
+  xg_update_frame_menubar (f);
+#endif
+  pgtk_crazy_callback_abort = false;
+
+  unblock_input ();
+}
+
+
+
+/* Called from Fx_create_frame to create the initial menubar of a frame
+   before it is mapped, so that the window is mapped with the menubar already
+   there instead of us tacking it on later and thrashing the window after it
+   is visible.  */
+
+void
+initialize_frame_menubar (struct frame *f)
+{
+#ifdef HAVE_GTK4
+  FRAME_MENU_BAR (f) = NULL;
+  FRAME_X_OUTPUT (f)->lmme = Qnil;
+#endif
+  if (FRAME_MENU_BAR_LINES (f) && FRAME_EXTERNAL_MENU_BAR (f))
+    {
+      /* This function is called before the first chance to redisplay
+	 the frame.  It has to be, so the frame will have the right size.  */
+      fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f)));
+      set_frame_menubar (f, true, true);
+    }
+}
+
+
+void
+pgtk_activate_menubar (struct frame *f)
+{
+  set_frame_menubar (f, false, false);
+
+  /* f->output_data.pgtk->menubar_active = 1; */
+}
+
+static void
+dialog_selection_callback (GtkWidget *widget, gpointer client_data)
+{
+  /* Treat the pointer as an integer.  There's no problem
+     as long as pointers have enough bits to hold small integers.  */
+  if ((intptr_t) client_data != -1)
+    menu_item_selection = client_data;
+
+  popup_activated_flag = 0;
+}
+
+static const char * button_names [] = {
+  "button1", "button2", "button3", "button4", "button5",
+  "button6", "button7", "button8", "button9", "button10" };
+
+static void
+cleanup_widget_value_tree (void *arg)
+{
+#ifndef HAVE_GTK4
+  free_menubar_widget_value_tree (arg);
+#endif
+}
+
+static void
+pop_down_menu (void *arg)
+{
+  popup_activated_flag = 0;
+#ifndef HAVE_GTK4
+  block_input ();
+  gtk_widget_destroy (GTK_WIDGET (arg));
+  unblock_input ();
+#endif
+}
+
+static void
+create_and_show_dialog (struct frame *f, widget_value *first_wv)
+{
+  GtkWidget *menu;
+
+  eassert (FRAME_X_P (f));
+#ifndef HAVE_GTK4
+  menu = xg_create_widget ("dialog", first_wv->name, f, first_wv,
+                           G_CALLBACK (dialog_selection_callback),
+                           G_CALLBACK (popup_deactivate_callback),
+                           0);
+#else
+  menu = GTK_WIDGET (egtk_build_dialog (f, first_wv,
+					G_CALLBACK (dialog_selection_callback),
+					G_CALLBACK (popup_deactivate_callback)));
+#endif
+
+  if (menu)
+    {
+      ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+      record_unwind_protect_ptr (pop_down_menu, menu);
+#ifndef HAVE_GTK4
+      /* Display the menu.  */
+      gtk_widget_show_all (menu);
+
+      /* Process events that apply to the menu.  */
+      ++popup_activated_flag;
+
+      /* Process events in the Gtk event loop until done.  */
+      while (popup_activated_flag)
+	{
+	  gtk_main_iteration ();
+	}
+#else
+      gint response = gtk_dialog_run (GTK_DIALOG (menu));
+      if (response != GTK_RESPONSE_CANCEL)
+	gtk_widget_destroy (menu);
+#endif
+      unbind_to (specpdl_count, Qnil);
+    }
+}
+
+extern Lisp_Object
+pgtk_dialog_show (struct frame *f, Lisp_Object title,
+		  Lisp_Object header, const char **error_name)
+{
+  int i, nb_buttons=0;
+  char dialog_name[6];
+
+  widget_value *wv, *first_wv = 0, *prev_wv = 0;
+
+  /* Number of elements seen so far, before boundary.  */
+  int left_count = 0;
+  /* Whether we've seen the boundary between left-hand elts and right-hand.  */
+  bool boundary_seen = false;
+
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  eassert (FRAME_X_P (f));
+
+  *error_name = NULL;
+
+  if (menu_items_n_panes > 1)
+    {
+      *error_name = "Multiple panes in dialog box";
+      return Qnil;
+    }
+
+  /* Create a tree of widget_value objects
+     representing the text label and buttons.  */
+  {
+    Lisp_Object pane_name;
+    const char *pane_string;
+    pane_name = AREF (menu_items, MENU_ITEMS_PANE_NAME);
+    pane_string = (NILP (pane_name)
+		   ? "" : SSDATA (pane_name));
+    prev_wv = make_widget_value ("message", (char *) pane_string, true, Qnil);
+    first_wv = prev_wv;
+
+    /* Loop over all panes and items, filling in the tree.  */
+    i = MENU_ITEMS_PANE_LENGTH;
+    while (i < menu_items_used)
+      {
+
+	/* Create a new item within current pane.  */
+	Lisp_Object item_name, enable, descrip;
+	item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+	enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+	descrip
+	  = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
+
+	if (NILP (item_name))
+	  {
+	    free_menubar_widget_value_tree (first_wv);
+	    *error_name = "Submenu in dialog items";
+	    return Qnil;
+	  }
+	if (EQ (item_name, Qquote))
+	  {
+	    /* This is the boundary between left-side elts
+	       and right-side elts.  Stop incrementing right_count.  */
+	    boundary_seen = true;
+	    i++;
+	    continue;
+	  }
+	if (nb_buttons >= 9)
+	  {
+	    free_menubar_widget_value_tree (first_wv);
+	    *error_name = "Too many dialog items";
+	    return Qnil;
+	  }
+
+	wv = make_widget_value (button_names[nb_buttons],
+				SSDATA (item_name),
+				!NILP (enable), Qnil);
+	prev_wv->next = wv;
+	if (!NILP (descrip))
+	  wv->key = SSDATA (descrip);
+	wv->call_data = aref_addr (menu_items, i);
+	prev_wv = wv;
+
+	if (! boundary_seen)
+	  left_count++;
+
+	nb_buttons++;
+	i += MENU_ITEMS_ITEM_LENGTH;
+      }
+
+    /* If the boundary was not specified,
+       by default put half on the left and half on the right.  */
+    if (! boundary_seen)
+      left_count = nb_buttons - nb_buttons / 2;
+
+    wv = make_widget_value (dialog_name, NULL, false, Qnil);
+
+    /*  Frame title: 'Q' = Question, 'I' = Information.
+        Can also have 'E' = Error if, one day, we want
+        a popup for errors. */
+    if (NILP (header))
+      dialog_name[0] = 'Q';
+    else
+      dialog_name[0] = 'I';
+
+    /* Dialog boxes use a really stupid name encoding
+       which specifies how many buttons to use
+       and how many buttons are on the right. */
+    dialog_name[1] = '0' + nb_buttons;
+    dialog_name[2] = 'B';
+    dialog_name[3] = 'R';
+    /* Number of buttons to put on the right.  */
+    dialog_name[4] = '0' + nb_buttons - left_count;
+    dialog_name[5] = 0;
+    wv->contents = first_wv;
+    first_wv = wv;
+  }
+
+  /* No selection has been chosen yet.  */
+  menu_item_selection = 0;
+
+  /* Make sure to free the widget_value objects we used to specify the
+     contents even with longjmp.  */
+  record_unwind_protect_ptr (cleanup_widget_value_tree, first_wv);
+
+  /* Actually create and show the dialog.  */
+  create_and_show_dialog (f, first_wv);
+
+  unbind_to (specpdl_count, Qnil);
+
+  /* Find the selected item, and its pane, to return
+     the proper value.  */
+  if (menu_item_selection != 0)
+    {
+      i = 0;
+      while (i < menu_items_used)
+	{
+	  Lisp_Object entry;
+
+	  if (EQ (AREF (menu_items, i), Qt))
+	    i += MENU_ITEMS_PANE_LENGTH;
+	  else if (EQ (AREF (menu_items, i), Qquote))
+	    {
+	      /* This is the boundary between left-side elts and
+		 right-side elts.  */
+	      ++i;
+	    }
+	  else
+	    {
+	      entry
+		= AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+	      if (menu_item_selection == aref_addr (menu_items, i))
+		return entry;
+	      i += MENU_ITEMS_ITEM_LENGTH;
+	    }
+	}
+    }
+  else
+    /* Make "Cancel" equivalent to C-g.  */
+    quit ();
+
+  return Qnil;
+}
+
+static void
+popup_widget_loop
+#ifndef HAVE_GTK4
+(GtkWidget *widget)
+#else
+(GtkWidget *widget, GdkRectangle rect, struct frame *f)
+#endif
+{
+  ++popup_activated_flag;
+
+  /* Process events in the Gtk event loop until done.  */
+#ifndef HAVE_GTK4
+  while (popup_activated_flag)
+#else
+  while (popup_activated_flag &&
+	 (!GTK_IS_POPOVER_MENU (widget) ? gtk_widget_is_visible (widget) : 1))
+#endif
+    {
+      eassert (! NILP (menu_items));
+#ifndef HAVE_GTK4
+      gtk_main_iteration ();
+#else
+      run_main_loop_iteration ();
+      gtk_popover_set_pointing_to (GTK_POPOVER (widget), &rect);
+#endif
+    }
+#ifdef HAVE_GTK4
+  gtk_widget_destroy (widget);
+#endif
+}
+#ifndef HAVE_GTK4
+static void
+pgtk_dialog_selection_callback (GtkWidget *widget, gpointer client_data)
+{
+  /* Treat the pointer as an integer.  There's no problem
+     as long as pointers have enough bits to hold small integers.  */
+  if ((intptr_t) client_data != -1)
+    menu_item_selection = client_data;
+
+  popup_activated_flag = 0;
+}
+#endif
+static void
+popup_selection_callback2 (GtkWidget *widget, gpointer client_data)
+{
+#ifdef HAVE_GTK4
+  egtk_menu_item_cb_data
+#else
+    xg_menu_item_cb_data
+#endif
+    *cb_data = client_data;
+
+  if (pgtk_crazy_callback_abort) return;
+  if (cb_data) menu_item_selection = cb_data->call_data;
+
+#ifdef HAVE_GTK4
+  popup_activated_flag = 0;
+  GtkWidget *parent = (GTK_IS_POPOVER (widget) ? widget :
+		       gtk_widget_get_parent
+		       (gtk_widget_get_parent (gtk_widget_get_parent (widget))));
+  gtk_widget_hide (parent);
+#endif
+}
+
+
+static void
+popup_deactivate_callback2 (GtkWidget *widget, gpointer client_data)
+{
+  popup_activated_flag = 0;
+}
+
+static void
+menu_highlight_callback2 (GtkWidget *widget, gpointer call_data)
+{
+  /* xg_menu_item_cb_data *cb_data; */
+  /* Lisp_Object help; */
+
+  /* cb_data = g_object_get_data (G_OBJECT (widget), XG_ITEM_DATA); */
+  /* if (! cb_data) return; */
+
+  /* help = call_data ? cb_data->help : Qnil; */
+
+  /* /\* If popup_activated_flag is greater than 1 we are in a popup menu. */
+  /*    Don't pass the frame to show_help_event for those. */
+  /*    Passing frame creates an Emacs event.  As we are looping in */
+  /*    popup_widget_loop, it won't be handled.  Passing NULL shows the tip */
+  /*    directly without using an Emacs event.  This is what the Lucid code */
+  /*    does below.  *\/ */
+  /* show_help_event (popup_activated_flag <= 1 ? cb_data->cl_data->f : NULL, */
+  /*                  widget, help); */
+  // TODO: NO-OP
+}
+
+/* Pop up the dialog for frame F defined by FIRST_WV and loop until the
+   dialog pops down.
+   menu_item_selection will be set to the selection.  */
+#ifndef HAVE_GTK4
+static void
+pgtk_create_and_show_dialog (struct frame *f, widget_value *first_wv)
+{
+  GtkWidget *menu;
+
+  eassert (FRAME_X_P (f));
+  menu = xg_create_widget ("dialog", first_wv->name, f, first_wv,
+                           G_CALLBACK (pgtk_dialog_selection_callback),
+                           G_CALLBACK (popup_deactivate_callback2), 0);
+  if (menu)
+    {
+      ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+      record_unwind_protect_ptr (pop_down_menu, menu);
+      gtk_widget_show_all (menu);
+      unbind_to (specpdl_count, Qnil);
+    }
+}
+#endif
+
+#ifndef GDK_VERSION_3_22
+#ifndef HAVE_GTK4
+static void
+pos_func (GtkMenu *menu,
+	  gint *x,
+	  gint *y,
+	  gboolean *push_in,
+	  gpointer user_data)
+{
+  *x = ((GdkRectangle *) user_data)->x;
+  *y = ((GdkRectangle *) user_data)->y;
+  *push_in = true;
+}
+#endif
+#endif
+
+static void
+create_and_show_popup_menu (struct frame *f, widget_value *first_wv,
+			    int x, int y, bool for_click)
+{
+  GtkWidget *menu;
+#ifndef HAVE_GTK4
+  int i;
+  pgtk_crazy_callback_abort = true;
+  eassert (FRAME_X_P (f));
+  menu = xg_create_widget ("popup", first_wv->name, f, first_wv,
+                           G_CALLBACK (popup_selection_callback2),
+                           G_CALLBACK (popup_deactivate_callback2),
+                           G_CALLBACK (menu_highlight_callback2));
+  pgtk_crazy_callback_abort = false;
+  GdkRectangle rect;
+  rect.x = x;
+  rect.y = y;
+  rect.width = 100;
+  rect.height = 100;
+
+  gtk_menu_attach_to_widget (GTK_MENU (menu), FRAME_GTK_OUTER_WIDGET (f), NULL);
+
+  gtk_widget_show_all (GTK_WIDGET (menu));
+
+#ifdef GDK_VERSION_3_22
+  gtk_menu_popup_at_rect (GTK_MENU (menu),
+			  gtk_widget_get_window (FRAME_GTK_OUTER_WIDGET (f)),
+			  &rect, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH_EAST,
+			  FRAME_DISPLAY_INFO (f)->last_event);
+#else
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, pos_func, &rect, 0, gtk_get_current_event_time ());
+#endif
+  popup_widget_loop (menu);
+#else
+  GdkRectangle rect;
+  rect.x = x;
+  rect.y = y;
+  rect.width = 1;
+  rect.height = 1;
+  menu = GTK_WIDGET (build_mm_from_wv (first_wv, f,
+				       G_CALLBACK (popup_selection_callback2),
+				       G_CALLBACK (popup_deactivate_callback2),
+				       G_CALLBACK (menu_highlight_callback2),
+				       true, false, NULL, "", NULL, NULL, &rect));
+  int m, n, z;
+  gtk_widget_measure (menu, GTK_ORIENTATION_HORIZONTAL, true,
+		      &m, &n, &z, &z);
+  rect.x -= n / 2;
+  gtk_popover_set_pointing_to (GTK_POPOVER (menu), &rect);
+  gtk_popover_popup (GTK_POPOVER (menu));
+  popup_widget_loop (menu, rect, f);
+#endif
+}
+
+Lisp_Object
+pgtk_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object title,
+                const char **error_name)
+{
+  block_input ();
+  FRAME_X_OUTPUT (f)->menubar_or_popup_count++;
+  int i;
+  widget_value *wv, *save_wv = 0, *first_wv = 0, *prev_wv = 0;
+  widget_value **submenu_stack
+    = alloca (menu_items_used * sizeof *submenu_stack);
+  Lisp_Object *subprefix_stack
+    = alloca (menu_items_used * sizeof *subprefix_stack);
+  int submenu_depth = 0;
+
+  ptrdiff_t specpdl_count = SPECPDL_INDEX ();
+
+  eassert (FRAME_X_P (f));
+
+  *error_name = NULL;
+
+  if (menu_items_used <= MENU_ITEMS_PANE_LENGTH)
+    {
+      *error_name = "Empty menu";
+      return Qnil;
+    }
+
+  wv = make_widget_value ("menu", NULL, true, Qnil);
+  wv->button_type = BUTTON_TYPE_NONE;
+  first_wv = wv;
+  bool first_pane = true;
+
+  i = 0;
+  while (i < menu_items_used)
+    {
+      if (NILP (AREF (menu_items, i)))
+	{
+	  submenu_stack[submenu_depth++] = save_wv;
+	  save_wv = prev_wv;
+	  prev_wv = 0;
+	  first_pane = true;
+	  i++;
+	}
+      else if (EQ (AREF (menu_items, i), Qlambda))
+	{
+	  prev_wv = save_wv;
+	  save_wv = submenu_stack[--submenu_depth];
+	  first_pane = false;
+	  i++;
+	}
+      else if (EQ (AREF (menu_items, i), Qt)
+	       && submenu_depth != 0)
+	i += MENU_ITEMS_PANE_LENGTH;
+      /* Ignore a nil in the item list.
+	 It's meaningful only for dialog boxes.  */
+      else if (EQ (AREF (menu_items, i), Qquote))
+	i += 1;
+      else if (EQ (AREF (menu_items, i), Qt))
+	{
+	  /* Create a new pane.  */
+	  Lisp_Object pane_name, prefix;
+	  const char *pane_string;
+
+	  pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
+	  prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+
+#ifndef HAVE_MULTILINGUAL_MENU
+	  if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name))
+	    {
+	      pane_name = ENCODE_MENU_STRING (pane_name);
+	      ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name);
+	    }
+#endif
+	  pane_string = (NILP (pane_name)
+			 ? "" : SSDATA (pane_name));
+	  /* If there is just one top-level pane, put all its items directly
+	     under the top-level menu.  */
+	  if (menu_items_n_panes == 1)
+	    pane_string = "";
+
+	  /* If the pane has a meaningful name,
+	     make the pane a top-level menu item
+	     with its items as a submenu beneath it.  */
+	  if (!(menuflags & MENU_KEYMAPS) && strcmp (pane_string, ""))
+	    {
+	      wv = make_widget_value (pane_string, NULL, true, Qnil);
+	      if (save_wv)
+		save_wv->next = wv;
+	      else
+		first_wv->contents = wv;
+	      if ((menuflags & MENU_KEYMAPS) && !NILP (prefix))
+		wv->name++;
+	      wv->button_type = BUTTON_TYPE_NONE;
+	      save_wv = wv;
+	      prev_wv = 0;
+	    }
+	  else if (first_pane)
+	    {
+	      save_wv = wv;
+	      prev_wv = 0;
+	    }
+	  first_pane = false;
+	  i += MENU_ITEMS_PANE_LENGTH;
+	}
+      else
+	{
+	  /* Create a new item within current pane.  */
+	  Lisp_Object item_name, enable, descrip, def, type, selected, help;
+	  item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+	  enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+	  descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY);
+	  def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
+	  type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
+	  selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
+	  help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
+
+#ifndef HAVE_MULTILINGUAL_MENU
+          if (STRINGP (item_name) && STRING_MULTIBYTE (item_name))
+	    {
+	      item_name = ENCODE_MENU_STRING (item_name);
+	      ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name);
+	    }
+
+          if (STRINGP (descrip) && STRING_MULTIBYTE (descrip))
+	    {
+	      descrip = ENCODE_MENU_STRING (descrip);
+	      ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip);
+	    }
+#endif /* not HAVE_MULTILINGUAL_MENU */
+
+	  wv = make_widget_value (SSDATA (item_name), NULL, !NILP (enable),
+				  STRINGP (help) ? help : Qnil);
+	  if (prev_wv)
+	    prev_wv->next = wv;
+	  else
+	    save_wv->contents = wv;
+	  if (!NILP (descrip))
+	    wv->key = SSDATA (descrip);
+	  /* If this item has a null value,
+	     make the call_data null so that it won't display a box
+	     when the mouse is on it.  */
+	  wv->call_data = !NILP (def) ? aref_addr (menu_items, i) : 0;
+
+	  if (NILP (type))
+	    wv->button_type = BUTTON_TYPE_NONE;
+	  else if (EQ (type, QCtoggle))
+	    wv->button_type = BUTTON_TYPE_TOGGLE;
+	  else if (EQ (type, QCradio))
+	    wv->button_type = BUTTON_TYPE_RADIO;
+	  else
+	    emacs_abort ();
+
+	  wv->selected = !NILP (selected);
+
+	  prev_wv = wv;
+
+	  i += MENU_ITEMS_ITEM_LENGTH;
+	}
+    }
+
+  if (!NILP (title))
+    {
+      widget_value *wv_title;
+      widget_value *wv_sep1 = make_widget_value ("--", NULL, false, Qnil);
+      widget_value *wv_sep2 = make_widget_value ("--", NULL, false, Qnil);
+
+      wv_sep2->next = first_wv->contents;
+      wv_sep1->next = wv_sep2;
+
+#ifndef HAVE_MULTILINGUAL_MENU
+      if (STRING_MULTIBYTE (title))
+	title = ENCODE_MENU_STRING (title);
+#endif
+
+      wv_title = make_widget_value (SSDATA (title), NULL, true, Qnil);
+      wv_title->button_type = BUTTON_TYPE_NONE;
+      wv_title->next = wv_sep1;
+      first_wv->contents = wv_title;
+    }
+
+  menu_item_selection = 0;
+  record_unwind_protect_ptr (cleanup_widget_value_tree, first_wv);
+  create_and_show_popup_menu (f, first_wv, x, y,
+			      menuflags & MENU_FOR_CLICK);
+  FRAME_X_OUTPUT (f)->menubar_or_popup_count--;
+  unbind_to (specpdl_count, Qnil);
+
+  if (menu_item_selection != 0)
+    {
+      Lisp_Object prefix, entry;
+
+      prefix = entry = Qnil;
+      i = 0;
+      while (i < menu_items_used)
+	{
+	  if (NILP (AREF (menu_items, i)))
+	    {
+	      subprefix_stack[submenu_depth++] = prefix;
+	      prefix = entry;
+	      i++;
+	    }
+	  else if (EQ (AREF (menu_items, i), Qlambda))
+	    {
+	      prefix = subprefix_stack[--submenu_depth];
+	      i++;
+	    }
+	  else if (EQ (AREF (menu_items, i), Qt))
+	    {
+	      prefix
+		= AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+	      i += MENU_ITEMS_PANE_LENGTH;
+	    }
+	  /* Ignore a nil in the item list.
+	     It's meaningful only for dialog boxes.  */
+	  else if (EQ (AREF (menu_items, i), Qquote))
+	    i += 1;
+	  else
+	    {
+	      entry
+		= AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+	      if (menu_item_selection == aref_addr (menu_items, i))
+		{
+		  if (menuflags & MENU_KEYMAPS)
+		    {
+		      int j;
+
+		      entry = list1 (entry);
+		      if (!NILP (prefix))
+			entry = Fcons (prefix, entry);
+		      for (j = submenu_depth - 1; j >= 0; j--)
+			if (!NILP (subprefix_stack[j]))
+			  entry = Fcons (subprefix_stack[j], entry);
+		    }
+		  unblock_input ();
+		  return entry;
+		}
+	      i += MENU_ITEMS_ITEM_LENGTH;
+	    }
+	}
+    }
+  else if (!(menuflags & MENU_FOR_CLICK))
+    {
+      unblock_input ();
+      /* Make "Cancel" equivalent to C-g.  */
+      quit ();
+    }
+  unblock_input ();
+  return Qnil;
+}
+
+/* The following is used by delayed window autoselection.  */
+
+DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0,
+       doc: /* SKIP: real doc in xmenu.c.  */)
+  (void)
+{
+  return
+    FRAME_PGTK_P (XFRAME (selected_frame)) &&
+    FRAME_X_OUTPUT (XFRAME (selected_frame))->menubar_or_popup_count ? Qt : Qnil;
+}
+
+void
+syms_of_pgtkmenu (void)
+{
+  DEFSYM (Qdebug_on_next_call, "debug-on-next-call");
+  DEFSYM (Qunsupported__w32_dialog, "unsupported--w32-dialog");
+
+  defsubr (&Smenu_or_popup_active_p);
+}
diff --git a/src/pgtkselect.c b/src/pgtkselect.c
new file mode 100644
index 0000000000..2a65ce49ba
--- /dev/null
+++ b/src/pgtkselect.c
@@ -0,0 +1,691 @@
+/* Gtk selection processing for emacs.
+   Copyright (C) 1993-1994, 2005-2006, 2008-2018 Free Software
+   Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/*
+Originally by Carl Edman
+Updated by Christian Limpach (chris@nice.ch)
+OpenStep/Rhapsody port by Scott Bender (sbender@harmony-ds.com)
+macOS/Aqua port by Christophe de Dinechin (descubes@earthlink.net)
+GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
+*/
+
+/* This should be the first include, as it may set up #defines affecting
+   interpretation of even the system includes.  */
+#include <config.h>
+
+#include <gdk/gdk.h>
+#include "lisp.h"
+#include "keyboard.h"
+#include "pgtkselect.h"
+#include "pgtkterm.h"
+#include "termhooks.h"
+
+#if 0
+static Lisp_Object Vselection_alist;
+#endif
+
+#ifndef HAVE_GTK4
+static GQuark quark_primary_data = 0;
+static GQuark quark_primary_size = 0;
+static GQuark quark_secondary_data = 0;
+static GQuark quark_secondary_size = 0;
+static GQuark quark_clipboard_data = 0;
+static GQuark quark_clipboard_size = 0;
+#endif
+
+#ifdef HAVE_GTK4
+Lisp_Object v_primary_selection;
+Lisp_Object selection_tbl;
+#endif
+
+/* ==========================================================================
+
+    Internal utility functions
+
+   ========================================================================== */
+
+/* From a Lisp_Object, return a suitable frame for selection
+   operations.  OBJECT may be a frame, a terminal object, or nil
+   (which stands for the selected frame--or, if that is not an pgtk
+   frame, the first pgtk display on the list).  If no suitable frame can
+   be found, return NULL.  */
+
+static struct frame *
+frame_for_pgtk_selection (Lisp_Object object)
+{
+  Lisp_Object tail, frame;
+  struct frame *f;
+
+  if (NILP (object))
+    {
+      f = XFRAME (selected_frame);
+      if (FRAME_PGTK_P (f) && FRAME_LIVE_P (f))
+        return f;
+
+      FOR_EACH_FRAME (tail, frame)
+      {
+        f = XFRAME (frame);
+        if (FRAME_PGTK_P (f) && FRAME_LIVE_P (f))
+          return f;
+      }
+    }
+  else if (TERMINALP (object))
+    {
+      struct terminal *t = decode_live_terminal (object);
+
+      if (t->type == output_pgtk)
+        FOR_EACH_FRAME (tail, frame)
+        {
+          f = XFRAME (frame);
+          if (FRAME_LIVE_P (f) && f->terminal == t)
+            return f;
+        }
+    }
+  else if (FRAMEP (object))
+    {
+      f = XFRAME (object);
+      if (FRAME_PGTK_P (f) && FRAME_LIVE_P (f))
+        return f;
+    }
+
+  return NULL;
+}
+
+#ifndef HAVE_GTK4
+static GtkClipboard *
+symbol_to_gtk_clipboard (GtkWidget *widget, Lisp_Object symbol)
+{
+  GdkAtom atom;
+
+  CHECK_SYMBOL (symbol);
+  if (NILP (symbol))
+    {
+      atom = GDK_SELECTION_PRIMARY;
+    }
+  else if (EQ (symbol, QCLIPBOARD))
+    {
+      atom = GDK_SELECTION_CLIPBOARD;
+    }
+  else if (EQ (symbol, QPRIMARY))
+    {
+      atom = GDK_SELECTION_PRIMARY;
+    }
+  else if (EQ (symbol, QSECONDARY))
+    {
+      atom = GDK_SELECTION_SECONDARY;
+    }
+  else if (EQ (symbol, Qt))
+    {
+      atom = GDK_SELECTION_SECONDARY;
+    }
+  else
+    {
+      atom = 0;
+      error ("Bad selection");
+    }
+
+  return gtk_widget_get_clipboard (widget, atom);
+}
+#endif
+
+#ifndef HAVE_GTK4
+static void
+selection_type_to_quarks (GdkAtom type, GQuark *quark_data, GQuark *quark_size)
+{
+  if (type == GDK_SELECTION_PRIMARY)
+    {
+      *quark_data = quark_primary_data;
+      *quark_size = quark_primary_size;
+    }
+  else if (type == GDK_SELECTION_SECONDARY)
+    {
+      *quark_data = quark_secondary_data;
+      *quark_size = quark_secondary_size;
+    }
+  else if (type == GDK_SELECTION_CLIPBOARD)
+    {
+      *quark_data = quark_clipboard_data;
+      *quark_size = quark_clipboard_size;
+    }
+  else
+    {
+      *quark_data = quark_clipboard_data;
+      *quark_size = quark_clipboard_size;
+    }
+}
+#endif
+#ifndef HAVE_GTK4
+static void
+get_func (GtkClipboard *cb, GtkSelectionData *data, guint info,
+          gpointer user_data_or_owner)
+{
+#ifndef HAVE_GTK4
+  PGTK_TRACE ("get_func:");
+  GObject *obj = G_OBJECT (user_data_or_owner);
+  const char *str;
+  int size;
+  GQuark quark_data, quark_size;
+#if defined(GDK_VERSION_3_22) || defined(HAVE_GTK4)
+  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
+                            &quark_size);
+#else
+  selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
+#endif
+  str = g_object_get_qdata (obj, quark_data);
+  size = GPOINTER_TO_SIZE (g_object_get_qdata (obj, quark_size));
+  PGTK_TRACE ("get_func: str: %s", str);
+  gtk_selection_data_set_text (data, str, size);
+#endif
+}
+#endif
+#ifndef HAVE_GTK4
+static void
+clear_func (GtkClipboard *cb, gpointer user_data_or_owner)
+{
+#ifndef HAVE_GTK4
+  PGTK_TRACE ("clear_func:");
+  GObject *obj = G_OBJECT (user_data_or_owner);
+  GQuark quark_data, quark_size;
+
+#ifdef GDK_VERSION_3_22
+  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
+                            &quark_size);
+#else
+  selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
+#endif
+
+  g_object_set_qdata (obj, quark_data, NULL);
+  g_object_set_qdata (obj, quark_size, 0);
+#endif
+}
+#endif
+/* ==========================================================================
+
+    Functions used externally
+
+   ========================================================================== */
+
+void
+pgtk_selection_init (void)
+{
+#ifndef HAVE_GTK4
+  if (quark_primary_data == 0)
+    {
+      quark_primary_data = g_quark_from_static_string ("pgtk-primary-data");
+      quark_primary_size = g_quark_from_static_string ("pgtk-primary-size");
+      quark_secondary_data = g_quark_from_static_string ("pgtk-secondary-data");
+      quark_secondary_size = g_quark_from_static_string ("pgtk-secondary-size");
+      quark_clipboard_data = g_quark_from_static_string ("pgtk-clipboard-data");
+      quark_clipboard_size = g_quark_from_static_string ("pgtk-clipboard-size");
+    }
+#else
+  v_primary_selection = Qnil;
+#endif
+}
+
+#ifndef HAVE_GTK4
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+void
+pgtk_selection_lost (GtkWidget *widget, GdkEventSelection *event,
+                     gpointer user_data)
+#else
+gboolean
+pgtk_selection_lost (GtkWidget *widget, GdkEventSelection *event,
+                     struct frame *frame)
+#endif
+{
+  GQuark quark_data, quark_size;
+  PGTK_TRACE ("pgtk_selection_lost:");
+
+  selection_type_to_quarks (event->selection, &quark_data, &quark_size);
+
+  g_object_set_qdata (G_OBJECT (widget), quark_data, NULL);
+  g_object_set_qdata (G_OBJECT (widget), quark_size, 0);
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+  return true;
+#endif
+}
+#endif
+#ifndef HAVE_GTK4
+static bool
+pgtk_selection_usable (void)
+{
+  GdkDisplayManager *dpyman = gdk_display_manager_get ();
+  GSList *list = gdk_display_manager_list_displays (dpyman);
+  int len = g_slist_length (list);
+  g_slist_free (list);
+  return len < 2;
+}
+#endif
+/* ==========================================================================
+
+    Lisp Defuns
+
+   ========================================================================== */
+
+DEFUN ("pgtk-own-selection-internal", Fpgtk_own_selection_internal,
+       Spgtk_own_selection_internal, 2, 3, 0,
+       doc: /* Assert an X selection of type SELECTION and value VALUE.
+SELECTION is a symbol, typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
+\(Those are literal upper-case symbol names, since that's what X expects.)
+VALUE is typically a string, or a cons of two markers, but may be
+anything that the functions on `selection-converter-alist' know about.
+
+FRAME should be a frame that should own the selection.  If omitted or
+nil, it defaults to the selected frame.*/)
+(Lisp_Object selection, Lisp_Object value, Lisp_Object frame)
+{
+#ifndef HAVE_GTK4
+  PGTK_TRACE ("pgtk-own-selection-internal.");
+  Lisp_Object successful_p = Qnil;
+  Lisp_Object target_symbol, rest;
+  GtkClipboard *cb;
+  struct frame *f;
+  GQuark quark_data, quark_size;
+
+  check_window_system (NULL);
+
+  if (!pgtk_selection_usable ())
+    return Qnil;
+
+  if (NILP (frame))
+    frame = selected_frame;
+  if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
+    error ("pgtk selection unavailable for this frame");
+  f = XFRAME (frame);
+
+  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+
+#if defined(GDK_VERSION_3_22) || defined(HAVE_GTK4)
+  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
+                            &quark_size);
+#else
+  selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
+#endif
+
+  /* We only support copy of text.  */
+  target_symbol = QTEXT;
+  if (STRINGP (value))
+    {
+      GtkTargetList *list;
+      GtkTargetEntry *targets;
+      gint n_targets;
+      GtkWidget *widget;
+
+      list = gtk_target_list_new (NULL, 0);
+      gtk_target_list_add_text_targets (list, 0);
+
+      targets = gtk_target_table_new_from_list (list, &n_targets);
+
+      int size = SBYTES (value);
+      gchar *str = xmalloc (size + 1);
+      memcpy (str, SSDATA (value), size);
+      str[size] = '\0';
+
+      widget = FRAME_GTK_WIDGET (f);
+      g_object_set_qdata_full (G_OBJECT (widget), quark_data, str, xfree);
+      g_object_set_qdata_full (G_OBJECT (widget), quark_size,
+                               GSIZE_TO_POINTER (size), NULL);
+
+      PGTK_TRACE ("set_with_owner: owner=%p", FRAME_GTK_WIDGET (f));
+      if (gtk_clipboard_set_with_owner (cb, targets, n_targets, get_func,
+                                        clear_func,
+                                        G_OBJECT (FRAME_GTK_WIDGET (f))))
+        {
+          PGTK_TRACE ("set_with_owner succeeded..");
+          successful_p = Qt;
+        }
+      else
+        {
+          PGTK_TRACE ("set_with_owner failed.");
+        }
+      gtk_clipboard_set_can_store (cb, NULL, 0);
+
+      gtk_target_table_free (targets, n_targets);
+      gtk_target_list_unref (list);
+    }
+
+  if (!EQ (Vpgtk_sent_selection_hooks, Qunbound))
+    {
+      /* FIXME: Use run-hook-with-args!  */
+      for (rest = Vpgtk_sent_selection_hooks; CONSP (rest); rest = Fcdr (rest))
+        call3 (Fcar (rest), selection, target_symbol, successful_p);
+    }
+
+  return value;
+#else
+  if (EQ (selection, QCLIPBOARD))
+    {
+      if (NILP (frame))
+	frame = selected_frame;
+      if (!FRAME_LIVE_P (XFRAME (frame)) || !FRAME_PGTK_P (XFRAME (frame)))
+	error ("pgtk selection unavailable for this frame");
+      struct frame *f = XFRAME (frame);
+      GdkClipboard *clipboard = gdk_display_get_clipboard (FRAME_X_DISPLAY (f));
+
+      CHECK_STRING (value);
+      gdk_clipboard_set_text (clipboard, SSDATA (value));
+      return value;
+    }
+  else if (EQ (selection, QPRIMARY))
+    {
+      v_primary_selection = value;
+      return v_primary_selection;
+    }
+  else
+    {
+      if (NILP (selection_tbl))
+	selection_tbl = Fmake_hash_table (0, NULL);
+      Fputhash (selection, value, selection_tbl);
+      return selection_tbl;
+    }
+  return Qnil;
+#endif
+}
+
+DEFUN ("pgtk-disown-selection-internal", Fpgtk_disown_selection_internal,
+       Spgtk_disown_selection_internal, 1, 3, 0,
+       doc: /* If we own the selection SELECTION, disown it.
+Disowning it means there is no such selection.
+
+Sets the last-change time for the selection to TIME-OBJECT (by default
+the time of the last event).
+
+TERMINAL should be a terminal object or a frame specifying the X
+server to query.  If omitted or nil, that stands for the selected
+frame's display, or the first available X display.
+
+On Nextstep, the TIME-OBJECT and TERMINAL arguments are unused.
+On MS-DOS, all this does is return non-nil if we own the selection.
+On PGTK, the TIME-OBJECT is unused.  */)
+(Lisp_Object selection, Lisp_Object time_object, Lisp_Object terminal)
+{
+#ifndef HAVE_GTK4
+  PGTK_TRACE ("pgtk-disown-selection-internal.");
+
+  struct frame *f = frame_for_pgtk_selection (terminal);
+  GtkClipboard *cb;
+
+  if (!pgtk_selection_usable ())
+    return Qnil;
+
+  if (!f)
+    return Qnil;
+
+  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+
+  gtk_clipboard_clear (cb);
+#else
+  if (EQ (selection, QPRIMARY))
+    {
+      GdkClipboard *clib = gdk_display_get_clipboard
+	(FRAME_X_DISPLAY (frame_for_pgtk_selection (terminal)));
+      gdk_clipboard_set_value (clib, NULL);
+    }
+  else if (EQ (selection, QPRIMARY))
+    {
+      v_primary_selection = NULL;
+    }
+  else
+    {
+      Fremhash (selection, selection_tbl);
+    }
+#endif
+
+  return Qt;
+}
+
+DEFUN ("pgtk-selection-exists-p", Fpgtk_selection_exists_p, Spgtk_selection_exists_p,
+       0, 2, 0, doc: /* Whether there is an owner for the given X selection.
+SELECTION should be the name of the selection in question, typically
+one of the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.  (X expects
+these literal upper-case names.)  The symbol nil is the same as
+`PRIMARY', and t is the same as `SECONDARY'.
+
+TERMINAL should be a terminal object or a frame specifying the X
+server to query.  If omitted or nil, that stands for the selected
+frame's display, or the first available X display.
+
+On Nextstep, TERMINAL is unused.
+On GTK 4, SELECTION is unused.  */)
+(Lisp_Object selection, Lisp_Object terminal)
+{
+#ifndef HAVE_GTK4
+  PGTK_TRACE ("pgtk-selection-exists-p.");
+  struct frame *f = frame_for_pgtk_selection (terminal);
+  GtkClipboard *cb;
+
+  if (!pgtk_selection_usable ())
+    return Qnil;
+
+  if (!f)
+    return Qnil;
+
+  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+
+  return gtk_clipboard_wait_is_text_available (cb) ? Qt : Qnil;
+#else
+  return Qt;
+#endif
+}
+
+DEFUN ("pgtk-selection-owner-p", Fpgtk_selection_owner_p, Spgtk_selection_owner_p,
+       0, 2, 0,
+       doc: /* Whether the current Emacs process owns the given X Selection.
+The arg should be the name of the selection in question, typically one of
+the symbols `PRIMARY', `SECONDARY', or `CLIPBOARD'.
+\(Those are literal upper-case symbol names, since that's what X expects.)
+For convenience, the symbol nil is the same as `PRIMARY',
+and t is the same as `SECONDARY'.
+
+TERMINAL should be a terminal object or a frame specifying the X
+server to query.  If omitted or nil, that stands for the selected
+frame's display, or the first available X display.
+
+On Nextstep, TERMINAL is unused.  */)
+(Lisp_Object selection, Lisp_Object terminal)
+{
+#ifndef HAVE_GTK4
+  struct frame *f = frame_for_pgtk_selection (terminal);
+  GtkClipboard *cb;
+  GObject *obj;
+  GQuark quark_data, quark_size;
+
+  if (!pgtk_selection_usable ())
+    return Qnil;
+
+  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection);
+#if defined(GDK_VERSION_3_22)
+  selection_type_to_quarks (gtk_clipboard_get_selection (cb), &quark_data,
+                            &quark_size);
+#else
+  selection_type_to_quarks (GDK_SELECTION_CLIPBOARD, &quark_data, &quark_size);
+#endif
+  obj = gtk_clipboard_get_owner (cb);
+
+  return g_object_get_qdata (obj, quark_data) != NULL ? Qt : Qnil;
+#else
+  return Qt;
+#endif
+}
+
+#ifdef HAVE_GTK4
+typedef struct {
+  Lisp_Object *chp_data;
+  GdkClipboard *clipboard;
+} zz_clipboard_cb_data;
+
+static void
+zz_clipboard_ready (GObject *source_object,
+		    GAsyncResult *res,
+		    gpointer user_data)
+{
+  Lisp_Object clipboard;
+  GError *e = NULL;
+  char *stream = gdk_clipboard_read_text_finish
+    (((zz_clipboard_cb_data *) user_data)->clipboard, res, &e);
+  if (e || !stream)
+    {
+      clipboard = build_pure_c_string ("ERR FETCHING CLIPBOARD");
+      g_error_free (e);
+    }
+  else
+    {
+      size_t idx;
+      size_t idz = 0;
+      for (idx = 0; idz < strlen (stream);)
+	{
+	  if (stream[idz] != '\xd')
+	    {
+	      stream[idx] = stream[idz];
+	      ++idx;
+	      ++idz;
+	    }
+	  else
+	    {
+	      ++idz;
+	    }
+	}
+      stream[idx] = 0;
+      clipboard = build_string (stream);
+    }
+  *((zz_clipboard_cb_data *) user_data)->chp_data = clipboard;
+}
+#endif
+
+DEFUN ("pgtk-get-selection-internal", Fpgtk_get_selection_internal,
+       Spgtk_get_selection_internal, 2, 4, 0,
+       doc: /* Return text selected from some X window.
+SELECTION-SYMBOL is typically `PRIMARY', `SECONDARY', or `CLIPBOARD'.
+\(Those are literal upper-case symbol names, since that's what X expects.)
+TARGET-TYPE is the type of data desired, typically `STRING'.
+
+TIME-STAMP is the time to use in the XConvertSelection call for foreign
+selections.  If omitted, defaults to the time for the last event.
+
+TERMINAL should be a terminal object or a frame specifying the X
+server to query.  If omitted or nil, that stands for the selected
+frame's display, or the first available X display.
+
+On Nextstep, TIME-STAMP and TERMINAL are unused.
+On PGTK, TIME-STAMP is unused.  */)
+(Lisp_Object selection_symbol, Lisp_Object target_type, Lisp_Object time_stamp,
+ Lisp_Object terminal)
+{
+#ifndef HAVE_GTK4
+  struct frame *f = frame_for_pgtk_selection (terminal);
+  GtkClipboard *cb;
+
+  CHECK_SYMBOL (selection_symbol);
+  CHECK_SYMBOL (target_type);
+  if (EQ (target_type, QMULTIPLE))
+    error ("Retrieving MULTIPLE selections is currently unimplemented");
+  if (!f)
+    error ("PGTK selection unavailable for this frame");
+
+  if (!pgtk_selection_usable ())
+    return Qnil;
+
+  cb = symbol_to_gtk_clipboard (FRAME_GTK_WIDGET (f), selection_symbol);
+
+  gchar *s = gtk_clipboard_wait_for_text (cb);
+  if (s == NULL)
+    return Qnil;
+  int size = strlen (s);
+  Lisp_Object str = make_unibyte_string (s, size);
+  Fput_text_property (make_fixnum (0), make_fixnum (size), Qforeign_selection,
+                      QUTF8_STRING, str);
+  return str;
+#else
+  if (EQ (selection_symbol, QCLIPBOARD))
+    {
+      GdkClipboard *clipboard = gdk_display_get_clipboard
+	(FRAME_X_DISPLAY (frame_for_pgtk_selection (terminal)));
+      zz_clipboard_cb_data data;
+      Lisp_Object object = NULL;
+      data.clipboard = clipboard;
+      data.chp_data = &object;
+
+      gdk_clipboard_read_text_async (clipboard, NULL,
+				     zz_clipboard_ready, &data);
+
+      while (!object)
+	g_main_context_iteration (NULL, false);
+      return object;
+    }
+  else if (EQ (selection_symbol, QPRIMARY))
+    {
+      return v_primary_selection;
+    }
+  else
+    {
+      if (NILP (selection_tbl))
+	return Qnil;
+      return Fgethash (selection_symbol, selection_tbl, Qnil);
+    }
+  return Qnil;
+#endif
+}
+
+void
+nxatoms_of_pgtkselect (void)
+{
+  PGTK_TRACE ("nxatoms_of_pgtkselect");
+}
+
+void
+syms_of_pgtkselect (void)
+{
+  PGTK_TRACE ("syms_of_pgtkselect");
+
+  DEFSYM (QCLIPBOARD, "CLIPBOARD");
+  DEFSYM (QSECONDARY, "SECONDARY");
+  DEFSYM (QPRIMARY, "PRIMARY")
+  DEFSYM (QTEXT, "TEXT");
+  DEFSYM (QFILE_NAME, "FILE_NAME");
+  DEFSYM (QMULTIPLE, "MULTIPLE");
+
+  DEFSYM (Qforeign_selection, "foreign-selection");
+  DEFSYM (QUTF8_STRING, "UTF8_STRING");
+
+  defsubr (&Spgtk_disown_selection_internal);
+  defsubr (&Spgtk_get_selection_internal);
+  defsubr (&Spgtk_own_selection_internal);
+  defsubr (&Spgtk_selection_exists_p);
+  defsubr (&Spgtk_selection_owner_p);
+
+#if 0
+  Vselection_alist = Qnil;
+  staticpro (&Vselection_alist);
+#endif
+
+  DEFVAR_LISP ("pgtk-sent-selection-hooks", Vpgtk_sent_selection_hooks,
+    "A list of functions to be called when Emacs answers a selection request.\n\
+The functions are called with four arguments:\n\
+  - the selection name (typically `PRIMARY', `SECONDARY', or `CLIPBOARD');\n\
+  - the selection-type which Emacs was asked to convert the\n\
+    selection into before sending (for example, `STRING' or `LENGTH');\n\
+  - a flag indicating success or failure for responding to the request.\n\
+We might have failed (and declined the request) for any number of reasons,\n\
+including being asked for a selection that we no longer own, or being asked\n\
+to convert into a type that we don't know about or that is inappropriate.\n\
+This hook doesn't let you change the behavior of Emacs's selection replies,\n\
+it merely informs you that they have happened.");
+  Vpgtk_sent_selection_hooks = Qnil;
+}
diff --git a/src/pgtkselect.h b/src/pgtkselect.h
new file mode 100644
index 0000000000..d1640a00d5
--- /dev/null
+++ b/src/pgtkselect.h
@@ -0,0 +1,38 @@
+/* Definitions and headers for selection of pure Gtk+3.
+   Copyright (C) 1989, 1993, 2005, 2008-2018 Free Software Foundation,
+   Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+
+#include "config.h"
+#include "dispextern.h"
+#include "frame.h"
+#include "pgtkterm.h"
+
+#ifdef HAVE_PGTK
+
+#include <gtk/gtk.h>
+
+extern void pgtk_selection_init(void);
+#ifndef HAVE_GTK4
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+gboolean pgtk_selection_lost(GtkWidget *widget, GdkEventSelection *event, struct frame *f);
+#else
+void pgtk_selection_lost(GtkWidget *widget, GdkEventSelection *event, gpointer user_data);
+#endif
+#endif
+#endif	/* HAVE_PGTK */
diff --git a/src/pgtksubr.c b/src/pgtksubr.c
new file mode 100644
index 0000000000..a4bdd56458
--- /dev/null
+++ b/src/pgtksubr.c
@@ -0,0 +1,3776 @@
+/* Utilities for GTK 4(.0) or later
+
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#define __GI_SCANNER__
+#include "config.h"
+
+#ifdef HAVE_GTK4
+#include "lisp.h"
+#include "atimer.h"
+#include "blockinput.h"
+#include "coding.h"
+#include "gtkinter.h"
+#include "keyboard.h"
+#include "pgtksubr.h"
+#include "buffer.h"
+#include "float.h"
+#include "dynlib.h"
+
+#include <stdint.h>
+
+#include <limits.h>
+#include <graphene.h>
+
+#include <c-ctype.h>
+#include <cairo/cairo.h>
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include <atk/atk.h>
+
+#ifdef HAVE_GDK_X11
+#include <gdk/x11/gdkx.h>
+#include <X11/X.h>
+#endif
+
+#define EGTK_TEXT_CANCEL "Cancel"
+#define EGTK_TEXT_OK "OK"
+#define EGTK_TEXT_OPEN "Open"
+
+#define ID_TO_WIDGET_INCR 32
+#define TB_INFO_KEY "xg_frame_tb_info"
+
+#define GTK_FOREGROUND_COLOR "gtk-style-foreground"
+#define GTK_BACKGROUND_COLOR "gtk-style-background"
+
+#define GTK_MFOREGROUND_COLOR "gtk-style-modeline-foreground"
+#define GTK_MBACKGROUND_COLOR "gtk-style-modeline-background"
+
+#define GTK_LFOREGROUND_COLOR "gtk-style-link-foreground"
+#define GTK_SLFOREGROUND_COLOR "gtk-style-slink-foreground"
+
+#define GTK_TBACKGROUND_COLOR "gtk-style-tb-background"
+
+#if ! (CAIRO_DEBUG_ARGB_THEMING)
+#define CAIRO_HAVE_RGBA128F CAIRO_VERSION_MAJOR > 1 \
+  || (CAIRO_VERSION_MINOR >= 17 && CAIRO_VERSION_MICRO >= 2)
+#else
+#define CAIRO_HAVE_RGBA128F 0
+#endif
+#define ZTYPE_MAXIMUM(t) \
+  ((t) ((t) 0 < (t) -1   \
+          ? (t) -1       \
+          : ((((t) 1 << (sizeof (t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
+
+#define EG_WEIGHT_TO_SYMBOL(w)                                   \
+  (w <= PANGO_WEIGHT_THIN                                        \
+     ? Qextra_light                                              \
+     : w <= PANGO_WEIGHT_ULTRALIGHT                              \
+         ? Qlight                                                \
+         : w <= PANGO_WEIGHT_LIGHT                               \
+             ? Qsemi_light                                       \
+             : w < PANGO_WEIGHT_MEDIUM                           \
+                 ? Qnormal                                       \
+                 : w <= PANGO_WEIGHT_SEMIBOLD                    \
+                     ? Qsemi_bold                                \
+                     : w <= PANGO_WEIGHT_BOLD                    \
+                         ? Qbold                                 \
+                         : w <= PANGO_WEIGHT_HEAVY ? Qextra_bold \
+                                                   : Qultra_bold)
+
+#define EG_STYLE_TO_SYMBOL(s)          \
+  (s == PANGO_STYLE_OBLIQUE ? Qoblique \
+                            : s == PANGO_STYLE_ITALIC ? Qitalic : Qnormal)
+
+#ifdef __GNUC__
+#define TODO                                                                 \
+  do                                                                         \
+    {                                                                        \
+      printf ("%s:%d: not implemented: %s\n", __FILE__, __LINE__, __func__); \
+    }                                                                        \
+  while (0)
+#else
+#define TODO
+#endif
+
+struct popover_cb_data
+{
+  widget_value *root_wv;
+  GtkWidget **popover;
+  GCallback select_cb;
+  GCallback highlight_cb;
+  GCallback deactivate_cb;
+  GdkRectangle *pos_rect;
+  egtk_menu_cb_data *cb_data;
+  struct frame *frame;
+};
+
+struct egtk_frame_tb_info
+{
+  Lisp_Object last_tool_bar;
+  Lisp_Object style;
+  int n_last_items;
+  int hmargin, vmargin;
+  GtkTextDirection dir;
+};
+
+static char *x_last_font_name;
+static GdkDisplay *default_display;
+static struct
+{
+  GtkWidget **widgets;
+  ptrdiff_t max_size;
+  ptrdiff_t used;
+} id_to_widget;
+
+/* Linked list of all allocated struct xg_menu_cb_data.  Used for marking
+   during GC.  The next member points to the items.  */
+static egtk_list_node egtk_menu_cb_list;
+
+/* Linked list of all allocated struct xg_menu_item_cb_data.  Used for marking
+   during GC.  The next member points to the items.  */
+static egtk_list_node egtk_menu_item_cb_list;
+
+static int scroll_bar_width_for_theme;
+static int scroll_bar_height_for_theme;
+
+typedef struct _EmacsGtkAccessible
+{
+  GtkAccessible parent_instance;
+  struct frame *frame;
+} EmacsGtkAccessible;
+
+typedef struct _EmacsGtkAccessibleClass
+{
+  GtkAccessibleClass parent_class;
+} EmacsGtkAccessibleClass;
+
+typedef struct _EmacsWindow
+{
+  GtkApplicationWindow parent;
+} EmacsWindow;
+
+typedef struct _EmacsWindowClass
+{
+  GtkApplicationWindowClass parent_class;
+} EmacsWindowClass;
+
+typedef struct _LwMenuItem
+{
+  GtkWidget parent;
+  GtkLabel *label;
+  GtkPopover *popover;
+  widget_value *wv;
+  GtkEventControllerMotion *motion_controller;
+  GtkWidget *menu_bar;
+} LwMenuItem;
+
+typedef struct _LwMenuBar
+{
+  GtkWidget parent;
+  widget_value **wvs;
+  ptrdiff_t nused;
+  LwMenuItem *selected_item;
+  egtk_menu_cb_data *cl_data;
+  struct frame *frame;
+  GCallback select_cb,
+    highlight_cb, deactivate_cb;
+} LwMenuBar;
+
+typedef struct _XegTtipPopover
+{
+  GtkPopover parent;
+} XegTtipPopover;
+
+typedef struct _XegTtipPopoverClass
+{
+  GtkPopoverClass parent_class;
+} XegTtipPopoverClass;
+
+typedef struct _XegBox
+{
+  GtkBox parent;
+} XegBox;
+
+typedef struct _XegBoxClass
+{
+  GtkBoxClass parent_class;
+} XegBoxClass;
+
+typedef struct _LwMenuBarClass
+{
+  GtkWidgetClass parent_class;
+} LwMenuBarClass;
+
+typedef struct _LwMenuItemClass
+{
+  GtkWidgetClass parent;
+} LwMenuItemClass;
+
+typedef struct _XegChildFrameBin
+{
+  GtkBin parent;
+} XegChildFrameBin;
+
+typedef struct _XegChildFrameBinClass
+{
+  GtkBinClass parent_class;
+} XegChildFrameBinClass;
+
+static GHashTable *atab;
+
+typedef struct _EmacsResizableDrawingAreaClass EmacsResizableDrawingAreaClass;
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#define G_STATIC_ASSERT(f) // FIXME kludge to fix GObject bug
+
+static void emacs_resizable_drawing_area_atk_text_init (AtkTextIface *iface);
+static void emacs_resizable_drawing_area_action_map_init (GActionMapInterface *iface);
+static void emacs_resizable_drawing_area_action (GtkWidget  *widget,
+						 const char *action_name,
+						 GVariant   *parameter);
+static void boolean_action_activate (GSimpleAction *simple, GVariant *parameter,
+				     gpointer user_data);
+
+static LwMenuBar *
+lw_menu_bar_new (void);
+static LwMenuItem *
+lw_menu_item_new (widget_value *wv);
+
+G_DEFINE_TYPE_WITH_CODE (EmacsGtkAccessible,
+			 emacs_gtk_accessible,
+			 GTK_TYPE_ACCESSIBLE,
+			 G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
+						emacs_resizable_drawing_area_atk_text_init))
+G_DEFINE_TYPE (LwMenuBar,
+	       lw_menu_bar,
+	       GTK_TYPE_WIDGET)
+
+G_DEFINE_TYPE (LwMenuItem,
+	       lw_menu_item,
+	       GTK_TYPE_WIDGET)
+
+G_DEFINE_TYPE (XegTtipPopover,
+	       xeg_ttip_popover,
+	       GTK_TYPE_POPOVER)
+
+G_DEFINE_TYPE (XegBox,
+	       xeg_box,
+	       GTK_TYPE_BOX)
+
+G_DEFINE_TYPE (XegChildFrameBin,
+	       xeg_child_frame_bin,
+	       GTK_TYPE_BIN);
+
+static void
+lw_menu_item_popover_unmap (GtkPopover *popover, LwMenuBar *mbar);
+
+static GtkWidget *
+xeg_ttip_popover_new (void)
+{
+  return g_object_new (xeg_ttip_popover_get_type (), NULL);
+}
+
+static void
+xeg_ttip_popover_init (XegTtipPopover *po)
+{
+}
+
+GtkWidget *
+xeg_child_frame_bin_new (void)
+{
+  return g_object_new (xeg_child_frame_bin_get_type (), NULL);
+}
+
+static void
+xeg_child_frame_bin_init (XegChildFrameBin *bin)
+{
+}
+
+static void
+xeg_child_frame_bin_snapshot (GtkWidget *w, GdkSnapshot *snap)
+{
+  gtk_snapshot_render_background
+    (snap, gtk_widget_get_style_context (GTK_WIDGET (gtk_widget_get_root (w))),
+     0, 0, gtk_widget_get_width (w), gtk_widget_get_height (w));
+  GTK_WIDGET_CLASS (xeg_child_frame_bin_parent_class)->snapshot (w, snap);
+}
+
+static void
+xeg_child_frame_bin_class_init (XegChildFrameBinClass *clazz)
+{
+  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (clazz), "window");
+  gtk_widget_class_set_layout_manager_type (GTK_WIDGET_CLASS (clazz),
+                                            GTK_TYPE_BIN_LAYOUT);
+  GTK_WIDGET_CLASS (clazz)->snapshot = xeg_child_frame_bin_snapshot;
+}
+
+static GtkWidget *
+xeg_box_new (void)
+{
+  return g_object_new (xeg_box_get_type (), "orientation",
+		       GTK_ORIENTATION_HORIZONTAL,
+		       "spacing", 2, NULL);
+}
+
+static void
+xeg_box_measure (GtkWidget *widget,
+		 GtkOrientation orientation,
+		 int for_size,
+		 int *minimum, int *natural,
+		 int *minimum_baseline,
+		 int *natural_baseline)
+{
+  GtkWidgetClass *wc = GTK_WIDGET_CLASS (xeg_box_parent_class);
+  wc->measure (widget, orientation,
+	       for_size, minimum,
+	       natural, minimum_baseline,
+	       natural_baseline);
+  *minimum = 0;
+  *minimum_baseline = 0;
+}
+
+static void
+xeg_box_init (XegBox *box)
+{
+}
+
+static void
+xeg_box_class_init (XegBoxClass *box_class)
+{
+  GtkWidgetClass *wc = GTK_WIDGET_CLASS (box_class);
+  wc->measure = xeg_box_measure;
+}
+
+static void
+xeg_ttip_popover_class_init (XegTtipPopoverClass *clazz)
+{
+  GtkWidgetClass *wc = GTK_WIDGET_CLASS (clazz);
+  gtk_widget_class_set_css_name (wc, "tooltip");
+  wc->focus = NULL;
+  wc->grab_focus = NULL;
+  wc->move_focus = NULL;
+  wc->activate_signal = 0;
+  GTK_POPOVER_CLASS (clazz)->activate_default = NULL;
+  gtk_widget_class_set_accessible_role (wc, ATK_ROLE_TOOL_TIP);
+}
+
+static GtkPopover *
+build_menu_from_widget_value (widget_value *wv, egtk_menu_cb_data *cl_data,
+                              GCallback select_cb, GCallback deactivate_cb,
+                              GCallback highlight_cb, struct frame *f, bool mb,
+			      bool fc);
+
+static void
+lw_menu_item_popup (LwMenuItem *item)
+{
+  LwMenuBar *mb = (gpointer) item->menu_bar;
+  item->popover
+    = build_menu_from_widget_value (item->wv, mb->cl_data,
+				    mb->select_cb, mb->deactivate_cb,
+				    mb->highlight_cb, mb->frame, true, true);
+  g_signal_connect (G_OBJECT (item->popover), "unmap",
+		    G_CALLBACK (lw_menu_item_popover_unmap), mb);
+  GtkAllocation alloc;
+  gtk_widget_get_allocation (GTK_WIDGET (item), &alloc);
+  gtk_widget_set_parent (GTK_WIDGET (item->popover),
+			 GTK_WIDGET (item));
+  gtk_popover_set_has_arrow (item->popover, false);
+  gtk_popover_set_position (item->popover, GTK_POS_BOTTOM);
+  gtk_widget_set_halign (GTK_WIDGET (item->popover), GTK_ALIGN_START);
+  gtk_popover_popup (item->popover);
+}
+
+static ptrdiff_t
+lw_menu_bar_extend_used (LwMenuBar *mbar)
+{
+  block_input ();
+  ptrdiff_t oldsize = mbar->nused * (sizeof (widget_value *));
+  ++mbar->nused;
+  ptrdiff_t size = mbar->nused * (sizeof (widget_value *));
+  widget_value **old = mbar->wvs;
+  mbar->wvs = xmalloc (size);
+  memcpy (mbar->wvs, old, oldsize);
+  xfree (old);
+  unblock_input ();
+  return mbar->nused;
+}
+
+static widget_value *
+lw_menu_bar_append_widget_value (LwMenuBar *bar, widget_value *wv)
+{
+  ptrdiff_t idx = lw_menu_bar_extend_used (bar);
+  LwMenuItem *item = lw_menu_item_new (wv);
+  item->menu_bar = GTK_WIDGET (bar);
+  gtk_widget_set_parent (GTK_WIDGET (item), GTK_WIDGET (bar));
+  bar->wvs[idx - 1] = item->wv;
+  return item->wv;
+}
+
+static void
+lw_menu_bar_remove_item_wv (LwMenuBar *bar, widget_value *wv)
+{
+  ptrdiff_t ids = 0;
+  for (ptrdiff_t q = 0; q < bar->nused; ++q)
+    if (bar->wvs[q] != wv)
+      ++ids;
+  bar->nused = ids;
+  ptrdiff_t counter = 0;
+  for (ptrdiff_t q = 0; q < ids; ++q)
+    {
+      while (bar->wvs[counter] == wv)
+	++counter;
+      bar->wvs[q] = bar->wvs[counter];
+      ++counter;
+    }
+  widget_value **old = bar->wvs;
+  bar->wvs = xmalloc (ids * (sizeof *bar->wvs));
+  memcpy (bar->wvs, old, ids * (sizeof *bar->wvs));
+  xfree (old);
+  GSList *widgets_to_remove = NULL;
+  for (LwMenuItem *item
+       = (LwMenuItem *) gtk_widget_get_first_child (GTK_WIDGET (bar));
+       item; item =
+	 (LwMenuItem *) gtk_widget_get_next_sibling (GTK_WIDGET (item)))
+    widgets_to_remove = g_slist_append (widgets_to_remove, item);
+  for (GSList *l = widgets_to_remove; l; l = l->next)
+    gtk_widget_destroy (l->data);
+  g_slist_free (widgets_to_remove);
+}
+
+static void
+lw_menu_item_popdown (LwMenuItem *item)
+{
+  if (item->popover)
+    gtk_popover_popdown (item->popover);
+}
+
+static void
+lw_menu_bar_size_allocate (GtkWidget *widget, gint width,
+			   gint height, gint baseline)
+{
+  LwMenuBar *bar = (gpointer) widget;
+  FRAME_X_OUTPUT (bar->frame)->menubar_height = height;
+}
+
+static void
+lw_menu_item_size_allocate (GtkWidget *widget, gint foo,
+			    gint bar, gint baz)
+{
+  for (GtkWidget *c = gtk_widget_get_first_child (widget); c;
+       c = gtk_widget_get_next_sibling (c))
+    {
+      if (GTK_IS_NATIVE (c) && gtk_widget_is_visible (c))
+        gtk_native_check_resize (GTK_NATIVE (c));
+    }
+}
+
+static void
+lw_menu_bar_select_item (LwMenuBar *bar,
+			 LwMenuItem *item)
+{
+  bool_bf was_mapped;
+  bool_bf changed = item != bar->selected_item;
+  if (bar->selected_item)
+    was_mapped = gtk_widget_get_mapped
+      (GTK_WIDGET (bar->selected_item->popover));
+  else
+    was_mapped = false;
+  if (!changed)
+    return;
+  if (was_mapped)
+    lw_menu_item_popdown (bar->selected_item);
+  if (bar->selected_item)
+    gtk_widget_unset_state_flags (GTK_WIDGET (bar->selected_item),
+				  GTK_STATE_FLAG_SELECTED);
+  bar->selected_item = item;
+  if (bar->selected_item)
+    gtk_widget_set_state_flags (GTK_WIDGET (bar->selected_item),
+				GTK_STATE_FLAG_SELECTED, FALSE);
+  if (bar->selected_item)
+    lw_menu_item_popup (bar->selected_item);
+
+  if (!item)
+    FRAME_X_OUTPUT (bar->frame)->menubar_or_popup_count -= 1;
+  else
+    FRAME_X_OUTPUT (bar->frame)->menubar_or_popup_count += 1;
+}
+
+static void
+lw_menu_item_popover_unmap (GtkPopover *popover,
+			    LwMenuBar *mbar)
+{
+  if (mbar->selected_item && mbar->selected_item->popover == popover)
+    lw_menu_bar_select_item (mbar, NULL);
+}
+
+static void
+lw_menu_item_click_gesture_pressed (GtkGestureClick *gesture,
+				    gint             n_press,
+				    gdouble          x,
+				    gdouble          y,
+				    gpointer         user_data)
+{
+  LwMenuItem *item = user_data;
+  if (item->wv->contents)
+    lw_menu_bar_select_item ((LwMenuBar *) item->menu_bar, item);
+  else
+    {
+      LwMenuBar *bar = (LwMenuBar *) item->menu_bar;
+      egtk_menu_item_cb_data *cb_data = xmalloc (sizeof *cb_data);
+#define I item->wv
+      cb_data->select_id = 0;
+      cb_data->help = I->help;
+      cb_data->cl_data = bar->cl_data;
+      cb_data->call_data = I->call_data;
+      ((void (*) (GtkWidget *, gpointer)) bar->select_cb)
+	(GTK_WIDGET (item), cb_data);
+#undef I
+      xfree (cb_data);
+    }
+}
+
+static void
+item_enter_cb (GtkEventController   *controller,
+               double                x,
+               double                y,
+               GdkCrossingMode       mode,
+               gpointer              data)
+{
+  GtkWidget *target;
+  LwMenuBar *bar;
+
+  target = gtk_event_controller_get_widget (controller);
+  bar = (LwMenuBar *) (gtk_widget_get_ancestor (target, lw_menu_bar_get_type ()));
+
+  if (!bar->selected_item)
+    gtk_widget_set_state_flags (target, GTK_STATE_FLAG_SELECTED, FALSE);
+  else
+    lw_menu_bar_select_item (bar, (LwMenuItem *) target);
+}
+
+static void
+item_leave (GtkEventController   *controller,
+	    GdkCrossingMode       mode,
+	    gpointer              data)
+{
+  GtkWidget *target;
+  LwMenuBar *bar;
+
+  target = gtk_event_controller_get_widget (controller);
+  bar = (LwMenuBar *) (gtk_widget_get_ancestor (target, lw_menu_bar_get_type ()));
+
+  if (target != (gpointer) bar->selected_item)
+    gtk_widget_unset_state_flags (target, GTK_STATE_FLAG_SELECTED);
+}
+
+
+
+static void
+lw_menu_item_init (LwMenuItem *item)
+{
+  item->motion_controller
+    = GTK_EVENT_CONTROLLER_MOTION (gtk_event_controller_motion_new ());
+  gtk_widget_add_controller (GTK_WIDGET (item),
+			     GTK_EVENT_CONTROLLER (item->motion_controller));
+  item->label = g_object_new (GTK_TYPE_LABEL,
+                              "use-underline", TRUE,
+                              NULL);
+  gtk_widget_set_parent (GTK_WIDGET (item->label), GTK_WIDGET (item));
+  gtk_event_controller_set_propagation_limit (GTK_EVENT_CONTROLLER (item->motion_controller),
+					      GTK_LIMIT_NONE);
+  g_signal_connect (GTK_EVENT_CONTROLLER (item->motion_controller),
+		    "enter", G_CALLBACK (item_enter_cb), NULL);
+  g_signal_connect (GTK_EVENT_CONTROLLER (item->motion_controller), "leave",
+		    G_CALLBACK (item_leave), NULL);
+  item->popover = NULL;
+  GtkGesture *gesture = gtk_gesture_click_new ();
+  g_signal_connect (G_OBJECT (gesture), "pressed",
+		    G_CALLBACK (lw_menu_item_click_gesture_pressed), item);
+  gtk_widget_add_controller (GTK_WIDGET (item), GTK_EVENT_CONTROLLER (gesture));
+}
+
+static void
+lw_menu_item_dispose (GObject *o)
+{
+  LwMenuItem *it = (LwMenuItem *) o;
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+  g_clear_pointer ((GtkWidget **) (gpointer) &it->label,
+		   gtk_widget_unparent);
+  g_clear_pointer ((GtkWidget **) (gpointer) &it->popover,
+		   gtk_widget_unparent);
+  if (((LwMenuBar *) it->menu_bar)->selected_item == it)
+    ((LwMenuBar *) it->menu_bar)->selected_item = NULL;
+  if (it->popover)
+    gtk_widget_unparent (GTK_WIDGET (it->popover));
+  GSList *widgets_to_remove = NULL;
+  for (GtkWidget *item = gtk_widget_get_first_child (GTK_WIDGET (it)); item;
+       item = gtk_widget_get_next_sibling (GTK_WIDGET (item)))
+    widgets_to_remove = g_slist_append (widgets_to_remove, item);
+  for (GSList *l = widgets_to_remove; l; l = l->next)
+    gtk_widget_destroy (l->data);
+  g_slist_free (widgets_to_remove);
+#pragma GCC diagnostic push
+  G_OBJECT_CLASS (lw_menu_item_parent_class)->dispose (o);
+}
+
+static void
+lw_menu_item_class_init (LwMenuItemClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *class = G_OBJECT_CLASS (klass);
+  gtk_widget_class_set_css_name (widget_class, "item");
+  class->dispose = lw_menu_item_dispose;
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
+  widget_class->size_allocate = lw_menu_item_size_allocate;
+}
+
+static LwMenuItem *
+lw_menu_item_new (widget_value *wv)
+{
+  block_input ();
+  LwMenuItem *item = g_object_new (lw_menu_item_get_type (), NULL);
+  item->wv = wv;
+  gtk_label_set_text (item->label, item->wv->name);
+  item->popover = NULL;
+  if (!NILP (item->wv->help))
+    gtk_widget_set_tooltip_text (GTK_WIDGET (item),
+                                 SSDATA (ENCODE_UTF_8 (item->wv->help)));
+  unblock_input ();
+  return item;
+}
+
+static void
+lw_menu_bar_dispose (GObject *o)
+{
+  LwMenuBar *bar = (LwMenuBar *) o;
+  GSList *widgets_to_remove = NULL;
+  for (LwMenuItem *item
+       = (LwMenuItem *) gtk_widget_get_first_child (GTK_WIDGET (bar));
+       item; item =
+	 (LwMenuItem *) gtk_widget_get_next_sibling (GTK_WIDGET (item)))
+    widgets_to_remove = g_slist_append (widgets_to_remove, item);
+  for (GSList *l = widgets_to_remove; l; l = l->next)
+    gtk_widget_destroy (l->data);
+  g_slist_free (widgets_to_remove);
+}
+
+static void
+lw_menu_bar_finalize (GObject *o)
+{
+  LwMenuBar *bar = (LwMenuBar *) o;
+  xfree (bar->wvs);
+  bar->nused = 0;
+  G_OBJECT_CLASS (lw_menu_bar_parent_class)->finalize (o);
+}
+
+static void
+lw_menu_bar_init (LwMenuBar *bar)
+{
+  bar->nused = 0;
+  bar->selected_item = NULL;
+  bar->wvs = xmalloc (sizeof *bar->wvs);
+}
+
+static LwMenuBar *
+lw_menu_bar_new (void)
+{
+  return g_object_new (lw_menu_bar_get_type (), NULL);
+}
+
+static void
+lw_menu_bar_class_init (LwMenuBarClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = lw_menu_bar_finalize;
+  object_class->dispose = lw_menu_bar_dispose;
+
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
+  gtk_widget_class_set_css_name (widget_class, "menubar");
+  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_MENU_BAR);
+  widget_class->size_allocate = lw_menu_bar_size_allocate;
+}
+
+static void
+lw_menu_bar_set_root_wv (LwMenuBar *mbar, widget_value *wv)
+{
+  for (ptrdiff_t z = 0; z < mbar->nused; ++z)
+    lw_menu_bar_remove_item_wv (mbar, mbar->wvs[z]);
+  for (widget_value *i = wv->contents; i; i = i->next)
+    lw_menu_bar_append_widget_value (mbar, i);
+}
+
+static GtkWidget *
+emacs_window_new (void);
+
+G_DEFINE_TYPE (EmacsWindow,
+	       emacs_window,
+	       GTK_TYPE_APPLICATION_WINDOW);
+
+static void
+emacs_window_init (EmacsWindow *w)
+{
+  static GActionEntry we[] = {{"menu.bef_enabled", boolean_action_activate, "b",
+                               "true", boolean_action_activate},
+                              {"mv.bef_disabled", boolean_action_activate,
+                               "b", "false", boolean_action_activate}};
+
+  g_action_map_add_action_entries (G_ACTION_MAP (w), we, 2, w);
+}
+
+static void
+emacs_window_class_init (EmacsWindowClass *klass)
+{
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (klass), "menu.select",
+                                   (gpointer) G_VARIANT_TYPE_ANY,
+                                   emacs_resizable_drawing_area_action);
+}
+
+static GtkWidget *
+emacs_window_new (void)
+{
+  EmacsWindow *window = g_object_new (emacs_window_get_type (), NULL);
+  return GTK_WIDGET (window);
+}
+
+static EmacsGtkAccessible *
+emacs_gtk_accessible_new (EmacsResizableDrawingArea *area)
+{
+  EmacsGtkAccessible *accessible =
+    (EmacsGtkAccessible *) g_object_new
+    (emacs_gtk_accessible_get_type (), NULL);
+  accessible->frame = area->f;
+  gtk_accessible_set_widget (GTK_ACCESSIBLE (accessible),
+			     GTK_WIDGET (area));
+  return accessible;
+}
+
+static void
+zz_add_action (GActionMap *map, GAction *action)
+{
+  EmacsResizableDrawingArea *area = (gpointer) map;
+  g_hash_table_insert (area->action_map,
+		       (gpointer) g_action_get_name (action),
+		       (gpointer) action);
+  g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (area->action_group), action);
+
+}
+
+static void
+zz_remove_action (GActionMap *map, const char *action_name)
+{
+  EmacsResizableDrawingArea *area = (gpointer) map;
+  g_hash_table_remove (area->action_map, action_name);
+  g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (area->action_group),
+				action_name);
+}
+
+static GAction *
+zz_lookup_action (GActionMap *map, const char *action_name)
+{
+  EmacsResizableDrawingArea *area = (gpointer) map;
+  return g_hash_table_lookup (area->action_map, action_name);
+}
+
+static void
+emacs_resizable_drawing_area_action_map_init (GActionMapInterface *iface)
+{
+  iface->add_action = zz_add_action;
+  iface->remove_action = zz_remove_action;
+  iface->lookup_action = zz_lookup_action;
+}
+
+static char *
+get_utf8_string (const char *str);
+static GtkWindow *
+locate_nonchild_window (struct frame *child);
+static void
+zz_action_update (EmacsResizableDrawingArea *a);
+
+static GMenuModel *
+zz_build_menu_model (EmacsResizableDrawingArea *area)
+{
+  GMenu *menu, *section;
+  GMenuItem *item;
+
+  menu = g_menu_new ();
+
+  section = g_menu_new ();
+  item = g_menu_item_new ("Kill", "clipboard.cut");
+  g_menu_item_set_attribute (item, "verb-icon", "s", "edit-cut-symbolic");
+  g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+  item = g_menu_item_new ("Save", "clipboard.copy");
+  g_menu_item_set_attribute (item, "verb-icon", "s", "edit-copy-symbolic");
+  g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+  item = g_menu_item_new ("Yank", "clipboard.paste");
+  g_menu_item_set_attribute (item, "verb-icon", "s", "edit-paste-symbolic");
+  g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+  item = g_menu_item_new ("Delete", "selection.delete");
+  g_menu_item_set_attribute (item, "verb-icon", "s", "edit-delete-symbolic");
+  g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+  item = g_menu_item_new ("Select all", "selection.select-all");
+  g_menu_item_set_attribute (item, "verb-icon", "s", "edit-select-all-symbolic");
+  g_menu_item_set_attribute (item, "display-hint", "s", "horizontal-buttons");
+  g_menu_append_item (section, item);
+  GMenuItem *sec = g_menu_item_new_section (NULL, G_MENU_MODEL (section));
+  g_menu_item_set_attribute (sec, "display-hint", "s", "horizontal-buttons");
+  g_menu_append_item (menu, sec);
+  g_object_unref (section);
+  g_object_unref (sec);
+  g_object_unref (item);
+
+  return G_MENU_MODEL (menu);
+}
+
+static void
+l_change_cb (struct atimer *atimer)
+{
+  if (!NILP (Ffeaturep (intern_c_string ("custom"), Qnil)))
+    if (!NILP (call1 (Qcustom_theme_enabled_p, Qgtk)))
+      call1 (Qload_theme, Qgtk);
+}
+
+static void
+style_change_cb (GtkStyleProvider *provider,
+		 GParamSpec       *spec,
+		 struct frame     *data)
+{
+  start_atimer (ATIMER_RELATIVE, make_timespec (0, 100), l_change_cb, 0);
+}
+
+static GVariant *
+variant_from_item_cb_data (struct popover_cb_data *data,
+			   GSList **ptab)
+{
+  GVariant *variant = g_variant_new_boolean (true);
+  *ptab = g_slist_append (*ptab, data);
+  g_hash_table_insert (atab, variant, data);
+  return variant;
+}
+
+static struct popover_cb_data *
+cbd_from_item_variant (GVariant *variant)
+{
+  return g_hash_table_lookup (atab, variant);
+}
+
+static void
+pop_closed (GtkPopover *popover,
+	    gpointer    user_data)
+{
+  gtk_window_set_focus (locate_nonchild_window
+			(((EmacsResizableDrawingArea *) gtk_widget_get_parent (user_data))->f),
+			FRAME_GTK_WIDGET
+			(((EmacsResizableDrawingArea *) gtk_widget_get_parent (user_data))->f));
+}
+
+void
+frame_activate_menu_bar (struct frame *f)
+{
+  block_input ();
+  eassert (FRAME_MENU_BAR (f));
+  LwMenuItem *item
+    = (LwMenuItem *) gtk_widget_get_first_child (GTK_WIDGET (FRAME_MENU_BAR (f)));
+  if (item)
+    lw_menu_bar_select_item ((LwMenuBar *) FRAME_MENU_BAR (f), item);
+  unblock_input ();
+}
+
+void
+emacs_resizable_drawing_area_show_selection_options (
+  EmacsResizableDrawingArea *area)
+{
+  int x = XWINDOW (FRAME_SELECTED_WINDOW (area->f))->cursor.x;
+  x += XWINDOW (FRAME_SELECTED_WINDOW (area->f))->pixel_left;
+  int y = XWINDOW (FRAME_SELECTED_WINDOW (area->f))->cursor.y;
+  y += XWINDOW (FRAME_SELECTED_WINDOW (area->f))->pixel_top
+       + FRAME_LINE_HEIGHT (area->f);
+  zz_action_update (area);
+  GMenuModel *mm = zz_build_menu_model (area);
+  GtkPopover *popover = GTK_POPOVER (
+    gtk_popover_menu_new_from_model_full (mm, GTK_POPOVER_MENU_NESTED));
+  if (area->nrc_popover)
+    gtk_widget_destroy (GTK_WIDGET (area->nrc_popover));
+  area->nrc_popover = popover;
+  g_object_ref (area->nrc_popover);
+  gtk_popover_set_position (popover, GTK_POS_BOTTOM);
+  gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, 1, 1});
+  gtk_widget_set_parent (GTK_WIDGET (popover), GTK_WIDGET (area));
+  gtk_popover_set_has_arrow (popover, false);
+  gtk_widget_set_halign (GTK_WIDGET (popover), GTK_ALIGN_START);
+  g_signal_connect (G_OBJECT (popover), "closed", G_CALLBACK (pop_closed),
+                    popover);
+  g_object_unref (mm);
+  gtk_popover_popup (popover);
+}
+
+static void
+emacs_resizable_drawing_area_action (GtkWidget *widget, const char *action_name,
+                                     GVariant *parameter)
+{
+  block_input ();
+  if (EQ (intern_c_string (action_name), intern_c_string ("clipboard.copy")))
+    {
+      CALLN (Ffuncall_interactively, intern_c_string ("kill-ring-save"),
+             CALLN (Ffuncall, intern_c_string ("mark")), Fpoint ());
+    }
+  else if (EQ (intern_c_string (action_name),
+               intern_c_string ("clipboard.cut")))
+    {
+      Ffuncall (1,
+                (Lisp_Object[]){(intern_c_string ("touch-screen--safe-kill"))});
+    }
+  else if (EQ (intern_c_string (action_name),
+               intern_c_string ("selection.select-all")))
+    {
+      CALLN (Ffuncall, intern_c_string ("mark-whole-buffer"));
+    }
+  else if (EQ (intern_c_string (action_name),
+               intern_c_string ("selection.delete")))
+    {
+      if (!NILP (call0 (intern_c_string ("region-active-p"))))
+        call0 (intern_c_string ("delete-active-region"));
+    }
+  else if (EQ (intern_c_string (action_name),
+               intern_c_string ("clipboard.paste")))
+    {
+      CALLN (Ffuncall_interactively, intern_c_string ("yank"));
+    }
+  else if (EQ (intern_c_string (action_name), intern_c_string ("menu.select")))
+    {
+      struct popover_cb_data *data = cbd_from_item_variant (parameter);
+      egtk_menu_item_cb_data *cb_data = xmalloc (sizeof *cb_data);
+#define I data->root_wv
+      cb_data->select_id = 0;
+      cb_data->help = I->help;
+      cb_data->cl_data = data->cb_data;
+      cb_data->call_data = I->call_data;
+      ((void (*) (GtkWidget *, gpointer)) data->select_cb) (GTK_WIDGET (
+                                                              *(data->popover)),
+                                                            cb_data);
+#undef I
+      xfree (cb_data);
+    }
+  unblock_input ();
+}
+
+static GMenu *
+build_menu_model_from_widget_value (GtkWidget **po, widget_value *wv,
+                                    egtk_menu_cb_data *cl_data,
+                                    GCallback select_cb,
+                                    GCallback deactivate_cb,
+                                    GCallback highlight_cb, GSList **ptab,
+                                    struct frame *f, GSList **pcd)
+{
+  GMenu *model = g_menu_new ();
+  GMenu *cs = g_menu_new ();
+  const char *fsn_name = NULL;
+  GSList *cns = 0;
+  int idx = 0;
+  for (widget_value *item = wv->contents; item; item = item->next)
+    {
+      ++idx;
+      if (!item->call_data && !item->contents
+          && !menu_separator_name_p (item->name))
+        {
+	  if (idx != 1)
+	    {
+	      GMenuItem *label = g_menu_item_new (item->name, NULL);
+	      g_menu_item_set_label (label, item->name);
+	      g_menu_append_item (cs, label);
+	      g_object_unref (label);
+	    }
+	  else
+	    {
+	      fsn_name = item->name;
+	    }
+        }
+      else if (menu_separator_name_p (item->name))
+        {
+          g_menu_append_section (model, fsn_name, G_MENU_MODEL (cs));
+          cs = g_menu_new ();
+	  idx = 0;
+	  fsn_name = NULL;
+        }
+      else
+        {
+          char *utf8_label;
+          char *utf8_key;
+          utf8_label = get_utf8_string (item->name);
+          utf8_key = get_utf8_string (item->key);
+          if (!item->contents)
+            {
+              struct popover_cb_data *cbd
+                = xmalloc (sizeof (struct popover_cb_data));
+              cbd->cb_data = cl_data;
+              cbd->frame = f;
+              cbd->highlight_cb = highlight_cb;
+              cbd->deactivate_cb = deactivate_cb;
+              cbd->root_wv = item;
+              cbd->select_cb = select_cb;
+              cbd->popover = po;
+
+              *pcd = g_slist_append (*pcd, cbd);
+
+              const char *bef = item->enabled
+                                  ? (item->button_type == BUTTON_TYPE_TOGGLE ||
+				     item->button_type == BUTTON_TYPE_RADIO
+                                       ? item->selected ? "win.menu.bef_enabled"
+                                                        : "win.mv.bef_disabled"
+                                       : "menu.select")
+                                  : "menu.select.disabled";
+
+              GMenuItem *v = g_menu_item_new (utf8_label, bef);
+              cns = g_slist_append (cns, cbd);
+              g_menu_item_set_action_and_target_value (
+                v, bef, variant_from_item_cb_data (cbd, ptab));
+	      if (utf8_key)
+                g_menu_item_set_attribute (v, "accel", "s", utf8_key, NULL);
+              g_menu_append_item (cs, v);
+              g_object_unref (v);
+            }
+          else
+            {
+              GMenu *menu
+                = build_menu_model_from_widget_value (po, item, cl_data,
+                                                      select_cb, deactivate_cb,
+                                                      highlight_cb, ptab, f,
+                                                      pcd);
+              g_menu_append_submenu (cs, item->name, G_MENU_MODEL (menu));
+	      g_object_unref (menu);
+            }
+	  xfree (utf8_label);
+	  if (utf8_key)
+	    xfree (utf8_key);
+        }
+    }
+  g_menu_append_section (model, fsn_name, G_MENU_MODEL (cs));
+  g_object_unref (cs);
+  g_slist_free (cns);
+  return model;
+}
+
+static void
+free_ptab_gs1 (gpointer gsl)
+{
+  xfree (gsl);
+}
+
+static void
+free_ptab_gsl (gpointer gsl)
+{
+  g_slist_free_full (gsl, free_ptab_gs1);
+}
+
+static void
+free_pzt_dat1 (gpointer pzt)
+{
+  g_hash_table_remove (atab, pzt);
+}
+
+static void
+free_pzt_dat (gpointer pzt)
+{
+  g_slist_free_full (pzt, free_pzt_dat1);
+}
+
+static void
+menu_widget_popped_down (GtkPopover *popover, gpointer user_data)
+{
+  gtk_window_set_focus (locate_nonchild_window (user_data),
+			FRAME_GTK_WIDGET ((struct frame *) user_data));
+}
+
+static GtkPopover *
+build_menu_from_widget_value (widget_value *wv, egtk_menu_cb_data *cl_data,
+                              GCallback select_cb, GCallback deactivate_cb,
+                              GCallback highlight_cb, struct frame *f, bool mb,
+			      bool fc)
+{
+  zz_action_update ((gpointer) FRAME_GTK_WIDGET (f));
+  GtkPopover **po = xmalloc (sizeof *po);
+  GSList *ptab = NULL;
+  GSList *pcd = NULL;
+  GMenu *model
+    = build_menu_model_from_widget_value ((GtkWidget **) po, wv, cl_data, select_cb,
+                                          deactivate_cb, highlight_cb, &ptab, f,
+                                          &pcd);
+  g_menu_freeze (model);
+  *po = GTK_POPOVER (gtk_popover_menu_new_from_model_full (
+							   G_MENU_MODEL (model), (!mb || fc) && NILP (Vpgtk_last_event_from_touchscreen)
+                            ? GTK_POPOVER_MENU_NESTED
+                            : 0));
+  if (!mb)
+    gtk_widget_set_parent (GTK_WIDGET (*po), FRAME_GTK_WIDGET (f));
+  gtk_widget_set_halign (GTK_WIDGET (*po), GTK_ALIGN_START);
+  gtk_popover_set_has_arrow (*po, mb);
+  g_object_set_data_full (G_OBJECT (*po), "eg-ptab-data", ptab, free_pzt_dat);
+  g_object_set_data_full (G_OBJECT (*po), "eg-pcd-data", pcd, free_ptab_gsl);
+  g_object_set_data_full (G_OBJECT (*po), "eg-pot", po, xfree);
+  g_signal_connect (*po, "closed", deactivate_cb, cl_data);
+  g_signal_connect (*po, "closed", G_CALLBACK (menu_widget_popped_down), f);
+  g_object_set_data_full (G_OBJECT (*po), "eg-model", model, g_object_unref);
+  gtk_popover_set_autohide (*po, true);
+  return *po;
+}
+
+GtkWidget *
+build_popover_menu_bar (widget_value *wv, egtk_menu_cb_data *cl_data,
+                        GCallback select_cb, GCallback deactivate_cb,
+                        GCallback highlight_cb, struct frame *f,
+                        GtkWidget *mbar)
+{
+  zz_action_update ((gpointer) FRAME_GTK_WIDGET (f));
+  LwMenuBar **po = xmalloc (sizeof *po);
+  *po = mbar ? (LwMenuBar *) mbar : lw_menu_bar_new ();
+  (*po)->frame = f;
+  lw_menu_bar_set_root_wv (*po, wv);
+  (*po)->cl_data = cl_data;
+  (*po)->deactivate_cb = deactivate_cb;
+  (*po)->highlight_cb = highlight_cb;
+  (*po)->select_cb = select_cb;
+  g_object_set_data_full (G_OBJECT (*po), "eg-pot", po, xfree);
+  g_object_set_data_full (G_OBJECT (*po), "eg-cb-data", cl_data,
+			  (GDestroyNotify) unref_cl_data);
+
+  if (!gtk_widget_get_parent (GTK_WIDGET (*po)) && FRAME_EXTERNAL_MENU_BAR (f))
+    gtk_container_add (GTK_CONTAINER (FRAME_MB_BOX (f)), GTK_WIDGET (*po));
+  return GTK_WIDGET (*po);
+}
+
+void
+free_frame_menubar (struct frame *f)
+{
+  if (FRAME_EXTERNAL_MENU_BAR (f) &&
+      FRAME_MENU_BAR (f))
+    gtk_widget_unparent (FRAME_MENU_BAR (f));
+  FRAME_MENU_BAR (f) = NULL;
+}
+
+static void
+emacs_gtk_accessible_class_init (EmacsGtkAccessibleClass *klass)
+{
+}
+
+void
+emacs_resizable_drawing_area_set_background (EmacsResizableDrawingArea *area,
+                                             unsigned long cl)
+{
+  GdkRGBA color;
+  Emacs_Color rcolor;
+  rcolor.pixel = cl;
+  pgtk_query_color (area->f, &rcolor);
+  color.alpha = 1.0;
+  color.blue = rcolor.blue / 65535.0;
+  color.red = rcolor.red / 65535.0;
+  color.green = rcolor.red / 65535.0;
+  if (area->background_color && gdk_rgba_equal (&color, area->background_color))
+    return;
+  GdkRGBA *clon = malloc (sizeof color);
+  memcpy (clon, &color, sizeof color);
+  free (area->background_color);
+  area->background_color = clon;
+  gtk_widget_queue_allocate (GTK_WIDGET (area));
+}
+
+static void
+emacs_gtk_accessible_init (EmacsGtkAccessible *area)
+{
+  atk_object_set_role (ATK_OBJECT (area), ATK_ROLE_TEXT);
+  atk_object_set_name (ATK_OBJECT (area), "Emacs");
+}
+
+static AtkObject *
+atk_get_accessible (GtkWidget *widget)
+{
+  GtkAccessible *accessible
+    = ((EmacsResizableDrawingArea *) widget)->accessible;
+  return ATK_OBJECT (accessible);
+}
+
+G_DEFINE_TYPE_WITH_CODE (
+  EmacsResizableDrawingArea, emacs_resizable_drawing_area,
+  GTK_TYPE_WIDGET,
+  G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_MAP,
+                         emacs_resizable_drawing_area_action_map_init))
+#undef G_STATIC_ASSERT
+#pragma GCC diagnostic push
+void
+run_main_loop_iteration (void)
+{
+  g_main_context_iteration (NULL, false);
+}
+
+static void
+zz_action_update (EmacsResizableDrawingArea *a)
+{
+  gtk_widget_action_set_enabled (GTK_WIDGET (a), "clipboard.copy",
+                                 !NILP (call0 (
+                                   intern_c_string ("region-active-p"))));
+  gtk_widget_action_set_enabled (GTK_WIDGET (a), "clipboard.cut",
+                                 !NILP (call0 (
+                                   intern_c_string ("region-active-p"))));
+  gtk_widget_action_set_enabled (GTK_WIDGET (a), "selection.delete",
+                                 !NILP (call0 (
+                                   intern_c_string ("region-active-p"))));
+  gtk_widget_action_set_enabled (GTK_WIDGET (a), "clipboard.paste", true);
+  gtk_widget_action_set_enabled (GTK_WIDGET (a), "selection.select-all", true);
+  gtk_widget_action_set_enabled (GTK_WIDGET (a), "menu.select", true);
+}
+
+static void
+boolean_action_activate (GSimpleAction *simple, GVariant *parameter,
+                         gpointer user_data)
+{
+  emacs_resizable_drawing_area_action (user_data, "menu.select", parameter);
+}
+
+void
+emacs_resizable_drawing_area_init (EmacsResizableDrawingArea *a)
+{
+  a->accessible = GTK_ACCESSIBLE (emacs_gtk_accessible_new (a));
+  gtk_accessible_set_widget (a->accessible, GTK_WIDGET (a));
+  gtk_widget_set_name (GTK_WIDGET (a), "Emacs entry");
+  zz_action_update (a);
+  a->nrc_popover = NULL;
+  a->action_map = g_hash_table_new (g_str_hash, g_str_equal);
+  a->action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+  gtk_widget_insert_action_group (GTK_WIDGET (a), "win", a->action_group);
+
+  static GActionEntry we[] = {{"menu.bef_enabled", boolean_action_activate, "b",
+                               "true", boolean_action_activate},
+                              {"mv.bef_disabled", boolean_action_activate,
+                               "b", "false", boolean_action_activate}};
+
+  g_action_map_add_action_entries (G_ACTION_MAP (a), we, 2, a);
+}
+
+static gchar *
+atk_get_text (AtkText *text, gint start_offset, gint end_offset)
+{
+  block_input ();
+  EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text;
+  Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame);
+  Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window));
+  if (NILP (buffer) || !BUFFERP (buffer))
+    return NULL;
+  ptrdiff_t specbind_down = SPECPDL_INDEX ();
+  record_unwind_current_buffer ();
+  Fset_buffer (buffer);
+  Lisp_Object bufstr = make_buffer_string (start_offset, end_offset, 0);
+  Lisp_Object utf = ENCODE_UTF_8 (bufstr);
+  const gchar *khar = g_strdup (SSDATA (utf));
+  unbind_to (specbind_down, Qnil);
+  unblock_input ();
+  return (gchar *) khar;
+}
+static gunichar
+atk_get_character_at_offset (AtkText *text, gint offset)
+{
+  EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text;
+  Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame);
+  Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window));
+  if (NILP (buffer) || !BUFFERP (buffer))
+    return 0;
+  ptrdiff_t specbind_down = SPECPDL_INDEX ();
+  record_unwind_current_buffer ();
+  Fset_buffer (buffer);
+  Lisp_Object khar = Fchar_after (make_fixnum (offset));
+  gunichar zchar = (gunichar) XFIXNUM (khar);
+  unbind_to (specbind_down, Qnil);
+  return zchar;
+}
+static gint
+atk_get_caret_offset (AtkText *text)
+{
+  EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text;
+  Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame);
+  Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window));
+  if (NILP (buffer) || !BUFFERP (buffer))
+    return 0;
+  return XBUFFER (buffer)->pt;
+}
+
+static AtkAttributeSet *
+atk_get_run_attributes (AtkText *text, gint offset, gint *start_offset,
+                        gint *end_offset)
+{
+  ptrdiff_t pdl = SPECPDL_INDEX ();
+  record_unwind_protect_excursion ();
+  EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text;
+  Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame);
+  Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window));
+  set_buffer_internal (XBUFFER (buffer));
+  Fset_window_point (window, make_fixnum (offset));
+  Fforward_word (window);
+  int point = XFIXNUM (Fpoint ());
+  unbind_to (pdl, Qnil);
+
+  *start_offset = offset;
+  *end_offset = point;
+
+  return NULL;
+}
+
+static AtkAttributeSet *
+atk_get_default_attributes (AtkText *text)
+{
+  TODO;
+  return NULL;
+}
+
+static void
+zz_size_allocate (GtkWidget *widget, int width, int height, int baseline)
+{
+  EmacsResizableDrawingArea *area = (gpointer) widget;
+  if (area->nrc_popover
+      && gtk_widget_is_visible (GTK_WIDGET (area->nrc_popover)))
+    gtk_native_check_resize (GTK_NATIVE (area->nrc_popover));
+  for (GtkWidget *c = gtk_widget_get_first_child (GTK_WIDGET (area)); c;
+       c = gtk_widget_get_next_sibling (c))
+    {
+      if (GTK_IS_NATIVE (c) && gtk_widget_is_visible (c))
+        gtk_native_check_resize (GTK_NATIVE (c));
+    }
+}
+
+static void
+atk_get_character_extents (AtkText *text, gint offset, gint *x, gint *y,
+                           gint *width, gint *height, AtkCoordType coords)
+{
+  block_input ();
+  EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text;
+  Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame);
+  Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window));
+  if (NILP (buffer) || !BUFFERP (buffer))
+    return;
+  ptrdiff_t binding = SPECPDL_INDEX ();
+  record_unwind_current_buffer ();
+  Fset_buffer (buffer);
+  Lisp_Object bcount
+    = call2 (intern_c_string ("window-absolute-pixel-position"),
+             make_fixnum (offset), Qnil);
+  if (NILP (bcount))
+    return;
+  *x = XFIXNUM (XCAR (bcount));
+  *y = XFIXNUM (XCDR (bcount));
+  *height = FRAME_LINE_HEIGHT (accessible->frame);
+  *width = FRAME_COLUMN_WIDTH (accessible->frame);
+  unbind_to (binding, Qnil);
+  unblock_input ();
+}
+
+static gint
+atk_get_character_count (AtkText *text)
+{
+  EmacsGtkAccessible *accessible = (EmacsGtkAccessible *) text;
+  Lisp_Object window = FRAME_SELECTED_WINDOW (accessible->frame);
+  Lisp_Object buffer = WINDOW_BUFFER (XWINDOW (window));
+  if (NILP (buffer) || !BUFFERP (buffer))
+    return 0;
+  return XFIXNUM (Fbuffer_size (buffer));
+}
+
+static void
+emacs_resizable_drawing_area_atk_text_init (AtkTextIface *iface)
+{
+  iface->get_text = atk_get_text;
+  iface->get_character_at_offset = atk_get_character_at_offset;
+  iface->get_caret_offset = atk_get_caret_offset;
+  iface->get_run_attributes = atk_get_run_attributes;
+  iface->get_default_attributes = atk_get_default_attributes;
+  iface->get_character_extents = atk_get_character_extents;
+  iface->get_character_count = atk_get_character_count;
+}
+
+GtkWidget *
+emacs_resizable_drawing_area_new (struct frame *f)
+{
+  EmacsResizableDrawingArea *area = (EmacsResizableDrawingArea *)
+    g_object_new (emacs_resizable_drawing_area_get_type (), NULL);
+  area->f = f;
+  emacs_resizable_drawing_area_set_background (area,
+                                               FRAME_BACKGROUND_COLOR (f));
+  return GTK_WIDGET (area);
+}
+
+static void
+update_theme_scrollbar_width (void)
+{
+  GtkWidget *widget = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL,
+                                         gtk_adjustment_new (0, 0, 0, 0, 0, 0));
+  int u, z;
+  gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, true, &u, &z, &u, &u);
+  scroll_bar_width_for_theme = z;
+  gtk_widget_destroy (widget);
+}
+
+static void
+update_theme_scrollbar_height (void)
+{
+  GtkWidget *widget = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL,
+                                         gtk_adjustment_new (0, 0, 0, 0, 0, 0));
+  int u, z;
+  gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, true, &u, &z, &u, &u);
+  scroll_bar_height_for_theme = z;
+  gtk_widget_destroy (widget);
+}
+
+static void
+emacs_resizable_drawing_area_snapshot (GtkWidget *widget, GtkSnapshot *snapshot)
+{
+  block_input ();
+  EmacsResizableDrawingArea *area = (gpointer) widget;
+  gtk_snapshot_append_node (
+    snapshot,
+    gsk_color_node_new (area->background_color,
+                        (&GRAPHENE_RECT_INIT
+			 (0, 0, gtk_widget_get_width (widget),
+			  gtk_widget_get_height (widget)))));
+  cairo_t *cr
+    = gtk_snapshot_append_cairo (snapshot, (&GRAPHENE_RECT_INIT
+					    (0, 0, gtk_widget_get_width (widget),
+					     gtk_widget_get_height (widget))));
+  pgtk_cr_draw_frame (cr, area->f);
+  cairo_destroy (cr);
+  if (FRAME_X_OUTPUT (area->f)->visible_bell_end_time)
+    {
+      GdkRGBA rgba;
+      rgba.red = area->background_color->red;
+      rgba.blue = area->background_color->blue;
+      rgba.green = area->background_color->green;
+      rgba.alpha = area->background_color->alpha / 2;
+      gtk_snapshot_append_color (snapshot, &rgba, (&GRAPHENE_RECT_INIT
+						   (0, 0, gtk_widget_get_width (widget),
+						    gtk_widget_get_height (widget))));
+    }
+  GtkWidgetClass *class = emacs_resizable_drawing_area_parent_class;
+  class->snapshot (widget, snapshot);
+  unblock_input ();
+}
+
+static void
+emacs_resizable_drawing_area_measure (GtkWidget *widget,
+                                      GtkOrientation orientation, int for_size,
+                                      int *minimum, int *natural,
+                                      int *minimum_baseline,
+                                      int *natural_baseline)
+{
+  GtkWidgetClass *parent_class = g_type_class_peek_parent (
+    G_TYPE_INSTANCE_GET_CLASS (widget, emacs_resizable_drawing_area_get_type (),
+                               EmacsResizableDrawingAreaClass));
+  parent_class->measure (widget, orientation, for_size, minimum, natural,
+                         minimum_baseline, natural_baseline);
+  *natural
+    = orientation == GTK_ORIENTATION_VERTICAL
+    ? FRAME_PIXEL_HEIGHT (((EmacsResizableDrawingArea *) widget)->f)
+    : FRAME_PIXEL_WIDTH (((EmacsResizableDrawingArea *) widget)->f);
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    if (*natural > gtk_widget_get_width
+	(GTK_WIDGET (FRAME_GTK_OUTER_WIDGET (((EmacsResizableDrawingArea *) widget)->f))))
+      *natural = gtk_widget_get_width
+	(GTK_WIDGET (FRAME_GTK_OUTER_WIDGET (((EmacsResizableDrawingArea *) widget)->f)));
+  *minimum = 0;
+}
+
+static ptrdiff_t eg_store_widget_in_map (GtkWidget *w);
+
+
+static gboolean
+emacs_resizable_drawing_area_focus_self (GtkWidget *widget)
+{
+  EmacsResizableDrawingArea *dr = (gpointer) widget;
+  if (!FRAME_NO_ACCEPT_FOCUS (dr->f))
+    gtk_root_set_focus (gtk_widget_get_root (widget), widget);
+  return TRUE;
+}
+
+
+void
+emacs_resizable_drawing_area_class_init (EmacsResizableDrawingAreaClass *claz)
+{
+  GTK_WIDGET_CLASS (claz)->measure = emacs_resizable_drawing_area_measure;
+  GTK_WIDGET_CLASS (claz)->get_accessible = atk_get_accessible;
+  GTK_WIDGET_CLASS (claz)->snapshot = emacs_resizable_drawing_area_snapshot;
+  GTK_WIDGET_CLASS (claz)->size_allocate = zz_size_allocate;
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "clipboard.cut",
+                                   NULL, emacs_resizable_drawing_area_action);
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "clipboard.copy",
+                                   NULL, emacs_resizable_drawing_area_action);
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "clipboard.paste",
+                                   NULL, emacs_resizable_drawing_area_action);
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "selection.delete",
+                                   NULL, emacs_resizable_drawing_area_action);
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz),
+                                   "selection.select-all", NULL,
+                                   emacs_resizable_drawing_area_action);
+  gtk_widget_class_install_action (GTK_WIDGET_CLASS (claz), "menu.select",
+                                   (gpointer) G_VARIANT_TYPE_ANY,
+                                   emacs_resizable_drawing_area_action);
+  GTK_WIDGET_CLASS (claz)->grab_focus = emacs_resizable_drawing_area_focus_self;
+}
+
+void
+egtk_list_insert (egtk_list_node *list, egtk_list_node *node)
+{
+  egtk_list_node *list_start = list->next;
+
+  if (list_start)
+    list_start->prev = node;
+  node->next = list_start;
+  node->prev = 0;
+  list->next = node;
+}
+
+gboolean
+overlay_allocate_cb (GtkOverlay *overlay, GtkWidget *widget,
+                     GdkRectangle *allocation, gpointer user_data)
+{
+  GdkRectangle *rect = g_object_get_data (G_OBJECT (widget), EG_OVERLAY_ALLOC);
+
+  if (!rect)
+    return false;
+
+  allocation->x = rect->x;
+  allocation->y = rect->y;
+  int height, width, mw, mh, r, b;
+  gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, true, &mh, &height, &r,
+                      &b);
+  gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, true, &mw, &width, &r,
+                      &b);
+
+  if (GTK_IS_SCROLLBAR (widget))
+    {
+      bool vis = g_object_get_data (G_OBJECT (widget), "eg-old-vis")
+                 || gtk_widget_is_visible (widget);
+
+      if (rect->height < mh || rect->width < mw)
+        {
+          gtk_widget_set_opacity (gtk_widget_get_first_child (widget), 0.0);
+          g_object_set_data (G_OBJECT (widget), "eg-old-vis", (gpointer) vis);
+        }
+      else
+        {
+          gtk_widget_set_opacity (gtk_widget_get_first_child (widget), !vis ? 0.0 : 1.0);
+          g_object_set_data (G_OBJECT (widget), "eg-old-vis", 0);
+        }
+    }
+
+  allocation->height = rect->height != -1 ? rect->height : height;
+  allocation->width = rect->width != -1 ? rect->width : width;
+  return true;
+}
+
+bool
+egtk_create_frame_widgets (struct frame *f)
+{
+  GtkWindow *wtop = GTK_WINDOW (emacs_window_new ());
+
+  GtkSettings *gs = gtk_settings_get_for_display (FRAME_X_DISPLAY (f));
+  g_signal_connect (G_OBJECT (gs), "notify::gtk-theme-name",
+		    G_CALLBACK (style_change_cb), f);
+  g_signal_connect (G_OBJECT (gs), "notify::gtk-application-prefer-dark-theme",
+		    G_CALLBACK (style_change_cb), f);
+  g_signal_connect (G_OBJECT (gs), "notify::gtk-font-name",
+		    G_CALLBACK (style_change_cb), f);
+  gtk_window_set_display (wtop, FRAME_X_OUTPUT (f)->display_info->gdpy);
+  gtk_window_set_decorated (wtop, true);
+  gtk_widget_set_name (GTK_WIDGET (wtop), EMACS_CLASS);
+
+  GtkHeaderBar *header_bar = GTK_HEADER_BAR (gtk_header_bar_new ());
+  gtk_header_bar_set_show_title_buttons (header_bar, true);
+  gtk_window_set_titlebar (wtop, GTK_WIDGET (header_bar));
+  gtk_header_bar_set_subtitle (header_bar, SSDATA (f->name));
+
+  GtkWidget *drawing_area = GTK_WIDGET (emacs_resizable_drawing_area_new (f));
+  gtk_window_set_default_widget (wtop, drawing_area);
+  gtk_window_set_focus (wtop, drawing_area);
+  gtk_widget_set_name (drawing_area, SSDATA (Vx_resource_name));
+
+  GtkBox *box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+  GtkBox *mbb = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+  GtkBox *tbb = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+  FRAME_BOX (f) = box;
+  FRAME_MB_BOX (f) = mbb;
+  FRAME_TB_BOX (f) = tbb;
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (mbb));
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (tbb));
+
+  GtkOverlay *decor_overlay = GTK_OVERLAY (gtk_overlay_new ());
+
+  GtkOverlay *overlay = GTK_OVERLAY (gtk_overlay_new ());
+  gtk_container_add (GTK_CONTAINER (wtop), GTK_WIDGET (box));
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (overlay));
+  gtk_container_add (GTK_CONTAINER (decor_overlay), GTK_WIDGET (drawing_area));
+  gtk_container_add (GTK_CONTAINER (overlay), GTK_WIDGET (decor_overlay));
+  g_signal_connect (G_OBJECT (overlay), "get-child-position",
+                    G_CALLBACK (overlay_allocate_cb), NULL);
+  g_signal_connect (G_OBJECT (decor_overlay), "get-child-position",
+                    G_CALLBACK (overlay_allocate_cb), NULL);
+  gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 0, 0);
+
+  FRAME_GTK_OUTER_WIDGET (f) = GTK_WIDGET (wtop);
+  FRAME_GTK_WIDGET (f) = GTK_WIDGET (drawing_area);
+  FRAME_GTK_HEADER_BAR (f) = header_bar;
+  FRAME_TOOL_BAR (f) = NULL;
+  FRAME_DECOR_OVERLAY (f) = decor_overlay;
+  FRAME_OVERLAY (f) = overlay;
+
+  xg_notify_header_bar_menu_state_changed (f);
+
+  gtk_window_resize (GTK_WINDOW (wtop), f->pixel_width, f->pixel_height);
+
+  gchar *title = NULL;
+  if (!NILP (f->title))
+    title = SSDATA (ENCODE_UTF_8 (f->title));
+  else if (!NILP (f->name))
+    title = SSDATA (ENCODE_UTF_8 (f->name));
+
+  if (title)
+    gtk_window_set_title (wtop, title);
+
+  if (FRAME_UNDECORATED (f))
+    {
+      gtk_window_set_decorated (wtop, false);
+      store_frame_param (f, Qundecorated, Qt);
+    }
+
+  gtk_widget_set_hexpand (GTK_WIDGET (drawing_area), true);
+  gtk_widget_set_vexpand (GTK_WIDGET (drawing_area), true);
+  gtk_window_set_resizable (GTK_WINDOW (wtop), true);
+  gtk_widget_set_focus_on_click (GTK_WIDGET (drawing_area), true);
+  GListModel *conts =
+    gtk_widget_observe_controllers (GTK_WIDGET (wtop));
+  GtkEventController *mba = NULL;
+  block_input ();
+  for (int i = 0; i < g_list_model_get_n_items (conts); ++i)
+    if (gtk_event_controller_get_name (g_list_model_get_item (conts, i)) &&
+	!strcmp (gtk_event_controller_get_name (g_list_model_get_item (conts, i)),
+		 "gtk-window-menubar-accel"))
+      mba = g_list_model_get_item (conts, i);
+  unblock_input ();
+  if (mba)
+    gtk_widget_remove_controller (GTK_WIDGET (wtop), mba);
+  gtk_widget_grab_focus (GTK_WIDGET (drawing_area));
+  return true;
+}
+
+static const char *
+get_dialog_title (char key)
+{
+  const char *title = "";
+
+  switch (key)
+    {
+    case 'E':
+    case 'e':
+      title = "Error";
+      break;
+
+    case 'I':
+    case 'i':
+      title = "Information";
+      break;
+
+    case 'L':
+    case 'l':
+      title = "Prompt";
+      break;
+
+    case 'P':
+    case 'p':
+      title = "Prompt";
+      break;
+
+    case 'Q':
+    case 'q':
+      title = "Question";
+      break;
+    }
+
+  return title;
+}
+
+GtkDialog *
+egtk_build_dialog (struct frame *f, widget_value *wv, GCallback select_cb,
+                   GCallback deactivate_cb)
+{
+  const char *title = get_dialog_title (wv->name[0]);
+  GtkDialog *d = build_dialog_n_items (wv->contents->value, wv->contents->next,
+                                       select_cb, deactivate_cb);
+  gtk_window_set_title (GTK_WINDOW (d), title);
+  gtk_widget_hide (GTK_WIDGET (d));
+  gtk_window_set_transient_for (GTK_WINDOW (d), locate_nonchild_window (f));
+  return d;
+}
+
+double
+egtk_get_monitor_hdpi (GdkMonitor *monitor)
+{
+  int pixheight;
+  GdkRectangle rect;
+  gdk_monitor_get_geometry (monitor, &rect);
+  pixheight = rect.height * gdk_monitor_get_scale_factor (monitor);
+  int realheight = gdk_monitor_get_height_mm (monitor);
+  double pixels_per_mm = ((double) pixheight) / ((double) realheight);
+  return (pixels_per_mm * 25.4) * 0.8;
+}
+
+double
+egtk_get_monitor_wdpi (GdkMonitor *monitor)
+{
+  int pixwidth;
+  GdkRectangle rect;
+  gdk_monitor_get_geometry (monitor, &rect);
+  pixwidth = rect.width * gdk_monitor_get_scale_factor (monitor);
+  int realwidth = gdk_monitor_get_width_mm (monitor);
+  double pixels_per_mm = ((double) pixwidth) / ((double) realwidth);
+  return (pixels_per_mm * 25.4) * 0.8;
+}
+
+typedef char *(*xg_get_file_func) (GtkWidget *);
+
+static char *
+xg_get_file_name_from_chooser (GtkWidget *w)
+{
+  return g_file_get_path (gtk_file_chooser_get_file (GTK_FILE_CHOOSER (w)));
+}
+
+struct xg_dialog_data
+{
+  GMainLoop *loop;
+  int response;
+  GtkWidget *w;
+  guint timerid;
+};
+
+static void
+pop_down_dialog (void *arg)
+{
+  struct xg_dialog_data *dd = arg;
+
+  block_input ();
+  if (dd->w)
+    gtk_widget_destroy (dd->w);
+  if (dd->timerid != 0)
+    g_source_remove (dd->timerid);
+
+  g_main_loop_quit (dd->loop);
+  g_main_loop_unref (dd->loop);
+
+  unblock_input ();
+}
+
+static gboolean
+xg_maybe_add_timer (gpointer data)
+{
+  struct xg_dialog_data *dd = data;
+  struct timespec next_time = timer_check ();
+
+  dd->timerid = 0;
+
+  if (timespec_valid_p (next_time))
+    {
+      time_t s = next_time.tv_sec;
+      int per_ms = TIMESPEC_HZ / 1000;
+      int ms = (next_time.tv_nsec + per_ms - 1) / per_ms;
+      if (s <= ((guint) -1 - ms) / 1000)
+        dd->timerid = g_timeout_add (s * 1000 + ms, xg_maybe_add_timer, dd);
+    }
+  return FALSE;
+}
+
+/* Function that is called when the file or font dialogs pop down.
+   W is the dialog widget, RESPONSE is the response code.
+   USER_DATA is what we passed in to g_signal_connect.  */
+
+static void
+eg_dialog_response_cb (GtkDialog *w, gint response, gpointer user_data)
+{
+  struct xg_dialog_data *dd = user_data;
+  dd->response = response;
+  g_main_loop_quit (dd->loop);
+}
+
+static int
+xg_dialog_run (struct frame *f, GtkWidget *w)
+{
+  ptrdiff_t count = SPECPDL_INDEX ();
+  struct xg_dialog_data dd;
+
+  gtk_window_set_transient_for (GTK_WINDOW (w), locate_nonchild_window (f));
+  gtk_window_set_destroy_with_parent (GTK_WINDOW (w), TRUE);
+  gtk_window_set_modal (GTK_WINDOW (w), TRUE);
+
+  dd.loop = g_main_loop_new (NULL, FALSE);
+  dd.response = GTK_RESPONSE_CANCEL;
+  dd.w = w;
+  dd.timerid = 0;
+
+  g_signal_connect (G_OBJECT (w), "response",
+                    G_CALLBACK (eg_dialog_response_cb), &dd);
+  gtk_widget_show (w);
+
+  record_unwind_protect_ptr (pop_down_dialog, &dd);
+
+  (void) xg_maybe_add_timer (&dd);
+  g_main_loop_run (dd.loop);
+
+  dd.w = 0;
+  unbind_to (count, Qnil);
+
+  return dd.response;
+}
+
+static GtkWidget *
+xg_get_file_with_chooser (struct frame *f, char *prompt, char *default_filename,
+                          bool mustmatch_p, bool only_dir_p,
+                          xg_get_file_func *func)
+{
+  GtkWidget *filewin;
+  GtkWindow *gwin = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f));
+  GtkFileChooserAction action = (mustmatch_p ? GTK_FILE_CHOOSER_ACTION_OPEN
+                                             : GTK_FILE_CHOOSER_ACTION_SAVE);
+
+  if (only_dir_p)
+    action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+
+  filewin
+    = gtk_file_chooser_dialog_new (prompt, gwin, action, EGTK_TEXT_CANCEL,
+                                   GTK_RESPONSE_CANCEL,
+                                   (mustmatch_p || only_dir_p ? EGTK_TEXT_OPEN
+                                                              : EGTK_TEXT_OK),
+                                   GTK_RESPONSE_OK, NULL);
+
+  {
+    Lisp_Object file;
+    char *utf8_filename;
+
+    file = build_string (default_filename);
+
+    /* File chooser does not understand ~/... in the file name.  It must be
+       an absolute name starting with /.  */
+    if (default_filename[0] != '/')
+      file = Fexpand_file_name (file, Qnil);
+
+    utf8_filename = SSDATA (ENCODE_UTF_8 (file));
+    if (!NILP (Ffile_directory_p (file)))
+      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filewin),
+                                           g_file_new_for_path (utf8_filename),
+                                           NULL);
+  }
+
+  *func = xg_get_file_name_from_chooser;
+  return filewin;
+}
+
+char *
+egtk_get_file_name (struct frame *f, char *prompt, char *default_filename,
+                    bool mustmatch_p, bool only_dir_p)
+{
+  GtkWidget *w = 0;
+  char *fn = 0;
+  int filesel_done = 0;
+  xg_get_file_func func;
+  w = xg_get_file_with_chooser (f, prompt, default_filename, mustmatch_p,
+                                only_dir_p, &func);
+  filesel_done = xg_dialog_run (f, w);
+  if (filesel_done == GTK_RESPONSE_OK)
+    fn = (*func) (w);
+  return fn;
+}
+
+static gboolean
+eg_font_filter (const PangoFontFamily *family, const PangoFontFace *face,
+                gpointer data)
+{
+  const char *name = pango_font_family_get_name ((PangoFontFamily *) family);
+  ptrdiff_t namelen = strlen (name);
+
+  if (font_is_ignored (name, namelen))
+    return FALSE;
+  return TRUE;
+}
+
+Lisp_Object
+egtk_get_font (struct frame *f, const char *default_name)
+{
+  GtkWidget *w;
+  int done = 0;
+  Lisp_Object font = Qnil;
+
+  w = gtk_font_chooser_dialog_new ("Pick a font",
+                                   GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+
+  gtk_font_chooser_set_filter_func (GTK_FONT_CHOOSER (w), eg_font_filter, NULL,
+                                    NULL);
+  if (default_name)
+    {
+      /* Convert fontconfig names to Gtk names, i.e. remove - before
+         number */
+      char *p = strrchr (default_name, '-');
+      if (p)
+        {
+          char *ep = p + 1;
+          while (c_isdigit (*ep))
+            ++ep;
+          if (*ep == '\0')
+            *p = ' ';
+        }
+    }
+  else if (x_last_font_name)
+    default_name = x_last_font_name;
+
+  if (default_name)
+    {
+      PangoFontDescription *desc
+        = pango_font_description_from_string (default_name);
+      gtk_font_chooser_set_font_desc (GTK_FONT_CHOOSER (w), desc);
+      pango_font_description_free (desc);
+    }
+
+  gtk_widget_set_name (w, "emacs-fontdialog");
+  done = xg_dialog_run (f, w);
+  if (done == GTK_RESPONSE_OK)
+    {
+      PangoFontDescription *desc
+        = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (w));
+
+      if (desc)
+        {
+          const char *family = pango_font_description_get_family (desc);
+          gint size = pango_font_description_get_size (desc);
+          PangoWeight weight = pango_font_description_get_weight (desc);
+          PangoStyle style = pango_font_description_get_style (desc);
+
+          font = CALLN (Ffont_spec, QCfamily, build_string (family), QCsize,
+                        make_float (pango_units_to_double (size)), QCweight,
+                        EG_WEIGHT_TO_SYMBOL (weight), QCslant,
+                        EG_STYLE_TO_SYMBOL (style));
+
+          char *font_desc_str = pango_font_description_to_string (desc);
+          dupstring (&x_last_font_name, font_desc_str);
+          g_free (font_desc_str);
+          pango_font_description_free (desc);
+        }
+    }
+
+  gtk_widget_destroy (w);
+  return font;
+}
+
+bool egtk_ignore_gtk_scrollbar;
+bool egtk_gtk_initialized;
+
+static void
+eg_finish_scroll_bar_creation (struct frame *f, GtkWidget *wscroll,
+                               struct scroll_bar *bar,
+                               GCallback scroll_callback,
+                               GCallback end_callback,
+                               const char *scroll_bar_name)
+{
+  gtk_widget_set_name (wscroll, scroll_bar_name);
+  g_object_set_data (G_OBJECT (wscroll), XG_FRAME_DATA, (gpointer) f);
+
+  ptrdiff_t scroll_id = eg_store_widget_in_map (wscroll);
+  GtkRange *gr = GTK_RANGE (gtk_widget_get_first_child (wscroll));
+  if (!gr)
+    emacs_abort ();
+  g_signal_connect (G_OBJECT (gr), "change-value", scroll_callback,
+                    (gpointer) bar);
+
+  GtkEventController *c = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+  gtk_widget_add_controller (GTK_WIDGET (gr), c);
+  g_signal_connect (G_OBJECT (c), "released", end_callback, (gpointer) bar);
+
+  gtk_overlay_add_overlay (FRAME_DECOR_OVERLAY (f), wscroll);
+  GdkRectangle *rect = g_malloc (sizeof (GdkRectangle));
+  rect->x = 0;
+  rect->y = 0;
+  rect->height = 0;
+  rect->width = 0;
+
+  g_object_set_data_full (G_OBJECT (wscroll), EG_OVERLAY_ALLOC, rect, g_free);
+
+  gtk_widget_realize (wscroll);
+  bar->x_window = scroll_id;
+}
+
+void
+egtk_create_scroll_bar (struct frame *f, struct scroll_bar *bar,
+                        GCallback scroll_callback, GCallback end_callback,
+                        const char *scroll_bar_name)
+{
+  GtkWidget *wscroll;
+  GtkAdjustment *widget_adjustment
+    = gtk_adjustment_new (XG_SB_MIN, XG_SB_MIN, XG_SB_MAX, 1.0, 1.0, 1.0);
+  wscroll = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL,
+                               GTK_ADJUSTMENT (widget_adjustment));
+  bar->horizontal = false;
+  eg_finish_scroll_bar_creation (f, wscroll, bar, scroll_callback, end_callback,
+                                 scroll_bar_name);
+}
+
+void
+egtk_create_horizontal_scroll_bar (struct frame *f, struct scroll_bar *bar,
+                                   GCallback scroll_callback,
+                                   GCallback end_callback,
+                                   const char *scroll_bar_name)
+{
+  GtkWidget *wscroll;
+  GtkAdjustment *widget_adjustment
+    = gtk_adjustment_new (XG_SB_MIN, XG_SB_MIN, XG_SB_MAX, 1.0, 1.0, 1.0);
+  wscroll = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL,
+                               GTK_ADJUSTMENT (widget_adjustment));
+  bar->horizontal = true;
+  eg_finish_scroll_bar_creation (f, wscroll, bar, scroll_callback, end_callback,
+                                 scroll_bar_name);
+}
+
+void
+egtk_set_toolkit_horizontal_scroll_bar_thumb (struct scroll_bar *bar,
+                                              int portion, int position,
+                                              int whole)
+{
+  GtkWidget *wscroll = eg_get_widget_from_map (bar->x_window);
+
+  if (wscroll && bar->dragging == -1)
+    {
+      GtkAdjustment *adj;
+      int lower = 0;
+      int upper = max (whole - 1, 0);
+      int pagesize = min (upper, max (portion, 0));
+      int value = max (0, min (position, upper - pagesize));
+      int page_increment = 4;
+      int step_increment = 1;
+
+      block_input ();
+      adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (wscroll));
+      gtk_adjustment_configure (adj, (gdouble) value, (gdouble) lower,
+                                (gdouble) upper, (gdouble) step_increment,
+                                (gdouble) page_increment, (gdouble) pagesize);
+      unblock_input ();
+    }
+}
+
+int
+egtk_get_default_scrollbar_width (struct frame *f)
+{
+  return scroll_bar_width_for_theme;
+}
+int
+egtk_get_default_scrollbar_height (struct frame *f)
+{
+  return scroll_bar_height_for_theme;
+}
+
+static void
+g_func_ref (gpointer o, gpointer ignored)
+{
+  g_object_ref (o);
+}
+
+static void
+g_func_force_floating (gpointer o, gpointer ignored)
+{
+  g_object_force_floating (o);
+}
+
+static void
+g_func_widget_unparent (gpointer o, gpointer ignored)
+{
+  gtk_widget_unparent (o);
+}
+
+static void
+eg_tool_bar_callback (GtkWidget *w, gpointer client_data)
+{
+  intptr_t idx = (intptr_t) client_data;
+  gpointer gmod = g_object_get_data (G_OBJECT (w), XG_TOOL_BAR_LAST_MODIFIER);
+  intptr_t mod = (intptr_t) gmod;
+
+  struct frame *f = g_object_get_data (G_OBJECT (w), XG_FRAME_DATA);
+  Lisp_Object key, frame;
+  struct input_event event;
+  EVENT_INIT (event);
+
+  if (!f || !f->n_tool_bar_items || NILP (f->tool_bar_items))
+    return;
+
+  idx *= TOOL_BAR_ITEM_NSLOTS;
+
+  key = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_KEY);
+  XSETFRAME (frame, f);
+
+  event.kind = TOOL_BAR_EVENT;
+  event.frame_or_window = frame;
+  event.arg = key;
+  /* Convert between the modifier bits GDK uses and the modifier bits
+     Emacs uses.  This assumes GDK and X masks are the same, which they are when
+     this is written.  */
+#ifndef HAVE_PGTK
+  event.modifiers = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod);
+#else
+  event.modifiers = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), mod);
+#endif
+  kbd_buffer_store_event (&event);
+
+  /* Return focus to the frame after we have clicked on a detached
+     tool bar button. */
+  FRAME_TERMINAL (f)->focus_frame_hook (f, false);
+}
+
+static Lisp_Object
+file_for_image (Lisp_Object image)
+{
+  Lisp_Object specified_file = Qnil;
+  Lisp_Object tail;
+
+  for (tail = XCDR (image);
+       NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail));
+       tail = XCDR (XCDR (tail)))
+    if (EQ (XCAR (tail), QCfile))
+      specified_file = XCAR (XCDR (tail));
+
+  return specified_file;
+}
+
+static char *
+find_icon_from_name (char *name, GtkIconTheme *icon_theme, char **icon_name)
+{
+  if (name[0] == 'n' && name[1] == ':')
+    {
+      *icon_name = name + 2;
+      name = NULL;
+
+      if (!gtk_icon_theme_has_icon (icon_theme, *icon_name))
+        *icon_name = NULL;
+    }
+  else if (gtk_icon_theme_has_icon (icon_theme, name))
+    {
+      *icon_name = name;
+      name = NULL;
+    }
+  else
+    {
+      name = NULL;
+      *icon_name = NULL;
+    }
+
+  return name;
+}
+
+void
+update_frame_tool_bar (struct frame *f)
+{
+  update_frame_header_bar (f);
+  if (!FRAME_EXTERNAL_TOOL_BAR (f))
+    {
+      if (FRAME_TB_BOX (f) &&
+	  gtk_widget_get_first_child (GTK_WIDGET (FRAME_TB_BOX (f))))
+	gtk_widget_unparent (gtk_widget_get_first_child
+			     (GTK_WIDGET (FRAME_TB_BOX (f))));
+      return;
+    }
+  if (!FRAME_TOOL_BAR (f) && FRAME_TB_BOX (f))
+    {
+      FRAME_TOOL_BAR (f) = xeg_box_new ();
+      gtk_container_add (GTK_CONTAINER (FRAME_TB_BOX (f)), FRAME_TOOL_BAR (f));
+    }
+  else if (!FRAME_TB_BOX (f))
+    return;
+  block_input ();
+#define TB FRAME_TOOL_BAR (f)
+
+  GSList *reusable_buttons = NULL;
+  GSList *reusable_separators = NULL;
+  for (GtkWidget *widget = gtk_widget_get_first_child (TB);
+       widget; widget = gtk_widget_get_next_sibling (widget))
+    if (GTK_IS_BUTTON (widget))
+      reusable_buttons = g_slist_append (reusable_buttons, widget);
+    else if (GTK_IS_SEPARATOR (widget))
+      reusable_separators = g_slist_append (reusable_separators, widget);
+    else
+      emacs_abort ();
+
+  g_slist_foreach (reusable_buttons, (GFunc) g_func_ref, NULL);
+  g_slist_foreach (reusable_separators, (GFunc) g_func_ref, NULL);
+  g_slist_foreach (reusable_buttons, (GFunc) g_func_widget_unparent, NULL);
+  g_slist_foreach (reusable_separators, (GFunc) g_func_widget_unparent, NULL);
+  g_slist_foreach (reusable_buttons, (GFunc) g_func_force_floating, NULL);
+  g_slist_foreach (reusable_separators, (GFunc) g_func_force_floating, NULL);
+
+  int toolbar_items = f->n_tool_bar_items;
+  int sepc = 0;
+  int entc = 0;
+  GSList *items = NULL;
+#define PROP(IDX) AREF (f->tool_bar_items, i *TOOL_BAR_ITEM_NSLOTS + (IDX))
+  for (int i = 0; i < toolbar_items; ++i)
+    {
+      if (!Fequal (PROP (TOOL_BAR_ITEM_TYPE), Qt))
+	{
+	  ++entc;
+	  struct xg_tool_bar_entry *tbe
+            = xmalloc (sizeof (struct xg_tool_bar_entry));
+          tbe->icon = PROP (TOOL_BAR_ITEM_IMAGES);
+          tbe->idx = i;
+          tbe->title = PROP (TOOL_BAR_ITEM_LABEL);
+	  if (! NILP (PROP (TOOL_BAR_ITEM_VERT_ONLY)))
+	    tbe->title = Qnil;
+          tbe->help = PROP (TOOL_BAR_ITEM_HELP);
+          tbe->selected = !NILP (PROP (TOOL_BAR_ITEM_SELECTED_P));
+          tbe->enabled = !NILP (PROP (TOOL_BAR_ITEM_ENABLED_P));
+          tbe->type = PROP (TOOL_BAR_ITEM_TYPE);
+	  items = g_slist_append (items, tbe);
+	}
+      else
+	{
+	  ++sepc;
+	  items = g_slist_append (items, NULL);
+	}
+    }
+  while (g_slist_length (reusable_buttons) > entc)
+    {
+      gtk_widget_destroy (g_slist_last (reusable_buttons)->data);
+      reusable_buttons = g_slist_remove (reusable_buttons,
+					 g_slist_last (reusable_buttons)->data);
+    }
+  while (g_slist_length (reusable_separators) > sepc)
+    {
+      gtk_widget_destroy (g_slist_last (reusable_separators)->data);
+      reusable_separators = g_slist_remove (reusable_separators,
+					    g_slist_last (reusable_separators)->data);
+    }
+  int sepi = 0;
+  int btni = 0;
+#define NEWSEP                                                          \
+  (g_slist_nth (reusable_separators, sepi++)                            \
+     ? GTK_SEPARATOR (g_slist_nth_data (reusable_separators, sepi - 1)) \
+     : GTK_SEPARATOR (gtk_separator_new (GTK_ORIENTATION_VERTICAL)))
+#define NEWBTN                                                    \
+  (g_slist_nth (reusable_buttons, btni++)                         \
+     ? GTK_BUTTON (g_slist_nth_data (reusable_buttons, btni - 1)) \
+     : GTK_BUTTON (gtk_button_new ()))
+  for (GSList *i = items; i; i = i->next)
+    {
+      struct xg_tool_bar_entry *ent = i->data;
+      if (!ent)
+        gtk_container_add (GTK_CONTAINER (TB), GTK_WIDGET (NEWSEP));
+      else
+	{
+	  GtkButton *btn = NEWBTN;
+	  gulong ptr;
+          if ((ptr = (gulong) g_object_get_data (G_OBJECT (btn), "click-handler")))
+	    g_signal_handler_disconnect (G_OBJECT (btn), ptr);
+	  if (gtk_bin_get_child (GTK_BIN (btn)))
+	    gtk_container_remove (GTK_CONTAINER (btn),
+				  GTK_WIDGET (gtk_bin_get_child (GTK_BIN (btn))));
+	  GtkLabel *label = GTK_LABEL (gtk_label_new (NULL));
+	  gtk_widget_add_css_class (GTK_WIDGET (btn), "flat");
+	  gtk_widget_set_margin_end (GTK_WIDGET (btn), 2);
+	  gtk_widget_set_margin_start (GTK_WIDGET (btn), 2);
+	  if (!NILP (ent->title))
+            gtk_label_set_text (label, SSDATA (ENCODE_UTF_8 (ent->title)));
+          else
+	    gtk_label_set_text (label, NULL);
+	  if (NILP (ent->title))
+	    gtk_widget_hide (GTK_WIDGET (label));
+          gtk_widget_set_sensitive (GTK_WIDGET (btn), ent->enabled);
+          GtkImage *gimage = NULL;
+
+	  if (!NILP (ent->help))
+	    gtk_widget_set_tooltip_text (GTK_WIDGET (btn), SSDATA (ENCODE_UTF_8 (ent->help)));
+	  else
+            gtk_widget_set_tooltip_text (GTK_WIDGET (btn), NULL);
+
+          {
+	    Lisp_Object image = ent->icon;
+	    Lisp_Object stock = Qnil;
+	    char *icon_name = NULL;
+	    char *stock_name = NULL;
+	    int idx;
+	    ptrdiff_t img_id;
+	    struct image *img = NULL;
+	    Lisp_Object specfile = file_for_image (image);
+	    if (!CONSP (image) && !valid_image_p (image))
+	      continue;
+
+	    if (!NILP (specfile))
+	      stock = call1 (Qx_gtk_map_stock, specfile);
+
+	    if (CONSP (stock))
+	      {
+		Lisp_Object itr;
+		for (itr = stock; CONSP (itr); itr = XCDR (itr))
+		  {
+		    stock_name =
+		      find_icon_from_name (SSDATA (XCAR (itr)),
+					   gtk_icon_theme_get_for_display
+					   (FRAME_X_DISPLAY (f)),
+					   &icon_name);
+		    if (icon_name || stock_name)
+		      break;
+		  }
+	      }
+	    else if (STRINGP (stock))
+	      {
+		stock_name =
+		  find_icon_from_name (SSDATA (stock),
+				       gtk_icon_theme_get_for_display
+				       (FRAME_X_DISPLAY (f)),
+				       &icon_name);
+	      }
+
+	    if (stock_name == NULL && icon_name == NULL)
+	      {
+		if (VECTORP (image))
+		  {
+		    if (ent->enabled)
+		      idx = (ent->selected ? TOOL_BAR_IMAGE_ENABLED_SELECTED
+			     : TOOL_BAR_IMAGE_ENABLED_DESELECTED);
+                    else
+                      idx = (ent->selected ? TOOL_BAR_IMAGE_DISABLED_SELECTED
+			     : TOOL_BAR_IMAGE_DISABLED_DESELECTED);
+
+                    eassert (ASIZE (image) >= idx);
+                    image = AREF (image, idx);
+                  }
+                else
+                  idx = -1;
+
+                img_id = lookup_image (f, image);
+                img = IMAGE_FROM_ID (f, img_id);
+                prepare_image_for_display (f, img);
+              }
+            if (stock_name || icon_name)
+              gimage = GTK_IMAGE (gtk_image_new_from_icon_name
+				  (icon_name ? icon_name : stock_name));
+            else if (img && !img->load_failed_p)
+              gimage = GTK_IMAGE (egtk_get_image_for_pixmap (f, img, TB, NULL));
+
+            if (stock_name)
+              xfree (stock_name);
+            if (icon_name)
+              xfree (icon_name);
+	  }
+
+	  GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+	  if (gimage)
+	    gtk_widget_set_parent (GTK_WIDGET (gimage), box);
+          gtk_widget_set_parent (GTK_WIDGET (label), box);
+	  gtk_container_add (GTK_CONTAINER (btn), box);
+
+          g_object_set_data (G_OBJECT (btn), XG_FRAME_DATA, f);
+	  gulong l = g_signal_connect (G_OBJECT (btn), "clicked",
+                                       G_CALLBACK (eg_tool_bar_callback),
+                                       (gpointer) (intptr_t) ent->idx);
+	  g_object_set_data (G_OBJECT (btn), "click-handler", (gpointer) l);
+	  gtk_container_add (GTK_CONTAINER (TB), GTK_WIDGET (btn));
+	}
+      if (ent)
+	xfree (ent);
+    }
+  g_slist_free (items);
+  g_slist_free (reusable_buttons);
+  g_slist_free (reusable_separators);
+  unblock_input ();
+#undef NEWSEP
+#undef NEWBTN
+#undef PROP
+#undef TB
+}
+
+void
+free_frame_tool_bar (struct frame *f)
+{
+  if (FRAME_TB_BOX (f) && FRAME_TOOL_BAR (f))
+    gtk_container_remove (GTK_CONTAINER (FRAME_TB_BOX (f)),
+			  FRAME_TOOL_BAR (f));
+  FRAME_TOOL_BAR (f) = NULL;
+  FRAME_EXTERNAL_TOOL_BAR (f) = NULL;
+}
+
+void
+egtk_change_toolbar_position (struct frame *f, Lisp_Object pos)
+{
+  if (!EQ (pos, Qtop))
+    error ("Toolbar position cannot be changed on GTK 4");
+}
+
+void
+egtk_set_undecorated (struct frame *f, Lisp_Object undecorated)
+{
+  if (!FRAME_PARENT_FRAME (f))
+    gtk_window_set_decorated (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                              NILP (undecorated));
+}
+
+void
+egtk_frame_resized (struct frame *f, int pixelwidth, int pixelheight)
+{
+  int width, height;
+  if (pixelwidth == -1 && pixelheight == -1)
+    {
+      if (FRAME_GTK_WIDGET (f) && gtk_widget_get_mapped (FRAME_GTK_WIDGET (f)))
+        {
+          if (!FRAME_PARENT_FRAME (f))
+            {
+              pixelwidth = gdk_surface_get_width (
+                EGTK_WIDGET_GET_WINDOW (FRAME_GTK_WIDGET (f)));
+              pixelheight = gdk_surface_get_height (
+                EGTK_WIDGET_GET_WINDOW (FRAME_GTK_WIDGET (f)));
+            }
+          else
+            {
+              pixelwidth = gtk_widget_get_width (FRAME_GTK_WIDGET (f));
+              pixelheight = gtk_widget_get_height (FRAME_GTK_WIDGET (f));
+            }
+        }
+      else
+        return;
+    }
+  block_input ();
+  width = FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth);
+  height = FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight);
+  frame_size_history_add (f, Qxg_frame_resized, width, height, Qnil);
+  FRAME_RIF (f)->clear_under_internal_border (f);
+  change_frame_size (f, width, height, 0, 1, 0, 1);
+  SET_FRAME_GARBAGED (f);
+  cancel_mouse_face (f);
+  FRAME_X_OUTPUT (f)->want_flip = false;
+  unblock_input ();
+}
+
+void
+egtk_frame_set_char_size (struct frame *f, int width, int height)
+{
+  int pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width);
+  int pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height);
+  Lisp_Object fullscreen = get_frame_param (f, Qfullscreen);
+  gint gwidth, gheight;
+  int totalheight
+    = pixelheight + FRAME_TOOLBAR_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f);
+  int totalwidth = pixelwidth + FRAME_TOOLBAR_WIDTH (f);
+
+  if (FRAME_PIXEL_HEIGHT (f) == 0)
+    return;
+  if (FRAME_PARENT_FRAME (f))
+    {
+      GtkAllocation alloc;
+      gtk_widget_get_allocation (FRAME_GTK_OUTER_WIDGET (f), &alloc);
+      gwidth = alloc.width;
+      gheight = alloc.height;
+    }
+  else
+    gtk_window_get_size (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), &gwidth,
+                         &gheight);
+
+  /* Do this before resize, as we don't know yet if we will be resized.  */
+  FRAME_RIF (f)->clear_under_internal_border (f);
+
+  totalheight /= egtk_get_scale (f);
+  totalwidth /= egtk_get_scale (f);
+
+  x_wm_set_size_hint (f, 0, 0);
+
+  /* Resize the top level widget so rows and columns remain constant.
+
+     When the frame is fullheight and we only want to change the width
+     or it is fullwidth and we only want to change the height we should
+     be able to preserve the fullscreen property.  However, due to the
+     fact that we have to send a resize request anyway, the window
+     manager will abolish it.  At least the respective size should
+     remain unchanged but giving the frame back its normal size will
+     be broken ... */
+  if (EQ (fullscreen, Qfullwidth) && width == FRAME_TEXT_WIDTH (f))
+    {
+      frame_size_history_add (f, Qxg_frame_set_char_size_1, width, height,
+                              list2i (gheight, totalheight));
+      if (!FRAME_PARENT_FRAME (f))
+        {
+          gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), gwidth,
+                             totalheight);
+        }
+      else
+        {
+          GdkRectangle *rect;
+          if (FRAME_PARENT_FRAME (f)
+              && (rect
+                  = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+                                       EG_OVERLAY_ALLOC)))
+            {
+              rect->height = totalheight;
+              rect->width = gwidth;
+            }
+        }
+    }
+  else if (EQ (fullscreen, Qfullheight) && height == FRAME_TEXT_HEIGHT (f))
+    {
+      frame_size_history_add (f, Qxg_frame_set_char_size_2, width, height,
+                              list2i (gwidth, totalwidth));
+      if (!FRAME_PARENT_FRAME (f))
+        {
+          gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                             totalwidth, gheight);
+        }
+      else
+        {
+          GdkRectangle *rect;
+          if (FRAME_PARENT_FRAME (f)
+              && (rect
+                  = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+                                       EG_OVERLAY_ALLOC)))
+            {
+              rect->width = totalwidth;
+              rect->height = gheight;
+            }
+        }
+    }
+  else
+    {
+      frame_size_history_add (f, Qxg_frame_set_char_size_3, width, height,
+                              list2i (totalwidth, totalheight));
+      if (!FRAME_PARENT_FRAME (f))
+        {
+          gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                             totalwidth, totalheight);
+        }
+      else
+        {
+          GdkRectangle *rect;
+          if (FRAME_PARENT_FRAME (f)
+              && (rect
+                  = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+                                       EG_OVERLAY_ALLOC)))
+            {
+              rect->width = totalwidth;
+              rect->height = totalheight;
+            }
+        }
+      fullscreen = Qnil;
+    }
+
+  SET_FRAME_GARBAGED (f);
+  cancel_mouse_face (f);
+
+  /* We can not call change_frame_size for a mapped frame,
+     we can not set pixel width/height either.  The window manager may
+     override our resize request, XMonad does this all the time.
+     The best we can do is try to sync, so lisp code sees the updated
+     size as fast as possible.
+     For unmapped windows, we can set rows/cols.  When
+     the frame is mapped again we will (hopefully) get the correct size.  */
+  if (FRAME_VISIBLE_P (f))
+    {
+      if (!NILP (fullscreen))
+        /* Try to restore fullscreen state.  */
+        {
+          store_frame_param (f, Qfullscreen, fullscreen);
+          gui_set_fullscreen (f, fullscreen, fullscreen);
+        }
+    }
+  else
+    adjust_frame_size (f, width, height, 5, 0, Qxg_frame_set_char_size);
+}
+
+void
+x_wm_set_size_hint (struct frame *f, long int flags, bool user_position)
+{
+}
+
+void
+egtk_free_frame_widgets (struct frame *f)
+{
+  if (FRAME_GTK_OUTER_WIDGET (f))
+    {
+      struct egtk_frame_tb_info *tbinfo
+        = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+                             TB_INFO_KEY);
+      if (tbinfo)
+        xfree (tbinfo);
+      FRAME_GTK_OUTER_WIDGET (f) = 0;
+      FRAME_GTK_WIDGET (f) = 0;
+      gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f));
+      FRAME_X_WINDOW (f) = 0;
+    }
+}
+
+static int
+egtk_get_gdk_scale (void)
+{
+  GSettings *set = g_settings_new ("org.gnome.desktop.interface");
+  gdouble x = g_settings_get_double (set, "text-scaling-factor");
+  g_object_unref (set);
+  return (int) round (x);
+}
+
+int
+egtk_get_scale (struct frame *f)
+{
+  if (FRAME_GTK_WIDGET (f))
+    return gtk_widget_get_scale_factor (FRAME_GTK_WIDGET (f));
+  return egtk_get_gdk_scale ();
+}
+
+void
+egtk_display_open (char *display_name, GdkDisplay **dpy)
+{
+  GdkDisplay *display;
+  unrequest_sigio ();
+  display = gdk_display_open (strlen (display_name) ? display_name : NULL);
+  request_sigio ();
+  if (!default_display && display)
+    {
+      default_display = display;
+      gdk_display_manager_set_default_display (gdk_display_manager_get (),
+                                               default_display);
+    }
+  *dpy = display;
+}
+
+void
+egtk_display_close (GdkDisplay *display)
+{
+  /* If this is the default display, try to change it before closing.
+     If there is no other display to use, gdpy_def is set to NULL, and
+     the next call to xg_display_open resets the default display.  */
+  if (gdk_display_get_default () == display)
+    {
+      struct pgtk_display_info *display_info;
+      GdkDisplay *display_new = NULL;
+
+      /* Find another display.  */
+      for (display_info = x_display_list; display_info;
+           display_info = display_info->next)
+        if (display_info->gdpy != display)
+          {
+            display_new = display_info->gdpy;
+            gdk_display_manager_set_default_display (gdk_display_manager_get (),
+                                                     display_new);
+            break;
+          }
+      default_display = display_new;
+    }
+
+  gdk_display_close (display);
+}
+
+GdkCursor *
+egtk_create_default_cursor (GdkDisplay *gdpy)
+{
+  return gdk_cursor_new_from_name ("default", NULL);
+}
+
+static void
+eg_set_widget_bg (struct frame *f, GtkWidget *w, unsigned long pixel)
+{
+  Emacs_Color xbg;
+  xbg.pixel = pixel;
+  xbg.red = (pixel >> 16) & 0xff;
+  xbg.green = (pixel >> 8) & 0xff;
+  xbg.blue = (pixel >> 0) & 0xff;
+  xbg.red |= xbg.red << 8;
+  xbg.green |= xbg.green << 8;
+  xbg.blue |= xbg.blue << 8;
+  {
+    const char format[] = "* { background-color: #%02x%02x%02x; }";
+    /* The format is always longer than the resulting string.  */
+    char buffer[sizeof format];
+    int n = snprintf (buffer, sizeof buffer, format, xbg.red >> 8,
+                      xbg.green >> 8, xbg.blue >> 8);
+    eassert (n > 0);
+    eassert (n < sizeof buffer);
+    GtkCssProvider *provider = gtk_css_provider_new ();
+    gtk_css_provider_load_from_data (provider, buffer, -1);
+    gtk_style_context_add_provider (gtk_widget_get_style_context (w),
+                                    GTK_STYLE_PROVIDER (provider),
+                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#define G_STATIC_ASSERT(f) // FIXME kludge to fix GTK bugs
+    g_clear_object (&provider);
+#undef G_STATIC_ASSERT
+  }
+}
+
+static ptrdiff_t
+eg_store_widget_in_map (GtkWidget *w)
+{
+  ptrdiff_t i;
+
+  if (id_to_widget.max_size == id_to_widget.used)
+    {
+      ptrdiff_t new_size;
+      if (ZTYPE_MAXIMUM (GWindow) - ID_TO_WIDGET_INCR < id_to_widget.max_size)
+        memory_full (SIZE_MAX);
+
+      new_size = id_to_widget.max_size + ID_TO_WIDGET_INCR;
+      id_to_widget.widgets
+        = xnrealloc (id_to_widget.widgets, new_size, sizeof (GtkWidget *));
+
+      for (i = id_to_widget.max_size; i < new_size; ++i)
+        id_to_widget.widgets[i] = 0;
+      id_to_widget.max_size = new_size;
+    }
+
+  /* Just loop over the array and find a free place.  After all,
+     how many scroll bars are we creating?  Should be a small number.
+     The check above guarantees we will find a free place.  */
+  for (i = 0; i < id_to_widget.max_size; ++i)
+    {
+      if (!id_to_widget.widgets[i])
+        {
+          id_to_widget.widgets[i] = w;
+          ++id_to_widget.used;
+
+          return i;
+        }
+    }
+
+  /* Should never end up here  */
+  emacs_abort ();
+}
+
+GtkWidget *
+eg_get_widget_from_map (ptrdiff_t idx)
+{
+  if (id_to_widget.widgets && idx < id_to_widget.max_size
+      && id_to_widget.widgets[idx] != 0)
+    return id_to_widget.widgets[idx];
+
+  return 0;
+}
+
+void
+egtk_set_background_color (struct frame *frame, unsigned long bg)
+{
+  if (FRAME_GTK_WIDGET (frame))
+    {
+      block_input ();
+      eg_set_widget_bg (frame, FRAME_GTK_WIDGET (frame),
+                        FRAME_BACKGROUND_PIXEL (frame));
+
+      unblock_input ();
+    }
+}
+
+void
+egtk_mark_data (void)
+{
+  egtk_list_node *iter;
+  Lisp_Object rest, frame;
+
+  for (iter = egtk_menu_cb_list.next; iter; iter = iter->next)
+    mark_object (((egtk_menu_cb_data *) iter)->menu_bar_vector);
+
+  for (iter = egtk_menu_item_cb_list.next; iter; iter = iter->next)
+    {
+      egtk_menu_item_cb_data *cb_data = (egtk_menu_item_cb_data *) iter;
+
+      if (!NILP (cb_data->help))
+        mark_object (cb_data->help);
+    }
+
+  FOR_EACH_FRAME (rest, frame)
+  {
+    struct frame *f = XFRAME (frame);
+
+    if ((FRAME_X_P (f) || FRAME_PGTK_P (f)) && FRAME_GTK_OUTER_WIDGET (f))
+      {
+        struct egtk_frame_tb_info *tbinfo
+          = g_object_get_data (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+                               TB_INFO_KEY);
+        if (tbinfo)
+          {
+            mark_object (tbinfo->last_tool_bar);
+            mark_object (tbinfo->style);
+          }
+      }
+  }
+}
+
+GtkWidget *
+egtk_get_image_for_pixmap (struct frame *f, struct image *img,
+                           GtkWidget *widget, GtkImage *old_widget)
+{
+  cairo_surface_t *surface;
+
+  /* If we have a file, let GTK do all the image handling.
+     This seems to be the only way to make insensitive and activated icons
+     look good in all cases.  */
+  Lisp_Object specified_file = file_for_image (img->spec);
+  Lisp_Object file;
+
+  /* We already loaded the image once before calling this
+     function, so this only fails if the image file has been removed.
+     In that case, use the pixmap already loaded.  */
+
+  if (STRINGP (specified_file)
+      && STRINGP (file = image_find_image_file (specified_file)))
+    {
+      char *encoded_file = SSDATA (ENCODE_FILE (file));
+      if (!old_widget)
+        old_widget = GTK_IMAGE (gtk_image_new_from_file (encoded_file));
+      else
+        gtk_image_set_from_file (old_widget, encoded_file);
+
+      return GTK_WIDGET (old_widget);
+    }
+
+  /* No file, do the image handling ourselves.  This will look very bad
+     on a monochrome display, and sometimes bad on all displays with
+     certain themes.  */
+
+  if (cairo_pattern_get_type (img->cr_data) == CAIRO_PATTERN_TYPE_SURFACE)
+    cairo_pattern_get_surface (img->cr_data, &surface);
+  else
+    surface = NULL;
+
+  if (surface)
+    {
+      if (!old_widget)
+        old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (
+          gdk_pixbuf_get_from_surface (surface, 0, 0,
+                                       cairo_image_surface_get_width (surface),
+                                       cairo_image_surface_get_height (
+                                         surface))));
+      else
+        gtk_image_set_from_pixbuf (
+          old_widget,
+          (gdk_pixbuf_get_from_surface (surface, 0, 0,
+                                        cairo_image_surface_get_width (surface),
+                                        cairo_image_surface_get_height (
+                                          surface))));
+    }
+  return GTK_WIDGET (old_widget);
+}
+
+static void
+eg_remove_widget_from_map (ptrdiff_t idx)
+{
+  if (idx < id_to_widget.max_size && id_to_widget.widgets[idx] != 0)
+    {
+      id_to_widget.widgets[idx] = 0;
+      --id_to_widget.used;
+    }
+}
+
+Lisp_Object
+eg_get_font_family (bool mono)
+{
+  GtkWidget *test_widget = gtk_text_view_new ();
+  gtk_text_view_set_monospace (GTK_TEXT_VIEW (test_widget), mono);
+  PangoContext *ctx = gtk_widget_create_pango_context (test_widget);
+  PangoFontDescription *desc = pango_context_get_font_description (ctx);
+  Lisp_Object name
+    = build_string_from_utf8 (pango_font_description_get_family (desc));
+  g_object_unref (ctx);
+  gtk_widget_destroy (test_widget);
+  return name;
+}
+
+#pragma GCC diagnostic ignored "-Wdouble-promotion"
+bool
+egtk_check_special_colors (struct frame *f, const char *color_name,
+                           Emacs_Color *color)
+{
+  if (!strcmp (GTK_FOREGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_text_view_new ();
+      GtkStyleContext *gsc = gtk_widget_get_style_context (tsc);
+      if (!gsc)
+	return false;
+      GdkRGBA col;
+      gtk_style_context_get_color (gsc, &col);
+      char buf[sizeof "rgb(255,255,255)"];
+      unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0),
+                    b = round (col.blue * 255.0);
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  else if (!strcmp (GTK_BACKGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_text_view_new ();
+      GtkStyleContext *gsc
+        = gtk_widget_get_style_context (tsc);
+      if (!gsc)
+        return false;
+#if CAIRO_HAVE_RGBA128F
+      cairo_surface_t *sf =
+	cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, 10, 10);
+#else
+      cairo_surface_t *sf =
+	cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
+#endif
+      cairo_t *cr = cairo_create (sf);
+      gtk_render_background (gsc, cr, 0, 0, 10, 10);
+      cairo_surface_flush (sf);
+#if CAIRO_HAVE_RGBA128F
+      float *rgb = (float *) cairo_image_surface_get_data (sf);
+#else
+      guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf);
+#endif
+      char buf[sizeof "rgb(255,255,255)"];
+#if CAIRO_HAVE_RGBA128F
+      guint8 r = 255 * rgb[0], g = 255 * rgb[1],
+	     b = 255 * rgb[2], a = 255 * rgb[3];
+#else
+#if BYTE_ORDER != G_LITTLE_ENDIAN
+      guint8 a = rgb[16 + 0], r = rgb[16 + 1], g = rgb[8 + 2], b = rgb[8 + 3];
+#else
+      guint8 a = rgb[16 + 3], r = rgb[16 + 2], g = rgb[16 + 1], b = rgb[16 + 0];
+#endif
+#endif
+      if (!a)
+        {
+          r = 255;
+          g = 255;
+          b = 255;
+        }
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      cairo_destroy (cr);
+      cairo_surface_destroy (sf);
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  else if (!strcmp (GTK_MFOREGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_text_view_new ();
+      GtkStyleContext *gsc = gtk_widget_get_style_context (tsc);
+      if (!gsc)
+        return false;
+      GdkRGBA col;
+      gtk_style_context_get_color (gsc, &col);
+      char buf[sizeof "rgb(255,255,255)"];
+      unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0),
+                    b = round (col.blue * 255.0);
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  else if (!strcmp (GTK_MBACKGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_header_bar_new ();
+      GtkStyleContext *gsc = gtk_widget_get_style_context (tsc);
+      if (!gsc)
+        return false;
+#if CAIRO_HAVE_RGBA128F
+      cairo_surface_t *sf
+        = cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, 10, 10);
+#else
+      cairo_surface_t *sf
+        = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
+#endif
+      cairo_t *cr = cairo_create (sf);
+      gtk_render_background (gsc, cr, 0, 0, 10, 10);
+      cairo_surface_flush (sf);
+#if CAIRO_HAVE_RGBA128F
+      float *rgb = (float *) cairo_image_surface_get_data (sf);
+#else
+      guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf);
+#endif
+      char buf[sizeof "rgb(255,255,255)"];
+#if CAIRO_HAVE_RGBA128F
+      guint8 r = 255 * rgb[4 + 0], g = 255 * rgb[4 + 1], b = 255 * rgb[4 + 2],
+             a = 255 * rgb[4 + 3];
+#else
+#if BYTE_ORDER != G_LITTLE_ENDIAN
+      guint8 a = rgb[16 + 0], r = rgb[16 + 1], g = rgb[8 + 2], b = rgb[8 + 3];
+#else
+      guint8 a = rgb[16 + 3], r = rgb[16 + 2], g = rgb[16 + 1], b = rgb[16 + 0];
+#endif
+#endif
+      if (!a)
+        {
+          r = 255;
+          g = 255;
+          b = 255;
+        }
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      cairo_destroy (cr);
+      cairo_surface_destroy (sf);
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  else if (!strcmp (GTK_LFOREGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_link_button_new ("https://foo.bar");
+      GtkStyleContext *gsc = gtk_widget_get_style_context (tsc);
+      gtk_style_context_set_state (gsc, gtk_style_context_get_state (gsc)
+                                          | GTK_STATE_FLAG_LINK
+                                          | GTK_STATE_FLAG_FOCUSED
+                                          | GTK_STATE_FLAG_FOCUS_VISIBLE);
+      if (!gsc)
+        return false;
+      GdkRGBA col;
+      gtk_style_context_get_color (gsc, &col);
+      char buf[sizeof "rgb(255,255,255)"];
+      unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0),
+                    b = round (col.blue * 255.0);
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  else if (!strcmp (GTK_SLFOREGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_link_button_new ("https://foo.bar");
+      GtkStyleContext *gsc = gtk_widget_get_style_context (tsc);
+      gtk_style_context_set_state (gsc, gtk_style_context_get_state (gsc)
+                                          | GTK_STATE_FLAG_LINK
+                                          | GTK_STATE_FLAG_FOCUSED
+                                          | GTK_STATE_FLAG_VISITED
+                                          | GTK_STATE_FLAG_FOCUS_VISIBLE);
+      if (!gsc)
+        return false;
+      GdkRGBA col;
+      gtk_style_context_get_color (gsc, &col);
+      char buf[sizeof "rgb(255,255,255)"];
+      unsigned char r = round (col.red * 255.0), g = round (col.green * 255.0),
+                    b = round (col.blue * 255.0);
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  else if (!strcmp (GTK_TBACKGROUND_COLOR, color_name))
+    {
+      GtkWidget *tsc = gtk_notebook_new ();
+      gtk_widget_add_css_class (tsc, "tab");
+      GtkStyleContext *gsc = gtk_widget_get_style_context (tsc);
+      gtk_style_context_set_state (gsc, GTK_STATE_FLAG_INSENSITIVE);
+#if CAIRO_HAVE_RGBA128F
+      cairo_surface_t *sf
+        = cairo_image_surface_create (CAIRO_FORMAT_RGBA128F, 10, 10);
+#else
+      cairo_surface_t *sf
+        = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
+#endif
+      cairo_t *cr = cairo_create (sf);
+      gtk_render_background (gsc, cr, 0, 0, 10, 10);
+      cairo_surface_flush (sf);
+#if CAIRO_HAVE_RGBA128F
+      float *rgb = (float *) cairo_image_surface_get_data (sf);
+#else
+      guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf);
+#endif
+      char buf[sizeof "rgb(255,255,255)"];
+#if CAIRO_HAVE_RGBA128F
+      guint8 r = 255 * rgb[0], g = 255 * rgb[1], b = 255 * rgb[2],
+             a = 255 * rgb[3];
+#else
+#if BYTE_ORDER != G_LITTLE_ENDIAN
+      guint8 a = rgb[4 + 0], r = rgb[4 + 1], g = rgb[4 + 2], b = rgb[4 + 3];
+#else
+      guint8 a = rgb[4 + 3], r = rgb[4 + 2], g = rgb[4 + 1], b = rgb[4 + 0];
+#endif
+#endif
+      if (!a)
+        {
+          r = 255;
+          g = 255;
+          b = 255;
+        }
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+      sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+      cairo_destroy (cr);
+      cairo_surface_destroy (sf);
+      gtk_widget_destroy (tsc);
+      return pgtk_parse_color (buf, color) != 0;
+    }
+  bool success_p = 0;
+  bool get_bg = strcmp ("gtk_selection_bg_color", color_name) == 0;
+  bool get_fg = !get_bg && strcmp ("gtk_selection_fg_color", color_name) == 0;
+  bool_bf qn = 0;
+  if (!(get_bg || get_fg))
+    return success_p;
+
+  block_input ();
+  {
+    GtkWidget *gcs = gtk_text_view_new ();
+    GtkStyleContext *gsty
+      = gtk_widget_get_style_context (gcs);
+    GdkRGBA col;
+    char buf[sizeof "rgb(255,255,255)"];
+    if (get_fg)
+      {
+	qn = 1;
+	int os = gtk_style_context_get_state (gsty);
+	gtk_style_context_set_state (gsty, os | GTK_STATE_FLAG_SELECTED |
+				     GTK_STATE_FLAG_FOCUSED);
+	gtk_style_context_get_color (gsty, &col);
+	gtk_style_context_set_state (gsty, os);
+      }
+    else
+      {
+	block_input ();
+	int os = gtk_style_context_get_state (gsty);
+        gtk_style_context_set_state (gsty, os | GTK_STATE_FLAG_SELECTED
+                                             | GTK_STATE_FLAG_FOCUSED);
+        cairo_surface_t *sf
+          = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
+        cairo_t *cr = cairo_create (sf);
+        gtk_render_background (gsty, cr, 0, 0, 10, 10);
+        cairo_surface_flush (sf);
+        guint8 *rgb = (guint8 *) cairo_image_surface_get_data (sf);
+#if BYTE_ORDER != G_LITTLE_ENDIAN
+        guint8 a = rgb[16 + 0], r = rgb[16 + 1], g = rgb[16 + 2], b = rgb[16 + 3];
+#else
+	guint8 a = rgb[16 + 3], r = rgb[16 + 2], g = rgb[16 + 1], b = rgb[16 + 0];
+#endif
+        col.red = r;
+        col.blue = b;
+        col.green = g;
+        col.alpha = a;
+        gtk_style_context_set_state (gsty, os);
+        cairo_destroy (cr);
+        cairo_surface_destroy (sf);
+        unblock_input ();
+      }
+
+    unsigned short r = round (!qn ? col.red : col.red * 255.0),
+      g = round (!qn ? col.green : col.green * 255.0),
+      b = round (!qn ? col.blue : col.blue * 255.0);
+#pragma GCC diagnostic ignored "-Wformat-overflow"
+    sprintf (buf, "rgb(%03d,%03d,%03d)", r, g, b);
+#pragma GCC diagnostic push
+
+    success_p = pgtk_parse_color (buf, color) != 0;
+    gtk_widget_destroy (gcs);
+  }
+  unblock_input ();
+  return success_p;
+}
+#pragma GCC diagnostic push
+
+void
+egtk_frame_restack (struct frame *f1, struct frame *f2, bool above)
+{
+  block_input ();
+  unblock_input ();
+}
+
+void
+egtk_set_skip_taskbar (struct frame *f, Lisp_Object skip_taskbar)
+{
+#ifdef HAVE_GDK_X11
+  if (FRAME_PARENT_FRAME (f))
+    return;
+  if (GDK_IS_X11_DISPLAY (FRAME_X_DISPLAY (f)))
+    gdk_x11_surface_set_skip_taskbar_hint
+      (GDK_X11_SURFACE (EGTK_WIDGET_GET_WINDOW (FRAME_GTK_OUTER_WIDGET (f))),
+       !NILP (skip_taskbar));
+#endif
+}
+
+void
+egtk_set_no_focus_on_map (struct frame *f, Lisp_Object no_focus_on_map)
+{
+  block_input ();
+  TODO;
+  unblock_input ();
+}
+
+void
+egtk_set_no_accept_focus (struct frame *f, Lisp_Object no_accept_focus)
+{
+  block_input ();
+  FRAME_NO_ACCEPT_FOCUS (f) = !NILP (no_accept_focus);
+  unblock_input ();
+}
+
+static GtkPrintSettings *print_settings = NULL;
+static GtkPageSetup *page_setup = NULL;
+
+void
+egtk_page_setup_dialog (void)
+{
+  GtkPageSetup *new_page_setup = NULL;
+
+  if (print_settings == NULL)
+    print_settings = gtk_print_settings_new ();
+  new_page_setup
+    = gtk_print_run_page_setup_dialog (NULL, page_setup, print_settings);
+  if (page_setup)
+    g_object_unref (page_setup);
+  page_setup = new_page_setup;
+}
+
+void
+egtk_set_override_redirect (struct frame *f, Lisp_Object override_redirect)
+{
+}
+
+bool
+egtk_hide_tooltip (struct frame *f)
+{
+  block_input ();
+  if (FRAME_X_OUTPUT (f)->ttip_popover)
+    {
+      gtk_widget_destroy (GTK_WIDGET (FRAME_X_OUTPUT (f)->ttip_popover));
+      FRAME_X_OUTPUT (f)->ttip_popover = NULL;
+    }
+  unblock_input ();
+  return true;
+}
+
+void
+egtk_remove_scroll_bar (struct frame *f, ptrdiff_t scrollbar_id)
+{
+  GtkWidget *w = eg_get_widget_from_map (scrollbar_id);
+  if (w)
+    {
+      gtk_widget_destroy (w);
+    }
+  eg_remove_widget_from_map (scrollbar_id);
+}
+
+void
+egtk_update_scrollbar_pos (struct frame *f, ptrdiff_t scrollbar_id, int top,
+                           int left, int width, int height)
+{
+  GtkWidget *sb = eg_get_widget_from_map (scrollbar_id);
+  GdkRectangle *rect = g_object_get_data (G_OBJECT (sb), EG_OVERLAY_ALLOC);
+  rect->y = top;
+  rect->x = left;
+  rect->width = width;
+  rect->height = height;
+
+  gtk_widget_queue_resize (gtk_widget_get_parent (sb));
+}
+
+void
+egtk_update_horizontal_scrollbar_pos (struct frame *f, ptrdiff_t scrollbar_id,
+                                      int top, int left, int width, int height)
+{
+  GtkWidget *sb = eg_get_widget_from_map (scrollbar_id);
+  GdkRectangle *rect = g_object_get_data (G_OBJECT (sb), EG_OVERLAY_ALLOC);
+  rect->y = top;
+  rect->x = left;
+  rect->width = width;
+  rect->height = height;
+
+  gtk_widget_queue_resize (gtk_widget_get_parent (sb));
+}
+
+static int
+int_gtk_range_get_value (GtkRange *range)
+{
+  return gtk_range_get_value (range);
+}
+
+void
+egtk_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar, int portion,
+                                   int position, int whole)
+{
+  GtkWidget *wscroll = eg_get_widget_from_map (bar->x_window);
+  wscroll = gtk_widget_get_first_child (wscroll);
+  if (!wscroll)
+    emacs_abort ();
+  struct frame *f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window)));
+
+  if (wscroll && bar->dragging == -1)
+    {
+      GtkAdjustment *adj;
+      gdouble shown;
+      gdouble top;
+      int size, value;
+      int old_size;
+      int new_step;
+      bool changed = 0;
+
+      adj = gtk_range_get_adjustment (GTK_RANGE (wscroll));
+
+      if (scroll_bar_adjust_thumb_portion_p)
+        {
+          /* We do the same as for MOTIF in xterm.c, use 30 chars per
+             line rather than the real portion value.  This makes the
+             thumb less likely to resize and that looks better.  */
+          portion = WINDOW_TOTAL_LINES (XWINDOW (bar->window)) * 30;
+
+          /* When the thumb is at the bottom, position == whole.
+             So we need to increase `whole' to make space for the thumb.  */
+          whole += portion;
+        }
+
+      if (whole <= 0)
+        top = 0, shown = 1;
+      else
+        {
+          top = (gdouble) position / whole;
+          shown = (gdouble) portion / whole;
+        }
+      size = clip_to_bounds (1, shown * XG_SB_RANGE, XG_SB_RANGE);
+      value = clip_to_bounds (XG_SB_MIN, top * XG_SB_RANGE, XG_SB_MAX - size);
+
+      /* Assume all lines are of equal size.  */
+      new_step = size / max (1, FRAME_LINES (f));
+
+      old_size = gtk_adjustment_get_page_size (adj);
+      if (old_size != size)
+        {
+          int old_step = gtk_adjustment_get_step_increment (adj);
+          if (old_step != new_step)
+            {
+              gtk_adjustment_set_page_size (adj, size);
+              gtk_adjustment_set_step_increment (adj, new_step);
+              /* Assume a page increment is about 95% of the page size  */
+              gtk_adjustment_set_page_increment (adj, size - size / 20);
+              changed = 1;
+            }
+        }
+
+      if (changed || int_gtk_range_get_value (GTK_RANGE (wscroll)) != value)
+        {
+          block_input ();
+
+          /* gtk_range_set_value invokes the callback.  Set
+             ignore_gtk_scrollbar to make the callback do nothing  */
+          egtk_ignore_gtk_scrollbar = 1;
+
+          if (int_gtk_range_get_value (GTK_RANGE (wscroll)) != value)
+            gtk_adjustment_set_value (adj, value);
+          egtk_ignore_gtk_scrollbar = 0;
+
+          unblock_input ();
+        }
+    }
+}
+
+void
+egtk_initialize (void)
+{
+  default_display = NULL;
+  egtk_ignore_gtk_scrollbar = 0;
+  egtk_menu_cb_list.prev = egtk_menu_cb_list.next = egtk_menu_item_cb_list.prev
+    = egtk_menu_item_cb_list.next = 0;
+
+  id_to_widget.max_size = id_to_widget.used = 0;
+  id_to_widget.widgets = 0;
+  update_theme_scrollbar_width ();
+  update_theme_scrollbar_height ();
+
+  atab = g_hash_table_new (NULL, NULL);
+
+#ifdef HAVE_FREETYPE
+  x_last_font_name = NULL;
+#endif
+}
+
+Lisp_Object
+egtk_get_page_setup (void)
+{
+  Lisp_Object orientation_symbol;
+
+  if (page_setup == NULL)
+    page_setup = gtk_page_setup_new ();
+
+  switch (gtk_page_setup_get_orientation (page_setup))
+    {
+    case GTK_PAGE_ORIENTATION_PORTRAIT:
+      orientation_symbol = Qportrait;
+      break;
+    case GTK_PAGE_ORIENTATION_LANDSCAPE:
+      orientation_symbol = Qlandscape;
+      break;
+    case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
+      orientation_symbol = Qreverse_portrait;
+      break;
+    case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
+      orientation_symbol = Qreverse_landscape;
+      break;
+    default:
+      eassume (false);
+    }
+
+#define GETSETUP(f) make_float (f (page_setup, GTK_UNIT_POINTS))
+  return list (Fcons (Qorientation, orientation_symbol),
+               Fcons (Qwidth, GETSETUP (gtk_page_setup_get_page_width)),
+               Fcons (Qheight, GETSETUP (gtk_page_setup_get_page_height)),
+               Fcons (Qleft_margin, GETSETUP (gtk_page_setup_get_left_margin)),
+               Fcons (Qright_margin,
+                      GETSETUP (gtk_page_setup_get_right_margin)),
+               Fcons (Qtop_margin, GETSETUP (gtk_page_setup_get_top_margin)),
+               Fcons (Qbottom_margin,
+                      GETSETUP (gtk_page_setup_get_bottom_margin)));
+#undef GETSETUP
+}
+
+static void
+draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr,
+           gpointer user_data)
+{
+  Lisp_Object frames = *((Lisp_Object *) user_data);
+  struct frame *f = XFRAME (Fnth (make_fixnum (page_nr), frames));
+  cairo_t *cr = gtk_print_context_get_cairo_context (context);
+
+  pgtk_cr_draw_frame (cr, f);
+  cairo_save (cr);
+}
+static GtkWindow *
+locate_nonchild_window (struct frame *child)
+{
+  while (FRAME_PARENT_FRAME (child))
+    child = FRAME_PARENT_FRAME (child);
+  return GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child));
+}
+void
+egtk_print_frames_dialog (Lisp_Object frames)
+{
+  GtkPrintOperation *print;
+  GtkPrintOperationResult res;
+
+  print = gtk_print_operation_new ();
+  if (print_settings != NULL)
+    gtk_print_operation_set_print_settings (print, print_settings);
+  if (page_setup != NULL)
+    gtk_print_operation_set_default_page_setup (print, page_setup);
+  gtk_print_operation_set_n_pages (print, list_length (frames));
+  g_signal_connect (print, "draw-page", G_CALLBACK (draw_page), &frames);
+  res
+    = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+                               locate_nonchild_window (XFRAME (XCAR (frames))),
+                               NULL);
+  if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
+    {
+      if (print_settings != NULL)
+        g_object_unref (print_settings);
+      print_settings
+        = g_object_ref (gtk_print_operation_get_print_settings (print));
+    }
+  g_object_unref (print);
+}
+
+bool
+egtk_prepare_tooltip (struct frame *f, Lisp_Object string, int *width,
+                      int *height)
+{
+  FRAME_X_OUTPUT (f)->ttip_label = string;
+  return TRUE;
+}
+
+static GtkWidget *
+create_popover (GtkWidget *parent, GtkWidget *child)
+{
+  GtkWidget *popover;
+
+  popover = xeg_ttip_popover_new ();
+  gtk_widget_set_parent (popover, parent);
+  gtk_container_add (GTK_CONTAINER (popover), child);
+  gtk_widget_set_margin_start (child, 6);
+  gtk_widget_set_margin_end (child, 6);
+  gtk_widget_set_margin_top (child, 6);
+  gtk_widget_set_margin_bottom (child, 6);
+  gtk_widget_show (child);
+  gtk_popover_set_autohide (GTK_POPOVER (popover), false);
+  gtk_widget_set_can_focus (popover, false);
+
+  return popover;
+}
+
+void
+egtk_show_tooltip (struct frame *f, int root_x, int root_y)
+{
+  block_input ();
+  GtkWidget *wg = GTK_WIDGET (gtk_label_new
+			      (SSDATA
+			       (ENCODE_UTF_8
+				(FRAME_X_OUTPUT (f)->ttip_label))));
+  gtk_widget_add_css_class (wg, "monospace");
+#define PO FRAME_X_OUTPUT (f)->ttip_popover
+  gtk_window_set_focus (locate_nonchild_window (f), FRAME_GTK_WIDGET (f));
+  PO = GTK_POPOVER (create_popover (FRAME_GTK_WIDGET (f), wg));
+  GdkRectangle rect;
+  rect.x = root_x;
+  rect.y = root_y;
+  rect.width = 0;
+  rect.height = 0;
+  gtk_popover_set_pointing_to (PO, &rect);
+  gtk_popover_set_has_arrow (PO, false);
+  gtk_widget_show (GTK_WIDGET (PO));
+  gtk_window_set_focus (locate_nonchild_window (f), FRAME_GTK_WIDGET (f));
+#undef PO
+  unblock_input ();
+}
+
+static char *
+get_utf8_string (const char *str)
+{
+  char *utf8_str;
+
+  if (!str)
+    return NULL;
+
+  /* If not UTF-8, try current locale.  */
+  if (!g_utf8_validate (str, -1, NULL))
+    utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0);
+  else
+    return g_strdup (str);
+
+  if (!utf8_str)
+    {
+      /* Probably some control characters in str.  Escape them. */
+      ptrdiff_t len;
+      ptrdiff_t nr_bad = 0;
+      size_t bytes_read;
+      size_t bytes_written;
+      unsigned char *p = (unsigned char *) str;
+      char *cp, *up;
+      GError *err = NULL;
+
+      while (!(cp = g_locale_to_utf8 ((char *) p, -1, &bytes_read,
+                                      &bytes_written, &err))
+             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+        {
+          ++nr_bad;
+          p += bytes_written + 1;
+          g_error_free (err);
+          err = NULL;
+        }
+
+      if (err)
+        {
+          g_error_free (err);
+          err = NULL;
+        }
+      if (cp)
+        g_free (cp);
+
+      len = strlen (str);
+      ptrdiff_t alloc;
+      if (INT_MULTIPLY_WRAPV (nr_bad, 4, &alloc)
+          || INT_ADD_WRAPV (len + 1, alloc, &alloc) || SIZE_MAX < alloc)
+        memory_full (SIZE_MAX);
+      up = utf8_str = xmalloc (alloc);
+      p = (unsigned char *) str;
+
+      while (!(cp = g_locale_to_utf8 ((char *) p, -1, &bytes_read,
+                                      &bytes_written, &err))
+             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+        {
+          memcpy (up, p, bytes_written);
+          up += bytes_written;
+          up += sprintf (up, "\\%03o", p[bytes_written]);
+          p += bytes_written + 1;
+          g_error_free (err);
+          err = NULL;
+        }
+
+      if (cp)
+        {
+          strcpy (up, cp);
+          g_free (cp);
+        }
+      if (err)
+        {
+          g_error_free (err);
+          err = NULL;
+        }
+    }
+  return utf8_str;
+}
+
+egtk_menu_cb_data *
+make_cl_data (egtk_menu_cb_data *cl_data, struct frame *f,
+              GCallback highlight_cb)
+{
+  if (!cl_data)
+    {
+      cl_data = xmalloc (sizeof *cl_data);
+      cl_data->f = f;
+      cl_data->menu_bar_vector = f->menu_bar_vector;
+      cl_data->menu_bar_items_used = f->menu_bar_items_used;
+      cl_data->highlight_cb = highlight_cb;
+      cl_data->ref_count = 0;
+
+      egtk_list_insert (&egtk_menu_cb_list, &cl_data->ptrs);
+    }
+
+  cl_data->ref_count++;
+
+  return cl_data;
+}
+
+static void
+egtk_list_remove (egtk_list_node *list, egtk_list_node *node)
+{
+  egtk_list_node *list_start = list->next;
+  if (node == list_start)
+    {
+      list->next = node->next;
+      if (list->next) list->next->prev = 0;
+    }
+  else
+    {
+      node->prev->next = node->next;
+      if (node->next) node->next->prev = node->prev;
+    }
+}
+
+void
+unref_cl_data (egtk_menu_cb_data *cl_data)
+{
+  if (cl_data && cl_data->ref_count > 0)
+    {
+      cl_data->ref_count--;
+      if (cl_data->ref_count < 1)
+	{
+	  egtk_list_remove (&egtk_menu_cb_list, &cl_data->ptrs);
+	  xfree (cl_data);
+	}
+    }
+}
+
+
+GtkPopover *
+build_mm_from_wv (widget_value *wv, struct frame *f, GCallback select_cb,
+                  GCallback deactivate_cb, GCallback highlight_cb,
+                  bool pop_up_p, bool menu_bar_p, egtk_menu_cb_data *cl_data,
+                  const char *name, GtkContainer *box, GtkPopover *popover,
+                  GdkRectangle *posrect)
+{
+  return build_menu_from_widget_value (wv, cl_data, select_cb, deactivate_cb,
+                                       highlight_cb, f, menu_bar_p, false);
+}
+
+int
+calculate_child_frame_distance_y (struct frame *f)
+{
+  GtkWidget *ncw = GTK_WIDGET (locate_nonchild_window (f));
+  gint y;
+  gtk_widget_translate_coordinates (FRAME_GTK_WIDGET (f), ncw, 0, 0, NULL, &y);
+  return y;
+}
+
+int
+calculate_child_frame_distance_x (struct frame *f)
+{
+  GtkWidget *ncw = GTK_WIDGET (locate_nonchild_window (f));
+  gint x;
+  gtk_widget_translate_coordinates (FRAME_GTK_WIDGET (f), ncw, 0, 0, &x, NULL);
+  return x;
+}
+
+void
+eg_notify_frame_header_bar_state_changed (struct frame *f)
+{
+  if (FRAME_PGTK_P (f) && !FRAME_PARENT_FRAME (f))
+    {
+      if (FRAME_DISPLAY_HEADER_BAR (f))
+      {
+	gtk_window_set_titlebar (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+				 GTK_WIDGET (FRAME_GTK_HEADER_BAR (f)));
+	g_object_unref (FRAME_GTK_HEADER_BAR (f));
+      }
+    else
+      {
+	g_object_ref (G_OBJECT (FRAME_GTK_HEADER_BAR (f)));
+	gtk_window_set_titlebar (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), NULL);
+      }
+    }
+}
+
+Lisp_Object
+eg_get_divider_width (void)
+{
+  GtkSeparator *sep = GTK_SEPARATOR (gtk_separator_new (GTK_ORIENTATION_VERTICAL));
+  int foo, bar, quux, width;
+  gtk_widget_measure (GTK_WIDGET (sep), GTK_ORIENTATION_HORIZONTAL, 0, &foo, &width, &bar, &quux);
+  gtk_widget_destroy (GTK_WIDGET (sep));
+
+  return make_fixnum (width);
+}
+
+Lisp_Object
+eg_get_caret_px_width (void)
+{
+  GtkLabel *tv = GTK_LABEL (gtk_label_new ("abcdefghijk"));
+  gtk_label_select_region (tv, 3, 2);
+  PangoRectangle sp, wp;
+  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (tv), "abcdefghijk");
+  pango_layout_get_cursor_pos (layout, 2, &sp, &wp);
+  double wd = PANGO_PIXELS (sp.height) * 0.04 + 1;
+  g_object_unref (layout);
+  gtk_widget_destroy (GTK_WIDGET (tv));
+  return make_fixnum (wd);
+}
+
+void
+eg_unparent_frame (struct frame *f)
+{
+  if (GTK_IS_WINDOW (FRAME_GTK_OUTER_WIDGET (f)))
+    return;
+  block_input ();
+  GtkWidget *wzchild = gtk_widget_get_first_child (FRAME_GTK_OUTER_WIDGET (f));
+  g_object_ref (wzchild);
+  gtk_container_remove (GTK_CONTAINER (FRAME_GTK_OUTER_WIDGET (f)),
+			wzchild);
+  gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (f));
+  GtkWindow *window = GTK_WINDOW (emacs_window_new ());
+  g_object_unref (FRAME_GTK_HEADER_BAR (f));
+  GtkHeaderBar *header_bar = GTK_HEADER_BAR (gtk_header_bar_new ());
+  gtk_container_add (GTK_CONTAINER (window), wzchild);
+  gtk_window_resize (window, FRAME_PIXEL_WIDTH (f),
+		     FRAME_PIXEL_HEIGHT (f));
+  gtk_widget_set_visible (GTK_WIDGET (window), true);
+  gtk_widget_map (GTK_WIDGET (window));
+  gtk_widget_realize (GTK_WIDGET (window));
+  FRAME_GTK_OUTER_WIDGET (f) = GTK_WIDGET (window);
+  FRAME_GTK_HEADER_BAR (f) = header_bar;
+  gtk_header_bar_set_show_title_buttons (header_bar, true);
+  gtk_window_set_titlebar (window, GTK_WIDGET (header_bar));
+  eg_notify_frame_header_bar_state_changed (f);
+  gtk_header_bar_set_subtitle (header_bar, SSDATA (f->name));
+  gtk_window_set_focus (window, FRAME_GTK_WIDGET (f));
+  GListModel *conts = gtk_widget_observe_controllers (GTK_WIDGET (window));
+  GtkEventController *mba = NULL;
+  for (int i = 0; i < g_list_model_get_n_items (conts); ++i)
+    if (gtk_event_controller_get_name (g_list_model_get_item (conts, i)) &&
+	!strcmp (gtk_event_controller_get_name (g_list_model_get_item (conts, i)),
+		 "gtk-window-menubar-accel"))
+      mba = g_list_model_get_item (conts, i);
+  if (mba)
+    gtk_widget_remove_controller (GTK_WIDGET (window), mba);
+  unblock_input ();
+}
+#endif
+#undef __GI_SCANNER__
+#undef TODO
diff --git a/src/pgtksubr.h b/src/pgtksubr.h
new file mode 100644
index 0000000000..6a328c8004
--- /dev/null
+++ b/src/pgtksubr.h
@@ -0,0 +1,282 @@
+/* Definitions and headers for GTK widgets.
+
+Copyright (C) 2003-2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef PGTKSUBR_H
+#define PGTKSUBR_H
+
+#include "config.h"
+
+#ifdef USE_GTK
+
+#include <gtk/gtk.h>
+#include "../lwlib/lwlib-widget.h"
+#include "pgtkterm.h"
+#include "menu.h"
+#define EVENT GdkEvent
+
+#include "gtkconfig.h"
+
+/* Minimum and maximum values used for GTK scroll bars  */
+
+#define XG_SB_MIN 1
+#define XG_SB_MAX 100
+#define XG_SB_RANGE (XG_SB_MAX - XG_SB_MIN)
+#define YG_SB_MIN 1
+#define YG_SB_MAX 100
+#define YG_SB_RANGE (YG_SB_MAX - YG_SB_MIN)
+
+/* Key for data that is valid for menus and scroll bars in a frame  */
+#define XG_FRAME_DATA "emacs_frame"
+
+/* Key for data that menu items hold.  */
+#define XG_ITEM_DATA "emacs_menuitem"
+
+#define EGTK_WIDGET_GET_WINDOW(widget) \
+  (gtk_native_get_surface (gtk_widget_get_native (widget)))
+
+#define EG_OVERLAY_ALLOC "eg-override-alloc"
+
+#define XG_TOOL_BAR_LAST_MODIFIER "tool-bar-last-mod"
+#define XG_TOOL_BAR_STOCK_NAME "tool-bar-stock-name"
+
+/* This is a list node in a generic list implementation.  */
+typedef struct egtk_list_node_
+{
+  struct egtk_list_node_ *prev;
+  struct egtk_list_node_ *next;
+} egtk_list_node;
+
+/* This structure is the callback data that is shared for menu items.
+   We need to keep it separate from the frame structure due to
+   detachable menus.  The data in the frame structure is only valid while
+   the menu is popped up.  This structure is kept around as long as
+   the menu is.  */
+typedef struct egtk_menu_cb_data_
+{
+  egtk_list_node ptrs;
+
+  struct frame *f;
+  Lisp_Object menu_bar_vector;
+  int menu_bar_items_used;
+  GCallback highlight_cb;
+  int ref_count;
+} egtk_menu_cb_data;
+
+/* This structure holds callback information for each individual menu item.  */
+typedef struct egtk_menu_item_cb_data_
+{
+  egtk_list_node ptrs;
+
+  gulong select_id;
+  Lisp_Object help;
+  gpointer call_data;
+  egtk_menu_cb_data *cl_data;
+
+} egtk_menu_item_cb_data;
+
+G_BEGIN_DECLS
+
+typedef struct _EmacsResizableDrawingArea
+{
+  GtkDrawingArea parent_instance;
+  GtkAccessible *accessible;
+  GdkRGBA *background_color;
+  GtkPopover *nrc_popover;
+  GHashTable *action_map;
+  GActionGroup *action_group;
+  struct frame *f;
+} EmacsResizableDrawingArea;
+
+struct _EmacsResizableDrawingAreaClass
+{
+  GtkDrawingAreaClass parent_class;
+};
+
+extern GtkWidget *emacs_resizable_drawing_area_new (struct frame *f);
+extern void emacs_resizable_drawing_area_set_background (EmacsResizableDrawingArea *area,
+							 unsigned long color);
+
+G_END_DECLS
+
+extern void run_main_loop_iteration (void);
+
+extern char *egtk_get_file_name (struct frame *f, char *prompt,
+                                 char *default_filename, bool mustmatch_p,
+                                 bool only_dir_p);
+
+extern Lisp_Object egtk_get_font (struct frame *f, const char *);
+
+
+extern void egtk_create_scroll_bar (struct frame *f, struct scroll_bar *bar,
+                                    GCallback scroll_callback,
+                                    GCallback end_callback,
+                                    const char *scroll_bar_name);
+extern void egtk_create_horizontal_scroll_bar (struct frame *f,
+                                               struct scroll_bar *bar,
+                                               GCallback scroll_callback,
+                                               GCallback end_callback,
+                                               const char *scroll_bar_name);
+extern void egtk_remove_scroll_bar (struct frame *f, ptrdiff_t scrollbar_id);
+
+extern void egtk_update_scrollbar_pos (struct frame *f, ptrdiff_t scrollbar_id,
+                                       int top, int left, int width,
+                                       int height);
+extern void egtk_update_horizontal_scrollbar_pos (struct frame *f,
+                                                  ptrdiff_t scrollbar_id,
+                                                  int top, int left, int width,
+                                                  int height);
+
+extern void egtk_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar,
+                                               int portion, int position,
+                                               int whole);
+extern void egtk_set_toolkit_horizontal_scroll_bar_thumb (struct scroll_bar *bar, int portion,
+							  int position, int whole);
+
+extern int egtk_get_default_scrollbar_width (struct frame *f);
+extern int egtk_get_default_scrollbar_height (struct frame *f);
+
+extern void update_frame_tool_bar (struct frame *f);
+extern void free_frame_tool_bar (struct frame *f);
+extern void egtk_change_toolbar_position (struct frame *f, Lisp_Object pos);
+
+extern void egtk_frame_resized (struct frame *f, int pixelwidth,
+                                int pixelheight);
+extern void egtk_frame_set_char_size (struct frame *f, int width, int height);
+
+extern int egtk_get_scale (struct frame *f);
+extern void egtk_display_open (char *display_name, GdkDisplay **dpy);
+extern void egtk_display_close (GdkDisplay *gdpy);
+extern GdkCursor *egtk_create_default_cursor (GdkDisplay *gdpy);
+
+extern bool egtk_create_frame_widgets (struct frame *f);
+extern void egtk_free_frame_widgets (struct frame *f);
+extern void egtk_set_background_color (struct frame *f, unsigned long bg);
+extern bool egtk_check_special_colors (struct frame *f, const char *color_name,
+                                       Emacs_Color *color);
+
+extern void egtk_set_undecorated (struct frame *f, Lisp_Object undecorated);
+extern void egtk_frame_restack (struct frame *f1, struct frame *f2, bool above);
+extern void egtk_set_skip_taskbar (struct frame *f, Lisp_Object skip_taskbar);
+extern void egtk_set_no_focus_on_map (struct frame *f,
+                                      Lisp_Object no_focus_on_map);
+extern void egtk_set_no_accept_focus (struct frame *f,
+                                      Lisp_Object no_accept_focus);
+extern void egtk_set_override_redirect (struct frame *f,
+                                        Lisp_Object override_redirect);
+
+extern bool egtk_prepare_tooltip (struct frame *f, Lisp_Object string,
+                                  int *width, int *height);
+extern void egtk_show_tooltip (struct frame *f, int root_x, int root_y);
+extern bool egtk_hide_tooltip (struct frame *f);
+
+extern void egtk_page_setup_dialog (void);
+extern Lisp_Object egtk_get_page_setup (void);
+extern void egtk_print_frames_dialog (Lisp_Object);
+
+
+/* Mark all callback data that are Lisp_object:s during GC.  */
+extern void egtk_mark_data (void);
+
+/* Initialize GTK specific parts.  */
+extern void egtk_initialize (void);
+
+/* Setting scrollbar values invokes the callback.  Use this variable
+   to indicate that the callback should do nothing.  */
+extern bool egtk_ignore_gtk_scrollbar;
+
+extern bool egtk_gtk_initialized;
+
+extern GtkWidget *egtk_get_image_for_pixmap (struct frame *f, struct image *img,
+                                             GtkWidget *widget,
+                                             GtkImage *old_widget);
+
+extern void egtk_list_insert (egtk_list_node *list, egtk_list_node *node);
+extern double egtk_get_monitor_wdpi (GdkMonitor *monitor);
+extern double egtk_get_monitor_hdpi (GdkMonitor *monitor);
+extern GtkDialog *
+egtk_build_dialog (struct frame *f,
+		   widget_value *wv,
+		   GCallback select_cb,
+		   GCallback deactivate_cb);
+extern GtkPopover *
+build_mm_from_wv (widget_value *wv,
+		  struct frame *f,
+		  GCallback select_cb,
+		  GCallback deactivate_cb,
+		  GCallback highlight_cb,
+		  bool pop_up_p,
+		  bool menu_bar_p,
+		  egtk_menu_cb_data *cl_data,
+		  const char *name,
+		  GtkContainer *box,
+		  GtkPopover *popover,
+		  GdkRectangle *posrect);
+
+extern int
+calculate_child_frame_distance_x (struct frame *f);
+extern int
+calculate_child_frame_distance_y (struct frame *f);
+
+extern gboolean
+overlay_allocate_cb (GtkOverlay *overlay,
+		     GtkWidget *widget,
+		     GdkRectangle *allocation,
+		     gpointer user_data);
+
+egtk_menu_cb_data *
+make_cl_data (egtk_menu_cb_data *cl_data, struct frame *f,
+	      GCallback highlight_cb);
+
+GtkWidget *
+eg_get_widget_from_map (ptrdiff_t idx);
+
+void
+eg_notify_frame_header_bar_state_changed (struct frame *f);
+
+void
+eg_unparent_frame (struct frame *f);
+
+void
+emacs_resizable_drawing_area_show_selection_options (EmacsResizableDrawingArea *area);
+
+void
+unref_cl_data (egtk_menu_cb_data *cl_data);
+
+Lisp_Object
+eg_get_font_family (bool mono);
+
+Lisp_Object
+eg_get_caret_px_width (void);
+
+Lisp_Object
+eg_get_divider_width (void);
+
+GtkWidget *
+build_popover_menu_bar (widget_value *wv, egtk_menu_cb_data *cl_data,
+                        GCallback select_cb, GCallback deactivate_cb,
+                        GCallback highlight_cb, struct frame *f,
+                        GtkWidget *mbar);
+
+GtkWidget *
+xeg_child_frame_bin_new (void);
+
+void
+frame_activate_menu_bar (struct frame *f);
+#endif /* USE_GTK */
+#endif /* PGTKSUBR_H */
diff --git a/src/pgtkterm.c b/src/pgtkterm.c
new file mode 100644
index 0000000000..c55f8e59e3
--- /dev/null
+++ b/src/pgtkterm.c
@@ -0,0 +1,8814 @@
+/* Pure GTK communication module.      -*- coding: utf-8 -*-
+
+   Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2018 Free Software
+   Foundation, Inc.
+
+   This file is part of GNU Emacs.
+
+   GNU Emacs 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.
+
+   GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+
+
+
+			 /-------------\
+			/		\
+		       /		 \
+		      /			  \
+		      |	  XXXX	   XXXX	  |
+		      |	  XXXX	   XXXX	  |
+		      |	  XXX	    XXX	  |
+		      \		X	  /
+		       --\     XXX     /--
+			| |    XXX    | |
+			| |	      | |
+			| I I I I I I I |
+			|  I I I I I I	|
+			 \	       /
+			  --	     --
+			    \-------/
+		    XXX			   XXX
+		   XXXXX		  XXXXX
+		   XXXXXXXXX	     XXXXXXXXXX
+			  XXXXX	  XXXXX
+			     XXXXXXX
+			  XXXXX	  XXXXX
+		   XXXXXXXXX	     XXXXXXXXXX
+		   XXXXX		  XXXXX
+		    XXX			   XXX
+
+			  **************
+			  *  BEWARE!!  *
+			  **************
+
+			All ye who enter here:
+		    Most of the code in this module
+		       is twisted beyond belief!
+
+			   Tread carefully.
+
+		    If you think you understand it,
+			      You Don't,
+			    So Look Again.
+ ****************************************************************/
+
+/* NOTICE
+ * We aim to fully support all major versions of GTK after and including GTK 2
+ * If you add something, make sure it works on all supported versions of GTK.
+ */
+
+#include <config.h>
+
+#include <cairo/cairo.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <c-ctype.h>
+#include <c-strcase.h>
+#include <ftoastr.h>
+
+#include "lisp.h"
+#include "blockinput.h"
+#include "ccl.h"
+#include "character.h"
+#include "composite.h"
+#include "fontset.h"
+#ifndef HAVE_GTK4
+#include "gtkutil.h"
+#else
+#include "pgtksubr.h"
+#endif
+#include "sysselect.h"
+#include "systime.h"
+#include "xwidget.h"
+
+#include "atimer.h"
+#include "buffer.h"
+#include "font.h"
+#include "keyboard.h"
+#include "menu.h"
+#include "pgtkselect.h"
+#include "termchar.h"
+#include "termhooks.h"
+#include "termopts.h"
+#include "window.h"
+#include "xsettings.h"
+
+#include "pgtkim.h"
+#include "gtkconfig.h"
+
+#ifdef GDK_WINDOWING_WAYLAND
+#ifdef HAVE_GTK4
+#include <gdk/wayland/gdkwayland.h>
+#else
+#include <gdk/gdkwayland.h>
+#endif
+#endif
+
+#include "dynlib.h"
+
+#ifdef HAVE_GDK_X11
+#ifndef HAVE_GTK4
+#include <gdk/gdkx.h>
+#else
+#include <gdk/x11/gdkx.h>
+#include <X11/X.h>
+#endif
+#endif
+
+#ifdef GDK_WINDOWING_BROADWAY
+#ifdef HAVE_GTK4
+#include <gdk/broadway/gdkbroadway.h>
+#endif
+#endif
+
+#ifndef HAVE_GTK3
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#define STORE_KEYSYM_FOR_DEBUG(keysym) ((void) 0)
+
+#define FRAME_CR_CONTEXT(f) ((f)->output_data.pgtk->cr_context)
+#define FRAME_CR_SURFACE(f) ((f)->output_data.pgtk->cr_surface)
+#define FRAME_CR_SURFACE_DESIRED_WIDTH(f) \
+  ((f)->output_data.pgtk->cr_surface_desired_width)
+#define FRAME_CR_SURFACE_DESIRED_HEIGHT(f) \
+  ((f)->output_data.pgtk->cr_surface_desired_height)
+
+
+struct pgtk_display_info *x_display_list; /* Chain of existing displays */
+extern Lisp_Object tip_frame;
+
+static struct event_queue_t
+{
+  union buffered_input_event *q;
+  int nr, cap;
+  int nr_pass_flag;
+} event_q = {
+  NULL,
+  0,
+  0,
+  0,
+};
+
+/* GHashTable [frame GSequence [frame]] */
+static GHashTable *frame_nchild_list;
+
+/* Non-zero timeout value means ignore next mouse click if it arrives
+   before that timeout elapses (i.e. as part of the same sequence of
+   events resulting from clicking on a frame to select it).  */
+
+static Time ignore_next_mouse_click_timeout;
+
+static Lisp_Object xg_default_icon_file;
+
+#ifdef HAVE_GTK4
+static GtkStyleContext *btn_style_context = NULL;
+static GtkStyleContext *chk_style_context = NULL;
+static GtkStyleContext *ent_style_context = NULL;
+#endif
+
+static void pgtk_delete_display (struct pgtk_display_info *dpyinfo);
+static void pgtk_clear_frame_area (struct frame *f, int x, int y, int width,
+                                   int height);
+static void pgtk_fill_rectangle (struct frame *f, unsigned long color, int x,
+                                 int y, int width, int height);
+static void pgtk_clip_to_row (struct window *w, struct glyph_row *row,
+                              enum glyph_row_area area, cairo_t *cr);
+
+static bool fdmap[800];
+
+#ifdef HAVE_GTK4
+static GtkWindow *
+locate_nonchild_window (struct frame *child);
+#endif
+
+#ifdef HAVE_GTK4
+static struct frame *pgtk_any_window_to_frame (GdkSurface *window);
+#else
+static struct frame *pgtk_any_window_to_frame (GdkWindow *window);
+#endif
+
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+static gboolean
+configure_event (GtkWidget *widget, GdkEvent *event, struct frame *user_data);
+#endif
+
+static void
+set_fullscreen_state (struct frame *);
+
+
+#ifdef HAVE_GTK4
+static gboolean
+focus_in_event (GtkWidget *widget, GdkEvent *unused, gpointer user_data);
+#endif
+void
+evq_enqueue (union buffered_input_event *ev)
+{
+  block_input ();
+  struct event_queue_t *evq = &event_q;
+  if (evq->cap == 0)
+    {
+      evq->cap = 4;
+      evq->q = xmalloc (sizeof *evq->q * evq->cap);
+    }
+
+  if (evq->nr >= evq->cap)
+    {
+      evq->cap += evq->cap / 2;
+      evq->q = xrealloc (evq->q, sizeof *evq->q * evq->cap);
+    }
+
+  evq->q[evq->nr++] = *ev;
+  unblock_input ();
+  raise (SIGIO);
+}
+
+static int
+evq_flush (struct input_event *hold_quit)
+{
+  struct event_queue_t *evq = &event_q;
+  int i, n = evq->nr;
+  for (i = 0; i < n; i++)
+    kbd_buffer_store_buffered_event (&evq->q[i], hold_quit);
+  evq->nr = 0;
+  return n;
+}
+
+void
+mark_pgtkterm (void)
+{
+  struct event_queue_t *evq = &event_q;
+  int i, n = evq->nr;
+  for (i = 0; i < n; i++)
+    {
+      union buffered_input_event *ev = &evq->q[i];
+      mark_object (ev->ie.x);
+      mark_object (ev->ie.y);
+      mark_object (ev->ie.frame_or_window);
+      mark_object (ev->ie.arg);
+    }
+
+  struct pgtk_display_info *dpyinfo;
+  for (dpyinfo = x_display_list; dpyinfo != NULL; dpyinfo = dpyinfo->next)
+    {
+      mark_object (dpyinfo->rdb);
+    }
+}
+
+int
+pgtk_get_window_decoration_width (struct frame *f)
+{
+  GtkWidget *window = FRAME_GTK_OUTER_WIDGET (f);
+  GtkWidget *emacs_fixed = FRAME_GTK_WIDGET (f);
+
+  int wx;
+  gtk_widget_translate_coordinates (emacs_fixed, window, 0, 0, &wx, 0);
+  return wx;
+}
+
+int
+pgtk_get_window_inside_decor_height (struct frame *f)
+{
+  GtkWidget *window = FRAME_GTK_OUTER_WIDGET (f);
+  GtkWidget *emacs_fixed = FRAME_GTK_WIDGET (f);
+
+  int wy;
+  gtk_widget_translate_coordinates (emacs_fixed, window, 0, 0, 0, &wy);
+  return wy;
+}
+
+char *
+get_keysym_name (int keysym)
+/* --------------------------------------------------------------------------
+    Called by keyboard.c.  Not sure if the return val is important, except
+    that it be unique.
+   -------------------------------------------------------------------------- */
+{
+  static char value[16];
+  sprintf (value, "%d", keysym);
+  return value;
+}
+
+void
+frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
+/* --------------------------------------------------------------------------
+     Programmatically reposition mouse pointer in pixel coordinates
+   -------------------------------------------------------------------------- */
+{
+#ifndef HAVE_GTK4
+#ifdef HAVE_GTK3
+  GdkDevice *device;
+#ifdef GDK_VERSION_3_22
+  GdkSeat *seat
+    = gdk_display_get_default_seat (FRAME_X_OUTPUT (f)->display_info->gdpy);
+  device = gdk_seat_get_pointer (seat);
+#else
+  GdkDeviceManager *mgr
+    = gdk_display_get_device_manager (FRAME_X_OUTPUT (f)->display_info->gdpy);
+  device = gdk_device_manager_get_client_pointer (mgr);
+#endif
+
+  gdk_device_warp (device,
+                   gdk_display_get_default_screen (
+                     FRAME_X_OUTPUT (f)->display_info->gdpy),
+                   pix_x, pix_y);
+#else
+  gdk_display_warp_pointer (FRAME_X_OUTPUT (f)->display_info->gdpy,
+			    gdk_display_get_default_screen (FRAME_X_OUTPUT (f)->display_info->gdpy),
+			    pix_x, pix_y);
+#endif
+#endif
+}
+
+#if defined (GDK_VERSION_3_22)
+static void
+pgtk_move_event_commit (GdkWindow *window,
+			gpointer   flipped_rect,
+			gpointer   final_rect,
+			gboolean   flipped_x,
+			gboolean   flipped_y,
+			gpointer   user_data)
+{
+  pgtk_child_frame_flush ((struct frame *) user_data);
+}
+#endif
+
+#ifdef HAVE_GTK4
+static void
+gtk_find_gtk_menu_button (GtkWidget *w, GtkMenuButton **user_data)
+{
+  if (GTK_IS_MENU_BUTTON (w))
+    (*user_data) = GTK_MENU_BUTTON (w);
+}
+#endif
+
+/* Free X resources of frame F.  */
+
+void
+x_free_frame_resources (struct frame *f)
+{
+  free_frame_faces (f);
+  struct pgtk_display_info *dpyinfo;
+  Mouse_HLInfo *hlinfo;
+
+  check_window_system (f);
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+  hlinfo = MOUSE_HL_INFO (f);
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f))
+    {
+      FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->child_frame_order =
+	g_slist_remove (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->child_frame_order, f);
+    }
+  if (FRAME_GTK_HEADER_BAR (f))
+    {
+      GtkMenuButton *btn = NULL;
+      gtk_container_foreach (GTK_CONTAINER (FRAME_GTK_HEADER_BAR (f)),
+			     (GtkCallback) gtk_find_gtk_menu_button, &btn);
+      if (btn)
+	gtk_menu_button_set_create_popup_func (btn, NULL, NULL, NULL);
+    }
+#ifndef FIX_GTK_LEGACY_HANDLER_BUG
+  g_clear_pointer (&FRAME_GTK_EV_HANDLER (f), g_object_unref);
+  g_clear_pointer (&FRAME_GTK_ES_HANDLER (f), g_object_unref);
+#endif
+#endif
+  pgtk_clear_child_occourences (f);
+
+  block_input ();
+
+  if (frame_nchild_list && g_hash_table_contains (frame_nchild_list, f))
+    g_hash_table_remove (frame_nchild_list, f);
+
+#define CLEAR_IF_EQ(FIELD)     \
+  do                           \
+    {                          \
+      if (f == dpyinfo->FIELD) \
+        dpyinfo->FIELD = 0;    \
+    }                          \
+  while (false)
+
+  CLEAR_IF_EQ (x_focus_frame);
+  CLEAR_IF_EQ (highlight_frame);
+  CLEAR_IF_EQ (x_focus_event_frame);
+  CLEAR_IF_EQ (last_mouse_frame);
+  CLEAR_IF_EQ (last_mouse_motion_frame);
+  CLEAR_IF_EQ (last_mouse_glyph_frame);
+  CLEAR_IF_EQ (im.focused_frame);
+
+#undef CLEAR_IF_EQ
+
+  if (f == hlinfo->mouse_face_mouse_frame)
+    reset_mouse_highlight (hlinfo);
+
+  GtkWidget *tmp = FRAME_GTK_OUTER_WIDGET (f);
+  FRAME_GTK_WIDGET (f) = NULL;
+  FRAME_GTK_OUTER_WIDGET (f) = NULL;
+
+  gtk_widget_destroy (tmp);
+
+#ifdef HAVE_GTK3
+  if (FRAME_X_OUTPUT (f)->border_color_css_provider != NULL)
+    {
+      GtkStyleContext *ctxt
+        = gtk_widget_get_style_context (FRAME_GTK_OUTER_WIDGET (f));
+      GtkCssProvider *old = FRAME_X_OUTPUT (f)->border_color_css_provider;
+      gtk_style_context_remove_provider (ctxt, GTK_STYLE_PROVIDER (old));
+      FRAME_X_OUTPUT (f)->border_color_css_provider = NULL;
+    }
+
+  if (FRAME_X_OUTPUT (f)->scrollbar_foreground_css_provider != NULL)
+    {
+      GtkCssProvider *old
+        = FRAME_X_OUTPUT (f)->scrollbar_foreground_css_provider;
+      g_object_unref (old);
+      FRAME_X_OUTPUT (f)->scrollbar_foreground_css_provider = NULL;
+    }
+
+  if (FRAME_X_OUTPUT (f)->scrollbar_background_css_provider != NULL)
+    {
+      GtkCssProvider *old
+        = FRAME_X_OUTPUT (f)->scrollbar_background_css_provider;
+      g_object_unref (old);
+      FRAME_X_OUTPUT (f)->scrollbar_background_css_provider = NULL;
+    }
+#endif
+
+  if (FRAME_X_OUTPUT (f)->cr_surface_visible_bell != NULL)
+    {
+      cairo_surface_destroy (FRAME_X_OUTPUT (f)->cr_surface_visible_bell);
+      FRAME_X_OUTPUT (f)->cr_surface_visible_bell = NULL;
+    }
+
+  if (FRAME_X_OUTPUT (f)->atimer_visible_bell != NULL)
+    {
+      cancel_atimer (FRAME_X_OUTPUT (f)->atimer_visible_bell);
+      FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
+    }
+
+  xfree (f->output_data.pgtk);
+  f->output_data.pgtk = NULL;
+#ifdef HAVE_GTK4
+  struct frame *fz;
+  if (FRAME_PARENT_FRAME (f)
+      && f == (fz = FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above))
+    {
+      FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above = NULL;
+      FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->child_frame_order
+        = g_slist_remove (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->child_frame_order,
+                          FRAME_GTK_OUTER_WIDGET (fz));
+    }
+#endif
+  unblock_input ();
+}
+
+void
+x_destroy_window (struct frame *f)
+/* --------------------------------------------------------------------------
+     External: Delete the window
+   -------------------------------------------------------------------------- */
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+
+  check_window_system (f);
+  if (dpyinfo->gdpy != NULL)
+    x_free_frame_resources (f);
+
+  dpyinfo->reference_count--;
+}
+
+/* Calculate the absolute position in frame F
+   from its current recorded position values and gravity.  */
+
+static void
+x_calc_absolute_position (struct frame *f)
+{
+  int flags = f->size_hint_flags;
+  struct frame *p = FRAME_PARENT_FRAME (f);
+
+  /* We have nothing to do if the current position
+     is already for the top-left corner.  */
+  if (!((flags & XNegative) || (flags & YNegative)))
+    return;
+
+  /* Treat negative positions as relative to the leftmost bottommost
+     position that fits on the screen.  */
+  if ((flags & XNegative) && (f->left_pos <= 0))
+    {
+      int width = FRAME_PIXEL_WIDTH (f);
+
+      /* A frame that has been visible at least once should have outer
+         edges.  */
+      if (FRAME_X_OUTPUT (f)->has_been_visible && !p)
+        {
+          Lisp_Object frame;
+          Lisp_Object edges = Qnil;
+
+          XSETFRAME (frame, f);
+          edges = Fpgtk_frame_edges (frame, Qouter_edges);
+          if (!NILP (edges))
+            width = (XFIXNUM (Fnth (make_fixnum (2), edges))
+                     - XFIXNUM (Fnth (make_fixnum (0), edges)));
+        }
+
+      if (p)
+        f->left_pos
+          = (FRAME_PIXEL_WIDTH (p) - width - 2 * f->border_width + f->left_pos);
+      else
+        f->left_pos = (x_display_pixel_width (FRAME_DISPLAY_INFO (f)) - width
+                       + f->left_pos);
+    }
+
+  if ((flags & YNegative) && (f->top_pos <= 0))
+    {
+      int height = FRAME_PIXEL_HEIGHT (f);
+
+      if (FRAME_X_OUTPUT (f)->has_been_visible && !p)
+        {
+          Lisp_Object frame;
+          Lisp_Object edges = Qnil;
+
+          XSETFRAME (frame, f);
+          if (NILP (edges))
+            edges = Fpgtk_frame_edges (frame, Qouter_edges);
+          if (!NILP (edges))
+            height = (XFIXNUM (Fnth (make_fixnum (3), edges))
+                      - XFIXNUM (Fnth (make_fixnum (1), edges)));
+        }
+
+      if (p)
+        f->top_pos = (FRAME_PIXEL_HEIGHT (p) - height - 2 * f->border_width
+                      + f->top_pos);
+      else
+        f->top_pos = (x_display_pixel_height (FRAME_DISPLAY_INFO (f)) - height
+                      + f->top_pos);
+    }
+
+  /* The left_pos and top_pos
+     are now relative to the top and left screen edges,
+     so the flags should correspond.  */
+  f->size_hint_flags &= ~(XNegative | YNegative);
+}
+
+/* CHANGE_GRAVITY is 1 when calling from Fset_frame_position,
+   to really change the position, and 0 when calling from
+   x_make_frame_visible (in that case, XOFF and YOFF are the current
+   position values).  It is -1 when calling from x_set_frame_parameters,
+   which means, do adjust for borders but don't change the gravity.  */
+
+static void
+x_set_offset (struct frame *f, int xoff, int yoff, int change_gravity)
+{
+  if (change_gravity > 0)
+    {
+      f->top_pos = yoff;
+      f->left_pos = xoff;
+      f->size_hint_flags &= ~(XNegative | YNegative);
+      if (xoff < 0)
+        f->size_hint_flags |= XNegative;
+      if (yoff < 0)
+        f->size_hint_flags |= YNegative;
+      f->win_gravity = NorthWestGravity;
+    }
+
+  x_calc_absolute_position (f);
+
+  block_input ();
+#ifndef HAVE_GTK4
+  x_wm_set_size_hint (f, 0, false);
+#endif
+
+  /* When a position change was requested and the outer GTK widget
+     has been realized already, leave it to gtk_window_move to DTRT
+     and return.  Used for Bug#25851 and Bug#25943.  */
+  if (change_gravity != 0 && FRAME_GTK_OUTER_WIDGET (f))
+    {
+      if (FRAME_PARENT_FRAME (f))
+        pgtk_child_frame_reposition (f, 0);
+      else
+	{
+#ifndef HAVE_GTK4
+	  gtk_window_move (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), f->left_pos,
+			   f->top_pos);
+#endif
+	}
+    }
+
+  unblock_input ();
+}
+
+static void
+pgtk_set_window_size (struct frame *f, bool change_gravity, int width,
+                      int height, bool pixelwise)
+{
+  int pixelwidth, pixelheight;
+
+  block_input ();
+
+  gtk_widget_get_size_request (FRAME_GTK_WIDGET (f), &pixelwidth, &pixelheight);
+
+  if (pixelwise)
+    {
+      pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width);
+      pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height);
+    }
+  else
+    {
+      pixelwidth = FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, width);
+      pixelheight = FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, height);
+    }
+
+  frame_size_history_add (f, Qx_set_window_size_1, width, height,
+                          list5 (Fcons (make_fixnum (pixelwidth),
+                                        make_fixnum (pixelheight)),
+                                 Fcons (make_fixnum (pixelwidth),
+                                        make_fixnum (pixelheight)),
+                                 make_fixnum (f->border_width),
+                                 make_fixnum (pgtk_get_window_inside_decor_height (f)),
+                                 make_fixnum (FRAME_TOOLBAR_HEIGHT (f))));
+
+  f->output_data.pgtk->preferred_width = pixelwidth;
+  f->output_data.pgtk->preferred_height = pixelheight;
+  x_wm_set_size_hint (f, 0, 0);
+#ifndef HAVE_GTK4
+  xg_frame_set_char_size
+#else
+  egtk_frame_set_char_size
+#endif
+    (f,
+     FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth),
+     FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight));
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f))
+    {
+      GdkRectangle *rectangle = g_object_get_data
+	(G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+	 EG_OVERLAY_ALLOC);
+      if (rectangle)
+	{
+	  rectangle->width = pixelwidth;
+	  rectangle->height = pixelheight;
+	  gtk_widget_queue_allocate (FRAME_GTK_OUTER_WIDGET (f));
+	}
+    }
+#endif
+  gtk_widget_queue_resize (FRAME_GTK_OUTER_WIDGET (f));
+
+  unblock_input ();
+}
+
+void
+pgtk_iconify_frame (struct frame *f)
+/* --------------------------------------------------------------------------
+     External: Iconify window
+   -------------------------------------------------------------------------- */
+{
+  if (FRAME_PARENT_FRAME (f))
+    return;
+
+  /* Don't keep the highlight on an invisible frame.  */
+  if (FRAME_DISPLAY_INFO (f)->highlight_frame == f)
+    FRAME_DISPLAY_INFO (f)->highlight_frame = 0;
+#ifndef HAVE_GTK4
+  if (!FRAME_VISIBLE_P (f))
+    gtk_widget_show_all (FRAME_GTK_OUTER_WIDGET (f));
+#endif
+#ifndef HAVE_GTK4
+  gtk_window_iconify (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+#else
+  gtk_window_minimize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)));
+#endif
+  block_input ();
+  SET_FRAME_ICONIFIED (f, true);
+  SET_FRAME_VISIBLE (f, 0);
+  unblock_input ();
+#ifdef HAVE_GTK4
+  flush_frame (f);
+#else
+  gdk_flush ();
+#endif
+}
+
+static gboolean
+pgtk_make_frame_visible_wait_for_map_event_cb (GtkWidget *widget,
+#ifndef HAVE_GTK4
+                                               GdkEvent *event,
+#endif
+                                               gpointer user_data)
+{
+  int *foundptr = user_data;
+  *foundptr = 1;
+  return FALSE;
+}
+
+static gboolean
+pgtk_make_frame_visible_wait_for_map_event_timeout (gpointer user_data)
+{
+  int *timedoutptr = user_data;
+  *timedoutptr = 1;
+  return FALSE;
+}
+#ifdef HAVE_GTK4
+static void
+pgtk_promote_child_frame (struct frame *f)
+{
+  if (!f || !FRAME_PARENT_FRAME (f))
+    return;
+#pragma GCC diagnostic ignored "-Wnull-dereference"
+  block_input ();
+  GSList **l = &FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->child_frame_order;
+  *l = g_slist_remove (*l, FRAME_GTK_OUTER_WIDGET (f));
+  *l = g_slist_append (*l, FRAME_GTK_OUTER_WIDGET (f));
+  gtk_container_remove (GTK_CONTAINER
+			(gtk_widget_get_parent
+			 (GTK_WIDGET
+			  (FRAME_DECOR_OVERLAY
+			   (FRAME_PARENT_FRAME (f))))),
+			FRAME_GTK_OUTER_WIDGET (f));
+  gtk_overlay_add_overlay (GTK_OVERLAY
+			   (gtk_widget_get_parent
+			    (GTK_WIDGET
+			     (FRAME_DECOR_OVERLAY
+			      (FRAME_PARENT_FRAME (f))))),
+			   FRAME_GTK_OUTER_WIDGET (f));
+  if (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above &&
+      FRAME_VISIBLE_P (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above) &&
+      FRAME_PARENT_FRAME (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above) ==
+      FRAME_PARENT_FRAME (f))
+    {
+      *l = g_slist_remove (*l, FRAME_GTK_OUTER_WIDGET
+			   (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above));
+#define N FRAME_X_OUTPUT (f)->keep_above
+      gtk_container_remove (GTK_CONTAINER
+			    (gtk_widget_get_parent
+			     (GTK_WIDGET
+			      (FRAME_DECOR_OVERLAY
+			       (FRAME_PARENT_FRAME (N))))),
+			    FRAME_GTK_OUTER_WIDGET (N));
+      gtk_overlay_add_overlay (GTK_OVERLAY
+			       (gtk_widget_get_parent
+				(GTK_WIDGET
+				 (FRAME_DECOR_OVERLAY
+				  (FRAME_PARENT_FRAME (N))))),
+			       FRAME_GTK_OUTER_WIDGET (N));
+    }
+#undef N
+  unblock_input ();
+#pragma GCC diagnostic push
+}
+#endif
+void
+pgtk_make_frame_visible (struct frame *f)
+/* --------------------------------------------------------------------------
+     External: Show the window (X11 semantics)
+   -------------------------------------------------------------------------- */
+{
+  PGTK_TRACE ("pgtk_make_frame_visible");
+
+  GtkWidget *win = FRAME_GTK_OUTER_WIDGET (f);
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f) && !f->visible)
+    {
+      block_input ();
+      pgtk_clear_frame (f);
+      pgtk_promote_child_frame (f);
+      SET_FRAME_GARBAGED (f);
+      if (!FRAME_X_OUTPUT (f)->child_map_flag
+	  && !(FRAME_NO_FOCUS_ON_MAP (f))
+          && !(FRAME_NO_ACCEPT_FOCUS (f)))
+        {
+	  focus_in_event (NULL, NULL, f);
+	  gtk_root_set_focus (gtk_widget_get_root (GTK_WIDGET (locate_nonchild_window (f))),
+			      FRAME_GTK_WIDGET (f));
+	  FRAME_X_OUTPUT (f)->bar_focus_out_events = 1;
+	  FRAME_X_OUTPUT (f)->has_been_visible = 1;
+	  FRAME_X_OUTPUT (f)->child_map_flag = 1;
+	  redisplay ();
+	  unblock_input ();
+	  raise (SIGIO);
+        }
+      else
+	unblock_input ();
+    }
+#endif
+  if (!FRAME_VISIBLE_P (f))
+    {
+      gtk_widget_show (win);
+#ifndef HAVE_GTK4
+      gtk_window_deiconify (GTK_WINDOW (win));
+#else
+      if (GTK_IS_WINDOW (win))
+	gtk_window_unminimize (GTK_WINDOW (win));
+#endif
+      SET_FRAME_ICONIFIED (f, 0);
+      flush_frame (f);
+
+      if (FLOATP (Vpgtk_wait_for_event_timeout))
+        {
+          guint msec
+            = (guint) (XFLOAT_DATA (Vpgtk_wait_for_event_timeout) * 1000);
+          int found = 0;
+          int timed_out = 0;
+          gulong id = g_signal_connect (
+            win,
+#ifndef HAVE_GTK4
+	    "map-event",
+#else
+	    "map",
+#endif
+            G_CALLBACK (pgtk_make_frame_visible_wait_for_map_event_cb), &found);
+          guint src
+            = g_timeout_add (msec,
+                             pgtk_make_frame_visible_wait_for_map_event_timeout,
+                             &timed_out);
+          while (!found && !timed_out)
+#ifndef HAVE_GTK4
+            gtk_main_iteration ();
+#else
+	    run_main_loop_iteration ();
+#endif
+          g_signal_handler_disconnect (win, id);
+          if (!timed_out)
+            g_source_remove (src);
+        }
+    }
+}
+
+void
+pgtk_make_frame_invisible (struct frame *f)
+/* --------------------------------------------------------------------------
+     External: Hide the window (X11 semantics)
+   -------------------------------------------------------------------------- */
+{
+  GtkWidget *win = FRAME_OUTPUT_DATA (f)->widget;
+
+  gtk_widget_hide (win);
+  flush_frame (f);
+
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f))
+    {
+      bool flag = 0;
+      for (GSList *l = FRAME_PARENT_FRAME (f)->output_data.pgtk->child_frame_order;
+	   !flag && l; l = l->next)
+	{
+	  if (gtk_widget_is_visible (l->data))
+	    flag = 1;
+	}
+      FRAME_X_OUTPUT (f)->child_map_flag = 0;
+      if (!flag)
+	{
+	  gtk_root_set_focus (gtk_widget_get_root (GTK_WIDGET (locate_nonchild_window (FRAME_PARENT_FRAME (f)))),
+			      FRAME_GTK_WIDGET (FRAME_PARENT_FRAME (f)));
+	  focus_in_event (NULL, NULL, FRAME_PARENT_FRAME (f));
+	}
+    }
+#endif
+
+  SET_FRAME_VISIBLE (f, 0);
+  SET_FRAME_ICONIFIED (f, false);
+}
+
+static void
+pgtk_make_frame_visible_invisible (struct frame *f, bool visible)
+{
+  if (visible)
+    pgtk_make_frame_visible (f);
+  else
+    pgtk_make_frame_invisible (f);
+}
+
+static Lisp_Object
+pgtk_new_font (struct frame *f, Lisp_Object font_object, int fontset)
+{
+  struct font *font = XFONT_OBJECT (font_object);
+  int font_ascent, font_descent;
+
+  if (fontset < 0)
+    fontset = fontset_from_font (font_object);
+  FRAME_FONTSET (f) = fontset;
+
+  if (FRAME_FONT (f) == font)
+    {
+      /* This font is already set in frame F.  There's nothing more to
+         do.  */
+      return font_object;
+    }
+
+  FRAME_FONT (f) = font;
+
+  FRAME_BASELINE_OFFSET (f) = font->baseline_offset;
+  FRAME_COLUMN_WIDTH (f) = font->average_width;
+  get_font_ascent_descent (font, &font_ascent, &font_descent);
+  FRAME_LINE_HEIGHT (f) = font_ascent + font_descent;
+
+  /* Compute the scroll bar width in character columns.  */
+  if (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0)
+    {
+      int wid = FRAME_COLUMN_WIDTH (f);
+      FRAME_CONFIG_SCROLL_BAR_COLS (f)
+        = (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) + wid - 1) / wid;
+    }
+  else
+    {
+      int wid = FRAME_COLUMN_WIDTH (f);
+      FRAME_CONFIG_SCROLL_BAR_COLS (f) = (14 + wid - 1) / wid;
+    }
+
+  /* Compute the scroll bar height in character lines.  */
+  if (FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) > 0)
+    {
+      int height = FRAME_LINE_HEIGHT (f);
+      FRAME_CONFIG_SCROLL_BAR_LINES (f)
+        = (FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) + height - 1) / height;
+    }
+  else
+    {
+      int height = FRAME_LINE_HEIGHT (f);
+      FRAME_CONFIG_SCROLL_BAR_LINES (f) = (14 + height - 1) / height;
+    }
+
+  /* Now make the frame display the given font.  */
+  if (FRAME_GTK_WIDGET (f) != NULL)
+    adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f),
+                       FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), 3, false,
+                       Qfont);
+
+  return font_object;
+}
+
+int
+x_display_pixel_height (struct pgtk_display_info *dpyinfo)
+{
+#ifndef HAVE_GTK4
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+  GdkScreen *gscr = gdk_display_get_default_screen (gdpy);
+  return gdk_screen_get_height (gscr);
+#else
+  GdkDisplay *dpy = dpyinfo->gdpy;
+  GdkMonitor *mon = gdk_display_get_monitor (dpy, 0);
+  GdkRectangle rect;
+  gdk_monitor_get_geometry (mon, &rect);
+  return rect.height;
+#endif
+}
+
+int
+x_display_pixel_width (struct pgtk_display_info *dpyinfo)
+{
+#ifndef HAVE_GTK4
+  PGTK_TRACE ("x_display_pixel_width");
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+  GdkScreen *gscr = gdk_display_get_default_screen (gdpy);
+  return gdk_screen_get_width (gscr);
+#else
+  GdkDisplay *dpy = dpyinfo->gdpy;
+  GdkMonitor *mon = gdk_display_get_monitor (dpy, 0);
+  GdkRectangle rect;
+  gdk_monitor_get_geometry (mon, &rect);
+  return rect.width;
+#endif
+}
+
+void
+x_set_no_focus_on_map (struct frame *f, Lisp_Object new_value,
+                       Lisp_Object old_value)
+/* Set frame F's `no-focus-on-map' parameter which, if non-nil, means
+ * that F's window-system window does not want to receive input focus
+ * when it is mapped.  (A frame's window is mapped when the frame is
+ * displayed for the first time and when the frame changes its state
+ * from `iconified' or `invisible' to `visible'.)
+ *
+ * Some window managers may not honor this parameter. */
+{
+  if (!EQ (new_value, old_value))
+    {
+#ifndef HAVE_GTK4
+      xg_set_no_focus_on_map (f, new_value);
+#else
+      egtk_set_no_focus_on_map (f, new_value);
+#endif
+      FRAME_NO_FOCUS_ON_MAP (f) = !NILP (new_value);
+    }
+}
+
+void
+x_set_no_accept_focus (struct frame *f, Lisp_Object new_value,
+                       Lisp_Object old_value)
+/*  Set frame F's `no-accept-focus' parameter which, if non-nil, hints
+ * that F's window-system window does not want to receive input focus
+ * via mouse clicks or by moving the mouse into it.
+ *
+ * If non-nil, this may have the unwanted side-effect that a user cannot
+ * scroll a non-selected frame with the mouse.
+ *
+ * Some window managers may not honor this parameter. */
+{
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f))
+    gtk_widget_set_can_focus (FRAME_GTK_OUTER_WIDGET (f), NILP (new_value));
+#endif
+#ifndef HAVE_GTK4
+    gtk_window_set_accept_focus (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+				 NILP (new_value));
+#endif
+  FRAME_NO_ACCEPT_FOCUS (f) = !NILP (new_value);
+}
+
+void
+x_set_z_group (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
+/* Set frame F's `z-group' parameter.  If `above', F's window-system
+   window is displayed above all windows that do not have the `above'
+   property set.  If nil, F's window is shown below all windows that
+   have the `above' property set and above all windows that have the
+   `below' property set.  If `below', F's window is displayed below
+   all windows that do.
+
+   Some window managers may not honor this parameter. */
+{
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f))
+    {
+      if (NILP (new_value) || !EQ (new_value, Qabove))
+	if (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above == f)
+	  FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above = 0;
+	else
+	  {}
+      else
+        {
+          FRAME_X_OUTPUT (FRAME_PARENT_FRAME (f))->keep_above = f;
+	  pgtk_promote_child_frame (f);
+        }
+      return;
+    }
+#endif
+#ifndef HAVE_GTK4
+  if (NILP (new_value))
+    {
+      gtk_window_set_keep_above (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                                 FALSE);
+      gtk_window_set_keep_below (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                                 FALSE);
+      FRAME_Z_GROUP (f) = z_group_none;
+    }
+  else if (EQ (new_value, Qabove))
+    {
+      gtk_window_set_keep_above (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), TRUE);
+      gtk_window_set_keep_below (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                                 FALSE);
+      FRAME_Z_GROUP (f) = z_group_above;
+    }
+  else if (EQ (new_value, Qabove_suspended))
+    {
+      gtk_window_set_keep_above (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                                 FALSE);
+      FRAME_Z_GROUP (f) = z_group_above_suspended;
+    }
+  else if (EQ (new_value, Qbelow))
+    {
+      gtk_window_set_keep_above (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+                                 FALSE);
+      gtk_window_set_keep_below (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), TRUE);
+      FRAME_Z_GROUP (f) = z_group_below;
+    }
+  else
+    error ("Invalid z-group specification");
+#endif
+}
+
+static void
+pgtk_initialize_display_info (struct pgtk_display_info *dpyinfo)
+/* --------------------------------------------------------------------------
+      Initialize global info and storage for display.
+   -------------------------------------------------------------------------- */
+{
+  dpyinfo->resx = 72.27; /* used 75.0, but this makes pt == pixel, expected */
+  dpyinfo->resy = 72.27;
+  dpyinfo->color_p = 1;
+  dpyinfo->n_planes = 32;
+  dpyinfo->root_window = 0;
+  dpyinfo->highlight_frame = dpyinfo->x_focus_frame = NULL;
+  dpyinfo->n_fonts = 0;
+  dpyinfo->smallest_font_height = 1;
+  dpyinfo->smallest_char_width = 1;
+
+  reset_mouse_highlight (&dpyinfo->mouse_highlight);
+}
+
+/* Set S->gc to a suitable GC for drawing glyph string S in cursor
+   face.  */
+
+static void
+x_set_cursor_gc (struct glyph_string *s)
+{
+  if (s->font == FRAME_FONT (s->f)
+      && s->face->background == FRAME_BACKGROUND_PIXEL (s->f)
+      && s->face->foreground == FRAME_FOREGROUND_PIXEL (s->f) && !s->cmp)
+    s->xgcv = FRAME_X_OUTPUT (s->f)->cursor_xgcv;
+  else
+    {
+      /* Cursor on non-default face: must merge.  */
+      Emacs_GC xgcv;
+
+      xgcv.background = FRAME_X_OUTPUT (s->f)->cursor_color;
+
+      /* If the glyph would be invisible, try a different foreground.  */
+      if (xgcv.foreground == xgcv.background)
+        xgcv.foreground = s->face->foreground;
+      if (xgcv.foreground == xgcv.background)
+        xgcv.foreground = FRAME_X_OUTPUT (s->f)->cursor_foreground_color;
+      if (xgcv.foreground == xgcv.background)
+        xgcv.foreground = s->face->foreground;
+
+      /* Make sure the cursor is distinct from text in this face.  */
+      if (xgcv.background == s->face->background
+          && xgcv.foreground == s->face->foreground)
+        {
+          xgcv.background = s->face->foreground;
+          xgcv.foreground = s->face->background;
+        }
+      IF_DEBUG (x_check_font (s->f, s->font));
+
+      s->xgcv = xgcv;
+    }
+}
+
+/* Set up S->gc of glyph string S for drawing text in mouse face.  */
+
+static void
+x_set_mouse_face_gc (struct glyph_string *s)
+{
+  int face_id;
+  struct face *face;
+
+  /* What face has to be used last for the mouse face?  */
+  face_id = MOUSE_HL_INFO (s->f)->mouse_face_face_id;
+  face = FACE_FROM_ID_OR_NULL (s->f, face_id);
+  if (face == NULL)
+    face = FACE_FROM_ID (s->f, MOUSE_FACE_ID);
+
+  if (s->first_glyph->type == CHAR_GLYPH)
+    face_id = FACE_FOR_CHAR (s->f, face, s->first_glyph->u.ch, -1, Qnil);
+  else
+    face_id = FACE_FOR_CHAR (s->f, face, 0, -1, Qnil);
+  s->face = FACE_FROM_ID (s->f, face_id);
+  prepare_face_for_display (s->f, s->face);
+
+  if (s->font == s->face->font)
+    {
+      s->xgcv.foreground = s->face->foreground;
+      s->xgcv.background = s->face->background;
+    }
+  else
+    {
+      /* Otherwise construct scratch_cursor_gc with values from FACE
+         except for FONT.  */
+      Emacs_GC xgcv;
+
+      xgcv.background = s->face->background;
+      xgcv.foreground = s->face->foreground;
+
+      s->xgcv = xgcv;
+    }
+}
+
+/* Set S->gc of glyph string S to a GC suitable for drawing a mode line.
+   Faces to use in the mode line have already been computed when the
+   matrix was built, so there isn't much to do, here.  */
+
+static void
+x_set_mode_line_face_gc (struct glyph_string *s)
+{
+  s->xgcv.foreground = s->face->foreground;
+  s->xgcv.background = s->face->background;
+}
+
+/* Set S->gc of glyph string S for drawing that glyph string.  Set
+   S->stippled_p to a non-zero value if the face of S has a stipple
+   pattern.  */
+
+static void
+x_set_glyph_string_gc (struct glyph_string *s)
+{
+  prepare_face_for_display (s->f, s->face);
+  if (s->hl == DRAW_NORMAL_TEXT)
+    {
+      s->xgcv.foreground = s->face->foreground;
+      s->xgcv.background = s->face->background;
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else if (s->hl == DRAW_INVERSE_VIDEO)
+    {
+      x_set_mode_line_face_gc (s);
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else if (s->hl == DRAW_CURSOR)
+    {
+      x_set_cursor_gc (s);
+      s->stippled_p = false;
+    }
+  else if (s->hl == DRAW_MOUSE_FACE)
+    {
+      x_set_mouse_face_gc (s);
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else if (s->hl == DRAW_IMAGE_RAISED || s->hl == DRAW_IMAGE_SUNKEN)
+    {
+      s->xgcv.foreground = s->face->foreground;
+      s->xgcv.background = s->face->background;
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else
+    emacs_abort ();
+}
+
+/* Set clipping for output of glyph string S.  S may be part of a mode
+   line or menu if we don't have X toolkit support.  */
+
+static void
+x_set_glyph_string_clipping (struct glyph_string *s, cairo_t *cr)
+{
+  NativeRectangle r[2];
+  int n = get_glyph_string_clip_rects (s, r, 2);
+  if (n > 0)
+    {
+      for (int i = 0; i < n; i++)
+	cairo_rectangle (cr, r[i].x, r[i].y, r[i].width, r[i].height);
+      cairo_clip (cr);
+    }
+  cairo_rectangle_list_t *rects = cairo_copy_clip_rectangle_list (cr);
+  cairo_rectangle_list_destroy (rects);
+}
+
+/* Set SRC's clipping for output of glyph string DST.  This is called
+   when we are drawing DST's left_overhang or right_overhang only in
+   the area of SRC.  */
+
+static void
+x_set_glyph_string_clipping_exactly (struct glyph_string *src,
+                                     struct glyph_string *dst, cairo_t *cr)
+{
+  dst->clip[0].x = src->x;
+  dst->clip[0].y = src->y;
+  dst->clip[0].width = src->width;
+  dst->clip[0].height = src->height;
+  dst->num_clips = 1;
+
+  cairo_rectangle (cr, src->x, src->y, src->width, src->height);
+  cairo_clip (cr);
+}
+
+/* RIF:
+   Compute left and right overhang of glyph string S.  */
+
+static void
+pgtk_compute_glyph_string_overhangs (struct glyph_string *s)
+{
+  if (s->cmp == NULL
+      && (s->first_glyph->type == CHAR_GLYPH
+          || s->first_glyph->type == COMPOSITE_GLYPH))
+    {
+      struct font_metrics metrics;
+
+      if (s->first_glyph->type == CHAR_GLYPH)
+        {
+          unsigned *code = alloca (sizeof (unsigned) * s->nchars);
+          struct font *font = s->font;
+          int i;
+
+          for (i = 0; i < s->nchars; i++)
+            code[i] = s->char2b[i];
+          font->driver->text_extents (font, code, s->nchars, &metrics);
+        }
+      else
+        {
+          Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
+
+          composition_gstring_width (gstring, s->cmp_from, s->cmp_to, &metrics);
+        }
+      s->right_overhang
+        = (metrics.rbearing > metrics.width ? metrics.rbearing - metrics.width
+                                            : 0);
+      s->left_overhang = metrics.lbearing < 0 ? -metrics.lbearing : 0;
+    }
+  else if (s->cmp)
+    {
+      s->right_overhang = s->cmp->rbearing - s->cmp->pixel_width;
+      s->left_overhang = -s->cmp->lbearing;
+    }
+}
+
+/* Fill rectangle X, Y, W, H with background color of glyph string S.  */
+
+static void
+x_clear_glyph_string_rect (struct glyph_string *s, int x, int y, int w, int h,
+			   bool defer_flip)
+{
+  pgtk_fill_rectangle (s->f, s->xgcv.background, x, y, w, h);
+  if (defer_flip)
+    FRAME_X_OUTPUT (s->f)->want_flip = false;
+}
+
+static void
+fill_background_by_face (struct frame *f, struct face *face, int x, int y,
+                         int width, int height)
+{
+  cairo_t *cr = pgtk_begin_cr_clip (f);
+
+  cairo_rectangle (cr, x, y, width, height);
+  cairo_clip (cr);
+
+  double r = ((face->background >> 16) & 0xff) / 255.0;
+  double g = ((face->background >> 8) & 0xff) / 255.0;
+  double b = ((face->background >> 0) & 0xff) / 255.0;
+  cairo_set_source_rgb (cr, r, g, b);
+  cairo_paint (cr);
+
+  if (face->stipple != 0)
+    {
+      cairo_pattern_t *mask
+        = FRAME_DISPLAY_INFO (f)->bitmaps[face->stipple - 1].pattern;
+
+      double r = ((face->foreground >> 16) & 0xff) / 255.0;
+      double g = ((face->foreground >> 8) & 0xff) / 255.0;
+      double b = ((face->foreground >> 0) & 0xff) / 255.0;
+      cairo_set_source_rgb (cr, r, g, b);
+      cairo_mask (cr, mask);
+    }
+
+  pgtk_end_cr_clip (f);
+}
+
+static void
+fill_background (struct glyph_string *s, int x, int y, int width, int height)
+{
+  fill_background_by_face (s->f, s->face, x, y, width, height);
+}
+
+/* Draw the background of glyph_string S.  If S->background_filled_p
+   is non-zero don't draw it.  FORCE_P non-zero means draw the
+   background even if it wouldn't be drawn normally.  This is used
+   when a string preceding S draws into the background of S, or S
+   contains the first component of a composition.  */
+
+static void
+x_draw_glyph_string_background (struct glyph_string *s, bool force_p)
+{
+  /* Nothing to do if background has already been drawn or if it
+     shouldn't be drawn in the first place.  */
+  if (!s->background_filled_p)
+    {
+      int box_line_width = max (s->face->box_horizontal_line_width, 0);
+      if (s->stippled_p)
+        {
+          /* Fill background with a stipple pattern.  */
+
+          fill_background (s, s->x, s->y + box_line_width, s->background_width,
+                           s->height - 2 * box_line_width);
+          s->background_filled_p = true;
+        }
+      else if (FONT_HEIGHT (s->font) < s->height - 2 * box_line_width
+               /* When xdisp.c ignores FONT_HEIGHT, we cannot trust
+                  font dimensions, since the actual glyphs might be
+                  much smaller.  So in that case we always clear the
+                  rectangle with background color.  */
+               || FONT_TOO_HIGH (s->font) || s->font_not_found_p
+               || s->extends_to_end_of_line_p || force_p)
+        {
+          x_clear_glyph_string_rect (s, s->x, s->y + box_line_width,
+                                     s->background_width,
+                                     s->height - 2 * box_line_width,
+				     true);
+          s->background_filled_p = true;
+        }
+    }
+}
+
+static void
+pgtk_draw_rectangle (struct frame *f, unsigned long color, int x, int y,
+                     int width, int height)
+{
+  cairo_t *cr;
+
+  cr = pgtk_begin_cr_clip (f);
+  pgtk_set_cr_source_with_color (f, color);
+  cairo_rectangle (cr, x + 0.5, y + 0.5, width, height);
+  cairo_set_line_width (cr, 1);
+  cairo_stroke (cr);
+  pgtk_end_cr_clip (f);
+}
+
+/* Draw the foreground of glyph string S.  */
+
+static void
+x_draw_glyph_string_foreground (struct glyph_string *s)
+{
+  int i, x;
+
+  /* If first glyph of S has a left box line, start drawing the text
+     of S to the right of that box line.  */
+  if (s->face->box != FACE_NO_BOX && s->first_glyph->left_box_line_p)
+    x = s->x + eabs (s->face->box_vertical_line_width);
+  else
+    x = s->x;
+
+  /* Draw characters of S as rectangles if S's font could not be
+     loaded.  */
+  if (s->font_not_found_p)
+    {
+      for (i = 0; i < s->nchars; ++i)
+        {
+          struct glyph *g = s->first_glyph + i;
+          pgtk_draw_rectangle (s->f, s->face->foreground, x, s->y,
+                               g->pixel_width - 1, s->height - 1);
+          x += g->pixel_width;
+        }
+    }
+  else
+    {
+      struct font *font = s->font;
+      int boff = font->baseline_offset;
+      int y;
+
+      if (font->vertical_centering)
+        boff = VCENTER_BASELINE_OFFSET (font, s->f) - boff;
+
+      y = s->ybase - boff;
+      if (s->for_overlaps || (s->background_filled_p && s->hl != DRAW_CURSOR))
+        font->driver->draw (s, 0, s->nchars, x, y, false);
+      else
+        font->driver->draw (s, 0, s->nchars, x, y, true);
+      if (s->face->overstrike)
+        font->driver->draw (s, 0, s->nchars, x + 1, y, false);
+    }
+}
+
+/* Draw the foreground of composite glyph string S.  */
+
+static void
+x_draw_composite_glyph_string_foreground (struct glyph_string *s)
+{
+  int i, j, x;
+  struct font *font = s->font;
+
+  /* If first glyph of S has a left box line, start drawing the text
+     of S to the right of that box line.  */
+  if (s->face && s->face->box != FACE_NO_BOX && s->first_glyph->left_box_line_p)
+    x = max (s->face->box_vertical_line_width, 0);
+  else
+    x = s->x;
+
+  /* S is a glyph string for a composition.  S->cmp_from is the index
+     of the first character drawn for glyphs of this composition.
+     S->cmp_from == 0 means we are drawing the very first character of
+     this composition.  */
+
+  /* Draw a rectangle for the composition if the font for the very
+     first character of the composition could not be loaded.  */
+  if (s->font_not_found_p)
+    {
+      if (s->cmp_from == 0)
+        pgtk_draw_rectangle (s->f, s->face->foreground, x, s->y, s->width - 1,
+                             s->height - 1);
+    }
+  else if (!s->first_glyph->u.cmp.automatic)
+    {
+      int y = s->ybase;
+
+      for (i = 0, j = s->cmp_from; i < s->nchars; i++, j++)
+        /* TAB in a composition means display glyphs with padding
+           space on the left or right.  */
+        if (COMPOSITION_GLYPH (s->cmp, j) != '\t')
+          {
+            int xx = x + s->cmp->offsets[j * 2];
+            int yy = y - s->cmp->offsets[j * 2 + 1];
+
+            font->driver->draw (s, j, j + 1, xx, yy, false);
+            if (s->face->overstrike)
+              font->driver->draw (s, j, j + 1, xx + 1, yy, false);
+          }
+    }
+  else
+    {
+      Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
+      Lisp_Object glyph;
+      int y = s->ybase;
+      int width = 0;
+
+      for (i = j = s->cmp_from; i < s->cmp_to; i++)
+        {
+          glyph = LGSTRING_GLYPH (gstring, i);
+          if (NILP (LGLYPH_ADJUSTMENT (glyph)))
+            width += LGLYPH_WIDTH (glyph);
+          else
+            {
+              int xoff, yoff, wadjust;
+
+              if (j < i)
+                {
+                  font->driver->draw (s, j, i, x, y, false);
+                  if (s->face->overstrike)
+                    font->driver->draw (s, j, i, x + 1, y, false);
+                  x += width;
+                }
+              xoff = LGLYPH_XOFF (glyph);
+              yoff = LGLYPH_YOFF (glyph);
+              wadjust = LGLYPH_WADJUST (glyph);
+              font->driver->draw (s, i, i + 1, x + xoff, y + yoff, false);
+              if (s->face->overstrike)
+                font->driver->draw (s, i, i + 1, x + xoff + 1, y + yoff, false);
+              x += wadjust;
+              j = i + 1;
+              width = 0;
+            }
+        }
+      if (j < i)
+        {
+          font->driver->draw (s, j, i, x, y, false);
+          if (s->face->overstrike)
+            font->driver->draw (s, j, i, x + 1, y, false);
+        }
+    }
+}
+
+/* Draw the foreground of glyph string S for glyphless characters.  */
+
+static void
+x_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
+{
+  struct glyph *glyph = s->first_glyph;
+  unsigned char2b[8];
+  int x, i, j;
+
+  /* If first glyph of S has a left box line, start drawing the text
+     of S to the right of that box line.  */
+  if (s->face && s->face->box != FACE_NO_BOX && s->first_glyph->left_box_line_p)
+    x = s->x + eabs (s->face->box_horizontal_line_width);
+  else
+    x = s->x;
+
+  s->char2b = char2b;
+
+  for (i = 0; i < s->nchars; i++, glyph++)
+    {
+#ifdef GCC_LINT
+      enum
+      {
+        PACIFY_GCC_BUG_81401 = 1
+      };
+#else
+      enum
+      {
+        PACIFY_GCC_BUG_81401 = 0
+      };
+#endif
+      char buf[7 + PACIFY_GCC_BUG_81401];
+      char *str = NULL;
+      int len = glyph->u.glyphless.len;
+
+      if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_ACRONYM)
+        {
+          if (len > 0 && CHAR_TABLE_P (Vglyphless_char_display)
+              && (CHAR_TABLE_EXTRA_SLOTS (XCHAR_TABLE (Vglyphless_char_display))
+                  >= 1))
+            {
+              Lisp_Object acronym
+                = (!glyph->u.glyphless.for_no_font
+                     ? CHAR_TABLE_REF (Vglyphless_char_display,
+                                       glyph->u.glyphless.ch)
+                     : XCHAR_TABLE (Vglyphless_char_display)->extras[0]);
+              if (STRINGP (acronym))
+                str = SSDATA (acronym);
+            }
+        }
+      else if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_HEX_CODE)
+        {
+          unsigned int ch = glyph->u.glyphless.ch;
+          eassume (ch <= MAX_CHAR);
+          sprintf (buf, "%0*X", ch < 0x10000 ? 4 : 6, ch);
+          str = buf;
+        }
+
+      if (str)
+        {
+          int upper_len = (len + 1) / 2;
+
+          /* It is assured that all LEN characters in STR is ASCII.  */
+          for (j = 0; j < len; j++)
+            char2b[j] = s->font->driver->encode_char (s->font, str[j]) & 0xFFFF;
+          s->font->driver->draw (s, 0, upper_len,
+                                 x + glyph->slice.glyphless.upper_xoff,
+                                 s->ybase + glyph->slice.glyphless.upper_yoff,
+                                 false);
+          s->font->driver->draw (s, upper_len, len,
+                                 x + glyph->slice.glyphless.lower_xoff,
+                                 s->ybase + glyph->slice.glyphless.lower_yoff,
+                                 false);
+        }
+      if (glyph->u.glyphless.method != GLYPHLESS_DISPLAY_THIN_SPACE)
+        pgtk_draw_rectangle (s->f, s->face->foreground, x,
+                             s->ybase - glyph->ascent, glyph->pixel_width - 1,
+                             glyph->ascent + glyph->descent - 1);
+      x += glyph->pixel_width;
+    }
+}
+
+/* Brightness beyond which a color won't have its highlight brightness
+   boosted.
+
+   Nominally, highlight colors for `3d' faces are calculated by
+   brightening an object's color by a constant scale factor, but this
+   doesn't yield good results for dark colors, so for colors who's
+   brightness is less than this value (on a scale of 0-65535) have an
+   use an additional additive factor.
+
+   The value here is set so that the default menu-bar/mode-line color
+   (grey75) will not have its highlights changed at all.  */
+#define HIGHLIGHT_COLOR_DARK_BOOST_LIMIT 48000
+
+/* Allocate a color which is lighter or darker than *PIXEL by FACTOR
+   or DELTA.  Try a color with RGB values multiplied by FACTOR first.
+   If this produces the same color as PIXEL, try a color where all RGB
+   values have DELTA added.  Return the allocated color in *PIXEL.
+   DISPLAY is the X display, CMAP is the colormap to operate on.
+   Value is non-zero if successful.  */
+
+static bool
+x_alloc_lighter_color (struct frame *f, unsigned long *pixel, double factor,
+                       int delta)
+{
+  Emacs_Color color, new;
+  long bright;
+  bool success_p;
+
+  /* Get RGB color values.  */
+  color.pixel = *pixel;
+  pgtk_query_color (f, &color);
+
+  /* Change RGB values by specified FACTOR.  Avoid overflow!  */
+  eassert (factor >= 0);
+  new.red = min (0xffff, factor * color.red);
+  new.green = min (0xffff, factor * color.green);
+  new.blue = min (0xffff, factor * color.blue);
+
+  /* Calculate brightness of COLOR.  */
+  bright = (2 * color.red + 3 * color.green + color.blue) / 6;
+
+  /* We only boost colors that are darker than
+     HIGHLIGHT_COLOR_DARK_BOOST_LIMIT.  */
+  if (bright < HIGHLIGHT_COLOR_DARK_BOOST_LIMIT)
+    /* Make an additive adjustment to NEW, because it's dark enough so
+       that scaling by FACTOR alone isn't enough.  */
+    {
+      /* How far below the limit this color is (0 - 1, 1 being darker).  */
+      double dimness = 1 - (double) bright / HIGHLIGHT_COLOR_DARK_BOOST_LIMIT;
+      /* The additive adjustment.  */
+      int min_delta = delta * dimness * factor / 2;
+
+      if (factor < 1)
+        {
+          new.red = max (0, new.red - min_delta);
+          new.green = max (0, new.green - min_delta);
+          new.blue = max (0, new.blue - min_delta);
+        }
+      else
+        {
+          new.red = min (0xffff, min_delta + new.red);
+          new.green = min (0xffff, min_delta + new.green);
+          new.blue = min (0xffff, min_delta + new.blue);
+        }
+    }
+
+  /* Try to allocate the color.  */
+  new.pixel = new.red >> 8 << 16 | new.green >> 8 << 8 | new.blue >> 8;
+  success_p = true;
+  if (success_p)
+    {
+      if (new.pixel == *pixel)
+        {
+          /* If we end up with the same color as before, try adding
+             delta to the RGB values.  */
+          new.red = min (0xffff, delta + color.red);
+          new.green = min (0xffff, delta + color.green);
+          new.blue = min (0xffff, delta + color.blue);
+          new.pixel = new.red >> 8 << 16 | new.green >> 8 << 8 | new.blue >> 8;
+          success_p = true;
+        }
+      else
+        success_p = true;
+      *pixel = new.pixel;
+    }
+
+  return success_p;
+}
+
+static void
+x_fill_trapezoid_for_relief (struct frame *f, unsigned long color, int x, int y,
+                             int width, int height, int top_p)
+{
+  cairo_t *cr;
+
+  cr = pgtk_begin_cr_clip (f);
+  pgtk_set_cr_source_with_color (f, color);
+  cairo_move_to (cr, top_p ? x : x + height, y);
+  cairo_line_to (cr, x, y + height);
+  cairo_line_to (cr, top_p ? x + width - height : x + width, y + height);
+  cairo_line_to (cr, x + width, y);
+  cairo_fill (cr);
+  pgtk_end_cr_clip (f);
+}
+
+enum corners
+{
+  CORNER_BOTTOM_RIGHT, /* 0 -> pi/2 */
+  CORNER_BOTTOM_LEFT,  /* pi/2 -> pi */
+  CORNER_TOP_LEFT,     /* pi -> 3pi/2 */
+  CORNER_TOP_RIGHT,    /* 3pi/2 -> 2pi */
+  CORNER_LAST
+};
+
+static void
+x_erase_corners_for_relief (struct frame *f, unsigned long color, int x, int y,
+                            int width, int height, double radius, double margin,
+                            int corners)
+{
+  cairo_t *cr;
+  int i;
+
+  cr = pgtk_begin_cr_clip (f);
+  pgtk_set_cr_source_with_color (f, color);
+  for (i = 0; i < CORNER_LAST; i++)
+    if (corners & (1 << i))
+      {
+        double xm, ym, xc, yc;
+
+        if (i == CORNER_TOP_LEFT || i == CORNER_BOTTOM_LEFT)
+          xm = x - margin, xc = xm + radius;
+        else
+          xm = x + width + margin, xc = xm - radius;
+        if (i == CORNER_TOP_LEFT || i == CORNER_TOP_RIGHT)
+          ym = y - margin, yc = ym + radius;
+        else
+          ym = y + height + margin, yc = ym - radius;
+
+        cairo_move_to (cr, xm, ym);
+        cairo_arc (cr, xc, yc, radius, i * M_PI_2, (i + 1) * M_PI_2);
+      }
+  cairo_clip (cr);
+  cairo_rectangle (cr, x, y, width, height);
+  cairo_fill (cr);
+  pgtk_end_cr_clip (f);
+}
+
+/* Set up the foreground color for drawing relief lines of glyph
+   string S.  RELIEF is a pointer to a struct relief containing the GC
+   with which lines will be drawn.  Use a color that is FACTOR or
+   DELTA lighter or darker than the relief's background which is found
+   in S->f->output_data.pgtk->relief_background.  If such a color cannot
+   be allocated, use DEFAULT_PIXEL, instead.  */
+
+static void
+x_setup_relief_color (struct frame *f, struct relief *relief, double factor,
+                      int delta, unsigned long default_pixel)
+{
+  Emacs_GC xgcv;
+  struct pgtk_output *di = FRAME_X_OUTPUT (f);
+  unsigned long pixel;
+  unsigned long background = di->relief_background;
+
+  /* Allocate new color.  */
+  xgcv.foreground = default_pixel;
+  pixel = background;
+  if (x_alloc_lighter_color (f, &pixel, factor, delta))
+    xgcv.foreground = relief->pixel = pixel;
+
+  relief->xgcv = xgcv;
+}
+
+/* Set up colors for the relief lines around glyph string S.  */
+
+static void
+x_setup_relief_colors (struct glyph_string *s)
+{
+  struct pgtk_output *di = FRAME_X_OUTPUT (s->f);
+  unsigned long color;
+
+  if (s->face->use_box_color_for_shadows_p)
+    color = s->face->box_color;
+  else if (s->first_glyph->type == IMAGE_GLYPH && s->img->pixmap
+           && !IMAGE_BACKGROUND_TRANSPARENT (s->img, s->f, 0))
+    color = IMAGE_BACKGROUND (s->img, s->f, 0);
+  else
+    {
+      /* Get the background color of the face.  */
+      color = s->xgcv.background;
+    }
+
+  if (TRUE)
+    {
+      di->relief_background = color;
+      x_setup_relief_color (s->f, &di->white_relief, 1.2, 0x8000,
+                            WHITE_PIX_DEFAULT (s->f));
+      x_setup_relief_color (s->f, &di->black_relief, 0.6, 0x4000,
+                            BLACK_PIX_DEFAULT (s->f));
+    }
+}
+
+static void
+x_set_clip_rectangles (struct frame *f, cairo_t *cr,
+                       NativeRectangle *rectangles, int n)
+{
+  if (n > 0)
+    {
+      for (int i = 0; i < n; i++)
+        {
+          cairo_rectangle (cr, rectangles[i].x, rectangles[i].y,
+                           rectangles[i].width, rectangles[i].height);
+        }
+      cairo_clip (cr);
+    }
+}
+
+/* Draw a relief on frame F inside the rectangle given by LEFT_X,
+   TOP_Y, RIGHT_X, and BOTTOM_Y.  WIDTH is the thickness of the relief
+   to draw, it must be >= 0.  RAISED_P means draw a raised
+   relief.  LEFT_P means draw a relief on the left side of
+   the rectangle.  RIGHT_P means draw a relief on the right
+   side of the rectangle.  CLIP_RECT is the clipping rectangle to use
+   when drawing.  */
+
+static void
+x_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x,
+                    int bottom_y, int vwidth, int hwidth, bool raised_p, bool top_p,
+                    bool bot_p, bool left_p, bool right_p,
+                    NativeRectangle *clip_rect)
+{
+  unsigned long top_left_color, bottom_right_color;
+  int corners = 0;
+
+  cairo_t *cr = pgtk_begin_cr_clip (f);
+
+  if (raised_p)
+    {
+      top_left_color = FRAME_X_OUTPUT (f)->white_relief.xgcv.foreground;
+      bottom_right_color = FRAME_X_OUTPUT (f)->black_relief.xgcv.foreground;
+    }
+  else
+    {
+      top_left_color = FRAME_X_OUTPUT (f)->black_relief.xgcv.foreground;
+      bottom_right_color = FRAME_X_OUTPUT (f)->white_relief.xgcv.foreground;
+    }
+
+  x_set_clip_rectangles (f, cr, clip_rect, 1);
+
+  if (left_p)
+    {
+      pgtk_fill_rectangle (f, top_left_color, left_x, top_y, vwidth,
+                           bottom_y + 1 - top_y);
+      if (top_p)
+        corners |= 1 << CORNER_TOP_LEFT;
+      if (bot_p)
+        corners |= 1 << CORNER_BOTTOM_LEFT;
+    }
+  if (right_p)
+    {
+      pgtk_fill_rectangle (f, bottom_right_color, right_x + 1 - vwidth, top_y,
+                           vwidth, bottom_y + 1 - top_y);
+      if (top_p)
+        corners |= 1 << CORNER_TOP_RIGHT;
+      if (bot_p)
+        corners |= 1 << CORNER_BOTTOM_RIGHT;
+    }
+  if (top_p)
+    {
+      if (!right_p)
+        pgtk_fill_rectangle (f, top_left_color, left_x, top_y,
+                             right_x + 1 - left_x, hwidth);
+      else
+        x_fill_trapezoid_for_relief (f, top_left_color, left_x, top_y,
+                                     right_x + 1 - left_x, hwidth, 1);
+    }
+  if (bot_p)
+    {
+      if (!left_p)
+        pgtk_fill_rectangle (f, bottom_right_color, left_x,
+                             bottom_y + 1 - hwidth, right_x + 1 - left_x, hwidth);
+      else
+        x_fill_trapezoid_for_relief (f, bottom_right_color, left_x,
+                                     bottom_y + 1 - hwidth, right_x + 1 - left_x,
+                                     hwidth, 0);
+    }
+  if (left_p && vwidth > 1)
+    pgtk_fill_rectangle (f, bottom_right_color, left_x, top_y, 1,
+                         bottom_y + 1 - top_y);
+  if (top_p && hwidth > 1)
+    pgtk_fill_rectangle (f, bottom_right_color, left_x, top_y,
+                         right_x + 1 - left_x, 1);
+  if (corners)
+    {
+      x_erase_corners_for_relief (f, FRAME_BACKGROUND_PIXEL (f), left_x, top_y,
+                                  right_x - left_x + 1, bottom_y - top_y + 1, 6,
+                                  1, corners);
+    }
+
+  pgtk_end_cr_clip (f);
+}
+#ifdef HAVE_GTK4
+static void
+pgtk_composite_draw_row_foreign_backgrounds (struct glyph_string *s);
+#endif
+
+static void
+x_draw_box_rect (struct glyph_string *s, int left_x, int top_y, int right_x,
+                 int bottom_y, int vwidth, int hwidth, bool left_p, bool right_p,
+                 NativeRectangle *clip_rect)
+{
+  unsigned long foreground_backup;
+
+  cairo_t *cr = pgtk_begin_cr_clip (s->f);
+
+  foreground_backup = s->xgcv.foreground;
+  s->xgcv.foreground = s->face->box_color;
+
+  x_set_clip_rectangles (s->f, cr, clip_rect, 1);
+#ifdef HAVE_GTK4
+  if (s->face->box < FACE_RAISED_GTK_BUTTON_BOX)
+    {
+#endif
+      /* Top.  */
+      pgtk_fill_rectangle (s->f, s->xgcv.foreground, left_x, top_y,
+                           right_x - left_x + 1, hwidth);
+
+      /* Left.  */
+      if (left_p)
+        pgtk_fill_rectangle (s->f, s->xgcv.foreground, left_x, top_y, vwidth,
+                             bottom_y - top_y + 1);
+
+      /* Bottom.  */
+      pgtk_fill_rectangle (s->f, s->xgcv.foreground, left_x,
+                           bottom_y - hwidth + 1, right_x - left_x + 1, hwidth);
+
+      /* Right.  */
+      if (right_p)
+        pgtk_fill_rectangle (s->f, s->xgcv.foreground, right_x - vwidth + 1,
+                             top_y, vwidth, bottom_y - top_y + 1);
+#ifdef HAVE_GTK4
+    }
+  else
+    {
+      if (s->face->box <= FACE_SUNKEN_GTK_BUTTON_BOX)
+	{
+	  pgtk_end_cr_clip (s->f);
+	  return;
+	}
+      struct glyph_string *next = s->next;
+      while (next && next->y == s->y && next->face->box == s->face->box)
+	{
+	  next->gtk_bi_composite_start = 1;
+          int _width, _right_x, _top_y, _bottom_y, last_x;
+          struct glyph *last_glyph;
+
+          last_x = ((s->row->full_width_p && !s->w->pseudo_window_p)
+		    ? WINDOW_RIGHT_EDGE_X (s->w)
+		    : window_box_right (s->w, s->area));
+
+          /* The glyph that may have a right box line.  */
+          last_glyph = (s->cmp || s->img ? s->first_glyph
+                                         : s->first_glyph + s->nchars - 1);
+
+          _width = eabs (s->face->box_horizontal_line_width);
+          _right_x = (s->row->full_width_p && s->extends_to_end_of_line_p
+                       ? last_x - 1
+                       : min (last_x, s->x + s->background_width) - 1);
+          _top_y = s->y;
+          _bottom_y = _top_y + s->height - 1;
+
+          left_p = (s->first_glyph->left_box_line_p
+                    || (s->hl == DRAW_MOUSE_FACE
+                        && (s->prev == NULL || s->prev->hl != s->hl)));
+          right_p = (last_glyph->right_box_line_p
+                     || (s->hl == DRAW_MOUSE_FACE
+                         && (s->next == NULL || s->next->hl != s->hl)));
+	  right_x = _right_x;
+	  bottom_y = _bottom_y;
+	  hwidth += _width;
+	  next = next->next;
+        }
+      right_x += 2;
+      pgtk_clear_area (s->f, left_x, top_y, 1 + (right_x - hwidth) - left_x,
+                       clip_rect->height);
+      bool chk = s->face->box >= FACE_UNCHECKED_GTK_CHECKBOX;
+      gtk_style_context_set_state (chk ? chk_style_context : btn_style_context,
+                                   s->face->box % 2 != 0
+                                     ? GTK_STATE_FLAG_ACTIVE
+                                         | GTK_STATE_FLAG_CHECKED
+                                     : GTK_STATE_FLAG_NORMAL);
+      gtk_render_background (chk ? chk_style_context : btn_style_context, cr,
+                             left_x, top_y - 1, (right_x - hwidth) - left_x,
+                             clip_rect->height + 1);
+      gtk_render_focus (chk ? chk_style_context : btn_style_context, cr, left_x,
+                        top_y - 1, (right_x - hwidth) - left_x,
+                        clip_rect->height + 1);
+      gtk_render_frame (chk ? chk_style_context : btn_style_context, cr, left_x,
+                        top_y - 1, (right_x - hwidth) - left_x,
+                        clip_rect->height + 1);
+      gtk_render_check (chk ? chk_style_context : btn_style_context, cr, left_x,
+                        top_y - 1, (right_x - hwidth) - left_x,
+                        clip_rect->height + 1);
+    }
+#endif
+
+  s->xgcv.foreground = foreground_backup;
+
+  pgtk_end_cr_clip (s->f);
+}
+
+/* Draw a box around glyph string S.  */
+
+static void
+x_draw_glyph_string_box (struct glyph_string *s)
+{
+  int vwidth, hwidth, left_x, right_x, top_y, bottom_y, last_x;
+  bool raised_p, left_p, right_p;
+  struct glyph *last_glyph;
+  NativeRectangle clip_rect;
+
+  last_x = ((s->row->full_width_p && !s->w->pseudo_window_p)
+              ? WINDOW_RIGHT_EDGE_X (s->w)
+              : window_box_right (s->w, s->area));
+
+  /* The glyph that may have a right box line.  */
+  last_glyph
+    = (s->cmp || s->img ? s->first_glyph : s->first_glyph + s->nchars - 1);
+
+  vwidth = eabs (s->face->box_vertical_line_width);
+  hwidth = eabs (s->face->box_horizontal_line_width);
+  raised_p = s->face->box == FACE_RAISED_BOX;
+  left_x = s->x;
+  right_x = (s->row->full_width_p && s->extends_to_end_of_line_p
+               ? last_x - 1
+               : min (last_x, s->x + s->background_width) - 1);
+  top_y = s->y;
+  bottom_y = top_y + s->height - 1;
+
+  left_p = (s->first_glyph->left_box_line_p
+            || (s->hl == DRAW_MOUSE_FACE
+                && (s->prev == NULL || s->prev->hl != s->hl)));
+  right_p = (last_glyph->right_box_line_p
+             || (s->hl == DRAW_MOUSE_FACE
+                 && (s->next == NULL || s->next->hl != s->hl)));
+
+  get_glyph_string_clip_rect (s, &clip_rect);
+
+  if (s->face->box == FACE_SIMPLE_BOX
+      || s->face->box >= FACE_RAISED_GTK_BUTTON_BOX)
+    x_draw_box_rect (s, left_x, top_y, right_x, bottom_y, vwidth,
+		     hwidth, left_p, right_p, &clip_rect);
+  else
+    {
+      x_setup_relief_colors (s);
+      x_draw_relief_rect (s->f, left_x, top_y, right_x, bottom_y, vwidth,
+                          hwidth, raised_p, true, true, left_p, right_p, &clip_rect);
+    }
+}
+
+static void
+x_get_scale_factor (int *scale_x, int *scale_y)
+{
+  *scale_x = *scale_y = 1;
+}
+
+static void
+x_draw_horizontal_wave (struct frame *f, unsigned long color, int x, int y,
+                        int width, int height, int wave_length)
+{
+  if (NILP (Vpgtk_flat_underwave_style))
+    {
+      cairo_t *cr;
+      double dx = wave_length, dy = height - 1;
+      int xoffset, n;
+
+      cr = pgtk_begin_cr_clip (f);
+      pgtk_set_cr_source_with_color (f, color);
+      cairo_rectangle (cr, x, y, width, height);
+      cairo_clip (cr);
+
+      if (x >= 0)
+        {
+          xoffset = x % (wave_length * 2);
+          if (xoffset == 0)
+            xoffset = wave_length * 2;
+        }
+      else
+        xoffset = x % (wave_length * 2) + wave_length * 2;
+      n = (width + xoffset) / wave_length + 1;
+      if (xoffset > wave_length)
+        {
+          xoffset -= wave_length;
+          --n;
+          y += height - 1;
+          dy = -dy;
+        }
+
+      cairo_move_to (cr, x - xoffset + 0.5, y + 0.5);
+      while (--n >= 0)
+        {
+          cairo_rel_line_to (cr, dx, dy);
+          dy = -dy;
+        }
+      cairo_set_line_width (cr, 1);
+      cairo_stroke (cr);
+      pgtk_end_cr_clip (f);
+    }
+  else
+    {
+      pgtk_fill_rectangle (f, color, x, y + 1, width, height / 3);
+    }
+}
+
+/*
+   Draw a wavy line under S. The wave fills wave_height pixels from y0.
+
+                    x0         wave_length = 2
+                                 --
+                y0   *   *   *   *   *
+                     |* * * * * * * * *
+    wave_height = 3  | *   *   *   *
+
+*/
+static void
+x_draw_underwave (struct glyph_string *s, unsigned long color)
+{
+  /* Adjust for scale/HiDPI.  */
+  int scale_x, scale_y;
+
+  x_get_scale_factor (&scale_x, &scale_y);
+
+  int wave_height = 3 * scale_y, wave_length = 2 * scale_x;
+
+  x_draw_horizontal_wave (s->f, color, s->x, s->ybase - wave_height + 3,
+                          s->width, wave_height, wave_length);
+}
+
+/* Draw a relief around the image glyph string S.  */
+
+static void
+x_draw_image_relief (struct glyph_string *s)
+{
+  int x1, y1, thick;
+  bool raised_p, top_p, bot_p, left_p, right_p;
+  int extra_x, extra_y;
+  NativeRectangle r;
+  int x = s->x;
+  int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
+
+  /* If first glyph of S has a left box line, start drawing it to the
+     right of that line.  */
+  if (s->face->box != FACE_NO_BOX && s->first_glyph->left_box_line_p
+      && s->slice.x == 0)
+    x += eabs (s->face->box_horizontal_line_width);
+
+  /* If there is a margin around the image, adjust x- and y-position
+     by that margin.  */
+  if (s->slice.x == 0)
+    x += s->img->hmargin;
+  if (s->slice.y == 0)
+    y += s->img->vmargin;
+
+  if (s->hl == DRAW_IMAGE_SUNKEN || s->hl == DRAW_IMAGE_RAISED)
+    {
+      thick = (tab_bar_button_relief < 0
+                 ? DEFAULT_TAB_BAR_BUTTON_RELIEF
+                 : (tool_bar_button_relief < 0
+                      ? DEFAULT_TOOL_BAR_BUTTON_RELIEF
+                      : min (tool_bar_button_relief, 1000000)));
+      raised_p = s->hl == DRAW_IMAGE_RAISED;
+    }
+  else
+    {
+      thick = eabs (s->img->relief);
+      raised_p = s->img->relief > 0;
+    }
+
+  x1 = x + s->slice.width - 1;
+  y1 = y + s->slice.height - 1;
+
+  extra_x = extra_y = 0;
+  if (s->face->id == TAB_BAR_FACE_ID)
+    {
+      if (CONSP (Vtab_bar_button_margin)
+          && FIXNUMP (XCAR (Vtab_bar_button_margin))
+          && FIXNUMP (XCDR (Vtab_bar_button_margin)))
+        {
+          extra_x = XFIXNUM (XCAR (Vtab_bar_button_margin));
+          extra_y = XFIXNUM (XCDR (Vtab_bar_button_margin));
+        }
+      else if (FIXNUMP (Vtab_bar_button_margin))
+        extra_x = extra_y = XFIXNUM (Vtab_bar_button_margin);
+    }
+
+  if (s->face->id == TOOL_BAR_FACE_ID)
+    {
+      if (CONSP (Vtool_bar_button_margin)
+          && INTEGERP (XCAR (Vtool_bar_button_margin))
+          && INTEGERP (XCDR (Vtool_bar_button_margin)))
+        {
+          extra_x = XFIXNUM (XCAR (Vtool_bar_button_margin));
+          extra_y = XFIXNUM (XCDR (Vtool_bar_button_margin));
+        }
+      else if (INTEGERP (Vtool_bar_button_margin))
+        extra_x = extra_y = XFIXNUM (Vtool_bar_button_margin);
+    }
+
+  top_p = bot_p = left_p = right_p = false;
+
+  if (s->slice.x == 0)
+    x -= thick + extra_x, left_p = true;
+  if (s->slice.y == 0)
+    y -= thick + extra_y, top_p = true;
+  if (s->slice.x + s->slice.width == s->img->width)
+    x1 += thick + extra_x, right_p = true;
+  if (s->slice.y + s->slice.height == s->img->height)
+    y1 += thick + extra_y, bot_p = true;
+
+  x_setup_relief_colors (s);
+  get_glyph_string_clip_rect (s, &r);
+  x_draw_relief_rect (s->f, x, y, x1, y1, thick, thick,
+		      raised_p, top_p, bot_p, left_p,
+                      right_p, &r);
+}
+
+/* Draw part of the background of glyph string S.  X, Y, W, and H
+   give the rectangle to draw.  */
+
+static void
+x_draw_glyph_string_bg_rect (struct glyph_string *s, int x, int y, int w, int h)
+{
+  if (s->stippled_p)
+    {
+      /* Fill background with a stipple pattern.  */
+
+      fill_background (s, x, y, w, h);
+    }
+  else
+    x_clear_glyph_string_rect (s, x, y, w, h, true);
+}
+
+static void
+pgtk_cr_draw_image (struct frame *f, Emacs_GC *gc, cairo_pattern_t *image,
+                    int src_x, int src_y, int width, int height, int dest_x,
+                    int dest_y, bool overlay_p);
+
+/* Draw foreground of image glyph string S.  */
+
+static void
+x_draw_image_foreground (struct glyph_string *s)
+{
+  int x = s->x;
+  int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
+
+  /* If first glyph of S has a left box line, start drawing it to the
+     right of that line.  */
+  if (s->face->box != FACE_NO_BOX && s->first_glyph->left_box_line_p
+      && s->slice.x == 0)
+    x += eabs (s->face->box_horizontal_line_width);
+
+  /* If there is a margin around the image, adjust x- and y-position
+     by that margin.  */
+  if (s->slice.x == 0)
+    x += s->img->hmargin;
+  if (s->slice.y == 0)
+    y += s->img->vmargin;
+
+  if (s->img->cr_data)
+    {
+      x_set_glyph_string_clipping (s, FRAME_CR_CONTEXT (s->f));
+      pgtk_cr_draw_image (s->f, &s->xgcv, s->img->cr_data,
+			  s->slice.x, s->slice.y, s->slice.width,
+			  s->slice.height, x, y, true);
+      if (!s->img->mask)
+	{
+	  if (s->hl == DRAW_CURSOR)
+	    {
+	      int relief = eabs (s->img->relief);
+	      pgtk_draw_rectangle (s->f, s->xgcv.foreground,
+				   x - relief, y - relief,
+				   s->slice.width + relief * 2 - 2,
+				   s->slice.height + relief * 2 - 1);
+	    }
+	}
+    }
+  else
+    {
+      pgtk_draw_rectangle (s->f, s->xgcv.foreground, x, y, s->slice.width - 1,
+			   s->slice.height - 1);
+    }
+}
+
+/* Draw image glyph string S.
+
+            s->y
+   s->x      +-------------------------
+             |   s->face->box
+             |
+             |     +-------------------------
+             |     |  s->img->margin
+             |     |
+             |     |       +-------------------
+             |     |       |  the image
+
+ */
+
+static void
+x_draw_image_glyph_string (struct glyph_string *s)
+{
+  int box_line_hwidth = max (s->face->box_vertical_line_width, 0);
+  int box_line_vwidth = max (s->face->box_horizontal_line_width, 0);
+  int height;
+
+  height = s->height;
+  if (s->slice.y == 0)
+    height -= box_line_vwidth;
+  if (s->slice.y + s->slice.height >= s->img->height)
+    height -= box_line_vwidth;
+
+  /* Fill background with face under the image.  Do it only if row is
+     taller than image or if image has a clip mask to reduce
+     flickering.  */
+  s->stippled_p = s->face->stipple != 0;
+  if (height > s->slice.height || s->img->hmargin || s->img->vmargin
+      || s->img->mask || s->img->pixmap == 0 || s->stippled_p
+      || s->width != s->background_width)
+    {
+      if (s->img->mask)
+        {
+          fill_background (s, s->x, s->y, s->background_width, s->height);
+        }
+      else
+        {
+          int x = s->x;
+          int y = s->y;
+          int width = s->background_width;
+
+          if (s->first_glyph->left_box_line_p && s->slice.x == 0)
+            {
+              x += box_line_hwidth;
+              width -= box_line_hwidth;
+            }
+
+          if (s->slice.y == 0)
+            y += box_line_vwidth;
+
+          fill_background (s, x, y, width, height);
+        }
+
+      s->background_filled_p = true;
+    }
+
+  x_draw_image_foreground (s);
+
+  /* If we must draw a relief around the image, do it.  */
+  if (s->img->relief || s->hl == DRAW_IMAGE_RAISED
+      || s->hl == DRAW_IMAGE_SUNKEN)
+    x_draw_image_relief (s);
+}
+
+/* Draw stretch glyph string S.  */
+
+static void
+x_draw_stretch_glyph_string (struct glyph_string *s)
+{
+  eassert (s->first_glyph->type == STRETCH_GLYPH);
+
+  if (s->hl == DRAW_CURSOR && !x_stretch_cursor_p)
+    {
+      /* If `x-stretch-cursor' is nil, don't draw a block cursor as
+         wide as the stretch glyph.  */
+      int width, background_width = s->background_width;
+      int x = s->x;
+
+      if (!s->row->reversed_p)
+        {
+          int left_x = window_box_left_offset (s->w, TEXT_AREA);
+
+          if (x < left_x)
+            {
+              background_width -= left_x - x;
+              x = left_x;
+            }
+        }
+      else
+        {
+          /* In R2L rows, draw the cursor on the right edge of the
+             stretch glyph.  */
+          int right_x = window_box_right (s->w, TEXT_AREA);
+
+          if (x + background_width > right_x)
+            background_width -= x - right_x;
+          x += background_width;
+        }
+      width = min (FRAME_COLUMN_WIDTH (s->f), background_width);
+      if (s->row->reversed_p)
+        x -= width;
+
+      /* Draw cursor.  */
+      x_draw_glyph_string_bg_rect (s, x, s->y, width, s->height);
+
+      /* Clear rest using the GC of the original non-cursor face.  */
+      if (width < background_width)
+        {
+          int y = s->y;
+          int w = background_width - width, h = s->height;
+          NativeRectangle r;
+          unsigned long color;
+
+          if (!s->row->reversed_p)
+            x += width;
+          else
+            x = s->x;
+          if (s->row->mouse_face_p && cursor_in_mouse_face_p (s->w))
+            {
+              x_set_mouse_face_gc (s);
+              color = s->xgcv.foreground;
+            }
+          else
+            color = s->face->foreground;
+
+          cairo_t *cr = pgtk_begin_cr_clip (s->f);
+
+          get_glyph_string_clip_rect (s, &r);
+          x_set_clip_rectangles (s->f, cr, &r, 1);
+
+          if (s->face->stipple)
+            {
+              /* Fill background with a stipple pattern.  */
+              fill_background (s, x, y, w, h);
+            }
+          else
+            {
+              pgtk_fill_rectangle (s->f, color, x, y, w, h);
+            }
+
+          pgtk_end_cr_clip (s->f);
+        }
+    }
+  else if (!s->background_filled_p)
+    {
+      int background_width = s->background_width;
+      int x = s->x, left_x = window_box_left_offset (s->w, TEXT_AREA);
+
+      /* Don't draw into left margin, fringe or scrollbar area
+         except for header line and mode line.  */
+      if (x < left_x && !s->row->mode_line_p)
+        {
+          background_width -= left_x - x;
+          x = left_x;
+        }
+      if (background_width > 0)
+        x_draw_glyph_string_bg_rect (s, x, s->y, background_width, s->height);
+    }
+
+  s->background_filled_p = true;
+}
+
+#ifdef HAVE_GTK4
+struct glyph_data
+{
+  int x, y, height, width;
+  struct face *face;
+  struct frame *frame;
+  struct glyph gl;
+};
+
+static void
+pgtk_draw_glyph_group_foreign_backgrounds (GSList *l, struct glyph_string *curstr, struct frame *f)
+{
+  struct glyph_data *data = l->data;
+  int x = data->x;
+  int y = data->y;
+  int width = 0;
+  int height = 0;
+
+  for (GSList *lt = l; lt; lt = lt->next)
+    {
+      struct glyph_data *dat = lt->data;
+      height = max (dat->height, height);
+      width += dat->width;
+    }
+  struct glyph_string *first = curstr;
+  for (struct glyph_string *f = curstr; f && f->prev; f = f->prev)
+    first = f;
+  if (data->face->box == FACE_RAISED_GTK_BUTTON_BOX ||
+      data->face->box == FACE_SUNKEN_GTK_BUTTON_BOX ||
+      data->face->box == FACE_PRELIGHT_GTK_BUTTON_BOX)
+    {
+      if (data->face->box == FACE_RAISED_GTK_BUTTON_BOX)
+	gtk_style_context_set_state (btn_style_context, GTK_STATE_FLAG_NORMAL);
+      else if (data->face->box == FACE_SUNKEN_GTK_BUTTON_BOX)
+	gtk_style_context_set_state (btn_style_context, GTK_STATE_FLAG_CHECKED |
+				     GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_ACTIVE);
+      else
+	gtk_style_context_set_state (btn_style_context, GTK_STATE_FLAG_PRELIGHT
+				     | GTK_STATE_FLAG_FOCUS_VISIBLE);
+      cairo_t *cr = pgtk_begin_cr_clip (f);
+      pgtk_clear_area (f, x - 2, y, width + 3, height);
+      gtk_render_background (btn_style_context, cr, x - 2, y, width + 2, height);
+      gtk_render_frame (btn_style_context, cr, x - 2, y, width + 2, height);
+      pgtk_end_cr_clip (f);
+
+    }
+  else
+    {
+      if (data->face->box == FACE_FOCUSED_GTK_ENTRY_BOX)
+	gtk_style_context_set_state (ent_style_context, GTK_STATE_FLAG_ACTIVE |
+				     GTK_STATE_FLAG_FOCUSED);
+      else
+	gtk_style_context_set_state (ent_style_context, GTK_STATE_FLAG_ACTIVE);
+      cairo_t *cr = pgtk_begin_cr_clip (f);
+      pgtk_clear_area (f, x - 2, y, width + 3, height);
+      gtk_render_background (ent_style_context, cr, x - 2, y, width + 2,
+                             height);
+      gtk_render_frame (ent_style_context, cr, x - 2, y, width + 2, height);
+      gtk_render_focus (ent_style_context, cr, x - 2, y, width + 2, height);
+      pgtk_end_cr_clip (f);
+    }
+
+  for (struct glyph_string *f = first; f; f = f->next)
+    {
+      if (f->face->box >= FACE_RAISED_GTK_BUTTON_BOX)
+        {
+          if (f->first_glyph->type == CHAR_GLYPH)
+            x_draw_glyph_string_foreground (f);
+          else if (f->first_glyph->type == COMPOSITE_GLYPH)
+            x_draw_composite_glyph_string_foreground (f);
+          else if (f->first_glyph->type == IMAGE_GLYPH)
+            x_draw_image_glyph_string (f);
+          else if (f->first_glyph->type == GLYPHLESS_GLYPH)
+            x_draw_glyphless_glyph_string_foreground (f);
+        }
+    }
+}
+
+static struct face *
+pgtk_get_mouse_face (struct glyph_string *s)
+{
+  int face_id;
+  struct face *face;
+  face_id = MOUSE_HL_INFO (s->f)->mouse_face_face_id;
+  face = FACE_FROM_ID_OR_NULL (s->f, face_id);
+  if (face == NULL)
+    face = FACE_FROM_ID (s->f, MOUSE_FACE_ID);
+  return face;
+}
+
+static void
+draw_foreign_bk (struct glyph_string *s, bool flag, enum face_box_type box)
+{
+  if (flag)
+    {
+      struct glyph_row *row = s->row;
+#define glyphs row->glyphs[TEXT_AREA]
+      int sidx = 0;
+      int idx = 0;
+      GSList **sl = xmalloc ((idx + 1) * sizeof *sl);
+      *sl = NULL;
+      int x = 0;
+      while (sidx < row->used[TEXT_AREA])
+        {
+          struct glyph gl = glyphs[sidx];
+          ++sidx;
+          x += gl.pixel_width;
+	  if (gl.face_id >= s->f->face_cache->used)
+	    continue;
+          struct face *face = FACE_FROM_ID_OR_NULL (s->f, gl.face_id);
+          while (face && face->box == box && sidx < row->used[TEXT_AREA])
+            {
+              struct glyph_data *d = xmalloc (sizeof *d);
+              d->x = s->w->pixel_left + WINDOW_LEFT_MARGIN_WIDTH (s->w) + row->x
+                     + x;
+              d->y = s->y;
+              if (s->hl == DRAW_MOUSE_FACE)
+                {
+                  if (d->x >= s->x && d->x <= s->x + s->width)
+                    d->face = pgtk_get_mouse_face (s);
+                  else
+                    d->face = face;
+                }
+              else
+                {
+                  d->face = face;
+                }
+              d->height = gl.ascent + gl.descent;
+              d->width = gl.pixel_width;
+              d->gl = gl;
+              sl[idx] = g_slist_append (sl[idx], d);
+              ++sidx;
+              gl = glyphs[sidx];
+              x += gl.pixel_width;
+              face = FACE_FROM_ID (s->f, gl.face_id);
+            }
+          if (sidx < row->used[TEXT_AREA] && sl[idx])
+            {
+              ++idx;
+              sl = xrealloc (sl, (idx + 1) * sizeof *s);
+              if (!sl)
+                emacs_abort ();
+              sl[idx] = NULL;
+            }
+        }
+      for (int i = 0; i < idx; ++i)
+        {
+          if (((struct glyph_data *) sl[i]->data)->x >= s->x)
+            pgtk_draw_glyph_group_foreign_backgrounds (sl[i], s, s->f);
+          g_slist_free_full (sl[i], xfree);
+        }
+      xfree (sl);
+    }
+#undef glyphs
+}
+
+static void
+pgtk_composite_draw_row_foreign_backgrounds (struct glyph_string *s)
+{
+  block_input ();
+  draw_foreign_bk (s, true, FACE_RAISED_GTK_BUTTON_BOX);
+  draw_foreign_bk (s, true, FACE_SUNKEN_GTK_BUTTON_BOX);
+  draw_foreign_bk (s, true, FACE_PRELIGHT_GTK_BUTTON_BOX);
+  draw_foreign_bk (s, true, FACE_UNFOCUSED_GTK_ENTRY_BOX);
+  draw_foreign_bk (s, true, FACE_FOCUSED_GTK_ENTRY_BOX);
+  unblock_input ();
+}
+#endif
+
+static void
+pgtk_draw_glyph_string (struct glyph_string *s)
+{
+  bool relief_drawn_p = false;
+#ifdef HAVE_GTK4
+  if (s->face->box >= FACE_RAISED_GTK_BUTTON_BOX)
+    {
+      struct glyph_string *last = s->prev;
+      while (last && last->y == s->y &&
+	     (last->width <= 5 || last->face->box == s->face->box))
+        {
+	  if (last->width <= 5)
+	    continue;
+          last->gtk_bi_composite_start = true;
+	  s = last;
+          last = last->prev;
+        }
+      s->gtk_bi_composite_start = false;
+      struct glyph_string *next = s->next;
+      while (next && next->y == s->y && next->face->box == s->face->box)
+        {
+          next->gtk_bi_composite_start = true;
+          next = next->next;
+        }
+    }
+  else
+    {
+      s->gtk_bi_composite_start = false;
+    }
+#endif
+  /* If S draws into the background of its successors, draw the
+     background of the successors first so that S can draw into it.
+     This makes S->next use XDrawString instead of XDrawImageString.  */
+  if (s->next && s->right_overhang && !s->for_overlaps)
+    {
+      int width;
+      struct glyph_string *next;
+
+      for (width = 0, next = s->next; next && width < s->right_overhang;
+           width += next->width, next = next->next)
+        if (next->first_glyph->type != IMAGE_GLYPH)
+          {
+            cairo_t *cr = pgtk_begin_cr_clip (next->f);
+            x_set_glyph_string_gc (next);
+            x_set_glyph_string_clipping (next, cr);
+            if (next->first_glyph->type == STRETCH_GLYPH)
+              x_draw_stretch_glyph_string (next);
+            else
+              x_draw_glyph_string_background (next, true);
+            next->num_clips = 0;
+            pgtk_end_cr_clip (next->f);
+          }
+    }
+
+  /* Set up S->gc, set clipping and draw S.  */
+  x_set_glyph_string_gc (s);
+
+  cairo_t *cr = pgtk_begin_cr_clip (s->f);
+  /* Draw relief (if any) in advance for char/composition so that the
+     glyph string can be drawn over it.  */
+  if (!s->for_overlaps && s->face->box != FACE_NO_BOX
+      && (s->first_glyph->type == CHAR_GLYPH
+          || s->first_glyph->type == COMPOSITE_GLYPH))
+
+    {
+      x_set_glyph_string_clipping (s, cr);
+      x_draw_glyph_string_background (s, true);
+      x_draw_glyph_string_box (s);
+      x_set_glyph_string_clipping (s, cr);
+      relief_drawn_p = true;
+    }
+  else if (!s->clip_head
+           && !s->clip_tail
+           && ((s->prev && s->prev->hl != s->hl && s->left_overhang)
+               || (s->next && s->next->hl != s->hl && s->right_overhang)))
+      x_set_glyph_string_clipping_exactly (s, s, cr);
+  else
+      x_set_glyph_string_clipping (s, cr);
+
+  switch (s->first_glyph->type)
+    {
+    case IMAGE_GLYPH:
+      x_draw_image_glyph_string (s);
+      break;
+
+    case XWIDGET_GLYPH:
+      x_draw_xwidget_glyph_string (s);
+      break;
+
+    case STRETCH_GLYPH:
+      x_draw_stretch_glyph_string (s);
+      break;
+
+    case CHAR_GLYPH:
+      if (s->for_overlaps)
+        s->background_filled_p = true;
+      else
+        x_draw_glyph_string_background (s, false);
+      x_draw_glyph_string_foreground (s);
+      break;
+
+    case COMPOSITE_GLYPH:
+      if (s->for_overlaps
+          || (s->cmp_from > 0 && !s->first_glyph->u.cmp.automatic))
+        s->background_filled_p = true;
+      else
+        x_draw_glyph_string_background (s, true);
+      x_draw_composite_glyph_string_foreground (s);
+      break;
+
+    case GLYPHLESS_GLYPH:
+      if (s->for_overlaps)
+        s->background_filled_p = true;
+      else
+        x_draw_glyph_string_background (s, true);
+      x_draw_glyphless_glyph_string_foreground (s);
+      break;
+
+    default:
+      emacs_abort ();
+    }
+
+  if (!s->for_overlaps)
+    {
+      /* Draw underline.  */
+      if (s->face->underline)
+        {
+          if (s->face->underline == FACE_UNDER_WAVE)
+            {
+              if (s->face->underline_defaulted_p)
+                x_draw_underwave (s, s->xgcv.foreground);
+              else
+                {
+                  x_draw_underwave (s, s->face->underline_color);
+                }
+            }
+          else if (s->face->underline == FACE_UNDER_LINE)
+            {
+              unsigned long thickness, position;
+              int y;
+
+              if (s->prev && s->prev->face->underline
+                  && s->prev->face->underline == FACE_UNDER_LINE)
+                {
+                  /* We use the same underline style as the previous one.  */
+                  thickness = s->prev->underline_thickness;
+                  position = s->prev->underline_position;
+                }
+              else
+                {
+                  struct font *font = font_for_underline_metrics (s);
+
+                  /* Get the underline thickness.  Default is 1 pixel.  */
+                  if (font && font->underline_thickness > 0)
+                    thickness = font->underline_thickness;
+                  else
+                    thickness = 1;
+                  if (x_underline_at_descent_line)
+                    position = (s->height - thickness) - (s->ybase - s->y);
+                  else
+                    {
+                      /* Get the underline position.  This is the recommended
+                         vertical offset in pixels from the baseline to the top
+                         of the underline.  This is a signed value according to
+                         the specs, and its default is
+
+                         ROUND ((maximum descent) / 2), with
+                         ROUND(x) = floor (x + 0.5)  */
+
+                      if (x_use_underline_position_properties && font
+                          && font->underline_position >= 0)
+                        position = font->underline_position;
+                      else if (font)
+                        position = (font->descent + 1) / 2;
+                      else
+                        position = underline_minimum_offset;
+                    }
+                  position = max (position, underline_minimum_offset);
+                }
+              /* Check the sanity of thickness and position.  We should
+                 avoid drawing underline out of the current line area.  */
+              if (s->y + s->height <= s->ybase + position)
+                position = (s->height - 1) - (s->ybase - s->y);
+              if (s->y + s->height < s->ybase + position + thickness)
+                thickness = (s->y + s->height) - (s->ybase + position);
+              s->underline_thickness = thickness;
+              s->underline_position = position;
+              y = s->ybase + position;
+              if (s->face->underline_defaulted_p)
+                pgtk_fill_rectangle (s->f, s->xgcv.foreground, s->x, y,
+                                     s->width, thickness);
+              else
+                {
+                  pgtk_fill_rectangle (s->f, s->face->underline_color, s->x, y,
+                                       s->width, thickness);
+                }
+            }
+        }
+      /* Draw overline.  */
+      if (s->face->overline_p)
+        {
+          unsigned long dy = 0, h = 1;
+
+          if (s->face->overline_color_defaulted_p)
+            pgtk_fill_rectangle (s->f, s->xgcv.foreground, s->x, s->y + dy,
+                                 s->width, h);
+          else
+            {
+              pgtk_fill_rectangle (s->f, s->face->overline_color, s->x,
+                                   s->y + dy, s->width, h);
+            }
+        }
+
+      /* Draw strike-through.  */
+      if (s->face->strike_through_p)
+        {
+          /* Y-coordinate and height of the glyph string's first
+             glyph.  We cannot use s->y and s->height because those
+             could be larger if there are taller display elements
+             (e.g., characters displayed with a larger font) in the
+             same glyph row.  */
+          int glyph_y = s->ybase - s->first_glyph->ascent;
+          int glyph_height = s->first_glyph->ascent + s->first_glyph->descent;
+          /* Strike-through width and offset from the glyph string's
+             top edge.  */
+          unsigned long h = 1;
+          unsigned long dy = (glyph_height - h) / 2;
+
+          if (s->face->strike_through_color_defaulted_p)
+            pgtk_fill_rectangle (s->f, s->xgcv.foreground, s->x, glyph_y + dy,
+                                 s->width, h);
+          else
+            {
+              pgtk_fill_rectangle (s->f, s->face->strike_through_color, s->x,
+                                   glyph_y + dy, s->width, h);
+            }
+        }
+
+      /* Draw relief if not yet drawn.  */
+      if (!relief_drawn_p && s->face->box != FACE_NO_BOX)
+        x_draw_glyph_string_box (s);
+
+      if (s->prev)
+        {
+          struct glyph_string *prev;
+
+          for (prev = s->prev; prev; prev = prev->prev)
+            if (prev->hl != s->hl
+                && prev->x + prev->width + prev->right_overhang > s->x)
+              {
+                /* As prev was drawn while clipped to its own area, we
+                   must draw the right_overhang part using s->hl now.  */
+                enum draw_glyphs_face save = prev->hl;
+
+                prev->hl = s->hl;
+                PGTK_TRACE ("pgtk_draw_glyph_string: 3.");
+                x_set_glyph_string_gc (prev);
+                cairo_save (cr);
+                x_set_glyph_string_clipping_exactly (s, prev, cr);
+                if (prev->first_glyph->type == CHAR_GLYPH)
+                  x_draw_glyph_string_foreground (prev);
+                else
+                  x_draw_composite_glyph_string_foreground (prev);
+                prev->hl = save;
+                prev->num_clips = 0;
+                cairo_restore (cr);
+              }
+        }
+
+      if (s->next)
+        {
+          struct glyph_string *next;
+
+          for (next = s->next; next; next = next->next)
+            if (next->hl != s->hl
+                && next->x - next->left_overhang < s->x + s->width)
+              {
+                /* As next will be drawn while clipped to its own area,
+                   we must draw the left_overhang part using s->hl now.  */
+                enum draw_glyphs_face save = next->hl;
+
+                next->hl = s->hl;
+                PGTK_TRACE ("pgtk_draw_glyph_string: 4.");
+                x_set_glyph_string_gc (next);
+                cairo_save (cr);
+                x_set_glyph_string_clipping_exactly (s, next, cr);
+                if (next->first_glyph->type == CHAR_GLYPH)
+                  x_draw_glyph_string_foreground (next);
+                else
+                  x_draw_composite_glyph_string_foreground (next);
+                cairo_restore (cr);
+                next->hl = save;
+                next->num_clips = 0;
+                next->clip_head = s->next;
+              }
+        }
+    }
+#ifdef HAVE_GTK4
+  pgtk_composite_draw_row_foreign_backgrounds (s);
+#endif
+  /* Reset clipp#ing.  */
+  pgtk_end_cr_clip (s->f);
+  s->num_clips = 0;
+}
+
+/* RIF: Define cursor CURSOR on frame F.  */
+
+static void
+pgtk_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
+{
+  if (!f->pointer_invisible && FRAME_X_OUTPUT (f)->current_cursor != cursor)
+    {
+#ifndef HAVE_GTK4
+      gdk_window_set_cursor (gtk_widget_get_window (FRAME_GTK_WIDGET (f)),
+			     cursor);
+#else
+      gtk_widget_set_cursor (FRAME_GTK_WIDGET (f), cursor);
+#endif
+    }
+  FRAME_X_OUTPUT (f)->current_cursor = cursor;
+}
+
+static void
+pgtk_after_update_window_line (struct window *w, struct glyph_row *desired_row)
+{
+  PGTK_TRACE ("after_update_window_line.");
+
+  struct frame *f;
+  int width, height;
+
+  /* begin copy from other terms */
+  eassert (w);
+
+  if (!desired_row->mode_line_p && !w->pseudo_window_p)
+    desired_row->redraw_fringe_bitmaps_p = 1;
+
+  /* When a window has disappeared, make sure that no rest of
+     full-width rows stays visible in the internal border.  */
+  if (windows_or_buffers_changed && desired_row->full_width_p
+      && (f = XFRAME (w->frame), width = FRAME_INTERNAL_BORDER_WIDTH (f),
+          width != 0)
+      && (height = desired_row->visible_height, height > 0))
+    {
+      int y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, desired_row->y));
+
+      block_input ();
+      pgtk_clear_frame_area (f, 0, y, width, height);
+      pgtk_clear_frame_area (f, FRAME_PIXEL_WIDTH (f) - width, y, width,
+                             height);
+      FRAME_X_OUTPUT (f)->want_flip = false;
+      unblock_input ();
+    }
+}
+
+static void
+pgtk_clear_frame_area (struct frame *f, int x, int y, int width, int height)
+{
+  PGTK_TRACE ("clear_frame_area.");
+  pgtk_clear_area (f, x, y, width, height);
+}
+
+/* Draw a hollow box cursor on window W in glyph row ROW.  */
+
+static void
+x_draw_hollow_cursor (struct window *w, struct glyph_row *row)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  int x, y, wd, h;
+  struct glyph *cursor_glyph;
+
+  /* Get the glyph the cursor is on.  If we can't tell because
+     the current matrix is invalid or such, give up.  */
+  cursor_glyph = get_phys_cursor_glyph (w);
+  if (cursor_glyph == NULL)
+    return;
+
+  /* Compute frame-relative coordinates for phys cursor.  */
+  get_phys_cursor_geometry (w, row, cursor_glyph, &x, &y, &h);
+  wd = w->phys_cursor_width - 1;
+
+  /* The foreground of cursor_gc is typically the same as the normal
+     background color, which can cause the cursor box to be invisible.  */
+  cairo_t *cr = pgtk_begin_cr_clip (f);
+  pgtk_set_cr_source_with_color (f, FRAME_X_OUTPUT (f)->cursor_color);
+
+  /* When on R2L character, show cursor at the right edge of the
+     glyph, unless the cursor box is as wide as the glyph or wider
+     (the latter happens when x-stretch-cursor is non-nil).  */
+  if ((cursor_glyph->resolved_level & 1) != 0 && cursor_glyph->pixel_width > wd)
+    {
+      x += cursor_glyph->pixel_width - wd;
+      if (wd > 0)
+        wd -= 1;
+    }
+  /* Set clipping, draw the rectangle, and reset clipping again.  */
+  pgtk_clip_to_row (w, row, TEXT_AREA, cr);
+  pgtk_draw_rectangle (f, FRAME_X_OUTPUT (f)->cursor_color, x, y, wd, h - 1);
+  pgtk_end_cr_clip (f);
+}
+
+/* Draw a bar cursor on window W in glyph row ROW.
+
+   Implementation note: One would like to draw a bar cursor with an
+   angle equal to the one given by the font property XA_ITALIC_ANGLE.
+   Unfortunately, I didn't find a font yet that has this property set.
+   --gerd.  */
+
+static void
+x_draw_bar_cursor (struct window *w, struct glyph_row *row, int width,
+                   enum text_cursor_kinds kind)
+{
+  struct frame *f = XFRAME (w->frame);
+  struct glyph *cursor_glyph;
+
+  /* If cursor is out of bounds, don't draw garbage.  This can happen
+     in mini-buffer windows when switching between echo area glyphs
+     and mini-buffer.  */
+  cursor_glyph = get_phys_cursor_glyph (w);
+  if (cursor_glyph == NULL)
+    return;
+
+  /* Experimental avoidance of cursor on xwidget.  */
+  if (cursor_glyph->type == XWIDGET_GLYPH)
+    return;
+
+  /* If on an image, draw like a normal cursor.  That's usually better
+     visible than drawing a bar, esp. if the image is large so that
+     the bar might not be in the window.  */
+  if (cursor_glyph->type == IMAGE_GLYPH)
+    {
+      struct glyph_row *r;
+      r = MATRIX_ROW (w->current_matrix, w->phys_cursor.vpos);
+      draw_phys_cursor_glyph (w, r, DRAW_CURSOR);
+    }
+  else
+    {
+      struct face *face = FACE_FROM_ID (f, cursor_glyph->face_id);
+      unsigned long color;
+
+      cairo_t *cr = pgtk_begin_cr_clip (f);
+
+      /* If the glyph's background equals the color we normally draw
+         the bars cursor in, the bar cursor in its normal color is
+         invisible.  Use the glyph's foreground color instead in this
+         case, on the assumption that the glyph's colors are chosen so
+         that the glyph is legible.  */
+      if (face->background == FRAME_X_OUTPUT (f)->cursor_color)
+        color = face->foreground;
+      else
+        color = FRAME_X_OUTPUT (f)->cursor_color;
+
+      pgtk_clip_to_row (w, row, TEXT_AREA, cr);
+
+      if (kind == BAR_CURSOR)
+        {
+          int x = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
+
+          if (width < 0)
+            width = FRAME_CURSOR_WIDTH (f);
+          width = min (cursor_glyph->pixel_width, width);
+
+	  if (Ffeaturep (intern_c_string ("custom"), Qnil) &&
+	      call1 (Qcustom_theme_enabled_p, Qgtk))
+	    width = (cursor_glyph->ascent - cursor_glyph->descent) * 0.04 + 1;
+	   w->phys_cursor_width = width;
+
+          /* If the character under cursor is R2L, draw the bar cursor
+             on the right of its glyph, rather than on the left.  */
+          if ((cursor_glyph->resolved_level & 1) != 0)
+            x += cursor_glyph->pixel_width - width;
+
+          pgtk_fill_rectangle (f, color, x,
+                               WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y),
+                               width, row->height);
+        }
+      else /* HBAR_CURSOR */
+        {
+          int dummy_x, dummy_y, dummy_h;
+          int x = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
+
+          if (width < 0)
+            width = row->height;
+
+          width = min (row->height, width);
+
+          get_phys_cursor_geometry (w, row, cursor_glyph, &dummy_x, &dummy_y,
+                                    &dummy_h);
+
+          if ((cursor_glyph->resolved_level & 1) != 0
+              && cursor_glyph->pixel_width > w->phys_cursor_width - 1)
+            x += cursor_glyph->pixel_width - w->phys_cursor_width + 1;
+          pgtk_fill_rectangle (f, color, x,
+                               WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y
+                                                             + row->height
+                                                             - width),
+                               w->phys_cursor_width - 1, width);
+        }
+
+      pgtk_end_cr_clip (f);
+    }
+}
+
+/* RIF: Draw cursor on window W.  */
+
+static void
+pgtk_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, int x,
+                         int y, enum text_cursor_kinds cursor_type,
+                         int cursor_width, bool on_p, bool active_p)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  if (!FRAME_LIVE_P (f))
+    return;
+
+  if (on_p)
+    {
+      w->phys_cursor_type = cursor_type;
+      w->phys_cursor_on_p = true;
+
+      if (glyph_row->exact_window_width_line_p
+          && (glyph_row->reversed_p
+                ? (w->phys_cursor.hpos < 0)
+                : (w->phys_cursor.hpos >= glyph_row->used[TEXT_AREA])))
+        {
+          glyph_row->cursor_in_fringe_p = true;
+          draw_fringe_bitmap (w, glyph_row, glyph_row->reversed_p);
+        }
+      else
+        {
+          switch (cursor_type)
+            {
+            case HOLLOW_BOX_CURSOR:
+              x_draw_hollow_cursor (w, glyph_row);
+              break;
+
+            case FILLED_BOX_CURSOR:
+              draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
+              break;
+
+            case BAR_CURSOR:
+              x_draw_bar_cursor (w, glyph_row, cursor_width, BAR_CURSOR);
+              break;
+
+            case HBAR_CURSOR:
+              x_draw_bar_cursor (w, glyph_row, cursor_width, HBAR_CURSOR);
+              break;
+
+            case NO_CURSOR:
+              w->phys_cursor_width = 0;
+              break;
+
+            default:
+              emacs_abort ();
+            }
+        }
+    }
+
+  gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+}
+
+static void
+pgtk_copy_bits (struct frame *f, cairo_rectangle_t *src_rect,
+                cairo_rectangle_t *dst_rect)
+{
+  PGTK_TRACE ("pgtk_copy_bits: %dx%d+%d+%d -> %dx%d+%d+%d",
+              (int) src_rect->width, (int) src_rect->height, (int) src_rect->x,
+              (int) src_rect->y, (int) dst_rect->width, (int) dst_rect->height,
+              (int) dst_rect->x, (int) dst_rect->y);
+
+  cairo_t *cr;
+  cairo_surface_t *surface; /* temporary surface */
+
+  surface = cairo_surface_create_similar (FRAME_CR_SURFACE (f),
+                                          CAIRO_CONTENT_COLOR_ALPHA,
+                                          (int) src_rect->width,
+                                          (int) src_rect->height);
+
+  cr = cairo_create (surface);
+  cairo_set_source_surface (cr, FRAME_CR_SURFACE (f), -src_rect->x,
+                            -src_rect->y);
+  cairo_rectangle (cr, 0, 0, src_rect->width, src_rect->height);
+  cairo_clip (cr);
+  cairo_paint (cr);
+  cairo_destroy (cr);
+
+  cr = pgtk_begin_cr_clip (f);
+  cairo_set_source_surface (cr, surface, dst_rect->x, dst_rect->y);
+  cairo_rectangle (cr, dst_rect->x, dst_rect->y, dst_rect->width,
+                   dst_rect->height);
+  cairo_clip (cr);
+  cairo_paint (cr);
+  pgtk_end_cr_clip (f);
+
+  cairo_surface_destroy (surface);
+}
+
+/* Scroll part of the display as described by RUN.  */
+
+static void
+pgtk_scroll_run (struct window *w, struct run *run)
+{
+  struct frame *f = XFRAME (w->frame);
+  int x, y, width, height, from_y, to_y, bottom_y;
+
+  /* Get frame-relative bounding box of the text display area of W,
+     without mode lines.  Include in this box the left and right
+     fringe of W.  */
+  window_box (w, ANY_AREA, &x, &y, &width, &height);
+
+  from_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->current_y);
+  to_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->desired_y);
+  bottom_y = y + height;
+
+  if (to_y < from_y)
+    {
+      /* Scrolling up.  Make sure we don't copy part of the mode
+         line at the bottom.  */
+      if (from_y + run->height > bottom_y)
+        height = bottom_y - from_y;
+      else
+        height = run->height;
+    }
+  else
+    {
+      /* Scrolling down.  Make sure we don't copy over the mode line.
+         at the bottom.  */
+      if (to_y + run->height > bottom_y)
+        height = bottom_y - to_y;
+      else
+        height = run->height;
+    }
+
+  block_input ();
+
+  /* Cursor off.  Will be switched on again in x_update_window_end.  */
+  gui_clear_cursor (w);
+
+  {
+    cairo_rectangle_t src_rect = {x, from_y, width, height};
+    cairo_rectangle_t dst_rect = {x, to_y, width, height};
+    pgtk_copy_bits (f, &src_rect, &dst_rect);
+  }
+
+  unblock_input ();
+}
+
+/***********************************************************************
+                    Starting and ending an update
+ ***********************************************************************/
+
+/* Start an update of frame F.  This function is installed as a hook
+   for update_begin, i.e. it is called when update_begin is called.
+   This function is called prior to calls to x_update_window_begin for
+   each window being updated.  Currently, there is nothing to do here
+   because all interesting stuff is done on a window basis.  */
+
+static void
+pgtk_update_begin (struct frame *f)
+{
+  if (!NILP (tip_frame) && XFRAME (tip_frame) == f && !FRAME_VISIBLE_P (f))
+    return;
+
+  if (!FRAME_CR_SURFACE (f))
+    {
+      int width = FRAME_PIXEL_WIDTH (f);
+      int height = FRAME_PIXEL_HEIGHT (f);
+
+      if (width > 0 && height > 0)
+        {
+          block_input ();
+          FRAME_CR_SURFACE (f)
+            = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+          unblock_input ();
+        }
+    }
+
+  pgtk_clear_under_internal_border (f);
+}
+
+/* Start update of window W.  */
+
+static void
+pgtk_update_window_begin (struct window *w)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (f);
+
+  w->output_cursor = w->cursor;
+
+  block_input ();
+
+  if (f == hlinfo->mouse_face_mouse_frame)
+    {
+      /* Don't do highlighting for mouse motion during the update.  */
+      hlinfo->mouse_face_defer = true;
+
+      /* If F needs to be redrawn, simply forget about any prior mouse
+         highlighting.  */
+      if (FRAME_GARBAGED_P (f))
+        hlinfo->mouse_face_window = Qnil;
+    }
+
+  unblock_input ();
+}
+
+/* Draw a vertical window border from (x,y0) to (x,y1)  */
+
+static void
+pgtk_draw_vertical_window_border (struct window *w, int x, int y0, int y1)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct face *face;
+  cairo_t *cr;
+
+  cr = pgtk_begin_cr_clip (f);
+
+  face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);
+  if (face)
+    pgtk_set_cr_source_with_color (f, face->foreground);
+
+  cairo_rectangle (cr, x, y0, 1, y1 - y0);
+  cairo_fill (cr);
+
+  pgtk_end_cr_clip (f);
+}
+
+/* Draw a window divider from (x0,y0) to (x1,y1)  */
+
+static void
+pgtk_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct face *face = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FACE_ID);
+  struct face *face_first
+    = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FIRST_PIXEL_FACE_ID);
+  struct face *face_last
+    = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_LAST_PIXEL_FACE_ID);
+  unsigned long color = face ? face->foreground : FRAME_FOREGROUND_PIXEL (f);
+  unsigned long color_first
+    = (face_first ? face_first->foreground : FRAME_FOREGROUND_PIXEL (f));
+  unsigned long color_last
+    = (face_last ? face_last->foreground : FRAME_FOREGROUND_PIXEL (f));
+  cairo_t *cr = pgtk_begin_cr_clip (f);
+
+  if (y1 - y0 > x1 - x0 && x1 - x0 > 2)
+    /* Vertical.  */
+    {
+#ifdef HAVE_GTK4
+      if (!(!NILP (Ffeaturep (intern_c_string ("custom"), Qnil))
+            && !NILP (call1 (Qcustom_theme_enabled_p, Qgtk))))
+        {
+#endif
+	  pgtk_set_cr_source_with_color (f, color_first);
+	  cairo_rectangle (cr, x0, y0, 1, y1 - y0);
+	  cairo_fill (cr);
+	  pgtk_set_cr_source_with_color (f, color);
+	  cairo_rectangle (cr, x0 + 1, y0, x1 - x0 - 2, y1 - y0);
+	  cairo_fill (cr);
+	  pgtk_set_cr_source_with_color (f, color_last);
+	  cairo_rectangle (cr, x1 - 1, y0, 1, y1 - y0);
+	  cairo_fill (cr);
+#ifdef HAVE_GTK4
+	}
+      else
+	{
+	  GtkWidget *sp = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+	  GtkStyleContext *ctx = gtk_widget_get_style_context (sp);
+	  gtk_render_background (ctx, cr, x0, y0, x1 - x0, y1 - y0);
+	  gtk_render_frame (ctx, cr, x0, y0, x1 - x0, y1 - y0);
+	  gtk_render_handle (ctx, cr, x0, y0, x1 - x0, y1 - y0);
+	  gtk_widget_destroy (sp);
+	}
+#endif
+    }
+  else if (x1 - x0 > y1 - y0 && y1 - y0 > 3)
+    /* Horizontal.  */
+    {
+#ifdef HAVE_GTK4
+      if (!(!NILP (Ffeaturep (intern_c_string ("custom"), Qnil))
+            && !NILP (call1 (Qcustom_theme_enabled_p, Qgtk))))
+        {
+#endif
+	  pgtk_set_cr_source_with_color (f, color_first);
+	  cairo_rectangle (cr, x0, y0, x1 - x0, 1);
+	  cairo_fill (cr);
+	  pgtk_set_cr_source_with_color (f, color);
+	  cairo_rectangle (cr, x0, y0 + 1, x1 - x0, y1 - y0 - 2);
+	  cairo_fill (cr);
+	  pgtk_set_cr_source_with_color (f, color_last);
+	  cairo_rectangle (cr, x0, y1 - 1, x1 - x0, 1);
+	  cairo_fill (cr);
+#ifdef HAVE_GTK4
+	}
+      else
+	{
+	  GtkWidget *sp = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+	  GtkStyleContext *ctx = gtk_widget_get_style_context (sp);
+	  gtk_render_background (ctx, cr, x0, y0, x1 - x0, y1 - y0);
+	  gtk_render_frame (ctx, cr, x0, y0, x1 - x0, y1 - y0);
+	  gtk_render_handle (ctx, cr, x0, y0, x1 - x0, y1 - y0);
+	  gtk_widget_destroy (sp);
+	}
+#endif
+    }
+  else
+    {
+      pgtk_set_cr_source_with_color (f, color);
+      cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
+      cairo_fill (cr);
+    }
+
+  pgtk_end_cr_clip (f);
+}
+
+/* End update of window W.
+
+   Draw vertical borders between horizontally adjacent windows, and
+   display W's cursor if CURSOR_ON_P is non-zero.
+
+   MOUSE_FACE_OVERWRITTEN_P non-zero means that some row containing
+   glyphs in mouse-face were overwritten.  In that case we have to
+   make sure that the mouse-highlight is properly redrawn.
+
+   W may be a menu bar pseudo-window in case we don't have X toolkit
+   support.  Such windows don't have a cursor, so don't display it
+   here.  */
+
+static void
+pgtk_update_window_end (struct window *w, bool cursor_on_p,
+                        bool mouse_face_overwritten_p)
+{
+  if (!w->pseudo_window_p)
+    {
+      block_input ();
+
+      if (cursor_on_p)
+        display_and_set_cursor (w, true, w->output_cursor.hpos,
+                                w->output_cursor.vpos, w->output_cursor.x,
+                                w->output_cursor.y);
+
+      if (draw_window_fringes (w, true))
+        {
+          if (WINDOW_RIGHT_DIVIDER_WIDTH (w))
+            gui_draw_right_divider (w);
+          else
+            gui_draw_vertical_border (w);
+        }
+
+      unblock_input ();
+    }
+
+  /* If a row with mouse-face was overwritten, arrange for
+     XTframe_up_to_date to redisplay the mouse highlight.  */
+  if (mouse_face_overwritten_p)
+    {
+      Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (XFRAME (w->frame));
+
+      hlinfo->mouse_face_beg_row = hlinfo->mouse_face_beg_col = -1;
+      hlinfo->mouse_face_end_row = hlinfo->mouse_face_end_col = -1;
+      hlinfo->mouse_face_window = Qnil;
+    }
+}
+
+static int
+conn_get_wait_descriptor (struct pgtk_display_info *info)
+{
+  if (getenv ("PGTK_INHIBIT_SIGIO"))
+    return 0;
+  static dynlib_handle_ptr h = NULL;
+  if (!h)
+    if (!(h = dynlib_open (NULL)))
+      return 0;
+#ifdef GDK_WINDOWING_X11
+#ifdef HAVE_GTK3
+  if (GDK_IS_X11_DISPLAY (info->gdpy))
+    {
+#endif
+      gpointer dpy = gdk_x11_display_get_xdisplay (info->gdpy);
+      int (*fn) (void *) = dynlib_sym (h, "XConnectionNumber");
+      return fn (dpy);
+#ifdef HAVE_GTK3
+    }
+#endif
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+  else if (GDK_IS_WAYLAND_DISPLAY (info->gdpy))
+    {
+      int (*fn) (void *) = dynlib_sym (h, "wl_display_get_fd");
+      if (!fn)
+	return 0;
+      return fn (gdk_wayland_display_get_wl_display (info->gdpy));
+    }
+#endif
+  return 0;
+}
+
+/* End update of frame F.  This function is installed as a hook in
+   update_end.  */
+
+static void
+pgtk_update_end (struct frame *f)
+{
+  /* Mouse highlight may be displayed again.  */
+  MOUSE_HL_INFO (f)->mouse_face_defer = false;
+}
+
+/* Return the current position of the mouse.
+   *FP should be a frame which indicates which display to ask about.
+
+   If the mouse movement started in a scroll bar, set *FP, *BAR_WINDOW,
+   and *PART to the frame, window, and scroll bar part that the mouse
+   is over.  Set *X and *Y to the portion and whole of the mouse's
+   position on the scroll bar.
+
+   If the mouse movement started elsewhere, set *FP to the frame the
+   mouse is on, *BAR_WINDOW to nil, and *X and *Y to the character cell
+   the mouse is over.
+
+   Set *TIMESTAMP to the server time-stamp for the time at which the mouse
+   was at this position.
+
+   Don't store anything if we don't have a valid set of values to report.
+
+   This clears the mouse_moved flag, so we can wait for the next mouse
+   movement.  */
+
+static void
+pgtk_mouse_position (struct frame **fp, int insist, Lisp_Object *bar_window,
+                     enum scroll_bar_part *part, Lisp_Object *x, Lisp_Object *y,
+                     Time *timestamp)
+{
+  struct frame *f1;
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (*fp);
+#ifndef HAVE_GTK4
+  int win_x, win_y;
+#else
+  double win_x, win_y;
+#endif
+#ifdef HAVE_GTK3
+#if defined (GDK_VERSION_3_22) || defined (HAVE_GTK4)
+  GdkSeat *seat;
+#endif
+  GdkDevice *device;
+#endif
+#ifndef HAVE_GTK4
+  GdkModifierType mask;
+#endif
+#ifndef HAVE_GTK4
+  GdkWindow *win;
+#else
+  GdkSurface *win;
+#endif
+
+  block_input ();
+
+  Lisp_Object frame, tail;
+
+  /* Clear the mouse-moved flag for every frame on this display.  */
+  FOR_EACH_FRAME (tail, frame)
+  if (FRAME_PGTK_P (XFRAME (frame))
+      && FRAME_X_DISPLAY (XFRAME (frame)) == FRAME_X_DISPLAY (*fp))
+    XFRAME (frame)->mouse_moved = false;
+
+  dpyinfo->last_mouse_scroll_bar = NULL;
+
+  if (gui_mouse_grabbed (dpyinfo))
+    {
+      /* 1.1. use last_mouse_frame as frame where the pointer is on. */
+      f1 = dpyinfo->last_mouse_frame;
+    }
+  else
+    {
+      f1 = *fp;
+      /* 1.2. get frame where the pointer is on. */
+#ifdef HAVE_GTK4
+      win = gtk_native_get_surface
+	(gtk_widget_get_native (FRAME_GTK_OUTER_WIDGET (f1)));
+#else
+      win = gtk_widget_get_window (FRAME_GTK_WIDGET (*fp));
+#endif
+
+#ifdef HAVE_GTK3
+#if defined (GDK_VERSION_3_22) || defined (HAVE_GTK4)
+      seat = gdk_display_get_default_seat (dpyinfo->gdpy);
+      device = gdk_seat_get_pointer (seat);
+#else
+      GdkDeviceManager *manager
+        = gdk_display_get_device_manager (dpyinfo->gdpy);
+      device = gdk_device_manager_get_client_pointer (manager);
+#endif
+#ifndef HAVE_GTK4
+      win = gdk_window_get_device_position (win, device, &win_x, &win_y, &mask);
+#endif
+#endif
+#ifndef HAVE_GTK4
+      win = gdk_display_get_window_at_pointer (dpyinfo->gdpy, &win_x, &win_y);
+#else
+      win = gdk_device_get_surface_at_position (gdk_seat_get_pointer (seat),
+						&win_x, &win_y);
+#endif
+#ifndef HAVE_GTK4
+      if (win != NULL)
+        f1 = pgtk_any_window_to_frame (win);
+      else
+#else
+      if (!win)
+#endif
+        {
+          // crossing display server?
+          f1 = SELECTED_FRAME ();
+        }
+    }
+
+  /* 2. get the display and the device. */
+#ifndef HAVE_GTK4
+  win = gtk_widget_get_window (FRAME_GTK_WIDGET (f1));
+#ifdef HAVE_GTK3
+  GdkDisplay *gdpy = gdk_window_get_display (win);
+#endif
+#else
+  GdkDisplay *gdpy = FRAME_X_DISPLAY (f1);
+#endif
+#ifdef HAVE_GTK3
+#if defined (GDK_VERSION_3_22) || HAVE_GTK4
+  seat = gdk_display_get_default_seat (gdpy);
+  device = gdk_seat_get_pointer (seat);
+#else
+  GdkDeviceManager *manager = gdk_display_get_device_manager (gdpy);
+  device = gdk_device_manager_get_client_pointer (manager);
+#endif
+
+#ifndef HAVE_GTK4
+  /* 3. get x, y relative to edit window of the frame. */
+  win = gdk_window_get_device_position (win, device, &win_x, &win_y, &mask);
+#else
+  win = gdk_device_get_surface_at_position (device, &win_x, &win_y);
+  if (FRAME_PARENT_FRAME (f1))
+    {
+      win_y -= calculate_child_frame_distance_y (f1);
+      win_x -= calculate_child_frame_distance_x (f1);
+    }
+  else
+    {
+      win_y -= pgtk_get_window_inside_decor_height (f1);
+      win_x -= pgtk_get_window_decoration_width (f1);
+    }
+#endif
+#else
+  win = gdk_window_get_pointer (win, &win_x, &win_y, &mask);
+#endif
+
+  if (f1 != NULL)
+    {
+      dpyinfo = FRAME_DISPLAY_INFO (f1);
+      remember_mouse_glyph (f1, win_x, win_y, &dpyinfo->last_mouse_glyph);
+      dpyinfo->last_mouse_glyph_frame = f1;
+
+      *bar_window = Qnil;
+      *part = 0;
+      *fp = f1;
+      XSETINT (*x, win_x);
+      XSETINT (*y, win_y);
+      *timestamp = dpyinfo->last_mouse_movement_time;
+    }
+
+  unblock_input ();
+}
+
+/* Fringe bitmaps.  */
+
+static int max_fringe_bmp = 0;
+static cairo_pattern_t **fringe_bmp = 0;
+
+static void
+pgtk_define_fringe_bitmap (int which, unsigned short *bits, int h, int wd)
+{
+  int i, stride;
+  cairo_surface_t *surface;
+  unsigned char *data;
+  cairo_pattern_t *pattern;
+
+  if (which >= max_fringe_bmp)
+    {
+      i = max_fringe_bmp;
+      max_fringe_bmp = which + 20;
+      fringe_bmp = (cairo_pattern_t **)
+        xrealloc (fringe_bmp, max_fringe_bmp * sizeof (cairo_pattern_t *));
+      while (i < max_fringe_bmp)
+        fringe_bmp[i++] = 0;
+    }
+
+  block_input ();
+
+  surface = cairo_image_surface_create (CAIRO_FORMAT_A1, wd, h);
+  stride = cairo_image_surface_get_stride (surface);
+  data = cairo_image_surface_get_data (surface);
+
+  for (i = 0; i < h; i++)
+    {
+      *((unsigned short *) data) = bits[i];
+      data += stride;
+    }
+
+  cairo_surface_mark_dirty (surface);
+  pattern = cairo_pattern_create_for_surface (surface);
+  cairo_surface_destroy (surface);
+
+  unblock_input ();
+
+  fringe_bmp[which] = pattern;
+}
+
+static void
+pgtk_destroy_fringe_bitmap (int which)
+{
+  if (which >= max_fringe_bmp)
+    return;
+
+  if (fringe_bmp[which])
+    {
+      block_input ();
+      cairo_pattern_destroy (fringe_bmp[which]);
+      unblock_input ();
+    }
+  fringe_bmp[which] = 0;
+}
+
+static void
+pgtk_clip_to_row (struct window *w, struct glyph_row *row,
+                  enum glyph_row_area area, cairo_t *cr)
+{
+  int window_x, window_y, window_width;
+  cairo_rectangle_int_t rect;
+
+  window_box (w, area, &window_x, &window_y, &window_width, 0);
+
+  rect.x = window_x;
+  rect.y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, row->y));
+  rect.y = max (rect.y, window_y);
+  rect.width = window_width;
+  rect.height = row->visible_height;
+
+  cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
+  cairo_clip (cr);
+}
+
+static void
+pgtk_cr_draw_image (struct frame *f, Emacs_GC *gc, cairo_pattern_t *image,
+                    int src_x, int src_y, int width, int height, int dest_x,
+                    int dest_y, bool overlay_p)
+{
+  cairo_t *cr = pgtk_begin_cr_clip (f);
+
+  if (overlay_p)
+    cairo_rectangle (cr, dest_x, dest_y, width, height);
+  else
+    {
+      pgtk_set_cr_source_with_gc_background (f, gc);
+      cairo_rectangle (cr, dest_x, dest_y, width, height);
+      cairo_fill_preserve (cr);
+    }
+
+  cairo_translate (cr, dest_x - src_x, dest_y - src_y);
+
+  cairo_surface_t *surface;
+  cairo_pattern_get_surface (image, &surface);
+  cairo_format_t format = cairo_image_surface_get_format (surface);
+  if (format != CAIRO_FORMAT_A8 && format != CAIRO_FORMAT_A1)
+    {
+      cairo_set_source (cr, image);
+      cairo_fill (cr);
+    }
+  else
+    {
+      pgtk_set_cr_source_with_gc_foreground (f, gc);
+      cairo_clip (cr);
+      cairo_mask (cr, image);
+    }
+
+  pgtk_end_cr_clip (f);
+}
+
+static void
+pgtk_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
+                         struct draw_fringe_bitmap_params *p)
+{
+  PGTK_TRACE ("draw_fringe_bitmap.");
+
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct face *face = p->face;
+
+  cairo_t *cr = pgtk_begin_cr_clip (f);
+
+  /* Must clip because of partially visible lines.  */
+  pgtk_clip_to_row (w, row, ANY_AREA, cr);
+
+  if (p->bx >= 0 && !p->overlay_p)
+    {
+      fill_background_by_face (f, face, p->bx, p->by, p->nx, p->ny);
+    }
+
+  PGTK_TRACE ("which: %d, max_fringe_bmp: %d.", p->which, max_fringe_bmp);
+  if (p->which && p->which < max_fringe_bmp)
+    {
+      Emacs_GC gcv;
+
+      PGTK_TRACE ("cursor_p=%d.", p->cursor_p);
+      PGTK_TRACE ("overlay_p_p=%d.", p->overlay_p);
+      PGTK_TRACE ("background=%08lx.", face->background);
+      PGTK_TRACE ("cursor_color=%08lx.", FRAME_X_OUTPUT (f)->cursor_color);
+      PGTK_TRACE ("foreground=%08lx.", face->foreground);
+      gcv.foreground
+        = (p->cursor_p ? (p->overlay_p ? face->background
+                                       : FRAME_X_OUTPUT (f)->cursor_color)
+                       : face->foreground);
+      gcv.background = face->background;
+      pgtk_cr_draw_image (f, &gcv, fringe_bmp[p->which], 0, p->dh, p->wd, p->h,
+                          p->x, p->y, p->overlay_p);
+    }
+  pgtk_end_cr_clip (f);
+}
+#ifndef HAVE_GTK4
+static struct atimer *hourglass_atimer = NULL;
+static int hourglass_enter_count = 0;
+static void
+hourglass_cb (struct atimer *timer)
+{
+  /*NOP*/
+}
+#endif
+static void
+pgtk_show_hourglass (struct frame *f)
+{
+#ifndef HAVE_GTK4
+  struct pgtk_output *x = FRAME_X_OUTPUT (f);
+  if (x->hourglass_widget != NULL)
+    gtk_widget_destroy (x->hourglass_widget);
+  x->hourglass_widget
+#ifndef HAVE_GTK4
+    = gtk_event_box_new (); /* gtk_event_box is GDK_INPUT_ONLY. */
+#else
+    = gtk_fixed_new ();
+#endif
+#ifdef HAVE_GTK4
+  gtk_widget_set_hexpand (x->hourglass_widget, true);
+#else
+  gtk_widget_set_has_window (x->hourglass_widget, true);
+#endif
+#ifndef HAVE_GTK4
+  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (f)), x->hourglass_widget, 0, 0);
+#else
+  gtk_overlay_add_overlay (GTK_OVERLAY (gtk_widget_get_parent (FRAME_GTK_WIDGET (f))), x->hourglass_widget);
+  GdkRectangle *rect;
+  if (!(rect = g_object_get_data (G_OBJECT (x->hourglass_widget),
+				  EG_OVERLAY_ALLOC)))
+    {
+      rect = g_malloc (sizeof (GdkRectangle));
+      g_object_set_data_full (G_OBJECT (x->hourglass_widget),
+			      EG_OVERLAY_ALLOC, rect, g_free);
+    }
+  rect->x = 0;
+  rect->y = 0;
+  rect->width = 0;
+  rect->height = 0;
+#endif
+  gtk_widget_show (x->hourglass_widget);
+#ifndef HAVE_GTK4
+  gtk_widget_set_size_request (x->hourglass_widget, 30000, 30000);
+#else
+  rect->x = 0;
+  rect->y = 0;
+  rect->width = 10000;
+  rect->height = 10000;
+#endif
+#ifndef HAVE_GTK4
+  gdk_window_raise (gtk_widget_get_window (x->hourglass_widget));
+  gdk_window_set_cursor (gtk_widget_get_window (x->hourglass_widget),
+                         x->hourglass_cursor);
+#else
+  gdk_surface_raise (gtk_native_get_surface (gtk_widget_get_native (x->hourglass_widget)));
+  gdk_surface_set_cursor (gtk_native_get_surface (gtk_widget_get_native (x->hourglass_widget)),
+                         x->hourglass_cursor);
+#endif
+
+  /* For cursor animation, we receive signals, set pending_signals, and
+   * dispatch. */
+  if (hourglass_enter_count++ == 0)
+    {
+      struct timespec ts = make_timespec (0, 50 * 1000 * 1000);
+      if (hourglass_atimer != NULL)
+        cancel_atimer (hourglass_atimer);
+      hourglass_atimer
+        = start_atimer (ATIMER_CONTINUOUS, ts, hourglass_cb, NULL);
+    }
+
+  /* Cursor frequently stops animation. gtk's bug? */
+#else
+  gtk_widget_set_cursor (FRAME_GTK_WIDGET (f),
+			 FRAME_X_OUTPUT (f)->hourglass_cursor);
+#endif
+}
+
+static void
+pgtk_hide_hourglass (struct frame *f)
+{
+#ifndef HAVE_GTK4
+  struct pgtk_output *x = FRAME_X_OUTPUT (f);
+  if (--hourglass_enter_count == 0)
+    {
+      if (hourglass_atimer != NULL)
+        {
+          cancel_atimer (hourglass_atimer);
+          hourglass_atimer = NULL;
+        }
+    }
+  if (x->hourglass_widget != NULL)
+    {
+      gtk_widget_destroy (x->hourglass_widget);
+      x->hourglass_widget = NULL;
+    }
+#else
+  gtk_widget_set_cursor (FRAME_GTK_WIDGET (f),
+                         f->pointer_invisible
+                           ? FRAME_DISPLAY_INFO (f)->invisible_cursor
+                           : FRAME_X_OUTPUT (f)->current_pointer);
+#endif
+}
+
+/* Flushes changes to display.  */
+static void
+pgtk_flush_display (struct frame *f)
+{
+  block_input ();
+#ifndef HAVE_GTK4
+  gdk_flush ();
+#else
+  gdk_display_flush (f->output_data.pgtk->display_info->gdpy);
+#endif
+  unblock_input ();
+}
+
+extern frame_parm_handler pgtk_frame_parm_handlers[];
+
+static struct redisplay_interface pgtk_redisplay_interface
+  = {pgtk_frame_parm_handlers,
+     gui_produce_glyphs,
+     gui_write_glyphs,
+     gui_insert_glyphs,
+     gui_clear_end_of_line,
+     pgtk_scroll_run,
+     pgtk_after_update_window_line,
+     pgtk_update_window_begin,
+     pgtk_update_window_end,
+     pgtk_flush_display,
+     gui_clear_window_mouse_face,
+     gui_get_glyph_overhangs,
+     gui_fix_overlapping_area,
+     pgtk_draw_fringe_bitmap,
+     pgtk_define_fringe_bitmap,
+     pgtk_destroy_fringe_bitmap,
+     pgtk_compute_glyph_string_overhangs,
+     pgtk_draw_glyph_string,
+     pgtk_define_frame_cursor,
+     pgtk_clear_frame_area,
+     pgtk_clear_under_internal_border,
+     pgtk_draw_window_cursor,
+     pgtk_draw_vertical_window_border,
+     pgtk_draw_window_divider,
+     NULL, // pgtk_shift_glyphs_for_insert,
+     pgtk_show_hourglass,
+     pgtk_hide_hourglass};
+#ifndef HAVE_GTK4
+static void
+pgtk_redraw_scroll_bars (struct frame *f)
+{
+}
+#endif
+
+static void
+pgtk_db_clear_frame (struct frame *f)
+{
+  block_input ();
+  pgtk_clear_frame (f);
+  FRAME_X_OUTPUT (f)->want_flip = false;
+  unblock_input ();
+}
+
+void
+pgtk_clear_frame (struct frame *f)
+{
+  if (!FRAME_DEFAULT_FACE (f))
+    return;
+
+  block_input ();
+  pgtk_clear_area (f, 0, 0, FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f));
+
+#ifndef HAVE_GTK4
+  pgtk_redraw_scroll_bars (f);
+#endif
+  unblock_input ();
+}
+
+/* Invert the middle quarter of the frame for .15 sec.  */
+
+static void
+recover_from_visible_bell (struct atimer *timer)
+{
+  struct frame *f = timer->client_data;
+
+  if (FRAME_X_OUTPUT (f)->cr_surface_visible_bell != NULL)
+    {
+      cairo_surface_destroy (FRAME_X_OUTPUT (f)->cr_surface_visible_bell);
+      FRAME_X_OUTPUT (f)->cr_surface_visible_bell = NULL;
+    }
+
+  if (FRAME_X_OUTPUT (f)->atimer_visible_bell != NULL)
+    FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
+#ifdef HAVE_GTK4
+  FRAME_X_OUTPUT (f)->visible_bell_end_time = false;
+#endif
+  gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+}
+
+static void
+pgtk_flash (struct frame *f)
+{
+  block_input ();
+
+  {
+    cairo_surface_t *surface_orig = FRAME_CR_SURFACE (f);
+
+    int width = cairo_image_surface_get_width (surface_orig);
+    int height = cairo_image_surface_get_height (surface_orig);
+    cairo_surface_t *surface
+      = cairo_surface_create_similar (surface_orig, CAIRO_CONTENT_COLOR_ALPHA,
+                                      width, height);
+
+    cairo_t *cr = cairo_create (surface);
+    cairo_set_source_surface (cr, surface_orig, 0, 0);
+    cairo_rectangle (cr, 0, 0, width, height);
+    cairo_clip (cr);
+    cairo_paint (cr);
+
+    cairo_set_source_rgb (cr, 1, 1, 1);
+    cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
+
+#define XFillRectangle(d, win, gc, x, y, w, h) \
+  (cairo_rectangle (cr, x, y, w, h), cairo_fill (cr))
+
+    {
+      /* Get the height not including a menu bar widget.  */
+      int height = FRAME_PIXEL_HEIGHT (f);
+      /* Height of each line to flash.  */
+      int flash_height = FRAME_LINE_HEIGHT (f);
+      /* These will be the left and right margins of the rectangles.  */
+      int flash_left = FRAME_INTERNAL_BORDER_WIDTH (f);
+      int flash_right = FRAME_PIXEL_WIDTH (f) - FRAME_INTERNAL_BORDER_WIDTH (f);
+      int width = flash_right - flash_left;
+
+      /* If window is tall, flash top and bottom line.  */
+      if (height > 3 * FRAME_LINE_HEIGHT (f))
+        {
+          XFillRectangle (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), gc,
+                          flash_left,
+                          (FRAME_INTERNAL_BORDER_WIDTH (f)
+                           + FRAME_TOP_MARGIN_HEIGHT (f)),
+                          width, flash_height);
+          XFillRectangle (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), gc,
+                          flash_left,
+                          (height - flash_height
+                           - FRAME_INTERNAL_BORDER_WIDTH (f)),
+                          width, flash_height);
+        }
+      else
+        /* If it is short, flash it all.  */
+        XFillRectangle (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), gc, flash_left,
+                        FRAME_INTERNAL_BORDER_WIDTH (f), width,
+                        height - 2 * FRAME_INTERNAL_BORDER_WIDTH (f));
+
+      FRAME_X_OUTPUT (f)->cr_surface_visible_bell = surface;
+      gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+
+      {
+        struct timespec delay = make_timespec (0, 50 * 1000 * 1000);
+        if (FRAME_X_OUTPUT (f)->atimer_visible_bell != NULL)
+          {
+            cancel_atimer (FRAME_X_OUTPUT (f)->atimer_visible_bell);
+            FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
+          }
+        FRAME_X_OUTPUT (f)->atimer_visible_bell
+          = start_atimer (ATIMER_RELATIVE, delay, recover_from_visible_bell, f);
+#ifdef HAVE_GTK4
+	FRAME_X_OUTPUT (f)->visible_bell_end_time = true;
+#endif
+      }
+
+#undef XFillRectangle
+    }
+    gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+  }
+
+  unblock_input ();
+}
+
+/* Make audible bell.  */
+
+static void
+pgtk_ring_bell (struct frame *f)
+{
+  if (visible_bell)
+    {
+      pgtk_flash (f);
+    }
+  else
+    {
+      block_input ();
+      gtk_widget_error_bell (FRAME_GTK_WIDGET (f));
+      unblock_input ();
+    }
+}
+
+/* Read events coming from the X server.
+   Return as soon as there are no more events to be read.
+
+   Return the number of characters stored into the buffer,
+   thus pretending to be `read' (except the characters we store
+   in the keyboard buffer can be multibyte, so are not necessarily
+   C chars).  */
+
+static int
+pgtk_read_socket (struct terminal *terminal, struct input_event *hold_quit)
+{
+  GMainContext *context;
+  bool context_acquired = false;
+  int count;
+
+  count = evq_flush (hold_quit);
+  if (count > 0)
+    return count;
+
+  context = g_main_context_default ();
+  context_acquired = g_main_context_acquire (context);
+
+  block_input ();
+  if (context_acquired)
+    {
+      while (g_main_context_pending (context))
+        {
+          g_main_context_dispatch (context);
+        }
+    }
+
+  unblock_input ();
+
+  if (context_acquired)
+    g_main_context_release (context);
+
+  count = evq_flush (hold_quit);
+  if (count > 0)
+    {
+      return count;
+    }
+
+  return 0;
+}
+
+int
+pgtk_select (int fds_lim, fd_set *rfds, fd_set *wfds, fd_set *efds,
+             struct timespec *timeout, sigset_t *sigmask)
+{
+  fd_set all_rfds, all_wfds;
+  struct timespec tmo;
+  struct timespec *tmop = timeout;
+
+  GMainContext *context;
+  bool have_wfds = wfds != NULL;
+  GPollFD gfds_buf[128];
+  GPollFD *gfds = gfds_buf;
+  int gfds_size = ARRAYELTS (gfds_buf);
+  int n_gfds, retval = 0, our_fds = 0, max_fds = fds_lim - 1;
+  bool context_acquired = false;
+  int i, nfds, tmo_in_millisec, must_free = 0;
+  bool need_to_dispatch;
+
+  if (event_q.nr >= 1)
+    {
+      if (input_blocked_p ())
+	{
+	  if (getenv ("E_DEBUG_BINPUT"))
+	    raise (SIGTRAP);
+	  else
+	    totally_unblock_input ();
+	}
+      raise (SIGIO);
+      errno = EINTR;
+      return -1;
+    }
+  event_q.nr_pass_flag = false;
+  context = g_main_context_default ();
+  context_acquired = g_main_context_acquire (context);
+  /* FIXME: If we couldn't acquire the context, we just silently proceed
+     because this function handles more than just glib file descriptors.
+     Note that, as implemented, this failure is completely silent: there is
+     no feedback to the caller.  */
+
+  if (rfds)
+    all_rfds = *rfds;
+  else
+    FD_ZERO (&all_rfds);
+  if (wfds)
+    all_wfds = *wfds;
+  else
+    FD_ZERO (&all_wfds);
+
+  n_gfds = (context_acquired
+              ? g_main_context_query (context, G_PRIORITY_LOW, &tmo_in_millisec,
+                                      gfds, gfds_size)
+              : -1);
+
+  if (gfds_size < n_gfds)
+    {
+      /* Avoid using SAFE_NALLOCA, as that implicitly refers to the
+         current thread.  Using xnmalloc avoids thread-switching
+         problems here.  */
+      gfds = xnmalloc (n_gfds, sizeof *gfds);
+      must_free = 1;
+      gfds_size = n_gfds;
+      n_gfds = g_main_context_query (context, G_PRIORITY_LOW, &tmo_in_millisec,
+                                     gfds, gfds_size);
+    }
+
+  for (i = 0; i < n_gfds; ++i)
+    {
+      if (gfds[i].events & G_IO_IN)
+        {
+          FD_SET (gfds[i].fd, &all_rfds);
+#ifdef F_SETOWN
+	  if (gfds[i].fd <= 800 &&
+	      !fdmap[gfds[i].fd])
+	    {
+	      add_keyboard_wait_descriptor (gfds[i].fd);
+	      fcntl (gfds[i].fd, F_SETOWN, getpid ());
+	      if (interrupt_input)
+		init_sigio (gfds[i].fd);
+	      fdmap[gfds[i].fd] = 1;
+	    }
+#endif
+          if (gfds[i].fd > max_fds)
+            max_fds = gfds[i].fd;
+        }
+      if (gfds[i].events & G_IO_OUT)
+        {
+          FD_SET (gfds[i].fd, &all_wfds);
+          if (gfds[i].fd > max_fds)
+            max_fds = gfds[i].fd;
+          have_wfds = true;
+        }
+    }
+
+  if (must_free)
+    xfree (gfds);
+
+  if (n_gfds >= 0 && tmo_in_millisec >= 0)
+    {
+      tmo = make_timespec (tmo_in_millisec / 1000,
+                           1000 * 1000 * (tmo_in_millisec % 1000));
+      if (!timeout || timespec_cmp (tmo, *timeout) < 0)
+        tmop = &tmo;
+    }
+
+  /* Before sleep, dispatch draw events. */
+  if (context_acquired)
+    {
+      int pselect_errno = errno;
+      block_input ();
+      while (g_main_context_pending (context))
+        g_main_context_dispatch (context);
+      unblock_input ();
+      errno = pselect_errno;
+    }
+
+  fds_lim = max_fds + 1;
+  nfds = thread_select (pselect, fds_lim, &all_rfds,
+                        have_wfds ? &all_wfds : NULL, efds, tmop, sigmask);
+  if (nfds < 0)
+    retval = nfds;
+  else if (nfds > 0)
+    {
+      for (i = 0; i < fds_lim; ++i)
+        {
+          if (FD_ISSET (i, &all_rfds))
+            {
+              if (rfds && FD_ISSET (i, rfds))
+                ++retval;
+              else
+                ++our_fds;
+            }
+          else if (rfds)
+            FD_CLR (i, rfds);
+
+          if (have_wfds && FD_ISSET (i, &all_wfds))
+            {
+              if (wfds && FD_ISSET (i, wfds))
+                ++retval;
+              else
+                ++our_fds;
+            }
+          else if (wfds)
+            FD_CLR (i, wfds);
+
+          if (efds && FD_ISSET (i, efds))
+            ++retval;
+        }
+    }
+
+  /* If Gtk+ is in use eventually gtk_main_iteration will be called,
+     unless retval is zero.  */
+  need_to_dispatch = retval == 0;
+  if (need_to_dispatch && context_acquired)
+    {
+      int pselect_errno = errno;
+      /* Prevent g_main_dispatch recursion, that would occur without
+         block_input wrapper, because event handlers call
+         unblock_input.  Event loop recursion was causing Bug#15801.  */
+      block_input ();
+      while (g_main_context_pending (context))
+        {
+	  g_main_context_dispatch (context);
+        }
+      unblock_input ();
+      errno = pselect_errno;
+    }
+
+  if (context_acquired)
+    g_main_context_release (context);
+
+  /* To not have to recalculate timeout, return like this.  */
+  if ((our_fds > 0 || (nfds == 0 && tmop == &tmo)) && (retval == 0))
+    {
+      retval = -1;
+      errno = EINTR;
+    }
+
+  PGTK_TRACE ("pgtk_select: leave.");
+  return retval;
+}
+
+/* Lisp window being scrolled.  Set when starting to interact with
+   a toolkit scroll bar, reset to nil when ending the interaction.  */
+
+static Lisp_Object window_being_scrolled;
+
+static void
+pgtk_send_scroll_bar_event (Lisp_Object window, enum scroll_bar_part part,
+                            int portion, int whole, bool horizontal)
+{
+  union buffered_input_event inev;
+
+  EVENT_INIT (inev.ie);
+
+  inev.ie.kind
+    = horizontal ? HORIZONTAL_SCROLL_BAR_CLICK_EVENT : SCROLL_BAR_CLICK_EVENT;
+  inev.ie.frame_or_window = window;
+  inev.ie.arg = Qnil;
+  inev.ie.timestamp = 0;
+  inev.ie.code = 0;
+  inev.ie.part = part;
+  inev.ie.x = make_fixnum (portion);
+  inev.ie.y = make_fixnum (whole);
+  inev.ie.modifiers = 0;
+  GList *seats = gdk_display_list_seats
+    (FRAME_X_DISPLAY (XFRAME (WINDOW_FRAME (XWINDOW (window)))));
+  for (GList *seat = seats; seat; seat = seat->next)
+    inev.ie.modifiers |= gdk_device_get_modifier_state (gdk_seat_get_keyboard (seat->data));
+  g_list_free (seats);
+  evq_enqueue (&inev);
+}
+
+/* Scroll bar callback for GTK scroll bars.  WIDGET is the scroll
+   bar widget.  DATA is a pointer to the scroll_bar structure. */
+
+static gboolean
+xg_scroll_callback (GtkRange *range, GtkScrollType scroll, gdouble value,
+                    gpointer user_data)
+{
+  int whole = 0, portion = 0;
+  struct scroll_bar *bar = user_data;
+  enum scroll_bar_part part = scroll_bar_nowhere;
+  GtkAdjustment *adj = GTK_ADJUSTMENT (gtk_range_get_adjustment (range));
+
+  if (
+#ifndef HAVE_GTK4
+      xg_ignore_gtk_scrollbar
+#else
+      egtk_ignore_gtk_scrollbar
+#endif
+      )
+    return false;
+  switch (scroll)
+    {
+    case GTK_SCROLL_JUMP:
+      {
+        if (bar->horizontal)
+          {
+            part = scroll_bar_horizontal_handle;
+            whole = (int) (gtk_adjustment_get_upper (adj)
+                           - gtk_adjustment_get_page_size (adj));
+            portion = min ((int) value, whole);
+            bar->dragging = portion;
+          }
+        else
+          {
+            part = scroll_bar_handle;
+            whole = gtk_adjustment_get_upper (adj)
+                    - gtk_adjustment_get_page_size (adj);
+            portion = min ((int) value, whole);
+            bar->dragging = portion;
+          }
+      }
+      break;
+    case GTK_SCROLL_STEP_BACKWARD:
+      part = (bar->horizontal ? scroll_bar_left_arrow : scroll_bar_up_arrow);
+      bar->dragging = -1;
+      break;
+    case GTK_SCROLL_STEP_FORWARD:
+      part = (bar->horizontal ? scroll_bar_right_arrow : scroll_bar_down_arrow);
+      bar->dragging = -1;
+      break;
+    case GTK_SCROLL_PAGE_BACKWARD:
+      part = (bar->horizontal ? scroll_bar_before_handle
+                              : scroll_bar_above_handle);
+      bar->dragging = -1;
+      break;
+    case GTK_SCROLL_PAGE_FORWARD:
+      part
+        = (bar->horizontal ? scroll_bar_after_handle : scroll_bar_below_handle);
+      bar->dragging = -1;
+      break;
+    default:
+      break;
+    }
+
+  if (part != scroll_bar_nowhere)
+    {
+      window_being_scrolled = bar->window;
+      pgtk_send_scroll_bar_event (bar->window, part, portion, whole,
+                                  bar->horizontal);
+    }
+
+  return false;
+}
+
+/* Callback for button release. Sets dragging to -1 when dragging is done.  */
+
+static gboolean
+#ifndef HAVE_GTK4
+xg_end_scroll_callback (GtkWidget *widget, GdkEvent *event,
+                        gpointer user_data)
+#else
+xg_end_scroll_callback (GtkGestureClick *gesture,
+			gint             n_press,
+			gdouble          x,
+			gdouble          y,
+			gpointer         user_data)
+#endif
+{
+  struct scroll_bar *bar = user_data;
+  bar->dragging = -1;
+  if (WINDOWP (window_being_scrolled))
+    {
+      pgtk_send_scroll_bar_event (window_being_scrolled, scroll_bar_end_scroll,
+                                  0, 0, bar->horizontal);
+      window_being_scrolled = Qnil;
+    }
+
+  return false;
+}
+
+#define SCROLL_BAR_NAME "verticalScrollBar"
+#define SCROLL_BAR_HORIZONTAL_NAME "horizontalScrollBar"
+
+/* Create the widget for scroll bar BAR on frame F.  Record the widget
+   and X window of the scroll bar in BAR.  */
+
+static void
+x_create_toolkit_scroll_bar (struct frame *f, struct scroll_bar *bar)
+{
+  const char *scroll_bar_name = SCROLL_BAR_NAME;
+
+  block_input ();
+#ifndef HAVE_GTK4
+  xg_create_scroll_bar (f, bar, G_CALLBACK (xg_scroll_callback),
+                        G_CALLBACK (xg_end_scroll_callback), scroll_bar_name);
+#else
+  egtk_create_scroll_bar (f, bar, G_CALLBACK (xg_scroll_callback),
+			  G_CALLBACK (xg_end_scroll_callback), scroll_bar_name);
+#endif
+  unblock_input ();
+}
+
+static void
+x_create_horizontal_toolkit_scroll_bar (struct frame *f, struct scroll_bar *bar)
+{
+  const char *scroll_bar_name = SCROLL_BAR_HORIZONTAL_NAME;
+
+  block_input ();
+#ifndef HAVE_GTK4
+  xg_create_horizontal_scroll_bar (f, bar, G_CALLBACK (xg_scroll_callback),
+                                   G_CALLBACK (xg_end_scroll_callback),
+                                   scroll_bar_name);
+#else
+  egtk_create_horizontal_scroll_bar (f, bar, G_CALLBACK (xg_scroll_callback),
+				     G_CALLBACK (xg_end_scroll_callback),
+				     scroll_bar_name);
+#endif
+  unblock_input ();
+}
+
+/* Set the thumb size and position of scroll bar BAR.  We are currently
+   displaying PORTION out of a whole WHOLE, and our position POSITION.  */
+
+static void
+x_set_toolkit_scroll_bar_thumb (struct scroll_bar *bar, int portion,
+                                int position, int whole)
+{
+#ifdef HAVE_GTK4
+  egtk_set_toolkit_scroll_bar_thumb (bar, portion, position, whole);
+#else
+  xg_set_toolkit_scroll_bar_thumb (bar, portion, position, whole);
+#endif
+}
+
+static void
+x_set_toolkit_horizontal_scroll_bar_thumb (struct scroll_bar *bar, int portion,
+                                           int position, int whole)
+{
+#ifdef HAVE_GTK4
+  egtk_set_toolkit_horizontal_scroll_bar_thumb (bar, portion, position, whole);
+#else
+  xg_set_toolkit_horizontal_scroll_bar_thumb (bar, portion, position, whole);
+#endif
+}
+
+/* Create a scroll bar and return the scroll bar vector for it.  W is
+   the Emacs window on which to create the scroll bar. TOP, LEFT,
+   WIDTH and HEIGHT are the pixel coordinates and dimensions of the
+   scroll bar. */
+
+static struct scroll_bar *
+x_scroll_bar_create (struct window *w, int top, int left, int width, int height,
+                     bool horizontal)
+{
+  struct frame *f = XFRAME (w->frame);
+  struct scroll_bar *bar
+    = ALLOCATE_PSEUDOVECTOR (struct scroll_bar, prev, PVEC_OTHER);
+  Lisp_Object barobj;
+
+  block_input ();
+
+  if (horizontal)
+    x_create_horizontal_toolkit_scroll_bar (f, bar);
+  else
+    x_create_toolkit_scroll_bar (f, bar);
+
+  XSETWINDOW (bar->window, w);
+  bar->top = top;
+  bar->left = left;
+  bar->width = width;
+  bar->height = height;
+  bar->start = 0;
+  bar->end = 0;
+  bar->dragging = -1;
+  bar->horizontal = horizontal;
+
+  /* Add bar to its frame's list of scroll bars.  */
+  bar->next = FRAME_SCROLL_BARS (f);
+  bar->prev = Qnil;
+  XSETVECTOR (barobj, bar);
+  fset_scroll_bars (f, barobj);
+  if (!NILP (bar->next))
+    XSETVECTOR (XSCROLL_BAR (bar->next)->prev, bar);
+
+  /* Map the window/widget.  */
+  {
+    if (horizontal)
+#ifdef HAVE_GTK4
+      egtk_update_horizontal_scrollbar_pos
+#else
+      xg_update_horizontal_scrollbar_pos
+#endif
+	(f, bar->x_window, top, left, width,
+	 max (height, 1));
+    else
+#ifdef HAVE_GTK4
+      egtk_update_scrollbar_pos
+#else
+      xg_update_scrollbar_pos
+#endif
+	(f, bar->x_window, top, left, width,
+	 max (height, 1));
+  }
+
+  unblock_input ();
+  return bar;
+}
+
+/* Destroy scroll bar BAR, and set its Emacs window's scroll bar to
+   nil.  */
+
+static void
+x_scroll_bar_remove (struct scroll_bar *bar)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window)));
+  block_input ();
+
+#ifdef HAVE_GTK4
+  egtk_remove_scroll_bar (f, bar->x_window);
+#else
+  xg_remove_scroll_bar (f, bar->x_window);
+#endif
+
+  /* Dissociate this scroll bar from its window.  */
+  if (bar->horizontal)
+    wset_horizontal_scroll_bar (XWINDOW (bar->window), Qnil);
+  else
+    wset_vertical_scroll_bar (XWINDOW (bar->window), Qnil);
+
+  unblock_input ();
+}
+
+/* Set the handle of the vertical scroll bar for WINDOW to indicate
+   that we are displaying PORTION characters out of a total of WHOLE
+   characters, starting at POSITION.  If WINDOW has no scroll bar,
+   create one.  */
+
+static void
+pgtk_set_vertical_scroll_bar (struct window *w, int portion, int whole,
+                              int position)
+{
+  struct frame *f = XFRAME (w->frame);
+  Lisp_Object barobj;
+  struct scroll_bar *bar;
+  int top, height, left, width;
+  int window_y, window_height;
+
+  /* Get window dimensions.  */
+  window_box (w, ANY_AREA, 0, &window_y, 0, &window_height);
+  top = window_y;
+  height = window_height;
+  left = WINDOW_SCROLL_BAR_AREA_X (w);
+  width = WINDOW_SCROLL_BAR_AREA_WIDTH (w);
+
+  /* Does the scroll bar exist yet?  */
+  if (NILP (w->vertical_scroll_bar))
+    {
+      if (width > 0 && height > 0)
+        {
+          block_input ();
+          pgtk_clear_area (f, left, top, width, height);
+          unblock_input ();
+        }
+
+      bar = x_scroll_bar_create (w, top, left, width, max (height, 1), false);
+    }
+  else
+    {
+      /* It may just need to be moved and resized.  */
+      unsigned int mask = 0;
+
+      bar = XSCROLL_BAR (w->vertical_scroll_bar);
+
+      block_input ();
+
+      if (left != bar->left)
+        mask |= 1;
+      if (top != bar->top)
+        mask |= 1;
+      if (width != bar->width)
+        mask |= 1;
+      if (height != bar->height)
+        mask |= 1;
+
+      /* Move/size the scroll bar widget.  */
+      if (mask)
+        {
+          /* Since toolkit scroll bars are smaller than the space reserved
+             for them on the frame, we have to clear "under" them.  */
+          if (width > 0 && height > 0)
+            pgtk_clear_area (f, left, top, width, height);
+#ifndef HAVE_GTK4
+          xg_update_scrollbar_pos (f, bar->x_window, top, left, width,
+                                   max (height, 1));
+#else
+	  egtk_update_scrollbar_pos (f, bar->x_window, top, left, width,
+				     max (height, 1));
+#endif
+        }
+
+      /* Remember new settings.  */
+      bar->left = left;
+      bar->top = top;
+      bar->width = width;
+      bar->height = height;
+
+      unblock_input ();
+    }
+
+  x_set_toolkit_scroll_bar_thumb (bar, portion, position, whole);
+
+  XSETVECTOR (barobj, bar);
+  wset_vertical_scroll_bar (w, barobj);
+}
+
+static void
+pgtk_set_horizontal_scroll_bar (struct window *w, int portion, int whole,
+                                int position)
+{
+  struct frame *f = XFRAME (w->frame);
+  Lisp_Object barobj;
+  struct scroll_bar *bar;
+  int top, height, left, width;
+  int window_x, window_width;
+  int pixel_width = WINDOW_PIXEL_WIDTH (w);
+
+  /* Get window dimensions.  */
+  window_box (w, ANY_AREA, &window_x, 0, &window_width, 0);
+  left = window_x;
+  width = window_width;
+  top = WINDOW_SCROLL_BAR_AREA_Y (w);
+  height = WINDOW_SCROLL_BAR_AREA_HEIGHT (w);
+
+  /* Does the scroll bar exist yet?  */
+  if (NILP (w->horizontal_scroll_bar))
+    {
+      if (width > 0 && height > 0)
+        {
+          block_input ();
+
+          /* Clear also part between window_width and
+             WINDOW_PIXEL_WIDTH.  */
+          pgtk_clear_area (f, left, top, pixel_width, height);
+          unblock_input ();
+        }
+
+      bar = x_scroll_bar_create (w, top, left, width, height, true);
+    }
+  else
+    {
+      /* It may just need to be moved and resized.  */
+      unsigned int mask = 0;
+
+      bar = XSCROLL_BAR (w->horizontal_scroll_bar);
+
+      block_input ();
+
+      if (left != bar->left)
+        mask |= 1;
+      if (top != bar->top)
+        mask |= 1;
+      if (width != bar->width)
+        mask |= 1;
+      if (height != bar->height)
+        mask |= 1;
+
+      /* Move/size the scroll bar widget.  */
+      if (mask)
+        {
+          /* Since toolkit scroll bars are smaller than the space reserved
+             for them on the frame, we have to clear "under" them.  */
+          if (width > 0 && height > 0)
+            pgtk_clear_area (f, WINDOW_LEFT_EDGE_X (w), top,
+                             pixel_width - WINDOW_RIGHT_DIVIDER_WIDTH (w),
+                             height);
+#ifndef HAVE_GTK4
+          xg_update_horizontal_scrollbar_pos (f, bar->x_window, top, left,
+                                              width, height);
+#else
+	  egtk_update_horizontal_scrollbar_pos (f, bar->x_window, top, left,
+						width, height);
+#endif
+	}
+
+      /* Remember new settings.  */
+      bar->left = left;
+      bar->top = top;
+      bar->width = width;
+      bar->height = height;
+
+      unblock_input ();
+    }
+
+  x_set_toolkit_horizontal_scroll_bar_thumb (bar, portion, position, whole);
+
+  XSETVECTOR (barobj, bar);
+  wset_horizontal_scroll_bar (w, barobj);
+}
+
+/* The following three hooks are used when we're doing a thorough
+   redisplay of the frame.  We don't explicitly know which scroll bars
+   are going to be deleted, because keeping track of when windows go
+   away is a real pain - "Can you say set-window-configuration, boys
+   and girls?"  Instead, we just assert at the beginning of redisplay
+   that *all* scroll bars are to be removed, and then save a scroll bar
+   from the fiery pit when we actually redisplay its window.  */
+
+/* Arrange for all scroll bars on FRAME to be removed at the next call
+   to `*judge_scroll_bars_hook'.  A scroll bar may be spared if
+   `*redeem_scroll_bar_hook' is applied to its window before the judgment.  */
+
+static void
+pgtk_condemn_scroll_bars (struct frame *frame)
+{
+  if (!NILP (FRAME_SCROLL_BARS (frame)))
+    {
+      if (!NILP (FRAME_CONDEMNED_SCROLL_BARS (frame)))
+        {
+          /* Prepend scrollbars to already condemned ones.  */
+          Lisp_Object last = FRAME_SCROLL_BARS (frame);
+
+          while (!NILP (XSCROLL_BAR (last)->next))
+            last = XSCROLL_BAR (last)->next;
+
+          XSCROLL_BAR (last)->next = FRAME_CONDEMNED_SCROLL_BARS (frame);
+          XSCROLL_BAR (FRAME_CONDEMNED_SCROLL_BARS (frame))->prev = last;
+        }
+
+      fset_condemned_scroll_bars (frame, FRAME_SCROLL_BARS (frame));
+      fset_scroll_bars (frame, Qnil);
+    }
+}
+
+/* Un-mark WINDOW's scroll bar for deletion in this judgment cycle.
+   Note that WINDOW isn't necessarily condemned at all.  */
+
+static void
+pgtk_redeem_scroll_bar (struct window *w)
+{
+  struct scroll_bar *bar;
+  Lisp_Object barobj;
+  struct frame *f;
+
+  /* We can't redeem this window's scroll bar if it doesn't have one.  */
+  if (NILP (w->vertical_scroll_bar) && NILP (w->horizontal_scroll_bar))
+    emacs_abort ();
+
+  if (!NILP (w->vertical_scroll_bar) && WINDOW_HAS_VERTICAL_SCROLL_BAR (w))
+    {
+      bar = XSCROLL_BAR (w->vertical_scroll_bar);
+      /* Unlink it from the condemned list.  */
+      f = XFRAME (WINDOW_FRAME (w));
+      if (NILP (bar->prev))
+        {
+          /* If the prev pointer is nil, it must be the first in one of
+             the lists.  */
+          if (EQ (FRAME_SCROLL_BARS (f), w->vertical_scroll_bar))
+            /* It's not condemned.  Everything's fine.  */
+            goto horizontal;
+          else if (EQ (FRAME_CONDEMNED_SCROLL_BARS (f), w->vertical_scroll_bar))
+            fset_condemned_scroll_bars (f, bar->next);
+          else
+            /* If its prev pointer is nil, it must be at the front of
+               one or the other!  */
+            emacs_abort ();
+        }
+      else
+        XSCROLL_BAR (bar->prev)->next = bar->next;
+
+      if (!NILP (bar->next))
+        XSCROLL_BAR (bar->next)->prev = bar->prev;
+
+      bar->next = FRAME_SCROLL_BARS (f);
+      bar->prev = Qnil;
+      XSETVECTOR (barobj, bar);
+      fset_scroll_bars (f, barobj);
+      if (!NILP (bar->next))
+        XSETVECTOR (XSCROLL_BAR (bar->next)->prev, bar);
+    }
+
+horizontal:
+  if (!NILP (w->horizontal_scroll_bar) && WINDOW_HAS_HORIZONTAL_SCROLL_BAR (w))
+    {
+      bar = XSCROLL_BAR (w->horizontal_scroll_bar);
+      /* Unlink it from the condemned list.  */
+      f = XFRAME (WINDOW_FRAME (w));
+      if (NILP (bar->prev))
+        {
+          /* If the prev pointer is nil, it must be the first in one of
+             the lists.  */
+          if (EQ (FRAME_SCROLL_BARS (f), w->horizontal_scroll_bar))
+            /* It's not condemned.  Everything's fine.  */
+            return;
+          else if (EQ (FRAME_CONDEMNED_SCROLL_BARS (f),
+                       w->horizontal_scroll_bar))
+            fset_condemned_scroll_bars (f, bar->next);
+          else
+            /* If its prev pointer is nil, it must be at the front of
+               one or the other!  */
+            emacs_abort ();
+        }
+      else
+        XSCROLL_BAR (bar->prev)->next = bar->next;
+
+      if (!NILP (bar->next))
+        XSCROLL_BAR (bar->next)->prev = bar->prev;
+
+      bar->next = FRAME_SCROLL_BARS (f);
+      bar->prev = Qnil;
+      XSETVECTOR (barobj, bar);
+      fset_scroll_bars (f, barobj);
+      if (!NILP (bar->next))
+        XSETVECTOR (XSCROLL_BAR (bar->next)->prev, bar);
+    }
+}
+
+/* Remove all scroll bars on FRAME that haven't been saved since the
+   last call to `*condemn_scroll_bars_hook'.  */
+
+static void
+pgtk_judge_scroll_bars (struct frame *f)
+{
+  Lisp_Object bar, next;
+
+  bar = FRAME_CONDEMNED_SCROLL_BARS (f);
+
+  /* Clear out the condemned list now so we won't try to process any
+     more events on the hapless scroll bars.  */
+  fset_condemned_scroll_bars (f, Qnil);
+
+  for (; !NILP (bar); bar = next)
+    {
+      struct scroll_bar *b = XSCROLL_BAR (bar);
+
+      x_scroll_bar_remove (b);
+
+      next = b->next;
+      b->next = b->prev = Qnil;
+    }
+
+  /* Now there should be no references to the condemned scroll bars,
+     and they should get garbage-collected.  */
+}
+
+static void
+set_fullscreen_state (struct frame *f)
+{
+#ifdef HAVE_GTK4
+  if (FRAME_PARENT_FRAME (f))
+    return;
+#endif
+  GtkWindow *widget = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f));
+#ifdef HAVE_GTK3
+  GtkWidget *tbar = GTK_WIDGET (FRAME_GTK_HEADER_BAR (f));
+#endif
+  switch (f->want_fullscreen)
+    {
+    case FULLSCREEN_NONE:
+      PGTK_TRACE ("pgtk_fullscreen_hook: none.");
+      gtk_window_unfullscreen (widget);
+      gtk_window_unmaximize (widget);
+#ifdef HAVE_GTK3
+      if (GTK_IS_WIDGET (tbar))
+        gtk_widget_set_visible (tbar, true);
+#endif
+      store_frame_param (f, Qfullscreen, Qnil);
+      break;
+
+    case FULLSCREEN_BOTH:
+      PGTK_TRACE ("pgtk_fullscreen_hook: both.");
+      gtk_window_unmaximize (widget);
+      gtk_window_fullscreen (widget);
+      store_frame_param (f, Qfullscreen, Qfullboth);
+#ifdef HAVE_GTK3
+      if (GTK_IS_WIDGET (tbar))
+        gtk_widget_set_visible (tbar, false);
+#endif
+      break;
+
+    case FULLSCREEN_MAXIMIZED:
+      PGTK_TRACE ("pgtk_fullscreen_hook: maximized.");
+      gtk_window_unfullscreen (widget);
+      gtk_window_maximize (widget);
+      store_frame_param (f, Qfullscreen, Qmaximized);
+#ifdef HAVE_GTK3
+      if (GTK_IS_WIDGET (tbar))
+        gtk_widget_set_visible (tbar, true);
+#endif
+      break;
+
+    case FULLSCREEN_WIDTH:
+    case FULLSCREEN_HEIGHT:
+      PGTK_TRACE ("pgtk_fullscreen_hook: width or height.");
+      /* Not supported by gtk. Ignore them.*/
+    }
+
+  f->want_fullscreen = FULLSCREEN_NONE;
+}
+
+static void
+pgtk_fullscreen_hook (struct frame *f)
+{
+  PGTK_TRACE ("pgtk_fullscreen_hook:");
+  if (FRAME_VISIBLE_P (f))
+    {
+      block_input ();
+      set_fullscreen_state (f);
+      unblock_input ();
+    }
+}
+
+/* This function is called when the last frame on a display is deleted. */
+void
+pgtk_delete_terminal (struct terminal *terminal)
+{
+  struct pgtk_display_info *dpyinfo = terminal->display_info.pgtk;
+
+  /* Protect against recursive calls.  delete_frame in
+     delete_terminal calls us back when it deletes our last frame.  */
+  if (!terminal->name)
+    return;
+
+  block_input ();
+
+  pgtk_im_finish (dpyinfo);
+
+  /* Normally, the display is available...  */
+  if (dpyinfo->gdpy)
+    {
+      image_destroy_all_bitmaps (dpyinfo);
+#ifndef HAVE_GTK4
+      xg_display_close (dpyinfo->gdpy);
+#else
+      egtk_display_close (dpyinfo->gdpy);
+#endif
+      dpyinfo->gdpy = NULL;
+    }
+
+  delete_keyboard_wait_descriptor (dpyinfo->connection);
+
+  pgtk_delete_display (dpyinfo);
+  unblock_input ();
+}
+
+/* Store F's background color into *BGCOLOR.  */
+static void
+pgtk_query_frame_background_color (struct frame *f, Emacs_Color *bgcolor)
+{
+  bgcolor->pixel = FRAME_BACKGROUND_PIXEL (f);
+  pgtk_query_color (f, bgcolor);
+}
+
+static void
+pgtk_free_pixmap (struct frame *_f, Emacs_Pixmap pixmap)
+{
+  if (pixmap)
+    {
+      xfree (pixmap->data);
+      xfree (pixmap);
+    }
+}
+
+#ifdef HAVE_GTK4
+static GtkWindow *
+locate_nonchild_window (struct frame *child)
+{
+  while (FRAME_PARENT_FRAME (child))
+    child = FRAME_PARENT_FRAME (child);
+  return GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child));
+}
+#endif
+
+void
+pgtk_focus_frame (struct frame *f, bool noactivate)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  GtkWidget *wid =
+#ifndef HAVE_GTK4
+    FRAME_GTK_OUTER_WIDGET (f);
+#else
+    GTK_WIDGET (locate_nonchild_window (f));
+#endif
+ if (dpyinfo->x_focus_frame != f)
+   {
+     if (FRAME_PARENT_FRAME (f))
+       gtk_window_present (GTK_WINDOW (wid));
+#ifndef HAVE_GTK4
+     gtk_window_set_focus (GTK_WINDOW (wid), FRAME_GTK_WIDGET (f));
+#else
+     if (!FRAME_PARENT_FRAME (f))
+       gtk_window_set_focus (GTK_WINDOW (wid), FRAME_GTK_WIDGET (f));
+     else
+       gtk_widget_grab_focus (FRAME_GTK_WIDGET (f));
+#endif
+     dpyinfo->x_focus_frame = f;
+     redisplay ();
+   }
+}
+
+static void
+set_opacity_recursively (GtkWidget *w, gpointer data)
+{
+#ifdef HAVE_GTK3
+  gtk_widget_set_opacity (w, *(double *) data);
+#endif
+#ifndef HAVE_GTK3
+  if (GTK_IS_CONTAINER (w))
+    gtk_container_foreach (GTK_CONTAINER (w), set_opacity_recursively, data);
+#endif
+}
+
+static void
+x_set_frame_alpha (struct frame *f)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  double alpha = 1.0;
+  double alpha_min = 1.0;
+
+  if (dpyinfo->highlight_frame == f)
+    alpha = f->alpha[0];
+  else
+    alpha = f->alpha[1];
+
+  if (alpha < 0.0)
+    return;
+
+  if (FLOATP (Vframe_alpha_lower_limit))
+    alpha_min = XFLOAT_DATA (Vframe_alpha_lower_limit);
+  else if (FIXNUMP (Vframe_alpha_lower_limit))
+    alpha_min = (XFIXNUM (Vframe_alpha_lower_limit)) / 100.0;
+
+  if (alpha > 1.0)
+    alpha = 1.0;
+  else if (alpha < alpha_min && alpha_min <= 1.0)
+    alpha = alpha_min;
+
+  set_opacity_recursively (FRAME_GTK_OUTER_WIDGET (f), &alpha);
+  /* without this, blending mode is strange on wayland. */
+#ifndef HAVE_GTK4
+  gtk_widget_queue_resize_no_redraw (FRAME_GTK_OUTER_WIDGET (f));
+#else
+  gtk_widget_queue_resize (FRAME_GTK_OUTER_WIDGET (f));
+#endif
+}
+
+static void
+frame_highlight (struct frame *f)
+{
+  gui_update_cursor (f, true);
+  x_set_frame_alpha (f);
+}
+
+static void
+frame_unhighlight (struct frame * f)
+{
+  gui_update_cursor (f, true);
+  x_set_frame_alpha (f);
+}
+
+void
+pgtk_frame_rehighlight (struct pgtk_display_info *dpyinfo)
+{
+  struct frame *old_highlight = dpyinfo->highlight_frame;
+
+  if (dpyinfo->x_focus_frame)
+    {
+      dpyinfo->highlight_frame
+        = ((FRAMEP (FRAME_FOCUS_FRAME (dpyinfo->x_focus_frame)))
+             ? XFRAME (FRAME_FOCUS_FRAME (dpyinfo->x_focus_frame))
+             : dpyinfo->x_focus_frame);
+      if (!FRAME_LIVE_P (dpyinfo->highlight_frame))
+        {
+          fset_focus_frame (dpyinfo->x_focus_frame, Qnil);
+          dpyinfo->highlight_frame = dpyinfo->x_focus_frame;
+        }
+    }
+  else
+    dpyinfo->highlight_frame = 0;
+
+  if (old_highlight)
+    frame_unhighlight (old_highlight);
+  if (dpyinfo->highlight_frame)
+    frame_highlight (dpyinfo->highlight_frame);
+}
+
+/* The focus has changed, or we have redirected a frame's focus to
+   another frame (this happens when a frame uses a surrogate
+   mini-buffer frame).  Shift the highlight as appropriate.
+
+   The FRAME argument doesn't necessarily have anything to do with which
+   frame is being highlighted or un-highlighted; we only use it to find
+   the appropriate X display info.  */
+
+static void
+XTframe_rehighlight (struct frame *frame)
+{
+  pgtk_frame_rehighlight (FRAME_DISPLAY_INFO (frame));
+}
+
+/* Toggle mouse pointer visibility on frame F by using invisible cursor.  */
+
+static void
+x_toggle_visible_pointer (struct frame *f, bool invisible)
+{
+  Emacs_Cursor cursor;
+  if (invisible)
+    cursor = FRAME_DISPLAY_INFO (f)->invisible_cursor;
+  else
+    cursor = f->output_data.pgtk->current_cursor;
+#ifndef HAVE_GTK4
+  gdk_window_set_cursor (gtk_widget_get_window (FRAME_GTK_WIDGET (f)), cursor);
+#else
+  gtk_widget_set_cursor (FRAME_GTK_WIDGET (f), cursor);
+#endif
+  f->pointer_invisible = invisible;
+}
+
+static void
+x_setup_pointer_blanking (struct pgtk_display_info *dpyinfo)
+{
+  dpyinfo->toggle_visible_pointer = x_toggle_visible_pointer;
+  dpyinfo->invisible_cursor
+#ifndef HAVE_GTK4
+    = gdk_cursor_new_for_display (dpyinfo->gdpy, GDK_BLANK_CURSOR);
+#else
+    = gdk_cursor_new_from_name ("blank", NULL);
+#endif
+}
+
+static void
+pgtk_toggle_invisible_pointer (struct frame *f, bool invisible)
+{
+  block_input ();
+  FRAME_DISPLAY_INFO (f)->toggle_visible_pointer (f, invisible);
+  unblock_input ();
+}
+
+/* The focus has changed.  Update the frames as necessary to reflect
+   the new situation.  Note that we can't change the selected frame
+   here, because the Lisp code we are interrupting might become confused.
+   Each event gets marked with the frame in which it occurred, so the
+   Lisp code can tell when the switch took place by examining the events.  */
+
+static void
+x_new_focus_frame (struct pgtk_display_info *dpyinfo, struct frame *frame)
+{
+#ifndef HAVE_GTK4
+  struct frame *old_focus = dpyinfo->x_focus_frame;
+#endif
+  if (frame != dpyinfo->x_focus_frame
+#ifdef HAVE_GTK4
+      && frame && !FRAME_PARENT_FRAME (frame)
+#endif
+      )
+    {
+      /* Set this before calling other routines, so that they see
+         the correct value of x_focus_frame.  */
+      dpyinfo->x_focus_frame = frame;
+#ifndef HAVE_GTK4
+      if (old_focus && old_focus->auto_lower)
+        gdk_window_lower (
+          gtk_widget_get_window (FRAME_GTK_OUTER_WIDGET (old_focus)));
+      if (dpyinfo->x_focus_frame && dpyinfo->x_focus_frame->auto_raise)
+        gdk_window_raise (
+          gtk_widget_get_window (FRAME_GTK_OUTER_WIDGET (old_focus)));
+#endif
+    }
+#ifdef HAVE_GTK4
+  else if (frame != dpyinfo->x_focus_frame)
+    {
+      dpyinfo->x_focus_frame = frame;
+      if (frame)
+	pgtk_promote_child_frame (frame);
+    }
+#endif
+
+  pgtk_frame_rehighlight (dpyinfo);
+}
+
+void
+pgtk_child_frame_reposition (struct frame *child_frame, gpointer ignored)
+{
+#ifndef HAVE_GTK4
+  if (!FRAME_PARENT_FRAME (child_frame))
+    return;
+
+  GtkWindow *child_w = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child_frame));
+  GtkWindow *parent_w = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET
+				    (FRAME_PARENT_FRAME (child_frame)));
+
+  struct frame *parent = FRAME_PARENT_FRAME (child_frame);
+
+  gint x, y;
+  gint ow, oh, wd, h;
+#ifdef HAVE_GTK3
+  gdk_window_get_geometry (gtk_widget_get_window (GTK_WIDGET (parent_w)), &x,
+                           &y, NULL, NULL);
+  gdk_window_get_geometry (gtk_widget_get_window (GTK_WIDGET (child_w)), NULL,
+                           NULL, &ow, &oh);
+#else
+  gdk_window_get_position (gtk_widget_get_window (GTK_WIDGET (parent_w)), &x,
+                           &y);
+  gdk_window_get_geometry (gtk_widget_get_window (GTK_WIDGET (child_w)), NULL,
+                           NULL, &ow, &oh, NULL);
+#endif
+  wd = FRAME_PIXEL_WIDTH (child_frame);
+  h = FRAME_PIXEL_HEIGHT (child_frame);
+  if (ow != wd || oh != h)
+    child_frame->resized_p = true;
+#ifdef GDK_WINDOWING_WAYLAND
+  if (GDK_IS_WAYLAND_DISPLAY (
+        gdk_window_get_display (gtk_widget_get_window (GTK_WIDGET (parent_w)))))
+    {
+      gtk_window_set_transient_for (child_w, parent_w);
+      gtk_window_move (child_w,
+                       child_frame->left_pos
+                         + pgtk_get_window_decoration_width (parent),
+                       child_frame->top_pos
+                         + pgtk_get_window_inside_decor_height (parent));
+    }
+  else
+    {
+#endif
+      gtk_window_move (child_w,
+                       x + child_frame->left_pos
+                         + pgtk_get_window_decoration_width (parent),
+                       y + child_frame->top_pos
+                         + pgtk_get_window_inside_decor_height (parent));
+#ifdef GDK_WINDOWING_WAYLAND
+    }
+#endif
+  child_frame->redisplay = true;
+  return;
+#else
+  if (!child_frame)
+    return;
+  if (!FRAME_PARENT_FRAME (child_frame))
+    return;
+  GtkOverlay *parent_overlay = FRAME_OVERLAY (FRAME_PARENT_FRAME (child_frame));
+  GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (child_frame);
+  bool bf = GTK_IS_WINDOW (widget);
+  GtkBin *new_container;
+  if (bf)
+    {
+      new_container = GTK_BIN (xeg_child_frame_bin_new ());
+      gtk_overlay_add_overlay (parent_overlay, GTK_WIDGET (new_container));
+      GtkWidget *zw = gtk_bin_get_child
+	(GTK_BIN (FRAME_GTK_OUTER_WIDGET (child_frame)));
+      g_object_ref (zw);
+      g_object_ref (new_container);
+      g_object_ref (FRAME_GTK_OUTER_WIDGET (child_frame));
+      gtk_container_remove (GTK_CONTAINER (FRAME_GTK_OUTER_WIDGET (child_frame)), zw);
+      gtk_window_close (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child_frame)));
+      gtk_widget_destroy (FRAME_GTK_OUTER_WIDGET (child_frame));
+      FRAME_GTK_OUTER_WIDGET (child_frame) = GTK_WIDGET (new_container);
+      eassert (FRAME_PARENT_FRAME (child_frame));
+      eassert (FRAME_X_OUTPUT (child_frame));
+#pragma GCC diagnostic ignored "-Wnull-dereference"
+      FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order =
+	g_slist_append (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order, new_container);
+      if (FRAME_X_OUTPUT (child_frame)->keep_above &&
+	  FRAME_PARENT_FRAME (FRAME_X_OUTPUT (child_frame)->keep_above) == child_frame)
+	{
+	  FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order =
+	    g_slist_remove (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order,
+			    FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->keep_above);
+	  FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order =
+	    g_slist_append (FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order,
+			    FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->keep_above);
+	}
+      gtk_container_add (GTK_CONTAINER (new_container), zw);
+
+      GSList *sl = FRAME_X_OUTPUT (FRAME_PARENT_FRAME (child_frame))->child_frame_order;
+      for (; sl; sl = sl->next)
+	{
+	  gtk_container_remove (GTK_CONTAINER (parent_overlay), sl->data);
+	  bool vis = gtk_widget_is_visible (sl->data);
+	  gtk_overlay_add_overlay (GTK_OVERLAY (parent_overlay), sl->data);
+	  gtk_widget_set_visible (sl->data, vis);
+	}
+      g_clear_pointer (&FRAME_GTK_EV_HANDLER (child_frame), g_object_unref);
+      g_clear_pointer (&FRAME_GTK_ES_HANDLER (child_frame), g_object_unref);
+      FRAME_GTK_EV_HANDLER (child_frame) = emacs_gtk_event_handler_new (child_frame);
+      FRAME_GTK_ES_HANDLER (child_frame) = emacs_gtk_event_handler_new (child_frame);
+      g_object_ref (FRAME_GTK_EV_HANDLER (child_frame));
+      g_object_ref (FRAME_GTK_ES_HANDLER (child_frame));
+      FRAME_CR_CONTEXT (child_frame) = NULL;
+      FRAME_CR_SURFACE (child_frame) = NULL;
+      widget = GTK_WIDGET (new_container);
+      pgtk_set_event_handler (child_frame);
+      SET_FRAME_GARBAGED (child_frame);
+    }
+  else
+    {
+      GtkAllocation allocation;
+      gtk_widget_get_allocation (FRAME_GTK_OUTER_WIDGET (child_frame), &allocation);
+      if (allocation.height != FRAME_PIXEL_HEIGHT (child_frame) ||
+	  allocation.width != FRAME_PIXEL_WIDTH (child_frame))
+	child_frame->resized_p = true;
+    }
+  GdkRectangle *rect = g_malloc (sizeof (GdkRectangle));
+  rect->x = child_frame->left_pos;
+  rect->y = child_frame->top_pos;
+  rect->height = FRAME_PIXEL_HEIGHT (child_frame);
+  rect->width = FRAME_PIXEL_WIDTH (child_frame);
+  g_object_set_data_full (G_OBJECT (widget),
+			  EG_OVERLAY_ALLOC, rect, g_free);
+  gtk_widget_queue_resize (widget);
+#endif
+#pragma GCC diagnostic push
+}
+
+void
+pgtk_child_frame_flush (struct frame *parent)
+{
+  GSequence *children;
+  if (frame_nchild_list
+      && (children
+          = ((GSequence *) g_hash_table_lookup (frame_nchild_list, parent))))
+    {
+      g_sequence_foreach (children, pgtk_child_frame_flush_2, parent);
+    }
+}
+
+void
+pgtk_child_frame_flush_2 (gpointer child, gpointer userdata)
+{
+  struct frame *child_frame = (struct frame *) child;
+#ifndef HAVE_GTK4
+  struct frame *parent = (struct frame *) userdata;
+
+  if (!FRAME_LIVE_P (child_frame))
+    {
+      pgtk_clear_child_occourences (child_frame);
+      return;
+    }
+
+  GtkWindow *parent_w = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (parent));
+  GtkWindow *child_w_original
+    = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (child_frame));
+  GtkWindow *child_w
+    = gtk_window_get_window_type (child_w_original) == GTK_WINDOW_POPUP
+        ? child_w_original
+        : GTK_WINDOW (gtk_window_new (GTK_WINDOW_POPUP));
+
+  bool vflag = child_w_original == child_w;
+
+  if (vflag)
+    {
+      pgtk_child_frame_reposition (child_frame, 0);
+      return;
+    }
+
+  GtkWidget *widget = gtk_bin_get_child (GTK_BIN (child_w_original));
+
+  gtk_window_set_decorated (child_w, false);
+
+  gint cw = FRAME_PIXEL_WIDTH (child_frame);
+  gint ch = FRAME_PIXEL_HEIGHT (child_frame);
+  gint x, y;
+#ifdef HAVE_GTK3
+  gdk_window_get_geometry (gtk_widget_get_window (GTK_WIDGET (parent_w)), &x,
+                           &y, NULL, NULL);
+#else
+  gdk_window_get_position (gtk_widget_get_window (GTK_WIDGET (parent_w)), &x, &y);
+#endif
+  FRAME_GTK_OUTER_WIDGET (child_frame) = GTK_WIDGET (child_w);
+#ifdef HAVE_GTK3
+  FRAME_X_OUTPUT (child_frame)->header_bar = NULL;
+#endif
+  gtk_widget_reparent (widget, GTK_WIDGET (child_w));
+  gtk_window_set_accept_focus (child_w, true);
+
+  /* On certain development tree builds of GTK+ 3,
+   * gdk_window_get_position actually returns an i
+   * nvalid negative number on windowing systems t
+   * hat do not support absolute coordinates, such
+   * as Wayland. Rah. F***. [More swear words]
+   */
+#ifdef GDK_WINDOWING_WAYLAND
+  if (GDK_IS_WAYLAND_DISPLAY
+      (gdk_window_get_display
+       (gtk_widget_get_window (GTK_WIDGET (parent_w)))))
+    {
+      gtk_window_set_transient_for (child_w, parent_w);
+      gtk_window_move (child_w,
+                       child_frame->left_pos
+                         + pgtk_get_window_decoration_width (parent),
+                       child_frame->top_pos
+                         + pgtk_get_window_inside_decor_height (parent));
+    }
+  else
+    {
+#endif
+#ifdef HAVE_GTK3
+#ifdef HAVE_GDK_X11
+      if (GDK_IS_X11_WINDOW (gtk_widget_get_window (GTK_WIDGET (child_w))) &&
+	  GDK_IS_X11_WINDOW (gtk_widget_get_window (GTK_WIDGET (parent_w))))
+	{
+	  Window dpyc = gdk_x11_window_get_xid (gtk_widget_get_window (GTK_WIDGET (child_w)));
+	  Window dpyw = gdk_x11_window_get_xid (gtk_widget_get_window (GTK_WIDGET (parent_w)));
+	  Display *dpy = gdk_x11_display_get_xdisplay
+	    (gdk_window_get_display (gtk_widget_get_window (GTK_WIDGET (parent_w))));
+	  XReparentWindow (dpy, dpyc, dpyw, child_frame->left_pos, child_frame->top_pos);
+	  gtk_window_set_transient_for (child_w, parent_w);
+	}
+      else
+	{
+#endif
+	  gtk_window_set_transient_for (child_w, parent_w);
+	  gtk_window_move (child_w,
+			   x + child_frame->left_pos
+			   + pgtk_get_window_decoration_width (parent),
+			   y + child_frame->top_pos
+			   + pgtk_get_window_inside_decor_height (parent));
+#ifdef HAVE_GDK_X11
+	}
+#endif
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+    }
+#endif
+
+  if (!vflag)
+    {
+      gtk_widget_realize (widget);
+      gtk_widget_realize (FRAME_GTK_WIDGET (child_frame));
+      gtk_widget_realize (GTK_WIDGET (child_w));
+    }
+
+  if (!vflag)
+    gtk_widget_destroy (GTK_WIDGET (child_w_original));
+
+  if (cw > 0 && ch > 0)
+    gtk_window_resize (child_w, cw, ch);
+
+  FRAME_CR_SURFACE (child_frame) = NULL;
+  FRAME_CR_CONTEXT (child_frame) = NULL;
+#ifdef HAVE_GTK3
+  FRAME_X_OUTPUT (child_frame)->border_color_css_provider = NULL;
+  FRAME_X_OUTPUT (child_frame)->scrollbar_background_css_provider = NULL;
+#endif
+
+  child_frame->resized_p = true;
+  child_frame->redisplay = true;
+
+  pgtk_set_event_handler (child_frame);
+#else
+  pgtk_child_frame_reposition (child_frame, 0);
+#endif
+}
+
+static gboolean
+pointer_eq (gconstpointer a, gconstpointer b, gpointer ignored)
+{
+  return a == b;
+}
+
+static gboolean
+pointer_eq_plain (gconstpointer a, gconstpointer b)
+{
+  return a == b;
+}
+
+static unsigned int
+hash_of_frame (gconstpointer a)
+{
+  return g_str_hash (SSDATA (((struct frame *) a)->name));
+}
+
+static void
+pgtk_clear_child_occourences_internal (gpointer seq, gpointer child_i)
+{
+  GSequenceIter *iter;
+  while ((iter = g_sequence_lookup ((GSequence *) seq, child_i, pointer_eq, NULL)))
+    {
+      g_sequence_remove (iter);
+#ifndef HAVE_GTK4
+      struct frame *f = child_i;
+      GtkWidget *child_w = FRAME_GTK_OUTER_WIDGET (f);
+      gtk_widget_destroy (child_w);
+      FRAME_CR_CONTEXT (f) = NULL;
+      FRAME_CR_SURFACE (f) = NULL;
+      xg_create_frame_widgets (f);
+      pgtk_set_event_handler (f);
+#endif
+    }
+}
+
+static void
+pgtk_clear_child_occourences_trim (gpointer ptr, gpointer _)
+{
+  GSequence *seq = g_hash_table_lookup (frame_nchild_list, ptr);
+  if (seq && g_sequence_is_empty (seq))
+    {
+      g_sequence_free (seq);
+      g_hash_table_remove (frame_nchild_list, ptr);
+    }
+}
+
+void
+pgtk_clear_child_occourences (struct frame *child)
+{
+#ifdef HAVE_GTK4
+  Lisp_Object tail, head;
+  FOR_EACH_FRAME (tail, head)
+    {
+      if (FRAME_PGTK_P (XFRAME (head)))
+	{
+	  FRAME_X_OUTPUT (XFRAME (head))->child_frame_order =
+	    g_slist_remove (FRAME_X_OUTPUT (XFRAME (head))->child_frame_order, child);
+	}
+    }
+#endif
+  if (frame_nchild_list)
+    {
+      GList *values = g_hash_table_get_values (frame_nchild_list);
+      g_list_foreach (values, pgtk_clear_child_occourences_internal, child);
+      g_list_free (values);
+      GList *keys = g_hash_table_get_keys (frame_nchild_list);
+      g_list_foreach (keys, pgtk_clear_child_occourences_trim, NULL);
+      g_list_free (keys);
+    }
+}
+
+static void
+pgtk_set_frame_parent (struct frame *child, struct frame *parent)
+{
+  if (!frame_nchild_list)
+    frame_nchild_list = g_hash_table_new (hash_of_frame, pointer_eq_plain);
+
+  if (parent)
+    {
+      block_input ();
+      pgtk_clear_child_occourences (child);
+      GSequence *seq_tmp = frame_nchild_list
+                             ? g_hash_table_lookup (frame_nchild_list, parent)
+                             : NULL;
+      GSequence *seq = seq_tmp ? seq_tmp : g_sequence_new (NULL);
+      g_assert (seq);
+
+      GSequenceIter *gsr_lt = g_sequence_lookup (seq, child, pointer_eq, NULL);
+      if (!gsr_lt)
+        {
+          g_sequence_append (seq, child);
+          if (!seq_tmp)
+            {
+              g_hash_table_insert (frame_nchild_list, parent, seq);
+            }
+        }
+#ifdef HAVE_GTK3
+#ifndef HAVE_GTK4
+      FRAME_GTK_HEADER_BAR (child) = NULL;
+#else
+      if (FRAME_DISPLAY_HEADER_BAR (child))
+	g_object_ref (FRAME_GTK_HEADER_BAR (child));
+#endif
+#endif
+      pgtk_child_frame_flush (parent);
+      unblock_input ();
+    }
+  else
+    {
+      block_input ();
+      pgtk_clear_child_occourences (child);
+      unblock_input ();
+    }
+  FRAME_X_OUTPUT (child)->has_been_visible = 0;
+#ifdef HAVE_GTK4
+  FRAME_X_OUTPUT (child)->child_map_flag = 0;
+#endif
+}
+
+static struct terminal *
+pgtk_create_terminal (struct pgtk_display_info *dpyinfo)
+/* --------------------------------------------------------------------------
+      Set up use of Gtk before we make the first connection.
+   -------------------------------------------------------------------------- */
+{
+  struct terminal *terminal;
+
+  terminal = create_terminal (output_pgtk, &pgtk_redisplay_interface);
+
+  terminal->display_info.pgtk = dpyinfo;
+  dpyinfo->terminal = terminal;
+
+  terminal->clear_frame_hook = pgtk_db_clear_frame;
+  terminal->ring_bell_hook = pgtk_ring_bell;
+  terminal->toggle_invisible_pointer_hook = pgtk_toggle_invisible_pointer;
+  terminal->update_begin_hook = pgtk_update_begin;
+  terminal->update_end_hook = pgtk_update_end;
+  terminal->read_socket_hook = pgtk_read_socket;
+  // terminal->frame_up_to_date_hook = pgtk_frame_up_to_date;
+  terminal->mouse_position_hook = pgtk_mouse_position;
+  terminal->frame_rehighlight_hook = XTframe_rehighlight;
+  // terminal->frame_raise_lower_hook = pgtk_frame_raise_lower;
+  terminal->frame_visible_invisible_hook = pgtk_make_frame_visible_invisible;
+  terminal->fullscreen_hook = pgtk_fullscreen_hook;
+  terminal->menu_show_hook = pgtk_menu_show;
+#ifdef HAVE_EXT_MENU_BAR
+  terminal->activate_menubar_hook = pgtk_activate_menubar;
+#endif
+  terminal->popup_dialog_hook = pgtk_popup_dialog;
+  terminal->change_tab_bar_height_hook = x_change_tab_bar_height;
+  terminal->set_vertical_scroll_bar_hook = pgtk_set_vertical_scroll_bar;
+  terminal->set_horizontal_scroll_bar_hook = pgtk_set_horizontal_scroll_bar;
+  terminal->condemn_scroll_bars_hook = pgtk_condemn_scroll_bars;
+  terminal->redeem_scroll_bar_hook = pgtk_redeem_scroll_bar;
+  terminal->judge_scroll_bars_hook = pgtk_judge_scroll_bars;
+  terminal->get_string_resource_hook = pgtk_get_string_resource;
+  terminal->delete_frame_hook = x_destroy_window;
+  terminal->delete_terminal_hook = pgtk_delete_terminal;
+  terminal->query_frame_background_color = pgtk_query_frame_background_color;
+  terminal->defined_color_hook = pgtk_defined_color;
+  terminal->set_new_font_hook = pgtk_new_font;
+  terminal->implicit_set_name_hook = pgtk_implicitly_set_name;
+  terminal->iconify_frame_hook = pgtk_iconify_frame;
+  terminal->set_scroll_bar_default_width_hook
+    = pgtk_set_scroll_bar_default_width;
+  terminal->set_scroll_bar_default_height_hook
+    = pgtk_set_scroll_bar_default_height;
+  terminal->set_window_size_hook = pgtk_set_window_size;
+  terminal->query_colors = pgtk_query_colors;
+  terminal->get_focus_frame = x_get_focus_frame;
+  terminal->focus_frame_hook = pgtk_focus_frame;
+  terminal->set_frame_offset_hook = x_set_offset;
+  terminal->free_pixmap = pgtk_free_pixmap;
+  terminal->set_parent_frame_hook = pgtk_set_frame_parent;
+  terminal->set_frame_alpha_hook = x_set_frame_alpha;
+
+  /* Other hooks are NULL by default.  */
+
+  return terminal;
+}
+
+struct pgtk_window_is_of_frame_recursive_t
+{
+#ifdef HAVE_GTK4
+  GdkSurface *window;
+#else
+  GdkWindow *window;
+#endif
+  bool result;
+};
+
+static void
+pgtk_window_is_of_frame_recursive (GtkWidget *widget, gpointer data)
+{
+  struct pgtk_window_is_of_frame_recursive_t *datap = data;
+
+  if (datap->result)
+    return;
+
+#ifndef HAVE_GTK4
+  if (gtk_widget_get_window (widget) == datap->window)
+#else
+  if (gtk_native_get_surface (gtk_widget_get_native (widget)) == datap->window)
+#endif
+    {
+      datap->result = true;
+      return;
+    }
+
+  if (GTK_IS_CONTAINER (widget))
+    gtk_container_foreach (GTK_CONTAINER (widget),
+                           pgtk_window_is_of_frame_recursive, datap);
+}
+
+#ifndef HAVE_GTK4
+static bool
+pgtk_window_is_of_frame (struct frame *f, GdkWindow *window)
+#else
+static bool
+pgtk_window_is_of_frame (struct frame *f, GdkSurface *window)
+#endif
+{
+  struct pgtk_window_is_of_frame_recursive_t data;
+  data.window = window;
+  data.result = false;
+  pgtk_window_is_of_frame_recursive (FRAME_GTK_OUTER_WIDGET (f), &data);
+  return data.result;
+}
+
+/* Like x_window_to_frame but also compares the window with the widget's
+   windows.  */
+static struct frame *
+#ifdef HAVE_GTK4
+pgtk_any_window_to_frame (GdkSurface *window)
+#else
+pgtk_any_window_to_frame (GdkWindow *window)
+#endif
+{
+  Lisp_Object tail, frame;
+  struct frame *f, *found = NULL;
+
+  if (window == NULL)
+    return NULL;
+
+  FOR_EACH_FRAME (tail, frame)
+  {
+    if (found)
+      break;
+    f = XFRAME (frame);
+    if (FRAME_PGTK_P (f))
+      {
+        if (pgtk_window_is_of_frame (f, window))
+          found = f;
+      }
+  }
+
+  return found;
+}
+#ifndef HAVE_GTK4
+static void
+pgtk_cf_reposition_wrapper (gpointer f, gpointer data)
+{
+  pgtk_child_frame_reposition (f, 0);
+}
+
+static gboolean
+pgtk_handle_event (GtkWidget *widget, GdkEvent *event, gpointer *data)
+{
+  GdkWindow *w;
+  struct frame *f;
+  struct pgtk_display_info *i;
+  if (
+#ifndef HAVE_GTK4
+      (w = gtk_widget_get_window (widget))
+#else
+      (w = gtk_native_get_surface (gtk_widget_get_native (widget)))
+#endif
+
+      && (f = pgtk_any_window_to_frame (w))
+      && (i = FRAME_DISPLAY_INFO (f)))
+    {
+      i->last_event = event;
+      if (
+#ifndef HAVE_GTK4
+	  event->type
+#else
+	  gdk_event_get_event_type (event)
+#endif
+	  == GDK_CONFIGURE)
+	{
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+	  configure_event (widget, event, f);
+#endif
+#ifndef HAVE_GTK4
+	  if (frame_nchild_list)
+	    {
+	      GSequence *seq = g_hash_table_lookup (frame_nchild_list, f);
+	      if (seq)
+		g_sequence_foreach (seq, pgtk_cf_reposition_wrapper, 0);
+	    }
+#endif
+	}
+#ifdef HAVE_GTK3
+      if (widget == FRAME_GTK_WIDGET (f))
+	return gtk_event_controller_handle_event (GTK_EVENT_CONTROLLER
+						  (FRAME_GTK_EV_HANDLER (f)), event);
+      if (widget == FRAME_GTK_OUTER_WIDGET (f))
+	return gtk_event_controller_handle_event (GTK_EVENT_CONTROLLER
+						  (FRAME_GTK_ES_HANDLER (f)), event);
+#endif
+    }
+  return FALSE;
+}
+#endif
+static void
+pgtk_fill_rectangle (struct frame *f, unsigned long color, int x, int y,
+                     int width, int height)
+{
+  cairo_t *cr;
+  cr = pgtk_begin_cr_clip (f);
+  pgtk_set_cr_source_with_color (f, color);
+  cairo_rectangle (cr, x, y, width, height);
+  cairo_fill (cr);
+  pgtk_end_cr_clip (f);
+}
+
+void
+pgtk_clear_under_internal_border (struct frame *f)
+{
+  PGTK_TRACE ("pgtk_clear_under_internal_border");
+  if (FRAME_INTERNAL_BORDER_WIDTH (f) > 0)
+    {
+      int border = FRAME_INTERNAL_BORDER_WIDTH (f);
+      int width = FRAME_PIXEL_WIDTH (f);
+      int height = FRAME_PIXEL_HEIGHT (f);
+      int margin = 0;
+      struct face *face = FACE_FROM_ID_OR_NULL (f, INTERNAL_BORDER_FACE_ID);
+
+      block_input ();
+
+      struct
+      {
+        int x, y, w, h;
+      } rects[] = {
+        {0, margin, width, border},
+        {0, 0, border, height},
+        {width - border, 0, border, height},
+        {0, height - border, width, border},
+      };
+
+      if (face)
+        {
+          for (int i = 0; i < 4; i++)
+            {
+              int x = rects[i].x;
+              int y = rects[i].y;
+              int w = rects[i].w;
+              int h = rects[i].h;
+              fill_background_by_face (f, face, x, y, w, h);
+            }
+        }
+      else
+        {
+          for (int i = 0; i < 4; i++)
+            pgtk_clear_area (f, rects[i].x, rects[i].y, rects[i].w, rects[i].h);
+        }
+
+      unblock_input ();
+    }
+}
+
+#ifndef HAVE_GTK4
+static gboolean
+pgtk_handle_draw (GtkWidget *widget, cairo_t *cr, gpointer *data)
+{
+  block_input ();
+  struct frame *f;
+  GdkWindow *win = gtk_widget_get_window (widget);
+  if (win
+      != NULL)
+    {
+      f = pgtk_any_window_to_frame (win);
+      pgtk_cr_draw_frame (cr, f);
+    }
+  unblock_input ();
+  return FALSE;
+}
+#endif
+#if !defined (HAVE_GTK3)
+static gboolean pgtk_expose(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+  return pgtk_handle_draw (widget, gdk_cairo_create (event->window), data);
+}
+#endif
+
+static gboolean
+size_allocate (GtkWidget *widget,
+#ifndef HAVE_GTK4
+	       GtkAllocation *alloc,
+	       gpointer user_data)
+#else
+	        int width,
+		int height,
+		int baseline,
+		gpointer user_data)
+#endif
+{
+  block_input ();
+  struct frame *f = user_data;
+  if (!f)
+    goto out;
+#ifndef HAVE_GTK4
+  xg_frame_resized (f, alloc->width, alloc->height);
+#else
+  if (!(FRAME_DISPLAY_INFO (f)->gdpy || gtk_widget_get_display (widget)))
+    goto out;
+  GdkSurface *surface = EGTK_WIDGET_GET_WINDOW (widget);
+  if (!surface)
+    goto out;
+  egtk_frame_resized (f, width, height);
+#endif
+  if (f)
+    {
+      pgtk_cr_update_surface_desired_size (f,
+#ifndef HAVE_GTK4
+					   alloc->width, alloc->height
+#else
+					   width, height
+#endif
+					   );
+      pgtk_child_frame_flush (f);
+    }
+ out:
+  if (FRAME_GARBAGED_P (f))
+    FRAME_X_OUTPUT (f)->want_flip = false;
+  unblock_input ();
+  return true;
+}
+
+static void
+x_find_modifier_meanings (struct pgtk_display_info *dpyinfo)
+{
+#ifndef HAVE_GTK4
+  GdkDisplay *gdpy = dpyinfo->gdpy;
+  GdkKeymap *keymap = gdk_keymap_get_for_display (gdpy);
+  GdkModifierType state = GDK_META_MASK;
+#endif
+#ifndef HAVE_GTK4
+  gboolean r = gdk_keymap_map_virtual_modifiers (keymap, &state);
+  if (r)
+    {
+#endif
+#ifndef HAVE_GTK4
+      /* Meta key exists. */
+      if (state == GDK_META_MASK)
+        {
+          dpyinfo->meta_mod_mask = GDK_MOD1_MASK; /* maybe this is meta. */
+          dpyinfo->alt_mod_mask = 0;
+        }
+      else
+        {
+#endif
+#ifndef HAVE_GTK4
+	  dpyinfo->meta_mod_mask = state & ~GDK_META_MASK;
+          if (dpyinfo->meta_mod_mask == GDK_MOD1_MASK)
+            dpyinfo->alt_mod_mask = 0;
+          else
+            dpyinfo->alt_mod_mask = GDK_MOD1_MASK;
+        }
+#endif
+#ifndef HAVE_GTK4
+    }
+  else
+    {
+      dpyinfo->meta_mod_mask = GDK_MOD1_MASK;
+      dpyinfo->alt_mod_mask = 0;
+    }
+#else
+  dpyinfo->meta_mod_mask = GDK_ALT_MASK;
+  dpyinfo->alt_mod_mask = GDK_META_MASK;
+#endif
+}
+
+static void
+get_modifier_values (int *mod_ctrl, int *mod_meta, int *mod_alt, int *mod_hyper,
+                     int *mod_super)
+{
+  Lisp_Object tem;
+
+  *mod_ctrl = ctrl_modifier;
+  *mod_meta = meta_modifier;
+  *mod_alt = alt_modifier;
+  *mod_hyper = hyper_modifier;
+  *mod_super = super_modifier;
+
+  tem = Fget (Vx_ctrl_keysym, Qmodifier_value);
+  if (INTEGERP (tem))
+    *mod_ctrl = XFIXNUM (tem) & INT_MAX;
+  tem = Fget (Vx_alt_keysym, Qmodifier_value);
+  if (INTEGERP (tem))
+    *mod_alt = XFIXNUM (tem) & INT_MAX;
+  tem = Fget (Vx_meta_keysym, Qmodifier_value);
+  if (INTEGERP (tem))
+    *mod_meta = XFIXNUM (tem) & INT_MAX;
+  tem = Fget (Vx_hyper_keysym, Qmodifier_value);
+  if (INTEGERP (tem))
+    *mod_hyper = XFIXNUM (tem) & INT_MAX;
+  tem = Fget (Vx_super_keysym, Qmodifier_value);
+  if (INTEGERP (tem))
+    *mod_super = XFIXNUM (tem) & INT_MAX;
+}
+
+int
+pgtk_gtk_to_emacs_modifiers (struct pgtk_display_info *dpyinfo, int state)
+{
+  int mod_ctrl;
+  int mod_meta;
+  int mod_alt;
+  int mod_hyper;
+  int mod_super;
+  int mod;
+
+  get_modifier_values (&mod_ctrl, &mod_meta, &mod_alt, &mod_hyper, &mod_super);
+
+  mod = 0;
+  if (state & GDK_SHIFT_MASK)
+    mod |= shift_modifier;
+  if (state & GDK_CONTROL_MASK)
+    mod |= mod_ctrl;
+  if (state & dpyinfo->meta_mod_mask)
+    mod |= mod_meta;
+  if (state & dpyinfo->alt_mod_mask)
+    mod |= mod_alt;
+  if (state & GDK_SUPER_MASK)
+    mod |= mod_super;
+  if (state & GDK_HYPER_MASK)
+    mod |= mod_hyper;
+  return mod;
+}
+
+static int
+pgtk_emacs_to_gtk_modifiers (struct pgtk_display_info *dpyinfo, int state)
+{
+  int mod_ctrl;
+  int mod_meta;
+  int mod_alt;
+  int mod_hyper;
+  int mod_super;
+  int mask;
+
+  get_modifier_values (&mod_ctrl, &mod_meta, &mod_alt, &mod_hyper, &mod_super);
+
+  mask = 0;
+  if (state & mod_alt)
+    mask |= dpyinfo->alt_mod_mask;
+  if (state & mod_super)
+    mask |= GDK_SUPER_MASK;
+  if (state & mod_hyper)
+    mask |= GDK_HYPER_MASK;
+  if (state & shift_modifier)
+    mask |= GDK_SHIFT_MASK;
+  if (state & mod_ctrl)
+    mask |= GDK_CONTROL_MASK;
+  if (state & mod_meta)
+    mask |= dpyinfo->meta_mod_mask;
+  return mask;
+}
+
+#define GIsCursorKey(keysym) (0xff50 <= (keysym) && (keysym) < 0xff60)
+#define GIsMiscFunctionKey(keysym) (0xff60 <= (keysym) && (keysym) < 0xff6c)
+#define GIsKeypadKey(keysym) (0xff80 <= (keysym) && (keysym) < 0xffbe)
+#define GIsFunctionKey(keysym) (0xffbe <= (keysym) && (keysym) < 0xffe1)
+
+void
+pgtk_enqueue_string (struct frame *f, gchar *str)
+{
+  gunichar *ustr;
+
+  ustr = g_utf8_to_ucs4 (str, -1, NULL, NULL, NULL);
+  if (ustr == NULL)
+    return;
+  for (; *ustr != 0; ustr++)
+    {
+      union buffered_input_event inev;
+      Lisp_Object c = make_fixnum (*ustr);
+      EVENT_INIT (inev.ie);
+      inev.ie.kind
+        = (SINGLE_BYTE_CHAR_P (XFIXNAT (c)) ? ASCII_KEYSTROKE_EVENT
+                                            : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+      inev.ie.arg = Qnil;
+      inev.ie.code = XFIXNAT (c);
+      XSETFRAME (inev.ie.frame_or_window, f);
+      inev.ie.modifiers = 0;
+      inev.ie.timestamp = 0;
+      evq_enqueue (&inev);
+    }
+}
+
+void
+pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit)
+{
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = PGTK_PREEDIT_TEXT_EVENT;
+  inev.ie.arg = preedit;
+  inev.ie.code = 0;
+  XSETFRAME (inev.ie.frame_or_window, f);
+  inev.ie.modifiers = 0;
+  inev.ie.timestamp = 0;
+  evq_enqueue (&inev);
+}
+#ifndef HAVE_GTK4
+static
+#endif
+gboolean
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+key_press_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+#else
+key_press_event (GtkWidget *widget, GdkEvent *event, struct frame *f)
+#endif
+{
+  union buffered_input_event inev;
+  ptrdiff_t nbytes = 0;
+  Mouse_HLInfo *hlinfo;
+#pragma GCC diagnostic ignored "-Wunused-variable"
+  USE_SAFE_ALLOCA;
+#pragma GCC diagnostic push
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+#ifdef HAVE_GTK4
+  if (FRAME_ICONIFIED_P (f))
+    pgtk_make_frame_visible (f);
+#endif
+
+#ifdef HAVE_GTK4
+  guint keyval = gdk_key_event_get_keyval (event);
+#else
+  guint keyval = event->key.keyval;
+#endif
+
+#ifdef HAVE_GTK3
+#ifdef HAVE_GTK4
+  if (keyval == GDK_KEY_Hyper_L || keyval == GDK_KEY_Hyper_R)
+    FRAME_X_OUTPUT (f)->hyper_flag = true;
+#endif
+  if (keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R
+      || keyval == GDK_KEY_Hyper_L || keyval == GDK_KEY_Hyper_R
+      || keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R
+      || keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R
+      || keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R
+      || keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R)
+    return true;
+#endif
+
+#ifndef HAVE_GTK3
+  struct frame *f = user_data;
+#endif
+
+  if (f->output_data.pgtk->display_info->x_focus_frame)
+    f = f->output_data.pgtk->display_info->x_focus_frame;
+  hlinfo = MOUSE_HL_INFO (f);
+
+  /* If mouse-highlight is an integer, input clears out
+     mouse highlighting.  */
+  if (!hlinfo->mouse_face_hidden && INTEGERP (Vmouse_highlight))
+    {
+      clear_mouse_face (hlinfo);
+      hlinfo->mouse_face_hidden = true;
+    }
+
+  if (f != 0)
+    {
+      guint keysym, orig_keysym;
+      int modifiers;
+      Lisp_Object coding_system = Qlatin_1;
+      Lisp_Object c;
+#ifdef HAVE_GTK4
+      guint state = gdk_event_get_modifier_state (event);
+#else
+      guint state = event->key.state;
+#endif
+
+      state |= pgtk_emacs_to_gtk_modifiers (FRAME_DISPLAY_INFO (f),
+                                            extra_keyboard_modifiers);
+      modifiers = state;
+
+      /* This will have to go some day...  */
+
+      /* make_lispy_event turns chars into control chars.
+         Don't do it here because XLookupString is too eager.  */
+      state &= ~GDK_CONTROL_MASK;
+      state
+        &= ~(GDK_META_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK
+#ifndef HAVE_GTK4
+	     | GDK_MOD1_MASK
+#endif
+	     );
+#ifdef HAVE_GTK4
+      keysym = gdk_key_event_get_keyval (event);
+#else
+      keysym = event->key.keyval;
+#endif
+      orig_keysym = keysym;
+
+      /* Common for all keysym input events.  */
+      XSETFRAME (inev.ie.frame_or_window, f);
+#ifdef HAVE_GTK4
+      if (FRAME_X_OUTPUT (f)->hyper_flag)
+	{
+	  modifiers |= GDK_HYPER_MASK;
+	  modifiers &= ~GDK_SUPER_MASK;
+	}
+#endif
+      inev.ie.modifiers
+        = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), modifiers);
+      inev.ie.timestamp = gdk_event_get_time (event);
+
+      /* First deal with keysyms which have defined
+         translations to characters.  */
+      if (keysym >= 32 && keysym < 128)
+        /* Avoid explicitly decoding each ASCII character.  */
+        {
+          inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+          inev.ie.code = keysym;
+          goto done;
+        }
+
+      /* Keysyms directly mapped to Unicode characters.  */
+      if (keysym >= 0x01000000 && keysym <= 0x0110FFFF)
+        {
+          if (keysym < 0x01000080)
+            inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+          else
+            inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+          inev.ie.code = keysym & 0xFFFFFF;
+          goto done;
+        }
+
+      /* Now non-ASCII.  */
+      if (HASH_TABLE_P (Vpgtk_keysym_table)
+          && (c = Fgethash (make_fixnum (keysym), Vpgtk_keysym_table, Qnil),
+              FIXNATP (c)))
+        {
+          inev.ie.kind = (SINGLE_BYTE_CHAR_P (XFIXNAT (c))
+                            ? ASCII_KEYSTROKE_EVENT
+                            : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+          inev.ie.code = XFIXNAT (c);
+          goto done;
+        }
+
+
+      /* Random non-modifier sorts of keysyms.  */
+      if (((keysym >= GDK_KEY_BackSpace && keysym <= GDK_KEY_Escape)
+           || keysym == GDK_KEY_Delete
+#ifdef GDK_KEY_ISO_Left_Tab
+           || (keysym >= GDK_KEY_ISO_Left_Tab && keysym <= GDK_KEY_ISO_Enter)
+#endif
+           || GIsCursorKey (keysym)       /* 0xff50 <= x < 0xff60 */
+           || GIsMiscFunctionKey (keysym) /* 0xff60 <= x < VARIES */
+#ifdef HPUX
+           /* This recognizes the "extended function
+              keys".  It seems there's no cleaner way.
+              Test IsModifierKey to avoid handling
+              mode_switch incorrectly.  */
+           || (GDK_KEY_Select <= keysym && keysym < GDK_KEY_KP_Space)
+#endif
+#ifdef GDK_KEY_dead_circumflex
+           || orig_keysym == GDK_KEY_dead_circumflex
+#endif
+#ifdef GDK_KEY_dead_grave
+           || orig_keysym == GDK_KEY_dead_grave
+#endif
+#ifdef GDK_KEY_dead_tilde
+           || orig_keysym == GDK_KEY_dead_tilde
+#endif
+#ifdef GDK_KEY_dead_diaeresis
+           || orig_keysym == GDK_KEY_dead_diaeresis
+#endif
+#ifdef GDK_KEY_dead_macron
+           || orig_keysym == GDK_KEY_dead_macron
+#endif
+#ifdef GDK_KEY_dead_degree
+           || orig_keysym == GDK_KEY_dead_degree
+#endif
+#ifdef GDK_KEY_dead_acute
+           || orig_keysym == GDK_KEY_dead_acute
+#endif
+#ifdef GDK_KEY_dead_cedilla
+           || orig_keysym == GDK_KEY_dead_cedilla
+#endif
+#ifdef GDK_KEY_dead_breve
+           || orig_keysym == GDK_KEY_dead_breve
+#endif
+#ifdef GDK_KEY_dead_ogonek
+           || orig_keysym == GDK_KEY_dead_ogonek
+#endif
+#ifdef GDK_KEY_dead_caron
+           || orig_keysym == GDK_KEY_dead_caron
+#endif
+#ifdef GDK_KEY_dead_doubleacute
+           || orig_keysym == GDK_KEY_dead_doubleacute
+#endif
+#ifdef GDK_KEY_dead_abovedot
+           || orig_keysym == GDK_KEY_dead_abovedot
+#endif
+           || GIsKeypadKey (keysym)   /* 0xff80 <= x < 0xffbe */
+           || GIsFunctionKey (keysym) /* 0xffbe <= x < 0xffe1 */
+           /* Any "vendor-specific" key is ok.  */
+           || (orig_keysym & (1 << 28))
+           || (keysym != GDK_KEY_VoidSymbol && nbytes == 0))
+          && !(
+#ifndef HAVE_GTK4
+	       event->key.is_modifier
+#else
+	       gdk_key_event_is_modifier (event)
+#endif
+#if defined GDK_KEY_ISO_Lock && defined GDK_KEY_ISO_Last_Group_Lock
+               || (GDK_KEY_ISO_Lock <= orig_keysym
+                   && orig_keysym <= GDK_KEY_ISO_Last_Group_Lock)
+#endif
+                 ))
+        {
+          STORE_KEYSYM_FOR_DEBUG (keysym);
+          /* make_lispy_event will convert this to a symbolic
+             key.  */
+          inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+          inev.ie.code = keysym;
+          goto done;
+        }
+    }
+
+done:
+  if (inev.ie.kind != NO_EVENT)
+    {
+      XSETFRAME (inev.ie.frame_or_window, f);
+      evq_enqueue (&inev);
+      // count++;
+    }
+
+  SAFE_FREE ();
+
+  return TRUE;
+}
+
+static gboolean
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+key_release_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data)
+#else
+key_release_event (GtkWidget *widget, GdkEvent *event, struct frame *_)
+#endif
+{
+#ifdef HAVE_GTK4
+  int keyval = gdk_key_event_get_keyval (event);
+  if (keyval == GDK_KEY_Hyper_L || keyval == GDK_KEY_Hyper_R)
+    FRAME_X_OUTPUT (_)->hyper_flag = false;
+#endif
+  return TRUE;
+}
+
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+static gboolean
+configure_event (GtkWidget *widget, GdkEvent *event, struct frame *user_data)
+#else
+static gboolean
+configure_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data)
+#endif
+{
+  struct frame *f = pgtk_any_window_to_frame (
+#ifndef HAVE_GTK4
+					      event->configure.window
+#else
+					      gdk_event_get_surface (event)
+#endif
+					      );
+
+  if (f)
+    {
+#ifndef HAVE_GTK4
+      if (FRAME_PARENT_FRAME (f))
+        pgtk_child_frame_reposition (f, 0);
+#endif
+    }
+  return TRUE;
+}
+
+static gboolean
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+map_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+#else
+map_event (GtkWidget *widget,
+#ifndef HAVE_GTK4
+	   GdkEvent *event,
+#endif
+	   struct frame *frame)
+#endif
+{
+  struct frame *f =
+#ifndef HAVE_GTK4
+    pgtk_any_window_to_frame (event->any.window);
+#else
+    frame;
+#endif
+  union buffered_input_event inev;
+
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+
+  if (f)
+    {
+      bool iconified = FRAME_ICONIFIED_P (f);
+
+      /* Check if fullscreen was specified before we where mapped the
+         first time, i.e. from the command line.  */
+      if (!FRAME_X_OUTPUT (f)->has_been_visible)
+        {
+          set_fullscreen_state (f);
+        }
+
+      if (!iconified)
+        {
+          /* The `z-group' is reset every time a frame becomes
+             invisible.  Handle this here.  */
+          if (FRAME_Z_GROUP (f) == z_group_above)
+            x_set_z_group (f, Qabove, Qnil);
+          else if (FRAME_Z_GROUP (f) == z_group_below)
+            x_set_z_group (f, Qbelow, Qnil);
+        }
+
+      SET_FRAME_VISIBLE (f, 1);
+      SET_FRAME_ICONIFIED (f, false);
+      FRAME_X_OUTPUT (f)->has_been_visible = true;
+
+      if (iconified)
+        {
+          inev.ie.kind = DEICONIFY_EVENT;
+          XSETFRAME (inev.ie.frame_or_window, f);
+        }
+      else if (!NILP (Vframe_list) && !NILP (XCDR (Vframe_list)))
+        /* Force a redisplay sooner or later to update the
+           frame titles in case this is the second frame.  */
+        record_asynch_buffer_change ();
+    }
+
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  return FALSE;
+}
+
+static gboolean
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+window_state_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+#else
+window_state_event (GtkWidget *widget, GdkEvent *event, struct frame *frame)
+#endif
+{
+  struct frame *f = pgtk_any_window_to_frame
+#ifndef HAVE_GTK4
+    (event->any.window);
+#else
+    (gdk_event_get_surface (event));
+#endif
+  union buffered_input_event inev;
+
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+
+  if (f)
+    {
+#ifdef HAVE_GTK3
+#ifndef HAVE_GTK4
+      if (event->window_state.new_window_state & GDK_WINDOW_STATE_FOCUSED)
+#else
+      if (gtk_window_is_active (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f))))
+#endif
+#endif
+        {
+          if (FRAME_ICONIFIED_P (f))
+            {
+              /* Gnome shell does not iconify us when C-z is pressed.
+                 It hides the frame.  So if our state says we aren't
+                 hidden anymore, treat it as deiconified.  */
+              SET_FRAME_VISIBLE (f, 1);
+              SET_FRAME_ICONIFIED (f, false);
+              FRAME_X_OUTPUT (f)->has_been_visible = true;
+              inev.ie.kind = DEICONIFY_EVENT;
+              XSETFRAME (inev.ie.frame_or_window, f);
+            }
+        }
+    }
+
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  return TRUE;
+}
+
+static gboolean
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+delete_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+#else
+#ifndef HAVE_GTK4
+delete_event (GtkWidget *widget, GdkEvent *event, struct frame *_)
+#else
+delete_event (GtkWindow *widget, struct frame *f)
+#endif
+#endif
+{
+#ifndef HAVE_GTK4
+  struct frame *f = pgtk_any_window_to_frame (event->any.window);
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+  if (f)
+    {
+      block_input ();
+      FRAME_GTK_OUTER_WIDGET (f) = NULL;
+      FRAME_GTK_WIDGET (f) = NULL;
+      FRAME_TERMINAL (f) = NULL;
+      inev.ie.kind = DELETE_WINDOW_EVENT;
+      XSETFRAME (inev.ie.frame_or_window, f);
+      unblock_input ();
+    }
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  unrequest_sigio ();
+#else
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = DELETE_WINDOW_EVENT;
+  XSETFRAME (inev.ie.frame_or_window, f);
+  evq_enqueue (&inev);
+  unrequest_sigio ();
+#endif
+  return TRUE;
+}
+
+/* The focus may have changed.  Figure out if it is a real focus change,
+   by checking both FocusIn/Out and Enter/LeaveNotify events.
+
+   Returns FOCUS_IN_EVENT event in *BUFP. */
+
+/* Handle FocusIn and FocusOut state changes for FRAME.
+   If FRAME has focus and there exists more than one frame, puts
+   a FOCUS_IN_EVENT into *BUFP.  */
+
+static void
+x_focus_changed (gboolean is_enter, int state,
+                 struct pgtk_display_info *dpyinfo, struct frame *frame,
+                 union buffered_input_event *bufp)
+{
+  if (is_enter)
+    {
+      if (dpyinfo->x_focus_event_frame != frame)
+        {
+          x_new_focus_frame (dpyinfo, frame);
+          dpyinfo->x_focus_event_frame = frame;
+
+          /* Don't stop displaying the initial startup message
+             for a switch-frame event we don't need.  */
+          /* When run as a daemon, Vterminal_frame is always NIL.  */
+          bufp->ie.arg = (((NILP (Vterminal_frame)
+                            || !FRAME_PGTK_P (XFRAME (Vterminal_frame))
+                            || EQ (Fdaemonp (), Qt))
+                           && CONSP (Vframe_list) && !NILP (XCDR (Vframe_list)))
+                            ? Qt
+                            : Qnil);
+          bufp->ie.kind = FOCUS_IN_EVENT;
+          XSETFRAME (bufp->ie.frame_or_window, frame);
+        }
+
+      frame->output_data.pgtk->focus_state |= state;
+    }
+  else
+    {
+      frame->output_data.pgtk->focus_state &= ~state;
+
+      if (dpyinfo->x_focus_event_frame == frame)
+        {
+          dpyinfo->x_focus_event_frame = 0;
+          x_new_focus_frame (dpyinfo, 0);
+
+          bufp->ie.kind = FOCUS_OUT_EVENT;
+          XSETFRAME (bufp->ie.frame_or_window, frame);
+        }
+
+      if (frame->pointer_invisible)
+        pgtk_toggle_invisible_pointer (frame, false);
+    }
+}
+
+static gboolean
+enter_notify_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+  union buffered_input_event inev;
+  struct frame *frame
+#ifdef HAVE_GTK4
+    = user_data;
+#else
+    = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#endif
+  if (frame == NULL)
+    return FALSE;
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
+  struct frame *focus_frame = dpyinfo->x_focus_frame;
+  int focus_state
+    = focus_frame ? focus_frame->output_data.pgtk->focus_state : 0;
+
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+#ifdef HAVE_GTK4
+  GdkNotifyType crossing_detail;
+  GdkCrossingMode crossing_focus;
+  crossing_detail = gdk_crossing_event_get_detail (event);
+  crossing_focus = gdk_crossing_event_get_mode (event);
+#endif
+  if (
+#ifdef HAVE_GTK4
+      crossing_detail
+#else
+      event->crossing.detail
+#endif
+      != GDK_NOTIFY_INFERIOR &&
+#ifndef HAVE_GTK4
+      event->crossing.focus
+#else
+      crossing_focus
+#endif
+      && !(focus_state & FOCUS_EXPLICIT))
+    x_focus_changed (TRUE, FOCUS_IMPLICIT, dpyinfo, frame, &inev);
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  return TRUE;
+}
+
+#ifdef HAVE_GTK4
+static gboolean
+long_press_event (struct frame *f, gdouble ex, gdouble ey)
+{
+  int x = XWINDOW (FRAME_SELECTED_WINDOW (f))->cursor.x;
+  x += XWINDOW (FRAME_SELECTED_WINDOW (f))->pixel_left;
+  int y = XWINDOW (FRAME_SELECTED_WINDOW (f))->cursor.y;
+  y += XWINDOW (FRAME_SELECTED_WINDOW (f))->pixel_top;
+  gdouble d = fabs (x - ex);
+  gdouble q = fabs (y - ey);
+
+  if (d < FRAME_COLUMN_WIDTH (f) + 3 &&
+      q < FRAME_LINE_HEIGHT (f) + 3)
+    emacs_resizable_drawing_area_show_selection_options
+      ((gpointer) FRAME_GTK_WIDGET (f));
+  else
+    {
+      union buffered_input_event inev;
+      EVENT_INIT (inev.ie);
+      inev.ie.kind = MOUSE_CLICK_EVENT;
+      inev.ie.code = 0;
+      inev.ie.timestamp = time (NULL);
+      inev.ie.modifiers = down_modifier;
+      inev.ie.x = make_fixnum ((int) round (ex));
+      inev.ie.y = make_fixnum ((int) round (ey));
+      XSETFRAME (inev.ie.frame_or_window, f);
+      if (!FRAME_GTK_EV_HANDLER (f)->s_flag)
+	emacs_gtk_event_handler_toggle_scroll_evs (FRAME_GTK_EV_HANDLER (f),
+						   false);
+      evq_enqueue (&inev);
+    }
+  return true;
+}
+#endif
+
+static gboolean
+leave_notify_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+  union buffered_input_event inev;
+  struct frame *frame
+#ifdef HAVE_GTK4
+    = user_data;
+#else
+    = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#endif
+  if (frame == NULL)
+    return FALSE;
+  if (!FRAME_LIVE_P (frame))
+    return FALSE;
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
+  struct frame *focus_frame = dpyinfo->x_focus_frame;
+  int focus_state
+    = focus_frame ? focus_frame->output_data.pgtk->focus_state : 0;
+
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+#ifdef HAVE_GTK4
+  GdkNotifyType crossing_detail;
+  GdkCrossingMode crossing_focus;
+
+  crossing_detail = gdk_crossing_event_get_detail (event);
+  crossing_focus = gdk_crossing_event_get_mode (event);
+#endif
+  if (
+#ifdef HAVE_GTK4
+      crossing_detail
+#else
+      event->crossing.detail
+#endif
+      != GDK_NOTIFY_INFERIOR &&
+#ifndef HAVE_GTK4
+      event->crossing.focus
+#else
+      crossing_focus
+#endif
+      && !(focus_state & FOCUS_EXPLICIT))
+    x_focus_changed (FALSE, FOCUS_IMPLICIT, dpyinfo, frame, &inev);
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  return TRUE;
+}
+
+#ifdef HAVE_GTK4
+static void
+leave (GtkWidget *w, GdkCrossingMode cm, struct frame *frame)
+{
+  if (!g_object_get_data (G_OBJECT (w), "paring-entered"))
+    return;
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
+  struct frame *focus_frame = dpyinfo->x_focus_frame;
+  int focus_state
+    = focus_frame ? focus_frame->output_data.pgtk->focus_state : 0;
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+
+  if (!(focus_state & FOCUS_EXPLICIT) && cm)
+    x_focus_changed (false, FOCUS_IMPLICIT, dpyinfo, frame, &inev);
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  g_object_set_data (G_OBJECT (w), "paring-entered", (gpointer) false);
+}
+
+static void
+enter (GtkWidget *w, GdkCrossingMode cm, struct frame *frame)
+{
+  g_object_set_data (G_OBJECT (w), "paring-entered", (gpointer) true);
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
+  struct frame *focus_frame = dpyinfo->x_focus_frame;
+  int focus_state
+    = focus_frame ? focus_frame->output_data.pgtk->focus_state : 0;
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+
+  if (!(focus_state & FOCUS_EXPLICIT) && cm)
+    x_focus_changed (true, FOCUS_IMPLICIT, dpyinfo, frame, &inev);
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+}
+#endif
+
+static gboolean
+focus_in_event (GtkWidget *widget, GdkEvent *unused, gpointer user_data)
+{
+  union buffered_input_event inev;
+#ifndef HAVE_GTK4
+  struct frame *frame
+    = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#else
+  struct frame *frame = user_data;
+#endif
+#ifdef HAVE_GTK4
+  if (FRAME_X_OUTPUT (frame)->bar_focus_in_events)
+    {
+      FRAME_X_OUTPUT (frame)->bar_focus_in_events = false;
+      return TRUE;
+    }
+  if (widget &&
+      !FRAME_PARENT_FRAME (frame) && FRAME_DISPLAY_INFO (frame)->last_mouse_motion_frame &&
+      FRAME_PARENT_FRAME (FRAME_DISPLAY_INFO (frame)->last_mouse_motion_frame) == frame)
+    frame = FRAME_DISPLAY_INFO (frame)->last_mouse_motion_frame;
+#ifdef HAVE_GTK4
+  if (widget && GTK_IS_WINDOW (widget)
+      && FRAME_X_OUTPUT (frame)->last_focus_child_frame
+      && FRAME_VISIBLE_P (FRAME_X_OUTPUT (frame)->last_focus_child_frame))
+    frame = FRAME_X_OUTPUT (frame)->last_focus_child_frame;
+
+  if (FRAME_PARENT_FRAME (frame))
+    FRAME_X_OUTPUT (FRAME_PARENT_FRAME (frame))->last_focus_child_frame = frame;
+  else
+    FRAME_X_OUTPUT (frame)->last_focus_child_frame = NULL;
+#endif
+#endif
+  if (frame == NULL)
+    return TRUE;
+
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+  if (FRAME_ICONIFIED_P (frame))
+    {
+      /* Gnome shell does not iconify us when C-z is pressed.
+	 It hides the frame.  So if our state says we aren't
+	 hidden anymore, treat it as deiconified.  */
+      SET_FRAME_VISIBLE (frame, 1);
+      SET_FRAME_ICONIFIED (frame, false);
+      FRAME_X_OUTPUT (frame)->has_been_visible = true;
+      inev.ie.kind = DEICONIFY_EVENT;
+      XSETFRAME (inev.ie.frame_or_window, frame);
+      evq_enqueue (&inev);
+    }
+  x_focus_changed (TRUE, FOCUS_EXPLICIT, FRAME_DISPLAY_INFO (frame), frame,
+                   &inev);
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+
+  pgtk_im_focus_in (frame);
+
+  return TRUE;
+}
+
+static gboolean
+focus_out_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+  union buffered_input_event inev;
+#ifndef HAVE_GTK4
+  struct frame *frame
+    = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#else
+  struct frame *frame = user_data;
+#endif
+  if (frame == NULL)
+    return TRUE;
+#ifdef HAVE_GTK4
+  if (FRAME_X_OUTPUT (frame)->bar_focus_out_events)
+    {
+      FRAME_X_OUTPUT (frame)->bar_focus_out_events = 0;
+      return TRUE;
+    }
+#endif
+#ifdef HAVE_GTK4
+  GtkWidget *focus = gtk_window_get_focus (locate_nonchild_window (frame));
+  if (focus &&
+      g_object_get_data (G_OBJECT (focus), "embedded-widget-p"))
+    return true;
+#endif
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+
+  x_focus_changed (FALSE, FOCUS_EXPLICIT, FRAME_DISPLAY_INFO (frame), frame,
+                   &inev);
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+
+  pgtk_im_focus_out (frame);
+
+  return TRUE;
+}
+
+/* Function to report a mouse movement to the mainstream Emacs code.
+   The input handler calls this.
+
+   We have received a mouse movement event, which is given in *event.
+   If the mouse is over a different glyph than it was last time, tell
+   the mainstream emacs code by setting mouse_moved.  If not, ask for
+   another motion event, so we can check again the next time it moves.  */
+#ifdef HAVE_GTK4
+static bool
+note_mouse_movement (struct frame *frame, GdkEvent *event)
+#else
+static bool
+note_mouse_movement (struct frame *frame, GdkEventMotion *event)
+#endif
+{
+  NativeRectangle *r;
+  struct pgtk_display_info *dpyinfo;
+
+  if (!FRAME_X_OUTPUT (frame))
+    return false;
+
+  dpyinfo = FRAME_DISPLAY_INFO (frame);
+#ifndef HAVE_GTK4
+  dpyinfo->last_mouse_movement_time = event->time;
+  dpyinfo->last_mouse_motion_frame = frame;
+  dpyinfo->last_mouse_motion_x = event->x;
+  dpyinfo->last_mouse_motion_y = event->y;
+#else
+  dpyinfo->last_mouse_movement_time = gdk_event_get_time (event);
+  dpyinfo->last_mouse_motion_frame = frame;
+  gdouble x, y;
+  gdk_event_get_position (event, &x, &y);
+  x -= calculate_child_frame_distance_x (frame);
+  y -= calculate_child_frame_distance_y (frame);
+  dpyinfo->last_mouse_motion_x = (int) round (x);
+  dpyinfo->last_mouse_motion_y = (int) round (y);
+#endif
+
+  if (
+#ifdef HAVE_GTK4
+      gdk_event_get_surface ((GdkEvent *) event) !=
+      gtk_native_get_surface (gtk_widget_get_native (FRAME_GTK_WIDGET (frame)))
+#else
+      event->window != gtk_widget_get_window (FRAME_GTK_WIDGET (frame))
+#endif
+      )
+    {
+      frame->mouse_moved = true;
+      dpyinfo->last_mouse_scroll_bar = NULL;
+      note_mouse_highlight (frame, -1, -1);
+      dpyinfo->last_mouse_glyph_frame = NULL;
+      return true;
+    }
+#ifdef HAVE_GTK4
+#define EX (int) round (x)
+#define EY (int) round (y)
+#else
+#define EX event->x
+#define EY event->y
+#endif
+  /* Has the mouse moved off the glyph it was on at the last sighting?  */
+  r = &dpyinfo->last_mouse_glyph;
+  if (frame != dpyinfo->last_mouse_glyph_frame || EX < r->x
+      || EY >= r->x + r->width || EY < r->y
+      || EX >= r->y + r->height)
+    {
+      frame->mouse_moved = true;
+      dpyinfo->last_mouse_scroll_bar = NULL;
+      note_mouse_highlight (frame, EX, EY);
+      /* Remember which glyph we're now on.  */
+      remember_mouse_glyph (frame, EX, EY, r);
+      dpyinfo->last_mouse_glyph_frame = frame;
+      return true;
+    }
+
+  pgtk_toggle_invisible_pointer (frame, false);
+
+  return false;
+}
+
+static gboolean
+motion_notify_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+  PGTK_TRACE ("motion_notify_event");
+  union buffered_input_event inev;
+  struct frame *f, *frame;
+  struct pgtk_display_info *dpyinfo;
+  Mouse_HLInfo *hlinfo;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+
+  previous_help_echo_string = help_echo_string;
+  help_echo_string = Qnil;
+
+#ifndef HAVE_GTK4
+  frame = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#else
+  frame = user_data;
+#endif
+  dpyinfo = FRAME_DISPLAY_INFO (frame);
+  f = (gui_mouse_grabbed (dpyinfo)
+         ? dpyinfo->last_mouse_frame
+         : frame);
+  hlinfo = MOUSE_HL_INFO (f);
+
+  if (hlinfo->mouse_face_hidden)
+    {
+      hlinfo->mouse_face_hidden = false;
+      clear_mouse_face (hlinfo);
+    }
+#ifndef HAVE_GTK4
+  if (f && xg_event_is_for_scrollbar (f, event))
+    f = 0;
+#endif
+  if (f)
+    {
+      pgtk_toggle_invisible_pointer (f, false);
+      /* Maybe generate a SELECT_WINDOW_EVENT for
+         `mouse-autoselect-window' but don't let popup menus
+         interfere with this (Bug#1261).  */
+      if (!NILP (Vmouse_autoselect_window)
+          && !MINI_WINDOW_P (XWINDOW (selected_window))
+          && (f == XFRAME (selected_frame) || !NILP (focus_follows_mouse)))
+        {
+          static Lisp_Object last_mouse_window;
+#ifdef HAVE_GTK4
+	  gdouble x, y;
+	  gdk_event_get_position (event, &x, &y);
+#endif
+          Lisp_Object window
+            = window_from_coordinates (f,
+#ifdef HAVE_GTK4
+				       (int) round (x), (int) round (y),
+#else
+				       event->motion.x,
+				       event->motion.y,
+#endif
+				       0, false, false);
+
+          /* A window will be autoselected only when it is not
+             selected now and the last mouse movement event was
+             not in it.  The remainder of the code is a bit vague
+             wrt what a "window" is.  For immediate autoselection,
+             the window is usually the entire window but for GTK
+             where the scroll bars don't count.  For delayed
+             autoselection the window is usually the window's text
+             area including the margins.  */
+          if (WINDOWP (window) && !EQ (window, last_mouse_window)
+              && !EQ (window, selected_window))
+            {
+              inev.ie.kind = SELECT_WINDOW_EVENT;
+              inev.ie.frame_or_window = window;
+            }
+
+          /* Remember the last window where we saw the mouse.  */
+          last_mouse_window = window;
+        }
+
+      if (!note_mouse_movement (f,
+#ifdef HAVE_GTK4
+				event
+#else
+				&event->motion
+#endif
+				))
+        help_echo_string = previous_help_echo_string;
+    }
+  else
+    {
+      /* If we move outside the frame, then we're
+         certainly no longer on any text in the frame.  */
+      clear_mouse_face (hlinfo);
+    }
+
+  /* If the contents of the global variable help_echo_string
+     has changed, generate a HELP_EVENT.  */
+  int do_help = 0;
+  if (!NILP (help_echo_string) || !NILP (previous_help_echo_string))
+    do_help = 1;
+
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+
+  if (do_help > 0)
+    {
+      Lisp_Object frame;
+      union buffered_input_event inev;
+
+      if (f)
+        XSETFRAME (frame, f);
+      else
+        frame = Qnil;
+
+      inev.ie.kind = HELP_EVENT;
+      inev.ie.frame_or_window = frame;
+      inev.ie.arg = help_echo_object;
+      inev.ie.x = help_echo_window;
+      inev.ie.y = help_echo_string;
+      inev.ie.timestamp = help_echo_pos;
+      evq_enqueue (&inev);
+    }
+
+  return TRUE;
+}
+
+/* Mouse clicks and mouse movement.  Rah.
+
+   Formerly, we used PointerMotionHintMask (in standard_event_mask)
+   so that we would have to call XQueryPointer after each MotionNotify
+   event to ask for another such event.  However, this made mouse tracking
+   slow, and there was a bug that made it eventually stop.
+
+   Simply asking for MotionNotify all the time seems to work better.
+
+   In order to avoid asking for motion events and then throwing most
+   of them away or busy-polling the server for mouse positions, we ask
+   the server for pointer motion hints.  This means that we get only
+   one event per group of mouse movements.  "Groups" are delimited by
+   other kinds of events (focus changes and button clicks, for
+   example), or by XQueryPointer calls; when one of these happens, we
+   get another MotionNotify event the next time the mouse moves.  This
+   is at least as efficient as getting motion events when mouse
+   tracking is on, and I suspect only negligibly worse when tracking
+   is off.  */
+
+/* Prepare a mouse-event in *RESULT for placement in the input queue.
+
+   If the event is a button press, then note that we have grabbed
+   the mouse.  */
+
+static Lisp_Object
+construct_mouse_click (struct input_event *result,
+#ifdef HAVE_GTK4
+		       GdkEvent *event,
+#else
+		       GdkEventButton *event,
+#endif
+                       struct frame *f)
+{
+  result->kind = MOUSE_CLICK_EVENT;
+#ifdef HAVE_GTK4
+  guint btn = gdk_button_event_get_button (event);
+#endif
+  result->code =
+#ifndef HAVE_GTK4
+    event->button - 1
+#else
+    btn - 1
+#endif
+    ;
+#ifdef HAVE_GTK4
+  GdkModifierType type;
+  gdouble x, y;
+
+  type = gdk_event_get_modifier_state (event);
+  gdk_event_get_position (event, &x, &y);
+  x -= calculate_child_frame_distance_x (f);
+  y -= calculate_child_frame_distance_y (f);
+#endif
+#ifdef HAVE_GTK4
+  result->timestamp = gdk_event_get_time (event);
+#else
+  result->timestamp = event->time;
+#endif
+#ifdef HAVE_GTK4
+  if (!FRAME_X_OUTPUT (f)->ts_up_flag)
+#endif
+    result->modifiers
+      = (pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f),
+#ifdef HAVE_GTK4
+				      type
+#else
+				      event->state
+#endif
+				      )
+	 | (gdk_event_get_event_type
+	    ((GdkEvent *) event) == GDK_BUTTON_RELEASE ? up_modifier : down_modifier));
+#ifdef HAVE_GTK4
+  else
+    result->modifiers = down_modifier;
+#endif
+  XSETINT (result->x,
+#ifdef HAVE_GTK4
+	   (int) x
+#else
+	   (int) round (event->x)
+#endif
+	   );
+  XSETINT (result->y,
+#ifdef HAVE_GTK4
+	   (int) y
+#else
+	   (int) round (event->y)
+#endif
+	   );
+  XSETFRAME (result->frame_or_window, f);
+  result->arg = Qnil;
+  return Qnil;
+}
+
+#ifdef HAVE_GTK4
+static gboolean
+gtk_simulate_touchscreen (void)
+{
+  static gint test_touchscreen;
+
+  if (test_touchscreen == 0)
+    test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN") != NULL ? 1 : -1;
+
+  return test_touchscreen > 0 || (gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0;
+ }
+#endif
+
+static gboolean
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+button_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data)
+#else
+button_event (GtkWidget *widget, GdkEvent *event, struct frame *user_data)
+#endif
+{
+  union buffered_input_event inev;
+  struct frame *f, *frame;
+  struct pgtk_display_info *dpyinfo;
+
+  /* If we decide we want to generate an event to be seen
+     by the rest of Emacs, we put it here.  */
+  bool tab_bar_p = false;
+  bool tool_bar_p = false;
+#ifdef HAVE_GTK4
+  bool bz_flag = false;
+#endif
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+  focus_in_event (widget, NULL, user_data);
+
+  /* ignore double click and triple click. */
+  if (gdk_event_get_event_type (event) != GDK_BUTTON_PRESS &&
+      gdk_event_get_event_type (event) != GDK_BUTTON_RELEASE)
+    return TRUE;
+#ifdef HAVE_GTK4
+  gboolean test_touchscreen = 0;
+  if (gdk_device_get_source (gdk_event_get_device (event)) != GDK_SOURCE_TOUCHSCREEN)
+    test_touchscreen = gtk_simulate_touchscreen ();
+
+  if ((gdk_device_get_source (gdk_event_get_device (event)) == GDK_SOURCE_TOUCHSCREEN ||
+       test_touchscreen) &&
+      gdk_event_get_event_type (event) == GDK_BUTTON_PRESS)
+    return false;
+  else if (gdk_device_get_source (gdk_event_get_device (event)) == GDK_SOURCE_TOUCHSCREEN ||
+	   test_touchscreen)
+    bz_flag = !FRAME_GTK_EV_HANDLER (user_data)->se_blocked;
+  FRAME_X_OUTPUT (user_data)->ts_up_flag = bz_flag;
+#endif
+#ifndef HAVE_GTK4
+  frame = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#else
+  frame = user_data;
+#endif
+  dpyinfo = FRAME_DISPLAY_INFO (frame);
+
+  dpyinfo->last_mouse_glyph_frame = NULL;
+  if (gui_mouse_grabbed (dpyinfo))
+    f = dpyinfo->last_mouse_frame;
+  else
+    {
+      f = frame;
+
+      if (f && gdk_event_get_event_type (event) == GDK_BUTTON_PRESS
+          && !FRAME_NO_ACCEPT_FOCUS (f))
+        {
+          pgtk_focus_frame (f, false);
+        }
+    }
+
+#ifndef HAVE_GTK4
+  if (f && xg_event_is_for_scrollbar (f, event))
+    f = 0;
+#endif
+
+  if (f)
+    {
+      /* Is this in the tab-bar?  */
+      if (WINDOWP (f->tab_bar_window)
+          && WINDOW_TOTAL_LINES (XWINDOW (f->tab_bar_window)))
+        {
+          Lisp_Object window;
+#ifndef HAVE_GTK4
+          int x = event->button.x;
+          int y = event->button.y;
+#else
+	  gdouble zx, zy;
+	  int x, y;
+	  gdk_event_get_position (event, &zx, &zy);
+	  x = (int) round (zx);
+	  y = (int) round (zy);
+	  x -= calculate_child_frame_distance_x (f);
+#endif
+
+          window = window_from_coordinates (f, x, y, 0, true, true);
+          tab_bar_p = EQ (window, f->tab_bar_window);
+#ifdef HAVE_GTK4
+	  guint btn;
+	  GdkModifierType mod;
+	  btn = gdk_button_event_get_button (event);
+	  mod = gdk_event_get_modifier_state (event);
+#endif
+          if (tab_bar_p &&
+#ifndef HAVE_GTK4
+	      event->button.button
+#else
+	      btn
+#endif
+	      < 4)
+            handle_tab_bar_click (f, x, y, gdk_event_get_event_type (event) == GDK_BUTTON_PRESS,
+                                  pgtk_gtk_to_emacs_modifiers (dpyinfo,
+#ifdef HAVE_GTK4
+							       mod
+#else
+                                                               event->button
+                                                                 .state
+#endif
+							       ));
+        }
+    }
+
+  if (f)
+    {
+      if (!tab_bar_p && !tool_bar_p)
+        {
+          if (ignore_next_mouse_click_timeout)
+            {
+              if (gdk_event_get_event_type (event) == GDK_BUTTON_PRESS
+                  &&
+#ifndef HAVE_GTK4
+		  event->button.time
+#else
+		  gdk_event_get_time (event)
+#endif
+		  > ignore_next_mouse_click_timeout)
+                {
+                  ignore_next_mouse_click_timeout = 0;
+                  construct_mouse_click (&inev.ie,
+#ifdef HAVE_GTK4
+					 event
+#else
+					 &event->motion
+#endif
+					 ,
+					 f);
+                }
+              if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE)
+                ignore_next_mouse_click_timeout = 0;
+            }
+          else
+	    {
+#ifdef HAVE_GTK4
+	      if (bz_flag)
+		{
+		  union buffered_input_event ev;
+		  EVENT_INIT (ev.ie);
+		  construct_mouse_click (&ev.ie, event, f);
+		  evq_enqueue (&ev);
+		  FRAME_X_OUTPUT (f)->ts_up_flag = false;
+		  construct_mouse_click (&inev.ie, event, f);
+		}
+	      else
+#endif
+		construct_mouse_click (&inev.ie, event, f);
+	    }
+        }
+    }
+#ifdef HAVE_GTK4
+  if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE)
+    emacs_gtk_event_handler_toggle_scroll_evs (FRAME_GTK_EV_HANDLER (f), true);
+#endif
+#ifdef HAVE_GTK4
+  guint btn;
+  btn = gdk_button_event_get_button (event);
+#endif
+
+  if (gdk_event_get_event_type (event) == GDK_BUTTON_PRESS)
+    {
+      if (f || frame)
+        {
+          dpyinfo->grabbed |= (1 <<
+#ifndef HAVE_GTK4
+			       event->button.button
+#else
+			       btn
+#endif
+			       );
+          dpyinfo->last_mouse_frame = f ? f : frame;
+          pgtk_focus_frame (f ? f : frame, 0);
+        }
+    }
+  else
+    dpyinfo->grabbed &= ~(1 <<
+#ifndef HAVE_GTK4
+			       event->button.button
+#else
+			       btn
+#endif
+			  );
+
+  /* Ignore any mouse motion that happened before this event;
+     any subsequent mouse-movement Emacs events should reflect
+     only motion after the ButtonPress/Release.  */
+  if (f != 0)
+    f->mouse_moved = false;
+
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  return TRUE;
+}
+
+static gboolean
+#ifdef HAVE_GTK4
+scroll_event (GtkEventControllerScroll *widget,
+	      gdouble dx,
+	      gdouble dy,
+	      struct frame *_)
+#elif defined (HAVE_GTK_EVENT_CONTROLLER)
+scroll_event (GtkWidget *widget, GdkEvent *event, struct frame *_)
+#else
+scroll_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data)
+#endif
+{
+  union buffered_input_event inev;
+  struct frame *f, *frame;
+  struct pgtk_display_info *dpyinfo;
+#ifndef HAVE_GTK4
+  if (!event->scroll.time || !event->scroll.x || !event->scroll.y
+      || event->type != GDK_SCROLL)
+    return false;
+#endif
+
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = NO_EVENT;
+  inev.ie.arg = Qnil;
+#ifdef HAVE_GTK4
+  GdkScrollDirection dir;
+  static GdkScrollDirection last_scroll = -1;
+  GdkModifierType mod;
+  gdouble x, y;
+  if (!FRAME_GTK_EV_HANDLER (_)->hq_flag)
+    pgtk_m_px_pos (_, &x, &y);
+  else
+    {
+      x = FRAME_GTK_EV_HANDLER (_)->dsx;
+      y = FRAME_GTK_EV_HANDLER (_)->dsy;
+    }
+  GList *seats = gdk_display_list_seats (GDK_DISPLAY (FRAME_X_DISPLAY (_)));
+  mod = 0;
+  for (GList *seat = seats; seat; seat = seat->next)
+    mod |= gdk_device_get_modifier_state (gdk_seat_get_keyboard (seat->data));
+  g_list_free (seats);
+  dir = fabs (dx) > fabs (dy) ? (dx < 0 ? GDK_SCROLL_LEFT : GDK_SCROLL_RIGHT)
+    : (dy < 0 ? GDK_SCROLL_UP : GDK_SCROLL_DOWN);
+#endif
+#ifndef HAVE_GTK4
+  frame = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+#else
+  frame = _;
+#endif
+  dpyinfo = FRAME_DISPLAY_INFO (frame);
+
+  if (gui_mouse_grabbed (dpyinfo))
+    f = dpyinfo->last_mouse_frame;
+  else
+    f = frame;
+#ifndef HAVE_GTK4
+  inev.ie.kind = WHEEL_EVENT;
+#endif
+  inev.ie.timestamp =
+#ifndef HAVE_GTK4
+    event->scroll.time
+#else
+    time (NULL)
+#endif
+    ;
+  inev.ie.modifiers
+    = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f),
+#ifndef HAVE_GTK4
+				   event->scroll.state
+#else
+				   mod
+#endif
+				   );
+  XSETINT (inev.ie.x,
+#ifndef HAVE_GTK4
+	   event->scroll.x
+#else
+	   (int) x
+#endif
+	   );
+  XSETINT (inev.ie.y,
+#ifndef HAVE_GTK4
+	   event->scroll.y
+#else
+	   (int) y
+#endif
+	   );
+  XSETFRAME (inev.ie.frame_or_window, f);
+  switch (
+#ifndef HAVE_GTK4
+	  event->scroll.direction
+#else
+	  dir
+#endif
+	  )
+    {
+    case GDK_SCROLL_UP:
+      inev.ie.kind = WHEEL_EVENT;
+      inev.ie.modifiers |= up_modifier;
+      break;
+    case GDK_SCROLL_DOWN:
+      inev.ie.kind = WHEEL_EVENT;
+      inev.ie.modifiers |= down_modifier;
+      break;
+    case GDK_SCROLL_LEFT:
+      inev.ie.kind = HORIZ_WHEEL_EVENT;
+      inev.ie.modifiers |= up_modifier;
+      break;
+    case GDK_SCROLL_RIGHT:
+      inev.ie.kind = HORIZ_WHEEL_EVENT;
+      inev.ie.modifiers |= down_modifier;
+      break;
+#ifdef HAVE_GTK3
+    case GDK_SCROLL_SMOOTH:
+#ifdef HAVE_GTK4
+      {
+	if (NILP (window_from_coordinates (f, (int) x, (int) y,
+					   0, false, false)))
+	  return FALSE;
+	struct window *window =
+	  XWINDOW (window_from_coordinates (f, (int) x, (int) y,
+					    0, false, false));
+	struct glyph_row *mr = MATRIX_FIRST_TEXT_ROW (window->current_matrix);
+	int lheight =
+	  !NILP (Vpgtk_use_pixel_scroll) ? 1 : mr ? mr->phys_height : FRAME_LINE_HEIGHT (f);
+	if (mod)
+	  lheight = lheight * 300;
+	int colheight = mr ? mr->glyphs[TEXT_AREA]->pixel_width : FRAME_COLUMN_WIDTH (f);
+	static int colscroll = 0;
+	static float htotal = 0.0;
+	static time_t hs_timeout = 0;
+	static int linscroll = 0;
+	static float total = 0.0;
+	static int itl = 0;
+	static time_t s_timeout = 0;
+#define DY dy * XFIXNUM (Vpgtk_scroll_strength)
+#define DX dx * XFIXNUM (Vpgtk_scroll_strength)
+#else
+#define DY event->scroll.delta_y
+#define DX event->scroll.delta_x
+#endif
+#ifndef HAVE_GTK4
+	if (DY >= 0.2)
+	  {
+	    inev.ie.kind = WHEEL_EVENT;
+	    inev.ie.modifiers |= down_modifier;
+	    inev.ie.arg = make_fixnum (1);
+	  }
+	else if (DY <= -0.2)
+	  {
+	    inev.ie.kind = WHEEL_EVENT;
+	    inev.ie.modifiers |= up_modifier;
+	    inev.ie.arg = make_fixnum (1);
+	  }
+	else if (DX >= 0.2)
+	  {
+	    inev.ie.kind = HORIZ_WHEEL_EVENT;
+	    inev.ie.modifiers |= down_modifier;
+	    inev.ie.arg = make_fixnum (1);
+	  }
+	else if (DX <= -0.2)
+	  {
+	    inev.ie.kind = HORIZ_WHEEL_EVENT;
+	    inev.ie.modifiers |= up_modifier;
+	    inev.ie.arg = make_fixnum (1);
+	  }
+#else
+	if (time (NULL) > s_timeout || last_scroll != dir)
+	  {
+	    total = 0.0f;
+	    linscroll = 0;
+	    itl = 0;
+	  }
+	if (time (NULL) > hs_timeout || last_scroll != dir)
+	  {
+	    htotal = 0.0f;
+	    colscroll = 0;
+	    itl = 0;
+	  }
+        if (fabs (DY) > fabs (DX))
+          {
+	    FRAME_GTK_EV_HANDLER (f)->s_flag = 1;
+	    bool up = DY < 0;
+	    total += (float) fabs (DY);
+	    s_timeout = time (NULL) + 500;
+	    if (total >= lheight)
+	      {
+		++itl;
+                linscroll += round (total / (float) lheight);
+              }
+
+	    if (linscroll > 0)
+	      {
+		if (NILP (Vpgtk_use_pixel_scroll))
+		  {
+                    inev.ie.kind = WHEEL_EVENT;
+                    inev.ie.modifiers |= up ? up_modifier : down_modifier;
+                    inev.ie.code = 0;
+                    inev.ie.arg = make_fixnum (linscroll);
+                    total = (int) total % lheight;
+                    linscroll = 0;
+                  }
+		else
+		  {
+		    struct window *window = XWINDOW (window_from_coordinates (f,
+									      (int) x,
+									      (int) y,
+									      0, 0, 0));
+		    pgtk_vpixoffset_scroll (up ? -linscroll : linscroll, window);
+		    linscroll = 0;
+		  }
+	      }
+	  }
+	else
+	  {
+	    FRAME_GTK_EV_HANDLER (f)->s_flag = 1;
+	    bool up = DX < 0;
+	    htotal += (float) fabs (DX);
+	    hs_timeout = time (NULL) + 500;
+	    if (total >= lheight)
+	      colscroll += round (total / (float) lheight);
+
+	    if (colscroll > 0)
+	      {
+		inev.ie.kind = HORIZ_WHEEL_EVENT;
+		inev.ie.modifiers |= up ? up_modifier : down_modifier;
+		inev.ie.code = 0;
+		inev.ie.arg = make_fixnum (colscroll);
+		total = (int) total % colheight;
+		colscroll = 0;
+	      }
+	  }
+#endif
+#undef DY
+#undef DX
+#ifndef HAVE_GTK4
+	else
+	  return TRUE;
+#endif
+	break;
+#ifdef HAVE_GTK4
+      }
+#endif
+#endif
+    default:
+      return TRUE;
+    }
+#ifdef HAVE_GTK4
+  last_scroll = dir;
+#endif
+
+  if (inev.ie.kind != NO_EVENT)
+    evq_enqueue (&inev);
+  return TRUE;
+}
+
+#ifndef HAVE_GTK4
+static gboolean
+drag_drop (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
+           guint time_, gpointer user_data)
+{
+  GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
+
+  if (target == GDK_NONE)
+    {
+      gtk_drag_finish (context, TRUE, FALSE, time_);
+      return FALSE;
+    }
+
+  gtk_drag_get_data (widget, context, target, time_);
+
+  return TRUE;
+}
+
+static void
+drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
+                    GtkSelectionData *data, guint info, guint time_,
+                    gpointer user_data)
+{
+  struct frame *f = pgtk_any_window_to_frame (gtk_widget_get_window (widget));
+  gchar **uris = gtk_selection_data_get_uris (data);
+
+  if (uris != NULL)
+    {
+      for (int i = 0; uris[i] != NULL; i++)
+        {
+          union buffered_input_event inev;
+          Lisp_Object arg = Qnil;
+
+          PGTK_TRACE ("drag_data_received: uri: %s", uris[i]);
+
+          EVENT_INIT (inev.ie);
+          inev.ie.kind = NO_EVENT;
+          inev.ie.arg = Qnil;
+
+          arg = list2 (Qurl, build_string (uris[i]));
+
+          inev.ie.kind = DRAG_N_DROP_EVENT;
+          inev.ie.modifiers = 0;
+          XSETINT (inev.ie.x, x);
+          XSETINT (inev.ie.y, y);
+          XSETFRAME (inev.ie.frame_or_window, f);
+          inev.ie.arg = arg;
+          inev.ie.timestamp = 0;
+
+          evq_enqueue (&inev);
+        }
+    }
+  PGTK_TRACE ("drag_data_received: that's all.");
+
+  gtk_drag_finish (context, TRUE, FALSE, time_);
+}
+#endif
+void
+pgtk_set_event_handler (struct frame *f)
+{
+#ifndef HAVE_GTK3
+  gtk_widget_set_can_focus (GTK_WIDGET (FRAME_GTK_OUTER_WIDGET (f)), true);
+  gtk_widget_set_can_focus (GTK_WIDGET (FRAME_GTK_WIDGET (f)), true);
+#endif
+#ifndef HAVE_GTK4
+  gtk_drag_dest_set (FRAME_GTK_WIDGET (f), GTK_DEST_DEFAULT_ALL, NULL, 0,
+                     GDK_ACTION_COPY);
+  gtk_drag_dest_add_uri_targets (FRAME_GTK_WIDGET (f));
+#endif
+#ifndef HAVE_GTK4
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "event",
+		    G_CALLBACK (pgtk_handle_event), NULL);
+#endif
+
+#ifdef GDK_VERSION_3_22
+  g_signal_connect (G_OBJECT (gtk_widget_get_window (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)))),
+		    "moved_to_rect", G_CALLBACK (pgtk_move_event_commit), f);
+#endif
+
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "window-state-event",
+                    G_CALLBACK (window_state_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "delete-event",
+                    G_CALLBACK (delete_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "map-event",
+                    G_CALLBACK (map_event), NULL);
+#else
+  FRAME_GTK_ES_HANDLER (f)->window_state_event = window_state_event;
+#ifndef HAVE_GTK4
+  FRAME_GTK_ES_HANDLER (f)->delete_event = delete_event;
+#else
+  if (!FRAME_PARENT_FRAME (f))
+    g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)),
+		      "close-request",
+		      G_CALLBACK (delete_event), f);
+#endif
+#ifndef HAVE_GTK4
+  FRAME_GTK_ES_HANDLER (f)->map_event = map_event;
+#else
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "map",
+                    G_CALLBACK (map_event), f);
+#endif
+  emacs_gtk_event_handler_set_widget (FRAME_GTK_ES_HANDLER (f),
+				      FRAME_GTK_OUTER_WIDGET (f));
+#endif
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "size-allocate",
+                    G_CALLBACK (size_allocate), f);
+#ifndef HAVE_GTK4
+#ifdef HAVE_GTK3
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "key-press-event",
+                    G_CALLBACK (key_press_event), f);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "key-release-event",
+                    G_CALLBACK (key_release_event), f);
+#else
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "key-press-event",
+                    G_CALLBACK (key_press_event), f);
+  g_signal_connect (G_OBJECT (FRAME_GTK_OUTER_WIDGET (f)), "key-release-event",
+                    G_CALLBACK (key_release_event), f);
+#endif
+#else
+  FRAME_GTK_EV_HANDLER (f)->key_down = key_press_event;
+  FRAME_GTK_EV_HANDLER (f)->key_up = key_release_event;
+#ifndef HAVE_GTK4
+  gtk_widget_add_controller (FRAME_GTK_WIDGET (f),
+			     GTK_EVENT_CONTROLLER (FRAME_GTK_EV_HANDLER (f)));
+#endif
+#endif
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "focus-in-event",
+                    G_CALLBACK (focus_in_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "focus-out-event",
+                    G_CALLBACK (focus_out_event), NULL);
+#else
+#ifndef HAVE_GTK4
+  FRAME_GTK_EV_HANDLER (f)->focus_in = focus_in_event;
+  FRAME_GTK_EV_HANDLER (f)->focus_out = focus_out_event;
+#else
+  if (FRAME_PARENT_FRAME (f))
+    {
+      FRAME_GTK_EV_HANDLER (f)->focus_in = focus_in_event;
+      FRAME_GTK_EV_HANDLER (f)->focus_out = focus_out_event;
+    }
+  else
+    {
+      FRAME_GTK_ES_HANDLER (f)->focus_in = focus_in_event;
+      FRAME_GTK_EV_HANDLER (f)->focus_in = focus_in_event;
+      FRAME_GTK_ES_HANDLER (f)->focus_out = focus_out_event;
+    }
+#endif
+#endif
+#ifndef HAVE_GTK4
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "enter-notify-event",
+                    G_CALLBACK (enter_notify_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "leave-notify-event",
+                    G_CALLBACK (leave_notify_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "motion-notify-event",
+                    G_CALLBACK (motion_notify_event), NULL);
+#else
+  FRAME_GTK_EV_HANDLER (f)->enter_notify = enter_notify_event;
+  FRAME_GTK_EV_HANDLER (f)->leave_notify = leave_notify_event;
+  FRAME_GTK_EV_HANDLER (f)->motion_notify = motion_notify_event;
+  FRAME_GTK_EV_HANDLER (f)->leave = leave;
+  FRAME_GTK_EV_HANDLER (f)->enter = enter;
+#endif
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "button-press-event",
+                    G_CALLBACK (button_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "button-release-event",
+                    G_CALLBACK (button_event), NULL);
+#else
+  FRAME_GTK_EV_HANDLER (f)->button_down = button_event;
+  FRAME_GTK_EV_HANDLER (f)->button_up = button_event;
+  emacs_gtk_event_handler_set_widget (FRAME_GTK_EV_HANDLER (f),
+				      FRAME_GTK_WIDGET (f));
+#endif
+#ifndef HAVE_GTK_EVENT_CONTROLLER
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "scroll-event",
+                    G_CALLBACK (scroll_event), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event",
+                    G_CALLBACK (pgtk_selection_lost), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "configure-event",
+                    G_CALLBACK (configure_event), NULL);
+#else
+  FRAME_GTK_EV_HANDLER (f)->configure = configure_event;
+#ifndef HAVE_GTK4
+  FRAME_GTK_EV_HANDLER (f)->selection_lost = pgtk_selection_lost;
+#endif
+  FRAME_GTK_EV_HANDLER (f)->scroll_event = scroll_event;
+#endif
+#ifdef HAVE_GTK4
+  FRAME_GTK_EV_HANDLER (f)->long_press_event = long_press_event;
+#endif
+#ifndef HAVE_GTK4
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-drop",
+                    G_CALLBACK (drag_drop), NULL);
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-data-received",
+                    G_CALLBACK (drag_data_received), NULL);
+#endif
+#ifdef HAVE_GTK3
+#ifndef HAVE_GTK4
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "draw",
+                    G_CALLBACK (pgtk_handle_draw), NULL);
+#endif
+#else
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "expose-event",
+		    G_CALLBACK (pgtk_expose), NULL);
+#endif
+#ifndef HAVE_GTK4
+  g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "event",
+                    G_CALLBACK (pgtk_handle_event), NULL);
+#endif
+}
+
+
+/* Test whether two display-name strings agree up to the dot that separates
+   the screen number from the server number.  */
+static bool
+same_x_server (const char *name1, const char *name2)
+{
+  bool seen_colon = false;
+  Lisp_Object sysname = Fsystem_name ();
+  const char *system_name = SSDATA (sysname);
+  ptrdiff_t system_name_length = SBYTES (sysname);
+  ptrdiff_t length_until_period = 0;
+
+  while (system_name[length_until_period] != 0
+         && system_name[length_until_period] != '.')
+    length_until_period++;
+
+  /* Treat `unix' like an empty host name.  */
+  if (!strncmp (name1, "unix:", 5))
+    name1 += 4;
+  if (!strncmp (name2, "unix:", 5))
+    name2 += 4;
+  /* Treat this host's name like an empty host name.  */
+  if (!strncmp (name1, system_name, system_name_length)
+      && name1[system_name_length] == ':')
+    name1 += system_name_length;
+  if (!strncmp (name2, system_name, system_name_length)
+      && name2[system_name_length] == ':')
+    name2 += system_name_length;
+  /* Treat this host's domainless name like an empty host name.  */
+  if (!strncmp (name1, system_name, length_until_period)
+      && name1[length_until_period] == ':')
+    name1 += length_until_period;
+  if (!strncmp (name2, system_name, length_until_period)
+      && name2[length_until_period] == ':')
+    name2 += length_until_period;
+
+  for (; *name1 != '\0' && *name1 == *name2; name1++, name2++)
+    {
+      if (*name1 == ':')
+        seen_colon = true;
+      if (seen_colon && *name1 == '.')
+        return true;
+    }
+  return (seen_colon && (*name1 == '.' || *name1 == '\0')
+          && (*name2 == '.' || *name2 == '\0'));
+}
+
+#ifndef HAVE_GTK4
+static void pgtk_screen_size_changed (GdkScreen *screen,
+				      struct pgtk_display_info *info)
+{
+  block_input ();
+  info->resx = gdk_screen_get_resolution (screen);
+  info->resy = gdk_screen_get_resolution (screen);
+  Lisp_Object tail, frame;
+  FOR_EACH_FRAME (tail, frame)
+    {
+      struct frame *f = XFRAME (frame);
+      if (FRAME_PGTK_P (f) &&
+	  FRAME_X_OUTPUT (f) &&
+	  FRAME_X_OUTPUT (f)->display_info == info)
+	{
+	  f->redisplay = true;
+	  f->resized_p = true;
+	}
+    }
+  unblock_input ();
+  redisplay ();
+}
+#else
+static void
+pgtk_display_changed (GdkDisplay *dpy,
+		      gpointer foo,
+		      struct pgtk_display_info *info)
+  {
+    block_input ();
+    info->resx = egtk_get_monitor_hdpi
+      (gdk_display_get_monitor (info->gdpy, 0));
+    info->resy = egtk_get_monitor_wdpi
+      (gdk_display_get_monitor (info->gdpy, 0));
+    if (info->resx < 0)
+      info->resx = 170.112;
+    if (info->resy < 0)
+      info->resy = 170.112;
+    Lisp_Object tail, frame;
+    FOR_EACH_FRAME (tail, frame)
+    {
+      struct frame *f = XFRAME (frame);
+      if (FRAME_PGTK_P (f) && FRAME_X_OUTPUT (f)
+          && FRAME_X_OUTPUT (f)->display_info == info)
+        {
+          f->redisplay = true;
+          f->resized_p = true;
+        }
+    }
+    unblock_input ();
+    redisplay ();
+  }
+#endif
+
+/* Open a connection to X display DISPLAY_NAME, and return
+   the structure that describes the open display.
+   If we cannot contact the display, return null.  */
+
+struct pgtk_display_info *
+pgtk_term_init (Lisp_Object display_name, char *resource_name)
+{
+  GdkDisplay *dpy;
+  struct terminal *terminal;
+  struct pgtk_display_info *dpyinfo;
+  static int x_initialized = 0;
+  static unsigned x_display_id = 0;
+  static char *initial_display = NULL;
+  char *dpy_name;
+  Lisp_Object lisp_dpy_name = Qnil;
+
+  block_input ();
+
+  if (!x_initialized)
+    {
+      Fset_input_interrupt_mode (Qt);
+      baud_rate = 19200;
+      gui_init_fringe (&pgtk_redisplay_interface);
+
+      ++x_initialized;
+    }
+
+  dpy_name = SSDATA (display_name);
+  if (strlen (dpy_name) == 0 && initial_display != NULL)
+    dpy_name = initial_display;
+  lisp_dpy_name = build_string (dpy_name);
+
+  {
+    if (x_initialized++ > 1)
+      {
+#ifndef HAVE_GTK4
+        xg_display_open (dpy_name, &dpy);
+#else
+	egtk_display_open (dpy_name, &dpy);
+#endif
+      }
+    else
+      {
+        /* gtk_init does set_locale.  Fix locale before and after.  */
+        fixup_locale ();
+#ifndef HAVE_GTK3
+        unrequest_sigio (); /* See comment in x_display_ok.  */
+#endif
+        gtk_init (
+#ifndef HAVE_GTK4
+		  0, 0
+#endif
+		  );
+#ifdef HAVE_GTK4
+        GtkWidget *btn = gtk_button_new ();
+        GtkStyleContext *gsc = gtk_widget_get_style_context (btn);
+        btn_style_context = gsc;
+	GtkWidget *chkbox = gtk_check_button_new ();
+	chk_style_context = gtk_widget_get_style_context (chkbox);
+	GtkWidget *entry = gtk_entry_new ();
+	ent_style_context = gtk_widget_get_style_context (entry);
+#endif
+        request_sigio ();
+        fixup_locale ();
+
+#ifndef HAVE_GTK4
+        xg_initialize ();
+#else
+	egtk_initialize ();
+#endif
+
+        dpy = DEFAULT_GDK_DISPLAY ();
+
+        initial_display = g_strdup (gdk_display_get_name (dpy));
+        dpy_name = initial_display;
+        lisp_dpy_name = build_string (dpy_name);
+      }
+  }
+
+  /* Detect failure.  */
+  if (dpy == 0)
+    {
+      unblock_input ();
+      return 0;
+    }
+
+  /* We have definitely succeeded.  Record the new connection.  */
+  dpyinfo = xzalloc (sizeof *dpyinfo);
+  pgtk_initialize_display_info (dpyinfo);
+  terminal = pgtk_create_terminal (dpyinfo);
+
+  {
+    struct pgtk_display_info *share;
+
+    for (share = x_display_list; share; share = share->next)
+      if (same_x_server (SSDATA (XCAR (share->name_list_element)), dpy_name))
+        break;
+    if (share)
+      terminal->kboard = share->terminal->kboard;
+    else
+      {
+        terminal->kboard = allocate_kboard (Qpgtk);
+
+        /* Don't let the initial kboard remain current longer than necessary.
+           That would cause problems if a file loaded on startup tries to
+           prompt in the mini-buffer.  */
+        if (current_kboard == initial_kboard)
+          current_kboard = terminal->kboard;
+      }
+    terminal->kboard->reference_count++;
+  }
+
+  /* Put this display on the chain.  */
+  dpyinfo->next = x_display_list;
+  x_display_list = dpyinfo;
+
+  dpyinfo->name_list_element = Fcons (lisp_dpy_name, Qnil);
+  dpyinfo->gdpy = dpy;
+
+  dpyinfo->connection = conn_get_wait_descriptor (dpyinfo);
+
+  /* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html  */
+  dpyinfo->smallest_font_height = 1;
+  dpyinfo->smallest_char_width = 1;
+
+  /* Set the name of the terminal. */
+  terminal->name = xlispstrdup (lisp_dpy_name);
+
+  Lisp_Object system_name = Fsystem_name ();
+  ptrdiff_t nbytes;
+  if (INT_ADD_WRAPV (SBYTES (Vinvocation_name), SBYTES (system_name) + 2,
+                     &nbytes))
+    memory_full (SIZE_MAX);
+  dpyinfo->x_id = ++x_display_id;
+  dpyinfo->x_id_name = xmalloc (nbytes);
+  char *nametail = lispstpcpy (dpyinfo->x_id_name, Vinvocation_name);
+  *nametail++ = '@';
+  lispstpcpy (nametail, system_name);
+
+  /* Figure out which modifier bits mean what.  */
+  x_find_modifier_meanings (dpyinfo);
+
+  /* Get the scroll bar cursor.  */
+  /* We must create a GTK cursor, it is required for GTK widgets.  */
+  dpyinfo->xg_cursor =
+#ifndef HAVE_GTK4
+    xg_create_default_cursor (dpyinfo->gdpy);
+#else
+  egtk_create_default_cursor (dpyinfo->gdpy);
+#endif
+
+#ifdef HAVE_GTK4
+  dpyinfo->vertical_scroll_bar_cursor
+    = gdk_cursor_new_from_name ("default", NULL);
+
+  dpyinfo->horizontal_scroll_bar_cursor
+    = gdk_cursor_new_from_name ("default", NULL);
+#else
+  dpyinfo->vertical_scroll_bar_cursor
+    = gdk_cursor_new_for_display (dpyinfo->gdpy, GDK_SB_V_DOUBLE_ARROW);
+
+  dpyinfo->horizontal_scroll_bar_cursor
+    = gdk_cursor_new_for_display (dpyinfo->gdpy, GDK_SB_H_DOUBLE_ARROW);
+#endif
+  reset_mouse_highlight (&dpyinfo->mouse_highlight);
+
+  {
+#ifndef HAVE_GTK4
+    GdkScreen *gscr = gdk_display_get_default_screen (dpyinfo->gdpy);
+    gdouble dpi = gdk_screen_get_resolution (gscr);
+    g_signal_connect (G_OBJECT (gscr), "size-changed", pgtk_screen_size_changed, dpyinfo);
+    if (dpi < 1)
+      {
+        dpi = 170.112;
+        gdk_screen_set_resolution (gscr, dpi);
+      }
+    dpyinfo->resx = dpi;
+    dpyinfo->resy = dpi;
+#else
+    dpyinfo->resx = egtk_get_monitor_hdpi (gdk_display_get_monitor (dpyinfo->gdpy, 0));
+    dpyinfo->resy = egtk_get_monitor_wdpi (gdk_display_get_monitor (dpyinfo->gdpy, 0));
+    if (dpyinfo->resx < 0)
+      dpyinfo->resx = 170.112;
+    if (dpyinfo->resy < 0)
+      dpyinfo->resy = 170.112;
+    g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-added",
+		      G_CALLBACK (pgtk_display_changed), dpyinfo);
+    g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-removed",
+		      G_CALLBACK (pgtk_display_changed), dpyinfo);
+    g_signal_connect (G_OBJECT (dpyinfo->gdpy), "setting-changed",
+		      G_CALLBACK (pgtk_display_changed), dpyinfo);
+#endif
+  }
+
+  x_setup_pointer_blanking (dpyinfo);
+
+  xsettings_initialize (dpyinfo);
+
+  add_keyboard_wait_descriptor (dpyinfo->connection);
+#ifdef F_SETOWN
+  fcntl (dpyinfo->connection, F_SETOWN, getpid ());
+#endif /* ! defined (F_SETOWN) */
+  if (interrupt_input)
+    init_sigio (dpyinfo->connection);
+  pgtk_selection_init ();
+
+  pgtk_im_init (dpyinfo);
+
+  unblock_input ();
+
+  return dpyinfo;
+}
+
+/* Get rid of display DPYINFO, deleting all frames on it,
+   and without sending any more commands to the X server.  */
+
+static void
+pgtk_delete_display (struct pgtk_display_info *dpyinfo)
+{
+  struct terminal *t;
+
+  /* Close all frames and delete the generic struct terminal for this
+     X display.  */
+  for (t = terminal_list; t; t = t->next_terminal)
+    if (t->type == output_pgtk && t->display_info.pgtk == dpyinfo)
+      {
+        delete_terminal (t);
+        break;
+      }
+
+  if (x_display_list == dpyinfo)
+    x_display_list = dpyinfo->next;
+  else
+    {
+      struct pgtk_display_info *tail;
+
+      for (tail = x_display_list; tail; tail = tail->next)
+        if (tail->next == dpyinfo)
+          tail->next = tail->next->next;
+    }
+
+  xfree (dpyinfo);
+}
+
+char *
+pgtk_xlfd_to_fontname (const char *xlfd)
+/* --------------------------------------------------------------------------
+    Convert an X font name (XLFD) to an Gtk font name.
+    Only family is used.
+    The string returned is temporarily allocated.
+   -------------------------------------------------------------------------- */
+{
+  PGTK_TRACE ("pgtk_xlfd_to_fontname");
+  char *name = xmalloc (180);
+
+  if (!strncmp (xlfd, "--", 2))
+    {
+      if (sscanf (xlfd, "--%179[^-]-", name) != 1)
+        name[0] = '\0';
+    }
+  else
+    {
+      if (sscanf (xlfd, "-%*[^-]-%179[^-]-", name) != 1)
+        name[0] = '\0';
+    }
+
+  /* stopgap for malformed XLFD input */
+  if (strlen (name) == 0)
+    strcpy (name, "Monospace");
+
+  PGTK_TRACE ("converted '%s' to '%s'", xlfd, name);
+  return name;
+}
+
+bool
+pgtk_defined_color (struct frame *f, const char *name, Emacs_Color *color_def,
+                    bool alloc, bool makeIndex)
+{
+#ifndef HAVE_GTK4
+  if (xg_check_special_colors (f, name, color_def))
+#else
+  if (egtk_check_special_colors (f, name, color_def))
+#endif
+    return true;
+
+  block_input ();
+  bool r = pgtk_parse_color (name, color_def);
+  unblock_input ();
+  return r;
+}
+
+/* On frame F, translate the color name to RGB values.  Use cached
+   information, if possible.
+
+   Note that there is currently no way to clean old entries out of the
+   cache.  However, it is limited to names in the server's database,
+   and names we've actually looked up; list-colors-display is probably
+   the most color-intensive case we're likely to hit.  */
+
+int
+pgtk_parse_color (const char *color_name, Emacs_Color *color)
+{
+#ifdef HAVE_GTK3
+  GdkRGBA rgba;
+  if (gdk_rgba_parse (&rgba, color_name))
+    {
+      color->red = rgba.red * 65535;
+      color->green = rgba.green * 65535;
+      color->blue = rgba.blue * 65535;
+      color->pixel = (unsigned long) 0xff << 24 | (color->red >> 8) << 16
+                     | (color->green >> 8) << 8 | (color->blue >> 8) << 0;
+      return 1;
+    }
+#else
+  GdkColor gc;
+  if (gdk_color_parse (color_name, &gc))
+    {
+      color->red = gc.red;
+      color->green = gc.green;
+      color->blue = gc.blue;
+      color->pixel = (unsigned long) 0xff << 24 | (color->red >> 8) << 16
+                     | (color->green >> 8) << 8 | (color->blue >> 8) << 0;
+      return 1;
+    }
+#endif
+  return 0;
+}
+
+int
+pgtk_lisp_to_color (Lisp_Object color, Emacs_Color *col)
+/* --------------------------------------------------------------------------
+     Convert a Lisp string object to a color
+   -------------------------------------------------------------------------- */
+{
+#ifdef HAVE_GTK4
+  return pgtk_lisp_to_color_with_frame (color, col, NULL);
+#else
+  char *chr;
+  if (STRINGP (color))
+    chr = SSDATA (color);
+  else if (SYMBOLP (color))
+    chr = SSDATA (SYMBOL_NAME (color));
+  else
+    return 1;
+  return !pgtk_parse_color (chr, col);
+#endif
+}
+
+int
+pgtk_lisp_to_color_with_frame (Lisp_Object color, Emacs_Color *col, struct frame *f)
+{
+  char *chr;
+  if (STRINGP (color))
+    chr = SSDATA (color);
+  else if (SYMBOLP (color))
+    chr = SSDATA (SYMBOL_NAME (color));
+  else
+    return 1;
+#ifdef HAVE_GTK4
+  if (egtk_check_special_colors (f, chr, col))
+#else
+  if (xg_check_special_colors (f, chr, col))
+#endif
+    return 0;
+  return !pgtk_parse_color (chr, col);
+}
+
+/* On frame F, translate pixel colors to RGB values for the NCOLORS
+   colors in COLORS.  On W32, we no longer try to map colors to
+   a palette.  */
+void
+pgtk_query_colors (struct frame *f, Emacs_Color *colors, int ncolors)
+{
+  int i;
+
+  for (i = 0; i < ncolors; i++)
+    {
+      unsigned long pixel = colors[i].pixel;
+      /* Convert to a 16 bit value in range 0 - 0xffff. */
+#define GetRValue(p) (((p) >> 16) & 0xff)
+#define GetGValue(p) (((p) >> 8) & 0xff)
+#define GetBValue(p) (((p) >> 0) & 0xff)
+      colors[i].red = GetRValue (pixel) * 257;
+      colors[i].green = GetGValue (pixel) * 257;
+      colors[i].blue = GetBValue (pixel) * 257;
+    }
+}
+
+void
+pgtk_query_color (struct frame *f, Emacs_Color *color)
+{
+  pgtk_query_colors (f, color, 1);
+}
+
+void
+pgtk_clear_area (struct frame *f, int x, int y, int width, int height)
+{
+  block_input ();
+  cairo_t *cr;
+
+  eassert (width > 0 && height > 0);
+
+  cr = pgtk_begin_cr_clip (f);
+  pgtk_set_cr_source_with_color (f, FRAME_X_OUTPUT (f)->background_color);
+#ifdef HAVE_GTK4
+  emacs_resizable_drawing_area_set_background ((gpointer) FRAME_GTK_WIDGET (f),
+					       FRAME_X_OUTPUT (f)->background_color);
+#endif
+  cairo_rectangle (cr, x, y, width, height);
+  cairo_fill (cr);
+  pgtk_end_cr_clip (f);
+  unblock_input ();
+}
+
+void
+syms_of_pgtkterm (void)
+{
+  /* from 23+ we need to tell emacs what modifiers there are.. */
+  DEFSYM (Qmodifier_value, "modifier-value");
+  DEFSYM (Qalt, "alt");
+  DEFSYM (Qhyper, "hyper");
+  DEFSYM (Qmeta, "meta");
+  DEFSYM (Qsuper, "super");
+  DEFSYM (Qcontrol, "control");
+  DEFSYM (QUTF8_STRING, "UTF8_STRING");
+
+  DEFSYM (Qfile, "file");
+  DEFSYM (Qurl, "url");
+
+  DEFSYM (Qlatin_1, "latin-1");
+
+  xg_default_icon_file
+    = build_pure_c_string ("icons/hicolor/scalable/apps/emacs.svg");
+  staticpro (&xg_default_icon_file);
+
+  DEFSYM (Qx_gtk_map_stock, "x-gtk-map-stock");
+
+  Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
+  Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
+  Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
+  Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
+  Fput (Qcontrol, Qmodifier_value, make_fixnum (ctrl_modifier));
+
+  DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym,
+    doc: /* Which keys Emacs uses for the ctrl modifier.
+This should be one of the symbols `ctrl', `alt', `hyper', `meta',
+`super'.  For example, `ctrl' means use the Ctrl_L and Ctrl_R keysyms.
+The default is nil, which is the same as `ctrl'.  */);
+  Vx_ctrl_keysym = Qnil;
+
+  DEFVAR_LISP ("pgtk-use-pixel-scroll", Vpgtk_use_pixel_scroll,
+	       doc: /* Mwheel-scroll will scroll the screen a few pixels at a time. */);
+  Vpgtk_use_pixel_scroll = Qnil;
+
+  DEFVAR_LISP ("x-alt-keysym", Vx_alt_keysym,
+    doc: /* Which keys Emacs uses for the alt modifier.
+This should be one of the symbols `ctrl', `alt', `hyper', `meta',
+`super'.  For example, `alt' means use the Alt_L and Alt_R keysyms.
+The default is nil, which is the same as `alt'.  */);
+  Vx_alt_keysym = Qnil;
+
+  DEFVAR_LISP ("x-hyper-keysym", Vx_hyper_keysym,
+    doc: /* Which keys Emacs uses for the hyper modifier.
+This should be one of the symbols `ctrl', `alt', `hyper', `meta',
+`super'.  For example, `hyper' means use the Hyper_L and Hyper_R
+keysyms.  The default is nil, which is the same as `hyper'.  */);
+  Vx_hyper_keysym = Qnil;
+
+  DEFVAR_LISP ("x-meta-keysym", Vx_meta_keysym,
+    doc: /* Which keys Emacs uses for the meta modifier.
+This should be one of the symbols `ctrl', `alt', `hyper', `meta',
+`super'.  For example, `meta' means use the Meta_L and Meta_R keysyms.
+The default is nil, which is the same as `meta'.  */);
+  Vx_meta_keysym = Qnil;
+
+  DEFVAR_LISP ("pgtk-flat-underwave-style", Vpgtk_flat_underwave_style,
+	       doc: /* Use GTK-like flat underwaves. */);
+  Vpgtk_flat_underwave_style = Qnil;
+
+  DEFVAR_LISP ("x-super-keysym", Vx_super_keysym,
+    doc: /* Which keys Emacs uses for the super modifier.
+This should be one of the symbols `ctrl', `alt', `hyper', `meta',
+`super'.  For example, `super' means use the Super_L and Super_R
+keysyms.  The default is nil, which is the same as `super'.  */);
+  Vx_super_keysym = Qnil;
+
+  /* TODO: move to common code */
+  DEFVAR_LISP ("x-toolkit-scroll-bars", Vx_toolkit_scroll_bars,
+	       doc: /* Which toolkit scroll bars Emacs uses, if any.
+A value of nil means Emacs doesn't use toolkit scroll bars.
+With the X Window system, the value is a symbol describing the
+X toolkit.  Possible values are: gtk, motif, xaw, or xaw3d.
+With MS Windows or Nextstep, the value is t.  */);
+  // Vx_toolkit_scroll_bars = Qt;
+  Vx_toolkit_scroll_bars = intern_c_string ("gtk");
+
+  DEFVAR_BOOL ("x-use-underline-position-properties",
+	       x_use_underline_position_properties,
+     doc: /*Non-nil means make use of UNDERLINE_POSITION font properties.
+A value of nil means ignore them.  If you encounter fonts with bogus
+UNDERLINE_POSITION font properties, for example 7x13 on XFree prior
+to 4.1, set this to nil. */);
+  x_use_underline_position_properties = 0;
+
+  DEFVAR_BOOL ("x-underline-at-descent-line",
+	       x_underline_at_descent_line,
+     doc: /* Non-nil means to draw the underline at the same place as the descent line.
+A value of nil means to draw the underline according to the value of the
+variable `x-use-underline-position-properties', which is usually at the
+baseline level.  The default value is nil.  */);
+  x_underline_at_descent_line = 0;
+
+  DEFVAR_BOOL ("x-gtk-use-window-move", x_gtk_use_window_move,
+    doc: /* Non-nil means rely on gtk_window_move to set frame positions.
+If this variable is t (the default), the GTK build uses the function
+gtk_window_move to set or store frame positions and disables some time
+consuming frame position adjustments.  In newer versions of GTK, Emacs
+always uses gtk_window_move and ignores the value of this variable.  */);
+  x_gtk_use_window_move = true;
+
+  DEFSYM (Qx_gtk_map_stock, "x-gtk-map-stock");
+
+#ifdef HAVE_GTK4
+  DEFVAR_LISP ("pgtk-scroll-strength", Vpgtk_scroll_strength,
+       doc: /* The strength to be used for touchpad scroll events. */);
+  XSETINT (Vpgtk_scroll_strength, 1);
+#endif
+
+  DEFVAR_LISP ("pgtk-wait-for-event-timeout", Vpgtk_wait_for_event_timeout,
+    doc: /* How long to wait for X events.
+
+Emacs will wait up to this many seconds to receive X events after
+making changes which affect the state of the graphical interface.
+Under some window managers this can take an indefinite amount of time,
+so it is important to limit the wait.
+
+If set to a non-float value, there will be no wait at all.  */);
+  Vpgtk_wait_for_event_timeout = make_float (0.1);
+#ifdef HAVE_GTK4
+  DEFVAR_LISP ("pgtk-last-event-from-touchscreen", Vpgtk_last_event_from_touchscreen,
+	       doc: /* Non-nil if the last processed event came from a touchscreen device. */);
+    Vpgtk_last_event_from_touchscreen = Qnil;
+#endif
+  DEFVAR_LISP ("pgtk-keysym-table", Vpgtk_keysym_table,
+	       doc: /* Hash table of character codes indexed by X keysym codes.  */);
+  Vpgtk_keysym_table = make_hash_table (hashtest_eql, 900, DEFAULT_REHASH_SIZE,
+                                        DEFAULT_REHASH_THRESHOLD, Qnil, false);
+
+  window_being_scrolled = Qnil;
+  staticpro (&window_being_scrolled);
+
+  /* Tell Emacs about this window system.  */
+  Fprovide (Qpgtk, Qnil);
+}
+
+void
+pgtk_cr_update_surface_desired_size (struct frame *f, int width, int height)
+{
+  block_input ();
+  if (FRAME_CR_SURFACE_DESIRED_WIDTH (f) != width
+      || FRAME_CR_SURFACE_DESIRED_HEIGHT (f) != height)
+    {
+      {
+        cairo_surface_t *old_surface = FRAME_CR_SURFACE (f);
+        cairo_t *cr = NULL;
+        cairo_t *old_cr = FRAME_CR_CONTEXT (f);
+        FRAME_CR_SURFACE (f)
+          = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+        if (old_surface)
+          {
+            cr = cairo_create (FRAME_CR_SURFACE (f));
+            cairo_set_source_surface (cr, old_surface, 0, 0);
+
+            cairo_paint (cr);
+            FRAME_CR_CONTEXT (f) = cr;
+
+            cairo_destroy (old_cr);
+            cairo_surface_destroy (old_surface);
+          }
+      }
+      {
+        cairo_surface_t *old_surface = FRAME_X_OUTPUT (f)->flip_buf;
+        cairo_t *cr = NULL;
+	cairo_t *old_cr = FRAME_X_OUTPUT (f)->cr_flip_context;
+        FRAME_X_OUTPUT (f)->flip_buf
+          = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+        if (old_surface)
+          {
+            cr = cairo_create (FRAME_X_OUTPUT (f)->flip_buf);
+            cairo_set_source_surface (cr, old_surface, 0, 0);
+
+            cairo_paint (cr);
+	    cairo_destroy (old_cr);
+            FRAME_X_OUTPUT (f)->cr_flip_context = cr;
+            cairo_surface_destroy (old_surface);
+          }
+      }
+      gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+      FRAME_CR_SURFACE_DESIRED_WIDTH (f) = width;
+      FRAME_CR_SURFACE_DESIRED_HEIGHT (f) = height;
+    }
+  unblock_input ();
+}
+
+cairo_t *
+pgtk_begin_cr_clip (struct frame *f)
+{
+  block_input ();
+  FRAME_X_OUTPUT (f)->clip_count++;
+  cairo_t *cr = FRAME_CR_CONTEXT (f);
+  cairo_t *fcr = FRAME_X_OUTPUT (f)->cr_flip_context;
+  if (!FRAME_CR_SURFACE (f))
+    {
+      FRAME_CR_SURFACE (f)
+	= cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+				      FRAME_PIXEL_WIDTH (f),
+				      FRAME_PIXEL_HEIGHT (f));
+    }
+
+  if (!FRAME_X_OUTPUT (f)->flip_buf)
+    {
+      FRAME_X_OUTPUT (f)->flip_buf
+	= cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+				      FRAME_PIXEL_WIDTH (f),
+				      FRAME_PIXEL_HEIGHT (f));
+    }
+
+  if (!cr)
+    {
+      cr = cairo_create (FRAME_CR_SURFACE (f));
+      FRAME_CR_CONTEXT (f) = cr;
+    }
+
+  if (!fcr)
+    {
+      fcr = cairo_create (FRAME_X_OUTPUT (f)->flip_buf);
+      FRAME_X_OUTPUT (f)->cr_flip_context = fcr;
+    }
+
+  cairo_save (cr);
+  return cr;
+}
+
+static void
+pgtk_flip_buffers (struct frame *f)
+{
+  block_input ();
+  if (FRAME_X_OUTPUT (f)->clip_count)
+    emacs_abort ();
+  cairo_surface_t *cur = FRAME_CR_SURFACE (f);
+  if (!cur)
+    {
+      unblock_input ();
+      return;
+    }
+  FRAME_CR_SURFACE (f) = FRAME_X_OUTPUT (f)->flip_buf;
+  cairo_t *ctx = FRAME_X_OUTPUT (f)->cr_flip_context;
+  cairo_set_source_surface (ctx, cur, 0, 0);
+  cairo_paint (ctx);
+  FRAME_X_OUTPUT (f)->cr_flip_context = FRAME_CR_CONTEXT (f);
+  FRAME_CR_CONTEXT (f) = ctx;
+  FRAME_X_OUTPUT (f)->flip_buf = cur;
+  unblock_input ();
+}
+
+void
+pgtk_end_cr_clip (struct frame *f)
+{
+  FRAME_X_OUTPUT (f)->clip_count--;
+  if (!FRAME_X_OUTPUT (f)->clip_count)
+    FRAME_X_OUTPUT (f)->want_flip = 1;
+  cairo_restore (FRAME_CR_CONTEXT (f));
+  unblock_input ();
+  if (input_blocked_p () || !FRAME_LIVE_P (f))
+    return;
+  block_input ();
+  GtkWidget *widget = FRAME_GTK_WIDGET (f);
+  gtk_widget_queue_draw (widget);
+  unblock_input ();
+}
+
+void
+pgtk_set_cr_source_with_gc_foreground (struct frame *f, Emacs_GC *gc)
+{
+  pgtk_set_cr_source_with_color (f, gc->foreground);
+}
+
+void
+pgtk_set_cr_source_with_gc_background (struct frame *f, Emacs_GC *gc)
+{
+  pgtk_set_cr_source_with_color (f, gc->background);
+}
+
+void
+pgtk_set_cr_source_with_color (struct frame *f, unsigned long color)
+{
+  block_input ();
+  Emacs_Color col;
+  col.pixel = color;
+  pgtk_query_color (f, &col);
+  cairo_set_source_rgb (FRAME_CR_CONTEXT (f), col.red / 65535.0,
+                        col.green / 65535.0, col.blue / 65535.0);
+  unblock_input ();
+}
+
+void
+pgtk_cr_draw_frame (cairo_t *cr, struct frame *f)
+{
+  block_input ();
+  if (!FRAME_X_OUTPUT (f)->clip_count &&
+      FRAME_X_OUTPUT (f)->want_flip &&
+      NILP (get_frame_param (f, Qinhibit_double_buffering)))
+    {
+      FRAME_X_OUTPUT (f)->want_flip = NULL;
+      pgtk_flip_buffers (f);
+    }
+  cairo_set_source_surface (cr, NILP (get_frame_param (f, Qinhibit_double_buffering)) ?
+			    FRAME_X_OUTPUT (f)->flip_buf : FRAME_CR_SURFACE (f), 0, 0);
+  cairo_paint (cr);
+  if (FRAME_X_OUTPUT (f)->cr_surface_visible_bell)
+    {
+      cairo_set_source_surface (cr, FRAME_X_OUTPUT (f)->cr_surface_visible_bell, 0, 0);
+      cairo_paint (cr);
+    }
+  unblock_input ();
+}
+
+void
+pgtk_cr_destroy_surface (struct frame *f)
+{
+  if (FRAME_CR_CONTEXT (f) != NULL)
+    {
+      cairo_destroy (FRAME_CR_CONTEXT (f));
+      FRAME_CR_CONTEXT (f) = NULL;
+    }
+  if (FRAME_CR_SURFACE (f) != NULL)
+    {
+      cairo_surface_destroy (FRAME_CR_SURFACE (f));
+      FRAME_CR_SURFACE (f) = NULL;
+    }
+  if (FRAME_X_OUTPUT (f)->flip_buf)
+    cairo_surface_destroy (FRAME_X_OUTPUT (f)->flip_buf);
+  SET_FRAME_GARBAGED (f);
+}
+
+void
+init_pgtkterm (void)
+{
+#ifdef HAVE_GTK4
+  pgtk_dnd_global_init ();
+#endif
+  bzero (fdmap, 800 * sizeof *fdmap);
+}
diff --git a/src/pgtkterm.h b/src/pgtkterm.h
new file mode 100644
index 0000000000..ae9bf3042f
--- /dev/null
+++ b/src/pgtkterm.h
@@ -0,0 +1,746 @@
+/* Definitions and headers for communication with pure Gtk+3.
+   Copyright (C) 1989, 1993, 2005, 2008-2018 Free Software Foundation,
+   Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+
+#include "dispextern.h"
+#include "frame.h"
+#include "character.h"
+#include "font.h"
+#include "sysselect.h"
+#include "keyboard.h"
+#include "atimer.h"
+
+#ifndef __PGTKTERM_H
+#define __PGTKTERM_H
+
+#ifdef HAVE_PGTK
+
+#include <gtk/gtk.h>
+
+#ifdef PGTK_DEBUG
+extern void pgtk_log(const char *file, int lineno, const char *fmt, ...)
+  ATTRIBUTE_FORMAT_PRINTF (3, 4);
+#define PGTK_TRACE(fmt, ...) pgtk_log(__FILE__, __LINE__, fmt, ## __VA_ARGS__)
+extern void pgtk_backtrace(const char *file, int lineno);
+#define PGTK_BACKTRACE() pgtk_backtrace(__FILE__, __LINE__)
+#else
+#define PGTK_TRACE(fmt, ...) ((void) 0)
+#define PGTK_BACKTRACE() ((void) 0)
+#endif
+
+#ifdef HAVE_GTK4
+#include "pgtkdnd.h"
+#endif
+
+#if (defined (HAVE_GTK3) && GTK_CHECK_VERSION (2, 14, 0)) || defined (HAVE_GTK4)
+#define HAVE_GTK_EVENT_CONTROLLER
+#endif
+
+/* The GtkTooltip API came in 2.12, but gtk-enable-tooltips in 2.14. */
+#if GTK_CHECK_VERSION (2, 14, 0)
+#define USE_GTK_TOOLTIP
+#endif
+
+#ifdef HAVE_GTK3
+#include "pgtkevent.h"
+#endif
+
+/* could use list to store these, but rest of emacs has a big infrastructure
+   for managing a table of bitmap "records" */
+struct pgtk_bitmap_record
+{
+  void *img;
+  char *file;
+  int refcount;
+  int height, width, depth;
+  cairo_pattern_t *pattern;
+};
+
+#define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b))
+#define ARGB_TO_ULONG(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
+
+#define ALPHA_FROM_ULONG(color) ((color) >> 24)
+#define RED_FROM_ULONG(color) (((color) >> 16) & 0xff)
+#define GREEN_FROM_ULONG(color) (((color) >> 8) & 0xff)
+#define BLUE_FROM_ULONG(color) ((color) & 0xff)
+
+struct scroll_bar
+{
+  /* These fields are shared by all vectors.  */
+  union vectorlike_header header;
+
+  /* The window we're a scroll bar for.  */
+  Lisp_Object window;
+
+  /* The next and previous in the chain of scroll bars in this frame.  */
+  Lisp_Object next, prev;
+
+  /* Fields from `x_window' down will not be traced by the GC.  */
+
+  /* The X window representing this scroll bar.  */
+  GWindow x_window;
+
+  /* The position and size of the scroll bar in pixels, relative to the
+     frame.  */
+  int top, left, width, height;
+
+  /* The starting and ending positions of the handle, relative to the
+     handle area (i.e. zero is the top position, not
+     SCROLL_BAR_TOP_BORDER).  If they're equal, that means the handle
+     hasn't been drawn yet.
+
+     These are not actually the locations where the beginning and end
+     are drawn; in order to keep handles from becoming invisible when
+     editing large files, we establish a minimum height by always
+     drawing handle bottoms VERTICAL_SCROLL_BAR_MIN_HANDLE pixels below
+     where they would be normally; the bottom and top are in a
+     different co-ordinate system.  */
+  int start, end;
+
+  /* If the scroll bar handle is currently being dragged by the user,
+     this is the number of pixels from the top of the handle to the
+     place where the user grabbed it.  If the handle isn't currently
+     being dragged, this is -1.  */
+  int dragging;
+
+#if defined (USE_TOOLKIT_SCROLL_BARS) && defined (USE_LUCID)
+  /* Last scroll bar part seen in xaw_jump_callback and xaw_scroll_callback.  */
+  enum scroll_bar_part last_seen_part;
+#endif
+
+#if defined (USE_TOOLKIT_SCROLL_BARS) && !defined (USE_GTK)
+  /* Last value of whole for horizontal scrollbars.  */
+  int whole;
+#endif
+
+  /* True if the scroll bar is horizontal.  */
+  bool horizontal;
+};
+
+
+/* init'd in pgtk_initialize_display_info () */
+struct pgtk_display_info
+{
+  /* Chain of all pgtk_display_info structures.  */
+  struct pgtk_display_info *next;
+
+  /* The generic display parameters corresponding to this PGTK display. */
+  struct terminal *terminal;
+
+  /* This says how to access this display in Gdk.  */
+  GdkDisplay *gdpy;
+
+  GdkEvent *last_event;
+
+  int connection;
+
+  /* This is a cons cell of the form (NAME . FONT-LIST-CACHE).  */
+  Lisp_Object name_list_element;
+
+  /* Number of frames that are on this display.  */
+  int reference_count;
+
+  /* Logical identifier of this display.  */
+  unsigned x_id;
+
+  /* Default name for all frames on this display.  */
+  char *x_id_name;
+
+  /* The number of fonts loaded. */
+  int n_fonts;
+
+  /* Minimum width over all characters in all fonts in font_table.  */
+  int smallest_char_width;
+
+  /* Minimum font height over all fonts in font_table.  */
+  int smallest_font_height;
+
+  struct pgtk_bitmap_record *bitmaps;
+  ptrdiff_t bitmaps_size;
+  ptrdiff_t bitmaps_last;
+
+  /* DPI resolution of this screen */
+  double resx, resy;
+
+  /* Mask of things that cause the mouse to be grabbed */
+  int grabbed;
+
+  int n_planes;
+
+  int color_p;
+
+  GWindow root_window;
+
+  /* Xism */
+  XrmDatabase rdb;
+
+  /* The cursor to use for vertical scroll bars. */
+  Emacs_Cursor vertical_scroll_bar_cursor;
+
+  /* The cursor to use for horizontal scroll bars. */
+  Emacs_Cursor horizontal_scroll_bar_cursor;
+
+  /* Information about the range of text currently shown in
+     mouse-face.  */
+  Mouse_HLInfo mouse_highlight;
+
+  struct frame *highlight_frame;
+  struct frame *x_focus_frame;
+
+  /* The last frame mentioned in a FocusIn or FocusOut event.  This is
+     separate from x_focus_frame, because whether or not LeaveNotify
+     events cause us to lose focus depends on whether or not we have
+     received a FocusIn event for it.  */
+  struct frame *x_focus_event_frame;
+
+  /* The frame where the mouse was last time we reported a mouse event.  */
+  struct frame *last_mouse_frame;
+
+  /* The frame where the mouse was last time we reported a mouse motion.  */
+  struct frame *last_mouse_motion_frame;
+
+  /* Position where the mouse was last time we reported a motion.
+     This is a position on last_mouse_motion_frame.  */
+  int last_mouse_motion_x;
+  int last_mouse_motion_y;
+
+  /* Where the mouse was last time we reported a mouse position.  */
+  NativeRectangle last_mouse_glyph;
+
+  /* Time of last mouse movement.  */
+  Time last_mouse_movement_time;
+
+  /* The scroll bar in which the last motion event occurred.  */
+  void *last_mouse_scroll_bar;
+
+  /* The invisible cursor used for pointer blanking.
+     Unused if this display supports Xfixes extension.  */
+  Emacs_Cursor invisible_cursor;
+
+  /* Function used to toggle pointer visibility on this display.  */
+  void (*toggle_visible_pointer) (struct frame *, bool);
+
+  /* The GDK cursor for scroll bars and popup menus.  */
+  GdkCursor *xg_cursor;
+
+
+  /* The frame where the mouse was last time we reported a mouse position.  */
+  struct frame *last_mouse_glyph_frame;
+
+  /* Modifier masks in gdk */
+  int meta_mod_mask, alt_mod_mask;
+
+  /* whether to use Gtk's IM context. */
+
+  /* input method */
+  struct {
+    GtkIMContext *context;
+    struct frame *focused_frame;
+  } im;
+};
+
+/* This is a chain of structures for all the PGTK displays currently in use.  */
+extern struct pgtk_display_info *x_display_list;
+
+struct pgtk_output
+{
+#ifdef HAVE_GTK4
+  bool_bf ts_up_flag;
+#endif
+  unsigned long foreground_color;
+  unsigned long background_color;
+  void *toolbar;
+
+  /* Cursors */
+  Emacs_Cursor current_cursor;
+  Emacs_Cursor text_cursor;
+  Emacs_Cursor nontext_cursor;
+  Emacs_Cursor modeline_cursor;
+  Emacs_Cursor hand_cursor;
+  Emacs_Cursor hourglass_cursor;
+  Emacs_Cursor horizontal_drag_cursor;
+  Emacs_Cursor vertical_drag_cursor;
+  Emacs_Cursor left_edge_cursor;
+  Emacs_Cursor top_left_corner_cursor;
+  Emacs_Cursor top_edge_cursor;
+  Emacs_Cursor top_right_corner_cursor;
+  Emacs_Cursor right_edge_cursor;
+  Emacs_Cursor bottom_right_corner_cursor;
+  Emacs_Cursor bottom_edge_cursor;
+  Emacs_Cursor bottom_left_corner_cursor;
+
+  /* PGTK-specific */
+  Emacs_Cursor current_pointer;
+
+#ifdef HAVE_GTK3
+  /* border color */
+  unsigned long border_pixel;
+  GtkCssProvider *border_color_css_provider;
+
+  /* scrollbar color */
+  GtkCssProvider *scrollbar_foreground_css_provider;
+  GtkCssProvider *scrollbar_background_css_provider;
+#endif
+
+  /* Widget whose cursor is hourglass_cursor.  This widget is temporarily
+     mapped to display an hourglass cursor.  */
+  GtkWidget *hourglass_widget;
+
+  Emacs_GC cursor_xgcv;
+
+  /* lord knows why Emacs needs to know about our Window ids.. */
+  GWindow window_desc, parent_desc;
+  char explicit_parent;
+
+  struct font *font;
+  int baseline_offset;
+
+  /* If a fontset is specified for this frame instead of font, this
+     value contains an ID of the fontset, else -1.  */
+  int fontset; /* only used with font_backend */
+
+  unsigned long mouse_color;
+  unsigned long cursor_color;
+  unsigned long cursor_foreground_color;
+
+  int icon_top;
+  int icon_left;
+
+  /* The size of the extra width currently allotted for vertical
+     scroll bars, in pixels.  */
+  int vertical_scroll_bar_extra;
+
+  /* The height of the titlebar decoration (included in PGTKWindow's frame). */
+  int titlebar_height;
+
+  /* The height of the toolbar if displayed, else 0. */
+  int toolbar_height;
+
+  /* This is the Emacs structure for the PGTK display this frame is on.  */
+  struct pgtk_display_info *display_info;
+
+  /* Non-zero if we are zooming (maximizing) the frame.  */
+  int zooming;
+
+  /* Non-zero if we are doing an animation, e.g. toggling the tool bar. */
+  int in_animation;
+
+  /* The last size hints set.  */
+#ifndef HAVE_GTK4
+  GdkGeometry size_hints;
+#endif
+
+#ifdef HAVE_GTK4
+  bool bar_focus_out_events;
+  bool bar_focus_in_events;
+  struct frame *last_focus_child_frame;
+#endif
+  long hint_flags;
+  int preferred_width, preferred_height;
+
+  /* The widget of this screen.  This is the window of a top widget.  */
+  GtkWidget *widget;
+
+#ifdef HAVE_GTK3
+  GtkHeaderBar *header_bar;
+#endif
+
+#ifdef HAVE_GTK4
+  GSList *child_frame_order;
+  struct frame *keep_above;
+#endif
+
+#ifdef HAVE_GTK4
+  struct atimer *mbar_deep_atimer;
+  bool visible_bell_end_time;
+#endif
+
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+  EmacsGtkEventHandler *event_handler;
+  EmacsGtkEventHandler *event_subhandler;
+#endif
+  /* The widget of the edit portion of this screen; the window in
+     "window_desc" is inside of this.  */
+
+  GtkWidget *edit_widget;
+  /* The widget used for laying out widgets vertically.  */
+
+#ifndef HAVE_GTK4
+  GtkWidget *vbox_widget;
+  /* The widget used for laying out widgets horizontally.  */
+  GtkWidget *hbox_widget;
+  /* The menubar in this frame.  */
+  GtkWidget *menubar_widget;
+  /* The tool bar in this frame  */
+  GtkWidget *toolbar_widget;
+  /* True if tool bar is packed into the hbox widget (i.e. vertical).  */
+  bool_bf toolbar_in_hbox : 1;
+  bool_bf toolbar_is_packed : 1;
+#else
+  GtkOverlay *decor_overlay;
+  GtkOverlay *overlay;
+  GtkBox *box;
+  GtkBox *mbbox;
+  GtkBox *tbbox;
+  GtkWidget *menu_bar;
+  GtkWidget *toolbar_widget;
+  Lisp_Object lmme;
+#endif
+
+#ifdef USE_GTK_TOOLTIP
+#ifndef HAVE_GTK4
+  GtkTooltip *ttip_widget;
+  GtkWidget *ttip_lbl;
+  GtkWindow *ttip_window;
+#else
+  GtkPopover *ttip_popover;
+  Lisp_Object ttip_label;
+#endif
+#endif /* USE_GTK_TOOLTIP */
+
+  /* Height of menu bar widget, in pixels.  This value
+     is not meaningful if the menubar is turned off.  */
+  int menubar_height;
+
+  /* Height of tool bar widget, in pixels.  top_height is used if tool bar
+     at top, bottom_height if tool bar is at the bottom.
+     Zero if not using an external tool bar or if tool bar is vertical.  */
+  int toolbar_top_height, toolbar_bottom_height;
+
+  /* Width of tool bar widget, in pixels.  left_width is used if tool bar
+     at left, right_width if tool bar is at the right.
+     Zero if not using an external tool bar or if tool bar is horizontal.  */
+  int toolbar_left_width, toolbar_right_width;
+
+  /* Cairo drawing context.  */
+  cairo_t *cr_context;
+  cairo_t *cr_flip_context;
+  int cr_surface_desired_width, cr_surface_desired_height;
+  /* Cairo surface for double buffering */
+  cairo_surface_t *cr_surface;
+  cairo_surface_t *flip_buf;
+  cairo_surface_t *cr_surface_visible_bell;
+  int clip_count;
+  bool want_flip;
+
+  struct atimer *atimer_visible_bell;
+
+  int has_been_visible;
+
+#ifdef HAVE_GTK4
+  bool child_map_flag;
+#endif
+
+  /* Relief GCs, colors etc.  */
+  struct relief
+  {
+    Emacs_GC xgcv;
+    unsigned long pixel;
+  }
+  black_relief, white_relief;
+
+  /* The background for which the above relief GCs were set up.
+     They are changed only when a different background is involved.  */
+  unsigned long relief_background;
+
+  /* Keep track of focus.  May be EXPLICIT if we received a FocusIn for this
+     frame, or IMPLICIT if we received an EnterNotify.
+     FocusOut and LeaveNotify clears EXPLICIT/IMPLICIT. */
+  int focus_state;
+#ifdef HAVE_GTK4
+  struct dnd_info di;
+#endif
+  int menubar_or_popup_count;
+#ifdef HAVE_GTK4
+  bool_bf hyper_flag : 1;
+#endif
+};
+
+/* this dummy decl needed to support TTYs */
+struct x_output
+{
+  int unused;
+};
+
+enum
+{
+  /* Values for focus_state, used as bit mask.
+     EXPLICIT means we received a FocusIn for the frame and know it has
+     the focus.  IMPLICIT means we received an EnterNotify and the frame
+     may have the focus if no window manager is running.
+     FocusOut and LeaveNotify clears EXPLICIT/IMPLICIT. */
+  FOCUS_NONE     = 0,
+  FOCUS_IMPLICIT = 1,
+  FOCUS_EXPLICIT = 2
+};
+
+/* This gives the pgtk_display_info structure for the display F is on.  */
+#define FRAME_X_OUTPUT(f)         ((f)->output_data.pgtk)
+#define FRAME_OUTPUT_DATA(f)      FRAME_X_OUTPUT (f)
+
+#define FRAME_DISPLAY_INFO(f)     (FRAME_X_OUTPUT (f)->display_info)
+#define FRAME_FOREGROUND_COLOR(f) (FRAME_X_OUTPUT (f)->foreground_color)
+#define FRAME_BACKGROUND_COLOR(f) (FRAME_X_OUTPUT (f)->background_color)
+#define FRAME_CURSOR_COLOR(f)     (FRAME_X_OUTPUT (f)->cursor_color)
+#define FRAME_POINTER_TYPE(f)     (FRAME_X_OUTPUT (f)->current_pointer)
+#define FRAME_FONT(f)             (FRAME_X_OUTPUT (f)->font)
+#define FRAME_GTK_OUTER_WIDGET(f) (FRAME_X_OUTPUT (f)->widget)
+#define FRAME_GTK_WIDGET(f)       (FRAME_X_OUTPUT (f)->edit_widget)
+
+#ifdef HAVE_GTK4
+#define FRAME_DECOR_OVERLAY(f)    (FRAME_X_OUTPUT (f)->decor_overlay)
+#define FRAME_OVERLAY(f)          (FRAME_X_OUTPUT (f)->overlay)
+#define FRAME_BOX(f)              (FRAME_X_OUTPUT (f)->box)
+#define FRAME_MB_BOX(f)           (FRAME_X_OUTPUT (f)->mbbox)
+#define FRAME_TB_BOX(f)           (FRAME_X_OUTPUT (f)->tbbox)
+#define FRAME_TOOL_BAR(f)         (FRAME_X_OUTPUT (f)->toolbar_widget)
+#define FRAME_MENU_BAR(f)         (FRAME_X_OUTPUT (f)->menu_bar)
+#endif
+
+#ifdef HAVE_GTK_EVENT_CONTROLLER
+#define FRAME_GTK_HEADER_BAR(f)   ((f)->output_data.pgtk->header_bar)
+#define FRAME_GTK_EV_HANDLER(f)   ((f)->output_data.pgtk->event_handler)
+#define FRAME_GTK_ES_HANDLER(f)   ((f)->output_data.pgtk->event_subhandler)
+#endif
+
+/* aliases */
+#define FRAME_PGTK_VIEW(f)         FRAME_GTK_WIDGET(f)
+#define FRAME_X_WINDOW(f)          FRAME_GTK_WIDGET(f)
+#define FRAME_NATIVE_WINDOW(f)     FRAME_GTK_WIDGET(f)
+
+#define FRAME_X_DISPLAY(f)        (FRAME_DISPLAY_INFO(f)->gdpy)
+
+#define DEFAULT_GDK_DISPLAY() gdk_display_get_default()
+
+/* Turning a lisp vector value into a pointer to a struct scroll_bar.  */
+#define XSCROLL_BAR(vec) ((struct scroll_bar *) XVECTOR (vec))
+
+#define PGTK_FACE_FOREGROUND(f) ((f)->foreground)
+#define PGTK_FACE_BACKGROUND(f) ((f)->background)
+#define FRAME_DEFAULT_FACE(f) FACE_FROM_ID_OR_NULL (f, DEFAULT_FACE_ID)
+
+
+/* Compute pixel size for vertical scroll bars */
+#define PGTK_SCROLL_BAR_WIDTH(f)					\
+  (FRAME_HAS_VERTICAL_SCROLL_BARS (f)					\
+   ? rint (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0			\
+	   ? FRAME_CONFIG_SCROLL_BAR_WIDTH (f)				\
+	   : (FRAME_SCROLL_BAR_COLS (f) * FRAME_COLUMN_WIDTH (f)))	\
+   : 0)
+
+/* Compute pixel size for horizontal scroll bars */
+#define PGTK_SCROLL_BAR_HEIGHT(f)					\
+  (FRAME_HAS_HORIZONTAL_SCROLL_BARS (f)					\
+   ? rint (FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) > 0			\
+	   ? FRAME_CONFIG_SCROLL_BAR_HEIGHT (f)				\
+	   : (FRAME_SCROLL_BAR_LINES (f) * FRAME_LINE_HEIGHT (f)))	\
+   : 0)
+
+/* Difference btwn char-column-calculated and actual SB widths.
+   This is only a concern for rendering when SB on left. */
+#define PGTK_SCROLL_BAR_ADJUST(w, f)				\
+  (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_LEFT (w) ?			\
+   (FRAME_SCROLL_BAR_COLS (f) * FRAME_COLUMN_WIDTH (f)		\
+    - PGTK_SCROLL_BAR_WIDTH (f)) : 0)
+
+/* Difference btwn char-line-calculated and actual SB heights.
+   This is only a concern for rendering when SB on top. */
+#define PGTK_SCROLL_BAR_ADJUST_HORIZONTALLY(w, f)		\
+  (WINDOW_HAS_HORIZONTAL_SCROLL_BARS (w) ?		\
+   (FRAME_SCROLL_BAR_LINES (f) * FRAME_LINE_HEIGHT (f)	\
+    - PGTK_SCROLL_BAR_HEIGHT (f)) : 0)
+
+#define FRAME_MENUBAR_HEIGHT(f) (FRAME_X_OUTPUT(f)->menubar_height)
+
+/* Calculate system coordinates of the left and top of the parent
+   window or, if there is no parent window, the screen. */
+#define PGTK_PARENT_WINDOW_LEFT_POS(f)                                    \
+  (FRAME_PARENT_FRAME (f) != NULL                                       \
+   ? [[FRAME_PGTK_VIEW (f) window] parentWindow].frame.origin.x : 0)
+#define PGTK_PARENT_WINDOW_TOP_POS(f)                                     \
+  (FRAME_PARENT_FRAME (f) != NULL                                       \
+   ? ([[FRAME_PGTK_VIEW (f) window] parentWindow].frame.origin.y          \
+      + [[FRAME_PGTK_VIEW (f) window] parentWindow].frame.size.height     \
+      - FRAME_PGTK_TITLEBAR_HEIGHT (FRAME_PARENT_FRAME (f)))              \
+   : [[[PGTKScreen screepgtk] objectAtIndex: 0] frame].size.height)
+
+#define FRAME_PGTK_FONT_TABLE(f) (FRAME_DISPLAY_INFO (f)->font_table)
+
+#define FRAME_TOOLBAR_TOP_HEIGHT(f) ((f)->output_data.pgtk->toolbar_top_height)
+#define FRAME_TOOLBAR_BOTTOM_HEIGHT(f) \
+  ((f)->output_data.pgtk->toolbar_bottom_height)
+#define FRAME_TOOLBAR_HEIGHT(f) \
+  (FRAME_TOOLBAR_TOP_HEIGHT (f) + FRAME_TOOLBAR_BOTTOM_HEIGHT (f))
+#define FRAME_TOOLBAR_LEFT_WIDTH(f) ((f)->output_data.pgtk->toolbar_left_width)
+#define FRAME_TOOLBAR_RIGHT_WIDTH(f) ((f)->output_data.pgtk->toolbar_right_width)
+#define FRAME_TOOLBAR_WIDTH(f) \
+  (FRAME_TOOLBAR_LEFT_WIDTH (f) + FRAME_TOOLBAR_RIGHT_WIDTH (f))
+
+#define FRAME_FONTSET(f) (FRAME_X_OUTPUT(f)->fontset)
+
+#define FRAME_BASELINE_OFFSET(f) (FRAME_X_OUTPUT(f)->baseline_offset)
+#define BLACK_PIX_DEFAULT(f) 0x000000
+#define WHITE_PIX_DEFAULT(f) 0xFFFFFF
+
+/* First position where characters can be shown (instead of scrollbar, if
+   it is on left. */
+#define FIRST_CHAR_POSITION(f)				\
+  (! (FRAME_HAS_VERTICAL_SCROLL_BARS_ON_LEFT (f)) ? 0	\
+   : FRAME_SCROLL_BAR_COLS (f))
+
+
+/* Display init/shutdown functions implemented in pgtkterm.c */
+extern struct pgtk_display_info *pgtk_term_init (Lisp_Object display_name, char *resource_name);
+extern void pgtk_term_shutdown (int sig);
+
+/* Implemented in pgtkterm, published in or needed from pgtkfns. */
+extern void pgtk_clear_frame (struct frame *f);
+extern char *pgtk_xlfd_to_fontname (const char *xlfd);
+
+/* Implemented in pgtkfns. */
+extern void pgtk_set_doc_edited (void);
+extern const char *pgtk_get_defaults_value (const char *key);
+extern const char *pgtk_get_string_resource (XrmDatabase rdb, const char *name, const char *class);
+extern void pgtk_implicitly_set_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval);
+extern void pgtk_set_inhibit_double_buffering (struct frame *f,
+					       Lisp_Object new_value,
+					       Lisp_Object old_value);
+
+/* Color management implemented in pgtkterm. */
+extern bool pgtk_defined_color (struct frame *f,
+				const char *name,
+				Emacs_Color *color_def, bool alloc,
+				bool makeIndex);
+extern void pgtk_query_color (struct frame *f, Emacs_Color *color);
+extern void pgtk_query_colors (struct frame *f, Emacs_Color *colors, int ncolors);
+extern int pgtk_parse_color (const char *color_name, Emacs_Color *color);
+extern int pgtk_lisp_to_color (Lisp_Object color, Emacs_Color *col);
+
+/* Implemented in pgtkterm.c */
+extern void pgtk_clear_area (struct frame *f, int x, int y, int width, int height);
+extern int pgtk_gtk_to_emacs_modifiers (struct pgtk_display_info *dpyinfo, int state);
+extern void pgtk_clear_under_internal_border (struct frame *f);
+extern void pgtk_set_event_handler(struct frame *f);
+
+/* Implemented in pgtkterm.c */
+extern int x_display_pixel_height (struct pgtk_display_info *);
+extern int x_display_pixel_width (struct pgtk_display_info *);
+
+/* Implemented in pgtkterm.c */
+extern void x_destroy_window (struct frame *f);
+extern void x_set_parent_frame (struct frame *f, Lisp_Object new_value,
+                                Lisp_Object old_value);
+extern void x_set_no_focus_on_map (struct frame *f, Lisp_Object new_value,
+                                   Lisp_Object old_value);
+extern void x_set_no_accept_focus (struct frame *f, Lisp_Object new_value,
+                                   Lisp_Object old_value);
+extern void x_set_z_group (struct frame *f, Lisp_Object new_value,
+                           Lisp_Object old_value);
+extern int pgtk_select (int nfds, fd_set *readfds, fd_set *writefds,
+			fd_set *exceptfds, struct timespec *timeout,
+			sigset_t *sigmask);
+
+/* Cairo related functions implemented in pgtkterm.c */
+extern void pgtk_cr_update_surface_desired_size (struct frame *, int, int);
+extern cairo_t *pgtk_begin_cr_clip (struct frame *f);
+extern void pgtk_end_cr_clip (struct frame *f);
+extern void pgtk_set_cr_source_with_gc_foreground (struct frame *f, Emacs_GC *gc);
+extern void pgtk_set_cr_source_with_gc_background (struct frame *f, Emacs_GC *gc);
+extern void pgtk_set_cr_source_with_color (struct frame *f, unsigned long color);
+extern void pgtk_cr_draw_frame (cairo_t *cr, struct frame *f);
+extern void pgtk_cr_destroy_surface(struct frame *f);
+
+/* Defined in pgtkmenu.c */
+extern Lisp_Object pgtk_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents);
+extern Lisp_Object pgtk_dialog_show (struct frame *f, Lisp_Object title,
+				     Lisp_Object header, const char **error);
+extern void initialize_frame_menubar (struct frame *);
+
+
+/* Symbol initializations implemented in each pgtk sources. */
+extern void syms_of_pgtkterm (void);
+extern void syms_of_pgtkfns (void);
+extern void syms_of_pgtkmenu (void);
+extern void syms_of_pgtkselect (void);
+extern void syms_of_pgtkim (void);
+
+/* Implemented in pgtkselect. */
+extern void nxatoms_of_pgtkselect (void);
+
+/* Initialization and marking implemented in pgtkterm.c */
+extern void init_pgtkterm (void);
+extern void mark_pgtkterm(void);
+extern void pgtk_delete_terminal (struct terminal *terminal);
+
+extern void pgtk_make_frame_visible (struct frame *f);
+extern void pgtk_make_frame_invisible (struct frame *f);
+extern void x_wm_set_size_hint (struct frame *, long, bool);
+extern void x_free_frame_resources (struct frame *);
+extern void pgtk_iconify_frame (struct frame *f);
+extern void pgtk_focus_frame (struct frame *f, bool noactivate);
+extern void pgtk_set_scroll_bar_default_width (struct frame *f);
+extern void pgtk_set_scroll_bar_default_height (struct frame *f);
+extern Lisp_Object x_get_focus_frame (struct frame *frame);
+
+extern void pgtk_frame_rehighlight (struct pgtk_display_info *dpyinfo);
+
+extern void x_change_tab_bar_height (struct frame *, int);
+
+extern struct pgtk_display_info *check_pgtk_display_info (Lisp_Object object);
+
+extern void pgtk_enqueue_string(struct frame *f, gchar *str);
+extern void pgtk_enqueue_preedit(struct frame *f, Lisp_Object image_data);
+extern void pgtk_im_init(struct pgtk_display_info *dpyinfo);
+extern void pgtk_im_finish(struct pgtk_display_info *dpyinfo);
+
+
+/* Utility functions for child frames defined in pgtkterm.c */
+extern void pgtk_child_frame_reposition (struct frame *child_frame, gpointer ignored);
+extern void pgtk_child_frame_flush (struct frame *parent);
+extern void pgtk_child_frame_flush_2 (gpointer child, gpointer userdata);
+extern void pgtk_clear_child_occourences (struct frame *child);
+
+extern int
+pgtk_get_window_inside_decor_height (struct frame *f);
+extern int
+pgtk_get_window_decoration_width (struct frame *f);
+
+/* Utility functions */
+
+extern int pgtk_get_title_bar_height (struct frame *f);
+
+extern void
+evq_enqueue (union buffered_input_event *ev);
+
+extern void
+pgtk_m_px_pos (struct frame *f, double *_x, double *_y);
+
+#ifdef HAVE_GTK4
+extern gboolean
+key_press_event (GtkWidget *widget, GdkEvent *event, struct frame *f);
+#endif
+
+extern void
+pgtk_vpixoffset_scroll (int pixels, struct window *window);
+
+extern int
+pgtk_lisp_to_color_with_frame (Lisp_Object color, Emacs_Color *col, struct frame *f);
+#endif	/* HAVE_PGTK */
+#endif  /* __PGTKTERM_H */
diff --git a/src/print.c b/src/print.c
index bd1769144e..ccdb936bca 100644
--- a/src/print.c
+++ b/src/print.c
@@ -48,6 +48,10 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 # include <sys/socket.h> /* for F_DUPFD_CLOEXEC */
 #endif
 
+# ifdef HAVE_GTK4
+#include "pgtkembed.h"
+# endif
+
 struct terminal;
 
 /* Avoid actual stack overflow in print.  */
@@ -1521,7 +1525,13 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       print_c_string ("#<xwidget ", printcharfun);
       printchar ('>', printcharfun);
       break;
-
+    case PVEC_EMBEDDED_WIDGET:
+      print_c_string ("#<embedded widget ", printcharfun);
+#ifdef HAVE_GTK4
+      print_string (Fsymbol_name (XGTKWIDGET (obj)->type), printcharfun);
+#endif
+      printchar ('>', printcharfun);
+      break;
     case PVEC_WINDOW:
       {
 	int len = sprintf (buf, "#<window %"pI"d",
@@ -1833,7 +1843,6 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       }
       break;
 #endif
-
     default:
       emacs_abort ();
     }
diff --git a/src/process.c b/src/process.c
index 6e5bcf307a..343e66cc8a 100644
--- a/src/process.c
+++ b/src/process.c
@@ -5560,8 +5560,12 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd,
 	    }
 #endif
 
-/* Non-macOS HAVE_GLIB builds call thread_select in xgselect.c.  */
-#if defined HAVE_GLIB && !defined HAVE_NS
+#if defined HAVE_PGTK
+	  nfds = pgtk_select (max_desc + 1,
+			      &Available, (check_write ? &Writeok : 0),
+			      NULL, &timeout, NULL);
+#elif defined HAVE_GLIB && !defined HAVE_NS
+	  /* Non-macOS HAVE_GLIB builds call thread_select in xgselect.c.  */
 	  nfds = xg_select (max_desc + 1,
 			    &Available, (check_write ? &Writeok : 0),
 			    NULL, &timeout, NULL);
@@ -5682,7 +5686,11 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd,
 	  if (xerrno == EINTR)
 	    no_avail = 1;
 	  else if (xerrno == EBADF)
+#ifndef HAVE_GTK4
 	    emacs_abort ();
+#else
+	    no_avail = 1;
+#endif
 	  else
 	    report_file_errno ("Failed select", Qnil, xerrno);
 	}
diff --git a/src/termhooks.h b/src/termhooks.h
index d18b750c3a..249f75d7b9 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -60,7 +60,8 @@ #define EMACS_TERMHOOKS_H
   output_x_window,
   output_msdos_raw,
   output_w32,
-  output_ns
+  output_ns,
+  output_pgtk
 };
 
 /* Input queue declarations and hooks.  */
@@ -267,6 +268,10 @@ #define EMACS_TERMHOOKS_H
   , FILE_NOTIFY_EVENT
 #endif
 
+#ifdef HAVE_PGTK
+  /* Pre-edit text was changed. */
+  , PGTK_PREEDIT_TEXT_EVENT
+#endif
 };
 
 /* Bit width of an enum event_kind tag at the start of structs and unions.  */
@@ -449,6 +454,7 @@ #define EVENT_INIT(event) memset (&(event), 0, sizeof (struct input_event))
     struct x_display_info *x;         /* xterm.h */
     struct w32_display_info *w32;     /* w32term.h */
     struct ns_display_info *ns;       /* nsterm.h */
+    struct pgtk_display_info *pgtk; /* pgtkterm.h */
   } display_info;
 
 \f
@@ -522,7 +528,7 @@ #define EVENT_INIT(event) memset (&(event), 0, sizeof (struct input_event))
      BGCOLOR.  */
   void (*query_frame_background_color) (struct frame *f, Emacs_Color *bgcolor);
 
-#if defined (HAVE_X_WINDOWS) || defined (HAVE_NTGUI)
+#if defined (HAVE_X_WINDOWS) || defined (HAVE_NTGUI) || defined (HAVE_PGTK)
   /* On frame F, translate pixel colors to RGB values for the NCOLORS
      colors in COLORS.  Use cached information, if available.  */
 
@@ -773,6 +779,8 @@ #define EVENT_INIT(event) memset (&(event), 0, sizeof (struct input_event))
      frames on the terminal when it calls this hook, so infinite
      recursion is prevented.  */
   void (*delete_terminal_hook) (struct terminal *);
+
+  void (*set_parent_frame_hook) (struct frame *, struct frame *);
 } GCALIGNED_STRUCT;
 
 INLINE bool
@@ -837,6 +845,9 @@ #define TERMINAL_FONT_CACHE(t)						\
 #elif defined (HAVE_NS)
 #define TERMINAL_FONT_CACHE(t)						\
   (t->type == output_ns ? t->display_info.ns->name_list_element : Qnil)
+#elif defined (HAVE_PGTK)
+#define TERMINAL_FONT_CACHE(t)						\
+  (t->type == output_pgtk ? t->display_info.pgtk->name_list_element : Qnil)
 #endif
 
 extern struct terminal *decode_live_terminal (Lisp_Object);
diff --git a/src/terminal.c b/src/terminal.c
index e3b666ba61..ef2f0c34c2 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -445,6 +445,8 @@ DEFUN ("terminal-live-p", Fterminal_live_p, Sterminal_live_p, 1, 1, 0,
       return Qpc;
     case output_ns:
       return Qns;
+    case output_pgtk:
+      return Qpgtk;
     default:
       emacs_abort ();
     }
diff --git a/src/window.c b/src/window.c
index e2dea8b70e..0a7ed059a3 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2417,6 +2417,8 @@ replace_window (Lisp_Object old, Lisp_Object new, bool setflag)
       wset_normal_lines (o, make_float (1.0));
       n->desired_matrix = n->current_matrix = 0;
       n->vscroll = 0;
+      n->vpixoffset = 0;
+      n->pixstateflag = 0;
       memset (&n->cursor, 0, sizeof (n->cursor));
       memset (&n->phys_cursor, 0, sizeof (n->phys_cursor));
       n->last_cursor_vpos = 0;
@@ -3996,6 +3998,7 @@ set_window_buffer (Lisp_Object window, Lisp_Object buffer,
       w->hscroll = w->min_hscroll = w->hscroll_whole = 0;
       w->suspend_auto_hscroll = false;
       w->vscroll = 0;
+      w->vpixoffset = 0;
       set_marker_both (w->pointm, buffer, BUF_PT (b), BUF_PT_BYTE (b));
       set_marker_both (w->old_pointm, buffer, BUF_PT (b), BUF_PT_BYTE (b));
       set_marker_restricted (w->start,
diff --git a/src/window.h b/src/window.h
index 167d1be7ab..d9f96e2990 100644
--- a/src/window.h
+++ b/src/window.h
@@ -448,7 +448,11 @@ #define WINDOW_H_INCLUDED
     /* Amount by which lines of this window are scrolled in
        y-direction (smooth scrolling).  */
     int vscroll;
-
+#ifdef HAVE_WINDOW_SYSTEM
+    /* Pixel-scroll amount */
+    int vpixoffset;
+    bool pixstateflag;
+#endif
     /* Z_BYTE - buffer position of the last glyph in the current matrix of W.
        Should be nonnegative, and only valid if window_end_valid is true.  */
     ptrdiff_t window_end_bytepos;
@@ -756,7 +760,7 @@ #define WINDOW_MENU_BAR_P(W) false
 #endif
 
 /* True if W is a tab bar window.  */
-#if defined (HAVE_WINDOW_SYSTEM)
+#if defined (HAVE_WINDOW_SYSTEM) && !defined(HAVE_PGTK)
 # define WINDOW_TAB_BAR_P(W) \
    (WINDOWP (WINDOW_XFRAME (W)->tab_bar_window) \
     && (W) == XWINDOW (WINDOW_XFRAME (W)->tab_bar_window))
diff --git a/src/xdisp.c b/src/xdisp.c
index 140d134572..7c0633a3c1 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -462,6 +462,11 @@ Copyright (C) 1985-1988, 1993-1995, 1997-2020 Free Software Foundation,
 #include "fontset.h"
 #include "blockinput.h"
 #include "xwidget.h"
+
+#ifdef HAVE_GTK3
+#include "gtkinter.h"
+#endif
+
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -3267,7 +3272,7 @@ init_iterator (struct it *it, struct window *w,
       it->tab_line_p = window_wants_tab_line (w);
       it->header_line_p = window_wants_header_line (w);
       body_height = WINDOW_TAB_LINE_HEIGHT (w) + WINDOW_HEADER_LINE_HEIGHT (w);
-      it->current_y =  body_height + w->vscroll;
+      it->current_y = body_height + w->vscroll + w->vpixoffset;
     }
 
   /* Leave room for a border glyph.  */
@@ -12620,7 +12625,7 @@ update_menu_bar (struct frame *f, bool save_match_data, bool hooks_run)
   if (FRAME_WINDOW_P (f)
       ?
 #ifdef HAVE_EXT_MENU_BAR
-      FRAME_EXTERNAL_MENU_BAR (f)
+      FRAME_EXTERNAL_MENU_BAR (f) || FRAME_DISPLAY_HEADER_BAR_MENU (f)
 #else
       FRAME_MENU_BAR_LINES (f) > 0
 #endif
@@ -13674,7 +13679,8 @@ tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p,
 update_tool_bar (struct frame *f, bool save_match_data)
 {
 #ifdef HAVE_EXT_TOOL_BAR
-  bool do_update = FRAME_EXTERNAL_TOOL_BAR (f);
+  bool do_update = FRAME_EXTERNAL_TOOL_BAR (f) ||
+    FRAME_DISPLAY_HEADER_BAR_ICONS (f);
 #else
   bool do_update = (WINDOWP (f->tool_bar_window)
 		    && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window)) > 0);
@@ -14134,6 +14140,10 @@ redisplay_tool_bar (struct frame *f)
 
   if (FRAME_EXTERNAL_TOOL_BAR (f))
     update_frame_tool_bar (f);
+#ifdef HAVE_GTK3
+  else
+    update_frame_header_bar (f);
+#endif
   return false;
 
 #else /* ! (HAVE_EXT_TOOL_BAR) */
@@ -15286,7 +15296,7 @@ redisplay_internal (void)
   if (!fr->glyphs_initialized_p)
     return;
 
-#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS)
+#if defined (USE_X_TOOLKIT) || (defined (USE_GTK) && !defined(HAVE_PGTK)) || defined (HAVE_NS)
   if (popup_activated ())
     {
       return;
@@ -18901,6 +18911,12 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	  clear_glyph_matrix (w->desired_matrix);
 	  goto recenter;
 	}
+      if (w->vpixoffset)
+	{
+	  w->vpixoffset = 0;
+	  clear_glyph_matrix (w->desired_matrix);
+	  goto recenter;
+	}
 
       /* Users who set scroll-conservatively to a large number want
 	 point just above/below the scroll margin.  If we ended up
@@ -18931,7 +18947,6 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
     }
 
  done:
-
   SET_TEXT_POS_FROM_MARKER (startp, w->start);
   w->start_at_line_beg = (CHARPOS (startp) == BEGV
 			  || FETCH_BYTE (BYTEPOS (startp) - 1) == '\n');
@@ -19036,6 +19051,9 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 #ifdef HAVE_EXT_TOOL_BAR
 	  if (FRAME_EXTERNAL_TOOL_BAR (f))
 	    redisplay_tool_bar (f);
+#ifdef HAVE_GTK3
+	  update_frame_header_bar (f);
+#endif
 #else
 	  if (WINDOWP (f->tool_bar_window)
 	      && (FRAME_TOOL_BAR_LINES (f) > 0
@@ -19119,7 +19137,33 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
      shorter.  This can be caused by log truncation in *Messages*.  */
   if (CHARPOS (lpoint) <= ZV)
     TEMP_SET_PT_BOTH (CHARPOS (lpoint), BYTEPOS (lpoint));
-
+#ifdef HAVE_WINDOW_SYSTEM
+  if (w->vpixoffset != 0)
+    {
+      struct glyph_row *row = MATRIX_FIRST_TEXT_ROW (w->current_matrix);
+      /* When the position of the next row is less than,
+	 or equal to zero, we can comfortably flush vscroll. */
+      if ((w->current_matrix->rows->mode_line_p ?
+	   row->y <= 0 : row->y && !(row->y % row->height) && w->current_matrix->nrows > 3))
+	{
+	  if (!w->pixstateflag)
+	    {
+	      if (w->current_matrix->rows->mode_line_p)
+		SET_MARKER_FROM_TEXT_POS (w->start,
+					  MATRIX_ROW (w->current_matrix, 2)->start.pos);
+	      else
+		SET_MARKER_FROM_TEXT_POS (w->start,
+					  (w->desired_matrix->rows + 1)->start.pos);
+	      w->vpixoffset = 0;
+	    }
+	  w->pixstateflag = true;
+	}
+      else
+	{
+	  w->pixstateflag = false;
+	}
+    }
+#endif
   unbind_to (count, Qnil);
 }
 
@@ -24776,6 +24820,11 @@ display_menu_bar (struct window *w)
   if (FRAME_W32_P (f))
     return;
 #endif
+#if defined (HAVE_PGTK)
+  if (FRAME_PGTK_P (f))
+    return;
+#endif
+
 #if defined (USE_X_TOOLKIT) || defined (USE_GTK)
   if (FRAME_X_P (f))
     return;
@@ -28450,6 +28499,24 @@ draw_glyphs (struct window *w, int x, struct glyph_row *row,
   end = min (end, row->used[area]);
   start = clip_to_bounds (0, start, end);
 
+#ifdef HAVE_GTK4
+  if (area == TEXT_AREA)
+    {
+      bool_bf flag = 0;
+      for (ptrdiff_t z = 0; z < row->used[area]; ++z)
+	{
+	  enum face_box_type box =
+	    FACE_FROM_ID (XFRAME (w->frame), row->glyphs[area][z].face_id)->box;
+	  if (box == FACE_RAISED_GTK_BUTTON_BOX ||
+	      box == FACE_SUNKEN_GTK_BUTTON_BOX ||
+	      box == FACE_PRELIGHT_GTK_BUTTON_BOX)
+	    flag = 1;
+	}
+      if (flag)
+	end = row->used[area];
+    }
+#endif
+
   /* Translate X to frame coordinates.  Set last_x to the right
      end of the drawing area.  */
   if (row->full_width_p)
@@ -28682,6 +28749,16 @@ draw_glyphs (struct window *w, int x, struct glyph_row *row,
   return x_reached;
 }
 
+static int draw_glyphs_debug(const char *file, int lineno,
+			     struct window *w, int x, struct glyph_row *row,
+			     enum glyph_row_area area, ptrdiff_t start, ptrdiff_t end,
+			     enum draw_glyphs_face hl, int overlaps)
+{
+  return draw_glyphs(w, x, row, area, start, end, hl, overlaps);
+}
+#define draw_glyphs(w, x, r, a, s, e, h, o) \
+  draw_glyphs_debug(__FILE__, __LINE__, w, x, r, a, s, e, h, o)
+
 /* Find the first glyph in the run of underlined glyphs preceding the
    beginning of glyph string S, and return its font (which could be
    NULL).  This is needed because that font determines the underline
@@ -29922,6 +29999,13 @@ gui_produce_glyphs (struct it *it)
   int extra_line_spacing = it->extra_line_spacing;
 
   it->glyph_not_available_p = false;
+#ifdef HAVE_GTK4
+  if (it->glyph_row)
+    {
+      it->glyph_row->button_selected_marked_p = false;
+      it->glyph_row->button_unselected_marked_p = false;
+    }
+#endif
 
   if (it->what == IT_CHARACTER)
     {
@@ -31434,7 +31518,37 @@ display_and_set_cursor (struct window *w, bool on,
       w->phys_cursor.hpos = hpos;
       w->phys_cursor.vpos = vpos;
     }
-
+#ifdef HAVE_GTK4
+#define area TEXT_AREA
+  block_input ();
+  if (w->window_end_valid)
+    {
+      for (ptrdiff_t z = max (0, w->last_cursor_vpos);
+	   z < max (vpos + 2, w->current_matrix->nrows); ++z)
+	{
+	  bool_bf flag = 0;
+	  struct glyph_row *r = w->current_matrix->rows + z;
+	  for (ptrdiff_t z = 0; z < r->used[area]; ++z)
+	    {
+	      if (z > 10000)
+		emacs_abort ();
+	      struct face *face =
+		FACE_FROM_ID_OR_NULL (XFRAME (w->frame), r->glyphs[area][z].face_id);
+	      if (!face)
+		continue;
+	      enum face_box_type box = face->box;
+	      if (box == FACE_RAISED_GTK_BUTTON_BOX ||
+		  box == FACE_SUNKEN_GTK_BUTTON_BOX ||
+		  box == FACE_PRELIGHT_GTK_BUTTON_BOX)
+		flag = 1;
+	    }
+	  if (flag)
+	    draw_glyphs (w, r->x, r, area, 0, r->used[area], DRAW_NORMAL_TEXT, 0);
+	}
+    }
+  unblock_input ();
+#undef area
+#endif
   FRAME_RIF (f)->draw_window_cursor (w, glyph_row, x, y,
                                      new_cursor_type, new_cursor_width,
                                      on, active_cursor);
@@ -32981,7 +33095,7 @@ note_mouse_highlight (struct frame *f, int x, int y)
   struct buffer *b;
 
   /* When a menu is active, don't highlight because this looks odd.  */
-#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS) || defined (MSDOS)
+#if defined (USE_X_TOOLKIT) || (defined (USE_GTK) && !defined(HAVE_PGTK)) || defined (HAVE_NS) || defined (MSDOS)
   if (popup_activated ())
     return;
 #endif
diff --git a/src/xfaces.c b/src/xfaces.c
index bab142ade0..e429e24812 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -244,6 +244,10 @@ #define GCGraphicsExposures 0
 #ifdef HAVE_NS
 #define GCGraphicsExposures 0
 #endif /* HAVE_NS */
+
+#ifdef HAVE_PGTK
+#define GCGraphicsExposures 0
+#endif /* HAVE_NS */
 #endif /* HAVE_WINDOW_SYSTEM */
 
 #include "buffer.h"
@@ -570,6 +574,26 @@ x_free_gc (struct frame *f, Emacs_GC *gc)
 }
 #endif  /* HAVE_NS */
 
+#ifdef HAVE_PGTK
+/* PGTK emulation of GCs */
+
+static Emacs_GC *
+x_create_gc (struct frame *f,
+	     unsigned long mask,
+	     Emacs_GC *xgcv)
+{
+  Emacs_GC *gc = xmalloc (sizeof *gc);
+  *gc = *xgcv;
+  return gc;
+}
+
+static void
+x_free_gc (struct frame *f, Emacs_GC *gc)
+{
+  xfree (gc);
+}
+#endif  /* HAVE_NS */
+
 /***********************************************************************
 			   Frames and faces
  ***********************************************************************/
@@ -3160,7 +3184,14 @@ DEFUN ("internal-set-lisp-face-attribute", Finternal_set_lisp_face_attribute,
 		}
 	      else if (EQ (k, QCstyle))
 		{
-		  if (!EQ (v, Qpressed_button) && !EQ (v, Qreleased_button))
+		  if (!EQ (v, Qpressed_button) && !EQ (v, Qreleased_button)
+#ifdef HAVE_GTK4
+		      && !EQ (v, Qgtk_pressed_button) && !EQ (v, Qgtk_released_button)
+		      && !EQ (v, Qgtk_checked_checkbox) && !EQ (v, Qgtk_unchecked_checkbox)
+		      && !EQ (v, Qgtk_prelight_button) && !EQ (v, Qgtk_unselected_entry)
+		      && !EQ (v, Qgtk_selected_entry)
+#endif
+		      )
 		    break;
 		}
 	      else
@@ -5888,6 +5919,22 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE]
 		face->box = FACE_RAISED_BOX;
 	      else if (EQ (value, Qpressed_button))
 		face->box = FACE_SUNKEN_BOX;
+#ifdef HAVE_GTK4
+	      else if (EQ (value, Qgtk_pressed_button))
+		face->box = FACE_SUNKEN_GTK_BUTTON_BOX;
+	      else if (EQ (value, Qgtk_released_button))
+		face->box = FACE_RAISED_GTK_BUTTON_BOX;
+	      else if (EQ (value, Qgtk_checked_checkbox))
+		face->box = FACE_CHECKED_GTK_CHECKBOX;
+	      else if (EQ (value, Qgtk_unchecked_checkbox))
+		face->box = FACE_UNCHECKED_GTK_CHECKBOX;
+	      else if (EQ (value, Qgtk_prelight_button))
+		face->box = FACE_PRELIGHT_GTK_BUTTON_BOX;
+	      else if (EQ (value, Qgtk_selected_entry))
+		face->box = FACE_FOCUSED_GTK_ENTRY_BOX;
+	      else if (EQ (value, Qgtk_unselected_entry))
+		face->box = FACE_UNFOCUSED_GTK_ENTRY_BOX;
+#endif
 	    }
 	}
     }
@@ -6773,6 +6820,15 @@ syms_of_xfaces (void)
   DEFSYM (Qwave, "wave");
   DEFSYM (Qreleased_button, "released-button");
   DEFSYM (Qpressed_button, "pressed-button");
+#ifdef HAVE_GTK4
+  DEFSYM (Qgtk_pressed_button, "gtk-pressed-button");
+  DEFSYM (Qgtk_released_button, "gtk-released-button");
+  DEFSYM (Qgtk_prelight_button, "gtk-prelight-button");
+  DEFSYM (Qgtk_checked_checkbox, "gtk-checked-checkbox");
+  DEFSYM (Qgtk_unchecked_checkbox, "gtk-unchecked-checkbox");
+  DEFSYM (Qgtk_unselected_entry, "gtk-unselected-entry");
+  DEFSYM (Qgtk_selected_entry, "gtk-selected-entry");
+#endif
   DEFSYM (Qnormal, "normal");
   DEFSYM (Qextra_light, "extra-light");
   DEFSYM (Qlight, "light");
diff --git a/src/xfns.c b/src/xfns.c
index 1f381e2a8b..89d6aa07f9 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -2022,6 +2022,11 @@ x_set_name_internal (struct frame *f, Lisp_Object name)
 #ifdef USE_GTK
         gtk_window_set_title (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
                               SSDATA (encoded_name));
+#ifdef GDK_VERSION_3_12
+	GtkHeaderBar *hb = FRAME_GTK_HEADER_BAR (f);
+	gtk_header_bar_set_has_subtitle (hb, true);
+	gtk_header_bar_set_subtitle (hb, !NILP (f->name) ? SSDATA (f->name) : NULL);
+#endif
 #else /* not USE_GTK */
 	XSetWMName (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), &text);
 	XChangeProperty (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f),
diff --git a/src/xgselect.c b/src/xgselect.c
index f8d0bac7fa..285d370010 100644
--- a/src/xgselect.c
+++ b/src/xgselect.c
@@ -23,6 +23,7 @@ Copyright (C) 2009-2020 Free Software Foundation, Inc.
 
 #ifdef HAVE_GLIB
 
+#include <stdio.h>
 #include <glib.h>
 #include <errno.h>
 #include "lisp.h"
diff --git a/src/xmenu.c b/src/xmenu.c
index 9201a283b4..56852688e8 100644
--- a/src/xmenu.c
+++ b/src/xmenu.c
@@ -923,7 +923,7 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p)
                                  G_CALLBACK (popup_deactivate_callback),
                                  G_CALLBACK (menu_highlight_callback));
     }
-  else
+  else if (FRAME_EXTERNAL_MENU_BAR (f))
     {
       menubar_widget
         = xg_create_widget ("menubar", "menubar", f, first_wv,
diff --git a/src/xsettings.c b/src/xsettings.c
index 1ba1021e40..a7b9b20380 100644
--- a/src/xsettings.c
+++ b/src/xsettings.c
@@ -26,7 +26,15 @@ Copyright (C) 2009-2020 Free Software Foundation, Inc.
 #include <byteswap.h>
 
 #include "lisp.h"
+#ifndef HAVE_PGTK
 #include "xterm.h"
+#else
+#ifndef HAVE_GTK4
+#include "gtkutil.h"
+#else
+#include "pgtksubr.h"
+#endif
+#endif
 #include "xsettings.h"
 #include "frame.h"
 #include "keyboard.h"
@@ -34,7 +42,12 @@ Copyright (C) 2009-2020 Free Software Foundation, Inc.
 #include "termhooks.h"
 #include "pdumper.h"
 
+#ifndef HAVE_PGTK
 #include <X11/Xproto.h>
+#else
+typedef unsigned short CARD16;
+typedef unsigned int CARD32;
+#endif
 
 #ifdef HAVE_GSETTINGS
 #include <glib-object.h>
@@ -55,7 +68,7 @@ Copyright (C) 2009-2020 Free Software Foundation, Inc.
 
 static char *current_mono_font;
 static char *current_font;
-static struct x_display_info *first_dpyinfo;
+static Display_Info *first_dpyinfo;
 static Lisp_Object current_tool_bar_style;
 
 /* Store a config changed event in to the event queue.  */
@@ -73,14 +86,18 @@ store_config_changed_event (Lisp_Object arg, Lisp_Object display_name)
 
 /* Return true if DPYINFO is still valid.  */
 static bool
-dpyinfo_valid (struct x_display_info *dpyinfo)
+dpyinfo_valid (Display_Info *dpyinfo)
 {
   bool found = false;
   if (dpyinfo != NULL)
     {
-      struct x_display_info *d;
+      Display_Info *d;
       for (d = x_display_list; !found && d; d = d->next)
+#ifndef HAVE_PGTK
         found = d == dpyinfo && d->display == dpyinfo->display;
+#else
+        found = d == dpyinfo && d->gdpy == dpyinfo->gdpy;
+#endif
     }
   return found;
 }
@@ -149,7 +166,7 @@ map_tool_bar_style (const char *tool_bar_style)
 
 static void
 store_tool_bar_style_changed (const char *newstyle,
-                              struct x_display_info *dpyinfo)
+                              Display_Info *dpyinfo)
 {
   Lisp_Object style = map_tool_bar_style (newstyle);
   if (EQ (current_tool_bar_style, style))
@@ -161,10 +178,12 @@ store_tool_bar_style_changed (const char *newstyle,
                                 XCAR (dpyinfo->name_list_element));
 }
 
+#ifndef HAVE_PGTK
 #if defined USE_CAIRO || defined HAVE_XFT
 #define XSETTINGS_FONT_NAME       "Gtk/FontName"
 #endif
 #define XSETTINGS_TOOL_BAR_STYLE  "Gtk/ToolbarStyle"
+#endif
 
 enum {
   SEEN_AA         = 0x01,
@@ -321,10 +340,11 @@ #define FC_LCD_FILTER "lcdfilter"
 
 #endif /* USE_CAIRO || HAVE_XFT */
 
+#ifndef HAVE_PGTK
 /* Find the window that contains the XSETTINGS property values.  */
 
 static void
-get_prop_window (struct x_display_info *dpyinfo)
+get_prop_window (Display_Info *dpyinfo)
 {
   Display *dpy = dpyinfo->display;
 
@@ -339,6 +359,9 @@ get_prop_window (struct x_display_info *dpyinfo)
 
   XUngrabServer (dpy);
 }
+#endif
+
+#ifndef HAVE_PGTK
 
 #define PAD(nr)    (((nr) + 3) & ~3)
 
@@ -566,13 +589,15 @@ parse_settings (unsigned char *prop,
 
   return settings_seen;
 }
+#endif
 
+#ifndef HAVE_PGTK
 /* Read settings from the XSettings property window on display for DPYINFO.
    Store settings read in SETTINGS.
    Return true iff successful.  */
 
 static bool
-read_settings (struct x_display_info *dpyinfo, struct xsettings *settings)
+read_settings (Display_Info *dpyinfo, struct xsettings *settings)
 {
   Atom act_type;
   int act_form;
@@ -600,12 +625,14 @@ read_settings (struct x_display_info *dpyinfo, struct xsettings *settings)
 
   return got_settings;
 }
+#endif
 
+#ifndef HAVE_PGTK
 /* Apply Xft settings in SETTINGS to the Xft library.
    Store a Lisp event that Xft settings changed.  */
 
 static void
-apply_xft_settings (struct x_display_info *dpyinfo,
+apply_xft_settings (Display_Info *dpyinfo,
                     struct xsettings *settings)
 {
 #ifdef HAVE_XFT
@@ -731,12 +758,14 @@ apply_xft_settings (struct x_display_info *dpyinfo,
     FcPatternDestroy (pat);
 #endif /* HAVE_XFT */
 }
+#endif
 
+#ifndef HAVE_PGTK
 /* Read XSettings from the display for DPYINFO.
    If SEND_EVENT_P store a Lisp event settings that changed.  */
 
 static void
-read_and_apply_settings (struct x_display_info *dpyinfo, bool send_event_p)
+read_and_apply_settings (Display_Info *dpyinfo, bool send_event_p)
 {
   struct xsettings settings;
 
@@ -763,11 +792,13 @@ read_and_apply_settings (struct x_display_info *dpyinfo, bool send_event_p)
     }
 #endif
 }
+#endif
 
+#ifndef HAVE_PGTK
 /* Check if EVENT for the display in DPYINFO is XSettings related.  */
 
 void
-xft_settings_event (struct x_display_info *dpyinfo, const XEvent *event)
+xft_settings_event (Display_Info *dpyinfo, const XEvent *event)
 {
   bool check_window_p = false, apply_settings_p = false;
 
@@ -805,6 +836,7 @@ xft_settings_event (struct x_display_info *dpyinfo, const XEvent *event)
   if (apply_settings_p)
     read_and_apply_settings (dpyinfo, true);
 }
+#endif
 
 /* Initialize GSettings and read startup values.  */
 
@@ -940,10 +972,11 @@ init_gconf (void)
 #endif /* HAVE_GCONF */
 }
 
+#ifndef HAVE_PGTK
 /* Init Xsettings and read startup values.  */
 
 static void
-init_xsettings (struct x_display_info *dpyinfo)
+init_xsettings (Display_Info *dpyinfo)
 {
   Display *dpy = dpyinfo->display;
 
@@ -959,13 +992,16 @@ init_xsettings (struct x_display_info *dpyinfo)
 
   unblock_input ();
 }
+#endif
 
 void
-xsettings_initialize (struct x_display_info *dpyinfo)
+xsettings_initialize (Display_Info *dpyinfo)
 {
   if (first_dpyinfo == NULL) first_dpyinfo = dpyinfo;
   init_gconf ();
+#ifndef HAVE_PGTK
   init_xsettings (dpyinfo);
+#endif
   init_gsettings ();
 }
 
diff --git a/src/xsettings.h b/src/xsettings.h
index f29ce77c7f..1d2f1c69c9 100644
--- a/src/xsettings.h
+++ b/src/xsettings.h
@@ -20,12 +20,23 @@ Copyright (C) 2009-2020 Free Software Foundation, Inc.
 #ifndef XSETTINGS_H
 #define XSETTINGS_H
 
+#ifndef HAVE_PGTK
 #include <X11/Xlib.h>
+#endif
 
 struct x_display_info;
+struct pgtk_display_info;
+
+#ifndef HAVE_PGTK
+typedef struct x_display_info Display_Info;
+#else
+typedef struct pgtk_display_info Display_Info;
+#endif
 
-extern void xsettings_initialize (struct x_display_info *);
-extern void xft_settings_event (struct x_display_info *, const XEvent *);
+extern void xsettings_initialize (Display_Info *);
+#ifndef HAVE_PGTK
+extern void xft_settings_event (Display_Info *, const XEvent *);
+#endif
 extern const char *xsettings_get_system_font (void);
 #ifdef USE_LUCID
 extern const char *xsettings_get_system_normal_font (void);
diff --git a/src/xterm.h b/src/xterm.h
index bc10043c54..a99d50952b 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -563,6 +563,10 @@ #define MAX_CLIP_RECTS 2
 #ifdef USE_GTK
   /* The widget of this screen.  This is the window of a top widget.  */
   GtkWidget *widget;
+
+#ifdef HAVE_GTK3
+  GtkHeaderBar *header_bar;
+#endif
   /* The widget of the edit portion of this screen; the window in
      "window_desc" is inside of this.  */
   GtkWidget *edit_widget;
@@ -802,6 +806,10 @@ #define FRAME_OUTER_WINDOW(f)                                   \
         GTK_WIDGET_TO_X_WIN (FRAME_GTK_OUTER_WIDGET (f)) :      \
          FRAME_X_WINDOW (f))
 
+#ifdef HAVE_GTK3
+#define FRAME_GTK_HEADER_BAR(f) ((f)->output_data.x->header_bar)
+#endif
+
 #else /* !USE_GTK */
 #define FRAME_OUTER_WINDOW(f) (FRAME_X_WINDOW (f))
 #endif /* !USE_GTK */

^ permalink raw reply related	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
@ 2020-04-27 11:54 Jeff Walsh
  2020-04-28  8:32 ` martin rudalics
  0 siblings, 1 reply; 90+ messages in thread
From: Jeff Walsh @ 2020-04-27 11:54 UTC (permalink / raw)
  To: rudalics; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1576 bytes --]


> Thank you!
> 
> Building on Debian with GTK+ Version 3.24.5, cairo version 1.16.0, and
> gcc (Debian 8.3.0-6) 8.3.0 configured as
> 
> CFLAGS='-O0 -g3 -no-pie' ../configure --without-x --with-cairo
> --with-modules --with-gif=ifavailable --with-tiff=ifavailable
> --with-gnutls=no --without-pop --enable-gcc-warnings=warn-only
> --enable-checking=yes --enable-check-lisp-object-type=yes
> 
> fails here thusly:
> 
>   CC       pgtkterm.o
> ../../src/pgtkterm.c: In function ‘mark_pgtkterm’:
> ../../src/pgtkterm.c:168:25: error: incompatible type for argument 1 
> of
> ‘mark_object’
>      mark_object (dpyinfo->rdb);
>                   ~~~~~~~^~~~~
> In file included from ../../src/pgtkterm.c:39:
> ../../src/lisp.h:3785:26: note: expected ‘Lisp_Object’ {aka 
> ‘struct
> Lisp_Object’} but argument is of type ‘XrmDatabase’ {aka 
> ‘void *’}
>  extern void mark_object (Lisp_Object);
>                           ^~~~~~~~~~~
> make[1]: *** [Makefile:405: pgtkterm.o] Fehler 1
> make[1]: *** Es wird auf noch nicht beendete Prozesse gewartet....
> make[1]: Verzeichnis 
> „/home/martin/emacs-git/masm/obj-pure-gtk/src“ wird
> verlassen
> make: *** [Makefile:431: src] Fehler 2
> 
> Any ideas?

Hi Martin,

I had a look at this, it seems that the line can be deleted.

I have a PR up on Yuuki Harano's Repo, or you can grab a cleaned up 
version here:
<https://github.com/fejfighter/emacs/tree/pgtk-xrdb-mark>

it seems to compile and run without error using the flags you provided.

Hope that helps,

Jeff Walsh






[-- Attachment #2: Type: text/html, Size: 3999 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26 14:01 ` Eli Zaretskii
@ 2020-04-27 12:37   ` Yuuki Harano
  2020-04-27 15:03     ` Eli Zaretskii
  2020-04-27 15:35     ` Yuuki Harano
  2020-11-17 14:50   ` Yuuki Harano
  1 sibling, 2 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-27 12:37 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel

Thank you.

On Sun, 26 Apr 2020 17:01:33 +0300,
	Eli Zaretskii <eliz@gnu.org> wrote:
>   . You don't seem to have a copyright assignment on file.  This would
>     be a significant contribution to Emacs, for which we must have
>     such an assignment from you before bringing this code into the
>     Emacs repository.  Would you be willing to start the legal
>     paperwork now?  If so, I will send you the form to fill.

Yes.  Please send me it.

By the way, this fork contains much code written by @fejfighter.
He said "for now: Yes I do agree to assign my code to the FSF." here,
https://github.com/masm11/emacs/pull/11#issuecomment-600856858 .
What to do?

>   . The code seem to be based on an relatively old version of our
>     master branch, which makes it hard to review (there are many
>     spurious changes unrelated to your work).  Please rebase on the
>     latest HEAD of the master branch.

OK, I'll rebase later.

>   . Would it be possible for you to describe the design of this
>     feature, and how that affects the various Emacs features, so that
>     understanding the changes would be facilitated?  In particular,
>     can this new window-system live together with X and TTY frames in
>     the same session? does it support Lisp threads? etc.  Also, what
>     are the requirements from the platforms where this could be built
>     and used?

I started porting to pure Gtk3 about 2 years ago.
I referred NS's code because X's is too complex.
But I ported text rendering code from X afterwards.
There are old codes because I can't follow NS and X code changes.

Because I was not going to merge to mainline when I started porting,
older commit messages are in Japanese.  If you don't like Japanese
messages, I can make one big commit instead of existing commits.

Since pgtk emacs is configured with '--without-x', existing X code
is disabled.  If configured with '--with-x', the existing X support
should be enabled as before.

Pgtk emacs supports X window system too through Gtk library.
It can handle Wayland, X window system, and TTY in the same session.
But segmentation fault may occur when running on X and Wayland
in the same session.

Since Gtk does not support these functions on Wayland, they don't work
on Wayland.  On X, they should work.
  - x_set_no_focus_on_map
  - x_set_no_accept_focus
  - x_set_z_group
  - raise/lower
  - gtk_plug
Since Gtk does not support this function, it doesn't work, even if on X.
  - vendor_specific_keysyms

I don't know about Lisp threads.  I have never supported it explicitly.
Pgtk emacs may not support it.

Pgtk emacs:
- needs '--with-cairo'.
- supports menubar, toolbar, scrollbar, tabbar, and fringe.
- supports stipple.
- supports childframe.

Pgtk emacs uses cairo.  It prepares a surface before rendering, render
on it, and copy it on the window on a Gtk 'draw' event.

X-gtk emacs uses gtkutil.c.  Pgtk emacs also uses it.

Since Gtk doesn't support xrdb, I implemented alternative using GNOME's gsettings.
  (pgtk-set-resource "background" "gray")
  (x-get-resource "background" "Background")
Xrdb is not supported even if on X.

The prefixes of functions and variables: 'pgtk-' and 'x-'.
I don't have rules about which should be used for each of functions and variables.

Gtk supports w32, but I have never tested on w32.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 12:37   ` Yuuki Harano
@ 2020-04-27 15:03     ` Eli Zaretskii
  2020-04-28 13:42       ` Yuuki Harano
  2020-04-27 15:35     ` Yuuki Harano
  1 sibling, 1 reply; 90+ messages in thread
From: Eli Zaretskii @ 2020-04-27 15:03 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

> Date: Mon, 27 Apr 2020 21:37:27 +0900 (JST)
> Cc: emacs-devel@gnu.org
> From: Yuuki Harano <masm+emacs@masm11.me>
> 
> >   . You don't seem to have a copyright assignment on file.  This would
> >     be a significant contribution to Emacs, for which we must have
> >     such an assignment from you before bringing this code into the
> >     Emacs repository.  Would you be willing to start the legal
> >     paperwork now?  If so, I will send you the form to fill.
> 
> Yes.  Please send me it.

Form sent off-list.

> By the way, this fork contains much code written by @fejfighter.
> He said "for now: Yes I do agree to assign my code to the FSF." here,
> https://github.com/masm11/emacs/pull/11#issuecomment-600856858 .
> What to do?

He should fill the form I sent to you, separately, and email it
according to instructions.

> Because I was not going to merge to mainline when I started porting,
> older commit messages are in Japanese.  If you don't like Japanese
> messages, I can make one big commit instead of existing commits.

That's probably the best.  But there's time before that happens, and
you can meanwhile keep the original log messages while the code is on
the branch.

> Since pgtk emacs is configured with '--without-x', existing X code
> is disabled.  If configured with '--with-x', the existing X support
> should be enabled as before.

Would configuring --with-x disable Pgtk support code?  That is, do the
X and Pgtk support contradict each other, and cannot live in the same
binary?  Or maybe I don't have a clear idea what exactly gets disabled
when building with Pgtk -- can you elaborate?

> Pgtk emacs supports X window system too through Gtk library.
> It can handle Wayland, X window system, and TTY in the same session.
> But segmentation fault may occur when running on X and Wayland
> in the same session.

I guess those segfaults need to be fixed, because having a GUI Emacs
that can only run on Wayland would be a limitation that users might be
unhappy about?

> Since Gtk does not support these functions on Wayland, they don't work
> on Wayland.  On X, they should work.
>   - x_set_no_focus_on_map
>   - x_set_no_accept_focus
>   - x_set_z_group
>   - raise/lower
>   - gtk_plug

Someone who uses Emacs a lot on X should chime in and tell whether the
lack of these features is a serious flaw or just a minor
inconvenience.

> Since Gtk does not support this function, it doesn't work, even if on X.
>   - vendor_specific_keysyms

Can you give examples of these keysyms, and say something about their
popularity?

> I don't know about Lisp threads.  I have never supported it explicitly.
> Pgtk emacs may not support it.

Well, for starters see if test/src/thread-tests.el runs and succeeds
in your Pgtk build.

> Gtk supports w32, but I have never tested on w32.

This would be nice, but is much less important, IMO.  The most
important task is to keep users of Posix systems happy with the Pgtk
build.

Thanks.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 12:37   ` Yuuki Harano
  2020-04-27 15:03     ` Eli Zaretskii
@ 2020-04-27 15:35     ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-27 15:35 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel


On Mon, 27 Apr 2020 21:37:27 +0900 (JST),
	Yuuki Harano <masm+emacs@masm11.me> wrote:
>>   . The code seem to be based on an relatively old version of our
>>     master branch, which makes it hard to review (there are many
>>     spurious changes unrelated to your work).  Please rebase on the
>>     latest HEAD of the master branch.
> 
> OK, I'll rebase later.

I rebased to master of emacs-mirror/emacs.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26 18:00 ` martin rudalics
  2020-04-26 18:43   ` Stefan Monnier
@ 2020-04-27 15:43   ` Yuuki Harano
  2020-04-28  8:32     ` martin rudalics
  1 sibling, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-04-27 15:43 UTC (permalink / raw)
  To: rudalics; +Cc: emacs-devel

Hi,

On Sun, 26 Apr 2020 20:00:23 +0200,
	martin rudalics <rudalics@gmx.at> wrote:
> ../../src/pgtkterm.c: In function ‘mark_pgtkterm’:
> ../../src/pgtkterm.c:168:25: error: incompatible type for argument 1
> of ‘mark_object’
>      mark_object (dpyinfo->rdb);
>                   ~~~~~~~^~~~~
> In file included from ../../src/pgtkterm.c:39:
> ../../src/lisp.h:3785:26: note: expected ‘Lisp_Object’ {aka ‘struct
> Lisp_Object’} but argument is of type ‘XrmDatabase’ {aka ‘void
> *’}
>  extern void mark_object (Lisp_Object);
>                           ^~~~~~~~~~~

That was fixed.  Would you retry?

Thank you.
-- 
Yuuki Harano

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27  8:37 ` Po Lu via Emacs development discussions.
@ 2020-04-27 16:08   ` Yuuki Harano
  2020-04-27 23:47     ` Po Lu
  2020-04-29  6:28   ` Po Lu
  1 sibling, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-04-27 16:08 UTC (permalink / raw)
  To: luangruo; +Cc: emacs-devel


On Mon, 27 Apr 2020 16:37:01 +0800,
	Po Lu <luangruo@yahoo.com> wrote:
> I was maintaining some separate work a while ago, but I moved some of my
> work on top of yours some time ago.  It would be nice if we could work
> together.

Of course welcome!

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 16:08   ` Yuuki Harano
@ 2020-04-27 23:47     ` Po Lu
  2020-04-27 23:49       ` Po Lu
  2020-04-28  0:05       ` Dmitry Gutov
  0 siblings, 2 replies; 90+ messages in thread
From: Po Lu @ 2020-04-27 23:47 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

>> I was maintaining some separate work a while ago, but I moved some of my
>> work on top of yours some time ago.  It would be nice if we could work
>> together.

I'll upload it to my sourcehut (git.sr.ht/~oldosfan).

[discussion taken off-list since I'm not sure if this is relevant to
emacs-devel]



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 23:47     ` Po Lu
@ 2020-04-27 23:49       ` Po Lu
  2020-04-28  0:05       ` Dmitry Gutov
  1 sibling, 0 replies; 90+ messages in thread
From: Po Lu @ 2020-04-27 23:49 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Yuuki Harano <masm+emacs@masm11.me> writes:
>
>>> I was maintaining some separate work a while ago, but I moved some of my
>>> work on top of yours some time ago.  It would be nice if we could work
>>> together.
>
> I'll upload it to my sourcehut (git.sr.ht/~oldosfan).
>
> [discussion taken off-list since I'm not sure if this is relevant to
> emacs-devel]

Ah and I was somewhat careless and forgot to remove the CC field.
I would appreciate it if you would take the discussion off-list later.
Thanks.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 23:47     ` Po Lu
  2020-04-27 23:49       ` Po Lu
@ 2020-04-28  0:05       ` Dmitry Gutov
  2020-04-28  6:08         ` Po Lu
  1 sibling, 1 reply; 90+ messages in thread
From: Dmitry Gutov @ 2020-04-28  0:05 UTC (permalink / raw)
  To: Po Lu, Yuuki Harano; +Cc: emacs-devel

On 28.04.2020 02:47, Po Lu wrote:
> [discussion taken off-list since I'm not sure if this is relevant to
> emacs-devel]

Maybe the details are not 100% essential, but if you're talking about 
new features for the pgtk branch, people might be interested anyway.

More importantly, if you're going to contribute to the pgtk branch, a 
copyright assignment from you will also be required.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
                   ` (4 preceding siblings ...)
  2020-04-27  8:37 ` Po Lu via Emacs development discussions.
@ 2020-04-28  0:51 ` Daniele Nicolodi
  2020-04-29  1:14 ` Andrew Cohen
  2020-04-29 13:01 ` Robert Pluim
  7 siblings, 0 replies; 90+ messages in thread
From: Daniele Nicolodi @ 2020-04-28  0:51 UTC (permalink / raw)
  To: Yuuki Harano, emacs-devel

On 26-04-2020 01:56, Yuuki Harano wrote:
> Hi,
> 
> You may know, I ported emacs for pure Gtk3, especially for wayland native.

Hello Yuuki,

a while ago I exchanged a couple of emails with Alex Gramiak about his
effort to write a pure Gtk terminal for Emacs. At the time of the last
email I have from him, May 2019, he had a working prototype, but I
suspect his efforts stalled. He may be interested in collaborating on
this effort.

Cheers,
Dan



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
@ 2020-04-28  3:19 Jeff Walsh
  2020-04-28  7:27 ` Eli Zaretskii
  2020-05-08  6:54 ` Jostein Kjønigsen
  0 siblings, 2 replies; 90+ messages in thread
From: Jeff Walsh @ 2020-04-28  3:19 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 4244 bytes --]

> 
*From*: Eli Zaretskii *Subject*: Re: emacs for pure Gtk3 *Date*: Mon, 
27 Apr 2020 18:03:54 +0300
> >/ Date: Mon, 27 Apr 2020 21:37:27 +0900 (JST)/
> >/ Cc: address@hidden/
> >/ From: Yuuki Harano <address@hidden>/
> >/ /
> >/ >   . You don't seem to have a copyright assignment on file.  This 
> would/
> >/ >     be a significant contribution to Emacs, for which we must 
> have/
> >/ >     such an assignment from you before bringing this code into 
> the/
> >/ >     Emacs repository.  Would you be willing to start the legal/
> >/ >     paperwork now?  If so, I will send you the form to fill./
> >/ /
> >/ Yes.  Please send me it./
> 
> Form sent off-list.
> 
> >/ By the way, this fork contains much code written by @fejfighter./
> >/ He said "for now: Yes I do agree to assign my code to the FSF." 
> here,/
> >/ <https://github.com/masm11/emacs/pull/11#issuecomment-600856858> ./
> >/ What to do?/
> 
> He should fill the form I sent to you, separately, and email it
> according to instructions.

No worries, happy to do so.

> 
> >/ Because I was not going to merge to mainline when I started 
> porting,/
> >/ older commit messages are in Japanese.  If you don't like Japanese/
> >/ messages, I can make one big commit instead of existing commits./
> 
> That's probably the best.  But there's time before that happens, and
> you can meanwhile keep the original log messages while the code is on
> the branch.
> 
> >/ Since pgtk emacs is configured with '--without-x', existing X code/
> >/ is disabled.  If configured with '--with-x', the existing X 
> support/
> >/ should be enabled as before./
> 
> Would configuring --with-x disable Pgtk support code?  That is, do the
> X and Pgtk support contradict each other, and cannot live in the same
> binary?  Or maybe I don't have a clear idea what exactly gets disabled
> when building with Pgtk -- can you elaborate?
> 
/I think this may need a little more work in configure.ac/
/In essence it's not that different to --with-ns or --with-w32./
/it just happens to re-use a chunk of the gtkutil.c code where 
possible./
/
/
/Effectively it selects pgtkterm.h instead of xterm.h/



> >/ Pgtk emacs supports X window system too through Gtk library./
> >/ It can handle Wayland, X window system, and TTY in the same 
> session./
> >/ But segmentation fault may occur when running on X and Wayland/
> >/ in the same session./
> 
> I guess those segfaults need to be fixed, because having a GUI Emacs
> that can only run on Wayland would be a limitation that users might be
> unhappy about?

it will run on wayland or xwayland or X11 from the same binary, but not 
on wayland and X11 concurrently.

I'm not sure of a use-case for this, but I'm hoping someone is able to 
provide one.

> 
> >/ I don't know about Lisp threads.  I have never supported it 
> explicitly./
> >/ Pgtk emacs may not support it./
> 
> Well, for starters see if test/src/thread-tests.el runs and succeeds
> in your Pgtk build.

I get:

make lisp/thread-tests
make[1]: Entering directory '/home/fejfighter/dev/emacs-gtk/test'
  GEN      lisp/thread-tests.log
Running 3 tests (2020-04-28 12:52:55+1000, selector `(not (tag 
:unstable))')
  skipped  1/3  thread-tests-list-threads-error-when-not-configured 
(0.000133 sec)
   passed  2/3  thread-tests-thread-list-send-error (0.000488 sec)
   passed  3/3  thread-tests-thread-list-show-backtrace (0.014913 sec)

Ran 3 tests, 2 results as expected, 0 unexpected, 1 skipped (2020-04-28 
12:52:55+1000, 0.015803 sec)

1 skipped results:
  SKIPPED  thread-tests-list-threads-error-when-not-configured

make[1]: Leaving directory '/home/fejfighter/dev/emacs-gtk/test'


which matches a master build checkout from 
e49d3a45cd4a0554aa98c45f0976ed513c500951 (approx 1300 Aus EST)



> 
> >/ Gtk supports w32, but I have never tested on w32./
> 
> This would be nice, but is much less important, IMO.  The most
> important task is to keep users of Posix systems happy with the Pgtk
> build.
> 
> Thanks.

FWIW, this seems to work fine with gtk-broadway, the in-browser 
implementation of GTK3, aside from keystroke-clashes with firefox.
it may be some indication of a minimally "X'd" emacs, but a w32 version 
would be better proof.

Regards,

Jeff Walsh
(fejfighter)






[-- Attachment #2: Type: text/html, Size: 7903 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28  0:05       ` Dmitry Gutov
@ 2020-04-28  6:08         ` Po Lu
  2020-04-28  7:37           ` Eli Zaretskii
  0 siblings, 1 reply; 90+ messages in thread
From: Po Lu @ 2020-04-28  6:08 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Yuuki Harano, emacs-devel

Dmitry Gutov <dgutov@yandex.ru> writes:

> Maybe the details are not 100% essential, but if you're talking about
> new features for the pgtk branch, people might be interested anyway.
>
> More importantly, if you're going to contribute to the pgtk branch, a
> copyright assignment from you will also be required.

I'm aware of that, and I'm willing to sign the relevant papers.
Perhaps the papers could be sent to my inbox? thanks



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28  3:19 Jeff Walsh
@ 2020-04-28  7:27 ` Eli Zaretskii
  2020-05-08  6:54 ` Jostein Kjønigsen
  1 sibling, 0 replies; 90+ messages in thread
From: Eli Zaretskii @ 2020-04-28  7:27 UTC (permalink / raw)
  To: Jeff Walsh; +Cc: emacs-devel

> Date: Tue, 28 Apr 2020 13:19:04 +1000
> From: Jeff Walsh <fejfighter@gmail.com>
> Cc: emacs-devel@gnu.org
> 
>  > Pgtk emacs supports X window system too through Gtk library.
> > It can handle Wayland, X window system, and TTY in the same session.
> > But segmentation fault may occur when running on X and Wayland
> > in the same session.
> 
> I guess those segfaults need to be fixed, because having a GUI Emacs
> that can only run on Wayland would be a limitation that users might be
> unhappy about?
> 
> it will run on wayland or xwayland or X11 from the same binary, but not on wayland and X11 concurrently.
> 
> I'm not sure of a use-case for this, but I'm hoping someone is able to provide one.

The use case is running Emacs on a system with Wayland, and opening a
frame on a remote host where there's no Wayland and only "normal" X
frames can be used.

> > I don't know about Lisp threads.  I have never supported it explicitly.
> > Pgtk emacs may not support it.
> 
> Well, for starters see if test/src/thread-tests.el runs and succeeds
> in your Pgtk build.
> 
> I get:
> 
> make lisp/thread-tests make[1]: Entering directory '/home/fejfighter/dev/emacs-gtk/test' GEN
> lisp/thread-tests.log Running 3 tests (2020-04-28 12:52:55+1000, selector `(not (tag :unstable))') skipped 1/3
> thread-tests-list-threads-error-when-not-configured (0.000133 sec) passed 2/3
> thread-tests-thread-list-send-error (0.000488 sec) passed 3/3 thread-tests-thread-list-show-backtrace
> (0.014913 sec) Ran 3 tests, 2 results as expected, 0 unexpected, 1 skipped (2020-04-28 12:52:55+1000,
> 0.015803 sec) 1 skipped results: SKIPPED thread-tests-list-threads-error-when-not-configured make[1]:
> Leaving directory '/home/fejfighter/dev/emacs-gtk/test' 
> 
> which matches a master build checkout from e49d3a45cd4a0554aa98c45f0976ed513c500951 (approx 1300
> Aus EST)

That's a different test.  I meant src/thread-tests, not
lisp/thread-tests.

Thanks.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28  6:08         ` Po Lu
@ 2020-04-28  7:37           ` Eli Zaretskii
  2020-04-29  6:13             ` Po Lu
  0 siblings, 1 reply; 90+ messages in thread
From: Eli Zaretskii @ 2020-04-28  7:37 UTC (permalink / raw)
  To: Po Lu; +Cc: masm+emacs, emacs-devel, dgutov

> From: Po Lu <luangruo@yahoo.com>
> Cc: Yuuki Harano <masm+emacs@masm11.me>,  emacs-devel@gnu.org
> Date: Tue, 28 Apr 2020 14:08:16 +0800
> 
> Dmitry Gutov <dgutov@yandex.ru> writes:
> 
> > Maybe the details are not 100% essential, but if you're talking about
> > new features for the pgtk branch, people might be interested anyway.
> >
> > More importantly, if you're going to contribute to the pgtk branch, a
> > copyright assignment from you will also be required.
> 
> I'm aware of that, and I'm willing to sign the relevant papers.
> Perhaps the papers could be sent to my inbox? thanks

Thanks, I sent the form off-list.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 11:54 Jeff Walsh
@ 2020-04-28  8:32 ` martin rudalics
  0 siblings, 0 replies; 90+ messages in thread
From: martin rudalics @ 2020-04-28  8:32 UTC (permalink / raw)
  To: Jeff Walsh; +Cc: emacs-devel

 > I had a look at this, it seems that the line can be deleted.
 >
 > I have a PR up on Yuuki Harano's Repo, or you can grab a cleaned up version here:
 > <https://github.com/fejfighter/emacs/tree/pgtk-xrdb-mark>
 >
 > it seems to compile and run without error using the flags you provided.

Thanks for the offer.  Pulling again from Yuuki's repository has fixed
this particular issue.

martin



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 15:43   ` Yuuki Harano
@ 2020-04-28  8:32     ` martin rudalics
  0 siblings, 0 replies; 90+ messages in thread
From: martin rudalics @ 2020-04-28  8:32 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

 > That was fixed.  Would you retry?

It's fixed, indeed.  Some random issues I've found are below.  Please
ask me further details, I did not delve too deeply into this so far.


A few trivial warnings I've seen while building:

../../lisp/term/pgtk-win.el:7:13: Warning: ‘invocation-name’ is an obsolete
     function (as of 27.1); use the variable of the same name.
../../lisp/term/pgtk-win.el:111:1: Warning: Alias for
     ‘pgtk-alternate-modifier’ should be declared before its referent
../../lisp/term/pgtk-win.el:112:1: Warning: Alias for
     ‘pgtk-right-alternate-modifier’ should be declared before its referent
../../lisp/term/pgtk-win.el:309:32: Warning: ‘invocation-name’ is an obsolete
     function (as of 27.1); use the variable of the same name.

../../lisp/server.el:871:1: Warning: Unused lexical variable ‘x’


When running an uninstalled "GdkX11Display" Emacs:

(1) The standard Emacs icon apparently does not get produced (at least
not for the task bar entry).

(2) Instead of the Emacs-Lisp menu entry I get an empty Lisp-Interaction
menu initially.  It eventually changes its name to Emacs-Lisp but remains
empty.  After a while it apparently fixes itself auto-magically.

(3) When starting Emacs maximized I cannot put the Emacs window back to
its "normal" size (neither via the squares in the title bar nor by
toggling the maximized state).  Things work when I start Emacs with a
normal size window.


The most important thing for me, at the moment, seems to convert your new
code to GNU style before putting it into an Emacs branch.  I suppose GNU
indent should handle that but I never used it ...

Many thanks for all the work, martin




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27 15:03     ` Eli Zaretskii
@ 2020-04-28 13:42       ` Yuuki Harano
  2020-04-28 14:34         ` Eli Zaretskii
  2020-04-29  6:16         ` Po Lu
  0 siblings, 2 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-28 13:42 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel


On Mon, 27 Apr 2020 18:03:54 +0300,
	Eli Zaretskii <eliz@gnu.org> wrote:
> Form sent off-list.

Thank you.

>> By the way, this fork contains much code written by @fejfighter.
>> He said "for now: Yes I do agree to assign my code to the FSF." here,
>> https://github.com/masm11/emacs/pull/11#issuecomment-600856858 .
>> What to do?
> 
> He should fill the form I sent to you, separately, and email it
> according to instructions.

Thank you.

>> Since pgtk emacs is configured with '--without-x', existing X code
>> is disabled.  If configured with '--with-x', the existing X support
>> should be enabled as before.
> 
> Would configuring --with-x disable Pgtk support code?  That is, do the
> X and Pgtk support contradict each other, and cannot live in the same
> binary?  Or maybe I don't have a clear idea what exactly gets disabled
> when building with Pgtk -- can you elaborate?

X code and pgtk code contradict.  Not "support", but "code".
Pgtk emacs supports X.

I wrote:
> > If configured with '--with-x', the existing X support
> > should be enabled as before.

I'm sorry.  That should have been:
| If configured with '--with-x', the existing X code
| should be enabled as before.

Since it needs --without-x to build pgtk emacs, these files are not compiled:
- xfns.c
- xgselect.c
- xmenu.c
- xrdb.c
- xselect.c
- xsmfns.c
- xterm.c

Instead, works in those files are done in these files in pure gtk way:
- pgtkfns.c
- pgtkim.c
- pgtkmenu.c
- pgtkselect.c
- pgtkterm.c

Both of pgtk emacs and X emacs use gtkutil.c.  It contains many
"#ifdef HAVE_PGTK" to decide which it is being compiled for.

Since there are not lucid, xaw, and motif in the pure gtk world,
they are not supported in pgtk emacs.

>> Pgtk emacs supports X window system too through Gtk library.
>> It can handle Wayland, X window system, and TTY in the same session.
>> But segmentation fault may occur when running on X and Wayland
>> in the same session.
> 
> I guess those segfaults need to be fixed, because having a GUI Emacs
> that can only run on Wayland would be a limitation that users might be
> unhappy about?

Yes, I think so, too.

I remember that Emacs has previously an issue when multiple display
environments.  I thought that the segfaults was the same issue.
Was that fixed?

>> Since Gtk does not support this function, it doesn't work, even if on X.
>>   - vendor_specific_keysyms
> 
> Can you give examples of these keysyms, and say something about their
> popularity?

No, I can't.

>> I don't know about Lisp threads.  I have never supported it explicitly.
>> Pgtk emacs may not support it.
> 
> Well, for starters see if test/src/thread-tests.el runs and succeeds
> in your Pgtk build.

Thank you.

----
luna:emacs % emacs --batch -l test/src/thread-tests.el 
luna:emacs % echo $?
0
luna:emacs % 
----

Did it succeed?

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28 13:42       ` Yuuki Harano
@ 2020-04-28 14:34         ` Eli Zaretskii
  2020-04-28 20:09           ` Alan Third
  2020-04-29  8:34           ` Yuuki Harano
  2020-04-29  6:16         ` Po Lu
  1 sibling, 2 replies; 90+ messages in thread
From: Eli Zaretskii @ 2020-04-28 14:34 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

> Date: Tue, 28 Apr 2020 22:42:53 +0900 (JST)
> Cc: emacs-devel@gnu.org
> From: Yuuki Harano <masm+emacs@masm11.me>
> 
> Since it needs --without-x to build pgtk emacs, these files are not compiled:
> - xfns.c
> - xgselect.c
> - xmenu.c
> - xrdb.c
> - xselect.c
> - xsmfns.c
> - xterm.c
> 
> Instead, works in those files are done in these files in pure gtk way:
> - pgtkfns.c
> - pgtkim.c
> - pgtkmenu.c
> - pgtkselect.c
> - pgtkterm.c

That's a lot of functionality to rewrite from scratch.

> >> Pgtk emacs supports X window system too through Gtk library.
> >> It can handle Wayland, X window system, and TTY in the same session.
> >> But segmentation fault may occur when running on X and Wayland
> >> in the same session.
> > 
> > I guess those segfaults need to be fixed, because having a GUI Emacs
> > that can only run on Wayland would be a limitation that users might be
> > unhappy about?
> 
> Yes, I think so, too.
> 
> I remember that Emacs has previously an issue when multiple display
> environments.  I thought that the segfaults was the same issue.
> Was that fixed?

I don't think I understand what issues you had in mind.  Can you give
any details, so I could look up those issues?

> luna:emacs % emacs --batch -l test/src/thread-tests.el 
> luna:emacs % echo $?
> 0
> luna:emacs % 
> ----
> 
> Did it succeed?

Probably.  But instead of running the tests blind, it is better to do
this:

  % cd test
  % make src/thread-tests

This will display the summary of the tests, and say explicitly whether
all of the passed.

Thanks.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28 14:34         ` Eli Zaretskii
@ 2020-04-28 20:09           ` Alan Third
  2020-04-29  8:34           ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: Alan Third @ 2020-04-28 20:09 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Yuuki Harano, emacs-devel

On Tue, Apr 28, 2020 at 05:34:17PM +0300, Eli Zaretskii wrote:
> > Date: Tue, 28 Apr 2020 22:42:53 +0900 (JST)
> > Cc: emacs-devel@gnu.org
> > From: Yuuki Harano <masm+emacs@masm11.me>
> > 
> > Since it needs --without-x to build pgtk emacs, these files are not compiled:
> > - xfns.c
> > - xgselect.c
> > - xmenu.c
> > - xrdb.c
> > - xselect.c
> > - xsmfns.c
> > - xterm.c
> > 
> > Instead, works in those files are done in these files in pure gtk way:
> > - pgtkfns.c
> > - pgtkim.c
> > - pgtkmenu.c
> > - pgtkselect.c
> > - pgtkterm.c
> 
> That's a lot of functionality to rewrite from scratch.

A pure GTK port is more akin to the NS port running on GNUstep. There
should be no X code at all, but it still works on X.

The current GTK support is really just X code with some GTK sprinkled
over the top. It’s impossible to remove the X code without rewriting
everything.
-- 
Alan Third



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
                   ` (5 preceding siblings ...)
  2020-04-28  0:51 ` Daniele Nicolodi
@ 2020-04-29  1:14 ` Andrew Cohen
  2020-04-29 13:01 ` Robert Pluim
  7 siblings, 0 replies; 90+ messages in thread
From: Andrew Cohen @ 2020-04-29  1:14 UTC (permalink / raw)
  To: emacs-devel


I have been playing with this for the past few days. Thanks for this
effort!


1. I have run the threads test and all passes.
  GEN      src/thread-tests.log
Running 31 tests (2020-04-29 09:09:03+0800, selector `(not (tag :unstable))')
   passed   1/31  threads-all-threads (0.000095 sec)
   passed   2/31  threads-basic (0.000258 sec)
   passed   3/31  threads-condvar-mutex (0.000084 sec)
   passed   4/31  threads-condvar-name (0.000064 sec)
   passed   5/31  threads-condvar-name-2 (0.000064 sec)
   passed   6/31  threads-condvar-type (0.000062 sec)
   passed   7/31  threads-condvar-wait (0.201323 sec)
   passed   8/31  threads-condvarp (0.000384 sec)
   passed   9/31  threads-condvarp-2 (0.000318 sec)
   passed  10/31  threads-errors (0.000870 sec)
   passed  11/31  threads-io-switch (1.034367 sec)
   passed  12/31  threads-is-one (0.000399 sec)
   passed  13/31  threads-join (0.000583 sec)
   passed  14/31  threads-join-self (0.000279 sec)
   passed  15/31  threads-let-binding (0.000432 sec)
   passed  16/31  threads-live (0.000303 sec)
   passed  17/31  threads-main-thread (0.000228 sec)
   passed  18/31  threads-mutex-contention (0.000710 sec)
   passed  19/31  threads-mutex-lock-unlock (0.000317 sec)
   passed  20/31  threads-mutex-recursive (0.000241 sec)
   passed  21/31  threads-mutex-signal (0.000627 sec)
   passed  22/31  threads-mutex-type (0.000265 sec)
   passed  23/31  threads-mutexp (0.000233 sec)
   passed  24/31  threads-mutexp-2 (0.000226 sec)
   passed  25/31  threads-name (0.000320 sec)
   passed  26/31  threads-signal-early (1.001646 sec)
Error #<thread 0x55ef50c722d0>: (error nil)
   passed  27/31  threads-signal-main-thread (0.101116 sec)
   passed  28/31  threads-sticky-point (1.002000 sec)
   passed  29/31  threads-test-bug33073 (0.000528 sec)
   passed  30/31  threads-threadp (0.000356 sec)
   passed  31/31  threads-type (0.000330 sec)

Ran 31 tests, 31 results as expected, 0 unexpected (2020-04-29
09:09:06+0800, 3.354012 sec)

1. I use a dual monitor setup with one monitor HDPI the other not. The
   good news: moving the emacs window from one display to the other
   properly resizes things (the X11 version does not). This is a big
   win!

   + But the fonts on the hdpi monitor are blurry. If I maximize the window
     and then restore to its original size, the blurriness is gone.

   + I use pdf-tools to view pdf files. These are blurry no matter
     what. 

   + Some of the drop-down menus are blank. This may be the same bug that
     was fixed not too long ago in mainline (or it might be something
     different). 




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28  7:37           ` Eli Zaretskii
@ 2020-04-29  6:13             ` Po Lu
  0 siblings, 0 replies; 90+ messages in thread
From: Po Lu @ 2020-04-29  6:13 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: masm+emacs, emacs-devel, dgutov

Eli Zaretskii <eliz@gnu.org> writes:

> Thanks, I sent the form off-list.

I finished signing the forms.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28 13:42       ` Yuuki Harano
  2020-04-28 14:34         ` Eli Zaretskii
@ 2020-04-29  6:16         ` Po Lu
  2020-04-29  7:46           ` Yuuki Harano
  1 sibling, 1 reply; 90+ messages in thread
From: Po Lu @ 2020-04-29  6:16 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: eliz, emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> I remember that Emacs has previously an issue when multiple display
> environments.  I thought that the segfaults was the same issue.
> Was that fixed?

The multiple display issue is a GTK problem not related to the
segfaults, and is also specific to various GDK backends.  AFAICT, GDK's
Wayland backend does not suddenly barf if a connection is abruptly
closed, but the X11 and Broadway backends do.

The segementation faults are something I have personally never
experienced, and I have been running my own fork of your code as a daily
driver for a very long time.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-27  8:37 ` Po Lu via Emacs development discussions.
  2020-04-27 16:08   ` Yuuki Harano
@ 2020-04-29  6:28   ` Po Lu
  2020-04-29  8:12     ` Yuuki Harano
  1 sibling, 1 reply; 90+ messages in thread
From: Po Lu @ 2020-04-29  6:28 UTC (permalink / raw)
  To: Po Lu via Emacs development discussions.; +Cc: Yuuki Harano

[-- Attachment #1: Type: text/plain, Size: 894 bytes --]

Po Lu via "Emacs development discussions." <emacs-devel@gnu.org> writes:

> Yuuki Harano <masm+emacs@masm11.me> writes:
>
>> Hi,
>>
>> You may know, I ported emacs for pure Gtk3, especially for wayland native.
>>
>> https://github.com/masm11/emacs
>>
>> I created a new window-system, pgtk, which doesn't use libX11 directly.
>>
>> What do you think? I want to merge to mainline.
>
> I was maintaining some separate work a while ago, but I moved some of my
> work on top of yours some time ago.  It would be nice if we could work
> together.  A few noticable differences:
> - Support for GTK 2, 3, and the latest GTK 4 snapshots
> - Some visual features similar to other GTK applications
> - The GTK foreign rendering that I was mentioning earlier
>
> It currently includes some fairly ugly code, but I'm working on cleaning
> it up.

Just a quick preview of some of the various improvements.


[-- Attachment #2: GTK themes --]
[-- Type: image/png, Size: 119355 bytes --]

[-- Attachment #3: GTK 4 --]
[-- Type: image/png, Size: 64599 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29  6:16         ` Po Lu
@ 2020-04-29  7:46           ` Yuuki Harano
  2020-04-29  8:05             ` Po Lu
  0 siblings, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-04-29  7:46 UTC (permalink / raw)
  To: luangruo; +Cc: eliz, emacs-devel


On Wed, 29 Apr 2020 14:16:22 +0800,
	Po Lu <luangruo@yahoo.com> wrote:
>> I remember that Emacs has previously an issue when multiple display
>> environments.  I thought that the segfaults was the same issue.
>> Was that fixed?
> 
> The multiple display issue is a GTK problem not related to the
> segfaults, and is also specific to various GDK backends.  AFAICT, GDK's
> Wayland backend does not suddenly barf if a connection is abruptly
> closed, but the X11 and Broadway backends do.

I think I saw such a comment, but I can't find it even in the past code.
I seem to have misunderstood.

> The segementation faults are something I have personally never
> experienced, and I have been running my own fork of your code as a daily
> driver for a very long time.

Indeed, the segfaults do not occur as far as I tested now.
It may be fixed before I knew, or it disappeared...

I'll debug if it is reproduced by someone (or me).

Thanks.
-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29  7:46           ` Yuuki Harano
@ 2020-04-29  8:05             ` Po Lu
  0 siblings, 0 replies; 90+ messages in thread
From: Po Lu @ 2020-04-29  8:05 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: eliz, emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> I think I saw such a comment, but I can't find it even in the past code.
> I seem to have misunderstood.

Well, if there's a SEGV then it shouldn't be that particular GTK bug,
since GTK only calls `abort' when a connection is closed.

A simple way to reproduce the existing GTK-on-X11 bug is to run an Xnest
on another port, open an Emacs session in the current display, call
`(make-frame-on-display ":<your other display here>")', and then close
the Xnest window.  GDK will promptly abort with a message complaining
about the connection to the other display being abruptly closed.

> Thanks.

You're welcome.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29  6:28   ` Po Lu
@ 2020-04-29  8:12     ` Yuuki Harano
  2020-04-30  0:15       ` Po Lu
  0 siblings, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-04-29  8:12 UTC (permalink / raw)
  To: luangruo; +Cc: emacs-devel

Hi,

On Wed, 29 Apr 2020 14:28:46 +0800,
	Po Lu <luangruo@yahoo.com> wrote:
>> I was maintaining some separate work a while ago, but I moved some of my
>> work on top of yours some time ago.  It would be nice if we could work
>> together.  A few noticable differences:
>> - Support for GTK 2, 3, and the latest GTK 4 snapshots
>> - Some visual features similar to other GTK applications
>> - The GTK foreign rendering that I was mentioning earlier
>>
>> It currently includes some fairly ugly code, but I'm working on cleaning
>> it up.
> 
> Just a quick preview of some of the various improvements.

What is "GTK foreign rendering"?  I googled but nothing found.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28 14:34         ` Eli Zaretskii
  2020-04-28 20:09           ` Alan Third
@ 2020-04-29  8:34           ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-04-29  8:34 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel


On Tue, 28 Apr 2020 17:34:17 +0300,
	Eli Zaretskii <eliz@gnu.org> wrote:
> Probably.  But instead of running the tests blind, it is better to do
> this:
> 
>   % cd test
>   % make src/thread-tests
> 
> This will display the summary of the tests, and say explicitly whether
> all of the passed.

Thank you for the procedure.

I tested again.

----------------
luna:emacs % cd test
luna:test % LANG=C make src/thread-tests
make[1]: Entering directory '/home/masm/src/emacs/test'
  GEN      src/thread-tests.log
Running 31 tests (2020-04-29 17:30:51+0900, selector `(not (tag :unstable))')
   passed   1/31  threads-all-threads (0.000074 sec)
   passed   2/31  threads-basic (0.000217 sec)
   passed   3/31  threads-condvar-mutex (0.000076 sec)
   passed   4/31  threads-condvar-name (0.000070 sec)
   passed   5/31  threads-condvar-name-2 (0.000050 sec)
   passed   6/31  threads-condvar-type (0.000089 sec)
   passed   7/31  threads-condvar-wait (0.200882 sec)
   passed   8/31  threads-condvarp (0.000312 sec)
   passed   9/31  threads-condvarp-2 (0.000237 sec)
   passed  10/31  threads-errors (0.000661 sec)
   passed  11/31  threads-io-switch (1.001562 sec)
   passed  12/31  threads-is-one (0.000309 sec)
   passed  13/31  threads-join (0.000579 sec)
   passed  14/31  threads-join-self (0.030799 sec)
   passed  15/31  threads-let-binding (0.000226 sec)
   passed  16/31  threads-live (0.000135 sec)
   passed  17/31  threads-main-thread (0.000095 sec)
   passed  18/31  threads-mutex-contention (0.000342 sec)
   passed  19/31  threads-mutex-lock-unlock (0.000110 sec)
   passed  20/31  threads-mutex-recursive (0.000063 sec)
   passed  21/31  threads-mutex-signal (0.000168 sec)
   passed  22/31  threads-mutex-type (0.000096 sec)
   passed  23/31  threads-mutexp (0.000101 sec)
   passed  24/31  threads-mutexp-2 (0.000073 sec)
   passed  25/31  threads-name (0.000117 sec)
   passed  26/31  threads-signal-early (1.001414 sec)
Error #<thread 0x564c963d6a00>: (error nil)
   passed  27/31  threads-signal-main-thread (0.100847 sec)
   passed  28/31  threads-sticky-point (1.001586 sec)
   passed  29/31  threads-test-bug33073 (0.000348 sec)
   passed  30/31  threads-threadp (0.000265 sec)
   passed  31/31  threads-type (0.000259 sec)

Ran 31 tests, 31 results as expected, 0 unexpected (2020-04-29 17:30:54+0900, 3.345098 sec)

make[1]: Leaving directory '/home/masm/src/emacs/test'
luna:test % 
----------------

All tests passed.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
                   ` (6 preceding siblings ...)
  2020-04-29  1:14 ` Andrew Cohen
@ 2020-04-29 13:01 ` Robert Pluim
  2020-04-29 15:03   ` martin rudalics
  2020-04-29 15:17   ` Yuuki Harano
  7 siblings, 2 replies; 90+ messages in thread
From: Robert Pluim @ 2020-04-29 13:01 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

>>>>> On Sun, 26 Apr 2020 16:56:04 +0900 (JST), Yuuki Harano <masm+emacs@masm11.me> said:

    Yuuki> Hi,
    Yuuki> You may know, I ported emacs for pure Gtk3, especially for wayland native.

    Yuuki> https://github.com/masm11/emacs

    Yuuki> I created a new window-system, pgtk, which doesn't use libX11 directly.

    Yuuki> What do you think? I want to merge to mainline.

I took a look at it, it works well for me here. One issue I noticed is
that the offsets when creating child-frames are being interpreted
relative to the screen, rather than the parent frame, ie code like
this (from
<https://lists.gnu.org/archive/html/emacs-devel/2020-01/msg00343.html>)
produces a frame in the top left corner of the screen, not at 50+50
from the top left corner of the frame.

    (defun open-test (buffer)
      (display-buffer-in-child-frame
       buffer '((child-frame-parameters
                 . ((width . 40)
                    (height . 10)
                    (top . 50)
                    (left . 50)
                    )))))

Robert



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29 13:01 ` Robert Pluim
@ 2020-04-29 15:03   ` martin rudalics
  2020-04-29 15:17   ` Yuuki Harano
  1 sibling, 0 replies; 90+ messages in thread
From: martin rudalics @ 2020-04-29 15:03 UTC (permalink / raw)
  To: Robert Pluim, Yuuki Harano; +Cc: emacs-devel

 > I took a look at it, it works well for me here. One issue I noticed is
 > that the offsets when creating child-frames are being interpreted
 > relative to the screen, rather than the parent frame, ie code like
 > this (from
 > <https://lists.gnu.org/archive/html/emacs-devel/2020-01/msg00343.html>)
 > produces a frame in the top left corner of the screen, not at 50+50
 > from the top left corner of the frame.
 >
 >      (defun open-test (buffer)
 >        (display-buffer-in-child-frame
 >         buffer '((child-frame-parameters
 >                   . ((width . 40)
 >                      (height . 10)
 >                      (top . 50)
 >                      (left . 50)
 >                      )))))

Same here.  Also, the position of self-fabricated tooltip frames is
highly erratic.

martin



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29 13:01 ` Robert Pluim
  2020-04-29 15:03   ` martin rudalics
@ 2020-04-29 15:17   ` Yuuki Harano
  2020-04-29 16:58     ` Robert Pluim
  1 sibling, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-04-29 15:17 UTC (permalink / raw)
  To: rpluim; +Cc: emacs-devel

Hi,

On Wed, 29 Apr 2020 15:01:44 +0200,
	Robert Pluim <rpluim@gmail.com> wrote:
> I took a look at it, it works well for me here. One issue I noticed is
> that the offsets when creating child-frames are being interpreted
> relative to the screen, rather than the parent frame, ie code like
> this (from
> <https://lists.gnu.org/archive/html/emacs-devel/2020-01/msg00343.html>)
> produces a frame in the top left corner of the screen, not at 50+50
> from the top left corner of the frame.
> 
>     (defun open-test (buffer)
>       (display-buffer-in-child-frame
>        buffer '((child-frame-parameters
>                  . ((width . 40)
>                     (height . 10)
>                     (top . 50)
>                     (left . 50)
>                     )))))

Thank you for testing.

Maybe, you tested on X.  I reproduced that on X.
That code works without the issue on Wayland (Wayfire).

I'll debug.
-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29 15:17   ` Yuuki Harano
@ 2020-04-29 16:58     ` Robert Pluim
  0 siblings, 0 replies; 90+ messages in thread
From: Robert Pluim @ 2020-04-29 16:58 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

>>>>> On Thu, 30 Apr 2020 00:17:33 +0900 (JST), Yuuki Harano <masm+emacs@masm11.me> said:


    Yuuki> Thank you for testing.

    Yuuki> Maybe, you tested on X.  I reproduced that on X.
    Yuuki> That code works without the issue on Wayland (Wayfire).

    Yuuki> I'll debug.

Yes, this is on X.

Thanks

Robert



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-29  8:12     ` Yuuki Harano
@ 2020-04-30  0:15       ` Po Lu
  0 siblings, 0 replies; 90+ messages in thread
From: Po Lu @ 2020-04-30  0:15 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> Hi,
>
> On Wed, 29 Apr 2020 14:28:46 +0800,
> 	Po Lu <luangruo@yahoo.com> wrote:
>>> I was maintaining some separate work a while ago, but I moved some of my
>>> work on top of yours some time ago.  It would be nice if we could work
>>> together.  A few noticable differences:
>>> - Support for GTK 2, 3, and the latest GTK 4 snapshots
>>> - Some visual features similar to other GTK applications
>>> - The GTK foreign rendering that I was mentioning earlier
>>>
>>> It currently includes some fairly ugly code, but I'm working on cleaning
>>> it up.
>> 
>> Just a quick preview of some of the various improvements.
>
> What is "GTK foreign rendering"?  I googled but nothing found.

It's a system that allows apps to respect the GTK stylesheet without
using actual GTK widgets.  For instance, I can extract the style context
from a GtkButton, and then use gtk_render_background and
gtk_render_frame to draw a GTK-like button into a Cairo surface.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-28  3:19 Jeff Walsh
  2020-04-28  7:27 ` Eli Zaretskii
@ 2020-05-08  6:54 ` Jostein Kjønigsen
  2020-05-08  6:59   ` Eli Zaretskii
  1 sibling, 1 reply; 90+ messages in thread
From: Jostein Kjønigsen @ 2020-05-08  6:54 UTC (permalink / raw)
  To: Jeff Walsh, eliz; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 823 bytes --]

On 28.04.2020 05:19, Jeff Walsh wrote:
> it will run on wayland or xwayland or X11 from the same binary, but 
> not on wayland and X11 concurrently.

The only scenario I can think of is SSHing into a machine with a Wayland 
desktop which already has Emacs running.

If you then try to launch Emacs from your SSH-session, that would 
normally cause a new X11-session to be started, using SSH and X-forwarding.

Are you saying such scenarios don't work with the current pure GTK3 
approach? If so, I guess that's ok and definitely not unprecendented. 
For instance I can't launch a new Firefox instance over SSH and X11, if 
I already have an existing Firefox session running on the machine being 
SSHed into.

-- 
Kind regards
*Jostein Kjønigsen*

jostein@kjonigsen.net 🍵 jostein@gmail.com
https://jostein.kjønigsen.no

[-- Attachment #2: Type: text/html, Size: 1858 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-05-08  6:54 ` Jostein Kjønigsen
@ 2020-05-08  6:59   ` Eli Zaretskii
  0 siblings, 0 replies; 90+ messages in thread
From: Eli Zaretskii @ 2020-05-08  6:59 UTC (permalink / raw)
  To: Jostein Kjønigsen; +Cc: fejfighter, emacs-devel

> From: Jostein Kjønigsen <jostein@secure.kjonigsen.net>
> Date: Fri, 8 May 2020 08:54:21 +0200
> Cc: emacs-devel@gnu.org
> 
> The only scenario I can think of is SSHing into a machine with a Wayland desktop which already has Emacs
> running.
> 
> If you then try to launch Emacs from your SSH-session, that would normally cause a new X11-session to be
> started, using SSH and X-forwarding.
> 
> Are you saying such scenarios don't work with the current pure GTK3 approach? If so, I guess that's ok and
> definitely not unprecendented. For instance I can't launch a new Firefox instance over SSH and X11, if I
> already have an existing Firefox session running on the machine being SSHed into.

It may not be unprecendented, but it's a restriction for Emacs users
(I think quite an annoyance, but maybe I'm wrong) that it would be
good to lift, if possible.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-04-26 14:01 ` Eli Zaretskii
  2020-04-27 12:37   ` Yuuki Harano
@ 2020-11-17 14:50   ` Yuuki Harano
  2020-11-17 15:24     ` Eli Zaretskii
  2020-11-19  3:18     ` 황병희
  1 sibling, 2 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-11-17 14:50 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel

Hi,

I and Jeff have done the copyright assignment.

Jeff rebased the pgtk branch.
We are going to push his one to fsf's repository.  It is:
  https://github.com/fejfighter/emacs#branch=upstream-base
My pgtk branch contains too many merge commits, but his one doesn't.
Both branches contain the same contents.

I want to push his one to fsf's repository.  How to do it?
Can I get an account of the repository here?:
https://savannah.gnu.org/account/register.php

-- 
Yuuki Harano


On Sun, 26 Apr 2020 17:01:33 +0300,
	Eli Zaretskii <eliz@gnu.org> wrote:
>> Date: Sun, 26 Apr 2020 16:56:04 +0900 (JST)
>> From: Yuuki Harano <masm+emacs@masm11.me>
>> 
>> You may know, I ported emacs for pure Gtk3, especially for wayland native.
>> 
>> https://github.com/masm11/emacs
>> 
>> I created a new window-system, pgtk, which doesn't use libX11 directly.
>> 
>> What do you think? I want to merge to mainline.
> 
> Thank you for your interest in Emacs, and in particular for working on
> this.
> 
> I think this should be pushed to a feature branch first, and we should
> then let people use it and report any problems, with the purpose of
> making it stable enough before we merge to master.
> 
> But before we create such a feature branch, there are a few
> prerequisites:
> 
>   . You don't seem to have a copyright assignment on file.  This would
>     be a significant contribution to Emacs, for which we must have
>     such an assignment from you before bringing this code into the
>     Emacs repository.  Would you be willing to start the legal
>     paperwork now?  If so, I will send you the form to fill.
> 
>   . The code seem to be based on an relatively old version of our
>     master branch, which makes it hard to review (there are many
>     spurious changes unrelated to your work).  Please rebase on the
>     latest HEAD of the master branch.
> 
>   . Would it be possible for you to describe the design of this
>     feature, and how that affects the various Emacs features, so that
>     understanding the changes would be facilitated?  In particular,
>     can this new window-system live together with X and TTY frames in
>     the same session? does it support Lisp threads? etc.  Also, what
>     are the requirements from the platforms where this could be built
>     and used?
> 
> Thanks again for your work on Emacs.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-17 14:50   ` Yuuki Harano
@ 2020-11-17 15:24     ` Eli Zaretskii
  2020-11-17 17:24       ` Robert Pluim
  2020-11-20 12:04       ` Yuuki Harano
  2020-11-19  3:18     ` 황병희
  1 sibling, 2 replies; 90+ messages in thread
From: Eli Zaretskii @ 2020-11-17 15:24 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

> Date: Tue, 17 Nov 2020 23:50:00 +0900 (JST)
> Cc: emacs-devel@gnu.org
> From: Yuuki Harano <masm+emacs@masm11.me>
> 
> I and Jeff have done the copyright assignment.

Are you two the only authors of the changes?

> I want to push his one to fsf's repository.  How to do it?
> Can I get an account of the repository here?:
> https://savannah.gnu.org/account/register.php

Register a user account there, and then request to become a member of
the Emacs project.  When you are approved for the membership, it will
give you write access to the Emacs Git repository.  Then please push
this as a separate branch under origin/feature/.

Thanks.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-17 15:24     ` Eli Zaretskii
@ 2020-11-17 17:24       ` Robert Pluim
  2020-11-24 13:12         ` Yuuki Harano
  2020-11-20 12:04       ` Yuuki Harano
  1 sibling, 1 reply; 90+ messages in thread
From: Robert Pluim @ 2020-11-17 17:24 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Yuuki Harano, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Date: Tue, 17 Nov 2020 23:50:00 +0900 (JST)
>> Cc: emacs-devel@gnu.org
>> From: Yuuki Harano <masm+emacs@masm11.me>
>> 
>> I and Jeff have done the copyright assignment.
>
> Are you two the only authors of the changes?
>
>> I want to push his one to fsf's repository.  How to do it?
>> Can I get an account of the repository here?:
>> https://savannah.gnu.org/account/register.php
>
> Register a user account there, and then request to become a member of
> the Emacs project.  When you are approved for the membership, it will
> give you write access to the Emacs Git repository.  Then please push
> this as a separate branch under origin/feature/.

I took a quick look, and I see you reformatted the code according to
Emacs' style, which is great. However these need fixing:

$ grep -i copyright pgtk*
pgtkfns.c:Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2017 Free Software
pgtkgui.h:   Copyright (C) 1995, 2005, 2008-2017 Free Software Foundation, Inc.
pgtkim.c:Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2018 Free Software
pgtkmenu.c:   Copyright (C) 2019 Free Software Foundation, Inc.
pgtkselect.c:   Copyright (C) 1993-1994, 2005-2006, 2008-2017 Free Software
pgtkselect.h:   Copyright (C) 1989, 1993, 2005, 2008-2017 Free Software Foundation,
pgtkterm.c:Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2017 Free Software
pgtkterm.h:   Copyright (C) 1989, 1993, 2005, 2008-2017 Free Software Foundation,

(and any other files you added)

Robert



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-17 14:50   ` Yuuki Harano
  2020-11-17 15:24     ` Eli Zaretskii
@ 2020-11-19  3:18     ` 황병희
  2020-11-20  4:23       ` Tim Cross
  1 sibling, 1 reply; 90+ messages in thread
From: 황병희 @ 2020-11-19  3:18 UTC (permalink / raw)
  To: emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> I and Jeff have done the copyright assignment.
> [...]

I like Wayland very much!!! Cheer up Yuuki and Jeff^^^

Sincerely, Byung-Hee

-- 
^고맙습니다 _白衣從軍_ 감사합니다_^))//

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-19  3:18     ` 황병희
@ 2020-11-20  4:23       ` Tim Cross
  2020-11-20  4:39         ` Eric Abrahamsen
  0 siblings, 1 reply; 90+ messages in thread
From: Tim Cross @ 2020-11-20  4:23 UTC (permalink / raw)
  To: 황병희; +Cc: Emacs developers

[-- Attachment #1: Type: text/plain, Size: 548 bytes --]

Just trying out this version under Ubuntu (not running Wayland) and it
looks pretty good. Have been using it for a day now with no issues.  Nice
work guys.


On Thu, 19 Nov 2020 at 14:20, 황병희 <soyeomul@doraji.xyz> wrote:

> Yuuki Harano <masm+emacs@masm11.me> writes:
>
> > I and Jeff have done the copyright assignment.
> > [...]
>
> I like Wayland very much!!! Cheer up Yuuki and Jeff^^^
>
> Sincerely, Byung-Hee
>
> --
> ^고맙습니다 _白衣從軍_ 감사합니다_^))//
>


-- 
regards,

Tim

--
Tim Cross

[-- Attachment #2: Type: text/html, Size: 1096 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20  4:23       ` Tim Cross
@ 2020-11-20  4:39         ` Eric Abrahamsen
  2020-11-20  7:11           ` Tim Cross
  0 siblings, 1 reply; 90+ messages in thread
From: Eric Abrahamsen @ 2020-11-20  4:39 UTC (permalink / raw)
  To: emacs-devel

Tim Cross <theophilusx@gmail.com> writes:

> Just trying out this version under Ubuntu (not running Wayland) and it
> looks pretty good. Have been using it for a day now with no issues.  Nice
> work guys.

Are you running it from the github mirror? I didn't see a feature/pgtk
branch on savannah...




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20  4:39         ` Eric Abrahamsen
@ 2020-11-20  7:11           ` Tim Cross
  2020-11-20  7:29             ` Jean Louis
  2020-11-20 16:33             ` Eric Abrahamsen
  0 siblings, 2 replies; 90+ messages in thread
From: Tim Cross @ 2020-11-20  7:11 UTC (permalink / raw)
  To: Eric Abrahamsen; +Cc: Emacs developers

[-- Attachment #1: Type: text/plain, Size: 568 bytes --]

Yes, from the github repo. Was interested to see how it looked and too
impatient to wait until it is merged into a feature branch :-)


On Fri, 20 Nov 2020 at 15:40, Eric Abrahamsen <eric@ericabrahamsen.net>
wrote:

> Tim Cross <theophilusx@gmail.com> writes:
>
> > Just trying out this version under Ubuntu (not running Wayland) and it
> > looks pretty good. Have been using it for a day now with no issues.  Nice
> > work guys.
>
> Are you running it from the github mirror? I didn't see a feature/pgtk
> branch on savannah...
>
>
>

-- 
regards,

Tim

--
Tim Cross

[-- Attachment #2: Type: text/html, Size: 1151 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20  7:11           ` Tim Cross
@ 2020-11-20  7:29             ` Jean Louis
  2020-11-21 23:35               ` Tim Cross
  2020-11-20 16:33             ` Eric Abrahamsen
  1 sibling, 1 reply; 90+ messages in thread
From: Jean Louis @ 2020-11-20  7:29 UTC (permalink / raw)
  To: Tim Cross; +Cc: Eric Abrahamsen, Emacs developers

* Tim Cross <theophilusx@gmail.com> [2020-11-20 10:12]:
> Yes, from the github repo. Was interested to see how it looked and too
> impatient to wait until it is merged into a feature branch :-)

Make a screenshot.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-17 15:24     ` Eli Zaretskii
  2020-11-17 17:24       ` Robert Pluim
@ 2020-11-20 12:04       ` Yuuki Harano
  2020-11-20 12:16         ` Eli Zaretskii
  2020-11-25  2:17         ` Zhu Zihao
  1 sibling, 2 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-11-20 12:04 UTC (permalink / raw)
  To: eliz; +Cc: emacs-devel

Hi,

On Tue, 17 Nov 2020 17:24:24 +0200,
	Eli Zaretskii <eliz@gnu.org> wrote:
>> I and Jeff have done the copyright assignment.
> 
> Are you two the only authors of the changes?

There is a commit by other:
  https://github.com/masm11/emacs/commit/d24a9f93ccc8e93b148dd4f662ffe6dd505a99cc

I think it can be treat as a tiny change.

All other commits are by us two.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20 12:04       ` Yuuki Harano
@ 2020-11-20 12:16         ` Eli Zaretskii
  2020-11-25  2:17         ` Zhu Zihao
  1 sibling, 0 replies; 90+ messages in thread
From: Eli Zaretskii @ 2020-11-20 12:16 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

> Date: Fri, 20 Nov 2020 21:04:04 +0900 (JST)
> Cc: emacs-devel@gnu.org
> From: Yuuki Harano <masm+emacs@masm11.me>
> 
> >> I and Jeff have done the copyright assignment.
> > 
> > Are you two the only authors of the changes?
> 
> There is a commit by other:
>   https://github.com/masm11/emacs/commit/d24a9f93ccc8e93b148dd4f662ffe6dd505a99cc
> 
> I think it can be treat as a tiny change.

Yes, thanks.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20  7:11           ` Tim Cross
  2020-11-20  7:29             ` Jean Louis
@ 2020-11-20 16:33             ` Eric Abrahamsen
  1 sibling, 0 replies; 90+ messages in thread
From: Eric Abrahamsen @ 2020-11-20 16:33 UTC (permalink / raw)
  To: emacs-devel

Tim Cross <theophilusx@gmail.com> writes:

> Yes, from the github repo. Was interested to see how it looked and too
> impatient to wait until it is merged into a feature branch :-)

I am only impatient enough to bother people on mailing lists, I think
I'll wait for the feature branch :)

>
> On Fri, 20 Nov 2020 at 15:40, Eric Abrahamsen <eric@ericabrahamsen.net>
> wrote:
>
>> Tim Cross <theophilusx@gmail.com> writes:
>>
>> > Just trying out this version under Ubuntu (not running Wayland) and it
>> > looks pretty good. Have been using it for a day now with no issues.  Nice
>> > work guys.
>>
>> Are you running it from the github mirror? I didn't see a feature/pgtk
>> branch on savannah...
>>
>>
>>




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20  7:29             ` Jean Louis
@ 2020-11-21 23:35               ` Tim Cross
  2020-11-22  1:49                 ` 황병희
  0 siblings, 1 reply; 90+ messages in thread
From: Tim Cross @ 2020-11-21 23:35 UTC (permalink / raw)
  To: Jean Louis; +Cc: Eric Abrahamsen, Emacs developers

[-- Attachment #1: Type: text/plain, Size: 1691 bytes --]

On Fri, 20 Nov 2020 at 19:55, Jean Louis <bugs@gnu.support> wrote:

> * Tim Cross <theophilusx@gmail.com> [2020-11-20 10:12]:
> > Yes, from the github repo. Was interested to see how it looked and too
> > impatient to wait until it is merged into a feature branch :-)
>
> Make a screenshot.
>

Sorry, only just saw this and I've already switched back to my 'normal'
setup (based on emacs 27.1).

I did find a couple of minor 'quirks', but don't know if they might not
have just been minor issues in current head anyway.

I did have to start the pgtk image with --no-x-resources switch to prevent
it from segfaulting.

It doesn't look a lot different than normal Emacs. Those with more of an
eye for the subtle aesthetics might notice more. I did notice that the
fonts sizes were larger i.e. same font specification gave slightly larger
fonts. Also noticed that on a couple of occasions, initial layout for
something complex was a bit 'jumpy'. For example, I tried running spacemacs
(which worked quite well) and the initial spash screen was all out of
alignment initially (at the end of loading it re-aligned and was fine).

So, probably not quite ready for day to day use, but still pretty good and
likely to be a real bonus for those running under Wayland who would like to
get rid of the X layer completely. Emacs was one of the 2 reasons I stopped
running Wauyland and switched back to X - the other being I was also
running stumpwm (part of a little experiment I was doing where I was trying
to make as much of my environment  on tools based on a lisp/functional
approach as possible (stumpwm, emacs, nyxt, rep, guile, clojure,
clojurescirpt etc).

Tim

-- 
regards,

Tim

--
Tim Cross

[-- Attachment #2: Type: text/html, Size: 2392 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-21 23:35               ` Tim Cross
@ 2020-11-22  1:49                 ` 황병희
  0 siblings, 0 replies; 90+ messages in thread
From: 황병희 @ 2020-11-22  1:49 UTC (permalink / raw)
  To: Emacs developers

Tim Cross <theophilusx@gmail.com> writes:

>  ...
>  Make a screenshot.
>
> Sorry, only just saw this and I've already switched back to my 'normal' setup (based on emacs 27.1). 
>
> I did find a couple of minor 'quirks', but don't know if they might not have just been minor issues in current head
> anyway. 
>
> I did have to start the pgtk image with --no-x-resources switch to prevent it from segfaulting. 
>
> It doesn't look a lot different than normal Emacs. Those with more of an eye for the subtle aesthetics might notice
> more. I did notice that the fonts sizes were larger i.e. same font specification gave slightly larger fonts. Also
> noticed that on a couple of occasions, initial layout for something complex was a bit 'jumpy'. For example, I tried
> running spacemacs (which worked quite well) and the initial spash screen was all out of alignment initially (at the
> end of loading it re-aligned and was fine). 
>
> So, probably not quite ready for day to day use, but still pretty good and likely to be a real bonus for those running
> under Wayland who would like to get rid of the X layer completely. Emacs was one of the 2 reasons I stopped
> running Wauyland and switched back to X - the other being I was also running stumpwm (part of a little
> experiment I was doing where I was trying to make as much of my environment  on tools based on a
> lisp/functional approach as possible (stumpwm, emacs, nyxt, rep, guile, clojure, clojurescirpt etc). 
>
> Tim

Switching from X11 to Wayland for perfactly is my final goal in GNU
Emacs. Thanks for testing Tim^^^ Indeed...

Sincerely, Wayland fan Byung-Hee

-- 
^고맙습니다 _地平天成_ 감사합니다_^))//



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-17 17:24       ` Robert Pluim
@ 2020-11-24 13:12         ` Yuuki Harano
  2020-11-24 14:41           ` Robert Pluim
                             ` (3 more replies)
  0 siblings, 4 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-11-24 13:12 UTC (permalink / raw)
  To: eliz, rpluim; +Cc: emacs-devel

Hi,

On Tue, 17 Nov 2020 17:24:24 +0200,
	Eli Zaretskii <eliz@gnu.org> wrote:
> Register a user account there, and then request to become a member of
> the Emacs project.  When you are approved for the membership, it will
> give you write access to the Emacs Git repository.  Then please push
> this as a separate branch under origin/feature/.

I pushed as feature/pgtk.  Thank you.


On Tue, 17 Nov 2020 18:24:25 +0100,
	Robert Pluim <rpluim@gmail.com> wrote:
> I took a quick look, and I see you reformatted the code according to
> Emacs' style, which is great. However these need fixing:

> (and any other files you added)

They are fixed.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-24 13:12         ` Yuuki Harano
@ 2020-11-24 14:41           ` Robert Pluim
  2020-11-25 12:24             ` Yuuki Harano
  2020-11-24 19:25           ` martin rudalics
                             ` (2 subsequent siblings)
  3 siblings, 1 reply; 90+ messages in thread
From: Robert Pluim @ 2020-11-24 14:41 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: eliz, emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> Hi,
>
> On Tue, 17 Nov 2020 17:24:24 +0200,
> 	Eli Zaretskii <eliz@gnu.org> wrote:
>> Register a user account there, and then request to become a member of
>> the Emacs project.  When you are approved for the membership, it will
>> give you write access to the Emacs Git repository.  Then please push
>> this as a separate branch under origin/feature/.
>
> I pushed as feature/pgtk.  Thank you.
>

Thanks. Iʼve been using it for a while, itʼs working well (Iʼm using
it to send this). I get the following on startup though:

Gdk-Message: 15:37:26.291: Unable to load sb_v_double_arrow from the cursor theme
Gdk-Message: 15:37:26.292: Unable to load sb_h_double_arrow from the cursor theme
Gdk-Message: 15:37:26.313: Unable to load hand2 from the cursor theme
Gdk-Message: 15:37:26.313: Unable to load sb_h_double_arrow from the cursor theme
Gdk-Message: 15:37:26.313: Unable to load sb_v_double_arrow from the cursor theme
Gdk-Message: 15:37:30.875: Unable to load hand2 from the cursor theme
Gdk-Message: 15:37:30.875: Unable to load sb_h_double_arrow from the cursor theme
Gdk-Message: 15:37:30.875: Unable to load sb_v_double_arrow from the cursor theme

This is under Gnome 3.30.2 on Debian 10, which uses Wayland by
default, and using the GdkWaylandDisplay backend.

Robert



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-24 13:12         ` Yuuki Harano
  2020-11-24 14:41           ` Robert Pluim
@ 2020-11-24 19:25           ` martin rudalics
  2020-11-25 12:19             ` Yuuki Harano
  2020-11-25 17:31           ` Eric Abrahamsen
  2021-01-02 22:43           ` Dmitry Gutov
  3 siblings, 1 reply; 90+ messages in thread
From: martin rudalics @ 2020-11-24 19:25 UTC (permalink / raw)
  To: Yuuki Harano, eliz, rpluim; +Cc: emacs-devel

 > I pushed as feature/pgtk.  Thank you.

Thanks.  A -O3 build succeeds here and can be run with minor glitches
(incidentally involving cursor and tooltip frames).  A

CFLAGS='-O0 -g3 -no-pie' ../configure --with-pgtk --without-x --with-cairo --with-modules --with-gif=ifavailable --with-tiff=ifavailable --with-gnutls=no --without-pop --enable-gcc-warnings=warn-only --enable-checking='yes,glyphs' --enable-check-lisp-object-type=yes

build currently fails thusly

../../src/pgtkfns.c: In function ‘x_create_tip_frame’:
../../src/pgtkfns.c:2661:3: error: ‘dpyinfo_refcount’ undeclared (first use in this function); did you mean ‘gatomicrefcount’?
    dpyinfo_refcount = dpyinfo->reference_count;
    ^~~~~~~~~~~~~~~~
    gatomicrefcount
../../src/pgtkfns.c:2661:3: note: each undeclared identifier is reported only once for each function it appears in
make[1]: *** [Makefile:404: pgtkfns.o] Fehler 1
make[1]: *** Es wird auf noch nicht beendete Prozesse gewartet....
In file included from ../../src/termhooks.h:27,
                  from ../../src/frame.h:22,
                  from ../../src/pgtkterm.c:41:
../../src/pgtkterm.c: In function ‘x_set_cursor_gc’:
../../src/pgtkterm.c:1007:17: warning: implicit declaration of function ‘x_check_font’; did you mean ‘xg_get_font’? [-Wimplicit-function-declaration]
        IF_DEBUG (x_check_font (s->f, s->font));
                  ^~~~~~~~~~~~
../../src/dispextern.h:206:30: note: in definition of macro ‘IF_DEBUG’
  #define IF_DEBUG(X) ((void) (X))
                               ^
../../src/pgtkterm.c:1007:17: warning: nested extern declaration of ‘x_check_font’ [-Wnested-externs]
        IF_DEBUG (x_check_font (s->f, s->font));
                  ^~~~~~~~~~~~
../../src/dispextern.h:206:30: note: in definition of macro ‘IF_DEBUG’
  #define IF_DEBUG(X) ((void) (X))
                               ^

martin




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-20 12:04       ` Yuuki Harano
  2020-11-20 12:16         ` Eli Zaretskii
@ 2020-11-25  2:17         ` Zhu Zihao
  2020-11-25 10:02           ` Robert Pluim
  1 sibling, 1 reply; 90+ messages in thread
From: Zhu Zihao @ 2020-11-25  2:17 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 466 bytes --]


Does a Pure GTK3 Emacs means we can say good bye to the warning message[1]
while starting `emacs --daemon` under X11?

[1]:
Warning: due to a long standing Gtk+ bug
https://gitlab.gnome.org/GNOME/gtk/issues/221
Emacs might crash when run in daemon mode and the X11 connection is unexpectedly lost.
Using an Emacs configured with --with-x-toolkit=lucid does not have this problem.

-- 
Retrieve my PGP public key: https://meta.sr.ht/~citreu.pgp

Zihao

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 515 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25  2:17         ` Zhu Zihao
@ 2020-11-25 10:02           ` Robert Pluim
  0 siblings, 0 replies; 90+ messages in thread
From: Robert Pluim @ 2020-11-25 10:02 UTC (permalink / raw)
  To: Zhu Zihao; +Cc: Yuuki Harano, emacs-devel

Zhu Zihao <all_but_last@163.com> writes:

> Does a Pure GTK3 Emacs means we can say good bye to the warning message[1]
> while starting `emacs --daemon` under X11?
>
> [1]:
> Warning: due to a long standing Gtk+ bug
> https://gitlab.gnome.org/GNOME/gtk/issues/221
> Emacs might crash when run in daemon mode and the X11 connection is unexpectedly lost.
> Using an Emacs configured with --with-x-toolkit=lucid does not have this problem.

No, this still happens. Maybe it will work when using waypipe, but I
donʼt have a way to easily test that. Maybe itʼs fixable:

Gdk-Message: 10:40:57.761: emacs: Fatal IO error 11 (Resource temporarily unavailable) on X server localhost:10.0.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-24 19:25           ` martin rudalics
@ 2020-11-25 12:19             ` Yuuki Harano
  2020-11-25 13:22               ` martin rudalics
  2020-11-25 18:35               ` martin rudalics
  0 siblings, 2 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-11-25 12:19 UTC (permalink / raw)
  To: rudalics; +Cc: emacs-devel


On Tue, 24 Nov 2020 20:25:02 +0100,
	martin rudalics <rudalics@gmx.at> wrote:
> Thanks.  A -O3 build succeeds here and can be run with minor glitches
> (incidentally involving cursor and tooltip frames).  A
> 
> CFLAGS='-O0 -g3 -no-pie' ../configure --with-pgtk --without-x
> --with-cairo --with-modules --with-gif=ifavailable
> --with-tiff=ifavailable --with-gnutls=no --without-pop
> --enable-gcc-warnings=warn-only --enable-checking='yes,glyphs'
> --enable-check-lisp-object-type=yes
> 
> build currently fails thusly

Fixed.  Could you try again?

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-24 14:41           ` Robert Pluim
@ 2020-11-25 12:24             ` Yuuki Harano
  2020-11-25 13:30               ` Robert Pluim
  0 siblings, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-11-25 12:24 UTC (permalink / raw)
  To: rpluim; +Cc: emacs-devel


On Tue, 24 Nov 2020 15:41:00 +0100,
	Robert Pluim <rpluim@gmail.com> wrote:
> I get the following on startup though:
> 
> Gdk-Message: 15:37:26.291: Unable to load sb_v_double_arrow from the cursor theme
> Gdk-Message: 15:37:26.292: Unable to load sb_h_double_arrow from the cursor theme
> Gdk-Message: 15:37:26.313: Unable to load hand2 from the cursor theme
> Gdk-Message: 15:37:26.313: Unable to load sb_h_double_arrow from the cursor theme
> Gdk-Message: 15:37:26.313: Unable to load sb_v_double_arrow from the cursor theme
> Gdk-Message: 15:37:30.875: Unable to load hand2 from the cursor theme
> Gdk-Message: 15:37:30.875: Unable to load sb_h_double_arrow from the cursor theme
> Gdk-Message: 15:37:30.875: Unable to load sb_v_double_arrow from the cursor theme
> 
> This is under Gnome 3.30.2 on Debian 10, which uses Wayland by
> default, and using the GdkWaylandDisplay backend.

What cursor theme do you use?

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 12:19             ` Yuuki Harano
@ 2020-11-25 13:22               ` martin rudalics
  2020-11-25 18:35               ` martin rudalics
  1 sibling, 0 replies; 90+ messages in thread
From: martin rudalics @ 2020-11-25 13:22 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

> Fixed.  Could you try again?

Builds and runs now.

Thanks, martin



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 12:24             ` Yuuki Harano
@ 2020-11-25 13:30               ` Robert Pluim
  0 siblings, 0 replies; 90+ messages in thread
From: Robert Pluim @ 2020-11-25 13:30 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> On Tue, 24 Nov 2020 15:41:00 +0100,
> 	Robert Pluim <rpluim@gmail.com> wrote:
>> I get the following on startup though:
>> 
>> Gdk-Message: 15:37:26.291: Unable to load sb_v_double_arrow from the cursor theme
>> Gdk-Message: 15:37:26.292: Unable to load sb_h_double_arrow from the cursor theme
>> Gdk-Message: 15:37:26.313: Unable to load hand2 from the cursor theme
>> Gdk-Message: 15:37:26.313: Unable to load sb_h_double_arrow from the cursor theme
>> Gdk-Message: 15:37:26.313: Unable to load sb_v_double_arrow from the cursor theme
>> Gdk-Message: 15:37:30.875: Unable to load hand2 from the cursor theme
>> Gdk-Message: 15:37:30.875: Unable to load sb_h_double_arrow from the cursor theme
>> Gdk-Message: 15:37:30.875: Unable to load sb_v_double_arrow from the cursor theme
>> 
>> This is under Gnome 3.30.2 on Debian 10, which uses Wayland by
>> default, and using the GdkWaylandDisplay backend.
>
> What cursor theme do you use?

Hmm, I took a look in tweak-tool, and it wasn't set to anything! I set
it to Adwaita, and now I no longer get the messages.

Robert



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-24 13:12         ` Yuuki Harano
  2020-11-24 14:41           ` Robert Pluim
  2020-11-24 19:25           ` martin rudalics
@ 2020-11-25 17:31           ` Eric Abrahamsen
  2020-11-27 16:07             ` Yuuki Harano
  2021-01-02 22:43           ` Dmitry Gutov
  3 siblings, 1 reply; 90+ messages in thread
From: Eric Abrahamsen @ 2020-11-25 17:31 UTC (permalink / raw)
  To: emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> Hi,
>
> On Tue, 17 Nov 2020 17:24:24 +0200,
> 	Eli Zaretskii <eliz@gnu.org> wrote:
>> Register a user account there, and then request to become a member of
>> the Emacs project.  When you are approved for the membership, it will
>> give you write access to the Emacs Git repository.  Then please push
>> this as a separate branch under origin/feature/.
>
> I pushed as feature/pgtk.  Thank you.

Very excited! This gets me off Xwayland, and the fonts are much crisper
as well.

I'm using this on Arch Linux, under the sway (Wayland) tiling window
manager. So far the only oddness I've seen is that if I toggle either of
`tool-bar-mode' or `menu-bar-mode' (either via M-x or M-:), the frame(s)
"freeze" and are not redrawn until I move a frame or change focus. While
frozen, the frames/windows still accept input and process it correctly.

Nothing else so far...

Eric




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 12:19             ` Yuuki Harano
  2020-11-25 13:22               ` martin rudalics
@ 2020-11-25 18:35               ` martin rudalics
  2020-11-25 23:06                 ` Tim Cross
  2020-11-26 13:39                 ` Yuuki Harano
  1 sibling, 2 replies; 90+ messages in thread
From: martin rudalics @ 2020-11-25 18:35 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

Trying to run Emacs with my customizations I'm now getting a segfault at
startup (this used to work with earlier checkouts).

martin


Thread 1 "emacs" received signal SIGSEGV, Segmentation fault.
0x0000000000551139 in hierarchy_ch_cb (widget=0x1356c50, previous_toplevel=0x24322d0, user_data=0xd99dc0) at ../../src/gtkutil.c:754
754	  GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
(gdb) bt
#0  0x0000000000551139 in hierarchy_ch_cb (widget=0x1356c50, previous_toplevel=0x24322d0, user_data=0xd99dc0) at ../../src/gtkutil.c:754
#1  0x00007ffff73eac8d in g_closure_invoke () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#2  0x00007ffff73fe365 in  () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#3  0x00007ffff74072be in g_signal_emit_valist () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#4  0x00007ffff740797f in g_signal_emit () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#5  0x00007ffff7c3b6d5 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#6  0x00007ffff7c3e877 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#7  0x00007ffff7c4cbdb in gtk_widget_unparent () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#8  0x00007ffff79e428b in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#9  0x00007ffff73edfff in g_cclosure_marshal_VOID__OBJECTv () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#10 0x00007ffff73eaec6 in  () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#11 0x00007ffff740738d in g_signal_emit_valist () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#12 0x00007ffff740797f in g_signal_emit () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#13 0x00007ffff7a2d6a6 in gtk_container_remove () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#14 0x00007ffff7bfcecc in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#15 0x00000000005512c2 in qttip_cb (widget=0x11c5f40, xpos=1125, ypos=402, keyboard_mode=0, tooltip=0x12b4980, user_data=0x1df16a8) at ../../src/gtkutil.c:786
#16 0x00007ffff7c8e805 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#17 0x00007ffff73eac8d in g_closure_invoke () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#18 0x00007ffff73fe365 in  () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#19 0x00007ffff74069ab in g_signal_emit_valist () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#20 0x00007ffff740797f in g_signal_emit () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
#21 0x00007ffff7c3caeb in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#22 0x00007ffff7bfbc69 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#23 0x00007ffff7bfc147 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#24 0x00007ffff7afc945 in gtk_main_do_event () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#25 0x00007ffff77fe465 in  () at /lib/x86_64-linux-gnu/libgdk-3.so.0
#26 0x00007ffff782f112 in  () at /lib/x86_64-linux-gnu/libgdk-3.so.0
#27 0x00007ffff7308f2e in g_main_context_dispatch () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#28 0x00007ffff73091c8 in  () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#29 0x00007ffff73094c2 in g_main_loop_run () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#30 0x00007ffff7c63d9e in gtk_clipboard_wait_for_contents () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#31 0x00007ffff7c642d2 in gtk_clipboard_wait_is_text_available () at /lib/x86_64-linux-gnu/libgtk-3.so.0
#32 0x000000000070d86c in Fpgtk_selection_exists_p (selection=XIL(0x120), terminal=XIL(0)) at ../../src/pgtkselect.c:478
#33 0x000000000064b59e in funcall_subr (subr=0xc111e0 <Spgtk_selection_exists_p>, numargs=1, args=0x7ffffffef658) at ../../src/eval.c:2884
#34 0x000000000064b047 in Ffuncall (nargs=2, args=0x7ffffffef650) at ../../src/eval.c:2809
#35 0x00000000006a3eec in exec_byte_code (bytestr=XIL(0x7ffff41e9984), vector=XIL(0x7ffff41e9925), maxdepth=make_fixnum(3), args_template=make_fixnum(257), nargs=1, args=0x7ffffffefcb0) at ../../src/bytecode.c:632
#36 0x000000000064b7ff in fetch_and_exec_byte_code (fun=XIL(0x7ffff41e98d5), syms_left=make_fixnum(257), nargs=1, args=0x7ffffffefca8) at ../../src/eval.c:2931
#37 0x000000000064bc85 in funcall_lambda (fun=XIL(0x7ffff41e98d5), nargs=1, arg_vector=0x7ffffffefca8) at ../../src/eval.c:3012
#38 0x000000000064b08b in Ffuncall (nargs=2, args=0x7ffffffefca0) at ../../src/eval.c:2811
#39 0x0000000000649eb9 in Fapply (nargs=2, args=0x7ffffffefca0) at ../../src/eval.c:2396
#40 0x000000000064b471 in funcall_subr (subr=0xc0b0a0 <Sapply>, numargs=2, args=0x7ffffffefca0) at ../../src/eval.c:2862
#41 0x000000000064b047 in Ffuncall (nargs=3, args=0x7ffffffefc98) at ../../src/eval.c:2809
#42 0x00000000006a3eec in exec_byte_code (bytestr=XIL(0x7ffff41ea26c), vector=XIL(0x7ffff41e9715), maxdepth=make_fixnum(15), args_template=make_fixnum(128), nargs=1, args=0x7fffffff01d0) at ../../src/bytecode.c:632
#43 0x000000000064b7ff in fetch_and_exec_byte_code (fun=XIL(0x7ffff41e96c5), syms_left=make_fixnum(128), nargs=1, args=0x7fffffff01d0) at ../../src/eval.c:2931
#44 0x000000000064bc85 in funcall_lambda (fun=XIL(0x7ffff41e96c5), nargs=1, arg_vector=0x7fffffff01d0) at ../../src/eval.c:3012
#45 0x000000000064b08b in Ffuncall (nargs=2, args=0x7fffffff01c8) at ../../src/eval.c:2811
#46 0x00000000006a3eec in exec_byte_code (bytestr=XIL(0x7ffff42b3cdc), vector=XIL(0x7ffff42b3c9d), maxdepth=make_fixnum(2), args_template=XIL(0), nargs=0, args=0x0) at ../../src/bytecode.c:632
#47 0x000000000064b7ff in fetch_and_exec_byte_code (fun=XIL(0x7ffff42b3c75), syms_left=XIL(0), nargs=0, args=0x0) at ../../src/eval.c:2931
#48 0x000000000064c165 in funcall_lambda (fun=XIL(0x7ffff42b3c75), nargs=0, arg_vector=0x0) at ../../src/eval.c:3081
#49 0x000000000064b08b in Ffuncall (nargs=1, args=0x7fffffff0680) at ../../src/eval.c:2811
#50 0x000000000064974a in eval_sub (form=XIL(0x7ffff42b3c33)) at ../../src/eval.c:2258
#51 0x0000000000648e83 in Feval (form=XIL(0x7ffff42b3c33), lexical=XIL(0)) at ../../src/eval.c:2115
#52 0x000000000057f9c3 in eval_dyn (form=XIL(0x7ffff42b3c33)) at ../../src/keyboard.c:7623
#53 0x00000000006470ad in internal_condition_case_1 (bfun=0x57f99b <eval_dyn>, arg=XIL(0x7ffff42b3c33), handlers=XIL(0x90), hfun=0x57f91c <menu_item_eval_property_1>) at ../../src/eval.c:1383
#54 0x000000000057fa24 in menu_item_eval_property (sexpr=XIL(0x7ffff42b3c33)) at ../../src/keyboard.c:7634
#55 0x0000000000580a93 in parse_menu_item (item=XIL(0), inmenubar=0) at ../../src/keyboard.c:7816
#56 0x00000000004bc12c in single_menu_item (key=XIL(0x7ffff362fa78), item=XIL(0x7ffff42b3ba3), dummy=XIL(0), skp_v=0x7fffffff0bf0) at ../../src/menu.c:323
#57 0x000000000058f6f9 in map_keymap_item (fun=0x4bc0df <single_menu_item>, args=XIL(0), key=XIL(0x7ffff362fa78), val=XIL(0x7ffff42b3ba3), data=0x7fffffff0bf0) at ../../src/keymap.c:537
#58 0x000000000058f9e3 in map_keymap_internal (map=XIL(0x11b8e93), fun=0x4bc0df <single_menu_item>, args=XIL(0), data=0x7fffffff0bf0) at ../../src/keymap.c:584
#59 0x000000000058fd85 in map_keymap_canonical (map=XIL(0x11b8e93), fun=0x4bc0df <single_menu_item>, args=XIL(0), data=0x7fffffff0bf0) at ../../src/keymap.c:644
#60 0x00000000004bbf59 in single_keymap_panes (keymap=XIL(0x7ffff42a0763), pane_name=XIL(0x7ffff42a0884), prefix=XIL(0x7ffff34e3b68), maxdepth=10) at ../../src/menu.c:292
#61 0x00000000004bcf19 in parse_single_submenu (item_key=XIL(0x7ffff34e3b68), item_name=XIL(0x7ffff42a0884), maps=XIL(0)) at ../../src/menu.c:556
#62 0x000000000070ee27 in set_frame_menubar (f=0x1df16a8, first_time=false, deep_p=true) at ../../src/pgtkmenu.c:361
#63 0x000000000046f3ab in update_menu_bar (f=0x1df16a8, save_match_data=false, hooks_run=true) at ../../src/xdisp.c:12834
#64 0x000000000046eee1 in prepare_menu_bars () at ../../src/xdisp.c:12720
#65 0x00000000004743b4 in redisplay_internal () at ../../src/xdisp.c:15582
#66 0x0000000000473123 in redisplay () at ../../src/xdisp.c:15166
#67 0x0000000000572a9e in read_char (commandflag=1, map=XIL(0x21077c3), prev_event=XIL(0), used_mouse_menu=0x7fffffffe15f, end_time=0x0) at ../../src/keyboard.c:2497
#68 0x0000000000585685 in read_key_sequence (keybuf=0x7fffffffe2f0, prompt=XIL(0), dont_downcase_last=false, can_return_switch_frame=true, fix_current_buffer=true, prevent_redisplay=false) at ../../src/keyboard.c:9544
#69 0x000000000056f05c in command_loop_1 () at ../../src/keyboard.c:1354
#70 0x0000000000646fd2 in internal_condition_case (bfun=0x56ebe0 <command_loop_1>, handlers=XIL(0x90), hfun=0x56e1ef <cmd_error>) at ../../src/eval.c:1359
#71 0x000000000056e7c5 in command_loop_2 (ignore=XIL(0)) at ../../src/keyboard.c:1095
#72 0x0000000000646486 in internal_catch (tag=XIL(0xd2c0), func=0x56e798 <command_loop_2>, arg=XIL(0)) at ../../src/eval.c:1120
#73 0x000000000056e763 in command_loop () at ../../src/keyboard.c:1074
#74 0x000000000056dcd6 in recursive_edit_1 () at ../../src/keyboard.c:718
#75 0x000000000056dece in Frecursive_edit () at ../../src/keyboard.c:790
#76 0x0000000000569ea7 in main (argc=1, argv=0x7fffffffe7c8) at ../../src/emacs.c:2062

Lisp Backtrace:
"pgtk-selection-exists-p" (0xfffef658)
0xf41e98d0 PVEC_COMPILED
"apply" (0xfffefca0)
"gui-backend-selection-exists-p" (0xffff01d0)
0xf42b3c70 PVEC_COMPILED
"funcall" (0xffff0680)
"redisplay_internal (C function)" (0x0)



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 18:35               ` martin rudalics
@ 2020-11-25 23:06                 ` Tim Cross
  2020-11-26 15:44                   ` martin rudalics
  2020-11-26 13:39                 ` Yuuki Harano
  1 sibling, 1 reply; 90+ messages in thread
From: Tim Cross @ 2020-11-25 23:06 UTC (permalink / raw)
  To: martin rudalics; +Cc: Yuuki Harano, Emacs developers

[-- Attachment #1: Type: text/plain, Size: 9665 bytes --]

I had a similar issue. Starting emacs with the --no-x-resources fixed it
for me. This was not required when I first built it, only after I did a
pull update, so maybe same for you?

On Thu, 26 Nov 2020 at 05:44, martin rudalics <rudalics@gmx.at> wrote:

> Trying to run Emacs with my customizations I'm now getting a segfault at
> startup (this used to work with earlier checkouts).
>
> martin
>
>
> Thread 1 "emacs" received signal SIGSEGV, Segmentation fault.
> 0x0000000000551139 in hierarchy_ch_cb (widget=0x1356c50,
> previous_toplevel=0x24322d0, user_data=0xd99dc0) at ../../src/gtkutil.c:754
> 754       GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
> (gdb) bt
> #0  0x0000000000551139 in hierarchy_ch_cb (widget=0x1356c50,
> previous_toplevel=0x24322d0, user_data=0xd99dc0) at ../../src/gtkutil.c:754
> #1  0x00007ffff73eac8d in g_closure_invoke () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #2  0x00007ffff73fe365 in  () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #3  0x00007ffff74072be in g_signal_emit_valist () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #4  0x00007ffff740797f in g_signal_emit () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #5  0x00007ffff7c3b6d5 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #6  0x00007ffff7c3e877 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #7  0x00007ffff7c4cbdb in gtk_widget_unparent () at
> /lib/x86_64-linux-gnu/libgtk-3.so.0
> #8  0x00007ffff79e428b in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #9  0x00007ffff73edfff in g_cclosure_marshal_VOID__OBJECTv () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #10 0x00007ffff73eaec6 in  () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #11 0x00007ffff740738d in g_signal_emit_valist () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #12 0x00007ffff740797f in g_signal_emit () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #13 0x00007ffff7a2d6a6 in gtk_container_remove () at
> /lib/x86_64-linux-gnu/libgtk-3.so.0
> #14 0x00007ffff7bfcecc in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #15 0x00000000005512c2 in qttip_cb (widget=0x11c5f40, xpos=1125, ypos=402,
> keyboard_mode=0, tooltip=0x12b4980, user_data=0x1df16a8) at
> ../../src/gtkutil.c:786
> #16 0x00007ffff7c8e805 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #17 0x00007ffff73eac8d in g_closure_invoke () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #18 0x00007ffff73fe365 in  () at /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #19 0x00007ffff74069ab in g_signal_emit_valist () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #20 0x00007ffff740797f in g_signal_emit () at
> /lib/x86_64-linux-gnu/libgobject-2.0.so.0
> #21 0x00007ffff7c3caeb in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #22 0x00007ffff7bfbc69 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #23 0x00007ffff7bfc147 in  () at /lib/x86_64-linux-gnu/libgtk-3.so.0
> #24 0x00007ffff7afc945 in gtk_main_do_event () at
> /lib/x86_64-linux-gnu/libgtk-3.so.0
> #25 0x00007ffff77fe465 in  () at /lib/x86_64-linux-gnu/libgdk-3.so.0
> #26 0x00007ffff782f112 in  () at /lib/x86_64-linux-gnu/libgdk-3.so.0
> #27 0x00007ffff7308f2e in g_main_context_dispatch () at
> /lib/x86_64-linux-gnu/libglib-2.0.so.0
> #28 0x00007ffff73091c8 in  () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
> #29 0x00007ffff73094c2 in g_main_loop_run () at
> /lib/x86_64-linux-gnu/libglib-2.0.so.0
> #30 0x00007ffff7c63d9e in gtk_clipboard_wait_for_contents () at
> /lib/x86_64-linux-gnu/libgtk-3.so.0
> #31 0x00007ffff7c642d2 in gtk_clipboard_wait_is_text_available () at
> /lib/x86_64-linux-gnu/libgtk-3.so.0
> #32 0x000000000070d86c in Fpgtk_selection_exists_p (selection=XIL(0x120),
> terminal=XIL(0)) at ../../src/pgtkselect.c:478
> #33 0x000000000064b59e in funcall_subr (subr=0xc111e0
> <Spgtk_selection_exists_p>, numargs=1, args=0x7ffffffef658) at
> ../../src/eval.c:2884
> #34 0x000000000064b047 in Ffuncall (nargs=2, args=0x7ffffffef650) at
> ../../src/eval.c:2809
> #35 0x00000000006a3eec in exec_byte_code (bytestr=XIL(0x7ffff41e9984),
> vector=XIL(0x7ffff41e9925), maxdepth=make_fixnum(3),
> args_template=make_fixnum(257), nargs=1, args=0x7ffffffefcb0) at
> ../../src/bytecode.c:632
> #36 0x000000000064b7ff in fetch_and_exec_byte_code
> (fun=XIL(0x7ffff41e98d5), syms_left=make_fixnum(257), nargs=1,
> args=0x7ffffffefca8) at ../../src/eval.c:2931
> #37 0x000000000064bc85 in funcall_lambda (fun=XIL(0x7ffff41e98d5),
> nargs=1, arg_vector=0x7ffffffefca8) at ../../src/eval.c:3012
> #38 0x000000000064b08b in Ffuncall (nargs=2, args=0x7ffffffefca0) at
> ../../src/eval.c:2811
> #39 0x0000000000649eb9 in Fapply (nargs=2, args=0x7ffffffefca0) at
> ../../src/eval.c:2396
> #40 0x000000000064b471 in funcall_subr (subr=0xc0b0a0 <Sapply>, numargs=2,
> args=0x7ffffffefca0) at ../../src/eval.c:2862
> #41 0x000000000064b047 in Ffuncall (nargs=3, args=0x7ffffffefc98) at
> ../../src/eval.c:2809
> #42 0x00000000006a3eec in exec_byte_code (bytestr=XIL(0x7ffff41ea26c),
> vector=XIL(0x7ffff41e9715), maxdepth=make_fixnum(15),
> args_template=make_fixnum(128), nargs=1, args=0x7fffffff01d0) at
> ../../src/bytecode.c:632
> #43 0x000000000064b7ff in fetch_and_exec_byte_code
> (fun=XIL(0x7ffff41e96c5), syms_left=make_fixnum(128), nargs=1,
> args=0x7fffffff01d0) at ../../src/eval.c:2931
> #44 0x000000000064bc85 in funcall_lambda (fun=XIL(0x7ffff41e96c5),
> nargs=1, arg_vector=0x7fffffff01d0) at ../../src/eval.c:3012
> #45 0x000000000064b08b in Ffuncall (nargs=2, args=0x7fffffff01c8) at
> ../../src/eval.c:2811
> #46 0x00000000006a3eec in exec_byte_code (bytestr=XIL(0x7ffff42b3cdc),
> vector=XIL(0x7ffff42b3c9d), maxdepth=make_fixnum(2), args_template=XIL(0),
> nargs=0, args=0x0) at ../../src/bytecode.c:632
> #47 0x000000000064b7ff in fetch_and_exec_byte_code
> (fun=XIL(0x7ffff42b3c75), syms_left=XIL(0), nargs=0, args=0x0) at
> ../../src/eval.c:2931
> #48 0x000000000064c165 in funcall_lambda (fun=XIL(0x7ffff42b3c75),
> nargs=0, arg_vector=0x0) at ../../src/eval.c:3081
> #49 0x000000000064b08b in Ffuncall (nargs=1, args=0x7fffffff0680) at
> ../../src/eval.c:2811
> #50 0x000000000064974a in eval_sub (form=XIL(0x7ffff42b3c33)) at
> ../../src/eval.c:2258
> #51 0x0000000000648e83 in Feval (form=XIL(0x7ffff42b3c33), lexical=XIL(0))
> at ../../src/eval.c:2115
> #52 0x000000000057f9c3 in eval_dyn (form=XIL(0x7ffff42b3c33)) at
> ../../src/keyboard.c:7623
> #53 0x00000000006470ad in internal_condition_case_1 (bfun=0x57f99b
> <eval_dyn>, arg=XIL(0x7ffff42b3c33), handlers=XIL(0x90), hfun=0x57f91c
> <menu_item_eval_property_1>) at ../../src/eval.c:1383
> #54 0x000000000057fa24 in menu_item_eval_property
> (sexpr=XIL(0x7ffff42b3c33)) at ../../src/keyboard.c:7634
> #55 0x0000000000580a93 in parse_menu_item (item=XIL(0), inmenubar=0) at
> ../../src/keyboard.c:7816
> #56 0x00000000004bc12c in single_menu_item (key=XIL(0x7ffff362fa78),
> item=XIL(0x7ffff42b3ba3), dummy=XIL(0), skp_v=0x7fffffff0bf0) at
> ../../src/menu.c:323
> #57 0x000000000058f6f9 in map_keymap_item (fun=0x4bc0df
> <single_menu_item>, args=XIL(0), key=XIL(0x7ffff362fa78),
> val=XIL(0x7ffff42b3ba3), data=0x7fffffff0bf0) at ../../src/keymap.c:537
> #58 0x000000000058f9e3 in map_keymap_internal (map=XIL(0x11b8e93),
> fun=0x4bc0df <single_menu_item>, args=XIL(0), data=0x7fffffff0bf0) at
> ../../src/keymap.c:584
> #59 0x000000000058fd85 in map_keymap_canonical (map=XIL(0x11b8e93),
> fun=0x4bc0df <single_menu_item>, args=XIL(0), data=0x7fffffff0bf0) at
> ../../src/keymap.c:644
> #60 0x00000000004bbf59 in single_keymap_panes (keymap=XIL(0x7ffff42a0763),
> pane_name=XIL(0x7ffff42a0884), prefix=XIL(0x7ffff34e3b68), maxdepth=10) at
> ../../src/menu.c:292
> #61 0x00000000004bcf19 in parse_single_submenu
> (item_key=XIL(0x7ffff34e3b68), item_name=XIL(0x7ffff42a0884), maps=XIL(0))
> at ../../src/menu.c:556
> #62 0x000000000070ee27 in set_frame_menubar (f=0x1df16a8,
> first_time=false, deep_p=true) at ../../src/pgtkmenu.c:361
> #63 0x000000000046f3ab in update_menu_bar (f=0x1df16a8,
> save_match_data=false, hooks_run=true) at ../../src/xdisp.c:12834
> #64 0x000000000046eee1 in prepare_menu_bars () at ../../src/xdisp.c:12720
> #65 0x00000000004743b4 in redisplay_internal () at ../../src/xdisp.c:15582
> #66 0x0000000000473123 in redisplay () at ../../src/xdisp.c:15166
> #67 0x0000000000572a9e in read_char (commandflag=1, map=XIL(0x21077c3),
> prev_event=XIL(0), used_mouse_menu=0x7fffffffe15f, end_time=0x0) at
> ../../src/keyboard.c:2497
> #68 0x0000000000585685 in read_key_sequence (keybuf=0x7fffffffe2f0,
> prompt=XIL(0), dont_downcase_last=false, can_return_switch_frame=true,
> fix_current_buffer=true, prevent_redisplay=false) at
> ../../src/keyboard.c:9544
> #69 0x000000000056f05c in command_loop_1 () at ../../src/keyboard.c:1354
> #70 0x0000000000646fd2 in internal_condition_case (bfun=0x56ebe0
> <command_loop_1>, handlers=XIL(0x90), hfun=0x56e1ef <cmd_error>) at
> ../../src/eval.c:1359
> #71 0x000000000056e7c5 in command_loop_2 (ignore=XIL(0)) at
> ../../src/keyboard.c:1095
> #72 0x0000000000646486 in internal_catch (tag=XIL(0xd2c0), func=0x56e798
> <command_loop_2>, arg=XIL(0)) at ../../src/eval.c:1120
> #73 0x000000000056e763 in command_loop () at ../../src/keyboard.c:1074
> #74 0x000000000056dcd6 in recursive_edit_1 () at ../../src/keyboard.c:718
> #75 0x000000000056dece in Frecursive_edit () at ../../src/keyboard.c:790
> #76 0x0000000000569ea7 in main (argc=1, argv=0x7fffffffe7c8) at
> ../../src/emacs.c:2062
>
> Lisp Backtrace:
> "pgtk-selection-exists-p" (0xfffef658)
> 0xf41e98d0 PVEC_COMPILED
> "apply" (0xfffefca0)
> "gui-backend-selection-exists-p" (0xffff01d0)
> 0xf42b3c70 PVEC_COMPILED
> "funcall" (0xffff0680)
> "redisplay_internal (C function)" (0x0)
>
>

-- 
regards,

Tim

--
Tim Cross

[-- Attachment #2: Type: text/html, Size: 10408 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 18:35               ` martin rudalics
  2020-11-25 23:06                 ` Tim Cross
@ 2020-11-26 13:39                 ` Yuuki Harano
  2020-11-26 15:45                   ` martin rudalics
  1 sibling, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-11-26 13:39 UTC (permalink / raw)
  To: rudalics; +Cc: emacs-devel


On Wed, 25 Nov 2020 19:35:44 +0100,
	martin rudalics <rudalics@gmx.at> wrote:
> Trying to run Emacs with my customizations I'm now getting a segfault
> at
> startup (this used to work with earlier checkouts).

It does not reproduce.

What command line do you use to configure?  I used:

CFLAGS='-O3' ../configure --with-pgtk --without-x --with-cairo  \
--with-modules --with-gif=ifavailable --with-tiff=ifavailable --with-gnutls=no \
--without-pop --enable-gcc-warnings=warn-only --enable-checking='yes,glyphs'  \
--enable-check-lisp-object-type=yes

Also, how about `emacs -Q`?

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 23:06                 ` Tim Cross
@ 2020-11-26 15:44                   ` martin rudalics
  0 siblings, 0 replies; 90+ messages in thread
From: martin rudalics @ 2020-11-26 15:44 UTC (permalink / raw)
  To: Tim Cross; +Cc: Yuuki Harano, Emacs developers

 > I had a similar issue. Starting emacs with the --no-x-resources fixed it
 > for me. This was not required when I first built it, only after I did a
 > pull update, so maybe same for you?

This works for me too.

Thanks, martin



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-26 13:39                 ` Yuuki Harano
@ 2020-11-26 15:45                   ` martin rudalics
  2020-11-27 12:59                     ` Yuuki Harano
  0 siblings, 1 reply; 90+ messages in thread
From: martin rudalics @ 2020-11-26 15:45 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

 > It does not reproduce.

I expected that.

 > What command line do you use to configure?  I used:
 >
 > CFLAGS='-O3' ../configure --with-pgtk --without-x --with-cairo  \
 > --with-modules --with-gif=ifavailable --with-tiff=ifavailable --with-gnutls=no \
 > --without-pop --enable-gcc-warnings=warn-only --enable-checking='yes,glyphs'  \
 > --enable-check-lisp-object-type=yes

"CFLAGS='-O0 -g3 -no-pie' ../configure --with-pgtk --without-x
--with-cairo --with-modules --with-gif=ifavailable
--with-tiff=ifavailable --with-gnutls=no --without-pop
--enable-gcc-warnings=warn-only --enable-checking='yes,glyphs'
--enable-check-lisp-object-type=yes"

 > Also, how about `emacs -Q`?

Starts normally.  As Tim told, switching off x-resources helps.

martin



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-26 15:45                   ` martin rudalics
@ 2020-11-27 12:59                     ` Yuuki Harano
  2020-11-27 15:42                       ` martin rudalics
  0 siblings, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-11-27 12:59 UTC (permalink / raw)
  To: rudalics; +Cc: emacs-devel


On Thu, 26 Nov 2020 16:45:41 +0100,
	martin rudalics <rudalics@gmx.at> wrote:
> Starts normally.  As Tim told, switching off x-resources helps.

`-Q` inhibits x-resources.
When x-resources is inhibited, some startup processes is omitted.
I think your emacs crashes in one of them.

If ~/.emacs is empty, does your emacs crash?

Also,

On Wed, 25 Nov 2020 19:35:44 +0100,
	martin rudalics <rudalics@gmx.at> wrote:
> 0x0000000000551139 in hierarchy_ch_cb (widget=0x1356c50,
> previous_toplevel=0x24322d0, user_data=0xd99dc0) at
> ../../src/gtkutil.c:754
> 754	  GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
> (gdb) bt
> #0 0x0000000000551139 in hierarchy_ch_cb (widget=0x1356c50,
> #previous_toplevel=0x24322d0, user_data=0xd99dc0) at
> #../../src/gtkutil.c:754

the crashing code is as follows:

----------------
static void
hierarchy_ch_cb (GtkWidget *widget,
                 GtkWidget *previous_toplevel,
                 gpointer   user_data)
{
  struct frame *f = user_data;
  xp_output *x = f->output_data.xp;
  GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
----------------

When your emacs crashed, what value do these variables have?
- widget
- previous_toplevel
- user_data
- f
- x
- x->ttip_lbl

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-27 12:59                     ` Yuuki Harano
@ 2020-11-27 15:42                       ` martin rudalics
  2020-11-27 15:52                         ` Yuuki Harano
  0 siblings, 1 reply; 90+ messages in thread
From: martin rudalics @ 2020-11-27 15:42 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

 >> Starts normally.  As Tim told, switching off x-resources helps.
 >
 > `-Q` inhibits x-resources.
 > When x-resources is inhibited, some startup processes is omitted.
 > I think your emacs crashes in one of them.
 >
 > If ~/.emacs is empty, does your emacs crash?

I meanwhile rebuilt from latest sources and can happily report that
neither an O3 nor an O0 build crash any more here.

Thanks for caring, martin



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-27 15:42                       ` martin rudalics
@ 2020-11-27 15:52                         ` Yuuki Harano
  0 siblings, 0 replies; 90+ messages in thread
From: Yuuki Harano @ 2020-11-27 15:52 UTC (permalink / raw)
  To: rudalics; +Cc: emacs-devel


On Fri, 27 Nov 2020 16:42:49 +0100,
	martin rudalics <rudalics@gmx.at> wrote:
> I meanwhile rebuilt from latest sources and can happily report that
> neither an O3 nor an O0 build crash any more here.

OK, thanks!

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-25 17:31           ` Eric Abrahamsen
@ 2020-11-27 16:07             ` Yuuki Harano
  2020-11-27 17:47               ` Eric Abrahamsen
  0 siblings, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2020-11-27 16:07 UTC (permalink / raw)
  To: eric; +Cc: emacs-devel

Hi,

On Wed, 25 Nov 2020 09:31:09 -0800,
	Eric Abrahamsen <eric@ericabrahamsen.net> wrote:
> I'm using this on Arch Linux, under the sway (Wayland) tiling window
> manager. So far the only oddness I've seen is that if I toggle either of
> `tool-bar-mode' or `menu-bar-mode' (either via M-x or M-:), the frame(s)
> "freeze" and are not redrawn until I move a frame or change focus. While
> frozen, the frames/windows still accept input and process it correctly.

I can reproduce that.  It is Gtk3's issue.
It should be fixed in Gtk 3.24.24, which is not released yet.

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-27 16:07             ` Yuuki Harano
@ 2020-11-27 17:47               ` Eric Abrahamsen
  2020-12-07 23:50                 ` Eric Abrahamsen
  0 siblings, 1 reply; 90+ messages in thread
From: Eric Abrahamsen @ 2020-11-27 17:47 UTC (permalink / raw)
  To: emacs-devel

Yuuki Harano <masm+emacs@masm11.me> writes:

> Hi,
>
> On Wed, 25 Nov 2020 09:31:09 -0800,
> 	Eric Abrahamsen <eric@ericabrahamsen.net> wrote:
>> I'm using this on Arch Linux, under the sway (Wayland) tiling window
>> manager. So far the only oddness I've seen is that if I toggle either of
>> `tool-bar-mode' or `menu-bar-mode' (either via M-x or M-:), the frame(s)
>> "freeze" and are not redrawn until I move a frame or change focus. While
>> frozen, the frames/windows still accept input and process it correctly.
>
> I can reproduce that.  It is Gtk3's issue.
> It should be fixed in Gtk 3.24.24, which is not released yet.

Okay! Thanks for checking, and I'll look forward to the new gtk.




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-27 17:47               ` Eric Abrahamsen
@ 2020-12-07 23:50                 ` Eric Abrahamsen
  2020-12-11  3:40                   ` 황병희
  0 siblings, 1 reply; 90+ messages in thread
From: Eric Abrahamsen @ 2020-12-07 23:50 UTC (permalink / raw)
  To: emacs-devel

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

> Yuuki Harano <masm+emacs@masm11.me> writes:
>
>> Hi,
>>
>> On Wed, 25 Nov 2020 09:31:09 -0800,
>> 	Eric Abrahamsen <eric@ericabrahamsen.net> wrote:
>>> I'm using this on Arch Linux, under the sway (Wayland) tiling window
>>> manager. So far the only oddness I've seen is that if I toggle either of
>>> `tool-bar-mode' or `menu-bar-mode' (either via M-x or M-:), the frame(s)
>>> "freeze" and are not redrawn until I move a frame or change focus. While
>>> frozen, the frames/windows still accept input and process it correctly.
>>
>> I can reproduce that.  It is Gtk3's issue.
>> It should be fixed in Gtk 3.24.24, which is not released yet.
>
> Okay! Thanks for checking, and I'll look forward to the new gtk.

FYI, Arch's gtk package just updated to 3.24.24 and this problem is no
longer visible.




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-12-07 23:50                 ` Eric Abrahamsen
@ 2020-12-11  3:40                   ` 황병희
  0 siblings, 0 replies; 90+ messages in thread
From: 황병희 @ 2020-12-11  3:40 UTC (permalink / raw)
  To: emacs-devel

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>
>> Yuuki Harano <masm+emacs@masm11.me> writes:
>>
>>> Hi,
>>>
>>> On Wed, 25 Nov 2020 09:31:09 -0800,
>>> 	Eric Abrahamsen <eric@ericabrahamsen.net> wrote:
>>>> I'm using this on Arch Linux, under the sway (Wayland) tiling window
>>>> manager. So far the only oddness I've seen is that if I toggle either of
>>>> `tool-bar-mode' or `menu-bar-mode' (either via M-x or M-:), the frame(s)
>>>> "freeze" and are not redrawn until I move a frame or change focus. While
>>>> frozen, the frames/windows still accept input and process it correctly.
>>>
>>> I can reproduce that.  It is Gtk3's issue.
>>> It should be fixed in Gtk 3.24.24, which is not released yet.
>>
>> Okay! Thanks for checking, and I'll look forward to the new gtk.
>
> FYI, Arch's gtk package just updated to 3.24.24 and this problem is no
> longer visible.

All the time, i watch carefully this threads, thanks Eric^^^

Sincerely, Wayland and Gnus fan Byung-Hee

-- 
^고맙습니다 _和合團結_ 감사합니다_^))//



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2020-11-24 13:12         ` Yuuki Harano
                             ` (2 preceding siblings ...)
  2020-11-25 17:31           ` Eric Abrahamsen
@ 2021-01-02 22:43           ` Dmitry Gutov
  2021-01-03  1:18             ` 황병희
                               ` (2 more replies)
  3 siblings, 3 replies; 90+ messages in thread
From: Dmitry Gutov @ 2021-01-02 22:43 UTC (permalink / raw)
  To: Yuuki Harano, eliz, rpluim; +Cc: emacs-devel

Hi!

On 24.11.2020 15:12, Yuuki Harano wrote:
> I pushed as feature/pgtk.

I've tried it out, it's pretty exciting.

The child frames seem to be displayed and resized faster. That's good.

Font rendering seems... somehow different. Looking at the screenshots, 
the horizontal spacing between the characters is lower. That's on X11, 
no Wayland.

The tool-bar shows "old" icons, and not the ones from my GTK3 theme. 
This probably just misses some integration.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-02 22:43           ` Dmitry Gutov
@ 2021-01-03  1:18             ` 황병희
  2021-01-03  3:11               ` Jose A. Ortega Ruiz
  2021-01-03  9:53             ` Daniele Nicolodi
  2021-01-10 14:10             ` Yuuki Harano
  2 siblings, 1 reply; 90+ messages in thread
From: 황병희 @ 2021-01-03  1:18 UTC (permalink / raw)
  To: emacs-devel

Dmitry Gutov <dgutov@yandex.ru> writes:

> Hi!
>
> On 24.11.2020 15:12, Yuuki Harano wrote:
>> I pushed as feature/pgtk.
>
> I've tried it out, it's pretty exciting.
>
> The child frames seem to be displayed and resized faster. That's good.
>
> Font rendering seems... somehow different. Looking at the screenshots,
> the horizontal spacing between the characters is lower. That's on X11, 
> no Wayland.
>
> The tool-bar shows "old" icons, and not the ones from my GTK3
> theme. This probably just misses some integration.

Thanks for testing Dmitry^^^ and i really like that people using pgtk
emacs because i think Wayland will be the next linux distro's soil.

Sincerely, Wayland fan Byung-Hee

-- 
^고맙습니다 _布德天下_ 감사합니다_^))//



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-03  1:18             ` 황병희
@ 2021-01-03  3:11               ` Jose A. Ortega Ruiz
  2021-01-03  8:13                 ` 황병희
  0 siblings, 1 reply; 90+ messages in thread
From: Jose A. Ortega Ruiz @ 2021-01-03  3:11 UTC (permalink / raw)
  To: emacs-devel

On Sun, Jan 03 2021, 황병희 wrote:

[...]

> Thanks for testing Dmitry^^^ and i really like that people using pgtk
> emacs because i think Wayland will be the next linux distro's soil.

FWIW, i've been using the pgtk variant with sway for a while, and it's
working well for me, no show-stoppers so far.  it sure feels snappier
than x11. font rendering looks pretty similar to me; if anything, i'd
say it's a tad crispier.

two minor glitches: i use transparency, and sometimes i've had to
re-enable my theme after startup for it to really be set at the level
i'm asking (95%); and emacs doesn't seem to realize that i'm using a
dark gtk theme when it chooses its default background/foreground (but i
don't think that works for me in X/exwm either, although there i'm just
using startx to launch the "desktop").

in all other regards, pgtk is working remarkably smoothly, and the only
disadvantage is that it seems a bit memory hungry: it easly goes to
~1.5Gb of RES RAM with the same setup and usage that makes the X version
consume ~800Mb compiled without any toolkit (and that's with exwm, so in
X11 emacs is also the window manager).

but it's definitely a pleasure to have emacs working on pure wayland,
and i find myself wondering how hard would it be to port also exwm
(which uses xcb, i think) so that i could get rid of sway too :) 

so many thanks and kudos to the developers, and please keep up the good
work!

cheers,
jao
-- 
More people would learn from their mistakes if they weren't so busy
denying them - Harlod J. Smith




^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-03  3:11               ` Jose A. Ortega Ruiz
@ 2021-01-03  8:13                 ` 황병희
  0 siblings, 0 replies; 90+ messages in thread
From: 황병희 @ 2021-01-03  8:13 UTC (permalink / raw)
  To: emacs-devel

"Jose A. Ortega Ruiz" <jao@gnu.org> writes:

> On Sun, Jan 03 2021, 황병희 wrote:
>
> [...]
>
>> Thanks for testing Dmitry^^^ and i really like that people using pgtk
>> emacs because i think Wayland will be the next linux distro's soil.
>
> FWIW, i've been using the pgtk variant with sway for a while, and it's
> working well for me, no show-stoppers so far.  it sure feels snappier
> than x11. font rendering looks pretty similar to me; if anything, i'd
> say it's a tad crispier.
>
> two minor glitches: i use transparency, and sometimes i've had to
> re-enable my theme after startup for it to really be set at the level
> i'm asking (95%); and emacs doesn't seem to realize that i'm using a
> dark gtk theme when it chooses its default background/foreground (but i
> don't think that works for me in X/exwm either, although there i'm just
> using startx to launch the "desktop").
>
> in all other regards, pgtk is working remarkably smoothly, and the only
> disadvantage is that it seems a bit memory hungry: it easly goes to
> ~1.5Gb of RES RAM with the same setup and usage that makes the X version
> consume ~800Mb compiled without any toolkit (and that's with exwm, so in
> X11 emacs is also the window manager).
>
> but it's definitely a pleasure to have emacs working on pure wayland,
> and i find myself wondering how hard would it be to port also exwm
> (which uses xcb, i think) so that i could get rid of sway too :) 
>
> so many thanks and kudos to the developers, and please keep up the good
> work!

Dear jao,

Wow! i am happy to know your sharing. To me, yours is really good
example using Wayland and Emacs. Thanks! Thanks! Thanks!

However still i am using X11 in Ubuntu 18.04 LTS. Debian 11 Bullseye
will be my next distro because i heard that Bullseye will have Wayland
by default in start-time. When Bullseye is out, at then i would like to
do testing this pgtk emacs (maybe 28.1?) ^^^

Sincerely, Wayland fan Byung-Hee

-- 
^고맙습니다 _布德天下_ 감사합니다_^))//



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-02 22:43           ` Dmitry Gutov
  2021-01-03  1:18             ` 황병희
@ 2021-01-03  9:53             ` Daniele Nicolodi
  2021-01-03 12:02               ` Dmitry Gutov
  2021-01-10 14:10             ` Yuuki Harano
  2 siblings, 1 reply; 90+ messages in thread
From: Daniele Nicolodi @ 2021-01-03  9:53 UTC (permalink / raw)
  To: Dmitry Gutov, Yuuki Harano, eliz, rpluim; +Cc: emacs-devel

On 02/01/2021 23:43, Dmitry Gutov wrote:
> The tool-bar shows "old" icons, and not the ones from my GTK3 theme. 
> This probably just misses some integration.

AFAIK, Emacs never uses GTK provided icons but its own set of icons.
IIRC, this is because newer GTK themes are distributed with licenses
that are not considered adequate. Thus what you see is by design.

Cheers,
Dan



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-03  9:53             ` Daniele Nicolodi
@ 2021-01-03 12:02               ` Dmitry Gutov
  0 siblings, 0 replies; 90+ messages in thread
From: Dmitry Gutov @ 2021-01-03 12:02 UTC (permalink / raw)
  To: Daniele Nicolodi, Yuuki Harano, eliz, rpluim; +Cc: emacs-devel

On 03.01.2021 11:53, Daniele Nicolodi wrote:
> AFAIK, Emacs never uses GTK provided icons but its own set of icons.
> IIRC, this is because newer GTK themes are distributed with licenses
> that are not considered adequate. Thus what you see is by design.

There is no problem with using a GTK theme, especially one that is 
already installed on the user's system.

And the X11 GTK3 build of Emacs does that. The pgtk one doesn't.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-02 22:43           ` Dmitry Gutov
  2021-01-03  1:18             ` 황병희
  2021-01-03  9:53             ` Daniele Nicolodi
@ 2021-01-10 14:10             ` Yuuki Harano
  2021-01-11  2:52               ` Dmitry Gutov
  2 siblings, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2021-01-10 14:10 UTC (permalink / raw)
  To: dgutov; +Cc: emacs-devel


On Sun, 3 Jan 2021 00:43:17 +0200,
	Dmitry Gutov <dgutov@yandex.ru> wrote:
> The tool-bar shows "old" icons, and not the ones from my GTK3
> theme. This probably just misses some integration.

It should be fixed in current feature/pgtk.

Thanks for the report.
-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-10 14:10             ` Yuuki Harano
@ 2021-01-11  2:52               ` Dmitry Gutov
  2021-01-11  2:59                 ` Thien-Thi Nguyen
  2021-01-11 11:58                 ` Yuuki Harano
  0 siblings, 2 replies; 90+ messages in thread
From: Dmitry Gutov @ 2021-01-11  2:52 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

On 10.01.2021 16:10, Yuuki Harano wrote:
>> The tool-bar shows "old" icons, and not the ones from my GTK3
>> theme. This probably just misses some integration.
> It should be fixed in current feature/pgtk.

Looks fixed, thank you!

A bit uneasy about the added duplication, but I'll let others judge that.

Another thing that seems a bit irritating: the C-g ding causes a 
full-frame flash. Any chance to have that similar to X11 Emacs (at least 
as an option)? Only the first line of the frame flashes there.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-11  2:52               ` Dmitry Gutov
@ 2021-01-11  2:59                 ` Thien-Thi Nguyen
  2021-01-11  3:06                   ` Dmitry Gutov
  2021-01-11 11:58                 ` Yuuki Harano
  1 sibling, 1 reply; 90+ messages in thread
From: Thien-Thi Nguyen @ 2021-01-11  2:59 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Yuuki Harano, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1124 bytes --]


() Dmitry Gutov <dgutov@yandex.ru>
() Mon, 11 Jan 2021 04:52:39 +0200

   Another thing that seems a bit irritating: the C-g ding
   causes a full-frame flash. Any chance to have that similar to
   X11 Emacs (at least as an option)?  Only the first line of
   the frame flashes there.

Hmm, w/ emacs -Q and visible-bell t on X11 Emacs, i see both
first and last line flashing.  Tangent: I wonder if something
like this:

 (defun flash-mode-line ()
   "Invert ‘mode-line’ face fg/bg; wait a moment; invert back.
 This function is intended to be the value of ‘ring-bell-function’."
   (interactive)
   (invert-face 'mode-line)
   (sit-for 0.042)
   (invert-face 'mode-line))

has already made it into Emacs proper...

-- 
Thien-Thi Nguyen -----------------------------------------------
 (defun responsep (query)               ; (2021) Software Libero
   (pcase (context query)               ;       = Dissenso Etico
     (`(technical ,ml) (correctp ml))
     ...))                              748E A0E8 1CB8 A748 9BFA
--------------------------------------- 6CE4 6703 2224 4C80 7502


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 219 bytes --]

^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-11  2:59                 ` Thien-Thi Nguyen
@ 2021-01-11  3:06                   ` Dmitry Gutov
  0 siblings, 0 replies; 90+ messages in thread
From: Dmitry Gutov @ 2021-01-11  3:06 UTC (permalink / raw)
  To: Thien-Thi Nguyen; +Cc: Yuuki Harano, emacs-devel

On 11.01.2021 04:59, Thien-Thi Nguyen wrote:
> Hmm, w/ emacs -Q and visible-bell t on X11 Emacs, i see both
> first and last line flashing.

I actually see 2 first lines flashing, and no last line.

Could be a HiDPI bug.



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-11  2:52               ` Dmitry Gutov
  2021-01-11  2:59                 ` Thien-Thi Nguyen
@ 2021-01-11 11:58                 ` Yuuki Harano
  2021-01-11 15:01                   ` Dmitry Gutov
  1 sibling, 1 reply; 90+ messages in thread
From: Yuuki Harano @ 2021-01-11 11:58 UTC (permalink / raw)
  To: dgutov; +Cc: emacs-devel


On Mon, 11 Jan 2021 04:52:39 +0200,
	Dmitry Gutov <dgutov@yandex.ru> wrote:
> Another thing that seems a bit irritating: the C-g ding causes a
> full-frame flash.

I pushed a commit.  It may fix the flash.
Could you try again?

-- 
Yuuki Harano



^ permalink raw reply	[flat|nested] 90+ messages in thread

* Re: emacs for pure Gtk3
  2021-01-11 11:58                 ` Yuuki Harano
@ 2021-01-11 15:01                   ` Dmitry Gutov
  0 siblings, 0 replies; 90+ messages in thread
From: Dmitry Gutov @ 2021-01-11 15:01 UTC (permalink / raw)
  To: Yuuki Harano; +Cc: emacs-devel

On 11.01.2021 13:58, Yuuki Harano wrote:
> 
> On Mon, 11 Jan 2021 04:52:39 +0200,
> 	Dmitry Gutov <dgutov@yandex.ru> wrote:
>> Another thing that seems a bit irritating: the C-g ding causes a
>> full-frame flash.
> 
> I pushed a commit.  It may fix the flash.
> Could you try again?

Thanks, looks good now, first and last lines are highlighted.

Another improvement over X11 on HiDPI is the fringes seem scaled 2x? 
That's probably a good thing, even though the icons look a bit blurry.

Probably nothing we can do until we support something like SVG in the 
fringes.

Could someone clarify about the fonts, though? The rendering is not bad, 
and I could probably live with the reduced spacing between characters, 
but since the behavior is indeed close, could there be just some minor 
bug lurking in there?



^ permalink raw reply	[flat|nested] 90+ messages in thread

end of thread, other threads:[~2021-01-11 15:01 UTC | newest]

Thread overview: 90+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-26  7:56 emacs for pure Gtk3 Yuuki Harano
2020-04-26  8:52 ` 조성빈
2020-04-26  9:35   ` Yuuki Harano
2020-04-26  9:52   ` Yuuki Harano
2020-04-26 14:01 ` Eli Zaretskii
2020-04-27 12:37   ` Yuuki Harano
2020-04-27 15:03     ` Eli Zaretskii
2020-04-28 13:42       ` Yuuki Harano
2020-04-28 14:34         ` Eli Zaretskii
2020-04-28 20:09           ` Alan Third
2020-04-29  8:34           ` Yuuki Harano
2020-04-29  6:16         ` Po Lu
2020-04-29  7:46           ` Yuuki Harano
2020-04-29  8:05             ` Po Lu
2020-04-27 15:35     ` Yuuki Harano
2020-11-17 14:50   ` Yuuki Harano
2020-11-17 15:24     ` Eli Zaretskii
2020-11-17 17:24       ` Robert Pluim
2020-11-24 13:12         ` Yuuki Harano
2020-11-24 14:41           ` Robert Pluim
2020-11-25 12:24             ` Yuuki Harano
2020-11-25 13:30               ` Robert Pluim
2020-11-24 19:25           ` martin rudalics
2020-11-25 12:19             ` Yuuki Harano
2020-11-25 13:22               ` martin rudalics
2020-11-25 18:35               ` martin rudalics
2020-11-25 23:06                 ` Tim Cross
2020-11-26 15:44                   ` martin rudalics
2020-11-26 13:39                 ` Yuuki Harano
2020-11-26 15:45                   ` martin rudalics
2020-11-27 12:59                     ` Yuuki Harano
2020-11-27 15:42                       ` martin rudalics
2020-11-27 15:52                         ` Yuuki Harano
2020-11-25 17:31           ` Eric Abrahamsen
2020-11-27 16:07             ` Yuuki Harano
2020-11-27 17:47               ` Eric Abrahamsen
2020-12-07 23:50                 ` Eric Abrahamsen
2020-12-11  3:40                   ` 황병희
2021-01-02 22:43           ` Dmitry Gutov
2021-01-03  1:18             ` 황병희
2021-01-03  3:11               ` Jose A. Ortega Ruiz
2021-01-03  8:13                 ` 황병희
2021-01-03  9:53             ` Daniele Nicolodi
2021-01-03 12:02               ` Dmitry Gutov
2021-01-10 14:10             ` Yuuki Harano
2021-01-11  2:52               ` Dmitry Gutov
2021-01-11  2:59                 ` Thien-Thi Nguyen
2021-01-11  3:06                   ` Dmitry Gutov
2021-01-11 11:58                 ` Yuuki Harano
2021-01-11 15:01                   ` Dmitry Gutov
2020-11-20 12:04       ` Yuuki Harano
2020-11-20 12:16         ` Eli Zaretskii
2020-11-25  2:17         ` Zhu Zihao
2020-11-25 10:02           ` Robert Pluim
2020-11-19  3:18     ` 황병희
2020-11-20  4:23       ` Tim Cross
2020-11-20  4:39         ` Eric Abrahamsen
2020-11-20  7:11           ` Tim Cross
2020-11-20  7:29             ` Jean Louis
2020-11-21 23:35               ` Tim Cross
2020-11-22  1:49                 ` 황병희
2020-11-20 16:33             ` Eric Abrahamsen
2020-04-26 18:00 ` martin rudalics
2020-04-26 18:43   ` Stefan Monnier
2020-04-27 15:43   ` Yuuki Harano
2020-04-28  8:32     ` martin rudalics
2020-04-27  2:33 ` 황병희
2020-04-27  8:37 ` Po Lu via Emacs development discussions.
2020-04-27 16:08   ` Yuuki Harano
2020-04-27 23:47     ` Po Lu
2020-04-27 23:49       ` Po Lu
2020-04-28  0:05       ` Dmitry Gutov
2020-04-28  6:08         ` Po Lu
2020-04-28  7:37           ` Eli Zaretskii
2020-04-29  6:13             ` Po Lu
2020-04-29  6:28   ` Po Lu
2020-04-29  8:12     ` Yuuki Harano
2020-04-30  0:15       ` Po Lu
2020-04-28  0:51 ` Daniele Nicolodi
2020-04-29  1:14 ` Andrew Cohen
2020-04-29 13:01 ` Robert Pluim
2020-04-29 15:03   ` martin rudalics
2020-04-29 15:17   ` Yuuki Harano
2020-04-29 16:58     ` Robert Pluim
  -- strict thread matches above, loose matches on Subject: below --
2020-04-27 11:54 Jeff Walsh
2020-04-28  8:32 ` martin rudalics
2020-04-28  3:19 Jeff Walsh
2020-04-28  7:27 ` Eli Zaretskii
2020-05-08  6:54 ` Jostein Kjønigsen
2020-05-08  6:59   ` Eli Zaretskii

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).