diff --git a/base/MMBitmap.h b/base/MMBitmap.h new file mode 100644 index 0000000..91fea63 --- /dev/null +++ b/base/MMBitmap.h @@ -0,0 +1,87 @@ +#pragma once +#ifndef MMBITMAP_H +#define MMBITMAP_H + +#include "types.h" +#include "rgb.h" +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct _MMBitmap { + uint8_t *imageBuffer; /* Pixels stored in Quad I format; i.e., origin is in + * top left. Length should be height * bytewidth. */ + size_t width; /* Never 0, unless image is NULL. */ + size_t height; /* Never 0, unless image is NULL. */ + size_t bytewidth; /* The aligned width (width + padding). */ + uint8_t bitsPerPixel; /* Should be either 24 or 32. */ + uint8_t bytesPerPixel; /* For convenience; should be bitsPerPixel / 8. */ +}; + +typedef struct _MMBitmap MMBitmap; +typedef MMBitmap *MMBitmapRef; + +/* Creates new MMBitmap with the given values. + * Follows the Create Rule (caller is responsible for destroy()'ing object). */ +MMBitmapRef createMMBitmap(uint8_t *buffer, size_t width, size_t height, + size_t bytewidth, uint8_t bitsPerPixel, + uint8_t bytesPerPixel); + +/* Releases memory occupied by MMBitmap. */ +void destroyMMBitmap(MMBitmapRef bitmap); + +/* Releases memory occupied by MMBitmap. Acts via CallBack method*/ +void destroyMMBitmapBuffer(char * bitmapBuffer, void * hint); + +/* Returns copy of MMBitmap, to be destroy()'d by caller. */ +MMBitmapRef copyMMBitmap(MMBitmapRef bitmap); + +/* Returns copy of one MMBitmap juxtaposed in another (to be destroy()'d + * by the caller.), or NULL on error. */ +MMBitmapRef copyMMBitmapFromPortion(MMBitmapRef source, MMRect rect); + +#define MMBitmapPointInBounds(image, p) ((p).x < (image)->width && \ + (p).y < (image)->height) +#define MMBitmapRectInBounds(image, r) \ + (((r).origin.x + (r).size.width <= (image)->width) && \ + ((r).origin.y + (r).size.height <= (image)->height)) + +#define MMBitmapGetBounds(image) MMRectMake(0, 0, image->width, image->height) + +/* Get pointer to pixel of MMBitmapRef. No bounds checking is performed (check + * yourself before calling this with MMBitmapPointInBounds(). */ +#define MMRGBColorRefAtPoint(image, x, y) \ + (MMRGBColor *)(assert(MMBitmapPointInBounds(bitmap, MMPointMake(x, y))), \ + ((image)->imageBuffer) + (((image)->bytewidth * (y)) \ + + ((x) * (image)->bytesPerPixel))) + +/* Dereference pixel of MMBitmapRef. Again, no bounds checking is performed. */ +#define MMRGBColorAtPoint(image, x, y) *MMRGBColorRefAtPoint(image, x, y) + +/* Hex/integer value of color at point. */ +#define MMRGBHexAtPoint(image, x, y) \ + hexFromMMRGB(MMRGBColorAtPoint(image, x, y)) + +/* Increment either point.x or point.y depending on the position of point.x. + * That is, if x + 1 is >= width, increment y and start x at the beginning. + * Otherwise, increment x. + * + * This is used as a convenience macro to scan rows when calling functions such + * as findColorInRectAt() and findBitmapInBitmapAt(). */ +#define ITER_NEXT_POINT(pixel, width, start_x) \ +do { \ + if (++(pixel).x >= (width)) { \ + (pixel).x = start_x; \ + ++(point).y; \ + } \ +} while (0); + +#ifdef __cplusplus +} +#endif + +#endif /* MMBITMAP_H */ \ No newline at end of file diff --git a/base/MMBitmap_init.h b/base/MMBitmap_init.h new file mode 100644 index 0000000..e300f2a --- /dev/null +++ b/base/MMBitmap_init.h @@ -0,0 +1,96 @@ +#include "MMBitmap.h" +#include +#include + + +//MMBitmapRef createMMBitmap() +MMBitmapRef createMMBitmap( + uint8_t *buffer, + size_t width, + size_t height, + size_t bytewidth, + uint8_t bitsPerPixel, + uint8_t bytesPerPixel +){ + MMBitmapRef bitmap = malloc(sizeof(MMBitmap)); + if (bitmap == NULL) return NULL; + + bitmap->imageBuffer = buffer; + bitmap->width = width; + bitmap->height = height; + bitmap->bytewidth = bytewidth; + bitmap->bitsPerPixel = bitsPerPixel; + bitmap->bytesPerPixel = bytesPerPixel; + + return bitmap; +} + +void destroyMMBitmap(MMBitmapRef bitmap) +{ + assert(bitmap != NULL); + + if (bitmap->imageBuffer != NULL) { + free(bitmap->imageBuffer); + bitmap->imageBuffer = NULL; + } + + free(bitmap); +} + +void destroyMMBitmapBuffer(char * bitmapBuffer, void * hint) +{ + if (bitmapBuffer != NULL) + { + free(bitmapBuffer); + } +} + +MMBitmapRef copyMMBitmap(MMBitmapRef bitmap) +{ + uint8_t *copiedBuf = NULL; + + assert(bitmap != NULL); + if (bitmap->imageBuffer != NULL) { + const size_t bufsize = bitmap->height * bitmap->bytewidth; + copiedBuf = malloc(bufsize); + if (copiedBuf == NULL) return NULL; + + memcpy(copiedBuf, bitmap->imageBuffer, bufsize); + } + + return createMMBitmap(copiedBuf, + bitmap->width, + bitmap->height, + bitmap->bytewidth, + bitmap->bitsPerPixel, + bitmap->bytesPerPixel); +} + +MMBitmapRef copyMMBitmapFromPortion(MMBitmapRef source, MMRect rect) +{ + assert(source != NULL); + + if (source->imageBuffer == NULL || !MMBitmapRectInBounds(source, rect)) { + return NULL; + } else { + uint8_t *copiedBuf = NULL; + const size_t bufsize = rect.size.height * source->bytewidth; + const size_t offset = (source->bytewidth * rect.origin.y) + + (rect.origin.x * source->bytesPerPixel); + + /* Don't go over the bounds, programmer! */ + assert((bufsize + offset) <= (source->bytewidth * source->height)); + + copiedBuf = malloc(bufsize); + if (copiedBuf == NULL) return NULL; + + memcpy(copiedBuf, source->imageBuffer + offset, bufsize); + + return createMMBitmap(copiedBuf, + rect.size.width, + rect.size.height, + source->bytewidth, + source->bitsPerPixel, + source->bytesPerPixel); + } +} diff --git a/base/bmp_io.h b/base/bmp_io.h new file mode 100644 index 0000000..6d52119 --- /dev/null +++ b/base/bmp_io.h @@ -0,0 +1,54 @@ +#pragma once +#ifndef BMP_IO_H +#define BMP_IO_H + +#include "MMBitmap.h" +#include "io.h" + +enum _BMPReadError { + kBMPGenericError = 0, + kBMPAccessError, + kBMPInvalidKeyError, + kBMPUnsupportedHeaderError, + kBMPInvalidColorPanesError, + kBMPUnsupportedColorDepthError, + kBMPUnsupportedCompressionError, + kBMPInvalidPixelDataError +}; + +typedef MMIOError MMBMPReadError; + +/* Returns description of given MMBMPReadError. + * Returned string is constant and hence should not be freed. */ +const char *MMBMPReadErrorString(MMIOError error); + +/* Attempts to read bitmap file at path; returns new MMBitmap on success, or + * NULL on error. If |error| is non-NULL, it will be set to the error code + * on return. + * + * Currently supports: + * - Uncompressed Windows v3/v4/v5 24-bit or 32-bit BMP. + * - OS/2 v1 or v2 24-bit BMP. + * - Does NOT yet support: 1-bit, 4-bit, 8-bit, 16-bit, compressed bitmaps, + * or PNGs/JPEGs disguised as BMPs (and returns NULL if those are given). + * + * Responsibility for destroy()'ing returned MMBitmap is left up to caller. */ +MMBitmapRef newMMBitmapFromBMP(const char *path, MMBMPReadError *error); + +/* Returns a buffer containing the raw BMP file data in Windows v3 BMP format, + * ready to be saved to a file. If |len| is not NULL, it will be set to the + * number of bytes allocated in the returned buffer. + * + * Responsibility for free()'ing data is left up to the caller. */ +uint8_t *createBitmapData(MMBitmapRef bitmap, size_t *len); + +/* Saves bitmap to file in Windows v3 BMP format. + * Returns 0 on success, -1 on error. */ +int saveMMBitmapAsBMP(MMBitmapRef bitmap, const char *path); + +/* Swaps bitmap from Quadrant 1 to Quadran III format, or vice versa + * (upside-down Cartesian/PostScript/GL <-> right side up QD/CG raster format). + */ +void flipBitmapData(void *data, size_t width, size_t height, size_t bytewidth); + +#endif /* BMP_IO_H */ diff --git a/base/bmp_io_init.h b/base/bmp_io_init.h new file mode 100644 index 0000000..97d9bf2 --- /dev/null +++ b/base/bmp_io_init.h @@ -0,0 +1,431 @@ +#include "bmp_io.h" +#include "os.h" +#include "endian.h" +#include /* fopen() */ +#include /* memcpy() */ + +#if defined(_MSC_VER) + #include "ms_stdbool.h" + #include "ms_stdint.h" +#else + #include + #include +#endif + +#pragma pack(push, 1) /* The following structs should be continguous, so we can + * copy them in one read. */ +/* + * Standard, initial BMP Header + */ +struct BITMAP_FILE_HEADER { + uint16_t magic; /* First two byes of the file; should be 0x4D42. */ + uint32_t fileSize; /* Size of the BMP file in bytes (unreliable). */ + uint32_t reserved; /* Application-specific. */ + uint32_t imageOffset; /* Offset to bitmap data. */ +}; + +#define BMP_MAGIC 0x4D42 /* The starting key that marks the file as a BMP. */ + +enum _BMP_COMPRESSION { + kBMP_RGB = 0, /* No compression. */ + kBMP_RLE8 = 1, /* Can only be used with 8-bit bitmaps. */ + kBMP_RLE4 = 2, /* Can only be used with 4-bit bitmaps. */ + kBMP_BITFIELDS = 3, /* Can only be used with 16/32-bit bitmaps. */ + kBMP_JPEG = 4, /* Bitmap contains a JPEG image. */ + kBMP_PNG = 5 /* Bitmap contains a PNG image. */ +}; + +typedef uint32_t BMP_COMPRESSION; + +/* + * Windows 3 Header + */ +struct BITMAP_INFO_HEADER { + uint32_t headerSize; /* The size of this header (40 bytes). */ + int32_t width; /* The bitmap width in pixels. */ + int32_t height; /* The bitmap height in pixels. */ + /* (A negative value denotes that the image + * is flipped.) */ + uint16_t colorPlanes; /* The number of color planes; must be 1. */ + uint16_t bitsPerPixel; /* The color depth of the image (1, 4, 8, 16, + * 24, or 32). */ + BMP_COMPRESSION compression; /* The compression method being used. */ + uint32_t imageSize; /* Size of the bitmap in bytes (unreliable).*/ + int32_t xRes; /* The horizontal resolution (unreliable). */ + int32_t yRes; /* The vertical resolution (unreliable). */ + uint32_t colorsUsed; /* The number of colors in the color table, + * or 0 to default to 2^n. */ + uint32_t colorsImportant; /* Colors important for displaying bitmap, + * or 0 when every color is equally important; + * ignored. */ +}; + +/* + * OS/2 v1 Header + */ +struct BITMAP_CORE_HEADER { + uint32_t headerSize; /* The size of this header (12 bytes). */ + uint16_t width; /* The bitmap width in pixels. */ + uint16_t height; /* The bitmap height in pixels. */ + uint16_t colorPlanes; /* The number of color planes; must be 1. */ + uint16_t bitsPerPixel; /* Color depth of the image (1, 4, 8, or 24). */ +}; + +#pragma pack(pop) /* Let the compiler do what it wants now. */ + +/* BMP files are always saved in little endian format (x86), so we need to + * convert them if we're not on a little endian machine (e.g., ARM & ppc). */ + +#if __BYTE_ORDER == __BIG_ENDIAN + +/* Converts bitmap file header from to and from little endian, if and only if + * host is big endian. */ +static void convertBitmapFileHeader(struct BITMAP_FILE_HEADER *header) +{ + header->magic = swapLittleAndHost16(header->magic); + swapLittleAndHost32(header->fileSize); + swapLittleAndHost32(header->reserved); + swapLittleAndHost32(header->imageOffset); +} + +/* Converts bitmap info header from to and from little endian, if and only if + * host is big endian. */ +static void convertBitmapInfoHeader(struct BITMAP_INFO_HEADER *header) +{ + header->headerSize = swapLittleAndHost32(header->headerSize); + header->width = swapLittleAndHost32(header->width); + header->height = swapLittleAndHost32(header->height); + header->colorPlanes = swapLittleAndHost16(header->colorPlanes); + header->bitsPerPixel = swapLittleAndHost16(header->bitsPerPixel); + header->compression = swapLittleAndHost32(header->compression); + header->imageSize = swapLittleAndHost32(header->imageSize); + header->xRes = swapLittleAndHost32(header->xRes); + header->yRes = swapLittleAndHost32(header->yRes); + header->colorsUsed = swapLittleAndHost32(header->colorsUsed); + header->colorsImportant = swapLittleAndHost32(header->colorsImportant); +} + +#elif __BYTE_ORDER == __LITTLE_ENDIAN + /* No conversion necessary if we are already little endian. */ + #define convertBitmapFileHeader(header) + #define convertBitmapInfoHeader(header) +#endif + +/* Returns newly alloc'd image data from bitmap file. The current position of + * the file must be at the start of the image before calling this. */ +static uint8_t *readImageData(FILE *fp, size_t width, size_t height, + uint8_t bytesPerPixel, size_t bytewidth); + +/* Copys image buffer from |bitmap| to |dest| in BGR format. */ +static void copyBGRDataFromMMBitmap(MMBitmapRef bitmap, uint8_t *dest); + +const char *MMBMPReadErrorString(MMIOError error) +{ + switch (error) { + case kBMPAccessError: + return "Could not open file"; + case kBMPInvalidKeyError: + return "Not a BMP file"; + case kBMPUnsupportedHeaderError: + return "Unsupported BMP header"; + case kBMPInvalidColorPanesError: + return "Invalid number of color panes in BMP file"; + case kBMPUnsupportedColorDepthError: + return "Unsupported color depth in BMP file"; + case kBMPUnsupportedCompressionError: + return "Unsupported file compression in BMP file"; + case kBMPInvalidPixelDataError: + return "Could not read BMP pixel data"; + default: + return NULL; + } +} + +MMBitmapRef newMMBitmapFromBMP(const char *path, MMBMPReadError *err) +{ + FILE *fp; + struct BITMAP_FILE_HEADER fileHeader = {0}; /* Initialize elements to 0. */ + struct BITMAP_INFO_HEADER dibHeader = {0}; + uint32_t headerSize = 0; + uint8_t bytesPerPixel; + size_t bytewidth; + uint8_t *imageBuf; + + if ((fp = fopen(path, "rb")) == NULL) { + if (err != NULL) *err = kBMPAccessError; + return NULL; + } + + /* Initialize error code to generic value. */ + if (err != NULL) *err = kBMPGenericError; + + if (fread(&fileHeader, sizeof(fileHeader), 1, fp) == 0) goto bail; + + /* Convert from little-endian if it's not already. */ + convertBitmapFileHeader(&fileHeader); + + /* First two bytes should always be 0x4D42. */ + if (fileHeader.magic != BMP_MAGIC) { + if (err != NULL) *err = kBMPInvalidKeyError; + goto bail; + } + + /* Get header size. */ + if (fread(&headerSize, sizeof(headerSize), 1, fp) == 0) goto bail; + headerSize = swapLittleAndHost32(headerSize); + + /* Back up before reading header. */ + if (fseek(fp, -(long)sizeof(headerSize), SEEK_CUR) < 0) goto bail; + + if (headerSize == 12) { /* OS/2 v1 header */ + struct BITMAP_CORE_HEADER coreHeader = {0}; + if (fread(&coreHeader, sizeof(coreHeader), 1, fp) == 0) goto bail; + + dibHeader.width = coreHeader.width; + dibHeader.height = coreHeader.height; + dibHeader.colorPlanes = coreHeader.colorPlanes; + dibHeader.bitsPerPixel = coreHeader.bitsPerPixel; + } else if (headerSize == 40 || headerSize == 108 || headerSize == 124) { + /* Windows v3/v4/v5 header */ + /* Read only the common part (v3) and skip over the rest. */ + if (fread(&dibHeader, sizeof(dibHeader), 1, fp) == 0) goto bail; + } else { + if (err != NULL) *err = kBMPUnsupportedHeaderError; + goto bail; + } + + convertBitmapInfoHeader(&dibHeader); + + if (dibHeader.colorPlanes != 1) { + if (err != NULL) *err = kBMPInvalidColorPanesError; + goto bail; + } + + /* Currently only 24-bit and 32-bit are supported. */ + if (dibHeader.bitsPerPixel != 24 && dibHeader.bitsPerPixel != 32) { + if (err != NULL) *err = kBMPUnsupportedColorDepthError; + goto bail; + } + + if (dibHeader.compression != kBMP_RGB) { + if (err != NULL) *err = kBMPUnsupportedCompressionError; + goto bail; + } + + /* This can happen because we don't fully parse Windows v4/v5 headers. */ + if (ftell(fp) != (long)fileHeader.imageOffset) { + fseek(fp, fileHeader.imageOffset, SEEK_SET); + } + + /* Get bytes per row, including padding. */ + bytesPerPixel = dibHeader.bitsPerPixel / 8; + bytewidth = ADD_PADDING(dibHeader.width * bytesPerPixel); + + imageBuf = readImageData(fp, dibHeader.width, abs(dibHeader.height), + bytesPerPixel, bytewidth); + fclose(fp); + + if (imageBuf == NULL) { + if (err != NULL) *err = kBMPInvalidPixelDataError; + return NULL; + } + + /* A negative height indicates that the image is flipped. + * + * We store our bitmaps as "flipped" according to the BMP format; i.e., (0, 0) + * is the top left, not bottom left. So we only need to flip the bitmap if + * the height is NOT negative. */ + if (dibHeader.height < 0) { + dibHeader.height = -dibHeader.height; + } else { + flipBitmapData(imageBuf, dibHeader.width, dibHeader.height, bytewidth); + } + + return createMMBitmap(imageBuf, dibHeader.width, dibHeader.height, + bytewidth, (uint8_t)dibHeader.bitsPerPixel, + bytesPerPixel); + +bail: + fclose(fp); + return NULL; +} + +uint8_t *createBitmapData(MMBitmapRef bitmap, size_t *len) +{ + /* BMP files are always aligned to 4 bytes. */ + const size_t bytewidth = ((bitmap->width * bitmap->bytesPerPixel) + 3) & ~3; + + const size_t imageSize = bytewidth * bitmap->height; + struct BITMAP_FILE_HEADER *fileHeader; + struct BITMAP_INFO_HEADER *dibHeader; + + /* Should always be 54. */ + const size_t imageOffset = sizeof(*fileHeader) + sizeof(*dibHeader); + uint8_t *data; + const size_t dataLen = imageOffset + imageSize; + + data = calloc(1, dataLen); + if (data == NULL) return NULL; + + /* Save top header. */ + fileHeader = (struct BITMAP_FILE_HEADER *)data; + fileHeader->magic = BMP_MAGIC; + fileHeader->fileSize = (uint32_t)(sizeof(*dibHeader) + imageSize); + fileHeader->imageOffset = (uint32_t)imageOffset; + + /* BMP files are always stored as little-endian, so we need to convert back + * if necessary. */ + convertBitmapFileHeader(fileHeader); + + /* Copy Windows v3 header. */ + dibHeader = (struct BITMAP_INFO_HEADER *)(data + sizeof(*fileHeader)); + dibHeader->headerSize = sizeof(*dibHeader); /* Should always be 40. */ + dibHeader->width = (int32_t)bitmap->width; + dibHeader->height = -(int32_t)bitmap->height; /* Our bitmaps are "flipped". */ + dibHeader->colorPlanes = 1; + dibHeader->bitsPerPixel = bitmap->bitsPerPixel; + dibHeader->compression = kBMP_RGB; /* Don't save with compression. */ + dibHeader->imageSize = (uint32_t)imageSize; + + convertBitmapInfoHeader(dibHeader); + + /* Lastly, copy the pixel data. */ + copyBGRDataFromMMBitmap(bitmap, data + imageOffset); + + if (len != NULL) *len = dataLen; + return data; +} + +int saveMMBitmapAsBMP(MMBitmapRef bitmap, const char *path) +{ + FILE *fp; + size_t dataLen; + uint8_t *data; + + if ((fp = fopen(path, "wb")) == NULL) return -1; + + if ((data = createBitmapData(bitmap, &dataLen)) == NULL) { + fclose(fp); + return -1; + } + + if (fwrite(data, dataLen, 1, fp) == 0) { + free(data); + fclose(fp); + return -1; + } + + free(data); + fclose(fp); + return 0; +} + +static uint8_t *readImageData(FILE *fp, size_t width, size_t height, + uint8_t bytesPerPixel, size_t bytewidth) +{ + size_t imageSize = bytewidth * height; + uint8_t *imageBuf = calloc(1, imageSize); + + if (MMRGB_IS_BGR && (bytewidth % 4) == 0) { /* No conversion needed. */ + if (fread(imageBuf, imageSize, 1, fp) == 0) { + free(imageBuf); + return NULL; + } + } else { /* Convert from BGR with 4-byte alignment. */ + uint8_t *row = malloc(bytewidth); + size_t y; + const size_t bmp_bytewidth = (width * bytesPerPixel + 3) & ~3; + + if (row == NULL) return NULL; + assert(bmp_bytewidth <= bytewidth); + + /* Read image data row by row. */ + for (y = 0; y < height; ++y) { + const size_t rowOffset = y * bytewidth; + size_t x; + uint8_t *rowptr = row; + if (fread(row, bmp_bytewidth, 1, fp) == 0) { + free(imageBuf); + free(row); + return NULL; + } + + for (x = 0; x < width; ++x) { + const size_t colOffset = x * bytesPerPixel; + MMRGBColor *color = (MMRGBColor *)(imageBuf + + rowOffset + colOffset); + + /* BMP files are stored in BGR format. */ + color->blue = rowptr[0]; + color->green = rowptr[1]; + color->red = rowptr[2]; + rowptr += bytesPerPixel; + } + } + + free(row); + } + + return imageBuf; +} + +static void copyBGRDataFromMMBitmap(MMBitmapRef bitmap, uint8_t *dest) +{ + if (MMRGB_IS_BGR && (bitmap->bytewidth % 4) == 0) { /* No conversion needed. */ + memcpy(dest, bitmap->imageBuffer, bitmap->bytewidth * bitmap->height); + } else { /* Convert to RGB with other-than-4-byte alignment. */ + const size_t bytewidth = (bitmap->width * bitmap->bytesPerPixel + 3) & ~3; + size_t y; + + /* Copy image data row by row. */ + for (y = 0; y < bitmap->height; ++y) { + uint8_t *rowptr = dest + (y * bytewidth); + size_t x; + for (x = 0; x < bitmap->width; ++x) { + MMRGBColor *color = MMRGBColorRefAtPoint(bitmap, x, y); + + /* BMP files are stored in BGR format. */ + rowptr[0] = color->blue; + rowptr[1] = color->green; + rowptr[2] = color->red; + + rowptr += bitmap->bytesPerPixel; + } + } + } +} + +/* Perform an in-place swap from Quadrant 1 to Quadrant III format (upside-down + * PostScript/GL to right side up QD/CG raster format) We do this in-place, + * which requires more copying, but will touch only half the pages. + * + * This is blatantly copied from Apple's glGrab example code. */ +void flipBitmapData(void *data, size_t width, size_t height, size_t bytewidth) +{ + size_t top, bottom; + void *topP; + void *bottomP; + void *tempbuf; + + if (height <= 1) return; /* No flipping necessary if height is <= 1. */ + + top = 0; + bottom = height - 1; + tempbuf = malloc(bytewidth); + if (tempbuf == NULL) return; + + while (top < bottom) { + topP = (void *)((top * bytewidth) + (intptr_t)data); + bottomP = (void *)((bottom * bytewidth) + (intptr_t)data); + + /* Save and swap scanlines. + * Does a simple in-place exchange with a temp buffer. */ + memcpy(tempbuf, topP, bytewidth); + memcpy(topP, bottomP, bytewidth); + memcpy(bottomP, tempbuf, bytewidth); + + ++top; + --bottom; + } + free(tempbuf); +} diff --git a/base/deadbeef_rand.h b/base/deadbeef_rand.h new file mode 100644 index 0000000..ec569a2 --- /dev/null +++ b/base/deadbeef_rand.h @@ -0,0 +1,35 @@ +#ifndef DEADBEEF_RAND_H +#define DEADBEEF_RAND_H + +#include + +#define DEADBEEF_MAX UINT32_MAX + +/* Dead Beef Random Number Generator + * From: http://inglorion.net/software/deadbeef_rand + * A fast, portable psuedo-random number generator by BJ Amsterdam Zuidoost. + * Stated in license terms: "Feel free to use the code in your own software." */ + +/* Generates a random number between 0 and DEADBEEF_MAX. */ +uint32_t deadbeef_rand(void); + +/* Seeds with the given integer. */ +void deadbeef_srand(uint32_t x); + +/* Generates seed from the current time. */ +uint32_t deadbeef_generate_seed(void); + +/* Seeds with the above function. */ +#define deadbeef_srand_time() deadbeef_srand(deadbeef_generate_seed()) + +/* Returns random double in the range [a, b). + * Taken directly from the rand() man page. */ +#define DEADBEEF_UNIFORM(a, b) \ + ((a) + (deadbeef_rand() / (((double)DEADBEEF_MAX / (b - a) + 1)))) + +/* Returns random integer in the range [a, b). + * Also taken from the rand() man page. */ +#define DEADBEEF_RANDRANGE(a, b) \ + (uint32_t)DEADBEEF_UNIFORM(a, b) + +#endif /* DEADBEEF_RAND_H */ diff --git a/base/deadbeef_rand_init.h b/base/deadbeef_rand_init.h new file mode 100644 index 0000000..1de295f --- /dev/null +++ b/base/deadbeef_rand_init.h @@ -0,0 +1,27 @@ +#include "deadbeef_rand.h" +#include + +static uint32_t deadbeef_seed; +static uint32_t deadbeef_beef = 0xdeadbeef; + +uint32_t deadbeef_rand(void) +{ + deadbeef_seed = (deadbeef_seed << 7) ^ ((deadbeef_seed >> 25) + deadbeef_beef); + deadbeef_beef = (deadbeef_beef << 7) ^ ((deadbeef_beef >> 25) + 0xdeadbeef); + return deadbeef_seed; +} + +void deadbeef_srand(uint32_t x) +{ + deadbeef_seed = x; + deadbeef_beef = 0xdeadbeef; +} + +/* Taken directly from the documentation: + * http://inglorion.net/software/cstuff/deadbeef_rand/ */ +uint32_t deadbeef_generate_seed(void) +{ + uint32_t t = (uint32_t)time(NULL); + uint32_t c = (uint32_t)clock(); + return (t << 24) ^ (c << 11) ^ t ^ (size_t) &c; +} diff --git a/base/endian.h b/base/endian.h new file mode 100644 index 0000000..2bc7c52 --- /dev/null +++ b/base/endian.h @@ -0,0 +1,123 @@ +#pragma once +#ifndef ENDIAN_H +#define ENDIAN_H + +#include "os.h" + +/* + * (Mostly) cross-platform endian definitions and bit swapping macros. + * Unfortunately, there is no standard C header for this, so we just + * include the most common ones and fallback to our own custom macros. + */ + +#if defined(__linux__) /* Linux */ + #include + #include +#elif (defined(__FreeBSD__) && __FreeBSD_version >= 470000) || \ + defined(__OpenBSD__) || defined(__NetBSD__) /* (Free|Open|Net)BSD */ + #include + #define __BIG_ENDIAN BIG_ENDIAN + #define __LITTLE_ENDIAN LITTLE_ENDIAN + #define __BYTE_ORDER BYTE_ORDER +#elif defined(IS_MACOSX) || (defined(BSD) && (BSD >= 199103)) /* Other BSD */ + #include + #define __BIG_ENDIAN BIG_ENDIAN + #define __LITTLE_ENDIAN LITTLE_ENDIAN + #define __BYTE_ORDER BYTE_ORDER +#elif defined(IS_WINDOWS) /* Windows is assumed to be little endian only. */ + #define __BIG_ENDIAN 4321 + #define __LITTLE_ENDIAN 1234 + #define __BYTE_ORDER __LITTLE_ENDIAN +#endif + +/* Fallback to custom constants. */ +#if !defined(__BIG_ENDIAN) + #define __BIG_ENDIAN 4321 +#endif + +#if !defined(__LITTLE_ENDIAN) + #define __LITTLE_ENDIAN 1234 +#endif + +/* Prefer compiler flag settings if given. */ +#if defined(MM_BIG_ENDIAN) + #undef __BYTE_ORDER /* Avoid redefined macro compiler warning. */ + #define __BYTE_ORDER __BIG_ENDIAN +#elif defined(MM_LITTLE_ENDIAN) + #undef __BYTE_ORDER /* Avoid redefined macro compiler warning. */ + #define __BYTE_ORDER __LITTLE_ENDIAN +#endif + +/* Define default endian-ness. */ +#ifndef __LITTLE_ENDIAN + #define __LITTLE_ENDIAN 1234 +#endif /* __LITTLE_ENDIAN */ + +#ifndef __BIG_ENDIAN + #define __BIG_ENDIAN 4321 +#endif /* __BIG_ENDIAN */ + +#ifndef __BYTE_ORDER + #warning "Byte order not defined on your system; assuming little endian" + #define __BYTE_ORDER __LITTLE_ENDIAN +#endif /* __BYTE_ORDER */ + +#if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN + #error "__BYTE_ORDER set to unknown byte order" +#endif + +#if defined(IS_MACOSX) + #include + + /* OS X system functions. */ + #define bitswap16(i) OSSwapInt16(i) + #define bitswap32(i) OSSwapInt32(i) + #define swapLittleAndHost32(i) OSSwapLittleToHostInt32(i) + #define swapLittleAndHost16(i) OSSwapLittleToHostInt16(i) +#else + #ifndef bitswap16 + #if defined(bswap16) + #define bitswap16(i) bswap16(i) /* FreeBSD system function */ + #elif defined(bswap_16) + #define bitswap16(i) bswap_16(i) /* Linux system function */ + #else /* Default macro */ + #define bitswap16(i) (((uint16_t)(i) & 0xFF00) >> 8) | \ + (((uint16_t)(i) & 0x00FF) << 8) + #endif + #endif /* bitswap16 */ + + #ifndef bitswap32 + #if defined(bswap32) + #define bitswap32(i) bswap32(i) /* FreeBSD system function. */ + #elif defined(bswap_32) + #define bitswap32(i) bswap_32(i) /* Linux system function. */ + #else /* Default macro */ + #define bitswap32(i) (((uint32_t)(i) & 0xFF000000) >> 24) | \ + ((uint32_t)((i) & 0x00FF0000) >> 8) | \ + ((uint32_t)((i) & 0x0000FF00) << 8) | \ + ((uint32_t)((i) & 0x000000FF) << 24) + #endif + #endif /* bitswap32 */ +#endif + +#if __BYTE_ORDER == __BIG_ENDIAN + /* Little endian to/from host byte order (big endian). */ + #ifndef swapLittleAndHost16 + #define swapLittleAndHost16(i) bitswap16(i) + #endif /* swapLittleAndHost16 */ + + #ifndef swapLittleAndHost32 + #define swapLittleAndHost32(i) bitswap32(i) + #endif /* swapLittleAndHost32 */ +#elif __BYTE_ORDER == __LITTLE_ENDIAN + /* We are already little endian, so no conversion is needed. */ + #ifndef swapLittleAndHost16 + #define swapLittleAndHost16(i) i + #endif /* swapLittleAndHost16 */ + + #ifndef swapLittleAndHost32 + #define swapLittleAndHost32(i) i + #endif /* swapLittleAndHost32 */ +#endif + +#endif /* ENDIAN_H */ diff --git a/base/inline_keywords.h b/base/inline_keywords.h new file mode 100644 index 0000000..a86b426 --- /dev/null +++ b/base/inline_keywords.h @@ -0,0 +1,15 @@ +#pragma once + +/* A complicated, portable model for declaring inline functions in + * header files. */ +#if !defined(H_INLINE) + #if defined(__GNUC__) + #define H_INLINE static __inline__ __attribute__((always_inline)) + #elif defined(__MWERKS__) || defined(__cplusplus) + #define H_INLINE static inline + #elif defined(_MSC_VER) + #define H_INLINE static __inline + #elif TARGET_OS_WIN32 + #define H_INLINE static __inline__ + #endif +#endif /* H_INLINE */ diff --git a/base/io.c b/base/io.c new file mode 100644 index 0000000..bf86359 --- /dev/null +++ b/base/io.c @@ -0,0 +1,79 @@ +#include "io.h" +#include "os.h" +#include "bmp_io.h" +#include "png_io.h" +#include /* For fputs() */ +#include /* For strcmp() */ +#include /* For tolower() */ + +const char *getExtension(const char *fname, size_t len) +{ + if (fname == NULL || len <= 0) return NULL; + + while (--len > 0 && fname[len] != '.' && fname[len] != '\0') + ; + + return fname + len + 1; +} + +MMImageType imageTypeFromExtension(const char *extension) +{ + char ext[4]; + const size_t maxlen = sizeof(ext) / sizeof(ext[0]); + size_t i; + + for (i = 0; extension[i] != '\0'; ++i) { + if (i >= maxlen) return kInvalidImageType; + ext[i] = tolower(extension[i]); + } + ext[i] = '\0'; + + if (strcmp(ext, "png") == 0) { + return kPNGImageType; + } else if (strcmp(ext, "bmp") == 0) { + return kBMPImageType; + } else { + return kInvalidImageType; + } +} + +MMBitmapRef newMMBitmapFromFile(const char *path, + MMImageType type, + MMIOError *err) +{ + switch (type) { + case kBMPImageType: + return newMMBitmapFromBMP(path, err); + case kPNGImageType: + return newMMBitmapFromPNG(path, err); + default: + if (err != NULL) *err = kMMIOUnsupportedTypeError; + return NULL; + } +} + +int saveMMBitmapToFile(MMBitmapRef bitmap, + const char *path, + MMImageType type) +{ + switch (type) { + case kBMPImageType: + return saveMMBitmapAsBMP(bitmap, path); + case kPNGImageType: + return saveMMBitmapAsPNG(bitmap, path); + default: + return -1; + } +} + +const char *MMIOErrorString(MMImageType type, MMIOError error) +{ + switch (type) { + case kBMPImageType: + return MMBMPReadErrorString(error); + case kPNGImageType: + return MMPNGReadErrorString(error); + default: + return "Unsupported image type"; + } +} diff --git a/base/io.h b/base/io.h new file mode 100644 index 0000000..47eb974 --- /dev/null +++ b/base/io.h @@ -0,0 +1,46 @@ +#pragma once +#ifndef IO_H +#define IO_H + +#include "MMBitmap.h" +#include +#include + + +enum _MMImageType { + kInvalidImageType = 0, + kPNGImageType, + kBMPImageType /* Currently only PNG and BMP are supported. */ +}; + +typedef uint16_t MMImageType; + +enum _MMIOError { + kMMIOUnsupportedTypeError = 0 +}; + +typedef uint16_t MMIOError; + +const char *getExtension(const char *fname, size_t len); + +/* Returns best guess at the MMImageType based on a file extension, or + * |kInvalidImageType| if no matching type was found. */ +MMImageType imageTypeFromExtension(const char *ext); + +/* Attempts to parse the file of the given type at the given path. + * |filepath| is an ASCII string describing the absolute POSIX path. + * Returns new bitmap (to be destroy()'d by caller) on success, NULL on error. + * If |error| is non-NULL, it will be set to the error code on return. + */ +MMBitmapRef newMMBitmapFromFile(const char *path, MMImageType type, MMIOError *err); + +/* Saves |bitmap| to a file of the given type at the given path. + * |filepath| is an ASCII string describing the absolute POSIX path. + * Returns 0 on success, -1 on error. */ +int saveMMBitmapToFile(MMBitmapRef bitmap, const char *path, MMImageType type); + +/* Returns description of given error code. + * Returned string is constant and hence should not be freed. */ +const char *MMIOErrorString(MMImageType type, MMIOError error); + +#endif /* IO_H */ diff --git a/base/microsleep.h b/base/microsleep.h new file mode 100644 index 0000000..bbb8a4f --- /dev/null +++ b/base/microsleep.h @@ -0,0 +1,40 @@ +#pragma once +#ifndef MICROSLEEP_H +#define MICROSLEEP_H + +#include "os.h" +#include "inline_keywords.h" + +#if !defined(IS_WINDOWS) + /* Make sure nanosleep gets defined even when using C89. */ + #if !defined(__USE_POSIX199309) || !__USE_POSIX199309 + #define __USE_POSIX199309 1 + #endif + + #include /* For nanosleep() */ +#endif + +/* + * A more widely supported alternative to usleep(), based on Sleep() in Windows + * and nanosleep() everywhere else. + * + * Pauses execution for the given amount of milliseconds. + */ +H_INLINE void microsleep(double milliseconds) +{ +#if defined(IS_WINDOWS) + Sleep((DWORD)milliseconds); /* (Unfortunately truncated to a 32-bit integer.) */ +#else + /* Technically, nanosleep() is not an ANSI function, but it is the most + * supported precise sleeping function I can find. + * + * If it is really necessary, it may be possible to emulate this with some + * hack using select() in the future if we really have to. */ + struct timespec sleepytime; + sleepytime.tv_sec = milliseconds / 1000; + sleepytime.tv_nsec = (milliseconds - (sleepytime.tv_sec * 1000)) * 1000000; + nanosleep(&sleepytime, NULL); +#endif +} + +#endif /* MICROSLEEP_H */ diff --git a/base/ms_stdbool.h b/base/ms_stdbool.h new file mode 100644 index 0000000..a655445 --- /dev/null +++ b/base/ms_stdbool.h @@ -0,0 +1,27 @@ +#pragma once +#if !defined(MS_STDBOOL_H) && \ + (!defined(__bool_true_false_are_defined) || __bool_true_false_are_defined) +#define MS_STDBOOL_H + +#ifndef _MSC_VER + #error "Use this header only with Microsoft Visual C++ compilers!" +#endif /* _MSC_VER */ + +#define __bool_true_false_are_defined 1 + +#ifndef __cplusplus + + #if defined(true) || defined(false) || defined(bool) + #error "Boolean type already defined" + #endif + + enum { + false = 0, + true = 1 + }; + + typedef unsigned char bool; + +#endif /* !__cplusplus */ + +#endif /* MS_STDBOOL_H */ diff --git a/base/ms_stdint.h b/base/ms_stdint.h new file mode 100644 index 0000000..24c33ef --- /dev/null +++ b/base/ms_stdint.h @@ -0,0 +1,224 @@ +/* ISO C9x compliant stdint.h for Microsoft Visual Studio + * Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 + * + * Copyright (c) 2006-2008 Alexander Chemeris + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _MSC_VER + #error "Use this header only with Microsoft Visual C++ compilers!" +#endif /* _MSC_VER */ + +#ifndef MSC_STDINT_H +#define MSC_STDINT_H + +#if _MSC_VER > 1000 + #pragma once +#endif + +#include + +/* For Visual Studio 6 in C++ mode and for many Visual Studio versions when + * compiling for ARM we should wrap include with 'extern "C++" {}' + * or compiler give many errors like this: */ +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + #include +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +/* Define _W64 macros to mark types changing their size, like intptr_t. */ +#ifndef _W64 + #if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 + #define _W64 __w64 + #else + #define _W64 + #endif +#endif + + +/* 7.18.1 Integer types */ + +/* 7.18.1.1 Exact-width integer types */ + +/* Visual Studio 6 and Embedded Visual C++ 4 doesn't + * realize that, e.g. char has the same size as __int8 + * so we give up on __intX for them. */ +#if _MSC_VER < 1300 + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +/* 7.18.1.2 Minimum-width integer types */ +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +/* 7.18.1.3 Fastest minimum-width integer types */ +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +/* 7.18.1.4 Integer types capable of holding object pointers */ +#if defined(_WIN64) + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif /* _WIN64 ] */ + +/* 7.18.1.5 Greatest-width integer types */ +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + +/* 7.18.2 Limits of specified-width integer types */ + +/* See footnote 220 at page 257 and footnote 221 at page 259 */ +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) + + /* 7.18.2.1 Limits of exact-width integer types */ + #define INT8_MIN ((int8_t)_I8_MIN) + #define INT8_MAX _I8_MAX + #define INT16_MIN ((int16_t)_I16_MIN) + #define INT16_MAX _I16_MAX + #define INT32_MIN ((int32_t)_I32_MIN) + #define INT32_MAX _I32_MAX + #define INT64_MIN ((int64_t)_I64_MIN) + #define INT64_MAX _I64_MAX + #define UINT8_MAX _UI8_MAX + #define UINT16_MAX _UI16_MAX + #define UINT32_MAX _UI32_MAX + #define UINT64_MAX _UI64_MAX + + /* 7.18.2.2 Limits of minimum-width integer types */ + #define INT_LEAST8_MIN INT8_MIN + #define INT_LEAST8_MAX INT8_MAX + #define INT_LEAST16_MIN INT16_MIN + #define INT_LEAST16_MAX INT16_MAX + #define INT_LEAST32_MIN INT32_MIN + #define INT_LEAST32_MAX INT32_MAX + #define INT_LEAST64_MIN INT64_MIN + #define INT_LEAST64_MAX INT64_MAX + #define UINT_LEAST8_MAX UINT8_MAX + #define UINT_LEAST16_MAX UINT16_MAX + #define UINT_LEAST32_MAX UINT32_MAX + #define UINT_LEAST64_MAX UINT64_MAX + + /* 7.18.2.4 Limits of integer types capable of holding object pointers */ + #if defined(_WIN64) + #define INTPTR_MIN INT64_MIN + #define INTPTR_MAX INT64_MAX + #define UINTPTR_MAX UINT64_MAX + #else + #define INTPTR_MIN INT32_MIN + #define INTPTR_MAX INT32_MAX + #define UINTPTR_MAX UINT32_MAX + #endif + + /* 7.18.3 Limits of other integer types */ + + #if defined(_WIN64) + #define PTRDIFF_MIN _I64_MIN + #define PTRDIFF_MAX _I64_MAX + #else + #define PTRDIFF_MIN _I32_MIN + #define PTRDIFF_MAX _I32_MAX + #endif /* _WIN64 */ + + #define SIG_ATOMIC_MIN INT_MIN + #define SIG_ATOMIC_MAX INT_MAX + + #ifndef SIZE_MAX + #if defined(_WIN64) + #define SIZE_MAX _UI64_MAX + #else + #define SIZE_MAX _UI32_MAX + #endif + #endif + + /* WCHAR_MIN and WCHAR_MAX are also defined in */ + #ifndef WCHAR_MIN + #define WCHAR_MIN 0 + #endif /* WCHAR_MIN */ + + #ifndef WCHAR_MAX + #define WCHAR_MAX _UI16_MAX + #endif /* WCHAR_MAX */ + + #define WINT_MIN 0 + #define WINT_MAX _UI16_MAX +#endif /* __STDC_LIMIT_MACROS */ + + +/* 7.18.4 Limits of other integer types */ + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) /* See footnote 224 at page 260 */ + + /* 7.18.4.1 Macros for minimum-width integer constants */ + + #define INT8_C(val) val##i8 + #define INT16_C(val) val##i16 + #define INT32_C(val) val##i32 + #define INT64_C(val) val##i64 + + #define UINT8_C(val) val##ui8 + #define UINT16_C(val) val##ui16 + #define UINT32_C(val) val##ui32 + #define UINT64_C(val) val##ui64 + + /* 7.18.4.2 Macros for greatest-width integer constants */ + #define INTMAX_C INT64_C + #define UINTMAX_C UINT64_C + +#endif /* __STDC_CONSTANT_MACROS */ + +#endif /* MSC_STDINT_H */ diff --git a/base/os.h b/base/os.h new file mode 100644 index 0000000..59860b8 --- /dev/null +++ b/base/os.h @@ -0,0 +1,49 @@ +#pragma once +#ifndef OS_H +#define OS_H + +/* Python versions under 2.5 don't support this macro, but it's not + * terribly difficult to replicate: */ +#ifndef PyModule_AddIntMacro + #define PyModule_AddIntMacro(module, macro) \ + PyModule_AddIntConstant(module, #macro, macro) +#endif /* PyModule_AddIntMacro */ + +#if !defined(IS_MACOSX) && defined(__APPLE__) && defined(__MACH__) + #define IS_MACOSX +#endif /* IS_MACOSX */ + +#if !defined(IS_WINDOWS) && (defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32__) || defined(__WINDOWS__)) + #define IS_WINDOWS +#endif /* IS_WINDOWS */ + +#if !defined(USE_X11) && !defined(NUSE_X11) && !defined(IS_MACOSX) && !defined(IS_WINDOWS) + #define USE_X11 +#endif /* USE_X11 */ + +#if defined(IS_WINDOWS) + #define STRICT /* Require use of exact types. */ + #define WIN32_LEAN_AND_MEAN 1 /* Speed up compilation. */ + #include +#elif !defined(IS_MACOSX) && !defined(USE_X11) + #error "Sorry, this platform isn't supported yet!" +#endif + +/* Interval to align by for large buffers (e.g. bitmaps). */ +/* Must be a power of 2. */ +#ifndef BYTE_ALIGN + #define BYTE_ALIGN 4 /* Bytes to align pixel buffers to. */ + /* #include */ + /* #define BYTE_ALIGN (sizeof(size_t)) */ +#endif /* BYTE_ALIGN */ + +#if BYTE_ALIGN == 0 + /* No alignment needed. */ + #define ADD_PADDING(width) (width) +#else + /* Aligns given width to padding. */ + #define ADD_PADDING(width) (BYTE_ALIGN + (((width) - 1) & ~(BYTE_ALIGN - 1))) +#endif + +#endif /* OS_H */ diff --git a/base/rgb.h b/base/rgb.h new file mode 100644 index 0000000..7a2ef5c --- /dev/null +++ b/base/rgb.h @@ -0,0 +1,103 @@ +#pragma once +#ifndef RGB_H +#define RGB_H + +#include /* For abs() */ +#include +#include "inline_keywords.h" /* For H_INLINE */ +#include + + +/* RGB colors in MMBitmaps are stored as BGR for convenience in converting + * to/from certain formats (mainly OpenGL). + * + * It is best not to rely on the order (simply use rgb.{blue,green,red} to + * access values), but some situations (e.g., glReadPixels) require one to + * do so. In that case, check to make sure to use MMRGB_IS_BGR for future + * compatibility. */ + +/* #define MMRGB_IS_BGR (offsetof(MMRGBColor, red) > offsetof(MMRGBColor, blue)) */ +#define MMRGB_IS_BGR 1 + +struct _MMRGBColor { + uint8_t blue; + uint8_t green; + uint8_t red; +}; + +typedef struct _MMRGBColor MMRGBColor; + +/* MMRGBHex is a hexadecimal color value, akin to HTML's, in the form 0xRRGGBB + * where RR is the red value expressed as hexadecimal, GG is the green value, + * and BB is the blue value. */ +typedef uint32_t MMRGBHex; + +#define MMRGBHEX_MIN 0x000000 +#define MMRGBHEX_MAX 0xFFFFFF + +/* Converts rgb color to hexadecimal value. + * |red|, |green|, and |blue| should each be of the type |uint8_t|, where the + * range is 0 - 255. */ +#define RGB_TO_HEX(red, green, blue) (((red) << 16) | ((green) << 8) | (blue)) + +/* Convenience wrapper for MMRGBColors. */ +H_INLINE MMRGBHex hexFromMMRGB(MMRGBColor rgb) +{ + return RGB_TO_HEX(rgb.red, rgb.green, rgb.blue); +} + +#define RED_FROM_HEX(hex) ((hex >> 16) & 0xFF) +#define GREEN_FROM_HEX(hex) ((hex >> 8) & 0xFF) +#define BLUE_FROM_HEX(hex) (hex & 0xFF) + +/* Converts hexadecimal color to MMRGBColor. */ +H_INLINE MMRGBColor MMRGBFromHex(MMRGBHex hex) +{ + MMRGBColor color; + color.red = RED_FROM_HEX(hex); + color.green = GREEN_FROM_HEX(hex); + color.blue = BLUE_FROM_HEX(hex); + return color; +} + +/* Check absolute equality of two RGB colors. */ +#define MMRGBColorEqualToColor(c1, c2) ((c1).red == (c2).red && \ + (c1).blue == (c2).blue && \ + (c1).green == (c2).green) + +/* Returns whether two colors are similar within the given range, |tolerance|. + * Tolerance can be in the range 0.0f - 1.0f, where 0 denotes the exact + * color and 1 denotes any color. */ +H_INLINE int MMRGBColorSimilarToColor(MMRGBColor c1, MMRGBColor c2, + float tolerance) +{ + /* Speedy case */ + if (tolerance <= 0.0f) { + return MMRGBColorEqualToColor(c1, c2); + } else { /* Otherwise, use a Euclidean space to determine similarity */ + uint8_t d1 = c1.red - c2.red; + uint8_t d2 = c1.green - c2.green; + uint8_t d3 = c1.blue - c2.blue; + return sqrt((double)(d1 * d1) + + (d2 * d2) + + (d3 * d3)) <= (tolerance * 442.0f); + } + +} + +/* Identical to MMRGBColorSimilarToColor, only for hex values. */ +H_INLINE int MMRGBHexSimilarToColor(MMRGBHex h1, MMRGBHex h2, float tolerance) +{ + if (tolerance <= 0.0f) { + return h1 == h2; + } else { + uint8_t d1 = RED_FROM_HEX(h1) - RED_FROM_HEX(h2); + uint8_t d2 = GREEN_FROM_HEX(h1) - GREEN_FROM_HEX(h2); + uint8_t d3 = BLUE_FROM_HEX(h1) - BLUE_FROM_HEX(h2); + return sqrt((double)(d1 * d1) + + (d2 * d2) + + (d3 * d3)) <= (tolerance * 442.0f); + } +} + +#endif /* RGB_H */ diff --git a/base/types.h b/base/types.h new file mode 100644 index 0000000..dde1299 --- /dev/null +++ b/base/types.h @@ -0,0 +1,69 @@ +#pragma once +#ifndef TYPES_H +#define TYPES_H + +#include "os.h" +#include "inline_keywords.h" /* For H_INLINE */ +#include + +/* Some generic, cross-platform types. */ + +struct _MMPoint { + size_t x; + size_t y; +}; + +typedef struct _MMPoint MMPoint; + +struct _MMSize { + size_t width; + size_t height; +}; + +typedef struct _MMSize MMSize; + +struct _MMRect { + MMPoint origin; + MMSize size; +}; + +typedef struct _MMRect MMRect; + +H_INLINE MMPoint MMPointMake(size_t x, size_t y) +{ + MMPoint point; + point.x = x; + point.y = y; + return point; +} + +H_INLINE MMSize MMSizeMake(size_t width, size_t height) +{ + MMSize size; + size.width = width; + size.height = height; + return size; +} + +H_INLINE MMRect MMRectMake(size_t x, size_t y, size_t width, size_t height) +{ + MMRect rect; + rect.origin = MMPointMake(x, y); + rect.size = MMSizeMake(width, height); + return rect; +} + +#define MMPointZero MMPointMake(0, 0) + +#if defined(IS_MACOSX) + +#define CGPointFromMMPoint(p) CGPointMake((CGFloat)(p).x, (CGFloat)(p).y) +#define MMPointFromCGPoint(p) MMPointMake((size_t)(p).x, (size_t)(p).y) + +#elif defined(IS_WINDOWS) + +#define MMPointFromPOINT(p) MMPointMake((size_t)p.x, (size_t)p.y) + +#endif + +#endif /* TYPES_H */ diff --git a/base/uthash.h b/base/uthash.h new file mode 100644 index 0000000..1ea0fea --- /dev/null +++ b/base/uthash.h @@ -0,0 +1,929 @@ +/* + * Copyright (c) 2003-2009, Troy D. Hanson http://uthash.sourceforge.net + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#ifndef UTHASH_H +#define UTHASH_H + +#include /* memcmp, strlen */ +#include /* ptrdiff_t */ +#include + + +#define UTHASH_VERSION 1.8 + +/* C++ requires extra stringent casting */ +#if defined __cplusplus +#define TYPEOF(x) (typeof(x)) +#else +#define TYPEOF(x) +#endif + + +#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#define uthash_free(ptr) free(ptr) /* free fcn */ + +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhe */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)hhp) - (tbl)->hho)) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_bkt,_hf_hashv; \ + out=TYPEOF(out)NULL; \ + if (head) { \ + HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ + keyptr,keylen,out); \ + } \ + } \ +} while (0) + +#if defined(HASH_BLOOM) +#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0) +#define HASH_BLOOM_MAKE(tbl) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ + memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ +} while (0); + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv); \ +} while (0); + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#else +#define HASH_BLOOM_MAKE(tbl) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#endif + +#define HASH_MAKE_TABLE(hh,head) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ + sizeof(UT_hash_table)); \ + if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl->buckets, 0, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ +} while(0) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh,head,&add->fieldname,keylen_in,add) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.next = NULL; \ + (add)->hh.key = (char*)keyptr; \ + (add)->hh.keylen = keylen_in; \ + if (!(head)) { \ + head = (add); \ + (head)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh,head); \ + } else { \ + (head)->hh.tbl->tail->next = (add); \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } \ + (head)->hh.tbl->num_items++; \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ + (add)->hh.hashv, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ + HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ + HASH_FSCK(hh,head); \ +} while(0) + +#define HASH_TO_BKT( hashv, num_bkts, bkt ) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1)); \ +} while(0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space in the table to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ +do { \ + unsigned _hd_bkt; \ + struct UT_hash_handle *_hd_hh_del; \ + if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ + uthash_free((head)->hh.tbl->buckets ); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl); \ + head = NULL; \ + } else { \ + _hd_hh_del = &((delptr)->hh); \ + if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ + (head)->hh.tbl->tail = \ + (UT_hash_handle*)((char*)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho); \ + } \ + if ((delptr)->hh.prev) { \ + ((UT_hash_handle*)((char*)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ + } else { \ + head = TYPEOF(head)((delptr)->hh.next); \ + } \ + if (_hd_hh_del->next) { \ + ((UT_hash_handle*)((char*)_hd_hh_del->next + \ + (head)->hh.tbl->hho))->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh,head); \ +} while (0) + + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ + HASH_FIND(hh,head,findstr,strlen(findstr),out) +#define HASH_ADD_STR(head,strfield,add) \ + HASH_ADD(hh,head,strfield,strlen(add->strfield),add) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#if defined(HASH_DEBUG) +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head) \ +do { \ + unsigned _bkt_i; \ + unsigned _count, _bkt_count; \ + char *_prev; \ + struct UT_hash_handle *_thh; \ + if (head) { \ + _count = 0; \ + for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ + _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("invalid hh_prev %p, actual %p\n", \ + _thh->hh_prev, _prev ); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("invalid bucket count %d, actual %d\n", \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid hh item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + /* traverse hh in app order; check next/prev integrity, count */ \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev !=(char*)(_thh->prev)) { \ + HASH_OOPS("invalid prev %p, actual %p\n", \ + _thh->prev, _prev ); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ + (head)->hh.tbl->hho) : NULL ); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid app item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#if defined(HASH_EMIT_KEYS) +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#if defined(HASH_FUNCTION) +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6 */ +#define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hb_keylen=keylen; \ + char *_hb_key=(char*)key; \ + (hashv) = 0; \ + while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \ + bkt = (hashv) & (num_bkts-1); \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _sx_i; \ + char *_hs_key=(char*)key; \ + hashv = 0; \ + for(_sx_i=0; _sx_i < keylen; _sx_i++) \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + bkt = hashv & (num_bkts-1); \ +} while (0) + +#define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _fn_i; \ + char *_hf_key=(char*)key; \ + hashv = 2166136261UL; \ + for(_fn_i=0; _fn_i < keylen; _fn_i++) \ + hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \ + bkt = hashv & (num_bkts-1); \ +} while(0); + +#define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _ho_i; \ + char *_ho_key=(char*)key; \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + char *_hj_key=(char*)key; \ + hashv = 0xfeedbeef; \ + _hj_i = _hj_j = 0x9e3779b9; \ + _hj_k = keylen; \ + while (_hj_k >= 12) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12; \ + } \ + hashv += keylen; \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ + case 5: _hj_j += _hj_key[4]; \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ +do { \ + char *_sfh_key=(char*)key; \ + uint32_t _sfh_tmp, _sfh_len = keylen; \ + \ + int _sfh_rem = _sfh_len & 3; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabe; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = (get16bits (_sfh_key+2) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= _sfh_key[sizeof (uint16_t)] << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + bkt = hashv & (num_bkts-1); \ +} while(0); + +#if defined(HASH_USING_NO_STRICT_ALIASING) +/* The MurmurHash exploits some CPU's (e.g. x86) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * So MurmurHash comes in two versions, the faster unaligned one and the slower + * aligned one. We only use the faster one on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__)) +#define HASH_MUR HASH_MUR_UNALIGNED +#else +#define HASH_MUR HASH_MUR_ALIGNED +#endif + +/* Appleby's MurmurHash fast version for unaligned-tolerant archs like i386 */ +#define HASH_MUR_UNALIGNED(key,keylen,num_bkts,hashv,bkt) \ +do { \ + const unsigned int _mur_m = 0x5bd1e995; \ + const int _mur_r = 24; \ + hashv = 0xcafebabe ^ keylen; \ + char *_mur_key = (char *)key; \ + uint32_t _mur_tmp, _mur_len = keylen; \ + \ + for (;_mur_len >= 4; _mur_len-=4) { \ + _mur_tmp = *(uint32_t *)_mur_key; \ + _mur_tmp *= _mur_m; \ + _mur_tmp ^= _mur_tmp >> _mur_r; \ + _mur_tmp *= _mur_m; \ + hashv *= _mur_m; \ + hashv ^= _mur_tmp; \ + _mur_key += 4; \ + } \ + \ + switch(_mur_len) \ + { \ + case 3: hashv ^= _mur_key[2] << 16; \ + case 2: hashv ^= _mur_key[1] << 8; \ + case 1: hashv ^= _mur_key[0]; \ + hashv *= _mur_m; \ + }; \ + \ + hashv ^= hashv >> 13; \ + hashv *= _mur_m; \ + hashv ^= hashv >> 15; \ + \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +/* Appleby's MurmurHash version for alignment-sensitive archs like Sparc */ +#define HASH_MUR_ALIGNED(key,keylen,num_bkts,hashv,bkt) \ +do { \ + const unsigned int _mur_m = 0x5bd1e995; \ + const int _mur_r = 24; \ + hashv = 0xcafebabe ^ keylen; \ + char *_mur_key = (char *)key; \ + uint32_t _mur_len = keylen; \ + int _mur_align = (int)_mur_key & 3; \ + \ + if (_mur_align && (_mur_len >= 4)) { \ + unsigned _mur_t = 0, _mur_d = 0; \ + switch(_mur_align) { \ + case 1: _mur_t |= _mur_key[2] << 16; \ + case 2: _mur_t |= _mur_key[1] << 8; \ + case 3: _mur_t |= _mur_key[0]; \ + } \ + _mur_t <<= (8 * _mur_align); \ + _mur_key += 4-_mur_align; \ + _mur_len -= 4-_mur_align; \ + int _mur_sl = 8 * (4-_mur_align); \ + int _mur_sr = 8 * _mur_align; \ + \ + for (;_mur_len >= 4; _mur_len-=4) { \ + _mur_d = *(unsigned *)_mur_key; \ + _mur_t = (_mur_t >> _mur_sr) | (_mur_d << _mur_sl); \ + unsigned _mur_k = _mur_t; \ + _mur_k *= _mur_m; \ + _mur_k ^= _mur_k >> _mur_r; \ + _mur_k *= _mur_m; \ + hashv *= _mur_m; \ + hashv ^= _mur_k; \ + _mur_t = _mur_d; \ + _mur_key += 4; \ + } \ + _mur_d = 0; \ + if(_mur_len >= _mur_align) { \ + switch(_mur_align) { \ + case 3: _mur_d |= _mur_key[2] << 16; \ + case 2: _mur_d |= _mur_key[1] << 8; \ + case 1: _mur_d |= _mur_key[0]; \ + } \ + unsigned _mur_k = (_mur_t >> _mur_sr) | (_mur_d << _mur_sl); \ + _mur_k *= _mur_m; \ + _mur_k ^= _mur_k >> _mur_r; \ + _mur_k *= _mur_m; \ + hashv *= _mur_m; \ + hashv ^= _mur_k; \ + _mur_k += _mur_align; \ + _mur_len -= _mur_align; \ + \ + switch(_mur_len) \ + { \ + case 3: hashv ^= _mur_key[2] << 16; \ + case 2: hashv ^= _mur_key[1] << 8; \ + case 1: hashv ^= _mur_key[0]; \ + hashv *= _mur_m; \ + } \ + } else { \ + switch(_mur_len) \ + { \ + case 3: _mur_d ^= _mur_key[2] << 16; \ + case 2: _mur_d ^= _mur_key[1] << 8; \ + case 1: _mur_d ^= _mur_key[0]; \ + case 0: hashv ^= (_mur_t >> _mur_sr) | (_mur_d << _mur_sl); \ + hashv *= _mur_m; \ + } \ + } \ + \ + hashv ^= hashv >> 13; \ + hashv *= _mur_m; \ + hashv ^= hashv >> 15; \ + } else { \ + for (;_mur_len >= 4; _mur_len-=4) { \ + unsigned _mur_k = *(unsigned*)_mur_key; \ + _mur_k *= _mur_m; \ + _mur_k ^= _mur_k >> _mur_r; \ + _mur_k *= _mur_m; \ + hashv *= _mur_m; \ + hashv ^= _mur_k; \ + _mur_key += 4; \ + } \ + switch(_mur_len) \ + { \ + case 3: hashv ^= _mur_key[2] << 16; \ + case 2: hashv ^= _mur_key[1] << 8; \ + case 1: hashv ^= _mur_key[0]; \ + hashv *= _mur_m; \ + } \ + \ + hashv ^= hashv >> 13; \ + hashv *= _mur_m; \ + hashv ^= hashv >> 15; \ + } \ + bkt = hashv & (num_bkts-1); \ +} while(0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* key comparison function; return 0 if keys equal */ +#define HASH_KEYCMP(a,b,len) memcmp(a,b,len) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ +out = TYPEOF(out)((head.hh_head) ? ELMT_FROM_HH(tbl,head.hh_head) : NULL); \ +while (out) { \ + if (out->hh.keylen == keylen_in) { \ + if ((HASH_KEYCMP(out->hh.key,keyptr,keylen_in)) == 0) break; \ + } \ + out= TYPEOF(out)((out->hh.hh_next) ? \ + ELMT_FROM_HH(tbl,out->hh.hh_next) : NULL); \ +} + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,addhh) \ +do { \ + head.count++; \ + (addhh)->hh_next = head.hh_head; \ + (addhh)->hh_prev = NULL; \ + if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \ + (head).hh_head=addhh; \ + if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \ + && (addhh)->tbl->noexpand != 1) { \ + HASH_EXPAND_BUCKETS((addhh)->tbl); \ + } \ +} while(0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(hh,head,hh_del) \ + (head).count--; \ + if ((head).hh_head == hh_del) { \ + (head).hh_head = hh_del->hh_next; \ + } \ + if (hh_del->hh_prev) { \ + hh_del->hh_prev->hh_next = hh_del->hh_next; \ + } \ + if (hh_del->hh_next) { \ + hh_del->hh_next->hh_prev = hh_del->hh_prev; \ + } + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(tbl) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ + memset(_he_new_buckets, 0, \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + tbl->ideal_chain_maxlen = \ + (tbl->num_items >> (tbl->log2_num_buckets+1)) + \ + ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \ + tbl->nonideal_items = 0; \ + for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ + if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ + tbl->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / \ + tbl->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \ + _he_thh; \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + tbl->num_buckets *= 2; \ + tbl->log2_num_buckets++; \ + uthash_free( tbl->buckets ); \ + tbl->buckets = _he_new_buckets; \ + tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ + (tbl->ineff_expands+1) : 0; \ + if (tbl->ineff_expands > 1) { \ + tbl->noexpand=1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ +} while(0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ + _hs_psize++; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + if (! (_hs_q) ) break; \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \ + if (_hs_psize == 0) { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \ + _hs_e = _hs_p; \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_psize--; \ + } else if (( \ + cmpfcn(TYPEOF(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ + TYPEOF(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ + ) <= 0) { \ + _hs_e = _hs_p; \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail ) { \ + _hs_tail->next = ((_hs_e) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + _hs_e->prev = ((_hs_tail) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + _hs_tail->next = NULL; \ + if ( _hs_nmerges <= 1 ) { \ + _hs_looping=0; \ + (head)->hh.tbl->tail = _hs_tail; \ + (head) = TYPEOF(head)ELMT_FROM_HH((head)->hh.tbl, _hs_list); \ + } \ + _hs_insize *= 2; \ + } \ + HASH_FSCK(hh,head); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt=NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if (src) { \ + for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh) { _last_elt_hh->next = _elt; } \ + if (!dst) { \ + dst = TYPEOF(dst)_elt; \ + HASH_MAKE_TABLE(hh_dst,dst); \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ + (dst)->hh_dst.tbl->num_items++; \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst,dst); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if (head) { \ + uthash_free((head)->hh.tbl->buckets ); \ + uthash_free((head)->hh.tbl); \ + (head)=NULL; \ + } \ +} while(0) + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) (head?(head->hh.tbl->num_items):0) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1 +#define HASH_BLOOM_SIGNATURE 0xb12220f2 + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#if defined(HASH_BLOOM) + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + char bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/base/xdisplay.h b/base/xdisplay.h new file mode 100644 index 0000000..3c8663c --- /dev/null +++ b/base/xdisplay.h @@ -0,0 +1,29 @@ +#pragma once +#ifndef XDISPLAY_H +#define XDISPLAY_H + +#include + +/* Returns the main display, closed either on exit or when closeMainDisplay() + * is invoked. This removes a bit of the overhead of calling XOpenDisplay() & + * XCloseDisplay() everytime the main display needs to be used. + * + * Note that this is almost certainly not thread safe. */ +Display *XGetMainDisplay(void); + +/* Closes the main display if it is open, or does nothing if not. */ +void XCloseMainDisplay(void); + +#ifdef __cplusplus +extern "C" +{ +#endif + +char *getXDisplay(void); +void setXDisplay(char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* XDISPLAY_H */ diff --git a/base/xdisplay_init.h b/base/xdisplay_init.h new file mode 100644 index 0000000..8f0dc8c --- /dev/null +++ b/base/xdisplay_init.h @@ -0,0 +1,55 @@ +#include "xdisplay.h" +#include /* For fputs() */ +#include /* For atexit() */ + +static Display *mainDisplay = NULL; +static int registered = 0; +static char *displayName = ":0.0"; +static int hasDisplayNameChanged = 0; + +Display *XGetMainDisplay(void) +{ + /* Close the display if displayName has changed */ + if (hasDisplayNameChanged) { + XCloseMainDisplay(); + hasDisplayNameChanged = 0; + } + + if (mainDisplay == NULL) { + /* First try the user set displayName */ + mainDisplay = XOpenDisplay(displayName); + + /* Then try using environment variable DISPLAY */ + if (mainDisplay == NULL) { + mainDisplay = XOpenDisplay(NULL); + } + + if (mainDisplay == NULL) { + fputs("Could not open main display\n", stderr); + } else if (!registered) { + atexit(&XCloseMainDisplay); + registered = 1; + } + } + + return mainDisplay; +} + +void XCloseMainDisplay(void) +{ + if (mainDisplay != NULL) { + XCloseDisplay(mainDisplay); + mainDisplay = NULL; + } +} + +char *getXDisplay(void) +{ + return displayName; +} + +void setXDisplay(char *name) +{ + displayName = strdup(name); + hasDisplayNameChanged = 1; +} diff --git a/bitmap/goBitmap.h b/bitmap/goBitmap.h new file mode 100644 index 0000000..217c74d --- /dev/null +++ b/bitmap/goBitmap.h @@ -0,0 +1,10 @@ +class BMP +{ + public: + size_t width; + size_t height; + size_t byteWidth; + uint8_t bitsPerPixel; + uint8_t bytesPerPixel; + uint8_t *image; +}; diff --git a/key/goKey.h b/key/goKey.h new file mode 100644 index 0000000..9b45ce2 --- /dev/null +++ b/key/goKey.h @@ -0,0 +1,235 @@ +#include "../base/types.h" +// #include "keycode.h" +// #include "keypress.h" +#include "keypress_init.h" +#include "keycode_init.h" + + +int keyboardDelay = 10; + +// struct KeyNames{ +// const char* name; +// MMKeyCode key; +// }; + +// static KeyNames key_names[] ={ +struct KeyNames{ + const char* name; + MMKeyCode key; +}key_names[] = { + { "backspace", K_BACKSPACE }, + { "delete", K_DELETE }, + { "enter", K_RETURN }, + { "tab", K_TAB }, + { "escape", K_ESCAPE }, + { "up", K_UP }, + { "down", K_DOWN }, + { "right", K_RIGHT }, + { "left", K_LEFT }, + { "home", K_HOME }, + { "end", K_END }, + { "pageup", K_PAGEUP }, + { "pagedown", K_PAGEDOWN }, + { "f1", K_F1 }, + { "f2", K_F2 }, + { "f3", K_F3 }, + { "f4", K_F4 }, + { "f5", K_F5 }, + { "f6", K_F6 }, + { "f7", K_F7 }, + { "f8", K_F8 }, + { "f9", K_F9 }, + { "f10", K_F10 }, + { "f11", K_F11 }, + { "f12", K_F12 }, + { "f13", K_F13 }, + { "f14", K_F14 }, + { "f15", K_F15 }, + { "f16", K_F16 }, + { "f17", K_F17 }, + { "f18", K_F18 }, + { "f19", K_F19 }, + { "f20", K_F20 }, + { "f21", K_F21 }, + { "f22", K_F22 }, + { "f23", K_F23 }, + { "f24", K_F24 }, + { "command", K_META }, + { "alt", K_ALT }, + { "control", K_CONTROL }, + { "shift", K_SHIFT }, + { "right_shift", K_RIGHTSHIFT }, + { "space", K_SPACE }, + { "printscreen", K_PRINTSCREEN }, + { "insert", K_INSERT }, + + { "audio_mute", K_AUDIO_VOLUME_MUTE }, + { "audio_vol_down", K_AUDIO_VOLUME_DOWN }, + { "audio_vol_up", K_AUDIO_VOLUME_UP }, + { "audio_play", K_AUDIO_PLAY }, + { "audio_stop", K_AUDIO_STOP }, + { "audio_pause", K_AUDIO_PAUSE }, + { "audio_prev", K_AUDIO_PREV }, + { "audio_next", K_AUDIO_NEXT }, + { "audio_rewind", K_AUDIO_REWIND }, + { "audio_forward", K_AUDIO_FORWARD }, + { "audio_repeat", K_AUDIO_REPEAT }, + { "audio_random", K_AUDIO_RANDOM }, + + { "numpad_0", K_NUMPAD_0 }, + { "numpad_1", K_NUMPAD_1 }, + { "numpad_2", K_NUMPAD_2 }, + { "numpad_3", K_NUMPAD_3 }, + { "numpad_4", K_NUMPAD_4 }, + { "numpad_5", K_NUMPAD_5 }, + { "numpad_6", K_NUMPAD_6 }, + { "numpad_7", K_NUMPAD_7 }, + { "numpad_8", K_NUMPAD_8 }, + { "numpad_9", K_NUMPAD_9 }, + + { "lights_mon_up", K_LIGHTS_MON_UP }, + { "lights_mon_down", K_LIGHTS_MON_DOWN }, + { "lights_kbd_toggle",K_LIGHTS_KBD_TOGGLE }, + { "lights_kbd_up", K_LIGHTS_KBD_UP }, + { "lights_kbd_down", K_LIGHTS_KBD_DOWN }, + + { NULL, K_NOT_A_KEY } /* end marker */ +}; + +int CheckKeyCodes(char* k, MMKeyCode *key) +{ + if (!key) return -1; + + if (strlen(k) == 1) + { + *key = keyCodeForChar(*k); + return 0; + } + + *key = K_NOT_A_KEY; + + struct KeyNames* kn = key_names; + while (kn->name) + { + if (strcmp(k, kn->name) == 0) + { + *key = kn->key; + break; + } + kn++; + } + + if (*key == K_NOT_A_KEY) + { + return -2; + } + + return 0; +} + +int CheckKeyFlags(char* f, MMKeyFlags* flags) +{ + if (!flags) return -1; + + if (strcmp(f, "alt") == 0) + { + *flags = MOD_ALT; + } + else if(strcmp(f, "command") == 0) + { + *flags = MOD_META; + } + else if(strcmp(f, "control") == 0) + { + *flags = MOD_CONTROL; + } + else if(strcmp(f, "shift") == 0) + { + *flags = MOD_SHIFT; + } + else if(strcmp(f, "none") == 0) + { + *flags = (MMKeyFlags) MOD_NONE; + } + else + { + return -2; + } + + return 0; +} + +// //If it's not an array, it should be a single string value. +// return GetFlagsFromString(value, flags); +// } + +int akeyTap(char *k){ + MMKeyFlags flags = (MMKeyFlags) MOD_NONE; + // MMKeyFlags flags = 0; + MMKeyCode key; + + // char *k; + // k = *kstr; + + switch(CheckKeyCodes(k, &key)){ + case -1: + return 1; + break; + case -2: + return 1; + break; + default: + tapKeyCode(key, flags); + microsleep(keyboardDelay); + } + + return 0; +} + +char* akeyToggle(char *k,char *d){ + MMKeyFlags flags = (MMKeyFlags) MOD_NONE; + MMKeyCode key; + + bool down; + // char *k; + // k = *kstr; + + if (d != 0){ + // char *d; + // d = *dstr; + if (strcmp(d, "down") == 0){ + down = true; + }else if (strcmp(d, "up") == 0){ + down = false; + }else{ + return "Invalid key state specified."; + } + } + + switch(CheckKeyCodes(k, &key)){ + case -1: + return "Null pointer in key code."; + break; + case -2: + return "Invalid key code specified."; + break; + default: + toggleKeyCode(key, down, flags); + microsleep(keyboardDelay); + } + + return "success"; +} + +void atypeString(char *str){ + typeString(str); +} + +void atypeStringDelayed(char *str,size_t cpm){ + + typeStringDelayed(str, cpm); +} + +void asetKeyboardDelay(size_t val){ + keyboardDelay =val; +} \ No newline at end of file diff --git a/key/keycode.h b/key/keycode.h new file mode 100644 index 0000000..e5e085f --- /dev/null +++ b/key/keycode.h @@ -0,0 +1,281 @@ +#pragma once +#ifndef KEYCODE_H +#define KEYCODE_H + +#include "../base/os.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if defined(IS_MACOSX) + +#include /* Really only need */ +#include +#import + +enum _MMKeyCode { + K_NOT_A_KEY = 9999, + K_BACKSPACE = kVK_Delete, + K_DELETE = kVK_ForwardDelete, + K_RETURN = kVK_Return, + K_TAB = kVK_Tab, + K_ESCAPE = kVK_Escape, + K_UP = kVK_UpArrow, + K_DOWN = kVK_DownArrow, + K_RIGHT = kVK_RightArrow, + K_LEFT = kVK_LeftArrow, + K_HOME = kVK_Home, + K_END = kVK_End, + K_PAGEUP = kVK_PageUp, + K_PAGEDOWN = kVK_PageDown, + K_F1 = kVK_F1, + K_F2 = kVK_F2, + K_F3 = kVK_F3, + K_F4 = kVK_F4, + K_F5 = kVK_F5, + K_F6 = kVK_F6, + K_F7 = kVK_F7, + K_F8 = kVK_F8, + K_F9 = kVK_F9, + K_F10 = kVK_F10, + K_F11 = kVK_F11, + K_F12 = kVK_F12, + K_F13 = kVK_F13, + K_F14 = kVK_F14, + K_F15 = kVK_F15, + K_F16 = kVK_F16, + K_F17 = kVK_F17, + K_F18 = kVK_F18, + K_F19 = kVK_F19, + K_F20 = kVK_F20, + K_F21 = K_NOT_A_KEY, + K_F22 = K_NOT_A_KEY, + K_F23 = K_NOT_A_KEY, + K_F24 = K_NOT_A_KEY, + K_META = kVK_Command, + K_ALT = kVK_Option, + K_CONTROL = kVK_Control, + K_SHIFT = kVK_Shift, + K_RIGHTSHIFT = kVK_RightShift, + K_CAPSLOCK = kVK_CapsLock, + K_SPACE = kVK_Space, + K_INSERT = K_NOT_A_KEY, + K_PRINTSCREEN = K_NOT_A_KEY, + + K_NUMPAD_0 = kVK_ANSI_Keypad0, + K_NUMPAD_1 = kVK_ANSI_Keypad1, + K_NUMPAD_2 = kVK_ANSI_Keypad2, + K_NUMPAD_3 = kVK_ANSI_Keypad3, + K_NUMPAD_4 = kVK_ANSI_Keypad4, + K_NUMPAD_5 = kVK_ANSI_Keypad5, + K_NUMPAD_6 = kVK_ANSI_Keypad6, + K_NUMPAD_7 = kVK_ANSI_Keypad7, + K_NUMPAD_8 = kVK_ANSI_Keypad8, + K_NUMPAD_9 = kVK_ANSI_Keypad9, + + K_AUDIO_VOLUME_MUTE = 1007, + K_AUDIO_VOLUME_DOWN = 1001, + K_AUDIO_VOLUME_UP = 1000, + K_AUDIO_PLAY = 1016, + K_AUDIO_STOP = K_NOT_A_KEY, + K_AUDIO_PAUSE = 1016, + K_AUDIO_PREV = 1018, + K_AUDIO_NEXT = 1017, + K_AUDIO_REWIND = K_NOT_A_KEY, + K_AUDIO_FORWARD = K_NOT_A_KEY, + K_AUDIO_REPEAT = K_NOT_A_KEY, + K_AUDIO_RANDOM = K_NOT_A_KEY, + + K_LIGHTS_MON_UP = 1002, + K_LIGHTS_MON_DOWN = 1003, + K_LIGHTS_KBD_TOGGLE = 1023, + K_LIGHTS_KBD_UP = 1021, + K_LIGHTS_KBD_DOWN = 1022 +}; + +typedef CGKeyCode MMKeyCode; + +#elif defined(USE_X11) + +#include +#include + +enum _MMKeyCode { + K_NOT_A_KEY = 9999, + K_BACKSPACE = XK_BackSpace, + K_DELETE = XK_Delete, + K_RETURN = XK_Return, + K_TAB = XK_Tab, + K_ESCAPE = XK_Escape, + K_UP = XK_Up, + K_DOWN = XK_Down, + K_RIGHT = XK_Right, + K_LEFT = XK_Left, + K_HOME = XK_Home, + K_END = XK_End, + K_PAGEUP = XK_Page_Up, + K_PAGEDOWN = XK_Page_Down, + K_F1 = XK_F1, + K_F2 = XK_F2, + K_F3 = XK_F3, + K_F4 = XK_F4, + K_F5 = XK_F5, + K_F6 = XK_F6, + K_F7 = XK_F7, + K_F8 = XK_F8, + K_F9 = XK_F9, + K_F10 = XK_F10, + K_F11 = XK_F11, + K_F12 = XK_F12, + K_F13 = XK_F13, + K_F14 = XK_F14, + K_F15 = XK_F15, + K_F16 = XK_F16, + K_F17 = XK_F17, + K_F18 = XK_F18, + K_F19 = XK_F19, + K_F20 = XK_F20, + K_F21 = XK_F21, + K_F22 = XK_F22, + K_F23 = XK_F23, + K_F24 = XK_F24, + K_META = XK_Super_L, + K_ALT = XK_Alt_L, + K_CONTROL = XK_Control_L, + K_SHIFT = XK_Shift_L, + K_RIGHTSHIFT = XK_Shift_R, + K_CAPSLOCK = XK_Shift_Lock, + K_SPACE = XK_space, + K_INSERT = XK_Insert, + K_PRINTSCREEN = XK_Print, + + K_NUMPAD_0 = K_NOT_A_KEY, + K_NUMPAD_1 = K_NOT_A_KEY, + K_NUMPAD_2 = K_NOT_A_KEY, + K_NUMPAD_3 = K_NOT_A_KEY, + K_NUMPAD_4 = K_NOT_A_KEY, + K_NUMPAD_5 = K_NOT_A_KEY, + K_NUMPAD_6 = K_NOT_A_KEY, + K_NUMPAD_7 = K_NOT_A_KEY, + K_NUMPAD_8 = K_NOT_A_KEY, + K_NUMPAD_9 = K_NOT_A_KEY, + + K_AUDIO_VOLUME_MUTE = XF86XK_AudioMute, + K_AUDIO_VOLUME_DOWN = XF86XK_AudioLowerVolume, + K_AUDIO_VOLUME_UP = XF86XK_AudioRaiseVolume, + K_AUDIO_PLAY = XF86XK_AudioPlay, + K_AUDIO_STOP = XF86XK_AudioStop, + K_AUDIO_PAUSE = XF86XK_AudioPause, + K_AUDIO_PREV = XF86XK_AudioPrev, + K_AUDIO_NEXT = XF86XK_AudioNext, + K_AUDIO_REWIND = XF86XK_AudioRewind, + K_AUDIO_FORWARD = XF86XK_AudioForward, + K_AUDIO_REPEAT = XF86XK_AudioRepeat, + K_AUDIO_RANDOM = XF86XK_AudioRandomPlay, + + K_LIGHTS_MON_UP = XF86XK_MonBrightnessUp, + K_LIGHTS_MON_DOWN = XF86XK_MonBrightnessDown, + K_LIGHTS_KBD_TOGGLE = XF86XK_KbdLightOnOff, + K_LIGHTS_KBD_UP = XF86XK_KbdBrightnessUp, + K_LIGHTS_KBD_DOWN = XF86XK_KbdBrightnessDown +}; + +typedef KeySym MMKeyCode; + +#elif defined(IS_WINDOWS) + +enum _MMKeyCode { + K_NOT_A_KEY = 9999, + K_BACKSPACE = VK_BACK, + K_DELETE = VK_DELETE, + K_RETURN = VK_RETURN, + K_TAB = VK_TAB, + K_ESCAPE = VK_ESCAPE, + K_UP = VK_UP, + K_DOWN = VK_DOWN, + K_RIGHT = VK_RIGHT, + K_LEFT = VK_LEFT, + K_HOME = VK_HOME, + K_END = VK_END, + K_PAGEUP = VK_PRIOR, + K_PAGEDOWN = VK_NEXT, + K_F1 = VK_F1, + K_F2 = VK_F2, + K_F3 = VK_F3, + K_F4 = VK_F4, + K_F5 = VK_F5, + K_F6 = VK_F6, + K_F7 = VK_F7, + K_F8 = VK_F8, + K_F9 = VK_F9, + K_F10 = VK_F10, + K_F11 = VK_F11, + K_F12 = VK_F12, + K_F13 = VK_F13, + K_F14 = VK_F14, + K_F15 = VK_F15, + K_F16 = VK_F16, + K_F17 = VK_F17, + K_F18 = VK_F18, + K_F19 = VK_F19, + K_F20 = VK_F20, + K_F21 = VK_F21, + K_F22 = VK_F22, + K_F23 = VK_F23, + K_F24 = VK_F24, + K_META = VK_LWIN, + K_CONTROL = VK_CONTROL, + K_SHIFT = VK_SHIFT, + K_RIGHTSHIFT = VK_RSHIFT, + K_ALT = VK_MENU, + K_CAPSLOCK = VK_CAPITAL, + K_SPACE = VK_SPACE, + K_PRINTSCREEN = VK_SNAPSHOT, + K_INSERT = VK_INSERT, + + K_NUMPAD_0 = VK_NUMPAD0, + K_NUMPAD_1 = VK_NUMPAD1, + K_NUMPAD_2 = VK_NUMPAD2, + K_NUMPAD_3 = VK_NUMPAD3, + K_NUMPAD_4 = VK_NUMPAD4, + K_NUMPAD_5 = VK_NUMPAD5, + K_NUMPAD_6 = VK_NUMPAD6, + K_NUMPAD_7 = VK_NUMPAD7, + K_NUMPAD_8 = VK_NUMPAD8, + K_NUMPAD_9 = VK_NUMPAD9, + + K_AUDIO_VOLUME_MUTE = VK_VOLUME_MUTE, + K_AUDIO_VOLUME_DOWN = VK_VOLUME_DOWN, + K_AUDIO_VOLUME_UP = VK_VOLUME_UP, + K_AUDIO_PLAY = VK_MEDIA_PLAY_PAUSE, + K_AUDIO_STOP = VK_MEDIA_STOP, + K_AUDIO_PAUSE = VK_MEDIA_PLAY_PAUSE, + K_AUDIO_PREV = VK_MEDIA_PREV_TRACK, + K_AUDIO_NEXT = VK_MEDIA_NEXT_TRACK, + K_AUDIO_REWIND = K_NOT_A_KEY, + K_AUDIO_FORWARD = K_NOT_A_KEY, + K_AUDIO_REPEAT = K_NOT_A_KEY, + K_AUDIO_RANDOM = K_NOT_A_KEY, + + K_LIGHTS_MON_UP = K_NOT_A_KEY, + K_LIGHTS_MON_DOWN = K_NOT_A_KEY, + K_LIGHTS_KBD_TOGGLE = K_NOT_A_KEY, + K_LIGHTS_KBD_UP = K_NOT_A_KEY, + K_LIGHTS_KBD_DOWN = K_NOT_A_KEY +}; + +typedef int MMKeyCode; + +#endif + +/* Returns the keyCode corresponding to the current keyboard layout for the + * given ASCII character. */ +MMKeyCode keyCodeForChar(const char c); + +#endif /* KEYCODE_H */ + +#ifdef __cplusplus +} +#endif diff --git a/key/keycode_init.h b/key/keycode_init.h new file mode 100644 index 0000000..ba47182 --- /dev/null +++ b/key/keycode_init.h @@ -0,0 +1,163 @@ +#include "keycode.h" + +#if defined(IS_MACOSX) + +#include +#include /* For kVK_ constants, and TIS functions. */ + +/* Returns string representation of key, if it is printable. + * Ownership follows the Create Rule; that is, it is the caller's + * responsibility to release the returned object. */ +CFStringRef createStringForKey(CGKeyCode keyCode); + +#elif defined(USE_X11) + +/* + * Structs to store key mappings not handled by XStringToKeysym() on some + * Linux systems. + */ + +struct XSpecialCharacterMapping { + char name; + MMKeyCode code; +}; + +struct XSpecialCharacterMapping XSpecialCharacterTable[] = { + {'~', XK_asciitilde}, + {'_', XK_underscore}, + {'[', XK_bracketleft}, + {']', XK_bracketright}, + {'!', XK_exclam}, + {'\'', XK_quotedbl}, + {'#', XK_numbersign}, + {'$', XK_dollar}, + {'%', XK_percent}, + {'&', XK_ampersand}, + {'\'', XK_quoteright}, + {'*', XK_asterisk}, + {'+', XK_plus}, + {',', XK_comma}, + {'-', XK_minus}, + {'.', XK_period}, + {'?', XK_question}, + {'<', XK_less}, + {'>', XK_greater}, + {'=', XK_equal}, + {'@', XK_at}, + {':', XK_colon}, + {';', XK_semicolon}, + {'\\', XK_backslash}, + {'`', XK_grave}, + {'{', XK_braceleft}, + {'}', XK_braceright}, + {'|', XK_bar}, + {'^', XK_asciicircum}, + {'(', XK_parenleft}, + {')', XK_parenright}, + {' ', XK_space}, + {'/', XK_slash}, + {'\t', XK_Tab}, + {'\n', XK_Return} +}; + +#endif + +MMKeyCode keyCodeForChar(const char c) +{ +#if defined(IS_MACOSX) + /* OS X does not appear to have a built-in function for this, so instead we + * have to write our own. */ + static CFMutableDictionaryRef charToCodeDict = NULL; + CGKeyCode code; + UniChar character = c; + CFStringRef charStr = NULL; + + /* Generate table of keycodes and characters. */ + if (charToCodeDict == NULL) { + size_t i; + charToCodeDict = CFDictionaryCreateMutable(kCFAllocatorDefault, + 128, + &kCFCopyStringDictionaryKeyCallBacks, + NULL); + if (charToCodeDict == NULL) return UINT16_MAX; + + /* Loop through every keycode (0 - 127) to find its current mapping. */ + for (i = 0; i < 128; ++i) { + CFStringRef string = createStringForKey((CGKeyCode)i); + if (string != NULL) { + CFDictionaryAddValue(charToCodeDict, string, (const void *)i); + CFRelease(string); + } + } + } + + charStr = CFStringCreateWithCharacters(kCFAllocatorDefault, &character, 1); + + /* Our values may be NULL (0), so we need to use this function. */ + if (!CFDictionaryGetValueIfPresent(charToCodeDict, charStr, + (const void **)&code)) { + code = UINT16_MAX; /* Error */ + } + + CFRelease(charStr); + return (MMKeyCode)code; +#elif defined(IS_WINDOWS) + return VkKeyScan(c); +#elif defined(USE_X11) + MMKeyCode code; + + char buf[2]; + buf[0] = c; + buf[1] = '\0'; + + code = XStringToKeysym(buf); + if (code == NoSymbol) { + /* Some special keys are apparently not handled properly by + * XStringToKeysym() on some systems, so search for them instead in our + * mapping table. */ + size_t i; + const size_t specialCharacterCount = + sizeof(XSpecialCharacterTable) / sizeof(XSpecialCharacterTable[0]); + for (i = 0; i < specialCharacterCount; ++i) { + if (c == XSpecialCharacterTable[i].name) { + code = XSpecialCharacterTable[i].code; + break; + } + } + } + + return code; +#endif +} + +#if defined(IS_MACOSX) + +CFStringRef createStringForKey(CGKeyCode keyCode) +{ + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + CFDataRef layoutData = + TISGetInputSourceProperty(currentKeyboard, + kTISPropertyUnicodeKeyLayoutData); + const UCKeyboardLayout *keyboardLayout = + (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); + + UInt32 keysDown = 0; + UniChar chars[4]; + UniCharCount realLength; + + UCKeyTranslate(keyboardLayout, + keyCode, + kUCKeyActionDisplay, + 0, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keysDown, + sizeof(chars) / sizeof(chars[0]), + &realLength, + chars); + CFRelease(currentKeyboard); + + return CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1); +} + +#endif diff --git a/key/keypress.h b/key/keypress.h new file mode 100644 index 0000000..2f0719e --- /dev/null +++ b/key/keypress.h @@ -0,0 +1,85 @@ +#pragma once +#ifndef KEYPRESS_H +#define KEYPRESS_H + +#include "../base/os.h" +#include "keycode.h" + +#if defined(_MSC_VER) + #include "../base/ms_stdbool.h" +#else + #include +#endif +#ifdef __cplusplus +extern "C" +{ +#endif +#if defined(IS_MACOSX) + + typedef enum { + MOD_NONE = 0, + MOD_META = kCGEventFlagMaskCommand, + MOD_ALT = kCGEventFlagMaskAlternate, + MOD_CONTROL = kCGEventFlagMaskControl, + MOD_SHIFT = kCGEventFlagMaskShift + } MMKeyFlags; + +#elif defined(USE_X11) + + enum _MMKeyFlags { + MOD_NONE = 0, + MOD_META = Mod4Mask, + MOD_ALT = Mod1Mask, + MOD_CONTROL = ControlMask, + MOD_SHIFT = ShiftMask + }; + + typedef unsigned int MMKeyFlags; + +#elif defined(IS_WINDOWS) + + enum _MMKeyFlags { + MOD_NONE = 0, + /* These are already defined by the Win32 API */ + /* MOD_ALT = 0, + MOD_CONTROL = 0, + MOD_SHIFT = 0, */ + MOD_META = MOD_WIN + }; + + typedef unsigned int MMKeyFlags; + +#endif + +#if defined(IS_WINDOWS) +/* Send win32 key event for given key. */ +void win32KeyEvent(int key, MMKeyFlags flags); +#endif + +/* Toggles the given key down or up. */ +void toggleKeyCode(MMKeyCode code, const bool down, MMKeyFlags flags); + +/* Toggles the key down and then up. */ +void tapKeyCode(MMKeyCode code, MMKeyFlags flags); + +/* Toggles the key corresponding to the given UTF character up or down. */ +void toggleKey(char c, const bool down, MMKeyFlags flags); +void tapKey(char c, MMKeyFlags flags); + +/* Sends a UTF-8 string without modifiers. */ +void typeString(const char *str); + +/* Macro to convert WPM to CPM integers. + * (the average English word length is 5.1 characters.) */ +#define WPM_TO_CPM(WPM) (unsigned)(5.1 * WPM) + +/* Sends a string with partially random delays between each letter. Note that + * deadbeef_srand() must be called before this function if you actually want + * randomness. */ +void typeStringDelayed(const char *str, const unsigned cpm); + +#ifdef __cplusplus +} +#endif + +#endif /* KEYPRESS_H */ diff --git a/key/keypress_init.h b/key/keypress_init.h new file mode 100644 index 0000000..7f259e0 --- /dev/null +++ b/key/keypress_init.h @@ -0,0 +1,245 @@ +#include "keypress.h" +// #include "../base/deadbeef_rand_init.h" +#include "../base/microsleep.h" + +#include /* For isupper() */ + +#if defined(IS_MACOSX) + #include + #import + #import +#elif defined(USE_X11) + #include + // #include "../base/xdisplay_init.h" +#endif + +/* Convenience wrappers around ugly APIs. */ +#if defined(IS_WINDOWS) + #define WIN32_KEY_EVENT_WAIT(key, flags) \ + (win32KeyEvent(key, flags), Sleep(DEADBEEF_RANDRANGE(0, 63))) +#elif defined(USE_X11) + #define X_KEY_EVENT(display, key, is_press) \ + (XTestFakeKeyEvent(display, \ + XKeysymToKeycode(display, key), \ + is_press, CurrentTime), \ + XFlush(display)) + #define X_KEY_EVENT_WAIT(display, key, is_press) \ + (X_KEY_EVENT(display, key, is_press), \ + microsleep(DEADBEEF_UNIFORM(0.0, 62.5))) +#endif + +#if defined(IS_MACOSX) +static io_connect_t _getAuxiliaryKeyDriver(void) +{ + static mach_port_t sEventDrvrRef = 0; + mach_port_t masterPort, service, iter; + kern_return_t kr; + + if (!sEventDrvrRef) { + kr = IOMasterPort( bootstrap_port, &masterPort ); + assert(KERN_SUCCESS == kr); + kr = IOServiceGetMatchingServices(masterPort, IOServiceMatching( kIOHIDSystemClass), &iter ); + assert(KERN_SUCCESS == kr); + service = IOIteratorNext( iter ); + assert(service); + kr = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &sEventDrvrRef ); + assert(KERN_SUCCESS == kr); + IOObjectRelease(service); + IOObjectRelease(iter); + } + return sEventDrvrRef; +} +#endif + +#if defined(IS_WINDOWS) +void win32KeyEvent(int key, MMKeyFlags flags) +{ + int scan = MapVirtualKey(key & 0xff, MAPVK_VK_TO_VSC); + + /* Set the scan code for extended keys */ + switch (key) + { + case VK_RCONTROL: + case VK_SNAPSHOT: /* Print Screen */ + case VK_RMENU: /* Right Alt / Alt Gr */ + case VK_PAUSE: /* Pause / Break */ + case VK_HOME: + case VK_UP: + case VK_PRIOR: /* Page up */ + case VK_LEFT: + case VK_RIGHT: + case VK_END: + case VK_DOWN: + case VK_NEXT: /* 'Page Down' */ + case VK_INSERT: + case VK_DELETE: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: /* Application */ + case VK_VOLUME_MUTE: + case VK_VOLUME_DOWN: + case VK_VOLUME_UP: + case VK_MEDIA_NEXT_TRACK: + case VK_MEDIA_PREV_TRACK: + case VK_MEDIA_STOP: + case VK_MEDIA_PLAY_PAUSE: + case VK_BROWSER_BACK: + case VK_BROWSER_FORWARD: + case VK_BROWSER_REFRESH: + case VK_BROWSER_STOP: + case VK_BROWSER_SEARCH: + case VK_BROWSER_FAVORITES: + case VK_BROWSER_HOME: + case VK_LAUNCH_MAIL: + { + flags |= KEYEVENTF_EXTENDEDKEY; + break; + } + } + + /* Set the scan code for keyup */ + if ( flags & KEYEVENTF_KEYUP ) { + scan |= 0x80; + } + + keybd_event(key, scan, flags, 0); +} +#endif + +void toggleKeyCode(MMKeyCode code, const bool down, MMKeyFlags flags) +{ +#if defined(IS_MACOSX) + /* The media keys all have 1000 added to them to help us detect them. */ + if (code >= 1000) { + code = code - 1000; /* Get the real keycode. */ + NXEventData event; + kern_return_t kr; + IOGPoint loc = { 0, 0 }; + UInt32 evtInfo = code << 16 | (down?NX_KEYDOWN:NX_KEYUP) << 8; + bzero(&event, sizeof(NXEventData)); + event.compound.subType = NX_SUBTYPE_AUX_CONTROL_BUTTONS; + event.compound.misc.L[0] = evtInfo; + kr = IOHIDPostEvent( _getAuxiliaryKeyDriver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE ); + assert( KERN_SUCCESS == kr ); + } else { + CGEventRef keyEvent = CGEventCreateKeyboardEvent(NULL, + (CGKeyCode)code, down); + assert(keyEvent != NULL); + + CGEventSetType(keyEvent, down ? kCGEventKeyDown : kCGEventKeyUp); + CGEventSetFlags(keyEvent, (int) flags); + // CGEventSetFlags(keyEvent, 0); + // CGEventSetFlags(keyEvent, kCGEventFlagMaskShift | kCGEventFlagMaskCommand); + CGEventPost(kCGSessionEventTap, keyEvent); + CFRelease(keyEvent); + } +#elif defined(IS_WINDOWS) + const DWORD dwFlags = down ? 0 : KEYEVENTF_KEYUP; + + /* Parse modifier keys. */ + if (flags & MOD_META) WIN32_KEY_EVENT_WAIT(K_META, dwFlags); + if (flags & MOD_ALT) WIN32_KEY_EVENT_WAIT(K_ALT, dwFlags); + if (flags & MOD_CONTROL) WIN32_KEY_EVENT_WAIT(K_CONTROL, dwFlags); + if (flags & MOD_SHIFT) WIN32_KEY_EVENT_WAIT(K_SHIFT, dwFlags); + + win32KeyEvent(code, dwFlags); +#elif defined(USE_X11) + Display *display = XGetMainDisplay(); + const Bool is_press = down ? True : False; /* Just to be safe. */ + + /* Parse modifier keys. */ + if (flags & MOD_META) X_KEY_EVENT_WAIT(display, K_META, is_press); + if (flags & MOD_ALT) X_KEY_EVENT_WAIT(display, K_ALT, is_press); + if (flags & MOD_CONTROL) X_KEY_EVENT_WAIT(display, K_CONTROL, is_press); + if (flags & MOD_SHIFT) X_KEY_EVENT_WAIT(display, K_SHIFT, is_press); + + X_KEY_EVENT(display, code, is_press); +#endif +} + +void tapKeyCode(MMKeyCode code, MMKeyFlags flags) +{ + toggleKeyCode(code, true, flags); + toggleKeyCode(code, false, flags); +} + +void toggleKey(char c, const bool down, MMKeyFlags flags) +{ + MMKeyCode keyCode = keyCodeForChar(c); + + //Prevent unused variable warning for Mac and Linux. +#if defined(IS_WINDOWS) + int modifiers; +#endif + + if (isupper(c) && !(flags & MOD_SHIFT)) { + flags |= MOD_SHIFT; /* Not sure if this is safe for all layouts. */ + } + +#if defined(IS_WINDOWS) + modifiers = keyCode >> 8; // Pull out modifers. + if ((modifiers & 1) != 0) flags |= MOD_SHIFT; // Uptdate flags from keycode modifiers. + if ((modifiers & 2) != 0) flags |= MOD_CONTROL; + if ((modifiers & 4) != 0) flags |= MOD_ALT; + keyCode = keyCode & 0xff; // Mask out modifiers. +#endif + toggleKeyCode(keyCode, down, flags); +} + +void tapKey(char c, MMKeyFlags flags) +{ + toggleKey(c, true, flags); + toggleKey(c, false, flags); +} + +#if defined(IS_MACOSX) +void toggleUniKey(char c, const bool down) +{ + /* This function relies on the convenient + * CGEventKeyboardSetUnicodeString(), which allows us to not have to + * convert characters to a keycode, but does not support adding modifier + * flags. It is therefore only used in typeString() and typeStringDelayed() + * -- if you need modifier keys, use the above functions instead. */ + UniChar ch = (UniChar)c; /* Convert to unsigned char */ + + CGEventRef keyEvent = CGEventCreateKeyboardEvent(NULL, 0, down); + if (keyEvent == NULL) { + fputs("Could not create keyboard event.\n", stderr); + return; + } + + CGEventKeyboardSetUnicodeString(keyEvent, 1, &ch); + + CGEventPost(kCGSessionEventTap, keyEvent); + CFRelease(keyEvent); +} +#else + #define toggleUniKey(c, down) toggleKey(c, down, MOD_NONE) +#endif + +static void tapUniKey(char c) +{ + toggleUniKey(c, true); + toggleUniKey(c, false); +} + +void typeString(const char *str) +{ + while (*str != '\0') { + tapUniKey(*str++); + } +} + +void typeStringDelayed(const char *str, const unsigned cpm) +{ + /* Characters per second */ + const double cps = (double)cpm / 60.0; + + /* Average milli-seconds per character */ + const double mspc = (cps == 0.0) ? 0.0 : 1000.0 / cps; + + while (*str != '\0') { + tapUniKey(*str++); + microsleep(mspc + (DEADBEEF_UNIFORM(0.0, 62.5))); + } +} diff --git a/mouse/goMouse.h b/mouse/goMouse.h new file mode 100644 index 0000000..17000e3 --- /dev/null +++ b/mouse/goMouse.h @@ -0,0 +1,121 @@ +#include "../base/types.h" +#include "mouse_init.h" + +//Global delays. +int mouseDelay = 10; +// int keyboardDelay = 10; + + +// int CheckMouseButton(const char * const b, MMMouseButton * const button){ +// if (!button) return -1; + +// if (strcmp(b, "left") == 0) +// { +// *button = LEFT_BUTTON; +// } +// else if (strcmp(b, "right") == 0) +// { +// *button = RIGHT_BUTTON; +// } +// else if (strcmp(b, "middle") == 0) +// { +// *button = CENTER_BUTTON; +// } +// else +// { +// return -2; +// } + +// return 0; +// } + +int amoveMouse(int x, int y){ + MMPoint point; + //int x =103; + //int y =104; + point = MMPointMake(x, y); + moveMouse(point); + + return 0; +} + +int adragMouse(int x, int y){ + // const size_t x=10; + // const size_t y=20; + MMMouseButton button = LEFT_BUTTON; + + MMPoint point; + point = MMPointMake(x, y); + dragMouse(point, button); + microsleep(mouseDelay); + + // printf("%s\n","gyp-----"); + return 0; +} + +int amoveMouseSmooth(int x, int y){ + MMPoint point; + point = MMPointMake(x, y); + smoothlyMoveMouse(point); + microsleep(mouseDelay); + + return 0; + +} + +MMPoint agetMousePos(){ + MMPoint pos = getMousePos(); + + //Return object with .x and .y. + // printf("%zu\n%zu\n", pos.x, pos.y ); + return pos; +} + +int amouseClick(){ + MMMouseButton button = LEFT_BUTTON; + bool doubleC = false; + + if (!doubleC){ + clickMouse(button); + }else{ + doubleClick(button); + } + + microsleep(mouseDelay); + + return 0; +} + +int amouseToggle(){ + MMMouseButton button = LEFT_BUTTON; + bool down = false; + + return 0; +} + +int asetMouseDelay(int val){ + // int val=10; + mouseDelay = val; + + return 0; +} + +int ascrollMouse(int scrollMagnitude,char *s){ + // int scrollMagnitude = 20; + + MMMouseWheelDirection scrollDirection; + + if (strcmp(s, "up") == 0){ + scrollDirection = DIRECTION_UP; + }else if (strcmp(s, "down") == 0){ + scrollDirection = DIRECTION_DOWN; + }else{ + // return "Invalid scroll direction specified."; + return 1; + } + + scrollMouse(scrollMagnitude, scrollDirection); + microsleep(mouseDelay); + + return 0; +} diff --git a/mouse/mouse.h b/mouse/mouse.h new file mode 100644 index 0000000..a32507d --- /dev/null +++ b/mouse/mouse.h @@ -0,0 +1,103 @@ +#pragma once +#ifndef MOUSE_H +#define MOUSE_H + +#include "../base/os.h" +#include "../base/types.h" + +#if defined(_MSC_VER) + #include "../base/ms_stdbool.h" +#else + #include +#endif +#ifdef __cplusplus +// #ifdefined(__cplusplus)||defined(c_plusplus) +extern "C" +{ +#endif +#if defined(IS_MACOSX) + + // #include + #include + // #include + + typedef enum { + LEFT_BUTTON = kCGMouseButtonLeft, + RIGHT_BUTTON = kCGMouseButtonRight, + CENTER_BUTTON = kCGMouseButtonCenter + } MMMouseButton; + +#elif defined(USE_X11) + + enum _MMMouseButton { + LEFT_BUTTON = 1, + CENTER_BUTTON = 2, + RIGHT_BUTTON = 3 + }; + typedef unsigned int MMMouseButton; + +#elif defined(IS_WINDOWS) + + enum _MMMouseButton { + LEFT_BUTTON = 1, + CENTER_BUTTON = 2, + RIGHT_BUTTON = 3 + }; + typedef unsigned int MMMouseButton; + +#else + #error "No mouse button constants set for platform" +#endif + +#define MMMouseButtonIsValid(button) \ + (button == LEFT_BUTTON || button == RIGHT_BUTTON || \ + button == CENTER_BUTTON) + +enum __MMMouseWheelDirection +{ + DIRECTION_DOWN = -1, + DIRECTION_UP = 1 +}; +typedef int MMMouseWheelDirection; + +/* Immediately moves the mouse to the given point on-screen. + * It is up to the caller to ensure that this point is within the + * screen boundaries. */ +void moveMouse(MMPoint point); + +/* Like moveMouse, moves the mouse to the given point on-screen, but marks + * the event as the mouse being dragged on platforms where it is supported. + * It is up to the caller to ensure that this point is within the screen + * boundaries. */ +void dragMouse(MMPoint point, const MMMouseButton button); + +/* Smoothly moves the mouse from the current position to the given point. + * deadbeef_srand() should be called before using this function. + * + * Returns false if unsuccessful (i.e. a point was hit that is outside of the + * screen boundaries), or true if successful. */ +bool smoothlyMoveMouse(MMPoint point); + +/* Returns the coordinates of the mouse on the current screen. */ +MMPoint getMousePos(void); + +/* Holds down or releases the mouse with the given button in the current + * position. */ +void toggleMouse(bool down, MMMouseButton button); + +/* Clicks the mouse with the given button in the current position. */ +void clickMouse(MMMouseButton button); + +/* Double clicks the mouse with the given button. */ +void doubleClick(MMMouseButton button); + +/* Scrolls the mouse in the stated direction. + * TODO: Add a smoothly scroll mouse next. */ +void scrollMouse(int scrollMagnitude, MMMouseWheelDirection scrollDirection); + +#endif /* MOUSE_H */ + +//#ifdefined(__cplusplus)||defined(c_plusplus) +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/mouse/mouse_init.h b/mouse/mouse_init.h new file mode 100644 index 0000000..4c62daa --- /dev/null +++ b/mouse/mouse_init.h @@ -0,0 +1,365 @@ +#include "mouse.h" +// #include "../screen/screen.h" +// #include "../screen/screen_init.h" +#include "../base/deadbeef_rand_init.h" +// #include "../deadbeef_rand.h" +#include "../base/microsleep.h" + +#include /* For floor() */ + +#if defined(IS_MACOSX) + // #include + #include + // #include +#elif defined(USE_X11) + #include + #include + #include + // #include "../base/xdisplay_init.h" +#endif + +#if !defined(M_SQRT2) + #define M_SQRT2 1.4142135623730950488016887 /* Fix for MSVC. */ +#endif + +/* Some convenience macros for converting our enums to the system API types. */ +#if defined(IS_MACOSX) + +#define MMMouseToCGEventType(down, button) \ + (down ? MMMouseDownToCGEventType(button) : MMMouseUpToCGEventType(button)) + +#define MMMouseDownToCGEventType(button) \ + ((button) == (LEFT_BUTTON) ? kCGEventLeftMouseDown \ + : ((button) == RIGHT_BUTTON ? kCGEventRightMouseDown \ + : kCGEventOtherMouseDown)) + +#define MMMouseUpToCGEventType(button) \ + ((button) == LEFT_BUTTON ? kCGEventLeftMouseUp \ + : ((button) == RIGHT_BUTTON ? kCGEventRightMouseUp \ + : kCGEventOtherMouseUp)) + +#define MMMouseDragToCGEventType(button) \ + ((button) == LEFT_BUTTON ? kCGEventLeftMouseDragged \ + : ((button) == RIGHT_BUTTON ? kCGEventRightMouseDragged \ + : kCGEventOtherMouseDragged)) + +#elif defined(IS_WINDOWS) + +#define MMMouseToMEventF(down, button) \ + (down ? MMMouseDownToMEventF(button) : MMMouseUpToMEventF(button)) + +#define MMMouseUpToMEventF(button) \ + ((button) == LEFT_BUTTON ? MOUSEEVENTF_LEFTUP \ + : ((button) == RIGHT_BUTTON ? MOUSEEVENTF_RIGHTUP \ + : MOUSEEVENTF_MIDDLEUP)) + +#define MMMouseDownToMEventF(button) \ + ((button) == LEFT_BUTTON ? MOUSEEVENTF_LEFTDOWN \ + : ((button) == RIGHT_BUTTON ? MOUSEEVENTF_RIGHTDOWN \ + : MOUSEEVENTF_MIDDLEDOWN)) + +#endif + +#if defined(IS_MACOSX) +/** + * Calculate the delta for a mouse move and add them to the event. + * @param event The mouse move event (by ref). + * @param point The new mouse x and y. + */ +void calculateDeltas(CGEventRef *event, MMPoint point) +{ + /** + * The next few lines are a workaround for games not detecting mouse moves. + * See this issue for more information: + * https://github.com/octalmage/robotjs/issues/159 + */ + CGEventRef get = CGEventCreate(NULL); + CGPoint mouse = CGEventGetLocation(get); + + // Calculate the deltas. + int64_t deltaX = point.x - mouse.x; + int64_t deltaY = point.y - mouse.y; + + CGEventSetIntegerValueField(*event, kCGMouseEventDeltaX, deltaX); + CGEventSetIntegerValueField(*event, kCGMouseEventDeltaY, deltaY); + + CFRelease(get); +} +#endif + + +/** + * Move the mouse to a specific point. + * @param point The coordinates to move the mouse to (x, y). + */ +void moveMouse(MMPoint point) +{ +#if defined(IS_MACOSX) + CGEventRef move = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, + CGPointFromMMPoint(point), + kCGMouseButtonLeft); + + calculateDeltas(&move, point); + + CGEventPost(kCGSessionEventTap, move); + CFRelease(move); +#elif defined(USE_X11) + Display *display = XGetMainDisplay(); + XWarpPointer(display, None, DefaultRootWindow(display), + 0, 0, 0, 0, point.x, point.y); + XFlush(display); +#elif defined(IS_WINDOWS) + //Mouse motion is now done using SendInput with MOUSEINPUT. We use Absolute mouse positioning + #define MOUSE_COORD_TO_ABS(coord, width_or_height) (((65536 * coord) / width_or_height) + (coord < 0 ? -1 : 1)) + point.x = MOUSE_COORD_TO_ABS(point.x, GetSystemMetrics(SM_CXSCREEN)); + point.y = MOUSE_COORD_TO_ABS(point.y, GetSystemMetrics(SM_CYSCREEN)); + INPUT mouseInput; + mouseInput.type = INPUT_MOUSE; + mouseInput.mi.dx = point.x; + mouseInput.mi.dy = point.y; + mouseInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + mouseInput.mi.time = 0; //System will provide the timestamp + mouseInput.mi.dwExtraInfo = 0; + mouseInput.mi.mouseData = 0; + SendInput(1, &mouseInput, sizeof(mouseInput)); + +#endif +} + +void dragMouse(MMPoint point, const MMMouseButton button) +{ +#if defined(IS_MACOSX) + const CGEventType dragType = MMMouseDragToCGEventType(button); + CGEventRef drag = CGEventCreateMouseEvent(NULL, dragType, + CGPointFromMMPoint(point), + (CGMouseButton)button); + calculateDeltas(&drag, point); + + CGEventPost(kCGSessionEventTap, drag); + CFRelease(drag); +#else + moveMouse(point); +#endif +} + +MMPoint getMousePos() +{ +#if defined(IS_MACOSX) + CGEventRef event = CGEventCreate(NULL); + CGPoint point = CGEventGetLocation(event); + CFRelease(event); + + return MMPointFromCGPoint(point); +#elif defined(USE_X11) + int x, y; /* This is all we care about. Seriously. */ + Window garb1, garb2; /* Why you can't specify NULL as a parameter */ + int garb_x, garb_y; /* is beyond me. */ + unsigned int more_garbage; + + Display *display = XGetMainDisplay(); + XQueryPointer(display, XDefaultRootWindow(display), &garb1, &garb2, + &x, &y, &garb_x, &garb_y, &more_garbage); + + return MMPointMake(x, y); +#elif defined(IS_WINDOWS) + POINT point; + GetCursorPos(&point); + + return MMPointFromPOINT(point); +#endif +} + +/** + * Press down a button, or release it. + * @param down True for down, false for up. + * @param button The button to press down or release. + */ +void toggleMouse(bool down, MMMouseButton button) +{ +#if defined(IS_MACOSX) + const CGPoint currentPos = CGPointFromMMPoint(getMousePos()); + const CGEventType mouseType = MMMouseToCGEventType(down, button); + CGEventRef event = CGEventCreateMouseEvent(NULL, + mouseType, + currentPos, + (CGMouseButton)button); + CGEventPost(kCGSessionEventTap, event); + CFRelease(event); +#elif defined(USE_X11) + Display *display = XGetMainDisplay(); + XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime); + XFlush(display); +#elif defined(IS_WINDOWS) + mouse_event(MMMouseToMEventF(down, button), 0, 0, 0, 0); +#endif +} + +void clickMouse(MMMouseButton button) +{ + toggleMouse(true, button); + toggleMouse(false, button); +} + +/** + * Special function for sending double clicks, needed for Mac OS X. + * @param button Button to click. + */ +void doubleClick(MMMouseButton button) +{ + +#if defined(IS_MACOSX) + + /* Double click for Mac. */ + const CGPoint currentPos = CGPointFromMMPoint(getMousePos()); + const CGEventType mouseTypeDown = MMMouseToCGEventType(true, button); + const CGEventType mouseTypeUP = MMMouseToCGEventType(false, button); + + CGEventRef event = CGEventCreateMouseEvent(NULL, mouseTypeDown, currentPos, kCGMouseButtonLeft); + + /* Set event to double click. */ + CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2); + + CGEventPost(kCGHIDEventTap, event); + + CGEventSetType(event, mouseTypeUP); + CGEventPost(kCGHIDEventTap, event); + + CFRelease(event); + +#else + + /* Double click for everything else. */ + clickMouse(button); + microsleep(200); + clickMouse(button); + +#endif +} + +/** + * Function used to scroll the screen in the required direction. + * This uses the magnitude to scroll the required amount in the direction. + * TODO Requires further fine tuning based on the requirements. + */ +void scrollMouse(int scrollMagnitude, MMMouseWheelDirection scrollDirection) +{ + #if defined(IS_WINDOWS) + // Fix for #97 https://github.com/octalmage/robotjs/issues/97, + // C89 needs variables declared on top of functions (mouseScrollInput) + INPUT mouseScrollInput; + #endif + + /* Direction should only be considered based on the scrollDirection. This + * Should not interfere. */ + int cleanScrollMagnitude = abs(scrollMagnitude); + if (!(scrollDirection == DIRECTION_UP || scrollDirection == DIRECTION_DOWN)) + { + return; + } + + /* Set up the OS specific solution */ + #if defined(__APPLE__) + + CGWheelCount wheel = 1; + CGEventRef event; + + /* Make scroll magnitude negative if we're scrolling down. */ + cleanScrollMagnitude = cleanScrollMagnitude * scrollDirection; + + event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, wheel, cleanScrollMagnitude, 0); + CGEventPost(kCGHIDEventTap, event); + + #elif defined(USE_X11) + + int x; + int dir = 4; /* Button 4 is up, 5 is down. */ + Display *display = XGetMainDisplay(); + + if (scrollDirection == DIRECTION_DOWN) + { + dir = 5; + } + + for (x = 0; x < cleanScrollMagnitude; x++) + { + XTestFakeButtonEvent(display, dir, 1, CurrentTime); + XTestFakeButtonEvent(display, dir, 0, CurrentTime); + } + + XFlush(display); + + #elif defined(IS_WINDOWS) + + mouseScrollInput.type = INPUT_MOUSE; + mouseScrollInput.mi.dx = 0; + mouseScrollInput.mi.dy = 0; + mouseScrollInput.mi.dwFlags = MOUSEEVENTF_WHEEL; + mouseScrollInput.mi.time = 0; + mouseScrollInput.mi.dwExtraInfo = 0; + mouseScrollInput.mi.mouseData = WHEEL_DELTA * scrollDirection * cleanScrollMagnitude; + + SendInput(1, &mouseScrollInput, sizeof(mouseScrollInput)); + + #endif +} + +/* + * A crude, fast hypot() approximation to get around the fact that hypot() is + * not a standard ANSI C function. + * + * It is not particularly accurate but that does not matter for our use case. + * + * Taken from this StackOverflow answer: + * http://stackoverflow.com/questions/3506404/fast-hypotenuse-algorithm-for-embedded-processor#3507882 + * + */ +static double crude_hypot(double x, double y) +{ + double big = fabs(x); /* max(|x|, |y|) */ + double small = fabs(y); /* min(|x|, |y|) */ + + if (big > small) { + double temp = big; + big = small; + small = temp; + } + + return ((M_SQRT2 - 1.0) * small) + big; +} + +bool smoothlyMoveMouse(MMPoint endPoint) +{ + MMPoint pos = getMousePos(); + MMSize screenSize = getMainDisplaySize(); + double velo_x = 0.0, velo_y = 0.0; + double distance; + + while ((distance = crude_hypot((double)pos.x - endPoint.x, + (double)pos.y - endPoint.y)) > 1.0) { + double gravity = DEADBEEF_UNIFORM(5.0, 500.0); + double veloDistance; + velo_x += (gravity * ((double)endPoint.x - pos.x)) / distance; + velo_y += (gravity * ((double)endPoint.y - pos.y)) / distance; + + /* Normalize velocity to get a unit vector of length 1. */ + veloDistance = crude_hypot(velo_x, velo_y); + velo_x /= veloDistance; + velo_y /= veloDistance; + + pos.x += floor(velo_x + 0.5); + pos.y += floor(velo_y + 0.5); + + /* Make sure we are in the screen boundaries! + * (Strange things will happen if we are not.) */ + if (pos.x >= screenSize.width || pos.y >= screenSize.height) { + return false; + } + + moveMouse(pos); + + /* Wait 1 - 3 milliseconds. */ + microsleep(DEADBEEF_UNIFORM(1.0, 3.0)); + } + + return true; +} diff --git a/robotgo.go b/robotgo.go new file mode 100644 index 0000000..8ba5bfb --- /dev/null +++ b/robotgo.go @@ -0,0 +1,148 @@ +package robotgo + +/* +//#if defined(IS_MACOSX) + #cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations + #cgo darwin LDFLAGS: -framework Cocoa -framework OpenGL -framework IOKit -framework Carbon -framework CoreFoundation +//#elif defined(USE_X11) + #cgo linux CFLAGS:-I/usr/src + #cgo linux LDFLAGS:-L/usr/src -lpng -lz -lX11 -lXtst -lm +//#endif + #cgo windows LDFLAGS: -lgdi32 -luser32 +//#include +#include "screen/goScreen.h" +#include "mouse/goMouse.h" +#include "key/goKey.h" +*/ +import "C" + +import ( + . "fmt" + // "runtime" + // "syscall" +) + +/* + __ __ +| \/ | ___ _ _ ___ ___ +| |\/| |/ _ \| | | / __|/ _ \ +| | | | (_) | |_| \__ \ __/ +|_| |_|\___/ \__,_|___/\___| + +*/ + +type MPoint struct { + x int + y int +} + +//C.size_t int +func MoveMouse(x, y C.int) { + C.amoveMouse(x, y) +} + +func DragMouse(x, y C.int) { + C.adragMouse(x, y) +} + +func MoveMouseSmooth(x, y C.int) { + C.amoveMouseSmooth(x, y) +} + +func GetMousePos() (C.size_t, C.size_t) { + pos := C.agetMousePos() + // Println("pos:###", pos, pos.x, pos.y) + return pos.x, pos.y +} + +func MouseClick() { + C.amouseClick() +} + +func MouseToggle() { + C.amouseToggle() +} + +func SetMouseDelay(x C.int) { + C.asetMouseDelay(x) +} + +func ScrollMouse(x C.int, y string) { + z := C.CString(y) + C.ascrollMouse(x, z) +} + +/* + _ __ _ _ +| |/ /___ _ _| |__ ___ __ _ _ __ __| | +| ' // _ \ | | | '_ \ / _ \ / _` | '__/ _` | +| . \ __/ |_| | |_) | (_) | (_| | | | (_| | +|_|\_\___|\__, |_.__/ \___/ \__,_|_| \__,_| + |___/ +*/ + +func KeyTap(x string) { + z := C.CString(x) + // Println("----") + C.akeyTap(z) +} + +func KeyToggle(x string, y string) { + cx := C.CString(x) + cy := C.CString(y) + str := C.akeyToggle(cx, cy) + Println(str) +} + +func TypeString(x string) { + cx := C.CString(x) + C.atypeString(cx) +} + +func TypeStringDelayed(x string, y C.size_t) { + cx := C.CString(x) + C.atypeStringDelayed(cx, y) +} + +func SetKeyboardDelay(x C.size_t) { + C.asetKeyboardDelay(x) +} + +/* + ____ + / ___| ___ _ __ ___ ___ _ __ + \___ \ / __| '__/ _ \/ _ \ '_ \ + ___) | (__| | | __/ __/ | | | + |____/ \___|_| \___|\___|_| |_| + +*/ + +func GetPixelColor(x, y C.size_t) string { + color := C.agetPixelColor(x, y) + gcolor := C.GoString(color) + return gcolor +} + +func GetScreenSize() (C.size_t, C.size_t) { + size := C.agetScreenSize() + // Println("...", size, size.width) + return size.width, size.height +} + +func GetXDisplayName() string { + name := C.agetXDisplayName() + gname := C.GoString(name) + return gname +} + +func SetXDisplayName(name string) string { + cname := C.CString(name) + str := C.asetXDisplayName(cname) + gstr := C.GoString(str) + return gstr +} + +func CaptureScreen(x, y, w, h C.int) { + bit := C.acaptureScreen(x, y, w, h) + Println("...", bit) +} diff --git a/screen/goScreen.h b/screen/goScreen.h new file mode 100644 index 0000000..134bcfb --- /dev/null +++ b/screen/goScreen.h @@ -0,0 +1,86 @@ +#include "../base/types.h" +#include "screengrab_init.h" +#include "screen_init.h" +// #include "../MMBitmap_init.h" + +void padHex(MMRGBHex color, char* hex){ + //Length needs to be 7 because snprintf includes a terminating null. + //Use %06x to pad hex value with leading 0s. + snprintf(hex, 7, "%06x", color); +} + + +char* agetPixelColor(size_t x, size_t y){ + MMBitmapRef bitmap; + MMRGBHex color; + + if (!pointVisibleOnMainDisplay(MMPointMake(x, y))){ + // return 1; + return "screen's dimensions."; + } + + bitmap = copyMMBitmapFromDisplayInRect(MMRectMake(x, y, 1, 1)); + // bitmap = MMRectMake(x, y, 1, 1); + + color = MMRGBHexAtPoint(bitmap, 0, 0); + + char hex[7]; + + padHex(color, hex); + + destroyMMBitmap(bitmap); + + // printf("%s\n", hex); + + // return 0; + + char* s=(char*)calloc(100,sizeof(char*)); + if(s)strcpy(s,hex); + + return s; +} + +MMSize agetScreenSize(){ + //Get display size. + MMSize displaySize = getMainDisplaySize(); + return displaySize; +} + +char* agetXDisplayName(){ + #if defined(USE_X11) + const char* display = getXDisplay(); + char* sd=(char*)calloc(100,sizeof(char*)); + if(sd)strcpy(sd,display); + + return sd; + #else + return "getXDisplayName is only supported on Linux"; + #endif +} + +char* asetXDisplayName(char* name){ + #if defined(USE_X11) + setXDisplay(name); + return "success"; + #else + return "setXDisplayName is only supported on Linux"; + #endif +} + +MMBitmapRef acaptureScreen(int x,int y,int w,int h){ + // if (){ + // x = 0; + // y = 0; + + // //Get screen size. + // MMSize displaySize = getMainDisplaySize(); + // w = displaySize.width; + // h = displaySize.height; + // } + + MMBitmapRef bitmap = copyMMBitmapFromDisplayInRect(MMRectMake(x, y, w, h)); + // printf("%s\n", bitmap); + + return bitmap; +} + diff --git a/screen/screen.h b/screen/screen.h new file mode 100644 index 0000000..dc00bf4 --- /dev/null +++ b/screen/screen.h @@ -0,0 +1,29 @@ +#pragma once +#ifndef SCREEN_H +#define SCREEN_H + +#include "../base/types.h" + +#if defined(_MSC_VER) + #include "../base/ms_stdbool.h" +#else + #include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Returns the size of the main display. */ +MMSize getMainDisplaySize(void); + +/* Convenience function that returns whether the given point is in the bounds + * of the main screen. */ +bool pointVisibleOnMainDisplay(MMPoint point); + +#ifdef __cplusplus +} +#endif + +#endif /* SCREEN_H */ diff --git a/screen/screen_init.h b/screen/screen_init.h new file mode 100644 index 0000000..4f6d704 --- /dev/null +++ b/screen/screen_init.h @@ -0,0 +1,33 @@ +#include "screen.h" +#include "../base/os.h" + +#if defined(IS_MACOSX) + #include +#elif defined(USE_X11) + #include + // #include "../base/xdisplay_init.h" +#endif + +MMSize getMainDisplaySize(void) +{ +#if defined(IS_MACOSX) + CGDirectDisplayID displayID = CGMainDisplayID(); + return MMSizeMake(CGDisplayPixelsWide(displayID), + CGDisplayPixelsHigh(displayID)); +#elif defined(USE_X11) + Display *display = XGetMainDisplay(); + const int screen = DefaultScreen(display); + + return MMSizeMake((size_t)DisplayWidth(display, screen), + (size_t)DisplayHeight(display, screen)); +#elif defined(IS_WINDOWS) + return MMSizeMake((size_t)GetSystemMetrics(SM_CXSCREEN), + (size_t)GetSystemMetrics(SM_CYSCREEN)); +#endif +} + +bool pointVisibleOnMainDisplay(MMPoint point) +{ + MMSize displaySize = getMainDisplaySize(); + return point.x < displaySize.width && point.y < displaySize.height; +} diff --git a/screen/screengrab.h b/screen/screengrab.h new file mode 100644 index 0000000..ce18628 --- /dev/null +++ b/screen/screengrab.h @@ -0,0 +1,21 @@ +#pragma once +#ifndef SCREENGRAB_H +#define SCREENGRAB_H + +#include "../base/types.h" +#include "../base/MMBitmap_init.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Returns a raw bitmap of screengrab of the display (to be destroyed()'d by + * caller), or NULL on error. */ +MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect); + +#ifdef __cplusplus +} +#endif + +#endif /* SCREENGRAB_H */ diff --git a/screen/screengrab_init.h b/screen/screengrab_init.h new file mode 100644 index 0000000..3ffc18b --- /dev/null +++ b/screen/screengrab_init.h @@ -0,0 +1,163 @@ +#include "screengrab.h" +#include "../base/bmp_io_init.h" +#include "../base/endian.h" +#include /* malloc() */ + +#if defined(IS_MACOSX) + #include + #include + #include +#elif defined(USE_X11) + #include + #include + #include "../base/xdisplay_init.h" +#elif defined(IS_WINDOWS) + // #include "windows.h" + // #include + #include +#endif + +MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect) +{ +#if defined(IS_MACOSX) + + size_t bytewidth; + uint8_t bitsPerPixel, bytesPerPixel; + //uint8_t *buffer; + + CGDirectDisplayID displayID = CGMainDisplayID(); + + //Replacement for CGDisplayBitsPerPixel. + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID); + size_t depth = 0; + + CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode); + if(CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + depth = 32; + else if(CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + depth = 16; + else if(CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + depth = 8; + + CGDisplayModeRelease(mode); + CFRelease(pixEnc); + + bitsPerPixel = (uint8_t) depth; + bytesPerPixel = bitsPerPixel / 8; + /* Align width to padding. */ + //bytewidth = ADD_PADDING(rect.size.width * bytesPerPixel); + bytewidth = rect.size.width * bytesPerPixel; + + /* Convert Quartz point to postscript point. */ + //rect.origin.y = CGDisplayPixelsHigh(displayID) - rect.origin.y - rect.size.height; + + CGImageRef image = CGDisplayCreateImageForRect(displayID, CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); + + // Request access to the raw pixel data via the image's DataProvider. + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef data = CGDataProviderCopyData(provider); + + size_t width, height; + width = CGImageGetWidth(image); + height = CGImageGetHeight(image); + size_t bpp = CGImageGetBitsPerPixel(image) / 8; + + uint8 *pixels = malloc(width * height * bpp); + memcpy(pixels, CFDataGetBytePtr(data), width * height * bpp); + CFRelease(data); + CGImageRelease(image); + + return createMMBitmap(pixels, rect.size.width, rect.size.height, bytewidth, + bitsPerPixel, bytesPerPixel); +#elif defined(USE_X11) + MMBitmapRef bitmap; + + Display *display = XOpenDisplay(NULL); + XImage *image = XGetImage(display, + XDefaultRootWindow(display), + (int)rect.origin.x, + (int)rect.origin.y, + (unsigned int)rect.size.width, + (unsigned int)rect.size.height, + AllPlanes, ZPixmap); + XCloseDisplay(display); + if (image == NULL) return NULL; + + bitmap = createMMBitmap((uint8_t *)image->data, + rect.size.width, + rect.size.height, + (size_t)image->bytes_per_line, + (uint8_t)image->bits_per_pixel, + (uint8_t)image->bits_per_pixel / 8); + image->data = NULL; /* Steal ownership of bitmap data so we don't have to + * copy it. */ + XDestroyImage(image); + + return bitmap; +#elif defined(IS_WINDOWS) + MMBitmapRef bitmap; + void *data; + HDC screen = NULL, screenMem = NULL; + HBITMAP dib; + BITMAPINFO bi; + + /* Initialize bitmap info. */ + bi.bmiHeader.biSize = sizeof(bi.bmiHeader); + bi.bmiHeader.biWidth = (long)rect.size.width; + bi.bmiHeader.biHeight = -(long)rect.size.height; /* Non-cartesian, please */ + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + bi.bmiHeader.biSizeImage = (DWORD)(4 * rect.size.width * rect.size.height); + bi.bmiHeader.biXPelsPerMeter = 0; + bi.bmiHeader.biYPelsPerMeter = 0; + bi.bmiHeader.biClrUsed = 0; + bi.bmiHeader.biClrImportant = 0; + + screen = GetDC(NULL); /* Get entire screen */ + if (screen == NULL) return NULL; + + /* Get screen data in display device context. */ + dib = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, &data, NULL, 0); + + /* Copy the data into a bitmap struct. */ + if ((screenMem = CreateCompatibleDC(screen)) == NULL || + SelectObject(screenMem, dib) == NULL || + !BitBlt(screenMem, + (int)0, + (int)0, + (int)rect.size.width, + (int)rect.size.height, + screen, + rect.origin.x, + rect.origin.y, + SRCCOPY)) { + + /* Error copying data. */ + ReleaseDC(NULL, screen); + DeleteObject(dib); + if (screenMem != NULL) DeleteDC(screenMem); + + return NULL; + } + + bitmap = createMMBitmap(NULL, + rect.size.width, + rect.size.height, + 4 * rect.size.width, + (uint8_t)bi.bmiHeader.biBitCount, + 4); + + /* Copy the data to our pixel buffer. */ + if (bitmap != NULL) { + bitmap->imageBuffer = malloc(bitmap->bytewidth * bitmap->height); + memcpy(bitmap->imageBuffer, data, bitmap->bytewidth * bitmap->height); + } + + ReleaseDC(NULL, screen); + DeleteObject(dib); + DeleteDC(screenMem); + + return bitmap; +#endif +} diff --git a/test/main.go b/test/main.go new file mode 100644 index 0000000..664b3ea --- /dev/null +++ b/test/main.go @@ -0,0 +1,29 @@ +package main + +import ( + . "fmt" + + "github.com/go-vgo/robotgo" +) + +func arobotgo() { + x, y := robotgo.GetMousePos() + Println("pos:", x, y) + + Println(robotgo.GetPixelColor(x, y)) + + color := robotgo.GetPixelColor(100, 200) + Println("color@@@", color) + + robotgo.TypeString("Hello World") + robotgo.KeyTap("enter") + // robotgo.KeyToggle("enter", "down") + robotgo.TypeString("en") + + // robotgo.MouseClick() + robotgo.ScrollMouse(10, "up") +} + +func main() { + arobotgo() +}