| | | 1 | | // SPDX-FileCopyrightText: 2026 Alper Çelik |
| | | 2 | | // SPDX-License-Identifier: MIT OR Apache-2.0 |
| | | 3 | | |
| | | 4 | | //! Screen/monitor/cursor provider for the Qt runtime. |
| | | 5 | | //! |
| | | 6 | | //! Reads Qt's own screen model (`QGuiApplication`, `QScreen`) and the cursor |
| | | 7 | | //! position, so the other modules never touch them. Qt reports geometry in |
| | | 8 | | //! device-independent (logical) global pixels; converting to physical pixels |
| | | 9 | | //! with `scale_factor` is the Rust side's job. |
| | | 10 | | //! |
| | | 11 | | //! Every entry point may be called from any thread: work that has to run on the |
| | | 12 | | //! GUI thread is marshalled there (see `on_gui_thread`). |
| | | 13 | | |
| | | 14 | | #include "qt_screen.hpp" |
| | | 15 | | |
| | | 16 | | // cxx-generated header of this bridge: declares the entry points and defines |
| | | 17 | | // the shared structs they return. |
| | | 18 | | #include "tauri-runtime-qt/src/bridge/screen.rs.h" |
| | | 19 | | // `qt_window_winid()` (registry handle -> native window id), used by |
| | | 20 | | // `qt_screen_for_window()` to find the window behind a handle. Included instead |
| | | 21 | | // of hand-declaring it, so the linkage cxx expects is the one cxx generates. |
| | | 22 | | #include "tauri-runtime-qt/src/bridge/window.rs.h" |
| | | 23 | | |
| | | 24 | | #include <QtCore/QCoreApplication> |
| | | 25 | | #include <QtCore/QList> |
| | | 26 | | #include <QtCore/QMetaObject> |
| | | 27 | | #include <QtCore/QPoint> |
| | | 28 | | #include <QtCore/QString> |
| | | 29 | | #include <QtCore/QThread> |
| | | 30 | | #include <QtCore/QtGlobal> |
| | | 31 | | #include <QtGui/QCursor> |
| | | 32 | | #include <QtGui/QGuiApplication> |
| | | 33 | | #include <QtGui/QScreen> |
| | | 34 | | #include <QtGui/QWindow> |
| | | 35 | | #include <QtWidgets/QApplication> |
| | | 36 | | #include <QtWidgets/QWidget> |
| | | 37 | | |
| | | 38 | | #include <cstdint> |
| | | 39 | | #include <string> |
| | | 40 | | #include <type_traits> |
| | | 41 | | #include <utility> |
| | | 42 | | |
| | | 43 | | namespace { |
| | | 44 | | |
| | | 45 | | /// The `QGuiApplication` of this process, or `nullptr` when there is none (or |
| | | 46 | | /// only a plain `QCoreApplication`, which has no screens). |
| | 105 | 47 | | QGuiApplication* gui_application() { |
| | 105 | 48 | | return qobject_cast<QGuiApplication*>(QCoreApplication::instance()); |
| | 105 | 49 | | } |
| | | 50 | | |
| | | 51 | | /// Runs `body` on the Qt GUI thread and returns its value. |
| | | 52 | | /// |
| | | 53 | | /// On the GUI thread (the common case) `body` runs inline. From another thread |
| | | 54 | | /// the call is queued to the application object — which lives on the GUI thread |
| | | 55 | | /// — as a blocking queued invocation, so the caller gets the real value and the |
| | | 56 | | /// Qt platform classes are only ever touched from their own thread. |
| | | 57 | | /// |
| | | 58 | | /// A blocking invocation waits for the GUI thread to process events; it must |
| | | 59 | | /// therefore not be used from inside a GUI-thread callback that blocks the |
| | | 60 | | /// event loop on the calling thread. |
| | | 61 | | template <typename F> |
| | 126 | 62 | | auto on_gui_thread(F&& body) -> std::invoke_result_t<F> { |
| | 126 | 63 | | QCoreApplication* app = QCoreApplication::instance(); |
| | 126 | 64 | | if (app == nullptr || QThread::currentThread() == app->thread()) { |
| | 126 | 65 | | return body(); |
| | 126 | 66 | | } |
| | | 67 | | |
| | 0 | 68 | | using Result = std::invoke_result_t<F>; |
| | | 69 | | if constexpr (std::is_void_v<Result>) { |
| | | 70 | | QMetaObject::invokeMethod(app, std::forward<F>(body), Qt::BlockingQueuedConnection); |
| | 0 | 71 | | } else { |
| | 0 | 72 | | Result result{}; |
| | 0 | 73 | | QMetaObject::invokeMethod( |
| | 0 | 74 | | app, [&body, &result]() { result = body(); }, Qt::BlockingQueuedConnection); |
| | 0 | 75 | | return result; |
| | 0 | 76 | | } |
| | 0 | 77 | | } |
| | | 78 | | |
| | | 79 | | /// Copies one `QScreen` into the bridge representation (logical pixels). |
| | 63 | 80 | | QtScreenInfo screen_info(const QScreen* screen) { |
| | 63 | 81 | | const QRect geometry = screen->geometry(); |
| | 63 | 82 | | const QRect available = screen->availableGeometry(); |
| | | 83 | | |
| | 63 | 84 | | QtScreenInfo info; |
| | 63 | 85 | | info.valid = true; |
| | 63 | 86 | | info.name = rust::String(screen->name().toStdString()); |
| | 63 | 87 | | info.x = geometry.x(); |
| | 63 | 88 | | info.y = geometry.y(); |
| | 63 | 89 | | info.width = geometry.width(); |
| | 63 | 90 | | info.height = geometry.height(); |
| | 63 | 91 | | info.available_x = available.x(); |
| | 63 | 92 | | info.available_y = available.y(); |
| | 63 | 93 | | info.available_width = available.width(); |
| | 63 | 94 | | info.available_height = available.height(); |
| | 63 | 95 | | info.scale_factor = screen->devicePixelRatio(); |
| | 63 | 96 | | return info; |
| | 63 | 97 | | } |
| | | 98 | | |
| | | 99 | | } // namespace |
| | | 100 | | |
| | 63 | 101 | | bool qt_screen_application_ready() { |
| | 63 | 102 | | return on_gui_thread([] { return gui_application() != nullptr; }); |
| | 63 | 103 | | } |
| | | 104 | | |
| | 21 | 105 | | rust::Vec<QtScreenInfo> qt_screen_monitors() { |
| | 21 | 106 | | return on_gui_thread([]() -> rust::Vec<QtScreenInfo> { |
| | 21 | 107 | | rust::Vec<QtScreenInfo> monitors; |
| | 21 | 108 | | if (gui_application() == nullptr) { |
| | 0 | 109 | | return monitors; // Rust turns this into an error (see ready check) |
| | 0 | 110 | | } |
| | 21 | 111 | | for (QScreen* screen : QGuiApplication::screens()) { |
| | 21 | 112 | | if (screen != nullptr) { |
| | 21 | 113 | | monitors.push_back(screen_info(screen)); |
| | 21 | 114 | | } |
| | 21 | 115 | | } |
| | 21 | 116 | | return monitors; |
| | 21 | 117 | | }); |
| | 21 | 118 | | } |
| | | 119 | | |
| | 21 | 120 | | QtScreenInfo qt_screen_primary() { |
| | 21 | 121 | | return on_gui_thread([]() -> QtScreenInfo { |
| | 21 | 122 | | if (gui_application() == nullptr) { |
| | 0 | 123 | | return QtScreenInfo{}; |
| | 0 | 124 | | } |
| | 21 | 125 | | const QScreen* primary = QGuiApplication::primaryScreen(); |
| | 21 | 126 | | return primary != nullptr ? screen_info(primary) : QtScreenInfo{}; |
| | 21 | 127 | | }); |
| | 21 | 128 | | } |
| | | 129 | | |
| | 0 | 130 | | QtScreenInfo qt_screen_at_point(double x, double y) { |
| | 0 | 131 | | return on_gui_thread([x, y]() -> QtScreenInfo { |
| | 0 | 132 | | if (gui_application() == nullptr) { |
| | 0 | 133 | | return QtScreenInfo{}; |
| | 0 | 134 | | } |
| | 0 | 135 | | const QPoint point(qRound(x), qRound(y)); |
| | 0 | 136 | | const QScreen* screen = QGuiApplication::screenAt(point); |
| | 0 | 137 | | return screen != nullptr ? screen_info(screen) : QtScreenInfo{}; |
| | 0 | 138 | | }); |
| | 0 | 139 | | } |
| | | 140 | | |
| | 21 | 141 | | QtWindowScreen qt_screen_for_window(std::uint64_t native) { |
| | 21 | 142 | | return on_gui_thread([native]() -> QtWindowScreen { |
| | 21 | 143 | | QtWindowScreen result; |
| | | 144 | | |
| | | 145 | | // 0 is the no-such-handle sentinel of `qt_window_winid`; the handle is |
| | | 146 | | // also what identifies the widget below. |
| | 21 | 147 | | const std::uint64_t wid = qt_window_winid(native); |
| | 21 | 148 | | if (wid == 0) { |
| | 0 | 149 | | return result; // window_known stays false |
| | 0 | 150 | | } |
| | 21 | 151 | | result.window_known = true; |
| | | 152 | | |
| | 21 | 153 | | QWidget* window = nullptr; |
| | 21 | 154 | | if (qobject_cast<QApplication*>(QCoreApplication::instance()) != nullptr) { |
| | | 155 | | // Only widgets that are already native can be the window behind |
| | | 156 | | // `wid`; widgets without a QWindow are skipped rather than forced. |
| | 21 | 157 | | for (QWidget* candidate : QApplication::topLevelWidgets()) { |
| | 21 | 158 | | QWindow* handle = candidate != nullptr ? candidate->windowHandle() : nullptr; |
| | 21 | 159 | | if (handle != nullptr && static_cast<std::uint64_t>(handle->winId()) == wid) { |
| | 21 | 160 | | window = candidate; |
| | 21 | 161 | | break; |
| | 21 | 162 | | } |
| | 21 | 163 | | } |
| | 21 | 164 | | } |
| | 21 | 165 | | if (window == nullptr) { |
| | 0 | 166 | | return result; // the window has no native screen yet |
| | 0 | 167 | | } |
| | | 168 | | |
| | | 169 | | // The screen of the window's platform handle, exactly as Qt exposes it |
| | | 170 | | // (`window->screen()` would fall back to the primary screen, which is a |
| | | 171 | | // guess rather than an answer). |
| | 21 | 172 | | QWindow* window_handle = window->windowHandle(); |
| | 21 | 173 | | const QScreen* screen = window_handle != nullptr ? window_handle->screen() : nullptr; |
| | 21 | 174 | | if (screen != nullptr) { |
| | 21 | 175 | | result.screen = screen_info(screen); |
| | 21 | 176 | | } |
| | 21 | 177 | | return result; |
| | 21 | 178 | | }); |
| | 21 | 179 | | } |
| | | 180 | | |
| | 0 | 181 | | QtCursorInfo qt_screen_cursor_position() { |
| | 0 | 182 | | return on_gui_thread([]() -> QtCursorInfo { |
| | 0 | 183 | | QtCursorInfo info; |
| | | 184 | | |
| | 0 | 185 | | if (gui_application() == nullptr) { |
| | 0 | 186 | | return info; // available stays false |
| | 0 | 187 | | } |
| | 0 | 188 | | if (QGuiApplication::platformName() == QStringLiteral("wayland")) { |
| | | 189 | | // Wayland has no global pointer position: clients only learn where |
| | | 190 | | // the cursor is from events on their own surfaces, so QCursor::pos() |
| | | 191 | | // cannot answer truthfully here. |
| | 0 | 192 | | return info; |
| | 0 | 193 | | } |
| | | 194 | | |
| | 0 | 195 | | const QPoint position = QCursor::pos(); |
| | 0 | 196 | | const QScreen* screen = QGuiApplication::screenAt(position); |
| | 0 | 197 | | info.available = true; |
| | 0 | 198 | | info.x = position.x(); |
| | 0 | 199 | | info.y = position.y(); |
| | 0 | 200 | | info.scale_factor = screen != nullptr ? screen->devicePixelRatio() : 1.0; |
| | 0 | 201 | | return info; |
| | 0 | 202 | | }); |
| | 0 | 203 | | } |