on xenial help sign in
[ Albert Astals Cid ]

* Clazy fixes * Enable Efficient String Construction by default [ CI Train Bot ] * New rebuild forced. [ Daniel d'Andrada ] * Have unity8 drawing its own cursor (LP: #1488417) * Initial multi-monitor support [ Daniel van Vugt ] * Disable Qt's stuttering 'touch compression' to fix scrolling smoothness (LP: #1486341, #1488327) [ Lukáš Tinkl ] * Fix autopilot wizard test skipping the reporting page * Implement Unity.Platform plugin wrapping org.freedesktop.hostname1 (LP: #1504318) * React to window title (aka surface name) changes (LP: #1497092) * Rotate the screenshots according to the actual orientation [ Michał Sawicz ] * Fix application API dependency * Have unity8 drawing its own cursor (LP: #1488417) * Initial multi-monitor support * Rotate the screenshots according to the actual orientation [ Nick Dedekind ] * Fixed leak in UnityMenuModelStackTest * Moved time translation to SDK (LP: #1372061)

commit8402714ed3865e500ba37e5c7c2b77ffccff5a78 authorJosh Arenson <joshua.arenson@canonical.com> dateMon 26 Oct 2015, 13:15 (11 years ago) parentc00faf3639f765
104 files changed+3076−1250
CMakeLists.txt+4−1 data/unity8.conf+10 debian/changelog+36 debian/control+9−7 debian/copyright+21 debian/unity8-private.install+1 debian/unity8.install+1 plugins/CMakeLists.txt+1 plugins/Cursor/3rd_party/CMakeLists.txt+1 plugins/Cursor/3rd_party/xcursor/CMakeLists.txt+15 plugins/Cursor/3rd_party/xcursor/xcursor.c+968 plugins/Cursor/3rd_party/xcursor/xcursor.h+65 plugins/Cursor/CMakeLists.txt+28 plugins/Cursor/Cursor.qml+28 plugins/Cursor/CursorImageProvider.cpp+191 plugins/Cursor/CursorImageProvider.h+76 plugins/Cursor/MousePointer.cpp+125 plugins/Cursor/MousePointer.h+59 plugins/Cursor/plugin.cpp+39 plugins/Cursor/plugin.h+33 plugins/Cursor/qmldir+3 plugins/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+2−1 plugins/LightDM/UsersModel.cpp+7−6 plugins/ScreenGrabber/screengrabber.cpp+2−2 plugins/ScreenGrabber/screengrabber.h+1−1 plugins/Unity/CMakeLists.txt+1 plugins/Unity/Launcher/desktopfilehandler.cpp+3−2 plugins/Unity/Platform/CMakeLists.txt+9 plugins/Unity/Platform/platform.cpp+43 plugins/Unity/Platform/platform.h+58 plugins/Unity/Platform/plugin.cpp+27 plugins/Utils/relativetimeformatter.h → plugins/Unity/Platform/plugin.h+10−12 plugins/Unity/Platform/qmldir+2 plugins/Unity/Session/dbusunitysessionservice.cpp+2−1 plugins/Utils/CMakeLists.txt−2 plugins/Utils/plugin.cpp−5 plugins/Utils/relativetimeformatter.cpp−260 plugins/Utils/timeformatter.cpp−206 plugins/Utils/timeformatter.h−68 plugins/Utils/timezoneFormatter.cpp+1−1 po/ug.po+5−5 qml/Components/Dialogs.qml+3−2 qml/Components/ScreenGrabber.qml+4−1 qml/Components/WallpaperResolver.qml+63 qml/DeviceConfiguration.qml+1 qml/DisabledScreenNotice.qml+49 qml/Greeter/Greeter.qml+1−1 qml/Launcher/Launcher.qml+1 qml/Notifications/Notification.qml+2 qml/Notifications/NotificationMenuItemFactory.qml+2−1 qml/Notifications/Notifications.qml+2 qml/OrientedShell.qml+10−1 qml/Panel/Indicators/MenuItemFactory.qml+19−15 qml/Panel/Indicators/MessageMenuItemFactory.qml+35−26 qml/Panel/Panel.qml+1 qml/Rotation/RotationStates.qml+2−2 qml/Shell.qml+24−40 qml/Stages/ApplicationWindow.qml+2 qml/Stages/DecoratedWindow.qml+7−4 qml/Stages/DesktopStage.qml+5−8 qml/Stages/SurfaceContainer.qml+1 qml/Stages/WindowDecoration.qml+32−4 qml/Stages/WindowMoveResizeArea.qml−109 qml/Stages/WindowResizeArea.qml+182 src/ApplicationArguments.h+10−2 src/CMakeLists.txt+3 src/SecondaryWindow.cpp+31 src/SecondaryWindow.h+30 src/ShellApplication.cpp+202 src/ShellApplication.h+55 src/ShellView.cpp+59 src/ShellView.h+34 src/main.cpp+4−109 tests/autopilot/unity8/fixture_setup.py+1−1 tests/autopilot/unity8/greeter/tests/__init__.py+1−1 tests/autopilot/unity8/launcher.py+1−1 tests/autopilot/unity8/settings_wizard/__init__.py+7−2 tests/autopilot/unity8/settings_wizard/tests/test_settings_wizard.py+10−5 tests/autopilot/unity8/shell/__init__.py+1−1 tests/autopilot/unity8/shell/tests/__init__.py+1−1 tests/mocks/CMakeLists.txt+1 tests/mocks/Cursor/CMakeLists.txt+1 tests/mocks/Cursor/Cursor.qml+20 tests/mocks/Cursor/qmldir+2 tests/mocks/Unity/Launcher/MockQuickListModel.cpp+1−1 tests/mocks/Unity/fake_resultsmodel.cpp+1−1 tests/mocks/Unity/fake_scopesoverview.cpp+1−1 tests/mocks/Utils/CMakeLists.txt−2 tests/mocks/Utils/Utils.qmltypes+31−28 tests/mocks/Utils/plugin.cpp−13 tests/plugins/ScreenGrabber/ScreenGrabberTest.cpp+11 tests/plugins/ScreenGrabber/grabber.qml+1−1 tests/plugins/Unity/Indicators/UnityMenuModelStackTest.cpp+2−9 tests/plugins/Utils/CMakeLists.txt−1 tests/plugins/Utils/TimeFormatterTest.cpp−155 tests/plugins/Wizard/tst_pagelist.cpp+5−5 tests/qmltests/CMakeLists.txt+3−1 tests/qmltests/Components/tst_WallpaperResolver.qml+89 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+6−16 tests/qmltests/Panel/Indicators/tst_MessageMenuItemFactory.qml+6−13 tests/qmltests/Stages/tst_WindowMoveResizeArea.qml → tests/qmltests/Stages/tst_WindowResizeArea.qml+18−61 tests/qmltests/tst_DisabledScreenNotice.qml+37 tests/qmltests/tst_OrientedShell.qml+55−1 tests/qmltests/tst_Shell.qml−24
CMakeLists.txt+4−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.2 REQUIRED)
5757find_package(Qt5Concurrent 5.2 REQUIRED)
5858find_package(Qt5Sql 5.2 REQUIRED)
5959
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application>=8)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
6161
6262# Standard install paths
6363include(GNUInstallDirs)
@@ -101,6 +101,9 @@ add_definitions(-DQT_NO_KEYWORDS)
101101# Save a few container detach and conversions
102102add_definitions(-DQT_STRICT_ITERATORS)
103103
104+# Use the fast string builder
105+add_definitions(-DQT_USE_QSTRINGBUILDER)
106+
104107# Autopilot tests
105108include(autopilot)
106109declare_autopilot_test(shell unity8.shell ${CMAKE_SOURCE_DIR}/tests/autopilot/)
data/unity8.conf+10
@@ -46,6 +46,16 @@ pre-start script
4646 initctl set-env --global MIR_SERVER_PROMPT_FILE=1
4747
4848 initctl emit --no-wait indicator-services-start
49+
50+ # Disable Qt's stuttering 'touch compression' to fix scrolling smoothness
51+ # issues (LP: #1486341). As a bonus, this eliminates most of the
52+ # lag seen in the indicator panel pull-down (LP: #1488327) and also
53+ # reduces lag seen in apps:
54+ initctl set-env --global QML_NO_TOUCH_COMPRESSION=1
55+
56+ # For twice the fun and half the latency, try this (Warning: not all
57+ # devices are fast enough to keep up smoothly yet)...
58+ # initctl set-env MIR_SERVER_NBUFFERS=2
4959end script
5060
5161exec ${BINARY:-unity8} $ARGS
debian/changelog+36
@@ -1,3 +1,39 @@
1+unity8 (8.11+15.10.20151021-0ubuntu1) wily; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * Clazy fixes
5+ * Enable Efficient String Construction by default
6+
7+ [ CI Train Bot ]
8+ * New rebuild forced.
9+
10+ [ Daniel d'Andrada ]
11+ * Have unity8 drawing its own cursor (LP: #1488417)
12+ * Initial multi-monitor support
13+
14+ [ Daniel van Vugt ]
15+ * Disable Qt's stuttering 'touch compression' to fix scrolling
16+ smoothness (LP: #1486341, #1488327)
17+
18+ [ Lukáš Tinkl ]
19+ * Fix autopilot wizard test skipping the reporting page
20+ * Implement Unity.Platform plugin wrapping org.freedesktop.hostname1
21+ (LP: #1504318)
22+ * React to window title (aka surface name) changes (LP: #1497092)
23+ * Rotate the screenshots according to the actual orientation
24+
25+ [ Michał Sawicz ]
26+ * Fix application API dependency
27+ * Have unity8 drawing its own cursor (LP: #1488417)
28+ * Initial multi-monitor support
29+ * Rotate the screenshots according to the actual orientation
30+
31+ [ Nick Dedekind ]
32+ * Fixed leak in UnityMenuModelStackTest
33+ * Moved time translation to SDK (LP: #1372061)
34+
35+ -- Michał Sawicz <michal.sawicz@canonical.com> Wed, 21 Oct 2015 11:51:53 +0000
36+
137unity8 (8.11+15.10.20151009-0ubuntu1) wily; urgency=medium
238
339 [ CI Train Bot ]
debian/control+9−7
@@ -26,10 +26,11 @@ Build-Depends: cmake,
2626 libpay2-dev,
2727 libpulse-dev,
2828 libqmenumodel-dev (>= 0.2.9),
29+ libqt5svg5-dev,
2930 libqt5xmlpatterns5-dev,
3031 libsystemsettings-dev,
3132 libudev-dev,
32− libunity-api-dev (>= 7.100),
33+ libunity-api-dev (>= 7.101),
3334 libusermetricsoutput1-dev,
3435 libxcb1-dev,
3536 pkg-config,
@@ -50,7 +51,7 @@ Build-Depends: cmake,
5051 qtdeclarative5-private-dev (>= 5.2.1),
5152 qtdeclarative5-qtmultimedia-plugin,
5253 qtdeclarative5-ubuntu-settings-components (>= 0.6),
53− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
54+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
5455 qtdeclarative5-ubuntu-web-plugin,
5556 ttf-ubuntu-font-family,
5657Standards-Version: 3.9.4
@@ -65,7 +66,7 @@ X-Ubuntu-Use-Langpack: yes
6566Package: indicators-client
6667Architecture: amd64 armhf i386
6768Depends: qmenumodel-qml (>= 0.2.9),
68− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
69+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
6970 unity8 (= ${binary:Version}),
7071 ${misc:Depends},
7172 ${shlibs:Depends},
@@ -97,7 +98,8 @@ Description: Development tool for Unity scopes
9798Package: unity8
9899Architecture: any
99100Provides: indicator-renderer,
100−Depends: gsettings-desktop-schemas,
101+Depends: dmz-cursor-theme,
102+ gsettings-desktop-schemas,
101103 libcap2-bin,
102104 libglib2.0-bin,
103105 qmenumodel-qml (>= 0.2.9),
@@ -132,10 +134,10 @@ Architecture: all
132134Depends: qml-module-qtquick-layouts,
133135 qtdeclarative5-ubuntu-settings-components (>= 0.6),
134136 qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
135− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
137+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
136138 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
137139 ubuntu-thumbnailer-impl-0,
138− unity-application-impl-8,
140+ unity-application-impl-9,
139141 unity-notifications-impl-3,
140142 unity-plugin-scopes | unity-scopes-impl,
141143 unity-scopes-impl-7,
@@ -181,7 +183,7 @@ Pre-Depends: ${misc:Pre-Depends},
181183Depends: ${misc:Depends},
182184 ${shlibs:Depends},
183185Provides: unity-application-impl,
184− unity-application-impl-8,
186+ unity-application-impl-9,
185187Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
186188Description: Fake environment for running Unity 8 shell
187189 Provides fake implementations of some QML modules used by Unity 8 shell
debian/copyright+21
@@ -60,3 +60,24 @@ License: LGPL-2.1 with Digia Qt LGPL Exception 1.1 or GPL-3
6060 packaging of this file. Please review the following information to
6161 ensure the GNU General Public License version 3.0 requirements will be
6262 met: http://www.gnu.org/copyleft/gpl.html.
63+
64+Files: plugins/Cursor/3rd_party/xcursor/xcursor.*
65+Copyright: 2002 Keith Packard
66+License: Keith Packard
67+ Permission to use, copy, modify, distribute, and sell this software and its
68+ documentation for any purpose is hereby granted without fee, provided that
69+ the above copyright notice appear in all copies and that both that
70+ copyright notice and this permission notice appear in supporting
71+ documentation, and that the name of Keith Packard not be used in
72+ advertising or publicity pertaining to distribution of the software without
73+ specific, written prior permission. Keith Packard makes no
74+ representations about the suitability of this software for any purpose. It
75+ is provided "as is" without express or implied warranty.
76+ .
77+ KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
78+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
79+ EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
80+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
81+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
82+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
83+ PERFORMANCE OF THIS SOFTWARE.
debian/unity8-private.install+1
@@ -1,6 +1,7 @@
11usr/lib/*/libunity8-private.*
22usr/lib/*/unity8/libUbuntuGestures*
33usr/lib/*/unity8/qml/AccountsService
4+usr/lib/*/unity8/qml/Cursor
45usr/lib/*/unity8/qml/Dash
56usr/lib/*/unity8/qml/GlobalShortcut
67usr/lib/*/unity8/qml/Greeter
debian/unity8.install+1
@@ -12,6 +12,7 @@ usr/share/unity8/Panel
1212usr/share/unity8/Rotation
1313usr/share/unity8/DeviceConfiguration.qml
1414usr/share/unity8/OrientedShell.qml
15+usr/share/unity8/DisabledScreenNotice.qml
1516usr/share/unity8/Shell.qml
1617usr/share/unity8/Stages
1718usr/share/unity8/Tutorial
plugins/CMakeLists.txt+1
@@ -12,6 +12,7 @@ macro(add_unity8_plugin PLUGIN VERSION PATH)
1212endmacro()
1313
1414add_subdirectory(AccountsService)
15+add_subdirectory(Cursor)
1516add_subdirectory(GlobalShortcut)
1617add_subdirectory(Greeter)
1718add_subdirectory(LightDM)
plugins/Cursor/3rd_party/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_subdirectory(xcursor)
plugins/Cursor/3rd_party/xcursor/CMakeLists.txtnew file+15
@@ -0,0 +1,15 @@
1+add_definitions(-D_DEFAULT_SOURCE=1)
2+
3+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
4+
5+set(
6+ XCURSOR_SOURCES
7+
8+ xcursor.c
9+)
10+
11+add_library(
12+ xcursorloader-static STATIC
13+
14+ ${XCURSOR_SOURCES}
15+)
plugins/Cursor/3rd_party/xcursor/xcursor.cnew file+968
@@ -0,0 +1,968 @@
1+/*
2+ * Copyright © 2002 Keith Packard
3+ *
4+ * Permission to use, copy, modify, distribute, and sell this software and its
5+ * documentation for any purpose is hereby granted without fee, provided that
6+ * the above copyright notice appear in all copies and that both that
7+ * copyright notice and this permission notice appear in supporting
8+ * documentation, and that the name of Keith Packard not be used in
9+ * advertising or publicity pertaining to distribution of the software without
10+ * specific, written prior permission. Keith Packard makes no
11+ * representations about the suitability of this software for any purpose. It
12+ * is provided "as is" without express or implied warranty.
13+ *
14+ * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16+ * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
20+ * PERFORMANCE OF THIS SOFTWARE.
21+ */
22+
23+#include "xcursor.h"
24+#include <stdio.h>
25+#include <stdlib.h>
26+#include <string.h>
27+#include <dirent.h>
28+
29+/*
30+ * From libXcursor/include/X11/extensions/Xcursor.h
31+ */
32+
33+#define XcursorTrue 1
34+#define XcursorFalse 0
35+
36+/*
37+ * Cursor files start with a header. The header
38+ * contains a magic number, a version number and a
39+ * table of contents which has type and offset information
40+ * for the remaining tables in the file.
41+ *
42+ * File minor versions increment for compatible changes
43+ * File major versions increment for incompatible changes (never, we hope)
44+ *
45+ * Chunks of the same type are always upward compatible. Incompatible
46+ * changes are made with new chunk types; the old data can remain under
47+ * the old type. Upward compatible changes can add header data as the
48+ * header lengths are specified in the file.
49+ *
50+ * File:
51+ * FileHeader
52+ * LISTofChunk
53+ *
54+ * FileHeader:
55+ * CARD32 magic magic number
56+ * CARD32 header bytes in file header
57+ * CARD32 version file version
58+ * CARD32 ntoc number of toc entries
59+ * LISTofFileToc toc table of contents
60+ *
61+ * FileToc:
62+ * CARD32 type entry type
63+ * CARD32 subtype entry subtype (size for images)
64+ * CARD32 position absolute file position
65+ */
66+
67+#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */
68+
69+/*
70+ * Current Xcursor version number. Will be substituted by configure
71+ * from the version in the libXcursor configure.ac file.
72+ */
73+
74+#define XCURSOR_LIB_MAJOR 1
75+#define XCURSOR_LIB_MINOR 1
76+#define XCURSOR_LIB_REVISION 13
77+#define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \
78+ (XCURSOR_LIB_MINOR * 100) + \
79+ (XCURSOR_LIB_REVISION))
80+
81+/*
82+ * This version number is stored in cursor files; changes to the
83+ * file format require updating this version number
84+ */
85+#define XCURSOR_FILE_MAJOR 1
86+#define XCURSOR_FILE_MINOR 0
87+#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR))
88+#define XCURSOR_FILE_HEADER_LEN (4 * 4)
89+#define XCURSOR_FILE_TOC_LEN (3 * 4)
90+
91+typedef struct _XcursorFileToc {
92+ XcursorUInt type; /* chunk type */
93+ XcursorUInt subtype; /* subtype (size for images) */
94+ XcursorUInt position; /* absolute position in file */
95+} XcursorFileToc;
96+
97+typedef struct _XcursorFileHeader {
98+ XcursorUInt magic; /* magic number */
99+ XcursorUInt header; /* byte length of header */
100+ XcursorUInt version; /* file version number */
101+ XcursorUInt ntoc; /* number of toc entries */
102+ XcursorFileToc *tocs; /* table of contents */
103+} XcursorFileHeader;
104+
105+/*
106+ * The rest of the file is a list of chunks, each tagged by type
107+ * and version.
108+ *
109+ * Chunk:
110+ * ChunkHeader
111+ * <extra type-specific header fields>
112+ * <type-specific data>
113+ *
114+ * ChunkHeader:
115+ * CARD32 header bytes in chunk header + type header
116+ * CARD32 type chunk type
117+ * CARD32 subtype chunk subtype
118+ * CARD32 version chunk type version
119+ */
120+
121+#define XCURSOR_CHUNK_HEADER_LEN (4 * 4)
122+
123+typedef struct _XcursorChunkHeader {
124+ XcursorUInt header; /* bytes in chunk header */
125+ XcursorUInt type; /* chunk type */
126+ XcursorUInt subtype; /* chunk subtype (size for images) */
127+ XcursorUInt version; /* version of this type */
128+} XcursorChunkHeader;
129+
130+/*
131+ * Here's a list of the known chunk types
132+ */
133+
134+/*
135+ * Comments consist of a 4-byte length field followed by
136+ * UTF-8 encoded text
137+ *
138+ * Comment:
139+ * ChunkHeader header chunk header
140+ * CARD32 length bytes in text
141+ * LISTofCARD8 text UTF-8 encoded text
142+ */
143+
144+#define XCURSOR_COMMENT_TYPE 0xfffe0001
145+#define XCURSOR_COMMENT_VERSION 1
146+#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4))
147+#define XCURSOR_COMMENT_COPYRIGHT 1
148+#define XCURSOR_COMMENT_LICENSE 2
149+#define XCURSOR_COMMENT_OTHER 3
150+#define XCURSOR_COMMENT_MAX_LEN 0x100000
151+
152+typedef struct _XcursorComment {
153+ XcursorUInt version;
154+ XcursorUInt comment_type;
155+ char *comment;
156+} XcursorComment;
157+
158+/*
159+ * Each cursor image occupies a separate image chunk.
160+ * The length of the image header follows the chunk header
161+ * so that future versions can extend the header without
162+ * breaking older applications
163+ *
164+ * Image:
165+ * ChunkHeader header chunk header
166+ * CARD32 width actual width
167+ * CARD32 height actual height
168+ * CARD32 xhot hot spot x
169+ * CARD32 yhot hot spot y
170+ * CARD32 delay animation delay
171+ * LISTofCARD32 pixels ARGB pixels
172+ */
173+
174+#define XCURSOR_IMAGE_TYPE 0xfffd0002
175+#define XCURSOR_IMAGE_VERSION 1
176+#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4))
177+#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */
178+
179+typedef struct _XcursorFile XcursorFile;
180+
181+struct _XcursorFile {
182+ void *closure;
183+ int (*read) (XcursorFile *file, unsigned char *buf, int len);
184+ int (*write) (XcursorFile *file, unsigned char *buf, int len);
185+ int (*seek) (XcursorFile *file, long offset, int whence);
186+};
187+
188+typedef struct _XcursorComments {
189+ int ncomment; /* number of comments */
190+ XcursorComment **comments; /* array of XcursorComment pointers */
191+} XcursorComments;
192+
193+/*
194+ * From libXcursor/src/file.c
195+ */
196+
197+static XcursorImage *
198+XcursorImageCreate (int width, int height)
199+{
200+ XcursorImage *image;
201+
202+ image = malloc (sizeof (XcursorImage) +
203+ width * height * sizeof (XcursorPixel));
204+ if (!image)
205+ return NULL;
206+ image->version = XCURSOR_IMAGE_VERSION;
207+ image->pixels = (XcursorPixel *) (image + 1);
208+ image->size = width > height ? width : height;
209+ image->width = width;
210+ image->height = height;
211+ image->delay = 0;
212+ return image;
213+}
214+
215+static void
216+XcursorImageDestroy (XcursorImage *image)
217+{
218+ free (image);
219+}
220+
221+static XcursorImages *
222+XcursorImagesCreate (int size)
223+{
224+ XcursorImages *images;
225+
226+ images = malloc (sizeof (XcursorImages) +
227+ size * sizeof (XcursorImage *));
228+ if (!images)
229+ return NULL;
230+ images->nimage = 0;
231+ images->images = (XcursorImage **) (images + 1);
232+ images->name = NULL;
233+ return images;
234+}
235+
236+void
237+XcursorImagesDestroy (XcursorImages *images)
238+{
239+ int n;
240+
241+ if (!images)
242+ return;
243+
244+ for (n = 0; n < images->nimage; n++)
245+ XcursorImageDestroy (images->images[n]);
246+ if (images->name)
247+ free (images->name);
248+ free (images);
249+}
250+
251+static void
252+XcursorImagesSetName (XcursorImages *images, const char *name)
253+{
254+ char *new;
255+
256+ if (!images || !name)
257+ return;
258+
259+ new = malloc (strlen (name) + 1);
260+
261+ if (!new)
262+ return;
263+
264+ strcpy (new, name);
265+ if (images->name)
266+ free (images->name);
267+ images->name = new;
268+}
269+
270+static XcursorBool
271+_XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
272+{
273+ unsigned char bytes[4];
274+
275+ if (!file || !u)
276+ return XcursorFalse;
277+
278+ if ((*file->read) (file, bytes, 4) != 4)
279+ return XcursorFalse;
280+ *u = ((bytes[0] << 0) |
281+ (bytes[1] << 8) |
282+ (bytes[2] << 16) |
283+ (bytes[3] << 24));
284+ return XcursorTrue;
285+}
286+
287+static void
288+_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
289+{
290+ free (fileHeader);
291+}
292+
293+static XcursorFileHeader *
294+_XcursorFileHeaderCreate (int ntoc)
295+{
296+ XcursorFileHeader *fileHeader;
297+
298+ if (ntoc > 0x10000)
299+ return NULL;
300+ fileHeader = malloc (sizeof (XcursorFileHeader) +
301+ ntoc * sizeof (XcursorFileToc));
302+ if (!fileHeader)
303+ return NULL;
304+ fileHeader->magic = XCURSOR_MAGIC;
305+ fileHeader->header = XCURSOR_FILE_HEADER_LEN;
306+ fileHeader->version = XCURSOR_FILE_VERSION;
307+ fileHeader->ntoc = ntoc;
308+ fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
309+ return fileHeader;
310+}
311+
312+static XcursorFileHeader *
313+_XcursorReadFileHeader (XcursorFile *file)
314+{
315+ XcursorFileHeader head, *fileHeader;
316+ XcursorUInt skip;
317+ unsigned int n;
318+
319+ if (!file)
320+ return NULL;
321+
322+ if (!_XcursorReadUInt (file, &head.magic))
323+ return NULL;
324+ if (head.magic != XCURSOR_MAGIC)
325+ return NULL;
326+ if (!_XcursorReadUInt (file, &head.header))
327+ return NULL;
328+ if (!_XcursorReadUInt (file, &head.version))
329+ return NULL;
330+ if (!_XcursorReadUInt (file, &head.ntoc))
331+ return NULL;
332+ skip = head.header - XCURSOR_FILE_HEADER_LEN;
333+ if (skip)
334+ if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
335+ return NULL;
336+ fileHeader = _XcursorFileHeaderCreate (head.ntoc);
337+ if (!fileHeader)
338+ return NULL;
339+ fileHeader->magic = head.magic;
340+ fileHeader->header = head.header;
341+ fileHeader->version = head.version;
342+ fileHeader->ntoc = head.ntoc;
343+ for (n = 0; n < fileHeader->ntoc; n++)
344+ {
345+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
346+ break;
347+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
348+ break;
349+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
350+ break;
351+ }
352+ if (n != fileHeader->ntoc)
353+ {
354+ _XcursorFileHeaderDestroy (fileHeader);
355+ return NULL;
356+ }
357+ return fileHeader;
358+}
359+
360+static XcursorBool
361+_XcursorSeekToToc (XcursorFile *file,
362+ XcursorFileHeader *fileHeader,
363+ int toc)
364+{
365+ if (!file || !fileHeader || \
366+ (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
367+ return XcursorFalse;
368+ return XcursorTrue;
369+}
370+
371+static XcursorBool
372+_XcursorFileReadChunkHeader (XcursorFile *file,
373+ XcursorFileHeader *fileHeader,
374+ int toc,
375+ XcursorChunkHeader *chunkHeader)
376+{
377+ if (!file || !fileHeader || !chunkHeader)
378+ return XcursorFalse;
379+ if (!_XcursorSeekToToc (file, fileHeader, toc))
380+ return XcursorFalse;
381+ if (!_XcursorReadUInt (file, &chunkHeader->header))
382+ return XcursorFalse;
383+ if (!_XcursorReadUInt (file, &chunkHeader->type))
384+ return XcursorFalse;
385+ if (!_XcursorReadUInt (file, &chunkHeader->subtype))
386+ return XcursorFalse;
387+ if (!_XcursorReadUInt (file, &chunkHeader->version))
388+ return XcursorFalse;
389+ /* sanity check */
390+ if (chunkHeader->type != fileHeader->tocs[toc].type ||
391+ chunkHeader->subtype != fileHeader->tocs[toc].subtype)
392+ return XcursorFalse;
393+ return XcursorTrue;
394+}
395+
396+#define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a))
397+
398+static XcursorDim
399+_XcursorFindBestSize (XcursorFileHeader *fileHeader,
400+ XcursorDim size,
401+ int *nsizesp)
402+{
403+ unsigned int n;
404+ int nsizes = 0;
405+ XcursorDim bestSize = 0;
406+ XcursorDim thisSize;
407+
408+ if (!fileHeader || !nsizesp)
409+ return 0;
410+
411+ for (n = 0; n < fileHeader->ntoc; n++)
412+ {
413+ if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
414+ continue;
415+ thisSize = fileHeader->tocs[n].subtype;
416+ if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
417+ {
418+ bestSize = thisSize;
419+ nsizes = 1;
420+ }
421+ else if (thisSize == bestSize)
422+ nsizes++;
423+ }
424+ *nsizesp = nsizes;
425+ return bestSize;
426+}
427+
428+static int
429+_XcursorFindImageToc (XcursorFileHeader *fileHeader,
430+ XcursorDim size,
431+ int count)
432+{
433+ unsigned int toc;
434+ XcursorDim thisSize;
435+
436+ if (!fileHeader)
437+ return 0;
438+
439+ for (toc = 0; toc < fileHeader->ntoc; toc++)
440+ {
441+ if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
442+ continue;
443+ thisSize = fileHeader->tocs[toc].subtype;
444+ if (thisSize != size)
445+ continue;
446+ if (!count)
447+ break;
448+ count--;
449+ }
450+ if (toc == fileHeader->ntoc)
451+ return -1;
452+ return toc;
453+}
454+
455+static XcursorImage *
456+_XcursorReadImage (XcursorFile *file,
457+ XcursorFileHeader *fileHeader,
458+ int toc)
459+{
460+ XcursorChunkHeader chunkHeader;
461+ XcursorImage head;
462+ XcursorImage *image;
463+ int n;
464+ XcursorPixel *p;
465+
466+ if (!file || !fileHeader)
467+ return NULL;
468+
469+ if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
470+ return NULL;
471+ if (!_XcursorReadUInt (file, &head.width))
472+ return NULL;
473+ if (!_XcursorReadUInt (file, &head.height))
474+ return NULL;
475+ if (!_XcursorReadUInt (file, &head.xhot))
476+ return NULL;
477+ if (!_XcursorReadUInt (file, &head.yhot))
478+ return NULL;
479+ if (!_XcursorReadUInt (file, &head.delay))
480+ return NULL;
481+ /* sanity check data */
482+ if (head.width >= 0x10000 || head.height > 0x10000)
483+ return NULL;
484+ if (head.width == 0 || head.height == 0)
485+ return NULL;
486+ if (head.xhot > head.width || head.yhot > head.height)
487+ return NULL;
488+
489+ /* Create the image and initialize it */
490+ image = XcursorImageCreate (head.width, head.height);
491+ if (image == NULL)
492+ return NULL;
493+ if (chunkHeader.version < image->version)
494+ image->version = chunkHeader.version;
495+ image->size = chunkHeader.subtype;
496+ image->xhot = head.xhot;
497+ image->yhot = head.yhot;
498+ image->delay = head.delay;
499+ n = image->width * image->height;
500+ p = image->pixels;
501+ while (n--)
502+ {
503+ if (!_XcursorReadUInt (file, p))
504+ {
505+ XcursorImageDestroy (image);
506+ return NULL;
507+ }
508+ p++;
509+ }
510+ return image;
511+}
512+
513+static XcursorImages *
514+XcursorXcFileLoadImages (XcursorFile *file, int size)
515+{
516+ XcursorFileHeader *fileHeader;
517+ XcursorDim bestSize;
518+ int nsize;
519+ XcursorImages *images;
520+ int n;
521+ int toc;
522+
523+ if (!file || size < 0)
524+ return NULL;
525+ fileHeader = _XcursorReadFileHeader (file);
526+ if (!fileHeader)
527+ return NULL;
528+ bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
529+ if (!bestSize)
530+ {
531+ _XcursorFileHeaderDestroy (fileHeader);
532+ return NULL;
533+ }
534+ images = XcursorImagesCreate (nsize);
535+ if (!images)
536+ {
537+ _XcursorFileHeaderDestroy (fileHeader);
538+ return NULL;
539+ }
540+ for (n = 0; n < nsize; n++)
541+ {
542+ toc = _XcursorFindImageToc (fileHeader, bestSize, n);
543+ if (toc < 0)
544+ break;
545+ images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
546+ toc);
547+ if (!images->images[images->nimage])
548+ break;
549+ images->nimage++;
550+ }
551+ _XcursorFileHeaderDestroy (fileHeader);
552+ if (images->nimage != nsize)
553+ {
554+ XcursorImagesDestroy (images);
555+ images = NULL;
556+ }
557+ return images;
558+}
559+
560+static int
561+_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
562+{
563+ FILE *f = file->closure;
564+ return fread (buf, 1, len, f);
565+}
566+
567+static int
568+_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
569+{
570+ FILE *f = file->closure;
571+ return fwrite (buf, 1, len, f);
572+}
573+
574+static int
575+_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
576+{
577+ FILE *f = file->closure;
578+ return fseek (f, offset, whence);
579+}
580+
581+static void
582+_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
583+{
584+ file->closure = stdfile;
585+ file->read = _XcursorStdioFileRead;
586+ file->write = _XcursorStdioFileWrite;
587+ file->seek = _XcursorStdioFileSeek;
588+}
589+
590+static XcursorImages *
591+XcursorFileLoadImages (FILE *file, int size)
592+{
593+ XcursorFile f;
594+
595+ if (!file)
596+ return NULL;
597+
598+ _XcursorStdioFileInitialize (file, &f);
599+ return XcursorXcFileLoadImages (&f, size);
600+}
601+
602+/*
603+ * From libXcursor/src/library.c
604+ */
605+
606+#ifndef ICONDIR
607+#define ICONDIR "/usr/X11R6/lib/X11/icons"
608+#endif
609+
610+#ifndef XCURSORPATH
611+#define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR
612+#endif
613+
614+static const char *
615+XcursorLibraryPath (void)
616+{
617+ static const char *path;
618+
619+ if (!path)
620+ {
621+ path = getenv ("XCURSOR_PATH");
622+ if (!path)
623+ path = XCURSORPATH;
624+ }
625+ return path;
626+}
627+
628+static void
629+_XcursorAddPathElt (char *path, const char *elt, int len)
630+{
631+ int pathlen = strlen (path);
632+
633+ /* append / if the path doesn't currently have one */
634+ if (path[0] == '\0' || path[pathlen - 1] != '/')
635+ {
636+ strcat (path, "/");
637+ pathlen++;
638+ }
639+ if (len == -1)
640+ len = strlen (elt);
641+ /* strip leading slashes */
642+ while (len && elt[0] == '/')
643+ {
644+ elt++;
645+ len--;
646+ }
647+ strncpy (path + pathlen, elt, len);
648+ path[pathlen + len] = '\0';
649+}
650+
651+static char *
652+_XcursorBuildThemeDir (const char *dir, const char *theme)
653+{
654+ const char *colon;
655+ const char *tcolon;
656+ char *full;
657+ char *home;
658+ int dirlen;
659+ int homelen;
660+ int themelen;
661+ int len;
662+
663+ if (!dir || !theme)
664+ return NULL;
665+
666+ colon = strchr (dir, ':');
667+ if (!colon)
668+ colon = dir + strlen (dir);
669+
670+ dirlen = colon - dir;
671+
672+ tcolon = strchr (theme, ':');
673+ if (!tcolon)
674+ tcolon = theme + strlen (theme);
675+
676+ themelen = tcolon - theme;
677+
678+ home = NULL;
679+ homelen = 0;
680+ if (*dir == '~')
681+ {
682+ home = getenv ("HOME");
683+ if (!home)
684+ return NULL;
685+ homelen = strlen (home);
686+ dir++;
687+ dirlen--;
688+ }
689+
690+ /*
691+ * add space for any needed directory separators, one per component,
692+ * and one for the trailing null
693+ */
694+ len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
695+
696+ full = malloc (len);
697+ if (!full)
698+ return NULL;
699+ full[0] = '\0';
700+
701+ if (home)
702+ _XcursorAddPathElt (full, home, -1);
703+ _XcursorAddPathElt (full, dir, dirlen);
704+ _XcursorAddPathElt (full, theme, themelen);
705+ return full;
706+}
707+
708+static char *
709+_XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
710+{
711+ char *full;
712+
713+ if (!dir || !subdir || !file)
714+ return NULL;
715+
716+ full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
717+ if (!full)
718+ return NULL;
719+ full[0] = '\0';
720+ _XcursorAddPathElt (full, dir, -1);
721+ _XcursorAddPathElt (full, subdir, -1);
722+ _XcursorAddPathElt (full, file, -1);
723+ return full;
724+}
725+
726+static const char *
727+_XcursorNextPath (const char *path)
728+{
729+ char *colon = strchr (path, ':');
730+
731+ if (!colon)
732+ return NULL;
733+ return colon + 1;
734+}
735+
736+#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
737+#define XcursorSep(c) ((c) == ';' || (c) == ',')
738+
739+static char *
740+_XcursorThemeInherits (const char *full)
741+{
742+ char line[8192];
743+ char *result = NULL;
744+ FILE *f;
745+
746+ if (!full)
747+ return NULL;
748+
749+ f = fopen (full, "r");
750+ if (f)
751+ {
752+ while (fgets (line, sizeof (line), f))
753+ {
754+ if (!strncmp (line, "Inherits", 8))
755+ {
756+ char *l = line + 8;
757+ char *r;
758+ while (*l == ' ') l++;
759+ if (*l != '=') continue;
760+ l++;
761+ while (*l == ' ') l++;
762+ result = malloc (strlen (l) + 1);
763+ if (result)
764+ {
765+ r = result;
766+ while (*l)
767+ {
768+ while (XcursorSep(*l) || XcursorWhite (*l)) l++;
769+ if (!*l)
770+ break;
771+ if (r != result)
772+ *r++ = ':';
773+ while (*l && !XcursorWhite(*l) &&
774+ !XcursorSep(*l))
775+ *r++ = *l++;
776+ }
777+ *r++ = '\0';
778+ }
779+ break;
780+ }
781+ }
782+ fclose (f);
783+ }
784+ return result;
785+}
786+
787+static FILE *
788+XcursorScanTheme (const char *theme, const char *name)
789+{
790+ FILE *f = NULL;
791+ char *full;
792+ char *dir;
793+ const char *path;
794+ char *inherits = NULL;
795+ const char *i;
796+
797+ if (!theme || !name)
798+ return NULL;
799+
800+ /*
801+ * Scan this theme
802+ */
803+ for (path = XcursorLibraryPath ();
804+ path && f == NULL;
805+ path = _XcursorNextPath (path))
806+ {
807+ dir = _XcursorBuildThemeDir (path, theme);
808+ if (dir)
809+ {
810+ full = _XcursorBuildFullname (dir, "cursors", name);
811+ if (full)
812+ {
813+ f = fopen (full, "r");
814+ free (full);
815+ }
816+ if (!f && !inherits)
817+ {
818+ full = _XcursorBuildFullname (dir, "", "index.theme");
819+ if (full)
820+ {
821+ inherits = _XcursorThemeInherits (full);
822+ free (full);
823+ }
824+ }
825+ free (dir);
826+ }
827+ }
828+ /*
829+ * Recurse to scan inherited themes
830+ */
831+ for (i = inherits; i && f == NULL; i = _XcursorNextPath (i))
832+ f = XcursorScanTheme (i, name);
833+ if (inherits != NULL)
834+ free (inherits);
835+ return f;
836+}
837+
838+XcursorImages *
839+XcursorLibraryLoadImages (const char *file, const char *theme, int size)
840+{
841+ FILE *f = NULL;
842+ XcursorImages *images = NULL;
843+
844+ if (!file)
845+ return NULL;
846+
847+ if (theme)
848+ f = XcursorScanTheme (theme, file);
849+ if (!f)
850+ f = XcursorScanTheme ("default", file);
851+ if (f)
852+ {
853+ images = XcursorFileLoadImages (f, size);
854+ if (images)
855+ XcursorImagesSetName (images, file);
856+ fclose (f);
857+ }
858+ return images;
859+}
860+
861+static void
862+load_all_cursors_from_dir(const char *path, int size,
863+ void (*load_callback)(XcursorImages *, void *),
864+ void *user_data)
865+{
866+ FILE *f;
867+ DIR *dir = opendir(path);
868+ struct dirent *ent;
869+ char *full;
870+ XcursorImages *images;
871+
872+ if (!dir)
873+ return;
874+
875+ for(ent = readdir(dir); ent; ent = readdir(dir)) {
876+#ifdef _DIRENT_HAVE_D_TYPE
877+ if (ent->d_type != DT_UNKNOWN &&
878+ (ent->d_type != DT_REG && ent->d_type != DT_LNK))
879+ continue;
880+#endif
881+
882+ full = _XcursorBuildFullname(path, "", ent->d_name);
883+ if (!full)
884+ continue;
885+
886+ f = fopen(full, "r");
887+ if (!f) {
888+ free(full);
889+ continue;
890+ }
891+
892+ images = XcursorFileLoadImages(f, size);
893+
894+ if (images) {
895+ XcursorImagesSetName(images, ent->d_name);
896+ load_callback(images, user_data);
897+ }
898+
899+ fclose (f);
900+ free(full);
901+ }
902+
903+ closedir(dir);
904+}
905+
906+/** Load all the cursor of a theme
907+ *
908+ * This function loads all the cursor images of a given theme and its
909+ * inherited themes. Each cursor is loaded into an XcursorImages object
910+ * which is passed to the caller's load callback. If a cursor appears
911+ * more than once across all the inherited themes, the load callback
912+ * will be called multiple times, with possibly different XcursorImages
913+ * object which have the same name. The user is expected to destroy the
914+ * XcursorImages objects passed to the callback with
915+ * XcursorImagesDestroy().
916+ *
917+ * \param theme The name of theme that should be loaded
918+ * \param size The desired size of the cursor images
919+ * \param load_callback A callback function that will be called
920+ * for each cursor loaded. The first parameter is the XcursorImages
921+ * object representing the loaded cursor and the second is a pointer
922+ * to data provided by the user.
923+ * \param user_data The data that should be passed to the load callback
924+ */
925+void
926+xcursor_load_theme(const char *theme, int size,
927+ void (*load_callback)(XcursorImages *, void *),
928+ void *user_data)
929+{
930+ char *full, *dir;
931+ char *inherits = NULL;
932+ const char *path, *i;
933+
934+ if (!theme)
935+ theme = "default";
936+
937+ for (path = XcursorLibraryPath();
938+ path;
939+ path = _XcursorNextPath(path)) {
940+ dir = _XcursorBuildThemeDir(path, theme);
941+ if (!dir)
942+ continue;
943+
944+ full = _XcursorBuildFullname(dir, "cursors", "");
945+
946+ if (full) {
947+ load_all_cursors_from_dir(full, size, load_callback,
948+ user_data);
949+ free(full);
950+ }
951+
952+ if (!inherits) {
953+ full = _XcursorBuildFullname(dir, "", "index.theme");
954+ if (full) {
955+ inherits = _XcursorThemeInherits(full);
956+ free(full);
957+ }
958+ }
959+
960+ free(dir);
961+ }
962+
963+ for (i = inherits; i; i = _XcursorNextPath(i))
964+ xcursor_load_theme(i, size, load_callback, user_data);
965+
966+ if (inherits)
967+ free(inherits);
968+}
plugins/Cursor/3rd_party/xcursor/xcursor.hnew file+65
@@ -0,0 +1,65 @@
1+/*
2+ * Copyright © 2002 Keith Packard
3+ *
4+ * Permission to use, copy, modify, distribute, and sell this software and its
5+ * documentation for any purpose is hereby granted without fee, provided that
6+ * the above copyright notice appear in all copies and that both that
7+ * copyright notice and this permission notice appear in supporting
8+ * documentation, and that the name of Keith Packard not be used in
9+ * advertising or publicity pertaining to distribution of the software without
10+ * specific, written prior permission. Keith Packard makes no
11+ * representations about the suitability of this software for any purpose. It
12+ * is provided "as is" without express or implied warranty.
13+ *
14+ * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16+ * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
20+ * PERFORMANCE OF THIS SOFTWARE.
21+ */
22+
23+#ifndef XCURSOR_H
24+#define XCURSOR_H
25+
26+#include <stdint.h>
27+
28+
29+typedef int XcursorBool;
30+typedef uint32_t XcursorUInt;
31+
32+typedef XcursorUInt XcursorDim;
33+typedef XcursorUInt XcursorPixel;
34+
35+typedef struct _XcursorImage {
36+ XcursorUInt version; /* version of the image data */
37+ XcursorDim size; /* nominal size for matching */
38+ XcursorDim width; /* actual width */
39+ XcursorDim height; /* actual height */
40+ XcursorDim xhot; /* hot spot x (must be inside image) */
41+ XcursorDim yhot; /* hot spot y (must be inside image) */
42+ XcursorUInt delay; /* animation delay to next frame (ms) */
43+ XcursorPixel *pixels; /* pointer to pixels */
44+} XcursorImage;
45+
46+/*
47+ * Other data structures exposed by the library API
48+ */
49+typedef struct _XcursorImages {
50+ int nimage; /* number of images */
51+ XcursorImage **images; /* array of XcursorImage pointers */
52+ char *name; /* name used to load images */
53+} XcursorImages;
54+
55+XcursorImages *
56+XcursorLibraryLoadImages (const char *file, const char *theme, int size);
57+
58+void
59+XcursorImagesDestroy (XcursorImages *images);
60+
61+void
62+xcursor_load_theme(const char *theme, int size,
63+ void (*load_callback)(XcursorImages *, void *),
64+ void *user_data);
65+#endif
plugins/Cursor/CMakeLists.txtnew file+28
@@ -0,0 +1,28 @@
1+add_subdirectory(3rd_party)
2+
3+include_directories(
4+ ${CMAKE_CURRENT_SOURCE_DIR}
5+ ${CMAKE_CURRENT_SOURCE_DIR}/3rd_party/xcursor
6+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7+)
8+
9+set(QMLPLUGIN_SRC
10+ plugin.cpp
11+ MousePointer.cpp
12+ CursorImageProvider.cpp
13+ # We need to run moc on this header
14+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
15+ )
16+
17+add_library(Cursor-qml SHARED
18+ ${QMLPLUGIN_SRC}
19+ )
20+
21+target_link_libraries(Cursor-qml
22+ xcursorloader-static
23+ ${QT5PLATFORM_SUPPORT_LDFLAGS}
24+)
25+
26+qt5_use_modules(Cursor-qml Qml Quick DBus Network Gui Sql Concurrent Svg)
27+
28+add_unity8_plugin(Cursor 1.0 Cursor TARGETS Cursor-qml)
plugins/Cursor/Cursor.qmlnew file+28
@@ -0,0 +1,28 @@
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+import QtQuick 2.4
18+import Cursor 1.0 // For MousePointer
19+
20+MousePointer {
21+ id: mousePointer
22+
23+ Image {
24+ x: -mousePointer.hotspotX
25+ y: -mousePointer.hotspotY
26+ source: "image://cursor/" + mousePointer.themeName + "/" + mousePointer.cursorName
27+ }
28+}
plugins/Cursor/CursorImageProvider.cppnew file+191
@@ -0,0 +1,191 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "CursorImageProvider.h"
18+
19+#include <QDebug>
20+#include <QFile>
21+#include <QPainter>
22+#include <QSvgRenderer>
23+
24+CursorImageProvider *CursorImageProvider::m_instance = nullptr;
25+
26+/////
27+// BuiltInCursorImage
28+
29+BuiltInCursorImage::BuiltInCursorImage()
30+{
31+ const char *svgString =
32+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
33+ "<svg"
34+ " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
35+ " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
36+ " xmlns:svg=\"http://www.w3.org/2000/svg\""
37+ " xmlns=\"http://www.w3.org/2000/svg\""
38+ " version=\"1.1\">"
39+ " <path"
40+ " style=\"fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:40;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\""
41+ " d=\"M 20.504,50.94931 460.42533,518.14486 266.47603,515.61948 366.48114,719.16522 274.05218,770.68296 172.53185,559.56112 20.504,716.13476 Z\" />"
42+ "</svg>";
43+
44+ qimage = QImage(20, 32, QImage::Format_ARGB32);
45+ QPainter imagePainter(&qimage);
46+
47+ QSvgRenderer *svgRenderer = new QSvgRenderer(QByteArray(svgString));
48+ svgRenderer->render(&imagePainter);
49+ delete svgRenderer;
50+}
51+
52+/////
53+// XCursorImage
54+
55+XCursorImage::XCursorImage(const QString &theme, const QString &file)
56+ : xcursorImages(nullptr)
57+{
58+ xcursorImages = XcursorLibraryLoadImages(QFile::encodeName(file), QFile::encodeName(theme), 32);
59+ if (!xcursorImages) {
60+ return;
61+ }
62+
63+ bool loaded = false;
64+ for (int i = 0; i < xcursorImages->nimage && !loaded; ++i) {
65+ XcursorImage *xcursorImage = xcursorImages->images[i];
66+ if (xcursorImage->size == 32) {
67+
68+ qimage = QImage((uchar*)xcursorImage->pixels,
69+ xcursorImage->width, xcursorImage->height, QImage::Format_ARGB32);
70+
71+ hotspot.setX(xcursorImage->xhot);
72+ hotspot.setY(xcursorImage->yhot);
73+
74+ loaded = true;
75+ }
76+ }
77+}
78+
79+XCursorImage::~XCursorImage()
80+{
81+ XcursorImagesDestroy(xcursorImages);
82+}
83+
84+/////
85+// CursorImageProvider
86+
87+CursorImageProvider::CursorImageProvider()
88+ : QQuickImageProvider(QQuickImageProvider::Image)
89+{
90+ if (m_instance) {
91+ qFatal("Cannot have multiple CursorImageProvider instances");
92+ }
93+ m_instance = this;
94+}
95+
96+CursorImageProvider::~CursorImageProvider()
97+{
98+ {
99+ QList< QMap<QString, CursorImage*> > cursorList = m_cursors.values();
100+
101+ for (int i = 0; i < cursorList.count(); ++i) {
102+ QList<CursorImage*> cursorImageList = cursorList[i].values();
103+ for (int j = 0; j < cursorImageList.count(); ++j) {
104+ delete cursorImageList[j];
105+ }
106+ }
107+ }
108+
109+ m_cursors.clear();
110+ m_instance = nullptr;
111+}
112+
113+QImage CursorImageProvider::requestImage(const QString &cursorThemeAndName, QSize *size, const QSize & /*requestedSize*/)
114+{
115+ CursorImage *cursorImage = fetchCursor(cursorThemeAndName);
116+ size->setWidth(cursorImage->qimage.width());
117+ size->setHeight(cursorImage->qimage.height());
118+
119+ return cursorImage->qimage;
120+}
121+
122+QPoint CursorImageProvider::hotspot(const QString &themeName, const QString &cursorName)
123+{
124+ CursorImage *cursorImage = fetchCursor(themeName, cursorName);
125+ if (cursorImage) {
126+ return cursorImage->hotspot;
127+ } else {
128+ return QPoint(0,0);
129+ }
130+}
131+
132+CursorImage *CursorImageProvider::fetchCursor(const QString &cursorThemeAndName)
133+{
134+ QString themeName;
135+ QString cursorName;
136+ {
137+ QStringList themeAndNameList = cursorThemeAndName.split("/");
138+ if (themeAndNameList.size() != 2) {
139+ return nullptr;
140+ }
141+ themeName = themeAndNameList[0];
142+ cursorName = themeAndNameList[1];
143+ }
144+
145+ return fetchCursor(themeName, cursorName);
146+}
147+
148+CursorImage *CursorImageProvider::fetchCursor(const QString &themeName, const QString &cursorName)
149+{
150+ CursorImage *cursorImage = fetchCursorHelper(themeName, cursorName);
151+
152+ // Try some fallbacks
153+ if (cursorImage->qimage.isNull()) {
154+ if (cursorName == "ibeam") {
155+ qDebug() << "CursorImageProvider: \"ibeam\" not found, falling back to \"xterm\"";
156+ cursorImage = fetchCursorHelper(themeName, "xterm");
157+ } else if (cursorName == "xterm") {
158+ qDebug() << "CursorImageProvider: \"xterm\" not found, falling back to \"ibeam\"";
159+ cursorImage = fetchCursorHelper(themeName, "ibeam");
160+ }
161+ }
162+
163+ // if it all fails, there must be at least a left_ptr
164+ if (cursorImage->qimage.isNull() && cursorName != "left_ptr") {
165+ qDebug() << "CursorImageProvider:" << cursorName
166+ << "not found (nor its fallbacks, if any). Going for \"left_ptr\" as a last resort.";
167+ cursorImage = fetchCursorHelper(themeName, "left_ptr");
168+ }
169+
170+ if (cursorImage->qimage.isNull()) {
171+ // finally, go for the built-in cursor
172+ qWarning() << "CursorImageProvider: couldn't find any cursors. Using the built-in one";
173+ if (!m_builtInCursorImage) {
174+ m_builtInCursorImage.reset(new BuiltInCursorImage);
175+ }
176+ cursorImage = m_builtInCursorImage.data();
177+ }
178+
179+ return cursorImage;
180+}
181+
182+CursorImage *CursorImageProvider::fetchCursorHelper(const QString &themeName, const QString &cursorName)
183+{
184+ QMap<QString, CursorImage*> &themeCursors = m_cursors[themeName];
185+
186+ if (!themeCursors.contains(cursorName)) {
187+ themeCursors[cursorName] = new XCursorImage(themeName, cursorName);
188+ }
189+
190+ return themeCursors[cursorName];
191+}
plugins/Cursor/CursorImageProvider.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 it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef CURSORIMAGEPROVIDER_H
18+#define CURSORIMAGEPROVIDER_H
19+
20+#include <QQuickImageProvider>
21+#include <QScopedPointer>
22+
23+// xcursor static lib
24+extern "C"
25+{
26+#include <xcursor.h>
27+}
28+
29+class CursorImage {
30+public:
31+ virtual ~CursorImage() {}
32+
33+ QImage qimage;
34+ QPoint hotspot;
35+};
36+
37+class XCursorImage : public CursorImage {
38+public:
39+ XCursorImage(const QString &theme, const QString &file);
40+ virtual ~XCursorImage();
41+
42+ XcursorImages *xcursorImages;
43+};
44+
45+class BuiltInCursorImage : public CursorImage {
46+public:
47+ BuiltInCursorImage();
48+};
49+
50+class CursorImageProvider : public QQuickImageProvider
51+{
52+public:
53+ CursorImageProvider();
54+ virtual ~CursorImageProvider();
55+
56+ static CursorImageProvider *instance() { return m_instance; }
57+
58+
59+ QImage requestImage(const QString &cursorName, QSize *size, const QSize &requestedSize) override;
60+
61+ QPoint hotspot(const QString &themeName, const QString &cursorName);
62+
63+private:
64+ CursorImage *fetchCursor(const QString &cursorThemeAndName);
65+ CursorImage *fetchCursor(const QString &themeName, const QString &cursorName);
66+ CursorImage *fetchCursorHelper(const QString &themeName, const QString &cursorName);
67+
68+ // themeName -> (cursorName -> cursorImage)
69+ QMap<QString, QMap<QString, CursorImage*> > m_cursors;
70+
71+ QScopedPointer<CursorImage> m_builtInCursorImage;
72+
73+ static CursorImageProvider *m_instance;
74+};
75+
76+#endif // CURSORIMAGEPROVIDER_H
plugins/Cursor/MousePointer.cppnew file+125
@@ -0,0 +1,125 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "MousePointer.h"
18+#include "CursorImageProvider.h"
19+
20+// Unity API
21+#include <unity/shell/application/MirPlatformCursor.h>
22+
23+#include <QQuickWindow>
24+#include <QGuiApplication>
25+
26+#include <qpa/qwindowsysteminterface.h>
27+
28+MousePointer::MousePointer(QQuickItem *parent)
29+ : MirMousePointerInterface(parent)
30+ , m_cursorName("left_ptr")
31+ , m_themeName("default")
32+ , m_hotspotX(0)
33+ , m_hotspotY(0)
34+{
35+}
36+
37+void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
38+ Qt::KeyboardModifiers modifiers)
39+{
40+ if (!parentItem()) {
41+ return;
42+ }
43+
44+ qreal newX = x() + movement.x();
45+ if (newX < 0) {
46+ newX = 0;
47+ } else if (newX > parentItem()->width()) {
48+ newX = parentItem()->width();
49+ }
50+ setX(newX);
51+
52+ qreal newY = y() + movement.y();
53+ if (newY < 0) {
54+ newY = 0;
55+ } else if (newY > parentItem()->height()) {
56+ newY = parentItem()->height();
57+ }
58+ setY(newY);
59+
60+ QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
61+ QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
62+ buttons, modifiers);
63+}
64+
65+void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
66+{
67+ if (change == ItemSceneChange) {
68+ registerWindow(value.window);
69+ }
70+}
71+
72+void MousePointer::registerWindow(QWindow *window)
73+{
74+ if (m_registeredWindow && window != m_registeredWindow) {
75+ auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredWindow->screen()->handle()->cursor());
76+ if (previousCursor) {
77+ previousCursor->setMousePointer(nullptr);
78+ } else {
79+ qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
80+ }
81+ }
82+
83+ m_registeredWindow = window;
84+
85+ if (m_registeredWindow) {
86+ auto cursor = dynamic_cast<MirPlatformCursor*>(window->screen()->handle()->cursor());
87+ if (cursor) {
88+ cursor->setMousePointer(this);
89+ } else {
90+ qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
91+ }
92+ }
93+}
94+
95+void MousePointer::setCursorName(const QString &cursorName)
96+{
97+ if (cursorName != m_cursorName) {
98+ m_cursorName = cursorName;
99+ Q_EMIT cursorNameChanged(m_cursorName);
100+ updateHotspot();
101+ }
102+}
103+
104+void MousePointer::updateHotspot()
105+{
106+ QPoint newHotspot = CursorImageProvider::instance()->hotspot(m_themeName, m_cursorName);
107+
108+ if (m_hotspotX != newHotspot.x()) {
109+ m_hotspotX = newHotspot.x();
110+ Q_EMIT hotspotXChanged(m_hotspotX);
111+ }
112+
113+ if (m_hotspotY != newHotspot.y()) {
114+ m_hotspotY = newHotspot.y();
115+ Q_EMIT hotspotYChanged(m_hotspotY);
116+ }
117+}
118+
119+void MousePointer::setThemeName(const QString &themeName)
120+{
121+ if (m_themeName != themeName) {
122+ m_themeName = themeName;
123+ Q_EMIT themeNameChanged(m_themeName);
124+ }
125+}
plugins/Cursor/MousePointer.hnew file+59
@@ -0,0 +1,59 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef MOUSEPOINTER_H
18+#define MOUSEPOINTER_H
19+
20+// Qt
21+#include <QPointer>
22+#include <QWindow>
23+
24+// Unity API
25+#include <unity/shell/application/MirMousePointerInterface.h>
26+
27+class MousePointer : public MirMousePointerInterface {
28+ Q_OBJECT
29+public:
30+ MousePointer(QQuickItem *parent = nullptr);
31+
32+ void setCursorName(const QString &qtCursorName) override;
33+ QString cursorName() const override { return m_cursorName; }
34+
35+ void setThemeName(const QString &themeName) override;
36+ QString themeName() const override { return m_themeName; }
37+
38+ qreal hotspotX() const override { return m_hotspotX; }
39+ qreal hotspotY() const override { return m_hotspotY; }
40+
41+public Q_SLOTS:
42+ void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
43+ Qt::KeyboardModifiers modifiers) override;
44+
45+protected:
46+ void itemChange(ItemChange change, const ItemChangeData &value) override;
47+
48+private:
49+ void registerWindow(QWindow *window);
50+ void updateHotspot();
51+
52+ QPointer<QWindow> m_registeredWindow;
53+ QString m_cursorName;
54+ QString m_themeName;
55+ int m_hotspotX;
56+ int m_hotspotY;
57+};
58+
59+#endif // MOUSEPOINTER_H
plugins/Cursor/plugin.cppnew file+39
@@ -0,0 +1,39 @@
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+// Qt
18+#include <QtQml/qqml.h>
19+#include <QQmlContext>
20+
21+// self
22+#include "plugin.h"
23+
24+// local
25+#include "CursorImageProvider.h"
26+#include "MousePointer.h"
27+
28+void CursorPlugin::registerTypes(const char *uri)
29+{
30+ Q_ASSERT(uri == QLatin1String("Cursor"));
31+ qmlRegisterType<MousePointer>(uri, 1, 0, "MousePointer");
32+}
33+
34+void CursorPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
35+{
36+ QQmlExtensionPlugin::initializeEngine(engine, uri);
37+
38+ engine->addImageProvider(QLatin1String("cursor"), new CursorImageProvider());
39+}
plugins/Cursor/plugin.hnew file+33
@@ -0,0 +1,33 @@
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 CURSOR_PLUGIN_H
18+#define CURSOR_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class CursorPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri) override;
30+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
31+};
32+
33+#endif // CURSOR_PLUGIN_H
plugins/Cursor/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Cursor
2+plugin Cursor-qml
3+Cursor 1.0 Cursor.qml
plugins/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+2−1
@@ -34,7 +34,8 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
3434 entries.reserve(users.count());
3535 Q_FOREACH(const QString &user, users)
3636 {
37− QString name = settings.value(user + "/name", user[0].toUpper() + user.mid(1)).toString();
37+ QVariant defaultValue = QString(user[0].toUpper() + user.mid(1));
38+ QString name = settings.value(user + "/name", defaultValue).toString();
3839 entries.append({user, name, 0, 0, false, false, 0, 0});
3940 }
4041}
plugins/LightDM/UsersModel.cpp+7−6
@@ -43,16 +43,17 @@ MangleModel::MangleModel(QObject* parent)
4343
4444QVariant MangleModel::data(const QModelIndex &index, int role) const
4545{
46− QVariant data = QSortFilterProxyModel::data(index, role);
46+ QVariant variantData = QSortFilterProxyModel::data(index, role);
4747
4848 // If user's real name is empty, switch to unix name
49− if (role == QLightDM::UsersModel::RealNameRole && data.toString().isEmpty()) {
50− data = QSortFilterProxyModel::data(index, QLightDM::UsersModel::NameRole);
51− } else if (role == QLightDM::UsersModel::BackgroundPathRole && data.toString().startsWith('#')) {
52− data = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + data.toString() + "'/></svg>";
49+ if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
50+ variantData = QSortFilterProxyModel::data(index, QLightDM::UsersModel::NameRole);
51+ } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
52+ const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
53+ variantData = stringData;
5354 }
5455
55− return data;
56+ return variantData;
5657}
5758
5859// **** Now we continue with actual UsersModel class ****
plugins/ScreenGrabber/screengrabber.cpp+2−2
@@ -60,7 +60,7 @@ ScreenGrabber::ScreenGrabber(QObject *parent)
6060 }
6161}
6262
63−void ScreenGrabber::captureAndSave()
63+void ScreenGrabber::captureAndSave(int angle)
6464{
6565 if (fileNamePrefix.isEmpty())
6666 {
@@ -82,7 +82,7 @@ void ScreenGrabber::captureAndSave()
8282 return;
8383 }
8484
85− const QImage screenshot = main_window->grabWindow();
85+ const QImage screenshot = main_window->grabWindow().transformed(QTransform().rotate(angle));
8686 const QString filename = makeFileName();
8787 qDebug() << "Saving screenshot to" << filename;
8888 auto saveOp = QtConcurrent::run(saveScreenshot, screenshot, filename, getFormat(), screenshotQuality);
plugins/ScreenGrabber/screengrabber.h+1−1
@@ -29,7 +29,7 @@ public:
2929 ~ScreenGrabber() = default;
3030
3131public Q_SLOTS:
32− void captureAndSave();
32+ void captureAndSave(int angle = 0);
3333
3434Q_SIGNALS:
3535 void screenshotSaved(const QString &filename);
plugins/Unity/CMakeLists.txt+1
@@ -4,3 +4,4 @@ add_subdirectory(Launcher)
44add_subdirectory(Session)
55add_subdirectory(DashCommunicator)
66add_subdirectory(InputInfo)
7+add_subdirectory(Platform)
plugins/Unity/Launcher/desktopfilehandler.cpp+3−2
@@ -111,8 +111,9 @@ QString DesktopFileHandler::displayName() const
111111 settings.beginGroup(QStringLiteral("Desktop Entry"));
112112
113113 // First try to find Name[xx_YY] and Name[xx] in .desktop file
114− QString locale = QLocale().name();
115− QString shortLocale = locale.split('_').first();
114+ const QString locale = QLocale().name();
115+ const QStringList splitLocale = locale.split(QLatin1Char('_'));
116+ const QString shortLocale = splitLocale.first();
116117
117118 if (locale != shortLocale && settings.contains(QStringLiteral("Name[%1]").arg(locale))) {
118119 return settings.value(QStringLiteral("Name[%1]").arg(locale)).toString();
plugins/Unity/Platform/CMakeLists.txtnew file+9
@@ -0,0 +1,9 @@
1+set(platformplugin_SRCS
2+ platform.cpp
3+ plugin.cpp)
4+
5+add_library(Platform-qml SHARED ${platformplugin_SRCS})
6+
7+qt5_use_modules(Platform-qml DBus Qml)
8+
9+add_unity8_plugin(Unity.Platform 1.0 Unity/Platform TARGETS Platform-qml)
plugins/Unity/Platform/platform.cppnew file+43
@@ -0,0 +1,43 @@
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 "platform.h"
18+
19+#include <QDBusConnection>
20+
21+Platform::Platform(QObject *parent)
22+ : QObject(parent)
23+ , m_iface("org.freedesktop.hostname1", "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
24+ QDBusConnection::systemBus(), this)
25+{
26+ QMetaObject::invokeMethod(this, "init");
27+}
28+
29+void Platform::init()
30+{
31+ m_chassis = m_iface.property("Chassis").toString();
32+ m_isPC = (m_chassis == "desktop" || m_chassis == "laptop" || m_chassis == "server");
33+}
34+
35+QString Platform::chassis() const
36+{
37+ return m_chassis;
38+}
39+
40+bool Platform::isPC() const
41+{
42+ return m_isPC;
43+}
plugins/Unity/Platform/platform.hnew file+58
@@ -0,0 +1,58 @@
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 PLATFORM_H
18+#define PLATFORM_H
19+
20+#include <QDBusInterface>
21+
22+/**
23+ * @brief The Platform class
24+ *
25+ * Wrapper around platform detection support (org.freedesktop.hostname1)
26+ */
27+class Platform: public QObject
28+{
29+ Q_OBJECT
30+ /**
31+ * The chassis property
32+ *
33+ * Supported values include: "laptop", "computer", "handset" or "tablet"
34+ * For full list see: http://www.freedesktop.org/wiki/Software/systemd/hostnamed/
35+ */
36+ Q_PROPERTY(QString chassis READ chassis CONSTANT)
37+ /**
38+ * Whether the machine is an ordinary PC (desktop, laptop or server)
39+ */
40+ Q_PROPERTY(bool isPC READ isPC CONSTANT)
41+
42+public:
43+ Platform(QObject *parent = nullptr);
44+ ~Platform() = default;
45+
46+ QString chassis() const;
47+ bool isPC() const;
48+
49+private Q_SLOTS:
50+ void init();
51+
52+private:
53+ QDBusInterface m_iface;
54+ QString m_chassis;
55+ bool m_isPC;
56+};
57+
58+#endif // PLATFORM_H
plugins/Unity/Platform/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 "platform.h"
19+
20+#include <QtQml>
21+
22+void GlobalShortcutPlugin::registerTypes(const char *uri)
23+{
24+ Q_ASSERT(uri == QLatin1String("Unity.Platform"));
25+
26+ qmlRegisterSingletonType<Platform>(uri, 1, 0, "Platform", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Platform; });
27+}
plugins/Utils/relativetimeformatter.h → plugins/Unity/Platform/plugin.hrenamed+10−12
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright (C) 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
@@ -12,23 +12,21 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
1615 */
1716
18−#ifndef RELATIVETIMEFORMATTER_H
19−#define RELATIVETIMEFORMATTER_H
17+#ifndef PLATFORMPLUGIN_H
18+#define PLATFORMPLUGIN_H
2019
21−#include "timeformatter.h"
20+#include <QQmlExtensionPlugin>
2221
23−// TODO - move this to the sdk
24−// https://blueprints.launchpad.net/ubuntu-ui-toolkit/+spec/time-formatter
25−class RelativeTimeFormatter : public GDateTimeFormatter
22+class GlobalShortcutPlugin: public QQmlExtensionPlugin
2623{
2724 Q_OBJECT
28−public:
29− RelativeTimeFormatter(QObject *parent = 0);
25+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
3026
31− QString format() const override;
27+public:
28+ void registerTypes(const char *uri) override;
3229};
3330
34−#endif // RELATIVETIMEFORMATTER_H
31+
32+#endif // PLATFORMPLUGIN_H
plugins/Unity/Platform/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Unity.Platform
2+plugin Platform-qml
plugins/Unity/Session/dbusunitysessionservice.cpp+2−1
@@ -292,7 +292,8 @@ QString DBusUnitySessionService::RealName() const
292292 if (p) {
293293 const QString gecos = QString::fromLocal8Bit(p->pw_gecos);
294294 if (!gecos.isEmpty()) {
295− return gecos.split(QLatin1Char(',')).first();
295+ const QStringList splitGecos = gecos.split(QLatin1Char(','));
296+ return splitGecos.first();
296297 }
297298 }
298299
plugins/Utils/CMakeLists.txt−2
@@ -15,8 +15,6 @@ set(QMLPLUGIN_SRC
1515 inputwatcher.cpp
1616 qlimitproxymodelqml.cpp
1717 unitysortfilterproxymodelqml.cpp
18− relativetimeformatter.cpp
19− timeformatter.cpp
2018 Timer.cpp
2119 unitymenumodelpaths.cpp
2220 windowkeysfilter.cpp
plugins/Utils/plugin.cpp−5
@@ -29,8 +29,6 @@
2929#include "inputwatcher.h"
3030#include "qlimitproxymodelqml.h"
3131#include "unitysortfilterproxymodelqml.h"
32−#include "relativetimeformatter.h"
33−#include "timeformatter.h"
3432#include "unitymenumodelpaths.h"
3533#include "windowkeysfilter.h"
3634#include "windowscreenshotprovider.h"
@@ -60,11 +58,8 @@ void UtilsPlugin::registerTypes(const char *uri)
6058 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
6159 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
6260 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
63− qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
6461 qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
65− qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
6662 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
67− qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
6863 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
6964 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
7065 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
plugins/Utils/relativetimeformatter.cppdeleted−260
@@ -1,260 +0,0 @@
1−/*
2− * Copyright 2014 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−
18−// Local
19−#include "relativetimeformatter.h"
20−
21−// Qt
22−#include <QDateTime>
23−
24−// Other
25−#include <glib.h>
26−#include <glib/gi18n.h>
27−#include <locale.h>
28−#include <langinfo.h>
29−#include <string.h>
30−
31−RelativeTimeFormatter::RelativeTimeFormatter(QObject *parent)
32− : GDateTimeFormatter(parent)
33−{
34−}
35−
36− /* Check the system locale setting to see if the format is 24-hour
37− time or 12-hour time */
38−gboolean
39−is_locale_12h(void)
40−{
41− int i;
42− static const char *formats_24h[] = {"%H", "%R", "%T", "%OH", "%k", nullptr};
43− const char* t_fmt = nl_langinfo(T_FMT);
44−
45− for (i=0; formats_24h[i]!=nullptr; i++)
46− if (strstr(t_fmt, formats_24h[i]) != nullptr)
47− return FALSE;
48−
49− return TRUE;
50−}
51−
52−typedef enum
53−{
54− DATE_PROXIMITY_YESTERDAY,
55− DATE_PROXIMITY_TODAY,
56− DATE_PROXIMITY_TOMORROW,
57− DATE_PROXIMITY_LAST_WEEK,
58− DATE_PROXIMITY_NEXT_WEEK,
59− DATE_PROXIMITY_FAR
60−} date_proximity_t;
61−
62−static date_proximity_t
63−getDateProximity(GDateTime* now, GDateTime* time)
64−{
65− date_proximity_t prox = DATE_PROXIMITY_FAR;
66− gint now_year, now_month, now_day;
67− gint time_year, time_month, time_day;
68−
69− // does it happen today?
70− g_date_time_get_ymd(now, &now_year, &now_month, &now_day);
71− g_date_time_get_ymd(time, &time_year, &time_month, &time_day);
72− if ((now_year == time_year) && (now_month == time_month) && (now_day == time_day)) {
73− return DATE_PROXIMITY_TODAY;
74− }
75−
76− // did it happen yesterday?
77− GDateTime* yesterday = g_date_time_add_days(now, -1);
78− gint tom_year, tom_month, tom_day;
79− g_date_time_get_ymd(yesterday, &tom_year, &tom_month, &tom_day);
80− g_date_time_unref(yesterday);
81− if ((tom_year == time_year) && (tom_month == time_month) && (tom_day == time_day)) {
82− return DATE_PROXIMITY_YESTERDAY;
83− }
84−
85− // does it happen tomorrow?
86− GDateTime* tomorrow = g_date_time_add_days(now, 1);
87− g_date_time_get_ymd(tomorrow, &tom_year, &tom_month, &tom_day);
88− g_date_time_unref(tomorrow);
89− if ((tom_year == time_year) && (tom_month == time_month) && (tom_day == time_day)) {
90− return DATE_PROXIMITY_TOMORROW;
91− }
92−
93− // does it happen this week?
94− if (g_date_time_compare(time, now) < 0) {
95− GDateTime* last_week = g_date_time_add_days(now, -6);
96− GDateTime* last_week_bound = g_date_time_new_local(g_date_time_get_year(last_week),
97− g_date_time_get_month(last_week),
98− g_date_time_get_day_of_month(last_week),
99− 0, 0, 0);
100− if (g_date_time_compare(time, last_week_bound) >= 0)
101− prox = DATE_PROXIMITY_LAST_WEEK;
102−
103− g_date_time_unref(last_week);
104− g_date_time_unref(last_week_bound);
105− } else {
106− GDateTime* next_week = g_date_time_add_days(now, 6);
107− GDateTime* next_week_bound = g_date_time_new_local(g_date_time_get_year(next_week),
108− g_date_time_get_month(next_week),
109− g_date_time_get_day_of_month(next_week),
110− 23, 59, 59.9);
111− if (g_date_time_compare(time, next_week_bound) <= 0)
112− prox = DATE_PROXIMITY_NEXT_WEEK;
113−
114− g_date_time_unref(next_week);
115− g_date_time_unref(next_week_bound);
116− }
117−
118− return prox;
119−}
120−
121−const char*
122−dgettext_datetime(const char *text)
123−{
124− return dgettext("unity8", text);
125−}
126−
127−/**
128− * _ a time yesterday should be shown as (e.g. “Yesterday 3:55 PM”)
129− * _ a time today should be shown as just the time (e.g. “3:55 PM”)
130− * _ a time tomorrow should be shown as(e.g. “Tomorrow 3:55 PM”)
131− * _ a time any other day this week should be shown as the short version of the
132− * day and time (e.g. “Wed 3:55 PM”)
133− * weekday (e.g. “Friday”)
134− * _ a time after this week should be shown as the short version of the day,
135− * date, and time (e.g. “Wed 21 Apr 3:55 PM”)
136− * _ in addition, when presenting the times of upcoming events, the time should
137− * be followed by the timezone if it is different from the one the computer
138− * is currently set to. For example, “Wed 3:55 PM UTC−5”.
139− *
140− * TODO - keep inline with indicator-datetime
141− */
142−char* generate_full_format_string_at_time (GDateTime* now,
143− GDateTime* then)
144−{
145− GString* ret = g_string_new (nullptr);
146−
147− if (then != nullptr) {
148− const date_proximity_t prox = getDateProximity(now, then);
149−
150− if (is_locale_12h()) {
151− switch (prox) {
152− case DATE_PROXIMITY_YESTERDAY:
153− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
154− This format string is used for showing, on a 12-hour clock, times that happen yesterday.
155− (\u2003 is a unicode em space which is slightly wider than a normal space.)
156− en_US example: "Yesterday\u2003%l:%M %p" --> "Yesterday 1:00 PM" */
157− g_string_assign (ret, dgettext_datetime("Yesterday\u2003%l:%M %p"));
158− break;
159−
160− case DATE_PROXIMITY_TODAY:
161− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
162− This format string is used for showing, on a 12-hour clock, times that happened today.
163− en_US example: "%l:%M %p" --> "1:00 PM" */
164− g_string_assign (ret, dgettext_datetime("%l:%M %p"));
165− break;
166−
167− case DATE_PROXIMITY_TOMORROW:
168− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
169− This format string is used for showing, on a 12-hour clock, events/appointments that happen tomorrow.
170− (\u2003 is a unicode em space which is slightly wider than a normal space.)
171− en_US example: "Tomorrow\u2003%l:%M %p" --> "Tomorrow 1:00 PM" */
172− g_string_assign (ret, dgettext_datetime("Tomorrow\u2003%l:%M %p"));
173− break;
174−
175− case DATE_PROXIMITY_LAST_WEEK:
176− case DATE_PROXIMITY_NEXT_WEEK:
177− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
178− This format string is used for showing, on a 12-hour clock, times that happened in the last week.
179− (\u2003 is a unicode em space which is slightly wider than a normal space.)
180− en_US example: "%a\u2003%l:%M %p" --> "Fri 1:00 PM" */
181− g_string_assign (ret, dgettext_datetime("%a\u2003%l:%M %p"));
182− break;
183−
184− case DATE_PROXIMITY_FAR:
185− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
186− This format string is used for showing, on a 12-hour clock, times that happened before a week from now.
187− (\u2003 is a unicode em space which is slightly wider than a normal space.)
188− en_US example: "%a %b %d\u2003%l:%M %p" --> "Fri Oct 31 1:00 PM"
189− en_GB example: "%a %d %b\u2003%l:%M %p" --> "Fri 31 Oct 1:00 PM" */
190− g_string_assign (ret, dgettext_datetime("%a %d %b\u2003%l:%M %p"));
191− break;
192− }
193− } else {
194− switch (prox) {
195−
196− case DATE_PROXIMITY_YESTERDAY:
197− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
198− This format string is used for showing, on a 24-hour clock, times that happen yesterday.
199− (\u2003 is a unicode em space which is slightly wider than a normal space.)
200− en_US example: "Yesterday\u2003%l:%M %p" --> "Yesterday 13:00" */
201− g_string_assign (ret, dgettext_datetime("Yesterday\u2003%H:%M"));
202− break;
203−
204− case DATE_PROXIMITY_TODAY:
205− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
206− This format string is used for showing, on a 24-hour clock, times that happened today.
207− en_US example: "%H:%M" --> "13:00" */
208− g_string_assign (ret, dgettext_datetime("%H:%M"));
209− break;
210−
211− case DATE_PROXIMITY_TOMORROW:
212− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
213− This format string is used for showing, on a 24-hour clock, events/appointments that happen tomorrow.
214− (\u2003 is a unicode em space which is slightly wider than a normal space.)
215− en_US example: "Tomorrow\u2003%l:%M %p" --> "Tomorrow 13:00" */
216− g_string_assign (ret, dgettext_datetime("Tomorrow\u2003%H:%M"));
217− break;
218−
219− case DATE_PROXIMITY_LAST_WEEK:
220− case DATE_PROXIMITY_NEXT_WEEK:
221− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
222− This format string is used for showing, on a 24-hour clock, times that happened in the last week.
223− (\u2003 is a unicode em space which is slightly wider than a normal space.)
224− en_US example: "%a\u2003%H:%M" --> "Fri 13:00" */
225− g_string_assign (ret, dgettext_datetime("%a\u2003%H:%M"));
226− break;
227−
228− case DATE_PROXIMITY_FAR:
229− /* Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
230− This format string is used for showing, on a 24-hour clock, times that happened before a week from now.
231− (\u2003 is a unicode em space which is slightly wider than a normal space.)
232− en_US example: "%a %b %d\u2003%H:%M" --> "Fri Oct 31 13:00"
233− en_GB example: "%a %d %b\u2003%H:%M" --> "Fri 31 Oct 13:00" */
234− g_string_assign (ret, dgettext_datetime("%a %d %b\u2003%H:%M"));
235− break;
236− }
237− }
238− }
239−
240− return g_string_free (ret, FALSE);
241−}
242−
243−QString RelativeTimeFormatter::format() const
244−{
245− GDateTime* now = g_date_time_new_now_local();
246− if (!now) { return QString(); }
247−
248− GDateTime* then = g_date_time_new_from_unix_local(time());
249− if (!then) { return QString(); }
250−
251− char* time_format = generate_full_format_string_at_time(now, then);
252−
253− QString str(QString::fromUtf8(time_format));
254− g_free(time_format);
255−
256− g_date_time_unref(now);
257− g_date_time_unref(then);
258−
259− return str;
260−}
plugins/Utils/timeformatter.cppdeleted−206
@@ -1,206 +0,0 @@
1−/*
2− * Copyright 2013 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− * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
17− */
18−
19−#include "timeformatter.h"
20−
21−#include <gio/gio.h>
22−#include <QDateTime>
23−
24−struct TimeFormatterPrivate
25−{
26− TimeFormatter *formatter;
27−
28− QString format;
29− QString timeString;
30− qint64 time;
31−
32− GDBusConnection *system_bus;
33− guint subscription_id;
34− GCancellable *cancellable;
35−};
36−
37−static void
38−timedate1_properties_changed (GDBusConnection *connection,
39− const gchar *sender_name,
40− const gchar *object_path,
41− const gchar *interface_name,
42− const gchar *signal_name,
43− GVariant *parameters,
44− gpointer user_data)
45−{
46− Q_UNUSED(connection);
47− Q_UNUSED(sender_name);
48− Q_UNUSED(object_path);
49− Q_UNUSED(interface_name);
50− Q_UNUSED(signal_name);
51−
52− TimeFormatterPrivate *priv = (TimeFormatterPrivate *)user_data;
53− GVariant *changed;
54− GVariantIter *iter;
55− const gchar *name;
56−
57− if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
58− return;
59−
60− g_variant_get (parameters, "(s@a{sv}as)", nullptr, &changed, &iter);
61−
62− if (g_variant_lookup (changed, "Timezone", "s", nullptr)) {
63− priv->formatter->update();
64− }
65− else {
66− while (g_variant_iter_next (iter, "&s", &name)) {
67− if (g_str_equal (name, "Timezone")) {
68− priv->formatter->update();
69− break;
70− }
71− }
72− }
73−
74− g_variant_unref (changed);
75− g_variant_iter_free (iter);
76−}
77−
78−static void
79−got_bus(GObject *object, GAsyncResult *result, gpointer user_data)
80−{
81− Q_UNUSED(object);
82−
83− TimeFormatterPrivate *priv = (TimeFormatterPrivate *)user_data;
84− GError *error = nullptr;
85−
86− priv->system_bus = g_bus_get_finish (result, &error);
87− if (priv->system_bus == nullptr) {
88− if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
89− qWarning("TimeFormatter: cannot connect to the system bus: %s", error->message);
90− g_error_free (error);
91− return;
92− }
93−
94− /* Listen to the PropertiesChanged on the org.freedesktop.timedate1
95− * interface from any sender. In practice, this signal will only be sent
96− * from timedated (we can trust other processes on the system bus to behave
97− * nicely). That way, we don't have to watch timedated's well-known name
98− * and keep the process alive.
99− */
100− priv->subscription_id = g_dbus_connection_signal_subscribe (priv->system_bus,
101− nullptr, /* sender */
102− "org.freedesktop.DBus.Properties",
103− "PropertiesChanged",
104− nullptr,
105− "org.freedesktop.timedate1",
106− G_DBUS_SIGNAL_FLAGS_NONE,
107− timedate1_properties_changed,
108− priv, nullptr);
109−}
110−
111−TimeFormatter::TimeFormatter(QObject *parent): QObject(parent)
112−{
113− priv = new TimeFormatterPrivate;
114− priv->formatter = this;
115− priv->time = 0;
116− priv->format = QStringLiteral("yyyy-MM-dd hh:mm");
117− priv->system_bus = nullptr;
118− priv->subscription_id = 0;
119− priv->cancellable = g_cancellable_new ();
120−
121− g_bus_get (G_BUS_TYPE_SYSTEM, priv->cancellable, got_bus, priv);
122−}
123−
124−TimeFormatter::TimeFormatter(const QString &initialFormat, QObject *parent): TimeFormatter(parent)
125−{
126− priv->format = initialFormat;
127−}
128−
129−TimeFormatter::~TimeFormatter()
130−{
131− if (priv->system_bus) {
132− g_dbus_connection_signal_unsubscribe (priv->system_bus, priv->subscription_id);
133− g_object_unref (priv->system_bus);
134− }
135−
136− g_cancellable_cancel (priv->cancellable);
137− g_object_unref (priv->cancellable);
138−}
139−
140−QString TimeFormatter::format() const
141−{
142− return priv->format;
143−}
144−
145−QString TimeFormatter::timeString() const
146−{
147− return priv->timeString;
148−}
149−
150−qint64 TimeFormatter::time() const
151−{
152− return priv->time;
153−}
154−
155−void TimeFormatter::setFormat(const QString &format)
156−{
157− if (priv->format != format) {
158− priv->format = format;
159− Q_EMIT formatChanged(priv->format);
160− update();
161− }
162−}
163−
164−void TimeFormatter::setTime(qint64 time)
165−{
166− if (priv->time != time) {
167− priv->time = time;
168− Q_EMIT timeChanged(priv->time);
169− update();
170− }
171−}
172−
173−void TimeFormatter::update()
174−{
175− priv->timeString = formatTime();
176− Q_EMIT timeStringChanged(priv->timeString);
177−}
178−
179−QString TimeFormatter::formatTime() const
180−{
181− return QDateTime::fromMSecsSinceEpoch(time() / 1000).toString(format());
182−}
183−
184−GDateTimeFormatter::GDateTimeFormatter(QObject* parent)
185−: TimeFormatter(QStringLiteral("%d-%m-%Y %I:%M%p"), parent)
186−{
187−}
188−
189−QString GDateTimeFormatter::formatTime() const
190−{
191− gchar* time_string;
192− GDateTime* datetime;
193− QByteArray formatBytes = format().toUtf8();
194−
195− datetime = g_date_time_new_from_unix_local(time());
196− if (!datetime) {
197− return QLatin1String("");
198− }
199−
200− time_string = g_date_time_format(datetime, formatBytes.constData());
201− QString formattedTime(QString::fromUtf8(time_string));
202−
203− g_free(time_string);
204− g_date_time_unref(datetime);
205− return formattedTime;
206−}
plugins/Utils/timeformatter.hdeleted−68
@@ -1,68 +0,0 @@
1−/*
2− * Copyright 2013 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− * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
17− */
18−
19−#ifndef TIME_FORMATTER_H
20−#define TIME_FORMATTER_H
21−
22−#include <QObject>
23−
24−// TODO - bug #1260728
25−class TimeFormatter : public QObject
26−{
27− Q_OBJECT
28− Q_PROPERTY(QString format READ format WRITE setFormat NOTIFY formatChanged)
29− Q_PROPERTY(QString timeString READ timeString NOTIFY timeStringChanged)
30− Q_PROPERTY(qint64 time READ time WRITE setTime NOTIFY timeChanged)
31−
32−public:
33− TimeFormatter(QObject *parent = 0);
34− virtual ~TimeFormatter();
35−
36− virtual QString format() const;
37− QString timeString() const;
38− qint64 time() const;
39−
40− void setFormat(const QString &format);
41− void setTime(qint64 time);
42−
43− void update();
44−
45−Q_SIGNALS:
46− void formatChanged(const QString &format);
47− void timeStringChanged(const QString &timeString);
48− void timeChanged(qint64 time);
49−
50−protected:
51− TimeFormatter(const QString &initialFormat, QObject *parent = 0);
52−
53− virtual QString formatTime() const;
54−
55−private:
56− struct TimeFormatterPrivate *priv;
57−};
58−
59−class GDateTimeFormatter : public TimeFormatter
60−{
61−public:
62− GDateTimeFormatter(QObject *parent = 0);
63−
64−protected:
65− QString formatTime() const override;
66−};
67−
68−#endif
plugins/Utils/timezoneFormatter.cpp+1−1
@@ -29,7 +29,7 @@ QString TimezoneFormatter::currentTimeInTimezone(const QVariant &tzId) const
2929 if (tz.isValid()) {
3030 const QDateTime now = QDateTime::currentDateTime().toTimeZone(tz);
3131 // return locale-aware string in the form "day, hh:mm", e.g. "Mon 14:30" or "Mon 1:30 pm"
32− return QStringLiteral("%1 %2").arg(now.toString("ddd")).arg(now.time().toString(Qt::DefaultLocaleShortDate));
32+ return QStringLiteral("%1 %2").arg(now.toString(QStringLiteral("ddd"))).arg(now.time().toString(Qt::DefaultLocaleShortDate));
3333 }
3434 return QString();
3535}
po/ug.po+5−5
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-09-16 00:49+0200\n"
11−"PO-Revision-Date: 2015-10-02 03:54+0000\n"
11+"PO-Revision-Date: 2015-10-25 04:00+0000\n"
1212"Last-Translator: Eltikin <eltikinuyghur@hotmail.com>\n"
1313"Language-Team: Uyghur Computer Science Association <UKIJ@yahoogroups.com>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2015-10-16 05:58+0000\n"
18+"X-Launchpad-Export-Date: 2015-10-26 05:24+0000\n"
1919"X-Generator: Launchpad (build 17812)\n"
2020
2121#: plugins/IntegratedLightDM/Greeter.cpp:112
@@ -41,7 +41,7 @@ msgstr "تېزلەتمىنى ئېلىۋەت"
4141#. en_US example: "Yesterday\u2003%l:%M %p" --> "Yesterday 1:00 PM"
4242#: plugins/Utils/relativetimeformatter.cpp:157
4343msgid "Yesterday %l:%M %p"
44−msgstr ""
44+msgstr "تۈنۈگۈن %l:%M %p"
4545
4646#. Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
4747#. This format string is used for showing, on a 12-hour clock, times that happened today.
@@ -81,7 +81,7 @@ msgstr "%a %d %b %l:%M %p"
8181#. en_US example: "Yesterday\u2003%l:%M %p" --> "Yesterday 13:00"
8282#: plugins/Utils/relativetimeformatter.cpp:201
8383msgid "Yesterday %H:%M"
84−msgstr ""
84+msgstr "تۈنۈگۈن %H:%M"
8585
8686#. Translators, please edit/rearrange these strftime(3) tokens to suit your locale!
8787#. This format string is used for showing, on a 24-hour clock, times that happened today.
@@ -502,7 +502,7 @@ msgstr ""
502502
503503#: qml/Tutorial/TutorialPage.qml:178
504504msgid "You almost got it!"
505−msgstr ""
505+msgstr "ھازىرلا پۈتۈرىدۇ!"
506506
507507#: qml/Tutorial/TutorialPage.qml:195
508508msgid "Try again."
qml/Components/Dialogs.qml+3−2
@@ -20,6 +20,7 @@ import Unity.Application 0.1
2020import Unity.Session 0.1
2121import Ubuntu.Components 1.1
2222import GlobalShortcut 1.0
23+import Unity.Platform 1.0
2324import "../Greeter"
2425
2526Item {
@@ -46,13 +47,13 @@ Item {
4647
4748 GlobalShortcut { // reboot/shutdown dialog
4849 shortcut: Qt.Key_PowerDown
49− active: root.usageScenario === "desktop"
50+ active: Platform.isPC
5051 onTriggered: root.unitySessionService.RequestShutdown()
5152 }
5253
5354 GlobalShortcut { // reboot/shutdown dialog
5455 shortcut: Qt.Key_PowerOff
55− active: root.usageScenario === "desktop"
56+ active: Platform.isPC
5657 onTriggered: root.unitySessionService.RequestShutdown()
5758 }
5859
qml/Components/ScreenGrabber.qml+4−1
@@ -26,6 +26,9 @@ Rectangle {
2626 anchors.fill: parent
2727 opacity: 0.0
2828
29+ // to be set from outside
30+ property int rotationAngle: 0
31+
2932 ScreenGrabber {
3033 id: screenGrabber
3134 objectName: "screenGrabber"
@@ -67,7 +70,7 @@ Rectangle {
6770 to: 0.0
6871 onStopped: {
6972 if (visible) {
70− screenGrabber.captureAndSave();
73+ screenGrabber.captureAndSave(root.rotationAngle);
7174 visible = false;
7275 }
7376 }
qml/Components/WallpaperResolver.qmlnew file+63
@@ -0,0 +1,63 @@
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+import QtQuick 2.4
18+import AccountsService 0.1
19+import GSettings 1.0
20+import Ubuntu.Components 1.3
21+
22+/*
23+ Defines the background URL based on several factors, such as:
24+ - default, fallback, background
25+ - Background set in AccountSettings, if any
26+ - Background set in GSettings, if any
27+ */
28+QtObject {
29+ // Users should set their UI width here.
30+ property real width
31+
32+ property url defaultBackground: Qt.resolvedUrl(width >= units.gu(60) ? "../graphics/tablet_background.jpg"
33+ : "../graphics/phone_background.jpg")
34+
35+ // That's the property users of this component are going to consume.
36+ readonly property url background: asImageTester.status == Image.Ready ? asImageTester.source
37+ : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
38+
39+ // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
40+ property var _asImageTester: Image {
41+ id: asImageTester
42+ source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
43+ height: 0
44+ width: 0
45+ sourceSize.height: 0
46+ sourceSize.width: 0
47+ }
48+
49+ // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
50+ property var _gsImageTester: Image {
51+ id: gsImageTester
52+ source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
53+ height: 0
54+ width: 0
55+ sourceSize.height: 0
56+ sourceSize.width: 0
57+ }
58+
59+ property var _gsettings: GSettings {
60+ id: backgroundSettings
61+ schema.id: "org.gnome.desktop.background"
62+ }
63+}
qml/DeviceConfiguration.qml+1
@@ -86,6 +86,7 @@ StateGroup {
8686 PropertyChanges {
8787 target: root
8888 category: "desktop"
89+ supportedOrientations: root.useNativeOrientation
8990 }
9091 }
9192 ]
qml/DisabledScreenNotice.qmlnew file+49
@@ -0,0 +1,49 @@
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+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import "Components"
20+
21+Image {
22+ id: root
23+
24+ WallpaperResolver {
25+ width: root.width
26+ id: wallpaperResolver
27+ }
28+
29+ source: wallpaperResolver.background
30+
31+ UbuntuShape {
32+ anchors.fill: text
33+ anchors.margins: -units.gu(2)
34+ backgroundColor: "black"
35+ opacity: 0.4
36+ }
37+
38+ Label {
39+ id: text
40+ anchors.centerIn: parent
41+ width: parent.width / 2
42+ text: i18n.tr("Your device is now connected to an external display.")
43+ color: "white"
44+ horizontalAlignment: Text.AlignHCenter
45+ verticalAlignment: Text.AlignVCenter
46+ fontSize: "x-large"
47+ wrapMode: Text.Wrap
48+ }
49+}
qml/Greeter/Greeter.qml+1−1
@@ -251,7 +251,7 @@ Showable {
251251
252252 // event eater
253253 // Nothing should leak to items behind the greeter
254− MouseArea { anchors.fill: parent }
254+ MouseArea { anchors.fill: parent; hoverEnabled: true }
255255
256256 Loader {
257257 id: loader
qml/Launcher/Launcher.qml+1
@@ -188,6 +188,7 @@ Item {
188188 bottom: parent.bottom
189189 }
190190 enabled: root.shadeBackground && root.state == "visible"
191+ visible: enabled // otherwise it will get in the way of cursor selection for some reason
191192 onPressed: {
192193 root.state = ""
193194 }
qml/Notifications/Notification.qml+2
@@ -50,6 +50,7 @@ Item {
5050 readonly property real contentSpacing: units.gu(2)
5151 readonly property bool canBeClosed: type === Notification.Ephemeral
5252 property bool hasMouse
53+ property url background: ""
5354
5455 objectName: "background"
5556 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
@@ -401,6 +402,7 @@ Item {
401402 menuData: model
402403 menuIndex: index
403404 maxHeight: notification.maxHeight
405+ background: notification.background
404406
405407 onLoaded: {
406408 notification.fullscreen = Qt.binding(function() { return fullscreen; });
qml/Notifications/NotificationMenuItemFactory.qml+2−1
@@ -30,6 +30,7 @@ Loader {
3030 property int menuIndex : -1
3131 property int maxHeight
3232 readonly property bool fullscreen: menuData.type === "com.canonical.snapdecision.pinlock"
33+ property url background: ""
3334
3435 signal accepted()
3536
@@ -149,7 +150,7 @@ Loader {
149150 infoText: notification.summary
150151 errorText: errorAction.valid ? errorAction.state : ""
151152 retryText: notification.body
152− background: shell.background
153+ background: menuFactory.background
153154 darkenBackground: 0.4
154155
155156 onEntered: {
qml/Notifications/Notifications.qml+2
@@ -29,6 +29,7 @@ ListView {
2929 property real margin
3030 property bool useModal: snapDecisionProxyModel.count > 0
3131 property bool hasMouse
32+ property url background: ""
3233
3334 UnitySortFilterProxyModel {
3435 id: snapDecisionProxyModel
@@ -60,6 +61,7 @@ ListView {
6061 maxHeight: notificationList.height
6162 margins: notificationList.margin
6263 hasMouse: notificationList.hasMouse
64+ background: notificationList.background
6365
6466 // make sure there's no opacity-difference between the several
6567 // elements in a notification
qml/OrientedShell.qml+10−1
@@ -76,6 +76,12 @@ Rectangle {
7676 oskSettings.disableHeight = shell.usageScenario == "desktop"
7777 }
7878
79+ // we must rotate to a supported orientation regardless of shell's preference
80+ property bool orientationChangesEnabled:
81+ (orientation & supportedOrientations) === 0 ? true
82+ : shell.orientationChangesEnabled
83+
84+
7985 Binding {
8086 target: oskSettings
8187 property: "stayHidden"
@@ -89,7 +95,10 @@ Rectangle {
8995 }
9096
9197 readonly property int supportedOrientations: shell.supportedOrientations
92− & deviceConfiguration.supportedOrientations
98+ & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
99+ ? nativeOrientation
100+ : deviceConfiguration.supportedOrientations)
101+
93102 property int acceptedOrientationAngle: {
94103 if (orientation & supportedOrientations) {
95104 return Screen.angleBetween(nativeOrientation, orientation);
qml/Panel/Indicators/MenuItemFactory.qml+19−15
@@ -20,7 +20,7 @@ import Ubuntu.Settings.Components 0.1
2020import QMenuModel 0.1
2121import Utils 0.1 as Utils
2222import Ubuntu.Components.ListItems 0.1 as ListItems
23−import Ubuntu.Components 1.2
23+import Ubuntu.Components 1.3
2424import Unity.Session 0.1
2525
2626Item {
@@ -350,20 +350,23 @@ Item {
350350 id: alarmMenu;
351351
352352 Menus.EventMenu {
353+ id: alarmItem
353354 objectName: "alarmMenu"
354355 property QtObject menuData: null
355356 property var menuModel: menuFactory.menuModel
356357 property int menuIndex: -1
357358 property var extendedData: menuData && menuData.ext || undefined
358− // TODO - bug #1260728
359− property var timeFormatter: Utils.GDateTimeFormatter {
360− time: getExtendedProperty(extendedData, "xCanonicalTime", 0)
361− format: getExtendedProperty(extendedData, "xCanonicalTimeFormat", "")
359+
360+ property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
361+ LiveTimer {
362+ frequency: LiveTimer.Relative
363+ relativeTime: alarmItem.serverTime
364+ onTrigger: alarmItem.serverTime = new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
362365 }
363366
364367 text: menuData && menuData.label || ""
365368 iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
366− time: timeFormatter.timeString
369+ time: i18n.relativeDateTime(serverTime)
367370 enabled: menuData && menuData.sensitive || false
368371 highlightWhenPressed: false
369372
@@ -379,8 +382,7 @@ Item {
379382
380383 function loadAttributes() {
381384 if (!menuModel || menuIndex == -1) return;
382− menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-time': 'int64',
383− 'x-canonical-time-format': 'string'});
385+ menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-time': 'int64'});
384386 }
385387 }
386388 }
@@ -389,20 +391,23 @@ Item {
389391 id: appointmentMenu;
390392
391393 Menus.EventMenu {
394+ id: appointmentItem
392395 objectName: "appointmentMenu"
393396 property QtObject menuData: null
394397 property var menuModel: menuFactory.menuModel
395398 property int menuIndex: -1
396399 property var extendedData: menuData && menuData.ext || undefined
397− // TODO - bug #1260728
398− property var timeFormatter: Utils.GDateTimeFormatter {
399− time: getExtendedProperty(extendedData, "xCanonicalTime", 0)
400− format: getExtendedProperty(extendedData, "xCanonicalTimeFormat", "")
400+
401+ property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
402+ LiveTimer {
403+ frequency: LiveTimer.Relative
404+ relativeTime: appointmentItem.serverTime
405+ onTrigger: appointmentItem.serverTime = new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
401406 }
402407
403408 text: menuData && menuData.label || ""
404409 iconSource: menuData && menuData.icon || "image://theme/calendar"
405− time: timeFormatter.timeString
410+ time: i18n.relativeDateTime(serverTime)
406411 eventColor: getExtendedProperty(extendedData, "xCanonicalColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
407412 enabled: menuData && menuData.sensitive || false
408413 highlightWhenPressed: false
@@ -420,8 +425,7 @@ Item {
420425 function loadAttributes() {
421426 if (!menuModel || menuIndex == -1) return;
422427 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-color': 'string',
423− 'x-canonical-time': 'int64',
424− 'x-canonical-time-format': 'string'});
428+ 'x-canonical-time': 'int64'});
425429 }
426430 }
427431 }
qml/Panel/Indicators/MessageMenuItemFactory.qml+35−26
@@ -19,7 +19,7 @@
1919 */
2020
2121import QtQuick 2.0
22−import Ubuntu.Components 0.1
22+import Ubuntu.Components 1.3
2323import Ubuntu.Settings.Menus 0.1 as Menus
2424import QMenuModel 0.1 as QMenuModel
2525import Utils 0.1 as Utils
@@ -35,12 +35,17 @@ Loader {
3535 signal menuSelected
3636 signal menuDeselected
3737
38− property var extendedData: menuData && menuData.ext || undefined
39− property var actionsDescription: getExtendedProperty(extendedData, "xCanonicalMessageActions", undefined)
40−
41− // TODO - bug #1260728
42− property var timeFormatter: Utils.RelativeTimeFormatter {
43− time: getExtendedProperty(extendedData, "xCanonicalTime", 0) / 1000000
38+ QtObject {
39+ id: priv
40+ property var extendedData: menuData && menuData.ext || undefined
41+ property var actionsDescription: getExtendedProperty(extendedData, "xCanonicalMessageActions", undefined)
42+ property date time: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) / 1000)
43+ property string timeString: i18n.relativeDateTime(time)
44+ }
45+ LiveTimer {
46+ frequency: LiveTimer.Relative
47+ relativeTime: priv.time
48+ onTrigger: priv.timeString = Qt.binding(function() { return i18n.relativeDateTime(priv.time); })
4449 }
4550
4651 onMenuModelChanged: {
@@ -50,7 +55,7 @@ Loader {
5055 loadAttributes();
5156 }
5257
53− sourceComponent: loadMessage(actionsDescription)
58+ sourceComponent: loadMessage(priv.actionsDescription)
5459
5560 function loadMessage(actions)
5661 {
@@ -100,11 +105,11 @@ Loader {
100105 objectName: "simpleTextMessage"
101106 // text
102107 title: menuData && menuData.label || ""
103− time: timeFormatter.timeString
104− body: getExtendedProperty(extendedData, "xCanonicalText", "")
108+ time: priv.timeString
109+ body: getExtendedProperty(priv.extendedData, "xCanonicalText", "")
105110 // icons
106− avatar: getExtendedProperty(extendedData, "icon", "image://theme/contact")
107− icon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "image://theme/message")
111+ avatar: getExtendedProperty(priv.extendedData, "icon", "image://theme/contact")
112+ icon: getExtendedProperty(priv.extendedData, "xCanonicalAppIcon", "image://theme/message")
108113 // actions
109114 enabled: menuData && menuData.sensitive || false
110115 removable: !selected
@@ -133,7 +138,9 @@ Loader {
133138 Menus.TextMessageMenu {
134139 id: message
135140 objectName: "textMessage"
136− property var replyActionDescription: actionsDescription && actionsDescription.length > 0 ? actionsDescription[0] : undefined
141+ property var replyActionDescription: priv.actionsDescription && priv.actionsDescription.length > 0 ?
142+ priv.actionsDescription[0] :
143+ undefined
137144
138145 property var replyAction: QMenuModel.UnityMenuAction {
139146 model: menuModel
@@ -143,13 +150,13 @@ Loader {
143150
144151 // text
145152 title: menuData && menuData.label || ""
146− time: timeFormatter.timeString
147− body: getExtendedProperty(extendedData, "xCanonicalText", "")
148− replyButtonText: getExtendedProperty(replyActionDescription, "label", i18n.tr("Send"))
153+ time: priv.timeString
154+ body: getExtendedProperty(priv.extendedData, "xCanonicalText", "")
155+ replyButtonText: getExtendedProperty(replyActionDescription, "label", i18n.ctr("Button: Send a reply message", "Send"))
149156 replyHintText: i18n.ctr("Label: Hint in message indicator line edit", "Reply")
150157 // icons
151− avatar: getExtendedProperty(extendedData, "icon", "image://theme/contact")
152− icon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "image://theme/message")
158+ avatar: getExtendedProperty(priv.extendedData, "icon", "image://theme/contact")
159+ icon: getExtendedProperty(priv.extendedData, "xCanonicalAppIcon", "image://theme/message")
153160 // actions
154161 replyEnabled: replyAction.valid && replyAction.enabled
155162 enabled: menuData && menuData.sensitive || false
@@ -183,8 +190,10 @@ Loader {
183190 Menus.SnapDecisionMenu {
184191 id: message
185192 objectName: "snapDecision"
186− property var activateActionDescription: actionsDescription && actionsDescription.length > 0 ? actionsDescription[0] : undefined
187− property var replyActionDescription: actionsDescription && actionsDescription.length > 1 ? actionsDescription[1] : undefined
193+ property var activateActionDescription: priv.actionsDescription && priv.actionsDescription.length > 0 ?
194+ priv.actionsDescription[0] : undefined
195+ property var replyActionDescription: priv.actionsDescription && priv.actionsDescription.length > 1 ?
196+ priv.actionsDescription[1] : undefined
188197
189198 property var activateAction: QMenuModel.UnityMenuAction {
190199 model: menuModel
@@ -199,13 +208,13 @@ Loader {
199208
200209 // text
201210 title: menuData && menuData.label || ""
202− time: timeFormatter.timeString
203− body: getExtendedProperty(extendedData, "xCanonicalText", "")
204− actionButtonText: getExtendedProperty(activateActionDescription, "label", i18n.tr("Call back"))
205− replyButtonText: getExtendedProperty(replyActionDescription, "label", i18n.tr("Send"))
211+ time: priv.timeString
212+ body: getExtendedProperty(priv.extendedData, "xCanonicalText", "")
213+ actionButtonText: getExtendedProperty(activateActionDescription, "label", i18n.ctr("Button: Call back on phone", "Call back"))
214+ replyButtonText: getExtendedProperty(replyActionDescription, "label", i18n.ctr("Button: Send a reply message", "Send"))
206215 // icons
207− avatar: getExtendedProperty(extendedData, "icon", "image://theme/contact")
208− icon: getExtendedProperty(extendedData, "xCanonicalAppIcon", "image://theme/missed-call")
216+ avatar: getExtendedProperty(priv.extendedData, "icon", "image://theme/contact")
217+ icon: getExtendedProperty(priv.extendedData, "xCanonicalAppIcon", "image://theme/missed-call")
209218 // actions
210219 actionEnabled: activateAction.valid && activateAction.enabled
211220 replyEnabled: replyAction.valid && replyAction.enabled
qml/Panel/Panel.qml+1
@@ -48,6 +48,7 @@ Item {
4848 MouseArea {
4949 anchors.fill: parent
5050 onClicked: if (indicators.fullyOpened) indicators.hide();
51+ hoverEnabled: true // should also eat hover events, otherwise they will pass through
5152 }
5253 }
5354
qml/Rotation/RotationStates.qml+2−2
@@ -83,7 +83,7 @@ StateGroup {
8383 }
8484
8585 function tryUpdateState() {
86− if (d.transitioning || (!d.startingUp && !root.shell.orientationChangesEnabled)) {
86+ if (d.transitioning || (!d.startingUp && !root.orientedShell.orientationChangesEnabled)) {
8787 return;
8888 }
8989
@@ -95,7 +95,7 @@ StateGroup {
9595 }
9696
9797 property Connections shellConnections: Connections {
98− target: root.shell
98+ target: root.orientedShell
9999 onOrientationChangesEnabledChanged: {
100100 d.tryUpdateState();
101101 }
qml/Shell.qml+24−40
@@ -17,7 +17,6 @@
1717import QtQuick 2.0
1818import QtQuick.Window 2.0
1919import AccountsService 0.1
20−import GSettings 1.0
2120import Unity.Application 0.1
2221import Ubuntu.Components 0.1
2322import Ubuntu.Components.Popups 1.0
@@ -41,6 +40,7 @@ import Unity.Notifications 1.0 as NotificationBackend
4140import Unity.Session 0.1
4241import Unity.DashCommunicator 0.1
4342import Unity.Indicators 0.1 as Indicators
43+import Cursor 1.0
4444
4545
4646Item {
@@ -63,7 +63,7 @@ Item {
6363 function updateFocusedAppOrientationAnimated() {
6464 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
6565 }
66− property bool hasMouse
66+ property bool hasMouse: false
6767
6868 // to be read from outside
6969 readonly property int mainAppWindowOrientationAngle:
@@ -107,9 +107,11 @@ Item {
107107 enabled: greeter && !greeter.waiting
108108
109109 property real edgeSize: units.gu(2)
110− property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
111− property url background: asImageTester.status == Image.Ready ? asImageTester.source
112− : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
110+
111+ WallpaperResolver {
112+ id: wallpaperResolver
113+ width: shell.width
114+ }
113115
114116 readonly property alias greeter: greeterLoader.item
115117
@@ -130,31 +132,6 @@ Item {
130132 shell.activateApplication(app);
131133 }
132134
133− // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
134− Image {
135− id: asImageTester
136− source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
137− height: 0
138− width: 0
139− sourceSize.height: 0
140− sourceSize.width: 0
141− }
142−
143− GSettings {
144− id: backgroundSettings
145− schema.id: "org.gnome.desktop.background"
146− }
147−
148− // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
149− Image {
150− id: gsImageTester
151− source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
152− height: 0
153− width: 0
154− sourceSize.height: 0
155− sourceSize.width: 0
156− }
157−
158135 Binding {
159136 target: LauncherModel
160137 property: "applicationManager"
@@ -189,11 +166,6 @@ Item {
189166 onScreenshotTriggered: screenGrabber.capture();
190167 }
191168
192− ScreenGrabber {
193− id: screenGrabber
194− z: dialogs.z + 10
195− }
196−
197169 GlobalShortcut {
198170 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
199171 }
@@ -326,7 +298,7 @@ Item {
326298 Binding {
327299 target: applicationsDisplayLoader.item
328300 property: "background"
329− value: shell.background
301+ value: wallpaperResolver.background
330302 }
331303 Binding {
332304 target: applicationsDisplayLoader.item
@@ -431,7 +403,7 @@ Item {
431403 tabletMode: shell.usageScenario != "phone"
432404 launcherOffset: launcher.progress
433405 forcedUnlock: tutorial.running
434− background: shell.background
406+ background: wallpaperResolver.background
435407
436408 // avoid overlapping with Launcher's edge drag area
437409 // FIXME: Fix TouchRegistry & friends and remove this workaround
@@ -606,8 +578,8 @@ Item {
606578 id: wizard
607579 objectName: "wizard"
608580 anchors.fill: parent
609− background: shell.background
610581 deferred: shell.mode === "greeter"
582+ background: wallpaperResolver.background
611583
612584 function unlockWhenDoneWithWizard() {
613585 if (!active) {
@@ -638,6 +610,7 @@ Item {
638610 model: NotificationBackend.Model
639611 margin: units.gu(1)
640612 hasMouse: shell.hasMouse
613+ background: wallpaperResolver.background
641614
642615 y: topmostIsFullscreen ? 0 : panel.panelHeight
643616 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
@@ -684,9 +657,21 @@ Item {
684657 onShowHome: showHome()
685658 }
686659
660+ ScreenGrabber {
661+ id: screenGrabber
662+ rotationAngle: -shell.orientationAngle
663+ z: dialogs.z + 10
664+ }
665+
666+ Cursor {
667+ id: cursor
668+ visible: shell.hasMouse
669+ z: screenGrabber.z + 1
670+ }
671+
687672 Rectangle {
688673 id: shutdownFadeOutRectangle
689− z: screenGrabber.z + 10
674+ z: cursor.z + 1
690675 enabled: false
691676 visible: false
692677 color: "black"
@@ -703,5 +688,4 @@ Item {
703688 }
704689 }
705690 }
706−
707691}
qml/Stages/ApplicationWindow.qml+2
@@ -27,6 +27,8 @@ FocusScope {
2727 readonly property bool fullscreen: application ? application.fullscreen : false
2828 property alias interactive: sessionContainer.interactive
2929 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
30+ readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
31+ sessionContainer.surface.name : d.name
3032
3133 // to be set from outside
3234 property QtObject application
qml/Stages/DecoratedWindow.qml+7−4
@@ -31,9 +31,10 @@ FocusScope {
3131 property bool highlightShown: false
3232 property real shadowOpacity: 1
3333
34− signal close();
35− signal maximize();
36− signal minimize();
34+ signal close()
35+ signal maximize()
36+ signal minimize()
37+ signal decorationPressed()
3738
3839 BorderImage {
3940 anchors {
@@ -61,13 +62,15 @@ FocusScope {
6162
6263 WindowDecoration {
6364 id: decoration
65+ target: root.parent
6466 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
6567 anchors { left: parent.left; top: parent.top; right: parent.right }
6668 height: units.gu(3)
67− title: model.name
69+ title: window.title !== "" ? window.title : model.name
6870 onClose: root.close();
6971 onMaximize: root.maximize();
7072 onMinimize: root.minimize();
73+ onPressed: root.decorationPressed();
7174 visible: decorationShown
7275 }
7376
qml/Stages/DesktopStage.qml+5−8
@@ -138,9 +138,6 @@ Rectangle {
138138 height: units.gu(50)
139139 focus: model.appId === priv.focusedAppId
140140
141− readonly property int minWidth: units.gu(10)
142− readonly property int minHeight: units.gu(10)
143−
144141 property bool maximized: false
145142 property bool minimized: false
146143
@@ -215,12 +212,11 @@ Rectangle {
215212 when: index == spread.highlightedIndex && blurLayer.ready
216213 }
217214
218− WindowMoveResizeArea {
219− id: windowMoveResizeArea
215+ WindowResizeArea {
220216 target: appDelegate
221− minWidth: appDelegate.minWidth
222− minHeight: appDelegate.minHeight
223− resizeHandleWidth: units.gu(2)
217+ minWidth: units.gu(10)
218+ minHeight: units.gu(10)
219+ borderThickness: units.gu(2)
224220 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
225221
226222 onPressed: { ApplicationManager.focusApplication(model.appId) }
@@ -240,6 +236,7 @@ Rectangle {
240236 onClose: ApplicationManager.stopApplication(model.appId)
241237 onMaximize: appDelegate.maximize()
242238 onMinimize: appDelegate.minimize()
239+ onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
243240 }
244241 }
245242 }
qml/Stages/SurfaceContainer.qml+1
@@ -28,6 +28,7 @@ FocusScope {
2828 property bool hadSurface: false
2929 property bool interactive
3030 property int surfaceOrientationAngle: 0
31+ property string name: surface ? surface.name : ""
3132 property bool resizeSurface: true
3233
3334 onSurfaceChanged: {
qml/Stages/WindowDecoration.qml+32−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-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
@@ -12,25 +12,53 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Zanetti <michael.zanetti@canonical.com>
1715 */
1816
1917import QtQuick 2.3
18+import Unity.Application 0.1 // For Mir singleton
2019import Ubuntu.Components 1.1
2120import "../Components"
2221
23−Item {
22+MouseArea {
2423 id: root
2524 clip: true
2625
26+ property Item target
2727 property alias title: titleLabel.text
2828 property bool active: false
29+ hoverEnabled: true
2930
3031 signal close()
3132 signal minimize()
3233 signal maximize()
3334
35+ QtObject {
36+ id: priv
37+ property real distanceX
38+ property real distanceY
39+ property bool dragging
40+ }
41+
42+ onPressedChanged: {
43+ if (pressed) {
44+ var pos = mapToItem(root.target, mouseX, mouseY);
45+ priv.distanceX = pos.x;
46+ priv.distanceY = pos.y;
47+ priv.dragging = true;
48+ Mir.cursorName = "grabbing";
49+ } else {
50+ priv.dragging = false;
51+ Mir.cursorName = "";
52+ }
53+ }
54+ onPositionChanged: {
55+ if (priv.dragging) {
56+ var pos = mapToItem(root.target.parent, mouseX, mouseY);
57+ root.target.x = pos.x - priv.distanceX;
58+ root.target.y = pos.y - priv.distanceY;
59+ }
60+ }
61+
3462 Rectangle {
3563 anchors.fill: parent
3664 anchors.bottomMargin: -radius
qml/Stages/WindowMoveResizeArea.qmldeleted−109
@@ -1,109 +0,0 @@
1−/*
2− * Copyright (C) 2014-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− * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17− */
18−
19−import QtQuick 2.3
20−import Ubuntu.Components 1.1
21−import Utils 0.1
22−
23−MouseArea {
24− id: root
25− anchors.fill: target
26− anchors.margins: -resizeHandleWidth
27−
28− property var windowStateStorage: WindowStateStorage
29−
30− // The target item managed by this. Must be a parent or a sibling
31− // The area will anchor to it and manage move and resize events
32− property Item target: null
33− property string windowId: ""
34− property int resizeHandleWidth: 0
35− property int minWidth: 0
36− property int minHeight: 0
37−
38− QtObject {
39− id: priv
40− readonly property int windowWidth: root.width - root.resizeHandleWidth * 2
41− readonly property int windowHeight: root.height - resizeHandleWidth * 2
42−
43− property var startPoint
44−
45− property bool resizeTop: false
46− property bool resizeBottom: false
47− property bool resizeLeft: false
48− property bool resizeRight: false
49−
50− }
51−
52− Component.onCompleted: {
53− var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
54− if (windowState !== undefined) {
55− target.x = windowState.x
56− target.y = windowState.y
57− target.width = windowState.width
58− target.height = windowState.height
59− }
60− }
61−
62− onPressed: {
63− priv.startPoint = Qt.point(mouse.x, mouse.y);
64− priv.resizeTop = mouseY < root.resizeHandleWidth;
65− priv.resizeBottom = mouseY > (root.height - root.resizeHandleWidth);
66− priv.resizeLeft = mouseX < root.resizeHandleWidth;
67− priv.resizeRight = mouseX > (root.width - root.resizeHandleWidth);
68− }
69−
70− onPositionChanged: {
71− var currentPoint = Qt.point(mouse.x, mouse.y);
72− var mouseDiff = Qt.point(currentPoint.x - priv.startPoint.x, currentPoint.y - priv.startPoint.y);
73− var moveDiff = Qt.point(0, 0);
74− var sizeDiff = Qt.point(0, 0);
75− var maxSizeDiff = Qt.point(root.minWidth - root.target.width, root.minHeight - root.target.height)
76−
77− if (priv.resizeTop || priv.resizeBottom || priv.resizeLeft || priv.resizeRight) {
78− if (priv.resizeTop) {
79− sizeDiff.y = Math.max(maxSizeDiff.y, -currentPoint.y + priv.startPoint.y)
80− moveDiff.y = -sizeDiff.y
81− }
82− if (priv.resizeBottom) {
83− sizeDiff.y = Math.max(maxSizeDiff.y, currentPoint.y - priv.startPoint.y)
84− priv.startPoint.y += sizeDiff.y
85− }
86− if (priv.resizeLeft) {
87− sizeDiff.x = Math.max(maxSizeDiff.x, -currentPoint.x + priv.startPoint.x)
88− moveDiff.x = -sizeDiff.x
89− }
90− if (priv.resizeRight) {
91− sizeDiff.x = Math.max(maxSizeDiff.x, currentPoint.x - priv.startPoint.x)
92− priv.startPoint.x += sizeDiff.x
93− }
94−
95− target.x += moveDiff.x;
96− target.y += moveDiff.y;
97− target.width += sizeDiff.x;
98− target.height += sizeDiff.y;
99− } else {
100− target.x += mouseDiff.x;
101− target.y += mouseDiff.y;
102− }
103−
104− }
105−
106− Component.onDestruction: {
107− windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
108− }
109−}
qml/Stages/WindowResizeArea.qmlnew file+182
@@ -0,0 +1,182 @@
1+/*
2+ * Copyright (C) 2014-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.3
18+import Ubuntu.Components 1.1
19+import Utils 0.1
20+import Unity.Application 0.1 // for Mir.cursorName
21+
22+MouseArea {
23+ id: root
24+ anchors.fill: target
25+ anchors.margins: -borderThickness
26+
27+ hoverEnabled: true
28+
29+ property var windowStateStorage: WindowStateStorage
30+
31+ // The target item managed by this. Must be a parent or a sibling
32+ // The area will anchor to it and manage move and resize events
33+ property Item target: null
34+ property string windowId: ""
35+ property int borderThickness: 0
36+ property int minWidth: 0
37+ property int minHeight: 0
38+
39+ Component.onCompleted: {
40+ var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
41+ if (windowState !== undefined) {
42+ target.x = windowState.x
43+ target.y = windowState.y
44+ target.width = windowState.width
45+ target.height = windowState.height
46+ }
47+ }
48+
49+ Component.onDestruction: {
50+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
51+ }
52+
53+ QtObject {
54+ id: d
55+ property bool leftBorder: false
56+ property bool rightBorder: false
57+ property bool topBorder: false
58+ property bool bottomBorder: false
59+
60+ property bool dragging: false
61+ property real startMousePosX
62+ property real startMousePosY
63+ property real startX
64+ property real startY
65+ property real startWidth
66+ property real startHeight
67+
68+ property string cursorName: {
69+ if (root.containsMouse || root.pressed) {
70+ if (leftBorder && !topBorder && !bottomBorder) {
71+ return "left_side";
72+ } else if (rightBorder && !topBorder && !bottomBorder) {
73+ return "right_side";
74+ } else if (topBorder && !leftBorder && !rightBorder) {
75+ return "top_side";
76+ } else if (bottomBorder && !leftBorder && !rightBorder) {
77+ return "bottom_side";
78+ } else if (leftBorder && topBorder) {
79+ return "top_left_corner";
80+ } else if (leftBorder && bottomBorder) {
81+ return "bottom_left_corner";
82+ } else if (rightBorder && topBorder) {
83+ return "top_right_corner";
84+ } else if (rightBorder && bottomBorder) {
85+ return "bottom_right_corner";
86+ } else {
87+ return "";
88+ }
89+ } else {
90+ return "";
91+ }
92+ }
93+ onCursorNameChanged: {
94+ Mir.cursorName = cursorName;
95+ }
96+
97+ function updateBorders() {
98+ leftBorder = mouseX <= borderThickness;
99+ rightBorder = mouseX >= width - borderThickness;
100+ topBorder = mouseY <= borderThickness;
101+ bottomBorder = mouseY >= height - borderThickness;
102+ }
103+ }
104+
105+ onPressedChanged: {
106+ var pos = mapToItem(target.parent, mouseX, mouseY);
107+
108+ if (pressed) {
109+ d.updateBorders();
110+ var pos = mapToItem(root.target.parent, mouseX, mouseY);
111+ d.startMousePosX = pos.x;
112+ d.startMousePosY = pos.y;
113+ d.startX = target.x;
114+ d.startY = target.y;
115+ d.startWidth = target.width;
116+ d.startHeight = target.height;
117+ d.dragging = true;
118+ } else {
119+ d.dragging = false;
120+ if (containsMouse) {
121+ d.updateBorders();
122+ }
123+ }
124+ }
125+
126+ onEntered: {
127+ if (!pressed) {
128+ d.updateBorders();
129+ }
130+ }
131+
132+ onPositionChanged: {
133+ if (!pressed) {
134+ d.updateBorders();
135+ }
136+
137+ if (!d.dragging) {
138+ return;
139+ }
140+
141+ var pos = mapToItem(target.parent, mouse.x, mouse.y);
142+
143+ var deltaX = pos.x - d.startMousePosX;
144+ var deltaY = pos.y - d.startMousePosY;
145+
146+ if (d.leftBorder) {
147+ var newTargetX = d.startX + deltaX;
148+ if (target.x + target.width > newTargetX + minWidth) {
149+ target.width = target.x + target.width - newTargetX;
150+ target.x = newTargetX;
151+ } else {
152+ target.x = target.x + target.width - minWidth;
153+ target.width = minWidth;
154+ }
155+
156+ } else if (d.rightBorder) {
157+ if (d.startWidth + deltaX >= minWidth) {
158+ target.width = d.startWidth + deltaX;
159+ } else {
160+ target.width = minWidth;
161+ }
162+ }
163+
164+ if (d.topBorder) {
165+ var newTargetY = d.startY + deltaY;
166+ if (target.y + target.height > newTargetY + minHeight) {
167+ target.height = target.y + target.height - newTargetY;
168+ target.y = newTargetY;
169+ } else {
170+ target.y = target.y + target.height - minHeight;
171+ target.height = minHeight;
172+ }
173+
174+ } else if (d.bottomBorder) {
175+ if (d.startHeight + deltaY >= minHeight) {
176+ target.height = d.startHeight + deltaY;
177+ } else {
178+ target.height = minHeight;
179+ }
180+ }
181+ }
182+}
src/ApplicationArguments.h+10−2
@@ -26,17 +26,25 @@
2626class ApplicationArguments : public QObject
2727{
2828 Q_OBJECT
29− Q_PROPERTY(QString deviceName READ deviceName CONSTANT)
29+ Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
3030 Q_PROPERTY(QString mode READ mode CONSTANT)
3131public:
3232 ApplicationArguments(QObject *parent = nullptr);
3333
34− void setDeviceName(const QString &deviceName) { m_deviceName = deviceName; }
34+ void setDeviceName(const QString &deviceName) {
35+ if (deviceName != m_deviceName) {
36+ m_deviceName = deviceName;
37+ Q_EMIT deviceNameChanged(m_deviceName);
38+ }
39+ }
3540 QString deviceName() const { return m_deviceName; }
3641
3742 void setMode(const QString &mode) { m_mode = mode; }
3843 QString mode() const { return m_mode; }
3944
45+Q_SIGNALS:
46+ void deviceNameChanged(const QString&);
47+
4048private:
4149 QString m_deviceName;
4250 QString m_mode;
src/CMakeLists.txt+3
@@ -21,6 +21,9 @@ add_executable(${SHELL_APP}
2121 main.cpp
2222 MouseTouchAdaptor.cpp
2323 CachingNetworkManagerFactory.cpp
24+ SecondaryWindow.cpp
25+ ShellApplication.cpp
26+ ShellView.cpp
2427 UnityCommandLineParser.cpp
2528 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
2629)
src/SecondaryWindow.cppnew file+31
@@ -0,0 +1,31 @@
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 "SecondaryWindow.h"
18+
19+// local
20+#include <paths.h>
21+
22+SecondaryWindow::SecondaryWindow(QQmlEngine *engine)
23+ : QQuickView(engine, nullptr)
24+{
25+ setResizeMode(QQuickView::SizeRootObjectToView);
26+ setColor("black");
27+ setTitle(QStringLiteral("Unity8 Shell - Secondary Screen"));
28+
29+ QUrl source(::qmlDirectory() + "/DisabledScreenNotice.qml");
30+ setSource(source);
31+}
src/SecondaryWindow.hnew file+30
@@ -0,0 +1,30 @@
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 UNITY_SECONDARY_WINDOW_H
18+#define UNITY_SECONDARY_WINDOW_H
19+
20+#include <QQuickView>
21+
22+class SecondaryWindow : public QQuickView
23+{
24+ Q_OBJECT
25+
26+public:
27+ SecondaryWindow(QQmlEngine *engine);
28+};
29+
30+#endif // UNITY_SECONDARY_WINDOW_H
src/ShellApplication.cppnew file+202
@@ -0,0 +1,202 @@
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 "ShellApplication.h"
18+
19+// Qt
20+#include <QLibrary>
21+#include <QScreen>
22+
23+#include <libintl.h>
24+
25+// libandroid-properties
26+#include <hybris/properties/properties.h>
27+
28+// local
29+#include <paths.h>
30+#include "CachingNetworkManagerFactory.h"
31+#include "MouseTouchAdaptor.h"
32+#include "UnityCommandLineParser.h"
33+
34+ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
35+ : QGuiApplication(argc, argv)
36+ , m_shellView(nullptr)
37+ , m_secondaryWindow(nullptr)
38+ , m_mouseTouchAdaptor(nullptr)
39+ , m_qmlEngine(nullptr)
40+{
41+
42+ setApplicationName(QStringLiteral("unity8"));
43+
44+ connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
45+
46+ setupQmlEngine(isMirServer);
47+
48+ UnityCommandLineParser parser(*this);
49+
50+ if (!parser.deviceName().isEmpty()) {
51+ m_deviceName = parser.deviceName();
52+ } else {
53+ char buffer[200];
54+ property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
55+ m_deviceName = QString(buffer);
56+ }
57+ m_qmlArgs.setDeviceName(m_deviceName);
58+
59+ m_qmlArgs.setMode(parser.mode());
60+
61+ // The testability driver is only loaded by QApplication but not by QGuiApplication.
62+ // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
63+ if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
64+ QLibrary testLib(QStringLiteral("qttestability"));
65+ if (testLib.load()) {
66+ typedef void (*TasInitialize)(void);
67+ TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
68+ if (initFunction) {
69+ initFunction();
70+ } else {
71+ qCritical("Library qttestability resolve failed!");
72+ }
73+ } else {
74+ qCritical("Library qttestability load failed!");
75+ }
76+ }
77+
78+ bindtextdomain("unity8", translationDirectory().toUtf8().data());
79+ textdomain("unity8");
80+
81+ m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
82+
83+ if (parser.windowGeometry().isValid()) {
84+ m_shellView->setWidth(parser.windowGeometry().width());
85+ m_shellView->setHeight(parser.windowGeometry().height());
86+ }
87+
88+ if (parser.hasFrameless()) {
89+ m_shellView->setFlags(Qt::FramelessWindowHint);
90+ }
91+
92+ // You will need this if you want to interact with touch-only components using a mouse
93+ // Needed only when manually testing on a desktop.
94+ if (parser.hasMouseToTouch()) {
95+ m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
96+ }
97+
98+
99+ // Some hard-coded policy for now.
100+ // NB: We don't support more than two screens at the moment
101+ //
102+ // TODO: Support an arbitrary number of screens and different policies
103+ // (eg cloned desktop, several desktops, etc)
104+ if (isMirServer && screens().count() == 2) {
105+ m_shellView->setScreen(screens().at(1));
106+ m_qmlArgs.setDeviceName("desktop");
107+
108+ m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
109+ m_secondaryWindow->setScreen(screens().at(0));
110+ // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
111+ m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
112+ m_secondaryWindow->setVisible(true);
113+ }
114+
115+ if (parser.mode().compare("greeter") == 0) {
116+ QSize primaryScreenSize = application->primaryScreen()->size();
117+ view->setHeight(primaryScreenSize.height());
118+ view->setWidth(primaryScreenSize.width());
119+ view->show();
120+ } else if (isMirServer || parser.hasFullscreen()) {
121+ m_shellView->showFullScreen();
122+ } else {
123+ m_shellView->show();
124+ }
125+}
126+
127+ShellApplication::~ShellApplication()
128+{
129+ destroyResources();
130+}
131+
132+void ShellApplication::destroyResources()
133+{
134+ // Deletion order is important. Don't use QScopedPointers and the like
135+ // Otherwise the process will hang on shutdown (bug somewhere I guess).
136+ delete m_shellView;
137+ m_shellView = nullptr;
138+
139+ delete m_secondaryWindow;
140+ m_secondaryWindow = nullptr;
141+
142+ delete m_mouseTouchAdaptor;
143+ m_mouseTouchAdaptor = nullptr;
144+
145+ delete m_qmlEngine;
146+ m_qmlEngine = nullptr;
147+}
148+
149+void ShellApplication::setupQmlEngine(bool isMirServer)
150+{
151+ m_qmlEngine = new QQmlEngine(this);
152+
153+ m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
154+
155+ prependImportPaths(m_qmlEngine, ::overrideImportPaths());
156+ if (!isMirServer) {
157+ prependImportPaths(m_qmlEngine, ::nonMirImportPaths());
158+ }
159+ appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
160+
161+ m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
162+
163+ QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
164+}
165+
166+void ShellApplication::onScreenAdded(QScreen * /*screen*/)
167+{
168+ // TODO: Support an arbitrary number of screens and different policies
169+ // (eg cloned desktop, several desktops, etc)
170+ if (screens().count() == 2) {
171+ m_shellView->setScreen(screens().at(1));
172+ m_qmlArgs.setDeviceName("desktop");
173+ // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
174+ // its backing QPlatformWindow recreated). So lets refocus it.
175+ m_shellView->requestActivate();
176+
177+ m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
178+ m_secondaryWindow->setScreen(screens().at(0));
179+
180+ // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
181+ m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
182+ m_secondaryWindow->setVisible(true);
183+ }
184+}
185+
186+void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
187+{
188+ // TODO: Support an arbitrary number of screens and different policies
189+ // (eg cloned desktop, several desktops, etc)
190+ if (screen == m_shellView->screen()) {
191+ Q_ASSERT(screens().count() > 1);
192+ Q_ASSERT(screens().at(0) != screen);
193+ Q_ASSERT(m_secondaryWindow);
194+ delete m_secondaryWindow;
195+ m_secondaryWindow = nullptr;
196+ m_shellView->setScreen(screens().first());
197+ m_qmlArgs.setDeviceName(m_deviceName);
198+ // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
199+ // its backing QPlatformWindow recreated). So lets refocus it.
200+ m_shellView->requestActivate();
201+ }
202+}
src/ShellApplication.hnew file+55
@@ -0,0 +1,55 @@
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 SHELLAPPLICATION_H
18+#define SHELLAPPLICATION_H
19+
20+#include <QGuiApplication>
21+#include <QQmlEngine>
22+#include <QQuickView>
23+#include <QScopedPointer>
24+
25+#include "ApplicationArguments.h"
26+#include "MouseTouchAdaptor.h"
27+#include "SecondaryWindow.h"
28+#include "ShellView.h"
29+
30+class ShellApplication : public QGuiApplication
31+{
32+ Q_OBJECT
33+public:
34+ ShellApplication(int & argc, char ** argv, bool isMirServer);
35+ virtual ~ShellApplication();
36+
37+ void destroyResources();
38+public Q_SLOTS:
39+ // called by qtmir
40+ void onScreenAboutToBeRemoved(QScreen *screen);
41+
42+private Q_SLOTS:
43+ void onScreenAdded(QScreen*);
44+
45+private:
46+ void setupQmlEngine(bool isMirServer);
47+ QString m_deviceName;
48+ ApplicationArguments m_qmlArgs;
49+ ShellView *m_shellView;
50+ SecondaryWindow *m_secondaryWindow;
51+ MouseTouchAdaptor *m_mouseTouchAdaptor;
52+ QQmlEngine *m_qmlEngine;
53+};
54+
55+#endif // SHELLAPPLICATION_H
src/ShellView.cppnew file+59
@@ -0,0 +1,59 @@
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 "ShellView.h"
18+
19+// Qt
20+#include <QQmlContext>
21+#include <QQuickItem>
22+
23+// local
24+#include <paths.h>
25+
26+ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
27+ : QQuickView(engine, nullptr)
28+{
29+ setResizeMode(QQuickView::SizeRootObjectToView);
30+ setColor("black");
31+ setTitle(QStringLiteral("Unity8"));
32+
33+ rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
34+
35+ QUrl source(::qmlDirectory() + "/OrientedShell.qml");
36+ setSource(source);
37+
38+ connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
39+ connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
40+}
41+
42+void ShellView::onWidthChanged(int w)
43+{
44+ // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
45+ //
46+ // There's at least one situation that's know to leave the root object with an outdated size.
47+ // (really looks like Qt bug)
48+ // Happens when starting unity8 with an external monitor already connected.
49+ // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
50+ // from the resize event handler, the root item doesn't get resized.
51+ // TODO: Confirm the Qt bug and submit a patch upstream
52+ rootObject()->setWidth(w);
53+}
54+
55+void ShellView::onHeightChanged(int h)
56+{
57+ // See comment in ShellView::onWidthChanged()
58+ rootObject()->setHeight(h);
59+}
src/ShellView.hnew file+34
@@ -0,0 +1,34 @@
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 UNITY_SHELL_VIEW_H
18+#define UNITY_SHELL_VIEW_H
19+
20+#include <QQuickView>
21+
22+class ShellView : public QQuickView
23+{
24+ Q_OBJECT
25+
26+public:
27+ ShellView(QQmlEngine *engine, QObject *qmlArgs);
28+
29+private Q_SLOTS:
30+ void onWidthChanged(int);
31+ void onHeightChanged(int);
32+};
33+
34+#endif // UNITY_SHELL_VIEW_H
src/main.cpp+4−109
@@ -14,27 +14,8 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−// Qt
18−#include <QCommandLineParser>
19−#include <QtQuick/QQuickView>
20−#include <QtGui/QGuiApplication>
21−#include <QtQml/QQmlEngine>
22−#include <QtQml/QQmlContext>
23−#include <QLibrary>
24−#include <QDebug>
25−#include <QScreen>
26−#include <csignal>
27−#include <libintl.h>
28−
29−// libandroid-properties
30−#include <hybris/properties/properties.h>
31−
3217// local
33−#include <paths.h>
34−#include "MouseTouchAdaptor.h"
35−#include "ApplicationArguments.h"
36−#include "CachingNetworkManagerFactory.h"
37−#include "UnityCommandLineParser.h"
18+#include "ShellApplication.h"
3819
3920int main(int argc, const char *argv[])
4021{
@@ -44,98 +25,12 @@ int main(int argc, const char *argv[])
4425 isMirServer = true;
4526 }
4627
47− QGuiApplication::setApplicationName(QStringLiteral("unity8"));
48− QGuiApplication *application;
49−
50− application = new QGuiApplication(argc, (char**)argv);
51−
52− UnityCommandLineParser parser(*application);
53−
54− ApplicationArguments qmlArgs;
55−
56− if (!parser.deviceName().isEmpty()) {
57− qmlArgs.setDeviceName(parser.deviceName());
58− } else {
59− char buffer[200];
60− property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
61− qmlArgs.setDeviceName(QString(buffer));
62− }
63−
64− qmlArgs.setMode(parser.mode());
65−
66− // The testability driver is only loaded by QApplication but not by QGuiApplication.
67− // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
68− if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
69− QLibrary testLib(QStringLiteral("qttestability"));
70− if (testLib.load()) {
71− typedef void (*TasInitialize)(void);
72− TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
73− if (initFunction) {
74− initFunction();
75− } else {
76− qCritical("Library qttestability resolve failed!");
77− }
78− } else {
79− qCritical("Library qttestability load failed!");
80− }
81− }
82−
83− bindtextdomain("unity8", translationDirectory().toUtf8().data());
84− textdomain("unity8");
85−
86− QQuickView* view = new QQuickView();
87− view->setResizeMode(QQuickView::SizeRootObjectToView);
88− view->setColor("black");
89− view->setTitle(QStringLiteral("Unity8 Shell"));
90−
91− if (parser.windowGeometry().isValid()) {
92− view->setWidth(parser.windowGeometry().width());
93− view->setHeight(parser.windowGeometry().height());
94− }
95−
96− view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
97− view->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &qmlArgs);
98− if (parser.hasFrameless()) {
99− view->setFlags(Qt::FramelessWindowHint);
100− }
101−
102− // You will need this if you want to interact with touch-only components using a mouse
103− // Needed only when manually testing on a desktop.
104− MouseTouchAdaptor *mouseTouchAdaptor = 0;
105− if (parser.hasMouseToTouch()) {
106− mouseTouchAdaptor = MouseTouchAdaptor::instance();
107− }
108−
109− QUrl source(::qmlDirectory() + "/OrientedShell.qml");
110− prependImportPaths(view->engine(), ::overrideImportPaths());
111− if (!isMirServer) {
112− prependImportPaths(view->engine(), ::nonMirImportPaths());
113− }
114− appendImportPaths(view->engine(), ::fallbackImportPaths());
115−
116− CachingNetworkManagerFactory *managerFactory = new CachingNetworkManagerFactory();
117− view->engine()->setNetworkAccessManagerFactory(managerFactory);
118−
119− view->setSource(source);
120− QObject::connect(view->engine(), &QQmlEngine::quit, application, &QGuiApplication::quit);
121−
122− // FIXME: This is a workaround to make the greeter fullscreen as
123− // view->showFullScreen() doesn't work quite right
124− if (parser.mode().compare("greeter") == 0) {
125− QSize primaryScreenSize = application->primaryScreen()->size();
126− view->setHeight(primaryScreenSize.height());
127− view->setWidth(primaryScreenSize.width());
128− view->show();
129− } else if (isMirServer || parser.hasFullscreen()) {
130− view->showFullScreen();
131− } else {
132− view->show();
133− }
28+ ShellApplication *application = new ShellApplication(argc, (char**)argv, isMirServer);
13429
13530 int result = application->exec();
13631
137− delete view;
138− delete mouseTouchAdaptor;
32+ application->destroyResources();
33+
13934 delete application;
14035
14136 return result;
tests/autopilot/unity8/fixture_setup.py+1−1
@@ -133,7 +133,7 @@ class LaunchUnityWithFakeSensors(fixtures.Fixture):
133133 args = [binary_arg] + env_args
134134 self.unity_proxy = process_helpers.restart_unity_with_testability(
135135 *args)
136− self.main_win = self.unity_proxy.select_single(shell.QQuickView)
136+ self.main_win = self.unity_proxy.select_single(shell.ShellView)
137137
138138 def _create_sensors(self):
139139 # Wait for unity to start running.
tests/autopilot/unity8/greeter/tests/__init__.py+1−1
@@ -24,5 +24,5 @@ from unity8.shell.tests import UnityTestCase
2424class GreeterTestCase(UnityTestCase):
2525 def get_shell(self, unity_proxy):
2626 main_window = (
27− unity_proxy.select_single(shell.QQuickView))
27+ unity_proxy.select_single(shell.ShellView))
2828 return main_window.select_single('Shell')
tests/autopilot/unity8/launcher.py+1−1
@@ -42,7 +42,7 @@ class Launcher(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
4242 logger.debug('The launcher is already opened.')
4343
4444 def _swipe_to_show_launcher(self):
45− view = self.get_root_instance().select_single('QQuickView')
45+ view = self.get_root_instance().select_single('ShellView')
4646 start_y = stop_y = view.y + view.height // 2
4747
4848 start_x = view.x + 1
tests/autopilot/unity8/settings_wizard/__init__.py+7−2
@@ -375,12 +375,17 @@ class WifiConnectPage(UbuntuUIToolkitCustomProxyObjectBase):
375375 wizard = get_wizard(self)
376376 next_page = wizard.get_current_page()
377377 locationPageEnabled = True
378+ reportingPageEnabled = True
378379 if next_page.objectName == 'locationPage':
379380 next_page = wizard.get_location_page()
380381 else:
381382 locationPageEnabled = False
382− next_page = wizard.get_reporting_page()
383− return locationPageEnabled, next_page
383+ if next_page.objectName == 'reportingPage':
384+ next_page = wizard.get_reporting_page()
385+ else:
386+ reportingPageEnabled = False
387+ next_page = wizard.get_finished_page()
388+ return locationPageEnabled, reportingPageEnabled, next_page
384389
385390 def _get_notification(self, unity):
386391 logger.info('Waiting longer for notification object')
tests/autopilot/unity8/settings_wizard/tests/test_settings_wizard.py+10−5
@@ -80,14 +80,19 @@ class SkipThroughSettingsWizardTestCase(tests.UnityTestCase):
8080 password_page = next_page
8181 wifi_connect_page = self._test_password_page(password_page)
8282
83− locationPageEnabled, next_page = self._test_wifi_connect_page(
84− wifi_connect_page)
83+ reporting_page = None
84+ locationPageEnabled, reportingPageEnabled, next_page = self._test_wifi_connect_page(wifi_connect_page)
8585 if locationPageEnabled:
8686 location_page = next_page
87− reporting_page = self._test_location_page(location_page)
87+ if reportingPageEnabled:
88+ reporting_page = self._test_location_page(location_page)
89+ else:
90+ finish_page = next_page
91+
92+ if reporting_page is not None:
93+ finish_page = self._test_reporting_page(reporting_page)
8894 else:
89− reporting_page = next_page
90− finish_page = self._test_reporting_page(reporting_page)
95+ finish_page = next_page
9196
9297 finish_page.finish()
9398 self.assertFalse(
tests/autopilot/unity8/shell/__init__.py+1−1
@@ -93,7 +93,7 @@ def _get_urgency(urgency):
9393 return _urgency_enums.get(urgency.upper())
9494
9595
96−class QQuickView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
96+class ShellView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
9797 """An helper class that makes it easy to interact with the shell"""
9898
9999 def get_greeter(self):
tests/autopilot/unity8/shell/tests/__init__.py+1−1
@@ -297,7 +297,7 @@ class UnityTestCase(AutopilotTestCase):
297297
298298 @property
299299 def main_window(self):
300− return self._proxy.select_single(shell.QQuickView)
300+ return self._proxy.select_single(shell.ShellView)
301301
302302
303303class DashBaseTestCase(AutopilotTestCase):
tests/mocks/CMakeLists.txt+1
@@ -29,6 +29,7 @@ macro(add_unity8_mock PLUGIN VERSION PATH)
2929endmacro()
3030
3131add_subdirectory(AccountsService)
32+add_subdirectory(Cursor)
3233add_subdirectory(GSettings.1.0)
3334add_subdirectory(indicator-service)
3435add_subdirectory(libusermetrics)
tests/mocks/Cursor/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_unity8_mock(Cursor 1.0 Cursor PREFIX mocks)
tests/mocks/Cursor/Cursor.qmlnew file+20
@@ -0,0 +1,20 @@
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+import QtQuick 2.4
18+
19+Item {
20+}
tests/mocks/Cursor/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Cursor
2+Cursor 1.0 Cursor.qml
tests/mocks/Unity/Launcher/MockQuickListModel.cpp+1−1
@@ -32,7 +32,7 @@ QVariant MockQuickListModel::data(const QModelIndex &index, int role) const
3232 switch (role)
3333 {
3434 case RoleLabel:
35− return QLatin1String("test menu entry ") + QString::number(index.row());
35+ return QString(QLatin1String("test menu entry ") + QString::number(index.row()));
3636 case RoleIcon:
3737 return QLatin1String("copy.png");
3838 case RoleClickable:
tests/mocks/Unity/fake_resultsmodel.cpp+1−1
@@ -78,7 +78,7 @@ ResultsModel::data(const QModelIndex& index, int role) const
7878 case RoleTitle:
7979 return QString("Title.%1.%2").arg(m_categoryId).arg(index.row());
8080 case RoleArt:
81− return qmlDirectory() + "/graphics/applicationIcons/dash.png";
81+ return QString(qmlDirectory() + "/graphics/applicationIcons/dash.png");
8282 case RoleSubtitle:
8383 return QString("Subtitle.%1.%2").arg(m_categoryId).arg(index.row());
8484 case RoleMascot:
tests/mocks/Unity/fake_scopesoverview.cpp+1−1
@@ -308,7 +308,7 @@ QVariant ScopesOverviewResultsModel::data(const QModelIndex& index, int role) co
308308 case RoleSubtitle:
309309 return scope && scope->name() == "Videos this is long ab cd ef gh ij kl" ? "tube, movies, cinema, pictures, art, moving images, magic in a box" : QString();
310310 case RoleArt:
311− return qmlDirectory() + "/graphics/applicationIcons/dash.png";
311+ return QString(qmlDirectory() + "/graphics/applicationIcons/dash.png");
312312 case RoleMascot:
313313 case RoleEmblem:
314314 case RoleSummary:
tests/mocks/Utils/CMakeLists.txt−2
@@ -13,8 +13,6 @@ set(QMLPLUGIN_SRC
1313 ${CMAKE_SOURCE_DIR}/plugins/Utils/activefocuslogger.cpp
1414 ${CMAKE_SOURCE_DIR}/plugins/Utils/qlimitproxymodelqml.cpp
1515 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
16− ${CMAKE_SOURCE_DIR}/plugins/Utils/relativetimeformatter.cpp
17− ${CMAKE_SOURCE_DIR}/plugins/Utils/timeformatter.cpp
1816 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
1917 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowkeysfilter.cpp
2018 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
tests/mocks/Utils/Utils.qmltypes+31−28
@@ -4,9 +4,18 @@ import QtQuick.tooling 1.1
44// It is used for QML tooling purposes only.
55//
66// This file was auto-generated by:
7−// 'qmlplugindump -notrelocatable Utils 0.1 plugins'
7+// 'qmlplugindump -notrelocatable Utils 0.1 tests/mocks'
88
99Module {
10+ Component {
11+ name: "Constants"
12+ prototype: "QObject"
13+ exports: ["Utils/Constants 0.1"]
14+ isCreatable: false
15+ isSingleton: true
16+ exportMetaObjectRevisions: [0]
17+ Property { name: "indicatorValueTimeout"; type: "int"; isReadonly: true }
18+ }
1019 Component {
1120 name: "EasingCurve"
1221 prototype: "QObject"
@@ -17,6 +26,22 @@ Module {
1726 Property { name: "progress"; type: "double" }
1827 Property { name: "value"; type: "double"; isReadonly: true }
1928 }
29+ Component {
30+ name: "InputWatcher"
31+ prototype: "QObject"
32+ exports: ["Utils/InputWatcher 0.1"]
33+ exportMetaObjectRevisions: [0]
34+ Property { name: "target"; type: "QObject"; isPointer: true }
35+ Property { name: "targetPressed"; type: "bool"; isReadonly: true }
36+ Signal {
37+ name: "targetChanged"
38+ Parameter { name: "value"; type: "QObject"; isPointer: true }
39+ }
40+ Signal {
41+ name: "targetPressedChanged"
42+ Parameter { name: "value"; type: "bool" }
43+ }
44+ }
2045 Component {
2146 name: "QAbstractProxyModel"
2247 prototype: "QAbstractItemModel"
@@ -59,33 +84,6 @@ Module {
5984 Method { name: "clear" }
6085 Method { name: "invalidate" }
6186 }
62− Component {
63− name: "RelativeTimeFormatter"
64− prototype: "TimeFormatter"
65− exports: ["Utils/RelativeTimeFormatter 0.1"]
66− exportMetaObjectRevisions: [0]
67− }
68− Component {
69− name: "TimeFormatter"
70− prototype: "QObject"
71− exports: ["Utils/GDateTimeFormatter 0.1", "Utils/TimeFormatter 0.1"]
72− exportMetaObjectRevisions: [0, 0]
73− Property { name: "format"; type: "string" }
74− Property { name: "timeString"; type: "string"; isReadonly: true }
75− Property { name: "time"; type: "qlonglong" }
76− Signal {
77− name: "formatChanged"
78− Parameter { name: "format"; type: "string" }
79− }
80− Signal {
81− name: "timeStringChanged"
82− Parameter { name: "timeString"; type: "string" }
83− }
84− Signal {
85− name: "timeChanged"
86− Parameter { name: "time"; type: "qlonglong" }
87− }
88− }
8987 Component {
9088 name: "UnityMenuModelPaths"
9189 prototype: "QObject"
@@ -160,6 +158,11 @@ Module {
160158 isCreatable: false
161159 isSingleton: true
162160 exportMetaObjectRevisions: [0]
161+ Property { name: "geometry"; type: "QVariantMap" }
162+ Signal {
163+ name: "geometryChanged"
164+ Parameter { name: "geometry"; type: "QVariantMap" }
165+ }
163166 Method {
164167 name: "saveGeometry"
165168 Parameter { name: "windowId"; type: "string" }
tests/mocks/Utils/plugin.cpp−13
@@ -23,14 +23,6 @@
2323
2424// local
2525#include "plugin.h"
26−#include "inputwatcher.h"
27−#include "qlimitproxymodelqml.h"
28−#include "unitysortfilterproxymodelqml.h"
29−#include "relativetimeformatter.h"
30−#include "timeformatter.h"
31−#include "unitymenumodelpaths.h"
32−#include "windowkeysfilter.h"
33−#include "easingcurve.h"
3426#include "windowstatestorage.h"
3527#include "constants.h"
3628
@@ -39,8 +31,6 @@
3931#include <inputwatcher.h>
4032#include <qlimitproxymodelqml.h>
4133#include <unitysortfilterproxymodelqml.h>
42−#include <relativetimeformatter.h>
43−#include <timeformatter.h>
4434#include <unitymenumodelpaths.h>
4535#include <windowkeysfilter.h>
4636#include <windowscreenshotprovider.h>
@@ -67,11 +57,8 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
6757 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
6858 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
6959 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
70− qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
7160 qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
72− qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
7361 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
74− qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
7562 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
7663 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
7764 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
tests/plugins/ScreenGrabber/ScreenGrabberTest.cpp+11
@@ -55,6 +55,17 @@ private Q_SLOTS:
5555 QVERIFY(!args.first().toString().isEmpty());
5656 }
5757
58+ void testRotatedScreenshot()
59+ {
60+ QSignalSpy grabberSpy(m_grabber, &ScreenGrabber::screenshotSaved);
61+ m_grabber->captureAndSave(90); // rotate by 90°
62+ const QVariantList args = grabberSpy.takeFirst();
63+ const QString filename = args.first().toString();
64+ QVERIFY(!filename.isEmpty());
65+ QImage img(filename);
66+ QVERIFY(img.height() > img.width()); // verify that the image got rotated by 90° (height > width)
67+ }
68+
5869private:
5970 QQuickView *m_view;
6071 ScreenGrabber *m_grabber = nullptr;
tests/plugins/ScreenGrabber/grabber.qml+1−1
@@ -20,7 +20,7 @@ import ScreenGrabber 0.1
2020Rectangle {
2121 property var grabber: screenGrabber
2222
23− width: 100
23+ width: 101 // width intentionally bigger than height to test rotation
2424 height: 100
2525 color: "green"
2626
tests/plugins/Unity/Indicators/UnityMenuModelStackTest.cpp+2−9
@@ -43,6 +43,8 @@ private Q_SLOTS:
4343 {
4444 delete m_model;
4545 m_model = nullptr;
46+ // send deleteLaters to avoid leaks.
47+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
4648 }
4749
4850 void testHeadOnSetHead()
@@ -146,19 +148,10 @@ private Q_SLOTS:
146148
147149 m_model->removeRow(removeIndex);
148150
149− waitFor([&stack, resultCount]() { return stack.count() == resultCount; }, 1000);
150151 QCOMPARE(stack.count(), resultCount);
151152 }
152153
153154private:
154− bool waitFor(std::function<bool()> functor, int ms) {
155−
156− QElapsedTimer timer;
157− timer.start();
158− while(!functor() && timer.elapsed() < ms) { QTest::qWait(10); }
159− return functor();
160− }
161−
162155 QVariant recuseAddMenu(int subMenuCount, int depth_remaining)
163156 {
164157 QVariantList rows;
tests/plugins/Utils/CMakeLists.txt−1
@@ -7,7 +7,6 @@ include_directories(
77foreach(util_test
88 QLimitProxyModel
99 UnitySortFilterProxyModel
10− TimeFormatter
1110 HomeKeyWatcher
1211)
1312 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
tests/plugins/Utils/TimeFormatterTest.cppdeleted−155
@@ -1,155 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
17− */
18−
19−#include "timeformatter.h"
20−#include "relativetimeformatter.h"
21−
22−#include <locale.h>
23−
24−#include <QtTest>
25−#include <QDebug>
26−
27−class TimeFormatterTest : public QObject
28−{
29− Q_OBJECT
30−private:
31− void addTestData(QList<int> daysToAdd = QList<int>()) {
32− QTest::addColumn<QDateTime>("time");
33−
34− Q_FOREACH(int dayToAdd, daysToAdd) {
35− QDateTime time = QDateTime::currentDateTime().addDays(dayToAdd);
36− QDateTime am(time); am.setTime(QTime(1, 1, 1));
37− QDateTime midday(time); midday.setTime(QTime(12, 0, 0));
38− QDateTime pm(time); pm.setTime(QTime(17, 59, 59));
39−
40− QTest::newRow(QString("%1day-am").arg(dayToAdd).toLatin1()) << am;
41− QTest::newRow(QString("%1day-midday").arg(dayToAdd).toLatin1()) << midday;
42− QTest::newRow(QString("%1day-pm").arg(dayToAdd).toLatin1()) << pm;
43− }
44−
45− }
46−
47−private Q_SLOTS:
48− void testTimeFormatter_Format_data() { addTestData(QList<int>() << 0); }
49− void testTimeFormatter_Format()
50− {
51− QFETCH(QDateTime, time);
52− const QString format = "hh:mm dd.MM.yy";
53−
54− TimeFormatter formatter;
55− formatter.setTime(time.toMSecsSinceEpoch() * 1000);
56− formatter.setFormat(format);
57−
58− QCOMPARE(formatter.timeString(), time.toString(format));
59− }
60−
61− void tesGDatetTimeFormatter_Format_data() { addTestData(QList<int>() << 0); }
62− void tesGDatetTimeFormatter_Format()
63− {
64− QFETCH(QDateTime, time);
65− const QString format = "%d-%m-%Y %I:%M%p";
66−
67− GDateTimeFormatter formatter;
68− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
69− formatter.setFormat(format);
70−
71− QCOMPARE(formatter.timeString(), time.toString("dd-MM-yyyy hh:mmAP"));
72− }
73−
74− void tesRelativeTimeFormatter_FarBack_data() { addTestData(QList<int>() << -200 << -20 << -7); }
75− void tesRelativeTimeFormatter_FarBack()
76− {
77− QFETCH(QDateTime, time);
78−
79− RelativeTimeFormatter formatter;
80− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
81−
82− QCOMPARE(formatter.timeString(), time.toString("ddd dd MMM\u2003HH:mm"));
83− }
84−
85− void tesRelativeTimeFormatter_WeekBack_data() { addTestData(QList<int>() << -6 << -2); }
86− void tesRelativeTimeFormatter_WeekBack()
87− {
88− QFETCH(QDateTime, time);
89−
90− RelativeTimeFormatter formatter;
91− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
92−
93− QCOMPARE(formatter.timeString(), time.toString("ddd\u2003HH:mm"));
94− }
95−
96− void tesRelativeTimeFormatter_Yesterday_data() { addTestData(QList<int>() << -1); }
97− void tesRelativeTimeFormatter_Yesterday()
98− {
99− QFETCH(QDateTime, time);
100−
101− RelativeTimeFormatter formatter;
102− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
103−
104− QCOMPARE(formatter.timeString(), QString("Yesterday\u2003%1").arg(time.toString("HH:mm")));
105− }
106−
107− void tesRelativeTimeFormatter_Today_data() { addTestData(QList<int>() << 0); }
108− void tesRelativeTimeFormatter_Today()
109− {
110− QFETCH(QDateTime, time);
111−
112− RelativeTimeFormatter formatter;
113− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
114−
115− QCOMPARE(formatter.timeString(), time.toString("HH:mm"));
116− }
117−
118− void tesRelativeTimeFormatter_Tomorrow_data() { addTestData(QList<int>() << 1); }
119− void tesRelativeTimeFormatter_Tomorrow()
120− {
121− QFETCH(QDateTime, time);
122−
123− RelativeTimeFormatter formatter;
124− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
125−
126− QCOMPARE(formatter.timeString(), QString("Tomorrow\u2003%1").arg(time.toString("HH:mm")));
127− }
128−
129−
130− void tesRelativeTimeFormatter_WeekForward_data() { addTestData(QList<int>() << 2 << 6); }
131− void tesRelativeTimeFormatter_WeekForward()
132− {
133− QFETCH(QDateTime, time);
134−
135− RelativeTimeFormatter formatter;
136− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
137−
138− QCOMPARE(formatter.timeString(), time.toString("ddd\u2003HH:mm"));
139− }
140−
141− void tesRelativeTimeFormatter_FarForward_data() { addTestData(QList<int>() << 7 << 20 << 200); }
142− void tesRelativeTimeFormatter_FarForward()
143− {
144− QFETCH(QDateTime, time);
145−
146− RelativeTimeFormatter formatter;
147− formatter.setTime(time.toMSecsSinceEpoch() / 1000); // strftime in seconds since epoc
148−
149− QCOMPARE(formatter.timeString(), time.toString("ddd dd MMM\u2003HH:mm"));
150− }
151−};
152−
153−QTEST_GUILESS_MAIN(TimeFormatterTest)
154−
155−#include "TimeFormatterTest.moc"
tests/plugins/Wizard/tst_pagelist.cpp+5−5
@@ -88,13 +88,13 @@ void PageListTest::testIterate()
8888
8989 PageList pageList;
9090 QCOMPARE(pageList.index(), -1);
91− QCOMPARE(pageList.next(), root.path() + "/a/" + PAGES_PATH + "/1.qml");
91+ QCOMPARE(pageList.next(), QString(root.path() + "/a/" + PAGES_PATH + "/1.qml"));
9292 QCOMPARE(pageList.prev(), QString());
93− QCOMPARE(pageList.next(), root.path() + "/a/" + PAGES_PATH + "/2.qml");
94− QCOMPARE(pageList.prev(), root.path() + "/a/" + PAGES_PATH + "/1.qml");
93+ QCOMPARE(pageList.next(), QString(root.path() + "/a/" + PAGES_PATH + "/2.qml"));
94+ QCOMPARE(pageList.prev(), QString(root.path() + "/a/" + PAGES_PATH + "/1.qml"));
9595 QCOMPARE(pageList.index(), 0);
96− QCOMPARE(pageList.next(), root.path() + "/a/" + PAGES_PATH + "/2.qml");
97− QCOMPARE(pageList.next(), root.path() + "/a/" + PAGES_PATH + "/3.qml");
96+ QCOMPARE(pageList.next(), QString(root.path() + "/a/" + PAGES_PATH + "/2.qml"));
97+ QCOMPARE(pageList.next(), QString(root.path() + "/a/" + PAGES_PATH + "/3.qml"));
9898 QCOMPARE(pageList.index(), 2);
9999 QCOMPARE(pageList.next(), QString());
100100 QCOMPARE(pageList.index(), 2);
tests/qmltests/CMakeLists.txt+3−1
@@ -2,6 +2,7 @@
22add_subdirectory(Components)
33
44add_unity8_qmltest(. OrientedShell LIGHTDM)
5+add_unity8_qmltest(. DisabledScreenNotice)
56add_unity8_qmltest(. Shell LIGHTDM)
67add_unity8_qmltest(. ShellWithPin LIGHTDM)
78add_unity8_qmltest(Components Background)
@@ -15,6 +16,7 @@ add_unity8_qmltest(Components Rating)
1516add_unity8_qmltest(Components ResponsiveGridView)
1617add_unity8_qmltest(Components ResponsiveVerticalJournal)
1718add_unity8_qmltest(Components Showable)
19+add_unity8_qmltest(Components WallpaperResolver)
1820add_unity8_qmltest(Components ZoomableImage)
1921add_unity8_qmltest(Dash Dash)
2022add_unity8_qmltest(Dash DashContent)
@@ -78,7 +80,7 @@ add_unity8_qmltest(Stages SpreadDelegate)
7880add_unity8_qmltest(Stages SurfaceContainer)
7981add_unity8_qmltest(Stages SessionContainer)
8082add_unity8_qmltest(Stages TabletStage)
81−add_unity8_qmltest(Stages WindowMoveResizeArea)
83+add_unity8_qmltest(Stages WindowResizeArea)
8284add_unity8_qmltest(Stages Splash)
8385add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
8486add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
tests/qmltests/Components/tst_WallpaperResolver.qmlnew file+89
@@ -0,0 +1,89 @@
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+import QtQuick 2.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import Unity.Test 0.1
21+import AccountsService 0.1
22+import GSettings 1.0
23+
24+import "../../../qml/Components"
25+
26+
27+Image {
28+ width: units.gu(70)
29+ height: units.gu(70)
30+
31+ source: wallpaperResolver.background
32+
33+ WallpaperResolver {
34+ id: wallpaperResolver
35+ width: units.gu(70)
36+ }
37+
38+ UnityTestCase {
39+ id: testCase
40+ name: "WallpaperResolver"
41+ when: windowShown
42+
43+ function test_background_data() {
44+ return [
45+ {tag: "color",
46+ accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
47+ gsettings: "",
48+ output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
49+
50+ {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
51+
52+ {tag: "as-specified",
53+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
54+ gsettings: "",
55+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
56+
57+ {tag: "gs-specified",
58+ accounts: "",
59+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
60+ output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
61+
62+ {tag: "both-specified",
63+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
64+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
65+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
66+
67+ {tag: "invalid-as",
68+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
69+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
70+ output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
71+
72+ {tag: "invalid-both",
73+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
74+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/stillnope.png"),
75+ output: "defaultBackground"},
76+ ]
77+ }
78+ function test_background(data) {
79+ AccountsService.backgroundFile = data.accounts;
80+ GSettingsController.setPictureUri(data.gsettings);
81+
82+ if (data.output === "defaultBackground") {
83+ tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
84+ } else {
85+ tryCompare(wallpaperResolver, "background", data.output);
86+ }
87+ }
88+ }
89+}
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+6−16
@@ -50,10 +50,6 @@ Item {
5050 }
5151 }
5252
53− Utils.GDateTimeFormatter {
54− id: timeFormatter
55− }
56−
5753 UT.UnityTestCase {
5854 name: "MenuItemFactory"
5955 when: windowShown
@@ -374,9 +370,9 @@ Item {
374370 function test_create_alarmMenu_data() {
375371 return [
376372 {label: "testLabel1", enabled: true, icon: "file:///testIcon1", color: Qt.rgba(0, 0, 0, 0),
377− time: new Date(2014, 04, 14), timeFormat: "%a %d %b %l:%M %p"},
373+ time: new Date(2014, 04, 14)},
378374 {label: "testLabel2", enabled: false, icon: "file:///testIcon2", color: Qt.rgba(1, 0, 0, 0),
379− time: new Date(2015, 11, 31), timeFormat: "%A" },
375+ time: new Date(2015, 11, 31) },
380376 ];
381377 }
382378
@@ -386,11 +382,8 @@ Item {
386382 menuData.sensitive = data.enabled;
387383 menuData.icon = data.icon;
388384 menuData.ext = {
389− 'xCanonicalTime': data.time.getTime() / 1000,
390− 'xCanonicalTimeFormat': data.timeFormat
385+ 'xCanonicalTime': data.time.getTime() / 1000 // expected in seconds
391386 };
392− timeFormatter.format = data.timeFormat;
393− timeFormatter.time = data.time.getTime() / 1000;
394387
395388 loader.data = menuData;
396389 loader.sourceComponent = factory.load(menuData);
@@ -399,7 +392,7 @@ Item {
399392
400393 compare(loader.item.text, data.label, "Label does not match data");
401394 compare(loader.item.iconSource, data.icon, "Icon does not match data");
402− compare(loader.item.time, timeFormatter.timeString, "Time does not match data");
395+ compare(loader.item.time, i18n.relativeDateTime(data.time), "Time does not match data");
403396 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
404397 }
405398
@@ -419,11 +412,8 @@ Item {
419412 menuData.icon = data.icon;
420413 menuData.ext = {
421414 'xCanonicalColor': data.colour,
422− 'xCanonicalTime': data.time.getTime() / 1000,
423− 'xCanonicalTimeFormat': data.timeFormat
415+ 'xCanonicalTime': data.time.getTime() / 1000 // expected in seconds
424416 };
425− timeFormatter.format = data.timeFormat;
426− timeFormatter.time = data.time.getTime() / 1000;
427417
428418 loader.data = menuData;
429419 loader.sourceComponent = factory.load(menuData);
@@ -432,7 +422,7 @@ Item {
432422
433423 compare(loader.item.text, data.label, "Label does not match data");
434424 compare(loader.item.iconSource, data.icon, "Icon does not match data");
435− compare(loader.item.time, timeFormatter.timeString, "Time does not match data");
425+ compare(loader.item.time, i18n.relativeDateTime(data.time), "Time does not match data");
436426 compare(loader.item.eventColor, data.color, "Colour does not match data");
437427 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
438428 }
tests/qmltests/Panel/Indicators/tst_MessageMenuItemFactory.qml+6−13
@@ -32,10 +32,6 @@ Item {
3232 menuIndex: 0
3333 }
3434
35− Utils.RelativeTimeFormatter {
36− id: timeFormatter
37− }
38−
3935 UT.UnityTestCase {
4036 name: "MessageMenuItemFactory"
4137 when: windowShown
@@ -82,17 +78,16 @@ Item {
8278 menuData.label = data.title;
8379 menuData.sensitive = data.enabled;
8480 menuData.ext = {
85− 'xCanonicalTime': data.time.getTime()*1000,
81+ 'xCanonicalTime': data.time.getTime()*1000, // expected in microseconds
8682 'xCanonicalText': data.body,
8783 'icon': data.avatar,
8884 'xCanonicalAppIcon': data.icon,
8985 };
9086 factory.menuData = menuData;
91− timeFormatter.time = data.time.getTime()/1000;
9287
9388 tryCompare(factory.item, "objectName", "simpleTextMessage");
9489 compare(factory.item.title, data.title, "Title does not match data");
95− compare(factory.item.time, timeFormatter.timeString, "Time does not match data");
90+ compare(factory.item.time, i18n.relativeDateTime(data.time), "Time does not match data");
9691 compare(factory.item.body, data.body, "Message does not match data");
9792 compare(factory.item.avatar, data.avatar, "Avatar does not match data");
9893 compare(factory.item.icon, data.icon, "App icon does not match data");
@@ -111,7 +106,7 @@ Item {
111106 menuData.label = data.title;
112107 menuData.sensitive = data.enabled;
113108 menuData.ext = {
114− 'xCanonicalTime': data.time.getTime()*1000,
109+ 'xCanonicalTime': data.time.getTime()*1000, // expected in microseconds
115110 'xCanonicalText': data.body,
116111 'icon': data.avatar,
117112 'xCanonicalAppIcon': data.icon,
@@ -123,11 +118,10 @@ Item {
123118 ]
124119 };
125120 factory.menuData = menuData;
126− timeFormatter.time = data.time.getTime()/1000;
127121
128122 tryCompare(factory.item, "objectName", "textMessage");
129123 compare(factory.item.title, data.title, "Title does not match data");
130− compare(factory.item.time, timeFormatter.timeString, "Time does not match data");
124+ compare(factory.item.time, i18n.relativeDateTime(data.time), "Time does not match data");
131125 compare(factory.item.body, data.body, "Message does not match data");
132126 compare(factory.item.avatar, data.avatar, "Avatar does not match data");
133127 compare(factory.item.icon, data.icon, "App icon does not match data");
@@ -147,7 +141,7 @@ Item {
147141 menuData.label = data.title;
148142 menuData.sensitive = data.enabled;
149143 menuData.ext = {
150− 'xCanonicalTime': data.time.getTime()*1000,
144+ 'xCanonicalTime': data.time.getTime()*1000, // expected in microseconds
151145 'xCanonicalText': data.body,
152146 'icon': data.avatar,
153147 'xCanonicalAppIcon': data.icon,
@@ -162,11 +156,10 @@ Item {
162156 ]
163157 };
164158 factory.menuData = menuData;
165− timeFormatter.time = data.time.getTime()/1000;
166159
167160 tryCompare(factory.item, "objectName", "snapDecision");
168161 compare(factory.item.title, data.title, "Title does not match data");
169− compare(factory.item.time, timeFormatter.timeString, "Time does not match data");
162+ compare(factory.item.time, i18n.relativeDateTime(data.time), "Time does not match data");
170163 compare(factory.item.body, data.body, "Message does not match data");
171164 compare(factory.item.avatar, data.avatar, "Avatar does not match data");
172165 compare(factory.item.icon, data.icon, "App icon does not match data");
tests/qmltests/Stages/tst_WindowMoveResizeArea.qml → tests/qmltests/Stages/tst_WindowResizeArea.qmlrenamed+18−61
@@ -17,7 +17,7 @@
1717import QtQuick 2.1
1818import QtQuick.Layouts 1.1
1919import QtTest 1.0
20−import Unity.Test 0.1 as UT
20+import Unity.Test 0.1
2121import ".."
2222import "../../../qml/Stages"
2323import Ubuntu.Components 0.1
@@ -36,8 +36,8 @@ Item {
3636
3737 Item {
3838 id: fakeWindow
39− property alias minWidth: moveResizeArea.minWidth
40− property alias minHeight: moveResizeArea.minHeight
39+ property alias minWidth: windowResizeArea.minWidth
40+ property alias minHeight: windowResizeArea.minHeight
4141 x: units.gu(20)
4242 y: units.gu(20)
4343 height: units.gu(20)
@@ -47,23 +47,27 @@ Item {
4747 onWindowHeightChanged: height = windowHeight
4848 onWindowWidthChanged: width = windowWidth
4949
50− WindowMoveResizeArea {
51− id: moveResizeArea
50+ WindowResizeArea {
51+ id: windowResizeArea
5252 target: fakeWindow
53− resizeHandleWidth: units.gu(0.5)
53+ borderThickness: units.gu(2)
5454 minWidth: units.gu(15)
5555 minHeight: units.gu(10)
5656 windowId: "test-window-id"
5757 }
5858
5959 Rectangle {
60− anchors.fill: moveResizeArea
60+ anchors.fill: windowResizeArea
6161 color: "red"
6262 }
6363
6464 Rectangle {
6565 anchors.fill: fakeWindow
6666 color: "blue"
67+ MouseArea {
68+ anchors.fill: parent
69+ hoverEnabled: true
70+ }
6771 }
6872 }
6973 }
@@ -73,8 +77,13 @@ Item {
7377 sourceComponent: fakeWindowComponent
7478 }
7579
76− UT.UnityTestCase {
77− name: "WindowMoveResizeArea"
80+ MouseTouchEmulationCheckbox {
81+ checked: false
82+ color: "black"
83+ }
84+
85+ UnityTestCase {
86+ name: "WindowResizeArea"
7887 when: windowShown
7988
8089 function init() {
@@ -84,34 +93,6 @@ Item {
8493 fakeWindow.height = units.gu(20)
8594 }
8695
87− function test_dragWindow_data() {
88− return [
89− { tag: "up", dx: 0, dy: units.gu(-10) },
90− { tag: "down", dx: 0, dy: units.gu(10) },
91− { tag: "left", dx: units.gu(-10), dy: 0 },
92− { tag: "right", dx: units.gu(10), dy: 0 },
93− { tag: "right/down", dx: units.gu(10), dy: units.gu(10) },
94− { tag: "left/down", dx: units.gu(-10), dy: units.gu(10) }
95− ]
96− }
97−
98− function test_dragWindow(data) {
99− var initialWindowX = fakeWindow.x;
100− var initialWindowY = fakeWindow.y;
101− var initialWindowWidth = fakeWindow.width
102− var initialWindowHeight = fakeWindow.height
103−
104− var startDragX = initialWindowX + fakeWindow.width / 2;
105− var startDragY = initialWindowY + fakeWindow.height / 2;
106− mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10)
107−
108− tryCompare(fakeWindow, "x", initialWindowX + data.dx)
109− tryCompare(fakeWindow, "y", initialWindowX + data.dy)
110−
111− compare(fakeWindow.height, initialWindowHeight);
112− compare(fakeWindow.width, initialWindowWidth);
113− }
114−
11596 function test_resizeWindowRightBottom_data() {
11697 return [
11798 { tag: "width", dx: units.gu(10), dy: 0 },
@@ -166,30 +147,6 @@ Item {
166147 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
167148 }
168149
169− function test_saveRestorePosition() {
170− var initialWindowX = fakeWindow.x;
171− var initialWindowY = fakeWindow.y;
172− var initialWindowWidth = fakeWindow.width;
173− var initialWindowHeight = fakeWindow.height;
174−
175− var moveDelta = units.gu(5);
176− var startDragX = initialWindowX + fakeWindow.width / 2;
177− var startDragY = initialWindowY + fakeWindow.height / 2;
178− mouseFlick(root, startDragX, startDragY, startDragX + moveDelta, startDragY + moveDelta, true, true, units.gu(.5), 10)
179−
180− tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
181− tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
182−
183− // This will destroy the window and recreate it
184− windowLoader.active = false;
185− waitForRendering(root);
186− windowLoader.active = true;
187−
188− // Make sure it's again where we left it before destroying
189− tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
190− tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
191− }
192−
193150 function test_saveRestoreSize() {
194151 var initialWindowX = fakeWindow.x;
195152 var initialWindowY = fakeWindow.y;
tests/qmltests/tst_DisabledScreenNotice.qmlnew file+37
@@ -0,0 +1,37 @@
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+import QtQuick 2.4
18+import QtTest 1.0
19+import Unity.Test 0.1
20+import "../../qml"
21+
22+
23+Item {
24+ id: root
25+ width: units.gu(70)
26+ height: units.gu(70)
27+
28+ DisabledScreenNotice {
29+ anchors.fill: parent
30+ }
31+
32+ UnityTestCase {
33+ id: testCase
34+ name: "DisabledScreenNotice"
35+ when: windowShown
36+ }
37+}
tests/qmltests/tst_OrientedShell.qml+55−1
@@ -110,6 +110,22 @@ Rectangle {
110110 physicalOrientation180: Qt.LandscapeOrientation
111111 primaryOrientationAngle: 90
112112 }
113+ },
114+ State {
115+ name: "desktop"
116+ PropertyChanges {
117+ target: orientedShellLoader
118+ width: units.gu(100)
119+ height: units.gu(56)
120+ }
121+ PropertyChanges {
122+ target: root
123+ physicalOrientation270: Qt.InvertedPortraitOrientation
124+ physicalOrientation0: Qt.LandscapeOrientation
125+ physicalOrientation90: Qt.PortraitOrientation
126+ physicalOrientation180: Qt.InvertedLandscapeOrientation
127+ primaryOrientationAngle: 0
128+ }
113129 }
114130 ]
115131
@@ -250,7 +266,7 @@ Rectangle {
250266 anchors { left: parent.left; right: parent.right }
251267 activeFocusOnPress: false
252268 text: "Device Name"
253− model: ["mako", "manta", "flo"]
269+ model: ["mako", "manta", "flo", "desktop"]
254270 onSelectedIndexChanged: {
255271 testCase.tearDown();
256272 applicationArguments.deviceName = model[selectedIndex];
@@ -264,6 +280,10 @@ Rectangle {
264280 text: "Usage Mode"
265281 model: ["Staged", "Windowed", "Automatic"]
266282 }
283+ MouseTouchEmulationCheckbox {
284+ checked: true
285+ color: "white"
286+ }
267287 Button {
268288 text: "Switch fullscreen"
269289 activeFocusOnPress: false
@@ -306,12 +326,14 @@ Rectangle {
306326 Row {
307327 Button {
308328 text: "Add mouse"
329+ activeFocusOnPress: false
309330 onClicked: {
310331 UnityInputInfo.inputInfo.addMockMouse()
311332 }
312333 }
313334 Button {
314335 text: "Remove mouse"
336+ activeFocusOnPress: false
315337 onClicked: {
316338 UnityInputInfo.inputInfo.removeMockMouse()
317339 }
@@ -320,17 +342,37 @@ Rectangle {
320342 Row {
321343 Button {
322344 text: "Add kbd"
345+ activeFocusOnPress: false
323346 onClicked: {
324347 UnityInputInfo.inputInfo.addMockKeyboard()
325348 }
326349 }
327350 Button {
351+ activeFocusOnPress: false
328352 text: "Remove kbd"
329353 onClicked: {
330354 UnityInputInfo.inputInfo.removeMockKeyboard()
331355 }
332356 }
333357 }
358+
359+ // Simulates what happens when the shell is moved to an external monitor and back
360+ Button {
361+ id: moveToFromMonitorButton
362+ text: applicationArguments.deviceName === "desktop" ? "Move to " + prevDevName + " screen" : "Move to desktop screen"
363+ activeFocusOnPress: false
364+ property string prevDevName: "mako"
365+ onClicked: {
366+ usageModeSelector.selectedIndex = 2; // "Automatic"
367+
368+ if (applicationArguments.deviceName === "desktop") {
369+ applicationArguments.deviceName = prevDevName;
370+ } else {
371+ prevDevName = applicationArguments.deviceName;
372+ applicationArguments.deviceName = "desktop"
373+ }
374+ }
375+ }
334376 }
335377 }
336378
@@ -1050,6 +1092,18 @@ Rectangle {
10501092 compare(signalSpy.count, 0);
10511093 }
10521094
1095+ function test_moveToExternalMonitor() {
1096+ loadShell("flo");
1097+
1098+ compare(orientedShell.orientation, Qt.InvertedLandscapeOrientation);
1099+ compare(shell.transformRotationAngle, 90);
1100+
1101+ moveToFromMonitorButton.clicked();
1102+
1103+ tryCompare(orientedShell, "orientation", Qt.LandscapeOrientation);
1104+ tryCompare(shell, "transformRotationAngle" , 0);
1105+ }
1106+
10531107 // angle - rotation angle in degrees clockwise, relative to the primary orientation.
10541108 function rotateTo(angle) {
10551109 switch (angle) {
tests/qmltests/tst_Shell.qml−24
@@ -1240,30 +1240,6 @@ Rectangle {
12401240 tryCompare(launcherPanel, "x", -launcherPanel.width);
12411241 }
12421242
1243− function test_background_data() {
1244− return [
1245− {tag: "color", accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"), gsettings: "", output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
1246− {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
1247− {tag: "as-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: "", output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
1248− {tag: "gs-specified", accounts: "", gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
1249− {tag: "both-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
1250− {tag: "invalid-as", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
1251− {tag: "invalid-both", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"), output: "defaultBackground"},
1252− ]
1253− }
1254− function test_background(data) {
1255− loadShell("phone");
1256− swipeAwayGreeter();
1257− AccountsService.backgroundFile = data.accounts;
1258− GSettingsController.setPictureUri(data.gsettings);
1259−
1260− if (data.output === "defaultBackground") {
1261− tryCompare(shell, "background", shell.defaultBackground);
1262− } else {
1263− tryCompare(shell, "background", data.output);
1264− }
1265− }
1266−
12671243 function test_tabletLogin_data() {
12681244 return [
12691245 {tag: "auth error", user: "auth-error", loggedIn: false, password: ""},