/**************************************************************************** ** ** Copyright (C) 2014-2016 Canonical, 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$ ** ****************************************************************************/ // Local #include "qmirclientinput.h" #include "qmirclientintegration.h" #include "qmirclientnativeinterface.h" #include "qmirclientscreen.h" #include "qmirclientwindow.h" #include "qmirclientlogging.h" #include "qmirclientorientationchangeevent_p.h" // Qt #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(mirclientInput, "qt.qpa.mirclient.input", QtWarningMsg) namespace { // XKB Keysyms which do not map directly to Qt types (i.e. Unicode points) static const uint32_t KeyTable[] = { XKB_KEY_Escape, Qt::Key_Escape, XKB_KEY_Tab, Qt::Key_Tab, XKB_KEY_ISO_Left_Tab, Qt::Key_Backtab, XKB_KEY_BackSpace, Qt::Key_Backspace, XKB_KEY_Return, Qt::Key_Return, XKB_KEY_Insert, Qt::Key_Insert, XKB_KEY_Delete, Qt::Key_Delete, XKB_KEY_Clear, Qt::Key_Delete, XKB_KEY_Pause, Qt::Key_Pause, XKB_KEY_Print, Qt::Key_Print, XKB_KEY_Home, Qt::Key_Home, XKB_KEY_End, Qt::Key_End, XKB_KEY_Left, Qt::Key_Left, XKB_KEY_Up, Qt::Key_Up, XKB_KEY_Right, Qt::Key_Right, XKB_KEY_Down, Qt::Key_Down, XKB_KEY_Prior, Qt::Key_PageUp, XKB_KEY_Next, Qt::Key_PageDown, XKB_KEY_Shift_L, Qt::Key_Shift, XKB_KEY_Shift_R, Qt::Key_Shift, XKB_KEY_Shift_Lock, Qt::Key_Shift, XKB_KEY_Control_L, Qt::Key_Control, XKB_KEY_Control_R, Qt::Key_Control, XKB_KEY_Meta_L, Qt::Key_Meta, XKB_KEY_Meta_R, Qt::Key_Meta, XKB_KEY_Alt_L, Qt::Key_Alt, XKB_KEY_Alt_R, Qt::Key_Alt, XKB_KEY_Caps_Lock, Qt::Key_CapsLock, XKB_KEY_Num_Lock, Qt::Key_NumLock, XKB_KEY_Scroll_Lock, Qt::Key_ScrollLock, XKB_KEY_Super_L, Qt::Key_Super_L, XKB_KEY_Super_R, Qt::Key_Super_R, XKB_KEY_Menu, Qt::Key_Menu, XKB_KEY_Hyper_L, Qt::Key_Hyper_L, XKB_KEY_Hyper_R, Qt::Key_Hyper_R, XKB_KEY_Help, Qt::Key_Help, XKB_KEY_KP_Space, Qt::Key_Space, XKB_KEY_KP_Tab, Qt::Key_Tab, XKB_KEY_KP_Enter, Qt::Key_Enter, XKB_KEY_KP_Home, Qt::Key_Home, XKB_KEY_KP_Left, Qt::Key_Left, XKB_KEY_KP_Up, Qt::Key_Up, XKB_KEY_KP_Right, Qt::Key_Right, XKB_KEY_KP_Down, Qt::Key_Down, XKB_KEY_KP_Prior, Qt::Key_PageUp, XKB_KEY_KP_Next, Qt::Key_PageDown, XKB_KEY_KP_End, Qt::Key_End, XKB_KEY_KP_Begin, Qt::Key_Clear, XKB_KEY_KP_Insert, Qt::Key_Insert, XKB_KEY_KP_Delete, Qt::Key_Delete, XKB_KEY_KP_Equal, Qt::Key_Equal, XKB_KEY_KP_Multiply, Qt::Key_Asterisk, XKB_KEY_KP_Add, Qt::Key_Plus, XKB_KEY_KP_Separator, Qt::Key_Comma, XKB_KEY_KP_Subtract, Qt::Key_Minus, XKB_KEY_KP_Decimal, Qt::Key_Period, XKB_KEY_KP_Divide, Qt::Key_Slash, XKB_KEY_ISO_Level3_Shift, Qt::Key_AltGr, XKB_KEY_Multi_key, Qt::Key_Multi_key, XKB_KEY_Codeinput, Qt::Key_Codeinput, XKB_KEY_SingleCandidate, Qt::Key_SingleCandidate, XKB_KEY_MultipleCandidate, Qt::Key_MultipleCandidate, XKB_KEY_PreviousCandidate, Qt::Key_PreviousCandidate, // dead keys XKB_KEY_dead_grave, Qt::Key_Dead_Grave, XKB_KEY_dead_acute, Qt::Key_Dead_Acute, XKB_KEY_dead_circumflex, Qt::Key_Dead_Circumflex, XKB_KEY_dead_tilde, Qt::Key_Dead_Tilde, XKB_KEY_dead_macron, Qt::Key_Dead_Macron, XKB_KEY_dead_breve, Qt::Key_Dead_Breve, XKB_KEY_dead_abovedot, Qt::Key_Dead_Abovedot, XKB_KEY_dead_diaeresis, Qt::Key_Dead_Diaeresis, XKB_KEY_dead_abovering, Qt::Key_Dead_Abovering, XKB_KEY_dead_doubleacute, Qt::Key_Dead_Doubleacute, XKB_KEY_dead_caron, Qt::Key_Dead_Caron, XKB_KEY_dead_cedilla, Qt::Key_Dead_Cedilla, XKB_KEY_dead_ogonek, Qt::Key_Dead_Ogonek, XKB_KEY_dead_iota, Qt::Key_Dead_Iota, XKB_KEY_dead_voiced_sound, Qt::Key_Dead_Voiced_Sound, XKB_KEY_dead_semivoiced_sound, Qt::Key_Dead_Semivoiced_Sound, XKB_KEY_dead_belowdot, Qt::Key_Dead_Belowdot, XKB_KEY_dead_hook, Qt::Key_Dead_Hook, XKB_KEY_dead_horn, Qt::Key_Dead_Horn, XKB_KEY_Mode_switch, Qt::Key_Mode_switch, XKB_KEY_script_switch, Qt::Key_Mode_switch, XKB_KEY_XF86AudioRaiseVolume, Qt::Key_VolumeUp, XKB_KEY_XF86AudioLowerVolume, Qt::Key_VolumeDown, XKB_KEY_XF86PowerOff, Qt::Key_PowerOff, XKB_KEY_XF86PowerDown, Qt::Key_PowerDown, 0, 0 }; Qt::WindowState mirSurfaceStateToWindowState(MirSurfaceState state) { switch (state) { case mir_surface_state_fullscreen: return Qt::WindowFullScreen; case mir_surface_state_maximized: case mir_surface_state_vertmaximized: case mir_surface_state_horizmaximized: return Qt::WindowMaximized; case mir_surface_state_minimized: return Qt::WindowMinimized; case mir_surface_state_hidden: // We should be handling this state separately. Q_ASSERT(false); case mir_surface_state_restored: case mir_surface_state_unknown: default: return Qt::WindowNoState; } } } // namespace class UbuntuEvent : public QEvent { public: UbuntuEvent(QMirClientWindow* window, const MirEvent *event, QEvent::Type type) : QEvent(type), window(window) { nativeEvent = mir_event_ref(event); } ~UbuntuEvent() { mir_event_unref(nativeEvent); } QPointer window; const MirEvent *nativeEvent; }; QMirClientInput::QMirClientInput(QMirClientClientIntegration* integration) : QObject(nullptr) , mIntegration(integration) , mEventFilterType(static_cast( integration->nativeInterface())->genericEventFilterType()) , mEventType(static_cast(QEvent::registerEventType())) , mLastInputWindow(nullptr) { // Initialize touch device. mTouchDevice = new QTouchDevice; mTouchDevice->setType(QTouchDevice::TouchScreen); mTouchDevice->setCapabilities( QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::Pressure | QTouchDevice::NormalizedPosition); QWindowSystemInterface::registerTouchDevice(mTouchDevice); } QMirClientInput::~QMirClientInput() { // Qt will take care of deleting mTouchDevice. } static const char* nativeEventTypeToStr(MirEventType t) { switch (t) { case mir_event_type_key: return "key"; case mir_event_type_motion: return "motion"; case mir_event_type_surface: return "surface"; case mir_event_type_resize: return "resize"; case mir_event_type_prompt_session_state_change: return "prompt_session_state_change"; case mir_event_type_orientation: return "orientation"; case mir_event_type_close_surface: return "close_surface"; case mir_event_type_input: return "input"; case mir_event_type_keymap: return "keymap"; case mir_event_type_input_configuration: return "input_configuration"; case mir_event_type_surface_output: return "surface_output"; case mir_event_type_input_device_state: return "input_device_state"; default: return "unknown"; } } void QMirClientInput::customEvent(QEvent* event) { Q_ASSERT(QThread::currentThread() == thread()); UbuntuEvent* ubuntuEvent = static_cast(event); const MirEvent *nativeEvent = ubuntuEvent->nativeEvent; if ((ubuntuEvent->window == nullptr) || (ubuntuEvent->window->window() == nullptr)) { qCWarning(mirclient) << "Attempted to deliver an event to a non-existent window, ignoring."; return; } // Event filtering. long result; if (QWindowSystemInterface::handleNativeEvent( ubuntuEvent->window->window(), mEventFilterType, const_cast(static_cast(nativeEvent)), &result) == true) { qCDebug(mirclient, "event filtered out by native interface"); return; } qCDebug(mirclientInput, "customEvent(type=%s)", nativeEventTypeToStr(mir_event_get_type(nativeEvent))); // Event dispatching. switch (mir_event_get_type(nativeEvent)) { case mir_event_type_input: dispatchInputEvent(ubuntuEvent->window, mir_event_get_input_event(nativeEvent)); break; case mir_event_type_resize: { auto resizeEvent = mir_event_get_resize_event(nativeEvent); // Enable workaround for Screen rotation auto const targetWindow = ubuntuEvent->window; if (targetWindow) { auto const screen = static_cast(targetWindow->screen()); if (screen) { screen->handleWindowSurfaceResize( mir_resize_event_get_width(resizeEvent), mir_resize_event_get_height(resizeEvent)); } targetWindow->handleSurfaceResized( mir_resize_event_get_width(resizeEvent), mir_resize_event_get_height(resizeEvent)); } break; } case mir_event_type_surface: handleSurfaceEvent(ubuntuEvent->window, mir_event_get_surface_event(nativeEvent)); break; case mir_event_type_surface_output: handleSurfaceOutputEvent(ubuntuEvent->window, mir_event_get_surface_output_event(nativeEvent)); break; case mir_event_type_orientation: dispatchOrientationEvent(ubuntuEvent->window->window(), mir_event_get_orientation_event(nativeEvent)); break; case mir_event_type_close_surface: QWindowSystemInterface::handleCloseEvent(ubuntuEvent->window->window()); break; default: qCDebug(mirclient, "unhandled event type: %d", static_cast(mir_event_get_type(nativeEvent))); } } void QMirClientInput::postEvent(QMirClientWindow *platformWindow, const MirEvent *event) { QWindow *window = platformWindow->window(); QCoreApplication::postEvent(this, new UbuntuEvent( platformWindow, event, mEventType)); if ((window->flags().testFlag(Qt::WindowTransparentForInput)) && window->parent()) { QCoreApplication::postEvent(this, new UbuntuEvent( static_cast(platformWindow->QPlatformWindow::parent()), event, mEventType)); } } void QMirClientInput::dispatchInputEvent(QMirClientWindow *window, const MirInputEvent *ev) { switch (mir_input_event_get_type(ev)) { case mir_input_event_type_key: dispatchKeyEvent(window, ev); break; case mir_input_event_type_touch: dispatchTouchEvent(window, ev); break; case mir_input_event_type_pointer: dispatchPointerEvent(window, ev); break; default: break; } } void QMirClientInput::dispatchTouchEvent(QMirClientWindow *window, const MirInputEvent *ev) { const MirTouchEvent *tev = mir_input_event_get_touch_event(ev); // FIXME(loicm) Max pressure is device specific. That one is for the Samsung Galaxy Nexus. That // needs to be fixed as soon as the compat input lib adds query support. const float kMaxPressure = 1.28; const QRect kWindowGeometry = window->geometry(); QList touchPoints; // TODO: Is it worth setting the Qt::TouchPointStationary ones? Currently they are left // as Qt::TouchPointMoved const unsigned int kPointerCount = mir_touch_event_point_count(tev); touchPoints.reserve(int(kPointerCount)); for (unsigned int i = 0; i < kPointerCount; ++i) { QWindowSystemInterface::TouchPoint touchPoint; const float kX = mir_touch_event_axis_value(tev, i, mir_touch_axis_x) + kWindowGeometry.x(); const float kY = mir_touch_event_axis_value(tev, i, mir_touch_axis_y) + kWindowGeometry.y(); // see bug lp:1346633 workaround comments elsewhere const float kW = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_major); const float kH = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_minor); const float kP = mir_touch_event_axis_value(tev, i, mir_touch_axis_pressure); touchPoint.id = mir_touch_event_id(tev, i); touchPoint.normalPosition = QPointF(kX / kWindowGeometry.width(), kY / kWindowGeometry.height()); touchPoint.area = QRectF(kX - (kW / 2.0), kY - (kH / 2.0), kW, kH); touchPoint.pressure = kP / kMaxPressure; MirTouchAction touch_action = mir_touch_event_action(tev, i); switch (touch_action) { case mir_touch_action_down: mLastInputWindow = window; touchPoint.state = Qt::TouchPointPressed; break; case mir_touch_action_up: touchPoint.state = Qt::TouchPointReleased; break; case mir_touch_action_change: touchPoint.state = Qt::TouchPointMoved; break; default: Q_UNREACHABLE(); } touchPoints.append(touchPoint); } ulong timestamp = mir_input_event_get_event_time(ev) / 1000000; QWindowSystemInterface::handleTouchEvent(window->window(), timestamp, mTouchDevice, touchPoints); } static uint32_t translateKeysym(uint32_t sym, const QString &text) { int code = 0; QTextCodec *systemCodec = QTextCodec::codecForLocale(); if (sym < 128 || (sym < 256 && systemCodec->mibEnum() == 4)) { // upper-case key, if known code = isprint((int)sym) ? toupper((int)sym) : 0; } else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F35) { return Qt::Key_F1 + (int(sym) - XKB_KEY_F1); } else if (text.length() == 1 && text.unicode()->unicode() > 0x1f && text.unicode()->unicode() != 0x7f && !(sym >= XKB_KEY_dead_grave && sym <= XKB_KEY_dead_currency)) { code = text.unicode()->toUpper().unicode(); } else { for (int i = 0; KeyTable[i]; i += 2) if (sym == KeyTable[i]) code = KeyTable[i + 1]; } return code; } namespace { Qt::KeyboardModifiers qt_modifiers_from_mir(MirInputEventModifiers modifiers) { Qt::KeyboardModifiers q_modifiers = Qt::NoModifier; if (modifiers & mir_input_event_modifier_shift) { q_modifiers |= Qt::ShiftModifier; } if (modifiers & mir_input_event_modifier_ctrl) { q_modifiers |= Qt::ControlModifier; } if (modifiers & mir_input_event_modifier_alt_left) { q_modifiers |= Qt::AltModifier; } if (modifiers & mir_input_event_modifier_meta) { q_modifiers |= Qt::MetaModifier; } if (modifiers & mir_input_event_modifier_alt_right) { q_modifiers |= Qt::GroupSwitchModifier; } return q_modifiers; } } void QMirClientInput::dispatchKeyEvent(QMirClientWindow *window, const MirInputEvent *event) { const MirKeyboardEvent *key_event = mir_input_event_get_keyboard_event(event); ulong timestamp = mir_input_event_get_event_time(event) / 1000000; xkb_keysym_t xk_sym = mir_keyboard_event_key_code(key_event); quint32 scan_code = mir_keyboard_event_scan_code(key_event); quint32 native_modifiers = mir_keyboard_event_modifiers(key_event); // Key modifier and unicode index mapping. auto modifiers = qt_modifiers_from_mir(native_modifiers); MirKeyboardAction action = mir_keyboard_event_action(key_event); QEvent::Type keyType = action == mir_keyboard_action_up ? QEvent::KeyRelease : QEvent::KeyPress; if (action == mir_keyboard_action_down) mLastInputWindow = window; QString text; QVarLengthArray chars(32); { int result = xkb_keysym_to_utf8(xk_sym, chars.data(), chars.size()); if (result > 0) { text = QString::fromUtf8(chars.constData()); } } int sym = translateKeysym(xk_sym, text); bool is_auto_rep = action == mir_keyboard_action_repeat; QPlatformInputContext *context = QGuiApplicationPrivate::platformIntegration()->inputContext(); if (context) { QKeyEvent qKeyEvent(keyType, sym, modifiers, scan_code, xk_sym, native_modifiers, text, is_auto_rep); qKeyEvent.setTimestamp(timestamp); if (context->filterEvent(&qKeyEvent)) { qCDebug(mirclient, "key event filtered out by input context"); return; } } QWindowSystemInterface::handleExtendedKeyEvent(window->window(), timestamp, keyType, sym, modifiers, scan_code, xk_sym, native_modifiers, text, is_auto_rep); } namespace { Qt::MouseButtons extract_buttons(const MirPointerEvent *pev) { Qt::MouseButtons buttons = Qt::NoButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_primary)) buttons |= Qt::LeftButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_secondary)) buttons |= Qt::RightButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_tertiary)) buttons |= Qt::MiddleButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_back)) buttons |= Qt::BackButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_forward)) buttons |= Qt::ForwardButton; return buttons; } } void QMirClientInput::dispatchPointerEvent(QMirClientWindow *platformWindow, const MirInputEvent *ev) { const auto window = platformWindow->window(); const auto timestamp = mir_input_event_get_event_time(ev) / 1000000; const auto pev = mir_input_event_get_pointer_event(ev); const auto action = mir_pointer_event_action(pev); const auto modifiers = qt_modifiers_from_mir(mir_pointer_event_modifiers(pev)); const auto localPoint = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_x), mir_pointer_event_axis_value(pev, mir_pointer_axis_y)); mLastInputWindow = platformWindow; switch (action) { case mir_pointer_action_button_up: case mir_pointer_action_button_down: case mir_pointer_action_motion: { const float hDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_hscroll); const float vDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_vscroll); if (hDelta != 0 || vDelta != 0) { // QWheelEvent::DefaultDeltasPerStep = 120 but doesn't exist on vivid const QPoint angleDelta(120 * hDelta, 120 * vDelta); QWindowSystemInterface::handleWheelEvent(window, timestamp, localPoint, window->position() + localPoint, QPoint(), angleDelta, modifiers, Qt::ScrollUpdate); } auto buttons = extract_buttons(pev); QWindowSystemInterface::handleMouseEvent(window, timestamp, localPoint, window->position() + localPoint /* Should we omit global point instead? */, buttons, modifiers); break; } case mir_pointer_action_enter: QWindowSystemInterface::handleEnterEvent(window, localPoint, window->position() + localPoint); break; case mir_pointer_action_leave: QWindowSystemInterface::handleLeaveEvent(window); break; default: Q_UNREACHABLE(); } } static const char* nativeOrientationDirectionToStr(MirOrientation orientation) { switch (orientation) { case mir_orientation_normal: return "Normal"; case mir_orientation_left: return "Left"; case mir_orientation_inverted: return "Inverted"; case mir_orientation_right: return "Right"; } Q_UNREACHABLE(); } void QMirClientInput::dispatchOrientationEvent(QWindow *window, const MirOrientationEvent *event) { MirOrientation mir_orientation = mir_orientation_event_get_direction(event); qCDebug(mirclientInput, "orientation direction: %s", nativeOrientationDirectionToStr(mir_orientation)); if (!window->screen()) { qCDebug(mirclient, "Window has no associated screen, dropping orientation event"); return; } OrientationChangeEvent::Orientation orientation; switch (mir_orientation) { case mir_orientation_normal: orientation = OrientationChangeEvent::TopUp; break; case mir_orientation_left: orientation = OrientationChangeEvent::LeftUp; break; case mir_orientation_inverted: orientation = OrientationChangeEvent::TopDown; break; case mir_orientation_right: orientation = OrientationChangeEvent::RightUp; break; default: qCDebug(mirclient, "No such orientation %d", mir_orientation); return; } // Dispatch orientation event to [Platform]Screen, as that is where Qt reads it. Screen will handle // notifying Qt of the actual orientation change - done to prevent multiple Windows each creating // an identical orientation change event and passing it directly to Qt. // [Platform]Screen can also factor in the native orientation. QCoreApplication::postEvent(static_cast(window->screen()->handle()), new OrientationChangeEvent(OrientationChangeEvent::mType, orientation)); } void QMirClientInput::handleSurfaceEvent(const QPointer &window, const MirSurfaceEvent *event) { auto surfaceEventAttribute = mir_surface_event_get_attribute(event); switch (surfaceEventAttribute) { case mir_surface_attrib_focus: { window->handleSurfaceFocusChanged( mir_surface_event_get_attribute_value(event) == mir_surface_focused); break; } case mir_surface_attrib_visibility: { window->handleSurfaceExposeChange( mir_surface_event_get_attribute_value(event) == mir_surface_visibility_exposed); break; } // Remaining attributes are ones client sets for server, and server should not override them case mir_surface_attrib_state: { MirSurfaceState state = static_cast(mir_surface_event_get_attribute_value(event)); if (state == mir_surface_state_hidden) { window->handleSurfaceVisibilityChanged(false); } else { // it's visible! window->handleSurfaceVisibilityChanged(true); window->handleSurfaceStateChanged(mirSurfaceStateToWindowState(state)); } break; } case mir_surface_attrib_type: case mir_surface_attrib_swapinterval: case mir_surface_attrib_dpi: case mir_surface_attrib_preferred_orientation: case mir_surface_attribs: break; } } void QMirClientInput::handleSurfaceOutputEvent(const QPointer &window, const MirSurfaceOutputEvent *event) { const uint32_t outputId = mir_surface_output_event_get_output_id(event); const int dpi = mir_surface_output_event_get_dpi(event); const MirFormFactor formFactor = mir_surface_output_event_get_form_factor(event); const float scale = mir_surface_output_event_get_scale(event); const auto screenObserver = mIntegration->screenObserver(); QMirClientScreen *screen = screenObserver->findScreenWithId(outputId); if (!screen) { qCWarning(mirclient) << "Mir notified window" << window->window() << "on an unknown screen with id" << outputId; return; } screenObserver->handleScreenPropertiesChange(screen, dpi, formFactor, scale); window->handleScreenPropertiesChange(formFactor, scale); if (window->screen() != screen) { QWindowSystemInterface::handleWindowScreenChanged(window->window(), screen->screen()); } }