| | | 1 | | // SPDX-FileCopyrightText: 2026 Alper Çelik |
| | | 2 | | // SPDX-License-Identifier: MIT OR Apache-2.0 |
| | | 3 | | |
| | | 4 | | //! Screen / monitor / cursor operations (owned by the screen worker). |
| | | 5 | | //! |
| | | 6 | | //! This is the provider module other workers consume for monitors: the window |
| | | 7 | | //! and runtime workers must call these functions instead of querying Qt |
| | | 8 | | //! themselves, so the mapping lives in exactly one place. |
| | | 9 | | //! |
| | | 10 | | //! Units: Qt answers in device-independent (logical) global pixels — the space |
| | | 11 | | //! `QScreen::geometry()` and `QGuiApplication::screenAt()` use, and the one |
| | | 12 | | //! tao accepts for `monitor_from_point` on Linux |
| | | 13 | | //! (`gdk_display_get_monitor_at_point`). The values returned here are physical |
| | | 14 | | //! pixels instead: logical lengths are multiplied by the screen's |
| | | 15 | | //! `devicePixelRatio` with the same rounding as `dpi`'s `to_physical`, which is |
| | | 16 | | //! what tao reports for `Monitor` on X11. So the `monitor_from_point` argument is |
| | | 17 | | //! logical while `Monitor::position`/`size` are physical, exactly as in tao. |
| | | 18 | | //! `cursor_position` is physical too (Qt's logical position times the scale |
| | | 19 | | //! factor of the screen under the cursor). |
| | | 20 | | |
| | | 21 | | use tauri_runtime::dpi::{PhysicalPosition, PhysicalRect, PhysicalSize}; |
| | | 22 | | use tauri_runtime::monitor::Monitor; |
| | | 23 | | use tauri_runtime::{Error, Result}; |
| | | 24 | | use tauri_runtime_qt_core as qtcore; |
| | | 25 | | |
| | | 26 | | use crate::bridge::screen::ffi::{self, QtScreenInfo}; |
| | | 27 | | |
| | | 28 | | /// Turns one Qt screen into a tauri [`Monitor`] (physical pixels). |
| | 63 | 29 | | fn to_monitor(info: &QtScreenInfo) -> Monitor { |
| | | 30 | | // A missing/zero ratio would silently collapse every value; Qt always |
| | | 31 | | // reports >= 1, so only an impossible value falls back to 1. |
| | 63 | 32 | | let scale_factor = qtcore::screen::scale_factor_or_one(info.scale_factor); |
| | | 33 | | |
| | | 34 | | Monitor { |
| | 63 | 35 | | name: (!info.name.is_empty()).then(|| info.name.clone()), |
| | 63 | 36 | | size: PhysicalSize::new( |
| | 63 | 37 | | qtcore::screen::to_physical_u32(info.width, scale_factor), |
| | 63 | 38 | | qtcore::screen::to_physical_u32(info.height, scale_factor), |
| | | 39 | | ), |
| | 63 | 40 | | position: PhysicalPosition::new( |
| | 63 | 41 | | qtcore::screen::to_physical(info.x, scale_factor), |
| | 63 | 42 | | qtcore::screen::to_physical(info.y, scale_factor), |
| | | 43 | | ), |
| | 63 | 44 | | work_area: PhysicalRect { |
| | 63 | 45 | | position: PhysicalPosition::new( |
| | 63 | 46 | | qtcore::screen::to_physical(info.available_x, scale_factor), |
| | 63 | 47 | | qtcore::screen::to_physical(info.available_y, scale_factor), |
| | 63 | 48 | | ), |
| | 63 | 49 | | size: PhysicalSize::new( |
| | 63 | 50 | | qtcore::screen::to_physical_u32(info.available_width, scale_factor), |
| | 63 | 51 | | qtcore::screen::to_physical_u32(info.available_height, scale_factor), |
| | 63 | 52 | | ), |
| | 63 | 53 | | }, |
| | 63 | 54 | | scale_factor, |
| | | 55 | | } |
| | 63 | 56 | | } |
| | | 57 | | |
| | | 58 | | /// Monitors can only be answered once Qt has a live `QGuiApplication`; before |
| | | 59 | | /// that, reporting an empty list would claim there are no screens. |
| | 63 | 60 | | fn ensure_ready() -> Result<()> { |
| | 63 | 61 | | if ffi::qt_screen_application_ready() { |
| | 63 | 62 | | Ok(()) |
| | | 63 | | } else { |
| | 0 | 64 | | Err(Error::FailedToGetMonitor) |
| | | 65 | | } |
| | 63 | 66 | | } |
| | | 67 | | |
| | | 68 | | /// All monitors, in Qt's screen order. |
| | 21 | 69 | | pub(crate) fn available_monitors() -> Result<Vec<Monitor>> { |
| | 21 | 70 | | ensure_ready()?; |
| | 21 | 71 | | let monitors: Vec<Monitor> = ffi::qt_screen_monitors().iter().map(to_monitor).collect(); |
| | | 72 | | |
| | 21 | 73 | | if std::env::var_os("TAURIQT_SCREEN_DEBUG").is_some() { |
| | 0 | 74 | | eprintln!("[tauri-runtime-qt] available_monitors: {monitors:#?}"); |
| | 21 | 75 | | } |
| | | 76 | | |
| | 21 | 77 | | Ok(monitors) |
| | 21 | 78 | | } |
| | | 79 | | |
| | | 80 | | /// The primary monitor, if the platform identifies one. |
| | 21 | 81 | | pub(crate) fn primary_monitor() -> Result<Option<Monitor>> { |
| | 21 | 82 | | ensure_ready()?; |
| | 21 | 83 | | let info = ffi::qt_screen_primary(); |
| | 21 | 84 | | Ok(info.valid.then(|| to_monitor(&info))) |
| | 21 | 85 | | } |
| | | 86 | | |
| | | 87 | | /// The monitor containing the given desktop point (device-independent global |
| | | 88 | | /// pixels, the space `QGuiApplication::screenAt` uses). |
| | 0 | 89 | | pub(crate) fn monitor_from_point(x: f64, y: f64) -> Result<Option<Monitor>> { |
| | 0 | 90 | | ensure_ready()?; |
| | 0 | 91 | | let info = ffi::qt_screen_at_point(x, y); |
| | 0 | 92 | | Ok(info.valid.then(|| to_monitor(&info))) |
| | 0 | 93 | | } |
| | | 94 | | |
| | | 95 | | /// The monitor the given window currently sits on. |
| | 21 | 96 | | pub(crate) fn monitor_for_window(native: u64) -> Result<Option<Monitor>> { |
| | 21 | 97 | | ensure_ready()?; |
| | 21 | 98 | | let lookup = ffi::qt_screen_for_window(native); |
| | 21 | 99 | | if !lookup.window_known { |
| | 0 | 100 | | return Err(Error::WindowNotFound); |
| | 21 | 101 | | } |
| | 21 | 102 | | Ok(lookup.screen.valid.then(|| to_monitor(&lookup.screen))) |
| | 21 | 103 | | } |
| | | 104 | | |
| | | 105 | | /// Cursor position relative to the top-left corner of the virtual desktop |
| | | 106 | | /// (physical pixels; Qt reports device-independent ones). |
| | 0 | 107 | | pub(crate) fn cursor_position() -> Result<PhysicalPosition<f64>> { |
| | 0 | 108 | | let cursor = ffi::qt_screen_cursor_position(); |
| | 0 | 109 | | if !cursor.available { |
| | | 110 | | // Wayland does not expose a global pointer position (and without a |
| | | 111 | | // QGuiApplication there is nothing to ask) — say so instead of |
| | | 112 | | // reporting a stale or invented point. |
| | 0 | 113 | | return Err(Error::FailedToGetCursorPosition); |
| | 0 | 114 | | } |
| | 0 | 115 | | let scale_factor = qtcore::screen::scale_factor_or_one(cursor.scale_factor); |
| | 0 | 116 | | Ok(PhysicalPosition::new( |
| | 0 | 117 | | cursor.x * scale_factor, |
| | 0 | 118 | | cursor.y * scale_factor, |
| | 0 | 119 | | )) |
| | 0 | 120 | | } |