Jan Grulich

When Your Webcam Doesn’t Work: Solving Firefox and PipeWire Issues

If you are a regular reader of my blog posts, as I am sure you are, you will know that we made a switch with Fedora 41 and now use PipeWire as the default backend for camera handling in Firefox. It won’t come as a surprise that such a huge change is not without its problems. After talking to many of you and debugging the same issues over and over again, I would like to go through most of the common issues and show you how to fix them, and also shed some light on the whole stack.

For Chrome/Chromium users out there, most of the issues mentioned here also apply to you, as most of the PipeWire camera code is shared in WebRTC, but I must mention that the PipeWire camera is completely broken in M131 and fixed in M132.

Issue #1 – Permission issue – “Camera is blocked” or “The request is not allowed” etc.

This is the most common problem and is usually caused by the user not giving access to the camera to an application that is requesting it. When Firefox wants to use a camera, it makes a request to the camera portal (xdg-desktop-portal). This results in a system dialogue asking for camera access for Firefox. If access is granted, it will be granted for all future sessions, but if access is denied or the dialogue is closed, it will remember this decision and all future requests to use the camera will be automatically denied.

You can check this by running:

$ flatpak permissions devices camera
Table   Object App                 Permissions Data
devices camera                     yes         0x00
devices camera org.mozilla.firefox yes         0x00

In the result you will see that “org.mozilla.firefox” has “yes” stored in the permission store. There is also an empty entry with “yes” stored. The empty entry is usually for applications for which we were unable to get an application id. This happens for host applications that are launched in an unusual way, such as the Alt + F2 command or from a terminal. If you have a permission problem, you will most likely see “no” stored there, and this is what is causing the problem for you.

You can clear this and be prompted again for camera access running this command:

flatpak permission-remove devices camera org.mozilla.firefox

You may be wondering why flatpak is involved, since you don’t use flatpak applications. Flatpak is not really necessary, but I use its command line to work with the permission store and it is easier for me to just give you a command and you give me the result, otherwise you could also use Flatseal to check your camera permissions. The permission store comes from portals (xdg-desktop-portal), which we use to get access to your camera. While portals were originally intended to be used mainly by sandboxed applications (Flatpak, Snap), they are now also used for things like screen sharing (Wayland) and now the PipeWire camera, making them an essential part of the Linux desktop stack. Always make sure that xdg-desktop-portal is installed with a specific portal backend for your desktop, e.g. xdg-desktop-portal-gnome for GNOME or xdg-desktop-portal-kde for Plasma.

Issue #2 – No camera found

This can be a problem with many components. Let’s start with the most important one, which is finding out if Wireplumber (the session and policy manager for PipeWire) detects it.

You can run:

$ wpctl status
Video
 ├─ Devices:
 │      50. Integrated Camera                   [v4l2]
 │      62. Integrated Camera: Integrated C     [libcamera]
 │      63. Integrated Camera: Integrated I     [libcamera]
 │      69. Integrated Camera                   [v4l2]
 │      85. Integrated Camera                   [v4l2]
 │      93. Integrated Camera                   [v4l2]
 │  
 ├─ Sinks:
 │  
 ├─ Sources:
 │      76. Integrated Camera (V4L2)           
 │  *   80. Integrated Camera (V4L2)           
 │  
 ├─ Filters:
 │  
 └─ Streams:

Here we are mainly interested in “Sources“, as this is what will appear in Firefox. Typically, most laptop cameras appear here twice, as one is an infrared camera for Windows Hello support, which we already filter out in Firefox. If your camera doesn’t appear there, it won’t work in Firefox or any other application that uses PipeWire.

If your camera is listed there but doesn’t appear in Firefox, I usually recommend that people try OBS Studio, which has great support for PipeWire cameras. This will always tell you if the problem is in Firefox or somewhere else. If it works in OBS Studio, you can open a bug to Firefox with all the necessary information (see below). If not, it is probably a bug in PipeWire.

We are already tracking one issue with the v4l2 plugin in PipeWire. This is most likely a race condition for which we have at least a workaround in the form of switching from v4l2 to libcamera.

In order to use libcamera, you can create following file:

$HOME/.config/wireplumber/wireplumber.conf.d/99-libcamera.conf

With the following content:

wireplumber.profiles = {
 main = {
   monitor.v4l2 = disabled
   monitor.libcamera = optional
  }
}

And restart both Wireplumber and PipeWire:

systemctl --user restart pipewire wireplumber

If this doesn’t solve the problem for you, please follow the instructions below to report a bug to Firefox.

Another known problem, probably a rare one, is if you restart PipeWire while Firefox is still running. This is because we keep a connection to PipeWire and when you restart it, that connection is broken and not initialised again. This problem affects OBS Studio in the same way and I’m already working on a fix. The solution here is to restart Firefox.

Debugging and reporting issues to Firefox

You came here because none of the above worked? You can still report a bug with all the necessary information to help us identify the problem. First, you want to report a bug to Firefox upstream. You can do this here by selecting the “Core” product and the “WebRTC: Audio/Video” component and providing all the logs from below.

Include DBus communication with xdg-desktop-portal.

Open a terminal of your choice and run:

dbus-monitor --session

Keep it running, while you try to access the camera in Firefox. For example, using the WebRTC getUserMedia test page. You should see all the DBus communication in the log from dbus-monitor.

Also a useful information might be to know whether the camera portal see any camera by running:

dbus-send --print-reply --dest=org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop org.freedesktop.DBus.Properties.Get string:"org.freedesktop.portal.Camera" string:"IsCameraPresent"

Including log from Firefox by running it with:

MOZ_LOG="MediaManager:5,CamerasParent:5,CamerasChild:5,VideoEngine:5,webrtc_trace:5"

And a log from:

pw-mon

Which will provide all advertised formats by your camera and supported formats by Firefox.

Last resort

