Skip to content

Expose Gtk objects on _window_linux.dart in order to better support out of tree LinuxWindowingOwners#188917

Open
mattkae wants to merge 1 commit into
flutter:masterfrom
canonical:third-party-linux-windowing
Open

Expose Gtk objects on _window_linux.dart in order to better support out of tree LinuxWindowingOwners#188917
mattkae wants to merge 1 commit into
flutter:masterfrom
canonical:third-party-linux-windowing

Conversation

@mattkae

@mattkae mattkae commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Problem

While authoring https://github.com/mattkae/layer_shell.dart, I realized that the Linux Windowing platform had a few major problems when it came to extending it for out-of-tree windowing owners:

  1. 3rd party windows could not register in the _windows or _views maps, which meant that they could never be parents. This is invalid, as other windows (e.g. layer shell windows) may indeed want to be valid parents and open popups. For example, a top panel may want to open a sound control dropdown.
  2. I wanted to extend RegularWindowLinux, but that was unfortunately not possible because the regular windows realize themselves by the time the constructor returns. Gtk layer shell windows require that the window become layer shell before it is realized. We could have added an odd flag to the regular window, but that felt like duct-tape over a larger issue whereby an external author could NOT do anything sufficiently complex with the current API.
  3. Windows were forced to reimplement a TON of common functionality, specifically around Gtk windows. This can feasibly be common, albeit hidden behind an "experimental" and @internal indicator so that users are aware that they are doing something funky.

What's new?

  • Created a GtkWindowRegistrar that can be used to register and unregister 3rd party windows
  • Now we export:
    • GdkWindowType
    • GdkWindowState
    • GtkWindow
    • FlWindowMonitor
    • FlView
    • FlEngine
    • FlViewMonitor
    • GdkGravity
    • GdkWindowTypeHint
    • GdkAnchorHint
    • GObject
    • GtkWidget

This may seem like a drastic export, and we may want slim it down. However, if a user is extending the Linux windowing API for their own usecase, they will 99% of the time want these lower level, less stable constructs to play with.

The user is doing something weird by making the cast to a Linux-specific API, and they should have the full capabilities of that weirdness at their disposal if that's what they choose.

In my opinion, the @internal flag can stay around these APIs forever. They do NOT need to be stabilized at the same time as the rest of the windowing APIs, as they provide potentially unstable but useful behavior.

Associated PR: mattkae/layer_shell.dart#1

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@github-actions github-actions Bot added the framework flutter/packages/flutter repository. See also f: labels. label Jul 2, 2026
@mattkae mattkae marked this pull request as ready for review July 2, 2026 15:08
@flutter-dashboard

Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Linux windowing implementation by introducing a GtkWindowRegistrar to track native GTK windows and Flutter views, and exposes previously private classes and enums as @internal public types. The review feedback suggests marking GtkWindowRegistrar as final, adding assertions to prevent duplicate registrations, throwing specific StateErrors instead of generic Exceptions when lookups fail, and removing obsolete // ignore: unused_field comments from the newly exposed enums.

Comment on lines +225 to +226
@internal
class GtkWindowRegistrar {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since GtkWindowRegistrar is a concrete utility class meant to be used directly rather than extended or implemented, consider marking it as final to adhere to Dart 3 class modifiers best practices.

@internal
final class GtkWindowRegistrar {

Comment on lines +238 to +241
void register({required int viewId, required GtkWindow window, required FlView view}) {
_windows[viewId] = window;
_views[viewId] = view;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent silent bugs, memory leaks, or incorrect window parenting due to duplicate registrations, consider adding assertions to ensure the viewId is not already registered.

  void register({required int viewId, required GtkWindow window, required FlView view}) {
    assert(!_windows.containsKey(viewId), 'A window with viewId $viewId is already registered.');
    assert(!_views.containsKey(viewId), 'A view with viewId $viewId is already registered.');
    _windows[viewId] = window;
    _views[viewId] = view;
  }

Comment on lines +539 to 542
final GtkWindow? parentWindow = owner.registrar.windowForViewId(parent.rootView.viewId);
if (parentWindow == null) {
throw Exception('Failed to find dialog parent window');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Dart, it is preferred to throw a more specific error like StateError or ArgumentError instead of a generic Exception when a lookup fails or an invalid state is encountered.

      final GtkWindow? parentWindow = owner.registrar.windowForViewId(parent.rootView.viewId);
      if (parentWindow == null) {
        throw StateError('Failed to find dialog parent window');
      }

Comment on lines +748 to 751
final GtkWindow? parentWindow = _owner.registrar.windowForViewId(_parent.rootView.viewId);
if (parentWindow == null) {
throw Exception('Failed to find tooltip parent window');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Dart, it is preferred to throw a more specific error like StateError or ArgumentError instead of a generic Exception when a lookup fails or an invalid state is encountered.

    final GtkWindow? parentWindow = _owner.registrar.windowForViewId(_parent.rootView.viewId);
    if (parentWindow == null) {
      throw StateError('Failed to find tooltip parent window');
    }

Comment on lines +939 to 942
final GtkWindow? parentWindow = _owner.registrar.windowForViewId(_parent.rootView.viewId);
if (parentWindow == null) {
throw Exception('Failed to find popup parent window');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Dart, it is preferred to throw a more specific error like StateError or ArgumentError instead of a generic Exception when a lookup fails or an invalid state is encountered.

    final GtkWindow? parentWindow = _owner.registrar.windowForViewId(_parent.rootView.viewId);
    if (parentWindow == null) {
      throw StateError('Failed to find popup parent window');
    }

Comment on lines +1068 to 1072
enum GtkWindowType {
toplevel,
// ignore: unused_field
popup,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since GtkWindowType is now a public enum (even though marked @internal), the Dart analyzer will not flag its values as unused. The // ignore: unused_field comment is obsolete and can be removed.

enum GtkWindowType {
  toplevel,
  popup,
}

Comment on lines +1104 to 1107
enum GdkWindowTypeHint {
// ignore: unused_field
normal,
dialog,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since GdkWindowTypeHint is now a public enum (even though marked @internal), the Dart analyzer will not flag its values as unused. The // ignore: unused_field comment is obsolete and can be removed.

enum GdkWindowTypeHint {
  normal,
  dialog,

Comment on lines +1137 to 1140
enum GdkGravity {
// ignore: unused_field
none,
northWest,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since GdkGravity is now a public enum (even though marked @internal), the Dart analyzer will not flag its values as unused. The // ignore: unused_field comment is obsolete and can be removed.

enum GdkGravity {
  none,
  northWest,

@mattkae mattkae added the CICD Run CI/CD label Jul 2, 2026
@robert-ancell

robert-ancell commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Two things immediately come to mind:

  • Should this be LinuxWindow, not GtkWindow to match the existing public naming style.
  • Should Gtk/LinuxWindow contain _GtkWindow and not expose this detail to the user. We're adding a lot of API and potential footguns. I think the user should just provide the GtkWindow* pointer and it is up to them to control everything in native code or via their own FFI APIs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants