From cb7a9456c09b55de4ca5bfd3452f6c0e467fac92 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Mon, 18 May 2020 22:37:41 +0300 Subject: [PATCH] [wayland] Rework Xcursor theme loading code Currently in order to load an Xcursor theme, kwin uses libwayland api, which looks really awkward because of the way how the compositor talks to itself via the internal connection. The main motivation behind this change is to limit the usage of kwayland client api in kwin. --- 3rdparty/xcursor.c | 982 ++++++++++++++++++ 3rdparty/xcursor.h | 74 ++ CMakeLists.txt | 5 +- autotests/integration/pointer_input.cpp | 71 +- cursor.cpp | 20 +- cursor.h | 10 +- libkwineffects/kwinglobals.h | 3 + platform.h | 1 - plugins/platforms/wayland/wayland_backend.cpp | 1 - plugins/platforms/wayland/wayland_backend.h | 2 - pointer_input.cpp | 111 +- pointer_input.h | 16 +- wayland_cursor_theme.cpp | 117 --- wayland_cursor_theme.h | 64 -- wayland_server.cpp | 7 - wayland_server.h | 5 - xcursortheme.cpp | 98 ++ xcursortheme.h | 102 ++ 18 files changed, 1405 insertions(+), 284 deletions(-) create mode 100644 3rdparty/xcursor.c create mode 100644 3rdparty/xcursor.h delete mode 100644 wayland_cursor_theme.cpp delete mode 100644 wayland_cursor_theme.h create mode 100644 xcursortheme.cpp create mode 100644 xcursortheme.h 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