File indexing completed on 2024-04-28 05:30:21

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 "inputmethod.h"
0013 #include "wayland/output.h"
0014 #include "wayland/seat.h"
0015 #include "wayland/surface.h"
0016 #include "wayland/textinput_v1.h"
0017 #include "wayland/textinput_v2.h"
0018 #include "wayland/textinput_v3.h"
0019 #include "wayland_server.h"
0020 #include "workspace.h"
0021 
0022 namespace KWin
0023 {
0024 
0025 InputPanelV1Window::InputPanelV1Window(InputPanelSurfaceV1Interface *panelSurface)
0026     : WaylandWindow(panelSurface->surface())
0027     , m_panelSurface(panelSurface)
0028 {
0029     setSkipSwitcher(true);
0030     setSkipPager(true);
0031     setSkipTaskbar(true);
0032 
0033     connect(surface(), &SurfaceInterface::aboutToBeDestroyed, this, &InputPanelV1Window::destroyWindow);
0034     connect(surface(), &SurfaceInterface::sizeChanged, this, &InputPanelV1Window::reposition);
0035     connect(surface(), &SurfaceInterface::inputChanged, this, &InputPanelV1Window::reposition);
0036     connect(surface(), &SurfaceInterface::mapped, this, &InputPanelV1Window::handleMapped);
0037 
0038     connect(panelSurface, &InputPanelSurfaceV1Interface::topLevel, this, &InputPanelV1Window::showTopLevel);
0039     connect(panelSurface, &InputPanelSurfaceV1Interface::overlayPanel, this, &InputPanelV1Window::showOverlayPanel);
0040     connect(panelSurface, &InputPanelSurfaceV1Interface::aboutToBeDestroyed, this, &InputPanelV1Window::destroyWindow);
0041 
0042     connect(workspace(), &Workspace::outputsChanged, this, &InputPanelV1Window::reposition);
0043 
0044     kwinApp()->inputMethod()->setPanel(this);
0045 }
0046 
0047 void InputPanelV1Window::showOverlayPanel()
0048 {
0049     m_mode = Mode::Overlay;
0050     maybeShow();
0051 }
0052 
0053 void InputPanelV1Window::showTopLevel(OutputInterface *output, InputPanelSurfaceV1Interface::Position position)
0054 {
0055     m_mode = Mode::VirtualKeyboard;
0056     maybeShow();
0057 }
0058 
0059 void InputPanelV1Window::allow()
0060 {
0061     m_allowed = true;
0062     maybeShow();
0063 }
0064 
0065 void InputPanelV1Window::show()
0066 {
0067     m_virtualKeyboardShouldBeShown = true;
0068     maybeShow();
0069 }
0070 
0071 void InputPanelV1Window::hide()
0072 {
0073     m_virtualKeyboardShouldBeShown = false;
0074     if (readyForPainting() && m_mode != Mode::Overlay) {
0075         setHidden(true);
0076     }
0077 }
0078 
0079 void InputPanelV1Window::reposition()
0080 {
0081     if (!readyForPainting()) {
0082         return;
0083     }
0084 
0085     switch (m_mode) {
0086     case Mode::None: {
0087         // should never happen
0088     }; break;
0089     case Mode::VirtualKeyboard: {
0090         // maliit creates a fullscreen overlay so use the input shape as the window geometry.
0091         m_windowGeometry = surface()->input().boundingRect();
0092 
0093         const auto activeOutput = workspace()->activeOutput();
0094         QRectF availableArea;
0095         if (waylandServer()->isScreenLocked()) {
0096             availableArea = workspace()->clientArea(FullScreenArea, this, activeOutput);
0097         } else {
0098             availableArea = workspace()->clientArea(MaximizeArea, this, activeOutput);
0099         }
0100 
0101         QRectF geo = m_windowGeometry;
0102         geo.moveLeft(availableArea.left() + (availableArea.width() - geo.width()) / 2);
0103         geo.moveBottom(availableArea.bottom());
0104 
0105         moveResize(geo);
0106     } break;
0107     case Mode::Overlay: {
0108         auto textInputSurface = waylandServer()->seat()->focusedTextInputSurface();
0109         auto textWindow = waylandServer()->findWindow(textInputSurface);
0110         QRect cursorRectangle;
0111         auto textInputV1 = waylandServer()->seat()->textInputV1();
0112         if (textInputV1 && textInputV1->isEnabled() && textInputV1->surface() == textInputSurface) {
0113             cursorRectangle = textInputV1->cursorRectangle();
0114         }
0115         auto textInputV2 = waylandServer()->seat()->textInputV2();
0116         if (textInputV2 && textInputV2->isEnabled() && textInputV2->surface() == textInputSurface) {
0117             cursorRectangle = textInputV2->cursorRectangle();
0118         }
0119         auto textInputV3 = waylandServer()->seat()->textInputV3();
0120         if (textInputV3 && textInputV3->isEnabled() && textInputV3->surface() == textInputSurface) {
0121             cursorRectangle = textInputV3->cursorRectangle();
0122         }
0123         if (textWindow) {
0124             cursorRectangle.translate(textWindow->bufferGeometry().topLeft().toPoint());
0125             const QRectF screen = Workspace::self()->clientArea(PlacementArea, this, cursorRectangle.bottomLeft());
0126 
0127             m_windowGeometry = QRectF(QPointF(0, 0), surface()->size());
0128 
0129             // Reuse the similar logic like xdg popup
0130             QRectF popupRect(popupOffset(cursorRectangle, Qt::BottomEdge | Qt::LeftEdge, Qt::RightEdge | Qt::BottomEdge, m_windowGeometry.size()), m_windowGeometry.size());
0131 
0132             if (popupRect.left() < screen.left()) {
0133                 popupRect.moveLeft(screen.left());
0134             }
0135             if (popupRect.right() > screen.right()) {
0136                 popupRect.moveRight(screen.right());
0137             }
0138             if (popupRect.top() < screen.top() || popupRect.bottom() > screen.bottom()) {
0139                 auto flippedPopupRect =
0140                     QRectF(popupOffset(cursorRectangle, Qt::TopEdge | Qt::LeftEdge, Qt::RightEdge | Qt::TopEdge, m_windowGeometry.size()), m_windowGeometry.size());
0141 
0142                 // if it still doesn't fit we should continue with the unflipped version
0143                 if (flippedPopupRect.top() >= screen.top() && flippedPopupRect.bottom() <= screen.bottom()) {
0144                     popupRect.moveTop(flippedPopupRect.top());
0145                 }
0146             }
0147             if (popupRect.top() < screen.top()) {
0148                 popupRect.moveTop(screen.top());
0149             }
0150             if (popupRect.bottom() > screen.bottom()) {
0151                 popupRect.moveBottom(screen.bottom());
0152             }
0153 
0154             moveResize(popupRect);
0155         }
0156     } break;
0157     }
0158 }
0159 
0160 void InputPanelV1Window::destroyWindow()
0161 {
0162     m_panelSurface->disconnect(this);
0163     m_panelSurface->surface()->disconnect(this);
0164 
0165     markAsDeleted();
0166 
0167     Q_EMIT closed();
0168     StackingUpdatesBlocker blocker(workspace());
0169     waylandServer()->removeWindow(this);
0170 
0171     unref();
0172 }
0173 
0174 NET::WindowType InputPanelV1Window::windowType() const
0175 {
0176     return NET::Utility;
0177 }
0178 
0179 QRectF InputPanelV1Window::frameRectToBufferRect(const QRectF &rect) const
0180 {
0181     return QRectF(rect.topLeft() - m_windowGeometry.topLeft(), surface()->size());
0182 }
0183 
0184 void InputPanelV1Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode)
0185 {
0186     updateGeometry(rect);
0187 }
0188 
0189 void InputPanelV1Window::handleMapped()
0190 {
0191     maybeShow();
0192 }
0193 
0194 void InputPanelV1Window::maybeShow()
0195 {
0196     const bool shouldShow = m_mode == Mode::Overlay || (m_mode == Mode::VirtualKeyboard && m_allowed && m_virtualKeyboardShouldBeShown);
0197     if (shouldShow && !isDeleted() && surface()->isMapped()) {
0198         markAsMapped();
0199         reposition();
0200         setHidden(false);
0201     }
0202 }
0203 
0204 } // namespace KWin
0205 
0206 #include "moc_inputpanelv1window.cpp"