on xenial help sign in
introduce GlobalShortcut component
commit6657af0a53186c4731f94acddb3588c164d57231 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateMon 24 Aug 2015, 16:39 (11 years ago) parenta8f7a60
30 files changed+812−164
plugins/CMakeLists.txt+1 plugins/GlobalShortcut/CMakeLists.txt+10 plugins/GlobalShortcut/globalshortcut.cpp+80 plugins/GlobalShortcut/globalshortcut.h+76 plugins/GlobalShortcut/globalshortcutregistry.cpp+95 plugins/GlobalShortcut/globalshortcutregistry.h+67 plugins/GlobalShortcut/plugin.cpp+27 plugins/GlobalShortcut/plugin.h+32 plugins/GlobalShortcut/qmldir+2 plugins/Unity/Session/Session.qmltypes+34 plugins/Unity/Session/dbusunitysessionservice.cpp+38−2 plugins/Utils/Utils.qmltypes+1−1 plugins/Utils/easingcurve.h+2−2 po/unity8.pot+83−73 qml/Components/Dialogs.qml+61−6 qml/Components/PhysicalKeysMapper.qml+7−8 qml/Components/ScreenGrabber.qml+7 qml/Components/ShellDialog.qml+7−1 qml/Components/VolumeControl.qml+20−6 qml/OrientedShell.qml+1−5 qml/Panel/Indicators/MessageMenuItemFactory.qml+2−2 qml/Shell.qml+13−4 tests/mocks/Utils/windowstatestorage.cpp−2 tests/mocks/Utils/windowstatestorage.h+1 tests/plugins/CMakeLists.txt+1 tests/plugins/GlobalShortcut/CMakeLists.txt+14 tests/plugins/GlobalShortcut/GlobalShortcutTest.cpp+77 tests/plugins/GlobalShortcut/shortcut.qml+39 tests/qmltests/Components/tst_Dialogs.qml+13−8 tests/qmltests/Components/tst_PhysicalKeysMapper.qml+1−44
plugins/CMakeLists.txt+1
@@ -12,6 +12,7 @@ macro(add_unity8_plugin PLUGIN VERSION PATH)
1212endmacro()
1313
1414add_subdirectory(AccountsService)
15+add_subdirectory(GlobalShortcut)
1516add_subdirectory(Greeter)
1617add_subdirectory(LightDM)
1718add_subdirectory(Lights)
plugins/GlobalShortcut/CMakeLists.txtnew file+10
@@ -0,0 +1,10 @@
1+set(globalshortcutplugin_SRCS
2+ globalshortcut.cpp
3+ globalshortcutregistry.cpp
4+ plugin.cpp)
5+
6+add_library(GlobalShortcut-qml SHARED ${globalshortcutplugin_SRCS})
7+
8+qt5_use_modules(GlobalShortcut-qml Quick Gui)
9+
10+add_unity8_plugin(GlobalShortcut 1.0 GlobalShortcut TARGETS GlobalShortcut-qml)
plugins/GlobalShortcut/globalshortcut.cppnew file+80
@@ -0,0 +1,80 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "globalshortcut.h"
18+#include "globalshortcutregistry.h"
19+
20+#include <QDebug>
21+#include <QQuickItem>
22+
23+Q_GLOBAL_STATIC(GlobalShortcutRegistry, registry)
24+
25+GlobalShortcut::GlobalShortcut(QQuickItem *parent)
26+ : QQuickItem(parent)
27+{
28+}
29+
30+QVariant GlobalShortcut::shortcut() const
31+{
32+ return m_shortcut;
33+}
34+
35+void GlobalShortcut::setShortcut(const QVariant &shortcut)
36+{
37+ if (m_shortcut == shortcut)
38+ return;
39+
40+ m_shortcut = shortcut;
41+ registry->addShortcut(shortcut, this);
42+ Q_EMIT shortcutChanged(shortcut);
43+}
44+
45+bool GlobalShortcut::isActive() const
46+{
47+ return m_active;
48+}
49+
50+void GlobalShortcut::setActive(bool active)
51+{
52+ if (m_active == active)
53+ return;
54+
55+ m_active = active;
56+ Q_EMIT activeChanged(active);
57+}
58+
59+void GlobalShortcut::componentComplete()
60+{
61+ connect(this, &QQuickItem::windowChanged, this, &GlobalShortcut::setupFilterOnWindow);
62+}
63+
64+void GlobalShortcut::keyPressEvent(QKeyEvent * event)
65+{
66+ Q_UNUSED(event)
67+ if (m_active) {
68+ Q_EMIT triggered(m_shortcut.toString());
69+ }
70+}
71+
72+void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
73+{
74+ if (!window) {
75+ qWarning() << Q_FUNC_INFO << "Failed to setup filter on window";
76+ return;
77+ }
78+
79+ registry->setupFilterOnWindow((qulonglong) window->winId());
80+}
plugins/GlobalShortcut/globalshortcut.hnew file+76
@@ -0,0 +1,76 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef GLOBALSHORTCUT_H
18+#define GLOBALSHORTCUT_H
19+
20+#include <QVariant>
21+#include <QQuickWindow>
22+#include <QQuickItem>
23+
24+/**
25+ * @brief The GlobalShortcut class
26+ *
27+ * QML component for registering a shortcut; the shortcut itself can be
28+ * specified either as a string ("Ctrl+Alt+L") or as an enum value
29+ * (Qt.ControlModifier|Qt.AltModifier|Qt.Key_L).
30+ *
31+ * When the shortcut is detected, the signal triggered() gets emitted.
32+ */
33+class GlobalShortcut: public QQuickItem
34+{
35+ Q_OBJECT
36+ /**
37+ * The shortcut itself
38+ */
39+ Q_PROPERTY(QVariant shortcut READ shortcut WRITE setShortcut NOTIFY shortcutChanged)
40+ /**
41+ * Whether the shortcut is active (true by default)
42+ */
43+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
44+
45+public:
46+ GlobalShortcut(QQuickItem *parent = nullptr);
47+ ~GlobalShortcut() = default;
48+
49+ QVariant shortcut() const;
50+ void setShortcut(const QVariant &shortcut);
51+
52+ bool isActive() const;
53+ void setActive(bool active);
54+
55+protected:
56+ void componentComplete() override;
57+ void keyPressEvent(QKeyEvent * event) override;
58+
59+Q_SIGNALS:
60+ void shortcutChanged(const QVariant &shortcut);
61+ /**
62+ * Emitted when a global keypress of @p shortcut is detected
63+ */
64+ void triggered(const QString &shortcut);
65+ void activeChanged(bool active);
66+
67+private Q_SLOTS:
68+ void setupFilterOnWindow(QQuickWindow* window);
69+
70+private:
71+ QVariant m_shortcut;
72+ bool m_active = true;
73+};
74+
75+
76+#endif // GLOBALSHORTCUT_H
plugins/GlobalShortcut/globalshortcutregistry.cppnew file+95
@@ -0,0 +1,95 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDebug>
18+#include <QGuiApplication>
19+#include <QKeyEvent>
20+#include <QKeySequence>
21+
22+#include "globalshortcutregistry.h"
23+
24+static qulonglong s_windowId = 0;
25+
26+GlobalShortcutRegistry::GlobalShortcutRegistry(QObject *parent)
27+ : QObject(parent)
28+{
29+}
30+
31+GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
32+{
33+ return m_shortcuts;
34+}
35+
36+bool GlobalShortcutRegistry::hasShortcut(const QVariant &seq) const
37+{
38+ return m_shortcuts.keys().contains(seq);
39+}
40+
41+void GlobalShortcutRegistry::addShortcut(const QVariant &seq, GlobalShortcut *sc)
42+{
43+ if (!m_shortcuts.keys().contains(seq)) { // create a new entry
44+ m_shortcuts.insert(seq, {sc});
45+ } else { // append to an existing one
46+ auto shortcuts = m_shortcuts[seq];
47+ shortcuts.append(sc);
48+ m_shortcuts.insert(seq, shortcuts);
49+ }
50+}
51+
52+bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
53+{
54+ Q_ASSERT(m_filteredWindow);
55+ Q_ASSERT(obj == static_cast<QObject*>(m_filteredWindow.data()));
56+
57+ if (event->type() == QEvent::KeyPress) {
58+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
59+
60+ QKeySequence seq = QKeySequence(keyEvent->key() + keyEvent->modifiers());
61+ if (m_shortcuts.keys().contains(seq)) {
62+ const auto shortcuts = m_shortcuts.value(seq);
63+ Q_FOREACH(auto shortcut, shortcuts) {
64+ qApp->sendEvent(shortcut, keyEvent);
65+ }
66+ event->accept();
67+ }
68+
69+ return event->isAccepted();
70+ }
71+
72+ return QObject::eventFilter(obj, event);
73+}
74+
75+void GlobalShortcutRegistry::setupFilterOnWindow(qulonglong wid)
76+{
77+ if (wid == s_windowId) {
78+ return;
79+ }
80+
81+ if (m_filteredWindow) {
82+ m_filteredWindow->removeEventFilter(this);
83+ m_filteredWindow.clear();
84+ s_windowId = 0;
85+ }
86+
87+ for(QWindow *window: qApp->allWindows()) {
88+ if (window && window->winId() == wid) {
89+ m_filteredWindow = window;
90+ window->installEventFilter(this);
91+ s_windowId = wid;
92+ break;
93+ }
94+ }
95+}
plugins/GlobalShortcut/globalshortcutregistry.hnew file+67
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef GLOBALSHORTCUT_REGISTRY_H
18+#define GLOBALSHORTCUT_REGISTRY_H
19+
20+#include <QObject>
21+#include <QVariantList>
22+#include <QPointer>
23+#include <QWindow>
24+
25+#include "globalshortcut.h"
26+
27+typedef QMap<QVariant, QVector<GlobalShortcut *>> GlobalShortcutList;
28+
29+/**
30+ * @brief The GlobalShortcutRegistry class
31+ *
32+ * Serves as a central point for shortcut registration.
33+ */
34+class Q_DECL_EXPORT GlobalShortcutRegistry: public QObject
35+{
36+ Q_OBJECT
37+public:
38+ GlobalShortcutRegistry(QObject * parent = nullptr);
39+ ~GlobalShortcutRegistry() = default;
40+
41+ /**
42+ * @return the list of shortcuts currently registered
43+ */
44+ GlobalShortcutList shortcuts() const;
45+ /**
46+ * @return whether shortcut @p seq is currently registered
47+ */
48+ bool hasShortcut(const QVariant &seq) const;
49+ /**
50+ * Adds a shortcut @p seq to the registry
51+ */
52+ void addShortcut(const QVariant &seq, GlobalShortcut * sc);
53+
54+ /**
55+ * Sets up key events filtering on window @p wid
56+ */
57+ void setupFilterOnWindow(qulonglong wid);
58+
59+protected:
60+ bool eventFilter(QObject *obj, QEvent *event) override;
61+
62+private:
63+ GlobalShortcutList m_shortcuts;
64+ QPointer<QWindow> m_filteredWindow = nullptr;
65+};
66+
67+#endif
plugins/GlobalShortcut/plugin.cppnew file+27
@@ -0,0 +1,27 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "plugin.h"
18+#include "globalshortcut.h"
19+
20+#include <QtQml>
21+
22+void GlobalShortcutPlugin::registerTypes(const char *uri)
23+{
24+ Q_ASSERT(uri == QLatin1String("GlobalShortcut"));
25+
26+ qmlRegisterType<GlobalShortcut>(uri, 1, 0, "GlobalShortcut");
27+}
plugins/GlobalShortcut/plugin.hnew file+32
@@ -0,0 +1,32 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef GLOBALSHORTCUTPLUGIN_H
18+#define GLOBALSHORTCUTPLUGIN_H
19+
20+#include <QQmlExtensionPlugin>
21+
22+class GlobalShortcutPlugin: public QQmlExtensionPlugin
23+{
24+ Q_OBJECT
25+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
26+
27+public:
28+ void registerTypes(const char *uri) override;
29+};
30+
31+
32+#endif // GLOBALSHORTCUTPLUGIN_H
plugins/GlobalShortcut/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module GlobalShortcut
2+plugin GlobalShortcut-qml
plugins/Unity/Session/Session.qmltypes+34
@@ -14,28 +14,62 @@ Module {
1414 isCreatable: false
1515 isSingleton: true
1616 exportMetaObjectRevisions: [0]
17+ Signal {
18+ name: "LogoutRequested"
19+ Parameter { name: "have_inhibitors"; type: "bool" }
20+ }
1721 Signal {
1822 name: "logoutRequested"
1923 Parameter { name: "have_inhibitors"; type: "bool" }
2024 }
25+ Signal {
26+ name: "RebootRequested"
27+ Parameter { name: "have_inhibitors"; type: "bool" }
28+ }
2129 Signal {
2230 name: "rebootRequested"
2331 Parameter { name: "have_inhibitors"; type: "bool" }
2432 }
33+ Signal {
34+ name: "ShutdownRequested"
35+ Parameter { name: "have_inhibitors"; type: "bool" }
36+ }
2537 Signal {
2638 name: "shutdownRequested"
2739 Parameter { name: "have_inhibitors"; type: "bool" }
2840 }
41+ Signal { name: "LogoutReady" }
2942 Signal { name: "logoutReady" }
43+ Signal { name: "LockRequested" }
44+ Signal { name: "lockRequested" }
45+ Signal { name: "Locked" }
46+ Signal { name: "Unlocked" }
3047 Method { name: "Logout" }
3148 Method { name: "Reboot" }
3249 Method { name: "Shutdown" }
50+ Method { name: "Suspend" }
51+ Method { name: "Hibernate" }
52+ Method { name: "HybridSleep" }
3353 Method { name: "RequestLogout" }
3454 Method { name: "RequestReboot" }
3555 Method { name: "RequestShutdown" }
56+ Method { name: "EndSession" }
57+ Method { name: "CanHibernate"; type: "bool" }
58+ Method { name: "CanSuspend"; type: "bool" }
59+ Method { name: "CanHybridSleep"; type: "bool" }
60+ Method { name: "CanReboot"; type: "bool" }
61+ Method { name: "CanShutdown"; type: "bool" }
62+ Method { name: "CanLock"; type: "bool" }
63+ Method { name: "UserName"; type: "string" }
64+ Method { name: "RealName"; type: "string" }
65+ Method { name: "HostName"; type: "string" }
66+ Method { name: "PromptLock" }
67+ Method { name: "Lock" }
68+ Method { name: "IsLocked"; type: "bool" }
3669 Method { name: "logout" }
3770 Method { name: "reboot" }
3871 Method { name: "shutdown" }
72+ Method { name: "endSession" }
3973 }
4074 Component {
4175 name: "OrientationLock"
plugins/Unity/Session/dbusunitysessionservice.cpp+38−2
@@ -28,6 +28,7 @@
2828#include <QDBusReply>
2929#include <QElapsedTimer>
3030#include <QDateTime>
31+#include <QDBusUnixFileDescriptor>
3132
3233#define LOGIN1_SERVICE QStringLiteral("org.freedesktop.login1")
3334#define LOGIN1_PATH QStringLiteral("/org/freedesktop/login1")
@@ -44,6 +45,7 @@ public:
4445 QString logindSessionPath;
4546 bool isSessionActive = true;
4647 QElapsedTimer screensaverActiveTimer;
48+ QDBusUnixFileDescriptor m_systemdInhibitFd;
4749
4850 DBusUnitySessionServicePrivate(): QObject() {
4951 init();
@@ -66,11 +68,40 @@ public:
6668 // start watching the Active property
6769 QDBusConnection::systemBus().connect(LOGIN1_SERVICE, logindSessionPath, "org.freedesktop.DBus.Properties", "PropertiesChanged",
6870 this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
71+
72+ setupSystemdInhibition();
73+
74+ // re-enable the inhibition upon resume from sleep
75+ QDBusConnection::systemBus().connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, "PrepareForSleep",
76+ this, SLOT(onResuming(bool)));
6977 } else {
7078 qWarning() << "Failed to get logind session path" << reply.error().message();
7179 }
7280 }
7381
82+ void setupSystemdInhibition()
83+ {
84+ if (m_systemdInhibitFd.isValid())
85+ return;
86+
87+ // inhibit systemd handling of power/sleep/lid buttons
88+ // http://www.freedesktop.org/wiki/Software/systemd/inhibit
89+
90+ QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, "Inhibit");
91+ msg << "handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch"; // what
92+ msg << "Unity"; // who
93+ msg << "Unity8 handles power events"; // why
94+ msg << "block"; // mode
95+ QDBusReply<QDBusUnixFileDescriptor> desc = QDBusConnection::systemBus().asyncCall(msg);
96+
97+ if (desc.isValid()) {
98+ m_systemdInhibitFd = desc.value();
99+ } else {
100+ qWarning() << "failed to inhibit systemd powersave handling";
101+ }
102+ }
103+
104+
74105 bool checkLogin1Call(const QString &method) const
75106 {
76107 QDBusMessage msg = QDBusMessage::createMethodCall(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_IFACE, method);
@@ -105,7 +136,6 @@ public:
105136 QDBusReply<QVariant> reply = QDBusConnection::systemBus().asyncCall(msg);
106137 if (reply.isValid()) {
107138 isSessionActive = reply.value().toBool();
108− qDebug() << "Session" << logindSessionPath << "is active:" << isSessionActive;
109139 } else {
110140 qWarning() << "Failed to get Active property" << reply.error().message();
111141 }
@@ -173,6 +203,13 @@ private Q_SLOTS:
173203 }
174204 }
175205
206+ void onResuming(bool active)
207+ {
208+ if (!active) {
209+ setupSystemdInhibition();
210+ }
211+ }
212+
176213Q_SIGNALS:
177214 void screensaverActiveChanged(bool active);
178215};
@@ -287,7 +324,6 @@ void DBusUnitySessionService::Lock()
287324 sessionPath,
288325 "org.freedesktop.DisplayManager.Session",
289326 "Lock");
290− qDebug() << "Locking session" << msg.path();
291327 QDBusReply<void> reply = QDBusConnection::systemBus().asyncCall(msg);
292328 if (!reply.isValid()) {
293329 qWarning() << "Lock call failed" << reply.error().message();
plugins/Utils/Utils.qmltypes+1−1
@@ -145,7 +145,7 @@ Module {
145145 type: "int"
146146 Parameter { name: "row"; type: "int" }
147147 }
148− }
148+ }
149149 Component {
150150 name: "WindowKeysFilter"
151151 defaultProperty: "data"
plugins/Utils/easingcurve.h+2−2
@@ -28,8 +28,8 @@
2828 * This class exposes the QEasingCurve C++ API to QML.
2929 * This is useful for user interactive animations. While the QML Animation types
3030 * all require a "from", "to" and "duration", this one is based on "period" and
31− * "progress". So you can control the position of the aimation by changing the
32− * progress, also going back and forward in the animation. Depending on the type
31+ * "progress". So you can control the position of the animation by changing the
32+ * progress, also going back and forward in the aimation. Depending on the type
3333 * of the easing curve, value will return the transformed progress.
3434 */
3535
po/unity8.pot+83−73
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2015-07-17 10:10+0200\n"
11+"POT-Creation-Date: 2015-08-06 14:55+0200\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -22,12 +22,16 @@ msgstr ""
2222msgid "Password: "
2323msgstr ""
2424
25−#: plugins/Unity/Launcher/launcheritem.cpp:45
26−#: plugins/Unity/Launcher/launcheritem.cpp:100
25+#: plugins/Unity/Launcher/launcheritem.cpp:47
26+#: plugins/Unity/Launcher/launcheritem.cpp:106
2727msgid "Pin shortcut"
2828msgstr ""
2929
30−#: plugins/Unity/Launcher/launcheritem.cpp:100
30+#: plugins/Unity/Launcher/launcheritem.cpp:52
31+msgid "Quit"
32+msgstr ""
33+
34+#: plugins/Unity/Launcher/launcheritem.cpp:106
3135msgid "Unpin shortcut"
3236msgstr ""
3337
@@ -130,83 +134,83 @@ msgid_plural "Please wait %1 minutes and then try again…"
130134msgstr[0] ""
131135msgstr[1] ""
132136
133−#: qml/Components/Dialogs.qml:69
137+#: qml/Components/Dialogs.qml:112
134138msgctxt "Title: Lock/Log out dialog"
135139msgid "Log out"
136140msgstr ""
137141
138−#: qml/Components/Dialogs.qml:70
142+#: qml/Components/Dialogs.qml:113
139143msgid "Are you sure you want to log out?"
140144msgstr ""
141145
142−#: qml/Components/Dialogs.qml:72
146+#: qml/Components/Dialogs.qml:115
143147msgctxt "Button: Lock the system"
144148msgid "Lock"
145149msgstr ""
146150
147−#: qml/Components/Dialogs.qml:79
151+#: qml/Components/Dialogs.qml:122
148152msgctxt "Button: Log out from the system"
149153msgid "Log Out"
150154msgstr ""
151155
152−#: qml/Components/Dialogs.qml:86 qml/Components/Dialogs.qml:117
153−#: qml/Components/Dialogs.qml:173
156+#: qml/Components/Dialogs.qml:129 qml/Components/Dialogs.qml:162
157+#: qml/Components/Dialogs.qml:221
154158msgid "Cancel"
155159msgstr ""
156160
157−#: qml/Components/Dialogs.qml:98
161+#: qml/Components/Dialogs.qml:141
158162msgctxt "Title: Reboot/Shut down dialog"
159163msgid "Shut down"
160164msgstr ""
161165
162−#: qml/Components/Dialogs.qml:99
166+#: qml/Components/Dialogs.qml:142
163167msgid "Are you sure you want to shut down?"
164168msgstr ""
165169
166−#: qml/Components/Dialogs.qml:101
170+#: qml/Components/Dialogs.qml:144
167171msgctxt "Button: Reboot the system"
168172msgid "Reboot"
169173msgstr ""
170174
171−#: qml/Components/Dialogs.qml:109
175+#: qml/Components/Dialogs.qml:153
172176msgctxt "Button: Shut down the system"
173177msgid "Shut down"
174178msgstr ""
175179
176−#: qml/Components/Dialogs.qml:129
180+#: qml/Components/Dialogs.qml:175
177181msgctxt "Title: Reboot dialog"
178182msgid "Reboot"
179183msgstr ""
180184
181−#: qml/Components/Dialogs.qml:130
185+#: qml/Components/Dialogs.qml:176
182186msgid "Are you sure you want to reboot?"
183187msgstr ""
184188
185−#: qml/Components/Dialogs.qml:132
189+#: qml/Components/Dialogs.qml:178
186190msgid "No"
187191msgstr ""
188192
189−#: qml/Components/Dialogs.qml:138
193+#: qml/Components/Dialogs.qml:185
190194msgid "Yes"
191195msgstr ""
192196
193−#: qml/Components/Dialogs.qml:152
197+#: qml/Components/Dialogs.qml:200
194198msgctxt "Title: Power off/Restart dialog"
195199msgid "Power"
196200msgstr ""
197201
198−#: qml/Components/Dialogs.qml:153
202+#: qml/Components/Dialogs.qml:201
199203msgid ""
200204"Are you sure you would like\n"
201205"to power off?"
202206msgstr ""
203207
204−#: qml/Components/Dialogs.qml:155
208+#: qml/Components/Dialogs.qml:203
205209msgctxt "Button: Power off the system"
206210msgid "Power off"
207211msgstr ""
208212
209−#: qml/Components/Dialogs.qml:164
213+#: qml/Components/Dialogs.qml:212
210214msgctxt "Button: Restart the system"
211215msgid "Restart"
212216msgstr ""
@@ -223,15 +227,15 @@ msgstr ""
223227msgid "OK"
224228msgstr ""
225229
226−#: qml/Dash/GenericScopeView.qml:539 qml/Dash/GenericScopeView.qml:697
230+#: qml/Dash/GenericScopeView.qml:544 qml/Dash/GenericScopeView.qml:702
227231msgid "See less"
228232msgstr ""
229233
230−#: qml/Dash/GenericScopeView.qml:539
234+#: qml/Dash/GenericScopeView.qml:544
231235msgid "See all"
232236msgstr ""
233237
234−#: qml/Dash/GenericScopeView.qml:601
238+#: qml/Dash/GenericScopeView.qml:606
235239msgctxt "Label: Hint for dash search line edit"
236240msgid "Search"
237241msgstr ""
@@ -267,6 +271,12 @@ msgstr ""
267271msgid "Less..."
268272msgstr ""
269273
274+#: qml/Dash/Previews/PreviewCommentInput.qml:61
275+#: qml/Dash/Previews/PreviewRatingInput.qml:169
276+#: qml/Panel/Indicators/MessageMenuItemFactory.qml:205
277+msgid "Send"
278+msgstr ""
279+
270280#: qml/Dash/Previews/PreviewRatingInput.qml:84
271281msgid "Rate this"
272282msgstr ""
@@ -275,10 +285,6 @@ msgstr ""
275285msgid "Add a review"
276286msgstr ""
277287
278−#: qml/Dash/Previews/PreviewRatingInput.qml:169
279−msgid "Send"
280−msgstr ""
281−
282288#: qml/Dash/PullToRefreshScopeStyle.qml:55
283289msgid "Pull to refresh…"
284290msgstr ""
@@ -382,62 +388,62 @@ msgstr ""
382388msgid "Conference"
383389msgstr ""
384390
385−#: qml/Panel/Indicators/MenuItemFactory.qml:639
391+#: qml/Panel/Indicators/MenuItemFactory.qml:674
386392msgid "Nothing is playing"
387393msgstr ""
388394
389−#: qml/Panel/Indicators/MenuItemFactory.qml:768
395+#: qml/Panel/Indicators/MenuItemFactory.qml:803
390396#, qt-format
391397msgid "%1 hour"
392398msgid_plural "%1 hours"
393399msgstr[0] ""
394400msgstr[1] ""
395401
396−#: qml/Panel/Indicators/MenuItemFactory.qml:772
402+#: qml/Panel/Indicators/MenuItemFactory.qml:807
397403#, qt-format
398404msgid "%1 minute"
399405msgid_plural "%1 minutes"
400406msgstr[0] ""
401407msgstr[1] ""
402408
403−#: qml/Panel/Indicators/MenuItemFactory.qml:777
409+#: qml/Panel/Indicators/MenuItemFactory.qml:812
404410#, qt-format
405411msgid "%1 second"
406412msgid_plural "%1 seconds"
407413msgstr[0] ""
408414msgstr[1] ""
409415
410−#: qml/Panel/Indicators/MenuItemFactory.qml:780
416+#: qml/Panel/Indicators/MenuItemFactory.qml:815
411417msgid "0 seconds"
412418msgstr ""
413419
414420#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
415−#: qml/Panel/Indicators/MenuItemFactory.qml:782
421+#: qml/Panel/Indicators/MenuItemFactory.qml:817
416422#, qt-format
417423msgid "%1 remaining"
418424msgstr ""
419425
420−#: qml/Panel/Indicators/MenuItemFactory.qml:788
426+#: qml/Panel/Indicators/MenuItemFactory.qml:823
421427msgid "In queue…"
422428msgstr ""
423429
424−#: qml/Panel/Indicators/MenuItemFactory.qml:792
430+#: qml/Panel/Indicators/MenuItemFactory.qml:827
425431msgid "Downloading"
426432msgstr ""
427433
428−#: qml/Panel/Indicators/MenuItemFactory.qml:794
434+#: qml/Panel/Indicators/MenuItemFactory.qml:829
429435msgid "Paused, tap to resume"
430436msgstr ""
431437
432−#: qml/Panel/Indicators/MenuItemFactory.qml:796
438+#: qml/Panel/Indicators/MenuItemFactory.qml:831
433439msgid "Canceled"
434440msgstr ""
435441
436−#: qml/Panel/Indicators/MenuItemFactory.qml:798
442+#: qml/Panel/Indicators/MenuItemFactory.qml:833
437443msgid "Finished"
438444msgstr ""
439445
440−#: qml/Panel/Indicators/MenuItemFactory.qml:800
446+#: qml/Panel/Indicators/MenuItemFactory.qml:835
441447msgid "Failed, tap to retry"
442448msgstr ""
443449
@@ -446,6 +452,10 @@ msgctxt "Label: Hint in message indicator line edit"
446452msgid "Reply"
447453msgstr ""
448454
455+#: qml/Panel/Indicators/MessageMenuItemFactory.qml:204
456+msgid "Call back"
457+msgstr ""
458+
449459#: qml/Panel/Indicators/ModemInfoItem.qml:105
450460msgid "Unlock SIM"
451461msgstr ""
@@ -516,6 +526,38 @@ msgctxt "Button: Go back one page in the Wizard"
516526msgid "Back"
517527msgstr ""
518528
529+#: qml/Wizard/Pages/here-terms.qml:27
530+msgid "Terms & Conditions"
531+msgstr ""
532+
533+#: qml/Wizard/Pages/passwd-confirm.qml:49
534+msgid "Confirm passphrase"
535+msgstr ""
536+
537+#: qml/Wizard/Pages/passwd-confirm.qml:50
538+msgid "Confirm passcode"
539+msgstr ""
540+
541+#: qml/Wizard/Pages/passwd-confirm.qml:53
542+#: qml/Wizard/Pages/passwd-confirm.qml:54
543+msgid "Please try again."
544+msgstr ""
545+
546+#: qml/Wizard/Pages/passwd-confirm.qml:82 qml/Wizard/Pages/passwd-set.qml:90
547+#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
548+#: qml/Wizard/Pages/40-wifi.qml:215 qml/Wizard/Pages/50-location.qml:131
549+#: qml/Wizard/Pages/60-reporting.qml:50
550+msgid "Continue"
551+msgstr ""
552+
553+#: qml/Wizard/Pages/passwd-set.qml:61
554+msgid "Choose your passcode"
555+msgstr ""
556+
557+#: qml/Wizard/Pages/passwd-set.qml:68
558+msgid "Passphrase must be 4 characters long"
559+msgstr ""
560+
519561#: qml/Wizard/Pages/10-welcome.qml:27
520562msgid "Hi!"
521563msgstr ""
@@ -528,13 +570,6 @@ msgstr ""
528570msgid "Let’s get started."
529571msgstr ""
530572
531−#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
532−#: qml/Wizard/Pages/40-wifi.qml:215 qml/Wizard/Pages/50-location.qml:131
533−#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82
534−#: qml/Wizard/Pages/passwd-set.qml:90
535−msgid "Continue"
536−msgstr ""
537−
538573#: qml/Wizard/Pages/20-sim.qml:25
539574msgid "Add a SIM card and restart your device"
540575msgstr ""
@@ -660,31 +695,6 @@ msgstr ""
660695msgid "Finish"
661696msgstr ""
662697
663−#: qml/Wizard/Pages/here-terms.qml:27
664−msgid "Terms & Conditions"
665−msgstr ""
666−
667−#: qml/Wizard/Pages/passwd-confirm.qml:49
668−msgid "Confirm passphrase"
669−msgstr ""
670−
671−#: qml/Wizard/Pages/passwd-confirm.qml:50
672−msgid "Confirm passcode"
673−msgstr ""
674−
675−#: qml/Wizard/Pages/passwd-confirm.qml:53
676−#: qml/Wizard/Pages/passwd-confirm.qml:54
677−msgid "Please try again."
678−msgstr ""
679−
680−#: qml/Wizard/Pages/passwd-set.qml:61
681−msgid "Choose your passcode"
682−msgstr ""
683−
684−#: qml/Wizard/Pages/passwd-set.qml:68
685−msgid "Passphrase must be 4 characters long"
686−msgstr ""
687−
688698#. Translators: This is the arrow for "Back" buttons
689699#: qml/Wizard/StackButton.qml:39
690700#, qt-format
qml/Components/Dialogs.qml+61−6
@@ -18,8 +18,9 @@ import QtQuick 2.0
1818
1919import Unity.Application 0.1
2020import Unity.Session 0.1
21−import Ubuntu.Components 1.1
21+import Ubuntu.Components 1.3
2222import LightDM 0.1 as LightDM
23+import GlobalShortcut 1.0
2324
2425Item {
2526 id: root
@@ -35,6 +36,7 @@ Item {
3536 ApplicationManager.stopApplication(app.appId);
3637 }
3738 }
39+ property string usageScenario
3840
3941 signal powerOffClicked();
4042
@@ -42,19 +44,61 @@ Item {
4244 d.showPowerDialog();
4345 }
4446
47+ GlobalShortcut { // reboot/shutdown dialog
48+ shortcut: Qt.Key_PowerDown
49+ active: root.usageScenario === "desktop"
50+ onTriggered: root.unitySessionService.RequestShutdown()
51+ }
52+
53+ GlobalShortcut { // reboot/shutdown dialog
54+ shortcut: Qt.Key_PowerOff
55+ active: root.usageScenario === "desktop"
56+ onTriggered: root.unitySessionService.RequestShutdown()
57+ }
58+
59+ GlobalShortcut { // sleep
60+ shortcut: Qt.Key_Sleep
61+ onTriggered: root.unitySessionService.Suspend()
62+ }
63+
64+ GlobalShortcut { // hibernate
65+ shortcut: Qt.Key_Hibernate
66+ onTriggered: root.unitySessionService.Hibernate()
67+ }
68+
69+ GlobalShortcut { // logout/lock dialog
70+ shortcut: Qt.Key_LogOff
71+ onTriggered: root.unitySessionService.RequestLogout()
72+ }
73+
74+ GlobalShortcut { // logout/lock dialog
75+ shortcut: Qt.ControlModifier|Qt.AltModifier|Qt.Key_Delete
76+ onTriggered: root.unitySessionService.RequestLogout()
77+ }
78+
79+ GlobalShortcut { // lock screen
80+ shortcut: Qt.Key_ScreenSaver
81+ onTriggered: LightDM.Greeter.showGreeter()
82+ }
83+
84+ GlobalShortcut { // lock screen
85+ shortcut: Qt.ControlModifier|Qt.AltModifier|Qt.Key_L
86+ onTriggered: LightDM.Greeter.showGreeter()
87+ }
88+
4589 QtObject {
4690 id: d // private stuff
4791 objectName: "dialogsPrivate"
4892
49−
5093 function showPowerDialog() {
5194 if (!dialogLoader.active) {
5295 dialogLoader.sourceComponent = powerDialogComponent;
96+ dialogLoader.focus = true;
5397 dialogLoader.active = true;
54− dialogLoader.item.forceActiveFocus();
5598 }
5699 }
57100 }
101+
58102 Loader {
59103 id: dialogLoader
60104 objectName: "dialogLoader"
@@ -104,6 +148,7 @@ Item {
104148 unitySessionService.reboot();
105149 shutdownDialog.hide();
106150 }
151+ color: UbuntuColors.lightGrey
107152 }
108153 Button {
109154 text: i18n.ctr("Button: Shut down the system", "Shut down")
@@ -112,12 +157,14 @@ Item {
112157 unitySessionService.shutdown();
113158 shutdownDialog.hide();
114159 }
160+ color: UbuntuColors.red
115161 }
116162 Button {
117163 text: i18n.tr("Cancel")
118164 onClicked: {
119165 shutdownDialog.hide();
120166 }
167+ color: UbuntuColors.lightGrey
121168 }
122169 }
123170 }
@@ -133,6 +180,7 @@ Item {
133180 onClicked: {
134181 rebootDialog.hide();
135182 }
183+ color: UbuntuColors.lightGrey
136184 }
137185 Button {
138186 text: i18n.tr("Yes")
@@ -141,6 +189,7 @@ Item {
141189 unitySessionService.reboot();
142190 rebootDialog.hide();
143191 }
192+ color: UbuntuColors.red
144193 }
145194 }
146195 }
@@ -167,14 +216,14 @@ Item {
167216 unitySessionService.reboot();
168217 powerDialog.hide();
169218 }
170− color: UbuntuColors.green
219+ color: UbuntuColors.lightGrey
171220 }
172221 Button {
173222 text: i18n.tr("Cancel")
174223 onClicked: {
175224 powerDialog.hide();
176225 }
177− color: UbuntuColors.coolGrey
226+ color: UbuntuColors.lightGrey
178227 }
179228 }
180229 }
@@ -186,6 +235,7 @@ Item {
186235 // Display a dialog to ask the user to confirm.
187236 if (!dialogLoader.active) {
188237 dialogLoader.sourceComponent = logoutDialogComponent;
238+ dialogLoader.focus = true;
189239 dialogLoader.active = true;
190240 }
191241 }
@@ -194,6 +244,7 @@ Item {
194244 // Display a dialog to ask the user to confirm.
195245 if (!dialogLoader.active) {
196246 dialogLoader.sourceComponent = shutdownDialogComponent;
247+ dialogLoader.focus = true;
197248 dialogLoader.active = true;
198249 }
199250 }
@@ -201,7 +252,11 @@ Item {
201252 onRebootRequested: {
202253 // Display a dialog to ask the user to confirm.
203254 if (!dialogLoader.active) {
204− dialogLoader.sourceComponent = rebootDialogComponent;
255+ // display a combined reboot/shutdown dialog, sadly the session indicator calls rather the "Reboot()" method
256+ // than shutdown when clicking on the "Shutdown..." menu item
257+ // FIXME: when/if session indicator is fixed, put the rebootDialogComponent here
258+ dialogLoader.sourceComponent = shutdownDialogComponent;
259+ dialogLoader.focus = true;
205260 dialogLoader.active = true;
206261 }
207262 }
qml/Components/PhysicalKeysMapper.qml+7−8
@@ -30,7 +30,6 @@ import Powerd 0.1
3030 * Power dialog
3131 * Volume Decreases/Increases
3232 * Screenshots
33−
3433*/
3534
3635Item {
@@ -80,15 +79,15 @@ Item {
8079 if (Powerd.status === Powerd.On) {
8180 powerKeyLongPressTimer.restart();
8281 }
83− event.accepted = true;
84− } else if ((event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay)
85− && !event.isAutoRepeat) {
82+ } else if ((event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) && !event.isAutoRepeat) {
8683 event.accepted = callManager.handleMediaKey(false);
8784 } else if (event.key == Qt.Key_VolumeDown) {
8885 if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeDownTriggered();
8986 else if (!event.isAutoRepeat) {
9087 if (d.volumeUpKeyPressed) {
91− if (Powerd.status === Powerd.On) root.screenshotTriggered();
88+ if (Powerd.status === Powerd.On) {
89+ root.screenshotTriggered();
90+ }
9291 d.ignoreVolumeEvents = true;
9392 }
9493 d.volumeDownKeyPressed = true;
@@ -97,7 +96,9 @@ Item {
9796 if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeUpTriggered();
9897 else if (!event.isAutoRepeat) {
9998 if (d.volumeDownKeyPressed) {
100− if (Powerd.status === Powerd.On) root.screenshotTriggered();
99+ if (Powerd.status === Powerd.On) {
100+ root.screenshotTriggered();
101+ }
101102 d.ignoreVolumeEvents = true;
102103 }
103104 d.volumeUpKeyPressed = true;
@@ -109,8 +110,6 @@ Item {
109110 d.altTabPressed = true;
110111 event.accepted = true;
111112 }
112− } else if (event.key == Qt.Key_Print) {
113− root.screenshotTriggered();
114113 }
115114 }
116115
qml/Components/ScreenGrabber.qml+7
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import QtMultimedia 5.0
1919import ScreenGrabber 0.1
20+import GlobalShortcut 1.0
2021
2122Rectangle {
2223 id: root
@@ -30,6 +31,12 @@ Rectangle {
3031 objectName: "screenGrabber"
3132 }
3233
34+ GlobalShortcut {
35+ id: screenshotShortcut
36+ shortcut: Qt.Key_Print
37+ onTriggered: capture()
38+ }
39+
3340 Audio {
3441 id: shutterSound
3542 audioRole: MediaPlayer.alert
qml/Components/ShellDialog.qml+7−1
@@ -33,5 +33,11 @@ Dialog {
3333
3434 onVisibleChanged: { if (!visible) { dialogLoader.active = false; } }
3535
36− Component.onCompleted: show()
36+ Keys.onEscapePressed: hide()
37+
38+ focus: true
39+
40+ Component.onCompleted: {
41+ show();
42+ }
3743}
qml/Components/VolumeControl.qml+20−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,8 +14,9 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import QMenuModel 0.1
17+import QtQuick 2.4
18+import QMenuModel 0.1 as QMenuModel
19+import GlobalShortcut 1.0
1920
2021Item {
2122 id: root
@@ -26,13 +27,20 @@ Item {
2627 readonly property int stepUp: 1
2728 readonly property int stepDown: -1
2829
29− QDBusActionGroup {
30+ GlobalShortcut {
31+ id: muteShortcut
32+ shortcut: Qt.Key_VolumeMute
33+ Keys.onPressed: toggleMute()
34+ }
35+
36+ QMenuModel.QDBusActionGroup {
3037 id: actionGroup
31− busType: 1
38+ busType: QMenuModel.DBus.SessionBus
3239 busName: "com.canonical.indicator.sound"
3340 objectPath: "/com/canonical/indicator/sound"
3441
3542 property variant actionObject: action("volume")
43+ property variant muteActionObject: action("mute")
3644 }
3745
3846 function volumeUp() {
@@ -43,5 +51,11 @@ Item {
4351 actionGroup.actionObject.activate(stepDown);
4452 }
4553
46− Component.onCompleted: actionGroup.start()
54+ function toggleMute() {
55+ actionGroup.muteActionObject.activate();
56+ }
57+
58+ Component.onCompleted: {
59+ actionGroup.start();
60+ }
4761}
qml/OrientedShell.qml+1−5
@@ -103,7 +103,7 @@ Rectangle {
103103 // TODO: Choose the closest to the current one
104104 if (supportedOrientations & Qt.PortraitOrientation) {
105105 return Screen.angleBetween(nativeOrientation, Qt.PortraitOrientation);
106− } else if (supportedOrientations & Qt.LandcscapeOrientation) {
106+ } else if (supportedOrientations & Qt.LandscapeOrientation) {
107107 return Screen.angleBetween(nativeOrientation, Qt.LandscapeOrientation);
108108 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
109109 return Screen.angleBetween(nativeOrientation, Qt.InvertedPortraitOrientation);
@@ -120,19 +120,15 @@ Rectangle {
120120 switch (angle) {
121121 case 0:
122122 return nativeOrientation;
123− break;
124123 case 90:
125124 return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
126125 : Qt.PortraitOrientation;
127− break;
128126 case 180:
129127 return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
130128 : Qt.InvertedLandscapeOrientation;
131− break;
132129 case 270:
133130 return nativeOrientation === Qt.PortraitOrientation ? Qt.LandscapeOrientation
134131 : Qt.InvertedPortraitOrientation;
135− break;
136132 default:
137133 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
138134 return primaryOrientation;
qml/Panel/Indicators/MessageMenuItemFactory.qml+2−2
@@ -201,8 +201,8 @@ Loader {
201201 title: menuData && menuData.label || ""
202202 time: timeFormatter.timeString
203203 body: getExtendedProperty(extendedData, "xCanonicalText", "")
204− actionButtonText: getExtendedProperty(activateActionDescription, "label", "Call back")
205− replyButtonText: getExtendedProperty(replyActionDescription, "label", "Send")
204+ actionButtonText: getExtendedProperty(activateActionDescription, "label", i18n.tr("Call back"))
205+ replyButtonText: getExtendedProperty(replyActionDescription, "label", i18n.tr("Send"))
206206 // icons
207207 avatar: getExtendedProperty(extendedData, "icon", "image://theme/contact")
208208 icon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "image://theme/missed-call")
qml/Shell.qml+13−4
@@ -25,6 +25,7 @@ import Ubuntu.Gestures 0.1
2525import Ubuntu.Telephony 0.1 as Telephony
2626import Unity.Connectivity 0.1
2727import Unity.Launcher 0.1
28+import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
2829import Utils 0.1
2930import LightDM 0.1 as LightDM
3031import Powerd 0.1
@@ -42,6 +43,7 @@ import Unity.Session 0.1
4243import Unity.DashCommunicator 0.1
4344import Unity.Indicators 0.1 as Indicators
4445
46+
4547Item {
4648 id: shell
4749
@@ -146,7 +148,7 @@ Item {
146148 // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
147149 Image {
148150 id: gsImageTester
149− source: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
151+ source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
150152 height: 0
151153 width: 0
152154 sourceSize.height: 0
@@ -180,7 +182,7 @@ Item {
180182 id: physicalKeysMapper
181183 objectName: "physicalKeysMapper"
182184
183− onPowerKeyLongPressed: dialogs.showPowerDialog()
185+ onPowerKeyLongPressed: dialogs.showPowerDialog();
184186 onVolumeDownTriggered: volumeControl.volumeDown();
185187 onVolumeUpTriggered: volumeControl.volumeUp();
186188 onScreenshotTriggered: screenGrabber.capture();
@@ -191,7 +193,13 @@ Item {
191193 z: dialogs.z + 10
192194 }
193195
196+ GlobalShortcut {
197+ // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
198+ }
199+
194200 WindowKeysFilter {
201+ // this serves merely for forwarding the remaining key events
202+ // to the PhysicalKeysMapper, not as a shortcut itself
195203 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
196204 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
197205 }
@@ -432,7 +440,7 @@ Item {
432440 sourceComponent: shell.mode != "shell" ? integratedGreeter :
433441 Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
434442 onLoaded: {
435− item.objectName = "greeter"
443+ item.objectName = "greeter"
436444 }
437445 }
438446
@@ -679,6 +687,7 @@ Item {
679687 objectName: "dialogs"
680688 anchors.fill: parent
681689 z: overlay.z + 10
690+ usageScenario: shell.usageScenario
682691 onPowerOffClicked: {
683692 shutdownFadeOutRectangle.enabled = true;
684693 shutdownFadeOutRectangle.visible = true;
@@ -705,7 +714,7 @@ Item {
705714 to: 1.0
706715 onStopped: {
707716 if (shutdownFadeOutRectangle.enabled && shutdownFadeOutRectangle.visible) {
708− DBusUnitySessionService.Shutdown();
717+ DBusUnitySessionService.shutdown();
709718 }
710719 }
711720 }
tests/mocks/Utils/windowstatestorage.cpp−2
@@ -16,8 +16,6 @@
1616
1717#include "windowstatestorage.h"
1818
19−#include <QRect>
20−
2119WindowStateStorage::WindowStateStorage(QObject *parent):
2220 QObject(parent)
2321{
tests/mocks/Utils/windowstatestorage.h+1
@@ -16,6 +16,7 @@
1616
1717#include <QObject>
1818#include <QVariantMap>
19+#include <QRect>
1920
2021class WindowStateStorage: public QObject
2122{
tests/plugins/CMakeLists.txt+1
@@ -1,4 +1,5 @@
11add_subdirectory(AccountsService)
2+add_subdirectory(GlobalShortcut)
23add_subdirectory(Greeter)
34add_subdirectory(LightDM)
45add_subdirectory(Dash)
tests/plugins/GlobalShortcut/CMakeLists.txtnew file+14
@@ -0,0 +1,14 @@
1+include_directories(
2+ ${CMAKE_SOURCE_DIR}/plugins/GlobalShortcut
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+ )
5+
6+add_definitions(
7+ -DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
8+ )
9+add_definitions(-DBUILT_PLUGINS_DIR="${CMAKE_BINARY_DIR}/plugins")
10+
11+add_executable(GlobalShortcutTestExec GlobalShortcutTest.cpp ${CMAKE_SOURCE_DIR}/plugins/GlobalShortcut/globalshortcut.cpp)
12+qt5_use_modules(GlobalShortcutTestExec Test Quick)
13+target_link_libraries(GlobalShortcutTestExec ${Qt5Quick_LIBRARIES} GlobalShortcut-qml)
14+add_unity8_uitest(GlobalShortcut GlobalShortcutTestExec DEPENDS GlobalShortcut-qml)
tests/plugins/GlobalShortcut/GlobalShortcutTest.cppnew file+77
@@ -0,0 +1,77 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "globalshortcut.h"
18+
19+#include <QtTest>
20+#include <QtQuick/QQuickView>
21+#include <QtQuick/QQuickItem>
22+#include <QQmlEngine>
23+
24+class GlobalShortcutTest: public QObject
25+{
26+ Q_OBJECT
27+
28+private Q_SLOTS:
29+
30+ void initTestCase()
31+ {
32+ m_view = new QQuickView();
33+ m_view->engine()->addImportPath(BUILT_PLUGINS_DIR);
34+ m_view->setSource(QUrl::fromLocalFile(CURRENT_SOURCE_DIR "/shortcut.qml"));
35+ m_shortcut = dynamic_cast<GlobalShortcut*>(m_view->rootObject()->property("shortcut").value<QObject*>());
36+ QVERIFY(m_shortcut);
37+ m_inactiveShortcut = dynamic_cast<GlobalShortcut*>(m_view->rootObject()->property("inactiveShortcut").value<QObject*>());
38+ QVERIFY(m_inactiveShortcut);
39+ m_view->show();
40+ QTest::qWaitForWindowExposed(m_view);
41+ }
42+
43+ void cleanupTestCase()
44+ {
45+ m_view.clear();
46+ }
47+
48+ void testGlobalShortcut()
49+ {
50+ QSignalSpy shortcutSpy(m_shortcut, &GlobalShortcut::triggered);
51+ // test pressing "Mute Volume"
52+ QTest::keyClick(m_view, Qt::Key_VolumeMute);
53+ QTRY_COMPARE(shortcutSpy.count(), 1);
54+ const QVariantList args = shortcutSpy.takeFirst();
55+ // verify we got the signal back and a non-empty shortcut
56+ QCOMPARE(args.count(), 1);
57+ QVERIFY(!args.first().toString().isEmpty());
58+ }
59+
60+ void testInactiveGlobalShortcut()
61+ {
62+ QSignalSpy shortcutSpy(m_inactiveShortcut, &GlobalShortcut::triggered);
63+ // test pressing Ctrl+Alt+L
64+ QTest::keyClick(m_view, Qt::Key_L, Qt::ControlModifier|Qt::AltModifier);
65+ // verify we didn't get any signal back, shortcut is not active
66+ QTRY_COMPARE(shortcutSpy.count(), 0);
67+ }
68+
69+private:
70+ QPointer<QQuickView> m_view;
71+ GlobalShortcut *m_shortcut = nullptr;
72+ GlobalShortcut *m_inactiveShortcut = nullptr;
73+};
74+
75+QTEST_MAIN(GlobalShortcutTest)
76+
77+#include "GlobalShortcutTest.moc"
tests/plugins/GlobalShortcut/shortcut.qmlnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import GlobalShortcut 1.0
19+
20+Rectangle {
21+ property var shortcut: globalShortcut
22+ property var inactiveShortcut: inactiveGlobalShortcut
23+
24+ width: 100
25+ height: 100
26+
27+ GlobalShortcut {
28+ id: globalShortcut
29+ objectName: "globalShortcut"
30+ shortcut: Qt.Key_VolumeMute
31+ }
32+
33+ GlobalShortcut {
34+ id: inactiveGlobalShortcut
35+ objectName: "inactiveGlobalShortcut"
36+ shortcut: Qt.ControlModifier|Qt.AltModifier|Qt.Key_L
37+ active: false
38+ }
39+}
tests/qmltests/Components/tst_Dialogs.qml+13−8
@@ -32,6 +32,7 @@ Rectangle {
3232 signal logoutRequested
3333 signal shutdownRequested
3434 signal rebootRequested
35+ signal logoutReady
3536 function logout() {}
3637 function shutdown() {}
3738 function reboot() {}
@@ -79,15 +80,19 @@ Rectangle {
7980 Column {
8081 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
8182 spacing: units.gu(1)
82− Button { text: "Power dialog"; onClicked: { testCase.test_showPowerDialog(); } }
83− Button { text: "Logout Requested"; onClicked: { fakeUnitySession.logoutRequested(); } }
84− Button { text: "Shutdown Requested"; onClicked: { fakeUnitySession.shutdownRequested(); } }
85− Button { text: "Reboot Requested"; onClicked: { fakeUnitySession.rebootRequested(); } }
83+ Button { text: "Power dialog"; onClicked: { testCase.test_showPowerDialog(); }
84+ activeFocusOnPress: false }
85+ Button { text: "Logout Requested"; onClicked: { fakeUnitySession.logoutRequested(); }
86+ activeFocusOnPress: false }
87+ Button { text: "Shutdown Requested"; onClicked: { fakeUnitySession.shutdownRequested(); }
88+ activeFocusOnPress: false }
89+ Button { text: "Reboot Requested"; onClicked: { fakeUnitySession.rebootRequested(); }
90+ activeFocusOnPress: false }
8691 Label { text: "Rotation:" }
87− Button { text: "0"; onClicked: {fakeShell.rotation = 0;} }
88− Button { text: "90"; onClicked: {fakeShell.rotation = 90;} }
89− Button { text: "180"; onClicked: {fakeShell.rotation = 180;} }
90− Button { text: "270"; onClicked: {fakeShell.rotation = 270;} }
92+ Button { text: "0"; onClicked: {fakeShell.rotation = 0;} activeFocusOnPress: false }
93+ Button { text: "90"; onClicked: {fakeShell.rotation = 90;} activeFocusOnPress: false }
94+ Button { text: "180"; onClicked: {fakeShell.rotation = 180;} activeFocusOnPress: false }
95+ Button { text: "270"; onClicked: {fakeShell.rotation = 270;} activeFocusOnPress: false }
9196 }
9297 }
9398
tests/qmltests/Components/tst_PhysicalKeysMapper.qml+1−44
@@ -37,18 +37,6 @@ TestCase {
3737 signalName: "powerKeyLongPressed"
3838 }
3939
40− SignalSpy {
41− id: volumeDownSpy
42− target: physicalKeysMapper
43− signalName: "volumeDownTriggered"
44− }
45−
46− SignalSpy {
47− id: volumeUpSpy
48− target: physicalKeysMapper
49− signalName: "volumeUpTriggered"
50− }
51−
5240 SignalSpy {
5341 id: screenshotSpy
5442 target: physicalKeysMapper
@@ -63,8 +51,6 @@ TestCase {
6351 function cleanup() {
6452 loader.active = false;
6553 powerSpy.clear();
66− volumeDownSpy.clear();
67− volumeUpSpy.clear();
6854 screenshotSpy.clear();
6955 }
7056
@@ -89,11 +75,6 @@ TestCase {
8975 compare(powerSpy.count, 1);
9076 }
9177
92− function test_screenshotPrtScr() {
93− physicalKeysMapper.onKeyPressed({ key: Qt.Key_Print })
94− tryCompare(screenshotSpy, "count", 1) // verify the screenshotTriggered signal has been called, exactly once
95− }
96−
9778 function test_screenshotButtons_data() {
9879 return [
9980 { tag: "UpFirst", first: Qt.Key_VolumeUp, second: Qt.Key_VolumeDown },
@@ -107,30 +88,6 @@ TestCase {
10788 screenshotSpy.wait();
10889 physicalKeysMapper.onKeyReleased({ key: data.first });
10990 physicalKeysMapper.onKeyReleased({ key: data.second });
110−
111− wait(100);
112− compare(volumeUpSpy.count, 0);
113− compare(volumeDownSpy.count, 0);
114− }
115−
116− function test_volumeButton_data() {
117− return [
118− { tag: "Down", key: Qt.Key_VolumeDown, spy: volumeDownSpy },
119− { tag: "Up", key: Qt.Key_VolumeUp, spy: volumeUpSpy },
120− ];
121− }
122−
123− function test_volumeButton(data) {
124− physicalKeysMapper.onKeyPressed({ key: data.key });
125−
126− wait(100);
127− compare(data.spy.count, 0);
128−
129− physicalKeysMapper.onKeyReleased({ key: data.key });
130− data.spy.wait();
131−
132− physicalKeysMapper.onKeyPressed({ key: data.key });
133− physicalKeysMapper.onKeyPressed({ key: data.key, isAutoRepeat: true });
134− data.spy.wait();
91+ compare(screenshotSpy.count, 1);
13592 }
13693}