Skip to content

Commit

Permalink
Add BeeWare Tutorial 1 support for GTK4 (#3087)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
3 people authored Feb 3, 2025
1 parent 34ea3a4 commit 71a5826
Show file tree
Hide file tree
Showing 73 changed files with 1,870 additions and 946 deletions.
41 changes: 36 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,9 @@ jobs:
- "macOS-x86_64"
- "macOS-arm64"
- "windows"
- "linux-x11"
- "linux-wayland"
- "linux-x11-gtk3"
- "linux-wayland-gtk3"
- "linux-wayland-gtk4"
- "android"
- "iOS"
- "textual-linux"
Expand All @@ -282,7 +283,7 @@ jobs:
# We use a fixed Ubuntu version rather than `-latest` because at some point,
# `-latest` will be updated, but it will be a soft changeover, which would cause
# the system Python version to become inconsistent from run to run.
- backend: "linux-x11"
- backend: "linux-x11-gtk3"
platform: "linux"
runs-on: "ubuntu-24.04"
# The package list should be the same as in unix-prerequisites.rst, and the BeeWare
Expand Down Expand Up @@ -311,7 +312,7 @@ jobs:
setup-python: false # Use the system Python packages
app-user-data-path: "$HOME/.local/share/testbed"

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

- backend: "linux-wayland-gtk4"
platform: "linux"
runs-on: "ubuntu-24.04"
env:
XDG_RUNTIME_DIR: "/tmp"
# The package list should be the same as in unix-prerequisites.rst, and the BeeWare
# tutorial, plus mutter to provide a window manager.
pre-command: |
sudo apt update -y
sudo apt install -y --no-install-recommends \
mutter pkg-config python3-dev libgirepository1.0-dev libcairo2-dev \
gir1.2-webkit-6.0 gir1.2-xapp-1.0 gir1.2-geoclue-2.0 gir1.2-flatpak-1.0 \
gir1.2-gtk-4.0
# Start Virtual X Server
echo "Start X server..."
Xvfb :99 -screen 0 2048x1536x24 &
sleep 1
# Start Window Manager
echo "Start window manager..."
# mutter is being run inside a virtual X server because mutter's headless
# mode does not provide a Gdk.Display
DISPLAY=:99 MUTTER_DEBUG_DUMMY_MODE_SPECS=2048x1536 \
mutter --nested --wayland --no-x11 --wayland-display toga &
sleep 1
briefcase-run-prefix: "WAYLAND_DISPLAY=toga TOGA_GTK=4"
setup-python: false # Use the system Python packages
app-user-data-path: "$HOME/.local/share/testbed"

- backend: "textual-linux"
platform: "linux"
runs-on: "ubuntu-latest"
Expand Down
2 changes: 2 additions & 0 deletions android/tests_backend/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
class WindowProbe(BaseProbe, DialogsMixin):
supports_fullscreen = True
supports_presentation = True
supports_as_image = True
supports_focus = True

def __init__(self, app, window):
super().__init__(app)
Expand Down
1 change: 1 addition & 0 deletions changes/3087.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +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.
2 changes: 2 additions & 0 deletions cocoa/tests_backend/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class WindowProbe(BaseProbe, DialogsMixin):
supports_unminimize = True
supports_minimize = True
supports_placement = True
supports_as_image = True
supports_focus = True

def __init__(self, app, window):
super().__init__()
Expand Down
24 changes: 24 additions & 0 deletions docs/how-to/contribute/code.rst
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,25 @@ that you could try to implement.
Again, you'll need to add unit tests and/or backend probes for any new features
you add.

Contribute to the GTK4 update
-----------------------------

Toga's GTK support is currently based on the GTK3 API. This API works, and ships with
most Linux distributions, but is no longer maintained by the GTK team. We're in the
process of adding GTK4 support to Toga's GTK backend. You can help with this update
process.

GTK4 support can be enabled by setting the ``TOGA_GTK=4`` environment variable. To
contribute to the update, pick a widget that currently has GTK3 support, and try
updating the widget's API to support GTK4 as well. You can identify a widget that hasn't
been ported by looking at the :ref:`GTK probe for the widget <testbed-probe>` - widgets
that aren't ported yet will have an "if GTK4, skip" block at the top of the probe
definition.

The code needs to support both GTK3 and GTK4; if there are significant differences in
API, you can add conditional branches based on the GTK version. See one of the widgets
that *has* been ported (e.g., Label) for examples of how this can be done.

Implement an entirely new platform backend
------------------------------------------

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

Finally, if you would like to run the tests against GTK4 on Linux, set the
environmental variable ``TOGA_GTK=4``. This is experimental and only partially
implemented, but we would greatly appreciate your help translating widgets from
GTK3 to GTK4.

.. _testbed-probe:

How the testbed works
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/api/widgets/mapview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ System requirements
- OpenSUSE Tumbleweed: ``libwebkit2gtk3 typelib(WebKit2)``
- FreeBSD: ``webkit2-gtk3``

MapView is not fully supported on GTK4. If you want to contribute to the GTK4 MapView
implementation, you will require v6.0 of the WebKit2 libraries. This is provided by
``gir1.2-webkit-6.0`` on Ubuntu/Debian, and ``webkitgtk6.0`` on Fedora; for other
distributions, consult your distributions's platform documentation.

* Using MapView on Android requires the OSMDroid package in your project's Gradle
dependencies. Ensure your app declares a dependency on
``org\.osmdroid:osmdroid-android:6.1.20`` or later.
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/api/widgets/webview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ System requirements
- OpenSUSE Tumbleweed: ``libwebkit2gtk3 typelib(WebKit2)``
- FreeBSD: ``webkit2-gtk3``

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

Notes
-----

Expand Down
5 changes: 5 additions & 0 deletions docs/reference/platforms/unix-prerequisites.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ If you're not using one of these, you'll need to work out how to install the dev
libraries for ``python3``, ``cairo``, and ``gobject-introspection`` (and please let us
know so we can improve this documentation!)

In addition to the dependencies above, if you would like to help add additional support
for GTK4, you need to also install ``gir1.2-gtk-4.0`` on Ubuntu/Debian, or ``gtk4`` on
Fedora or Arch. For other distributions, consult your distributions's platform
documentation.

Some widgets (most notably, the :ref:`WebView <webview-system-requires>` and
:ref:`MapView <mapview-system-requires>` widgets) have additional system requirements.
Likewise, certain hardware features (:ref:`Location <location-system-requires>`) have
Expand Down
58 changes: 37 additions & 21 deletions gtk/src/toga_gtk/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from .keys import gtk_accel
from .libs import (
GTK_VERSION,
IS_WAYLAND,
TOGA_DEFAULT_STYLES,
Gdk,
Expand Down Expand Up @@ -34,7 +35,7 @@ def __init__(self, interface):
# Stimulate the build of the app
self.native = Gtk.Application(
application_id=self.interface.app_id,
flags=Gio.ApplicationFlags.FLAGS_NONE,
flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
)
self.native_about_dialog = None

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

# Set any custom styles
css_provider = Gtk.CssProvider()
css_provider.load_from_data(TOGA_DEFAULT_STYLES)

context = Gtk.StyleContext()
context.add_provider_for_screen(
Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER
)
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
css_provider.load_from_data(TOGA_DEFAULT_STYLES)
context = Gtk.StyleContext()
context.add_provider_for_screen(
Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER
)
elif GTK_VERSION >= (4, 12, 0): # pragma: no-cover-if-gtk3
css_provider.load_from_string(TOGA_DEFAULT_STYLES)
elif GTK_VERSION >= (4, 8, 0): # pragma: no-cover-if-gtk3
css_provider.load_from_data(TOGA_DEFAULT_STYLES, len(TOGA_DEFAULT_STYLES))
else: # pragma: no-cover-if-gtk3
# Earlier than GTK 4.8
css_provider.load_from_data(TOGA_DEFAULT_STYLES.encode("utf-8"))

######################################################################
# Commands and menus
Expand Down Expand Up @@ -173,20 +182,24 @@ def set_main_window(self, window):

def get_screens(self):
display = Gdk.Display.get_default()
if IS_WAYLAND: # pragma: no-cover-if-linux-x
# `get_primary_monitor()` doesn't work on wayland, so return as it is.
return [
ScreenImpl(native=display.get_monitor(i))
for i in range(display.get_n_monitors())
]
else: # pragma: no-cover-if-linux-wayland
primary_screen = ScreenImpl(display.get_primary_monitor())
screen_list = [primary_screen] + [
ScreenImpl(native=display.get_monitor(i))
for i in range(display.get_n_monitors())
if display.get_monitor(i) != primary_screen.native
]
return screen_list
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
if IS_WAYLAND: # pragma: no-cover-if-linux-x
# `get_primary_monitor()` doesn't work on wayland, so return as it is.
return [
ScreenImpl(native=display.get_monitor(i))
for i in range(display.get_n_monitors())
]

else: # pragma: no-cover-if-linux-wayland
primary_screen = ScreenImpl(display.get_primary_monitor())
screen_list = [primary_screen] + [
ScreenImpl(native=display.get_monitor(i))
for i in range(display.get_n_monitors())
if display.get_monitor(i) != primary_screen.native
]
return screen_list
else: # pragma: no-cover-if-gtk3
return [ScreenImpl(native=monitor) for monitor in display.get_monitors()]

######################################################################
# App state
Expand All @@ -201,7 +214,10 @@ def get_dark_mode_state(self):
######################################################################

def beep(self):
Gdk.beep()
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
Gdk.beep()
else: # pragma: no-cover-if-gtk3
Gdk.Display.get_default().beep()

def _close_about(self, dialog, *args, **kwargs):
self.native_about_dialog.destroy()
Expand Down
Loading

0 comments on commit 71a5826

Please sign in to comment.