482 lines
15 KiB
C++
482 lines
15 KiB
C++
// Copyright (C) 2024 UnionTech Software Technology Co., Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
#include "rootsurfacecontainer.h"
|
|
#include "helper.h"
|
|
#include "surfacewrapper.h"
|
|
#include "output.h"
|
|
|
|
#include <woutputlayout.h>
|
|
#include <wcursor.h>
|
|
#include <woutputitem.h>
|
|
#include <woutput.h>
|
|
#include <wxdgsurface.h>
|
|
#include <wxdgpopupsurface.h>
|
|
|
|
#include <qwoutputlayout.h>
|
|
|
|
#include <QQuickWindow>
|
|
|
|
WAYLIB_SERVER_USE_NAMESPACE
|
|
|
|
OutputListModel::OutputListModel(QObject *parent)
|
|
: ObjectListModel("output", parent)
|
|
{
|
|
|
|
}
|
|
|
|
RootSurfaceContainer::RootSurfaceContainer(QQuickItem *parent)
|
|
: SurfaceContainer(parent)
|
|
, m_outputModel(new OutputListModel(this))
|
|
, m_cursor(new WCursor(this))
|
|
{
|
|
m_cursor->setEventWindow(window());
|
|
}
|
|
|
|
void RootSurfaceContainer::init(WServer *server)
|
|
{
|
|
m_outputLayout = new WOutputLayout(server);
|
|
m_cursor->setLayout(m_outputLayout);
|
|
|
|
connect(m_outputLayout, &WOutputLayout::implicitWidthChanged, this, [this] {
|
|
const auto width = m_outputLayout->implicitWidth();
|
|
window()->setWidth(width);
|
|
setWidth(width);
|
|
});
|
|
|
|
connect(m_outputLayout, &WOutputLayout::implicitHeightChanged, this, [this] {
|
|
const auto height = m_outputLayout->implicitHeight();
|
|
window()->setHeight(height);
|
|
setHeight(height);
|
|
});
|
|
|
|
m_outputLayout->safeConnect(&qw_output_layout::notify_change, this, [this] {
|
|
for (auto output : std::as_const(outputs())) {
|
|
output->updatePositionFromLayout();
|
|
}
|
|
|
|
ensureCursorVisible();
|
|
|
|
// for (auto s : m_surfaceContainer->surfaces()) {
|
|
// ensureSurfaceNormalPositionValid(s);
|
|
// updateSurfaceOutputs(s);
|
|
// }
|
|
});
|
|
|
|
m_dragSurfaceItem = new WSurfaceItem(window()->contentItem());
|
|
m_dragSurfaceItem->setZ(static_cast<std::underlying_type_t<WOutputLayout::Layer>>(WOutputLayout::Layer::Cursor) - 1);
|
|
m_dragSurfaceItem->setFlags(WSurfaceItem::DontCacheLastBuffer);
|
|
|
|
m_cursor->safeConnect(&WCursor::positionChanged, this, [this] {
|
|
m_dragSurfaceItem->setPosition(m_cursor->position());
|
|
});
|
|
|
|
m_cursor->safeConnect(&WCursor::requestedDragSurfaceChanged, this, [this] {
|
|
m_dragSurfaceItem->setSurface(m_cursor->requestedDragSurface());
|
|
});
|
|
}
|
|
|
|
SurfaceWrapper *RootSurfaceContainer::getSurface(WSurface *surface) const
|
|
{
|
|
const auto surfaces = this->surfaces();
|
|
for (const auto &wrapper: surfaces) {
|
|
if (wrapper->surface() == surface)
|
|
return wrapper;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SurfaceWrapper *RootSurfaceContainer::getSurface(WToplevelSurface *surface) const
|
|
{
|
|
const auto surfaces = this->surfaces();
|
|
for (const auto &wrapper: surfaces) {
|
|
if (wrapper->shellSurface() == surface)
|
|
return wrapper;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void RootSurfaceContainer::destroyForSurface(WSurface *surface)
|
|
{
|
|
auto wrapper = getSurface(surface);
|
|
if (wrapper == moveResizeState.surface)
|
|
endMoveResize();
|
|
|
|
delete wrapper;
|
|
}
|
|
|
|
void RootSurfaceContainer::addOutput(Output *output)
|
|
{
|
|
m_outputModel->addObject(output);
|
|
m_outputLayout->autoAdd(output->output());
|
|
if (!m_primaryOutput)
|
|
setPrimaryOutput(output);
|
|
|
|
SurfaceContainer::addOutput(output);
|
|
}
|
|
|
|
void RootSurfaceContainer::removeOutput(Output *output)
|
|
{
|
|
m_outputModel->removeObject(output);
|
|
SurfaceContainer::removeOutput(output);
|
|
|
|
if (moveResizeState.surface && moveResizeState.surface->ownsOutput() == output) {
|
|
endMoveResize();
|
|
}
|
|
|
|
m_outputLayout->remove(output->output());
|
|
if (m_primaryOutput == output) {
|
|
const auto outputs = m_outputLayout->outputs();
|
|
if (!outputs.isEmpty()) {
|
|
auto newPrimaryOutput = Helper::instance()->getOutput(outputs.first());
|
|
setPrimaryOutput(newPrimaryOutput);
|
|
}
|
|
}
|
|
|
|
// ensure cursor within output
|
|
const auto outputPos = output->outputItem()->position();
|
|
if (output->geometry().contains(m_cursor->position()) && m_primaryOutput) {
|
|
const auto posInOutput = m_cursor->position() - outputPos;
|
|
const auto newCursorPos = m_primaryOutput->outputItem()->position() + posInOutput;
|
|
|
|
if (m_primaryOutput->geometry().contains(newCursorPos))
|
|
Helper::instance()->setCursorPosition(newCursorPos);
|
|
else
|
|
Helper::instance()->setCursorPosition(m_primaryOutput->geometry().center());
|
|
}
|
|
}
|
|
|
|
void RootSurfaceContainer::beginMoveResize(SurfaceWrapper *surface, Qt::Edges edges)
|
|
{
|
|
if (surface->surfaceState() != SurfaceWrapper::State::Normal
|
|
|| surface->isAnimationRunning())
|
|
return;
|
|
|
|
Q_ASSERT(!moveResizeState.surface);
|
|
moveResizeState.surface = surface;
|
|
moveResizeState.startGeometry = surface->geometry();
|
|
moveResizeState.resizeEdges = edges;
|
|
surface->setXwaylandPositionFromSurface(false);
|
|
surface->setPositionAutomatic(false);
|
|
}
|
|
|
|
void RootSurfaceContainer::doMoveResize(const QPointF &incrementPos)
|
|
{
|
|
Q_ASSERT(moveResizeState.surface);
|
|
|
|
if (moveResizeState.resizeEdges) {
|
|
QRectF geo = moveResizeState.startGeometry;
|
|
|
|
if (moveResizeState.resizeEdges & Qt::LeftEdge)
|
|
geo.setLeft(geo.left() + incrementPos.x());
|
|
if (moveResizeState.resizeEdges & Qt::TopEdge)
|
|
geo.setTop(geo.top() + incrementPos.y());
|
|
|
|
if (moveResizeState.resizeEdges & Qt::RightEdge)
|
|
geo.setRight(geo.right() + incrementPos.x());
|
|
if (moveResizeState.resizeEdges & Qt::BottomEdge)
|
|
geo.setBottom(geo.bottom() + incrementPos.y());
|
|
|
|
moveResizeState.surface->resize(geo.size());
|
|
} else {
|
|
auto new_pos = moveResizeState.startGeometry.topLeft() + incrementPos;
|
|
moveResizeState.surface->setPosition(new_pos);
|
|
}
|
|
}
|
|
|
|
void RootSurfaceContainer::cancelMoveResize(SurfaceWrapper *surface)
|
|
{
|
|
if (moveResizeState.surface != surface)
|
|
return;
|
|
endMoveResize();
|
|
}
|
|
|
|
void RootSurfaceContainer::endMoveResize()
|
|
{
|
|
if (!moveResizeState.surface)
|
|
return;
|
|
|
|
auto o = moveResizeState.surface->ownsOutput();
|
|
moveResizeState.surface->shellSurface()->setResizeing(false);
|
|
|
|
if (!o || !moveResizeState.surface->surface()->outputs().contains(o->output())) {
|
|
o = cursorOutput();
|
|
Q_ASSERT(o);
|
|
moveResizeState.surface->setOwnsOutput(o);
|
|
}
|
|
|
|
ensureSurfaceNormalPositionValid(moveResizeState.surface);
|
|
|
|
moveResizeState.surface->setXwaylandPositionFromSurface(true);
|
|
moveResizeState.surface = nullptr;
|
|
Q_EMIT moveResizeFinised();
|
|
}
|
|
|
|
SurfaceWrapper *RootSurfaceContainer::moveResizeSurface() const
|
|
{
|
|
return moveResizeState.surface;
|
|
}
|
|
|
|
void RootSurfaceContainer::startMove(SurfaceWrapper *surface)
|
|
{
|
|
endMoveResize();
|
|
beginMoveResize(surface, Qt::Edges{0});
|
|
|
|
Helper::instance()->activeSurface(surface);
|
|
}
|
|
|
|
void RootSurfaceContainer::startResize(SurfaceWrapper *surface, Qt::Edges edges)
|
|
{
|
|
endMoveResize();
|
|
Q_ASSERT(edges != 0);
|
|
|
|
beginMoveResize(surface, edges);
|
|
surface->shellSurface()->setResizeing(true);
|
|
Helper::instance()->activeSurface(surface);
|
|
}
|
|
|
|
void RootSurfaceContainer::addSurface(SurfaceWrapper *)
|
|
{
|
|
Q_UNREACHABLE_RETURN();
|
|
}
|
|
|
|
void RootSurfaceContainer::removeSurface(SurfaceWrapper *)
|
|
{
|
|
Q_UNREACHABLE_RETURN();
|
|
}
|
|
|
|
void RootSurfaceContainer::addBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface)
|
|
{
|
|
SurfaceContainer::addBySubContainer(sub, surface);
|
|
|
|
connect(surface, &SurfaceWrapper::requestMove, this, [this] {
|
|
auto surface = qobject_cast<SurfaceWrapper*>(sender());
|
|
Q_ASSERT(surface);
|
|
startMove(surface);
|
|
});
|
|
connect(surface, &SurfaceWrapper::requestResize, this, [this] (Qt::Edges edges) {
|
|
auto surface = qobject_cast<SurfaceWrapper*>(sender());
|
|
Q_ASSERT(surface);
|
|
startResize(surface, edges);
|
|
});
|
|
connect(surface, &SurfaceWrapper::surfaceStateChanged, this, [surface, this] {
|
|
if (surface->surfaceState() == SurfaceWrapper::State::Minimized
|
|
|| surface->surfaceState() == SurfaceWrapper::State::Tiling)
|
|
return;
|
|
Helper::instance()->activeSurface(surface);
|
|
});
|
|
|
|
if (!surface->ownsOutput()) {
|
|
auto parentSurface = surface->parentSurface();
|
|
auto output = parentSurface ? parentSurface->ownsOutput() : primaryOutput();
|
|
|
|
if (auto xdgPopupSurface = qobject_cast<WXdgPopupSurface*>(surface->shellSurface())) {
|
|
if (parentSurface->type() != SurfaceWrapper::Type::Layer) {
|
|
auto pos = parentSurface->position() + parentSurface->surfaceItem()->position() + xdgPopupSurface->getPopupPosition();
|
|
if (auto op = m_outputLayout->handle()->output_at(pos.x(), pos.y()))
|
|
output = Helper::instance()->getOutput(WOutput::fromHandle(qw_output::from(op)));
|
|
}
|
|
}
|
|
surface->setOwnsOutput(output);
|
|
}
|
|
|
|
connect(surface, &SurfaceWrapper::geometryChanged, this, [this, surface] {
|
|
updateSurfaceOutputs(surface);
|
|
});
|
|
|
|
updateSurfaceOutputs(surface);
|
|
if (surface->shellSurface()->surface()->mapped())
|
|
Helper::instance()->activeSurface(surface, Qt::OtherFocusReason);
|
|
}
|
|
|
|
void RootSurfaceContainer::removeBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface)
|
|
{
|
|
if (moveResizeState.surface == surface)
|
|
endMoveResize();
|
|
|
|
SurfaceContainer::removeBySubContainer(sub, surface);
|
|
}
|
|
|
|
bool RootSurfaceContainer::filterSurfaceGeometryChanged(SurfaceWrapper *surface, QRectF &newGeometry, const QRectF &oldGeometry)
|
|
{
|
|
if (surface != moveResizeState.surface)
|
|
return false;
|
|
if (moveResizeState.setSurfacePositionForAnchorEdgets) {
|
|
Q_ASSERT(newGeometry.size() == oldGeometry.size());
|
|
return true;
|
|
}
|
|
|
|
if (moveResizeState.resizeEdges != 0) {
|
|
QRectF geometry = newGeometry;
|
|
if (moveResizeState.resizeEdges & Qt::RightEdge)
|
|
geometry.moveLeft(oldGeometry.left());
|
|
if (moveResizeState.resizeEdges & Qt::BottomEdge)
|
|
geometry.moveTop(oldGeometry.top());
|
|
if (moveResizeState.resizeEdges & Qt::LeftEdge)
|
|
geometry.moveRight(oldGeometry.right());
|
|
if (moveResizeState.resizeEdges & Qt::TopEdge)
|
|
geometry.moveBottom(oldGeometry.bottom());
|
|
|
|
if (geometry.topLeft() != newGeometry.topLeft()) {
|
|
newGeometry = geometry;
|
|
moveResizeState.setSurfacePositionForAnchorEdgets = true;
|
|
surface->setPosition(geometry.topLeft());
|
|
moveResizeState.setSurfacePositionForAnchorEdgets = false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool RootSurfaceContainer::filterSurfaceStateChange(SurfaceWrapper *surface, SurfaceWrapper::State newState, SurfaceWrapper::State oldState)
|
|
{
|
|
Q_UNUSED(oldState);
|
|
Q_UNUSED(newState);
|
|
return surface == moveResizeState.surface;
|
|
}
|
|
|
|
WOutputLayout *RootSurfaceContainer::outputLayout() const
|
|
{
|
|
return m_outputLayout;
|
|
}
|
|
|
|
WCursor *RootSurfaceContainer::cursor() const
|
|
{
|
|
return m_cursor;
|
|
}
|
|
|
|
Output *RootSurfaceContainer::cursorOutput() const
|
|
{
|
|
Q_ASSERT(m_cursor->layout() == m_outputLayout);
|
|
const auto &pos = m_cursor->position();
|
|
auto o = m_outputLayout->handle()->output_at(pos.x(), pos.y());
|
|
if (!o)
|
|
return nullptr;
|
|
|
|
return Helper::instance()->getOutput(WOutput::fromHandle(qw_output::from(o)));
|
|
}
|
|
|
|
Output *RootSurfaceContainer::primaryOutput() const
|
|
{
|
|
return m_primaryOutput;
|
|
}
|
|
|
|
void RootSurfaceContainer::setPrimaryOutput(Output *newPrimaryOutput)
|
|
{
|
|
if (m_primaryOutput == newPrimaryOutput)
|
|
return;
|
|
m_primaryOutput = newPrimaryOutput;
|
|
emit primaryOutputChanged();
|
|
}
|
|
|
|
const QList<Output*> &RootSurfaceContainer::outputs() const
|
|
{
|
|
return m_outputModel->objects();
|
|
}
|
|
|
|
void RootSurfaceContainer::ensureCursorVisible()
|
|
{
|
|
const auto cursorPos = m_cursor->position();
|
|
if (m_outputLayout->handle()->output_at(cursorPos.x(), cursorPos.y()))
|
|
return;
|
|
|
|
if (m_primaryOutput) {
|
|
Helper::instance()->setCursorPosition(m_primaryOutput->geometry().center());
|
|
}
|
|
}
|
|
|
|
void RootSurfaceContainer::updateSurfaceOutputs(SurfaceWrapper *surface)
|
|
{
|
|
const QRectF geometry = surface->geometry();
|
|
auto outputs = m_outputLayout->getIntersectedOutputs(geometry.toRect());
|
|
surface->setOutputs(outputs);
|
|
}
|
|
|
|
static qreal pointToRectMinDistance(const QPointF &pos, const QRectF &rect) {
|
|
if (rect.contains(pos))
|
|
return 0;
|
|
return std::min({std::abs(rect.x() - pos.x()), std::abs(rect.y() - pos.y()),
|
|
std::abs(rect.right() - pos.x()), std::abs(rect.bottom() - pos.y())});
|
|
}
|
|
|
|
static QRectF adjustRectToMakePointVisible(const QRectF& inputRect, const QPointF& absolutePoint, const QList<QRectF>& visibleAreas)
|
|
{
|
|
Q_ASSERT(inputRect.contains(absolutePoint));
|
|
QRectF adjustedRect = inputRect;
|
|
|
|
QRectF targetRect;
|
|
qreal distanceToTargetRect = std::numeric_limits<qreal>::max();
|
|
for (const QRectF& area : visibleAreas) {
|
|
Q_ASSERT(!area.isEmpty());
|
|
if (area.contains(absolutePoint))
|
|
return adjustedRect;
|
|
const auto distance = pointToRectMinDistance(absolutePoint, area);
|
|
if (distance < distanceToTargetRect) {
|
|
distanceToTargetRect = distance;
|
|
targetRect = area;
|
|
}
|
|
}
|
|
Q_ASSERT(!targetRect.isEmpty());
|
|
|
|
if (absolutePoint.x() < targetRect.x())
|
|
adjustedRect.moveLeft(adjustedRect.x() + targetRect.x() - absolutePoint.x());
|
|
else if (absolutePoint.x() > targetRect.right())
|
|
adjustedRect.moveRight(adjustedRect.right() + targetRect.right() - absolutePoint.x());
|
|
|
|
if (absolutePoint.y() < targetRect.y())
|
|
adjustedRect.moveTop(adjustedRect.y() + targetRect.y() - absolutePoint.y());
|
|
else if (absolutePoint.y() > targetRect.bottom())
|
|
adjustedRect.moveBottom(adjustedRect.bottom() + targetRect.bottom() - absolutePoint.y());
|
|
|
|
return adjustedRect;
|
|
}
|
|
|
|
void RootSurfaceContainer::ensureSurfaceNormalPositionValid(SurfaceWrapper *surface)
|
|
{
|
|
if (surface->type() == SurfaceWrapper::Type::Layer)
|
|
return;
|
|
|
|
auto normalGeo = surface->normalGeometry();
|
|
if (normalGeo.size().isEmpty())
|
|
return;
|
|
|
|
auto output = surface->ownsOutput();
|
|
if (!output)
|
|
return;
|
|
|
|
QList<QRectF> outputRects;
|
|
outputRects.reserve(outputs().size());
|
|
for (auto o : outputs())
|
|
outputRects << o->validGeometry();
|
|
|
|
// Ensure window is not outside the screen
|
|
const QPointF mustVisiblePosOfSurface(qMin(normalGeo.right(), normalGeo.x() + 20),
|
|
qMin(normalGeo.bottom(), normalGeo.y() + 20));
|
|
normalGeo = adjustRectToMakePointVisible(normalGeo, mustVisiblePosOfSurface, outputRects);
|
|
|
|
// Ensure titlebar is not outside the screen
|
|
const auto titlebarGeometry = surface->titlebarGeometry().translated(normalGeo.topLeft());
|
|
if (titlebarGeometry.isValid()) {
|
|
bool titlebarGeometryAdjusted = false;
|
|
for (auto r : std::as_const(outputRects)) {
|
|
if ((r & titlebarGeometry).isEmpty())
|
|
continue;
|
|
if (titlebarGeometry.top() < r.top()) {
|
|
normalGeo.moveTop(normalGeo.top() + r.top() - titlebarGeometry.top());
|
|
titlebarGeometryAdjusted = true;
|
|
}
|
|
}
|
|
|
|
if (!titlebarGeometryAdjusted) {
|
|
normalGeo = adjustRectToMakePointVisible(normalGeo, titlebarGeometry.topLeft(), outputRects);
|
|
}
|
|
}
|
|
|
|
surface->moveNormalGeometryInOutput(normalGeo.topLeft());
|
|
}
|
|
|
|
OutputListModel *RootSurfaceContainer::outputModel() const
|
|
{
|
|
return m_outputModel;
|
|
}
|