Once you have done your duty and opened a bug with all the above information, which I’m really grateful for, you can now go to “about:config” in Firefox, disable “media.webrtc.camera.allow-pipewire” and restart Firefox. This will switch back from PipeWire to v4l2, but I hope you will accept this as a temporary solution until we can identify and fix your problem.

Debugging and reporting issues to Chromium

All the logs you can provide also apply to Chrome/Chromium, with the exception to logs from the app itself.

In order to get logs from Chrome/Chromium, you need to run it with:

google-chrome --enable-logging --vmodule=*/webrtc/*=1

Once you have collected all the necessary logs, you can open a bug here for the “CameraCapture” component and add me to the bug (use grulja AT gmail.com) or let me know at least so I’m aware.

To switch back from PipeWire to v4l2 you have to go to “chrome://flags” and disable “PipeWire Camera support“, but you already know that since you had to enable it yourself before :).

Making PipeWire default option for Firefox camera handling

Starting with Fedora 41, we will make PipeWire the default backend for camera handling in Firefox. This is part of the effort to integrate support for Intel IPU6 cameras. You can read more about IPU6 cameras at the link proposing this feature, but the important part is that these cameras can only (as of now) be used with PipeWire.

Is PipeWire camera support mature enough?

Yes! At least I think so. I know there are people using it every day and I’ve been working hard to fix all the possible issues and close the gaps between PipeWire and V4L2 backends. There has also been a lot of progress in making libcamera’s software ISP work with PipeWire camera in WebRTC. This was mainly the work of Robert Mader and Kieran Bingham. Robert submitted a patch to libcamera and Kieran has helped me to verify and test changes on the WebRTC side, where I had to add support for all the additional video formats [1, 2].

Screenshot with libcamera’s software ISP in action using Firefox. Rumors are that Kieran is already using it regularly for softISP calls 🙂

There has also been amazing work done by Hans de Goede on this front. Besides all the IPU6 work, Hans fixed duplicate device entries in PipeWire’s libcamera implementation and pushed some fixes to V4L2 support in PipeWire to fix some races in device enumeration. This should fix issues with some cameras not being properly detected at startup, which affects the camera portal to properly advertise camera availability, as we rely on that Firefox. Speaking of duplicate device entries, I recently also fixed (removed) duplicate camera entries represented by IR cameras. These were previously shown as duplicate camera entries in the device list, but if the user accidentally selected the IR camera, it didn’t work at all. Another set of PipeWire camera fixes in Firefox are currently under review, mainly with some fixes and WebRTC backports and also implementing missing support for device change updates. I have to also mention Andreas Pehrson from Mozilla, who has been a great and active reviewer of all the changes in WebRTC upstream and especially in Firefox, which also contributes to the adoption of PipeWire camera support.

PipeWire camera support in Chromium

PipeWire camera support has been merged into Chromium for M127 and was implemented by Michael Olbrich from Pengutronix. To use it, you just need to go to chrome://flags and enable the PipeWire camera support flag. Unfortunately, this version is broken, because Michael and I haven’t tested it with an official build. The official build enables Control Flow Integrity, which might be violated by PipeWire calls, because of the way the PipeWire library is used (it is dlopened). We had the same problem in the past with screencast support. This was uncaught and released, but I fixed it already in time to be backported to M128, which is behind the doors already. Chromium will also benefit from all of the above fixes because it’s all done in WebRTC, which is shared with Chromium and Firefox.

Testing PipeWire camera support

Testing is something we need right now to catch all the bugs in time for the Fedora 41 release. In the case of Chromium, you will need to wait for Chromium 128 and enable the flag as mentioned above. For Firefox you can go to about:config and enable the media.webrtc.camera.allow-pipewire option. In the case of Fedora 41+, you shouldn’t need to do this as we’ve already made the switch in the latest Firefox build. You can also read my previous blog post for more details, but you can ignore all the issues there as they are already fixed. If you do find a problem, the best place to report it is on the WebRTC bug tracker, as it’s most likely a general issue, not specific to Chromium or Firefox.

In most setups (default), you will probably end up using PipeWire camera with V4L2 backend in PipeWire, but you can also install libcamera and libcamera plugin in PipeWire as mentioned in my previous blog post. The only thing that has changed is that recent Wireplumber will avoid duplicate camera entries and libcamera nodes will be hidden. To filter out V4L2 nodes and show libcamera instead, you can create a .config/wireplumber/wireplumber.conf.d/disable-v4l2.conf file with the following content:

wireplumber.profiles = {
  main = {
    monitor.v4l2 = disabled
  }
}

After restarting Wireplumber, you should see only libcamera nodes. You can check this with wpctl status.

Now let’s make Fedora 41 another great release, packed with the latest technologies.

How to use PipeWire camera in Firefox

I decided to write a post about how to use the PipeWire camera support in Firefox, how to enable it, how to check if it’s working and how to debug possible problems.

Prerequsities

To use PipeWire camera support, you need Firefox 116 or newer. However, the latest Firefox 122 includes major PipeWire camera changes, making it actually usable (you can read about it in my previous blog post).The PipeWire camera is not enabled by default. To enable it, go to “about:config” in your address bar, find the “media.webrtc.camera.allow-pipewire” option, and enable it. Afterward, restart Firefox.

Using PipeWire camera in Firefox

When using the PipeWire camera, you should not notice any difference compared to using V4L2. The only change is that you will receive an additional system dialog from xdg-desktop-portal asking for camera access, which is a one-time occurrence. Your cameras will have ‘(V4L2)’ in their name if no libcamera is involved (more on that later). This indicates that they are using V4L2 through PipeWire. To check if PipeWire is being used, you can also use an application like Helvum to check if your camera is detected and used through PipeWire. Below, you can see what Helvum graphs look like when using my camera with PipeWire:

To verify that your camera is detected by PipeWire, you can use Helvum alone, without Firefox. This way, your camera on the left side will just not be connected to any client on the right side.

PipeWire camera is not working

1) I don’t get any system dialog or Firefox says access to the camera has been rejected

If your camera is visible in Helvum, then PipeWire can detect it. First, verify that you have not previously denied access to the camera or accidentally clicked “do not allow” when prompted. To do this, reset the permissions by running “flatpak permission-remove devices camera” to ensure you will get asked again. In case you still don’t get prompted, make sure xdg-desktop-portal is installed and running, as well as either xdg-desktop-portal-gnome or xdg-desktop-portal-kde. Note that for example xdg-desktop-portal-wlr doesn’t support camera portal, which is essential for this to work. You can also restart xdg-desktop-portal with “G_MESSAGES_DEBUG=ALL” env variable set to further debug issues with xdg-desktop-portal.

2) Camera is not working in general or once selected and allowed to be used

In this case best thing you can do is to open a bug, while providing information about your camera hardware and including output from Firefox you run with PIPEWIRE_DEBUG=5 MOZ_LOG="MediaManager:5,CamerasParent:5,CamerasChild:5,VideoEngine:5". Since it might be a missing format we (WebRTC) don’t support yet or issue in V4L2 integration or you having some modern camera that doesn’t work yet very well on Linux (e.g. Intel MIPI camera).

3) Google Meet doesn’t work

This is a known issue and actually a problem in Google Meet that stopped working after some recent update. There is an upstream ticket in Firefox to track this problem, but sadly hasn’t been resolved yet. To workaround this issue, visit any testing camera page (e.g. this one) first, and then use Google Meet.

4) Firefox crashes when sharing my camera with two websites

Also a known issue. Here is an upstream WebRTC ticket and I have a change already submitted for review. This change will be backported to Firefox as soon as it’s merged in WebRTC upstream.

5) Any other issue

Please, let me know with any other issue you have. Best thing you can do is to open a bug report to Firefox, where you pick “WebRTC: Audio/Video” as a component.

Using libcamera

For some modern cameras, this may be the only way to make them work in Firefox. This is not a comprehensive guide on how to use it, but it is what made it work in my setup on Fedora.

1) Install libcamera and libcamera plugin for PipeWire. On Fedora you can run:
sudo dnf install libcamera pipewire-plugin-libcamera

2) Restart PipeWire and Wireplumber (PipeWire session manager) by running:
systemctl --user stop wireplumber
systemctl --user stop pipewire
systemctl --user start wireplumber

You should see something like:
led 30 15:30:43 fedora wireplumber[91582]: [2:36:44.619446407] [91582]  INFO Camera camera_manager.cpp:284 libcamera v0.2.0
Compared to following output while not having the plugin installed:
led 30 12:54:29 fedora wireplumber[1925]: SPA handle 'api.libcamera.enum.manager' could not be loaded; is it installed?
led 30 12:54:29 fedora wireplumber[1925]: PipeWire's libcamera SPA missing or broken. libcamera not supported.

3) Check your camera in Helvum

In the screenshot above, my cameras are presented twice: once through the V4L2 integration in PipeWire and again thanks to libcamera. Unfortunately, I don’t know yet how to disable the V4L2 integration when using libcamera so that your cameras are not shown twice.

Anything else?

Currently, I am working on implementing a fallback mechanism to use V4L2 if PipeWire fails in certain scenarios (upstream bug | upstream change) to ensure that users still have a functioning camera in the case of a broken PipeWire/portal setup. However, I expect that many issues will be discovered as people begin to test this feature, for which I am very grateful.

PipeWire Camera Support in Firefox #2

I wrote the first blog post about PipeWire cameras in Firefox in May and a lot has happened since then. The first PipeWire support arrived shortly after the blog post was published and was released as part of Firefox 116 (August). We didn’t enable it by default, of course since it’s still a “work in progress”, but many of you tried it (thank you for that) and we were able to fix some issues that I, as the only tester at the time, hadn’t found. However, aside from all the crashes and minor issues we were able to fix relatively quickly, there was one major problem (or drawback) with the PipeWire camera that made it unusable with most popular video conferencing sites, such as Google Meet. Kind of a deal breaker, right? This has kept me busy ever since, but we are finally close to fixing it in upstream. I’m going to explain why this was a problem and how we fixed it, and forgive me in advance if I write anything wrong, I’m still learning and discovering things as they unfold.

There are Javascript APIs that are implemented by all the major browsers. The API documentation is here. It defines APIs sites can use to query information about media devices. I will now describe a simplified workflow used with V4L2 on the aforementioned Google Meet once you start a meeting:

  • GMeet makes enumerateDevices() call to get information about available devices
  • Firefox can respond with the list of available cameras (+ audio devices obviously) on the system because the information about cameras is available and no permission is needed
  • GMeet makes getUserMedia() call to get access to the camera since it knows there is a camera available
  • Firefox will prompt the user to get access to the selected devices (including camera) and start streaming

Now the same situation, but with PipeWire:

  • GMeet makes enumerateDevices() call to get information about available devices
  • Firefox cannot respond with the list of available cameras because this enumeration request cannot ask for user permission and we cannot access PipeWire without it. Firefox will return an empty list of camera devices and there will be only audio devices announced
  • GMeet makes getUserMedia() call, but only to get access to the devices that were previously announced, so only audio devices
  • Firefox will prompt the user to get access only to the selected audio devices and no camera

How did we solve this?

The documentation here also covers this situation. The enumerateDevices() request is allowed to return a placeholder device for each type. This means that we can return a placeholder camera device, which tells Google Meet there is actually a camera device to ask for. With this device placeholder, the subsequent getUserMedia() request will also request access to camera devices. How do we know that a camera device is present without having access to PipeWire, you ask? The camera portal from xdg-desktop-portal has a IsCameraPresent property for exactly the same purpose and we use it to know whether to insert the camera device placeholder or not.

While such a solution sounds simple on paper, it required a significant amount of changes to the entire media handling stack. There is not a small amount of PipeWire specific code, so this fix also involves some restructuring so that all the backend specific logic is in one place. And while I’m getting more and more familiar with the Firefox code, which is helping me to progress faster, there’s still a lot to learn.

Anyway, the reason I’m writing this blog post now is that all the related changes have been approved and will hopefully be landing soon, making Firefox fully usable with PipeWire . Although not yet merged, Fedora users can use a COPR repository I created. The repository has Fedora Firefox builds with all the necessary changes backported and PipeWire camera enabled by default. Just note that while I’ve been testing and using it for the past few months and it’s worked perfectly for me, you use it at your own risk. You better to use it just to test PipeWire camera support as the official Fedora Firefox package is the one we keep fully updated and my repo may lag behind. There will be a new PipeWire 1.0 release soon, which will be a big milestone for PipeWire and I hope that PipeWire camera support in Firefox and browsers in general will be part of the PipeWire success story.

Qt theming in Fedora Workstation

We have been working on and using custom Qt theming in Fedora Workstation for many years now. By custom Qt theming, I’m talking about the QGnomePlatform and Adwaita-qt projects. If you haven’t heard of them, you can read my recent blog post explaining what they are. While these projects are in some ways better than what Qt upstream has to offer, there were also drawbacks/issues and that’s why I decided to make a final decision and discontinue both projects. The issues are explained in the aforementioned blog post, but one of the main drawbacks is that we are in this development alone and not working directly in the upstream makes it less attractive for other contributors. It’s also not used by default anywhere other than Fedora, so it’s not properly tested by other developers working on Qt applications using different distributions. These reasons led me to submit a Fedora 39 feature to remove our custom Qt theming in Fedora Workstation in favor of Qt’s defaults. The only problem is that if we just go with Qt’s default, we would go backwards a bit. This is because upstream Qt does not provide any decent client-side window decorations (problem #1), and the QGtkTheme in Qt5 (QGnomePlatform equivalent) is a bit behind its Qt6 version with many improvements and integration goodies (problem #2) recently made by Axel Spoerl of the Qt Group, whom I met during this year’s KDE Akademy.

Solution to problem #1

QGnomePlatform used to be our solution to this problem, as QGnomePlatform implemented it’s own version of the QWaylandAbstractDecoration plugin. This was a GTK 3-like decoration plugin that used Adwaita-qt for button rendering and QGnomePlatform bits (e.g. GSettings configuration) to get the titlebar layout. Since we are going to remove QGnomePlatform, we needed an alternative. So I started working on the QAdwaitaDecorations project. This is supposed to be a an intermediate step as I would like to have a proper GNOME/Gtk decorations directly in Qt upstream, but since I was in a hurry to get everything done in time for Fedora 39, we have this for now. QAdwaitaDecorations plugin is based on the decorations we have in QGnomePlatform, but there is no dependency on GTK or Glib (e.g. GSettings) or on Adwaita-qt. We use xdg-desktop-portal to get the titlebar layout and do our own drawing instead. This decoration plugin should also have now a GTK 4-like style so the buttons and colors of the decorations are different.

Below is a screenshot of Wireshark (Qt6) using QAdwaitaDecorations plugin + QGtkTheme + Fusion:

Solution to problem #2

Since Qt5 is no longer actively developed, the only possible solution is to backport all QGtkTheme improvements from Qt6, so I did that + modified some of those changes to avoid breaking binary compatibility. This results in about ~15 related backports to Qt5 so far, and it seems to work pretty well. I also made sure that Fedora 38 and older will still use QGnomePlatform by default, so we don’t change the behavior for existing users. Also, a small change to our QtWayland package was needed to make it use the new decoration plugin by default.

Future plans

As mentioned, I would really like to have everything directly in Qt upstream (talking about QAdwaitaDecorations). That way we get other contributions and thus fixes/improvements for free and a lot more users. Another thing is that QGnomePlatform supports things that are not yet supported/implemented in QGtkTheme, like support for xdg-desktop-portal instead of just relying on GSettings. Not to mention that GTK 4 has been around for a while, and both QGnomePlatform and QGtkTheme are still GTK 3 based. I will definitely try to make some of these things happen for Fedora 40, but knowing myself, it’s better not to make any promises, as things usually don’t go according to plan.

PipeWire camera support in Firefox

New year, new challenges.

We finally reached a major milestone with Chromium 110, which was a release where we finally got screen sharing enabled by default on Wayland, and since then you no longer have to go into the preferences and enable the flag you need. That doesn’t mean my work there is over, but I’ve shifted my focus to something related but slightly different and that is PipeWire camera support.

Work on PipeWire camera support started in 2021 and was done by Michael Olbrich (Pengutronix). He submitted a huge change to Chromium to add this support and had trouble finding a reviewer because there was actually no one who knew anything about PipeWire in the Chromium project. I actually saw his change request by accident, but we got in touch and decided to move this to WebRTC instead, because having it lower in the stack means we would get it automatically in other browsers, like Firefox. Michael attended a meeting we used to have regularly for screen sharing support in WebRTC and we discussed how to implement PipeWire camera support in WebRTC instead and how to reuse some of the code we already had for screen sharing to avoid code duplication. After a few submitted and reverted reviews (usually when things break Chromium parts that are not covered by CI, happened to me many times), we ended up with PipeWire camera support in WebRTC (talking about the beginning of this year).

Journey to PipeWire camera support in Firefox

Up to recently, my work has mostly been 95% WebRTC and 5% Chromium, but I have not been familiar with Firefox at all (not counting WebRTC backports). I actually started fixing screen sharing support in there first before moving to camera, because I noticed a few issues after Firefox (finally) did a WebRTC rebase to some of the newer versions. They’ve actually started doing monthly WebRTC rebases, which is really a good thing and I’m glad to see that happening. Anyway, even though Firefox has more recent WebRTC these days, when I started in February, there was still no PipeWire camera support at all because WebRTC was still a few months behind, so I had to backport all the patches and make them work with Firefox. Only then I could finally start working on the actual PipeWire camera support from WebRTC. Working on the backports, I was still working in the WebRTC space, so everything was somewhat familiar. Implementing the actual PipeWire support was a different story and took me some extra time to understand how everything works. This includes camera API on the WebRTC side, camera support on the Firefox side, and I also had to learn all the APIs specifically used in Firefox, but admittedly, learning about new things is fun too. After some tries and errors it started to work and I was able to share my camera using the PipeWire camera backend from WebRTC. You have to trust me that the picture below is not using the V4L2 backend.

I went ahead and submitted my WebRTC backports and the PipeWire camera backend implementation for review to Firefox. Unfortunately, I was told that the code where I placed my implementation could also be used by the WebRTC Javascript API, which is used by bots to check for camera presence on the client side, which I didn’t know as someone who just recently started working on camera support. This was a problem because we get PipeWire access through xdg-desktop-portal and this involves showing a dialog to the user asking for camera access. Showing a camera request dialog randomly to the user would not be a good experience. Going back to the drawing board, I talked to Andreas Pehrson (Mozilla/WebRTC). Andreas was a great helper and we came up with a solution on how to implement it properly in Firefox and avoid things like I’ve mentioned before. This time it involved some re-org changes in WebRTC, where I split the xdg-desktop-portal and PipeWire implementations for PipeWire video capture, so we can request camera access in Firefox only when appropriate and only do the PipeWire stuff in the backend assuming the access was granted. So I did implement it again, this time according to what we agreed on with Andreas and it worked.

This is now submitted for review again and hopefully this time it will only need some minor fixes and not a complete rewrite like before, and you will be able to try/use it sooner rather than later. The main change is submitted here, but it is accompanied by other changes with WebRTC backports or changes that make the backports buildable with Firefox. With the first version of the change, I had a Fedora COPR repository, but I had to discontinue it, because it was too hard to maintain it in a buildable state on top of a stable Firefox. But you can be sure that Fedora will be the very first consumer of these changes once they are merged.

Why do we need this?

For many reasons. I would recommend you to read a blog post from Christian Schaller, where everything is explained into the details and gives you more information about the camera stack. Main reasons are:

  • Security
    • Access to the camera must be granted by the user, so you can be sure that no one is using your camera behind your back.
  • Flexibility
    • Your camera can be accessed by multiple clients simultaneously.
  • Libcamera support
    • Needed for ARM devices or devices using ChromeOS

Chromium support

While support in WebRTC has been done already a few months ago, Chromium originally didn’t use WebRTC video capture API for camera support and for that reason it had to be added. Michael implemented it and it is still currently pending on review so currently both Chromium and Firefox are both implemented, but waiting for approval.

Future plans

Most importantly, I want to get everything merged and working seamlessly, but I’m already aware of some issues and missing functionality in the PipeWire backend in WebRTC. And we also have the same problem we used to have with screen sharing, which is that it’s not enabled by default, unit tested, and feature complete and these things take time fix.

Explained: QGnomePlatform and Adwaita-qt

I decided to write this post to explain everything I can about these two projects. There have been discussions and people demanding these projects should not be used by default in Fedora. As part of this, some issues were raised and it might not be clear which component might be responsible for what. I ended up constantly defending these projects in many discussions and ended up being exhausted by doing so over and over, so take this as an explainer to shed some light.

Brief introduction to QGnomePlatform and Adwaita-qt

To give you some context before I go into details, you can think about Adwaita-qt as the UI representation and QGnomePlatform as the integration between GNOME and Qt. QGnomePlatform applies your GNOME configuration and behavior to Qt apps, together with some integration bits, like dialogs or client-side decorations. Adwaita-qt is responsible for the style of the app itself, including the style of all visible parts (widgets/buttons).

QGnomePlatform

What is QGnomePlatform?

QGnomePlatform is a Qt Platform Theme (part of Qt Platform Abstraction API), where such a plugin is responsible for the app integration into the desktop environment. It is designed to provide integration between Qt apps and the GNOME platform. To explain in an example. Without any platform integration, Qt apps running on GNOME would use default styling and configuration so your fonts, icon theme, dialogs would not fit into the desktop. Also in the case of QGnomePlatform you would not have GNOME-like client-side decorations.

What QGnomePlatform provides?

QGnomePlatform provides the following integration for Qt apps running in GNOME:

  • Font configuration *
  • Icon theme *
  • Cursor size and cursor theme *
  • Static hints (like double-click time, long-press time etc.) *
  • Dialogs:
    • File dialog (both using GTK3 * and native dialog using xdg-desktop-portal)
    • Font dialog using GTK3 *
    • Color dialog using GTK3 *
  • Client-side decorations
  • Support for Settings portal from xdg-desktop-portal settings
    • Unlike the built-in GTK3 theme that can get everything only from GSettings
    • Brings support for light/dark theme switching introduced in GNOME 42
  • Use Adwaita-qt theme by default (elephant in the room) and Adwaita color palette
    • Also provides support for additional themes, like Kvantum
  • Support for Cinnamon desktop

* these can also be provided by built-in GTK3 platform theme in Qt itself (just for comparison what QGnomePlatform does extra)

Issues QGnomePlatform gets wrongly blamed for

Client-side decorations

As stated above, QGnomePlatform provides an implementation of CSD. It’s actually the only Qt CSD implementation I’m aware of, excluding the reference implementation provided by QtWayland, named Bradient. Below is a screenshot comparing QGnomePlatform (left) and Bradient (right).

I saw many times people complaining about missing shadows support and resizing issues. The truth is that officially there was no proper shadows support in the Qt API until I introduced it with Qt 6.2. That’s the reason we don’t have it for Qt5, unless you are a Fedora user, where this support has been backported and enabled in QGnomePlatform build. I also fixed all kinds of CSD related issues in QtWayland.ven though Qt has proper support for shadows in Qt6 now, the reference implementation doesn’t use them.

Misplaced popups/menus

This has nothing to do with Qt platform theme or CSD implementation, because it was actually a bug in QtWayland. Unfortunately this fix is only in Qt 6 and cannot be backported officially to Qt 5 as it would break KDE Plasma. I managed to at least patch QtWayland in Fedora and Flatpak KDE runtime, where I modified this patch to not affect KDE Plasma at all.

What can QGnomePlatform be blamed for?

Forcing Adwaita-qt color palette

QGnomePlatform sets Adwaita-qt color palette to each Qt app so applications can use QPalette API to get access to colors used in the style itself. This can be for example useful when an app creates custom widgets that would not get styled by the QStyle itself. This creates a problem for KDE applications using the KColorScheme API. 

Examples of this issue:

The reason is that KColorScheme and QPalette are out of sync and there might be color roles that are in KColorScheme, but not in QPalette. If an app requests a color from KColorScheme that’s not in QPalette, KColorScheme will default to Breeze style and a color that’s not going to fit the Adwaita-qt style will be provided, causing the app to mix light and dark colors.

Luckily, we have identified a workaround that can be done in QGnomePlatform to avoid this issue. Here is the QGnomePlatform bug with more details.

I think this is the most visible and user-facing issue we currently have and get blamed for so I would like to fix this as soon as possible.

Adwaita-qt

Adwaita-qt is a Qt style for widgets. Qt style is again part of Qt Platform Abstraction. You can think of it as a theme for your application. It’s what changes the visualization of your widgets like buttons, checkboxes etc. and it’s the only thing that changes the appearance of the application itself. For comparison, the screenshot below is Adwaita-qt (light) and the second one is the Qt’s default Fusion style used on Linux used without any QGnomePlatform influence so basically what you would get by default.

I can also add that Adwaita-qt supports HighContrast variants, which are useful for visually impaired people.

What issues can Adwaita-qt be blamed for?

I already mentioned the color mismatch issue which is not really Adwaita-qt’s fault. There are of course issues in Adwaita-qt itself and it’s far from being perfect. The whole style needs a complete overhaul, because the last one was done in 2019 and the Adwaita theme changed a lot recently with GTK4. Another issue is that while the majority of common widgets are styled just fine, there are still some widgets that are rarely used and might have issues with this style.

Another issue is that  apps that customize standard widgets (e.g. through CSS), might get into trouble. Below is a screenshot of Wireshark (pure Qt app) compared to Breeze in KDE.

Another example can be seen in the Black Chocobo app, which uses some customization:

 
Below is Black Chocobo using Fusion style.

Should QGnomePlatform get removed/replaced?

Definitely not. It has many benefits and extras compared to Qt’s default platform and most importantly gives you CSD support. Once I fix the color mismatch issue, there shouldn’t be anything users should complain about. Obviously, this would not be an issue when Breeze is used instead of Adwaita-qt, but still an issue when Fusion is used so it would still need to be addressed. You can see a screenshot below showing Fusion style used in combination with GNOME set to dark theme:

Should Adwaita-qt get removed/replaced?

Maybe. It depends on the alternatives. Obviously using Breeze would get rid of all the widget issues one might experience with Adwaita-qt, however, it is problematic to ship it by default due to bringing dependencies on KDE Frameworks and Plasma breeze style. With the default Fusion style you will also get many widget issues fixed, but you still need to set the color palette through QGnomePlatform in case you want Fusion to be “dark” and fit into the desktop, otherwise you will always end up using the default “light” variant no matter what configuration you set in GNOME.

Conclusion

I hope that this post is useful for those observing issues with Qt apps under GNOME, and will help them to understand which component is responsible for what, as well as the issues involved. In case you are interested and would like to either contribute a patch or report an issue, here are links to QGnomePlatform and Adwaita-qt repositories.

WebRTC (Chromium): Year end report

Although Wayland screen sharing is still not yet enabled by default in Chromium, which is what I hoped to achieve this year, I think I can say we are almost there and you can expect it sooner than later. Let’s summarize what we have accomplished this year to make this change happen:

Stream restoration support

You probably remember that you had to go through two portal (xdg-desktop-portal) dialogs all the time you wanted to share your screen. We had first portal dialog to have your selected screen visible in Chromium preview dialog and yet another portal dialog to make your screen shared with the web page itself. This was quite annoying as users had to make the same selection twice. Thanks to a new addition into portal API I was able to implement stream restoration support in WebRTC to bypass the second portal dialog and have your selection instantly shared with the web page itself once you confirm it in the Chromium dialog. This was released in Chromium 105.

Tests for PipeWire (streaming) code

This is not a feature that is visible to users, but it makes an important part of the whole process. It was a long effort to make this happen as it’s something that is not trivial to test and needs some dependencies for the tests itself to run. As a first step we had to bring PipeWire and some of its dependencies into the infrastructure. As easy as it sounds, in order to add a new dependency there you have to add it in form of a CIPD (Chrome Infrastructure Package Deployment) package. This means you write recipes with information how to build and get your package. This all on a CentOS 7 based distribution where you have to work with older libraries or missing libraries and tools (e.g. Meson). This makes your packages later available in third_party directory that is available to both Chromium and WebRTC. Next step was to write the tests itself. The only way how I could test our PipeWire code, code that is all about receiving frames over PipeWire stream, was to write another “testing” stream that will be sending us frames with parameters where we will know what to expect and can verify we received what we were supposed to receive. For the tests itself I used GTest framework which is very well documented and quite comprehensive. Sadly, we were still just in the middle of the process and one would hope that it cannot get more complicated, but the opposite is true. There is a whole runtime setup we need for our tests to run. We need PipeWire and PipeWire session manager to run, otherwise we would never activate and connect our streams. To create the setup I had to write a Python wrapper script that sets all environment variables for PipeWire to find everything in the third_party directory (plugins, libspa, etc.) and run both PipeWire and PipeWire session manager in order to run our test. As a last step, because we had a script that runs the test, we had to create a mapping in the infrastructure making the script itself a launcher of our test + limit this only to x86_64 architecture as that’s the only one where we have PipeWire available as CIPD package.

Here are upstream changes that implement all above mentioned:

UX improvements in Chromium preview dialog

All these improvements were made by Alexander Cooper, Alex is Google engineer working on screen sharing stack in Chromium and WebRTC. I have been intensively working with Alex for the past year and he is my go to person when it comes to code reviews or anything related to screen sharing in Chromium. Alex made a great set of UX improvements in their preview dialog. My original implementation always automatically invoked portal dialog when screen sharing was initiated. This led into one dialog overlaping the other. In most recent Chromium version (I think starting with 107) users will be presented with Chromium preview dialog first asking to share a web tab and only once they pick to share a screen they will get presented with the portal dialog. You can also now re-request the portal dialog in case you pick a wrong screen to share.

Web Engines Hackfest in A Coruña

I travelled to A Coruña in May to attend the Web Engines hackfest and meet Alex Cooper from Google there. This was a perfect opportunity for us to meet in person and I’m really grateful for that. Even though that for me it was a bit stepping outside of my comfort zone as I went somewhere where I didn’t know anyone (besides Alex), but as I later found out, all the people there were super friendly and I’m happy that I met some new faces. We had very productive conversations with Alex and having enough time to talk in person was beneficial for both sides as we could explain to each other technical details of our backgrounds and talk about future plans and current issues.

Firefox and WebRTC rebase

Firefox upstream has been behind with our WebRTC changes for more than ~2 years. This has changed recently with Firefox 106, where they finally rebased their WebRTC version to M103 (Chromium 103). I also provided them list of additional backports they should pick up in order to have fixes for some crashes and issues we have fixed since then. Sadly, as I later found out, even though they did the rebase and all the backports, the new codebase (for screen sharing) is not in use and instead they still keep the old code and use it instead. This is because they don’t have all the dependencies in place and it’s been blocked on this issue since then, hopefully it will get figured out soon and also Firefox users can benefit from all the improvements we did over the past two years. This is not an issue for Fedora users where I created a patch for our Firefox package that enables the new code and use it instead.

Plans for the future

  • Extend PipeWire code test coverage. Currently we test only the essential part of the code, but I would like to further extend the tests with tests for all kinds of metadata we can use (damage regions, crops, mouse cursor etc.)
  • Use portal dialog as the default one. This has some requirements that need to be fulfilled before Chromium can rely on it. We need to show screen/window previous, as currently it can easily happen you pick a wrong screen (especially when you have two identical monitors) and we need a way how to invoke Chromium dialog so users can share a web tab.
  • Fix bugs. I’m currently not aware of any issue and we already fixed a bunch of them this year as people use it more often, but I expect more issues to appear as Wayland becomes more and more dominant and we finally make it enabled by default.

WebRTC: journey to make wayland screen sharing enabled by default

While we have pretty good support for screen sharing on Wayland in WebRTC, which is included in browsers like Chromium or Firefox, it is still not enabled by default in Chromium and it is kept behind a flag. Not only you have to remember to always enable it for new configurations, but for many users it is not even something they are aware of. This has been my main focus recently and I would like to share with you steps that has been done and what are the plans for the future.

What are the changes to expect in Chromium soon?

DMA-BUF improvements/fixes:

Last year I landed proper DMA-BUF support in WebRTC, which made things way faster. It was working, but it was not perfect and there were some corner cases where it might not be working at all. Here are changes I made recently:

  • Advertise DMA-BUF support when it is really supported. Older versions of PipeWire don’t handle the new way of DMA-BUF negotiation and therefore it shouldn’t be used in such cases. Also using DMA-BUF modifiers requires some recent versions of PipeWire on both sides.
  • Implemented stream renegotiation. In situation when we fail to import a DMA-BUF with given modifier, we will drop this modifier and try to renegotiate stream parameters and go with a different modifier or fallback to shared memory buffers in case we fail completely.
  • Make sure to import DMA-BUF with correct render node. In case of multi-gpu setups, we always picked the first render node to import DMA-BUFs, but it can happen that they were actually produced by a different render node and for that reason we might fail to import them. We now try to get default EGLDisplay, which should be the same one used by the wayland compositor and we should be using same render node.

Better mouse cursor support:

Until now we had mouse cursor as part of the screen content. This means that everytime you moved with your mouse cursor, we had to update whole image and that is very inefficient. The API in WebRTC allows you to implement MouseCursorMonitor which can be used to track mouse changes only and each platform can have both MouseCursorMonitor and DesktopCapturer implementations combined in DesktopAndCursorComposer to get complete image and this all works automatically like a magic. Unlike X11 implementation, our only option is to get everything from one PipeWire stream we connect to and there was no way how to make it shared from DesktopCapturer implementation so it can be used by MouseCursorMonitor implementation. I had to split DesktopCapturer to have xdg-desktop-portal and PipeWire separate implementations. Code for PipeWire is now a SharedScreenCastStream class which is being shared through DesktopCaptureOptions. This is set of parameters associated with each capturer instance and luckily this is also passed to MouseCursorMonitor so we can have access to already initialized PipeWire stream and get the cursor data from there. Implement MouseCursorMonitor with SharedScreenCastStream was then piece of cake.

List of merge requests:

This should again significantly improve performance of screen sharing, because moving with a mouse over a static screen content doesn’t need full screen content update.

Misc:

Last but not least, I’m now in touch with Google developers who help me to review all my changes and discuss with me the current state, issues I have, etc. on monthly meetings we have. The plan is to make this finally enabled by default, hopefully in the first half of this year. There are still some things that need to be solved before this is enabled and there is lot of work ahead, but things look promising.

Plans for the future:

  • Implement stream restoration
    • this will allow us to skip the second portal dialog and I already have plan in my head how to do this in WebRTC. This is currently only supported by xdg-desktop-portal-gnome and xdg-desktop-portal-kde lacks this functionality.
  • Improve UX of the Chromium screen sharing dialog
  • Write tests for all PipeWire/portal code in WebRTC

Even though WebRTC is used in Firefox, I mostly talk about Chromium, because Firefox doesn’t use most recent WebRTC and will need to pick all the changes I did or rebase to newer WebRTC in order to have them. Firefox also has PipeWire/Wayland screen sharing enabled by default and doesn’t have UX issues as there is no internal screen sharing dialog like in Chromium.

I hope all these changes will make your experience better and next time when you read a new blog post I will be informing you about end of this journey.

How to use libportal/libportal-qt

There was a blog post from Peter Hutterer about Flatpak portals posted few months back. Peter explained what are portals and how do they work. Portals are used mostly because of security and sandbox/Wayland restrictions. Many times your only way to get access outside (opening a file, sending a notification, sharing a screen, etc.) is to use a portal. For most use-cases applications or developers don’t need to care about them as their support is usually implemented in libraries they use. For example Qt and GTK use portals internally so apps can use still the same APIs as before and they don’t need to worry about their apps not working in sandboxed environments. BUT there are still scenarios where libraries have unsufficient or none portal support, or a different options are desired so what are the options in this case if you still need to use portals?

  1. Do everything yourself, which means you will implement all the DBus calls and handling yourself.
  2. Use a library. Most logic choice would be libportal, but there is also a project called ASHPD for Rust users.

What is libportal and libportal-qt?

The libportal library provides GIO-style async APIs for Flatpak portals. It hides all the DBus complexity users would face in case of using portals directly and provides a user-friendly library instead. You might think that the libportal-qt is the same thing, just with Qt-style APIs, but the idea behind it is that each toolkit (Gtk3, Gtk4, Qt5, Qt6) has a different way to get a window handle which is needed to associate portal dialogs with the app that invoked them. So libportal-qt just provides a way to get a XdpParent object from a QWindow. As a C++/Qt developer I don’t mind using C/Glib APIs and I used it many times, but there is still one speciality I fail to use everytime, my friend GVariant. Some of the portal APIs in libportal expects a GVariant for all the complex structures, for example to specify a filter option for OpenFile() call from the fillechooser portal, you have to build a very complex GVariant based on the DBus specification.

Remember I told you libportal-qt doesn’t offer Qt-style APIs? This is not necessarily true, because I implemented all the complex structures you will have to pass in most of the portals and implemented functions that will return them as GVariants so you don’t need to get in touch with GVariants at all.

How to use libportal-qt?

First of all, all libportal flavours have pkgconfig file installed so it’s easy to use them from any build system and you just need to search for libportal-qt5 (we don’t have -qt6 version yet).

And how does the code look like? For example let’s say you want to open an image:

// Creates a filter rule, this can be a Mimetype or Pattern.
XdpQt::FileChooserFilterRule rule;
rule.type = XdpQt::FileChooserFilterRuleType::Mimetype;
rule.rule = QStringLiteral("image/jpeg");

// Create a filter with our rules, we will then pass it to OpenFile() call as GVariant.
XdpQt::FileChooserFilter filter;
filter.label = QStringLiteral("Images");
filter.rules << rule;

// Create a GVariant from our filter. This will result into variant in form of:
// "[('Images', [(1, 'image/jpeg')])]"
g_autoptr(GVariant) filterVariant = XdpQt::filechooserFiltersToGVariant({filter});

// Get XdpParent to associate this call (portal dialog) with our window.
XdpParent *parent = xdp_parent_new_qt(m_mainWindow->windowHandle());

// Finally open a file. XdpQt::globalPortalObject() is another convenient function 
// that creates a global instance of XdpPortal object so you don't need to take care
// of creating it yourself. For some of the arguments we just pass a nullptr to don't 
// specify them.
xdp_portal_open_file(XdpQt::globalPortalObject() /*XdpPortal object*/,
                                  parent /*XdpParent object*/, "Title", filterVariant /*filters*/,
                                  nullptr /*current_filter*/, nullptr /*choices*/, 
                                  XDP_OPEN_FILE_FLAG_NONE /*flags*/, nullptr /*cancellable*/, 
                                  openedFile /*callback*/, this /*data*/);
