diff --git a/CMakeLists.txt b/CMakeLists.txt
index e02c012181..974320401d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,7 @@ find_package(Qt5Network 5.6 REQUIRED)
find_package(Qt5Xml 5.6 REQUIRED)
pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
+pkg_check_modules(QTMIRSERVER REQUIRED qtmirserver>=0.6.0)
pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
@@ -87,6 +88,7 @@ pkg_check_modules(QTDBUSTEST REQUIRED
libqtdbustest-1
libqtdbusmock-1
)
+pkg_check_modules(MIRAL REQUIRED miral)
### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
find_path(UBUNTUGESTUREPRIV
@@ -121,7 +123,9 @@ if(SHELL_PLUGINDIR STREQUAL "")
message(FATAL_ERROR "Could not determine plugin import dir.")
endif()
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-permissive -pedantic -Wall -Wextra")
+
+set (CMAKE_CXX_STANDARD 14)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
option(Werror "Treat warnings as errors" ON)
diff --git a/data/com.canonical.Unity8.gschema.xml b/data/com.canonical.Unity8.gschema.xml
index fa531b873f..782b824377 100644
--- a/data/com.canonical.Unity8.gschema.xml
+++ b/data/com.canonical.Unity8.gschema.xml
@@ -63,6 +63,16 @@
Whether the OSK switch should be visible
Toggle the visibility of the OSK switch
+
+ false
+ Enable or disable workspaces
+ Toggle the availability of workspaces
+
+
+ false
+ Force enable workspaces
+ Forces workspaces availability even when not in windowed mode
+
diff --git a/debian/control b/debian/control
index 2cbb1942d8..c6444f13b9 100644
--- a/debian/control
+++ b/debian/control
@@ -29,6 +29,7 @@ Build-Depends:
libpam0g-dev,
libpulse-dev,
libqmenumodel-dev (>= 0.2.12),
+ libqtmirserver-dev (>= 0.6.0),
libqt5sql5-sqlite,
libqt5svg5-dev,
libqt5xmlpatterns5-dev,
@@ -46,6 +47,7 @@ Build-Depends:
libx11-dev[!arm64 !armhf],
libxcb1-dev[!arm64 !armhf],
libxi-dev[!arm64 !armhf],
+ mirtest-dev,
pkg-config,
python3-all:any,
python3-setuptools,
@@ -73,6 +75,9 @@ Build-Depends:
ttf-ubuntu-font-family,
ubports-wallpapers,
xvfb,
+# mirtest pkgconfig requires these, but doesn't have a deb dependency. Bug lp:1633537
+ libboost-filesystem-dev,
+ libboost-system-dev,
Standards-Version: 3.9.4
Homepage: https://github.com/ubports/unity8
Vcs-Git: https://github.com/ubports/unity8
@@ -122,6 +127,7 @@ Depends:
qml-module-qtsysteminfo,
qtdeclarative5-qtmir-plugin (>= 0.4.8),
qtdeclarative5-ubuntu-telephony0.1,
+ qtmir-desktop (>= 0.6.0) | qtmir-android (>= 0.6.0),
ubuntu-system-settings (>= 0.4),
unity-launcher-impl-13,
unity8-common (= ${source:Version}),
diff --git a/debian/unity8.install b/debian/unity8.install
index 216389d486..499627dcf4 100644
--- a/debian/unity8.install
+++ b/debian/unity8.install
@@ -12,7 +12,12 @@ usr/share/unity8/Launcher
usr/share/unity8/OrientedShell.qml
usr/share/unity8/Panel
usr/share/unity8/Rotation
+usr/share/unity8/ErrorApplication.qml
+usr/share/unity8/qmldir
usr/share/unity8/Shell.qml
+usr/share/unity8/ShellApplication.qml
+usr/share/unity8/ShellNotifier.qml
+usr/share/unity8/ShellScreen.qml
usr/share/unity8/Stage
usr/share/unity8/Tutorial
usr/share/unity8/Wizard
diff --git a/plugins/Cursor/CMakeLists.txt b/plugins/Cursor/CMakeLists.txt
index a7fa59c846..317d35c153 100644
--- a/plugins/Cursor/CMakeLists.txt
+++ b/plugins/Cursor/CMakeLists.txt
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
CursorImageInfo.cpp
CursorImageProvider.cpp
MousePointer.cpp
+ InputDispatcherFilter.cpp
# We need to run moc on this header
${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
)
diff --git a/plugins/Cursor/InputDispatcherFilter.cpp b/plugins/Cursor/InputDispatcherFilter.cpp
new file mode 100644
index 0000000000..5ec75cf62d
--- /dev/null
+++ b/plugins/Cursor/InputDispatcherFilter.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2016 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#include "InputDispatcherFilter.h"
+#include "MousePointer.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+InputDispatcherFilter *InputDispatcherFilter::instance()
+{
+ static InputDispatcherFilter filter;
+ return &filter;
+}
+
+InputDispatcherFilter::InputDispatcherFilter(QObject *parent)
+ : QObject(parent)
+ , m_pushing(false)
+{
+ QPlatformNativeInterface *ni = QGuiApplication::platformNativeInterface();
+ m_inputDispatcher = static_cast(ni->nativeResourceForIntegration("InputDispatcher"));
+ if (m_inputDispatcher) {
+ m_inputDispatcher->installEventFilter(this);
+ }
+}
+
+void InputDispatcherFilter::registerPointer(MousePointer *pointer)
+{
+ // allow first registered pointer to be visible.
+ m_pointers.insert(pointer);
+}
+
+void InputDispatcherFilter::unregisterPointer(MousePointer *pointer)
+{
+ m_pointers.remove(pointer);
+}
+
+bool InputDispatcherFilter::eventFilter(QObject *o, QEvent *e)
+{
+ if (o != m_inputDispatcher) return false;
+
+ switch (e->type()) {
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ {
+ // if we don't have any pointers, filter all mouse events.
+ auto pointer = currentPointer();
+ if (!pointer) return true;
+
+ QMouseEvent* me = static_cast(e);
+
+ // Local position gives relative change of mouse pointer.
+ QPointF movement = me->localPos();
+
+ // Adjust the position
+ QPointF oldPos = pointer->window()->geometry().topLeft() + pointer->position();
+ QPointF newPos = adjustedPositionForMovement(oldPos, movement);
+
+ QScreen* currentScreen = screenAt(newPos);
+ if (currentScreen) {
+ QRect screenRect = currentScreen->geometry();
+ qreal newX = (oldPos + movement).x();
+ qreal newY = (oldPos + movement).y();
+
+ if (newX <= screenRect.left() && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top left corner
+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY- screenRect.top() - pointer->topBoundaryOffset(), 2));
+ Q_EMIT pushedTopLeftCorner(currentScreen, qAbs(distance), me->buttons());
+ m_pushing = true;
+ } else if (newX >= screenRect.right()-1 && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top right corner
+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY - screenRect.top() - pointer->topBoundaryOffset(), 2));
+ Q_EMIT pushedTopRightCorner(currentScreen, qAbs(distance), me->buttons());
+ m_pushing = true;
+ } else if (newX < 0 && newY >= screenRect.bottom()-1) { // bottom left corner
+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-screenRect.bottom(), 2));
+ Q_EMIT pushedBottomLeftCorner(currentScreen, qAbs(distance), me->buttons());
+ m_pushing = true;
+ } else if (newX >= screenRect.right()-1 && newY >= screenRect.bottom()-1) { // bottom right corner
+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY-screenRect.bottom(), 2));
+ Q_EMIT pushedBottomRightCorner(currentScreen, qAbs(distance), me->buttons());
+ m_pushing = true;
+ } else if (newX < screenRect.left()) { // left edge
+ Q_EMIT pushedLeftBoundary(currentScreen, qAbs(newX), me->buttons());
+ m_pushing = true;
+ } else if (newX >= screenRect.right()) { // right edge
+ Q_EMIT pushedRightBoundary(currentScreen, newX - (screenRect.right() - 1), me->buttons());
+ m_pushing = true;
+ } else if (newY < screenRect.top() + pointer->topBoundaryOffset()) { // top edge
+ Q_EMIT pushedTopBoundary(currentScreen, qAbs(newY - screenRect.top() - pointer->topBoundaryOffset()), me->buttons());
+ m_pushing = true;
+ } else if (Q_LIKELY(newX > 0 && newX < screenRect.right()-1 && newY > 0 && newY < screenRect.bottom()-1)) { // normal pos, not pushing
+ if (m_pushing) {
+ Q_EMIT pushStopped(currentScreen);
+ m_pushing = false;
+ }
+ }
+ }
+
+ // Send the event
+ QMouseEvent eCopy(me->type(), me->localPos(), newPos, me->button(), me->buttons(), me->modifiers());
+ eCopy.setTimestamp(me->timestamp());
+ o->event(&eCopy);
+ return true;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+QPointF InputDispatcherFilter::adjustedPositionForMovement(const QPointF &pt, const QPointF &movement) const
+{
+ QPointF adjusted = pt + movement;
+
+ auto screen = screenAt(adjusted); // first check if our move was to a screen with an enabled pointer.
+ if (screen) {
+ return adjusted;
+ } else if ((screen = screenAt(pt))) { // then check if our old position was valid
+ QRectF screenBounds = screen->geometry();
+ // bound the new position to the old screen geometry
+ adjusted.rx() = qMax(screenBounds.left(), qMin(adjusted.x(), screenBounds.right()-1));
+ adjusted.ry() = qMax(screenBounds.top(), qMin(adjusted.y(), screenBounds.bottom()-1));
+ } else {
+ auto screens = QGuiApplication::screens();
+
+ // center of first screen with a pointer.
+ Q_FOREACH(QScreen* screen, screens) {
+ Q_FOREACH(MousePointer* pointer, m_pointers) {
+ if (pointer->isEnabled() && pointer->screen() == screen) {
+ return screen->geometry().center();
+ }
+ }
+ }
+ }
+ return adjusted;
+}
+
+QScreen *InputDispatcherFilter::screenAt(const QPointF &pt) const
+{
+ Q_FOREACH(MousePointer* pointer, m_pointers) {
+ if (!pointer->isEnabled()) continue;
+
+ QScreen* screen = pointer->screen();
+ if (screen && screen->geometry().contains(pt.toPoint()))
+ return screen;
+ }
+ return nullptr;
+}
+
+MousePointer *InputDispatcherFilter::currentPointer() const
+{
+ Q_FOREACH(MousePointer* pointer, m_pointers) {
+ if (!pointer->isEnabled()) continue;
+ return pointer;
+ }
+ return nullptr;
+}
diff --git a/plugins/Cursor/InputDispatcherFilter.h b/plugins/Cursor/InputDispatcherFilter.h
new file mode 100644
index 0000000000..b347f4f016
--- /dev/null
+++ b/plugins/Cursor/InputDispatcherFilter.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef INPUTDISPATCHERFILTER_H
+#define INPUTDISPATCHERFILTER_H
+
+#include
+#include
+#include
+
+class MousePointer;
+class QScreen;
+
+class InputDispatcherFilter : public QObject
+{
+ Q_OBJECT
+public:
+ static InputDispatcherFilter *instance();
+
+ void registerPointer(MousePointer* pointer);
+ void unregisterPointer(MousePointer* pointer);
+
+Q_SIGNALS:
+ void pushedLeftBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushedRightBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushedTopBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushedTopLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushedTopRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushedBottomLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushedBottomRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
+ void pushStopped(QScreen* screen);
+
+protected:
+ InputDispatcherFilter(QObject* parent = nullptr);
+
+ bool eventFilter(QObject *o, QEvent *e) override;
+
+ QPointF adjustedPositionForMovement(const QPointF& pt, const QPointF& movement) const;
+ QScreen* screenAt(const QPointF& pt) const;
+
+ MousePointer* currentPointer() const;
+
+private:
+ QObject* m_inputDispatcher;
+ QSet m_pointers;
+ bool m_pushing;
+};
+
+#endif // INPUTDISPATCHERFILTER_H
diff --git a/plugins/Cursor/MousePointer.cpp b/plugins/Cursor/MousePointer.cpp
index adfa020a9a..0b46925857 100644
--- a/plugins/Cursor/MousePointer.cpp
+++ b/plugins/Cursor/MousePointer.cpp
@@ -16,84 +16,74 @@
#include "MousePointer.h"
#include "CursorImageProvider.h"
-
-// Unity API
-#include
+#include "InputDispatcherFilter.h"
#include
-#include
-#include
-#include
+// Unity API
+#include
MousePointer::MousePointer(QQuickItem *parent)
: MirMousePointerInterface(parent)
, m_cursorName(QStringLiteral("left_ptr"))
, m_themeName(QStringLiteral("default"))
{
-}
-
-void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
- Qt::KeyboardModifiers modifiers)
-{
- if (!parentItem()) {
- return;
- }
-
- if (!movement.isNull()) {
- Q_EMIT mouseMoved();
- }
-
- m_accumulatedMovement += movement;
- // don't apply the fractional part
- QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
- m_accumulatedMovement -= appliedMovement;
-
- qreal newX = x() + appliedMovement.x();
- qreal newY = y() + appliedMovement.y();
- const qreal sceneWidth = parentItem()->width();
- const qreal sceneHeight = parentItem()->height();
-
- if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
- const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
- Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
- m_pushing = true;
- } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
- const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
- Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
- m_pushing = true;
- } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
- const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
- Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
- m_pushing = true;
- } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
- const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
- Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
- m_pushing = true;
- } else if (newX < 0) { // left edge
- Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
- m_pushing = true;
- } else if (newX >= sceneWidth) { // right edge
- Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
- m_pushing = true;
- } else if (newY < m_topBoundaryOffset) { // top edge
- Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
- m_pushing = true;
- } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
- if (m_pushing) {
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedLeftBoundary(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedRightBoundary(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopBoundary,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedTopBoundary(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopLeftCorner,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedTopLeftCorner(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopRightCorner,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedTopRightCorner(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomLeftCorner,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedBottomLeftCorner(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomRightCorner,
+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
+ if (window() && window()->screen() == screen) {
+ Q_EMIT pushedBottomRightCorner(amount, buttons);
+ }
+ });
+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushStopped,
+ this, [this](QScreen* screen) {
+ if (window() && window()->screen() == screen) {
Q_EMIT pushStopped();
- m_pushing = false;
}
- }
+ });
- applyItemConfinement(newX, newY);
-
- setX(qBound(0.0, newX, sceneWidth - 1));
- setY(qBound(0.0, newY, sceneHeight - 1));
+ InputDispatcherFilter::instance()->registerPointer(this);
+}
- QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
- QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
- buttons, modifiers);
+MousePointer::~MousePointer()
+{
+ registerScreen(nullptr);
+ InputDispatcherFilter::instance()->unregisterPointer(this);
}
void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,17 +111,6 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
}
}
-void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
-{
- if (!parentItem()) {
- return;
- }
-
- QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
- QWindowSystemInterface::handleWheelEvent(window(), timestamp, scenePosition /* local */, scenePosition /* global */,
- QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
-}
-
int MousePointer::topBoundaryOffset() const
{
return m_topBoundaryOffset;
@@ -182,7 +161,7 @@ void MousePointer::registerScreen(QScreen *screen)
if (m_registeredScreen) {
auto previousCursor = dynamic_cast(m_registeredScreen->handle()->cursor());
if (previousCursor) {
- previousCursor->setMousePointer(nullptr);
+ previousCursor->unregisterMousePointer(this);
} else {
qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
}
@@ -193,7 +172,7 @@ void MousePointer::registerScreen(QScreen *screen)
if (m_registeredScreen) {
auto cursor = dynamic_cast(m_registeredScreen->handle()->cursor());
if (cursor) {
- cursor->setMousePointer(this);
+ cursor->registerMousePointer(this);
} else {
qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
}
@@ -216,6 +195,12 @@ void MousePointer::setThemeName(const QString &themeName)
}
}
+void MousePointer::moveTo(const QPoint &position)
+{
+ setPosition(position);
+ Q_EMIT mouseMoved();
+}
+
void MousePointer::setCustomCursor(const QCursor &customCursor)
{
CursorImageProvider::instance()->setCustomCursor(customCursor);
diff --git a/plugins/Cursor/MousePointer.h b/plugins/Cursor/MousePointer.h
index 6624d36327..aa47b3c899 100644
--- a/plugins/Cursor/MousePointer.h
+++ b/plugins/Cursor/MousePointer.h
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
public:
MousePointer(QQuickItem *parent = nullptr);
+ ~MousePointer();
void setCursorName(const QString &qtCursorName) override;
QString cursorName() const override { return m_cursorName; }
@@ -38,6 +39,8 @@ class MousePointer : public MirMousePointerInterface {
void setThemeName(const QString &themeName) override;
QString themeName() const override { return m_themeName; }
+ void moveTo(const QPoint& position) override;
+
void setCustomCursor(const QCursor &) override;
QQuickItem* confiningItem() const;
@@ -46,10 +49,7 @@ class MousePointer : public MirMousePointerInterface {
int topBoundaryOffset() const;
void setTopBoundaryOffset(int topBoundaryOffset);
-public Q_SLOTS:
- void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
- Qt::KeyboardModifiers modifiers) override;
- void handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers) override;
+ QScreen* screen() const { return m_registeredScreen; }
Q_SIGNALS:
void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
@@ -79,6 +79,7 @@ private Q_SLOTS:
QPointer m_registeredScreen;
QString m_cursorName;
QString m_themeName;
+ bool m_active;
// Accumulated, unapplied, mouse movement.
QPointF m_accumulatedMovement;
diff --git a/plugins/GlobalShortcut/globalshortcut.cpp b/plugins/GlobalShortcut/globalshortcut.cpp
index 6266dd0b59..5f87e5599c 100644
--- a/plugins/GlobalShortcut/globalshortcut.cpp
+++ b/plugins/GlobalShortcut/globalshortcut.cpp
@@ -56,14 +56,6 @@ void GlobalShortcut::setActive(bool active)
Q_EMIT activeChanged(active);
}
-void GlobalShortcut::componentComplete()
-{
- if (this->window()) {
- QMetaObject::invokeMethod(this, "setupFilterOnWindow", Q_ARG(QQuickWindow*, this->window()));
- }
- connect(this, &QQuickItem::windowChanged, this, &GlobalShortcut::setupFilterOnWindow);
-}
-
void GlobalShortcut::keyPressEvent(QKeyEvent * event)
{
if (!m_active) return;
@@ -79,13 +71,3 @@ void GlobalShortcut::keyReleaseEvent(QKeyEvent * event)
event->accept();
Q_EMIT released(m_shortcut.toString());
}
-
-void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
-{
- if (!window) {
-// qWarning() << Q_FUNC_INFO << "Failed to setup filter on window";
- return;
- }
-
- registry->setupFilterOnWindow((qulonglong) window->winId());
-}
diff --git a/plugins/GlobalShortcut/globalshortcut.h b/plugins/GlobalShortcut/globalshortcut.h
index ac1fb81d04..2866ec166d 100644
--- a/plugins/GlobalShortcut/globalshortcut.h
+++ b/plugins/GlobalShortcut/globalshortcut.h
@@ -53,7 +53,6 @@ class GlobalShortcut: public QQuickItem
void setActive(bool active);
protected:
- void componentComplete() override;
void keyPressEvent(QKeyEvent * event) override;
void keyReleaseEvent(QKeyEvent * event) override;
@@ -69,9 +68,6 @@ class GlobalShortcut: public QQuickItem
void released(const QString &shortcut);
void activeChanged(bool active);
-private Q_SLOTS:
- void setupFilterOnWindow(QQuickWindow* window);
-
private:
QVariant m_shortcut;
bool m_active = true;
diff --git a/plugins/GlobalShortcut/globalshortcutregistry.cpp b/plugins/GlobalShortcut/globalshortcutregistry.cpp
index 5ec4e9ee76..600c310b95 100644
--- a/plugins/GlobalShortcut/globalshortcutregistry.cpp
+++ b/plugins/GlobalShortcut/globalshortcutregistry.cpp
@@ -21,11 +21,25 @@
#include "globalshortcutregistry.h"
-static qulonglong s_windowId = 0;
+namespace {
+QWindow* windowForShortcut(GlobalShortcut *sc) {
+ QObject* parent= sc;
+ while(parent) {
+ if (auto item = qobject_cast(parent)) {
+ auto window = item->window();
+ if (window) return window;
+ }
+ parent = parent->parent();
+ }
+ return nullptr;
+}
+} // namespace
GlobalShortcutRegistry::GlobalShortcutRegistry(QObject *parent)
: QObject(parent)
{
+ connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &GlobalShortcutRegistry::setupFilterOnWindow);
+ setupFilterOnWindow(qGuiApp->focusWindow());
}
GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
@@ -91,7 +105,10 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
const auto shortcuts = m_shortcuts.value(seq);
Q_FOREACH(const auto &shortcut, shortcuts) {
if (shortcut) {
- qApp->sendEvent(shortcut, &eCopy);
+ auto window = windowForShortcut(shortcut);
+ if (!window || window == obj) { // accept shortcut if it's not attached to a window or it's window is active.
+ qApp->sendEvent(shortcut, &eCopy);
+ }
}
}
}
@@ -102,24 +119,15 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
return QObject::eventFilter(obj, event);
}
-void GlobalShortcutRegistry::setupFilterOnWindow(qulonglong wid)
+void GlobalShortcutRegistry::setupFilterOnWindow(QWindow* window)
{
- if (wid == s_windowId) {
- return;
- }
-
if (m_filteredWindow) {
m_filteredWindow->removeEventFilter(this);
m_filteredWindow.clear();
- s_windowId = 0;
}
- Q_FOREACH(QWindow *window, qApp->allWindows()) {
- if (window && window->winId() == wid) {
- m_filteredWindow = window;
- window->installEventFilter(this);
- s_windowId = wid;
- break;
- }
+ if (window) {
+ m_filteredWindow = window;
+ window->installEventFilter(this);
}
}
diff --git a/plugins/GlobalShortcut/globalshortcutregistry.h b/plugins/GlobalShortcut/globalshortcutregistry.h
index 082c8e678a..e8b3791bf1 100644
--- a/plugins/GlobalShortcut/globalshortcutregistry.h
+++ b/plugins/GlobalShortcut/globalshortcutregistry.h
@@ -52,9 +52,9 @@ class Q_DECL_EXPORT GlobalShortcutRegistry: public QObject
void addShortcut(const QVariant &seq, GlobalShortcut * sc);
/**
- * Sets up key events filtering on window @p wid
+ * Sets up key events filtering on window @p window
*/
- void setupFilterOnWindow(qulonglong wid);
+ void setupFilterOnWindow(QWindow* window);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
diff --git a/plugins/UInput/plugin.cpp b/plugins/UInput/plugin.cpp
index d5bf4c6591..5489fb6d15 100644
--- a/plugins/UInput/plugin.cpp
+++ b/plugins/UInput/plugin.cpp
@@ -19,8 +19,14 @@
#include
+
+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
+{
+ return new UInput;
+}
+
void UInputPlugin::registerTypes(const char *uri)
{
Q_ASSERT(uri == QLatin1String("UInput"));
- qmlRegisterType(uri, 0, 1, "UInput");
+ qmlRegisterSingletonType(uri, 0, 1, "UInput", uinputSingleton);
}
diff --git a/plugins/WindowManager/CMakeLists.txt b/plugins/WindowManager/CMakeLists.txt
index d5831d99ae..81a0c91e14 100644
--- a/plugins/WindowManager/CMakeLists.txt
+++ b/plugins/WindowManager/CMakeLists.txt
@@ -1,26 +1,46 @@
+include_directories(
+ SYSTEM
+ ${QTMIRSERVER_INCLUDE_DIRS}
+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
+)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${libunity8-private_SOURCE_DIR}
+)
+
set(WINDOWMANAGER_SRC
AvailableDesktopArea.cpp
TopLevelWindowModel.cpp
Window.cpp
WindowManagerPlugin.cpp
WindowMargins.cpp
+ Screen.cpp
+ ScreenAttached.cpp
+ Screens.cpp
+ ScreensConfiguration.cpp
+ ScreenWindow.cpp
+ WindowManagerObjects.cpp
+ Workspace.cpp
+ WorkspaceManager.cpp
+ WorkspaceModel.cpp
InputMethodManager.cpp
${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
- )
+)
add_definitions(-DWINDOWMANAGERQML_LIBRARY)
-include_directories(
- SYSTEM
- ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
-)
-
add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
-target_link_libraries(windowmanager-qml Qt5::Qml Qt5::Quick Qt5::Gui)
+target_link_libraries(windowmanager-qml
+ Qt5::Qml Qt5::Quick Qt5::Gui
+ ${QTMIRSERVER_LDFLAGS}
+ unity8-private
+)
-add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
+add_unity8_plugin(WindowManager 1.0 WindowManager TARGETS windowmanager-qml)
diff --git a/plugins/WindowManager/Screen.cpp b/plugins/WindowManager/Screen.cpp
new file mode 100644
index 0000000000..637c93e02a
--- /dev/null
+++ b/plugins/WindowManager/Screen.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#include "Screen.h"
+#include "Screens.h"
+#include "WorkspaceManager.h"
+#include "Workspace.h"
+
+Screen::Screen(QObject *parent)
+ : QObject(parent)
+{
+}
+
+void Screen::connectToScreen(qtmir::Screen *screen)
+{
+ m_wrapped = screen;
+ connect(screen, &qtmir::Screen::usedChanged, this, &Screen::usedChanged);
+ connect(screen, &qtmir::Screen::nameChanged, this, &Screen::nameChanged);
+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeChanged);
+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeNameChanged);
+ connect(screen, &qtmir::Screen::scaleChanged, this, &Screen::scaleChanged);
+ connect(screen, &qtmir::Screen::formFactorChanged, this, &Screen::formFactorChanged);
+ connect(screen, &qtmir::Screen::physicalSizeChanged, this, &Screen::physicalSizeChanged);
+ connect(screen, &qtmir::Screen::positionChanged, this, &Screen::positionChanged);
+ connect(screen, &qtmir::Screen::activeChanged, this, &Screen::activeChanged);
+ connect(screen, &qtmir::Screen::currentModeIndexChanged, this, &Screen::currentModeIndexChanged);
+ connect(screen, &qtmir::Screen::availableModesChanged, this, &Screen::availableModesChanged);
+}
+
+void Screen::connectToScreen(Screen *screen)
+{
+ connectToScreen(screen->wrapped());
+ connect(screen, &Screen::currentWorkspaceChanged, this, &Screen::currentWorkspaceChanged);
+}
+
+void Screen::setCurrentWorkspace2(Workspace *workspace)
+{
+ // Make sure we use the correct concrete class. Don't want to use a Proxy.
+ workspace->setCurrentOn(this);
+}
+
+bool Screen::used() const
+{
+ if (!m_wrapped) return false;
+ return m_wrapped->used();
+}
+
+QString Screen::name() const
+{
+ if (!m_wrapped) return QString();
+ return m_wrapped->name();
+}
+
+float Screen::scale() const
+{
+ if (!m_wrapped) return 1.0;
+ return m_wrapped->scale();
+}
+
+QSizeF Screen::physicalSize() const
+{
+ if (!m_wrapped) return QSizeF();
+ return m_wrapped->physicalSize();
+}
+
+qtmir::FormFactor Screen::formFactor() const
+{
+ if (!m_wrapped) return qtmir::FormFactorUnknown;
+ return m_wrapped->formFactor();
+}
+
+qtmir::OutputTypes Screen::outputType() const
+{
+ if (!m_wrapped) return qtmir::Unknown;
+ return m_wrapped->outputType();
+}
+
+MirPowerMode Screen::powerMode() const
+{
+ if (!m_wrapped) return mir_power_mode_on;
+ return m_wrapped->powerMode();
+}
+
+Qt::ScreenOrientation Screen::orientation() const
+{
+ if (!m_wrapped) return Qt::PrimaryOrientation;
+ return m_wrapped->orientation();
+}
+
+QPoint Screen::position() const
+{
+ if (!m_wrapped) return QPoint();
+ return m_wrapped->position();
+}
+
+QQmlListProperty Screen::availableModes()
+{
+ if (!m_wrapped) return QQmlListProperty();
+ return m_wrapped->availableModes();
+}
+
+uint Screen::currentModeIndex() const
+{
+ if (!m_wrapped) return -1;
+ return m_wrapped->currentModeIndex();
+}
+
+bool Screen::isActive() const
+{
+ if (!m_wrapped) return false;
+ return m_wrapped->isActive();
+}
+
+void Screen::activate()
+{
+ setActive(true);
+}
+
+void Screen::setActive(bool active)
+{
+ if (!m_wrapped) return;
+ m_wrapped->setActive(active);
+}
+
+QScreen *Screen::qscreen() const
+{
+ if (!m_wrapped) return nullptr;
+ return m_wrapped->qscreen();
+}
+
+ScreenConfig *Screen::beginConfiguration() const
+{
+ if (!m_wrapped) return nullptr;
+ return new ScreenConfig(m_wrapped->beginConfiguration());
+}
+
+bool Screen::applyConfiguration(ScreenConfig *configuration)
+{
+ if (!m_wrapped) return false;
+ return m_wrapped->applyConfiguration(configuration->m_config);
+}
+
+QString Screen::outputTypeName() const
+{
+ switch (m_wrapped->outputType()) {
+ case qtmir::Unknown:
+ return tr("Unknown");
+ case qtmir::VGA:
+ return tr("VGA");
+ case qtmir::DVII:
+ case qtmir::DVID:
+ case qtmir::DVIA:
+ return tr("DVI");
+ case qtmir::Composite:
+ return tr("Composite");
+ case qtmir::SVideo:
+ return tr("S-Video");
+ case qtmir::LVDS:
+ case qtmir::NinePinDIN:
+ case qtmir::EDP:
+ return tr("Internal");
+ case qtmir::Component:
+ return tr("Component");
+ case qtmir::DisplayPort:
+ return tr("DisplayPort");
+ case qtmir::HDMIA:
+ case qtmir::HDMIB:
+ return tr("HDMI");
+ case qtmir::TV:
+ return tr("TV");
+ }
+ return QString();
+}
+
+bool Screen::isSameAs(Screen *screen) const
+{
+ if (!screen) return false;
+ if (screen == this) return true;
+ return wrapped() == screen->wrapped();
+}
+
+void Screen::sync(Screen *proxy)
+{
+ if (!proxy) return;
+ workspaces()->sync(proxy->workspaces());
+}
+
+ConcreteScreen::ConcreteScreen(qtmir::Screen* wrapped)
+ : m_workspaces(new WorkspaceModel)
+{
+ connectToScreen(wrapped);
+
+ // Connect the active workspace to activate the screen.
+ connect(m_workspaces.data(), &WorkspaceModel::workspaceInserted, this, [this](int, Workspace* workspace) {
+ connect(workspace, &Workspace::activeChanged, this, [this, workspace](bool active) {
+ if (active) {
+ setCurrentWorkspace(workspace);
+ activate();
+ }
+ });
+ if (workspace->isActive()) {
+ activate();
+ setCurrentWorkspace(workspace);
+ }
+ if (!m_currentWorspace) {
+ setCurrentWorkspace(workspace);
+ }
+ });
+ connect(m_workspaces.data(), &WorkspaceModel::workspaceRemoved, this, [this](Workspace* workspace) {
+ disconnect(workspace, &Workspace::activeChanged, this, 0);
+ if (workspace == m_currentWorspace) {
+ resetCurrentWorkspace();
+ }
+ });
+ connect(this, &ConcreteScreen::activeChanged, this, [this](bool active) {
+ if (active && m_currentWorspace) {
+ m_currentWorspace->activate();
+ }
+ });
+}
+
+void ConcreteScreen::resetCurrentWorkspace()
+{
+ auto newCurrent = m_workspaces->rowCount() > 0 ? m_workspaces->get(0) : nullptr;
+ if (m_currentWorspace != newCurrent) {
+ m_currentWorspace = newCurrent;
+ Q_EMIT currentWorkspaceChanged(newCurrent);
+ }
+}
+
+
+WorkspaceModel *ConcreteScreen::workspaces() const
+{
+ return m_workspaces.data();
+}
+
+Workspace *ConcreteScreen::currentWorkspace() const
+{
+ return m_currentWorspace.data();
+}
+
+void ConcreteScreen::setCurrentWorkspace(Workspace *workspace)
+{
+ if (m_currentWorspace != workspace) {
+ m_currentWorspace = workspace;
+ Q_EMIT currentWorkspaceChanged(workspace);
+ }
+}
+
+ProxyScreen::ProxyScreen(Screen *const screen, ProxyScreens* screens)
+ : m_workspaces(new ProxyWorkspaceModel(screen->workspaces(), this))
+ , m_original(screen)
+ , m_screens(screens)
+{
+ connectToScreen(screen);
+
+ auto updateCurrentWorkspaceFn = [this](Workspace* realWorkspace) {
+ Q_FOREACH(Workspace* workspace, m_workspaces->list()) {
+ auto p = qobject_cast(workspace);
+ if (p && p->proxyObject() == realWorkspace) {
+ if (m_currentWorspace != p) {
+ m_currentWorspace = p;
+ Q_EMIT currentWorkspaceChanged(p);
+ }
+ }
+ }
+ };
+ connect(screen, &Screen::currentWorkspaceChanged, this, updateCurrentWorkspaceFn);
+ updateCurrentWorkspaceFn(screen->currentWorkspace());
+}
+
+WorkspaceModel *ProxyScreen::workspaces() const
+{
+ return m_workspaces.data();
+}
+
+Workspace *ProxyScreen::currentWorkspace() const
+{
+ return m_currentWorspace.data();
+}
+
+void ProxyScreen::setCurrentWorkspace(Workspace *workspace)
+{
+ auto p = qobject_cast(workspace);
+ if (p) {
+ m_original->setCurrentWorkspace(p->proxyObject());
+ }
+}
+
+bool ProxyScreen::isSyncing() const
+{
+ return m_screens->isSyncing();
+}
+
+ScreenConfig::ScreenConfig(qtmir::ScreenConfiguration *config)
+ : m_config(config)
+{
+}
+
+ScreenConfig::~ScreenConfig()
+{
+ delete m_config;
+}
diff --git a/plugins/WindowManager/Screen.h b/plugins/WindowManager/Screen.h
new file mode 100644
index 0000000000..dbdb7dec43
--- /dev/null
+++ b/plugins/WindowManager/Screen.h
@@ -0,0 +1,153 @@
+#ifndef SCREEN_H
+#define SCREEN_H
+
+#include
+#include
+#include
+
+#include "WorkspaceModel.h"
+
+class ProxyScreen;
+class ProxyScreens;
+class ScreenConfig;
+
+class Screen: public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
+ Q_PROPERTY(bool used READ used NOTIFY usedChanged)
+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
+ Q_PROPERTY(qtmir::OutputTypes outputType READ outputType NOTIFY outputTypeChanged)
+ Q_PROPERTY(float scale READ scale NOTIFY scaleChanged)
+ Q_PROPERTY(qtmir::FormFactor formFactor READ formFactor NOTIFY formFactorChanged)
+ Q_PROPERTY(MirPowerMode powerMode READ powerMode NOTIFY powerModeChanged)
+ Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged)
+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
+ Q_PROPERTY(uint currentModeIndex READ currentModeIndex NOTIFY currentModeIndexChanged)
+ Q_PROPERTY(QQmlListProperty availableModes READ availableModes NOTIFY availableModesChanged)
+ Q_PROPERTY(QSizeF physicalSize READ physicalSize NOTIFY physicalSizeChanged)
+ Q_PROPERTY(QString outputTypeName READ outputTypeName NOTIFY outputTypeChanged)
+ Q_PROPERTY(WorkspaceModel* workspaces READ workspaces CONSTANT)
+ Q_PROPERTY(Workspace* currentWorkspace READ currentWorkspace WRITE setCurrentWorkspace2 NOTIFY currentWorkspaceChanged)
+public:
+ bool used() const;
+ QString name() const;
+ float scale() const;
+ QSizeF physicalSize() const;
+ qtmir::FormFactor formFactor() const;
+ qtmir::OutputTypes outputType() const;
+ MirPowerMode powerMode() const;
+ Qt::ScreenOrientation orientation() const;
+ QPoint position() const;
+ QQmlListProperty availableModes();
+ uint currentModeIndex() const;
+ bool isActive() const;
+ void setActive(bool active);
+ QScreen* qscreen() const;
+ QString outputTypeName() const;
+
+ Q_INVOKABLE bool isSameAs(Screen*) const;
+
+ Q_INVOKABLE ScreenConfig *beginConfiguration() const;
+ Q_INVOKABLE bool applyConfiguration(ScreenConfig *configuration);
+
+ virtual WorkspaceModel* workspaces() const = 0;
+ virtual Workspace *currentWorkspace() const = 0;
+ virtual void setCurrentWorkspace(Workspace* workspace) = 0;
+
+ void sync(Screen* proxy);
+
+ qtmir::Screen* wrapped() const { return m_wrapped; }
+
+public Q_SLOTS:
+ void activate();
+
+Q_SIGNALS:
+ void usedChanged();
+ void nameChanged();
+ void outputTypeChanged();
+ void outputTypeNameChanged();
+ void scaleChanged();
+ void formFactorChanged();
+ void powerModeChanged();
+ void orientationChanged();
+ void positionChanged();
+ void currentModeIndexChanged();
+ void physicalSizeChanged();
+ void availableModesChanged();
+ void activeChanged(bool active);
+ void currentWorkspaceChanged(Workspace*);
+
+protected:
+ Screen(QObject* parent = 0);
+
+ void connectToScreen(qtmir::Screen* screen);
+ void connectToScreen(Screen* screen);
+
+private:
+ void setCurrentWorkspace2(Workspace* workspace);
+
+protected:
+ QPointer m_wrapped;
+};
+
+
+class ConcreteScreen : public Screen
+{
+ Q_OBJECT
+public:
+ explicit ConcreteScreen(qtmir::Screen*const wrapped);
+
+ // From qtmir::Screen
+ WorkspaceModel* workspaces() const override;
+ Workspace *currentWorkspace() const override;
+ void setCurrentWorkspace(Workspace* workspace) override;
+
+protected:
+ void resetCurrentWorkspace();
+
+ const QScopedPointer m_workspaces;
+ QPointer m_currentWorspace;
+};
+
+class ProxyScreen : public Screen
+{
+ Q_OBJECT
+public:
+ explicit ProxyScreen(Screen*const screen, ProxyScreens* screens);
+
+ // From qtmir::Screen
+ WorkspaceModel* workspaces() const override;
+ Workspace *currentWorkspace() const override;
+ void setCurrentWorkspace(Workspace* workspace) override;
+
+ Screen* proxyObject() const { return m_original.data(); }
+
+ bool isSyncing() const;
+
+private:
+ const QScopedPointer m_workspaces;
+ const QPointer m_original;
+ const ProxyScreens* m_screens;
+ QPointer m_currentWorspace;
+};
+
+class ScreenConfig: public QObject
+{
+ Q_OBJECT
+ Q_PRIVATE_PROPERTY(m_config, bool valid MEMBER used CONSTANT)
+ Q_PRIVATE_PROPERTY(m_config, bool used MEMBER used)
+ Q_PRIVATE_PROPERTY(m_config, float scale MEMBER scale)
+ Q_PRIVATE_PROPERTY(m_config, qtmir::FormFactor formFactor MEMBER formFactor)
+ Q_PRIVATE_PROPERTY(m_config, uint currentModeIndex MEMBER currentModeIndex)
+ Q_PRIVATE_PROPERTY(m_config, QPoint position MEMBER topLeft)
+
+public:
+ ScreenConfig(qtmir::ScreenConfiguration*);
+ ~ScreenConfig();
+
+ qtmir::ScreenConfiguration* m_config;
+};
+
+#endif // SCREEN_H
diff --git a/plugins/WindowManager/ScreenAttached.cpp b/plugins/WindowManager/ScreenAttached.cpp
new file mode 100644
index 0000000000..3ef182817f
--- /dev/null
+++ b/plugins/WindowManager/ScreenAttached.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#include "ScreenAttached.h"
+#include "ScreenWindow.h"
+#include "Screens.h"
+
+#include
+#include
+
+namespace
+{
+QQuickItem* itemForOwner(QObject* obj) {
+ QObject* parent = obj;
+ while(parent) {
+ auto item = qobject_cast(parent);
+ if (item) return item;
+ parent = parent->parent();
+ }
+ return nullptr;
+}
+} // namesapce
+
+ScreenAttached::ScreenAttached(QObject *owner)
+ : Screen(owner)
+ , m_window(nullptr)
+{
+ if (auto item = itemForOwner(owner)) {
+ connect(item, &QQuickItem::windowChanged, this, &ScreenAttached::windowChanged);
+ windowChanged(item->window());
+ } else if (auto window = qobject_cast(owner)) {
+ windowChanged(window);
+ }
+}
+
+WorkspaceModel *ScreenAttached::workspaces() const
+{
+ if (!m_screen) return nullptr;
+ return m_screen->workspaces();
+}
+
+Workspace *ScreenAttached::currentWorkspace() const
+{
+ if (!m_screen) return nullptr;
+ return m_screen->currentWorkspace();
+}
+
+void ScreenAttached::setCurrentWorkspace(Workspace *workspace)
+{
+ if (!m_screen) return;
+ return m_screen->setCurrentWorkspace(workspace);
+}
+
+void ScreenAttached::windowChanged(QQuickWindow *window)
+{
+ if (m_window) {
+ disconnect(m_window, &QWindow::screenChanged, this, &ScreenAttached::screenChanged);
+ }
+
+ m_window = window;
+ auto screenWindow = qobject_cast(window);
+
+ if (screenWindow) {
+ screenChanged2(screenWindow->screenWrapper());
+ connect(screenWindow, &ScreenWindow::screenWrapperChanged, this, &ScreenAttached::screenChanged2);
+ } else {
+ screenChanged(window ? window->screen() : NULL);
+ if (window) {
+ connect(window, &QWindow::screenChanged, this, &ScreenAttached::screenChanged);
+ }
+ }
+}
+
+void ScreenAttached::screenChanged(QScreen *qscreen)
+{
+ // Find a screen that matches.
+ // Should only get here in mocks if we don't have a ScreenWindow
+ Screen* screen{nullptr};
+ Q_FOREACH(auto s, ConcreteScreens::self()->list()) {
+ if (s->qscreen() == qscreen) {
+ screen = s;
+ }
+ }
+ screenChanged2(screen);
+}
+
+void ScreenAttached::screenChanged2(Screen* screen)
+{
+ if (screen == m_screen) return;
+
+ Screen* oldScreen = m_screen;
+ m_screen = screen;
+
+ if (oldScreen)
+ oldScreen->disconnect(this);
+
+ if (!screen)
+ return; //Don't bother emitting signals, because the new values are garbage anyways
+
+ if (!oldScreen || screen->isActive() != oldScreen->isActive())
+ Q_EMIT activeChanged(screen->isActive());
+ if (!oldScreen || screen->used() != oldScreen->used())
+ Q_EMIT usedChanged();
+ if (!oldScreen || screen->name() != oldScreen->name())
+ Q_EMIT nameChanged();
+ if (!oldScreen || screen->outputType() != oldScreen->outputType())
+ Q_EMIT outputTypeChanged();
+ if (!oldScreen || screen->scale() != oldScreen->scale())
+ Q_EMIT scaleChanged();
+ if (!oldScreen || screen->formFactor() != oldScreen->formFactor())
+ Q_EMIT formFactorChanged();
+ if (!oldScreen || screen->powerMode() != oldScreen->powerMode())
+ Q_EMIT powerModeChanged();
+ if (!oldScreen || screen->orientation() != oldScreen->orientation())
+ Q_EMIT orientationChanged();
+ if (!oldScreen || screen->position() != oldScreen->position())
+ Q_EMIT positionChanged();
+ if (!oldScreen || screen->currentModeIndex() != oldScreen->currentModeIndex())
+ Q_EMIT currentModeIndexChanged();
+ if (!oldScreen || screen->physicalSize() != oldScreen->physicalSize())
+ Q_EMIT physicalSizeChanged();
+ if (!oldScreen || screen->currentWorkspace() != oldScreen->currentWorkspace())
+ Q_EMIT currentWorkspaceChanged(currentWorkspace());
+
+ if (oldScreen) {
+ QVector oldModes;
+ auto oldModesQmlList = oldScreen->availableModes();
+ for (int i = 0; i < oldModesQmlList.count(&oldModesQmlList); i++) {
+ oldModes << oldModesQmlList.at(&oldModesQmlList, i);
+ }
+
+ QVector newModes;
+ auto newModesQmlList = screen->availableModes();
+ for (int i = 0; i < newModesQmlList.count(&newModesQmlList); i++) {
+ newModes << newModesQmlList.at(&newModesQmlList, i);
+ }
+
+ if (newModes != newModes) {
+ Q_EMIT availableModesChanged();
+ }
+ } else {
+ Q_EMIT availableModesChanged();
+ }
+
+ connectToScreen(screen);
+}
+
+ScreenAttached *WMScreen::qmlAttachedProperties(QObject *owner)
+{
+ return new ScreenAttached(owner);
+}
diff --git a/plugins/WindowManager/ScreenAttached.h b/plugins/WindowManager/ScreenAttached.h
new file mode 100644
index 0000000000..4fb0d2e3b5
--- /dev/null
+++ b/plugins/WindowManager/ScreenAttached.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SCREENATTACHED_H
+#define SCREENATTACHED_H
+
+#include "Screen.h"
+
+#include
+#include
+
+class QQuickWindow;
+
+class ScreenAttached : public Screen
+{
+ Q_OBJECT
+public:
+ ScreenAttached(QObject* owner);
+
+ WorkspaceModel* workspaces() const override;
+ Workspace *currentWorkspace() const override;
+ void setCurrentWorkspace(Workspace* workspace) override;
+
+private Q_SLOTS:
+ void windowChanged(QQuickWindow*);
+ void screenChanged(QScreen*);
+ void screenChanged2(Screen* screen);
+
+private:
+ QPointer m_screen;
+ QQuickWindow* m_window;
+};
+
+class WMScreen : public QObject
+{
+ Q_OBJECT
+public:
+ static ScreenAttached *qmlAttachedProperties(QObject *owner);
+};
+
+QML_DECLARE_TYPEINFO(WMScreen, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // SCREENATTACHED_H
diff --git a/plugins/WindowManager/ScreenWindow.cpp b/plugins/WindowManager/ScreenWindow.cpp
new file mode 100644
index 0000000000..312222e89b
--- /dev/null
+++ b/plugins/WindowManager/ScreenWindow.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016-2017 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#include "ScreenWindow.h"
+#include "Screen.h"
+
+// Qt
+#include
+#include
+
+ScreenWindow::ScreenWindow(QQuickWindow *parent)
+ : QQuickWindow(parent)
+{
+}
+
+ScreenWindow::~ScreenWindow()
+{
+}
+
+ConcreteScreen *ScreenWindow::screenWrapper() const
+{
+ return m_screen.data();
+}
+
+void ScreenWindow::setScreenWrapper(ConcreteScreen *screen)
+{
+ if (m_screen != screen) {
+ m_screen = screen;
+ Q_EMIT screenWrapperChanged(screen);
+ }
+ QQuickWindow::setScreen(screen->qscreen());
+}
diff --git a/plugins/WindowManager/ScreenWindow.h b/plugins/WindowManager/ScreenWindow.h
new file mode 100644
index 0000000000..5afc6096dd
--- /dev/null
+++ b/plugins/WindowManager/ScreenWindow.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016-2017 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef UNITY_SCREENWINDOW_H
+#define UNITY_SCREENWINDOW_H
+
+#include
+#include
+
+#include "Screen.h"
+
+class ScreenAdapter;
+
+/*
+ * ScreenWindow - wrapper of QQuickWindow to enable QML to specify destination screen.
+**/
+class ScreenWindow : public QQuickWindow
+{
+ Q_OBJECT
+ Q_PROPERTY(ConcreteScreen *screen READ screenWrapper WRITE setScreenWrapper NOTIFY screenWrapperChanged)
+ Q_PROPERTY(int winId READ winId CONSTANT)
+public:
+ explicit ScreenWindow(QQuickWindow *parent = 0);
+ ~ScreenWindow();
+
+ ConcreteScreen *screenWrapper() const;
+ void setScreenWrapper(ConcreteScreen *screen);
+
+Q_SIGNALS:
+ void screenWrapperChanged(ConcreteScreen* screen);
+
+private:
+ QPointer m_screen;
+};
+
+#endif // UNITY_SCREENWINDOW_H
diff --git a/plugins/WindowManager/Screens.cpp b/plugins/WindowManager/Screens.cpp
new file mode 100644
index 0000000000..796e8bf709
--- /dev/null
+++ b/plugins/WindowManager/Screens.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016-2017 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#include "Screens.h"
+#include "ScreensConfiguration.h"
+#include "Screen.h"
+#include "WorkspaceManager.h"
+
+// qtmirserver
+#include
+#include
+#include
+
+// Qt
+#include
+#include
+
+ConcreteScreens* ConcreteScreens::m_self{nullptr};
+
+Screens::Screens(const QSharedPointer& model)
+ : m_wrapped(model)
+{
+}
+
+Screens::~Screens()
+{
+ qDeleteAll(m_screens);
+ m_screens.clear();
+}
+
+QHash Screens::roleNames() const
+{
+ QHash roles;
+ roles[ScreenRole] = "screen";
+ return roles;
+}
+
+QVariant Screens::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= m_screens.size()) {
+ return QVariant();
+ }
+
+ switch(role) {
+ case ScreenRole:
+ return QVariant::fromValue(m_screens.at(index.row()));
+ } // switch
+
+ return QVariant();
+}
+
+int Screens::rowCount(const QModelIndex &) const
+{
+ return count();
+}
+
+int Screens::indexOf(Screen *screen) const
+{
+ return m_screens.indexOf(screen);
+}
+
+Screen *Screens::get(int index) const
+{
+ return m_screens.at(index);
+}
+
+int Screens::count() const
+{
+ return m_screens.size();
+}
+
+QVariant Screens::activeScreen() const
+{
+ for (int i = 0; i < m_screens.count(); i++) {
+ if (m_screens[i]->isActive()) return i;
+ }
+ return QVariant();
+}
+
+void Screens::activateScreen(const QVariant& vindex)
+{
+ bool ok = false;
+ int index = vindex.toInt(&ok);
+ if (!ok || index < 0 || m_screens.count() <= index) return;
+
+ auto screen = m_screens.at(index);
+ screen->setActive(true);
+}
+
+
+ConcreteScreens::ConcreteScreens(const QSharedPointer &model, ScreensConfiguration* config)
+ : Screens(model)
+ , m_config(config)
+{
+ m_self = this;
+ connect(m_wrapped.data(), &qtmir::Screens::screenAdded, this, &ConcreteScreens::onScreenAdded);
+ connect(m_wrapped.data(), &qtmir::Screens::screenRemoved, this, &ConcreteScreens::onScreenRemoved);
+ connect(m_wrapped.data(), &qtmir::Screens::activeScreenChanged, this, &ConcreteScreens::activeScreenChanged);
+
+ Q_FOREACH(qtmir::Screen* screen, m_wrapped->screens()) {
+ auto screenWrapper(new ConcreteScreen(screen));
+ m_config->load(screenWrapper);
+
+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
+ m_screens.push_back(screenWrapper);
+ }
+}
+
+ConcreteScreens::~ConcreteScreens()
+{
+ Q_FOREACH(Screen* screen, m_screens) {
+ m_config->save(screen);
+ }
+ delete m_config;
+}
+
+ConcreteScreens *ConcreteScreens::self()
+{
+ return ConcreteScreens::m_self;
+}
+
+ProxyScreens *ConcreteScreens::createProxy()
+{
+ return new ProxyScreens(this);
+}
+
+void ConcreteScreens::sync(ProxyScreens *proxy)
+{
+ if (!proxy) return;
+ proxy->setSyncing(true);
+
+ const auto& proxyList = proxy->list();
+ for (int i = 0; i < m_screens.count() && i < proxyList.count(); ++i) {
+ m_screens[i]->sync(proxyList[i]);
+ }
+
+ proxy->setSyncing(false);
+}
+
+void ConcreteScreens::onScreenAdded(qtmir::Screen *added)
+{
+ Q_FOREACH(auto screenWrapper, m_screens) {
+ if (screenWrapper->wrapped() == added) return;
+ }
+
+ beginInsertRows(QModelIndex(), count(), count());
+ auto screenWrapper(new ConcreteScreen(added));
+ m_config->load(screenWrapper);
+
+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
+ m_screens.push_back(screenWrapper);
+ endInsertRows();
+ Q_EMIT screenAdded(screenWrapper);
+ Q_EMIT countChanged();
+}
+
+void ConcreteScreens::onScreenRemoved(qtmir::Screen *removed)
+{
+ int index = 0;
+ QMutableVectorIterator iter(m_screens);
+ while(iter.hasNext()) {
+ auto screenWrapper = iter.next();
+ if (screenWrapper->wrapped() == removed) {
+ m_config->save(screenWrapper);
+
+ beginRemoveRows(QModelIndex(), index, index);
+ iter.remove();
+ endRemoveRows();
+
+ Q_EMIT screenRemoved(screenWrapper);
+ Q_EMIT countChanged();
+
+ screenWrapper->deleteLater();
+ break;
+ }
+ index++;
+ }
+}
+
+
+ProxyScreens::ProxyScreens(Screens * const screens)
+ : Screens(screens->m_wrapped)
+ , m_original(screens)
+ , m_syncing(false)
+{
+ connect(screens, &Screens::screenAdded, this, [this](Screen *added) {
+ Q_FOREACH(auto screen, m_screens) {
+ auto proxy = static_cast(screen);
+ if (proxy->proxyObject() == added) return;
+ }
+
+ beginInsertRows(QModelIndex(), count(), count());
+ auto screenWrapper(new ProxyScreen(added, this));
+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
+ m_screens.push_back(screenWrapper);
+ endInsertRows();
+ Q_EMIT screenAdded(screenWrapper);
+ Q_EMIT countChanged();
+ });
+
+ connect(screens, &Screens::screenRemoved, this, [this](Screen *removed) {
+ int index = 0;
+ QMutableVectorIterator iter(m_screens);
+ while(iter.hasNext()) {
+ auto proxy = static_cast(iter.next());
+ if (proxy->proxyObject() == removed) {
+
+ beginRemoveRows(QModelIndex(), index, index);
+ iter.remove();
+ endRemoveRows();
+
+ Q_EMIT screenRemoved(proxy);
+ Q_EMIT countChanged();
+
+ delete proxy;
+ break;
+ }
+ index++;
+ }
+ });
+
+ Q_FOREACH(Screen* screen, screens->list()) {
+ auto screenWrapper(new ProxyScreen(screen, this));
+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
+ m_screens.push_back(screenWrapper);
+ }
+}
+
+void ProxyScreens::setSyncing(bool syncing)
+{
+ m_syncing = syncing;
+}
diff --git a/plugins/WindowManager/Screens.h b/plugins/WindowManager/Screens.h
new file mode 100644
index 0000000000..6f895e4ad3
--- /dev/null
+++ b/plugins/WindowManager/Screens.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License version 3, as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef UNITY_SCREENS_H
+#define UNITY_SCREENS_H
+
+#include
+#include
+#include
+
+namespace qtmir
+{
+class Screen;
+class Screens;
+}
+
+class Screen;
+class ProxyScreens;
+class ScreensConfiguration;
+
+class Screens : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(QVariant activeScreen READ activeScreen WRITE activateScreen NOTIFY activeScreenChanged)
+
+public:
+ enum ItemRoles {
+ ScreenRole = Qt::UserRole + 1
+ };
+
+ virtual ~Screens();
+
+ /* QAbstractItemModel */
+ QHash roleNames() const override;
+ QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ Q_INVOKABLE int indexOf(Screen*) const;
+ Q_INVOKABLE Screen* get(int index) const;
+
+ int count() const;
+ QVariant activeScreen() const;
+
+ const QVector& list() const { return m_screens; }
+
+public Q_SLOTS:
+ void activateScreen(const QVariant& index);
+
+Q_SIGNALS:
+ void countChanged();
+ void activeScreenChanged();
+
+ void screenAdded(Screen* screen);
+ void screenRemoved(Screen* screen);
+
+protected:
+ Screens(const QSharedPointer& model);
+
+ QVector m_screens;
+ const QSharedPointer m_wrapped;
+
+ friend class ProxyScreens;
+};
+
+class ConcreteScreens : public Screens
+{
+ Q_OBJECT
+public:
+ explicit ConcreteScreens(const QSharedPointer& model, ScreensConfiguration* config);
+ ~ConcreteScreens();
+
+ Q_INVOKABLE ProxyScreens *createProxy();
+ Q_INVOKABLE void sync(ProxyScreens *proxy);
+
+ static ConcreteScreens *self();
+
+protected Q_SLOTS:
+ void onScreenAdded(qtmir::Screen *screen);
+ void onScreenRemoved(qtmir::Screen *screen);
+
+private:
+ ScreensConfiguration* m_config;
+
+ static ConcreteScreens* m_self;
+};
+
+class ProxyScreens : public Screens
+{
+public:
+ explicit ProxyScreens(Screens*const screens);
+
+ void setSyncing(bool syncing);
+ bool isSyncing() const { return m_syncing; }
+
+private:
+ const QPointer m_original;
+ bool m_syncing;
+};
+
+#endif // SCREENS_H
diff --git a/plugins/WindowManager/ScreensConfiguration.cpp b/plugins/WindowManager/ScreensConfiguration.cpp
new file mode 100644
index 0000000000..f587346132
--- /dev/null
+++ b/plugins/WindowManager/ScreensConfiguration.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "ScreensConfiguration.h"
+#include "Screen.h"
+#include "Workspace.h"
+#include "WorkspaceManager.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+QJsonArray jsonScreens;
+}
+
+ScreensConfiguration::ScreensConfiguration()
+{
+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
+ QFile f(dbPath + "workspaces");
+
+ if (f.open(QIODevice::ReadOnly)) {
+ QByteArray saveData = f.readAll();
+ QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
+ QJsonObject json(loadDoc.object());
+ jsonScreens = json["screens"].toArray();
+ }
+}
+
+ScreensConfiguration::~ScreensConfiguration()
+{
+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
+ QFile f(dbPath + "workspaces");
+ if (f.open(QIODevice::WriteOnly)) {
+ QJsonObject json;
+ json["screens"] = jsonScreens;
+ QJsonDocument saveDoc(json);
+ f.write(saveDoc.toJson());
+ }
+}
+
+void ScreensConfiguration::load(Screen *screen)
+{
+ int workspaces = 2;
+ for (auto iter = jsonScreens.begin(); iter != jsonScreens.end(); ++iter) {
+ QJsonObject jsonScreen = (*iter).toObject();
+ if (jsonScreen["name"] == screen->name()) {
+ QJsonValue jsonWorkspaces = jsonScreen["workspaces"];
+ workspaces = qMax(jsonWorkspaces.toInt(workspaces), 1);
+ break;
+ }
+ }
+
+ for (int i = 0; i < workspaces; i++) {
+ WorkspaceManager::instance()->createWorkspace()->assign(screen->workspaces());
+ }
+}
+
+void ScreensConfiguration::save(Screen *screen)
+{
+ QJsonObject newJsonScreen;
+ newJsonScreen["name"] = screen->name();
+ newJsonScreen["workspaces"] = qMax(screen->workspaces()->rowCount(), 1);
+
+ auto iter = jsonScreens.begin();
+ for (; iter != jsonScreens.end(); ++iter) {
+ QJsonObject jsonScreen = (*iter).toObject();
+ if (jsonScreen["name"] == screen->name()) {
+ break;
+ }
+ }
+
+ if (iter == jsonScreens.end()) {
+ jsonScreens.push_back(newJsonScreen);
+ } else {
+ *iter = newJsonScreen;
+ }
+}
diff --git a/src/ShellView.h b/plugins/WindowManager/ScreensConfiguration.h
similarity index 65%
rename from src/ShellView.h
rename to plugins/WindowManager/ScreensConfiguration.h
index 607265a38e..6cedd5bad0 100644
--- a/src/ShellView.h
+++ b/plugins/WindowManager/ScreensConfiguration.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Canonical, Ltd.
+ * Copyright (C) 2017 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,21 +14,19 @@
* along with this program. If not, see .
*/
-#ifndef UNITY_SHELL_VIEW_H
-#define UNITY_SHELL_VIEW_H
+#ifndef SCREENSCONFIGURATION_H
+#define SCREENSCONFIGURATION_H
-#include
+class Screen;
-class ShellView : public QQuickView
+class ScreensConfiguration
{
- Q_OBJECT
-
public:
- ShellView(QQmlEngine *engine, QObject *qmlArgs);
+ explicit ScreensConfiguration();
+ ~ScreensConfiguration();
-private Q_SLOTS:
- void onWidthChanged(int);
- void onHeightChanged(int);
+ void load(Screen* screen);
+ void save(Screen* screen);
};
-#endif // UNITY_SHELL_VIEW_H
+#endif // SCREENSCONFIGURATION_H
diff --git a/plugins/WindowManager/TopLevelWindowModel.cpp b/plugins/WindowManager/TopLevelWindowModel.cpp
index 9c602a64c5..c6c3f458c3 100644
--- a/plugins/WindowManager/TopLevelWindowModel.cpp
+++ b/plugins/WindowManager/TopLevelWindowModel.cpp
@@ -16,6 +16,7 @@
*/
#include "TopLevelWindowModel.h"
+#include "WindowManagerObjects.h"
// unity-api
#include
@@ -25,11 +26,11 @@
#include
// Qt
-#include
#include
// local
#include "Window.h"
+#include "Workspace.h"
#include "InputMethodManager.h"
Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
@@ -39,15 +40,28 @@ Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
namespace unityapi = unity::shell::application;
-TopLevelWindowModel::TopLevelWindowModel()
- : m_nullWindow(createNullWindow()),
+TopLevelWindowModel::TopLevelWindowModel(Workspace* workspace)
+ : m_nullWindow(createWindow(nullptr)),
+ m_workspace(workspace),
m_surfaceManagerBusy(false)
{
+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::applicationManagerChanged,
+ this, &TopLevelWindowModel::setApplicationManager);
+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::surfaceManagerChanged,
+ this, &TopLevelWindowModel::setSurfaceManager);
+
+ setApplicationManager(WindowManagerObjects::instance()->applicationManager());
+ setSurfaceManager(WindowManagerObjects::instance()->surfaceManager());
+
connect(m_nullWindow, &Window::focusedChanged, this, [this] {
Q_EMIT rootFocusChanged();
});
}
+TopLevelWindowModel::~TopLevelWindowModel()
+{
+}
+
void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
{
if (m_applicationManager == value) {
@@ -62,7 +76,6 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
beginResetModel();
if (m_applicationManager) {
- m_windowModel.clear();
disconnect(m_applicationManager, 0, this, 0);
}
@@ -71,26 +84,26 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
if (m_applicationManager) {
connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
this, [this](const QModelIndex &/*parent*/, int first, int last) {
- for (int i = first; i <= last; ++i) {
- auto application = m_applicationManager->get(i);
- addApplication(application);
- }
- });
+ if (!m_workspace || !m_workspace->isActive())
+ return;
+
+ for (int i = first; i <= last; ++i) {
+ auto application = m_applicationManager->get(i);
+ addApplication(application);
+ }
+ });
connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
this, [this](const QModelIndex &/*parent*/, int first, int last) {
- for (int i = first; i <= last; ++i) {
- auto application = m_applicationManager->get(i);
- removeApplication(application);
- }
- });
-
- for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
- auto application = m_applicationManager->get(i);
- addApplication(application);
- }
+ for (int i = first; i <= last; ++i) {
+ auto application = m_applicationManager->get(i);
+ removeApplication(application);
+ }
+ });
}
+ refreshWindows();
+
endResetModel();
m_modelState = IdleState;
}
@@ -103,6 +116,11 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
DEBUG_MSG << "(" << surfaceManager << ")";
+ Q_ASSERT(m_modelState == IdleState);
+ m_modelState = ResettingState;
+
+ beginResetModel();
+
if (m_surfaceManager) {
disconnect(m_surfaceManager, 0, this, 0);
}
@@ -110,13 +128,16 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
m_surfaceManager = surfaceManager;
if (m_surfaceManager) {
- connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesAddedToWorkspace, this, &TopLevelWindowModel::onSurfacesAddedToWorkspace);
connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
}
- Q_EMIT surfaceManagerChanged(m_surfaceManager);
+ refreshWindows();
+
+ endResetModel();
+ m_modelState = IdleState;
}
void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
@@ -156,6 +177,7 @@ void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface,
Q_ASSERT(surface != nullptr);
connectSurface(surface);
+ m_allSurfaces.insert(surface);
bool filledPlaceholder = false;
for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
@@ -231,15 +253,14 @@ void TopLevelWindowModel::connectWindow(Window *window)
connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
if (window->surface()) {
- // Condense changes to the focused window
- // eg: Do focusedWindow=A to focusedWindow=B instead of
- // focusedWindow=A to focusedWindow=null to focusedWindow=B
if (focused) {
- Q_ASSERT(m_newlyFocusedWindow == nullptr);
- m_focusedWindowChanged = true;
- m_newlyFocusedWindow = window;
+ setFocusedWindow(window);
+ m_focusedWindowCleared = false;
} else if (m_focusedWindow == window) {
- m_focusedWindowChanged = true;
+ // Condense changes to the focused window
+ // eg: Do focusedWindow=A to focusedWindow=B instead of
+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
+ m_focusedWindowCleared = true;
} else {
// don't clear the focused window if you were not there in the first place
// happens when a filled window gets replaced with an empty one (no surface) as the focused window.
@@ -344,6 +365,7 @@ void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surf
auto window = m_windowModel[i].window;
window->setSurface(nullptr);
window->setFocused(false);
+ m_allSurfaces.remove(surface);
INFO_MSG << " Removed surface from entry. After: " << toString();
}
}
@@ -369,48 +391,111 @@ Window *TopLevelWindowModel::createWindowWithId(unityapi::MirSurfaceInterface *s
return qmlWindow;
}
-void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
+void TopLevelWindowModel::onSurfacesAddedToWorkspace(const std::shared_ptr& workspace,
+ const QVector surfaces)
{
- DEBUG_MSG << "(" << surface << ")";
+ if (!m_workspace || !m_applicationManager) return;
+ if (workspace != m_workspace->workspace()) {
+ removeSurfaces(surfaces);
+ return;
+ }
- if (surface->parentSurface()) {
- // Wrap it in a Window so that we keep focusedWindow() up to date.
- Window *window = createWindow(surface);
- connect(surface, &QObject::destroyed, window, [=](){
- window->setSurface(nullptr);
- window->deleteLater();
- });
- } else {
- if (surface->type() == Mir::InputMethodType) {
- connectSurface(surface);
- setInputMethodWindow(createWindow(surface));
+ Q_FOREACH(auto surface, surfaces) {
+ if (m_allSurfaces.contains(surface)) continue;
+
+ if (surface->parentSurface()) {
+ // Wrap it in a Window so that we keep focusedWindow() up to date.
+ Window *window = createWindow(surface);
+ connect(surface, &QObject::destroyed, window, [=](){
+ window->setSurface(nullptr);
+ window->deleteLater();
+ });
} else {
- auto *application = m_applicationManager->findApplicationWithSurface(surface);
- if (application) {
- if (surface->state() == Mir::HiddenState) {
- // Ignore it until it's finally shown
- connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
- Q_ASSERT(newState != Mir::HiddenState);
- disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
+ if (surface->type() == Mir::InputMethodType) {
+ connectSurface(surface);
+ setInputMethodWindow(createWindow(surface));
+ } else {
+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
+ if (application) {
+ if (surface->state() == Mir::HiddenState) {
+ // Ignore it until it's finally shown
+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
+ Q_ASSERT(newState != Mir::HiddenState);
+ disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
+ prependSurface(surface, application);
+ });
+ } else {
prependSurface(surface, application);
- });
+ }
} else {
- prependSurface(surface, application);
+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
+ Window *promptWindow = createWindow(surface);
+ connect(surface, &QObject::destroyed, promptWindow, [=](){
+ promptWindow->setSurface(nullptr);
+ promptWindow->deleteLater();
+ });
}
- } else {
- // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
- // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
- // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
- Window *promptWindow = createWindow(surface);
- connect(surface, &QObject::destroyed, promptWindow, [=](){
- promptWindow->setSurface(nullptr);
- promptWindow->deleteLater();
- });
}
}
}
}
+void TopLevelWindowModel::removeSurfaces(const QVector surfaces)
+{
+ int start = -1;
+ int end = -1;
+ for (auto iter = surfaces.constBegin(); iter != surfaces.constEnd();) {
+ auto surface = *iter;
+ iter++;
+
+ // Do removals in adjacent blocks.
+ start = end = indexOf(surface);
+ if (start == -1) {
+ // could be a child surface
+ m_allSurfaces.remove(surface);
+ continue;
+ }
+ while(iter != surfaces.constEnd()) {
+ int index = indexOf(*iter);
+ if (index != end+1) {
+ break;
+ }
+ end++;
+ iter++;
+ }
+
+ if (m_modelState == IdleState) {
+ beginRemoveRows(QModelIndex(), start, end);
+ m_modelState = RemovingState;
+ } else {
+ Q_ASSERT(m_modelState == ResettingState);
+ // No point in signaling anything if we're resetting the whole model
+ }
+
+ for (int index = start; index <= end; index++) {
+ auto window = m_windowModel[start].window;
+ window->setSurface(nullptr);
+ window->setFocused(false);
+
+ if (window == m_previousWindow) {
+ m_previousWindow = nullptr;
+ }
+
+ m_windowModel.removeAt(start);
+ m_allSurfaces.remove(surface);
+ }
+
+ if (m_modelState == RemovingState) {
+ endRemoveRows();
+ Q_EMIT countChanged();
+ Q_EMIT listChanged();
+ m_modelState = IdleState;
+ }
+ }
+}
+
void TopLevelWindowModel::deleteAt(int index)
{
auto window = m_windowModel[index].window;
@@ -433,12 +518,18 @@ void TopLevelWindowModel::removeAt(int index)
}
auto window = m_windowModel[index].window;
+ auto surface = window->surface();
if (!window->surface()) {
window->setFocused(false);
}
+ if (window == m_previousWindow) {
+ m_previousWindow = nullptr;
+ }
+
m_windowModel.removeAt(index);
+ m_allSurfaces.remove(surface);
if (m_modelState == RemovingState) {
endRemoveRows();
@@ -449,6 +540,7 @@ void TopLevelWindowModel::removeAt(int index)
if (m_focusedWindow == window) {
setFocusedWindow(nullptr);
+ m_focusedWindowCleared = false;
}
if (m_previousWindow == window) {
@@ -478,6 +570,15 @@ void TopLevelWindowModel::setInputMethodWindow(Window *window)
void TopLevelWindowModel::removeInputMethodWindow()
{
if (m_inputMethodWindow) {
+ auto surface = m_inputMethodWindow->surface();
+ if (surface) {
+ m_allSurfaces.remove(surface);
+ }
+ if (m_focusedWindow == m_inputMethodWindow) {
+ setFocusedWindow(nullptr);
+ m_focusedWindowCleared = false;
+ }
+
delete m_inputMethodWindow;
m_inputMethodWindow = nullptr;
Q_EMIT inputMethodSurfaceChanged(nullptr);
@@ -490,7 +591,7 @@ void TopLevelWindowModel::onSurfacesRaised(const QVectorsurface() == surface) {
- return i;
- }
- }
- return -1;
-}
-
QString TopLevelWindowModel::toString()
{
QString str;
@@ -701,12 +792,11 @@ void TopLevelWindowModel::onModificationsStarted()
void TopLevelWindowModel::onModificationsEnded()
{
- if (m_focusedWindowChanged) {
- setFocusedWindow(m_newlyFocusedWindow);
+ if (m_focusedWindowCleared) {
+ setFocusedWindow(nullptr);
}
// reset
- m_focusedWindowChanged = false;
- m_newlyFocusedWindow = nullptr;
+ m_focusedWindowCleared = false;
m_surfaceManagerBusy = false;
}
@@ -723,6 +813,58 @@ void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
}
}
+void TopLevelWindowModel::refreshWindows()
+{
+ DEBUG_MSG << "()";
+ clear();
+
+ if (!m_workspace || !m_applicationManager || !m_surfaceManager) return;
+
+ m_surfaceManager->forEachSurfaceInWorkspace(m_workspace->workspace(), [this](unity::shell::application::MirSurfaceInterface* surface) {
+ if (surface->parentSurface()) {
+ // Wrap it in a Window so that we keep focusedWindow() up to date.
+ Window *window = createWindow(surface);
+ connect(surface, &QObject::destroyed, window, [=](){
+ window->setSurface(nullptr);
+ window->deleteLater();
+ });
+ } else {
+ if (surface->type() == Mir::InputMethodType) {
+ setInputMethodWindow(createWindow(surface));
+ } else {
+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
+ if (application) {
+ prependSurface(surface, application);
+ } else {
+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
+ Window *promptWindow = createWindow(surface);
+ connect(surface, &QObject::destroyed, promptWindow, [=](){
+ promptWindow->setSurface(nullptr);
+ promptWindow->deleteLater();
+ });
+ }
+ }
+ }
+ });
+}
+
+void TopLevelWindowModel::clear()
+{
+ DEBUG_MSG << "()";
+
+ while(m_windowModel.count() > 0) {
+ ModelEntry entry = m_windowModel.takeAt(0);
+ disconnect(entry.window, 0, this, 0);
+ delete entry.window;
+ }
+ m_allSurfaces.clear();
+ setFocusedWindow(nullptr);
+ m_focusedWindowCleared = false;
+ m_previousWindow = nullptr;
+}
+
void TopLevelWindowModel::closeAllWindows()
{
m_closingAllApps = true;
diff --git a/plugins/WindowManager/TopLevelWindowModel.h b/plugins/WindowManager/TopLevelWindowModel.h
index 1312bfa319..2434f3a992 100644
--- a/plugins/WindowManager/TopLevelWindowModel.h
+++ b/plugins/WindowManager/TopLevelWindowModel.h
@@ -22,11 +22,16 @@
#include
#include
+#include
+
#include "WindowManagerGlobal.h"
Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
class Window;
+class Workspace;
+
+namespace miral { class Workspace; }
namespace unity {
namespace shell {
@@ -74,16 +79,6 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
*/
Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
- Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
- READ surfaceManager
- WRITE setSurfaceManager
- NOTIFY surfaceManagerChanged)
-
- Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
- READ applicationManager
- WRITE setApplicationManager
- NOTIFY applicationManagerChanged)
-
/**
The id to be used on the next entry created
Useful for tests
@@ -123,7 +118,8 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
ApplicationRole = Qt::UserRole + 1,
};
- TopLevelWindowModel();
+ TopLevelWindowModel(Workspace* workspace);
+ ~TopLevelWindowModel();
// From QAbstractItemModel
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -134,17 +130,11 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
return roleNames;
}
- // Own API
+ // Own API;
unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
Window* focusedWindow() const;
- unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
- void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
-
- unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
- void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
-
int nextId() const { return m_nextId.load(); }
public:
@@ -197,6 +187,8 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
*/
Q_INVOKABLE void pendingActivation();
+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
void setRootFocus(bool focus);
bool rootFocus();
@@ -204,8 +196,6 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
void countChanged();
void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
void focusedWindowChanged(Window *focusedWindow);
- void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
- void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
/**
* @brief Emitted when the list changes
@@ -219,7 +209,8 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
void rootFocusChanged();
private Q_SLOTS:
- void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
+ void onSurfacesAddedToWorkspace(const std::shared_ptr& workspace,
+ const QVector surfaces);
void onSurfacesRaised(const QVector &surfaces);
void onModificationsStarted();
@@ -236,9 +227,9 @@ private Q_SLOTS:
void setInputMethodWindow(Window *window);
void setFocusedWindow(Window *window);
void removeInputMethodWindow();
- int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
void deleteAt(int index);
void removeAt(int index);
+ void removeSurfaces(const QVector surfaces);
void addApplication(unity::shell::application::ApplicationInfoInterface *application);
void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
@@ -261,6 +252,8 @@ private Q_SLOTS:
void activateEmptyWindow(Window *window);
void activateTopMostWindowWithoutId(int forbiddenId);
+ void refreshWindows();
+ void clear();
Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
Window *createWindowWithId(unity::shell::application::MirSurfaceInterface *surface, int id);
@@ -280,6 +273,9 @@ private Q_SLOTS:
Window* m_inputMethodWindow{nullptr};
Window* m_focusedWindow{nullptr};
Window* m_nullWindow;
+ Workspace* m_workspace{nullptr};
+ // track all the surfaces we've been told about.
+ QSet m_allSurfaces;
Window* m_previousWindow{nullptr};
bool m_pendingActivation;
@@ -299,8 +295,7 @@ private Q_SLOTS:
ModelState m_modelState{IdleState};
// Valid between modificationsStarted and modificationsEnded
- bool m_focusedWindowChanged{false};
- Window *m_newlyFocusedWindow{nullptr};
+ bool m_focusedWindowCleared{false};
bool m_closingAllApps{false};
};
diff --git a/plugins/WindowManager/WindowManagerObjects.cpp b/plugins/WindowManager/WindowManagerObjects.cpp
new file mode 100644
index 0000000000..e9e1a4f207
--- /dev/null
+++ b/plugins/WindowManager/WindowManagerObjects.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "WindowManagerObjects.h"
+
+WindowManagerObjects::WindowManagerObjects(QObject *parent)
+ : QObject(parent)
+ , m_surfaceManager(nullptr)
+ , m_applicationManager(nullptr)
+{
+}
+
+WindowManagerObjects *WindowManagerObjects::instance()
+{
+ static WindowManagerObjects* objects(new WindowManagerObjects());
+ return objects;
+}
+
+
+void WindowManagerObjects::setSurfaceManager(unity::shell::application::SurfaceManagerInterface *surfaceManager)
+{
+ if (m_surfaceManager == surfaceManager) return;
+
+ m_surfaceManager = surfaceManager;
+ Q_EMIT surfaceManagerChanged(surfaceManager);
+}
+
+void WindowManagerObjects::setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager)
+{
+ if (m_applicationManager == applicationManager) return;
+
+ m_applicationManager = applicationManager;
+ Q_EMIT applicationManagerChanged(applicationManager);
+}
diff --git a/plugins/WindowManager/WindowManagerObjects.h b/plugins/WindowManager/WindowManagerObjects.h
new file mode 100644
index 0000000000..c6fe7e9945
--- /dev/null
+++ b/plugins/WindowManager/WindowManagerObjects.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef WINDOWMANAGEROBJECTS_H
+#define WINDOWMANAGEROBJECTS_H
+
+#include
+
+#include "WindowManagerGlobal.h"
+
+namespace unity {
+ namespace shell {
+ namespace application {
+ class SurfaceManagerInterface;
+ class ApplicationManagerInterface;
+ }
+ }
+}
+
+class WINDOWMANAGERQML_EXPORT WindowManagerObjects : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
+ READ surfaceManager
+ WRITE setSurfaceManager
+ NOTIFY surfaceManagerChanged)
+
+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
+ READ applicationManager
+ WRITE setApplicationManager
+ NOTIFY applicationManagerChanged)
+public:
+ explicit WindowManagerObjects(QObject *parent = 0);
+
+ static WindowManagerObjects *instance();
+
+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
+
+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
+
+Q_SIGNALS:
+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
+
+private:
+ unity::shell::application::SurfaceManagerInterface* m_surfaceManager;
+ unity::shell::application::ApplicationManagerInterface* m_applicationManager;
+};
+
+#endif // WINDOWMANAGEROBJECTS_H
diff --git a/plugins/WindowManager/WindowManagerPlugin.cpp b/plugins/WindowManager/WindowManagerPlugin.cpp
index c06b08fade..626e3e92c3 100644
--- a/plugins/WindowManager/WindowManagerPlugin.cpp
+++ b/plugins/WindowManager/WindowManagerPlugin.cpp
@@ -17,12 +17,45 @@
#include "WindowManagerPlugin.h"
#include "AvailableDesktopArea.h"
+#include "Screen.h"
+#include "ScreenAttached.h"
+#include "Screens.h"
+#include "ScreensConfiguration.h"
+#include "ScreenWindow.h"
#include "TopLevelWindowModel.h"
#include "Window.h"
+#include "WindowManagerObjects.h"
#include "WindowMargins.h"
+#include "WorkspaceManager.h"
+#include "Workspace.h"
+#include "WorkspaceModel.h"
#include "InputMethodManager.h"
#include
+#include
+
+namespace {
+
+static const QString notInstantiatable = QStringLiteral("Not instantiatable");
+
+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
+{
+ Q_UNUSED(engine)
+ Q_UNUSED(scriptEngine)
+ return WorkspaceManager::instance();
+}
+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
+ Q_UNUSED(engine);
+ Q_UNUSED(scriptEngine);
+ return ConcreteScreens::self();
+}
+QObject* objectsSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
+ Q_UNUSED(engine);
+ Q_UNUSED(scriptEngine);
+ return WindowManagerObjects::instance();
+}
+
+} // namspace
QObject *inputMethodManager(QQmlEngine *engine, QJSEngine *scriptEngine)
{
@@ -34,11 +67,33 @@ QObject *inputMethodManager(QQmlEngine *engine, QJSEngine *scriptEngine)
void WindowManagerPlugin::registerTypes(const char *uri)
{
qmlRegisterType(uri, 1, 0, "AvailableDesktopArea");
- qmlRegisterType(uri, 1, 0, "TopLevelWindowModel");
qmlRegisterType(uri, 1, 0, "WindowMargins");
+ qmlRegisterSingletonType(uri, 1, 0, "WorkspaceManager", workspace_manager);
+ qmlRegisterSingletonType(uri, 1, 0, "Screens", screensSingleton);
+ qmlRegisterUncreatableType(uri, 1, 0, "ScreenMode", notInstantiatable);
+ qmlRegisterSingletonType(uri, 1, 0, "WindowManagerObjects", objectsSingleton);
qmlRegisterSingletonType(uri, 1, 0, "InputMethodManager", inputMethodManager);
- qRegisterMetaType("Window*");
+ qRegisterMetaType("ConcreteScreen*");
+ qRegisterMetaType("ProxyScreens*");
+ qRegisterMetaType("Workspace*");
+ qRegisterMetaType("TopLevelWindowModel*");
+ qRegisterMetaType("ScreenConfig*");
+ qRegisterMetaType("WorkspaceModel*");
+ qRegisterMetaType("Window*");
qRegisterMetaType("QAbstractListModel*");
+
+ qmlRegisterType(uri, 1, 0, "ScreenWindow");
+ qmlRegisterRevision(uri, 1, 0);
+
+ qmlRegisterUncreatableType(uri, 1, 0, "WMScreen", notInstantiatable);
+}
+
+void WindowManagerPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
+{
+ QQmlExtensionPlugin::initializeEngine(engine, uri);
+
+ // Create Screens
+ new ConcreteScreens(qtmir::get_screen_model(), new ScreensConfiguration());
}
diff --git a/plugins/WindowManager/WindowManagerPlugin.h b/plugins/WindowManager/WindowManagerPlugin.h
index 48c6fe3718..c1c8d66678 100644
--- a/plugins/WindowManager/WindowManagerPlugin.h
+++ b/plugins/WindowManager/WindowManagerPlugin.h
@@ -27,6 +27,7 @@ class WindowManagerPlugin : public QQmlExtensionPlugin
public:
void registerTypes(const char *uri) override;
+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
};
#endif // WINDOWMANAGER_PLUGIN_H
diff --git a/plugins/WindowManager/Workspace.cpp b/plugins/WindowManager/Workspace.cpp
new file mode 100644
index 0000000000..14c659f96a
--- /dev/null
+++ b/plugins/WindowManager/Workspace.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "Workspace.h"
+#include "WorkspaceModel.h"
+#include "WorkspaceManager.h"
+#include "TopLevelWindowModel.h"
+#include "Screen.h"
+
+#include "wmpolicyinterface.h"
+
+int nextWorkspace = 0;
+
+Workspace::Workspace(QObject *parent)
+ : QObject(parent)
+ , m_workspace(WMPolicyInterface::instance()->createWorkspace())
+ , m_model(nullptr)
+{
+ setObjectName((QString("Wks%1").arg(nextWorkspace++)));
+}
+
+Workspace::Workspace(const Workspace &other)
+ : QObject(nullptr)
+ , m_workspace(other.m_workspace)
+ , m_model(nullptr)
+{
+ setObjectName(other.objectName());
+
+ connect(&other, &Workspace::activeChanged, this, &Workspace::activeChanged);
+}
+
+Workspace::~Workspace()
+{
+ if (m_model) {
+ m_model->remove(this);
+ }
+}
+
+void Workspace::assign(WorkspaceModel *model, const QVariant& vIndex)
+{
+ if (m_model == model) return;
+
+ if (m_model) {
+ disconnect(m_model, 0, this, 0);
+ m_model->remove(this);
+ }
+
+ m_model = model;
+
+ if (model) {
+ int index = m_model->rowCount();
+ if (vIndex.isValid() && vIndex.canConvert(QVariant::Int)) {
+ index = vIndex.toInt();
+ }
+ m_model->insert(index, this);
+
+ connect(m_model, &QObject::destroyed, this, [this]() {
+ m_model = nullptr;
+ Q_EMIT unassigned();
+ });
+ Q_EMIT assigned();
+ } else {
+ Q_EMIT unassigned();
+ }
+}
+
+void Workspace::unassign()
+{
+ assign(nullptr);
+}
+
+bool Workspace::isAssigned() const
+{
+ return m_model != nullptr;
+}
+
+bool Workspace::isSameAs(Workspace *wks) const
+{
+ if (!wks) return false;
+ if (wks == this) return true;
+ return wks->workspace() == workspace();
+}
+
+
+ConcreteWorkspace::ConcreteWorkspace(QObject *parent)
+ : Workspace(parent)
+ , m_active(false)
+ , m_windowModel(new TopLevelWindowModel(this))
+{
+ connect(WorkspaceManager::instance(), &WorkspaceManager::activeWorkspaceChanged, this, [this](Workspace* activeWorkspace) {
+ bool newActive = activeWorkspace == this;
+ if (newActive != m_active) {
+ m_active = newActive;
+ Q_EMIT activeChanged(m_active);
+
+ if (m_active) {
+ WMPolicyInterface::instance()->setActiveWorkspace(m_workspace);
+ }
+ }
+ });
+}
+
+ConcreteWorkspace::~ConcreteWorkspace()
+{
+ WorkspaceManager::instance()->destroyWorkspace(this);
+ WMPolicyInterface::instance()->releaseWorkspace(m_workspace);
+}
+
+TopLevelWindowModel *ConcreteWorkspace::windowModel() const
+{
+ return m_windowModel.data();
+}
+
+void ConcreteWorkspace::activate()
+{
+ WorkspaceManager::instance()->setActiveWorkspace(this);
+}
+
+void ConcreteWorkspace::setCurrentOn(Screen *screen)
+{
+ if (screen) {
+ screen->setCurrentWorkspace(this);
+ }
+}
+
+
+ProxyWorkspace::ProxyWorkspace(Workspace * const workspace)
+ : Workspace(*workspace)
+ , m_original(workspace)
+{
+}
+
+void ProxyWorkspace::assign(WorkspaceModel *model, const QVariant &index)
+{
+ Workspace::assign(model, index);
+}
+
+void ProxyWorkspace::unassign()
+{
+ Workspace::unassign();
+}
+
+bool ProxyWorkspace::isActive() const
+{
+ return m_original ? m_original->isActive() : false;
+}
+
+TopLevelWindowModel *ProxyWorkspace::windowModel() const
+{
+ return m_original ? m_original->windowModel() : nullptr;
+}
+
+void ProxyWorkspace::activate()
+{
+ if (m_original) {
+ m_original->activate();
+ }
+}
+
+void ProxyWorkspace::setCurrentOn(Screen *screen)
+{
+ if (screen && m_original) {
+ screen->setCurrentWorkspace(m_original);
+ }
+}
diff --git a/plugins/WindowManager/Workspace.h b/plugins/WindowManager/Workspace.h
new file mode 100644
index 0000000000..6d54d04416
--- /dev/null
+++ b/plugins/WindowManager/Workspace.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef WINDOWMANAGER_WORKSPACE_H
+#define WINDOWMANAGER_WORKSPACE_H
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "WindowManagerGlobal.h"
+
+class WorkspaceModel;
+class TopLevelWindowModel;
+class Screen;
+
+namespace miral { class Workspace; }
+
+namespace unity {
+ namespace shell {
+ namespace application {
+ class MirSurfaceInterface;
+ }
+ }
+}
+
+class WINDOWMANAGERQML_EXPORT Workspace : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
+ Q_PROPERTY(TopLevelWindowModel* windowModel READ windowModel CONSTANT)
+public:
+ virtual ~Workspace();
+
+ virtual void assign(WorkspaceModel* model, const QVariant& index = QVariant());
+ virtual void unassign();
+
+ virtual bool isActive() const = 0;
+ virtual TopLevelWindowModel *windowModel() const = 0;
+ virtual void setCurrentOn(Screen*) = 0;
+
+ std::shared_ptr workspace() const { return m_workspace; }
+ bool isAssigned() const;
+ Q_INVOKABLE bool isSameAs(Workspace*) const;
+
+public Q_SLOTS:
+ virtual void activate() = 0;
+
+Q_SIGNALS:
+ void assigned();
+ void unassigned();
+
+ void activeChanged(bool);
+
+protected:
+ Workspace(QObject *parent = nullptr);
+ Workspace(Workspace const& other);
+
+ std::shared_ptr m_workspace;
+ WorkspaceModel* m_model;
+};
+
+class WINDOWMANAGERQML_EXPORT ConcreteWorkspace : public Workspace
+{
+public:
+ ~ConcreteWorkspace();
+
+ bool isActive() const override { return m_active; }
+ TopLevelWindowModel *windowModel() const override;
+ void activate() override;
+ void setCurrentOn(Screen*) override;
+
+private:
+ explicit ConcreteWorkspace(QObject *parent = nullptr);
+
+ bool m_active;
+ const QScopedPointer m_windowModel;
+
+ friend class WorkspaceManager;
+};
+
+class ProxyWorkspace : public Workspace
+{
+ Q_OBJECT
+public:
+ explicit ProxyWorkspace(Workspace*const workspace);
+ ~ProxyWorkspace() = default;
+
+ Q_INVOKABLE void assign(WorkspaceModel* model, const QVariant& index = QVariant()) override;
+
+ bool isActive() const override;
+ TopLevelWindowModel *windowModel() const override;
+ void activate() override;
+ void setCurrentOn(Screen*) override;
+
+ Workspace* proxyObject() const { return m_original.data(); }
+
+public Q_SLOTS:
+ void unassign() override;
+
+private:
+ const QPointer m_original;
+};
+
+#endif // WINDOWMANAGER_WORKSPACE_H
diff --git a/plugins/WindowManager/WorkspaceManager.cpp b/plugins/WindowManager/WorkspaceManager.cpp
new file mode 100644
index 0000000000..95fd5f3992
--- /dev/null
+++ b/plugins/WindowManager/WorkspaceManager.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "WorkspaceManager.h"
+#include "Workspace.h"
+#include "TopLevelWindowModel.h"
+#include "WindowManagerObjects.h"
+#include
+
+// Qt
+#include
+#include
+#include
+
+WorkspaceManager *WorkspaceManager::instance()
+{
+ static WorkspaceManager* workspaceManager(new WorkspaceManager());
+ return workspaceManager;
+}
+
+WorkspaceManager::WorkspaceManager()
+ : m_activeWorkspace(nullptr),
+ m_surfaceManager(nullptr)
+{
+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::surfaceManagerChanged,
+ this, &WorkspaceManager::setSurfaceManager);
+
+ setSurfaceManager(WindowManagerObjects::instance()->surfaceManager());
+}
+
+void WorkspaceManager::setSurfaceManager(unity::shell::application::SurfaceManagerInterface *surfaceManager)
+{
+ if (m_surfaceManager == surfaceManager) return;
+
+ if (m_surfaceManager) {
+ disconnect(m_surfaceManager, &QObject::destroyed, this, 0);
+ }
+
+ m_surfaceManager = surfaceManager;
+
+ if (m_surfaceManager) {
+ connect(m_surfaceManager, &QObject::destroyed, this, [this](){
+ setSurfaceManager(nullptr);
+ });
+ }
+}
+
+Workspace *WorkspaceManager::createWorkspace()
+{
+ auto workspace = new ConcreteWorkspace(this);
+ QQmlEngine::setObjectOwnership(workspace, QQmlEngine::CppOwnership);
+ m_allWorkspaces.insert(workspace);
+
+ if (m_allWorkspaces.count() == 0 && m_activeWorkspace) {
+ setActiveWorkspace(nullptr);
+ } else if (m_allWorkspaces.count() == 1) {
+ setActiveWorkspace(workspace);
+ }
+
+ return workspace;
+}
+
+void WorkspaceManager::destroyWorkspace(Workspace *workspace)
+{
+ if (!workspace) return;
+
+ if (workspace->isAssigned()) {
+ workspace->unassign();
+ }
+ m_allWorkspaces.remove(workspace);
+
+ if (m_activeWorkspace == workspace) {
+ setActiveWorkspace(m_allWorkspaces.count() ? *m_allWorkspaces.begin() : nullptr);
+ }
+ if (m_activeWorkspace) {
+ moveWorkspaceContentToWorkspace(m_activeWorkspace, workspace);
+ }
+
+ disconnect(workspace, 0, this, 0);
+}
+
+void WorkspaceManager::moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface *surface, Workspace *workspace)
+{
+ if (m_surfaceManager) {
+ m_surfaceManager->moveSurfaceToWorkspace(surface, workspace->workspace());
+ }
+}
+
+void WorkspaceManager::moveWorkspaceContentToWorkspace(Workspace *to, Workspace *from)
+{
+ if (m_surfaceManager) {
+ m_surfaceManager->moveWorkspaceContentToWorkspace(to->workspace(), from->workspace());
+ }
+}
+
+Workspace *WorkspaceManager::activeWorkspace() const
+{
+ return m_activeWorkspace;
+}
+
+void WorkspaceManager::setActiveWorkspace(Workspace *workspace)
+{
+ if (workspace != m_activeWorkspace) {
+ m_activeWorkspace = workspace;
+ Q_EMIT activeWorkspaceChanged(workspace);
+ }
+}
+
+void WorkspaceManager::setActiveWorkspace2(Workspace *workspace)
+{
+ if (!workspace) return;
+ workspace->activate();
+}
diff --git a/plugins/WindowManager/WorkspaceManager.h b/plugins/WindowManager/WorkspaceManager.h
new file mode 100644
index 0000000000..46c9cf80c6
--- /dev/null
+++ b/plugins/WindowManager/WorkspaceManager.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef WORKSPACEMANAGER_H
+#define WORKSPACEMANAGER_H
+
+#include
+
+#include "WindowManagerGlobal.h"
+#include "WorkspaceModel.h"
+
+class Workspace;
+
+namespace unity {
+ namespace shell {
+ namespace application {
+ class MirSurfaceInterface;
+ class SurfaceManagerInterface;
+ }
+ }
+}
+
+class WINDOWMANAGERQML_EXPORT WorkspaceManager : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Workspace* activeWorkspace READ activeWorkspace WRITE setActiveWorkspace2 NOTIFY activeWorkspaceChanged)
+
+public:
+ WorkspaceManager();
+ static WorkspaceManager* instance();
+
+ Workspace* activeWorkspace() const;
+ void setActiveWorkspace(Workspace* workspace);
+
+ Workspace* createWorkspace();
+ void destroyWorkspace(Workspace* workspace);
+
+ void destroyFloatingWorkspaces();
+
+ Q_INVOKABLE void moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface* surface,
+ Workspace* workspace);
+
+ Q_INVOKABLE void moveWorkspaceContentToWorkspace(Workspace* to, Workspace* from);
+
+public Q_SLOTS:
+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
+
+Q_SIGNALS:
+ void activeWorkspaceChanged(Workspace*);
+
+private:
+ void setActiveWorkspace2(Workspace* workspace);
+
+ QSet m_allWorkspaces;
+ Workspace* m_activeWorkspace;
+ unity::shell::application::SurfaceManagerInterface* m_surfaceManager;
+};
+
+#endif // WORKSPACEMANAGER_H
diff --git a/plugins/WindowManager/WorkspaceModel.cpp b/plugins/WindowManager/WorkspaceModel.cpp
new file mode 100644
index 0000000000..45bc1e321b
--- /dev/null
+++ b/plugins/WindowManager/WorkspaceModel.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "WorkspaceModel.h"
+#include "WorkspaceManager.h"
+#include "Workspace.h"
+#include "Screen.h"
+
+#include
+
+Q_LOGGING_CATEGORY(WORKSPACES, "Workspaces", QtInfoMsg)
+
+#define DEBUG_MSG qCDebug(WORKSPACES).nospace().noquote() << __func__
+#define INFO_MSG qCInfo(WORKSPACES).nospace().noquote() << __func__
+
+WorkspaceModel::WorkspaceModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+WorkspaceModel::~WorkspaceModel()
+{
+ qDeleteAll(m_workspaces.toList()); // make a copy so the list doesnt edit itself during delete.
+ m_workspaces.clear();
+}
+
+void WorkspaceModel::append(Workspace *workspace)
+{
+ insert(m_workspaces.count(), workspace);
+}
+
+void WorkspaceModel::insert(int index, Workspace *workspace)
+{
+ beginInsertRows(QModelIndex(), index, index);
+
+ m_workspaces.insert(index, workspace);
+
+ endInsertRows();
+
+ Q_EMIT workspaceInserted(index, workspace);
+ Q_EMIT countChanged();
+}
+
+void WorkspaceModel::remove(Workspace *workspace)
+{
+ int index = m_workspaces.indexOf(workspace);
+ if (index < 0) return;
+
+ beginRemoveRows(QModelIndex(), index, index);
+
+ m_workspaces.removeAt(index);
+ insertUnassigned(workspace);
+
+ endRemoveRows();
+
+ Q_EMIT workspaceRemoved(workspace);
+ Q_EMIT countChanged();
+}
+
+void WorkspaceModel::move(int from, int to)
+{
+ if (from == to) return;
+ DEBUG_MSG << " from=" << from << " to=" << to;
+
+ if (from >= 0 && from < m_workspaces.size() && to >= 0 && to < m_workspaces.size()) {
+ QModelIndex parent;
+
+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ const auto &window = m_windowModel.takeAt(from);
+ m_workspaces.insert(to, window);
+#else
+ m_workspaces.move(from, to);
+#endif
+ endMoveRows();
+
+ Q_EMIT workspaceMoved(from, to);
+ }
+}
+
+int WorkspaceModel::indexOf(Workspace *workspace) const
+{
+ return m_workspaces.indexOf(workspace);
+}
+
+Workspace *WorkspaceModel::get(int index) const
+{
+ if (index < 0 || index >= rowCount()) return nullptr;
+ return m_workspaces.at(index);
+}
+
+int WorkspaceModel::rowCount(const QModelIndex &) const
+{
+ return m_workspaces.count();
+}
+
+QVariant WorkspaceModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() < 0 || index.row() >= m_workspaces.size())
+ return QVariant();
+
+ if (role == WorkspaceRole) {
+ Workspace *workspace = m_workspaces.at(index.row());
+ return QVariant::fromValue(workspace);
+ } else {
+ return QVariant();
+ }
+}
+
+void WorkspaceModel::sync(WorkspaceModel *proxy)
+{
+ if (!proxy) return;
+ const auto& proxyList = proxy->list();
+
+ // check for removals
+ int removedIndexWhichWasActive = -1;
+ QVector dpCpy(this->list());
+ Q_FOREACH(auto workspace, dpCpy) {
+
+ bool found = false;
+ Q_FOREACH(auto p, proxyList) {
+ auto workspaceProxy = qobject_cast(p);
+ if (workspaceProxy->proxyObject() == workspace) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (workspace->isActive()) {
+ removedIndexWhichWasActive = indexOf(workspace);
+ }
+ workspace->unassign();
+ }
+ }
+
+ // existing
+ QSet newWorkspaces;
+ for (int i = 0; i < proxyList.count(); i++) {
+ auto workspaceProxy = qobject_cast(proxyList[i]);
+ auto workspace = workspaceProxy->proxyObject();
+
+ int oldIndex = this->indexOf(workspace);
+
+ if (oldIndex < 0) {
+ workspace->assign(this, QVariant(i));
+ } else if (oldIndex != i) {
+ this->move(oldIndex, i);
+ }
+ newWorkspaces.insert(workspace);
+ }
+
+ // Make sure we have at least one workspace in the model.
+ if (rowCount() == 0) {
+ Workspace* workspace = WorkspaceManager::instance()->createWorkspace();
+ workspace->assign(this);
+ (new ProxyWorkspace(workspace))->assign(proxy);
+ }
+
+ if (removedIndexWhichWasActive != -1) {
+ int newActiveIndex = qMin(removedIndexWhichWasActive, this->rowCount()-1);
+ Workspace* newActiveWorkspace = newActiveIndex >= 0 ? this->get(newActiveIndex) : nullptr;
+
+ WorkspaceManager::instance()->setActiveWorkspace(newActiveWorkspace);
+ }
+
+ proxy->finishSync();
+ finishSync();
+}
+
+void WorkspaceModel::finishSync()
+{
+ QSet dpCpy(m_unassignedWorkspaces);
+ Q_FOREACH(auto workspace, dpCpy) {
+ delete workspace;
+ }
+ m_unassignedWorkspaces.clear();
+}
+
+void WorkspaceModel::insertUnassigned(Workspace *workspace)
+{
+ m_unassignedWorkspaces.insert(workspace);
+ connect(workspace, &Workspace::assigned, this, [=]() {
+ m_unassignedWorkspaces.remove(workspace);
+ disconnect(workspace, &Workspace::assigned, this, 0);
+ });
+ connect(workspace, &QObject::destroyed, this, [=]() {
+ m_unassignedWorkspaces.remove(workspace);
+ });
+}
+
+
+ProxyWorkspaceModel::ProxyWorkspaceModel(WorkspaceModel * const model, ProxyScreen* screen)
+ : m_original(model)
+ , m_screen(screen)
+{
+ Q_FOREACH(auto workspace, model->list()) {
+ auto proxy = new ProxyWorkspace(workspace);
+ QQmlEngine::setObjectOwnership(proxy, QQmlEngine::CppOwnership);
+ proxy->assign(this);
+ }
+ connect(m_original, &WorkspaceModel::workspaceInserted, this, [this](int index, Workspace* inserted) {
+ if (isSyncing()) return;
+
+ (new ProxyWorkspace(inserted))->assign(this, index);
+ });
+ connect(m_original, &WorkspaceModel::workspaceRemoved, this, [this](Workspace* removed) {
+ if (isSyncing()) return;
+
+ for (int i = 0; i < rowCount(); i++) {
+ auto workspaceProxy = qobject_cast(get(i));
+ auto w = workspaceProxy->proxyObject();
+ if (w == removed) {
+ remove(workspaceProxy);
+ break;
+ }
+ }
+ });
+ connect(m_original, &WorkspaceModel::workspaceMoved, this, [this](int from, int to) {
+ if (isSyncing()) return;
+
+ move(from, to);
+ });
+}
+
+void ProxyWorkspaceModel::move(int from, int to)
+{
+ WorkspaceModel::move(from, to);
+}
+
+bool ProxyWorkspaceModel::isSyncing() const
+{
+ return m_screen->isSyncing();
+}
+
+void ProxyWorkspaceModel::addWorkspace()
+{
+ auto newWorkspace = WorkspaceManager::instance()->createWorkspace();
+ m_original->insertUnassigned(newWorkspace);
+
+ (new ProxyWorkspace(newWorkspace))->assign(this);
+}
diff --git a/plugins/WindowManager/WorkspaceModel.h b/plugins/WindowManager/WorkspaceModel.h
new file mode 100644
index 0000000000..0f64090348
--- /dev/null
+++ b/plugins/WindowManager/WorkspaceModel.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef WORKSPACEMODEL_H
+#define WORKSPACEMODEL_H
+
+#include
+#include
+#include
+
+Q_DECLARE_LOGGING_CATEGORY(WORKSPACES)
+
+class Workspace;
+class ProxyWorkspaceModel;
+class ProxyScreen;
+
+class WorkspaceModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
+public:
+ /**
+ * @brief The Roles supported by the model
+ *
+ * WorkspaceRole - A workspace.
+ */
+ enum Roles {
+ WorkspaceRole = Qt::UserRole
+ };
+
+ explicit WorkspaceModel(QObject *parent = 0);
+ ~WorkspaceModel();
+
+ void append(Workspace *workspace);
+ void insert(int index, Workspace *workspace);
+ void remove(Workspace* workspace);
+ virtual void move(int from, int to);
+
+ Q_INVOKABLE int indexOf(Workspace *workspace) const;
+ Q_INVOKABLE Workspace* get(int index) const;
+
+ // From QAbstractItemModel
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role) const override;
+ QHash roleNames() const override {
+ QHash roleNames { {WorkspaceRole, "workspace"} };
+ return roleNames;
+ }
+
+ const QVector& list() const { return m_workspaces; }
+
+ void sync(WorkspaceModel* proxy);
+ void finishSync();
+
+Q_SIGNALS:
+ void countChanged();
+
+ void workspaceInserted(int index, Workspace *workspace);
+ void workspaceRemoved(Workspace *workspace);
+ void workspaceMoved(int from, int to);
+
+protected:
+ void insertUnassigned(Workspace* workspace);
+
+ QVector m_workspaces;
+ QSet m_unassignedWorkspaces;
+
+ friend class ProxyWorkspaceModel;
+};
+
+class ProxyWorkspaceModel : public WorkspaceModel
+{
+ Q_OBJECT
+public:
+ explicit ProxyWorkspaceModel(WorkspaceModel*const model, ProxyScreen* screen);
+
+ Q_INVOKABLE void move(int from, int to) override;
+
+ bool isSyncing() const;
+
+public Q_SLOTS:
+ void addWorkspace();
+
+protected:
+ const QPointer m_original;
+ const ProxyScreen* m_screen;
+};
+
+#endif // WORKSPACEMODEL_H
diff --git a/qml/ApplicationMenus/MenuBar.qml b/qml/ApplicationMenus/MenuBar.qml
index c074e21a4a..6a1b051556 100644
--- a/qml/ApplicationMenus/MenuBar.qml
+++ b/qml/ApplicationMenus/MenuBar.qml
@@ -19,6 +19,7 @@ import QtQuick.Layouts 1.1
import Utils 0.1
import Ubuntu.Components 1.3
import GlobalShortcut 1.0
+import "../Components/PanelState"
Item {
id: root
@@ -29,6 +30,7 @@ Item {
property bool enableKeyFilter: false
property real overflowWidth: width
property bool windowMoving: false
+ property PanelState panelState
// read from outside
readonly property bool valid: rowRepeater.count > 0
@@ -106,7 +108,9 @@ Item {
Component {
id: menuComponent
- MenuPopup { }
+ MenuPopup {
+ panelState: root.panelState
+ }
}
Repeater {
diff --git a/qml/ApplicationMenus/MenuPopup.qml b/qml/ApplicationMenus/MenuPopup.qml
index 2726496156..7b0950bacb 100644
--- a/qml/ApplicationMenus/MenuPopup.qml
+++ b/qml/ApplicationMenus/MenuPopup.qml
@@ -64,6 +64,7 @@ UbuntuShape {
}
property alias unityMenuModel: repeater.model
+ property PanelState panelState
function show() {
visible = true;
@@ -103,7 +104,7 @@ UbuntuShape {
property real __minimumWidth: units.gu(20)
property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
property real __minimumHeight: units.gu(2)
- property real __maximumHeight: ApplicationMenusLimits.screenHeight - PanelState.panelHeight
+ property real __maximumHeight: ApplicationMenusLimits.screenHeight - panelState.panelHeight
signal dismissAll()
@@ -492,6 +493,7 @@ UbuntuShape {
onLoaded: {
item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
+ item.panelState = Qt.binding(function() { return root.panelState; });
item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
diff --git a/qml/CMakeLists.txt b/qml/CMakeLists.txt
index 60e6a643ef..422357213a 100644
--- a/qml/CMakeLists.txt
+++ b/qml/CMakeLists.txt
@@ -1,4 +1,4 @@
-file(GLOB QML_JS_FILES *.qml *.js)
+file(GLOB QML_JS_FILES *.qml *.js qmldir)
install(FILES ${QML_JS_FILES}
DESTINATION ${SHELL_APP_DIR}
diff --git a/qml/Components/PanelState/PanelState.qml b/qml/Components/PanelState/PanelState.qml
index 05fbce21f5..e5345e4f1f 100644
--- a/qml/Components/PanelState/PanelState.qml
+++ b/qml/Components/PanelState/PanelState.qml
@@ -14,7 +14,6 @@
* along with this program. If not, see .
*/
-pragma Singleton
import QtQuick 2.4
QtObject {
diff --git a/qml/Components/PanelState/qmldir b/qml/Components/PanelState/qmldir
index 7de88a53b1..05b81ddef7 100644
--- a/qml/Components/PanelState/qmldir
+++ b/qml/Components/PanelState/qmldir
@@ -1 +1 @@
-singleton PanelState 1.0 PanelState.qml
+PanelState 1.0 PanelState.qml
diff --git a/qml/Components/VirtualTouchPad.qml b/qml/Components/VirtualTouchPad.qml
index a95865a22c..f16f8e5544 100644
--- a/qml/Components/VirtualTouchPad.qml
+++ b/qml/Components/VirtualTouchPad.qml
@@ -18,22 +18,21 @@ import QtQuick 2.4
import QtQuick.Layouts 1.1
import Ubuntu.Components 1.3
import Qt.labs.settings 1.0
-import Unity.Screens 0.1
import UInput 0.1
import "../Components"
Item {
id: root
- property var uinput: UInput {
- Component.onCompleted: createMouse();
- Component.onDestruction: removeMouse();
- }
+
+ property bool oskEnabled: false
Component.onCompleted: {
+ UInput.createMouse();
if (!settings.touchpadTutorialHasRun) {
root.runTutorial()
}
}
+ Component.onDestruction: UInput.removeMouse()
function runTutorial() {
// If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +84,7 @@ Item {
if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
&& clickTimer.running) {
clickTimer.stop();
- uinput.pressMouse(UInput.ButtonLeft)
+ UInput.pressMouse(UInput.ButtonLeft)
isDoubleClick = true;
}
isClick = true;
@@ -104,7 +103,7 @@ Item {
onReleased: {
if (isDoubleClick || isDrag) {
- uinput.releaseMouse(UInput.ButtonLeft)
+ UInput.releaseMouse(UInput.ButtonLeft)
isDoubleClick = false;
}
if (isClick) {
@@ -120,8 +119,8 @@ Item {
interval: 200
property int button: UInput.ButtonLeft
onTriggered: {
- uinput.pressMouse(button);
- uinput.releaseMouse(button);
+ UInput.pressMouse(button);
+ UInput.releaseMouse(button);
}
function scheduleClick(button) {
clickTimer.button = button;
@@ -138,7 +137,7 @@ Item {
isDrag = true;
}
- uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
}
function scroll(touchPoints) {
@@ -166,7 +165,7 @@ Item {
dh /= 2;
dv /= 2;
- uinput.scrollMouse(dh, dv);
+ UInput.scrollMouse(dh, dv);
}
touchPoints: [
@@ -189,8 +188,8 @@ Item {
objectName: "leftButton"
Layout.fillWidth: true
Layout.fillHeight: true
- onPressed: uinput.pressMouse(UInput.ButtonLeft);
- onReleased: uinput.releaseMouse(UInput.ButtonLeft);
+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
property bool highlight: false
UbuntuShape {
anchors.fill: parent
@@ -204,8 +203,8 @@ Item {
objectName: "rightButton"
Layout.fillWidth: true
Layout.fillHeight: true
- onPressed: uinput.pressMouse(UInput.ButtonRight);
- onReleased: uinput.releaseMouse(UInput.ButtonRight);
+ onPressed: UInput.pressMouse(UInput.ButtonRight);
+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
property bool highlight: false
UbuntuShape {
anchors.fill: parent
@@ -239,14 +238,10 @@ Item {
}
}
- Screens {
- id: screens
- }
-
InputMethod {
id: inputMethod
// Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
- enabled: screens.count > 1 && settings.oskEnabled && !tutorial.running
+ enabled: root.oskEnabled && settings.oskEnabled && !tutorial.running
objectName: "inputMethod"
anchors.fill: parent
}
diff --git a/qml/DisabledScreenNotice.qml b/qml/DisabledScreenNotice.qml
index a6ff50551c..752d63f246 100644
--- a/qml/DisabledScreenNotice.qml
+++ b/qml/DisabledScreenNotice.qml
@@ -28,8 +28,11 @@ Item {
property var screen: Screen
property var orientationLock: OrientationLock
+ property bool oskEnabled: false
+
+ property alias deviceConfiguration: _deviceConfiguration
DeviceConfiguration {
- id: deviceConfiguration
+ id: _deviceConfiguration
name: applicationArguments.deviceName
}
@@ -76,6 +79,7 @@ Item {
VirtualTouchPad {
objectName: "virtualTouchPad"
anchors.fill: parent
+ oskEnabled: root.oskEnabled
}
}
}
diff --git a/qml/ErrorApplication.qml b/qml/ErrorApplication.qml
new file mode 100644
index 0000000000..4ea93bbab7
--- /dev/null
+++ b/qml/ErrorApplication.qml
@@ -0,0 +1,67 @@
+/*
+* Copyright (C) 2017 Canonical, Ltd.
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; version 3.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+import QtQuick 2.4
+import QtQuick.Window 2.2
+import Ubuntu.Components 1.3
+import WindowManager 1.0
+
+Instantiator {
+ id: root
+ model: Screens
+
+ ScreenWindow {
+ id: window
+ objectName: "screen"+index
+ screen: model.screen
+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
+
+ Binding {
+ when: applicationArguments.hasGeometry
+ target: window
+ property: "width"
+ value: applicationArguments.windowGeometry.width
+ }
+ Binding {
+ when: applicationArguments.hasGeometry
+ target: window
+ property: "height"
+ value: applicationArguments.windowGeometry.height
+ }
+
+ Loader {
+ width: window.width
+ height: window.height
+
+ Rectangle {
+ color: "white"
+ Column {
+ spacing: units.gu(1)
+
+ Label {
+ text: "Unity encountered an unrecoverable error while loading:"
+ fontSize: "large"
+ }
+
+ Label {
+ text: errorString
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/qml/Greeter/Greeter.qml b/qml/Greeter/Greeter.qml
index 3eb44f387a..4ea66cd578 100644
--- a/qml/Greeter/Greeter.qml
+++ b/qml/Greeter/Greeter.qml
@@ -24,6 +24,7 @@ import Unity.Launcher 0.1
import Unity.Session 0.1
import "." 0.1
+import ".." 0.1
import "../Components"
Showable {
@@ -225,7 +226,7 @@ Showable {
if (forcedUnlock && shown) {
hideView();
if (hideNow) {
- root.hideNow(); // skip hide animation
+ ShellNotifier.greeter.hide(true); // skip hide animation
}
}
}
@@ -373,7 +374,7 @@ Showable {
onEmergencyCall: root.emergencyCall()
onRequiredChanged: {
if (!loader.item.required) {
- root.hide();
+ ShellNotifier.greeter.hide(false);
}
}
}
@@ -487,12 +488,29 @@ Showable {
onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
}
+ Connections {
+ target: ShellNotifier.greeter
+ onHide: {
+ if (now) {
+ root.hideNow(); // skip hide animation
+ } else {
+ root.hide();
+ }
+ }
+ }
+
+ Binding {
+ target: ShellNotifier.greeter
+ property: "shown"
+ value: root.shown
+ }
+
Connections {
target: DBusUnitySessionService
onLockRequested: root.forceShow()
onUnlocked: {
root.forcedUnlock = true;
- root.hideNow();
+ ShellNotifier.greeter.hide(true);
}
}
diff --git a/qml/Greeter/SecondaryGreeter.qml b/qml/Greeter/SecondaryGreeter.qml
new file mode 100644
index 0000000000..1b7557f572
--- /dev/null
+++ b/qml/Greeter/SecondaryGreeter.qml
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+import "../Components"
+import ".." 0.1
+
+Showable {
+ id: root
+
+ readonly property bool active: required || hasLockedApp
+
+ readonly property bool hasLockedApp: lockedApp !== ""
+ readonly property bool locked: false
+ readonly property bool waiting: false
+ readonly property bool fullyShown: shown
+
+ property string lockedApp: ""
+
+ function forceShow() { show(); }
+ property var notifyAppFocusRequested: (function(appId) { return; })
+ property var notifyUserRequestedApp: (function(appId) { return; })
+ property var notifyShowingDashFromDrag: (function(appId) { return false; })
+
+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
+
+ shown: ShellNotifier.greeter.shown
+ Component.onCompleted: opacity = shown ? 1 : 0
+ visible: opacity != 0
+
+ Rectangle {
+ anchors.fill: parent
+ color: UbuntuColors.purple
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+ onWheel: wheel.accepted = true
+ }
+
+ Connections {
+ target: ShellNotifier.greeter
+ onHide: {
+ if (now) {
+ root.hideNow(); // skip hide animation
+ } else {
+ root.hide();
+ }
+ }
+ onShownChanged: {
+ if (ShellNotifier.greeter.shown) {
+ root.show();
+ } else {
+ root.hide();
+ }
+ }
+ }
+}
diff --git a/qml/Launcher/Launcher.qml b/qml/Launcher/Launcher.qml
index d81d35411c..33f437b674 100644
--- a/qml/Launcher/Launcher.qml
+++ b/qml/Launcher/Launcher.qml
@@ -104,6 +104,13 @@ FocusScope {
}
onLockedVisibleChanged: {
+ // We are in the progress of moving to the drawer
+ // this is caused by the user pressing the bfb on unlock
+ // in this case we want to show the drawer and not
+ // just visible
+ if (animateTimer.nextState == "drawer")
+ return;
+
if (lockedVisible && state == "") {
panel.dismissTimer.stop();
fadeOutAnimation.stop();
diff --git a/qml/OrientedShell.qml b/qml/OrientedShell.qml
index 544de66f84..9144865bb7 100644
--- a/qml/OrientedShell.qml
+++ b/qml/OrientedShell.qml
@@ -18,7 +18,7 @@ import QtQuick 2.4
import QtQuick.Window 2.2
import Unity.InputInfo 0.1
import Unity.Session 0.1
-import Unity.Screens 0.1
+import WindowManager 1.0
import Utils 0.1
import GSettings 1.0
import "Components"
@@ -32,16 +32,17 @@ Item {
implicitWidth: units.gu(40)
implicitHeight: units.gu(71)
+ property alias deviceConfiguration: _deviceConfiguration
+ property alias orientations: d.orientations
+ property bool lightIndicators: false
+
onWidthChanged: calculateUsageMode();
DeviceConfiguration {
- id: deviceConfiguration
+ id: _deviceConfiguration
name: applicationArguments.deviceName
}
- property alias orientations: d.orientations
- property bool lightIndicators: false
-
Item {
id: d
@@ -158,10 +159,6 @@ Item {
return false;
}
- Screens {
- id: screens
- }
-
property int orientation
onPhysicalOrientationChanged: {
if (!orientationLocked) {
@@ -283,7 +280,7 @@ Item {
// hardcode screen count to only show osk on this screen
// when it's the only one connected.
// FIXME once multiscreen has landed
- oskEnabled: (!hasKeyboard && screens.count === 1) ||
+ oskEnabled: (!hasKeyboard && Screens.count === 1) ||
unity8Settings.alwaysShowOsk || forceOSKEnabled
usageScenario: {
diff --git a/qml/Panel/Panel.qml b/qml/Panel/Panel.qml
index 399d9173b6..e77d661a5b 100644
--- a/qml/Panel/Panel.qml
+++ b/qml/Panel/Panel.qml
@@ -53,6 +53,7 @@ Item {
property bool partialWidth: width >= units.gu(60)
property string mode: "staged"
+ property PanelState panelState
MouseArea {
id: backMouseEater
@@ -69,14 +70,14 @@ Item {
}
Binding {
- target: PanelState
+ target: panelState
property: "panelHeight"
value: minimizedPanelHeight
}
RegisteredApplicationMenuModel {
id: registeredMenuModel
- persistentSurfaceId: PanelState.focusedPersistentSurfaceId
+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
}
QtObject {
@@ -87,11 +88,11 @@ Item {
!indicators.shown &&
(decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
- property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
- PanelState.decorationsAlwaysVisible
+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
+ panelState.decorationsAlwaysVisible
- property bool showPointerMenu: revealControls && enablePointerMenu &&
- (PanelState.decorationsVisible || mode == "windowed")
+ property bool showPointerMenu: revealControls &&
+ (panelState.decorationsVisible || mode == "windowed")
property bool enablePointerMenu: applicationMenus.available &&
applicationMenus.model
@@ -143,7 +144,7 @@ Item {
fill: panelAreaBackground
bottomMargin: -units.gu(1)
}
- visible: PanelState.dropShadow
+ visible: panelState.dropShadow
source: "graphics/rectangular_dropshadow.sci"
}
@@ -200,12 +201,12 @@ Item {
visible: opacity != 0
Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
- active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
windowIsMaximized: true
- onCloseClicked: PanelState.closeClicked()
- onMinimizeClicked: PanelState.minimizeClicked()
- onMaximizeClicked: PanelState.restoreClicked()
- closeButtonShown: PanelState.closeButtonShown
+ onCloseClicked: panelState.closeClicked()
+ onMinimizeClicked: panelState.minimizeClicked()
+ onMaximizeClicked: panelState.restoreClicked()
+ closeButtonShown: panelState.closeButtonShown
}
Loader {
@@ -225,11 +226,12 @@ Item {
sourceComponent: MenuBar {
id: bar
objectName: "menuBar"
- anchors.left: parent ? parent.left : undefined
+ anchors.left: menuBarLoader ? menuBarLoader.left : undefined
anchors.margins: units.gu(1)
height: menuBarLoader.height
- enableKeyFilter: valid && PanelState.decorationsVisible
+ enableKeyFilter: valid && panelState.decorationsVisible
unityMenuModel: __applicationMenus.model
+ panelState: root.panelState
Connections {
target: __applicationMenus
@@ -241,7 +243,7 @@ Item {
onShownChanged: bar.dismiss();
}
- onDoubleClicked: PanelState.restoreClicked()
+ onDoubleClicked: panelState.restoreClicked()
onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
}
}
@@ -354,7 +356,7 @@ Item {
fontSize: "medium"
font.weight: Font.Medium
color: theme.palette.selected.backgroundText
- text: (root.partialWidth && !callHint.visible) ? PanelState.title : ""
+ text: (root.partialWidth && !callHint.visible) ? panelState.title : ""
opacity: __applicationMenus.visible && !__applicationMenus.expanded
Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
visible: opacity !== 0
diff --git a/qml/Shell.qml b/qml/Shell.qml
index ebc7cf0205..81b98c4bae 100644
--- a/qml/Shell.qml
+++ b/qml/Shell.qml
@@ -38,6 +38,7 @@ import "Notifications"
import "Stage"
import "Tutorial"
import "Wizard"
+import "Components/PanelState"
import Unity.Notifications 1.0 as NotificationBackend
import Unity.Session 0.1
import Unity.Indicators 0.1 as Indicators
@@ -99,6 +100,11 @@ StyledItem {
readonly property var mainApp: stage.mainApp
+ readonly property var topLevelSurfaceList: {
+ if (!WMScreen.currentWorkspace) return null;
+ return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
+ }
+
onMainAppChanged: {
_onMainAppChanged((mainApp ? mainApp.appId : ""));
}
@@ -201,11 +207,11 @@ StyledItem {
}
function startApp(appId) {
- if (ApplicationManager.findApplication(appId)) {
- ApplicationManager.requestFocusApplication(appId);
- } else {
+ if (!ApplicationManager.findApplication(appId)) {
ApplicationManager.startApplication(appId);
}
+ ApplicationManager.requestFocusApplication(appId);
+ stage.closeSpread();
}
function startLockedApp(app) {
@@ -283,23 +289,17 @@ StyledItem {
schema.id: "com.canonical.Unity8"
}
+ PanelState {
+ id: panelState
+ objectName: "panelState"
+ }
+
Item {
id: stages
objectName: "stages"
width: parent.width
height: parent.height
- SurfaceManager {
- id: surfaceMan
- objectName: "surfaceManager"
- }
- TopLevelWindowModel {
- id: topLevelSurfaceList
- objectName: "topLevelSurfaceList"
- applicationManager: ApplicationManager // it's a singleton
- surfaceManager: surfaceMan
- }
-
Stage {
id: stage
objectName: "stage"
@@ -310,7 +310,7 @@ StyledItem {
background: wallpaperResolver.background
applicationManager: ApplicationManager
- topLevelSurfaceList: topLevelSurfaceList
+ topLevelSurfaceList: shell.topLevelSurfaceList
inputMethodRect: inputMethod.visibleRect
rightEdgePushProgress: rightEdgeBarrier.progress
availableDesktopArea: availableDesktopAreaItem
@@ -339,6 +339,7 @@ StyledItem {
altTabPressed: physicalKeysMapper.altTabPressed
oskEnabled: shell.oskEnabled
spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
+ panelState: panelState
onSpreadShownChanged: {
panel.indicators.hide();
@@ -392,8 +393,13 @@ StyledItem {
objectName: "greeterLoader"
anchors.fill: parent
anchors.topMargin: panel.panelHeight
- sourceComponent: shell.mode != "shell" ? integratedGreeter :
- Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
+ sourceComponent: {
+ if (shell.mode != "shell") {
+ if (screenWindow.primary) return integratedGreeter;
+ return secondaryGreeter;
+ }
+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
+ }
onLoaded: {
item.objectName = "greeter"
}
@@ -445,6 +451,13 @@ StyledItem {
}
}
+ Component {
+ id: secondaryGreeter
+ SecondaryGreeter {
+ hides: [launcher, panel.indicators]
+ }
+ }
+
Timer {
// See powerConnection for why this is useful
id: showGreeterDelayed
@@ -556,13 +569,14 @@ StyledItem {
&& !stage.spreadShown
}
- readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
- ? topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
+ readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
+ ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
: false
fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
|| greeter.hasLockedApp
greeterShown: greeter && greeter.shown
hasKeyboard: shell.hasKeyboard
+ panelState: panelState
supportsMultiColorLed: shell.supportsMultiColorLed
}
@@ -822,11 +836,11 @@ StyledItem {
Cursor {
id: cursor
objectName: "cursor"
- visible: shell.hasMouse
+
z: itemGrabber.z + 1
topBoundaryOffset: panel.panelHeight
-
- confiningItem: stage.itemConfiningMouseCursor
+ enabled: shell.hasMouse && screenWindow.active
+ visible: enabled
property bool mouseNeverMoved: true
Binding {
@@ -838,6 +852,8 @@ StyledItem {
when: cursor.mouseNeverMoved && cursor.visible
}
+ confiningItem: stage.itemConfiningMouseCursor
+
height: units.gu(3)
readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -901,7 +917,7 @@ StyledItem {
// non-visual objects
KeymapSwitcher {
- focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
+ focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
}
BrightnessControl {}
diff --git a/qml/ShellApplication.qml b/qml/ShellApplication.qml
new file mode 100644
index 0000000000..557d6bb025
--- /dev/null
+++ b/qml/ShellApplication.qml
@@ -0,0 +1,65 @@
+/*
+* Copyright (C) 2016 Canonical, Ltd.
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; version 3.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+import QtQuick 2.4
+import QtQuick.Window 2.2
+import WindowManager 1.0
+import Unity.Application 0.1
+
+Instantiator {
+ id: root
+ model: Screens
+
+ ShellScreen {
+ id: window
+ objectName: "screen"+index
+ screen: model.screen
+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
+
+ Binding {
+ when: applicationArguments.hasGeometry
+ target: window
+ property: "width"
+ value: applicationArguments.windowGeometry.width
+ }
+ Binding {
+ when: applicationArguments.hasGeometry
+ target: window
+ property: "height"
+ value: applicationArguments.windowGeometry.height
+ }
+
+ Component.onCompleted: screen.active = primary
+ primary: index == 0
+ }
+
+ property var windowManagerSurfaceManagerBinding: Binding {
+ target: WindowManagerObjects
+ property: "surfaceManager"
+ value: SurfaceManager
+ }
+ property var windowManagerApplicationManagerBinding: Binding {
+ target: WindowManagerObjects
+ property: "applicationManager"
+ value: ApplicationManager
+ }
+
+ Component.onDestruction: {
+ WindowManagerObjects.surfaceManager = null;
+ WindowManagerObjects.applicationManager = null;
+ }
+}
diff --git a/qml/ShellNotifier.qml b/qml/ShellNotifier.qml
new file mode 100644
index 0000000000..f339393ee3
--- /dev/null
+++ b/qml/ShellNotifier.qml
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/*
+ Shared signals & properties on multi-window desktop
+ */
+
+pragma Singleton
+import QtQuick 2.4
+
+QtObject {
+ property var greeter: QtObject {
+ signal hide(bool now)
+
+ property bool shown: true
+ }
+}
diff --git a/qml/ShellScreen.qml b/qml/ShellScreen.qml
new file mode 100644
index 0000000000..838ff54edb
--- /dev/null
+++ b/qml/ShellScreen.qml
@@ -0,0 +1,66 @@
+/*
+* Copyright (C) 2016 Canonical, Ltd.
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; version 3.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import WindowManager 1.0
+import Cursor 1.1
+import "Components"
+
+ScreenWindow {
+ id: screenWindow
+
+ color: "black"
+ title: "Unity8 Shell"
+ property bool primary: false
+
+ DeviceConfiguration {
+ id: deviceConfiguration
+ name: applicationArguments.deviceName
+ }
+
+ Loader {
+ id: loader
+ width: screenWindow.width
+ height: screenWindow.height
+
+ sourceComponent: {
+ if (Screens.count > 1 && primary && deviceConfiguration.category !== "desktop") {
+ return disabledScreenComponent;
+ }
+ return shellComponent;
+ }
+ }
+
+ Component {
+ id: shellComponent
+ OrientedShell {
+ implicitWidth: screenWindow.width
+ implicitHeight: screenWindow.height
+
+ deviceConfiguration {
+ name: Screens.count > 1 ? "desktop" : applicationArguments.deviceName
+ }
+ }
+ }
+
+ Component {
+ id: disabledScreenComponent
+ DisabledScreenNotice {
+ oskEnabled: Screens.count > 1
+ }
+ }
+}
diff --git a/qml/Stage/ApplicationWindow.qml b/qml/Stage/ApplicationWindow.qml
index 479f323b9a..060b126c41 100644
--- a/qml/Stage/ApplicationWindow.qml
+++ b/qml/Stage/ApplicationWindow.qml
@@ -52,13 +52,8 @@ FocusScope {
// other instructions.
if (surface) {
surfaceContainer.surface = surface;
- d.liveSurface = surface.live;
- d.hadSurface = false;
surfaceInitTimer.start();
} else {
- if (d.surfaceInitialized) {
- d.hadSurface = true;
- }
d.surfaceInitialized = false;
surfaceContainer.surface = null;
}
@@ -67,21 +62,6 @@ FocusScope {
QtObject {
id: d
- property bool liveSurface: false;
- property var con: Connections {
- target: root.surface
- onLiveChanged: d.liveSurface = root.surface.live
- }
- // using liveSurface instead of root.surface.live because with the latter
- // this expression is not reevaluated when root.surface changes
- readonly property bool needToTakeScreenshot: root.surface && d.surfaceInitialized && !d.liveSurface
- && applicationState !== ApplicationInfoInterface.Running
- onNeedToTakeScreenshotChanged: {
- if (needToTakeScreenshot && screenshotImage.status === Image.Null) {
- screenshotImage.take();
- }
- }
-
// helpers so that we don't have to check for the existence of an application everywhere
// (in order to avoid breaking qml binding due to a javascript exception)
readonly property string name: root.application ? root.application.name : ""
@@ -131,34 +111,11 @@ FocusScope {
id: surfaceInitTimer
interval: 100
onTriggered: {
- if (root.surface && root.surface.live) {d.surfaceInitialized = true;}
- }
- }
-
- Timer {
- id: surfaceIsOldTimer
- interval: 1000
- onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }
- }
-
- Image {
- id: screenshotImage
- objectName: "screenshotImage"
- anchors.fill: parent
- fillMode: Image.PreserveAspectCrop
- horizontalAlignment: Image.AlignLeft
- verticalAlignment: Image.AlignTop
- antialiasing: !root.interactive
- z: 1
-
- function take() {
- // Save memory by using a half-resolution (thus quarter size) screenshot.
- // Do not make this a binding, we can only take the screenshot once!
- surfaceContainer.grabToImage(
- function(result) {
- screenshotImage.source = result.url;
- },
- Qt.size(root.width / 2, root.height / 2));
+ if (root.surface && root.surface.live) {
+ d.surfaceInitialized = true;
+ d.hadSurface = true;
+ d.surfaceOldEnoughToBeResized = true;
+ }
}
}
@@ -167,7 +124,7 @@ FocusScope {
visible: active
active: false
anchors.fill: parent
- z: screenshotImage.z + 1
+ z: 1
sourceComponent: Component {
Splash {
id: splash
@@ -240,167 +197,14 @@ FocusScope {
id: stateGroup
objectName: "applicationWindowStateGroup"
states: [
- State {
- name: "void"
- when:
- d.hadSurface && (!root.surface || !d.surfaceInitialized)
- &&
- screenshotImage.status !== Image.Ready
- },
- State {
- name: "splashScreen"
- when:
- !d.hadSurface && (!root.surface || !d.surfaceInitialized)
- &&
- screenshotImage.status !== Image.Ready
- },
- State {
+ State{
name: "surface"
- when:
- (root.surface && d.surfaceInitialized)
- &&
- (d.liveSurface ||
- (d.applicationState !== ApplicationInfoInterface.Running
- && screenshotImage.status !== Image.Ready))
- PropertyChanges {
- target: root
- implicitWidth: surfaceContainer.implicitWidth
- implicitHeight: surfaceContainer.implicitHeight
- }
- },
- State {
- name: "screenshot"
- when:
- screenshotImage.status === Image.Ready
- &&
- (d.applicationState !== ApplicationInfoInterface.Running
- || !root.surface || !d.surfaceInitialized)
+ when: (root.surface && d.surfaceInitialized) || d.hadSurface
},
State {
- // This is a dead end. From here we expect the surface to be removed from the model
- // shortly after we stop referencing to it in our SurfaceContainer.
- name: "closed"
- when:
- // The surface died while the application is running. It must have been closed
- // by the shell or the application decided to destroy it by itself
- root.surface && d.surfaceInitialized && !d.liveSurface
- && d.applicationState === ApplicationInfoInterface.Running
- }
- ]
-
- transitions: [
- Transition {
- from: ""; to: "splashScreen"
- PropertyAction { target: splashLoader; property: "active"; value: true }
- PropertyAction { target: surfaceContainer
- property: "visible"; value: false }
- },
- Transition {
- from: "splashScreen"; to: "surface"
- SequentialAnimation {
- PropertyAction { target: surfaceContainer
- property: "opacity"; value: 0.0 }
- PropertyAction { target: surfaceContainer
- property: "visible"; value: true }
- UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
- from: 0.0; to: 1.0
- duration: UbuntuAnimation.BriskDuration }
- ScriptAction { script: {
- splashLoader.active = false;
- surfaceIsOldTimer.start();
- } }
- }
- },
- Transition {
- from: "surface"; to: "splashScreen"
- SequentialAnimation {
- ScriptAction { script: {
- surfaceIsOldTimer.stop();
- d.surfaceOldEnoughToBeResized = false;
- splashLoader.active = true;
- surfaceContainer.visible = true;
- } }
- UbuntuNumberAnimation { target: splashLoader; property: "opacity";
- from: 0.0; to: 1.0
- duration: UbuntuAnimation.BriskDuration }
- PropertyAction { target: surfaceContainer
- property: "visible"; value: false }
- }
- },
- Transition {
- from: "surface"; to: "screenshot"
- SequentialAnimation {
- ScriptAction { script: {
- surfaceIsOldTimer.stop();
- d.surfaceOldEnoughToBeResized = false;
- screenshotImage.visible = true;
- } }
- UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
- from: 0.0; to: 1.0
- duration: UbuntuAnimation.BriskDuration }
- ScriptAction { script: {
- surfaceContainer.visible = false;
- surfaceContainer.surface = null;
- d.hadSurface = true;
- } }
- }
- },
- Transition {
- from: "screenshot"; to: "surface"
- SequentialAnimation {
- PropertyAction { target: surfaceContainer
- property: "visible"; value: true }
- UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
- from: 1.0; to: 0.0
- duration: UbuntuAnimation.BriskDuration }
- ScriptAction { script: {
- screenshotImage.visible = false;
- screenshotImage.source = "";
- surfaceIsOldTimer.start();
- } }
- }
- },
- Transition {
- from: "splashScreen"; to: "screenshot"
- SequentialAnimation {
- PropertyAction { target: screenshotImage
- property: "visible"; value: true }
- UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
- from: 0.0; to: 1.0
- duration: UbuntuAnimation.BriskDuration }
- PropertyAction { target: splashLoader; property: "active"; value: false }
- }
- },
- Transition {
- from: "surface"; to: "void"
- ScriptAction { script: {
- surfaceIsOldTimer.stop();
- d.surfaceOldEnoughToBeResized = false;
- surfaceContainer.visible = false;
- } }
- },
- Transition {
- from: "void"; to: "surface"
- SequentialAnimation {
- PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
- PropertyAction { target: surfaceContainer; property: "visible"; value: true }
- UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
- from: 0.0; to: 1.0
- duration: UbuntuAnimation.BriskDuration }
- ScriptAction { script: {
- surfaceIsOldTimer.start();
- } }
- }
- },
- Transition {
- to: "closed"
- SequentialAnimation {
- ScriptAction { script: {
- surfaceContainer.visible = false;
- surfaceContainer.surface = null;
- d.hadSurface = true;
- } }
- }
+ name: "splash"
+ when: !root.surface && !d.surfaceInitialized && !d.hadSurface
+ PropertyChanges { target: splashLoader; active: true }
}
]
}
diff --git a/qml/Stage/DecoratedWindow.qml b/qml/Stage/DecoratedWindow.qml
index fcdf9a82c2..d713af4e2e 100644
--- a/qml/Stage/DecoratedWindow.qml
+++ b/qml/Stage/DecoratedWindow.qml
@@ -39,6 +39,7 @@ FocusScope {
property alias interactive: applicationWindow.interactive
readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
property alias windowControlButtonsVisible: decoration.windowControlButtonsVisible
+ property PanelState panelState
// Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
property bool hasDecoration: true
@@ -126,15 +127,15 @@ FocusScope {
name: "preview"; when: root.scaleToPreviewProgress > 0
PropertyChanges {
target: root
- implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
- implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
+ implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
+ implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
}
PropertyChanges {
target: applicationWindow;
- requestedWidth: applicationWindow.oldRequestedWidth
- requestedHeight: applicationWindow.oldRequestedHeight
- width: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
- height: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
+// requestedWidth: applicationWindow.oldRequestedWidth
+// requestedHeight: applicationWindow.oldRequestedHeight
+ width: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
+ height: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
itemScale: root.implicitWidth / width
}
}
@@ -173,10 +174,10 @@ FocusScope {
height: implicitHeight
requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
- property int oldRequestedWidth: requestedWidth
- property int oldRequestedHeight: requestedHeight
- onRequestedWidthChanged: oldRequestedWidth = requestedWidth
- onRequestedHeightChanged: oldRequestedHeight = requestedHeight
+// property int oldRequestedWidth: requestedWidth
+// property int oldRequestedHeight: requestedHeight
+// onRequestedWidthChanged: oldRequestedWidth = requestedWidth
+// onRequestedHeightChanged: oldRequestedHeight = requestedHeight
focus: true
property real itemScale: 1
@@ -219,6 +220,7 @@ FocusScope {
title: applicationWindow.title
windowMoving: moveHandler.moving && !altDragHandler.dragging
+ panelState: root.panelState
opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
Behavior on opacity { UbuntuNumberAnimation { } }
@@ -242,7 +244,7 @@ FocusScope {
enableMenus: {
return active &&
surface &&
- (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
}
menu: sharedAppModel.model
diff --git a/qml/Stage/FakeMaximizeDelegate.qml b/qml/Stage/FakeMaximizeDelegate.qml
index 26095e321f..1e26140152 100644
--- a/qml/Stage/FakeMaximizeDelegate.qml
+++ b/qml/Stage/FakeMaximizeDelegate.qml
@@ -35,6 +35,7 @@ Rectangle {
property int leftMargin
property real appContainerWidth
property real appContainerHeight
+ property PanelState panelState
readonly property real hintThreshold: 0.1
@@ -178,7 +179,7 @@ Rectangle {
ParallelAnimation {
id: fakeMaximizeAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
}
@@ -186,39 +187,39 @@ Rectangle {
ParallelAnimation {
id: fakeMaximizeLeftAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
}
ParallelAnimation {
id: fakeMaximizeRightAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
}
ParallelAnimation {
id: fakeMaximizeTopLeftAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
}
ParallelAnimation {
id: fakeMaximizeTopRightAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
}
ParallelAnimation {
id: fakeMaximizeBottomLeftAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
}
@@ -226,7 +227,7 @@ Rectangle {
ParallelAnimation {
id: fakeMaximizeBottomRightAnimation
UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
- UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
}
diff --git a/qml/Stage/PromptSurfaceAnimations.qml b/qml/Stage/PromptSurfaceAnimations.qml
index dd933a7134..5b7d71684e 100644
--- a/qml/Stage/PromptSurfaceAnimations.qml
+++ b/qml/Stage/PromptSurfaceAnimations.qml
@@ -21,11 +21,12 @@ StateGroup {
id: root
property var container
property var surfaceItem
+ property var hadSurface
states: [
State {
name: "blank"
- when: !root.surfaceItem.surface
+ when: !root.surfaceItem.surface && !root.hadSurface
},
State {
name: "ready"
@@ -33,7 +34,7 @@ StateGroup {
},
State {
name: "zombie"
- when: root.surfaceItem.surface && !root.surfaceItem.live
+ when: root.hadSurface && !root.surfaceItem.live
}
]
transitions: [
diff --git a/qml/Stage/Spread/ScreensAndWorkspaces.qml b/qml/Stage/Spread/ScreensAndWorkspaces.qml
new file mode 100644
index 0000000000..a104662bd9
--- /dev/null
+++ b/qml/Stage/Spread/ScreensAndWorkspaces.qml
@@ -0,0 +1,261 @@
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.Popups 1.3
+import WindowManager 1.0
+import Unity.Application 0.1
+import ".."
+
+Item {
+ id: root
+
+ property string background
+
+ property var screensProxy: Screens.createProxy();
+
+ property QtObject activeWorkspace: null
+
+ signal closeSpread();
+
+ Row {
+ id: row
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: UbuntuAnimation.SlowDuration } }
+ spacing: units.gu(1)
+
+ property var selectedIndex: undefined
+
+ Repeater {
+ model: screensProxy
+
+ delegate: Item {
+ height: root.height - units.gu(6)
+ width: workspaces.width
+
+ Item {
+ id: header
+ anchors { left: parent.left; top: parent.top; right: parent.right }
+ height: units.gu(7)
+ z: 1
+
+ property bool isCurrent: {
+ // another screen is selected.
+ if (row.selectedIndex != undefined && row.selectedIndex != index) return false;
+
+ // this screen is active.
+ if (WMScreen.active && WMScreen.isSameAs(model.screen) && WMScreen.currentWorkspace.isSameAs(activeWorkspace)) return true;
+ if (model.screen.workspaces.indexOf(activeWorkspace) >= 0) return true;
+
+ // not active.
+ return false;
+ }
+
+ property bool isSelected: screenMA.containsMouse
+ onIsSelectedChanged: {
+ if (isSelected) {
+ row.selectedIndex = Qt.binding(function() { return index; });
+ } else if (row.selectedIndex === index) {
+ row.selectedIndex = undefined;
+ }
+ }
+
+ UbuntuShape {
+ anchors.fill: parent
+ backgroundColor: "white"
+ opacity: header.isCurrent || header.isSelected ? 1.0 : 0.5
+ }
+
+ DropArea {
+ anchors.fill: parent
+ keys: ["workspace"]
+
+ onEntered: {
+ workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
+ drag.source.inDropArea = true;
+ }
+
+ onExited: {
+ workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
+ drag.source.inDropArea = false;
+ }
+
+ onDropped: {
+ drag.source.inDropArea = false;
+ }
+ }
+
+ Column {
+ anchors.fill: parent
+ anchors.margins: units.gu(1)
+
+ Label {
+ text: model.screen.name
+ color: header.isCurrent || header.isSelected ? "black" : "white"
+ }
+
+ Label {
+ text: model.screen.outputTypeName
+ color: header.isCurrent || header.isSelected ? "black" : "white"
+ fontSize: "x-small"
+ }
+
+ Label {
+ text: screen.availableModes[screen.currentModeIndex].size.width + "x" + screen.availableModes[screen.currentModeIndex].size.height
+ color: header.isCurrent || header.isSelected ? "black" : "white"
+ fontSize: "x-small"
+ }
+ }
+
+ Icon {
+ anchors {
+ top: parent.top
+ right: parent.right
+ margins: units.gu(1)
+ }
+ width: units.gu(3)
+ height: width
+ source: "image://theme/select"
+ color: header.isCurrent || header.isSelected ? "black" : "white"
+ visible: model.screen.active
+ }
+
+ MouseArea {
+ id: screenMA
+ hoverEnabled: true
+ anchors.fill: parent
+
+ onClicked: {
+ var obj = screensMenuComponent.createObject(header)
+ obj.open(mouseX, mouseY)
+ }
+ }
+
+ Component {
+ id: screensMenuComponent
+ UbuntuShape {
+ id: screensMenu
+ width: units.gu(20)
+ height: contentColumn.childrenRect.height
+ backgroundColor: "white"
+
+ function open(mouseX, mouseY) {
+ x = Math.max(0, Math.min(mouseX - width / 2, parent.width - width))
+ y = mouseY + units.gu(1)
+ }
+
+ InverseMouseArea {
+ anchors.fill: parent
+ onClicked: {
+ screensMenu.destroy()
+ }
+ }
+
+ Column {
+ id: contentColumn
+ width: parent.width
+ ListItem {
+ height: layout.height
+ highlightColor: "transparent"
+ ListItemLayout {
+ id: layout
+ title.text: qsTr("Add workspace")
+ title.color: "black"
+ }
+ onClicked: {
+ screen.workspaces.addWorkspace();
+ Screens.sync(root.screensProxy);
+ screensMenu.destroy();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Workspaces {
+ id: workspaces
+ height: parent.height - header.height - units.gu(2)
+ width: {
+ var width = 0;
+ if (screensProxy.count == 1) {
+ width = Math.min(implicitWidth, root.width - units.gu(8));
+ } else {
+ width = Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
+ }
+ return Math.max(workspaces.minimumWidth, width);
+ }
+
+ Behavior on width { UbuntuNumberAnimation {} }
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: units.gu(1)
+ anchors.horizontalCenter: parent.horizontalCenter
+ screen: model.screen
+ background: root.background
+
+ workspaceModel: model.screen.workspaces
+ activeWorkspace: root.activeWorkspace
+ readOnly: false
+
+ onCommitScreenSetup: Screens.sync(root.screensProxy)
+ onCloseSpread: root.closeSpread();
+
+ onClicked: {
+ root.activeWorkspace = workspace;
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
+ width: units.gu(5)
+ color: "#33000000"
+ visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
+ MouseArea {
+ id: leftScrollArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onPressed: mouse.accepted = false;
+ }
+ DropArea {
+ id: leftFakeDropArea
+ anchors.fill: parent
+ keys: ["application", "workspace"]
+ }
+ }
+ Rectangle {
+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
+ width: units.gu(5)
+ color: "#33000000"
+ visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
+ MouseArea {
+ id: rightScrollArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onPressed: mouse.accepted = false;
+ }
+ DropArea {
+ id: rightFakeDropArea
+ anchors.fill: parent
+ keys: ["application", "workspace"]
+ }
+ }
+ Timer {
+ repeat: true
+ running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
+ interval: UbuntuAnimation.SlowDuration
+ triggeredOnStart: true
+ onTriggered: {
+ var newOffset = row.anchors.horizontalCenterOffset;
+ var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
+ if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
+ newOffset += units.gu(20)
+ } else {
+ newOffset -= units.gu(20)
+ }
+ newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
+ row.anchors.horizontalCenterOffset = newOffset;
+ }
+ }
+}
diff --git a/qml/Stage/Spread/Spread.qml b/qml/Stage/Spread/Spread.qml
index 93a1692717..7ed013c0d3 100644
--- a/qml/Stage/Spread/Spread.qml
+++ b/qml/Stage/Spread/Spread.qml
@@ -28,10 +28,10 @@ Item {
property var spreadFlickable
// some config options
- property real contentMargin: 0.16 * root.height
- property real contentTopMargin: contentMargin
- property real contentBottomMargin: 0.35 * contentMargin
- property real windowTitleTopMargin: 3/4 * (contentTopMargin - windowTitle.height)
+ property real contentMargin: 0.1 * root.height
+ property real contentTopMargin: contentMargin + root.y + windowTitle.height
+ property real contentBottomMargin: contentMargin
+ property real windowTitleTopMargin: contentMargin - windowTitle.height
property int stackItemCount: 3
property real leftRotationAngle: 22
property real rightRotationAngle: 32
@@ -52,7 +52,7 @@ Item {
readonly property real spreadWidth: rightStackXPos - leftStackXPos
readonly property real spreadHeight: root.height
- readonly property real spreadItemHeight: spreadHeight - contentTopMargin - contentBottomMargin
+ readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
readonly property real spreadItemWidth: stackHeight
readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
@@ -94,8 +94,7 @@ Item {
readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
- readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth ,0) / (2 * spreadWidth)
-
+ readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth + (leftStackXPos - leftMargin) * 2, 0) / (2 * spreadWidth)
readonly property var curve: BezierCurve {
controlPoint2: {'x': 0.19, 'y': 0.00}
diff --git a/qml/Stage/Spread/SpreadDelegateInputArea.qml b/qml/Stage/Spread/SpreadDelegateInputArea.qml
index 7ff37937e8..f5b272b163 100644
--- a/qml/Stage/Spread/SpreadDelegateInputArea.qml
+++ b/qml/Stage/Spread/SpreadDelegateInputArea.qml
@@ -28,6 +28,9 @@ Item {
readonly property alias distance: d.distance
+ property var stage: null
+ property var dragDelegate: null
+
signal clicked()
signal close()
@@ -84,33 +87,27 @@ Item {
}
}
- // Event eater
- MouseArea {
- anchors.fill: parent
- onClicked: root.clicked()
- onWheel: wheel.accepted = true
- }
-
MultiPointTouchArea {
anchors.fill: parent
- mouseEnabled: false
maximumTouchPoints: 1
property int offset: 0
+ // tp.startY seems to be broken for mouse interaction... lets track it ourselves
+ property int startY: 0
+
touchPoints: [
TouchPoint {
id: tp
}
]
- onCanceled: {
- d.moving = false
- animation.animate("center");
+ onPressed: {
+ startY = tp.y
}
onTouchUpdated: {
- if (!d.moving) {
- if (Math.abs(tp.startY - tp.y) > d.threshold) {
+ if (!d.moving || !tp.pressed) {
+ if (Math.abs(startY - tp.y) > d.threshold) {
d.moving = true;
d.dragEvents = []
offset = tp.y - tp.startY;
@@ -119,17 +116,32 @@ Item {
}
}
- if (root.closeable) {
- d.distance = tp.y - tp.startY - offset
+
+ var value = tp.y - tp.startY - offset;
+ if (value < 0 && stage.workspaceEnabled) {
+ var coords = mapToItem(stage, tp.x, tp.y);
+ dragDelegate.Drag.hotSpot.x = dragDelegate.width / 2
+ dragDelegate.Drag.hotSpot.y = units.gu(2)
+ dragDelegate.x = coords.x - dragDelegate.Drag.hotSpot.x
+ dragDelegate.y = coords.y - dragDelegate.Drag.hotSpot.y
+ dragDelegate.Drag.active = true;
+ dragDelegate.surface = model.window.surface;
+
} else {
- var value = tp.y - tp.startY - offset;
- d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
+ if (root.closeable) {
+ d.distance = value
+ } else {
+ d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
+ }
}
d.pushDragEvent(tp);
}
onReleased: {
+ var result = dragDelegate.Drag.drop();
+ dragDelegate.surface = null;
+
if (!d.moving) {
root.clicked()
}
@@ -149,6 +161,13 @@ Item {
animation.animate("center")
}
}
+
+ onCanceled: {
+ dragDelegate.Drag.active = false;
+ dragDelegate.surface = null;
+ d.moving = false
+ animation.animate("center");
+ }
}
UbuntuNumberAnimation {
diff --git a/qml/Stage/Spread/WorkspacePreview.qml b/qml/Stage/Spread/WorkspacePreview.qml
new file mode 100644
index 0000000000..13b603d2c0
--- /dev/null
+++ b/qml/Stage/Spread/WorkspacePreview.qml
@@ -0,0 +1,127 @@
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Unity.Application 0.1
+import WindowManager 1.0
+import ".."
+import "../../Components"
+
+Item {
+ id: previewSpace
+ clip: true
+
+ property var workspace
+
+ property string background
+ property int screenHeight
+
+ property real previewScale: previewSpace.height / previewSpace.screenHeight
+
+ property bool containsDragLeft: false
+ property bool containsDragRight: false
+ property bool isActive: false
+ property bool isSelected: false
+
+ Image {
+ source: previewSpace.background
+ anchors.fill: parent
+ sourceSize.width: width
+ sourceSize.height: height
+
+ Repeater {
+ id: topLevelSurfaceRepeater
+ model: visible ? workspace.windowModel : null
+ delegate: Item {
+ width: surfaceItem.width
+ height: surfaceItem.height + decorationHeight * previewScale
+ x: model.window.position.x * previewScale
+ y: (model.window.position.y - decorationHeight) * previewScale
+ z: topLevelSurfaceRepeater.count - index
+ visible: model.window.state !== Mir.MinimizedState && model.window.state !== Mir.HiddenState
+
+ property int decorationHeight: units.gu(3)
+
+ WindowDecoration {
+ width: surfaceItem.implicitWidth
+ height: parent.decorationHeight
+ transform: Scale {
+ origin.x: 0
+ origin.y: 0
+ xScale: previewScale
+ yScale: previewScale
+ }
+ title: model.window && model.window.surface ? model.window.surface.name : ""
+ z: 3
+ }
+
+ MirSurfaceItem {
+ id: surfaceItem
+ y: parent.decorationHeight * previewScale
+ width: implicitWidth * previewScale
+ height: implicitHeight * previewScale
+ surfaceWidth: -1
+ surfaceHeight: -1
+ surface: model.window.surface
+ }
+ }
+ }
+
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ border.color: UbuntuColors.ash
+ border.width: units.gu(.5)
+ color: "transparent"
+ visible: previewSpace.isActive
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ border.color: UbuntuColors.blue
+ border.width: units.gu(.5)
+ color: "transparent"
+ visible: previewSpace.isSelected
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.rightMargin: parent.width / 2
+ color: "#55000000"
+ visible: previewSpace.containsDragLeft
+
+ Column {
+ anchors.centerIn: parent
+ spacing: units.gu(1)
+ Icon {
+ source: "../graphics/multi-monitor_drop-here.png"
+ height: units.gu(4)
+ width: height
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ Label {
+ text: qsTr("Drop here")
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.leftMargin: parent.width / 2
+ color: "#55000000"
+ visible: previewSpace.containsDragRight
+
+ Column {
+ anchors.centerIn: parent
+ spacing: units.gu(1)
+ Icon {
+ source: "../graphics/multi-monitor_leave.png"
+ height: units.gu(4)
+ width: height
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ Label {
+ text: qsTr("Drop and go")
+ }
+ }
+ }
+}
diff --git a/qml/Stage/Spread/Workspaces.qml b/qml/Stage/Spread/Workspaces.qml
new file mode 100644
index 0000000000..ce749ea1d3
--- /dev/null
+++ b/qml/Stage/Spread/Workspaces.qml
@@ -0,0 +1,412 @@
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import WindowManager 1.0
+import "MathUtils.js" as MathUtils
+import "../../Components"
+
+Item {
+ id: root
+ implicitWidth: listView.contentWidth
+ readonly property int minimumWidth: {
+ var count = Math.min(3, listView.count);
+ return listView.itemWidth * count + listView.spacing * (count - 1)
+ }
+
+ property QtObject screen: null
+ property alias workspaceModel: listView.model
+ property var background // TODO: should be stored in the workspace data
+ property int selectedIndex: -1
+ property bool readOnly: true
+ property var activeWorkspace: null
+
+ signal commitScreenSetup();
+ signal closeSpread();
+ signal clicked(var workspace);
+
+ DropArea {
+ anchors.fill: root
+
+ keys: ['workspace']
+
+ onEntered: {
+ var index = listView.getDropIndex(drag);
+ drag.source.workspace.assign(workspaceModel, index)
+ drag.source.inDropArea = true;
+ }
+
+ onPositionChanged: {
+ var index = listView.getDropIndex(drag);
+ if (listView.dropItemIndex == index) return;
+ listView.model.move(listView.dropItemIndex, index, 1);
+ listView.dropItemIndex = index;
+ }
+
+ onExited: {
+ drag.source.workspace.unassign()
+ listView.dropItemIndex = -1;
+ listView.hoveredWorkspaceIndex = -1;
+ drag.source.inDropArea = false;
+ }
+
+ onDropped: {
+ drop.accept(Qt.MoveAction);
+ listView.dropItemIndex = -1;
+ drag.source.inDropArea = false;
+ }
+ }
+ DropArea {
+ anchors.fill: parent
+ keys: ["application"]
+
+ onPositionChanged: {
+ listView.progressiveScroll(drag.x)
+ listView.updateDropProperties(drag)
+ }
+ onExited: {
+ listView.hoveredWorkspaceIndex = -1
+ }
+ onDropped: {
+ var surface = drag.source.surface;
+ drag.source.surface = null;
+ var workspace = listView.model.get(listView.hoveredWorkspaceIndex);
+ WorkspaceManager.moveSurfaceToWorkspace(surface, workspace);
+ drop.accept(Qt.MoveAction)
+ if (listView.hoveredHalf == "right") {
+ root.closeSpread();
+ workspace.activate();
+ }
+ surface.activate();
+ listView.hoveredWorkspaceIndex = -1
+ }
+ }
+
+ onSelectedIndexChanged: {
+ listView.positionViewAtIndex(selectedIndex, ListView.Center);
+ }
+
+ Item {
+ // We need to clip the listview as it has left/right margins and it would
+ // overlap with items next to it and eat mouse input. However, we can't
+ // just clip at the actual bounds as the delegates have the close button
+ // on hover which reaches a bit outside, so lets some margins for the clipping
+ anchors.fill: parent
+ anchors.margins: -units.gu(2)
+ clip: true
+
+
+ ListView {
+ id: listView
+ anchors {
+ fill: parent
+ topMargin: -parent.anchors.margins
+ bottomMargin: -parent.anchors.margins
+ leftMargin: -itemWidth - parent.anchors.margins
+ rightMargin: -itemWidth - parent.anchors.margins
+ }
+ boundsBehavior: Flickable.StopAtBounds
+
+ Behavior on contentX {
+ SmoothedAnimation { duration: 200 }
+ }
+
+ property var clickedWorkspace: null
+
+ orientation: ListView.Horizontal
+ spacing: units.gu(1)
+ leftMargin: itemWidth
+ rightMargin: itemWidth
+
+ property int screenWidth: screen.availableModes[screen.currentModeIndex].size.width
+ property int screenHeight: screen.availableModes[screen.currentModeIndex].size.height
+ property int itemWidth: height * screenWidth / screenHeight
+ property int foldingAreaWidth: itemWidth / 2
+ property int maxAngle: 40
+
+ property real realContentX: contentX - originX + leftMargin
+ property int dropItemIndex: -1
+ property int hoveredWorkspaceIndex: -1
+ property string hoveredHalf: "" // left or right
+
+ function getDropIndex(drag) {
+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
+ if (index < 0) index = 0;
+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
+ if (index > upperLimit) index = upperLimit;
+ return index;
+ }
+
+ function updateDropProperties(drag) {
+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
+ var index = Math.floor(drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing);
+ if (index < 0) {
+ listView.hoveredWorkspaceIndex = -1;
+ listView.hoveredHalf = "";
+ return;
+ }
+
+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
+ if (index > upperLimit) index = upperLimit;
+ listView.hoveredWorkspaceIndex = index;
+ var pixelsInTile = (drag.x + listView.realContentX) % (listView.itemWidth + listView.spacing);
+ listView.hoveredHalf = (pixelsInTile / listView.itemWidth) < .5 ? "left" : "right";
+ }
+
+ function progressiveScroll(mouseX) {
+ var progress = Math.max(0, Math.min(1, (mouseX - listView.itemWidth) / (width - listView.leftMargin * 2 - listView.itemWidth * 2)))
+ listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
+ }
+
+ displaced: Transition { UbuntuNumberAnimation { properties: "x" } }
+
+ delegate: Item {
+ id: workspaceDelegate
+ objectName: "delegate" + index
+ height: parent.height
+ width: listView.itemWidth
+ Behavior on width { UbuntuNumberAnimation {} }
+ visible: listView.dropItemIndex !== index
+
+ property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
+ property int distanceFromLeft: itemX //- listView.leftMargin
+ property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
+
+ property int itemAngle: {
+ if (index == 0) {
+ if (distanceFromLeft < 0) {
+ var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
+ return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
+ }
+ return 0
+ }
+ if (index == listView.count - 1) {
+ if (distanceFromRight < 0) {
+ var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
+ return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
+ }
+ return 0
+ }
+
+ if (distanceFromLeft < listView.foldingAreaWidth) {
+ // itemX : 10gu = p : 100
+ var progress = distanceFromLeft / listView.foldingAreaWidth
+ return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
+ }
+ if (distanceFromRight < listView.foldingAreaWidth) {
+ var progress = distanceFromRight / listView.foldingAreaWidth
+ return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
+ }
+ return 0
+ }
+
+ property int itemOffset: {
+ if (index == 0) {
+ if (distanceFromLeft < 0) {
+ return -distanceFromLeft
+ }
+ return 0
+ }
+ if (index == listView.count - 1) {
+ if (distanceFromRight < 0) {
+ return distanceFromRight
+ }
+ return 0
+ }
+
+ if (itemX < -listView.foldingAreaWidth) {
+ return -itemX
+ }
+ if (distanceFromLeft < listView.foldingAreaWidth) {
+ return (listView.foldingAreaWidth - distanceFromLeft) / 2
+ }
+
+ if (distanceFromRight < -listView.foldingAreaWidth) {
+ return distanceFromRight
+ }
+
+ if (distanceFromRight < listView.foldingAreaWidth) {
+ return -(listView.foldingAreaWidth - distanceFromRight) / 2
+ }
+
+ return 0
+ }
+
+ z: itemOffset < 0 ? itemOffset : -itemOffset
+ transform: [
+ Rotation {
+ angle: itemAngle
+ axis { x: 0; y: 1; z: 0 }
+ origin { x: itemAngle < 0 ? listView.itemWidth : 0; y: height / 2 }
+ },
+ Translate {
+ x: itemOffset
+ }
+ ]
+
+ WorkspacePreview {
+ id: workspacePreview
+ height: listView.height
+ width: listView.itemWidth
+ background: root.background
+ screenHeight: listView.screenHeight
+ containsDragLeft: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "left"
+ containsDragRight: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "right"
+ isActive: workspace.isSameAs(root.activeWorkspace)
+ isSelected: index === root.selectedIndex
+ workspace: model.workspace
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ root.clicked(model.workspace)
+ }
+ onDoubleClicked: {
+ model.workspace.activate();
+ root.closeSpread();
+ }
+ }
+
+ MouseArea {
+ id: closeMouseArea
+ objectName: "closeMouseArea"
+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 }
+ hoverEnabled: true
+ height: units.gu(4)
+ width: height
+ visible: !root.readOnly && listView.count > 1
+
+ onClicked: {
+ model.workspace.unassign();
+ root.commitScreenSetup();
+ }
+ Image {
+ id: closeImage
+ source: "../graphics/window-close.svg"
+ anchors.fill: closeMouseArea
+ anchors.margins: units.gu(1)
+ sourceSize.width: width
+ sourceSize.height: height
+ readonly property var mousePos: hoverMouseArea.mapToItem(workspaceDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
+ readonly property bool shown: (hoverMouseArea.containsMouse || parent.containsMouse)
+ && mousePos.y < workspaceDelegate.width / 4
+ && mousePos.y > -units.gu(2)
+ && mousePos.x > -units.gu(2)
+ && mousePos.x < workspaceDelegate.height / 4
+ opacity: shown ? 1 : 0
+ visible: opacity > 0
+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
+
+ }
+ }
+ }
+
+ MouseArea {
+ id: hoverMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ propagateComposedEvents: true
+ anchors.leftMargin: listView.leftMargin
+ anchors.rightMargin: listView.rightMargin
+ enabled: !root.readOnly
+
+ property int draggedIndex: -1
+
+ property int startX: 0
+ property int startY: 0
+
+ onMouseXChanged: {
+ if (!pressed || dragging) {
+ listView.progressiveScroll(mouseX)
+ }
+ }
+ onMouseYChanged: {
+ if (Math.abs(mouseY - startY) > units.gu(3)) {
+ drag.axis = Drag.XAndYAxis;
+ }
+ }
+
+ onReleased: {
+ var result = fakeDragItem.Drag.drop();
+ // if (result == Qt.IgnoreAction) {
+ // WorkspaceManager.destroyWorkspace(fakeDragItem.workspace);
+ // }
+ root.commitScreenSetup();
+ drag.target = null;
+ }
+
+ property bool dragging: drag.active
+ onDraggingChanged: {
+ if (drag.active) {
+ var ws = listView.model.get(draggedIndex);
+ if (ws) ws.unassign();
+ }
+ }
+
+ onPressed: {
+ startX = mouseX;
+ startY = mouseY;
+ if (listView.model.count < 2) return;
+
+ var coords = mapToItem(listView.contentItem, mouseX, mouseY)
+ draggedIndex = listView.indexAt(coords.x, coords.y)
+ var clickedItem = listView.itemAt(coords.x, coords.y)
+
+ var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
+ fakeDragItem.x = itemCoords.x
+ fakeDragItem.y = itemCoords.y
+ fakeDragItem.workspace = listView.model.get(draggedIndex)
+
+ var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
+ fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
+ fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
+
+ drag.axis = Drag.YAxis;
+ drag.target = fakeDragItem;
+ }
+
+ WorkspacePreview {
+ id: fakeDragItem
+ height: listView.height
+ width: listView.itemWidth
+ background: root.background
+ screenHeight: screen.availableModes[screen.currentModeIndex].size.height
+ visible: Drag.active
+
+ Drag.active: hoverMouseArea.drag.active
+ Drag.keys: ['workspace']
+
+ property bool inDropArea: false
+
+ Rectangle {
+ anchors.fill: parent
+ color: "#33000000"
+ opacity: parent.inDropArea ? 0 : 1
+ Behavior on opacity { UbuntuNumberAnimation { } }
+ Rectangle {
+ anchors.centerIn: parent
+ width: units.gu(6)
+ height: units.gu(6)
+ radius: width / 2
+ color: "#aa000000"
+ }
+
+ Icon {
+ height: units.gu(3)
+ width: height
+ anchors.centerIn: parent
+ name: "edit-delete"
+ color: "white"
+ }
+ }
+
+ states: [
+ State {
+ when: fakeDragItem.Drag.active
+ ParentChange { target: fakeDragItem; parent: shell }
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/qml/Stage/Stage.qml b/qml/Stage/Stage.qml
index 7ddc9966d4..048c7edb5f 100644
--- a/qml/Stage/Stage.qml
+++ b/qml/Stage/Stage.qml
@@ -48,6 +48,7 @@ FocusScope {
property rect inputMethodRect
property real rightEdgePushProgress: 0
property Item availableDesktopArea
+ property PanelState panelState
// Whether outside forces say that the Stage may have focus
property bool allowInteractivity
@@ -57,6 +58,9 @@ FocusScope {
// Configuration
property string mode: "staged"
+ readonly property var temporarySelectedWorkspace: state == "spread" ? screensAndWorkspaces.activeWorkspace : null
+ property bool workspaceEnabled: (mode == "windowed" || settings.forceEnableWorkspace) && settings.enableWorkspace
+
// Used by the tutorial code
readonly property real rightEdgeDragProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0 // How far left the stage has been dragged
@@ -94,6 +98,11 @@ FocusScope {
Qt.InvertedLandscapeOrientation;
}
+ GSettings {
+ id: settings
+ schema.id: "com.canonical.Unity8"
+ }
+
property int launcherLeftMargin : 0
Binding {
@@ -157,6 +166,7 @@ FocusScope {
function updateFocusedAppOrientationAnimated() { /* TODO */}
function closeSpread() {
+ spreadItem.highlightedIndex = -1;
priv.goneToSpread = false;
}
@@ -263,6 +273,43 @@ FocusScope {
}
}
+ GlobalShortcut {
+ id: showWorkspaceSwitcherShortcutLeft
+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Left
+ active: !workspaceSwitcher.active
+ onTriggered: {
+ root.focus = true;
+ workspaceSwitcher.showLeft()
+ }
+ }
+ GlobalShortcut {
+ id: showWorkspaceSwitcherShortcutRight
+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Right
+ active: !workspaceSwitcher.active
+ onTriggered: {
+ root.focus = true;
+ workspaceSwitcher.showRight()
+ }
+ }
+ GlobalShortcut {
+ id: showWorkspaceSwitcherShortcutUp
+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Up
+ active: !workspaceSwitcher.active
+ onTriggered: {
+ root.focus = true;
+ workspaceSwitcher.showUp()
+ }
+ }
+ GlobalShortcut {
+ id: showWorkspaceSwitcherShortcutDown
+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Down
+ active: !workspaceSwitcher.active
+ onTriggered: {
+ root.focus = true;
+ workspaceSwitcher.showDown()
+ }
+ }
+
QtObject {
id: priv
objectName: "DesktopStagePrivate"
@@ -398,23 +445,23 @@ FocusScope {
readonly property real windowDecorationHeight: units.gu(3)
}
- Component.onCompleted: priv.updateMainAndSideStageIndexes();
+ Component.onCompleted: priv.updateMainAndSideStageIndexes()
Connections {
- target: PanelState
+ target: panelState
onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
}
Binding {
- target: PanelState
+ target: panelState
property: "decorationsVisible"
value: mode == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !root.spreadShown
}
Binding {
- target: PanelState
+ target: panelState
property: "title"
value: {
if (priv.focusedAppDelegate !== null) {
@@ -429,7 +476,7 @@ FocusScope {
}
Binding {
- target: PanelState
+ target: panelState
property: "focusedPersistentSurfaceId"
value: {
if (priv.focusedAppDelegate !== null) {
@@ -443,21 +490,21 @@ FocusScope {
}
Binding {
- target: PanelState
+ target: panelState
property: "dropShadow"
value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
}
Binding {
- target: PanelState
+ target: panelState
property: "closeButtonShown"
value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
}
Component.onDestruction: {
- PanelState.title = "";
- PanelState.decorationsVisible = false;
- PanelState.dropShadow = false;
+ panelState.title = "";
+ panelState.decorationsVisible = false;
+ panelState.dropShadow = false;
}
Instantiator {
@@ -523,6 +570,7 @@ FocusScope {
PropertyChanges { target: noAppsRunningHint; visible: (root.topLevelSurfaceList.count < 1) }
PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
PropertyChanges { target: wallpaper; visible: false }
+ PropertyChanges { target: screensAndWorkspaces; opacity: 1 }
},
State {
name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
@@ -577,11 +625,16 @@ FocusScope {
Transition {
from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
+ PropertyAction { target: screensAndWorkspaces; property: "activeWorkspace"; value: WMScreen.currentWorkspace }
PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
},
Transition {
to: "spread"
+ PropertyAction { target: screensAndWorkspaces; property: "activeWorkspace"; value: WMScreen.currentWorkspace }
PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
},
Transition {
from: "spread"
@@ -639,10 +692,26 @@ FocusScope {
visible: false
}
+ ScreensAndWorkspaces {
+ id: screensAndWorkspaces
+ anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: root.leftMargin }
+ height: Math.max(units.gu(30), parent.height * .3)
+ background: root.background
+ opacity: 0
+ visible: workspaceEnabled ? opacity > 0 : false
+ enabled: workspaceEnabled
+ onCloseSpread: priv.goneToSpread = false;
+ }
+
Spread {
id: spreadItem
objectName: "spreadItem"
- anchors.fill: appContainer
+ anchors {
+ left: parent.left;
+ bottom: parent.bottom;
+ right: parent.right;
+ top: workspaceEnabled ? screensAndWorkspaces.bottom : parent.top;
+ }
leftMargin: root.availableDesktopArea.x
model: root.topLevelSurfaceList
spreadFlickable: floatingFlickable
@@ -655,6 +724,89 @@ FocusScope {
onCloseCurrentApp: {
appRepeater.itemAt(highlightedIndex).close();
}
+
+ FloatingFlickable {
+ id: floatingFlickable
+ objectName: "spreadFlickable"
+ anchors.fill: parent
+ enabled: false
+ contentWidth: spreadItem.spreadTotalWidth
+
+ function snap(toIndex) {
+ var delegate = appRepeater.itemAt(toIndex)
+ var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
+ if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
+ var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
+ snapAnimation.to = floatingFlickable.contentX - offset;
+ snapAnimation.start();
+ } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
+ var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
+ snapAnimation.to = floatingFlickable.contentX - offset;
+ snapAnimation.start();
+ }
+ }
+ UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
+ }
+
+ MouseArea {
+ id: hoverMouseArea
+ objectName: "hoverMouseArea"
+ anchors.fill: parent
+ propagateComposedEvents: true
+ hoverEnabled: true
+ enabled: false
+ visible: enabled
+ property bool wasTouchPress: false
+
+ property int scrollAreaWidth: width / 3
+ property bool progressiveScrollingEnabled: false
+
+ onMouseXChanged: {
+ mouse.accepted = false
+
+ if (hoverMouseArea.pressed || wasTouchPress) {
+ return;
+ }
+
+ // Find the hovered item and mark it active
+ for (var i = appRepeater.count - 1; i >= 0; i--) {
+ var appDelegate = appRepeater.itemAt(i);
+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
+ spreadItem.highlightedIndex = i;
+ break;
+ }
+ }
+
+ if (floatingFlickable.contentWidth > floatingFlickable.width) {
+ var margins = floatingFlickable.width * 0.05;
+
+ if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
+ progressiveScrollingEnabled = true
+ }
+
+ // do we need to scroll?
+ if (mouseX < scrollAreaWidth + margins) {
+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
+ var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
+ floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
+ }
+ if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
+ var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
+ var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
+ floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
+ }
+ }
+ }
+
+ onPressed: {
+ mouse.accepted = false;
+ wasTouchPress = mouse.source === Qt.MouseEventSynthesizedByQt;
+ }
+
+ onExited: wasTouchPress = false;
+ }
}
Label {
@@ -763,6 +915,25 @@ FocusScope {
}
}
+ MirSurfaceItem {
+ id: fakeDragItem
+ property real previewScale: .5
+ height: (screensAndWorkspaces.height - units.gu(8)) / 2
+ // w : h = iw : ih
+ width: implicitWidth * height / implicitHeight
+ surfaceWidth: -1
+ surfaceHeight: -1
+ opacity: surface != null ? 1 : 0
+ Behavior on opacity { UbuntuNumberAnimation {} }
+ visible: opacity > 0
+ enabled: workspaceSwitcher
+
+ Drag.active: surface != null
+ Drag.keys: ["application"]
+
+ z: 1000
+ }
+
Repeater {
id: appRepeater
model: topLevelSurfaceList
@@ -791,6 +962,9 @@ FocusScope {
}
z: normalZ
+ opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
+ Behavior on opacity { UbuntuNumberAnimation {} }
+
// Set these as propertyes as they wont update otherwise
property real screenOffsetX: Screen.virtualX
property real screenOffsetY: Screen.virtualY
@@ -994,7 +1168,6 @@ FocusScope {
function claimFocus() {
if (root.state == "spread") {
spreadItem.highlightedIndex = index
- priv.goneToSpread = false;
}
if (root.mode == "stagedWithSideStage") {
if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
@@ -1359,8 +1532,6 @@ FocusScope {
y: spreadMaths.targetY
z: index
height: spreadItem.spreadItemHeight
- requestedWidth: decoratedWindow.oldRequestedWidth
- requestedHeight: decoratedWindow.oldRequestedHeight
visible: spreadMaths.itemVisible
}
PropertyChanges { target: dragArea; enabled: true }
@@ -1380,8 +1551,6 @@ FocusScope {
y: stagedRightEdgeMaths.animatedY
z: stagedRightEdgeMaths.animatedZ
height: stagedRightEdgeMaths.animatedHeight
- requestedWidth: decoratedWindow.oldRequestedWidth
- requestedHeight: decoratedWindow.oldRequestedHeight
visible: appDelegate.x < root.width
}
PropertyChanges {
@@ -1410,8 +1579,6 @@ FocusScope {
y: windowedRightEdgeMaths.animatedY
z: windowedRightEdgeMaths.animatedZ
height: stagedRightEdgeMaths.animatedHeight
- requestedWidth: decoratedWindow.oldRequestedWidth
- requestedHeight: decoratedWindow.oldRequestedHeight
}
PropertyChanges {
target: decoratedWindow
@@ -1434,11 +1601,15 @@ FocusScope {
target: appDelegate
x: stageMaths.itemX
y: root.availableDesktopArea.y
- requestedWidth: appContainer.width
- requestedHeight: root.availableDesktopArea.height
visuallyMaximized: true
visible: appDelegate.x < root.width
}
+ PropertyChanges {
+ target: appDelegate
+ requestedWidth: appContainer.width
+ requestedHeight: root.availableDesktopArea.height
+ restoreEntryValues: false
+ }
PropertyChanges {
target: decoratedWindow
hasDecoration: false
@@ -1467,11 +1638,15 @@ FocusScope {
x: stageMaths.itemX
y: root.availableDesktopArea.y
z: stageMaths.itemZ
- requestedWidth: stageMaths.itemWidth
- requestedHeight: root.availableDesktopArea.height
visuallyMaximized: true
visible: appDelegate.x < root.width
}
+ PropertyChanges {
+ target: appDelegate
+ requestedWidth: stageMaths.itemWidth
+ requestedHeight: root.availableDesktopArea.height
+ restoreEntryValues: false
+ }
PropertyChanges {
target: decoratedWindow
hasDecoration: false
@@ -1492,8 +1667,13 @@ FocusScope {
requestedX: root.availableDesktopArea.x;
requestedY: 0;
visuallyMinimized: false;
+ visuallyMaximized: true
+ }
+ PropertyChanges {
+ target: appDelegate
requestedWidth: root.availableDesktopArea.width;
requestedHeight: appContainer.height;
+ restoreEntryValues: false
}
PropertyChanges { target: touchControls; enabled: true }
PropertyChanges { target: decoratedWindow; windowControlButtonsVisible: false }
@@ -1504,8 +1684,12 @@ FocusScope {
target: appDelegate;
requestedX: 0
requestedY: 0
- requestedWidth: appContainer.width;
- requestedHeight: appContainer.height;
+ }
+ PropertyChanges {
+ target: appDelegate
+ requestedWidth: appContainer.width
+ requestedHeight: appContainer.height
+ restoreEntryValues: false
}
PropertyChanges { target: decoratedWindow; hasDecoration: false }
},
@@ -1519,6 +1703,12 @@ FocusScope {
PropertyChanges { target: touchControls; enabled: true }
PropertyChanges { target: resizeArea; enabled: true }
PropertyChanges { target: decoratedWindow; shadowOpacity: .3; windowControlButtonsVisible: true}
+ PropertyChanges {
+ target: appDelegate
+ requestedWidth: windowedWidth
+ requestedHeight: windowedHeight
+ restoreEntryValues: false
+ }
},
State {
name: "restored";
@@ -1619,6 +1809,7 @@ FocusScope {
}
}
]
+
transitions: [
// These two animate applications into position from Staged to Desktop and back
@@ -1636,6 +1827,7 @@ FocusScope {
},
Transition {
+ from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,staged,stagedWithSideStage,windowedRightEdge,stagedRightEdge";
to: "spread"
// DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
PropertyAction { target: appDelegate; properties: "z,visible" }
@@ -1688,6 +1880,7 @@ FocusScope {
SequentialAnimation {
ScriptAction { script: { fakeRectangle.stop(); } }
PropertyAction { target: appDelegate; property: "visuallyMaximized" }
+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
UbuntuNumberAnimation { target: appDelegate; properties: "x,y,scale,opacity"; duration: priv.animationDuration }
PropertyAction { target: appDelegate; property: "visuallyMinimized" }
}
@@ -1728,7 +1921,7 @@ FocusScope {
]
Binding {
- target: PanelState
+ target: panelState
property: "decorationsAlwaysVisible"
value: appDelegate && appDelegate.maximized && touchControls.overlayShown
}
@@ -1749,6 +1942,7 @@ FocusScope {
borderThickness: units.gu(2)
enabled: false
visible: enabled
+ readyToAssesBounds: !appDelegate._constructing
onPressed: {
appDelegate.activate();
@@ -1772,18 +1966,13 @@ FocusScope {
width: implicitWidth
height: implicitHeight
highlightSize: windowInfoItem.iconMargin / 2
- altDragEnabled: root.mode == "windowed"
boundsItem: root.availableDesktopArea
+ panelState: root.panelState
+ altDragEnabled: root.mode == "windowed"
requestedWidth: appDelegate.requestedWidth
requestedHeight: appDelegate.requestedHeight
- property int oldRequestedWidth: -1
- property int oldRequestedHeight: -1
-
- onRequestedWidthChanged: oldRequestedWidth = requestedWidth
- onRequestedHeightChanged: oldRequestedHeight = requestedHeight
-
onCloseClicked: { appDelegate.close(); }
onMaximizeClicked: {
if (appDelegate.canBeMaximized) {
@@ -1864,6 +2053,8 @@ FocusScope {
anchors.fill: decoratedWindow
enabled: false
closeable: true
+ stage: root
+ dragDelegate: fakeDragItem
onClicked: {
spreadItem.highlightedIndex = index;
@@ -1873,7 +2064,7 @@ FocusScope {
}
onClose: {
priv.closingIndex = index
- model.window.close();
+ appDelegate.close();
}
}
@@ -1906,12 +2097,15 @@ FocusScope {
objectName: "closeMouseArea"
anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
- visible: dragArea.distance == 0
+ readonly property bool shown: dragArea.distance == 0
&& index == spreadItem.highlightedIndex
&& mousePos.y < (decoratedWindow.height / 3)
&& mousePos.y > -units.gu(4)
&& mousePos.x > -units.gu(4)
&& mousePos.x < (decoratedWindow.width * 2 / 3)
+ opacity: shown ? 1 : 0
+ visible: opacity > 0
+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
height: units.gu(6)
width: height
@@ -1982,89 +2176,21 @@ FocusScope {
leftMargin: root.availableDesktopArea.x
appContainerWidth: appContainer.width
appContainerHeight: appContainer.height
+ panelState: root.panelState
}
- MouseArea {
- id: hoverMouseArea
- objectName: "hoverMouseArea"
- anchors.fill: appContainer
- propagateComposedEvents: true
- hoverEnabled: true
- enabled: false
- visible: enabled
-
- property int scrollAreaWidth: width / 3
- property bool progressiveScrollingEnabled: false
- property bool wasTouchPress: false
-
- onMouseXChanged: {
- mouse.accepted = false
-
- if (hoverMouseArea.pressed || wasTouchPress) {
- return;
- }
-
- // Find the hovered item and mark it active
- for (var i = appRepeater.count - 1; i >= 0; i--) {
- var appDelegate = appRepeater.itemAt(i);
- var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
- var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
- if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
- spreadItem.highlightedIndex = i;
- break;
- }
- }
-
- if (floatingFlickable.contentWidth > floatingFlickable.width) {
- var margins = floatingFlickable.width * 0.05;
-
- if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
- progressiveScrollingEnabled = true
- }
-
- // do we need to scroll?
- if (mouseX < scrollAreaWidth + margins) {
- var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
- var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
- floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
- }
- if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
- var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
- var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
- floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
- }
- }
- }
-
- onPressed: {
- mouse.accepted = false;
- wasTouchPress = mouse.source === Qt.MouseEventSynthesizedByQt;
- }
-
- onExited: wasTouchPress = false;
- }
-
- FloatingFlickable {
- id: floatingFlickable
- objectName: "spreadFlickable"
- anchors.fill: appContainer
- enabled: false
- contentWidth: spreadItem.spreadTotalWidth
-
- function snap(toIndex) {
- var delegate = appRepeater.itemAt(toIndex)
- var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
- if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
- var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
- snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
- snapAnimation.start();
- } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
- var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
- snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
- snapAnimation.start();
+ WorkspaceSwitcher {
+ id: workspaceSwitcher
+ enabled: workspaceEnabled
+ anchors.centerIn: parent
+ height: units.gu(20)
+ width: root.width - units.gu(8)
+ background: root.background
+ onActiveChanged: {
+ if (!active) {
+ appContainer.focus = true;
}
}
- UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
}
PropertyAnimation {
diff --git a/qml/Stage/SurfaceContainer.qml b/qml/Stage/SurfaceContainer.qml
index 8074bcfa5c..3c7787cd6e 100644
--- a/qml/Stage/SurfaceContainer.qml
+++ b/qml/Stage/SurfaceContainer.qml
@@ -39,10 +39,15 @@ FocusScope {
// to update surface activeFocus. See mock MirSurfaceItem.
property alias consumesInput: surfaceItem.consumesInput
+ property bool hadSurface: false
+
onSurfaceChanged: {
// Not a binding because animations might remove the surface from the surfaceItem
// programatically (in order to signal that a zombie surface is free for deletion),
// even though root.surface is still !null.
+ if (surface != null)
+ root.hadSurface = true;
+
surfaceItem.surface = surface;
}
@@ -83,7 +88,7 @@ FocusScope {
Loader {
id: animationsLoader
objectName: "animationsLoader"
- active: root.surface
+ active: root.surface || root.hadSurface
source: {
if (root.isPromptSurface) {
return "PromptSurfaceAnimations.qml";
@@ -104,5 +109,11 @@ FocusScope {
property: "container"
value: root
}
+ Binding {
+ target: animationsLoader.item
+ when: animationsLoader.item
+ property: "hadSurface"
+ value: hadSurface
+ }
}
}
diff --git a/qml/Stage/WindowDecoration.qml b/qml/Stage/WindowDecoration.qml
index 666f97bb60..356f2e3854 100644
--- a/qml/Stage/WindowDecoration.qml
+++ b/qml/Stage/WindowDecoration.qml
@@ -35,6 +35,7 @@ MouseArea {
property bool enableMenus: true
property bool windowMoving: false
property alias windowControlButtonsVisible: buttons.visible
+ property PanelState panelState
readonly property real buttonsWidth: buttons.width + row.spacing
@@ -146,6 +147,7 @@ MouseArea {
enableKeyFilter: valid && root.active && root.enableMenus
unityMenuModel: root.menu
windowMoving: root.windowMoving
+ panelState: root.panelState
onPressed: root.onPressed(mouse)
onPressedChangedEx: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
diff --git a/qml/Stage/WindowResizeArea.qml b/qml/Stage/WindowResizeArea.qml
index d3e5d2f01b..1bd739371d 100644
--- a/qml/Stage/WindowResizeArea.qml
+++ b/qml/Stage/WindowResizeArea.qml
@@ -36,35 +36,58 @@ MouseArea {
property int minWidth: 0
property int minHeight: 0
+ property bool readyToAssesBounds: false
+ onReadyToAssesBoundsChanged: d.reassesBounds()
+
QtObject {
id: d
readonly property int maxSafeInt: 2147483647
readonly property int maxSizeIncrement: units.gu(40)
+ function reassesBounds() {
+ if (!readyToAssesBounds) return;
+
+ if (target.windowedWidth < minimumWidth) {
+ target.windowedWidth = minimumWidth;
+ }
+ if (target.windowedHeight < minimumHeight) {
+ target.windowedHeight = minimumHeight;
+ }
+ if (target.windowedHeight < minimumHeight) {
+ target.windowedHeight = minimumHeight;
+ }
+ if (target.windowedWidth > maximumWidth) {
+ target.windowedWidth = maximumWidth;
+ }
+ if (target.windowedHeight > maximumHeight) {
+ target.windowedHeight = maximumHeight;
+ }
+ }
+
readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
onMinimumWidthChanged: {
- if (target.windowedWidth < minimumWidth) {
+ if (readyToAssesBounds && target.windowedWidth < minimumWidth) {
target.windowedWidth = minimumWidth;
}
}
readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
onMinimumHeightChanged: {
- if (target.windowedHeight < minimumHeight) {
+ if (readyToAssesBounds && target.windowedHeight < minimumHeight) {
target.windowedHeight = minimumHeight;
}
}
readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
? root.target.maximumWidth : maxSafeInt
onMaximumWidthChanged: {
- if (target.windowedWidth > maximumWidth) {
+ if (readyToAssesBounds && target.windowedWidth > maximumWidth) {
target.windowedWidth = maximumWidth;
}
}
readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
? root.target.maximumHeight : maxSafeInt
onMaximumHeightChanged: {
- if (target.windowedHeight > maximumHeight) {
+ if (readyToAssesBounds && target.windowedHeight > maximumHeight) {
target.windowedHeight = maximumHeight;
}
}
diff --git a/qml/Stage/WindowStateSaver.qml b/qml/Stage/WindowStateSaver.qml
index 19f423a892..dcbba97cf1 100644
--- a/qml/Stage/WindowStateSaver.qml
+++ b/qml/Stage/WindowStateSaver.qml
@@ -43,7 +43,10 @@ QtObject {
(target.fullscreen ? 0 : root.leftMargin)); });
target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
- target.updateNormalGeometry();
+ target.normalWidth = target.windowedWidth;
+ target.normalHeight = target.windowedHeight;
+ target.normalX = target.windowedX;
+ target.normalY = target.windowedY;
// initialize the x/y to restore to
target.restoredX = target.normalX;
diff --git a/qml/Stage/WorkspaceSwitcher.qml b/qml/Stage/WorkspaceSwitcher.qml
new file mode 100644
index 0000000000..cb1df95899
--- /dev/null
+++ b/qml/Stage/WorkspaceSwitcher.qml
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014-2016 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "Spread"
+import WindowManager 1.0
+import Unity.Application 0.1
+
+Item {
+ id: root
+
+ opacity: d.shown ? 1 : 0
+ visible: opacity > 0
+ Behavior on opacity { UbuntuNumberAnimation {} }
+
+ property var screensProxy: Screens.createProxy();
+ property string background
+
+ readonly property alias active: d.active
+
+ function showLeft() {
+ show();
+ d.previousWorkspace();
+ }
+ function showRight() {
+ show();
+ d.nextWorkspace();
+ }
+ function showUp() {
+ show();
+ d.previousScreen();
+ }
+ function showDown() {
+ show();
+ d.nextScreen();
+ }
+
+ function show() {
+ hideTimer.stop();
+ d.altPressed = true;
+ d.ctrlPressed = true;
+ d.active = true;
+ d.shown = true;
+ focus = true;
+
+ d.highlightedScreenIndex = screensProxy.activeScreen;
+ var activeScreen = screensProxy.get(screensProxy.activeScreen);
+ d.highlightedWorkspaceIndex = activeScreen.workspaces.indexOf(activeScreen.currentWorkspace)
+ }
+
+ QtObject {
+ id: d
+
+ property bool active: false
+ property bool shown: false
+ property bool altPressed: false
+ property bool ctrlPressed: false
+
+ property int rowHeight: root.height - units.gu(4)
+
+ property int highlightedScreenIndex: -1
+ property int highlightedWorkspaceIndex: -1
+
+ function previousWorkspace() {
+ highlightedWorkspaceIndex = Math.max(highlightedWorkspaceIndex - 1, 0);
+ }
+ function nextWorkspace() {
+ var screen = screensProxy.get(highlightedScreenIndex);
+ highlightedWorkspaceIndex = Math.min(highlightedWorkspaceIndex + 1, screen.workspaces.count - 1);
+ }
+ function previousScreen() {
+ highlightedScreenIndex = Math.max(highlightedScreenIndex - 1, 0);
+ var screen = screensProxy.get(highlightedScreenIndex);
+ highlightedWorkspaceIndex = Math.min(highlightedWorkspaceIndex, screen.workspaces.count - 1)
+ }
+ function nextScreen() {
+ highlightedScreenIndex = Math.min(highlightedScreenIndex + 1, screensProxy.count - 1);
+ var screen = screensProxy.get(highlightedScreenIndex);
+ highlightedWorkspaceIndex = Math.min(highlightedWorkspaceIndex, screen.workspaces.count - 1)
+ }
+ }
+
+ Timer {
+ id: hideTimer
+ interval: 300
+ onTriggered: d.shown = false;
+ }
+
+ Keys.onPressed: {
+ switch (event.key) {
+ case Qt.Key_Left:
+ d.previousWorkspace();
+ break;
+ case Qt.Key_Right:
+ d.nextWorkspace()
+ break;
+ case Qt.Key_Up:
+ d.previousScreen();
+ break;
+ case Qt.Key_Down:
+ d.nextScreen();
+ }
+ }
+ Keys.onReleased: {
+ switch (event.key) {
+ case Qt.Key_Alt:
+ d.altPressed = false;
+ break;
+ case Qt.Key_Control:
+ d.ctrlPressed = false;
+ break;
+ }
+
+ if (!d.altPressed && !d.ctrlPressed) {
+ d.active = false;
+ hideTimer.start();
+ focus = false;
+ screensProxy.get(d.highlightedScreenIndex).workspaces.get(d.highlightedWorkspaceIndex).activate();
+ }
+ }
+
+ UbuntuShape {
+ backgroundColor: "#F2111111"
+ clip: true
+ width: Math.min(parent.width, screensColumn.width + units.gu(4))
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: parent.height
+
+ Column {
+ id: screensColumn
+ anchors {
+ top: parent.top; topMargin: units.gu(2) - d.highlightedScreenIndex * (d.rowHeight + screensColumn.spacing)
+ left: parent.left; leftMargin: units.gu(2)
+ }
+ width: screensRepeater.itemAt(d.highlightedScreenIndex).width
+ spacing: units.gu(2)
+ Behavior on anchors.topMargin { UbuntuNumberAnimation {} }
+ Behavior on width { UbuntuNumberAnimation {} }
+
+ Repeater {
+ id: screensRepeater
+ model: screensProxy
+
+ delegate: Item {
+ height: d.rowHeight
+ width: workspaces.width
+ anchors.horizontalCenter: parent.horizontalCenter
+ opacity: d.highlightedScreenIndex == index ? 1 : 0
+ Behavior on opacity { UbuntuNumberAnimation {} }
+
+ UbuntuShape {
+ id: header
+ anchors { left: parent.left; top: parent.top; right: parent.right }
+ height: units.gu(4)
+ backgroundColor: "white"
+
+ Label {
+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
+ text: model.screen.name
+ color: UbuntuColors.ash
+ }
+ }
+
+ Workspaces {
+ id: workspaces
+ height: parent.height - header.height - units.gu(2)
+ width: Math.min(implicitWidth, root.width - units.gu(4))
+
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: units.gu(1)
+ anchors.horizontalCenter: parent.horizontalCenter
+ screen: model.screen
+ background: root.background
+ selectedIndex: d.highlightedScreenIndex == index ? d.highlightedWorkspaceIndex : -1
+
+ workspaceModel: model.screen.workspaces
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/qml/Stage/graphics/multi-monitor_drop-here.png b/qml/Stage/graphics/multi-monitor_drop-here.png
new file mode 100644
index 0000000000..7215e655c4
Binary files /dev/null and b/qml/Stage/graphics/multi-monitor_drop-here.png differ
diff --git a/qml/Stage/graphics/multi-monitor_leave.png b/qml/Stage/graphics/multi-monitor_leave.png
new file mode 100644
index 0000000000..3d71d6f32c
Binary files /dev/null and b/qml/Stage/graphics/multi-monitor_leave.png differ
diff --git a/qml/qmldir b/qml/qmldir
new file mode 100644
index 0000000000..060972edc9
--- /dev/null
+++ b/qml/qmldir
@@ -0,0 +1 @@
+singleton ShellNotifier 0.1 ShellNotifier.qml
diff --git a/src/ApplicationArguments.cpp b/src/ApplicationArguments.cpp
index 96a5055fbd..6fa667c876 100644
--- a/src/ApplicationArguments.cpp
+++ b/src/ApplicationArguments.cpp
@@ -16,7 +16,16 @@
#include "ApplicationArguments.h"
-ApplicationArguments::ApplicationArguments(QObject *parent)
- : QObject(parent)
+ApplicationArguments::ApplicationArguments(QCoreApplication *app)
+ : QObject(app)
+ , UnityCommandLineParser(*app)
{
}
+
+void ApplicationArguments::setDeviceName(const QString &deviceName)
+{
+ if (deviceName != m_deviceName) {
+ m_deviceName = deviceName;
+ Q_EMIT deviceNameChanged(m_deviceName);
+ }
+}
diff --git a/src/ApplicationArguments.h b/src/ApplicationArguments.h
index d309049615..62e37039cd 100644
--- a/src/ApplicationArguments.h
+++ b/src/ApplicationArguments.h
@@ -23,31 +23,33 @@
#include
#include
-class ApplicationArguments : public QObject
+#include "UnityCommandLineParser.h"
+
+class ApplicationArguments : public QObject,
+ public UnityCommandLineParser
{
Q_OBJECT
Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
Q_PROPERTY(QString mode READ mode CONSTANT)
+
+ Q_PROPERTY(bool hasGeometry READ hasGeometry CONSTANT)
+ Q_PROPERTY(QSize windowGeometry READ windowGeometry CONSTANT)
+ Q_PROPERTY(bool hasTestability READ hasTestability CONSTANT)
+ Q_PROPERTY(bool hasFrameless READ hasFrameless CONSTANT)
+ Q_PROPERTY(bool hasFullscreen READ hasFullscreen CONSTANT)
+#ifdef UNITY8_ENABLE_TOUCH_EMULATION
+ Q_PROPERTY(bool hasMouseToTouch READ hasMouseToTouch CONSTANT)
+#endif
+
public:
- ApplicationArguments(QObject *parent = nullptr);
+ ApplicationArguments(QCoreApplication *app);
- void setDeviceName(const QString &deviceName) {
- if (deviceName != m_deviceName) {
- m_deviceName = deviceName;
- Q_EMIT deviceNameChanged(m_deviceName);
- }
- }
- QString deviceName() const { return m_deviceName; }
+ void setDeviceName(const QString &deviceName);
- void setMode(const QString &mode) { m_mode = mode; }
- QString mode() const { return m_mode; }
+ bool hasGeometry() const { return m_windowGeometry.isValid(); }
Q_SIGNALS:
void deviceNameChanged(const QString&);
-
-private:
- QString m_deviceName;
- QString m_mode;
};
#endif // APPLICATION_ARGUMENTS_H
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fc0c9ca9b9..29a605c76f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -9,6 +9,7 @@ include_directories(
${Qt5Quick_PRIVATE_INCLUDE_DIRS}
${GSETTINGS_QT_INCLUDE_DIRS}
${CONNECTIVITY_INCLUDE_DIRS}
+ ${QTMIRSERVER_INCLUDE_DIRS}
)
include_directories(
@@ -29,12 +30,12 @@ set(SOURCE_FILES
ApplicationArguments.cpp
main.cpp
CachingNetworkManagerFactory.cpp
- SecondaryWindow.cpp
- ShellApplication.cpp
- ShellView.cpp
+ DisplayConfigurationStorage.cpp
+ UnityApplication.cpp
UnityCommandLineParser.cpp
UnixSignalHandler.cpp
DebuggingController.cpp
+ WindowManagementPolicy.cpp
${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
)
@@ -50,9 +51,13 @@ if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
endif()
target_link_libraries(${SHELL_APP}
Qt5::DBus Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Test
- ${ANDROID_PROPERTIES_LDFLAGS} ${GSETTINGS_QT_LDFLAGS}
- UbuntuGestures connectivity-qt1 unity8-private
- )
+ ${ANDROID_PROPERTIES_LDFLAGS}
+ ${GSETTINGS_QT_LDFLAGS}
+ ${QTMIRSERVER_LDFLAGS}
+ UbuntuGestures
+ connectivity-qt1
+ unity8-private
+)
if (ENABLE_TOUCH_EMULATION)
target_link_libraries(${SHELL_APP} ${MOUSETOUCHADAPTOR_LIBS_LDFLAGS})
diff --git a/src/DisplayConfigurationStorage.cpp b/src/DisplayConfigurationStorage.cpp
new file mode 100644
index 0000000000..6dc02b2838
--- /dev/null
+++ b/src/DisplayConfigurationStorage.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "DisplayConfigurationStorage.h"
+
+#include
+#include
+#include
+#include
+
+namespace {
+
+inline QString stringFromEdid(const miral::Edid& edid)
+{
+ QString str;
+ str += QString::fromStdString(edid.vendor);
+ str += QString("%1%2").arg(edid.product_code).arg(edid.serial_number);
+
+ for (int i = 0; i < 4; i++) {
+ str += QString::fromStdString(edid.descriptors[i].string_value());
+ }
+ return str;
+}
+
+}
+
+DisplayConfigurationStorage::DisplayConfigurationStorage()
+{
+}
+
+void DisplayConfigurationStorage::save(const miral::DisplayId &displayId, const miral::DisplayConfigurationOptions &options)
+{
+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
+ QFile f(dbPath + stringFromEdid(displayId.edid) + ".edid");
+
+ QJsonObject json;
+ if (options.used.is_set()) json.insert("used", options.used.value());
+ if (options.clone_output_index.is_set()) json.insert("clone_output_index", static_cast(options.clone_output_index.value()));
+ if (options.mode.is_set()) {
+ auto const& mode = options.mode.value();
+
+ QString sz(QString("%1x%2").arg(mode.size.width.as_int()).arg(mode.size.height.as_int()));
+ QJsonObject jsonMode({
+ {"size", sz},
+ {"refresh_rate", mode.refresh_rate }
+ });
+ json.insert("mode", jsonMode);
+ }
+ if (options.orientation.is_set()) json.insert("orientation", static_cast(options.orientation.value()));
+ if (options.form_factor.is_set()) json.insert("form_factor", static_cast(options.form_factor.value()));
+ if (options.scale.is_set()) json.insert("scale", options.scale.value());
+
+ if (f.open(QIODevice::WriteOnly)) {
+ QJsonDocument saveDoc(json);
+ f.write(saveDoc.toJson());
+ }
+}
+
+bool DisplayConfigurationStorage::load(const miral::DisplayId &displayId, miral::DisplayConfigurationOptions &options) const
+{
+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
+ QFile f(dbPath + stringFromEdid(displayId.edid) + ".edid");
+
+ if (f.open(QIODevice::ReadOnly)) {
+ QByteArray saveData = f.readAll();
+ QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
+
+ QJsonObject json(loadDoc.object());
+ if (json.contains("used")) options.used = json["used"].toBool();
+ if (json.contains("clone_output_index")) options.clone_output_index = json["clone_output_index"].toInt();
+ if (json.contains("mode")) {
+ QJsonObject jsonMode = json["mode"].toObject();
+
+ if (jsonMode.contains("size") && jsonMode.contains("refresh_rate")) {
+ QString sz(jsonMode["size"].toString());
+ QStringList geo = sz.split("x", QString::SkipEmptyParts);
+ if (geo.count() == 2) {
+ miral::DisplayConfigurationOptions::DisplayMode mode;
+ mode.size = mir::geometry::Size(geo[0].toInt(), geo[1].toInt());
+ mode.refresh_rate = jsonMode["refresh_rate"].toDouble();
+ options.mode = mode;
+ }
+ }
+ }
+ if (json.contains("orientation")) options.orientation = static_cast(json["orientation"].toInt());
+ if (json.contains("form_factor")) options.form_factor = static_cast(json["form_factor"].toInt());
+ if (json.contains("scale")) options.scale = json["form_factor"].toDouble();
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/DisplayConfigurationStorage.h b/src/DisplayConfigurationStorage.h
new file mode 100644
index 0000000000..e19687792b
--- /dev/null
+++ b/src/DisplayConfigurationStorage.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef UNITY_DISPLAYCONFIGURATIONSTORAGE_H
+#define UNITY_DISPLAYCONFIGURATIONSTORAGE_H
+
+#include
+
+class DisplayConfigurationStorage : public miral::DisplayConfigurationStorage
+{
+public:
+ DisplayConfigurationStorage();
+
+ void save(const miral::DisplayId& displayId, const miral::DisplayConfigurationOptions& options) override;
+ bool load(const miral::DisplayId& displayId, miral::DisplayConfigurationOptions& options) const override;
+};
+
+#endif // UNITY_DISPLAYCONFIGURATIONSTORAGE_H
diff --git a/src/SecondaryWindow.cpp b/src/SecondaryWindow.cpp
deleted file mode 100644
index 1f5c1fb8ab..0000000000
--- a/src/SecondaryWindow.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include "SecondaryWindow.h"
-
-// local
-#include
-
-#include
-
-SecondaryWindow::SecondaryWindow(QQmlEngine *engine)
- : QQuickView(engine, nullptr)
-{
- QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
- bool ok;
- int pxpgu = pxpguEnv.toInt(&ok);
- if (!ok) {
- pxpgu = 8;
- }
- engine->rootContext()->setContextProperty(QStringLiteral("internalGu"), pxpgu);
- setResizeMode(QQuickView::SizeRootObjectToView);
- setColor("black");
- setTitle(QStringLiteral("Unity8 Shell - Secondary Screen"));
-
- QUrl source(::qmlDirectory() + "/DisabledScreenNotice.qml");
- setSource(source);
-}
diff --git a/src/ShellApplication.cpp b/src/ShellApplication.cpp
deleted file mode 100644
index 58532bc488..0000000000
--- a/src/ShellApplication.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2015 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include "ShellApplication.h"
-
-// Qt
-#include
-#include
-#include
-
-#include
-
-#include
-
-// libandroid-properties
-#include
-
-// local
-#include
-#include "CachingNetworkManagerFactory.h"
-#include "UnityCommandLineParser.h"
-#include "DebuggingController.h"
-
-ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
- : QGuiApplication(argc, argv)
-{
- setApplicationName(QStringLiteral("unity8"));
- setOrganizationName(QStringLiteral("Canonical"));
-
- connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
- connect(this, &QGuiApplication::screenRemoved, this, &ShellApplication::onScreenRemoved);
-
- setupQmlEngine(isMirServer);
-
- UnityCommandLineParser parser(*this);
-
- if (!parser.deviceName().isEmpty()) {
- m_deviceName = parser.deviceName();
- } else {
- char buffer[200];
- property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
- m_deviceName = QString(buffer);
- }
- m_qmlArgs.setDeviceName(m_deviceName);
-
- m_qmlArgs.setMode(parser.mode());
-
- // The testability driver is only loaded by QApplication but not by QGuiApplication.
- // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
- if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
- QLibrary testLib(QStringLiteral("qttestability"));
- if (testLib.load()) {
- typedef void (*TasInitialize)(void);
- TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
- if (initFunction) {
- initFunction();
- } else {
- qCritical("Library qttestability resolve failed!");
- }
- } else {
- qCritical("Library qttestability load failed!");
- }
- }
-
- bindtextdomain("unity8", translationDirectory().toUtf8().data());
- textdomain("unity8");
-
- QScopedPointer gSettings(new QGSettings("com.canonical.Unity8"));
- gSettings->reset(QStringLiteral("alwaysShowOsk"));
-
- m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
-
- if (parser.windowGeometry().isValid()) {
- m_shellView->setWidth(parser.windowGeometry().width());
- m_shellView->setHeight(parser.windowGeometry().height());
- }
-
- if (parser.hasFrameless()) {
- m_shellView->setFlags(Qt::FramelessWindowHint);
- }
-
-
- #ifdef UNITY8_ENABLE_TOUCH_EMULATION
- // You will need this if you want to interact with touch-only components using a mouse
- // Needed only when manually testing on a desktop.
- if (parser.hasMouseToTouch()) {
- m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
- }
- #endif
-
- new DebuggingController(this);
-
- // Some hard-coded policy for now.
- // NB: We don't support more than two screens at the moment
- //
- // TODO: Support an arbitrary number of screens and different policies
- // (eg cloned desktop, several desktops, etc)
- if (isMirServer && screens().count() == 2) {
- m_shellView->setScreen(screens().at(1));
- m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
-
- m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
- m_secondaryWindow->setScreen(screens().at(0));
- // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
- m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
- m_secondaryWindow->setVisible(true);
- }
-
- if (parser.mode().compare("greeter") == 0) {
- QSize primaryScreenSize = this->primaryScreen()->size();
- m_shellView->setHeight(primaryScreenSize.height());
- m_shellView->setWidth(primaryScreenSize.width());
- m_shellView->show();
- m_shellView->requestActivate();
- if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
- qDebug() << "Unable to send unity8-greeter-started event to Upstart";
- }
- } else if (isMirServer || parser.hasFullscreen()) {
- m_shellView->showFullScreen();
- } else {
- m_shellView->show();
- }
-}
-
-ShellApplication::~ShellApplication()
-{
- destroyResources();
-}
-
-void ShellApplication::destroyResources()
-{
- // Deletion order is important. Don't use QScopedPointers and the like
- // Otherwise the process will hang on shutdown (bug somewhere I guess).
- delete m_shellView;
- m_shellView = nullptr;
-
- delete m_secondaryWindow;
- m_secondaryWindow = nullptr;
-
- #ifdef UNITY8_ENABLE_TOUCH_EMULATION
- delete m_mouseTouchAdaptor;
- m_mouseTouchAdaptor = nullptr;
- #endif
-
- delete m_qmlEngine;
- m_qmlEngine = nullptr;
-}
-
-void ShellApplication::setupQmlEngine(bool isMirServer)
-{
- m_qmlEngine = new QQmlEngine(this);
-
- m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
-
- prependImportPaths(m_qmlEngine, ::overrideImportPaths());
- if (!isMirServer) {
- prependImportPaths(m_qmlEngine, ::nonMirImportPaths());
- }
- appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
-
- m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
-
- QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
-}
-
-void ShellApplication::onScreenAdded(QScreen * /*screen*/)
-{
- // TODO: Support an arbitrary number of screens and different policies
- // (eg cloned desktop, several desktops, etc)
- if (screens().count() == 2) {
- m_shellView->setScreen(screens().at(1));
- m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
- // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
- // This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
- m_shellView->setVisible(true);
-
- m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
- m_secondaryWindow->setScreen(screens().at(0));
-
- // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
- m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
- m_secondaryWindow->setVisible(true);
-
- // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
- // its backing QPlatformWindow recreated). So lets refocus it.
- // This must be done after the creation of the SecondaryWindow, else
- // the SecondaryWindow takes focus anyway.
- m_shellView->requestActivate();
- }
-}
-
-void ShellApplication::onScreenRemoved(QScreen *screen)
-{
- // TODO: Support an arbitrary number of screens and different policies
- // (eg cloned desktop, several desktops, etc)
- if (screen == m_shellView->screen()) {
- const QList allScreens = screens();
- Q_ASSERT(allScreens.count() > 1);
- Q_ASSERT(allScreens.at(0) != screen);
- Q_ASSERT(m_secondaryWindow);
- delete m_secondaryWindow;
- m_secondaryWindow = nullptr;
- m_shellView->setScreen(allScreens.first());
- m_qmlArgs.setDeviceName(m_deviceName);
- // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
- // its backing QPlatformWindow recreated). So lets refocus it.
- m_shellView->requestActivate();
- // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
- // This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
- m_shellView->setVisible(true);
- }
-}
diff --git a/src/ShellView.cpp b/src/ShellView.cpp
deleted file mode 100644
index 9320c237f2..0000000000
--- a/src/ShellView.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include "ShellView.h"
-
-// Qt
-#include
-#include
-#include
-#include
-#include
-
-// local
-#include
-
-ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
- : QQuickView(engine, nullptr)
-{
- setResizeMode(QQuickView::SizeRootObjectToView);
- setColor("black");
- setTitle(QStringLiteral("Unity8"));
-
- rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
-
- connect(this, &QQuickView::statusChanged, this, [this] {
- if (status() == QQuickView::Error) {
- QQuickRectangle *rect = new QQuickRectangle(contentItem());
- rect->setColor(Qt::white);
- QQuickItemPrivate::get(rect)->anchors()->setFill(contentItem());
-
- QString errorsString;
- for(const QQmlError &e: errors()) {
- errorsString += e.toString() + "\n";
- }
- QQuickText *text = new QQuickText(rect);
- text->setColor(Qt::black);
- text->setWrapMode(QQuickText::Wrap);
- text->setText(QString("There was an error loading Unity8:\n%1").arg(errorsString));
- QQuickItemPrivate::get(text)->anchors()->setFill(rect);
- }
- }
- );
-
- QUrl source(::qmlDirectory() + "/OrientedShell.qml");
- setSource(source);
-
- connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
- connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
-}
-
-void ShellView::onWidthChanged(int w)
-{
- // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
- //
- // There's at least one situation that's know to leave the root object with an outdated size.
- // (really looks like Qt bug)
- // Happens when starting unity8 with an external monitor already connected.
- // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
- // from the resize event handler, the root item doesn't get resized.
- // TODO: Confirm the Qt bug and submit a patch upstream
- if (rootObject()) {
- rootObject()->setWidth(w);
- }
-}
-
-void ShellView::onHeightChanged(int h)
-{
- // See comment in ShellView::onWidthChanged()
- if (rootObject()) {
- rootObject()->setHeight(h);
- }
-}
diff --git a/src/UnityApplication.cpp b/src/UnityApplication.cpp
new file mode 100644
index 0000000000..dd1bb36a52
--- /dev/null
+++ b/src/UnityApplication.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "UnityApplication.h"
+
+// Qt
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+// libandroid-properties
+#include
+
+// qtmir
+#include
+
+// local
+#include
+#include "CachingNetworkManagerFactory.h"
+#include "UnityCommandLineParser.h"
+#include "DebuggingController.h"
+#include "WindowManagementPolicy.h"
+#include "DisplayConfigurationStorage.h"
+
+#include
+
+
+
+UnityApplication::UnityApplication(int & argc, char ** argv)
+ : qtmir::MirServerApplication(argc, argv, { qtmir::SetWindowManagementPolicy(),
+ qtmir::SetDisplayConfigurationStorage() })
+ , m_qmlArgs(this)
+{
+ setApplicationName(QStringLiteral("unity8"));
+ setOrganizationName(QStringLiteral("Canonical"));
+
+ setupQmlEngine();
+
+ if (m_qmlArgs.deviceName().isEmpty()) {
+ char buffer[200];
+ property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
+ m_qmlArgs.setDeviceName(QString(buffer));
+ }
+
+ // The testability driver is only loaded by QApplication but not by QGuiApplication.
+ // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
+ if (m_qmlArgs.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
+ QLibrary testLib(QStringLiteral("qttestability"));
+ if (testLib.load()) {
+ typedef void (*TasInitialize)(void);
+ TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
+ if (initFunction) {
+ initFunction();
+ } else {
+ qCritical("Library qttestability resolve failed!");
+ }
+ } else {
+ qCritical("Library qttestability load failed!");
+ }
+ }
+
+ bindtextdomain("unity8", translationDirectory().toUtf8().data());
+ textdomain("unity8");
+
+ QScopedPointer gSettings(new QGSettings("com.canonical.Unity8"));
+ gSettings->reset(QStringLiteral("alwaysShowOsk"));
+
+
+ QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
+ bool ok;
+ int pxpgu = pxpguEnv.toInt(&ok);
+ if (!ok) {
+ pxpgu = 8;
+ }
+ m_qmlEngine->rootContext()->setContextProperty("internalGu", pxpgu);
+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &m_qmlArgs);
+ m_qmlEngine->rootContext()->setContextProperty("DebuggingController", new DebuggingController(this));
+
+ auto component(new QQmlComponent(m_qmlEngine, m_qmlArgs.qmlfie()));
+ component->create();
+ if (component->status() == QQmlComponent::Error) {
+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("errorString"), component->errorString());
+ auto errorComponent(new QQmlComponent(m_qmlEngine,
+ QUrl::fromLocalFile(::qmlDirectory() + "/ErrorApplication.qml")));
+ errorComponent->create();
+ qDebug() << errorComponent->errorString();
+ return;
+ }
+
+ #ifdef UNITY8_ENABLE_TOUCH_EMULATION
+ // You will need this if you want to interact with touch-only components using a mouse
+ // Needed only when manually testing on a desktop.
+ if (m_qmlArgs.hasMouseToTouch()) {
+ m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
+ }
+ #endif
+
+ if (m_qmlArgs.mode().compare("greeter") == 0) {
+ if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
+ qDebug() << "Unable to send unity8-greeter-started event to Upstart";
+ }
+ }
+}
+
+UnityApplication::~UnityApplication()
+{
+ destroyResources();
+}
+
+void UnityApplication::destroyResources()
+{
+ #ifdef UNITY8_ENABLE_TOUCH_EMULATION
+ delete m_mouseTouchAdaptor;
+ m_mouseTouchAdaptor = nullptr;
+ #endif
+
+ delete m_qmlEngine;
+ m_qmlEngine = nullptr;
+}
+
+void UnityApplication::setupQmlEngine()
+{
+ m_qmlEngine = new QQmlEngine(this);
+
+ m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
+
+ prependImportPaths(m_qmlEngine, ::overrideImportPaths());
+ appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
+
+ m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
+
+ QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
+}
diff --git a/src/ShellApplication.h b/src/UnityApplication.h
similarity index 66%
rename from src/ShellApplication.h
rename to src/UnityApplication.h
index d74f5e8346..2f04575669 100644
--- a/src/ShellApplication.h
+++ b/src/UnityApplication.h
@@ -14,11 +14,11 @@
* along with this program. If not, see .
*/
-#ifndef SHELLAPPLICATION_H
-#define SHELLAPPLICATION_H
+#ifndef UNITYAPPLICATION_H
+#define UNITYAPPLICATION_H
#include
-#include
+#include
#include
#include
@@ -28,28 +28,20 @@
#include "MouseTouchAdaptor.h"
#endif
-#include "SecondaryWindow.h"
-#include "ShellView.h"
+#include
-class ShellApplication : public QGuiApplication
+class UnityApplication : public qtmir::MirServerApplication
{
Q_OBJECT
public:
- ShellApplication(int & argc, char ** argv, bool isMirServer);
- virtual ~ShellApplication();
+ UnityApplication(int & argc, char ** argv);
+ virtual ~UnityApplication();
void destroyResources();
-private Q_SLOTS:
- void onScreenAdded(QScreen*);
- void onScreenRemoved(QScreen*);
-
private:
- void setupQmlEngine(bool isMirServer);
- QString m_deviceName;
+ void setupQmlEngine();
ApplicationArguments m_qmlArgs;
- ShellView *m_shellView{nullptr};
- SecondaryWindow *m_secondaryWindow{nullptr};
#ifdef UNITY8_ENABLE_TOUCH_EMULATION
MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
@@ -58,4 +50,4 @@ private Q_SLOTS:
QQmlEngine *m_qmlEngine{nullptr};
};
-#endif // SHELLAPPLICATION_H
+#endif // UNITYAPPLICATION_H
diff --git a/src/UnityCommandLineParser.cpp b/src/UnityCommandLineParser.cpp
index 920c17c0b4..9156122d85 100644
--- a/src/UnityCommandLineParser.cpp
+++ b/src/UnityCommandLineParser.cpp
@@ -15,6 +15,7 @@
*/
#include "UnityCommandLineParser.h"
+#include
#include
@@ -60,6 +61,11 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
QStringLiteral("mode"), QStringLiteral("full-greeter"));
parser.addOption(modeOption);
+ QCommandLineOption qmlfileOption(QStringLiteral("qmlfile"),
+ QStringLiteral("The base qml file to load"),
+ QStringLiteral("qmlfile"), ::qmlDirectory() + "/ShellApplication.qml");
+ parser.addOption(qmlfileOption);
+
// Treat args with single dashes the same as arguments with two dashes
// Ex: -fullscreen == --fullscreen
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
@@ -85,6 +91,8 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
m_hasFullscreen = parser.isSet(fullscreenOption);
m_deviceName = parser.value(devicenameOption);
resolveMode(parser, modeOption);
+
+ m_qmlfile = parser.value(qmlfileOption);
}
int UnityCommandLineParser::parsePixelsValue(const QString &str)
diff --git a/src/UnityCommandLineParser.h b/src/UnityCommandLineParser.h
index 7f86c51909..24bdf904d8 100644
--- a/src/UnityCommandLineParser.h
+++ b/src/UnityCommandLineParser.h
@@ -36,8 +36,10 @@ class UnityCommandLineParser {
bool hasFullscreen() const { return m_hasFullscreen; }
QString deviceName() const { return m_deviceName; }
QString mode() const { return m_mode; }
-private:
+ QString qmlfie() const { return m_qmlfile; }
+
+protected:
int parsePixelsValue(const QString &str);
static float getenvFloat(const char* name, float defaultValue);
void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
@@ -55,6 +57,7 @@ class UnityCommandLineParser {
bool m_hasFullscreen;
QString m_deviceName;
QString m_mode;
+ QString m_qmlfile;
};
#endif // UNITY_COMMAND_LINE_PARSER_H
diff --git a/src/WindowManagementPolicy.cpp b/src/WindowManagementPolicy.cpp
new file mode 100644
index 0000000000..fa72a17147
--- /dev/null
+++ b/src/WindowManagementPolicy.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "WindowManagementPolicy.h"
+
+WindowManagementPolicy::WindowManagementPolicy(const miral::WindowManagerTools &tools, std::shared_ptr dd)
+ : qtmir::WindowManagementPolicy(tools, dd)
+ , m_dummyWorkspace(this->tools.create_workspace())
+{
+ wmPolicyInterface = this;
+
+ // we must always have a active workspace.
+ m_activeWorkspace = m_dummyWorkspace;
+}
+
+void WindowManagementPolicy::advise_new_window(miral::WindowInfo const& window_info)
+{
+ qtmir::WindowManagementPolicy::advise_new_window(window_info);
+
+ auto const parent = window_info.parent();
+
+ auto activeWorkspace = m_activeWorkspace.lock();
+ if (!parent && activeWorkspace) {
+ tools.add_tree_to_workspace(window_info.window(), activeWorkspace);
+ }
+}
+
+std::shared_ptr WindowManagementPolicy::createWorkspace()
+{
+ auto workspace = tools.create_workspace();
+ m_workspaces.insert(workspace);
+
+ if (m_activeWorkspace.lock() == m_dummyWorkspace) {
+ tools.move_workspace_content_to_workspace(workspace, m_dummyWorkspace);
+ m_activeWorkspace = workspace;
+ }
+ return workspace;
+}
+
+void WindowManagementPolicy::releaseWorkspace(const std::shared_ptr &workspace)
+{
+ auto iter = m_workspaces.find(workspace);
+ if (iter != m_workspaces.end()) m_workspaces.erase(iter);
+
+ if (m_workspaces.size() == 0) {
+ m_activeWorkspace = m_dummyWorkspace;
+ tools.move_workspace_content_to_workspace(m_dummyWorkspace, workspace);
+ }
+}
+
+void WindowManagementPolicy::setActiveWorkspace(const std::shared_ptr &workspace)
+{
+ if (m_activeWorkspace.lock() == workspace)
+ return;
+ m_activeWorkspace = workspace ? workspace : m_dummyWorkspace;
+}
diff --git a/src/WindowManagementPolicy.h b/src/WindowManagementPolicy.h
new file mode 100644
index 0000000000..0241ad2914
--- /dev/null
+++ b/src/WindowManagementPolicy.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef UNITY_WINDOWMANAGEMENTPOLICY_H
+#define UNITY_WINDOWMANAGEMENTPOLICY_H
+
+#include
+#include "wmpolicyinterface.h"
+
+#include
+
+class Q_DECL_EXPORT WindowManagementPolicy : public qtmir::WindowManagementPolicy,
+ public WMPolicyInterface
+{
+public:
+ WindowManagementPolicy(const miral::WindowManagerTools &tools, std::shared_ptr dd);
+
+ void advise_new_window(miral::WindowInfo const& window_info) override;
+
+ // From WMPolicyInterface
+ std::shared_ptr createWorkspace() override;
+
+ void releaseWorkspace(const std::shared_ptr &workspace) override;
+
+ void setActiveWorkspace(const std::shared_ptr &workspace) override;
+
+private:
+ std::weak_ptr m_activeWorkspace;
+
+ std::unordered_set> m_workspaces;
+ const std::shared_ptr m_dummyWorkspace;
+};
+
+#endif // UNITY_WINDOWMANAGEMENTPOLICY_H
diff --git a/src/libunity8-private/CMakeLists.txt b/src/libunity8-private/CMakeLists.txt
index 5c2ee32945..9f276994d6 100644
--- a/src/libunity8-private/CMakeLists.txt
+++ b/src/libunity8-private/CMakeLists.txt
@@ -8,6 +8,7 @@ set(lib${LIB_NAME}_SRCS
abstractdbusservicemonitor.cpp
unitydbusobject.cpp
unitydbusvirtualobject.cpp
+ wmpolicyinterface.cpp
)
add_library(${LIB_NAME} SHARED
diff --git a/src/SecondaryWindow.h b/src/libunity8-private/wmpolicyinterface.cpp
similarity index 68%
rename from src/SecondaryWindow.h
rename to src/libunity8-private/wmpolicyinterface.cpp
index a5dd07c29f..b9606b3afe 100644
--- a/src/SecondaryWindow.h
+++ b/src/libunity8-private/wmpolicyinterface.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Canonical, Ltd.
+ * Copyright (C) 2017 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,17 +14,11 @@
* along with this program. If not, see .
*/
-#ifndef UNITY_SECONDARY_WINDOW_H
-#define UNITY_SECONDARY_WINDOW_H
+#include "wmpolicyinterface.h"
-#include
+WMPolicyInterface* wmPolicyInterface = nullptr;
-class SecondaryWindow : public QQuickView
+WMPolicyInterface *WMPolicyInterface::instance()
{
- Q_OBJECT
-
-public:
- SecondaryWindow(QQmlEngine *engine);
-};
-
-#endif // UNITY_SECONDARY_WINDOW_H
+ return wmPolicyInterface;
+}
diff --git a/src/libunity8-private/wmpolicyinterface.h b/src/libunity8-private/wmpolicyinterface.h
new file mode 100644
index 0000000000..f145f3a777
--- /dev/null
+++ b/src/libunity8-private/wmpolicyinterface.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef WMPOLICYINTERFACE_H
+#define WMPOLICYINTERFACE_H
+
+#include
+#include
+
+#include
+
+namespace miral {
+class Workspace;
+class Window;
+}
+
+class Q_DECL_EXPORT WMPolicyInterface
+{
+public:
+ virtual ~WMPolicyInterface() {}
+
+ static WMPolicyInterface *instance();
+
+ virtual std::shared_ptr createWorkspace() = 0;
+
+ virtual void releaseWorkspace(const std::shared_ptr &workspace) = 0;
+
+ virtual void setActiveWorkspace(const std::shared_ptr &workspace) = 0;
+};
+
+extern Q_DECL_EXPORT WMPolicyInterface* wmPolicyInterface;
+
+#endif // WMPOLICYINTERFACE_H
diff --git a/src/main.cpp b/src/main.cpp
index 34d9518061..f77bc33694 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -15,9 +15,10 @@
*/
// local
-#include "ShellApplication.h"
+#include "UnityApplication.h"
#include "qmldebuggerutils.h"
#include "UnixSignalHandler.h"
+#include
#include
#include
@@ -45,7 +46,8 @@ int main(int argc, const char *argv[])
QQmlDebuggingEnabler qQmlEnableDebuggingHelper(true);
}
- ShellApplication *application = new ShellApplication(argc, (char**)argv, isMirServer);
+ auto *application = new UnityApplication(argc,
+ (char**)argv);
UnixSignalHandler signalHandler([]{
QGuiApplication::exit(0);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 899747ccee..f92689f00b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -47,6 +47,7 @@ macro(unity8_parse_arguments)
list(APPEND ld_paths
${UNITY_MOCKPATH}/liblightdm
${UNITY_MOCKPATH}/libusermetrics
+ ${UNITY_MOCKPATH}/WindowManager
)
string(REPLACE ";" ":" ld_library_path "${ld_paths}")
diff --git a/tests/mocks/CMakeLists.txt b/tests/mocks/CMakeLists.txt
index 69b2619298..cf8a54f205 100644
--- a/tests/mocks/CMakeLists.txt
+++ b/tests/mocks/CMakeLists.txt
@@ -43,6 +43,7 @@ add_subdirectory(SessionBroadcast)
add_subdirectory(Ubuntu)
add_subdirectory(Unity)
add_subdirectory(QtMultimedia)
+add_subdirectory(WindowManager)
add_subdirectory(Wizard)
add_subdirectory(Utils)
add_subdirectory(UInput)
diff --git a/tests/mocks/Cursor/Cursor.qml b/tests/mocks/Cursor/Cursor.qml
index b69bc81c9c..134ae7ddd8 100644
--- a/tests/mocks/Cursor/Cursor.qml
+++ b/tests/mocks/Cursor/Cursor.qml
@@ -15,8 +15,11 @@
*/
import QtQuick 2.4
+import UInput 0.1
+
+Canvas {
+ id: root
-Item {
property int topBoundaryOffset // effectively panel height
property Item confiningItem
@@ -30,5 +33,49 @@ Item {
signal pushStopped()
signal mouseMoved()
- onMouseMoved: opacity = 1;
+ width: units.gu(2)
+ height: units.gu(2)
+ antialiasing: true
+
+ onPaint: {
+ var ctx = getContext("2d");
+ ctx.save();
+ ctx.clearRect(0,0,width, height);
+ ctx.strokeStyle = "#000000";
+ ctx.lineWidth = 1
+ ctx.fillStyle = "#ffffff";
+ ctx.globalAlpha = 1.0;
+ ctx.lineJoin = "round";
+ ctx.beginPath();
+
+ // put rectangle in the middle
+ // draw the rectangle
+ ctx.moveTo(width/2,height);
+ ctx.lineTo(width, height/2);
+ ctx.lineTo(0,0);
+
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+ ctx.restore();
+ }
+
+ Connections {
+ target: UInput
+ onMouseMoved: {
+ var newX = root.x;
+ newX += dx;
+ if (newX < 0) newX = 0;
+ else if (newX >= parent.width) newX = parent.width-1;
+
+ var newY = root.y;
+ newY += dy;
+ if (newY < 0) newY = 0;
+ else if (newY >= parent.height) newY = parent.height-1;
+
+ root.x = newX;
+ root.y = newY;
+ root.mouseMoved();
+ }
+ }
}
diff --git a/tests/mocks/GSettings.1.0/plugin.cpp b/tests/mocks/GSettings.1.0/plugin.cpp
index 87e5e1e636..b5f6cd21be 100644
--- a/tests/mocks/GSettings.1.0/plugin.cpp
+++ b/tests/mocks/GSettings.1.0/plugin.cpp
@@ -18,6 +18,8 @@
#include "fake_gsettings.h"
#include
+#include
+#include
static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
qmlRegisterUncreatableType(uri, 1, 0, "GSettingsSchema",
"GSettingsSchema can only be used inside of a GSettings component");
}
+
+void FakeGSettingsQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
+{
+ QQmlExtensionPlugin::initializeEngine(engine, uri);
+
+ QString usageType = qgetenv("UNITY_MOCK_DESKTOP");
+ if (!usageType.isEmpty()) {
+ GSettingsControllerQml::instance()->setUsageMode("Windowed");
+ }
+}
diff --git a/tests/mocks/GSettings.1.0/plugin.h b/tests/mocks/GSettings.1.0/plugin.h
index c9e0f77dfa..619580e645 100644
--- a/tests/mocks/GSettings.1.0/plugin.h
+++ b/tests/mocks/GSettings.1.0/plugin.h
@@ -26,6 +26,7 @@ class FakeGSettingsQmlPlugin : public QQmlExtensionPlugin
public:
void registerTypes(const char *uri) override;
+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
};
#endif // PLUGIN_H
diff --git a/tests/mocks/UInput/plugin.cpp b/tests/mocks/UInput/plugin.cpp
index 8fd3029737..ea448a892a 100644
--- a/tests/mocks/UInput/plugin.cpp
+++ b/tests/mocks/UInput/plugin.cpp
@@ -19,8 +19,13 @@
#include
+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
+{
+ return new MockUInput;
+}
+
void MockUInputPlugin::registerTypes(const char *uri)
{
Q_ASSERT(uri == QLatin1String("UInput"));
- qmlRegisterType(uri, 0, 1, "UInput");
+ qmlRegisterSingletonType(uri, 0, 1, "UInput", uinputSingleton);
}
diff --git a/tests/mocks/Unity/Application/CMakeLists.txt b/tests/mocks/Unity/Application/CMakeLists.txt
index adcf4b2958..0b9b7e2501 100644
--- a/tests/mocks/Unity/Application/CMakeLists.txt
+++ b/tests/mocks/Unity/Application/CMakeLists.txt
@@ -1,3 +1,12 @@
+pkg_check_modules(MIRTEST mirtest>=0.26 REQUIRED)
+
+include_directories(
+ SYSTEM
+ ${MIRTEST_INCLUDE_DIRS}
+ ${MIRAL_INCLUDE_DIRS}
+ ${libunity8-private_SOURCE_DIR}
+)
+
set(FakeUnityApplicationQml_SOURCES
plugin.cpp
ApplicationInfo.cpp
@@ -19,13 +28,29 @@ set(FakeUnityApplicationQml_SOURCES
resources/surfaces.qrc
)
-add_library(FakeUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
+add_library(FakeUnityApplicationQml MODULE
+ ${FakeUnityApplicationQml_SOURCES}
+)
+
+#add_dependencies(FakeUnityApplicationQml windowmanagementpolicy)
+
+target_link_libraries(FakeUnityApplicationQml
+ ${MIRTEST_LDFLAGS}
+ ${MIRAL_LDFLAGS}
+ mockwindowmanagmentpolicy
+)
add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
+target_link_libraries(NonMirUnityApplicationQml
+ ${MIRTEST_LDFLAGS}
+ ${MIRAL_LDFLAGS}
+ mockwindowmanagmentpolicy
+ Qt5::Core Qt5::Quick Qt5::DBus
+)
+
target_link_libraries(FakeUnityApplicationQml Qt5::Core Qt5::Quick Qt5::DBus)
-target_link_libraries(NonMirUnityApplicationQml Qt5::Core Qt5::Quick Qt5::DBus)
add_unity8_mock(Unity.Application 0.1 Unity/Application TARGETS FakeUnityApplicationQml)
add_unity8_mock(Unity.Application 0.1 Unity/Application
diff --git a/tests/mocks/Unity/Application/MirSurfaceItem.cpp b/tests/mocks/Unity/Application/MirSurfaceItem.cpp
index b9e965da05..0f5272145d 100644
--- a/tests/mocks/Unity/Application/MirSurfaceItem.cpp
+++ b/tests/mocks/Unity/Application/MirSurfaceItem.cpp
@@ -189,11 +189,13 @@ void MirSurfaceItem::createQmlContentItem()
m_qmlItem = qobject_cast(m_qmlContentComponent->create());
m_qmlItem->setParentItem(this);
- m_qmlItem->setWidth(m_surfaceWidth);
- m_qmlItem->setHeight(m_surfaceHeight);
-
- setImplicitWidth(m_qmlItem->implicitWidth());
- setImplicitHeight(m_qmlItem->implicitHeight());
+ if (m_fillMode == FillMode::Stretch && width() != 0 && height() != 0) {
+ m_qmlItem->setSize(QSize(this->width(), this->height()));
+ } else {
+ m_qmlItem->setSize(m_qmlSurface->size());
+ }
+ setImplicitWidth(m_qmlItem->width());
+ setImplicitHeight(m_qmlItem->height());
{
QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
@@ -306,8 +308,23 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
}
connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
- connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
+ connect(m_qmlSurface, &MirSurface::liveChanged, this, [this] (bool live) {
+ if (!live) {
+ setSurface(nullptr);
+ }
+ Q_EMIT liveChanged(live);
+ });
connect(m_qmlSurface, &MirSurface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
+ connect(m_qmlSurface, &MirSurface::sizeChanged, this, [this] () {
+ setImplicitSize(m_qmlSurface->width(), m_qmlSurface->height());
+ if (m_fillMode == FillMode::Stretch) {
+ m_qmlItem->setSize(QSize(this->width(), this->height()));
+ } else {
+ m_qmlItem->setSize(m_qmlSurface->size());
+ }
+ });
+ m_surfaceWidth = surface->size().width();
+ m_surfaceHeight = surface->size().height();
QUrl qmlComponentFilePath;
if (!m_qmlSurface->qmlFilePath().isEmpty()) {
@@ -321,6 +338,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
switch (m_qmlContentComponent->status()) {
case QQmlComponent::Ready:
createQmlContentItem();
+ qDebug() << "content created" << m_surfaceWidth << implicitWidth() << width();
break;
case QQmlComponent::Loading:
connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
@@ -399,9 +417,15 @@ void MirSurfaceItem::updateSurfaceSize()
if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
if (m_qmlItem) {
- m_qmlItem->setWidth(m_surfaceWidth);
- m_qmlItem->setHeight(m_surfaceHeight);
+ if (m_fillMode == FillMode::Stretch) {
+ m_qmlItem->setWidth(width());
+ m_qmlItem->setHeight(height());
+ } else {
+ m_qmlItem->setWidth(m_surfaceWidth);
+ m_qmlItem->setHeight(m_surfaceHeight);
+ }
}
+ qDebug() << this << "setting implicitsize" << m_surfaceWidth << m_surfaceHeight;
setImplicitSize(m_surfaceWidth, m_surfaceHeight);
}
}
diff --git a/tests/mocks/Unity/Application/SurfaceManager.cpp b/tests/mocks/Unity/Application/SurfaceManager.cpp
index 90d46bef70..0ae4de8ba8 100644
--- a/tests/mocks/Unity/Application/SurfaceManager.cpp
+++ b/tests/mocks/Unity/Application/SurfaceManager.cpp
@@ -18,10 +18,12 @@
#include "ApplicationInfo.h"
#include "VirtualKeyboard.h"
+#include "../../WindowManager/WindowManagementPolicy.h"
#include
+#include
-#define SURFACEMANAGER_DEBUG 0
+#define SURFACEMANAGER_DEBUG 1
#if SURFACEMANAGER_DEBUG
#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
@@ -31,6 +33,12 @@
namespace unityapi = unity::shell::application;
+uint qHash(const WindowWrapper &key, uint)
+{
+ std::shared_ptr surface = key.window;
+ return (quintptr)surface.get();
+}
+
SurfaceManager *SurfaceManager::m_instance = nullptr;
SurfaceManager *SurfaceManager::instance()
@@ -44,20 +52,63 @@ SurfaceManager::SurfaceManager(QObject *)
Q_ASSERT(m_instance == nullptr);
m_instance = this;
+
+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowAdded,
+ this, [this](const miral::Window& window) {
+ Q_EMIT surfaceCreated(surfaceFor(window));
+ });
+
+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowsAddedToWorkspace,
+ this, [this](const std::shared_ptr &workspace, const std::vector &windows) {
+ Q_EMIT surfacesAddedToWorkspace(workspace, surfacesFor(windows));
+ });
+
+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowsAboutToBeRemovedFromWorkspace,
+ this, [this](const std::shared_ptr &workspace, const std::vector &windows) {
+ Q_EMIT surfacesAboutToBeRemovedFromWorkspace(workspace, surfacesFor(windows));
+ });
}
SurfaceManager::~SurfaceManager()
{
DEBUG_MSG("");
- if (m_virtualKeyboard) {
- m_virtualKeyboard->setLive(false);
- }
+ releaseInputMethodSurface();
Q_ASSERT(m_instance == this);
m_instance = nullptr;
}
+MirSurfaceInterface *SurfaceManager::surfaceFor(const miral::Window& window) const
+{
+ auto iter = m_windowToSurface.find({window});
+ if (iter != m_windowToSurface.end()) {
+ return *iter;
+ }
+ return nullptr;
+}
+
+QVector SurfaceManager::surfacesFor(const std::vector &windows) const
+{
+ QVector surfaces;
+ for (size_t i = 0; i < windows.size(); i++) {
+ auto mirSurface = surfaceFor(windows[i]);
+ if (mirSurface) {
+ surfaces.push_back(mirSurface);
+ }
+ }
+ return surfaces;
+}
+
+miral::Window SurfaceManager::windowFor(MirSurfaceInterface *surface) const
+{
+ auto iter = m_surfaceToWindow.find(surface);
+ if (iter != m_surfaceToWindow.end()) {
+ return iter->window;
+ }
+ return miral::Window();
+}
+
MirSurface *SurfaceManager::createSurface(const QString& name,
Mir::Type type,
Mir::State state,
@@ -75,7 +126,12 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
void SurfaceManager::registerSurface(MirSurface *surface)
{
+ auto fakeSurface = std::make_shared();
+ WindowWrapper window{miral::Window(nullptr, fakeSurface), fakeSurface};
+
m_surfaces.prepend(surface);
+ m_windowToSurface.insert(window, surface);
+ m_surfaceToWindow.insert(surface, window);
if (!surface->parentSurface()) {
surface->setMinimumWidth(m_newSurfaceMinimumWidth);
@@ -90,16 +146,24 @@ void SurfaceManager::registerSurface(MirSurface *surface)
this->onStateRequested(surface, state);
});
- const QString persistentId = surface->persistentId();
connect(surface, &QObject::destroyed, this, [=]() {
- this->onSurfaceDestroyed(surface, persistentId);
+ auto iter = m_surfaceToWindow.find(surface);
+ if (iter != m_surfaceToWindow.end()) {
+ WindowWrapper key = m_surfaceToWindow.value(surface);
+ WindowManagementPolicy::instance()->removeWindow(key.window);
+ this->onSurfaceDestroyed(surface);
+ }
});
-
}
void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
{
- Q_EMIT surfaceCreated(surface);
+ if (Q_UNLIKELY(!m_surfaceToWindow.contains(surface))) {
+ Q_EMIT surfaceCreated(surface);
+ return;
+ }
+
+ WindowManagementPolicy::instance()->addWindow(m_surfaceToWindow[surface].window);
}
void SurfaceManager::setNewSurfaceMinimumWidth(int value)
@@ -209,6 +273,32 @@ void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
DEBUG_MSG("("< &workspace,
+ const std::function &callback)
+{
+ WindowManagementPolicy::instance()->forEachWindowInWorkspace(workspace, [&](const miral::Window &window) {
+ auto surface = surfaceFor(window);
+ if (surface) {
+ callback(surface);
+ }
+ });
+}
+
+void SurfaceManager::moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface* surface,
+ const std::shared_ptr