logo

xcursorgen-nox

xcursorgen(1) without libX11/libXcursor dependency
commit: 7b7fa6456446485c098aa35614fbd4fcadc3658b
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Fri, 20 Oct 2023 06:39:45 +0200

init

Diffstat:

A.clang-format24++++++++++++++++++++++++
A.gitignore5+++++
ALICENSES/MIT.txt9+++++++++
AMakefile28++++++++++++++++++++++++++++
AXcursor/Xcursor.h466+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AXcursor/file.c960+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.h5+++++
Axcursorgen.c463+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 1960 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2019-2023 Haelwenn (lanodan) Monnier +# SPDX-License-Identifier: MIT + +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: true +AlignOperands: true +AlignTrailingComments: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AlwaysBreakAfterReturnType: AllDefinitions +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Allman +SpaceBeforeParens: Never +IncludeBlocks: Regroup +ReflowComments: false +SortIncludes: true +UseTab: ForIndentation +IndentWidth: 2 +TabWidth: 2 +ColumnLimit: 100 + +NamespaceIndentation: All diff --git a/.gitignore b/.gitignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2019-2023 Haelwenn (lanodan) Monnier +# SPDX-License-Identifier: MIT + +/xcursorgen +/Xcursor/file.o diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) <year> <copyright holders> + +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 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. diff --git a/Makefile b/Makefile @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier +# SPDX-License-Identifier: MIT + +CC ?= cc +CFLAGS ?= -O2 -g -Wall -Wextra + +PKG_CONFIG ?= pkg-config + +PNG_CFLAGS = `${PKG_CONFIG} --cflags libpng` +PNG_LIBS = `${PKG_CONFIG} --libs libpng` + +all: xcursorgen + +Xcursor/file.o: Xcursor/file.c + ${CC} ${CFLAGS} -c -o $@ Xcursor/file.c + +XCURSORGEN_SRC = xcursorgen.c Xcursor/file.o +xcursorgen: ${XCURSORGEN_SRC} | *.h Xcursor/*.h + ${CC} ${CFLAGS} ${PNG_CFLAGS} -o $@ ${XCURSORGEN_SRC} ${LDFLAGS} ${PNG_LIBS} + +SOURCES = *.c *.h Xcursor/*.h Xcursor/*.c +.PHONY: format +format: + clang-format -i --style=file:.clang-format ${SOURCES} + +.PHONY: clean +clean: + rm xcursorgen Xcursor/file.o diff --git a/Xcursor/Xcursor.h b/Xcursor/Xcursor.h @@ -0,0 +1,466 @@ +/* include/X11/Xcursor/Xcursor.h. Generated from Xcursor.h.in by configure. */ +/* + * Copyright © 2002 Keith Packard + * + * SPDX-License-Identifier: MIT + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _XCURSOR_H_ +#define _XCURSOR_H_ +#include <stdint.h> +#include <stdio.h> + +typedef int XcursorBool; +typedef uint32_t XcursorUInt; + +typedef XcursorUInt XcursorDim; +typedef XcursorUInt XcursorPixel; + +#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 2 +#define XCURSOR_LIB_REVISION 1 +#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 + * <extra type-specific header fields> + * <type-specific data> + * + * 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 _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; + +//typedef struct _XcursorCursors { +// Display *dpy; /* Display holding cursors */ +// int ref; /* reference count */ +// int ncursor; /* number of cursors */ +// Cursor *cursors; /* array of cursors */ +//} XcursorCursors; + +//typedef struct _XcursorAnimate { +// XcursorCursors *cursors; /* list of cursors to use */ +// int sequence; /* which cursor is next */ +//} XcursorAnimate; + +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; + +#define XCURSOR_CORE_THEME "core" + +/* + * Manage Image objects + */ +XcursorImage *XcursorImageCreate(int width, int height); + +void XcursorImageDestroy(XcursorImage *image); + +/* + * Manage Images objects + */ +extern XcursorImages *XcursorImagesCreate(int size); + +void XcursorImagesDestroy(XcursorImages *images); + +void XcursorImagesSetName(XcursorImages *images, const char *name); + +/* + * Manage Cursor objects + */ +//XcursorCursors * +//XcursorCursorsCreate (Display *dpy, int size); + +//void +//XcursorCursorsDestroy (XcursorCursors *cursors); + +/* + * Manage Animate objects + */ +//XcursorAnimate * +//XcursorAnimateCreate (XcursorCursors *cursors); +// +//void +//XcursorAnimateDestroy (XcursorAnimate *animate); +// +//Cursor +//XcursorAnimateNext (XcursorAnimate *animate); + +/* + * Manage Comment objects + */ +XcursorComment *XcursorCommentCreate(XcursorUInt comment_type, int length); + +void XcursorCommentDestroy(XcursorComment *comment); + +XcursorComments *XcursorCommentsCreate(int size); + +void XcursorCommentsDestroy(XcursorComments *comments); + +/* + * XcursorFile/Image APIs + */ +XcursorImage *XcursorXcFileLoadImage(XcursorFile *file, int size); + +XcursorImages *XcursorXcFileLoadImages(XcursorFile *file, int size); + +XcursorImages *XcursorXcFileLoadAllImages(XcursorFile *file); + +XcursorBool +XcursorXcFileLoad(XcursorFile *file, XcursorComments **commentsp, XcursorImages **imagesp); + +XcursorBool +XcursorXcFileSave(XcursorFile *file, const XcursorComments *comments, const XcursorImages *images); + +/* + * FILE/Image APIs + */ +XcursorImage *XcursorFileLoadImage(FILE *file, int size); + +XcursorImages *XcursorFileLoadImages(FILE *file, int size); + +XcursorImages *XcursorFileLoadAllImages(FILE *file); + +XcursorBool XcursorFileLoad(FILE *file, XcursorComments **commentsp, XcursorImages **imagesp); + +XcursorBool XcursorFileSaveImages(FILE *file, const XcursorImages *images); + +XcursorBool +XcursorFileSave(FILE *file, const XcursorComments *comments, const XcursorImages *images); + +/* + * Filename/Image APIs + */ +XcursorImage *XcursorFilenameLoadImage(const char *filename, int size); + +XcursorImages *XcursorFilenameLoadImages(const char *filename, int size); + +XcursorImages *XcursorFilenameLoadAllImages(const char *filename); + +XcursorBool +XcursorFilenameLoad(const char *file, XcursorComments **commentsp, XcursorImages **imagesp); + +XcursorBool XcursorFilenameSaveImages(const char *filename, const XcursorImages *images); + +XcursorBool +XcursorFilenameSave(const char *file, const XcursorComments *comments, const XcursorImages *images); + +/* + * Library/Image APIs + */ +XcursorImage *XcursorLibraryLoadImage(const char *library, const char *theme, int size); + +XcursorImages *XcursorLibraryLoadImages(const char *library, const char *theme, int size); + +/* + * Library/shape API + */ + +const char *XcursorLibraryPath(void); + +int XcursorLibraryShape(const char *library); + +/* + * Image/Cursor APIs + */ + +//Cursor +//XcursorImageLoadCursor (Display *dpy, const XcursorImage *image); +// +//XcursorCursors * +//XcursorImagesLoadCursors (Display *dpy, const XcursorImages *images); +// +//Cursor +//XcursorImagesLoadCursor (Display *dpy, const XcursorImages *images); + +/* + * Filename/Cursor APIs + */ +//Cursor +//XcursorFilenameLoadCursor (Display *dpy, const char *file); +// +//XcursorCursors * +//XcursorFilenameLoadCursors (Display *dpy, const char *file); + +/* + * Library/Cursor APIs + */ +//Cursor +//XcursorLibraryLoadCursor (Display *dpy, const char *file); +// +//XcursorCursors * +//XcursorLibraryLoadCursors (Display *dpy, const char *file); + +/* + * Shape/Image APIs + */ + +XcursorImage *XcursorShapeLoadImage(unsigned int shape, const char *theme, int size); + +XcursorImages *XcursorShapeLoadImages(unsigned int shape, const char *theme, int size); + +/* + * Shape/Cursor APIs + */ +//Cursor +//XcursorShapeLoadCursor (Display *dpy, unsigned int shape); +// +//XcursorCursors * +//XcursorShapeLoadCursors (Display *dpy, unsigned int shape); + +/* + * This is the function called by Xlib when attempting to + * load cursors from XCreateGlyphCursor. The interface must + * not change as Xlib loads 'libXcursor.so' instead of + * a specific major version + */ +//Cursor +//XcursorTryShapeCursor (Display *dpy, +// Font source_font, +// Font mask_font, +// unsigned int source_char, +// unsigned int mask_char, +// XColor _Xconst *foreground, +// XColor _Xconst *background); +// +//void +//XcursorNoticeCreateBitmap (Display *dpy, +// Pixmap pid, +// unsigned int width, +// unsigned int height); +// +//void +//XcursorNoticePutBitmap (Display *dpy, +// Drawable draw, +// XImage *image); +// +//Cursor +//XcursorTryShapeBitmapCursor (Display *dpy, +// Pixmap source, +// Pixmap mask, +// XColor *foreground, +// XColor *background, +// unsigned int x, +// unsigned int y); + +#define XCURSOR_BITMAP_HASH_SIZE 16 +// XImage undefined +// +//void +//XcursorImageHash (XImage *image, +// unsigned char hash[XCURSOR_BITMAP_HASH_SIZE]); + +/* + * Display information APIs + */ +//XcursorBool +//XcursorSupportsARGB (Display *dpy); +// +//XcursorBool +//XcursorSupportsAnim (Display *dpy); +// +//XcursorBool +//XcursorSetDefaultSize (Display *dpy, int size); +// +//int +//XcursorGetDefaultSize (Display *dpy); +// +//XcursorBool +//XcursorSetTheme (Display *dpy, const char *theme); +// +//char * +//XcursorGetTheme (Display *dpy); +// +//XcursorBool +//XcursorGetThemeCore (Display *dpy); +// +//XcursorBool +//XcursorSetThemeCore (Display *dpy, XcursorBool theme_core); + +#endif diff --git a/Xcursor/file.c b/Xcursor/file.c @@ -0,0 +1,960 @@ +/* + * Copyright © 2002 Keith Packard + * + * SPDX-License-Identifier: MIT + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "Xcursor.h" + +#include <stdlib.h> +#include <string.h> + +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) + (size_t)(width * height) * sizeof(XcursorPixel)); + if(!image) return NULL; + image->version = XCURSOR_IMAGE_VERSION; + image->pixels = (XcursorPixel *)(image + 1); + image->size = (XcursorDim)(width > height ? width : height); + image->width = (XcursorDim)width; + image->height = (XcursorDim)height; + image->delay = 0; + return image; +} + +void +XcursorImageDestroy(XcursorImage *image) +{ + free(image); +} + +XcursorImages * +XcursorImagesCreate(int size) +{ + XcursorImages *images; + + images = malloc(sizeof(XcursorImages) + (size_t)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); +} + +void +XcursorImagesSetName(XcursorImages *images, const char *name) +{ + char *new; + + if(!images || !name) return; + + new = strdup(name); + + if(!new) return; + + if(images->name) free(images->name); + images->name = new; +} + +XcursorComment * +XcursorCommentCreate(XcursorUInt comment_type, int length) +{ + XcursorComment *comment; + + if(length < 0 || length > XCURSOR_COMMENT_MAX_LEN) return NULL; + + comment = malloc(sizeof(XcursorComment) + (size_t)length + 1); + if(!comment) return NULL; + comment->version = XCURSOR_COMMENT_VERSION; + comment->comment_type = comment_type; + comment->comment = (char *)(comment + 1); + comment->comment[0] = '\0'; + return comment; +} + +void +XcursorCommentDestroy(XcursorComment *comment) +{ + free(comment); +} + +XcursorComments * +XcursorCommentsCreate(int size) +{ + XcursorComments *comments; + + comments = malloc(sizeof(XcursorComments) + (size_t)size * sizeof(XcursorComment *)); + if(!comments) return NULL; + comments->ncomment = 0; + comments->comments = (XcursorComment **)(comments + 1); + return comments; +} + +void +XcursorCommentsDestroy(XcursorComments *comments) +{ + int n; + + if(!comments) return; + + for(n = 0; n < comments->ncomment; n++) + XcursorCommentDestroy(comments->comments[n]); + free(comments); +} + +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 = ((XcursorUInt)(bytes[0]) << 0) | ((XcursorUInt)(bytes[1]) << 8) | + ((XcursorUInt)(bytes[2]) << 16) | ((XcursorUInt)(bytes[3]) << 24); + return XcursorTrue; +} + +static XcursorBool +_XcursorReadBytes(XcursorFile *file, char *bytes, int length) +{ + if(!file || !bytes || (*file->read)(file, (unsigned char *)bytes, length) != length) + return XcursorFalse; + return XcursorTrue; +} + +static XcursorBool +_XcursorWriteUInt(XcursorFile *file, XcursorUInt u) +{ + unsigned char bytes[4]; + + if(!file) return XcursorFalse; + + bytes[0] = (unsigned char)(u); + bytes[1] = (unsigned char)(u >> 8); + bytes[2] = (unsigned char)(u >> 16); + bytes[3] = (unsigned char)(u >> 24); + if((*file->write)(file, bytes, 4) != 4) return XcursorFalse; + return XcursorTrue; +} + +static XcursorBool +_XcursorWriteBytes(XcursorFile *file, char *bytes, int length) +{ + if(!file || !bytes || (*file->write)(file, (unsigned char *)bytes, length) != length) + return XcursorFalse; + return XcursorTrue; +} + +static void +_XcursorFileHeaderDestroy(XcursorFileHeader *fileHeader) +{ + free(fileHeader); +} + +static XcursorFileHeader * +_XcursorFileHeaderCreate(XcursorUInt 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; + XcursorUInt 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 XcursorUInt +_XcursorFileHeaderLength(XcursorFileHeader *fileHeader) +{ + return (XCURSOR_FILE_HEADER_LEN + fileHeader->ntoc * XCURSOR_FILE_TOC_LEN); +} + +static XcursorBool +_XcursorWriteFileHeader(XcursorFile *file, XcursorFileHeader *fileHeader) +{ + XcursorUInt toc; + + if(!file || !fileHeader) return XcursorFalse; + + if(!_XcursorWriteUInt(file, fileHeader->magic)) return XcursorFalse; + if(!_XcursorWriteUInt(file, fileHeader->header)) return XcursorFalse; + if(!_XcursorWriteUInt(file, fileHeader->version)) return XcursorFalse; + if(!_XcursorWriteUInt(file, fileHeader->ntoc)) return XcursorFalse; + for(toc = 0; toc < fileHeader->ntoc; toc++) + { + if(!_XcursorWriteUInt(file, fileHeader->tocs[toc].type)) return XcursorFalse; + if(!_XcursorWriteUInt(file, fileHeader->tocs[toc].subtype)) return XcursorFalse; + if(!_XcursorWriteUInt(file, fileHeader->tocs[toc].position)) return XcursorFalse; + } + return XcursorTrue; +} + +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; +} + +static XcursorBool +_XcursorFileWriteChunkHeader(XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc, + XcursorChunkHeader *chunkHeader) +{ + if(!file || !fileHeader || !chunkHeader) return XcursorFalse; + if(!_XcursorSeekToToc(file, fileHeader, toc)) return XcursorFalse; + if(!_XcursorWriteUInt(file, chunkHeader->header)) return XcursorFalse; + if(!_XcursorWriteUInt(file, chunkHeader->type)) return XcursorFalse; + if(!_XcursorWriteUInt(file, chunkHeader->subtype)) return XcursorFalse; + if(!_XcursorWriteUInt(file, chunkHeader->version)) return XcursorFalse; + return XcursorTrue; +} + +#define dist(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) + +static XcursorDim +_XcursorFindBestSize(XcursorFileHeader *fileHeader, XcursorDim size, int *nsizesp) +{ + XcursorUInt 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) +{ + XcursorUInt 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 (int)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((int)head.width, (int)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 = (int)(image->width * image->height); + p = image->pixels; + while(n--) + { + if(!_XcursorReadUInt(file, p)) + { + XcursorImageDestroy(image); + return NULL; + } + p++; + } + return image; +} + +static XcursorUInt +_XcursorImageLength(XcursorImage *image) +{ + if(!image) return 0; + + return XCURSOR_IMAGE_HEADER_LEN + (image->width * image->height) * 4; +} + +static XcursorBool +_XcursorWriteImage(XcursorFile *file, XcursorFileHeader *fileHeader, int toc, XcursorImage *image) +{ + XcursorChunkHeader chunkHeader; + int n; + XcursorPixel *p; + + if(!file || !fileHeader || !image) return XcursorFalse; + + /* sanity check data */ + if(image->width > XCURSOR_IMAGE_MAX_SIZE || image->height > XCURSOR_IMAGE_MAX_SIZE) + return XcursorFalse; + if(image->width == 0 || image->height == 0) return XcursorFalse; + if(image->xhot > image->width || image->yhot > image->height) return XcursorFalse; + + /* write chunk header */ + chunkHeader.header = XCURSOR_IMAGE_HEADER_LEN; + chunkHeader.type = XCURSOR_IMAGE_TYPE; + chunkHeader.subtype = image->size; + chunkHeader.version = XCURSOR_IMAGE_VERSION; + + if(!_XcursorFileWriteChunkHeader(file, fileHeader, toc, &chunkHeader)) return XcursorFalse; + + /* write extra image header fields */ + if(!_XcursorWriteUInt(file, image->width)) return XcursorFalse; + if(!_XcursorWriteUInt(file, image->height)) return XcursorFalse; + if(!_XcursorWriteUInt(file, image->xhot)) return XcursorFalse; + if(!_XcursorWriteUInt(file, image->yhot)) return XcursorFalse; + if(!_XcursorWriteUInt(file, image->delay)) return XcursorFalse; + + /* write the image */ + n = (int)(image->width * image->height); + p = image->pixels; + while(n--) + { + if(!_XcursorWriteUInt(file, *p)) return XcursorFalse; + p++; + } + return XcursorTrue; +} + +static XcursorComment * +_XcursorReadComment(XcursorFile *file, XcursorFileHeader *fileHeader, int toc) +{ + XcursorChunkHeader chunkHeader; + XcursorUInt length; + XcursorComment *comment; + + if(!file || !fileHeader) return NULL; + + /* read chunk header */ + if(!_XcursorFileReadChunkHeader(file, fileHeader, toc, &chunkHeader)) return NULL; + /* read extra comment header fields */ + if(!_XcursorReadUInt(file, &length)) return NULL; + comment = XcursorCommentCreate(chunkHeader.subtype, (int)length); + if(!comment) return NULL; + if(!_XcursorReadBytes(file, comment->comment, (int)length)) + { + XcursorCommentDestroy(comment); + return NULL; + } + comment->comment[length] = '\0'; + return comment; +} + +static XcursorUInt +_XcursorCommentLength(XcursorComment *comment) +{ + return XCURSOR_COMMENT_HEADER_LEN + (XcursorUInt)strlen(comment->comment); +} + +static XcursorBool +_XcursorWriteComment(XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc, + XcursorComment *comment) +{ + XcursorChunkHeader chunkHeader; + XcursorUInt length; + + if(!file || !fileHeader || !comment || !comment->comment) return XcursorFalse; + + length = (XcursorUInt)strlen(comment->comment); + + /* sanity check data */ + if(length > XCURSOR_COMMENT_MAX_LEN) return XcursorFalse; + + /* read chunk header */ + chunkHeader.header = XCURSOR_COMMENT_HEADER_LEN; + chunkHeader.type = XCURSOR_COMMENT_TYPE; + chunkHeader.subtype = comment->comment_type; + chunkHeader.version = XCURSOR_COMMENT_VERSION; + + if(!_XcursorFileWriteChunkHeader(file, fileHeader, toc, &chunkHeader)) return XcursorFalse; + + /* write extra comment header fields */ + if(!_XcursorWriteUInt(file, length)) return XcursorFalse; + + if(!_XcursorWriteBytes(file, comment->comment, (int)length)) return XcursorFalse; + return XcursorTrue; +} + +XcursorImage * +XcursorXcFileLoadImage(XcursorFile *file, int size) +{ + XcursorFileHeader *fileHeader; + XcursorDim bestSize; + int nsize; + int toc; + XcursorImage *image; + + if(size < 0) return NULL; + fileHeader = _XcursorReadFileHeader(file); + if(!fileHeader) return NULL; + bestSize = _XcursorFindBestSize(fileHeader, (XcursorDim)size, &nsize); + if(!bestSize) return NULL; + toc = _XcursorFindImageToc(fileHeader, bestSize, 0); + if(toc < 0) return NULL; + image = _XcursorReadImage(file, fileHeader, toc); + _XcursorFileHeaderDestroy(fileHeader); + return image; +} + +XcursorImages * +XcursorXcFileLoadImages(XcursorFile *file, int size) +{ + XcursorFileHeader *fileHeader; + XcursorDim bestSize; + int nsize; + XcursorImages *images; + int n; + + 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++) + { + int 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; +} + +XcursorImages * +XcursorXcFileLoadAllImages(XcursorFile *file) +{ + XcursorFileHeader *fileHeader; + XcursorImage *image; + XcursorImages *images; + int nimage; + XcursorUInt n; + XcursorUInt toc; + + if(!file) return NULL; + + fileHeader = _XcursorReadFileHeader(file); + if(!fileHeader) return NULL; + nimage = 0; + for(n = 0; n < fileHeader->ntoc; n++) + { + switch(fileHeader->tocs[n].type) + { + case XCURSOR_IMAGE_TYPE: + nimage++; + break; + } + } + images = XcursorImagesCreate(nimage); + if(!images) + { + _XcursorFileHeaderDestroy(fileHeader); + return NULL; + } + for(toc = 0; toc < fileHeader->ntoc; toc++) + { + switch(fileHeader->tocs[toc].type) + { + case XCURSOR_IMAGE_TYPE: + image = _XcursorReadImage(file, fileHeader, (int)toc); + if(image) + { + images->images[images->nimage] = image; + images->nimage++; + } + break; + } + } + _XcursorFileHeaderDestroy(fileHeader); + if(images->nimage != nimage) + { + XcursorImagesDestroy(images); + images = NULL; + } + return images; +} + +XcursorBool +XcursorXcFileLoad(XcursorFile *file, XcursorComments **commentsp, XcursorImages **imagesp) +{ + XcursorFileHeader *fileHeader; + int nimage; + int ncomment; + XcursorImages *images; + XcursorImage *image; + XcursorComment *comment; + XcursorComments *comments; + XcursorUInt toc; + + if(!file) return 0; + fileHeader = _XcursorReadFileHeader(file); + if(!fileHeader) return 0; + nimage = 0; + ncomment = 0; + for(toc = 0; toc < fileHeader->ntoc; toc++) + { + switch(fileHeader->tocs[toc].type) + { + case XCURSOR_COMMENT_TYPE: + ncomment++; + break; + case XCURSOR_IMAGE_TYPE: + nimage++; + break; + } + } + images = XcursorImagesCreate(nimage); + if(!images) return 0; + comments = XcursorCommentsCreate(ncomment); + if(!comments) + { + XcursorImagesDestroy(images); + return 0; + } + for(toc = 0; toc < fileHeader->ntoc; toc++) + { + switch(fileHeader->tocs[toc].type) + { + case XCURSOR_COMMENT_TYPE: + comment = _XcursorReadComment(file, fileHeader, (int)toc); + if(comment) + { + comments->comments[comments->ncomment] = comment; + comments->ncomment++; + } + break; + case XCURSOR_IMAGE_TYPE: + image = _XcursorReadImage(file, fileHeader, (int)toc); + if(image) + { + images->images[images->nimage] = image; + images->nimage++; + } + break; + } + } + _XcursorFileHeaderDestroy(fileHeader); + if(images->nimage != nimage || comments->ncomment != ncomment) + { + XcursorImagesDestroy(images); + XcursorCommentsDestroy(comments); + images = NULL; + comments = NULL; + return XcursorFalse; + } + *imagesp = images; + *commentsp = comments; + return XcursorTrue; +} + +XcursorBool +XcursorXcFileSave(XcursorFile *file, const XcursorComments *comments, const XcursorImages *images) +{ + XcursorFileHeader *fileHeader; + XcursorUInt position; + int n; + int toc; + + if(!file || !comments || !images) return XcursorFalse; + + fileHeader = _XcursorFileHeaderCreate((XcursorUInt)(comments->ncomment + images->nimage)); + if(!fileHeader) return XcursorFalse; + + position = _XcursorFileHeaderLength(fileHeader); + + /* + * Compute the toc. Place the images before the comments + * as they're more often read + */ + + toc = 0; + for(n = 0; n < images->nimage; n++) + { + fileHeader->tocs[toc].type = XCURSOR_IMAGE_TYPE; + fileHeader->tocs[toc].subtype = images->images[n]->size; + fileHeader->tocs[toc].position = position; + position += _XcursorImageLength(images->images[n]); + toc++; + } + + for(n = 0; n < comments->ncomment; n++) + { + fileHeader->tocs[toc].type = XCURSOR_COMMENT_TYPE; + fileHeader->tocs[toc].subtype = comments->comments[n]->comment_type; + fileHeader->tocs[toc].position = position; + position += _XcursorCommentLength(comments->comments[n]); + toc++; + } + + /* + * Write the header and the toc + */ + if(!_XcursorWriteFileHeader(file, fileHeader)) goto bail; + + /* + * Write the images + */ + toc = 0; + for(n = 0; n < images->nimage; n++) + { + if(!_XcursorWriteImage(file, fileHeader, toc, images->images[n])) goto bail; + toc++; + } + + /* + * Write the comments + */ + for(n = 0; n < comments->ncomment; n++) + { + if(!_XcursorWriteComment(file, fileHeader, toc, comments->comments[n])) goto bail; + toc++; + } + + _XcursorFileHeaderDestroy(fileHeader); + return XcursorTrue; +bail: + _XcursorFileHeaderDestroy(fileHeader); + return XcursorFalse; +} + +static int +_XcursorStdioFileRead(XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return (int)fread(buf, 1, (size_t)len, f); +} + +static int +_XcursorStdioFileWrite(XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return (int)fwrite(buf, 1, (size_t)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; +} + +XcursorImage * +XcursorFileLoadImage(FILE *file, int size) +{ + XcursorFile f; + + if(!file) return NULL; + + _XcursorStdioFileInitialize(file, &f); + return XcursorXcFileLoadImage(&f, size); +} + +XcursorImages * +XcursorFileLoadImages(FILE *file, int size) +{ + XcursorFile f; + + if(!file) return NULL; + + _XcursorStdioFileInitialize(file, &f); + return XcursorXcFileLoadImages(&f, size); +} + +XcursorImages * +XcursorFileLoadAllImages(FILE *file) +{ + XcursorFile f; + + if(!file) return NULL; + + _XcursorStdioFileInitialize(file, &f); + return XcursorXcFileLoadAllImages(&f); +} + +XcursorBool +XcursorFileLoad(FILE *file, XcursorComments **commentsp, XcursorImages **imagesp) +{ + XcursorFile f; + + if(!file || !commentsp || !imagesp) return XcursorFalse; + + _XcursorStdioFileInitialize(file, &f); + return XcursorXcFileLoad(&f, commentsp, imagesp); +} + +XcursorBool +XcursorFileSaveImages(FILE *file, const XcursorImages *images) +{ + XcursorComments *comments; + XcursorFile f; + XcursorBool ret; + + if(!file || !images) return 0; + if((comments = XcursorCommentsCreate(0)) == NULL) return 0; + _XcursorStdioFileInitialize(file, &f); + ret = XcursorXcFileSave(&f, comments, images) && fflush(file) != EOF; + XcursorCommentsDestroy(comments); + return ret; +} + +XcursorBool +XcursorFileSave(FILE *file, const XcursorComments *comments, const XcursorImages *images) +{ + XcursorFile f; + + if(!file || !comments || !images) return XcursorFalse; + + _XcursorStdioFileInitialize(file, &f); + return XcursorXcFileSave(&f, comments, images) && fflush(file) != EOF; +} + +XcursorImage * +XcursorFilenameLoadImage(const char *file, int size) +{ + FILE *f; + XcursorImage *image; + + if(!file || size < 0) return NULL; + + f = fopen(file, "r"); + if(!f) return NULL; + image = XcursorFileLoadImage(f, size); + fclose(f); + return image; +} + +XcursorImages * +XcursorFilenameLoadImages(const char *file, int size) +{ + FILE *f; + XcursorImages *images; + + if(!file || size < 0) return NULL; + + f = fopen(file, "r"); + if(!f) return NULL; + images = XcursorFileLoadImages(f, size); + fclose(f); + return images; +} + +XcursorImages * +XcursorFilenameLoadAllImages(const char *file) +{ + FILE *f; + XcursorImages *images; + + if(!file) return NULL; + + f = fopen(file, "r"); + if(!f) return NULL; + images = XcursorFileLoadAllImages(f); + fclose(f); + return images; +} + +XcursorBool +XcursorFilenameLoad(const char *file, XcursorComments **commentsp, XcursorImages **imagesp) +{ + FILE *f; + XcursorBool ret; + + if(!file) return XcursorFalse; + + f = fopen(file, "r"); + if(!f) return 0; + ret = XcursorFileLoad(f, commentsp, imagesp); + fclose(f); + return ret; +} + +XcursorBool +XcursorFilenameSaveImages(const char *file, const XcursorImages *images) +{ + FILE *f; + XcursorBool ret; + + if(!file || !images) return XcursorFalse; + + f = fopen(file, "w"); + if(!f) return 0; + ret = XcursorFileSaveImages(f, images); + return fclose(f) != EOF && ret; +} + +XcursorBool +XcursorFilenameSave(const char *file, const XcursorComments *comments, const XcursorImages *images) +{ + FILE *f; + XcursorBool ret; + + if(!file || !comments || !images) return XcursorFalse; + + f = fopen(file, "w"); + if(!f) return 0; + ret = XcursorFileSave(f, comments, images); + return fclose(f) != EOF && ret; +} diff --git a/config.h b/config.h @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier +// SPDX-License-Identifier: MIT + +#define PACKAGE_VERSION "0.0.1" +\ No newline at end of file diff --git a/xcursorgen.c b/xcursorgen.c @@ -0,0 +1,463 @@ +/* + * xcursorgen.c + * + * Copyright (C) 2002 Manish Singh + * + * SPDX-License-Identifier: MIT + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Manish Singh not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Manish Singh makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * MANISH SINGH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL MANISH SINGH BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "Xcursor/Xcursor.h" +#include "config.h" + +#include <ctype.h> +#include <errno.h> +#include <png.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const char *ProgramName; + +struct flist +{ + int size; + int xhot, yhot; + int delay; + char *pngfile; + struct flist *next; +}; + +static void +usage(const char *name) +{ + fprintf(stderr, + "usage: %s [-V] [--version] [-?] [--help] [-p <dir>] [--prefix <dir>] [CONFIG [OUT]]\n%s", + name, + "Generate an Xcursor file from a series of PNG images\n" + "\n" + " -V, --version display the version number and exit\n" + " -?, --help display this message and exit\n" + " -p, --prefix <dir> find cursor images in <dir>\n" + "\n" + "With no CONFIG, or when CONFIG is -, read standard input. " + "Same with OUT and\n" + "standard output.\n"); +} + +static int +read_config_file(const char *config, struct flist **list) +{ + FILE *fp; + char line[4096], pngfile[4000]; + int size, xhot, yhot, delay; + struct flist *start = NULL, *end = NULL, *curr; + int count = 0; + + if(strcmp(config, "-") != 0) + { + fp = fopen(config, "r"); + if(!fp) + { + *list = NULL; + return 0; + } + } + else + fp = stdin; + + while(fgets(line, sizeof(line), fp) != NULL) + { + if(line[0] == '#') continue; + + switch(sscanf(line, "%d %d %d %3999s %d", &size, &xhot, &yhot, pngfile, &delay)) + { + case 4: + delay = 50; + break; + case 5: + break; + default: + { + fprintf(stderr, + "%s: Bad config file data on line %d of %s\n", + ProgramName, + count + 1, + strcmp(config, "-") ? config : "stdin"); + fclose(fp); + return 0; + } + } + + curr = malloc(sizeof(struct flist)); + if(curr == NULL) + { + fprintf(stderr, "%s: malloc() failed: %s\n", ProgramName, strerror(errno)); + fclose(fp); + return 0; + } + + curr->size = size; + curr->xhot = xhot; + curr->yhot = yhot; + + curr->delay = delay; + + curr->pngfile = strdup(pngfile); + if(curr->pngfile == NULL) + { + fprintf(stderr, "%s: strdup() failed: %s\n", ProgramName, strerror(errno)); + fclose(fp); + free(curr); + return 0; + } + + curr->next = NULL; + + if(start) + { + end->next = curr; + end = curr; + } + else + { + start = curr; + end = curr; + } + + count++; + } + + fclose(fp); + + *list = start; + return count; +} + +#define div_255(x) (((x) + 0x80 + (((x) + 0x80) >> 8)) >> 8) + +static void +premultiply_data(png_structp png, png_row_infop row_info, png_bytep data) +{ + png_size_t i; + + for(i = 0; i < row_info->rowbytes; i += 4) + { + unsigned char *base = &data[i]; + unsigned char blue = base[0]; + unsigned char green = base[1]; + unsigned char red = base[2]; + unsigned char alpha = base[3]; + XcursorPixel p; + + red = (unsigned char)div_255((unsigned)red * (unsigned)alpha); + green = (unsigned char)div_255((unsigned)green * (unsigned)alpha); + blue = (unsigned char)div_255((unsigned)blue * (unsigned)alpha); + p = ((unsigned int)alpha << 24) | ((unsigned int)red << 16) | ((unsigned int)green << 8) | + ((unsigned int)blue << 0); + memcpy(base, &p, sizeof(XcursorPixel)); + } +} + +static XcursorImage * +load_image(struct flist *list, const char *prefix) +{ + XcursorImage *image; + png_structp png; + png_infop info; + png_bytepp rows; + FILE *fp; + png_uint_32 i; + png_uint_32 width, height; + int depth, color, interlace; + char *file; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(png == NULL) return NULL; + + info = png_create_info_struct(png); + if(info == NULL) + { + png_destroy_read_struct(&png, NULL, NULL); + return NULL; + } + + if(setjmp(png_jmpbuf(png))) + { + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + if(prefix) + { +#ifdef HAVE_ASPRINTF + if(asprintf(&file, "%s/%s", prefix, list->pngfile) == -1) + { + fprintf(stderr, "%s: asprintf() failed: %s\n", ProgramName, strerror(errno)); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } +#else + file = malloc(strlen(prefix) + 1 + strlen(list->pngfile) + 1); + if(file == NULL) + { + fprintf(stderr, "%s: malloc() failed: %s\n", ProgramName, strerror(errno)); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + strcpy(file, prefix); + strcat(file, "/"); + strcat(file, list->pngfile); +#endif + } + else + file = list->pngfile; + fp = fopen(file, "rb"); + if(prefix) free(file); + + if(fp == NULL) + { + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + png_init_io(png, fp); + png_read_info(png, info); + png_get_IHDR(png, info, &width, &height, &depth, &color, &interlace, NULL, NULL); + + /* TODO: More needs to be done here maybe */ + + if(color == PNG_COLOR_TYPE_PALETTE && depth <= 8) png_set_expand(png); + + if(color == PNG_COLOR_TYPE_GRAY && depth < 8) png_set_expand(png); + + if(png_get_valid(png, info, PNG_INFO_tRNS)) png_set_expand(png); + + if(depth == 16) png_set_strip_16(png); + + if(depth < 8) png_set_packing(png); + + if(color == PNG_COLOR_TYPE_GRAY || color == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); + + if(interlace != PNG_INTERLACE_NONE) png_set_interlace_handling(png); + + png_set_bgr(png); + png_set_filler(png, 255, PNG_FILLER_AFTER); + + png_set_read_user_transform_fn(png, premultiply_data); + + png_read_update_info(png, info); + + image = XcursorImageCreate((int)width, (int)height); + if(image == NULL) + { + fprintf(stderr, + "%s: XcursorImageCreate() failed to create %u x %u image\n" + " for file %s\n", + ProgramName, + width, + height, + list->pngfile); + fclose(fp); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + image->size = (XcursorDim)list->size; + image->xhot = (XcursorDim)list->xhot; + image->yhot = (XcursorDim)list->yhot; + image->delay = (XcursorUInt)list->delay; + + rows = malloc(sizeof(png_bytep) * height); + if(rows == NULL) + { + fclose(fp); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + for(i = 0; i < height; i++) + rows[i] = (png_bytep)(image->pixels + i * width); + + png_read_image(png, rows); + png_read_end(png, info); + + free(rows); + fclose(fp); + png_destroy_read_struct(&png, &info, NULL); + + return image; +} + +static int +write_cursors(int count, struct flist *list, const char *filename, const char *prefix) +{ + XcursorImages *cimages; + XcursorImage *image; + int i; + FILE *fp; + int ret; + + if(strcmp(filename, "-") != 0) + { + fp = fopen(filename, "wb"); + if(!fp) return 1; + } + else + fp = stdout; + + cimages = XcursorImagesCreate(count); + + cimages->nimage = count; + + for(i = 0; i < count; i++, list = list->next) + { + image = load_image(list, prefix); + if(image == NULL) + { + fprintf(stderr, "%s: PNG error while reading %s\n", ProgramName, list->pngfile); + fclose(fp); + return 1; + } + + cimages->images[i] = image; + } + + ret = XcursorFileSaveImages(fp, cimages); + + fclose(fp); + + return ret ? 0 : 1; +} + +#if 0 +static int +check_image(char *image) +{ + unsigned int width, height; + unsigned char *data; + int x_hot, y_hot; + unsigned char hash[XCURSOR_BITMAP_HASH_SIZE]; + int i; + + if (XReadBitmapFileData(image, &width, &height, &data, &x_hot, &y_hot) + != BitmapSuccess) { + fprintf(stderr, "%s: Can't open bitmap file \"%s\"\n", ProgramName, + image); + return 1; + } + else { + XImage ximage = { + .width = (int) width, + .height = (int) height, + .depth = 1, + .bits_per_pixel = 1, + .xoffset = 0, + .format = XYPixmap, + .data = (char *) data, + .byte_order = LSBFirst, + .bitmap_unit = 8, + .bitmap_bit_order = LSBFirst, + .bitmap_pad = 8, + .bytes_per_line = (width + 7) / 8 + }; + XcursorImageHash(&ximage, hash); + } + printf("%s: ", image); + for (i = 0; i < XCURSOR_BITMAP_HASH_SIZE; i++) + printf("%02x", hash[i]); + printf("\n"); + return 0; +} +#endif + +int +main(int argc, char *argv[]) +{ + struct flist *list; + int count; + const char *in = NULL, *out = NULL; + const char *prefix = NULL; + int i; + + ProgramName = argv[0]; + + for(i = 1; i < argc; i++) + { + if(strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) + { + printf("xcursorgen version %s\n", PACKAGE_VERSION); + return 0; + } + + if(strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) + { + usage(argv[0]); + return 0; + } +#if 0 + if (strcmp(argv[i], "-image") == 0) { + int ret = 0; + + while (argv[++i]) { + if (check_image(argv[i])) + ret = i; + } + return ret; + } +#endif + if(strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--prefix") == 0) + { + i++; + if(argv[i] == NULL) + { + fprintf(stderr, "%s: %s requires an argument\n", argv[0], argv[i - 1]); + usage(argv[0]); + return 1; + } + prefix = argv[i]; + continue; + } + + if(!in) + in = argv[i]; + else if(!out) + out = argv[i]; + else + { + fprintf(stderr, "%s: unexpected argument '%s'\n", argv[0], argv[i]); + usage(argv[0]); + return 1; + } + } + + if(!in) in = "-"; + if(!out) out = "-"; + + count = read_config_file(in, &list); + if(count == 0) + { + fprintf( + stderr, "%s: Error reading config file %s\n", argv[0], strcmp(in, "-") ? in : "from stdin"); + return 1; + } + + return write_cursors(count, list, out, prefix); +}