on xenial help sign in
Merge mousePointer
commit7d01e2f365d3733afc9a19bc29e09adac0f20a16 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateTue 29 Sep 2015, 19:08 (11 years ago) parentd1c74de436260b
34 files changed+1934−181
CMakeLists.txt+1−1 debian/control+5−3 debian/copyright+21 debian/unity8-private.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+12 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 qml/Greeter/Greeter.qml+1−1 qml/Launcher/Launcher.qml+1 qml/Panel/Panel.qml+1 qml/Shell.qml+14−7 qml/Stages/DecoratedWindow.qml+6−3 qml/Stages/DesktopStage.qml+4−8 qml/Stages/WindowDecoration.qml+32−4 qml/Stages/WindowMoveResizeArea.qml−109 qml/Stages/WindowResizeArea.qml+182 tests/mocks/CMakeLists.txt+1 tests/mocks/Cursor/CMakeLists.txt+1 tests/mocks/Cursor/Cursor.qml+20 tests/mocks/Cursor/qmldir+2 tests/qmltests/CMakeLists.txt+1−1 tests/qmltests/Stages/tst_WindowMoveResizeArea.qml → tests/qmltests/Stages/tst_WindowResizeArea.qml+20−44 tests/qmltests/tst_OrientedShell.qml+4
CMakeLists.txt+1−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)
debian/control+5−3
@@ -25,10 +25,11 @@ Build-Depends: cmake,
2525 libpay2-dev,
2626 libpulse-dev,
2727 libqmenumodel-dev (>= 0.2.9),
28+ libqt5svg5-dev,
2829 libqt5xmlpatterns5-dev,
2930 libsystemsettings-dev,
3031 libudev-dev,
31− libunity-api-dev (>= 7.100),
32+ libunity-api-dev (>= 7.101),
3233 libusermetricsoutput1-dev,
3334 libxcb1-dev,
3435 pkg-config,
@@ -87,7 +88,8 @@ Description: Development tool for Unity scopes
8788Package: unity8
8889Architecture: any
8990Provides: indicator-renderer,
90−Depends: gsettings-desktop-schemas,
91+Depends: dmz-cursor-theme,
92+ gsettings-desktop-schemas,
9193 libcap2-bin,
9294 libglib2.0-bin,
9395 qmenumodel-qml (>= 0.2.9),
@@ -125,7 +127,7 @@ Depends: qml-module-qtquick-layouts,
125127 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
126128 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
127129 ubuntu-thumbnailer-impl-0,
128− unity-application-impl-8,
130+ unity-application-impl-9,
129131 unity-notifications-impl-3,
130132 unity-plugin-scopes | unity-scopes-impl,
131133 unity-scopes-impl-7,
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
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(IntegratedLightDM)
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+12
@@ -0,0 +1,12 @@
1+import QtQuick 2.4
2+import Cursor 1.0 // For MousePointer
3+
4+MousePointer {
5+ id: mousePointer
6+
7+ Image {
8+ x: -mousePointer.hotspotX
9+ y: -mousePointer.hotspotY
10+ source: "image://cursor/" + mousePointer.themeName + "/" + mousePointer.cursorName
11+ }
12+}
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::MouseButton 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::MouseButton 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
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/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/Shell.qml+14−7
@@ -41,6 +41,7 @@ import Unity.Notifications 1.0 as NotificationBackend
4141import Unity.Session 0.1
4242import Unity.DashCommunicator 0.1
4343import Unity.Indicators 0.1 as Indicators
44+import Cursor 1.0
4445
4546
4647Item {
@@ -63,7 +64,7 @@ Item {
6364 function updateFocusedAppOrientationAnimated() {
6465 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
6566 }
66− property bool hasMouse
67+ property bool hasMouse: false
6768
6869 // to be read from outside
6970 readonly property int mainAppWindowOrientationAngle:
@@ -190,11 +191,6 @@ Item {
190191 onScreenshotTriggered: screenGrabber.capture();
191192 }
192193
193− ScreenGrabber {
194− id: screenGrabber
195− z: dialogs.z + 10
196− }
197−
198194 GlobalShortcut {
199195 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
200196 }
@@ -684,9 +680,20 @@ Item {
684680 onShowHome: showHome()
685681 }
686682
683+ ScreenGrabber {
684+ id: screenGrabber
685+ z: dialogs.z + 10
686+ }
687+
688+ Cursor {
689+ id: cursor
690+ visible: shell.hasMouse
691+ z: screenGrabber.z + 1
692+ }
693+
687694 Rectangle {
688695 id: shutdownFadeOutRectangle
689− z: screenGrabber.z + 10
696+ z: cursor.z + 1
690697 enabled: false
691698 visible: false
692699 color: "black"
qml/Stages/DecoratedWindow.qml+6−3
@@ -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,6 +62,7 @@ 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)
@@ -68,6 +70,7 @@ FocusScope {
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+4−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) }
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+ onMouseXChanged: {
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+}
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/qmltests/CMakeLists.txt+1−1
@@ -78,7 +78,7 @@ add_unity8_qmltest(Stages SpreadDelegate)
7878add_unity8_qmltest(Stages SurfaceContainer)
7979add_unity8_qmltest(Stages SessionContainer)
8080add_unity8_qmltest(Stages TabletStage)
81−add_unity8_qmltest(Stages WindowMoveResizeArea)
81+add_unity8_qmltest(Stages WindowResizeArea)
8282add_unity8_qmltest(Stages Splash)
8383add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
8484add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
tests/qmltests/Stages/tst_WindowMoveResizeArea.qml → tests/qmltests/Stages/tst_WindowResizeArea.qmlrenamed+20−44
@@ -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 },
@@ -169,16 +150,11 @@ Item {
169150 function test_saveRestorePosition() {
170151 var initialWindowX = fakeWindow.x;
171152 var initialWindowY = fakeWindow.y;
172− var initialWindowWidth = fakeWindow.width;
173− var initialWindowHeight = fakeWindow.height;
174153
175154 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)
179155
180− tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
181− tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
156+ fakeWindow.x = initialWindowX + moveDelta
157+ fakeWindow.y = initialWindowY + moveDelta
182158
183159 // This will destroy the window and recreate it
184160 windowLoader.active = false;
tests/qmltests/tst_OrientedShell.qml+4
@@ -264,6 +264,10 @@ Rectangle {
264264 text: "Usage Mode"
265265 model: ["Staged", "Windowed", "Automatic"]
266266 }
267+ MouseTouchEmulationCheckbox {
268+ checked: true
269+ color: "white"
270+ }
267271 Button {
268272 text: "Switch fullscreen"
269273 activeFocusOnPress: false