on xenial help sign in
Have unity8 drawing its own cursor
436260bDaniel d'Andrada
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.2 REQUIRED)
57find_package(Qt5Concurrent 5.2 REQUIRED)
57find_package(Qt5Concurrent 5.2 REQUIRED)
58find_package(Qt5Sql 5.2 REQUIRED)
58find_package(Qt5Sql 5.2 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=8)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
61
61
62# Standard install paths
62# Standard install paths
63include(GNUInstallDirs)
63include(GNUInstallDirs)
debian/control+5−3
@@ -25,10 +25,11 @@ Build-Depends: cmake,
25 libpay2-dev,
25 libpay2-dev,
26 libpulse-dev,
26 libpulse-dev,
27 libqmenumodel-dev (>= 0.2.9),
27 libqmenumodel-dev (>= 0.2.9),
28+ libqt5svg5-dev,
28 libqt5xmlpatterns5-dev,
29 libqt5xmlpatterns5-dev,
29 libsystemsettings-dev,
30 libsystemsettings-dev,
30 libudev-dev,
31 libudev-dev,
31− libunity-api-dev (>= 7.100),
32+ libunity-api-dev (>= 7.101),
32 libusermetricsoutput1-dev,
33 libusermetricsoutput1-dev,
33 libxcb1-dev,
34 libxcb1-dev,
34 pkg-config,
35 pkg-config,
@@ -87,7 +88,8 @@ Description: Development tool for Unity scopes
87Package: unity8
88Package: unity8
88Architecture: any
89Architecture: any
89Provides: indicator-renderer,
90Provides: indicator-renderer,
91+Depends: dmz-cursor-theme,
90−Depends: gsettings-desktop-schemas,
92+ gsettings-desktop-schemas,
91 libcap2-bin,
93 libcap2-bin,
92 libglib2.0-bin,
94 libglib2.0-bin,
93 qmenumodel-qml (>= 0.2.9),
95 qmenumodel-qml (>= 0.2.9),
@@ -125,7 +127,7 @@ Depends: qml-module-qtquick-layouts,
125 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
127 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
126 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
128 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
127 ubuntu-thumbnailer-impl-0,
129 ubuntu-thumbnailer-impl-0,
128− unity-application-impl-8,
130+ unity-application-impl-9,
129 unity-notifications-impl-3,
131 unity-notifications-impl-3,
130 unity-plugin-scopes | unity-scopes-impl,
132 unity-plugin-scopes | unity-scopes-impl,
131 unity-scopes-impl-7,
133 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
60 packaging of this file. Please review the following information to
60 packaging of this file. Please review the following information to
61 ensure the GNU General Public License version 3.0 requirements will be
61 ensure the GNU General Public License version 3.0 requirements will be
62 met: http://www.gnu.org/copyleft/gpl.html.
62 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 @@
1usr/lib/*/libunity8-private.*
1usr/lib/*/libunity8-private.*
2usr/lib/*/unity8/libUbuntuGestures*
2usr/lib/*/unity8/libUbuntuGestures*
3usr/lib/*/unity8/qml/AccountsService
3usr/lib/*/unity8/qml/AccountsService
4+usr/lib/*/unity8/qml/Cursor
4usr/lib/*/unity8/qml/Dash
5usr/lib/*/unity8/qml/Dash
5usr/lib/*/unity8/qml/GlobalShortcut
6usr/lib/*/unity8/qml/GlobalShortcut
6usr/lib/*/unity8/qml/Greeter
7usr/lib/*/unity8/qml/Greeter
plugins/CMakeLists.txt+1
@@ -12,6 +12,7 @@ macro(add_unity8_plugin PLUGIN VERSION PATH)
12endmacro()
12endmacro()
13
13
14add_subdirectory(AccountsService)
14add_subdirectory(AccountsService)
15+add_subdirectory(Cursor)
15add_subdirectory(GlobalShortcut)
16add_subdirectory(GlobalShortcut)
16add_subdirectory(Greeter)
17add_subdirectory(Greeter)
17add_subdirectory(IntegratedLightDM)
18add_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 {
251
251
252 // event eater
252 // event eater
253 // Nothing should leak to items behind the greeter
253 // Nothing should leak to items behind the greeter
254− MouseArea { anchors.fill: parent }
254+ MouseArea { anchors.fill: parent; hoverEnabled: true }
255
255
256 Loader {
256 Loader {
257 id: loader
257 id: loader
qml/Launcher/Launcher.qml+1
@@ -188,6 +188,7 @@ Item {
188 bottom: parent.bottom
188 bottom: parent.bottom
189 }
189 }
190 enabled: root.shadeBackground && root.state == "visible"
190 enabled: root.shadeBackground && root.state == "visible"
191+ visible: enabled // otherwise it will get in the way of cursor selection for some reason
191 onPressed: {
192 onPressed: {
192 root.state = ""
193 root.state = ""
193 }
194 }
qml/Panel/Panel.qml+1
@@ -48,6 +48,7 @@ Item {
48 MouseArea {
48 MouseArea {
49 anchors.fill: parent
49 anchors.fill: parent
50 onClicked: if (indicators.fullyOpened) indicators.hide();
50 onClicked: if (indicators.fullyOpened) indicators.hide();
51+ hoverEnabled: true // should also eat hover events, otherwise they will pass through
51 }
52 }
52 }
53 }
53
54
qml/Shell.qml+14−7
@@ -41,6 +41,7 @@ import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
41import Unity.Session 0.1
42import Unity.DashCommunicator 0.1
42import Unity.DashCommunicator 0.1
43import Unity.Indicators 0.1 as Indicators
43import Unity.Indicators 0.1 as Indicators
44+import Cursor 1.0
44
45
45
46
46Item {
47Item {
@@ -63,7 +64,7 @@ Item {
63 function updateFocusedAppOrientationAnimated() {
64 function updateFocusedAppOrientationAnimated() {
64 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
65 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
65 }
66 }
66− property bool hasMouse
67+ property bool hasMouse: false
67
68
68 // to be read from outside
69 // to be read from outside
69 readonly property int mainAppWindowOrientationAngle:
70 readonly property int mainAppWindowOrientationAngle:
@@ -190,11 +191,6 @@ Item {
190 onScreenshotTriggered: screenGrabber.capture();
191 onScreenshotTriggered: screenGrabber.capture();
191 }
192 }
192
193
193− ScreenGrabber {
194− id: screenGrabber
195− z: dialogs.z + 10
196− }
197−
198 GlobalShortcut {
194 GlobalShortcut {
199 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
195 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
200 }
196 }
@@ -684,9 +680,20 @@ Item {
684 onShowHome: showHome()
680 onShowHome: showHome()
685 }
681 }
686
682
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+
687 Rectangle {
694 Rectangle {
688 id: shutdownFadeOutRectangle
695 id: shutdownFadeOutRectangle
689− z: screenGrabber.z + 10
696+ z: cursor.z + 1
690 enabled: false
697 enabled: false
691 visible: false
698 visible: false
692 color: "black"
699 color: "black"
qml/Stages/DecoratedWindow.qml+6−3
@@ -31,9 +31,10 @@ FocusScope {
31 property bool highlightShown: false
31 property bool highlightShown: false
32 property real shadowOpacity: 1
32 property real shadowOpacity: 1
33
33
34− signal close();
34+ signal close()
35− signal maximize();
35+ signal maximize()
36− signal minimize();
36+ signal minimize()
37+ signal decorationPressed()
37
38
38 BorderImage {
39 BorderImage {
39 anchors {
40 anchors {
@@ -61,6 +62,7 @@ FocusScope {
61
62
62 WindowDecoration {
63 WindowDecoration {
63 id: decoration
64 id: decoration
65+ target: root.parent
64 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
66 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
65 anchors { left: parent.left; top: parent.top; right: parent.right }
67 anchors { left: parent.left; top: parent.top; right: parent.right }
66 height: units.gu(3)
68 height: units.gu(3)
@@ -68,6 +70,7 @@ FocusScope {
68 onClose: root.close();
70 onClose: root.close();
69 onMaximize: root.maximize();
71 onMaximize: root.maximize();
70 onMinimize: root.minimize();
72 onMinimize: root.minimize();
73+ onPressed: root.decorationPressed();
71 visible: decorationShown
74 visible: decorationShown
72 }
75 }
73
76
qml/Stages/DesktopStage.qml+4−8
@@ -138,9 +138,6 @@ Rectangle {
138 height: units.gu(50)
138 height: units.gu(50)
139 focus: model.appId === priv.focusedAppId
139 focus: model.appId === priv.focusedAppId
140
140
141− readonly property int minWidth: units.gu(10)
142− readonly property int minHeight: units.gu(10)
143−
144 property bool maximized: false
141 property bool maximized: false
145 property bool minimized: false
142 property bool minimized: false
146
143
@@ -215,12 +212,11 @@ Rectangle {
215 when: index == spread.highlightedIndex && blurLayer.ready
212 when: index == spread.highlightedIndex && blurLayer.ready
216 }
213 }
217
214
218− WindowMoveResizeArea {
215+ WindowResizeArea {
219− id: windowMoveResizeArea
220 target: appDelegate
216 target: appDelegate
221− minWidth: appDelegate.minWidth
217+ minWidth: units.gu(10)
222− minHeight: appDelegate.minHeight
218+ minHeight: units.gu(10)
223− resizeHandleWidth: units.gu(2)
219+ borderThickness: units.gu(2)
224 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
220 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
225
221
226 onPressed: { ApplicationManager.focusApplication(model.appId) }
222 onPressed: { ApplicationManager.focusApplication(model.appId) }
qml/Stages/WindowDecoration.qml+32−4
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -12,25 +12,53 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17 */
15 */
18
16
19import QtQuick 2.3
17import QtQuick 2.3
18+import Unity.Application 0.1 // For Mir singleton
20import Ubuntu.Components 1.1
19import Ubuntu.Components 1.1
21import "../Components"
20import "../Components"
22
21
23−Item {
22+MouseArea {
24 id: root
23 id: root
25 clip: true
24 clip: true
26
25
26+ property Item target
27 property alias title: titleLabel.text
27 property alias title: titleLabel.text
28 property bool active: false
28 property bool active: false
29+ hoverEnabled: true
29
30
30 signal close()
31 signal close()
31 signal minimize()
32 signal minimize()
32 signal maximize()
33 signal maximize()
33
34
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+
34 Rectangle {
62 Rectangle {
35 anchors.fill: parent
63 anchors.fill: parent
36 anchors.bottomMargin: -radius
64 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)
29endmacro()
29endmacro()
30
30
31add_subdirectory(AccountsService)
31add_subdirectory(AccountsService)
32+add_subdirectory(Cursor)
32add_subdirectory(GSettings.1.0)
33add_subdirectory(GSettings.1.0)
33add_subdirectory(indicator-service)
34add_subdirectory(indicator-service)
34add_subdirectory(libusermetrics)
35add_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)
78add_unity8_qmltest(Stages SurfaceContainer)
78add_unity8_qmltest(Stages SurfaceContainer)
79add_unity8_qmltest(Stages SessionContainer)
79add_unity8_qmltest(Stages SessionContainer)
80add_unity8_qmltest(Stages TabletStage)
80add_unity8_qmltest(Stages TabletStage)
81−add_unity8_qmltest(Stages WindowMoveResizeArea)
81+add_unity8_qmltest(Stages WindowResizeArea)
82add_unity8_qmltest(Stages Splash)
82add_unity8_qmltest(Stages Splash)
83add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
83add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
84add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
84add_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 @@
17import QtQuick 2.1
17import QtQuick 2.1
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import QtTest 1.0
19import QtTest 1.0
20−import Unity.Test 0.1 as UT
20+import Unity.Test 0.1
21import ".."
21import ".."
22import "../../../qml/Stages"
22import "../../../qml/Stages"
23import Ubuntu.Components 0.1
23import Ubuntu.Components 0.1
@@ -36,8 +36,8 @@ Item {
36
36
37 Item {
37 Item {
38 id: fakeWindow
38 id: fakeWindow
39− property alias minWidth: moveResizeArea.minWidth
39+ property alias minWidth: windowResizeArea.minWidth
40− property alias minHeight: moveResizeArea.minHeight
40+ property alias minHeight: windowResizeArea.minHeight
41 x: units.gu(20)
41 x: units.gu(20)
42 y: units.gu(20)
42 y: units.gu(20)
43 height: units.gu(20)
43 height: units.gu(20)
@@ -47,23 +47,27 @@ Item {
47 onWindowHeightChanged: height = windowHeight
47 onWindowHeightChanged: height = windowHeight
48 onWindowWidthChanged: width = windowWidth
48 onWindowWidthChanged: width = windowWidth
49
49
50− WindowMoveResizeArea {
50+ WindowResizeArea {
51− id: moveResizeArea
51+ id: windowResizeArea
52 target: fakeWindow
52 target: fakeWindow
53− resizeHandleWidth: units.gu(0.5)
53+ borderThickness: units.gu(2)
54 minWidth: units.gu(15)
54 minWidth: units.gu(15)
55 minHeight: units.gu(10)
55 minHeight: units.gu(10)
56 windowId: "test-window-id"
56 windowId: "test-window-id"
57 }
57 }
58
58
59 Rectangle {
59 Rectangle {
60− anchors.fill: moveResizeArea
60+ anchors.fill: windowResizeArea
61 color: "red"
61 color: "red"
62 }
62 }
63
63
64 Rectangle {
64 Rectangle {
65 anchors.fill: fakeWindow
65 anchors.fill: fakeWindow
66 color: "blue"
66 color: "blue"
67+ MouseArea {
68+ anchors.fill: parent
69+ hoverEnabled: true
70+ }
67 }
71 }
68 }
72 }
69 }
73 }
@@ -73,8 +77,13 @@ Item {
73 sourceComponent: fakeWindowComponent
77 sourceComponent: fakeWindowComponent
74 }
78 }
75
79
80+ MouseTouchEmulationCheckbox {
81+ checked: false
82+ color: "black"
83+ }
84+
76− UT.UnityTestCase {
85+ UnityTestCase {
77− name: "WindowMoveResizeArea"
86+ name: "WindowResizeArea"
78 when: windowShown
87 when: windowShown
79
88
80 function init() {
89 function init() {
@@ -84,34 +93,6 @@ Item {
84 fakeWindow.height = units.gu(20)
93 fakeWindow.height = units.gu(20)
85 }
94 }
86
95
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−
115 function test_resizeWindowRightBottom_data() {
96 function test_resizeWindowRightBottom_data() {
116 return [
97 return [
117 { tag: "width", dx: units.gu(10), dy: 0 },
98 { tag: "width", dx: units.gu(10), dy: 0 },
@@ -169,16 +150,11 @@ Item {
169 function test_saveRestorePosition() {
150 function test_saveRestorePosition() {
170 var initialWindowX = fakeWindow.x;
151 var initialWindowX = fakeWindow.x;
171 var initialWindowY = fakeWindow.y;
152 var initialWindowY = fakeWindow.y;
172− var initialWindowWidth = fakeWindow.width;
173− var initialWindowHeight = fakeWindow.height;
174
153
175 var moveDelta = units.gu(5);
154 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
155
180− tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
156+ fakeWindow.x = initialWindowX + moveDelta
181− tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
157+ fakeWindow.y = initialWindowY + moveDelta
182
158
183 // This will destroy the window and recreate it
159 // This will destroy the window and recreate it
184 windowLoader.active = false;
160 windowLoader.active = false;
tests/qmltests/tst_OrientedShell.qml+4
@@ -264,6 +264,10 @@ Rectangle {
264 text: "Usage Mode"
264 text: "Usage Mode"
265 model: ["Staged", "Windowed", "Automatic"]
265 model: ["Staged", "Windowed", "Automatic"]
266 }
266 }
267+ MouseTouchEmulationCheckbox {
268+ checked: true
269+ color: "white"
270+ }
267 Button {
271 Button {
268 text: "Switch fullscreen"
272 text: "Switch fullscreen"
269 activeFocusOnPress: false
273 activeFocusOnPress: false