diff --git a/3rdparty/xcursor.c b/3rdparty/xcursor.c new file mode 100644 index 0000000000..08da1cb548 --- /dev/null +++ b/3rdparty/xcursor.c @@ -0,0 +1,982 @@ +/* + * Copyright © 2002 Keith Packard + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "xcursor.h" +#include +#include +#include +#include + +/* + * From libXcursor/include/X11/extensions/Xcursor.h + */ + +#define XcursorTrue 1 +#define XcursorFalse 0 + +/* + * Cursor files start with a header. The header + * contains a magic number, a version number and a + * table of contents which has type and offset information + * for the remaining tables in the file. + * + * File minor versions increment for compatible changes + * File major versions increment for incompatible changes (never, we hope) + * + * Chunks of the same type are always upward compatible. Incompatible + * changes are made with new chunk types; the old data can remain under + * the old type. Upward compatible changes can add header data as the + * header lengths are specified in the file. + * + * File: + * FileHeader + * LISTofChunk + * + * FileHeader: + * CARD32 magic magic number + * CARD32 header bytes in file header + * CARD32 version file version + * CARD32 ntoc number of toc entries + * LISTofFileToc toc table of contents + * + * FileToc: + * CARD32 type entry type + * CARD32 subtype entry subtype (size for images) + * CARD32 position absolute file position + */ + +#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */ + +/* + * Current Xcursor version number. Will be substituted by configure + * from the version in the libXcursor configure.ac file. + */ + +#define XCURSOR_LIB_MAJOR 1 +#define XCURSOR_LIB_MINOR 1 +#define XCURSOR_LIB_REVISION 13 +#define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \ + (XCURSOR_LIB_MINOR * 100) + \ + (XCURSOR_LIB_REVISION)) + +/* + * This version number is stored in cursor files; changes to the + * file format require updating this version number + */ +#define XCURSOR_FILE_MAJOR 1 +#define XCURSOR_FILE_MINOR 0 +#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR)) +#define XCURSOR_FILE_HEADER_LEN (4 * 4) +#define XCURSOR_FILE_TOC_LEN (3 * 4) + +typedef struct _XcursorFileToc { + XcursorUInt type; /* chunk type */ + XcursorUInt subtype; /* subtype (size for images) */ + XcursorUInt position; /* absolute position in file */ +} XcursorFileToc; + +typedef struct _XcursorFileHeader { + XcursorUInt magic; /* magic number */ + XcursorUInt header; /* byte length of header */ + XcursorUInt version; /* file version number */ + XcursorUInt ntoc; /* number of toc entries */ + XcursorFileToc *tocs; /* table of contents */ +} XcursorFileHeader; + +/* + * The rest of the file is a list of chunks, each tagged by type + * and version. + * + * Chunk: + * ChunkHeader + * + * + * + * ChunkHeader: + * CARD32 header bytes in chunk header + type header + * CARD32 type chunk type + * CARD32 subtype chunk subtype + * CARD32 version chunk type version + */ + +#define XCURSOR_CHUNK_HEADER_LEN (4 * 4) + +typedef struct _XcursorChunkHeader { + XcursorUInt header; /* bytes in chunk header */ + XcursorUInt type; /* chunk type */ + XcursorUInt subtype; /* chunk subtype (size for images) */ + XcursorUInt version; /* version of this type */ +} XcursorChunkHeader; + +/* + * Here's a list of the known chunk types + */ + +/* + * Comments consist of a 4-byte length field followed by + * UTF-8 encoded text + * + * Comment: + * ChunkHeader header chunk header + * CARD32 length bytes in text + * LISTofCARD8 text UTF-8 encoded text + */ + +#define XCURSOR_COMMENT_TYPE 0xfffe0001 +#define XCURSOR_COMMENT_VERSION 1 +#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4)) +#define XCURSOR_COMMENT_COPYRIGHT 1 +#define XCURSOR_COMMENT_LICENSE 2 +#define XCURSOR_COMMENT_OTHER 3 +#define XCURSOR_COMMENT_MAX_LEN 0x100000 + +typedef struct _XcursorComment { + XcursorUInt version; + XcursorUInt comment_type; + char *comment; +} XcursorComment; + +/* + * Each cursor image occupies a separate image chunk. + * The length of the image header follows the chunk header + * so that future versions can extend the header without + * breaking older applications + * + * Image: + * ChunkHeader header chunk header + * CARD32 width actual width + * CARD32 height actual height + * CARD32 xhot hot spot x + * CARD32 yhot hot spot y + * CARD32 delay animation delay + * LISTofCARD32 pixels ARGB pixels + */ + +#define XCURSOR_IMAGE_TYPE 0xfffd0002 +#define XCURSOR_IMAGE_VERSION 1 +#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4)) +#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */ + +typedef struct _XcursorFile XcursorFile; + +struct _XcursorFile { + void *closure; + int (*read) (XcursorFile *file, unsigned char *buf, int len); + int (*write) (XcursorFile *file, unsigned char *buf, int len); + int (*seek) (XcursorFile *file, long offset, int whence); +}; + +typedef struct _XcursorComments { + int ncomment; /* number of comments */ + XcursorComment **comments; /* array of XcursorComment pointers */ +} XcursorComments; + +/* + * From libXcursor/src/file.c + */ + +static XcursorImage * +XcursorImageCreate (int width, int height) +{ + XcursorImage *image; + + if (width < 0 || height < 0) + return NULL; + if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + + image = malloc (sizeof (XcursorImage) + + width * height * sizeof (XcursorPixel)); + if (!image) + return NULL; + image->version = XCURSOR_IMAGE_VERSION; + image->pixels = (XcursorPixel *) (image + 1); + image->size = width > height ? width : height; + image->width = width; + image->height = height; + image->delay = 0; + return image; +} + +static void +XcursorImageDestroy (XcursorImage *image) +{ + free (image); +} + +static XcursorImages * +XcursorImagesCreate (int size) +{ + XcursorImages *images; + + images = malloc (sizeof (XcursorImages) + + size * sizeof (XcursorImage *)); + if (!images) + return NULL; + images->nimage = 0; + images->images = (XcursorImage **) (images + 1); + images->name = NULL; + return images; +} + +void +XcursorImagesDestroy (XcursorImages *images) +{ + int n; + + if (!images) + return; + + for (n = 0; n < images->nimage; n++) + XcursorImageDestroy (images->images[n]); + if (images->name) + free (images->name); + free (images); +} + +static void +XcursorImagesSetName (XcursorImages *images, const char *name) +{ + char *new; + + if (!images || !name) + return; + + new = malloc (strlen (name) + 1); + + if (!new) + return; + + strcpy (new, name); + if (images->name) + free (images->name); + images->name = new; +} + +static XcursorBool +_XcursorReadUInt (XcursorFile *file, XcursorUInt *u) +{ + unsigned char bytes[4]; + + if (!file || !u) + return XcursorFalse; + + if ((*file->read) (file, bytes, 4) != 4) + return XcursorFalse; + *u = ((bytes[0] << 0) | + (bytes[1] << 8) | + (bytes[2] << 16) | + (bytes[3] << 24)); + return XcursorTrue; +} + +static void +_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader) +{ + free (fileHeader); +} + +static XcursorFileHeader * +_XcursorFileHeaderCreate (int ntoc) +{ + XcursorFileHeader *fileHeader; + + if (ntoc > 0x10000) + return NULL; + fileHeader = malloc (sizeof (XcursorFileHeader) + + ntoc * sizeof (XcursorFileToc)); + if (!fileHeader) + return NULL; + fileHeader->magic = XCURSOR_MAGIC; + fileHeader->header = XCURSOR_FILE_HEADER_LEN; + fileHeader->version = XCURSOR_FILE_VERSION; + fileHeader->ntoc = ntoc; + fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1); + return fileHeader; +} + +static XcursorFileHeader * +_XcursorReadFileHeader (XcursorFile *file) +{ + XcursorFileHeader head, *fileHeader; + XcursorUInt skip; + unsigned int n; + + if (!file) + return NULL; + + if (!_XcursorReadUInt (file, &head.magic)) + return NULL; + if (head.magic != XCURSOR_MAGIC) + return NULL; + if (!_XcursorReadUInt (file, &head.header)) + return NULL; + if (!_XcursorReadUInt (file, &head.version)) + return NULL; + if (!_XcursorReadUInt (file, &head.ntoc)) + return NULL; + skip = head.header - XCURSOR_FILE_HEADER_LEN; + if (skip) + if ((*file->seek) (file, skip, SEEK_CUR) == EOF) + return NULL; + fileHeader = _XcursorFileHeaderCreate (head.ntoc); + if (!fileHeader) + return NULL; + fileHeader->magic = head.magic; + fileHeader->header = head.header; + fileHeader->version = head.version; + fileHeader->ntoc = head.ntoc; + for (n = 0; n < fileHeader->ntoc; n++) + { + if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type)) + break; + if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype)) + break; + if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position)) + break; + } + if (n != fileHeader->ntoc) + { + _XcursorFileHeaderDestroy (fileHeader); + return NULL; + } + return fileHeader; +} + +static XcursorBool +_XcursorSeekToToc (XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc) +{ + if (!file || !fileHeader || \ + (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF) + return XcursorFalse; + return XcursorTrue; +} + +static XcursorBool +_XcursorFileReadChunkHeader (XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc, + XcursorChunkHeader *chunkHeader) +{ + if (!file || !fileHeader || !chunkHeader) + return XcursorFalse; + if (!_XcursorSeekToToc (file, fileHeader, toc)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->header)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->type)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->subtype)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->version)) + return XcursorFalse; + /* sanity check */ + if (chunkHeader->type != fileHeader->tocs[toc].type || + chunkHeader->subtype != fileHeader->tocs[toc].subtype) + return XcursorFalse; + return XcursorTrue; +} + +#define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a)) + +static XcursorDim +_XcursorFindBestSize (XcursorFileHeader *fileHeader, + XcursorDim size, + int *nsizesp) +{ + unsigned int n; + int nsizes = 0; + XcursorDim bestSize = 0; + XcursorDim thisSize; + + if (!fileHeader || !nsizesp) + return 0; + + for (n = 0; n < fileHeader->ntoc; n++) + { + if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE) + continue; + thisSize = fileHeader->tocs[n].subtype; + if (!bestSize || dist (thisSize, size) < dist (bestSize, size)) + { + bestSize = thisSize; + nsizes = 1; + } + else if (thisSize == bestSize) + nsizes++; + } + *nsizesp = nsizes; + return bestSize; +} + +static int +_XcursorFindImageToc (XcursorFileHeader *fileHeader, + XcursorDim size, + int count) +{ + unsigned int toc; + XcursorDim thisSize; + + if (!fileHeader) + return 0; + + for (toc = 0; toc < fileHeader->ntoc; toc++) + { + if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE) + continue; + thisSize = fileHeader->tocs[toc].subtype; + if (thisSize != size) + continue; + if (!count) + break; + count--; + } + if (toc == fileHeader->ntoc) + return -1; + return toc; +} + +static XcursorImage * +_XcursorReadImage (XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc) +{ + XcursorChunkHeader chunkHeader; + XcursorImage head; + XcursorImage *image; + int n; + XcursorPixel *p; + + if (!file || !fileHeader) + return NULL; + + if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader)) + return NULL; + if (!_XcursorReadUInt (file, &head.width)) + return NULL; + if (!_XcursorReadUInt (file, &head.height)) + return NULL; + if (!_XcursorReadUInt (file, &head.xhot)) + return NULL; + if (!_XcursorReadUInt (file, &head.yhot)) + return NULL; + if (!_XcursorReadUInt (file, &head.delay)) + return NULL; + /* sanity check data */ + if (head.width > XCURSOR_IMAGE_MAX_SIZE || + head.height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + if (head.width == 0 || head.height == 0) + return NULL; + if (head.xhot > head.width || head.yhot > head.height) + return NULL; + + /* Create the image and initialize it */ + image = XcursorImageCreate (head.width, head.height); + if (image == NULL) + return NULL; + if (chunkHeader.version < image->version) + image->version = chunkHeader.version; + image->size = chunkHeader.subtype; + image->xhot = head.xhot; + image->yhot = head.yhot; + image->delay = head.delay; + n = image->width * image->height; + p = image->pixels; + while (n--) + { + if (!_XcursorReadUInt (file, p)) + { + XcursorImageDestroy (image); + return NULL; + } + p++; + } + return image; +} + +static XcursorImages * +XcursorXcFileLoadImages (XcursorFile *file, int size) +{ + XcursorFileHeader *fileHeader; + XcursorDim bestSize; + int nsize; + XcursorImages *images; + int n; + int toc; + + if (!file || size < 0) + return NULL; + fileHeader = _XcursorReadFileHeader (file); + if (!fileHeader) + return NULL; + bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize); + if (!bestSize) + { + _XcursorFileHeaderDestroy (fileHeader); + return NULL; + } + images = XcursorImagesCreate (nsize); + if (!images) + { + _XcursorFileHeaderDestroy (fileHeader); + return NULL; + } + for (n = 0; n < nsize; n++) + { + toc = _XcursorFindImageToc (fileHeader, bestSize, n); + if (toc < 0) + break; + images->images[images->nimage] = _XcursorReadImage (file, fileHeader, + toc); + if (!images->images[images->nimage]) + break; + images->nimage++; + } + _XcursorFileHeaderDestroy (fileHeader); + if (images->nimage != nsize) + { + XcursorImagesDestroy (images); + images = NULL; + } + return images; +} + +static int +_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return fread (buf, 1, len, f); +} + +static int +_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return fwrite (buf, 1, len, f); +} + +static int +_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence) +{ + FILE *f = file->closure; + return fseek (f, offset, whence); +} + +static void +_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file) +{ + file->closure = stdfile; + file->read = _XcursorStdioFileRead; + file->write = _XcursorStdioFileWrite; + file->seek = _XcursorStdioFileSeek; +} + +static XcursorImages * +XcursorFileLoadImages (FILE *file, int size) +{ + XcursorFile f; + + if (!file) + return NULL; + + _XcursorStdioFileInitialize (file, &f); + return XcursorXcFileLoadImages (&f, size); +} + +/* + * From libXcursor/src/library.c + */ + +#ifndef ICONDIR +#define ICONDIR "/usr/X11R6/lib/X11/icons" +#endif + +#ifndef XCURSORPATH +#define XCURSORPATH "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"ICONDIR +#endif + +static const char * +XcursorLibraryPath (void) +{ + static const char *path; + + if (!path) + { + path = getenv ("XCURSOR_PATH"); + if (!path) + path = XCURSORPATH; + } + return path; +} + +static void +_XcursorAddPathElt (char *path, const char *elt, int len) +{ + int pathlen = strlen (path); + + /* append / if the path doesn't currently have one */ + if (path[0] == '\0' || path[pathlen - 1] != '/') + { + strcat (path, "/"); + pathlen++; + } + if (len == -1) + len = strlen (elt); + /* strip leading slashes */ + while (len && elt[0] == '/') + { + elt++; + len--; + } + strncpy (path + pathlen, elt, len); + path[pathlen + len] = '\0'; +} + +static char * +_XcursorBuildThemeDir (const char *dir, const char *theme) +{ + const char *colon; + const char *tcolon; + char *full; + char *home; + int dirlen; + int homelen; + int themelen; + int len; + + if (!dir || !theme) + return NULL; + + colon = strchr (dir, ':'); + if (!colon) + colon = dir + strlen (dir); + + dirlen = colon - dir; + + tcolon = strchr (theme, ':'); + if (!tcolon) + tcolon = theme + strlen (theme); + + themelen = tcolon - theme; + + home = NULL; + homelen = 0; + if (*dir == '~') + { + home = getenv ("HOME"); + if (!home) + return NULL; + homelen = strlen (home); + dir++; + dirlen--; + } + + /* + * add space for any needed directory separators, one per component, + * and one for the trailing null + */ + len = 1 + homelen + 1 + dirlen + 1 + themelen + 1; + + full = malloc (len); + if (!full) + return NULL; + full[0] = '\0'; + + if (home) + _XcursorAddPathElt (full, home, -1); + _XcursorAddPathElt (full, dir, dirlen); + _XcursorAddPathElt (full, theme, themelen); + return full; +} + +static char * +_XcursorBuildFullname (const char *dir, const char *subdir, const char *file) +{ + char *full; + + if (!dir || !subdir || !file) + return NULL; + + full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1); + if (!full) + return NULL; + full[0] = '\0'; + _XcursorAddPathElt (full, dir, -1); + _XcursorAddPathElt (full, subdir, -1); + _XcursorAddPathElt (full, file, -1); + return full; +} + +static const char * +_XcursorNextPath (const char *path) +{ + char *colon = strchr (path, ':'); + + if (!colon) + return NULL; + return colon + 1; +} + +#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') +#define XcursorSep(c) ((c) == ';' || (c) == ',') + +static char * +_XcursorThemeInherits (const char *full) +{ + char line[8192]; + char *result = NULL; + FILE *f; + + if (!full) + return NULL; + + f = fopen (full, "r"); + if (f) + { + while (fgets (line, sizeof (line), f)) + { + if (!strncmp (line, "Inherits", 8)) + { + char *l = line + 8; + char *r; + while (*l == ' ') l++; + if (*l != '=') continue; + l++; + while (*l == ' ') l++; + result = malloc (strlen (l) + 1); + if (result) + { + r = result; + while (*l) + { + while (XcursorSep(*l) || XcursorWhite (*l)) l++; + if (!*l) + break; + if (r != result) + *r++ = ':'; + while (*l && !XcursorWhite(*l) && + !XcursorSep(*l)) + *r++ = *l++; + } + *r++ = '\0'; + } + break; + } + } + fclose (f); + } + return result; +} + +static FILE * +XcursorScanTheme (const char *theme, const char *name) +{ + FILE *f = NULL; + char *full; + char *dir; + const char *path; + char *inherits = NULL; + const char *i; + + if (!theme || !name) + return NULL; + + /* + * Scan this theme + */ + for (path = XcursorLibraryPath (); + path && f == NULL; + path = _XcursorNextPath (path)) + { + dir = _XcursorBuildThemeDir (path, theme); + if (dir) + { + full = _XcursorBuildFullname (dir, "cursors", name); + if (full) + { + f = fopen (full, "r"); + free (full); + } + if (!f && !inherits) + { + full = _XcursorBuildFullname (dir, "", "index.theme"); + if (full) + { + inherits = _XcursorThemeInherits (full); + free (full); + } + } + free (dir); + } + } + /* + * Recurse to scan inherited themes + */ + for (i = inherits; i && f == NULL; i = _XcursorNextPath (i)) + { + if (strcmp(i, theme) != 0) + f = XcursorScanTheme (i, name); + else + printf("Not calling XcursorScanTheme because of circular dependency: %s. %s", i, name); + } + if (inherits != NULL) + free (inherits); + return f; +} + +XcursorImages * +XcursorLibraryLoadImages (const char *file, const char *theme, int size) +{ + FILE *f = NULL; + XcursorImages *images = NULL; + + if (!file) + return NULL; + + if (theme) + f = XcursorScanTheme (theme, file); + if (!f) + f = XcursorScanTheme ("default", file); + if (f) + { + images = XcursorFileLoadImages (f, size); + if (images) + XcursorImagesSetName (images, file); + fclose (f); + } + return images; +} + +static void +load_all_cursors_from_dir(const char *path, int size, + void (*load_callback)(XcursorImages *, void *), + void *user_data) +{ + FILE *f; + DIR *dir = opendir(path); + struct dirent *ent; + char *full; + XcursorImages *images; + + if (!dir) + return; + + for(ent = readdir(dir); ent; ent = readdir(dir)) { +#ifdef _DIRENT_HAVE_D_TYPE + if (ent->d_type != DT_UNKNOWN && + (ent->d_type != DT_REG && ent->d_type != DT_LNK)) + continue; +#endif + + full = _XcursorBuildFullname(path, "", ent->d_name); + if (!full) + continue; + + f = fopen(full, "r"); + if (!f) { + free(full); + continue; + } + + images = XcursorFileLoadImages(f, size); + + if (images) { + XcursorImagesSetName(images, ent->d_name); + load_callback(images, user_data); + } + + fclose (f); + free(full); + } + + closedir(dir); +} + +/** Load all the cursor of a theme + * + * This function loads all the cursor images of a given theme and its + * inherited themes. Each cursor is loaded into an XcursorImages object + * which is passed to the caller's load callback. If a cursor appears + * more than once across all the inherited themes, the load callback + * will be called multiple times, with possibly different XcursorImages + * object which have the same name. The user is expected to destroy the + * XcursorImages objects passed to the callback with + * XcursorImagesDestroy(). + * + * \param theme The name of theme that should be loaded + * \param size The desired size of the cursor images + * \param load_callback A callback function that will be called + * for each cursor loaded. The first parameter is the XcursorImages + * object representing the loaded cursor and the second is a pointer + * to data provided by the user. + * \param user_data The data that should be passed to the load callback + */ +void +xcursor_load_theme(const char *theme, int size, + void (*load_callback)(XcursorImages *, void *), + void *user_data) +{ + char *full, *dir; + char *inherits = NULL; + const char *path, *i; + + if (!theme) + theme = "default"; + + for (path = XcursorLibraryPath(); + path; + path = _XcursorNextPath(path)) { + dir = _XcursorBuildThemeDir(path, theme); + if (!dir) + continue; + + full = _XcursorBuildFullname(dir, "cursors", ""); + + if (full) { + load_all_cursors_from_dir(full, size, load_callback, + user_data); + free(full); + } + + if (!inherits) { + full = _XcursorBuildFullname(dir, "", "index.theme"); + if (full) { + inherits = _XcursorThemeInherits(full); + free(full); + } + } + + free(dir); + } + + for (i = inherits; i; i = _XcursorNextPath(i)) + xcursor_load_theme(i, size, load_callback, user_data); + + if (inherits) + free(inherits); +} diff --git a/3rdparty/xcursor.h b/3rdparty/xcursor.h new file mode 100644 index 0000000000..188c3fa76a --- /dev/null +++ b/3rdparty/xcursor.h @@ -0,0 +1,74 @@ +/* + * Copyright © 2002 Keith Packard + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef XCURSOR_H +#define XCURSOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int XcursorBool; +typedef unsigned int XcursorUInt; + +typedef XcursorUInt XcursorDim; +typedef XcursorUInt XcursorPixel; + +typedef struct _XcursorImage { + XcursorUInt version; /* version of the image data */ + XcursorDim size; /* nominal size for matching */ + XcursorDim width; /* actual width */ + XcursorDim height; /* actual height */ + XcursorDim xhot; /* hot spot x (must be inside image) */ + XcursorDim yhot; /* hot spot y (must be inside image) */ + XcursorUInt delay; /* animation delay to next frame (ms) */ + XcursorPixel *pixels; /* pointer to pixels */ +} XcursorImage; + +/* + * Other data structures exposed by the library API + */ +typedef struct _XcursorImages { + int nimage; /* number of images */ + XcursorImage **images; /* array of XcursorImage pointers */ + char *name; /* name used to load images */ +} XcursorImages; + +XcursorImages * +XcursorLibraryLoadImages (const char *file, const char *theme, int size); + +void +XcursorImagesDestroy (XcursorImages *images); + +void +xcursor_load_theme(const char *theme, int size, + void (*load_callback)(XcursorImages *, void *), + void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index d78de72350..f77ec8f8f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -424,6 +424,7 @@ add_subdirectory(helpers) ########### next target ############### set(kwin_SRCS + 3rdparty/xcursor.c abstract_client.cpp abstract_opengl_context_attribute_builder.cpp abstract_output.cpp @@ -490,8 +491,8 @@ set(kwin_SRCS pointer_input.cpp popup_input_filter.cpp rootinfo_filter.cpp - rules.cpp rulebooksettings.cpp + rules.cpp scene.cpp screenedge.cpp screenlockerwatcher.cpp @@ -524,7 +525,6 @@ set(kwin_SRCS virtualkeyboard.cpp virtualkeyboard_dbus.cpp was_user_interaction_x11_filter.cpp - wayland_cursor_theme.cpp wayland_server.cpp waylandclient.cpp waylandshellintegration.cpp @@ -534,6 +534,7 @@ set(kwin_SRCS x11client.cpp x11eventfilter.cpp xcbutils.cpp + xcursortheme.cpp xdgshellclient.cpp xkb.cpp xwaylandclient.cpp diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index fe7fd27c9f..e9999c387d 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -27,9 +27,9 @@ along with this program. If not, see . #include "options.h" #include "screenedge.h" #include "screens.h" -#include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" +#include "xcursortheme.h" #include #include @@ -52,46 +52,53 @@ along with this program. If not, see . namespace KWin { -template -PlatformCursorImage loadReferenceThemeCursor(const T &shape) +static PlatformCursorImage loadReferenceThemeCursor_helper(const KXcursorTheme &theme, + const QByteArray &name) { - if (!waylandServer()->internalShmPool()) { + const QVector sprites = theme.shape(name); + if (sprites.isEmpty()) { return PlatformCursorImage(); } - QScopedPointer cursorTheme; - cursorTheme.reset(new WaylandCursorTheme(waylandServer()->internalShmPool())); + QImage cursorImage = sprites.first().data(); + cursorImage.setDevicePixelRatio(theme.devicePixelRatio()); - wl_cursor_image *cursor = cursorTheme->get(shape); - if (!cursor) { + QPoint cursorHotspot = sprites.first().hotspot(); + cursorHotspot /= theme.devicePixelRatio(); + + return PlatformCursorImage(cursorImage, cursorHotspot); +} + +static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name) +{ + const Cursor *pointerCursor = Cursors::self()->mouse(); + + const KXcursorTheme theme = KXcursorTheme::fromTheme(pointerCursor->themeName(), + pointerCursor->themeSize(), + screens()->maxScale()); + if (theme.isEmpty()) { return PlatformCursorImage(); } - wl_buffer *b = wl_cursor_image_get_buffer(cursor); - if (!b) { - return PlatformCursorImage(); + PlatformCursorImage platformCursorImage = loadReferenceThemeCursor_helper(theme, name); + if (!platformCursorImage.isNull()) { + return platformCursorImage; } - waylandServer()->internalClientConection()->flush(); - waylandServer()->dispatch(); - - auto buffer = KWaylandServer::BufferInterface::get( - waylandServer()->internalConnection()->getResource( - KWayland::Client::Buffer::getId(b))); - if (!buffer) { - return PlatformCursorImage{}; + const QVector alternativeNames = Cursor::cursorAlternativeNames(name); + for (const QByteArray &alternativeName : alternativeNames) { + platformCursorImage = loadReferenceThemeCursor_helper(theme, alternativeName); + if (!platformCursorImage.isNull()) { + break; + } } - const qreal scale = screens()->maxScale(); - QImage image = buffer->data().copy(); - image.setDevicePixelRatio(scale); + return platformCursorImage; +} - const QPoint hotSpot( - qRound(cursor->hotspot_x / scale), - qRound(cursor->hotspot_y / scale) - ); - - return PlatformCursorImage(image, hotSpot); +static PlatformCursorImage loadReferenceThemeCursor(const CursorShape &shape) +{ + return loadReferenceThemeCursor(shape.name()); } static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0"); @@ -1526,7 +1533,7 @@ void PointerInputTest::testResizeCursor() Cursors::self()->mouse()->setPos(cursorPos); const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor); - QVERIFY(!arrowCursor.image().isNull()); + QVERIFY(!arrowCursor.isNull()); QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); @@ -1538,7 +1545,7 @@ void PointerInputTest::testResizeCursor() QFETCH(KWin::CursorShape, cursorShape); const PlatformCursorImage resizeCursor = loadReferenceThemeCursor(cursorShape); - QVERIFY(!resizeCursor.image().isNull()); + QVERIFY(!resizeCursor.isNull()); QCOMPARE(kwinApp()->platform()->cursorImage().image(), resizeCursor.image()); QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), resizeCursor.hotSpot()); @@ -1577,7 +1584,7 @@ void PointerInputTest::testMoveCursor() Cursors::self()->mouse()->setPos(c->frameGeometry().center()); const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor); - QVERIFY(!arrowCursor.image().isNull()); + QVERIFY(!arrowCursor.isNull()); QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); @@ -1588,7 +1595,7 @@ void PointerInputTest::testMoveCursor() QVERIFY(c->isMove()); const PlatformCursorImage sizeAllCursor = loadReferenceThemeCursor(Qt::SizeAllCursor); - QVERIFY(!sizeAllCursor.image().isNull()); + QVERIFY(!sizeAllCursor.isNull()); QCOMPARE(kwinApp()->platform()->cursorImage().image(), sizeAllCursor.image()); QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), sizeAllCursor.hotSpot()); diff --git a/cursor.cpp b/cursor.cpp index d0fe596e3d..e6e01792e6 100644 --- a/cursor.cpp +++ b/cursor.cpp @@ -98,8 +98,8 @@ Cursor::Cursor(QObject *parent) : QObject(parent) , m_mousePollingCounter(0) , m_cursorTrackingCounter(0) - , m_themeName("default") - , m_themeSize(24) + , m_themeName(defaultThemeName()) + , m_themeSize(defaultThemeSize()) { loadThemeSettings(); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), @@ -128,8 +128,8 @@ void Cursor::loadThemeSettings() void Cursor::loadThemeFromKConfig() { KConfigGroup mousecfg(kwinApp()->inputConfig(), "Mouse"); - const QString themeName = mousecfg.readEntry("cursorTheme", "default"); - const uint themeSize = mousecfg.readEntry("cursorSize", 24); + const QString themeName = mousecfg.readEntry("cursorTheme", defaultThemeName()); + const uint themeSize = mousecfg.readEntry("cursorSize", defaultThemeSize()); updateTheme(themeName, themeSize); } @@ -277,7 +277,7 @@ void Cursor::doStopCursorTracking() { } -QVector Cursor::cursorAlternativeNames(const QByteArray &name) const +QVector Cursor::cursorAlternativeNames(const QByteArray &name) { static const QHash> alternatives = { {QByteArrayLiteral("left_ptr"), {QByteArrayLiteral("arrow"), @@ -409,6 +409,16 @@ QVector Cursor::cursorAlternativeNames(const QByteArray &name) const return QVector(); } +QString Cursor::defaultThemeName() +{ + return QStringLiteral("default"); +} + +int Cursor::defaultThemeSize() +{ + return 24; +} + QByteArray CursorShape::name() const { switch (m_shape) { diff --git a/cursor.h b/cursor.h index 40138cac23..b140a24323 100644 --- a/cursor.h +++ b/cursor.h @@ -139,7 +139,15 @@ public: /** * @return list of alternative names for the cursor with @p name */ - QVector cursorAlternativeNames(const QByteArray &name) const; + static QVector cursorAlternativeNames(const QByteArray &name); + /** + * Returns the default Xcursor theme name. + */ + static QString defaultThemeName(); + /** + * Returns the default Xcursor theme size. + */ + static int defaultThemeSize(); /** * Returns the current cursor position. This method does an update of the mouse position if diff --git a/libkwineffects/kwinglobals.h b/libkwineffects/kwinglobals.h index be5e39a1d8..8bfcfe8ce8 100644 --- a/libkwineffects/kwinglobals.h +++ b/libkwineffects/kwinglobals.h @@ -226,6 +226,9 @@ public: } virtual ~PlatformCursorImage() = default; + bool isNull() const { + return m_image.isNull(); + } QImage image() const { return m_image; } diff --git a/platform.h b/platform.h index faf4f83156..4e12ca7744 100644 --- a/platform.h +++ b/platform.h @@ -55,7 +55,6 @@ class Scene; class Screens; class ScreenEdges; class Toplevel; -class WaylandCursorTheme; namespace Decoration { diff --git a/plugins/platforms/wayland/wayland_backend.cpp b/plugins/platforms/wayland/wayland_backend.cpp index 233943a3d2..d16b1afeb7 100644 --- a/plugins/platforms/wayland/wayland_backend.cpp +++ b/plugins/platforms/wayland/wayland_backend.cpp @@ -34,7 +34,6 @@ along with this program. If not, see . #include "outputscreens.h" #include "pointer_input.h" #include "screens.h" -#include "wayland_cursor_theme.h" #include "wayland_server.h" #include diff --git a/plugins/platforms/wayland/wayland_backend.h b/plugins/platforms/wayland/wayland_backend.h index 5495b5b8b0..e9cda4b139 100644 --- a/plugins/platforms/wayland/wayland_backend.h +++ b/plugins/platforms/wayland/wayland_backend.h @@ -66,8 +66,6 @@ class XdgShell; namespace KWin { -class WaylandCursorTheme; - namespace Wayland { diff --git a/pointer_input.cpp b/pointer_input.cpp index 3a9f0a5bf9..e2eab9c731 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -27,15 +27,12 @@ along with this program. If not, see . #include "input_event_spy.h" #include "osd.h" #include "screens.h" -#include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" // KDecoration #include // KWayland -#include -#include #include #include #include @@ -1119,23 +1116,6 @@ void CursorImage::updateServerCursor() } } -void WaylandCursorImage::loadTheme() -{ - if (m_cursorTheme) { - return; - } - // check whether we can create it - if (waylandServer()->internalShmPool()) { - m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this); - connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, - [this] { - delete m_cursorTheme; - m_cursorTheme = nullptr; - } - ); - } -} - void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { loadThemeCursor(shape, &m_effectsCursor); @@ -1268,36 +1248,83 @@ void CursorImage::loadThemeCursor(const QByteArray &shape, WaylandCursorImage::I m_waylandImage.loadThemeCursor(shape, image); } -template -void WaylandCursorImage::loadThemeCursor(const T &shape, Image *image) +WaylandCursorImage::WaylandCursorImage(QObject *parent) + : QObject(parent) { - loadTheme(); - if (!m_cursorTheme) { + Cursor *pointerCursor = Cursors::self()->mouse(); + + connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::invalidateCursorTheme); + connect(screens(), &Screens::maxScaleChanged, this, &WaylandCursorImage::invalidateCursorTheme); +} + +bool WaylandCursorImage::ensureCursorTheme() +{ + if (!m_cursorTheme.isEmpty()) { + return true; + } + + const Cursor *pointerCursor = Cursors::self()->mouse(); + const qreal targetDevicePixelRatio = screens()->maxScale(); + + m_cursorTheme = KXcursorTheme::fromTheme(pointerCursor->themeName(), pointerCursor->themeSize(), + targetDevicePixelRatio); + if (!m_cursorTheme.isEmpty()) { + return true; + } + + m_cursorTheme = KXcursorTheme::fromTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), + targetDevicePixelRatio); + if (!m_cursorTheme.isEmpty()) { + return true; + } + + return false; +} + +void WaylandCursorImage::invalidateCursorTheme() +{ + m_cursorTheme = KXcursorTheme(); +} + +void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, Image *cursorImage) +{ + loadThemeCursor(shape.name(), cursorImage); +} + +void WaylandCursorImage::loadThemeCursor(const QByteArray &name, Image *cursorImage) +{ + if (!ensureCursorTheme()) { return; } - image->image = {}; - wl_cursor_image *cursor = m_cursorTheme->get(shape); - if (!cursor) { - qDebug() << "Could not find cursor" << shape; + if (loadThemeCursor_helper(name, cursorImage)) { return; } - wl_buffer *b = wl_cursor_image_get_buffer(cursor); - if (!b) { - return; + + const auto alternativeNames = Cursor::cursorAlternativeNames(name); + for (const QByteArray &alternativeName : alternativeNames) { + if (loadThemeCursor_helper(alternativeName, cursorImage)) { + return; + } } - waylandServer()->internalClientConection()->flush(); - waylandServer()->dispatch(); - auto buffer = KWaylandServer::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b))); - if (!buffer) { - return; + + qCWarning(KWIN_CORE) << "Failed to load theme cursor for shape" << name; +} + +bool WaylandCursorImage::loadThemeCursor_helper(const QByteArray &name, Image *cursorImage) +{ + const QVector sprites = m_cursorTheme.shape(name); + if (sprites.isEmpty()) { + return false; } - auto scale = screens()->maxScale(); - int hotSpotX = qRound(cursor->hotspot_x / scale); - int hotSpotY = qRound(cursor->hotspot_y / scale); - QImage img = buffer->data().copy(); - img.setDevicePixelRatio(scale); - *image = {img, QPoint(hotSpotX, hotSpotY)}; + + cursorImage->image = sprites.first().data(); + cursorImage->image.setDevicePixelRatio(m_cursorTheme.devicePixelRatio()); + + cursorImage->hotspot = sprites.first().hotspot(); + cursorImage->hotspot /= m_cursorTheme.devicePixelRatio(); + + return true; } void CursorImage::reevaluteSource() diff --git a/pointer_input.h b/pointer_input.h index fae8d1203d..bbe8240f81 100644 --- a/pointer_input.h +++ b/pointer_input.h @@ -24,6 +24,7 @@ along with this program. If not, see . #include "input.h" #include "cursor.h" +#include "xcursortheme.h" #include #include @@ -42,7 +43,6 @@ namespace KWin class CursorImage; class InputRedirection; class Toplevel; -class WaylandCursorTheme; class CursorShape; namespace Decoration @@ -181,19 +181,25 @@ class WaylandCursorImage : public QObject { Q_OBJECT public: - void loadTheme(); + explicit WaylandCursorImage(QObject *parent = nullptr); + struct Image { QImage image; QPoint hotspot; }; - template - void loadThemeCursor(const T &shape, Image *image); + + void loadThemeCursor(const CursorShape &shape, Image *cursorImage); + void loadThemeCursor(const QByteArray &name, Image *cursorImage); Q_SIGNALS: void themeChanged(); private: - WaylandCursorTheme *m_cursorTheme = nullptr; + bool loadThemeCursor_helper(const QByteArray &name, Image *cursorImage); + bool ensureCursorTheme(); + void invalidateCursorTheme(); + + KXcursorTheme m_cursorTheme; }; class CursorImage : public QObject diff --git a/wayland_cursor_theme.cpp b/wayland_cursor_theme.cpp deleted file mode 100644 index 721ed17d87..0000000000 --- a/wayland_cursor_theme.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/******************************************************************** - KWin - the KDE window manager - This file is part of the KDE project. - -Copyright (C) 2015 Martin Gräßlin - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*********************************************************************/ -#include "wayland_cursor_theme.h" -#include "cursor.h" -#include "wayland_server.h" -#include "screens.h" -// Qt -#include -// KWayland -#include -#include -#include -// Wayland -#include - -namespace KWin -{ - -WaylandCursorTheme::WaylandCursorTheme(KWayland::Client::ShmPool *shm, QObject *parent) - : QObject(parent) - , m_theme(nullptr) - , m_shm(shm) -{ - connect(screens(), &Screens::maxScaleChanged, this, &WaylandCursorTheme::loadTheme); -} - -WaylandCursorTheme::~WaylandCursorTheme() -{ - destroyTheme(); -} - -void WaylandCursorTheme::loadTheme() -{ - if (!m_shm->isValid()) { - return; - } - Cursor *c = Cursors::self()->mouse(); - int size = c->themeSize(); - if (size == 0) { - //set a default size - size = 24; - } - - size *= screens()->maxScale(); - - auto theme = wl_cursor_theme_load(c->themeName().toUtf8().constData(), - size, m_shm->shm()); - if (theme) { - if (!m_theme) { - // so far the theme had not been created, this means we need to start tracking theme changes - connect(c, &Cursor::themeChanged, this, &WaylandCursorTheme::loadTheme); - } else { - destroyTheme(); - } - m_theme = theme; - emit themeChanged(); - } -} - -void WaylandCursorTheme::destroyTheme() -{ - if (!m_theme) { - return; - } - wl_cursor_theme_destroy(m_theme); - m_theme = nullptr; -} - -wl_cursor_image *WaylandCursorTheme::get(CursorShape shape) -{ - return get(shape.name()); -} - -wl_cursor_image *WaylandCursorTheme::get(const QByteArray &name) -{ - if (!m_theme) { - loadTheme(); - } - if (!m_theme) { - // loading cursor failed - return nullptr; - } - wl_cursor *c = wl_cursor_theme_get_cursor(m_theme, name.constData()); - if (!c || c->image_count <= 0) { - const auto &names = Cursors::self()->mouse()->cursorAlternativeNames(name); - for (auto it = names.begin(), end = names.end(); it != end; it++) { - c = wl_cursor_theme_get_cursor(m_theme, (*it).constData()); - if (c && c->image_count > 0) { - break; - } - } - } - if (!c || c->image_count <= 0) { - return nullptr; - } - // TODO: who deletes c? - return c->images[0]; -} - -} diff --git a/wayland_cursor_theme.h b/wayland_cursor_theme.h deleted file mode 100644 index 5f3a90b6e9..0000000000 --- a/wayland_cursor_theme.h +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************** - KWin - the KDE window manager - This file is part of the KDE project. - -Copyright (C) 2015 Martin Gräßlin - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*********************************************************************/ -#ifndef KWIN_WAYLAND_CURSOR_THEME_H -#define KWIN_WAYLAND_CURSOR_THEME_H - -#include - -#include -#include "cursor.h" - -struct wl_cursor_image; -struct wl_cursor_theme; - -namespace KWayland -{ -namespace Client -{ -class ShmPool; -} -} - -namespace KWin -{ - -class KWIN_EXPORT WaylandCursorTheme : public QObject -{ - Q_OBJECT -public: - explicit WaylandCursorTheme(KWayland::Client::ShmPool *shm, QObject *parent = nullptr); - ~WaylandCursorTheme() override; - - wl_cursor_image *get(CursorShape shape); - wl_cursor_image *get(const QByteArray &name); - -Q_SIGNALS: - void themeChanged(); - -private: - void loadTheme(); - void destroyTheme(); - wl_cursor_theme *m_theme; - KWayland::Client::ShmPool *m_shm = nullptr; -}; - -} - -#endif diff --git a/wayland_server.cpp b/wayland_server.cpp index 7b51754d9a..f7c0c8f5dc 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -35,7 +35,6 @@ along with this program. If not, see . #include #include #include -#include #include // Server #include @@ -119,7 +118,6 @@ void WaylandServer::destroyInternalConnection() delete m_internalConnection.compositor; delete m_internalConnection.seat; delete m_internalConnection.ddm; - delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); @@ -673,11 +671,6 @@ void WaylandServer::createInternalConnection() registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; - connect(registry, &Registry::shmAnnounced, this, - [this] (quint32 name, quint32 version) { - m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); - } - ); connect(registry, &Registry::interfacesAnnounced, this, [this, registry] { m_internalConnection.interfacesAnnounced = true; diff --git a/wayland_server.h b/wayland_server.h index 825b42c92e..b2ba8fd4e5 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -38,7 +38,6 @@ class Registry; class Compositor; class Seat; class DataDeviceManager; -class ShmPool; class Surface; } } @@ -205,9 +204,6 @@ public: KWayland::Client::DataDeviceManager *internalDataDeviceManager() { return m_internalConnection.ddm; } - KWayland::Client::ShmPool *internalShmPool() { - return m_internalConnection.shm; - } KWayland::Client::ConnectionThread *internalClientConection() { return m_internalConnection.client; } @@ -298,7 +294,6 @@ private: KWayland::Client::Compositor *compositor = nullptr; KWayland::Client::Seat *seat = nullptr; KWayland::Client::DataDeviceManager *ddm = nullptr; - KWayland::Client::ShmPool *shm = nullptr; bool interfacesAnnounced = false; } m_internalConnection; diff --git a/xcursortheme.cpp b/xcursortheme.cpp new file mode 100644 index 0000000000..f1ef1f35eb --- /dev/null +++ b/xcursortheme.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 Vlad Zahorodnii + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xcursortheme.h" +#include "3rdparty/xcursor.h" + +namespace KWin +{ + +KXcursorSprite::KXcursorSprite() +{ +} + +KXcursorSprite::KXcursorSprite(const QImage &data, const QPoint &hotspot, + const std::chrono::milliseconds &delay) + : m_data(data) + , m_hotspot(hotspot) + , m_delay(delay) +{ +} + +QImage KXcursorSprite::data() const +{ + return m_data; +} + +QPoint KXcursorSprite::hotspot() const +{ + return m_hotspot; +} + +std::chrono::milliseconds KXcursorSprite::delay() const +{ + return m_delay; +} + +static void load_callback(XcursorImages *images, void *data) +{ + QVector sprites; + + for (int i = 0; i < images->nimage; ++i) { + const XcursorImage *nativeCursorImage = images->images[i]; + const QPoint hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot); + const std::chrono::milliseconds delay(nativeCursorImage->delay); + + QImage data(nativeCursorImage->width, nativeCursorImage->height, QImage::Format_ARGB32); + memcpy(data.bits(), nativeCursorImage->pixels, data.sizeInBytes()); + + sprites.append(KXcursorSprite(data, hotspot, delay)); + } + + auto cursorRegistry = static_cast> *>(data); + cursorRegistry->insert(images->name, sprites); + + XcursorImagesDestroy(images); +} + +qreal KXcursorTheme::devicePixelRatio() const +{ + return m_devicePixelRatio; +} + +bool KXcursorTheme::isEmpty() const +{ + return m_cursorRegistry.isEmpty(); +} + +QVector KXcursorTheme::shape(const QByteArray &name) const +{ + return m_cursorRegistry.value(name); +} + +KXcursorTheme KXcursorTheme::fromTheme(const QString &themeName, int size, qreal dpr) +{ + KXcursorTheme theme; + theme.m_devicePixelRatio = dpr; + + const QByteArray nativeThemeName = themeName.toUtf8(); + xcursor_load_theme(nativeThemeName, size * dpr, load_callback, &theme.m_cursorRegistry); + + return theme; +} + +} // namespace KWin diff --git a/xcursortheme.h b/xcursortheme.h new file mode 100644 index 0000000000..e53e1ca7b4 --- /dev/null +++ b/xcursortheme.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 Vlad Zahorodnii + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace KWin +{ + +/** + * The KXcursorSprite class represents a single sprite in the Xcursor theme. + */ +class KWIN_EXPORT KXcursorSprite +{ +public: + /** + * Constructs an empty XcursorSprite. + */ + KXcursorSprite(); + + /** + * Constructs an XcursorSprite with the specified @a data, @a hotspot, and @a delay. + */ + KXcursorSprite(const QImage &data, const QPoint &hotspot, + const std::chrono::milliseconds &delay); + + /** + * Returns the image for this sprite. + */ + QImage data() const; + + /** + * Returns the hotspot for this sprite. (0, 0) corresponds to the upper left corner. + * + * The coordinates of the hotspot are in device pixels. + */ + QPoint hotspot() const; + + /** + * Returns the time interval between this sprite and the next one, in milliseconds. + */ + std::chrono::milliseconds delay() const; + +private: + QImage m_data; + QPoint m_hotspot; + std::chrono::milliseconds m_delay; +}; + +/** + * The KXcursorTheme class represents an Xcursor theme. + */ +class KWIN_EXPORT KXcursorTheme +{ +public: + /** + * Returns the ratio between device pixels and logical pixels for the Xcursor theme. + */ + qreal devicePixelRatio() const; + + /** + * Returns @c true if the Xcursor theme is empty; otherwise returns @c false. + */ + bool isEmpty() const; + + /** + * Returns the list of cursor sprites for the cursor with the given @a name. + */ + QVector shape(const QByteArray &name) const; + + /** + * Attempts to load the Xcursor theme with the given @a themeName and @a size. + */ + static KXcursorTheme fromTheme(const QString &themeName, int size, qreal dpr); + +private: + QMap> m_cursorRegistry; + qreal m_devicePixelRatio = 1; +}; + +} // namespace KWin