New backend - Qt
On linux toga uses GTK+ but some platforms like KDE already has Qt. some users are familiar with Qt.
so its better if toga can develop a Qt port
Threre are few Qt impliments
- PyQt5
- PyQt4
- Pyside2
- Pyside They all has nearly same API.
I like to work on this issue myself
If you want to contribute a Qt implementation of Toga, then go right ahead! A placeholder implementation already exists in the nursery. Submit pull requests against that location; once the implementation starts to show signs of viability, we can move it to the supported list.
ok I will start creating basics on toga_qt/libs folser
What does HANDLES_COMMAND_LINE mean in an App class?
EDIT: Thanks.
PyQt{4,5,6} are NOT usable. They're GPL'd.
@freakboy3742 Is LGPL okay?
On the other hand, PySide6 is LGPL (EDITED), and that is what I'm using.
EDIT just realized PyQt isn't official... another reason to use PySide*.
@freakboy3742 FYI: I've started to work on this, currently almost everything is stubbed out, it runs without errors though, but somehow no window is displayed... probably messed up somewhere.
https://github.com/johnzhou721/togax_qt
GPL won't be acceptable in Toga's core. LGPL would be fine. PySide6 is the most recent version of the official Qt Python bindings, so it makes sense that's the one to be used.
As for "no window being displayed"... well, that's the problem to solve, isn't it :-) I can't profess any expertise with Qt, so I can't really offer any help there.
I can't profess any expertise with Qt
I can't either... I was just trying to tkinter with stuff (pun intended).
Also... I probably won't work on this for a moment because I've accidentally found myself in the middle of 3 large projects... mac catalyst support, gettext on Jinja templates on beeware.org, and this one...
I've found the cause for no window displayed.
Wait hold on... I forgot to hook the app startup method to interface._startup... I feel so stupid now.
Time to read about signals.
List from Haiku ticket that I will tick as I go:
- [x] Get an app that starts, displaying a single window.
- [x] Get an app that starts, cooperating with the asyncio event loop (done using qasync package under BSD license, QtAsyncio uses a custom module instead of the builtin asyncio which I suspect might have problems later on)
- ~~[ ] (ADDED BY ME) Fill in a bunch of stubs I had~~ (yak shaving, not really important right now, will need this later)
- [x] Add a single widget (Button is a good choice) to the window
- [x] Apply padding to the button, and work out how to implement set_bounds() on a widget to position on the screen
- [x] Add a Box widget, and put the button in a box; work out how to add a widget as a child of a widget.
- [x] Get Tutorial 0 running
- [x] Add Label and TextView widgets
- [x] Get Tutorial 1 running.
OK... I've implemented Step 2, but QtAsyncio (integration of Qt with Asyncio, builtin to PySide6) is its own module that provides the same API as Python's asyncio... so like basically everywhere you call asyncio.whatever needs to be QtAsyncio.whatever... specific cases can be worked around (such as using a task factory), but a cursory search of core reveals a lot of asyncio calls.
And.. a lot of APIs aren't available... such as things like asyncio.sleep... this really seems like a piece of snowflake that doesn't fit in w/ regular asyncio stuff... will need to find alternaties
Hold on... Somehow this Qt asyncio minimal example uses asyncio.sleep! How in the world???
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget)
import PySide6.QtAsyncio as QtAsyncio
import asyncio
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout(widget)
self.text = QLabel("The answer is 42.")
layout.addWidget(self.text, alignment=Qt.AlignmentFlag.AlignCenter)
async_trigger = QPushButton(text="What is the question?")
async_trigger.clicked.connect(lambda: asyncio.ensure_future(self.set_text()))
layout.addWidget(async_trigger, alignment=Qt.AlignmentFlag.AlignCenter)
async def set_text(self):
await asyncio.sleep(1)
self.text.setText("What do you get if you multiply six by nine?")
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
QtAsyncio.run(handle_sigint=True)
So I must be doing something incorrectly...
@johnzhou721: This kind of constant progress updating is only adding noise and making the issue more difficult to read. Please only post comments if they're likely to be useful to other people, or if you're asking for help.
@freakboy3742 Questions:
- Is using QAsync fine? It's a BSD-licensed package on PyPI that integrates Qt with Asyncio without needing to do stuff with a custom module; it provides an event loop that integrates into the builtin asyncio package. (EDIT: Nevermind... it uses event loop policies, to be deprecated in python 3.16... what's the attribution policy if we decide to copy code from a package for our own use? because it's unmaintained for 2 years)
- While filling in some stubs in app.py, I implemented Screens (a huge mistake because it's yak shaving). AFAIK there's no way you can get the unique identifier of a screen in Qt. Is this implementation fine? https://github.com/johnzhou721/togax_qt/blob/master/togax_qt/screens.py#L20-L21 I doubt that two monitors will have all the same properties.
@johnzhou721 There's no problem using a third-party package to implement async support if it's required - as long as the package has reasonable provenance, and looks like it's being updated regularly. In this case, QAsync hasn't been updated since 2023, so that doesn't bode well.
If we decide to fork the package and maintain an event loop, attribution comes down to the license of the code in question. QAsync appears to be BSD licensed, so we can copy as much of the code as we need to, as long as we preserve the attribution. The preferred approach would be to contribute to an existing project; the next best option is to fork the project. The BSD licensing makes a fork trivial (and looking at the history, QAsync is a fork of a fork of an original project, so there's a history here).
As for screens - I have no idea if that implementation is fine. I'm not familiar with Qt's APIs, so I don't know if that collection of values will yield a unique result. I would have thought serial number would be unique by itself - but that's based purely on "reading the method name".
@freakboy3742 Hmm... what if there's not a serial number available -- that's my concern. That's why I did all that stuff just to be safe... do we care about what the screen name is as long as it's unique? If not, I can try to verify this stuff, or find some other identifier.
As for maintenance time: if we want to use a third-party package, there really isn't a bunch of options. All packages I found were updated at least 2 years ago... and 3 of them are all updated two years ago by coincidence. Not sure what happened to all Qt and Asyncio devs in 2023...
QtAsyncio is a different module than asyncio [EDITED]; if we're going with this official route, we need to find a way to replace all instances of asyncio with QtAsyncio depending on the platform (EDIT even though most things are compatible, we don't want to take risks)... and if I do that in the backend, timing is pretty important... and QtAsyncio misses some APIs that asyncio has... so, do you think this would be a good option, i.e. conditionally call QtAsyncio or asyncio depending on the platform (don't mean an if condition every time, I mean like assigning the module to a variable and using that...)?
If not, if I personally fork qasync and publish it to PyPI, would you object to that? Or would you prefer that BeeWare as a project fork that package under [EDITED: a new] name and publish it to PyPI (or somehow make it available for toga_qt) as a BeeWare package? If the latter, are you okay with me, for the time of working on this, to bundle this modified QAsync into togax_qt for now?
If not, if I personally fork qasync and publish it to PyPI, would you object to that?
Forking wouldn't be my preferred option. If working with an existing maintainer is an option, that would be preferable. However, if the upstream project isn't responsive, then forking may be inevitable.
Talking about ownership of the fork is a bit premature, though - until toga-qt is actually viable, any discussion about who owns a fork of qasync is a bit moot from BeeWare's perspective. If toga-qt becomes viable enough for us to consider adding it as a backend, we'd likely want to take ownership of the forked qasync project; but until then, it doesn't even need to be published to PyPI - you can specify your own forked GitHub repository as a dependency.
@freakboy3742 Thanks for letting me know. Since I have already vendored the sources into togax_qt, I will unvendor them and put the code in a fork in my personal GitHub namespace, and update pyproject.toml accordingly.
Edit
The above is done. I've filed an issue at https://github.com/CabbageDevelopment/qasync/issues/130 to see if the maintainer triages it and/or responds; if so, I'll open an PR upstream and delete my fork, and point the dependency upstream; if not, I'll keep the current approach of forking into my personal namespace.
Update for 7/11/25 (posted 7/12/25, U.S. times): I spent the day digging into QAsync issue to see if any of them are of impact, and most aren't -- there might be a few, but if they come up we can fix them.
@johnzhou721 Hi, I'm the maintainer of qasync. The owner of the fork seem to have gone silent for a while and in 2023. I have since stopped using qasync myself and have moved to other projects. I'm willing to help out if you want to submit a PR with changes necessary for toga project.
Wow, thanks for helping! That makes it a lot easier. A few questions:
- Can you still reproduce https://github.com/CabbageDevelopment/qasync/issues/10 on Linux? I can't.
- What's the use case for multiple event loops? This is to determine if https://github.com/CabbageDevelopment/qasync/issues/73 is relevant; if so, let's fix it.
- Can you test and review https://github.com/CabbageDevelopment/qasync/pull/131? Thanks!
- Besides that, just confirming that you're active and you can respond to (and potentially fix, but if you give me some pointers I might do it) bugs that is of relevance of BeeWare is good, I think.
The qt backend doesn't have lots of fast progress recently due to me being busy, but just confirming that the maintainer is active is great for us. Thank you for maintaining qasync!
- Closed as I was not able to repro it.
- The author of the PR mentions the use case being unit testing where event loops are created and teared down. I've PR'd a fix for this in https://github.com/CabbageDevelopment/qasync/pull/132 if you'd like to take a look.
- I'll take a look at it later today or tomorrow
- I'm not active daily on github, since I have other obligations, however I can usually respond if you ping me directly. Certainly cannot guarantee that I'd be there to fix issue with qasync. The best thing would be for you to Issue/PR your changes describing what problem you have.
@freakboy3742 I've applied padding to my widget, but set_bounds isn't called in widget base. Can you think of any reason? Thanks!
Meta thing: I won’t be able to respond to or work on anything for the next 7 days.
@freakboy3742 I've applied padding to my widget, but
set_boundsisn't called in widget base. Can you think of any reason? Thanks!
set_bounds() is called when the widget layout is computed and then style is applied. So - if you haven't integrated a trigger point where Qt is calling the layout, or the widget hasn't been hooked up to respond to layout changes, then you won't get calls to set_bounds()
@freakboy3742 (posting last-minute before I’m unable to access electronics) thanks for the advice, I’ve dug into it a bit but didn’t find any more hints on how to implement this. Could you please point me to some implementations of what you’re talking about on other backends? Thanks!
@freakboy3742 I am sorry for the repeated pings, but could you please provide some guidance on my previous comment? I've searched everywhere for applicator, but couldn't find where to implement these stuff. Thanks!
The applicator is defined in core/src/toga/style/applicator.py. An instance of the applicator is assigned to every widget at the core level.
The entry point is the call to refresh() on Widget; that recomputes layout, and calls back onto the applicator (which is also set on the base widget) to set bounds, styles and so on.
@freakboy3742 Thanks for your very helpful comment. I am having great mileage here as you can see from the checklist I am maintaining.
Also, regarding qasync issue with the deprecation for event loop policies, I have a draft PR ready; the maintainer says we need to wait for 3.14 official release... which is only a few months (much less than the time I need to get tutorial 1 running).
qasync maintainer is very responsive, we're making a great deal of mileage fixing issues there as well.
EDIT
Question: Am I allowed to use GPL software to help me with my development, but not reuse any code from it? I've found GammaRay which I figured would be helpful to inspect the app.
EDIT 2
While trying to increase disk space on my Ubuntu VM, I accidentally made it non-bootable. I'll be developing this on my mac, but I need to compress my VM first to save disk space for me to clone togax_qt. This may take a while
EDIT 3
I have children working. However there's an edge case that seems neglected. See this repro (without using togax qt):
import os
#os.environ['TOGA_BACKEND'] = 'togax_qt'
import toga
from toga.style import Pack
from toga.style.pack import CENTER
def button_handler(widget):
print("Button clicked!")
def button_handler2(widget):
print("Button2 clicked!")
def build(app):
# Create a button and set its on_press handler
box = toga.Box()
button = toga.Button('Click Me', on_press=button_handler, style=Pack(margin=5))
# button2 = toga.Button('Click Me', on_press=button_handler2, style=Pack(margin=5))
box.add(button)
# box.add(button2)
return button
def main():
return toga.App('Simple Button App', 'org.example.simplebutton', startup=build)
if __name__ == '__main__':
app = main()
app.main_loop()
Result is this cramped button:
If I comment out the box = and box.add, it works properly.
Same thing happens with Qt, but looks weirder.
Is this an issue I have to solve? Might happen on other backends.
EDIT 4
In https://github.com/beeware/toga/issues/2073#issuecomment-1668793114 you referenced TextView -- did you mean TextField
Question: Am I allowed to use GPL software to help me with my development, but not reuse any code from it? I've found GammaRay which I figured would be helpful to inspect the app.
I am not a lawyer, but in general, how the source of a tool or application is licensed (or whether it's even open source at all) shouldn't affect the licensing of what you make via using it. I type my code in Sublime Text, for instance, which is proprietary software. If someone crops or dresses up a screenshot for the docs with Photoshop, that doesn't mean Adobe has any rights to that image.
Question: Am I allowed to use GPL software to help me with my development, but not reuse any code from it? I've found GammaRay which I figured would be helpful to inspect the app.
I am not a lawyer, but in general, how the source of a tool or application is licensed (or whether it's even open source at all) shouldn't affect the licensing of what you make via using it. I type my code in Sublime Text, for instance, which is proprietary software. If someone crops or dresses up a screenshot for the docs with Photoshop, that doesn't mean Adobe has any rights to that image.
Thanks! By the way, literally just remembered Freedom 0 which says you can use Free Software for any purpose.
EDIT Somehow I couldn't get GammaRay to work, so this is sort of a moot point.
Speaking of Freedom, here's Qt-based Freedom Units (Farenheit to Celcius app) up and running on macOS!!!! (sorry, my Ubuntu VM stopped working).
EDIT I just finished the checklist for adding a new backend (as mentioned in the Haiku ticket, modulo the question on what TextView is), some widgets are already up and running but no fancy stuff like fonts, colors etc yet. No testbed up and running either, but I'm doing great so far!
EDIT 2 & EDIT 3..
Roadmap for the next few months (if I ever get time, school's about to start, can't spend half a day working on BeeWare anymore; at the current rate I can probably finish this within a few weeks but not after school starts):
- [x] Revisit Window & related impls, study what BeeWare concepts as a window and how to map that nicely to Qt's window, right now all toy apps I've used only creates 1 window, so I just used QMainWindow in the Window parent class.
- Investigated 7/24/25. "Correct" mapping would be to use QWidget for window (because you can tell it to show by itself without a parent, you can also set window title etc. on it) and QMainWindow for well, main window. Container is also a QWidget, so I wonder if we could just show the container for the window. Seems that Winforms is doing something similar here by subclassing Container for window ~~, but need more investigation. QMainWindow is just QWidget with a bunch of fluff to support e.g. menu bars; however, simply showing a qwidget as a container (or if you wrap it in another qwidget, adding the children) and setting the "central widget" is two different things... which would make the code a bit more complex. I'm starting to think if we should use QMainWindow even for a simple Toga window, and not use all the extra fluff it adds (except the central widget) in Window, so we can subclass MainWindow from Window relatively easily and just add the code to exploit its menu bar capabilities.~~
- 7/25/25 Nope... turns out most of the methods are the same, and I can do the central widget setting in create() and set self.native to container.native... so back to QWidget being Window and QMainWindow being MainWindow without bloating every Window with unexposed MainWindow functionality.
- [x] Commands (and menu bars) [moved into previous item 7/24, moved out 7/25, I'm bad at planning]
- 7/26/25 Things left to implement now about this: toolbar, ~~some random builtin stuff that's also not builtin in the Edit section (on App class)~~ , turns out a lot of the stuff only applies if you do a Document App which is not a supported concept yet. ~~Also need to figure out custom icons for Preferences and About -- will need for icon impl for that to happen~~ , also need to make About dialog match the native apps.
- 7/28/25 striked through some items above, need toolbar and better About dialog, somehow I have icon impl but setting icon on app is not working, will need to investigate further but I need to consider this done for now because I can't shave yak forever. completed, another issue is the keyboard mapping for backtab etc as seen at https://forum.qt.io/topic/162828/how-do-i-check-for-shift-tab but seems that registering shift + tab actually works.
- 8/10/25 icon impl doesn't use sizes anymore, icon on app is working, the question i've asked at https://forum.qt.io/topic/162828/how-do-i-check-for-shift-tab somehow doesn't seem to impact kde as if I register Shift + Tab it also somehow works
- [x] Figure out how to test this unofficial backend (any setup guidelines may be able to go in the docs?) [edit 3] [moved up 7/26, this is urgent]
- 8/10/25
test_app.pyis now passing with a stub for the icon assert (don't want to do stuff with pillow right now). app and window probes are barely written with a bunch of stubs. found metric tons of bugs in my code, trying to fix them.
- 8/10/25
- [x] Colors (and maybe figure out how to do styling things in general)
- [x] Find some way I can indicate error on TextInput, that is currently stubbed
- [x] qasync removal of event loop policy references ~~-- held until October when Py3.14 gets released~~
- 7/24 ACTUALLY, patch is ready, Green CI except some unrelated intermittent failures.
- 7/25 merged without 3.14-dev testing in CI
EDIT 7/24
I'm not that familiar with Qt; I've only used PySide6 maybe a couple of times for some extremely simple toy apps (down to the level of a text field, a button, and a label), so I'm not that experienced. I'm getting a lot more experienced with Qt as I implement this, so I might make tons of mistakes. Are there any other people who's extremely familiar with both Qt and Toga to do some code reviewing later in the process? If not, maybe I'll just get way more familiar with Qt over time.