Skip to content

Commit 71a5826

Browse files
danyeawMuhammadMuradGfreakboy3742
authored
Add BeeWare Tutorial 1 support for GTK4 (beeware#3087)
Adds minimal support for GTK4 to the GTK backend. If the user explicitly defines `TOGA_GTK=4` in their environment, the app will try to use GTK4 libraries instead of GTK3 libraries. At this time, support is sufficient to start an app and show a window, but not much else. Co-authored-by: Muhammad Murad <[email protected]> Co-authored-by: Russell Keith-Magee <[email protected]>
1 parent 34ea3a4 commit 71a5826

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1870
-946
lines changed

.github/workflows/ci.yml

+36-5
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ jobs:
256256
- "macOS-x86_64"
257257
- "macOS-arm64"
258258
- "windows"
259-
- "linux-x11"
260-
- "linux-wayland"
259+
- "linux-x11-gtk3"
260+
- "linux-wayland-gtk3"
261+
- "linux-wayland-gtk4"
261262
- "android"
262263
- "iOS"
263264
- "textual-linux"
@@ -282,7 +283,7 @@ jobs:
282283
# We use a fixed Ubuntu version rather than `-latest` because at some point,
283284
# `-latest` will be updated, but it will be a soft changeover, which would cause
284285
# the system Python version to become inconsistent from run to run.
285-
- backend: "linux-x11"
286+
- backend: "linux-x11-gtk3"
286287
platform: "linux"
287288
runs-on: "ubuntu-24.04"
288289
# The package list should be the same as in unix-prerequisites.rst, and the BeeWare
@@ -311,7 +312,7 @@ jobs:
311312
setup-python: false # Use the system Python packages
312313
app-user-data-path: "$HOME/.local/share/testbed"
313314

314-
- backend: "linux-wayland"
315+
- backend: "linux-wayland-gtk3"
315316
platform: "linux"
316317
runs-on: "ubuntu-24.04"
317318
# The package list should be the same as in unix-prerequisites.rst, and the BeeWare
@@ -330,14 +331,44 @@ jobs:
330331
# Start Window Manager
331332
echo "Start window manager..."
332333
# mutter is being run inside a virtual X server because mutter's headless
333-
# mode is not compatible with Gtk
334+
# mode does not provide a Gdk.Display
334335
DISPLAY=:99 MUTTER_DEBUG_DUMMY_MODE_SPECS=2048x1536 \
335336
mutter --nested --wayland --no-x11 --wayland-display toga &
336337
sleep 1
337338
briefcase-run-prefix: "WAYLAND_DISPLAY=toga"
338339
setup-python: false # Use the system Python packages
339340
app-user-data-path: "$HOME/.local/share/testbed"
340341

342+
- backend: "linux-wayland-gtk4"
343+
platform: "linux"
344+
runs-on: "ubuntu-24.04"
345+
env:
346+
XDG_RUNTIME_DIR: "/tmp"
347+
# The package list should be the same as in unix-prerequisites.rst, and the BeeWare
348+
# tutorial, plus mutter to provide a window manager.
349+
pre-command: |
350+
sudo apt update -y
351+
sudo apt install -y --no-install-recommends \
352+
mutter pkg-config python3-dev libgirepository1.0-dev libcairo2-dev \
353+
gir1.2-webkit-6.0 gir1.2-xapp-1.0 gir1.2-geoclue-2.0 gir1.2-flatpak-1.0 \
354+
gir1.2-gtk-4.0
355+
356+
# Start Virtual X Server
357+
echo "Start X server..."
358+
Xvfb :99 -screen 0 2048x1536x24 &
359+
sleep 1
360+
361+
# Start Window Manager
362+
echo "Start window manager..."
363+
# mutter is being run inside a virtual X server because mutter's headless
364+
# mode does not provide a Gdk.Display
365+
DISPLAY=:99 MUTTER_DEBUG_DUMMY_MODE_SPECS=2048x1536 \
366+
mutter --nested --wayland --no-x11 --wayland-display toga &
367+
sleep 1
368+
briefcase-run-prefix: "WAYLAND_DISPLAY=toga TOGA_GTK=4"
369+
setup-python: false # Use the system Python packages
370+
app-user-data-path: "$HOME/.local/share/testbed"
371+
341372
- backend: "textual-linux"
342373
platform: "linux"
343374
runs-on: "ubuntu-latest"

android/tests_backend/window.py

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
class WindowProbe(BaseProbe, DialogsMixin):
1313
supports_fullscreen = True
1414
supports_presentation = True
15+
supports_as_image = True
16+
supports_focus = True
1517

1618
def __init__(self, app, window):
1719
super().__init__(app)

changes/3087.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Initial experimental support for GTK4 has been added to Toga's GTK backend. This support can be enabled by setting ``TOGA_GTK=4`` in your environment.

cocoa/tests_backend/window.py

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class WindowProbe(BaseProbe, DialogsMixin):
1616
supports_unminimize = True
1717
supports_minimize = True
1818
supports_placement = True
19+
supports_as_image = True
20+
supports_focus = True
1921

2022
def __init__(self, app, window):
2123
super().__init__()

docs/how-to/contribute/code.rst

+24
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,25 @@ that you could try to implement.
447447
Again, you'll need to add unit tests and/or backend probes for any new features
448448
you add.
449449

450+
Contribute to the GTK4 update
451+
-----------------------------
452+
453+
Toga's GTK support is currently based on the GTK3 API. This API works, and ships with
454+
most Linux distributions, but is no longer maintained by the GTK team. We're in the
455+
process of adding GTK4 support to Toga's GTK backend. You can help with this update
456+
process.
457+
458+
GTK4 support can be enabled by setting the ``TOGA_GTK=4`` environment variable. To
459+
contribute to the update, pick a widget that currently has GTK3 support, and try
460+
updating the widget's API to support GTK4 as well. You can identify a widget that hasn't
461+
been ported by looking at the :ref:`GTK probe for the widget <testbed-probe>` - widgets
462+
that aren't ported yet will have an "if GTK4, skip" block at the top of the probe
463+
definition.
464+
465+
The code needs to support both GTK3 and GTK4; if there are significant differences in
466+
API, you can add conditional branches based on the GTK version. See one of the widgets
467+
that *has* been ported (e.g., Label) for examples of how this can be done.
468+
450469
Implement an entirely new platform backend
451470
------------------------------------------
452471

@@ -832,6 +851,11 @@ run``.
832851
You can also use slow mode or pytest specifiers with ``briefcase run``, using
833852
the same ``--`` syntax as you used in developer mode.
834853

854+
Finally, if you would like to run the tests against GTK4 on Linux, set the
855+
environmental variable ``TOGA_GTK=4``. This is experimental and only partially
856+
implemented, but we would greatly appreciate your help translating widgets from
857+
GTK3 to GTK4.
858+
835859
.. _testbed-probe:
836860

837861
How the testbed works

docs/reference/api/widgets/mapview.rst

+5
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ System requirements
116116
- OpenSUSE Tumbleweed: ``libwebkit2gtk3 typelib(WebKit2)``
117117
- FreeBSD: ``webkit2-gtk3``
118118

119+
MapView is not fully supported on GTK4. If you want to contribute to the GTK4 MapView
120+
implementation, you will require v6.0 of the WebKit2 libraries. This is provided by
121+
``gir1.2-webkit-6.0`` on Ubuntu/Debian, and ``webkitgtk6.0`` on Fedora; for other
122+
distributions, consult your distributions's platform documentation.
123+
119124
* Using MapView on Android requires the OSMDroid package in your project's Gradle
120125
dependencies. Ensure your app declares a dependency on
121126
``org\.osmdroid:osmdroid-android:6.1.20`` or later.

docs/reference/api/widgets/webview.rst

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ System requirements
8383
- OpenSUSE Tumbleweed: ``libwebkit2gtk3 typelib(WebKit2)``
8484
- FreeBSD: ``webkit2-gtk3``
8585

86+
WebView is not fully supported on GTK4. If you want to contribute to the GTK4 WebView
87+
implementation, you will require v6.0 of the WebKit2 libraries. This is provided by
88+
``gir1.2-webkit-6.0`` on Ubuntu/Debian, and ``webkitgtk6.0`` on Fedora; for other
89+
distributions, consult your distributions's platform documentation.
90+
8691
Notes
8792
-----
8893

docs/reference/platforms/unix-prerequisites.rst

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ If you're not using one of these, you'll need to work out how to install the dev
4141
libraries for ``python3``, ``cairo``, and ``gobject-introspection`` (and please let us
4242
know so we can improve this documentation!)
4343

44+
In addition to the dependencies above, if you would like to help add additional support
45+
for GTK4, you need to also install ``gir1.2-gtk-4.0`` on Ubuntu/Debian, or ``gtk4`` on
46+
Fedora or Arch. For other distributions, consult your distributions's platform
47+
documentation.
48+
4449
Some widgets (most notably, the :ref:`WebView <webview-system-requires>` and
4550
:ref:`MapView <mapview-system-requires>` widgets) have additional system requirements.
4651
Likewise, certain hardware features (:ref:`Location <location-system-requires>`) have

gtk/src/toga_gtk/app.py

+37-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .keys import gtk_accel
88
from .libs import (
9+
GTK_VERSION,
910
IS_WAYLAND,
1011
TOGA_DEFAULT_STYLES,
1112
Gdk,
@@ -34,7 +35,7 @@ def __init__(self, interface):
3435
# Stimulate the build of the app
3536
self.native = Gtk.Application(
3637
application_id=self.interface.app_id,
37-
flags=Gio.ApplicationFlags.FLAGS_NONE,
38+
flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
3839
)
3940
self.native_about_dialog = None
4041

@@ -53,12 +54,20 @@ def gtk_startup(self, data=None):
5354

5455
# Set any custom styles
5556
css_provider = Gtk.CssProvider()
56-
css_provider.load_from_data(TOGA_DEFAULT_STYLES)
5757

58-
context = Gtk.StyleContext()
59-
context.add_provider_for_screen(
60-
Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER
61-
)
58+
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
59+
css_provider.load_from_data(TOGA_DEFAULT_STYLES)
60+
context = Gtk.StyleContext()
61+
context.add_provider_for_screen(
62+
Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER
63+
)
64+
elif GTK_VERSION >= (4, 12, 0): # pragma: no-cover-if-gtk3
65+
css_provider.load_from_string(TOGA_DEFAULT_STYLES)
66+
elif GTK_VERSION >= (4, 8, 0): # pragma: no-cover-if-gtk3
67+
css_provider.load_from_data(TOGA_DEFAULT_STYLES, len(TOGA_DEFAULT_STYLES))
68+
else: # pragma: no-cover-if-gtk3
69+
# Earlier than GTK 4.8
70+
css_provider.load_from_data(TOGA_DEFAULT_STYLES.encode("utf-8"))
6271

6372
######################################################################
6473
# Commands and menus
@@ -173,20 +182,24 @@ def set_main_window(self, window):
173182

174183
def get_screens(self):
175184
display = Gdk.Display.get_default()
176-
if IS_WAYLAND: # pragma: no-cover-if-linux-x
177-
# `get_primary_monitor()` doesn't work on wayland, so return as it is.
178-
return [
179-
ScreenImpl(native=display.get_monitor(i))
180-
for i in range(display.get_n_monitors())
181-
]
182-
else: # pragma: no-cover-if-linux-wayland
183-
primary_screen = ScreenImpl(display.get_primary_monitor())
184-
screen_list = [primary_screen] + [
185-
ScreenImpl(native=display.get_monitor(i))
186-
for i in range(display.get_n_monitors())
187-
if display.get_monitor(i) != primary_screen.native
188-
]
189-
return screen_list
185+
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
186+
if IS_WAYLAND: # pragma: no-cover-if-linux-x
187+
# `get_primary_monitor()` doesn't work on wayland, so return as it is.
188+
return [
189+
ScreenImpl(native=display.get_monitor(i))
190+
for i in range(display.get_n_monitors())
191+
]
192+
193+
else: # pragma: no-cover-if-linux-wayland
194+
primary_screen = ScreenImpl(display.get_primary_monitor())
195+
screen_list = [primary_screen] + [
196+
ScreenImpl(native=display.get_monitor(i))
197+
for i in range(display.get_n_monitors())
198+
if display.get_monitor(i) != primary_screen.native
199+
]
200+
return screen_list
201+
else: # pragma: no-cover-if-gtk3
202+
return [ScreenImpl(native=monitor) for monitor in display.get_monitors()]
190203

191204
######################################################################
192205
# App state
@@ -201,7 +214,10 @@ def get_dark_mode_state(self):
201214
######################################################################
202215

203216
def beep(self):
204-
Gdk.beep()
217+
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
218+
Gdk.beep()
219+
else: # pragma: no-cover-if-gtk3
220+
Gdk.Display.get_default().beep()
205221

206222
def _close_about(self, dialog, *args, **kwargs):
207223
self.native_about_dialog.destroy()

0 commit comments

Comments
 (0)