File indexing completed on 2024-12-01 13:37:30

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "inputpanelv1window.h"
0011 #include "core/output.h"
0012 #include "deleted.h"
0013 #include "inputmethod.h"
0014 #include "wayland/output_interface.h"
0015 #include "wayland/seat_interface.h"
0016 #include "wayland/surface_interface.h"
0017 #include "wayland/textinput_v1_interface.h"
0018 #include "wayland/textinput_v2_interface.h"
0019 #include "wayland/textinput_v3_interface.h"
0020 #include "wayland_server.h"
0021 #include "workspace.h"
0022 
0023 using namespace KWaylandServer;
0024 
0025 namespace KWin
0026 {
0027 
0028 InputPanelV1Window::InputPanelV1Window(InputPanelSurfaceV1Interface *panelSurface)
0029     : WaylandWindow(panelSurface->surface())
0030     , m_panelSurface(panelSurface)
0031 {
0032     setSkipSwitcher(true);
0033     setSkipPager(true);
0034     setSkipTaskbar(true);
0035 
0036     connect(surface(), &SurfaceInterface::aboutToBeDestroyed, this, &InputPanelV1Window::destroyWindow);
0037     connect(surface(), &SurfaceInterface::sizeChanged, this, &InputPanelV1Window::reposition);
0038     connect(surface(), &SurfaceInterface::mapped, this, &InputPanelV1Window::handleMapped);
0039 
0040     connect(panelSurface, &InputPanelSurfaceV1Interface::topLevel, this, &InputPanelV1Window::showTopLevel);
0041     connect(panelSurface, &InputPanelSurfaceV1Interface::overlayPanel, this, &InputPanelV1Window::showOverlayPanel);
0042     connect(panelSurface, &InputPanelSurfaceV1Interface::destroyed, this, &InputPanelV1Window::destroyWindow);
0043 
0044     connect(workspace(), &Workspace::outputsChanged, this, &InputPanelV1Window::reposition);
0045 
0046     kwinApp()->inputMethod()->setPanel(this);
0047 }
0048 
0049 void InputPanelV1Window::showOverlayPanel()
0050 {
0051     m_mode = Mode::Overlay;
0052     maybeShow();
0053 }
0054 
0055 void InputPanelV1Window::showTopLevel(OutputInterface *output, InputPanelSurfaceV1Interface::Position position)
0056 {
0057     m_mode = Mode::VirtualKeyboard;
0058     maybeShow();
0059 }
0060 
0061 void InputPanelV1Window::allow()
0062 {
0063     m_allowed = true;
0064     maybeShow();
0065 }
0066 
0067 void InputPanelV1Window::show()
0068 {
0069     m_virtualKeyboardShouldBeShown = true;
0070     maybeShow();
0071 }
0072 
0073 void InputPanelV1Window::hide()
0074 {
0075     m_virtualKeyboardShouldBeShown = false;
0076     if (readyForPainting() && m_mode != Mode::Overlay) {
0077         hideClient();
0078     }
0079 }
0080 
0081 void KWin::InputPanelV1Window::reposition()
0082 {
0083     if (!readyForPainting()) {
0084         return;
0085     }
0086 
0087     switch (m_mode) {
0088     case Mode::None: {
0089         // should never happen
0090     }; break;
0091     case Mode::VirtualKeyboard: {
0092         QSizeF panelSize = surface()->size();
0093         if (!panelSize.isValid() || panelSize.isEmpty()) {
0094             return;
0095         }
0096 
0097         const auto activeOutput = workspace()->activeOutput();
0098         const QRectF outputArea = activeOutput->geometry();
0099         QRectF availableArea;
0100         if (waylandServer()->isScreenLocked()) {
0101             availableArea = outputArea;
0102         } else {
0103             availableArea = workspace()->clientArea(MaximizeArea, this, activeOutput);
0104         }
0105 
0106         panelSize = panelSize.boundedTo(availableArea.size());
0107         QRectF geo(QPointF(availableArea.left(), availableArea.top() + availableArea.height() - panelSize.height()), panelSize);
0108         geo.translate((availableArea.width() - panelSize.width()) / 2, availableArea.height() - outputArea.height());
0109         moveResize(geo);
0110     } break;
0111     case Mode::Overlay: {
0112         auto textInputSurface = waylandServer()->seat()->focusedTextInputSurface();
0113         auto textWindow = waylandServer()->findWindow(textInputSurface);
0114         QRect cursorRectangle;
0115         auto textInputV1 = waylandServer()->seat()->textInputV1();
0116         if (textInputV1 && textInputV1->isEnabled() && textInputV1->surface() == textInputSurface) {
0117             cursorRectangle = textInputV1->cursorRectangle();
0118         }
0119         auto textInputV2 = waylandServer()->seat()->textInputV2();
0120         if (textInputV2 && textInputV2->isEnabled() && textInputV2->surface() == textInputSurface) {
0121             cursorRectangle = textInputV2->cursorRectangle();
0122         }
0123         auto textInputV3 = waylandServer()->seat()->textInputV3();
0124         if (textInputV3 && textInputV3->isEnabled() && textInputV3->surface() == textInputSurface) {
0125             cursorRectangle = textInputV3->cursorRectangle();
0126         }
0127         if (textWindow) {
0128             cursorRectangle.translate(textWindow->bufferGeometry().topLeft().toPoint());
0129             const QRectF screen = Workspace::self()->clientArea(PlacementArea, this, cursorRectangle.bottomLeft());
0130 
0131             // Reuse the similar logic like xdg popup
0132             QRectF popupRect(popupOffset(cursorRectangle, Qt::BottomEdge | Qt::LeftEdge, Qt::RightEdge | Qt::BottomEdge, surface()->size()), surface()->size());
0133 
0134             if (popupRect.left() < screen.left()) {
0135                 popupRect.moveLeft(screen.left());
0136             }
0137             if (popupRect.right() > screen.right()) {
0138                 popupRect.moveRight(screen.right());
0139             }
0140             if (popupRect.top() < screen.top() || popupRect.bottom() > screen.bottom()) {
0141                 auto flippedPopupRect =
0142                     QRectF(popupOffset(cursorRectangle, Qt::TopEdge | Qt::LeftEdge, Qt::RightEdge | Qt::TopEdge, surface()->size()), surface()->size());
0143 
0144                 // if it still doesn't fit we should continue with the unflipped version
0145                 if (flippedPopupRect.top() >= screen.top() || flippedPopupRect.bottom() <= screen.bottom()) {
0146                     popupRect.moveTop(flippedPopupRect.top());
0147                 }
0148             }
0149             moveResize(popupRect);
0150         }
0151     } break;
0152     }
0153 }
0154 
0155 void InputPanelV1Window::destroyWindow()
0156 {
0157     markAsZombie();
0158 
0159     Deleted *deleted = Deleted::create(this);
0160     Q_EMIT windowClosed(this, deleted);
0161     StackingUpdatesBlocker blocker(workspace());
0162     waylandServer()->removeWindow(this);
0163     deleted->unrefWindow();
0164 
0165     delete this;
0166 }
0167 
0168 NET::WindowType InputPanelV1Window::windowType(bool, int) const
0169 {
0170     return NET::Utility;
0171 }
0172 
0173 QRectF InputPanelV1Window::inputGeometry() const
0174 {
0175     return readyForPainting() ? QRectF(surface()->input().boundingRect()).translated(pos()) : QRectF();
0176 }
0177 
0178 void InputPanelV1Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode)
0179 {
0180     updateGeometry(rect);
0181 }
0182 
0183 void InputPanelV1Window::handleMapped()
0184 {
0185     updateDepth();
0186     maybeShow();
0187 }
0188 
0189 void InputPanelV1Window::maybeShow()
0190 {
0191     const bool shouldShow = m_mode == Mode::Overlay || (m_mode == Mode::VirtualKeyboard && m_allowed && m_virtualKeyboardShouldBeShown);
0192     if (shouldShow && !isZombie() && surface()->isMapped()) {
0193         setReadyForPainting();
0194         reposition();
0195         showClient();
0196     }
0197 }
0198 
0199 } // namespace KWin