-
-
Notifications
You must be signed in to change notification settings - Fork 779
New Backend - Qt #3769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
New Backend - Qt #3769
Conversation
@freakboy3742 By now I have not added CI testing yet because the test skips have not yet convereged at 100% This is rough, I need to go back and clean up comments (especially some random jokes I've made -- if you have spare time I give you permission to try to spot them) but a few things I need confirmation on:
So if beeware/briefcase#2480 (which I've filed for a feature to replace installed PySide6 with system symlink when packaging an app) doesn't get resolved in the duration of this PR, should I still remove this hack such that the new backend will not be able to look native and access system themes?
Thank you! |
@freakboy3742 Could you please respond to those questions I've asked about above? Thank you! |
No. Platform identification is only used because You'll also note that the current GTK backend doesn't describe its platform as GTK - it's The approach used by Textual is the model to follow here. If you're on Linux, toga_qt is a candidate backend; if that's the only backend, it's used; if there's more than one candidate installed in the current environment,
For now, I'd say yes. We can only cook with the ingredients we're given. If Qt doesn't work in virtual environments, that's a problem for Qt to resolve. It's not up to us to work around it - if only because there may be use cases for not adopting the global environment. If there's a hack that could be installed, I'd argue the
In core - No - or, at least, there's a much bigger concept that needs to be wrapped here. If the underlying question is about "system icons" - GTK has an analogous concept, which we don't currently handle. See #2441 for some initial thoughts about this (although it doesn't mention the GTK analog that exists). |
@freakboy3742 Thanks for the responses, there's much going on and I'm in the middle of a major comments cleanup (it's done through GitHub's review interface so expect a huge email next week with all the ntoes I made for myself, sorry about that) Re to response 1: Then how the heck am I gonna get all the tests skipped by platform? Do I need a bunch of stub probes to just say "skip on Qt", sort of like GTK4? Anyways if there's 1 platform only also for Briefcase how do we handle the testbed then? Right now I used tests_backend_qt for qt probes and hacks it at conftest.py: https://github.com/beeware/toga/pull/3769/files#diff-b845936988736dd9f884ab2aeb4175fa8ee7914217ed87d3c8aad18f803e74f0R24-R33 -- I can check the backend there [b]ut (EDITED TYPO) it just feels messy to have a hack like that at all. Re to response 2: IMO system-pyside6 for symlinking system packages is not feasible. With all this wheel madness going on you simply don't know where the final system-pyside6 package even gets installed and you can't put symlinks in wheels either (it extracts as plain text when extracted by pip, which uses its own extraction logic). There's some options for "build destination" in setup.py but that's to like an intermediate location. Should I extract this sys.modules hackery into a system-pyside6 package that installs a shim PySide6.py to replace itself, then? Re to response 3: Yeah, but it'd look really weird on Qt without like a native icon attached to the actions. KDE inserts extra space before an action with no icon to fill up for the icon space. |
@freakboy3742 Also do you think that beeware/briefcase#2480 is a candicate issue to be handled on Briefcase? Or are we like if PySide6 doesn't work it doesn't work and we're not going to have our packaging tool symlink a system copy? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welp... I've been told that I'd need a few hours between writing and posting a message... but then after I posted my last set of comments this morning, I came up with some other stuff that I needed to make note of... sorry about that.
qt/src/toga_qt/window.py
Outdated
if size.width() < min_width and size.height() < min_height: | ||
self.set_size((min_width, min_height + self._extra_height())) | ||
elif size.width() < min_width: | ||
self.set_size((min_width, size.height() + self._extra_height())) | ||
elif size.height() < min_height: | ||
self.set_size((size.width(), size.height() + self._extra_height())) | ||
self.container.native.setMinimumSize(min_width, min_height) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've noted before that the sizes aren't round-tripping exactly on windows in Qt sometimes, however I've found that in all the cases I've found, a setMinimumSize directive will actually force the window size to round-trip exactly from what we gave Qt as the window size... so there's no concern of an infinite loop here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does setMinimumSize()
then prevent the window from becoming any smaller? I'd be concerned about a call to set_size()
then forcing a new limit on the size of the window.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it does prevent the window size from getting smaller. The set_size
calls will force the exact same size as the setMinimumSize call forces the minimum to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok - then that's not a fix for the problem. Setting a size sets the current size of the window, not the minimum size of the window.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@freakboy3742 I apologize but I have slightly lost you here. Not a fix for which problem? You mean for like the window-size-does-not-round-trip-exactly problem? Or like a potential infinite loop on this function when it keeps trying to enforce the minimum window size but never does it because it didn't round trip?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@freakboy3742 I think I understand what you are talking about now, and I apologize for some confusion here.
Right here is the code for container_refrehsed
(it's content_refreshed
in Cocoa/iOS, I'll push a rename later) which is the on_refresh handler of the Container object.
set_size
doesn't use setMinimumSize
as a workaround. If you specify a window to (300, 200)
on X11 with 175% scaling it will give you (299, 200)
. So setMinimumSize
is not used there. The docs say users should not make UI decisions based on window size or position anyways, since tiling window managers etc., so my understanding is that this is not that big of a concern.
container_refreshed
here, on the other hand, is when we recompute our layout and impose a minimum size on the window. I am not very sure about if we use setMinimumSize
here on the container directly if it'll work properly if the container is in a different size when I wrote this code at first; so I explicitly set the size of the window first, and then constrain Qt to the new minimum size. I think setting minimum size at container_refreshed
is the correct solution because any thing that might change the minimum size would cause another container_refreshed
and the minimum size will be updated.
The issue I was concerned about with this approach is that if size doesn't round-trip properly, the if branches will re-trigger because it's just 1 or 2 short of the correct width/height and pump another window resize event, which causes container to refresh again and trigger an infinite loop. I was saying that setMinimumSize
has the side effect of stopping this kind of error, but that doesn't mean we're relying on it elsewhere than the function here.
That said, it seems like if setMinimumSize is invoked on children, the window will automatically expand to the correct size in Qt, so we don't have to worry about setting our window size manually, just the container's minimum layout size. So I'm removing L147-L149 on the line numbers on this comment.
@freakboy3742 If you disagree with anything here, please let me know, or if I've confused you again, I apologise. In all cases, let me know and I'll resolve this.
@freakboy3742 I've resolved most of your comments and am waiting response on some of them, after which I would consider this PR ready for a final review. However it seems that Briefcase sdisting is not parsing [optional extra groups] properly... any ideas on how to fix this? Should I just repeat system-pyside6 additionally in the testbed? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've given this another review pass; it's getting close to the point where it's mergeable (or, at least, it's good enough that merging means we have a manageable base for future work, rather than trying to review changes to a 130 file behemoth).
At this point, the only major concerns I have are:
- The minimum size fix for size roundtripping.
- The timer/callback based approach to window resizing. Having a mechanism in place that has potential to recursively cascade is definitely inadvisable
- The additional fixes to the actions workflows to fully incorporate Qt into the release process.
sudo apt install -y --no-install-recommends \ | ||
blackbox python3-dev xvfb \ | ||
gnome-session-canberra build-essential \ | ||
libfontconfig1-dev libfreetype-dev libgtk-3-dev \ | ||
libx11-dev libx11-xcb-dev libxext-dev \ | ||
libxfixes-dev libxi-dev libxkbcommon-dev \ | ||
libxkbcommon-x11-dev libxrender-dev 'libxcb*-dev' \ | ||
libwayland-dev libwayland-egl1-mesa libwayland-server0 \ | ||
libgles2-mesa-dev libxkbcommon-dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised so many GTK/Gnome libraries are required here. Are you sure this is a minimal set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See https://doc.qt.io/qt-6/linux-requirements.html -- this is the list.
gnome-session-canberra
is used for App.beep -- QApplication::beep does not work if pcspkr which is a very bad-noised module is enabled, and the proper way to do it in KDE is through KNotifications -- but then we'd have to bundle a .knotifyrc file which we currently does not support in Briefcase AFAICT, and that needs a meaningful event name. So we just use libcanberra here (canberra-gtk-play command line actaully) to play the freedesktop.org sound theme.
EDIT -- would it perhaps be more preferrable to use something like https://pypi.org/project/py-canberra/ for sounding the bell rather than calling it through the command line?
Wonder why it's called Canberra though... sounds weirdly nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the App.beep thing, the problem seems to be that QApplication.beep isn't implemented at all on Wayland... also KNotifications upon further research seems to be that each app can declare notification types and in System Settings you can tweak what sort of action you want these notifications to make... that leaves libcanberra being the only way we could possibly play a bell sound.
Integrating libcanberra from the python side is nontrivial... the way to play a sound from the correct theme seems to be using its gtk integration, which requires a gtk event loop, but we already run a Qt event loop in our applications. Else we can hardcode a sound theme, but I think the goal of Toga is to be native as much as possible, and users might customize sound themes... so that's why despite discouragement from doing so, I'm using canberra-gtk-play in a subprocess.Popen for playing App.beep. Sorry for commenting on unrelated thread.
EDIT -- clarification: it's using some asyncio internal utils, not subprocess.Popen for ResourceWarning to not happen.
setup-python: false # Use the system Python packages | ||
app-user-data-path: "$HOME/.local/share/testbed" | ||
|
||
- backend: "linux-x11-qt" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to test both X11 and Wayland? Given this is (a) a pre-alpha prototype, and (b) a new backend, it seems like just testing on Wayland is probably sufficient for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Major concern for this is Window States handling. X11 has some quirks where you can't directly go through some window states, and Waylnad window state handling is completely different from X11. Given that I need a final go/no-go confirm from you to remove x11 tests.
docs/en/reference/platforms/linux.md
Outdated
|
||
The Toga backend for Linux (and other Unix-like operating systems) is [`toga-gtk`](https://github.com/beeware/toga/tree/main/gtk). | ||
The Toga backend for Linux (and other Unix-like operating systems) is [`toga-gtk`](https://github.com/beeware/toga/tree/main/gtk) for | ||
GNOME-based Desktops or [`toga-qt`](https://github.com/beeware/toga/tree/main/qt) for KDE-based desktops. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This document would be better split into two distinct halves - one for GTK, and one for Qt. The "GTK or Qt" references are a but cumbersome; and beyond high level "what options are available?" research, a user who is looking for information will only be looking for one or the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should also be clear that GTK is a mostly complete, fully supported backend; and Qt is a early alpha work in progress.
gtk3-devel libX11-devel libX11-xcb libXext-devel \ | ||
libXfixes-devel libXi-devel libxkbcommon-devel \ | ||
libxkbcommon-x11-devel libXrender-devel 'xcb-util*devel' \ | ||
wayland-devel libwayland-server mesa-libEGL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again - surprised at the amount of GTK stuff here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my reply on Comment on lines +351 to +359
But also -- I'm installing all the x11 deps for Wayland as well (and the converse) because we don't list X11 and Wayand deps separately in the docs, so if the user just install all those packages and they happen to interact weirdly, we need to get those things caught in CI so we can resolve them.
# unique? | ||
# FIXME: What combinations of values are guaranteed to be | ||
# unique? | ||
return "|".join( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got this terrible feeling of deja vu... :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@freakboy3742 Can you elaborate? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-read lines 23-26. Notice anything familiar?
if icon is not None: | ||
self.native.setIcon(icon._impl.native) | ||
else: | ||
self.native.setIcon(QIcon()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a "default icon" behave the same as "No icon"? i.e., setIcon(None)
isn't an option here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@freakboy3742 No, it is not an option. QIcon() is the way to clear the icon: https://stackoverflow.com/questions/27108130/how-can-we-delete-icon-from-qpushbutton
qt/src/toga_qt/window.py
Outdated
if size.width() < min_width and size.height() < min_height: | ||
self.set_size((min_width, min_height + self._extra_height())) | ||
elif size.width() < min_width: | ||
self.set_size((min_width, size.height() + self._extra_height())) | ||
elif size.height() < min_height: | ||
self.set_size((size.width(), size.height() + self._extra_height())) | ||
self.container.native.setMinimumSize(min_width, min_height) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok - then that's not a fix for the problem. Setting a size sets the current size of the window, not the minimum size of the window.
], | ||
) | ||
async def test_edit_readonly_noop(widget, probe, app_probe, action, select, undo): | ||
if not app_probe.edit_menu_noop_enabled: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is effectively a platform-specific test, as Qt is the only backend that enables the flag. At the very least, Cocoa has an Undo/Redo menu item; is there a reason that this test can't be extended for other platforms? Essentially I want to make sure that either (a) this is a behavior that is very specific to Qt, or (b) the test is written in a way that it could be implemented on other platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behavior is specific to Qt. macOS has menus disabled automatically by the operating system when you Copy and Paste, so it does not make sense to invoke a disabled action to make sure it's a no-op. The native handler cut:
does nothing on a readonly textfield, but it'd be a lot of effort to implement all the probes for Cocoa.
The behavior itself is specific to Qt because in Qt it's not easy to automatically have things like Edit > Cut disabled on a readonly widget automatically, so the Edit > Cut action is still enabled to the user. This is the part we want to test that it works properly -- even if it's enabled, it does nothing. In Cocoa it isn't even enabled.
@freakboy3742 Most comments are replied to inline; thanks for working through this with me. On
I'm not so sure on what else to do either... nasty things like automatically window's state being put back to normal will happen if we do window state transitions too fast... One thing I can do is terminate the chain early if the window state changed to the correct one already. However given the unstableness of all the stuff in Qt... I'd rather not really do that and wait a moment before we finalize the window state change. That'd make MINIMIZE putting into work because by the time the next extra WindowState==NORMAL change event emmitted whenever we put stuff into MINIMIZED. Would you like me to try that? EDIT -- I am adapting that approach. Seems to work. EDIT 2 -- Actually... it didn't. @freakboy3742 The problem is like this -- if you stuff in too many window state transitions too fast, weird things happens. So we need to delay the clearing of pending window state; that way we can achieve the effect of "no one do anything in the following 100ms". So the approach I suggested ultimately did not work out as intended. I'll be attaching some diagrams here showing some stuff about this tomorrow. |
@freakboy3742 This is what the window state transitions look like when you reviewed. A few labels are in the diagram below -- the first timeline is the current state of this PR which is a fix I tried to prevent recursing MINIMIZED (look at the third diagram that the first approach catches the first MINIMIZED event), however it's flawed. ![]() What I'm trying to get at here is that in the third diagram, we see that the cause of the recursion is that Qt under Wayland can't round-trip the correct window state a while later. MINIMIZED is sort of special here that it has this bug. But all the other window states would round-trip correctly under both X11 and Wayland. But then I'd argue that if reading the window state isn't even reliable, we'd just dissupport window states entirely. But reading the window state is reliable in non-minimized case. To summarize -- recursion only happens when you request a window state but it consistently does not roundtrip correctly, and if this happens in any other cases, they should be pretty rare and in my opinion it would be obvious what the issue is. Comments on this? |
@freakboy3742 The CI is green and I have responded to almost all comments we have made and made modifiations for the rest accordingly. I need response on the things I'm asking you inline for, and another review from you. Thanks for working me through this! P.S. Sorry for the repeated small commit pushes, my laptop is literally burning as I'm writing a draft for a book while I'm waiting for CI, I have a VM running that I just realized I forgot to close, so I did not run the testbed locally before pushes. |
Clarified the manual installation requirements for GTK and Qt on Linux, specifying version dependencies.
Increase delay to ensure it completes on slow systems
WIP: Fixes #1142
PR Checklist: