Guidelines and HOWTOs/Wayland Porting Notes: Difference between revisions

From KDE Community Wiki
(Note about KPart embedding)
(→‎Application Icon: add link to desktop specification)
 
(17 intermediate revisions by 3 users not shown)
Line 1: Line 1:
This documents contains porting notes for Wayland.
This documents contains porting notes for Wayland.
If you don't use the Plasma Wayland session as your daily driver, you can still test the behavior of your application on Wayland and fix the bugs. Check the [[KWin/Wayland]] wiki page and also [https://blog.martin-graesslin.com/blog/2015/07/porting-qt-applications-to-wayland/ this blog post] by Martin.
Even if you don't use the Plasma Wayland session as your daily driver, you can still test the behavior of your application on Wayland and fix the bugs. Check the [[KWin/Wayland]] wiki page and also [https://blog.martin-graesslin.com/blog/2015/07/porting-qt-applications-to-wayland/ this blog post] by Martin, which contains general info about Wayland porting.


= Popup Menus =
= Popup Menus =


Chances are that many popup menus of your application will be misplaced on Wayland. This is because the compositor needs to know how to relate the QMenu's window with the main window of the application. This is done by setting a transient parent window on the QMenu. The easiest way to do so is ensuring that the menu is created with a parent widget:
Chances are that some popup menus of your application will be misplaced on Wayland. This is because the compositor needs to know how to relate the QMenu's window with the main window of the application. This is done by setting a transient parent QWindow on the QMenu. The easiest way to do so is ensuring that the menu is created with a parent widget:


<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
Line 20: Line 20:
    
    
</syntaxhighlight>
</syntaxhighlight>
{{ Note | Setting a parent widget also changes memory management, those aspects are currently not handled separately. So be aware that the QMenu instance will be deleted as child if the parent widget is deleted. So if e.g. menu entries result in synchronous deletion of the parent widget, this will result in a crash in the QMenu::exec(...) methods. }}
If setting a normal parent widget is not possible/wanted, you will currently (as by Qt <=5.9.1 API) have to set the transient parent on the menu manually. The following code assumes that the transient parent widget to be used is already shown on the screen, thus registered with the native display system:
<syntaxhighlight lang="cpp-qt">
auto menu = new QMenu;
menu->winId(); // trigger being a native widget already, to ensure windowHandle created
// generic code if not known if the available parent widget is a native widget or not
auto parentWindowHandle = parentWidget->windowHandle();
if (!parentWindowHandle) {
    parentWindowHandle = parentWidget->nativeParentWidget()->windowHandle();
}
menu->windowHandle()->setTransientParent(parentWindowHandle);
</syntaxhighlight>
{{ Warning | winId() will break applications that use QQuickWidget. If that's the case, you could try to subclass QMenu in order to call the safer QWidget::create() protected method. }}
== Drop Menus ==
"Drop menus" are popup menus that are created because of a drag-and-drop event. On Wayland there is no global cursor position and <tt>QCursor::pos()</tt> only works by tracking the mouse move events. That's not 100% reliable, and in fact it won't work inside <tt>dropEvent()</tt> functions:
<syntaxhighlight lang="cpp-qt">
void SomeWidget::dropEvent(QDropEvent *event)
{
    auto menu = new QMenu(this);
    // Wrong. It will position the popup where the drag started, not where the drop happened.
    menu->popup(QCursor::pos());
    // Good.
    menu->popup(mapToGlobal(event->pos());
}
</syntaxhighlight>
== Context Menu ==
Similarly to Drop Menus, on wayland <tt>QCursor::pos()</tt> does not work as expected, and should be avoided to position context menus :
<syntaxhighlight lang="cpp-qt">
void SomeWidget::contextMenuEvent(QContextMenuEvent* event)
{
    auto menu = new QMenu(this);
    // Wrong. It is unreliable
    QAction* action = menu->exec(QCursor::pos());
    // Good.
    QAction* action =  menu->exec(event->globalPos());
}
</syntaxhighlight>


== Embedding KParts ==  
== Embedding KParts ==  


KXMLGui widgets can have "stand-alone" popup menus defined in the XML .rc file (i.e.<tt><Menu></tt> elements that are not children of other elements).
KXMLGui widgets can have "stand-alone" popup menus defined in the XML .rc file (i.e. <tt><Menu></tt> elements that are not children of other elements).
 
KXMLGui >= 5.35 has been fixed to use the <tt>QMainWindow</tt> of the application as parent of stand-alone menus. If your application is embedding a KPart widget, make sure you are not doing it wrong:
 
* '''Wrong''': embed a part widget in a <tt>QDialog</tt>.
* '''Good''': embed a part widget in a <tt>KParts::MainWindow</tt> and call <tt>createGUI()</tt> on the part.
 
= Tooltips =
 
Tooltips have the same problem of popup menus, as they also need a transient parent window. Text-only tooltips created by Qt are fine, but if your application is using custom tooltips that contain other widgets, you should port to [https://api.kde.org/frameworks/kwidgetsaddons/html/classKToolTipWidget.html KToolTipWidget].
 
= Application Icon =
 
On Wayland <tt>setWindowIcon()</tt> no longer works. This also means that currently is not possible to set a per-window icon (because the xdg-shell standard doesn't allow it). It is still possible to set the main application icon that will be shown in task managers and window decorations:
 
* The name of the application icon will be fetched from the [https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html .desktop file] of the application.
* The name of the .desktop file must adhere to the reverse domain standard (e.g. <code>org.kde.app.desktop</code>).
 
Most of the KDE applications are already working fine because <tt>KAboutData</tt> takes care of all the necessary steps. If for some reason your application is not using <tt>KAboutData</tt>, you need to manually call <tt>QGuiApplication::setDesktopFileName()</tt>.
 
== Exec key of .desktop files ==
 
The <tt>Exec</tt> key of your .desktop file should not contain the <code>%i</code> "field code". Your application won't start on Wayland otherwise, because the [https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables specification] expands that code in the <tt>--icon</tt> argument, which is accepted by <tt>QGuiApplication</tt> only on XCB platforms. This [https://codereview.qt-project.org/#/c/82796/ code review] contains a discussion about the upstream decision. You can use the <tt>-qwindowicon</tt> argument as replacement. It still won't work on Wayland (it will on most other platforms) but now your app will start everywhere.
 
<syntaxhighlight lang="ini">
// Don't
Exec=someapp %i %U
// Do
Exec=someapp -qwindowicon someicon %U
</syntaxhighlight>


KXMLGui >= 5.35 has been fixed to use the QMainWindow of the application as parent of stand-alone menus. If your application is embedding a KPart widget, make sure you are not doing it wrong:
= QClipboard::mimeData() =


* '''Wrong''': embed a part widget in a QDialog
<tt>QClipboard::mimeData()</tt> can return nullptr on Wayland, leading to crashes in code assuming that the mimeData pointer is always valid. Always check the validity of the pointer returned by <tt>QClipboard::mimeData()</tt>.
* '''Good''': embed a part widget in a KParts::MainWindow and call createGUI() on the part.

Latest revision as of 16:46, 18 October 2019

This documents contains porting notes for Wayland. Even if you don't use the Plasma Wayland session as your daily driver, you can still test the behavior of your application on Wayland and fix the bugs. Check the KWin/Wayland wiki page and also this blog post by Martin, which contains general info about Wayland porting.

Popup Menus

Chances are that some popup menus of your application will be misplaced on Wayland. This is because the compositor needs to know how to relate the QMenu's window with the main window of the application. This is done by setting a transient parent QWindow on the QMenu. The easiest way to do so is ensuring that the menu is created with a parent widget:

// Don't
auto menu = new QMenu;
menu->popup(somePos);
// Do
auto menu = new QMenu(someParentWidget);
menu->popup(somePos);

// Don't
QMenu::exec(someActions, somePos);
// Do
QMenu::exec(someActions, somePos, nullptr, someParentWidget);

Note

Setting a parent widget also changes memory management, those aspects are currently not handled separately. So be aware that the QMenu instance will be deleted as child if the parent widget is deleted. So if e.g. menu entries result in synchronous deletion of the parent widget, this will result in a crash in the QMenu::exec(...) methods.


If setting a normal parent widget is not possible/wanted, you will currently (as by Qt <=5.9.1 API) have to set the transient parent on the menu manually. The following code assumes that the transient parent widget to be used is already shown on the screen, thus registered with the native display system:

auto menu = new QMenu;
menu->winId(); // trigger being a native widget already, to ensure windowHandle created
// generic code if not known if the available parent widget is a native widget or not
auto parentWindowHandle = parentWidget->windowHandle();
if (!parentWindowHandle) {
    parentWindowHandle = parentWidget->nativeParentWidget()->windowHandle();
}
menu->windowHandle()->setTransientParent(parentWindowHandle);

Warning

winId() will break applications that use QQuickWidget. If that's the case, you could try to subclass QMenu in order to call the safer QWidget::create() protected method.


Drop Menus

"Drop menus" are popup menus that are created because of a drag-and-drop event. On Wayland there is no global cursor position and QCursor::pos() only works by tracking the mouse move events. That's not 100% reliable, and in fact it won't work inside dropEvent() functions:

void SomeWidget::dropEvent(QDropEvent *event)
{
    auto menu = new QMenu(this);
    // Wrong. It will position the popup where the drag started, not where the drop happened.
    menu->popup(QCursor::pos());
    // Good.
    menu->popup(mapToGlobal(event->pos());
}

Context Menu

Similarly to Drop Menus, on wayland QCursor::pos() does not work as expected, and should be avoided to position context menus :

void SomeWidget::contextMenuEvent(QContextMenuEvent* event)
{
    auto menu = new QMenu(this);
    // Wrong. It is unreliable
    QAction* action = menu->exec(QCursor::pos());
    // Good.
    QAction* action =  menu->exec(event->globalPos());
}


Embedding KParts

KXMLGui widgets can have "stand-alone" popup menus defined in the XML .rc file (i.e. <Menu> elements that are not children of other elements).

KXMLGui >= 5.35 has been fixed to use the QMainWindow of the application as parent of stand-alone menus. If your application is embedding a KPart widget, make sure you are not doing it wrong:

  • Wrong: embed a part widget in a QDialog.
  • Good: embed a part widget in a KParts::MainWindow and call createGUI() on the part.

Tooltips

Tooltips have the same problem of popup menus, as they also need a transient parent window. Text-only tooltips created by Qt are fine, but if your application is using custom tooltips that contain other widgets, you should port to KToolTipWidget.

Application Icon

On Wayland setWindowIcon() no longer works. This also means that currently is not possible to set a per-window icon (because the xdg-shell standard doesn't allow it). It is still possible to set the main application icon that will be shown in task managers and window decorations:

  • The name of the application icon will be fetched from the .desktop file of the application.
  • The name of the .desktop file must adhere to the reverse domain standard (e.g. org.kde.app.desktop).

Most of the KDE applications are already working fine because KAboutData takes care of all the necessary steps. If for some reason your application is not using KAboutData, you need to manually call QGuiApplication::setDesktopFileName().

Exec key of .desktop files

The Exec key of your .desktop file should not contain the %i "field code". Your application won't start on Wayland otherwise, because the specification expands that code in the --icon argument, which is accepted by QGuiApplication only on XCB platforms. This code review contains a discussion about the upstream decision. You can use the -qwindowicon argument as replacement. It still won't work on Wayland (it will on most other platforms) but now your app will start everywhere.

// Don't
Exec=someapp %i %U
// Do
Exec=someapp -qwindowicon someicon %U

QClipboard::mimeData()

QClipboard::mimeData() can return nullptr on Wayland, leading to crashes in code assuming that the mimeData pointer is always valid. Always check the validity of the pointer returned by QClipboard::mimeData().