Skip to content
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

[WIP] Various Wayland Fixes #173

Open
wants to merge 2 commits into
base: fixes/wayland
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmake/templates/Projecteur.desktop.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ GenericName=Linux/X11 application for the Logitech Spotlight device.
Icon=projecteur
Terminal=false
Categories=Office;Presentation;
X-DBUS-StartupType=Unique
X-DBUS-ServiceName=org.projecteur.Projecteur
X-KDE-DBUS-Restricted-Interfaces=org.kde.kwin.Screenshot,org.kde.KWin.ScreenShot2

107 changes: 96 additions & 11 deletions src/linuxdesktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <QFile>
#include <QProcessEnvironment>
#include <QScreen>
#include <QUuid>

#if HAS_Qt_DBus
#include <QDBusInterface>
Expand All @@ -24,6 +25,61 @@ LOGGING_CATEGORY(desktop, "desktop")
namespace {
#if HAS_Qt_DBus
// -----------------------------------------------------------------------------------------------
// This function works. However, it is not user-friendly and automated.
// Please see https://github.com/flatpak/xdg-desktop-portal/issues/649
QPixmap grabScreenDBusXDGdesktop()
{
QDBusInterface interface(QStringLiteral("org.freedesktop.portal.Desktop"),
QStringLiteral("/org/freedesktop/portal/desktop"),
QStringLiteral("org.freedesktop.portal.Screenshot"));
// unique token
QString token = QUuid::createUuid().toString().remove('-').remove('{').remove('}');
// premake interface
auto* request = new OrgFreedesktopPortalRequestInterface(
QStringLiteral("org.freedesktop.portal.Desktop"),
"/org/freedesktop/portal/desktop/request/" +
QDBusConnection::sessionBus().baseService().remove(':').replace('.', '_') +
"/" + token,
QDBusConnection::sessionBus(), NULL);

QEventLoop loop;
QPixmap pm;
const auto gotSignal = [&pm, &loop](uint status, const QVariantMap& map) {
if (status == 0)
{
QString uri = map.value("uri").toString().remove(0, 7);
pm = QPixmap(uri);
pm.setDevicePixelRatio(qApp->devicePixelRatio());
QFile imgFile(uri);
imgFile.remove();
}
loop.quit();
};

// prevent racy situations and listen before calling screenshot
QMetaObject::Connection conn = QObject::connect(
request, &org::freedesktop::portal::Request::Response, gotSignal);

interface.call(QStringLiteral("Screenshot"),
"",
QMap<QString, QVariant>({ { "handle_token", QVariant(token) },
{ "interactive", QVariant(false) } }));

loop.exec();
QObject::disconnect(conn);
request->Close().waitForFinished();
request->deleteLater();

if (pm.isNull())
{
logError(desktop) << LinuxDesktop::tr("Screenshot via DBus interface failed.");
return QPixmap();
}
return pm;
}
// -----------------------------------------------------------------------------------------------
// This function do not work in Gnome 41+. Remove this in future as grabScreenDBusXDGdesktop is
// more universal way of capturing screen on wayland.
QPixmap grabScreenDBusGnome()
{
const auto filepath = QDir::temp().absoluteFilePath("000_projecteur_zoom_screenshot.png");
Expand Down Expand Up @@ -84,21 +140,39 @@ namespace {
}
} // end anonymous namespace


OrgFreedesktopPortalRequestInterface::OrgFreedesktopPortalRequestInterface(
const QString& service,
const QString& path,
const QDBusConnection& connection,
QObject* parent)
: QDBusAbstractInterface(service,
path,
"org.freedesktop.portal.Request",
connection,
parent)
{}

OrgFreedesktopPortalRequestInterface::~OrgFreedesktopPortalRequestInterface() {}

LinuxDesktop::LinuxDesktop(QObject* parent)
: QObject(parent)
{
const auto env = QProcessEnvironment::systemEnvironment();
{ // check for Kde and Gnome
const auto kdeFullSession = env.value(QStringLiteral("KDE_FULL_SESSION"));
const auto gnomeSessionId = env.value(QStringLiteral("GNOME_DESKTOP_SESSION_ID"));
const auto desktopSession = env.value(QStringLiteral("DESKTOP_SESSION"));
const auto xdgCurrentDesktop = env.value(QStringLiteral("XDG_CURRENT_DESKTOP"));

if (gnomeSessionId.size() || xdgCurrentDesktop.contains("Gnome", Qt::CaseInsensitive)) {
m_type = LinuxDesktop::Type::Gnome;
}
else if (kdeFullSession.size() || desktopSession == "kde-plasma") {
else if (kdeFullSession.size() || xdgCurrentDesktop.contains("kde-plasma", Qt::CaseInsensitive)) {
m_type = LinuxDesktop::Type::KDE;
}
else if (xdgCurrentDesktop.contains(QLatin1String("sway"), Qt::CaseInsensitive)) {
m_type = LinuxDesktop::Type::Sway;
}
}

{ // check for wayland session
Expand Down Expand Up @@ -137,22 +211,33 @@ QPixmap LinuxDesktop::grabScreenWayland(QScreen* screen) const
{
#if HAS_Qt_DBus
QPixmap pm;
switch (type())
if (type() == LinuxDesktop::Type::Gnome)
{
case LinuxDesktop::Type::Gnome:
pm = grabScreenDBusGnome();
break;
case LinuxDesktop::Type::KDE:
} else if (type() == LinuxDesktop::Type::KDE)
{
pm = grabScreenDBusKde();
break;
default:
logWarning(desktop) << tr("Currently zoom on Wayland is only supported via DBus on KDE and GNOME.");
}

// grabScreenDBusGnome may fail with Gnome 41+. Use xdg-desktop-portal
// grab function for any wayland compositor. However this function is
// not used as default as it is not user friendly. Please see
// https://github.com/flatpak/xdg-desktop-portal/issues/649
// If the PixelMap remain Null after this step then the compositor
// is not supported.
if (pm.isNull())
{
pm = grabScreenDBusXDGdesktop();
}

if (pm.isNull())
{
logWarning(desktop) << tr("Currently zoom on Wayland is only supported via DBus on KDE, GNOME and Sway.");
}
return pm.isNull() ? pm : pm.copy(screen->geometry());
#else
Q_UNUSED(screen);
logWarning(desktop) << tr("Projecteur was compiled without Qt DBus. Currently zoom on Wayland is "
"only supported via DBus on KDE and GNOME.");
logWarning(desktop) << tr("Projecteur was compiled without Qt DBus.");
return QPixmap();
#endif
}
38 changes: 36 additions & 2 deletions src/linuxdesktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <QObject>
#include <QPixmap>
#include <QtDBus>

class QScreen;

Expand All @@ -12,7 +13,7 @@ class LinuxDesktop : public QObject
Q_OBJECT

public:
enum class Type : uint8_t { KDE, Gnome, Other };
enum class Type : uint8_t { KDE, Gnome, Sway, Other };

explicit LinuxDesktop(QObject* parent = nullptr);

Expand All @@ -26,4 +27,37 @@ class LinuxDesktop : public QObject
Type m_type = Type::Other;

QPixmap grabScreenWayland(QScreen* screen) const;
};
};

/*
* Proxy class for interface org.freedesktop.portal.Request
*/
class OrgFreedesktopPortalRequestInterface : public QDBusAbstractInterface
{
Q_OBJECT
public:
OrgFreedesktopPortalRequestInterface(const QString& service,
const QString& path,
const QDBusConnection& connection,
QObject* parent = nullptr);

~OrgFreedesktopPortalRequestInterface();

public Q_SLOTS:
inline QDBusPendingReply<> Close()
{
QList<QVariant> argumentList;
return asyncCallWithArgumentList(QStringLiteral("Close"), argumentList);
}

Q_SIGNALS: // SIGNALS
void Response(uint response, QVariantMap results);
};

namespace org {
namespace freedesktop {
namespace portal {
typedef ::OrgFreedesktopPortalRequestInterface Request;
}
}
}
1 change: 1 addition & 0 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ namespace {
// -------------------------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("projecteur");
QCoreApplication::setApplicationName("Projecteur");
QCoreApplication::setApplicationVersion(projecteur::version_string());
ProjecteurApplication::Options options;
Expand Down
5 changes: 3 additions & 2 deletions src/projecteurapp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ namespace {
ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Options& options)
: QApplication(argc, argv)
, m_trayIcon(new QSystemTrayIcon())
, m_trayMenu(new QMenu())
, m_trayMenu(new QMenu(qobject_cast<QWidget*>(m_trayIcon.get())))
, m_localServer(new QLocalServer(this))
, m_linuxDesktop(new LinuxDesktop(this))
, m_xcbOnWayland(QGuiApplication::platformName() == "xcb" && m_linuxDesktop->isWayland())
Expand Down Expand Up @@ -78,7 +78,8 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio
});

const QString desktopEnv = m_linuxDesktop->type() == LinuxDesktop::Type::KDE ? "KDE" :
m_linuxDesktop->type() == LinuxDesktop::Type::Gnome ? "Gnome"
m_linuxDesktop->type() == LinuxDesktop::Type::Gnome ? "Gnome":
m_linuxDesktop->type() == LinuxDesktop::Type::Sway ? "Sway"
: tr("Unknown");

logDebug(mainapp) << tr("Qt platform plugin: %1;").arg(QGuiApplication::platformName())
Expand Down