xdp_parent_free(parent);

// Then the callback would look like this, eg.
static void openedFile(GObject *object, GAsyncResult *result, gpointer data) {
    g_autoptr(GError) error;
    g_autoptr(GVariant) ret = 
        xdp_portal_open_file_finish(XdpQt::globalPortalObject(), result, &error);

    if (ret) {
        // Another convenient function that will get you uris and choices from 
        // GVariant returned by xdp_portal_open_file() call.
        XdpQt::FileChooserResult result = filechooserResultFromGVariant(ret);
        
        // Do whatever you want to do with the result. Here we just print opened selected files.
        qDebug() << result.uris;
    }
}

As you can see, no GVariant got hurt and you can easily open a file without any GVariant knowledge. Besides FileChooser portal helpers, we also have Notification portal helpers, because serializing icons and buttons is also something that is not trivial. For the rest of the portals you either don’t need to use complex GVariants so you can use them easily without helper functions same way as shown above, or some portals like ScreenCast or RemoteDesktop are not used that often and we don’t have helper functions for those just yet.

I hope you can find this helpful in case you want to join this world. The libportal project is hosted on GitHub in case you want to try it just now, because this is still not part of any stable release (will be in libportal 0.6), or report a bug or just look at my GVariant helpers to see what I spare you of.

Scroll To Top