Repository: CrimsonAS/gtkplatform Branch: master Commit: 1149f4f77523 Files: 60 Total size: 311.8 KB Directory structure: gitextract_lr58kn4j/ ├── .gitignore ├── LICENSE ├── README.md ├── examples/ │ ├── examples.pro │ └── gtkextras/ │ ├── gtkextras.pro │ └── headerbar/ │ ├── headerbar.pro │ └── main.cpp ├── gtkplatform.pro ├── src/ │ ├── gtkextras/ │ │ ├── gtkextras.pro │ │ ├── qgtkextrasglobal.h │ │ ├── qgtkheaderbar.cpp │ │ ├── qgtkheaderbar.h │ │ └── qgtkrefptr.h │ ├── platform-plugin/ │ │ ├── CSystrace.cpp │ │ ├── CSystrace.h │ │ ├── CTraceMessages.h │ │ ├── gtk.json │ │ ├── main.cpp │ │ ├── platform-plugin.pro │ │ ├── qgtk3dialoghelpers.cpp │ │ ├── qgtk3dialoghelpers.h │ │ ├── qgtkbackingstore.cpp │ │ ├── qgtkbackingstore.h │ │ ├── qgtkclipboard.cpp │ │ ├── qgtkclipboard.h │ │ ├── qgtkcursor.cpp │ │ ├── qgtkcursor.h │ │ ├── qgtkeventdispatcher.cpp │ │ ├── qgtkeventdispatcher.h │ │ ├── qgtkhelpers.cpp │ │ ├── qgtkhelpers.h │ │ ├── qgtkintegration.cpp │ │ ├── qgtkintegration.h │ │ ├── qgtkmenu.cpp │ │ ├── qgtkmenu.h │ │ ├── qgtkmenubar.cpp │ │ ├── qgtkmenubar.h │ │ ├── qgtkmenuitem.cpp │ │ ├── qgtkmenuitem.h │ │ ├── qgtkopenglcontext.cpp │ │ ├── qgtkopenglcontext.h │ │ ├── qgtkopenglcontext_wayland.cpp │ │ ├── qgtkopenglcontext_x11.cpp │ │ ├── qgtkscreen.cpp │ │ ├── qgtkscreen.h │ │ ├── qgtkservices.cpp │ │ ├── qgtkservices.h │ │ ├── qgtksystemtrayicon.cpp │ │ ├── qgtksystemtrayicon.h │ │ ├── qgtktheme.cpp │ │ ├── qgtktheme.h │ │ ├── qgtkwindow.cpp │ │ ├── qgtkwindow.h │ │ ├── qgtkwindow_gesture.cpp │ │ ├── qgtkwindow_keyboard.cpp │ │ ├── qgtkwindow_mouse.cpp │ │ ├── qgtkwindow_render.cpp │ │ └── qgtkwindow_touch.cpp │ └── src.pro └── sync.profile ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .qmake.* *.o Makefile moc_*.cpp moc_*.h *.moc ================================================ FILE: LICENSE ================================================ The source code in this repository is available under the same terms as that of the open source licenses Qt itself is available under: LGPL v3 or GPL 2+. If these license terms are not suitable, please contact us: info@crimson.no. ================================================ FILE: README.md ================================================ # introduction gtkplatform is a Qt Platform Abstraction plugin providing Qt applications with the capability to use gtk+ as a host toolkit, primarily intended for use on Linux desktops. That is: it lets Qt applications render with native gtk+ menus, and use gtk+ for input (mouse, keyboard, touch), and getting window content on screen, the same as it uses e.g. cocoa on macOS for instance. Thanks to: * Robin Burchell (@rburchell, initial idea & heavy lifting) * John Brooks (@special, rendering work and OpenGL implementation) * Gunnar Sletta (@sletta, all sorts of assistance and brainstorming) * Donald Carr (@sirspudd, Arch Linux packaging) If you'd like to have a chat with us, feel free to [drop in on Telegram](https://t.me/joinchat/FlwGHw366p2Z9tBZ_f1yTA). ## what this is It's a way to get better integrated, consistent application behaviour on the Linux desktop. ## what this is **not** It's not the most performant way to run applications, and as a result, not well suited for the embedded environment. This is particularly noticable with QtQuick applications, as they make use of OpenGL. This goes through a copy step: the scene is drawn offscreen, copied, and uploaded to the gtk+ window, which is rather inefficient. Hopefully, gtk+ will grow API to allow this to be done better in the future. ## current state What works: * Showing, resizing, and hiding windows windows (all hopefully flicker-free) * Rendering in those windows * Using QPainter * Using QOpenGLContext * A mix of OpenGL and software rendering in those windows (QOpenGLWidget, etc) * QtWebEngine (if patched, tracked at [#9](https://github.com/CrimsonAS/gtkplatform/issues/9)) * Simple clipboard interaction (text/image copying) * Native gtk+ dialogs (taken from Qt) * Native gtk+ menubar * Notifications using libnotify * Input events * Touch * Keyboard * Mouse, including smooth scroll events Not everything does work, though. See the known issues section. # screenshots Here's Qt Creator running with the gtk+ platform plugin: ![Creator with the gtk+ platform plugin](https://qtl.me/E3D02BB87DD13C40C2DEF08E2440B735.png) If you'd like to see more, [go take a look at the wiki](https://github.com/CrimsonAS/gtkplatform/wiki). # building These are the versions I test with. * Qt 5.10.1 * gtk+ 3.22.30 * libnotify 0.7.7 These are all available in Fedora 28, which is where I do testing/development. Good support is also available on Arch Linux, using [the package generously maintained by @sirspudd](https://aur.archlinux.org/packages/qt-gtk-platform-plugin/). Note that on Fedora, you need qt5-qtbase-static and redhat-rpm-config installed to build from source. Other distributions may, or may not work, but I don't have any involvement with them. With dependencies installed: * `qmake` * `make` * `make install` (as root) Then try launch something after setting `QT_QPA_PLATFORM=gtk` (or `-platform gtk` as a command line option) # history Qt is pretty portable. I don't think there's any doubt to this statement; just take a look at the vast myriad of platform ports out there. It runs on macOS, Windows, even Haiku. It's everywhere. There's a bit of a fly in the ointment, though: on the Linux desktop, things aren't quite so well defined. There isn't a "sanctioned" platform toolkit. As a result, Qt has to do quite a lot of heavy lifting itself, and this doesn't always result in something that is too well integrated with the host desktop system. As an additional problem, the Linux desktop world is changing. The stability (which some may consider stagnation) of xcb has been giving way to the rise of Wayland. In many ways, this change has been beneficial: it's a lot harder to introduce some bad graphical glitches like flicker on resize. On the other hand, it introduces a host of its own brand new problems. Even discounting all of these as solvable problems on top of the usual things like reasonably performant flicker-free graphics that we ought to have and ought to be able to take for granted, there's the root issue that there is a significant amount of duplicate work going on here: any new development has to be solved in (at least) two major toolkits. So with this background, we get to the situation that lead to this project. A while ago, I moved from macOS back to Linux as my day to day desktop system, and quickly experienced frustration with a myriad of bad, very user-visible bugs on Linux like variances in how high DPI is dealt with, font sizing and selection that wasn't identical, black flicker on resizing, bad trackpad scroll behaviour, the reliance of Qt applications on xwayland rather than being first class Wayland citizens, etc. This project aims to help mitigate those issues. # Known Issues * Popup positioning (like combo box dropdowns) will often be wrong. When running on Wayland, this platform plugin will not allow absolute positioning of a window in global coordinate space. Instead, popups are positioned relative to their parent window. This usually manifests as windows appearing very far away from where they ought to have triggered because they failed to set a parent. * Notifications don't work right. Right now, we're using libnotify, because using `GtkApplication` without using `g_application_run` doesn't seem trivial. This means that we're not using the latest and greatest stuff, unfortunately. I'd like to fix this somehow. * Accessibility doesn't work * Drag and drop doesn't work It isn't written yet. There's rather a lot of features like that, actually. I'm sure it will improve with time. * My menu shortcuts have funny things in them The mapping of GTK+ keys to Qt keys is incomplete. See [#8](https://github.com/CrimsonAS/gtkplatform/issues/8). * QtWebEngine doesn't work out of the box Correct. Currently there's a hardcoded "whitelist" of platform plugins that will work. The patch to make it work is quite trivial, see `src/core/content_browser_client_qt.cpp`, and make it request "eglcontext" unconditionally (or when using the "gtk" plugin). See [#9](https://github.com/CrimsonAS/gtkplatform/issues/9). # FAQ * **Q:** But my desktop works just fine. I don't want to use this. **A:** That's fine. Keep using what you are using today, and pretend this doesn't exist. * **Q:** Why is this any better than a theme for Qt which looks like a gtk+ theme? **A:** These are separate, but related concerns. If I was just interested in getting the contents of a window to look like the contents of a Qt window, then sure, a theme/style plugin alone would be more than sufficient. More importantly than the contents of the window, though, I want consistency on the level of things underneath theming too. For instance, using this, you get transparent Wayland support that looks and works good *right now* without having to fix the numerous desktop-related pieces that are missing from QtWayland. You get consistent high DPI support. You get window resizing that doesn't flicker, unlike that of the xcb platform plugin. Longer term, I have even bigger goals than this. I want to be able to use platform-native features like GNotification, GtkHeaderBar, app menus, and more. I'd also like to look into mapping GtkGesture into something that Qt applications can make use of, so pinch/rotate/etc all work in the same way across all desktop applications. Most of this isn't realised yet, as for the time being I'm focusing on the "basics", but in the longer term I expect it will come. In the short term, this is about a more consistent, more usable out-of-the-box desktop experience. * **Q:** Are you changing Qt's API? **A:** No. Your existing Qt applications of today will work with this with the same amount of tweaking they might need to make when running on a new platform like macOS or Wayland. We may provide some helpers to allow using some gtk+ specific features in future, like GtkHeaderBar, though. * **Q:** Why are you doing this? Qt supports _my pet feature_ better. **A:** That might be true. The main problem here is inconsistencies between different applications on my desktop. I want everything to look, and act the same. And maybe once I have that, we can focus on improving everything at once rather than fighting over who has the better toolkit. * **Q:** Why isn't this part of Qt? **A:** Firstly, it's easier to develop something that is rapidly changing out-of-tree. Secondly, I want this to be usable on my desktop _now_, not in 6-12 months time. * **Q:** There's no system tray icon support **A:** [Correct](https://bugzilla.gnome.org/show_bug.cgi?id=785956). Given that GtkStatusIcon is deprecated, and the system tray is a dead concept inside GNOME, I don't think this warrants supporting. * **Q:** How does high DPI support work? **A:** We're using gtk+, so, it works the same way it does there. Window size etc is reported in units that aren't real pixels. The window content is drawn by scaling those sizes by the appropriate amount into a larger buffer, which is then drawn by gtk+. * **Q:** How do multiple monitors work? **A:** They probably don't. More seriously, I haven't tested them much yet. There's probably going to be a lot of bugs there. When all that stabilizes, though, it should work the same as it does with gtk+ applications. * **Q:** My application doesn't work! **A:** Not exactly a question, but: many applications rely on features of the underlying platform at build time. This of course won't work out of the box in many cases, sometimes due to missing mapping of a feature, and sometimes because it's outright not possible. It might be a bug in gtkplatform, or it might be that the application requires adaptation. * **Q:** Does this render using gtk+? **A:** Sort of. With the exception of menu bars on windows, the content of the window is entirely rendered by Qt right now. Once Qt is done with that, it passes over to our code, and we use gtk+ to get that content on screen as well as to provide some of the desktop's settings, like font settings. So the reason that widgets look "native" is for the most part not down to the work that we've done, but rather the work of the people who wrote the Adwaita theme plugin. This having been said, it might be an area of interest to look at the possibility of using Pango to render text in the future, rather than fontconfig/freetype. * **Q:** What technologies does this build on? **A:** The "glib" event dispatcher, originally written by [Bradley Hughes](https://blog.qt.io/blog/2006/02/24/qt-and-glib/) during his time working on Qt has proven very useful. Without that, this would have been a more difficult task. The ["adwaita" QStyle plugin](https://github.com/MartinBriza/adwaita-qt) is also great in that it helps the contents of windows blend in very well. Of course, none of this would be possible without the work of everyone in the greater Linux desktop community, too, particularly the gtk+ and Qt contributors. ================================================ FILE: examples/examples.pro ================================================ TEMPLATE = subdirs SUBDIRS += gtkextras ================================================ FILE: examples/gtkextras/gtkextras.pro ================================================ TEMPLATE = subdirs SUBDIRS += \ headerbar ================================================ FILE: examples/gtkextras/headerbar/headerbar.pro ================================================ QT += widgets gui-private gtkextras TEMPLATE = app TARGET = testheaderbar INCLUDEPATH += ../src # Input SOURCES += main.cpp CONFIG += link_pkgconfig PKGCONFIG += gtk+-3.0 CONFIG += no_keywords target.path = $$[QT_INSTALL_EXAMPLES]/gtkextras/headerbar INSTALLS += target ================================================ FILE: examples/gtkextras/headerbar/main.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include #include #include #include #include #include #include #include class TestWindow : public QWidget { public: TestWindow() { QGtkHeaderBar *qhb = new QGtkHeaderBar(this); GtkWidget *hb = qhb->headerBarWidget(); gtk_header_bar_set_title(GTK_HEADER_BAR(hb), "A magical Qt test"); gtk_header_bar_set_subtitle(GTK_HEADER_BAR(hb), "Featuring a real GtkHeaderBar"); gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(hb), TRUE); openMenuButton = gtk_button_new_from_icon_name("open-menu-symbolic", GTK_ICON_SIZE_BUTTON); gtk_header_bar_pack_end(GTK_HEADER_BAR(hb), openMenuButton.get()); } private: QGtkRefPtr openMenuButton; }; int main(int argc, char **argv) { QApplication app(argc, argv); TestWindow w; QLabel l(&w); l.setText("I'm a QLabel"); w.resize(200, 200); w.show(); return app.exec(); } ================================================ FILE: gtkplatform.pro ================================================ load(qt_parts) ================================================ FILE: src/gtkextras/gtkextras.pro ================================================ TARGET = QtGtkExtras QT -= gui QT += widgets gui-private HEADERS += \ qgtkrefptr.h \ qgtkheaderbar.h SOURCES += \ qgtkheaderbar.cpp CONFIG += link_pkgconfig PKGCONFIG_PRIVATE += gdk-3.0 gtk+-3.0 CONFIG += no_keywords load(qt_module) CONFIG -= create_cmake # should be fixed, but ... ================================================ FILE: src/gtkextras/qgtkextrasglobal.h ================================================ /**************************************************************************** ** ** Copyright (C) 2018 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QTGTKEXTRASGLOBAL_H #define QTGTKEXTRASGLOBAL_H #include QT_BEGIN_NAMESPACE #ifndef QT_STATIC # if defined(QT_BUILD_GTKEXTRAS_LIB) # define Q_GTKEXTRAS_EXPORT Q_DECL_EXPORT # else # define Q_GTKEXTRAS_EXPORT Q_DECL_IMPORT # endif #else # define Q_GTKEXTRAS_EXPORT #endif QT_END_NAMESPACE #endif // QTGTKEXTRASGLOBAL_H ================================================ FILE: src/gtkextras/qgtkheaderbar.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2018 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include #include #include #include #include #include "qgtkheaderbar.h" #include "qgtkrefptr.h" class QGtkHeaderBar::QGtkHeaderBarPrivate { public: QGtkRefPtr headerBar; }; QGtkHeaderBar::QGtkHeaderBar(QObject *parent) : d(new QGtkHeaderBarPrivate) { QWindow *win = 0; win = qobject_cast(parent); if (!win) { QWidget *w = qobject_cast(parent); if (!w->isVisible()) { w->show(); // ensure pwin creation w->hide(); } qWarning() << "widget w" << w; if (w) { // ### this stuff all feels super sketchy //w = w->window(); //if (!w) { // qFatal("Created a QGtkHeaderBar on a widget with no parent window..."); //} win = w->windowHandle(); qWarning() << "widget win" << win; if (!win) { qWarning() << "widget npw" << w->nativeParentWidget(); qWarning() << "widget npw win" << w->nativeParentWidget()->windowHandle(); win = w->nativeParentWidget()->windowHandle(); if (!win) { qFatal("QGtkHeaderBar couldn't get window handle of widget..."); } } } } // ensure that QPA resources are created win->create(); d->headerBar = gtk_header_bar_new(); QPlatformNativeInterface *platformNativeInterface = QGuiApplication::platformNativeInterface(); GtkWidget *w = static_cast(platformNativeInterface->nativeResourceForWindow("gtkwindow", win)); if (!w) { qFatal("no GtkWidget! (not using the right QPA?)"); } gtk_window_set_titlebar(GTK_WINDOW(w), d->headerBar.get()); } QGtkHeaderBar::~QGtkHeaderBar() { delete d; } GtkWidget *QGtkHeaderBar::headerBarWidget() const { return d->headerBar.get(); } ================================================ FILE: src/gtkextras/qgtkheaderbar.h ================================================ /**************************************************************************** ** ** Copyright (C) 2018 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKHEADERBAR_H #define QGTKHEADERBAR_H #include #include #include #include "qgtkextrasglobal.h" QT_BEGIN_NAMESPACE class Q_GTKEXTRAS_EXPORT QGtkHeaderBar : public QObject { public: QGtkHeaderBar(QObject *parent); ~QGtkHeaderBar(); GtkWidget *headerBarWidget() const; private: class QGtkHeaderBarPrivate; QGtkHeaderBarPrivate *d; }; QT_END_NAMESPACE #endif // QGTKHEADERBAR_H ================================================ FILE: src/gtkextras/qgtkrefptr.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKREFPTR_H #define QGTKREFPTR_H #include #include QT_BEGIN_NAMESPACE template class QGtkRefPtr { public: QGtkRefPtr() : m_obj(0) { } QGtkRefPtr(gpointer obj) : m_obj(obj) { if (m_obj) g_object_ref_sink(m_obj); } QGtkRefPtr(const QGtkRefPtr& other) : m_obj(other.m_obj) { if (m_obj) g_object_ref(m_obj); } ~QGtkRefPtr() { if (m_obj) g_object_unref(m_obj); } QGtkRefPtr& operator=(const QGtkRefPtr &other) { reset(other.get()); return *this; } bool operator==(const QGtkRefPtr &other) { return this->m_obj == other.m_obj; } bool operator!=(const QGtkRefPtr &other) { return !(*this == other); } bool operator==(gpointer other) { return this->m_obj == other; } bool operator!=(gpointer other) { return !(*this == other); } // ### consider moves T* get() const { return static_cast(m_obj); } operator bool() const { return m_obj != nullptr; } void reset(T* newObj) { if (m_obj) g_object_unref(m_obj); m_obj = newObj; if (m_obj) g_object_ref_sink(m_obj); } private: gpointer m_obj; }; QT_END_NAMESPACE #endif // QGTKREFPTR_H ================================================ FILE: src/platform-plugin/CSystrace.cpp ================================================ /* * Copyright (c) 2017 Crimson AS * Author: Robin Burchell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // MAC #include // syscall() #include // SYS_thread_selfid // ENDMAC #include #include "CSystrace.h" #include "CTraceMessages.h" #include // Information about SHM chunks const int ShmChunkSize = 1024 * 10; // Data about the process of tracing itself. // This is held thread-local. struct CTracerThreadData { // The FD for the open SHM chunk int m_shm_fd = -1; // The pointer to the start of the SHM chunk char *m_shmInitialPtr = 0; // The pointer to the current location in the SHM chunk (so written len // would be m_shmPtr - m_shmInitialPtr). char *m_shmPtr = 0; // The name of the current SHM chunk char *m_currentChunkName = 0; // How much of the SHM chunk for this thread is left, in bytes? int m_remainingChunkSize = 0; // A map of string -> ID for this thread. Each thread registers strings // independently (as it has its own chunks, its own code, and we don't want // to lock as much as possible). std::unordered_map m_registeredStrings; }; static thread_local CTracerThreadData tracerThreadData; // Global data. There are no locks in place, so don't be dumb when using this. struct CTracerGlobalData { // FD to communicate with traced int m_traced_fd = -1; // Each thread registers unique strings as it comes across them here and sends a // registration message to traced. std::atomic m_currentStringId; // When the trace started (when systrace_init was called). // Do not modify this outside of systrace_init! It is read from multiple // threads. struct timespec m_originalTp; }; static CTracerGlobalData tracerGlobalData; //gettid(); except that mac sucks static int gettid() { #ifdef __APPLE__ return syscall(SYS_thread_selfid); #else return syscall(SYS_gettid); #endif } /*! Update the book keeping for the current position in the chunk. */ static void advance_chunk(int len) { tracerThreadData.m_shmPtr += len; tracerThreadData.m_remainingChunkSize -= len; assert(tracerThreadData.m_remainingChunkSize >= 0); } /*! * Send the current chunk to traced for processing. * * ### right now, this will not be called if a thread terminates abruptly. * we should somehow monitor old, stale chunks and force-submit them. */ static void submit_chunk() { if (tracerThreadData.m_shm_fd == -1) return; munmap(tracerThreadData.m_shmInitialPtr, ShmChunkSize); close(tracerThreadData.m_shm_fd); tracerThreadData.m_shm_fd = -1; tracerThreadData.m_shmPtr = 0; char buf[1024]; int blen = sprintf(buf, "%s\n", tracerThreadData.m_currentChunkName); if (0) // left for debug purposes printf("TID %d sending %s", gettid(), buf); int ret = write(tracerGlobalData.m_traced_fd, buf, blen); if (ret == -1) { // ### we also need to ignore SIGPIPE or clients will die if traced does. perror("Can't write to traced! Giving up!"); shm_unlink(tracerThreadData.m_currentChunkName); close(tracerGlobalData.m_traced_fd); tracerGlobalData.m_traced_fd = -1; } } static uint64_t getMicroseconds() { struct timespec tp; if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1) { perror("Can't get time"); abort(); } return (tp.tv_sec - tracerGlobalData.m_originalTp.tv_sec) * 1000000 + (tp.tv_nsec / 1000) - (tracerGlobalData.m_originalTp.tv_nsec / 1000); } static void systrace_debug() { #if 0 static thread_local bool debugging = false; if (debugging) return; debugging = true; // These vars are to try avoid spurious reporting. static thread_local int lastm_remainingChunkSize = 0; if (m_remainingChunkSize != lastm_remainingChunkSize) { lastm_remainingChunkSize = m_remainingChunkSize; systrace_record_counter("systrace", "m_remainingChunkSize", m_remainingChunkSize, gettid()); } static uint64_t lastStringCount = 0; if (lastStringCount != tracerGlobalData.m_currentStringId.load()) { lastStringCount = tracerGlobalData.m_currentStringId.load(); systrace_record_counter("systrace", "registeredStringCount", lastStringCount); // not thread-specific } debugging = false; #endif } /*! * Make sure we have a valid SHM chunk to write events to, or abort if not. */ static void ensure_chunk(int mlen) { if (tracerThreadData.m_shm_fd != -1 && tracerThreadData.m_remainingChunkSize >= mlen) return; if (tracerThreadData.m_shm_fd != -1) { submit_chunk(); } // ### linux via /dev/shm or memfd_create? int nextShmId = 0; while (tracerThreadData.m_shm_fd == -1 && nextShmId < TRACED_MAX_SHM_CHUNKS) { if (!tracerThreadData.m_currentChunkName) asprintf(&tracerThreadData.m_currentChunkName, "tracechunk-%d", nextShmId++); tracerThreadData.m_shm_fd = shm_open(tracerThreadData.m_currentChunkName, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); if (tracerThreadData.m_shm_fd != -1) { break; // we won! } else { // try again. either something is using that chunk name, or traced // hasn't released it for us to reuse yet. free(tracerThreadData.m_currentChunkName); tracerThreadData.m_currentChunkName = 0; } } if (tracerThreadData.m_shm_fd == -1) { fprintf(stderr, "Something is seriously screwed. Can't find any free SHM chunk, tried all %d\n", TRACED_MAX_SHM_CHUNKS); abort(); } if (ftruncate(tracerThreadData.m_shm_fd, ShmChunkSize) == -1) { perror("Can't ftruncate SHM!"); abort(); } tracerThreadData.m_shmPtr = (char*)mmap(0, ShmChunkSize, PROT_READ | PROT_WRITE, MAP_SHARED, tracerThreadData.m_shm_fd, 0); if (tracerThreadData.m_shmPtr == MAP_FAILED) { perror("Can't map SHM!"); abort(); } tracerThreadData.m_remainingChunkSize = ShmChunkSize; ChunkHeader *h = (ChunkHeader*)tracerThreadData.m_shmPtr; h->magic = TRACED_PROTOCOL_MAGIC; h->version = TRACED_PROTOCOL_VERSION; h->pid = getpid(); h->tid = gettid(); h->epoch = (tracerGlobalData.m_originalTp.tv_sec * 1000000) + (tracerGlobalData.m_originalTp.tv_nsec / 1000); advance_chunk(sizeof(ChunkHeader)); } __attribute__((constructor)) void systrace_init() { if (clock_gettime(CLOCK_MONOTONIC, &tracerGlobalData.m_originalTp) == -1) { perror("Can't get time"); abort(); } if (getenv("TRACED") == NULL) { tracerGlobalData.m_traced_fd = open("/tmp/traced", O_WRONLY); if ((tracerGlobalData.m_traced_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("Can't create socket for traced!"); } struct sockaddr_un remote; remote.sun_family = AF_UNIX; strcpy(remote.sun_path, "/tmp/traced"); int len = strlen(remote.sun_path) + sizeof(remote.sun_family) + 1; if (connect(tracerGlobalData.m_traced_fd, (struct sockaddr *)&remote, len) == -1) { perror("Can't connect to traced!"); } } else { fprintf(stderr, "Running trace daemon. Not tracing.\n"); } } __attribute__((destructor)) void systrace_deinit() { if (tracerGlobalData.m_traced_fd == -1) return; submit_chunk(); close(tracerGlobalData.m_traced_fd); tracerGlobalData.m_traced_fd = -1; } int systrace_should_trace(const char *module) { if (tracerGlobalData.m_traced_fd == -1) return 0; // hack this if you want to temporarily omit some traces. return 1; } static uint64_t getStringId(const char *string) { auto it = tracerThreadData.m_registeredStrings.find(string); if (it == tracerThreadData.m_registeredStrings.end()) { uint64_t nid = tracerGlobalData.m_currentStringId.fetch_add(1); tracerThreadData.m_registeredStrings[string] = nid; int slen = strlen(string); assert(slen < ShmChunkSize / 100); // 102 characters, assuming 10kb ensure_chunk(sizeof(RegisterStringMessage) + slen); RegisterStringMessage *m = (RegisterStringMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::RegisterStringMessage; m->id = nid; m->length = slen; strncpy(&m->stringData, string, slen); advance_chunk(sizeof(RegisterStringMessage) + slen); systrace_debug(); return nid; } return it->second; } void systrace_duration_begin(const char *module, const char *tracepoint) { if (!systrace_should_trace(module)) return; uint64_t modid = getStringId(module); uint64_t tpid = getStringId(tracepoint); ensure_chunk(sizeof(BeginMessage)); BeginMessage *m = (BeginMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::BeginMessage; m->microseconds = getMicroseconds(); m->categoryId = modid; m->tracepointId = tpid; advance_chunk(sizeof(BeginMessage)); systrace_debug(); } void systrace_duration_end(const char *module, const char *tracepoint) { if (!systrace_should_trace(module)) return; uint64_t modid = getStringId(module); uint64_t tpid = getStringId(tracepoint); ensure_chunk(sizeof(EndMessage)); EndMessage *m = (EndMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::EndMessage; m->microseconds = getMicroseconds(); m->categoryId = modid; m->tracepointId = tpid; advance_chunk(sizeof(EndMessage)); systrace_debug(); } void systrace_duration_begin(CSystraceEvent &event) { if (!systrace_should_trace(event.m_module)) return; event.m_begin = getMicroseconds(); // Do nothing We will write the event on end. } void systrace_duration_end(CSystraceEvent &event) { if (!systrace_should_trace(event.m_module)) return; uint64_t modid = getStringId(event.m_module); uint64_t tpid = getStringId(event.m_tracepoint); ensure_chunk(sizeof(DurationMessage)); DurationMessage *m = (DurationMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::DurationMessage; m->microseconds = event.m_begin; m->duration = getMicroseconds() - event.m_begin; m->categoryId = modid; m->tracepointId = tpid; advance_chunk(sizeof(DurationMessage)); systrace_debug(); } void systrace_record_counter(const char *module, const char *tracepoint, int value, int id) { if (!systrace_should_trace(module)) return; uint64_t modid = getStringId(module); uint64_t tpid = getStringId(tracepoint); if (id == -1) { ensure_chunk(sizeof(CounterMessage)); CounterMessage *m = (CounterMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::CounterMessage; m->microseconds = getMicroseconds(); m->categoryId = modid; m->tracepointId = tpid; m->value = value; advance_chunk(sizeof(CounterMessage)); } else { ensure_chunk(sizeof(CounterMessageWithId)); CounterMessageWithId *m = (CounterMessageWithId*)tracerThreadData.m_shmPtr; m->messageType = MessageType::CounterMessageWithId; m->microseconds = getMicroseconds(); m->categoryId = modid; m->tracepointId = tpid; m->value = value; m->id = id; advance_chunk(sizeof(CounterMessageWithId)); } systrace_debug(); } void systrace_async_begin(const char *module, const char *tracepoint, const void *cookie) { if (!systrace_should_trace(module)) return; uint64_t modid = getStringId(module); uint64_t tpid = getStringId(tracepoint); ensure_chunk(sizeof(AsyncBeginMessage)); AsyncBeginMessage *m = (AsyncBeginMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::AsyncBeginMessage; m->microseconds = getMicroseconds(); m->categoryId = modid; m->tracepointId = tpid; m->cookie = (intptr_t)cookie; advance_chunk(sizeof(AsyncBeginMessage)); systrace_debug(); } void systrace_async_end(const char *module, const char *tracepoint, const void *cookie) { if (!systrace_should_trace(module)) return; uint64_t modid = getStringId(module); uint64_t tpid = getStringId(tracepoint); ensure_chunk(sizeof(AsyncEndMessage)); AsyncEndMessage *m = (AsyncEndMessage*)tracerThreadData.m_shmPtr; m->messageType = MessageType::AsyncEndMessage; m->microseconds = getMicroseconds(); m->categoryId = modid; m->tracepointId = tpid; m->cookie = (intptr_t)cookie; advance_chunk(sizeof(AsyncEndMessage)); systrace_debug(); } ================================================ FILE: src/platform-plugin/CSystrace.h ================================================ /* * Copyright (c) 2017 Crimson AS * Author: Robin Burchell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef SYSTRACE_H #define SYSTRACE_H #if defined(DISABLE_TRACE_CODE) struct CSystraceEvent; inline void systrace_init() {} inline void systrace_deinit() {} inline int systrace_should_trace(const char *) { return 0; } inline void systrace_duration_begin(const char *, const char *) {} inline void systrace_duration_end(const char *, const char *) {} inline void systrace_duration_begin(CSystraceEvent &) {} inline void systrace_duration_end(CSystraceEvent &) {} inline void systrace_record_counter(const char *, const char *, int , int = -1) {} inline void systrace_async_begin(const char *, const char *, const void *) {} inline void systrace_async_end(const char *, const char *, const void *) {} struct CSystraceEvent { public: CSystraceEvent(const char *, const char *) { } ~CSystraceEvent() { } }; struct CSystraceAsyncEvent { public: CSystraceAsyncEvent(const char *, const char *, const void *) { } ~CSystraceAsyncEvent() { } }; #else #include #include #include #include #include #include #if defined(_WIN32) || defined(__CYGWIN__) # if defined(BUILDING_DLL) # if defined(__GNUC__) # define SYSTRACE_EXPORT __attribute__ ((dllexport)) # else # define SYSTRACE_EXPORT __declspec(dllexport) # endif # else # if defined(__GNUC__) # define SYSTRACE_EXPORT __attribute__ ((dllimport)) # else # define SYSTRACE_EXPORT __declspec(dllimport) # endif # endif #else # define SYSTRACE_EXPORT __attribute__ ((visibility ("default"))) #endif /*! * Perform necessary set up. Should be called before any other functions. * * \note If your compiler supports the `constructor` attribute (gcc does), * then this method will be called for you. Calling it multiple times will not * cause trouble, however, so feel free to call it yourself. * * \sa systrace_deinit() */ SYSTRACE_EXPORT void systrace_init(); /*! * Perform necessary tear down. Should be called before termination, and no systrace * methods should be called after it. * * \note If your compiler supports the `destructor` attribute (gcc does), * then this method will be called for you. Calling it multiple times will not * cause trouble, however, so feel free to call it yourself. * * \sa systrace_init() */ SYSTRACE_EXPORT void systrace_deinit(); /*! * Determine whether or not a given \a module should be traced. * This can be used to avoid expensive setup (such as allocation of data for the * trace event). * * Returns 1 if the event should be traced, 0 otherwise. */ SYSTRACE_EXPORT int systrace_should_trace(const char *module); struct CSystraceEvent; SYSTRACE_EXPORT void systrace_duration_begin(const char *module, const char *tracepoint); SYSTRACE_EXPORT void systrace_duration_end(const char *module, const char *tracepoint); SYSTRACE_EXPORT void systrace_duration_begin(CSystraceEvent &event); SYSTRACE_EXPORT void systrace_duration_end(CSystraceEvent &event); SYSTRACE_EXPORT void systrace_record_counter(const char *module, const char *tracepoint, int value, int id = -1); SYSTRACE_EXPORT void systrace_async_begin(const char *module, const char *tracepoint, const void *cookie); SYSTRACE_EXPORT void systrace_async_end(const char *module, const char *tracepoint, const void *cookie); struct SYSTRACE_EXPORT CSystraceEvent { public: CSystraceEvent(const char *module, const char *tracepoint) : m_module(module) , m_tracepoint(tracepoint) { systrace_duration_begin(*this); } ~CSystraceEvent() { systrace_duration_end(*this); } const char *m_module; const char *m_tracepoint; uint64_t m_begin; }; struct SYSTRACE_EXPORT CSystraceAsyncEvent { public: CSystraceAsyncEvent(const char *module, const char *tracepoint, const void *cookie) : m_module(module) , m_tracepoint(tracepoint) , m_cookie(cookie) { systrace_async_begin(m_module, m_tracepoint, m_cookie); } ~CSystraceAsyncEvent() { systrace_async_end(m_module, m_tracepoint, m_cookie); } private: const char *m_module; const char *m_tracepoint; const void *m_cookie; }; #endif // DISABLE_TRACE_CODE #define COMBINE1(X,Y) X##Y // helper macros #define COMBINE(X,Y) COMBINE1(X,Y) // ### TRACE_STR_COPY // ### TRACE_EVENT_COPY_XXX // Records a pair of begin and end events called "name" for the current // scope, with 0, 1 or 2 associated arguments. If the category is not // enabled, then this does nothing. // - category and name strings must have application lifetime (statics or // literals). They may not include " chars. #define TRACE_EVENT0(module, tracepoint) \ CSystraceEvent COMBINE(ev, __LINE__) (module, tracepoint); // ### EVENT1, EVENT2 // ### TRACE_EVENT_INSTANT0? #define TRACE_EVENT_BEGIN0(module, tracepoint) \ systrace_duration_begin(module, tracepoint); #define TRACE_EVENT_END0(module, tracepoint) \ systrace_duration_end(module, tracepoint); // ### BEGIN & END 1, 2 // ###: // Pointers can be used for the ID parameter, and they will be mangled // internally so that the same pointer on two different processes will not // match. #define TRACE_EVENT_ASYNC_BEGIN0(module, tracepoint, cookie) \ systrace_async_begin(module, tracepoint, cookie); #define TRACE_EVENT_ASYNC_END0(module, tracepoint, cookie) \ systrace_async_end(module, tracepoint, cookie); // ### 1 & 2 #define TRACE_COUNTER1(module, tracepoint, value) \ systrace_record_counter(module, tracepoint, value); // ### TRACE_COUNTER2 #define TRACE_COUNTER_ID1(module, tracepoint, value, id) \ systrace_record_counter(module, tracepoint, value, id); #endif // SYSTRACE_H ================================================ FILE: src/platform-plugin/CTraceMessages.h ================================================ /* * Copyright (c) 2017 Crimson AS * Author: Robin Burchell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef CTRACEMESSAGES_H #define CTRACEMESSAGES_H // Used by traced to clean up at startup time. Used client side to figure out // what to allocate. #define TRACED_MAX_SHM_CHUNKS 99999 // Used to mark a SHM chunk for some measure of safety. #define TRACED_PROTOCOL_MAGIC 0xDEADBEEFBAAD // Used to mark a SHM chunk as being written/read by a given version, for // safety's sake. Bump this if the protocol changes. #define TRACED_PROTOCOL_VERSION 256 enum class MessageType : uint8_t { NoMessage = 0, RegisterStringMessage = 1, BeginMessage = 2, EndMessage = 3, DurationMessage = 4, AsyncBeginMessage = 5, AsyncEndMessage = 6, CounterMessage = 7, CounterMessageWithId = 8 }; // ### consider splitting ChunkHeader to a ProcessHeaderMessage and // ChunkHeaderMessage. pid & epoch should never change, so that's 16 bytes of // each chunk wasted at present. struct ChunkHeader { uint64_t magic; uint16_t version; uint64_t pid; uint64_t tid; // when the process under trace started. traced uses this against its own start // time to calculate relative times. uint64_t epoch; }; struct BaseMessage { MessageType messageType; }; struct RegisterStringMessage : public BaseMessage { uint64_t id; uint8_t length; char stringData; // and it follows on for length bytes }; struct RegularMessage : public BaseMessage { uint64_t microseconds; uint16_t categoryId; uint64_t tracepointId; }; struct BeginMessage : public RegularMessage { }; struct EndMessage : public RegularMessage { }; struct DurationMessage : public RegularMessage { uint64_t duration; }; struct AsyncBeginMessage : public RegularMessage { uint64_t cookie; }; struct AsyncEndMessage : public RegularMessage { uint64_t cookie; }; struct CounterMessage : public RegularMessage { uint64_t value; }; struct CounterMessageWithId : public CounterMessage { uint64_t id; }; #endif // CTRACEMESSAGES_H ================================================ FILE: src/platform-plugin/gtk.json ================================================ { "Keys": [ "gtk" ] } ================================================ FILE: src/platform-plugin/main.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include #include "qgtkintegration.h" QT_BEGIN_NAMESPACE class QGtkIntegrationPlugin : public QPlatformIntegrationPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "gtk.json") public: QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; }; QPlatformIntegration *QGtkIntegrationPlugin::create(const QString& system, const QStringList& paramList) { if (!system.compare(QLatin1String("gtk"), Qt::CaseInsensitive)) return new QGtkIntegration(paramList); return 0; } QT_END_NAMESPACE #include "main.moc" ================================================ FILE: src/platform-plugin/platform-plugin.pro ================================================ TARGET = qgtk !gtkplatform-release { CONFIG -= release CONFIG += debug } QT += core-private gui-private widgets equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 8): { QT += platformsupport-private } else { QT += fontdatabase_support_private glx_support_private egl_support_private service_support_private theme_support_private } equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 7): { # qhighdpi has some bugs with this in at least 5.7. DEFINES += QT_NO_FOREACH } SOURCES = main.cpp \ qgtkintegration.cpp \ qgtkbackingstore.cpp \ qgtkscreen.cpp \ qgtkwindow.cpp \ qgtkwindow_keyboard.cpp \ qgtkwindow_mouse.cpp \ qgtkwindow_touch.cpp \ qgtkwindow_render.cpp \ qgtkwindow_gesture.cpp \ qgtktheme.cpp \ qgtksystemtrayicon.cpp \ qgtkmenubar.cpp \ qgtkmenu.cpp \ qgtkmenuitem.cpp \ qgtkhelpers.cpp \ qgtk3dialoghelpers.cpp \ qgtkopenglcontext.cpp \ qgtkopenglcontext_wayland.cpp \ qgtkopenglcontext_x11.cpp \ qgtkcursor.cpp \ qgtkeventdispatcher.cpp \ qgtkclipboard.cpp \ qgtkservices.cpp HEADERS = qgtkintegration.h \ qgtkbackingstore.h \ qgtkscreen.h \ qgtkwindow.h \ qgtktheme.h \ qgtksystemtrayicon.h \ qgtkmenubar.h \ qgtkmenu.h \ qgtkmenuitem.h \ qgtkhelpers.h \ qgtk3dialoghelpers.h \ qgtkopenglcontext.h \ qgtkcursor.h \ qgtkeventdispatcher.h \ qgtkclipboard.h \ qgtkservices.h # CSystrace DEFINES += DISABLE_TRACE_CODE #LIBS += -lrt #SOURCES += CSystrace.cpp HEADERS += CSystrace.h \ CTraceMessages.h QT += gtkextras #INCLUDEPATH += ../gtkextras CONFIG += qpa/genericunixfontdatabase LIBS += -lX11-xcb -lxcb CONFIG += link_pkgconfig PKGCONFIG_PRIVATE += gdk-3.0 gtk+-3.0 libnotify # for GL PKGCONFIG_PRIVATE += egl CONFIG += no_keywords PLUGIN_TYPE = platforms PLUGIN_CLASS_NAME = QGtkIntegrationPlugin !equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = - load(qt_plugin) ================================================ FILE: src/platform-plugin/qgtk3dialoghelpers.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qgtk3dialoghelpers.h" #include "qgtktheme.h" #include "qgtkwindow.h" #include #include #include #include #include #include #include #undef signals #include #include #include #include QT_BEGIN_NAMESPACE static const char *standardButtonText(int button) { return QGtkTheme::defaultStandardButtonText(button).toUtf8(); } QGtk3Dialog::QGtk3Dialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) { g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this); g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); } QGtk3Dialog::~QGtk3Dialog() { gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); gtk_widget_destroy(gtkWidget); } GtkDialog *QGtk3Dialog::gtkDialog() const { return GTK_DIALOG(gtkWidget); } void QGtk3Dialog::exec() { if (modality() == Qt::ApplicationModal) { // transient to the active window (best we can do really). QWindow *focusWin = QGuiApplication::focusWindow(); if (focusWin) { QGtkWindow *parentWin = static_cast(focusWin->handle()); gtk_window_set_transient_for(GTK_WINDOW(gtkWidget), GTK_WINDOW(parentWin->gtkWindow().get())); } // block input to the whole app, including other GTK dialogs // gtk_dialog_run(gtkDialog()); } else { // block input to the window, allow input to other GTK dialogs QEventLoop loop; connect(this, SIGNAL(accept()), &loop, SLOT(quit())); connect(this, SIGNAL(reject()), &loop, SLOT(quit())); loop.exec(); } } bool QGtk3Dialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { if (parent) { connect(parent, &QWindow::destroyed, this, &QGtk3Dialog::onParentWindowDestroyed, Qt::UniqueConnection); } setParent(parent); setFlags(flags); setModality(modality); gtk_widget_realize(gtkWidget); // creates X window GdkWindow *gdkWindow = gtk_widget_get_window(gtkWidget); if (parent) { QGtkWindow *parentWin = static_cast(parent->handle()); gtk_window_set_transient_for(GTK_WINDOW(gtkWidget), GTK_WINDOW(parentWin->gtkWindow().get())); } if (modality != Qt::NonModal) { gdk_window_set_modal_hint(gdkWindow, true); QGuiApplicationPrivate::showModalWindow(this); } gtk_widget_show(gtkWidget); gdk_window_focus(gdkWindow, GDK_CURRENT_TIME); return true; } void QGtk3Dialog::hide() { QGuiApplicationPrivate::hideModalWindow(this); gtk_widget_hide(gtkWidget); } void QGtk3Dialog::onResponse(QGtk3Dialog *dialog, int response) { if (response == GTK_RESPONSE_OK) Q_EMIT dialog->accept(); else Q_EMIT dialog->reject(); } void QGtk3Dialog::onParentWindowDestroyed() { // The QGtk3*DialogHelper classes own this object. Make sure the parent doesn't delete it. setParent(0); } QGtk3ColorDialogHelper::QGtk3ColorDialogHelper() { d.reset(new QGtk3Dialog(gtk_color_chooser_dialog_new("", 0))); connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted())); connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject())); g_signal_connect_swapped(d->gtkDialog(), "notify::rgba", G_CALLBACK(onColorChanged), this); } QGtk3ColorDialogHelper::~QGtk3ColorDialogHelper() { } bool QGtk3ColorDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { applyOptions(); return d->show(flags, modality, parent); } void QGtk3ColorDialogHelper::exec() { d->exec(); } void QGtk3ColorDialogHelper::hide() { d->hide(); } void QGtk3ColorDialogHelper::setCurrentColor(const QColor &color) { GtkDialog *gtkDialog = d->gtkDialog(); if (color.alpha() < 255) gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(gtkDialog), true); GdkRGBA gdkColor; gdkColor.red = color.redF(); gdkColor.green = color.greenF(); gdkColor.blue = color.blueF(); gdkColor.alpha = color.alphaF(); gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(gtkDialog), &gdkColor); } QColor QGtk3ColorDialogHelper::currentColor() const { GtkDialog *gtkDialog = d->gtkDialog(); GdkRGBA gdkColor; gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(gtkDialog), &gdkColor); return QColor::fromRgbF(gdkColor.red, gdkColor.green, gdkColor.blue, gdkColor.alpha); } void QGtk3ColorDialogHelper::onAccepted() { Q_EMIT accept(); } void QGtk3ColorDialogHelper::onColorChanged(QGtk3ColorDialogHelper *dialog) { Q_EMIT dialog->currentColorChanged(dialog->currentColor()); } void QGtk3ColorDialogHelper::applyOptions() { GtkDialog *gtkDialog = d->gtkDialog(); gtk_window_set_title(GTK_WINDOW(gtkDialog), options()->windowTitle().toUtf8()); gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(gtkDialog), options()->testOption(QColorDialogOptions::ShowAlphaChannel)); } QGtk3FileDialogHelper::QGtk3FileDialogHelper() { d.reset(new QGtk3Dialog(gtk_file_chooser_dialog_new("", 0, GTK_FILE_CHOOSER_ACTION_OPEN, standardButtonText(QPlatformDialogHelper::Cancel), GTK_RESPONSE_CANCEL, standardButtonText(QPlatformDialogHelper::Ok), GTK_RESPONSE_OK, NULL))); connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted())); connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject())); g_signal_connect(GTK_FILE_CHOOSER(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this); g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this); g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "notify::filter", G_CALLBACK(onFilterChanged), this); } QGtk3FileDialogHelper::~QGtk3FileDialogHelper() { } bool QGtk3FileDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { _dir.clear(); _selection.clear(); applyOptions(); return d->show(flags, modality, parent); } void QGtk3FileDialogHelper::exec() { d->exec(); } void QGtk3FileDialogHelper::hide() { // After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder() // & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual // values before hiding the dialog _dir = directory(); _selection = selectedFiles(); d->hide(); } bool QGtk3FileDialogHelper::defaultNameFilterDisables() const { return false; } void QGtk3FileDialogHelper::setDirectory(const QUrl &directory) { GtkDialog *gtkDialog = d->gtkDialog(); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), directory.toLocalFile().toUtf8()); } QUrl QGtk3FileDialogHelper::directory() const { // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder() // returns a bogus value -> return the cached value before hiding if (!_dir.isEmpty()) return _dir; QString ret; GtkDialog *gtkDialog = d->gtkDialog(); gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(gtkDialog)); if (folder) { ret = QString::fromUtf8(folder); g_free(folder); } return QUrl::fromLocalFile(ret); } void QGtk3FileDialogHelper::selectFile(const QUrl &filename) { GtkDialog *gtkDialog = d->gtkDialog(); if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { QFileInfo fi(filename.toLocalFile()); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), fi.path().toUtf8()); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(gtkDialog), fi.fileName().toUtf8()); } else { gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(gtkDialog), filename.toLocalFile().toUtf8()); } } QList QGtk3FileDialogHelper::selectedFiles() const { // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames() // returns a bogus value -> return the cached value before hiding if (!_selection.isEmpty()) return _selection; QList selection; GtkDialog *gtkDialog = d->gtkDialog(); GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(gtkDialog)); for (GSList *it = filenames; it; it = it->next) selection += QUrl::fromLocalFile(QString::fromUtf8((const char*)it->data)); g_slist_free(filenames); return selection; } void QGtk3FileDialogHelper::setFilter() { applyOptions(); } void QGtk3FileDialogHelper::selectNameFilter(const QString &filter) { GtkFileFilter *gtkFilter = _filters.value(filter); if (gtkFilter) { GtkDialog *gtkDialog = d->gtkDialog(); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(gtkDialog), gtkFilter); } } QString QGtk3FileDialogHelper::selectedNameFilter() const { GtkDialog *gtkDialog = d->gtkDialog(); GtkFileFilter *gtkFilter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(gtkDialog)); return _filterNames.value(gtkFilter); } void QGtk3FileDialogHelper::onAccepted() { Q_EMIT accept(); } void QGtk3FileDialogHelper::onSelectionChanged(GtkDialog *gtkDialog, QGtk3FileDialogHelper *helper) { QString selection; gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog)); if (filename) { selection = QString::fromUtf8(filename); g_free(filename); } Q_EMIT helper->currentChanged(QUrl::fromLocalFile(selection)); } void QGtk3FileDialogHelper::onCurrentFolderChanged(QGtk3FileDialogHelper *dialog) { Q_EMIT dialog->directoryEntered(dialog->directory()); } void QGtk3FileDialogHelper::onFilterChanged(QGtk3FileDialogHelper *dialog) { Q_EMIT dialog->filterSelected(dialog->selectedNameFilter()); } static GtkFileChooserAction gtkFileChooserAction(const QSharedPointer &options) { switch (options->fileMode()) { case QFileDialogOptions::AnyFile: case QFileDialogOptions::ExistingFile: case QFileDialogOptions::ExistingFiles: if (options->acceptMode() == QFileDialogOptions::AcceptOpen) return GTK_FILE_CHOOSER_ACTION_OPEN; else return GTK_FILE_CHOOSER_ACTION_SAVE; case QFileDialogOptions::Directory: case QFileDialogOptions::DirectoryOnly: default: if (options->acceptMode() == QFileDialogOptions::AcceptOpen) return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; else return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; } } void QGtk3FileDialogHelper::applyOptions() { GtkDialog *gtkDialog = d->gtkDialog(); const QSharedPointer &opts = options(); gtk_window_set_title(GTK_WINDOW(gtkDialog), opts->windowTitle().toUtf8()); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(gtkDialog), true); const GtkFileChooserAction action = gtkFileChooserAction(opts); gtk_file_chooser_set_action(GTK_FILE_CHOOSER(gtkDialog), action); const bool selectMultiple = opts->fileMode() == QFileDialogOptions::ExistingFiles; gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(gtkDialog), selectMultiple); const bool confirmOverwrite = !opts->testOption(QFileDialogOptions::DontConfirmOverwrite); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(gtkDialog), confirmOverwrite); const QStringList nameFilters = opts->nameFilters(); if (!nameFilters.isEmpty()) setNameFilters(nameFilters); if (opts->initialDirectory().isLocalFile()) setDirectory(opts->initialDirectory()); for (const QUrl &filename : opts->initiallySelectedFiles()) selectFile(filename); const QString initialNameFilter = opts->initiallySelectedNameFilter(); if (!initialNameFilter.isEmpty()) selectNameFilter(initialNameFilter); GtkWidget *acceptButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK); if (acceptButton) { if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept)) gtk_button_set_label(GTK_BUTTON(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8()); else if (opts->acceptMode() == QFileDialogOptions::AcceptOpen) gtk_button_set_label(GTK_BUTTON(acceptButton), standardButtonText(QPlatformDialogHelper::Open)); else gtk_button_set_label(GTK_BUTTON(acceptButton), standardButtonText(QPlatformDialogHelper::Save)); } GtkWidget *rejectButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL); if (rejectButton) { if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject)) gtk_button_set_label(GTK_BUTTON(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8()); else gtk_button_set_label(GTK_BUTTON(rejectButton), standardButtonText(QPlatformDialogHelper::Cancel)); } } void QGtk3FileDialogHelper::setNameFilters(const QStringList &filters) { GtkDialog *gtkDialog = d->gtkDialog(); for (GtkFileFilter *filter : _filters) gtk_file_chooser_remove_filter(GTK_FILE_CHOOSER(gtkDialog), filter); _filters.clear(); _filterNames.clear(); for (const QString &filter : filters) { GtkFileFilter *gtkFilter = gtk_file_filter_new(); const QString name = filter.left(filter.indexOf(QLatin1Char('('))); const QStringList extensions = cleanFilterList(filter); gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8()); for (const QString &ext : extensions) gtk_file_filter_add_pattern(gtkFilter, ext.toUtf8()); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(gtkDialog), gtkFilter); _filters.insert(filter, gtkFilter); _filterNames.insert(gtkFilter, filter); } } QGtk3FontDialogHelper::QGtk3FontDialogHelper() { d.reset(new QGtk3Dialog(gtk_font_chooser_dialog_new("", 0))); connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted())); connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject())); g_signal_connect_swapped(d->gtkDialog(), "notify::font", G_CALLBACK(onFontChanged), this); } QGtk3FontDialogHelper::~QGtk3FontDialogHelper() { } bool QGtk3FontDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { applyOptions(); return d->show(flags, modality, parent); } void QGtk3FontDialogHelper::exec() { d->exec(); } void QGtk3FontDialogHelper::hide() { d->hide(); } static QString qt_fontToString(const QFont &font) { PangoFontDescription *desc = pango_font_description_new(); pango_font_description_set_size(desc, (font.pointSizeF() > 0.0 ? font.pointSizeF() : QFontInfo(font).pointSizeF()) * PANGO_SCALE); pango_font_description_set_family(desc, QFontInfo(font).family().toUtf8()); int weight = font.weight(); if (weight >= QFont::Black) pango_font_description_set_weight(desc, PANGO_WEIGHT_HEAVY); else if (weight >= QFont::ExtraBold) pango_font_description_set_weight(desc, PANGO_WEIGHT_ULTRABOLD); else if (weight >= QFont::Bold) pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); else if (weight >= QFont::DemiBold) pango_font_description_set_weight(desc, PANGO_WEIGHT_SEMIBOLD); else if (weight >= QFont::Medium) pango_font_description_set_weight(desc, PANGO_WEIGHT_MEDIUM); else if (weight >= QFont::Normal) pango_font_description_set_weight(desc, PANGO_WEIGHT_NORMAL); else if (weight >= QFont::Light) pango_font_description_set_weight(desc, PANGO_WEIGHT_LIGHT); else if (weight >= QFont::ExtraLight) pango_font_description_set_weight(desc, PANGO_WEIGHT_ULTRALIGHT); else pango_font_description_set_weight(desc, PANGO_WEIGHT_THIN); int style = font.style(); if (style == QFont::StyleItalic) pango_font_description_set_style(desc, PANGO_STYLE_ITALIC); else if (style == QFont::StyleOblique) pango_font_description_set_style(desc, PANGO_STYLE_OBLIQUE); else pango_font_description_set_style(desc, PANGO_STYLE_NORMAL); char *str = pango_font_description_to_string(desc); QString name = QString::fromUtf8(str); pango_font_description_free(desc); g_free(str); return name; } static QFont qt_fontFromString(const QString &name) { QFont font; PangoFontDescription *desc = pango_font_description_from_string(name.toUtf8()); font.setPointSizeF(static_cast(pango_font_description_get_size(desc)) / PANGO_SCALE); QString family = QString::fromUtf8(pango_font_description_get_family(desc)); if (!family.isEmpty()) font.setFamily(family); const int weight = pango_font_description_get_weight(desc); font.setWeight(QPlatformFontDatabase::weightFromInteger(weight)); PangoStyle style = pango_font_description_get_style(desc); if (style == PANGO_STYLE_ITALIC) font.setStyle(QFont::StyleItalic); else if (style == PANGO_STYLE_OBLIQUE) font.setStyle(QFont::StyleOblique); else font.setStyle(QFont::StyleNormal); pango_font_description_free(desc); return font; } void QGtk3FontDialogHelper::setCurrentFont(const QFont &font) { GtkFontChooser *gtkDialog = GTK_FONT_CHOOSER(d->gtkDialog()); gtk_font_chooser_set_font(gtkDialog, qt_fontToString(font).toUtf8()); } QFont QGtk3FontDialogHelper::currentFont() const { GtkFontChooser *gtkDialog = GTK_FONT_CHOOSER(d->gtkDialog()); gchar *name = gtk_font_chooser_get_font(gtkDialog); QFont font = qt_fontFromString(QString::fromUtf8(name)); g_free(name); return font; } void QGtk3FontDialogHelper::onAccepted() { Q_EMIT accept(); } void QGtk3FontDialogHelper::onFontChanged(QGtk3FontDialogHelper *dialog) { Q_EMIT dialog->currentFontChanged(dialog->currentFont()); } void QGtk3FontDialogHelper::applyOptions() { GtkDialog *gtkDialog = d->gtkDialog(); const QSharedPointer &opts = options(); gtk_window_set_title(GTK_WINDOW(gtkDialog), opts->windowTitle().toUtf8()); } QT_END_NAMESPACE ================================================ FILE: src/platform-plugin/qgtk3dialoghelpers.h ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QGTK3DIALOGHELPERS_H #define QGTK3DIALOGHELPERS_H #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QColor; class QGtk3Dialog : public QWindow { Q_OBJECT public: QGtk3Dialog(GtkWidget *gtkWidget); ~QGtk3Dialog(); GtkDialog *gtkDialog() const; void exec(); bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); void hide(); Q_SIGNALS: void accept(); void reject(); protected: static void onResponse(QGtk3Dialog *dialog, int response); private Q_SLOTS: void onParentWindowDestroyed(); private: GtkWidget *gtkWidget; }; class QGtk3ColorDialogHelper : public QPlatformColorDialogHelper { Q_OBJECT public: QGtk3ColorDialogHelper(); ~QGtk3ColorDialogHelper(); bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) Q_DECL_OVERRIDE; void exec() Q_DECL_OVERRIDE; void hide() Q_DECL_OVERRIDE; void setCurrentColor(const QColor &color) Q_DECL_OVERRIDE; QColor currentColor() const Q_DECL_OVERRIDE; private Q_SLOTS: void onAccepted(); private: static void onColorChanged(QGtk3ColorDialogHelper *helper); void applyOptions(); QScopedPointer d; }; class QGtk3FileDialogHelper : public QPlatformFileDialogHelper { Q_OBJECT public: QGtk3FileDialogHelper(); ~QGtk3FileDialogHelper(); bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) Q_DECL_OVERRIDE; void exec() Q_DECL_OVERRIDE; void hide() Q_DECL_OVERRIDE; bool defaultNameFilterDisables() const Q_DECL_OVERRIDE; void setDirectory(const QUrl &directory) Q_DECL_OVERRIDE; QUrl directory() const Q_DECL_OVERRIDE; void selectFile(const QUrl &filename) Q_DECL_OVERRIDE; QList selectedFiles() const Q_DECL_OVERRIDE; void setFilter() Q_DECL_OVERRIDE; void selectNameFilter(const QString &filter) Q_DECL_OVERRIDE; QString selectedNameFilter() const Q_DECL_OVERRIDE; private Q_SLOTS: void onAccepted(); private: static void onSelectionChanged(GtkDialog *dialog, QGtk3FileDialogHelper *helper); static void onCurrentFolderChanged(QGtk3FileDialogHelper *helper); static void onFilterChanged(QGtk3FileDialogHelper *helper); void applyOptions(); void setNameFilters(const QStringList &filters); QUrl _dir; QList _selection; QHash _filters; QHash _filterNames; QScopedPointer d; }; class QGtk3FontDialogHelper : public QPlatformFontDialogHelper { Q_OBJECT public: QGtk3FontDialogHelper(); ~QGtk3FontDialogHelper(); bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) Q_DECL_OVERRIDE; void exec() Q_DECL_OVERRIDE; void hide() Q_DECL_OVERRIDE; void setCurrentFont(const QFont &font) Q_DECL_OVERRIDE; QFont currentFont() const Q_DECL_OVERRIDE; private Q_SLOTS: void onAccepted(); private: static void onFontChanged(QGtk3FontDialogHelper *helper); void applyOptions(); QScopedPointer d; }; QT_END_NAMESPACE #endif // QGTK3DIALOGHELPERS_H ================================================ FILE: src/platform-plugin/qgtkbackingstore.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkbackingstore.h" #include "qgtkintegration.h" #include "qgtkwindow.h" #include "qscreen.h" #include #include #include #include "CSystrace.h" // we have no_keywords, but this header uses one. #define foreach Q_FOREACH #include #undef foreach QGtkBackingStore::QGtkBackingStore(QWindow *window) : QPlatformBackingStore(window) , m_paintImage(nullptr) { } QGtkBackingStore::~QGtkBackingStore() { } QPaintDevice *QGtkBackingStore::paintDevice() { return m_paintImage; } QImage QGtkBackingStore::toImage() const { return static_cast(window()->handle())->currentFrameImage(); } void QGtkBackingStore::beginPaint(const QRegion ®ion) { TRACE_EVENT_ASYNC_BEGIN0("gfx", "QGtkBackingStore::paint", this); Q_UNUSED(region); if (!m_paintImage) m_paintImage = static_cast(window()->handle())->beginUpdateFrame("beginPaint"); } void QGtkBackingStore::endPaint() { Q_ASSERT(m_paintImage); static_cast(window()->handle())->endUpdateFrame("endPaint"); m_paintImage = nullptr; TRACE_EVENT_ASYNC_END0("gfx", "QGtkBackingStore::paint", this); } void QGtkBackingStore::composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QPlatformTextureList *textures, bool translucentBackground) #else QPlatformTextureList *textures, QOpenGLContext *context, bool translucentBackground) #endif { TRACE_EVENT0("gfx", "QGtkBackingStore::composeAndFlush"); #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground); #else QPlatformBackingStore::composeAndFlush(window, region, offset, textures, context, translucentBackground); #endif } void QGtkBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { TRACE_EVENT0("gfx", "QGtkBackingStore::flush"); static_cast(window->handle())->invalidateRegion(region.translated(offset)); } void QGtkBackingStore::resize(const QSize &size, const QRegion &) { TRACE_EVENT0("gfx", "QGtkBackingStore::resize"); QGtkWindow *qgwin = static_cast(window()->handle()); QImage *image = qgwin->beginUpdateFrame("resize"); qreal dpr = window()->devicePixelRatio() / QHighDpiScaling::factor(window()); QSize realSize = size * dpr; QImage::Format format = QGuiApplication::primaryScreen()->handle()->format(); if (image->size() != realSize) { *image = QImage(realSize, format); image->setDevicePixelRatio(dpr); } qgwin->endUpdateFrame("resize"); } bool QGtkBackingStore::scroll(const QRegion ®ion, int dx, int dy) { // ### temporarily disabled // // this helps to accelerate widget scrolling by copying a region of the // backing store rather than re-rendering them from scratch. however, it's // currently not working quite right because of bad event ordering. // // the way this is supposed to work: // QWidget::scroll() // -> backingStore->scroll() // -> QPABackingStore->scroll() // -> QWidget::update() // QPABackingStore::beginPaint // QWidget::paintEvent // ... // gtk_render_stuff // // But events are arriving out of order at the moment (perhaps due to event // dispatcher? perhaps due to something else?) and as a result we get: // QWidget::scroll // -> backingStore->scroll // -> QPABackingStore->scroll // -> QWidget::update // gtk_render_stuff <--- OOPS! didn't render the area that we couldn't // scroll yet! // QPABackingStore::beginPaint // QWidget::paintEvent // gtk_render_stuff <- eventually consistent, but looks bad. return false; TRACE_EVENT0("gfx", "QGtkBackingStore::scroll"); extern void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &); QGtkWindow *qgwin = static_cast(window()->handle()); const qreal dpr = qgwin->devicePixelRatio(); const QPoint delta = QPoint(dx * dpr, dy * dpr); QImage *image = qgwin->beginUpdateFrame("scroll"); for (const QRect &rect : region.rects()) { qt_scrollRectInImage(*image, QRect(rect.topLeft() * dpr, rect.size() * dpr), delta); } qgwin->endUpdateFrame("scroll"); QRegion uregion = region.united(region.translated(delta)); qgwin->invalidateRegion(uregion); return true; } ================================================ FILE: src/platform-plugin/qgtkbackingstore.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QBACKINGSTORE_GTK_H #define QBACKINGSTORE_GTK_H #include #include #include QT_BEGIN_NAMESPACE class QGtkBackingStore : public QPlatformBackingStore { public: QGtkBackingStore(QWindow *window); ~QGtkBackingStore(); QPaintDevice *paintDevice() override; void beginPaint(const QRegion ®ion) override; void endPaint() override; void composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QPlatformTextureList *textures, bool translucentBackground) override; #else QPlatformTextureList *textures, QOpenGLContext *context, bool translucentBackground) override; #endif void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; void resize(const QSize &size, const QRegion &staticContents) override; QImage toImage() const override; bool scroll(const QRegion ®ion, int dx, int dy) override; private: QImage *m_paintImage; }; QT_END_NAMESPACE #endif ================================================ FILE: src/platform-plugin/qgtkclipboard.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkclipboard.h" #include "qgtkhelpers.h" #include #include Q_LOGGING_CATEGORY(lcClipboard, "qt.qpa.gtk.clipboard"); enum TargetTypes { TargetTypeText = 1, TargetTypeImage = 2 }; QGtkClipboard::QGtkClipboard(QObject *parent) : QObject(parent) , m_clipData(QClipboard::Clipboard) , m_selData(QClipboard::Selection) { QObject::connect(&m_clipData, &QGtkClipboardData::changed, this, [=](){ emitChanged(QClipboard::Clipboard); qCDebug(lcClipboard) << "Clipboard changed"; }); QObject::connect(&m_selData, &QGtkClipboardData::changed, this, [=](){ emitChanged(QClipboard::Selection); qCDebug(lcClipboard) << "Selection changed"; }); } QGtkClipboard::~QGtkClipboard() { } QGtkClipboardData *QGtkClipboard::mimeForMode(QClipboard::Mode mode) const { switch (mode) { case QClipboard::Clipboard: return const_cast(&m_clipData); case QClipboard::Selection: return const_cast(&m_selData); default: Q_UNREACHABLE(); } } QMimeData *QGtkClipboard::mimeData(QClipboard::Mode mode) { if (!supportsMode(mode)) return 0; QGtkClipboardData *m = mimeForMode(mode); return m->mimeData(); } void QGtkClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) { if (!supportsMode(mode)) return; QGtkClipboardData *m = mimeForMode(mode); m->setData(data); qCDebug(lcClipboard) << "setMimeData changed to " << data; emitChanged(mode); } bool QGtkClipboard::supportsMode(QClipboard::Mode mode) const { switch (mode) { case QClipboard::Clipboard: case QClipboard::Selection: return true; default: return false; } } bool QGtkClipboard::ownsMode(QClipboard::Mode mode) const { if (!supportsMode(mode)) return false; QGtkClipboardData *m = mimeForMode(mode); return m->ownsMode(); } QGtkClipboardData::QGtkClipboardData(QClipboard::Mode clipboardMode) : m_mode(clipboardMode) { switch (clipboardMode) { case QClipboard::Clipboard: m_clipboard = gtk_clipboard_get(gdk_atom_intern("CLIPBOARD", TRUE)); break; case QClipboard::Selection: m_clipboard = gtk_clipboard_get(gdk_atom_intern("PRIMARY", TRUE)); break; default: Q_UNREACHABLE(); } } QGtkClipboardData::~QGtkClipboardData() { gtk_clipboard_set_can_store(m_clipboard, NULL, 0); delete m_localData; delete m_systemData; } bool QGtkClipboardData::ownsMode() const { return gtk_clipboard_get_owner(m_clipboard) != NULL; } static void getFun(GtkClipboard *, GtkSelectionData *selection_data, guint info, gpointer gtkClipboardData) { QGtkClipboardData *gdata = static_cast(gtkClipboardData); gdata->onLocalGet(selection_data, info); } static void clearFun(GtkClipboard*, gpointer gtkClipboardData) { QGtkClipboardData *gdata = static_cast(gtkClipboardData); gdata->onLocalClear(); } void QGtkClipboardData::onLocalClear() { qCDebug(lcClipboard) << "Clear func" << m_mode; delete m_localData; m_localData = nullptr; } // Request for the data from our set clipboard to give to a remote client void QGtkClipboardData::onLocalGet(GtkSelectionData *selection_data, guint info) { qCDebug(lcClipboard) << "Local get for " << m_localData; if (!m_localData) { return; } if (info == TargetTypeText) { gtk_selection_data_set_text(selection_data, m_localData->text().toUtf8().constData(), -1); } else if (info == TargetTypeImage) { QImage imageData = qvariant_cast(m_localData->imageData()); gtk_selection_data_set_pixbuf(selection_data, qt_pixmapToPixbuf(QPixmap::fromImage(imageData)).get()); } } // Set our local clipboard, and inform the system about it void QGtkClipboardData::setData(QMimeData *data) { qCDebug(lcClipboard) << "Setting mime data " << data << m_mode << (data ? data->formats() : QStringList()); if (!data || data->formats().isEmpty()) { qCDebug(lcClipboard) << "Clearing mime data" << data; gtk_clipboard_clear(m_clipboard); return; } GtkTargetList *targetList = gtk_target_list_new(nullptr, 0); if (data->hasText()) { gtk_target_list_add_text_targets(targetList, TargetTypeText); } if (data->hasImage()) { QImage imageData = qvariant_cast(data->imageData()); gtk_target_list_add_image_targets(targetList, TargetTypeImage, TRUE); } // ### rich text? html? what else should we handle... int targetCount = 0; GtkTargetEntry *table = gtk_target_table_new_from_list(targetList, &targetCount); if (targetCount > 0 && table) { if (gtk_clipboard_set_with_data( m_clipboard, table, targetCount, getFun, clearFun, this ) == TRUE) { gtk_clipboard_set_can_store(m_clipboard, nullptr, 0); } else { qCWarning(lcClipboard) << "Store FAILED"; } } else { qCWarning(lcClipboard) << "No targets"; } if (table) { gtk_target_table_free(table, targetCount); } gtk_target_list_unref(targetList); m_localData = data; } QMimeData *QGtkClipboardData::mimeData() const { qCDebug(lcClipboard) << "Getting data" << m_mode << m_localData << m_systemData; if (ownsMode()) { qCDebug(lcClipboard) << "Getting local data"; return m_localData; } if (m_systemData) { qCDebug(lcClipboard) << "Clearing system data"; m_systemData->clear(); } else { qCDebug(lcClipboard) << "Creating system data"; const_cast(this)->m_systemData = new QMimeData(); } GtkSelectionData *gsel = gtk_clipboard_wait_for_contents(m_clipboard, gdk_atom_intern("TARGETS", TRUE)); if (gsel) { if (gtk_selection_data_targets_include_image(gsel, FALSE)) { qCDebug(lcClipboard) << "Reading image data"; QGtkRefPtr img = gtk_clipboard_wait_for_image(m_clipboard); if (img.get()) { QImage data = qt_pixbufToImage(img); if (!data.isNull()) { m_systemData->setImageData(QVariant::fromValue(data)); qCDebug(lcClipboard) << "Read image " << data; } } } if (gtk_selection_data_targets_include_text(gsel)) { qCDebug(lcClipboard) << "Reading text data"; const gchar *rdata = gtk_clipboard_wait_for_text(m_clipboard); if (rdata) { QString data = QString::fromUtf8((rdata), strlen(rdata)); g_free((void*)rdata); if (!data.isNull()) { m_systemData->setText(data); qCDebug(lcClipboard) << "Read text " << data; } } } gtk_selection_data_free(gsel); } return m_systemData; } ================================================ FILE: src/platform-plugin/qgtkclipboard.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKCLIPBOARD_H #define QGTKCLIPBOARD_H #include #include #include QT_BEGIN_NAMESPACE class QGtkClipboardData : public QObject { Q_OBJECT public: QGtkClipboardData(QClipboard::Mode clipboardMode); ~QGtkClipboardData(); void setData(QMimeData *data); QMimeData *mimeData() const; bool ownsMode() const; void onLocalClear(); void onLocalGet(GtkSelectionData *selection_data, guint info); Q_SIGNALS: void changed(); private: QStringList formats() const; GtkClipboard *m_clipboard = nullptr; QMimeData *m_localData = nullptr; // app-local QMimeData *m_systemData = nullptr; // system-global, remote QClipboard::Mode m_mode; }; class QGtkClipboard : public QPlatformClipboard, QObject { public: QGtkClipboard(QObject *parent = 0); ~QGtkClipboard(); QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; bool supportsMode(QClipboard::Mode mode) const override; bool ownsMode(QClipboard::Mode mode) const override; private: QGtkClipboardData m_clipData; QGtkClipboardData m_selData; QGtkClipboardData *mimeForMode(QClipboard::Mode mode) const; }; QT_END_NAMESPACE #endif // QGTKCLIPBOARD_H ================================================ FILE: src/platform-plugin/qgtkcursor.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkcursor.h" #include "qgtkhelpers.h" #include "qgtkwindow.h" QGtkCursor::QGtkCursor() : QPlatformCursor() { } void QGtkCursor::changeCursor(QCursor *windowCursor, QWindow *window) { Qt::CursorShape shape = Qt::BlankCursor; if (windowCursor) { shape = windowCursor->shape(); } QByteArray gtkCursorName; bool bitmapCursor = false; switch (shape) { case Qt::BlankCursor: case Qt::CustomCursor: case Qt::ArrowCursor: gtkCursorName = "default"; break; case Qt::UpArrowCursor: gtkCursorName = "default"; break; case Qt::CrossCursor: gtkCursorName = "crosshair"; break; case Qt::WaitCursor: gtkCursorName = "wait"; break; case Qt::IBeamCursor: gtkCursorName = "text"; break; case Qt::SizeVerCursor: gtkCursorName = "row-resize"; break; case Qt::SizeHorCursor: gtkCursorName = "col-resize"; break; case Qt::SizeBDiagCursor: gtkCursorName = "nesw-resize"; break; case Qt::SizeFDiagCursor: gtkCursorName = "nwse-resize"; break; case Qt::SizeAllCursor: gtkCursorName = "all-scroll"; break; case Qt::SplitVCursor: gtkCursorName = "ns-resize"; break; case Qt::SplitHCursor: gtkCursorName = "ew-resize"; break; case Qt::PointingHandCursor: gtkCursorName = "pointer"; break; case Qt::ForbiddenCursor: gtkCursorName = "not-allowed"; break; case Qt::OpenHandCursor: gtkCursorName = "grab"; break; case Qt::ClosedHandCursor: gtkCursorName = "grabbing"; break; case Qt::WhatsThisCursor: gtkCursorName = "help"; break; case Qt::BusyCursor: gtkCursorName = "progress"; break; case Qt::DragMoveCursor: gtkCursorName = "grabbing"; break; case Qt::DragCopyCursor: gtkCursorName = "copy"; break; case Qt::DragLinkCursor: gtkCursorName = "alias"; break; case Qt::BitmapCursor: bitmapCursor = true; break; } QGtkRefPtr c; if (bitmapCursor == false) { c = gdk_cursor_new_from_name(gdk_display_get_default(), gtkCursorName.constData()); } else { c = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), qt_pixmapToPixbuf(windowCursor->pixmap()).get(), 0, 0); } QGtkWindow *pw = static_cast(window->handle()); // ### are we called before being realized, sometimes? why does this happen? if (gtk_widget_get_window(pw->gtkWindow().get()) != nullptr) { gdk_window_set_cursor(gtk_widget_get_window(pw->gtkWindow().get()), c.get()); } } QPoint QGtkCursor::pos() const { // not supported. return m_pos; } void QGtkCursor::setPos(const QPoint &pos) { // not supported. m_pos = pos; } ================================================ FILE: src/platform-plugin/qgtkcursor.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKCURSOR_H #define QGTKCURSOR_H #include #include QT_BEGIN_NAMESPACE class QGtkCursor; class QGtkCursor : public QPlatformCursor { public: QGtkCursor(); void changeCursor(QCursor *windowCursor, QWindow *window) override; QPoint pos() const override; void setPos(const QPoint &pos) override; private: QPoint m_pos; }; QT_END_NAMESPACE #endif // QGTKCURSOR_H ================================================ FILE: src/platform-plugin/qgtkeventdispatcher.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qgtkeventdispatcher.h" #include #include #include #include "qcoreapplication.h" #include "qsocketnotifier.h" #include #include #include #include "CSystrace.h" QT_BEGIN_NAMESPACE struct GPollFDWithQSocketNotifier { GPollFD pollfd; QSocketNotifier *socketNotifier; }; struct GSocketNotifierSource { GSource source; QList pollfds; }; static gboolean socketNotifierSourcePrepare(GSource *, gint *timeout) { if (timeout) *timeout = -1; return false; } static gboolean socketNotifierSourceCheck(GSource *source) { GSocketNotifierSource *src = reinterpret_cast(source); bool pending = false; for (int i = 0; !pending && i < src->pollfds.count(); ++i) { GPollFDWithQSocketNotifier *p = src->pollfds.at(i); if (p->pollfd.revents & G_IO_NVAL) { // disable the invalid socket notifier static const char *t[] = { "Read", "Write", "Exception" }; qWarning("QSocketNotifier: Invalid socket %d and type '%s', disabling...", p->pollfd.fd, t[int(p->socketNotifier->type())]); // ### note, modifies src->pollfds! p->socketNotifier->setEnabled(false); } pending = ((p->pollfd.revents & p->pollfd.events) != 0); } return pending; } static gboolean socketNotifierSourceDispatch(GSource *source, GSourceFunc, gpointer) { QEvent event(QEvent::SockAct); GSocketNotifierSource *src = reinterpret_cast(source); for (int i = 0; i < src->pollfds.count(); ++i) { GPollFDWithQSocketNotifier *p = src->pollfds.at(i); if ((p->pollfd.revents & p->pollfd.events) != 0) { TRACE_EVENT0("gfx", "QGtkEventDispatcher::socketNotifierSourceDispatch"); QCoreApplication::sendEvent(p->socketNotifier, &event); } } return true; // ??? don't remove, right? } static GSourceFuncs socketNotifierSourceFuncs = { socketNotifierSourcePrepare, socketNotifierSourceCheck, socketNotifierSourceDispatch, NULL, NULL, NULL }; struct GTimerSource { GSource source; QTimerInfoList timerList; QEventLoop::ProcessEventsFlags processEventsFlags; bool runWithIdlePriority; }; static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout) { timespec tv = { 0l, 0l }; if (!(src->processEventsFlags & QEventLoop::X11ExcludeTimers) && src->timerList.timerWait(tv)) *timeout = (tv.tv_sec * 1000) + ((tv.tv_nsec + 999999) / 1000 / 1000); else *timeout = -1; return (*timeout == 0); } static gboolean timerSourceCheckHelper(GTimerSource *src) { if (src->timerList.isEmpty() || (src->processEventsFlags & QEventLoop::X11ExcludeTimers)) return false; if (src->timerList.updateCurrentTime() < src->timerList.constFirst()->timeout) return false; return true; } static gboolean timerSourcePrepare(GSource *source, gint *timeout) { gint dummy; if (!timeout) timeout = &dummy; GTimerSource *src = reinterpret_cast(source); if (src->runWithIdlePriority) { if (timeout) *timeout = -1; return false; } return timerSourcePrepareHelper(src, timeout); } static gboolean timerSourceCheck(GSource *source) { GTimerSource *src = reinterpret_cast(source); if (src->runWithIdlePriority) return false; return timerSourceCheckHelper(src); } static gboolean timerSourceDispatch(GSource *source, GSourceFunc, gpointer) { GTimerSource *timerSource = reinterpret_cast(source); if (timerSource->processEventsFlags & QEventLoop::X11ExcludeTimers) return true; timerSource->runWithIdlePriority = true; (void) timerSource->timerList.activateTimers(); return true; // ??? don't remove, right again? } static GSourceFuncs timerSourceFuncs = { timerSourcePrepare, timerSourceCheck, timerSourceDispatch, NULL, NULL, NULL }; struct GIdleTimerSource { GSource source; GTimerSource *timerSource; }; static gboolean idleTimerSourcePrepare(GSource *source, gint *timeout) { GIdleTimerSource *idleTimerSource = reinterpret_cast(source); GTimerSource *timerSource = idleTimerSource->timerSource; if (!timerSource->runWithIdlePriority) { // Yield to the normal priority timer source if (timeout) *timeout = -1; return false; } return timerSourcePrepareHelper(timerSource, timeout); } static gboolean idleTimerSourceCheck(GSource *source) { GIdleTimerSource *idleTimerSource = reinterpret_cast(source); GTimerSource *timerSource = idleTimerSource->timerSource; if (!timerSource->runWithIdlePriority) { // Yield to the normal priority timer source return false; } return timerSourceCheckHelper(timerSource); } static gboolean idleTimerSourceDispatch(GSource *source, GSourceFunc, gpointer) { GTimerSource *timerSource = reinterpret_cast(source)->timerSource; (void) timerSourceDispatch(&timerSource->source, 0, 0); return true; } static GSourceFuncs idleTimerSourceFuncs = { idleTimerSourcePrepare, idleTimerSourceCheck, idleTimerSourceDispatch, NULL, NULL, NULL }; struct GPostEventSource { GSource source; QAtomicInt serialNumber; int lastSerialNumber; QGtkEventDispatcherPrivate *d; }; static gboolean postEventSourcePrepare(GSource *s, gint *timeout) { QThreadData *data = reinterpret_cast(QObjectPrivate::get(QThread::currentThread()))->data; if (!data) return false; gint dummy; if (!timeout) timeout = &dummy; const bool canWait = data->canWaitLocked(); *timeout = canWait ? -1 : 0; GPostEventSource *source = reinterpret_cast(s); return (!canWait #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) || (source->serialNumber.loadRelaxed() != source->lastSerialNumber)); #else || (source->serialNumber.load() != source->lastSerialNumber)); #endif } static gboolean postEventSourceCheck(GSource *source) { return postEventSourcePrepare(source, 0); } static gboolean postEventSourceDispatch(GSource *s, GSourceFunc, gpointer) { TRACE_EVENT0("gfx", "QGtkEventDispatcher::postEventSourceDispatch"); GPostEventSource *source = reinterpret_cast(s); #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) source->lastSerialNumber = source->serialNumber.loadRelaxed(); #else source->lastSerialNumber = source->serialNumber.load(); #endif QCoreApplication::sendPostedEvents(); source->d->runTimersOnceWithNormalPriority(); return true; // i dunno, george... } static GSourceFuncs postEventSourceFuncs = { postEventSourcePrepare, postEventSourceCheck, postEventSourceDispatch, NULL, NULL, NULL }; struct GUserEventSource { GSource source; QGtkEventDispatcherPrivate *d; }; static gboolean userEventSourcePrepare(GSource *s, gint *timeout) { Q_UNUSED(s); Q_UNUSED(timeout); return QWindowSystemInterface::windowSystemEventsQueued() > 0; } static gboolean userEventSourceCheck(GSource *source) { return userEventSourcePrepare(source, 0); } static gboolean userEventSourceDispatch(GSource *source, GSourceFunc, gpointer) { TRACE_EVENT0("gfx", "QGtkEventDispatcher::userEventSourceDispatch"); GUserEventSource *userEventSource = reinterpret_cast(source); QGtkEventDispatcherPrivate *dispatcher = userEventSource->d; QWindowSystemInterface::sendWindowSystemEvents(dispatcher->m_flags); return true; } static GSourceFuncs userEventSourceFuncs = { userEventSourcePrepare, userEventSourceCheck, userEventSourceDispatch, NULL, NULL, NULL }; QGtkEventDispatcherPrivate::QGtkEventDispatcherPrivate(GMainContext *context) : mainContext(context) { #if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32 if (qEnvironmentVariableIsEmpty("QT_NO_THREADED_GLIB")) { static QBasicMutex mutex; QMutexLocker locker(&mutex); if (!g_thread_supported()) g_thread_init(NULL); } #endif if (mainContext) { g_main_context_ref(mainContext); } else { QCoreApplication *app = QCoreApplication::instance(); if (app && QThread::currentThread() == app->thread()) { mainContext = g_main_context_default(); g_main_context_ref(mainContext); } else { mainContext = g_main_context_new(); } } #if GLIB_CHECK_VERSION (2, 22, 0) g_main_context_push_thread_default (mainContext); #endif // ### RB: modified from upstream // We must ensure that gtk's priority settings are above us, otherwise we // might exhaust gtk, meaning that windows won't show etc. int QT_BASE_PRIORITY = G_PRIORITY_LOW - 1; // user event source userEventSource = reinterpret_cast(g_source_new(&userEventSourceFuncs, sizeof(GUserEventSource))); userEventSource->d = this; // must be above QT_BASE_PRIORITY, and similar to gtk's base priorities, otherwise we will have window flicker on show/resize. g_source_set_priority(&userEventSource->source, G_PRIORITY_DEFAULT); g_source_set_can_recurse(&userEventSource->source, true); g_source_attach(&userEventSource->source, mainContext); // setup post event source postEventSource = reinterpret_cast(g_source_new(&postEventSourceFuncs, sizeof(GPostEventSource))); g_source_set_priority(&postEventSource->source, QT_BASE_PRIORITY - 1); #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) postEventSource->serialNumber.storeRelaxed(1); #else postEventSource->serialNumber.store(1); #endif postEventSource->d = this; g_source_set_can_recurse(&postEventSource->source, true); g_source_attach(&postEventSource->source, mainContext); // setup socketNotifierSource socketNotifierSource = reinterpret_cast(g_source_new(&socketNotifierSourceFuncs, sizeof(GSocketNotifierSource))); g_source_set_priority(&socketNotifierSource->source, QT_BASE_PRIORITY - 3); (void) new (&socketNotifierSource->pollfds) QList(); g_source_set_can_recurse(&socketNotifierSource->source, true); g_source_attach(&socketNotifierSource->source, mainContext); // setup normal and idle timer sources timerSource = reinterpret_cast(g_source_new(&timerSourceFuncs, sizeof(GTimerSource))); g_source_set_priority(&timerSource->source, QT_BASE_PRIORITY - 3); (void) new (&timerSource->timerList) QTimerInfoList(); timerSource->processEventsFlags = QEventLoop::AllEvents; timerSource->runWithIdlePriority = false; g_source_set_can_recurse(&timerSource->source, true); g_source_attach(&timerSource->source, mainContext); idleTimerSource = reinterpret_cast(g_source_new(&idleTimerSourceFuncs, sizeof(GIdleTimerSource))); g_source_set_priority(&idleTimerSource->source, QT_BASE_PRIORITY - 4); idleTimerSource->timerSource = timerSource; g_source_set_can_recurse(&idleTimerSource->source, true); g_source_attach(&idleTimerSource->source, mainContext); } void QGtkEventDispatcherPrivate::runTimersOnceWithNormalPriority() { timerSource->runWithIdlePriority = false; } QGtkEventDispatcher::QGtkEventDispatcher(QObject *parent) : QAbstractEventDispatcher(*(new QGtkEventDispatcherPrivate), parent) { } QGtkEventDispatcher::QGtkEventDispatcher(GMainContext *mainContext, QObject *parent) : QAbstractEventDispatcher(*(new QGtkEventDispatcherPrivate(mainContext)), parent) { } QGtkEventDispatcher::~QGtkEventDispatcher() { Q_D(QGtkEventDispatcher); // destroy user event source g_source_destroy(&d->userEventSource->source); g_source_unref(&d->userEventSource->source); d->userEventSource = 0; // destroy all timer sources qDeleteAll(d->timerSource->timerList); d->timerSource->timerList.~QTimerInfoList(); g_source_destroy(&d->timerSource->source); g_source_unref(&d->timerSource->source); d->timerSource = 0; g_source_destroy(&d->idleTimerSource->source); g_source_unref(&d->idleTimerSource->source); d->idleTimerSource = 0; // destroy socket notifier source for (int i = 0; i < d->socketNotifierSource->pollfds.count(); ++i) { GPollFDWithQSocketNotifier *p = d->socketNotifierSource->pollfds[i]; g_source_remove_poll(&d->socketNotifierSource->source, &p->pollfd); delete p; } d->socketNotifierSource->pollfds.~QList(); g_source_destroy(&d->socketNotifierSource->source); g_source_unref(&d->socketNotifierSource->source); d->socketNotifierSource = 0; // destroy post event source g_source_destroy(&d->postEventSource->source); g_source_unref(&d->postEventSource->source); d->postEventSource = 0; Q_ASSERT(d->mainContext != 0); #if GLIB_CHECK_VERSION (2, 22, 0) g_main_context_pop_thread_default (d->mainContext); #endif g_main_context_unref(d->mainContext); d->mainContext = 0; } bool QGtkEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) { TRACE_EVENT0("gfx", "QGtkEventDispatcher::processEvents"); Q_D(QGtkEventDispatcher); d->m_flags = flags; const bool canWait = (flags & QEventLoop::WaitForMoreEvents); if (canWait) Q_EMIT aboutToBlock(); else Q_EMIT awake(); // tell postEventSourcePrepare() and timerSource about any new flags QEventLoop::ProcessEventsFlags savedFlags = d->timerSource->processEventsFlags; d->timerSource->processEventsFlags = flags; if (!(flags & QEventLoop::EventLoopExec)) { // force timers to be sent at normal priority d->timerSource->runWithIdlePriority = false; } bool result = g_main_context_iteration(d->mainContext, canWait); while (!result && canWait) result = g_main_context_iteration(d->mainContext, canWait); d->timerSource->processEventsFlags = savedFlags; if (canWait) Q_EMIT awake(); return result; } bool QGtkEventDispatcher::hasPendingEvents() { Q_D(QGtkEventDispatcher); return g_main_context_pending(d->mainContext); } void QGtkEventDispatcher::registerSocketNotifier(QSocketNotifier *notifier) { Q_ASSERT(notifier); int sockfd = notifier->socket(); int type = notifier->type(); #ifndef QT_NO_DEBUG if (sockfd < 0) { qWarning("QSocketNotifier: Internal error"); return; } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) { qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread"); return; } #endif Q_D(QGtkEventDispatcher); TRACE_COUNTER1("core", "registeredSockets", ++d->m_sockets); GPollFDWithQSocketNotifier *p = new GPollFDWithQSocketNotifier; p->pollfd.fd = sockfd; switch (type) { case QSocketNotifier::Read: p->pollfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; break; case QSocketNotifier::Write: p->pollfd.events = G_IO_OUT | G_IO_ERR; break; case QSocketNotifier::Exception: p->pollfd.events = G_IO_PRI | G_IO_ERR; break; } p->socketNotifier = notifier; d->socketNotifierSource->pollfds.append(p); g_source_add_poll(&d->socketNotifierSource->source, &p->pollfd); } void QGtkEventDispatcher::unregisterSocketNotifier(QSocketNotifier *notifier) { Q_ASSERT(notifier); #ifndef QT_NO_DEBUG int sockfd = notifier->socket(); if (sockfd < 0) { qWarning("QSocketNotifier: Internal error"); return; } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) { qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread"); return; } #endif Q_D(QGtkEventDispatcher); TRACE_COUNTER1("core", "registeredSockets", --d->m_sockets); for (int i = 0; i < d->socketNotifierSource->pollfds.count(); ++i) { GPollFDWithQSocketNotifier *p = d->socketNotifierSource->pollfds.at(i); if (p->socketNotifier == notifier) { // found it g_source_remove_poll(&d->socketNotifierSource->source, &p->pollfd); d->socketNotifierSource->pollfds.removeAt(i); delete p; return; } } } void QGtkEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object) { #ifndef QT_NO_DEBUG if (timerId < 1 || interval < 0 || !object) { qWarning("QGtkEventDispatcher::registerTimer: invalid arguments"); return; } else if (object->thread() != thread() || thread() != QThread::currentThread()) { qWarning("QGtkEventDispatcher::registerTimer: timers cannot be started from another thread"); return; } #endif Q_D(QGtkEventDispatcher); TRACE_COUNTER1("core", "registeredTimers", ++d->m_timers); d->timerSource->timerList.registerTimer(timerId, interval, timerType, object); } bool QGtkEventDispatcher::unregisterTimer(int timerId) { #ifndef QT_NO_DEBUG if (timerId < 1) { qWarning("QGtkEventDispatcher::unregisterTimer: invalid argument"); return false; } else if (thread() != QThread::currentThread()) { qWarning("QGtkEventDispatcher::unregisterTimer: timers cannot be stopped from another thread"); return false; } #endif Q_D(QGtkEventDispatcher); TRACE_COUNTER1("core", "registeredTimers", --d->m_timers); return d->timerSource->timerList.unregisterTimer(timerId); } bool QGtkEventDispatcher::unregisterTimers(QObject *object) { #ifndef QT_NO_DEBUG if (!object) { qWarning("QGtkEventDispatcher::unregisterTimers: invalid argument"); return false; } else if (object->thread() != thread() || thread() != QThread::currentThread()) { qWarning("QGtkEventDispatcher::unregisterTimers: timers cannot be stopped from another thread"); return false; } #endif Q_D(QGtkEventDispatcher); return d->timerSource->timerList.unregisterTimers(object); } QList QGtkEventDispatcher::registeredTimers(QObject *object) const { if (!object) { qWarning("QGtkEventDispatcher:registeredTimers: invalid argument"); return QList(); } Q_D(const QGtkEventDispatcher); return d->timerSource->timerList.registeredTimers(object); } int QGtkEventDispatcher::remainingTime(int timerId) { #ifndef QT_NO_DEBUG if (timerId < 1) { qWarning("QGtkEventDispatcher::remainingTimeTime: invalid argument"); return -1; } #endif Q_D(QGtkEventDispatcher); return d->timerSource->timerList.timerRemainingTime(timerId); } void QGtkEventDispatcher::interrupt() { wakeUp(); } void QGtkEventDispatcher::wakeUp() { Q_D(QGtkEventDispatcher); d->postEventSource->serialNumber.ref(); g_main_context_wakeup(d->mainContext); } void QGtkEventDispatcher::flush() { } bool QGtkEventDispatcher::versionSupported() { #if !defined(GLIB_MAJOR_VERSION) || !defined(GLIB_MINOR_VERSION) || !defined(GLIB_MICRO_VERSION) return false; #else return ((GLIB_MAJOR_VERSION << 16) + (GLIB_MINOR_VERSION << 8) + GLIB_MICRO_VERSION) >= 0x020301; #endif } QGtkEventDispatcher::QGtkEventDispatcher(QGtkEventDispatcherPrivate &dd, QObject *parent) : QAbstractEventDispatcher(dd, parent) { } QT_END_NAMESPACE ================================================ FILE: src/platform-plugin/qgtkeventdispatcher.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QEVENTDISPATCHER_GLIB_P_H #define QEVENTDISPATCHER_GLIB_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists for the convenience // of the QLibrary class. This header file may change from // version to version without notice, or even be removed. // // We mean it. // #include #include typedef struct _GMainContext GMainContext; QT_BEGIN_NAMESPACE class QGtkEventDispatcherPrivate; class Q_CORE_EXPORT QGtkEventDispatcher : public QAbstractEventDispatcher { Q_OBJECT Q_DECLARE_PRIVATE(QGtkEventDispatcher) public: explicit QGtkEventDispatcher(QObject *parent = 0); explicit QGtkEventDispatcher(GMainContext *context, QObject *parent = 0); ~QGtkEventDispatcher(); bool processEvents(QEventLoop::ProcessEventsFlags flags) Q_DECL_OVERRIDE; bool hasPendingEvents() Q_DECL_OVERRIDE; void registerSocketNotifier(QSocketNotifier *socketNotifier) Q_DECL_FINAL; void unregisterSocketNotifier(QSocketNotifier *socketNotifier) Q_DECL_FINAL; void registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object) Q_DECL_FINAL; bool unregisterTimer(int timerId) Q_DECL_FINAL; bool unregisterTimers(QObject *object) Q_DECL_FINAL; QList registeredTimers(QObject *object) const Q_DECL_FINAL; int remainingTime(int timerId) Q_DECL_FINAL; void wakeUp() Q_DECL_FINAL; void interrupt() Q_DECL_FINAL; void flush() Q_DECL_FINAL; static bool versionSupported(); protected: QGtkEventDispatcher(QGtkEventDispatcherPrivate &dd, QObject *parent); }; struct GPostEventSource; struct GSocketNotifierSource; struct GTimerSource; struct GIdleTimerSource; struct GUserEventSource; class Q_CORE_EXPORT QGtkEventDispatcherPrivate : public QAbstractEventDispatcherPrivate { public: QGtkEventDispatcherPrivate(GMainContext *context = 0); GMainContext *mainContext; GPostEventSource *postEventSource; GSocketNotifierSource *socketNotifierSource; GTimerSource *timerSource; GIdleTimerSource *idleTimerSource; GUserEventSource *userEventSource; void runTimersOnceWithNormalPriority(); QEventLoop::ProcessEventsFlags m_flags = QEventLoop::AllEvents; uint m_timers = 0; uint m_sockets = 0; }; QT_END_NAMESPACE #endif // QEVENTDISPATCHER_GLIB_P_H ================================================ FILE: src/platform-plugin/qgtkhelpers.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkhelpers.h" QGtkRefPtr qt_imageToPixbuf(const QImage &cimage) { if (cimage.isNull()) return 0; QImage image = cimage; if (image.hasAlphaChannel()) { if (image.format() != QImage::Format_RGBA8888) { image = image.convertToFormat(QImage::Format_RGBA8888); } } else { if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } } guchar *buf = (guchar*)malloc(image.byteCount()); memcpy(buf, image.constBits(), image.byteCount()); QGtkRefPtr gpb = gdk_pixbuf_new_from_data( buf, GDK_COLORSPACE_RGB, image.hasAlphaChannel(), 8, image.width(), image.height(), image.bytesPerLine(), (GdkPixbufDestroyNotify)free, NULL ); return gpb; } QGtkRefPtr qt_pixmapToPixbuf(const QPixmap &pixmap) { QImage i = pixmap.toImage(); return qt_imageToPixbuf(i); } QImage qt_pixbufToImage(const QGtkRefPtr &pixbuf) { QImage data; if (gdk_pixbuf_get_has_alpha(pixbuf.get())) { data = QImage(gdk_pixbuf_get_pixels(pixbuf.get()), gdk_pixbuf_get_width(pixbuf.get()), gdk_pixbuf_get_height(pixbuf.get()), gdk_pixbuf_get_rowstride(pixbuf.get()), QImage::Format_RGBA8888).copy(); } else { data = QImage(gdk_pixbuf_get_pixels(pixbuf.get()), gdk_pixbuf_get_width(pixbuf.get()), gdk_pixbuf_get_height(pixbuf.get()), gdk_pixbuf_get_rowstride(pixbuf.get()), QImage::Format_RGB888).copy(); } return data; } // Returns the largest image for this icon, in RGBA format. QImage qt_getBiggestImageForIcon(const QIcon &icon) { QIcon::State st = QIcon::On; QList sizes = icon.availableSizes(QIcon::Normal, st); if (!sizes.length()) { st = QIcon::Off; sizes = icon.availableSizes(QIcon::Normal, st); if (!sizes.length()) { qWarning() << "No available icons for icon?" << icon; return QImage(); } } // Find the largest size, hopefully it's the best looking. QSize sz; for (int i = 0; i < sizes.length(); ++i) { const QSize &nsz = sizes.at(i); if (nsz.width() * nsz.height() >= sz.width() * sz.height()) { sz = nsz; } } QPixmap p = icon.pixmap(sz, QIcon::Normal, st); QImage i = p.toImage().convertToFormat(QImage::Format_RGBA8888); return i; } // Convert a QIcon to a GdkPixbuf for use elsewhere. QGtkRefPtr qt_iconToPixbuf(const QIcon &icon) { QImage i = qt_getBiggestImageForIcon(icon); return qt_imageToPixbuf(i); } // Convert a QIcon to a GIcon for use elsewhere. QGtkRefPtr qt_iconToIcon(const QIcon &icon) { QImage i = qt_getBiggestImageForIcon(icon); QGtkRefPtr bytes = g_bytes_new_take(const_cast(i.constBits()), i.byteCount()); QGtkRefPtr ico = g_bytes_icon_new(bytes.get()); return ico; } // Qt uses &, gtk uses _. QString qt_convertToGtkMnemonics(const QString &text) { QString cpy = text; return cpy.replace("&", "_"); // ### too simple! need to leave &&. } Qt::KeyboardModifiers qt_convertToQtKeyboardMods(guint mask) { Qt::KeyboardModifiers mods = Qt::NoModifier; if (mask & GDK_SHIFT_MASK) mods |= Qt::ShiftModifier; if (mask & GDK_CONTROL_MASK) mods |= Qt::ControlModifier; if (mask & GDK_MOD1_MASK) mods |= Qt::AltModifier; if (mask & GDK_META_MASK) mods |= Qt::MetaModifier; #if 0 if (mask & GDK_SUPER_MASK) qDebug() << "Super"; if (mask & GDK_HYPER_MASK) qDebug() << "Hyper"; if (mask & GDK_MOD2_MASK) qDebug() << "Mod2"; if (mask & GDK_MOD3_MASK) qDebug() << "Mod3"; if (mask & GDK_MOD4_MASK) qDebug() << "Mod4"; if (mask & GDK_MOD5_MASK) qDebug() << "Mod5"; #endif return mods; } Qt::Key qt_convertToQtKey(int keyval) { switch (keyval) { case GDK_KEY_BackSpace: return Qt::Key_Backspace; case GDK_KEY_KP_Tab: case GDK_KEY_Tab: return Qt::Key_Tab; case GDK_KEY_Clear: return Qt::Key_Clear; case GDK_KEY_Return: return Qt::Key_Return; case GDK_KEY_KP_Enter: return Qt::Key_Enter; case GDK_KEY_Pause: return Qt::Key_Pause; case GDK_KEY_Scroll_Lock: return Qt::Key_ScrollLock; case GDK_KEY_Sys_Req: return Qt::Key_SysReq; case GDK_KEY_Escape: return Qt::Key_Escape; case GDK_KEY_KP_Delete: case GDK_KEY_Delete: return Qt::Key_Delete; case GDK_KEY_Multi_key: return Qt::Key_Multi_key; case GDK_KEY_Codeinput: return Qt::Key_Codeinput; case GDK_KEY_SingleCandidate: return Qt::Key_SingleCandidate; case GDK_KEY_MultipleCandidate: return Qt::Key_MultipleCandidate; case GDK_KEY_PreviousCandidate: return Qt::Key_PreviousCandidate; case GDK_KEY_Kanji: return Qt::Key_Kanji; case GDK_KEY_Muhenkan: return Qt::Key_Muhenkan; case GDK_KEY_Henkan: return Qt::Key_Henkan; case GDK_KEY_Romaji: return Qt::Key_Romaji; case GDK_KEY_Hiragana: return Qt::Key_Hiragana; case GDK_KEY_Katakana: return Qt::Key_Katakana; case GDK_KEY_Hiragana_Katakana: return Qt::Key_Hiragana_Katakana; case GDK_KEY_Zenkaku: return Qt::Key_Zenkaku; case GDK_KEY_Hankaku: return Qt::Key_Hankaku; case GDK_KEY_Zenkaku_Hankaku: return Qt::Key_Zenkaku_Hankaku; case GDK_KEY_Touroku: return Qt::Key_Touroku; case GDK_KEY_Massyo: return Qt::Key_Massyo; case GDK_KEY_Kana_Lock: return Qt::Key_Kana_Lock; case GDK_KEY_Kana_Shift: return Qt::Key_Kana_Shift; case GDK_KEY_Eisu_Shift: return Qt::Key_Eisu_Shift; case GDK_KEY_Eisu_toggle: return Qt::Key_Eisu_toggle; case GDK_KEY_KP_Home: case GDK_KEY_Home: return Qt::Key_Home; case GDK_KEY_KP_Left: case GDK_KEY_Left: return Qt::Key_Left; case GDK_KEY_KP_Up: case GDK_KEY_Up: return Qt::Key_Up; case GDK_KEY_KP_Right: case GDK_KEY_Right: return Qt::Key_Right; case GDK_KEY_KP_Down: case GDK_KEY_Down: return Qt::Key_Down; case GDK_KEY_KP_Page_Up: case GDK_KEY_Page_Up: return Qt::Key_PageUp; case GDK_KEY_KP_Page_Down: case GDK_KEY_Page_Down: return Qt::Key_PageDown; case GDK_KEY_KP_End: case GDK_KEY_End: return Qt::Key_End; case GDK_KEY_KP_Begin: case GDK_KEY_Begin: return Qt::Key_Home; case GDK_KEY_Select: return Qt::Key_Select; case GDK_KEY_Print: return Qt::Key_Print; case GDK_KEY_Execute: return Qt::Key_Execute; case GDK_KEY_KP_Insert: case GDK_KEY_Insert: return Qt::Key_Insert; case GDK_KEY_Menu: return Qt::Key_Menu; case GDK_KEY_Cancel: return Qt::Key_Cancel; case GDK_KEY_Help: return Qt::Key_Help; case GDK_KEY_Mode_switch: return Qt::Key_Mode_switch; case GDK_KEY_Num_Lock: return Qt::Key_NumLock; case GDK_KEY_F1: case GDK_KEY_KP_F1: return Qt::Key_F1; case GDK_KEY_F2: case GDK_KEY_KP_F2: return Qt::Key_F2; case GDK_KEY_F3: case GDK_KEY_KP_F3: return Qt::Key_F3; case GDK_KEY_F4: case GDK_KEY_KP_F4: return Qt::Key_F4; case GDK_KEY_F5: return Qt::Key_F5; case GDK_KEY_F6: return Qt::Key_F6; case GDK_KEY_F7: return Qt::Key_F7; case GDK_KEY_F8: return Qt::Key_F8; case GDK_KEY_F9: return Qt::Key_F9; case GDK_KEY_F10: return Qt::Key_F10; case GDK_KEY_F11: return Qt::Key_F11; case GDK_KEY_F12: return Qt::Key_F12; case GDK_KEY_F13: return Qt::Key_F13; case GDK_KEY_F14: return Qt::Key_F14; case GDK_KEY_F15: return Qt::Key_F15; case GDK_KEY_F16: return Qt::Key_F16; case GDK_KEY_F17: return Qt::Key_F17; case GDK_KEY_F18: return Qt::Key_F18; case GDK_KEY_F19: return Qt::Key_F19; case GDK_KEY_F20: return Qt::Key_F20; case GDK_KEY_F21: return Qt::Key_F21; case GDK_KEY_F22: return Qt::Key_F22; case GDK_KEY_F23: return Qt::Key_F23; case GDK_KEY_F24: return Qt::Key_F24; case GDK_KEY_F25: return Qt::Key_F25; case GDK_KEY_F26: return Qt::Key_F26; case GDK_KEY_F27: return Qt::Key_F27; case GDK_KEY_F28: return Qt::Key_F28; case GDK_KEY_F29: return Qt::Key_F29; case GDK_KEY_F30: return Qt::Key_F30; case GDK_KEY_F31: return Qt::Key_F31; case GDK_KEY_F32: return Qt::Key_F32; case GDK_KEY_F33: return Qt::Key_F33; case GDK_KEY_F34: return Qt::Key_F34; case GDK_KEY_F35: return Qt::Key_F35; case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: return Qt::Key_Shift; case GDK_KEY_Control_L: case GDK_KEY_Control_R: return Qt::Key_Control; case GDK_KEY_Caps_Lock: return Qt::Key_CapsLock; case GDK_KEY_Meta_L: case GDK_KEY_Meta_R: return Qt::Key_Meta; case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: return Qt::Key_Alt; case GDK_KEY_Super_L: return Qt::Key_Super_L; case GDK_KEY_Super_R: return Qt::Key_Super_R; case GDK_KEY_Hyper_L: return Qt::Key_Hyper_R; case GDK_KEY_Hyper_R: return Qt::Key_Hyper_R; case GDK_KEY_MonBrightnessUp: return Qt::Key_MonBrightnessUp; case GDK_KEY_MonBrightnessDown: return Qt::Key_MonBrightnessDown; case GDK_KEY_KbdLightOnOff: return Qt::Key_KeyboardLightOnOff; case GDK_KEY_KbdBrightnessUp: return Qt::Key_KeyboardBrightnessUp; case GDK_KEY_KbdBrightnessDown: return Qt::Key_KeyboardBrightnessDown; case GDK_KEY_Standby: return Qt::Key_Standby; case GDK_KEY_AudioLowerVolume: return Qt::Key_VolumeDown; case GDK_KEY_AudioMute: return Qt::Key_VolumeMute; case GDK_KEY_AudioRaiseVolume: return Qt::Key_VolumeUp; case GDK_KEY_AudioPlay: return Qt::Key_MediaPlay; case GDK_KEY_AudioStop: return Qt::Key_MediaStop; case GDK_KEY_AudioPrev: return Qt::Key_MediaPrevious; case GDK_KEY_AudioNext: return Qt::Key_MediaNext; case GDK_KEY_HomePage: return Qt::Key_HomePage; case GDK_KEY_Mail: return Qt::Key_LaunchMail; case GDK_KEY_Start: return Qt::Key_Standby; case GDK_KEY_Find: case GDK_KEY_Search: return Qt::Key_Search; case GDK_KEY_AudioRecord: case GDK_KEY_Calculator: return Qt::Key_Calculator; case GDK_KEY_Memo: return Qt::Key_Memo; case GDK_KEY_ToDoList: return Qt::Key_ToDoList; case GDK_KEY_Calendar: return Qt::Key_Calendar; case GDK_KEY_PowerDown: return Qt::Key_PowerDown; case GDK_KEY_ContrastAdjust: return Qt::Key_ContrastAdjust; case GDK_KEY_Back: return Qt::Key_Back; case GDK_KEY_Forward: return Qt::Key_Forward; case GDK_KEY_Stop: return Qt::Key_Stop; case GDK_KEY_Refresh: return Qt::Key_Refresh; case GDK_KEY_PowerOff: return Qt::Key_PowerOff; case GDK_KEY_WakeUp: return Qt::Key_WakeUp; case GDK_KEY_Eject: return Qt::Key_Eject; case GDK_KEY_ScreenSaver: return Qt::Key_ScreenSaver; case GDK_KEY_WWW: return Qt::Key_WWW; case GDK_KEY_Sleep: return Qt::Key_Sleep; case GDK_KEY_Favorites: return Qt::Key_Favorites; case GDK_KEY_AudioPause: return Qt::Key_MediaPause; case GDK_KEY_AudioMedia: return Qt::Key_LaunchMedia; case GDK_KEY_MyComputer: return Qt::Key_Launch0; case GDK_KEY_VendorHome: return Qt::Key_OfficeHome; case GDK_KEY_LightBulb: return Qt::Key_LightBulb; case GDK_KEY_Shop: return Qt::Key_Shop; case GDK_KEY_History: return Qt::Key_History; case GDK_KEY_OpenURL: return Qt::Key_OpenUrl; case GDK_KEY_AddFavorite: return Qt::Key_AddFavorite; case GDK_KEY_HotLinks: return Qt::Key_HotLinks; case GDK_KEY_BrightnessAdjust: return Qt::Key_BrightnessAdjust; case GDK_KEY_Finance: return Qt::Key_Finance; case GDK_KEY_Community: return Qt::Key_Community; case GDK_KEY_AudioRewind: return Qt::Key_AudioRewind; case GDK_KEY_BackForward: return Qt::Key_BackForward; case GDK_KEY_Launch0: return Qt::Key_Launch0; case GDK_KEY_Launch1: return Qt::Key_Launch1; case GDK_KEY_Launch2: return Qt::Key_Launch2; case GDK_KEY_Launch3: return Qt::Key_Launch3; case GDK_KEY_Launch4: return Qt::Key_Launch4; case GDK_KEY_Launch5: return Qt::Key_Launch5; case GDK_KEY_Launch6: return Qt::Key_Launch6; case GDK_KEY_Launch7: return Qt::Key_Launch7; case GDK_KEY_Launch8: return Qt::Key_Launch8; case GDK_KEY_Launch9: return Qt::Key_Launch9; case GDK_KEY_LaunchA: return Qt::Key_LaunchA; case GDK_KEY_LaunchB: return Qt::Key_LaunchB; case GDK_KEY_LaunchC: return Qt::Key_LaunchC; case GDK_KEY_LaunchD: return Qt::Key_LaunchD; case GDK_KEY_LaunchE: return Qt::Key_LaunchE; case GDK_KEY_LaunchF: return Qt::Key_LaunchF; case GDK_KEY_ApplicationLeft: return Qt::Key_ApplicationLeft; case GDK_KEY_ApplicationRight: return Qt::Key_ApplicationRight; case GDK_KEY_Book: return Qt::Key_Book; case GDK_KEY_CD: return Qt::Key_CD; case GDK_KEY_Close: return Qt::Key_Close; case GDK_KEY_Copy: return Qt::Key_Copy; case GDK_KEY_Cut: return Qt::Key_Cut; case GDK_KEY_Display: return Qt::Key_Display; case GDK_KEY_DOS: return Qt::Key_DOS; case GDK_KEY_Documents: return Qt::Key_Documents; case GDK_KEY_Excel: return Qt::Key_Excel; case GDK_KEY_Explorer: return Qt::Key_Explorer; case GDK_KEY_Game: return Qt::Key_Game; case GDK_KEY_Go: return Qt::Key_Go; case GDK_KEY_iTouch: return Qt::Key_iTouch; case GDK_KEY_LogOff: return Qt::Key_LogOff; case GDK_KEY_Market: return Qt::Key_Market; case GDK_KEY_Meeting: return Qt::Key_Meeting; case GDK_KEY_MenuKB: return Qt::Key_MenuKB; case GDK_KEY_MenuPB: return Qt::Key_MenuPB; case GDK_KEY_MySites: return Qt::Key_MySites; case GDK_KEY_News: return Qt::Key_News; case GDK_KEY_OfficeHome: return Qt::Key_OfficeHome; case GDK_KEY_Option: return Qt::Key_Option; case GDK_KEY_Paste: return Qt::Key_Paste; case GDK_KEY_Phone: return Qt::Key_Phone; case GDK_KEY_Reply: return Qt::Key_Reply; case GDK_KEY_Reload: return Qt::Key_Reload; case GDK_KEY_RotateWindows: return Qt::Key_RotateWindows; case GDK_KEY_RotationPB: return Qt::Key_RotationPB; case GDK_KEY_RotationKB: return Qt::Key_RotationKB; case GDK_KEY_Save: return Qt::Key_Save; case GDK_KEY_Send: return Qt::Key_Send; case GDK_KEY_Spell: return Qt::Key_Spell; case GDK_KEY_SplitScreen: return Qt::Key_SplitScreen; case GDK_KEY_Support: return Qt::Key_Support; case GDK_KEY_TaskPane: return Qt::Key_TaskPane; case GDK_KEY_Terminal: return Qt::Key_Terminal; case GDK_KEY_Tools: return Qt::Key_Tools; case GDK_KEY_Travel: return Qt::Key_Travel; case GDK_KEY_Video: return Qt::Key_Video; case GDK_KEY_Word: return Qt::Key_Word; case GDK_KEY_Xfer: return Qt::Key_Xfer; case GDK_KEY_ZoomIn: return Qt::Key_ZoomIn; case GDK_KEY_ZoomOut: return Qt::Key_ZoomOut; case GDK_KEY_Away: return Qt::Key_Away; case GDK_KEY_Messenger: return Qt::Key_Messenger; case GDK_KEY_WebCam: return Qt::Key_WebCam; case GDK_KEY_MailForward: return Qt::Key_MailForward; case GDK_KEY_Pictures: return Qt::Key_Pictures; case GDK_KEY_Music: return Qt::Key_Music; case GDK_KEY_Battery: return Qt::Key_Battery; case GDK_KEY_Bluetooth: return Qt::Key_Bluetooth; case GDK_KEY_WLAN: return Qt::Key_WLAN; case GDK_KEY_UWB: return Qt::Key_UWB; case GDK_KEY_AudioForward: return Qt::Key_AudioForward; case GDK_KEY_AudioRepeat: return Qt::Key_AudioRepeat; case GDK_KEY_AudioRandomPlay: return Qt::Key_AudioRandomPlay; case GDK_KEY_Subtitle: return Qt::Key_Subtitle; case GDK_KEY_AudioCycleTrack: return Qt::Key_AudioCycleTrack; case GDK_KEY_Time: return Qt::Key_Time; case GDK_KEY_View: return Qt::Key_View; case GDK_KEY_TopMenu: return Qt::Key_TopMenu; case GDK_KEY_Suspend: return Qt::Key_Suspend; case GDK_KEY_Hibernate: return Qt::Key_Hibernate; case GDK_KEY_ClearGrab: return Qt::Key_ClearGrab; } if (keyval < 256) { if (isprint(keyval)) keyval = toupper(keyval); } return Qt::Key(keyval); } // ### keyvalToQtKey in reverse, ugh // ### finish guint qt_convertToGdkKeyval(Qt::Key qKey) { switch (qKey) { case Qt::Key_Insert: return GDK_KEY_Insert; case Qt::Key_Delete: return GDK_KEY_Delete; case Qt::Key_Left: return GDK_KEY_Left; case Qt::Key_Right: return GDK_KEY_Right; case Qt::Key_Up: return GDK_KEY_Up; case Qt::Key_Down: return GDK_KEY_Down; case Qt::Key_Tab: return GDK_KEY_Tab; case Qt::Key_F1: return GDK_KEY_F1; case Qt::Key_F2: return GDK_KEY_F2; case Qt::Key_F3: return GDK_KEY_F3; case Qt::Key_F4: return GDK_KEY_F4; case Qt::Key_F5: return GDK_KEY_F5; case Qt::Key_F6: return GDK_KEY_F6; case Qt::Key_F7: return GDK_KEY_F7; case Qt::Key_F8: return GDK_KEY_F8; case Qt::Key_F9: return GDK_KEY_F9; case Qt::Key_F10: return GDK_KEY_F10; case Qt::Key_F11: return GDK_KEY_F11; case Qt::Key_F12: return GDK_KEY_F12; case Qt::Key_F13: return GDK_KEY_F13; case Qt::Key_F14: return GDK_KEY_F14; case Qt::Key_F15: return GDK_KEY_F15; case Qt::Key_F16: return GDK_KEY_F16; case Qt::Key_F17: return GDK_KEY_F17; case Qt::Key_F18: return GDK_KEY_F18; case Qt::Key_F19: return GDK_KEY_F19; case Qt::Key_F20: return GDK_KEY_F20; case Qt::Key_F21: return GDK_KEY_F21; } return (guint)qKey; } Qt::MouseButton qt_convertGButtonToQButton(guint button) { Qt::MouseButton b = Qt::NoButton; switch (button) { case 1: b = Qt::LeftButton; break; case 2: b = Qt::MiddleButton; break; case 3: b = Qt::RightButton; break; case 4: b = Qt::ExtraButton1; break; case 5: b = Qt::ExtraButton2; break; case 6: b = Qt::ExtraButton3; break; case 7: b = Qt::ExtraButton4; break; case 8: b = Qt::ExtraButton5; break; case 9: b = Qt::ExtraButton6; break; case 10: b = Qt::ExtraButton7; break; case 11: b = Qt::ExtraButton8; break; case 12: b = Qt::ExtraButton9; break; case 13: b = Qt::ExtraButton10; break; case 14: b = Qt::ExtraButton11; break; case 15: b = Qt::ExtraButton12; break; case 16: b = Qt::ExtraButton13; break; case 17: b = Qt::ExtraButton14; break; case 18: b = Qt::ExtraButton15; break; case 19: b = Qt::ExtraButton16; break; case 20: b = Qt::ExtraButton17; break; case 21: b = Qt::ExtraButton18; break; case 22: b = Qt::ExtraButton19; break; case 23: b = Qt::ExtraButton20; break; case 24: b = Qt::ExtraButton21; break; case 25: b = Qt::ExtraButton22; break; case 26: b = Qt::ExtraButton23; break; case 27: b = Qt::ExtraButton24; break; default: qWarning() << "Unrecognized button" << button; } return b; } Qt::TouchPointState qt_convertToQtTouchPointState(GdkEventType type) { switch (type) { case GDK_TOUCH_BEGIN: return Qt::TouchPointPressed; case GDK_TOUCH_UPDATE: return Qt::TouchPointMoved; case GDK_TOUCH_END: case GDK_TOUCH_CANCEL: return Qt::TouchPointReleased; default: Q_UNREACHABLE(); } } cairo_region_t *qt_convertToCairoRegion(const QRegion ®ion) { cairo_region_t *r = cairo_region_create(); for (const QRect &qrect : region.rects()) { cairo_rectangle_int_t rect = { qrect.x(), qrect.y(), qrect.width(), qrect.height() }; cairo_region_union_rectangle(r, &rect); } return r; } ================================================ FILE: src/platform-plugin/qgtkhelpers.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkmenuitem.h" #include "qgtkrefptr.h" #include #ifndef QGTKHELPERS_H #define QGTKHELPERS_H QT_BEGIN_NAMESPACE QGtkRefPtr qt_imageToPixbuf(const QImage &image); QGtkRefPtr qt_pixmapToPixbuf(const QPixmap &pixmap); QImage qt_pixbufToImage(const QGtkRefPtr &pixbuf); QGtkRefPtr qt_iconToPixbuf(const QIcon &icon); QGtkRefPtr qt_iconToIcon(const QIcon &icon); QString qt_convertToGtkMnemonics(const QString &text); Qt::KeyboardModifiers qt_convertToQtKeyboardMods(guint mask); Qt::Key qt_convertToQtKey(int keyval); guint qt_convertToGdkKeyval(Qt::Key qKey); Qt::MouseButton qt_convertGButtonToQButton(guint button); Qt::TouchPointState qt_convertToQtTouchPointState(GdkEventType type); cairo_region_t *qt_convertToCairoRegion(const QRegion ®ion); QT_END_NAMESPACE #endif ================================================ FILE: src/platform-plugin/qgtkintegration.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkintegration.h" #include "qgtkbackingstore.h" #include "qgtkwindow.h" #include "qgtkscreen.h" #include "qgtktheme.h" #include "qgtkopenglcontext.h" #include "qgtkeventdispatcher.h" #include "qgtkclipboard.h" #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) #include #include #else #include #include #endif #include #include #ifdef GDK_WINDOWING_WAYLAND #include #include static EGLDisplay createWaylandEGLDisplay(wl_display *display); #endif #ifdef GDK_WINDOWING_X11 # include # include #endif #include "CSystrace.h" QT_BEGIN_NAMESPACE class QCoreTextFontEngine; void monitor_added(GdkDisplay *, GdkMonitor *monitor, gpointer integration) { QGtkIntegration *ig = static_cast(integration); ig->onMonitorAdded(monitor); } void monitor_removed(GdkDisplay *, GdkMonitor *monitor, gpointer integration) { QGtkIntegration *ig = static_cast(integration); ig->onMonitorRemoved(monitor); } QGtkIntegration::QGtkIntegration(const QStringList &) : m_services(new QGtkServices) , m_fontDatabase(new QGenericUnixFontDatabase) , m_eglDisplay(nullptr) { systrace_init(); gtk_init(NULL, NULL); notify_init(qApp->applicationName().toUtf8().constData()); // Set up screens m_display = gdk_display_get_default(); g_signal_connect(m_display, "monitor-added", G_CALLBACK(monitor_added), this); g_signal_connect(m_display, "monitor-removed", G_CALLBACK(monitor_removed), this); int num_monitors = gdk_display_get_n_monitors(m_display); for (int i = 0; i < num_monitors; i++) { GdkMonitor *monitor = gdk_display_get_monitor(m_display, i); monitor_added(m_display, monitor, this); } #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY(m_display)) { wl_display *wldisplay = gdk_wayland_display_get_wl_display(GDK_WAYLAND_DISPLAY(m_display)); m_eglDisplay = createWaylandEGLDisplay(wldisplay); Q_ASSERT(m_eglDisplay); } else #endif #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_DISPLAY(m_display)) { qWarning() << "Application is running under X11. While this may work, it is experimental."; qWarning() << "Run under Wayland for best results."; } else #endif qWarning("GTK platform does not support this display backend; GL contexts will fail"); } QGtkIntegration::~QGtkIntegration() { notify_uninit(); #ifdef GDK_WINDOWING_WAYLAND if (m_eglDisplay) { eglTerminate(m_eglDisplay); } #endif systrace_deinit(); } void QGtkIntegration::onMonitorAdded(GdkMonitor *monitor) { bool isPrimary = gdk_monitor_is_primary(monitor) || m_screens.count() == 0; qDebug() << "Added " << monitor << " isPrimary " << isPrimary; QGtkScreen *screen = new QGtkScreen(monitor); screen->setPrimary(isPrimary); m_screens.append(screen); #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handleScreenAdded(screen, isPrimary); #else screenAdded(screen, isPrimary); #endif if (isPrimary) { qDebug() << "Changed primary screen on add"; // clear old screens for (int i = 0; i < m_screens.count(); i++) { QGtkScreen *os = m_screens.at(i); screen->setPrimary(os == screen); } #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handlePrimaryScreenChanged(screen); #else setPrimaryScreen(screen); #endif } } void QGtkIntegration::onMonitorRemoved(GdkMonitor *monitor) { qDebug() << "Removed " << monitor; for (int i = 0; i < m_screens.count(); ++i) { QGtkScreen *screen = m_screens.at(i); if (screen->monitor() == monitor) { qDebug() << "Removing QGtkScreen " << screen << screen->isPrimary(); bool wasPrimary = screen->isPrimary(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handleScreenRemoved(screen); #else removeScreen(screen->screen()); #endif m_screens.removeAt(i); if (wasPrimary) { qDebug() << "Changed primary screen on remove"; GdkMonitor *primaryScreen = gdk_display_get_primary_monitor(m_display); QGtkScreen *newPrimary = nullptr; for (int i = 0; i < m_screens.count(); ++i) { QGtkScreen *screen = m_screens.at(i); screen->setPrimary(false); if (screen->monitor() == primaryScreen || primaryScreen == nullptr) { qDebug() << "Changed primary screen to index " << i << " ptr " << screen; newPrimary = screen; } } #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handlePrimaryScreenChanged(newPrimary); #else setPrimaryScreen(newPrimary); #endif newPrimary->setPrimary(true); } return; } } } QPlatformOpenGLContext *QGtkIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY(m_display)) { return new QGtkWaylandContext(context->format(), static_cast(context->shareHandle())); } #endif #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_DISPLAY(m_display)) { return new QGtkX11Context(context->format(), static_cast(context->shareHandle())); } #endif return nullptr; } bool QGtkIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { case ThreadedPixmaps: case MultipleWindows: case OpenGL: case ThreadedOpenGL: case RasterGLSurface: case WindowManagement: return true; default: return QPlatformIntegration::hasCapability(cap); } } QPlatformClipboard *QGtkIntegration::clipboard() const { if (m_clipboard == nullptr) { QGtkIntegration *that = const_cast(this); that->m_clipboard = new QGtkClipboard(that); } return this->m_clipboard; } QPlatformFontDatabase *QGtkIntegration::fontDatabase() const { return m_fontDatabase.data(); } QStringList QGtkIntegration::themeNames() const { return QStringList(QLatin1String(QGtkTheme::name)); } QPlatformTheme *QGtkIntegration::createPlatformTheme(const QString &name) const { if (name == QLatin1String(QGtkTheme::name)) return new QGtkTheme; return QPlatformIntegration::createPlatformTheme(name); } QPlatformServices *QGtkIntegration::services() const { return m_services.data(); } QPlatformNativeInterface *QGtkIntegration::nativeInterface() const { return const_cast(this); } void *QGtkIntegration::nativeResourceForIntegration(const QByteArray &resource) { void *result = 0; if (resource == "egldisplay") { result = reinterpret_cast(m_eglDisplay); } else if (resource == "connection") { #ifdef GDK_WINDOWING_X11 static bool xcb_warned = false; if (!xcb_warned) { qWarning() << "XCB connection requested; this is experimental, and may not work well."; xcb_warned = true; } Display *dpy = nullptr; if (GDK_IS_X11_DISPLAY(m_display)) { dpy = gdk_x11_display_get_xdisplay(m_display); } else { qWarning() << "Can't get XCB connection, GDK_BACKEND is not X11."; } xcb_connection_t *conn = XGetXCBConnection(dpy); result = reinterpret_cast(conn); #endif } else if (resource == "display") { #ifdef GDK_WINDOWING_X11 static bool x11_warned = false; if (!x11_warned) { qWarning() << "X11 display handle; this is experimental, and may not work well."; x11_warned = true; } Display *dpy = nullptr; if (GDK_IS_X11_DISPLAY(m_display)) { dpy = gdk_x11_display_get_xdisplay(m_display); } else { qWarning() << "Can't get XCB connection, GDK_BACKEND is not X11."; } result = reinterpret_cast(dpy); #endif } else { qWarning() << "Unimplemented request for " << resource; } return result; } void *QGtkIntegration::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) { void *result = 0; QByteArray res = resource.toLower(); // ### notify on change if (res == "antialiasingenabled") { int aa = -1; g_object_get(gtk_settings_get_default(), "gtk-xft-antialias", &aa, NULL); result = reinterpret_cast(aa + 1); } else if (res == "subpixeltype") { GtkSettings *s = gtk_settings_get_default(); gchararray value; g_object_get(s, "gtk-xft-rgba", &value, NULL); QString qtVal = QString::fromUtf8(value); g_free(value); QFontEngine::SubpixelAntialiasingType type = QFontEngine::SubpixelAntialiasingType(-1); if (qtVal == "none") { type = QFontEngine::Subpixel_None; } else if (qtVal == "rgb") { type = QFontEngine::Subpixel_RGB; } else if (qtVal == "bgr") { type = QFontEngine::Subpixel_BGR; } else if (qtVal == "vrgb") { type = QFontEngine::Subpixel_VRGB; } else if (qtVal == "vbgr") { type = QFontEngine::Subpixel_VBGR; } result = reinterpret_cast(type + 1); } else if (resource == "rootwindow") { #ifdef GDK_WINDOWING_X11 static bool rootwin_warned = false; if (!rootwin_warned) { qWarning() << "X root window requested; this is experimental, and may not work well."; rootwin_warned = true; } Display *dpy = nullptr; xcb_screen_t *screen = nullptr; if (GDK_IS_X11_DISPLAY(m_display)) { dpy = gdk_x11_display_get_xdisplay(m_display); xcb_connection_t *conn = XGetXCBConnection(dpy); // use the first screen... hopefully this is okay? sigh... screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; } else { qWarning() << "Can't get root X window, GDK_BACKEND is not X11."; } result = reinterpret_cast(screen ? screen->root : 0); #endif } else { qWarning() << "Unimplemented request for " << resource << " on " << screen; } return result; } void *QGtkIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window) { void *result = 0; if (resource == "gtkwindow") { return static_cast(window->handle())->gtkWindow().get(); } qWarning() << "Unimplemented request for " << resource << " on " << window; return result; } void *QGtkIntegration::nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) { void *result = 0; if (!context->handle()) { return result; } result = static_cast(context->handle())->nativeResource(resource); if (!result) { qWarning() << "Unimplemented request for " << resource << " on " << context; } return result; } QPlatformNativeInterface::NativeResourceForContextFunction QGtkIntegration::nativeResourceFunctionForContext(const QByteArray &resource) { qWarning() << "Unimplemented request for " << resource; return 0; } QPlatformWindow *QGtkIntegration::createPlatformWindow(QWindow *window) const { Q_UNUSED(window); QGtkWindow *w = new QGtkWindow(window); return w; } QPlatformBackingStore *QGtkIntegration::createPlatformBackingStore(QWindow *window) const { return new QGtkBackingStore(window); } QAbstractEventDispatcher *QGtkIntegration::createEventDispatcher() const { return new QGtkEventDispatcher; } QGtkIntegration *QGtkIntegration::instance() { return static_cast(QGuiApplicationPrivate::platformIntegration()); } #ifdef GDK_WINDOWING_WAYLAND static EGLDisplay createWaylandEGLDisplay(wl_display *display) { eglBindAPI(EGL_OPENGL_API); EGLDisplay dpy = eglGetDisplay((EGLNativeDisplayType)display); if (dpy == EGL_NO_DISPLAY) { qWarning() << "eglGetDisplay failed"; return dpy; } if (!eglInitialize(dpy, NULL, NULL)) { qWarning() << "eglInitialize failed"; return EGL_NO_DISPLAY; } return dpy; } #endif EGLDisplay QGtkIntegration::eglDisplay() const { return m_eglDisplay; } QT_END_NAMESPACE ================================================ FILE: src/platform-plugin/qgtkintegration.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QPLATFORMINTEGRATION_GTK_H #define QPLATFORMINTEGRATION_GTK_H #include "qgtkservices.h" #include #include #include #include #include #include typedef void *EGLDisplay; QT_BEGIN_NAMESPACE class QTouchDevice; class QGtkScreen; class QGtkClipboard; class QGtkIntegration : public QPlatformIntegration, public QPlatformNativeInterface { public: explicit QGtkIntegration(const QStringList ¶meters); ~QGtkIntegration(); QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; bool hasCapability(QPlatformIntegration::Capability cap) const override; QPlatformFontDatabase *fontDatabase() const override; QPlatformClipboard *clipboard() const override; QStringList themeNames() const override; QPlatformTheme *createPlatformTheme(const QString &name) const override; QPlatformServices *services() const override; QPlatformNativeInterface *nativeInterface() const override; // QPlatformNativeInterface void *nativeResourceForIntegration(const QByteArray &resource) override; void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override; void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) override; NativeResourceForContextFunction nativeResourceFunctionForContext(const QByteArray &resource) override; QPlatformWindow *createPlatformWindow(QWindow *window) const override; QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; QAbstractEventDispatcher *createEventDispatcher() const override; static QGtkIntegration *instance(); void onMonitorAdded(GdkMonitor *monitor); void onMonitorRemoved(GdkMonitor *monitor); GtkApplication *application() const; EGLDisplay eglDisplay() const; private: QScopedPointer m_services; QScopedPointer m_fontDatabase; GdkDisplay *m_display; QVector m_arguments; /* must remain allocated for gdk's sake */ QVector m_screens; QGtkClipboard *m_clipboard = nullptr; EGLDisplay m_eglDisplay; // non-null for wayland platforms }; QT_END_NAMESPACE #endif ================================================ FILE: src/platform-plugin/qgtkmenu.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include "qgtkmenu.h" #include "qgtkmenuitem.h" #include "qgtkhelpers.h" #include #include #include #include Q_LOGGING_CATEGORY(lcMenu, "qt.qpa.gtk.menu"); QGtkMenu::QGtkMenu() : m_tag((qintptr)this) { } QGtkMenu::~QGtkMenu() { } void QGtkMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) { QGtkMenuItem *mi = static_cast(menuItem); QGtkMenuItem *bi = static_cast(before); int idx = m_items.indexOf(bi); if (idx < 0) { m_items.append(mi); } else { m_items.insert(idx, mi); } if (mi->menu()) { connect(mi->menu(), &QGtkMenu::updated, this, &QGtkMenu::updated, Qt::UniqueConnection); } qCDebug(lcMenu) << "Added menu item " << mi << " before " << bi << " at " << idx; Q_EMIT updated(); } void QGtkMenu::removeMenuItem(QPlatformMenuItem *menuItem) { QGtkMenuItem *mi = static_cast(menuItem); int idx = m_items.indexOf(mi); m_items.removeAt(idx); m_items.removeAll(0); // if it was deleted, remove those too. if (mi->menu()) { disconnect(mi->menu(), &QGtkMenu::updated, this, &QGtkMenu::updated); } Q_EMIT updated(); } void QGtkMenu::syncMenuItem(QPlatformMenuItem *menuItem) { QGtkMenu *m = static_cast(menuItem)->menu(); if (m) { connect(m, &QGtkMenu::updated, this, &QGtkMenu::updated, Qt::UniqueConnection); } Q_EMIT updated(); } void QGtkMenu::syncSeparatorsCollapsible(bool enable) { Q_UNUSED(enable); } void QGtkMenu::setTag(quintptr tag) { m_tag = tag; Q_EMIT updated(); } quintptr QGtkMenu::tag()const { return m_tag; } void QGtkMenu::setText(const QString &text) { m_text = text; Q_EMIT updated(); } void QGtkMenu::setIcon(const QIcon &icon) { Q_UNUSED(icon); } void QGtkMenu::setEnabled(bool enabled) { m_enabled = enabled; Q_EMIT updated(); } bool QGtkMenu::isEnabled() const { return m_enabled; } void QGtkMenu::setVisible(bool visible) { //aboutToShow, aboutToHide signals Q_UNUSED(visible); m_visible = visible; Q_EMIT updated(); } void QGtkMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) { Q_UNUSED(item); if (m_popup) { dismiss(); } Q_EMIT aboutToShow(); QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height()); if (parentWindow) pos = parentWindow->mapToGlobal(pos); m_popup = gtkMenu(); GdkRectangle gRect { pos.x(), pos.y(), targetRect.width(), targetRect.height() }; gtk_menu_popup_at_rect( m_popup.get(), gtk_widget_get_window(static_cast(parentWindow->handle())->gtkWindow().get()), &gRect, GdkGravity(GDK_GRAVITY_NORTH_WEST), GdkGravity(GDK_GRAVITY_NORTH_WEST), NULL ); connect(qGuiApp, &QGuiApplication::focusObjectChanged, this, &QGtkMenu::dismiss); } void QGtkMenu::dismiss() { Q_EMIT aboutToHide(); if (m_popup) { gtk_menu_popdown(m_popup.get()); gtk_widget_destroy(GTK_WIDGET(m_popup.get())); m_popup = nullptr; } disconnect(qGuiApp, &QGuiApplication::focusObjectChanged, this, &QGtkMenu::dismiss); } QPlatformMenuItem *QGtkMenu::menuItemAt(int position) const { int idx = 0; while (position >= 0 && idx < m_items.size()) { if (m_items.at(idx)) { position--; } idx++; } if (idx >= 0 && idx < m_items.size()) return m_items.at(idx); return nullptr; } QPlatformMenuItem *QGtkMenu::menuItemForTag(quintptr tag) const { for (QGtkMenuItem *item : qAsConst(m_items)) { if (item && item->tag() == tag) { return item; } } return nullptr; } QGtkRefPtr QGtkMenu::gtkMenuItem() const { QGtkRefPtr mi = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(qt_convertToGtkMnemonics(m_text).toUtf8().constData())); gtk_menu_item_set_submenu(mi.get(), GTK_WIDGET(gtkMenu().get())); gtk_widget_set_sensitive(GTK_WIDGET(mi.get()), m_enabled); gtk_widget_set_visible(GTK_WIDGET(mi.get()), m_visible); return mi; } QGtkRefPtr QGtkMenu::gtkMenu() const { QGtkRefPtr menu = GTK_MENU(gtk_menu_new()); for (QGtkMenuItem *i : m_items) { if (i) gtk_menu_shell_append(GTK_MENU_SHELL(menu.get()), i->gtkMenuItem().get()); } return menu; } QVector> QGtkMenu::items() const { return m_items; } ================================================ FILE: src/platform-plugin/qgtkmenu.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKMENU_H #define QGTKMENU_H #include "qgtkrefptr.h" #include #include QT_BEGIN_NAMESPACE class QGtkMenuItem; class QGtkMenu : public QPlatformMenu { Q_OBJECT public: QGtkMenu(); ~QGtkMenu(); void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; void removeMenuItem(QPlatformMenuItem *menuItem) override; void syncMenuItem(QPlatformMenuItem *menuItem) override; void syncSeparatorsCollapsible(bool enable) override; void setTag(quintptr tag) override; quintptr tag()const override; void setText(const QString &text) override; void setIcon(const QIcon &icon) override; void setEnabled(bool enabled) override; bool isEnabled() const override; void setVisible(bool visible) override; void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) override; void dismiss() override; QPlatformMenuItem *menuItemAt(int position) const override; QPlatformMenuItem *menuItemForTag(quintptr tag) const override; QGtkRefPtr gtkMenu() const; QGtkRefPtr gtkMenuItem() const; QVector> items() const; Q_SIGNALS: void updated(); private: QVector> m_items; QGtkRefPtr m_popup = nullptr; bool m_enabled = true; bool m_visible = true; QString m_text; qintptr m_tag; }; QT_END_NAMESPACE #endif // QGTKMENU_H ================================================ FILE: src/platform-plugin/qgtkmenubar.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkmenubar.h" #include "qgtkmenu.h" #include "qgtkmenuitem.h" #include "qgtkwindow.h" #include #include Q_LOGGING_CATEGORY(lcMenuBar, "qt.qpa.gtk.menubar"); QGtkMenuBar::QGtkMenuBar() { connect(this, &QGtkMenuBar::updated, this, &QGtkMenuBar::queueRegenerate); } QGtkMenuBar::~QGtkMenuBar() { } void QGtkMenuBar::insertMenu(QPlatformMenu *menu, QPlatformMenu *before) { QGtkMenu *m = static_cast(menu); QGtkMenu *b = static_cast(before); Q_ASSERT(m && !m_items.contains(m)); Q_ASSERT(!b || m_items.contains(b)); int idx = m_items.indexOf(b); qCDebug(lcMenuBar) << "Inserting menu " << m << idx; if (idx < 0) { m_items.append(m); } else { m_items.insert(idx, m); } connect(m, &QGtkMenu::updated, this, &QGtkMenuBar::queueRegenerate); syncMenu(menu); Q_EMIT updated(); } void QGtkMenuBar::removeMenu(QPlatformMenu *menu) { QGtkMenu *m = static_cast(menu); int idx = m_items.indexOf(m); Q_ASSERT(idx >= 0); qCDebug(lcMenuBar) << "Removing menu " << m_items.at(idx) << idx; m_items.removeAt(idx); m_items.removeAll(0); // if it was deleted, remove nulls too. disconnect(m, &QGtkMenu::updated, this, &QGtkMenuBar::queueRegenerate); Q_EMIT updated(); } void QGtkMenuBar::syncMenu(QPlatformMenu *menuItem) { QGtkMenu *menu = static_cast(menuItem); for (QGtkMenuItem *item : menu->items()) { if (item) menu->syncMenuItem(item); } } void QGtkMenuBar::queueRegenerate() { if (m_regenerateQueued) { return; } QMetaObject::invokeMethod(this, "regenerate", Qt::QueuedConnection); m_regenerateQueued = true; } void QGtkMenuBar::regenerate() { m_regenerateQueued = false; GtkContainer *omb = GTK_CONTAINER(m_menubar.get()); GList *children = gtk_container_get_children(omb); for (GList *iter = children; iter != NULL; iter = g_list_next(iter)) { GtkWidget *menuChild = (GtkWidget*)iter->data; gtk_container_remove(omb, menuChild); } g_list_free(children); for (QGtkMenu *menu : m_items) { if (menu) gtk_menu_shell_append(GTK_MENU_SHELL(m_menubar.get()), GTK_WIDGET(menu->gtkMenuItem().get())); } } void QGtkMenuBar::handleReparent(QWindow *newParentWindow) { QGtkRefPtr oldMenuBar = m_menubar; if (!newParentWindow) { m_menubar.reset(nullptr); } else { QGtkWindow *w = static_cast(newParentWindow->handle()); if (!w) { // force creation of pwin newParentWindow->create(); } w = static_cast(newParentWindow->handle()); m_menubar = w->gtkMenuBar(); } if (oldMenuBar) { GtkContainer *omb = GTK_CONTAINER(oldMenuBar.get()); GtkContainer *nmb = GTK_CONTAINER(m_menubar.get()); GList *children = gtk_container_get_children(omb); for (GList *iter = children; iter != NULL; iter = g_list_next(iter)) { GtkWidget *menuChild = (GtkWidget*)iter->data; g_object_ref(menuChild); // temporaray ref, to save it past remove() gtk_container_remove(omb, menuChild); if (m_menubar.get()) { gtk_container_add(nmb, menuChild); } g_object_unref(menuChild); } g_list_free(children); } } QPlatformMenu *QGtkMenuBar::menuForTag(quintptr tag) const { for (QGtkMenu *menu : qAsConst(m_items)) { if (menu && menu->tag() == tag) { return menu; } } return nullptr; } QPlatformMenu *QGtkMenuBar::createMenu() const { return new QGtkMenu; } ================================================ FILE: src/platform-plugin/qgtkmenubar.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKMENUBAR_H #define QGTKMENUBAR_H #include "qgtkrefptr.h" #include #include QT_BEGIN_NAMESPACE class QGtkMenu; class QGtkMenuBar : public QPlatformMenuBar { Q_OBJECT public: QGtkMenuBar(); ~QGtkMenuBar(); void insertMenu(QPlatformMenu *menu, QPlatformMenu *before) override; void removeMenu(QPlatformMenu *menu) override; void syncMenu(QPlatformMenu *menuItem) override; void handleReparent(QWindow *newParentWindow) override; QPlatformMenu *menuForTag(quintptr tag) const override; QPlatformMenu *createMenu() const override; Q_SIGNALS: void updated(); private Q_SLOTS: void queueRegenerate(); void regenerate(); private: QGtkRefPtr m_menubar; QVector> m_items; bool m_regenerateQueued = false; }; QT_END_NAMESPACE #endif // QGTKMENUBAR ================================================ FILE: src/platform-plugin/qgtkmenuitem.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkmenuitem.h" #include "qgtkhelpers.h" #include "qgtkmenu.h" #include static void select_cb(GtkMenuItem *, gpointer qgtkMenuItem) { QGtkMenuItem *gm = static_cast(qgtkMenuItem); gm->emitSelect(); } static void activate_cb(GtkMenuItem *, gpointer qgtkMenuItem) { QGtkMenuItem *gm = static_cast(qgtkMenuItem); gm->emitActivate(); } QGtkMenuItem::QGtkMenuItem() : m_tag((qintptr)this) { } QGtkMenuItem::~QGtkMenuItem() { } void QGtkMenuItem::setTag(quintptr tag) { m_tag = tag; Q_EMIT updated(); } quintptr QGtkMenuItem::tag()const { return m_tag; } void QGtkMenuItem::setText(const QString &text) { m_text = qt_convertToGtkMnemonics(text); Q_EMIT updated(); } void QGtkMenuItem::setIcon(const QIcon &icon) { Q_UNUSED(icon); } void QGtkMenuItem::setMenu(QPlatformMenu *pmenu) { QGtkMenu *childMenu = static_cast(pmenu); m_childMenu = childMenu; Q_EMIT updated(); } void QGtkMenuItem::setVisible(bool isVisible) { m_visible = isVisible; Q_EMIT updated(); } void QGtkMenuItem::setIsSeparator(bool isSeparator) { m_isSeparator = isSeparator; Q_EMIT updated(); } void QGtkMenuItem::setFont(const QFont &font) { Q_UNUSED(font); } void QGtkMenuItem::setRole(MenuRole role) { Q_UNUSED(role); } void QGtkMenuItem::setCheckable(bool checkable) { m_checkable = checkable; Q_EMIT updated(); } void QGtkMenuItem::setChecked(bool isChecked) { m_checked = isChecked; Q_EMIT updated(); } void QGtkMenuItem::setShortcut(const QKeySequence& shortcut) { m_shortcut = shortcut; Q_EMIT updated(); } void QGtkMenuItem::setEnabled(bool enabled) { m_enabled = enabled; Q_EMIT updated(); } void QGtkMenuItem::setIconSize(int size) { Q_UNUSED(size); } void QGtkMenuItem::setNativeContents(WId item) { Q_UNUSED(item); } void QGtkMenuItem::setHasExclusiveGroup(bool hasExclusiveGroup) { m_hasExclusiveGroup = hasExclusiveGroup; Q_EMIT updated(); } QGtkRefPtr QGtkMenuItem::gtkMenuItem() const { QGtkRefPtr ret; if (m_isSeparator) { ret = gtk_separator_menu_item_new(); } else if (m_childMenu) { QGtkRefPtr mi = m_childMenu->gtkMenuItem(); //g_signal_connect(mi, "select", G_CALLBACK(select_cb), const_cast(this)); //g_signal_connect(mi, "activate", G_CALLBACK(activate_cb), const_cast(this)); // stick our title on it GtkWidget *child = gtk_bin_get_child(GTK_BIN(mi.get())); gtk_label_set_markup_with_mnemonic(GTK_LABEL(child), m_text.toUtf8().constData()); gtk_widget_set_sensitive(GTK_WIDGET(mi.get()), m_enabled); ret = GTK_WIDGET(mi.get()); } else { GtkWidget *mi = nullptr; if (m_checkable) { mi = gtk_check_menu_item_new_with_mnemonic(m_text.toUtf8().constData()); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), m_checked); } else { mi = gtk_menu_item_new_with_mnemonic(m_text.toUtf8().constData()); } if (GTK_IS_CHECK_MENU_ITEM(mi)) { g_object_set(mi, "draw-as-radio", m_hasExclusiveGroup, NULL); } gtk_widget_set_sensitive(mi, m_enabled); g_signal_connect(mi, "select", G_CALLBACK(select_cb), const_cast(this)); g_signal_connect(mi, "activate", G_CALLBACK(activate_cb), const_cast(this)); GtkWidget *label = gtk_bin_get_child(GTK_BIN(mi)); Qt::KeyboardModifiers qtMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; // Only attempt to map top level shortcuts. GTK+ accels only take a // single key, but QKeySequence can take multiple-- to overcome this // mismatch, we map the simple key sequences (and their modifiers), but // don't even attempt to try map more complex sequences. if (m_shortcut[1] == 0 && m_shortcut[2] == 0 && m_shortcut[3] == 0) { guint gKey = qt_convertToGdkKeyval(Qt::Key(m_shortcut[0] & ~qtMods)); guint gModifiers = 0; if (m_shortcut[0] & Qt::ShiftModifier) { gModifiers |= GDK_SHIFT_MASK; } if (m_shortcut[0] & Qt::ControlModifier) { gModifiers |= GDK_CONTROL_MASK; } if (m_shortcut[0] & Qt::AltModifier) { gModifiers |= GDK_MOD1_MASK; } if (m_shortcut[0] & Qt::MetaModifier) { gModifiers |= GDK_META_MASK; } gtk_accel_label_set_accel(GTK_ACCEL_LABEL(label), gKey, GdkModifierType(gModifiers)); } ret = mi; } gtk_widget_set_visible(ret.get(), m_visible); return ret; } void QGtkMenuItem::emitSelect() { // ### right? Q_EMIT hovered(); } void QGtkMenuItem::emitActivate() { Q_EMIT activated(); } ================================================ FILE: src/platform-plugin/qgtkmenuitem.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKMENUITEM_H #define QGTKMENUITEM_H #include "qgtkrefptr.h" #include "qgtkmenu.h" #include #include class QGtkMenuItem : public QPlatformMenuItem { Q_OBJECT public: QGtkMenuItem(); ~QGtkMenuItem(); void setTag(quintptr tag) override; quintptr tag()const override; void setText(const QString &text) override; void setIcon(const QIcon &icon) override; void setMenu(QPlatformMenu *menu) override; void setVisible(bool isVisible) override; void setIsSeparator(bool isSeparator) override; void setFont(const QFont &font) override; void setRole(MenuRole role) override; void setCheckable(bool checkable) override; void setChecked(bool isChecked) override; void setShortcut(const QKeySequence& shortcut) override; void setEnabled(bool enabled) override; void setIconSize(int size) override; void setNativeContents(WId item) override; void setHasExclusiveGroup(bool hasExclusiveGroup) override; QGtkRefPtr gtkMenuItem() const; void emitSelect(); void emitActivate(); QGtkMenu *menu() { return m_childMenu.data(); } Q_SIGNALS: void updated(); private: QString m_text; bool m_checkable = false; bool m_isSeparator = false; bool m_enabled = true; bool m_visible = true; bool m_checked = false; bool m_hasExclusiveGroup = false; QPointer m_childMenu; QKeySequence m_shortcut; qintptr m_tag; }; #endif // QGTKMENUITEM_H ================================================ FILE: src/platform-plugin/qgtkopenglcontext.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkopenglcontext.h" #include "qgtkwindow.h" #include "qgtkintegration.h" #include #include #include #include #include #include #include #include #include #ifdef GDK_WINDOWING_WAYLAND #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) #include #else #include #endif #include #endif #ifdef GDK_WINDOWING_X11 #include #endif #include #include "CSystrace.h" Q_LOGGING_CATEGORY(lcContext, "qt.qpa.gtk.context"); // GDK creates an internal 'paint context' for each GDKWindow, and exposes // only an API to create additional contexts which share with the paint // context. Unfortunately, that means it's not possible to create a GDK // context that can share with multiple GDKWindows, which is a requirement // of how the QOpenGLContext API is structured. QOpenGLContext is created // independently of surfaces and can be attached to different services at // makeCurrent() time. // // Since the GDK APIs aren't really useful here, QGtkOpenGLContext creates // contexts directly with EGL/GLX. // // Even more unfortunately, GDK as of now doesn't actually have opengl // windows; all GL rendering happens into a framebuffer that is downloaded // and composited by cairo. Since this is what GDK would be doing anyway, // QGtkOpenGLContext downloads the framebuffer on swapBuffers and passes // the raster image back to QGtkWindow to composite. QGtkOpenGLContext::QGtkOpenGLContext(const QSurfaceFormat &format, QGtkOpenGLContext *shareContext) : m_shareContext(nullptr) , m_fbo(nullptr) , m_fbo_mirrored(nullptr) { m_format = format; m_shareContext = shareContext; } QGtkOpenGLContext::~QGtkOpenGLContext() { delete m_fbo; delete m_fbo_mirrored; } QSurfaceFormat QGtkOpenGLContext::format() const { return m_format; } GLuint QGtkOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) const { // XXX This will result in recreating FBOs if a context renders to differently // sized surfaces. It would be smarter to store the FBO with the surface, and // recreate it only if the context changes. Q_UNUSED(surface); Q_ASSERT(m_fbo); return m_fbo->handle(); } void QGtkOpenGLContext::swapBuffers(QPlatformSurface *surface) { TRACE_EVENT0("gfx", "QGtkOpenGLContext::swapBuffers"); QGtkWindow *win = static_cast(surface); QImage *image = win->beginUpdateFrame("swapBuffers"); // ### perhaps this should be done in one place (inside QGtkBackingStore)? if (image->size() != QSize(m_fbo_mirrored->width(), m_fbo_mirrored->height()) || image->format() != QImage::Format_ARGB32) { *image = QImage(m_fbo_mirrored->width(), m_fbo_mirrored->height(), QImage::Format_ARGB32); image->setDevicePixelRatio(win->devicePixelRatio()); } // Download rendered frame, slowly, so slowly. // First we need to invert y, otherwise we'll draw upside down. To do that, // blit to a framebuffer with inverted coordinate. QRect srcRect(0, image->size().height(), image->size().width(), -image->size().height()); QRect destRect(0, 0, image->size().width(), image->size().height()); QOpenGLFramebufferObject::blitFramebuffer(m_fbo_mirrored, destRect, m_fbo, srcRect, GL_COLOR_BUFFER_BIT, GL_LINEAR, 0, 0, QOpenGLFramebufferObject::DontRestoreFramebufferBinding); // Now read back the flipped data into the backing store image. QOpenGLFunctions funcs(QOpenGLContext::currentContext()); m_fbo_mirrored->bind(); funcs.glReadPixels(0, 0, image->width(), image->height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, image->bits()); win->endUpdateFrame("swapBuffers"); win->invalidateRegion(QRegion()); // If swap is called on the main thread, then assume that the application // knows what it is doing, and can self-throttle (either via requestUpdate, // or QWidget). We can't really do more than this because if we do block we // may lose the chance to process some events. // // If the application requested a swap interval, though, that means they // want us to block, so let's try so they don't render as fast as our little // bits can take us. if (m_format.swapInterval() > 0) { // So yeah, this isn't exactly an ideal throttling mechanism, but it // should be quite deadlock-proof, and works well enough for the time // being. We take the allowed frame time, and if we're finishing ahead // of the allowed time, we sleep. This isn't perfect in that it doesn't // include the time taken by gtk+ to get the frame onto the display but // it's better than nothing. qint64 timeBudget = 1000 / win->window()->screen()->refreshRate(); qint64 delta = timeBudget - m_swapTimer.elapsed(); if (m_swapTimer.isValid() && m_swapTimer.elapsed() < timeBudget) { TRACE_EVENT0("gfx", "QGtkOpenGLContext::swapBuffers::usleep"); usleep(delta * 1000); } m_swapTimer.restart(); } } bool QGtkOpenGLContext::makeCurrent(QPlatformSurface *surface) { QGtkWindow *win = static_cast(surface); QSize sz = win->geometry().size() * win->devicePixelRatio(); if (sz.isEmpty()) sz = QSize(1, 1); if (m_fbo && m_fbo->size() != sz) { qCDebug(lcContext) << "clearing old context FBO of size" << m_fbo->size(); delete m_fbo_mirrored; m_fbo_mirrored = nullptr; // XXX ###: I've seen defaultFramebufferObject getting called when a // QOpenGLFramebufferObject is deleted. That seems scary given this. To // reproduce, delete m_fbo_mirrored after m_fbo is already set to // nullptr, and watch it assert. delete m_fbo; m_fbo = nullptr; } if (!m_fbo) { m_fbo = new QOpenGLFramebufferObject(sz, QOpenGLFramebufferObject::CombinedDepthStencil); m_fbo_mirrored = new QOpenGLFramebufferObject(sz, QOpenGLFramebufferObject::CombinedDepthStencil); qCDebug(lcContext) << "created new context FBO of size" << m_fbo->size(); } if (!m_fbo->isValid()) return false; m_fbo->bind(); return true; } void QGtkOpenGLContext::doneCurrent() { } bool QGtkOpenGLContext::isSharing() const { return m_shareContext; } bool QGtkOpenGLContext::isValid() const { return false; } void *QGtkOpenGLContext::nativeResource(const QByteArray &resource) const { Q_UNUSED(resource); return nullptr; } ================================================ FILE: src/platform-plugin/qgtkopenglcontext.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKOPENGLCONTEXT_H #define QGTKOPENGLCONTEXT_H #include #include #include #include #ifdef GDK_WINDOWING_WAYLAND typedef void *EGLContext; typedef void *EGLDisplay; typedef void *EGLConfig; #endif QT_BEGIN_NAMESPACE class QGtkOpenGLContext : public QPlatformOpenGLContext { public: QGtkOpenGLContext(const QSurfaceFormat &format, QGtkOpenGLContext *shareContext); ~QGtkOpenGLContext(); GLuint defaultFramebufferObject(QPlatformSurface *surface) const override; QSurfaceFormat format() const override; void swapBuffers(QPlatformSurface *surface) override; bool makeCurrent(QPlatformSurface *surface) override; void doneCurrent() override; bool isSharing() const override; bool isValid() const override; virtual void *nativeResource(const QByteArray &resource) const; protected: QSurfaceFormat m_format; QGtkOpenGLContext *m_shareContext; QOpenGLFramebufferObject *m_fbo; QOpenGLFramebufferObject *m_fbo_mirrored; QElapsedTimer m_swapTimer; }; #ifdef GDK_WINDOWING_WAYLAND class QGtkWaylandContext : public QGtkOpenGLContext { public: using QGtkOpenGLContext::QGtkOpenGLContext; virtual ~QGtkWaylandContext(); void initialize() override; bool makeCurrent(QPlatformSurface *surface) override; void doneCurrent() override; QFunctionPointer getProcAddress(const char *procName) override; bool isValid() const override; virtual void *nativeResource(const QByteArray &resource) const override; EGLContext eglContext() const; EGLDisplay eglDisplay() const; EGLConfig eglConfig() const; protected: EGLContext m_eglContext; EGLDisplay m_eglDisplay; EGLConfig m_eglConfig; }; #endif #ifdef GDK_WINDOWING_X11 class QGtkX11Context : public QGtkOpenGLContext { using QGtkOpenGLContext::QGtkOpenGLContext; virtual ~QGtkX11Context(); void initialize() override; bool makeCurrent(QPlatformSurface *surface) override; void doneCurrent() override; QFunctionPointer getProcAddress(const char *procName) override; bool isValid() const override; protected: void *m_display; void *m_glxContext; }; #endif QT_END_NAMESPACE #endif // QGTKOPENGLCONTEXT_H ================================================ FILE: src/platform-plugin/qgtkopenglcontext_wayland.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ // This file is not compiled if GDK does not support Wayland. #include "qgtkopenglcontext.h" #ifdef GDK_WINDOWING_WAYLAND #include "qgtkintegration.h" #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) #include #else #include #endif #include #include "CSystrace.h" static QSurfaceFormat qgtk_wayland_update_format(const QSurfaceFormat &refFormat, EGLDisplay display, EGLContext context); void QGtkWaylandContext::initialize() { QGtkIntegration *integration = QGtkIntegration::instance(); m_eglDisplay = integration->eglDisplay(); // QGtkWaylandContext should not be created on non-wayland displays Q_ASSERT(m_eglDisplay); EGLContext shareEgl = nullptr; if (m_shareContext) shareEgl = static_cast(m_shareContext)->eglContext(); m_eglConfig = q_configFromGLFormat(m_eglDisplay, m_format); QVector eglContextAttrs; eglContextAttrs.append(EGL_CONTEXT_CLIENT_VERSION); eglContextAttrs.append(m_format.majorVersion()); const bool hasKHRCreateContext = q_hasEglExtension(m_eglDisplay, "EGL_KHR_create_context"); if (hasKHRCreateContext) { eglContextAttrs.append(EGL_CONTEXT_MINOR_VERSION_KHR); eglContextAttrs.append(m_format.minorVersion()); int flags = 0; // The debug bit is supported both for OpenGL and OpenGL ES. if (m_format.testOption(QSurfaceFormat::DebugContext)) flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; // The fwdcompat bit is only for OpenGL 3.0+. if (m_format.renderableType() == QSurfaceFormat::OpenGL && m_format.majorVersion() >= 3 && !m_format.testOption(QSurfaceFormat::DeprecatedFunctions)) flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; if (flags) { eglContextAttrs.append(EGL_CONTEXT_FLAGS_KHR); eglContextAttrs.append(flags); } // Profiles are OpenGL only and mandatory in 3.2+. The value is silently ignored for < 3.2. if (m_format.renderableType() == QSurfaceFormat::OpenGL) { eglContextAttrs.append(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR); eglContextAttrs.append(m_format.profile() == QSurfaceFormat::CoreProfile ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR); } } eglContextAttrs.append(EGL_NONE); m_format = q_glFormatFromConfig(m_eglDisplay, m_eglConfig, m_format); m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, shareEgl, eglContextAttrs.data()); if (m_eglContext) m_format = qgtk_wayland_update_format(m_format, m_eglDisplay, m_eglContext); } // Gently borrowed from the wayland QPA plugin; this code appears in several places, // it should really be put into eglconvenience instead. static QSurfaceFormat qgtk_wayland_update_format(const QSurfaceFormat &refFormat, EGLDisplay display, EGLContext context) { QSurfaceFormat format = refFormat; // Have to save & restore to prevent QOpenGLContext::currentContext() from becoming // inconsistent after QOpenGLContext::create(). EGLDisplay prevDisplay = eglGetCurrentDisplay(); if (prevDisplay == EGL_NO_DISPLAY) // when no context is current prevDisplay = display; EGLContext prevContext = eglGetCurrentContext(); EGLSurface prevSurfaceDraw = eglGetCurrentSurface(EGL_DRAW); EGLSurface prevSurfaceRead = eglGetCurrentSurface(EGL_READ); if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)) { if (format.renderableType() == QSurfaceFormat::OpenGL || format.renderableType() == QSurfaceFormat::OpenGLES) { const GLubyte *s = glGetString(GL_VERSION); if (s) { QByteArray version = QByteArray(reinterpret_cast(s)); int major, minor; if (QPlatformOpenGLContext::parseOpenGLVersion(version, major, minor)) { format.setMajorVersion(major); format.setMinorVersion(minor); } } format.setProfile(QSurfaceFormat::NoProfile); format.setOptions(QSurfaceFormat::FormatOptions()); if (format.renderableType() == QSurfaceFormat::OpenGL) { // Check profile and options. if (format.majorVersion() < 3) { format.setOption(QSurfaceFormat::DeprecatedFunctions); } else { GLint value = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &value); if (!(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT)) format.setOption(QSurfaceFormat::DeprecatedFunctions); if (value & GL_CONTEXT_FLAG_DEBUG_BIT) format.setOption(QSurfaceFormat::DebugContext); if (format.version() >= qMakePair(3, 2)) { value = 0; glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value); if (value & GL_CONTEXT_CORE_PROFILE_BIT) format.setProfile(QSurfaceFormat::CoreProfile); else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) format.setProfile(QSurfaceFormat::CompatibilityProfile); } } } } eglMakeCurrent(prevDisplay, prevSurfaceDraw, prevSurfaceRead, prevContext); } return format; } QGtkWaylandContext::~QGtkWaylandContext() { if (m_eglContext) { eglDestroyContext(m_eglDisplay, m_eglContext); } } bool QGtkWaylandContext::makeCurrent(QPlatformSurface *surface) { TRACE_EVENT0("gfx", "QGtkWaylandContext::makeCurrent"); if (!m_eglContext) { qWarning("No context in QGtkOpenGLContext::makeCurrent"); return false; } bool ok = eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext); if (!ok) { qWarning() << "eglMakeCurrent failed"; return ok; } return QGtkOpenGLContext::makeCurrent(surface); } void QGtkWaylandContext::doneCurrent() { TRACE_EVENT0("gfx", "QGtkWaylandContext::doneCurrent"); if (m_eglContext) { eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } QGtkOpenGLContext::doneCurrent(); } bool QGtkWaylandContext::isValid() const { return m_eglContext; } QFunctionPointer QGtkWaylandContext::getProcAddress(const char *procName) { eglBindAPI(EGL_OPENGL_API); // ### EGL_OPENGL_ES_API? QFunctionPointer proc = (QFunctionPointer)eglGetProcAddress(procName); if (!proc) { proc = (QFunctionPointer)dlsym(RTLD_DEFAULT, procName); } return proc; } void *QGtkWaylandContext::nativeResource(const QByteArray &resource) const { if (resource == "eglcontext") { return m_eglContext; } else if (resource == "eglconfig") { return m_eglConfig; } else if (resource == "egldisplay") { return m_eglDisplay; } else { return QGtkOpenGLContext::nativeResource(resource); } } EGLContext QGtkWaylandContext::eglContext() const { return m_eglContext; } EGLDisplay QGtkWaylandContext::eglDisplay() const { return m_eglDisplay; } EGLConfig QGtkWaylandContext::eglConfig() const { return m_eglConfig; } #endif // GDK_WINDOWING_WAYLAND ================================================ FILE: src/platform-plugin/qgtkopenglcontext_x11.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ // This file is not compiled if GDK does not support X11. #include "qgtkopenglcontext.h" #ifdef GDK_WINDOWING_X11 #include #include #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) #include #else #include #endif #include #include #include #include "CSystrace.h" static void updateFormatFromContext(QSurfaceFormat &format) { // Update the version, profile, and context bit of the format int major = 0, minor = 0; QByteArray versionString(reinterpret_cast(glGetString(GL_VERSION))); if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) { format.setMajorVersion(major); format.setMinorVersion(minor); } format.setProfile(QSurfaceFormat::NoProfile); format.setOptions(QSurfaceFormat::FormatOptions()); GLint value = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &value); if (!(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT)) format.setOption(QSurfaceFormat::DeprecatedFunctions); if (value & GL_CONTEXT_FLAG_DEBUG_BIT) format.setOption(QSurfaceFormat::DebugContext); if (format.version() < qMakePair(3, 2)) return; // Version 3.2 and newer have a profile value = 0; glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value); if (value & GL_CONTEXT_CORE_PROFILE_BIT) format.setProfile(QSurfaceFormat::CoreProfile); else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) format.setProfile(QSurfaceFormat::CompatibilityProfile); } typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); void QGtkX11Context::initialize() { m_glxContext = nullptr; m_display = gdk_x11_get_default_xdisplay(); if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) { qFatal("QGtkX11Context should not be used for non-X11 displays"); } Display *display = reinterpret_cast(m_display); int xScreen = gdk_x11_get_default_screen(); GLXContext shareCtx = nullptr; if (m_shareContext) shareCtx = reinterpret_cast(static_cast(m_shareContext)->m_glxContext); // Requiring OpenGL 3.0+ for surfaceless contexts. It would be possible to add support for other configs. m_format.setRenderableType(QSurfaceFormat::OpenGL); if (m_format.version() < qMakePair(3, 0)) m_format.setVersion(3, 0); GLXFBConfig glConfig = qglx_findConfig(display, xScreen, m_format); glXCreateContextAttribsARBProc glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddress((const GLubyte*)"glXCreateContextAttribsARB"); // Try to create an OpenGL context for each known OpenGL version in descending // order from the requested version. const int requestedVersion = m_format.majorVersion() * 10 + qMin(m_format.minorVersion(), 9); QVector glVersions; if (requestedVersion > 45) glVersions << requestedVersion; // Don't bother with versions below 3.0 glVersions << 45 << 44 << 43 << 42 << 41 << 40 << 33 << 32 << 31 << 30; for (int i = 0; !m_glxContext && i < glVersions.count(); i++) { const int version = glVersions[i]; if (version > requestedVersion) continue; const int majorVersion = version / 10; const int minorVersion = version % 10; QVector contextAttributes; contextAttributes << GLX_CONTEXT_MAJOR_VERSION_ARB << majorVersion << GLX_CONTEXT_MINOR_VERSION_ARB << minorVersion; // If asking for OpenGL 3.2 or newer we should also specify a profile if (version >= 32) { if (m_format.profile() == QSurfaceFormat::CoreProfile) contextAttributes << GLX_CONTEXT_PROFILE_MASK_ARB << GLX_CONTEXT_CORE_PROFILE_BIT_ARB; else contextAttributes << GLX_CONTEXT_PROFILE_MASK_ARB << GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; } int flags = 0; if (m_format.testOption(QSurfaceFormat::DebugContext)) flags |= GLX_CONTEXT_DEBUG_BIT_ARB; // A forward-compatible context may be requested for 3.0 and later if (version >= 30 && !m_format.testOption(QSurfaceFormat::DeprecatedFunctions)) flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; if (flags != 0) contextAttributes << GLX_CONTEXT_FLAGS_ARB << flags; contextAttributes << None; m_glxContext = glXCreateContextAttribsARB(display, glConfig, shareCtx, true, contextAttributes.data()); if (!m_glxContext && shareCtx) { // re-try without a shared glx context m_glxContext = glXCreateContextAttribsARB(display, glConfig, 0, true, contextAttributes.data()); if (m_glxContext) m_shareContext = 0; } } if (m_glxContext) { qglx_surfaceFormatFromGLXFBConfig(&m_format, display, glConfig); // Query the OpenGL version and profile GLXContext prevContext = glXGetCurrentContext(); GLXDrawable prevDrawable = glXGetCurrentDrawable(); glXMakeContextCurrent(display, None, None, reinterpret_cast(m_glxContext)); updateFormatFromContext(m_format); // Make our context non-current glXMakeContextCurrent(display, prevDrawable, prevDrawable, prevContext); } } QGtkX11Context::~QGtkX11Context() { if (m_glxContext) { glXDestroyContext(reinterpret_cast(m_display), reinterpret_cast(m_glxContext)); } } bool QGtkX11Context::makeCurrent(QPlatformSurface *surface) { TRACE_EVENT0("gfx", "QGtkX11Context::makeCurrent"); if (!m_glxContext) { qWarning("No context in QGtkOpenGLContext::makeCurrent"); return false; } // Assuming support for surfaceless contexts bool ok = glXMakeContextCurrent(reinterpret_cast(m_display), None, None, reinterpret_cast(m_glxContext)); if (!ok) return ok; return QGtkOpenGLContext::makeCurrent(surface); } void QGtkX11Context::doneCurrent() { TRACE_EVENT0("gfx", "QGtkX11Context::doneCurrent"); glXMakeContextCurrent(reinterpret_cast(m_display), None, None, NULL); QGtkOpenGLContext::doneCurrent(); } bool QGtkX11Context::isValid() const { return m_glxContext; } QFunctionPointer QGtkX11Context::getProcAddress(const char *procName) { return (QFunctionPointer)glXGetProcAddressARB(reinterpret_cast(procName)); } #endif // GDK_WINDOWING_X11 ================================================ FILE: src/platform-plugin/qgtkscreen.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkscreen.h" #include "qgtkcursor.h" #include #include #include #ifdef GDK_WINDOWING_WAYLAND #include #endif Q_LOGGING_CATEGORY(lcScreen, "qt.qpa.gtk.screen"); QGtkScreen::QGtkScreen(GdkMonitor *monitor) : m_monitor(monitor) , m_cursor(new QGtkCursor()) { } QRect QGtkScreen::availableGeometry() const { qreal dpr = 1.0; #if defined(GDK_WINDOWING_WAYLAND) && !GTK_CHECK_VERSION(3, 22, 25) GdkDisplay *dpy = gdk_display_get_default(); // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=788497 if (GDK_IS_WAYLAND_DISPLAY(dpy)) { dpr = devicePixelRatio(); } #endif GdkRectangle geometry; gdk_monitor_get_workarea(m_monitor, &geometry); return QRect(geometry.x / dpr, geometry.y / dpr, geometry.width / dpr, geometry.height / dpr); } QRect QGtkScreen::geometry() const { qreal dpr = 1.0; #if defined(GDK_WINDOWING_WAYLAND) && !GTK_CHECK_VERSION(3, 22, 25) GdkDisplay *dpy = gdk_display_get_default(); // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=788497 if (GDK_IS_WAYLAND_DISPLAY(dpy)) { dpr = devicePixelRatio(); } #endif GdkRectangle geometry; gdk_monitor_get_geometry(m_monitor, &geometry); return QRect(geometry.x / dpr, geometry.y / dpr, geometry.width / dpr, geometry.height / dpr); } int QGtkScreen::depth() const { return 32; } QImage::Format QGtkScreen::format() const { return QImage::Format_ARGB32_Premultiplied; } QSizeF QGtkScreen::physicalSize() const { return QSizeF(gdk_monitor_get_width_mm(m_monitor), gdk_monitor_get_height_mm(m_monitor)); } QDpi QGtkScreen::logicalDpi() const { // ### notify on change int dpi = -1; g_object_get(gtk_settings_get_default(), "gtk-xft-dpi", &dpi, NULL); if (dpi == -1) { dpi = 96; } else { dpi /= 1024; } return QDpi(dpi, dpi); } qreal QGtkScreen::devicePixelRatio() const { return gdk_monitor_get_scale_factor(m_monitor); } qreal QGtkScreen::refreshRate() const { // gdk gives us millihz.. return gdk_monitor_get_refresh_rate(m_monitor) / 1000; } QPlatformCursor *QGtkScreen::cursor() const { return m_cursor.get(); } ================================================ FILE: src/platform-plugin/qgtkscreen.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKSCREEN_H #define QGTKSCREEN_H #include #include #include QT_BEGIN_NAMESPACE class QGtkCursor; class QGtkScreen : public QPlatformScreen { public: QGtkScreen(GdkMonitor *monitor); QRect availableGeometry() const override; QRect geometry() const override; int depth() const override; QImage::Format format() const override; QSizeF physicalSize() const override; QDpi logicalDpi() const override; qreal devicePixelRatio() const override; qreal refreshRate() const override; QPlatformCursor *cursor() const override; GdkMonitor *monitor() const { return m_monitor; } bool isPrimary() const { return m_isPrimary; } void setPrimary(bool p) { m_isPrimary = p; } public: GdkMonitor *m_monitor; std::unique_ptr m_cursor; bool m_isPrimary = false; }; QT_END_NAMESPACE #endif // QGTKSCREEN_H ================================================ FILE: src/platform-plugin/qgtkservices.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkservices.h" #include #include #include #include QGtkServices::QGtkServices() { } QGtkServices::~QGtkServices() { } bool QGtkServices::openUrl(const QUrl &url) { GError *err = nullptr; g_app_info_launch_default_for_uri(url.toString(QUrl::FullyEncoded).toUtf8().constData(), NULL, &err); if (err) { qWarning() << "Open failed: " << err->message; g_error_free(err); return false; } return true; } bool QGtkServices::openDocument(const QUrl &url) { GError *err = nullptr; g_app_info_launch_default_for_uri(url.toString(QUrl::FullyEncoded).toUtf8().constData(), NULL, &err); if (err) { qWarning() << "Open failed: " << err->message; g_error_free(err); return false; } return true; } QByteArray QGtkServices::desktopEnvironment() const { return qgetenv("XDG_CURRENT_DESKTOP"); } ================================================ FILE: src/platform-plugin/qgtkservices.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKSERVICES_H #define QGTKSERVICES_H #include #include QT_BEGIN_NAMESPACE class QUrl; class QGtkServices : public QPlatformServices { public: QGtkServices(); virtual ~QGtkServices(); bool openUrl(const QUrl &url) override; bool openDocument(const QUrl &url) override; QByteArray desktopEnvironment() const override; }; QT_END_NAMESPACE #endif // QGTKSERVICES_H ================================================ FILE: src/platform-plugin/qgtksystemtrayicon.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkhelpers.h" #include "qgtksystemtrayicon.h" #include "qgtkintegration.h" #include #include #include QGtkSystemTrayIcon::QGtkSystemTrayIcon() { } QGtkSystemTrayIcon::~QGtkSystemTrayIcon() { } void QGtkSystemTrayIcon::init() { } void QGtkSystemTrayIcon::cleanup() { } void QGtkSystemTrayIcon::updateIcon(const QIcon &) { } void QGtkSystemTrayIcon::updateToolTip(const QString &) { } void QGtkSystemTrayIcon::updateMenu(QPlatformMenu *) { } QRect QGtkSystemTrayIcon::geometry() const { return QRect(); } void action_cb(NotifyNotification*, gchar *, gpointer gtkSystemTrayIcon) { QGtkSystemTrayIcon *ico = (QGtkSystemTrayIcon*)gtkSystemTrayIcon; Q_EMIT ico->messageClicked(); } void QGtkSystemTrayIcon::showMessage(const QString &title, const QString &msg, const QIcon& icon, MessageIcon iconType, int msecs) { NotifyNotification *n = notify_notification_new(title.toUtf8().constData(), msg.toUtf8().constData(), nullptr); // ### technically, we could delete this after 'msecs'. we need to keep it // around to fire action_cb. m_notification.reset(n); if (!icon.isNull()) { QGtkRefPtr ico = qt_iconToPixbuf(icon); notify_notification_set_icon_from_pixbuf(n, ico.get()); } switch (iconType) { case QPlatformSystemTrayIcon::NoIcon: case QPlatformSystemTrayIcon::Information: notify_notification_set_urgency(n, NOTIFY_URGENCY_LOW); break; case QPlatformSystemTrayIcon::Warning: notify_notification_set_urgency(n, NOTIFY_URGENCY_NORMAL); break; case QPlatformSystemTrayIcon::Critical: notify_notification_set_urgency(n, NOTIFY_URGENCY_CRITICAL); break; } notify_notification_set_timeout(n, msecs); notify_notification_add_action(n, "default", "default", action_cb, this, NULL); notify_notification_show(n, NULL); } bool QGtkSystemTrayIcon::isSystemTrayAvailable() const { return true; } bool QGtkSystemTrayIcon::supportsMessages() const { return true; } ================================================ FILE: src/platform-plugin/qgtksystemtrayicon.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKSYSTEMTRAYICON_P_H #define QGTKSYSTEMTRAYICON_P_H #include #include "QtCore/qstring.h" #include "QtGui/qpa/qplatformsystemtrayicon.h" #include #include QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QGtkSystemTrayIcon : public QPlatformSystemTrayIcon { public: QGtkSystemTrayIcon(); ~QGtkSystemTrayIcon(); void init() override; void cleanup() override; void updateIcon(const QIcon &icon) override; void updateToolTip(const QString &toolTip) override; void updateMenu(QPlatformMenu *menu) override; QRect geometry() const override; void showMessage(const QString &title, const QString &msg, const QIcon& icon, MessageIcon iconType, int msecs) override; bool isSystemTrayAvailable() const override; bool supportsMessages() const override; private: QGtkRefPtr m_icon; QGtkRefPtr m_notification; }; QT_END_NAMESPACE #endif // QGTKSYSTEMTRAYICON_P_H ================================================ FILE: src/platform-plugin/qgtktheme.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtk3dialoghelpers.h" #include "qgtkmenubar.h" #include "qgtkmenu.h" #include "qgtkmenuitem.h" #include "qgtktheme.h" #include "qgtksystemtrayicon.h" #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) # include #else #include #endif #include #include #include #include #include const char *QGtkTheme::name = "gtkqpainternal"; QGtkTheme::QGtkTheme() { } QGtkTheme::~QGtkTheme() { } QPlatformMenuItem* QGtkTheme::createPlatformMenuItem() const { return new QGtkMenuItem; } QPlatformMenu* QGtkTheme::createPlatformMenu() const { return new QGtkMenu; } QPlatformMenuBar* QGtkTheme::createPlatformMenuBar() const { return new QGtkMenuBar; } #ifndef QT_NO_SYSTEMTRAYICON QPlatformSystemTrayIcon *QGtkTheme::createPlatformSystemTrayIcon() const { return new QGtkSystemTrayIcon; } #endif bool QGtkTheme::usePlatformNativeDialog(DialogType dialogType) const { switch (dialogType) { case QPlatformTheme::FileDialog: case QPlatformTheme::FontDialog: case QPlatformTheme::ColorDialog: return true; case QPlatformTheme::MessageDialog: break; } return false; } QPlatformDialogHelper *QGtkTheme::createPlatformDialogHelper(DialogType dialogType) const { switch (dialogType) { case QPlatformTheme::FileDialog: return new QGtk3FileDialogHelper; case QPlatformTheme::FontDialog: return new QGtk3FontDialogHelper; case QPlatformTheme::ColorDialog: return new QGtk3ColorDialogHelper; case QPlatformTheme::MessageDialog: break; } return 0; } const QPalette *QGtkTheme::palette(Palette type) const { return QPlatformTheme::palette(type); } const QFont *QGtkTheme::font(Font type) const { if (!m_fontConfigured) { m_fontConfigured = true; GtkSettings *s = gtk_settings_get_default(); gchararray value; g_object_get(s, "gtk-font-name", &value, NULL); QString qtVal = QString::fromUtf8(value); g_free(value); if (qtVal.isNull()) { m_systemFont = QFont("Sans Serif", 11); m_monoFont = QFont("Monospace", m_systemFont.pointSize()); } else { int lastSpace = qtVal.lastIndexOf(' '); int pointSize = qtVal.midRef(lastSpace+1).toInt(); m_systemFont = QFont(qtVal.left(lastSpace), pointSize); // ### dconf also has monospace fonts, document fonts... how can we get these? // this is a bit of a hack... m_monoFont = QFont("Monospace", m_systemFont.pointSize()); } } if (type == QPlatformTheme::FixedFont) { return &m_monoFont; } return &m_systemFont; } QPixmap QGtkTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const { return QPlatformTheme::standardPixmap(sp, size); } static QIcon fileIconForFile(const QFileInfo &fi) { QMimeDatabase md; QMimeType mt = md.mimeTypeForFile(fi); if (!mt.isValid()) { return QIcon(); } const QString &iconName = mt.iconName(); if (!iconName.isEmpty()) { QIcon ico = QIcon::fromTheme(iconName); if (!ico.isNull()) { return ico; } } const QString &genericIconName = mt.genericIconName(); return QIcon::fromTheme(genericIconName); } #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) QIcon QGtkTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options) const { return fileIconForFile(fileInfo); } #else QPixmap QGtkTheme::fileIconPixmap(const QFileInfo &fileInfo, const QSizeF &size, QPlatformTheme::IconOptions options) const { Q_UNUSED(options); QIcon ico = fileIconForFile(fileInfo); return ico.pixmap(size.toSize()); } #endif QVariant QGtkTheme::themeHint(ThemeHint hint) const { switch (hint) { case QPlatformTheme::SystemIconThemeName: return QVariant("Adwaita"); case QPlatformTheme::SystemIconFallbackThemeName: return QVariant("gnome"); case QPlatformTheme::StyleNames: return QStringList() << "Adwaita" << "Fusion"; case QPlatformTheme::PasswordMaskCharacter: return QVariant(QChar(0x2022)); case QPlatformTheme::IconThemeSearchPaths: return QVariant(QGenericUnixTheme::xdgIconThemePaths()); case QPlatformTheme::IconPixmapSizes: return QVariant::fromValue(QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes()); default: break; } return QPlatformTheme::themeHint(hint); } QString QGtkTheme::standardButtonText(int button) const { return QPlatformTheme::standardButtonText(button); } ================================================ FILE: src/platform-plugin/qgtktheme.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKTHEME_H #define QGTKTHEME_H #include #include class QGtkTheme : public QPlatformTheme { public: QGtkTheme(); ~QGtkTheme(); QPlatformMenuItem* createPlatformMenuItem() const override; QPlatformMenu* createPlatformMenu() const override; QPlatformMenuBar* createPlatformMenuBar() const override; #ifndef QT_NO_SYSTEMTRAYICON QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; #endif bool usePlatformNativeDialog(DialogType dialogType) const override; QPlatformDialogHelper *createPlatformDialogHelper(DialogType dialogType) const override; const QPalette *palette(Palette type = SystemPalette) const override; const QFont *font(Font type = SystemFont) const override; QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = 0) const override; #else QPixmap fileIconPixmap(const QFileInfo &fileInfo, const QSizeF &size, QPlatformTheme::IconOptions options = 0) const override; #endif QVariant themeHint(ThemeHint hint) const override; QString standardButtonText(int button) const override; static const char *name; mutable QFont m_systemFont; mutable QFont m_monoFont; mutable bool m_fontConfigured = false; }; #endif // QGTKTHEME_H ================================================ FILE: src/platform-plugin/qgtkwindow.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include "qgtkhelpers.h" #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcWindow, "qt.qpa.gtk.window"); Q_LOGGING_CATEGORY(lcWindowEvents, "qt.qpa.gtk.window"); static gboolean map_cb(GtkWidget *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "map_cb" << pw; pw->onMap(); return FALSE; } static gboolean unmap_cb(GtkWidget *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "unmap_cb" << pw; pw->onUnmap(); return FALSE; } static gboolean configure_cb(GtkWidget *, GdkEvent *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "configure_cb" << pw; pw->onConfigure(); return FALSE; } static gboolean size_allocate_cb(GtkWidget *, GdkRectangle *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "size_allocate_cb" << pw; pw->onConfigure(); return FALSE; } static gboolean delete_cb(GtkWidget *, GdkEvent *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "delete_cb" << pw; return pw->onDelete() ? TRUE : FALSE; } static gboolean key_press_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "key_press_cb" << pw; return pw->onKeyPress(event) ? TRUE : FALSE; } static gboolean key_release_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "key_release_cb" << pw; return pw->onKeyRelease(event) ? TRUE : FALSE; } static gboolean button_press_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "button_press_cb" << pw; return pw->onButtonPress(event) ? TRUE : FALSE; } static gboolean button_release_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "button_release_cb" << pw; return pw->onButtonRelease(event) ? TRUE : FALSE; } static gboolean touch_event_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "touch_event_cb" << pw; return pw->onTouchEvent(event) ? TRUE : FALSE; } static gboolean motion_notify_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "motion_notify_cb" << pw; return pw->onMotionNotify(event) ? TRUE : FALSE; } static gboolean scroll_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "scroll_cb" << pw; return pw->onScrollEvent(event) ? TRUE : FALSE; } static gboolean window_state_event_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "window_state_event_cb" << pw; pw->onWindowStateEvent(event); return FALSE; } static gboolean enter_leave_window_notify_cb(GtkWidget *, GdkEvent *event, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); bool entering = event->type == GDK_ENTER_NOTIFY; qCDebug(lcWindowEvents) << "enter_leave_window_notify_cb" << pw << entering; pw->onEnterLeaveWindow(event, entering); return false; } static gboolean leave_content_notify_cb(GtkWidget *, GdkEvent *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowEvents) << "leave_content_notify_cb" << pw; pw->onLeaveContent(); return false; } QGtkWindow::QGtkWindow(QWindow *window) : QPlatformWindow(window) , m_buttons(Qt::NoButton) , m_windowGeometry(0, 0, 1, 1) { create(window->type()); if (!QGtkCourierObject::instance) QGtkCourierObject::instance = new QGtkCourierObject(QCoreApplication::instance()); } void QGtkWindow::create(Qt::WindowType windowType) { if (m_window) { gtk_widget_destroy(m_window.get()); } // Determine the window type. GTK_WINDOW_TOPLEVEL is usually right. GtkWindowType gtkWindowType = GTK_WINDOW_TOPLEVEL; if (windowType == Qt::ToolTip || windowType == Qt::Popup) { gtkWindowType = GTK_WINDOW_POPUP; } // Create the window. m_window = gtk_window_new(gtkWindowType); // First things first, set a proper type hint on the window. switch (windowType) { case Qt::Window: gtk_window_set_type_hint(GTK_WINDOW(m_window.get()), GDK_WINDOW_TYPE_HINT_NORMAL); break; case Qt::Dialog: case Qt::Sheet: gtk_window_set_type_hint(GTK_WINDOW(m_window.get()), GDK_WINDOW_TYPE_HINT_DIALOG); break; case Qt::Popup: gtk_window_set_type_hint(GTK_WINDOW(m_window.get()), GDK_WINDOW_TYPE_HINT_MENU); break; case Qt::Tool: gtk_window_set_type_hint(GTK_WINDOW(m_window.get()), GDK_WINDOW_TYPE_HINT_TOOLBAR); break; case Qt::SplashScreen: gtk_window_set_type_hint(GTK_WINDOW(m_window.get()), GDK_WINDOW_TYPE_HINT_SPLASHSCREEN); break; case Qt::ToolTip: gtk_window_set_type_hint(GTK_WINDOW(m_window.get()), GDK_WINDOW_TYPE_HINT_TOOLTIP); break; default: break; } // Now set a transient parent (for things that ought to have one). This is // required otherwise things like positioning windows will not work, as // Wayland doesn't have any concept of a global window position. maybeForceTransientParent(windowType); g_signal_connect(m_window.get(), "map", G_CALLBACK(map_cb), this); g_signal_connect(m_window.get(), "unmap", G_CALLBACK(unmap_cb), this); g_signal_connect(m_window.get(), "configure-event", G_CALLBACK(configure_cb), this); g_signal_connect(m_window.get(), "enter-notify-event", G_CALLBACK(enter_leave_window_notify_cb), this); g_signal_connect(m_window.get(), "leave-notify-event", G_CALLBACK(enter_leave_window_notify_cb), this); // for whatever reason, configure-event is not enough. it doesn't seem to // get emitted for popup type windows. so also connect to size-allocate just // to be sure... g_signal_connect(m_window.get(), "size-allocate", G_CALLBACK(size_allocate_cb), this); g_signal_connect(m_window.get(), "delete-event", G_CALLBACK(delete_cb), this); g_signal_connect(m_window.get(), "window-state-event", G_CALLBACK(window_state_event_cb), this); GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(m_window.get()), vbox); m_menubar = GTK_MENU_BAR(gtk_menu_bar_new()); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(m_menubar.get()), FALSE, FALSE, 0); m_content = gtk_drawing_area_new(); g_signal_connect(m_content.get(), "draw", G_CALLBACK(QGtkWindow::drawCallback), this); gtk_box_pack_end(GTK_BOX(vbox), m_content.get(), TRUE, TRUE, 0); // ### Proximity? Touchpad gesture? Tablet? gtk_widget_set_events(m_content.get(), GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_LEAVE_NOTIFY_MASK ); // Register event handlers that need coordinates on the content widget, not // the window. g_signal_connect(m_content.get(), "button-press-event", G_CALLBACK(button_press_cb), this); g_signal_connect(m_content.get(), "button-release-event", G_CALLBACK(button_release_cb), this); g_signal_connect(m_content.get(), "touch-event", G_CALLBACK(touch_event_cb), this); g_signal_connect(m_content.get(), "motion-notify-event", G_CALLBACK(motion_notify_cb), this); g_signal_connect(m_content.get(), "key-press-event", G_CALLBACK(key_press_cb), this); g_signal_connect(m_content.get(), "key-release-event", G_CALLBACK(key_release_cb), this); g_signal_connect(m_content.get(), "scroll-event", G_CALLBACK(scroll_cb), this); g_signal_connect(m_content.get(), "leave-notify-event", G_CALLBACK(leave_content_notify_cb), this); gtk_widget_set_can_focus(m_content.get(), true); m_zoomGesture = gtk_gesture_zoom_new(m_content.get()); gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_zoomGesture.get()), GTK_PHASE_CAPTURE); g_signal_connect(m_zoomGesture.get(), "scale-changed", G_CALLBACK(QGtkWindow::zoom_cb), this); g_signal_connect(m_zoomGesture.get(), "begin", G_CALLBACK(QGtkWindow::begin_zoom_cb), this); g_signal_connect(m_zoomGesture.get(), "cancel", G_CALLBACK(QGtkWindow::cancel_zoom_cb), this); g_signal_connect(m_zoomGesture.get(), "end", G_CALLBACK(QGtkWindow::end_zoom_cb), this); m_rotateGesture = gtk_gesture_rotate_new(m_content.get()); gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_rotateGesture.get()), GTK_PHASE_CAPTURE); g_signal_connect(m_rotateGesture.get(), "angle-changed", G_CALLBACK(QGtkWindow::rotate_cb), this); g_signal_connect(m_rotateGesture.get(), "begin", G_CALLBACK(QGtkWindow::begin_rotate_cb), this); g_signal_connect(m_rotateGesture.get(), "cancel", G_CALLBACK(QGtkWindow::cancel_rotate_cb), this); g_signal_connect(m_rotateGesture.get(), "end", G_CALLBACK(QGtkWindow::end_rotate_cb), this); gtk_gesture_group(m_zoomGesture.get(), m_rotateGesture.get()); m_touchDevice = new QTouchDevice; m_touchDevice->setType(QTouchDevice::TouchScreen); // ### use GdkDevice or not? m_touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::MouseEmulation); QWindowSystemInterface::registerTouchDevice(m_touchDevice); setWindowState(window()->windowState()); propagateSizeHints(); setWindowFlags(window()->flags()); setGeometry(window()->geometry()); gtk_window_set_modal(GTK_WINDOW(m_window.get()), window()->modality() != Qt::NonModal); if (!window()->title().isEmpty()) setWindowTitle(window()->title()); if (!qFuzzyCompare(QWindowPrivate::get(window())->opacity, qreal(1.0))) { setOpacity(QWindowPrivate::get(window())->opacity); } if (window()->isTopLevel()) { setWindowIcon(window()->icon()); } } QGtkWindow::~QGtkWindow() { gtk_widget_remove_tick_callback(m_window.get(), m_tick_callback); QWindowSystemInterface::unregisterTouchDevice(m_touchDevice); gtk_widget_destroy(m_window.get()); } void QGtkWindow::maybeForceTransientParent(Qt::WindowType windowType) { bool shouldTransient = window()->modality() != Qt::NonModal; switch (windowType) { case Qt::Dialog: case Qt::Sheet: case Qt::Tool: case Qt::SplashScreen: case Qt::ToolTip: case Qt::Drawer: case Qt::Popup: shouldTransient = true; break; default: break; } if (!shouldTransient) { return; } // Hope they specified one first. QWindow *transientParent = window()->transientParent(); if (transientParent) { reallyForceTransientFor(transientParent); return; } // Try fall back to focus. We must have a top level window, though. if (qApp->focusWindow() && qApp->focusWindow()->type() == Qt::Window) { qWarning() << "Forcing transient parent to focus window " << qApp->focusWindow() << " for window " << window() << " -- this is bad, it ought to have a transientParent set, the window may end up incorrectly positioned"; reallyForceTransientFor(qApp->focusWindow()); return; } // Last ditch effort: try find a top level window. QWindowList wl = qApp->topLevelWindows(); for (QWindow *win : wl) { if (win->type() == Qt::Window) { qWarning() << "Forcing transient parent to first available toplevel " << win << " for window " << window() << " -- this is bad, it ought to have a transientParent set, the window may end up incorrectly positioned."; reallyForceTransientFor(win); return; } } qWarning() << "Showing " << window() << " as a transient window without a transient parent, positioning will almost certainly be incorrect (if it works at all!)"; } void QGtkWindow::reallyForceTransientFor(QWindow *transientParent) { transientParent->create(); // force pwin creation QGtkWindow *transientParentPlatform = static_cast(transientParent->handle()); gtk_window_set_transient_for(GTK_WINDOW(m_window.get()), GTK_WINDOW(transientParentPlatform->gtkWindow().get())); } void QGtkWindow::onMap() { QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); } void QGtkWindow::onUnmap() { QWindowSystemInterface::handleExposeEvent(window(), QRegion()); } void QGtkWindow::onConfigure() { // windowX and windowY are the window system coordinates of the top-left of the client // portion of the window. They don't include server-side decorations but do include client-side. int windowX = 0, windowY = 0; GdkWindow *gwindow = gtk_widget_get_window(m_window.get()); if (gwindow) gdk_window_get_position(gwindow, &windowX, &windowY); // contentRect is the drawn content area of the window in window coordinates. This excludes // client-side decorations and other frame elements (e.g. menubar). GdkRectangle contentRect; gtk_widget_get_allocated_size(m_content.get(), &contentRect, nullptr); // QWindow geometry is contentRect translated to window system coordinates with windowX/windowY m_newGeometry = QRect(windowX + contentRect.x, windowY + contentRect.y, contentRect.width, contentRect.height); } bool QGtkWindow::onDelete() { #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) return QWindowSystemInterface::handleCloseEvent(window()); #else bool accepted = false; QWindowSystemInterface::handleCloseEvent(window(), &accepted); QWindowSystemInterface::flushWindowSystemEvents(); return accepted; #endif } QSurfaceFormat QGtkWindow::format() const { return window()->requestedFormat(); } void QGtkWindow::setGeometry(const QRect &crect) { QRect rect(crect); Qt::WindowType type = static_cast(int(m_flags & Qt::WindowType_Mask)); if (type != Qt::Window) { // Ensure that child windows are positioned somewhere that makes sense. // If we don't do this, then popup-type windows will end up off-screen, // which isn't very useful. // // It'd be quite nice if in Qt 6, we could consider doing away with // absolute positioning of menus and such, and rather, tie them to a // pointer device or a parent widget + offset inside that widget, or // something. const QSize screenSize = window()->screen()->availableGeometry().size(); int deltaX = rect.x() - rect.width(); int deltaY = rect.y() - rect.height(); if (rect.y() + rect.height() > screenSize.height()) { rect.moveTop(deltaY); } if (rect.x() + rect.width() > screenSize.width()) { rect.moveLeft(deltaX); } } if (!window()->isVisible()) { // if we aren't visible, we won't get a configure event, so cache the // geometry for the time being. m_windowGeometry = QRect(QPoint(0, 0), rect.size()); } gtk_window_move(GTK_WINDOW(m_window.get()), rect.x(), rect.y()); gtk_window_resize(GTK_WINDOW(m_window.get()), qMax(rect.width(), 1), qMax(rect.height(), 1)); } QRect QGtkWindow::geometry() const { return m_windowGeometry; } QRect QGtkWindow::normalGeometry() const { return geometry(); } qreal QGtkWindow::devicePixelRatio() const { // ### may change on configure event return gtk_widget_get_scale_factor(m_window.get()); } QMargins QGtkWindow::frameMargins() const { GdkWindow *gwindow = gtk_widget_get_window(m_window.get()); if (!gwindow) { return QMargins(); } // Bounding rectangle of the entire window, including server-side frame // x and y are in root window coordinates GdkRectangle frameRect; gdk_window_get_frame_extents(gwindow, &frameRect); // Position of the top-left of the window area, excluding server-side frames, // also in root window coordinates int originX, originY; gdk_window_get_origin(gwindow, &originX, &originY); // Rectangle in window coordinates of the content area, excluding client-side frames GdkRectangle contentRect; gtk_widget_get_allocated_size(m_content.get(), &contentRect, nullptr); // Size of the margin for top and left. This is the server-side margin (difference // between the frame and origin's X) plus the client-side margin (contentRect.x) int leftMargin = originX - frameRect.x + contentRect.x; int topMargin = originY - frameRect.y + contentRect.y; // Bottom and right margins are the remainder of frameRect's size after removing the // top/left margins and contentRect size. return QMargins(leftMargin, topMargin, frameRect.width - leftMargin - contentRect.width, frameRect.height - topMargin - contentRect.height); } void QGtkWindow::setVisible(bool visible) { if (visible) { gtk_widget_show_all(m_window.get()); gtk_widget_grab_focus(m_content.get()); } else { gtk_widget_hide(m_window.get()); } } void QGtkWindow::setWindowFlags(Qt::WindowFlags flags) { if (flags == m_flags) { // probably means we're being called from create(), do our best to make // it harmless. return; } Qt::WindowType oldType = static_cast(int(m_flags & Qt::WindowType_Mask)); Qt::WindowType type = static_cast(int(flags & Qt::WindowType_Mask)); m_flags = flags; if (type == Qt::Popup) { flags |= Qt::FramelessWindowHint; } if (type != oldType) { if (type == Qt::Popup) { // ### do we really support changing to other types of windows at // runtime? this will probably break all sorts of stuff. //create(Qt::Popup); } } // ### recreate the window if the type changes, but be careful, we may // recurse. gtk_window_set_decorated(GTK_WINDOW(m_window.get()), !(flags & Qt::FramelessWindowHint)); if ((flags & Qt::CustomizeWindowHint)) { gtk_window_set_deletable(GTK_WINDOW(m_window.get()), (flags & Qt::WindowCloseButtonHint)); } } #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) void QGtkWindow::setWindowState(Qt::WindowStates requestedState) { const Qt::WindowState state = QWindowPrivate::effectiveState(requestedState); #else void QGtkWindow::setWindowState(Qt::WindowState requestedState) { const Qt::WindowState state = requestedState; #endif if (state == m_state) { return; } switch (m_state) { case Qt::WindowMinimized: gtk_window_deiconify(GTK_WINDOW(m_window.get())); break; case Qt::WindowMaximized: gtk_window_unmaximize(GTK_WINDOW(m_window.get())); break; case Qt::WindowFullScreen: gtk_window_unfullscreen(GTK_WINDOW(m_window.get())); break; case Qt::WindowNoState: case Qt::WindowActive: break; } switch (state) { case Qt::WindowMinimized: gtk_window_iconify(GTK_WINDOW(m_window.get())); break; case Qt::WindowMaximized: gtk_window_maximize(GTK_WINDOW(m_window.get())); break; case Qt::WindowFullScreen: gtk_window_fullscreen(GTK_WINDOW(m_window.get())); break; case Qt::WindowNoState: case Qt::WindowActive: break; } m_state = state; } void QGtkWindow::onWindowStateEvent(GdkEvent *event) { GdkEventWindowState *ev = (GdkEventWindowState*)event; Qt::WindowState newState = Qt::WindowNoState; if (ev->new_window_state & GDK_WINDOW_STATE_ICONIFIED) { newState = Qt::WindowMinimized; } if (ev->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { newState = Qt::WindowMaximized; } if (ev->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) { newState = Qt::WindowFullScreen; } if (newState != m_state) { m_state = newState; QWindowSystemInterface::handleWindowStateChanged(window(), newState); } Qt::WindowType type = static_cast(int(m_flags & Qt::WindowType_Mask)); // We must not send window activation changes for tooltip windows, as they // will auto-hide on activation change. if (type != Qt::ToolTip && (ev->changed_mask & GDK_WINDOW_STATE_FOCUSED)) { static QPointer newActiveWindow = nullptr; if (ev->new_window_state & GDK_WINDOW_STATE_FOCUSED) { qCDebug(lcWindow) << window() << " focused"; newActiveWindow = window(); } else if (newActiveWindow == window()) { qCDebug(lcWindow) << window() << " unfocused"; newActiveWindow = nullptr; } // We need a timer here to debounce the focus changes. Reason being that // one window appearing results in two GDK_WINDOW_STATE_FOCUSED changes: // one for the old window to remove it, one for the new window to add // it. // // Without a debounce, we set the active window like this: // old // nullptr // new // // ... which, in the case of say, popups, may result in their being // dismissed (since a combo box shouldn't be kept open if its parent // window loses focus to something other than the combo). QTimer::singleShot(0, [=]() { qCDebug(lcWindow) << "Active changed to " << newActiveWindow.data(); QWindowSystemInterface::handleWindowActivated(newActiveWindow.data(), Qt::ActiveWindowFocusReason); }); } // GDK_WINDOW_STATE_TILED not handled. // GDK_WINDOW_STATE_STICKY not handled. // GDK_WINDOW_STATE_ABOVE not handled. // GDK_WINDOW_STATE_BELOW not handled. // GDK_WINDOW_STATE_WITHDRAWN not handled. } void QGtkWindow::onEnterLeaveWindow(GdkEvent *event, bool entered) { GdkEventCrossing *ev = (GdkEventCrossing*)event; static QPointer enterWindow; static QPoint enterPos; static QPoint globalEnterPos; static QPointer leaveWindow; static QPoint leavePos; static QPoint globalLeavePos; if (entered) { enterWindow = window(); enterPos = QPoint(ev->x, ev->y); globalEnterPos = QPoint(ev->x_root, ev->y_root); } else { leaveWindow = window(); leavePos = QPoint(ev->x, ev->y); globalLeavePos = QPoint(ev->x_root, ev->y_root); } QTimer::singleShot(0, [=]() { if (enterWindow && leaveWindow) { QWindowSystemInterface::handleEnterLeaveEvent(enterWindow.data(), leaveWindow.data(), leavePos, globalLeavePos); } else if (enterWindow) { QWindowSystemInterface::handleEnterEvent(enterWindow.data(), enterPos, globalEnterPos); } else if (leaveWindow) { QWindowSystemInterface::handleLeaveEvent(leaveWindow.data()); } }); } void QGtkWindow::onLeaveContent() { // reset the mouse cursor. QGtkRefPtr c = gdk_cursor_new_from_name(gdk_display_get_default(), "default"); gdk_window_set_cursor(gtk_widget_get_window(m_window.get()), c.get()); } WId QGtkWindow::winId() const { return (WId)m_window.get(); } void QGtkWindow::setParent(const QPlatformWindow *) { } void QGtkWindow::setWindowTitle(const QString &title) { gtk_window_set_title(GTK_WINDOW(m_window.get()), title.toUtf8().constData()); } void QGtkWindow::setWindowFilePath(const QString &title) { // we can't do anything useful with this Q_UNUSED(title); } void QGtkWindow::setWindowIcon(const QIcon &icon) { if (icon.isNull()) { gtk_window_set_icon(GTK_WINDOW(m_window.get()), nullptr); return; } QGtkRefPtr pb = qt_iconToPixbuf(icon); // ### consider gtk_window_set_icon_list gtk_window_set_icon(GTK_WINDOW(m_window.get()), pb.get()); } void QGtkWindow::raise() { // we cannot control the stacking order } void QGtkWindow::lower() { // we cannot control the stacking order } bool QGtkWindow::isExposed() const { return gtk_widget_get_visible(m_window.get()); } bool QGtkWindow::isActive() const { return gtk_widget_has_focus(m_window.get()); } void QGtkWindow::propagateSizeHints() { QSize minSize = windowMinimumSize(); QSize maxSize = windowMaximumSize(); QSize baseSize = windowBaseSize(); QSize sizeIncrement = windowSizeIncrement(); int activeHints = GdkWindowHints(0); GdkGeometry hints; if (!minSize.isNull()) { hints.min_width = minSize.width(); hints.min_height = minSize.height(); activeHints |= GDK_HINT_MIN_SIZE; gtk_widget_set_size_request(GTK_WIDGET(m_content.get()), hints.min_width, hints.min_height); } if (!maxSize.isNull()) { hints.max_width = maxSize.width(); hints.max_height = maxSize.height(); activeHints |= GDK_HINT_MAX_SIZE; } if (!baseSize.isNull()) { hints.base_width = baseSize.width(); hints.base_height = baseSize.height(); activeHints |= GDK_HINT_BASE_SIZE; } if (sizeIncrement.isNull()) { hints.width_inc = sizeIncrement.width(); hints.height_inc = sizeIncrement.height(); activeHints |= GDK_HINT_RESIZE_INC; } if ((activeHints & GDK_HINT_MIN_SIZE) && (activeHints & GDK_HINT_MAX_SIZE)) { if (minSize == maxSize) { gtk_window_set_resizable(GTK_WINDOW(m_window.get()), false); } else { gtk_window_set_resizable(GTK_WINDOW(m_window.get()), true); } } else { gtk_window_set_resizable(GTK_WINDOW(m_window.get()), true); } gtk_window_set_geometry_hints( GTK_WINDOW(m_window.get()), m_window.get(), &hints, GdkWindowHints(activeHints) ); } void QGtkWindow::setOpacity(qreal level) { gtk_widget_set_opacity(m_window.get(), level); } void QGtkWindow::requestActivateWindow() { qCDebug(lcWindow) << "Request activate" << window(); gtk_window_present(GTK_WINDOW(m_window.get())); } void QGtkWindow::setMask(const QRegion ®ion) { // can't do anything useful with this Q_UNUSED(region); } bool QGtkWindow::setKeyboardGrabEnabled(bool grab) { if (grab) { gtk_window_present(GTK_WINDOW(m_window.get())); } return true; } bool QGtkWindow::setMouseGrabEnabled(bool grab) { if (grab) { gtk_window_present(GTK_WINDOW(m_window.get())); } return true; } /* void QGtkWindow::setFrameStrutEventsEnabled(bool enabled){} bool QGtkWindow::frameStrutEventsEnabled() const{} */ void QGtkWindow::setAlertState(bool enabled) { gtk_window_set_urgency_hint(GTK_WINDOW(m_window.get()), enabled); } bool QGtkWindow::isAlertState() const { return gtk_window_get_urgency_hint(GTK_WINDOW(m_window.get())); } /* void QGtkWindow::invalidateSurface(){} */ QGtkRefPtr QGtkWindow::gtkWindow() const { return m_window; } QGtkRefPtr QGtkWindow::gtkMenuBar() const { return m_menubar; } ================================================ FILE: src/platform-plugin/qgtkwindow.h ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #ifndef QGTKWINDOW_H #define QGTKWINDOW_H #include "qgtkrefptr.h" #include #include #include #include QT_BEGIN_NAMESPACE class QTouchDevice; class QGtkWindow : public QObject, public QPlatformWindow { public: QGtkWindow(QWindow *window); ~QGtkWindow(); void create(Qt::WindowType windowType); QSurfaceFormat format() const override; void setGeometry(const QRect &rect) override; QRect geometry() const override; QRect normalGeometry() const override; qreal devicePixelRatio() const; QMargins frameMargins() const override; void setVisible(bool visible) override; void setWindowFlags(Qt::WindowFlags flags) override; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) void setWindowState(Qt::WindowStates state) override; #else void setWindowState(Qt::WindowState state) override; #endif WId winId() const override; void setParent(const QPlatformWindow *window) override; void setWindowTitle(const QString &title) override; void setWindowFilePath(const QString &title) override; void setWindowIcon(const QIcon &icon) override; void raise() override; void lower() override; bool isExposed() const override; bool isActive() const override; void propagateSizeHints() override; void setOpacity(qreal level) override; void requestActivateWindow() override; void setMask(const QRegion ®ion) override; bool setKeyboardGrabEnabled(bool grab) override; bool setMouseGrabEnabled(bool grab) override; /* bool setWindowModified(bool modified) override; void windowEvent(QEvent *event) override; bool startSystemResize(const QPoint &pos, Qt::Corner corner) override; void setFrameStrutEventsEnabled(bool enabled) override; bool frameStrutEventsEnabled() const override; */ void setAlertState(bool enabled) override; bool isAlertState() const override; /* void invalidateSurface() override; */ void requestUpdate() override; // End API, start implementation. void onDraw(cairo_t *cr); void onRender(); void onMap(); void onUnmap(); void onConfigure(); bool onDelete(); bool onKeyPress(GdkEvent *event); bool onKeyRelease(GdkEvent *event); bool onButtonPress(GdkEvent *event); bool onButtonRelease(GdkEvent *event); bool onMotionNotify(GdkEvent *event); bool onTouchEvent(GdkEvent *event); bool onScrollEvent(GdkEvent *event); void onWindowStateEvent(GdkEvent *event); void onWindowTickCallback(); void onEnterLeaveWindow(GdkEvent *event, bool entered); void onLeaveContent(); QImage *beginUpdateFrame(const QString &reason); void endUpdateFrame(const QString &reason); void invalidateRegion(const QRegion ®ion); QImage currentFrameImage() const; QGtkRefPtr gtkMenuBar() const; QGtkRefPtr gtkWindow() const; void beginZoom(QPointF &contentPoint, guint32 ts); void zoom(QPointF &contentPoint, double scale, guint32 ts); void endZoom(QPointF &contentPoint, guint32 ts); void beginRotate(QPointF &contentPoint, guint32 ts); void rotate(QPointF &contentPoint, double angle, double angle_delta, guint32 ts); void endRotate(QPointF &contentPoint, guint32 ts); private: void maybeForceTransientParent(Qt::WindowType windowType); void reallyForceTransientFor(QWindow *transientParent); QGtkRefPtr m_window; QGtkRefPtr m_menubar; QGtkRefPtr m_content; QMutex m_frameMutex; QImage m_frame; QTouchDevice *m_touchDevice = nullptr; QList m_activeTouchPoints; Qt::MouseButtons m_buttons; Qt::WindowState m_state = Qt::WindowNoState; bool m_wantsUpdate = false; guint m_tick_callback = 0; Qt::WindowFlags m_flags = Qt::Widget; QRect m_windowGeometry; // must be cached as it's accessed from multiple threads QRect m_newGeometry; Qt::KeyboardModifiers m_scrollModifiers = Qt::NoModifier; bool m_scrollStarted = false; static void drawCallback(GtkWidget *, cairo_t *cr, gpointer platformWindow); static gboolean windowTickCallback(GtkWidget*, GdkFrameClock *, gpointer platformWindow); static void zoom_cb(GtkGestureZoom *pt, gdouble scale, gpointer platformWindow); static void begin_zoom_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow); static void end_zoom_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow); static void cancel_zoom_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow); static void rotate_cb(GtkGestureRotate *pt, gdouble angle, gdouble angle_delta, gpointer platformWindow); static void begin_rotate_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow); static void end_rotate_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow); static void cancel_rotate_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow); QGtkRefPtr m_zoomGesture; QGtkRefPtr m_rotateGesture; int m_activeNativeGestures = 0; bool m_initialZoomSet = false; double m_initialZoom = 0; bool m_initialRotateSet = false; double m_initialRotate = 0; bool m_hasTickCallback = false; QTimer *m_cancelTickTimer = nullptr; }; class QGtkCourierObject : public QObject { Q_OBJECT public: static QGtkCourierObject *instance; QGtkCourierObject(QObject *parent = nullptr); Q_INVOKABLE void queueDraw(QGtkWindow *win); }; // QGtkWindow is ambiguous with the QObject* and QPlatformSurface* overloads; choose one inline QDebug operator<<(QDebug debug, const QGtkWindow *surface) { return debug << static_cast(surface); } QT_END_NAMESPACE #endif // QGTKWINDOW_H ================================================ FILE: src/platform-plugin/qgtkwindow_gesture.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include // M_PI #include Q_LOGGING_CATEGORY(lcGesture, "qt.qpa.gtk.gesture"); static void populateTsAndPoint(GtkGesture *gesture, guint32 &ts, QPointF &contentPoint) { gdouble x; gdouble y; GdkEventSequence *seq = gtk_gesture_get_last_updated_sequence(gesture); gtk_gesture_get_point(gesture, seq, &x, &y); contentPoint = QPointF(x, y); const GdkEvent *ev = gtk_gesture_get_last_event(gesture, seq); ts = gdk_event_get_time(ev); } void QGtkWindow::zoom_cb(GtkGestureZoom *pt, gdouble scale, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); GdkEventSequence *seq = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(pt)); gtk_gesture_set_sequence_state(GTK_GESTURE(pt), seq, GTK_EVENT_SEQUENCE_CLAIMED); // ### not really sure this should be done here, but crashes in begin pw->zoom(contentPoint, scale, ts); } void QGtkWindow::begin_zoom_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); qCDebug(lcGesture) << "Begin zoom " << pw->window() << ts << contentPoint; pw->beginZoom(contentPoint, ts); } void QGtkWindow::end_zoom_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); qCDebug(lcGesture) << "End zoom " << pw->window() << ts << contentPoint; pw->endZoom(contentPoint, ts); } void QGtkWindow::cancel_zoom_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); qCDebug(lcGesture) << "Cancel zoom " << pw->window() << ts << contentPoint; pw->endZoom(contentPoint, ts); } void QGtkWindow::beginZoom(QPointF &contentPoint, guint32 ts) { m_initialZoomSet = false; if (m_activeNativeGestures++ == 0) { qCDebug(lcGesture) << "Started native gesture sequence (due to zoom)"; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGestureEvent(window(), nullptr, ts, Qt::BeginNativeGesture, contentPoint, contentPoint); #else QWindowSystemInterface::handleGestureEvent(window(), ts, Qt::BeginNativeGesture, contentPoint, contentPoint); #endif } } void QGtkWindow::zoom(QPointF &contentPoint, double scale, guint32 ts) { if (scale == 0.0) { return; // insane } if (!m_initialZoomSet) { m_initialZoomSet = true; m_initialZoom = scale; } double modScale = (scale - m_initialZoom) / m_initialZoom; m_initialZoom = scale; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGestureEventWithRealValue(window(), nullptr, ts, Qt::ZoomNativeGesture, modScale, contentPoint, contentPoint); #else QWindowSystemInterface::handleGestureEventWithRealValue(window(), ts, Qt::ZoomNativeGesture, modScale, contentPoint, contentPoint); #endif } void QGtkWindow::endZoom(QPointF &contentPoint, guint32 ts) { if (--m_activeNativeGestures == 0) { qCDebug(lcGesture) << "Ended native gesture sequence (due to zoom)"; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGestureEvent(window(), nullptr, ts, Qt::EndNativeGesture, contentPoint, contentPoint); #else QWindowSystemInterface::handleGestureEvent(window(), ts, Qt::EndNativeGesture, contentPoint, contentPoint); #endif } } void QGtkWindow::rotate_cb(GtkGestureRotate *pt, gdouble angle, gdouble angle_delta, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); GdkEventSequence *seq = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(pt)); gtk_gesture_set_sequence_state(GTK_GESTURE(pt), seq, GTK_EVENT_SEQUENCE_CLAIMED); // ### not really sure this should be done here, but crashes in begin pw->rotate(contentPoint, angle, angle_delta, ts); } void QGtkWindow::begin_rotate_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); qCDebug(lcGesture) << "Begin rotate " << pw->window() << ts << contentPoint; pw->beginRotate(contentPoint, ts); } void QGtkWindow::end_rotate_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); qCDebug(lcGesture) << "End rotate " << pw->window() << ts << contentPoint; pw->endRotate(contentPoint, ts); } void QGtkWindow::cancel_rotate_cb(GtkGesture *pt, GdkEventSequence*, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); guint32 ts; QPointF contentPoint; populateTsAndPoint(GTK_GESTURE(pt), ts, contentPoint); qCDebug(lcGesture) << "Cancel rotate " << pw->window() << ts << contentPoint; pw->endRotate(contentPoint, ts); } void QGtkWindow::beginRotate(QPointF &contentPoint, guint32 ts) { m_initialRotateSet = false; if (m_activeNativeGestures++ == 0) { qCDebug(lcGesture) << "Started native gesture sequence (due to rotate)"; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGestureEvent(window(), nullptr, ts, Qt::BeginNativeGesture, contentPoint, contentPoint); #else QWindowSystemInterface::handleGestureEvent(window(), ts, Qt::BeginNativeGesture, contentPoint, contentPoint); #endif } } void QGtkWindow::rotate(QPointF &contentPoint, double angle, double angle_delta, guint32 ts) { Q_UNUSED(angle_delta) angle = -angle; if (!m_initialRotateSet) { m_initialRotateSet = true; m_initialRotate = angle * 180 / M_PI; } double degrees = m_initialRotate - (angle * 180 / M_PI); m_initialRotate = angle * 180 / M_PI; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGestureEventWithRealValue(window(), nullptr, ts, Qt::RotateNativeGesture, degrees, contentPoint, contentPoint); #else QWindowSystemInterface::handleGestureEventWithRealValue(window(), ts, Qt::RotateNativeGesture, degrees, contentPoint, contentPoint); #endif } void QGtkWindow::endRotate(QPointF &contentPoint, guint32 ts) { if (--m_activeNativeGestures == 0) { qCDebug(lcGesture) << "Ended native gesture sequence (due to rotate)"; #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGestureEvent(window(), nullptr, ts, Qt::EndNativeGesture, contentPoint, contentPoint); #else QWindowSystemInterface::handleGestureEvent(window(), ts, Qt::EndNativeGesture, contentPoint, contentPoint); #endif } } ================================================ FILE: src/platform-plugin/qgtkwindow_keyboard.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include "qgtkhelpers.h" #include #include #include "CSystrace.h" bool QGtkWindow::onKeyPress(GdkEvent *event) { GdkEventKey *ev = (GdkEventKey*)event; TRACE_EVENT_ASYNC_BEGIN0("input", "QGtkWindow::keyDown", (void*)ev->hardware_keycode); TRACE_EVENT0("input", "QGtkWindow::onKeyPress"); const QString text = QString::fromUtf8(ev->string, ev->length); Qt::KeyboardModifiers qtMods = qt_convertToQtKeyboardMods(ev->state); Qt::Key qtKey = qt_convertToQtKey(ev->keyval); return QWindowSystemInterface::handleExtendedKeyEvent( window(), ev->time, QEvent::KeyPress, qtKey, qtMods, ev->hardware_keycode, ev->hardware_keycode, 0, text ); } bool QGtkWindow::onKeyRelease(GdkEvent *event) { GdkEventKey *ev = (GdkEventKey*)event; TRACE_EVENT_ASYNC_END0("input", "QGtkWindow::keyDown", (void*)ev->hardware_keycode); TRACE_EVENT0("input", "QGtkWindow::onKeyRelease"); const QString text = QString::fromUtf8(ev->string, ev->length); Qt::KeyboardModifiers qtMods = qt_convertToQtKeyboardMods(ev->state); Qt::Key qtKey = qt_convertToQtKey(ev->keyval); return QWindowSystemInterface::handleExtendedKeyEvent( window(), ev->time, QEvent::KeyRelease, qtKey, qtMods, ev->hardware_keycode, ev->hardware_keycode, 0, text ); } ================================================ FILE: src/platform-plugin/qgtkwindow_mouse.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include "qgtkhelpers.h" #include #include #include #include "CSystrace.h" Q_LOGGING_CATEGORY(lcMouse, "qt.qpa.gtk.mouse"); Q_LOGGING_CATEGORY(lcMouseMotion, "qt.qpa.gtk.mouse.motion"); bool QGtkWindow::onButtonPress(GdkEvent *event) { TRACE_EVENT0("input", "QGtkWindow::onButtonPress"); TRACE_EVENT_ASYNC_BEGIN0("input", "QGtkWindow::mouseDown", this); GdkEventButton *ev = (GdkEventButton*)event; // ### would be nice if we could support GDK_2BUTTON_PRESS/GDK_3BUTTON_PRESS // directly (and not via emulation internally). Qt::MouseButton b = qt_convertGButtonToQButton(ev->button); m_buttons |= b; qCDebug(lcMouse) << "Pressed " << b << " at " << ev->x << ev->y << ev->x_root << ev->y_root << " total pressed " << m_buttons; bool isTabletEvent = false; QWindowSystemInterface::handleMouseEvent( window(), ev->time, QPointF(ev->x, ev->y), QPointF(ev->x_root, ev->y_root), // ### _root is probably wrong. m_buttons, qt_convertToQtKeyboardMods(ev->state), isTabletEvent ? Qt::MouseEventSynthesizedByQt : Qt::MouseEventNotSynthesized ); return true; } bool QGtkWindow::onButtonRelease(GdkEvent *event) { TRACE_EVENT0("input", "QGtkWindow::onButtonRelease"); GdkEventButton *ev = (GdkEventButton*)event; Qt::MouseButton b = qt_convertGButtonToQButton(ev->button); m_buttons &= ~b; qCDebug(lcMouse) << "Released " << b << " at " << ev->x << ev->y << ev->x_root << ev->y_root << " total pressed " << m_buttons; bool isTabletEvent = false; QWindowSystemInterface::handleMouseEvent( window(), ev->time, QPointF(ev->x, ev->y), QPointF(ev->x_root, ev->y_root), m_buttons, qt_convertToQtKeyboardMods(ev->state), isTabletEvent ? Qt::MouseEventSynthesizedByQt : Qt::MouseEventNotSynthesized ); TRACE_EVENT_ASYNC_END0("input", "QGtkWindow::mouseDown", this); return true; } bool QGtkWindow::onMotionNotify(GdkEvent *event) { TRACE_EVENT0("input", "QGtkWindow::onMotionNotify"); GdkEventButton *ev = (GdkEventButton*)event; qCDebug(lcMouseMotion) << "Moved mouse at " << ev->x << ev->y << ev->x_root << ev->y_root; QPoint mousePos(ev->x, ev->y); mousePos = window()->mapToGlobal(mousePos); QCursor::setPos(mousePos); bool isTabletEvent = false; QWindowSystemInterface::handleMouseEvent( window(), ev->time, QPointF(ev->x, ev->y), QPointF(ev->x_root, ev->y_root), m_buttons, qt_convertToQtKeyboardMods(ev->state), isTabletEvent ? Qt::MouseEventSynthesizedByQt : Qt::MouseEventNotSynthesized ); return true; } bool QGtkWindow::onScrollEvent(GdkEvent *event) { TRACE_EVENT0("input", "QGtkWindow::onScrollEvent"); GdkEventScroll *ev = (GdkEventScroll*)event; QPoint angleDelta; QPoint pixelDelta; Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; Qt::ScrollPhase phase = Qt::ScrollUpdate; // We cache the modifiers as they should not change after the scroll has // started. Doing that means that you'll zoom text in Creator or something, // which is pretty annoying. if (!m_scrollStarted) { m_scrollStarted = true; m_scrollModifiers = qt_convertToQtKeyboardMods(ev->state); phase = Qt::ScrollBegin; } if (gdk_event_is_scroll_stop_event(event)) { m_scrollStarted = false; m_scrollModifiers = Qt::NoModifier; phase = Qt::ScrollEnd; } if (ev->direction == GDK_SCROLL_SMOOTH) { // ### I have literally no idea what I'm doing here const int pixelsToDegrees = 50; angleDelta.setX(-ev->delta_x * pixelsToDegrees); angleDelta.setY(-ev->delta_y * pixelsToDegrees); source = Qt::MouseEventSynthesizedBySystem; pixelDelta.setX(ev->delta_x * pixelsToDegrees); pixelDelta.setY(-ev->delta_y * pixelsToDegrees); } else if (ev->direction == GDK_SCROLL_UP || ev->direction == GDK_SCROLL_DOWN) { angleDelta.setY(qBound(-120, int(ev->delta_y * 10000), 120)); } else if (ev->direction == GDK_SCROLL_LEFT || ev->direction == GDK_SCROLL_RIGHT) { angleDelta.setX(qBound(-120, int(ev->delta_x * 10000), 120)); } else { Q_UNREACHABLE(); } qCDebug(lcMouseMotion) << "Scrolled mouse at " << ev->x << ev->y << ev->x_root << ev->y_root << " angle delta " << angleDelta << " pixelDelta " << pixelDelta << " original deltas " << ev->delta_x << ev->delta_y; QWindowSystemInterface::handleWheelEvent( window(), ev->time, QPointF(ev->x, ev->y), QPointF(ev->x_root, ev->y_root), pixelDelta, angleDelta, m_scrollModifiers, phase, source, false /* isInverted */ ); return true; } ================================================ FILE: src/platform-plugin/qgtkwindow_render.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include "qgtkhelpers.h" #include #include #include #include #include "CSystrace.h" Q_LOGGING_CATEGORY(lcWindowRender, "qt.qpa.gtk.window.render"); // debug QGtkWindow lock on surface content #undef LOCK_DEBUG void QGtkWindow::drawCallback(GtkWidget *, cairo_t *cr, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowRender) << "drawCallback" << pw; pw->onDraw(cr); } void QGtkWindow::onDraw(cairo_t *cr) { if (m_newGeometry != m_windowGeometry) { bool needsExpose = m_newGeometry.size() != m_windowGeometry.size(); #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QWindowSystemInterface::handleGeometryChange(window(), m_newGeometry); #else QWindowSystemInterface::handleGeometryChange(window(), m_newGeometry, m_windowGeometry); #endif m_windowGeometry = m_newGeometry; if (needsExpose) { QWindowSystemInterface::handleExposeEvent(window(), m_windowGeometry); // we must flush, otherwise the content we'll render might be out of date. // ### would be nice if we could compress these, somehow: at the least we'll // get a configure and a size-allocate independent of each other. QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); } } TRACE_EVENT0("gfx", "QGtkWindow::onDraw"); // Hold frameMutex during blit to cairo to prevent changes QMutexLocker lock(&m_frameMutex); #if defined(LOCK_DEBUG) qWarning() << "rendering (LOCKED)" << this; #endif if (m_frame.isNull()) return; #if 0 QString clipString; cairo_rectangle_list_t *clip = cairo_copy_clip_rectangle_list(cr); for (int i = 0; i < clip->num_rectangles; i++) { auto r = clip->rectangles[i]; clipString += QString("%1,%2@%3x%4 ").arg(r.x).arg(r.y).arg(r.width).arg(r.height); } qDebug(lcWindow) << "onDraw with clip:" << clipString; #endif cairo_surface_t *surf = cairo_image_surface_create_for_data( const_cast(m_frame.constBits()), CAIRO_FORMAT_ARGB32, m_frame.width(), m_frame.height(), m_frame.bytesPerLine() ); int sf = gtk_widget_get_scale_factor(m_window.get()); cairo_surface_set_device_scale(surf, sf, sf); cairo_set_source_surface(cr, surf, 0, 0); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); // cairo_paint respects the current clip, which GTK sets based on // updated regions of the window, so we don't need to do anything // other than include the updated regions in queue_draw calls. cairo_paint(cr); cairo_surface_destroy(surf); #if defined(LOCK_DEBUG) qWarning() << "rendering (UNLOCKING)" << this; #endif } gboolean QGtkWindow::windowTickCallback(GtkWidget*, GdkFrameClock *, gpointer platformWindow) { QGtkWindow *pw = static_cast(platformWindow); qCDebug(lcWindowRender) << "windowTickCallback" << pw; pw->onWindowTickCallback(); return G_SOURCE_CONTINUE; } void QGtkWindow::onWindowTickCallback() { TRACE_EVENT0("gfx", "QGtkWindow::onWindowTickCallback"); if (m_wantsUpdate) { m_wantsUpdate = false; TRACE_EVENT_ASYNC_END0("gfx", "QGtkWindow::requestUpdate", this); #if QT_VERSION >= QT_VERSION_CHECK(5,12,0) this->deliverUpdateRequest(); #else QWindowPrivate::get(window())->deliverUpdateRequest(); #endif } else { if (m_cancelTickTimer) { return; } qCDebug(lcWindowRender) << "Preparing to remove tick callback" << this; // Stop delivering ticks if update requests end for a long time. m_cancelTickTimer = new QTimer(this); m_cancelTickTimer->setInterval(1000); m_cancelTickTimer->setSingleShot(true); m_cancelTickTimer->start(); connect(m_cancelTickTimer, &QTimer::timeout, this, [=]() { if (!m_wantsUpdate) { qCDebug(lcWindowRender) << "Removing tick callback" << this; gtk_widget_remove_tick_callback(m_window.get(), m_tick_callback); m_tick_callback = 0; m_hasTickCallback = false; m_cancelTickTimer->deleteLater(); m_cancelTickTimer = nullptr; } }); } } void QGtkWindow::requestUpdate() { TRACE_EVENT_ASYNC_BEGIN0("gfx", "QGtkWindow::requestUpdate", this); m_wantsUpdate = true; if (!m_hasTickCallback) { qCDebug(lcWindowRender) << "Installing tick callback" << this; m_hasTickCallback = true; m_tick_callback = gtk_widget_add_tick_callback(m_window.get(), QGtkWindow::windowTickCallback, this, NULL); } if (m_cancelTickTimer) { m_cancelTickTimer->deleteLater(); m_cancelTickTimer = nullptr; qCDebug(lcWindowRender) << "Cancelling remove of tick callback" << this; } } QGtkCourierObject::QGtkCourierObject(QObject *parent) : QObject(parent) { qRegisterMetaType("QGtkWindow*"); } void QGtkCourierObject::queueDraw(QGtkWindow *win) { gtk_widget_queue_draw(win->gtkWindow().get()); } QGtkCourierObject *QGtkCourierObject::instance; void QGtkWindow::invalidateRegion(const QRegion ®ion) { auto courier = QGtkCourierObject::instance; Q_ASSERT(courier); if (courier->thread() != QThread::currentThread()) { // In the multithreaded case, always signal a full screen update for now courier->metaObject()->invokeMethod(courier, "queueDraw", Qt::QueuedConnection, Q_ARG(QGtkWindow*, this)); return; } QRegion realRegion = region.isNull() ? QRegion(m_frame.rect()) : region; cairo_region_t *cairoRegion = qt_convertToCairoRegion(realRegion); gtk_widget_queue_draw_region(m_content.get(), cairoRegion); cairo_region_destroy(cairoRegion); } QImage *QGtkWindow::beginUpdateFrame(const QString &reason) { m_frameMutex.lock(); Q_UNUSED(reason); #if defined(LOCK_DEBUG) qWarning() << "beginUpdateFrame " << reason << "(LOCKED)" << this; #endif return &m_frame; } void QGtkWindow::endUpdateFrame(const QString &reason) { m_frameMutex.unlock(); Q_UNUSED(reason); #if defined(LOCK_DEBUG) qWarning() << "endUpdateFrame " << reason << "(UNLOCKED)" << this; #endif } QImage QGtkWindow::currentFrameImage() const { return m_frame; } ================================================ FILE: src/platform-plugin/qgtkwindow_touch.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2017 Crimson AS ** Contact: https://www.crimson.no ** ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qgtkwindow.h" #include "qgtkhelpers.h" #include #include #include #include "CSystrace.h" Q_LOGGING_CATEGORY(lcTouch, "qt.qpa.gtk.touch"); Q_LOGGING_CATEGORY(lcTouchUpdate, "qt.qpa.gtk.touch.update"); bool QGtkWindow::onTouchEvent(GdkEvent *event) { TRACE_EVENT0("input", "QGtkWindow::onTouchEvent"); GdkEventTouch *ev = (GdkEventTouch*)event; QWindowSystemInterface::TouchPoint *tp = 0; int touchpointId = int(reinterpret_cast(ev->sequence)); switch (ev->type) { case GDK_TOUCH_BEGIN: TRACE_EVENT_ASYNC_BEGIN0("input", "QGtkWindow::touchDown", (void*)touchpointId); qCDebug(lcTouch) << "Begin " << touchpointId; m_activeTouchPoints.append(QWindowSystemInterface::TouchPoint()); tp = &m_activeTouchPoints.last(); tp->id = touchpointId; break; case GDK_TOUCH_UPDATE: qCDebug(lcTouchUpdate) << "Update " << touchpointId; for (int i = 0; i < m_activeTouchPoints.length(); ++i) { if (m_activeTouchPoints.at(i).id == touchpointId) { tp = &m_activeTouchPoints[i]; break; } } break; case GDK_TOUCH_END: qCDebug(lcTouch) << "End " << touchpointId; for (int i = 0; i < m_activeTouchPoints.length(); ++i) { if (m_activeTouchPoints.at(i).id == touchpointId) { tp = &m_activeTouchPoints[i]; break; } } break; case GDK_TOUCH_CANCEL: qCDebug(lcTouch) << "Cancel " << touchpointId; for (int i = 0; i < m_activeTouchPoints.length(); ++i) { if (m_activeTouchPoints.at(i).id == touchpointId) { tp = &m_activeTouchPoints[i]; break; } } break; default: qWarning() << "Unknown touch type" << ev->type; return false; } // ### it's unfortunate that we have to report touch events so often. // perhaps we should tie the report of these into the frameclock so they are // only sent once per frame? or ask gtk for a 'touch frame' event? if (tp) { // Update it tp->pressure = 1.0; // ### should be able to read this somehow tp->state = qt_convertToQtTouchPointState(ev->type); // ### touchpoint size? // the area is supposed to be centered on the point, hence the - 0.5 // subtractions (as we're reporting a size of 1). QRectF tpArea = QRectF(ev->x - 0.5, ev->y - 0.5, 1, 1); // make sure it really moved... if (tp->state == Qt::TouchPointMoved) { if (tp->area == tpArea) { tp->state = Qt::TouchPointStationary; } } tp->area = tpArea; QSize s = window()->screen()->size(); qreal nx = ev->x / (s.width() / 2); qreal ny = ev->y / (s.height() / 2); tp->normalPosition = QPointF(nx, ny); } // report unconditionally even if tp was not found // (in that case there was a release event) QWindowSystemInterface::handleTouchEvent( window(), ev->time, m_touchDevice, m_activeTouchPoints, qt_convertToQtKeyboardMods(ev->state) ); switch (ev->type) { case GDK_TOUCH_END: case GDK_TOUCH_CANCEL: for (int i = 0; i < m_activeTouchPoints.length(); ++i) { if (m_activeTouchPoints.at(i).id == touchpointId) { m_activeTouchPoints.removeAt(i); break; } } TRACE_EVENT_ASYNC_END0("input", "QGtkWindow::touchDown", (void*)touchpointId); break; default: break; } // Eat the event so that GTK doesn't synthesize pointer events from these. return true; } ================================================ FILE: src/src.pro ================================================ TEMPLATE = subdirs SUBDIRS += \ gtkextras \ platform_plugin platform_plugin.subdir = platform-plugin platform_plugin.depends = gtkextras ================================================ FILE: sync.profile ================================================ %modules = ( "QtGtkExtras" => "$basedir/src/gtkextras", ); %moduleheaders = ( );